So far we’ve built a working blog application that uses forms, but we’re missing a major piece of most web applications: user authentication. Fortunately Django provides a powerful, built-in user authentication system that makes our job much easier. In this chapter we will add signup, login, and logout pages to our project.

Complete code can be found on Github.

Login

Django provides us with a default view for a login page via LoginView. All we need to add are a project-level urlpattern for the auth system, a login template, and a small update to our settings.py file.

First update the project-level urls.py file. We’ll place our login and logout pages at the users/ URL. This is a one-line addition on the next-to-last line.

# blog_project/urls.py
from django.conf.urls import include, url
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^users/', include('django.contrib.auth.urls')),
    url(r'', include('blog_app.urls')),
]

As the LoginView documentation notes, by default Django will look within a templates folder called registration for a file called login.html for a login form. So we need to create a new directory called registration and the requisite file within it. From the command line type Control-C to quit our local server. Then enter the following:

(blog) $ mkdir templates
(blog) $ mkdir templates/registration
(blog) $ touch templates/registration/login.html

Now type the following template code for our newly-created file.

<!-- templates/registration/login.html -->
{% extends 'blog_app/base.html' %}

{% block content %}
<h2>Login</h2>
<form method="post">
  {% csrf_token %}
  {{ form.as_p }}
  <button type="submit">Login</button>
</form>
{% endblock content %}

We’re using HTML <form></form> tags and specifying the POST method since we’re sending data to the server (we’d use GET if we were requesting data, such as in a search engine form). We add {% csrf_token %} for security concerns, namely to prevent a XSS Attack. The form’s contents are outputted between paragraph tags thanks to {{ form.as_p }} and then we add a “submit” button.

The final step is to update our settings.py file. We need to tell Django where to find our newly created templates folder. Update the DIRS setting within TEMPLATES as follows. This is a one-line change.

# settings.py
TEMPLATES = [
    {
        ...
        'DIRS': ['templates'],
        ...

    },
]

Finally we need to specify where to redirect the user upon a successful login. We can set this with the LOGIN_REDIRECT_URL setting. At the bottom of the settings.py file add the following:

# settings.py
LOGIN_REDIRECT_URL = '/'

We’re actually done at this point! If you now start up the Django server again with ./manage.py runserver and navigate to our login page at http://127.0.0.1:8000/users/login/ you’ll see the following.

Login page

Upon entering the login info for our superuser account, we are redirected to the homepage. Notice that we didn’t add any view logic or create a database model because the Django auth system provided both for us automatically. Thanks Django!

Updated homepage

Let’s update our base.html template so we display a message to users whether they are logged in or not. We can use the is_authenticated attribute for this.

For now, we can simply place this code in a prominent position. Later on we can style it more appropriately. Update the base.html file with new code starting beneath the closing </header> tag.

<!-- blog_app/templates/blog_app/base.html -->
...
</header>
  {% if user.is_authenticated %}
    <p>Hi {{ user.username }}!</p>
  {% else %}
    <p>You are not logged in.</p>
    <a href="{% url 'login' %}">login</a>
  {% endif %}
  {% block content %}
  {% endblock content %}

If the user is logged in we say hello to them by name, if not we provide a link to our newly created login page.

Homepage logged in

It worked! My superuser name is wsv so that’s what I see on the page.

We added template page logic for logged out users but…how do we log out now? We could go into the Admin panel and it manually, but there’s a better way. Let’s add a logout link instead that redirects to the homepage. Thanks to the Django auth system, this is dead-simple to achieve.

In our base.html file add a one-line link for logging out.

<!-- templates/base.html-->
...
{% if user.is_authenticated %}
  <p>Hi {{ user.username }}!</p>
  <p><a href="{% url 'logout' %}">logout</a></p>
{% else %}
...

That’s all we need to do as the necessary view is provided to us by the Django auth app. We do need to specify where to redirect a user upon logout though.

Update settings.py to provide a redirect link which is called, appropriately, LOGOUT_REDIRECT_URL. We can add it right next to our login redirect so the bottom of the file should look as follows:

# blog_project/settings.py
LOGIN_REDIRECT_URL = '/'
LOGOUT_REDIRECT_URL = '/'

If you refresh the homepage you’ll see it now has a “logout” link for logged in users.

