Merge branch 'radio-version' into 'master'

Extend the heartbeat and crashreport models with the radio version

See merge request !5
diff --git a/crashreport_stats/migrations/0002_version_and_versiondaily_with_defaults.py b/crashreport_stats/migrations/0002_version_and_versiondaily_with_defaults.py
new file mode 100644
index 0000000..0a58758
--- /dev/null
+++ b/crashreport_stats/migrations/0002_version_and_versiondaily_with_defaults.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+#
+# Set the default values for the Version and VersionDaily models.
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('crashreport_stats', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='version',
+            name='first_seen_on',
+            field=models.DateField(auto_now_add=True),
+        ),
+        migrations.AlterField(
+            model_name='version',
+            name='heartbeats',
+            field=models.IntegerField(default=0),
+        ),
+        migrations.AlterField(
+            model_name='version',
+            name='other',
+            field=models.IntegerField(default=0),
+        ),
+        migrations.AlterField(
+            model_name='version',
+            name='prob_crashes',
+            field=models.IntegerField(default=0),
+        ),
+        migrations.AlterField(
+            model_name='version',
+            name='released_on',
+            field=models.DateField(auto_now_add=True),
+        ),
+        migrations.AlterField(
+            model_name='version',
+            name='smpl',
+            field=models.IntegerField(default=0),
+        ),
+        migrations.AlterField(
+            model_name='versiondaily',
+            name='date',
+            field=models.DateField(auto_now_add=True),
+        ),
+        migrations.AlterField(
+            model_name='versiondaily',
+            name='heartbeats',
+            field=models.IntegerField(default=0),
+        ),
+        migrations.AlterField(
+            model_name='versiondaily',
+            name='other',
+            field=models.IntegerField(default=0),
+        ),
+        migrations.AlterField(
+            model_name='versiondaily',
+            name='prob_crashes',
+            field=models.IntegerField(default=0),
+        ),
+        migrations.AlterField(
+            model_name='versiondaily',
+            name='smpl',
+            field=models.IntegerField(default=0),
+        ),
+    ]
diff --git a/crashreport_stats/migrations/0003_radioversion_radioversiondaily.py b/crashreport_stats/migrations/0003_radioversion_radioversiondaily.py
new file mode 100644
index 0000000..3ce431a
--- /dev/null
+++ b/crashreport_stats/migrations/0003_radioversion_radioversiondaily.py
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+#
+# Introducing the RadioVersion and RadioVersionDaily models
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('crashreport_stats', '0002_version_and_versiondaily_with_defaults'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='RadioVersion',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('is_official_release', models.BooleanField(default=False)),
+                ('is_beta_release', models.BooleanField(default=False)),
+                ('first_seen_on', models.DateField(auto_now_add=True)),
+                ('released_on', models.DateField(auto_now_add=True)),
+                ('heartbeats', models.IntegerField(default=0)),
+                ('prob_crashes', models.IntegerField(default=0)),
+                ('smpl', models.IntegerField(default=0)),
+                ('other', models.IntegerField(default=0)),
+                ('radio_version', models.CharField(max_length=200, unique=True)),
+            ],
+            options={
+                'abstract': False,
+            },
+        ),
+        migrations.CreateModel(
+            name='RadioVersionDaily',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('date', models.DateField(auto_now_add=True)),
+                ('heartbeats', models.IntegerField(default=0)),
+                ('prob_crashes', models.IntegerField(default=0)),
+                ('smpl', models.IntegerField(default=0)),
+                ('other', models.IntegerField(default=0)),
+                ('version', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='daily_stats', to='crashreport_stats.RadioVersion')),
+            ],
+            options={
+                'abstract': False,
+            },
+        ),
+    ]
diff --git a/crashreport_stats/models.py b/crashreport_stats/models.py
index 04f38ab..f7fd05c 100644
--- a/crashreport_stats/models.py
+++ b/crashreport_stats/models.py
@@ -1,76 +1,96 @@
 from django.db import models
 from crashreports.models import *
+from django.db.models import F
 from django.db.models.signals import post_save
 from django.dispatch import receiver
 import datetime
 
 
-def getVersion(build_fingerprint):
-    v= None
-    try: 
-        v = Version.objects.get(build_fingerprint=build_fingerprint)
-    except Version.DoesNotExist:
-        v =Version(build_fingerprint=build_fingerprint,
-            first_seen_on=datetime.date.today(),
-            released_on=datetime.date.today(),
-            heartbeats=0, prob_crashes=0, smpl=0, other=0)
-        v.save()
-    return v
-
-def getVersionDaily(version,day):
-    try: 
-        v = VersionDaily.objects.get(version=version, date=day)
-    except VersionDaily.DoesNotExist:
-        v =VersionDaily(version=version, date=day,
-            heartbeats=0, prob_crashes=0, smpl=0, other=0)
-    return v
-
 @receiver(post_save, sender=Crashreport)
 def on_crashreport_create(sender, **kwargs):
     crashreport = kwargs.get('instance')
