Как удалить несколько объектов в отношении ManyToMany на основе фильтра? - PullRequest
15 голосов
/ 18 января 2011

С учетом этих двух моделей:

class Item(models.Model):
    timestamp = models.DateTimeField()

class Source(models.Model):
    items = models.ManyToManyField(Item, related_name="sources")

Я могу найти все предметы источника до определенного времени, используя это:

source.items.filter(timestamp__lte=some_datetime)

Как эффективно удалить все предметычто соответствует этому запросу?Я полагаю, я мог бы попробовать что-то вроде этого:

items_to_remove = list(source.items.filter(timestamp__lte=some_datetime))
source.items.remove(*items_to_remove)

, но это кажется плохим.

Обратите внимание, что я не хочу удалять эти элементы, так как они могут такжепринадлежат к другим источникам.Я просто хочу удалить их связь с конкретным источником.

1 Ответ

23 голосов
/ 18 января 2011

Я думаю, что вы правильно поняли деньги, за исключением того, что вам не нужно конвертировать в список.

source.items.remove(*source.items.filter(*args))

Метод remove / add выглядит следующим образом

remove(self, *objs)
add(self, *objs)

и документы добавить несколько примеров в виде [p1, p2, p3], поэтому я бы поспорил, то же самое относится и к remove, поскольку аргументы одинаковы.

>>> a2.publications.add(p1, p2, p3)

Еще немного покопавшись, функция удаления перебирает *objs одну за другой, проверяя, соответствует ли она действительной модели, в противном случае, используя значения в качестве PK, затем удаляет элементы с pk__in, так что я скажу да, лучший способ - сначала запросить у вашей таблицы m2m объекты для удаления, а затем передать эти объекты менеджеру m2m.

    # django.db.models.related.py
    def _remove_items(self, source_field_name, target_field_name, *objs):
        # source_col_name: the PK colname in join_table for the source object
        # target_col_name: the PK colname in join_table for the target object
        # *objs - objects to remove

        # If there aren't any objects, there is nothing to do.
        if objs:
            # Check that all the objects are of the right type
            old_ids = set()
            for obj in objs:
                if isinstance(obj, self.model):
                    old_ids.add(obj.pk)
                else:
                    old_ids.add(obj)
            if self.reverse or source_field_name == self.source_field_name:
                # Don't send the signal when we are deleting the
                # duplicate data row for symmetrical reverse entries.
                signals.m2m_changed.send(sender=rel.through, action="pre_remove",
                    instance=self.instance, reverse=self.reverse,
                    model=self.model, pk_set=old_ids)
            # Remove the specified objects from the join table
            db = router.db_for_write(self.through.__class__, instance=self.instance)
            self.through._default_manager.using(db).filter(**{
                source_field_name: self._pk_val,
                '%s__in' % target_field_name: old_ids
            }).delete()
            if self.reverse or source_field_name == self.source_field_name:
                # Don't send the signal when we are deleting the
                # duplicate data row for symmetrical reverse entries.
                signals.m2m_changed.send(sender=rel.through, action="post_remove",
                    instance=self.instance, reverse=self.reverse,
                    model=self.model, pk_set=old_ids)
...