tag (user)


custom user profile and extending the user admin in django Dec

In almost every project I do these days I need to extend and add fields to the default Django User model to store things like profiles, preferences, history etc.... One way to do this is to create a new model that inherits off of the django User class and use that class for authentication. This is usually not the best idea. In my case it got complicated integrating this change back into third party applications even when setting the AUTH_PROFILE_MODULE and writing a custom authentication backend.

The method I have been using lately works off a custom Model that links back to the User through a OneToOneField: based off the docs this is the recommended way to extend the django user. I find this method much cleaner to work with, and ultimately more compatible and maintainable. For instance I can work in my additions to the User in parallel with other applications. This is not possible if we are all inheriting off the User.

One of the main arguments that I hear against using a OneToOneField over Inheritance is that it creates extra database calls. This is not true. Perhaps a little known fact is that django implements model inheritance through a one-to-one relation: accessing fields from the subclass will do a query and accessing fields from the parent will do another query, so you're doing the same amount of queries either way. It's been mentioned that sub-classing models is not the right solution in most cases.

The following technique is not specific to extending just the User. This is a way to add additional attributes to an existing model written anywhere else in the project without having to modify the original source code. You can then make the additions show up in the admin by registering a custom extended ModelAdmin. This is essentially the same way I add additional properties to Satchmo products for e-commerce shops, although there are additional steps I need to take that are specific to Satchmo.

For this example lets do something simple, lets say something like adding a user profile for a blog that gives me the ability to create an avatar and a biography description for each user.

I'll first start out by defining a custom profile model class that'll attach the desired fields to the django user model.

I want to create this model whenever a user is created so I'll also add a profile creation method to the post_save signal for the User model.

# models.py
from django.db import models
from django.db.models.signals import post_save
from django.contrib.auth.models import User

class UserProfile(models.Model):
    user = models.OneToOneField(User)
    avatar = models.ImageField(upload_to='uploads/avatars', default='', blank=True)
    biography = models.TextField(default='', blank=True)

    def __unicode__(self):
        return self.user.username

def create_user_profile(sender, instance, created, **kwargs):
    """Create the UserProfile when a new User is saved"""
    if created:
        profile = UserProfile()
        profile.user = instance

post_save.connect(create_user_profile, sender=User)

This is a great start. The above code will provide me the ability to add additional fields to the django user with minimal intervention. This might be all I actually need, but I like to push things further; and in this case it's a good idea to make the user profile available in the admin.

Although I could have the admin autodiscover the model like usual, I find it much more efficient to inline the UserProfile into the existing User admin view. To do this I create an inline for the new profile and add it to a custom UserAdmin class that inherits off of the django UserAdmin class.

The process of replacing an existing registered model admin is easy, you just need to remember to unregister the model first. This gives me the ability to adjust the default django user admin view without having to modify any core code.

# admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin

from django.contrib.auth.models import User
from models import UserProfile

class ProfileInline(admin.StackedInline):
    model = UserProfile
    fk_name = 'user'
    max_num = 1

class CustomUserAdmin(UserAdmin):
    inlines = [ProfileInline,]

admin.site.register(User, CustomUserAdmin)

An optional step is to can add AUTH_PROFILE_MODULE in settings.py. I tend NOT to do this because other applications that I integrate, like Satchmo, already have this field set and I don't want step on anyone's toes.

Lets say I put models.py and admin.py in an application named 'custom'.

# settings.py
AUTH_PROFILE_MODULE = 'custom.UserProfile'

This setting allows me to pull the profile off of the django user with the get_profile() method.

profile = user.get_profile()

It's handy but not necessary. You can always get the profile the old-fashioned way:

profile = user.userprofile

I hope this explanation helps. I'm jotting it down mainly so I can refer back to it later as I tend to forget where things are located and what I need to include. There could be a better way to do things, but this has worked for me and hopefully it'll be helpful for someone else. As always feedback and comments are welcome.

Happy coding!