Django tips: extending the User model

Published June 6, 2006. Filed under: Django, Frameworks.

One of Django‘s great strengths is its built-in user and authentication system; the bundled application django.contrib.auth includes models for users, user groups and permissions and views for logging users in and out and changing and resetting their passwords. This is enough to cover the needs of a huge number of sites and, after the admin, the auth system is probably the most popular bundled application Django ships (or maybe the other way around, since the admin requires the auth system to be installed).

Because of the auth system’s popularity, though, one of the most common questions people ask about Django is “how do I extend the User model?” By default, users have a small but useful set of fields which take care of most common use cases, but there are plenty of times where it’d be extremely handy to be able to add just a few more to suit a particular application’s needs.

There are a few immediately obvious ways to do this:

  1. Modify the User model in place before installing the auth application. This is easy to do at first, but can lead to maintenance headaches when it comes time to upgrade Django. If you’re tracking trunk through Subversion that can be much more of an annoyance than it’s worth.
  2. Copy the auth application over into your own project and modify it to your needs. This avoids some of the maintenance troubles, but removes the utility of Django bundling an auth system in the first place. It can also cause compatibility problems with other applications which expect the User model to be in django.contrib.auth.
  3. Subclass the User model and add the fields you need. As I’m writing this, the option to subclass is only available in older releases of Django — model subclassing is being refactored in trunk right now — and even then, getting Django to use your subclassed User model instead of the original (via replaces_module, which was ugly and undocumented and was thankfully removed a while back) can involve quite a bit of work.
  4. Create an entirely new model with the extra fields you want, and relate it back to the built-in User model with a OneToOneField. This is by far the easiest method, and Django has a feature which makes this even better; we’ll see how that works in a minute.

Edit: I’ve seen a couple people mentioning that the method I describe below is “the best way until model subclassing is fixed in trunk”. Please be sure to read the comment in item 3 above about how, even when subclassing is working again, trying to do that will be much more difficult than it would seem — the “replaces_module” option has been removed, so there is no longer any way to tell Django to use your subclass in place of the built-in User class.

Let’s extend the User model

For sake of a simple example, let’s assume we want to add three extra fields for users: the URL of their website, their phone number and their home address. We begin by defining our new model; I’m going to call it UserProfile for reasons which will become apparent shortly, but feel free to call it whatever you like — the name of the model really doesn’t matter.

Updated January 30 2007: the Django book now officially recommends using a foreign key with “unique=True”. So do that instead of the OneToOne. I’ve updated the code snippet to reflect this.

Here’s the code:

from django.db import models
from django.contrib.auth.models import User
     
class UserProfile(models.Model):
    url = models.URLField()
    home_address = models.TextField()
    phone_numer = models.PhoneNumberField()
    user = models.ForeignKey(User, unique=True)

Once the model is defined, we can install the app it lives in and it’ll be immediately available. At this point we can take advantage of Django’s object-relational mapping; given a User, we can access their UserProfile, and given a UserProfile we can access the User. But there’s one more thing we can do that’ll get Django to help us out a bit more.

Quick edit because I get asked this a lot: read the next paragraph carefully. The value of the setting is not “appname.models.modelname”, it’s just “appname.modelname”. The reason is that Django is not using this to do a direct import; instead, it’s using an internal model-loading function which only wants the name of the app and the name of the model. Trying to do things like “appname.models.modelname” or “projectname.appname.models.modelname” in the AUTH_PROFILE_MODULE setting will cause Django to blow up with the dreaded “too many values to unpack” error, so make sure you’ve put “appname.modelname”, and nothing else, in the value of AUTH_PROFILE_MODULE.

All we have to do is open up the Django settings file and add a new setting called AUTH_PROFILE_MODULE; this should be of the form “appname.modelname”, where “appname” is the name of your application and “modelname” is the name of your new model. So if our application was called “myapp”, for example, we’d add this:

AUTH_PROFILE_MODULE = 'myapp.UserProfile'

There’s a method on the built-in User model called get_profile(), which keys off this setting; if AUTH_PROFILE_MODULE is defined, then calling get_profile() on any User will return the associated object from your custom class. So, for example, we could do something like this:

from django.contrib.auth.models import User
u = User.objects.get(pk=1) # Get the first user in the system
user_address = u.get_profile().home_address

And that’s why I chose to call the example model “UserProfile”; you don’t have to name it like that, but I think giving it a name that involves “profile” can be useful to help you remember what it’s for.

More than meets the eye

At first it might not seem like this offers any great advantage over Django’s normal API — which would let us do u.userprofile.home_address — but there are a couple important features here that aren’t present in the standard API access for a OneToOneField:

  1. It’s a completely consistent generic interface. Using the standard API in the example above means hard-coding u.userprofile all over the place; what happens if you later change the name of that model, or decide you need to reuse that code somewhere else? Using the AUTH_PROFILE_MODULE setting and get_profile() makes your code more robust and more portable. You can even use introspection of the object returned by get_profile() (which is automatically cached, by the way, to save you from having to hit your database too often) to make your code completely generic.
  2. It makes site-specific user customization insanely easy. If you’re using Django’s bundled ‘sites’ application to manage multiple sites which each have their own settings files, each one can use a different custom model tailored to its needs. And since the interface is consistent, you can write less code because each site only needs to know what to do with these objects, not how to fetch them.

Better code. More portable code. Less code. And it’s incredibly easy to set up. Things like this are why I love Django.