Nitin Raturi's Software Engineering Blog

Nitin Raturi's Software Engineering Blog

Share this post

Nitin Raturi's Software Engineering Blog
Nitin Raturi's Software Engineering Blog
Django Custom User Model: Abstractuser
Copy link
Facebook
Email
Notes
More
User's avatar
Discover more from Nitin Raturi's Software Engineering Blog
tech.raturi.in is a personal programming blog owned and maintained by Nitin Raturi. The primary goal of this blog is to share insights on Python programming language and fullstack web development.
Already have an account? Sign in

Django Custom User Model: Abstractuser

Extend or create a custom user model in django using AbstractUser

Nitin Raturi's avatar
Nitin Raturi
Mar 10, 2025

Share this post

Nitin Raturi's Software Engineering Blog
Nitin Raturi's Software Engineering Blog
Django Custom User Model: Abstractuser
Copy link
Facebook
Email
Notes
More
Share

Django ships with a built-in User model for authentication, however, the official Django documentation highly recommends using a custom user model for new projects.

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. So here we will use AbstractUser.

We will also create a UserProfile model to store additional information about users with the help of a One-To-One Link. After this, we will extend the BaseUserManager functionality a little bit. We will also configure signals to automatically create UserProfile whenever a User object is created. Finally, we will customize the Django admin.

Note: This should be done at the begining of the project before running any migrations.

Step 1: New app

  • Create app users using the command python manage.py startapp users

  • Now register your app in your installed apps in settings.py: INSTALLED_APPS += [ 'users', ]

  • Set AUTH_USER_MODEL in settings.py to the new user model: AUTH_USER_MODEL = 'users.User'

Step 2: Extend model and manager

Create a file constants.py inside users app directory

# users/constants.py
SUPERUSER = 1
STAFF = 2
STUDENT = 3
TUTOR = 4

USER_TYPE_CHOICES = (
      (SUPERUSER, 'superuser'),
      (STAFF, 'staff'),
      (STUDENT, 'student'),
      (TUTOR, 'tutor'),
  )

Extend the User model in your users/models.py

# users/models.py

from django.contrib.auth.models import AbstractUser
from django.db import models
from django.utils import timezone
from apps.users.managers import UserManager
from . import constants as user_constants

class User(AbstractUser):
    username = None # remove username field, we will use email as unique identifier
    email = models.EmailField(unique=True, null=True, db_index=True)
    is_active = models.BooleanField(default=False)
    is_staff = models.BooleanField(default=False)
    date_joined = models.DateTimeField(default=timezone.now)
    user_type = models.PositiveSmallIntegerField(choices=user_constants.USER_TYPE_CHOICES)

    REQUIRED_FIELDS = []
    USERNAME_FIELD = 'email'

    objects = UserManager()


class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True,related_name="user_profile")
    phone = models.CharField(max_length=255,blank=True,null=True)
    is_verified = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.user.email

Extend UserManager in users/managers.py

# users/managers.py

from django.contrib.auth.models import BaseUserManager
from . import constants as user_constants


class UserManager(BaseUserManager):
    def create_user(self, email, password, **extra_fields):

        if not email:
            raise ValueError('The Email must be set')
        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_superuser', True)
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_active', True)
        extra_fields.setdefault('user_type', user_constants.SUPERUSER)
        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: Migrations

Run the commands below to make database changes

python manage.py makemigrations 
python manage.py migrate

Step 4: Create Signal

Now let's create signals for automatically creating a UserProfile instance in users/signals.py

# users/signals.py
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import UserProfile, User

@receiver(post_save, sender=User)
def create_or_update_user_profile(sender, instance, created, **kwargs):
    if created:
        UserProfile.objects.create(user=instance)
    instance.user_profile.save()

Now, register your signals file inside users/apps.py by adding a method ready and importing the signals file path.

# users/apps.py
from django.apps import AppConfig

class UsersConfig(AppConfig):
    name = 'apps.users'

    def ready(self):
        import apps.users.signals

Now, add default_app_config to users/init.py

