Django ORM, Q statements and customized sorting - django

I am simplifying this for clarity. Let's say I had this function that finds Document records based on a requestedColor.
def find_docs(requestedColor):
docs = Document.objects.filter(Q(color=requestedColor) | Q(other_color=requestedColor))
I'd like to order the results so that Document found using color will appear before objects found with other_color and I want to do it within the query - without any external sorting.
Is there a way to do this within the ORM query? I could not find a way to do that.
Pointers will be appreciated.

You can use Conditional Expressions to annotate a value which would indicate which field matched and order by this value:
from django.db.models import Case, IntegerField, Value, When
docs = Document.objects.annotate(
color_order=Case(
When(color=requestedColor, then=Value(1, output_field=IntegerField())),
When(other_color=requestedColor, then=Value(2, output_field=IntegerField())),
default=Value(0, output_field=IntegerField()),
)
).filter(
Q(color=requestedColor) | Q(other_color=requestedColor)
).order_by('color_order')

Related

How to get boolean result in annotate django?

I have a filter which should return a queryset with 2 objects, and should have one different field. for example:
obj_1 = (name='John', age='23', is_fielder=True)
obj_2 = (name='John', age='23', is_fielder=False)
Both the objects are of same model, but different primary key. I tried usign the below filter:
qs = Model.objects.filter(name='John', age='23').annotate(is_fielder=F('plays__outdoor_game_role')=='Fielder')
I used annotate first time, but it gave me the below error:
TypeError: QuerySet.annotate() received non-expression(s): False.
I am new to Django, so what am I doing wrong, and what should be the annotate to get the required objects as shown above?
The solution by #ktowen works well, quite straightforward.
Here is another solution I am using, hope it is helpful too.
queryset = queryset.annotate(is_fielder=ExpressionWrapper(
Q(plays__outdoor_game_role='Fielder'),
output_field=BooleanField(),
),)
Here are some explanations for those who are not familiar with Django ORM:
Annotate make a new column/field on the fly, in this case, is_fielder. This means you do not have a field named is_fielder in your model while you can use it like plays.outdor_game_role.is_fielder after you add this 'annotation'. Annotate is extremely useful and flexible, can be combined with almost every other expression, should be a MUST-KNOWN method in Django ORM.
ExpressionWrapper basically gives you space to wrap a more complecated combination of conditions, use in a format like ExpressionWrapper(expression, output_field). It is useful when you are combining different types of fields or want to specify an output type since Django cannot tell automatically.
Q object is a frequently used expression to specify a condition, I think the most powerful part is that it is possible to chain the conditions:
AND (&): filter(Q(condition1) & Q(condition2))
OR (|): filter(Q(condition1) | Q(condition2))
Negative(~): filter(~Q(condition))
It is possible to use Q with normal conditions like below:
(Q(condition1)|id__in=[list])
The point is Q object must come to the first or it will not work.
Case When(then) can be simply explained as if con1 elif con2 elif con3 .... It is quite powerful and personally, I love to use this to customize an ordering object for a queryset.
For example, you need to return a queryset of watch history items, and those must be in an order of watching by the user. You can do it with for loop to keep the order but this will generate plenty of similar queries. A more elegant way with Case When would be:
item_ids = [list]
ordering = Case(*[When(pk=pk, then=pos)
for pos, pk in enumerate(item_ids)])
watch_history = Item.objects.filter(id__in=item_ids)\
.order_by(ordering)
As you can see, by using Case When(then) it is possible to bind those very concrete relations, which could be considered as 1) a pinpoint/precise condition expression and 2) especially useful in a sequential multiple conditions case.
You can use Case/When with annotate
from django.db.models import Case, BooleanField, Value, When
Model.objects.filter(name='John', age='23').annotate(
is_fielder=Case(
When(plays__outdoor_game_role='Fielder', then=Value(True)),
default=Value(False),
output_field=BooleanField(),
),
)

Django __in lookup doesn't look of None values

Consider I have a model called Subject, and I want to filter the values based on the list of values.
Consider the below list.
["John", "Brad", None]
When I try to filter out the result using __in lookup it doesn't look for None values. For example
Subject.objects.filter(user__in=["John", "Brad", None])
This will provide the queryset for John and Brad but not None. What I'm missing here? Can anyone please help me
NULLs in DB (None in django) are not reqular values, so they need different approach:
Subject.objects.filter(Q(user__in=["John", "Brad", ]) | Q(user__isnull=True,))
Try filtering like this
from django.db.models import Q
Subject.objects.filter(Q(user__in=["John", "Brad"]) | Q(user__isnull=True))
Using Q objects is better suited for this:
Subject.objects.filter(Q(user__in=["John", "Brad"]) | Q(user__isnull=True))
You can use Q objects for OR, AND and NOT operations. Q objects provide you complete control over the where clause of the query.
Your queryset would be something like:
from django.db.models import Q
Subject.objects.filter(Q(user=None) | Q(user__in=["John", "Brad"]))
You can combine the Q objects in more complex ways to generate complex queries.

