If I have a view like:
class MyAPIView(APIView):
def get(self, request, name=None):
return {"hello": name or "world"}
How can I get that included in the generated documentation? Specifically, how can I get it included in the API Root, so it appears when I visit "http://example.com/api/"?
The documentation includes an example of an APIView with description, but doesn't describe the process of actually getting it included in the API browser.
Django Rest Framework makes it easy to use your Django Server as an REST API. REST stands for "representational state transfer" and API stands for application programming interface. Note that with DRF you easily have list and create views as well as authentication.
To make use of generic class-based views, the view classes should import from rest_framework. generics. ListAPIView: It provides a get method handler and is used for read-only endpoints to represent a collection of model instances. ListAPIView extends GenericAPIView and ListModelMixin.
The browsable API feature in the Django REST framework generates HTML output for different resources. It facilitates interaction with RESTful web service through any web browser. To enable this feature, we should specify text/html for the Content-Type key in the request header.
To mix with routers and APIView classes or method based in such a way that the API Root displays both with minimal code views in the APIRoot view I wrote a custom router that extends DefaultRouter and overrides get_urls and get_api_root_view; it looks like as follows :
from rest_framework import routers, views, reverse, response
class HybridRouter(routers.DefaultRouter):
def __init__(self, *args, **kwargs):
super(HybridRouter, self).__init__(*args, **kwargs)
self._api_view_urls = {}
def add_api_view(self, name, url):
self._api_view_urls[name] = url
def remove_api_view(self, name):
del self._api_view_urls[name]
@property
def api_view_urls(self):
ret = {}
ret.update(self._api_view_urls)
return ret
def get_urls(self):
urls = super(HybridRouter, self).get_urls()
for api_view_key in self._api_view_urls.keys():
urls.append(self._api_view_urls[api_view_key])
return urls
def get_api_root_view(self):
# Copy the following block from Default Router
api_root_dict = {}
list_name = self.routes[0].name
for prefix, viewset, basename in self.registry:
api_root_dict[prefix] = list_name.format(basename=basename)
api_view_urls = self._api_view_urls
class APIRoot(views.APIView):
_ignore_model_permissions = True
def get(self, request, format=None):
ret = {}
for key, url_name in api_root_dict.items():
ret[key] = reverse.reverse(url_name, request=request, format=format)
# In addition to what had been added, now add the APIView urls
for api_view_key in api_view_urls.keys():
ret[api_view_key] = reverse.reverse(api_view_urls[api_view_key].name, request=request, format=format)
return response.Response(ret)
return APIRoot.as_view()
Then I use it as -
router = routers.HybridRouter()
router.register(r'abc', views.ABCViewSet)
router.add_api_view("api-view", url(r'^aview/$', views.AView.as_view(), name='aview-name'))
urlpatterns = patterns('',
url(r'^api/', include(router.urls)),
the generated documentation?
Hi David, first up I wouldn't quite describe the browsable API as 'generated documentation'.
If you need static documentation you're best off looking at a third party tool like django-rest-swagger.
The browsable API does mean that the APIs you build will be self-describing, but it's a little different from conventional static documentation tools. The browsable API ensures that all the endpoints you create in your API are able to respond both with machine readable (ie JSON) and human readable (ie HTML) representations. It also ensures you can fully interact directly through the browser - any URL that you would normally interact with using a programmatic client will also be capable of responding with a browser friendly view onto the API.
How can I get that included.
Just add a docstring to the view and it'll be included in the browsable API representation of whichever URLs you route to that view.
By default you can use markdown notation to include HTML markup in the description but you can also customise that behaviour, for example if you'd rather use rst.
Specifically, how can I get it included in the API Root.
You'll just want to explicitly add the URL to into the response returned by whatever view you have wired up to /api/
. For example...
from rest_framework import renderers
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.reverse import reverse
class APIRoot(APIView):
def get(self, request):
# Assuming we have views named 'foo-view' and 'bar-view'
# in our project's URLconf.
return Response({
'foo': reverse('foo-view', request=request),
'bar': reverse('bar-view', request=request)
})
I have optimized HybridRouter for my use case and removed some code. Check it out:
class HybridRouter(routers.DefaultRouter):
def __init__(self, *args, **kwargs):
super(HybridRouter, self).__init__(*args, **kwargs)
self.view_urls = []
def add_url(self, view):
self.view_urls.append(view)
def get_urls(self):
return super(HybridRouter, self).get_urls() + self.view_urls
def get_api_root_view(self):
original_view = super(HybridRouter, self).get_api_root_view()
def view(request, *args, **kwargs):
resp = original_view(request, *args, **kwargs)
namespace = request.resolver_match.namespace
for view_url in self.view_urls:
name = view_url.name
url_name = name
if namespace:
url_name = namespace + ':' + url_name
resp.data[name] = reverse(url_name,
args=args,
kwargs=kwargs,
request=request,
format=kwargs.get('format', None))
return resp
return view
And usage:
router = routers.HybridRouter(trailing_slash=False)
router.add_url(url(r'^me', v1.me.view, name='me'))
router.add_url(url(r'^status', v1.status.view, name='status'))
urlpatterns = router.urls
Or:
router = routers.HybridRouter(trailing_slash=False)
router.view_urls = [
url(r'^me', v1.me.view, name='me'),
url(r'^status', v1.status.view, name='status'),
]
urlpatterns = router.urls
Updated version of @imyousuf code to work with DRF 3.4.1.
class HybridRouter(routers.DefaultRouter):
def __init__(self, *args, **kwargs):
super(HybridRouter, self).__init__(*args, **kwargs)
self._api_view_urls = {}
def add_api_view(self, name, url):
self._api_view_urls[name] = url
def remove_api_view(self, name):
del self._api_view_urls[name]
@property
def api_view_urls(self):
ret = {}
ret.update(self._api_view_urls)
return ret
def get_urls(self):
urls = super(HybridRouter, self).get_urls()
for api_view_key in self._api_view_urls.keys():
urls.append(self._api_view_urls[api_view_key])
return urls
def get_api_root_view(self, api_urls=None):
# Copy the following block from Default Router
api_root_dict = OrderedDict()
list_name = self.routes[0].name
for prefix, viewset, basename in self.registry:
api_root_dict[prefix] = list_name.format(basename=basename)
view_renderers = list(self.root_renderers)
schema_media_types = []
if api_urls and self.schema_title:
view_renderers += list(self.schema_renderers)
schema_generator = SchemaGenerator(
title=self.schema_title,
url=self.schema_url,
patterns=api_urls
)
schema_media_types = [
renderer.media_type
for renderer in self.schema_renderers
]
api_view_urls = self._api_view_urls
class APIRoot(views.APIView):
_ignore_model_permissions = True
renderer_classes = view_renderers
def get(self, request, *args, **kwargs):
if request.accepted_renderer.media_type in schema_media_types:
# Return a schema response.
schema = schema_generator.get_schema(request)
if schema is None:
raise exceptions.PermissionDenied()
return Response(schema)
# Return a plain {"name": "hyperlink"} response.
ret = OrderedDict()
namespace = request.resolver_match.namespace
for key, url_name in api_root_dict.items():
if namespace:
url_name = namespace + ':' + url_name
try:
ret[key] = reverse.reverse(
url_name,
args=args,
kwargs=kwargs,
request=request,
format=kwargs.get('format', None)
)
except NoReverseMatch:
# Don't bail out if eg. no list routes exist, only detail routes.
continue
# In addition to what had been added, now add the APIView urls
for api_view_key in api_view_urls.keys():
url_name = api_view_urls[api_view_key].name
if namespace:
url_name = namespace + ':' + url_name
ret[api_view_key] = reverse.reverse(url_name, request=request, format=kwargs.get('format'))
return response.Response(ret)
return APIRoot.as_view()
How to use:
mobile_router = HybridRouter()
mobile_router.add_api_view("device", url(r'^device/register/$', DeviceViewSet.as_view({'post': 'register'}), name='device-register'))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With