88from django .utils import timezone
99from dateutil .relativedelta import relativedelta
1010from 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
1212from rest_framework import mixins , pagination , viewsets
1313
14- from ..models import SensorDataStat , LastActiveNodes , City , Node
15- from .serializers import SensorDataStatSerializer , CitySerializer
14+ from django .db import connection
15+
16+ from ..models import LastActiveNodes , City , Node
17+ from .serializers import RawSensorDataStatSerializer , CitySerializer
1618
1719from feinstaub .sensors .views import StandardResultsSetPagination
1820
@@ -76,7 +78,8 @@ def get_paginated_response(self, data_stats):
7678 results [city_slug ][value_type ] = [] if from_date else {}
7779
7880 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" )
8083 include_result (
8184 {
8285 "average" : data_stat ["average" ],
@@ -98,8 +101,7 @@ def get_paginated_response(self, data_stats):
98101
99102
100103class SensorDataStatView (mixins .ListModelMixin , viewsets .GenericViewSet ):
101- queryset = SensorDataStat .objects .none ()
102- serializer_class = SensorDataStatSerializer
104+ serializer_class = RawSensorDataStatSerializer
103105 pagination_class = CustomPagination
104106
105107 @method_decorator (cache_page (3600 ))
@@ -112,60 +114,31 @@ def get_queryset(self):
112114 city_slugs = self .request .query_params .get ("city" , None )
113115 from_date = self .request .query_params .get ("from" , None )
114116 to_date = self .request .query_params .get ("to" , None )
117+ avg = self .request .query_params .get ("avg" , 'day' )
115118
116119 if to_date and not from_date :
117- raise ValidationError ({"from" : "Must be provide along with to query" })
120+ raise ValidationError (
121+ {"from" : "Must be provide along with to query" })
118122 if from_date :
119- validate_date (from_date , {"from" : "Must be a date in the format Y-m-d." })
123+ validate_date (
124+ from_date , {"from" : "Must be a date in the format Y-m-d." })
120125 if to_date :
121- validate_date (to_date , {"to" : "Must be a date in the format Y-m-d." })
126+ validate_date (
127+ to_date , {"to" : "Must be a date in the format Y-m-d." })
122128
123- value_type_to_filter = self .request .query_params .get ("value_type" , None )
129+ value_type_to_filter = self .request .query_params .get (
130+ "value_type" , None )
124131
125132 filter_value_types = value_types [sensor_type ]
126133 if value_type_to_filter :
127- filter_value_types = set (value_type_to_filter .upper ().split ("," )) & set (
134+ filter_value_types = "," . join ( set (value_type_to_filter .upper ().split ("," )) & set (
128135 [x .upper () for x in value_types [sensor_type ]]
129- )
136+ ))
130137
131138 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 :
139+ to_date = timezone .now ().replace (minute = 0 , second = 0 , microsecond = 0 )
140+ from_date = to_date - datetime .timedelta (hours = 24 )
141+ elif not to_date :
169142 from_date = beginning_of_day (from_date )
170143 # Get data from_date until the end
171144 # of day yesterday which is the beginning of today
@@ -174,31 +147,36 @@ def _retrieve_range(from_date, to_date, city_slugs, filter_value_types):
174147 from_date = beginning_of_day (from_date )
175148 to_date = end_of_day (to_date )
176149
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- )
150+ with connection .cursor () as cursor :
151+ cursor .execute (
152+ """
153+ SELECT
154+ sl.city as city_slug,
155+ min(sd."timestamp") as start_datetime,
156+ max(sd."timestamp") as end_datetime,
157+ sum(CAST("value" as float)) / COUNT(*) AS average,
158+ min(CAST("value" as float)) as minimum,
159+ max(CAST("value" as float)) as maximum,
160+ v.value_type
161+ FROM
162+ sensors_sensordatavalue v
163+ INNER JOIN sensors_sensordata sd ON sd.id = sensordata_id
164+ INNER JOIN sensors_sensorlocation sl ON sl.id = location_id
165+ WHERE
166+ v.value_type IN (%s)
167+ """
168+ +
169+ ("AND sl.city IN (%s)" if city_slugs else "" )
170+ +
171+ """
172+ AND sd."timestamp" >= TIMESTAMP %s
173+ AND sd."timestamp" <= TIMESTAMP %s
174+ GROUP BY
175+ DATE_TRUNC(%s, sd."timestamp"),
176+ v.value_type,
177+ sl.city
178+ """ , [filter_value_types , city_slugs , from_date , to_date , avg ] if city_slugs else [filter_value_types , from_date , to_date , avg ])
179+ return cursor .fetchall ()
202180
203181
204182class CityView (mixins .ListModelMixin , viewsets .GenericViewSet ):
@@ -225,7 +203,8 @@ def list(self, request):
225203 moved_to = None
226204 # Get data stats from 5mins before last_data_received_at
227205 if last_data_received_at :
228- last_5_mins = last_data_received_at - datetime .timedelta (minutes = 5 )
206+ last_5_mins = last_data_received_at - \
207+ datetime .timedelta (minutes = 5 )
229208 stats = (
230209 SensorDataValue .objects .filter (
231210 Q (sensordata__sensor__node = last_active .node .id ),
0 commit comments