Search code examples
pythondjangodjango-modelsdjango-querysetdjango-q

A puzzle concerning Q objects and Foreign Keys


I've got a model like this:

class Thing(models.Model):
    property1 = models.IntegerField()
    property2 = models.IntegerField()
    property3 = models.IntegerField()

class Subthing(models.Model):
    subproperty = models.IntegerField()
    thing = modelsForeignkey(Thing)
    main = models.BooleanField()

I've got a function that is passed a list of filters where each filter is of the form {'type':something, 'value':x}. This function needs to return a set of results ANDing all the filters together:

final_q = Q()
for filter in filters:
        q = None
        if filter['type'] =='thing-property1':
            q = Q(property1=filter['value'])
        elif filter['type'] =='thing-property2':
            q = Q(property2=filter['value'])
        elif filter['type'] =='thing-property2':
            q = Q(property3=filter['value'])
        if q:
            final_q = final_q & q
return Thing.objects.filter(final_q).distinct()

Each Subthing has a Boolean property 'main'. Every Thing has 1 and only 1 Subthing where main==True.

I now need to add filter that returns all the Things which have a Subthing where main==True and subproperty==filter['value']

Can I do this as part of the Q object I'm constructing? If not how else? The queryset I get before my new filter can be quite large so I would like a method that doesn't involve looping over the results.


Solution

  • It's a bit easier to understand if you explicitly give your Subthings a "related_name" in their relationship to the Thing

    class Subthing(models.Model):
        ...
        thing = models.ForeignKey(Thing, related_name='subthings')
        ...
    

    Now, you use Django join syntax to build your Q object:

    Q(subthings__main=True) & Q(subthings__subproperty=filter['value'])
    

    The reverse relationship has the default name 'subthing_set', but I find that it's easier to follow if you give it a better name like 'subthings'.