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