diff --git a/README.md b/README.md index 3bdae58..6b4b6ee 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,12 @@ Many thanks to Tom Christie & all the contributors who have developed [Django RE * David Wolever (@wolever) * Brian Moe (@bmoe) * Ian Martin (@aztechian) +* @pzrq +* @jfelectron +* Warnar Boekkooi (@boekkooi) +* Darren Thompson (@WhiteDawn) +* Lukasz Balcerzak (@lukaszb) +* David Newgas (@davidn) ### Django REST Framework Docs contributors: @@ -126,6 +132,12 @@ Many thanks to Tom Christie & all the contributors who have developed [Django RE ## Release Notes: +### v0.1.12 (Feb 24, 2014) +* Improved resource grouping +* Alphabetical sorting of resources +* Fixed CSRF headers +* Misc bug fixes & improvements + ### v0.1.11 (Dec 1, 2013) * Added proper unicode support for Python 2 * Compatibility fixes for Python 3 diff --git a/rest_framework_swagger/__init__.py b/rest_framework_swagger/__init__.py index b37889f..b053bd8 100644 --- a/rest_framework_swagger/__init__.py +++ b/rest_framework_swagger/__init__.py @@ -7,8 +7,7 @@ 'api_key': '', 'enabled_methods': ['get', 'post', 'put', 'patch', 'delete'], 'is_authenticated': False, - 'is_superuser': False, - 'resource_url_prefix': None + 'is_superuser': False } try: diff --git a/rest_framework_swagger/tests.py b/rest_framework_swagger/tests.py index acf0719..35cf65e 100644 --- a/rest_framework_swagger/tests.py +++ b/rest_framework_swagger/tests.py @@ -82,7 +82,7 @@ def test_flatten_url_tree_with_filter(self): urlparser = UrlParser() apis = urlparser.get_apis(self.url_patterns, filter_path="a-view") - self.assertEqual(1, len(apis)) + self.assertEqual(3, len(apis)) def test_flatten_url_tree_excluded_namesapce(self): urls = patterns('', @@ -135,9 +135,9 @@ def test_get_api_callback_not_rest_view(self): def test_get_top_level_api(self): urlparser = UrlParser() - apis = urlparser.get_top_level_apis(urlparser.get_apis(self.url_patterns), None) + apis = urlparser.get_top_level_apis(urlparser.get_apis(self.url_patterns)) - self.assertEqual(4, len(apis)) + self.assertEqual(2, len(apis)) def test_assemble_endpoint_data(self): """ @@ -175,6 +175,20 @@ class MyViewSet(ModelViewSet): self.assertEqual(4, urls_created - len(apis)) + def test_get_base_path_for_common_endpoints(self): + parser = UrlParser() + paths = ['api/endpoint1', 'api/endpoint2'] + base_path = parser.__get_base_path__(paths) + + self.assertEqual('api/', base_path) + + def test_get_base_path_for_root_level_endpoints(self): + parser = UrlParser() + paths = ['endpoint1', 'endpoint2', 'endpoint3'] + base_path = parser.__get_base_path__(paths) + + self.assertEqual('', base_path) + class NestedUrlParserTest(TestCase): def setUp(self): diff --git a/rest_framework_swagger/urlparser.py b/rest_framework_swagger/urlparser.py index f91c4a3..f681dfd 100644 --- a/rest_framework_swagger/urlparser.py +++ b/rest_framework_swagger/urlparser.py @@ -1,17 +1,18 @@ -import unipath +import os from django.conf import settings from django.utils.importlib import import_module from django.core.urlresolvers import RegexURLResolver, RegexURLPattern from django.contrib.admindocs.views import simplify_regex + from rest_framework.views import APIView -from rest_framework_swagger.apidocview import APIDocView +from .apidocview import APIDocView class UrlParser(object): - def get_apis(self, patterns=None, filter_path=None, exclude_namespaces=[], resource_url_prefix=None): + def get_apis(self, patterns=None, filter_path=None, exclude_namespaces=[]): """ Returns all the DRF APIViews found in the project URLs @@ -22,40 +23,26 @@ def get_apis(self, patterns=None, filter_path=None, exclude_namespaces=[], resou urls = import_module(settings.ROOT_URLCONF) patterns = urls.urlpatterns - if filter_path is not None: - return self.get_filtered_apis(patterns, filter_path, resource_url_prefix) - - patterns = self.__flatten_patterns_tree__( + apis = self.__flatten_patterns_tree__( patterns, filter_path=filter_path, exclude_namespaces=exclude_namespaces, ) + if filter_path is not None: + return self.get_filtered_apis(apis, filter_path) - return patterns + return apis - def get_filtered_apis(self, patterns, filter_path, resource_url_prefix): + def get_filtered_apis(self, apis, filter_path): filtered_list = [] - all_apis = self.get_apis(patterns, exclude_namespaces=[]) - top_level_apis = self.get_top_level_apis(all_apis, resource_url_prefix) - top_level_apis.discard(filter_path) - - for top in list(top_level_apis): - if top in filter_path: #and len(filter_path) > len(top): - top_level_apis.remove(top) - - for api in all_apis: - remove = False - for top in top_level_apis: - if top + '/' in api['path'].lstrip("/"): - remove = True - - if filter_path in api['path'].strip("/") and not remove: + for api in apis: + if filter_path in api['path'].strip('/'): filtered_list.append(api) return filtered_list - def get_top_level_apis(self, apis, resource_url_prefix): + def get_top_level_apis(self, apis): """ Returns the 'top level' APIs (ie. swagger 'resources') @@ -68,14 +55,36 @@ def get_top_level_apis(self, apis, resource_url_prefix): # If a URLs /resource/ and /resource/{pk} exist, use the base # as the resource. If there is no base resource URL, then include path_base = path.split('/{')[0] - if resource_url_prefix is not None: - path_base = path_base.replace(resource_url_prefix, '', 1) - path_base = path_base.split('/')[0] if '{' in path and path_base in api_paths: continue root_paths.add(path_base) - return root_paths + top_level_apis = self.__filter_top_level_apis__(root_paths) + + return sorted(top_level_apis, key=self.__get_last_element__) + + def __filter_top_level_apis__(self, root_paths): + """ + Returns top level APIs + """ + filtered_paths = set() + base_path = self.__get_base_path__(root_paths) + for path in root_paths: + resource = path.lstrip(base_path).split('/')[0] + filtered_paths.add(base_path + resource) + + return list(filtered_paths) + + def __get_base_path__(self, root_paths): + base_path = os.path.commonprefix(root_paths) + slash_index = base_path.rfind('/') + 1 + base_path = base_path[:slash_index] + + return base_path + + def __get_last_element__(self, paths): + split_paths = paths.split('/') + return split_paths[len(split_paths) - 1] def __assemble_endpoint_data__(self, pattern, prefix='', filter_path=None): """ diff --git a/rest_framework_swagger/views.py b/rest_framework_swagger/views.py index 4bf62e8..c90f838 100644 --- a/rest_framework_swagger/views.py +++ b/rest_framework_swagger/views.py @@ -66,21 +66,11 @@ def get(self, request): def get_resources(self): urlparser = UrlParser() - apis = urlparser.get_apis( - exclude_namespaces=SWAGGER_SETTINGS.get('exclude_namespaces') - ) - resources = urlparser.get_top_level_apis( - apis, - resource_url_prefix=SWAGGER_SETTINGS.get('resource_url_prefix') - ) - resources = sorted(resources, key=self.get_child) + apis = urlparser.get_apis(exclude_namespaces=SWAGGER_SETTINGS.get('exclude_namespaces')) + resources = urlparser.get_top_level_apis(apis) return resources - def get_child(self, path): - split_path = path.split('/') - return split_path[len(split_path) - 1] - class SwaggerApiView(APIDocView): @@ -101,7 +91,4 @@ def get(self, request, path): def get_api_for_resource(self, filter_path): urlparser = UrlParser() - return urlparser.get_apis( - filter_path=filter_path, - resource_url_prefix=SWAGGER_SETTINGS.get('resource_url_prefix') - ) + return urlparser.get_apis(filter_path=filter_path) diff --git a/setup.py b/setup.py index b1e3882..4c8ac11 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from rest_framework_swagger import VERSION -#README = open(os.path.join(os.path.dirname(__file__), 'README.md')).read() + README = """ Django REST Swagger @@ -50,4 +50,3 @@ 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', ], ) -