具有可选信息的多对多Multiplechoice表单

时间:2016-12-09 08:51:22

标签: django django-models django-forms django-templates

在我的应用程序的早期版本中,我与帐户和俱乐部之间存在多对多的关系。在我的AccountForm中,我使用" club = forms.MultipleChoiceField(widget = CheckboxSelectMultiple)"使用户能够从俱乐部的完整列表中进行选择。

▢足球

▢曲棍球

▢网球

▢游泳

但是,我现在需要包含一个可选字段,如果有的话,可以包含其成员资格引用号。像

这样的东西

▢足球________

▢曲棍球________

▢网球________

▢游泳________

我意识到我必须使用through模型,但现在我正在努力复制之前的多选样式布局。

a)我认为我需要使用内联formset但基于直通表,所以不知何故我需要一个formset工厂为每个俱乐部创建表单。我不知道该怎么做。线索?

b)包含一个复选框以反映该俱乐部的成员资格。所以可能是一个布尔字段,其中隐藏字段指示了俱乐部的ID,然后是一些自定义工作清理和保存功能。

这看起来是对的,还是有更简单的方法?

class Account(models.Model):
    name = models.CharField(max_length=20)
    address_street01 = models.CharField(max_length=50)
    address_pc = models.CharField(max_length=10)
    address_city = models.CharField(max_length=50)

class Club(models.Model):
    name = models.CharField(max_length=30, unique=True)

class Membership(models.Model):
    club = models.ForeignKey(Club)
    account = models.ForeignKey(Account)
    membership_ref = models.CharField(max_length=50, blank=True)

1 个答案:

答案 0 :(得分:3)

我们正在使用django-extra-views中的ModelFormSetView来查找类似的用例。它不是由through模型支持,而是由具有多对一关系的表支持,其中与其所有属性的许多关系显示为通过ForeignKey关联的主模型的详细视图的一部分。

只需将through模型作为through的模型属性,它也适用于ModelFormSetView模型。保存时甚至之前,通过get_extra_form_kwargs,您必须设置对定义m2m字段的主模型实例的引用。

常规django FormSets的棘手问题是(对我来说)它主要用于创建新对象,而我们只需要显示现有对象并对其进行修改。基本上我们需要重复填充初始数据的表单,这些数据一次全部保存。也可以删除它们。

视图

# You could additionally try to inherit from SingleObjectMixin
# if you override the methods that refer to cls.model
class ImportMatchView(ImportSessionMixin, ModelFormSetView):
    template_name = 'import_match.html'
    model = Entry  # this is your through model class
    form_class = EntryForm
    can_delete = True

    def get_success_url(self):
        return self.get_main_object().get_absolute_url()

    def get_factory_kwargs(self):
        kwargs = super().get_factory_kwargs()
        num = len(self.get_match_result())
        kwargs['extra'] = num  # this controls how many forms are generated
        kwargs['max_num'] = num  # no empty forms!
        return kwargs

    def get_initial(self):
        # override this if you have to previous m2m relations for
        # this main object
        # this is a dictionary with the attributes required to prefill
        # new instances of the through model
        return self.get_match_result()  # this fetches data from the session

    def get_extra_form_kwargs(self):
        # you could add the instance of the m2m main model here and
        # handle it in your custom form.save method
        return {'user': self.request.user}

    def get_queryset(self):
        # return none() if you have implemented get_initial()
        return Entry.objects.none()
        # return existing m2m relations if they exist
        # main_id = self.get_object().pk  # SingleObjectMixin or alike
        # return Entry.objects.filter(main=main_id)

    def formset_valid(self, formset):
        # just some example code of what you could do
        main = self.get_main_object()
        response = super().formset_valid(formset)
        main_attr_list = filter(None, [form.cleaned_data.get('entry_attr') for form in formset.forms])
        main.main_attr = sum(main_attr_list)
        main.save()
        return response

表格

适用于through模型的常规Django ModelForm。与此处的用户一样,提供对定义m2m字段的模型实例的引用,以便您可以在保存之前进行分配。

def __init__(self, *args, user=None, **kwargs):
    self.user = user
    super().__init__(*args, **kwargs)

def save(self, commit=True):
    self.instance.owner = self.user
    return super().save(commit)

模板

<form id="the-matching" method="POST"
      action="{{ save_url }}" data-session-url="{{ session_url }}">
    {% csrf_token %}
    {{ formset.management_form }}
    <ul class="match__matches">
    {% for form in formset %}
        {% include 'import_match__match.html' %}
    {% endfor %}
    </ul>
</form>

在每种形式(内部import_match__match.html)中,您以通常的django方式迭代字段。这是隐藏字段的示例:

{% for field in form %}
{% if field.value %}
<input type="hidden" name="{{ form.prefix }}-{{ field.name }}" value="{{ field.value }}"/>
{% endif %}
{% endfor %}

处理主要对象的表单:

  • 您可以创建两个视图,并在点击一个“保存”按钮后通过JS提交给他们两个。
  • 或者您可以提交到一个视图(如上所述)并在get()和post()中显式创建主对象的表单,然后在调用formset_valid时保存它。
  • 您也可以尝试实现ModelFormsetView和FormView,并覆盖所有相关方法来处理两个表单实例(formset实例和主窗体)。