Using Django Rest Framework's browsable API with APIViews?

后端 未结 6 1040
走了就别回头了
走了就别回头了 2020-12-24 01:32

If I have a view like:

class MyAPIView(APIView):
    def get(self, request, name=None):
        return {\"hello\": name or \"world\"}

How c

相关标签:
6条回答
  • 2020-12-24 01:42

    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
    
    0 讨论(0)
  • 2020-12-24 01:46

    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)),
    
    0 讨论(0)
  • 2020-12-24 01:53

    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)
            })
    
    0 讨论(0)
  • 2020-12-24 01:54

    For the record, it is 2019 now and https://bitbucket.org/hub9/django-hybrid-router is still working, only modification is that line 64 has to be edited to become:

                    regex = api_view_urls[api_view_key].pattern.regex
    
    0 讨论(0)
  • 2020-12-24 01:56

    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'))
    
    0 讨论(0)
  • 2020-12-24 01:57

    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()
    
    0 讨论(0)
提交回复
热议问题