1

我有一个数据结构,其中 aDocument有很多Blocks只有一个ParagraphHeader. 一个简化的实现:

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})
\documentclass[a4paper,10pt]{report}
\begin{document}
  {% for block in chapter.block_set.all() %}
    {% if block.content_block_type.name == 'header' %}
      \section{ {{- block.content_block.latex_text -}} }
    {% elif block.content_block_type.name == 'paragraph' %}
      {{ block.content_block.latex_text }}
    {% endif %}
  {% endfor %}
\end{document}

content_block.latex_text()是将 HTML 字符串转换为 Latex 字符串的函数)

因此我想预取script.blocksblocks.content_block. 我知道在 Django 中有两种预取方法:

  1. select_related()执行JOIN查询,但仅适用于ForeignKeys. 它适用于script.blocks但不适用于blocks.content_block.

  2. prefetch_related()也可与 GenericForeignKeys 一起使用,但如果我正确理解文档,它一次只能获取一个ContentType,而我有两个。

有什么方法可以在这里执行必要的预取吗?谢谢您的帮助。

4

2 回答 2

0

不是一个优雅的解决方案,但您可以尝试使用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}
于 2021-10-28T05:00:09.627 回答
0

我的不好,我没有注意到文档是一个 FK,并且反向 FK 不能与select_related.

首先,我建议related_name="blocks"无论如何添加。

预取时,您可以传递查询集。但是你不应该通过过滤器doc_id,Django的ORM会自动添加它。

如果您通过查询集,您还可以在那里添加选择/预取相关调用。

blocks_qs = Block.objects.all().prefetch_related('content_block')
doc_prefetched = Document.objects.prefetch_related(
    Prefetch('blocks', queryset=blocks_qs)
  ).get(uuid=doc_uuid)

但是,如果您不需要额外的过滤器或注释,则更简单的语法可能适合您

document = (
 Document.objects
  .prefecth_related('blocks', 'blocks__content_block')
  .get(uuid=doc_uuid)
)
于 2021-10-27T12:57:44.193 回答