2010-04-10 2 views
22

Я предполагаю, что подобная проблема была бы обсуждена здесь, но я не мог ее найти.Как ограничить поля в django-admin в зависимости от пользователя?

Предположим, у меня есть редактор и супервайзер. Я хочу, чтобы редактор мог добавлять новый контент (например, новостной пост), но перед публикацией он должен быть подтвержден Супервизором.

Когда редактор перечисляет все элементы, я хочу установить некоторые поля в моделях (например, поле «ack») как только для чтения (чтобы он мог знать, что было сделано и что еще ждет одобрения), но Супервизор должен иметь возможность изменить все (list_editable будет perfect)

Каковы возможные решения этой проблемы?

ответ

15

Я думаю, что есть более простой способ сделать это:

Гостя мы имеем ту же проблему блог -Post

блог/models.py:

Class Blog(models.Model): 
    ... 
    #fields like autor, title, stuff.. 
    ... 

class Post(models.Model): 
    ... 
    #fields like blog, title, stuff.. 
    ... 
    approved = models.BooleanField(default=False) 
    approved_by = models.ForeignKey(User) 
    class Meta: 
     permissions = (
      ("can_approve_post", "Can approve post"), 
     ) 

И магия в админке:

блог/admin.py:

... 
from django.views.decorators.csrf import csrf_protect 
... 
def has_approval_permission(request, obj=None): 
    if request.user.has_perm('blog.can_approve_post'): 
     return True 
    return False 

Class PostAdmin(admin.ModelAdmin): 
    @csrf_protect 
    def changelist_view(self, request, extra_context=None): 
     if not has_approval_permission(request): 
      self.list_display = [...] # list of fields to show if user can't approve the post 
      self.editable = [...] 
     else: 
      self.list_display = [...] # list of fields to show if user can approve the post 
     return super(PostAdmin, self).changelist_view(request, extra_context) 
    def get_form(self, request, obj=None, **kwargs): 
     if not has_approval_permission(request, obj): 
      self.fields = [...] # same thing 
     else: 
      self.fields = ['approved'] 
     return super(PostAdmin, self).get_form(request, obj, **kwargs) 

Таким образом вы можете использовать api custom permission в django, и вы можете переопределить методы сохранения модели или получить запрос, если вам нужно. В методе has_approval_permission вы можете определить логику того, когда пользователь может или не может что-то сделать.

+0

вы, вероятно, имел в виду self.exclude = [ 'утвержден'] в get_form() и есть также небольшой глюк в changelist_view() ;) Спасибо, это выглядит великолепно и в сочетании с битами от T. Ответ Стоуна, это именно то, что я искал :) – minder

+0

Что делать, если я получил 'объект не имеет атрибута 'COOKIES''? – andi

+0

должны ли разрешения регистрироваться где-нибудь еще, чтобы их можно было видеть в модуле администратора? – andi

2

У меня есть система вроде этого в проекте, который я только что закончил. Там будет много работы, чтобы поставить это вместе, но вот некоторые из компонентов, которые я должен был сделать мою работу системы:

  • Вам нужен способ, чтобы определить редактор и супервизора. Три способа сделать это: 1.) путем определения поля M2M, определяющего Supervisor [и предполагая, что все остальные, имеющие разрешение на чтение/запись, являются Редактором], 2.) создают 2 новые модели пользователей, которые наследуют пользователя [ вероятно, больше работы, чем необходимо] или 3.) используйте способность django.auth иметь класс UserProfile. Метод №1, вероятно, является наиболее разумным.

  • Как только вы можете определить, какой тип пользователя, вам нужен способ в целом обеспечить соблюдение авторизации, которую вы ищете. Я думаю, что лучший маршрут здесь, вероятно, является общей моделью администратора.

  • Наконец, вам понадобится модель типа «родитель», которая будет хранить разрешения для всех, что должно быть модерировано. Например, если у вас была модель Blog и модель BlogPost (при условии, что несколько блогов находятся на одном сайте), тогда Blog является родительской моделью (она может содержать разрешения того, кто ее одобряет). Однако, если у вас есть один блог и нет родительской модели для BlogPost, нам нужно место для хранения разрешений. Я нашел, что ContentType хорошо работает здесь.

Вот некоторые идеи в коде (непроверенные и более концептуальные, чем фактические).

Сделайте новое приложение под названием «модерированное», которое будет содержать наш общий материал.

moderated.models.py

class ModeratedModelParent(models.Model): 
    """Class to govern rules for a given model""" 
    content_type = models.OneToOneField(ContentType) 
    can_approve = models.ManyToManyField(User) 

