Get Hourly based sum in django - django

I have a model which contains two fields DateTimeField and an IntegerField.
I want to sum the total integers on the basis of hour.
class ModelA(models.Model):
date_time = models.DateTimeField()
relative_change = models.IntegerField()
Now i want to query the modelA so that it will return the total sum of relative_change for each hour,What's the good way to do it?
I checked this stackoverflow question but i am not clear what is 'hour' there.
The Output should look like
[
{
"time":"9-10",
"relative_change":"20"
},
{
"time":"10-11",
"relative_change":"40"
},
{
"time":"11-12",
"relative_change":12
}
]

This should do it:
from django.db.models import Sum
queryset = (
ModelA
.objects
.values('date_time__hour')
.annotate(relative_change_sum=Sum('relative_change')
)
You should expect the following result:
<QuerySet [{
'date_time__hour': 9,
'relative_change_sum': 20,
}, {
'date_time__hour': 10,
'relative_change_sum': 40,
}]
Here's more details about the 'hour' lookup:
https://docs.djangoproject.com/en/2.2/ref/models/querysets/#hour

Related

Better way to do the django api response

I have tried to create a model to represent a situation like this.
each question row can have a multiple question column. there are multiple types of question column
class QuestionRow(models.Model):
report_question = models.CharField(max_length=200)
report_group = models.CharField(max_length=20)
class QuestionColumn(models.Model):
header_text = models.CharField(max_length=100)
data_type = models.CharField(max_length=10)
class QuestionItem(models.Model):
column = models.ForeignKey(QuestionColumn)
row = models.ForeignKey(QuestionRow)
My objective is to find the optimized way to query and return the response.
where each question row in question item may or may not have multiple question columns. thinking of a way to do this using django annotate, aggregate
[{
"report_group": 1,
"question_row": "1a. Alarm system not active or not sufficient?",
"question_columns" : [
{
"header_text": "Yes/No",
"data_type": "Bool"
},
{
"header_text": "Risk Score",
"data_type": ""
}
]
},
{
"report_group": 1,
"question_row": "1b. Notification system not active or inactive?",
"question_columns" : [
{
"header_text": "Yes/No",
"data_type": "Bool"
},
{
"header_text": "Risk Score",
"data_type": ""
}
]
}]

How to display all dates for multiple model annotations in django

So I'm working on a website, and I want to have some kind of a summary page to display the data that I have. Let's say I have these models:
class IceCream(TimeStampedModel):
name = models.CharField()
color = models.CharField()
class Cupcake(TimeStampedModel):
name = models.CharField()
icing = models.CharField()
So on this page, users will be able to input a date range for the summary. I'm using DRF to serialize the data and to display them on the view actions. After I receive the filter dates, I will filter out the IceCream objects and Cupcake objects using the created field from TimeStampedModel.
#action(detail=False, methods=['get'])
def dessert_summary(self, request, **kwargs):
start_date = self.request.query_params.get('start_date')
end_date = self.request.query_params.get('end_date')
cupcakes = Cupcake.objects.filter(created__date__range=[start_date, end_date])
ice_creams = IceCream.objects.filter(created__date__range=[start_date, end_date])
After filtering, I want to count the total cupcakes and the total ice creams that is created within that period of time. But I also want to group them by the dates, and display the total count for both ice creams and cupcakes based on that date. So I tried to annotate the querysets like this:
cupcakes = cupcakes.annotate(date=TruncDate('created'))
cupcakes = cupcakes.values('date')
cupcakes = cupcakes.annotate(total_cupcakes=Count('id'))
ice_creams = ice_creams.annotate(date=TruncDate('created'))
ice_creams = ice_creams.values('date')
ice_creams = ice_creams.annotate(total_ice_creams=Count('id'))
So I want the result to be something like this:
{
'summary': [{
'date': "2020-09-24",
'total_ice_creams': 10,
'total_cupcakes': 7,
'total_dessert': 17
}, {
'date': "2020-09-25',
'total_ice_creams': 6,
'total_cupcakes': 5,
'total_dessert': 11
}]
}
But right now this is what I am getting:
{
'summary': [{
'cupcakes': [{
'date': "2020-09-24",
'total_cupcakes': 10,
}, {
'date': "2020-09-25",
'total_cupcakes': 5,
}],
'ice_creams': [{
'date': "2020-09-24",
'total_ice_creams': 7,
}, {
'date': "2020-09-27",
'total_ice_creams': 6,
}]
}]
}
What I want to ask is how do I get all the dates of both querysets, sum the ice creams and cupcakes, and return the data like the expected result? Thanks in advance for your help!
So here's what you can do:
gather all icecream/cupcakes count data into a dictionary
icecream_dict = {obj['date']: obj['count'] for obj in ice_creams}
cupcakes_dict = {obj['date']: obj['count'] for obj in cupcakes}
create a sorted list with all the dates
all_dates = sorted(set(list(icecream_dict.keys()) + list(cupcakes_dict.keys())))
create a list with items for each date and their count
result = []
for each_date in all_dates:
total_ice_creams = icecream_dict.get(each_date, 0)
total_cupcakes = cupcakes_dict.get(each_date, 0)
res = {
'date': each_date,
'total_ice_creams': total_ice_creams,
'total_cupcakes': total_cupcakes,
'total_dessert': total_ice_creams + total_cupcakes
}
result.append(res)
# check the result
print(result)
Hint: If you plan to add more desert-like models, consider have a base model Desert that you could query directly instead of querying each desert type model.

Django annotate several same objects in QuerySet by different related object

I got:
# models
class Building(models.Model):
...
class Flat(models.Model):
building = models.ForeignKey(Building)
class Profile(models.Model):
flats = models.ManyToManyField(Flat)
# logic
building = Building.objects.create()
flat_1 = Flat.objects.create(building=building)
flat_2 = Flat.objects.create(building=building)
profile = Profile.objects.create()
profile.flats.add(flat_1)
profile.flats.add(flat_2)
profiles = Profile.objects.filter(flats__building=building)
I got in profiles 2 same profiles. How i can annotate each of them by different flat like this: profiles.first().flat == flat_1 and profiles.last().flat == flat_2?
Maybe Subquery() but how?
UPD I need this in some DRF list view. Output in JSON must be something like:
[
{
"profile_id": 1,
"flat_id": 2
},
{
"profile_id": 1,
"flat_id": 3
}
]
To obtain that output, you could do:
data = Profile.objects.all().values('flats', 'id')
return Response(data=data)
in your DRF view.
You don't have to profile instances ...
I wrote the code for your exact needs at the end, but first wrote a couple of things that might be of interest.
In your code sample, you've created only one profile, I'm sure you are not getting 2 instances of Profile that are equals but only one.
The thing is if you have a QuerySet with only one entry, then:
profiles.first() == profiles.last() # True
since profile.first() and profiles.last() are the same instance.
You should try creating 2 Profile instances:
building = Building.objects.create()
flat_1 = Flat.objects.create(building=building)
flat_2 = Flat.objects.create(building=building)
profile_1 = Profile.objects.create() # You coud/should use bulk_create here.
profile_2 = Profile.objects.create()
profile_1.flats.add(flat_1)
profile_2.flats.add(flat_2)
Then
profiles = Profile.objects.filter(flats__building=building)
will return two different profile objects.
On the other hand, obtaining the JSON like you want ...
Following the example, you posted, filter flats by profile and get the values (this also works if you have more that one profile).
Flat.objects.filter(profile=profile_1).values('profile__id', 'id')
This will return something like ("id" stands for flats ids):
[
{
"profile__id": 1,
"id": 1
},
{
"profile__id": 1,
"id": 3
}
]
If you do not filter by profile (and you have more than one) you could get something like:
[
{
"profile__id": 1,
"id": 1
},
{
"profile__id": 2,
"id": 3
},
{
"profile__id": 2,
"id": 4
},
...
]
Annotating to get the EXACT json you want:
Filter as shown previously annotate, and get desired values:
Flat.objects.filter(profile=profile_1).annotate(
flat_id=F('id')
).annotate(
profile_id=F('profile__id')
).values(
'profile_id', 'flat_id'
)
will give exactly what you want:
[
{
"profile_id": 1,
"flat_id": 2
},
{
"profile_id": 1,
"flat_id": 3
}
]
You can do that with the right serializer and the right annotation:
The serializer:
class FlatSerializer(serializers.ModelSerializer):
class Meta:
model = Flat
fields = ('flat_id', 'building_id')
flat_id = serializers.CharField(read_only=True)
Then I would simply query Flats rather than profiles and serialize:
flats = Flat.objects \
.annotate(flat_id=F('id')) \
.filter(building=building)
serialized = FlatSerializer(flats, many=True)
print(serialized.data) # [ { flat_id: 1, building_id: 1 }, { flat_id: 2, building_id: 1 } ]
Let me know if that works for you

How to annotate field without duplicate fields django

So I basically have this simple model:
class BaseLesson(models.Model):
YOUTUBE_VIDEO = '0'
MARKDOWN = '1'
TYPE_CHOICES = (
(YOUTUBE_VIDEO, 'youtube-video'),
(MARKDOWN, 'markdown'),
)
type = models.CharField(
max_length=10, choices=TYPE_CHOICES, default=MARKDOWN, verbose_name=_('type'))
shown_users = models.ManyToManyField(
User, related_name='lessons', verbose_name=_('shown users'), blank=True)
objects = managers.BaseLessonManager()
There is a many-to-many relationship with the User model in shown_users
And I wanna annotate the is_shown status based on the many-to-many table, so I did this:
class BaseLessonManager(InheritanceManager, CachingManager):
def get_lesson_with_is_shown(self, user):
shown_user_case = django_models.Case(
django_models.When(shown_users__id=user.id,
then=django_models.Value(True)),
default=django_models.Value(False),
output_field=django_models.BooleanField())
return self.get_queryset().annotate(
is_shown=shown_user_case)
The problem with this is that if user1 and user2 saw the same lesson it will be duplicate, for example:
+-----------------+-----------+
| lesson_id | user_id |
+-----------------+-----------+
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
+-----------------+-----------+
For such case, I will get these duplicated lessons:
{
"id": 1
"type": "0",
"is_shown": true
},
{
"id": 1
"type": "0",
"is_shown": false
},
{
"id": 1
"type": "0",
"is_shown": false
}
So it's checking each related lesson field in the SHOWN_USERS table... sample photo:
https://imgur.com/GJCPWjk
What I tried so far:
1. Exclude:
I added an exclude expression to get rid of the extra lessons:
return self.get_queryset().annotate(
is_shown=shown_user_case).exclude(
django_models.Q(is_shown=False) & django_models.Q(shown_users__id=user.id))
And I think this is super ugly cuz if I have 1000 users and 50 lessons it means I'm taking all 50000 fields then filter 50 of them :(
Is there any cleaner way to do that ?
2. Distinct:
I have tried distinct before and it's not fixing the problem, instead of shown the lesson three times it will show:
once (is_shown = True),
and another time (is_shown = False)
I managed to fix the problem, and If figured out that my way of querying many-to-many is the problem, instead of shown_users__id == user.id I used id__in==user.lessons.values('id'), full code:
class BaseLessonManager(InheritanceManager, CachingManager):
def with_is_shown(self, user):
user_shown_lessons = user.lessons.values('id')
shown_user_case = django_models.Case(
django_models.When(id__in=user_shown_lessons,
then=django_models.Value(True)),
default=django_models.Value(False),
output_field=django_models.BooleanField())
return self.get_queryset().annotate(
is_shown=shown_user_case)

return data in from django queryset in the a specified format below

I have a database table with a field named groupname.
I have a queryset that counts the number of groupname as follows
count_groups=Eventgroup.objects.values('groupname').annotate(group_count=Count('groupname'))
I want to return this values in the following format:
[{
data : [[0, 4]],
label : "Event 2345"
}, {
data : [[0, 3]],
label : "Event 34567"
}, {
data : [[0, 5]],
label : "Event 4567",
}, {
data : [[0, 3]],
label : "Event 4356"
}];
where label should be the name of the groupname, in data,the 0 is always there but the second value is the count of the groupname.
here is my model:
class Eventgroup(models.Model):
event_id=models.CharField(max_length=500)
OS_CHOICE=(('Win 2003','windows 2003'),
('Win 2008','Windows 2008'),
('Win XP','Windows XP'),
('Win VISTA','Win VISTA'),
('Win 2007','Windows 2007'),
)
windows=models.CharField(max_length=20,choices=OS_CHOICE,default='Win 2003')
groupname=models.CharField(max_length=250)
def __unicode__(self):
return " %s, group name:%s" \
% (int(self.event_id), str(self.groupname.name),str(self.windows.name))
class Meta:
db_table= 'Eventgroups'
verbose_name_plural='eventgroups'
please give insights based on my data. regards
Something like this
def format_results(queryset):
for eventgroup in queryset:
yield {
'data': [[0, eventgroup['group_count']]],
'label': eventgroup['name'],
}
count_groups = Eventgroup.objects.values('groupname').annotate(group_count=Count('groupname'))
results = format_results(count_groups)
Using a generator like this behaves similarly to a queryset in that it is not immediately evaluated or fully loaded into memory as a list object.