Custom User Model in Django

Category: Django
Tags: #django#webdevelopment#advanced

In this tutorial, we will be learning how to create a custom user model or extending user model to add some fields and to grab more control over user model.

django custom user model

Django's built-in User model is great and allows us to start using it. For most of the cases, it will work fine but official Django documentation highly recommends using a custom user model.

Django gives some limited fields to store data but in most of the cases, we want to store few more user information it's challenging to customize existing user model that's why it's better to extend existing user model.

We can create a custom user model in two ways -

  1. by extending AbstractUser class
  2. by extending AbstractBaseUser class

Extending AbstractUser class #

It is a good idea to extend to AbstractUser and add our fields to the existing model. So, all the basic fields will be by default available.

Set Up

Create a new directory and navigate into it and install django.

pip install django

Create a new project

django-admin startproject userproject

Navigate into to project folder then run the server to check everything is working or not.

python manage.py runserver

If everything works fine then open http://127.0.0.1:8000/ and you should get this

django runserver

Action

Then start a new app

python manage.py startapp users

At this point your project structure should look like -

userproject/
    |-- userproject/
    |   |-- __init__.py
    |   |-- asgi.py
    |   |-- settings.py
    |   |-- urls.py
    |   |-- wsgi.py
    |-- users/
    |   |-- __init__.py
    |   |-- admin.py
    |   |-- apps.py
    |   |-- models.py
    |   |-- tests.py
    |   |-- views.py
    |-- db.sqlite3
    |-- manage.py

Then update settings.py file

# userproject/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'users', # new
]

AUTH_USER_MODEL = 'users.CustomUser' # new

NOTE: Do not migrate the app. Because we want to change the user table and after migration normal user table will be created. Now update users/models.py with custom user model and with all the new fields required by extending the AbstractUser class

# users/models.py

from django.db import models
from django.contrib.auth.models import AbstractUser

class CustomUser(AbstractUser):
    address = models.TextField()
    contact_no = models.CharField(max_length=10)

Now we have to create forms to create new user and modification of an existing user. Create a new file inside users app forms.py i.e., users/forms.py

# users/forms.py

from django import forms
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from users.models import CustomUser

class CustomUserCreationForm(UserCreationForm):
    class Meta:
        model = CustomUser
        fields = UserCreationForm.Meta.fields + ('address', 'contact_no')


class CustomUserChangeForm(UserChangeForm):
    class Meta:
        model = CustomUser
        fields = UserChangeForm.Meta.fields

Here we are using model forms and setting model to our CustomUser model and all the default fields. By default, django provides these fields: username, first_name, last_name, email, password, groups etc. For more information check out: Django User Model description.

Now update admin.py

# users/admin.py

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from users.forms import CustomUserCreationForm, CustomUserChangeForm
from users.models import CustomUser


class CustomUserAdmin(UserAdmin):
    add_form = CustomUserCreationForm
    form = CustomUserChangeForm
    model = CustomUser

admin.site.register(CustomUser, CustomUserAdmin)

Here, we have created an admin for custom user to show information on Django admin page.

Now make migrations

python manage.py makemigrations

After this, you may see this output for migration of our users model

Migrations for 'users':
users\migrations\0001_initial.py
- Create model CustomUser

And then migrate

python manage.py migrate

Thats it, we are done with Custom user model.

To ensure that everything is working fine create a superuser.

python manage.py createsuperuser

If you are able to create a superuser then everything is fine. Now we can use our Custom user model in our application.

Extending AbstractBaseUser class #

If we try to create a custom user by extending AbstractBaseUser and basically we need to rewrite Django. It can be useful for a completely customized application like email instead of a username for login.

Set Up

Create a new directory and navigate into it and install django.

pip install django

Create a new project

django-admin startproject userproject

Navigate into to project folder then run the server to check everything is working or not.

python manage.py runserver

If everything works fine then open http://127.0.0.1:8000/ and you should get this

django runserver

Action

Then start a new app

python manage.py startapp users

At this point your project structure should look like -

userproject/
    |-- userproject/
    |   |-- __init__.py
    |   |-- asgi.py
    |   |-- settings.py
    |   |-- urls.py
    |   |-- wsgi.py
    |-- users/
    |   |-- __init__.py
    |   |-- admin.py
    |   |-- apps.py
    |   |-- models.py
    |   |-- tests.py
    |   |-- views.py
    |-- db.sqlite3
    |-- manage.py

Then update settings.py file

# userproject/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'users', # new
]

AUTH_USER_MODEL = 'users.User' # new

NOTE: Do not migrate the app. Because we want to change the user table and after migration normal user table will be created. Now update users/models.py with custom user model and with all the new fields required by extending the AbstractBaseUser class

