From b52c73008a9d67e9ddbb841872dc15cdd3d6ee01 Mon Sep 17 00:00:00 2001 From: Preston Timmons Date: Tue, 27 Dec 2016 17:00:56 -0500 Subject: Fixed #15667 -- Added template-based widget rendering. Thanks Carl Meyer and Tim Graham for contributing to the patch. --- tests/admin_inlines/tests.py | 26 +-- tests/admin_views/tests.py | 2 +- tests/admin_widgets/tests.py | 62 ++++--- .../forms_tests/field_tests/test_filepathfield.py | 3 + .../jinja2/forms_tests/custom_widget.html | 1 + .../templates/forms_tests/custom_widget.html | 1 + tests/forms_tests/tests/test_forms.py | 93 +++++++++- tests/forms_tests/tests/test_renderers.py | 52 ++++++ tests/forms_tests/tests/test_widgets.py | 201 +-------------------- tests/forms_tests/widget_tests/base.py | 20 +- tests/forms_tests/widget_tests/test_select.py | 62 +++++++ tests/model_forms/tests.py | 9 +- tests/runtests.py | 5 + 13 files changed, 292 insertions(+), 245 deletions(-) create mode 100644 tests/forms_tests/jinja2/forms_tests/custom_widget.html create mode 100644 tests/forms_tests/templates/forms_tests/custom_widget.html create mode 100644 tests/forms_tests/tests/test_renderers.py (limited to 'tests') diff --git a/tests/admin_inlines/tests.py b/tests/admin_inlines/tests.py index 2716d2abdb..6922a9f82f 100644 --- a/tests/admin_inlines/tests.py +++ b/tests/admin_inlines/tests.py @@ -184,9 +184,11 @@ class TestInline(TestDataMixin, TestCase): SomeChildModel.objects.create(name='c', position='1', parent=parent) response = self.client.get(reverse('admin:admin_inlines_someparentmodel_change', args=(parent.pk,))) self.assertNotContains(response, '') - self.assertContains(response, ( + self.assertInHTML( '')) + 'name="somechildmodel_set-1-position" type="hidden" value="1" />', + response.rendered_content, + ) def test_non_related_name_inline(self): """ @@ -273,12 +275,12 @@ class TestInline(TestDataMixin, TestCase): 'name="binarytree_set-TOTAL_FORMS" type="hidden" value="2" />' ) response = self.client.get(reverse('admin:admin_inlines_binarytree_add')) - self.assertContains(response, max_forms_input % 3) - self.assertContains(response, total_forms_hidden) + self.assertInHTML(max_forms_input % 3, response.rendered_content) + self.assertInHTML(total_forms_hidden, response.rendered_content) response = self.client.get(reverse('admin:admin_inlines_binarytree_change', args=(bt_head.id,))) - self.assertContains(response, max_forms_input % 2) - self.assertContains(response, total_forms_hidden) + self.assertInHTML(max_forms_input % 2, response.rendered_content) + self.assertInHTML(total_forms_hidden, response.rendered_content) def test_min_num(self): """ @@ -302,8 +304,8 @@ class TestInline(TestDataMixin, TestCase): request = self.factory.get(reverse('admin:admin_inlines_binarytree_add')) request.user = User(username='super', is_superuser=True) response = modeladmin.changeform_view(request) - self.assertContains(response, min_forms) - self.assertContains(response, total_forms) + self.assertInHTML(min_forms, response.rendered_content) + self.assertInHTML(total_forms, response.rendered_content) def test_custom_min_num(self): bt_head = BinaryTree.objects.create(name="Tree Head") @@ -331,14 +333,14 @@ class TestInline(TestDataMixin, TestCase): request = self.factory.get(reverse('admin:admin_inlines_binarytree_add')) request.user = User(username='super', is_superuser=True) response = modeladmin.changeform_view(request) - self.assertContains(response, min_forms % 2) - self.assertContains(response, total_forms % 5) + self.assertInHTML(min_forms % 2, response.rendered_content) + self.assertInHTML(total_forms % 5, response.rendered_content) request = self.factory.get(reverse('admin:admin_inlines_binarytree_change', args=(bt_head.id,))) request.user = User(username='super', is_superuser=True) response = modeladmin.changeform_view(request, object_id=str(bt_head.id)) - self.assertContains(response, min_forms % 5) - self.assertContains(response, total_forms % 8) + self.assertInHTML(min_forms % 5, response.rendered_content) + self.assertInHTML(total_forms % 8, response.rendered_content) def test_inline_nonauto_noneditable_pk(self): response = self.client.get(reverse('admin:admin_inlines_author_add')) diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index c6894dd2a2..115c854c70 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -5980,7 +5980,7 @@ class AdminViewOnSiteTests(TestCase): response, 'inline_admin_formset', 0, None, ['Children must share a family name with their parents in this contrived test case'] ) - msg = "The formset 'inline_admin_formset' in context 4 does not contain any non-form errors." + msg = "The formset 'inline_admin_formset' in context 10 does not contain any non-form errors." with self.assertRaisesMessage(AssertionError, msg): self.assertFormsetError(response, 'inline_admin_formset', None, None, ['Error']) diff --git a/tests/admin_widgets/tests.py b/tests/admin_widgets/tests.py index 2d8b88f2cf..b74df4e251 100644 --- a/tests/admin_widgets/tests.py +++ b/tests/admin_widgets/tests.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals import gettext import os +import re from datetime import datetime, timedelta from importlib import import_module @@ -354,34 +355,53 @@ class AdminURLWidgetTest(SimpleTestCase): ) def test_render_quoting(self): - # WARNING: Don't use assertHTMLEqual in that testcase! - # assertHTMLEqual will get rid of some escapes which are tested here! + """ + WARNING: This test doesn't use assertHTMLEqual since it will get rid + of some escapes which are tested here! + """ + HREF_RE = re.compile('href="([^"]+)"') + VALUE_RE = re.compile('value="([^"]+)"') + TEXT_RE = re.compile(']+>([^>]+)') w = widgets.AdminURLFieldWidget() + output = w.render('test', 'http://example.com/some text') + self.assertEqual( + HREF_RE.search(output).groups()[0], + 'http://example.com/%3Csometag%3Esome%20text%3C/sometag%3E', + ) + self.assertEqual( + TEXT_RE.search(output).groups()[0], + 'http://example.com/<sometag>some text</sometag>', + ) + self.assertEqual( + VALUE_RE.search(output).groups()[0], + 'http://example.com/<sometag>some text</sometag>', + ) + output = w.render('test', 'http://example-äüö.com/some text') + self.assertEqual( + HREF_RE.search(output).groups()[0], + 'http://xn--example--7za4pnc.com/%3Csometag%3Esome%20text%3C/sometag%3E', + ) self.assertEqual( - w.render('test', 'http://example.com/some text'), - '

