From 225c47beede0bb0dff5a64cca0e14477537491e3 Mon Sep 17 00:00:00 2001 From: Maxence Date: Thu, 21 Jan 2016 09:55:37 +0100 Subject: [PATCH 01/12] Support Model View Sets --- rest_framework_docs/api_endpoint.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/rest_framework_docs/api_endpoint.py b/rest_framework_docs/api_endpoint.py index f37155c..d991d2b 100644 --- a/rest_framework_docs/api_endpoint.py +++ b/rest_framework_docs/api_endpoint.py @@ -2,6 +2,12 @@ import inspect from django.contrib.admindocs.views import simplify_regex from django.utils.encoding import force_str +from rest_framework.viewsets import ModelViewSet + +VIEWSET_METHODS = { + 'List': ['get', 'post'], + 'Instance': ['get', 'put', 'patch', 'delete'], +} class ApiEndpoint(object): @@ -25,8 +31,14 @@ def __get_path__(self, parent_pattern): return "/{0}{1}".format(self.name_parent, simplify_regex(self.pattern.regex.pattern)) return simplify_regex(self.pattern.regex.pattern) + def is_method_allowed(self, method_name, callback_cls): + return hasattr(callback_cls, method_name) or ( + issubclass(callback_cls, ModelViewSet) and method_name in VIEWSET_METHODS.get(self.callback.suffix, []) + ) + def __get_allowed_methods__(self): - return [force_str(m).upper() for m in self.callback.cls.http_method_names if hasattr(self.callback.cls, m)] + callback_cls = self.callback.cls + return sorted([force_str(name).upper() for name in callback_cls.http_method_names if self.is_method_allowed(name, callback_cls)]) def __get_docstring__(self): return inspect.getdoc(self.callback) From f44ccbae43a9b1614b0c61f164175ae2fa6748eb Mon Sep 17 00:00:00 2001 From: Brobin Date: Fri, 8 Apr 2016 14:50:26 -0500 Subject: [PATCH 02/12] fix broken test by sorting both lists --- tests/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests.py b/tests/tests.py index afb58d0..c1978ec 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -31,7 +31,7 @@ def test_index_view_with_endpoints(self): # 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(sorted(response.context["endpoints"][0].allowed_methods), sorted(['OPTIONS', 'POST'])) 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) From b776596b1a6121492203b898f804dff8569db583 Mon Sep 17 00:00:00 2001 From: Jens Timmerman Date: Fri, 1 Jul 2016 14:38:41 +0200 Subject: [PATCH 03/12] Create setup.cfg when updating rpm's from django-rest-framework-docs to drfdocs the rpm mail fail to update because of conflicting files, this obsolets should tell yum/dnf to first remove the old rpm --- setup.cfg | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 setup.cfg diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..f3b0a13 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[bdist_rpm] +obsoletes = django-rest-framework-docs From ff56db4af52f8a09eb91ac2e3f90362f3f5e16cb Mon Sep 17 00:00:00 2001 From: Rachel Hutchison Date: Fri, 29 Jul 2016 11:39:44 -0700 Subject: [PATCH 04/12] Add optional markdown --- .../templates/rest_framework_docs/home.html | 3 ++- rest_framework_docs/templatetags/__init__.py | 0 rest_framework_docs/templatetags/drfdocs_filters.py | 12 ++++++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 rest_framework_docs/templatetags/__init__.py create mode 100644 rest_framework_docs/templatetags/drfdocs_filters.py diff --git a/rest_framework_docs/templates/rest_framework_docs/home.html b/rest_framework_docs/templates/rest_framework_docs/home.html index 235a6ee..e13e5a5 100644 --- a/rest_framework_docs/templates/rest_framework_docs/home.html +++ b/rest_framework_docs/templates/rest_framework_docs/home.html @@ -1,4 +1,5 @@ {% extends "rest_framework_docs/docs.html" %} +{% load drfdocs_filters %} {% block apps_menu %} {% regroup endpoints by name_parent as endpoints_grouped %} @@ -56,7 +57,7 @@

