django 模型中的循环检测

发布于 2024-11-27 18:00:54 字数 2438 浏览 2 评论 0原文

我有一个与其自身具有多对多关系的模型。

我想在模型上创建一个验证,这将防止一个组成为它自己的子组,或其子组的子组等。目的是防止出现可能导致循环/无限递归的情况。

我尝试在模型 clean() 方法中实现这一点,如下所示。

我还尝试使用事务在模型 save() 方法中实现此功能。

在这两种情况下,我最终都会遇到这样的情况:无效的更改(错误地)保存到数据库中,但如果我尝试对任一实例进行进一步更改,验证会检测到错误,但此时,错误数据已经在数据库中了。

我想知道这是否可能,如果可以,是否可以在模型验证中这样做,这样我就不必确保团队中的每个人都记得从他们将来创建的所有表单中调用这些验证。

不再拖延,代码:

class Group(models.Model):
    name = models.CharField(max_length=200)
    sub_groups = models.ManyToManyField('self', through='SubGroup', symmetrical=False)

    def validate_no_group_loops(self, seen=None):
        if seen is None:
            seen = []
        if self.id in seen:
            raise ValidationError("LOOP DETECTED")
        seen.append(self.id)
        for sub_group in self.target.all():
            sub_group.target.validate_no_group_loops(seen)

    # I thought I would use the standard validation mechanism in the clean()
    # method, but it appears that when I recurse back to the group I started 
    # with, I do so with a query to the database which retreives the data before
    # it's been modified. I'm still not 100% sure if this is the case, but
    # regardless, it does not work.
    def clean(self):
        self.validate_no_group_loops()

    # Suspecting that the problem with implementing this in clean() was that 
    # I wasn't testing the data with the pending modifications due to the 
    # repeated queries to the database, I thought that I could handle the
    # validation in save(), let the save actually put the bad data into the
    # database, and then roll back the transaction if I detect a problem.
    # This also doesn't work.
    def save(self, *args, **kwargs):
        super(Group, self).save(*args, **kwargs)
        try:
            self.validate_no_group_loops()
        except ValidationError as e:
            transaction.rollback()
            raise e
        else:
            transaction.commit()


class SubGroup(models.Model):
    VERBS = { '+': '+', '-': '-' }
    action = models.CharField(max_length=1, choices=VERBS.items(), default='+')
    source = models.ForeignKey('Group', related_name='target')
    target = models.ForeignKey('Group', related_name='source')

提前感谢您提供的任何帮助。

[编辑] 仅供参考,如果您无法根据我用来管理事务的机制来判断,我目前正在使用 django 1.2,因为这是 RHEL6 的 fedora EPEL 存储库中提供的版本。如果有可用的解决方案但需要升级到 1.3,我升级没有问题。我还使用 python 2.6.6,因为这是 RedHat 的 RHEL6 中提供的版本。我宁愿避免 python 升级,但我非常怀疑它是否相关。

I have an model which has a many to many relationship with itself.

I want to create a validation on the model(s) which would prevent a group from being it's own subgroup, or a subgroup of it's subgroups, etc. The objective is to prevent a situation which can result in a loop / infinite recursion.

I've tried implementing this in the model clean() method as shown below.

I've also tried implementing this in the model save() method using transactions.

In both situations, I've ended up in a situation where invalid changes are (incorrectly) saved to the database, but if I attempt to make further changes to either instance, the validations detect the error, but at that point, the bad data is already in the database.

I'm wondering if this is possible, and if so, if it's possible to do so in model validations so I don't have to make sure everyone on my team remembers to call these validations from all forms they create in the future.

Without further delay, the code:

class Group(models.Model):
    name = models.CharField(max_length=200)
    sub_groups = models.ManyToManyField('self', through='SubGroup', symmetrical=False)

    def validate_no_group_loops(self, seen=None):
        if seen is None:
            seen = []
        if self.id in seen:
            raise ValidationError("LOOP DETECTED")
        seen.append(self.id)
        for sub_group in self.target.all():
            sub_group.target.validate_no_group_loops(seen)

    # I thought I would use the standard validation mechanism in the clean()
    # method, but it appears that when I recurse back to the group I started 
    # with, I do so with a query to the database which retreives the data before
    # it's been modified. I'm still not 100% sure if this is the case, but
    # regardless, it does not work.
    def clean(self):
        self.validate_no_group_loops()

    # Suspecting that the problem with implementing this in clean() was that 
    # I wasn't testing the data with the pending modifications due to the 
    # repeated queries to the database, I thought that I could handle the
    # validation in save(), let the save actually put the bad data into the
    # database, and then roll back the transaction if I detect a problem.
    # This also doesn't work.
    def save(self, *args, **kwargs):
        super(Group, self).save(*args, **kwargs)
        try:
            self.validate_no_group_loops()
        except ValidationError as e:
            transaction.rollback()
            raise e
        else:
            transaction.commit()


class SubGroup(models.Model):
    VERBS = { '+': '+', '-': '-' }
    action = models.CharField(max_length=1, choices=VERBS.items(), default='+')
    source = models.ForeignKey('Group', related_name='target')
    target = models.ForeignKey('Group', related_name='source')

Thanks ahead of time for any assistance you may provide.

[edit] FYI, If you couldn't tell based on the mechanism I'm using to manage transactions, I'm currently using django 1.2 because that's what's available in the fedora EPEL repository for RHEL6. If a solution is available but it requires an upgrade to 1.3, I have no problem upgrading. I'm also using python 2.6.6 because that's what's available in RHEL6 from RedHat. I'd rather avoid a python upgrade, but I highly doubt that it's relevant.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

深白境迁sunset 2024-12-04 18:00:54

应该“瞄准”。真的在你的代码中的这个循环内吗?看起来这会让它跳过一个级别。

    for sub_group in self.target.all():
        sub_group.target.validate_no_group_loops(seen)

Should "target." really be inside this loop in your code? It seems like that would make it skip a level.

    for sub_group in self.target.all():
        sub_group.target.validate_no_group_loops(seen)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文