CBR parser + uploader

This commit is contained in:
Sebastian Lohff 2017-01-26 23:47:58 +01:00
parent c97cda3c49
commit 8aa7910775
5 changed files with 240 additions and 1 deletions

2
TODO
View File

@ -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

155
contest/cbrparser.py Normal file
View File

@ -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<key>[A-Z-]+):(?: (?P<value>.*))?$")
qsore = re.compile(r"^(?P<band>144|432)\s+(?P<mode>[A-Z]{2})\s+(?P<datetime>\d{4}-\d{2}-\d{2} \d{4}) (?P<call_s>[A-Z0-9/-]+)\s+(?P<rst_s>\d{2,3})\s+(?P<exc_s>[A-Z0-9-]+)\s+(?P<call_r>[A-Z0-9/-]+)\s+(?P<rst_r>\d{2,3})\s+(?P<exc_r>[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})

View File

@ -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<qsoid>\d+)/$', contest_views.logEdit, name='logEdit'),
url(r'^log/delete/(?P<qsoid>\d+)/$', contest_views.logDelete, name='logDelete'),
url(r'^uploadcbr/$', uploadCBR, name='uploadCBR'),
]

View File

@ -43,6 +43,7 @@
<li{% if request.resolver_match.url_name == 'index' %} class="active"{%endif%}><a href="{% url "contest:index" %}">Contest Overview</a></li>
{% if user.ref %}
<li{% if "log" in request.resolver_match.url_name %} class="active"{%endif%}><a href="{% url "contest:log" %}">Log</a></li>
<li{% if request.resolver_match.url_name == "uploadCBR" %} class="active"{%endif%}><a href="{% url "contest:uploadCBR" %}">Upload CBR</a></li>
{% endif %}
{% else %}
<li{% if request.resolver_match.url_name == 'index' %} class="active"{%endif%}><a href="{% url "index" %}">Home</a></li>

View File

@ -0,0 +1,81 @@
{% extends "base.html" %}
{% load crispy_forms_tags %}
{% block content %}
{% if not deadline %}
<div class="row">
<div class="col-sm-12">
<div class="panel panel-default">
<div class="panel-heading">Upload cabrillo data</div>
<div class="panel-body">
{# {% crispy form %} #}
<form method="post" action="{% url 'contest:uploadCBR' %}">
{% csrf_token %}
{{ form | crispy }}
<div class="form-action">
<button class="btn btn-danger" type="submit" name="action" value="save">Save</button>
<button class="btn btn-success" type="submit" name="action" value="verify">Verify</button>
<p>
<strong>Note:</strong> 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!
</p>
<p>
<strong>Note:</strong> QSOs marked red in the verify phase will not be saved.
</p>
{% if save and not saved %}
<p class="danger">
Could not save, as your data still contains errors!
</p>
{% endif %}
</div>
</form>
{% if verifyErrors %}
<div class="alert alert-danger">
<ul>
{% for error in verifyErrors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
<table class="table">
<thead>
<tr>
<th>QSO</th>
<th>Error</th>
</tr>
</thead>
<tbody>
{% for qso, qsoForm in verifyData %}
<tr class="{% if qsoForm.errors %}danger{% else %}success{% endif %}">
<td>{{ qso }}</td>
<td>{{ qsoForm.errors }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
{% else %}
<div class="row">
<div class="col-sm-6 col-sm-offset-3">
<div class="panel panel-danger">
<div class="panel-heading">Upload closed</div>
<div class="panel-body">
<p>
The deadline for this contest has passed.
Uploading cabrillo files is not possible anymore.
</p>
</div>
</div>
</div>
</div>
{% endif %}
{% endblock %}