2010-09-21 57 views
1

我有以下型號(簡化的例子):Django的:驗證一個多到許多通過模型

class Book(models.Model): 
users = models.ManyToManyField(User, through=Permission) 

class Permission(models.Model): 
user = models.ForeignKey(User) 
role = models.ForeignKey(Group) 
active = models.BooleanField() 
book = models.ForeignKey(Book) 

我需要的是,對於一個Book實例不能有與多個用戶相同的角色和主動。 所以這是允許的:

Alice, Admin, False (not active), BookA 
Dick, Admin, True (active), BookA 
Chris, Editor, False (not active), BookA 
Matt, Editor, False (not active), BookA 

但是,這是不允許的:

Alice, Admin, True (active), BookA 
Dick, Admin, True (active), BookA 

現在,這不能與unique_together完成,因爲它只有當活動是真正重要的。我試圖寫一個自定義的清潔方法(就像我已經完成here)。但是,當您保存一本書並且它在每個Permission上運行驗證時,似乎已經驗證的Permission實例在所有驗證完成之後纔會保存。這是有道理的,因爲你不希望他們被保存以防萬一不能確認。

任何人都可以告訴我是否有辦法執行上述驗證?

P.S.我可以想象使用保存點功能(http://docs.djangoproject.com/en/1.2/topics/db/transactions/),但我只是真的想把它作爲最後的手段。
也許你可以做點像:unique_together = [[book, role, active=1],]

編輯2010年9月23日14:00迴應馬諾Govindan:

我的admin.py(簡本爲清楚起見):

class BookAdmin(admin.ModelAdmin): 
    inlines = (PermissionInline,) 

class PermissionInline(admin.TabularInline): 
    model = Permission 

在您的驗證工作將外殼。因爲您首先必須創建書本實例,然後逐個創建所有權限實例:http://docs.djangoproject.com/en/1.2/topics/db/models/#extra-fields-on-many-to-many-relationships。因此,在shell中,如果添加2個Permission實例,則第2個正在驗證的時間已經保存了第1個Permission實例,因此驗證工作。

但是,當您使用Admin界面並通過書籍用戶內聯同時添加所有book.users實例時,我相信它會先保存所有book.users實例上的所有驗證。 當我嘗試它時,驗證不起作用,它應該有一個ValidationError時沒有錯誤就成功了。

回答

0

做到這一點的一種方法是使用新穎的model validation。具體而言,您可以將自定義validate_unique方法添加到Permission模型以實現此效果。對於例如

from django.core.exceptions import ValidationError, NON_FIELD_ERRORS 

class Permission(models.Model): 
    ... 
    def validate_unique(self, exclude = None): 
     options = dict(book = self.book, role = self.role, active = True) 
     if Permission.objects.filter(**options).count() != 0: 
      template = """There cannot be more than one User of with the 
       same Role and Active (book: {0})""" 
      message = template.format(self.book) 
      raise ValidationError({NON_FIELD_ERRORS: [message]}) 

我做了一些基本的測試,使用我的一個項目的管理應用程序,它似乎工作。

+1

感謝您的回覆!不幸的是,它不起作用,因爲當你第一次創建實例時,'Permission.objects.filter(** options).count()'將總是返回0.你在Book Permission Inline中輸入的權限有尚未保存,因此它們不會在數據庫中,因此您無法使用'Permission.objects.filter'來捕獲它們。我測試過了,這確實是發生了什麼事。 – Heyl1 2010-09-23 10:13:39

+0

@ Heyl1:恐怕我不明白。不應該第一次創建條目?否則,權限將如何創建? – 2010-09-23 10:19:39

+0

書本實例和book.users實例在驗證之後纔會保存。這是有道理的,因爲如果驗證返回一個ValidationError,它們不應該被保存。 Permission.objects.filter將僅返回已保存的內容,因此永遠不會包含您現在創建的新Permission對象。因此,此驗證將始終返回OK(除非更新實例,否則它將檢查舊版book.users,它可能在此更新版本中發生了更改,所以現在驗證只是不正確)。 – Heyl1 2010-09-23 11:31:17

0

現在,這不能與unique_together一起完成,因爲它只在active爲True時纔算。

最簡單的方法是將active的類型從BooleanField更改爲CharField。將「Y」和「N」存儲在active中。 這樣你可以使用內置的unique_together = [[book, role, active],]

1

你可以使用信號來防止保存無效的數據:我仍然在一個很好的解決方案,以便如何讓驗證以很好的方式在管理員。

@receiver(models.signals.m2m_changed, sender=Book.users.through) 
def prevent_duplicate_active_user(sender, instance, action, reverse, model, pk_set, **kwargs): 
    if action != "pre_add": 
     return 
    if reverse: 
     # Editing the Permission, not the Book. 
     pass 
    else: 
     # At this point, look for already saved Users with the book/active. 
     if instance.permissions.filter(active=True).exists(): 
      raise forms.ValidationError(...) 

請注意,這不是一個完整的解決方案,但它是一個指針,我如何做類似的事情。