Home Blog The User-Profile Pattern in Django

DEV

The User-Profile Pattern in Django

Posted by gavin on Oct. 31, 2014, 6 p.m.

With the release of Django 1.5, you can now replace the django.contrib.auth User model with your own customized version. This allows you to alter the database fields of the User to, for example, use an email address as a username. It would seem that this obviates the need for the user profile pattern. This is not the case. The User model should only be responsible for authentication and authorization, not storing extra data. When an app needs to store data related to a User, the app should use its own model and define aOneToOneField to the AUTH_USER_MODEL.

This is different from Django's pre-1.5 implementation of the user profile pattern. Django's implementation was ugly and unnecessary, which turned many programmers off of the pattern in general. With Django's reverse relationships, explicit support for user profiles is not necessary. user.get_profile() can just be user.userprofile. Additionally, a single user profile shouldn't be special. Every app that needs profile data should bring their own.

Single responsibility principle

Every class should have a single responsibility. The only responsibility of the User class is to perform authentication and authorization. The storage or manipulation of information not related to authentication or authorization in the User model is a violation of this principle.

Reusability

The best Django apps are reusable. When an app requires extra user data, it should be implemented in a way that can be reused elsewhere. A user profile is eminently reusable, because it exists in isolation. Consider adding a 'phone number' field to a user. If the data is stored on the User model itself, installing an app requires editing the site's user model. A profile, on the other hand, can be installed with no modifications.

Isolation

The data from different apps should be isolated and independent from other apps. Modifying a single User model to store every installed app's data does not allow for reuse. If every app's profile is separate, they can be installed and modified without any changes to the User model and in isolation from other apps. Manually modifying code is not only a violation of the open-closed principle, but is dangerous when dealing with important authentication code. It is too easy to make security mistakes when haphazardly modifying the User class.

Explicit dependencies

An app-specific user profile makes data dependencies explicit. If an app provides a profile, it knows it can rely on the profile's data. Otherwise, the app should not make any assumptions on the available data beyond what is mandated by the User class contract.

Example


class ContactInfo(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL)
    phone_numer = models.CharField()
    address = models.CharField()

def get_contact_info(user):
    return user.contactinfo

Or, for complete isolation when worried about clashing related_names on User, don't use a related name:


class ContactInfo(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL,
                                related_name='+')

def get_contact_info(user):
    return ContactInfo.objects.get(user=user)

tl;dr

Despite the ability to replace the User model, the role of the user profile is still important. The pattern encourages reusability, isolation, and beneficial factoring. The User model should only be replaced when absolutely necessary to customize the authentication or authorization that Django provides.