mirror of
https://github.com/encode/django-rest-framework.git
synced 2026-01-11 19:20:54 +03:00
261 lines
10 KiB
Python
261 lines
10 KiB
Python
from django.http import QueryDict
|
||
|
||
from rest_framework import serializers
|
||
from rest_framework.exceptions import ValidationError
|
||
|
||
class TestSimpleBoundField:
|
||
def test_empty_bound_field(self):
|
||
class ExampleSerializer(serializers.Serializer):
|
||
text = serializers.CharField(max_length=100)
|
||
amount = serializers.IntegerField()
|
||
|
||
serializer = ExampleSerializer()
|
||
|
||
assert serializer['text'].value == ''
|
||
assert serializer['text'].errors is None
|
||
assert serializer['text'].name == 'text'
|
||
assert serializer['amount'].value is None
|
||
assert serializer['amount'].errors is None
|
||
assert serializer['amount'].name == 'amount'
|
||
|
||
def test_populated_bound_field(self):
|
||
class ExampleSerializer(serializers.Serializer):
|
||
text = serializers.CharField(max_length=100)
|
||
amount = serializers.IntegerField()
|
||
|
||
serializer = ExampleSerializer(data={'text': 'abc', 'amount': 123})
|
||
assert serializer.is_valid()
|
||
assert serializer['text'].value == 'abc'
|
||
assert serializer['text'].errors is None
|
||
assert serializer['text'].name == 'text'
|
||
assert serializer['amount'].value == 123
|
||
assert serializer['amount'].errors is None
|
||
assert serializer['amount'].name == 'amount'
|
||
|
||
def test_error_bound_field(self):
|
||
class ExampleSerializer(serializers.Serializer):
|
||
text = serializers.CharField(max_length=100)
|
||
amount = serializers.IntegerField()
|
||
|
||
serializer = ExampleSerializer(data={'text': 'x' * 1000, 'amount': 123})
|
||
serializer.is_valid()
|
||
|
||
assert serializer['text'].value == 'x' * 1000
|
||
assert serializer['text'].errors == ['Ensure this field has no more than 100 characters.']
|
||
assert serializer['text'].name == 'text'
|
||
assert serializer['amount'].value == 123
|
||
assert serializer['amount'].errors is None
|
||
assert serializer['amount'].name == 'amount'
|
||
|
||
def test_delete_field(self):
|
||
class ExampleSerializer(serializers.Serializer):
|
||
text = serializers.CharField(max_length=100)
|
||
amount = serializers.IntegerField()
|
||
|
||
serializer = ExampleSerializer()
|
||
del serializer.fields['text']
|
||
assert 'text' not in serializer.fields
|
||
|
||
def test_as_form_fields(self):
|
||
class ExampleSerializer(serializers.Serializer):
|
||
bool_field = serializers.BooleanField()
|
||
null_field = serializers.IntegerField(allow_null=True)
|
||
|
||
serializer = ExampleSerializer(data={'bool_field': False, 'null_field': None})
|
||
assert serializer.is_valid()
|
||
assert serializer['bool_field'].as_form_field().value == ''
|
||
assert serializer['null_field'].as_form_field().value == ''
|
||
|
||
def test_rendering_boolean_field(self):
|
||
from rest_framework.renderers import HTMLFormRenderer
|
||
|
||
class ExampleSerializer(serializers.Serializer):
|
||
bool_field = serializers.BooleanField(
|
||
style={'base_template': 'checkbox.html', 'template_pack': 'rest_framework/vertical'})
|
||
|
||
serializer = ExampleSerializer(data={'bool_field': True})
|
||
assert serializer.is_valid()
|
||
renderer = HTMLFormRenderer()
|
||
rendered = renderer.render_field(serializer['bool_field'], {})
|
||
expected_packed = (
|
||
'<divclass="form-group">'
|
||
'<divclass="checkbox">'
|
||
'<label>'
|
||
'<inputtype="checkbox"name="bool_field"value="true"checked>'
|
||
'Boolfield'
|
||
'</label>'
|
||
'</div>'
|
||
'</div>'
|
||
)
|
||
rendered_packed = ''.join(rendered.split())
|
||
assert rendered_packed == expected_packed
|
||
|
||
|
||
class CustomJSONField(serializers.JSONField):
|
||
pass
|
||
|
||
|
||
class TestNestedBoundField:
|
||
def test_nested_empty_bound_field(self):
|
||
class Nested(serializers.Serializer):
|
||
more_text = serializers.CharField(max_length=100)
|
||
amount = serializers.IntegerField()
|
||
|
||
class ExampleSerializer(serializers.Serializer):
|
||
text = serializers.CharField(max_length=100)
|
||
nested = Nested()
|
||
|
||
serializer = ExampleSerializer()
|
||
|
||
assert serializer['text'].value == ''
|
||
assert serializer['text'].errors is None
|
||
assert serializer['text'].name == 'text'
|
||
assert serializer['nested']['more_text'].value == ''
|
||
assert serializer['nested']['more_text'].errors is None
|
||
assert serializer['nested']['more_text'].name == 'nested.more_text'
|
||
assert serializer['nested']['amount'].value is None
|
||
assert serializer['nested']['amount'].errors is None
|
||
assert serializer['nested']['amount'].name == 'nested.amount'
|
||
|
||
def test_as_form_fields(self):
|
||
class Nested(serializers.Serializer):
|
||
bool_field = serializers.BooleanField()
|
||
null_field = serializers.IntegerField(allow_null=True)
|
||
json_field = serializers.JSONField()
|
||
custom_json_field = CustomJSONField()
|
||
|
||
class ExampleSerializer(serializers.Serializer):
|
||
nested = Nested()
|
||
|
||
serializer = ExampleSerializer(
|
||
data={'nested': {
|
||
'bool_field': False, 'null_field': None,
|
||
'json_field': {'bool_item': True, 'number': 1, 'text_item': 'text'},
|
||
'custom_json_field': {'bool_item': True, 'number': 1, 'text_item': 'text'},
|
||
}})
|
||
assert serializer.is_valid()
|
||
assert serializer['nested']['bool_field'].as_form_field().value == ''
|
||
assert serializer['nested']['null_field'].as_form_field().value == ''
|
||
assert serializer['nested']['json_field'].as_form_field().value == '''{
|
||
"bool_item": true,
|
||
"number": 1,
|
||
"text_item": "text"
|
||
}'''
|
||
assert serializer['nested']['custom_json_field'].as_form_field().value == '''{
|
||
"bool_item": true,
|
||
"number": 1,
|
||
"text_item": "text"
|
||
}'''
|
||
|
||
def test_rendering_nested_fields_with_none_value(self):
|
||
from rest_framework.renderers import HTMLFormRenderer
|
||
|
||
class Nested1(serializers.Serializer):
|
||
text_field = serializers.CharField()
|
||
|
||
class Nested2(serializers.Serializer):
|
||
nested1 = Nested1(allow_null=True)
|
||
text_field = serializers.CharField()
|
||
|
||
class ExampleSerializer(serializers.Serializer):
|
||
nested2 = Nested2()
|
||
|
||
serializer = ExampleSerializer(data={'nested2': {'nested1': None, 'text_field': 'test'}})
|
||
assert serializer.is_valid()
|
||
renderer = HTMLFormRenderer()
|
||
for field in serializer:
|
||
rendered = renderer.render_field(field, {})
|
||
expected_packed = (
|
||
'<fieldset>'
|
||
'<legend>Nested2</legend>'
|
||
'<fieldset>'
|
||
'<legend>Nested1</legend>'
|
||
'<divclass="form-group">'
|
||
'<label>Textfield</label>'
|
||
'<inputname="nested2.nested1.text_field"class="form-control"type="text"value="">'
|
||
'</div>'
|
||
'</fieldset>'
|
||
'<divclass="form-group">'
|
||
'<label>Textfield</label>'
|
||
'<inputname="nested2.text_field"class="form-control"type="text"value="test">'
|
||
'</div>'
|
||
'</fieldset>'
|
||
)
|
||
rendered_packed = ''.join(rendered.split())
|
||
assert rendered_packed == expected_packed
|
||
|
||
def test_rendering_nested_fields_with_not_mappable_value(self):
|
||
from rest_framework.renderers import HTMLFormRenderer
|
||
|
||
class Nested(serializers.Serializer):
|
||
text_field = serializers.CharField()
|
||
|
||
class ExampleSerializer(serializers.Serializer):
|
||
nested = Nested()
|
||
|
||
serializer = ExampleSerializer(data={'nested': 1})
|
||
assert not serializer.is_valid()
|
||
renderer = HTMLFormRenderer()
|
||
for field in serializer:
|
||
rendered = renderer.render_field(field, {})
|
||
expected_packed = (
|
||
'<fieldset>'
|
||
'<legend>Nested</legend>'
|
||
'<divclass="form-group">'
|
||
'<label>Textfield</label>'
|
||
'<inputname="nested.text_field"class="form-control"type="text"value="">'
|
||
'</div>'
|
||
'</fieldset>'
|
||
)
|
||
|
||
rendered_packed = ''.join(rendered.split())
|
||
assert rendered_packed == expected_packed
|
||
|
||
def test_child_bound_field_after_parent_validation_error(self):
|
||
"""
|
||
After a parent-level ValidationError on a nested serializer field,
|
||
child BoundFields should remain accessible and receive a mapping
|
||
for `errors` so the Browsable API can render safely.
|
||
|
||
Regression test for #4073.
|
||
"""
|
||
class ChildSerializer(serializers.Serializer):
|
||
value = serializers.CharField()
|
||
class ParentSerializer(serializers.Serializer):
|
||
nested = ChildSerializer()
|
||
|
||
def validate_nested(self, nested):
|
||
# Raise parent-level (non-field) validation error
|
||
raise ValidationError(["parent-level nested error"])
|
||
|
||
serializer = ParentSerializer(data={"nested": {"value": "ignored"}})
|
||
assert not serializer.is_valid()
|
||
|
||
# Parent-level error is a list (current problematic case)
|
||
assert serializer.errors["nested"] == ["parent-level nested error"]
|
||
|
||
# Access nested bound field
|
||
parent_bound = serializer["nested"]
|
||
|
||
# Access child bound field – should not raise
|
||
child_bound = parent_bound["value"]
|
||
|
||
# Core contract: errors must be a mapping, not None or list
|
||
assert isinstance(child_bound.errors, dict)
|
||
|
||
# Sanity checks
|
||
assert child_bound.value == "ignored"
|
||
assert child_bound.name == "nested.value"
|
||
|
||
|
||
class TestJSONBoundField:
|
||
def test_as_form_fields(self):
|
||
class TestSerializer(serializers.Serializer):
|
||
json_field = serializers.JSONField()
|
||
|
||
data = QueryDict(mutable=True)
|
||
data.update({'json_field': '{"some": ["json"}'})
|
||
serializer = TestSerializer(data=data)
|
||
assert serializer.is_valid() is False
|
||
assert serializer['json_field'].as_form_field().value == '{"some": ["json"}'
|