Django model group as a list - django

I have a model with test data as below
id days
1, 30
1, 40
2, 10
2, 20
1, 90
I want output as
1, [30,40,90]
2, [10,20]
How can I get this in Django?

It's not much Django, it's pure python. To get the result as a mapping on 'id' as key:
result = {}
for obj in Mymodel.objects.all():
if result.has_key(obj.id):
result[obj.id].append(obj.days)
else:
result[obj.id] = [obj.days]
print result
>>> {1: [30, 40, 90], 2: [10, 20]}
The order of the elements in each list is not defined. If you require these to be ordered, best would be to append .order_by('days') on the Queryset.
A final remark: Your 'id' is not unique. I would consider a non-pk-column named 'id' a bad practice, since 'id' is Django's default name for the automatically created pk-field.

Related

Better way to make dictionary of counts from a QuerySet based on a field in Django?

I have a queryset of objects that could have any text value in their field and I would like a dictionary of the counts of the values in that field. For example:
I have a Test Drives Object which has a foreign key to vehicle, which has a make (Char field). I would like to know how many Test Drives each make had. I have actually solved it already, but I wonder if there is a better way using Django's inbuilt functionality.
My existing, working solution:
customer_test_drives_makes = customer_test_drives.values_list('vehicle__make', flat=True).order_by('vehicle__make').distinct()
customer_test_drives_makes_dictionary = {}
for make in customer_test_drives_makes :
customer_test_drives_makes_dictionary[make] = customer_test_drives.filter(vehicle__make=make).count()
print(customer_test_drives_makes_dictionary)
This prints:{'BMW': 1, 'Honda': 1, 'Hyundai': 1, 'Mazda': 2} Which is correct
There is. Try group by using annotate:
from django.db.models import Count
customer_test_drives.values('vehicle__make').annotate(count=Count('vehicle__make')).values()
Another example: Suppose your model looks like this:
class Cars(models.Model):
vehicle = models.ForeignKey(Vehicle, on_delete=models.DO_NOTHING)
Then you can run this queryset as well:
Vehicle.objects.annotate(car_count=Count('cars'))
Ruddra's answer got me most of the way, but I had to make the values distinct. Here are the final solutions:
Pre-existing way:
queryset_makes = queryset.values_list('vehicle__make', flat=True).order_by('vehicle__make').distinct()
queryset_makes_dictionairy = {}
for make in queryset_makes :
queryset_makes_dictionairy[make] = queryset.filter(vehicle__make=make).count()
print(queryset_makes_dictionairy)
Result:
{'BMW': 1, 'Honda': 4, 'Hyundai': 1, 'Mazda': 2, 'Mitsubishi': 1, 'Nissan': 2}
------------
Updated Way:
print(queryset.values('vehicle__make').order_by('vehicle__make').distinct().annotate(count=Count('vehicle__make')))
Result:
<QuerySet [{'vehicle__make': 'BMW', 'count': 1}, {'vehicle__make': 'Honda', 'count': 4}, {'vehicle__make': 'Hyundai', 'count': 1}, {'vehicle__make': 'Mazda', 'count': 2}, {'vehicle__make': 'Mitsubishi', 'count': 1}, {'vehicle__make': 'Nissan', 'count': 2}]>

Django - update dictionary with missing date values, set to 0

So to display a small bargraph using Django and Chart.js I constructed the following query on my model.
views.py
class BookingsView(TemplateView):
template_name = 'orders/bookings.html'
def get_context_data(self, **kwargs):
today = datetime.date.today()
seven_days = today + datetime.timedelta(days=7)
bookings = dict(Booking.objects.filter(start_date__range = [today, seven_days]) \
.order_by('start_date') \
.values_list('start_date') \
.annotate(Count('id')))
# Edit set default for missing dictonairy values
for dt in range(7):
bookings.setdefault(today+datetime.timedelta(dt), 0)
# Edit reorder the dictionary before using it in a template
context['bookings'] = OrderedDict(sorted(bookings.items()))
This led me to the following result;
# Edit; after setting the default on the dictionary and the reorder
{
datetime.date(2019, 8, 6): 12,
datetime.date(2019, 8, 7): 12,
datetime.date(2019, 8, 8): 0,
datetime.date(2019, 8, 9): 4,
datetime.date(2019, 8, 10): 7,
datetime.date(2019, 8, 11): 0,
datetime.date(2019, 8, 12): 7
}
To use the data in a chart I would like to add the missing start_dates into the dictionary but I'm not entirely sure how to do this.
So I want to update the dictionary with a value "0" for the 8th and 11th of August.
I tried to add the for statement but I got the error;
"'datetime.date' object is not iterable"
Like the error says, you can not iterate over a date object, so for start_date in seven_days will not work.
You can however use a for loop here like:
for dt in range(7):
bookings.setdefault(today+datetime.timedelta(dt), 0)
A dictionary has a .setdefault(..) function that allows you to set a value, given the key does not yet exists in the dicionary. This is thus shorter and more efficient than first checking if the key exists yourself since Python does not have to perform two lookups.
EDIT: Since python-3.7 dictionaries are ordered in insertion order (in the CPython version of python-3.6 that was already the case, but seen as an "implementation detail"). Since python-3.7, you can thus sort the dictionaries with:
bookings = dict(sorted(bookings.items()))
Prior to python-3.7, you can use an OrderedDict [Python-doc]:
from collections import OrderedDict
bookings = OrderedDict(sorted(bookings.items()))

Exact filtering on QuerySets [duplicate]

