2012-09-01 1 views
5

Ho una relazione M2M tra due modelli che utilizza un modello intermedio. Per il bene della discussione, usiamo l'esempio da manuale:Visualizzazioni basate su classi per la relazione M2M con il modello intermedio

class Person(models.Model): 
    name = models.CharField(max_length=128) 

    def __unicode__(self): 
     return self.name 

class Group(models.Model): 
    name = models.CharField(max_length=128) 
    members = models.ManyToManyField(Person, through='Membership') 

    def __unicode__(self): 
     return self.name 

class Membership(models.Model): 
    person = models.ForeignKey(Person) 
    group = models.ForeignKey(Group) 
    date_joined = models.DateField() 
    invite_reason = models.CharField(max_length=64) 

mi piacerebbe fare uso di punti di vista basati su Class di Django, di evitare di scrivere viste CRUD di gestione. Tuttavia, se si tenta di utilizzare il CreateView di default, non funziona:

class GroupCreate(CreateView): 
    model=Group 

Questo rende un modulo con tutti i campi sull'oggetto Gruppo, e dà una scatola di selezione multipla per il campo i membri, che sarebbe corretto per una semplice relazione M2M. Tuttavia, non è possibile specificare date_joined o invite_reason e l'invio del modulo restituisce il seguente AttributeError:

"Impossibile impostare valori su un ManyToManyField che specifica un modello intermedio. Utilizzare invece Gestione membri."

C'è un modo semplice per scavalcare parte del generico CreateView, o comporre la mia vista personalizzata per farlo con mixin? Sembra che questo dovrebbe essere parte del framework, in quanto l'interfaccia di amministrazione gestisce in modo atomico le relazioni M2M con i prodotti intermedi utilizzando inline.

+0

