Django admin site: access, filtering and restricting

Tags: django

I’m going to show you how to filter what’s shown in django’s admin based on the current request, so for instance limiting the list of objects to what the current user has permission to see.

Django’s build-in admin site is pretty great. It is easy to get a quick edit/add/delete interface for your database objects. The only basic action you have to take is to register your model:

from django.contrib import admin
from django.db import models


class YourModel(models.Model):
    # Couple of fields


admin.register(YourModel)
# ^^^ Can be right in your models.py.

If you want something more, you can tweak by coupling your model with an “Admin” and giving that some attributes. Customarily, you’d place those admins in a separate file, admin.py, next to your models.py. So something like:

from django.contrib import admin
from my_app.models import ProgramFile  # Just an example.


class ProgramFileAdmin(admin.ModelAdmin):
    list_display = ('title', 'last_modified',
        'last_modified_by', 'file', 'public',
        'program',)
    list_filter = ('public', 'program',)
    search_fields = ('textual_content', 'file')
    readonly_fields = ('textual_content',
                       'last_modified_by',
                       'last_modified')
    ordering = ('title',)

...

admin.site.register(ProgramFile, ProgramFileAdmin)

I’ll leave the specifics of those list_filter objects to your own google skills. Just look in the Django documentation and see what’s possible. Neat.

Admin access

First things first: if you’re The Real Admin of Your Django Site, you can see and do all, of course.

If you want to give someone else access to Django’s build-in admin interface, you must switch on one checkbox for that user: is_staff. (And of course the user should not be set to inactive.)

If you register a model with the admin, this automatically generates three permissions named after the app and the model: an add, a change and a delete permission. For the app “delta” and a model “program”, the add permission would be delta.add_program. Both the app name and the model name are lowercased.

There is one main check that determines whether you see a certain model at all when you’re allowed to go to the admin interface: you have to have one of those three automatically generated add/change/delete permissions to be able to do something with the model and thus to see the model in the admin interface.

The add/change/delete permissions have different effects on the admin interface:

  • The add permission gives you a “plus” to add new items. If this is the only permission you have, the admin is thus pretty bare-bones.
  • The change permission is the big one. Once you can change things, you also see the list of instances in the admin interface so that you can click on ‘em to change ‘em! The change permission effectively doubles as a view permission as far as the admin interface is concerned.
  • The delete permission gives you a “delete” button once you view an actual model instance or list of model instances. Just the delete permission on its own won’t give you a list of items that you can delete in the admin! You need to have the change permission to see that list!

When it comes to restricting users inside the admin interface, you have three basic options:

  • If a user hasn’t gotten its is_staff flag set: no admin! You simply don’t get in. Simple and effective.
  • If a model isn’t registered with the admin, there’s no such model in the admin. Plain and simple.
  • Don’t give a user any of the automatic add/change/delete permissions and that user won’t see the model in the admin.

Filtering objects in the admin

The admin by defaults shows all available instances of a model. But that isn’t a good idea when you’ve build in some additional security into your models.

For instance if you’ve got a couple of “Page” objects in your database: each with a user that owns it. And you don’t want other users to see/edit/delete another user’s pages.

It was only two days ago that I discovered that this is actually possible with django! You probably have a PageAdmin class in your admin.py with some settings and tweaks for your Page model. Just add a .queryset() method to that PageAdmin:

....

class PageAdmin(admin.ModelAdmin):
    ...

    def queryset(self, request):
        """Limit Pages to those that belong to the request's user."""
        qs = super(PageAdmin, self).queryset(request)
        if request.user.is_superuser:
            # It is mine, all mine. Just return everything.
            return qs
        # Now we just add an extra filter on the queryset and
        # we're done. Assumption: Page.owner is a foreignkey
        # to a User.
        return qs.filter(owner=request.user)

And that’s it! An admin’s .queryset() method takes a request as parameter! This means we can do all the filtering (on path, on user, on whatever) that we want.

Restricting fields based on the request

Let’s say there’s also a Picture model, also with an owner foreignkey to a user. And a Page has a foreignkey to a picture.

This means that you only want to show the current user’s pictures in the foreignkey dropdown in the page’s admin form:

....

class PageAdmin(admin.ModelAdmin):
    ...

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        """Limit choices for 'picture' field to only your pictures."""
        if db_field.name == 'picture':
            if not request.user.is_superuser:
                kwargs["queryset"] = Picture.objects.filter(
                    owner=request.user)
        return super(PageAdmin, self).formfield_for_foreignkey(
            db_field, request, **kwargs)

Pretty handy stuff! Happy to have discovered it! There’s probably even more handy stuff in Django just waiting to be discovered. And lots of that handy stuff is, of course, already in heavy use by those who are truly experienced or those that read all of Django’s on-line documentation :-) It is at times like this that I discover that I’ve only got two solid years of Django experience :-)

Tram between Utrecht and Nieuwegein (the Netherlands)
blog comments powered by Disqus
 
vanrees.org logo

About me

My name is Reinout van Rees and I work a lot with Python (programming language) and Django (website framework). I live in The Netherlands and I'm happily married to Annie van Rees-Kooiman.

Weblog feeds

Most of my website content is in my weblog. You can keep up to date by subscribing to the automatic feeds (for instance with Google reader):