From 8aa7910775c8fc73aefb14a200d0554357c988da Mon Sep 17 00:00:00 2001 From: Sebastian Lohff Date: Thu, 26 Jan 2017 23:47:58 +0100 Subject: [PATCH] CBR parser + uploader --- TODO | 2 +- contest/cbrparser.py | 155 +++++++++++++++++++++++++++++++ contest/urls.py | 2 + templates/base.html | 1 + templates/contest/uploadCBR.html | 81 ++++++++++++++++ 5 files changed, 240 insertions(+), 1 deletion(-) create mode 100644 contest/cbrparser.py create mode 100644 templates/contest/uploadCBR.html diff --git a/TODO b/TODO index 6da4192..7b3f3ef 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,3 @@ - - cbr parser zum hochladen - calculate "claimed points" - link/funktion um qsos erneut gegeneinander checken zu lassen - listviews mit pagination evaluieren @@ -25,6 +24,7 @@ - register users still has "reference" labels, make form better - ensure QSOs are logged in UTC (they currently are, but why?) - rearrange login index: contest content rüberziehen; exchange registration nach rechts in der nav + - cbr parser zum hochladen Glaube nich, dass ich das mache - call dupe validation könnte ins model wandern diff --git a/contest/cbrparser.py b/contest/cbrparser.py new file mode 100644 index 0000000..af997f8 --- /dev/null +++ b/contest/cbrparser.py @@ -0,0 +1,155 @@ +from django.shortcuts import render +from django.contrib.auth.decorators import login_required +from .models import Contest, Band +from django.utils import timezone +from django.contrib import messages +from django import forms +from django.urls import reverse +from django.http import HttpResponseRedirect + + +from .forms import QSOFormWithTime + +import re + +def parseCBR(raw): + """ Parse a CBR file for the CQTU + + Yes, this could be used for other tools, BUT you'd have to take + look at the regex and parsingfoo, as there is some cqtu specific foo + inside them. + """ + kvlinere = re.compile(r"^(?P[A-Z-]+):(?: (?P.*))?$") + qsore = re.compile(r"^(?P144|432)\s+(?P[A-Z]{2})\s+(?P\d{4}-\d{2}-\d{2} \d{4}) (?P[A-Z0-9/-]+)\s+(?P\d{2,3})\s+(?P[A-Z0-9-]+)\s+(?P[A-Z0-9/-]+)\s+(?P\d{2,3})\s+(?P[A-Z0-9-]+)\s+0$") + + + qsoNo = 1 + info = { + "call": None, + "location": None, + "qsos": [], + } + + for n, line in enumerate(raw.split("\n"), 1): + line = line.strip() + # ignore empty lines + if line == "": + continue + + m = kvlinere.match(line) + if m: + k = m.group("key") + if k == "CALLSIGN": + info["call"] = m.group("value").strip().upper() + elif k == "LOCATION": + info["location"] = m.group("value").strip().upper() + elif k == "QSO": + q = m.group("value").strip() + # no-s FM date UTC-HHMM call rst-s exch-s call-r rst-r exch-r + # no-s / date UTC-HH-MM call rst-s + qm = qsore.search(q) + if qm: + qsoData = qm.groupdict() + qsoData["band"] = "2m" if qsoData["band"] == 144 else "70cm" + qsoTime = timezone.datetime.strptime(qsoData["datetime"], "%Y-%m-%d %H%M") + qsoData["datetime"] = timezone.get_current_timezone().localize(qsoTime) + qsoData["no_s"] = qsoNo + info["qsos"].append(qsoData) + + if info["call"] != qsoData["call_s"]: + raise forms.ValidationError("Error in line %d: qso was not made by you? (callsigns do not match)" % n) + if info["location"] != qsoData["exc_s"]: + raise forms.ValidationError("Error in line %d: exchange does not match your location? (callsigns do not match)" % n) + else: + raise forms.ValidationError("Error in line %d: qso was broken, regex did not match" % n) + + qsoNo += 1 + elif k == "X-QSO": + qsoNo += 1 + + else: + raise forms.ValidationError("Error in line %d: could not parse \"KEY: value\" pair" % n) + + return info + +class CBRForm(forms.Form): + data = forms.CharField(widget=forms.Textarea, label="Cabrillo data", help_text="Paste your cabrillo file contents here") + + def clean_data(self): + rawData = self.cleaned_data["data"] + parsedData = parseCBR(rawData) + + return parsedData + +def checkCBRConsistency(contest, user, info): + errors = [] + qsos = [] + if user.username != info["call"]: + errors.append("You are not the owner of this logfile! (%s != %s)" % (user.username, info["call"])) + + if user.ref.name != info["location"]: + errors.append("Location of logfile and registered exchange do not match! (%s != %s)" % (user.ref.name, info["location"])) + + for n, qsoData in enumerate(info["qsos"], 1): + qsoFormData = { + #"owner": user, + "time": qsoData["datetime"], + "call": qsoData["call_r"], + "band": Band.objects.get(contest=contest, name=qsoData["band"]).id, + "reportTX": qsoData["rst_s"], + "reportRX": qsoData["rst_r"], + "ownNo": qsoData["no_s"], + "otherNo": None, + "refStr": qsoData["exc_r"], + "remarks": "", + } + + qsoForm = QSOFormWithTime(user, data=qsoFormData) + qsoForm.is_valid() + qsoForm.instance.owner = user + print(qsoForm.errors) + qsos.append((qsoForm.instance, qsoForm)) + + return qsos, errors + +@login_required +def uploadCBR(request): + if not request.user.ref: + return HttpResponseRedirect(reverse("contest:index")) + + contest = Contest.objects.get(id=1) + deadline = False + form = None + verifyData = [] + verifyErrors = [] + save = saved = False + + if timezone.now() < contest.deadline: + if request.method == "POST": + form = CBRForm(data=request.POST) + if form.is_valid(): + verifyData, verifyErrors = checkCBRConsistency(contest, request.user, form.cleaned_data["data"]) + + if request.POST.get("action") == "save": + save = True + if not verifyErrors: + cnt = 0 + for qso, qsoForm in verifyData: + if qsoForm.is_valid(): + qso.save() + cnt += 1 + + if cnt > 0: + messages.success(request, "%d QSOs have been saved from the cbr file" % cnt) + else: + messages.warnnig(request, "CBR file was parsed, but no QSOs could be saved, as all cointained errors.") + + return HttpResponseRedirect(reverse("contest:uploadCBR")) + else: + pass + else: + form = CBRForm() + else: + deadline = True + + return render(request, "contest/uploadCBR.html", {"deadline": deadline, 'form': form, 'verifyData': verifyData, 'verifyErrors': verifyErrors, 'save': save, 'saved': saved}) diff --git a/contest/urls.py b/contest/urls.py index 84f6c87..aa7c405 100644 --- a/contest/urls.py +++ b/contest/urls.py @@ -17,6 +17,7 @@ from django.conf.urls import url import contest.views as contest_views +from contest.cbrparser import uploadCBR urlpatterns = [ url(r'^$', contest_views.contestIndex, name='index'), @@ -29,4 +30,5 @@ urlpatterns = [ url(r'^log/$', contest_views.log, name='log'), url(r'^log/edit/(?P\d+)/$', contest_views.logEdit, name='logEdit'), url(r'^log/delete/(?P\d+)/$', contest_views.logDelete, name='logDelete'), + url(r'^uploadcbr/$', uploadCBR, name='uploadCBR'), ] diff --git a/templates/base.html b/templates/base.html index 528cef4..081bc5b 100644 --- a/templates/base.html +++ b/templates/base.html @@ -43,6 +43,7 @@ Contest Overview {% if user.ref %} Log + Upload CBR {% endif %} {% else %} Home diff --git a/templates/contest/uploadCBR.html b/templates/contest/uploadCBR.html new file mode 100644 index 0000000..c7d538c --- /dev/null +++ b/templates/contest/uploadCBR.html @@ -0,0 +1,81 @@ +{% extends "base.html" %} + +{% load crispy_forms_tags %} + +{% block content %} +{% if not deadline %} +
+
+
+
Upload cabrillo data
+
+ {# {% crispy form %} #} +
+ {% csrf_token %} + {{ form | crispy }} +
+ + +

+ Note: On clicking save your QSOs will be saved to the database and you will have + to use the log form to verify and edit your data! +

+

+ Note: QSOs marked red in the verify phase will not be saved. +

+ {% if save and not saved %} +

+ Could not save, as your data still contains errors! +

+ {% endif %} +
+
+ {% if verifyErrors %} +
+
    + {% for error in verifyErrors %} +
  • {{ error }}
  • + {% endfor %} +
+
+ {% endif %} + + + + + + + + + + + {% for qso, qsoForm in verifyData %} + + + + + {% endfor %} + +
QSOError
{{ qso }}{{ qsoForm.errors }}
+ +
+
+
+
+{% else %} +
+
+
+
Upload closed
+
+

+ The deadline for this contest has passed. + Uploading cabrillo files is not possible anymore. +

+
+
+
+
+{% endif %} +{% endblock %} +