Show Django AdminSite fields depending on the current user
Introduction
The auto-created AdminSite in Django is a nifty feature for creating an administrative view of your model classes. Somehow it is not perfect, even if you want to customized it. To guide you to my problem and solution you've to know, that I implemented the SoftDelete behaviour for model classes as described by Greg Allard. It simply adds a 'deleted' field to each model marking it as deleted if its deleted in the admin site (or in some application) but never really gets deleted from the database. So in the admin site there is this field 'deleted' displayed, if you have configured it.
The Problem
My project has 2 different 'usergroups', the editors and the superusers. Superusers should see the 'deleted' fields, the others not. Unfortunatly that is not possible out of the box with Django.
The solution
I'm not the first one struggling with the user dependent display of model fields. Luke Plant found a solution for an older version of Django dealing with the field display in the add/change view. Later he updated the thing as the Django version grew. I adapted some lines of his last code for the add/change view and wrote something new to also handle the history view (that lists all model ob jects). I divided it into 2 subclasses of admin.ModelAdmin for better abstraction: one for the handling the user dependent queryset for SoftDelete and one for the display of the fields.
SoftDeleteModelAdmin
As the superusers should see the deleted field and the others not, we'll only query for the field if there is a superuser. Quite logical. The class uses the proper queryset and also returns a deleted objects if directly requested (beautyfied code @ snipt.org):
class SoftDeleteModelAdmin(admin.ModelAdmin):
def queryset(self, request):
if request.user.is_superuser:
qs = self.model._default_manager.all_with_deleted()
else:
qs = self.model._default_manager.all()
ordering = self.ordering or ()
if ordering:
qs = qs.order_by(*ordering)
return qs
## If a specific record was requested, return it even if it's deleted
def get(self, *args, **kwargs):
return self.all_with_deleted().get(*args, **kwargs)
## If pk was specified as a kwarg, return even if it's deleted
def filter(self, *args, **kwargs):
if 'pk' in kwargs:
return self.all_with_deleted().filter(*args, **kwargs)
return self.get_query_set().filter(*args, **kwargs)
UserDependentModelAdmin
For the display of the field depending on the current user you have to inherit from the UserDependentModelAdmin class as listed below. Adjust the ModelAdmin class and add 'list_display_editors' (for all users != superuser) and 'list_display_superuser' (for superusers) tuples with the field names for the appropriate user (I'll only use a field called 'name' and the 'deleted' field):
class SomeAdmin(SoftDeleteModelAdmin, UserDependentModelAdmin):
list_display = ()
# editor fields and fieldsets
list_display_editor = ('name',)
fieldsets_editor = [(None, { 'fields': ['name'] })]
# superuser fields and fieldsets
list_display_superuser = list_display_editor + ('deleted',)
fieldsets_superuser = [(None, { 'fields': ['name', 'deleted'] })]
All other magic is done by the UserDependentModelAdmin class. It simply changes the list_display field in the history view depending on the user and sets some prerequisites. The 'get_fieldset' method returns the fields for the add/change view, this is nearly the same code Luke Plant wrote. (beautified code @ snipt.org)
csrf_protect_m = method_decorator(csrf_protect)
## User dependent admin model.
# This class allows to specify tuples of the listed fields and displayed fieldsets
# for the editors and superusers.
class UserDependentModelAdmin(admin.ModelAdmin):
## Class constructor, adding actions an/or links for the user fields.
# @param model the model the admin site is created for
# @param admin_site the admin site
def __init__(self, model, admin_site):
# this is performed regularly on the list_display tuple
# do the same for the additional list_display fields
self._add_actions_and_links_for_list_display('editor')
self._add_actions_and_links_for_list_display('superuser')
super(UserDependentModelAdmin, self).__init__(model, admin_site)
## Adds actions and/or links for the list_display tuple of the given user
# definid list_type.
# @param list_type the list_display type, currently 'editor' for editors or 'superuser' for superuser
def _add_actions_and_links_for_list_display(self, list_type):
if 'action_checkbox' not in getattr(self, 'list_display_' + list_type) and self.actions is not None:
setattr(
self,
'list_display_' + list_type,
['action_checkbox'] + list(getattr(self, 'list_display_' + list_type))
)
if not self.list_display_links:
for name in getattr(self, 'list_display_' + list_type):
if name != 'action_checkbox':
self.list_display_links = [name]
break
## View for listing the models.
# Adjusts the list_display tuple according to the current user.
# @param request the sent request
# @param extra_content an additional context
@csrf_protect_m
def changelist_view(self, request, extra_context=None):
if request.user.is_superuser:
self.list_display = self.list_display_superuser
else:
self.list_display = self.list_display_editor
return super(UserDependentModelAdmin, self).changelist_view(request, extra_context=extra_context)
## Returns the fieldsets depending on the user.
# @param request the sent request
# @param obj the model object if already persisted
def get_fieldsets(self, request, obj=None):
user = request.user
if user is None or user.is_anonymous():
# never get here normally
return ()
else:
if user.is_superuser:
return self.fieldsets_superuser
else:
return self.fieldsets_editor

Another quite flexible solution:
http://stackoverflow.com/questions/687829/django-overriding-get-form-to-customize-admin-forms-based-on-request/6166646#6166646
IMHO one should use ModelForms to limit the fields in order to avoid problems.
Thanks for the post, I’ll try it!