Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: manosim/django-rest-framework-docs
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: master
Choose a base ref
...
head repository: PerfectPitchTech/django-rest-framework-docs
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref
Can’t automatically merge. Don’t worry, you can still create the pull request.
  • 17 commits
  • 16 files changed
  • 4 contributors

Commits on Aug 24, 2016

  1. Enhance api_endpoint model to track the tree structure of an api, and…

    … add router settings for a better integration of viewsets.
    dimitri-justeau committed Aug 24, 2016
    Copy the full SHA
    a4267e7 View commit details
  2. Copy the full SHA
    4470670 View commit details
  3. Copy the full SHA
    f33aa98 View commit details
  4. Copy the full SHA
    ab5e821 View commit details
  5. Update README.md

    dimitri-justeau committed Aug 24, 2016
    Copy the full SHA
    77dc489 View commit details

Commits on Aug 25, 2016

  1. Copy the full SHA
    e90aa70 View commit details
  2. Copy the full SHA
    7a56175 View commit details
  3. Copy the full SHA
    6bd92d0 View commit details

Commits on Sep 19, 2016

  1. Adding in MarkDown support. Original contributed here #127

    Jared Mackey committed Sep 19, 2016
    Copy the full SHA
    82ad711 View commit details
  2. Merge pull request #1 from PerfectPitchTech/master_md

    Adding in MarkDown support. Original contributed here https://github.…
    jared-mackey authored Sep 19, 2016
    Copy the full SHA
    53bc450 View commit details
  3. updated gitignore for pycharm

    Jared Mackey committed Sep 19, 2016
    Copy the full SHA
    0ebc886 View commit details
  4. Update docs

    manosim authored and Jared Mackey committed Sep 19, 2016
    Copy the full SHA
    19f4993 View commit details
  5. Minor Changes

    manosim authored and Jared Mackey committed Sep 19, 2016
    Copy the full SHA
    0f8f4b9 View commit details
  6. Adding in MarkDown support. Original contributed here #127

    Jared Mackey committed Sep 19, 2016
    Copy the full SHA
    6b403a8 View commit details
  7. updated gitignore for pycharm

    Jared Mackey committed Sep 19, 2016
    Copy the full SHA
    5c91f22 View commit details
  8. Allow json header. Originally contributed at 089f754

    Jared Mackey committed Sep 19, 2016
    Copy the full SHA
    2315399 View commit details
  9. Merge pull request #2 from PerfectPitchTech/master_bug_fixes

    Bug fixes from PR on original fork.
    jared-mackey authored Sep 19, 2016
    Copy the full SHA
    110ddd9 View commit details
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -16,3 +16,4 @@ 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
.idea
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -61,6 +61,9 @@ You can find detailed information about the package's settings at [the docs](htt

REST_FRAMEWORK_DOCS = {
'HIDE_DOCS': True # Default: False
'MODULE_ROUTERS': {},
'DEFAULT_MODULE_ROUTER': 'router',
'DEFAULT_ROUTER': None,
}


12 changes: 11 additions & 1 deletion demo/project/accounts/urls.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
from django.conf.urls import url
from django.conf.urls import url, include
from rest_framework import routers
from project.accounts import views


router = routers.DefaultRouter()
router.register(
r'user_viewset',
views.UserModelViewSet,
)


