filter with select_related on Django - 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.

Related

How to use select_related with Django create()?

class Parent(models.Model):
# some fields
class Child(models.Model):
parent = models.ForeginKey(Parent)
species = models.ForeignKey(Species)
# other fields
I have a function like this:
1. def some_function(unique_id):
2. parent_object = Parent.objects.get(unique_id=unique_id)
3. new_child = Child.objects.create(name='Joe', parent=parent_object)
4. call_some_func(new_child.parent.name, new_child.species.name)
In the line 4, a db query is generated for Species. Is there any way, I can use select_related to prefetch the Species, so as to prevent extra query.
Can it be done while I use .create(). This is just an example , I am using many other fields too and they are querying the DB every time.
The only way I can think is after line 3 using this code:
child_obj = Child.objects.select_related('species').get(id=new_child.id)
The only parameter that create accepts is force_insert which is not related to what you're asking, so it seems it's not possible. Also, noticing that create performs an INSERT ... RETURNING ... statement, I don't think it would be possible anyway because you cannot return columns from other tables.
Possibly the best approach is what you already suggested: do a get() afterwards with the related fields you need.

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 Query. Correct use of objects.select_related()

I have this models:
A = class(models.Model):
onefield = ...
B = class(models.Model):
property = models.CharField(...)
foreign = models.ForeignKey(A)
So, I want to get all the objects A that are ForeignKeys of objects B with property = x.
I tried this:
query = B.objects.filter(property=x).select_related('A')
But it doesn't work. Is it possible to do this with select_related()?
Although I hesitate to contradict the illustrious Alex, and he's technically correct (the best kind of correct, after all), select_related is not the answer here. Using that never gives you different objects as a result; it only improves the efficiency of subsequently accessing related objects.
To get As, you need to start your query from A. You can use the double-underscore syntax to filter on the related property. So:
query = A.objects.filter(b__property=x)
You need to write .select_related('foreign'). select_related takes a field name, not a class name.

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

Quering distinct values throught related model

I have a simple one-to-many (models.ForeignKey) relationship between two of my model classes:
class TeacherAssignment(models.Model):
# ... some fields
year = models.CharField(max_length=4)
class LessonPlan(models.Model):
teacher_assignment = models.ForeignKey(TeacherAssignment)
# ... other fields
I'd like to query my database to get the set of distinct years of TeacherAssignments related to at least one LessonPlan. I'm able to get this set using Django query API if I ignore the relation to LessonPlan:
class TeacherAssignment(models.Model):
# ... model's fields
def get_years(self):
year_values = self.objects.all().values_list('year').distinct().order_by('-year')
return [yv[0] for yv in year_values if len(yv[0]) == 4]
Unfortunately I don't know how to express the condition that the TeacherAssignment must be related to at least one LessonPlan. Any ideas how I'd be able to write the query?
Thanks in advance.
It is recommended to use the ModelManager for table-level ORM as follows:
class TeacherAssignmentManager(models.Manager):
def get_years(self):
year_values = self.filter(lessonplan__isnull=False).\
values_list('year').distinct().\
order_by('-year')
return [yv[0] for yv in year_values if len(yv[0]) == 4]
Add your custom manager model to TeacherAssignment model class
objects = TeacherAssignmentManager()
Now, you can use TeacherAssignment.objects.get_years() to retrieve all the distinct years that are related to at least one LessonPlan.
TeacherAssignment.objects.filter(lessonplan__isnull=False)
Satisfies your query. Get all TeacherAssignments that have a LessonPlan.
Reverse relationships can be queried this way from the parent model.