Использование свойства 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-doc]:

format_html_join() теперь поддерживается использование итераций отображений, передающих их содержимое в качестве аргументов ключевых слов в format_html().

До , можно было использовать только повторяющиеся элементы, такие как кортежи или списки, что усложняло правильное форматирование. Мы можем видеть это в исходном коде [GitHub]:

return mark_safe(
    conditional_escape(sep).join(
        format_html(format_string, *args) for args in args_generator
    )
)

Итак, у вас есть, по сути, три вещи, которые вы можете сделать:

  1. обновите до ;

  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
            )
        )
    
  3. или вместо этого используйте кортежи:

    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)
         )
    
Вернуться на верх