Reputation: 4146
I am trying to perform a grouping on my model which looks like this:
class Restaurant(models.Model):
pass
class Order(models.Model):
created = models.DateTimeField(auto_now_add=True)
restaurant = models.ForeignKey('Restaurant')
Now I want to know how many orders were created each day. This means I need to get date from DateTime field and then a count.
If it's relevant, I'm getting Order
queryset in this way:
restaurant = Restaurant.objects.get(id=request.data['restaurant_id'])
orders = restaurant.order_set.filter(created__lte=some_date)
Now, how can I get what I want from orders
? I have tried things like:
orders.values('created').annotate(Count('created'))
Using TruncDate
and so on.
I am using Python 2 and Django 1.11.
EDIT To better express my intentions. I want something that I would achieve in pure python with something like this:
orders_by_date = {}
for order in orders:
if orders_by_date.get(datetime.date(orders[0].created.year, orders[0].created.month, orders[0].created.day):
orders_by_date[datetime.date(orders[0].created.year, orders[0].created.month, orders[0].created.day)] += 1
else:
orders_by_date[datetime.date(orders[0].created.year, orders[0].created.month, orders[0].created.day)] = 1
EDIT2 I was able to successfully display count for a single date:
orders.filter(created__date=datetime.date(2018, 6, 8)).aggregate(Count('id'))
Gave me {'id__count': 3}
. Now It would be perfect to group by all dates, not just this single one.
Upvotes: 5
Views: 7403
Reputation: 477854
You can fetch the data in one query with:
from django.db.models import DateField, Sum
from django.db.models.functions import Cast
query = Order.objects.filter(
restaurant=some_restaurant
).annotate(
create_date=Cast('created', DateField())
).values('create_date').annotate(
id_count=Count('id')
).order_by('create_date')
This will return a QuerySet
of dictionaries like:
<QuerySet [{'create_date': datetime.date(2017, 1, 31), 'id_count': 14},
{'create_date': datetime.date(2017, 2, 2), 'id_count': 25},
{'create_date': datetime.date(2017, 2, 9), 'id_count': 13},
{'create_date': datetime.date(2017, 2, 10), 'id_count': 2},
{'create_date': datetime.date(2017, 2, 16), 'id_count': 17},
{'create_date': datetime.date(2017, 2, 17), 'id_count': 89},
{'create_date': datetime.date(2017, 2, 20), 'id_count': 20},
{'create_date': datetime.date(2017, 2, 23), 'id_count': 18},
{'create_date': datetime.date(2017, 2, 24), 'id_count': 20},
{'create_date': datetime.date(2017, 2, 28), 'id_count': 20},
{'create_date': datetime.date(2017, 3, 1), 'id_count': 3},
{'create_date': datetime.date(2017, 3, 3), 'id_count': 9},
{'create_date': datetime.date(2017, 3, 7), 'id_count': 9},
{'create_date': datetime.date(2017, 3, 9), 'id_count': 1},
{'create_date': datetime.date(2017, 3, 10), 'id_count': 7},
{'create_date': datetime.date(2017, 3, 14), 'id_count': 2},
{'create_date': datetime.date(2017, 3, 15), 'id_count': 7},
{'create_date': datetime.date(2017, 3, 17), 'id_count': 9},
{'create_date': datetime.date(2017, 3, 22), 'id_count': 2},
{'create_date': datetime.date(2017, 3, 24), 'id_count': 8},
'...
(remaining elements truncated)...']>
(added formatting)
Behind the curtains, it will generate a query like:
SELECT CAST(`order`.`created` AS date) AS `create_date`,
COUNT(`order`.`id`) AS `id_count`
FROM `order`
WHERE `order`.`restaurant_id` = 123
GROUP BY CAST(`order`.`created` AS date)
ORDER BY `create_date` ASC
(where 123
is here a sample restaurant id).
So you can then for instance iterate over the result and construct a JSON object, etc.
We can for example translate it into a dictionary that maps datetime.date
objects to counts by iterating over the query with dictionary comprehension:
result = { t['create_date']: t['id_count'] for t in query }
Note that dates without any Order
will not be part of the queryset (nor of the result
dictionary, this is logical, since we take the Order
table as "root", and if there are no rows, then there will be no output)
Upvotes: 2
Reputation: 1243
You should be able to do orders.filter(created__date=datetime.date(2018, 6, 8)).values('id__count')
, which would give you a list of dictionaries: [{'id__count': 3},{'id__count': 1},...]
.
Here are the docs
Edit: Try annotating then getting the values:
orders_with_counts = orders.annotate(Count('id'))
dates_and_counts = orders_with_counts.values_list('created__date','id__count')
dct = dict(dates_and_counts)
dates_and_counts
would be a list of tuples: [(date1,count1),(date2,count2)...]
. dct
would be that converted to a dictionary.
Upvotes: 2