Django 1.8 Tutorial – 4. Integrating the Default Login Screens, Adding HTML Email

So, I started implementing the Django provided user login screens yesterday and it was requiring a ton of reading to get the different parts working. It seems so simple, from the outside, but all the configuration options made it seem more difficult than it really is.

In the attached file, a few of the old configs and views have been deleted, but I’m not going to cover that here. Just do diffs between the contents of the attached tgz files to see the differences.

Create a file in your global templates, templates/registration/login.html:

{% extends "base.html" %}

{% block content %}

{% if form.errors %}
<p>Your username and password didn't match. Please try again.</p>
{% endif %}

<form method="post" action="{% url 'django.contrib.auth.views.login' %}">
{% csrf_token %}
<table>
<tr>
    <td>{{ form.username.label_tag }}</td>
    <td>{{ form.username }}</td>
</tr>
<tr>
    <td>{{ form.password.label_tag }}</td>
    <td>{{ form.password }}</td>
</tr>
</table>

<input type="submit" value="login" />
<input type="hidden" name="next" value="{{ next }}" />
</form>

<a href="{% url 'password_reset' %}">forgot password?</a>

{% endblock %}

In settings.py, set this value to the page they go to after they log in. Here’s mine.

LOGIN_REDIRECT_URL = ‘/c/’

There are ways to override this, so you can redirect to the correct page in the event they started logging in from a different page.

Then, you need to copy all the files in the admin templates included with the django package. If you’re using a virtualenv, copy them from the directory that looks like the path below.

…/env/lib/python2.7/site-packages/django/contrib/admin/templates/registration/

Edit each file to work with your site. The general pattern I used was to change the ‘extend’ statement to extend “base.html”, and removed blocks that didn’t exist in my template.

This is going to take a while. Once you’re done with this tutorial, you should have ten separate templates in registration. So, set aside at least a day to implement just the HTML for login and password reset and changing.

While you’re editing this, you may want to know what the URLs are. Here’s a link to the sources.

Email Configuration
The default email sever is the localhost, and you’re probably not running a mail server, so you need to configure email. My configuration looks like below – SSL, and no authentication:

EMAIL_HOST = 'mail.example.com'
EMAIL_PORT = 465
EMAIL_USE_SSL = True
# commented settings below
# EMAIL_USE_TLS = True
# EMAIL_HOST_USER = 'foo'
# EMAIL_HOST_PASSWORD = 'bar'

This server happens to also host my email, so the round trip is quick.

Sending HTML Emails

The default password reset form uses EmailMultiAlternatives, which allows for multipart emails with HTML and text alternatives, and the password_reset view supports a text email template and an HTML email template, but it’s not enabled by default. The HTML email template is a little tricky to enable.

First, you need to pass the a parameter named ‘html_email_template_name’ to the password_reset view. If you’re using the django.contrib.auth.urls instead of manually setting up the URLs, you need to override one url, accounts/password_reset/.

To do this, you need to define a url regex for that before the inclusion of the canned urls. The urlpatterns in urls.py should look like this:

from django.conf.urls import include, url
from django.contrib import admin
import comment.urls
from django.contrib.auth.views import password_reset

urlpatterns = [
    url(r'^c/', include(comment.urls)),
    url(r'^admin/', include(admin.site.urls)),
    url(r'^accounts/password_reset/$',
        password_reset,
        {'html_email_template_name': 
            'registration/password_reset_email_html.html'},
        name='password_reset',
        ),
    url(r'^accounts/', include('django.contrib.auth.urls')),
]

The router reads the urlpatterns from first to last, dispatching to the first match. So it’ll match accounts/password_reset, and call it with the additional named parameter.

Create the file registration/password_reset_email_html.html. This is an HTML version of the default template:

{% load i18n %}{% autoescape off %}
<table cellpadding="5">
    <tr>
        <td>
            <font family="Arial">
{% blocktrans %}You're receiving this email because you requested a password reset for your user account at {{ site_name }}.{% endblocktrans %}
<br />
<br />
{% trans "Please go to the following page and choose a new password:" %}
<br />
{% block reset_link %}
<a href="{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}">{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}</a>
{% endblock %}
<br />
<br />
{% trans "Your username, in case you've forgotten:" %} {{ user.get_username }}
<br />
<br />
{% trans "Thanks for using our site!" %}
<br />
<br />
{% blocktrans %}The {{ site_name }} team{% endblocktrans %}
<br />
            </font>
        </td>
    </tr>
</table>
{% endautoescape %}

The layout being used is a “table layout”, which was common in the late 90s and early 2000s, before better support for CSS in email. CSS in email is still a problem, and coders still use tables. You can look it up.

When you test the format, you can usually switch between HTML and plain text email via a menu item.

Class Based Views? Nope

If you look at the sources, you’ll notice that this part of the application is largely lifted from the admin, and it’s written in the old style, with function-based-views.

Consequently, it is a little easier to override the default behavior – you copy the code and alter it. The problem, of course, is that this is a brittle technique. Class based views are harder to learn, but easier to modify. Theoretically, the code is more reusable, but I’m not entirely convinced of that. Given these trade-offs, if I were going to reimplement one of the auth views, I’d probably use the View classes.

Stats

Right now, we’re at 595 lines of code across 27 files. So, it’s not a lot of code, but it’s a lot of files. In UX terms, it’s around 12 or 13 screens or panes on a storyboard. So, it’s worth using Django’s default authentication pages.

Attachment Size
minimal4.tgz 16.04 KB