Logo

How to Remove Username from Django's Default User model

Learn how to allow users to authenticate with your app without a username.

Published: 23 Aug, 2024


If you’re building an app where users have to authenticate with an attribute other than a username, you have two choices:

  1. Create your own User model
  2. Customize the one that Django provides.

Customizing the default model is better because it’s both easier to do and adapt to third party libraries that expect a model that’s similar to the default one.

In this post, I’ll show you how to remove the username field and use the user’s email address instead for authentication.

Note: A sample project with all the code is available on Github.

Subclass AbstractUser

There are three ways to create your own User model:

  1. Create one from scratch.
  2. Subclass AbstractBaseUser.
  3. Subclass AbstractUser.

Create a User model from scratch should be done in extreme cases where you don’t want to use Django’s authentication system at all.

AbstractBaseUser is for when you still want to use the included authentication framework but you need to customize it heavily. For example, maybe you don’t want users you have passwords, or you have complex signup flows for creating users.

In our case, we just want to remove the username field, so we’ll go with the third option, which is also the easiest.

from django.db import models
from django.contrib.auth.models import AbstractUser
from django.utils.translation import gettext_lazy as _


class User(AbstractUser):
    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = ()
    username = None
    email = models.EmailField(_("email address"), unique=True)

Django’s authentication framework uses the USERNAMEFIELD attribute to determine which field identifies users when logging in. Here, you set it to email because by default it’s set to _username.

REQUIREDFIELDS is a _sequence that’s used in some parts of the framework, such as the createsuperuser management command. The command will look at this list for fields that they should prompt for in order to create the user. By default, it’s set to [‘email’]. That’s why the createsuperuser command prompts for your email address. However, the command also prompts for the field specified in USERNAME_FIELD before prompting for the ones listed in REQUIRED_FIELDS. So, if you have USERNAME_FIELD set to email and also leave REQUIRED_FIELDS to [‘email’], the command will get confused and fail because each field must be unique. That’s why you set it to an empty tuple instead.

Then you set username to None so that Django doesn’t add it to migrations.

The email field also needs to be set to be unique because by default, it’s not. Now that it’s being used for identifying users, it better be unique!

Go ahead and run the makemigrations command. You’ll see that either Django creates a new migration that doesn’t include username or it alters the model to remove the username field.

Run migrate and you’re now done creating your custom User model.

Excellent! Now it’s time to create a custom manager that will allow interoperability with other parts of the authentication framework.

Create a custom manager

The logic to create new users is implemented in the UserManager. By default, it expects a username. That’s why you need to subclass it and re-implement the methods used for creating new users.

from django.apps import apps
from django.contrib.auth.hashers import make_password
from django.contrib.auth.models import UserManager as AuthUserManager
from django.utils.translation import gettext_lazy as _


class UserManager(AuthUserManager):
    def _create_user(self, email, password, **extra_fields):
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.password = make_password(password)
        user.save(using=self._db)
        return user

    def create_user(self, email, password, **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)

Here, you’re re-implementing three methods that are used to create new users. Notice how you only require email and password.

Now, tell your user model to use this new manager.

...
from .managers import UserManager

class User(AbstractUser):
  ...
  objects = UserManager()

Try running the createsuperuser command and you’ll see that it doesn’t ask for username anymore. You should be able to successfully create a new user with only email address and password.

Create a custom UserAdmin

If you register your new User model with the admin framework, it should work but things will get messy because the forms used are still expecting a username. For example, passwords won’t be set properly because the fields will get jumbled up without the username field.

That’s why if you want to use the admin framework, you’ll also customize the UserChangeForm, UserCreationForm, and register a custom UserAdmin.

from django.contrib.auth.forms import (
    UserChangeForm as DefaultUserChangeForm,
    UserCreationForm as DefaultUserCreationForm,
)

from .models import User


class UserChangeForm(DefaultUserChangeForm):

    class Meta:
        model = User
        fields = "__all__"


class UserCreationForm(DefaultUserCreationForm):

    class Meta:
        model = User
        fields = ["email"]

When subclassing, all you need is to redefine the Meta class because they contain username specific logic by default.

Next you’ll implement a custom UserAdmin that will make use of these new forms, as well as update the UI to reflect your new username-less model.

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as AuthUserAdmin
from .forms import UserChangeForm, UserCreationForm
from django.utils.translation import gettext_lazy as _
from .models import User


class UserAdmin(AuthUserAdmin):
    fieldsets = (
        (None, {"fields": ("email", "password")}),
        (
            _("Personal info"),
            {
                "fields": (
                    "first_name",
                    "last_name",
                )
            },
        ),
        (
            _("Permissions"),
            {
                "fields": (
                    "is_active",
                    "is_staff",
                    "is_superuser",
                    "groups",
                    "user_permissions",
                ),
            },
        ),
        (_("Important dates"), {"fields": ("last_login", "date_joined")}),
    )
    add_fieldsets = (
        (
            None,
            {
                "classes": ("wide",),
                "fields": ("email", "password1", "password2"),
            },
        ),
    )

    form = UserChangeForm
    add_form = UserCreationForm
    list_display = ("email", "first_name", "last_name", "is_staff")
    search_fields = ("first_name", "last_name", "email")
    ordering = ("email",)


admin.site.register(User, UserAdmin)

Visit the admin site and to change and add new users. You’ll find that everything works flawlessly.

Conclusion

As you saw, Django provides a full-fledged authentication system that’s also easy to customize.

To summarize, you followed these three easy steps you created

  1. Your own User model by subclassing AbstractUser.
  2. A custom manager for creating users that conform to your new model.
  3. A custom UserAdmin and forms that work flawlessly with the admin site.

With this knowledge, you’ll be able to build apps that require any kind of non-standard authentication logic.


Email me if you have any questions about this post.

Subscribe to the RSS feed to keep updated.