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

Django обычно моделирует, дескриптор НА УДАЛЯЮТ КАСКАДНОЕ поведение вполне соответственно (способом, который работает над базами данных, которые не поддерживают его исходно.)

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

  • НА УДАЛЯЮТ, ОГРАНИЧИВАЮТ (т.е. предотвратите удаление объекта, если оно имеет дочерние записи),

  • НА УДАЛЯЮТ ПУСТОЙ УКАЗАТЕЛЬ НАБОРА (т.е. не удаляйте дочернюю запись, но устанавливайте, это - родительский ключ к ПУСТОМУ УКАЗАТЕЛЮ вместо этого для повреждения отношений),

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

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

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

  • Используйте сигналы. Это, кажется, идеально, как их называют, непосредственно удаляя модель или удаляя через QuerySet. Однако нет никакой возможности препятствовать тому, чтобы дочерний объект был удален так, это не применимо для реализации НА КАСКАДЕ, ОГРАНИЧИВАЮТ или УСТАНАВЛИВАЮТ ПУСТОЙ УКАЗАТЕЛЬ.

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

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

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

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

57
задан Tom 19 March 2010 в 05:56
поделиться

2 ответа

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

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

  • Чтобы повторить сценарий ON DELETE SET NULL - вручную выполните obj.rel_set.clear () (для каждой связанной модели) перед obj .удалять().
  • Чтобы повторить сценарий ON DELETE RESTRICT - вручную проверьте, является ли obj.rel_set пустым перед obj.delete ().
6
ответ дан 24 November 2019 в 19:39
поделиться

Хорошо, я выбрал следующее решение, хотя оно далеко не удовлетворительное.

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

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 и .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 и позволяет модели очищать любые другие данные (например, удаление файлов for 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)

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

Любые предложения по улучшению этого более чем приветствуются.

4
ответ дан 24 November 2019 в 19:39
поделиться
Другие вопросы по тегам:

Похожие вопросы: