User model with email as the identifier in Django
I think this might be the most common change I make when creating a new Django project: adjusting the user model for accepting an email instead of a username.
When users sign up to one of my sites I want the least friction possible. Having a way to contact users, an email address for instance, is necessary. Since this is unique and can act as an identifier, I see no reason in having yet another bit of information, a username, in addition to the email.
I would expect having an email would be the default in Django, given the sensible defaults it has. But the user model comes with a username as the unique identifier instead. Changing the default user model to using email is not hard, but requires some effort.
I tend to try to make as few changes as possible when changing core Django bits, such as the user. This is for not breaking some core functionality unintentionally and for having the greatest chance of not breaking something in a future version of Django when I will need to upgrade.
Custom user model
Django doc recommends replacing the default user model with a custom one early on when creating a new project. The reason is that if you decide you need a custom user model after the launch of your project, it will be extremely hard to replace it with a custom one.
- Notice we are extending
AbstractUser. This class implements a fully featured User model with admin-compliant permissions, that require a username and an email. We will modify it to require only the email.
AbstractUserdefines a username, so we are overriding the model to "delete" the username field.
AbstractUseralready defines an email field, but it's not unique, so we override it as well.
- See the next section.
- This sets the field that will act as a unique identifier of the User model. We are setting it to the email field.
- This sets any other required field for the User model. The
AbstractUserdefines the email field, but since now that acts as the unique identifier, there are no "additional" required fields.
Next, set this in settings to indicate to Django that the user model is now this custom model. It's best practice to reference this variable when you want to reference the user model, instead of importing Django's built-in one. For instance, if you need to define a foreign key that references the user model use
ref_user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE).
Custom user manager
The user manager is called when you call the
createsuperuser command and the default one is only compatible if the user model defines the following fields: username, email, is_staff, is_active, is_superuser, last_login, date_joined. Our custom model no longer defines username, so it's incompatible with the default one.
class CustomUserManager(BaseUserManager): # 1. def create_user(self, email, password=None): # 2. if not email: raise ValueError('Users must have an email address') user = self.model( email=self.normalize_email(email), ) user.set_password(password) user.save(using=self._db) return user def create_superuser(self, email, password=None): # 3. user = self.create_user( email, password=password, ) user.is_admin = True user.is_superuser = True user.is_staff = True user.save(using=self._db) return user
- Note that we are extending
BaseUserManagerto get as much built-in functionality as possible.
- This method creates a normal user. This should accept the username field (in our case the email), plus all required fields as arguments. We check for the presence of an email and we normalize it before saving.
- Creating a super user is using the previous function and then setting the admin/superuser/staff bits to true.
Custom admin model
Finally, to access the user in the admin portal you need to define a custom admin model since the custom user model is not compatible anymore. The following
ModelAdmin is a sample you can use.
Hopefully, by now you understood how to quickly replace Django's default user model with a custom one that uses email as the identifier.