8
8
from django .utils import timezone
9
9
from dateutil .relativedelta import relativedelta
10
10
from django .db .models import ExpressionWrapper , F , FloatField , Max , Min , Sum , Avg , Q
11
- from django .db .models .functions import Cast , TruncDate
11
+ from django .db .models .functions import Cast , TruncDay , TruncHour , TruncMinute , TruncMonth
12
12
from rest_framework import mixins , pagination , viewsets
13
13
14
+ from django .db import connection
15
+
14
16
from ..models import SensorDataStat , LastActiveNodes , City , Node
15
- from .serializers import SensorDataStatSerializer , CitySerializer
17
+ from .serializers import RawSensorDataStatSerializer , CitySerializer
16
18
17
19
from feinstaub .sensors .views import StandardResultsSetPagination
18
20
@@ -76,7 +78,8 @@ def get_paginated_response(self, data_stats):
76
78
results [city_slug ][value_type ] = [] if from_date else {}
77
79
78
80
values = results [city_slug ][value_type ]
79
- include_result = getattr (values , "append" if from_date else "update" )
81
+ include_result = getattr (
82
+ values , "append" if from_date else "update" )
80
83
include_result (
81
84
{
82
85
"average" : data_stat ["average" ],
@@ -99,7 +102,7 @@ def get_paginated_response(self, data_stats):
99
102
100
103
class SensorDataStatView (mixins .ListModelMixin , viewsets .GenericViewSet ):
101
104
queryset = SensorDataStat .objects .none ()
102
- serializer_class = SensorDataStatSerializer
105
+ serializer_class = RawSensorDataStatSerializer
103
106
pagination_class = CustomPagination
104
107
105
108
@method_decorator (cache_page (3600 ))
@@ -112,60 +115,31 @@ def get_queryset(self):
112
115
city_slugs = self .request .query_params .get ("city" , None )
113
116
from_date = self .request .query_params .get ("from" , None )
114
117
to_date = self .request .query_params .get ("to" , None )
118
+ avg = self .request .query_params .get ("avg" , 'day' )
115
119
116
120
if to_date and not from_date :
117
- raise ValidationError ({"from" : "Must be provide along with to query" })
121
+ raise ValidationError (
122
+ {"from" : "Must be provide along with to query" })
118
123
if from_date :
119
- validate_date (from_date , {"from" : "Must be a date in the format Y-m-d." })
124
+ validate_date (
125
+ from_date , {"from" : "Must be a date in the format Y-m-d." })
120
126
if to_date :
121
- validate_date (to_date , {"to" : "Must be a date in the format Y-m-d." })
127
+ validate_date (
128
+ to_date , {"to" : "Must be a date in the format Y-m-d." })
122
129
123
- value_type_to_filter = self .request .query_params .get ("value_type" , None )
130
+ value_type_to_filter = self .request .query_params .get (
131
+ "value_type" , None )
124
132
125
133
filter_value_types = value_types [sensor_type ]
126
134
if value_type_to_filter :
127
- filter_value_types = set (value_type_to_filter .upper ().split ("," )) & set (
135
+ filter_value_types = "," . join ( set (value_type_to_filter .upper ().split ("," )) & set (
128
136
[x .upper () for x in value_types [sensor_type ]]
129
- )
137
+ ))
130
138
131
139
if not from_date and not to_date :
132
- return self ._retrieve_past_24hrs (city_slugs , filter_value_types )
133
-
134
- return self ._retrieve_range (from_date , to_date , city_slugs , filter_value_types )
135
-
136
- @staticmethod
137
- def _retrieve_past_24hrs (city_slugs , filter_value_types ):
138
- to_date = timezone .now ().replace (minute = 0 , second = 0 , microsecond = 0 )
139
- from_date = to_date - datetime .timedelta (hours = 24 )
140
-
141
- queryset = SensorDataStat .objects .filter (
142
- value_type__in = filter_value_types ,
143
- timestamp__gte = from_date ,
144
- timestamp__lte = to_date ,
145
- )
146
-
147
- if city_slugs :
148
- queryset = queryset .filter (city_slug__in = city_slugs .split ("," ))
149
-
150
- return (
151
- queryset .order_by ()
152
- .values ("value_type" , "city_slug" )
153
- .annotate (
154
- start_datetime = Min ("timestamp" ),
155
- end_datetime = Max ("timestamp" ),
156
- average = ExpressionWrapper (
157
- Sum (F ("average" ) * F ("sample_size" )) / Sum ("sample_size" ),
158
- output_field = FloatField (),
159
- ),
160
- minimum = Min ("minimum" ),
161
- maximum = Max ("maximum" ),
162
- )
163
- .order_by ("city_slug" )
164
- )
165
-
166
- @staticmethod
167
- def _retrieve_range (from_date , to_date , city_slugs , filter_value_types ):
168
- if not to_date :
140
+ to_date = timezone .now ().replace (minute = 0 , second = 0 , microsecond = 0 )
141
+ from_date = to_date - datetime .timedelta (hours = 24 )
142
+ elif not to_date :
169
143
from_date = beginning_of_day (from_date )
170
144
# Get data from_date until the end
171
145
# of day yesterday which is the beginning of today
@@ -174,31 +148,36 @@ def _retrieve_range(from_date, to_date, city_slugs, filter_value_types):
174
148
from_date = beginning_of_day (from_date )
175
149
to_date = end_of_day (to_date )
176
150
177
- queryset = SensorDataStat .objects .filter (
178
- value_type__in = filter_value_types ,
179
- timestamp__gte = from_date ,
180
- timestamp__lt = to_date ,
181
- )
182
-
183
- if city_slugs :
184
- queryset = queryset .filter (city_slug__in = city_slugs .split ("," ))
185
-
186
- return (
187
- queryset .annotate (date = TruncDate ("timestamp" ))
188
- .values ("date" , "value_type" )
189
- .annotate (
190
- city_slug = F ("city_slug" ),
191
- start_datetime = Min ("timestamp" ),
192
- end_datetime = Max ("timestamp" ),
193
- average = ExpressionWrapper (
194
- Sum (F ("average" ) * F ("sample_size" )) / Sum ("sample_size" ),
195
- output_field = FloatField (),
196
- ),
197
- minimum = Min ("minimum" ),
198
- maximum = Max ("maximum" ),
199
- )
200
- .order_by ("-date" )
201
- )
151
+ with connection .cursor () as cursor :
152
+ cursor .execute (
153
+ """
154
+ SELECT
155
+ sl.city as city_slug,
156
+ min(sd."timestamp") as start_datetime,
157
+ max(sd."timestamp") as end_datetime,
158
+ sum(CAST("value" as float)) / COUNT(*) AS average,
159
+ min(CAST("value" as float)) as minimum,
160
+ max(CAST("value" as float)) as maximum,
161
+ v.value_type
162
+ FROM
163
+ sensors_sensordatavalue v
164
+ INNER JOIN sensors_sensordata sd ON sd.id = sensordata_id
165
+ INNER JOIN sensors_sensorlocation sl ON sl.id = location_id
166
+ WHERE
167
+ v.value_type IN (%s)
168
+ """
169
+ +
170
+ ("AND sl.city IN (%s)" if city_slugs else "" )
171
+ +
172
+ """
173
+ AND sd."timestamp" >= TIMESTAMP %s
174
+ AND sd."timestamp" <= TIMESTAMP %s
175
+ GROUP BY
176
+ DATE_TRUNC(%s, sd."timestamp"),
177
+ v.value_type,
178
+ sl.city
179
+ """ , [filter_value_types , city_slugs , from_date , to_date , avg ] if city_slugs else [filter_value_types , from_date , to_date , avg ])
180
+ return cursor .fetchall ()
202
181
203
182
204
183
class CityView (mixins .ListModelMixin , viewsets .GenericViewSet ):
@@ -225,7 +204,8 @@ def list(self, request):
225
204
moved_to = None
226
205
# Get data stats from 5mins before last_data_received_at
227
206
if last_data_received_at :
228
- last_5_mins = last_data_received_at - datetime .timedelta (minutes = 5 )
207
+ last_5_mins = last_data_received_at - \
208
+ datetime .timedelta (minutes = 5 )
229
209
stats = (
230
210
SensorDataValue .objects .filter (
231
211
Q (sensordata__sensor__node = last_active .node .id ),
0 commit comments