diff options
Diffstat (limited to 'tests/forms_tests')
| -rw-r--r-- | tests/forms_tests/field_tests/test_filepathfield.py | 3 | ||||
| -rw-r--r-- | tests/forms_tests/jinja2/forms_tests/custom_widget.html | 1 | ||||
| -rw-r--r-- | tests/forms_tests/templates/forms_tests/custom_widget.html | 1 | ||||
| -rw-r--r-- | tests/forms_tests/tests/test_forms.py | 93 | ||||
| -rw-r--r-- | tests/forms_tests/tests/test_renderers.py | 52 | ||||
| -rw-r--r-- | tests/forms_tests/tests/test_widgets.py | 201 | ||||
| -rw-r--r-- | tests/forms_tests/widget_tests/base.py | 20 | ||||
| -rw-r--r-- | tests/forms_tests/widget_tests/test_select.py | 62 |
8 files changed, 230 insertions, 203 deletions
diff --git a/tests/forms_tests/field_tests/test_filepathfield.py b/tests/forms_tests/field_tests/test_filepathfield.py index 5c4bd1bbb2..d518e65db1 100644 --- a/tests/forms_tests/field_tests/test_filepathfield.py +++ b/tests/forms_tests/field_tests/test_filepathfield.py @@ -39,6 +39,7 @@ class FilePathFieldTest(SimpleTestCase): ('/django/forms/forms.py', 'forms.py'), ('/django/forms/formsets.py', 'formsets.py'), ('/django/forms/models.py', 'models.py'), + ('/django/forms/renderers.py', 'renderers.py'), ('/django/forms/utils.py', 'utils.py'), ('/django/forms/widgets.py', 'widgets.py') ] @@ -62,6 +63,7 @@ class FilePathFieldTest(SimpleTestCase): ('/django/forms/forms.py', 'forms.py'), ('/django/forms/formsets.py', 'formsets.py'), ('/django/forms/models.py', 'models.py'), + ('/django/forms/renderers.py', 'renderers.py'), ('/django/forms/utils.py', 'utils.py'), ('/django/forms/widgets.py', 'widgets.py') ] @@ -83,6 +85,7 @@ class FilePathFieldTest(SimpleTestCase): ('/django/forms/forms.py', 'forms.py'), ('/django/forms/formsets.py', 'formsets.py'), ('/django/forms/models.py', 'models.py'), + ('/django/forms/renderers.py', 'renderers.py'), ('/django/forms/utils.py', 'utils.py'), ('/django/forms/widgets.py', 'widgets.py') ] diff --git a/tests/forms_tests/jinja2/forms_tests/custom_widget.html b/tests/forms_tests/jinja2/forms_tests/custom_widget.html new file mode 100644 index 0000000000..c6e8abd4ad --- /dev/null +++ b/tests/forms_tests/jinja2/forms_tests/custom_widget.html @@ -0,0 +1 @@ +<input type="text" name="custom"> diff --git a/tests/forms_tests/templates/forms_tests/custom_widget.html b/tests/forms_tests/templates/forms_tests/custom_widget.html new file mode 100644 index 0000000000..c6e8abd4ad --- /dev/null +++ b/tests/forms_tests/templates/forms_tests/custom_widget.html @@ -0,0 +1 @@ +<input type="text" name="custom"> diff --git a/tests/forms_tests/tests/test_forms.py b/tests/forms_tests/tests/test_forms.py index 8e94d29463..4079be1235 100644 --- a/tests/forms_tests/tests/test_forms.py +++ b/tests/forms_tests/tests/test_forms.py @@ -17,6 +17,7 @@ from django.forms import ( SplitDateTimeField, SplitHiddenDateTimeWidget, Textarea, TextInput, TimeField, ValidationError, forms, ) +from django.forms.renderers import DjangoTemplates, get_default_renderer from django.forms.utils import ErrorList from django.http import QueryDict from django.template import Context, Template @@ -678,6 +679,50 @@ Java</label></li> <div><label><input type="radio" name="name" value="ringo" required /> Ringo</label></div>""" ) + def test_form_with_iterable_boundfield_id(self): + class BeatleForm(Form): + name = ChoiceField( + choices=[('john', 'John'), ('paul', 'Paul'), ('george', 'George'), ('ringo', 'Ringo')], + widget=RadioSelect, + ) + fields = list(BeatleForm()['name']) + self.assertEqual(len(fields), 4) + + self.assertEqual(fields[0].id_for_label, 'id_name_0') + self.assertEqual(fields[0].choice_label, 'John') + self.assertHTMLEqual( + fields[0].tag(), + '<input type="radio" name="name" value="john" id="id_name_0" required />' + ) + self.assertHTMLEqual( + str(fields[0]), + '<label for="id_name_0"><input type="radio" name="name" ' + 'value="john" id="id_name_0" required /> John</label>' + ) + + self.assertEqual(fields[1].id_for_label, 'id_name_1') + self.assertEqual(fields[1].choice_label, 'Paul') + self.assertHTMLEqual( + fields[1].tag(), + '<input type="radio" name="name" value="paul" id="id_name_1" required />' + ) + self.assertHTMLEqual( + str(fields[1]), + '<label for="id_name_1"><input type="radio" name="name" ' + 'value="paul" id="id_name_1" required /> Paul</label>' + ) + + def test_iterable_boundfield_select(self): + class BeatleForm(Form): + name = ChoiceField(choices=[('john', 'John'), ('paul', 'Paul'), ('george', 'George'), ('ringo', 'Ringo')]) + fields = list(BeatleForm(auto_id=False)['name']) + self.assertEqual(len(fields), 4) + + self.assertEqual(fields[0].id_for_label, 'id_name_0') + self.assertEqual(fields[0].choice_label, 'John') + self.assertHTMLEqual(fields[0].tag(), '<option value="john">John</option>') + self.assertHTMLEqual(str(fields[0]), '<option value="john">John</option>') + def test_form_with_noniterable_boundfield(self): # You can iterate over any BoundField, not just those with widget=RadioSelect. class BeatleForm(Form): @@ -1993,8 +2038,9 @@ Password: <input type="password" name="password" required /></li> doesn't lose it's safe string status (#22950). """ class CustomWidget(TextInput): - def render(self, name, value, attrs=None): - return format_html(str('<input{} required />'), ' id=custom') + def render(self, name, value, attrs=None, choices=None, + renderer=None, extra_context=None): + return format_html(str('<input{} />'), ' id=custom') class SampleForm(Form): name = CharField(widget=CustomWidget) @@ -3573,3 +3619,46 @@ Good luck picking a username that doesn't already exist.</p> f = DataForm({'data': 'xyzzy'}) self.assertTrue(f.is_valid()) self.assertEqual(f.cleaned_data, {'data': 'xyzzy'}) + + +class CustomRenderer(DjangoTemplates): + pass + + +class RendererTests(SimpleTestCase): + + def test_default(self): + form = Form() + self.assertEqual(form.renderer, get_default_renderer()) + + def test_kwarg_instance(self): + custom = CustomRenderer() + form = Form(renderer=custom) + self.assertEqual(form.renderer, custom) + + def test_kwarg_class(self): + custom = CustomRenderer() + form = Form(renderer=custom) + self.assertEqual(form.renderer, custom) + + def test_attribute_instance(self): + class CustomForm(Form): + default_renderer = DjangoTemplates() + + form = CustomForm() + self.assertEqual(form.renderer, CustomForm.default_renderer) + + def test_attribute_class(self): + class CustomForm(Form): + default_renderer = CustomRenderer + + form = CustomForm() + self.assertTrue(isinstance(form.renderer, CustomForm.default_renderer)) + + def test_attribute_override(self): + class CustomForm(Form): + default_renderer = DjangoTemplates() + + custom = CustomRenderer() + form = CustomForm(renderer=custom) + self.assertEqual(form.renderer, custom) diff --git a/tests/forms_tests/tests/test_renderers.py b/tests/forms_tests/tests/test_renderers.py new file mode 100644 index 0000000000..7c37b6fd4e --- /dev/null +++ b/tests/forms_tests/tests/test_renderers.py @@ -0,0 +1,52 @@ +import os +import unittest + +from django.forms.renderers import ( + BaseRenderer, DjangoTemplates, Jinja2, TemplatesSetting, +) +from django.test import SimpleTestCase +from django.utils._os import upath + +try: + import jinja2 +except ImportError: + jinja2 = None + + +class SharedTests(object): + expected_widget_dir = 'templates' + + def test_installed_apps_template_found(self): + """Can find a custom template in INSTALLED_APPS.""" + renderer = self.renderer() + # Found because forms_tests is . + tpl = renderer.get_template('forms_tests/custom_widget.html') + expected_path = os.path.abspath( + os.path.join( + upath(os.path.dirname(__file__)), + '..', + self.expected_widget_dir + '/forms_tests/custom_widget.html', + ) + ) + self.assertEqual(tpl.origin.name, expected_path) + + +class BaseTemplateRendererTests(SimpleTestCase): + + def test_get_renderer(self): + with self.assertRaisesMessage(NotImplementedError, 'subclasses must implement get_template()'): + BaseRenderer().get_template('') + + +class DjangoTemplatesTests(SharedTests, SimpleTestCase): + renderer = DjangoTemplates + + +@unittest.skipIf(jinja2 is None, 'jinja2 required') +class Jinja2Tests(SharedTests, SimpleTestCase): + renderer = Jinja2 + expected_widget_dir = 'jinja2' + + +class TemplatesSettingTests(SharedTests, SimpleTestCase): + renderer = TemplatesSetting diff --git a/tests/forms_tests/tests/test_widgets.py b/tests/forms_tests/tests/test_widgets.py index 7ea2b35266..f67954fd3a 100644 --- a/tests/forms_tests/tests/test_widgets.py +++ b/tests/forms_tests/tests/test_widgets.py @@ -1,182 +1,12 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.contrib.admin.tests import AdminSeleniumTestCase -from django.forms import ( - CheckboxSelectMultiple, ClearableFileInput, RadioSelect, TextInput, -) -from django.forms.widgets import ( - ChoiceFieldRenderer, ChoiceInput, RadioFieldRenderer, -) -from django.test import SimpleTestCase, override_settings +from django.test import override_settings from django.urls import reverse -from django.utils import six -from django.utils.encoding import force_text, python_2_unicode_compatible -from django.utils.safestring import SafeData from ..models import Article -class FormsWidgetTests(SimpleTestCase): - - def test_radiofieldrenderer(self): - # RadioSelect uses a RadioFieldRenderer to render the individual radio inputs. - # You can manipulate that object directly to customize the way the RadioSelect - # is rendered. - w = RadioSelect(choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))) - r = w.get_renderer('beatle', 'J') - inp_set1 = [] - inp_set2 = [] - inp_set3 = [] - inp_set4 = [] - - for inp in r: - inp_set1.append(str(inp)) - inp_set2.append('%s<br />' % inp) - inp_set3.append('<p>%s %s</p>' % (inp.tag(), inp.choice_label)) - inp_set4.append( - '%s %s %s %s %s' % ( - inp.name, - inp.value, - inp.choice_value, - inp.choice_label, - inp.is_checked(), - ) - ) - - self.assertHTMLEqual('\n'.join(inp_set1), """<label><input checked type="radio" name="beatle" value="J" /> John</label> -<label><input type="radio" name="beatle" value="P" /> Paul</label> -<label><input type="radio" name="beatle" value="G" /> George</label> -<label><input type="radio" name="beatle" value="R" /> Ringo</label>""") - self.assertHTMLEqual('\n'.join(inp_set2), """<label><input checked type="radio" name="beatle" value="J" /> John</label><br /> -<label><input type="radio" name="beatle" value="P" /> Paul</label><br /> -<label><input type="radio" name="beatle" value="G" /> George</label><br /> -<label><input type="radio" name="beatle" value="R" /> Ringo</label><br />""") - self.assertHTMLEqual('\n'.join(inp_set3), """<p><input checked type="radio" name="beatle" value="J" /> John</p> -<p><input type="radio" name="beatle" value="P" /> Paul</p> -<p><input type="radio" name="beatle" value="G" /> George</p> -<p><input type="radio" name="beatle" value="R" /> Ringo</p>""") - self.assertHTMLEqual('\n'.join(inp_set4), """beatle J J John True -beatle J P Paul False -beatle J G George False -beatle J R Ringo False""") - - # A RadioFieldRenderer object also allows index access to individual RadioChoiceInput - w = RadioSelect(choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))) - r = w.get_renderer('beatle', 'J') - self.assertHTMLEqual(str(r[1]), '<label><input type="radio" name="beatle" value="P" /> Paul</label>') - self.assertHTMLEqual( - str(r[0]), - '<label><input checked type="radio" name="beatle" value="J" /> John</label>' - ) - self.assertTrue(r[0].is_checked()) - self.assertFalse(r[1].is_checked()) - self.assertEqual((r[1].name, r[1].value, r[1].choice_value, r[1].choice_label), ('beatle', 'J', 'P', 'Paul')) - - # These individual widgets can accept extra attributes if manually rendered. - self.assertHTMLEqual( - r[1].render(attrs={'extra': 'value'}), - '<label><input type="radio" extra="value" name="beatle" value="P" /> Paul</label>' - ) - - with self.assertRaises(IndexError): - r[10] - - # You can create your own custom renderers for RadioSelect to use. - class MyRenderer(RadioFieldRenderer): - def render(self): - return '<br />\n'.join(six.text_type(choice) for choice in self) - w = RadioSelect(choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')), renderer=MyRenderer) - self.assertHTMLEqual( - w.render('beatle', 'G'), - """<label><input type="radio" name="beatle" value="J" /> John</label><br /> -<label><input type="radio" name="beatle" value="P" /> Paul</label><br /> -<label><input checked type="radio" name="beatle" value="G" /> George</label><br /> -<label><input type="radio" name="beatle" value="R" /> Ringo</label>""" - ) - - # Or you can use custom RadioSelect fields that use your custom renderer. - class CustomRadioSelect(RadioSelect): - renderer = MyRenderer - w = CustomRadioSelect(choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))) - self.assertHTMLEqual( - w.render('beatle', 'G'), - """<label><input type="radio" name="beatle" value="J" /> John</label><br /> -<label><input type="radio" name="beatle" value="P" /> Paul</label><br /> -<label><input checked type="radio" name="beatle" value="G" /> George</label><br /> -<label><input type="radio" name="beatle" value="R" /> Ringo</label>""" - ) - - # You can customize rendering with outer_html/inner_html renderer variables (#22950) - class MyRenderer(RadioFieldRenderer): - # str is just to test some Python 2 issue with bytestrings - outer_html = str('<div{id_attr}>{content}</div>') - inner_html = '<p>{choice_value}{sub_widgets}</p>' - w = RadioSelect(choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')), renderer=MyRenderer) - output = w.render('beatle', 'J', attrs={'id': 'bar'}) - self.assertIsInstance(output, SafeData) - self.assertHTMLEqual( - output, - """<div id="bar"> -<p><label for="bar_0"><input checked type="radio" id="bar_0" value="J" name="beatle" /> John</label></p> -<p><label for="bar_1"><input type="radio" id="bar_1" value="P" name="beatle" /> Paul</label></p> -<p><label for="bar_2"><input type="radio" id="bar_2" value="G" name="beatle" /> George</label></p> -<p><label for="bar_3"><input type="radio" id="bar_3" value="R" name="beatle" /> Ringo</label></p> -</div>""") - - def test_subwidget(self): - # Each subwidget tag gets a separate ID when the widget has an ID specified - self.assertHTMLEqual( - "\n".join( - c.tag() for c in CheckboxSelectMultiple( - attrs={'id': 'abc'}, - choices=zip('abc', 'ABC') - ).subwidgets('letters', list('ac')) - ), - """<input checked type="checkbox" name="letters" value="a" id="abc_0" /> -<input type="checkbox" name="letters" value="b" id="abc_1" /> -<input checked type="checkbox" name="letters" value="c" id="abc_2" />""") - - # Each subwidget tag does not get an ID if the widget does not have an ID specified - self.assertHTMLEqual( - "\n".join(c.tag() for c in CheckboxSelectMultiple( - choices=zip('abc', 'ABC'), - ).subwidgets('letters', list('ac'))), - """<input checked type="checkbox" name="letters" value="a" /> -<input type="checkbox" name="letters" value="b" /> -<input checked type="checkbox" name="letters" value="c" />""") - - # The id_for_label property of the subwidget should return the ID that is used on the subwidget's tag - self.assertHTMLEqual( - "\n".join( - '<input type="checkbox" name="letters" value="%s" id="%s" />' - % (c.choice_value, c.id_for_label) for c in CheckboxSelectMultiple( - attrs={'id': 'abc'}, - choices=zip('abc', 'ABC'), - ).subwidgets('letters', []) - ), - """<input type="checkbox" name="letters" value="a" id="abc_0" /> -<input type="checkbox" name="letters" value="b" id="abc_1" /> -<input type="checkbox" name="letters" value="c" id="abc_2" />""") - - def test_sub_widget_html_safe(self): - widget = TextInput() - subwidget = next(widget.subwidgets('username', 'John Doe')) - self.assertTrue(hasattr(subwidget, '__html__')) - self.assertEqual(force_text(subwidget), subwidget.__html__()) - - def test_choice_input_html_safe(self): - widget = ChoiceInput('choices', 'CHOICE1', {}, ('CHOICE1', 'first choice'), 0) - self.assertTrue(hasattr(ChoiceInput, '__html__')) - self.assertEqual(force_text(widget), widget.__html__()) - - def test_choice_field_renderer_html_safe(self): - renderer = ChoiceFieldRenderer('choices', 'CHOICE1', {}, [('CHOICE1', 'first_choice')]) - renderer.choice_input_class = lambda *args: args - self.assertTrue(hasattr(ChoiceFieldRenderer, '__html__')) - self.assertEqual(force_text(renderer), renderer.__html__()) - - @override_settings(ROOT_URLCONF='forms_tests.urls') class LiveWidgetTests(AdminSeleniumTestCase): @@ -190,33 +20,4 @@ class LiveWidgetTests(AdminSeleniumTestCase): self.selenium.get(self.live_server_url + reverse('article_form', args=[article.pk])) self.selenium.find_element_by_id('submit').submit() article = Article.objects.get(pk=article.pk) - # Should be "\nTst\n" after #19251 is fixed self.assertEqual(article.content, "\r\nTst\r\n") - - -@python_2_unicode_compatible -class FakeFieldFile(object): - """ - Quacks like a FieldFile (has a .url and unicode representation), but - doesn't require us to care about storages etc. - """ - url = 'something' - - def __str__(self): - return self.url - - -class ClearableFileInputTests(SimpleTestCase): - - def test_render_custom_template(self): - widget = ClearableFileInput() - widget.template_with_initial = ( - '%(initial_text)s: <img src="%(initial_url)s" alt="%(initial)s" /> ' - '%(clear_template)s<br />%(input_text)s: %(input)s' - ) - self.assertHTMLEqual( - widget.render('myfile', FakeFieldFile()), - 'Currently: <img src="something" alt="something" /> ' - '<input type="checkbox" name="myfile-clear" id="myfile-clear_id" /> ' - '<label for="myfile-clear_id">Clear</label><br />Change: <input type="file" name="myfile" />' - ) diff --git a/tests/forms_tests/widget_tests/base.py b/tests/forms_tests/widget_tests/base.py index 3127cb85f2..dbbea25d7a 100644 --- a/tests/forms_tests/widget_tests/base.py +++ b/tests/forms_tests/widget_tests/base.py @@ -1,9 +1,27 @@ +from django.forms.renderers import DjangoTemplates, Jinja2 from django.test import SimpleTestCase +try: + import jinja2 +except ImportError: + jinja2 = None + class WidgetTest(SimpleTestCase): beatles = (('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')) + @classmethod + def setUpClass(cls): + cls.django_renderer = DjangoTemplates() + cls.jinja2_renderer = Jinja2() if jinja2 else None + cls.renderers = [cls.django_renderer] + ([cls.jinja2_renderer] if cls.jinja2_renderer else []) + super(WidgetTest, cls).setUpClass() + def check_html(self, widget, name, value, html='', attrs=None, **kwargs): - output = widget.render(name, value, attrs=attrs, **kwargs) + if self.jinja2_renderer: + output = widget.render(name, value, attrs=attrs, renderer=self.jinja2_renderer, **kwargs) + # Django escapes quotes with '"' while Jinja2 uses '"'. + self.assertHTMLEqual(output.replace('"', '"'), html) + + output = widget.render(name, value, attrs=attrs, renderer=self.django_renderer, **kwargs) self.assertHTMLEqual(output, html) diff --git a/tests/forms_tests/widget_tests/test_select.py b/tests/forms_tests/widget_tests/test_select.py index dde84c476f..4b717b6043 100644 --- a/tests/forms_tests/widget_tests/test_select.py +++ b/tests/forms_tests/widget_tests/test_select.py @@ -221,6 +221,68 @@ class SelectTest(WidgetTest): </select>""" )) + def test_options(self): + options = list(self.widget(choices=self.beatles).options( + 'name', ['J'], attrs={'class': 'super'}, + )) + self.assertEqual(len(options), 4) + self.assertEqual(options[0]['name'], 'name') + self.assertEqual(options[0]['value'], 'J') + self.assertEqual(options[0]['label'], 'John') + self.assertEqual(options[0]['index'], '0') + self.assertEqual(options[0]['selected'], True) + # Template-related attributes + self.assertEqual(options[1]['name'], 'name') + self.assertEqual(options[1]['value'], 'P') + self.assertEqual(options[1]['label'], 'Paul') + self.assertEqual(options[1]['index'], '1') + self.assertEqual(options[1]['selected'], False) + + def test_optgroups(self): + choices = [ + ('Audio', [ + ('vinyl', 'Vinyl'), + ('cd', 'CD'), + ]), + ('Video', [ + ('vhs', 'VHS Tape'), + ('dvd', 'DVD'), + ]), + ('unknown', 'Unknown'), + ] + groups = list(self.widget(choices=choices).optgroups( + 'name', ['vhs'], attrs={'class': 'super'}, + )) + self.assertEqual(len(groups), 3) + self.assertEqual(groups[0][0], None) + self.assertEqual(groups[0][2], 0) + self.assertEqual(len(groups[0][1]), 1) + options = groups[0][1] + self.assertEqual(options[0]['name'], 'name') + self.assertEqual(options[0]['value'], 'unknown') + self.assertEqual(options[0]['label'], 'Unknown') + self.assertEqual(options[0]['index'], '0') + self.assertEqual(options[0]['selected'], False) + self.assertEqual(groups[1][0], 'Audio') + self.assertEqual(groups[1][2], 1) + self.assertEqual(len(groups[1][1]), 2) + options = groups[1][1] + self.assertEqual(options[0]['name'], 'name') + self.assertEqual(options[0]['value'], 'vinyl') + self.assertEqual(options[0]['label'], 'Vinyl') + self.assertEqual(options[0]['index'], '1_0') + self.assertEqual(options[1]['index'], '1_1') + self.assertEqual(groups[2][0], 'Video') + self.assertEqual(groups[2][2], 2) + self.assertEqual(len(groups[2][1]), 2) + options = groups[2][1] + self.assertEqual(options[0]['name'], 'name') + self.assertEqual(options[0]['value'], 'vhs') + self.assertEqual(options[0]['label'], 'VHS Tape') + self.assertEqual(options[0]['index'], '2_0') + self.assertEqual(options[0]['selected'], True) + self.assertEqual(options[1]['index'], '2_1') + def test_deepcopy(self): """ __deepcopy__() should copy all attributes properly (#25085). |
