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:
Reupen Shah 2019-07-30 21:19:43 +01:00 committed by Carlton Gibson
parent a3f244d85e
commit b45ff07294
2 changed files with 138 additions and 40 deletions

View File

@ -460,22 +460,30 @@ class AutoSchema(ViewInspector):
}
def _get_responses(self, path, method):
# TODO: Handle multiple codes.
content = {}
# TODO: Handle multiple codes and pagination classes.
item_schema = {}
serializer = self._get_serializer(path, method)
if isinstance(serializer, serializers.Serializer):
content = self._map_serializer(serializer)
item_schema = self._map_serializer(serializer)
# 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:
del content['properties'][name]
content['required'] = [f for f in content['required'] if f != name]
del item_schema['properties'][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 {
'200': {
'content': {
ct: {'schema': content}
ct: {'schema': response_schema}
for ct in self.content_types
}
}

View File

@ -82,7 +82,18 @@ class TestOperationIntrospection(TestCase):
assert operation == {
'operationId': 'ListExamples',
'parameters': [],
'responses': {'200': {'content': {'application/json': {'schema': {}}}}},
'responses': {
'200': {
'content': {
'application/json': {
'schema': {
'type': 'array',
'items': {},
},
},
},
},
},
}
def test_path_with_id_parameter(self):
@ -184,6 +195,83 @@ class TestOperationIntrospection(TestCase):
assert list(schema['properties']['nested']['properties'].keys()) == ['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):
path = '/'
method = 'GET'
@ -226,10 +314,11 @@ class TestOperationIntrospection(TestCase):
inspector.view = view
responses = inspector._get_responses(path, method)
response_schema = responses['200']['content']['application/json']['schema']['properties']
assert response_schema['date']['type'] == response_schema['datetime']['type'] == 'string'
assert response_schema['date']['format'] == 'date'
assert response_schema['datetime']['format'] == 'date-time'
response_schema = responses['200']['content']['application/json']['schema']
properties = response_schema['items']['properties']
assert properties['date']['type'] == properties['datetime']['type'] == 'string'
assert properties['date']['format'] == 'date'
assert properties['datetime']['format'] == 'date-time'
def test_serializer_validators(self):
path = '/'
@ -243,45 +332,46 @@ class TestOperationIntrospection(TestCase):
inspector.view = view
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 response_schema['integer']['maximum'] == 99
assert response_schema['integer']['minimum'] == -11
assert properties['integer']['type'] == 'integer'
assert properties['integer']['maximum'] == 99
assert properties['integer']['minimum'] == -11
assert response_schema['string']['minLength'] == 2
assert response_schema['string']['maxLength'] == 10
assert properties['string']['minLength'] == 2
assert properties['string']['maxLength'] == 10
assert response_schema['regex']['pattern'] == r'[ABC]12{3}'
assert response_schema['regex']['description'] == 'must have an A, B, or C followed by 1222'
assert properties['regex']['pattern'] == r'[ABC]12{3}'
assert properties['regex']['description'] == 'must have an A, B, or C followed by 1222'
assert response_schema['decimal1']['type'] == 'number'
assert response_schema['decimal1']['multipleOf'] == .01
assert response_schema['decimal1']['maximum'] == 10000
assert response_schema['decimal1']['minimum'] == -10000
assert properties['decimal1']['type'] == 'number'
assert properties['decimal1']['multipleOf'] == .01
assert properties['decimal1']['maximum'] == 10000
assert properties['decimal1']['minimum'] == -10000
assert response_schema['decimal2']['type'] == 'number'
assert response_schema['decimal2']['multipleOf'] == .0001
assert properties['decimal2']['type'] == 'number'
assert properties['decimal2']['multipleOf'] == .0001
assert response_schema['email']['type'] == 'string'
assert response_schema['email']['format'] == 'email'
assert response_schema['email']['default'] == 'foo@bar.com'
assert properties['email']['type'] == 'string'
assert properties['email']['format'] == 'email'
assert properties['email']['default'] == 'foo@bar.com'
assert response_schema['url']['type'] == 'string'
assert response_schema['url']['nullable'] is True
assert response_schema['url']['default'] == 'http://www.example.com'
assert properties['url']['type'] == 'string'
assert properties['url']['nullable'] is True
assert properties['url']['default'] == 'http://www.example.com'
assert response_schema['uuid']['type'] == 'string'
assert response_schema['uuid']['format'] == 'uuid'
assert properties['uuid']['type'] == 'string'
assert properties['uuid']['format'] == 'uuid'
assert response_schema['ip4']['type'] == 'string'
assert response_schema['ip4']['format'] == 'ipv4'
assert properties['ip4']['type'] == 'string'
assert properties['ip4']['format'] == 'ipv4'
assert response_schema['ip6']['type'] == 'string'
assert response_schema['ip6']['format'] == 'ipv6'
assert properties['ip6']['type'] == 'string'
assert properties['ip6']['format'] == 'ipv6'
assert response_schema['ip']['type'] == 'string'
assert 'format' not in response_schema['ip']
assert properties['ip']['type'] == 'string'
assert 'format' not in properties['ip']
@pytest.mark.skipif(uritemplate is None, reason='uritemplate not installed.')