2011-01-17 13 views
11

Alla luce di questi due modelli:Come rimuovere più oggetti in una relazione ManyToMany basata su un filtro?

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

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

posso trovare tutti i prodotti che di una fonte prima di un dato tempo che utilizzano questo:

source.items.filter(timestamp__lte=some_datetime) 

Come posso rimuovere in modo efficiente tutti gli elementi che corrispondono a quella query? Suppongo di poter provare qualcosa del genere:

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

ma che sembra male.

Nota che non voglio eliminare questi elementi, poiché potrebbero appartenere anche ad altre fonti. Voglio solo rimuovere la loro relazione con la fonte specifica.

risposta

18

Penso che tu abbia capito bene con i soldi, tranne che non hai bisogno di convertirli in una lista.

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

Il metodo remove/add si presenta come il seguente

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

e la documentazione http://www.djangoproject.com/documentation/models/many_to_many/ uso aggiungere più esempi sotto forma di [p1, p2, p3] quindi scommetto lo stesso vale per remove, visto che gli argomenti sono gli stessi.

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

Scavando in un po 'di più, i itera funzione Remove oltre *objs uno per uno, controllando se è del modello valido, altrimenti utilizzando i valori come PK, quindi elimina le voci con un pk__in, quindi sono dirò di si, il modo migliore è di interrogare prima la tabella m2m per gli oggetti da eliminare e poi passare quegli oggetti nel manager 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

Grazie! Ci proverò e vedrò come funziona. In questo momento sto usando una singola istruzione SQL raw (usando "DELETE ... USING" in PostgreSQL, che capisco non è standard). – bunnyhero

+1

Sì, la maggior parte delle persone preferisce attenersi all'ORM :) –

Problemi correlati