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: