Extending the Django User model with inheritance
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.
this is beautiful.
Comment by Mogga — 21 August 2008 @ 9:16 pm
Instead of using a custom authentication backend a middleware could be used to replace contrib.auth’s one, i think it would be more simple. Nice example.
Comment by David Elias — 22 August 2008 @ 1:42 am
Since inheritance is implemented under the hood as a one-to-one relation, this doesn’t reduce the number of queries at all — accessing User fields from your subclass will do a query, and accessing subclass fields from a User will do a query.
Also, I *still* think this is the wrong way to go about it from a design perspective: http://www.b-list.org/weblog/2007/feb/20/about-model-subclassing/
Comment by James Bennett — 22 August 2008 @ 3:33 am
pretty easy for using the user model!
thanks
Comment by sean — 22 August 2008 @ 1:43 pm
I think I agree with James. Also, the get_profile() is almost a convention on pluggable apps already.
Comment by Eduardo Padoan — 22 August 2008 @ 7:55 pm
@David Elias
Using middleware sounds like a good idea.
@James Bennett
That’s not the behaviour I’m seeing in r8194. I’m seeing one query with an inner join when the model is retrieved, after which fields from both the base and subclass can be accessed without further queries. That’s one query, versus two queries for user + profile. Have I misunderstood you?
>>> from accounts.models import CustomUser >>> c = CustomUser.objects.get(username='sgb') >>> print c.first_name Scott >>> print c.timezone Europe/London >>> from django.db import connection >>> connection.queries [{'time': '0.231', 'sql': 'SELECT "auth_user"."id", "auth_user"."username", "auth_user"."first_name", "auth_user"."last_name", "auth_user"."email", "auth_user"."password", "auth_user"."is_staff", "auth_user"."is_active", "auth_user"."is_superuser", "auth_user"."last_login", "auth_user"."date_joined", "accounts_customuser"."user_ptr_id", "accounts_customuser"."timezone" FROM "accounts_customuser" INNER JOIN "auth_user" ON ("accounts_customuser"."user_ptr_id" = "auth_user"."id") WHERE "auth_user"."username" = \'sgb\' '}]@Eduardo Padoan
What do the pluggable apps do with the profile? How do they know what fields it has? It’s an interesting point I hadn’t thought of.
Comment by Scott — 24 August 2008 @ 8:32 pm
I got everything modified, but had no luck getting my customuser to show up in the admin site. I’m just learning django while writing a web app, so I may be making too many assumptions. Should my customuser show up in the admin pages for users. It still looks like a user, not customuser.
Comment by Nik — 1 September 2008 @ 1:24 am
@Nik
Your CustomUser class can show up in admin as well as the User class. You just need to register it in the usual way. For example, if you have a CustomUser model, in the admin.py file you could have:
Or to set some display options:
from django.contrib import admin from models import * class CustomUserAdmin(admin.ModelAdmin): list_display = ('first_name', 'last_name', 'timezone') admin.site.register(CustomUser, CustomUserAdmin)If you don’t want User to show up in admin, you can unregister it, with something like this in your main urls.py file:
Comment by Scott — 1 September 2008 @ 9:43 am
Hello, Scott
i’ve tryed this example
——
from django.contrib import admin
from django.contrib.auth.models import User
admin.autodiscover()
admin.site.unregister(User)
——-
but get in return only exceptions like
Error while importing URLconf ‘MyProject.urls’: ‘module’ object has no attribute ’site’
Comment by Vovk — 4 September 2008 @ 10:13 am
Hi,
What if I need *multiple* custom users, e.g., teachers, students, and staff members? Creating multiple subclasses is straightforward, obviously, but what about the authentication backend? How, for different types of users, do I plug the right class into ‘user_class’? Or, more generally, return an instance of the appropriate subclass
I’m a django newbie just trying to get this all figured out.
Thanks in advance!
Comment by Ed Hagen — 4 September 2008 @ 7:01 pm
2 Ed Hagen:
I’m in the exact situation as you described. I use another approach: create abstract base model with extra fields, common to all profiles, then derive each profile model from this base. Thus, you’ll have additional foreign keys in the Django’s User model, pointing back to your profiles. It’s up to your application logic to decide whether the user is allowed to have multiple profiles of different types or only the one. The here mentioned middleware (or backend) could check, for example, if user.teacher is not None: return user.teacher and so on. Personally, I’m not using inheritance from Django User (because my users can have profiles of different types simultaneously), but you could try it ;)
Here is sample code:
class BaseProfile(models.Model): """Abstract model with site's profile data""" # Link to the Django user record user = models.OneToOneField(User, related_name='%(class)s', verbose_name=u'Django user', unique=True,) ... additional common fields goes here class Meta: abstract = True class Person(BaseProfile): """ Site's personal profile for application-specific personal data. """ ... Person specific fields goes hereEach profile will have ‘user’ field:
In the Django User:
You could have as many profile types as you want :)
Comment by irium — 6 September 2008 @ 11:48 pm
I haven’t yet tried your solution but I think you saved my life.
In my app I made a “comments” app which allows users to comment some entities like News and other stuff. The problem is that I’m displaying, for each posted comment, the user’s avatar, which is part of its profile, which was fetched in a new SQL query for each comment. So I’m getting (comments_count) SQL queries executed, which is not a viable solution.
I think your solution will solve this problem and I thank you for that.
Comment by Sephi — 7 September 2008 @ 10:43 pm
@Vovk
Maybe you have an older version of Django.
admin.sitewas added as part of the newforms-admin merge in r7967Comment by Scott — 13 September 2008 @ 8:52 pm
I’m going through a trial implementation on a branch of a site I’m developing. But I’ve run into a stumbling block. Basically, it does not seem to play well with the django-registration module. Have you tried integrating it with that module?
Comment by Paul — 19 September 2008 @ 5:53 pm
I must be missing something.. how is the table for CustomUser created?
–confused.
Comment by Mikael — 3 October 2008 @ 8:28 am
@Mikael
The table is created when you run
manage.py syncdb. The same as the tables for any other model.If you used the
Usermodel, then you changed to use aCustomUsermodel, theCustomUserinstances won’t automatically exist. You will need to manually create them in the database and make them point to the rows in theUsertable. When you create aCustomUserinstance in future, one row is inserted in the*_customusertable and one in theauth_usertable.Comment by Scott — 3 October 2008 @ 9:28 am
Any Chance of getting a middleware example I’m stumped
Comment by Daniel Worth — 8 October 2008 @ 10:02 pm
@Daniel
If you have a look in the Django source in
contrib/auth/middleware.py, you’ll see what it’s doing.I haven’t used it, but you could write a middleware function something like:
from accounts.models import CustomUser class AuthenticationMiddleware(object): def process_request(self, request): request.__class__.user = CustomUser() return NoneThen in your
settings.pyinstead of the usual:MIDDLEWARE_CLASSES = ( ... 'django.contrib.auth.middleware.AuthenticationMiddleware', ...)You would have:
MIDDLEWARE_CLASSES = ( ... 'myproject.middleware.AuthenticationMiddleware', ...)Hope this helps.
Comment by Scott — 9 October 2008 @ 9:49 am
@irium and @Scott
I was really tired yesterday and should have been specific.
The code example helps me understand middleware a little better. However here is the situation. The site needs to have a separate profile for male models, female models, stylists and photographers.
My plan is to implement it as suggested by irium so I can have multiple profiles.
My confusion is how to handle AUTH_PROFILE_MODULE = ?
and what would the middle ware need to look like?
Would a middleware separate from a new AuthenticationMiddleware be needed or is there something I’m missing in how profiles are being passed.
I’m reading code like a mad man but for some reason haven’t been able to wrap my head around it yet.
Comment by Daniel Worth — 9 October 2008 @ 3:43 pm
Hello Scott, thanks for an informative article.
I’m having some trouble, as I’m a django newb, first of all I have the feeling that new users created with the admin interface will not have entries created in the customuser table as well (although I hope I’m wrong on this one). Second I have some trouble creating new users from the shell. The reason for my modifications to the user model, is that I want to tie the users in to parts of my application model, not just add more metadata to each user. Currently when using the objects.create_user(username,email,pass) method, I get a database exception:
IntegrityError: (1048, “Column ‘customer_id’ cannot be null”)
Now in my models I’ve set up the following:
class Customer(models.Model): name = models.CharField(max_length=100) website = models.URLField() def __str__(self): return self.name def __unicode__(self): return self.name class Admin: pass class CustomUser(User): """User with app settings.""" timezone = models.CharField(max_length=50, default='Europe/London') customer = models.ForeignKey(Customer) # Use UserManager to get the create_user method, etc. objects = UserManager()So the problem is obviously the fact that the UserManager can’t create the customuser table entries, because the customer_id foreign key field there can’t be null.
Now this is probably a complete newb question, but I’m wondering how I can overload the user creation so that when creating a new customuser I can pass along a customer to which it belongs?
Best,
Steinn E. Sigurðarson
Comment by Steinn — 14 October 2008 @ 5:47 pm
@Steinn
You are correct that creating a
Userin admin will not cause aCustomUserto be created. You need to make it soCustomUsers are created in admin. See comment #8 above for an idea of how to do that.Does
Customerneed to be a separate model fromCustomUser? If not, you could useCustomeras your custom user class — that’s what I’m doing in my current project.If they do need to be separate, you could make the
customerproperty ofCustomUseroptional so aCustomUsercan be created first then aCustomercan be created and assigned. That’s a bit messy, though.An alternative is to write your own manager for
CustomUserand give it acreate_usermethod that does the right thing. You can look at the source indjango/contrib/auth/models.pyto see what the default method does (not much), copy and customise.If there is always one
Customerfor eachCustomUser, it seems like maybe they should be the same model, so that would be the first thing to consider.Comment by Scott — 15 October 2008 @ 12:46 am
Thanks Scott, I’ll take a look at these links.
Each customer is a company, and can have several users under it, so unfortunately I can’t go with the route of having the CustomUser be the Customer.
One thing I realized yesterday after posting my question here, was that when I go into the administration I can go and create a CustomUser as part of my application, and that will of course create the entries in both tables (and allows me to choose which Customer the new user belongs to). Since all the users will be created this way, I think I can manage without writing a new manager :-)
Best,
Steinn
Comment by Steinn — 15 October 2008 @ 12:59 pm
I tried your example above, but when I try to login the user explicitly through login(request, user), I get the following error message:
‘CustomUser’ object has no attribute ‘backend’
And the code is below:
def my_view(request): if request.method == 'POST': # If the form has been submitted... form = LoginForm(request.POST) # A form bound to the POST data if form.is_valid(): email = request.POST['email'] password = request.POST['password'] c = CustomUserModelBackend() user = c.authenticate(email=email, password=password) if user is not None: if user.is_active: login(request, user)When I looked into the super authenticate method, it does set the backend. Not sure why it’s not being set in my case.
Note: I am able to authenticate the user fine, but login fails with the given error message.
My settings.py has following entry for AUTHENTICATION_BACKENDS:
AUTHENTICATION_BACKENDS = ( 'learnsite.events.AuthBackends.CustomUserModelBackend', )Any insight will be appreciated.
Comment by Ravi Hasija — 18 October 2008 @ 11:03 pm
@Ravi
I think instead of making an instance of
CustomUserModelBackendand callingauthenticateon it, you should just call theauthenticatefunction which is indjango.contrib.auth.If you look in the source you can see it calls the backend’s
authenticatemethod and then sets thebackendattribute on the user object.It uses the backend(s) configured in your
settings.py, so it will be using theCustomUserModelBackendanyway.So, more like:
Comment by Scott — 18 October 2008 @ 11:22 pm
Scott,
Thanks a million. I did look in the source and saw that authenticate method is setting the backend. Coming from Java background, I thought the child method will call super() automatically and did not know that I should call the parent directly.
It works! BEAUTIFUL! You made my day!
Thanks again for the example.
Comment by Ravi Hasija — 19 October 2008 @ 12:10 am
Thanks! You’ve saved me a lot of time!
Nice example!!
Comment by Eduardo — 29 October 2008 @ 6:16 pm
I do inheritance from User model, but i just leave the ‘django.contrib.auth.middleware.AuthenticationMiddleware’,
Since i can get access to my custom user trough user i do:
@login_required def my_view(request): try: cutom_user=request.user.custom_user except Custom_User.DoesNotExist: return HttpResponseRedirect(reverse('custom_client_login'))i would like to have your feedback :)
Thanks in advance,
Sergio Hinojosa
Comment by sergio — 26 November 2008 @ 3:57 am
@Sergio
If you have to add those four lines to each view, that’s a fair bit of extra code, especially if you have lots of views. You could write a custom decorator to do that and set
request.userorrequest.custom_userin the decorator.Another thing to consider is if you are making an extra call to the database when you get
request.user.custom_user. The advantage to custom middleware or authentication backend is that you only need to hit the database once to get the custom user object.Comment by Scott — 26 November 2008 @ 11:03 am
Hi guys, I found a way to create my custom User model every time a auth User instance is created. I putted these lines on my models.py:
from django.db.models import signals def create_profile_for_user(sender, **kwargs): if kwargs['created']: p = Profile() # 'Profile' is my custom User model p.__dict__.update(kwargs['instance'].__dict__) p.save() signals.post_save.connect(create_profile_for_user, sender=User)I hope this helps someone else!
- Thiago
Comment by Thiago Alves — 1 December 2008 @ 5:25 pm
The inheritance feature is great so far, but there is still a little error in the configuration.
Registration to the admin should be done this way:
[code]
from django.contrib.auth.admin import UserAdmin
admin.site.register(CustomUser,UserAdmin)
[/code]
Then, it’s also possible to set/change the password of the user
Comment by bencoder — 23 December 2008 @ 11:51 am