【问题标题】:Class-based views for M2M relationship with intermediate model与中间模型的 M2M 关系的基于类的视图
【发布时间】:2012-08-26 19:24:22
【问题描述】:

我在两个使用中间模型的模型之间建立了 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)

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

class GroupCreate(CreateView):
    model=Group

这会呈现一个包含 Group 对象上所有字段的表单,并为成员字段提供一个多选框,这对于简单的 M2M 关系来说是正确的。但是,没有办法指定date_joined或invite_reason,提交表单会出现以下AttributeError:

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

有没有一种巧妙的方法来覆盖通用 CreateView 的一部分,或者编写我自己的自定义视图来使用 mixins 来做到这一点?感觉这应该是框架的一部分,因为 Admin 界面使用内联自动处理与中间体的 M2M 关系。

【问题讨论】:

标签: django many-to-many


【解决方案1】:

你必须扩展CreateView

from django.views.generic import CreateView

class GroupCreate(CreateView):
    model=Group

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

正如documentation 所说,您必须为groupperson 之间的每个关系创建新的memberships。

我在这里看到了 form_valid 覆盖: Using class-based UpdateView on a m-t-m with an intermediary model

【讨论】:

  • 我没有机会对此进行测试(我最终走上了不同的路线),但它看起来是正确的方法。干杯!
  • 这不会让您有任何机会输入date_joinedinvite_reason...只是将它们保存为空...
【解决方案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】:

    几天前我也遇到了同样的问题。 Django 在处理中间 m2m 关系方面存在问题。

    这是我发现有用的解决方案:

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

    然后修改已定义表单的保存方法——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
    

    注意:我已经稍微改变了模型,如你所见(我有自己独特的 IntegerField 作为主键,不使用序列号。这就是它进入get_object_or_404 的方式

    【讨论】:

    • 这个答案没有多大帮助。你能展示你的模型吗?将 pk 从“id”更改为“group_id”没有多大意义。
    【解决方案4】:

    '作为参考,我最终没有使用基于类的视图,而是做了这样的事情:

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

    这可能是基于类的实现的起点,但它足够简单,我不值得尝试将其硬塞到基于类的范例中。

    【讨论】:

      【解决方案5】:

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

      【讨论】:

      • 有没有机会获得更多关于这方面的信息...?按照接受的答案,我可以获取代码以保存Membership 的唯一方法是使用commit=False,否则它会抛出问题中显示的AttributeError。事实上,即使我在保存Membership 后尝试使用form.save(),我仍然会得到AttributeError。但是,就像你说的那样,group 没有 id,所以 Membership 无论如何都没有被正确保存......
      • 我的意思是提出的解决方案不起作用,因为当您检查时,组实例尚未保存到数据库,因此它没有 id。你需要做的是创建一个新组并保存它(commit=True)。现在它有了一个 id,您可以创建新的 Membership 对象。关于错误,我没有尝试代码,所以我不知道确切的原因。也许,自动生成的表单包含一个带有成员的必填字段...您对此有更多详细信息吗?
      • 用新 id 创建一个新组似乎很好,但我怎样才能像 Zad-Man 所说的那样输入 date_joined 和 invite_reason 呢?有一个完整的解决方案与视图,模型。我认为看看“管理员”应用程序如何解决它可能会有所帮助。
      猜你喜欢
      • 1970-01-01
      • 2016-09-19
      • 1970-01-01
      • 2012-07-28
      • 2010-10-22
      • 2014-05-01
      • 1970-01-01
      • 2012-07-01
      • 1970-01-01
      相关资源
      最近更新 更多