Overview

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 of user  with the help of 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 an User object is created. Finally we will customize django admin.

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

Step 1

  • Create an app.
  • python manage.py startapp users
  • Now register your app in your installed apps.
  • # settings.py
    INSTALLED_APPS += [
        'users',
    ]
    
  • Add AUTH_USER_MODEL in settings.py
  • # settings.py
    AUTH_USER_MODEL = 'users.User'

 

Step 2

  • Create a file constants.py inside users 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

  • Run the commands below to make changes
  • python manage.py makemigrations
    python manage.py migrate

Step 4

  • Now let's create signals for automatically creating 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 import signals 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

  • 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

  • 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': ('email', 'password')}),
            ('Permissions', {'fields': ('is_staff', 'is_active','user_type')}),
        )
        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 6

  • You have successfully extended the user model. Lets create an user
  • python manage.py createsuperuser
    
    #output
    Email: develop@raturi.in
    Password: ..........
    Password (again): ..........
    Superuser created successfully
  • You user should be created by now, login to your admin panel to see.