The missing django-allauth tutorial

Recently I needed to give the users of a Django web app I built the ability authenticate themselves either directly or through Facebook.

So I researched the available Django social-integration applications and found a few, including Raymond Penners’ django-allauth. I don’t remember why I ultimately settled on using django-allauth for my task, but it definitely wasn’t because of the clear documentation or ease of troubleshooting. I found myself frustrated by a series of minor but time-consuming hurdles to getting going with allauth, as others have before me, and what’s worse, the 3rd party step-by-step guide (Don’t bother clicking) that everyone including Penners himself seemed to be relying on to fill the documentation gap had disappeared from the internet (I hope you didn’t bother clicking).

Of course things rarely actually disappear from the internet, so I soon found it on the way-back machine, but it’s not quite as comprehensive as I was hoping it’d be.

So I figured I’d create a second edition of the missing allauth tutorial, but instead of trying to remember for you what I did to make it work (which it does, by the way, quite beautifully), I’m just going to start from scratch for you and add both Facebook and Django authentication to a bare-bones application and then walk you through a few basic customizations and tweaks.

Note: I’m using Django 1.5 and release 0.12.0 of django-allauth.
Also: All of the code discussed below can be found here.

A bare-bones application called Vort

We’ll start out with a seriously basic index.html:

<!DOCTYPE html>
<html>
    <head>
        <title>Vort</title>
        <link rel="stylesheet" type="text/css" href="/static/css/style.css" />
    </head>
    <body class="logged-out">
    </body>
</html>

And a tiny bit of CSS to style it:

.logged-out {
    background: gainsboro url("/static/img/WhatAreYouLookingAt.jpg") no-repeat center 30px;
    background-size: 90%;
}

And a Django view to serve it:

def index(request):
    return render_to_response("larb/index.html",
                              RequestContext(request))

Giving us a website that looks like this and does nothing:

Add django-allauth

Now we’ll add the ability to log-in with Django or Facebook.

First we’ll install django-allauth:

pip install django-allauth

Then we’ll add the following bits to settings.py:

AUTHENTICATION_BACKENDS = (
    # Needed to login by username in Django admin, regardless of `allauth`
    "django.contrib.auth.backends.ModelBackend",
    # `allauth` specific authentication methods, such as login by e-mail
    "allauth.account.auth_backends.AuthenticationBackend"
)
 
TEMPLATE_CONTEXT_PROCESSORS = (
    "django.core.context_processors.request",
    "django.contrib.auth.context_processors.auth",
    "allauth.account.context_processors.account",
    "allauth.socialaccount.context_processors.socialaccount",
)
 
# auth and allauth settings
LOGIN_REDIRECT_URL = '/'
SOCIALACCOUNT_QUERY_EMAIL = True
SOCIALACCOUNT_PROVIDERS = {
    'facebook': {
        'SCOPE': ['email', 'publish_stream'],
        'METHOD': 'js_sdk'  # instead of 'oauth2'
    }
}

And to INSTALLED_APPS, we’ll add these:

'allauth',
'allauth.account',
'allauth.socialaccount',
'allauth.socialaccount.providers.facebook',

Now we have to add this to urls.py:

url(r'^accounts/', include('allauth.urls')),

And finally, we’ll create the database:

python manage.py syncdb

Or if you’re using South:

python manage.py migrate allauth.socialaccount
python manage.py migrate allauth.socialaccount.providers.facebook

Create and configure a Facebook app

Before the Facebook login will work, we need to configure some application settings on Facebook.

To do this, I’ll log in to Facebook and choose Manage Apps from the menu that drops from this thing in upper right-hand corner –> and then hit the Create App button. (You may first have to sign up as a Facebook Developer before the Manage Apps menu option becomes available.)

And I’ll name the app VortLarb:

And configure the basic settings thusly:

Now we have to create a few records in our database before django-site and django-allauth will know their heads from a hole in the ground:

UPDATE django_site SET DOMAIN = '127.0.0.1:8000', name = 'Vort' WHERE id=1;
INSERT INTO socialaccount_socialapp (provider, name, secret, client_id, `key`)
VALUES ("facebook", "Facebook", "--put-your-own-app-secret-here--", "--put-your-own-app-id-here--", '');
INSERT INTO socialaccount_socialapp_sites (socialapp_id, site_id) VALUES (1,1);

Now we can login using Django or Facebook

Now, when we go to http://127.0.0.1:8000/accounts/login/, we get an ugly but functional login page (You may need to restart your server before you see ‘Vort’ instead of ‘example.com’):

From here, if we click the Facebook link, if the user does not already exist, the user will be created automatically from data received from Facebook, and then that user will be logged in to our application. To log in as a brand new Django user, you’ll need to run through the user registration process first by clicking on the “Sign Up” link (or you can of course always log in to the app’s admin site and create a user there).

So now, we can log in to the app successfully, but we can’t tell we’ve been successful because the application looks the same either way, so let’s make the page look different when the user is logged in.

Detect authentication and email-verification status

When a user is logged in, let’s change the title and background image, display the user’s name, and display whether or not the user’s email address has been verified. And while we’re at it, let’s throw links on the page for logging in and logging out.

So this is what the app will look like when user Skoodge is logged in:

This updated index.html includes all of these changes:

<!DOCTYPE html>
<html>
<head>
    <title>{% if request.user.is_authenticated %}Logged In{% else %}Not Logged In{% endif %}</title>
    <link rel="stylesheet" type="text/css" href="/static/css/style.css" />
</head>
<body class="{% if request.user.is_authenticated %}logged-in{% else %}logged-out{% endif %}">
 
