How to Create/Extend Django Custom User Model

Rohit Lakhotia
May 31, 2020 · 12 min read
Image for post
Image for post

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:

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

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.

models.pyfrom 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,

models.pyfrom 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.

models.pyfrom 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:

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

For using it in views:

views.pydef 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

forms.pyfrom 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

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

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:

models.pyfrom 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:

admin.pyfrom 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:

settings.pyAUTH_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.

models.pyfrom 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

settings.pyAUTH_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:

models.pyfrom 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

settings.pyAUTH_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:

models.pyfrom 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:

models.pyfrom 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

model.pyfrom 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.

About the Author

Image for post
Image for post

Originally published at https://learnitfree.org on May 31, 2020.

Learnitfree

We provide the Best Free Courses and Tutorials related to topics like Java, Python, Swift, Android.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store