Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Django 2.0 #177

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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/
11 changes: 9 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,17 @@ 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

matrix:
exclude:
- env: DJANGO_VERSION=2.0
python: "2.7"

cache:
- pip
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions demo/project/accounts/urls.py
Original file line number Diff line number Diff line change
@@ -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"),
Expand Down
4 changes: 2 additions & 2 deletions demo/project/organisations/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
1 change: 1 addition & 0 deletions demo/project/organisations/urls.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from django.conf.urls import url
from project.organisations import views

app_name = "organisations"

urlpatterns = [

Expand Down
2 changes: 1 addition & 1 deletion demo/project/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Django==1.8.7
djangorestframework==3.3.2
coverage==4.0.3
Django==1.11
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this will cause 1.11.0 to be installed, not any 1.11.*

djangorestframework==3.7.7
coverage==4.4.2
flake8==2.5.1
mkdocs==0.15.3
2 changes: 1 addition & 1 deletion rest_framework_docs/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '0.0.11'
__version__ = '0.0.12b'
12 changes: 7 additions & 5 deletions rest_framework_docs/api_docs.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from importlib import import_module
from django.conf import settings
from django.core.urlresolvers import RegexURLResolver, RegexURLPattern

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, is_url_pattern, is_url_resolver


class ApiDocumentation(object):

Expand All @@ -23,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, RegexURLResolver):
regex = '' if pattern._regex == "^" else pattern._regex
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, RegexURLPattern) 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)

Expand All @@ -40,7 +42,7 @@ def _is_format_endpoint(self, pattern):
"""
Exclude endpoints with a "format" parameter
"""
return '?P<format>' in pattern._regex
return '?P<format>' in get_regex_pattern(pattern)

def get_endpoints(self):
return self.endpoints
9 changes: 6 additions & 3 deletions rest_framework_docs/api_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from rest_framework.viewsets import ModelViewSet
from rest_framework.serializers import BaseSerializer

from .compat import get_regex_pattern

VIEWSET_METHODS = {
'List': ['get', 'post'],
'Instance': ['get', 'put', 'patch', 'delete'],
Expand Down Expand Up @@ -35,9 +37,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)
Expand Down Expand Up @@ -69,7 +72,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
Expand Down
33 changes: 33 additions & 0 deletions rest_framework_docs/compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
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
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


def is_url_resolver(instance):
return isinstance(instance, URLResolver)


def is_url_pattern(instance):
return isinstance(instance, URLPattern)
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
],
)
4 changes: 2 additions & 2 deletions tests/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
17 changes: 17 additions & 0 deletions tests/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
],
},
},
]
15 changes: 10 additions & 5 deletions tests/tests.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
from django.core.urlresolvers import reverse
try:
from django.urls import reverse_lazy
except ImportError:
# Will be removed in Django 2.0
from django.core.urlresolvers import reverse_lazy

from django.test import TestCase, override_settings
from rest_framework_docs.settings import DRFSettings

Expand All @@ -24,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)
Expand All @@ -50,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)
Expand All @@ -63,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)

Expand Down
6 changes: 3 additions & 3 deletions tests/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 18 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[tox]
envlist =
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