OverIQ.com

User Registration in Django

Last updated on July 27, 2020


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:

from django.contrib.auth.forms import UserCreationForm

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

1
2
3
4
5
6
#...
urlpatterns = [
    #...
    url(r'^userdetails/$', views.user_details, name='user_details'),
    url(r'^signup/$', views.signup, name='signup'),
]

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

djangobin/django_project/djangobin/views.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#...
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import UserCreationForm
import datetime
from .forms import SnippetForm, ContactForm, LoginForm
#...

def login(request, **kwargs):
    #...

def signup(request):

    if request.user.is_authenticated:
        return redirect('djangobin:profile', username=request.user.username)

    if request.method == 'POST':
        f = UserCreationForm(request.POST)
        if f.is_valid():
            f.save()
            messages.success(request, 'Account created successfully')
            return redirect('signup')

    else:
        f = UserCreationForm()

    return render(request, 'djangobin/signup.html', {'form': f})

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
{% extends "djangobin/base.html"  %}

{% block title %}
    Sign Up - {{ block.super }}
{% endblock %}

{% block main %}

    <div class="row">
        <div class="col-lg-6 col-md-6 col-sm-6">
            <h4>Sign Up</h4>
            <hr>
            {% if messages %}
                {% for message in messages %}
                    <p class="alert alert-info">{{ message }}</p>
                {% endfor %}
            {% endif %}

            <form method="post">

                {% csrf_token %}

                <table class="table">
                    {{ form.as_table }}
                    <tr>
                        <td><input type="hidden" name="next" value="{{ next }}"></td>
                        <td><button type="submit" class="btn btn-primary">Submit</button></td>
                    </tr>
                </table>

            </form>
        </div>

        <div class="col-lg-6 col-md-6 col-sm-6">
            <h4>Related Links</h4>
            <p>
                <a href="/reset-password/">Forgot Password?</a> <br>
                <a href="{% url 'djangobin:login' %}">Login.</a> <br>
            </p>
        </div>

    </div>

