У меня есть модель, которая имеет отношение многие ко многим сама с собой.
Я хочу создать проверку модели (моделей), которая не позволила бы группе быть собственной подгруппой или подгруппой ее подгрупп и т. д. Цель состоит в том, чтобы предотвратить ситуацию, которая может привести к циклу/бесконечной рекурсии.
Я попытался реализовать это в методе модели 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, потому что это то, что доступно в репозитории EPEL Fedora для RHEL6. Если решение доступно, но требует обновления до версии 1.3, у меня нет проблем с обновлением. Я также использую python 2.6.6, потому что он доступен в RHEL6 от RedHat. Я бы предпочел избежать обновления Python, но я очень сомневаюсь, что это актуально.
@staticmethod
с именемcircular_checker
, возможно, вы сможете найти вдохновение в его коде. - person j_syk   schedule 03.08.2011