Update March 2013:
Django 1.5 includes a configurable custom user model that makes all this obsolete.
Extra fields for Users
Most of the Django projects I’ve worked on need to store information about each user in addition to the standard name and email address held by the contrib.auth.models.User
model.
The old way: User Profiles
The solution in the past was to create a “user profile” model which is associated 1-to-1 with the user. Something like:
the model
class UserProfile(models.Model):
user = models.ForeignKey(User, unique=True, related_name='profile')
timezone = models.CharField(max_length=50, default='Europe/London')
config in settings.py
AUTH_PROFILE_MODULE = 'accounts.UserProfile'
usage
profile = request.user.get_profile()
print profile.timezone
It works ok, but it’s an extra database query for each request that uses the profile (it’s cached during the request so each call to get_profile()
is not a query). Also, the information about the user is stored in two separate models, so you need to display and update fields from both the User
and the UserProfile
models.
The new way: Model Inheritance
As part of the great work done on the queryset-refactor by Malcolm et al, Django now has model inheritance.
If you’re using trunk as of revision 7477 (26th April 2008), your model classes can inherit from an existing model class. Additional fields are stored in a separate table which is linked to the table of the base model. When you retrieve your model, the query uses a join to get the fields from it and the base model.
Inheriting from User
Instead of creating a User Profile class, why don’t we inherit from the normal User
class and add some fields?
from django.contrib.auth.models import User, UserManager
class CustomUser(User):
"""User with app settings."""
timezone = models.CharField(max_length=50, default='Europe/London')
# Use UserManager to get the create_user method, etc.
objects = UserManager()
Now each instance of CustomUser
will have the normal User
fields and methods, as well as our additional fields and methods. Pretty handy, no?
We add UserManager
as the default manager so that the standard methods are available. In particular, to create a user, we really want to say:
user = CustomUser.objects.create(...)
If we just created the user from the User
class, we wouldn’t get a row in the CustomUser
table. Creation needs to be done in the derived class.
You can still get and update the underlying User
model, no problem, but it won’t have the additional fields and methods found in our CustomUser
class.
Getting the CustomUser
class by default
So far, there’s one problem. When you get request.user
, it’s an instance of User
, not an instance of CustomUser
, so you don’t get the extra fields and methods.
What we want is for Django to retrieve the CustomUser
instance transparently. It turns out to be quite easy.
Users come from authentication backends
The default authentication backend gets the User
model from the database, checks the password is correct then returns the User
. You can write your own authentication backend, for example to check the username and password against some other data source or to use the email address instead of username.
In our case, we can use an authentication backend to return an instance of CustomUser
instead of User
.
the authentication backend in auth_backends.py
from django.conf import settings
from django.contrib.auth.backends import ModelBackend
from django.core.exceptions import ImproperlyConfigured
from django.db.models import get_model
class CustomUserModelBackend(ModelBackend):
def authenticate(self, username=None, password=None):
try:
user = self.user_class.objects.get(username=username)
if user.check_password(password):
return user
except self.user_class.DoesNotExist:
return None
def get_user(self, user_id):
try:
return self.user_class.objects.get(pk=user_id)
except self.user_class.DoesNotExist:
return None
@property
def user_class(self):
if not hasattr(self, '_user_class'):
self._user_class = get_model(*settings.CUSTOM_USER_MODEL.split('.', 2))
if not self._user_class:
raise ImproperlyConfigured('Could not get custom user model')
return self._user_class
config in settings.py
AUTHENTICATION_BACKENDS = (
'myproject.auth_backends.CustomUserModelBackend',
)
...
CUSTOM_USER_MODEL = 'accounts.CustomUser'
That’s it. Now when you get request.user
, it’s an instance of the CustomUser
class with whatever additional fields or methods you have added.
P.S. Looking for Django hosting? I’d recommend WebFaction for shared hosting and Slicehost for a VPS.