在Django中获取相关对象以及如何将Prefetch与相关模型一起使用
Get related objects in Django and how to use Prefetch with related models
models.py
class Press(models.Model):
created = models.DateTimeField(editable=False)
title = models.CharField(max_length=500, blank=True)
slug = models.SlugField(max_length=600, blank=True, unique=True)
def __str__(self):
return self.title
class Scan(models.Model):
press = models.ForeignKey(Press, related_name='scans', on_delete=models.CASCADE)
created = models.DateTimeField(editable=False)
title = models.CharField(max_length=300, blank=True)
main_scan = models.ImageField(upload_to='press/scans', blank=True)
def __str__(self):
return self.title
views.py
class PressListView(ListView):
model = Press
context_object_name = "press"
template_name = "press.html"
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
context['press'] =
Press.objects.prefetch_related(Prefetch('scans',
queryset=Scan.objects.select_related('press')))
return context
我想在网站前端实现的是列出所有媒体,作为每个媒体的封面图片,我想使用来自 Scan
模型的第一张 main_scan
图片.
我知道我可以将 models.ImageField
添加到 Press
模型,但我不想 - 在管理员中我使用 admin.TabularInline
来自附加到 Press 模型的扫描模型.
我知道有关于预取的文档,但可能我用错了,而且在模板的前端也做错了。
问题是如何让它非常优化,性能最好,只访问数据库一次。
以前我是这样做的并且它有效,但这导致 15 个重复的 SQL 语句用于 15 个对象 - 通过使用 django-toolbar 我正在检查它,它的性能非常低效。
{% for article in press %}
{{ article.title }}<br>
<img src="{{ MEDIA_URL }}{{ article.scans.all.0.main_scan }}" alt="">
{% endfor %}
我的目标是通过预取或其他方式访问数据库一次。下面的代码不起作用,所以 views.py 是错误的,HTML 也是错误的。
{% for article in press %}
{{ article.title }}<br>
<img src="{{ MEDIA_URL }}{{ article.scans.main_image }}" alt="">
{% endfor %}
更新:
15 个副本消失了。 3 使用Prefetch()
优化后的查询
当您使用 Prefetch()
时,您应该分配一个 to_attr
,这是预取对象的 list 可用的属性名称。目前,过多的查询是访问字段 press
的反向关系的结果,因为您分配了 related_name='scans'
.
如果您将属性重命名为 scans_list
.
之类的名称,您将能够访问预取的相关对象
press = Press.objects.prefetch_related(Prefetch(
'scans',
queryset=Scan.objects.select_related('press'),
to_attr='scans_list'
))
然后在您的模板中您将能够:
{% for article in press %}
{{ article.title }}<br>
<img src="{{ MEDIA_URL }}{{ article.scans_list.0.main_scan }}" alt="">
{% endfor %}
models.py
class Press(models.Model):
created = models.DateTimeField(editable=False)
title = models.CharField(max_length=500, blank=True)
slug = models.SlugField(max_length=600, blank=True, unique=True)
def __str__(self):
return self.title
class Scan(models.Model):
press = models.ForeignKey(Press, related_name='scans', on_delete=models.CASCADE)
created = models.DateTimeField(editable=False)
title = models.CharField(max_length=300, blank=True)
main_scan = models.ImageField(upload_to='press/scans', blank=True)
def __str__(self):
return self.title
views.py
class PressListView(ListView):
model = Press
context_object_name = "press"
template_name = "press.html"
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
context['press'] =
Press.objects.prefetch_related(Prefetch('scans',
queryset=Scan.objects.select_related('press')))
return context
我想在网站前端实现的是列出所有媒体,作为每个媒体的封面图片,我想使用来自 Scan
模型的第一张 main_scan
图片.
我知道我可以将 models.ImageField
添加到 Press
模型,但我不想 - 在管理员中我使用 admin.TabularInline
来自附加到 Press 模型的扫描模型.
我知道有关于预取的文档,但可能我用错了,而且在模板的前端也做错了。
问题是如何让它非常优化,性能最好,只访问数据库一次。
以前我是这样做的并且它有效,但这导致 15 个重复的 SQL 语句用于 15 个对象 - 通过使用 django-toolbar 我正在检查它,它的性能非常低效。
{% for article in press %}
{{ article.title }}<br>
<img src="{{ MEDIA_URL }}{{ article.scans.all.0.main_scan }}" alt="">
{% endfor %}
我的目标是通过预取或其他方式访问数据库一次。下面的代码不起作用,所以 views.py 是错误的,HTML 也是错误的。
{% for article in press %}
{{ article.title }}<br>
<img src="{{ MEDIA_URL }}{{ article.scans.main_image }}" alt="">
{% endfor %}
更新:
15 个副本消失了。 3 使用Prefetch()
当您使用 Prefetch()
时,您应该分配一个 to_attr
,这是预取对象的 list 可用的属性名称。目前,过多的查询是访问字段 press
的反向关系的结果,因为您分配了 related_name='scans'
.
如果您将属性重命名为 scans_list
.
press = Press.objects.prefetch_related(Prefetch(
'scans',
queryset=Scan.objects.select_related('press'),
to_attr='scans_list'
))
然后在您的模板中您将能够:
{% for article in press %}
{{ article.title }}<br>
<img src="{{ MEDIA_URL }}{{ article.scans_list.0.main_scan }}" alt="">
{% endfor %}