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