Currently: ' - '' - 'http://example.com/<sometag>some text</sometag>
' - 'Change:

' + TEXT_RE.search(output).groups()[0], + 'http://example-äüö.com/<sometag>some text</sometag>', ) self.assertEqual( - w.render('test', 'http://example-äüö.com/some text'), - '

Currently: ' - '' - 'http://example-äüö.com/<sometag>some text</sometag>
' - 'Change:

' + VALUE_RE.search(output).groups()[0], + 'http://example-äüö.com/<sometag>some text</sometag>', ) + output = w.render('test', 'http://www.example.com/%C3%A4">"') self.assertEqual( - w.render('test', 'http://www.example.com/%C3%A4">"'), - '

Currently: ' - '' + HREF_RE.search(output).groups()[0], + 'http://www.example.com/%C3%A4%22%3E%3Cscript%3Ealert(%22XSS!%22)%3C/script%3E%22', + ) + self.assertEqual( + TEXT_RE.search(output).groups()[0], 'http://www.example.com/%C3%A4"><script>' - 'alert("XSS!")</script>"
' - 'Change:

' + 'alert("XSS!")</script>"' + ) + self.assertEqual( + VALUE_RE.search(output).groups()[0], + 'http://www.example.com/%C3%A4"><script>alert("XSS!")</script>"', ) 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 @@ + 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 @@ + 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
""" ) + 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(), + '' + ) + self.assertHTMLEqual( + str(fields[0]), + '' + ) + + self.assertEqual(fields[1].id_for_label, 'id_name_1') + self.assertEqual(fields[1].choice_label, 'Paul') + self.assertHTMLEqual( + fields[1].tag(), + '' + ) + self.assertHTMLEqual( + str(fields[1]), + '' + ) + + 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(), '') + self.assertHTMLEqual(str(fields[0]), '') + 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: doesn't lose it's safe string status (#22950). """ class CustomWidget(TextInput): - def render(self, name, value, attrs=None): - return format_html(str(''), ' id=custom') + def render(self, name, value, attrs=None, choices=None, + renderer=None, extra_context=None): + return format_html(str(''), ' id=custom') class SampleForm(Form): name = CharField(widget=CustomWidget) @@ -3573,3 +3619,46 @@ Good luck picking a username that doesn't already exist.

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
' % inp) - inp_set3.append('