# users/__init__.py
default_app_config = 'users.apps.UsersConfig'

Step 5: Extend the admin form

Let's extend UserCreationForm and UserChangeForm in users/forms.py

# users/forms.py
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from .models import User


class CustomUserCreationForm(UserCreationForm):

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


class CustomUserChangeForm(UserChangeForm):

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

Step 6: Customize Django user admin

Let's customize Django's admin now

# users/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import UserProfile, User
from .forms import CustomUserCreationForm, CustomUserChangeForm


class UserProfileInline(admin.StackedInline):
    model = UserProfile
    can_delete=False
    verbose_plural_name="User Profile"
    fk_name = 'user'  

class CustomUserAdmin(UserAdmin):
    add_form = CustomUserCreationForm
    form = CustomUserChangeForm
    model = User
    list_display_links = ['email']
    search_fields = ('email',)
    ordering = ('email',)
    # inlines = (UserProfileInline,)
    list_display = ('email', 'is_staff', 'is_active', 'is_superuser',)
    list_filter = ('email', 'is_staff', 'is_active', 'is_superuser', 'user_type')
    fieldsets = (
        (None, {'fields': ('username', 'password')}),
        (_('Personal info'), {'fields': ('first_name', 'last_name', 'email','user_type')}),
        (_('Permissions'), {
            'fields': ('is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions'),
        }),
        (_('Important dates'), {'fields': ('last_login', 'date_joined')}),
    )
    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('email', 'password1', 'password2', 'is_staff', 'is_active', 'user_type')}
         ),
    )

    def get_inline_instances(self, request, obj=None):
        if not obj:
            return list()
        return super(CustomUserAdmin, self).get_inline_instances(request, obj)

admin.site.register(User, CustomUserAdmin)

Step 7: Verifying

You have successfully extended the user model. Let's create a user

python manage.py createsuperuser

#output
Email: develop@raturi.in
Password: ..........
Password (again): ..........
Superuser created successfully

Your user should be created by now, login in to your admin panel to see.

That's all.


Subscribe to Nitin Raturi's Software Engineering Blog

Launched 2 months ago
tech.raturi.in is a personal programming blog owned and maintained by Nitin Raturi. The primary goal of this blog is to share insights on Python programming language and fullstack web development.

Share this post

Nitin Raturi's Software Engineering Blog
Nitin Raturi's Software Engineering Blog
Django Custom User Model: Abstractuser
Copy link
Facebook
Email
Notes
More
Share

Discussion about this post

User's avatar
Convert A Django Queryset To List
Use list(queryset) to convert a queryset to a list. Read more for alternate methods
Mar 10 â€¢ 
Nitin Raturi

Share this post

Nitin Raturi's Software Engineering Blog
Nitin Raturi's Software Engineering Blog
Convert A Django Queryset To List
Copy link
Facebook
Email
Notes
More
Django Mixin: Custom Class Based Mixins, Example, And Types
A Mixin is a special kind of inheritance in Python to allow classes to share methods between any class.
Mar 10 â€¢ 
Nitin Raturi

Share this post

Nitin Raturi's Software Engineering Blog
Nitin Raturi's Software Engineering Blog
Django Mixin: Custom Class Based Mixins, Example, And Types
Copy link
Facebook
Email
Notes
More
Python-Decouple: Manage Environment Variables In Django
Learn to manage environment variables for multiple django settings: development, production, testing, stage, etc
Mar 10 â€¢ 
Nitin Raturi

Share this post

Nitin Raturi's Software Engineering Blog
Nitin Raturi's Software Engineering Blog
Python-Decouple: Manage Environment Variables In Django
Copy link
Facebook
Email
Notes
More

Ready for more?

© 2025 Nitin Raturi
Privacy ∙ Terms ∙ Collection notice
Start writingGet the app
Substack is the home for great culture

Share

Copy link
Facebook
Email
Notes
More

Create your profile

User's avatar

Only paid subscribers can comment on this post

Already a paid subscriber? Sign in

Check your email

For your security, we need to re-authenticate you.

Click the link we sent to , or click here to sign in.