This tutorial is going to be all about Django’s authentication system. We are going to implement the whole thing:
registration, login, logout, password reset, and password change.
You are also going to get a brief introduction on how to protect some views from non-authorized users and how to
access the information of the logged in user.
In the next section, you will find a few wireframes of authentication-related pages that we are going to implement
in this tutorial. After that, you will find an initial setup of a new Django app. So far we have been working on
an app named boards. But all the authentication related stuff can live in a different app, so to achieve a better
organization of the code.
Wireframes
We have to update the wireframes of the application. First, we are going to add new options for the top menu. If
the user is not authenticated, we should have two buttons: sign up and log in.
Figure 1: Top menu for not authenticated users.
If the user is authenticated, we should instead display their names along with a dropdown menu with three options:
my account, change password and log out.
Figure 2: Top menu for authenticated users.
On the log in page, we need a form with username and password, a button with the main action (log in) and two
alternative paths: sign up page and password reset page.
Figure 3: Log in page
On the sign up page, we should have a form with four fields: username, email address, password, and
password confirmation. The user should also be able to reach the log in page.
Figure 4: Sign up page
On the password reset page, we will have a form with just the email address.
Figure 5: Password reset
Then, after clicking on a special token link, the user will be redirected to a page where they can set a new password:
Figure 6: Change password
Initial Setup
To manage all this information, we can break it down in a different app. In the project root, in the same page where
the manage.py script is, run the following command to start a new app:
The project structure should like this right now:
Next step, include the accounts app to the INSTALLED_APPS in the settings.py file:
From now on, we will be working on the accounts app.
Sign Up
Let’s start by creating the sign up view. First thing, create a new route in the urls.py file:
myproject/urls.py
Notice how we are importing the views module from the accounts app in a different way:
We are giving an alias because otherwise, it would clash with the boards’ views. We can improve the urls.py
design later on. But for now, let’s focus on the authentication features.
Now edit the views.py inside the accounts app and create a new view named signup:
accounts/views.py
Create the new template, named signup.html:
templates/signup.html
Open the URL http://127.0.0.1:8000/signup/ in the browser, check if everything is working:
Time to write some tests:
accounts/tests.py
Testing the status code (200 = success) and if the URL /signup/ is returning the correct view function.
For the authentication views (sign up, log in, password reset, etc.) we won’t use the top bar or the breadcrumb.
We can still use the base.html template. It just needs some tweaks:
templates/base.html
I marked with comments the new bits in the base.html template. The block
{%blockstylesheet%}{%endblock%} will be used to add extra CSS, specific to some pages.
The block {%blockbody%} is wrapping the whole HTML document. We can use it to have an
empty document taking advantage of the head of the base.html. Notice how we named the end block
{%endblockbody%}. In cases like this, it’s a good practice to name the closing tag, so it’s
easier to identify where it ends.
Now on the signup.html template, instead of using the {%blockcontent%}, we can use the
{%blockbody%}.
templates/signup.html
Time to create the sign up form. Django has a built-in form named UserCreationForm. Let’s use it:
accounts/views.py
templates/signup.html
Looking a little bit messy, right? We can use our form.html template to make it look better:
templates/signup.html
Uh, almost there. Currently, our form.html partial template is displaying some raw HTML. It’s a security feature.
By default Django treats all strings as unsafe, escaping all the special characters that may cause trouble. But in this
case, we can trust it.
templates/includes/form.html
Basically, in the previous template we added the option safe to the field.help_text: {{field.help_text|safe}}.
Save the form.html file, and check the sign up page again:
Now let’s implement the business logic in the signup view:
accounts/views.py
A basic form processing with a small detail: the login function (renamed to auth_login to avoid clashing with
the built-in login view).
Note:
I renamed the login function to auth_login, but later I realized that Django 1.11
has a class-based view for the login view, LoginView, so there was no risk of clashing the names.
On the older versions there was a auth.login and auth.view.login, which used to
cause some confusion, because one was the function that logs the user in, and the other was the view.
Long story short: you can import it just as login if you want, it will not cause any problem.
If the form is valid, a User instance is created with the user = form.save(). The created user is then passed
as an argument to the auth_login function, manually authenticating the user. After that, the view redirects the
user to the homepage, keeping the flow of the application.
Let’s try it. First, submit some invalid data. Either an empty form, non-matching fields, or an existing username:
Now fill the form and submit it, check if the user is created and redirected to the homepage:
Referencing the Authenticated User in the Template
How can we know if it worked? Well, we can edit the base.html template to add the name of the user on the top bar:
templates/base.html
Testing the Sign Up View
Let’s now improve our test cases:
accounts/tests.py
We changed a little bit the SignUpTests class. Defined a setUp method, moved the response object to there. Then
now we are also testing if there are a form and the CSRF token in the response.
Now we are going to test a successful sign up. This time, let’s create a new class to organize better the tests:
accounts/tests.py
Run the tests.
Using a similar strategy, now let’s create a new class for sign up tests when the data is invalid:
Adding the Email Field to the Form
Everything is working, but… The email address field is missing. Well, the UserCreationForm does not provide
an email field. But we can extend it.
Create a file named forms.py inside the accounts folder:
accounts/forms.py
Now, instead of using the UserCreationForm in our views.py, let’s import the new form, SignUpForm, and use
it instead:
accounts/views.py
Just with this small change, everything is already working:
Remember to change the test case to use the SignUpForm instead of UserCreationForm:
The previous test case would still pass because since SignUpForm extends the UserCreationForm, it is an
instance of UserCreationForm.
Now let’s think about what happened for a moment. We added a new form field:
And it automatically reflected in the HTML template. It’s good, right? Well, depends. What if in the future, a new
developer wanted to re-use the SignUpForm for something else, and add some extra fields to it. Then those new
fields would also show up in the signup.html, which may not be the desired behavior. This change could pass
unnoticed, and we don’t want any surprises.
So let’s create a new test, that verifies the HTML inputs in the template:
accounts/tests.py
Improving the Tests Layout
Alright, so we are testing the inputs and everything, but we still have to test the form itself. Instead of just keep
adding tests to the accounts/tests.py file, let’s improve the project design a little bit.
Create a new folder named tests within the accounts folder. Then, inside the tests folder, create an empty
file named __init__.py.
Now, move the tests.py file to inside the tests folder, and rename it to test_view_signup.py.
The final result should be the following:
Note that since we are using relative import within the context of the apps, we need to fix the imports in the new
test_view_signup.py:
accounts/tests/test_view_signup.py
We are using relative imports inside the app modules so we can have the freedom to rename the Django app later on,
without having to fix all the absolute imports.
Now let’s create a new test file, to test the SignUpForm. Add a new test file named test_form_signup.py:
accounts/tests/test_form_signup.py
It looks very strict, right? For example, if in the future we have to change the SignUpForm, to include the user’s
first and last name, we will probably end up having to fix a few test cases, even if we didn’t break anything.
Those alerts are useful because they help to bring awareness, especially for newcomers touching the code for the first
time. It helps them code with confidence.
Improving the Sign Up Template
Let’s work a little bit on it. Here we can use Bootstrap 4 cards components to make it look good.
Go to https://www.toptal.com/designers/subtlepatterns/
and find a nice background pattern to use as a background of the accounts pages. Download it, create a new folder
named img inside the static folder, and place the image there.
Then after that, create a new CSS file named accounts.css in the static/css. The result should be the
following:
Now edit the accounts.css file:
static/css/accounts.css
In the signup.html template, we can change it to make use of the new CSS and also take the Bootstrap 4 card
components into use:
templates/signup.html
With that, this should be our sign up page right now:
Logout
To keep a natural flow in the implementation, let’s add the log out view. First, edit the urls.py to add a new
route:
myproject/urls.py
We imported the views from the Django’s contrib module. We renamed it to auth_views to avoid clashing
with the boards.views. Notice that this view is a little bit different: LogoutView.as_view(). It’s a Django’s
class-based view. So far we have only implemented views as Python functions. The class-based views provide a more
flexible way to extend and reuse views. We will discuss more that subject later on.
Open the settings.py file and add the LOGOUT_REDIRECT_URL variable to the bottom of the file:
myproject/settings.py
Here we are passing the name of the URL pattern we want to redirect the user after the log out.
After that, it’s already done. Just access the URL 127.0.0.1:8000/logout/ and you will be logged out. But hold on
a second. Before you log out, let’s create the dropdown menu for logged in users.
Displaying Menu For Authenticated Users
Now we will need to do some tweaks in our base.html template. We have to add a dropdown menu with the logout link.
The Bootstrap 4 dropdown component needs jQuery to work.
First, go to jquery.com/download/ and download the
compressed, production jQuery 3.2.1 version.
Inside the static folder, create a new folder named js. Copy the jquery-3.2.1.min.js file to there.
Bootstrap 4 also needs a library called Popper to work. Go to popper.js.org
and download the latest version.
Inside the popper.js-1.12.5 folder, go to dist/umd and copy the file popper.min.js to our js folder.
Pay attention here; Bootstrap 4 will only work with the umd/popper.min.js. So make sure you are copying the right
file.
If you no longer have all the Bootstrap 4 files, download it again from getbootstrap.com.
Similarly, copy the bootstrap.min.js file to our js folder as well.
The final result should be:
In the bottom of the base.html file, add the scripts after the {%endblockbody%}:
templates/base.html
If you found the instructions confusing, just download the files using the direct links below:
It’s working. But the dropdown is showing regardless of the user being logged in or not. The difference is that now the
username is empty, and we can only see an arrow.
We can improve it a little bit:
Now we are telling Django to show the dropdown menu if the user is logged in, and if not, show the log in and sign up
buttons:
Login
First thing, add a new URL route:
myproject/urls.py
Inside the as_view() we can pass some extra parameters, so to override the defaults. In this case, we are instructing
the LoginView to look for a template at login.html.
Edit the settings.py and add the following configuration:
myproject/settings.py
This configuration is telling Django where to redirect the user after a successful login.
Finally, add the login URL to the base.html template:
templates/base.html
We can create a template similar to the sign up page. Create a new file named login.html:
templates/login.html
And we are repeating HTML templates. Let’s refactor it.
Create a new template named base_accounts.html:
templates/base_accounts.html
Now use it on both signup.html and login.html:
templates/login.html
We still don’t have the password reset URL, so let’s leave it as # for now.
templates/signup.html
Notice that we added the log in URL: <a href="{% url 'login' %}">Log in</a>.
Log In Non Field Errors
If we submit the log in form empty, we get some nice error messages:
But if we submit an username that doesn’t exist or an invalid password, right now that’s what’s going to happen:
A little bit misleading. The fields are showing green, suggesting they are okay. Also, there’s no message saying
anything.
That’s because forms have a special type of error, which is called non-field errors. It’s a collection of errors
that are not related to a specific field. Let’s refactor the form.html partial template to display those errors as
well:
templates/includes/form.html
The {%ifforloop.last%} is just a minor thing. Because the p tag has a margin-bottom.
And a form may have several non-field errors. For each non-field error, we render a p tag with the error. Then
I’m checking if it’s the last error to render. If so, we add a Bootstrap 4 CSS class mb-0 which stands for
“margin bottom = 0”. Then the alert doesn’t look weird, with some extra space. Again, just a very minor detail. I did
that just to keep the consistency of the spacing.
We still have to deal with the password field though. The thing is, Django never returned the data of password fields
to the client. So, instead of trying to do something smart, let’s just ignore the is-valid and is-invalid CSS
classes in some cases. But our form template already looks complicated. We can move some of the code to a
template tag.
Creating Custom Template Tags
Inside the boards app, create a new folder named templatetags. Then inside this folder, create two empty files
named __init__.py and form_tags.py.
The structure should be the following:
In the form_tags.py file, let’s create two template tags:
boards/templatetags/form_tags.py
Those are template filters. They work like this:
First, we load it in a template as we do with the widget_tweaks or static template tags. Note that after
creating this file, you will have to manually stop the development server and start it again so Django can identify
the new template tags.
Then after that, we can use them in a template:
Will return:
Or in case of the input_class:
Now update the form.html to use the new template tags:
templates/includes/form.html
Much better, right? Reduced the complexity of the template. It looks cleaner now. And it also solved the problem with
the password field displaying a green border:
Testing the Template Tags
First, let’s just organize the boards’ tests a little bit. Like we did with the accounts app, create a new folder
named tests, add a __init__.py, copy the tests.py and rename it to just test_views.py for now.
Add a new empty file named test_templatetags.py.
Fix the test_views.py imports:
boards/tests/test_views.py
Execute the tests just to make sure everything is working.
boards/tests/test_templatetags.py
We created a form class to be used in the tests then added test cases covering the possible scenarios in the two
template tags.
Password Reset
The password reset process involves some nasty URL patterns. But as we discussed in the previous tutorial, we don’t
need to be an expert in regular expressions. It’s just a matter of knowing the common ones.
Another important thing before we start is that, for the password reset process, we need to send emails. It’s a little
bit complicated in the beginning because we need an external service. For now, we won’t be configuring a production
quality email service. In fact, during the development phase, we can use Django’s debug tools to check if the emails
are being sent correctly.
Console Email Backend
The idea is during the development of the project, instead of sending real emails, we just log them. There are
two options: writing all emails in a text file or simply displaying them in the console. I find the latter option more
convenient because we are already using a console to run the development server and the setup is a bit easier.
Edit the settings.py module and add the EMAIL_BACKEND variable to the end of the file:
myproject/settings.py
Configuring the Routes
The password reset process requires four views:
A page with a form to start the reset process;
A success page saying the process initiated, instructing the user to check their spam folders, etc.;
A page to check the token sent via email;
A page to tell the user if the reset was successful or not.
The views are built-in, we don’t need to implement anything. All we need to do is add the routes to the urls.py
and create the templates.
The template_name parameter in the password reset views are optional. But I thought it would be a good idea to
re-define it, so the link between the view and the template be more obvious than just using the defaults.
Inside the templates folder, the following template files:
password_reset.html
password_reset_email.html: this template is the body of the email message sent to the user
password_reset_subject.txt: this template is the subject line of the email, it should be a single line file
password_reset_done.html
password_reset_confirm.html
password_reset_complete.html
Before we start implementing the templates, let’s prepare a new test file.
We can add just some basic tests because those views and forms are already tested in the Django code. We are going to
test just the specifics of our application.
Create a new test file named test_view_password_reset.py inside the accounts/tests folder.
Password Reset View
templates/password_reset.html
accounts/tests/test_view_password_reset.py
templates/password_reset_subject.txt
templates/password_reset_email.html
We can create a specific file to test the email message. Create a new file named test_mail_password_reset.py
inside the accounts/tests folder:
accounts/tests/test_mail_password_reset.py
This test case grabs the email sent by the application, and examine the subject line, the body contents, and to who
was the email sent to.
Password Reset Done View
templates/password_reset_done.html
accounts/tests/test_view_password_reset.py
Password Reset Confirm View
templates/password_reset_confirm.html
This page can only be accessed with the link sent in the email. It looks like this: http://127.0.0.1:8000/reset/Mw/4po-2b5f2d47c19966e294a1/
During the development phase, grab this link from the email in the console.
This view is meant to be used by logged in users that want to change their password. Usually, those forms are composed
of three fields: old password, new password, and new password confirmation.
Those views only works for logged in users. They make use of a view decorator named @login_required. This decorator
prevents non-authorized users to access this page. If the user is not logged in, Django will redirect them to the login
page.
Now we have to define what is the login URL of our application in the settings.py:
Regarding the password change view, we can implement similar test cases as we have already been doing so far. Create
a new test file named test_view_password_change.py.
I will list below new types of tests. You can check all the tests I wrote for the password change view clicking in the
view complete file contents link next to the code snippet. Most of the tests are similar to what we have been
doing so far. I moved to an external file to avoid being too repetitive.
Here we defined a new class named PasswordChangeTestCase. It does a basic setup, creating a user and making a
POST request to the password_change view. In the next set of test cases, we are going to use this class
instead of the TestCase class and test a successful request and an invalid request:
The refresh_from_db() method make sure we have the latest state of the data. It forces Django to query the database
again to update the data. We have to do it because the change_password view update the password in the database.
So to test if the password really changed, we have to grab the latest data from the database.
Conclusions
Authentication is a very common use case for most Django applications. In this tutorial, we implemented all the
important views: sign up, log in, log out, password reset, and change password. Now that we have a way to create users
and authenticate them, we will be able to proceed with the development of the other views of our application.
We still have to improve lots of things regarding the code design: the templates folder is starting to get messy with
too many files. The boards app tests are still disorganized. Also, we have to start refactoring the new topic
view, because now we can retrieve the logged in user. We will get to that part soon.
I hope you enjoyed the forth part of this tutorial series! The fifth part is coming out next week, on Oct 2, 2017.
If you would like to get notified when the fifth part is out, you can subscribe to our mailing list.
The source code of the project is available on GitHub. The current state of the project can be found under the release
tag v0.4-lw. The link below will take you to the right place: