从单一视图到不同端点的方法:将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)
问题描述
您能帮忙建议如何在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如何处理渲染器:
-
可以在
-
渲染器全局定义为
DEFAULT_RENDERER_CLASSES
集合,也可以将每个视图(技术上是具有方法操作映射和相关逻辑的每个视图集)定义为renderer_classes
类属性. -
渲染器的顺序非常重要. DRF根据
Accept
值选择最特定的渲染器.对于更通用的渲染器或全包(*/*
),将选择满足媒体类型的第一个渲染器. -
如果将DRF的
DefaultRouter
用于URL映射,则还可以使用format
扩展名过滤掉不支持所传递格式的任何渲染器.例如,如果您有端点/foo/1/
,则可以添加类似/foo/1.json/
的格式.然后,只会选择具有format = json
作为属性的渲染器类(然后,前面提到的最终选择将仅在这些渲染器中进行).
settings.py
中将因此,基于上述,从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 JSONAccept: 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
asDEFAULT_RENDERER_CLASSES
collection or on a per view (viewsets are technically views with the method-action mappings and associated logics) basis asrenderer_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 theformat
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 haveformat = 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 therender
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屋!