Django related_name naming best practice in case of multiple relations - django

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+')

Related

How to join multiple model using select_related?

i have few models namely
class Alpha(models.Model):
name = models.CharField()
class XXX(models.Model):
owner = models.ForeignKey(Alpha)
class YYY(models.Model):
name = models.OneToOneField(Alpha)
Now while doing select_related like this
test = Alpha.objects.filter(id=pk).select_related('XXX')
It gives me Invalid field name(s) given in select_related, choices are YYY
I understand that YYY is at OneToOne, so its showing up - but is there a way to fetch XXX also ? or should i use "prefetch_related". But i dont wanna use prefetch as its just making slow queries and meanwhile i have 7 models which needs to be select_related :(
You can use prefetch_related in order to achieve this. Also, add related_name in foreign key fields for referencing.
class Alpha(models.Model):
name = models.CharField()
class XXX(models.Model):
owner = models.ForeignKey(Alpha, related_name='xxx')
class YYY(models.Model):
name = models.OneToOneField(Alpha, related_name='yyy')
Alpha.objects.filter(id=pk).prefetch_related('xxx', 'yyy')
You can use select_related when going in the direction of the 1 model.
For example XXX.objects.all().select_related('alpha')
But when you go from 1 towards many you have to use prefetch_related.
so Alpha.objects.all().prefetch_related('xxx')
Check out this article

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

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.