Compare commits
9 Commits
master
...
cleanup-an
Author | SHA1 | Date |
---|---|---|
Sebastian Lohff | f4144633d6 | |
Sebastian Lohff | 1f93f9e7bd | |
Sebastian Lohff | 7ae53b6150 | |
Sebastian Lohff | 13afffda02 | |
Sebastian Lohff | 1f39cc5b40 | |
Sebastian Lohff | 933a337eac | |
Sebastian Lohff | 635289ed8f | |
Sebastian Lohff | 970a3ae517 | |
Sebastian Lohff | e5465632b4 |
|
@ -1,4 +1,4 @@
|
||||||
from django.conf.urls import include, url
|
from django.urls import include, path
|
||||||
from rest_framework import routers
|
from rest_framework import routers
|
||||||
|
|
||||||
from .views import ContestViewSet, BandViewSet, FrequencyViewSet, EntryCategoryViewSet, ReferenceViewSet, QSOViewSet, \
|
from .views import ContestViewSet, BandViewSet, FrequencyViewSet, EntryCategoryViewSet, ReferenceViewSet, QSOViewSet, \
|
||||||
|
@ -15,6 +15,6 @@ router.register('shadowcalls', ShadowCallViewSet)
|
||||||
router.register('profile', UserProfileViewSet, basename='profile')
|
router.register('profile', UserProfileViewSet, basename='profile')
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^', include(router.urls)),
|
path('', include(router.urls)),
|
||||||
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
|
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
# prepare environment
|
# prepare environment
|
||||||
import sys
|
import sys
|
||||||
sys.path.append("..")
|
sys.path.append("..")
|
||||||
|
@ -19,7 +17,6 @@ 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
|
||||||
|
|
||||||
print("{0} QSOs deleted".format(*QSO.objects.all().delete()))
|
print("{0} QSOs deleted".format(*QSO.objects.all().delete()))
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from .models import Frequency, Band, Reference, QSO, User, Contest, ShadowCall, EntryCategory
|
from .models import Frequency, Band, Reference, QSO, User, Contest, ShadowCall, EntryCategory
|
||||||
|
|
||||||
|
|
||||||
class UserAdmin(admin.ModelAdmin):
|
class UserAdmin(admin.ModelAdmin):
|
||||||
list_display = ('username', 'dncall', 'qrv2m', 'qrv70cm', 'extra2m70cm')
|
list_display = ('username', 'dncall', 'qrv2m', 'qrv70cm', 'extra2m70cm')
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(User, UserAdmin)
|
admin.site.register(User, UserAdmin)
|
||||||
admin.site.register(QSO)
|
admin.site.register(QSO)
|
||||||
admin.site.register(Band)
|
admin.site.register(Band)
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
|
import re
|
||||||
|
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from .models import Contest, Band
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
|
|
||||||
|
|
||||||
from .forms import QSOFormWithTime
|
from .forms import QSOFormWithTime
|
||||||
|
from .models import Contest, Band
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
def parseCBR(raw):
|
def parseCBR(raw):
|
||||||
""" Parse a CBR file for the CQTU
|
""" Parse a CBR file for the CQTU
|
||||||
|
@ -20,8 +20,9 @@ def parseCBR(raw):
|
||||||
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}) "
|
||||||
|
r"(?P<call_s>[A-Z0-9/-]+)\s+(?P<rst_s>\d{2,3})\s+(?P<exc_s>[A-Z0-9-]+)\s+"
|
||||||
|
r"(?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 = {
|
||||||
|
@ -59,14 +60,17 @@ def parseCBR(raw):
|
||||||
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)
|
||||||
|
|
||||||
|
@ -79,8 +83,10 @@ def parseCBR(raw):
|
||||||
|
|
||||||
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"]
|
||||||
|
@ -88,6 +94,7 @@ class CBRForm(forms.Form):
|
||||||
|
|
||||||
return parsedData
|
return parsedData
|
||||||
|
|
||||||
|
|
||||||
def checkCBRConsistency(contest, user, info):
|
def checkCBRConsistency(contest, user, info):
|
||||||
errors = []
|
errors = []
|
||||||
qsos = []
|
qsos = []
|
||||||
|
@ -95,11 +102,11 @@ def checkCBRConsistency(contest, user, info):
|
||||||
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,
|
|
||||||
"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,
|
||||||
|
@ -119,6 +126,7 @@ def checkCBRConsistency(contest, user, info):
|
||||||
|
|
||||||
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:
|
||||||
|
@ -149,7 +157,8 @@ def uploadCBR(request):
|
||||||
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:
|
||||||
|
@ -157,4 +166,6 @@ def uploadCBR(request):
|
||||||
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})
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
from .models import Contest
|
||||||
|
|
||||||
|
|
||||||
|
def current_contest(self):
|
||||||
|
return {
|
||||||
|
'current_contest': Contest.get_current_contest(),
|
||||||
|
}
|
|
@ -1,14 +1,14 @@
|
||||||
from django import forms
|
|
||||||
from django.contrib.auth.forms import UserCreationForm
|
|
||||||
from django.utils import timezone
|
|
||||||
|
|
||||||
from crispy_forms.helper import FormHelper
|
from crispy_forms.helper import FormHelper
|
||||||
from crispy_forms.layout import Submit, Layout
|
from crispy_forms.layout import Submit, Layout
|
||||||
|
from django import forms
|
||||||
|
from django.contrib.auth.forms import UserCreationForm
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
from .models import User, Reference, QSO, ShadowCall, EntryCategory, Contest
|
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
|
||||||
|
@ -17,11 +17,15 @@ class CustomUserCreationForm(UserCreationForm):
|
||||||
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):
|
|
||||||
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)
|
|
||||||
|
|
||||||
location = forms.CharField(max_length=128, label='Exact Location', help_text="E.g. MAR bei den Fahrstuehlen, TEL 15. OG", required=False)
|
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)
|
||||||
|
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)
|
||||||
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")
|
||||||
|
|
||||||
|
@ -49,8 +53,10 @@ class UpdateRefForm(forms.Form):
|
||||||
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)
|
||||||
|
|
||||||
|
@ -68,10 +74,11 @@ class UpdateCategoryForm(forms.Form):
|
||||||
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):
|
||||||
|
@ -80,16 +87,15 @@ class QSOForm(forms.ModelForm):
|
||||||
|
|
||||||
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()
|
||||||
|
@ -141,12 +147,14 @@ class QSOForm(forms.ModelForm):
|
||||||
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:
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
# Generated by Django 4.0.1 on 2022-01-22 16:18
|
||||||
|
|
||||||
|
import django.contrib.auth.validators
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('contest', '0020_auto_20190122_2348'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='qso',
|
||||||
|
name='otherNo',
|
||||||
|
field=models.IntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(1000000)], verbose_name='No-R'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='qso',
|
||||||
|
name='ownNo',
|
||||||
|
field=models.IntegerField(validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(1000000)], verbose_name='No'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='user',
|
||||||
|
name='dncall',
|
||||||
|
field=models.CharField(blank=True, default='', help_text='If you have a DN call that you will offer to SWLs please enter it here', max_length=16, verbose_name='DN-Call'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='user',
|
||||||
|
name='extra2m70cm',
|
||||||
|
field=models.BooleanField(default=False, help_text='Will you bring an additional 2m/70cm TRX to lend to other participants?', verbose_name='Additional 2m/70cm TRX'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='user',
|
||||||
|
name='first_name',
|
||||||
|
field=models.CharField(blank=True, max_length=150, verbose_name='first name'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='user',
|
||||||
|
name='last_name',
|
||||||
|
field=models.CharField(blank=True, max_length=150, verbose_name='last name'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='user',
|
||||||
|
name='qrv2m',
|
||||||
|
field=models.BooleanField(default=False, help_text='Will you be QRV on 2m during the contest?', verbose_name='QRV on 2m'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='user',
|
||||||
|
name='qrv70cm',
|
||||||
|
field=models.BooleanField(default=False, help_text='Will you be QRV on 70cm during the contest?', verbose_name='QRV on 70cm'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='user',
|
||||||
|
name='regTime',
|
||||||
|
field=models.DateTimeField(blank=True, default=None, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='user',
|
||||||
|
name='username',
|
||||||
|
field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Generated by Django 4.0.1 on 2022-01-22 17:52
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('contest', '0021_alter_qso_otherno_alter_qso_ownno_alter_user_dncall_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='contest',
|
||||||
|
name='contestNo',
|
||||||
|
field=models.IntegerField(default=1, help_text='Running number of contest (for vanity reasons)', validators=[django.core.validators.MinValueValidator(1)]),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Generated by Django 4.0.1 on 2022-01-22 17:56
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('contest', '0022_contest_contestno'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='contest',
|
||||||
|
name='rulesetLink',
|
||||||
|
field=models.TextField(default='', help_text='URL to the ruleset pdf for this contest'),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,19 +1,21 @@
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
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, MinValueValidator, MaxValueValidator
|
||||||
|
from django.db import models
|
||||||
from django.db.models import Q, signals
|
from django.db.models import Q, signals
|
||||||
|
|
||||||
from .validators import CallUsernameValidator
|
|
||||||
from .signals import checkForShadowCall
|
from .signals import checkForShadowCall
|
||||||
|
from .validators import CallUsernameValidator
|
||||||
|
|
||||||
|
|
||||||
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)
|
contestNo = models.IntegerField(validators=[MinValueValidator(1)],
|
||||||
|
help_text="Running number of contest (for vanity reasons)")
|
||||||
|
rulesetLink = models.TextField(help_text="URL to the ruleset pdf for this contest")
|
||||||
|
callQrg = models.ForeignKey("Frequency", on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
deadline = models.DateTimeField()
|
deadline = models.DateTimeField()
|
||||||
|
|
||||||
qsoStartTime = models.DateTimeField()
|
qsoStartTime = models.DateTimeField()
|
||||||
|
@ -24,7 +26,12 @@ class Contest(models.Model):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_current_contest(cls):
|
def get_current_contest(cls):
|
||||||
return cls.objects.get(id=1)
|
# Currently the contest with the latest deadline is the active one
|
||||||
|
# This definitely has potential for improvement, but it's better than a hardcoded contest
|
||||||
|
contests = cls.objects.order_by("-deadline")
|
||||||
|
if len(contests) > 0:
|
||||||
|
return contests[0]
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class Reference(models.Model):
|
class Reference(models.Model):
|
||||||
|
@ -34,6 +41,7 @@ class Reference(models.Model):
|
||||||
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)
|
||||||
|
@ -41,9 +49,10 @@ class EntryCategory(models.Model):
|
||||||
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)
|
||||||
|
@ -52,6 +61,7 @@ class ShadowCall(models.Model):
|
||||||
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)
|
||||||
|
@ -67,17 +77,18 @@ class User(AbstractUser):
|
||||||
# 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)
|
||||||
|
@ -124,27 +135,32 @@ class User(AbstractUser):
|
||||||
"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, on_delete=models.CASCADE)
|
||||||
|
|
||||||
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, on_delete=models.CASCADE)
|
||||||
|
|
||||||
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
|
MAX_NO_VALUE = 1000000
|
||||||
reportValidator = RegexValidator("[1-5][1-9]")
|
reportValidator = RegexValidator("[1-5][1-9]")
|
||||||
|
@ -154,11 +170,11 @@ class QSO(models.Model):
|
||||||
["owner", "call"],
|
["owner", "call"],
|
||||||
]
|
]
|
||||||
|
|
||||||
owner = models.ForeignKey(User, db_index=True)
|
owner = models.ForeignKey(User, on_delete=models.CASCADE, 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, on_delete=models.SET_NULL, related_name='qsoref', null=True, blank=True, default=None)
|
||||||
band = models.ForeignKey(Band)
|
band = models.ForeignKey(Band, on_delete=models.CASCADE)
|
||||||
|
|
||||||
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])
|
||||||
|
@ -168,13 +184,13 @@ class QSO(models.Model):
|
||||||
validators=[MinValueValidator(1), MaxValueValidator(MAX_NO_VALUE)])
|
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, on_delete=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", on_delete=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! """
|
||||||
|
@ -213,10 +229,10 @@ class QSO(models.Model):
|
||||||
# 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:
|
||||||
|
@ -228,7 +244,8 @@ class QSO(models.Model):
|
||||||
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,
|
||||||
|
@ -250,4 +267,6 @@ class QSO(models.Model):
|
||||||
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,35 +1,22 @@
|
||||||
"""cqtu URL Configuration
|
from django.urls import re_path
|
||||||
|
|
||||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
|
||||||
https://docs.djangoproject.com/en/1.10/topics/http/urls/
|
|
||||||
Examples:
|
|
||||||
Function views
|
|
||||||
1. Add an import: from my_app import views
|
|
||||||
2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
|
|
||||||
Class-based views
|
|
||||||
1. Add an import: from other_app.views import Home
|
|
||||||
2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
|
|
||||||
Including another URLconf
|
|
||||||
1. Import the include() function: from django.conf.urls import url, include
|
|
||||||
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
|
|
||||||
"""
|
|
||||||
from django.conf.urls import url
|
|
||||||
|
|
||||||
|
|
||||||
import contest.views as contest_views
|
import contest.views as contest_views
|
||||||
from contest.cbrparser import uploadCBR
|
from contest.cbrparser import uploadCBR
|
||||||
|
|
||||||
|
app_name = 'context'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^$', contest_views.contestIndex, name='index'),
|
re_path(r'^$', contest_views.contestIndex, name='index'),
|
||||||
url(r'^regref/$', contest_views.registerRefs, name='registerRefs'),
|
re_path(r'^regref/$', contest_views.registerRefs, name='registerRefs'),
|
||||||
url(r'^regref/edit/(?P<uid>\d+)/$', contest_views.updateRef, {"shadow": False}, name='updateRef'),
|
re_path(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'),
|
re_path(r'^regref/shadow/edit/(?P<uid>\d+)/$', contest_views.updateRef, {"shadow": True}, name='updateShadowRef'),
|
||||||
url(r'^regref/qsos/all/$', contest_views.viewAllQSOs, name='viewAllQSOs'),
|
re_path(r'^regref/qsos/all/$', contest_views.viewAllQSOs, name='viewAllQSOs'),
|
||||||
url(r'^regref/qsos/user/(?P<uid>\d+)/$', contest_views.viewUserQSOs, name='viewUserQSOs'),
|
re_path(r'^regref/qsos/user/(?P<uid>\d+)/$', contest_views.viewUserQSOs, name='viewUserQSOs'),
|
||||||
url(r'^overview/$', contest_views.overview, name='overview'),
|
re_path(r'^overview/$', contest_views.overview, name='overview'),
|
||||||
url(r'^log/$', contest_views.log, name='log'),
|
re_path(r'^log/$', contest_views.log, name='log'),
|
||||||
url(r'^log/edit/(?P<qsoid>\d+)/$', contest_views.logEdit, name='logEdit'),
|
re_path(r'^log/edit/(?P<qsoid>\d+)/$', contest_views.logEdit, name='logEdit'),
|
||||||
url(r'^log/delete/(?P<qsoid>\d+)/$', contest_views.logDelete, name='logDelete'),
|
re_path(r'^log/delete/(?P<qsoid>\d+)/$', contest_views.logDelete, name='logDelete'),
|
||||||
url(r'^uploadcbr/$', uploadCBR, name='uploadCBR'),
|
re_path(r'^uploadcbr/$', uploadCBR, name='uploadCBR'),
|
||||||
url(r'^regref/recheckqsos/$', contest_views.recheckAllQSOs, name='recheckAllQSOs'),
|
re_path(r'^regref/recheckqsos/$', contest_views.recheckAllQSOs, name='recheckAllQSOs'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,27 +1,25 @@
|
||||||
from django.core import validators
|
from django.core import validators
|
||||||
from django.utils import six
|
|
||||||
from django.utils.deconstruct import deconstructible
|
from django.utils.deconstruct import deconstructible
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
@deconstructible
|
@deconstructible
|
||||||
class CallUsernameValidator(validators.RegexValidator):
|
class CallUsernameValidator(validators.RegexValidator):
|
||||||
#regex = r'^[\w.@+-]+$'
|
|
||||||
regex = r'^(?:[A-Z]+/)?[A-Z]{1,2}[0-9][A-Z]{1,4}(?:-[0-9])??$'
|
regex = r'^(?:[A-Z]+/)?[A-Z]{1,2}[0-9][A-Z]{1,4}(?:-[0-9])??$'
|
||||||
message = _(
|
message = _(
|
||||||
'Enter a valid Callsign as Username, ALL UPPERCASE, if needed with -1 / -2,'
|
'Enter a valid Callsign as Username, ALL UPPERCASE, if needed with -1 / -2,'
|
||||||
'e.g. DL7BST, DN1BER-1, DL/OE1FOO.'
|
'e.g. DL7BST, DN1BER-1, DL/OE1FOO.'
|
||||||
)
|
)
|
||||||
flags = re.ASCII if six.PY3 else 0
|
flags = re.ASCII
|
||||||
|
|
||||||
|
|
||||||
@deconstructible
|
@deconstructible
|
||||||
class CallLogValidator(validators.RegexValidator):
|
class CallLogValidator(validators.RegexValidator):
|
||||||
#regex = r'^[\w.@+-]+$'
|
|
||||||
regex = r'^(?:[A-Z]+/)?[A-Z]{1,2}[0-9][A-Z]{1,4}(?:-[0-9])?(?:/[A-Z]{1,3})?$'
|
regex = r'^(?:[A-Z]+/)?[A-Z]{1,2}[0-9][A-Z]{1,4}(?:-[0-9])?(?:/[A-Z]{1,3})?$'
|
||||||
message = _(
|
message = _(
|
||||||
'Enter a valid callsign, ALL UPPERCASE, if needed with -1 / -2,'
|
'Enter a valid callsign, ALL UPPERCASE, if needed with -1 / -2,'
|
||||||
'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
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
from django.shortcuts import render, get_object_or_404
|
import datetime
|
||||||
|
|
||||||
|
from django.shortcuts import render, get_object_or_404
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib.admin.views.decorators import staff_member_required
|
from django.contrib.admin.views.decorators import staff_member_required
|
||||||
#from django.db.models import Q
|
|
||||||
from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm
|
from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
@ -11,19 +11,18 @@ from django.contrib.auth import login as auth_login
|
||||||
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
from .models import User, Contest, Frequency, Reference, QSO, ShadowCall
|
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)
|
||||||
|
@ -32,6 +31,7 @@ def contestIndex(request):
|
||||||
|
|
||||||
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:
|
||||||
|
@ -68,9 +68,9 @@ def log(request):
|
||||||
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:
|
||||||
|
@ -91,6 +91,7 @@ def logEdit(request, qsoid):
|
||||||
|
|
||||||
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"))
|
||||||
|
@ -109,7 +110,6 @@ def logDelete(request, qsoid):
|
||||||
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()
|
||||||
|
@ -127,7 +127,9 @@ def registerRefs(request):
|
||||||
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:
|
||||||
|
@ -153,6 +155,7 @@ def recheckAllQSOs(request):
|
||||||
|
|
||||||
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)
|
||||||
|
@ -162,7 +165,9 @@ def viewUserQSOs(request, uid, page=1):
|
||||||
|
|
||||||
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):
|
||||||
|
@ -208,6 +213,7 @@ def updateRef(request, shadow, uid):
|
||||||
|
|
||||||
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")
|
||||||
|
@ -216,12 +222,14 @@ def viewAllQSOs(request, page=1):
|
||||||
|
|
||||||
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':
|
||||||
|
@ -237,6 +245,7 @@ def register(request):
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -40,6 +40,7 @@ INSTALLED_APPS = [
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
|
'django.contrib.humanize',
|
||||||
'crispy_forms',
|
'crispy_forms',
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
'django_filters',
|
'django_filters',
|
||||||
|
@ -72,6 +73,7 @@ TEMPLATES = [
|
||||||
'django.template.context_processors.request',
|
'django.template.context_processors.request',
|
||||||
'django.contrib.auth.context_processors.auth',
|
'django.contrib.auth.context_processors.auth',
|
||||||
'django.contrib.messages.context_processors.messages',
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
'contest.context_processors.current_contest',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -107,12 +109,6 @@ AUTH_PASSWORD_VALIDATORS = [
|
||||||
'min_length': 4,
|
'min_length': 4,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
#{
|
|
||||||
# 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
|
||||||
#},
|
|
||||||
#{
|
|
||||||
# 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
|
||||||
#},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
43
cqtu/urls.py
43
cqtu/urls.py
|
@ -1,43 +1,18 @@
|
||||||
"""cqtu URL Configuration
|
from django.urls import include, path
|
||||||
|
|
||||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
|
||||||
https://docs.djangoproject.com/en/1.10/topics/http/urls/
|
|
||||||
Examples:
|
|
||||||
Function views
|
|
||||||
1. Add an import: from my_app import views
|
|
||||||
2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
|
|
||||||
Class-based views
|
|
||||||
1. Add an import: from other_app.views import Home
|
|
||||||
2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
|
|
||||||
Including another URLconf
|
|
||||||
1. Import the include() function: from django.conf.urls import url, include
|
|
||||||
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
|
|
||||||
"""
|
|
||||||
from django.conf.urls import url, include
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from django.contrib.auth import views as auth_views
|
from django.contrib.auth import views as auth_views
|
||||||
#from django.views.generic.edit import CreateView
|
|
||||||
#from django.contrib.auth.forms import UserCreationForm
|
|
||||||
|
|
||||||
#from contest.forms import CustomUserCreationForm
|
|
||||||
from contest.views import index, register, profile
|
from contest.views import index, register, profile
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url('^$', index, name="index"),
|
path('', index, name="index"),
|
||||||
url('^cqtufm2019/', include('contest.urls', namespace='contest')),
|
path('contest/', include('contest.urls', namespace='contest')),
|
||||||
|
|
||||||
url(r'^admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
url(r'^login/$', auth_views.login, name='login'),
|
path('login/', auth_views.LoginView.as_view(), name='login'),
|
||||||
url(r'^logout/$', auth_views.logout, {'next_page': '/'}, name='logout'),
|
path('logout/', auth_views.LogoutView.as_view(), {'next_page': '/'}, name='logout'),
|
||||||
url(r'^register/$', register, name='register'),
|
path('register/', register, name='register'),
|
||||||
url(r'^profile/$', profile, name='profile'),
|
path('profile/', profile, name='profile'),
|
||||||
url(r'^api/', include('api.urls')),
|
path('api/', include('api.urls')),
|
||||||
#url(r'^register/$', CreateView.as_view(
|
|
||||||
# template_name='registration/register.html',
|
|
||||||
# form_class=CustomUserCreationForm,
|
|
||||||
# success_url='/',
|
|
||||||
#), name='register'),
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
Django<1.12
|
Django==4.0.1
|
||||||
django-crispy-forms
|
django-crispy-forms
|
||||||
django-rest-framework
|
django-rest-framework
|
||||||
django-filter
|
django-filter
|
||||||
|
|
|
@ -8,9 +8,9 @@
|
||||||
<meta name="description" content="">
|
<meta name="description" content="">
|
||||||
<meta name="author" content="">
|
<meta name="author" content="">
|
||||||
<link rel="icon" href="/favicon.ico">
|
<link rel="icon" href="/favicon.ico">
|
||||||
{% load staticfiles %}
|
{% load static %}
|
||||||
|
|
||||||
<title>CQTUFM2019 - CQ TU FM Contest 2019</title>
|
<title>{{ current_contest.name }}</title>
|
||||||
|
|
||||||
<!-- Bootstrap core CSS -->
|
<!-- Bootstrap core CSS -->
|
||||||
<link href="{% static "css/bootstrap.min.css" %}" rel="stylesheet">
|
<link href="{% static "css/bootstrap.min.css" %}" rel="stylesheet">
|
||||||
|
@ -35,7 +35,7 @@
|
||||||
<span class="icon-bar"></span>
|
<span class="icon-bar"></span>
|
||||||
<span class="icon-bar"></span>
|
<span class="icon-bar"></span>
|
||||||
</button>
|
</button>
|
||||||
<a class="navbar-brand" href="{% url "index" %}">CQ TU FM 2019</a>
|
<a class="navbar-brand" href="{% url "index" %}">{{ current_contest.name | default:"NO CONTEST" }}</a>
|
||||||
</div>
|
</div>
|
||||||
<div id="navbar" class="navbar-collapse collapse">
|
<div id="navbar" class="navbar-collapse collapse">
|
||||||
<ul class="nav navbar-nav">
|
<ul class="nav navbar-nav">
|
||||||
|
@ -88,7 +88,7 @@
|
||||||
|
|
||||||
<footer class="footer">
|
<footer class="footer">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<p class="text-muted">CQ TU FM 2019, a <a href="http://dk0tu.de/">DK0TU</a> product</p>
|
<p class="text-muted">{{ current_contest.name }} powered by <a href="https://git.dk0tu.de/cqtu/cqtu-webif/">CQTU Contest Webinterface</a> - a <a href="http://dk0tu.de/">DK0TU</a> product</p>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<p>
|
<p>
|
||||||
Welcome to {{ contest.name }}!
|
Welcome to {{ contest.name }}!
|
||||||
<p/>
|
<p/>
|
||||||
<p>Here you can find a short overview over the rules, but note that this is not a replacement for the <a href="http://dk0tu.de/contests/cqtu/CQTU_2017-02-02_FM_Rules+Log.300dpi.pdf">complete ruleset</a>.</p>
|
<p>Here you can find a short overview over the rules, but note that this is not a replacement for the <a href="{{ current_contest.rulesetLink }}">complete ruleset</a>.</p>
|
||||||
<p>
|
<p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Change frequency after a successful CQ-call! If you answered, feel free to call CQ on this frequency.</li>
|
<li>Change frequency after a successful CQ-call! If you answered, feel free to call CQ on this frequency.</li>
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% load crispy_forms_tags %}
|
{% load crispy_forms_tags %}
|
||||||
|
{% load humanize %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<p class="lead">Hello and welcome to the 4th DK0TU CQ TU contest, the CQ TU 2019!</p>
|
{% if current_contest %}
|
||||||
|
<p class="lead">Hello and welcome to the {{ current_contest.contestNo | ordinal }} DK0TU CQ TU contest, the {{ current_contest.name }}!</p>
|
||||||
|
{% else %}
|
||||||
|
<p class="lead">ERROR: No contest set in admin area! If you're an admin <a href="/admin/">go create one</a>.</p>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue