Django rest framework Part 1: Custom User model Django comes with a built-in User model for authentication. However, the official Django documentation recommends using a custom user model. In this tutorial, we will create a project with a custom user model. Requirements Python3 and above Pipenv Postgresql Set up the project On your local computer create a working folder and name it blog mkdir blog Navigate into the working directory using cd blog Open the working directory using your favorite IDE, for those using VsCode and you have setup the path command in your IDE, use code. to open the working directory. Django rest framework Part 1: Custom User model 1 On the terminal inside your working directory, run this command to create and activate virtual env. pipenv shell You will notice filename Pipfile has been created in your working directory. To install django and djangorestframework run the following command. pipenv install django djangorestframework You will notice another file working directory, it helps Pipfile.lock has been created in your in tracking the version of dependencies installed. Open the settings.py under INSTALLED_APPS and add rest_framework . Your settings should look like this [ ... 'rest_framework', ... ] Create a Django project using the following command. We will name our project app django-admin startproject app This is our current directory structure Django rest framework Part 1: Custom User model 2 Lets do some clean up in our directory structure. This will eliminate the need of navigating to app folder to start the server. We are going to move all the files inside this command mv app/app/* app and move app/app/ to app/ use to the top of our directory structure using mv app/manage.py . Now delete the app folder inside the app using this command rm -rf app/app This is what our project structure should look like. manage.py ├── app │ ├── asgi.py │ │ │ │ ├── ├── └── ├── __init__.py ├── settings.py ├── urls.py └── wsgi.py manage.py Pipfile Pipfile.lock Run python manage.py runserver to start the server. Visit this link http://127.0.0.1:8000/ you and you should see the Django welcome screen. Django rest framework Part 1: Custom User model 3 Create API app Inside app folder, run django-admin startapp authentication you will notice a authentication folder has been created inside app folder and inside authentication there are a couple of files. Register the authentication app with the app project We edit app/settings.py under INSTALLED_APPS and add app.authentication [ ... 'app.authentication' ... ] Django rest framework Part 1: Custom User model 4 Database connection We are using PostgreSQL database. We will need to install psycopg2 which is the most popular PostgreSQL database adapter for the Python programming. pipenv install psycopg2 Create a database and give it the name of your choice, I named mine blog. Create a .env file to store the environment variables. export DB_NAME=<your_db_name> export DB_USER=<your_db_user> export DB_HOST=localhost export DB_PASSWORD=<your_db_password> Run source .env to set the environment variables So we edit app/settings.py under DATABASES and do the following changes as follows import os .... DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', 'NAME': os.getenv('DB_NAME'), 'PASSWORD': os.getenv('DB_PASSWORD', ''), 'HOST': os.getenv('DB_HOST'), 'USER': os.getenv('DB_USER'), 'PORT': '5432', } } Django rest framework Part 1: Custom User model 5 .. Migrate the database Run python manage.py migrate to migrate default migrations. After running the command above you will see the following output without any failure. If error, repeat the above step to ensure the db setup was done correctly. Operations to perform: Apply all migrations: admin, auth, contenttypes, sessions Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK Applying admin.0002_logentry_remove_auto_add... OK Applying admin.0003_logentry_add_action_flag_choices... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0003_alter_user_email_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK Applying auth.0008_alter_user_username_max_length... OK Applying auth.0009_alter_user_last_name_max_length... OK Applying auth.0010_alter_group_name_max_length... OK Applying auth.0011_update_proxy_permissions... OK Applying auth.0012_alter_user_first_name_max_length... OK Applying sessions.0001_initial... OK Django rest framework Part 1: Custom User model 6 Creating Models Navigate in app/authentication/model.py and add the following code. This will override the custom authentication model. from django.db import models from django.contrib.auth.models import ( AbstractBaseUser, BaseUserManager, PermissionsMixin ) class UserManager(BaseUserManager): """ Django requires that custom users define their own Manager c inheriting from `BaseUserManager`, we get a lot of the same Django to create a `User` for free. All we have to do is override the `create_user` function whi to create `User` objects. """ def create_user(self, username, email, password=None): """Create and return a `User` with an email, username an if username is None: raise TypeError('Users must have a username.') if email is None: raise TypeError('Users must have an email address.') user = self.model(username=username, email=self.normaliz user.set_password(password) user.save() return user def create_superuser(self, username, email, password): """ Django rest framework Part 1: Custom User model 7 Create and return a `User` with superuser powers. Superuser powers means that this use is an admin that ca they want. """ if password is None: raise TypeError('Superusers must have a password.') user = self.create_user(username, email, password) user.is_superuser = True user.is_staff = True user.save() return user class User(AbstractBaseUser, PermissionsMixin): username = models.CharField(db_index=True, max_length=255, u default="default-usernam email = models.EmailField(db_index=True, unique=True) is_active = models.BooleanField(default=False) USERNAME_FIELD = 'email' REQUIRED_FIELDS = ['username'] objects = UserManager() def __str__(self): """ Returns a string representation of this `User`. This string is used when a `User` is printed in the cons """ return self.email Then under app/settings.py change the AUTH_USER_MODEL to use our custom model Django rest framework Part 1: Custom User model 8 ... AUTH_USER_MODEL = 'authentication.User' ... Make migrations Whenever we define or change a model, we need to tell Django to migrate those changes $ python manage.py makemigrations Migrations for 'authentication': app/authentication/migrations/0001_initial.py - Create model User $ python manage.py migrate Operations to perform: Apply all migrations: admin, auth, authentication, contenttype Running migrations: Applying authentication.0001_initial... OK Keeping our code in order We want to keep our code in order and we need to have our helper functions in different files where we can reuse them. Create a folder app/helpers and add these files app/helpers/serialization_errors.py error_dict = { "required": "This field is required.", "empty_field": "This field cannot be empty.", Django rest framework Part 1: Custom User model 9 "invalid_name": "{0} cannot have spaces or special character "invalid_id_number": "The ID number must contain numbers onl 'invalid_password': 'Password must have at least a number, a "invalid_phone_no": "Phone number must be numbers of the for "already_exist": "{0} already exist.", "min_length": "{0} must be at least {1} characters long.", "blank_field": "This field may not be blank.", "does_not_exist": "{} does not exist", } jwt_errors = { 'token_expired': 'Token expired. Please login to get a new t 'invalid_token': 'Authorization failed due to an Invalid tok 'invalid_secret': 'Cannot verify the token provided as the e 'token_user_not_found': "No user found for token provided" } app/helpers/token.py import datetime import jwt from django.conf import settings from rest_framework import exceptions from ..authentication.models import User from .serialization_errors import jwt_errors def generate_password_reset_token(email, id_): """ generates token that expires in an hour Args: email (str): user email id_ (str): user id Return: Django rest framework Part 1: Custom User model 10 token (str): a token that expires in the specified time """ time = datetime.datetime.utcnow( ) + datetime.timedelta(hours=int(settings.RESET_TOKEN_EXP_TI payload = { 'email': email, 'id': id_, 'exp': time } token = jwt.encode(payload, settings.SECRET_KEY, algorithm= return token.decode('utf-8') def get_token_data(token): """ checks validity of a token Args: token (str): token to be validated Return: user (obj): valid user object """ try: payload = jwt.decode( token, settings.SECRET_KEY, algorithms="HS256", ) user = User.objects.get(email=payload['email']) except Exception as error: exception_mapper = { jwt.ExpiredSignatureError: jwt_errors['token_expired jwt.DecodeError: jwt_errors['invalid_token'], jwt.InvalidIssuerError: jwt_errors['invalid_secret'] User.DoesNotExist: jwt_errors['token_user_not_found } message = exception_mapper.get( type(error), 'Authorization failed.') Django rest framework Part 1: Custom User model 11 raise exceptions.AuthenticationFailed(message) return user Serialize the User model Now we’re starting to get into some Restful concepts. We need to tell REST Framework about our User model and how it should serialize the data. Serialization is the process of converting a Model to JSON. The serializer will convert the user model instance to JSON representation. In turn when the user post JSON data to our api, the serializer will convert it to User model instance inorder for us to save. So we will edit app/authentication/serializers.py from rest_framework import serializers from rest_framework.validators import UniqueValidator from ..helpers.serialization_errors import error_dict from .models import User class RegistrationSerializer(serializers.ModelSerializer): email = serializers.EmailField( required=True, allow_null=False, validators=[ UniqueValidator( queryset=User.objects.all(), message=error_dict['already_exist'].format("Emai ) ], error_messages={ 'required': error_dict['required'], } Django rest framework Part 1: Custom User model 12 ) password = serializers.RegexField( regex=("^(?=.{8,}$)(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9]).*") min_length=8, max_length=30, required=True, allow_null=False, write_only=True, error_messages={ 'required': error_dict['required'], 'min_length': error_dict['min_length'].format("Passw 'max_length': 'Password cannot be more than 30 chara 'invalid': error_dict['invalid_password'], } ) username = serializers.RegexField( regex='^(?!.*\ )[A-Za-z\d\-\_]+$', allow_null=False, required=True, validators=[ UniqueValidator( queryset=User.objects.all(), message=error_dict['already_exist'].format("User ) ], error_messages={ 'required': error_dict['required'], 'invalid': error_dict['invalid_name'].format('Userna } ) class Meta: model = User fields = ['email', 'username', 'id', 'password'] Django rest framework Part 1: Custom User model 13 @classmethod def create(self, data): # Use the `create_user` method we wrote earlier to creat return User.objects.create_user(**data) Display the data(Views) We need to recive http request with JSON data via our api and save it to our User model. Write the following code. from from from from from rest_framework import generics rest_framework import status rest_framework.permissions import AllowAny django.shortcuts import render rest_framework.response import Response from .serializers import RegistrationSerializer, User class RegistrationApiView(generics.CreateAPIView): permission_classes = (AllowAny, ) serializer_class = RegistrationSerializer def post(self, request): user_data = request.data serializer = self.serializer_class(data=user_data) serializer.is_valid(raise_exception=True) serializer.save return_message = { "message": "Registration successful", "data": serializer.data Django rest framework Part 1: Custom User model 14 } return Response(return_message, status=status.HTTP_201_C Default authentication class Create app/authentication/backends.py and paste the following code from from from from django.conf import settings rest_framework import authentication rest_framework.exceptions import AuthenticationFailed ..helpers.token import get_token_data class JWTAuthentication(authentication.BaseAuthentication): """This is called on every request to check if the user is a """ @classmethod def authenticate(self, request): """ This checks that the passed JWT token is valid and retur a user and his/her token on successful verification """ # Get the passed token auth_header = authentication.get_authorization_header( request).decode('utf-8') if not auth_header or auth_header.split()[0].lower() != return None token = auth_header.split(" ")[1] # Attempt decoding the token user = get_token_data(token) Django rest framework Part 1: Custom User model 15 # Check this user is activated if not user.is_active: raise AuthenticationFailed('This user is deactivated return (user, token) Handling exceptions Create app/core/exceptions.py from rest_framework.views import exception_handler def core_exception_handler(exc, context): # If an exception is thrown that we don't explicitly handle # to delegate to the default exception handler offered by DR # handle this exception type, we will still want access to t # generated by DRF, so we get that response up front. response = exception_handler(exc, context) handlers = { 'ValidationError': _handle_generic_error } # This is how we identify the type of the current exception # this in a moment to see whether we should handle this exce # Django REST Framework do it's thing. exception_class = exc.__class__.__name__ if exception_class in handlers: # If this exception is one that we can handle, handle it # return the response generated earlier by the default e # handler. return handlers[exception_class](exc, context, response) return response Django rest framework Part 1: Custom User model 16 def _handle_generic_error(exc, context, response): # This is about the most straightforward exception handler w # We take the response generated by DRF and wrap it in the response.data = { 'errors': response.data } return response So we will edit app/settings.py and add the following REST_FRAMEWORK = { 'EXCEPTION_HANDLER': 'app.core.exceptions.core_exception_han 'NON_FIELD_ERRORS_KEY': 'error', 'DEFAULT_AUTHENTICATION_CLASSES': ( 'app.authentication.backends.JWTAuthentication', # app/a 'rest_framework.authentication.BasicAuthentication', 'rest_framework.authentication.SessionAuthentication', ), } Handling Urls We'll specify URLs as endpoints for consuming our API. Create a app/authentication/url.py this is where we are going to define our url patterns. from django.urls import path from .views import RegistrationApiView urlpatterns = [ path('signup/', RegistrationApiView.as_view(), name='user-re ] Django rest framework Part 1: Custom User model 17 Finally we add a url to the main app's urls.py file so that it points to our API app. Open app/urls.py """app URL Configuration The `urlpatterns` list routes URLs to views. For more informatio https://docs.djangoproject.com/en/3.1/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: path('', views.home, name='hom Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: path('', Home.as_view(), name= Including another URLconf 1. Import the include() function: from django.urls import in 2. Add a URL to urlpatterns: path('blog/', include('blog.ur """ from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('auth/', include('app.authentication.urls')) ] Project structure Our final directory structure should be like the following ├── │ │ │ app ├── asgi.py ├── authentication │ ├── admin.py │ │ ├── apps.py Django rest framework Part 1: Custom User model 18 │ │ │ │ │ │ ├── backends.py ├── __init__.py ├── migrations │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── ├── ├── ├── └── │ │ │ │ │ │ │ ├── │ │ ├── │ │ │ core ├── exceptions.py └── __init__.py helpers ├── __init__.py ├── serialization_errors.py └── token.py │ │ │ │ ├── ├── ├── ├── __init__.py ├── settings.py ├── urls.py └── wsgi.py db.sqlite3 manage.py Pipfile ├── 0001_initial.py └── __init__.py models.py serializers.py tests.py urls.py views.py ├── Pipfile.lock └── README.md Finally! Lets run $ python manage.py runserver Watching for file changes with StatReloader Performing system checks... Django rest framework Part 1: Custom User model 19 System check identified no issues (0 silenced). February 20, 2021 - 09:05:08 Django version 3.1.5, using settings 'app.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C. Navigate to http://127.0.0.1:8000/auth/signup/ .Yeeah!! it works Django rest framework Part 1: Custom User model 20