From 4a4cd24cabe865d90fa064cc978d7559a38c2a52 Mon Sep 17 00:00:00 2001 From: sol HYUN Date: Thu, 23 Mar 2017 17:32:40 +0900 Subject: [PATCH 01/13] Meaningless function removal (associated with subpath) --- rest_framework/schemas.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/rest_framework/schemas.py b/rest_framework/schemas.py index ec6d7f3c3..432f09952 100644 --- a/rest_framework/schemas.py +++ b/rest_framework/schemas.py @@ -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 From 3c163324ef4c604f87b63c4dcef6a52c23fe3685 Mon Sep 17 00:00:00 2001 From: sol HYUN Date: Thu, 23 Mar 2017 17:34:01 +0900 Subject: [PATCH 02/13] Meaningless method removal (associated with subpath) --- rest_framework/schemas.py | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/rest_framework/schemas.py b/rest_framework/schemas.py index 432f09952..27434d337 100644 --- a/rest_framework/schemas.py +++ b/rest_framework/schemas.py @@ -335,36 +335,6 @@ class SchemaGenerator(object): # Methods used when we generate a view instance from the raw callback... - def determine_path_prefix(self, paths): - """ - 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/' - """ - 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) def create_view(self, callback, method, request=None): """ From f6d04e651a73f076757a45948533ce44ecd692cb Mon Sep 17 00:00:00 2001 From: sol HYUN Date: Thu, 23 Mar 2017 17:42:08 +0900 Subject: [PATCH 03/13] Add make_typical_path method. --- rest_framework/schemas.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/rest_framework/schemas.py b/rest_framework/schemas.py index 27434d337..b2cfe4d77 100644 --- a/rest_framework/schemas.py +++ b/rest_framework/schemas.py @@ -336,6 +336,15 @@ class SchemaGenerator(object): # Methods used when we generate a view instance from the raw callback... + def make_typical_path(self, path): + """ + Removes '/' from path and returns a linked value using '-'. + """ + ret = [ partial for partial in path.split('/') + if partial and '{' not in partial ] + return '-'.join(ret) + + def create_view(self, callback, method, request=None): """ Given a callback, return an actual view instance. From f60114c0a7c83216789eefea067c3b4522e085a0 Mon Sep 17 00:00:00 2001 From: sol HYUN Date: Thu, 23 Mar 2017 17:48:01 +0900 Subject: [PATCH 04/13] Change get_links method code --- rest_framework/schemas.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rest_framework/schemas.py b/rest_framework/schemas.py index b2cfe4d77..b6ce371b6 100644 --- a/rest_framework/schemas.py +++ b/rest_framework/schemas.py @@ -322,13 +322,12 @@ 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):] + typical_path = self.make_typical_path(path) keys = self.get_keys(subpath, method, view) insert_into(links, keys, link) return links From 926382172a84e5e36d83e9efd3522ccfcc070990 Mon Sep 17 00:00:00 2001 From: sol HYUN Date: Thu, 23 Mar 2017 17:51:09 +0900 Subject: [PATCH 05/13] Add symbolize filter --- rest_framework/templatetags/rest_framework.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py index 4e69b62c9..7693793c9 100644 --- a/rest_framework/templatetags/rest_framework.py +++ b/rest_framework/templatetags/rest_framework.py @@ -362,3 +362,8 @@ def break_long_headers(header): if len(header) > 160 and ',' in header: header = mark_safe('
' + ',
'.join(header.split(','))) return header + + +@register.filter +def symbolize(word): + return word.replace('-', ' ').upper() From 505106d881bc21a95e7a7eba07bc9894110f8ad1 Mon Sep 17 00:00:00 2001 From: sol HYUN Date: Thu, 23 Mar 2017 17:53:11 +0900 Subject: [PATCH 06/13] Apply symbolize filter to template --- rest_framework/templates/rest_framework/docs/document.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/templates/rest_framework/docs/document.html b/rest_framework/templates/rest_framework/docs/document.html index ef5f5966b..b37fefe65 100644 --- a/rest_framework/templates/rest_framework/docs/document.html +++ b/rest_framework/templates/rest_framework/docs/document.html @@ -16,7 +16,7 @@ {% for section_key, section in document.data|items %} {% if section_key %} -

{{ section_key }} +

{{ section_key | symbolize }}

