Creating a Custom User Model in Django

The built-in User model and authentication functionality in Django are fantastic. For a long time, it’s been a Django mainstay. It is Django’s source of preference over alternative web frameworks like Flask, FastAPI, AIOHttp, and others. When a username and password contained in the user model need to be authenticated against a service other than Django’s default, authentication backends provide an expandable system.

You can grant specific permissions to your models, which can be validated using Django’s authorization system.

You’ll need to alter the default user model’s functionality now and then. Assume you no longer require a username field. Alternatively, you may make the email field your default “username” field. We’ll need to tweak our regular Django User model for this.

Remember that changing Django’s defaults adds a lot of complexity to an already complicated system. When possible, stick to the original settings. In other words, if you don’t have to, don’t develop a unique user model; Foreign Keys are perfect for that.

You can either extend the default User model or create a new one. In this article, we will opt to go with the last-mentioned.

Other sources of authentication

There are instances to connect to another authentication source, such as a database of users and passwords or a means of authentication. For example, your organization may already have an LDAP server set up that maintains each employee’s username and password. If users had different accounts in LDAP and Django-based applications, it would be inconvenient for both the network administrator and the users.

As a result, the Django authentication system allows you to plug in alternative authentication sources to handle this scenario. You can either change Django’s default database-based strategy or use it with other systems.

Extending the User model that already exists

There are two methods for extending the basic User model without creating your own. You can develop a proxy model based on User if the changes you need are simply behavioral and don’t require any modifications to the database. It allows for any proxy model’s characteristics, such as default ordering, custom managers, and custom model methods.

You can use an OneToOneField to a model containing the fields for additional information if you want to store a user’s information. Because it may keep non-auth-related information about a site user, this one-to-one approach is commonly referred to as a profile model. You could, for example, establish a Staff model:

from django.contrib.auth.models import User

class Staff(models.Model):
  user = models.OneToOneField(User, on_delete=models.CASCADE)
  department = models.CharField(max_length=100)

Using Django’s standard related model conventions, you may retrieve the related information for an existing Staff Ann Keen, who has both a User and an Employee model:

u = User.objects.get(username='ann')
anns_department = u.employee.department

In your app’s admin.py, define an InlineModelAdmin. For this example, we’ll use a StackedInline and add it to a UserAdmin class that is registered with the User class: To add a profile model’s fields to the user page in the admin, define an InlineModelAdmin and add it to a UserAdmin class that is registered with the User class:

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as MainUserAdmin
from django.contrib.auth.models import User as CustomUser

from my_user_profile_app.models import Staff

# It is the Staff's inline admin model acting like a singleton
class StaffInline(admin.StackedInline):
  model = Staff
  can_delete = False
  verbose_name_plural = 'staff'
  
# Definition of a new User admin

class UserAdmin(MainUserAdmin):
  inlines = (StaffInline,)
  
# Re-register UserAdmin

admin.site.unregister(CustomUser)
admin.site.register(CustomUser, UserAdmin)

These profile models are nothing remarkable; they’re just Django models with a one-to-one relationship with a user model. As a result, they aren’t produced automatically when a user is created; however, a django.db.models.signals.post_save might be used to create or update relevant models as needed.

Using related models necessitates more queries or joins to retrieve the relevant data. Depending on your needs, a bespoke user model with the associated fields may be a preferable solution; however, existing relationships to the default user model within your project’s apps may justify the increased database burden.

Creating a whole new app

Some applications may have authentication requirements that Django’s built-in User model isn’t always suitable. For example, using an email address rather than a username as your identity token makes more sense on some websites.

By supplying a value for the AUTH_USER_MODEL parameter that refers to a custom model, Django allows you to alter the default user model. It is where you’ll store your custom user model, and in our case, we will call it accounts. It explains the name of the Django model you want to use as your user model and the label of the Django app, which must be in your INSTALLED_APPS.

When establishing a project, utilize a custom user model. Even if the default User model is acceptable for you, it’s highly suggested to build up a custom user model if you’re starting a new project. This model functions just like the default user model, but you can alter it if necessary in the future:

python manage.py startapp accounts

In models.py, create the Custom User Model

