Каковы варианты переопределения каскадного поведения удаления Django?

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

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

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

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

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

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

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

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

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

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

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

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

3 ответов


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

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

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

on_delete=models.SET_NULL

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

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

  • повторить при удалении сценария SET NULL-вручную сделать obj.rel_set.clear () (для каждой связанной модели) перед 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 via self.related_set.clear() или подобные:

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

чтобы избежать необходимости изменять слишком много кода в my views.py и models.py, the 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 сигнал и позволяет устранить любые другие данные (например, удалить файлы ImageFields.)

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)

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

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