Как мягко удалить отношения многие ко многим с Джанго - PullRequest
6 голосов
/ 21 февраля 2012

В моем проекте Django все сущности, удаленные пользователем, должны быть мягко удалены, установив текущее значение datetime в свойство selected_at.Моя модель выглядит так: Trip <-> TripDestination <-> Destination (многие ко многим относятся).Другими словами, поездка может иметь несколько адресатов.

Когда я удаляю поездку, SoftDeleteManager отфильтровывает все удаленные поездки.Однако, если я запрашиваю все пункты назначения поездки (используя get_object_or_404 (Trip, pk = id)), я также получаю удаленные (то есть модели TripDestination с удаленным_катом == нулевым ИЛИ удаленным_ат! = Нулевым).Я действительно не понимаю, почему, поскольку все мои модели наследуются от LifeTimeTracking и используют SoftDeleteManager.

Может кто-нибудь помочь мне понять, почему SoftDeleteManager не работает для отношения n: m?

class SoftDeleteManager(models.Manager):
    def get_query_set(self):
        query_set = super(SoftDeleteManager, self).get_query_set()
        return query_set.filter(deleted_at__isnull = True)

class LifeTimeTrackingModel(models.Model):
    created_at = models.DateTimeField(auto_now_add = True)
    updated_at = models.DateTimeField(auto_now = True)
    deleted_at = models.DateTimeField(null = True)

    objects = SoftDeleteManager()
    all_objects = models.Manager()

    class Meta:
        abstract = True

class Destination(LifeTimeTrackingModel):
    city_name = models.CharField(max_length = 45)

class Trip(LifeTimeTrackingModel):
    name = models.CharField(max_length = 250)
    destinations = models.ManyToManyField(Destination, through = 'TripDestination')

class TripDestination(LifeTimeTrackingModel):
    trip = models.ForeignKey(Trip)
    destination = models.ForeignKey(Destination)

Разрешение Я подал ошибку 17746 в Django Bug DB.Спасибо Каспару за помощь.

Ответы [ 2 ]

2 голосов
/ 24 апреля 2014

Чтобы включить фильтрацию по полю deleted_at моделей Destinantion и Trip, достаточно установить use_for_related_fields = True для класса SoftDeleteManager. Согласно ответу Каспара, это не возвращает удаленное Destinations для trip_object.destinations.all().

Однако из ваших комментариев мы видим, что вы хотели бы отфильтровать Destinations, которые связаны с Trip через объект TripDestination с установленным полем deleted_at, то есть мягкое удаление на через экземпляр.

Давайте выясним, как работают менеджеры. Связанные менеджеры - это менеджеры удаленной модели, а не сквозной модели.

trip_object.destinantions.some_method() звонки по умолчанию Destination менеджер. destinantion_object.trip_set.some_method() звонки по умолчанию Trip менеджер. TripDestination менеджер не вызывается в любое время.

Вы можете позвонить по номеру trip_object.destinantions.through.objects.some_method(), если вы действительно этого хотите. Теперь я хотел бы добавить метод Instance Trip.get_destinations и аналогичный Destination.get_trips, который отфильтровывает удаленные соединения.

Если вы настаиваете на использовании менеджера для фильтрации, он становится более сложным:

class DestinationManager(models.Manager):
    use_for_related_fields = True

    def get_query_set(self):
        query_set = super(DestinationManager, self).get_query_set()
        if hasattr(self, "through"):
            through_objects = self.through.objects.filter(
                destination_id=query_set.filter(**self.core_filters).get().id,
                trip_id=self._fk_val,
                deleted_at__isnull=True)
            query_set = query_set.filter(
                id__in=through_objects.values("destination_id"))

        return query_set.filter(deleted_at__isnull = True)

То же самое должно быть сделано для TripManager, поскольку они будут отличаться. Вы можете проверить производительность и посмотреть django/db/models/fields/related.py для справки.

Изменение метода get_queryset диспетчера по умолчанию может затруднить возможность резервного копирования базы данных, а документация препятствует этому. Написание метода Trip.get_destinations является альтернативой.

2 голосов
/ 21 февраля 2012

Похоже, такое поведение происходит из-за того, что ManyToManyField выбрал использование собственного менеджера, о котором упоминает Связанные объекты , потому что, когда я пытаюсь создать некоторые из своих собственных экземпляров и пытаюсь мягко удалить их, используяВ вашем коде модели (через оболочку manage.py) все работает как положено.

К сожалению, здесь не упоминается, как вы можете переопределить менеджер моделей.Я потратил около 15 минут на поиск в источнике ManyToManyField, но не отследил, где он создает экземпляр своего менеджера (см. Django / db / models / fields / related.py).

Чтобы узнать поведениевы после этого, вы должны указать use_for_related_fields = True в вашем SoftDeleteManager классе, как указано в документации по контролю автоматических менеджеров :

class SoftDeleteManager(models.Manager):
    use_for_related_fields = True

    def get_query_set(self):
        query_set = super(SoftDeleteManager, self).get_query_set()
        return query_set.filter(deleted_at__isnull = True)

Это работает, как и ожидалось: я могуопределить Trip с 2 Destination с, каждый через TripDestination, и если я установлю deleted_at Destination значение datetime.datetime.now(), то Destination больше не появится в списке, указанномна mytrip.destinations.all(), как вы можете сказать, это то, что вам нужно.

Однако , документы также специально говорят , не фильтруют набор запросов путем переопределения get_query_set()на менеджере, используемом для связанных полей , поэтому, если у вас возникнут проблемы позже, помните об этом как о возможной причине.

...