Предварительная выборка модели с помощью GenericForeignKey

У меня есть структура данных, в которой Document имеет множество Blocks, которые имеют ровно один Paragraph или Header. Упрощенная реализация:

class Document(models.Model):
  title = models.CharField()

class Block(models.Model):
  document = models.ForeignKey(to=Document)
    content_block_type = models.ForeignKey(to=ContentType)
    content_block_id = models.CharField()
    content_block = GenericForeignKey(
        ct_field="content_block_type",
        fk_field="content_block_id",
    )

class Paragraph(models.Model):
  text = models.TextField()

class Header(models.Model):
  text = models.TextField()
  level = models.SmallPositiveIntegerField()

(Обратите внимание, что существует реальная необходимость иметь Paragraph и Header в отдельных моделях, в отличие от реализации выше)

Я использую jinja2 для шаблонирования Latex-файла для документа. Шаблонирование происходит медленно, поскольку jinja выполняет новый запрос к базе данных для каждого блока, параграфа или заголовка.

template = get_template(template_name="latex_templates/document.tex", using="tex")
return template.render(context={'script': self.script})

Таким образом, я хотел бы выполнить предварительную выборку script.blocks и blocks.content_block. Я понимаю, что в Django есть два метода для предварительной выборки:

  1. select_related() performs a JOIN query but only works on ForeignKeys. It would work for script.blocks but not for blocks.content_block.

  2. prefetch_related() works with GenericForeignKeys as well, but if I understand the docs correctly, it can only fetch one ContentType at a time while I have two.

Есть ли способ выполнить необходимую предварительную выборку здесь? Спасибо за помощь.

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

Пытались ли вы

document = (
 Document.objects
  .select_related('blocks')
  .prefecth_related('blocks__content_block')
)

Вызывает ли он какие-либо ошибки? Не могли бы вы обновить ваш код с кодом представления?

Не совсем элегантное решение, но вы можете попробовать использовать reverse generic relations:

from django.contrib.contenttypes.fields import GenericRelation


class Paragraph(models.Model):
  text = models.TextField()
  blocks = GenericRelation(Block, related_query_name='paragraph')

class Header(models.Model):
  text = models.TextField()
  level = models.SmallPositiveIntegerField()
  blocks = GenericRelation(Block, related_query_name='header')

и префетч на этом:

Document.objects.prefetch_related('block_set__header', 'block_set__paragraph')

затем измените рендеринг шаблона на что-то вроде (не проверено, попробую проверить позже):

\documentclass[a4paper,10pt]{report}
\begin{document}
  {% for block in chapter.block_set.all %}
    {% if block.header %}
      \section{ {{- block.header.0.latex_text -}} }
    {% elif block.paragraph %}
      {{ block.paragraph.0.latex_text }}
    {% endif %}
  {% endfor %}
\end{document}
Вернуться на верх