Django tips: extending the User model
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:
- 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.
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
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.
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.
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.
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.
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
It’s a completely consistent generic interface. Using the standard API in the example above means hard-coding
u.userprofileall 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
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.
- 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.