from __future__ import unicode_literals import datetime from django.db import models from django.contrib.auth.models import AbstractUser from django.core.validators import RegexValidator from django.db.models import Q, signals from .validators import CallUsernameValidator from .signals import checkForShadowCall class Contest(models.Model): name = models.CharField(max_length=20) shortName = models.CharField(max_length=20, unique=True) callQrg = models.ForeignKey("Frequency", models.SET_NULL, null=True, blank=True) deadline = models.DateTimeField() qsoStartTime = models.DateTimeField() qsoEndTime = models.DateTimeField() def __str__(self): return self.name @classmethod def get_current_contest(cls): return cls.objects.get(id=1) class Reference(models.Model): name = models.CharField(max_length=20, unique=True, db_index=True) description = models.TextField() def __str__(self): return self.name class EntryCategory(models.Model): name = models.CharField(max_length=64, unique=True) description = models.TextField(blank=True) def __str__(self): return self.name class ShadowCall(models.Model): username = models.CharField(max_length=20, unique=True, db_index=True, validators=[CallUsernameValidator()]) ref = models.ForeignKey(Reference, models.SET_NULL,null=True, blank=True) location = models.CharField(max_length=128, default="", blank=True) opName = models.CharField(max_length=128, default="", blank=True) regTime = models.DateTimeField(null=True, default=None) def __str__(self): return self.username class User(AbstractUser): ref = models.ForeignKey(Reference, models.SET_NULL, null=True, blank=True) cat = models.ForeignKey(EntryCategory, models.SET_NULL, null=True, blank=True) location = models.CharField(max_length=128, default="", blank=True) opName = models.CharField(max_length=128, default="", blank=True) regTime = models.DateTimeField(null=True, default=None, blank=True) # because of cbr parsing bug, we sometimes have users who only have 70cm qsos # we ignore the band for them when checking QSOs ignoreBand = models.BooleanField(default=False) # extra profile stuff so DL7BST can sleep well without his doodles editedProfile = models.BooleanField(default=False) dncall = models.CharField(max_length=16, default='', blank=True, verbose_name="DN-Call", help_text="If you have a DN call that you will offer to SWLs please enter it here") qrv2m = models.BooleanField(default=False, verbose_name="QRV on 2m", help_text="Will you be QRV on 2m during the contest?") qrv70cm = models.BooleanField(default=False, verbose_name="QRV on 70cm", help_text="Will you be QRV on 70cm during the contest?") extra2m70cm = models.BooleanField(default=False, verbose_name="Additional 2m/70cm TRX", help_text="Will you bring an additional 2m/70cm TRX to lend to other participants?") def __init__(self, *args, **kwargs): super(User, self).__init__(*args, **kwargs) self._meta.get_field("username").validators = [CallUsernameValidator()] def getQSOCount(self): return self.qso_set.count() def getCfmdQSOCount(self): return self.qso_set.filter(~Q(cfmdQSO=None)).count() def getCfmdRefCount(self): return len(set(map(lambda _x: _x["refStr"], self.qso_set.filter(ref__isnull=False).values("ref", "refStr")))) def calcClaimedPoints(self): return self.calcPoints(cfmd=False) def calcCfmdPoints(self): return self.calcPoints(cfmd=True) def calcPoints(self, cfmd): contest = Contest.objects.get(id=1) result = {"refCount": 0, "qsoCount": 0} for band in contest.band_set.all(): result[band.name] = self.calcBandPoints(band, cfmd) result["refCount"] += result[band.name]["refCount"] result["qsoCount"] += result[band.name]["qsoCount"] result["points"] = result["qsoCount"] * result["refCount"] return result def calcBandPoints(self, band, cfmd=False): contest = band.contest qsos = self.qso_set.filter(band=band, time__gte=contest.qsoStartTime, time__lte=contest.qsoEndTime) if cfmd: qsos = qsos.filter(cfmdQSO__isnull=False) refs = set(map(lambda _x: _x["refStr"], qsos.values("refStr"))) return { "band": band, "refs": refs, "qsoCount": qsos.count(), "refCount": len(refs) } signals.post_save.connect(checkForShadowCall, sender=User) class Band(models.Model): name = models.CharField(max_length=10) contest = models.ForeignKey(Contest) def __str__(self): return self.name class Frequency(models.Model): # qrg # band channel = models.CharField(max_length=3) qrg = models.DecimalField(max_digits=7, decimal_places=3) band = models.ForeignKey(Band) note = models.CharField(max_length=50, blank=True) def __str__(self): return "Channel %s: %s MHz" % (self.channel, self.qrg) class QSO(models.Model): reportValidator = RegexValidator("[1-5][1-9]") class Meta: index_together = [ ["owner", "call"], ] owner = models.ForeignKey(User, db_index=True) time = models.DateTimeField(blank=True) call = models.CharField(max_length=20, db_index=True) callRef = models.ForeignKey(User, models.SET_NULL, related_name='qsoref', null=True, blank=True, default=None) band = models.ForeignKey(Band) reportTX = models.CharField(max_length=7, default=59, verbose_name='RS-S', validators=[reportValidator]) reportRX = models.CharField(max_length=7, default=59, verbose_name='RS-R', validators=[reportValidator]) ownNo = models.IntegerField(verbose_name='No') otherNo = models.IntegerField(verbose_name='No-R', null=True, blank=True) refStr = models.CharField(max_length=20, verbose_name="EXC") ref = models.ForeignKey(Reference, models.SET_NULL, null=True, blank=True) remarks = models.CharField(max_length=50, blank=True, default=None) cfmdQSO = models.ForeignKey("QSO", models.SET_NULL, null=True, blank=True, default=None) CFMD_SEC = 5*60 def checkQSOData(self): """ Match strdata to log rows. Only call, if you intent to save this object if we return True! """ # find reference changed = False if self.refStr: refName = self.refStr.replace("-", "") if refName == "GX": refName = "DX" # Old reference exists? if self.ref and self.ref.name != refName: self.ref = None changed = True if not self.ref: # find matching ref try: self.ref = Reference.objects.get(name=refName) changed = True except Reference.DoesNotExist: pass # find call if not self.callRef or self.callRef.username != self.call: try: self.callRef = User.objects.get(username=self.call) changed = True except User.DoesNotExist: if self.callRef: changed = True self.callRef = None # find matching qso if self.cfmdQSO: # check if this still checks out q = self.cfmdQSO if abs((self.time - q.time).total_seconds()) <= self.CFMD_SEC and \ self.ref and self.owner.ref and self.callRef and q.callRef and \ q.owner == self.callRef and q.callRef == self.owner and \ self.ref == q.owner.ref and self.owner.ref == q.ref and \ self.band == q.band: # checks out pass else: changed = True self.cfmdQSO.cfmdQSO = None self.cfmdQSO.save(checkQSO=False) self.cfmdQSO = None if self.ref and self.callRef and self.callRef.ref and not self.cfmdQSO: # look for a matching line q = QSO.objects.filter( (Q(time__lte=self.time + datetime.timedelta(seconds=self.CFMD_SEC)) & Q(time__gte=self.time - datetime.timedelta(seconds=self.CFMD_SEC))), owner=self.callRef, callRef=self.owner, owner__ref=self.ref, ref=self.owner.ref, band=self.band) if q.count() == 1: changed = True q[0].cfmdQSO = self q[0].save(checkQSO=False) self.cfmdQSO = q[0] return changed def save(self, checkQSO=True, *args, **kwargs): if checkQSO: self.checkQSOData() super(QSO, self).save(*args, **kwargs) def __str__(self): return "QSO no %s at %s on band %s from %s with %s@%s %s/%s" % (self.ownNo, self.time.strftime("%H:%M"), self.band, self.owner.username, self.call, self.refStr, self.reportTX, self.reportRX)