django 表单:在单个表单中编辑多组相关对象 [英] django forms: editing multiple sets of related objects in a single form

查看:18
本文介绍了django 表单:在单个表单中编辑多组相关对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm trying to do something that should be very common: add/edit a bunch of related models in a single form. For example:

Visitor Details:
Select destinations and activities:
    Miami  []   -  swimming [], clubbing [], sunbathing[]
    Cancun []   -  swimming [], clubbing [], sunbathing[]

我的模型是访问者、目标和活动,访问者通过中间模型访问者目标具有进入目标的多对多字段,访问者目标具有要在目标上完成的活动的详细信息(本身是进入活动的多对多字段).

My models are Visitor, Destination and Activity, with Visitor having a ManyToMany field into Destination through an intermediary model, VisitorDestination, which has the details of the activities to be done on the destination (in itself a ManyToMany field into Activity).

Visitor ---->(M2M though VisitorDestination) -------------> Destination
                                            |
                       activities            ---->(M2M)---> Activity  

请注意,我不想输入目的地/活动值,只是从数据库中可用的值中选择(但这是对 M2M 字段的完全合法使用对吗?)

Note that I don't want to enter new destination / activity values, just choose from those available in the db (but that's a perfectly legit use of M2M fields right?)

对我来说,这看起来是一种非常普遍的情况(与其他细节的多对多关系,这些细节是 FK 或 M2M 字段到其他模型中),这看起来是最明智的建模,但如果我'我错了.

To me this looks like an extremely common situation (a many to many relation with additional details which are a FK or M2M field into some other model), and this looks like the most sensible modelling, but please correct me if I'm wrong.

我花了几天时间搜索 Django 文档/SO/谷歌搜索,但一直无法弄清楚如何处理这个问题.我尝试了几种方法:

I've spent a few days searching Django docs / SO / googling but haven't been able to work out how to deal with this. I tried several approaches:

  1. 访客的自定义模型表单,我在其中为目的地和活动添加了多个选择字段.如果 Destination 和 Activity 可以独立选择,那就没问题了,但这里它们相关,即我想为每个目的地选择一个或多个活动

  1. Custom Model form for Visitor, where I add multiple choice fields for Destination and Activity. That works ok if Destination and Activity could be selected independently, but here they are correlated, ie I want to choose one or several activities for each destination

使用inlineformset_factory 生成一组目的地/活动表单,使用inlineformset_factory(Destination, Visitor).这打破了,因为访问者与目的地有 M2M 关系,而不是 FK.

Using inlineformset_factory to generate the set of destination / activities forms, with inlineformset_factory(Destination, Visitor). This breaks, because Visitor has a M2M relation to Destination, rather than a FK.

使用formset_factory 自定义普通表单集,例如DestinationActivityFormSet = formset_factory(DestinationActivityForm, extra=2).但是如何设计DestinationActivityForm?我还没有对此进行足够的探索,但它看起来不太有希望:我不想输入目的地和活动列表,我想要一个复选框列表,其中标签设置为我想要的目的地/活动选择,但 formset_factory 将返回具有相同标签的表单列表.

Customizing a plain formset, using formset_factory, eg DestinationActivityFormSet = formset_factory(DestinationActivityForm, extra=2). But how to design DestinationActivityForm? I haven't explored this enough, but it doesn't look very promising: I don't want to type in the destination and a list of activities, I want a list of checkboxes with the labels set to the destination / activities I want to select, but the formset_factory would return a list of forms with identical labels.

我是 django 的完全新手,所以也许解决方案是显而易见的,但我发现这方面的文档非常薄弱 - 如果有人有一些指向表单/表单集使用示例的指示,也会有所帮助

I'm a complete newbie with django so maybe the solution is obvious, but I find that the documentation in this area is very weak - if anyone has some pointers to examples of use for forms / formsets that would be also helpful

谢谢!

推荐答案

最后,我选择在同一个视图中处理多个表单,一个用于访问者详细信息的访问者模型表单,然后是每个表单的自定义表单列表目的地.

In the end I opted for processing multiple forms within the same view, a Visitor model form for the visitor details, then a list of custom forms for each of the destinations.

在同一个视图中处理多个表单非常简单(至少在这种情况下,没有跨字段验证问题).

Processing multiple forms in the same view turned out to be simple enough (at least in this case, where there were no cross-field validation issues).

我仍然很惊讶没有内置支持多对多与中介模型的关系,并且在网上环顾四周我发现没有直接引用它.如果它对任何人有帮助,我会发布代码.

I'm still surprised there is no built-in support for many to many relationships with an intermediary model, and looking around in the web I found no direct reference to it. I'll post the code in case it helps anyone.

首先是自定义表单:

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

我通过传递一个 VisitorDestination 对象(以及一个为方便起见在外部计算的已访问"标志)来自定义每个表单

I customize each form by passing a Visitor and Destination objects (and a 'visited' flag which is calculated outside for convenience)

我使用布尔字段来允许用户选择每个目的地.该字段称为已访问",但是我将标签设置为目的地,以便很好地显示.

I use a boolean field to allow the user to select each destination. The field is called 'visited', however I set the label to the destination so it gets nicely displayed.

活动由通常的 MultipleChoiceField 处理(我使用我自定义的小部件让复选框显示在表格上,非常简单,但如果有人需要,可以发布)

The activities get handled by the usual MultipleChoiceField (I used I customized widget to get the checkboxes to display on a table, pretty simple but can post it if somebody needs that)

然后查看代码:

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

我只是将我的目标表单收集在一个列表中,然后将此列表传递给我的模板,以便它可以遍历它们并显示它们.只要您不忘记为构造函数中的每个前缀传递不同的前缀

I simply collect my destination forms in a list and pass this list to my template, so that it can iterate over them and display them. It works well as long as you don't forget to pass a different prefix for each one in the constructor

如果有人有更简洁的方法,我会将这个问题保留几天.

I'll leave the question open for a few days in case some one has a cleaner method.

谢谢!

这篇关于django 表单:在单个表单中编辑多组相关对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