You may either use the Django example or the one we’ve provided below. We’ll make it easier for you here.

# accouts/models.py

from django.db import models
from django.contrib.auth.models import (
BaseUserManager, AbstractBaseUser
)

class User(AbstractBaseUser):
  email = models.EmailField(
  verbose_name='user email address',
  max_length=100,
  unique=True,
  )
  is_active = models.BooleanField(default=True)
  is_staff = models.BooleanField(default=False)
  
  
  is_admin = models.BooleanField(default=False) # a superuser

# notice the absence of a "Password field" that is built-in.

USERNAME_FIELD = 'email'
REQUIRED_FIELDS = [] # Email & Password are required by default.

def get_full_name(self):
    # users identification is by email
    return self.email

def get_user_short_name(self):
    # user identification is by email
    return self.email

def __str__(self):
    return self.email

def has_perm(self, perm, obj=None):
    # return true if user has the necessary permissions
	return True
  
def has_module_perms(self, app_label):
    # return true if user is granted the permission to view 'app_label'
    return True
  
@property
def is_staff(self):
    # returns true if user is a staff
    return self.is_staff

@property
def is_admin(self):
    # returns true if user is admin member
    return self.is_admin

So, what precisely is the USERNAME_FIELD? That’ll be how Django will recognize this user. It takes the place of the built-in username field with whatever you specify. In this situation, we choose to use an EmailField, but you could instead use a phone number.

Create the User Model Manager

For your user model, you need to additionally create a custom manager. You can use Django’s UserManager if your user model has the same email, is_staff, date joined fields, is_active, is_superuser, last_login, and username as Django’s default user. However, if your user model has different fields, you’ll need to define a custom manager that extends BaseUserManager and provides two additional methods:

create_user(username_field, password=None, **additional_fields)
create_superuser(username_field, password=None, **additional_fields)

The User Manager in Django has built-in methods. For our custom user model to perform correctly, we must customize them.

class UserManager(BaseUserManager):
  
  def create_user(self, email, password=None):
    """
    responsible for both creating and saving a user with the given parameters
    
    """
    if not, email:
      raise ValueError('Cannot create a user without an email address ')
      
    user = self.model(
        email=self.normalize_email(email),
    )

    user.set_password(password)
    user.save(using=self._db)
    return user

  def create_staffuser(self, email, password):
      """
      Creates and saves a staff user with the given email and password.
      """
      user = self.create_user(
          email,
          password=password,
      )
      user.staff = True
      user.save(using=self._db)
      return user

  def create_superuser(self, email, password):
      """
  Responsible for creating and saving a superuser with the given parameters.
      """
      user = self.create_user(
          email,
          password=password,
      )
      user.staff = True
      user.admin = True
      user.save(using=self._db)
      return user
    
# hook in the New Manager to our Model
class User(AbstractBaseUser): # from step 2
    ...
    objects = UserManager()	

Update settings module

Run migrations

python manage.py makemigrations
python manage.py migrate

update settings.py

AUTH_USER_MODEL = 'accounts.User'

Run migrations one more time

python manage.py makemigrations
python manage.py migrate

create a superuser

python manage.py createsuperuser

Setting AUTH_USER_MODEL allows us to use a variety of third-party packages that use the Django user model, such as Django Rest Framework, Django AllAuth, and Python Social Auth.

Now, whenever you require the user model, type:

from django.contrib.auth import get_user_model
User = get_user_model()

It gives other developers and our future selves the kind of peace of mind that only well-written code can deliver. In fact, get_user_model is one of those components that the core Django developers settled on a long time ago. But what about foreign keys created by users? You’ll come across this issue at some point:

class SomeModel(models.Model):
  user = models.ForeignKey(…)

You’ll make use of the option settings. AUTH USER MODEL every time, regardless of whether the user model has been customized. As a result, it will appear like this:

from django.conf import settings

User = settings.AUTH_USER_MODEL

class SomeModel(models.Model):
  user = models.ForeignKey(User, on_delete=models.CASCADE)

Create the registration, change, and admin-level forms

We can utilize the built-in User Creation Form now that we’ve altered the AUTH_USER_MODEL because it’s a model form based on our user model, but let’s learn how to construct our own anyhow.