{% endblock %}

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
{# ... #}
                    {% if request.user.is_authenticated %}
                        <ul class="dropdown-menu">
                            <li><a href="">My Pastes</a></li>
                            <li><a href="">Account Details</a></li>
                            <li><a href="">Settings</a></li>
                            <li role="separator" class="divider"></li>
                            <li><a href="{% url 'djangobin:logout' %}">Logout</a></li>
                        </ul>
                    {% else %}
                        <ul class="dropdown-menu">
                            <li><a href="{% url 'djangobin:signup' %}">Sign Up</a></li>
                            <li><a href="{% url 'djangobin:login' %}?next={{ request.path }}">Login</a></li>
                        </ul>
                    {% endif %}
{# ... #}

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#...
from django.core.exceptions import ValidationError
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.tokens import default_token_generator
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode
from django.conf import settings
from django.template.loader import render_to_string
from django.core.mail import send_mail
from .models import Snippet, Language, Author, Tag
from .utils import Preference, get_current_user

#...

class CreateUserForm(UserCreationForm):
    class Meta:
        model = User
        fields = ('username', 'email', 'password1', 'password2')

    def clean_email(self):
        email = self.cleaned_data['email']
        if not email:
            raise ValidationError("This field is required.")
        if User.objects.filter(email=self.cleaned_data['email']).count():
            raise ValidationError("Email is taken.")
        return self.cleaned_data['email']

    def save(self, request):

        user = super(CreateUserForm, self).save(commit=False)
        user.is_active = False
        user.save()

        context = {
            # 'from_email': settings.DEFAULT_FROM_EMAIL,
            'request': request,
            'protocol': request.scheme,
            'username': self.cleaned_data.get('username'),
            'domain': request.META['HTTP_HOST'],
            'uid': urlsafe_base64_encode(force_bytes(user.pk)),
            'token': default_token_generator.make_token(user),
        }

        subject = render_to_string('djangobin/email/activation_subject.txt', context)
        email = render_to_string('djangobin/email/activation_email.txt', context)

        send_mail(subject, email, settings.DEFAULT_FROM_EMAIL, [user.email])

        return user

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

Action Required to Complete the Account Creation - djangobin

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

1
2
3
4
5
6
7
Hello {{ username }}!

To confirm your registration, visit the following link:

{{ protocol }}://{{ domain }}{% url 'djangobin:activate' uid token %}

Welcome to Djanagobin!

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:

4vf-8544d0407636ec564e5b

And the complete verification link looks like this:

http://example.com/MQ/4vf-8544d0407636ec564e5b/

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#...
import datetime
from .forms import SnippetForm, ContactForm, LoginForm, CreateUserForm
from .models import Language, Snippet, Tag
from .utils import paginate_result
#...


def signup(request):
    if request.method == 'POST':
        f = CreateUserForm(request.POST)
        if f.is_valid():
            f.save(request)
            messages.success(request, 'Account created successfully. Check email to verify the account.')
            return redirect('djangobin:signup')

    else:
        f = CreateUserForm()

    return render(request, 'djangobin/signup.html', {'form': f})

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#...
import datetime
from django.contrib.auth.tokens import default_token_generator
from django.utils.encoding import force_bytes, force_text
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
from .forms import SnippetForm, ContactForm, LoginForm, CreateUserForm
#...

#...

def signup(request):
    #...

def activate_account(request, uidb64, token):
    try:
        uid = force_text(urlsafe_base64_decode(uidb64))
        user = User.objects.get(pk=uid)
    except (TypeError, ValueError, OverflowError, User.DoesNotExist):
        user = None
    if (user is not None and default_token_generator.check_token(user, token)):
        user.is_active = True
        user.save()
        messages.add_message(request, messages.INFO, 'Account activated. Please login.')
    else:
        messages.add_message(request, messages.INFO, 'Link Expired. Contact admin to activate your account.')

    return redirect('djangobin:login')

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

djangobin/django_project/djangobin/urls.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#...
urlpatterns = [
    #...
    url(r'^userdetails/$', views.user_details, name='user_details'),
    url(r'^signup/$', views.signup, name='signup'),
    url(r'^activate/'
        r'(?P<uidb64>[0-9A-Za-z_\-]+)/'
        r'(?P<token>[0-9A-Za-z]{1,13}'
        r'-[0-9A-Za-z]{1,20})/$',
        views.activate_account, name='activate'),
]

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
[11/May/2018 11:27:31] "GET /signup/ HTTP/1.1" 200 10594
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: Action Required to Complete the Account Creation - djangobin
From: infooveriq@gmail.com
To: django@example.com
Date: Fri, 11 May 2018 11:27:53 -0000
Message-ID: <20180511112753.25336.4090@pc>

Hello django!

To confirm your registration, visit the following link:

http://localhost:8000/activate/MTM/4w3-81e3511bd45dc04e8ba9/

Welcome to Djanagobin!
-------------------------------------------------------------------------------

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
def password_reset(request,
                   template_name='registration/password_reset_form.html',
                   email_template_name='registration/password_reset_email.html',
                   subject_template_name='registration/password_reset_subject.txt',
                   password_reset_form=PasswordResetForm,
                   #...):

def password_reset_done(request,
                        template_name='registration/password_reset_done.html',
                        extra_context=None):                   

def password_reset_confirm(request, uidb64=None, token=None,
                           template_name='registration/password_reset_confirm.html',
                           token_generator=default_token_generator,
                           #...):

def password_reset_complete(request,
                            template_name='registration/password_reset_complete.html',
                            extra_context=None):

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#...
urlpatterns = [
    #...
    url(r'^activate/'
        r'(?P<uidb64>[0-9A-Za-z_\-]+)/'
        r'(?P<token>[0-9A-Za-z]{1,13}'
        r'-[0-9A-Za-z]{1,20})/$',
        views.activate_account, name='activate'),

    # password reset URLs

    url('^password-reset/$', auth_views.password_reset,
        {'template_name': 'djangobin/password_reset.html',
         'email_template_name': 'djangobin/email/password_reset_email.txt',
         'subject_template_name': 'djangobin/email/password_reset_subject.txt',
         'post_reset_redirect': 'djangobin:password_reset_done',
        },
        name='password_reset'),

    url('^password-reset-done/$', auth_views.password_reset_done,
        {'template_name': 'djangobin/password_reset_done.html',},
        name='password_reset_done'),

    url(r'^password-confirm/'
        r'(?P<uidb64>[0-9A-Za-z_\-]+)/'
        r'(?P<token>[0-9A-Za-z]{1,13}'
        r'-[0-9A-Za-z]{1,20})/$',
        auth_views.password_reset_confirm,
        {'template_name': 'djangobin/password_reset_confirm.html',
         'post_reset_redirect': 'djangobin:password_reset_complete'},
        name='password_reset_confirm'),

    url(r'password-reset-complete/$',
        auth_views.password_reset_complete,
        {'template_name':
             'djangobin/password_reset_complete.html'},
        name='password_reset_complete'),
    ]

Here are few things to notice:

  • 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.

  • 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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
{% extends "djangobin/base.html"  %}

{% block title %}
    Password Reset - {{ block.super }}
{% endblock %}

{% block main %}

    <div class="row">
        <div class="col-lg-6 col-md-6 col-sm-6">
            <h4>Password Reset</h4>
            <hr>
            {% if messages %}
                {% for message in messages %}
                    <p class="alert alert-info">{{ message }}</p>
                {% endfor %}
            {% endif %}

            <form method="post">

                {% csrf_token %}

                <table class="table">
                    {{ form.as_table }}
                    <tr>
                        <td><input type="hidden" name="next" value="{{ next }}"></td>
                        <td><button type="submit" class="btn btn-primary">Submit</button></td>
                    </tr>
                </table>

            </form>
        </div>

        <div class="col-lg-6 col-md-6 col-sm-6">
            <h4>Related Links</h4>
            <p>
                <a href="{% url  'djangobin:signup' %}">Sign Up</a> <br>
                <a href="{% url  'djangobin:login' %}">Login.</a> <br>
            </p>
        </div>

    </div>

{% endblock %}

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Hello {{ user.email }}!

We've received a request to reset {{ user.get_username }}'s password.

If you would like to reset the password, visit the following link:

{{ protocol }}://{{ domain }}{% url 'djangobin:password_reset_confirm' uid token %}

If you did not request a password reset, please disregard this mail.

~ Djangobin

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

Password Reset Request - Djangobin

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{% extends 'djangobin/base.html' %}

{% load static %}
{% load humanize %}

{% block main %}

    <div class="row">

        <div class="col-lg-6 col-md-6 col-sm-6">
            <h4>Email Sent</h4>
            <hr>
            <p>Password reset link is sent to your email.</p>
        </div>

        <div class="col-lg-6 col-md-6 col-sm-6">
            <h4>Related Links</h4>
            <p>
                <a href="">Forgot Password?</a> <br>
                <a href="{% url  'djangobin:signup' %}">Sign Up.</a> <br>
                <a href="#">Feedback</a>
            </p>
        </div>

    </div>

{% endblock %}

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
{% extends 'djangobin/base.html' %}

{% load static %}
{% load humanize %}

{% block main %}

    <div class="row">

        <div class="col-lg-6 col-md-6 col-sm-6">

            <h4>Enter your new password.</h4>
            <hr>
            {% if validlink %}
                <form action="" method="post">
                    <table>
                        {% csrf_token %}
                        {{ form.as_table }}
                        <tr>
                            <td></td>
                            <td><button type="submit" class="btn btn-primary">Reset Password</button></td>
                        </tr>
                    </table>
                </form>
            {% else %}
                <p>This Link is no longer valid. Click <a href="{% url 'djangobin:password_reset' %}">here</a> to create a new one.</p>
            {% endif %}

        </div>

        <div class="col-lg-6 col-md-6 col-sm-6">
            <h4>Related Links</h4>
            <p>
                <a href="">Forgot Password?</a> <br>
                <a href="{% url  'djangobin:signup' %}">Sign Up.</a> <br>
                <a href="#">Feedback</a>
            </p>
        </div>

    </div>

{% endblock %}

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{% extends 'djangobin/base.html' %}

{% load static %}
{% load humanize %}

{% block main %}

    <div class="row">

        <div class="col-lg-6 col-md-6 col-sm-6">

            <h4>Password Reset Success</h4>

            <hr>

            <div class="db-form">

                <p>Your password has been reset. Click <a href="{% url  'djangobin:login' %}">here</a> to login.</p>

            </div>
        </div>

        <div class="col-lg-6 col-md-6 col-sm-6">
            <h4>Related Links</h4>
            <p>
                <a href="">Forgot Password?</a> <br>
                <a href="{% url  'djangobin:signup' %}">Sign Up.</a>
                <a href="#">Feedback</a>
            </p>
        </div>

    </div>

{% endblock %}

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
[11/May/2018 13:07:33] "GET /password-reset/ HTTP/1.1" 200 9524
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: Password Reset Request - Djangobin
From: infooveriq@gmail.com
To: noisyboy@mail.com
Date: Fri, 11 May 2018 13:14:49 -0000
Message-ID: <20180511131449.29374.83844@pc>

Hello noisyboy@mail.com!

We've received a request to reset noisyboy's password.

If you would like to reset the password, visit the following link:

http://localhost:8000/password-confirm/Mg/4w3-32c4a6bf60ae7b9dee77/

If you did not request a password reset, please disregard this mail.

~ Djangobin
-------------------------------------------------------------------------------

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#...
urlpatterns = [
    #...

    # password change URLs

    url(r'^password-change/$', auth_views.password_change,
        {'template_name': 'djangobin/password_change.html',
        'post_change_redirect': 'djangobin:password_change_done'},
        name='password_change'
        ),

    url(r'^password-change-done/$', auth_views.password_change_done,
        {'template_name': 'djangobin/password_change_done.html'},
        name='password_change_done'
        ),
    ]

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
{% extends 'djangobin/base.html' %}

{% block title %}
    Password Change - {{ block.super }}
{% endblock %}

{% block main %}

    <div class="row">

        <div class="col-lg-6 col-md-6 col-sm-6">

            <h4>Password change</h4>
            <hr>

            <form action="" method="post" class="form-horizontal" >
                {% csrf_token %}

                 <table class="table">
                    {{ form.as_table }}
                    <tr>
                        <td>&nbsp;</td>
                        <td><button type="submit" class="btn btn-primary">Submit</button></td>
                    </tr>
                </table>

            </form>

        </div>

        <div class="col-lg-6 col-md-6 col-sm-6">
            <h4>Related Links</h4>
            <p>
                <a href="{% url 'djangobin:profile' request.user.username %}">My Pastes</a> <br>
                <a href="{% url 'djangobin:user_details' %}">Account Details</a> <br>
                <a href="">Settings</a> <br>
                <a href="{% url 'djangobin:contact' %}">Feedback</a>
            </p>
        </div>

    </div>

{% endblock %}

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{% extends 'djangobin/base.html' %}

{% block title %}
    Password Change Done - {{ block.super }}
{% endblock %}

{% block main %}

    <div class="row">

        <div class="col-lg-6 col-md-6 col-sm-6">

            <h4>Password change successful</h4>
            <hr>

            <p>Your password was changed.</p>

        </div>

        <div class="col-lg-6 col-md-6 col-sm-6">
            <h4>Related Links</h4>
            <p>
                <a href="{% url 'djangobin:profile' request.user.username %}">My Pastes</a> <br>
                <a href="{% url 'djangobin:user_details' %}">Account Details</a> <br>
                <a href="">Settings</a> <br>
                <a href="{% url 'djangobin:contact' %}">Feedback</a>
            </p>
        </div>

    </div>

{% endblock %}

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: