May 31, 2020 by

How to Create/Extend Django Custom User Model

Image defining How to CreateExtend Django Custom User Model

The Django build-in User model out of the box is solid. But it sometimes might not suit your need since it has some predefined field and you might want to add some more field to it like Date of Birth or maybe making email-only authentication since Django has username-password authentication out of the box. That’s when Django’s custom user model comes handy.

Django uses Python language. If you want to learn Python go to Learn Python from the Best Free Python Courses and Tutorials.

Do I need a Custom User Model?

Most of the time you don’t. Django’s built-in User models provide more than enough features to serve you out of the box. But if you want the user to authenticate with an email address instead of username then the custom model could be the right choice.

But that being said Django Official documentation suggests you have a custom model even if you don’t want too so in future if you want to add more information about the user you can do so which is more likely if your app grows.

Application-specific data

The User model is meant to show information related to your user’s authentication or authentication point of view. You might even be tempted to store other application-specific information related to the user.

Since most users will have a profile picture, bio. Data like this could be stored in another model which is referenced with an OneToOneField with the User Model.

Storing application data on the User model

There are some advantages and disadvantages to storing application data in the user model.

  • For smaller applications, it will be simpler. There won’t be many things to look for. Everything in one place. But if the application grows the same model can become clutter in no time.
  • Since everything will be in one place there won’t be a need for advanced database queries. But that would also make your table in the database bloated.

Storing application data in a profile model

The alternative way to store your user application data is to have another model Profile which is referenced with OneToOneField to the User Model.

  • Since the application data is kept on another model the User model is only used for authentication purposes and the Profile model is used for user’s application data. Keeping it clutter-free.
  • But since the application data is different from authentication data than to get the user application data we need to load another object from the database in order to retrieve the data. Depending on your application it could be worth the effort.
  • The decoupling of application data with authentication data makes it easier to make changes in the future.

Default Fields of User Model

The User Model Django provides out of the box has some fields in general:

  1. username: The username that the user will use to authenticate. This will be unique in the table.
  2. email: The email that the user can choose to share. But out-of-the-box it is not unique.
  3. password1: The password user chooses to authenticate. It is not a plain password. It will be hashed for security reasons.
  4. password2: This is used for confirm password. At the Admin side, only one field named password is seen.
  5. first_name: First name of the person.
  6. last_name: Last name of the person.
  7. last_login: The last Date and Time the user logged in.
  8. date_joined: The Date and Time the user joined/authenticated with your website.
  9. groups: The groups the user is in.
  10. user_permissions: Admin permissions if any.
  11. is_staff: Designates whether the user can log into the admin site.
  12. is_active: Designates whether this user should be treated as active. Unselect this instead of deleting accounts.
  13. is_superuser: Designates that this user has all permissions without explicitly assigning them.

Ways to Extend the User model

There are 4 ways to extend an existing User model in Django. Check them below and choose the one that fits your needs.

  1. Proxy Model
  2. Profile Model with OneToOneField with User Model
  3. AbstractBaseUser to Create a Custom User Model
  4. AbstractUser to Create a Custom User Model

1. Proxy Model

Proxy models are a feature that developers might come across and move along without giving much importance. They can come in handy in many situations.

A proxy model is just another class that provides a different interface for the same underlying database model.

A proxy model is actually a subclass of a database-table defining model. Typically creating a subclass of a model results in a new database table with a reference back to the original model’s table ie. multi-table inheritance.

A proxy model doesn’t get its own database table. Instead, it operates on the original table.

It is used to change the behavior of an existing model (e.g. order, new methods, etc.) without changing the existing database schema.

When to use Proxy Model?

When you don’t need to add extra data about but just want extra methods or change the model’s query Manager.

It is a less intuitive way to extend the existing User model and is limited in many ways. You won’t have any drawbacks with this strategy.

from django.contrib.auth.models import User
from .managers import PersonManager

class Person(User):
    objects = PersonManager()

    class Meta:
        proxy = True
        ordering = ('email', )

    def do_something(self):
        ...

Let see what we did here.

  • First, we defined a Proxy Model named Person. To tell Django that it is a proxy model we make proxy = True in class Meta.
  • We assign a custom Manager named PersonManager to the Proxy model by objects = PersonManager( ).
  • Change the default ordering to email.
  • Defined a function do_something.

Also now User.objects.all( ) and Person.objects.all( ) will be using the same database table to query. The only difference has been the behavior of the Proxy Model.

2. Profile Model with OneToOne link with User Model

The idea behind this is to create another table in the database and point the User model to that table in the database with OneToOneField.

What is OneToOne Link?

It’s the Foreign Key relation but with unique=True. So every row in a table would have only one relating row in another table and vice versa.

