从单个视图到不同端点的方法:保持HTML和JSON的api分离(Django Rest框架)

2024-04-19 20:27:33 发布

您现在位置:Python中文网/ 问答频道 /正文

在Django Rest框架中,您能否帮助建议如何保持编码风格的有序性,以保持端点与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,并为html路由与API分离的端点

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

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提供服务的:这里我重复queryset和serialising部分。我想做:

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 ??
]

Tags: fromimportselfapireturnrequestdefpage
1条回答
网友
1楼 · 发布于 2024-04-19 20:27:33

如果响应阶段之前的所有内容都是相同的,则除了渲染器之外,不应触碰任何内容。您可以根据用户的请求选择正确的呈现器,精确地在媒体类型Accept头上,以所需的格式提供响应

例如,假设您希望基于媒体类型(Accept头)发送JSON和HTML响应。因此,当您传递时(仅传递一种媒体类型以保持示例简单)

  • Accept: application/json它应该返回JSON中的response
  • Accept: text/html它应该返回HTML响应

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

  • 呈现器可以在settings.py中全局地定义为DEFAULT_RENDERER_CLASSES集合,或者在每个视图(从技术上讲,视图集是带有方法操作映射和关联逻辑的视图)的基础上定义为renderer_classes类属性

  • 渲染器的顺序非常重要。DRF根据Accept值选择最具体的呈现器。对于更通用的one或catch all(*/*),选择满足媒体类型的第一个呈现器

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

因此,基于上述内容,您需要从API使用者传递正确的Accept头,如果使用DefaultRouter,那么最好添加format扩展来对呈现器进行预过滤

在API上,执行以下操作:

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

如果您想发送一个JSON响应,实际上可以利用JSONRendererrest_framework.renderers.JSONRenderer)来实现这一目的。如果只想定制一些东西,可以创建自己的子类JSONRenderer

在发送HTTP响应的情况下,您可以从TemplateHTMLRendererrest_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以进行更多的定制

最后,如果你想自己做一个定制的,the DRF doc is pretty awesome in explaining what you need to do

相关问题 更多 >