Starting a Django Project With the Right User Model

Learn what the different user models are in Django and how to use them correctly

Jasmine Webb
Oct 15, 2019 · 6 min read
The Django project donations icon: a pink pixel art heart.
The Django project donations icon: a pink pixel art heart.

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:

  1. 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.
  2. 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:

Profile.objects.get(user=req.user)

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()
...
class Profile(models.Model):
custom_field = models.Whatever()
...
# these are the signals
@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()

(Code snippet from here.)

I’d recommend using a user profile from the start but that’s just my preference. They’re relatively easy to add later. Read more about senders/signals and get more code samples or read the docs.

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_superuser and is_staff could mitigate the damage.

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

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 accounts_user instead.

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_staff/is_superuser, and some advanced use cases.

  1. AbstractBaseUser

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 ForeignKey field.
  • 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.

Recap

  1. 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 pass under the declaration) because adding one later is a huge pain.
  2. 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.

Further Reading

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.

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:

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:

Please let me know if anything is incorrect or unclear by leaving highlight comments. Thanks for reading!

The Startup

Medium's largest active publication, followed by +752K people. Follow to join our community.

Thanks to Marianna Campbell

Jasmine Webb

Written by

Developer and appsec researcher living in Portland, Oregon.

The Startup

Medium's largest active publication, followed by +752K people. Follow to join our community.

Jasmine Webb

Written by

Developer and appsec researcher living in Portland, Oregon.

The Startup

Medium's largest active publication, followed by +752K people. Follow to join our community.

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