Q queries in Django - django

If I have the following query
return Table.objects.filter(Q(cond1) | Q(cond2))
is there a way to know which condition has given a specific row?

You can split your query in two queries:
qs1 = Table.objects.filter(cond1).extra(select={'condition': 'cond1'})
qs2 = Table.objects.filter(cond2).extra(select={'condition': 'cond2'})
Then create a union of the querysets:
qs12 = qs1 | qs2
EDITED: Unions are not supported between querysets with extra()
Then create a chain of your querysets:
from itertools import chain
qs12 = list(chain(qs1, qs2))
And use it like this:
for obj in qs12:
if obj.condition == 'cond1':
...
elif obj.condition == 'cond2':
...

Not really, no. Your query is roughly equivalent to the following SQL:
SELECT *
FROM Table
WHERE condition OR other_condition
Just like your django query, there is no natural indicator that will let you know which condition happened to be true for that particular relation. You either have to execute two queries, add extra information (the condition) to the relation, or use the condition itself.
c1 = Q('name__exact'='Bob') # condition 1
c2 = Q('name__exact'='Mary') # condition 2
# use separate queries
set1 = Table.objects.filter(c1) # meets condition 1
set2 = Table.objects.filter(c2) # meets condition 2
# or use the natural condition
both = Table.objects.filter(c1|c2)
for item in both:
if item.name == 'Bob':
# condition 1
elif item.name == 'Mary':
# condition 2

Related

Django conditional many-to-many relation exists check

The model is:
class RecordModel(BaseModel):
visibility_setting = models.PositiveIntegerField()
visible_to = models.ManyToManyField(UserModel, blank=True)
I need to return rows depending on row visibility_setting:
if visibility_setting == 0 - return row without any checks,
if visibility_setting == 1 - I need to check if user is in visible_to m2m relation exists.
In second case query works okay, but in first case my approach of setting None on first case not working (as expected):
Feed.objects.filter(
visible_to=Case(
When(visibility_setting=0, then=None), # no data returned in this case, but I want to
# return all rows with visibility_setting=0
When(visibility_setting=1, then=user.id), # rows with visibility_setting=1 are queried okay
)
)
I am a little bit stuck which way to use in this situation? Can we not apply case at all in some conditions or use some specific default to skip visible_to relations check?
You can .filter(…) [Django-doc] with:
from django.db.models import Q
Feed.objects.filter(Q(visibility_setting=0) | Q(visible_to=my_user)).distinct()
the .distinct() call [Django-doc]
prevents returning the same Feed multiple times.

Django with filtered join

I have an inner join:
SELECT c.name, b.name FROM company c
INNER JOIN branch b ON b.company_id = c.id
WHERE c.id = 5 AND b.employees > 10;
That I got 3 registers.
How do I make this query in Django to returns only 3 registers?
c = Company.objects.prefetch_related("comp_branches").filter(id=5, comp_branches__employees__gt=10)
c[0].comp_branches.all() # returns 5 registers
You can use a Prefetch object [Django-doc]:
from django.db.models import Prefetch
c = Company.objects.prefetch_related(
Prefetch('comp_branches', Branch.objects.filter(employees__gte=10))
).filter(id=5)
Here Brach is the model that is targeted by comp_branches, this thus might be different.
If you .filter(), you do not filter on the related objects you are prefetching, since that is done in a separate query. The comp_brances__employees__gte=10 would only remove Companys from the QuerySet that have no brach with 10 or more employees.

How to combine two filters from two models?

How to combine two filters from two models?
Must be work as AND (&)
Credit.objects.filter(id__in=CreditPayment.objects.filter(security='Deposit - deposit').values('credit__id').distinct(), bank__id=1))
Credit.objects.filter(id__in=Condition.objects.filter(purpose=3).values('credit__id').distinct(), bank__id=1))
you can use django Q with &
from django.db.models import Q
Credit.objects.filter(Q(id__in=CreditPayment.objects.filter(security='Deposit - deposit').values('credit__id').distinct()) & Q(id__in=Condition.objects.filter(purpose=3).values('credit__id').distinct()), bank__id=1))
Given I understood it correctly, you can do this with two filter statements where you join on the Condition model, like:
Credit.objects.filter(
bank__id=1,
condition__security='Deposit - deposit'
).filter(
condition__purpose=3
).distinct()
This yields a query like:
SELECT DISTINCT credit.*
FROM credit
INNER JOIN condition ON credit.id = condition.credit_id
INNER JOIN condition T3 ON credit.id = T3.credit_id
WHERE credit.bank_id = 1
AND condition.security = Deposit - deposit
AND T3.purpose = 3

