Roy Prins · Follow
7 min read · Dec 15, 2018
--
Django offers a built-in User model with utilities for authentication, password hashing, etc. As your app develops, it is fully to be expected that the default User model will not exactly fit your needs. Replacing it in a later phase is a hassle, so it is recommended to roll your custom user model from the start. Actually it is the official recommendation.
In a lot of cases, there is no use for the username
field: using only the user’s email
for registration will make the user experience a bit better. Another quirk of Django’s user are the first_name
and last_name
fields. Coming from a country where ‘interjections’ are a thing, this will not suffice. A simple optional name
field would be much better.
This post will detail how to create a custom user with email
authentication and how to integrate this in your admin interface, and your project as a whole. We will then replace the custom views with django-allauth
and add Oauth (3rd party / social login).
- Basic Django setup
- About custom user models
- Create the model and manager
- Use the custom user
- Custom views for account management
- Using Allauth for account management
Here is a brief rundown to get started. We will name the project “customuser”.
pipenv install
pipenv shell
pipenv install django
django-admin.py startproject customuser . # mind the dot
python manage.py runserver
This should give you a test server on: http://127.0.0.1:8000/.
Note that we did not yet apply any migrations. It is best to first make migrations for your custom user. Not having the custom user model when you start your project will likely lead to conflicts!
An example project can be found on github: https://github.com/snirp/django-email-authentication
Django documents three ways to extend or replace the default User:
- Extend with a one-to-one field to
User
- Substitute by subclassing from
AbstractUser
: this preserves the fields and authentication methods - Substitute by subclassing from
AbstractBaseUser
: this will not preserve the default fields, also impacting existing methods.
The third option is most involved, but it is the one we will choose, because it offers the freedom to customize registration with email
rather than username
.
First order of business: a new app named users
to hold our custom user model. Name it however you please.
python manage.py startapp users
And add the following to settings.py
. This indicates that we want to use a yet-to-be-created model users.User
for authentication, replacing the default django.contrib.auth.models.User
.
INSTALLED_APPS = [
...
'users',
]AUTH_USER_MODEL = 'users.User'
When you need to refer to the custom user from other parts of your code, you can do that in any of the following ways:
from users import User
from customuser.settings import AUTH_USER_MODEL
- use the
get_user_model()
method fromdjango.contrib.auth
The third method is preferred — certain for code that you intend to re-use — as it deals with both the default and custom user models.
The following model in users/models.py
takes care of the custom User model.
Our User
subclasses AbstractBaseUser
, giving it the methods, but none of the default fields. We define our own fields, with the following being special cases:
USERNAME_FIELD
: The name of the field that will serve as unique identifier (which will be theemail
field for us).EMAIL_FIELD
: The name of the field that will be returned whenget_email_field_name()
is called on a User instance.REQUIRED_FIELDS
: Required fields besides thepassword
andUSERNAME_FIELD
when signing up.is_staff
: required by the admin.is_superuser
: used by thePermissionsMixin
to grant all permissions.is_active
: indicates whether the user is considered “active”.
The UserManager
subclasses the BaseUserManager
and overrides the methods create_user
and create_superuser
. These custom methods are needed because the default methods expect a username to be provided. The admin app and manage.py
will call these methods.
Apply the migrations and create a superuser. You should be prompted for an Email:
(and not a username) and no errors should occur.
python manage.py makemigrations users
python manage.py migrate
python manage.py createsuperuser
Email:
python manage.py runserver
The admin interface isn’t aware of the custom User model yet, so let’s take care of that in users/admin.py
:
After running the server, try to log into 127.0.0.1:8000/admin with the newly created superuser account. Try adding, editing and removing a few more user accounts for good measure.
You can also play around with the User model in the shell:
python manage.py shell
>>> from users.models import User
>>> u = User.objects.all()[0]
>>> u.set_password("newPassword")
>>> u.save()
>>> u.get_absolute_url()
# '/users/1/'
It is worth taking the following from the Django docs into consideration concerning custom user models:
Think carefully before handling information not directly related to authentication in your custom user model.
It may be better to store app-specific user information in a model that has a relation with the user model. That allows each app to specify its own user data requirements without risking conflicts with other apps. On the other hand, queries to retrieve this related information will involve a database join, which may have an effect on performance.
We will next look at custom account-related operations, such as: logging in, signing up and viewing the account information. This section shows you how to do it with custom views. In most cases it would be better to use a library for these common operations, so feel free to skip ahead to the next section, where we will cover the same using the Allauth
library.
The users should be able to log in, log out, sign up and view their profile. This is what that would look like in customuser/urls.py
and users/urls.py
.
The urls should be self-explanatory. Notice that we use the built-in views for log in and log out and that we redirect to the /accounts/login
on logging out.
The other two views — for profile
and signup
— are custom. Let’s take a look at users/forms.py
and users/views.py
The SignUpForm
is derived from the built-in UserCreationForm
, making use of the custom User model and replacing the username
field with the email
field.
Notice that the signup
view also authenticates the user with their email
:
user = authenticate(request, email=user.email, password=raw_password)
The UserView is a “class based view” in which we override the get_object
method. It would normally expect a primary key in the request to get the correct instance, but here we can get the user
instance directly from the request. We can be sure that the request has a user, because we added the login_required
decorator to this view in urls.py
.
These are the templates used for the account management:
No rocket science here. Start the server to test the url’s and authentication views.
Checkout the
custom
branch of the repository to view the code at this point.
In the previous step we wrote the views for basic account management. As the requirements grow, this will result in more custom work. Fortunately there are a few libraries that are designed to handle this exact scenario. We will use the well-designed library “django-allauth”, at it works well with our custom User model.
It also handles additional requirements such as:
- email address verification
- enter a single password on sign up
- add social authentication (Oauth)
- reset passwords
pipenv install django-allauth
Configuration
Assuming you start with a default settings.py
, these are the changes required by Allauth in settings.py
:
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend', # existing backend
'allauth.account.auth_backends.AuthenticationBackend',
)INSTALLED_APPS = [
...
'django.contrib.sites', # make sure sites is included
'allauth',
'allauth.account',
'allauth.socialaccount',# the social providers
'allauth.socialaccount.providers.facebook',
'allauth.socialaccount.providers.google',
'allauth.socialaccount.providers.twitter',
...
]SITE_ID = 1ACCOUNT_USER_MODEL_USERNAME_FIELD = None
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_AUTHENTICATION_METHOD = 'email'
Edit urls.py
:
urlpatterns = [
...
path('accounts/', include('allauth.urls')),
...
]
And finally:
python manage.py migrate
python manage.py runserver
You will now have the basic functionality of our “custom” solution above and then some. The Allauth views and forms gracefully handle our User model with email authentication, after we provided this information in the settings.
Add Oauth providers
A further feature is the support of the OAuth authentication standard. This allows third party servers to perform the authentication. Users can also authorize your app to access profile data, such as the email address.
After starting your server, visit the admin pages (localhost:8000/admin/) and follow these steps:
- In the admin add a
Site
for your domain, matchingsettings.SITE_ID
. Set the Domain name tohttp://127.0.0.1:8000
for development. - For each OAuth based provider, add a
Social Application
. - Go to the provider (Google, etc) to add your client app, and add a redirect URL in the form of:
http://127.0.0.1:8000/accounts/google/login/callback/
- The provider will give you a Client ID and a Client Secret. Fill these in with the Social Application in the admin.
The official documentation has detailed instructions, and a list of social authentication providers.
Try it out
Allauth gives a range of account functionalities. Try them out:
- http://127.0.0.1:8000/accounts/signup/
- http://127.0.0.1:8000/accounts/logout/
- http://127.0.0.1:8000/accounts/login/
login with email or social account - http://127.0.0.1:8000/accounts/email/
manage email addresses - http://127.0.0.1:8000/accounts/password/change/
- http://127.0.0.1:8000/accounts/social/connections/
add social accounts to your user account
Clone the repository to view the code at this point.
Implementing a custom user model is a non-trivial exercise, but made much easier by libraries such as Allauth. As it is recommended to start your project with a custom user model, I would advise to start Django projects from a pre-configured repository. Skip django-admin startproject
and start with a template that has at least a custom user model.