mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-05-29 02:33:07 +03:00
Resolve form display with ChoiceField, MultipleChoiceField and non-string choices. (#4374)
* Add tests for html-form-rendering choice fields * Resolve issues with ChoiceField, MultipleChoiceField and non-string options * Ensure None template comparisons don't match string None
This commit is contained in:
parent
0781182646
commit
8105a4ac5a
|
@ -1,3 +1,5 @@
|
||||||
|
{% load rest_framework %}
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
{% if field.label %}
|
{% if field.label %}
|
||||||
<label class="col-sm-2 control-label {% if style.hide_label %}sr-only{% endif %}">
|
<label class="col-sm-2 control-label {% if style.hide_label %}sr-only{% endif %}">
|
||||||
|
@ -9,7 +11,7 @@
|
||||||
{% if style.inline %}
|
{% if style.inline %}
|
||||||
{% for key, text in field.choices.items %}
|
{% for key, text in field.choices.items %}
|
||||||
<label class="checkbox-inline">
|
<label class="checkbox-inline">
|
||||||
<input type="checkbox" name="{{ field.name }}" value="{{ key }}" {% if key in field.value %}checked{% endif %}>
|
<input type="checkbox" name="{{ field.name }}" value="{{ key }}" {% if key|as_string in field.value|as_list_of_strings %}checked{% endif %}>
|
||||||
{{ text }}
|
{{ text }}
|
||||||
</label>
|
</label>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -17,7 +19,7 @@
|
||||||
{% for key, text in field.choices.items %}
|
{% for key, text in field.choices.items %}
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" name="{{ field.name }}" value="{{ key }}" {% if key in field.value %}checked{% endif %}>
|
<input type="checkbox" name="{{ field.name }}" value="{{ key }}" {% if key|as_string in field.value|as_list_of_strings %}checked{% endif %}>
|
||||||
{{ text }}
|
{{ text }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
{% load rest_framework %}
|
||||||
|
|
||||||
{% trans "None" as none_choice %}
|
{% trans "None" as none_choice %}
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -19,7 +21,7 @@
|
||||||
|
|
||||||
{% for key, text in field.choices.items %}
|
{% for key, text in field.choices.items %}
|
||||||
<label class="radio-inline">
|
<label class="radio-inline">
|
||||||
<input type="radio" name="{{ field.name }}" value="{{ key }}" {% if key == field.value %}checked{% endif %} />
|
<input type="radio" name="{{ field.name }}" value="{{ key }}" {% if key|as_string == field.value|as_string %}checked{% endif %} />
|
||||||
{{ text }}
|
{{ text }}
|
||||||
</label>
|
</label>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -35,7 +37,7 @@
|
||||||
{% for key, text in field.choices.items %}
|
{% for key, text in field.choices.items %}
|
||||||
<div class="radio">
|
<div class="radio">
|
||||||
<label>
|
<label>
|
||||||
<input type="radio" name="{{ field.name }}" value="{{ key }}" {% if key == field.value %}checked{% endif %} />
|
<input type="radio" name="{{ field.name }}" value="{{ key }}" {% if key|as_string == field.value|as_string %}checked{% endif %} />
|
||||||
{{ text }}
|
{{ text }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
{% load rest_framework %}
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
{% if field.label %}
|
{% if field.label %}
|
||||||
<label class="col-sm-2 control-label {% if style.hide_label %}sr-only{% endif %}">
|
<label class="col-sm-2 control-label {% if style.hide_label %}sr-only{% endif %}">
|
||||||
|
@ -16,7 +18,7 @@
|
||||||
{% elif select.end_option_group %}
|
{% elif select.end_option_group %}
|
||||||
</optgroup>
|
</optgroup>
|
||||||
{% else %}
|
{% else %}
|
||||||
<option value="{{ select.value }}" {% if select.value == field.value %}selected{% endif %} {% if select.disabled %}disabled{% endif %}>{{ select.display_text }}</option>
|
<option value="{{ select.value }}" {% if select.value|as_string == field.value|as_string %}selected{% endif %} {% if select.disabled %}disabled{% endif %}>{{ select.display_text }}</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
{% load rest_framework %}
|
||||||
|
|
||||||
{% trans "No items to select." as no_items %}
|
{% trans "No items to select." as no_items %}
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -16,7 +18,7 @@
|
||||||
{% elif select.end_option_group %}
|
{% elif select.end_option_group %}
|
||||||
</optgroup>
|
</optgroup>
|
||||||
{% else %}
|
{% else %}
|
||||||
<option value="{{ select.value }}" {% if select.value in field.value %}selected{% endif %} {% if select.disabled %}disabled{% endif %}>{{ select.display_text }}</option>
|
<option value="{{ select.value }}" {% if select.value|as_string in field.value|as_list_of_strings %}selected{% endif %} {% if select.disabled %}disabled{% endif %}>{{ select.display_text }}</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<option>{{ no_items }}</option>
|
<option>{{ no_items }}</option>
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
{% load rest_framework %}
|
||||||
|
|
||||||
<div class="form-group {% if field.errors %}has-error{% endif %}">
|
<div class="form-group {% if field.errors %}has-error{% endif %}">
|
||||||
{% if field.label %}
|
{% if field.label %}
|
||||||
<label class="sr-only">{{ field.label }}</label>
|
<label class="sr-only">{{ field.label }}</label>
|
||||||
|
@ -6,7 +8,7 @@
|
||||||
{% for key, text in field.choices.items %}
|
{% for key, text in field.choices.items %}
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" name="{{ field.name }}" value="{{ key }}" {% if key in field.value %}checked{% endif %}>
|
<input type="checkbox" name="{{ field.name }}" value="{{ key }}" {% if key|as_string in field.value|as_list_of_strings %}checked{% endif %}>
|
||||||
{{ text }}
|
{{ text }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
{% load rest_framework %}
|
||||||
{% trans "None" as none_choice %}
|
{% trans "None" as none_choice %}
|
||||||
|
|
||||||
<div class="form-group {% if field.errors %}has-error{% endif %}">
|
<div class="form-group {% if field.errors %}has-error{% endif %}">
|
||||||
|
@ -20,7 +21,7 @@
|
||||||
{% for key, text in field.choices.items %}
|
{% for key, text in field.choices.items %}
|
||||||
<div class="radio">
|
<div class="radio">
|
||||||
<label>
|
<label>
|
||||||
<input type="radio" name="{{ field.name }}" value="{{ key }}" {% if key == field.value %}checked{% endif %}>
|
<input type="radio" name="{{ field.name }}" value="{{ key }}" {% if key|as_string == field.value|as_string %}checked{% endif %}>
|
||||||
{{ text }}
|
{{ text }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
{% load rest_framework %}
|
||||||
|
|
||||||
<div class="form-group {% if field.errors %}has-error{% endif %}">
|
<div class="form-group {% if field.errors %}has-error{% endif %}">
|
||||||
{% if field.label %}
|
{% if field.label %}
|
||||||
<label class="sr-only">
|
<label class="sr-only">
|
||||||
|
@ -15,7 +17,7 @@
|
||||||
{% elif select.end_option_group %}
|
{% elif select.end_option_group %}
|
||||||
</optgroup>
|
</optgroup>
|
||||||
{% else %}
|
{% else %}
|
||||||
<option value="{{ select.value }}" {% if select.value == field.value %}selected{% endif %} {% if select.disabled %}disabled{% endif %}>{{ select.display_text }}</option>
|
<option value="{{ select.value }}" {% if select.value|as_string == field.value|as_string %}selected{% endif %} {% if select.disabled %}disabled{% endif %}>{{ select.display_text }}</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
{% load rest_framework %}
|
||||||
{% trans "No items to select." as no_items %}
|
{% trans "No items to select." as no_items %}
|
||||||
|
|
||||||
<div class="form-group {% if field.errors %}has-error{% endif %}">
|
<div class="form-group {% if field.errors %}has-error{% endif %}">
|
||||||
|
@ -15,7 +16,7 @@
|
||||||
{% elif select.end_option_group %}
|
{% elif select.end_option_group %}
|
||||||
</optgroup>
|
</optgroup>
|
||||||
{% else %}
|
{% else %}
|
||||||
<option value="{{ select.value }}" {% if select.value in field.value %}selected{% endif %} {% if select.disabled %}disabled{% endif %}>{{ select.display_text }}</option>
|
<option value="{{ select.value }}" {% if select.value|as_string in field.value|as_list_of_strings %}selected{% endif %} {% if select.disabled %}disabled{% endif %}>{{ select.display_text }}</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<option>{{ no_items }}</option>
|
<option>{{ no_items }}</option>
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
{% load rest_framework %}
|
||||||
|
|
||||||
<div class="form-group {% if field.errors %}has-error{% endif %}">
|
<div class="form-group {% if field.errors %}has-error{% endif %}">
|
||||||
{% if field.label %}
|
{% if field.label %}
|
||||||
<label {% if style.hide_label %}class="sr-only"{% endif %}>{{ field.label }}</label>
|
<label {% if style.hide_label %}class="sr-only"{% endif %}>{{ field.label }}</label>
|
||||||
|
@ -7,7 +9,7 @@
|
||||||
<div>
|
<div>
|
||||||
{% for key, text in field.choices.items %}
|
{% for key, text in field.choices.items %}
|
||||||
<label class="checkbox-inline">
|
<label class="checkbox-inline">
|
||||||
<input type="checkbox" name="{{ field.name }}" value="{{ key }}" {% if key in field.value %}checked{% endif %}>
|
<input type="checkbox" name="{{ field.name }}" value="{{ key }}" {% if key|as_string in field.value|as_list_of_stringsg %}checked{% endif %}>
|
||||||
{{ text }}
|
{{ text }}
|
||||||
</label>
|
</label>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -16,7 +18,7 @@
|
||||||
{% for key, text in field.choices.items %}
|
{% for key, text in field.choices.items %}
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" name="{{ field.name }}" value="{{ key }}" {% if key in field.value %}checked{% endif %}>
|
<input type="checkbox" name="{{ field.name }}" value="{{ key }}" {% if key|as_string in field.value|as_list_of_stringsg %}checked{% endif %}>
|
||||||
{{ text }}
|
{{ text }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
{% load rest_framework %}
|
||||||
{% trans "None" as none_choice %}
|
{% trans "None" as none_choice %}
|
||||||
|
|
||||||
<div class="form-group {% if field.errors %}has-error{% endif %}">
|
<div class="form-group {% if field.errors %}has-error{% endif %}">
|
||||||
|
@ -19,7 +20,7 @@
|
||||||
|
|
||||||
{% for key, text in field.choices.items %}
|
{% for key, text in field.choices.items %}
|
||||||
<label class="radio-inline">
|
<label class="radio-inline">
|
||||||
<input type="radio" name="{{ field.name }}" value="{{ key }}" {% if key == field.value %}checked{% endif %}>
|
<input type="radio" name="{{ field.name }}" value="{{ key }}" {% if key|as_string == field.value|as_string %}checked{% endif %}>
|
||||||
{{ text }}
|
{{ text }}
|
||||||
</label>
|
</label>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -37,7 +38,7 @@
|
||||||
{% for key, text in field.choices.items %}
|
{% for key, text in field.choices.items %}
|
||||||
<div class="radio">
|
<div class="radio">
|
||||||
<label>
|
<label>
|
||||||
<input type="radio" name="{{ field.name }}" value="{{ key }}" {% if key == field.value %}checked{% endif %}>
|
<input type="radio" name="{{ field.name }}" value="{{ key }}" {% if key|as_string == field.value|as_string %}checked{% endif %}>
|
||||||
{{ text }}
|
{{ text }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
{% load rest_framework %}
|
||||||
|
|
||||||
<div class="form-group {% if field.errors %}has-error{% endif %}">
|
<div class="form-group {% if field.errors %}has-error{% endif %}">
|
||||||
{% if field.label %}
|
{% if field.label %}
|
||||||
<label {% if style.hide_label %}class="sr-only"{% endif %}>
|
<label {% if style.hide_label %}class="sr-only"{% endif %}>
|
||||||
|
@ -15,7 +17,7 @@
|
||||||
{% elif select.end_option_group %}
|
{% elif select.end_option_group %}
|
||||||
</optgroup>
|
</optgroup>
|
||||||
{% else %}
|
{% else %}
|
||||||
<option value="{{ select.value }}" {% if select.value == field.value %}selected{% endif %} {% if select.disabled %}disabled{% endif %}>{{ select.display_text }}</option>
|
<option value="{{ select.value }}" {% if select.value|as_string == field.value|as_string %}selected{% endif %} {% if select.disabled %}disabled{% endif %}>{{ select.display_text }}</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
{% load rest_framework %}
|
||||||
{% trans "No items to select." as no_items %}
|
{% trans "No items to select." as no_items %}
|
||||||
|
|
||||||
<div class="form-group {% if field.errors %}has-error{% endif %}">
|
<div class="form-group {% if field.errors %}has-error{% endif %}">
|
||||||
|
@ -15,7 +16,7 @@
|
||||||
{% elif select.end_option_group %}
|
{% elif select.end_option_group %}
|
||||||
</optgroup>
|
</optgroup>
|
||||||
{% else %}
|
{% else %}
|
||||||
<option value="{{ select.value }}" {% if select.value in field.value %}selected{% endif %} {% if select.disabled %}disabled{% endif %}>{{ select.display_text }}</option>
|
<option value="{{ select.value }}" {% if select.value|as_string in field.value|as_list_of_strings %}selected{% endif %} {% if select.disabled %}disabled{% endif %}>{{ select.display_text }}</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<option>{{ no_items }}</option>
|
<option>{{ no_items }}</option>
|
||||||
|
|
|
@ -89,6 +89,21 @@ def add_query_param(request, key, val):
|
||||||
return escape(replace_query_param(uri, key, val))
|
return escape(replace_query_param(uri, key, val))
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def as_string(value):
|
||||||
|
if value is None:
|
||||||
|
return ''
|
||||||
|
return '%s' % value
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def as_list_of_strings(value):
|
||||||
|
return [
|
||||||
|
'' if (item is None) else ('%s' % item)
|
||||||
|
for item in value
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def add_class(value, css_class):
|
def add_class(value, css_class):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -78,7 +78,7 @@ class BoundField(object):
|
||||||
))
|
))
|
||||||
|
|
||||||
def as_form_field(self):
|
def as_form_field(self):
|
||||||
value = '' if (self.value is None or self.value is False) else force_text(self.value)
|
value = '' if (self.value is None or self.value is False) else self.value
|
||||||
return self.__class__(self._field, value, self.errors, self._prefix)
|
return self.__class__(self._field, value, self.errors, self._prefix)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -481,3 +481,90 @@ class TestHTMLFormRenderer(TestCase):
|
||||||
result = renderer.render(self.serializer.data, None, {})
|
result = renderer.render(self.serializer.data, None, {})
|
||||||
|
|
||||||
self.assertIsInstance(result, SafeText)
|
self.assertIsInstance(result, SafeText)
|
||||||
|
|
||||||
|
|
||||||
|
class TestChoiceFieldHTMLFormRenderer(TestCase):
|
||||||
|
"""
|
||||||
|
Test rendering ChoiceField with HTMLFormRenderer.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
choices = ((1, 'Option1'), (2, 'Option2'), (12, 'Option12'))
|
||||||
|
|
||||||
|
class TestSerializer(serializers.Serializer):
|
||||||
|
test_field = serializers.ChoiceField(choices=choices,
|
||||||
|
initial=2)
|
||||||
|
|
||||||
|
self.TestSerializer = TestSerializer
|
||||||
|
self.renderer = HTMLFormRenderer()
|
||||||
|
|
||||||
|
def test_render_initial_option(self):
|
||||||
|
serializer = self.TestSerializer()
|
||||||
|
result = self.renderer.render(serializer.data)
|
||||||
|
|
||||||
|
self.assertIsInstance(result, SafeText)
|
||||||
|
|
||||||
|
self.assertInHTML('<option value="2" selected>Option2</option>',
|
||||||
|
result)
|
||||||
|
self.assertInHTML('<option value="1">Option1</option>', result)
|
||||||
|
self.assertInHTML('<option value="12">Option12</option>', result)
|
||||||
|
|
||||||
|
def test_render_selected_option(self):
|
||||||
|
serializer = self.TestSerializer(data={'test_field': '12'})
|
||||||
|
|
||||||
|
serializer.is_valid()
|
||||||
|
result = self.renderer.render(serializer.data)
|
||||||
|
|
||||||
|
self.assertIsInstance(result, SafeText)
|
||||||
|
|
||||||
|
self.assertInHTML('<option value="12" selected>Option12</option>',
|
||||||
|
result)
|
||||||
|
self.assertInHTML('<option value="1">Option1</option>', result)
|
||||||
|
self.assertInHTML('<option value="2">Option2</option>', result)
|
||||||
|
|
||||||
|
|
||||||
|
class TestMultipleChoiceFieldHTMLFormRenderer(TestCase):
|
||||||
|
"""
|
||||||
|
Test rendering MultipleChoiceField with HTMLFormRenderer.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.renderer = HTMLFormRenderer()
|
||||||
|
|
||||||
|
def test_render_selected_option_with_string_option_ids(self):
|
||||||
|
choices = (('1', 'Option1'), ('2', 'Option2'), ('12', 'Option12'),
|
||||||
|
('}', 'OptionBrace'))
|
||||||
|
|
||||||
|
class TestSerializer(serializers.Serializer):
|
||||||
|
test_field = serializers.MultipleChoiceField(choices=choices)
|
||||||
|
|
||||||
|
serializer = TestSerializer(data={'test_field': ['12']})
|
||||||
|
serializer.is_valid()
|
||||||
|
|
||||||
|
result = self.renderer.render(serializer.data)
|
||||||
|
|
||||||
|
self.assertIsInstance(result, SafeText)
|
||||||
|
|
||||||
|
self.assertInHTML('<option value="12" selected>Option12</option>',
|
||||||
|
result)
|
||||||
|
self.assertInHTML('<option value="1">Option1</option>', result)
|
||||||
|
self.assertInHTML('<option value="2">Option2</option>', result)
|
||||||
|
self.assertInHTML('<option value="}">OptionBrace</option>', result)
|
||||||
|
|
||||||
|
def test_render_selected_option_with_integer_option_ids(self):
|
||||||
|
choices = ((1, 'Option1'), (2, 'Option2'), (12, 'Option12'))
|
||||||
|
|
||||||
|
class TestSerializer(serializers.Serializer):
|
||||||
|
test_field = serializers.MultipleChoiceField(choices=choices)
|
||||||
|
|
||||||
|
serializer = TestSerializer(data={'test_field': ['12']})
|
||||||
|
serializer.is_valid()
|
||||||
|
|
||||||
|
result = self.renderer.render(serializer.data)
|
||||||
|
|
||||||
|
self.assertIsInstance(result, SafeText)
|
||||||
|
|
||||||
|
self.assertInHTML('<option value="12" selected>Option12</option>',
|
||||||
|
result)
|
||||||
|
self.assertInHTML('<option value="1">Option1</option>', result)
|
||||||
|
self.assertInHTML('<option value="2">Option2</option>', result)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user