But, when should I use this?

You should do this when you want to store profile related information like a profile picture, bio, etc. Basically everything related to the user that is not authentication specific.

Most of the time this is what you want.

Since we will be creating another model that stores user’s profile data that will be related to the User model. This will need an additional query or join to access the related data. To access the related data Django needs to fire additional queries.

So let’s see what to do here,

from django.db import models
from django.contrib.auth.models import User

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    bio = models.CharField(null=True, max_length=200)
    birth_date = models.DateField(null=True, blank=True)    
    profile_pic = models.ImageField(default='default.jpg', upload_to='profiles_pics')
    hobby = models.CharField(null=True, max_length=200)

Now since the models are created, but they need to be created whenever a user is created. We need to use signals to do the same.

from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    bio = models.CharField(null=True, max_length=200)
    birth_date = models.DateField(null=True, blank=True)
    profile_pic = models.ImageField(default='default.jpg', upload_to='profiles_pics')
    hobby = models.CharField(null=True, max_length=200)

@receiver(post_save, sender=User)
def create_profile(sender, instance, created, **kwargs):
    if created:
        Profile.objects.create(user=instance)

@receiver(post_save, sender=User)
def save_profile(sender, instance, **kwargs):
    instance.profile.save()

Here, we are telling Django that whenever a save event occurs (signal called post_save) create or save the profile depending on the situation.

So, here whenever an entry/row of a user is created in the table, a new entry/row is also created in the profile table in the database.

But that’s all fine, how do I access this profile data?

You can access this data in Django Templates like:

<h2>{{ user.username }}</h2>
<p>{{ user.email }}</p>
<p>{{ user.profile.bio }}</p>
<p>{{ user.profile.birth_date }}</p>

For using it in views:

def update_profile(request, user_id):
    user = User.objects.get(pk=user_id)
    user.profile.hobby = 'Coding, Chess, Guitar'
    user.save()

Since we have signals setup we don’t have to save the Profile models. If we save the User model the Profile models will also be saved.

In Django, we can have multiple forms in the same templates at once. So, if we want to have a profile page where users email, username and other profile data like bio, etc is shown and can be updated by the user.

First, we will create the forms: form.py

from django import forms
from django.contrib.auth.models import User
from .models import Profile

class UserUpdateForm(forms.ModelForm):
	email = forms.EmailField()

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


class ProfileUpdateForm(forms.ModelForm):
	class Meta:
		model = Profile
		fields = ['profile_pic', 'bio']

To access the forms in views.py

@login_required
def profile(request):
	if request.method == 'POST':
		u_form = UserUpdateForm(request.POST, instance=request.user)
		p_form = ProfileUpdateForm(request.POST, request.FILES, instance=request.user.profile)

		if u_form.is_valid() and p_form.is_valid():
			u_form.save()
			p_form.save()
			messages.success(request, f'Your Profile has been Updated Successfully')
			return redirect('profile')
	else:	
		u_form = UserUpdateForm(instance=request.user)
		p_form = ProfileUpdateForm(instance=request.user.profile)
		context = {
			'u_form': u_form,
			'p_form': p_form
		}
	return render(request, 'users/profile.html', context)

The template: profile.html

<form method="POST" enctype="multipart/form-data">
	{% csrf_token %}
	<fieldset class="form-group">
		<legend class="border-bottom mb-4">Profile Info</legend>
		{{ u_form.as_p }}
		{{ p_form.as_p }}
	</fieldset>
	<button type="submit">Update</button>
</form>

3. AbstractBaseUser to Create a Custom User Model

This method is like starting from a clean slate. Use this method if you want to start from scratch.

This method will have no predefined field, it only contains the authentication functionality, but no actual fields. You have to supply them when you subclass.

It can be helpful in many cases like when you want to use email for authentication instead of username. But there are better ways to do the same by using AbstractUser instead of AbstractBaseUser .

You this at your own will. JK. You will be fine.

To demonstrate this we will be using the same example as in the documentation.

Here is an example of an admin-compliant custom user app. This user model uses an email address as the username, and has a required date of birth; it provides no permission checking beyond an admin flag on the user account.

This model would be compatible with all the built-in auth forms and views, except for the user creation forms. This example illustrates how most of the components work together, but is not intended to be copied directly into projects for production use.

This code would all live in a models.py file for a custom authentication app:

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

class MyUserManager(BaseUserManager):
    def create_user(self, email, date_of_birth, password=None):
        """
        Creates and saves a User with the given email, date of
        birth and password.
        """
        if not email:
            raise ValueError('Users must have an email address')

        user = self.model(
            email=self.normalize_email(email),
            date_of_birth=date_of_birth,
        )

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

    def create_superuser(self, email, date_of_birth, password=None):
        """
        Creates and saves a superuser with the given email, date of
        birth and password.
        """
        user = self.create_user(
            email,
            password=password,
            date_of_birth=date_of_birth,
        )
        user.is_admin = True
        user.save(using=self._db)
        return user

