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.

class CustomUser(AbstractUser): # 1.
    username = None # 2.
    email = models.EmailField(unique=True) # 3.

    objects = CustomUserManager() # 4.

    USERNAME_FIELD = 'email' # 5.
    REQUIRED_FIELDS = [] # 6.
models.py
  1. 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.
  2. AbstractUser defines a username, so we are overriding the model to "delete" the username field.
  3. AbstractUser already defines an email field, but it's not unique, so we override it as well.
  4. See the next section.
  5. This sets the field that will act as a unique identifier of the User model. We are setting it to the email field.
  6. This sets any other required field for the User model. The AbstractUser defines the email field, but since now that acts as the unique identifier, there are no "additional" required fields.
AUTH_USER_MODEL = 'core.CustomUser'
settings.py

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
  1. Note that we are extending BaseUserManager to get as much built-in functionality as possible.
  2. 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.
  3. 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.

class CustomUserAdmin(admin.ModelAdmin):
    exclude = ('password')
    ordering = ('email',)
    list_display = ('email', 'first_name', 'last_name', 'is_superuser')
    search_fields = ('email', 'first_name', 'last_name')
    list_filter = ('is_superuser',)
    readonly_fields = ('email',)
    
admin.site.register(CustomUser, CustomUserAdmin)
admin.py

Hopefully, by now you understood how to quickly replace Django's default user model with a custom one that uses email as the identifier.

Happy coding!