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

おかげで先にあなたが提供するどのような援助のための時間の。

私はトランザクションを管理するために使用しているメカニズムに基づいて判断できない場合は、RHEL6用のfedora EPELリポジトリで利用できるので、現在django 1.2を使用しています。解決策がありますが、1.3へのアップグレードが必要な場合は、アップグレードに問題はありません。私はRed HatからRHEL6で入手できるものだから、Python 2.6.6も使用しています。私はむしろPythonのアップグレードを避けたいですが、私はそれが適切であることを非常に疑う。

+0

多対多の関係を使用する理由を説明してください。私は木のような構造を持ち、このパターンを使用します。このノードが空の場合、すべてのノードに属性 "parent"があります。これはルートノードです。または、これを使用することができますhttps://github.com/django-mptt/django-mptt/ – guettli

+0

私のアプリケーションでは、ツリーではありません。グループは他の多くのグループに含めることができ、グループには多数のグループを含めることができます。これは、無関係のコードをたくさん持っている人に負担をかけずに問題を表現しているアプリケーションの簡略化されたバージョンです。そのため、サブグループ関係に関するパラメータを定義するスルーモデルが必要です。 – mkomitee

+1

[私の質問](http://stackoverflow.com/questions/5795742/django-mptt)にリンクしている[django_dag](https://github.com/elpaso/django-dag)プロジェクトを見ると、 -old-multiple-parents)の場合、models.pyの行には 'circular_checker'という名前の' @ staticmethod'があることに気づくでしょう。あなたは彼のコードでいくつかのインスピレーションを見つけることができます。 –

答えて

0

"対象"にする必要があります。本当にあなたのコードでこのループの中にいるのでしょうか?それは、それがレベルをスキップするように思われる。

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