From d036de034e9ace7e66a4fb76f678faa5c7fd3019 Mon Sep 17 00:00:00 2001 From: Maxence Date: Tue, 22 Dec 2015 17:24:50 +0100 Subject: [PATCH 1/5] (Not tested yet) Added the possibility to filter by application name in the url + modifed the name_parent's value of the ApiEndpoint --- rest_framework_docs/api_docs.py | 20 ++++++++++--------- rest_framework_docs/api_endpoint.py | 12 ++++++++--- .../templates/rest_framework_docs/base.html | 12 +++++------ rest_framework_docs/urls.py | 1 + rest_framework_docs/views.py | 4 ++-- 5 files changed, 29 insertions(+), 20 deletions(-) diff --git a/rest_framework_docs/api_docs.py b/rest_framework_docs/api_docs.py index 6e9028a..56382c1 100644 --- a/rest_framework_docs/api_docs.py +++ b/rest_framework_docs/api_docs.py @@ -1,3 +1,4 @@ +from operator import attrgetter from django.conf import settings from django.core.urlresolvers import RegexURLResolver, RegexURLPattern from rest_framework.views import APIView @@ -6,27 +7,28 @@ class ApiDocumentation(object): - def __init__(self): + def __init__(self, app_name=None): self.endpoints = [] root_urlconf = __import__(settings.ROOT_URLCONF) if hasattr(root_urlconf, 'urls'): - self.get_all_view_names(root_urlconf.urls.urlpatterns) + self.get_all_view_names(root_urlconf.urls.urlpatterns, app_name=app_name) else: - self.get_all_view_names(root_urlconf.urlpatterns) + self.get_all_view_names(root_urlconf.urlpatterns, app_name=app_name) - def get_all_view_names(self, urlpatterns, parent_pattern=None): + def get_all_view_names(self, urlpatterns, parent_pattern=None, app_name=None): for pattern in urlpatterns: - if isinstance(pattern, RegexURLResolver): + if isinstance(pattern, RegexURLResolver) and (not app_name or app_name == pattern.app_name): self.get_all_view_names(urlpatterns=pattern.url_patterns, parent_pattern=pattern) elif isinstance(pattern, RegexURLPattern) and self._is_drf_view(pattern): - api_endpoint = ApiEndpoint(pattern, parent_pattern) - self.endpoints.append(api_endpoint) + if not app_name or getattr(parent_pattern, 'app_name', None) == app_name: + api_endpoint = ApiEndpoint(pattern, parent_pattern) + self.endpoints.append(api_endpoint) def _is_drf_view(self, pattern): # Should check whether a pattern inherits from DRF's APIView - if (hasattr(pattern.callback, 'cls') and issubclass(pattern.callback.cls, APIView)): + if hasattr(pattern.callback, 'cls') and issubclass(pattern.callback.cls, APIView): return True return False def get_endpoints(self): - return self.endpoints + return sorted(self.endpoints, key=attrgetter('name_parent')) diff --git a/rest_framework_docs/api_endpoint.py b/rest_framework_docs/api_endpoint.py index 60cc763..3e0ceba 100644 --- a/rest_framework_docs/api_endpoint.py +++ b/rest_framework_docs/api_endpoint.py @@ -1,5 +1,6 @@ import inspect from django.contrib.admindocs.views import simplify_regex +from rest_framework.viewsets import ModelViewSet class ApiEndpoint(object): @@ -9,16 +10,21 @@ def __init__(self, pattern, parent_pattern=None): self.callback = pattern.callback # self.name = pattern.name self.docstring = self.__get_docstring__() - self.name_parent = simplify_regex(parent_pattern.regex.pattern).replace('/', '') if parent_pattern else None + if parent_pattern: + self.name_parent = parent_pattern.namespace or parent_pattern.app_name or \ + simplify_regex(parent_pattern.regex.pattern).replace('/', '-') + if hasattr(pattern.callback, 'cls') and issubclass(pattern.callback.cls, ModelViewSet): + self.name_parent = '{} (REST)'.format(self.name_parent) + else: + self.name_parent = '' self.path = self.__get_path__(parent_pattern) self.allowed_methods = self.__get_allowed_methods__() - # self.view_name = pattern.callback.__name__ self.errors = None self.fields = self.__get_serializer_fields__() def __get_path__(self, parent_pattern): if parent_pattern: - return "/{0}{1}".format(self.name_parent, simplify_regex(self.pattern.regex.pattern)) + return simplify_regex(parent_pattern.regex.pattern + self.pattern.regex.pattern) return simplify_regex(self.pattern.regex.pattern) def __get_allowed_methods__(self): diff --git a/rest_framework_docs/templates/rest_framework_docs/base.html b/rest_framework_docs/templates/rest_framework_docs/base.html index 71a7f61..c9a8b9b 100644 --- a/rest_framework_docs/templates/rest_framework_docs/base.html +++ b/rest_framework_docs/templates/rest_framework_docs/base.html @@ -54,12 +54,12 @@ - {% block jumbotron %} -
-

