Replace raw SQL queries with native Django code

Issue: HIC-248
Change-Id: I46542f2b6558f6ad3e372cb2900fbcf8614a3144
diff --git a/crashreport_stats/rest_endpoints.py b/crashreport_stats/rest_endpoints.py
index f24b625..b5cd108 100644
--- a/crashreport_stats/rest_endpoints.py
+++ b/crashreport_stats/rest_endpoints.py
@@ -1,4 +1,5 @@
 """REST API for accessing the crashreports statistics."""
+import operator
 import zipfile
 from collections import OrderedDict
 
@@ -11,7 +12,6 @@
 from rest_framework.views import APIView
 
 from django.core.exceptions import ObjectDoesNotExist
-from django.db import connection
 from django.db.models.expressions import F
 from django.utils.decorators import method_decorator
 
@@ -38,19 +38,9 @@
 )
 from crashreports.response_descriptions import default_desc
 
-from . import raw_querys
-
 _RESPONSE_STATUS_200_DESCRIPTION = "OK"
 
 
-def dictfetchall(cursor):
-    """Return all rows from a cursor as a dict."""
-    desc = cursor.description
-    return [
-        dict(zip([col[0] for col in desc], row)) for row in cursor.fetchall()
-    ]
-
-
 _DEVICE_UPDATE_HISTORY_SCHEMA = openapi.Schema(
     type=openapi.TYPE_ARRAY,
     items=openapi.Schema(
@@ -99,13 +89,51 @@
             request: Http request
             uuid: The UUID of the device
 
-        Returns: The update history of the requested device.
+        Returns:
+            The update history of the requested device, sorted by the update
+            date.
 
         """
-        cursor = connection.cursor()
-        raw_querys.execute_device_update_history_query(cursor, {"uuid": uuid})
-        res = dictfetchall(cursor)
-        return Response(res)
+        device = Device.objects.get(uuid=uuid)
+
+        device_heartbeats = list(device.heartbeats.all())
+        device_crashreports = list(device.crashreports.all())
+
+        build_fingerprints = {hb.build_fingerprint for hb in device_heartbeats}
+
+        response = [
+            get_release_stats(
+                build_fingerprint,
+                device,
+                device_crashreports,
+                device_heartbeats,
+            )
+            for build_fingerprint in build_fingerprints
+        ]
+        response = sorted(response, key=operator.itemgetter("update_date"))
+
+        return Response(response)
+
+
+def get_release_stats(build_fingerprint, device, crashreports, heartbeats):
+    """Get the stats for a device for a specific release."""
+    heartbeats = filter_instances(
+        heartbeats, lambda hb: hb.build_fingerprint == build_fingerprint
+    )
+    crashreports = filter_instances(
+        crashreports, lambda c: c.build_fingerprint == build_fingerprint
+    )
+
+    stats = get_stats(heartbeats, crashreports)
+    stats.update(
+        {
+            "build_fingerprint": build_fingerprint,
+            "update_date": min([heartbeat.date for heartbeat in heartbeats]),
+            "max": device.id,
+        }
+    )
+
+    return stats
 
 
 _DEVICE_REPORT_HISTORY_SCHEMA = openapi.Schema(
@@ -154,13 +182,64 @@
             request: Http request
             uuid: The UUID of the device
 
-        Returns: The report history of the requested device.
+        Returns: The report history of the requested device, sorted by date.
 
         """
-        cursor = connection.cursor()
-        raw_querys.execute_device_report_history(cursor, {"uuid": uuid})
-        res = dictfetchall(cursor)
-        return Response(res)
+        device = Device.objects.get(uuid=uuid)
+
+        device_heartbeats = list(device.heartbeats.all())
+        device_crashreports = list(device.crashreports.all())
+
+        dates = {heartbeat.date.date() for heartbeat in device_heartbeats}
+
+        response = [
+            get_stats_for_date(date, device_crashreports, device_heartbeats)
+            for date in sorted(dates)
+        ]
+
+        return Response(response)
+
+
+def get_stats_for_date(date, crashreports, heartbeats):
+    """Get the stats for a device for a specific date."""
+    heartbeats = filter_instances(heartbeats, lambda hb: hb.date.date() == date)
+    crashreports = filter_instances(
+        crashreports, lambda c: c.date.date() == date
+    )
+
+    stats = get_stats(heartbeats, crashreports)
+    stats.update(date=date)
+    return stats
+
+
+def filter_instances(instances, filter_expr):
+    """Filter instances using a lambda filter function."""
+    return list(filter(filter_expr, instances))
+
+
+def get_stats(heartbeats, crashreports):
+    """Get the numbers of heartbeats and crashes per for each type."""
+    crashes = [
+        crashreport
+        for crashreport in crashreports
+        if crashreport.boot_reason in Crashreport.CRASH_BOOT_REASONS
+    ]
+    smpls = [
+        crashreport
+        for crashreport in crashreports
+        if crashreport.boot_reason in Crashreport.SMPL_BOOT_REASONS
+    ]
+    others = [
+        crashreport
+        for crashreport in crashreports
+        if crashreport not in crashes + smpls
+    ]
+    return {
+        "heartbeats": len(heartbeats),
+        "smpl": len(smpls),
+        "prob_crashes": len(crashes),
+        "other": len(others),
+    }
 
 
 _STATUS_RESPONSE_SCHEMA = openapi.Schema(