[Answer]-Using optimized exists() for a set

1👍

✅

exists() is a method of the queryset, not of the model instance. So you need to call filter() instead of get().

And why do you use the antecedentinfo_set intermediate relation? Use the M2M field:

@property
def is_diabetic(self):
    return self.antecedent_tags.filter(name="Diabetes").exists()

0👍

In my Person class, I was thinking of adding a @property like the
below. It works, but if I have to use this on a list of let’s say 250
individual persons, I’m afraid performance wise this is not going to
be the best.

I can think of two approaches that can save you from counting 250 times for a set of 250 persons.

1. Using .extra

I will suppose that you don’t have any custom Managers/QuerySets yet, for a maximum DRY-ness of the code it will be better to place the next lines of code into a custom Manager/QuerySet (It will be still fine to write it into the View, but if you want to reuse this functionality somewhere else you will have to repeat it.).

So we are going to create a custom QuerySet class. If the topic is unfamiliar to you you can read more at Django Managers

class PersonQuerySet(models.QuerySet):

    #this adds a new field "has_"+disease 
    #for every object in our queryset
    def annotate_disease_existence(self, disease):
        has_disease = 'has_'+disease
        return self.extra(select={
            has_disease: "SELECT count(*) \
                FROM someapp_antecedentinfo \
                LEFT JOIN someapp_antecedenttag \
                    ON someapp_antecedenttag.id = someapp_antecedentinfo.antecedent_tag_id
                WHERE someapp_antecedentinfo.person_id = someapp_person.id \
                    AND someapp_antecedenttag.name = '%s" % disease
        })

#now lets use it
class Person(models.Model):
    #...    
    objects = PersonQuerySet.as_manager()

How to use it?

This custom method we wrote gives us the ability to call it against every Person QuerySet. So we can do:

def my_special_view(request):
    my250persons = Person.objects.annotate_disease_existence("Diabetes")[:250]

    for person in my250persons:
        print "{} has Diabetes? {}".format(
            person, 
            'Yes' if person.has_diabetes else 'No'
        )

2. Using .prefetch_related

I hope you already saw what we did in the first example – we used a subquery add an extra bit of information to the Person if he has some illness or not. But a problem arrives when we actually want to use the whole Disease (Tag or Info in our case) objects. Then we can use prefetch_related with custom Prefetch object.

class PersonQuerySet(models.QuerySet):

    def prefetch_disease(self, disease):
        disease_set = disease.lower() + '_set'
        return self.prefetch_related(Prefetch(
            #depending on our needs
            'antecedent_tags', # OR 'antecedentinfo_set'
            queryset = AntecedentTag.objects.filter(name=disease),
            #OR queryset = AntecedentInfo.objects.filter(antecedent_tag__name=disease)
            to_attr = disease_set
        ))

How to use?

When using this approach we are adding a new list of objects to every Person of our queryset. So we can access them like so:

def my_very_special_view(request):
    my250persons = Person.objects.prefetch_disease("Diabetes")[:250]

    for person in my250persons:
        print "{} has Diabetes? {}! More Info: {}".format(
            person, 
            'Yes' if person.diabetes_set else 'No',
            person.diabetes_set[0]
        )

OK it works, but how to use it with single object?

def my_single_person_view(request, id):
    person = get_object_or_404(Person.objects.prefetch_disease("Diabetes"),pk=id)
    #or
    person = get_object_or_404(Person.objects.annotate_disease_existence("Diabetes"),pk=id)

Leave a comment