Я использую Django Rest Framework 3.11.0, и я хочу использовать BrowsableAPIRenderer с настраиваемым шаблоном для отображения деталей экземпляра. Я только хочу переопределить рендеринг dict / json, помеченного красным на изображении ниже, и я хочу оставить все остальные.
Перезаписав restframework/api.html
Мне удалось изменить только заголовок, заголовок и некоторые поля, но я не нашел способ визуализации деталей экземпляра, например, в таблице. Есть ли способ сделать это?
Уточнение : у меня есть модели с большими словарями, которые я хочу отображать красивее, чем просто строковые строки. Я чувствую, что когда я узнаю, как настроить (уже красивый) Django RestFramework BrowsableAPI, я также смогу решить мою проблему.
(посмотрите на мое обновление 2, если вы хотите решить похожая проблема.)
Обновление 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
- 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
Я также понимаю, что мог бы сформулировать свой вопрос иначе, чтобы, возможно, прийти к этому закрытию быстрее.