Подтвердить что ты не робот

Как ограничить поля в django-admin в зависимости от пользователя?

Я предполагаю, что подобная проблема была бы обсуждена здесь, но я не мог ее найти.

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

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

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

4b9b3361

Ответ 1

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

У гостя у нас та же проблема Blog-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 пользовательские разрешения в django, и вы можете переопределить методы сохранения модели или получить запрос если вам нужно. В методе has_approval_permission вы можете определить логику того, когда пользователь может или не может что-то сделать.

Ответ 2

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

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

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

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

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

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

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 type
        For example, for a BlogPost model it could return self.parent_blog
        """

        # Get self 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 models.py
class NewsItem(ModeratedModel):
    title = models.CharField(max_length=200)
    text = models.TextField()


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

admin.site.register(NewsItem, NewsItemAdmin)

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

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

Ответ 3

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

# For example, get_form() modifies the single PostAdmin 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 в качестве ключевого слова arg в родительский метод 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)
...

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

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

Ответ 4

Запустив 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