Auth views

HomeView

Before we start any authentication views, we should probably have a home page. So, let’s make one. Our stub, pycon/survivalguide/survivalguide/ doesn’t have a views.py, so let’s go ahead and create it with touch survivalguide/views.py. Let’s create our first view here:

from django.views import generic


class HomePageView(generic.TemplateView):
    template_name = 'home.html'

Template

Now we need to touch templates/home.html and open it up for editing. It’ll be a pretty simple view so let’s just put the following into it:

{% extends '_layouts/base.html' %}

{% block headline %}<h1>Welcome to the PyCon Survival Guide!</h1>{% endblock headline %}

{% block content %}
<p>Howdy{% if user.is_authenticated %} {{ user.username }}{% endif %}!</p>
{% endblock %}

URL

Finally, we need an URL so we can reach the view. In survivalguide/urls.py, add the following:

[...]
from .views import HomePageView
[...]

url('^$', HomePageView.as_view(), name='home'),

Now any time we go to / on our site, we’ll get our template.

SignUpView

Now, we need to make a view for users to be able to signup at. Let’s update our survivalguide/views.py file like so:

from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User


class SignUpView(generic.CreateView):
    form_class = UserCreationForm
    model = User
    template_name = 'accounts/signup.html'

URL

Since we want to be able to get to the view from a URL, we should add one to survivalguide/urls.py.

[...]
from .views import SignUpView
[...]

url(r'^accounts/register/$', SignUpView.as_view(), name='signup'),
[...]

Template

Since we told the view that the template was in an accounts directory, we need to make one in our global templates directory. We have to make this directory because accounts isn’t an app. mkdir templates/accounts and then touch templates/accounts/signup.html.

signup.html should look like:

{% extends '_layouts/base.html' %}

{% block title %}Register | {{ block.super }}{% endblock %}

{% block headline %}<h1>Register for the PyCon Survival Guide</h1>{% endblock %}

{% block content %}
    <form action='' method="POST">
        {% csrf_token %}
        {{ form.as_p }}
        <input type="submit" value="Sign Up">
    </form>
{% endblock %}

This default form doesn’t render the most beautiful HTML and, thinking about our future forms, we’ll have to do a lot of HTML typing just to make them work. None of this sounds like fun work and we’re not using a Python web framework in order to have to write a bunch of HTML, so let’s save ourselves some time and trouble by using django-crispy-forms.

django-crispy-forms

Like pretty much everything, first we need to install django-crispy-forms with pip install django-crispy-forms. Then we need to add 'crispy_forms' to INSTALLED_APPS in our settings file and provide a new setting:

CRISPY_TEMPLATE_PACK = 'bootstrap3'

We have to tell django-crispy-forms what set of templates to use to render our forms.

New form

touch survivalguide/forms.py and open it in your editor. We need to create a new, custom form, based off of Django’s default UserCreationForm.

from django.contrib.auth.forms import UserCreationForm

from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, ButtonHolder, Submit

class RegistrationForm(UserCreationForm):
    def __init__(self, *args, **kwargs):
        super(RegistrationForm, self).__init__(*args, **kwargs)

        self.helper = FormHelper()
        self.helper.layout = Layout(
            'username',
            'password1',
            'password2',
            ButtonHolder(
                Submit('register', 'Register', css_class='btn-primary')
            )
        )

View changes

In survivalguide/views.py, we need to change our form import from:

from django.contrib.auth.forms import UserCreationForm

to

from .forms import RegistrationForm

Since we’re using relative imports, we should add:

from __future__ import absolute_import

to the top of the file to ensure that our imports work like we want.

Change the form_class in the view to RegistrationForm and the view should be done.

Template change

Finally, in the template, change the <form> area to be: {% crispy form %} and load the django-crispy-forms tags with {% load crispy_forms_tags %} near the top of the file. If we refresh the page, we should now see a decent looking form that works to sign up our user.

LogInView

Most of LogInView is the same work as SignUpView. Since we know we’re going to need a custom form, because we want to use django-crispy-forms, let’s start there.

Form

Back in survivalguide/forms.py:

from django.contrib.auth.forms import AuthenticationForm


class LoginForm(AuthenticationForm):
    def __init__(self, *args, **kwargs):
        super(LoginForm, self).__init__(*args, **kwargs)

        self.helper = FormHelper()
        self.helper.layout = Layout(
            'username',
            'password',
            ButtonHolder(
                Submit('login', 'Login', css_class='btn-primary')
            )
        )

