Django-ORM instead of python for loop - django

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]))

Related

Django count nested m2m objects

I have 3 models -
class A(models.Model):
...
b = models.ManyToManyField(B, related_name="related_a")
class B(models.Model):
...
class Entries(models.Model):
...
b = models.ForeignKey(B, related_name="entries")
I need to loop over all A objects and show the count of entries for each b in A, along with other fields in A. What would be the most efficient way to do it?
You can get it done in 2 queries:
from django.db.models import Count, Prefetch
qs = B.objects.annotate(entry_count=Count('entries'))
a_list = A.objects.prefetch_related(Prefetch('b', queryset=qs))
I hope I've got all related names right. You can access the counts like this:
for a in a_list:
for b_item in a.b:
print(b_item.entry_count)

Django excluding a queryset without values() keyword

I have a few models but my question is about two of them, A and B.
class A(models.Model):
...
class B(models.Model):
a = models.ForeignKey(A)
c = models.CharField(...)
d = models.ForeignKey(C)
All i want is to exclude this queryset:
set1 = B.objects.all()
From below queryset:
set2 = A.objects.all()
I know i can manage this by:
set1 = B.objects.all().values('a')
set2 = A.objects.all().exclude(pk__in = set1)
But i need all values of set1 for the remaining code. If i use values(), i cant use "c" and "d" fields of set1.
So, is there any method excluding without narrowing the fields of B?
PS: i prefer to keep away from new queries. i know i can write a second query of B objects to fit my needs.
I believe you are asking for the instances of model A with no related B.
set2 = A.objects.filter(b__isnull=True)
Yes, you can use a list comprehension:
[obj.a for obj in queryset]
This will return a list with the a attribute for all the objects in the queryset, without modifying them.

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 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

Django: SQL query with distinct on model function

Hi Stackoverflow people,
in my current project, I have the following model structure:
class Project(models.Model):
student_id = models.ForeignKey(User)
class_a = models.ForeignKey(ClassA)
class_b = models.ForeignKey(ClassB)
...
class ClassA(models.Model):
teacher_name = models.CharField(...)
class ClassB(models.Model):
teacher_name = models.CharField(...)
ClassA and ClassB are very different and only they only have the teacher_name in common, therefore I keep it in separate classes.
In addition, in each project only class_a or class_b can contain a ForeignKey (it can not be assigned to two classes).
When I display all projects and I want to list the names of the teacher, therefore I created a little model function, which returns the teacher's name:
def get_teacher_name(self):
if self.class_a:
return self.class_a.teacher_name
if self.class_b:
return self.class_b.teacher_name
Now, I want to write a query which returns the names of teachers for a student_id and only returns the teachers name once, e.g.:
For Student X
Project Teacher
Biology Mrs. X
Physics Mr. Y
(-> Math Mr. Y should be disregarded)
How should I structure the Django query? I would like to do
Project.objects.filter(student_id=user.pk).values('get_teacher_name').distinct(), but this is not possible. Is there a solution for it?
Thank you for your suggestions!
You'er looking for values and annotate
https://docs.djangoproject.com/en/dev/topics/db/aggregation/#order-of-annotate-and-values-clauses
Which will give you unique values for a values call.
import itertools
class_a_names = user.project_set.values_list('class_a__teacher_name', flat=True).annotate()
class_b_names = user.project_set.values_list('class_b__teacher_name', flat=True).annotate()
unique_teachers = set(itertools.chain(class_a_names, class_b_names))