{% if request.user.is_authenticated %}
    <a href="/accounts/logout/" class="pull-right">Logout</a>
    {% if request.user.first_name or request.user.last_name %}
        {{ request.user.first_name }} {{ request.user.last_name }}
    {% else %}
        {{ request.user.username }}
    {% endif %}
    {% if request.user.profile.account_verified %} (verified) {% else %} (unverified) {% endif %}
 
{% else %}
    <a href="/accounts/login/" class="pull-right">Login</a>
{% endif %}
</body>
</html>

And our CSS now looks like this:

.logged-in {
    background: gainsboro url("/static/img/CCTVMobile.jpg") no-repeat center 80px;
}
.logged-out {
    background: gainsboro url("/static/img/WhatAreYouLookingAt.jpg") no-repeat center 30px;
    background-size: 90%;
}
.pull-right {
    float:right;
}

To make the user’s email-verification status available to the template via request.user.profile.account_verified, we’ll add a UserProfile model to models.py, then add an account_verified method to the model which will detect the user’s email-verification status:

from django.contrib.auth.models import User
from django.db import models
from allauth.account.models import EmailAddress
 
class UserProfile(models.Model):
    user = models.OneToOneField(User, related_name='profile')
 
    def __unicode__(self):
        return "{}'s profile".format(self.user.username)
 
    class Meta:
        db_table = 'user_profile'
 
    def account_verified(self):
        if self.user.is_authenticated:
            result = EmailAddress.objects.filter(email=self.user.email)
            if len(result):
                return result[0].verified
        return False
 
User.profile = property(lambda u: UserProfile.objects.get_or_create(user=u)[0])

Since we’ve added a model, of course now we have to perform another syncdb (or a migration if you’re using South).

python manage.py syncdb

Display the user’s Facebook or Gravatar icon

If a user is logged in via Facebook, let’s show the user’s Facebook image. Otherwise, let’s show their Gravatar icon if they have one.

To do this, first we’ll add this to our UserProfile in models.py:

from allauth.socialaccount.models import SocialAccount
import hashlib
 
def profile_image_url(self):
    fb_uid = SocialAccount.objects.filter(user_id=self.user.id, provider='facebook')
 
    if len(fb_uid):
        return "http://graph.facebook.com/{}/picture?width=40&height=40".format(fb_uid[0].uid)
 
    return "http://www.gravatar.com/avatar/{}?s=40".format(hashlib.md5(self.user.email).hexdigest())

And then we’ll add this to index.html’s logged in view, just before the name display:

    <img src="{{ request.user.profile.profile_image_url }}"/>

Now when Skoodge logs in, he’ll see this:

And when I log in using Facebook, I’ll see this:

Configure email

Now let’s get email working so our users can verify their email addresses.

IRL, I recommend using django-sendgrid.

However for purposes of this conversation, I’m just going to use Django’s console backend by adding this to settings.py:

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

Test email and the user registration flow

Now let’s give the (still ugly) registration flow a go. To do that, we’ll log out and follow the instructions here:

http://127.0.0.1:8000/accounts/signup/

When we do that, we see evidence in the server log that an email was sent to our new user:

Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: [Vort] Confirm E-mail Address
From: webmaster@localhost
To: skoodge@blorch.com
Date: Fri, 12 Jul 2013 17:54:49 -0000
Message-ID: <20130712175449.79225.42282@Sarahs-MacBook-Pro-2.local>
 
User skoodge at Vort has given this as an email address.
 
To confirm this is correct, go to 
http://127.0.0.1:8000/accounts/confirm_email/3ff199dc53b5c61bd3566ae01a87b1dba3f7a95f1e6784359acbc63e7b4cfd46/

Now we’ll follow the link in the log to verify the email address, and ta-da! Verified (if you’re following along, remember your link will be different. Don’t use the one shown above, as it almost definitely won’t work. Use the one you find in your own server log.):

Remove the logout-confirmation step

An adjustment you may want to make is to remove the extra do-you-really-want-to-log-out step upon logging out.

To do that, put this in your project’s urls.py file before the line that includes the allauth urls:

(r'^accounts/logout/$', 'django.contrib.auth.views.logout',
     {'next_page': '/'}),

Style the login and registration templates

And of course, these days, no web page on earth looks the way the bare-bones registration and login pages look out of the box. We must style these templates!

To begin with, just copy the templates you want to style, including the base.htmls (which I collapsed into one instead of maintaining both of the base.htmls allauth uses), over from allauth’s site-packages directory to your app’s template directory. So to customize the ones we’ve touched so far:

copy

site-packages/allauth/templates/base.html
site-packages/allauth/templates/account/signup.html
site-packages/allauth/templates/account/login.html
site-packages/allauth/templates/account/email_confirm.html

to

your-app/templates/account/base.html
your-app/templates/account/signup.html
your-app/templates/account/login.html
your-app/templates/account/email_confirm.html

respectively.

Now that they’re all in our back yard, we can do what we want with them.

For purposes of this demo, I’ve styled mine to look like this:

See the sample code to see how I did that.

Customize the email message

Likewise, the boilerplate django-allauth email messages are a great start, but they aren’t for everyone. To customize the email that gets sent out, copy these:

site-packages/allauth/templates/account/email/email_confirmation_message.txt
site-packages/allauth/templates/account/email/email_confirmation_subject.txt

to your application’s templates directory:

your-app/templates/account/email/email_confirmation_message.txt
your-app/templates/account/email/email_confirmation_subject.txt

And make any changes you like to your local copies.

Fin

I’ve made many more tweaks to my own implementation of django-allauth, which I may discuss in a future post if it seems people want to hear about them, but this concludes the basic instruction I wished I’d had a hold of when I was evaluating and then implementing django-allauth for the first time.

I hope you’ve enjoyed our time together as much as I have.

Leave a Reply