-    v= getVersion(crashreport.build_fingerprint)
-    vd = getVersionDaily(v, crashreport.date.date())
+    v, _ = Version.objects.get_or_create(build_fingerprint=crashreport.build_fingerprint)
+    vd, _ = VersionDaily.objects.get_or_create(version=v, date=crashreport.date)
+    stats = [v, vd]
+
+    if crashreport.radio_version:
+        rv, _ = RadioVersion.objects.get_or_create(radio_version=crashreport.radio_version)
+        rvd, _ = RadioVersionDaily.objects.get_or_create(version=rv, date=crashreport.date)
+        stats += [rv, rvd]
+
     if crashreport.boot_reason == "RTC alarm":
-        v.smpl = v.smpl + 1
-        vd.smpl = vd.smpl + 1
+        for element in stats:
+            element.smpl = F('smpl') + 1
     elif crashreport.boot_reason in ["UNKNOWN", "keyboard power on"]:
-        v.prob_crashes = v.prob_crashes + 1
-        vd.prob_crashes = vd.prob_crashes + 1
+        for element in stats:
+            element.prob_crashes = F('prob_crashes') + 1
     else:
-        v.other = v.other + 1
-        vd.other = vd.other + 1
-    v.save()
-    vd.save()
+        for element in stats:
+            element.other = F('other') + 1
+
+    for element in stats:
+        element.save()
+
 
 @receiver(post_save, sender=HeartBeat)
 def on_heartbeat_create(sender, **kwargs):
     hb = kwargs.get('instance')
-    v  = getVersion(hb.build_fingerprint)
-    vd = getVersionDaily(v, hb.date)
-    v.heartbeats = v.heartbeats + 1
-    vd.heartbeats = vd.heartbeats + 1
-    v.save()
-    vd.save()
+
+    v, _ = Version.objects.get_or_create(build_fingerprint=hb.build_fingerprint)
+    vd, _ = VersionDaily.objects.get_or_create(version=v, date=hb.date)
+    stats = [v, vd]
+
+    if hb.radio_version:
+        rv, _ = RadioVersion.objects.get_or_create(radio_version=hb.radio_version)
+        rvd, _ = RadioVersionDaily.objects.get_or_create(version=rv, date=hb.date)
+        stats += [rv, rvd]
+
+    for element in stats:
+        element.heartbeats = F('heartbeats') + 1
+        element.save()
 
 
-class Version(models.Model):
-    build_fingerprint    = models.CharField(max_length=200,  unique=True)
+class _VersionStats(models.Model):
     is_official_release = models.BooleanField(default=False)
-    is_beta_release     = models.BooleanField(default=False)
-    first_seen_on        = models.DateField()
-    released_on          = models.DateField()
-    heartbeats           = models.IntegerField()
-    prob_crashes         = models.IntegerField()
-    smpl                 = models.IntegerField()
-    other                = models.IntegerField()
+    is_beta_release = models.BooleanField(default=False)
+    first_seen_on = models.DateField(auto_now_add=True)
+    released_on = models.DateField(auto_now_add=True)
+    heartbeats = models.IntegerField(default=0)
+    prob_crashes = models.IntegerField(default=0)
+    smpl = models.IntegerField(default=0)
+    other = models.IntegerField(default=0)
+
+    class Meta:
+        abstract = True
+
+class _DailyVersionStats(models.Model):
+    date  = models.DateField(auto_now_add=True)
+    heartbeats = models.IntegerField(default=0)
+    prob_crashes = models.IntegerField(default=0)
+    smpl = models.IntegerField(default=0)
+    other = models.IntegerField(default=0)
+
+    class Meta:
+        abstract = True
+
+
+class Version(_VersionStats):
+    build_fingerprint = models.CharField(max_length=200, unique=True)
+
     def __str__(self):
         return self.build_fingerprint
-    
-    
-class VersionDaily(models.Model):
-    version        = models.ForeignKey(Version, db_index=True, related_name='daily_stats', on_delete=models.CASCADE)
-    date           = models.DateField()
-    heartbeats     = models.IntegerField()
-    prob_crashes   = models.IntegerField()
-    smpl           = models.IntegerField()
-    other          = models.IntegerField()
+
+class VersionDaily(_DailyVersionStats):
+    version = models.ForeignKey(Version, db_index=True, related_name='daily_stats',
+            on_delete=models.CASCADE)
+
+
+class RadioVersion(_VersionStats):
+    radio_version = models.CharField(max_length=200, unique=True)
+
+    def __str__(self):
+        return self.radio_version
+
+class RadioVersionDaily(_DailyVersionStats):
+    version = models.ForeignKey(RadioVersion, db_index=True, related_name='daily_stats',
+            on_delete=models.CASCADE)
diff --git a/crashreports/migrations/0003_crashreport_and_heartbeat_with_radio_version.py b/crashreports/migrations/0003_crashreport_and_heartbeat_with_radio_version.py
new file mode 100644
index 0000000..564cc0e
--- /dev/null
+++ b/crashreports/migrations/0003_crashreport_and_heartbeat_with_radio_version.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+#
+# Extend the Crashreport and Heartbeat models to support the radio version.
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('crashreports', '0002_auto_20170502_1155'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='crashreport',
+            name='radio_version',
+            field=models.CharField(db_index=True, max_length=200, null=True),
+        ),
+        migrations.AddField(
+            model_name='heartbeat',
+            name='radio_version',
+            field=models.CharField(db_index=True, max_length=200, null=True),
+        ),
+    ]
diff --git a/crashreports/models.py b/crashreports/models.py
index b894522..56c4fb2 100644
--- a/crashreports/models.py
+++ b/crashreports/models.py
@@ -56,6 +56,7 @@
     app_version = models.IntegerField()
     uptime = models.CharField(max_length=200)
     build_fingerprint = models.CharField(db_index=True, max_length=200)