# accounts/forms.py

from django import forms
from django.contrib.auth import get_user_model
from django.contrib.auth.forms import ReadOnlyPasswordHashField

User = get_user_model()

class RegisterForm(forms.ModelForm):
    """
    The default

    """

    password = forms.CharField(widget=forms.PasswordInput)
    password_2 = forms.CharField(label='Confirm Password', widget=forms.PasswordInput)

    class Meta:
        model = User
        fields = ['email']

    def clean_email(self):
        '''
        Verify email is available.
        '''
        email = self.cleaned_data.get('email')
        qs = User.objects.filter(email=email)
        if qs.exists():
            raise forms.ValidationError("email is taken")
        return email

    def clean(self):
        '''
        Verify both passwords match.
        '''
        cleaned_data = super().clean()
        password = cleaned_data.get("password")
        password_2 = cleaned_data.get("password_2")
        if password is not None and password != password_2:
            self.add_error("password_2", "Your passwords must match")
        return cleaned_data


class UserAdminCreationForm(forms.ModelForm):
    """
    A form for creating new users. Includes all the required
    fields, plus a repeated password.
    """
    password = forms.CharField(widget=forms.PasswordInput)
    password_2 = forms.CharField(label='Confirm Password', widget=forms.PasswordInput)

    class Meta:
        model = User
        fields = ['email']

    def clean(self):
        '''
        Verify both passwords match.
        '''
        cleaned_data = super().clean()
        password = cleaned_data.get("password")
        password_2 = cleaned_data.get("password_2")
        if password is not None and password != password_2:
            self.add_error("password_2", "Your passwords must match")
        return cleaned_data

    def save(self, commit=True):
        # Saving the given password as a hash 
        user_info = super().save(commit=False)
        user_info.set_password(self.cleaned_data["password"])
        if commit:
            user_info.save()
        return user_info


class UserAdminChangeForm(forms.ModelForm):
    """
 The form is responsible for user updates and has all user fields
. It also uses the admin's
 password hash for display. 
    """
    password = ReadOnlyPasswordHashField()

    class Meta:
        model = User
        fields = ['email', 'password', 'is_active', 'admin']

    def clean_password(self):
	# return the first value irrespective of what is provided by the user
        return self.initial["password"]

Updating Django Admin

Let’s put our forms as mentioned above to good use.

from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.contrib.auth.admin import UserAdmin as MainUserAdmin

from .forms import UserAdminCreationForm, UserAdminChangeForm

User = get_user_model()
# Remove Group Model from admin. We're not using it.

admin.site.unregister(Group)

class UserAdmin(MainUserAdmin):
  # These forms forms to add and change user instances
  form = UserAdminChangeForm
  add_form = UserAdminCreationForm
  
  # The fields to be used in displaying the User model.
  # These override the definitions on the base UserAdmin
  # that reference specific fields on auth.User.
  list_display = ['email', 'admin']
  list_filter = ['admin']
  fieldsets = (
      (None, {'fields': ('email', 'password')}),
      ('Personal info', {'fields': ()}),
      ('Permissions', {'fields': ('admin',)}),
  )
  # add_fieldsets is not a standard ModelAdmin attribute. UserAdmin
  # overrides get_fieldsets to use this attribute when creating a user.
  add_fieldsets = (
      (None, {
          'classes': ('wide',),
          'fields': ('email', 'password1', 'password2')}
      ),
  )
  search_fields = ['email']
  ordering = ['email']
  filter_horizontal = ()
  
  admin.site.register(User, UserAdmin)

Models for custom users and proxy users

Installing a custom user model will break any proxy model extending User, which is one of the limitations of custom user models. Proxy models must have a concrete base class; establishing a custom user model disables Django’s ability to identify the base class reliably.

If your project uses proxy models, you must either adapt the proxy to extend the user model in use or merge the behavior of your proxy into your User subclass.

Conclusion

Although the authentication provided by Django is enough in most circumstances, you may have requirements that are not covered by the defaults. Understanding what parts of the supplied system are expandable or replaceable is necessary for customizing authentication in your projects. This document has clearly explained how the authentication system is changed.

Similar Posts

Leave a Reply

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