Обнаружение цикла в моделях 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, потому что это то, что доступно в репозитории EPEL Fedora для RHEL6. Если решение доступно, но требует обновления до версии 1.3, у меня нет проблем с обновлением. Я также использую python 2.6.6, потому что он доступен в RHEL6 от RedHat. Я бы предпочел избежать обновления Python, но я очень сомневаюсь, что это актуально.


person mkomitee    schedule 03.08.2011    source источник
comment
Не могли бы вы объяснить, почему вы используете отношение «многие ко многим»? У меня есть древовидные структуры, и я использую этот шаблон: у каждого узла есть родительский атрибут, если этот родитель пуст, это корневой узел. Или вы можете использовать этот github.com/django-mptt/django-mptt   -  person guettli    schedule 03.08.2011
comment
В моем приложении это не дерево. Группа может быть включена во многие другие группы, а в группу может быть включено много групп. Это также упрощенная версия моего приложения, которое выражает проблему, не обременяя всех большим количеством несвязанного кода, поэтому для определения параметров отношений подгруппы требуется сквозная модель.   -  person mkomitee    schedule 03.08.2011
comment
Если вы посмотрите на проект django_dag, ссылку на который я дал в мой вопрос, вы заметите, что в строке models.py 195 есть @staticmethod с именем circular_checker, возможно, вы сможете найти вдохновение в его коде.   -  person j_syk    schedule 03.08.2011
comment
На самом деле, я заметил ваш вопрос, понял, что тоже пытался реализовать даг, проверил этот проект и сам реализовал его, щедро украв из этого проекта. Если вы хотите отправить что-то в этом роде в качестве ответа здесь, я дам вам кредит за правильный ответ.   -  person mkomitee    schedule 03.08.2011
comment
j_syk, если вы отправите ответ с рекомендацией django_dag, я обязательно его приму.   -  person mkomitee    schedule 09.08.2011


Ответы (1)


Должен "нацеливаться". действительно быть внутри этого цикла в вашем коде? Кажется, что это заставит его пропустить уровень.

    for sub_group in self.target.all():
        sub_group.target.validate_no_group_loops(seen)
person jcfollower    schedule 03.08.2011