Какие есть варианты для того, чтобы переопределить каскадное удаление поведении Джанго?
модели Django обычно обрабатывают поведение каскада при удалении вполне адекватно (таким образом, что работает с базами данных, которые не поддерживают его изначально.)
тем не менее, я изо всех сил пытаюсь выяснить, что является лучшим способом переопределить это поведение, когда оно не подходит, например, в следующих сценариях:
on DELETE RESTRICT (т. е. запретить удаление объекта, если он имеет дочерние записи)
ON DELETE SET NULL (т. е. не удаляйте дочернюю запись, но установите ее родительский ключ в NULL вместо того, чтобы разорвать связь)
обновление других связанных данных при удалении записи (например, удаление загруженного файла изображения)
ниже перечислены потенциальные способы достижения этих целей, о которых я знаю:
переопределить модель
delete()
метод. Хотя этот вид работ, он обходится, когда записи удаляются черезQuerySet
. Кроме того, каждая модельdelete()
должен быть переопределен, чтобы убедиться, что код Django никогда не вызывается иsuper()
не может быть вызван, поскольку он может использоватьQuerySet
для удаления дочерних объектов.использовать сигналы. Это кажется идеальным, поскольку они называются при непосредственном удалении модели или удалении через QuerySet. Однако нет возможности предотвратить удаление дочернего объекта, поэтому его нельзя реализовать на каскадном ограничении или наборе НОЛЬ.
используйте компонент database engine, который обрабатывает это правильно (что делает Django в этом случае?)
подождите, пока Django не поддержит его (и жить с ошибками до тех пор...)
похоже, что первый вариант является единственным жизнеспособным, но он уродлив, выбрасывает ребенка с водой для ванны и рискует пропустить что-то, когда добавляется новая модель/отношение.
Я что-то пропустила? Любой рекомендации?
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)
я надеюсь, что это поможет кому-то, и что код может быть повторно нанизан на что-то более полезное без особых проблем.
любые предложения о том, как улучшить это более чем приветствуется.