Pluggable Django Apps and Django's Admin
There are a lot of us out there who build these reusable, pluggable Django applications and share them with the world. Some of the applications are more useful than others. Personally, I think my applications tend to fall in the less useful category, but I like to build and release them nonetheless.
The other day, I received a suggestion from one of the users of django-pendulum for how to make it a little more user-friendly. The problem was that he had not yet configured Pendulum for his current Site. As soon as he tried to access his site after installing django-pendulum, he was greeted with a nasty Django exception page, admonishing him to configure Pendulum for the site. It was pretty dirty and effective, but not very pleasant at all.
This user suggested that, instead of displaying the nasty exception page, I redirect the user to a place where they could in fact configure Pendulum. I thought this was a grand idea, and I set out to make it happen last night.
That's when I realized I had a problem. django-pendulum is intended to be a reusable Django application. Most of the time it might be safe to assume that everyone uses /admin/ to get to the Django administration utility. However, there's also a good chance that this will not be the prefix used to access the administration utility. What do you do when you hardcode your applications to redirect a user to /admin/ and they use something more secure like /milwaukee/ for their administration utility?
So the question is this: how do you determine what URL will bring a user to the Django administration page for a particular Django model based on the specific site's URLconf setup?
It turns out that one solution (not sure if it's safe to say "the solution" necessarily) with Django 1.1+ is to have something like this:
from django.core.urlresolvers import reverse admin_url = reverse('admin_pendulum_pendulumconfiguration_add')
The named URLconf used in the reverse function should ensure that the appropriate administration URL prefix is used for whatever site your application is installed on.
I did a bit of browsing through the Django documentation, but I've never seen it stated anywhere what the names are for administration URLs actually are. I found out about this little nugget by examining the output of the django.contrib.admin.ModelAdmin.get_urls method (see get_urls(self)). From the looks of it, pretty much any Django model registered with your Django administration will have URLconf items similar to the following:
[<RegexURLPattern admin_pendulum_pendulumconfiguration_changelist ^$>, <RegexURLPattern admin_pendulum_pendulumconfiguration_add ^add/$>, <RegexURLPattern admin_pendulum_pendulumconfiguration_history ^(.+)/history/$>, <RegexURLPattern admin_pendulum_pendulumconfiguration_delete ^(.+)/delete/$>, <RegexURLPattern admin_pendulum_pendulumconfiguration_change ^(.+)/$>]
In other words, you get 5 URLconf patterns per model: changelist, add, history, delete, and change. These patterns are named, and they all appear to be prefixed with admin_[app_label]_[model]_. In the case of django-pendulum, the application is called pendulum so [app_label] becomes pendulum. The model in question is called PendulumConfiguration so the [model] becomes pendulumconfiguration. I assume this same pattern will be followed for any registered model. Please correct me if I'm wrong.
For those of you interested in more details, django-pendulum uses a middleware class to redirect users to the screen where they can configure Pendulum for the site in question (unless it's already been configured). Here's my middleware as of revision 18:
from django.contrib.auth.views import login from django.contrib.sites.models import Site from django.core.urlresolvers import reverse from django.http import HttpResponseRedirect from django.conf import settings from pendulum.models import PendulumConfiguration SITE = Site.objects.get_current() admin_url = reverse('admin_pendulum_pendulumconfiguration_add') login_url = getattr(settings, 'LOGIN_URL', '/accounts/login/') class PendulumMiddleware: """ This middleware ensures that anyone trying to access Pendulum must be logged in. If Pendulum hasn't been configured for the current site, the staff users will be redirected to the page to configure it. """ def process_request(self, request): try: SITE.pendulumconfiguration except PendulumConfiguration.DoesNotExist: # this will force the user to configure pendulum if they're staff if request.user.has_perm('add_pendulumconfiguration') and \ request.path not in (admin_url, login_url): # leave the user a message request.user.message_set.create(message='Please configure Pendulum for %s' % SITE) return HttpResponseRedirect(admin_url) else: entry_url = reverse('pendulum-entries') if request.path[:len(entry_url)] == entry_url and request.user.is_anonymous(): if request.POST: return login(request) else: return HttpResponseRedirect('%s?next=%s' % (login_url, request.path))
This checks to see if Pendulum has been configured for the site. If not, the middleware will check to see if the current user has permission to add a configuration for Pendulum. If so, they will be redirected to the appropriate configuration screen.
If Pendulum has been configured for the site or the current user does not have permission to add a configuration, the user should be unaffected. If the user is trying to access Pendulum on the site's front-end, the middleware will ensure that they're actually logged in.
As always, suggestions are welcome!