我正在尝试做一些应该很常见的事情:在一个表单中添加/编辑一堆相关模型。例如:
Visitor Details:
Select destinations and activities:
Miami [] - swimming [], clubbing [], sunbathing[]
Cancun [] - swimming [], clubbing [], sunbathing[]
我的模型是访问者、目标和事件,访问者通过中间模型访问者目标具有进入目标的多对多字段,访问者目标具有要在目标上完成的事件的详细信息(本身是进入事件的多对多字段)。
Visitor ---->(M2M though VisitorDestination) -------------> Destination
|
activities ---->(M2M)---> Activity
请注意,我不想输入 新品 目的地/事件值,只是 选择 来自数据库中可用的那些(但这是对 M2M 字段的完全合法使用,对吗?)
对我来说,这看起来是一种非常常见的情况(与其他细节的多对多关系,这些细节是 FK 或 M2M 字段到其他模型中),这看起来是最明智的建模,但如果我错了,请纠正我。
我花了几天时间搜索 Django 文档/SO/谷歌搜索,但一直无法弄清楚如何处理这个问题。我尝试了几种方法:
inlineformset_factory
生成一组目的地/事件表单,使用 inlineformset_factory(Destination, Visitor)
.这打破了,因为访问者与目的地有 M2M 关系,而不是 FK。 formset_factory
自定义普通表单集,例如 DestinationActivityFormSet = formset_factory(DestinationActivityForm, extra=2)
.但是如何设计DestinationActivityForm
?我还没有对此进行足够的探索,但它看起来不太有希望:我不想输入目的地和事件列表,我想要一个复选框列表,其中标签设置为我想要的目的地/事件选择,但formset_factory
将返回具有相同标签的表单列表。 我是 django 的完全新手,所以也许解决方案很明显,但我发现这方面的文档非常薄弱 - 如果有人有一些指向表单/表单集使用示例的指示,这也会有帮助
谢谢!
最佳答案
最后,我选择在同一个 View 中处理多个表单,一个用于访问者详细信息的访问者模型表单,然后是每个目的地的自定义表单列表。
在同一个 View 中处理多个表单非常简单(至少在这种情况下,没有跨字段验证问题)。
我仍然感到惊讶的是,没有内置支持与中间模型的多对多关系,并且在网上环顾四周,我发现没有直接引用它。我会发布代码以防它对任何人有帮助。
首先是自定义表单:
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
我通过传递
Visitor
来自定义每个表单和 Destination
对象(以及为方便起见在外部计算的“已访问”标志)我使用 bool 字段来允许用户选择每个目的地。该字段称为“已访问”,但是我将标签设置为目的地,以便很好地显示。
事件由通常的 MultipleChoiceField 处理(我使用我自定义的小部件来让复选框显示在表格上,非常简单,但如果有人需要,可以发布它)
然后查看代码:
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 表单 : editing multiple sets of related objects in a single form,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8333127/