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.