2011-10-07 7 views
5

Ho un set di oggetti documento e oggetti etichetta e voglio che questi due oggetti siano collegati. È una tipica relazione molti-a-molti. Ho il seguente codice:Quando e in che modo viene creata una relazione molti-a-molti durante il salvataggio di un modello?

Models.py:

class Document(models.Model): 
    title = models.CharField(max_length=50, unique=True) 
    title_slug = models.SlugField(max_length=50, unique=True, editable=False) 
    labels = models.ManyToManyField('Label') 

    def save(self, *args, **kwargs): 
     self.title_slug = slugify(self.title) 
     super(Document, self).save(*args, **kwargs) 

class Label(models.Model): 
    name = models.CharField(max_length=40, unique=True) 
    slug = models.SlugField(max_length=40, unique=True, editable=False) 

    def save(self, *args, **kwargs): 
     self.slug = slugify(self.name) 
     super(Document, self).save(*args, **kwargs) 

Views.py:

class DocumentForm(ModelForm): 
    class Meta: 
     model = Document 
     fields = ["title","labels"] 

def upload_document(request): 
    if request.method == 'POST': 
     form = DocumentForm(request.POST, request.FILES) 
     if form.is_valid(): 
      new_document = form.save() 
      return HttpResponseRedirect("/thanks/") 

    else: 
     form = DocumentForm() 

    return render_to_response('upload_page.html', {'form':form}, context_instance=RequestContext(request)) 

Quando ho caricare un documento, esso viene aggiunto al database, ma nessuna etichetta sono in corso creato o associato al documento. Devo aggiungere qualcosa alla funzione save() del documento per farlo accadere? O da qualche parte nel file Views.py? Mi piacerebbe pensare che sarebbe andata qualcosa di simile:

  • controllare per vedere se l'etichetta che viene aggiunto esiste già
  • Se non lo fa, quindi creare una nuova etichetta
  • Grab sia la corrente document_id e il nuovo/esistente label_id
  • Aggiungere un record alla tabella document_labels (creata automaticamente per la relazione molti-a-molti)

mi sento come se questa è la funzionalità piuttosto standard che ho assunto sarebbe stato costruito per am rapporto any-to-many in django, ma non sembra funzionare per me finora. Sto cercando di evitare di reinventare la ruota qui. Un po 'nuovo per il Django.

Grazie in anticipo!

+0

Si sta utilizzando un modulo? Stai aggiungendo 'Document's con altri mezzi? Di quale metodo save() stai parlando? Quello in 'Documento',' Etichetta' o una forma? Si prega di fornire alcuni esempi di vista/modulo/altro codice che salva l'oggetto. – andreaspelme

+0

Immagino di non essere veramente sicuro di quale metodo di salvataggio debba entrare. Da qui la mia domanda =] i documenti vengono tutti aggiunti tramite un modulo sul sito. Ho aggiornato la domanda per includere quel codice da views.py –

risposta

4

Come altre persone hanno detto, non è possibile salvare nell'oggetto Documentato one-shot e il suo campo ManyToMany, perché django crea "intermediatary join table", che richiede l'ID dell'oggetto Document, che non è definito in quel punto.

C'è una funzione save_m2m in ModelForm, che dovrebbe essere chiamato dal modulo stesso, come descritto nella the doc

Tuttavia, se non funziona, forse un trucco è quello di chiamare save_m2m nella vista la funzione, come questo:

def upload_document(request): 
    if request.method == 'POST': 
     form = DocumentForm(request.POST, request.FILES) 
     if form.is_valid(): 
      new_document = form.save() 
      form.save_m2m() 
      return HttpResponseRedirect("/thanks/") 

    else: 
     form = DocumentForm() 

    return render_to_response('upload_page.html', {'form':form}, context_instance=RequestContext(request)) 

Speranza che aiuta, Stéphane

+0

