92

I have the following Django model:

class Make:
   name = models.CharField(max_length=200)

class MakeContent:
   make = models.ForeignKey(Make)
   published = models.BooleanField()

I'd like to know if it's possible (without writing SQL directly) for me to generate a queryset that contains all Makes and each one's related MakeContents where published = True.

1
  • Could you be more specific about your question?
    – endre
    Mar 25, 2011 at 20:08

5 Answers 5

75

Yes, I think you want

make = Make.objects.get(pk=1)
make.make_content_set.filter(published=True)

or maybe

make_ids = MakeContent.objects.filter(published=True).values_list('make_id', flat=True)
makes = Make.objects.filter(id__in=make_ids)
4
  • 2
    Your first code snippet doesn't work. It gets all MakeContents for one make, where MakeContents for all Makes is needed. _set works for a single object but not for a queryset.
    – knite
    Sep 7, 2012 at 0:18
  • What's the point of adding flat = True ? The ids will already be unique by definition, and ensuring again that they are unique might require some additional computation.
    – pintoch
    Sep 27, 2014 at 19:29
  • 2
    pintoch, flat=True does not offer anything related to uniqueness. It only causes the return of single values, rather than tuples when only a single field is requested.
    – Rob
    Apr 16, 2019 at 13:57
  • 1
    I believe make.make_content_set should be make.makecontent_set in the newer versions of Django (I'm using 2.2). Apr 23, 2019 at 20:02
55

I know this is very old question, but I am answering. As I think my answer can help others. I have changed the model a bit as follows. I have used Django 1.8.

class Make(models.Model):
    name = models.CharField(max_length=200)

class MakeContent(models.Model):
    make = models.ForeignKey(Make, related_name='makecontent')
    published = models.BooleanField()

I have used the following queryset.

Make.objects.filter(makecontent__published=True)

You should use distinct() to avoid the duplicate result.

Make.objects.filter(makecontent__published=True).distinct()

I hope it will help.

2
  • 8
    when there are multiple MakeContent pointing to the same Make, this will duplicate the Make entries. Something like select from make right join makecontent on make.id=makecontent.make_id with multiple rows in MakeContent having the same make_id
    – Shadi
    Sep 5, 2018 at 12:41
  • 9
    You can use Make.objects.filter(makecontent__published=True).distinct() Sep 6, 2018 at 13:10
20

Django doesn't support the select_related() method for reverse foreign key lookups, so the best you can do without leaving Python is two database queries. The first is to grab all the Makes that contain MakeContents where published = True, and the second is to grab all the MakeContents where published = True. You then have to loop through and arrange the data how you want it. Here's a good article about how to do this:

http://blog.roseman.org.uk/2010/01/11/django-patterns-part-2-efficient-reverse-lookups/

1
  • 4
    see the prefetch_related() method to have it streamline the two queries you mention.
    – B Robster
    Mar 19, 2013 at 17:19
18

Let me translate Spike's worded answer into codes for future viewers. Please note that each 'Make' can have zero to multiple 'MakeContent'

If the asker means to query 'Make' with AT LEAST ONE 'MakeContent' whose published=True, then Jason Christa's 2nd snippet answers the question.

The snippet is equivalent to

makes = Make.objects.select_related().filter(makecontent__published=True).distinct()

But if the asker means to query 'Make' with ALL 'MakeContent' whose published=True, then following the 'makes' above,

import operator
make_ids = [m.id for m in makes if 
    reduce(operator.and_, [c.published for c in m.makecontent_set.all()] ) 
]
makes_query = Make.objects.filter(id__in=make_ids)

contains the desired query.

3
  • I think only this answer stick to the question
    – lengxuehx
    Apr 14, 2018 at 12:41
  • This is what I want, but I'm wondering if there's a performance penalty for selecting all then distinct()
    – MiDaa
    Aug 2, 2018 at 8:33
  • This filters m.makecontent_set in code (not to mention fully formed objects) instead of the database, which is an extremely extremely extremely bad practice. You should basically never do this.
    – kevlar
    Feb 12 at 23:27
3

One more time, it is not clear what was the question, but if it was desired that all related MakeContent objects must have been published this can work:

Make.objects.exclude(MakeContent_set__published=False)

And if at least one of them (as it was in other answers):

Make.objects.filter(MakeContent_set__published=True)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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