Skip to content

Commit e2744a0

Browse files
author
Maxence
committed
- 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)
1 parent deb9446 commit e2744a0

File tree

8 files changed

+118
-44
lines changed

8 files changed

+118
-44
lines changed

demo/project/organisations/urls.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
1+
import django
12
from django.conf.urls import url
23
from project.organisations import views
34

45

5-
urlpatterns = [
6-
6+
organisations_urlpatterns = [
77
url(r'^create/$', view=views.CreateOrganisationView.as_view(), name="create"),
8-
url(r'^(?P<slug>[\w-]+)/members/$', view=views.OrganisationMembersView.as_view(), name="members"),
98
url(r'^(?P<slug>[\w-]+)/leave/$', view=views.LeaveOrganisationView.as_view(), name="leave")
9+
]
1010

11+
members_urlpatterns = [
12+
url(r'^(?P<slug>[\w-]+)/members/$', view=views.OrganisationMembersView.as_view(), name="members"),
1113
]
14+
15+
# Django 1.9 Support for the app_name argument is deprecated
16+
# https://docs.djangoproject.com/en/1.9/ref/urls/#include
17+
django_version = django.VERSION
18+
if django.VERSION[:2] >= (1, 9, ):
19+
organisations_urlpatterns = (organisations_urlpatterns, 'organisations_app', )
20+
members_urlpatterns = (members_urlpatterns, 'organisations_app', )

demo/project/urls.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,29 @@
1313
1. Add an import: from blog import urls as blog_urls
1414
2. Add a URL to urlpatterns: url(r'^blog/', include(blog_urls))
1515
"""
16+
import django
1617
from django.conf.urls import include, url
1718
from django.contrib import admin
19+
from .organisations.urls import organisations_urlpatterns, members_urlpatterns
1820

1921
urlpatterns = [
2022
url(r'^admin/', include(admin.site.urls)),
2123
url(r'^docs/', include('rest_framework_docs.urls')),
2224

2325
# API
24-
url(r'^accounts/', view=include('project.accounts.urls', namespace='accounts', app_name='accounts')),
25-
url(r'^organisations/', view=include('project.organisations.urls', namespace='organisations',
26-
app_name='organisations')),
26+
url(r'^accounts/', view=include('project.accounts.urls', namespace='accounts')),
2727
]
28+
29+
# Django 1.9 Support for the app_name argument is deprecated
30+
# https://docs.djangoproject.com/en/1.9/ref/urls/#include
31+
django_version = django.VERSION
32+
if django.VERSION[:2] >= (1, 9, ):
33+
urlpatterns.extend([
34+
url(r'^organisations/', view=include(organisations_urlpatterns, namespace='organisations')),
35+
url(r'^members/', view=include(members_urlpatterns, namespace='members')),
36+
])
37+
else:
38+
urlpatterns.extend([
39+
url(r'^organisations/', view=include(organisations_urlpatterns, namespace='organisations', app_name='organisations_app')),
40+
url(r'^members/', view=include(members_urlpatterns, namespace='members', app_name='organisations_app')),
41+
])

rest_framework_docs/api_docs.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,23 @@
77

88
class ApiDocumentation(object):
99

10-
def __init__(self, app_name=None):
10+
def __init__(self, filter_param=None):
11+
"""
12+
:param filter_param: namespace or app_name
13+
"""
1114
self.endpoints = []
1215
root_urlconf = __import__(settings.ROOT_URLCONF)
1316
if hasattr(root_urlconf, 'urls'):
14-
self.get_all_view_names(root_urlconf.urls.urlpatterns, app_name=app_name)
17+
self.get_all_view_names(root_urlconf.urls.urlpatterns, filter_param=filter_param)
1518
else:
16-
self.get_all_view_names(root_urlconf.urlpatterns, app_name=app_name)
19+
self.get_all_view_names(root_urlconf.urlpatterns, filter_param=filter_param)
1720

18-
def get_all_view_names(self, urlpatterns, parent_pattern=None, app_name=None):
21+
def get_all_view_names(self, urlpatterns, parent_pattern=None, filter_param=None):
1922
for pattern in urlpatterns:
20-
if isinstance(pattern, RegexURLResolver) and (not app_name or app_name == pattern.app_name):
21-
self.get_all_view_names(urlpatterns=pattern.url_patterns, parent_pattern=pattern)
23+
if isinstance(pattern, RegexURLResolver) and (not filter_param or filter_param in [pattern.app_name, pattern.namespace]):
24+
self.get_all_view_names(urlpatterns=pattern.url_patterns, parent_pattern=pattern, filter_param=filter_param)
2225
elif isinstance(pattern, RegexURLPattern) and self._is_drf_view(pattern):
23-
if not app_name or getattr(parent_pattern, 'app_name', None) == app_name:
26+
if not filter_param or (parent_pattern and filter_param in [parent_pattern.app_name, parent_pattern.namespace]):
2427
api_endpoint = ApiEndpoint(pattern, parent_pattern)
2528
self.endpoints.append(api_endpoint)
2629

rest_framework_docs/templates/rest_framework_docs/home.html

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,28 @@
22

33
{% block apps_menu %}
44
{% regroup endpoints by name_parent as endpoints_grouped %}
5-
<li class="dropdown">
6-
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Jump To <span class="caret"></span></a>
7-
<ul class="dropdown-menu">
8-
{% for group in endpoints_grouped %}
9-
<li><a href="#{{ group.grouper|lower }}-group">{{ group.grouper }}</a></li>
10-
{% endfor %}
11-
</ul>
12-
</li>
5+
{% if endpoints_grouped|length > 1 %}
6+
<li class="dropdown">
7+
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Jump To <span class="caret"></span></a>
8+
<ul class="dropdown-menu">
9+
{% for group in endpoints_grouped %}
10+
<li><a href="#{{ group.grouper|lower }}-group">{{ group.grouper }}</a></li>
11+
{% endfor %}
12+
</ul>
13+
</li>
14+
{% endif %}
1315
{% endblock %}
1416

1517

1618
{% block content %}
17-
1819
{% regroup endpoints by name_parent as endpoints_grouped %}
19-
2020
{% if endpoints_grouped %}
2121
{% for group in endpoints_grouped %}
22-
23-
<h1 id="{{ group.grouper|lower }}-group">{{group.grouper}}</h1>
22+
<h1 id="{{ group.grouper|lower }}-group">
23+
{% if group.grouper %}
24+
<a href="{% url 'drfdocs-filter' group.grouper %}">{{group.grouper}}</a>
25+
{% endif %}
26+
</h1>
2427

2528
<div class="panel-group" role="tablist">
2629

rest_framework_docs/urls.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@
44
urlpatterns = [
55
# Url to view the API Docs
66
url(r'^$', DRFDocsView.as_view(), name='drfdocs'),
7-
url(r'^(?P<app_name>\w+)/$', DRFDocsView.as_view(), name='drfdocs-ns'),
7+
# Url to view the API Docs with a specific namespace or app_name
8+
url(r'^(?P<filter_param>\w+)/$', DRFDocsView.as_view(), name='drfdocs-filter'),
89
]

rest_framework_docs/views.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ class DRFDocsView(TemplateView):
88

99
template_name = "rest_framework_docs/home.html"
1010

11-
def get_context_data(self, app_name=None, **kwargs):
11+
def get_context_data(self, filter_param=None, **kwargs):
1212
settings = DRFSettings().settings
1313
if settings["HIDDEN"]:
1414
raise Http404("Django Rest Framework Docs are hidden. Check your settings.")
1515

1616
context = super(DRFDocsView, self).get_context_data(**kwargs)
17-
docs = ApiDocumentation(app_name=app_name)
17+
docs = ApiDocumentation(filter_param=filter_param)
1818
endpoints = docs.get_endpoints()
1919

2020
query = self.request.GET.get("search", "")

tests/tests.py

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,13 @@ def test_index_view_docs_hidden(self):
6060
self.assertEqual(response.status_code, 404)
6161
self.assertEqual(response.reason_phrase.upper(), "NOT FOUND")
6262

63-
def test_index_view_with_existent_app_name(self):
63+
def test_index_view_with_existent_namespace(self):
6464
"""
65-
Should load the drf docs view with all the endpoints contained in the specified app_name.
65+
Should load the drf docs view with all the endpoints contained in the specified namespace.
6666
NOTE: Views that do **not** inherit from DRF's "APIView" are not included.
6767
"""
68-
# Test 'accounts' app_name
69-
response = self.client.get(reverse('drfdocs-ns', args=['accounts']))
68+
# Test 'accounts' namespace
69+
response = self.client.get(reverse('drfdocs-filter', args=['accounts']))
7070
self.assertEqual(response.status_code, 200)
7171
self.assertEqual(len(response.context["endpoints"]), 5)
7272

@@ -75,26 +75,52 @@ def test_index_view_with_existent_app_name(self):
7575
self.assertEqual(response.context["endpoints"][0].allowed_methods, ['POST', 'OPTIONS'])
7676
self.assertEqual(response.context["endpoints"][0].path, "/accounts/login/")
7777

78-
# Test 'organisations' app_name
79-
response = self.client.get(reverse('drfdocs-ns', args=['organisations']))
78+
# Test 'organisations' namespace
79+
response = self.client.get(reverse('drfdocs-filter', args=['organisations']))
8080
self.assertEqual(response.status_code, 200)
81-
self.assertEqual(len(response.context["endpoints"]), 4)
81+
self.assertEqual(len(response.context["endpoints"]), 3)
8282

8383
# The view "OrganisationErroredView" (organisations/(?P<slug>[\w-]+)/errored/) should contain an error.
84-
self.assertEqual(str(response.context["endpoints"][3].errors), "'test_value'")
84+
self.assertEqual(str(response.context["endpoints"][2].errors), "'test_value'")
8585

86-
def test_index_search_with_existent_app_name(self):
87-
response = self.client.get("%s?search=reset-password" % reverse('drfdocs-ns', args=['accounts']))
86+
# Test 'members' namespace
87+
response = self.client.get(reverse('drfdocs-filter', args=['members']))
88+
self.assertEqual(response.status_code, 200)
89+
self.assertEqual(len(response.context["endpoints"]), 1)
90+
91+
def test_index_search_with_existent_namespace(self):
92+
response = self.client.get("%s?search=reset-password" % reverse('drfdocs-filter', args=['accounts']))
8893

8994
self.assertEqual(response.status_code, 200)
9095
self.assertEqual(len(response.context["endpoints"]), 2)
9196
self.assertEqual(response.context["endpoints"][1].path, "/accounts/reset-password/confirm/")
9297
self.assertEqual(len(response.context["endpoints"][1].fields), 3)
9398

94-
def test_index_view_with_non_existent_app_name(self):
99+
def test_index_view_with_existent_app_name(self):
100+
"""
101+
Should load the drf docs view with all the endpoints contained in the specified app_name.
102+
NOTE: Views that do **not** inherit from DRF's "APIView" are not included.
103+
"""
104+
# Test 'organisations_app' app_name
105+
response = self.client.get(reverse('drfdocs-filter', args=['organisations_app']))
106+
self.assertEqual(response.status_code, 200)
107+
self.assertEqual(len(response.context["endpoints"]), 4)
108+
parents_name = [e.name_parent for e in response.context["endpoints"]]
109+
self.assertEquals(parents_name.count('organisations'), 3)
110+
self.assertEquals(parents_name.count('members'), 1)
111+
112+
def test_index_search_with_existent_app_name(self):
113+
response = self.client.get("%s?search=create" % reverse('drfdocs-filter', args=['organisations_app']))
114+
115+
self.assertEqual(response.status_code, 200)
116+
self.assertEqual(len(response.context["endpoints"]), 1)
117+
self.assertEqual(response.context["endpoints"][0].path, "/organisations/create/")
118+
self.assertEqual(len(response.context["endpoints"][0].fields), 2)
119+
120+
def test_index_view_with_non_existent_namespace_or_app_name(self):
95121
"""
96122
Should load the drf docs view with no endpoint.
97123
"""
98-
response = self.client.get(reverse('drfdocs-ns', args=['non_existent_app_name']))
124+
response = self.client.get(reverse('drfdocs-filter', args=['non_existent_ns_or_app_name']))
99125
self.assertEqual(response.status_code, 200)
100126
self.assertEqual(len(response.context["endpoints"]), 0)

tests/urls.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import absolute_import, division, print_function
22

3+
import django
34
from django.conf.urls import include, url
45
from django.contrib import admin
56
from tests import views
@@ -17,19 +18,36 @@
1718

1819
organisations_urls = [
1920
url(r'^create/$', view=views.CreateOrganisationView.as_view(), name="create"),
20-
url(r'^(?P<slug>[\w-]+)/members/$', view=views.OrganisationMembersView.as_view(), name="members"),
2121
url(r'^(?P<slug>[\w-]+)/leave/$', view=views.LeaveOrganisationView.as_view(), name="leave"),
2222
url(r'^(?P<slug>[\w-]+)/errored/$', view=views.OrganisationErroredView.as_view(), name="errored")
2323
]
2424

25+
members_urls = [
26+
url(r'^(?P<slug>[\w-]+)/members/$', view=views.OrganisationMembersView.as_view(), name="members"),
27+
]
28+
2529
urlpatterns = [
2630
url(r'^admin/', include(admin.site.urls)),
2731
url(r'^docs/', include('rest_framework_docs.urls')),
2832

2933
# API
30-
url(r'^accounts/', view=include(accounts_urls, namespace='accounts', app_name='accounts')),
31-
url(r'^organisations/', view=include(organisations_urls, namespace='organisations', app_name='organisations')),
32-
34+
url(r'^accounts/', view=include(accounts_urls, namespace='accounts')),
3335
# Endpoints without parents/namespaces
3436
url(r'^another-login/$', views.LoginView.as_view(), name="login"),
3537
]
38+
39+
# Django 1.9 Support for the app_name argument is deprecated
40+
# https://docs.djangoproject.com/en/1.9/ref/urls/#include
41+
django_version = django.VERSION
42+
if django.VERSION[:2] >= (1, 9, ):
43+
organisations_urls = (organisations_urls, 'organisations_app', )
44+
members_urls = (members_urls, 'organisations_app', )
45+
urlpatterns.extend([
46+
url(r'^organisations/', view=include(organisations_urls, namespace='organisations')),
47+
url(r'^members/', view=include(members_urls, namespace='members')),
48+
])
49+
else:
50+
urlpatterns.extend([
51+
url(r'^organisations/', view=include(organisations_urls, namespace='organisations', app_name='organisations_app')),
52+
url(r'^members/', view=include(members_urls, namespace='members', app_name='organisations_app')),
53+
])

0 commit comments

Comments
 (0)