Extending the Django User model with inheritance

21 August 2008

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.

Filed under: Django — Scott @ 8:40 pm

26 Comments »

  1. this is beautiful.

    Comment by Mogga — 21 August 2008 @ 9:16 pm

  2. 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

  3. 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

  4. pretty easy for using the user model!
    thanks

    Comment by sean — 22 August 2008 @ 1:43 pm

  5. 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

  6. @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

  7. 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

  8. @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:

    from django.contrib import admin
    
    from models import *
    
    admin.site.register(CustomUser)

    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:

    from django.contrib import admin
    from django.contrib.auth.models import User
    
    admin.autodiscover()
    admin.site.unregister(User)

    Comment by Scott — 1 September 2008 @ 9:43 am

  9. 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

  10. 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

  11. 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 here
    

    Each profile will have ‘user’ field:

    person = Person.objects.get(id=...)
    person.user

    In the Django User:

    user = User.objects.get(id=...)
    user.person

    You could have as many profile types as you want :)

    Comment by irium — 6 September 2008 @ 11:48 pm

  12. 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

  13. @Vovk

    Maybe you have an older version of Django. admin.site was added as part of the newforms-admin merge in r7967

    Comment by Scott — 13 September 2008 @ 8:52 pm

  14. 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

  15. I must be missing something.. how is the table for CustomUser created?

    –confused.

    Comment by Mikael — 3 October 2008 @ 8:28 am

  16. @Mikael

    The table is created when you run manage.py syncdb. The same as the tables for any other model.

    If you used the User model, then you changed to use a CustomUser model, the CustomUser instances won’t automatically exist. You will need to manually create them in the database and make them point to the rows in the User table. When you create a CustomUser instance in future, one row is inserted in the *_customuser table and one in the auth_user table.

    Comment by Scott — 3 October 2008 @ 9:28 am

  17. Any Chance of getting a middleware example I’m stumped

    Comment by Daniel Worth — 8 October 2008 @ 10:02 pm

  18. @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 None

    Then in your settings.py instead 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

  19. @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

  20. 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

  21. @Steinn

    You are correct that creating a User in admin will not cause a CustomUser to be created. You need to make it so CustomUsers are created in admin. See comment #8 above for an idea of how to do that.

    Does Customer need to be a separate model from CustomUser? If not, you could use Customer as 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 customer property of CustomUser optional so a CustomUser can be created first then a Customer can be created and assigned. That’s a bit messy, though.

    An alternative is to write your own manager for CustomUser and give it a create_user method that does the right thing. You can look at the source in django/contrib/auth/models.py to see what the default method does (not much), copy and customise.

    If there is always one Customer for each CustomUser, 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

  22. 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

  23. 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

  24. @Ravi

    I think instead of making an instance of CustomUserModelBackend and calling authenticate on it, you should just call the authenticate function which is in django.contrib.auth.

    If you look in the source you can see it calls the backend’s authenticate method and then sets the backend attribute on the user object.

    It uses the backend(s) configured in your settings.py, so it will be using the CustomUserModelBackend anyway.

    So, more like:

    from django.contrib.auth import authenticate
    ...
    user = authenticate(email=email, password=password)
    ...
    login(request, user)

    Comment by Scott — 18 October 2008 @ 11:22 pm

  25. 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

  26. Thanks! You’ve saved me a lot of time!
    Nice example!!

    Comment by Eduardo — 29 October 2008 @ 6:16 pm

RSS feed for comments on this post. TrackBack URL

Leave a comment