Tryolabs logo
Tryolabs logo

A Django Administration interface for non staff users
Mon, Jun 18, 2012

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 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 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 ( 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 file.


August 2018: Please note that this post was written for an older version of Django. Changes in the code might be necessary to adapt it to the latest versions and best practices.

from django.contrib import admin
from django.contrib.admin.sites import AdminSite
class UserAdmin(AdminSite):
# Anything we wish to add or override
user_admin_site = UserAdmin(name='usersadmin')
# Run user_admin_site.register() for each model we wish to register
# for our admin interface for users
# Run for each model we wish to register
# with the REAL django admin!

And in the

from django.conf.urls import patterns, include, url
from django.contrib import admin
from myapp.admin import user_admin_site
urlpatterns = patterns('',
url(r'^admin/', include(,
url(r'^', include(user_admin_site.urls)),
url(r'^', include('myapp.urls')),

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/ 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:

class UserAdmin(AdminSite):
def has_permission(self, request):
Removed check for is_staff.
return request.user.is_active

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:

defaults = {
'extra_context': context,
'authentication_form': self.login_form or AdminAuthenticationForm,
'template_name': self.login_template or 'admin/login.html',
return login(request, **defaults)

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:

from django.contrib.auth.forms import AuthenticationForm
class UserAdminAuthenticationForm(AuthenticationForm):
Same as Django's AdminAuthenticationForm but allows to login
any user who is not staff.
this_is_the_login_form = forms.BooleanField(widget=forms.HiddenInput,
error_messages={'required': ugettext_lazy(
"Please log in again, because your session has"
" expired.")})
def clean(self):
username = self.cleaned_data.get('username')
password = self.cleaned_data.get('password')
if username and password:
self.user_cache = authenticate(username=username,
if self.user_cache is None:
if u'@' in username:
# Mistakenly entered e-mail address instead of username?
# Look it up.
user = User.objects.get(email=username)
except (User.DoesNotExist, User.MultipleObjectsReturned):
# Nothing to do here, moving along.
if user.check_password(password):
message = _("Your e-mail address is not your "
" Try '%s' instead.") % user.username
raise forms.ValidationError(message)
# Removed check for is_staff here!
elif not self.user_cache.is_active:
raise forms.ValidationError(message)
return self.cleaned_data

And we plug it in our UserAdmin class:

class UserAdmin(AdminSite):
login_form = UserAdminAuthenticationForm
def has_permission(self, request):
Removed check for is_staff.
return request.user.is_active

(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 :-)

Tryolabs logo
© 2021. All rights reserved.