2011-08-03 126 views
1

我有一个模型与自身有多对多的关系。django模型中的循环检测

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

我已经尝试在模型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升级,但我非常怀疑它是相关的。

+0

你能解释一下为什么使用多对多关系吗?我有树状结构并使用此模式:如果此父项为空,则每个节点都有一个“父级”属性,这是一个根节点。或者你可以使用这个https://github.com/django-mptt/django-mptt/ – guettli

+0

在我的应用程序中,它不是一棵树。一个团队可能会被包含在其他许多团队中,并且一个团队可能会包含许多团队。这也是我的应用程序的一个简化版本,它表达了这个问题,并且没有为每个人提供大量不相关的代码,这就是为什么它需要一个通过模型来定义关于子群关系的参数。 – mkomitee

+1

如果您看[django_dag](https://github.com/elpaso/django-dag)项目,我在[我的问题]中链接(http://stackoverflow.com/questions/5795742/django-mptt -and-multiple-parents),你会注意到在models.py第195行有一个名为'circular_checker'的'@ staticmethod',你可以在他的代码中找到一些灵感。 –

回答

0

应该“目标”。真的在你的代码中循环?看起来这会让它跳过一个关卡。

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