2012-09-01 9 views
5

Mam relację M2M między dwoma modelami, która korzysta z modelu pośredniego. Dla dobra dyskusji, użyjmy przykładu z podręcznika:Widoki oparte na klasach dla relacji M2M z modelem pośrednim

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) 

Chciałbym skorzystać z widokiem na bazie Klasy Django, aby uniknąć pisania widoki CRUD obsługi. Jednak, gdy próbuję użyć domyślnego CreateView, to nie działa:

class GroupCreate(CreateView): 
    model=Group 

Czyni to postać ze wszystkich pól na obiekcie grupy i daje pole wielokrotnego wyboru dla pola członków, co byłoby poprawne dla prostej relacji M2M. Jednak nie można określić daty date_joined lub invite_reason, a przesłanie formularza daje następujący atrybut AttributeError:

"Nie można ustawić wartości w polu ManyToManyField, który określa model pośredniczący. Zamiast tego należy użyć menedżera członkostwa."

Czy istnieje sposób na przesłonięcie części ogólnego widoku CreateView lub utworzenie własnego widoku niestandardowego, aby zrobić to za pomocą mixins? Wydaje się, że powinno to być częścią struktury, ponieważ interfejs administracyjny atomatycznie obsługuje relacje M2M ze związkami pośrednimi za pomocą inline.

+0

możliwy duplikat [django Nie można ustawić wartości w polu ManyToManyField, który określa model pośredniczący. Zamiast tego użyj Menedżera] (http://stackoverflow.com/questions/3091328/django-cannot-set-values-on-a-manytomanyfield- który-specifies-an-intermediary-mo) – juliocesar

Odpowiedz

6

należy rozszerzyć CreateView:

from django.views.generic import CreateView 

class GroupCreate(CreateView): 
    model=Group 

i zastąpić 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) 

Jak documentation mówi, należy utworzyć nowy membership s dla każdej relacji group i person.

Widziałem form_valid nadpisanie tutaj: Using class-based UpdateView on a m-t-m with an intermediary model

+0

Nie miałem okazji przetestować tego (i skończyło się na innej drodze), ale wygląda na to, że jest to właściwa metoda. Twoje zdrowie! – Symmetric

+0

To nie daje ci szansy na wpisanie 'date_joined' i' invite_reason' ... tylko zapisuje je puste ... –

0

Jeszcze kilka dni temu miałem do czynienia z tym samym problemem. Django ma problemy z przetwarzaniem pośrednich relacji m2m.

To rozwiązania jakie znalazłem przydatne:

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

Następnie zmienić metodę zapisywania w określonej formie - GroupCreateForm. Save jest odpowiedzialny za wprowadzanie zmian na stałe w DB. I nie był w stanie dokonać tej pracy tylko przez ORM, więc użyłem zbyt surowy SQL:

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 

Uwaga: Zmieniłem modelowi trochę, jak widać (mam swój własny niepowtarzalny IntegerField dla klucza podstawowego, nie używając seryjny, który znajduje się w jaki sposób dostał się get_object_or_404

+0

Ta odpowiedź niewiele pomoże. Czy możesz pokazać swój model? Zmiana pk z "id" na "group_id" nie ma większego sensu. – Timo

0

„dla porównania, nie kończy się przy użyciu widoku klasy oparte zamiast zrobiłem coś takiego:.

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

Może to być punkt wyjścia dla implementacji opartej na Klasy, ale jest to dość proste, że nie było orth mój czas, aby spróbować zepsuć to do paradygmatu klasy.

0

Tylko jedna uwaga, przy użyciu CBV trzeba zapisać formularz z popełnić = True, więc grupa jest tworzona i id podano, które mogą być wykorzystane do utwórz członkostwo. W przeciwnym razie, z commit = False, obiekt grupy nie ma jeszcze identyfikatora i powstaje błąd.

+0

Czy masz szansę na zdobycie więcej informacji na ten temat ...? Po zaakceptowanej odpowiedzi jedyny sposób, w jaki mogę dostać się do kodu, aby zapisać "Członkostwo", to użycie 'commit = False', w przeciwnym razie rzuci" AttributeError "pokazany w pytaniu. W rzeczywistości, nawet jeśli spróbuję użyć 'form.save()' po zapisaniu 'Membership', nadal otrzymuję' AttributeError'. Jednak, jak mówisz, nie ma "id" dla "grupy", więc "Członkostwo" i tak nie jest właściwie zapisywane ... –

+0

Co miałem na myśli to, że proponowane rozwiązanie nie zadziałało, ponieważ jak sprawdziłeś instancja grupy nie jest jeszcze zapisana w DB, więc nie ma identyfikatora. Musisz utworzyć nową grupę tylko z nazwą i zapisać ją (commit = True). Teraz ma identyfikator i możesz tworzyć nowe obiekty członkowskie. O błędzie, nie próbowałem kodu, więc nie znam dokładnego powodu. Być może autogenerowany formularz zawiera pole obowiązkowe z członkami ... Czy masz więcej szczegółów na ten temat? – kiril

+0

Tworzenie nowej grupy z nowym identyfikatorem wydaje się być dobre, ale jak mogę wpisać date_joined i invite_reason, jak powiedział Zad-Man? Ma kogoś kompletne rozwiązanie z widokiem, model. Myślę, że może to pomóc, aby rozwiązać problem z aplikacją "admin". – 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) 
Powiązane problemy