2012-09-01 4 views
5

Ich habe eine M2M-Beziehung zwischen zwei Modellen, die ein Zwischenmodell verwenden. Aus Gründen der Diskussion, lassen Sie uns das Beispiel aus dem Handbuch verwenden:Klassenbasierte Ansichten für die M2M-Beziehung mit dem Intermediate-Modell

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) 

Ich möchte Verwendung von Djangos klassenbasierte Ansichten machen, schreiben CRUD-Handling Ansichten zu vermeiden. Allerdings, wenn ich versuche, das Standard-Create zu verwenden, funktioniert es nicht:

class GroupCreate(CreateView): 
    model=Group 

Dies macht ein Formular mit allen Feldern auf dem Gruppenobjekt, und gibt ein Mehrfachauswahlfeld für die Mitglieder Feld, was wäre richtig für eine einfache M2M-Beziehung. Es gibt jedoch keine Möglichkeit, date_joined oder invite_reason anzugeben, und das Übermitteln des Formulars gibt das folgende AttributeError:

"Kann Werte auf einem ManyToManyField nicht festlegen, das ein intermediäres Modell angibt. Verwenden Sie stattdessen Membership Manager."

Gibt es eine gute Möglichkeit, einen Teil des generischen CreateView zu überschreiben oder meine eigene benutzerdefinierte Ansicht zu erstellen, um dies mit Mixins zu tun? Es fühlt sich an, als sollte dies Teil des Frameworks sein, da das Admin-Interface automatisch M2M-Beziehungen mit Intermediates behandelt, die Inlines verwenden.

+0

mögliches Duplikat von [django Kann keine Werte für ein ManyToManyField festlegen, das ein intermediäres Modell angibt. Benutze stattdessen Manager] (http://stackoverflow.com/questions/3091328/django-cannot-set-values-on-a-manytomanyfield-which-specifies-an-intermediary-mo) – juliocesar

Antwort

6

Sie müssen CreateView erweitern:

from django.views.generic import CreateView 

class GroupCreate(CreateView): 
    model=Group 

und überschreiben die 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) 

Wie der documentation sagt, müssen Sie für jede Beziehung zwischen group und person neuen membership s erstellen.

sah ich die form_valid Überschreibung hier: Using class-based UpdateView on a m-t-m with an intermediary model

+0

Ich hatte keine Gelegenheit, dies zu testen (und ich landete eine andere Route), aber es sieht wie die richtige Methode aus. Prost! – Symmetric

+0

Das gibt dir keine Chance, 'date_joined' und' invite_reason' einzugeben ... es speichert sie einfach leer ... –

0

Ich war vor ein paar Tagen ziemlich genau das gleiche Problem konfrontiert. Django hat Probleme, intermediate m2m-Beziehungen zu verarbeiten.

Dies sind die Lösungen, was ich nützlich gefunden habe:

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

dann das Speichern ändern Methode definierter Form - GroupCreateForm. Save ist dafür verantwortlich, Änderungen dauerhaft in der DB vorzunehmen. Ich nicht in der Lage war nur durch ORM diese Arbeit zu machen, so habe ich roh SQL auch verwendet:

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 

Hinweis: Ich habe das Modell ein wenig geändert, wie man sehen kann (ich habe ganz eigen Integer für Primärschlüssel, mit Serien nicht das ist, wie es in get_object_or_404 bekam

+0

Diese Antwort hilft nicht viel. Können Sie Ihr Modell zeigen? Das Ändern der PK von "id" zu "group_id" macht wenig Sinn. – Timo

0

‚Als Referenz ich habe keine klassenbasierten Ansicht am Ende mit, stattdessen habe ich etwas wie folgt aus:.

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), 
    ) 

Dies kann ein Ausgangspunkt für eine klassenbasierte Implementierung sein, aber es ist einfach genug, dass es nicht w war Während ich versuche, dies in das klassenbasierte Paradigma zu schärfen.

0

Nur einen Kommentar, wenn CBV verwenden, müssen Sie das Formular speichern mit = True begehen, so dass die Gruppe erstellt wird und eine ID gegeben, die verwendet werden können, Erstellen Sie die Mitgliedschaften. Andernfalls, mit commit = False, hat das Gruppenobjekt noch keine ID und ein Fehler ist aufgetreten.

+0

Hast du eine Chance, mehr Infos dazu zu bekommen ...? Nach der akzeptierten Antwort ist der einzige Weg, den Code zum Speichern der "Mitgliedschaft" zu erhalten, die Verwendung von 'commit = False', andernfalls wird der in der Frage gezeigte' AttributeError' ausgelöst. In der Tat, auch wenn ich versuche, 'form.save()' nach dem Speichern der 'Mitgliedschaft' zu verwenden, bekomme ich immer noch diesen' AttributeError'. Wie du aber sagst, es gibt keine 'ID' für 'Gruppe', also 'Mitgliedschaft' wird sowieso nicht richtig gespeichert ... –

+0

Was ich meinte ist, dass die vorgeschlagene Lösung nicht funktionieren würde, weil du überprüft hast , die Gruppeninstanz wird noch nicht in der DB gespeichert, also hat sie keine ID. Was Sie tun müssen, ist eine neue Gruppe nur mit einem Namen zu erstellen und zu speichern (commit = True). Jetzt hat es eine ID und Sie können neue Mitgliedschaftsobjekte erstellen. Über den Fehler habe ich den Code nicht getestet, daher kenne ich den genauen Grund nicht. Vielleicht enthält das automatisch generierte Formular ein Pflichtfeld mit Mitgliedern ... Haben Sie weitere Details dazu? – kiril

+0

Erstellen einer neuen Gruppe mit neuer ID scheint gut zu sein, aber wie kann ich date_joined und invite_reason eingeben, wie Zad-Man sagte? Hat jemand eine komplette Lösung mit Sicht, Modell. Ich denke, es könnte helfen zu sehen, wie die "admin" -App es löste. – 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) 
Verwandte Themen