Использование свойства model (списка словарей) в качестве входных данных для функции format_html_join() в django приводит к ошибке KeyError
Я пытаюсь использовать утилиту Django format_html_join()
, чтобы вернуть историю версий в формате html для одной из моих моделей. Но я не могу заставить format_html_join()
принять мой список словарей.
Вот что предлагается в документации:
format_html_join(
"\n",
'<li data-id="{id}">{id} {title}</li>',
({"id": b.id, "title": b.title} for b in books),
)
Этот третий аргумент должен быть следующим: args_generator должен быть итератором, который выдает аргументы для передачи в format_html(), либо последовательности позиционных аргументов, либо сопоставления аргументов ключевых слов.
Я пробовал разные способы заставить это работать, но у меня ничего не получается, поэтому я прошу о помощи. Я думал, что список словарей можно перебирать. Я также думаю, что должен быть способ использовать список словарей в утилите, которая ожидает список словарей без необходимости заново создавать список словарей. Вот метод model, который мне нужен для получения истории версий:
@property # I have tried this as a property and not as a property, neither works
def get_version_history(self):
versions = Version.objects.get_for_object(self)
version_history = []
for version in versions:
history_fields = version.field_dict
hdict = {"question": history_fields['question'],
"answer": history_fields['answer'],
"user": version.revision.user.username,
"timestamp": version.revision.date_created.strftime("%Y-%m-%d %H:%M"),
}
version_history.append(hdict)
return version_history
Этот метод возвращает что-то вроде этого:
[{'question': "I'm out of questions",
'answer': 'bye',
'user': 'test.supervisor',
'timestamp': '2025-07-31 20:19'},
{'question': "I'm out of questions",
'answer': 'me too',
'user': 'test.supervisor',
'timestamp': '2025-07-31 20:18'},
{'question': "I'm out of questions",
'answer': '',
'user': 'test.supervisor',
'timestamp': '2025-07-31 20:18'}]
Теперь я пытаюсь вернуть версию этого списка словарей в формате html:
def version_html(self):
html = format_html_join(
"\n",
"""<tr>
<td>{question}</td>
<td>{answer}</td>
<td>{user}</td>
<td>{timestamp}</td>
</tr>""",
self.get_version_history
)
return html
Приведенный выше код возвращает эту ошибку:
File ~/.virtualenvs/cep/lib/python3.12/site-packages/django/utils/html.py:112, in format_html(format_string, *args, **kwargs)
110 args_safe = map(conditional_escape, args)
111 kwargs_safe = {k: conditional_escape(v) for (k, v) in kwargs.items()}
--> 112 return mark_safe(format_string.format(*args_safe, **kwargs_safe))
KeyError: 'question'
Я перепробовал различные варианты для третьего аргумента, все с различными ошибками:
self.get_version_history
self.get_version_history()
**self.get_version_history
**self.get_version_history()
{"question": v.question, "answer": v.answer, "user": v.user, "timestamp": v.timestamp,} for v in self.get_version_history())
({"question": v['question'], "answer": v['answer'], "user": v['user'], "timestamp": v['timestamp']} for v in self.get_version_history())
{"question": v.question, "answer": v.answer, "user": v.user, "timestamp": v.timestamp,} for v in self.get_version_history)
({"question": v['question'], "answer": v['answer'], "user": v['user'], "timestamp": v['timestamp']} for v in self.get_version_history)
(d for d in self.get_version_history)
(d for d in self.get_version_history())
[d for d in self.get_version_history]
[d for d in self.get_version_history()]
Сейчас я просто бьюсь.
Эта функция была добавлена недавно. Действительно, это реализовано в pull request 18469 [GitHub] и, таким образом, было введено в django-5.2. Действительно, это упоминается в примечаниях к выпуску [Django-doc]:
format_html_join()
теперь поддерживается использование итераций отображений, передающих их содержимое в качестве аргументов ключевых слов вformat_html()
.
До django-5.2, можно было использовать только повторяющиеся элементы, такие как кортежи или списки, что усложняло правильное форматирование. Мы можем видеть это в исходном коде [GitHub]:
return mark_safe( conditional_escape(sep).join( format_html(format_string, *args) for args in args_generator ) )
Итак, у вас есть, по сути, три вещи, которые вы можете сделать:
обновите до django-5.2;
скатайте свой собственный
format_html_join
, что не очень сложно:from collections.abc import Mapping from django.utils.html import conditional_escape, format_html, mark_safe def format_html_join(sep, format_string, args_generator): return mark_safe( conditional_escape(sep).join( ( format_html(format_string, **args) if isinstance(args, Mapping) else format_html(format_string, *args) ) for args in args_generator ) )
или вместо этого используйте кортежи:
from operator import itemgetter def version_html(self): return format_html_join( '\n', '<tr>' + '<tr>{}</td>'*4 + '</tr>', map(itemgetter('question', 'answer', 'user', 'timestamp'), self.get_version_history) )