blob: d1dec3303ea1c1645ab047c5c33135031745611f [file] [log] [blame]
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +02001"""REST API for accessing the crashreports statistics."""
2import zipfile
Mitja Nikolaus24f4d122018-08-24 16:32:58 +02003from collections import OrderedDict
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +02004
Mitja Nikolaus24f4d122018-08-24 16:32:58 +02005from drf_yasg import openapi
6from drf_yasg.utils import swagger_auto_schema
7from rest_framework import generics, status
Dirk Vogt62ff7f22017-05-04 16:07:21 +02008from rest_framework import serializers
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +02009from rest_framework.exceptions import NotFound
Dirk Vogt62ff7f22017-05-04 16:07:21 +020010from rest_framework.response import Response
11from rest_framework.views import APIView
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +020012
13from django.core.exceptions import ObjectDoesNotExist
Dirk Vogt62ff7f22017-05-04 16:07:21 +020014from django.db import connection
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 HasRightsOrIsDeviceOwnerDeviceCreation,
35 HasStatsAccess,
Mitja Nikolaus4d759da2018-08-28 15:31:29 +020036 SWAGGER_SECURITY_REQUIREMENTS_ALL,
37 SWAGGER_SECURITY_REQUIREMENTS_OAUTH,
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +020038)
Mitja Nikolaus24f4d122018-08-24 16:32:58 +020039from crashreports.response_descriptions import default_desc
40
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +020041from . import raw_querys
42
Mitja Nikolaus24f4d122018-08-24 16:32:58 +020043_RESPONSE_STATUS_200_DESCRIPTION = "OK"
44
Dirk Vogt62ff7f22017-05-04 16:07:21 +020045
46def dictfetchall(cursor):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +020047 """Return all rows from a cursor as a dict."""
Dirk Vogt62ff7f22017-05-04 16:07:21 +020048 desc = cursor.description
49 return [
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +020050 dict(zip([col[0] for col in desc], row)) for row in cursor.fetchall()
Dirk Vogt62ff7f22017-05-04 16:07:21 +020051 ]
52
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +020053
Mitja Nikolaus24f4d122018-08-24 16:32:58 +020054_DEVICE_UPDATE_HISTORY_SCHEMA = openapi.Schema(
55 type=openapi.TYPE_ARRAY,
56 items=openapi.Schema(
57 type=openapi.TYPE_OBJECT,
58 title="DeviceUpdateHistoryEntry",
59 properties=OrderedDict(
60 [
61 ("build_fingerprint", openapi.Schema(type=openapi.TYPE_STRING)),
62 ("heartbeats", openapi.Schema(type=openapi.TYPE_INTEGER)),
63 ("max", openapi.Schema(type=openapi.TYPE_INTEGER)),
64 ("other", openapi.Schema(type=openapi.TYPE_INTEGER)),
65 ("prob_crashes", openapi.Schema(type=openapi.TYPE_INTEGER)),
66 ("smpl", openapi.Schema(type=openapi.TYPE_INTEGER)),
67 ("update_date", openapi.Schema(type=openapi.TYPE_STRING)),
68 ]
69 ),
70 ),
71)
72
73
Dirk Vogt62ff7f22017-05-04 16:07:21 +020074class DeviceUpdateHistory(APIView):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +020075 """View the update history of a specific device."""
76
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +020077 permission_classes = (HasRightsOrIsDeviceOwnerDeviceCreation,)
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +020078
Mitja Nikolaus24f4d122018-08-24 16:32:58 +020079 @swagger_auto_schema(
80 operation_description="Get the update history of a device",
Mitja Nikolaus4d759da2018-08-28 15:31:29 +020081 security=SWAGGER_SECURITY_REQUIREMENTS_ALL,
Mitja Nikolaus24f4d122018-08-24 16:32:58 +020082 responses=dict(
83 [
84 default_desc(NotFound),
85 (
86 status.HTTP_200_OK,
87 openapi.Response(
88 _RESPONSE_STATUS_200_DESCRIPTION,
89 _DEVICE_UPDATE_HISTORY_SCHEMA,
90 ),
91 ),
92 ]
93 ),
94 )
Mitja Nikolausb61247b2018-08-31 10:53:28 +020095 def get(self, request, uuid):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +020096 """Get the update history of a device.
97
98 Args:
99 request: Http request
100 uuid: The UUID of the device
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200101
102 Returns: The update history of the requested device.
103
104 """
Dirk Vogt62ff7f22017-05-04 16:07:21 +0200105 cursor = connection.cursor()
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200106 raw_querys.execute_device_update_history_query(cursor, {"uuid": uuid})
Dirk Vogt62ff7f22017-05-04 16:07:21 +0200107 res = dictfetchall(cursor)
108 return Response(res)
109
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200110
Mitja Nikolaus24f4d122018-08-24 16:32:58 +0200111_DEVICE_REPORT_HISTORY_SCHEMA = openapi.Schema(
112 type=openapi.TYPE_ARRAY,
113 items=openapi.Schema(
114 type=openapi.TYPE_OBJECT,
115 title="DeviceReportHistoryEntry",
116 properties=OrderedDict(
117 [
118 ("date", openapi.Schema(type=openapi.TYPE_STRING)),
119 ("heartbeats", openapi.Schema(type=openapi.TYPE_INTEGER)),
120 ("other", openapi.Schema(type=openapi.TYPE_INTEGER)),
121 ("prob_crashes", openapi.Schema(type=openapi.TYPE_INTEGER)),
122 ("smpl", openapi.Schema(type=openapi.TYPE_INTEGER)),
123 ]
124 ),
125 ),
126)
127
128
Dirk Vogt62ff7f22017-05-04 16:07:21 +0200129class DeviceReportHistory(APIView):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200130 """View the report history of a specific device."""
131
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200132 permission_classes = (HasRightsOrIsDeviceOwnerDeviceCreation,)
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200133
Mitja Nikolaus24f4d122018-08-24 16:32:58 +0200134 @swagger_auto_schema(
135 operation_description="Get the report history of a device",
Mitja Nikolaus4d759da2018-08-28 15:31:29 +0200136 security=SWAGGER_SECURITY_REQUIREMENTS_ALL,
Mitja Nikolaus24f4d122018-08-24 16:32:58 +0200137 responses=dict(
138 [
139 default_desc(NotFound),
140 (
141 status.HTTP_200_OK,
142 openapi.Response(
143 _RESPONSE_STATUS_200_DESCRIPTION,
144 _DEVICE_REPORT_HISTORY_SCHEMA,
145 ),
146 ),
147 ]
148 ),
149 )
Mitja Nikolausb61247b2018-08-31 10:53:28 +0200150 def get(self, request, uuid):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200151 """Get the report history of a device.
152
153 Args:
154 request: Http request
155 uuid: The UUID of the device
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200156
157 Returns: The report history of the requested device.
158
159 """
Dirk Vogt62ff7f22017-05-04 16:07:21 +0200160 cursor = connection.cursor()
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200161 raw_querys.execute_device_report_history(cursor, {"uuid": uuid})
Dirk Vogt62ff7f22017-05-04 16:07:21 +0200162 res = dictfetchall(cursor)
163 return Response(res)
164
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200165
Mitja Nikolaus24f4d122018-08-24 16:32:58 +0200166_STATUS_RESPONSE_SCHEMA = openapi.Schema(
167 title="Status",
168 type=openapi.TYPE_OBJECT,
169 properties=OrderedDict(
170 [
171 ("devices", openapi.Schema(type=openapi.TYPE_INTEGER)),
172 ("crashreports", openapi.Schema(type=openapi.TYPE_INTEGER)),
173 ("heartbeats", openapi.Schema(type=openapi.TYPE_INTEGER)),
174 ]
175 ),
176)
177
178
Dirk Vogt571168c2017-12-08 16:54:12 +0100179class Status(APIView):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200180 """View the number of devices, crashreports and heartbeats."""
181
Borjan Tchakalofffa134bd2018-04-09 16:16:11 +0200182 permission_classes = (HasStatsAccess,)
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200183
Mitja Nikolaus24f4d122018-08-24 16:32:58 +0200184 @swagger_auto_schema(
185 operation_description="Get the number of devices, crashreports and "
186 "heartbeats",
Mitja Nikolaus4d759da2018-08-28 15:31:29 +0200187 security=SWAGGER_SECURITY_REQUIREMENTS_OAUTH,
Mitja Nikolaus24f4d122018-08-24 16:32:58 +0200188 responses=dict(
189 [
190 (
191 status.HTTP_200_OK,
192 openapi.Response(
193 _RESPONSE_STATUS_200_DESCRIPTION,
194 _STATUS_RESPONSE_SCHEMA,
195 ),
196 )
197 ]
198 ),
199 )
Mitja Nikolausb61247b2018-08-31 10:53:28 +0200200 def get(self, request):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200201 """Get the number of devices, crashreports and heartbeats.
202
203 Args:
204 request: Http request
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200205
206 Returns: The number of devices, crashreports and heartbeats.
207
208 """
Dirk Vogt571168c2017-12-08 16:54:12 +0100209 num_devices = Device.objects.count()
210 num_crashreports = Crashreport.objects.count()
211 num_heartbeats = HeartBeat.objects.count()
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200212 return Response(
213 {
214 "devices": num_devices,
215 "crashreports": num_crashreports,
216 "heartbeats": num_heartbeats,
217 }
218 )
Dirk Vogt571168c2017-12-08 16:54:12 +0100219
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200220
Mitja Nikolaus24f4d122018-08-24 16:32:58 +0200221_DEVICE_STAT_OVERVIEW_SCHEMA = openapi.Schema(
222 title="DeviceStatOverview",
223 type=openapi.TYPE_OBJECT,
224 properties=OrderedDict(
225 [
226 ("board_date", openapi.Schema(type=openapi.TYPE_STRING)),
227 ("crashes_per_day", openapi.Schema(type=openapi.TYPE_NUMBER)),
228 ("crashreports", openapi.Schema(type=openapi.TYPE_INTEGER)),
229 ("heartbeats", openapi.Schema(type=openapi.TYPE_INTEGER)),
230 ("last_active", openapi.Schema(type=openapi.TYPE_STRING)),
231 ("smpl_per_day", openapi.Schema(type=openapi.TYPE_NUMBER)),
232 ("smpls", openapi.Schema(type=openapi.TYPE_INTEGER)),
233 ("uuid", openapi.Schema(type=openapi.TYPE_STRING)),
234 ]
235 ),
236)
237
238
Dirk Vogt62ff7f22017-05-04 16:07:21 +0200239class DeviceStat(APIView):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200240 """View an overview of the statistics of a device."""
241
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200242 permission_classes = (HasRightsOrIsDeviceOwnerDeviceCreation,)
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200243
Mitja Nikolaus24f4d122018-08-24 16:32:58 +0200244 @swagger_auto_schema(
245 operation_description="Get some general statistics for a device.",
Mitja Nikolaus4d759da2018-08-28 15:31:29 +0200246 security=SWAGGER_SECURITY_REQUIREMENTS_ALL,
Mitja Nikolaus24f4d122018-08-24 16:32:58 +0200247 responses=dict(
248 [
249 default_desc(NotFound),
250 (
251 status.HTTP_200_OK,
252 openapi.Response(
253 _RESPONSE_STATUS_200_DESCRIPTION,
254 _DEVICE_STAT_OVERVIEW_SCHEMA,
255 ),
256 ),
257 ]
258 ),
259 )
Mitja Nikolausb61247b2018-08-31 10:53:28 +0200260 def get(self, request, uuid):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200261 """Get some general statistics for a device.
262
263 Args:
264 request: Http request
265 uuid: The UUID of the device
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200266
267 Returns: Some general information of the device in a dictionary.
268
269 """
270 device = Device.objects.filter(uuid=uuid)
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200271 last_active = (
272 HeartBeat.objects.filter(device=device).order_by("-date")[0].date
273 )
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200274 heartbeats = HeartBeat.objects.filter(device=device).count()
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200275 crashreports = (
276 Crashreport.objects.filter(device=device)
277 .filter(boot_reason__in=Crashreport.CRASH_BOOT_REASONS)
278 .count()
279 )
280 crashes_per_day = (
281 crashreports * 1.0 / heartbeats if heartbeats > 0 else 0
282 )
283 smpls = (
284 Crashreport.objects.filter(device=device)
285 .filter(boot_reason__in=Crashreport.SMPL_BOOT_REASONS)
286 .count()
287 )
288 smpl_per_day = smpls * 1.0 / heartbeats if heartbeats > 0 else 0
Dirk Vogt62ff7f22017-05-04 16:07:21 +0200289 return Response(
290 {
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200291 "uuid": uuid,
292 "last_active": last_active,
293 "heartbeats": heartbeats,
294 "crashreports": crashreports,
295 "crashes_per_day": crashes_per_day,
296 "smpls": smpls,
297 "smpl_per_day": smpl_per_day,
298 "board_date": device[0].board_date,
299 }
300 )
Dirk Vogt62ff7f22017-05-04 16:07:21 +0200301
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200302
Mitja Nikolaus24f4d122018-08-24 16:32:58 +0200303_LOG_FILE_SCHEMA = openapi.Schema(title="LogFile", type=openapi.TYPE_FILE)
304
305
Dirk Vogt62ff7f22017-05-04 16:07:21 +0200306class LogFileDownload(APIView):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200307 """View for downloading log files."""
308
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200309 permission_classes = (HasRightsOrIsDeviceOwnerDeviceCreation,)
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200310
Mitja Nikolaus24f4d122018-08-24 16:32:58 +0200311 @swagger_auto_schema(
312 operation_description="Get a log file.",
Mitja Nikolaus4d759da2018-08-28 15:31:29 +0200313 security=SWAGGER_SECURITY_REQUIREMENTS_ALL,
Mitja Nikolaus24f4d122018-08-24 16:32:58 +0200314 responses=dict(
315 [
316 default_desc(NotFound),
317 (
318 status.HTTP_200_OK,
319 openapi.Response(
320 _RESPONSE_STATUS_200_DESCRIPTION, _LOG_FILE_SCHEMA
321 ),
322 ),
323 ]
324 ),
325 )
Mitja Nikolausb61247b2018-08-31 10:53:28 +0200326 def get(self, request, id_logfile):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200327 """Get a logfile.
328
329 Args:
330 request: Http request
331 id_logfile: The id of the log file
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200332
333 Returns: The log file with the corresponding id.
334
335 """
Dirk Vogt62ff7f22017-05-04 16:07:21 +0200336 try:
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200337 logfile = LogFile.objects.get(id=id_logfile)
338 except ObjectDoesNotExist:
Dirk Vogt62ff7f22017-05-04 16:07:21 +0200339 raise NotFound(detail="Logfile does not exist.")
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200340 zip_file = zipfile.ZipFile(logfile.logfile.path)
Dirk Vogt62ff7f22017-05-04 16:07:21 +0200341 ret = {}
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200342 for file in zip_file.filelist:
343 file_open = zip_file.open(file)
344 ret[file.filename] = file_open.read()
Dirk Vogt62ff7f22017-05-04 16:07:21 +0200345 return Response(ret)
Dirk Vogt1accb672017-05-10 14:07:42 +0200346
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400347
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200348class _VersionStatsFilter(FilterSet):
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200349 first_seen_before = DateFilter(
350 field_name="first_seen_on", lookup_expr="lte"
351 )
352 first_seen_after = DateFilter(field_name="first_seen_on", lookup_expr="gte")
353 released_before = DateFilter(field_name="released_on", lookup_expr="lte")
354 released_after = DateFilter(field_name="released_on", lookup_expr="gte")
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200355
Dirk Vogt1accb672017-05-10 14:07:42 +0200356
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400357class _VersionStatsSerializer(serializers.ModelSerializer):
Borjan Tchakalofffa134bd2018-04-09 16:16:11 +0200358 permission_classes = (HasStatsAccess,)
Dirk Vogt1accb672017-05-10 14:07:42 +0200359
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200360
Mitja Nikolaus4d759da2018-08-28 15:31:29 +0200361@method_decorator(
362 name="get",
363 decorator=swagger_auto_schema(security=SWAGGER_SECURITY_REQUIREMENTS_OAUTH),
364)
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400365class _VersionStatsListView(generics.ListAPIView):
Borjan Tchakalofffa134bd2018-04-09 16:16:11 +0200366 permission_classes = (HasStatsAccess,)
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200367 filter_backends = (DjangoFilterBackend,)
Dirk Vogt1accb672017-05-10 14:07:42 +0200368
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200369
370class _DailyVersionStatsFilter(FilterSet):
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200371 date_start = DateFilter(field_name="date", lookup_expr="gte")
372 date_end = DateFilter(field_name="date", lookup_expr="lte")
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200373
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400374
375class _DailyVersionStatsSerializer(serializers.ModelSerializer):
Borjan Tchakalofffa134bd2018-04-09 16:16:11 +0200376 permission_classes = (HasStatsAccess,)
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400377
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200378
Mitja Nikolaus4d759da2018-08-28 15:31:29 +0200379@method_decorator(
380 name="get",
381 decorator=swagger_auto_schema(security=SWAGGER_SECURITY_REQUIREMENTS_OAUTH),
382)
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400383class _DailyVersionStatsListView(generics.ListAPIView):
Borjan Tchakalofffa134bd2018-04-09 16:16:11 +0200384 permission_classes = (HasStatsAccess,)
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200385 filter_backends = (DjangoFilterBackend,)
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400386
387
388class VersionSerializer(_VersionStatsSerializer):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200389 """Serializer for the Version class."""
390
391 class Meta: # noqa: D106
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400392 model = Version
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200393 fields = "__all__"
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400394
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200395
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400396class VersionFilter(_VersionStatsFilter):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200397 """Filter for Version instances."""
398
399 class Meta: # noqa: D106
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400400 model = Version
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200401 fields = "__all__"
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400402
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200403
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400404class VersionListView(_VersionStatsListView):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200405 """View for listing versions."""
406
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200407 queryset = Version.objects.all().order_by("-heartbeats")
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400408 filter_class = VersionFilter
409 serializer_class = VersionSerializer
410
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400411
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200412class VersionDailyFilter(_DailyVersionStatsFilter):
413 """Filter for VersionDaily instances."""
414
415 version__build_fingerprint = CharFilter()
416 version__is_official_release = BooleanFilter()
417 version__is_beta_release = BooleanFilter()
418
419 class Meta: # noqa: D106
Dirk Vogt1accb672017-05-10 14:07:42 +0200420 model = VersionDaily
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200421 fields = "__all__"
Dirk Vogt1accb672017-05-10 14:07:42 +0200422
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200423
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400424class VersionDailySerializer(_DailyVersionStatsSerializer):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200425 """Serializer for VersionDaily instances."""
426
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400427 build_fingerprint = serializers.CharField()
428
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200429 class Meta: # noqa: D106
Dirk Vogt1accb672017-05-10 14:07:42 +0200430 model = VersionDaily
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200431 fields = "__all__"
Dirk Vogt1accb672017-05-10 14:07:42 +0200432
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200433
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400434class VersionDailyListView(_DailyVersionStatsListView):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200435 """View for listing VersionDaily instances."""
436
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400437 queryset = (
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200438 VersionDaily.objects.annotate(
439 build_fingerprint=F("version__build_fingerprint")
440 )
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200441 .all()
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200442 .order_by("date")
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400443 )
444 filter_class = VersionDailyFilter
445 filter_fields = (
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200446 "version__build_fingerprint",
447 "version__is_official_release",
448 "version__is_beta_release",
Borjan Tchakaloff227b4312018-02-23 17:02:16 +0400449 )
Dirk Vogt1accb672017-05-10 14:07:42 +0200450 serializer_class = VersionDailySerializer
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400451
452
453class RadioVersionSerializer(_VersionStatsSerializer):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200454 """Serializer for RadioVersion instances."""
455
456 class Meta: # noqa: D106
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400457 model = RadioVersion
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200458 fields = "__all__"
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400459
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200460
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400461class RadioVersionFilter(_VersionStatsFilter):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200462 """Filter for RadioVersion instances."""
463
464 class Meta: # noqa: D106
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400465 model = RadioVersion
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200466 fields = "__all__"
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400467
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200468
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400469class RadioVersionListView(_VersionStatsListView):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200470 """View for listing RadioVersion instances."""
471
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200472 queryset = RadioVersion.objects.all().order_by("-heartbeats")
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400473 serializer_class = RadioVersionSerializer
474 filter_class = RadioVersionFilter
475
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400476
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200477class RadioVersionDailyFilter(_DailyVersionStatsFilter):
478 """Filter for RadioVersionDaily instances."""
479
480 version__radio_version = CharFilter()
481 version__is_official_release = BooleanFilter()
482 version__is_beta_release = BooleanFilter()
483
484 class Meta: # noqa: D106
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400485 model = RadioVersionDaily
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200486 fields = "__all__"
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400487
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200488
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400489class RadioVersionDailySerializer(_DailyVersionStatsSerializer):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200490 """Serializer for RadioVersionDaily instances."""
491
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400492 radio_version = serializers.CharField()
493
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200494 class Meta: # noqa: D106
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400495 model = RadioVersionDaily
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200496 fields = "__all__"
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400497
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200498
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400499class RadioVersionDailyListView(_DailyVersionStatsListView):
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200500 """View for listing RadioVersionDaily instances."""
501
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400502 queryset = (
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200503 RadioVersionDaily.objects.annotate(
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200504 radio_version=F("version__radio_version")
505 )
Mitja Nikolaus5c3e0572018-07-30 13:36:14 +0200506 .all()
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200507 .order_by("date")
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400508 )
509 filter_class = RadioVersionDailyFilter
510 filter_fields = (
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200511 "version__radio_version",
512 "version__is_official_release",
513 "version__is_beta_release",
Borjan Tchakaloff1db45c72018-02-23 17:03:49 +0400514 )
515 serializer_class = RadioVersionDailySerializer