【问题标题】:Customize DjangoRestFramework Browsable API自定义 DjangoRestFramework 可浏览 API
【发布时间】:2020-06-19 03:06:13
【问题描述】:

我正在使用 Django Rest Framework 3.11.0,我想使用带有自定义模板的 BrowsableAPIRenderer 来呈现实例的详细信息。我只想覆盖 dict/json 的渲染,下图中标记为红色,其余的我想保留。

通过覆盖restframework/api.html,我只设法更改了标题、标题和一些字段,但我没有找到一种方法来呈现实例的详细信息,例如在一张桌子上。有没有办法做到这一点?

澄清:我的模型包含大型字典,我希望它们显示得比内联字符串更漂亮。我认为当我发现如何自定义(已经很漂亮的)Django RestFramework BrowsableAPI 时,我也将能够解决我的问题。

(如果您想解决类似问题,请查看我的更新 2。)


更新 1

这是我使用Bedilbeks answer 的地方(直到第一次更新)。

我不想更改所有视图,所以我没有全局注册渲染器。

REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
         # 'users.renderers.CustomBrowsableAPIRenderer',
    ]
}

相反,我将renderer_classes 设置为我的UserViewSet 并在此处使用我的CustomBrowsableAPIRenderer

class UserViewSet(GenericViewSet, ListModelMixin, RetrieveModelMixin):
    queryset = UserModel.objects.all()
    serializer_class = UserSerializer
    renderer_classes = [renderers.JSONRenderer, CustomBrowsableAPIRenderer]

我需要覆盖api.html 模板,但我不希望此更改适用于任何地方,因此我在渲染器中动态选择模板。默认情况下,BrowsableAPIRenderer 有一个template = "rest_framework/api.html" 属性,但我需要逻辑,所以我使用@property 装饰器来执行以下操作:

  • 检查我们是否在detail视图中
  • 检查 GET 参数

如果我们在详细视图中并且存在"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)

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

我实际上并没有为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

所以由URL控制,我可以在默认渲染器和自定义/表格渲染器之间切换。

  • localhost.me:8000/api/users/1/?table

  • localhost.me:8000/api/users/1/

到目前为止一切顺利,我现在有了自己的渲染器类,我可以修改我的用户实例的 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;
}

更新 2

由于我现在可以使用自定义的 BrowsableAPIRenderer 和带有很多技巧的模板显示一些视图,所以我回到了导致我提出这个问题的问题。我想了解 DRF 如何渲染我的模型,以进行更改以显示大型嵌套字典。

我发现BrowsableAPIRenderer 将模型内容作为单个字符串插入到api.html 模板中,例如{{ content|urlize_quoted_links }}&lt;pre&gt; 标记内。 插入发生在BrowsableAPIRenderer.get_content 方法中。

# original code
renderer_context['indent'] = 4
content = renderer.render(data, accepted_media_type, renderer_context)

我现在看到我所要做的就是继承 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

我也意识到我可以用不同的措辞来表达我的问题,以便更快地结束。

【问题讨论】:

  • 我没有得到你真正想要的东西?拜托,你能为你的场景提供一些例子吗?你想要什么样的桌子?
  • 因此,django restframework 的可浏览 API 功能使显示我的 Django REST 端点变得非常容易,并且还可以呈现截图中所示的数据库模型。您可以自定义 DRF 中的大多数内容,例如更改引导主题或覆盖用于构建页面的模板。你可以例如覆盖api.html 以自定义我截屏页面的外观。我没有找到一种方法来自定义模型的表示和渲染方式。表格的类型并不重要,如果我可以在表格中显示模型(例如键值),我可以以任何方式显示它。
  • 其实我知道重写模板的目的和 BrowsableAPI 的使用,我只是想知道你的场景,给出一个更好理解的答案。所以,无论如何我现在会尝试给出答案

标签: django django-rest-framework django-templates


【解决方案1】:

这不是最合适和最好的答案,但我认为它或多或少是你想要的,而且有点小技巧。

假设我们要公开 /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


首先,让我们在我们的一个应用中创建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)

通过这种方式,我们用我们的CustomBrowsableAPIRenderer 覆盖BrowsableAPIRenderer,只是为了更改我们content 的默认渲染器(在我们的例子中,它是dict obj 是json 可序列化的)。我们通过访问 viewset.detail 属性来检查我们的视图是否为detail=True

然后,我们需要用我们自己的TableHTMLRenderer 覆盖现有的TemplateHTMLRenderer,以将我们的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>

现在,我们准备在settings.py 中启用渲染器后进行检查:

REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
        'users.renderers.CustomBrowsableAPIRenderer',
    ]
}

如果你看到了,现在我们用我们自己的 users.renderers.CustomBrowsableAPIRenderer 自定义渲染器替换了 rest_framework.renderers.BrowsableAPIRenderer

我们看到现在我们有表格而不是 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 %}

... 放置 3 个点的位置,我们必须从 content 块开始复制并粘贴 base.html 的其余部分,因为 content 块太大,我们想更改该块内的内容。如果不复制粘贴整个content 块,我们就无法做到这一点。如果我们将我们的模板与旧的base.html&lt;/span&gt; 元素结束的地方)进行比较,我们删除{{ content|urlize_quoted_links }} 上下文并将其放在&lt;/pre&gt; 元素结束后的新div 中,如代码示例中所示,所以现在我们不使用&lt;pre&gt; 元素并且没有放置额外的空格。

那么,如果我们看一下结果:

但是,不好的是,现在我们已经更改并覆盖了整个模板,所以我们也破坏了其他视图:

因此,您会看到我们的结果变得笨拙,并且我们做了太多的修改,无法得到我们想要的结果。这就是为什么我不会真的建议重写 api.html 只是为了更改 content 部分。相反,我会通过覆盖渲染器类来使用我的第一个答案,它不那么 hacky。

【讨论】:

  • 这非常接近我的想象。感谢您的详细回答,它帮助我更多地了解了框架。也应该可以仅将自定义渲染器用于特定的 ViewSet 而不是全局默认值,对吗?今天晚些时候,当我有机会尝试时,我会回到这个问题上。顺便说一句,你会认为你的答案的哪一部分是骇人听闻的?
  • table_template 的渲染非常奇怪,如您的屏幕截图所示,例如所有空格都被渲染。这是为什么?在 DRF 中呈现数据部分(示例中的键、值)的代码在哪里?是否使用了类似的模板?
  • @robinki 实际上,渲染器是在全局范围内使用的,不明智,建议对特定的 ViewSet 使用渲染器。但是通过我在CustomBrowsableAPIRendererget_default_renderer 方法中完成的hack,您可以检查和使用不同的内容渲染器。 table_template 的渲染很奇怪,因为我没有覆盖 rest_framework 中的 base.html。如果您查看该模板内部,您会看到 {{content}} 已保留在
     元素中,该元素保留了所有空格,因为它是在内容中写入的。我会更新我的回复,你会看到的。
  • 我将您的建议与&lt;pre&gt; 标签一起使用。我做了一些更改,只为我的特定视图使用渲染器。我更新了问题,描述了我所做的更改。
  • @robinki 我看到您对渲染器的理解更好,而且您的解决方案看起来也可以接受。关于你关于换行的问题,很抱歉,但对前端问题无能为力,因为我更关心后端问题。希望您能找到问题的答案:)
猜你喜欢
  • 2020-07-15
  • 2018-07-08
  • 2015-07-17
  • 2019-10-24
  • 2022-10-17
  • 1970-01-01
  • 2012-05-10
  • 2017-10-16
  • 1970-01-01
相关资源
最近更新 更多