I'm working on a Django app that is getting gradually closer to some kind of release, but I want to be able to ramp things up slowly, starting with a few targeted users and iterating from there. I've previously been quite impressed with django-authopenid for supporting OpenID and traditional sign-up and sign-in, and wanted to continue using it, so I sat down to plot a devious scheme that would allow me to restrict user registration without tampering with the django-authopenid's internals.
The cornerstone of my approach is the AppInvite model, which allows the creation of signup codes with a limited number of uses.
class AppInvite(models.Model):
password = models.CharField(max_length=10)
max = models.IntegerField()
current = models.IntegerField()
Then I modified the project's urls.py to override django-authopenid's signup page:
(r'account/signup/','views.restrict_signup'),
(r'account/',include('django_authopenid.urls')),
As mentioned, I didn't want to mangle django-authopenid's internals, because this is only a quick fix to a temporary problem, not an integral piece of code that will stay around forever.
Next I needed to write the restrict_signup view:
from django_authopenid.views import signup
from django.shortcuts import render_to_response
from models import AppInvite
def restrict_signup(request):
"If posting, pass it directly to signup."
if request.method == 'POST':
return signup(request)
extra = {}
if request.GET.has_key("pw"):
pw = request.GET['pw']
ai = AppInvite.objects.filter(password=pw)
if len(ai) != 0:
ai = ai[0]
if ai.current < ai.max:
ai.current = ai.current + 1
ai.save()
return signup(request)
else:
extra['error'] = "The signup code '%s' has expired." % pw
else:
extra['error'] = "'%s' isn't a valid signup code." % pw
return render_to_response("restrict_signup.html", extra)
It the request recieves a GET request, then it forces the user to
authenticate with a signup code, but if it receives a POST
request, then it proxies it forward to django_authopenid's
signup view.
The restrict_signup view has a couple of minor flaws:
it counts the number of times it present the blank registration form, as opposed to the actual number of users created.
it could be circumvented by manually sending a POST request instead of a GET request.
For my purposes, I'm not overly worried if a few fewer people are let in, or if a couple of people circumvent the system to sneak in, so it is still a sufficient solution for me.
The last piece of the puzzle is the restrict_signup.html template.
{% extends "base.html" %}
{% block content %}
<div class="main">
<h2> Please Enter Signup Code </h2>
{% if error %}<p>{{ error }}</p>{% endif %}
<form action="" method="GET">
<label for="pw">Signup Code:</label>
<input name="pw">
<input type="submit" value="Check Code">
</form>
</div>
{% endblock %}
I'm sure it would be useful if this pattern could be abstracted (and extended a bit) into a pluggable application, but that would require a bit more thought about how to structure it. My thoughts on that are:
Support for arbitrary signup codes, that optionally have a limited number of uses.
Support limiting the number of sign ups per time period (100/day, etc).
Have a parameter in
settings.pythat specifies the view with the registration form for users who send a correct signup code.Yep.. that's pretty much it.
Hey Will, that's a really neat overview. Thanks for sharing it.
Reply to this entry