Django 表单向导:为什么提交后不调用 done() 方法?
Django Form Wizard: why done() method is not called after submitting?
我正在使用 SessionWizardView
,但我不明白为什么从未调用 done()
方法。相反,在发布我的表单后,在最后一步中,我可以在我的服务器上看到一个 POST HTTP 200
,但这没有任何作用。
get_form()
方法按预期工作。
我怀疑是分心错误,因为我对另一个视图有完全相同的逻辑,而且效果很好。
下面是完整的代码。
景色
class DiscountsCreateView(PermissionRequiredCanHandleProducts,
ModelInContextMixin,
RestaurantMixin, SubSectionDiscounts,
SessionWizardView):
""" Wizard view to create a discount in 2 steps """
model = Discount # used for model context
form_list = [DiscountForm0, DiscountForm1]
template_name = "discounts/discount_add.html"
def get_form(self, step=None, data=None, files=None):
form = super().get_form(step, data, files)
if step is None:
step = self.steps.current
# step0 - name, kind, tax_rate
# => nothing special to do, always the same form
# step1 - specific fields related to the chosen kind
if step == '1':
step0_data = self.storage.get_step_data('0')
kind = step0_data['0-kind']
# combo => combo, combo_unit_price
if kind == Discount.COMBO:
form.fields['combo'].queryset = Combo.objects.restaurant(self.restaurant)
# NOTE : this is not a scalable way to show/hide fields (exponential)
form.fields['rebate_amount'].widget = forms.HiddenInput()
elif kind == Discount.REBATE:
form.fields['combo'].widget = forms.HiddenInput()
form.fields['combo_unit_price'].widget = forms.HiddenInput()
return form
def done(self, form_list, **kwargs):
data = [form.cleaned_data for form in form_list]
try:
Discount.objects.create(
name=data[0]['name'],
kind=data[0]['kind'],
tax_rate=data[0]['tax_rate'],
rebate_amount=data[1]['rebate_amount'],
combo=data[1]['combo'],
combo_unit_price=data[1]['combo_unit_price']
)
except Exception as e:
messages.add_message(self.request, messages.ERROR, MSG_DISCOUNT_ADD_KO.format(e))
else:
messages.add_message(self.request, messages.SUCCESS, MSG_DISCOUNT_ADD_OK)
return redirect(reverse('bo:discount-list'))
表格
class DiscountForm0(forms.Form):
name = forms.CharField(
label=verbose_display(Discount, 'name'))
kind = forms.ChoiceField(
label=verbose_display(Discount, 'kind'),
choices=Discount.KIND_CHOICES)
tax_rate = forms.ModelChoiceField(
label=verbose_display(Discount, 'tax_rate'),
queryset=TaxRate.objects.all())
class DiscountForm1(forms.Form):
"""
Contains all the specific fields for all discount kinds.
The goal is to only show the fields related to the right discount kind
"""
# For REBATE kind only
rebate_amount = forms.DecimalField(
label=verbose_display(Discount, 'rebate_amount'),
validators=[MaxValueValidator(0)])
# For COMBO kind only
combo = forms.ModelChoiceField(
label=verbose_display(Discount, 'combo'),
queryset=Combo.objects.none())
combo_unit_price = forms.DecimalField(
label=verbose_display(Discount, 'combo_unit_price'),
validators=[MinValueValidator(0)])
模板
add_discount.html
{% extends "base_dashboard.html" %}
{% load verbose_name %}
{% block dashboard_title %}
Créer une {% model_name model %} : étape {{ wizard.steps.step1 }} / {{ wizard.steps.count }}
{% endblock dashboard_title %}
{% block dashboard_content %}
<form action='' method='post' novalidate>
{% csrf_token %}
{% include 'includes/_wizard_form_horizontal.html' with wizard=wizard %}
</form>
{% endblock dashboard_content %}
_wizard_form_horizontal.html
{{ wizard.management_form }}
{% if wizard.form.forms %}
{{ wizard.form.management_form }}
{% for form in wizard.form.forms %}
{% include 'includes/_form_horizontal.html' with form=form %}
{% endfor %}
{% else %}
{% include 'includes/_form_horizontal.html' with form=wizard.form %}
{% endif %}
{% if wizard.steps.prev %}
<button class="btn btn-primary" name="wizard_goto_step" type="submit"
value="{{ wizard.steps.prev }}">
« étape précédente
</button>
{% endif %}
<input type="submit" class="btn btn-primary" value="étape suivante »"/>
如果 form
在最后一步 is_valid()
中提交,则始终调用 done()
方法。因此,如果不是,那一定意味着您的 form
无效。
在您的情况下,您隐藏了 DiscountForm1
所需的字段。所以你也隐藏了这些字段的错误。如果填写了适当的字段,您应该将它们设为可选并检查表单的 clean()
方法。
我正在使用 SessionWizardView
,但我不明白为什么从未调用 done()
方法。相反,在发布我的表单后,在最后一步中,我可以在我的服务器上看到一个 POST HTTP 200
,但这没有任何作用。
get_form()
方法按预期工作。
我怀疑是分心错误,因为我对另一个视图有完全相同的逻辑,而且效果很好。
下面是完整的代码。
景色
class DiscountsCreateView(PermissionRequiredCanHandleProducts,
ModelInContextMixin,
RestaurantMixin, SubSectionDiscounts,
SessionWizardView):
""" Wizard view to create a discount in 2 steps """
model = Discount # used for model context
form_list = [DiscountForm0, DiscountForm1]
template_name = "discounts/discount_add.html"
def get_form(self, step=None, data=None, files=None):
form = super().get_form(step, data, files)
if step is None:
step = self.steps.current
# step0 - name, kind, tax_rate
# => nothing special to do, always the same form
# step1 - specific fields related to the chosen kind
if step == '1':
step0_data = self.storage.get_step_data('0')
kind = step0_data['0-kind']
# combo => combo, combo_unit_price
if kind == Discount.COMBO:
form.fields['combo'].queryset = Combo.objects.restaurant(self.restaurant)
# NOTE : this is not a scalable way to show/hide fields (exponential)
form.fields['rebate_amount'].widget = forms.HiddenInput()
elif kind == Discount.REBATE:
form.fields['combo'].widget = forms.HiddenInput()
form.fields['combo_unit_price'].widget = forms.HiddenInput()
return form
def done(self, form_list, **kwargs):
data = [form.cleaned_data for form in form_list]
try:
Discount.objects.create(
name=data[0]['name'],
kind=data[0]['kind'],
tax_rate=data[0]['tax_rate'],
rebate_amount=data[1]['rebate_amount'],
combo=data[1]['combo'],
combo_unit_price=data[1]['combo_unit_price']
)
except Exception as e:
messages.add_message(self.request, messages.ERROR, MSG_DISCOUNT_ADD_KO.format(e))
else:
messages.add_message(self.request, messages.SUCCESS, MSG_DISCOUNT_ADD_OK)
return redirect(reverse('bo:discount-list'))
表格
class DiscountForm0(forms.Form):
name = forms.CharField(
label=verbose_display(Discount, 'name'))
kind = forms.ChoiceField(
label=verbose_display(Discount, 'kind'),
choices=Discount.KIND_CHOICES)
tax_rate = forms.ModelChoiceField(
label=verbose_display(Discount, 'tax_rate'),
queryset=TaxRate.objects.all())
class DiscountForm1(forms.Form):
"""
Contains all the specific fields for all discount kinds.
The goal is to only show the fields related to the right discount kind
"""
# For REBATE kind only
rebate_amount = forms.DecimalField(
label=verbose_display(Discount, 'rebate_amount'),
validators=[MaxValueValidator(0)])
# For COMBO kind only
combo = forms.ModelChoiceField(
label=verbose_display(Discount, 'combo'),
queryset=Combo.objects.none())
combo_unit_price = forms.DecimalField(
label=verbose_display(Discount, 'combo_unit_price'),
validators=[MinValueValidator(0)])
模板
add_discount.html
{% extends "base_dashboard.html" %}
{% load verbose_name %}
{% block dashboard_title %}
Créer une {% model_name model %} : étape {{ wizard.steps.step1 }} / {{ wizard.steps.count }}
{% endblock dashboard_title %}
{% block dashboard_content %}
<form action='' method='post' novalidate>
{% csrf_token %}
{% include 'includes/_wizard_form_horizontal.html' with wizard=wizard %}
</form>
{% endblock dashboard_content %}
_wizard_form_horizontal.html
{{ wizard.management_form }}
{% if wizard.form.forms %}
{{ wizard.form.management_form }}
{% for form in wizard.form.forms %}
{% include 'includes/_form_horizontal.html' with form=form %}
{% endfor %}
{% else %}
{% include 'includes/_form_horizontal.html' with form=wizard.form %}
{% endif %}
{% if wizard.steps.prev %}
<button class="btn btn-primary" name="wizard_goto_step" type="submit"
value="{{ wizard.steps.prev }}">
« étape précédente
</button>
{% endif %}
<input type="submit" class="btn btn-primary" value="étape suivante »"/>
如果 form
在最后一步 is_valid()
中提交,则始终调用 done()
方法。因此,如果不是,那一定意味着您的 form
无效。
在您的情况下,您隐藏了 DiscountForm1
所需的字段。所以你也隐藏了这些字段的错误。如果填写了适当的字段,您应该将它们设为可选并检查表单的 clean()
方法。