Какие есть варианты для того, чтобы переопределить каскадное удаление поведении Джанго?


модели Django обычно обрабатывают поведение каскада при удалении вполне адекватно (таким образом, что работает с базами данных, которые не поддерживают его изначально.)

тем не менее, я изо всех сил пытаюсь выяснить, что является лучшим способом переопределить это поведение, когда оно не подходит, например, в следующих сценариях:

  • on DELETE RESTRICT (т. е. запретить удаление объекта, если он имеет дочерние записи)

  • ON DELETE SET NULL (т. е. не удаляйте дочернюю запись, но установите ее родительский ключ в NULL вместо того, чтобы разорвать связь)

  • обновление других связанных данных при удалении записи (например, удаление загруженного файла изображения)

ниже перечислены потенциальные способы достижения этих целей, о которых я знаю:

  • переопределить модель delete() метод. Хотя этот вид работ, он обходится, когда записи удаляются через QuerySet. Кроме того, каждая модель delete() должен быть переопределен, чтобы убедиться, что код Django никогда не вызывается и super() не может быть вызван, поскольку он может использовать QuerySet для удаления дочерних объектов.

  • использовать сигналы. Это кажется идеальным, поскольку они называются при непосредственном удалении модели или удалении через QuerySet. Однако нет возможности предотвратить удаление дочернего объекта, поэтому его нельзя реализовать на каскадном ограничении или наборе НОЛЬ.

  • используйте компонент database engine, который обрабатывает это правильно (что делает Django в этом случае?)

  • подождите, пока Django не поддержит его (и жить с ошибками до тех пор...)

похоже, что первый вариант является единственным жизнеспособным, но он уродлив, выбрасывает ребенка с водой для ванны и рискует пропустить что-то, когда добавляется новая модель/отношение.

Я что-то пропустила? Любой рекомендации?

3 54

3 ответа:

просто примечание для тех, кто сталкивается с этой проблемой, а также, теперь есть встроенное решение в Django 1.3.

смотрите подробности в документации Джанго.децибел.модели.ForeignKey.on_delete Спасибо за редактор фрагментов кода сайта, чтобы указать на это.

самый простой возможный сценарий просто добавьте в свою модель определение поля FK:

on_delete=models.SET_NULL

Django только эмулирует каскадное поведение.

по данным обсуждение в группе пользователей Django наиболее адекватными решениями являются:

  • чтобы повторить сценарий удаления SET NULL-вручную выполните obj.rel_set.очистить () (для каждой связанной модели) перед obj.исключать.)(
  • чтобы повторить на Удалить ограничить сценарий-вручную проверить obj.rel_set пустой перед obj.исключать.)(

хорошо, следующее решение, на котором я остановился, хотя это далеко не удовлетворяет.

я добавил абстрактный базовый класс для всех моих моделей:

class MyModel(models.Model):
    class Meta:
        abstract = True

    def pre_delete_handler(self):
        pass

обработчик сигналов ловит любой pre_delete события для подклассов этой модели:

def pre_delete_handler(sender, instance, **kwargs):
    if isinstance(instance, MyModel):
        instance.pre_delete_handler()
models.signals.pre_delete.connect(pre_delete_handler)

в каждой из моих моделей, я моделировать любые "ON DELETE RESTRICT" отношения, бросая исключение из pre_delete_handler метод, если дочерняя запись существует.

class RelatedRecordsExist(Exception): pass

class SomeModel(MyModel):
    ...
    def pre_delete_handler(self):
        if children.count(): 
            raise RelatedRecordsExist("SomeModel has child records!")

это прерывает удаление перед любым данные изменяются.

к сожалению, невозможно обновить какие-либо данные в сигнале pre_delete (например, эмулировать ON DELETE SET NULL) поскольку список объектов для удаления уже был сгенерирован Django перед отправкой сигналов. Django делает это, чтобы избежать застревания на круговых ссылках и предотвратить сигнализацию объекта несколько раз без необходимости.

обеспечение удаления может быть выполнено теперь ответственность вызывающего кода. Чтобы помочь в этом, каждый модель имеет prepare_delete() метод, который заботится о настройке клавиш NULL через self.related_set.clear() или подобные:

class MyModel(models.Model):
    ...
    def prepare_delete(self):
        pass

чтобы избежать необходимости менять слишком много кода в моем views.py и models.py на delete() метод переопределен на MyModel называть prepare_delete():

class MyModel(models.Model):
    ...
    def delete(self):
        self.prepare_delete()
        super(MyModel, self).delete()

это означает, что любое удаление явно вызывается через obj.delete() будет работать, как ожидалось, но если удаление каскадом из связанного объекта или делается через queryset.delete() и вызывающий код не обеспечили, что все ссылки разрываются там, где это необходимо, тогда pre_delete_handler вызовет исключение.

и наконец, я добавил подобную post_delete_handler метод для моделей, которые вызываются на post_delete сигнал и позволяет устранить любые другие данные (например, удалить файлы ImageField s.)

class MyModel(models.Model):
     ...

    def post_delete_handler(self):
        pass

def post_delete_handler(sender, instance, **kwargs):
    if isinstance(instance, MyModel):
        instance.post_delete_handler()
models.signals.post_delete.connect(post_delete_handler)

я надеюсь, что это поможет кому-то, и что код может быть повторно нанизан на что-то более полезное без особых проблем.

любые предложения о том, как улучшить это более чем приветствуется.