Django 表单 : editing multiple sets of related objects in a single form

标签 django django-forms django-orm

我正在尝试做一些应该很常见的事情:在一个表单中添加/编辑一堆相关模型。例如:

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/谷歌搜索,但一直无法弄清楚如何处理这个问题。我尝试了几种方法:
  • 访客的自定义模型表单,我在其中为目的地和事件添加了多个选择字段。如果可以独立选择 Destination 和 Activity 就可以了,但这里是 相关 , 即我想为 选择一项或多项事件每个目的地
  • 使用 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/

    相关文章:

    python - Django:如何使用自定义模板制作表单?

    Python Django 表单多选 HTML 输出

    django - 如何在 Django 中获取表单字段的 ID

    django - Windows OS上的Django Haystack + ElasticSearch实现

    python - Django Rest 框架中的多部分解析器

    django - 在django queryset中获取反向相关模型字段的值

    django - 如何在django中使用多表继承复制对象

    python - Django:默认执行不区分大小写的查找

    django - 在 Grunt 中启动 Django 虚拟环境

    python - 错误 : 'login' not found. 'login' 不是有效的 View 函数或模式名称