Django:验证多对多通过模型

时间:2010-09-21 11:08:52

标签: django validation many-to-many

我有以下模型(简化示例):

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实例,不能有多个具有相同Role和Active的User。 所以这是允许的:

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完成此操作,因为它仅在active为True时计数。我试着写一个自定义的干净方法(就像我做的here)。但是,当您保存Book并且它在每个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回应Manoj Govindan:

我的admin.py(为了清晰起见,简化版):

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

class PermissionInline(admin.TabularInline):
    model = Permission

在shell中,您的验证将起作用。因为您首先必须创建工作簿实例,然后逐个创建所有权限实例:http://docs.djangoproject.com/en/1.2/topics/db/models/#extra-fields-on-many-to-many-relationships。因此,在shell中,如果添加2个Permission实例,则第2个Permission实例在第2个验证时已经保存,因此验证工作正常。

但是,当您使用Admin界面并通过书籍用户内联同时添加所有book.users实例时,我相信它会在保存所有book.users实例之前先对所有book.users实例进行所有验证。 当我尝试它时,验证不起作用,它只是在应该存在ValidationError时成功而没有错误。

3 个答案:

答案 0 :(得分: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(...)

请注意,这不是一个完整的解决方案,而是指示我如何做类似的事情。

答案 1 :(得分: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]})

我使用我的一个项目的管理应用程序进行了一些基本测试,它似乎有效。

答案 2 :(得分:0)

  

现在无法使用unique_together完成此操作,因为它仅在active为True时计数。

最简单的方法是将active的类型从BooleanField更改为CharField。在'{1}}中存储'Y'和'N'。 这样您就可以使用内置的active