User Registration in Django

Django authentication framework (django.contrib.auth) provides a form named UserCreationForm (which inherits from ModelForm class) to handle the creation of new users. It has three fields namely username, password1 and password2 (for password confirmation). To use UserCreationForm you have to first import it from django.contrib.auth.forms as follows:

Unfortunately, Django doesn’t provide any view to handle the creation of users, so we have to create our own view.

In djangobin app’s urls.py file, add a URL pattern named signup at the end of the urlpatterns list.

djangobin/django_project/djangobin/urls.py

Create a view function called signup() in djangobin’s views.py as follows:

djangobin/django_project/djangobin/views.py

Next, create a new template signup.html in the templates directory with the following code:

djangobin/django_project/djangobin/templates/djangobin/signup.html

Add a link to Sign Up page in base.html template as follows:

djangobin/django_project/djangobin/templates/djangobin/base.html

Open your browser and navigate to http://127.0.0.1:8000/signup/. You should see Sign Up page like this.

Create a new user by entering username and password. On success, you will be greeted with “Account created successfully” message.

If entered username already exists or passwords don’t match then the form will display errors like this:

Users created using UserCreationForm will have is_superuser and is_staff attribute set to False but is_active set to True, which means that these users can’t login into the Django admin site.

Another point to keep in mind is that everytime we create a new User object, an associated Author instance will be created automatically. This is because the post_save signal from the User model triggers the execution of create_author() function defined in models.py file.

The only drawback of UserCreationForm is that it doesn’t have email field. As a result, we can’t use it to send email verification to verify the account.

Most of the time a user registration involves the following steps:

  1. The user fills the registration form and hit submit.
  2. The site sends an email verification link to the submitted email.
  3. User clicks on the activation link to verify the account.

At this point, we have two options:

  1. Extend the UserCreationForm to include email field and email verification capability.
  2. Create a completely new user registration form from scratch.

In Django 1.10 series, we have already seen how you can go about creating a registration form from scratch. Consequently, in this series, we will take an alternate approach.

Extending UserCreationForm

Open djangobin’s forms.py and add CreateUserForm towards the end of the file as follows:

djangobin/django_project/djangobin/forms.py

The first step in creating a custom registration form is to create a class which inherits from UserCreationForm class.

In line 19, we specify model fields we want to show in the form.

In lines 21-27, we are defining a cleaning method for the email field. This method ensures that the user must provide a unique email id.

Finally, in lines 29-50, we are overriding save() method of the UserCreationForm. The save() method does two things: saves the user into the database after setting its is_active attribute to False (so that account can’t be used to login) and sends an email verification to the user email.

To send email we are using built-in send_mail() function.

The code for email body and subject is stored in the email subdirectory inside the djangobin’s templates/djangobin directory.

djangobin/django_project/djangobin/templates/djangobin/email/activation_subject.txt

djangobin/django_project/djangobin/templates/djangobin/email/activation_email.txt

In lines 45-46, we are using a new function named render_to_string(). The render_to_string() function loads the template, renders it and returns the resulting string.

The context variable we define in lines 35-43, contains the data that will be used by our templates.

There are two important bits of information here:

  1. uid
  2. token

We use these two pieces of information to create the activation link which will be sent to the user’s email.

The uid is the user’s primary key encoded in base 64 and token is a hash value created using user related data and the current timestamp. A token is used to check whether the activation like is valid or not. By default, the token is only valid for 3 days.

We have included an encoded version of user’s primary key in the activation link so that the activation function can determine the user it needs to activate.

To create the base 64 value from the user’s primary key, we use urlsafe_base64_encode() function. It accepts bytestring and returns a base 64 value. To convert an integer to bytestring, we use force_bytes() function (line 41).

For generating tokens, Django provides a class named PasswordResetTokenGenerator.

This class has two methods:

  1. make_token(user)
  2. check_token(user, token)

The make_token() accepts a user, and returns a token based upon the user related data (line 42). This is how a token looks like:

And the complete verification link looks like this:

where MQ refers to the user’s primary key encoded in base 64.

Now you know how the activation like is created. Let’s see what happens when a user clicks the link.

The first step in activating an account is to decode the user’s primary key in base 64 (MQ in the above URL). To do so, we use use urlsafe_base64_decode() function. The function returns bytestring, so we use force_bytes() function to convert bytestring to str.

Once we have the primary key, we fetch the associated object from the database and call check_token() to verify the whether the token is valid or not.

If the token is valid we set is_active attribute to True and redirect the user to the login page. On the other hand, if the token is invalid, we show an invalid token error message to the user.

Open views.py and modify signup() view function to use CreateUserForm as follows:

djangobin/django_project/djangobin/views.py

The view function to activate the user is called activate_account() and is defined just below the signup() view function:

djangobin/django_project/djangobin/views.py

