Starting a Django Project With the Right User Model
Learn what the different user models are in Django and how to use them correctly
Django has solid docs and strong opinions. One thing it has strong opinions on is user models. There are a few important tips and best practices for starting these off on the right foot. Follow them to avoid migration headaches as your project grows.
This article is meant to be read all the way through. It is especially for beginners but may have some helpful references for more advanced Django developers. If you’re looking for copy-pasteable code snippets instead of an explanation, check out this blog post or look at an example app.
If you’re already experienced with
AbstractUser and using profile models, you may want to skip to the recap at the end.
Why Different User Models? What Are They For?
If you need to add fields to a user account, there are a few things you might want to do, and they fall into two broad categories:
- User bios, profile pictures, taglines, site usage metrics, UI preferences (dark/light mode), and things that don’t impact how the user logs in or is authenticated.
- Email (if used for login), API tokens, username, password, superuser/staff boolean flags, and things that impact what the user is allowed to do/see or how they access the site.
The first should use a one-to-one model, the second should go in an extended user model.
This type of model is essentially just a model with this field in it:
user = models.OneToOneField(User, on_delete=models.CASCADE)
This adds a reference to the actual
User the model is associated with.
A good example here would be a user profile. You’d use this to save a user’s bio, maybe their birth date, or profile photo.
The user model doesn’t need to know about the profile, but the profile should know what user it belongs to. When you look this up, you’ll search the profiles by user and get the field from there, like this:
For something every user should have from the moment they create an account (like a profile), you’ll need to add signals to tell the profile model to make a new instance of itself when a user is created. They look like this:
from django.db.models.signals import post_save
from django.dispatch import receiver# not needed if defined in the same place as custom user model
from django.contrib.auth import get_user_model
User = get_user_model()
custom_field = models.Whatever()
...# these are the signals
def create_user_profile(sender, instance, created, **kwargs):
def save_user_profile(sender, instance, **kwargs):
Django will make a table in the database, especially for these profile items called
appname_modelname instead of putting the fields in the same table as a user’s password and login info.
One-to-one models are good here because they separate code and data by concern. Instead of having one giant user model with a field for every little setting, you can have models for specific uses.
A blog, for example, will have a user profile that contains public info about an author, and blog posts will have their own model that has foreign key relationships to their authors.
Side note: It’s pretty hard to end up with an SQL injection in Django. (I’ve tried: it is quite literally my job.) But, in the very unfortunate case that you do, keeping data that users edit directly separate from flags like
is_staff could mitigate the damage.
Extended AbstractUser model
To add fields directly to a user model, you can’t just use the built-in default user. Instead, you’ll need to use your own. Luckily, Django’s
AbstractUser class makes this simple.
A custom user model is a huge pain to switch over to if there are already users in the database. Which is why it is recommended to start all projects extending
AbstractUser. It’s as easy as this:
from django.contrib.auth.models import AbstractUserclass User(AbstractUser):
custom_field = models.CharField(max_length=200)
You’ll also need to tell Django that you’re using it by adding this to
AUTH_USER_MODEL = 'yourapp.User'
I usually start my projects with a special app called
accounts (check it out on GitHub) that has its own user and ways to handle login/signup.
Every long-term project I’ve done has eventually needed a feature where extending the user model was unavoidable, so now I just extend it from the get-go! There is no downside to doing this and it takes a few minutes that could save you hours of messing with migrations.
The default user makes a table called
auth_user and if you replace it with your own user model, Django is smart enough to do everything normally as long as the regular fields are there too. In the case of the my
accounts app, the database will have a table called
The “import” for a custom user is different. We use
User = get_user_model(). Like this:
from django.contrib.auth import get_user_model
User = get_user_model()
Things you absolutely need an extended user model for include: logging in with email, OAuth, more custom permissions beyond
is_superuser, and some advanced use cases.
There will be rare and advanced cases where
AbstractUser won’t work. If that’s you, you’ll want to look into
AbstractBaseUser and/or consider if a less opinionated framework is a better match for your project.
2. Proxy models
These allow you to add methods (but not fields!) to a model and give it a different name. I’ve personally never needed to use one so this article does not cover them.
3. User groups
Groups can be extended like users and follow many of the same patterns. I’ve never used user groups beyond site admins and superusers and so left them out of this article.
Where to Put Stuff
- Fields that are directly used for authentication should go in your extended user model. In most cases, this will just be email/username (if used to log in) and maybe some permissions flags.
- Things that a user might have many of — and don’t have anything to do with auth (like blog posts) — should be in their own models that link back to the user via a
- Things that a user will only have one of — and don’t impact authentication like a profile — should go in their own model that links back to the user via a one-to-one field.
- Groups should also be in their own model! Django does this by default. In fact, you can extend user groups in the same way as you extend users. If your project has complex requirements for permissions and groups, check out this other great tutorial.
- From the start, most projects should use an extended user model. That means you’re not using the default Django user — you’re using your own model class based on
AbstractUser. You should do this even if you don’t need any additional custom fields (you can put
passunder the declaration) because adding one later is a huge pain.
- Most data should not go directly in the user model. Instead, use a profile model to store data that isn’t relevant to login/auth.
Seriously, if you’re here and confused, go read the docs! They are very approachable and have great examples. Start here for accounts and user authentication.
Customizing authentication in Django | Django documentation | Django
The authentication that comes with Django is good enough for most common cases, but you may have needs not met by the…
User authentication in Django | Django documentation | Django
The Django authentication system handles both authentication and authorization. Briefly, authentication verifies a user…
This article references a post over at Simple is Better Than Complex, a lot because it’s the best guide on the topic I could find! If this article didn’t answer your questions, Vitor Freitas might:
How to Extend Django User Model
The Django's built-in authentication system is great. For the most part we can use it out-of-the-box, saving a lot of…
I re-use an accounts layout for all my side-projects and assembled the whole thing into a neat package for this article. It might be a good place to look for examples (I did my best to make it approachable and easy to use). Check it out on GitHub:
This is an example app that can be used in a Django project to handle authentication and account creation for users. It…
Please let me know if anything is incorrect or unclear by leaving highlight comments. Thanks for reading!