{% if endpoint.docstring %} -

{{ endpoint.docstring }}

+

{{ endpoint.docstring|markdown }}

{% endif %} {% if endpoint.errors %} diff --git a/rest_framework_docs/templatetags/__init__.py b/rest_framework_docs/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rest_framework_docs/templatetags/drfdocs_filters.py b/rest_framework_docs/templatetags/drfdocs_filters.py new file mode 100644 index 0000000..4d1642a --- /dev/null +++ b/rest_framework_docs/templatetags/drfdocs_filters.py @@ -0,0 +1,12 @@ +from django import template +from django.template.defaultfilters import stringfilter +from rest_framework.utils.formatting import markup_description + + +register = template.Library() + + +@register.filter(name='markdown') +@stringfilter +def markdown(value): + return markup_description(value) From 3e54ed0ea8ccdce2ae8826bc52354da92b6be3b5 Mon Sep 17 00:00:00 2001 From: Marcos Dione Date: Thu, 3 Nov 2016 17:19:35 +0100 Subject: [PATCH 05/12] First step to fix nested `urls.py` files. This patch accumulates parent_regexps with the current one, but changes the ABI, so there's a second patch to come to fix `ApiEndpoint` --- rest_framework_docs/api_docs.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rest_framework_docs/api_docs.py b/rest_framework_docs/api_docs.py index d22dd4c..868224d 100644 --- a/rest_framework_docs/api_docs.py +++ b/rest_framework_docs/api_docs.py @@ -21,13 +21,13 @@ def __init__(self, drf_router=None): else: self.get_all_view_names(root_urlconf.urlpatterns) - def get_all_view_names(self, urlpatterns, parent_pattern=None): + def get_all_view_names(self, urlpatterns, parent_regex=None): for pattern in urlpatterns: if isinstance(pattern, RegexURLResolver): - parent_pattern = None if pattern._regex == "^" else pattern - self.get_all_view_names(urlpatterns=pattern.url_patterns, parent_pattern=parent_pattern) + regex = '' if pattern._regex == "^" else pattern._regex + self.get_all_view_names(urlpatterns=pattern.url_patterns, parent_regex=parent_regex+regex) elif isinstance(pattern, RegexURLPattern) and self._is_drf_view(pattern) and not self._is_format_endpoint(pattern): - api_endpoint = ApiEndpoint(pattern, parent_pattern, self.drf_router) + api_endpoint = ApiEndpoint(pattern, parent_regex, self.drf_router) self.endpoints.append(api_endpoint) def _is_drf_view(self, pattern): From a2d8710721b22ff9e587f673bde8e0b7cf2a5416 Mon Sep 17 00:00:00 2001 From: Marcos Dione Date: Thu, 3 Nov 2016 17:23:08 +0100 Subject: [PATCH 06/12] Second step to fix nested `urls.py` files. This patch fixes the ABI changed in `ApiDocumentation`. --- rest_framework_docs/api_endpoint.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rest_framework_docs/api_endpoint.py b/rest_framework_docs/api_endpoint.py index 89a33f8..318eaa1 100644 --- a/rest_framework_docs/api_endpoint.py +++ b/rest_framework_docs/api_endpoint.py @@ -7,14 +7,14 @@ class ApiEndpoint(object): - def __init__(self, pattern, parent_pattern=None, drf_router=None): + def __init__(self, pattern, parent_regex=None, drf_router=None): self.drf_router = drf_router self.pattern = pattern self.callback = pattern.callback # self.name = pattern.name self.docstring = self.__get_docstring__() - self.name_parent = simplify_regex(parent_pattern.regex.pattern).strip('/') if parent_pattern else None - self.path = self.__get_path__(parent_pattern) + self.name_parent = simplify_regex(parent_regex).strip('/') if parent_pattern else None + self.path = self.__get_path__(parent_regex) self.allowed_methods = self.__get_allowed_methods__() # self.view_name = pattern.callback.__name__ self.errors = None @@ -26,8 +26,8 @@ def __init__(self, pattern, parent_pattern=None, drf_router=None): self.permissions = self.__get_permissions_class__() - def __get_path__(self, parent_pattern): - if parent_pattern: + def __get_path__(self, parent_regex): + if parent_regex: return "/{0}{1}".format(self.name_parent, simplify_regex(self.pattern.regex.pattern)) return simplify_regex(self.pattern.regex.pattern) From e69fd13a4c61d170fea4fbe96ab15c493b1e77b5 Mon Sep 17 00:00:00 2001 From: Marcos Dione Date: Thu, 3 Nov 2016 17:39:45 +0100 Subject: [PATCH 07/12] Fix flake8 complaint. ... --- rest_framework_docs/api_docs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework_docs/api_docs.py b/rest_framework_docs/api_docs.py index 868224d..487eb97 100644 --- a/rest_framework_docs/api_docs.py +++ b/rest_framework_docs/api_docs.py @@ -25,7 +25,7 @@ def get_all_view_names(self, urlpatterns, parent_regex=None): for pattern in urlpatterns: if isinstance(pattern, RegexURLResolver): regex = '' if pattern._regex == "^" else pattern._regex - self.get_all_view_names(urlpatterns=pattern.url_patterns, parent_regex=parent_regex+regex) + self.get_all_view_names(urlpatterns=pattern.url_patterns, parent_regex=parent_regex + regex) elif isinstance(pattern, RegexURLPattern) and self._is_drf_view(pattern) and not self._is_format_endpoint(pattern): api_endpoint = ApiEndpoint(pattern, parent_regex, self.drf_router) self.endpoints.append(api_endpoint) From 5e238b8eab0405aabdcc4d188f0a92f2c4ddd983 Mon Sep 17 00:00:00 2001 From: Marcos Dione Date: Thu, 3 Nov 2016 17:44:54 +0100 Subject: [PATCH 08/12] Typo/missed replace. I shuld do local dev instead of edit via GitHub's page... --- rest_framework_docs/api_endpoint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework_docs/api_endpoint.py b/rest_framework_docs/api_endpoint.py index 318eaa1..f598e34 100644 --- a/rest_framework_docs/api_endpoint.py +++ b/rest_framework_docs/api_endpoint.py @@ -13,7 +13,7 @@ def __init__(self, pattern, parent_regex=None, drf_router=None): self.callback = pattern.callback # self.name = pattern.name self.docstring = self.__get_docstring__() - self.name_parent = simplify_regex(parent_regex).strip('/') if parent_pattern else None + self.name_parent = simplify_regex(parent_regex).strip('/') if parent_regex else None self.path = self.__get_path__(parent_regex) self.allowed_methods = self.__get_allowed_methods__() # self.view_name = pattern.callback.__name__ From fccdce9579d8a4c32a7ab7a2f8b96bd4af5c481f Mon Sep 17 00:00:00 2001 From: Marcos Dione Date: Thu, 3 Nov 2016 17:49:21 +0100 Subject: [PATCH 09/12] Another bug 3 and counting... --- rest_framework_docs/api_docs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework_docs/api_docs.py b/rest_framework_docs/api_docs.py index 487eb97..d14fae4 100644 --- a/rest_framework_docs/api_docs.py +++ b/rest_framework_docs/api_docs.py @@ -21,7 +21,7 @@ def __init__(self, drf_router=None): else: self.get_all_view_names(root_urlconf.urlpatterns) - def get_all_view_names(self, urlpatterns, parent_regex=None): + def get_all_view_names(self, urlpatterns, parent_regex=''): for pattern in urlpatterns: if isinstance(pattern, RegexURLResolver): regex = '' if pattern._regex == "^" else pattern._regex From 5174f68bee65cfe8790f8d14b8423ab9603ea8a8 Mon Sep 17 00:00:00 2001 From: Marcos Dione Date: Wed, 25 Jan 2017 18:48:49 +0100 Subject: [PATCH 10/12] Make sure methods returned by allowed_methods can be in any order and count. We might need to make sure that the returned value is a set. --- tests/tests.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/tests.py b/tests/tests.py index c49499a..c394147 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -31,7 +31,7 @@ def test_index_view_with_endpoints(self): # Test the login view self.assertEqual(response.context["endpoints"][0].name_parent, "accounts") - self.assertEqual(sorted(response.context["endpoints"][0].allowed_methods), sorted(['OPTIONS', 'POST'])) + self.assertEqual(set(response.context["endpoints"][0].allowed_methods), set(['OPTIONS', 'POST'])) 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) @@ -39,7 +39,7 @@ def test_index_view_with_endpoints(self): 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(set(response.context["endpoints"][1].allowed_methods), set(['POST', 'OPTIONS'])) self.assertEqual(response.context["endpoints"][1].path, "/accounts/login2/") self.assertEqual(response.context["endpoints"][1].docstring, "A view that allows users to login providing their username and password. Without serializer_class") self.assertEqual(len(response.context["endpoints"][1].fields), 2) @@ -77,7 +77,7 @@ def test_model_viewset(self): self.assertEqual(response.context['endpoints'][6].fields[2]['to_many_relation'], True) self.assertEqual(response.context["endpoints"][11].path, '/organisation-model-viewsets/') self.assertEqual(response.context["endpoints"][12].path, '/organisation-model-viewsets//') - self.assertEqual(response.context["endpoints"][11].allowed_methods, ['GET', 'POST', 'OPTIONS']) - self.assertEqual(response.context["endpoints"][12].allowed_methods, ['GET', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']) - self.assertEqual(response.context["endpoints"][13].allowed_methods, ['POST', 'OPTIONS']) + self.assertEqual(set(response.context["endpoints"][11].allowed_methods), set(['GET', 'POST', 'OPTIONS'])) + self.assertEqual(set(response.context["endpoints"][12].allowed_methods), set(['GET', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'])) + self.assertEqual(set(response.context["endpoints"][13].allowed_methods), set(['POST', 'OPTIONS'])) self.assertEqual(response.context["endpoints"][13].docstring, 'This is a test.') From 84b8f1f45fb12281c53f1c35dbd86775b31c04a3 Mon Sep 17 00:00:00 2001 From: Marcos Dione Date: Wed, 25 Jan 2017 18:49:04 +0100 Subject: [PATCH 11/12] Typo. --- tests/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests.py b/tests/tests.py index c394147..f94736c 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -21,7 +21,7 @@ def test_settings_module(self): def test_index_view_with_endpoints(self): """ - Should load the drf focs view with all the endpoints. + Should load the drf docs view with all the endpoints. NOTE: Views that do **not** inherit from DRF's "APIView" are not included. """ response = self.client.get(reverse('drfdocs')) From 2ca9599118aa13d8846a3ed399b34a7ead9cc285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joon=20Hwan=20=EA=B9=80=EC=A4=80=ED=99=98?= Date: Tue, 7 Feb 2017 22:54:41 +0900 Subject: [PATCH 12/12] Fix typo in documentation 0.0.0.0:/8000 -> 0.0.0.0:8000 --- docs/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation.md b/docs/installation.md index 1e7805c..f0a4f68 100755 --- a/docs/installation.md +++ b/docs/installation.md @@ -22,4 +22,4 @@ Finally include the `rest_framework_docs` urls in your `urls.py`: url(r'^docs/', include('rest_framework_docs.urls')), ] -You can now visit [http://0.0.0.0:/8000/docs/](http://0.0.0.0:8000/docs/) to view your Web API's docs. +You can now visit [http://0.0.0.0:8000/docs/](http://0.0.0.0:8000/docs/) to view your Web API's docs.