Reputation: 1189
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
What I would like to achieve on the front-end of the site is to list all Press and as the cover image for each would like to use 1st main_scan
image from the Scan
model.
I know I can add models.ImageField
to Press
model, but I do not want to - in the admin I am using admin.TabularInline
from Scan model that is attached to Press model.
I know there is documentation on prefetch but probably I am using it wrong and as well doing it wrong on the front-end in the template.
Question is how to make it very optimized, that the performance is best, hitting the Database just once.
Previously I was doing it this way and it worked, but this is causing 15 duplicated SQL statements for 15 objects - by using django-toolbar I am checking it which is highly inefficient as performance.
{% for article in press %}
{{ article.title }}<br>
<img src="{{ MEDIA_URL }}{{ article.scans.all.0.main_scan }}" alt="">
{% endfor %}
My goal is to hit DB once by prefetch or something. This code below does not work so views.py is wrong and HTML as well.
{% for article in press %}
{{ article.title }}<br>
<img src="{{ MEDIA_URL }}{{ article.scans.main_image }}" alt="">
{% endfor %}
UPDATE:
15 duplicates gone. 3 Queries after the optimization by using Prefetch()
Upvotes: 0
Views: 2672
Reputation: 234
When you use Prefetch()
you should assign a to_attr
which is the attribute name that the list of prefetched objects will be available under. Currently the excess queries are a result of accessing the reverse relationship for the field press
because you assigned related_name='scans'
.
You'll be able to access the prefetched related objects if you rename the attribute to something like scans_list
.
press = Press.objects.prefetch_related(Prefetch(
'scans',
queryset=Scan.objects.select_related('press'),
to_attr='scans_list'
))
Then in your template you will be able to do:
{% for article in press %}
{{ article.title }}<br>
<img src="{{ MEDIA_URL }}{{ article.scans_list.0.main_scan }}" alt="">
{% endfor %}
Upvotes: 3