2011-01-17 8 views
11

Dados estos dos modelos:¿Cómo elimino varios objetos en una relación ManyToMany basada en un filtro?

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

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

puedo encontrar todos los artículos de la Fuente ante un momento dado que utilizan este:

source.items.filter(timestamp__lte=some_datetime) 

¿Cómo eficiente quitar todos los elementos que coinciden con esa consulta? Supongo que podría intentar algo como esto:

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

pero eso parece malo.

Tenga en cuenta que no quiero eliminar estos elementos, ya que también pueden pertenecer a otras fuentes. Solo quiero eliminar su relación con la fuente específica.

Respuesta

18

Creo que lo hizo bien con el dinero, excepto que no necesita convertirlo a una lista.

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

El método remove/add tiene el siguiente

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

y los documentos http://www.djangoproject.com/documentation/models/many_to_many/ uso agregar varios ejemplos en forma de [p1, p2, p3] así que apostaría lo mismo vale para remove, ya los argumentos son lo mismo

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

de excavación en un poco más, se repite el funcionamiento eliminar más *objs uno por uno, comprobando si es del modelo válido, de lo contrario el uso de los valores como PK, a continuación, elimina los artículos con una pk__in, así que estoy Voy a decir que sí, la mejor manera es consultar primero la tabla m2m por objetos para eliminar y luego pasar esos objetos al administrador 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) 
+0

¡Gracias! Lo intentaré y veré qué tan bien funciona. En este momento estoy usando una sola instrucción SQL sin formato (usando "DELETE ... USING" en PostgreSQL, que entiendo no es estándar). – bunnyhero

+1

Sí, la mayoría de las personas prefiere seguir con el ORM :) –

Cuestiones relacionadas