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)