blob: a15edf5e319908024704ecb224208226821b508b [file] [log] [blame]
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +02001"""REST API for accessing the crashreports statistics."""
Mitja Nikolaus21f3b1b2018-11-02 17:10:02 +01002import operator
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +02003import zipfile
Mitja Nikolaus24f4d122018-08-24 16:32:58 +02004from collections import OrderedDict
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +02005
Mitja Nikolaus24f4d122018-08-24 16:32:58 +02006from drf_yasg import openapi
7from drf_yasg.utils import swagger_auto_schema
8from rest_framework import generics, status
Dirk Vogt62ff7f22017-05-04 16:07:21 +02009from rest_framework import serializers
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +020010from rest_framework.exceptions import NotFound
Dirk Vogt62ff7f22017-05-04 16:07:21 +020011from rest_framework.response import Response
12from rest_framework.views import APIView
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +020013
14from django.core.exceptions import ObjectDoesNotExist
Dirk Vogt1accb672017-05-10 14:07:42 +020015from django.db.models.expressions import F
Mitja Nikolaus4d759da2018-08-28 15:31:29 +020016from django.utils.decorators import method_decorator
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +020017
18from django_filters.rest_framework import (
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +020019 DjangoFilterBackend,
20 DateFilter,
21 FilterSet,
22 CharFilter,
23 BooleanFilter,
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +020024)
25
26from crashreport_stats.models import (
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +020027 Version,
28 VersionDaily,
29 RadioVersion,
30 RadioVersionDaily,
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +020031)
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +020032from crashreports.models import Device, Crashreport, HeartBeat, LogFile
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +020033from crashreports.permissions import (
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +020034 HasStatsAccess,
Mitja Nikolaus4d759da2018-08-28 15:31:29 +020035 SWAGGER_SECURITY_REQUIREMENTS_ALL,
36 SWAGGER_SECURITY_REQUIREMENTS_OAUTH,
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +020037)
Mitja Nikolaus24f4d122018-08-24 16:32:58 +020038from crashreports.response_descriptions import default_desc
39
Mitja Nikolaus24f4d122018-08-24 16:32:58 +020040_RESPONSE_STATUS_200_DESCRIPTION = "OK"
41
Dirk Vogt62ff7f22017-05-04 16:07:21 +020042
Mitja Nikolaus24f4d122018-08-24 16:32:58 +020043_DEVICE_UPDATE_HISTORY_SCHEMA = openapi.Schema(
44 type=openapi.TYPE_ARRAY,
45 items=openapi.Schema(
46 type=openapi.TYPE_OBJECT,
47 title="DeviceUpdateHistoryEntry",
48 properties=OrderedDict(
49 [
50 ("build_fingerprint", openapi.Schema(type=openapi.TYPE_STRING)),
51 ("heartbeats", openapi.Schema(type=openapi.TYPE_INTEGER)),
52 ("max", openapi.Schema(type=openapi.TYPE_INTEGER)),
53 ("other", openapi.Schema(type=openapi.TYPE_INTEGER)),
54 ("prob_crashes", openapi.Schema(type=openapi.TYPE_INTEGER)),
55 ("smpl", openapi.Schema(type=openapi.TYPE_INTEGER)),
56 ("update_date", openapi.Schema(type=openapi.TYPE_STRING)),
57 ]
58 ),
59 ),
60)
61
62
Dirk Vogt62ff7f22017-05-04 16:07:21 +020063class DeviceUpdateHistory(APIView):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +020064 """View the update history of a specific device."""
65
Mitja Nikolaus048e20a2018-11-12 19:07:30 +010066 permission_classes = (HasStatsAccess,)
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +020067
Mitja Nikolaus24f4d122018-08-24 16:32:58 +020068 @swagger_auto_schema(
69 operation_description="Get the update history of a device",
Mitja Nikolaus4d759da2018-08-28 15:31:29 +020070 security=SWAGGER_SECURITY_REQUIREMENTS_ALL,
Mitja Nikolaus24f4d122018-08-24 16:32:58 +020071 responses=dict(
72 [
73 default_desc(NotFound),
74 (
75 status.HTTP_200_OK,
76 openapi.Response(
77 _RESPONSE_STATUS_200_DESCRIPTION,
78 _DEVICE_UPDATE_HISTORY_SCHEMA,
79 ),
80 ),
81 ]
82 ),
83 )
Mitja Nikolausb61247b2018-08-31 10:53:28 +020084 def get(self, request, uuid):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +020085 """Get the update history of a device.
86
87 Args:
88 request: Http request
89 uuid: The UUID of the device
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +020090
Mitja Nikolaus21f3b1b2018-11-02 17:10:02 +010091 Returns:
92 The update history of the requested device, sorted by the update
93 date.
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +020094
95 """
Mitja Nikolaus21f3b1b2018-11-02 17:10:02 +010096 device = Device.objects.get(uuid=uuid)
97
98 device_heartbeats = list(device.heartbeats.all())
99 device_crashreports = list(device.crashreports.all())
100
101 build_fingerprints = {hb.build_fingerprint for hb in device_heartbeats}
102
103 response = [
104 get_release_stats(
105 build_fingerprint,
106 device,
107 device_crashreports,
108 device_heartbeats,
109 )
110 for build_fingerprint in build_fingerprints
111 ]
112 response = sorted(response, key=operator.itemgetter("update_date"))
113
114 return Response(response)
115
116
117def get_release_stats(build_fingerprint, device, crashreports, heartbeats):
118 """Get the stats for a device for a specific release."""
119 heartbeats = filter_instances(
120 heartbeats, lambda hb: hb.build_fingerprint == build_fingerprint
121 )
122 crashreports = filter_instances(
123 crashreports, lambda c: c.build_fingerprint == build_fingerprint
124 )
125
126 stats = get_stats(heartbeats, crashreports)
127 stats.update(
128 {
129 "build_fingerprint": build_fingerprint,
130 "update_date": min([heartbeat.date for heartbeat in heartbeats]),
131 "max": device.id,
132 }
133 )
134
135 return stats
Dirk Vogt62ff7f22017-05-04 16:07:21 +0200136
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200137
Mitja Nikolaus24f4d122018-08-24 16:32:58 +0200138_DEVICE_REPORT_HISTORY_SCHEMA = openapi.Schema(
139 type=openapi.TYPE_ARRAY,
140 items=openapi.Schema(
141 type=openapi.TYPE_OBJECT,
142 title="DeviceReportHistoryEntry",
143 properties=OrderedDict(
144 [
145 ("date", openapi.Schema(type=openapi.TYPE_STRING)),
146 ("heartbeats", openapi.Schema(type=openapi.TYPE_INTEGER)),
147 ("other", openapi.Schema(type=openapi.TYPE_INTEGER)),
148 ("prob_crashes", openapi.Schema(type=openapi.TYPE_INTEGER)),
149 ("smpl", openapi.Schema(type=openapi.TYPE_INTEGER)),
150 ]
151 ),
152 ),
153)
154
155
Dirk Vogt62ff7f22017-05-04 16:07:21 +0200156class DeviceReportHistory(APIView):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200157 """View the report history of a specific device."""
158
Mitja Nikolaus048e20a2018-11-12 19:07:30 +0100159 permission_classes = (HasStatsAccess,)
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200160
Mitja Nikolaus24f4d122018-08-24 16:32:58 +0200161 @swagger_auto_schema(
162 operation_description="Get the report history of a device",
Mitja Nikolaus4d759da2018-08-28 15:31:29 +0200163 security=SWAGGER_SECURITY_REQUIREMENTS_ALL,
Mitja Nikolaus24f4d122018-08-24 16:32:58 +0200164 responses=dict(
165 [
166 default_desc(NotFound),
167 (
168 status.HTTP_200_OK,
169 openapi.Response(
170 _RESPONSE_STATUS_200_DESCRIPTION,
171 _DEVICE_REPORT_HISTORY_SCHEMA,
172 ),
173 ),
174 ]
175 ),
176 )
Mitja Nikolausb61247b2018-08-31 10:53:28 +0200177 def get(self, request, uuid):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200178 """Get the report history of a device.
179
180 Args:
181 request: Http request
182 uuid: The UUID of the device
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200183
Mitja Nikolaus21f3b1b2018-11-02 17:10:02 +0100184 Returns: The report history of the requested device, sorted by date.
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200185
186 """
Mitja Nikolaus21f3b1b2018-11-02 17:10:02 +0100187 device = Device.objects.get(uuid=uuid)
188
189 device_heartbeats = list(device.heartbeats.all())
190 device_crashreports = list(device.crashreports.all())
191
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100192 dates = {heartbeat.date for heartbeat in device_heartbeats}
Mitja Nikolaus21f3b1b2018-11-02 17:10:02 +0100193
194 response = [
195 get_stats_for_date(date, device_crashreports, device_heartbeats)
196 for date in sorted(dates)
197 ]
198
199 return Response(response)
200
201
202def get_stats_for_date(date, crashreports, heartbeats):
203 """Get the stats for a device for a specific date."""
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100204 heartbeats = filter_instances(heartbeats, lambda hb: hb.date == date)
Mitja Nikolaus21f3b1b2018-11-02 17:10:02 +0100205 crashreports = filter_instances(
206 crashreports, lambda c: c.date.date() == date
207 )
208
209 stats = get_stats(heartbeats, crashreports)
210 stats.update(date=date)
211 return stats
212
213
214def filter_instances(instances, filter_expr):
215 """Filter instances using a lambda filter function."""
216 return list(filter(filter_expr, instances))
217
218
219def get_stats(heartbeats, crashreports):
220 """Get the numbers of heartbeats and crashes per for each type."""
221 crashes = [
222 crashreport
223 for crashreport in crashreports
224 if crashreport.boot_reason in Crashreport.CRASH_BOOT_REASONS
225 ]
226 smpls = [
227 crashreport
228 for crashreport in crashreports
229 if crashreport.boot_reason in Crashreport.SMPL_BOOT_REASONS
230 ]
231 others = [
232 crashreport
233 for crashreport in crashreports
234 if crashreport not in crashes + smpls
235 ]
236 return {
237 "heartbeats": len(heartbeats),
238 "smpl": len(smpls),
239 "prob_crashes": len(crashes),
240 "other": len(others),
241 }
Dirk Vogt62ff7f22017-05-04 16:07:21 +0200242
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200243
Mitja Nikolaus24f4d122018-08-24 16:32:58 +0200244_STATUS_RESPONSE_SCHEMA = openapi.Schema(
245 title="Status",
246 type=openapi.TYPE_OBJECT,
247 properties=OrderedDict(
248 [
249 ("devices", openapi.Schema(type=openapi.TYPE_INTEGER)),
250 ("crashreports", openapi.Schema(type=openapi.TYPE_INTEGER)),
251 ("heartbeats", openapi.Schema(type=openapi.TYPE_INTEGER)),
252 ]
253 ),
254)
255
256
Dirk Vogt571168c2017-12-08 16:54:12 +0100257class Status(APIView):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200258 """View the number of devices, crashreports and heartbeats."""
259
Borjan Tchakalofffa134bd2018-04-09 16:16:11 +0200260 permission_classes = (HasStatsAccess,)
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200261
Mitja Nikolaus24f4d122018-08-24 16:32:58 +0200262 @swagger_auto_schema(
263 operation_description="Get the number of devices, crashreports and "
264 "heartbeats",
Mitja Nikolaus4d759da2018-08-28 15:31:29 +0200265 security=SWAGGER_SECURITY_REQUIREMENTS_OAUTH,
Mitja Nikolaus24f4d122018-08-24 16:32:58 +0200266 responses=dict(
267 [
268 (
269 status.HTTP_200_OK,
270 openapi.Response(
271 _RESPONSE_STATUS_200_DESCRIPTION,
272 _STATUS_RESPONSE_SCHEMA,
273 ),
274 )
275 ]
276 ),
277 )
Mitja Nikolausb61247b2018-08-31 10:53:28 +0200278 def get(self, request):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200279 """Get the number of devices, crashreports and heartbeats.
280
281 Args:
282 request: Http request
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200283
284 Returns: The number of devices, crashreports and heartbeats.
285
286 """
Dirk Vogt571168c2017-12-08 16:54:12 +0100287 num_devices = Device.objects.count()
288 num_crashreports = Crashreport.objects.count()
289 num_heartbeats = HeartBeat.objects.count()
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200290 return Response(
291 {
292 "devices": num_devices,
293 "crashreports": num_crashreports,
294 "heartbeats": num_heartbeats,
295 }
296 )
Dirk Vogt571168c2017-12-08 16:54:12 +0100297
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200298
Mitja Nikolaus24f4d122018-08-24 16:32:58 +0200299_DEVICE_STAT_OVERVIEW_SCHEMA = openapi.Schema(
300 title="DeviceStatOverview",
301 type=openapi.TYPE_OBJECT,
302 properties=OrderedDict(
303 [
304 ("board_date", openapi.Schema(type=openapi.TYPE_STRING)),
305 ("crashes_per_day", openapi.Schema(type=openapi.TYPE_NUMBER)),
306 ("crashreports", openapi.Schema(type=openapi.TYPE_INTEGER)),
307 ("heartbeats", openapi.Schema(type=openapi.TYPE_INTEGER)),
308 ("last_active", openapi.Schema(type=openapi.TYPE_STRING)),
309 ("smpl_per_day", openapi.Schema(type=openapi.TYPE_NUMBER)),
310 ("smpls", openapi.Schema(type=openapi.TYPE_INTEGER)),
311 ("uuid", openapi.Schema(type=openapi.TYPE_STRING)),
312 ]
313 ),
314)
315
316
Dirk Vogt62ff7f22017-05-04 16:07:21 +0200317class DeviceStat(APIView):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200318 """View an overview of the statistics of a device."""
319
Mitja Nikolaus048e20a2018-11-12 19:07:30 +0100320 permission_classes = (HasStatsAccess,)
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200321
Mitja Nikolaus24f4d122018-08-24 16:32:58 +0200322 @swagger_auto_schema(
323 operation_description="Get some general statistics for a device.",
Mitja Nikolaus4d759da2018-08-28 15:31:29 +0200324 security=SWAGGER_SECURITY_REQUIREMENTS_ALL,
Mitja Nikolaus24f4d122018-08-24 16:32:58 +0200325 responses=dict(
326 [
327 default_desc(NotFound),
328 (
329 status.HTTP_200_OK,
330 openapi.Response(
331 _RESPONSE_STATUS_200_DESCRIPTION,
332 _DEVICE_STAT_OVERVIEW_SCHEMA,
333 ),
334 ),
335 ]
336 ),
337 )
Mitja Nikolausb61247b2018-08-31 10:53:28 +0200338 def get(self, request, uuid):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200339 """Get some general statistics for a device.
340
341 Args:
342 request: Http request
343 uuid: The UUID of the device
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200344
345 Returns: Some general information of the device in a dictionary.
346
347 """
348 device = Device.objects.filter(uuid=uuid)
Mitja Nikolausc6e5ca02018-11-02 15:30:45 +0100349
350 heartbeat_instances = HeartBeat.objects.filter(device=device)
351 if heartbeat_instances.exists():
352 last_active = heartbeat_instances.order_by("-date")[0].date
353 else:
354 last_active = device[0].board_date
355
356 heartbeats = heartbeat_instances.count()
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200357 crashreports = (
358 Crashreport.objects.filter(device=device)
359 .filter(boot_reason__in=Crashreport.CRASH_BOOT_REASONS)
360 .count()
361 )
362 crashes_per_day = (
363 crashreports * 1.0 / heartbeats if heartbeats > 0 else 0
364 )
365 smpls = (
366 Crashreport.objects.filter(device=device)
367 .filter(boot_reason__in=Crashreport.SMPL_BOOT_REASONS)
368 .count()
369 )
370 smpl_per_day = smpls * 1.0 / heartbeats if heartbeats > 0 else 0
Dirk Vogt62ff7f22017-05-04 16:07:21 +0200371 return Response(
372 {
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200373 "uuid": uuid,
374 "last_active": last_active,
375 "heartbeats": heartbeats,
376 "crashreports": crashreports,
377 "crashes_per_day": crashes_per_day,
378 "smpls": smpls,
379 "smpl_per_day": smpl_per_day,
380 "board_date": device[0].board_date,
381 }
382 )
Dirk Vogt62ff7f22017-05-04 16:07:21 +0200383
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200384
Mitja Nikolaus24f4d122018-08-24 16:32:58 +0200385_LOG_FILE_SCHEMA = openapi.Schema(title="LogFile", type=openapi.TYPE_FILE)
386
387
Dirk Vogt62ff7f22017-05-04 16:07:21 +0200388class LogFileDownload(APIView):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200389 """View for downloading log files."""
390
Mitja Nikolaus048e20a2018-11-12 19:07:30 +0100391 permission_classes = (HasStatsAccess,)
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200392
Mitja Nikolaus24f4d122018-08-24 16:32:58 +0200393 @swagger_auto_schema(
394 operation_description="Get a log file.",
Mitja Nikolaus4d759da2018-08-28 15:31:29 +0200395 security=SWAGGER_SECURITY_REQUIREMENTS_ALL,
Mitja Nikolaus24f4d122018-08-24 16:32:58 +0200396 responses=dict(
397 [
398 default_desc(NotFound),
399 (
400 status.HTTP_200_OK,
401 openapi.Response(
402 _RESPONSE_STATUS_200_DESCRIPTION, _LOG_FILE_SCHEMA
403 ),
404 ),
405 ]
406 ),
407 )
Mitja Nikolausb61247b2018-08-31 10:53:28 +0200408 def get(self, request, id_logfile):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200409 """Get a logfile.
410
411 Args:
412 request: Http request
413 id_logfile: The id of the log file
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200414
415 Returns: The log file with the corresponding id.
416
417 """
Dirk Vogt62ff7f22017-05-04 16:07:21 +0200418 try:
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200419 logfile = LogFile.objects.get(id=id_logfile)
420 except ObjectDoesNotExist:
Dirk Vogt62ff7f22017-05-04 16:07:21 +0200421 raise NotFound(detail="Logfile does not exist.")
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200422 zip_file = zipfile.ZipFile(logfile.logfile.path)
Dirk Vogt62ff7f22017-05-04 16:07:21 +0200423 ret = {}
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200424 for file in zip_file.filelist:
425 file_open = zip_file.open(file)
426 ret[file.filename] = file_open.read()
Dirk Vogt62ff7f22017-05-04 16:07:21 +0200427 return Response(ret)
Dirk Vogt1accb672017-05-10 14:07:42 +0200428
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400429
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200430class _VersionStatsFilter(FilterSet):
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200431 first_seen_before = DateFilter(
432 field_name="first_seen_on", lookup_expr="lte"
433 )
434 first_seen_after = DateFilter(field_name="first_seen_on", lookup_expr="gte")
435 released_before = DateFilter(field_name="released_on", lookup_expr="lte")
436 released_after = DateFilter(field_name="released_on", lookup_expr="gte")
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200437
Dirk Vogt1accb672017-05-10 14:07:42 +0200438
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400439class _VersionStatsSerializer(serializers.ModelSerializer):
Borjan Tchakalofffa134bd2018-04-09 16:16:11 +0200440 permission_classes = (HasStatsAccess,)
Dirk Vogt1accb672017-05-10 14:07:42 +0200441
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200442
Mitja Nikolaus4d759da2018-08-28 15:31:29 +0200443@method_decorator(
444 name="get",
445 decorator=swagger_auto_schema(security=SWAGGER_SECURITY_REQUIREMENTS_OAUTH),
446)
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400447class _VersionStatsListView(generics.ListAPIView):
Borjan Tchakalofffa134bd2018-04-09 16:16:11 +0200448 permission_classes = (HasStatsAccess,)
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200449 filter_backends = (DjangoFilterBackend,)
Dirk Vogt1accb672017-05-10 14:07:42 +0200450
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200451
452class _DailyVersionStatsFilter(FilterSet):
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200453 date_start = DateFilter(field_name="date", lookup_expr="gte")
454 date_end = DateFilter(field_name="date", lookup_expr="lte")
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200455
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400456
457class _DailyVersionStatsSerializer(serializers.ModelSerializer):
Borjan Tchakalofffa134bd2018-04-09 16:16:11 +0200458 permission_classes = (HasStatsAccess,)
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400459
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200460
Mitja Nikolaus4d759da2018-08-28 15:31:29 +0200461@method_decorator(
462 name="get",
463 decorator=swagger_auto_schema(security=SWAGGER_SECURITY_REQUIREMENTS_OAUTH),
464)
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400465class _DailyVersionStatsListView(generics.ListAPIView):
Borjan Tchakalofffa134bd2018-04-09 16:16:11 +0200466 permission_classes = (HasStatsAccess,)
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200467 filter_backends = (DjangoFilterBackend,)
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400468
469
470class VersionSerializer(_VersionStatsSerializer):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200471 """Serializer for the Version class."""
472
473 class Meta: # noqa: D106
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400474 model = Version
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200475 fields = "__all__"
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400476
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200477
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400478class VersionFilter(_VersionStatsFilter):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200479 """Filter for Version instances."""
480
481 class Meta: # noqa: D106
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400482 model = Version
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200483 fields = "__all__"
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400484
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200485
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400486class VersionListView(_VersionStatsListView):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200487 """View for listing versions."""
488
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200489 queryset = Version.objects.all().order_by("-heartbeats")
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400490 filter_class = VersionFilter
491 serializer_class = VersionSerializer
492
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400493
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200494class VersionDailyFilter(_DailyVersionStatsFilter):
495 """Filter for VersionDaily instances."""
496
497 version__build_fingerprint = CharFilter()
498 version__is_official_release = BooleanFilter()
499 version__is_beta_release = BooleanFilter()
500
501 class Meta: # noqa: D106
Dirk Vogt1accb672017-05-10 14:07:42 +0200502 model = VersionDaily
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200503 fields = "__all__"
Dirk Vogt1accb672017-05-10 14:07:42 +0200504
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200505
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400506class VersionDailySerializer(_DailyVersionStatsSerializer):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200507 """Serializer for VersionDaily instances."""
508
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400509 build_fingerprint = serializers.CharField()
510
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200511 class Meta: # noqa: D106
Dirk Vogt1accb672017-05-10 14:07:42 +0200512 model = VersionDaily
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200513 fields = "__all__"
Dirk Vogt1accb672017-05-10 14:07:42 +0200514
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200515
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400516class VersionDailyListView(_DailyVersionStatsListView):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200517 """View for listing VersionDaily instances."""
518
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400519 queryset = (
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200520 VersionDaily.objects.annotate(
521 build_fingerprint=F("version__build_fingerprint")
522 )
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200523 .all()
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200524 .order_by("date")
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400525 )
526 filter_class = VersionDailyFilter
527 filter_fields = (
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200528 "version__build_fingerprint",
529 "version__is_official_release",
530 "version__is_beta_release",
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400531 )
Dirk Vogt1accb672017-05-10 14:07:42 +0200532 serializer_class = VersionDailySerializer
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400533
534
535class RadioVersionSerializer(_VersionStatsSerializer):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200536 """Serializer for RadioVersion instances."""
537
538 class Meta: # noqa: D106
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400539 model = RadioVersion
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200540 fields = "__all__"
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400541
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200542
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400543class RadioVersionFilter(_VersionStatsFilter):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200544 """Filter for RadioVersion instances."""
545
546 class Meta: # noqa: D106
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400547 model = RadioVersion
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200548 fields = "__all__"
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400549
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200550
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400551class RadioVersionListView(_VersionStatsListView):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200552 """View for listing RadioVersion instances."""
553
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200554 queryset = RadioVersion.objects.all().order_by("-heartbeats")
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400555 serializer_class = RadioVersionSerializer
556 filter_class = RadioVersionFilter
557
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400558
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200559class RadioVersionDailyFilter(_DailyVersionStatsFilter):
560 """Filter for RadioVersionDaily instances."""
561
562 version__radio_version = CharFilter()
563 version__is_official_release = BooleanFilter()
564 version__is_beta_release = BooleanFilter()
565
566 class Meta: # noqa: D106
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400567 model = RadioVersionDaily
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200568 fields = "__all__"
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400569
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200570
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400571class RadioVersionDailySerializer(_DailyVersionStatsSerializer):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200572 """Serializer for RadioVersionDaily instances."""
573
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400574 radio_version = serializers.CharField()
575
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200576 class Meta: # noqa: D106
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400577 model = RadioVersionDaily
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200578 fields = "__all__"
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400579
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200580
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400581class RadioVersionDailyListView(_DailyVersionStatsListView):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200582 """View for listing RadioVersionDaily instances."""
583
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400584 queryset = (
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200585 RadioVersionDaily.objects.annotate(
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200586 radio_version=F("version__radio_version")
587 )
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200588 .all()
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200589 .order_by("date")
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400590 )
591 filter_class = RadioVersionDailyFilter
592 filter_fields = (
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200593 "version__radio_version",
594 "version__is_official_release",
595 "version__is_beta_release",
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400596 )
597 serializer_class = RadioVersionDailySerializer