Adding Custom User model in Django – part 1

One of the first things I do after creating a new Django or Django Rest Framework project is to create a custom User model. Part 1 deals with case when you add custom User model right at the start of a project. In part 2, we deal with scenario when custom User model is added later in the project.

What exactly is a User model for?

Django uses a User model for only one purpose, user authentication. If the project is really small and not ever going to scale, you can tack on non-authentication related stuff as well, like user address, user profile data etc but this is not recommended. I like to keep my models simple and they only contain what is necessary for authentication. Everything else, I move it to other models like UserProfile etc

Why custom user model?

Well, Django’s default created User model makes certain crippling assumptions. Like it will only use username for login. If you already put your project in production with active users and then down the line wanted to change it to use email for authentication, this will not be easy. If the active users are too many, it might even be impossible to change. It is so because it involves deleting the old database models and create new ones that use the new user model. So, we must create a custom user model right at the start and switch over to email for authentication. This also makes it easy to add other customization later on. Do this before doing your first migration.

Create Custom User Model

Follow these steps.

  1. create a new app, only for managing custom user model. I call it accounts in most of my projects.
  2. create User Model and Model Manager. In my case, I called the model as MzkUser and model manager as MzkUserManager. Note, it is important to NOT name them as User and UserManager as these might become reserved words in future. Since my project was about a music app, I prepended it with Mzk. In the code below, all models and model-methods and properties are required.
    from django.db import models
    from django.contrib.auth.models import (
        AbstractBaseUser, BaseUserManager
    )
    
    class MzkUserManager(BaseUserManager):
        # create_user takes all REQUIRED_FIELDS and USERNAME_FIELD
        def create_user(self, email, password=None, is_active=True, is_staff=False, is_admin=False):
            if not email:
                raise ValueError("Users must have an email address")
            if not password:
                raise ValueError("Users must have a password")
    
            user_obj = self.model(
                email = self.normalize_email(email)
            )
            user_obj.set_password(password)
            user_obj.staff = is_staff
            user_obj.admin = is_admin
            user_obj.active = is_active
            user_obj.save(using=self._db)
            return user_obj
        
        def create_staff_user(self, email, password=None):
            user = self.create_user(
                email,
                password=password,
                is_staff=True
            )
            return user
    
        def create_superuser(self, email, password=None):
            user = self.create_user(
                email,
                password=password,
                is_staff=True,
                is_admin=True
            )
            return user
    
    # password is inherited from AbstractBaseUser
    class MzkUser(AbstractBaseUser):
        email = models.EmailField(default='abc@gmail.com', max_length=255, unique=True)
        active = models.BooleanField(default=True)
        staff = models.BooleanField(default=False)
        admin = models.BooleanField(default=False)
        timestamp = models.DateTimeField(auto_now_add=True)
        
        USERNAME_FIELD = 'email'
        REQUIRED_FIELDS = []
    
        objects = MzkUserManager()
    
        def __str__(self):
            return self.email
        
        def get_full_name(self):
            return self.email
    
        def get_short_name(self):
            return self.email
    
        # we don't use object/module level permissions
        def has_perm(self, perm, obj=None):
            return True
    
        # we don't use module level permissions
        def has_module_perms(self, app_label):
            return True
    
        @property
        def is_staff(self):
            return self.staff
        
        @property
        def is_admin(self):
            return self.admin
    
        @property
        def is_active(self):
            return self.active
  3. settings.py: Register the accounts app by adding it to INSTALLED_APPS. Then add the following line:
    AUTH_USER_MODEL = 'accounts.MzkUser'
    This will indicate which custom user model we are using
  4. Run makemigrations and migrate
  5. accounts/admin.py: The above steps will enable the project to use custom user model. But if we went to the admin page, this will still not be visible (admin will use this model to add new users). To be able to see this on admin page, we need to also register this model with admin panel like so:
    from django.contrib import admin
    from django.contrib.auth import get_user_model
    
    User = get_user_model()
    
    
    class UserAdmin(admin.ModelAdmin):
        class Meta:
            model = User
    
    
    admin.site.register(User)
  6. Now run migrations, create superuser and then goto admin page to see the new user model.

Note: Here, I am not using object-level permissions. Hence am returning True in has_module_perms method. You will anyways want to use a library like django-guardian for managing object-level permissions.

You will also want to revisit has_perm method when you implement user level permissions. For simplicity, I am returning True here.




No Comments


You can leave the first : )



Leave a Reply

Your email address will not be published. Required fields are marked *