urlpatterns = [
url(r'^test/$', views.TestView.as_view(), name="test-view"),

url(r'^login/$', views.LoginView.as_view(), name="login"),
url(r'^register/$', views.UserRegistrationView.as_view(), name="register"),
url(r'^register/$', views.UserRegistrationView.as_view(), name="register"),
url(r'^reset-password/$', view=views.PasswordResetView.as_view(), name="reset-password"),
url(r'^reset-password/confirm/$', views.PasswordResetConfirmView.as_view(), name="reset-password-confirm"),

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

url(r'^viewset_test/', include(router.urls), name="user_viewset"),
]
6 changes: 6 additions & 0 deletions demo/project/accounts/views.py
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.viewsets import ModelViewSet
from project.accounts.models import User
from project.accounts.serializers import (
UserRegistrationSerializer, UserProfileSerializer, ResetPasswordSerializer
@@ -81,3 +82,8 @@ def post(self, request, *args, **kwargs):
if not serializer.is_valid():
return Response({'errors': serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
return Response({"msg": "Password updated successfully."}, status=status.HTTP_200_OK)


class UserModelViewSet(ModelViewSet):
queryset = User.objects.all()
serializer_class = UserProfileSerializer
13 changes: 11 additions & 2 deletions demo/project/organisations/urls.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
from django.conf.urls import url
from django.conf.urls import url, include
from rest_framework import routers
from project.organisations import views


router = routers.DefaultRouter()
router.register(
r'organisation_viewset',
views.OrganisationViewSet,
)


urlpatterns = [

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

url(r'^', include(router.urls), name="organisation_viewset"),
]
9 changes: 8 additions & 1 deletion demo/project/organisations/views.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from rest_framework import generics, status
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from project.organisations.models import Organisation, Membership
from project.organisations.serializers import (
CreateOrganisationSerializer, OrganisationMembersSerializer, RetrieveOrganisationSerializer
CreateOrganisationSerializer, OrganisationMembersSerializer,
RetrieveOrganisationSerializer, OrganisationDetailSerializer
)


@@ -34,3 +36,8 @@ def delete(self, request, *args, **kwargs):
instance = self.get_object()
self.perform_destroy(instance)
return Response(status=status.HTTP_204_NO_CONTENT)


class OrganisationViewSet(ModelViewSet):
queryset = Organisation.objects.all()
serializer_class = OrganisationDetailSerializer
8 changes: 8 additions & 0 deletions demo/project/settings.py
Original file line number Diff line number Diff line change
@@ -101,6 +101,14 @@
)
}

REST_FRAMEWORK_DOCS = {
'HIDE_DOCS': False,
'MODULE_ROUTERS': {
'project.accounts.urls': 'router',
},
'DEFAULT_MODULE_ROUTER': 'router',
}

# Internationalization
# https://docs.djangoproject.com/en/1.8/topics/i18n/

4 changes: 4 additions & 0 deletions demo/project/urls.py
Original file line number Diff line number Diff line change
@@ -16,11 +16,15 @@
from django.conf.urls import include, url
from django.contrib import admin


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

# API
url(r'^accounts/', view=include('project.accounts.urls', namespace='accounts')),


url(r'^organisations/', view=include('project.organisations.urls', namespace='organisations')),

]
35 changes: 30 additions & 5 deletions docs/settings.md
Original file line number Diff line number Diff line change
@@ -6,7 +6,12 @@ source_filename: settings
To set DRF docs' settings just include the dictionary below in Django's `settings.py` file.

REST_FRAMEWORK_DOCS = {
'HIDE_DOCS': True
'HIDE_DOCS': True,
'MODULE_ROUTERS': {
'project.accounts.urls': 'accounts_router',
},
'DEFAULT_MODULE_ROUTER': 'router',
'DEFAULT_ROUTER': 'project.urls.default_router',
}


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

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

##### MODULE_ROUTERS
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.
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:

'MODULE_ROUTERS': {
'gifts.urls': 'gift_router',
'scuba_diving.urls': 'scuba_diving_router'
}

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.

##### DEFAULT_MODULE_ROUTER
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.

##### DEFAULT_ROUTER
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.
This parameter is useful when there is only one router for your whole API.

### List of Settings

| Setting | Type | Options | Default |
|---------|---------|-----------------|---------|
|HIDE_DOCS| Boolean | `True`, `False` | `False` |
| | | | |
| Setting | Type | Options | Default |
|---------------------|-----------------------------------------------------------|-----------------|---------|
|HIDE_DOCS | Boolean | `True`, `False` | `False` |
|MODULE_ROUTERS | dict of python dotted paths -> router instance name | | `None` |
|DEFAULT_MODULE_ROUTER| str representing a default router instance name | | `None` |
|DEFAULT_ROUTER | str representing a python dotted path to a router instance| | `None` |
130 changes: 113 additions & 17 deletions rest_framework_docs/api_docs.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
from importlib import import_module
from types import ModuleType
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 rest_framework.routers import BaseRouter
from rest_framework_docs.api_endpoint import ApiNode, ApiEndpoint
from rest_framework_docs.settings import DRFSettings