Error on OR on filter Django

i'm try create or on filter on django
This is my little example :
products=Products.objects.values('name', 'price').all().filter(status=1|0)
The problem is that don't validate the two options (1|0)
don't get a error on the print(products.query) only validate one option don't the 2 options..!!
Please thanks !!
To filter using OR in django you need a special class called Q.
Documentation about Complex lookups with Q objects
from django.db.models import Q
products = Products.objects.values('name', 'price').filter(Q(status=1) | Q(status=0))
It's good to use Q object
manager.filter(Q(status=1) | Q(status=0))
You need to know that the method all() on a manager just delegates to get_queryset().
To use filter(), you would already have the QuerySet
Rather than all() whose calls the queryset, and then filter whose already call the queryset,
just do manager.filter()
all().filter() becomes just filter() because it's redundant
There it is:
from django.db.models import Q
products = Product.objects.values('name','price').filter(
Q(status=1) | Q(status=0),
)

django icontains with __in lookup

So I want to find any kind of matching given some fields, so for example, this is what I would like to do:
possible_merchants = ["amazon", "web", "services"]
# Possible name --> "Amazon Service"
Companies.objects.filter(name__icontains__in=possible_merchants)
sadly it is not possible to mix icontains and the __in lookup.
It seems to be a pretty complex query so if at least I could ignore case the name that would be enough, for example:
Companies.objects.filter(name__ignorecase__in=possible_merchants)
Any ideas?
P.D.: The queries I posted don't work, it's just a way to express what I need (just in case heh)
You can create querysets with the Q constructor and combine them with the | operator to get their union:
from django.db.models import Q
def companies_matching(merchants):
"""
Return a queryset for companies whose names contain case-insensitive
matches for any of the `merchants`.
"""
q = Q()
for merchant in merchants:
q |= Q(name__icontains = merchant)
return Companies.objects.filter(q)
(And similarly with iexact instead of icontains.)
I find it a cleaner approach using reduce and or_ operator:
from django.db.models import Q
from functools import reduce
from operator import or_
def get_companies_from_merchants(merchant_list):
q_object = reduce(or_, (Q(name__icontains=merchant) for merchant in merchant_list))
return Companies.objects.filter(q_object)
This would create a list of Q objects querying the name to contain a single element in merchant list. This would happpen for all the elements in merchant_list and all these Q objects would be reduced to a single Q object having mutliple ORs which can be directly applied to the filter query.
This is the approach that I adopted:
class MyManager(models.Manager):
def exclusive_in(self, lookup, value_list):
return self.filter(reduce(or_, (Q(**{lookup:_}) for _ in value_list)))
Here is now to use it:
Companies.objects.exclusive_in('name__icontains', possible_merchants])
It was inspired by other answers in this thread, as well as Django filter queryset __in for *every* item in list.
Another approach would be to simulate the actions that Django normally does for iexact queries (it converts both parts of the comparison statement to the upper case via SQL Upper function.
This way, the query will look like this:
Companies.objects.annotate(
upper_name=models.Upper("name")
).filter(
upper_name__in=[rchant.upper() for merchant in possible_merchants]
)

Django conditional annotation

I'm surprised that this question apparently doesn't yet exist. If it does, please help me find it.
I want to use annotate (Count) and order_by, but I don't want to count every instance of a related object, only those that meet a certain criteron.
To wit, that I might list swallows by the number of green coconuts they have carried:
swallow.objects.annotate(num_coconuts=Count('coconuts_carried__husk__color = "green"').order_by('num_coconuts')
For Django >= 1.8:
from django.db.models import Sum, Case, When, IntegerField
swallow.objects.annotate(
num_coconuts=Sum(Case(
When(coconuts_carried__husk__color="green", then=1),
output_field=IntegerField(),
))
).order_by('num_coconuts')
This should be the right way.
swallow.objects.filter(
coconuts_carried__husk__color="green"
).annotate(
num_coconuts=Count('coconuts_carried')
).order_by('num_coconuts')
Note that when you filter for a related field, in raw SQL it translates as a LEFT JOIN plus a WHERE. In the end the annotation will act on the result set, which contains only the related rows which are selected from the first filter.