Custom User Model in Django
Tags: python, django, tutorial
Published: 2022-03-17 07:54:26

Hello everyone. Follow this article to understand about custom user model in Django.

---------------

I've attached the source code at the end of this article. 

Django's built-in `User` model is good enough for many cases while also having a room for customization. Why would you need to tweak some of the functionalities of the default `User` model?

- Maybe you want to make `email` field the primary unique identifier of users instead of `username`.
- Maybe you want to include other unique fields for authentication like phone number.
- Maybe you want to stack all user information, be it auth related or non-auth fields all in the `User` model.

That being said, let's get to it.

---------------------

## Step 1: Set up a Django project.

1) Create a python virtual environment and activate it.

2) Install Django and Django rest framework.

```
pip install django djangorestframework
```

3) Start a new project: 
```
django-admin startproject config .
```
4) Run the following command to see if the installation is correct. 
```
py manage.py runserver
```
> **Note**: Do not create and apply migrations because changing the `User` model after you’ve created database tables is significantly more difficult since it affects foreign keys and many-to-many relationships.

Now, create a new app `users` and add it to installed apps.
```
py manage.py startapp users
```

_settings.py_
```python
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    # Third-party apps
    'rest_framework',

    # Local apps
    'users',
]
```

--------

When you have projects that require a different authentication that the built-in `User` model can’t provide, you have to create your own custom `User` model by extending from either `AbstractUser` or `AbstractBaseUser` Now, the question is which one should you extend from?

1. `AbstractUser` : Are you satisfied with the existing fields in the built-in `User` model but you want to use email as the primary unique identifier of your users or perhaps remove the `username` field? If yes, `AbstractUser` is the right option for you.
2. `AbstractBaseUser` : There are two things to consider whenever starting a new project :
    - `User` model focused on authentication, and another model like `Profile` that keeps app-related information of the user. Here you may use `AbstractBaseUser` if you have additional auth-related attributes that you want to include in your `User` model.
    - `User` model that stores all information (auth related or non-auth attributes) all in one. You may want to do this to avoid using additional database queries to retrieve related model. If you want to take this approach, `AbstractBaseUser` is the right option.

> **Note**: If you’re starting a new project, it’s highly recommended to set up a custom user model, even if the default `User` model is sufficient for you. This model behaves identically to the default user model, but you’ll be able to customize it in the future if the need arises:
    
```python
from django.contrib.auth.models import AbstractUser


class User(AbstractUser):
    pass
```   

For the purpose of this article, we are going to use `AbstractUser` but the steps for `AbstractBaseUser` are also similar.

## Step 2: Custom Model Manager

`Manager` is a class that provides an interface through which database query operations are provided to Django models. You can have more than one manager for your model. 

Consider this model:

```python
from django.db import models

class Car(models.Model):
    pass
```

- To get all instances of `Car`, you will use `Car.objects.all()`

`objects` is the default name that Django managers use. To change this name:

```python
from django.db import models

class Car(models.Model):
    cars = models.Manager();
```

Now, to get all instances of car, you should use `Car.cars.all()`. 

For our custom user model, we need to define a custom manager class because we are going to modify the initial `Queryset` that the default `Manager` class returns. We do this by extending from [`BaseUserManager`](https://docs.djangoproject.com/en/4.0/topics/auth/customizing/#django.contrib.auth.models.BaseUserManager) and providing two additional methods `create_user` and `create_superuser`. 

- Create a file named _managers.py_ inside _users_ app and put the following.

*managers.py*

```python
from django.contrib.auth.base_user import BaseUserManager
from django.utils.translation import gettext as _

class CustomUserManager(BaseUserManager):
    """
    Custom user model manager where email is the unique identifier
    for authentication instead of usernames.
    """

    def create_user(self, email, password, **extra_fields):
        if not email:
            raise ValueError(_('Users must have an email address'))
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save()
        return user

    def create_superuser(self, email, password, **extra_fields):
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)
        extra_fields.setdefault('is_active', True)

        if extra_fields.get('is_staff') is not True:
            raise ValueError(_('Superuser must have is_staff=True.'))
        if extra_fields.get('is_superuser') is not True:
            raise ValueError(_('Superuser must have is_superuser=True.'))
        return self.create_user(email, password, **extra_fields)

```

## Step 3: The User Model

*models.py*
```python
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.utils.translation import gettext as _

from .managers import CustomUserManager

class CustomUser(AbstractUser):
    email = models.EmailField(_('email address'), unique=True)

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ('username',)

    objects = CustomUserManager()

    def __str__(self):
        return self.email

```

- `USERNAME_FIELD` specifies the name of the field on the user model that is used as the unique identifier. In our case it’s email.
- `REQUIRED_FIELDS` A list of the field names that will be prompted for when creating a superuser via the `createsuperuser` management command. This doesn’t have any effect in other parts of Django like when creating a user in the admin panel.

## Step 4: The Settings

We now have to tell Django our new model that should be used to represent a User. This is done as follows.

*settings.py*

```python
AUTH_USER_MODEL = 'users.CustomUser'
```

→ You can now create and apply migrations.

```python
py manage.py makemigrations
py manage.py migrate
```

## Step 5: Forms

Django’s built-in `UserCreationForm` and `UserChangeForm` forms must be extended to let them know the new user model that we are working with.

Create a file named _forms.py_ inside _users_ app and add the following:

*forms.py*

```python
from django.contrib.auth.forms import UserCreationForm, UserChangeForm

from .models import CustomUser

class CustomUserCreationForm(UserCreationForm):

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

class CustomUserChangeForm(UserChangeForm):

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

## Step 6: Admin

Tell the admin panel to use these forms by extending from `UserAdmin` in _users/admin.py_

*admin.py*

```python
from django.contrib import admin
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 = ('username', 'email', 'is_active',
                    'is_staff', 'is_superuser', 'last_login',)
    list_filter = ('is_active', 'is_staff', 'is_superuser')
    fieldsets = (
        (None, {'fields': ('username', 'email', 'password')}),
        ('Permissions', {'fields': ('is_staff', 'is_active',
         'is_superuser', 'groups', 'user_permissions')}),
        ('Dates', {'fields': ('last_login', 'date_joined')})
    )
    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('username', 'email', 'password1', 'password2', 'is_staff', 'is_active')}
         ),
    )
    search_fields = ('email',)
    ordering = ('email',)

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

- `add_form` and `form` specifies the forms to add and change user instances.
- `fieldsets` specifies the fields to be used in editing users and `add_fieldsets` specifies fields to be used when creating a user.


You can now go to the admin panel and add/edit users.

Source code: https://github.com/earthcomfy/jwt-auth