mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-08-04 20:40:14 +03:00
Merge 361c9f4f3c
into 836e49b535
This commit is contained in:
commit
a264c8ca15
|
@ -83,18 +83,6 @@ def field_to_schema(field):
|
|||
return coreschema.String(title=title, description=description)
|
||||
|
||||
|
||||
def common_path(paths):
|
||||
split_paths = [path.strip('/').split('/') for path in paths]
|
||||
s1 = min(split_paths)
|
||||
s2 = max(split_paths)
|
||||
common = s1
|
||||
for i, c in enumerate(s1):
|
||||
if c != s2[i]:
|
||||
common = s1[:i]
|
||||
break
|
||||
return '/' + '/'.join(common)
|
||||
|
||||
|
||||
def get_pk_name(model):
|
||||
meta = model._meta.concrete_model._meta
|
||||
return _get_pk(meta).name
|
||||
|
@ -336,49 +324,24 @@ class SchemaGenerator(object):
|
|||
# Only generate the path prefix for paths that will be included
|
||||
if not paths:
|
||||
return None
|
||||
prefix = self.determine_path_prefix(paths)
|
||||
|
||||
for path, method, view in view_endpoints:
|
||||
if not self.has_view_permissions(path, method, view):
|
||||
continue
|
||||
link = self.get_link(path, method, view)
|
||||
subpath = path[len(prefix):]
|
||||
keys = self.get_keys(subpath, method, view)
|
||||
overall = self.make_overall(path)
|
||||
keys = self.get_keys(path, overall, method, view)
|
||||
insert_into(links, keys, link)
|
||||
return links
|
||||
|
||||
# Methods used when we generate a view instance from the raw callback...
|
||||
|
||||
def determine_path_prefix(self, paths):
|
||||
def make_overall(self, path):
|
||||
"""
|
||||
Given a list of all paths, return the common prefix which should be
|
||||
discounted when generating a schema structure.
|
||||
|
||||
This will be the longest common string that does not include that last
|
||||
component of the URL, or the last component before a path parameter.
|
||||
|
||||
For example:
|
||||
|
||||
/api/v1/users/
|
||||
/api/v1/users/{pk}/
|
||||
|
||||
The path prefix is '/api/v1/'
|
||||
Removes '/' from path and returns a linked value using '-'.
|
||||
"""
|
||||
prefixes = []
|
||||
for path in paths:
|
||||
components = path.strip('/').split('/')
|
||||
initial_components = []
|
||||
for component in components:
|
||||
if '{' in component:
|
||||
break
|
||||
initial_components.append(component)
|
||||
prefix = '/'.join(initial_components[:-1])
|
||||
if not prefix:
|
||||
# We can just break early in the case that there's at least
|
||||
# one URL that doesn't have a path prefix.
|
||||
return '/'
|
||||
prefixes.append('/' + prefix + '/')
|
||||
return common_path(prefixes)
|
||||
ret = [partial for partial in path.split('/') if partial and '{' not in partial]
|
||||
return '-'.join(ret)
|
||||
|
||||
def create_view(self, callback, method, request=None):
|
||||
"""
|
||||
|
@ -626,7 +589,7 @@ class SchemaGenerator(object):
|
|||
|
||||
# Method for generating the link layout....
|
||||
|
||||
def get_keys(self, subpath, method, view):
|
||||
def get_keys(self, path, overall, method, view):
|
||||
"""
|
||||
Return a list of keys that should be used to layout a link within
|
||||
the schema document.
|
||||
|
@ -643,14 +606,14 @@ class SchemaGenerator(object):
|
|||
action = view.action
|
||||
else:
|
||||
# Views have no associated action, so we determine one from the method.
|
||||
if is_list_view(subpath, method, view):
|
||||
if is_list_view(path, method, view):
|
||||
action = 'list'
|
||||
else:
|
||||
action = self.default_mapping[method.lower()]
|
||||
|
||||
named_path_components = [
|
||||
component for component
|
||||
in subpath.strip('/').split('/')
|
||||
in overall.strip('/').split('/')
|
||||
if '{' not in component
|
||||
]
|
||||
|
||||
|
@ -662,8 +625,8 @@ class SchemaGenerator(object):
|
|||
action = self.coerce_method_names[action]
|
||||
return named_path_components + [action]
|
||||
else:
|
||||
named_path_components = overall.split('-')
|
||||
return named_path_components[:-1] + [action]
|
||||
|
||||
if action in self.coerce_method_names:
|
||||
action = self.coerce_method_names[action]
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
{% for section_key, section in document.data|items %}
|
||||
{% if section_key %}
|
||||
<h2 id="{{ section_key }}" class="coredocs-section-title">{{ section_key }} <a href="#{{ section_key }}"><i class="fa fa-link" aria-hidden="true"></i>
|
||||
<h2 id="{{ section_key }}" class="coredocs-section-title">{{ section_key | symbolize }} <a href="#{{ section_key }}"><i class="fa fa-link" aria-hidden="true"></i>
|
||||
</a></h2>
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -362,3 +362,8 @@ def break_long_headers(header):
|
|||
if len(header) > 160 and ',' in header:
|
||||
header = mark_safe('<br> ' + ', <br>'.join(header.split(',')))
|
||||
return header
|
||||
|
||||
|
||||
@register.filter
|
||||
def symbolize(word):
|
||||
return word.replace('-', ' ').upper()
|
||||
|
|
|
@ -45,6 +45,7 @@ class NonAtomicAPIExceptionView(APIView):
|
|||
BasicModel.objects.all()
|
||||
raise Http404
|
||||
|
||||
|
||||
urlpatterns = (
|
||||
url(r'^$', NonAtomicAPIExceptionView.as_view()),
|
||||
)
|
||||
|
|
|
@ -1132,9 +1132,6 @@ class TestDateTimeField(FieldValues):
|
|||
valid_inputs = {
|
||||
'2001-01-01 13:00': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc),
|
||||
'2001-01-01T13:00': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc),
|
||||
'2001-01-01T13:00Z': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc),
|
||||
datetime.datetime(2001, 1, 1, 13, 00): datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc),
|
||||
datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc): datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc),
|
||||
# Django 1.4 does not support timezone string parsing.
|
||||
'2001-01-01T13:00Z': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc)
|
||||
}
|
||||
|
|
|
@ -246,6 +246,7 @@ class ObjectPermissionInstanceView(generics.RetrieveUpdateDestroyAPIView):
|
|||
authentication_classes = [authentication.BasicAuthentication]
|
||||
permission_classes = [ViewObjectPermissions]
|
||||
|
||||
|
||||
object_permissions_view = ObjectPermissionInstanceView.as_view()
|
||||
|
||||
|
||||
|
@ -255,6 +256,7 @@ class ObjectPermissionListView(generics.ListAPIView):
|
|||
authentication_classes = [authentication.BasicAuthentication]
|
||||
permission_classes = [ViewObjectPermissions]
|
||||
|
||||
|
||||
object_permissions_list_view = ObjectPermissionListView.as_view()
|
||||
|
||||
|
||||
|
@ -443,6 +445,7 @@ class DeniedObjectView(PermissionInstanceView):
|
|||
class DeniedObjectViewWithDetail(PermissionInstanceView):
|
||||
permission_classes = (BasicObjectPermWithDetail,)
|
||||
|
||||
|
||||
denied_view = DeniedView.as_view()
|
||||
|
||||
denied_view_with_detail = DeniedViewWithDetail.as_view()
|
||||
|
|
|
@ -108,6 +108,7 @@ class HTMLView1(APIView):
|
|||
def get(self, request, **kwargs):
|
||||
return Response('text')
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^.*\.(?P<format>.+)$', MockView.as_view(renderer_classes=[RendererA, RendererB])),
|
||||
url(r'^$', MockView.as_view(renderer_classes=[RendererA, RendererB])),
|
||||
|
|
|
@ -120,6 +120,7 @@ class MockView(APIView):
|
|||
|
||||
return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', MockView.as_view()),
|
||||
]
|
||||
|
|
|
@ -32,6 +32,7 @@ class MockJsonRenderer(BaseRenderer):
|
|||
class MockTextMediaRenderer(BaseRenderer):
|
||||
media_type = 'text/html'
|
||||
|
||||
|
||||
DUMMYSTATUS = status.HTTP_200_OK
|
||||
DUMMYCONTENT = 'dummycontent'
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ factory = APIRequestFactory()
|
|||
def null_view(request):
|
||||
pass
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^view$', null_view, name='view'),
|
||||
]
|
||||
|
|
|
@ -70,6 +70,7 @@ class ExampleViewSet(ModelViewSet):
|
|||
assert self.action
|
||||
return super(ExampleViewSet, self).get_serializer(*args, **kwargs)
|
||||
|
||||
|
||||
if coreapi:
|
||||
schema_view = get_schema_view(title='Example API')
|
||||
else:
|
||||
|
@ -109,12 +110,6 @@ class TestRouterGeneratedSchema(TestCase):
|
|||
url='/example/custom_list_action/',
|
||||
action='get'
|
||||
),
|
||||
'custom_list_action_multiple_methods': {
|
||||
'read': coreapi.Link(
|
||||
url='/example/custom_list_action_multiple_methods/',
|
||||
action='get'
|
||||
)
|
||||
},
|
||||
'read': coreapi.Link(
|
||||
url='/example/{id}/',
|
||||
action='get',
|
||||
|
@ -122,6 +117,12 @@ class TestRouterGeneratedSchema(TestCase):
|
|||
coreapi.Field('id', required=True, location='path', schema=coreschema.String())
|
||||
]
|
||||
)
|
||||
},
|
||||
'example-custom_list_action_multiple_methods': {
|
||||
'read': coreapi.Link(
|
||||
url='/example/custom_list_action_multiple_methods/',
|
||||
action='get'
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -162,31 +163,6 @@ class TestRouterGeneratedSchema(TestCase):
|
|||
coreapi.Field('id', required=True, location='path', schema=coreschema.String())
|
||||
]
|
||||
),
|
||||
'custom_action': coreapi.Link(
|
||||
url='/example/{id}/custom_action/',
|
||||
action='post',
|
||||
encoding='application/json',
|
||||
description='A description of custom action.',
|
||||
fields=[
|
||||
coreapi.Field('id', required=True, location='path', schema=coreschema.String()),
|
||||
coreapi.Field('c', required=True, location='form', schema=coreschema.String(title='C')),
|
||||
coreapi.Field('d', required=False, location='form', schema=coreschema.String(title='D')),
|
||||
]
|
||||
),
|
||||
'custom_list_action': coreapi.Link(
|
||||
url='/example/custom_list_action/',
|
||||
action='get'
|
||||
),
|
||||
'custom_list_action_multiple_methods': {
|
||||
'read': coreapi.Link(
|
||||
url='/example/custom_list_action_multiple_methods/',
|
||||
action='get'
|
||||
),
|
||||
'create': coreapi.Link(
|
||||
url='/example/custom_list_action_multiple_methods/',
|
||||
action='post'
|
||||
)
|
||||
},
|
||||
'update': coreapi.Link(
|
||||
url='/example/{id}/',
|
||||
action='put',
|
||||
|
@ -213,8 +189,33 @@ class TestRouterGeneratedSchema(TestCase):
|
|||
fields=[
|
||||
coreapi.Field('id', required=True, location='path', schema=coreschema.String())
|
||||
]
|
||||
),
|
||||
'custom_list_action': coreapi.Link(
|
||||
url='/example/custom_list_action/',
|
||||
action='get'
|
||||
),
|
||||
'custom_action': coreapi.Link(
|
||||
url='/example/{id}/custom_action/',
|
||||
action='post',
|
||||
encoding='application/json',
|
||||
description='A description of custom action.',
|
||||
fields=[
|
||||
coreapi.Field('id', required=True, location='path', schema=coreschema.String()),
|
||||
coreapi.Field('c', required=True, location='form', schema=coreschema.String(title='C')),
|
||||
coreapi.Field('d', required=False, location='form', schema=coreschema.String(title='D')),
|
||||
]
|
||||
)
|
||||
}
|
||||
},
|
||||
'example-custom_list_action_multiple_methods': {
|
||||
'read': coreapi.Link(
|
||||
url='/example/custom_list_action_multiple_methods/',
|
||||
action='get'
|
||||
),
|
||||
'create': coreapi.Link(
|
||||
url='/example/custom_list_action_multiple_methods/',
|
||||
action='post'
|
||||
)
|
||||
},
|
||||
}
|
||||
)
|
||||
assert response.data == expected
|
||||
|
@ -304,16 +305,15 @@ class TestSchemaGenerator(TestCase):
|
|||
fields=[
|
||||
coreapi.Field('id', required=True, location='path', schema=coreschema.String())
|
||||
]
|
||||
),
|
||||
'sub': {
|
||||
'list': coreapi.Link(
|
||||
url='/example/{id}/sub/',
|
||||
action='get',
|
||||
fields=[
|
||||
coreapi.Field('id', required=True, location='path', schema=coreschema.String())
|
||||
]
|
||||
)
|
||||
}
|
||||
)},
|
||||
'example-sub': {
|
||||
'list': coreapi.Link(
|
||||
url='/example/{id}/sub/',
|
||||
action='get',
|
||||
fields=[
|
||||
coreapi.Field('id', required=True, location='path', schema=coreschema.String())
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -340,7 +340,7 @@ class TestSchemaGeneratorNotAtRoot(TestCase):
|
|||
url='',
|
||||
title='Example API',
|
||||
content={
|
||||
'example': {
|
||||
'api-v1-example': {
|
||||
'create': coreapi.Link(
|
||||
url='/api/v1/example/',
|
||||
action='post',
|
||||
|
@ -357,16 +357,15 @@ class TestSchemaGeneratorNotAtRoot(TestCase):
|
|||
fields=[
|
||||
coreapi.Field('id', required=True, location='path', schema=coreschema.String())
|
||||
]
|
||||
),
|
||||
'sub': {
|
||||
'list': coreapi.Link(
|
||||
url='/api/v1/example/{id}/sub/',
|
||||
action='get',
|
||||
fields=[
|
||||
coreapi.Field('id', required=True, location='path', schema=coreschema.String())
|
||||
]
|
||||
)
|
||||
}
|
||||
)},
|
||||
'api-v1-example-sub': {
|
||||
'list': coreapi.Link(
|
||||
url='/api/v1/example/{id}/sub/',
|
||||
action='get',
|
||||
fields=[
|
||||
coreapi.Field('id', required=True, location='path', schema=coreschema.String())
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -467,8 +466,11 @@ class TestSchemaGeneratorWithRestrictedViewSets(TestCase):
|
|||
class Test4605Regression(TestCase):
|
||||
def test_4605_regression(self):
|
||||
generator = SchemaGenerator()
|
||||
prefix = generator.determine_path_prefix([
|
||||
'/api/v1/items/',
|
||||
overall = generator.make_overall(
|
||||
'/api/',
|
||||
)
|
||||
assert overall == 'api'
|
||||
overall = generator.make_overall(
|
||||
'/auth/convert-token/'
|
||||
])
|
||||
assert prefix == '/'
|
||||
)
|
||||
assert overall == 'auth-convert-token'
|
||||
|
|
|
@ -243,6 +243,7 @@ class RegexSerializer(serializers.Serializer):
|
|||
validators=[RegexValidator(regex=re.compile('^[0-9]{4,6}$'),
|
||||
message='A PIN is 4-6 digits')])
|
||||
|
||||
|
||||
expected_repr = """
|
||||
RegexSerializer():
|
||||
pin = CharField(validators=[<django.core.validators.RegexValidator object>])
|
||||
|
|
Loading…
Reference in New Issue
Block a user