From 5195e2a112d862f7066ae331cc633b5b12e1ef62 Mon Sep 17 00:00:00 2001 From: musanaeem Date: Thu, 26 Jun 2025 19:25:21 +0500 Subject: [PATCH 1/2] Fix Issue 1076: Implemented Select2 Initial Values --- src/dal_select2/widgets.py | 40 +++++++++++++++++++++-- test_project/tests/test_widgets.py | 52 +++++++++++++++++++++++++++++- 2 files changed, 89 insertions(+), 3 deletions(-) diff --git a/src/dal_select2/widgets.py b/src/dal_select2/widgets.py index 186e3e48..d9b4350e 100644 --- a/src/dal_select2/widgets.py +++ b/src/dal_select2/widgets.py @@ -1,4 +1,5 @@ """Select2 widget implementation module.""" +import ast try: from functools import lru_cache @@ -113,15 +114,50 @@ def media(self): autocomplete_function = 'select2' +class Select2InitialRenderMixin: + def render(self, name, value, attrs=None, renderer=None): + if not value: + return super().render(name, value, attrs=attrs, renderer=renderer) + + values = [] + + if isinstance(value, str): + try: + parsed = ast.literal_eval(value) + if isinstance(parsed, (list, tuple)): + values = [v.strip() for v in parsed if v] + else: + values = [str(parsed)] + except (ValueError, SyntaxError): + values = [v.strip() for v in value.split(',') if v.strip()] + + elif isinstance(value, (list, tuple)): + values = list(value) + + else: + values = [value] + + existing = dict(self.choices) + extended = [(v, v) for v in values if v not in existing] + if extended: + self.choices = list(self.choices) + extended + + return super().render(name, values, attrs=attrs, renderer=renderer) + + class Select2(Select2WidgetMixin, Select): """Select2 widget for regular choices.""" -class Select2Multiple(Select2WidgetMixin, SelectMultiple): +class Select2Multiple(Select2InitialRenderMixin, Select2WidgetMixin, SelectMultiple): """Select2Multiple widget for regular choices.""" -class ListSelect2(WidgetMixin, Select2WidgetMixin, forms.Select): +class ListSelect2( + Select2InitialRenderMixin, + WidgetMixin, Select2WidgetMixin, + forms.Select +): """Select widget for regular choices and Select2.""" diff --git a/test_project/tests/test_widgets.py b/test_project/tests/test_widgets.py index 2ef3fec7..ca62046d 100644 --- a/test_project/tests/test_widgets.py +++ b/test_project/tests/test_widgets.py @@ -9,7 +9,10 @@ from django import forms from django import http from django import test +from django.http import HttpResponse from django.urls import re_path as url +from django.views import View + try: from django.urls import reverse except ImportError: @@ -19,10 +22,15 @@ import mock +class DummyView(View): + def get(self, request): + return HttpResponse("ok") + + urlpatterns = [ url( r'^test-url/$', - mock.Mock(), + DummyView.as_view(), # replace with sample Mock View name='test_url' ), ] @@ -158,3 +166,45 @@ def render_forward_conf(self, id): # attrs with id observed = widget.render('myname', '', attrs={'id': 'myid'}) self.assertEqual(observed, 'myid') + + +@override_settings(ROOT_URLCONF='tests.test_widgets') +class Select2InitialRenderMixinTest(test.TestCase): + def test_listselect2_adds_missing_selected_value(self): + class Form(forms.Form): + test = forms.ChoiceField( + widget=select2_widget.ListSelect2(url=reverse('test_url')), + required=False, + ) + + form = Form(initial={'test': 'Urdu'}) + rendered = form.as_p() + + assert 'value="Urdu" selected' in rendered + + def test_select2multiple_adds_multiple_selected_values(self): + class Form(forms.Form): + test = forms.MultipleChoiceField( + widget=select2_widget.Select2Multiple(url=reverse('test_url')), + required=False, + ) + + form = Form(initial={'test': ['English', 'Urdu']}) + rendered = form.as_p() + + assert 'value="English" selected' in rendered + assert 'value="Urdu" selected' in rendered + + def test_select2multiple_handles_stringified_list(self): + class Form(forms.Form): + test = forms.MultipleChoiceField( + widget=select2_widget.Select2Multiple(url=reverse('test_url')), + required=False, + ) + + form = Form() + form.fields['test'].initial = "['English', 'Urdu']" + rendered = form.as_p() + + assert 'value="English" selected' in rendered + assert 'value="Urdu" selected' in rendered From 857e386fd71d46c004270be76ca264a62a43d1cd Mon Sep 17 00:00:00 2001 From: musanaeem Date: Fri, 27 Jun 2025 09:16:39 +0500 Subject: [PATCH 2/2] Fix: updated to avoid side effect --- src/dal_select2/widgets.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/dal_select2/widgets.py b/src/dal_select2/widgets.py index d9b4350e..0f78b943 100644 --- a/src/dal_select2/widgets.py +++ b/src/dal_select2/widgets.py @@ -140,7 +140,12 @@ def render(self, name, value, attrs=None, renderer=None): existing = dict(self.choices) extended = [(v, v) for v in values if v not in existing] if extended: + original_choices = self.choices self.choices = list(self.choices) + extended + try: + return super().render(name, values, attrs=attrs, renderer=renderer) + finally: + self.choices = original_choices return super().render(name, values, attrs=attrs, renderer=renderer)