Computer :(

/home/rrix:blog:tags:cgit:rss

Django Tidbit: Logging in After User Activation

Table of Contents

I'm working on a fun little side project (related to my recent hacking for good article) as an excuse to teach myself Django and Android development – specifically focusing on building client APIs which Android applications consume. Until I have a real idea of the workflow behind this application, I'm focusing on a web frontend as MVP, building it out with what I would think is a fairly standard stack barring the HAML and SCSS obsession I have:

  • celery
  • djaml
  • django-facebook
  • django-pipeline
  • django-registration
  • django-scss
  • django.contrib.auth
  • south

One of the things I've focused on in my early MVP was making the signup/login flow very very smooth. Initially, I went a route of only allowing Facebook Connect logins because the application needs some social graph features that I didn't necessarily want to build out at the time. Based on initial feedback, that seemed like a showstopper because of the target market of this application.

Since that initial MVP, I've taken a break to focus on other projects, and also spend some time figuring out how to properly tackle this project. At tonight's SF Python meetup (which was grand, btw), I hashed out a plan to allow 'typical' logins alongside Facebook Connect logins, and subtly nudge those people in to provide Connect logins once they're set up with an account.

Anyways, it's late and I'm rambling: The signup flow that django-registration provides by default kind of sucks, though in large part this is due to shortcomings in django.contrib.auth.

The auth documentation tries to make it very clear that to log a user in, you have to pass them through authenticate() first. So, as a new user, here's your signup flow:

  • Visit homepage
  • Click sign up
  • Fill in details
  • Get bumped to page saying "hey check your email"
  • Email has a link to the site
  • Link opens up, and user is now activated.
  • User is now expected to log in with their credentials (assuming they even remember them)

That is way too many steps, however, the only way I can really lessen that is to remove the email authentication (lolno), or kill that final step.

1 tl;dr

Because of that little authenticate() requirement, the basic assumption is that you have to do something horrific like implement your own authentication backend or something horrific like that. Instead, as discovered after ten minutes of trying to drag information out of the #Django channel on freenode, you can just stuff the data you need in to the #backend property of the User, since that's all the authenticate() helper does anyways (assuming one of the backends decides to authenticate it).

def activate_user(request, activation_key):
    activated_user = RegistrationProfile.objects.activate_user(activation_key)
    if activated_user:
        backend = get_backends()[0]
        activated_user.backend = "%s.%s" % (backend.__module__,
                                            backend.__class__.__name__)
        login(request, activated_user)
        return render(request, "registration/activation_complete.html")
    else:
        return render(request, "registration/activate.html")

^ views.py

Wire that up as a URL:

url(r'^accounts/activate/(?P<activation_key>\w+)/$', views.activate_user,   name='registration_activate'),

^ urls.py

Bob's your uncle, you will now be logged in after authenticating. Obviously there are some security things to keep in mind; a person could log in to an account without necessarily proving they are that user, but at that point, they're reading your user's emails anyways and could reset the password, basically making this a moot point.