Я предполагаю, что подобная проблема была бы обсуждена здесь, но я не мог найти его.
Давайте предположим, что у меня есть Редактор и Супервизор. Я хочу, чтобы Редактор смог добавить новое содержание (например, сообщение новостей), но перед публикацией оно должно быть подтверждено Супервизором.
Когда Редактор перечисляет все объекты, я хочу установить некоторые поля на моделях (как 'ack' поле) как только для чтения (таким образом, он мог знать то, что было ack'ed и что все еще ожидает одобрение), но Супервизор должен смочь измениться, все (list_editable было бы прекрасно),
Что возможные решения к этой проблеме?
Я думаю, что есть более простой способ сделать это:
Гость, у нас та же проблема, что и в Blog-Post
blog / 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"),
)
И волшебство в админке:
blog / 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
вы можете определить логику того, когда пользователь может или не может что-то делать.
У меня есть система, похожая на эту, в проекте, который я только что закончил. Будет много работы, чтобы собрать ее вместе, но вот некоторые из компонентов, которые я должен был сделать, чтобы моя система работала:
Вам нужен способ определить редактора и супервизора. Это можно сделать тремя способами: 1.) с помощью поля M2M, определяющего супервайзера [и предполагая, что все остальные, имеющие право читать/писать, являются редакторами], 2.) сделать 2 новые модели пользователей, которые наследуются от User [вероятно, больше работы, чем нужно] или 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'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
. (т.е. вы же не хотите, чтобы неодобренные элементы попадали в раздел новостей, верно?)