Django select_related - should I use? - django

I have a model like:
class A(models.Model):
number = models.SmallIntegerField()
class B(models.Model):
a = models.OneToOneField(A)
and I want to do something like that:
b = B.objects.get(pk=1)
b.a.number = 5
b.a.save()
My question is: Should I use .select_related('a') in this case?
b = B.objects.select_related('a').get(pk=1)

Just to summarize: Yes. Without select_related you will have to do two separate database queries (one for getting the b, and one for getting the associated a). With select_related you can get everything in one query.

Related

Django-ORM instead of python for loop

Hello Awesome People!
Such a simple question, sometimes I used to loop through my models with the python for loop, this is not good for the performance of a website.
I have three 3 models:
class A(models.Model):
Bs = ManyToManyField(B)
class B(models.Model):
Cs = ManyToManyField(C)
class C(models.Model):
name = CharField(max_length=100)
If I want to have all the instances of C model related to an instance of A, how will I proceed rather than this python for loop?
all_c = []
for b in a_instance.Bs.all():
for c in b.Cs.all():
all_c.append(c)
You could use prefetch_related https://docs.djangoproject.com/en/2.0/ref/models/querysets/#prefetch-related
all_c = []
for b in a_instance.Bs.all().prefetch_related('Cs'):
for c in b.Cs.all():
all_c.append(c)
But better way will be just filtering on C model
all_c = C.objects.filter(b_set__a_set__in=[a_instance])
# or if you need it to be list and not queryset
all_c = list(C.objects.filter(b_set__a_set__in=[a_instance]))

Django model inheritance and select_related

I have the following in models and run into strange behavior when i use select_related and model inheritance:
Models:
class A(models.Model):
field_fk = models.ForeignKey('C')
class B(A):
fields_b = models.CharField(max_length=255)
class C(models.Model):
field_c = models.CharField(max_length=255)
So A has a foreign key to C and B inherits from A.
Now I want to query A downcast it to B and read the relationship to C. To minimize sql queries I use select_related:
obj = A.objects.select_related('b', 'field_fk).first()
obj = obj.b
print(obj.field_fk) # this prints "C object"
Because I use select_related this should result in just one query. But somehow the information is lost during downcasting and I get to sql queries:
SELECT ••• FROM "base_a" INNER JOIN "base_c" ON
( "base_a"."field_fk_id" = "base_c"."id" ) LEFT OUTER JOIN "base_b" ON
( "base_a"."id" = "base_b"."a_ptr_id" ) ORDER BY "base_a"."id" ASC LIMIT 1
SELECT ••• FROM "base_c" WHERE "base_c"."id" = 1
So in the first query looks fine. But I am surprised that I get a second query.
Is this a bug in django's ORM or am I doing something wrong?
You reassign the obj variable, so what you basically do is this:
print(obj.b.field_fk)
while .b.field_fk is not being selected as a related object. So either add that into select_related, or reuse the pre-fetched object before reassigning the obj variable
As mentioned I submitted a ticket at django-project.
https://code.djangoproject.com/ticket/25173
This is now considered as a bug and will hopefully be fixed soon.
A suggested workaround is:
obj = obj.b
print (obj.a_ptr.field_fk)

filter with select_related on Django

I have a problem with the usage of select_related feature of Django with the filter operation, here's my problem, I have three classes :
class A:
# various fields here
class B(models.model):
related_A = models.ForeignKey(A)
related_C = models.ForeignKey(C)
attribute1 = models.CharField(..)
# Other attributes
class C(models.model):
# Attributes
What I'm trying to do is, getting class A by filtering on class B on the key related_C according to another parameter attribute1 (from class B).
To illustrate it properly, I have a function get_class_A(self) in my class C
get_class_A(self,param):
classes_B = B.objects.filter(related_C = self,attribute1 = param)
It returns a QuerySet of classes B. What I want to do is to follow the ForeignKey pointing to A in order to transform this QuerySet of B into a list of objects A.
I tried various things such as :
classes_A = B.objects.select_related('A').filter(related_C = self, attribute1 = param)
and some variations but nothing worked. Does anyone knows how to do this ?
Thanks
def get_class_A(self, param):
return A.objects.filter(b__related_c=self, b__attribute1=param).distinct()
What you've described looks a lot like a ManyToMany relationship between A and C. If you declare it as such, and include your extra attributes by specifying B as a through model, Django will create the relationship between A and C for you.
Also, select_related() has nothing to do with filtering results, it's just a tool that can allow you to reduce the number of database queries. From the docs:
This is a performance booster which results in a single more complex query but means later use of foreign-key relationships won’t require database queries.

Django: Is it possible to retrieve a set of related objects from multiple nested levels?

Say I have the following models:
class A(models.Model):
pass
class B(models.Model):
a = models.ForeignKey(A, related_name='bs')
If I want to retrieve the set of B's that a specific A has, I do the following:
a = A.objects.get(pk=[whatever])
a.bs.all()
Now, If I add the following model:
class C(models.Model):
b = models.ForeignKey(B, related_name='cs')
How can I get all Cs that a specific A has? I tried:
a = A.objects.get(pk=[whatever])
a.bs.cs.all()
But it doesn't work. Is this possible? If it is, how can I achieve it?
You can do:
cs = C.objects.filter(b__a__pk=[whatever])
You can do it like this:
[b.cs.all() for b in A.objects.get(pk=[whatever]).bs.all()]
Or the more appropriate manner, through the C object like this:
C.objects.filter(b__a__pk=[whatever])

How to get object from manytomany?

I have models:
class Z(models.Model):
name = ...
class B(model.Model):
something = model...
other = models.ForeignKey(Z)
class A(models.Model):
date = model.DateTimeField()
objs_b = models.ManyToManyField(B)
def get_obj_b(self,z_id):
self.obj_b = self.objs_b.get(other=z_id)
and query:
qs = A.objects.filter(...)
but if I want get object B related to A I must call get_obj_b:
for item in gs:
item.get_obj_b(my_known_z_id)
It was generate many queries. How to do it simple? I can not change models, and generally I must use filter (not my own manager) function.
If you are using Django 1.4, I would suggest that you use prefetch_related like this:
A.objects.all().prefetch_related('objs_b__other')
This would minimize the number of queries to 2: one for model A, and one for 'objs_b' joined with 'other'
And you can combine it with a filter suggested by pastylegs:
A.objects.filter(objs_b__other__id=z_id).prefetch_related('objs_b__other')
For details see: https://docs.djangoproject.com/en/1.4/ref/models/querysets/#prefetch-related