Использование API-интерфейсов Django Rest Framework с APIViews?
Если у меня есть вид:
class MyAPIView(APIView):
def get(self, request, name=None):
return {"hello": name or "world"}
Как я могу получить это в сгенерированной документации? В частности, как я могу его включить в API Root, поэтому он появляется, когда я нахожусь "http://example.com/api/"
Документация содержит пример APIView с описанием, но не описывает процесс фактического его включения в браузер API.
Ответы
Ответ 1
сгенерированная документация?
Привет, Дэвид, сначала я бы не совсем описал API для просмотра как "сгенерированную документацию".
Если вам нужна статическая документация, вам лучше смотреть на сторонний инструмент, например django-rest-swagger.
API-интерфейс, просматриваемый в браузере, означает, что создаваемые вами API будут самоописательными, но немного отличаются от обычных статических инструментов документации. API-интерфейс, доступный для просмотра, гарантирует, что все конечные точки, созданные в вашем API, могут отвечать как машиносчитываемыми (то есть JSON), так и человекообразными (т.е. HTML) представлениями. Он также гарантирует, что вы можете полностью взаимодействовать напрямую через браузер - любой URL-адрес, с которым вы обычно взаимодействуете с программным клиентом, также сможет отвечать браузером на API.
Как я могу получить это.
Просто добавьте docstring в представление и он будет включен в представление API-интерфейса браузера, в зависимости от того, какие URL-адреса вы направляете в это представление.
По умолчанию вы можете использовать обозначение markdown для включения HTML-разметки в описание, но вы также можете настроить это поведение, например, если бы вы скорее всего, используйте rst.
В частности, как я могу его включить в корне API.
Вы просто хотите явно добавить URL-адрес в ответ, возвращенный любым видом, который вы подключили до /api/
. Например...
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 URLconf.
return Response({
'foo': reverse('foo-view', request=request),
'bar': reverse('bar-view', request=request)
})
Ответ 2
Чтобы смешивать с маршрутизаторами и классами или методами APIView, основанными на том, что API Root отображает оба с минимальными представлениями кода в представлении APIRoot, я написал настраиваемый маршрутизатор, который расширяет DefaultRouter и переопределяет get_urls и get_api_root_view; это выглядит следующим образом:
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()
Затем я использую его как -
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
Я оптимизировал HybridRouter для своего варианта использования и удалил некоторый код. Проверьте это:
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
И использование:
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
Или:
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
Обновлена версия кода @imyousuf для работы с 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()
Как использовать:
mobile_router = HybridRouter()
mobile_router.add_api_view("device", url(r'^device/register/$', DeviceViewSet.as_view({'post': 'register'}), name='device-register'))
Ответ 5
Решение @imyousuf приятно, но оно не поддерживает пространства имен url и немного устарело.
Здесь обновление:
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()
Ответ 6
Для справки, сейчас 2019 год, и https://bitbucket.org/hub9/django-hybrid-router все еще работает, единственное изменение заключается в том, что строка 64 должна быть отредактирована, чтобы стать:
regex = api_view_urls[api_view_key].pattern.regex