Django queryset union appears not to be working when combined with .annotate()

I have the following queryset:
photos = Photo.objects.all()
I filter out two queries:
a = photos.filter(gallery__name='NGL')
b = photos.filter(gallery__name='NGA')
I add them together, and they form one new, bigger queryset:
c = a | b
Indeed, the length of a + b equals c:
a.count() + b.count() == c.count()
>>> True
So far so good. Yet, if I introduce a .annotate(), the | no longer seems to work:
a = photos.annotate(c=Count('label').exclude(c__lte=4)
b = photos.filter(painting=True)
c = a | b
a.count() + b.count() == c.count()
>>> False
How do I combine querysets, even when .annotate() is being used? Note that query one and two both work as intended in isolation, only when combining them using | does it seem to go wrong.
the pipe | or ampersand & to combine querysets actually puts OR or AND to SQL query so it looks like combined.
one = Photo.objects.filter(id=1)
two = Photo.objects.filter(id=2)
combined = one | two
print(combined.query)
>>> ... WHERE ("photo_photo"."id" = 1 OR "photo_photo"."id" = 2)...
But when you combine more filters and excludes you may notice it will give you strange results due to this. So that is why it doesn't match when you compare counts.
If you use .union() you have to have same columns with same data type, so you have to annotate both querysets. Info about .union()
SELECT statement within .UNION() must have the same number of columns
The columns must also have similar data types
The columns in each SELECT statement must also be in the same order
You have to keep in mind, that pythons argument kwargs for indefinite number of arguments are dictionary, so if you want to use annotate with multiple annotations, you can't ensure correct order of columns. Fortunatelly you can solve this with chaining annotate commands.
# printed query of this won't be consistent
photo_queryset.annotate(label_count=Count('labels'), tag_count=Count('tags'))
# this will always have same order of columns
photo_queryset.annotate(label_count=Count('labels')).annotate(tag_count=Count('tags'))
Then you can use .union() and it won't mess up results of annotation. Also .union() should be last method, because after .union() you can't use filter like methods. If you want to preserve duplicates, you use .union(qs, all=True) since .union() has default all=False and calls DISTINCT on queryset
photos = Photo.objects.annotate(c=Count('labels'))
one = photos.exclude(c__lte=4)
two = photos.filter(painting=True)
all = one.union(two, all=True)
one.count() + two.count() == all.count()
>>> True
then it should work like you described in question

Looking for Django any() and all() on querysets

I have a form that has a group of 13 checkboxes that together make up my search criteria... except that I also added a pair of radio buttons for ALL or ANY.
I was hoping to get away with something elegant like:
priority_ids = request.GET.getlist("priority") # checkboxes
collection = request.GET.get("collection") # radio buttons
priorities = []
for priority_id in priority_ids:
priorities.append(Q(focus__priority=priority_id))
if (collection == "any"): qset = any(priorities)
elif (collection == "all"): qset = all(priorities)
However, any() and all() return a boolean, not a queryset that I can use in a filter. I want an "any" or "all" that does the equivalent of "Q(...) | Q(...) | Q(...)" or "Q(...) & Q(...) & Q(...)" for anywhere from 1 to 13 criteria.
There's nothing that Django needs to do about that. You just need to combine your Q-s with & and respectively |, in a simple loop or in a more compact way with reduce.
And regarding terminology it seems to me that you are calling Q a queryset, but it's not. It's a filter on a queryset.
Something like the below should work:
priority_ids = request.GET.getlist("priority")
collection = request.GET.get("collection")
priority_filters = []
for priority_id in priority_ids:
priority_filters.append(Q(focus__priority=priority_id))
base_qs = SomeModel.objects.all()
if collection == "any":
filtered_qset = base_qs.filter(reduce(operator.or_, priority_filters))
elif collection == "all":
filtered_qset = base_qs.filter(reduce(operator.and_, priority_filters))
You can query dynamically with Q() as below...
from django.db.models import Q
from priority_app.models import Priority # get your model identified
#(whatever API place you had this):
priority_id = request.GET.get("priority") #getlist is isn't a proper list
field_id = request.query_params.get('collection')
if priority_id:
priority_id_list = list(map(str.strip, priority_id.split(",")))
dynamic_query_filters = Priority.objects.none()
for a_item in priority_id_list:
if collection == "any":
dynamic_query_filters |= (Q(id=a_item)) # OR query
else:
dynamic_query_filters &= (Q(id=a_item)) # AND query
queryset = Priority.objects.filter(dynamic_query_filters)
# you will notice, it executes in order of "query", maybe take them in different variables and chain them accordingly.