2011-08-03 81 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)