从单一视图到不同端点的方法:将HTML和JSON的API分开(Django Rest Framework) [英] Methods from a single view to different endpoints: keep APIs for HTML and for JSON separated (Django Rest Framework)

查看:87
本文介绍了从单一视图到不同端点的方法:将HTML和JSON的API分开(Django Rest Framework)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

您能帮忙建议如何在Django Rest Framework中组织井井有条的编码样式,以使端点与HTML和JSON分离吗?

在Flask中,我习惯于将用于服务Json的端点和用于服务HTML的端点分开,例如:

@application.route('/api/')
def api_root():
    #...
    return jsonify({'data' : data})

@application.route('/home/<string:page>/', endpoint='page_template')
    #...
   return render_template(template, page)

所以我可以像这样提供API:

/api/page => serve the json for the page, say for AJAX etc.
/page => serve the corresponding html page 

在Django RF中,我读到ModelViewSet可以同时提供这两种服务.

所以我可以将所有东西都放在一个地方.

但是,当我在路由器上映射视图时,我希望所有端点都遵守与模型相关的路径,它们都是/api

的子路径.

您能帮忙建议一个好的编码实践,以利用ModelViewSet并路由与API分离的html的端点吗?

这是我正在研究的示例,我的疑问在于注释:

from rest_framework import viewsets
from rest_framework import generics
from rest_framework.decorators import action
from rest_framework.response import Response

from .serializers import PersonSerializer
from .models import Person

class PersonViewSet( viewsets.ModelViewSet):
    queryset = Person.objects.all().order_by('name')
    serializer_class = PersonSerializer

    # this will return last person
    # I can see it registered at: 127.0.0.1:8000/api/people/last_person/
    @action(detail=False) 
    def last_person(self, request):
        queryset = Person.objects.all().order_by('timestamp').reverse()[0]
        serializer = self.get_serializer(queryset)
        return Response(serializer.data)

    # this will return a template:
    # I can see it registered at: ../api/people/greetings : I wanted at /greetings
    @action(detail=False)
    def greetings(self, request):

        queryset = Person.objects.all().order_by('timestamp').reverse()[0]
        serializer = self.get_serializer(queryset)

        return render(
            request,
            'myapi/greetings.html',
            {
                'person': serializer.data
            }
        )  

此外,请注意我如何使用方法greetings:在这里,我重复查询集和序列化部分.我想这么做:

def greetings(self, request):

        person = self.last_person(request)
        return render(
            request,
            'myapi/greetings.html',
            {
                'person': person
            }
        )  

但是它将给出错误,因为person将是Response对象,并且找不到将其传递给render的方法.

哪种编码可能是避免重复的东西,并保持API和模板分离的良好编码风格?

/myapi/url.py中,我注册了以下端点:

router = routers.DefaultRouter()
router.register(r'people', views.PersonViewSet)

app_name = 'myapi'
urlpatterns = [
    path('', include(router.urls)),
]

在主url.py中,如下所示:

from django.urls import include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('myapi.urls')),
    path('', include('myapi.urls'))  # How to keep views for templates and for Json separated ??
]

解决方案

如果响应阶段之前的所有内容都相同,则除了渲染器外,您请勿触摸其他任何东西.您可以通过基于用户的请求选择正确的渲染器来提供所需格式的响应,准确地取决于媒体类型- Accept 标头.

例如,假设您要基于媒体类型(Accept标头)发送JSON和HTML响应.因此,当您传递(仅传递一种媒体类型以使示例简单):

  • Accept: application/json它应以JSON返回响应
  • Accept: text/html它应返回HTML响应

