Django User Model Extension Strategies
Django has an excellent user authentication system built in. In most cases, we can use it out of the box, which saves a lot of time for developers and testers. But sometimes we need to expand it to meet the needs of our site.
Typically, there is a need to store additional data about users, for example, a brief biography (about), date of birth, location, and other similar data.
This article will talk about strategies that you can use to extend your custom Django model, rather than write it from scratch.
We briefly describe the strategies for expanding the Django user model and the need for their application. And then we will reveal the configuration details for each strategy.
This is the least time consuming way to extend your user model. It is completely limited in shortcomings, but also does not have any wide possibilities.
In the above example, we defined the extension of the model by
Also in the example, a custom model manager is assigned, the default order is changed, and a new method is defined
It is worth noting that
Most likely, this is what you need. Personally, I use this method in most cases. We will be creating a new Django model to store additional information that is related to the user model.
Keep in mind that using this strategy generates additional queries or connections within the query. Basically, all the time when you request data, an additional request will be triggered. But this can be avoided in most cases. I will say a few words about how to do this, below.
Now let's add a little magic: let's define signals so that our model is
We are "hooked"
And now an example Django template using data
And you can like this:
Generally speaking, you should never call persistence methods
If you need to work with forms, then below are sample code for this. Remember that you can process data from more than one model (class) at a time (from one form).
And about the promised query optimization. In full, the issue is considered in another article of mine .
But, in short, the Django relationship is lazy. Django makes a request to the database table, if you need to read one of its fields. Regarding our example, using the method will be effective
Knowing in advance that you need to access the associated data, you can proactively do this with one request:
Expansion
To be honest, I try to avoid this method at all costs. But sometimes this is not possible. And that is wonderful. There is hardly such a thing as a better or worse solution. For the most part, there is a more or less suitable solution. If this is the most suitable solution for you, then go ahead.
I had to do it once. Honestly, I don’t know if there is a cleaner way to do this, but I haven’t found anything else.
I needed to use the email address as
This is how I defined my own user model:
I wanted to keep it as close as possible to the “standard” user model. Having inherited from,
I also had my own
My UserManager looked like this:
In fact, I cleared the existing
Finishing touch. Need to change
Thus, we tell Django to use our custom model instead of the supplied “in box”. In the example above, I created a custom model inside the application with the name
How to reference this model?
There are two ways. Consider a model called
Generally normal. But, if you plan to use the application in other projects or distribute, it is recommended to use the following approach:
Expansion
This is quite simple, since the class
After that, you need to change
As in the previous strategy, ideally, this should be done at the beginning of the project and with special care, as it will change the entire database schema. It is also a good rule to create keys to the user model through importing settings
Fine! We looked at four different expansion strategies for the “standard” user model. I tried to do this in as much detail as possible. But, as I said, there is no better solution. Everything will depend on what you want to receive.
Typically, there is a need to store additional data about users, for example, a brief biography (about), date of birth, location, and other similar data.
This article will talk about strategies that you can use to extend your custom Django model, rather than write it from scratch.
Expansion strategies
We briefly describe the strategies for expanding the Django user model and the need for their application. And then we will reveal the configuration details for each strategy.
Simple model extension (proxy)
This strategy is without creating new tables in the database. Used to change the behavior of an existing model (for example, default ordering, adding new methods, etc.) without affecting the existing database schema.
You can use this strategy when you do not need to store additional information in the database, but simply add additional methods or change the query manager of the model. →Using one-to-one communication with a user model (user profiles)
This is a strategy using an additional ordinary Django model with its own table in the database, which is linked by the user of the standard model through communicationOneToOneField
.
You can use this strategy to store additional information that is not related to the authentication process (for example, date of birth). This is usually called a user profile. →Расширение AbstractBaseUser
This is a strategy for using a completely new user model that is inherited fromAbstractBaseUser
. Requires extreme caution and changing settings insettings.py
. Ideally, it should be done at the beginning of the project, as it will significantly affect the database schema.
You can use this strategy when your site has specific requirements regarding the authentication process. For example, in some cases it makes sense to use an email address as a token identification instead of a username. →Расширение AbstractUser
This is a strategy for using a new user model that is inherited fromAbstractUser
. Requires extreme caution and changing settings insettings.py
. Ideally, it should be done at the beginning of the project, as it will significantly affect the database schema.
You can use this strategy when the Django authentication process itself is completely satisfying and you do not want to change it. However, you want to add some additional information directly to the user model, without the need to create an additional class (as in option 2). →
Simple model extension (proxy)
This is the least time consuming way to extend your user model. It is completely limited in shortcomings, but also does not have any wide possibilities.
models.py
from django.contrib.auth.models import User
from .managers import PersonManager
classPerson(User):
objects = PersonManager()
classMeta:
proxy = True
ordering = ('first_name', )
defdo_something(self):
...
In the above example, we defined the extension of the model by
User
model Person
. We say Django is a proxy model by adding the following property inside class Meta
:Proxy = True
Also in the example, a custom model manager is assigned, the default order is changed, and a new method is defined
do_something()
. It is worth noting that
User.objects.all()
it Person.objects.all()
will query the same database table. The only difference is the behavior that we define for the proxy model.Using one-to-one communication with a user model (user profiles)
Most likely, this is what you need. Personally, I use this method in most cases. We will be creating a new Django model to store additional information that is related to the user model.
Keep in mind that using this strategy generates additional queries or connections within the query. Basically, all the time when you request data, an additional request will be triggered. But this can be avoided in most cases. I will say a few words about how to do this, below.
models.py
from django.db import models
from django.contrib.auth.models import User
classProfile(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 let's add a little magic: let's define signals so that our model is
Profile
automatically updated when creating / changing model data User
.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
classProfile(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)defcreate_user_profile(sender, instance, created, **kwargs):if created:
Profile.objects.create(user=instance)
@receiver(post_save, sender=User)defsave_user_profile(sender, instance, **kwargs):
instance.profile.save()
We are "hooked"
create_user_profile()
and save_user_profile()
an event model storage User
. Such a signal is called post_save
. And now an example Django template using data
Profile
:<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>
And you can like this:
defupdate_profile(request, user_id):
user = User.objects.get(pk=user_id)
user.profile.bio = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit...'
user.save()
Generally speaking, you should never call persistence methods
Profile
. All this is done using the model User
. If you need to work with forms, then below are sample code for this. Remember that you can process data from more than one model (class) at a time (from one form).
forms.py
classUserForm(forms.ModelForm):classMeta:
model = User
fields = ('first_name', 'last_name', 'email')
classProfileForm(forms.ModelForm):classMeta:
model = Profile
fields = ('url', 'location', 'company')
views.py
@login_required@transaction.atomicdefupdate_profile(request):if request.method == 'POST':
user_form = UserForm(request.POST, instance=request.user)
profile_form = ProfileForm(request.POST, 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 redirect('settings:profile')
else:
messages.error(request, _('Please correct the error below.'))
else:
user_form = UserForm(instance=request.user)
profile_form = ProfileForm(instance=request.user.profile)
return render(request, 'profiles/profile.html', {
'user_form': user_form,
'profile_form': profile_form
})
profile.html
<formmethod="post">{% csrf_token %}{{ user_form.as_p }}{{ profile_form.as_p }}<buttontype="submit">Save changes</button></form>
And about the promised query optimization. In full, the issue is considered in another article of mine .
But, in short, the Django relationship is lazy. Django makes a request to the database table, if you need to read one of its fields. Regarding our example, using the method will be effective
select_related()
. Knowing in advance that you need to access the associated data, you can proactively do this with one request:
users = User.objects.all().select_related('Profile')
Expansion AbstractBaseUser
To be honest, I try to avoid this method at all costs. But sometimes this is not possible. And that is wonderful. There is hardly such a thing as a better or worse solution. For the most part, there is a more or less suitable solution. If this is the most suitable solution for you, then go ahead.
I had to do it once. Honestly, I don’t know if there is a cleaner way to do this, but I haven’t found anything else.
I needed to use the email address as
auth token
, but username
absolutely was not needed. In addition, there was no need for a flag is_staff
since I did not use Django Admin. This is how I defined my own user model:
from __future__ import unicode_literals
from django.db import models
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
classUser(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)
avatar = models.ImageField(upload_to='avatars/', null=True, blank=True)
objects = UserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
classMeta:
verbose_name = _('user')
verbose_name_plural = _('users')
defget_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()
defget_short_name(self):'''
Returns the short name for the user.
'''return self.first_name
defemail_user(self, subject, message, from_email=None, **kwargs):'''
Sends an email to this User.
'''
send_mail(subject, message, from_email, [self.email], **kwargs)
I wanted to keep it as close as possible to the “standard” user model. Having inherited from,
AbstractBaseUser
we must follow some rules:USERNAME_FIELD
- a string with the name of the model field, which is used as a unique identifier (unique=True
in the definition);REQUIRED_FIELDS
- a list of field names that will be requested when creating a user using the control commandcreatesuperuser
is_active
- a logical attribute that indicates whether the user is considered “active”;get_full_name()
- long description of the user: not necessarily the full name of the user, it can be any line that describes the user;get_short_name()
- A short description of the user, for example, his name or nickname.
I also had my own
UserManager
. Because the existing manager defines create_user()
and create_superuser()
methods. My UserManager looked like this:
from django.contrib.auth.base_user import BaseUserManager
classUserManager(BaseUserManager):
use_in_migrations = Truedef_create_user(self, email, password, **extra_fields):"""
Creates and saves a User with the given email and password.
"""ifnot 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
defcreate_user(self, email, password=None, **extra_fields):
extra_fields.setdefault('is_superuser', False)
return self._create_user(email, password, **extra_fields)
defcreate_superuser(self, email, password, **extra_fields):
extra_fields.setdefault('is_superuser', True)
if extra_fields.get('is_superuser') isnotTrue:
raise ValueError('Superuser must have is_superuser=True.')
return self._create_user(email, password, **extra_fields)
In fact, I cleared the existing
UserManager
fields username
and is_staff
. Finishing touch. Need to change
settings.py
:AUTH_USER_MODEL = 'core.User'
Thus, we tell Django to use our custom model instead of the supplied “in box”. In the example above, I created a custom model inside the application with the name
core
. How to reference this model?
There are two ways. Consider a model called
Course
:from django.db import models
from testapp.core.models import User
classCourse(models.Model):
slug = models.SlugField(max_length=100)
name = models.CharField(max_length=100)
tutor = models.ForeignKey(User, on_delete=models.CASCADE)
Generally normal. But, if you plan to use the application in other projects or distribute, it is recommended to use the following approach:
from django.db import models
from django.conf import settings
classCourse(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)
Expansion AbstractUser
This is quite simple, since the class
django.contrib.auth.models.AbstractUser
provides a complete implementation of the user model by default as an abstract model.from django.db import models
from django.contrib.auth.models import AbstractUser
classUser(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 that, you need to change
settings.py
:AUTH_USER_MODEL = 'core.User'
As in the previous strategy, ideally, this should be done at the beginning of the project and with special care, as it will change the entire database schema. It is also a good rule to create keys to the user model through importing settings
from django.conf import settings
and using settings.AUTH_USER_MODEL
instead of directly referring to the class User
.Summary
Fine! We looked at four different expansion strategies for the “standard” user model. I tried to do this in as much detail as possible. But, as I said, there is no better solution. Everything will depend on what you want to receive.
- A simple extension of the model (proxy) - you are satisfied with the authentication process and the storage of user data, you just would like to change some behavior.
- Using one-to-one communication with the user model (user profiles) - you are satisfied with the authentication process, but I would like to add additional user data (the data will be stored in a special model).
Расширение AbstractBaseUser
- The authentication process "out of the box" does not suit you.Расширение AbstractUser
- you are satisfied with the authentication process, but I would like to add additional user data (the data will be stored directly in the user model, and not in a special model).
Original