DRF Docs

-

Document Web APIs made with Django REST Framework.

-
- {% endblock %} + + + + + + {% block content %}{% endblock %} diff --git a/rest_framework_docs/urls.py b/rest_framework_docs/urls.py index beb1588..3e7d7f5 100644 --- a/rest_framework_docs/urls.py +++ b/rest_framework_docs/urls.py @@ -4,4 +4,5 @@ urlpatterns = [ # Url to view the API Docs url(r'^$', DRFDocsView.as_view(), name='drfdocs'), + url(r'^(?P\w+)/$', DRFDocsView.as_view(), name='drfdocs-ns'), ] diff --git a/rest_framework_docs/views.py b/rest_framework_docs/views.py index 3d8805a..ef99d91 100644 --- a/rest_framework_docs/views.py +++ b/rest_framework_docs/views.py @@ -8,13 +8,13 @@ class DRFDocsView(TemplateView): template_name = "rest_framework_docs/home.html" - def get_context_data(self, **kwargs): + def get_context_data(self, app_name=None, **kwargs): settings = DRFSettings().settings if settings["HIDDEN"]: raise Http404("Django Rest Framework Docs are hidden. Check your settings.") context = super(DRFDocsView, self).get_context_data(**kwargs) - docs = ApiDocumentation() + docs = ApiDocumentation(app_name=app_name) endpoints = docs.get_endpoints() query = self.request.GET.get("search", "") From deb94461af2d2030d8d5bb56c9faa0637d7d4e75 Mon Sep 17 00:00:00 2001 From: Maxence Date: Mon, 4 Jan 2016 17:01:00 +0100 Subject: [PATCH 2/5] Add app_name in urls + tests --- demo/project/urls.py | 5 +-- rest_framework_docs/api_endpoint.py | 3 +- tests/tests.py | 55 ++++++++++++++++++++++++----- tests/urls.py | 4 +-- 4 files changed, 53 insertions(+), 14 deletions(-) diff --git a/demo/project/urls.py b/demo/project/urls.py index d8e049f..bf25fbd 100644 --- a/demo/project/urls.py +++ b/demo/project/urls.py @@ -21,6 +21,7 @@ url(r'^docs/', include('rest_framework_docs.urls')), # API - url(r'^accounts/', view=include('project.accounts.urls', namespace='accounts')), - url(r'^organisations/', view=include('project.organisations.urls', namespace='organisations')), + url(r'^accounts/', view=include('project.accounts.urls', namespace='accounts', app_name='accounts')), + url(r'^organisations/', view=include('project.organisations.urls', namespace='organisations', + app_name='organisations')), ] diff --git a/rest_framework_docs/api_endpoint.py b/rest_framework_docs/api_endpoint.py index 7cd51a9..07a7737 100644 --- a/rest_framework_docs/api_endpoint.py +++ b/rest_framework_docs/api_endpoint.py @@ -9,13 +9,12 @@ class ApiEndpoint(object): def __init__(self, pattern, parent_pattern=None): self.pattern = pattern self.callback = pattern.callback - # self.name = pattern.name self.docstring = self.__get_docstring__() if parent_pattern: self.name_parent = parent_pattern.namespace or parent_pattern.app_name or \ simplify_regex(parent_pattern.regex.pattern).replace('/', '-') if hasattr(pattern.callback, 'cls') and issubclass(pattern.callback.cls, ModelViewSet): - self.name_parent = '{} (REST)'.format(self.name_parent) + self.name_parent = '%s (REST)' % self.name_parent else: self.name_parent = '' self.path = self.__get_path__(parent_pattern) diff --git a/tests/tests.py b/tests/tests.py index e9ba17a..bd644e9 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -30,16 +30,16 @@ def test_index_view_with_endpoints(self): self.assertEqual(len(response.context["endpoints"]), 10) # Test the login view - self.assertEqual(response.context["endpoints"][0].name_parent, "accounts") - self.assertEqual(response.context["endpoints"][0].allowed_methods, ['POST', 'OPTIONS']) - self.assertEqual(response.context["endpoints"][0].path, "/accounts/login/") - self.assertEqual(response.context["endpoints"][0].docstring, "A view that allows users to login providing their username and password.") - self.assertEqual(len(response.context["endpoints"][0].fields), 2) - self.assertEqual(response.context["endpoints"][0].fields[0]["type"], "CharField") - self.assertTrue(response.context["endpoints"][0].fields[0]["required"]) + self.assertEqual(response.context["endpoints"][1].name_parent, "accounts") + self.assertEqual(response.context["endpoints"][1].allowed_methods, ['POST', 'OPTIONS']) + self.assertEqual(response.context["endpoints"][1].path, "/accounts/login/") + self.assertEqual(response.context["endpoints"][1].docstring, "A view that allows users to login providing their username and password.") + self.assertEqual(len(response.context["endpoints"][1].fields), 2) + self.assertEqual(response.context["endpoints"][1].fields[0]["type"], "CharField") + self.assertTrue(response.context["endpoints"][1].fields[0]["required"]) # The view "OrganisationErroredView" (organisations/(?P[\w-]+)/errored/) should contain an error. - self.assertEqual(str(response.context["endpoints"][8].errors), "'test_value'") + self.assertEqual(str(response.context["endpoints"][9].errors), "'test_value'") def test_index_search_with_endpoints(self): response = self.client.get("%s?search=reset-password" % reverse("drfdocs")) @@ -59,3 +59,42 @@ def test_index_view_docs_hidden(self): self.assertEqual(response.status_code, 404) self.assertEqual(response.reason_phrase.upper(), "NOT FOUND") + + def test_index_view_with_existent_app_name(self): + """ + Should load the drf docs view with all the endpoints contained in the specified app_name. + NOTE: Views that do **not** inherit from DRF's "APIView" are not included. + """ + # Test 'accounts' app_name + response = self.client.get(reverse('drfdocs-ns', args=['accounts'])) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.context["endpoints"]), 5) + + # Test the login view + self.assertEqual(response.context["endpoints"][0].name_parent, "accounts") + self.assertEqual(response.context["endpoints"][0].allowed_methods, ['POST', 'OPTIONS']) + self.assertEqual(response.context["endpoints"][0].path, "/accounts/login/") + + # Test 'organisations' app_name + response = self.client.get(reverse('drfdocs-ns', args=['organisations'])) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.context["endpoints"]), 4) + + # The view "OrganisationErroredView" (organisations/(?P[\w-]+)/errored/) should contain an error. + self.assertEqual(str(response.context["endpoints"][3].errors), "'test_value'") + + def test_index_search_with_existent_app_name(self): + response = self.client.get("%s?search=reset-password" % reverse('drfdocs-ns', args=['accounts'])) + + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.context["endpoints"]), 2) + self.assertEqual(response.context["endpoints"][1].path, "/accounts/reset-password/confirm/") + self.assertEqual(len(response.context["endpoints"][1].fields), 3) + + def test_index_view_with_non_existent_app_name(self): + """ + Should load the drf docs view with no endpoint. + """ + response = self.client.get(reverse('drfdocs-ns', args=['non_existent_app_name'])) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.context["endpoints"]), 0) diff --git a/tests/urls.py b/tests/urls.py index b226620..1efb9f1 100644 --- a/tests/urls.py +++ b/tests/urls.py @@ -27,8 +27,8 @@ url(r'^docs/', include('rest_framework_docs.urls')), # API - url(r'^accounts/', view=include(accounts_urls, namespace='accounts')), - url(r'^organisations/', view=include(organisations_urls, namespace='organisations')), + url(r'^accounts/', view=include(accounts_urls, namespace='accounts', app_name='accounts')), + url(r'^organisations/', view=include(organisations_urls, namespace='organisations', app_name='organisations')), # Endpoints without parents/namespaces url(r'^another-login/$', views.LoginView.as_view(), name="login"), From e2744a0f65fa0ee24e2e60a2de87ade592159a65 Mon Sep 17 00:00:00 2001 From: Maxence Date: Tue, 5 Jan 2016 15:25:40 +0100 Subject: [PATCH 3/5] - Show the "Jump to" dropdown only if there is more than 1 value - Add link to the ''/docs/[filter_name]'' for each group.grouper (name_parent) - Parameter in the 'docs/filter_name' now works with app_name or namespace - WARNING: Modify the urlpatterns for django version >= 1.9 (see deprecated use of app_name : https://docs.djangoproject.com/en/1.9/ref/urls/#include) --- demo/project/organisations/urls.py | 15 ++++-- demo/project/urls.py | 20 ++++++-- rest_framework_docs/api_docs.py | 17 ++++--- .../templates/rest_framework_docs/home.html | 27 +++++----- rest_framework_docs/urls.py | 3 +- rest_framework_docs/views.py | 4 +- tests/tests.py | 50 ++++++++++++++----- tests/urls.py | 26 ++++++++-- 8 files changed, 118 insertions(+), 44 deletions(-) diff --git a/demo/project/organisations/urls.py b/demo/project/organisations/urls.py index 423e04d..ac21289 100644 --- a/demo/project/organisations/urls.py +++ b/demo/project/organisations/urls.py @@ -1,11 +1,20 @@ +import django from django.conf.urls import url from project.organisations import views -urlpatterns = [ - +organisations_urlpatterns = [ url(r'^create/$', view=views.CreateOrganisationView.as_view(), name="create"), - url(r'^(?P[\w-]+)/members/$', view=views.OrganisationMembersView.as_view(), name="members"), url(r'^(?P[\w-]+)/leave/$', view=views.LeaveOrganisationView.as_view(), name="leave") +] +members_urlpatterns = [ + url(r'^(?P[\w-]+)/members/$', view=views.OrganisationMembersView.as_view(), name="members"), ] + +# Django 1.9 Support for the app_name argument is deprecated +# https://docs.djangoproject.com/en/1.9/ref/urls/#include +django_version = django.VERSION +if django.VERSION[:2] >= (1, 9, ): + organisations_urlpatterns = (organisations_urlpatterns, 'organisations_app', ) + members_urlpatterns = (members_urlpatterns, 'organisations_app', ) diff --git a/demo/project/urls.py b/demo/project/urls.py index bf25fbd..8cae347 100644 --- a/demo/project/urls.py +++ b/demo/project/urls.py @@ -13,15 +13,29 @@ 1. Add an import: from blog import urls as blog_urls 2. Add a URL to urlpatterns: url(r'^blog/', include(blog_urls)) """ +import django from django.conf.urls import include, url from django.contrib import admin +from .organisations.urls import organisations_urlpatterns, members_urlpatterns urlpatterns = [ url(r'^admin/', include(admin.site.urls)), url(r'^docs/', include('rest_framework_docs.urls')), # API - url(r'^accounts/', view=include('project.accounts.urls', namespace='accounts', app_name='accounts')), - url(r'^organisations/', view=include('project.organisations.urls', namespace='organisations', - app_name='organisations')), + url(r'^accounts/', view=include('project.accounts.urls', namespace='accounts')), ] + +# Django 1.9 Support for the app_name argument is deprecated +# https://docs.djangoproject.com/en/1.9/ref/urls/#include +django_version = django.VERSION +if django.VERSION[:2] >= (1, 9, ): + urlpatterns.extend([ + url(r'^organisations/', view=include(organisations_urlpatterns, namespace='organisations')), + url(r'^members/', view=include(members_urlpatterns, namespace='members')), + ]) +else: + urlpatterns.extend([ + url(r'^organisations/', view=include(organisations_urlpatterns, namespace='organisations', app_name='organisations_app')), + url(r'^members/', view=include(members_urlpatterns, namespace='members', app_name='organisations_app')), + ]) diff --git a/rest_framework_docs/api_docs.py b/rest_framework_docs/api_docs.py index f7c342d..2baf533 100644 --- a/rest_framework_docs/api_docs.py +++ b/rest_framework_docs/api_docs.py @@ -7,20 +7,23 @@ class ApiDocumentation(object): - def __init__(self, app_name=None): + def __init__(self, filter_param=None): + """ + :param filter_param: namespace or app_name + """ self.endpoints = [] root_urlconf = __import__(settings.ROOT_URLCONF) if hasattr(root_urlconf, 'urls'): - self.get_all_view_names(root_urlconf.urls.urlpatterns, app_name=app_name) + self.get_all_view_names(root_urlconf.urls.urlpatterns, filter_param=filter_param) else: - self.get_all_view_names(root_urlconf.urlpatterns, app_name=app_name) + self.get_all_view_names(root_urlconf.urlpatterns, filter_param=filter_param) - def get_all_view_names(self, urlpatterns, parent_pattern=None, app_name=None): + def get_all_view_names(self, urlpatterns, parent_pattern=None, filter_param=None): for pattern in urlpatterns: - if isinstance(pattern, RegexURLResolver) and (not app_name or app_name == pattern.app_name): - self.get_all_view_names(urlpatterns=pattern.url_patterns, parent_pattern=pattern) + if isinstance(pattern, RegexURLResolver) and (not filter_param or filter_param in [pattern.app_name, pattern.namespace]): + self.get_all_view_names(urlpatterns=pattern.url_patterns, parent_pattern=pattern, filter_param=filter_param) elif isinstance(pattern, RegexURLPattern) and self._is_drf_view(pattern): - if not app_name or getattr(parent_pattern, 'app_name', None) == app_name: + if not filter_param or (parent_pattern and filter_param in [parent_pattern.app_name, parent_pattern.namespace]): api_endpoint = ApiEndpoint(pattern, parent_pattern) self.endpoints.append(api_endpoint) diff --git a/rest_framework_docs/templates/rest_framework_docs/home.html b/rest_framework_docs/templates/rest_framework_docs/home.html index 76d783d..f22973a 100644 --- a/rest_framework_docs/templates/rest_framework_docs/home.html +++ b/rest_framework_docs/templates/rest_framework_docs/home.html @@ -2,25 +2,28 @@ {% block apps_menu %} {% regroup endpoints by name_parent as endpoints_grouped %} - +{% if endpoints_grouped|length > 1 %} + +{% endif %} {% endblock %} {% block content %} - {% regroup endpoints by name_parent as endpoints_grouped %} - {% if endpoints_grouped %} {% for group in endpoints_grouped %} - -

{{group.grouper}}

+

+ {% if group.grouper %} + {{group.grouper}} + {% endif %} +

diff --git a/rest_framework_docs/urls.py b/rest_framework_docs/urls.py index 3e7d7f5..795bedb 100644 --- a/rest_framework_docs/urls.py +++ b/rest_framework_docs/urls.py @@ -4,5 +4,6 @@ urlpatterns = [ # Url to view the API Docs url(r'^$', DRFDocsView.as_view(), name='drfdocs'), - url(r'^(?P\w+)/$', DRFDocsView.as_view(), name='drfdocs-ns'), + # Url to view the API Docs with a specific namespace or app_name + url(r'^(?P\w+)/$', DRFDocsView.as_view(), name='drfdocs-filter'), ] diff --git a/rest_framework_docs/views.py b/rest_framework_docs/views.py index ef99d91..8de0518 100644 --- a/rest_framework_docs/views.py +++ b/rest_framework_docs/views.py @@ -8,13 +8,13 @@ class DRFDocsView(TemplateView): template_name = "rest_framework_docs/home.html" - def get_context_data(self, app_name=None, **kwargs): + def get_context_data(self, filter_param=None, **kwargs): settings = DRFSettings().settings if settings["HIDDEN"]: raise Http404("Django Rest Framework Docs are hidden. Check your settings.") context = super(DRFDocsView, self).get_context_data(**kwargs) - docs = ApiDocumentation(app_name=app_name) + docs = ApiDocumentation(filter_param=filter_param) endpoints = docs.get_endpoints() query = self.request.GET.get("search", "") diff --git a/tests/tests.py b/tests/tests.py index bd644e9..b84151b 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -60,13 +60,13 @@ def test_index_view_docs_hidden(self): self.assertEqual(response.status_code, 404) self.assertEqual(response.reason_phrase.upper(), "NOT FOUND") - def test_index_view_with_existent_app_name(self): + def test_index_view_with_existent_namespace(self): """ - Should load the drf docs view with all the endpoints contained in the specified app_name. + Should load the drf docs view with all the endpoints contained in the specified namespace. NOTE: Views that do **not** inherit from DRF's "APIView" are not included. """ - # Test 'accounts' app_name - response = self.client.get(reverse('drfdocs-ns', args=['accounts'])) + # Test 'accounts' namespace + response = self.client.get(reverse('drfdocs-filter', args=['accounts'])) self.assertEqual(response.status_code, 200) self.assertEqual(len(response.context["endpoints"]), 5) @@ -75,26 +75,52 @@ def test_index_view_with_existent_app_name(self): self.assertEqual(response.context["endpoints"][0].allowed_methods, ['POST', 'OPTIONS']) self.assertEqual(response.context["endpoints"][0].path, "/accounts/login/") - # Test 'organisations' app_name - response = self.client.get(reverse('drfdocs-ns', args=['organisations'])) + # Test 'organisations' namespace + response = self.client.get(reverse('drfdocs-filter', args=['organisations'])) self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.context["endpoints"]), 4) + self.assertEqual(len(response.context["endpoints"]), 3) # The view "OrganisationErroredView" (organisations/(?P[\w-]+)/errored/) should contain an error. - self.assertEqual(str(response.context["endpoints"][3].errors), "'test_value'") + self.assertEqual(str(response.context["endpoints"][2].errors), "'test_value'") - def test_index_search_with_existent_app_name(self): - response = self.client.get("%s?search=reset-password" % reverse('drfdocs-ns', args=['accounts'])) + # Test 'members' namespace + response = self.client.get(reverse('drfdocs-filter', args=['members'])) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.context["endpoints"]), 1) + + def test_index_search_with_existent_namespace(self): + response = self.client.get("%s?search=reset-password" % reverse('drfdocs-filter', args=['accounts'])) self.assertEqual(response.status_code, 200) self.assertEqual(len(response.context["endpoints"]), 2) self.assertEqual(response.context["endpoints"][1].path, "/accounts/reset-password/confirm/") self.assertEqual(len(response.context["endpoints"][1].fields), 3) - def test_index_view_with_non_existent_app_name(self): + def test_index_view_with_existent_app_name(self): + """ + Should load the drf docs view with all the endpoints contained in the specified app_name. + NOTE: Views that do **not** inherit from DRF's "APIView" are not included. + """ + # Test 'organisations_app' app_name + response = self.client.get(reverse('drfdocs-filter', args=['organisations_app'])) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.context["endpoints"]), 4) + parents_name = [e.name_parent for e in response.context["endpoints"]] + self.assertEquals(parents_name.count('organisations'), 3) + self.assertEquals(parents_name.count('members'), 1) + + def test_index_search_with_existent_app_name(self): + response = self.client.get("%s?search=create" % reverse('drfdocs-filter', args=['organisations_app'])) + + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.context["endpoints"]), 1) + self.assertEqual(response.context["endpoints"][0].path, "/organisations/create/") + self.assertEqual(len(response.context["endpoints"][0].fields), 2) + + def test_index_view_with_non_existent_namespace_or_app_name(self): """ Should load the drf docs view with no endpoint. """ - response = self.client.get(reverse('drfdocs-ns', args=['non_existent_app_name'])) + response = self.client.get(reverse('drfdocs-filter', args=['non_existent_ns_or_app_name'])) self.assertEqual(response.status_code, 200) self.assertEqual(len(response.context["endpoints"]), 0) diff --git a/tests/urls.py b/tests/urls.py index 1efb9f1..24b7282 100644 --- a/tests/urls.py +++ b/tests/urls.py @@ -1,5 +1,6 @@ from __future__ import absolute_import, division, print_function +import django from django.conf.urls import include, url from django.contrib import admin from tests import views @@ -17,19 +18,36 @@ organisations_urls = [ url(r'^create/$', view=views.CreateOrganisationView.as_view(), name="create"), - url(r'^(?P[\w-]+)/members/$', view=views.OrganisationMembersView.as_view(), name="members"), url(r'^(?P[\w-]+)/leave/$', view=views.LeaveOrganisationView.as_view(), name="leave"), url(r'^(?P[\w-]+)/errored/$', view=views.OrganisationErroredView.as_view(), name="errored") ] +members_urls = [ + url(r'^(?P[\w-]+)/members/$', view=views.OrganisationMembersView.as_view(), name="members"), +] + urlpatterns = [ url(r'^admin/', include(admin.site.urls)), url(r'^docs/', include('rest_framework_docs.urls')), # API - url(r'^accounts/', view=include(accounts_urls, namespace='accounts', app_name='accounts')), - url(r'^organisations/', view=include(organisations_urls, namespace='organisations', app_name='organisations')), - + url(r'^accounts/', view=include(accounts_urls, namespace='accounts')), # Endpoints without parents/namespaces url(r'^another-login/$', views.LoginView.as_view(), name="login"), ] + +# Django 1.9 Support for the app_name argument is deprecated +# https://docs.djangoproject.com/en/1.9/ref/urls/#include +django_version = django.VERSION +if django.VERSION[:2] >= (1, 9, ): + organisations_urls = (organisations_urls, 'organisations_app', ) + members_urls = (members_urls, 'organisations_app', ) + urlpatterns.extend([ + url(r'^organisations/', view=include(organisations_urls, namespace='organisations')), + url(r'^members/', view=include(members_urls, namespace='members')), + ]) +else: + urlpatterns.extend([ + url(r'^organisations/', view=include(organisations_urls, namespace='organisations', app_name='organisations_app')), + url(r'^members/', view=include(members_urls, namespace='members', app_name='organisations_app')), + ]) From b774268b97daa7c28d7a5e59a9a8d19ecc306580 Mon Sep 17 00:00:00 2001 From: Maxence Date: Tue, 5 Jan 2016 16:08:08 +0100 Subject: [PATCH 4/5] Add '-' to the url patten --- demo/project/settings.py | 1 - rest_framework_docs/urls.py | 2 +- runtests.py | 1 + tests/tests.py | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) diff --git a/demo/project/settings.py b/demo/project/settings.py index 5e06207..0c33d3d 100644 --- a/demo/project/settings.py +++ b/demo/project/settings.py @@ -43,7 +43,6 @@ 'project.accounts', 'project.organisations', - ) MIDDLEWARE_CLASSES = ( diff --git a/rest_framework_docs/urls.py b/rest_framework_docs/urls.py index 795bedb..5bcfc8b 100644 --- a/rest_framework_docs/urls.py +++ b/rest_framework_docs/urls.py @@ -5,5 +5,5 @@ # Url to view the API Docs url(r'^$', DRFDocsView.as_view(), name='drfdocs'), # Url to view the API Docs with a specific namespace or app_name - url(r'^(?P\w+)/$', DRFDocsView.as_view(), name='drfdocs-filter'), + url(r'^(?P[\w-]+)/$', DRFDocsView.as_view(), name='drfdocs-filter'), ] diff --git a/runtests.py b/runtests.py index c388477..4b06497 100644 --- a/runtests.py +++ b/runtests.py @@ -53,6 +53,7 @@ def run_tests_coverage(): cov.report() cov.html_report(directory='covhtml') + exit_on_failure(flake8_main(FLAKE8_ARGS)) exit_on_failure(run_tests_eslint()) exit_on_failure(run_tests_coverage()) diff --git a/tests/tests.py b/tests/tests.py index b84151b..7b63b34 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -121,6 +121,6 @@ def test_index_view_with_non_existent_namespace_or_app_name(self): """ Should load the drf docs view with no endpoint. """ - response = self.client.get(reverse('drfdocs-filter', args=['non_existent_ns_or_app_name'])) + response = self.client.get(reverse('drfdocs-filter', args=['non-existent-ns-or-app-name'])) self.assertEqual(response.status_code, 200) self.assertEqual(len(response.context["endpoints"]), 0) From e866a414b4fd1e2bb3eb6157fc0a15e3336cf9ae Mon Sep 17 00:00:00 2001 From: Maxence Date: Tue, 5 Jan 2016 17:26:40 +0100 Subject: [PATCH 5/5] Use the endpoint's namespace into the group url --- rest_framework_docs/api_docs.py | 2 +- rest_framework_docs/api_endpoint.py | 6 +++++- .../templates/rest_framework_docs/home.html | 12 ++++++------ 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/rest_framework_docs/api_docs.py b/rest_framework_docs/api_docs.py index 2baf533..3673228 100644 --- a/rest_framework_docs/api_docs.py +++ b/rest_framework_docs/api_docs.py @@ -32,4 +32,4 @@ def _is_drf_view(self, pattern): return hasattr(pattern.callback, 'cls') and issubclass(pattern.callback.cls, APIView) def get_endpoints(self): - return sorted(self.endpoints, key=attrgetter('name_parent')) + return sorted(self.endpoints, key=attrgetter('name')) diff --git a/rest_framework_docs/api_endpoint.py b/rest_framework_docs/api_endpoint.py index 07a7737..42af2b3 100644 --- a/rest_framework_docs/api_endpoint.py +++ b/rest_framework_docs/api_endpoint.py @@ -13,10 +13,14 @@ def __init__(self, pattern, parent_pattern=None): if parent_pattern: self.name_parent = parent_pattern.namespace or parent_pattern.app_name or \ simplify_regex(parent_pattern.regex.pattern).replace('/', '-') + self.name = self.name_parent if hasattr(pattern.callback, 'cls') and issubclass(pattern.callback.cls, ModelViewSet): - self.name_parent = '%s (REST)' % self.name_parent + self.name = '%s (REST)' % self.name_parent else: self.name_parent = '' + self.name = '' + # self.labels = (self.name_parent, self.name, slugify(self.name)) + self.labels = dict(parent=self.name_parent, name=self.name) self.path = self.__get_path__(parent_pattern) self.allowed_methods = self.__get_allowed_methods__() self.errors = None diff --git a/rest_framework_docs/templates/rest_framework_docs/home.html b/rest_framework_docs/templates/rest_framework_docs/home.html index f22973a..e63d4ac 100644 --- a/rest_framework_docs/templates/rest_framework_docs/home.html +++ b/rest_framework_docs/templates/rest_framework_docs/home.html @@ -1,13 +1,13 @@ {% extends "rest_framework_docs/docs.html" %} {% block apps_menu %} -{% regroup endpoints by name_parent as endpoints_grouped %} +{% regroup endpoints by labels as endpoints_grouped %} {% if endpoints_grouped|length > 1 %} @@ -16,12 +16,12 @@ {% block content %} - {% regroup endpoints by name_parent as endpoints_grouped %} + {% regroup endpoints by labels as endpoints_grouped %} {% if endpoints_grouped %} {% for group in endpoints_grouped %} -

- {% if group.grouper %} - {{group.grouper}} +

+ {% if group.grouper.parent %} + {{ group.grouper.name }} {% endif %}