%s %s

' % (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), """ - - -""") - self.assertHTMLEqual('\n'.join(inp_set2), """
-
-
-
""") - self.assertHTMLEqual('\n'.join(inp_set3), """

John

-

Paul

-

George

-

Ringo

""") - 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]), '') - self.assertHTMLEqual( - str(r[0]), - '' - ) - 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'}), - '' - ) - - with self.assertRaises(IndexError): - r[10] - - # You can create your own custom renderers for RadioSelect to use. - class MyRenderer(RadioFieldRenderer): - def render(self): - return '
\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'), - """
-
-
-""" - ) - - # 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'), - """
-
-
-""" - ) - - # 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('{content}') - inner_html = '

{choice_value}{sub_widgets}

' - 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, - """
-

-

-

-

-
""") - - 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')) - ), - """ - -""") - - # 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'))), - """ - -""") - - # The id_for_label property of the subwidget should return the ID that is used on the subwidget's tag - self.assertHTMLEqual( - "\n".join( - '' - % (c.choice_value, c.id_for_label) for c in CheckboxSelectMultiple( - attrs={'id': 'abc'}, - choices=zip('abc', 'ABC'), - ).subwidgets('letters', []) - ), - """ - -""") - - 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: %(initial)s ' - '%(clear_template)s
%(input_text)s: %(input)s' - ) - self.assertHTMLEqual( - widget.render('myfile', FakeFieldFile()), - 'Currently: something ' - ' ' - '
Change: ' - ) 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): """ )) + 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). diff --git a/tests/model_forms/tests.py b/tests/model_forms/tests.py index 84aa04b40f..f03b4433a8 100644 --- a/tests/model_forms/tests.py +++ b/tests/model_forms/tests.py @@ -1651,13 +1651,6 @@ class ModelChoiceFieldTests(TestCase): with self.assertNumQueries(1): template.render(Context({'field': field})) - def test_modelchoicefield_index_renderer(self): - field = forms.ModelChoiceField(Category.objects.all(), widget=forms.RadioSelect) - self.assertEqual( - str(field.widget.get_renderer('foo', [])[0]), - '' - ) - def test_disabled_modelchoicefield(self): class ModelChoiceForm(forms.ModelForm): author = forms.ModelChoiceField(Author.objects.all(), disabled=True) @@ -2115,7 +2108,7 @@ class FileAndImageFieldTests(TestCase): doc = Document.objects.create() form = DocumentForm(instance=doc) - self.assertEqual( + self.assertHTMLEqual( str(form['myfile']), '' ) diff --git a/tests/runtests.py b/tests/runtests.py index 00b14b808f..0dda1dbee6 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -169,6 +169,11 @@ def setup(verbosity, test_labels, parallel): 'The GeoManager class is deprecated.', RemovedInDjango20Warning ) + warnings.filterwarnings( + 'ignore', + 'django.forms.extras is deprecated.', + RemovedInDjango20Warning + ) # Load all the ALWAYS_INSTALLED_APPS. django.setup() -- cgit v1.3