Skip to main content
 首页 » 编程设计

python之从单个 View 到不同端点的方法 : keep APIs for HTML and for JSON separated (Django Rest Framework)

2024年09月03日33三少

在 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 可以为两者提供服务。

所以我可以把所有东西都放在一个地方。

但是,当我在路由器上映射 View 时,我会让所有端点服务都遵循与我的模型相关的路径,它们都是 /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 header 选择正确的渲染器,以所需格式提供响应。

例如,假设您要根据媒体类型(Accept header )发送 JSON 和 HTML 响应。因此,当您传递 (仅传递一种媒体类型以使示例简单)时:

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

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

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

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

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

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

在 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 以获得更多自定义。

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