Compare commits
No commits in common. "94d2f5a8d87e89aa1c7152fa7652855c26ccd620" and "efcea4d2df2f29ac2285e155b380b9517b665b5c" have entirely different histories.
94d2f5a8d8
...
efcea4d2df
|
@ -1,3 +0,0 @@
|
||||||
from django.contrib import admin
|
|
||||||
|
|
||||||
# Register your models here.
|
|
|
@ -1,5 +0,0 @@
|
||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class ApiConfig(AppConfig):
|
|
||||||
name = 'api'
|
|
|
@ -1,3 +0,0 @@
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
# Create your models here.
|
|
|
@ -1,98 +0,0 @@
|
||||||
from django import forms
|
|
||||||
from django.utils import timezone
|
|
||||||
from rest_framework import serializers
|
|
||||||
|
|
||||||
from contest.models import Contest, Band, Frequency, QSO, EntryCategory, User, ShadowCall, Reference
|
|
||||||
from contest.validators import CallLogValidator
|
|
||||||
|
|
||||||
|
|
||||||
class ContestSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = Contest
|
|
||||||
# FIXME: add callQrg
|
|
||||||
fields = ('id', 'shortName', 'deadline', 'qsoStartTime', 'qsoEndTime', 'callQrg')
|
|
||||||
|
|
||||||
|
|
||||||
class BandSerializer(serializers.ModelSerializer):
|
|
||||||
# contest = ContestSerializer()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Band
|
|
||||||
fields = ('id', 'name', 'contest')
|
|
||||||
|
|
||||||
|
|
||||||
class FrequencySerializer(serializers.ModelSerializer):
|
|
||||||
# band = BandSerializer()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Frequency
|
|
||||||
fields = ('id', 'channel', 'qrg', 'band', 'note')
|
|
||||||
|
|
||||||
|
|
||||||
class EntryCategorySerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = EntryCategory
|
|
||||||
fields = ('id', 'name', 'description')
|
|
||||||
|
|
||||||
|
|
||||||
class ReferenceSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = Reference
|
|
||||||
fields = ('id', 'name', 'description')
|
|
||||||
|
|
||||||
|
|
||||||
class UserSerializer(serializers.ModelSerializer):
|
|
||||||
# ref = ReferenceSerializer()
|
|
||||||
# cat = EntryCategorySerializer()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = User
|
|
||||||
fields = ('id', 'ref', 'cat', 'location', 'opName', 'regTime', 'dncall', 'qrv2m', 'qrv70cm', 'extra2m70cm')
|
|
||||||
read_only_fields = ('ref', 'location', 'regTime')
|
|
||||||
|
|
||||||
def validate(self, attrs):
|
|
||||||
contest = Contest.get_current_contest()
|
|
||||||
if contest.deadline < timezone.now():
|
|
||||||
raise serializers.ValidationError("The deadline for changing the entry category has passed")
|
|
||||||
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
|
|
||||||
class QSOSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = QSO
|
|
||||||
fields = ('id', 'owner', 'time', 'ownNo', 'band', 'call', 'reportTX', 'reportRX', 'refStr', 'remarks')
|
|
||||||
read_only_fields = ('owner',)
|
|
||||||
|
|
||||||
def validate_call(self, value):
|
|
||||||
val = value.strip().upper()
|
|
||||||
try:
|
|
||||||
CallLogValidator()(val)
|
|
||||||
except forms.ValidationError as e:
|
|
||||||
raise serializers.ValidationError({'errors': e.error_list})
|
|
||||||
|
|
||||||
return val
|
|
||||||
|
|
||||||
def validate(self, attrs):
|
|
||||||
ownNo = attrs['ownNo']
|
|
||||||
try:
|
|
||||||
o = QSO.objects.get(owner=self.context['request'].user, ownNo=ownNo)
|
|
||||||
if not (self.instance and self.instance.id and self.instance.id == o.id):
|
|
||||||
raise serializers.ValidationError("You already logged a QSO with the number %s" % ownNo)
|
|
||||||
except QSO.DoesNotExist:
|
|
||||||
pass
|
|
||||||
|
|
||||||
band = attrs.get('band')
|
|
||||||
if band:
|
|
||||||
if band.contest.deadline < timezone.now():
|
|
||||||
raise serializers.ValidationError("The deadline for logging and editing QSOs has passed")
|
|
||||||
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
|
|
||||||
class ShadowCallSerializer(serializers.ModelSerializer):
|
|
||||||
ref = ReferenceSerializer()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = ShadowCall
|
|
||||||
fields = ('id', 'username', 'ref', 'location', 'opName', 'regTime')
|
|
|
@ -1,3 +0,0 @@
|
||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
# Create your tests here.
|
|
20
api/urls.py
20
api/urls.py
|
@ -1,20 +0,0 @@
|
||||||
from django.conf.urls import include, url
|
|
||||||
from rest_framework import routers
|
|
||||||
|
|
||||||
from .views import ContestViewSet, BandViewSet, FrequencyViewSet, EntryCategoryViewSet, ReferenceViewSet, QSOViewSet, \
|
|
||||||
ShadowCallViewSet, UserProfileViewSet
|
|
||||||
|
|
||||||
router = routers.DefaultRouter()
|
|
||||||
router.register('contests', ContestViewSet)
|
|
||||||
router.register('bands', BandViewSet)
|
|
||||||
router.register('frequencies', FrequencyViewSet)
|
|
||||||
router.register('entrycategories', EntryCategoryViewSet)
|
|
||||||
router.register('references', ReferenceViewSet)
|
|
||||||
router.register('qsos', QSOViewSet, basename='qso')
|
|
||||||
router.register('shadowcalls', ShadowCallViewSet)
|
|
||||||
router.register('profile', UserProfileViewSet, basename='profile')
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
url(r'^', include(router.urls)),
|
|
||||||
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
|
|
||||||
]
|
|
91
api/views.py
91
api/views.py
|
@ -1,91 +0,0 @@
|
||||||
from rest_framework.permissions import IsAuthenticated, IsAdminUser
|
|
||||||
from rest_framework import viewsets
|
|
||||||
|
|
||||||
from .serializers import ContestSerializer, BandSerializer, FrequencySerializer, EntryCategorySerializer, \
|
|
||||||
ReferenceSerializer, QSOSerializer, ShadowCallSerializer, UserSerializer
|
|
||||||
from contest.models import Contest, Band, Frequency, EntryCategory, Reference, QSO, ShadowCall, User
|
|
||||||
|
|
||||||
|
|
||||||
class ContestViewSet(viewsets.ReadOnlyModelViewSet):
|
|
||||||
"""
|
|
||||||
Resource to list and view all available contests. Use `current/` to get the current Contest.
|
|
||||||
"""
|
|
||||||
queryset = Contest.objects.all()
|
|
||||||
serializer_class = ContestSerializer
|
|
||||||
filterset_fields = ['shortName']
|
|
||||||
|
|
||||||
def get_object(self):
|
|
||||||
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
|
|
||||||
if self.kwargs.get(lookup_url_kwarg) == "current":
|
|
||||||
obj = Contest.get_current_contest()
|
|
||||||
self.check_object_permissions(self.request, obj)
|
|
||||||
else:
|
|
||||||
obj = super(ContestViewSet, self).get_object()
|
|
||||||
|
|
||||||
return obj
|
|
||||||
|
|
||||||
|
|
||||||
class BandViewSet(viewsets.ReadOnlyModelViewSet):
|
|
||||||
queryset = Band.objects.all()
|
|
||||||
serializer_class = BandSerializer
|
|
||||||
filterset_fields = ['name', 'contest']
|
|
||||||
|
|
||||||
|
|
||||||
class FrequencyViewSet(viewsets.ReadOnlyModelViewSet):
|
|
||||||
queryset = Frequency.objects.all()
|
|
||||||
serializer_class = FrequencySerializer
|
|
||||||
filterset_fields = ['band', 'channel']
|
|
||||||
|
|
||||||
|
|
||||||
class EntryCategoryViewSet(viewsets.ReadOnlyModelViewSet):
|
|
||||||
queryset = EntryCategory.objects.all()
|
|
||||||
serializer_class = EntryCategorySerializer
|
|
||||||
filterset_fields = ['name']
|
|
||||||
|
|
||||||
|
|
||||||
class ReferenceViewSet(viewsets.ReadOnlyModelViewSet):
|
|
||||||
permission_classes = [IsAdminUser]
|
|
||||||
queryset = Reference.objects.all()
|
|
||||||
serializer_class = ReferenceSerializer
|
|
||||||
filterset_fields = ['name']
|
|
||||||
|
|
||||||
|
|
||||||
class QSOViewSet(viewsets.ModelViewSet):
|
|
||||||
permission_classes = [IsAuthenticated]
|
|
||||||
serializer_class = QSOSerializer
|
|
||||||
filterset_fields = ['time', 'ownNo', 'band', 'call', 'refStr']
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
return QSO.objects.filter(owner=self.request.user)
|
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
|
||||||
return serializer.save(owner=self.request.user)
|
|
||||||
|
|
||||||
|
|
||||||
class UserProfileViewSet(viewsets.ModelViewSet):
|
|
||||||
"""
|
|
||||||
Resource to view user-profiles, currently restricted to the current user's profile.
|
|
||||||
Use `me/` to get the profile of the currently logged in user.
|
|
||||||
"""
|
|
||||||
permission_classes = [IsAuthenticated]
|
|
||||||
serializer_class = UserSerializer
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
return User.objects.filter(id=self.request.user.id)
|
|
||||||
|
|
||||||
def get_object(self):
|
|
||||||
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
|
|
||||||
if self.kwargs.get(lookup_url_kwarg) == "me":
|
|
||||||
obj = self.request.user
|
|
||||||
self.check_object_permissions(self.request, obj)
|
|
||||||
else:
|
|
||||||
obj = super(ContestViewSet, self).get_object()
|
|
||||||
|
|
||||||
return obj
|
|
||||||
|
|
||||||
|
|
||||||
class ShadowCallViewSet(viewsets.ReadOnlyModelViewSet):
|
|
||||||
permission_classes = [IsAdminUser]
|
|
||||||
queryset = ShadowCall.objects.all()
|
|
||||||
serializer_class = ShadowCallSerializer
|
|
||||||
filterset_fields = ['username', 'ref']
|
|
|
@ -1,6 +1,8 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
|
||||||
# prepare environment
|
# prepare environment
|
||||||
import sys
|
import sys
|
||||||
sys.path.append("..")
|
sys.path.append("..")
|
||||||
|
@ -9,15 +11,11 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cqtu.settings")
|
||||||
import django
|
import django
|
||||||
django.setup()
|
django.setup()
|
||||||
|
|
||||||
confirm_msg = "Do are you sure you want to clear all contest data? Answer with uppercase YES: "
|
confirm = raw_input("Do are you sure you want to clear all contest data? Answer with uppercase YES: ")
|
||||||
try:
|
|
||||||
confirm = raw_input(confirm_msg)
|
|
||||||
except NameError:
|
|
||||||
confirm = input(confirm_msg)
|
|
||||||
|
|
||||||
if confirm != "YES":
|
if confirm != "YES":
|
||||||
print("Aborting")
|
print("Aborting")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
from contest.models import QSO, ShadowCall, Reference, User
|
from contest.models import QSO, ShadowCall, Reference, User
|
||||||
|
|
|
@ -13,148 +13,148 @@ from .forms import QSOFormWithTime
|
||||||
import re
|
import re
|
||||||
|
|
||||||
def parseCBR(raw):
|
def parseCBR(raw):
|
||||||
""" Parse a CBR file for the CQTU
|
""" Parse a CBR file for the CQTU
|
||||||
|
|
||||||
Yes, this could be used for other tools, BUT you'd have to take
|
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
|
look at the regex and parsingfoo, as there is some cqtu specific foo
|
||||||
inside them.
|
inside them.
|
||||||
"""
|
"""
|
||||||
kvlinere = re.compile(r"^(?P<key>[A-Z-]+):(?: (?P<value>.*))?$")
|
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$")
|
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
|
qsoNo = 1
|
||||||
info = {
|
info = {
|
||||||
"call": None,
|
"call": None,
|
||||||
"location": None,
|
"location": None,
|
||||||
"qsos": [],
|
"qsos": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
for n, line in enumerate(raw.split("\n"), 1):
|
for n, line in enumerate(raw.split("\n"), 1):
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
# ignore empty lines
|
# ignore empty lines
|
||||||
if line == "":
|
if line == "":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
m = kvlinere.match(line)
|
m = kvlinere.match(line)
|
||||||
if m:
|
if m:
|
||||||
k = m.group("key")
|
k = m.group("key")
|
||||||
if k == "CALLSIGN":
|
if k == "CALLSIGN":
|
||||||
info["call"] = m.group("value").strip().upper()
|
info["call"] = m.group("value").strip().upper()
|
||||||
elif k == "LOCATION":
|
elif k == "LOCATION":
|
||||||
info["location"] = m.group("value").strip().upper()
|
info["location"] = m.group("value").strip().upper()
|
||||||
elif k == "QSO":
|
elif k == "QSO":
|
||||||
q = m.group("value").strip()
|
q = m.group("value").strip()
|
||||||
# no-s FM date UTC-HHMM call rst-s exch-s call-r rst-r exch-r
|
# 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
|
# no-s / date UTC-HH-MM call rst-s
|
||||||
qm = qsore.search(q)
|
qm = qsore.search(q)
|
||||||
if qm:
|
if qm:
|
||||||
qsoData = qm.groupdict()
|
qsoData = qm.groupdict()
|
||||||
qsoTime = timezone.datetime.strptime(qsoData["datetime"], "%Y-%m-%d %H%M")
|
qsoTime = timezone.datetime.strptime(qsoData["datetime"], "%Y-%m-%d %H%M")
|
||||||
qsoData["datetime"] = timezone.get_current_timezone().localize(qsoTime)
|
qsoData["datetime"] = timezone.get_current_timezone().localize(qsoTime)
|
||||||
qsoData["no_s"] = qsoNo
|
qsoData["no_s"] = qsoNo
|
||||||
|
|
||||||
if qsoData["band"] == "144":
|
if qsoData["band"] == "144":
|
||||||
qsoData["band"] = "2m"
|
qsoData["band"] = "2m"
|
||||||
elif qsoData["band"] == "432":
|
elif qsoData["band"] == "432":
|
||||||
qsoData["band"] = "70cm"
|
qsoData["band"] = "70cm"
|
||||||
else:
|
else:
|
||||||
raise forms.ValidationError("Error parsing band, needs to be either 144 or 432 (as we only support 2m and 70cm in this contest")
|
raise forms.ValidationError("Error parsing band, needs to be either 144 or 432 (as we only support 2m and 70cm in this contest")
|
||||||
|
|
||||||
info["qsos"].append(qsoData)
|
info["qsos"].append(qsoData)
|
||||||
|
|
||||||
if info["call"] != qsoData["call_s"]:
|
if info["call"] != qsoData["call_s"]:
|
||||||
raise forms.ValidationError("Error in line %d: qso was not made by you? (callsigns do not match)" % n)
|
raise forms.ValidationError("Error in line %d: qso was not made by you? (callsigns do not match)" % n)
|
||||||
if info["location"] != qsoData["exc_s"]:
|
if info["location"] != qsoData["exc_s"]:
|
||||||
raise forms.ValidationError("Error in line %d: exchange does not match your location? (callsigns do not match)" % n)
|
raise forms.ValidationError("Error in line %d: exchange does not match your location? (callsigns do not match)" % n)
|
||||||
else:
|
else:
|
||||||
raise forms.ValidationError("Error in line %d: qso was broken, regex did not match" % n)
|
raise forms.ValidationError("Error in line %d: qso was broken, regex did not match" % n)
|
||||||
|
|
||||||
qsoNo += 1
|
qsoNo += 1
|
||||||
elif k == "X-QSO":
|
elif k == "X-QSO":
|
||||||
qsoNo += 1
|
qsoNo += 1
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise forms.ValidationError("Error in line %d: could not parse \"KEY: value\" pair" % n)
|
raise forms.ValidationError("Error in line %d: could not parse \"KEY: value\" pair" % n)
|
||||||
|
|
||||||
return info
|
return info
|
||||||
|
|
||||||
class CBRForm(forms.Form):
|
class CBRForm(forms.Form):
|
||||||
data = forms.CharField(widget=forms.Textarea, label="Cabrillo data", help_text="Paste your cabrillo file contents here")
|
data = forms.CharField(widget=forms.Textarea, label="Cabrillo data", help_text="Paste your cabrillo file contents here")
|
||||||
|
|
||||||
def clean_data(self):
|
def clean_data(self):
|
||||||
rawData = self.cleaned_data["data"]
|
rawData = self.cleaned_data["data"]
|
||||||
parsedData = parseCBR(rawData)
|
parsedData = parseCBR(rawData)
|
||||||
|
|
||||||
return parsedData
|
return parsedData
|
||||||
|
|
||||||
def checkCBRConsistency(contest, user, info):
|
def checkCBRConsistency(contest, user, info):
|
||||||
errors = []
|
errors = []
|
||||||
qsos = []
|
qsos = []
|
||||||
if user.username != info["call"]:
|
if user.username != info["call"]:
|
||||||
errors.append("You are not the owner of this logfile! (%s != %s)" % (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"]:
|
if user.ref.name != info["location"]:
|
||||||
errors.append("Location of logfile and registered exchange do not match! (%s != %s)" % (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):
|
for n, qsoData in enumerate(info["qsos"], 1):
|
||||||
qsoFormData = {
|
qsoFormData = {
|
||||||
#"owner": user,
|
#"owner": user,
|
||||||
"time": qsoData["datetime"],
|
"time": qsoData["datetime"],
|
||||||
"call": qsoData["call_r"],
|
"call": qsoData["call_r"],
|
||||||
"band": Band.objects.get(contest=contest, name=qsoData["band"]).id,
|
"band": Band.objects.get(contest=contest, name=qsoData["band"]).id,
|
||||||
"reportTX": qsoData["rst_s"],
|
"reportTX": qsoData["rst_s"],
|
||||||
"reportRX": qsoData["rst_r"],
|
"reportRX": qsoData["rst_r"],
|
||||||
"ownNo": qsoData["no_s"],
|
"ownNo": qsoData["no_s"],
|
||||||
"otherNo": None,
|
"otherNo": None,
|
||||||
"refStr": qsoData["exc_r"],
|
"refStr": qsoData["exc_r"],
|
||||||
"remarks": "",
|
"remarks": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
qsoForm = QSOFormWithTime(user, data=qsoFormData)
|
qsoForm = QSOFormWithTime(user, data=qsoFormData)
|
||||||
qsoForm.is_valid()
|
qsoForm.is_valid()
|
||||||
qsoForm.instance.owner = user
|
qsoForm.instance.owner = user
|
||||||
print(qsoForm.errors)
|
print(qsoForm.errors)
|
||||||
qsos.append((qsoForm.instance, qsoForm))
|
qsos.append((qsoForm.instance, qsoForm))
|
||||||
|
|
||||||
return qsos, errors
|
return qsos, errors
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def uploadCBR(request):
|
def uploadCBR(request):
|
||||||
if not request.user.ref:
|
if not request.user.ref:
|
||||||
return HttpResponseRedirect(reverse("contest:index"))
|
return HttpResponseRedirect(reverse("contest:index"))
|
||||||
|
|
||||||
contest = Contest.objects.get(id=1)
|
contest = Contest.objects.get(id=1)
|
||||||
deadline = False
|
deadline = False
|
||||||
form = None
|
form = None
|
||||||
verifyData = []
|
verifyData = []
|
||||||
verifyErrors = []
|
verifyErrors = []
|
||||||
save = saved = False
|
save = saved = False
|
||||||
|
|
||||||
if timezone.now() < contest.deadline:
|
if timezone.now() < contest.deadline:
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
form = CBRForm(data=request.POST)
|
form = CBRForm(data=request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
verifyData, verifyErrors = checkCBRConsistency(contest, request.user, form.cleaned_data["data"])
|
verifyData, verifyErrors = checkCBRConsistency(contest, request.user, form.cleaned_data["data"])
|
||||||
|
|
||||||
if request.POST.get("action") == "save":
|
if request.POST.get("action") == "save":
|
||||||
save = True
|
save = True
|
||||||
if not verifyErrors:
|
if not verifyErrors:
|
||||||
cnt = 0
|
cnt = 0
|
||||||
for qso, qsoForm in verifyData:
|
for qso, qsoForm in verifyData:
|
||||||
if qsoForm.is_valid():
|
if qsoForm.is_valid():
|
||||||
qso.save()
|
qso.save()
|
||||||
cnt += 1
|
cnt += 1
|
||||||
|
|
||||||
if cnt > 0:
|
if cnt > 0:
|
||||||
messages.success(request, "%d QSOs have been saved from the cbr file" % cnt)
|
messages.success(request, "%d QSOs have been saved from the cbr file" % cnt)
|
||||||
else:
|
else:
|
||||||
messages.warnnig(request, "CBR file was parsed, but no QSOs could be saved, as all cointained errors.")
|
messages.warnnig(request, "CBR file was parsed, but no QSOs could be saved, as all cointained errors.")
|
||||||
|
|
||||||
return HttpResponseRedirect(reverse("contest:uploadCBR"))
|
return HttpResponseRedirect(reverse("contest:uploadCBR"))
|
||||||
else:
|
else:
|
||||||
form = CBRForm()
|
form = CBRForm()
|
||||||
else:
|
else:
|
||||||
deadline = True
|
deadline = True
|
||||||
|
|
||||||
return render(request, "contest/uploadCBR.html", {"deadline": deadline, 'form': form, 'verifyData': verifyData, 'verifyErrors': verifyErrors, 'save': save, 'saved': saved})
|
return render(request, "contest/uploadCBR.html", {"deadline": deadline, 'form': form, 'verifyData': verifyData, 'verifyErrors': verifyErrors, 'save': save, 'saved': saved})
|
||||||
|
|
234
contest/forms.py
234
contest/forms.py
|
@ -10,163 +10,165 @@ from .models import User, Reference, QSO, ShadowCall, EntryCategory, Contest
|
||||||
from .validators import CallUsernameValidator, CallLogValidator
|
from .validators import CallUsernameValidator, CallLogValidator
|
||||||
|
|
||||||
class CustomUserCreationForm(UserCreationForm):
|
class CustomUserCreationForm(UserCreationForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = ("username", "email", "dncall", "qrv2m", "qrv70cm", "extra2m70cm")
|
fields = ("username", "email", "dncall", "qrv2m", "qrv70cm", "extra2m70cm")
|
||||||
|
|
||||||
username = forms.CharField(max_length=50, validators=[CallUsernameValidator()])
|
username = forms.CharField(max_length=50, validators=[CallUsernameValidator()])
|
||||||
email = forms.EmailField(required=True)
|
email = forms.EmailField(required=True)
|
||||||
|
|
||||||
class UpdateRefForm(forms.Form):
|
class UpdateRefForm(forms.Form):
|
||||||
existingRef = forms.ModelChoiceField(label="Existing Exchange", queryset=Reference.objects.all(), help_text="If exchange already exists, select it here.", required=False)
|
existingRef = forms.ModelChoiceField(label="Existing Exchange", queryset=Reference.objects.all(), help_text="If exchange already exists, select it here.", required=False)
|
||||||
newRefName = forms.CharField(max_length=50, label="New Exchange", help_text="Enter name of new exchange, if we should create a new", required=False)
|
newRefName = forms.CharField(max_length=50, label="New Exchange", help_text="Enter name of new exchange, if we should create a new", required=False)
|
||||||
|
|
||||||
location = forms.CharField(max_length=128, label='Exact Location', help_text="E.g. MAR bei den Fahrstuehlen, TEL 15. OG", required=False)
|
location = forms.CharField(max_length=128, label='Exact Location', help_text="E.g. MAR bei den Fahrstuehlen, TEL 15. OG", required=False)
|
||||||
opName = forms.CharField(max_length=128, label='Operators', help_text="Name of operator(s)", required=False)
|
opName = forms.CharField(max_length=128, label='Operators', help_text="Name of operator(s)", required=False)
|
||||||
regTime = forms.DateTimeField(label="Registration time", help_text="Time of Registration")
|
regTime = forms.DateTimeField(label="Registration time", help_text="Time of Registration")
|
||||||
|
|
||||||
def clean_newRefName(self):
|
def clean_newRefName(self):
|
||||||
data = self.cleaned_data["newRefName"].strip().upper()
|
data = self.cleaned_data["newRefName"].strip().upper()
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
cleaned_data = super(UpdateRefForm, self).clean()
|
cleaned_data = super(UpdateRefForm, self).clean()
|
||||||
|
|
||||||
existingRef = cleaned_data.get("existingRef")
|
existingRef = cleaned_data.get("existingRef")
|
||||||
newRefName = cleaned_data.get("newRefName")
|
newRefName = cleaned_data.get("newRefName")
|
||||||
|
|
||||||
if newRefName:
|
if newRefName:
|
||||||
try:
|
try:
|
||||||
ref = Reference.objects.get(name=newRefName)
|
ref = Reference.objects.get(name=newRefName)
|
||||||
self.cleaned_data['newRefName'] = None
|
self.cleaned_data['newRefName'] = None
|
||||||
self.cleaned_data['existingRef'] = ref
|
self.cleaned_data['existingRef'] = ref
|
||||||
except Reference.DoesNotExist:
|
except Reference.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if existingRef and newRefName:
|
if existingRef and newRefName:
|
||||||
raise forms.ValidationError("Select an existing exchange or create a new one, not both!")
|
raise forms.ValidationError("Select an existing exchange or create a new one, not both!")
|
||||||
if not existingRef and not newRefName:
|
if not existingRef and not newRefName:
|
||||||
raise forms.ValidationError("Select either an existing exchange or create a new one!")
|
raise forms.ValidationError("Select either an existing exchange or create a new one!")
|
||||||
|
|
||||||
class UpdateCategoryForm(forms.Form):
|
class UpdateCategoryForm(forms.Form):
|
||||||
entry = forms.ModelChoiceField(label="Entry category", queryset=EntryCategory.objects.all())
|
entry = forms.ModelChoiceField(label="Entry category", queryset=EntryCategory.objects.all())
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(UpdateCategoryForm, self).__init__(*args, **kwargs)
|
super(UpdateCategoryForm, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.helper = FormHelper()
|
self.helper = FormHelper()
|
||||||
self.helper.form_class = "form-inline "
|
self.helper.form_class = "form-inline "
|
||||||
self.helper.form_style = 'inline'
|
self.helper.form_style = 'inline'
|
||||||
self.helper.field_template = "bootstrap3/layout/inline_field.html"
|
self.helper.field_template = "bootstrap3/layout/inline_field.html"
|
||||||
self.helper.action = reverse("profile")
|
self.helper.action = reverse("profile")
|
||||||
self.helper.add_input(Submit('submit', 'Set category'))
|
self.helper.add_input(Submit('submit', 'Set category'))
|
||||||
self.helper.layout = Layout('entry')
|
self.helper.layout = Layout('entry')
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
contest = Contest.objects.get(id=1)
|
contest = Contest.objects.get(id=1)
|
||||||
|
|
||||||
if contest.deadline < timezone.now():
|
if contest.deadline < timezone.now():
|
||||||
raise forms.ValidationError("The deadline for setting your contest category has passed")
|
raise forms.ValidationError("The deadline for setting your contest category has passed")
|
||||||
|
|
||||||
class QSOForm(forms.ModelForm):
|
class QSOForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = QSO
|
model = QSO
|
||||||
#fields = ["ownNo", "band", "call", "reportTX", "reportRX", "refStr", "otherNo", "remarks"]
|
#fields = ["ownNo", "band", "call", "reportTX", "reportRX", "refStr", "otherNo", "remarks"]
|
||||||
fields = ["ownNo", "band", "call", "reportTX", "reportRX", "refStr", "remarks"]
|
fields = ["ownNo", "band", "call", "reportTX", "reportRX", "refStr", "remarks"]
|
||||||
|
|
||||||
def __init__(self, user, *args, **kwargs):
|
def __init__(self, user, *args, **kwargs):
|
||||||
super(QSOForm, self).__init__(*args, **kwargs)
|
super(QSOForm, self).__init__(*args, **kwargs)
|
||||||
self.user = user
|
self.user = user
|
||||||
|
|
||||||
self.helper = FormHelper()
|
self.helper = FormHelper()
|
||||||
self.helper.form_id = "qso-log-form"
|
self.helper.form_id = "qso-log-form"
|
||||||
#self.helper.form_class = "form-inline "
|
#self.helper.form_class = "form-inline "
|
||||||
#self.helper.form_class = "form-horizontal"
|
#self.helper.form_class = "form-horizontal"
|
||||||
#self.helper.form_style = 'inline'
|
#self.helper.form_style = 'inline'
|
||||||
#self.helper.field_template = "bootstrap3/layout/inline_field.html"
|
#self.helper.field_template = "bootstrap3/layout/inline_field.html"
|
||||||
self.helper.action = reverse("contest:log")
|
self.helper.action = reverse("contest:log")
|
||||||
self.helper.add_input(Submit('submit', 'Log'))
|
self.helper.add_input(Submit('submit', 'Log'))
|
||||||
#self.helper.layout = Layout(
|
#self.helper.layout = Layout(
|
||||||
# #*(QSOForm.Meta.fields + [ButtonHolder(Submit('submit', 'Submit', css_class='button white'))]))
|
# #*(QSOForm.Meta.fields + [ButtonHolder(Submit('submit', 'Submit', css_class='button white'))]))
|
||||||
# *(QSOForm.Meta.fields + [FormActions(Submit('submit', 'Log!'))]))
|
# *(QSOForm.Meta.fields + [FormActions(Submit('submit', 'Log!'))]))
|
||||||
|
|
||||||
|
|
||||||
def clean_call(self):
|
def clean_call(self):
|
||||||
data = self.cleaned_data["call"].upper().strip()
|
data = self.cleaned_data["call"].upper().strip()
|
||||||
|
if Reference.objects.filter(name=data).count() > 0:
|
||||||
|
raise forms.ValidationError("Reference already exists")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
CallLogValidator()(data)
|
CallLogValidator()(data)
|
||||||
except forms.ValidationError:
|
except forms.ValidationError:
|
||||||
raise forms.ValidationError("Enter a valid callsign (1-2 chars, a number, 1-4 chars, maybe a /[A-Z])")
|
raise forms.ValidationError("Enter a valid callsign (1-2 chars, a number, 1-4 chars, maybe a /[A-Z])")
|
||||||
|
|
||||||
if data == self.user.username:
|
if data == self.user.username:
|
||||||
raise forms.ValidationError("You cannot log QSOs with yourself")
|
raise forms.ValidationError("You cannot log QSOs with yourself")
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def clean_ownNo(self):
|
def clean_ownNo(self):
|
||||||
data = self.cleaned_data["ownNo"]
|
data = self.cleaned_data["ownNo"]
|
||||||
|
|
||||||
if data < 1 or data > 100000:
|
if data < 1 or data > 100000:
|
||||||
raise forms.ValidationError("Number has to be in range of [1, 1000000]")
|
raise forms.ValidationError("Number has to be in range of [1, 1000000]")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
o = QSO.objects.get(owner=self.user.id, ownNo=data)
|
o = QSO.objects.get(owner=self.user.id, ownNo=data)
|
||||||
if not (self.instance and self.instance.id and self.instance.id == o.id):
|
if not (self.instance and self.instance.id and self.instance.id == o.id):
|
||||||
raise forms.ValidationError("You already logged a QSO with the number %s" % data)
|
raise forms.ValidationError("You already logged a QSO with the number %s" % data)
|
||||||
except QSO.DoesNotExist:
|
except QSO.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def clean_otherNo(self):
|
def clean_otherNo(self):
|
||||||
data = self.cleaned_data["otherNo"]
|
data = self.cleaned_data["otherNo"]
|
||||||
|
|
||||||
if not data:
|
if not data:
|
||||||
# empty value
|
# empty value
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if data < 1 or data > 100000:
|
if data < 1 or data > 100000:
|
||||||
raise forms.ValidationError("Number has to be in range of [1, 1000000]")
|
raise forms.ValidationError("Number has to be in range of [1, 1000000]")
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def clean_refStr(self):
|
def clean_refStr(self):
|
||||||
return self.cleaned_data["refStr"].upper()
|
return self.cleaned_data["refStr"].upper()
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
cleaned_data = super(QSOForm, self).clean()
|
cleaned_data = super(QSOForm, self).clean()
|
||||||
band = cleaned_data.get("band")
|
band = cleaned_data.get("band")
|
||||||
|
|
||||||
if band.contest.deadline < timezone.now():
|
if band.contest.deadline < timezone.now():
|
||||||
raise forms.ValidationError("The deadline for logging and editing QSOs has passed")
|
raise forms.ValidationError("The deadline for logging and editing QSOs has passed")
|
||||||
|
|
||||||
class QSOFormWithTime(QSOForm):
|
class QSOFormWithTime(QSOForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = QSO
|
model = QSO
|
||||||
#fields = ["time", "ownNo", "band", "call", "reportTX", "reportRX", "otherNo", "refStr", "remarks"]
|
#fields = ["time", "ownNo", "band", "call", "reportTX", "reportRX", "otherNo", "refStr", "remarks"]
|
||||||
fields = ["time", "ownNo", "band", "call", "reportTX", "reportRX", "refStr", "remarks"]
|
fields = ["time", "ownNo", "band", "call", "reportTX", "reportRX", "refStr", "remarks"]
|
||||||
|
|
||||||
class ShadowCallAddForm(forms.ModelForm):
|
class ShadowCallAddForm(forms.ModelForm):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ShadowCall
|
model = ShadowCall
|
||||||
fields = ['username']
|
fields = ['username']
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(ShadowCallAddForm, self).__init__(*args, **kwargs)
|
super(ShadowCallAddForm, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.helper = FormHelper()
|
self.helper = FormHelper()
|
||||||
self.helper.form_class = "form-inline "
|
self.helper.form_class = "form-inline "
|
||||||
self.helper.form_style = 'inline'
|
self.helper.form_style = 'inline'
|
||||||
self.helper.field_template = "bootstrap3/layout/inline_field.html"
|
self.helper.field_template = "bootstrap3/layout/inline_field.html"
|
||||||
self.helper.action = reverse("contest:registerRefs")
|
self.helper.action = reverse("contest:registerRefs")
|
||||||
self.helper.add_input(Submit('submit', 'Add shadow'))
|
self.helper.add_input(Submit('submit', 'Add shadow'))
|
||||||
self.helper.layout = Layout('username')
|
self.helper.layout = Layout('username')
|
||||||
|
|
||||||
def clean_username(self):
|
def clean_username(self):
|
||||||
data = self.cleaned_data["username"]
|
data = self.cleaned_data["username"]
|
||||||
if User.objects.filter(username=data).count() > 0:
|
if User.objects.filter(username=data).count() > 0:
|
||||||
raise forms.ValidationError("A user with this call already exists, this is not a shadow!")
|
raise forms.ValidationError("A user with this call already exists, this is not a shadow!")
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
|
@ -4,250 +4,243 @@ import datetime
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib.auth.models import AbstractUser
|
from django.contrib.auth.models import AbstractUser
|
||||||
from django.core.validators import RegexValidator, MinValueValidator, MaxValueValidator
|
from django.core.validators import RegexValidator
|
||||||
from django.db.models import Q, signals
|
from django.db.models import Q, signals
|
||||||
|
|
||||||
from .validators import CallUsernameValidator
|
from .validators import CallUsernameValidator
|
||||||
from .signals import checkForShadowCall
|
from .signals import checkForShadowCall
|
||||||
|
|
||||||
class Contest(models.Model):
|
class Contest(models.Model):
|
||||||
name = models.CharField(max_length=20)
|
name = models.CharField(max_length=20)
|
||||||
shortName = models.CharField(max_length=20, unique=True)
|
shortName = models.CharField(max_length=20, unique=True)
|
||||||
callQrg = models.ForeignKey("Frequency", models.SET_NULL, null=True, blank=True)
|
callQrg = models.ForeignKey("Frequency", models.SET_NULL, null=True, blank=True)
|
||||||
deadline = models.DateTimeField()
|
deadline = models.DateTimeField()
|
||||||
|
|
||||||
qsoStartTime = models.DateTimeField()
|
qsoStartTime = models.DateTimeField()
|
||||||
qsoEndTime = models.DateTimeField()
|
qsoEndTime = models.DateTimeField()
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_current_contest(cls):
|
|
||||||
return cls.objects.get(id=1)
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
class Reference(models.Model):
|
class Reference(models.Model):
|
||||||
name = models.CharField(max_length=20, unique=True, db_index=True)
|
name = models.CharField(max_length=20, unique=True, db_index=True)
|
||||||
description = models.TextField()
|
description = models.TextField()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
class EntryCategory(models.Model):
|
class EntryCategory(models.Model):
|
||||||
name = models.CharField(max_length=64, unique=True)
|
name = models.CharField(max_length=64, unique=True)
|
||||||
description = models.TextField(blank=True)
|
description = models.TextField(blank=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
class ShadowCall(models.Model):
|
class ShadowCall(models.Model):
|
||||||
username = models.CharField(max_length=20, unique=True, db_index=True, validators=[CallUsernameValidator()])
|
username = models.CharField(max_length=20, unique=True, db_index=True, validators=[CallUsernameValidator()])
|
||||||
ref = models.ForeignKey(Reference, models.SET_NULL,null=True, blank=True)
|
ref = models.ForeignKey(Reference, models.SET_NULL,null=True, blank=True)
|
||||||
|
|
||||||
location = models.CharField(max_length=128, default="", blank=True)
|
location = models.CharField(max_length=128, default="", blank=True)
|
||||||
opName = models.CharField(max_length=128, default="", blank=True)
|
opName = models.CharField(max_length=128, default="", blank=True)
|
||||||
regTime = models.DateTimeField(null=True, default=None)
|
regTime = models.DateTimeField(null=True, default=None)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.username
|
return self.username
|
||||||
|
|
||||||
class User(AbstractUser):
|
class User(AbstractUser):
|
||||||
ref = models.ForeignKey(Reference, models.SET_NULL, null=True, blank=True)
|
ref = models.ForeignKey(Reference, models.SET_NULL, null=True, blank=True)
|
||||||
cat = models.ForeignKey(EntryCategory, 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)
|
location = models.CharField(max_length=128, default="", blank=True)
|
||||||
opName = 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)
|
regTime = models.DateTimeField(null=True, default=None)
|
||||||
|
|
||||||
# because of cbr parsing bug, we sometimes have users who only have 70cm qsos
|
# because of cbr parsing bug, we sometimes have users who only have 70cm qsos
|
||||||
# we ignore the band for them when checking QSOs
|
# we ignore the band for them when checking QSOs
|
||||||
ignoreBand = models.BooleanField(default=False)
|
ignoreBand = models.BooleanField(default=False)
|
||||||
|
|
||||||
# extra profile stuff so DL7BST can sleep well without his doodles
|
# extra profile stuff so DL7BST can sleep well without his doodles
|
||||||
editedProfile = models.BooleanField(default=False)
|
editedProfile = models.BooleanField(default=False)
|
||||||
dncall = models.CharField(max_length=16, default='', blank=True,
|
dncall = models.CharField(max_length=16, default='', blank=True,
|
||||||
verbose_name="DN-Call",
|
verbose_name="DN-Call",
|
||||||
help_text="If you have a DN call that you will offer to SWLs please enter it here")
|
help_text="If you have a DN call that you will offer to SWLs please enter it here")
|
||||||
qrv2m = models.BooleanField(default=False,
|
qrv2m = models.BooleanField(default=False,
|
||||||
verbose_name="QRV on 2m",
|
verbose_name="QRV on 2m",
|
||||||
help_text="Will you be QRV on 2m during the contest?")
|
help_text="Will you be QRV on 2m during the contest?")
|
||||||
qrv70cm = models.BooleanField(default=False,
|
qrv70cm = models.BooleanField(default=False,
|
||||||
verbose_name="QRV on 70cm",
|
verbose_name="QRV on 70cm",
|
||||||
help_text="Will you be QRV on 70cm during the contest?")
|
help_text="Will you be QRV on 70cm during the contest?")
|
||||||
extra2m70cm = models.BooleanField(default=False,
|
extra2m70cm = models.BooleanField(default=False,
|
||||||
verbose_name="Additional 2m/70cm TRX",
|
verbose_name="Additional 2m/70cm TRX",
|
||||||
help_text="Will you bring an additional 2m/70cm TRX to lend to other participants?")
|
help_text="Will you bring an additional 2m/70cm TRX to lend to other participants?")
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(User, self).__init__(*args, **kwargs)
|
super(User, self).__init__(*args, **kwargs)
|
||||||
self._meta.get_field("username").validators = [CallUsernameValidator()]
|
self._meta.get_field("username").validators = [CallUsernameValidator()]
|
||||||
|
|
||||||
def getQSOCount(self):
|
def getQSOCount(self):
|
||||||
return self.qso_set.count()
|
return self.qso_set.count()
|
||||||
|
|
||||||
def getCfmdQSOCount(self):
|
def getCfmdQSOCount(self):
|
||||||
return self.qso_set.filter(~Q(cfmdQSO=None)).count()
|
return self.qso_set.filter(~Q(cfmdQSO=None)).count()
|
||||||
|
|
||||||
def getCfmdRefCount(self):
|
def getCfmdRefCount(self):
|
||||||
return len(set(map(lambda _x: _x["refStr"], self.qso_set.filter(ref__isnull=False).values("ref", "refStr"))))
|
return len(set(map(lambda _x: _x["refStr"], self.qso_set.filter(ref__isnull=False).values("ref", "refStr"))))
|
||||||
|
|
||||||
def calcClaimedPoints(self):
|
def calcClaimedPoints(self):
|
||||||
return self.calcPoints(cfmd=False)
|
return self.calcPoints(cfmd=False)
|
||||||
|
|
||||||
def calcCfmdPoints(self):
|
def calcCfmdPoints(self):
|
||||||
return self.calcPoints(cfmd=True)
|
return self.calcPoints(cfmd=True)
|
||||||
|
|
||||||
def calcPoints(self, cfmd):
|
def calcPoints(self, cfmd):
|
||||||
contest = Contest.objects.get(id=1)
|
contest = Contest.objects.get(id=1)
|
||||||
|
|
||||||
result = {"refCount": 0, "qsoCount": 0}
|
result = {"refCount": 0, "qsoCount": 0}
|
||||||
for band in contest.band_set.all():
|
for band in contest.band_set.all():
|
||||||
result[band.name] = self.calcBandPoints(band, cfmd)
|
result[band.name] = self.calcBandPoints(band, cfmd)
|
||||||
result["refCount"] += result[band.name]["refCount"]
|
result["refCount"] += result[band.name]["refCount"]
|
||||||
result["qsoCount"] += result[band.name]["qsoCount"]
|
result["qsoCount"] += result[band.name]["qsoCount"]
|
||||||
|
|
||||||
result["points"] = result["qsoCount"] * result["refCount"]
|
result["points"] = result["qsoCount"] * result["refCount"]
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def calcBandPoints(self, band, cfmd=False):
|
def calcBandPoints(self, band, cfmd=False):
|
||||||
contest = band.contest
|
contest = band.contest
|
||||||
qsos = self.qso_set.filter(band=band, time__gte=contest.qsoStartTime, time__lte=contest.qsoEndTime)
|
qsos = self.qso_set.filter(band=band, time__gte=contest.qsoStartTime, time__lte=contest.qsoEndTime)
|
||||||
if cfmd:
|
if cfmd:
|
||||||
qsos = qsos.filter(cfmdQSO__isnull=False)
|
qsos = qsos.filter(cfmdQSO__isnull=False)
|
||||||
refs = set(map(lambda _x: _x["refStr"], qsos.values("refStr")))
|
refs = set(map(lambda _x: _x["refStr"], qsos.values("refStr")))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"band": band,
|
"band": band,
|
||||||
"refs": refs,
|
"refs": refs,
|
||||||
"qsoCount": qsos.count(),
|
"qsoCount": qsos.count(),
|
||||||
"refCount": len(refs)
|
"refCount": len(refs)
|
||||||
}
|
}
|
||||||
signals.post_save.connect(checkForShadowCall, sender=User)
|
signals.post_save.connect(checkForShadowCall, sender=User)
|
||||||
|
|
||||||
class Band(models.Model):
|
class Band(models.Model):
|
||||||
name = models.CharField(max_length=10)
|
name = models.CharField(max_length=10)
|
||||||
contest = models.ForeignKey(Contest)
|
contest = models.ForeignKey(Contest)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
class Frequency(models.Model):
|
class Frequency(models.Model):
|
||||||
# qrg
|
# qrg
|
||||||
# band
|
# band
|
||||||
channel = models.CharField(max_length=3)
|
channel = models.CharField(max_length=3)
|
||||||
qrg = models.DecimalField(max_digits=7, decimal_places=3)
|
qrg = models.DecimalField(max_digits=7, decimal_places=3)
|
||||||
band = models.ForeignKey(Band)
|
band = models.ForeignKey(Band)
|
||||||
|
|
||||||
note = models.CharField(max_length=50, blank=True)
|
note = models.CharField(max_length=50, blank=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Channel %s: %s MHz" % (self.channel, self.qrg)
|
return "Channel %s: %s MHz" % (self.channel, self.qrg)
|
||||||
|
|
||||||
class QSO(models.Model):
|
class QSO(models.Model):
|
||||||
MAX_NO_VALUE = 1000000
|
reportValidator = RegexValidator("[1-5][1-9]")
|
||||||
reportValidator = RegexValidator("[1-5][1-9]")
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
index_together = [
|
index_together = [
|
||||||
["owner", "call"],
|
["owner", "call"],
|
||||||
]
|
]
|
||||||
|
|
||||||
owner = models.ForeignKey(User, db_index=True)
|
owner = models.ForeignKey(User, db_index=True)
|
||||||
time = models.DateTimeField(blank=True)
|
time = models.DateTimeField(blank=True)
|
||||||
call = models.CharField(max_length=20, db_index=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)
|
callRef = models.ForeignKey(User, models.SET_NULL, related_name='qsoref', null=True, blank=True, default=None)
|
||||||
band = models.ForeignKey(Band)
|
band = models.ForeignKey(Band)
|
||||||
|
|
||||||
reportTX = models.CharField(max_length=7, default=59, verbose_name='RS-S', validators=[reportValidator])
|
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])
|
reportRX = models.CharField(max_length=7, default=59, verbose_name='RS-R', validators=[reportValidator])
|
||||||
|
|
||||||
ownNo = models.IntegerField(verbose_name='No', validators=[MinValueValidator(1), MaxValueValidator(MAX_NO_VALUE)])
|
ownNo = models.IntegerField(verbose_name='No')
|
||||||
otherNo = models.IntegerField(verbose_name='No-R', null=True, blank=True,
|
otherNo = models.IntegerField(verbose_name='No-R', null=True, blank=True)
|
||||||
validators=[MinValueValidator(1), MaxValueValidator(MAX_NO_VALUE)])
|
|
||||||
|
|
||||||
refStr = models.CharField(max_length=20, verbose_name="EXC")
|
refStr = models.CharField(max_length=20, verbose_name="EXC")
|
||||||
ref = models.ForeignKey(Reference, models.SET_NULL, null=True, blank=True)
|
ref = models.ForeignKey(Reference, models.SET_NULL, null=True, blank=True)
|
||||||
|
|
||||||
remarks = models.CharField(max_length=50, blank=True, default=None)
|
remarks = models.CharField(max_length=50, blank=True, default=None)
|
||||||
|
|
||||||
cfmdQSO = models.ForeignKey("QSO", models.SET_NULL, null=True, blank=True, default=None)
|
cfmdQSO = models.ForeignKey("QSO", models.SET_NULL, null=True, blank=True, default=None)
|
||||||
|
|
||||||
CFMD_SEC = 5*60
|
CFMD_SEC = 5*60
|
||||||
|
|
||||||
def checkQSOData(self):
|
def checkQSOData(self):
|
||||||
""" Match strdata to log rows. Only call, if you intent to save this object if we return True! """
|
""" Match strdata to log rows. Only call, if you intent to save this object if we return True! """
|
||||||
# find reference
|
# find reference
|
||||||
changed = False
|
changed = False
|
||||||
if self.refStr:
|
if self.refStr:
|
||||||
refName = self.refStr.replace("-", "")
|
refName = self.refStr.replace("-", "")
|
||||||
if refName == "GX":
|
if refName == "GX":
|
||||||
refName = "DX"
|
refName = "DX"
|
||||||
|
|
||||||
# Old reference exists?
|
# Old reference exists?
|
||||||
if self.ref and self.ref.name != refName:
|
if self.ref and self.ref.name != refName:
|
||||||
self.ref = None
|
self.ref = None
|
||||||
changed = True
|
changed = True
|
||||||
|
|
||||||
if not self.ref:
|
if not self.ref:
|
||||||
# find matching ref
|
# find matching ref
|
||||||
try:
|
try:
|
||||||
self.ref = Reference.objects.get(name=refName)
|
self.ref = Reference.objects.get(name=refName)
|
||||||
changed = True
|
changed = True
|
||||||
except Reference.DoesNotExist:
|
except Reference.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# find call
|
# find call
|
||||||
if not self.callRef or self.callRef.username != self.call:
|
if not self.callRef or self.callRef.username != self.call:
|
||||||
try:
|
try:
|
||||||
self.callRef = User.objects.get(username=self.call)
|
self.callRef = User.objects.get(username=self.call)
|
||||||
changed = True
|
changed = True
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
if self.callRef:
|
if self.callRef:
|
||||||
changed = True
|
changed = True
|
||||||
self.callRef = None
|
self.callRef = None
|
||||||
|
|
||||||
# find matching qso
|
# find matching qso
|
||||||
if self.cfmdQSO:
|
if self.cfmdQSO:
|
||||||
# check if this still checks out
|
# check if this still checks out
|
||||||
q = self.cfmdQSO
|
q = self.cfmdQSO
|
||||||
if abs((self.time - q.time).total_seconds()) <= self.CFMD_SEC and \
|
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 \
|
self.ref and self.owner.ref and self.callRef and q.callRef and \
|
||||||
q.owner == self.callRef and q.callRef == self.owner and \
|
q.owner == self.callRef and q.callRef == self.owner and \
|
||||||
self.ref == q.owner.ref and self.owner.ref == q.ref and \
|
self.ref == q.owner.ref and self.owner.ref == q.ref and \
|
||||||
self.band == q.band:
|
self.band == q.band:
|
||||||
# checks out
|
# checks out
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
changed = True
|
changed = True
|
||||||
self.cfmdQSO.cfmdQSO = None
|
self.cfmdQSO.cfmdQSO = None
|
||||||
self.cfmdQSO.save(checkQSO=False)
|
self.cfmdQSO.save(checkQSO=False)
|
||||||
self.cfmdQSO = None
|
self.cfmdQSO = None
|
||||||
|
|
||||||
if self.ref and self.callRef and self.callRef.ref and not self.cfmdQSO:
|
if self.ref and self.callRef and self.callRef.ref and not self.cfmdQSO:
|
||||||
# look for a matching line
|
# look for a matching line
|
||||||
q = QSO.objects.filter(
|
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))),
|
(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,
|
owner=self.callRef,
|
||||||
callRef=self.owner,
|
callRef=self.owner,
|
||||||
owner__ref=self.ref,
|
owner__ref=self.ref,
|
||||||
ref=self.owner.ref,
|
ref=self.owner.ref,
|
||||||
band=self.band)
|
band=self.band)
|
||||||
|
|
||||||
if q.count() == 1:
|
if q.count() == 1:
|
||||||
changed = True
|
changed = True
|
||||||
q[0].cfmdQSO = self
|
q[0].cfmdQSO = self
|
||||||
q[0].save(checkQSO=False)
|
q[0].save(checkQSO=False)
|
||||||
self.cfmdQSO = q[0]
|
self.cfmdQSO = q[0]
|
||||||
|
|
||||||
return changed
|
return changed
|
||||||
|
|
||||||
def save(self, checkQSO=True, *args, **kwargs):
|
def save(self, checkQSO=True, *args, **kwargs):
|
||||||
if checkQSO:
|
if checkQSO:
|
||||||
self.checkQSOData()
|
self.checkQSOData()
|
||||||
|
|
||||||
super(QSO, self).save(*args, **kwargs)
|
super(QSO, self).save(*args, **kwargs)
|
||||||
|
|
||||||
def __str__(self):
|
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)
|
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)
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
|
|
||||||
def checkForShadowCall(sender, instance, created, raw, **kwargs):
|
def checkForShadowCall(sender, instance, created, raw, **kwargs):
|
||||||
""" Check for existing shadow call. If present copy it's data and delete it. """
|
""" Check for existing shadow call. If present copy it's data and delete it. """
|
||||||
if created:
|
if created:
|
||||||
# to prevent circular imports we import ShadowCall here
|
# to prevent circular imports we import ShadowCall here
|
||||||
from .models import ShadowCall
|
from .models import ShadowCall
|
||||||
|
|
||||||
try:
|
try:
|
||||||
shadow = ShadowCall.objects.get(username=instance.username)
|
shadow = ShadowCall.objects.get(username=instance.username)
|
||||||
instance.ref = shadow.ref
|
instance.ref = shadow.ref
|
||||||
instance.location = shadow.location
|
instance.location = shadow.location
|
||||||
instance.opName = shadow.opName
|
instance.opName = shadow.opName
|
||||||
instance.regTime = shadow.regTime
|
instance.regTime = shadow.regTime
|
||||||
|
|
||||||
instance.save()
|
instance.save()
|
||||||
shadow.delete()
|
shadow.delete()
|
||||||
except ShadowCall.DoesNotExist:
|
except ShadowCall.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -20,16 +20,16 @@ import contest.views as contest_views
|
||||||
from contest.cbrparser import uploadCBR
|
from contest.cbrparser import uploadCBR
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^$', contest_views.contestIndex, name='index'),
|
url(r'^$', contest_views.contestIndex, name='index'),
|
||||||
url(r'^regref/$', contest_views.registerRefs, name='registerRefs'),
|
url(r'^regref/$', contest_views.registerRefs, name='registerRefs'),
|
||||||
url(r'^regref/edit/(?P<uid>\d+)/$', contest_views.updateRef, {"shadow": False}, name='updateRef'),
|
url(r'^regref/edit/(?P<uid>\d+)/$', contest_views.updateRef, {"shadow": False}, name='updateRef'),
|
||||||
url(r'^regref/shadow/edit/(?P<uid>\d+)/$', contest_views.updateRef, {"shadow": True}, name='updateShadowRef'),
|
url(r'^regref/shadow/edit/(?P<uid>\d+)/$', contest_views.updateRef, {"shadow": True}, name='updateShadowRef'),
|
||||||
url(r'^regref/qsos/all/$', contest_views.viewAllQSOs, name='viewAllQSOs'),
|
url(r'^regref/qsos/all/$', contest_views.viewAllQSOs, name='viewAllQSOs'),
|
||||||
url(r'^regref/qsos/user/(?P<uid>\d+)/$', contest_views.viewUserQSOs, name='viewUserQSOs'),
|
url(r'^regref/qsos/user/(?P<uid>\d+)/$', contest_views.viewUserQSOs, name='viewUserQSOs'),
|
||||||
url(r'^overview/$', contest_views.overview, name='overview'),
|
url(r'^overview/$', contest_views.overview, name='overview'),
|
||||||
url(r'^log/$', contest_views.log, name='log'),
|
url(r'^log/$', contest_views.log, name='log'),
|
||||||
url(r'^log/edit/(?P<qsoid>\d+)/$', contest_views.logEdit, name='logEdit'),
|
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'^log/delete/(?P<qsoid>\d+)/$', contest_views.logDelete, name='logDelete'),
|
||||||
url(r'^uploadcbr/$', uploadCBR, name='uploadCBR'),
|
url(r'^uploadcbr/$', uploadCBR, name='uploadCBR'),
|
||||||
url(r'^regref/recheckqsos/$', contest_views.recheckAllQSOs, name='recheckAllQSOs'),
|
url(r'^regref/recheckqsos/$', contest_views.recheckAllQSOs, name='recheckAllQSOs'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -25,3 +25,4 @@ class CallLogValidator(validators.RegexValidator):
|
||||||
'e.g. DL7BST, DN1BER-1, DL/OE1FOO, DN1FTW-1/p'
|
'e.g. DL7BST, DN1BER-1, DL/OE1FOO, DN1FTW-1/p'
|
||||||
)
|
)
|
||||||
flags = re.ASCII if six.PY3 else 0
|
flags = re.ASCII if six.PY3 else 0
|
||||||
|
|
||||||
|
|
340
contest/views.py
340
contest/views.py
|
@ -19,251 +19,251 @@ from .models import User, Contest, Frequency, Reference, QSO, ShadowCall
|
||||||
from .forms import UpdateRefForm, QSOForm, QSOFormWithTime, CustomUserCreationForm, ShadowCallAddForm, UpdateCategoryForm
|
from .forms import UpdateRefForm, QSOForm, QSOFormWithTime, CustomUserCreationForm, ShadowCallAddForm, UpdateCategoryForm
|
||||||
|
|
||||||
def index(request):
|
def index(request):
|
||||||
if request.user.is_authenticated():
|
if request.user.is_authenticated():
|
||||||
return HttpResponseRedirect(reverse("contest:index"))
|
return HttpResponseRedirect(reverse("contest:index"))
|
||||||
|
|
||||||
return render(request, "index.html", {"loginForm": AuthenticationForm()})
|
return render(request, "index.html", {"loginForm": AuthenticationForm()})
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def contestIndex(request):
|
def contestIndex(request):
|
||||||
qsoform = QSOForm(request.user)
|
qsoform = QSOForm(request.user)
|
||||||
contest = Contest.objects.get(id=1)
|
contest = Contest.objects.get(id=1)
|
||||||
qrgs = Frequency.objects.filter(band__contest=contest).order_by("channel")
|
qrgs = Frequency.objects.filter(band__contest=contest).order_by("channel")
|
||||||
|
|
||||||
return render(request, 'contest/index.html', {"qsoform": qsoform, "contest": contest, "qrgs": qrgs})
|
return render(request, 'contest/index.html', {"qsoform": qsoform, "contest": contest, "qrgs": qrgs})
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def log(request):
|
def log(request):
|
||||||
if not request.user.ref:
|
if not request.user.ref:
|
||||||
return HttpResponseRedirect(reverse("contest:index"))
|
return HttpResponseRedirect(reverse("contest:index"))
|
||||||
|
|
||||||
form = None
|
form = None
|
||||||
|
|
||||||
qsos = QSO.objects.filter(owner=request.user).order_by("-ownNo")
|
qsos = QSO.objects.filter(owner=request.user).order_by("-ownNo")
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
form = QSOForm(user=request.user, data=request.POST)
|
form = QSOForm(user=request.user, data=request.POST)
|
||||||
form.helper.form_tag = False
|
form.helper.form_tag = False
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
l = form.instance
|
l = form.instance
|
||||||
if not l.time:
|
if not l.time:
|
||||||
# set current time
|
# set current time
|
||||||
l.time = datetime.datetime.now()
|
l.time = datetime.datetime.now()
|
||||||
|
|
||||||
l.owner = request.user
|
l.owner = request.user
|
||||||
l.save()
|
l.save()
|
||||||
|
|
||||||
messages.success(request, "QSO saved!")
|
messages.success(request, "QSO saved!")
|
||||||
|
|
||||||
return HttpResponseRedirect(reverse("contest:log"))
|
return HttpResponseRedirect(reverse("contest:log"))
|
||||||
else:
|
else:
|
||||||
data = {
|
data = {
|
||||||
"ownNo": qsos[0].ownNo + 1 if len(qsos) > 0 else 1,
|
"ownNo": qsos[0].ownNo + 1 if len(qsos) > 0 else 1,
|
||||||
"reportRX": "59",
|
"reportRX": "59",
|
||||||
"reportTX": "59",
|
"reportTX": "59",
|
||||||
}
|
}
|
||||||
if qsos.count() > 0:
|
if qsos.count() > 0:
|
||||||
data["band"] = qsos[0].band
|
data["band"] = qsos[0].band
|
||||||
|
|
||||||
form = QSOForm(request.user, initial=data)
|
form = QSOForm(request.user, initial=data)
|
||||||
form.helper.form_tag = False
|
form.helper.form_tag = False
|
||||||
|
|
||||||
|
|
||||||
return render(request, 'contest/log.html', {'form': form, 'qsos': qsos})
|
return render(request, 'contest/log.html', {'form': form, 'qsos': qsos})
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def logEdit(request, qsoid):
|
def logEdit(request, qsoid):
|
||||||
if not request.user.ref:
|
if not request.user.ref:
|
||||||
return HttpResponseRedirect(reverse("contest:index"))
|
return HttpResponseRedirect(reverse("contest:index"))
|
||||||
|
|
||||||
qso = QSO.objects.get(id=qsoid, owner=request.user)
|
qso = QSO.objects.get(id=qsoid, owner=request.user)
|
||||||
form = None
|
form = None
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
form = QSOFormWithTime(user=request.user, instance=qso, data=request.POST)
|
form = QSOFormWithTime(user=request.user, instance=qso, data=request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
form.instance.save()
|
form.instance.save()
|
||||||
|
|
||||||
messages.info(request, "QSO has been edited")
|
messages.info(request, "QSO has been edited")
|
||||||
return HttpResponseRedirect(reverse("contest:log"))
|
return HttpResponseRedirect(reverse("contest:log"))
|
||||||
else:
|
else:
|
||||||
form = QSOFormWithTime(user=request.user, instance=qso)
|
form = QSOFormWithTime(user=request.user, instance=qso)
|
||||||
|
|
||||||
return render(request, 'contest/logEdit.html', {'form': form, "qso": qso})
|
return render(request, 'contest/logEdit.html', {'form': form, "qso": qso})
|
||||||
|
|
||||||
def logDelete(request, qsoid):
|
def logDelete(request, qsoid):
|
||||||
if not request.user.ref:
|
if not request.user.ref:
|
||||||
return HttpResponseRedirect(reverse("contest:index"))
|
return HttpResponseRedirect(reverse("contest:index"))
|
||||||
|
|
||||||
qso = QSO.objects.get(id=qsoid, owner=request.user)
|
qso = QSO.objects.get(id=qsoid, owner=request.user)
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
if "delete" in request.POST:
|
if "delete" in request.POST:
|
||||||
if request.POST["delete"].lower() == "yes":
|
if request.POST["delete"].lower() == "yes":
|
||||||
qso.delete()
|
qso.delete()
|
||||||
messages.info(request, "QSO has been deleted")
|
messages.info(request, "QSO has been deleted")
|
||||||
return HttpResponseRedirect(reverse("contest:log"))
|
return HttpResponseRedirect(reverse("contest:log"))
|
||||||
elif request.POST["delete"].lower() == "no":
|
elif request.POST["delete"].lower() == "no":
|
||||||
return HttpResponseRedirect(reverse("contest:log"))
|
return HttpResponseRedirect(reverse("contest:log"))
|
||||||
|
|
||||||
return render(request, 'contest/logDelete.html', {"qso": qso})
|
return render(request, 'contest/logDelete.html', {"qso": qso})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@staff_member_required
|
@staff_member_required
|
||||||
def registerRefs(request):
|
def registerRefs(request):
|
||||||
allUser = User.objects.all()
|
allUser = User.objects.all()
|
||||||
shadows = ShadowCall.objects.all()
|
shadows = ShadowCall.objects.all()
|
||||||
|
|
||||||
qsos = QSO.objects.all().order_by("-time")[0:10]
|
qsos = QSO.objects.all().order_by("-time")[0:10]
|
||||||
|
|
||||||
shadowForm = None
|
shadowForm = None
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
shadowForm = ShadowCallAddForm(data=request.POST)
|
shadowForm = ShadowCallAddForm(data=request.POST)
|
||||||
if shadowForm.is_valid():
|
if shadowForm.is_valid():
|
||||||
shadowForm.instance.save()
|
shadowForm.instance.save()
|
||||||
messages.success(request, "Successfully added shadow user %s" % (shadowForm.instance.username,))
|
messages.success(request, "Successfully added shadow user %s" % (shadowForm.instance.username,))
|
||||||
return HttpResponseRedirect(reverse("contest:registerRefs"))
|
return HttpResponseRedirect(reverse("contest:registerRefs"))
|
||||||
else:
|
else:
|
||||||
shadowForm = ShadowCallAddForm()
|
shadowForm = ShadowCallAddForm()
|
||||||
|
|
||||||
return render(request, 'contest/registerRefs.html', {'alluser': allUser, "qsos": qsos, "shadowForm": shadowForm, "shadows": shadows})
|
return render(request, 'contest/registerRefs.html', {'alluser': allUser, "qsos": qsos, "shadowForm": shadowForm, "shadows": shadows})
|
||||||
|
|
||||||
def getPage(paginator, pageNo):
|
def getPage(paginator, pageNo):
|
||||||
try:
|
try:
|
||||||
return paginator.page(pageNo)
|
return paginator.page(pageNo)
|
||||||
except PageNotAnInteger:
|
except PageNotAnInteger:
|
||||||
return paginator.page(1)
|
return paginator.page(1)
|
||||||
except EmptyPage:
|
except EmptyPage:
|
||||||
return paginator.page(paginator.num_pages)
|
return paginator.page(paginator.num_pages)
|
||||||
|
|
||||||
|
|
||||||
@staff_member_required
|
@staff_member_required
|
||||||
def recheckAllQSOs(request):
|
def recheckAllQSOs(request):
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
cnt = 0
|
cnt = 0
|
||||||
for qso in QSO.objects.all():
|
for qso in QSO.objects.all():
|
||||||
if qso.checkQSOData():
|
if qso.checkQSOData():
|
||||||
print(qso)
|
print(qso)
|
||||||
qso.save()
|
qso.save()
|
||||||
cnt += 1
|
cnt += 1
|
||||||
|
|
||||||
messages.success(request, "Allo QSO have been checked against each other (%d QSOs modified)" % (cnt,))
|
messages.success(request, "Allo QSO have been checked against each other (%d QSOs modified)" % (cnt,))
|
||||||
return HttpResponseRedirect(reverse("contest:registerRefs"))
|
return HttpResponseRedirect(reverse("contest:registerRefs"))
|
||||||
|
|
||||||
return render(request, "contest/checkAllQSOs.html", {})
|
return render(request, "contest/checkAllQSOs.html", {})
|
||||||
|
|
||||||
@staff_member_required
|
@staff_member_required
|
||||||
def viewUserQSOs(request, uid, page=1):
|
def viewUserQSOs(request, uid, page=1):
|
||||||
user = get_object_or_404(User, id=uid)
|
user = get_object_or_404(User, id=uid)
|
||||||
qsos = QSO.objects.filter(owner=user).order_by("-time")
|
qsos = QSO.objects.filter(owner=user).order_by("-time")
|
||||||
qsoPager = Paginator(qsos, 50)
|
qsoPager = Paginator(qsos, 50)
|
||||||
qsoPage = getPage(qsoPager, request.GET.get('page'))
|
qsoPage = getPage(qsoPager, request.GET.get('page'))
|
||||||
|
|
||||||
userRefs = set(map(lambda _x: _x["refStr"], user.qso_set.filter(ref__isnull=False).values("ref", "refStr")))
|
userRefs = set(map(lambda _x: _x["refStr"], user.qso_set.filter(ref__isnull=False).values("ref", "refStr")))
|
||||||
|
|
||||||
return render(request, "contest/viewUserQSOs.html", {'owner': user, 'qsos': qsos, 'qsoPage': qsoPage, 'userRefs': userRefs})
|
return render(request, "contest/viewUserQSOs.html", {'owner': user, 'qsos': qsos, 'qsoPage': qsoPage, 'userRefs': userRefs})
|
||||||
|
|
||||||
@staff_member_required
|
@staff_member_required
|
||||||
def updateRef(request, shadow, uid):
|
def updateRef(request, shadow, uid):
|
||||||
user = None
|
user = None
|
||||||
form = None
|
form = None
|
||||||
|
|
||||||
if shadow:
|
if shadow:
|
||||||
user = get_object_or_404(ShadowCall, id=uid)
|
user = get_object_or_404(ShadowCall, id=uid)
|
||||||
else:
|
else:
|
||||||
user = get_object_or_404(User, id=uid)
|
user = get_object_or_404(User, id=uid)
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
form = UpdateRefForm(data=request.POST)
|
form = UpdateRefForm(data=request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
ref = None
|
ref = None
|
||||||
if form.cleaned_data["existingRef"]:
|
if form.cleaned_data["existingRef"]:
|
||||||
ref = form.cleaned_data["existingRef"]
|
ref = form.cleaned_data["existingRef"]
|
||||||
else:
|
else:
|
||||||
ref = Reference(name=form.cleaned_data["newRefName"])
|
ref = Reference(name=form.cleaned_data["newRefName"])
|
||||||
ref.save()
|
ref.save()
|
||||||
messages.info(request, "New Ref '%s' created" % ref)
|
messages.info(request, "New Ref '%s' created" % ref)
|
||||||
|
|
||||||
user.ref = ref
|
user.ref = ref
|
||||||
user.regTime = form.cleaned_data["regTime"]
|
user.regTime = form.cleaned_data["regTime"]
|
||||||
user.location = form.cleaned_data["location"]
|
user.location = form.cleaned_data["location"]
|
||||||
user.opName = form.cleaned_data["opName"]
|
user.opName = form.cleaned_data["opName"]
|
||||||
user.save()
|
user.save()
|
||||||
messages.success(request, "%s%s ref set to %s" % ("(shadow) " if shadow else "", user, ref))
|
messages.success(request, "%s%s ref set to %s" % ("(shadow) " if shadow else "", user, ref))
|
||||||
return HttpResponseRedirect(reverse("contest:registerRefs"))
|
return HttpResponseRedirect(reverse("contest:registerRefs"))
|
||||||
else:
|
else:
|
||||||
data = {}
|
data = {}
|
||||||
if user.ref:
|
if user.ref:
|
||||||
data["existingRef"] = user.ref
|
data["existingRef"] = user.ref
|
||||||
|
|
||||||
if user.regTime:
|
if user.regTime:
|
||||||
data["regTime"] = user.regTime
|
data["regTime"] = user.regTime
|
||||||
else:
|
else:
|
||||||
data["regTime"] = timezone.now()
|
data["regTime"] = timezone.now()
|
||||||
data["opName"] = user.opName
|
data["opName"] = user.opName
|
||||||
data["location"] = user.location
|
data["location"] = user.location
|
||||||
|
|
||||||
form = UpdateRefForm(initial=data)
|
form = UpdateRefForm(initial=data)
|
||||||
|
|
||||||
return render(request, 'contest/updateRef.html', {'userobj': user, 'form': form, "shadow": shadow})
|
return render(request, 'contest/updateRef.html', {'userobj': user, 'form': form, "shadow": shadow})
|
||||||
|
|
||||||
@staff_member_required
|
@staff_member_required
|
||||||
def viewAllQSOs(request, page=1):
|
def viewAllQSOs(request, page=1):
|
||||||
qsos = QSO.objects.all().order_by("-time")
|
qsos = QSO.objects.all().order_by("-time")
|
||||||
qsoPager = Paginator(qsos, 10)
|
qsoPager = Paginator(qsos, 10)
|
||||||
qsoPage = getPage(qsoPager, request.GET.get('page'))
|
qsoPage = getPage(qsoPager, request.GET.get('page'))
|
||||||
|
|
||||||
return render(request, 'contest/viewAllQSOs.html', {'qsoPage': qsoPage})
|
return render(request, 'contest/viewAllQSOs.html', {'qsoPage': qsoPage})
|
||||||
|
|
||||||
def overview(request):
|
def overview(request):
|
||||||
# FIXME: Hardcoded for cqtu... everywhere
|
# FIXME: Hardcoded for cqtu... everywhere
|
||||||
c = Contest.objects.get(id=1)
|
c = Contest.objects.get(id=1)
|
||||||
qrgs = Frequency.objects.filter(band__contest=c).order_by("channel")
|
qrgs = Frequency.objects.filter(band__contest=c).order_by("channel")
|
||||||
return render(request, 'contest/overview.html', {'contest': c, 'qrgs': qrgs})
|
return render(request, 'contest/overview.html', {'contest': c, 'qrgs': qrgs})
|
||||||
|
|
||||||
def register(request):
|
def register(request):
|
||||||
form = None
|
form = None
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
form = CustomUserCreationForm(data=request.POST)
|
form = CustomUserCreationForm(data=request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
form.save()
|
form.save()
|
||||||
auth_login(request, form.instance)
|
auth_login(request, form.instance)
|
||||||
messages.info(request, "Registration as user %s successfull!" % form.instance.username)
|
messages.info(request, "Registration as user %s successfull!" % form.instance.username)
|
||||||
|
|
||||||
return HttpResponseRedirect(reverse("contest:index"))
|
return HttpResponseRedirect(reverse("contest:index"))
|
||||||
else:
|
else:
|
||||||
form = CustomUserCreationForm()
|
form = CustomUserCreationForm()
|
||||||
|
|
||||||
return render(request, 'registration/register.html', {"form": form})
|
return render(request, 'registration/register.html', {"form": form})
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def profile(request):
|
def profile(request):
|
||||||
pwForm = None
|
pwForm = None
|
||||||
catForm = None
|
catForm = None
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
if request.POST.get("submit", None) == "pwchange":
|
if request.POST.get("submit", None) == "pwchange":
|
||||||
pwForm = PasswordChangeForm(user=request.user, data=request.POST)
|
pwForm = PasswordChangeForm(user=request.user, data=request.POST)
|
||||||
if pwForm.is_valid():
|
if pwForm.is_valid():
|
||||||
pwForm.save()
|
pwForm.save()
|
||||||
auth_login(request, pwForm.user)
|
auth_login(request, pwForm.user)
|
||||||
messages.success(request, "Password changed")
|
messages.success(request, "Password changed")
|
||||||
|
|
||||||
return HttpResponseRedirect(reverse("profile"))
|
return HttpResponseRedirect(reverse("profile"))
|
||||||
else:
|
else:
|
||||||
catForm = UpdateCategoryForm(data=request.POST)
|
catForm = UpdateCategoryForm(data=request.POST)
|
||||||
if catForm.is_valid():
|
if catForm.is_valid():
|
||||||
request.user.cat = catForm.cleaned_data["entry"]
|
request.user.cat = catForm.cleaned_data["entry"]
|
||||||
request.user.save()
|
request.user.save()
|
||||||
messages.success(request, "Entry category set")
|
messages.success(request, "Entry category set")
|
||||||
|
|
||||||
return HttpResponseRedirect(reverse("profile"))
|
return HttpResponseRedirect(reverse("profile"))
|
||||||
|
|
||||||
if not pwForm:
|
if not pwForm:
|
||||||
pwForm = PasswordChangeForm(user=request.user)
|
pwForm = PasswordChangeForm(user=request.user)
|
||||||
|
|
||||||
if not catForm:
|
if not catForm:
|
||||||
catForm = UpdateCategoryForm(initial={'entry': request.user.cat})
|
catForm = UpdateCategoryForm(initial={'entry': request.user.cat})
|
||||||
|
|
||||||
return render(request, 'registration/profile.html', {"pwForm": pwForm, "catForm": catForm})
|
return render(request, 'registration/profile.html', {"pwForm": pwForm, "catForm": catForm})
|
||||||
|
|
|
@ -33,20 +33,18 @@ ALLOWED_HOSTS = []
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
# default
|
# default
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'crispy_forms',
|
|
||||||
'rest_framework',
|
'crispy_forms',
|
||||||
'django_filters',
|
|
||||||
|
|
||||||
# local
|
# local
|
||||||
'contest',
|
'contest',
|
||||||
'api',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
@ -142,9 +140,6 @@ STATICFILES_DIRS = [
|
||||||
CRISPY_TEMPLATE_PACK = 'bootstrap3'
|
CRISPY_TEMPLATE_PACK = 'bootstrap3'
|
||||||
|
|
||||||
MESSAGE_TAGS = {
|
MESSAGE_TAGS = {
|
||||||
messages.ERROR: 'danger',
|
messages.ERROR: 'danger',
|
||||||
}
|
}
|
||||||
|
|
||||||
REST_FRAMEWORK = {
|
|
||||||
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend']
|
|
||||||
}
|
|
||||||
|
|
23
cqtu/urls.py
23
cqtu/urls.py
|
@ -26,18 +26,17 @@ from contest.views import index, register, profile
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url('^$', index, name="index"),
|
url('^$', index, name="index"),
|
||||||
url('^cqtufm2019/', include('contest.urls', namespace='contest')),
|
url('^cqtufm2019/', include('contest.urls', namespace='contest')),
|
||||||
|
|
||||||
url(r'^admin/', admin.site.urls),
|
url(r'^admin/', admin.site.urls),
|
||||||
url(r'^login/$', auth_views.login, name='login'),
|
url(r'^login/$', auth_views.login, name='login'),
|
||||||
url(r'^logout/$', auth_views.logout, {'next_page': '/'}, name='logout'),
|
url(r'^logout/$', auth_views.logout, {'next_page': '/'}, name='logout'),
|
||||||
url(r'^register/$', register, name='register'),
|
url(r'^register/$', register, name='register'),
|
||||||
url(r'^profile/$', profile, name='profile'),
|
url(r'^profile/$', profile, name='profile'),
|
||||||
url(r'^api/', include('api.urls')),
|
#url(r'^register/$', CreateView.as_view(
|
||||||
#url(r'^register/$', CreateView.as_view(
|
# template_name='registration/register.html',
|
||||||
# template_name='registration/register.html',
|
# form_class=CustomUserCreationForm,
|
||||||
# form_class=CustomUserCreationForm,
|
# success_url='/',
|
||||||
# success_url='/',
|
#), name='register'),
|
||||||
#), name='register'),
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,4 +1,2 @@
|
||||||
Django<1.12
|
Django==1.10
|
||||||
django-crispy-forms
|
django-crispy-forms
|
||||||
django-rest-framework
|
|
||||||
django-filter
|
|
||||||
|
|
|
@ -10,11 +10,8 @@
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<p>
|
<p>
|
||||||
Please register with your (uppercase) Callsign as Usernames.
|
Please register with your (uppercase) Callsign as Usernames.
|
||||||
For DN-Calls, -[0-9] is allowed (e.g. DN1ABC-2 for the second group).
|
For DN-Calls, -[0-9] is allowed.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
|
||||||
Note: If you are a <strong>Ham/OM/YL</strong> please with your <strong>own</strong> callsign (e.g. DL7DOC). If you are a <strong>SWL</strong>, please use the <strong>DN-Call provided</strong> by your operator.
|
|
||||||
</p>
|
|
||||||
<form method="POST" action="{% url 'register' %}">
|
<form method="POST" action="{% url 'register' %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form|crispy }}
|
{{ form|crispy }}
|
||||||
|
|
Loading…
Reference in New Issue