django表单:在单个表单中编辑多组相关对象
我正在尝试做一些非常常见的事情:以单一形式添加/编辑一组相关模型。例如:django表单:在单个表单中编辑多组相关对象,django,django-forms,django-orm,Django,Django Forms,Django Orm,我正在尝试做一些非常常见的事情:以单一形式添加/编辑一组相关模型。例如: Visitor Details: Select destinations and activities: Miami [] - swimming [], clubbing [], sunbathing[] Cancun [] - swimming [], clubbing [], sunbathing[] 我的模型是Visitor、Destination和Activity,Visitor通过
Visitor Details:
Select destinations and activities:
Miami [] - swimming [], clubbing [], sunbathing[]
Cancun [] - swimming [], clubbing [], sunbathing[]
我的模型是Visitor、Destination和Activity,Visitor通过中介模型VisitorDestination将多个字段转换为Destination,该模型包含要在目的地上执行的活动的详细信息(本身就是多个字段转换为Activity)
请注意,我不想输入新的目的地/活动值,只需从数据库中的可用值中选择(但这是M2M字段的完全合法使用,对吗?)
对我来说,这似乎是一种非常常见的情况(与其他模型中的FK或M2M字段的附加细节存在多对多关系),这看起来是最合理的建模,但如果我错了,请纠正我
我花了几天时间搜索Django docs/SO/google,但还没有找到解决方法。我尝试了几种方法:
inlineformset\u工厂
生成目标/活动表单集,使用inlineformset\u工厂(目标,访问者)
。这会中断,因为访问者与目的地的关系是M2M,而不是FKformset\u factory
自定义普通表单集,例如DestinationActivityFormSet=formset\u factory(DestinationActivityForm,extra=2)
。但是如何设计目标活动形式呢?我对这方面的研究还不够深入,但它看起来不太有希望:我不想输入目的地和活动列表,我想要一个带有标签的复选框列表,标签设置为我想要选择的目的地/活动,但是formset\u工厂
将返回一个带有相同标签的表单列表谢谢 因此,正如您所看到的,inlineformset_工厂的一个特点是它需要两个模型—一个父模型和一个与父模型有外键关系的子模型。对于中间模型中的额外数据,如何动态地将额外数据传递给表单 我如何做到这一点是通过使用咖喱:
from django.utils.functional import curry
from my_app.models import ParentModel, ChildModel, SomeOtherModel
def some_view(request, child_id, extra_object_id):
instance = ChildModel.objects.get(pk=child_id)
some_extra_model = SomeOtherModel.objects.get(pk=extra_object_id)
MyFormset = inlineformset_factory(ParentModel, ChildModel, form=ChildModelForm)
#This is where the object "some_extra_model" gets passed to each form via the
#static method
MyFormset.form = staticmethod(curry(ChildModelForm,
some_extra_model=some_extra_model))
formset = MyFormset(request.POST or None, request.FILES or None,
queryset=SomeObject.objects.filter(something=something), instance=instance)
form类“ChildModelForm”需要有一个init重写,用于从参数中添加“some_extra_model”对象:
def ChildModelForm(forms.ModelForm):
class Meta:
model = ChildModel
def __init__(self, some_extra_model, *args, **kwargs):
super(ChildModelForm, self).__init__(*args, **kwargs)
#do something with "some_extra_model" here
希望这能帮助您走上正轨。最后,我选择在同一个视图中处理多个表单,一个用于访问者详细信息的访问者模型表单,然后是每个目的地的自定义表单列表 在同一个视图中处理多个表单变得足够简单(至少在这种情况下,不存在跨字段验证问题) 我仍然感到惊讶的是,对于中介模型的多对多关系没有内置的支持,在网络上四处查看,我发现没有直接引用它。我会发布代码,以防对任何人有所帮助 首先是自定义表单:
class VisitorForm(ModelForm):
class Meta:
model = Visitor
exclude = ['destinations']
class VisitorDestinationForm(Form):
visited = forms.BooleanField(required=False)
activities = forms.MultipleChoiceField(choices = [(obj.pk, obj.name) for obj in Activity.objects.all()], required=False,
widget = CheckboxSelectMultipleInline(attrs={'style' : 'display:inline'}))
def __init__(self, visitor, destination, visited, *args, **kwargs):
super(VisitorDestinationForm, self).__init__(*args, **kwargs)
self.destination = destination
self.fields['visited'].initial = visited
self.fields['visited'].label= destination.destination
# load initial choices for activities
activities_initial = []
try:
visitorDestination_entry = VisitorDestination.objects.get(visitor=visitor, destination=destination)
activities = visitorDestination_entry.activities.all()
for activity in Activity.objects.all():
if activity in activities:
activities_initial.append(activity.pk)
except VisitorDestination.DoesNotExist:
pass
self.fields['activities'].initial = activities_initial
我通过传递访问者
和目的地
对象(以及为方便起见在外部计算的“已访问”标志)来定制每个表单
我使用一个布尔字段来允许用户选择每个目的地。该字段称为“已访问”,但我将标签设置为目的地,因此它可以很好地显示
这些活动由通常的multipleechoicefield处理(我使用I自定义小部件将复选框显示在表上,非常简单,但如果有人需要,可以将其发布)
然后查看代码:
def edit_visitor(request, pk):
visitor_obj = Visitor.objects.get(pk=pk)
visitorDestinations = visitor_obj.destinations.all()
if request.method == 'POST':
visitorForm = VisitorForm(request.POST, instance=visitor_obj)
# set up the visitor destination forms
destinationForms = []
for destination in Destination.objects.all():
visited = destination in visitorDestinations
destinationForms.append(VisitorDestinationForm(visitor_obj, destination, visited, request.POST, prefix=destination.destination))
if visitorForm.is_valid() and all([form.is_valid() for form in destinationForms]):
visitor_obj = visitorForm.save()
# clear any existing entries,
visitor_obj.destinations.clear()
for form in destinationForms:
if form.cleaned_data['visited']:
visitorDestination_entry = VisitorDestination(visitor = visitor_obj, destination=form.destination)
visitorDestination_entry.save()
for activity_pk in form.cleaned_data['activities']:
activity = Activity.objects.get(pk=activity_pk)
visitorDestination_entry.activities.add(activity)
print 'activities: %s' % visitorDestination_entry.activities.all()
visitorDestination_entry.save()
success_url = reverse('visitor_detail', kwargs={'pk' : visitor_obj.pk})
return HttpResponseRedirect(success_url)
else:
visitorForm = VisitorForm(instance=visitor_obj)
# set up the visitor destination forms
destinationForms = []
for destination in Destination.objects.all():
visited = destination in visitorDestinations
destinationForms.append(VisitorDestinationForm(visitor_obj, destination, visited, prefix=destination.destination))
return render_to_response('testapp/edit_visitor.html', {'form': visitorForm, 'destinationForms' : destinationForms, 'visitor' : visitor_obj}, context_instance= RequestContext(request))
我只需在一个列表中收集我的目标表单,并将该列表传递给我的模板,这样它就可以迭代并显示它们。只要您不忘记为构造函数中的每个前缀传递不同的前缀,它就可以正常工作
我将把这个问题留待几天,以防有人有更干净的方法
谢谢 django 1.9支持向表单集表单传递自定义参数: 只需将form_kwargs添加到FormSet init,如下所示:
from my_app.models import ParentModel, ChildModel, SomeOtherModel
def some_view(request, child_id, extra_object_id):
instance = ChildModel.objects.get(pk=child_id)
some_extra_model = SomeOtherModel.objects.get(pk=extra_object_id)
MyFormset = inlineformset_factory(ParentModel, ChildModel, form=ChildModelForm)
formset = MyFormset(request.POST or None, request.FILES or None,
queryset=SomeObject.objects.filter(something=something), instance=instance,
form_kwargs={"some_extra_model": some_extra_model})
我喜欢使用curry在模型表单集中使用自定义构造函数的想法,但仍然存在inlineformset_factory要求两个模型通过FK链接的问题,而我的模型通过M2M链接。如果我尝试使用它们,我会得到一个错误,说子模型没有到父模型的FK,或者我从错误的角度来看这个问题,也许我应该让我的表单使用VisitorDestination作为基础模型,它有到Visitor和Destination的FK,以及到Activity的M2M,并以此作为我的表单集的基础如果没有看到确切的模型、视图、工作流程等,我不确定你的应用程序的正确解决方案是什么,但是,听起来你可能朝着阻力最小的方向前进。看起来在Django 1.10中,此解决方案不再有效。现在出现此错误:AttributeError:“函数”对象没有属性“\u meta”。寻找解决方案,如果我发现有用的东西,我会添加一个答案。@martync让我知道你找到了什么感谢这篇文章-我一直认为文档和教程没有为初学者提供足够清晰的信息。从“民意调查”或“博客”到在大多数web应用程序中真正有用的表单种类有了巨大的飞跃。在编辑模型的单个实例后,处理表单中的多个对象可能是最常见的应用程序
from my_app.models import ParentModel, ChildModel, SomeOtherModel
def some_view(request, child_id, extra_object_id):
instance = ChildModel.objects.get(pk=child_id)
some_extra_model = SomeOtherModel.objects.get(pk=extra_object_id)
MyFormset = inlineformset_factory(ParentModel, ChildModel, form=ChildModelForm)
formset = MyFormset(request.POST or None, request.FILES or None,
queryset=SomeObject.objects.filter(something=something), instance=instance,
form_kwargs={"some_extra_model": some_extra_model})