class MyUser(AbstractBaseUser):
    email = models.EmailField(
        verbose_name='email address',
        max_length=255,
        unique=True,
    )
    date_of_birth = models.DateField()
    is_active = models.BooleanField(default=True)
    is_admin = models.BooleanField(default=False)

    objects = MyUserManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['date_of_birth']

    def __str__(self):
        return self.email

    def has_perm(self, perm, obj=None):
        "Does the user have a specific permission?"
        # Simplest possible answer: Yes, always
        return True

    def has_module_perms(self, app_label):
        "Does the user have permissions to view the app `app_label`?"
        # Simplest possible answer: Yes, always
        return True

    @property
    def is_staff(self):
        "Is the user a member of staff?"
        # Simplest possible answer: All admins are staff
        return self.is_admin

Then, to register this custom user model with Django’s admin, the following code would be required in the app’s admin.py file:

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 users.models import MyUser

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 = MyUser
        fields = ('email', 'date_of_birth')

    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().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 = MyUser
        fields = ('email', 'password', 'date_of_birth', 'is_active', 'is_admin')

    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', 'date_of_birth', 'is_admin')
    list_filter = ('is_admin',)
    fieldsets = (
        (None, {'fields': ('email', 'password')}),
        ('Personal info', {'fields': ('date_of_birth',)}),
        ('Permissions', {'fields': ('is_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', 'date_of_birth', 'password1', 'password2'),
        }),
    )
    search_fields = ('email',)
    ordering = ('email',)
    filter_horizontal = ()

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

Finally, specify the custom model as the default user model for your project using the AUTH_USER_MODEL setting in your settings.py:

AUTH_USER_MODEL = 'users.MyUser'

4. AbstractUser to Create a Custom User Model

This is pretty simple to start with and is flexible too. AbstractUser is your User Model the Django framework provides out of the box. Since you will be subclassing it. So now you can change stuff inside the class like using email field instead of username field for authentications.

AbstractUser is a full User model, complete with fields, like an abstract class, so that you can inherit from it and add your own profile fields and methods.

from django.db import models
from django.contrib.auth.models import AbstractUser

class MyUser(AbstractUser):
    bio = models.TextField(max_length=500, blank=True)
    hobby = models.CharField(max_length=200, blank=True)

Update your setting.py to use the new User Model

AUTH_USER_MODEL = 'users.MyUser'

Where users is the app’s name

Django recommends having your own custom model even if you happy with the out of the box User model.

What to do if I just need the default User Model and don’t want to add any other fields?

Do it like this:

from django.contrib.auth.models import AbstractUser

class MyUser(AbstractUser):
    pass

    def __str__(self):
        return self.username

And obviously, tell Django to use your Custom User Model in settings.py

AUTH_USER_MODEL = 'users.MyUser'

Referencing the User model

Now since you have changed your default User Model. You might need to change the way you access it.

Now in Option 2 where we used OneToOneField in Profile Model to relate to User model like this:

from django.db import models
from django.contrib.auth.models import User

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
		# Other Fields

After using our Custom Model, the right/better way is to use get_user_model( ), like this:

from django.db import models
from django.contrib.auth import get_user_model

class Profile(models.Model):
    user = models.OneToOneField(get_user_model(), on_delete=models.CASCADE)
		# Other Fields

or

from django.db import models
from django.conf import settings

class Profile(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
		# Other Fields

Up to you what you choose. Both will work fine.

Reusable apps and AUTH_USER_MODEL

Reusable apps shouldn’t implement a custom user model. A project may use many apps, and two reusable apps that implemented a custom user model couldn’t be used together. If you need to store per user information in your app, use a ForeignKey or OneToOneField to settings.AUTH_USER_MODEL as described above.

Right Choice?

It all ends up on you, your needs.

A project I am working now needed Email Authentication instead of the username-password authentication that Django provides out of the box and we wanted the username field to be removed from my User Model. So we used AbstractUser to subclass my Custom User Model.

To store more information about Users like the profile picture, bio, and other user-related information we created another Model called Profile and we used OneToOneField to relate it to Custom User Model.

Conclusion

It’s always a good idea to use a custom user model if you are still happy with the one provided by Django out of the box.

That’s all for Custom User Model in Django.

Let me know if this post helps you in any way in the comment section below.


Leave a Reply

Please Login to comment
avatar
  Subscribe  
Notify of