In the djangobin’s urls.py file add a new URL pattern to activate the account as follows:

djangobin/django_project/djangobin/urls.py

Now, open your browser and navigate to http://localhost:8000/signup/. Enter data in all the fields and hit submit.

In the shell, running the server you will get a verification email like this:

To activate the account, copy the verification link and paste it into your browser address bar. On success, you will be redirected to the login page:

If the link is expired or tampered with, then will see the following error.

Resetting Password

Let’s admit, once in a while, everyone forgets their password. Resetting password involves working with the following four built-in view functions:

  • password_reset()
  • password_reset_done()
  • password_reset_confirm()
  • password_reset_complete()

Here is the workflow for resetting the password:

  1. The password_reset() view displays the form which lets users to enter email and sends the password reset link to the user’s email.
  2. The password_reset_done() view shows a page containing instructions on how to reset password like open email, click the link, look for an email in spam etc. This view is called immediately after the password_reset() view done working. Note that this view will always be called no matter whether the email entered in step 1 exists in the database or not. This prevents potential attackers from knowing whether an email exists in the database or not.
  3. The password_reset_confirm() is called when the user visits the password reset link sent to the email. It shows a form for entering a new password.
  4. Finally, the password_reset_complete() view is called to inform the user that the password has been changed.

The following table lists the default templates used by these view functions:

View Templates
password_reset() registration/password_reset_form.html, registration/password_reset_email.html, registration/password_reset_subject.txt
password_reset_done() registration/password_reset_done.html
password_reset_confirm() registration/password_reset_confirm.html
password_reset_complete() registration/password_reset_confirm.html

These templates are stored in the templates directory of the django.contrib.admin app.

The look and feel of all these templates closely resemble to the Django admin site. As a result, they are not suitable for our project.

So how do we override default templates?

If you inspect the signature of view functions in django/contrib/auth/views.py, you would find that each of them accepts an argument named template_name.

django/contrib/auth/views.py

We will also override the default template used to create the email. If you pay close attention to the password_reset() signature you would find that the arguments of interest are: email_template_name and subject_template_name.

Open urls.py file and add the following 4 URL patterns at the end of the list:

djangobin/django_project/djangobin/urls.py

Here are few things to notice:

  1. The post_reset_redirect argument in the password_reset and password_reset_confirm URL pattern specifies the URL to redirect after after view has done working.
  2. Just like activate_account() view the password_reset_confirm() also takes uidb64 (user’s id encoded in base 64) and token as arguments. As usual, the password_reset_confirm() view decodes the value in uidb64, to know the user it is resetting a password for. And the token is used to check whether the password reset link is valid or not.

The code for templates used for resetting password is as follows:

djangobin/django_project/djangobin/templates/djangobin/password_reset.html

djangobin/django_project/djangobin/templates/djangobin/email/password_reset_email.txt

djangobin/django_project/djangobin/templates/djangobin/email/password_reset_subject.txt

djangobin/django_project/djangobin/templates/djangobin/password_reset_done.html

djangobin/django_project/djangobin/templates/djangobin/password_reset_confirm.html

djangobin/django_project/djangobin/templates/djangobin/password_reset_complete.html

Open your browser and navigate to http://localhost:8000/password-reset/. You will get a password reset page asking for email an like this:

Enter a valid email and hit submit. You will be taken to http://localhost:8000/password-reset-done/ URL which looks like this:

If the email entered exists in the database, then you should see the following output in the shell.

Copy the link and paste it into the browser address bar. You will be presented a form for entering a new password.

Enter new password and hit enter. Finally, you get a success message like this:

It is important to note that the password reset link is only valid for 3 days. To extend the validity of the link use PASSWORD_RESET_TIMEOUT_DAYS setting.

Furthermore, password reset link automatically expires after a successful password reset. Trying to visit a password reset link which is expired or tampered with or already used will result in the following error:

Password Change

In this section, we will create a form to allow logged in users to change their password.

Just like password reset, Django provides the following two built-in views to handle password changes:

  1. password_change
  2. password_change_done

The password_change view displays a form which allows logged in users to change user password, after entering old password.

And the password_change_done view displays success message after the user has changed their password.

Both of these views are password protected so you have to login before you access them.

By default, password_change() and password_change_done() uses password_change_form.html and password_change_done.html templates respectively from Django admin app (django.contrib.admin). We can override this behavior by using template_name keyword argument.

Open urls.py file and add following two URL patterns at the end of the list:

djangobin/django_project/djangobin/urls.py

Create two new templates password_change.html and password_change_done.html in the templates directory with the following code:

djangobin/django_project/djangobin/templates/djangobin/password_change.html

djangobin/django_project/djangobin/templates/djangobin/password_change_done.html

Now, open your browser and navigate to http://localhost:8000/password-change/. You should see password change page like this:

Change your password and on success, you will get a page like this:

Leave a Comment