authentication   7   9789
Creating custom user model  and custom authentication in Django

While working on some Django project you might feel that the default user model is not fulfilling all the requirements.

For example, you may want to authenticate users by email Id and not by username. You may want to store some more extra information in the user model. In short, you might want to define your own custom user model.

In particular, we may encounter one out of below two scenarios:

  • You are happy with the way default authentication but you need to store extra information in the model itself.
  • You want different authentication process, for example using email, but do not want to store extra information.


We will discuss the second approach here. Our approach will use custom-defined user model and custom define backend authentication mechanism.


How to create a custom user model:

First of all, create the user model class in your models directory. Add the usermanager class also.

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


class MyUserManager(BaseUserManager):
    use_in_migrations = True
    
    # python manage.py createsuperuser
    def create_superuser(self, email, is_staff, password):
        user = self.model(
                          email = email,                         
                          is_staff = is_staff,
                          )
        user.set_password(password)
        user.save(using=self._db)
        return user

class UserModel(AbstractBaseUser):
    sys_id = models.AutoField(primary_key=True, blank=True)        
    email = models.EmailField(max_length=127, unique=True, null=False, blank=False)
    is_staff = models.BooleanField()
    is_active = models.BooleanField(default=True)
    
    objects = MyUserManager()

    USERNAME_FIELD = "email"
    # REQUIRED_FIELDS must contain all required fields on your User model, 
    # but should not contain the USERNAME_FIELD or password as these fields will always be prompted for.
    REQUIRED_FIELDS = ['is_staff']

    class Meta:
        app_label = "accounts"
        db_table = "users"

    def __str__(self):
        return self.email

    def get_full_name(self):
        return self.email

    def get_short_name(self):
        return self.email


    # this methods are require to login super user from admin panel
    def has_perm(self, perm, obj=None):
        return self.is_staff

    # this methods are require to login super user from admin panel
    def has_module_perms(self, app_label):
        return self.is_staff


Since I don't require anything except email, password, is_active and  is_staff, I removed everything else from the model.

Is_staff is required for user login in the admin panel. If you are not going to use admin site then is_staff can be removed as well.

Define this newly created custom user model in the settings file. The project should know that we are going to use other than the default user model.

AUTH_USER_MODEL = 'accounts.UserModel'
AUTHENTICATION_BACKENDS = ('accounts.backends.MyAuthBackend','django.contrib.auth.backends.ModelBackend',)

 
As shown in the code above, mention the name of custom authentication backend we will create to support our custom user model.

AUTHENTICATION_BACKENDS  is a list of backends. If the first one fails to authenticate, the second is used as a fallback. Here first authentication backend in the list is the one we will create and second is the Django's default authentication backend.

Create the custom authentication backend.


backends.py :

from accounts.models import UserModel
import logging


class MyAuthBackend(object):
    def authenticate(self, email, password):    
        try:
            user = UserModel.objects.get(email=email)
            if user.check_password(password):
                return user
            else:
                return None
        except UserModel.DoesNotExist:
            logging.getLogger("error_logger").error("user with login %s does not exists " % login)
            return None
        except Exception as e:
            logging.getLogger("error_logger").error(repr(e))
            return None

    def get_user(self, user_id):
        try:
            user = UserModel.objects.get(sys_id=user_id)
            if user.is_active:
                return user
            return None
        except UserModel.DoesNotExist:
            logging.getLogger("error_logger").error("user with %(user_id)d not found")
            return None

 

Now run makemigrations and migrations command. This will create the custom user model in the database. (Assuming database settings are ok).

$ python manage.py makemigrations
$ python manage.py migrate


Now create a super user. is_staff should be true for this user.

$ python manage.py createsuperuser

Running this command will ask you email id, is_staff, password and confirm password.

After the user is created successfully, go to admin login and confirm if you are able to log in.


If you are getting error Please enter the correct email and password for a staff account. Note that both fields may be case-sensitive, make sure you have is_staff field in the model and has_perm and pas_module_perm methods define and they return true.

If you are able to login successfully, Good.


How to Register a Custom user model with the Admin Site:

