Skip to content

Commit 735db8a

Browse files
committed
Handle Select widget specifically otherwise field.widget.format_value will return a list. #617
1 parent c3070e1 commit 735db8a

File tree

6 files changed

+69
-6
lines changed

6 files changed

+69
-6
lines changed

django_unicorn/components/unicorn_view.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from django.conf import settings
1212
from django.core.exceptions import ImproperlyConfigured
1313
from django.db.models import Model
14-
from django.forms.widgets import CheckboxInput
14+
from django.forms.widgets import CheckboxInput, Select
1515
from django.http import HttpRequest
1616
from django.utils.decorators import classonlymethod
1717
from django.views.generic.base import TemplateView
@@ -457,20 +457,23 @@ def get_frontend_context_variables(self) -> str:
457457
form = self._get_form(attributes)
458458

459459
if form:
460-
form.is_valid()
461-
462460
for key in attributes.keys():
463461
if key in form.fields:
464462
field = form.fields[key]
465463

466464
if key in form.cleaned_data:
467465
cleaned_value = form.cleaned_data[key]
468-
value = field.widget.format_value(cleaned_value)
469466

470467
if isinstance(field.widget, CheckboxInput) and isinstance(cleaned_value, bool):
471468
# Handle booleans for checkboxes explicitly because `format_value`
472-
# returns `None` in this case
469+
# returns `None`
470+
value = cleaned_value
471+
elif isinstance(field.widget, Select) and not field.widget.allow_multiple_selected:
472+
# Handle value for Select widgets explicitly because `format_value`
473+
# returns a list of stringified values
473474
value = cleaned_value
475+
else:
476+
value = field.widget.format_value(cleaned_value)
474477

475478
# Don't update the frontend variable if the only change is
476479
# stripping off the whitespace from the field value
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Generated by Django 4.2.5 on 2023-10-29 22:53
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
dependencies = [
8+
("books", "0003_auto_20221110_0400"),
9+
]
10+
11+
operations = [
12+
migrations.AddField(
13+
model_name="book",
14+
name="type",
15+
field=models.IntegerField(choices=[(1, "Hardcover"), (2, "Softcover")], default=1),
16+
),
17+
]

example/books/models.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33

44
class Book(models.Model):
5+
TYPES = ((1, "Hardcover"), (2, "Softcover"))
56
title = models.CharField(max_length=255)
67
date_published = models.DateField()
8+
type = models.IntegerField(choices=TYPES, default=1)
79

810

911
class Author(models.Model):

tests/components/test_component.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ def test_get_frontend_context_variables_form_with_boolean_field(component):
291291
frontend_context_variables = component.get_frontend_context_variables()
292292
frontend_context_variables_dict = orjson.loads(frontend_context_variables)
293293

294-
assert frontend_context_variables_dict.get("permanent")
294+
assert frontend_context_variables_dict.get("permanent") is not None
295295

296296

297297
def test_get_frontend_context_variables_authentication_form(component):

tests/views/fake_components.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
PollUpdate,
1414
UnicornView,
1515
)
16+
from example.books.models import Book
1617
from example.coffee.models import Flavor
1718

1819

@@ -65,6 +66,21 @@ def test_validation_error_list(self):
6566
raise ValidationError([ValidationError({"check": "Check is required"}, code="required")])
6667

6768

69+
class FakeModelForm(forms.ModelForm):
70+
class Meta:
71+
model = Book
72+
fields = ("title", "date_published", "type")
73+
74+
75+
class FakeModelFormComponent(UnicornView):
76+
template_name = "templates/test_component.html"
77+
form_class = FakeModelForm
78+
79+
title = None
80+
date_published = None
81+
type = None # noqa: A003
82+
83+
6884
class FakeModelComponent(UnicornView):
6985
template_name = "templates/test_component.html"
7086
flavors = Flavor.objects.all()

tests/views/message/test_sync_input.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,28 @@ def test_message_nested_sync_input(client):
1818

1919
assert not response["errors"]
2020
assert response["data"].get("dictionary") == {"name": "test1"}
21+
22+
23+
def test_message_sync_input_choices_with_select_widget(client):
24+
"""
25+
ModelForms with a Model that have a field with `choices` and the form's field uses a Select widget.
26+
Need to handle Select widget specifically otherwise `field.widget.format_value` will return a list
27+
that only contains one object.
28+
"""
29+
30+
data = {"type": 1}
31+
action_queue = [
32+
{
33+
"payload": {"name": "type", "value": 2},
34+
"type": "syncInput",
35+
}
36+
]
37+
response = post_and_get_response(
38+
client,
39+
url="/message/tests.views.fake_components.FakeModelFormComponent",
40+
data=data,
41+
action_queue=action_queue,
42+
)
43+
44+
assert not response["errors"]
45+
assert response["data"].get("type") == 2

0 commit comments

Comments
 (0)