This is my model:
class Sales(models.Model):
title = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
I am trying to get last 7 days data like i want to see how many sales occurred in last Friday, Saturday, Monday, etc date.
Only for the last 7 days but it should appear day by day,
like:
friday: 40
mondey:80
widnesday: 88 etc date
I am not getting how to do this...
I have tried like this below:
some_day_last_week = timezone.now().date() - timedelta(days=7)
sales = Sales.objects.filter(
created_at__gte=some_day_last_week,
).values(
'created_at__date'
).annotate(
created_count=Count('created_at__date')
)
Can anyone help me with this case?
You need to use TruncDay in your case:
from django.db.models.functions import TruncDay
sales = Sales.objects.filter(
created_at__gte=some_day_last_week,
).annotate(
day=TruncDay('created_at')
created_count=Count('created_at__date')
).values(
'day',
'created_count',
)
As a result you will get:
[
{'day': datetime.date(2019, 12, 1), 'created_count': 4.0},
{'day': datetime.date(2019, 12, 2), 'created_count': 10.0},
{'day': datetime.date(2019, 12, 3), 'created_count': 5.0},
{'day': datetime.date(2019, 12, 4), 'created_count': 1.0},
{'day': datetime.date(2019, 12, 5), 'created_count': 8.0},
{'day': datetime.date(2019, 12, 6), 'created_count': 4.0},
]
In order to get a date name you can convert datetime object with date.strftime("%A"):
for i in result:
print(i['day'].strftime("%A"))
# result
Sunday
Monday
...
Friday
Related
Hi stackoverflow community, my question is about django annotate.
Basically what I am trying to do is to find duplicated value with same values from two different fields in two different tables.
This is my models.py
class Order(models.Model):
id_order = models.AutoField(primary_key=True)
class OrderDelivery(models.Model):
order = models.ForeignKey(Order, on_delete=models.SET_NULL, null=True, blank=True)
delivery_address = models.TextField()
class OrderPickup(models.Model):
order = models.ForeignKey(Order, on_delete=models.SET_NULL, null=True, blank=True)
pickup_date = models.DateField(blank=True, null=True)
This is my current code:
dup_job = Order.objects.filter(
orderpickup__pickup_date__range=(start_date, end_date)
).values(
'orderdelivery__delivery_address',
'orderpickup__pickup_date',
).annotate(
duplicated=Count('orderdelivery__delivery_address')
).filter(
duplicated__gt=1
)
Based on what I have, I am getting result like this (delivery_address is omitted for privacy purpose):
{'orderdelivery__delivery_address': '118A', 'orderpickup__pickup_date': datetime.date(2022, 3, 9), 'duplicated': 2}
{'orderdelivery__delivery_address': '11', 'orderpickup__pickup_date': datetime.date(2022, 3, 2), 'duplicated': 6}
{'orderdelivery__delivery_address': '11 A ', 'orderpickup__pickup_date': datetime.date(2022, 3, 3), 'duplicated': 5}
{'orderdelivery__delivery_address': '21', 'orderpickup__pickup_date': datetime.date(2022, 3, 10), 'duplicated': 3}
{'orderdelivery__delivery_address': '642', 'orderpickup__pickup_date': datetime.date(2022, 3, 7), 'duplicated': 2}
{'orderdelivery__delivery_address': '642', 'orderpickup__pickup_date': datetime.date(2022, 3, 8), 'duplicated': 2}
{'orderdelivery__delivery_address': 'N/A,5', 'orderpickup__pickup_date': datetime.date(2022, 3, 8), 'duplicated': 19}
Is there a way to get the id_order of those 'duplicated'?
I have tried include id_order in .values() but the output will not be accurate as the annotation is grouping by the id_order instead of delivery_address.
Thank you in advance
You can get the smallest (or largest) item with a Min [Django-doc] (or Max) aggregate:
from django.db.models import Min
dup_job = Order.objects.filter(
orderpickup__pickup_date__range=(start_date, end_date)
).values(
'orderdelivery__delivery_address',
'orderpickup__pickup_date',
).annotate(
min_id_order=Min('id_order')
duplicated=Count('orderdelivery__delivery_address')
).filter(
duplicated__gt=1
)
or for postgresql, you can make use of the ArrayAgg [Django-doc] to generate a list:
# PostgreSQL only
from django.contrib.postgres.aggregates import ArrayAgg
dup_job = Order.objects.filter(
orderpickup__pickup_date__range=(start_date, end_date)
).values(
'orderdelivery__delivery_address',
'orderpickup__pickup_date',
).annotate(
min_id_order=ArrayAgg('id_order')
duplicated=Count('orderdelivery__delivery_address')
).filter(
duplicated__gt=1
)
I am trying to query and the group is the Order of the last 6 months.
and this is my models:
class Order(models.Model):
created_on = models.DateTimeField(_("Created On"), auto_now_add=True)
and this is my method to parse month:
from django.db.models import Func
class Month(Func):
"""
Method to extract month
"""
function = 'EXTRACT'
template = '%(function)s(MONTH from %(expressions)s)'
output_field = models.IntegerField()
And this is my query:
current_date = date.today()
months_ago = 6
six_month_previous_date = current_date - timedelta(days=(months_ago * 365 / 12))
order = Order.objects.filter(
created_on__gte=six_month_previous_date,
).annotate(
month=Month('created_on')
).values(
'month'
).annotate(
count=Count('id')
).values(
'month',
'count'
).order_by(
'month'
)
In my database order table, there is only on entry:
So it is returning
[{'month': 10, 'count': 1}]
But i dont want like this, i want like these of last 6 month, if in one month, there is no sales, it should return the count: 0
Like thise bellow:
[
{'month': 10, 'count': 1},
{'month': 9, 'count': 0}
{'month': 8, 'count': 0}
{'month': 7, 'count': 0}
{'month': 6, 'count': 0}
{'month': 5, 'count': 0}
]
A database works under the closed world assumption, so it will not insert rows with 0. You can however post-process the list.
from django.utils.timezone import now
order = Order.objects.filter(
created_on__gte=six_month_previous_date,
).values(
month=Month('created_on')
).annotate(
count=Count('id')
).order_by('month')
order = {r['month']: r['count'] for r in order}
month = now().month
result = [
{'month': (m % 12)+1, 'count': order.get((m % 12) + 1, 0)}
for m in range(month-1, month-8, -1)
]
Note that Django already has an ExtractMonth function [Django-doc].
I'm having trouble performing a simple transformation with the django orm.
Desired outcome should look like this:
2018-08
2018-07
2018-06
...
And is created with this sql:
select
distinct
strftime('%Y',a."Buchung") || "-" ||
strftime('%m',a."Buchung") as YearMonth
from
hhdata_transaktion a
order by
1 desc
I need it for a ModelChoiceField as queryset, so I'm bound to the ORM here?
My try
from django.db.models.functions import TruncMonth, TruncYear
Transaktion.objects
.annotate(year=TruncYear('Buchung'),
month=TruncMonth('Buchung'))
.distinct()
.order_by('-year', '-month')
.values('year','month')
returns:
<QuerySet [{'year': datetime.date(2018, 1, 1), 'month': datetime.date(2018, 8, 1)}, {'year': datetime.date(2018, 1, 1), 'month': datetime.date(2018, 7, 1)}, {'year': datetime.date(2018, 1, 1), 'month': datetime.date(2018, 6, 1)}, {'year': datetime.date(2018, 1, 1), 'month': datetime.date(2018, 5, 1)}, {'year': datetime.date(2018, 1, 1), 'month': datetime.date(2018, 4, 1)}, {'year': datetime.date(2018, 1, 1), 'month': datetime.date(2018, 3, 1)}, {'year': datetime.date(2018, 1, 1), 'month': datetime.date(2018, 2, 1)}, {'year': datetime.date(2018, 1, 1), 'month': datetime.date(2018, 1, 1)}, {'year': datetime.date(2017, 1, 1), 'month': datetime.date(2017, 12, 1)}, {'year': datetime.date(2017, 1, 1), 'month': datetime.date(2017, 11, 1)}, {'year': datetime.date(2017, 1, 1), 'month': datetime.date(2017, 10, 1)}, {'year': datetime.date(2017, 1, 1), 'month': datetime.date(2017, 9, 1)}, {'year': datetime.date(2017, 1, 1), 'month': datetime.date(2017, 8, 1)}]>
I have the feeling I'm miles away from the desired result..
If you want to obtain the year or month, you can use ExtractYear [Django-doc] and ExtractMonth [Django-doc] respectively. Truncating will give you the start of the year or month.
So we can rewrite the query to:
from django.db.models.functions import ExtractMonth, ExtractYear
qs = Transaktion.objects.annotate(
year=ExtractYear('Buchung'),
month=ExtractMonth('Buchung')
).order_by('-year', '-month').values('year','month').distinct()
Although it is possible to do the processing at SQL level, I think it will make work more complex. For example if you concatenate the numbers in SQL, it will probably require some work to get leading zeros for months (less than 10). Furthermore it is likely that the query contains "SQL dialect"-specific features making it less portable.
Therefore I suggest to do the post processing at the Django/Python level. For exampe with:
from django.db.models.functions import ExtractMonth, ExtractYear
class MyForm(forms.Form):
my_choice_field = forms.ChoiceField()
# ...
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
qs = Transaktion.objects.annotate(
year=ExtractYear('Buchung'),
month=ExtractMonth('Buchung')
).order_by('-year', '-month').values('year','month').distinct()
self.fields['my_choice_field'].choices = [
(row['year']*100+row['month'], '{}-{:02d}'.format(row['year'], row['month'])
for row in qs
]
Here we thus generate a list of 2-tuples where the first element is some sort of number we use to identify the choice (I here multiplied the year by 100, such that 201804 is april 2018). The second element of the tuple is the string that determines the format.
If you want a list of strings like 2018-06, something like that should work:
[ '%i-%02i' % (x.Buchung.year, x.Buchung.month) for x in Transaktion.objects.order_by(-Buchung) ]
class Ticket(models.Model):
booked_at = models.DateTimeField(default=timezone.now)
bought = models.BooleanField(default=False)
I would like to group tickets by booked day to get list of ticket or ticket's id for each day. Something like this:
[
{
'day': datetime.datetime(2018, 5, 6, 0, 0, ...>),
'ticket_list': [1, 2, 3, 4],
},
{
'day': datetime.datetime(2018, 5, 7, 0, 0, ...>),
'ticket_list': [5, 6, 7, 8, 9],
}
]
I could group tickets by day this way and count total tickets per day,
Ticket.objects.filter(bought=True).annotate(day=TruncDay('booked_at')).values('day').annotate(c=Count('id')).order_by()
But I cannot figure out how to group by day and return ticket objects for that day. Could you please help me solve this.
Thank you
I'd like to sum all the event durations per day. This is my model:
class Event(models.Model):
start = models.DateTimeField()
end = models.DateTimeField()
Sample data:
import datetime
from random import randint
for i in range(0, 1000):
start = datetime.datetime(
year=2016,
month=1,
day=randint(1, 10),
hour=randint(0, 23),
minute=randint(0, 59),
second=randint(0, 59)
)
end = start + datetime.timedelta(seconds=randint(30, 1000))
Event.objects.create(start=start, end=end)
I can get the event count per day like so:
(I know extra is bad, but I'm using 1.9 at the moment. When I upgrade I'll move to using TruncDate)
Event.objects.extra({'date': 'date(start)'}).order_by('date').values('date').annotate(count=Count('id'))
[{'count': 131, 'date': datetime.date(2016, 1, 1)},
{'count': 95, 'date': datetime.date(2016, 1, 2)},
{'count': 99, 'date': datetime.date(2016, 1, 3)},
{'count': 85, 'date': datetime.date(2016, 1, 4)},
{'count': 87, 'date': datetime.date(2016, 1, 5)},
{'count': 94, 'date': datetime.date(2016, 1, 6)},
{'count': 97, 'date': datetime.date(2016, 1, 7)},
{'count': 111, 'date': datetime.date(2016, 1, 8)},
{'count': 97, 'date': datetime.date(2016, 1, 9)},
{'count': 104, 'date': datetime.date(2016, 1, 10)}]
I can annotate to add the duration:
In [3]: Event.objects.annotate(duration=F('end') - F('start')).first().duration
Out[3]: datetime.timedelta(0, 470)
But I can't figure out how to sum this annotation the same way I can count events. I've tried the following but I get a KeyError on 'duration'.
Event.objects.annotate(duration=F('end') - F('start')).extra({'date': 'date(start)'}).order_by('date').values('date').annotate(total_duration=Sum('duration'))
And If I add duration to the values clause then it no longer groups by date.
Is this possible in a single query and without adding a duration field to the model?
I was about to write an answer that Django ORM does not support this. And yes, then I spent another hour on this problem (in addition to the 1,5 hours already spent before starting to write this answer), but as it turns out, Django does support it. And without hacking. Good news!
import datetime as dt
from django.db import models
from django.db.models import F, Sum, When, Case
from django.db.models.functions import TruncDate
from app.models import Event
a = Event.objects.annotate(date=TruncDate('start')).values('date').annotate(
day_duration=Sum(Case(
When(date=TruncDate(F('start')), then=F('end') - F('start')),
default=dt.timedelta(), output_field=models.DurationField()
))
)
And some preliminary tests to (hopefully) prove that this stuff actually does what you asked.
In [71]: a = Event.objects.annotate(date=TruncDate('start')).values('date').annotate(day_duration=Sum(Case(
...: When(date=TruncDate(F('start')), then=F('end') - F('start')),
...: default=dt.timedelta(), output_field=models.DurationField()
...: ))
...: )
In [72]: for e in a:
...: print(e)
...:
{'day_duration': datetime.timedelta(0, 41681), 'date': datetime.date(2016, 1, 10)}
{'day_duration': datetime.timedelta(0, 46881), 'date': datetime.date(2016, 1, 3)}
{'day_duration': datetime.timedelta(0, 48650), 'date': datetime.date(2016, 1, 1)}
{'day_duration': datetime.timedelta(0, 52689), 'date': datetime.date(2016, 1, 8)}
{'day_duration': datetime.timedelta(0, 45788), 'date': datetime.date(2016, 1, 5)}
{'day_duration': datetime.timedelta(0, 49418), 'date': datetime.date(2016, 1, 7)}
{'day_duration': datetime.timedelta(0, 45984), 'date': datetime.date(2016, 1, 9)}
{'day_duration': datetime.timedelta(0, 51841), 'date': datetime.date(2016, 1, 2)}
{'day_duration': datetime.timedelta(0, 63770), 'date': datetime.date(2016, 1, 4)}
{'day_duration': datetime.timedelta(0, 57205), 'date': datetime.date(2016, 1, 6)}
In [73]: q = dt.timedelta()
In [74]: o = Event.objects.filter(start__date=dt.date(2016, 1, 7))
In [75]: p = Event.objects.filter(start__date=dt.date(2016, 1, 10))
In [76]: for e in o:
...: q += (e.end - e.start)
In [77]: q
Out[77]: datetime.timedelta(0, 49418) # Matches 2016.1.7, yay!
In [78]: q = dt.timedelta()
In [79]: for e in p:
...: q += (e.end - e.start)
In [80]: q
Out[80]: datetime.timedelta(0, 41681) # Matches 2016.1.10, yay!
NB! This works from version 1.9, I don't think you can do this with the earlier versions because the TruncDate function is missing. And before 1.8 you don't have the Case and When thingies as well of course.