Introducing django-lazysignup
django-lazysignup is a Django application that was partly inspired by a talk that Simon Willison gave at EuroPython a few years back (perhaps 2008, or 2009?) and partly to scratch an itch I had with an application I was building at the time. The problem it tries to solve is that making users sign up with a web site just to try out your app is quite a high barrier - potential users just bounce right off that registration form.
I'd seen some efforts to solve this problem before. Most seemed to involve stashing the data for some predetermined part of the website somewhere (often in the session) and then reconstituting it into real application data when the user eventually bites the bullet and signed up. This worked OK, but you had to write it anew for every web site, as clearly the data you'd want to store would change from site to site. You also ended up effectively developing a miniature version of your site that would work with some limited data set.
This didn't really seem good enough.
So I started wondering - what if we just created a real user for every person who visited the site? Django already has support for creating users with unusable passwords - so if we just create a user with an unusable password every time a new person comes along, log them in, and then at some future point (presumably once they've fallen in love with your site) they can set themselves up with a real username and password. And as a bonus, all that data that they created while messing about with the site sticks around, and carries over into their 'real' user.
This is, in essence, how django-lazysignup works.
Let's take a look in a bit more detail. You can grab an official release from PyPI, or clone it on GitHub.
What's in the package?
Once you've installed django-lazysignup (I'll let you read the docs to see how to do that), you've got a few tools to play with:
- An authentication backend
- A user conversion view
- The allow_lazy_user view decorator
- The is_lazy_user template filter
- The user agent blacklist
- Custom user models
Authentication backend
The authentication backend needs to be installed for django-lazysignup to work. This backend is required so that we can authenticate the temporary user accounts without a password. We refer to these temporary users as 'lazy' users - they haven't bothered to sign up yet.
The user conversion view
In many cases, you're going to want your users to sign up eventually. The package includes a view to allow you to do this, converting a lazy user to a real user. In practice, this simply involves the user setting a username and password for their temporary user account. This approach means they get to keep all the data that they've already created in your application.
The allow_lazy_user decorator
The temporary user creation process is potentially an expensive one (and on a high-traffic site, may cause contention on the user table). django-lazysignup therefore provides a decorator that allows the developer to specify which views can trigger this process. Sites I've developed that use this package tend to apply the decorator to the views that do interesting things, but exclude the homepage and any static pages on the site.
The is_lazy_user template filter
It's often useful - particularly in templates - to find out whether the current user is a lazy (ie. temporary) user or not. In particular, you may want to show a link to the convert view if they're a lazy user. This template filter is provided for this purpose. Note that lazy users will appear as authenticated (ie. is_authenticated() returns True). For now, has_usable_password also returns False for lazy users, though this should not be relied on. The canonical way of detecting a lazy user is through the is_lazy_user filter (and its associated function in the utils module)
The user agent blacklist
You probably don't want every request to a view that's opted in to lazy user creation to actually create a user. Principally, you probably don't want search engines to do this. The user agent blacklist is a crude way of filtering out such robots.
Note that this means that views that have the allow_lazy_user decorator won't be guaranteed to always have an authenticated user. You still need to make sure that your views work with unauthenticated user (or mark them with the login_required decorator or similar).
Custom user models
As of version 0.7, django-lazysignup also has limited support for custom user models. Just set the LAZYSIGNUP_USER_MODEL setting appropriately (by default, it's auth.User to support contrib.auth out of the box). As alluded to before, support for custom users is basically predicated on the user model looking pretty much like a normal Django user - especially in that it's stored in the database.
I anticipate that this mechanism will change in the distant future, if Django were to adopt some form of pluggable model (or at least, a pluggable user model).
Restrictions
django-lazysignup was built with Django's own contrib.auth application in mind. If you're not using this for authentication, then integrating django-lazysignup will be more of a challenge. (If you do, and there are some changes to the package that would help you, please let me know!). In particular, it expects that you will have a user model stored somewhere in the database.
The Future
Honestly, I'd expected to have a 1.0 release out the door by now, but people keep suggesting great features that I'd like to get in before committing to API stability. Currently on the list are:
- Global view opt in, with opt-out decorator (suggested by Rob Hudson)
- Support for deferred user validation - for example, to support email address validation (suggested by Alex Ehlke)
In addition to Rob and Alex, thanks also to Luke Zapart for suggesting and providing an initial implementation and tests for the custom user model feature.
If you have a play with the app, and there's something you'd like to see, then do get in touch - or just fork it on GitHub, add the feature (with docs and tests, preferably!) and send me a pull request.
And that application that django-lazysignup was originally built for? Well, I still want to do it. Someday.
