How to execute arithmetic operations between Model fields in django - python

Prologue:
This is a question arising often in SO:
Subtracting two annotated columns
Django query with simple arithmetic among model fields and comparison with field from another model
Django Aggregation: Summation of Multiplication of two fields
And can also be applied here:
Django F expression on datetime objects
I have composed an example on SO Documentation but since the Documentation will get shut down on August 8, 2017, I will follow the suggestion of this widely upvoted and discussed meta answer and transform my example to a self-answered post.
Of course, I would be more than happy to see any different approach as well!!
Question:
Assume the following model:
class MyModel(models.Model):
number_1 = models.IntegerField()
number_2 = models.IntegerField()
date_1 = models.DateTimeField()
date_2 = models.DateTimeField()
How can I execute arithmetic operations between fields of this model?
For example, how can I find:
The product of number_1 and number_2 of a MyModel object?
How to filter items where date_2 is 10 or more days older than date_1?

F() expressions can be used to execute arithmetic operations (+, -, * etc.) among model fields, in order to define an algebraic lookup/connection between them.
An F() object represents the value of a model field or annotated column. It makes it possible to refer to model field values and perform database operations using them without actually having to pull them out of the database into Python memory.
let's tackle the issues then:
The product of two fields:
result = MyModel.objects.all().annotate(prod=F('number_1') * F('number_2'))
Now every item in result has an extra column named 'prod' which contains the product of number_1 and number_2 of each item respectively.
Filter by day difference:
from datetime import timedelta
result = MyModel.objects.all().annotate(
delta=F('date_2') - F('date_1')
).filter(delta__gte=timedelta(days=10))
Now the items in result are those from MyModel whose date_2 is 10 or more days older than date_1. These items have a new column named delta with that difference.
A different case:
We can even use F() expressions to make arithmetic operations on annotated columns as follows:
result = MyModel.objects.all()
.annotate(sum_1=Sum('number_1'))
.annotate(sum_2=Sum('number_2'))
.annotate(sum_diff=F('sum_2') - F('sum_1'))

Step 1: from django.db.models import F
Step 2: from datetime import timedelta
Step 3: You may then apply the F operator in your queryset as follows:
MyModel.objects.all().annotate(
date_diff=F('date_2') - F('date_1')
).filter(date_diff__gte=timedelta(days=10)
)

Related

Filtering a date range field with date range

React/Django app. I want to add a date range filter (flatpickr) to already existing filters for orders. Orders has a period field (how long the order is valid), which is a DateRange field. Via the flatpickr I select a date range, and if at least one day of the order period is in that selected date range, it should show up as a result.
ex. period: DateRange(datetime.date(2021, 2, 11), datetime.date(2021, 3, 14), '[)')
I have the filter ready in FE to accept the results from BE. But I'm not sure how to achieve this in the BE. As I'm fairly new to this, my ideas are limited, but currently have this:
...
from django_filters.rest_framework import DjangoFilterBackend, IsoDateTimeFilter, FilterSet
from rest_framework import filters, mixins, status
...
class OrderFilter(FilterSet):
start_date = IsoDateTimeFilter(field_name="period", lookup_expr="gte")
end_date = IsoDateTimeFilter(field_name="period", lookup_expr="lte")
class Meta:
model = Order
fields = {
"status": ["in"],
"client": ["exact"],
"created_by": ["exact"],
}
ordering_fields = ["period", "client__name", "destination__name"]
ordering = ["period"]
custom_field_target_model = Order
I believe the closest thing I found, is IsoDateTimeFromToRangeFilter, but that doesn't seem to be what I'm looking for.
Since your field is DateRangeField, your lookup expressions are limited. You can use the following options:
contains
contained_by
overlap
If your case to determine if period contains a specific date range (start_date, end_date), you can use it as:
Order.objects.filter(period__contains=(start_date, end_date))
you can also play with others if you wish.
The problem here is not the filter class, it is the field that you're trying to filter. If you keep period_start_date and period_end_date separately, you can filter the dates separately.
To filter a field with two inputs, you can use a custom filter method or you can override the get_queryset method of your view.
Not: As #aberkb mentioned, you need to add "period" to fields.

Python/Django: Filter objects by Concat() and order by the same concatenation

