Factor the models to prepare for new ones related to the radio version

Other notable improvements:
  - Define the default values to be able to use the built-in convenience method
    `get_or_create`;
  - Use the F helper to *atomically* increment the counters; the current method
    could run into race conditions and miss increments.

The models remain functionally the same - also, their title is left unchanged to
not break compatibility and avoid a migration.

Change-Id: Iad86302d869705867efadf8cfd2e661dac1ff4f9
diff --git a/crashreport_stats/models.py b/crashreport_stats/models.py
index 04f38ab..7c2a83f 100644
--- a/crashreport_stats/models.py
+++ b/crashreport_stats/models.py
@@ -1,76 +1,74 @@
 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.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)
+
+    for element in [v, vd]:
+        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)