possibile duplicato di [django Impossibile impostare valori su un ManyToManyField che specifica un modello intermedio. Utilizzare Gestione invece] (http://stackoverflow.com/questions/3091328/django-cannot-set-values-on-a-manytomanyfield-which-specifies-an-intermediary-mo) – juliocesar

risposta

6

È necessario estendere CreateView:

from django.views.generic import CreateView 

class GroupCreate(CreateView): 
    model=Group 

e ignorare il form_valid():

from django.views.generic.edit import ModelFormMixin 
from django.views.generic import CreateView 

class GroupCreate(CreateView): 
    model = Group 

    def form_valid(self, form): 
     self.object = form.save(commit=False) 
     for person in form.cleaned_data['members']: 
      membership = Membership() 
      membership.group = self.object 
      membership.person = person 
      membership.save() 
     return super(ModelFormMixin, self).form_valid(form) 

Come il documentation dice, è necessario creare nuovi membership s per ogni relazione tra group e person.

ho visto la form_valid di override qui: Using class-based UpdateView on a m-t-m with an intermediary model

+0

Non ho avuto la possibilità di testarlo (e ho finito per percorrere una strada diversa), ma sembra il metodo corretto. Saluti! – Symmetric

+0

Questo non ti dà alcuna possibilità di entrare '' date_joined' e invite_reason' ... semplicemente li salva vuoto ... –

0

Stavo affrontando lo stesso problema solo pochi giorni fa. Django ha problemi nel processare relazioni intermedie di m2m.

Questa è la soluzione che ho trovato utile:

1. Define new CreateView 
class GroupCreateView(CreateView): 
    form_class = GroupCreateForm 
    model = Group 
    template_name = 'forms/group_add.html' 
    success_url = '/thanks' 

Poi alterare il metodo save di forma definita - GroupCreateForm. Save è responsabile di apportare modifiche permanenti al DB. Non ero in grado di fare questo lavoro solo attraverso ORM, così ho usato SQL prime troppo:

1. Define new CreateView 
class GroupCreateView(CreateView): 


class GroupCreateForm(ModelForm): 
    def save(self): 
     # get data from the form 
     data = self.cleaned_data 
     cursor = connection.cursor() 
     # use raw SQL to insert the object (in your case Group) 
     cursor.execute("""INSERT INTO group(group_id, name) 
          VALUES (%s, %s);""" (data['group_id'],data['name'],)) 
     #commit changes to DB 
     transaction.commit_unless_managed() 
     # create m2m relationships (using classical object approach) 
     new_group = get_object_or_404(Group, klient_id = data['group_id']) 
     #for each relationship create new object in m2m entity 
     for el in data['members']: 
      Membership.objects.create(group = new_group, membership = el) 
     # return an object Group, not boolean! 
     return new_group 

Nota: ho cambiato il modello un po ', come si può vedere (ho proprio unico IntegerField per la chiave primaria, non si utilizza di serie Ecco come è entrato in get_object_or_404

+0

Questa risposta non aiuta molto. Puoi mostrare il tuo modello? Cambiare il pk da "id" a "id_gruppo" non ha molto senso. – Timo

0

'per avere un riferimento, non ho finire con una vista di classe, invece ho fatto qualcosa di simile:.

def group_create(request): 
    group_form = GroupForm(request.POST or None) 
    if request.POST and group_form.is_valid(): 
     group = group_form.save(commit=False) 
     membership_formset = MembershipFormSet(request.POST, instance=group) 
     if membership_formset.is_valid(): 
      group.save() 
      membership_formset.save() 
      return redirect('success_page.html') 
    else: 
     # Instantiate formset with POST data if this was a POST with an invalid from, 
     # or with no bound data (use existing) if this is a GET request for the edit page. 
     membership_formset = MembershipFormSet(request.POST or None, instance=Group()) 

    return render_to_response(
     'group_create.html', 
     { 
      'group_form': recipe_form, 
      'membership_formset': membership_formset, 
     }, 
     context_instance=RequestContext(request), 
    ) 

Questo può essere un punto di partenza per un'implementazione basata su classi, ma è abbastanza semplice che non sia stato w il mio istante per provare a calzare questo nel paradigma Class-based.

0

Solo un commento, quando si utilizza il CBV è necessario salvare il modulo con commit = True, quindi viene creato il gruppo e un id è dato che può essere utilizzato per creare le iscrizioni. Altrimenti, con commit = False, l'oggetto gruppo non ha ancora un ID e si è verificato un errore.

+0

Qualsiasi possibilità di ottenere maggiori informazioni su questo ...? Seguendo la risposta accettata, l'unico modo per ottenere il codice per salvare l'appartenenza è usare 'commit = False', altrimenti getta il' AttributeError' mostrato nella domanda. Infatti, anche se provo a usare 'form.save()' dopo aver salvato 'Membership', ottengo ancora quell''attributo'. Tuttavia, come dici tu, non ci sono '' id' per group', in modo da 'Membership' non è in corso il salvataggio correttamente in ogni caso ... –

+0

Quello che volevo dire è che la soluzione proposta non avrebbe funzionato, perché, come si controllato , l'istanza di gruppo non è ancora stata salvata nel DB, quindi non ha ID. Quello che devi fare è creare un nuovo gruppo solo con un nome e salvarlo (commit = True). Ora ha un ID e puoi creare nuovi oggetti Membership. Riguardo l'errore, non ho provato il codice, quindi non conosco il motivo esatto. Forse, il modulo autogenerato include un campo obbligatorio con i membri ... Hai altri dettagli su questo? – kiril

+0

Creare un nuovo gruppo con un nuovo ID sembra essere buono, ma come posso inserire date_joined e invite_reason come ha detto Zad-Man? Qualcuno ha una soluzione completa con vista, modello. Penso che potrebbe aiutare a vedere come l'app "admin" l'ha risolta. – Timo

2
class GroupCreate(CreateView): 
    model = Group 

    def form_valid(self, form): 
     self.object = form.save(commit=False) 

     ### delete current mappings 
     Membership.objects.filter(group=self.object).delete() 

     ### find or create (find if using soft delete) 
     for member in form.cleaned_data['members']: 
      x, created = Membership.objects.get_or_create(group=self.object, person=member) 
      x.group = self.object 
      x.person = member 
      #x.alive = True # if using soft delete 
      x.save() 
     return super(ModelFormMixin, self).form_valid(form)