可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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.
回答1:
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)
})
回答2:
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)),
回答3:
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
回答4:
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'))
回答5:
Solution by @imyousuf is nice, but it doesn't support url namespaces and is a bit outdated.
Here's an update of it:
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)
# In addition to that:
api_view_urls = self._api_view_urls
class APIRoot(views.APIView):
_ignore_model_permissions = True
def get(self, request, *args, **kwargs):
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(
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():
namespace = request.resolver_match.namespace
if namespace:
url_name = namespace + ":" + api_view_key
ret[api_view_key] = reverse(url_name,
args=args,
kwargs=kwargs,
request=request,
format=kwargs.get('format', None))
return response.Response(ret)
return APIRoot.as_view()