M2M与中间模型关系的基于类的视图

时间:2022-10-04 14:01:34

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:

我在两个使用中间模型的模型之间建立了M2M关系。为了便于讨论,让我们使用手册中的示例:

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)

I'd like to make use of Django's Class-based views, to avoid writing CRUD-handling views. However, if I try to use the default CreateView, it doesn't work:

我想使用Django的基于类的视图,以避免编写CRUD处理视图。但是,如果我尝试使用默认的CreateView,它不起作用:

class GroupCreate(CreateView):
    model=Group

This renders a form with all of the fields on the Group object, and gives a multi-select box for the members field, which would be correct for a simple M2M relationship. However, there is no way to specify the date_joined or invite_reason, and submitting the form gives the following AttributeError:

这将呈现一个包含Group对象上所有字段的表单,并为members字段提供一个多选框,这对于简单的M2M关系是正确的。但是,无法指定date_joined或invite_reason,并且提交表单会产生以下AttributeError:

"Cannot set values on a ManyToManyField which specifies an intermediary model. Use Membership's Manager instead."

“无法在指定中间模型的ManyToManyField上设置值。请改用Membership的管理器。”

Is there a neat way to override part of the generic CreateView, or compose my own custom view to do this with mixins? It feels like this should be part of the framework, as the Admin interface atomatically handles M2M relationships with intermediates using inlines.

是否有一种巧妙的方法来覆盖通用CreateView的一部分,或者使用mixins组合我自己的自定义视图来执行此操作?感觉这应该是框架的一部分,因为Admin界面使用内联自动处理与中间体的M2M关系。

5 个解决方案

#1


6  

You must extend CreateView:

您必须扩展CreateView:

from django.views.generic import CreateView

class GroupCreate(CreateView):
    model=Group

and override the form_valid():

并覆盖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

我在这里看到了form_valid覆盖:在具有中间模型的m-t-m上使用基于类的UpdateView

#2


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)

#3


0  

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

几天前我面临着同样的问题。 Django在处理中间m2m关系方面存在问题。

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:

然后更改已定义表单的保存方法 - GroupCreateForm。 Save负责对DB进行永久性更改。我无法通过ORM完成这项工作,所以我也使用了原始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

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

注意:我已经改变了一点模型,你可以看到(我有一个独特的IntegerField用于主键,而不是使用serial。这就是它进入get_object_or_404的方式

#4


0  

'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.

这可能是基于类的实现的起点,但它很简单,以至于尝试将其变为基于类的范例并不值得我这么做。

#5


0  

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.

只需注释一条,当使用CBV时,您需要使用commit = True保存表单,因此创建了组并且给出了可用于创建成员资格的id。否则,使用commit = False,组对象还没有id,并且出现错误。

#1


6  

You must extend CreateView:

您必须扩展CreateView:

from django.views.generic import CreateView

class GroupCreate(CreateView):
    model=Group

and override the form_valid():

并覆盖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

我在这里看到了form_valid覆盖:在具有中间模型的m-t-m上使用基于类的UpdateView

#2


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)

#3


0  

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

几天前我面临着同样的问题。 Django在处理中间m2m关系方面存在问题。

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:

然后更改已定义表单的保存方法 - GroupCreateForm。 Save负责对DB进行永久性更改。我无法通过ORM完成这项工作,所以我也使用了原始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

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

注意:我已经改变了一点模型,你可以看到(我有一个独特的IntegerField用于主键,而不是使用serial。这就是它进入get_object_or_404的方式

#4


0  

'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.

这可能是基于类的实现的起点,但它很简单,以至于尝试将其变为基于类的范例并不值得我这么做。

#5


0  

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.

只需注释一条,当使用CBV时,您需要使用commit = True保存表单,因此创建了组并且给出了可用于创建成员资格的id。否则,使用commit = False,组对象还没有id,并且出现错误。