Ответы местных гуру просто впечатляют. Такое ощущение что никто ни разу не использовал ON DELETE RESTRICT:
* Evg: Очень вероятно у вас какой-то косяк в проектировании моделей)
* Daevaorn: Косяк в … ДНК
* Daevaorn: Такая логика в БД – зло.
* regall: вашу БД используют другие программы кроме django, причем во всякой извращенной форме
Тут хочется заметить что если ON DELETE RESTRICT явно ( mysql, postgresql ) или неявно ( oracle, mssql ) присутствует в большинстве нормальных баз, то наверное он злом или ошибкой в днк являться не может. Это так же глупо, как утверждать что крестовых отверток не существует, есть только плоские, только потому что вы не хотите пользоваться крестовой ( ужос ) :) А касательно кривости структуры и применимости этого поведения - ну вот хочу я чтобы запись можно было удалить только если на неё нет ссылок, мне удавиться или использовать другой фрейворк?
Отдельно порадовал ответ regall - “Все-таки django-ORM разрабатывался как часть django, а не как отдельный ORM, он удовлетворяет тем требованиям, которые на него возложены, и никаким другим.” Фраза ни о чем и полностью отражает подпись под этим постом. Кстати, было бы неплохо посмотреть на ссылочку, где расписана цель и задачи джанго орм.
Ответ lorien вообще является образцом “дружелюбия”. Новичкам не рады?
Да, собственно о чем я… Я не ругаться и не высказывать свое фи пришел. Просто каждый раз когда искал решение по эмуляции рестрикта наталкивался именно на эту страничку, потому решил на фоне прочих “фи” запечатлеть таки действенное решение - мало ли кто еще набредет. Впрочем, решений тонна.
Исходная задача - эмуляция ON DELETE RESTRICT, что в переводе на русский - запретить удаление объектов, на которые есть ссылки.
1. Перегрузить метод delete у models.ManagerРеализуемо, но меньше всего хотелось бы этим заниматься, поскольку model.Manager это практически полностью прокси к QuerySet, и фактически прийдется переписывать соответствующий метод именно у queryset, а там все не так тривиально, хоть и возможно. Вобщем отмел как геморный.
2. Перегрузить метод delete у models.ModelА вот тут как раз ничего сложного. Оригинальный код:
def delete(self, using=None):
using = using or router.db_for_write(self.__class__, instance=self)
assert self._get_pk_val() is not None, "%s object can't be deleted because its %s attribute is set to None." % (self._meta.object_name, self._meta.pk.attname)
# Find all the objects than need to be deleted.
seen_objs = CollectedObjects()
self._collect_sub_objects(seen_objs)
# Actually delete the objects.
delete_objects(seen_objs, using)
Его без проблем можно скопировать в класс-наследник и вставить проверку на количество seen_objs с выбросом исключения в случае > 1 .
Потенциальных минусов тут два:
а) если в будущем код delete из базового класса изменится, то наш может стать не актуальным.
б) Кажется была проблема с вызовом mode.delete() из queryset.delete() в некоторых случаях и я так и не понял решили её или нет.
3. Хуки\сигналы наше все.Нужно определить какой-то универсальный обработчик удаления и приаттачить его ко всем моделям - будет счастье. Причем “счастье” лишено минусов из пункта 2, но обладает своим - снижение наглядности. Сам сигнал объявляется отдельно от модели и можно банально его не заметить при небрежном просмотре кода. Впрочем, кто сказал что к коду нужно относиться небрежно :)
Ну и собственно пример такого обработчика:
from django.db.models.signals import pre_delete
from django.db.models.query_utils import CollectedObjects
from django.db.utils import IntegrityError
def on_delete_restrict( sender, **kwargs ):
# Find all the objects than need to be deleted.
seen_objs = CollectedObjects()
kwargs['instance']._collect_sub_objects(seen_objs)
if len(seen_objs.data) > 1:
raise IntegrityError('Some objects are still depends on %s(pk=%s)' % ( str(kwargs['instance'].__class__.__name__), str(kwargs['instance'].pk) ) )
pre_delete.connect(on_delete_restrict, sender = Model1)
pre_delete.connect(on_delete_restrict, sender = Model2)
pre_delete.connect(on_delete_restrict, sender = Model3)
Итого - для эмуляции ON DELETE RESTRICT потребутся определить один обработчик и подкинуть его на pre_delete для _нужных_ моделей.
Что тут можно добавить… Вообще эмуляция on_delete_restrict не отменяет необходимости обработки соответствующей ситуации, дабы банально не выдавать юзеру непонятные мессаги. С другой стороны, теперь мы спокойны что юзер по тупняку не грохнет полбазы при удалении, как ему показалось, лишней записи из таблицы-справочника.