mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-10 19:56:59 +03:00
Use an array type for list view response schemas
This is the first part of #6846. Previously, the response schema for list views was an object representing a single item. However, list views return a list of items, and hence it should be an array. Further work will need to be done to support how pagination classes modify list responses. There should be no change for views not determined to be list views.
This commit is contained in:
parent
a3f244d85e
commit
b45ff07294
|
@ -460,22 +460,30 @@ class AutoSchema(ViewInspector):
|
||||||
}
|
}
|
||||||
|
|
||||||
def _get_responses(self, path, method):
|
def _get_responses(self, path, method):
|
||||||
# TODO: Handle multiple codes.
|
# TODO: Handle multiple codes and pagination classes.
|
||||||
content = {}
|
item_schema = {}
|
||||||
serializer = self._get_serializer(path, method)
|
serializer = self._get_serializer(path, method)
|
||||||
|
|
||||||
if isinstance(serializer, serializers.Serializer):
|
if isinstance(serializer, serializers.Serializer):
|
||||||
content = self._map_serializer(serializer)
|
item_schema = self._map_serializer(serializer)
|
||||||
# No write_only fields for response.
|
# No write_only fields for response.
|
||||||
for name, schema in content['properties'].copy().items():
|
for name, schema in item_schema['properties'].copy().items():
|
||||||
if 'writeOnly' in schema:
|
if 'writeOnly' in schema:
|
||||||
del content['properties'][name]
|
del item_schema['properties'][name]
|
||||||
content['required'] = [f for f in content['required'] if f != name]
|
item_schema['required'] = [f for f in item_schema['required'] if f != name]
|
||||||
|
|
||||||
|
if is_list_view(path, method, self.view):
|
||||||
|
response_schema = {
|
||||||
|
'type': 'array',
|
||||||
|
'items': item_schema,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
response_schema = item_schema
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'200': {
|
'200': {
|
||||||
'content': {
|
'content': {
|
||||||
ct: {'schema': content}
|
ct: {'schema': response_schema}
|
||||||
for ct in self.content_types
|
for ct in self.content_types
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,7 +82,18 @@ class TestOperationIntrospection(TestCase):
|
||||||
assert operation == {
|
assert operation == {
|
||||||
'operationId': 'ListExamples',
|
'operationId': 'ListExamples',
|
||||||
'parameters': [],
|
'parameters': [],
|
||||||
'responses': {'200': {'content': {'application/json': {'schema': {}}}}},
|
'responses': {
|
||||||
|
'200': {
|
||||||
|
'content': {
|
||||||
|
'application/json': {
|
||||||
|
'schema': {
|
||||||
|
'type': 'array',
|
||||||
|
'items': {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def test_path_with_id_parameter(self):
|
def test_path_with_id_parameter(self):
|
||||||
|
@ -184,6 +195,83 @@ class TestOperationIntrospection(TestCase):
|
||||||
assert list(schema['properties']['nested']['properties'].keys()) == ['number']
|
assert list(schema['properties']['nested']['properties'].keys()) == ['number']
|
||||||
assert schema['properties']['nested']['required'] == ['number']
|
assert schema['properties']['nested']['required'] == ['number']
|
||||||
|
|
||||||
|
def test_list_response_body_generation(self):
|
||||||
|
"""Test that an array schema is returned for list views."""
|
||||||
|
path = '/'
|
||||||
|
method = 'GET'
|
||||||
|
|
||||||
|
class ItemSerializer(serializers.Serializer):
|
||||||
|
text = serializers.CharField()
|
||||||
|
|
||||||
|
class View(generics.GenericAPIView):
|
||||||
|
serializer_class = ItemSerializer
|
||||||
|
|
||||||
|
view = create_view(
|
||||||
|
View,
|
||||||
|
method,
|
||||||
|
create_request(path),
|
||||||
|
)
|
||||||
|
inspector = AutoSchema()
|
||||||
|
inspector.view = view
|
||||||
|
|
||||||
|
responses = inspector._get_responses(path, method)
|
||||||
|
assert responses == {
|
||||||
|
'200': {
|
||||||
|
'content': {
|
||||||
|
'application/json': {
|
||||||
|
'schema': {
|
||||||
|
'type': 'array',
|
||||||
|
'items': {
|
||||||
|
'properties': {
|
||||||
|
'text': {
|
||||||
|
'type': 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'required': ['text'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_retrieve_response_body_generation(self):
|
||||||
|
"""Test that a list of properties is returned for retrieve item views."""
|
||||||
|
path = '/{id}/'
|
||||||
|
method = 'GET'
|
||||||
|
|
||||||
|
class ItemSerializer(serializers.Serializer):
|
||||||
|
text = serializers.CharField()
|
||||||
|
|
||||||
|
class View(generics.GenericAPIView):
|
||||||
|
serializer_class = ItemSerializer
|
||||||
|
|
||||||
|
view = create_view(
|
||||||
|
View,
|
||||||
|
method,
|
||||||
|
create_request(path),
|
||||||
|
)
|
||||||
|
inspector = AutoSchema()
|
||||||
|
inspector.view = view
|
||||||
|
|
||||||
|
responses = inspector._get_responses(path, method)
|
||||||
|
assert responses == {
|
||||||
|
'200': {
|
||||||
|
'content': {
|
||||||
|
'application/json': {
|
||||||
|
'schema': {
|
||||||
|
'properties': {
|
||||||
|
'text': {
|
||||||
|
'type': 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'required': ['text'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
def test_operation_id_generation(self):
|
def test_operation_id_generation(self):
|
||||||
path = '/'
|
path = '/'
|
||||||
method = 'GET'
|
method = 'GET'
|
||||||
|
@ -226,10 +314,11 @@ class TestOperationIntrospection(TestCase):
|
||||||
inspector.view = view
|
inspector.view = view
|
||||||
|
|
||||||
responses = inspector._get_responses(path, method)
|
responses = inspector._get_responses(path, method)
|
||||||
response_schema = responses['200']['content']['application/json']['schema']['properties']
|
response_schema = responses['200']['content']['application/json']['schema']
|
||||||
assert response_schema['date']['type'] == response_schema['datetime']['type'] == 'string'
|
properties = response_schema['items']['properties']
|
||||||
assert response_schema['date']['format'] == 'date'
|
assert properties['date']['type'] == properties['datetime']['type'] == 'string'
|
||||||
assert response_schema['datetime']['format'] == 'date-time'
|
assert properties['date']['format'] == 'date'
|
||||||
|
assert properties['datetime']['format'] == 'date-time'
|
||||||
|
|
||||||
def test_serializer_validators(self):
|
def test_serializer_validators(self):
|
||||||
path = '/'
|
path = '/'
|
||||||
|
@ -243,45 +332,46 @@ class TestOperationIntrospection(TestCase):
|
||||||
inspector.view = view
|
inspector.view = view
|
||||||
|
|
||||||
responses = inspector._get_responses(path, method)
|
responses = inspector._get_responses(path, method)
|
||||||
response_schema = responses['200']['content']['application/json']['schema']['properties']
|
response_schema = responses['200']['content']['application/json']['schema']
|
||||||
|
properties = response_schema['items']['properties']
|
||||||
|
|
||||||
assert response_schema['integer']['type'] == 'integer'
|
assert properties['integer']['type'] == 'integer'
|
||||||
assert response_schema['integer']['maximum'] == 99
|
assert properties['integer']['maximum'] == 99
|
||||||
assert response_schema['integer']['minimum'] == -11
|
assert properties['integer']['minimum'] == -11
|
||||||
|
|
||||||
assert response_schema['string']['minLength'] == 2
|
assert properties['string']['minLength'] == 2
|
||||||
assert response_schema['string']['maxLength'] == 10
|
assert properties['string']['maxLength'] == 10
|
||||||
|
|
||||||
assert response_schema['regex']['pattern'] == r'[ABC]12{3}'
|
assert properties['regex']['pattern'] == r'[ABC]12{3}'
|
||||||
assert response_schema['regex']['description'] == 'must have an A, B, or C followed by 1222'
|
assert properties['regex']['description'] == 'must have an A, B, or C followed by 1222'
|
||||||
|
|
||||||
assert response_schema['decimal1']['type'] == 'number'
|
assert properties['decimal1']['type'] == 'number'
|
||||||
assert response_schema['decimal1']['multipleOf'] == .01
|
assert properties['decimal1']['multipleOf'] == .01
|
||||||
assert response_schema['decimal1']['maximum'] == 10000
|
assert properties['decimal1']['maximum'] == 10000
|
||||||
assert response_schema['decimal1']['minimum'] == -10000
|
assert properties['decimal1']['minimum'] == -10000
|
||||||
|
|
||||||
assert response_schema['decimal2']['type'] == 'number'
|
assert properties['decimal2']['type'] == 'number'
|
||||||
assert response_schema['decimal2']['multipleOf'] == .0001
|
assert properties['decimal2']['multipleOf'] == .0001
|
||||||
|
|
||||||
assert response_schema['email']['type'] == 'string'
|
assert properties['email']['type'] == 'string'
|
||||||
assert response_schema['email']['format'] == 'email'
|
assert properties['email']['format'] == 'email'
|
||||||
assert response_schema['email']['default'] == 'foo@bar.com'
|
assert properties['email']['default'] == 'foo@bar.com'
|
||||||
|
|
||||||
assert response_schema['url']['type'] == 'string'
|
assert properties['url']['type'] == 'string'
|
||||||
assert response_schema['url']['nullable'] is True
|
assert properties['url']['nullable'] is True
|
||||||
assert response_schema['url']['default'] == 'http://www.example.com'
|
assert properties['url']['default'] == 'http://www.example.com'
|
||||||
|
|
||||||
assert response_schema['uuid']['type'] == 'string'
|
assert properties['uuid']['type'] == 'string'
|
||||||
assert response_schema['uuid']['format'] == 'uuid'
|
assert properties['uuid']['format'] == 'uuid'
|
||||||
|
|
||||||
assert response_schema['ip4']['type'] == 'string'
|
assert properties['ip4']['type'] == 'string'
|
||||||
assert response_schema['ip4']['format'] == 'ipv4'
|
assert properties['ip4']['format'] == 'ipv4'
|
||||||
|
|
||||||
assert response_schema['ip6']['type'] == 'string'
|
assert properties['ip6']['type'] == 'string'
|
||||||
assert response_schema['ip6']['format'] == 'ipv6'
|
assert properties['ip6']['format'] == 'ipv6'
|
||||||
|
|
||||||
assert response_schema['ip']['type'] == 'string'
|
assert properties['ip']['type'] == 'string'
|
||||||
assert 'format' not in response_schema['ip']
|
assert 'format' not in properties['ip']
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(uritemplate is None, reason='uritemplate not installed.')
|
@pytest.mark.skipif(uritemplate is None, reason='uritemplate not installed.')
|
||||||
|
|
Loading…
Reference in New Issue
Block a user