Group by weeks, months, days - django

I would like to group querysets by a date interval according to a datetime attribute of the model. I want to show average values from the model on a day-to-day, week-by-week and month-by-month basis.
E.g.
Week commencing 01/01/2017 - average distance: 30
Week commencing 08/01/2017 - average distance: 40
...
Can this be achieved with the standard Django queryset API?

Assuming the following model which might match your description
class Activity(models.Model):
timestamp = models.DateTimeField(auto_now_add=True, blank=True)
distance = models.IntegerField()
You can achieve a week by week statistic with the following query
from django.db.models.functions import ExtractWeek, ExtractYear
from django.db.models import Sum, Count
stats = (Activity.objects
.annotate(year=ExtractYear('timestamp'))
.annotate(week=ExtractWeek('timestamp'))
.values('year', 'week')
.annotate(avg_distance=Avg('distance'))
)
Sample output
<QuerySet [{'year': 2018, 'week': 31, 'distance': 3.2}]>
To recover the first day of week, check Get date from week number
In particular:
for record in stats:
week = "{year}-W{week}-1".format(year=record['year'], week=record['week'])
timestamp = datetime.datetime.strptime(week, "%Y-W%W-%w")

There are a bunch of field lookups specifically for date/datetime fields: week, day, month (should be combined with year) etc.

Related

Django ORM query on fk set

I have a problem with lookup that looks for a value in related set.
class Room(models.Model):
name = models.Charfield(max_lentgh=64)
class Availability(models.Model):
date = models.DateField()
closed = models.BooleanField(default=False)
room = models.ForeignKey(Room)
Considering that there is one availability for every date in a year. How can use ORM query to find whether room withing given daterange (i.e. 7 days) has:
availability exist for every day within given daterange
none of the availabilities has closed=True
I was unable to find any orm examples that check whether all objects within daterange exist
You can enumerate over the dates, and ensure that it has for that date an Availability with closed=False:
from datetime import date, timedelta
rooms = Room.objects.all()
start_date = date(2022, 7, 21) # first day
for dd in range(7): # number of days
dt = start_date + timedelta(days=dd)
rooms = rooms.filter(availability__date=dt, availability__closed=False)
The rooms will after the for loop have a QuerySet with all Rooms that have for all dates in that range Availability objects with closed=False.

In DJANGO, how do I query for records that do not exist after a certain date?

Assume I have a model as such:
class Invoices(models.Model):
customer = models.ForeignKey(Customer, ...)
date = models.DateField()
amount = models.FloatField()
I want to know all customers that have not purchased for the last 30 days.
Basically the inverse of this query:
no_sales = Invoices.objects.filter(
date__gte=certain_date_30_days_ago
).values(
'customer__code'
)
using .exclude() just excludes the customers who have purchased in the last 30 days but not include the people who have not purchased in the last 30 days.
Any thoughts? :) Thanks!
Using .exclude(…) [Django-doc] on the Invoice model will not work: it will not include any Customers that have never bought anything. But these will still include people that bought something before: indeed, if a Customer bought 100 days ago something, and then 20 days ago, that Customer will still be included, since the Invoice for the second transaction is included, but the one 100 days ago will still be retained.
You can work on the Customer model with .exclude(…) with:
from datetime import timedelta
from django.utils.timezone import now
Customer.objects.exclude(
invoices__date__gte=now()-timedelta(days=30)
)
This will return a QuerySet of Customers that are not related to an Invoices record in the last 30 days.

How to order by values in ArrayList in Django?

I have a model lets say products, which has a field prices that is basically an array:
prices = [price on Monday, price on Tuesday, price on Thursday]
How do I order products by prices on a specific day, eg: price on Monday?
I assume your prices field is an ArrayField of DecimalField.
You can access items like this prices__0, prices__1, ... (See index transforms)
For instance if you want to order by prices according to today's week day:
from datetime import date
weekday = date.today().weekday()
results = Products.objects.all().order_by('prices__{}'.format(weekday))
You can have another model:
class WeekPrice(...):
#The day field can be a choice field
day = models.CharField(....)
amount = models.DecimalField(..)
class Product(...):
weekly_prices = models.ManyToManyField(..)

Django view returning count of users based on last_login

I have a Django application where I have a view that returns the count of the users who did login today. Corresponding statement is as follows:
login_count= User.objects.filter(last_login__startswith=timezone.now().date()).count()
I want to get the count of users who did login once in the last week and last the month. Like, here timezone.now() returns today's date, is there anything like range which will cover a week or a month?
Yes, there is, and its even named range.
Example
import datetime
start_date = datetime.date(2005, 1, 1)
end_date = datetime.date(2005, 3, 31)
User.objects.filter(last_login__range=(start_date, end_date))
Of for the last week
today = datetime.date.today()
last_week = today - datetime.timedelta(days=7)
User.objects.filter(last_login__range=(last_week, today))
However,
Filtering a DateTimeField with dates won’t include items on the last day, because the bounds are interpreted as “0am on the given date”. If pub_date was a DateTimeField, the above expression would be turned into this SQL:
A quick solution would be to simply add a day, so the date range includes up to the last minute of today, but not more (also update last_week).
today = datetime.date.today() + datetime.timedelta(days=1)
last_week = datetime.date.today() - datetime.timedelta(days=7)
User.objects.filter(last_login__range=(last_week, today))

django group query by day with timezone specification

I have a model with datetime field saved in UTC:
class Order(models.Model):
created = models.DateTimeField(db_index=True, auto_now_add=True)
...
I need to count number of orders per day for last 30 days to represent it with plot.
time_zone = 'Europe/Kiev'
date_to = datetime.date.today().strftime("%Y-%m-%d") + ' 23:59:59'
date_from = (datetime.date.today() - datetime.timedelta(days=29)).strftime("%Y-%m-%d") + ' 00:00:00'
date_range = (date_from, date_to)
data = Order.objects.filter(
created__range=date_range
).extra(
{'date': "date(created) AT TIME ZONE '{0}'".format(time_zone)}
).values('date').annotate(items=Count('id'))
But this call still groups data by day in UTC which is wrong. I can simply check that by counting number of orders in specific day:
data = Order.objects.filter(
created_range=('2016-05-03 00:00:00', '2016-05-03 23:59:59')
).count()
Is there any way to make database group items with timezone correctly in one call, because looping through 30 days and making 30 calls is slow?
Working with django 1.8.7 and postgres 9.4.4.1
As it turned out, there were some mistakes in extra({...}).
So
extra({'date': "date(created) AT TIME ZONE '{0}'".format(time_zone)}
Should be changed to
extra(select={'date': "date(goods_order.created AT TIME ZONE '{0}')".format(time_zone))
where goods_order.created is database_tablename.fieldname