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:
django.contrib.auth.
replaces_module, which was ugly and undocumented and was thankfully removed a while back) can involve quite a bit of work.
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.
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.
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:
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.
Better code. More portable code. Less code. And it’s incredibly easy to set up. Things like this are why I love Django.
Comments for this entry are closed. If you'd like to share your thoughts on this entry with me, please contact me directly.
Isn’t use of the
OneToOneFieldcurrently discouraged?The official docs say that, yes, because whatever solution we end up with for model subclassing is probably going to change/somewhat replace one-to-one relationships.
But this is a situation where you probably wouldn’t want to switch over to subclassing regardless (because then we’re back in the hellish situation of how you tell Django that your subclass should be the “real” User class), so I wouldn’t worry about it. If you are concerned about this issue, though, a
ForeignKey, properly secured to ensure that only one profile can ever be created for a given user, would work too.This won’t work in the admin site will it?
OneToOne didn’t work for me, so I used: models.ForeignKey(User, edit_inline=models.TABULAR, num_in_admin=1,min_num_in_admin=1, max_num_in_admin=1,num_extra_on_change=0)
Dagur, if you mean “will this make all of the fields show up on the same page in the admin”, then no. It will work exactly the same as any other
OneToOneFieldin the admin application.João, yeah, doing a
ForeignKeywill work, but as I said above you have to be careful to ensure that only one profile instance gets created per user (since a foreign key enables many-to-one relationships).In which case,
edit_inlinewill let you edit both the user and the associated profile in one admin page.Excellent post James,
You mentioned to Dagur that it will not make the fields show up on the same page in the admin.
A followup question to that - is that possible in an easy-to-follow fashion? Or is it pretty complex? I’m fairly sure its do-able, but how exactly to do it right is a different story.
Doing it as a
ForeignKeyinstead of aOneToOneField, and using theedit_inlineattribute will cause your “extra” fields to show up on the same page in the admin as the mainUserobject, but you will have to be careful to ensure that only one instance of your model ever gets associated with a given user; otherwise things will break.I tried setting the UserProfile to edit_inline=True using the OneToOneField. Now I get a “UserProfile matching query does not exist” if I create a new User and save it without populating anything in the UserProfile section.
Is there something obvious I should be doing that I’m missing (i’m admittedly not particularly clueful as a web developer).
OneToOneFieldimposes the restriction that there must be one object on each side of the relationship, so you can’t create aUserwithout creating a correspondingUserProfileand vice-versa. I believe that when you don’t useedit_inline, it lets you create one side, but will immediately redirect and make you create the other upon saving.heehee: ForeignKey(…, unique=True) …Django doesn’t complain and the SQL looks ok, so I guess it works!
Combining with above: models.ForeignKey(User, unique=True, edit_inline=models.TABULAR, num_in_admin=1,min_num_in_admin=1, max_num_in_admin=1,num_extra_on_change=0)
Great tip, thanks ! The only issue, as others commented, is getting to edit these new fields inline in the admin page.
Trying as Joshua did above doesn’t work here. Fields do show up, with the correct values if already set, but django silently fails to update or create them..