Fixed #33622 -- Allowed customizing error messages for invalid number of forms.
Co-authored-by: Mariusz Felisiak <felisiak.mariusz@gmail.com>
This commit is contained in:
parent
667105877e
commit
262fde94de
1
AUTHORS
1
AUTHORS
|
@ -600,6 +600,7 @@ answer newbie questions, and generally made Django that much better:
|
||||||
Marc Garcia <marc.garcia@accopensys.com>
|
Marc Garcia <marc.garcia@accopensys.com>
|
||||||
Marcin Wróbel
|
Marcin Wróbel
|
||||||
Marc Remolt <m.remolt@webmasters.de>
|
Marc Remolt <m.remolt@webmasters.de>
|
||||||
|
Marc Seguí Coll <metarizard@gmail.com>
|
||||||
Marc Tamlyn <marc.tamlyn@gmail.com>
|
Marc Tamlyn <marc.tamlyn@gmail.com>
|
||||||
Marc-Aurèle Brothier <ma.brothier@gmail.com>
|
Marc-Aurèle Brothier <ma.brothier@gmail.com>
|
||||||
Marian Andre <django@andre.sk>
|
Marian Andre <django@andre.sk>
|
||||||
|
|
|
@ -6,7 +6,7 @@ from django.forms.utils import ErrorList, RenderableFormMixin
|
||||||
from django.forms.widgets import CheckboxInput, HiddenInput, NumberInput
|
from django.forms.widgets import CheckboxInput, HiddenInput, NumberInput
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.utils.translation import ngettext
|
from django.utils.translation import ngettext_lazy
|
||||||
|
|
||||||
__all__ = ("BaseFormSet", "formset_factory", "all_valid")
|
__all__ = ("BaseFormSet", "formset_factory", "all_valid")
|
||||||
|
|
||||||
|
@ -61,6 +61,16 @@ class BaseFormSet(RenderableFormMixin):
|
||||||
"ManagementForm data is missing or has been tampered with. Missing fields: "
|
"ManagementForm data is missing or has been tampered with. Missing fields: "
|
||||||
"%(field_names)s. You may need to file a bug report if the issue persists."
|
"%(field_names)s. You may need to file a bug report if the issue persists."
|
||||||
),
|
),
|
||||||
|
"too_many_forms": ngettext_lazy(
|
||||||
|
"Please submit at most %(num)d form.",
|
||||||
|
"Please submit at most %(num)d forms.",
|
||||||
|
"num",
|
||||||
|
),
|
||||||
|
"too_few_forms": ngettext_lazy(
|
||||||
|
"Please submit at least %(num)d form.",
|
||||||
|
"Please submit at least %(num)d forms.",
|
||||||
|
"num",
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
template_name_div = "django/forms/formsets/div.html"
|
template_name_div = "django/forms/formsets/div.html"
|
||||||
|
@ -425,12 +435,7 @@ class BaseFormSet(RenderableFormMixin):
|
||||||
TOTAL_FORM_COUNT
|
TOTAL_FORM_COUNT
|
||||||
] > self.absolute_max:
|
] > self.absolute_max:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
ngettext(
|
self.error_messages["too_many_forms"] % {"num": self.max_num},
|
||||||
"Please submit at most %d form.",
|
|
||||||
"Please submit at most %d forms.",
|
|
||||||
self.max_num,
|
|
||||||
)
|
|
||||||
% self.max_num,
|
|
||||||
code="too_many_forms",
|
code="too_many_forms",
|
||||||
)
|
)
|
||||||
if (
|
if (
|
||||||
|
@ -441,12 +446,7 @@ class BaseFormSet(RenderableFormMixin):
|
||||||
< self.min_num
|
< self.min_num
|
||||||
):
|
):
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
ngettext(
|
self.error_messages["too_few_forms"] % {"num": self.min_num},
|
||||||
"Please submit at least %d form.",
|
|
||||||
"Please submit at least %d forms.",
|
|
||||||
self.min_num,
|
|
||||||
)
|
|
||||||
% self.min_num,
|
|
||||||
code="too_few_forms",
|
code="too_few_forms",
|
||||||
)
|
)
|
||||||
# Give self.clean() a chance to do cross-form validation.
|
# Give self.clean() a chance to do cross-form validation.
|
||||||
|
|
|
@ -292,6 +292,11 @@ Forms
|
||||||
attributes help to identify widgets where its inputs should be grouped in a
|
attributes help to identify widgets where its inputs should be grouped in a
|
||||||
``<fieldset>`` with a ``<legend>``.
|
``<fieldset>`` with a ``<legend>``.
|
||||||
|
|
||||||
|
* The :ref:`formsets-error-messages` argument for
|
||||||
|
:class:`~django.forms.formsets.BaseFormSet` now allows customizing
|
||||||
|
error messages for invalid number of forms by passing ``'too_few_forms'``
|
||||||
|
and ``'too_many_forms'`` keys.
|
||||||
|
|
||||||
Generic Views
|
Generic Views
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -287,12 +287,20 @@ sure you understand what they do before doing so.
|
||||||
a form instance with a prefix of ``__prefix__`` for easier use in dynamic
|
a form instance with a prefix of ``__prefix__`` for easier use in dynamic
|
||||||
forms with JavaScript.
|
forms with JavaScript.
|
||||||
|
|
||||||
|
.. _formsets-error-messages:
|
||||||
|
|
||||||
``error_messages``
|
``error_messages``
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
The ``error_messages`` argument lets you override the default messages that the
|
The ``error_messages`` argument lets you override the default messages that the
|
||||||
formset will raise. Pass in a dictionary with keys matching the error messages
|
formset will raise. Pass in a dictionary with keys matching the error messages
|
||||||
you want to override. For example, here is the default error message when the
|
you want to override. Error message keys include ``'too_few_forms'``,
|
||||||
|
``'too_many_forms'``, and ``'missing_management_form'``. The
|
||||||
|
``'too_few_forms'`` and ``'too_many_forms'`` error messages may contain
|
||||||
|
``%(num)d``, which will be replaced with ``min_num`` and ``max_num``,
|
||||||
|
respectively.
|
||||||
|
|
||||||
|
For example, here is the default error message when the
|
||||||
management form is missing::
|
management form is missing::
|
||||||
|
|
||||||
>>> formset = ArticleFormSet({})
|
>>> formset = ArticleFormSet({})
|
||||||
|
@ -309,6 +317,10 @@ And here is a custom error message::
|
||||||
>>> formset.non_form_errors()
|
>>> formset.non_form_errors()
|
||||||
['Sorry, something went wrong.']
|
['Sorry, something went wrong.']
|
||||||
|
|
||||||
|
.. versionchanged:: 4.1
|
||||||
|
|
||||||
|
The ``'too_few_forms'`` and ``'too_many_forms'`` keys were added.
|
||||||
|
|
||||||
Custom formset validation
|
Custom formset validation
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
|
@ -410,6 +422,9 @@ deletion, is less than or equal to ``max_num``.
|
||||||
``max_num`` was exceeded because the amount of initial data supplied was
|
``max_num`` was exceeded because the amount of initial data supplied was
|
||||||
excessive.
|
excessive.
|
||||||
|
|
||||||
|
The error message can be customized by passing the ``'too_many_forms'`` message
|
||||||
|
to the :ref:`formsets-error-messages` argument.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
Regardless of ``validate_max``, if the number of forms in a data set
|
Regardless of ``validate_max``, if the number of forms in a data set
|
||||||
|
@ -446,6 +461,9 @@ deletion, is greater than or equal to ``min_num``.
|
||||||
>>> formset.non_form_errors()
|
>>> formset.non_form_errors()
|
||||||
['Please submit at least 3 forms.']
|
['Please submit at least 3 forms.']
|
||||||
|
|
||||||
|
The error message can be customized by passing the ``'too_few_forms'`` message
|
||||||
|
to the :ref:`formsets-error-messages` argument.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
Regardless of ``validate_min``, if a formset contains no data, then
|
Regardless of ``validate_min``, if a formset contains no data, then
|
||||||
|
|
|
@ -404,6 +404,37 @@ class FormsFormsetTestCase(SimpleTestCase):
|
||||||
'<ul class="errorlist nonform"><li>Please submit at most 1 form.</li></ul>',
|
'<ul class="errorlist nonform"><li>Please submit at most 1 form.</li></ul>',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_formset_validate_max_flag_custom_error(self):
|
||||||
|
data = {
|
||||||
|
"choices-TOTAL_FORMS": "2",
|
||||||
|
"choices-INITIAL_FORMS": "0",
|
||||||
|
"choices-MIN_NUM_FORMS": "0",
|
||||||
|
"choices-MAX_NUM_FORMS": "2",
|
||||||
|
"choices-0-choice": "Zero",
|
||||||
|
"choices-0-votes": "0",
|
||||||
|
"choices-1-choice": "One",
|
||||||
|
"choices-1-votes": "1",
|
||||||
|
}
|
||||||
|
ChoiceFormSet = formset_factory(Choice, extra=1, max_num=1, validate_max=True)
|
||||||
|
formset = ChoiceFormSet(
|
||||||
|
data,
|
||||||
|
auto_id=False,
|
||||||
|
prefix="choices",
|
||||||
|
error_messages={
|
||||||
|
"too_many_forms": "Number of submitted forms should be at most %(num)d."
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertFalse(formset.is_valid())
|
||||||
|
self.assertEqual(
|
||||||
|
formset.non_form_errors(),
|
||||||
|
["Number of submitted forms should be at most 1."],
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
str(formset.non_form_errors()),
|
||||||
|
'<ul class="errorlist nonform">'
|
||||||
|
"<li>Number of submitted forms should be at most 1.</li></ul>",
|
||||||
|
)
|
||||||
|
|
||||||
def test_formset_validate_min_flag(self):
|
def test_formset_validate_min_flag(self):
|
||||||
"""
|
"""
|
||||||
If validate_min is set and min_num is more than TOTAL_FORMS in the
|
If validate_min is set and min_num is more than TOTAL_FORMS in the
|
||||||
|
@ -431,6 +462,37 @@ class FormsFormsetTestCase(SimpleTestCase):
|
||||||
"Please submit at least 3 forms.</li></ul>",
|
"Please submit at least 3 forms.</li></ul>",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_formset_validate_min_flag_custom_formatted_error(self):
|
||||||
|
data = {
|
||||||
|
"choices-TOTAL_FORMS": "2",
|
||||||
|
"choices-INITIAL_FORMS": "0",
|
||||||
|
"choices-MIN_NUM_FORMS": "0",
|
||||||
|
"choices-MAX_NUM_FORMS": "0",
|
||||||
|
"choices-0-choice": "Zero",
|
||||||
|
"choices-0-votes": "0",
|
||||||
|
"choices-1-choice": "One",
|
||||||
|
"choices-1-votes": "1",
|
||||||
|
}
|
||||||
|
ChoiceFormSet = formset_factory(Choice, extra=1, min_num=3, validate_min=True)
|
||||||
|
formset = ChoiceFormSet(
|
||||||
|
data,
|
||||||
|
auto_id=False,
|
||||||
|
prefix="choices",
|
||||||
|
error_messages={
|
||||||
|
"too_few_forms": "Number of submitted forms should be at least %(num)d."
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertFalse(formset.is_valid())
|
||||||
|
self.assertEqual(
|
||||||
|
formset.non_form_errors(),
|
||||||
|
["Number of submitted forms should be at least 3."],
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
str(formset.non_form_errors()),
|
||||||
|
'<ul class="errorlist nonform">'
|
||||||
|
"<li>Number of submitted forms should be at least 3.</li></ul>",
|
||||||
|
)
|
||||||
|
|
||||||
def test_formset_validate_min_unchanged_forms(self):
|
def test_formset_validate_min_unchanged_forms(self):
|
||||||
"""
|
"""
|
||||||
min_num validation doesn't consider unchanged forms with initial data
|
min_num validation doesn't consider unchanged forms with initial data
|
||||||
|
|
Loading…
Reference in New Issue