Django удалить посторонний объект?
Если мы настроим профиль, который рекомендует Django:
class Profile(models.Model):
user = models.ForeignKey(User, unique=True)
Затем, когда вы удаляете объект User
из администратора Django, он также удаляет свой профиль. Это связано с тем, что профиль имеет внешний ключ для пользователя и он хочет защитить ссылочную целостность. Тем не менее, я хочу эту функциональность, даже если указатель идет в другую сторону. Например, в моем классе Profile
у меня есть:
shipper = models.ForeignKey(Shipper, unique=True, blank=True, null=True)
carrier = models.ForeignKey(Carrier, unique=True, blank=True, null=True)
affiliat = models.ForeignKey(Affiliate, unique=True, blank=True, null=True, verbose_name='Affiliate')
И я хочу, чтобы, если вы удалите Profile
, он удалит связанные объекты отправителя/перевозчика/партнера (не спрашивайте меня, почему Django сделал "affiliate" какое-то странное ключевое слово). Поскольку грузоотправители, перевозчики и ветки являются типами пользователей, и им не имеет смысла существовать без остальной части данных (никто не сможет войти в систему как один).
Причина, по которой я не клал ключи на другие объекты, заключается в том, что тогда Django пришлось бы внутренне присоединяться ко всем этим таблицам каждый раз, когда я хотел бы проверить, какой тип пользователя был...
Ответы
Ответ 1
При использовании сигнала post_delete, описанного выше, это одобренный подход, который будет работать хорошо, я стараюсь избегать использования сигналов настолько, насколько это возможно по-человечески, так как я чувствую, что он излишне сворачивает ваш код, добавляя поведение к стандартной функциональности в места, которые можно ожидать.
Я предпочитаю вышеприведенный метод выше, однако пример, приведенный Феликс, имеет один фатальный недостаток; функция delete(), которая является переопределяющей, выглядит следующим образом:
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)
collector = Collector(using=using)
collector.collect([self])
collector.delete()
Обратите внимание на параметр "using", в большинстве случаев мы вызываем delete() с пустыми аргументами, чтобы мы, возможно, даже знали, что он есть. В приведенном выше примере этот параметр похож на нас, переопределяя и не смотря на функциональность суперкласса, если кто-то, кто передаст параметр "использование" при удалении профиля, вызовет неожиданное поведение. Чтобы этого избежать, мы обязательно сохраним аргумент вместе с его значением по умолчанию:
class Profile(models.Model):
# ...
def delete(self, using=None):
if self.shipper:
self.shipper.delete()
if self.carrier:
self.carrier.delete()
if self.affiliat:
self.affiliat.delete()
super(Profile, self).delete(using)
Однако одна ошибка заключается в том, что delete() не получает явного вызова в записи db при массовых ударах, это означает, что если вы захотите удалить несколько профилей за один раз и сохраните переопределение (например, вызов .delete() в наборе запросов django), вам нужно будет либо использовать сигнал удаления (как описано bernardo), либо вам потребуется перебирать каждую запись, удаляя их по отдельности (дорого и уродливо).
Ответ 2
Вы можете переопределить метод delete()
класса Profile и удалить другие объекты в этом методе перед удалением фактического профиля.
Что-то вроде:
class Profile(models.Model):
# ...
def delete(self):
if self.shipper:
self.shipper.delete()
if self.carrier:
self.carrier.delete()
if self.affiliat:
self.affiliat.delete()
super(Profile, self).delete()
Ответ 3
Лучший способ сделать это, и это работает с методом удаления объекта и методом удаления запроса, использует сигнал post_delete
, как вы можете видеть в документации.
В вашем случае ваш код будет очень похож на этот:
from django.db import models
from django.dispatch import receiver
@receiver(models.signals.post_delete, sender=Profile)
def handle_deleted_profile(sender, instance, **kwargs):
if instance.shipper:
instance.shipper.delete()
if instance.carrier:
instance.carrier.delete()
if instance.affiliat:
instance.affiliat.delete()
Это работает только для Django 1.3 или выше, потому что в этой версии Django был добавлен сигнал post_delete
.