How to chain select_related functions in Django? - 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

Related

Django - MongoDB - referring to wrong model

I have 3 models, A, B and C.
Model C have 2 reference fields, one is pointing to A and another to B. There are 3 update APIs, for each of the model tables.
There is a 4th API that will retrieve data from table C and related data in table A and B.
Sometimes during fetching the data in 4th API, we face issue where the variable pointing to model B is said to be pointing to model A and hence fail to get the field which is in B and not in A. This sometimes happen for variable pointing to model A as well.
This happen on and off and it get resolved on its own.
I am not sure where to start looking for the issue.
Can someone please help me?
EDIT 1:
Here is the basic model example
class A(models.Model):
short_name = fields.StringField()
class B(models.Model):
description = fields.StringField()
class C(models.Model):
name = fields.StringField()
short_name_id = fields.ReferenceField(A)
desc_id = fields.ReferenceField(B)

Multiple object types references in Django

We are currently running with the following configuration to avoid other issues.
So for the question: let's assume that this is a must and we can not change the Models part.
At the beginning we had the following models:
class A(Model):
b = ForeignKey(B)
... set of fields ...
class B(Model):
...
Then we added something like this:
class AVer2(Model):
b = ForeignKey(B)
... ANOTHER set of fields ...
Assuming an object of type B can only be referenced by either A or AVer2 but never both:
Is there a way to run a query on B that will return, at runtime, the correct object type that references it, in the query result (and the query has both types in it)?
You can assume that an object of type B holds the information regarding who's referencing it.
I am trying to avoid costly whole-system code changes for this.
EDIT:
Apparently, my question was not clear. So I will try to explain it better. The answers I got were great but apparently I missed a key point in my question so here it is. Assuming I have the model B from above, and I get some objects:
b_filter = B.objects.filter(some_of_them_have_this_true=True)
Now, I want to get a field that is in both A and AVer2 with one filter into one values list. So for example, I want to get a field named "MyVal" (both A and AVer2 have it) I don't care what is the actual type. So I want to write something like:
b_filter.values(['a__myval', 'aver2__myval'])
and get something like the following in return: [{'myval': }]
Instead, I currently get [{'a__myval': , 'aver2__myval': None}]
I hope it is clearer.
Thanks!
Short answer: You can not make your exact need.
Long answer: The first thing that came to my mind when I read your question is Content Types, Generic Foreign Keys and Generic Relations
Whether you will use "normal" foreign keys or "generic foreign keys" (combined with Generic Relation), Your B instances will have both A field and AVer2 field and this natural thing make life easier and make your goal (B instance has a single Field that may be A or Avr2) unreachable. And here you should also override the B model save method to force it to have only the A field and the Avr2 to be None or A to be None and Avr2 to be used. And if you do so, don't forget to add null=True, blank=True to A and Avr2 foreign key fields.
On the other hand, the opposite of your schema makes your goal reachable:
B model references A and Avr2 that means that B model has ONE generic foreign key to both A and Avr2 like this: (this code is with Django 1.8, for Django 1.9 or higher the import of GenericRelation, GenericForeignKey has changed)
from django.db import models
from django.contrib.contenttypes.generic import GenericRelation, GenericForeignKey
from django.contrib.contenttypes.models import ContentType
class B(models.Model):
# Some of your fields here...
content_type = models.ForeignKey(ContentType, null=True, blank=True)
object_id = models.PositiveIntegerField(null=True, blank=True)
# Generic relational field will be associed to diffrent models like A or Avr2
content_object = GenericForeignKey('content_type', 'object_id')
class A(models.Model):
# Some of your fields here...
the_common_field = models.BooleanField()
bbb = GenericRelation(B, related_query_name="a") # since it is a foreign key, this may be one or many objects refernced (One-To-Many)
class Avr2(models.Model):
# Some of your fields here...
the_common_field = models.BooleanField()
bbb = GenericRelation(B, related_query_name="avr2") # since it is a foreign key, this may be one or many objects refernced (One-To-Many)
Now both A and Avr2 have "bbb" field which is a B instance.
a = A(some fields initializations)
a.save()
b = B(some fields initializations)
b.save()
a.bbb = [b]
a.save()
Now you can do a.bbb and you get the B instances
And get the A or Avr2 out of b like this:
b.content_object # which will return an `A object` or an `Avr2 object`
Now let's return to your goals:
Is there a way to run a query on B that will return, at runtime, the correct object type that references it, in the query result (and the query has both types in it)?
Yes: like this:
B.objects.get(id=1).content_type # will return A or Avr2
You wanna perform something like this: b_filter = B.objects.filter(some_of_them_have_this_true=True) :
from django.db.models import Q
filter = Q(a__common_field=True) | Q(avr2__common_field=True)
B.objects.filter(filter)
Getting [{'a__myval': , 'aver2__myval': None}] is 100% normal since values is asked to provide two fields values. One way to overcome this, is by getting two clean queries and then chain them together like so:
from itertools import chain
c1 = B.objects.filter(content_type__model='a').values('a__common_field')
c2 = B.objects.filter(content_type__model='avr2').values('avr2__common_field')
result_list = list(chain(c1, c2))
Please notice that when we added related_query_name to the generic relation, a and avr2 has become accessible from B instances, which is not the default case.
And voilà ! I hope this helps !
I'm not sure what do you want to get in query set.
I assumed that you want set of "correct object types" that "has both types in it", so in fact you want set of related class types (like [<class 'main.models.A'>, <class 'main.models.A2'>]). If that is not the case, I can change answer after more specific details in comments.
This is solution for that "class list", you can use it to get what you precisely want.
# Our custom QuerySet that with function that returns list of classes related to given B objects
class CustomQuerySet(models.QuerySet):
def get_types(self, *args, **kwargs):
all_queryset = self.all()
return [b.get_a() for b in all_queryset]
# Our custom manager - we make sure we get CustomQuerySet, not QuerySet
class TypesManager(models.Manager):
def get_queryset(self, *args, **kwargs):
return CustomQuerySet(self.model)
class B(models.Model):
# some fields
# Managers
objects = models.Manager()
a_types_objects = TypesManager()
# Get proper A "type"
def get_a(self):
if self.a_set.all() and self.a2_set.all():
raise Exception('B object is related to A and A2 at the same time!')
elif self.a_set.all():
return A
elif self.a2_set.all():
return A2
return None
class A(models.Model):
b = models.ForeignKey(
B
)
class A2(models.Model):
b = models.ForeignKey(
B
)
And now you can use it like this:
>>> from main.models import *
>>> B.a_types_objects.all()
<CustomQuerySet [<B: B object>, <B: B object>]>
>>> B.a_types_objects.all().get_types()
[<class 'main.models.A'>, <class 'main.models.A2'>]
>>> B.a_types_objects.filter(id=1)
<CustomQuerySet [<B: B object>]>
>>> B.a_types_objects.filter(id=1).get_types()
[<class 'main.models.A'>]
Using a_types_objects works like normal objects, but it returns CustomQuerySet, which has extra function returning list of class.
EDIT:
If you worrying about changing a lot of B.objects.(...) into B.a_types_objects.(...) you could just set your main manager to TypesManager like that:
class B(models.Model):
# some fields
# Override manager
objects = TypesManager()
Rest of your code will remain intact, but from now on you will use CustomQuerySet instead of QuerySet - still, nothing really changes.

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)

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.

