Django REST Framework Custom User Model

In this post, we’ll look at customizing a user model in DRF. In particular, we’ll focus on using a user’s email address as their username, so that a user can log in with their email address and password.

WHY DO WE NEED THIS?

There are lots of ways of achieving this. You could implement custom logic to log a user in by using an email address lookup, but that presents certain issues. You then end up with the problem of having to manage the database to make sure usernames and email addresses stay in sync, otherwise some users might not be able to log in.

On top of that, I’ve seen instances where fields that should belong to a user are added to a “profile” model, that’s then related to the user. It’s a great idea to have a profile model for additional information, but it’s generally just simpler to keep user-specific information attached to the user.

So let’s create a customised user model that does what we need it to do. This should simplify management as well. 

WHAT ARE WE DOING?

In this post, I’m not going to go over the entire setup process for the whole project. This post assumes you have created a basic project, and that you have an app that will be used for user management.
We will:

  • Create a custom user model, along with a custom manager
  • Add our cool new user model to the admin UI
  • Tell the app to use that model
  • Create a view that allows us to register a new user

I’m sure there are other (probably better) ways to achieve this, but this is my take on things.
So once you’ve created your basic project and app, you can follow along here.
In my case, the project for this post is called myproject, and the app is called users

USER MANAGER

Before we create our model, we need to create a manager for our user. This enables us to still use the command line tools properly, such as python manage.py createsuperuser.
This basically just checks that the email address is set when creating the new user, since the default behavior is to allow for this to allow blank email addresses.

				
					from django.contrib.auth.base_user import BaseUserManager

class AppUserManager(BaseUserManager):
    """
    Custom user model manager where email is the unique identifier
    for authentication instead of username.
    """
    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 Email must be set')
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save()
        return user

    def create_superuser(self, email, password, **extra_fields):
        """
        Create and save a SuperUser with the given email and password.
        """
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)
        extra_fields.setdefault('is_active', 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)
				
			

USER MODEL

Our user model will inherit from AbstractUser. We can then remove the username field, and tell it to use the email field as the new username field.
The username field is generally required as well, so we set REQUIRED_FIELDS to an empty list.
In models.py, you can create the following:

				
					import uuid

from django.contrib.auth.models import AbstractUser
from django.db import models

from myproject.users.user_manager import AppUserManager


class AppUser(AbstractUser):
    username = None
    user_id = models.UUIDField(default=uuid.uuid4, editable=False)
    email = models.EmailField(unique=True)

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []

    objects = AppUserManager()

    def __str__(self):
        return f'{self.email}'
				
			

SETTINGS

Next, we need to let DRF know that it would use the custom user model we’ve defined above.
In your settings.py, add the following:

				
					AUTH_USER_MODEL = "users.AppUser"

				
			

REGISTRATION REQUEST SERIALIZER

Next up, we need to create an endpoint that allows for user registration.
Let’s make a simple serializer to validate our registration request. We’ll also create a simple serializer for user information.

				
					class RegisterRequestSerializer(Serializer):
    email = serializers.EmailField()
    password = serializers.CharField()
    first_name = serializers.CharField()
    last_name = serializers.CharField()
    
class UserSerializer(ModelSerializer):
    groups = UserGroupSerializer(many=True, read_only=True)

    class Meta:
        model = AppUser
        fields = (
            "id",
            "email",
            "first_name",
            "last_name",
            "is_staff",
            "is_superuser",
            "groups",
            "user_permissions",
            "date_joined",
            "is_active",
        )
				
			

REGISTRATION VIEW

Now that we have most of what we need, we can create an endpoint that allows for user registration.
This endpoint will:

  • Validate the request input
  • Creates a user
  • Sets the password for the user so that it is encrypted
  • Returns a response containing the newly created user

The IntegrityError exception being handled will be raised if the email address is already used. There are other exceptions that can be raised here, but this is the most common one as far as I know.

				
					class RegisterView(APIView):
    permission_classes = ()

    def post(self, request, *args, **kwargs):
        """
        Endpoint to register new users.
        """
        serializer = RegisterRequestSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        try:
            created_user = AppUser.objects.create(
                first_name=serializer.validated_data.get("first_name"),
                last_name=serializer.validated_data.get("last_name"),
                email=serializer.validated_data.get("email"),
            )
            created_user.set_password(serializer.validated_data.get("password"))
            created_user.save()
        except IntegrityError:
            return Response(
                data="The email address you've selected is already in use.",
                status=status.HTTP_400_BAD_REQUEST,
            )

        result = UserSerializer(created_user)
        return Response(data=result.data, status=status.HTTP_201_CREATED)
				
			

REGISTER THE URL

You can add this to your urls.py file to register the endpoint:

				
					path("register/", RegisterView.as_view()),

				
			

You should now have an endpoint at /register/ that will allow you to register a new user in the system.

ADMIN UI FORMS

We can create some basic forms that let us edit users in the admin UI. This lets us edit the email addresses and groups of users.

				
					from django.contrib.auth.forms import UserCreationForm, UserChangeForm

from .models import AppUser


class AppUserCreationForm(UserCreationForm):
    class Meta(UserCreationForm):
        model = AppUser
        fields = ("email", "groups")


class AppUserChangeForm(UserChangeForm):
    class Meta:
        model = AppUser
        fields = ("email", "groups")
				
			

ADMIN UI

Since we’ve replaced the default user model, let’s also make sure we can manage users effectively in the admin UI.
This enables us to use the forms above to edit users, and allows us to edit their permissions at the same time.

				
					from django.contrib import admin
from django.contrib.auth.admin import UserAdmin

from .forms import AppUserCreationForm, AppUserChangeForm
from .models import AppUser


class AppUserAdmin(UserAdmin):
    add_form = AppUserCreationForm
    form = AppUserChangeForm
    model = AppUser
    list_display = (
        "email",
        "is_staff",
        "is_active",
    )
    list_filter = (
        "email",
        "is_staff",
        "is_active",
    )
    #  The fields below are available when a new user is being edited.
    fieldsets = (
        (None, {"fields": ("email", "password", "groups")}),
        ("Permissions", {"fields": ("is_staff", "is_active")}),
    )
    #  The fields below are available when a new user is being created.
    add_fieldsets = (
        (
            None,
            {
                "classes": ("wide",),
                "fields": ("email", "password1", "password2", "is_staff", "is_active"),
            },
        ),
    )
    search_fields = ("email",)
    ordering = ("email",)


admin.site.register(AppUser, AppUserAdmin)
				
			

ALL DONE!

Aaaand we’re done! Now you can use customised users in your DRF app, and sign in with custom fields.
It’s worth noting that this is most easily done when you are starting a project. It’s pretty difficult to do all of this when you’re further along in a project with existing users.

Much of the code used above can be found in my base backend repo.