In my Python/Django API project I need to get a quesryset from a model filtered by a concatenated combination of two fields and order by the same concatenation. However I only know to filter by one field like shown below.
Requirement: TimeTable has two fields "date" and "time". I need to list data where the combination of both these fields are greater than current date time.
Code:
current_time = datetime.now()
timetable = TimeTable.objects.filter(user=user_id, date__gte=current_time.date()).order_by(Concat('date','time'))
MySQL equivalent of the scenario:
SELECT * FROM time_table where concat(date,' ',time)>='2020-03-24 12:00:00' and user=user_id order by concat(date,' ',time) ASC
How can I accomplish this?
Just provide multiple fields in the order_by(...) function as,
TimeTable.objects.filter(date__gte='2020-04-02').order_by('date','time')
Note: You don't have to concatinate those values (in your case).
Update
from django.db.models.functions import Concat, Cast
from django.db.models import DateTimeField, CharField, Value
from datetime import datetime
date_time_expr = Cast(Concat('date', Value(' '), 'time', output_field=CharField()), output_field=DateTimeField())
TimeTable.objects.annotate(date_time=date_time_expr).filter(date_time__gte=datetime.now()).order_by('date_time')

Django groupby day fill missing data with 0

I want to make a request in Django where I group by day but I want to fill the day where there is no result with 0, is it possible?
# I use the following query
AccessLog
.objects
.filter(attempt_time__gte=last_30_days)
.annotate(day=TruncDay('attempt_time'))
.values('day', 'username')
.annotate(c = Count('username'))
.order_by('day')
No, it is not possible with annotations. Annotations work with the similar types, for example, Coalesce function requires similar types and mixing datetime and numbers will result in a database error. The same for the Case function there is only one output field per result.
The function TruncDay returns a DateTime (in this case) with fields up to Day set to their minimum value, so for instance 2015-06-15 14:30:50.000321+00:00 will be converted to 2015-01-01 00:00:00+00:00 how documentation outlines. And actually annotated value cannot be sometimes integer and sometimes datetime object.
Occasionally to denote that the values are "None" in such situations preferable way would be to set it to the minimal/maximum value (we assume that the value cannot be equal to it), for instance:
AccessLog.objects.filter(
attempt_time__gte=last_30_days
).annotate(
day=Coalesce(TruncDay('attempt_time'), datetime.min)
).values('day', 'username').annotate(
c=Count('username')
).order_by('day')

Django how to join two query sets so that they are sequentially serialized

Suppose I have a model call MyModel defined as below
class MyModel(models.Model):
fk = models.ForeignKey('AnotherModel')
rank = models.FloatField()
I wish to create a query set for serialization so that instances with instance.fk.other_fk_id in some set comes before the ones that don't and then sorted by decreasing rank.
So I wish to join
a = MyModel.objects.filter(fk__other_fk_id__in=some_set).order_by('-rank')
and
b = MyModel.objects.exclude(fk__other_fk_id__in=some_set).order_by('-rank')
sequentially so that a comes before b when it's handed to the serializers. Any idea on how to achieve this functionality efficiently? (let's say there is 50 instances in a and 200000 instances in b so I cannot directly concat those two as lists).
The end goal is to feed it into a custom serializer w/ pagination, say, to present in a webpage.
ok I figured this out. What you can do is to annotate another value, join them and sort by two fields.
a = a.annotate(rank2=Value(1, IntegerField()))
b = b.annotate(rank2=Value(0, IntegerField()))
qs = a.union(b).order_by('-rank2', '-rank')
You can use itertools.chain:
from itertools import chain
from django.core import serializers
data = serializers.serialize("xml", chain(a, b))

Use a model field to query another model field in django

I have two models in our django app
class Reg(models.Model):
transactions = ManyToMany
price = IntegerField
class Transaction(models.Model)
amount = IntegerField
Now I would like to make a lookup like:
Registration.objects.filter(reg__price==transaction__amount)
Previously we used the following approach:
Registration has a property is_paid that computes wether a transaction with equal amount exists
[r for r in Registration.objects.filter(...) if r.is_paid]
This is ofc very query-consuming and inefficient.
I wonder whether there would be a better way to do this!
Any hint is appreciated :)
You can use an F expression for such a query:
from django.db.models import F
Registration.objects.filter(price=F('transactions__amount'))
This will filter all Registration instances whose price is equal to one of their transactions' amount. If you want all transactions amounts' sum to be equal or more than the registration price, you can use annotations to aggregate each registration's Sum:
paid_registrations = Registration.objects.\
annotate(ta=Sum('transactions__amount')).\ # annotate with ta sum
filter(price__lte=F('ta')) # filter those whose price is <= that sum

Categories

Resources