Django queryset exclude empty foreign key set - django

I have the following models where B has a many-to-one relationship with A:
class A(model.Model):
name = models.IntegerField()
class B(models.Model
a = models.ForeignKey(A, db_column='a_id')
When I use a queryset on A, is there a way to exclude the rows in A that have no rows in B?

Use isnull :
A.objects.filter(b__isnull=False).distinct()
Using distinct() prevents duplicate entries, otherwise each a appears once for every b which is linked to it.

no_rows_in_b = B.objects.all().select_related('a')
will get you all the B's with A's
Then you can cycle through them and output the A's
If you want non-repeats:
no_rows_in_b = B.objects.all().distinct('a').select_related('a')
Then:
for rec in no_rows_in_b:
print(rec.a)

Notice that if you want to be more explicit, you could do something like this:
A.objects.exclude(b__isnull=True).distinct()
using exclude instead of filter and using the True boolean arg.

Related

Django annotate multiple objects based on multiple fields on a M2M relationship

I want to efficiently annotate Model A objects based on some fields on model B which has a plain many-to-many relationship (not using a through model) to A. A wrinkle is that I must find the oldest B for each A (using B.created_timestamp) but then populate using B.name. I want to use the ORM not raw SQL.
I tried this but it's not correct:
a_qs = A.objects.filter(id__in=ids)
ordered_qs = a_qs.order_by('-b__created_timestamp')
oldest_qs = Subquery(ordered_qs.values('b__name')[:1])
result = list(a_qs.annotate(name=oldest_qs))
This annotates every A with the same oldest name of B across all Bs related to A, but I want the oldest B among associated Bs for each A.
You forgot to set OuterRef https://docs.djangoproject.com/en/2.2/ref/models/expressions/
b_qs = B.objects.filter(a=OuterRef('pk')).order_by('-created_timestamp')
a_qs = A.objects.filter(id__in=ids).annotate(oldest_name=Subquery(b_qs.values('name')[:1])
result = list(a_qs)

Django getting foreign-key object list

I have model like this:
class A:
....
class B:
....
a = model.ForeignKey(A, related_name='a')
....
Let's assume there is an B object.
I can get A object like this:
b = B()
a = b.a
Then what is the simplest way to get all B object related with A?
Additionally,
I can get a list of A.
list_a = A.objects.filter()
Then what is the simplest way of getting a list of B which relates with A object in the list_a?
One more reverse case: I have a list of B:
list_b = B.objects.filter()
Then what is the simplest and optimized way to get the list of A object related to the B object in the list_b?
B.objects.filter(a__in=a_list)
note that you can filter on related objects like this (instead if executing two queries do it in one)
for example if your a_list is a query like this:
a_list = A.objects.filter(field=2)
you can filter B like this:
B.objects.filter(a__field=2)
which is more readable and also django can optimize it too)
Update: you can query reversed relations the same way
A.objects.filter(b__in=b_list)
A.objects.filter(b__field=2)
note that it's better to change your code to
a = model.ForeignKey(A, related_name='b')
b is the name of the field in reveres relations so an_a_instance.b.all() returns all instances of b which are pointing at given a_instance

multiple Django annotate Count over reverse relation of a foreign key with an exclude returns a strange result (18)

The strangest thing, either I'm missing something basic, or maybe a django bug
for example:
class Author(Model):
name = CharField()
class Parent(Model):
name = CharField(
class Subscription(Model):
parent = ForeignKey(Parent, related_name='subscriptions')
class Book(Model):
name = CharField()
good_book = BooleanField()
author = ForeignKey(Author, related_name='books')
class AggregatePerson(Model):
author = OneToOneField(Author, related_name='+')
parent = OneToOneField(Parent, related_name='+')
when I try:
AggregatePerson.objects.annotate(counter=Count('author__books')).order_by('counter')
everything work correctly. both ordering and fields counter and existing_subs show the correct number BUT if I add the following:
AggregatePerson.objects.annotate(existing_subs=Count('parent__subscriptions')).exclude(existing_subs=0).annotate(counter=Count('author__books')).order_by('counter')
Then counter and existing_subs fields become 18
Why 18? and what am I doing wrong?
Thanks for the help!
EDIT clarification after further research:
is the number of parent__subscriptions, the code breaks even without the exclude, **for some reason counter also gets the value of existing_subs
I found the answer to this issue.
Tl;dr:
You need to add distinct=True inside the Count like this:
AggregatePerson.objects.annotate(counter=Count('author__books', distinct=True))
Longer version:
Adding a Count annotation is adding a LEFT OUTER JOIN behind the scene. Since we add two annotations, both referring to the same table, the number of selected and grouped_by rows is increased since some rows may appear twice (once for the first annotation and another for the second annotation) because LEFT OUTER JOIN allows empty cells (rows) on select from the right table.
(repeating essentials of my reply in another forum)
This looks like a Django bug. Possible workarounds:
1) Add the two annotations in one annotate() call:
...annotate(existing_subs=Count('parent__subscriptions'),counter=Count('author__books'))...
2) Replace the annotation for existing_subs and exclude(existing_subs=0) with an exclude (parent__subscriptions=None).

How to use Q objects to check if any members of arbitrary-length list are in Many-To-Many Relationship

Suppose I have the following Django models:
class myObj1(models.Model):
myField1 = models.IntegerField()
class myObj2(models.Model):
myLocalObj1 = models.ManyToManyField(myObj1)
Furthermore, suppose I have a list of unique myObj1s:
a = myObj1(myField=1)
b = myObj1(myField=2)
c = myObj1(myField=3)
myTargetList = [a, b, c]
Now, I would like to write a Django query using Q objects such that it returns all the myObj2s that have any member of myTargetList as myLocalObj1. Furthermore, I don't know the exact size of myTargetList in advance.
How should I do it? This obviously won't work:
myObj2.objects.filter(Q(myLocalObj1__in=myTargetList))
EDIT: To make this a little more in line with what you are looking for (although I would not necessarily recommend doing things this way), you could:
vallist=[]
for b in myTargetList:
vallist.append(b.myField)
myObj2.objects.filter(myLocalObj1__in=myObj1.objects.filter(myField__in=vallist))
This is available in the Django docs here:
https://docs.djangoproject.com/en/dev/topics/db/queries/#lookups-that-span-relationships

Checking for object's existence in ManyToMany relation (Django)

I'd like to check for a particular object's existence within a ManyToMany relation. For instance:
class A(models.Model):
members = models.ManyToManyField(B)
class B(models.Model):
pass
results = [some query]
for r in results:
print r.has_object // True if object is related to some B of pk=1
My first stab at [some query] was A.objects.all().annotate(Count(has_object='members__id=1')) but it looks like I can't put anything more than the field name into the argument to Count. Is there some other way to do this?
You can try
A.objects.filter(members__id=1).exists()
I pretty sure there won't be any decently performing way to do this in pure Python until many-to-many prefetching gets implemented in 1.4
In the meantime, this is how I'd do it by dropping down into SQL:
results = A.objects.all().extra(
select={
'has_object': 'EXISTS(SELECT * FROM myapp_a_members WHERE a_id=myapp_a.id AND b_id=1)'
}
)
Of course, the simpler way would simply be to refactor your code to operate on two separate querysets:
results_with_member_1 = A.objects.filter(members__id=1)
results_without_member_1 = A.objects.exclude(members__id=1)