Get respectives values from Django annotate method - django

I have the following query:
result = data.values('collaborator').annotate(amount=Count('cc'))
top = result.order_by('-amount')[:3]
This one, get the collaborator field from data, data is a Django Queryset, i am trying to make like a GROUP BY query, and it's functional, but when i call the .values() method on the top variable, it's returning all the models instances as dicts into a queryset, i need the annotate method result as a list of dicts:
The following is the top variable content on shell:
<QuerySet [{'collaborator': '1092788966', 'amount': 20}, {'collaborator': '1083692812', 'amount': 20}, {'collaborator': '1083572767', 'amount': 20}]>
But when i make list(top.values()) i get the following result:
[{'name': 'Alyse Caffin', 'cc': '1043346592', 'location': 'Wu’an', 'gender': 'MASCULINO', 'voting_place': 'Corporación Educativa American School Barranquilla', 'table_number': '6', 'status': 'ESPERADO', 'amount': 1}, {'name': 'Barthel Hanlin', 'cc': '1043238706', 'location': 'General Santos', 'gender': 'MASCULINO', 'voting_place': 'Colegio San José – Compañía de Jesús Barranquilla', 'table_number': '10', 'status': 'PENDIENTE', 'amount': 1}, {'name': 'Harv Gertz', 'cc': '1043550513', 'location': 'Makueni', 'gender': 'FEMENINO', 'voting_place': 'Corporación Educativa American School Barranquilla', 'table_number': '7', 'status': 'ESPERADO', 'amount': 1}]
I just want the result to be like:
[{'collaborator': '1092788966', 'amount': 20}, {'collaborator': '1083692812', 'amount': 20}, {'collaborator': '1083572767', 'amount': 20}]

there is something wrong, maybe a typo (also it seems you do not show the full query... something like data=yourmodel.objects.filter... is missing before):
The output of list(top.values()) returns a completely different model's fields then what you post as top Queryset- are you sure you really did:
result = data.values('collaborator').annotate(amount=Count('cc'))
top = result.order_by('-amount')[:3]
list(top.values())
because it should deliver what you expect (provided that data is a Queryset)

Related

Deal with multiple forms from the same Model in Django

I have a template page where I can create multiple jobs at the same time.
They all use the same Form, and when I submit the form, I receive POST data on the view like this:
<QueryDict: {'csrfmiddlewaretoken': ['(token)'], 'name': ['name1', 'name2', 'name3'], 'min': ['1', '2', '3'], 'max': ['10', '20', '30'], 'color': ['green', 'blue', 'red']}>
In the view, when I do
form = JobForm(request.POST), I get the following clean data {'name': 'name3', 'min': 3, 'max': 30, 'color': 'red'}. I have seen this solution, but I don't know how many Jobs will be created at the same time so I can't create different prefixes on the view, so I only send the form to the template like this form = JobForm().
How can I check if all the submitted data is valid and create all the objects in a single page submission?

Django - Query count of each distinct status

I have a model Model that has Model.status field. The status field can be of value draft, active or cancelled.
Is it possible to get a count of all objects based on their status? I would prefer to do that in one query instead of this:
Model.objects.filter(status='draft').count()
Model.objects.filter(status='active').count()
Model.objects.filter(status='cancelled').count()
I think that aggregate could help.
Yes, you can work with:
from django.db.models import Count
Model.objects.values('status').annotate(
count=Count('pk')
).order_by('count')
This will return a QuerSet of dictionaries:
<QuerySet [
{'status': 'active', 'count': 25 },
{'status': 'cancelled', 'count': 14 },
{'status': 'draft', 'count': 13 }
]>
This will however not list statuses for which no Model is present in the database.
Or you can make use of an aggregate with filter=:
from django.db.models import Count, Q
Model.objects.aggregate(
nactive=Count('pk', filter=Q(status='active')),
ncancelled=Count('pk', filter=Q(status='cancelled')),
ndraft=Count('pk', filter=Q(status='draft'))
)
This will return a dictionary:
{
'nactive': 25,
'ncancelled': 25,
'ndraft': 13
}
items for which it can not find a Model will be returned as None.

Separate nested python dictionary into 2 separate dictionaries

