Django Query. Correct use of objects.select_related() - django

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.

Related

Efficient ways of removing duplicate from a queryset?

I have a table called Clue which has a foreignkey relation with another entity called Entry.
class Entry(models.Model):
entry_text = models.CharField(max_length=50, unique=True)
.....
class Clue(models.Model):
entry = models.ForeignKey(Entry, on_delete=models.CASCADE)
......
Now, let's say I have the following queryset
clues = Clue.objects.filter(clue_text=clue.clue_text)
which returns something like this-
[<Clue: ATREST-Still>, <Clue: ATREST-Still>, <Clue: ATREST-Still>, <Clue: YET-Still>, <Clue: YET-Still>, <Clue: SILENT-Still>]
As, you can see there are different clue objects but some of them are tied to the same entry objects.
I tried the following:-
clues = Clue.objects.filter(clue_text=clue.clue_text).distinct()
But this won't work as the field repeating is a foreign key value. Correct me if I am wrong.
Essentially, I want my queryset to look something like this
[<Clue: ATREST-Still>, <Clue: YET-Still>, <Clue: SILENT-Still>]
I was able to achieve it through the following but I was looking at a solution that can be done at the database level rather than doing it in memory.
This is my approach
clue_objs=[]
temp = {}
clues = Clue.objects.filter(clue_text=clue.clue_text)
for clue in clues:
if not temp.get(clue.entry.entry_text):
temp[clue.entry.entry_text]=1
clue_objs.append(clue)
You can call .distinct() on the QuerySet you obtain with .values_list(…) [Django-doc], so something like:
clues = Clue.objects.filter(
clue_text=clue.clue_text
).values_list('clue_text', flat=True).distinct()
But this looks more like a modeling problem: if you have a lot of duplicated data, that often means you should construct a new model that stores that data only once, and then reference that model with a relation (like a ForeignKey, OneToOneField or ManyToManyField).

Django related_name naming best practice in case of multiple relations

After a year of Django experience I found out that I am not quite sure that I use Django related_names correctly.
Imagine I have three models
classA(models.Model):
pass
classB(models.Model):
pass
classC(models.Model):
modelA = models.ForeignKey(classA)
modelB = models.ForeignKey(classB)
Fine. Now I am thinking of adding related_name to classC's modelA and modelB, but the frustrating think is that I cannot use the same name for two fields. In other words, this code is apparently wrong
classC(models.Model):
modelA = models.ForeignKey(classA, related_name = 'classC') # wrong
modelB = models.ForeignKey(classB, related_name = 'classC') # wrong
On the other hand, coming up with an approach like this:
classC(models.Model):
modelA = models.ForeignKey(classA, related_name = 'classA') # wrong
modelB = models.ForeignKey(classB, related_name = 'classB') # wrong
would result in a very misleading (at least for me) code. Consider this:
obj = classA.filter(classC__in = classA_qs)
So such naming results in a very disruptive code classC = classA_instance.
What is the best practice in terms of naming related_names. And is there something I am missing about ManyToManyFields ? Actually, I have a large project, but I've never used ManyToManyFields, always going for a third table like classC in the example. Is there something I am missing ?
How about using variable related_names that way you can relate them according to their app and class.
class ClassB(models.Model):
readers = ForeignKey('Reader',
related_name='readable_%(app_label)s_%(class)s_set+')

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: Annotating difference of two fields

I have the following model:
class P (models.Model):
title = models.CharField(max_length=100)
votes_up = models.ManyToManyField(User)
votes_down = models.ManyToManyField(User)
Is there a way to do something like
P.objects.annotate(rating = Count(votes_up) - Count(votes_down)).order_by('-rating')
?
Nope.
You either write your own aggregate function and then use it in your annotation (which is really a bad idea) or simply rely on raw sql.
Personally, I'd handle that use case with a denormalized counter, though.
Why are you using m2m relations to represent votes? Maybe change that to integerfields instead. Then you could have a model method that returns the subtracted amount. And then maybe you could annotate on the model method instead.

Django filter the model on ManyToMany count?

Suppose I have something like this in my models.py:
class Hipster(models.Model):
name = CharField(max_length=50)
class Party(models.Model):
organiser = models.ForeignKey()
participants = models.ManyToManyField(Profile, related_name="participants")
Now in my views.py I would like to do a query which would fetch a party for the user where there are more than 0 participants.
Something like this maybe:
user = Hipster.get(pk=1)
hip_parties = Party.objects.filter(organiser=user, len(participants) > 0)
What's the best way of doing it?
If this works this is how I would do it.
Best way can mean a lot of things: best performance, most maintainable, etc. Therefore I will not say this is the best way, but I like to stick to the ORM features as much as possible since it seems more maintainable.
from django.db.models import Count
user = Hipster.objects.get(pk=1)
hip_parties = (Party.objects.annotate(num_participants=Count('participants'))
.filter(organiser=user, num_participants__gt=0))
Party.objects.filter(organizer=user, participants__isnull=False)
Party.objects.filter(organizer=user, participants=None)
Easier with exclude:
# organized by user and has more than 0 participants
Party.objects.filter(organizer=user).exclude(participants=None)
Also returns distinct results
Derived from #Yuji-'Tomita'-Tomita answer, I've also added .distinct('id') to exclude the duplitate records:
Party.objects.filter(organizer=user, participants__isnull=False).distinct('id')
Therefore, each party is listed only once.
I use the following method when trying to return a queryset having at least one object in a manytomany field:
First, return all the possible manytomany objects:
profiles = Profile.objects.all()
Next, filter the model by returning only the queryset containing at least one of the profiles:
hid_parties = Party.objects.filter(profiles__in=profiles)
To do the above in a single line:
hid_parties = Party.objects.filter(profiles__in=Profile.objects.all())
You can further refine individual querysets the normal way for more specific filtering.
NOTE:This may not be the most effective way, but at least it works for me.