Radek
Radek

Reputation: 1189

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

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()

Queries after optimization

Upvotes: 0

Views: 2672

Answers (1)

bromosapien
bromosapien

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

Related Questions