How do I remove multiple objects in a ManyToMany relationship based on a filter?

前端 未结 2 702
夕颜
夕颜 2020-12-28 18:52

Given these two Models:

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

class Source(models.Model):
    items = models.ManyToManyField(Item         


        
相关标签:
2条回答
  • 2020-12-28 19:28

    I think you got it right on the money, except you don't need to convert to a list.

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

    The remove/add method looks like the following

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

    and the docs use add multiple examples in the form of [p1, p2, p3] so I'd wager the same goes for remove, seeing as the arguments are the same.

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

    Digging in a little more, the remove function iterates over *objs one by one, checking if it's of the valid model, otherwise using the values as PK's, then deletes the items with a pk__in, so I'm gonna say yes, the best way is to query your m2m table first for objects to delete then pass in those objects into the m2m manager.

        # 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)
    
    0 讨论(0)
  • 2020-12-28 19:49

    According to current docs there is a through property that gives you an access to table that manages many-to-many relation, like so Model.m2mfield.through.objects.all()

    So in terms of your example:

    source.items.through.objects \
        .filter(item__timestamp__lte=some_datetime) \
        .delete()
    
    0 讨论(0)
提交回复
热议问题