class ModeratedModel(models.Model): 
    """Class to implement a model that is moderated by a supervisor""" 
    is_approved = models.BooleanField(default=False) 

    def get_parent_instance(self): 
     """ 
     If the model already has a parent, override to return the parent's type 
     For example, for a BlogPost model it could return self.parent_blog 
     """ 

     # Get self's ContentType then return ModeratedModelParent for that type 
     self_content_type = ContentType.objects.get_for_model(self) 
     try:    
      return ModeratedModelParent.objects.get(content_type=self_content_type) 
     except: 
      # Create it if it doesn't already exist... 
      return ModeratedModelParent.objects.create(content_type=self_content_type).save() 

    class Meta: 
     abstract = True 

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

Далее, мы можем реализовать нашу политику в админке, опять-таки через общую модель:

moderated.admin.py

class ModeratedModelAdmin(admin.ModelAdmin): 

    # Save our request object for later 
    def __call__(self, request, url): 
     self.request = request 
     return super(ModeratedModelAdmin, self).__call__(request, url) 

    # Adjust our 'is_approved' widget based on the parent permissions 
    def formfield_for_dbfield(self, db_field, **kwargs): 
     if db_field.name == 'is_approved': 
      if not self.request.user in self.get_parent_instance().can_approve.all(): 
       kwargs['widget'] = forms.CheckboxInput(attrs={ 'disabled':'disabled' }) 

    # Enforce our "unapproved" policy on saves 
    def save_model(self, *args, **kwargs): 
     if not self.request.user in self.get_parent_instance().can_approve.all(): 
      self.is_approved = False 
     return super(ModeratedModelAdmin, self).save_model(*args, **kwargs) 

После их установки и работы, мы можем повторно используйте их во многих моделях, как я нашел, когда вы добавляете структурированные разрешения для чего-то вроде этого, вы легко хотите его для многих других вещей.

Скажите, например, что у вас есть модель новостей, вам просто нужно было бы унаследовать от модели, которую мы только что создали, и вы хороши.

# in your app's models.py 
class NewsItem(ModeratedModel): 
    title = models.CharField(max_length=200) 
    text = models.TextField() 


# in your app's admin.py 
class NewsItemAdmin(ModeratedModelAdmin): 
    pass 

admin.site.register(NewsItem, NewsItemAdmin) 

Я уверен, что я сделал некоторые ошибки кода и ошибки там, но мы надеемся, что это может дать вам некоторые идеи, чтобы действовать в качестве стартовой площадки для все, что вы решили реализовать.

Последнее, что вам нужно сделать, что я оставлю вам, - это реализовать фильтрацию для элементов is_approved. (Т. Е вы не хотите, одобренные ООН пункты будут перечислены в разделе новостей, верно?)

2

Проблема, использующая подход, описанный @ diegueus9, заключается в том, что ModelAdmin действует как одноэлементный и не имеет значения для каждого запроса. Это означает, что каждый запрос изменяет тот же объект ModelAdmin, к которому обращаются другие запросы, что не является идеальным. Ниже предлагаемые решения по @ diegueus9:

# For example, get_form() modifies the single PostAdmin's fields on each request 
... 
class PostAdmin(ModelAdmin): 
    def get_form(self, request, obj=None, **kwargs): 
     if not has_approval_permission(request, obj): 
      self.fields = [...] # list of fields to show if user can't approve the post 
     else: 
      self.fields = ['approved', ...] # add 'approved' to the list of fields if the user can approve the post 
... 

Альтернативный подход будет проходить fields в качестве ключевого слова арг к get_form() методе родителя, как так:

... 
from django.contrib.admin.util import flatten_fieldsets 

class PostAdmin(ModelAdmin): 
    def get_form(self, request, obj=None, **kwargs): 
     if has_approval_permission(request, obj): 
      fields = ['approved'] 
      if self.declared_fieldsets: 
       fields += flatten_fieldsets(self.declared_fieldsets) 

      # Update the keyword args as needed to allow the parent to build 
      # and return the ModelForm instance you require for the user given their perms 
      kwargs.update({'fields': fields}) 
     return super(PostAdmin, self).get_form(request, obj=None, **kwargs) 
... 

Таким образом, вы не изменяете однопользовательский адрес PostAdmin по каждому запросу; вы просто передаете соответствующие ключевые слова args, необходимые для сборки и возврата ModelForm из родителя.

Это, вероятно, стоит посмотреть на get_form() метода на базовом ModelAdmin для получения дополнительной информации: https://code.djangoproject.com/browser/django/trunk/django/contrib/admin/options.py#L431

2

Начиная с Django 1.7, теперь вы можете использовать get_fields крючок, что делает его намного проще реализовать условные поля.

class MyModelAdmin(admin.ModelAdmin): 
    ... 

    def get_fields(self, request, obj=None): 
     fields = super(MyModelAdmin, self).get_fields(request, obj) 
     if request.user.is_superuser: 
      fields += ('approve',) 

     return fields 
+0

Отличная техника, но 'fields = self.fields' привела к' None' для меня.Я изменил эту строку на 'fields = super (MyModelAdmin, self) .get_fields (request, obj)', и он работал как шарм. – PaulR

+0

Nice catch. Я обновил свой ответ. – mixxorz

Смежные вопросы