我在使用中间模型的两个模型之间存在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对象上所有字段的表单,并为members字段提供一个多选框,这对于简单的M2M关系是正确的。但是,无法指定date_joined或invite_reason,并且提交表单会产生以下AttributeError:
"无法在指定中间模型的ManyToManyField上设置值。请改用会员管理员。"
是否有一种巧妙的方法来覆盖通用CreateView的一部分,或者使用mixins组合我自己的自定义视图来执行此操作?感觉这应该是框架的一部分,因为Admin界面使用内联自动处理与中间体的M2M关系。
答案 0 :(得分:6)
您必须延长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所述,您必须为membership
和group
之间的每个关系创建新的person
。
我在这里看到了form_valid
覆盖:
Using class-based UpdateView on a m-t-m with an intermediary model
答案 1 :(得分: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)
答案 2 :(得分:0)
几天前我面临着同样的问题。 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用于主键,而不是使用serial。这就是它进入get_object_or_404
的方式
答案 3 :(得分:0)
'作为参考,我最终没有使用基于类的视图,而是做了类似的事情:
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),
)
这可能是基于类的实现的起点,但它很简单,以至于尝试将其变为基于类的范例是不值得的。
答案 4 :(得分:0)
只需一条评论,当使用CBV时,您需要使用commit = True保存表单,因此创建了组并且给出了可用于创建成员资格的ID。 否则,使用commit = False,组对象还没有id,并且出现错误。