Homepage logout link

And clicking it takes you back to the homepage with a “login” link.

Homepage logged out

Go ahead and try logging in and out several times with your user account.

Signup

We need to write our own view for a signup page to register new users, but Django does provide us with a form class, UserCreationForm, to make things easier. By default it comes with three fields: username, password1, and password2.

There are many ways to organize your code and url structure for a robust user authentication system. Here we will create a dedicated new app, register_app, for our signup page.

(blog) $ ./manage.py startapp register_app

Add the new app to the INSTALLED_APPS setting in our settings.py file.

# blog_project/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    # my apps
    'blog_app',
    'register_app',
]

Next add a project-level view pointing to this new app.

# blog_project/urls.py
from django.conf.urls import include, url
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^users/', include('django.contrib.auth.urls')),
    url(r'^signup/', include('register_app.urls')),
    url(r'', include('blog_app.urls')),
]

On the next-to-last line we’ve indicated the url pattern will be signup/ and simply pointed it to our app-level urls.

Let’s go ahead and create our register_app/urls.py file.

(blog) $ touch register_app/urls.py

And add the following code:

# register_app/urls.py
from django.conf.urls import url

from .views import signup


urlpatterns = [
    url(r'^$', signup),
]

We are simply importing a view called “signup” (which we haven’t made yet), and then indicating the signup page will be at the homepage of our app, which we just set in the project-level urls file would be at /signup/.

Now for the view which is written from scratch, albeit using Django’s UserCreationForm.

# register_app/views.py
from django.contrib.auth import login, authenticate
from django.contrib.auth.forms import UserCreationForm
from django.shortcuts import render, redirect

def signup(request):
    if request.method == 'POST':
        form = UserCreationForm(request.POST)
        if form.is_valid():
            form.save()
            username = form.cleaned_data.get('username')
            raw_password = form.cleaned_data.get('password1')
            user = authenticate(username=username, password=raw_password)
            login(request, user)
            return redirect('post-list')
    else:
        form = UserCreationForm()
    return render(request, 'signup.html', {'form': form})

There’s a bit going on here so let’s review everything. At the top we import both the login and authenticate modules from the auth app, as well as UserCreationForm. We also add render and redirect which we’ll use to display the page and redirect the user to a new page upon successful registration.

In the view itself signup we specify our method, POST, the form, and then perform basic form validation. Finally upon success we redirect to our homepage, which was set in the other app, blog_app, and has the URL name of post-list.

The final step is to create a template for our signup form. We have two options for where to place it. We could create a nested directory structure within this app and make a new templates folder, within it a register_app folder, and then place the file. This is what we did in blog_app.

However it is somewhat cleaner to just put our template in the project-level templates folder we already created. We’ll take this approach. There are few right and wrong answers when it comes to organizing your templates as long as you’re consistent.

So within this project-level templates folder create a new file signup.html.

(blog) $ touch templates/signup.html

Add then populate it with the code below.

<!-- templates/signup.html -->
{% extends 'blog_app/base.html' %}

{% block content %}
  <h2>Sign up</h2>
  <form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">Sign up</button>
  </form>
{% endblock %}

This format is very similar to what we’ve done before. We extend our base template at the top, place our logic between <form></form> tags, use the csrf_token for security, display the form’s content in paragraph tags with form.as_p, and include a submit button.

We’re now done! To test it out, navigate to our newly created page http://127.0.0.1:8000/signup/.

Django signup page

Notice there is a lot of extra text that Django includes by default. We can customize this using something like the built-in messages framework but for now try out the form.

I’ve created a new user called “william” and upon submission was redirected to the homepage with our personalized “Hi !" greeting.

Homepage for user wsvincent

Conclusion

With very little code, the Django framework has allowed us to create a login, logout, and signup user authentication flow. And thankfully it has taken care of the many security gotchas that can crop up if you try to create your own user authentication flow from scratch.

A more robust implementation would include user notifications via the messages framework, a reset password form, social logins, and more. In practice, many Django developers rely on a third-party plugin django-allauth to help implment such features.

Continue on to the Conclusion for an overview of what we’ve learned and tips for progressing in your Django journey.




Sign up for the Django For Beginners newsletter for updates when new chapters are available and special discounts for the print edition of the book.