2010-04-10 71 views
22

我想在這裏會討論類似的問題,但我找不到它。如何根據用戶限制django-admin中的字段?

假設我有一位編輯和一位主管。我希望編輯能夠添加新內容(例如新聞稿),但在發佈之前必須由主管確認。

當編輯器列出所有項目時,我想將模型上的某些字段(如'ack'字段)設置爲只讀(所以他可以知道什麼已經被刪除,哪些還在等待批准),但是主管應該能夠改變一切(list_editable將是完美

這個問題可能的解決方案是什麼?

回答

15

我覺得還有一個更簡單的方法來做到這一點:

客戶,我們有博客的同樣的問題 - 後

博客/ 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) 

通過這種方式,您可以在django中使用custom permission的api,並且您可以覆蓋保存模型或獲取查詢集的方法(如果必須的話)。在方法has_approval_permission中,您可以定義用戶何時可以或不可以做什麼的邏輯。

+0

你可能是指 self.exclude = [ '批准']在get_form (),也有在changelist_view(小毛刺) ;) 謝謝,這看起來不錯,與T.位組合Stone的回答,這正是我一直在尋找的東西:) – minder 2010-04-11 08:59:16

+0

如果我有'對象沒有屬性'COOKIES'呢? – andi 2014-02-13 12:18:05

+0

應該將權限註冊到其他地方,以便在管理模塊中可見? – andi 2014-02-13 12:21:18

2

我有一個這樣的系統,就像我剛完成的一個項目一樣。將有大量的工作放在了一起,但這裏有一些,我不得不讓我的系統工作的組成部分:

  • 您需要一種方法來定義一個編輯器和監事。這可以通過以下三種方式完成:1.通過具有定義管理程序的M2M字段[並假定每個有讀/寫權限的人都是編輯器]; 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'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作爲關鍵字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) 
... 

這樣,你是不是修改每個請求上的PostAdmin單例;您只需傳遞構建父代所需的適當關鍵字參數並返回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 2017-06-21 14:28:45

+0

好的。我更新了我的答案。 – mixxorz 2017-06-21 15:58:02