Search code examples
djangodjango-select-related

How to select a specific related element in Django?


I have a model called 'Projects'.

Projects can be featured. If they are, they will be displayed on the home page (Projects.filter(is_featured=True))

Each project contains several slides. These slides can be featured (Slide.is_featured=True), and contain an Image model which contains the actual image.

In my home page, I would like to display a slideshow in which each slide contains the project name, and the image contained in the featured slide.

I did that by adding a method called 'featured_slide()' in my Project Model, but now I realize that I've been hitting on the DB every time, and I would like to improve that by using the 'select_related' statement.

How can I do that?

Optimally, I would like to have a field like 'featured_slide' to represent the featured slide.

I was thinking of doing something along these lines:

Projects.filter(is_featured=True).annotate(featured_slide='slides__is_featured=True').select_related(slides__image)

I know it can't be that simple (slides__is_featured is not a database field), but you get the idea.


Solution

  • If you want a slideshow of only those Slides that themselves are featured as well as being related to a featured Projects:

    class Project(...):
        name = models.CharField()
        is_featured = models.BooleanField()
    
    class Slide(...):
        project = models.ForeignKey(Project)
        is_featured = models.BooleanField()
        image = models.ImageField()
    

    to query the slides (using select_related to avoid unnecessary queries):

    slides = Slide.select_related("project").filter(is_featured=True, project__is_featured=True)
    

    and template:

    <ul>
        {% for slide in slides %}
             <li><img src="{{ slide.image.url }} /><span class="caption">{{ slide.project.name }}</caption></li>
        {% endfor %}
    </ul>
    

    EDIT:

    If you want to lookup the reverse relationship (i.e. get all the slides for a project), by default you can do the following:

    project = Project.objects.get(...)
    project_slides = project.slide_set.all()
    

    You add _set to the model name. You can make this more intuitive by adding a related_name attribute to the relationship:

    class Slide(...):
        project = models.ForeignKey(Project, related_name="slideshow_slides")
    

    and now use:

    project = Project.objects.get(...)
    project.slideshow_slides.all()