#30261: Calling a form method _html_output modifies the self._errors dict for
NON_FIELD_ERRORS if there are hidden field with errors
--------------------------------------+-----------------------------
               Reporter:  bmampaey    |          Owner:  nobody
                   Type:  Bug         |         Status:  new
              Component:  Forms       |        Version:  2.1
               Severity:  Normal      |       Keywords:  form errors
           Triage Stage:  Unreviewed  |      Has patch:  0
    Needs documentation:  0           |    Needs tests:  0
Patch needs improvement:  0           |  Easy pickings:  0
                  UI/UX:  0           |
--------------------------------------+-----------------------------
 Each time the  _html_output method of a form is called, it appends the
 errors of the hidden field errors to the NON_FIELD_ERRORS (__all__) entry.

 This happen for example when the form methods as_p() as_table() as_ul()
 are called multiple time, or any other method that themselves call one of
 them.

 For example, a test form with an hidden input field that add errors during
 the clean call.

 {{{
 Python 3.6.5 (default, Apr 25 2018, 14:26:36)
 Type 'copyright', 'credits' or 'license' for more information
 IPython 6.4.0 -- An enhanced Interactive Python. Type '?' for help.

 In [1]: import django

 In [2]: django.__version__
 Out[2]: '2.1.7'

 In [3]: from django import forms
    ...:

 In [4]: class TestForm(forms.Form):
    ...:     hidden_input = forms.CharField(widget=forms.HiddenInput)
    ...:
    ...:     def clean(self):
    ...:          self.add_error(None, 'Form error')
    ...:          self.add_error('hidden_input', 'Hidden input error')
    ...:

 In [5]: test_form = TestForm({})

 In [6]: test_form.errors
 Out[6]:
 {'hidden_input': ['This field is required.', 'Hidden input error'],
  '__all__': ['Form error']}

 In [7]: print(test_form.as_table())
 <tr><td colspan="2"><ul class="errorlist nonfield"><li>Form
 error</li><li>(Hidden field hidden_input) This field is
 required.</li><li>(Hidden field hidden_input) Hidden input
 error</li></ul><input type="hidden" name="hidden_input"
 id="id_hidden_input"></td></tr>

 In [8]: test_form.errors
 Out[8]:
 {'hidden_input': ['This field is required.', 'Hidden input error'],
  '__all__': ['Form error', '(Hidden field hidden_input) This field is
 required.', '(Hidden field hidden_input) Hidden input error']}

 In [9]: print(test_form.as_table())
 <tr><td colspan="2"><ul class="errorlist nonfield"><li>Form
 error</li><li>(Hidden field hidden_input) This field is
 required.</li><li>(Hidden field hidden_input) Hidden input
 error</li><li>(Hidden field hidden_input) This field is
 required.</li><li>(Hidden field hidden_input) Hidden input
 error</li></ul><input type="hidden" name="hidden_input"
 id="id_hidden_input"></td></tr>

 In [10]: test_form.errors
 Out[10]:
 {'hidden_input': ['This field is required.', 'Hidden input error'],
  '__all__': ['Form error', '(Hidden field hidden_input) This field is
 required.', '(Hidden field hidden_input) Hidden input error', '(Hidden
 field hidden_input) This field is required.', '(Hidden field hidden_input)
 Hidden input error']}

 In [11]: test_form.non_field_errors()
 Out[11]: ['Form error', '(Hidden field hidden_input) This field is
 required.', '(Hidden field hidden_input) Hidden input error', '(Hidden
 field hidden_input) This field is required.', '(Hidden field hidden_input)
 Hidden input error']
 }}}

 This bug affects probably also version 2.2.

 A simple fix would be to use a copy of the error list before adding the
 hidden field errors in the file django/forms/forms.py:

 {{{
 --- forms.py    2019-03-17 18:59:04.000000000 +0100
 +++ forms_fixed.py      2019-03-17 19:00:08.000000000 +0100
 @@ -194,7 +194,7 @@

      def _html_output(self, normal_row, error_row, row_ender,
 help_text_html, errors_on_separate_row):
          "Output HTML. Used by as_table(), as_ul(), as_p()."
 -        top_errors = self.non_field_errors()  # Errors that should be
 displayed above all fields.
 +        top_errors = self.non_field_errors().copy()  # Errors that should
 be displayed above all fields.
          output, hidden_fields = [], []

          for name, field in self.fields.items():
 }}}

-- 
Ticket URL: <https://code.djangoproject.com/ticket/30261>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/051.73137d3ad4baf7596f83e8052a2ab9c4%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to