Джанго удалить посторонний объект? - PullRequest
18 голосов
/ 15 февраля 2010

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

Причина, по которой я не поместил ключи в другие объекты, заключается в том, что тогда Django должен был бы внутренне объединять все эти таблицы каждый раз, когда я хотел проверить, какой тип был у пользователя ...

Ответы [ 3 ]

11 голосов
/ 08 декабря 2014

Хотя использование сигнала 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 () с пустыми аргументами, поэтому мы могли даже знать, что он там был. В приведенном выше примере этот параметр похоронен нами, переопределяя и не глядя на функциональность суперкласса, если кто-то, кому передать параметр 'using' при удалении профиля, вызовет непредвиденное поведение. Чтобы избежать этого, мы должны сохранить аргумент вместе со значением по умолчанию lika так:

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

5 голосов
/ 09 сентября 2011

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

5 голосов
/ 15 февраля 2010

Вы можете переопределить метод 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()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...