Custom User Model and Authentication in Django

Posted on Apr 14, 2018 #python #django

Django comes with authentication system out-of-the-box. But it may not be good enough for most common cases. For example, Django uses the username/email/password pattern for both signup and login. However, these days it is more common to simply use email/password. So, we need to have a custom user model. Otherwise, it’ll be very difficult to make any updates to the default user model.

Download complete project from GitHub.

Create Project

cd ~/Desktop
mkdir django_user
cd django_user
virtualenv --python=python3 venv
source venv/bin/activate
pip install django==2
django-admin startproject django_user
cd django_user
python manage.py runserver

Navigate to http://127.0.0.1:8000/ and we’ll see the Django welcome screen. Do not run migrate at this point.

Custom User Model

We will extend the existing User model to create our custom User model. It will have identical functionality but as it is custom, we will have the flexibility to make changes later on as needed.

Create a new app called users:

python manage.py startapp users

Add it to INSTALLED_APPS in settings.py:

INSTALLED_APPS = [ 
    ...
    'users',
]

Change the default user model for our custom User model. At the bottom of the settings.py file, add the following line:

AUTH_USER_MODEL = 'users.CustomUser'

It tells Django that the user auth model to use is in our users app and called CustomUser.

Model

Every model has a corresponding model manager. So, to make our own CustomUser model, we’ll need to create both a new model and manager. We can create our own CustomUserManager and CustomUser by extending the model and manager used by Django for the default User. The existing model is called AbstractUser and its corresponding model manager is UserManager.

Update users/models.py as follows:

from django.contrib.auth.models import AbstractUser, UserManager

class CustomUserManager(UserManager):
    pass

class CustomUser(AbstractUser):
    objects = CustomUserManager()

Here CustomUserManager does nothing at this point except extend UserManager, which means it contains all of UserManager’s code which we can modify later if needed. CustomUser extends AbstractUser and specifies that all its objects come from the CustomUserManager.

Form

Now, we need to customize two forms: UserCreationForm and UserChangeForm. Create a new file in the users app called forms.py and update it with the following code:

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

class CustomUserCreationForm(UserCreationForm):
    class Meta(UserCreationForm.Meta):
        model = CustomUser
        fields = UserCreationForm.Meta.fields

class CustomUserChangeForm(UserChangeForm):
    class Meta:
        model = CustomUser
        fields = UserChangeForm.Meta.fields

Here, we created a new class called CustomUserCreationForm that extends the existing UserCreationForm. In CustomUserChangeForm, we’re again subclassing an existing form UserChangeForm and specifying it use our new CustomUser model.

Admin

Update the admin.py file to tell the admin about our new CustomUser model. Update the existing UserAdmin class:

from django.contrib import admin 
from django.contrib.auth.admin import UserAdmin 
from .forms import CustomUserCreationForm, CustomUserChangeForm 
from .models import CustomUser

class CustomUserAdmin(UserAdmin):
    model = CustomUser 
    add_form = CustomUserCreationForm 
    form = CustomUserChangeForm 

admin.site.register(CustomUser, CustomUserAdmin) 

Here, we created a new class CustomUserAdmin and specified it to use the new model and forms.

Run makemigrations and migrate for the first time to create a new database that uses the custom user model.

python manage.py makemigrations
python manage.py migrate 

Create a superuser account:

python manage.py createsuperuser

If it works then our custom user model works as expected.

User Authentication

Django provides everything for login and logout but we will create our own form to sign up new users.

Templates

Create a new templates directory in project root directory and within it a registration folder as that’s where Django will look for the login template.

mkdir templates
mkdir templates/registration

Update the configuration for ‘DIRS’ in settings.py:

'DIRS': [ 
    os.path.join(BASE_DIR, 'templates'),
],

Add these two lines at the bottom of the settings.py file:

LOGIN_REDIRECT_URL = 'home' 
LOGOUT_REDIRECT_URL = 'home'

These two lines tell Django where to redirect after login or logout of a site. In this case, we want to redirect to our homepage.

Now, create four new templates. The base.html will be inherited by every other template in our project.

templates/base.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title> Custom User Model </title>
</head>
<body>
    {{ "{% block content" }} %}
    {{ "{% endblock" }} %}
</body>
</html>

templates/home.html

{{ "{% extends 'base.html'" }} %}

{{ "{% block title" }} %} Home {{ "{% endblock" }} %}

{{ "{% block content" }} %}
    {{ "{% if user.is_authenticated" }} %}
        Welcome {{ "{{ user.username" }} }}!
        {{ '<p><a href="{%' }} url 'logout' %}"> Logout </a></p>
    {{ "{% else" }} %}
        <p>You are not logged in</p>
        {{ '<a href="{% url' }} 'login' %}"> Login </a> | {{ '<a href="{% url' }} 'signup' %}"> Signup </a>
    {{ "{% endif" }} %}
{{ "{% endblock" }} %}

templates/registration/login.html

{{ "{% extends" }} 'base.html' %}

{{ "{%" }} block title %} Login {{ "{%" }} endblock %}

{{ "{%" }} block content %}
    <h2> Login </h2>
    <form method="post">
        {{ "{%" }} csrf_token %}
        {{ "{{" }} form.as_p }}
        <button type="submit"> Login </button>
    </form>
{{ "{% endblock" }} %}

templates/signup.html

{{ "{% extends" }} 'base.html' %}

{{ "{%" }} block title %} Sign Up {{ "{% endblock" }} %}

{{ "{%" }} block content %}
    <h2> Sign Up </h2>
    <form method="post">
        {{ "{%" }} csrf_token %}
        {{ "{{" }} form.as_p }}
        <button type="submit"> Sign up </button>
    </form>
{{ "{% endblock" }} %}

Views

Update users/views.py which will contain the logic for signup form. We’re using Django’s generic CreateView and telling it to use our custom form_class, to redirect to login once a user signs up successfully, and that the template is named signup.html.

from django.urls import reverse_lazy
from django.views import generic
from .forms import CustomUserCreationForm

class SignUp(generic.CreateView):
    form_class = CustomUserCreationForm
    success_url = reverse_lazy('login')
    template_name = 'signup.html'

URLs

Update django_user/urls.py:

from django.contrib import admin
from django.urls import path, include
from django.views.generic.base import TemplateView

urlpatterns = [
    path('', TemplateView.as_view(template_name='home.html'), name='home'),
    path('admin/', admin.site.urls),
    path('users/', include('users.urls')),
    path('users/', include('django.contrib.auth.urls')),
]

Create a urls.py file in the users app:

from django.urls import path
from . import views

urlpatterns = [path('signup/', views.SignUp.as_view(), name='signup'), ]

Start the server and go to the homepage.

Home

Sign up a new user.

Sign Up

Login with the new user.

Login

After login, the user will be redirected to the personalized homepage.

Home

For custom authentication process, visit Customizing authentication in Django.

comments powered by Disqus