CBR parser + uploader
This commit is contained in:
parent
c97cda3c49
commit
8aa7910775
2
TODO
2
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
|
||||
|
|
|
@ -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})
|
|
@ -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'),
|
||||
]
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 %}
|
||||
|
Loading…
Reference in New Issue