Настройте DjangoRestFramework Browsable API - PullRequest
3 голосов
/ 06 марта 2020

Я использую Django Rest Framework 3.11.0, и я хочу использовать BrowsableAPIRenderer с настраиваемым шаблоном для отображения деталей экземпляра. Я только хочу переопределить рендеринг dict / json, помеченного красным на изображении ниже, и я хочу оставить все остальные.

Перезаписав restframework/api.html Мне удалось изменить только заголовок, заголовок и некоторые поля, но я не нашел способ визуализации деталей экземпляра, например, в таблице. Есть ли способ сделать это?

Уточнение : у меня есть модели с большими словарями, которые я хочу отображать красивее, чем просто строковые строки. Я чувствую, что когда я узнаю, как настроить (уже красивый) Django RestFramework BrowsableAPI, я также смогу решить мою проблему.

(посмотрите на мое обновление 2, если вы хотите решить похожая проблема.)

Screenshot


Обновление 1

Вот где я получил ответ Бедилбекс (до первого обновления).

Я не хочу менять все представления, поэтому я не регистрирую визуализатор глобально.

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", но мне нужен logi c, поэтому я использую декоратор @property, чтобы сделать следующее:

  • проверить, находимся ли мы в detail view
  • проверка параметров 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).

<code>...
{% block style %}
    {{ block.super }}
    <link rel="stylesheet" type="text/css" href="{% static "css/api.css" %}"/>
{% endblock %}

<!-- HERE IS THE ACTUAL CONTENT -->
</span>
{{content | urlize_quoted_links}} ...

Я на самом деле не делаю этого для 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, я могу переключаться между рендерером по умолчанию и рендерером Custom / Table.

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

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

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

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

Пока все хорошо, у меня теперь есть свои собственные классы рендерера, и я могу изменить то, как выглядит представление API для моего экземпляра User. Я все еще борюсь с таблицей, потому что разрывы строк в длинных строках не работают, и они не останутся в границах div.

Вот app.css, который загружен в api.html template.

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 }} внутри <pre> тегов. Вставка происходит в методе 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

Я также понимаю, что мог бы сформулировать свой вопрос иначе, чтобы, возможно, прийти к этому закрытию быстрее.

1 Ответ

1 голос
/ 09 марта 2020

Это не самый подходящий и лучший ответ, но я думаю, что это более или менее то, что вы хотите, и это небольшой взлом.

Допустим, мы хотим выставить / 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)

Таким образом, мы переопределяем BrowsableAPIRenderer с помощью нашего CustomBrowsableAPIRenderer, просто чтобы изменить средство визуализации по умолчанию для нашего content (В нашем случае это dict obj, который json сериализуемый) , Мы проверяем, является ли наше представление detail=True, обращаясь к атрибуту viewset.detail.

Затем нам нужно переопределить существующее 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>

Теперь мы готовы проверить сразу после включения наших средств визуализации в settings.py:

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

Если вы видите, теперь мы заменили rest_framework.renderers.BrowsableAPIRenderer нашим собственным users.renderers.CustomBrowsableAPIRenderer настраиваемым средством визуализации.

enter image description here

Мы теперь у нас есть таблица вместо json структуры.

Надеюсь, это должно сработать и на вашей стороне.


UPDATE

Если мы хотим избавиться от пробелов или хотим больше пользовательских модификаций, мы должны переопределить и изменить api.html. Итак, мы создаем свои собственные users/api.html:

{% extends "rest_framework/base.html" %}
{% load i18n %}
{% load rest_framework %}

{% block content %}
...
{{content | urlize_quoted_links}} ... {% endblock content%}

, где поставлено ... 3 точки, мы должны скопировать и вставить остальные base.html начиная с блока content, потому что блок content слишком большой, и мы хотим изменить что-то внутри этого блока. Мы не можем сделать это без копирования-вставки всего блока content. Если мы сравним наш шаблон со старым base.html, местом, где заканчивается элемент </span>, мы удалим контекст {{ content|urlize_quoted_links }} и поместим его в новый div после конца элемента </pre>, как мы видим в примере кода, поэтому что теперь мы не используем элемент <pre> и дополнительные пробелы не добавляются.

Затем, если мы посмотрим на результат: enter image description here

Но что Плохо, теперь мы изменили и отвергли весь шаблон, поэтому мы нарушили и другие представления: enter image description here

В результате вы видите, что наш результат стал неуклюжим, и мы сделали слишком много хака, чтобы прийти к результату, который мы хотим. Вот почему я бы не советовал переопределять api.html только для того, чтобы изменить эту content деталь. Вместо этого я бы использовал свой первый ответ, который менее хакерский, переопределив классы рендерера.

...