Django model inheritance and select_related - django

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)

Related

Django doesn't respect Prefetch filters in annotate

class Subject(models.Model):
...
students = models.ManyToMany('Student')
type = models.CharField(max_length=100)
class Student(models.Model):
class = models.IntergerField()
dropped = models.BooleanField()
...
subjects_with_dropouts = (
Subject.objects.filter(category=Subject.STEM).
prefetch_related(
Prefetch('students', queryset=Students.objects.filter(class=2020))
.annotate(dropped_out=Case(
When(
students__dropped=True,
then=True,
),
output_field=BooleanField(),
default=False,
))
.filter(dropped_out=True)
)
I am trying to get all Subjects from category STEM, that have dropouts of class 2020, but for some reason I get Subjects that have dropouts from other classes as well.
I know that I can achive with
subjects_with_dropouts = Subject.objects.filter(
category=Subject.STEM,
students__dropped=True,
students__class=2020,
)
But why 1st approach doesn't work? I am using PostgreSQL.
When using prefetch, the joining is done in python. A good way to think of this is that you have two tables in the first query. One of subjects with at least one student who dropped out (note that you are doing an aggregate there (Case) so there is a JOIN with a GROUP BY on student.id), and one of students in class of 2020 (this is separate than the join in the first table). The prefetch just says to join these two separate queries using the through table that contains both of their ids representing a connection that is auto generated by ManyToManyField.
A good way to see what is actually happening is by using print(QuerySet.query) where QuerySet is the instance of the QuerySet (Subject.objects.all()). Or if you have the means, django debug toolbar is a fantastic tool that shows you the EXPLAIN statement of each query in each endpoint.

How to chain select_related functions in Django?

I have the following tables in my database:
class A(models.model):
...
class B(models.model):
a = models.ForeignKey(A)
class C(models.model):
b = models.ForeignKey(B)
data = models.TextField(max_length=50)
What I want to do is get the C object with a pk of 215, and select the related B object, and also select the related A object of the B object. Right now, what I am doing is this:
c = Models.objects.select_related('b').select_related('a').get(pk=215)
However, I get the following error:
django.core.exceptions.FieldError: Invalid field name(s) given in select_related: 'a'. Choices are: b
Is this possible to actually get all 3 objects with just one database hit? Thanks for any answers.
Try
c = Models.objects.select_related('b__a').get(pk=215)
Note the double underscore.
See the section starting with You can follow foreign keys... under https://docs.djangoproject.com/en/2.2/ref/models/querysets/#django.db.models.query.QuerySet.select_related

Django select_related - should I use?

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.

How can I order by ForeignKey field itself in django without join?

So I have a model MyModel with a ForeignKey field fkfield. And i need to do something like this (simplified):
MyModel.objects.values_list('id', 'fkfield').order_by('fkfield')
For example I want to groupby them further by fkfield so I need my objects to be sorted by this field. And the only thing I will use later is fkfield_id. I mean I dont need any data from related model.
But django performs a join sql query (as described in docs) and uses related model's ordering. The same happens if i explicitly try to order by id:
MyModel.objects.values_list('id', 'fkfield').order_by('fkfield__id')
and I get:
SELECT `mymodel`.`id`,
`mymodel`.`fkfield_id`
FROM `mymodel`
LEFT OUTER JOIN `related_table`
ON ( `mymodel`.`fkfield_id` = `related_table`.`id` )
ORDER BY
`related_table`.`id` ASC
What i really expect is:
SELECT `id`,
`fkfield_id`
FROM `mymodel`
ORDER BY
`fkfield_id` ASC
But I can't find a way to do it. .order_by('fkfield_id') raises exception that says that there is no such a field.
I managed to get things work using extra but I can't understand why such a simple and obvious behaviour can't be used without hacks. Or maybe i missed smth?
UPDATE: models.py
class Producer(models.Model):
name = models.CharField(max_length=100)
class Meta:
ordering = ('name',)
class Collection(models.Model):
name = models.CharField(max_length=100)
producer = models.ForeignKey('Producer')
class Meta:
ordering = ('name',)
print Collection.objects.values_list('producer', 'id').order_by('producer').query
>>> SELECT `catalog_collection`.`producer_id`, `catalog_collection`.`id`
>>> FROM `catalog_collection`
>>> INNER JOIN `catalog_producer` ON
>>> (`catalog_collection`.`producer_id` = `catalog_producer`.`id`)
>>> ORDER BY `catalog_producer`.`name` ASC
Try
.order_by('fkfield')
My query is
Post.objects.values('author', 'id').order_by('author')
as sql:
SELECT "blogs_post"."author_id",
"blogs_post"."id"
FROM "blogs_post"
ORDER BY "blogs_post"."author_id" ASC
UPDATE
Kind of messy solution:
MyModel.objects.extra(select={'fkfield_id': 'fkfield_id'})\
.values_list('id', 'fkfield_id')\
.order_by('fkfield_id')

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