User Login with Django

by Steve Carey, last updated 10/12/13


In this tutorial we will create a Login form. We will assume you already have a Django project started with the initial files set up. You can do that by following the simplesite tutorial to create a simple two page website using the Django framework.

Since Login is generally a site-wide function we will not create a separate app for it. Rather we will use the files in the inner site folder where the settings.py file is. Django's out-of-the-box login process is good enough for most cases. We added other versions where you create your own views and forms for practice on those core Django files.

Prerequisites: This tutorial assumes you are familiar with the basics of HTML and CSS; a basic command line interface (e.g., Windows Command Prompt, OSX Terminal, Linux Bash); the basics of the Python Programming Language; and have been doing the Django Apps tutorials on this site in order. At a minimum, you should have the following already installed: A text editor, Python version 2.7 or above, Django, and pip.

There are multiple versions of this tutorial. Click on the tab to see each. It is recommended that you go through each in order.
  • Out of the Box
  • Make Your Own Views
  • Use a Form Class
  • Create a page restricted to Loggedin users

Django's Out-of-the-Box Login

Overview

  • Django's out-of-the-box login process is part of their Auth module. The relevant files (views.py and forms.py) are located in your python or virtualenv directory at /Lib/site-packages/django/contrib/auth/. It does not include templates so we'll make our own.
  • Here is how the default solution works:

    • You include a Login link in an appropriate place on your site.
    • The user clicks the link to login, the page opens with a login form asking for username and password.
    • They enter those. If they don't match they will get an error message.
    • If they match they are redirected to a success page, and a Session is started as a loggedin user.

