How to Extend Django User Model

How to Extend Django User Model

Django’s built-in authentication system is excellent. We can use it out-of-the-box, which can result in saving a lot of development and testing time. It is extremely safe and fits most of the use cases. But sometimes we need to extend some functionalities so to fit our Web application.

Commonly you want to store some more data or fields related to the user. If your Web application has an eCommerce appeal, you might want to store payment info, the location of the user, address and other things like that.

In this tutorial, I will show you different strategies you can use to extend the Django User Model. Nothing to worry about, you don’t have to do everything from scratch.

Ways to Extend the Existing User Model

Here, I am going to show you 4 different ways to extend the existing Django user model.


Option 1: Extending User Model Using a Proxy Model

What is a Proxy Model?
The proxy model is a method in which you are not creating a new table in the database. It is used to change the behavior of a current model (e.g. adding new methods, etc.) without affecting the existing database.

When should I use a Proxy Model?
You can use a Proxy Model when you don’t need to store extra information in the database, but simply to change the model’s query Manager or to add extra methods.

That’s what I need! – Take me to the instructions.

Video Tutorial – Take me to the instructions.


Option 2: Extending User Model Using a One-To-One Link

What is a One-To-One Link?
In One-To-One link, the Django model is going to have its own database table and will hold a One-To-One relationship with the existing User Model through a OneToOneField

When should I use a One-To-One Link?
One-To-One Link can be used when you need to collect extra information about the existing User Model that’s not related to the authentication process. For example, User Profile.

That’s what I need! – Take me to the instructions.


Option 3: Extending User Model Using a Custom Model Extending AbstractBaseUser

What is a Custom User Model Extending AbstractBaseUser?
It is an entirely new User model that inherits from AbstractBaseUser. There are special things you need to take care of i.e update some references in settings.py at the beginning of the project.

When should I use a Custom User Model Extending AbstractBaseUser?
You can use a Custom User Model when your application has specific requirements in relation to the authentication process. For example, using an email address as your identification instead of a username.

That’s what I need! – Take me to the instructions.


Option 4: Extending User Model Using a Custom Model Extending AbstractUser

What is a Custom User Model Extending AbstractUser?
It is a new User model that inherits from AbstractUser. There are special things you need to take care of i.e update some references in settings.py at the beginning of the project.

When should I use a Custom User Model Extending AbstractUser?
You can use it when you are happy with the way Django handles the authentication process and you wouldn’t change anything on it. Yet, you want to add some additional information straight away in the User model, without having to create an extra class (like in 2nd Option).

That’s what I need! – Take me to the instructions.


Extending User Model Using a Proxy Model

See the below example:

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

class Person(User):
    objects = PersonManager()
    
    class Meta:
        proxy = True
        ordering = ('first_name', )
    
    def do_something(self):
        ... 

In this example, we have defined a Proxy Model named Person. In Meta class, we are adding the property proxy = True, which tells Django this is a Proxy Model.

Above case, you can redefine the default ordering as shown in the example. I have assigned a custom Manager to the model, and also defined a new method do_something.

Note that User.objects.all() and Person.objects.all() will query the same database table. The only difference will be the behavior we define for the Proxy Model.


We will be creating a new Django Model to store some extra information that relates to the User Model.

Create a Model Profile:

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.TextField(max_length=500, blank=True)
    location = models.CharField(max_length=30, blank=True)
    birth_date = models.DateField(null=True, blank=True) 

Now we will define signals so that our Profile model will be created/updated automatically when the User instance is created or updated.

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.TextField(max_length=500, blank=True)
    location = models.CharField(max_length=30, blank=True)
    birth_date = models.DateField(null=True, blank=True)

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

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

Here, whenever a save event occurs, we are hooking the create_user_profile and save_user_profile methods to the User model. This kind of signal is called post_save.

Let’s see how we can use it.

See the below example of Dango template:

<h2>{{ user.get_full_name }}</h2>
<ul>
  <li>Username: {{ user.username }}</li>
  <li>Location: {{ user.profile.location }}</li>
  <li>Birth Date: {{ user.profile.birth_date }}</li>
</ul> 

How about inside a view method?

def update_profile(request, user_id):
    user = User.objects.get(pk=user_id)
    user.profile.bio = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit...'
    user.save() 

You will never have to call the Profile’s save method. Everything is done through the User model.

What if I’m using Django Forms?

Did you know that you can process more than one form at once? Check out this snippet below:

forms.py

class UserForm(forms.ModelForm):
    class Meta:
        model = User
        fields = ('first_name', 'last_name', 'email')

class ProfileForm(forms.ModelForm):
    class Meta:
        model = Profile
        fields = ('url', 'location', 'company') 

views.py

 class ProfileUpdateView(LoginRequiredMixin, TemplateView):
    user_form = UserForm
    profile_form = ProfileForm
    template_name = 'common/profile-update.html'

    def post(self, request):

        post_data = request.POST or None

        user_form = UserForm(post_data, instance=request.user)
        profile_form = ProfileForm(post_data, instance=request.user.profile)

        if user_form.is_valid() and profile_form.is_valid():
            user_form.save()
            profile_form.save()
            messages.success(request, 'Your profile was successfully updated!')
            return HttpResponseRedirect(reverse_lazy('profile'))

        context = self.get_context_data(
                                        user_form=user_form,
                                        profile_form=profile_form
                                    )

        return self.render_to_response(context)     

    def get(self, request, *args, **kwargs):
        return self.post(request, *args, **kwargs)

