Adding Users to Your Django Project With A Custom User Model
Overview
Django makes it easy to handle users, there is already a module: django.contrib.auth that provides an user authentication system. But you probably need some flexibility and add custom fields to the User model keeping the the default user model behaviour.
That means that you will have:
- user accounts with your custom fields
- groups
- permissions
- cookie-based user sessions
In this guide we will:
- Create a new Django project (called dcu aka.: django-custom-users)
- Set up a custom user model (as recommended in Django official documentation) by creating a new users app.
- Use shipped modules’ templates/views/urls for login, and user management related tasks.
- Have a signup page directly in front-site.
1. Setting up
Create required directories:
$ mkdir -f ~/.virtualenvs $ mkdir django-custom-users $ cd django-custom-users django-custom-users$
Create a virtual environment:
django-custom-users$ mkvirtualenv -python=/usr/bin/python3.6 ~/.virtualenvs/dcu django-custom-users$ workon dcu
Activate the above virtual environment:
(dcu)django-custom-users$ workon dcu
Install Django:
(dcu)django-custom-users$ pip install django
Create the Django project:
(dcu)django-custom-users$ django-admin startproject dcu .
Now we have the following structure:
.
βββ dcu
βΒ Β βββ __init__.py
βΒ Β βββ settings.py
βΒ Β βββ urls.py
βΒ Β βββ wsgi.py
βββ manage.py
2. Create users app
We are going to create a Django app that will handle our custom users called users.
(dcu)django-custom-users$./manage.py startapp users (dcu)django-custom-users$ tree . βββ dcu βΒ Β βββ __init__.py βΒ Β βββ __pycache__ βΒ Β βΒ Β βββ __init__.cpython-36.pyc βΒ Β βΒ Β βββ settings.cpython-36.pyc βΒ Β βββ settings.py βΒ Β βββ urls.py βΒ Β βββ wsgi.py βββ manage.py βββ users βββ admin.py βββ apps.py βββ __init__.py βββ migrations βΒ Β βββ __init__.py βββ models.py βββ tests.py βββ views.py 4 directories, 14 files
2.1 Using new app
Let’s follow Django’s recommendation:
itβs highly recommended to set up a custom user model, even if the default User model is sufficient for you. This model behaves identically to the default user model, but youβll be able to customize it in the future if the need arises
We start by making Django aware of our new app adding it to
settings.py
:
In dcu/settings.py
:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'users'
]
And defining the default User model as our new CustomUser
model
instead of the default auth.User
using the
AUTH_USER_MODEL
key:
In dcu/settings.py
:
AUTH_USER_MODEL = 'users.CustomUser'
Now every time we need to use our custom model, instead of referring
directly to users.CustomUser
we should use the function
django.contrib.auth.get_user_model
that returns the User model that
is active in this project like:
from django.contrib.auth import get_user_model
UserModel = get_user_model()
That means that you may not use django.contrib.auth.models.User
directly, but use django.contrib.auth.get_user_model
so you will get
the correct User model to work with.
2.2 Extending the User Model
In users.models
:
from django.contrib.auth.models import AbstractUser
class CustomUser(AbstractUser):
pass
2.3 Use new model in Admin
Admin backend uses two forms that has the old User model hardcoded
so we need to subclass them to use our new CustomUser
model.
In users/forms.py
1:
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from .models import CustomUser
class CustomUserCreationForm(UserCreationForm):
class Meta(UserCreationForm.Meta):
model = CustomUser
fields = ('username', 'email')
class CustomUserChangeForm(UserChangeForm):
class Meta:
model = CustomUser
fields = ('username', 'email')
And tell admin to use these new forms:
In users/admin.py
:
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin
from .forms import CustomUserCreationForm, CustomUserChangeForm
from .models import CustomUser
class CustomUserAdmin(UserAdmin):
add_form = CustomUserCreationForm
form = CustomUserChangeForm
model = CustomUser
list_display = ['email', 'username',]
admin.site.register(CustomUser, CustomUserAdmin)
Finally, generate and apply migrations:
(dcu)django-custom-users$./manage.py makemigrations Migrations for 'users': users/migrations/0001_initial.py - Create model CustomUser (dcu)django-custom-users$./manage.py migrate Operations to perform: Apply all migrations: admin, auth, contenttypes, sessions, users Running migrations: Applying contenttypes.0001_initial... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0001_initial... 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 users.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 sessions.0001_initial... OK
3. Creating pages
We will start creating a base.hml
that will contain the HTML
skeleton for our site, which all user related templates will extend
and a simple home.html
template.
3.1 Basic layouts
To create base.html
and home.html
, we tell Django to include a
special /templates
directory at the root of our project, so in
dcu/settings.py
:
TEMPLATES = [
{
#...
'DIRS': ['./templates',],
}
]
Create the directory templates
at project root level:
(dcu)django-custom-users$./manage.py makemigrations
Then in /templates/base.html
:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{% block title %}Django Auth Tutorial{% endblock %}</title>
</head>
<body>
<main>
{% block content %}
{% endblock %}
</main>
</body>
</html>
And a basic homepage /templates/homepage.html
:
{% extends 'base.html' %}
{% block title %}Homepage{% endblock %}
{% block content %}
<a href="{% url 'login' %}">login</a> |
<a href="{% url 'signup' %}">signup</a>
{% endblock %}
3.1 Settings
The django.contrib.auth module makes use of three special settings
in its /views.py
file:
- Default:
/accounts/login/
- “The URL where requests are redirected for login, especially when using the login_required() decorator.”
- Default:
- Default:
/accounts/profile/
- The URL where requests are redirected after login when the contrib.auth.login view gets no next parameter.
- This is used by the
login_required()
decorator.
- Default:
- Default: None
- The URL where requests are redirected after a user logs out using LogoutView (if the view doesnβt get a next_page argument).
You can modify them as needed at dcu/settings.py
:
# LOGIN_URL = '/accounts/login/'
# LOGIN_REDIRECT_URL = '/accounts/profile'
LOGOUT_REDIRECT_URL = 'homepage' #redirect to named pattern
3.2 Adding contrib URLs
The module comes already with the ability to handle the following
URLs in /urls.py
:
accounts/login/ [name='login']
accounts/logout/ [name='logout']
accounts/password_change/ [name='password_change']
accounts/password_change/done/ [name='password_change_done']
accounts/password_reset/ [name='password_reset']
accounts/password_reset/done/ [name='password_reset_done']
accounts/reset/<uidb64>/<token>/ [name='password_reset_confirm']
accounts/reset/done/ [name='password_reset_complete']
To use them, we include them in our main urls.py
file at
dcu/urls.py
:
from django.views.generic.base import TemplateView
urlpatterns = [
# ...
path('', TemplateView.as_view(template_name='homepage.html'), name='homepage'),
path('accounts/', include('users.urls')),
path('accounts/', include('django.contrib.auth.urls')),
]
They are all defined with Class Based Views, and some of the above
URLs use views that depend on templates, some of them exists and
others not, lets have a look at the templates used at django/contrib/auth/
:
class LoginView(SuccessURLAllowedHostsMixin, FormView):
template_name = 'registration/login.html'
class LogoutView(SuccessURLAllowedHostsMixin, TemplateView):
template_name = 'registration/logged_out.html'
class PasswordResetView(PasswordContextMixin, FormView):
template_name = 'registration/password_reset_form.html'
class PasswordResetDoneView(PasswordContextMixin, TemplateView):
template_name = 'registration/password_reset_done.html'
class PasswordResetConfirmView(PasswordContextMixin, FormView):
template_name = 'registration/password_reset_confirm.html'
class PasswordChangeView(PasswordContextMixin, FormView):
template_name = 'registration/password_change_form.html'
class PasswordChangeDoneView(PasswordContextMixin, TemplateView):
template_name = 'registration/password_change_done.html'
But in django/contrib/auth/templates
we only have:
templates/
βββ auth
βΒ Β βββ widgets
βΒ Β βββ read_only_password_hash.html
βββ registration
βββ password_reset_subject.txt
3 directories, 2 files
That means we need to create the following templates:
registration/login.html
registration/logged_out.html'
registration/password_reset_form.html'
registration/password_reset_done.html'
registration/password_reset_confirm.html'
registration/password_change_form.html'
registration/password_change_done.html'
3.2.1 Add login template
In /users/templates/registration/login.html
:
{% extends "base.html" %}
{% block title %}Login{% endblock %}
{% block content %}
{% if form.errors %}
<p>Your username and password didn't match. Please try again.</p>
{% endif %}
{% if next %}
{% if user.is_authenticated %}
<p>Your account doesn't have access to this page. To proceed,
please login with an account that has access.</p>
{% else %}
<p>Please login to see this page.</p>
{% endif %}
{% endif %}
<form method="post" action="{% url 'login' %}">
{% csrf_token %}
<table>
<tr>
<td>{{ form.username.label_tag }}</td>
<td>{{ form.username }}</td>
</tr>
<tr>
<td>{{ form.password.label_tag }}</td>
<td>{{ form.password }}</td>
</tr>
</table>
<input type="submit" value="login">
<input type="hidden" name="next" value="{{ next }}">
</form>
<p><a href="{% url 'password_reset' %}">Lost password?</a></p>
{% endblock %}
3.2.2 Add logged out template
In /users/templates/registration/logged_out.html
:
{% extends "base.html" %}
{% block title %}Logged out{% endblock %}
{% block content %}
<p>You have been logged out</p>
<a href="{% url 'login'%}">Login</a>
{% endblock %}
3.2.3 Add password related templates
In registration/password_reset_form.html
:
{% extends "base.html" %}
{% block title %}Password reset form{% endblock %}
{% block content %}
<form action="" method="post">
{% csrf_token %}
{% if form.email.errors %}
{{ form.email.errors }}
{% endif %}
<p>{{ form.email }}</p>
<input type="submit" value="Reset password" />
</form>
{% endblock %}
In registration/password_reset_done.html
:
{% extends "base.html" %}
{% block title %}Password reset done{% endblock %}
{% block content %}
<p>Check your mail for resetting your password.</p>
{% endblock %}
In registration/password_reset_confirm.html
:
{% extends "base.html" %}
{% block title %}Password reset confirm{% endblock %}
{% block content %}
{% if validlink %}
<p>Please enter your new password.</p>
<form action="" method="post">
<div style="display:none">
<input type="hidden" value="{{ csrf_token }}" name="csrfmiddlewaretoken">
</div>
<table>
<tr>
<td>{{ form.new_password1.errors }}
<label for="id_new_password1">New password:</label></td>
<td>{{ form.new_password1 }}</td>
</tr>
<tr>
<td>{{ form.new_password2.errors }}
<label for="id_new_password2">Confirm password:</label></td>
<td>{{ form.new_password2 }}</td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="Change my password" /></td>
</tr>
</table>
</form>
{% else %}
<h1>Failed</h1>
<p>The password reset link was invalid. Please request a new password.</p>
{% endif %}
{% endblock %}
In registration/password_change_form.html
:
{% extends "base.html" %}
{% block title %}Password change form{% endblock %}
{% block content %}
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit" />
</form>
{% endblock %}
In registration/password_change_done.html
:
{% extends "base.html" %}
{% block title %}Password change done{% endblock %}
{% block content %}
<p>Password changed</p>
{% endblock %}
3.3 Add signup and profile page
In users/templates/signup.html
:
{% extends 'base.html' %}
{% block title %}Sign Up{% endblock %}
{% block content %}
<h1>Sign up</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit">Sign up</input>
</form>
{% endblock %}
In users/templates/profile.html
:
{% extends 'base.html' %}
{% block title %}User Profile{% endblock %}
{% block content %}
{% if user.is_authenticated %}
<p>User: {{ user.username }} logged in.</p>
<p><a href="{% url 'homepage' %}">homepage</a></p>
<p><a href="{% url 'logout' %}">logout</a></p>
{% else %}
<a href="{% url 'login' %}">login</a> |
<a href="{% url 'signup' %}">signup</a>
{% endif %}
{% endblock %}
And we map the route with it users/urls.py
:
from django.urls import path
from . import views
from django.views.generic.base import TemplateView
urlpatterns = [
path('signup/', views.SignUp.as_view(), name='signup'),
path('profile/', TemplateView.as_view(template_name='profile.html'), name='profile'),
]
That url uses the views.Signup
view as defined at users/views.py
:
from django.urls import reverse_lazy
from django.views.generic import CreateView
from .forms import CustomUserCreationForm
class SignUp(CreateView):
form_class = CustomUserCreationForm
success_url = reverse_lazy('login')
template_name = 'signup.html'
Final tree
This is the final structure of the project:
.
βββ db.sqlite3
βββ dcu
βΒ Β βββ __init__.py
βΒ Β βββ settings.py
βΒ Β βββ urls.py
βΒ Β βββ wsgi.py
βββ Makefile
βββ manage.py
βββ README.md
βββ README.md~
βββ requirements.txt
βββ templates
βΒ Β βββ base.html
βΒ Β βββ homepage.html
βΒ Β βββ homepage.html~
βββ users
βββ admin.py
βββ apps.py
βββ forms.py
βββ __init__.py
βββ migrations
βΒ Β βββ 0001_initial.py
βΒ Β βββ __init__.py
βββ models.py
βββ templates
βΒ Β βββ profile.html
βΒ Β βββ profile.html~
βΒ Β βββ registration
βΒ Β βΒ Β βββ logged_out.html
βΒ Β βΒ Β βββ login.html
βΒ Β βΒ Β βββ password_change_done.html
βΒ Β βΒ Β βββ password_change_form.html
βΒ Β βΒ Β βββ password_reset_confirm.html
βΒ Β βΒ Β βββ password_reset_done.html
βΒ Β βΒ Β βββ password_reset_form.html
βΒ Β βββ signup.html
βββ tests.py
βββ urls.py
βββ views.py
With the following classes:
And this URLs:
/ django.views.generic.base.TemplateView homepage
/accounts/login/ django.contrib.auth.views.LoginView login
/accounts/logout/ django.contrib.auth.views.LogoutView logout
/accounts/password_change/ django.contrib.auth.views.PasswordChangeView password_change
/accounts/password_change/done/ django.contrib.auth.views.PasswordChangeDoneView password_change_done
/accounts/password_reset/ django.contrib.auth.views.PasswordResetView password_reset
/accounts/password_reset/done/ django.contrib.auth.views.PasswordResetDoneView password_reset_done
/accounts/profile/ django.views.generic.base.TemplateView profile
/accounts/reset/<uidb64>/<token>/ django.contrib.auth.views.PasswordResetConfirmView password_reset_confirm
/accounts/reset/done/ django.contrib.auth.views.PasswordResetCompleteView password_reset_complete
/accounts/signup/ users.views.SignUp signup
/admin/ django.contrib.admin.sites.index admin:index
/admin/<app_label>/ django.contrib.admin.sites.app_index admin:app_list
/admin/auth/group/ django.contrib.admin.options.changelist_view admin:auth_group_changelist
/admin/auth/group/<path:object_id>/ django.views.generic.base.RedirectView
/admin/auth/group/<path:object_id>/change/ django.contrib.admin.options.change_view admin:auth_group_change
/admin/auth/group/<path:object_id>/delete/ django.contrib.admin.options.delete_view admin:auth_group_delete
/admin/auth/group/<path:object_id>/history/ django.contrib.admin.options.history_view admin:auth_group_history
/admin/auth/group/add/ django.contrib.admin.options.add_view admin:auth_group_add
/admin/auth/group/autocomplete/ django.contrib.admin.options.autocomplete_view admin:auth_group_autocomplete
/admin/jsi18n/ django.contrib.admin.sites.i18n_javascript admin:jsi18n
/admin/login/ django.contrib.admin.sites.login admin:login
/admin/logout/ django.contrib.admin.sites.logout admin:logout
/admin/password_change/ django.contrib.admin.sites.password_change admin:password_change
/admin/password_change/done/ django.contrib.admin.sites.password_change_done admin:password_change_done
/admin/r/<int:content_type_id>/<path:object_id>/ django.contrib.contenttypes.views.shortcut admin:view_on_site
/admin/users/customuser/ django.contrib.admin.options.changelist_view admin:users_customuser_changelist
/admin/users/customuser/<id>/password/ django.contrib.auth.admin.user_change_password admin:auth_user_password_change
/admin/users/customuser/<path:object_id>/ django.views.generic.base.RedirectView
/admin/users/customuser/<path:object_id>/change/ django.contrib.admin.options.change_view admin:users_customuser_change
/admin/users/customuser/<path:object_id>/delete/ django.contrib.admin.options.delete_view admin:users_customuser_delete
/admin/users/customuser/<path:object_id>/history/ django.contrib.admin.options.history_view admin:users_customuser_history
/admin/users/customuser/add/ django.contrib.auth.admin.add_view admin:users_customuser_add
/admin/users/customuser/autocomplete/ django.contrib.admin.options.autocomplete_view admin:users_customuser_autocomplete
Repo
There is a Github repo after following all the above steps at: https://github.com/marcanuy/django-custom-users
References
- https://docs.djangoproject.com/en/2.1/topics/auth/customizing/
- https://wsvincent.com/django-custom-user-model-tutorial/
Source code from https://wsvincent.com/django-custom-user-model-tutorial/ ↩︎
- August 1, 2023
- How to create a reusable Django app and distribute it with PIP or publish to pypi.orgJune 29, 2021
- How To Serve Multiple Django Applications with uWSGI and Nginx in Ubuntu 20.04October 26, 2020
- How to add favicon to Django in 4 stepsSeptember 3, 2020
- Categories in Django with BreadcrumbsAugust 30, 2020
- How To Migrate From SQLite To PostgreSQL In Django In 3 stepsAugust 28, 2020
- Practical guide to internationalize a Django app in 5 steps.August 24, 2020
- Disable new users singup when using Django's allauth packageSeptember 3, 2019
- How to add ads.txt to Django as requested by Google AdsenseAugust 30, 2019
- Have multiple submit buttons for the same Django formJuly 2, 2019
- Better Testing with Page Object Design in DjangoMay 1, 2019
- Generating slugs automatically in Django without packages - Two easy and solid approachesFebruary 14, 2019
- How to set up Django tests to use a free PostgreSQL database in HerokuFebruary 13, 2019
- Dynamically adding forms to a Django FormSet with an add button using jQueryFebruary 6, 2019
- Use of Django's static templatetag in css file to set a background imageFebruary 1, 2019
- Activate Django's manage.py commands completion in Bash in 2 stepsJanuary 29, 2019
- Sending Emails with Django using SendGrid in 3 easy stepsJanuary 9, 2019
- Adding Users to Your Django Project With A Custom User Model
- Setting Up A Factory For One To Many Relationships In FactoryboyApril 17, 2018
- Generate UML class diagrams from django modelsMarch 24, 2018
- Set Up Ubuntu To Serve A Django Website Step By StepJuly 3, 2017
- Django Project Directory StructureJuly 16, 2016
- How to Have Different Django Settings for Development and Production, and environment isolationJune 10, 2016
- Django OverviewJune 2, 2016
Django Forms
- Adding a Cancel button in Django class-based views, editing views and formsJuly 15, 2019
- Using Django Model Primary Key in Custom Forms THE RIGHT WAYJuly 13, 2019
- Django formset handling with class based views, custom errors and validationJuly 4, 2019
- How To Use Bootstrap 4 In Django FormsMay 25, 2018
- Understanding Django FormsApril 30, 2018
- How To Create A Form In DjangoJuly 29, 2016
Articles
Subcategories
Except as otherwise noted, the content of this page is licensed under CC BY-NC-ND 4.0 . Terms and Policy.
Powered by SimpleIT Hugo Theme
·