Django: How to avoid reassigning ForeignKey on every save?

I want to save the A object and B object. A has a foreignkey to B. B has a OneToOneField to A. Is there a way I can get around having to reassign the variables between each save? or perhaps there's a better way to do it?
# The models a and b were created earlier in code
# a.b = b Was already set earlier in code
# b.a = a Was already set earlier in code
with transaction.commit_on_success():
a.save() # pk created for a
b.a = a # attach a.pk to b
b.save() # pk created for b
a.b = b # attach b.pk to a
a.save() # re-save a to database
"A has a foreignkey to B. B has a OneToOneField to A." I don't think this is the way to set up your relationships. You can just set B as a ForeignKey of A and then you can access A from B using B.a_set.all().
To paraphrase from here, with the following model definitions, Book is one-to-many (ForeignKey) with Publisher and many-to-many with Author. (The ~~~~ just means other stuff not related to the relationships between the models)
class Publisher(~~~~):
~~~~
class Author(~~~~):
~~~~
class Book(~~~~):
publisher = models.ForeignKey(Publisher)
authors = models.ManyToManyField(Author)
Then you can access publisher and author from a given book like so:
b = Book.objects.get(id=50)
publisher1 = b.publisher # publisher object for a given book
author_set = b.authors.all() # query set of authors for a given book
and you can do the reverse relationship (which isn't explicitly defined in the models above).
p = Publisher.objects.get(name='Apress Publishing')
p.book_set.all() # query set of books for a given publisher
publisher1.book_set.all() # set of books for the publisher from above.
a = Author.objects.get(name="JK Rowling")
a.book_set.all() # query set of books for a given author
Any time you update an object field you need to call save() to save it in the database, but you shouldn't need to reassign ForeignKey or any other field before doing so. If that wasn't working for you, maybe it was because of the funky relationship between the models. I'm not 100% sure, but I think the only time an object gets a (new) primary key is the first time it's saved to the database, and then that's its first pk.