# users/models.py

from django.db import models
from django.contrib.auth.models import AbstractBaseUser

class User(AbstractBaseUser):
    # add all the required fields
    email = models.EmailField(max_length=255, unique=True)
    first_name = models.CharField(max_length=30, blank=True)
    last_name = models.CharField(max_length=30, blank=True)
    is_active = models.BooleanField(default=True)
    is_admin = models.BooleanField(default=False)

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []

    def __str__(self):
        return self.email

    def get_full_name(self):
        full_name = '%s %s' % (self.first_name, self.last_name)
        return full_name.strip()

    def get_short_name(self):
        return self.first_name.strip()

    @property
    def is_staff(self):
        return self.is_admin

    def has_perm(self, perm, obj=None):
        return True

    def has_module_perms(self, app_label):
        return True
  • USERNAME_FIELD: Whatever field we mention here will be treated as main field to login and logout user. It means by default username field is prompted during but now we have to input email during login.
  • REQUIRED_FIELDS: Fields which are added here will be aksed to user during creation of superuser if they use python manage.py createsuperuser command. Here, we are keeping it blank because by default the feild mentioned as USERNAME_FIELD and password is present here.

Then create a new file managers.py inside users app i.e., users/managers.py

Now users directory should look like -

users/

    |-- __init__.py

    |-- admin.py

    |-- apps.py

    |-- managers.py

    |-- models.py

    |-- tests.py

    `-- views.py

Now create a UserManager class inside managers.py file and define create_user and create_superuser methods.

# users/managers.py

from django.contrib.auth.models import BaseUserManager

class UserManager(BaseUserManager):
    def create_user(self, email, password=None):
        if not email:
            raise ValueError('Users must have an email address')
        if not password:
            raise ValueError('Users must have a password')
        user = self.model(
            email=self.normalize_email(email),
        )
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, password):
        user = self.create_user(
            email,
            password=password,
        )
        user.is_admin = True
        user.save(using=self._db)
        return user

Now import UserManager from managers.py and add it to User model class. Add anywhere inside User class.

# users/models.py

from django.db import models
from django.contrib.auth.models import AbstractBaseUser
from users.managers import UserManager # new

class User(AbstractBaseUser):
    ...
    objects = UserManager() # new
    ...

Now we have to create forms for creating and updating users. Create a new file forms.py inside users app directory users/forms.py

# users/forms.py

from django import forms
from django.contrib import admin
from django.contrib.auth.forms import ReadOnlyPasswordHashField
from users.models import User

class UserCreationForm(forms.ModelForm):
    password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
    password2 = forms.CharField(
        label='Password confirmation', widget=forms.PasswordInput)

    class Meta:
        model = User
        fields = ('email', 'first_name', 'last_name')

    def clean_password2(self):
        password1 = self.cleaned_data.get('password1')
        password2 = self.cleaned_data.get('password2')
        if password1 and password2 and password1 != password2:
            raise forms.ValidationError("Password don't match")
        return password2

    def save(self, commit=True):
        user = super().save(commit=False)
        user.set_password(self.cleaned_data['password1'])
        if commit:
            user.save()
        return user


class UserChangeForm(forms.ModelForm):
    password = ReadOnlyPasswordHashField()

    class Meta:
        model = User
        fields = ('email', 'password', 'first_name', 'last_name', 'is_active', 'is_admin')

    def clean_password(self):
        return self.initial['password']

Then register all the required data into admin.py

# users/admin.py

from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import Group
from django.contrib import admin
from users.forms import UserChangeForm, UserCreationForm
from users.models import User

class UserAdmin(BaseUserAdmin):
    form = UserChangeForm
    add_form = UserCreationForm
    list_display = ('email', 'is_admin')
    list_filter = ('is_admin',)

    fieldsets = (
        (None, {'fields': ('email', 'password', 'first_name', 'last_name',)}),
        ('Permissions', {'fields': ('is_admin',)}),
    )

    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('email', 'password1', 'password2'),
        }),
    )

    search_fields = ('email',)
    ordering = ('email',)
    filter_horizontal = ()

    class Meta:
        model = User


admin.site.register(User, UserAdmin)
admin.site.unregister(Group)

Now make migrations

python manage.py makemigrations

After this, you may see this output for migration of our users model

Migrations for 'users':
users\migrations\0001_initial.py
 - Create model User

And then migrate

python manage.py migrate

Thats it, we are done with Custom user model. Now try to create a superuser you will be prompted for email instead if username.

python manage.py createsuperuser

By following these steps we can create our custom user model with email as primary field.