Class-based views for M2M relationship with intermediate model

前端 未结 5 1752
时光取名叫无心
时光取名叫无心 2020-12-09 13:55

I have a M2M relationship between two Models which uses an intermediate model. For the sake of discussion, let\'s use the example from the manual:

class Pers         


        
相关标签:
5条回答
  • 2020-12-09 14:10

    Just one comment, when using CBV you need to save the form with commit=True, so the group is created and an id is given that can be used to create the memberships. Otherwise, with commit=False, the group object has no id yet and an error is risen.

    0 讨论(0)
  • 2020-12-09 14:15
    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)
    
    0 讨论(0)
  • 2020-12-09 14:17

    I was facing pretty the same problem just a few days ago. Django has problems to process intermediary m2m relationships.

    This is the solutions what I have found useful:

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

    Then alter the save method of defined form - GroupCreateForm. Save is responsible for making changes permanent to DB. I wasn't able to make this work just through ORM, so I've used raw SQL too:

    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
    

    Note:I've changed the model a little bit, as you can see (i have own unique IntegerField for primary key, not using serial. That's how it got into get_object_or_404

    0 讨论(0)
  • 2020-12-09 14:26

    You must extend CreateView:

    from django.views.generic import CreateView
    
    class GroupCreate(CreateView):
        model=Group
    

    and override the 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)
    

    As the documentation says, you must create new memberships for each relation between group and person.

    I saw the form_valid override here: Using class-based UpdateView on a m-t-m with an intermediary model

    0 讨论(0)
  • 2020-12-09 14:34

    'For reference, I didn't end up using a class-based view, instead I did something like this:

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

    This may be a starting point for a Class-based implementation, but it's simple enough that it's not been worth my while to try to shoehorn this into the Class-based paradigm.

    0 讨论(0)
提交回复
热议问题