drf_settings = DRFSettings().settings


class ApiDocumentation(object):

def __init__(self, drf_router=None):
self.endpoints = []
self.drf_router = drf_router

try:
root_urlconf = import_string(settings.ROOT_URLCONF)
except ImportError:
@@ -21,26 +28,115 @@ 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_api_node=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)
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)
# Try to get router from settings, if no router is found,
# Use the instance drf_router property.
router = get_router(pattern)
if router is None:
parent_router = None
if parent_api_node is not None:
parent_router = parent_api_node.drf_router
if parent_router is not None:
router = parent_router
else:
router = self.drf_router
if pattern._regex == "^":
parent = parent_api_node
else:
parent = ApiNode(
pattern,
parent_node=parent_api_node,
drf_router=router
)
self.get_all_view_names(urlpatterns=pattern.url_patterns, parent_api_node=parent)
elif isinstance(pattern, RegexURLPattern) and _is_drf_view(pattern) and not _is_format_endpoint(pattern):
router = self.drf_router
if parent_api_node is not None:
if parent_api_node.drf_router is not None:
router = parent_api_node.drf_router
api_endpoint = ApiEndpoint(pattern, parent_api_node, router)
self.endpoints.append(api_endpoint)

def _is_drf_view(self, pattern):
"""
Should check whether a pattern inherits from DRF's APIView
"""
return hasattr(pattern.callback, 'cls') and issubclass(pattern.callback.cls, APIView)
def get_endpoints(self):
return self.endpoints

def _is_format_endpoint(self, pattern):
"""
Exclude endpoints with a "format" parameter

def _is_drf_view(pattern):
"""
Should check whether a pattern inherits from DRF's APIView
"""
return hasattr(pattern.callback, 'cls') and issubclass(pattern.callback.cls,
APIView)


def _is_format_endpoint(pattern):
"""
Exclude endpoints with a "format" parameter
"""
return '?P<format>' in pattern._regex


def get_router(pattern):
urlconf = pattern.urlconf_name
router = None
if isinstance(urlconf, ModuleType):
# First: try MODULE_ROUTERS setting - Don't ignore errors
router = get_module_router(urlconf)
if router is not None:
return router
# Second: try DEFAULT_MODULE_ROUTER setting - Ignore errors
try:
router = get_default_module_router(urlconf)
if router is not None:
return router
except:
pass
# Third: try DEFAULT_ROUTER setting - Don't ignore errors
router = get_default_router()
if router is not None:
return router
return router


def get_module_router(module):
routers = drf_settings['MODULE_ROUTERS']
if routers is None:
return None
if module.__name__ in routers:
router_name = routers[module.__name__]
router = getattr(module, router_name)
assert isinstance(router, BaseRouter), \
"""
drfdocs 'ROUTERS' setting does not correspond to
a router instance for module {}.
""".format(module.__name__)
return router
return None


def get_default_module_router(module):
default_module_router = drf_settings['DEFAULT_MODULE_ROUTER']
if default_module_router is None:
return None
router = getattr(module, default_module_router)
assert isinstance(router, BaseRouter), \
"""
return '?P<format>' in pattern._regex
drfdocs 'DEFAULT_MODULE_ROUTER' setting does not correspond to
a router instance for module {}.
""".format(module.__name__)
return router

def get_endpoints(self):
return self.endpoints

def get_default_router():
default_router_path = drf_settings['DEFAULT_ROUTER']
if default_router_path is None:
return None
router = import_string(default_router_path)
assert isinstance(router, BaseRouter), \
"""
drfdocs 'DEFAULT_ROUTER_NAME' setting does not correspond to
a router instance {}.
""".format(router.__name__)
return router
Loading