rob*_*nki 6 django django-templates django-rest-framework
我正在使用 Django Rest Framework 3.11.0,我想使用 BrowsableAPIRenderer 和自定义模板来呈现实例的详细信息。我只想覆盖 dict/json 的渲染,在下图中标记为红色,我想保留其余所有内容。
通过覆盖,restframework/api.html我只能更改标题、标题和一些字段,但我没有找到一种方法来呈现实例的详细信息,例如在表格中。有没有办法做到这一点?
澄清:我有带有大型字典的模型,我想将它们显示得比内联字符串更漂亮。我认为当我找到如何定制(已经很漂亮的)Django RestFramework BrowsableAPI 时,我也将能够解决我的问题。
(如果您想解决类似的问题,请查看我的更新 2。)
这是我从 Bedilbeks 得到的答案(直到第一次更新)。
我不想更改所有视图,因此我不会全局注册渲染器。
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
# 'users.renderers.CustomBrowsableAPIRenderer',
]
}
Run Code Online (Sandbox Code Playgroud)
相反,我将设置renderer_classes为 myUserViewSet并在此处使用 my CustomBrowsableAPIRenderer。
class UserViewSet(GenericViewSet, ListModelMixin, RetrieveModelMixin):
queryset = UserModel.objects.all()
serializer_class = UserSerializer
renderer_classes = [renderers.JSONRenderer, CustomBrowsableAPIRenderer]
Run Code Online (Sandbox Code Playgroud)
我需要覆盖api.html模板,但我不希望此更改应用到所有地方,因此我在渲染器中动态选择模板。默认情况下,它BrowsableAPIRenderer有一个template = "rest_framework/api.html"属性,但我需要逻辑,所以我使用@property装饰器来执行以下操作:
detail视野范围内如果我们在详细视图中并且"table"存在参数,则返回我的模板,否则返回默认值。
class CustomBrowsableAPIRenderer(BrowsableAPIRenderer):
@property
def template(self):
view = self.renderer_context.get("view", {})
table = "table" in view.request.query_params
if view and hasattr(view, "detail") and view.detail and table:
return "users/api.html" # custom template
else:
return "rest_framework/api.html" # default
def get_default_renderer(self, view):
table = "table" in view.request.query_params
if hasattr(view, "detail") and view.detail and table:
return TableHtmlRenderer()
return super().get_default_renderer(view)
Run Code Online (Sandbox Code Playgroud)
的关键部分api.html如下所示(第 123 行左右)。
...
{% block style %}
{{ block.super }}
<link rel="stylesheet" type="text/css" href="{% static "css/api.css" %}"/>
{% endblock %}
<!-- HERE IS THE ACTUAL CONTENT -->
</span></pre><div class="prettyprint" style="overflow: auto;">{{ content|urlize_quoted_links }}</div>
</div>
...
Run Code Online (Sandbox Code Playgroud)
实际上,我并不是为User模型和 ViewSet 这样做,但为了示例,我坚持这样做。在我的模型中,我想要渲染更大的 JSON 元素,因此我在我的模型中进行一些预处理,TableHTMLRenderer以缩进形式返回 JSON。
class TableHtmlRenderer(TemplateHTMLRenderer):
media_type = "text/html"
format = "api"
template_name = "table_template.html"
def get_template_context(self, data, renderer_context):
for key in data.keys():
try:
data[key] = json.dumps(json.loads(data[key]), indent=4)
except (JSONDecodeError, TypeError):
pass
context = {
"data": data
}
response = renderer_context["response"]
if response.exception:
context["status_code"] = response.status_code
return context
Run Code Online (Sandbox Code Playgroud)
通过 URL 的控制,我可以在默认渲染器和自定义/表格渲染器之间切换。
到目前为止一切顺利,我现在有了自己的 Renderer 类,并且可以修改 User 实例的 API 视图的外观。我仍然在与表格作斗争,因为长行上的换行符不起作用,并且它不会停留在 div 的边界内。
这是模板app.css中加载的内容api.html。
pre.inline {
padding: 0;
border: none;
word-break: break-all;
word-wrap: break-word;
display: contents;
}
table, th, td {
vertical-align: top;
padding: 2px;
text-align: left;}
table {
//table-layout: fixed;
width: 100% !important;
word-wrap:break-word;
}
th, td {
border-bottom: 1px solid #ddd;
overflow: auto;
width: 100%;
}
tr:hover {
background-color: #f2f2f2;
}
tr:nth-child(even) {
background-color: #f5f5f5;
}
Run Code Online (Sandbox Code Playgroud)
由于我现在可以使用自定义的 BrowsableAPIRenderer 和模板显示一些视图,并进行了一些技巧,所以我回到了导致我提出这个问题的问题。我想了解 DRF 如何渲染我的模型,进行更改以显示大型嵌套字典。
我发现将BrowsableAPIRenderer模型内容作为单个字符串插入到api.html模板中,就像标签{{ content|urlize_quoted_links }}内部一样<pre>。插入发生在BrowsableAPIRenderer.get_content方法中。
# original code
renderer_context['indent'] = 4
content = renderer.render(data, accepted_media_type, renderer_context)
Run Code Online (Sandbox Code Playgroud)
我现在发现我所要做的就是子类化BrowsableAPIRenderer并重写该get_content方法。我就是这样做的。
class LogBrowsableAPIRenderer(BrowsableAPIRenderer):
def get_content(self, renderer, data, accepted_media_type, renderer_context):
"""
Extends BrowsableAPIRenderer.get_content.
"""
if not renderer:
return '[No renderers were found]'
renderer_context['indent'] = 4
# content = renderer.render(data, accepted_media_type, renderer_context)
# try to convert all string-values into dictionaries
data_dict = dict(data.items())
for k in data_dict.keys():
try:
data_dict[k] = json.loads(data_dict[k], strict=False)
except JSONDecodeError:
# ignore errors and move on for now
pass
# dump into indented string again
content = json.dumps(data_dict, indent=4, sort_keys=True).encode(encoding="utf-8")
render_style = getattr(renderer, 'render_style', 'text')
assert render_style in ['text', 'binary'], 'Expected .render_style "text" or "binary", but got "%s"' % render_style
if render_style == 'binary':
return '[%d bytes of binary content]' % len(content)
return content
Run Code Online (Sandbox Code Playgroud)
我还意识到我可以用不同的措辞来表达我的问题,以便更快地结束这个问题。
这不是最合适和最好的答案,但我认为这或多或少是你想要的,而且有点黑客。
假设我们想要公开/users/端点,并且我们有以下内容views.py:
from django.contrib.auth import get_user_model
from rest_framework.mixins import ListModelMixin, RetrieveModelMixin
from rest_framework.routers import DefaultRouter
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import GenericViewSet
UserModel = get_user_model()
class UserSerializer(ModelSerializer):
class Meta:
model = UserModel
fields = ('first_name', 'last_name')
class UserViewSet(GenericViewSet, ListModelMixin, RetrieveModelMixin):
queryset = UserModel.objects.all()
serializer_class = UserSerializer
router = DefaultRouter()
router.register('users', UserViewSet)
urlpatterns = router.urls
Run Code Online (Sandbox Code Playgroud)
因此,首先,让我们renderers.py在一个应用程序中创建(假设我们有users应用程序):
from rest_framework.renderers import BrowsableAPIRenderer, TemplateHTMLRenderer
class TableHtmlRenderer(TemplateHTMLRenderer):
media_type = 'text/html'
format = 'api'
template_name = 'users/table_template.html'
def get_template_context(self, data, renderer_context):
context = {'data': data}
response = renderer_context['response']
if response.exception:
context['status_code'] = response.status_code
return context
class CustomBrowsableAPIRenderer(BrowsableAPIRenderer):
def get_default_renderer(self, view):
if view.detail:
return TableHtmlRenderer()
return super().get_default_renderer(view)
Run Code Online (Sandbox Code Playgroud)
通过这种方式,我们重写BrowsableAPIRenderer我们的CustomBrowsableAPIRenderer只是为了更改我们的默认渲染器content(在我们的例子中,它是dictjson 可序列化的 obj)。我们通过访问 viewset.detail 属性来检查我们的视图是否是detail=True。
然后,我们需要TemplateHTMLRenderer用我们自己的覆盖现有的TableHTMLRenderer,以将我们的 dict 对象作为data上下文users/table_template.html
所以,现在我们必须创建我们的users/table_template.html:
<table>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
{% for key, value in data.items %}
<tr>
<td>{{ key }}</td>
<td>{{ value }}</td>
</tr>
{% endfor %}
</table>
Run Code Online (Sandbox Code Playgroud)
现在,我们准备在启用渲染器后进行检查settings.py:
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
'users.renderers.CustomBrowsableAPIRenderer',
]
}
Run Code Online (Sandbox Code Playgroud)
如果你看到了,现在我们rest_framework.renderers.BrowsableAPIRenderer用我们自己的users.renderers.CustomBrowsableAPIRenderer自定义渲染器替换了。
我们看到现在我们有表而不是 json 结构。
我希望,这也对你有用。
更新
如果我们想要摆脱空格或者想要更多自定义修改,我们必须覆盖并更改api.html. 因此,我们创建自己的users/api.html:
{% extends "rest_framework/base.html" %}
{% load i18n %}
{% load rest_framework %}
{% block content %}
...
</span></pre>
<div class="prettyprint">{{ content|urlize_quoted_links }}
</div>
</div>
...
{% endblock content %}
Run Code Online (Sandbox Code Playgroud)
在放置 3 个点的地方,我们必须复制并粘贴从块开始的...其余部分,因为块太大,我们想要更改该块内的某些内容。如果不复制粘贴整个块,我们就无法做到这一点。如果我们将我们的模板与旧的(元素完成的地方)进行比较,我们会删除上下文并将其放入元素末尾之后的新 div 中,如代码示例中所示,这样现在我们不使用元素,也不会出现额外的空格放。base.htmlcontentcontentcontentbase.html</span>{{ content|urlize_quoted_links }}</pre><pre>
但是,糟糕的是,现在我们已经更改并覆盖了整个模板,因此我们也破坏了其他视图:

结果,你会看到我们的结果变得笨拙,并且我们做了太多的修改来达到我们想要的结果。这就是为什么我真的不建议api.html仅仅为了改变那content部分而重写。相反,我会通过覆盖渲染器类来使用我的第一个答案,该答案不那么老套。
| 归档时间: |
|
| 查看次数: |
3981 次 |
| 最近记录: |