从单一视图到不同端点的方法:将 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)

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

问题描述

您能否帮助建议如何在 Django Rest Framework 中保持编码风格的组织,以将端点与 HTML 和 JSON 分开?

Could you help suggesting how to keep coding style organised to keep endpoints from HTML and JSON separated, in Django Rest Framework ?

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

In Flask I am used to keeps endpoints for serving Json, and ones for serving HTML, separated, like:

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

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

所以我可以提供如下 API:

And so I could serve the APIs like:

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

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

In Django RF, I read that a ModelViewSet can serve both.

这样我就可以把所有东西都放在一个地方.

So I could keep everything in one place.

但是,当我在路由器上映射视图时,我会让所有服务的端点都尊重与我的模型相关的路径,它们都是 /api

However, when I come to map views on the router, I would have all the endpoint served respect the path related my model, they would be all sub-path of /api

您能否帮助建议使用 ModelViewSet 的良好编码实践,并为与 API 分离的 html 路由端点?

Could you help in advising a good coding practice to make use of ModelViewSet, and route endpoints for html separated from APIs ?

这是我正在研究的例子,我的疑问在评论中:

This is the example Im working on, my doubts are in comments:

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 提供服务:这里我重复了查询集和序列化部分.我想这样做:

Also, please note how I am serving the method greetings: here I am repeating the queryset and serialising part. I thought to do:

def greetings(self, request):

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

但是它会报错,因为 person 将是一个 Response 对象,并且找不到将它传递给 render 的方法.

But it will give error, because personwould be a Response object, and could not find a way to pass it to the render.

哪种编码风格可以避免重复,并保持 API 和模板分离?

Which could be a good coding style to avoid replicating things, and keep APIs and templates separated ?

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

In /myapi/url.py I am registered the endpoints like:

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 标头,以所需格式提供响应.

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.

例如,假设您想根据媒体类型(Accept 标头)发送 JSON 和 HTML 响应.所以当你传递(只传递一种媒体类型以保持示例简单)时:

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 它应该以 JSON 格式返回响应
  • Accept: text/html 它应该返回 HTML 响应
  • Accept: application/json it should return reponse in JSON
  • Accept: text/html it should return HTML response

在开始实施之前,让我们先讨论一下 DRF 如何处理渲染器:

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

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

  • 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.

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

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.

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

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).

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

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.

在 API 上,执行:

On the API, do:

  • 按照前面提到的正确顺序定义渲染器类
  • 如果消费者通过format,确保渲染器将格式名称作为属性
  • 如果您自己发送响应,请确保使用传递正确渲染器的 Response (rest_framework.response.Response) 类context 并调用渲染器的 render 方法来发送正确的响应
  • 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

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

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.

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

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'

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

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.

最后,如果你想自己定制一个,DRF 文档在解释您需要做什么方面非常棒.

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天全站免登陆