Ah, ho trovato il problema. Per semplificare il codice nel mio esempio, ho eliminato la riga 'new_document = form.save (commit = False)'. Ho dovuto usare commit = False poiché sto lavorando ancora un po 'sull'oggetto documento prima di salvarlo nel database, ma non pensavo che avrebbe avuto un impatto sul risparmio di m2m. Il tuo link a https://docs.djangoproject.com/en/1.3/topics/forms/modelforms/#the-save-method aveva proprio quello di cui avevo bisogno, grazie! –

2

Suggerirei di fare riferimento a come l'app Django Admin funziona in situazioni come questa. Tipicamente, questa sarebbe un'operazione a due stadi; Innanzitutto dovresti creare più etichette, quindi devi creare un documento, scegliere le etichette che desideri associare da un elenco a selezione multipla, quindi salvarlo. Django quindi assocerebbe automaticamente le etichette selezionate nell'elenco tramite la tabella molti a molti tra Documenti ed Etichette.

Se si spera di fare tutto questo in un unico passaggio, c'è la possibilità di utilizzare inline formsets. L'app di amministrazione li utilizza principalmente per le chiavi esterne (sondaggio e domande, ad esempio), ma possono essere utilizzati in misura limitata anche con relazioni molti-a-molti.

Attenzione, i moduli in linea possono essere complicati. Se è possibile suddividere l'operazione in due viste separate, sarebbe molto più semplice. Basta creare una vista per la creazione di etichette e un'altra per la creazione di documenti, che avrà automaticamente un elenco per il prelievo delle etichette da associare al documento.

0

Gli oggetti collegati non vengono creati automaticamente su save(). Dovresti creare un altro modulo per Etichette e salvarle esplicitamente.

+0

Devo fare un altro * modulo * per le etichette ?? Ciò significherebbe rimuovere il campo "Etichetta" dal modulo "Documento" e quindi salvarli contemporaneamente? –

+0

Sì, rimuovi il campo "etichetta" dal modulo "Documento", crea un nuovo modulo "Etichetta" e salva che dopo aver salvato il documento –

2

Le etichette nel modello Documento sono un campo M2M, quindi questo finirà per rendere una selezione multipla nel modulo di rendering (mostrando tutte le etichette disponibili nel sistema).

Partendo dal presupposto che è quello che vuoi,

in views.py

def upload_document(request): 
    if request.method == 'POST': 
     form = DocumentForm(request.POST, request.FILES) 
     if form.is_valid(): 
      labels = request.POST.getlist('labels') 
      new_document = form.save() 
      for label_id in labels: # we're only going to add currently defined labels 
       label = Label.objects.get(id=int(label_id)) 
       new_document.labels.add(label) 
      new_document.save() 
      return HttpResponseRedirect("/thanks/") 
    else: 
     form = DocumentForm() 

    return render_to_response('doc_form.html', {'form':form}, context_instance=RequestContext(request)) 

Ho aggiornato il modello di etichetta in modelli.py,

class Label(models.Model): 
    name = models.CharField(max_length=40, unique=True) 
    slug = models.SlugField(max_length=40, unique=True, editable=False) 

    def save(self, *args, **kwargs): 
     self.slug = slugify(self.name) 
     super(Label, self).save(*args, **kwargs) 

    def __unicode__(self): 
     return self.name 

Se stavate pensando di avere all'utente di creare anche etichette al volo, è necessario eseguire l'override del campo etichette nel modulo con qualcos'altro, come un campo di input. Per fare un esempio, se si sta istruendo gli utenti di inserire le etichette separati da virgole, allora avrete un views.py aggiornato come,

for label in labels: # labels entered by user 
    try: 
     lbl = Label.objects.get(name='label') 
    except Label.DoesNotExist: 
     lbl = None 

    if not lbl: 
     lbl = Label() 
     lbl.name = label 
     lbl.save() 

    newDoc.labels.add(lbl) 

newDoc.save() 

Spero che questo risolve il problema o si dà qualcosa su cui lavorare.

Problemi correlati