mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-08-04 12:30:11 +03:00
Optimized solution
This commit is contained in:
parent
ce12c0bbd9
commit
bde90d2656
|
@ -4,7 +4,7 @@ generators.py # Top-down schema generation
|
||||||
See schemas.__init__.py for package overview.
|
See schemas.__init__.py for package overview.
|
||||||
"""
|
"""
|
||||||
import warnings
|
import warnings
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict, Counter
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -67,8 +67,14 @@ to customise schema structure.
|
||||||
class LinkNode(OrderedDict):
|
class LinkNode(OrderedDict):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.links = []
|
self.links = []
|
||||||
|
self.methods_counter = Counter()
|
||||||
super(LinkNode, self).__init__()
|
super(LinkNode, self).__init__()
|
||||||
|
|
||||||
|
def get_next_key(self, method):
|
||||||
|
current_val = self.methods_counter[method]
|
||||||
|
self.methods_counter[method] += 1
|
||||||
|
return '{}_{}'.format(method, current_val)
|
||||||
|
|
||||||
|
|
||||||
def insert_into(target, keys, value):
|
def insert_into(target, keys, value):
|
||||||
"""
|
"""
|
||||||
|
@ -95,25 +101,13 @@ def insert_into(target, keys, value):
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
|
||||||
def get_unique_key(obj, base_key):
|
def distribute_links(obj):
|
||||||
i = 0
|
for key, value in obj.items():
|
||||||
while True:
|
distribute_links(value)
|
||||||
key = '{}_{}'.format(base_key, i)
|
|
||||||
if key not in obj:
|
|
||||||
return key
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
|
|
||||||
def distribute_links(obj, parent=None, parent_key='root'):
|
|
||||||
if parent is None:
|
|
||||||
parent = obj
|
|
||||||
|
|
||||||
for link in obj.links:
|
for link in obj.links:
|
||||||
key = get_unique_key(parent, parent_key)
|
key = obj.get_next_key(str(link.action))
|
||||||
parent[key] = link
|
obj[key] = link
|
||||||
|
|
||||||
for key in list(obj.keys()):
|
|
||||||
distribute_links(obj[key], obj, key)
|
|
||||||
|
|
||||||
|
|
||||||
def is_custom_action(action):
|
def is_custom_action(action):
|
||||||
|
@ -438,16 +432,8 @@ class SchemaGenerator(object):
|
||||||
|
|
||||||
if is_custom_action(action):
|
if is_custom_action(action):
|
||||||
# Custom action, eg "/users/{pk}/activate/", "/users/active/"
|
# Custom action, eg "/users/{pk}/activate/", "/users/active/"
|
||||||
if len(view.action_map) > 1:
|
if len(view.action_map) == 1:
|
||||||
action = self.default_mapping[method.lower()]
|
return named_path_components[:-1]
|
||||||
if action in self.coerce_method_names:
|
|
||||||
action = self.coerce_method_names[action]
|
|
||||||
return named_path_components + [action]
|
|
||||||
else:
|
|
||||||
return named_path_components[:-1] + [action]
|
|
||||||
|
|
||||||
if action in self.coerce_method_names:
|
|
||||||
action = self.coerce_method_names[action]
|
|
||||||
|
|
||||||
# Default action, eg "/users/", "/users/{pk}/"
|
# Default action, eg "/users/", "/users/{pk}/"
|
||||||
return named_path_components + [action]
|
return named_path_components
|
||||||
|
|
|
@ -120,8 +120,7 @@ class TestRouterGeneratedSchema(TestCase):
|
||||||
title='Example API',
|
title='Example API',
|
||||||
content={
|
content={
|
||||||
'example': {
|
'example': {
|
||||||
'list': {},
|
'get_0': coreapi.Link(
|
||||||
'list_0': coreapi.Link(
|
|
||||||
url='/example/',
|
url='/example/',
|
||||||
action='get',
|
action='get',
|
||||||
fields=[
|
fields=[
|
||||||
|
@ -130,20 +129,17 @@ class TestRouterGeneratedSchema(TestCase):
|
||||||
coreapi.Field('ordering', required=False, location='query', schema=coreschema.String(title='Ordering', description='Which field to use when ordering the results.'))
|
coreapi.Field('ordering', required=False, location='query', schema=coreschema.String(title='Ordering', description='Which field to use when ordering the results.'))
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
'custom_list_action': {},
|
'get_1': coreapi.Link(
|
||||||
'custom_list_action_0': coreapi.Link(
|
|
||||||
url='/example/custom_list_action/',
|
url='/example/custom_list_action/',
|
||||||
action='get'
|
action='get'
|
||||||
),
|
),
|
||||||
'custom_list_action_multiple_methods': {
|
'custom_list_action_multiple_methods': {
|
||||||
'read': {},
|
'get_0': coreapi.Link(
|
||||||
'read_0': coreapi.Link(
|
|
||||||
url='/example/custom_list_action_multiple_methods/',
|
url='/example/custom_list_action_multiple_methods/',
|
||||||
action='get'
|
action='get'
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
'read': {},
|
'get_2': coreapi.Link(
|
||||||
'read_0': coreapi.Link(
|
|
||||||
url='/example/{id}/',
|
url='/example/{id}/',
|
||||||
action='get',
|
action='get',
|
||||||
fields=[
|
fields=[
|
||||||
|
@ -167,8 +163,7 @@ class TestRouterGeneratedSchema(TestCase):
|
||||||
title='Example API',
|
title='Example API',
|
||||||
content={
|
content={
|
||||||
'example': {
|
'example': {
|
||||||
'list': {},
|
'get_0': coreapi.Link(
|
||||||
'list_0': coreapi.Link(
|
|
||||||
url='/example/',
|
url='/example/',
|
||||||
action='get',
|
action='get',
|
||||||
fields=[
|
fields=[
|
||||||
|
@ -177,8 +172,7 @@ class TestRouterGeneratedSchema(TestCase):
|
||||||
coreapi.Field('ordering', required=False, location='query', schema=coreschema.String(title='Ordering', description='Which field to use when ordering the results.'))
|
coreapi.Field('ordering', required=False, location='query', schema=coreschema.String(title='Ordering', description='Which field to use when ordering the results.'))
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
'create': {},
|
'post_0': coreapi.Link(
|
||||||
'create_0': coreapi.Link(
|
|
||||||
url='/example/',
|
url='/example/',
|
||||||
action='post',
|
action='post',
|
||||||
encoding='application/json',
|
encoding='application/json',
|
||||||
|
@ -187,8 +181,7 @@ class TestRouterGeneratedSchema(TestCase):
|
||||||
coreapi.Field('b', required=False, location='form', schema=coreschema.String(title='B'))
|
coreapi.Field('b', required=False, location='form', schema=coreschema.String(title='B'))
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
'read': {},
|
'get_2': coreapi.Link(
|
||||||
'read_0': coreapi.Link(
|
|
||||||
url='/example/{id}/',
|
url='/example/{id}/',
|
||||||
action='get',
|
action='get',
|
||||||
fields=[
|
fields=[
|
||||||
|
@ -196,8 +189,7 @@ class TestRouterGeneratedSchema(TestCase):
|
||||||
coreapi.Field('ordering', required=False, location='query', schema=coreschema.String(title='Ordering', description='Which field to use when ordering the results.'))
|
coreapi.Field('ordering', required=False, location='query', schema=coreschema.String(title='Ordering', description='Which field to use when ordering the results.'))
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
'custom_action': {},
|
'post_1': coreapi.Link(
|
||||||
'custom_action_0': coreapi.Link(
|
|
||||||
url='/example/{id}/custom_action/',
|
url='/example/{id}/custom_action/',
|
||||||
action='post',
|
action='post',
|
||||||
encoding='application/json',
|
encoding='application/json',
|
||||||
|
@ -208,8 +200,7 @@ class TestRouterGeneratedSchema(TestCase):
|
||||||
coreapi.Field('d', required=False, location='form', schema=coreschema.String(title='D')),
|
coreapi.Field('d', required=False, location='form', schema=coreschema.String(title='D')),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
'custom_action_with_list_fields': {},
|
'post_2': coreapi.Link(
|
||||||
'custom_action_with_list_fields_0': coreapi.Link(
|
|
||||||
url='/example/{id}/custom_action_with_list_fields/',
|
url='/example/{id}/custom_action_with_list_fields/',
|
||||||
action='post',
|
action='post',
|
||||||
encoding='application/json',
|
encoding='application/json',
|
||||||
|
@ -220,25 +211,21 @@ class TestRouterGeneratedSchema(TestCase):
|
||||||
coreapi.Field('b', required=True, location='form', schema=coreschema.Array(title='B', items=coreschema.String())),
|
coreapi.Field('b', required=True, location='form', schema=coreschema.Array(title='B', items=coreschema.String())),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
'custom_list_action': {},
|
'get_1': coreapi.Link(
|
||||||
'custom_list_action_0': coreapi.Link(
|
|
||||||
url='/example/custom_list_action/',
|
url='/example/custom_list_action/',
|
||||||
action='get'
|
action='get'
|
||||||
),
|
),
|
||||||
'custom_list_action_multiple_methods': {
|
'custom_list_action_multiple_methods': {
|
||||||
'read': {},
|
'get_0': coreapi.Link(
|
||||||
'read_0': coreapi.Link(
|
|
||||||
url='/example/custom_list_action_multiple_methods/',
|
url='/example/custom_list_action_multiple_methods/',
|
||||||
action='get'
|
action='get'
|
||||||
),
|
),
|
||||||
'create': {},
|
'post_0': coreapi.Link(
|
||||||
'create_0': coreapi.Link(
|
|
||||||
url='/example/custom_list_action_multiple_methods/',
|
url='/example/custom_list_action_multiple_methods/',
|
||||||
action='post'
|
action='post'
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
'update': {},
|
'put_0': coreapi.Link(
|
||||||
'update_0': coreapi.Link(
|
|
||||||
url='/example/{id}/',
|
url='/example/{id}/',
|
||||||
action='put',
|
action='put',
|
||||||
encoding='application/json',
|
encoding='application/json',
|
||||||
|
@ -249,8 +236,7 @@ class TestRouterGeneratedSchema(TestCase):
|
||||||
coreapi.Field('ordering', required=False, location='query', schema=coreschema.String(title='Ordering', description='Which field to use when ordering the results.'))
|
coreapi.Field('ordering', required=False, location='query', schema=coreschema.String(title='Ordering', description='Which field to use when ordering the results.'))
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
'partial_update': {},
|
'patch_0': coreapi.Link(
|
||||||
'partial_update_0': coreapi.Link(
|
|
||||||
url='/example/{id}/',
|
url='/example/{id}/',
|
||||||
action='patch',
|
action='patch',
|
||||||
encoding='application/json',
|
encoding='application/json',
|
||||||
|
@ -261,7 +247,6 @@ class TestRouterGeneratedSchema(TestCase):
|
||||||
coreapi.Field('ordering', required=False, location='query', schema=coreschema.String(title='Ordering', description='Which field to use when ordering the results.'))
|
coreapi.Field('ordering', required=False, location='query', schema=coreschema.String(title='Ordering', description='Which field to use when ordering the results.'))
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
'delete': {},
|
|
||||||
'delete_0': coreapi.Link(
|
'delete_0': coreapi.Link(
|
||||||
url='/example/{id}/',
|
url='/example/{id}/',
|
||||||
action='delete',
|
action='delete',
|
||||||
|
@ -344,20 +329,17 @@ class TestSchemaGenerator(TestCase):
|
||||||
title='Example API',
|
title='Example API',
|
||||||
content={
|
content={
|
||||||
'example': {
|
'example': {
|
||||||
'create': {},
|
'post_0': coreapi.Link(
|
||||||
'create_0': coreapi.Link(
|
|
||||||
url='/example/',
|
url='/example/',
|
||||||
action='post',
|
action='post',
|
||||||
fields=[]
|
fields=[]
|
||||||
),
|
),
|
||||||
'list': {},
|
'get_0': coreapi.Link(
|
||||||
'list_0': coreapi.Link(
|
|
||||||
url='/example/',
|
url='/example/',
|
||||||
action='get',
|
action='get',
|
||||||
fields=[]
|
fields=[]
|
||||||
),
|
),
|
||||||
'read': {},
|
'get_1': coreapi.Link(
|
||||||
'read_0': coreapi.Link(
|
|
||||||
url='/example/{id}/',
|
url='/example/{id}/',
|
||||||
action='get',
|
action='get',
|
||||||
fields=[
|
fields=[
|
||||||
|
@ -365,8 +347,7 @@ class TestSchemaGenerator(TestCase):
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
'sub': {
|
'sub': {
|
||||||
'list': {},
|
'get_0': coreapi.Link(
|
||||||
'list_0': coreapi.Link(
|
|
||||||
url='/example/{id}/sub/',
|
url='/example/{id}/sub/',
|
||||||
action='get',
|
action='get',
|
||||||
fields=[
|
fields=[
|
||||||
|
@ -401,20 +382,17 @@ class TestSchemaGeneratorNotAtRoot(TestCase):
|
||||||
title='Example API',
|
title='Example API',
|
||||||
content={
|
content={
|
||||||
'example': {
|
'example': {
|
||||||
'create': {},
|
'post_0': coreapi.Link(
|
||||||
'create_0': coreapi.Link(
|
|
||||||
url='/api/v1/example/',
|
url='/api/v1/example/',
|
||||||
action='post',
|
action='post',
|
||||||
fields=[]
|
fields=[]
|
||||||
),
|
),
|
||||||
'list': {},
|
'get_0': coreapi.Link(
|
||||||
'list_0': coreapi.Link(
|
|
||||||
url='/api/v1/example/',
|
url='/api/v1/example/',
|
||||||
action='get',
|
action='get',
|
||||||
fields=[]
|
fields=[]
|
||||||
),
|
),
|
||||||
'read': {},
|
'get_1': coreapi.Link(
|
||||||
'read_0': coreapi.Link(
|
|
||||||
url='/api/v1/example/{id}/',
|
url='/api/v1/example/{id}/',
|
||||||
action='get',
|
action='get',
|
||||||
fields=[
|
fields=[
|
||||||
|
@ -422,8 +400,7 @@ class TestSchemaGeneratorNotAtRoot(TestCase):
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
'sub': {
|
'sub': {
|
||||||
'list': {},
|
'get_0': coreapi.Link(
|
||||||
'list_0': coreapi.Link(
|
|
||||||
url='/api/v1/example/{id}/sub/',
|
url='/api/v1/example/{id}/sub/',
|
||||||
action='get',
|
action='get',
|
||||||
fields=[
|
fields=[
|
||||||
|
@ -460,8 +437,7 @@ class TestSchemaGeneratorWithMethodLimitedViewSets(TestCase):
|
||||||
title='Example API',
|
title='Example API',
|
||||||
content={
|
content={
|
||||||
'example1': {
|
'example1': {
|
||||||
'list': {},
|
'get_0': coreapi.Link(
|
||||||
'list_0': coreapi.Link(
|
|
||||||
url='/example1/',
|
url='/example1/',
|
||||||
action='get',
|
action='get',
|
||||||
fields=[
|
fields=[
|
||||||
|
@ -470,20 +446,17 @@ class TestSchemaGeneratorWithMethodLimitedViewSets(TestCase):
|
||||||
coreapi.Field('ordering', required=False, location='query', schema=coreschema.String(title='Ordering', description='Which field to use when ordering the results.'))
|
coreapi.Field('ordering', required=False, location='query', schema=coreschema.String(title='Ordering', description='Which field to use when ordering the results.'))
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
'custom_list_action': {},
|
'get_1': coreapi.Link(
|
||||||
'custom_list_action_0': coreapi.Link(
|
|
||||||
url='/example1/custom_list_action/',
|
url='/example1/custom_list_action/',
|
||||||
action='get'
|
action='get'
|
||||||
),
|
),
|
||||||
'custom_list_action_multiple_methods': {
|
'custom_list_action_multiple_methods': {
|
||||||
'read': {},
|
'get_0': coreapi.Link(
|
||||||
'read_0': coreapi.Link(
|
|
||||||
url='/example1/custom_list_action_multiple_methods/',
|
url='/example1/custom_list_action_multiple_methods/',
|
||||||
action='get'
|
action='get'
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
'read': {},
|
'get_2': coreapi.Link(
|
||||||
'read_0': coreapi.Link(
|
|
||||||
url='/example1/{id}/',
|
url='/example1/{id}/',
|
||||||
action='get',
|
action='get',
|
||||||
fields=[
|
fields=[
|
||||||
|
@ -521,8 +494,7 @@ class TestSchemaGeneratorWithRestrictedViewSets(TestCase):
|
||||||
title='Example API',
|
title='Example API',
|
||||||
content={
|
content={
|
||||||
'example': {
|
'example': {
|
||||||
'list': {},
|
'get_0': coreapi.Link(
|
||||||
'list_0': coreapi.Link(
|
|
||||||
url='/example/',
|
url='/example/',
|
||||||
action='get',
|
action='get',
|
||||||
fields=[]
|
fields=[]
|
||||||
|
@ -694,8 +666,7 @@ class SchemaGenerationExclusionTests(TestCase):
|
||||||
title='Exclusions',
|
title='Exclusions',
|
||||||
content={
|
content={
|
||||||
'included-fbv': {
|
'included-fbv': {
|
||||||
'list': {},
|
'get_0': coreapi.Link(url='/included-fbv/', action='get')
|
||||||
'list_0': coreapi.Link(url='/included-fbv/', action='get')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -820,10 +791,9 @@ class TestURLNamingCollisions(TestCase):
|
||||||
content={
|
content={
|
||||||
'test': {
|
'test': {
|
||||||
'list': {
|
'list': {
|
||||||
'list': {},
|
'get_0': coreapi.Link(url='/test/list/', action='get')
|
||||||
'list_0': coreapi.Link(url='/test/list/', action='get')
|
|
||||||
},
|
},
|
||||||
'list_0': coreapi.Link(url='/test', action='get')
|
'get_0': coreapi.Link(url='/test', action='get')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -832,7 +802,7 @@ class TestURLNamingCollisions(TestCase):
|
||||||
|
|
||||||
def _verify_cbv_links(self, loc, url, methods=None, number=0):
|
def _verify_cbv_links(self, loc, url, methods=None, number=0):
|
||||||
if methods is None:
|
if methods is None:
|
||||||
methods = ('read', 'update', 'partial_update', 'delete')
|
methods = ('get', 'put', 'patch', 'delete')
|
||||||
|
|
||||||
for method in methods:
|
for method in methods:
|
||||||
key = '{}_{}'.format(method, number)
|
key = '{}_{}'.format(method, number)
|
||||||
|
@ -868,20 +838,19 @@ class TestURLNamingCollisions(TestCase):
|
||||||
|
|
||||||
generator = SchemaGenerator(title='Naming Colisions', patterns=patterns)
|
generator = SchemaGenerator(title='Naming Colisions', patterns=patterns)
|
||||||
schema = generator.get_schema()
|
schema = generator.get_schema()
|
||||||
desc = schema['detail_0'].description # not important here
|
desc = schema['get_0'].description # not important here
|
||||||
|
|
||||||
expected = coreapi.Document(
|
expected = coreapi.Document(
|
||||||
url='',
|
url='',
|
||||||
title='Naming Colisions',
|
title='Naming Colisions',
|
||||||
content={
|
content={
|
||||||
'detail': {
|
'detail': {
|
||||||
'detail_export': {},
|
'get_0': coreapi.Link(
|
||||||
'detail_export_0': coreapi.Link(
|
|
||||||
url='/from-routercollision/detail/export/',
|
url='/from-routercollision/detail/export/',
|
||||||
action='get',
|
action='get',
|
||||||
description=desc)
|
description=desc)
|
||||||
},
|
},
|
||||||
'detail_0': coreapi.Link(
|
'get_0': coreapi.Link(
|
||||||
url='/from-routercollision/detail/',
|
url='/from-routercollision/detail/',
|
||||||
action='get',
|
action='get',
|
||||||
description=desc
|
description=desc
|
||||||
|
@ -900,9 +869,8 @@ class TestURLNamingCollisions(TestCase):
|
||||||
generator = SchemaGenerator(title='Naming Colisions', patterns=patterns)
|
generator = SchemaGenerator(title='Naming Colisions', patterns=patterns)
|
||||||
schema = generator.get_schema()
|
schema = generator.get_schema()
|
||||||
|
|
||||||
assert schema['example']['read'] == {}
|
assert schema['example']['get_0'].url == '/example/{id}/'
|
||||||
assert schema['example']['read_0'].url == '/example/{id}/'
|
assert schema['example']['get_1'].url == '/example/{slug}/'
|
||||||
assert schema['example']['read_1'].url == '/example/{slug}/'
|
|
||||||
|
|
||||||
def test_url_under_same_key_not_replaced_another(self):
|
def test_url_under_same_key_not_replaced_another(self):
|
||||||
|
|
||||||
|
@ -914,5 +882,5 @@ class TestURLNamingCollisions(TestCase):
|
||||||
generator = SchemaGenerator(title='Naming Colisions', patterns=patterns)
|
generator = SchemaGenerator(title='Naming Colisions', patterns=patterns)
|
||||||
schema = generator.get_schema()
|
schema = generator.get_schema()
|
||||||
|
|
||||||
assert schema['test']['list']['list_0'].url == '/test/list/'
|
assert schema['test']['list']['get_0'].url == '/test/list/'
|
||||||
assert schema['test']['list']['list_1'].url == '/test/{id}/list/'
|
assert schema['test']['list']['get_1'].url == '/test/{id}/list/'
|
||||||
|
|
Loading…
Reference in New Issue
Block a user