A Django Administration interface for non staff users

A while back I had a Django application in which I needed registered users able to view, create, update and delete objects in my system. These objects were instances of only a subset of all the Django models.Model subclasses I had defined in the models.py file of my application.

You may find this problem very similar to what th Django Admin site solves for administrators: you register some models that are displayed, and then the admins can create/update/delete the objects in the system as needed. Cool, lets use the Django admin! We should only call admin.site.register() on those models we want users to be able to act on and we have a full CRUD interface over the models. However, there are some issues with this naive approach:

  • Only users flagged as staff members can login into the Django admin interface.
  • We would like to maintain the real administrator’s ability to act on many more models, not just those over which we want the mere mortal users to have CRUD capabilities. By not registering for the admin view, the real admin cannot create, view or delete content he might need to.

So, should we copy every template and replicate the logic of the Django Admin interface? No need. Fortunately, we can have multiple admin sites functioning at the same time.

The Django documentation explains that the default Admin site (django.contrib.admin.site) is actually just an instance of django.contrib.admin.sites.AdminSite. We can easily subclass AdminSite class to build new instances of the Django administration site, provided we give it a name different than “admin” when we instantiate it. So, having our own custom admin is as easy as having code like this in an admin.py file:

And in the urls.py:

Unfortunately a problem still persists, since only staff members are allowed to login into our new UserAdmin site. For this, we inspect the django/contrib/admin/sites.py file and find all the checks for is_staff. We find only one match in the has_permission() function of the AdminSite class itself. So in our subclass, we override the function removing the check:

Done? Not yet. If you try it out you will see that non staff users still can’t login. Further inspection finds a login() function in the AdminSite class, and this chunk of code in it:

So as we are not providing any custom login_form, it uses the default AdminAuthenticationForm which must contain the check for is_staff. Thanks to Django’s modular architecture it is very easy to just make our own login form and plug it in our UserAdmin. We just subclass AdminAuthenticationForm and provide our own clean() function, which works the same but avoids the check:

And we plug it in our UserAdmin class:

(Note aside: with this knowledge we could even create a different admin site for Django’s superuser and regular staff members, just adding the is_superuser check to has_permission() ;))

Now we are done! We can login successfully into our brand new admin (or not so much) site for non staff users. There are a few gotchas, namely that some of the templates used by the admin site also contains an is_staff check in the template itself. For example in Django 1.3.1 line 26 of contrib/admin/templates/admin/base.html is:

{% if user.is_active and user.is_staff %}

so, for non-staff users, it won’t show the logout bar at the top.

To make it really usable we must copy these templates, edit them, and include them in our application. This is really easy to do, and the documentation shows how you can override the templates for our UserAdmin site only. This is more boring than the rest, so I’ll leave it as homework 🙂