So I have a nested dictionary in python as follows:
{'name': 'Waffles',
'subCategories': [{'menu': [{'name': 'Fig & Honey with Fresh Cream','price': 120},
{'name': 'Toffeed Banana', 'price': 110}],
'name': 'Sweet',
'description': 'Sweet and yummy'},
{'menu': [{'name': 'Mushroom Cheese Gratin','price': 175},
{'name': 'Pepper Chicken Waffle', 'price': 180}],
'name': 'Savoury'
'description' : 'Salty and yummy'}]
}
What I am looking at is to separate out the dict into 2 dicts as follows:
{'name': 'Waffles(Sweet)',
'menu': [{'name': 'Fig & Honey with Fresh Cream','price': 120},
{'name': 'Toffeed Banana', 'price': 110}],
'description' : 'Sweet and yummy'}
{'name': 'Waffles(Savoury)',
'menu': [{'name': 'Mushroom Cheese Gratin','price': 175},
{'name': 'Pepper Chicken Waffle', 'price': 180}],
'description': 'Salty and yummy'}
Note that the name key is a combination of the same key in the outer and inner dicts
What would be the best way to tackle this ?
Hoping the code is self explantory!!
import pprint
d = {'name': 'Waffles',
'subCategories': [
{'menu': [{'name': 'Fig & Honey with Fresh Cream','price': 120},
{'name': 'Toffeed Banana', 'price': 110}],
'name': 'Sweet',
'description': 'Sweet and yummy'},
{'menu': [{'name': 'Mushroom Cheese Gratin','price': 175},
{'name': 'Pepper Chicken Waffle', 'price': 180}],
'name': 'Savoury',
'description' : 'Salty and yummy'}]
}
menu = []
for category in d.get('subCategories', []):
category['name'] = "{}({})".format(d['name'], category.get('name', ''))
menu.append(category)
pprint.pprint(menu)
and the sample output
[{'description': 'Sweet and yummy',
'menu': [{'name': 'Fig & Honey with Fresh Cream', 'price': 120},
{'name': 'Toffeed Banana', 'price': 110}],
'name': 'Waffles(Sweet)'},
{'description': 'Salty and yummy',
'menu': [{'name': 'Mushroom Cheese Gratin', 'price': 175},
{'name': 'Pepper Chicken Waffle', 'price': 180}],
'name': 'Waffles(Savoury)'}]
This is the code to do what you ask. Note that it can be optimized, but was left as is for readability.
dict = #your dictionary
# list to hold your new dictionaries (if there are more than one)
dict_list = []
# loop to extract
for item in dict['subCategories']:
d = {}
id = item['name']
# this could be made more compact
d.update({'name':'Waffles('+id+')'})
d.update({'menu':item['menu']})
d.update({'description':item['description']})
dict_list.append(d)
# print new dictionaries
for i in dict_list:
print(i)
Note that the program could be made more robust to handle arbitrary yamls (or json, I don't know what format this dictionary came from) if needed. Here the keys for extraction are hard coded.
Best of luck!
ps: there was formatting error in your starting dictionary

Wrongly big numbers when use multiple of Sum, Count aggregations in annotate

I have these models:
User:
email = EmailField()
Payment:
user = ForeignKey(User)
sum = DecimalField()
GuestAccount:
user = ForeignKey(User)
guest = ForeignKey(User)
I want to get user emails, amount of money that came from every user
and number of its guests accounts.
My query:
User.objects.annotate(
money=Sum('payment__sum'),
guests_number=Count('guestaccount')
).values('email', 'money', 'guests_number')
But money and guests_number in the result of the query are bigger then they really are:
{'guests_number': 0, 'email': 'a#b.cd', 'money': None}
{'guests_number': 20, 'email': 'user1#mail.com', 'money': Decimal('6600.00')}
{'guests_number': 4, 'email': 'user1000#test.com', 'money': Decimal('2500.00')}
{'guests_number': 0, 'email': 'zzzz#bbbbb.com', 'money': None}
I noticed that I get correct data if I split the query into 2 separate queries:
User.objects.annotate(money=Sum('payment__sum')).values('email', 'money')
User.objects.annotate(guests_number=Count('guestaccount')).values('email', 'guests_number')
Correct result of 1st half:
{'email': 'a#b.cd', 'money': None}
{'email': 'user1#mail.com', 'money': Decimal('1650.00')}
{'email': 'user1000#test.com', 'money': Decimal('1250.00')}
{'email': 'zzzz#bbbbb.com', 'money': None}
Correct result of 2nd half:
{'email': 'a#b.cd', 'guests_number': 0}
{'email': 'user1#mail.com', 'guests_number': 4}
{'email': 'user1000#test.com', 'guests_number': 2}
{'email': 'zzzz#bbbbb.com', 'guests_number': 0}
Also I noticed that I can add distinct=True in Count aggregation:
User.objects.annotate(
money=Sum('payment__sum'),
guests_number=Count('guestaccount', distinct=True)
).values('email', 'money', 'guests_number')
It fixes guests_number:
{'guests_number': 0, 'email': 'a#b.cd', 'money': None}
{'guests_number': 4, 'email': 'user1#mail.com', 'money': Decimal('6600.00')}
{'guests_number': 2, 'email': 'user1000#test.com', 'money': Decimal('2500.00')}
{'guests_number': 0, 'email': 'zzzz#bbbbb.com', 'money': None}
Unfortunatly, there are no distinct parameter in Sum aggregation.
What is wrong with my query? How to fix these numbers getting bigger with every aggregation in annotate?
Raw SQL query investigation showed that the problem comes from multiple LEFT OUTER JOINs. So I ended up with raw SQL:
User.objects.extra(select={
"money": """
SELECT SUM("website_payment"."sum")
FROM "website_payment"
WHERE "website_user"."id" = "website_payment"."user_id"
""",
"guests_number": """
SELECT COUNT("guests_guestaccount"."id")
FROM "guests_guestaccount"
WHERE "website_user"."id" = "guests_guestaccount"."user_id"
""",
}
).values('email', 'money', 'guests_number')
But I need to annotate these fields into queried objects and extra don't do it.

Django query — how to get list of dictionaries with M2M relation?

Let's say, I have this simple application with two models — Tag and SomeModel
class Tag(models.Model):
text = ...
class SomeModel(models.Model):
tags = models.ManyToManyField(Tag, related_name='tags')
And I want to get something like this from database:
[{'id': 1, 'tags': [1, 4, 8, 10]}, {'id': 6, 'tags': []}, {'id': 8, 'tags': [1, 2]}]
It is list of several SomeModel's dictionaries with SomeModel's id and ids of tags.
What should the Django query looks like? I tried this:
>>> SomeModel.objects.values('id', 'tags').filter(pk__in=[1,6,8])
[{'id': 1, 'tags': 1}, {'id': 1, 'tags': 4}, {'id': 1, 'tags': 8}, ...]
This is not what I want, so I tried something like this:
>>> SomeModel.objects.values_list('id', 'tags').filter(pk__in=[1,6,8])
[(1, 1), (1, 4), (1, 8), ...]
And my last try was:
>>> SomeModel.objects.values_list('id', 'tags', flat=True).filter(pk__in=[1,6,8])
...
TypeError: 'flat' is not valid when values_list is called with more than one field.
—
Maybe Django cannot do this, so the most similar result to what I want is:
[{'id': 1, 'tags': 1}, {'id': 1, 'tags': 4}, {'id': 1, 'tags': 8}, ...]
Is there any Python build-in method which transform it to this?
[{'id': 1, 'tags': [1, 4, 8, 10]}, {'id': 6, 'tags': []}, {'id': 8, 'tags': [1, 2]}]
— EDIT:
If I write method in SomeModel:
class SomeModel(models.Model):
tags = models.ManyToManyField(Tag, related_name='tags')
def get_tag_ids(self):
aid = []
for a in self.answers.all():
aid.append(a.id)
return aid
And then call:
>>> sm = SomeModel.objects.only('id', 'tags').filter(pk__in=[1,6,8])
# Hit database
>>> for s in sm:
... s.get_tag_ids()
...
>>> # Hit database 3 times.
This is not working, because it access to database 4 times. I need just one access.
As ArgsKwargs mentioned here in comments — I write my own code, which packs the list:
>>> sm = SomeModel.objects.values('id', 'tags').filter(pk__in=[1,6,8])
>>> a = {}
>>> for s in sm:
... if s['id'] not in a:
... a[s['id']] = [s['tags'],]
... else:
... a[s['id']].append(s['tags'])
...
The output of this code is exactly what I need, and it hit database only once. But it is not very elegant, I don't like this code :)
Btw. is better use pk or id in queries? .values('id', 'tags') or .values('pk', 'tags')?
What about a custom method on the model that returns a list of all tags
class Tag(models.Model):
text = ...
class SomeModel(models.Model):
tags = models.ManyToManyField(Tag, related_name='tags')
def all_tags(self):
return self.tags.values_list('pk',flat=True)
and then
SomeModel.objects.values('id', 'all_tags').filter(pk__in=[1,6,8])