我们将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 实例和主表单)。