Django: Custom User model without username
Django by default uses the username field for authentication purposes, but it's becoming a common trend to allow for email field for that purpose. As email identifies a user uniquely, the need of an additional username field can be omitted. Also, the email can be used in the places where a username can be used, so it does not break any internal identification structure as well.
In the later sections, when I mentiond the User model please think of the default AUTH_USER_MODEL (settings.AUTH_USER_MODEL).
So our eventual plan is to get rid of the username field and use email field in the User model for authentication purposes (<user_model>.USERNAME_FIELD).
Now, let's see the usual ways to modify/entend the django User model:
-
Create a Profile model: For example a
UserProfilemodel that has a one-to-one relationship with theUsermodel. But this is to entend the functionalities of theUsermodel e.g. to add a new field, not to modify it. So this option is of no use to us in this case. -
Subclass
AbstractUser(django.contrib.auth.models.AbstractUser):AbstractUseris a abstract model that can be subclassed directly to create a concreteUsermodel. In fact, the defaultUsermodel subclasses this (django.contrib.auth.models.PermissionMixin). But again if create a model subclassing this, the model would allow us to extend the functionalities of the abstract class, not to allow for dropping theusernamefield. So this option cab be dropped as well. -
Subclass
AbstractBaseUser(django.contrib.auth.base_user.AbstractBaseUser): This is what we need (sorry, spoiler alert!). This is the superclass ofAbstractUserabstract model as well and defines only a very few fields and associated logics. The fields it define are:password,last_login. So only a bare minimum and a perfect abstract superclass to extend and add only the fields we want.
So we'll be focusing on subclassing the AbstractBaseUser model and modify it to our need. The
decision to take now is that which fields to included in our concrete user model as the
AbstractBaseUser defines only 2 fields mentioned fields. From here, we can go for absolutely
limited number of fields as we need.
Before moving forward, if we were to extend the User model, the easier option is to create
another profile model and put all the new fields there. But this also has the penalty of
creating an extra database table which can be avoided by using the AbstractUser model; as
it is an abstract model, it does not result in a database table itself. So we would get only
one table created for the concrete subclass of it (which will contain all the additional fields).
Now, as our plan is to drop the username field and use email field as the USERNAME_FIELD, at
this poing we can get some inspiration from the AbstractUser model as it basically defines
all the fields we see in the default User model. The fields AbstractUser defines are:
username, first_name, last_name, email, is_staff, is_active, date_joined. It also
inherits from AbstractBaseUser so got password, last_login from there. It also has
PermissionsMixin (django.contrib.auth.models.PermissionsMixin) as it's super class so it
inherits is_superuser, groups, user_permissions fields from there. As we can imagine, the
superclasses have all the relevant methods and attributes, and AbstractUser implements some
if it's own. You can check out the source codes of them from links given at the bottom.
So based on the above idea, a simple User model that does not have the username field and
uses email as the username field can take be defined as:
from django.db import models from django.contrib.auth.base_user import AbstractBaseUser from django.contrib.auth.models import PermissionsMixin from django.utils.translation import ugettext_lazy as _ class User(AbstractBaseUser, PermissionsMixin): email = models.EmailField(unique=True, max_length=255, blank=False) # All these field declarations are copied as-is from `AbstractUser` first_name = models.CharField(_('first name'), max_length=30, blank=True) last_name = models.CharField(_('last name'), max_length=150, blank=True) email = models.EmailField(_('email address'), blank=True) is_staff = models.BooleanField( _('staff status'), default=False, help_text=_('Designates whether the user can log into this admin site.'), ) is_active = models.BooleanField( _('active'), default=True, help_text=_( 'Designates whether this user should be treated as active. ' 'Unselect this instead of deleting accounts.' ), ) date_joined = models.DateTimeField(_('date joined'), default=timezone.now) # Add additional fields here if needed objects = UserManager() USERNAME_FIELD = 'email'
As seen, the major changes are:
-
emailfield is madeuniqueand it can't beblank - Declare
emailas theUSERNAME_FIELDclass attribute - A custom manager is needed that overrides the
create_userandcreate_superusermethods used by Django internally in many places to create regular/super users, respectively.
Let's create our new UserManager now, this is also inspired from the idea implemented by the
manager used in AbstractUser:
from django.contrib.auth.base_user import BaseUserManager class UserManager(BaseUserManager): use_in_migrations = True def _create_user(self, email, password, **extra_fields): '''Create and save a user with the given email, and password.''' if not email: raise ValueError('The given email must be set') email = self.normalize_email(email) user = self.model(email=email, **extra_fields) user.set_password(password) user.save(using=self._db) return user def create_user(self, email, password=None, **extra_fields): extra_fields.setdefault('is_staff', False) extra_fields.setdefault('is_superuser', False) return self._create_user(email, password, **extra_fields) def create_superuser(self, email, password, **extra_fields): extra_fields.setdefault('is_staff', True) extra_fields.setdefault('is_superuser', True) if extra_fields.get('is_staff') is not True: raise ValueError('Superuser must have is_staff=True.') if extra_fields.get('is_superuser') is not True: raise ValueError('Superuser must have is_superuser=True.') return self._create_user(email, password, **extra_fields)
We need to move this definition before User model or import if it's defined in
some other module.
The major change from the default UserManager is the absense of username in the create_user
and create_superuser signatures. These methods delegate the actual user creation to the
_create_user method which is set up accordingly to create users based on email-password.
Note that, the names User and UserManager are only a convention, you can give any name
you want but it's highly recommended that you keep the same naming conventions.
That's it! Now we only need to set our custome User model as the default user model in settings.py:
AUTH_USER_MODEL = 'path.to.new.User'
And we're done! We can now use our new user model like the way we want. We can customize it further to meet our need but here I've shown the basic idea of such an implementation.
--
Django source codes:

Comments
Comments powered by Disqus