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.
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.
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 .get_queryset()
method
to that PageAdmin
:
....
class PageAdmin(admin.ModelAdmin):
...
def get_queryset(self, request):
"""Limit Pages to those that belong to the request's user."""
qs = super().get_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 .get_queryset()
method takes a request as parameter!
This means we can do all the filtering (on path, on user, on whatever) that we
want.
2022 update: I changed .queryset()
to .get_queryset()
in the
example above after someone helpfully emailed me about it. I don’t know if I
made a mistake originally or whether django djanged this after 2011, but in
any case… this blog post is at least valid again for the next ten years :-)
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().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 :-)
My name is Reinout van Rees and I program in Python, I live in the Netherlands, I cycle recumbent bikes and I have a model railway.
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):