Skip to content

Commit 110ddd9

Browse files
authored
Merge pull request #2 from PerfectPitchTech/master_bug_fixes
Bug fixes from PR on original fork.
2 parents 0ebc886 + 2315399 commit 110ddd9

File tree

13 files changed

+265
-43
lines changed

13 files changed

+265
-43
lines changed

Diff for: README.md

+3
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ You can find detailed information about the package's settings at [the docs](htt
6161

6262
REST_FRAMEWORK_DOCS = {
6363
'HIDE_DOCS': True # Default: False
64+
'MODULE_ROUTERS': {},
65+
'DEFAULT_MODULE_ROUTER': 'router',
66+
'DEFAULT_ROUTER': None,
6467
}
6568

6669

Diff for: demo/project/accounts/urls.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,25 @@
1-
from django.conf.urls import url
1+
from django.conf.urls import url, include
2+
from rest_framework import routers
23
from project.accounts import views
34

45

6+
router = routers.DefaultRouter()
7+
router.register(
8+
r'user_viewset',
9+
views.UserModelViewSet,
10+
)
11+
12+
513
urlpatterns = [
614
url(r'^test/$', views.TestView.as_view(), name="test-view"),
715

816
url(r'^login/$', views.LoginView.as_view(), name="login"),
917
url(r'^register/$', views.UserRegistrationView.as_view(), name="register"),
18+
url(r'^register/$', views.UserRegistrationView.as_view(), name="register"),
1019
url(r'^reset-password/$', view=views.PasswordResetView.as_view(), name="reset-password"),
1120
url(r'^reset-password/confirm/$', views.PasswordResetConfirmView.as_view(), name="reset-password-confirm"),
1221

1322
url(r'^user/profile/$', views.UserProfileView.as_view(), name="profile"),
1423

24+
url(r'^viewset_test/', include(router.urls), name="user_viewset"),
1525
]

Diff for: demo/project/accounts/views.py