profile.html

<form method="post">
  {% csrf_token %}
  {{ user_form.as_p }}
  {{ profile_form.as_p }}
  <button type="submit">Save changes</button>
</form> 

Sometimes, there are problems that can be caused, like firing hundreds or thousands of queries. This problem can be mitigated using the select_related method.

Knowing beforehand you will need to access a related data, you can prefetch it in a single database query below:

users = User.objects.all().select_related('profile')

Extending User Model Using a Custom Model Extending AbstractBaseUser

There was a situation for me where I had to use an email address as the authentication token instead of a username. Also, there was no need for the is_staff flag, as I wasn’t using the Django Admin.

See the below model I had to define:

from __future__ import unicode_literals

from django.db import models
from django.core.mail import send_mail
from django.contrib.auth.models import PermissionsMixin
from django.contrib.auth.base_user import AbstractBaseUser
from django.utils.translation import ugettext_lazy as _

from .managers import UserManager


class User(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(_('email address'), unique=True)
    first_name = models.CharField(_('first name'), max_length=30, blank=True)
    last_name = models.CharField(_('last name'), max_length=30, blank=True)
    date_joined = models.DateTimeField(_('date joined'), auto_now_add=True)
    is_active = models.BooleanField(_('active'), default=True)
    is_staff = models.BooleanField(_('staff'), default=True) 
    avatar = models.ImageField(upload_to='avatars/', null=True, blank=True)

    objects = UserManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []

    class Meta:
        verbose_name = _('user')
        verbose_name_plural = _('users')

    def get_full_name(self):
        '''
        Returns the first_name plus the last_name, with a space in between.
        '''
        full_name = '%s %s' % (self.first_name, self.last_name)
        return full_name.strip()

    def get_short_name(self):
        '''
        Returns the short name for the user.
        '''
        return self.first_name

    def email_user(self, subject, message, from_email=None, **kwargs):
        '''
        Sends an email to this User.
        '''
        send_mail(subject, message, from_email, [self.email], **kwargs) 

Since we are inheriting from the AbstractBaseUser we have to follow some rules in order to execute:

  • USERNAME_FIELD: It is a string describing the name of the field on the User model that is used as the unique identifier. The field must be unique (i.e., have unique=True set in its definition).
  • REQUIRED_FIELDS: It is a list of the field names that will be prompted when creating a user via the createsuperuser command.
  • is_active: A boolean attribute that indicates whether the user is considered “active”;
  • get_full_name(): A longer formal identifier for the user. A common interpretation would be the full name of the user, but it can be any string that identifies the user.
  • get_short_name(): A short, informal identifier for the user. A common interpretation would be the first name of the user.

Okay, let’s move ahead. I had to define my own UserManager because the existing manager defines the create_user and create_superuser methods.

So, here’s my UserManager looks like:

from django.contrib.auth.base_user import BaseUserManager

class UserManager(BaseUserManager):
    use_in_migrations = True

    def _create_user(self, email, password, **extra_fields):
        """
        Creates and saves a User with the given email and password.
        """
        if not email:
            raise ValueError('The given email must be set')
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_user(self, email, password=None, **extra_fields):
        extra_fields.setdefault('is_superuser', False)
        return self._create_user(email, password, **extra_fields)

    def create_superuser(self, email, password, **extra_fields):
        extra_fields.setdefault('is_superuser', True)

        if extra_fields.get('is_superuser') is not True:
            raise ValueError('Superuser must have is_superuser=True.')

        return self._create_user(email, password, **extra_fields) 

Basically I’ve clean up of the existing UserManager, removing the is_staff property and the username.

Now finally we have to update our settings.py. In settings.py defining AUTH_USER_MODEL property.

AUTH_USER_MODEL = 'core.User' 

This way we are telling Django to use our custom model instead of the default one. In the example above, I’ve created the custom model inside an app named core.

How should I reference this model?

There are two ways. Considering a model named Course:

from django.db import models
from testapp.core.models import User

class Course(models.Model):
    slug = models.SlugField(max_length=100)
    name = models.CharField(max_length=100)
    tutor = models.ForeignKey(User, on_delete=models.CASCADE) 

This is perfectly okay. But if you are creating a reusable app, that will be publicly available, it is strongly advised to use the following method:

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

class Course(models.Model):
    slug = models.SlugField(max_length=100)
    name = models.CharField(max_length=100)
    tutor = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)

Extending User Model Using a Custom Model Extending AbstractUser

The class django.contrib.auth.models.AbstractUser provides the full implementation of the default User as an abstract model which is a very straightforward way.

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

class User(AbstractUser):
    bio = models.TextField(max_length=500, blank=True)
    location = models.CharField(max_length=30, blank=True)
    birth_date = models.DateField(null=True, blank=True) 

After this we have to update our settings.py defining the AUTH_USER_MODEL property as:

AUTH_USER_MODEL = 'core.User' 

This is a similar way as done in the previous method. Keep in mind that this should be done ideally at the beginning with extra care, as it will change the whole database schema. Also, create foreign keys to the User model. Import the settings from django.conf import settings and refer them to the settings.AUTH_USER_MODEL instead of referring directly to the custom User model.

Video Tutorial

One To One Link Field
Share