4
\$\begingroup\$

I have the following Django models setup:

class Publication(models.Model):
    value=models.CharField(max_length=255, unique=True)

class Article(models.Model):
    filename = models.CharField(max_length=255)
    publication = models.ForeignKey(Publication, on_delete=models.CASCADE)

class Image(models.Model):
    name = models.CharField(max_length=255)
    articles = models.ManyToManyField(Article)

I wanted to add an 'images' property to the Publication model. I have implemented it like this

@property
def images(self):
    images = []
    for article in self.article_set.all():
        images += list(article.images.all())
    return list(set(images))

This works. I also considered trying something like

return Images.objects.filter(articles__publication=self)

But I don't think that I can do this because then I have to define Images above Publications, which won't work with Articles and Images.

Is there a way for me to improve this query to handle less of the filtering in application logic and more of it in the database?

\$\endgroup\$
  • 4
    \$\begingroup\$ Please edit your question so that the title describes the purpose of the code, rather than its mechanism. We really need to understand the motivational context to give good reviews. Thanks! \$\endgroup\$ – Toby Speight Dec 2 '19 at 15:11
2
\$\begingroup\$

I also considered trying something like

return Images.objects.filter(articles__publication=self) 

But I don't think that I can do this because then I have to define Images above Publications, which won't work with Articles and Images.

Did you try this? It seems like it would work and would be the best approach. You could add a .distinct() clause at the end of the query to avoid converting to a set.

It is best practice to set the related_name and related_query_name variables on your model related fields. This will make your queries less verbose; article_set and image_set become articles and images, respectively. See example below

class Article(models.Model):
    filename = models.CharField(max_length=255)
    publication = models.ForeignKey(to=Publication,
                                    on_delete=models.CASCADE,
                                    related_name='articles',
                                    related_query_name='article')


class Image(models.Model):
    name = models.CharField(max_length=255)
    articles = models.ManyToManyField(to=Article,
                                      related_name='images',
                                      related_query_name='image')

Look at the prefetch_related documentation. You are querying the db for every image set on each article.

@property
def images(self):
    images = []
    for article in self.article_set.all():
        images += list(article.images.all())
    return list(set(images))

Using prefetch_related:

@property
def images(self):
    images = []
    publication = self.objects.prefetch_related('articles__images')
    for article in publication.articles.all():
        for image in article.images.all():
            images.append(image)

    return set(images)
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.