How to serialize custom user model and register new instances with Django REST Framework
Most of the time, when you are building a real-world web application, Django's built-in user model will not be a perfect fit for your application because you might need to collect other vital user information that are not part of the built-in user model. This means you will have to create a custom user model to fit your project requirements and still make use of the rich Django support for working with user instances.
One of the common headaches you’re likely to face when building a web API with Django REST Framework is how to register custom user instances. Perhaps, you are facing this problem presently and that’s why you’re reading this now. Don’t worry! You’re about to learn the best approach to deal with this issue.
We will take a tutorial approach to explain the process. In this tutorial, we will be using a user model for a simple Federal Identity Management Application (FIMA). FIMA is an application that holds and manage information of citizens. If you do not want to follow through this tutorial, feel free to jump right to any section you want.
Here is the table of content for this tutorial
Installation and setting up your project
Create your custom user model
The brass tacks: Serializing custom user model
The brass tacks: Registering new user instance
INSTALLATION AND SETTING UP YOUR PROJECT
My sure guess right now is that you’re not new to Django if you’ve followed this post to this point (unless you’re planning to hire me). So, you know how it goes. Just follow the steps so that we can get to the real deal as soon as possible.
Create a folder for your project
From your terminal, change your working directory to your project folder. Then create and activate a virtual environment to your project.
Install the packages you will be using for this tutorial by executing the command below in your terminal:
pip install django djangorestframework django-rest-auth django-allauth
You can also add all your installed packages in requirements.txt by executing pip freeze > requirements.txt
. This is optional but a good practice to manage all your project dependencies in one file.
Now, let's create our FIMA project and start a new application named 'citizens' for our project. You do these by executing the corresponding commands in your terminal.
django-admin startproject FIMA .
python manage.py startapp citizens
Now that you've installed the required packages for the REST API, created project FIMA and the citizens application. Let's update INSTALLED_APPS in the settings.py located in FIMA directory.
Your INSTALLED_APPS in settings.py should look like this:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.sites',
'allauth',
'allauth.account',
'citizens.apps.CitizensConfig',
'rest_framework',
'rest_auth',
'rest_auth.registration',
'rest_framework.authtoken',
]
Now, you will need to include settings for django REST framework, rest_auth, and allauth. Add the following to your settings.py:
SITE_ID = 1
ACCOUNT_EMAIL_REQUIRED = False
ACCOUNT_AUTHENTICATION_METHOD = 'username'
ACCOUNT_EMAIL_VERIFICATION = 'none'
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
],
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.BasicAuthentication',
]
}
CREATING YOUR CUSTOM USER MODEL
I want to believe you have all the settings properly configured in your settings.py as described before this section.
Now, go to models.py in citizens app folder. We will now create our custom user model as shown below:
from django.db import models
from django.contrib.auth.models import AbstractUser
class Citizen(AbstractUser):
age = models.PositiveIntegerField()
occupation = models.CharField(max_length=50)
date_of_birth = models.DateField()
def __str__(self):
return "%s %s" % (self.first_name, self.last_name)
Our Citizen model will inherit all the basic django user fields from AbstractUser. This means that aside from age, occupation, and date_of_birth fields, a Citizen instance will also have username, password, first_name, last_name, email fields.
Now, set your Citizen model as the default user model for project FIMA. All you need to do is add AUTH_USER_MODEL in your settings.py as shown below:
AUTH_USER_MODEL = "citizens.Citizen"
Now, you need make migrations for Citizen model and create tables for your models (including models from installed packages declared in INSTALLED_APPS earlier). You do this by executing the following commands in your terminal:
python manage.py makemigrations
python manage.py migrate
THE BRASS TACKS: SERIALIZING CUSTOM USER MODEL
Now that we have our default user model set as Citizen. Let’s set our URL patterns. In FIMA directory, find urls.py and add URL patterns for rest_auth. Your urls.py should look like this:
# FIMA URL Configuration
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('rest-auth/', include('rest_auth.urls')),
path('rest-auth/registration/', include('rest_auth.registration.urls')),
]
Now, let’s run our server. You know the drill already. Run python manage.py runserver
in your terminal. Now, open your browser and go to 127.0.0.1:8000/rest-auth/registration
The page you see should look like this.
You will notice that the extra fields we provided in our custom user model (Citizen) are not added in the registration form.
This is because django-rest-auth’s default register serializer only recognizes fields for django built-in user model. Therefore, we need to create a custom register serializer and instruct django-rest-auth to use it as our register serializer.
Now, create a serializers.py file in citizens app folder and create a custom register serializer in it as shown below:
from rest_framework import serializers
from rest_auth.registration.serializers import RegisterSerializer
from .models import Citizen
class CustomRegisterSerializer(RegisterSerializer):
age = serializers.IntegerField(max_value=None, min_value=1)
occupation = serializers.CharField(max_length=50)
date_of_birth = serializers.DateField()
class Meta:
model = Citizen
fields = ('username', 'email', 'password', 'age', 'occupation', 'date_of_birth',)
Now that you’ve created your custom register serializer, you need to tell django-rest-auth to use it as a your register serializer. To do this, go to your settings.py and add the following:
REST_AUTH_REGISTER_SERIALIZERS = {
'REGISTER_SERIALIZER': 'citizens.serializers.CustomRegisterSerializer'
}
Now, back to the browser! Go to 127.0.0.1:8000/rest-auth/registration/ You will find out that the registration form now has age, occupation and date of birth fields. Great work! Now, let’s try register. Just to be sure we’ve done a good job.
If you actually tried registering. You will get an integrity error like this one below:
This is because our custom register serializer only supplies the extra fields but django-rest-auth doesn’t know what to do with the values we entered for these fields so it attempted to save null in “age” field. Unfortunately, age field of Citizen table cannot take null value because by default models.PositiveIntegerField (as used for age in model “Citizens”) has null set to False. This takes us to the final section of this tutorial.
THE BRASS TACKS: REGISTERING USER INSTANCES
Now, we need to explicitly tell django-rest-auth to take the values we entered in age, occupation and date_of_birth fields and include them as corresponding attributes for the new Citizen to be registered (that is: the new Citizen instance to be saved).
To do this, you will override the default get_cleaned_data method and save method of RegisterSerializer in CustomRegisterSerializer. Your serializers.py file in citizens app folder should now look like this:
# get_adapter in save method to get an instance of our user model (Citizen)
from allauth.account.adapter import get_adapter
from rest_framework import serializers
from rest_auth.registration.serializers import RegisterSerializer
from .models import Citizen
class CustomRegisterSerializer(RegisterSerializer):
age = serializers.IntegerField(max_value=None, min_value=1)
occupation = serializers.CharField(max_length=50)
date_of_birth = serializers.DateField()
class Meta:
model = Citizen
fields = ('username', 'email', 'password', 'age', 'occupation', 'date_of_birth',)
# override get_cleaned_data of RegisterSerializer
def get_cleaned_data(self):
return {
'username': self.validated_data.get('username', ''),
'password1': self.validated_data.get('password1', ''),
'password2': self.validated_data.get('password2', ''),
'email': self.validated_data.get('email', ''),
'age': self.validated_data.get('age'),
'occupation': self.validated_data.get('occupation'),
'date_of_birth': self.validated_data.get('date_of_birth'),
}
# override save method of RegisterSerializer
def save(self, request):
adapter = get_adapter()
user = adapter.new_user(request)
self.cleaned_data = self.get_cleaned_data()
user.age = self.cleaned_data.get('age')
user.occupation = self.cleaned_data.get('occupation')
user.date_of_birth = self.cleaned_data.get('date_of_birth')
user.save()
adapter.save_user(request, user, self)
return user
I have placed comments above the codes you’ll be adding to serializers.py.
The get_cleaned_data method collects the validated values of each fields for registration and the save method collects the data from get_cleaned_data and handles how the Citizen instance is saved.
Run python manage.py runserver
in your terminal and repeat the registration process. Your code should be running fine now.
Congratulations!
And that’s how you serialize custom user model and register a custom user instance with Django REST framework.
I have one question for you, did you notice we were asking citizens for age and date of birth in our FIMA project. Lol! With just one, you can know the other. So why ask? Well, this is just a short tutorial project. And it served its purpose, Right?
Anyways, I hope this was helpful to you. If it was, please, punch a like emoji and any of those cool emojis you prefer.
If you have a question, feel free to drop a comment or send me a DM on twitter.
Thank you.