+    radio_version = models.CharField(db_index=True, max_length=200, null=True)
     boot_reason = models.CharField(db_index=True, max_length=200)
     power_on_reason = models.CharField(db_index=True, max_length=200)
     power_off_reason = models.CharField(db_index=True, max_length=200)
@@ -104,6 +105,7 @@
     app_version = models.IntegerField()
     uptime = models.CharField(max_length=200)
     build_fingerprint = models.CharField( db_index=True, max_length=200)
+    radio_version = models.CharField(db_index=True, max_length=200, null=True)
     date = models.DateTimeField(db_index=True)
     device_local_id = models.PositiveIntegerField(blank=True)
     created_at = models.DateTimeField(auto_now_add=True)
diff --git a/crashreports/rest_api_crashreports.py b/crashreports/rest_api_crashreports.py
index ecf5d74..b6555fd 100644
--- a/crashreports/rest_api_crashreports.py
+++ b/crashreports/rest_api_crashreports.py
@@ -12,7 +12,7 @@
     paginate_by = 20
     permission_classes = (HasRightsOrIsDeviceOwnerDeviceCreation, )
     serializer_class = CrashReportSerializer
-    filter_fields = ('device','build_fingerprint')
+    filter_fields = ('device', 'build_fingerprint', 'radio_version')
 
     pass
 
diff --git a/crashreports/rest_api_heartbeats.py b/crashreports/rest_api_heartbeats.py
index 3db751f..841dec8 100644
--- a/crashreports/rest_api_heartbeats.py
+++ b/crashreports/rest_api_heartbeats.py
@@ -13,7 +13,7 @@
     paginate_by = 20
     permission_classes = (HasRightsOrIsDeviceOwnerDeviceCreation, )
     serializer_class = HeartBeatSerializer
-    filter_fields = ('device','build_fingerprint')
+    filter_fields = ('device', 'build_fingerprint', 'radio_version')
 
 
     def get(self, *args, **kwargs):
diff --git a/crashreports/tests.py b/crashreports/tests.py
index 9f4465b..ea29d57 100644
--- a/crashreports/tests.py
+++ b/crashreports/tests.py
@@ -116,6 +116,7 @@
             'app_version': 2,
             'uptime': "2 Hours",
             'build_fingerprint': "models.CharField(max_length=200)",
+            'radio_version': 'XXXX.X-FP2-X-XX',
             'date': str(datetime.datetime(year=2016, month=1, day=1))
         }
 
@@ -214,6 +215,25 @@
         request = self.user.get(self.url)
         self.assertEqual(request.status_code, status.HTTP_403_FORBIDDEN)
 
+    def test_no_radio_version(self):
+        data = self.data.copy()
+        data.pop('radio_version')
+        self.post_multiple(self.user, data, 1)
+        url = self.url_by_uuid.format(self.uuid)
+        request = self.admin.get(url)
+        self.assertEqual(request.status_code, status.HTTP_200_OK)
+        self.assertEqual(len(request.data['results']), 1)
+        self.assertIsNone(request.data['results'][0]['radio_version'])
+
+    def test_radio_version_field(self):
+        self.post_multiple(self.user, self.data, 1)
+        url = self.url_by_uuid.format(self.uuid)
+        request = self.admin.get(url)
+        self.assertEqual(request.status_code, status.HTTP_200_OK)
+        self.assertEqual(len(request.data['results']), 1)
+        self.assertEqual(request.data['results'][0]['radio_version'],
+                self.data['radio_version'])
+
 
 def create_crashreport(uuid="not set"):
     return {
@@ -222,6 +242,7 @@
         'app_version': 2,
         'uptime': "2 Hours",
         'build_fingerprint': "models.CharField(max_length=200)",
+        'radio_version': 'XXXX.X-FP2-X-XX',
         'boot_reason': "models.CharField(max_length=200)",
         'power_on_reason': "models.CharField(max_length=200)",
         'power_off_reason': "models.CharField(max_length=200)",