+6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from rest_framework.permissions import AllowAny
77
from rest_framework.response import Response
88
from rest_framework.views import APIView
9+
from rest_framework.viewsets import ModelViewSet
910
from project.accounts.models import User
1011
from project.accounts.serializers import (
1112
UserRegistrationSerializer, UserProfileSerializer, ResetPasswordSerializer
@@ -81,3 +82,8 @@ def post(self, request, *args, **kwargs):
8182
if not serializer.is_valid():
8283
return Response({'errors': serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
8384
return Response({"msg": "Password updated successfully."}, status=status.HTTP_200_OK)
85+
86+
87+
class UserModelViewSet(ModelViewSet):
88+
queryset = User.objects.all()
89+
serializer_class = UserProfileSerializer

Diff for: demo/project/organisations/urls.py

+11-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
1-
from django.conf.urls import url
1+
from django.conf.urls import url, include
2+
from rest_framework import routers
23
from project.organisations import views
34

45

6+
router = routers.DefaultRouter()
7+
router.register(
8+
r'organisation_viewset',
9+
views.OrganisationViewSet,
10+
)
11+
12+
513
urlpatterns = [
614

715
url(r'^create/$', view=views.CreateOrganisationView.as_view(), name="create"),
816
url(r'^(?P<slug>[\w-]+)/$', view=views.RetrieveOrganisationView.as_view(), name="organisation"),
917
url(r'^(?P<slug>[\w-]+)/members/$', view=views.OrganisationMembersView.as_view(), name="members"),
10-
url(r'^(?P<slug>[\w-]+)/leave/$', view=views.LeaveOrganisationView.as_view(), name="leave")
18+
url(r'^(?P<slug>[\w-]+)/leave/$', view=views.LeaveOrganisationView.as_view(), name="leave"),
1119

20+
url(r'^', include(router.urls), name="organisation_viewset"),
1221
]

Diff for: demo/project/organisations/views.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
from rest_framework import generics, status
22
from rest_framework.response import Response
3+
from rest_framework.viewsets import ModelViewSet
34
from project.organisations.models import Organisation, Membership
45
from project.organisations.serializers import (
5-
CreateOrganisationSerializer, OrganisationMembersSerializer, RetrieveOrganisationSerializer
6+
CreateOrganisationSerializer, OrganisationMembersSerializer,
7+
RetrieveOrganisationSerializer, OrganisationDetailSerializer
68
)
79

810

@@ -34,3 +36,8 @@ def delete(self, request, *args, **kwargs):
3436
instance = self.get_object()
3537
self.perform_destroy(instance)
3638
return Response(status=status.HTTP_204_NO_CONTENT)
39+
40+
41+
class OrganisationViewSet(ModelViewSet):
42+
queryset = Organisation.objects.all()
43+
serializer_class = OrganisationDetailSerializer

Diff for: demo/project/settings.py

+8
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,14 @@
101101
)
102102
}
103103

104+
REST_FRAMEWORK_DOCS = {
105+
'HIDE_DOCS': False,
106+
'MODULE_ROUTERS': {
107+
'project.accounts.urls': 'router',
108+
},
109+
'DEFAULT_MODULE_ROUTER': 'router',
110+
}
111+
104112
# Internationalization
105113
# https://docs.djangoproject.com/en/1.8/topics/i18n/
106114

Diff for: demo/project/urls.py

+4
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,15 @@
1616
from django.conf.urls import include, url
1717
from django.contrib import admin
1818

19+
1920
urlpatterns = [
2021
url(r'^admin/', include(admin.site.urls)),
2122
url(r'^docs/', include('rest_framework_docs.urls')),
2223

2324
# API
2425
url(r'^accounts/', view=include('project.accounts.urls', namespace='accounts')),
26+
27+
2528
url(r'^organisations/', view=include('project.organisations.urls', namespace='organisations')),
29+
2630
]

Diff for: docs/settings.md

+30-5
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@ source_filename: settings
66
To set DRF docs' settings just include the dictionary below in Django's `settings.py` file.
77

88
REST_FRAMEWORK_DOCS = {
9-
'HIDE_DOCS': True
9+
'HIDE_DOCS': True,
10+
'MODULE_ROUTERS': {
11+
'project.accounts.urls': 'accounts_router',
12+
},
13+
'DEFAULT_MODULE_ROUTER': 'router',
14+
'DEFAULT_ROUTER': 'project.urls.default_router',
1015
}
1116

1217

@@ -21,9 +26,29 @@ You can use hidden to prevent your docs from showing up in different environment
2126

2227
Then set the value of the environment variable `HIDE_DRFDOCS` for each environment (ie. Use `.env` files)
2328

29+
##### MODULE_ROUTERS
30+
Use this setting to manually bind url modules to a router instance. The router must be defined in the module, or imported in the module.
31+
For instance, if the router of an app called 'gifts' is 'gifts_router', and the router of another app called 'scuba_diving' is 'scuba_diving_router', the MODULE_ROUTERS setting should look like:
32+
33+
'MODULE_ROUTERS': {
34+
'gifts.urls': 'gift_router',
35+
'scuba_diving.urls': 'scuba_diving_router'
36+
}
37+
38+
If there is no entry for a given module, if this setting is not set, or if it is set to None, the value of the DEFAULT_MODULE_ROUTER setting is used.
39+
40+
##### DEFAULT_MODULE_ROUTER
41+
When set, the value of this setting will be used to find a router for a urls module. If there is no router having the DEFAULT_MODULE_ROUTER name, the setting is ignored and the value of DEFAULT_ROUTER is used.
42+
43+
##### DEFAULT_ROUTER
44+
When defined, this setting must describe a python dotted path leading to the router that should be used when MODULE_ROUTERS and DEFAULT_MODULE_ROUTER are not set.
45+
This parameter is useful when there is only one router for your whole API.
46+
2447
### List of Settings
2548

26-
| Setting | Type | Options | Default |
27-
|---------|---------|-----------------|---------|
28-
|HIDE_DOCS| Boolean | `True`, `False` | `False` |
29-
| | | | |
49+
| Setting | Type | Options | Default |
50+
|---------------------|-----------------------------------------------------------|-----------------|---------|
51+
|HIDE_DOCS | Boolean | `True`, `False` | `False` |
52+
|MODULE_ROUTERS | dict of python dotted paths -> router instance name | | `None` |
53+
|DEFAULT_MODULE_ROUTER| str representing a default router instance name | | `None` |
54+
|DEFAULT_ROUTER | str representing a python dotted path to a router instance| | `None` |

Diff for: rest_framework_docs/api_docs.py

+113-17
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
from importlib import import_module
2+
from types import ModuleType
23
from django.conf import settings
34
from django.core.urlresolvers import RegexURLResolver, RegexURLPattern
45
from django.utils.module_loading import import_string
56
from rest_framework.views import APIView
6-
from rest_framework_docs.api_endpoint import ApiEndpoint
7+
from rest_framework.routers import BaseRouter
8+
from rest_framework_docs.api_endpoint import ApiNode, ApiEndpoint
9+
from rest_framework_docs.settings import DRFSettings
10+
11+
12+
drf_settings = DRFSettings().settings
713

814

915
class ApiDocumentation(object):
1016

1117
def __init__(self, drf_router=None):
1218
self.endpoints = []
1319
self.drf_router = drf_router
20+
1421
try:
1522
root_urlconf = import_string(settings.ROOT_URLCONF)
1623
except ImportError:
@@ -21,26 +28,115 @@ def __init__(self, drf_router=None):
2128
else:
2229
self.get_all_view_names(root_urlconf.urlpatterns)
2330

24-
def get_all_view_names(self, urlpatterns, parent_pattern=None):
31+
def get_all_view_names(self, urlpatterns, parent_api_node=None):
2532
for pattern in urlpatterns:
2633
if isinstance(pattern, RegexURLResolver):
27-
parent_pattern = None if pattern._regex == "^" else pattern
28-
self.get_all_view_names(urlpatterns=pattern.url_patterns, parent_pattern=parent_pattern)
29-
elif isinstance(pattern, RegexURLPattern) and self._is_drf_view(pattern) and not self._is_format_endpoint(pattern):
30-
api_endpoint = ApiEndpoint(pattern, parent_pattern, self.drf_router)
34+
# Try to get router from settings, if no router is found,
35+
# Use the instance drf_router property.
36+
router = get_router(pattern)
37+
if router is None:
38+
parent_router = None
39+
if parent_api_node is not None:
40+
parent_router = parent_api_node.drf_router
41+
if parent_router is not None:
42+
router = parent_router
43+
else:
44+
router = self.drf_router
45+
if pattern._regex == "^":
46+
parent = parent_api_node
47+
else:
48+
parent = ApiNode(
49+
pattern,
50+
parent_node=parent_api_node,
51+
drf_router=router
52+
)
53+
self.get_all_view_names(urlpatterns=pattern.url_patterns, parent_api_node=parent)
54+
elif isinstance(pattern, RegexURLPattern) and _is_drf_view(pattern) and not _is_format_endpoint(pattern):
55+
router = self.drf_router
56+
if parent_api_node is not None:
57+
if parent_api_node.drf_router is not None:
58+
router = parent_api_node.drf_router
59+
api_endpoint = ApiEndpoint(pattern, parent_api_node, router)
3160
self.endpoints.append(api_endpoint)
3261

33-
def _is_drf_view(self, pattern):
34-
"""
35-
Should check whether a pattern inherits from DRF's APIView
36-
"""
37-
return hasattr(pattern.callback, 'cls') and issubclass(pattern.callback.cls, APIView)
62+
def get_endpoints(self):
63+
return self.endpoints
3864

39-
def _is_format_endpoint(self, pattern):
40-
"""
41-
Exclude endpoints with a "format" parameter
65+
66+
def _is_drf_view(pattern):
67+
"""
68+
Should check whether a pattern inherits from DRF's APIView
69+
"""
70+
return hasattr(pattern.callback, 'cls') and issubclass(pattern.callback.cls,
71+
APIView)
72+
73+
74+
def _is_format_endpoint(pattern):
75+
"""
76+
Exclude endpoints with a "format" parameter
77+
"""
78+
return '?P<format>' in pattern._regex
79+
80+
81+
def get_router(pattern):
82+
urlconf = pattern.urlconf_name
83+
router = None
84+
if isinstance(urlconf, ModuleType):
85+
# First: try MODULE_ROUTERS setting - Don't ignore errors
86+
router = get_module_router(urlconf)
87+
if router is not None:
88+
return router
89+
# Second: try DEFAULT_MODULE_ROUTER setting - Ignore errors
90+
try:
91+
router = get_default_module_router(urlconf)
92+
if router is not None:
93+
return router
94+
except:
95+
pass
96+
# Third: try DEFAULT_ROUTER setting - Don't ignore errors
97+
router = get_default_router()
98+
if router is not None:
99+
return router
100+
return router
101+
102+
103+
def get_module_router(module):
104+
routers = drf_settings['MODULE_ROUTERS']
105+
if routers is None:
106+
return None
107+
if module.__name__ in routers:
108+
router_name = routers[module.__name__]
109+
router = getattr(module, router_name)
110+
assert isinstance(router, BaseRouter), \
111+
"""
112+
drfdocs 'ROUTERS' setting does not correspond to
113+
a router instance for module {}.
114+
""".format(module.__name__)
115+
return router
116+
return None
117+
118+
119+
def get_default_module_router(module):
120+
default_module_router = drf_settings['DEFAULT_MODULE_ROUTER']
121+
if default_module_router is None:
122+
return None
123+
router = getattr(module, default_module_router)
124+
assert isinstance(router, BaseRouter), \
42125
"""
43-
return '?P<format>' in pattern._regex
126+
drfdocs 'DEFAULT_MODULE_ROUTER' setting does not correspond to
127+
a router instance for module {}.
128+
""".format(module.__name__)
129+
return router
44130

45-
def get_endpoints(self):
46-
return self.endpoints
131+
132+
def get_default_router():
133+
default_router_path = drf_settings['DEFAULT_ROUTER']
134+
if default_router_path is None:
135+
return None
136+
router = import_string(default_router_path)
137+
assert isinstance(router, BaseRouter), \
138+
"""
139+
drfdocs 'DEFAULT_ROUTER_NAME' setting does not correspond to
140+
a router instance {}.
141+
""".format(router.__name__)
142+
return router

0 commit comments

Comments
 (0)