それ自体と多対多の関係を持つモデルがあります。
グループがそれ自体のサブグループまたはそのサブグループのサブグループなどになるのを防ぐモデルの検証を作成したいと考えています。目的は、ループ/無限再帰が発生する可能性がある状況を防ぐことです。
以下に示すように、モデルの 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 へのアップグレードが必要な場合、アップグレードに問題はありません。また、RedHat の RHEL6 で利用できる python 2.6.6 も使用しています。Python のアップグレードは避けたいのですが、関連性があるかどうかは非常に疑わしいです。