scopyleft

le blog

Advanced Django user model inheritance

Recently I had to manage a few user types each having its own specificities with Django 1.5. Not a big deal in theory.

It actually turned quite different and clues were scattered around the web.

Here is what I expected to get at the end:

Email authentication

A lot of ressources available on the web.

from django.db import models
from django.contrib.auth.models import AbstractBaseUser

class BaseUser(AbstractBaseUser):
    email = models.EmailField(unique=True)

    USERNAME_FIELD = 'email'
    REQUIRED_FIELD = USERNAME_FIELD

Extending BaseUser

Let's make BaseUser abstract and write specific Users that extend it.

class GenericUser(BaseUser):
    pass

class Professional(BaseUser):
    company = models.CharField(max_length=50)
    reference = models.CharField(max_length=10)

class Individual(BaseUser):
    name = models.CharField(max_length=100)

A GenericUser is a system user such as an administrator. settings.AUTH_USER_MODEL would refer to it.

create_user and create_superuser

As we're not using the default Django model, we lost these functionalities. We can reproduce them easily.

from django.contrib.auth.models import (AbstractBaseUser, BaseUserManager as DjBaseUserManager)

class BaseUserManager(DjBaseUserManager):
    def create_user(self, email=None, password=None, **extra_fields):
        now = timezone.now()
        email = BaseUserManager.normalize_email(email)
        u = GenericUser(email=email, is_superuser=False, last_login=now, **extra_fields)
        u.set_password(password)
        u.save(using=self._db)
        return u

    def create_superuser(self, email, password, **extra_fields):
        u = self.create_user(email, password, **extra_fields)
        u.is_superuser = True
        u.save(using=self._db)
        return u

class BaseUser(AbstractBaseUser):
    is_superuser = False
    objects = BaseUserManager()

We also make BaseUser.is_superuser as a hardcoded field which is False by default.

Call a User and get its matching subtype

So far we can't call SomeUser.objects.get(pk=1) and get the matching user with its properties. Django model utils with InheritanceManager comes to the rescue.

As our BaseUser is abstract we cannot call BaseUser.objects.get(pk=1). We need to write an intermediate concrete table that we can call objects on.

We also make BaseUserManager inherit from InheritanceManager.

class BaseUserManager(DjBaseUserManager, InheritanceManager):class CallableUser(AbstractBaseUser):
    objects = BaseUserManager()

Ressources