diff options
Diffstat (limited to 'tests/forms_tests')
| -rw-r--r-- | tests/forms_tests/templates/forms_tests/error.html | 1 | ||||
| -rw-r--r-- | tests/forms_tests/templates/forms_tests/form_snippet.html | 6 | ||||
| -rw-r--r-- | tests/forms_tests/tests/__init__.py | 30 | ||||
| -rw-r--r-- | tests/forms_tests/tests/test_deprecation_forms.py | 183 | ||||
| -rw-r--r-- | tests/forms_tests/tests/test_forms.py | 174 | ||||
| -rw-r--r-- | tests/forms_tests/tests/test_formsets.py | 32 | ||||
| -rw-r--r-- | tests/forms_tests/tests/test_i18n.py | 2 | ||||
| -rw-r--r-- | tests/forms_tests/tests/tests.py | 2 |
8 files changed, 293 insertions, 137 deletions
diff --git a/tests/forms_tests/templates/forms_tests/error.html b/tests/forms_tests/templates/forms_tests/error.html new file mode 100644 index 0000000000..4981d32b70 --- /dev/null +++ b/tests/forms_tests/templates/forms_tests/error.html @@ -0,0 +1 @@ +{% if errors %}<div class="errorlist">{% for error in errors %}<div class="error">{{ error }}</div>{% endfor %}</div>{% endif %} diff --git a/tests/forms_tests/templates/forms_tests/form_snippet.html b/tests/forms_tests/templates/forms_tests/form_snippet.html new file mode 100644 index 0000000000..90ab4f2981 --- /dev/null +++ b/tests/forms_tests/templates/forms_tests/form_snippet.html @@ -0,0 +1,6 @@ +{% for field in form %} + <div class="fieldWrapper"> + {{ field.errors }} + {{ field.label_tag }} {{ field }} + </div> +{% endfor %} diff --git a/tests/forms_tests/tests/__init__.py b/tests/forms_tests/tests/__init__.py index e69de29bb2..7fc0269ec5 100644 --- a/tests/forms_tests/tests/__init__.py +++ b/tests/forms_tests/tests/__init__.py @@ -0,0 +1,30 @@ +import inspect + +from django.test.utils import override_settings + +TEST_SETTINGS = [ + { + 'FORM_RENDERER': 'django.forms.renderers.DjangoTemplates', + 'TEMPLATES': {'BACKEND': 'django.template.backends.django.DjangoTemplates'}, + }, + { + 'FORM_RENDERER': 'django.forms.renderers.Jinja2', + 'TEMPLATES': {'BACKEND': 'django.template.backends.jinja2.Jinja2'}, + }, +] + + +def test_all_form_renderers(): + def wrapper(func): + def inner(*args, **kwargs): + for settings in TEST_SETTINGS: + with override_settings(**settings): + func(*args, **kwargs) + return inner + + def decorator(cls): + for name, func in inspect.getmembers(cls, inspect.isfunction): + if name.startswith('test_'): + setattr(cls, name, wrapper(func)) + return cls + return decorator diff --git a/tests/forms_tests/tests/test_deprecation_forms.py b/tests/forms_tests/tests/test_deprecation_forms.py new file mode 100644 index 0000000000..6bdb595602 --- /dev/null +++ b/tests/forms_tests/tests/test_deprecation_forms.py @@ -0,0 +1,183 @@ +# RemovedInDjango50 +from django.forms import CharField, EmailField, Form, HiddenInput +from django.forms.utils import ErrorList +from django.test import SimpleTestCase, ignore_warnings +from django.utils.deprecation import RemovedInDjango50Warning + +from .test_forms import Person + + +class DivErrorList(ErrorList): + def __str__(self): + return self.as_divs() + + def as_divs(self): + if not self: + return '' + return '<div class="errorlist">%s</div>' % ''.join( + f'<div class="error">{error}</div>' for error in self + ) + + +class DeprecationTests(SimpleTestCase): + def test_deprecation_warning_html_output(self): + msg = ( + 'django.forms.BaseForm._html_output() is deprecated. Please use ' + '.render() and .get_context() instead.' + ) + with self.assertRaisesMessage(RemovedInDjango50Warning, msg): + form = Person() + form._html_output( + normal_row='<p id="p_%(field_name)s"></p>', + error_row='%s', + row_ender='</p>', + help_text_html=' %s', + errors_on_separate_row=True, + ) + + def test_deprecation_warning_error_list(self): + class EmailForm(Form): + email = EmailField() + comment = CharField() + + data = {'email': 'invalid'} + f = EmailForm(data, error_class=DivErrorList) + msg = ( + 'Returning a plain string from DivErrorList is deprecated. Please ' + 'customize via the template system instead.' + ) + with self.assertRaisesMessage(RemovedInDjango50Warning, msg): + f.as_p() + + +@ignore_warnings(category=RemovedInDjango50Warning) +class DeprecatedTests(SimpleTestCase): + def test_errorlist_override_str(self): + class CommentForm(Form): + name = CharField(max_length=50, required=False) + email = EmailField() + comment = CharField() + + data = {'email': 'invalid'} + f = CommentForm(data, auto_id=False, error_class=DivErrorList) + self.assertHTMLEqual( + f.as_p(), + '<p>Name: <input type="text" name="name" maxlength="50"></p>' + '<div class="errorlist">' + '<div class="error">Enter a valid email address.</div></div>' + '<p>Email: <input type="email" name="email" value="invalid" required></p>' + '<div class="errorlist">' + '<div class="error">This field is required.</div></div>' + '<p>Comment: <input type="text" name="comment" required></p>', + ) + + def test_field_name(self): + """#5749 - `field_name` may be used as a key in _html_output().""" + class SomeForm(Form): + some_field = CharField() + + def as_p(self): + return self._html_output( + normal_row='<p id="p_%(field_name)s"></p>', + error_row='%s', + row_ender='</p>', + help_text_html=' %s', + errors_on_separate_row=True, + ) + + form = SomeForm() + self.assertHTMLEqual(form.as_p(), '<p id="p_some_field"></p>') + + def test_field_without_css_classes(self): + """ + `css_classes` may be used as a key in _html_output() (empty classes). + """ + class SomeForm(Form): + some_field = CharField() + + def as_p(self): + return self._html_output( + normal_row='<p class="%(css_classes)s"></p>', + error_row='%s', + row_ender='</p>', + help_text_html=' %s', + errors_on_separate_row=True, + ) + + form = SomeForm() + self.assertHTMLEqual(form.as_p(), '<p class=""></p>') + + def test_field_with_css_class(self): + """ + `css_classes` may be used as a key in _html_output() (class comes + from required_css_class in this case). + """ + class SomeForm(Form): + some_field = CharField() + required_css_class = 'foo' + + def as_p(self): + return self._html_output( + normal_row='<p class="%(css_classes)s"></p>', + error_row='%s', + row_ender='</p>', + help_text_html=' %s', + errors_on_separate_row=True, + ) + + form = SomeForm() + self.assertHTMLEqual(form.as_p(), '<p class="foo"></p>') + + def test_field_name_with_hidden_input(self): + """ + BaseForm._html_output() should merge all the hidden input fields and + put them in the last row. + """ + class SomeForm(Form): + hidden1 = CharField(widget=HiddenInput) + custom = CharField() + hidden2 = CharField(widget=HiddenInput) + + def as_p(self): + return self._html_output( + normal_row='<p%(html_class_attr)s>%(field)s %(field_name)s</p>', + error_row='%s', + row_ender='</p>', + help_text_html=' %s', + errors_on_separate_row=True, + ) + + form = SomeForm() + self.assertHTMLEqual( + form.as_p(), + '<p><input id="id_custom" name="custom" type="text" required> custom' + '<input id="id_hidden1" name="hidden1" type="hidden">' + '<input id="id_hidden2" name="hidden2" type="hidden"></p>' + ) + + def test_field_name_with_hidden_input_and_non_matching_row_ender(self): + """ + BaseForm._html_output() should merge all the hidden input fields and + put them in the last row ended with the specific row ender. + """ + class SomeForm(Form): + hidden1 = CharField(widget=HiddenInput) + custom = CharField() + hidden2 = CharField(widget=HiddenInput) + + def as_p(self): + return self._html_output( + normal_row='<p%(html_class_attr)s>%(field)s %(field_name)s</p>', + error_row='%s', + row_ender='<hr><hr>', + help_text_html=' %s', + errors_on_separate_row=True, + ) + + form = SomeForm() + self.assertHTMLEqual( + form.as_p(), + '<p><input id="id_custom" name="custom" type="text" required> custom</p>\n' + '<input id="id_hidden1" name="hidden1" type="hidden">' + '<input id="id_hidden2" name="hidden2" type="hidden"><hr><hr>' + ) diff --git a/tests/forms_tests/tests/test_forms.py b/tests/forms_tests/tests/test_forms.py index b713cb1321..8c08cd5ad4 100644 --- a/tests/forms_tests/tests/test_forms.py +++ b/tests/forms_tests/tests/test_forms.py @@ -23,6 +23,7 @@ from django.test import SimpleTestCase from django.test.utils import override_settings from django.utils.datastructures import MultiValueDict from django.utils.safestring import mark_safe +from tests.forms_tests.tests import test_all_form_renderers class FrameworkForm(Form): @@ -55,6 +56,7 @@ class MultiValueDictLike(dict): return [self[key]] +@test_all_form_renderers() class FormsTestCase(SimpleTestCase): # A Form is a collection of Fields. It knows how to validate a set of data and it # knows how to render itself in a couple of default ways (e.g., an HTML table). @@ -3077,117 +3079,6 @@ Password: <input type="password" name="password" required> self.assertHTMLEqual(boundfield.label_tag(label_suffix='$'), '<label for="id_field">Field$</label>') - def test_field_name(self): - """#5749 - `field_name` may be used as a key in _html_output().""" - class SomeForm(Form): - some_field = CharField() - - def as_p(self): - return self._html_output( - normal_row='<p id="p_%(field_name)s"></p>', - error_row='%s', - row_ender='</p>', - help_text_html=' %s', - errors_on_separate_row=True, - ) - - form = SomeForm() - self.assertHTMLEqual(form.as_p(), '<p id="p_some_field"></p>') - - def test_field_without_css_classes(self): - """ - `css_classes` may be used as a key in _html_output() (empty classes). - """ - class SomeForm(Form): - some_field = CharField() - - def as_p(self): - return self._html_output( - normal_row='<p class="%(css_classes)s"></p>', - error_row='%s', - row_ender='</p>', - help_text_html=' %s', - errors_on_separate_row=True, - ) - - form = SomeForm() - self.assertHTMLEqual(form.as_p(), '<p class=""></p>') - - def test_field_with_css_class(self): - """ - `css_classes` may be used as a key in _html_output() (class comes - from required_css_class in this case). - """ - class SomeForm(Form): - some_field = CharField() - required_css_class = 'foo' - - def as_p(self): - return self._html_output( - normal_row='<p class="%(css_classes)s"></p>', - error_row='%s', - row_ender='</p>', - help_text_html=' %s', - errors_on_separate_row=True, - ) - - form = SomeForm() - self.assertHTMLEqual(form.as_p(), '<p class="foo"></p>') - - def test_field_name_with_hidden_input(self): - """ - BaseForm._html_output() should merge all the hidden input fields and - put them in the last row. - """ - class SomeForm(Form): - hidden1 = CharField(widget=HiddenInput) - custom = CharField() - hidden2 = CharField(widget=HiddenInput) - - def as_p(self): - return self._html_output( - normal_row='<p%(html_class_attr)s>%(field)s %(field_name)s</p>', - error_row='%s', - row_ender='</p>', - help_text_html=' %s', - errors_on_separate_row=True, - ) - - form = SomeForm() - self.assertHTMLEqual( - form.as_p(), - '<p><input id="id_custom" name="custom" type="text" required> custom' - '<input id="id_hidden1" name="hidden1" type="hidden">' - '<input id="id_hidden2" name="hidden2" type="hidden"></p>' - ) - - def test_field_name_with_hidden_input_and_non_matching_row_ender(self): - """ - BaseForm._html_output() should merge all the hidden input fields and - put them in the last row ended with the specific row ender. - """ - class SomeForm(Form): - hidden1 = CharField(widget=HiddenInput) - custom = CharField() - hidden2 = CharField(widget=HiddenInput) - - def as_p(self): - return self._html_output( - normal_row='<p%(html_class_attr)s>%(field)s %(field_name)s</p>', - error_row='%s', - row_ender='<hr><hr>', - help_text_html=' %s', - errors_on_separate_row=True - ) - - form = SomeForm() - self.assertHTMLEqual( - form.as_p(), - '<p><input id="id_custom" name="custom" type="text" required> custom</p>\n' - '<input id="id_hidden1" name="hidden1" type="hidden">' - '<input id="id_hidden2" name="hidden2" type="hidden"><hr><hr>' - ) - def test_error_dict(self): class MyForm(Form): foo = CharField() @@ -3377,30 +3268,6 @@ Password: <input type="password" name="password" required> <input id="id_last_name" name="last_name" type="text" value="Lennon" required></td></tr>""" ) - def test_errorlist_override(self): - class DivErrorList(ErrorList): - def __str__(self): - return self.as_divs() - - def as_divs(self): - if not self: - return '' - return '<div class="errorlist">%s</div>' % ''.join( - '<div class="error">%s</div>' % e for e in self) - - class CommentForm(Form): - name = CharField(max_length=50, required=False) - email = EmailField() - comment = CharField() - - data = {'email': 'invalid'} - f = CommentForm(data, auto_id=False, error_class=DivErrorList) - self.assertHTMLEqual(f.as_p(), """<p>Name: <input type="text" name="name" maxlength="50"></p> -<div class="errorlist"><div class="error">Enter a valid email address.</div></div> -<p>Email: <input type="email" name="email" value="invalid" required></p> -<div class="errorlist"><div class="error">This field is required.</div></div> -<p>Comment: <input type="text" name="comment" required></p>""") - def test_error_escaping(self): class TestForm(Form): hidden = CharField(widget=HiddenInput(), required=False) @@ -4045,3 +3912,40 @@ class TemplateTests(SimpleTestCase): "VALID: [('password1', 'secret'), ('password2', 'secret'), " "('username', 'adrian')]", ) + + +class OverrideTests(SimpleTestCase): + def test_use_custom_template(self): + class Person(Form): + first_name = CharField() + template_name = 'forms_tests/form_snippet.html' + + t = Template('{{ form }}') + html = t.render(Context({'form': Person()})) + expected = """ + <div class="fieldWrapper"><label for="id_first_name">First name:</label> + <input type="text" name="first_name" required id="id_first_name"></div> + """ + self.assertHTMLEqual(html, expected) + + def test_errorlist_override(self): + class CustomErrorList(ErrorList): + template_name = 'forms_tests/error.html' + + class CommentForm(Form): + name = CharField(max_length=50, required=False) + email = EmailField() + comment = CharField() + + data = {'email': 'invalid'} + f = CommentForm(data, auto_id=False, error_class=CustomErrorList) + self.assertHTMLEqual( + f.as_p(), + '<p>Name: <input type="text" name="name" maxlength="50"></p>' + '<div class="errorlist">' + '<div class="error">Enter a valid email address.</div></div>' + '<p>Email: <input type="email" name="email" value="invalid" required></p>' + '<div class="errorlist">' + '<div class="error">This field is required.</div></div>' + '<p>Comment: <input type="text" name="comment" required></p>', + ) diff --git a/tests/forms_tests/tests/test_formsets.py b/tests/forms_tests/tests/test_formsets.py index 06b61306fe..0d7d452d71 100644 --- a/tests/forms_tests/tests/test_formsets.py +++ b/tests/forms_tests/tests/test_formsets.py @@ -11,6 +11,7 @@ from django.forms.formsets import BaseFormSet, all_valid, formset_factory from django.forms.utils import ErrorList from django.forms.widgets import HiddenInput from django.test import SimpleTestCase +from tests.forms_tests.tests import test_all_form_renderers class Choice(Form): @@ -47,6 +48,7 @@ class CustomKwargForm(Form): super().__init__(*args, **kwargs) +@test_all_form_renderers() class FormsFormsetTestCase(SimpleTestCase): def make_choiceformset( @@ -1288,7 +1290,32 @@ class FormsFormsetTestCase(SimpleTestCase): self.assertIs(formset._should_delete_form(formset.forms[1]), False) self.assertIs(formset._should_delete_form(formset.forms[2]), False) + def test_custom_renderer(self): + """ + A custom renderer passed to a formset_factory() is passed to all forms + and ErrorList. + """ + from django.forms.renderers import Jinja2 + renderer = Jinja2() + data = { + 'choices-TOTAL_FORMS': '2', + 'choices-INITIAL_FORMS': '0', + 'choices-MIN_NUM_FORMS': '0', + 'choices-0-choice': 'Zero', + 'choices-0-votes': '', + 'choices-1-choice': 'One', + 'choices-1-votes': '', + } + ChoiceFormSet = formset_factory(Choice, renderer=renderer) + formset = ChoiceFormSet(data, auto_id=False, prefix='choices') + self.assertEqual(formset.renderer, renderer) + self.assertEqual(formset.forms[0].renderer, renderer) + self.assertEqual(formset.management_form.renderer, renderer) + self.assertEqual(formset.non_form_errors().renderer, renderer) + self.assertEqual(formset.empty_form.renderer, renderer) + +@test_all_form_renderers() class FormsetAsTagTests(SimpleTestCase): def setUp(self): data = { @@ -1345,6 +1372,7 @@ class ArticleForm(Form): ArticleFormSet = formset_factory(ArticleForm) +@test_all_form_renderers() class TestIsBoundBehavior(SimpleTestCase): def test_no_data_error(self): formset = ArticleFormSet({}) @@ -1359,7 +1387,7 @@ class TestIsBoundBehavior(SimpleTestCase): ) self.assertEqual(formset.errors, []) # Can still render the formset. - self.assertEqual( + self.assertHTMLEqual( str(formset), '<tr><td colspan="2">' '<ul class="errorlist nonfield">' @@ -1390,7 +1418,7 @@ class TestIsBoundBehavior(SimpleTestCase): ) self.assertEqual(formset.errors, []) # Can still render the formset. - self.assertEqual( + self.assertHTMLEqual( str(formset), '<tr><td colspan="2">' '<ul class="errorlist nonfield">' diff --git a/tests/forms_tests/tests/test_i18n.py b/tests/forms_tests/tests/test_i18n.py index d941902af2..fc5d59c480 100644 --- a/tests/forms_tests/tests/test_i18n.py +++ b/tests/forms_tests/tests/test_i18n.py @@ -4,8 +4,10 @@ from django.forms import ( from django.test import SimpleTestCase from django.utils import translation from django.utils.translation import gettext_lazy +from tests.forms_tests.tests import test_all_form_renderers +@test_all_form_renderers() class FormsI18nTests(SimpleTestCase): def test_lazy_labels(self): class SomeForm(Form): diff --git a/tests/forms_tests/tests/tests.py b/tests/forms_tests/tests/tests.py index 18b4c58b5e..ec08a5f9ef 100644 --- a/tests/forms_tests/tests/tests.py +++ b/tests/forms_tests/tests/tests.py @@ -5,6 +5,7 @@ from django.db import models from django.forms import CharField, FileField, Form, ModelForm from django.forms.models import ModelFormMetaclass from django.test import SimpleTestCase, TestCase +from tests.forms_tests.tests import test_all_form_renderers from ..models import ( BoundaryModel, ChoiceFieldModel, ChoiceModel, ChoiceOptionModel, Defaults, @@ -283,6 +284,7 @@ class ManyToManyExclusionTestCase(TestCase): self.assertEqual([obj.pk for obj in form.instance.multi_choice_int.all()], data['multi_choice_int']) +@test_all_form_renderers() class EmptyLabelTestCase(TestCase): def test_empty_field_char(self): f = EmptyCharLabelChoiceForm() |
