Restricting User Signups in Django
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)
<span class="n">extra</span> <span class="o">=</span> <span class="p">{}</span>
<span class="k">if</span> <span class="n">request</span><span class="o">.</span><span class="n">GET</span><span class="o">.</span><span class="n">has_key</span><span class="p">(</span><span class="s">"pw"</span><span class="p">):</span>
<span class="n">pw</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">GET</span><span class="p">[</span><span class="s">'pw'</span><span class="p">]</span>
<span class="n">ai</span> <span class="o">=</span> <span class="n">AppInvite</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span><span class="n">password</span><span class="o">=</span><span class="n">pw</span><span class="p">)</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">ai</span><span class="p">)</span> <span class="o">!=</span> <span class="mf">0</span><span class="p">:</span>
<span class="n">ai</span> <span class="o">=</span> <span class="n">ai</span><span class="p">[</span><span class="mf">0</span><span class="p">]</span>
<span class="k">if</span> <span class="n">ai</span><span class="o">.</span><span class="n">current</span> <span class="o"><</span> <span class="n">ai</span><span class="o">.</span><span class="n">max</span><span class="p">:</span>
<span class="n">ai</span><span class="o">.</span><span class="n">current</span> <span class="o">=</span> <span class="n">ai</span><span class="o">.</span><span class="n">current</span> <span class="o">+</span> <span class="mf">1</span>
<span class="n">ai</span><span class="o">.</span><span class="n">save</span><span class="p">()</span>
<span class="k">return</span> <span class="n">signup</span><span class="p">(</span><span class="n">request</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">extra</span><span class="p">[</span><span class="s">'error'</span><span class="p">]</span> <span class="o">=</span> <span class="s">"The signup code '</span><span class="si">%s</span><span class="s">' has expired."</span> <span class="o">%</span> <span class="n">pw</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">extra</span><span class="p">[</span><span class="s">'error'</span><span class="p">]</span> <span class="o">=</span> <span class="s">"'</span><span class="si">%s</span><span class="s">' isn't a valid signup code."</span> <span class="o">%</span> <span class="n">pw</span>
<span class="k">return</span> <span class="n">render_to_response</span><span class="p">(</span><span class="s">"restrict_signup.html"</span><span class="p">,</span> <span class="n">extra</span><span class="p">)</span>
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.py
that specifies the view with the registration form for users who send a correct signup code.Yep.. that's pretty much it.