mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-08-04 20:40:14 +03:00
Added preferred key to preserve if available
This commit is contained in:
parent
50b3c05eb3
commit
dcd64d0982
|
@ -70,12 +70,15 @@ class LinkNode(OrderedDict):
|
||||||
self.methods_counter = Counter()
|
self.methods_counter = Counter()
|
||||||
super(LinkNode, self).__init__()
|
super(LinkNode, self).__init__()
|
||||||
|
|
||||||
def get_next_key(self, method):
|
def get_available_key(self, preferred_key):
|
||||||
while True:
|
if preferred_key not in self:
|
||||||
current_val = self.methods_counter[method]
|
return preferred_key
|
||||||
self.methods_counter[method] += 1
|
|
||||||
|
|
||||||
key = '{}_{}'.format(method, current_val)
|
while True:
|
||||||
|
current_val = self.methods_counter[preferred_key]
|
||||||
|
self.methods_counter[preferred_key] += 1
|
||||||
|
|
||||||
|
key = '{}_{}'.format(preferred_key, current_val)
|
||||||
if key not in self:
|
if key not in self:
|
||||||
return key
|
return key
|
||||||
|
|
||||||
|
@ -89,13 +92,13 @@ def insert_into(target, keys, value):
|
||||||
>>> example
|
>>> example
|
||||||
LinkNode({'a': LinkNode({'b': LinkNode({'c': LinkNode(links=[123])}}})))
|
LinkNode({'a': LinkNode({'b': LinkNode({'c': LinkNode(links=[123])}}})))
|
||||||
"""
|
"""
|
||||||
for key in keys:
|
for key in keys[:-1]:
|
||||||
if key not in target:
|
if key not in target:
|
||||||
target[key] = LinkNode()
|
target[key] = LinkNode()
|
||||||
target = target[key]
|
target = target[key]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
target.links.append(value)
|
target.links.append((keys[-1], value))
|
||||||
except TypeError:
|
except TypeError:
|
||||||
msg = INSERT_INTO_COLLISION_FMT.format(
|
msg = INSERT_INTO_COLLISION_FMT.format(
|
||||||
value_url=value.url,
|
value_url=value.url,
|
||||||
|
@ -109,8 +112,8 @@ def distribute_links(obj):
|
||||||
for key, value in obj.items():
|
for key, value in obj.items():
|
||||||
distribute_links(value)
|
distribute_links(value)
|
||||||
|
|
||||||
for link in obj.links:
|
for preferred_key, link in obj.links:
|
||||||
key = obj.get_next_key(link.action)
|
key = obj.get_available_key(preferred_key)
|
||||||
obj[key] = link
|
obj[key] = link
|
||||||
|
|
||||||
|
|
||||||
|
@ -436,8 +439,16 @@ 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:
|
||||||
return named_path_components[:-1]
|
action = self.default_mapping[method.lower()]
|
||||||
|
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
|
return named_path_components + [action]
|
||||||
|
|
|
@ -120,7 +120,7 @@ class TestRouterGeneratedSchema(TestCase):
|
||||||
title='Example API',
|
title='Example API',
|
||||||
content={
|
content={
|
||||||
'example': {
|
'example': {
|
||||||
'get_0': coreapi.Link(
|
'list': coreapi.Link(
|
||||||
url='/example/',
|
url='/example/',
|
||||||
action='get',
|
action='get',
|
||||||
fields=[
|
fields=[
|
||||||
|
@ -129,17 +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.'))
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
'get_1': coreapi.Link(
|
'custom_list_action': 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': {
|
||||||
'get_0': coreapi.Link(
|
'read': coreapi.Link(
|
||||||
url='/example/custom_list_action_multiple_methods/',
|
url='/example/custom_list_action_multiple_methods/',
|
||||||
action='get'
|
action='get'
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
'get_2': coreapi.Link(
|
'read': coreapi.Link(
|
||||||
url='/example/{id}/',
|
url='/example/{id}/',
|
||||||
action='get',
|
action='get',
|
||||||
fields=[
|
fields=[
|
||||||
|
@ -150,7 +150,6 @@ class TestRouterGeneratedSchema(TestCase):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.data == expected
|
assert response.data == expected
|
||||||
|
|
||||||
def test_authenticated_request(self):
|
def test_authenticated_request(self):
|
||||||
|
@ -163,7 +162,7 @@ class TestRouterGeneratedSchema(TestCase):
|
||||||
title='Example API',
|
title='Example API',
|
||||||
content={
|
content={
|
||||||
'example': {
|
'example': {
|
||||||
'get_0': coreapi.Link(
|
'list': coreapi.Link(
|
||||||
url='/example/',
|
url='/example/',
|
||||||
action='get',
|
action='get',
|
||||||
fields=[
|
fields=[
|
||||||
|
@ -172,7 +171,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.'))
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
'post_0': coreapi.Link(
|
'create': coreapi.Link(
|
||||||
url='/example/',
|
url='/example/',
|
||||||
action='post',
|
action='post',
|
||||||
encoding='application/json',
|
encoding='application/json',
|
||||||
|
@ -181,7 +180,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'))
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
'get_2': coreapi.Link(
|
'read': coreapi.Link(
|
||||||
url='/example/{id}/',
|
url='/example/{id}/',
|
||||||
action='get',
|
action='get',
|
||||||
fields=[
|
fields=[
|
||||||
|
@ -189,7 +188,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.'))
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
'post_1': coreapi.Link(
|
'custom_action': coreapi.Link(
|
||||||
url='/example/{id}/custom_action/',
|
url='/example/{id}/custom_action/',
|
||||||
action='post',
|
action='post',
|
||||||
encoding='application/json',
|
encoding='application/json',
|
||||||
|
@ -200,7 +199,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')),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
'post_2': coreapi.Link(
|
'custom_action_with_list_fields': 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',
|
||||||
|
@ -211,21 +210,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())),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
'get_1': coreapi.Link(
|
'custom_list_action': 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': {
|
||||||
'get_0': coreapi.Link(
|
'read': coreapi.Link(
|
||||||
url='/example/custom_list_action_multiple_methods/',
|
url='/example/custom_list_action_multiple_methods/',
|
||||||
action='get'
|
action='get'
|
||||||
),
|
),
|
||||||
'post_0': coreapi.Link(
|
'create': coreapi.Link(
|
||||||
url='/example/custom_list_action_multiple_methods/',
|
url='/example/custom_list_action_multiple_methods/',
|
||||||
action='post'
|
action='post'
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
'put_0': coreapi.Link(
|
'update': coreapi.Link(
|
||||||
url='/example/{id}/',
|
url='/example/{id}/',
|
||||||
action='put',
|
action='put',
|
||||||
encoding='application/json',
|
encoding='application/json',
|
||||||
|
@ -236,7 +235,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.'))
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
'patch_0': coreapi.Link(
|
'partial_update': coreapi.Link(
|
||||||
url='/example/{id}/',
|
url='/example/{id}/',
|
||||||
action='patch',
|
action='patch',
|
||||||
encoding='application/json',
|
encoding='application/json',
|
||||||
|
@ -247,7 +246,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.'))
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
'delete_0': coreapi.Link(
|
'delete': coreapi.Link(
|
||||||
url='/example/{id}/',
|
url='/example/{id}/',
|
||||||
action='delete',
|
action='delete',
|
||||||
fields=[
|
fields=[
|
||||||
|
@ -329,17 +328,17 @@ class TestSchemaGenerator(TestCase):
|
||||||
title='Example API',
|
title='Example API',
|
||||||
content={
|
content={
|
||||||
'example': {
|
'example': {
|
||||||
'post_0': coreapi.Link(
|
'create': coreapi.Link(
|
||||||
url='/example/',
|
url='/example/',
|
||||||
action='post',
|
action='post',
|
||||||
fields=[]
|
fields=[]
|
||||||
),
|
),
|
||||||
'get_0': coreapi.Link(
|
'list': coreapi.Link(
|
||||||
url='/example/',
|
url='/example/',
|
||||||
action='get',
|
action='get',
|
||||||
fields=[]
|
fields=[]
|
||||||
),
|
),
|
||||||
'get_1': coreapi.Link(
|
'read': coreapi.Link(
|
||||||
url='/example/{id}/',
|
url='/example/{id}/',
|
||||||
action='get',
|
action='get',
|
||||||
fields=[
|
fields=[
|
||||||
|
@ -347,7 +346,7 @@ class TestSchemaGenerator(TestCase):
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
'sub': {
|
'sub': {
|
||||||
'get_0': coreapi.Link(
|
'list': coreapi.Link(
|
||||||
url='/example/{id}/sub/',
|
url='/example/{id}/sub/',
|
||||||
action='get',
|
action='get',
|
||||||
fields=[
|
fields=[
|
||||||
|
@ -382,17 +381,17 @@ class TestSchemaGeneratorNotAtRoot(TestCase):
|
||||||
title='Example API',
|
title='Example API',
|
||||||
content={
|
content={
|
||||||
'example': {
|
'example': {
|
||||||
'post_0': coreapi.Link(
|
'create': coreapi.Link(
|
||||||
url='/api/v1/example/',
|
url='/api/v1/example/',
|
||||||
action='post',
|
action='post',
|
||||||
fields=[]
|
fields=[]
|
||||||
),
|
),
|
||||||
'get_0': coreapi.Link(
|
'list': coreapi.Link(
|
||||||
url='/api/v1/example/',
|
url='/api/v1/example/',
|
||||||
action='get',
|
action='get',
|
||||||
fields=[]
|
fields=[]
|
||||||
),
|
),
|
||||||
'get_1': coreapi.Link(
|
'read': coreapi.Link(
|
||||||
url='/api/v1/example/{id}/',
|
url='/api/v1/example/{id}/',
|
||||||
action='get',
|
action='get',
|
||||||
fields=[
|
fields=[
|
||||||
|
@ -400,7 +399,7 @@ class TestSchemaGeneratorNotAtRoot(TestCase):
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
'sub': {
|
'sub': {
|
||||||
'get_0': coreapi.Link(
|
'list': coreapi.Link(
|
||||||
url='/api/v1/example/{id}/sub/',
|
url='/api/v1/example/{id}/sub/',
|
||||||
action='get',
|
action='get',
|
||||||
fields=[
|
fields=[
|
||||||
|
@ -437,7 +436,7 @@ class TestSchemaGeneratorWithMethodLimitedViewSets(TestCase):
|
||||||
title='Example API',
|
title='Example API',
|
||||||
content={
|
content={
|
||||||
'example1': {
|
'example1': {
|
||||||
'get_0': coreapi.Link(
|
'list': coreapi.Link(
|
||||||
url='/example1/',
|
url='/example1/',
|
||||||
action='get',
|
action='get',
|
||||||
fields=[
|
fields=[
|
||||||
|
@ -446,17 +445,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.'))
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
'get_1': coreapi.Link(
|
'custom_list_action': 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': {
|
||||||
'get_0': coreapi.Link(
|
'read': coreapi.Link(
|
||||||
url='/example1/custom_list_action_multiple_methods/',
|
url='/example1/custom_list_action_multiple_methods/',
|
||||||
action='get'
|
action='get'
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
'get_2': coreapi.Link(
|
'read': coreapi.Link(
|
||||||
url='/example1/{id}/',
|
url='/example1/{id}/',
|
||||||
action='get',
|
action='get',
|
||||||
fields=[
|
fields=[
|
||||||
|
@ -494,7 +493,7 @@ class TestSchemaGeneratorWithRestrictedViewSets(TestCase):
|
||||||
title='Example API',
|
title='Example API',
|
||||||
content={
|
content={
|
||||||
'example': {
|
'example': {
|
||||||
'get_0': coreapi.Link(
|
'list': coreapi.Link(
|
||||||
url='/example/',
|
url='/example/',
|
||||||
action='get',
|
action='get',
|
||||||
fields=[]
|
fields=[]
|
||||||
|
@ -666,7 +665,7 @@ class SchemaGenerationExclusionTests(TestCase):
|
||||||
title='Exclusions',
|
title='Exclusions',
|
||||||
content={
|
content={
|
||||||
'included-fbv': {
|
'included-fbv': {
|
||||||
'get_0': coreapi.Link(url='/included-fbv/', action='get')
|
'list': coreapi.Link(url='/included-fbv/', action='get')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -791,21 +790,26 @@ class TestURLNamingCollisions(TestCase):
|
||||||
content={
|
content={
|
||||||
'test': {
|
'test': {
|
||||||
'list': {
|
'list': {
|
||||||
'get_0': coreapi.Link(url='/test/list/', action='get')
|
'list': coreapi.Link(url='/test/list/', action='get')
|
||||||
},
|
},
|
||||||
'get_0': coreapi.Link(url='/test', action='get')
|
'list_0': coreapi.Link(url='/test', action='get')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
assert expected == schema
|
assert expected == schema
|
||||||
|
|
||||||
def _verify_cbv_links(self, loc, url, methods=None, number=0):
|
def _verify_cbv_links(self, loc, url, methods=None, suffixes=None):
|
||||||
if methods is None:
|
if methods is None:
|
||||||
methods = ('get', 'put', 'patch', 'delete')
|
methods = ('read', 'update', 'partial_update', 'delete')
|
||||||
|
if suffixes is None:
|
||||||
|
suffixes = (None for m in methods)
|
||||||
|
|
||||||
for method in methods:
|
for method, suffix in zip(methods, suffixes):
|
||||||
key = '{}_{}'.format(method, number)
|
if suffix is not None:
|
||||||
|
key = '{}_{}'.format(method, suffix)
|
||||||
|
else:
|
||||||
|
key = method
|
||||||
assert loc[key].url == url
|
assert loc[key].url == url
|
||||||
|
|
||||||
def test_manually_routing_generic_view(self):
|
def test_manually_routing_generic_view(self):
|
||||||
|
@ -829,7 +833,7 @@ class TestURLNamingCollisions(TestCase):
|
||||||
self._verify_cbv_links(schema['test']['get'], '/test/get/')
|
self._verify_cbv_links(schema['test']['get'], '/test/get/')
|
||||||
self._verify_cbv_links(schema['test']['update'], '/test/update/')
|
self._verify_cbv_links(schema['test']['update'], '/test/update/')
|
||||||
self._verify_cbv_links(schema['test']['retrieve'], '/test/retrieve/')
|
self._verify_cbv_links(schema['test']['retrieve'], '/test/retrieve/')
|
||||||
self._verify_cbv_links(schema['test'], '/test')
|
self._verify_cbv_links(schema['test'], '/test', suffixes=(None, '0', None, '0'))
|
||||||
|
|
||||||
def test_from_router(self):
|
def test_from_router(self):
|
||||||
patterns = [
|
patterns = [
|
||||||
|
@ -838,19 +842,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['get_0'].description # not important here
|
desc = schema['detail_0'].description # not important here
|
||||||
|
|
||||||
expected = coreapi.Document(
|
expected = coreapi.Document(
|
||||||
url='',
|
url='',
|
||||||
title='Naming Colisions',
|
title='Naming Colisions',
|
||||||
content={
|
content={
|
||||||
'detail': {
|
'detail': {
|
||||||
'get_0': coreapi.Link(
|
'detail_export': coreapi.Link(
|
||||||
url='/from-routercollision/detail/export/',
|
url='/from-routercollision/detail/export/',
|
||||||
action='get',
|
action='get',
|
||||||
description=desc)
|
description=desc)
|
||||||
},
|
},
|
||||||
'get_0': coreapi.Link(
|
'detail_0': coreapi.Link(
|
||||||
url='/from-routercollision/detail/',
|
url='/from-routercollision/detail/',
|
||||||
action='get',
|
action='get',
|
||||||
description=desc
|
description=desc
|
||||||
|
@ -869,8 +873,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']['get_0'].url == '/example/{id}/'
|
assert schema['example']['read'].url == '/example/{id}/'
|
||||||
assert schema['example']['get_1'].url == '/example/{slug}/'
|
assert schema['example']['read_0'].url == '/example/{slug}/'
|
||||||
|
|
||||||
def test_url_under_same_key_not_replaced_another(self):
|
def test_url_under_same_key_not_replaced_another(self):
|
||||||
|
|
||||||
|
@ -882,5 +886,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']['get_0'].url == '/test/list/'
|
assert schema['test']['list']['list'].url == '/test/list/'
|
||||||
assert schema['test']['list']['get_1'].url == '/test/{id}/list/'
|
assert schema['test']['list']['list_0'].url == '/test/{id}/list/'
|
||||||
|
|
Loading…
Reference in New Issue
Block a user