在继续实施之前,让我们首先讨论DRF如何处理渲染器:

    可以在settings.py中将
  • 渲染器全局定义为DEFAULT_RENDERER_CLASSES集合,也可以将每个视图(技术上是具有方法操作映射和相关逻辑的每个视图集)定义为renderer_classes类属性.

  • 渲染器的顺序非常重要. DRF根据Accept值选择最特定的渲染器.对于更通用的渲染器或全包(*/*),将选择满足媒体类型的第一个渲染器.

  • 如果将DRF的DefaultRouter用于URL映射,则还可以使用format扩展名过滤掉不支持所传递格式的任何渲染器.例如,如果您有端点/foo/1/,则可以添加类似/foo/1.json/的格式.然后,只会选择具有format = json作为属性的渲染器类(然后,前面提到的最终选择将仅在这些渲染器中进行).

因此,基于上述,从API使用者那里,您需要传递正确的Accept标头,如果使用DefaultRouter,也最好添加format扩展名,以在渲染器上进行预过滤./p>

在API上,执行以下操作:

  • 按照前面提到的正确顺序定义渲染器类
  • 如果使用者通过了format,请确保渲染器将格式名称作为属性
  • 如果您自己发送响应,请确保使用Response(rest_framework.response.Response)类,该类传递正确的渲染器上下文并调用渲染器的render方法来发送正确回复

如果要发送JSON响应,则实际上可以利用JSONRenderer(rest_framework.renderers.JSONRenderer)来完美地达到目的.如果您只想自定义一些内容,则可以创建自己的子类JSONRenderer.

在发送HTTP响应的情况下,您可以从TemplateHTMLRenderer(rest_framework.renderers.TemplateHTMLRenderer)中汲取灵感,也可以对其进行扩展以满足您的需求.它具有样板:

media_type = 'text/html'
format = 'html'
template_name = None
exception_template_names = [
    '%(status_code)s.html',
    'api_exception.html'
]
charset = 'utf-8'

,并且序列化程序传递的数据已经可以用作模板上下文.因此,您可以设置上面的template_name(如果要覆盖则通过Response传递)并在此添加所有HTML表示形式.如果需要,您还可以覆盖render以在其中进行更多自定义.

最后,如果您想自己进行自定义,请 解决方案

If everything before the response phase is the same, you should not touch anything except renderers. You can provide a response in the desired format by choosing the right renderer based on user's request, precisely on the media-type -- Accept header.

For example, let's say you want to send a JSON and HTML response based on the media-type (Accept header). So when you pass (passing only one media-type to keep the example simple):

  • Accept: application/json it should return reponse in JSON
  • Accept: text/html it should return HTML response

Before moving with the implementation, let's discuss first how DRF handles renderers:

  • Renderers can be defined globally in settings.py as DEFAULT_RENDERER_CLASSES collection or on a per view (viewsets are technically views with the method-action mappings and associated logics) basis as renderer_classes class attribute.

  • The order of renderers is very important. DRF chooses the most specific renderer based on the Accept value. For more generic one or for catch-all (*/*) the first renderer that satisfies media-type is chosen.

  • if you use DRF's DefaultRouter for URL mappings, you can also use the format extension to filter out any renderer that does not support the passed format. For example, if you have an endpoint /foo/1/, you can add a format like /foo/1.json/. Then only the renderer classes that have format = json as an attribute will be selected (and then the final selection mentioned in the earlier point will take place only among these renderers).

So based on the above, from the API consumer, you need to pass the correct Accept header and if using DefaultRouter also better to add the format extension to do the pre-filtering on the renderers.

On the API, do:

  • define renderer classes in the correct order as mentioned earlier
  • if the consumer passes format, makes sure the renderer has the format name as an attribute
  • if you send the response yourself, make sure you use the Response (rest_framework.response.Response) class which passes the correct renderer context and calls the render method of the renderer to send the correct response back

If you want to send a JSON response, you can actually leverage the JSONRenderer (rest_framework.renderers.JSONRenderer) which serves the purpose perfectly. If you want to customize only a few things, you can create your own subclassing JSONRenderer.

In the case of sending an HTTP response, you can take inspiration from the TemplateHTMLRenderer (rest_framework.renderers.TemplateHTMLRenderer) or extend it to meet your needs. It has the boilerplate:

media_type = 'text/html'
format = 'html'
template_name = None
exception_template_names = [
    '%(status_code)s.html',
    'api_exception.html'
]
charset = 'utf-8'

and the data passed in by the serializer is already available as the template context. So you can set the template_name above (or pass in with Response if you're overriding) and add all HTML representations there. You can also override render to have more customization there if you want.

And eventually, if you feel like making a custom one yourself, the DRF doc is pretty awesome in explaining what you need to do.

这篇关于从单一视图到不同端点的方法:将HTML和JSON的API分开(Django Rest Framework)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