From 5059b17e17260f04a78a172f370330eaed5f5440 Mon Sep 17 00:00:00 2001 From: Girish Koliki Date: Wed, 9 Oct 2024 17:13:04 +0100 Subject: [PATCH 1/5] creating support for BoundWidget based of #122 with tests --- .vscode/settings.json | 11 +++++++++++ README.rst | 21 ++++++++++++++++----- tests/forms.py | 1 + tests/tests.py | 11 +++++++++++ widget_tweaks/templatetags/widget_tweaks.py | 14 ++++++++++++++ 5 files changed, 53 insertions(+), 5 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..21e0ca1 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "python.testing.unittestArgs": [ + "-v", + "-s", + "./tests", + "-p", + "*test*.py" + ], + "python.testing.pytestEnabled": false, + "python.testing.unittestEnabled": true +} \ No newline at end of file diff --git a/README.rst b/README.rst index ef848a1..ac3bd22 100644 --- a/README.rst +++ b/README.rst @@ -268,6 +268,22 @@ Output: +Fields with multiple widgets +============================ + +Some fields may render as a `MultiWidget`, composed of multiple subwidgets +(for example, a `ChoiceField` using `RadioSelect`). You can use the same tags +and filters, but your template code will need to include a for loop for fields +like this: + +.. code-block:: html+django + + {% load widget_tweaks %} + + {% for widget in form.choice %} + {{ widget|add_class:"css_class_1 css_class_2" }} + {% endfor %} + Mixing render_field and filters =============================== @@ -419,8 +435,3 @@ Make sure you have `tox `_ installed, then type tox from the source checkout. - -NOT SUPPORTED -============= - -MultiWidgets: SplitDateTimeWidget, SplitHiddenDateTimeWidget diff --git a/tests/forms.py b/tests/forms.py index 7b6a842..5cf3eb6 100644 --- a/tests/forms.py +++ b/tests/forms.py @@ -15,6 +15,7 @@ class MyForm(Form): with_attrs = CharField(widget=TextInput(attrs={"foo": "baz", "egg": "spam"})) with_cls = CharField(widget=TextInput(attrs={"class": "class0"})) date = forms.DateField(widget=SelectDateWidget(attrs={"egg": "spam"})) + choice = forms.ChoiceField(choices=[(1, "one"), (2, "two")]) def render_form(text, form=None, **context_args): diff --git a/tests/tests.py b/tests/tests.py index 80a2921..5cc9629 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -389,3 +389,14 @@ def test_field_arroba_dot(self): def test_field_double_colon_missing(self): res = render_form('{{ form.simple|attr:"::class:{active:True}" }}') assertIn(':class="{active:True}"', res) + + +class ChoiceFieldTest(TestCase): + def test_choice(self): + res = render_field("choice", "attr", "foo:bar") + assertIn("select", res) + assertIn('name="choice"', res) + assertIn('id="id_choice"', res) + assertIn('foo="bar"', res) + assertIn('', res) + assertIn('', res) diff --git a/widget_tweaks/templatetags/widget_tweaks.py b/widget_tweaks/templatetags/widget_tweaks.py index 116b3c3..87b8ce2 100644 --- a/widget_tweaks/templatetags/widget_tweaks.py +++ b/widget_tweaks/templatetags/widget_tweaks.py @@ -24,6 +24,20 @@ def _process_field_attributes(field, attr, process): attribute = params[0].replace("::", ":") value = params[1] if len(params) == 2 else True field = copy(field) + + if not hasattr(field, "as_widget"): + old_tag = field.tag + + def tag(self): # pylint: disable=unused-argument + attrs = self.data["attrs"] + process(self.parent_widget, attrs, attribute, value) + html = old_tag() + self.tag = old_tag + return html + + field.tag = types.MethodType(tag, field) + return field + # decorate field.as_widget method with updated attributes old_as_widget = field.as_widget From a0dfc81843a9f7f974807d3a6769d3d75d7e027d Mon Sep 17 00:00:00 2001 From: Girish Koliki Date: Wed, 9 Oct 2024 17:28:32 +0100 Subject: [PATCH 2/5] removing vscode settings --- .gitignore | 3 +++ .vscode/settings.json | 11 ----------- tests/forms.py | 5 +++++ 3 files changed, 8 insertions(+), 11 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index d1a92b1..719a800 100644 --- a/.gitignore +++ b/.gitignore @@ -54,3 +54,6 @@ coverage.xml docs/_build/ .idea + +# ingore VSCode settings +.vscode/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 21e0ca1..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "python.testing.unittestArgs": [ - "-v", - "-s", - "./tests", - "-p", - "*test*.py" - ], - "python.testing.pytestEnabled": false, - "python.testing.unittestEnabled": true -} \ No newline at end of file diff --git a/tests/forms.py b/tests/forms.py index 5cf3eb6..6610252 100644 --- a/tests/forms.py +++ b/tests/forms.py @@ -16,6 +16,11 @@ class MyForm(Form): with_cls = CharField(widget=TextInput(attrs={"class": "class0"})) date = forms.DateField(widget=SelectDateWidget(attrs={"egg": "spam"})) choice = forms.ChoiceField(choices=[(1, "one"), (2, "two")]) + radio = forms.ChoiceField( + label="Radio Input", + choices=[("option1", "Option 1"), ("option2", "Option 2")], + widget=forms.RadioSelect, + ) def render_form(text, form=None, **context_args): From 9a3cea318988fc2b63595cc09cb8cc6e11a5cd78 Mon Sep 17 00:00:00 2001 From: Girish Koliki Date: Wed, 9 Oct 2024 17:29:08 +0100 Subject: [PATCH 3/5] typo --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 719a800..197ef8b 100644 --- a/.gitignore +++ b/.gitignore @@ -55,5 +55,5 @@ docs/_build/ .idea -# ingore VSCode settings +# VSCode settings .vscode/ \ No newline at end of file From fd2df333267f93adfb5c01b76b3c929064eac93d Mon Sep 17 00:00:00 2001 From: Girish Koliki Date: Wed, 9 Oct 2024 18:47:21 +0100 Subject: [PATCH 4/5] updating tests --- tests/forms.py | 26 +++++++++++++++++++++ tests/tests.py | 24 ++++++++++++++++--- widget_tweaks/templatetags/widget_tweaks.py | 4 ++-- 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/tests/forms.py b/tests/forms.py index 6610252..b0175ee 100644 --- a/tests/forms.py +++ b/tests/forms.py @@ -52,6 +52,32 @@ def render_field(field, template_filter, params, *args, **kwargs): return render_form(render_field_str, **kwargs) +def render_choice_field( + field, choice_no, template_filter, params, *args, **kwargs +): + """ + Renders ``field`` of MyForm with choice_no and filter ``template_filter`` + applied. + ``params`` are filter arguments. + + If you want to apply several filters (in a chain), + pass extra ``template_filter`` and ``params`` as positional arguments. + + In order to use custom form, pass form instance as ``form`` + keyword argument. + """ + filters = [(template_filter, params)] + filters.extend(zip(args[::2], args[1::2])) + filter_strings = ['|%s:"%s"' % (f[0], f[1]) for f in filters] + render_field_str = "{{ form.%s.%s%s }}" % ( + field, + choice_no, + "".join(filter_strings), + ) + print(render_field_str) + return render_form(render_field_str, **kwargs) + + def render_field_from_tag(field, *attributes): """ Renders MyForm's field ``field`` with attributes passed diff --git a/tests/tests.py b/tests/tests.py index 5cc9629..bdcbf04 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -1,6 +1,6 @@ from unittest import TestCase -from .forms import render_field, render_field_from_tag, render_form, MyForm +from .forms import render_field, render_choice_field, render_field_from_tag, render_form, MyForm def assertIn(value, obj): @@ -391,8 +391,8 @@ def test_field_double_colon_missing(self): assertIn(':class="{active:True}"', res) -class ChoiceFieldTest(TestCase): - def test_choice(self): +class SelectFieldTest(TestCase): + def test_parent_field(self): res = render_field("choice", "attr", "foo:bar") assertIn("select", res) assertIn('name="choice"', res) @@ -400,3 +400,21 @@ def test_choice(self): assertIn('foo="bar"', res) assertIn('', res) assertIn('', res) + + +class RadioFieldTest(TestCase): + def test_first_choice(self): + res = render_choice_field("radio", 0, "attr", "foo:bar") + assertIn('type="radio"', res) + assertIn('name="radio"', res) + assertIn('value="option1"', res) + assertIn('id="id_radio_0"', res) + assertIn('foo="bar"', res) + + def test_second_choice(self): + res = render_choice_field("radio", 1, "attr", "foo:bar") + assertIn('type="radio"', res) + assertIn('name="radio"', res) + assertIn('value="option2"', res) + assertIn('id="id_radio_1"', res) + assertIn('foo="bar"', res) \ No newline at end of file diff --git a/widget_tweaks/templatetags/widget_tweaks.py b/widget_tweaks/templatetags/widget_tweaks.py index 87b8ce2..13997e8 100644 --- a/widget_tweaks/templatetags/widget_tweaks.py +++ b/widget_tweaks/templatetags/widget_tweaks.py @@ -28,10 +28,10 @@ def _process_field_attributes(field, attr, process): if not hasattr(field, "as_widget"): old_tag = field.tag - def tag(self): # pylint: disable=unused-argument + def tag(self, wrap_label=False): # pylint: disable=unused-argument attrs = self.data["attrs"] process(self.parent_widget, attrs, attribute, value) - html = old_tag() + html = old_tag(wrap_label=False) self.tag = old_tag return html From 0d12ceefabb16ee59309c25ec847cc27be23781c Mon Sep 17 00:00:00 2001 From: Girish Koliki Date: Wed, 9 Oct 2024 18:56:26 +0100 Subject: [PATCH 5/5] adding couple more tests --- tests/forms.py | 1 - tests/tests.py | 24 ++++++++++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/tests/forms.py b/tests/forms.py index b0175ee..e6a5cdb 100644 --- a/tests/forms.py +++ b/tests/forms.py @@ -74,7 +74,6 @@ def render_choice_field( choice_no, "".join(filter_strings), ) - print(render_field_str) return render_form(render_field_str, **kwargs) diff --git a/tests/tests.py b/tests/tests.py index bdcbf04..a00dfa5 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -401,6 +401,16 @@ def test_parent_field(self): assertIn('', res) assertIn('', res) + def test_rendering_id_class(self): + res = render_form( + '{% render_field form.choice id="id_1" class="c_1" %}' + '{% render_field form.choice id="id_2" class="c_2" %}' + ) + self.assertEqual(res.count("id_1"), 1) + self.assertEqual(res.count("id_2"), 1) + self.assertEqual(res.count("c_1"), 1) + self.assertEqual(res.count("c_2"), 1) + class RadioFieldTest(TestCase): def test_first_choice(self): @@ -410,11 +420,21 @@ def test_first_choice(self): assertIn('value="option1"', res) assertIn('id="id_radio_0"', res) assertIn('foo="bar"', res) - + def test_second_choice(self): res = render_choice_field("radio", 1, "attr", "foo:bar") assertIn('type="radio"', res) assertIn('name="radio"', res) assertIn('value="option2"', res) assertIn('id="id_radio_1"', res) - assertIn('foo="bar"', res) \ No newline at end of file + assertIn('foo="bar"', res) + + def test_rendering_id_class(self): + res = render_form( + '{% render_field form.radio.0 id="id_1" class="c_1" %}' + '{% render_field form.radio.1 id="id_2" class="c_2" %}' + ) + self.assertEqual(res.count("id_1"), 1) + self.assertEqual(res.count("id_2"), 1) + self.assertEqual(res.count("c_1"), 1) + self.assertEqual(res.count("c_2"), 1)