This commit is contained in:
COCOLMAN 2017-06-05 22:31:45 +00:00 committed by GitHub
commit a264c8ca15
12 changed files with 84 additions and 108 deletions

View File

@ -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]

View File

@ -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 %}

View File

@ -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()

View File

@ -45,6 +45,7 @@ class NonAtomicAPIExceptionView(APIView):
BasicModel.objects.all()
raise Http404
urlpatterns = (
url(r'^$', NonAtomicAPIExceptionView.as_view()),
)

View File

@ -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)
}

View File

@ -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()

View File

@ -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])),

View File

@ -120,6 +120,7 @@ class MockView(APIView):
return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
urlpatterns = [
url(r'^$', MockView.as_view()),
]

View File

@ -32,6 +32,7 @@ class MockJsonRenderer(BaseRenderer):
class MockTextMediaRenderer(BaseRenderer):
media_type = 'text/html'
DUMMYSTATUS = status.HTTP_200_OK
DUMMYCONTENT = 'dummycontent'

View File

@ -13,6 +13,7 @@ factory = APIRequestFactory()
def null_view(request):
pass
urlpatterns = [
url(r'^view$', null_view, name='view'),
]

View File

@ -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'

View File

@ -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>])