From aa28f4fa19af03728bea2320da1ec392e4c7b5ec Mon Sep 17 00:00:00 2001 From: Jasper Little Date: Sat, 3 Feb 2018 13:52:04 +1100 Subject: [PATCH 01/10] Implemented support for Django 2.0 --- demo/project/accounts/urls.py | 1 + demo/project/organisations/models.py | 4 ++-- demo/project/organisations/urls.py | 1 + demo/project/urls.py | 2 +- rest_framework_docs/api_docs.py | 21 ++++++++++++++++----- rest_framework_docs/api_endpoint.py | 8 +++++--- tests/models.py | 4 ++-- tests/settings.py | 17 +++++++++++++++++ tests/tests.py | 7 ++++++- tests/urls.py | 6 +++--- 10 files changed, 54 insertions(+), 17 deletions(-) diff --git a/demo/project/accounts/urls.py b/demo/project/accounts/urls.py index 1486675..d396169 100644 --- a/demo/project/accounts/urls.py +++ b/demo/project/accounts/urls.py @@ -1,6 +1,7 @@ from django.conf.urls import url from project.accounts import views +app_name = "accounts" urlpatterns = [ url(r'^test/$', views.TestView.as_view(), name="test-view"), diff --git a/demo/project/organisations/models.py b/demo/project/organisations/models.py index fcb0cba..6d3f7c4 100644 --- a/demo/project/organisations/models.py +++ b/demo/project/organisations/models.py @@ -29,7 +29,7 @@ class Meta: id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) joined = models.DateTimeField(auto_now_add=True) - organisation = models.ForeignKey(Organisation) - user = models.ForeignKey(User) + organisation = models.ForeignKey(Organisation, on_delete=models.CASCADE) + user = models.ForeignKey(User, on_delete=models.CASCADE) role = models.CharField(choices=MEMBER_ROLES, max_length=20, default="USER") is_owner = models.BooleanField(default=False) diff --git a/demo/project/organisations/urls.py b/demo/project/organisations/urls.py index 4d0311e..d9e9ee9 100644 --- a/demo/project/organisations/urls.py +++ b/demo/project/organisations/urls.py @@ -1,6 +1,7 @@ from django.conf.urls import url from project.organisations import views +app_name = "organisations" urlpatterns = [ diff --git a/demo/project/urls.py b/demo/project/urls.py index d8e049f..48f9711 100644 --- a/demo/project/urls.py +++ b/demo/project/urls.py @@ -17,7 +17,7 @@ from django.contrib import admin urlpatterns = [ - url(r'^admin/', include(admin.site.urls)), + url(r'^admin/', admin.site.urls), url(r'^docs/', include('rest_framework_docs.urls')), # API diff --git a/rest_framework_docs/api_docs.py b/rest_framework_docs/api_docs.py index d14fae4..00703e7 100644 --- a/rest_framework_docs/api_docs.py +++ b/rest_framework_docs/api_docs.py @@ -1,9 +1,20 @@ from importlib import import_module from django.conf import settings -from django.core.urlresolvers import RegexURLResolver, RegexURLPattern +try: + from django.urls import ( + URLPattern, + URLResolver, + ) +except ImportError: + # Will be removed in Django 2.0 + from django.urls import ( + RegexURLPattern as URLPattern, + RegexURLResolver as URLResolver, + ) from django.utils.module_loading import import_string from rest_framework.views import APIView from rest_framework_docs.api_endpoint import ApiEndpoint +from rest_framework.compat import get_regex_pattern class ApiDocumentation(object): @@ -23,10 +34,10 @@ def __init__(self, drf_router=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 + if isinstance(pattern, URLResolver): + regex = '' if get_regex_pattern(pattern) == "^" else get_regex_pattern(pattern) 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): + elif isinstance(pattern, URLPattern) 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) @@ -40,7 +51,7 @@ def _is_format_endpoint(self, pattern): """ Exclude endpoints with a "format" parameter """ - return '?P' in pattern._regex + return '?P' in get_regex_pattern(pattern) def get_endpoints(self): return self.endpoints diff --git a/rest_framework_docs/api_endpoint.py b/rest_framework_docs/api_endpoint.py index 953f9a0..80f1c66 100644 --- a/rest_framework_docs/api_endpoint.py +++ b/rest_framework_docs/api_endpoint.py @@ -6,6 +6,7 @@ from rest_framework.viewsets import ModelViewSet from rest_framework.serializers import BaseSerializer +from rest_framework.compat import get_regex_pattern VIEWSET_METHODS = { 'List': ['get', 'post'], @@ -35,9 +36,10 @@ def __init__(self, pattern, parent_regex=None, drf_router=None): self.permissions = self.__get_permissions_class__() def __get_path__(self, parent_regex): + regex = get_regex_pattern(self.pattern) if parent_regex: - return "/{0}{1}".format(self.name_parent, simplify_regex(self.pattern.regex.pattern)) - return simplify_regex(self.pattern.regex.pattern) + return "/{0}{1}".format(self.name_parent, simplify_regex(regex)) + return simplify_regex(regex) def is_method_allowed(self, callback_cls, method_name): has_attr = hasattr(callback_cls, method_name) @@ -69,7 +71,7 @@ def __get_allowed_methods__(self): lookup=lookup, trailing_slash=self.drf_router.trailing_slash ) - if self.pattern.regex.pattern == regex: + if get_regex_pattern(self.pattern) == regex: funcs, viewset_methods = zip( *[(mapping[m], m.upper()) for m in self.callback.cls.http_method_names diff --git a/tests/models.py b/tests/models.py index 9489337..68d00df 100644 --- a/tests/models.py +++ b/tests/models.py @@ -43,7 +43,7 @@ class Meta: id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) joined = models.DateTimeField(auto_now_add=True) - organisation = models.ForeignKey(Organisation) - user = models.ForeignKey(User) + organisation = models.ForeignKey(Organisation, on_delete=models.CASCADE) + user = models.ForeignKey(User, on_delete=models.CASCADE) role = models.CharField(choices=MEMBER_ROLES, max_length=20, default="USER") is_owner = models.BooleanField(default=False) diff --git a/tests/settings.py b/tests/settings.py index d80e9f2..c3fa306 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -39,3 +39,20 @@ # https://docs.djangoproject.com/en/1.8/howto/static-files/ STATIC_URL = '/static/' + + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] diff --git a/tests/tests.py b/tests/tests.py index f94736c..ff12130 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -1,4 +1,9 @@ -from django.core.urlresolvers import reverse +try: + from django.urls import reverse +except ImportError: + # Will be removed in Django 2.0 + from django.core.urlresolvers import reverse + from django.test import TestCase, override_settings from rest_framework_docs.settings import DRFSettings diff --git a/tests/urls.py b/tests/urls.py index abdf71b..a11fd23 100644 --- a/tests/urls.py +++ b/tests/urls.py @@ -30,12 +30,12 @@ router.register('organisation-model-viewsets', views.TestModelViewSet, base_name='organisation') urlpatterns = [ - url(r'^admin/', include(admin.site.urls)), + url(r'^admin/', admin.site.urls), url(r'^docs/', DRFDocsView.as_view(drf_router=router), name='drfdocs'), # 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, "accounts"), namespace='accounts')), + url(r'^organisations/', view=include((organisations_urls, "organisations"), namespace='organisations')), url(r'^', include(router.urls)), # Endpoints without parents/namespaces From e5656c1b92edc9704333b08c827aa44f90a330a4 Mon Sep 17 00:00:00 2001 From: Jasper Little Date: Sat, 3 Feb 2018 15:08:21 +1100 Subject: [PATCH 02/10] Support older versions of drf --- rest_framework_docs/api_docs.py | 3 ++- rest_framework_docs/api_endpoint.py | 3 ++- rest_framework_docs/compat.py | 14 ++++++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 rest_framework_docs/compat.py diff --git a/rest_framework_docs/api_docs.py b/rest_framework_docs/api_docs.py index 00703e7..4084c83 100644 --- a/rest_framework_docs/api_docs.py +++ b/rest_framework_docs/api_docs.py @@ -14,7 +14,8 @@ from django.utils.module_loading import import_string from rest_framework.views import APIView from rest_framework_docs.api_endpoint import ApiEndpoint -from rest_framework.compat import get_regex_pattern + +from .compat import get_regex_pattern class ApiDocumentation(object): diff --git a/rest_framework_docs/api_endpoint.py b/rest_framework_docs/api_endpoint.py index 80f1c66..c691850 100644 --- a/rest_framework_docs/api_endpoint.py +++ b/rest_framework_docs/api_endpoint.py @@ -6,7 +6,8 @@ from rest_framework.viewsets import ModelViewSet from rest_framework.serializers import BaseSerializer -from rest_framework.compat import get_regex_pattern + +from .compat import get_regex_pattern VIEWSET_METHODS = { 'List': ['get', 'post'], diff --git a/rest_framework_docs/compat.py b/rest_framework_docs/compat.py new file mode 100644 index 0000000..4e65c32 --- /dev/null +++ b/rest_framework_docs/compat.py @@ -0,0 +1,14 @@ + + +# This is from the similarly named compat.py file of django-rest-framework 3.7 +def get_regex_pattern(urlpattern): + """ + Get the raw regex out of the urlpattern's RegexPattern or RoutePattern. + This is always a regular expression, unlike get_original_route above. + """ + if hasattr(urlpattern, 'pattern'): + # Django 2.0 + return urlpattern.pattern.regex.pattern + else: + # Django < 2.0 + return urlpattern.regex.pattern From 1750d95716c7326432287f25d0feb8e4db84ca44 Mon Sep 17 00:00:00 2001 From: Jasper Little Date: Sat, 3 Feb 2018 15:24:55 +1100 Subject: [PATCH 03/10] Improved dependency testing with tox --- .gitignore | 2 ++ tests/tests.py | 12 ++++++------ tox.ini | 19 +++++++++++++++++++ 3 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 tox.ini diff --git a/.gitignore b/.gitignore index bde1b9d..6a693eb 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ rest_framework_docs/static/rest_framework_docs/js/dist.min.js rest_framework_docs/static/node_modules/ rest_framework_docs/static/rest_framework_docs/js/dist.min.js.map + +.tox/ \ No newline at end of file diff --git a/tests/tests.py b/tests/tests.py index ff12130..4d21db2 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -1,8 +1,8 @@ try: - from django.urls import reverse + from django.urls import reverse_lazy except ImportError: # Will be removed in Django 2.0 - from django.core.urlresolvers import reverse + from django.core.urlresolvers import reverse_lazy from django.test import TestCase, override_settings from rest_framework_docs.settings import DRFSettings @@ -29,7 +29,7 @@ def test_index_view_with_endpoints(self): 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')) + response = self.client.get(reverse_lazy('drfdocs')) self.assertEqual(response.status_code, 200) self.assertEqual(len(response.context["endpoints"]), 15) @@ -55,7 +55,7 @@ def test_index_view_with_endpoints(self): 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")) + response = self.client.get("%s?search=reset-password" % reverse_lazy("drfdocs")) self.assertEqual(response.status_code, 200) self.assertEqual(len(response.context["endpoints"]), 2) @@ -68,13 +68,13 @@ def test_index_view_docs_hidden(self): Should prevent the docs from loading the "HIDE_DOCS" is set to "True" or undefined under settings """ - response = self.client.get(reverse('drfdocs')) + response = self.client.get(reverse_lazy('drfdocs')) self.assertEqual(response.status_code, 404) self.assertEqual(response.reason_phrase.upper(), "NOT FOUND") def test_model_viewset(self): - response = self.client.get(reverse('drfdocs')) + response = self.client.get(reverse_lazy('drfdocs')) self.assertEqual(response.status_code, 200) diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..5855753 --- /dev/null +++ b/tox.ini @@ -0,0 +1,19 @@ +[tox] +envlist = + py27-django1.9-drf3.4 + py{27,34,36}-django{1.10,1.11}-drf{3.4,3.5,3.6,3.7} + py{34,36}-django2.0-drf3.7 + +[testenv] +deps = + py{27,34,36}: coverage == 4.4.2 + flake8 == 2.5.1 + django1.10: Django>=1.10,<1.11 + django1.11: Django>=1.11,<1.12 + django2.0: Django>=2.0,<2.1 + drf3.4: djangorestframework>=3.4,<3.5 + drf3.5: djangorestframework>=3.5,<3.6 + drf3.6: djangorestframework>=3.6,<3.7 + drf3.7: djangorestframework>=3.7,<3.8 +commands = + python runtests.py \ No newline at end of file From 807af1fc8c78c1fdfb17ec9cb950f432a1408f1a Mon Sep 17 00:00:00 2001 From: Jasper Little Date: Sat, 3 Feb 2018 15:26:14 +1100 Subject: [PATCH 04/10] Updated setup and README to reflect supported versions --- README.md | 6 +++--- setup.py | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3bfde01..7dd0425 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ Document Web APIs made with Django Rest Framework. [View Demo](http://demo.drfdo ### Supports - - Python (2.7, 3.3, 3.4, 3.5) - - Django (1.8, 1.9) - - Django Rest Framework (3+) + - Python (2.7, 3.4, 3.5, 3.6) + - Django (1.10, 1.11, 2.0) + - Django Rest Framework (3.4+) ### Documentation - Table of contents diff --git a/setup.py b/setup.py index b85340a..3e51d91 100644 --- a/setup.py +++ b/setup.py @@ -23,6 +23,7 @@ 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5' + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6' ], ) From 4a35ce541ce1b7edf7606724735c4dde20e47c75 Mon Sep 17 00:00:00 2001 From: Jasper Little Date: Sat, 3 Feb 2018 15:27:31 +1100 Subject: [PATCH 05/10] Updated requirements --- requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index f449bbb..bb1439e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -Django==1.8.7 -djangorestframework==3.3.2 -coverage==4.0.3 +Django==2.0.1 +djangorestframework==3.7.7 +coverage==4.4.2 flake8==2.5.1 mkdocs==0.15.3 From 0375ec461f3c31ae486c1d7a1b852525e90d0997 Mon Sep 17 00:00:00 2001 From: Jasper Little Date: Sat, 3 Feb 2018 15:32:30 +1100 Subject: [PATCH 06/10] Remove mistake from tox.ini --- tox.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/tox.ini b/tox.ini index 5855753..e3bd399 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,5 @@ [tox] envlist = - py27-django1.9-drf3.4 py{27,34,36}-django{1.10,1.11}-drf{3.4,3.5,3.6,3.7} py{34,36}-django2.0-drf3.7 From 02b562176326b6aa3b8c110e02352bc3100288f5 Mon Sep 17 00:00:00 2001 From: Jasper Little Date: Sat, 3 Feb 2018 15:41:46 +1100 Subject: [PATCH 07/10] Moved further details to compat --- rest_framework_docs/api_docs.py | 18 ++++-------------- rest_framework_docs/compat.py | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/rest_framework_docs/api_docs.py b/rest_framework_docs/api_docs.py index 4084c83..9291134 100644 --- a/rest_framework_docs/api_docs.py +++ b/rest_framework_docs/api_docs.py @@ -1,21 +1,11 @@ from importlib import import_module from django.conf import settings -try: - from django.urls import ( - URLPattern, - URLResolver, - ) -except ImportError: - # Will be removed in Django 2.0 - from django.urls import ( - RegexURLPattern as URLPattern, - RegexURLResolver as URLResolver, - ) + from django.utils.module_loading import import_string from rest_framework.views import APIView from rest_framework_docs.api_endpoint import ApiEndpoint -from .compat import get_regex_pattern +from .compat import get_regex_pattern, is_url_pattern, is_url_resolver class ApiDocumentation(object): @@ -35,10 +25,10 @@ def __init__(self, drf_router=None): def get_all_view_names(self, urlpatterns, parent_regex=''): for pattern in urlpatterns: - if isinstance(pattern, URLResolver): + if is_url_resolver(pattern): regex = '' if get_regex_pattern(pattern) == "^" else get_regex_pattern(pattern) self.get_all_view_names(urlpatterns=pattern.url_patterns, parent_regex=parent_regex + regex) - elif isinstance(pattern, URLPattern) and self._is_drf_view(pattern) and not self._is_format_endpoint(pattern): + elif is_url_pattern(pattern) 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) diff --git a/rest_framework_docs/compat.py b/rest_framework_docs/compat.py index 4e65c32..1d0ab96 100644 --- a/rest_framework_docs/compat.py +++ b/rest_framework_docs/compat.py @@ -1,3 +1,14 @@ +try: + from django.urls import ( + URLPattern, + URLResolver, + ) +except ImportError: + # Will be removed in Django 2.0 + from django.urls import ( + RegexURLPattern as URLPattern, + RegexURLResolver as URLResolver, + ) # This is from the similarly named compat.py file of django-rest-framework 3.7 @@ -12,3 +23,11 @@ def get_regex_pattern(urlpattern): else: # Django < 2.0 return urlpattern.regex.pattern + + +def is_url_resolver(instance): + return isinstance(instance, URLResolver) + + +def is_url_pattern(instance): + return isinstance(instance, URLPattern) From 075dc6d9188d3b56969f25a0038e50acaa44c871 Mon Sep 17 00:00:00 2001 From: Jasper Little Date: Sat, 3 Feb 2018 16:00:34 +1100 Subject: [PATCH 08/10] Updated travis config --- .travis.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8a61d83..c62bf09 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,10 +6,12 @@ python: - "2.7" - "3.4" - "3.5" + - "3.6" env: - - DJANGO_VERSION=1.8 - - DJANGO_VERSION=1.9 + - DJANGO_VERSION=1.10 + - DJANGO_VERSION=1.11 + - DJANGO_VERSION=2.0 cache: - pip From 72bc5463cd33afc7d45e1bd6f65d6fbe99652c41 Mon Sep 17 00:00:00 2001 From: Jasper Little Date: Sat, 3 Feb 2018 16:16:03 +1100 Subject: [PATCH 09/10] Further travis fixes --- .travis.yml | 5 +++++ requirements.txt | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c62bf09..949a576 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,11 @@ env: - DJANGO_VERSION=1.11 - DJANGO_VERSION=2.0 +matrix: + exclude: + - env: DJANGO_VERSION=2.0 + python: "2.7" + cache: - pip - directories: diff --git a/requirements.txt b/requirements.txt index bb1439e..98cbdba 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -Django==2.0.1 +Django==1.11 djangorestframework==3.7.7 coverage==4.4.2 flake8==2.5.1 From 2c1a8a98fc31f7ecab4048ecedbe3ceb5da92843 Mon Sep 17 00:00:00 2001 From: Ismael de Esteban Date: Mon, 26 Feb 2018 07:25:29 +0100 Subject: [PATCH 10/10] Update package version --- rest_framework_docs/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework_docs/__init__.py b/rest_framework_docs/__init__.py index ad3cf1d..b56a50b 100644 --- a/rest_framework_docs/__init__.py +++ b/rest_framework_docs/__init__.py @@ -1 +1 @@ -__version__ = '0.0.11' +__version__ = '0.0.12b'