In the above section, we created a custom user model with them help of AbstractBaseUser. Custom user models created by this method are not registered normally with the admin site. For this, we need to override Django's default UserAdmin.

In the app directory, create a file admin.py  if the file does not exist already.

from django import forms
from django.contrib import admin
from django.contrib.auth.models import Group
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.forms import ReadOnlyPasswordHashField

from accounts.models import UserModel


class UserCreationForm(forms.ModelForm):
    #A form for creating new users. Includes all the required
    #fields, plus a repeated password.
    password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
    password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)

    class Meta:
        model = UserModel
        fields = ('email')

    def clean_password2(self):
        # Check that the two password entries match
        password1 = self.cleaned_data.get("password1")
        password2 = self.cleaned_data.get("password2")
        if password1 and password2 and password1 != password2:
            raise forms.ValidationError("Passwords don't match")
        return password2

    def save(self, commit=True):
        # Save the provided password in hashed format
        user = super(UserCreationForm, self).save(commit=False)
        user.set_password(self.cleaned_data["password1"])
        if commit:
            user.save()
        return user


class UserChangeForm(forms.ModelForm):
    #A form for updating users. Includes all the fields on
    #the user, but replaces the password field with admin's
    #password hash display field.
    
    password = ReadOnlyPasswordHashField()

    class Meta:
        model = UserModel
        fields = ('email', 'password', 'is_active','is_staff')

    def clean_password(self):
        # Regardless of what the user provides, return the initial value.
        # This is done here, rather than on the field, because the
        # field does not have access to the initial value
        return self.initial["password"]


class UserAdmin(BaseUserAdmin):
    # The forms to add and change user instances
    form = UserChangeForm
    add_form = UserCreationForm

    # 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', 'is_active', 'is_staff')
    list_filter = ('email',)
    fieldsets = (
        (None, {'fields': ('email', 'password')}),
        ('Permissions', {'fields': ('is_staff',)}),
    )
    # 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 = ()

# Now register the new UserAdmin...
admin.site.register(UserModel, UserAdmin)
# ... and, since we're not using Django's built-in permissions,
# unregister the Group model from admin.
admin.site.unregister(Group)



Adequate comments are used in the above code to make it easy to understand.

Now restart the server and login to the admin site. You will be able to see the users model and you will be able to add, edit and delete the model objects.

Let me know in case of any question.  

References: https://docs.djangoproject.com/en/1.10/topics/auth/customizing/

authentication   7   9789

7 thoughts on 'Creating Custom User Model And Custom Authentication In Django'
Sreenivas :
The same example will work for custom student roll number field (replace with email). My aim is students login with their roll numbers these are unique.
Admin :
You are right Sreenivas, It will work for roll numbers too.

Sreenivas :
Creating Custom User Model And Custom Authentication In Django:am getting error when applying makemigrations. so , plz can u provide code for this example
Admin :
I don't have any sample code available as of now for this. Please share your code and error and we may be able to help you.

Stephen :
m getting this error when trying to migratedjango.db.utils.OperationalError: (1170, "BLOB/TEXT column 'username' used in key specification without a key length")

Amit Kumar :
i'm getting error for user.DoesNotExist.it says user has no attribute called DoesNotExist
Admin :
Amit, can you please share complete error trace via pastebin service.

Jovany :
How can I change the column from where it takes the password? since it generates the following error:"django.db.utils.OperationalError: (1054," Unknown column 'collaborators.password' in 'field list' ")."I want the password from a column called 'pass_generic'

Desmond Audu :
This is the error i got after i created the UserModel class super().__init__(*args, **kwargs)TypeError: __init__() got an unexpected keyword argument 'primary_true'

Ajay :
Can you show us how to set url guard to check authentication of logging before going to perticular url. If user in not authenticated he should be redirected to login page.

Leave a comment:


*All Fields are mandatory. **Email Id will not be published publicly.


SUBSCRIBE
Please subscribe to get the latest articles in your mailbox.


Recent Posts:






© pythoncircle.com 2018-2019
Contact Us: code108labs [at] gmail.com
Address: 3747 Smithfield Avenue, Houston, Texas