Доступ только к собственным записям из Django админки

Иногда нужно, чтобы каждый пользователь админки Django мог видеть и редактировать только свои записи. По умолчанию такая возможность не доступна, но с помощью нехитрых действий можно всё исправить.
Суть подхода заключается в добавлении к нашей модели внешнего ключа на модель пользователя Django, а затем переопределение некоторых методов класса, отвечающего за генерацию админки.

Администратор сайта видит все записи и может менять их владельцев (или создавать объекты от чужого имени). Остальные пользователи, имеющие доступ в панель администрирования, могут создавать и редактировать только свои записи.

models.py

<pre># -*- coding: utf-8 -*-
from django.db import models
from django.contrib.auth.models import User

class Entry(models.Model):
   user = models.ForeignKey(User, verbose_name=u"пользователь", blank=True, null=True)
   title = models.CharField(u"заголовок", max_length=100)

   def __unicode__(self):
      return self.title

   class Meta:
      verbose_name = u"""запись"""
      verbose_name_plural = u"""записи"""</pre>


Обратите внимание на атрибуты blank и null у поля user.

Так как поле будет заполняться автоматически, то нужно разрешить Django оставлять его пустым (blank=True).

null=True необходим для корректной работы нашего кода. Вообще, поле всегда будет содержать id пользователя и было бы правильно указать null=False, но из-за внутреннего устройства Django такой подход не пройдет.

admin.py

<pre># -*- coding: utf-8 -*-
from django.contrib import admin
from app.models import Entry

class EntryAdmin(admin.ModelAdmin):

   # Поля, доступные для редактирования простым пользователям.
   # Разрешаем только title
   user_fieldsets = (
     (None, {
         'classes': ('wide',),
         'fields': ('title',)
      }),
   )

   list_display = ['title', 'user']
   raw_id_list_displayfields = ('user',)
   search_fields = ['title', 'user__username']

   def save_model(self, request, obj, form, change):
      if form.is_valid():
         if not request.user.is_superuser or not form.cleaned_data["user"]:
            obj.user = request.user
            obj.save()
         elif form.cleaned_data["user"]:
            obj.user = form.cleaned_data["user"]
            obj.save()

   def preprocess_list_display(self, request):
      if 'user' not in self.list_display:
         self.list_display.insert(self.list_display.__len__(), 'user')
      if not request.user.is_superuser:
         if 'user' in self.list_display:
            self.list_display.remove('user')

   def preprocess_search_fields(self, request):
      if 'user__username' not in self.search_fields:
         self.search_fields.insert(self.search_fields.__len__(), 'user__username')
      if not request.user.is_superuser:
         if 'user__username' in self.search_fields:
            self.search_fields.remove('user__username')

   def changelist_view(self, request, extra_context=None):
      self.preprocess_list_display(request)
      self.preprocess_search_fields(request)
      return super(EntryAdmin, self).changelist_view(request)

   def queryset(self, request):
      if request.user.is_superuser:
         return super(EntryAdmin, self).queryset(request)
      else:
         qs = super(EntryAdmin, self).queryset(request)
         return qs.filter(user=request.user)

   def get_fieldsets(self, request, obj=None):
      if request.user.is_superuser:
         return super(EntryAdmin, self).get_fieldsets(request, obj)
      return self.user_fieldsets

admin.site.register(Entry, EntryAdmin)</pre>

Разберемся с кодом.

Метод save_model отвечает за автозаполнение поля user. Несколько несложных условий подставляют необходимый id пользователя.

Метод preprocess_list_display добавляет колонку user в список объектов, если его просматривает администратор и удаляет колонку для обычных пользователей. Обратите внимание, что сперва мы проверяем, есть ли запись в list_display и если её нет, то добавляем, а потом уже смотрим кто просматривает список.
При создании экземпляра EntryAdmin все его поля становятся глобальными для процесса, поэтому если мы удалим колонку list_display для обычного пользователя и не добавим для администратора, то администратор колонку не увидит. Именно поэтому мы каждый раз её вставляем в список.

Метод preprocess_search_fields делает тоже, что и preprocess_list_display, но только для search_fields. Фактически мы дали возможность администратору не только видеть автора записи, но и осуществлять поиск по его username.

Оба предыдущих метода вызываются из переопределенного changelist_view.

Метод queryset возвращает все записи для администратора и персональные записи для обычных пользователей. При изменении записи, обращение к ней идет через этот самый queryset, поэтому если пользователь пытается напрямую получить доступ к чужому объекту (введет в строку браузера что-то вроде /admin/app/entry/3/), то ему вернется HTTP 404.

Метод get_fieldsets подключает user_fieldsets в случае обычных пользователей или же генерирует fieldsets на основе данных модели Entry.

Вот и все, осталось только назначить пользователям необходимые права «can add entry», «can change entry» и т.д. А также поставить галочку напротив поля «Статус персонала» на странице редактирования данных пользователя. «Статус персонала» разрешит человеку входить в административную часть сайта.


0 комментариев

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.