View

Then, we should create a view.

[...]
from django.contrib.auth import authenticate, login, logout
from django.core.urlresolvers import reverse_lazy
[...]
from .forms import LoginForm
[...]

class LoginView(generic.FormView):
    form_class = LoginForm
    success_url = reverse_lazy('home')
    template_name = 'accounts/login.html'

    def form_valid(self, form):
        username = form.cleaned_data['username']
        password = form.cleaned_data['password']
        user = authenticate(username=username, password=password)

        if user is not None and user.is_active:
            login(self.request, user)
            return super(LoginView, self).form_valid(form)
        else:
            return self.form_invalid(form)

URL

In our survivalguide/urls.py file, we need to add a route to our new login view.

from .views import LoginView
[...]
url(r'^accounts/login/$', LoginView.as_view(), name='login'),
[...]

Template

And, of course, since we gave our view a template name, we have to make sure the template exists. So, in templates/accounts/ go ahead and touch login.html and fill the file with:

{% extends '_layouts/base.html' %}

{% load crispy_forms_tags %}

{% block title %}Login | {{ block.super }}{% endblock %}

{% block headline %}<h1>Login to the PyCon Survival Guide</h1>{% endblock %}

{% block content %}
{% crispy form %}
{% endblock %}

LogOutView

We should also provide a quick and easy way for users to log out. Thankfully Django makes this pretty simple and we just need a view and an URL.

View

In survivalguide/views.py:

class LogOutView(generic.RedirectView):
    url = reverse_lazy('home')

    def get(self, request, *args, **kwargs):
        logout(request)
        return super(LogOutView, self).get(request, *args, **kwargs)

URL

And in our survivalguide/urls.py, we’ll import the new view and create an URL:

[...]
from .views import LogOutView
[...]

url(r'^accounts/logout/$', LogOutView.as_view(), name='logout'),
[...]

Global template changes

Finally, though, we should have the ability to see if we’re logged in or not, and have some links for logging in, signing up, and logging out. So open up templates/_layouts/base.html and add the following to the .navbar-collapse area:

{% if not user.is_authenticated %}
<a href="{% url 'signup' %}" class="btn btn-default navbar-btn">Register</a>
<a href="{% url 'login' %}" class="btn btn-default navbar-btn">Login</a>
{% else %}
<a href="{% url 'logout' %}" class="btn btn-default navbar-btn">Logout</a>
{% endif %}

django-braces

Our views are complete and pretty solid but it’s a little weird that logged-in users can go to the login view and signup view and that logged-out users can go to the logout view. It would also be nice to send the users messages when something happens. Writing code to do all of these things is easy enough but there are already packages out there that provide this functionality. Namely django-braces.

As usual, install it with pip install django-braces. Since braces doesn’t provide any models or templates, we don’t have to add it to INSTALLED_APPS, but, as we want to show messages, we should update our base.html file to provide a place for them.

Messages

Open up templates/_layouts/base.html and add:

{% if messages %}
<ul class="messages">
    {% for message in messages %}
    <li{% if message.tags %} class="alert alert-{{ message.tags }}"{% endif %}>{{ message }}</li>
    {% endfor %}
</ul>
{% endif %}

before the .jumbotron area. This snippet will show any messages in the session in a way that Bootstrap expects.

Views

Now, back in survivalguide/views.py, we need to import django-braces, so add:

from braces import views

to the imports area near the top of the file. We need to add a few mixins and attributes to a few of the views, so let’s do that now.

SignUpView

Add views.AnonymousRequiredMixin and views.FormValidMessageMixin to the class’s signature. We should also add a form_valid_message attribute to the class which’ll be shown to the user when they have successfully signed up.

The AnonymousRequiredMixin prevents authenticated users from accessing the view.

LogInView

Add the same two mixins to this view as well and set a form_valid_message that tells the user that they’re logged in.

LogOutView

LogOutView needs the views.LoginRequiredMixin and the views.MessageMixin added to it.

The LoginRequiredMixin prevents this view from being accessed by anonymous users.

We also need to update the get method on the view and add:

self.messages.success("You've been logged out. Come back soon!")

to it before the super() call.

Now all of our views should be properly protected and give useful feedback when they’re used.