Views and URLs

  • Open the views.py file in the lower site folder (where the settings.py file is). If there isn't one then create and save it.
  • At the top of the file insert the following import:
    from django.shortcuts import render_to_response
    
  • Below that you only need to add one function rendering the loggedin page. The other functions (login and logout) are in the views.py file in the Django Auth folder.
    def loggedin(request):
        return render_to_response('registration/loggedin.html')
    
  • Optionally, if you want to show their username when they login then call their username in the view. Change the loggedin function to:
    def loggedin(request):
        return render_to_response('registration/loggedin.html',
                                  {'username': request.user.username})
    
  • Open the urls.py file in the site folder (same folder as settings.py). Below urlpatterns = patterns('', insert the following lines.
        # Auth-related URLs:
        url(r'^accounts/login/$', 'django.contrib.auth.views.login', name='login'),
        url(r'^accounts/logout/$', 'django.contrib.auth.views.logout', name='logout'),
        url(r'^accounts/loggedin/$', 'simplesite.views.loggedin', name='loggedin'),
    
    With simplesite being the name of the folder that holds the views.py file that you are calling.
  • Open the settings.py file and at the bottom insert LOGIN_REDIRECT_URL = '/accounts/loggedin/'. Django's default is to redirect to /accounts/profile when you log in, which is fine if you have an profile page at that url. If not you need to change your settings default for the Login redirect url to the one holding your loggedin.html page.

Templates

  • We will assume your site already has a templates directory and a base.html file with the navigation bar. Open the base.html file and in the nav element add a navigation menu link to the login page <a href="/accounts/login">login</a>
  • Add a logout link too <a href="/accounts/logout">logout</a>
  • Create a directory called registration inside the templates folder. If you do this through the command line, type mkdir registration
  • Create a file called login.html, save it to the templates/registration folder, and populate it with the following:
    {% extends "base.html" %}
    {% block title %}Log In{% endblock %}
    {% block content %}
    
    <form method="post" action="{% url 'django.contrib.auth.views.login' %}">
    {% csrf_token %}
    <table>
      {{ form.as_table }}
    </table>
    
    <input type="submit" value="login" />
    </form>
    
    {% endblock %}
    
  • {{ form.as_table }} uses the Django Forms module to create the form. You can create an unformatted form by using {{ form }} without the HTML table tags, or have each field put inside paragraph tags with {{ form.as_p }}, or as an unordered list {{ form.as_ul }}
  • Optionally, you can also lay out your own form structure and use the form field tags as follows:
  • {% extends "base.html" %}
    {% block title %}Log In{% endblock %}
    {% block content %}
    
    <form method="post" action="{% url 'django.contrib.auth.views.login' %}">
    {% csrf_token %}
    
    {% if form.errors %}
    <p>Your Username or Password were not entered correctly. Please try again.</p>
    {% endif %}
    
    <table>
    <tr>
        <td>{{ form.username.label_tag }}</td>
        <td>{{ form.username }}</td>
        <td>{{ form.username.errors }}</td> 
    </tr>
    <tr>
        <td>{{ form.password.label_tag }}</td>
        <td>{{ form.password }}</td>
        <td>{{ form.password.errors }}</td>
    </tr>
    </table>
    
    <input type="submit" value="login" />
    </form>
    
    {% endblock %}
    
  • Create a file called loggedin.html, save it to the templates/registration folder, and populate it with the following:
    {% extends "base.html" %}
    {% block title %}Logged In{% endblock %}
    {% block content %}
    
      <h2>You are logged in</h2>
    
    {% endblock %}
    
  • If you want to display the username, you would make the adjustment to the view discussed in the views section. Then change the loggedin.html template to the below (change the wording as you see fit):
    {% extends "base.html" %}
    {% block title %}Logged In{% endblock %}
    {% block content %}
    
    <h1>Welcome {{username}}</h1>
    <p>Thank you for logging in.</p>
    <p><a href="/accounts/logout/">Logout</a></p>
    
    {% endblock %}
    
  • Create a file called logged_out.html, save it to the templates/registration folder and populate it with the following:
    {% extends "base.html" %}
    {% block title %}Logged Out{% endblock %}
    {% block content %}
    
      <h2>Logged out!</h2>
      <p><a href="/accounts/login/">Log back in</a></p>
    
    {% endblock %}
    

Testing

  • Now run the virtual server and test that the Login form is working.
    • Open the command line interface.
    • from the upper site directory (the folder with the manage.py file in it) enter python manage.py runserver
    • Go to the browser and put in your local ip address and port 8000 as url: http://127.0.0.1:8000/
    • Click on the navigation bar's "login" link. It should take you to the login page.
    • Fill out the form both incorrectly, to see the error messages, and correctly.
    • When filled out correctly you should be directed to the loggedin page.
    • Logout. If it works, you're done.

Make Your Own Views

Overview

  • The out-of-the-box process will generally meet your needs. In this tutorial we will use our own views mainly just to help get familiar with making views (i.e., functions). In this version of the Login tutorial, we will make our own view and our own form. We will still use Django's authentication functions.
  • This version includes a walk-through of the code. It's fairly elementary so if you understand all the code you can skip it. We also combined some of the functions at the end to show different ways of achieving the same end.

Views and URLs

  • Open the views.py file lower site folder (where the settings.py file is). If one doesn't exist then create it.
  • At the top of the file insert the following imports:
    from django.shortcuts import render_to_response
    from django.http import HttpResponseRedirect
    from django.contrib import auth
    from django.core.context_processors import csrf
    
  • Below that add the following functions:
    def login(request):
        token = {}
        token.update(csrf(request))    
        return render_to_response('registration/login.html', token)
        
    def process_login(request):
        username = request.POST.get('username', '')
        password = request.POST.get('password', '')
        user = auth.authenticate(username=username, password=password)
        
        if user is not None:
            auth.login(request, user)
            return HttpResponseRedirect('/accounts/loggedin')
        else:
            return HttpResponseRedirect('/accounts/login_error')
    
    def loggedin(request):
        return render_to_response('registration/loggedin.html', 
                                  {'username': request.user.username})
    
    def login_error(request):
        return render_to_response('registration/login_error.html')
    
    def logout(request):
        auth.logout(request)
        return render_to_response('registration/logged_out.html')
    
  • Open the urls.py file in the site folder (same folder as settings.py). Below urlpatterns = patterns('', insert the following lines.
        # Auth-related URLs:
        url(r'^accounts/login/$', 'simplesite.views.login', name='login'),
        url(r'^accounts/process_login/$', 'simplesite.views.process_login', name='process_login'),
        url(r'^accounts/loggedin/$', 'simplesite.views.loggedin', name='loggedin'),
        url(r'^accounts/login_error/$', 'simplesite.views.login_error', name='login_error'),
        url(r'^accounts/logout/$', 'simplesite.views.logout', name='logout'),
    

Templates

  • We will assume your site already has a templates directory and a base.html file with the navigation bar. Open the base.html file and in the nav element add a navigation menu link to the login page <a href="/accounts/login">login</a>
  • Add a logout link too <a href="/accounts/logout">logout</a>
  • Create a directory called registration inside the templates folder mkdir registration
  • Create a file called login.html and save it to the templates/registration folder.
  • Populate the login.html template with the following:
    {% extends "base.html" %}
    {% block title %}Log In{% endblock %}
    {% block content %}
    
    <form method="post" action="/accounts/process_login/">
    {% csrf_token %}
    <table>
    <tr>
        <td><label for="username">User name:</label></td>
        <td><input type="text" name="username" value="" id="username"></td>
    </tr>
    <tr>
        <td><label for="password">Password:</label></td>
        <td><input type="password" name="password" value="" id="password"></td>
    </tr>
    </table>
    
    <input type="submit" value="login" />
    </form>
    
    {% endblock %}
    
  • Create a file called loggedin.html, save it to the templates/registration folder, and populate it with the following:
    {% extends "base.html" %}
    {% block title %}Logged In{% endblock %}
    {% block content %}
    
    <h1>Welcome {{username}}</h1>
    <p>Thank you for logging in.</p>
    <p><a href="/accounts/logout/">Logout</a></p>
    
    {% endblock %}
    
  • Create a file called login_error.html, save it to the templates folder, and populate it with the following:
    {% extends "base.html" %}
    {% block title %}Loggin Error{% endblock %}
    {% block content %}
    
      <h4>Your username or password didn't match.</h4>
      <p>Please try again <a href="/accounts/login/">Login Page</a></p>
    
    {% endblock %}
    
  • Create a file called logged_out.html, save it to the templates/registration folder and populate it with the following:
    {% extends "base.html" %}
    {% block title %}Logged Out{% endblock %}
    {% block content %}
    
      <h2>Logged out!</h2>
      <p><a href="/accounts/login/">Log back in</a></p>
    
    {% endblock %}
    

Testing

  • Now run the virtual server and test that the Login form is working.
    • Open the command line interface.
    • from the upper site directory (the folder with the manage.py file in it) enter python manage.py runserver
    • Go to the browser and put in your local ip address and port 8000 as url: http://127.0.0.1:8000/
    • Click on the navigation bar's "login" link. It should take you to the login page.
    • Fill out the form both incorrectly, to see the error messages, and correctly.
    • When filled out correctly you should be directed to the loggedin page.
    • Logout. If it works, you're done.

Walk Through the Code

  • If you understand the code you can skip this part. If not then follow the flow, especially if you are a new developer.
  • The process starts with links on the base.html page to login and logout <a href="/accounts/login">login</a>, <a href="/accounts/logout">logout</a>. Use relative links.
URLs:
  • The urls.py file dispatches the URLs to functions and classes in the views.py file. It is preloaded by Django in the lower site folder.
  • At the top of the file are the imports from django.conf.urls import patterns, include, url. This pulls in the patterns, include, and url functions from the file /Lib/site-packages/django/conf/urls/__init__.py.
  • For each URL (e.g., accounts/login) you call the URL function. url(r'^accounts/login/$', 'simplesite.views.login', name='login'),. This function pairs URLs with Views.
    • The first argument r'^accounts/login/$', is a regular expression. It looks for a URL that contains /accounts/login/. ^ means it begins with, $ means ends with, so it can't have anything before accounts other then the site's URL (the / is automatically added by Django). And it can't have anything after login/. The 'r' in front tells Python that the string is “raw” and nothing in the string should be escaped. It is optional but recommended.
    • You can use whatever URL names you want. "Accounts" is the default used by Django but if you like "user" better than "accounts" then use that. Or leave the first part out and just use r'^login/$',. Change the link on the base.html page accordingly.
    • The second argument 'simplesite.views.login', is the view. It uses dot notation with the path to the login function (simplesite folder -> views.py file -> login function).
    • The name argument is optional. It defaults to none.
  • The patterns() function puts the url/view pairs in a list, which is called by the urlpatterns variable. When the user clicks the URL link, Python searches through this list of URLs until it finds a match, then goes to the view.py file to the function or class to take some action.
Login View
  • Clicking on the Login link routes you to the login function in the views.py file.
    def login(request):
        token = {}
        token.update(csrf(request))    
        return render_to_response('registration/login.html', token)
    
  • This function first creates an empty variable named token (you can name it anything, doesn't have to be called token). It then requests a token number with the csrf(request) function. The csrf (cross site request forgery) token is a security measure. It creates and submits a token with your form submission.
  • You must insert from django.core.context_processors import csrf at the top of the views.py file in order to call the csrf function.
  • The render_to_response function loads the login.html template. It is imported from the shortcuts module from django.shortcuts import render_to_response. You can see the function in the file /Lib/site-packages/django/shortcuts/__init__.py.
Login Template
  • The Login template contains the Login form. The user must enter their username and password.
  • To include the csrf token with the form submission, you must place the code {% csrf_token %} between the HTML form tags.
  • The form uses the standard post method and sends the form, without any validation at this point, to the URL /accounts/process_login/.
Process_login View
  • The /accounts/process_login/ is dispatched through the url function url(r'^accounts/process_login/$', 'simplesite.views.process_login', name='process_login'),to the process_login function in the view.py file.
  • Variables are created for username and password that get the submitted username and password from the post data.
  • A variable called user is created by putting the username and password into the authenticate function. The authenticate function is part of the Django Auth module. It checks if the username and password match the database. If it does it returns the user object and stores it in the variable.
  • The conditional if statement checks to see that the user variable is not empty. If it's not empty (i.e., it has a user object) then the login function creates a session for the user. As long as the browser stays on the site, the session will store the user object temporarily on your server. When they close their browser the server will delete the session.
  • Then the HTTPResponseRedirect function routes you to the /accounts/loggedin url. To use this function you must import it at the top of the views.py file from django.http import HttpResponseRedirect.
  • If the username and password did not match the database then the else conditional would route them to the login_error page.
Logged In
  • If the usename and password is authenticated the urls.py file will route you to the loggedin.html file.
  • The loggedin function includes an optional argument {'username': request.user.username} which passes the variable "username" containing the username to the loggedin template.
  • The loggedin.html template includes the username variable {{username}} in it's welcome message.

Combine the login and process_login views and urls.

  • You can combine the login and process_login views by including a conditional if statement as follows:
    def login(request):
        if request.method == "POST":
            username = request.POST.get('username', '')
            password = request.POST.get('password', '')
            user = auth.authenticate(username=username, password=password)
    
            if user is not None:
                auth.login(request, user)
                return HttpResponseRedirect('/accounts/loggedin')
            else:
                return HttpResponseRedirect('/accounts/login_error')
    
        else:
            token = {}
            token.update(csrf(request))
            return render_to_response('registration/login.html', token)
    
  • The login function adds an if statement that tests whether a Post method was used (i.e., whether the form was submitted). If the users clicked the login link then they haven't even seen the form yet, so the result would be false, and the function would skip down and execute the else: statement. That would render the login.html page and assign it a csrf token.
  • If the post method was used (i.e., the form was submitted) then the username and password would be authenticated, and rerouted to either a loggedin or login_error page.
  • You have to make two more changes. In the Login.html template change the action parameter of the opening form tag <form method="post" action="/accounts/process_login/">. Replace process_login with login.
  • Then go to the urls.py file and delete the process_login url function url(r'^accounts/process_login/$', 'simplesite.views.process_login', name='process_login'),, since it's no longer used.
  • That makes the view and urls a bit cleaner.

Use a Form Class

Overview

  • Django's built-in forms module for authentication is located in the simpleproj/Lib/site-packages/django/forms directory. It's a useful feature.
  • When you use the forms module, instead of writing the form with HTML on the login.html file, you create a separate forms.py file to add your fields and include a form variable on the template.
  • This version of the Login tutorial shows a very basic Login process using the forms module.

Forms

  • Create a file named forms.py and save it to your lower site folder (where the settings.py file is). At the top of the file import Django's forms and auth modules.
    from django import forms
    
  • Below that add a class for the login form.
    class LoginForm(forms.Form):
        username = forms.CharField()
        password = forms.CharField(widget=forms.PasswordInput())
    
  • This creates two variables called username and password that are assigned form character fields.
  • You can add arguments to CharField such as the PasswordInput widget. You can set a maximum length of 25 characters by inserting in the parentheses max_length=25. Or a min_length of 4 characters min_length=4. All form fields are required by default. If you want a form field to be optional add required=False. Separate multiple arguments with commas password = forms.CharField(widget=forms.PasswordInput, min_length=4.

Views and URLs

  • Open the views.py file lower site folder (where the settings.py file is). If one doesn't exist then create it.
  • At the top of the file insert the following imports:
    from django.shortcuts import render_to_response
    from django.http import HttpResponseRedirect
    from django.contrib import auth
    from django.template import RequestContext
    from simplesite.forms import LoginForm 
    
  • The LoginForm is the class you created in the forms.py file. You can give it any name. Change the dot notation path name from simplesite if your directory name is different.
  • Below that add the following functions:
    def login(request):
        message = None
        if request.method == "POST":
            form = LoginForm(request.POST)
            if form.is_valid():
                username = request.POST['username']
                password = request.POST['password']
                user = auth.authenticate(username=username, password=password)
                if user is not None:
                    auth.login(request, user)
                    return HttpResponseRedirect('/accounts/loggedin')
                else:
                    message = "Invalid username and/or password, please reenter"
        else:
            form = LoginForm()
        return render_to_response('registration/login.html', {'message': message, 'form': form},
                                 context_instance=RequestContext(request))
                                 
    def loggedin(request):
        return render_to_response('registration/loggedin.html',
                                  {'username': request.user.username})
    
    def logout(request):
        auth.logout(request)
        return render_to_response('registration/logged_out.html')
    
  • The Django's Auth process includes a distiction between active and inactive users. All users are active by default. The site's administrator can make a users inactive through the Admin Console without deleting them. If you use this feature you can limit login to only active users. In the Login view created above, between if user is not None: and else:, change the code to:
    if user.is_active:
        auth.login(request, user)
        return HttpResponseRedirect('/accounts/loggedin')
    else:
        message = "Your account is inactive"
    
  • Open the urls.py file in the site folder (same folder as settings.py). Below urlpatterns = patterns('', insert the following lines.
        # Auth-related URLs:
        url(r'^accounts/login/$', 'simplesite.views.login', name='login'),
        url(r'^accounts/loggedin/$', 'simplesite.views.loggedin', name='loggedin'),
        url(r'^accounts/logout/$', 'simplesite.views.loggedin', name='logout'),
    

Templates

  • We will assume your site already has a templates directory and a base.html file with the navigation bar. Open the base.html file and in the nav element add a navigation menu link to the login page <a href="/accounts/login">login</a>
  • Add a logout link too <a href="/accounts/logout">logout</a>
  • Create a directory called registration inside the templates folder mkdir registration
  • Create a file called login.html and save it to the templates/registration folder.
  • Populate the login.html template with the following:
    {% extends "base.html" %}
    
    {% block title %}Log In{% endblock %}
    {% block content %}
    
    <form method="post" action="/accounts/login/">
    {% csrf_token %}
    
      {% if message %}
      <p>{{message}}</p>
      {% endif %}
    
      <table>
        {{ form.as_table }}
      </table>
    
    <input type="submit" value="login" />
    </form>
    
    {% endblock %}
    
  • If you don't want the form in table format, remove the HTML table tags and change the form variable to {{ form.as_p }} to put paragraph tags around each input, or {{ form.as_ul }} to display the form as an unordered list (add HTML ul tags to indent). To make it an in-line form just use {{ form }}
  • Create a file called loggedin.html, save it to the templates/registration folder, and populate it with the following:
    {% extends "base.html" %}
    {% block title %}Logged In{% endblock %}
    {% block content %}
    
    <h1>Welcome {{username}}</h1>
    <p>Thank you for logging in.</p>
    <p><a href="/accounts/logout/">Logout</a></p>
    
    {% endblock %}
    
  • Create a file called logged_out.html, save it to the templates/registration folder and populate it with the following:
    {% extends "base.html" %}
    {% block title %}Logged Out{% endblock %}
    {% block content %}
    
      <h2>Logged out!</h2>
      <p><a href="/accounts/login/">Log back in</a></p>
    
    {% endblock %}
    

Testing

  • Now run the virtual server and test that the Login form is working.
    • Open the command line interface.
    • from the upper site directory (the folder with the manage.py file in it) enter python manage.py runserver
    • Go to the browser and put in your local ip address and port 8000 as url: http://127.0.0.1:8000/
    • Click on the navigation bar's "login" link. It should take you to the login page.
    • Fill out the form both incorrectly, to see the error messages, and correctly.
    • When filled out correctly you should be directed to the loggedin page.
    • Logout. If it works, you're done.

Create a page restricted to Loggedin users

  • Django makes it easy to restrict pages to loggedin users. If you have an existing page you just have to add two lines of code in the views.py file.
  • Open the views.py file.
  • At the top of the file insert an import from django.contrib.auth.decorators import login_required.
  • This imports the login_required decorator from Django's Auth module. A decorator is a software design pattern used to alter the functionality of a function, method, or class without having to directly use subclasses or change the source code of the function being decorated. Python decorators go before the function or class and start with @.
  • Add @login_required immediately above the function that renders the page. For example if you have a view called restricted that renders a file called restricted.html it would look like:
    @login_required 
    def restricted(request):
        return render_to_response('restricted.html')
    
    That's all there is to it. If the user clicks on a link to that page and they are not logged in, they will be redirected to the login page.
  • Lets test it out. Create and save a file named restricted.html in the templates directory. Populate it with:
    {% extends "base.html" %}
    {% block title %}Restricted Page{% endblock %}
    {% block content %}
    
    <h1>Restricted Page</h1>
    <p>Restricted Content goes here.</p>
    
    {% endblock %}
    
  • Create a link to the restricted page somewhere. For simplicity we'll put it in the navigation bar in the base.html file. Insert <a href="/restricted">Restricted Page</a>
  • Open the urls.py file. Below urlpatterns = patterns('', insert:
    url(r'^restricted/$', 'simplesite.views.restricted', name='restricted'),
    
  • Now test it out. If you are not logged in it will redirect you to the login page. If you are logged in you can see the page.