This question already has answers here:
How to do many-to-many Django query to find book with 2 given authors?
(4 answers)
Closed 9 years ago.
I have a pk list of instances of Tag model, say
pk_list = [10, 6, 3]
I have another model with m2m field of tags and an instance that contains exactly 3 tags (of above pks).
class Node(models.Model):
...
tags = models.ManyToManyField(Tag, related_name='nodes')
I'd like to retrieve a Node that contains exact set of tags as specified in my pk_list. When I do
Node.objects.filter(tags__in=pk_list)
it returns a list of three same instances
[<Node: My node title>, <Node: My node title>, <Node: My node title>]
Calling .get() doesn't work cause it have to return a single instance, obviously.
So, how do I retrieve a single instance?
I must note that if my pk_list was different eg. [10, 6] or [10, 6, 3, 7] then I must receive nothing. I need an exact matching.
Thanks
One approach is to use chain of filters:
node_query = Node.objects.all()
pk_list = [10, 6, 3]
for pk in pk_list:
node_query = node_query.filter(tags=pk)
Now node_query will match node, that has at least three tags with pk 10, 6, 3. To exact matching of three tags:
UPDATE:
Thanks to #janos and #Adrián López, the correct answer is:
from django.db.models import Count
pk_list = [10, 6, 3]
node_query = Node.objects.annotate(count=Count('tags')).filter(count=len(pk_list))
for pk in pk_list:
node_query = node_query.filter(tags__pk=pk)

Weird behavior in Django queryset union of values

I want to join the sum of related values from users with the users that do not have those values.
Here's a simplified version of my model structure:
class Answer(models.Model):
person = models.ForeignKey(Person)
points = models.PositiveIntegerField(default=100)
correct = models.BooleanField(default=False)
class Person(models.Model):
# irrelevant model fields
Sample dataset:
Person | Answer.Points
------ | ------
3 | 50
3 | 100
2 | 100
2 | 90
Person 4 has no answers and therefore, points
With the query below, I can achieve the sum of points for each person:
people_with_points = Person.objects.\
filter(answer__correct=True).\
annotate(points=Sum('answer__points')).\
values('pk', 'points')
<QuerySet [{'pk': 2, 'points': 190}, {'pk': 3, 'points': 150}]>
But, since some people might not have any related Answer entries, they will have 0 points and with the query below I use Coalesce to "fake" their points, like so:
people_without_points = Person.objects.\
exclude(pk__in=people_with_points.values_list('pk')).\
annotate(points=Coalesce(Sum('answer__points'), 0)).\
values('pk', 'points')
<QuerySet [{'pk': 4, 'points': 0}]>
Both of these work as intended but I want to have them in the same queryset so I use the union operator | to join them:
everyone = people_with_points | people_without_points
Now, for the problem:
After this, the people without points have their points value turned into None instead of 0.
<QuerySet [{'pk': 2, 'points': 190}, {'pk': 3, 'points': 150}, {'pk': 4, 'points': None}]>
Anyone has any idea of why this happens?
Thanks!
I should mention that I can fix that by annotating the queryset again and coalescing the null values to 0, like this:
everyone.\
annotate(real_points=Concat(Coalesce(F('points'), 0), Value(''))).\
values('pk', 'real_points')
<QuerySet [{'pk': 2, 'real_points': 190}, {'pk': 3, 'real_points': 150}, {'pk': 4, 'real_points': 0}]>
But I wish to understand why the union does not work as I expected in my original question.
EDIT:
I think I got it. A friend instructed me to use django-debug-toolbar to check my SQL queries to investigate further on this situation and I found out the following:
Since it's a union of two queries, the second query annotation is somehow not considered and the COALESCE to 0 is not used. By moving that to the first query it is propagated to the second query and I could achieve the expected result.
Basically, I changed the following:
# Moved the "Coalesce" to the initial query
people_with_points = Person.objects.\
filter(answer__correct=True).\
annotate(points=Coalesce(Sum('answer__points'), 0)).\
values('pk', 'points')
# Second query does not have it anymore
people_without_points = Person.objects.\
exclude(pk__in=people_with_points.values_list('pk')).\
values('pk', 'points')
# We will have the values with 0 here!
everyone = people_with_points | people_without_points

Select record from a list of ids through M2M relations

First, sorry for my bad english, this problem is not trivial to explain so I hope you will understand me.
I have 2 models as the following:
class A(models.Model):
code = models.CharField(unique=True, max_length=10)
list_of_b = models.ManyToManyField('B')
class B(models.Model):
code = models.CharField(unique=True, max_length=10)
I aim to retrieve instances of A which match exactly with a given list of B ids.
For example, imagine I have the following records of A in my database:
id: 1 - code: X - list_of_b: [1, 2, 4, 6]
id: 2 - code: Y - list_of_b: [2, 5, 6]
id: 3 - code: Z - list_of_b: [2, 3, 4, 5, 6]
With [2, 5, 6] as given list, I should retrieve the record 2 and 3, not 1.
I succeed to retrieve records with an exact match of ids with this query:
queryset = A.objects.prefetch_related('list_of_b')
queryset = queryset.annotate(nb=Count('list_of_b')).filter(nb=len(my_list))
for id in my_list:
queryset = queryset.filter(list_of_b=id)
It works for the record 2 but not for the record 3.
Thanks for any help. Don't hesitate to question me if not clear enough. ;)
EDIT:
Just one more thing: it's also possible that my_list contains more IDs than necessary. For exemple, with [2, 5, 6, 7] I should retrieve records 2 and 3.
Just remove the filter by count:
queryset = A.objects.prefetch_related('list_of_b')
for id in my_list:
queryset = queryset.filter(list_of_b=id)