django-rest-framework - queryset - django rest framework with django 2
¿Usando la API navegable de Django Rest Framework con APIViews? (5)
la documentación generada?
Hola David, primero no describiría la API navegable como ''documentación generada''.
Si necesita documentación estática, lo mejor es buscar una herramienta de terceros como django-rest-swagger .
La API navegable significa que las API que construye serán autodescriptivas, pero es un poco diferente de las herramientas convencionales de documentación estática. La API navegable garantiza que todos los puntos finales que cree en su API puedan responder tanto con representaciones legibles por máquina (es decir, JSON) como legibles por el ser humano (es decir, HTML). También garantiza que pueda interactuar por completo directamente a través del navegador; cualquier URL con la que normalmente interactúe con un cliente programático también podrá responder con una vista amigable del navegador a la API.
¿Cómo puedo incluir eso?
Solo agregue una docstring a la vista y se incluirá en la representación de la API navegable de las URL que dirija a esa vista.
De forma predeterminada, puede utilizar la notación de rebaja para incluir el marcado HTML en la descripción, pero también puede personalizar ese comportamiento , por ejemplo, si prefiere usar primero.
Específicamente, ¿cómo puedo incluirlo en la API Root?
Simplemente querrá agregar explícitamente la URL a la respuesta devuelta por cualquier vista que haya conectado a /api/
. Por ejemplo...
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)
})
Si tengo una vista como:
class MyAPIView(APIView):
def get(self, request, name=None):
return {"hello": name or "world"}
¿Cómo puedo incluir eso en la documentación generada? Específicamente, ¿cómo puedo incluirlo en la API Root, por lo que aparece cuando visito " http://example.com/api/ "?
La documentación incluye un ejemplo de APIView con descripción, pero no describe el proceso para incluirlo realmente en el navegador API.
La solución de @imyousuf es agradable, pero no admite los espacios de nombres de url y está un poco desactualizada.
Aquí hay una actualización de esto:
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()
Optimicé HybridRouter para mi caso de uso y eliminé algunos códigos. Echale un vistazo:
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
Y el uso:
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
O:
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
Para mezclar con enrutadores y clases APIView o método basado de tal manera que la raíz de la API muestra ambos con vistas de código mínimas en la vista APIRoot escribí un enrutador personalizado que amplía DefaultRouter y anula get_urls y get_api_root_view; se ve así:
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()
Entonces lo uso como -
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)),
Versión actualizada del código @imyousuf para trabajar con 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()
Cómo utilizar:
mobile_router = HybridRouter()
mobile_router.add_api_view("device", url(r''^device/register/$'', DeviceViewSet.as_view({''post'': ''register''}), name=''device-register''))