{% endif %} From 309c138b33e5eb2e183552ad376e5ed48a0d50ac Mon Sep 17 00:00:00 2001 From: sol HYUN Date: Thu, 23 Mar 2017 18:01:38 +0900 Subject: [PATCH 07/13] Modify arguments to get_link --- rest_framework/schemas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/schemas.py b/rest_framework/schemas.py index b6ce371b6..43aa1c34e 100644 --- a/rest_framework/schemas.py +++ b/rest_framework/schemas.py @@ -328,7 +328,7 @@ class SchemaGenerator(object): continue link = self.get_link(path, method, view) typical_path = self.make_typical_path(path) - keys = self.get_keys(subpath, method, view) + keys = self.get_keys(typical_path, method, view) insert_into(links, keys, link) return links From 9b88ea7da705d7dc62e1bc47fdc0643a9f64ddd5 Mon Sep 17 00:00:00 2001 From: sol HYUN Date: Thu, 23 Mar 2017 22:47:38 +0900 Subject: [PATCH 08/13] Modify action check feature --- rest_framework/schemas.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rest_framework/schemas.py b/rest_framework/schemas.py index 43aa1c34e..9b48226ad 100644 --- a/rest_framework/schemas.py +++ b/rest_framework/schemas.py @@ -328,7 +328,7 @@ class SchemaGenerator(object): continue link = self.get_link(path, method, view) typical_path = self.make_typical_path(path) - keys = self.get_keys(typical_path, method, view) + keys = self.get_keys(path, typical_path, method, view) insert_into(links, keys, link) return links @@ -590,7 +590,7 @@ class SchemaGenerator(object): # Method for generating the link layout.... - def get_keys(self, subpath, method, view): + def get_keys(self, path, subpath, method, view): """ Return a list of keys that should be used to layout a link within the schema document. @@ -607,7 +607,7 @@ 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()] From eb8032835699cdd289fb789d1156e16420574b34 Mon Sep 17 00:00:00 2001 From: sol HYUN Date: Fri, 24 Mar 2017 15:27:44 +0900 Subject: [PATCH 09/13] Modify test code --- tests/test_schemas.py | 113 +++++++++++++++++++++--------------------- 1 file changed, 57 insertions(+), 56 deletions(-) diff --git a/tests/test_schemas.py b/tests/test_schemas.py index f75370170..1d7249ddb 100644 --- a/tests/test_schemas.py +++ b/tests/test_schemas.py @@ -109,12 +109,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 +116,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 +162,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 +188,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 @@ -299,16 +299,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()) + ] + ) } } ) @@ -335,7 +334,7 @@ class TestSchemaGeneratorNotAtRoot(TestCase): url='', title='Example API', content={ - 'example': { + 'api-v1-example': { 'create': coreapi.Link( url='/api/v1/example/', action='post', @@ -352,16 +351,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()) + ] + ) } } ) @@ -407,8 +405,11 @@ class TestSchemaGeneratorWithRestrictedViewSets(TestCase): class Test4605Regression(TestCase): def test_4605_regression(self): generator = SchemaGenerator() - prefix = generator.determine_path_prefix([ + typical_path1 = generator.make_typical_path( '/api/v1/items/', + ) + assert typical_path1 == 'api-v1-items' + typical_path2 = generator.make_typical_path( '/auth/convert-token/' - ]) - assert prefix == '/' + ) + assert typical_path2 == 'auth-convert-token' From c0cf29202db4bf8f5a5d0659bb1ec9af2a3bc8b5 Mon Sep 17 00:00:00 2001 From: sol HYUN Date: Fri, 24 Mar 2017 15:36:40 +0900 Subject: [PATCH 10/13] Change to more accurate words (overall) --- tests/test_schemas.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/test_schemas.py b/tests/test_schemas.py index 1d7249ddb..b1b87b8dc 100644 --- a/tests/test_schemas.py +++ b/tests/test_schemas.py @@ -405,11 +405,11 @@ class TestSchemaGeneratorWithRestrictedViewSets(TestCase): class Test4605Regression(TestCase): def test_4605_regression(self): generator = SchemaGenerator() - typical_path1 = generator.make_typical_path( - '/api/v1/items/', + overall = generator.make_overall( + '/api/', ) - assert typical_path1 == 'api-v1-items' - typical_path2 = generator.make_typical_path( + assert overall == 'api' + overall = generator.make_overall( '/auth/convert-token/' ) - assert typical_path2 == 'auth-convert-token' + assert overall == 'auth-convert-token' From 94808d7eb2ae097f27a2a7b12a37c01523dc7721 Mon Sep 17 00:00:00 2001 From: sol HYUN Date: Fri, 24 Mar 2017 15:37:01 +0900 Subject: [PATCH 11/13] apply custom action Change to more accurate word and apply custom action --- rest_framework/schemas.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/rest_framework/schemas.py b/rest_framework/schemas.py index 9b48226ad..65b10588e 100644 --- a/rest_framework/schemas.py +++ b/rest_framework/schemas.py @@ -327,15 +327,14 @@ class SchemaGenerator(object): if not self.has_view_permissions(path, method, view): continue link = self.get_link(path, method, view) - typical_path = self.make_typical_path(path) - keys = self.get_keys(path, typical_path, 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 make_typical_path(self, path): + def make_overall(self, path): """ Removes '/' from path and returns a linked value using '-'. """ @@ -343,7 +342,6 @@ class SchemaGenerator(object): if partial and '{' not in partial ] return '-'.join(ret) - def create_view(self, callback, method, request=None): """ Given a callback, return an actual view instance. @@ -590,7 +588,7 @@ class SchemaGenerator(object): # Method for generating the link layout.... - def get_keys(self, path, 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. @@ -614,7 +612,7 @@ class SchemaGenerator(object): named_path_components = [ component for component - in subpath.strip('/').split('/') + in overall.strip('/').split('/') if '{' not in component ] @@ -626,8 +624,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] From 743c6131aa4212598e3d04272381a0e5fb387aa1 Mon Sep 17 00:00:00 2001 From: sol HYUN Date: Fri, 24 Mar 2017 16:59:19 +0900 Subject: [PATCH 12/13] clean up --- rest_framework/schemas.py | 3 +-- tests/test_atomic_requests.py | 1 + tests/test_permissions.py | 3 +++ tests/test_renderers.py | 1 + tests/test_request.py | 1 + tests/test_response.py | 1 + tests/test_reverse.py | 1 + tests/test_schemas.py | 1 + tests/test_validation.py | 1 + 9 files changed, 11 insertions(+), 2 deletions(-) diff --git a/rest_framework/schemas.py b/rest_framework/schemas.py index 65b10588e..78689c986 100644 --- a/rest_framework/schemas.py +++ b/rest_framework/schemas.py @@ -338,8 +338,7 @@ class SchemaGenerator(object): """ Removes '/' from path and returns a linked value using '-'. """ - ret = [ partial for partial in path.split('/') - if partial and '{' not in partial ] + ret = [partial for partial in path.split('/') if partial and '{' not in partial] return '-'.join(ret) def create_view(self, callback, method, request=None): diff --git a/tests/test_atomic_requests.py b/tests/test_atomic_requests.py index 9085bfc89..f925ce3d3 100644 --- a/tests/test_atomic_requests.py +++ b/tests/test_atomic_requests.py @@ -45,6 +45,7 @@ class NonAtomicAPIExceptionView(APIView): BasicModel.objects.all() raise Http404 + urlpatterns = ( url(r'^$', NonAtomicAPIExceptionView.as_view()), ) diff --git a/tests/test_permissions.py b/tests/test_permissions.py index cabf66883..6fbf766a0 100644 --- a/tests/test_permissions.py +++ b/tests/test_permissions.py @@ -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() diff --git a/tests/test_renderers.py b/tests/test_renderers.py index 12ac2dfc8..dd52b39e5 100644 --- a/tests/test_renderers.py +++ b/tests/test_renderers.py @@ -108,6 +108,7 @@ class HTMLView1(APIView): def get(self, request, **kwargs): return Response('text') + urlpatterns = [ url(r'^.*\.(?P.+)$', MockView.as_view(renderer_classes=[RendererA, RendererB])), url(r'^$', MockView.as_view(renderer_classes=[RendererA, RendererB])), diff --git a/tests/test_request.py b/tests/test_request.py index 428b969f5..2cf9320ea 100644 --- a/tests/test_request.py +++ b/tests/test_request.py @@ -120,6 +120,7 @@ class MockView(APIView): return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR) + urlpatterns = [ url(r'^$', MockView.as_view()), ] diff --git a/tests/test_response.py b/tests/test_response.py index 33c51e773..e92bf54c1 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -32,6 +32,7 @@ class MockJsonRenderer(BaseRenderer): class MockTextMediaRenderer(BaseRenderer): media_type = 'text/html' + DUMMYSTATUS = status.HTTP_200_OK DUMMYCONTENT = 'dummycontent' diff --git a/tests/test_reverse.py b/tests/test_reverse.py index 2ca44ab77..47eda256e 100644 --- a/tests/test_reverse.py +++ b/tests/test_reverse.py @@ -13,6 +13,7 @@ factory = APIRequestFactory() def null_view(request): pass + urlpatterns = [ url(r'^view$', null_view, name='view'), ] diff --git a/tests/test_schemas.py b/tests/test_schemas.py index b1b87b8dc..c026289e6 100644 --- a/tests/test_schemas.py +++ b/tests/test_schemas.py @@ -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: diff --git a/tests/test_validation.py b/tests/test_validation.py index 8ff4aaf38..8b71693c5 100644 --- a/tests/test_validation.py +++ b/tests/test_validation.py @@ -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=[]) From 361c9f4f3cbd3cacb0f25f8652de9e58b6e3c179 Mon Sep 17 00:00:00 2001 From: sol HYUN Date: Fri, 24 Mar 2017 17:04:34 +0900 Subject: [PATCH 13/13] Removed redundant, unnecessary valid_inputs. --- tests/test_fields.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/test_fields.py b/tests/test_fields.py index 16221d4cc..b3ce1e04c 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -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) }