Django: Custom User model without username field (and using email in place of it)
Django by default uses the username
field for authentication purposes of a user, but it's becoming a common trend to use email
field for that purpose. As email
identifies a user uniquely, the idea of an additional username
field can be somewhat dropped. 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 mentioned the (default) User
model please think of the default Django User
model (settings.AUTH_USER_MODEL
).
So to make sure we're on the same page, our eventual goal is to get rid of the username
field and use email
field in the User
model for authentication purposes.
Now, let's check out the usual ways to modify/extend the django User
model:
-
Create a Profile model: For example a
UserProfile
model that has a one-to-one relationship with theUser
model. But this is to extend the functionalities of theUser
model 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
):AbstractUser
is an abstract model that can be subclassed directly to create a concreteUser
model. In fact, the defaultUser
model subclasses this. But again if we create a model subclassing this, the model would allow us to extend the functionalities of the abstract class, not to allow for dropping theusername
field. There is a possibility of overriding theusername
class variable (the field declaration) with e.g.None
but that could break some other functionalities as this would makeusername
a valid attribute of the model -- this could also lead to some hidden bugs down the line, so we want to omit it altogether. As a result, this option can't be taken into consideration as well. -
Subclass
AbstractBaseUser
(django.contrib.auth.base_user.AbstractBaseUser
): This is what we need (sorry, spoiler alert!). This is the superclass ofAbstractUser
abstract model as well and defines only 2 fields and associated logic. 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 meet our need.
The decision to take now is that which fields to include in our concrete user model as the
AbstractBaseUser
defines only 2 fields mentioned fields. From here, we can go for a total
control over field definitions.
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 and methods there. But this also has the penalty
of creating an extra database table which can be avoided by using the AbstractUser
model; as
AbstractUser
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 point 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 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 you can imagine, the
superclasses have all the relevant methods and attributes defined as well, and AbstractUser
also 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 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, ) 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 from default User
model are:
-
email
field is madeunique
and it must be provided - Declare
email
as theUSERNAME_FIELD
class attribute - A custom manager is needed that overrides the
create_user
andcreate_superuser
methods used by Django internally in many places to create regular and 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 our User
model or import
if it's defined in
some other module.
The major change from the default UserManager
is the absence of username
in the create_user
and create_superuser
methods. In our example, 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 so that
it all makes sense to the next reader.
That's it! Now we only need to set our custom User
model as the default user model in the settings file (settings.py
):
AUTH_USER_MODEL = '<app_name>.User'
And we're done! We can now use our custom user model like the way we want. We can customize it further to meet our need but here I've shown the basics of such an implementation.
Django source:
Comments
Comments powered by Disqus