Django Custom User Model
Tags: django, python, tutorial, beginners
Published: 2020-02-05 20:49:55

Django ships with a built-in [User model](https://docs.djangoproject.com/en/3.0/ref/contrib/auth/#django.contrib.auth.models.User) for authentication, however the [official Django documentation](https://docs.djangoproject.com/en/3.0/topics/auth/customizing/#using-a-custom-user-model-when-starting-a-project) *highly recommends* using a custom user model for new projects. The reason is if you want to make *any* changes to the User model down the road--for example adding a date of birth field--using a custom user model from the beginning makes this quite easy. But if you do not, updating the default User model in an existing Django project is very, very challenging.

So **always use a custom user model for all new Django projects**. However the official documentation [example](https://docs.djangoproject.com/en/3.0/topics/auth/customizing/#a-full-example) is not actually what many Django experts recommend using. There is a far easier yet still powerful approach to starting off new Django projects with a custom user model which I'll demonstrate here.

If you're brand new to user authentication in Django, I recommend first reviewing [how to implement a regular login, logout, signup flow]({{ site.baseurl }}{% post_url 2017-09-05-django-user-authentication-tutorial-login-and-logout %}) in Django which covers the basics in detail.

## Setup

To start, create a new Django project from the command line. We need to do several things:

* create and navigate into a dedicated directory called `users` for our code
* install Django
* make a new Django project called `config`
* make a new app `users`
* start the local web server

Here are the commands to run:

```
$ cd ~/Desktop
$ mkdir users && cd users
$ pipenv install django==3.0.3
$ pipenv shell
(users) $ django-admin.py startproject config .
(users) $ python manage.py startapp users
(users) $ python manage.py runserver
```

Note that we **did not** run `migrate` to configure our database. It's important to wait until after we've created our new custom user model before doing so.

If you navigate to [http://127.0.0.1:8000](http://127.0.0.1:8000) you’ll see the Django welcome screen.

![Django welcome page](https://dev-to-uploads.s3.amazonaws.com/i/nabsh1bhtcoffrwqu4as.png)

## AbstractUser vs AbstractBaseUser

There are two modern ways to create a custom user model in Django: `AbstractUser` and `AbstractBaseUser`. In both cases we can subclass them to extend existing functionality however `AbstractBaseUser` requires **much, much more work.** Seriously, don't mess with it unless you really know what you're doing. And if you did, you wouldn't be reading this tutorial, would you?

So we'll use `AbstractUser` which actually subclasses `AbstractBaseUser` but provides more default configuration.

## Custom User Model

Creating our initial custom user model requires four steps:

* update `settings.py`
* create a new `CustomUser` model
* create new `UserCreation` and `UserChangeForm`
* update the admin

In `settings.py` we'll add the `users` app and use the `AUTH_USER_MODEL` config to tell Django to use our new custom user model in place of the built-in `User` model. We'll call our custom user model `CustomUser`.

Within `INSTALLED_APPS` add `users.apps.UsersConfig` at the bottom. Then at the bottom of the entire file, add the `AUTH_USER_MODEL` config.

```python
# config/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'users.apps.UsersConfig', # new
]
...
AUTH_USER_MODEL = 'users.CustomUser' # new
```

Now update `models.py` with a new User model which we'll call `CustomUser`.

```python
# users/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models

class CustomUser(AbstractUser):
    pass
    # add additional fields in here

    def __str__(self):
        return self.username
```

We need new versions of two form methods that receive heavy use working with users. Stop the local server with `Control+c` and create a new file in the `users` app called `forms.py`.

```
(users) $ touch users/forms.py
```

We'll update it with the following code to largely subclass the existing forms.

```python
# users/forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from .models import CustomUser

class CustomUserCreationForm(UserCreationForm):

    class Meta:
        model = CustomUser
        fields = ('username', 'email')

class CustomUserChangeForm(UserChangeForm):

    class Meta:
        model = CustomUser
        fields = ('username', 'email')
```

Finally we update `admin.py` since the Admin is highly coupled to the default User model.

```python
# users/admin.py
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin

from .forms import CustomUserCreationForm, CustomUserChangeForm
from .models import CustomUser

class CustomUserAdmin(UserAdmin):
    add_form = CustomUserCreationForm
    form = CustomUserChangeForm
    model = CustomUser
    list_display = ['email', 'username',]

admin.site.register(CustomUser, CustomUserAdmin)
```

And we're done! We can now run `makemigrations` and `migrate` for the first time to create a new database that uses the custom user model.

```
(users) $ python manage.py makemigrations users
(users) $ python manage.py migrate
```

## Superuser

It's helpful to create a superuser that we can use to login to the admin and test out login/logout. On the command line type the following command and go through the prompts.

```
(users) $ python manage.py createsuperuser
```

## Templates/Views/URLs

Our goal is a homepage with links to login, logout, and signup. Start by updating `settings.py` to use a project-level templates directory.

```python
# config/settings.py
TEMPLATES = [
    {
        ...
        'DIRS': [os.path.join(BASE_DIR, 'templates')], # new
        ...
    },
]
```

Then set the redirect links for login and logout, which will both go to our `home` template. Add these two lines at the bottom of the file.

```python
# config/settings.py
LOGIN_REDIRECT_URL = 'home'
LOGOUT_REDIRECT_URL = 'home'
```

Create a new project-level templates folder and within it a registration folder as that's where Django will look for the login template.

```
(users) $ mkdir templates
(users) $ mkdir templates/registration
```

Then create four templates:

```
(users) $ touch templates/registration/login.html
(users) $ touch templates/base.html
(users) $ touch templates/home.html
(users) $ touch templates/signup.html
```

Update the files as follows:

```html
<!-- templates/base.html -->
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>{% block title %}Django Auth Tutorial{% endblock %}</title>
</head>
<body>
  <main>
    {% block content %}
    {% endblock %}
  </main>
</body>
</html>
```

```html
<!-- templates/home.html -->
{% extends 'base.html' %}

{% block title %}Home{% endblock %}

{% block content %}
{% if user.is_authenticated %}
  Hi {{ user.username }}!
  <p><a href="{% url 'logout' %}">logout</a></p>
{% else %}
  <p>You are not logged in</p>
  <a href="{% url 'login' %}">login</a> |
  <a href="{% url 'signup' %}">signup</a>
{% endif %}
{% endblock %}
```

```html
<!-- templates/registration/login.html -->
{% extends 'base.html' %}

{% block title %}Login{% endblock %}

{% block content %}
<h2>Login</h2>
<form method="post">
  {% csrf_token %}
  {{ form.as_p }}
  <button type="submit">Login</button>
</form>
{% endblock %}
```

```html
<!-- templates/signup.html -->
{% extends 'base.html' %}

{% block title %}Sign Up{% endblock %}

{% block content %}
<h2>Sign up</h2>
<form method="post">
  {% csrf_token %}
  {{ form.as_p }}
  <button type="submit">Sign up</button>
</form>
{% endblock %}
```

Now for our `urls.py` files at the project and app level.

```python
# config/urls.py
from django.contrib import admin
from django.urls import path, include
from django.views.generic.base import TemplateView

urlpatterns = [
    path('', TemplateView.as_view(template_name='home.html'), name='home'),
    path('admin/', admin.site.urls),
    path('users/', include('users.urls')),
    path('users/', include('django.contrib.auth.urls')),
]
```

Create a `urls.py` file in the `users` app.

```
(users) $ touch users/urls.py
```

Then fill in the following code:

```python
# users/urls.py
from django.urls import path
from .views import SignUpView

urlpatterns = [
    path('signup/', SignUpView.as_view(), name='signup'),
]
```

Last step is our `views.py` file in the `users` app which will contain our signup form.

```python
# users/views.py
from django.urls import reverse_lazy
from django.views.generic.edit import CreateView

from .forms import CustomUserCreationForm

class SignUpView(CreateView):
    form_class = CustomUserCreationForm
    success_url = reverse_lazy('login')
    template_name = 'signup.html'
```

Ok, phew! We're done. Let's test it out.

Start up the server with `python manage.py runserver` and go to the homepage at [http://127.0.0.1:8000/](http://127.0.0.1:8000/).

![Home page](https://dev-to-uploads.s3.amazonaws.com/i/icsx7yti7fmq54uucj7e.png)

Click on _login_ and use your **superuser** credentials.

![Login page](https://dev-to-uploads.s3.amazonaws.com/i/d6hhvkqyd61jg8vqanht.png)

Upon successful submission you'll be redirected back to the homepage and see a personalized greeting.

![Homepage with superuser](https://dev-to-uploads.s3.amazonaws.com/i/sovmv507qmp5plpgt0mx.png)

Now use the _logout_ link and then click on _signup_.

![Signup page](https://dev-to-uploads.s3.amazonaws.com/i/24wno7kk1gvtu43w50pi.png)

Create a new user. Mine is called `testuser`. After successfully submitting the form you'll be redirected to the login page. Login with your new user and you'll again be redirected to the homepage with a personalized greeting for the new user.

![Homepage with new user](https://dev-to-uploads.s3.amazonaws.com/i/16tvc0o5yoac65bee145.png)

If you want to peruse the admin log into it with your superuser account at [http://127.0.0.1:8000/admin](http://127.0.0.1:8000/admin). If you look at `Users` you can see our two users.

![Users in the Admin](https://dev-to-uploads.s3.amazonaws.com/i/871uqt193uiuotjlc0m9.png)

## Conclusion

Now that our custom user model is configured you can easily and **at any time** add additional fields to it. See [the Django docs](https://docs.djangoproject.com/en/3.0/topics/auth/customizing/) for further instructions.

You can also check out [DjangoX](https://github.com/wsvincent/djangox), which is an open-source Django starter framework that includes a custom user model, email/password by default instead of username/email/password, social authentication, and more.