Compare commits

...

8 Commits

Author SHA1 Message Date
Sebastian Lohff c43c5c1e10 Move QSO no validation into model
4 years ago
Sebastian Lohff e049e45698 Remove broken check from QSO form
4 years ago
Sebastian Lohff e825685105 Add REST API to webinterface
4 years ago
Sebastian Lohff ddb09d148e Add REST API to webinterface
4 years ago
Sebastian Lohff 782f5cbe32 Allow regTime in user to be blank as well
4 years ago
Sebastian Lohff 99ab46272b Clarify choice of username at registration, again
4 years ago
Sebastian Lohff 92007bab02 clear_contest: make python3 ready, remove unused import
4 years ago
Sebastian Lohff 9efdeda3c6 Bump django version to 1.11
4 years ago

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

@ -0,0 +1,5 @@
from django.apps import AppConfig
class ApiConfig(AppConfig):
name = 'api'

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

@ -0,0 +1,98 @@
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')

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

@ -0,0 +1,20 @@
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')),
]

@ -0,0 +1,84 @@
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import viewsets, generics, filters
from rest_framework.permissions import IsAuthenticated, IsAdminUser
from rest_framework.response import Response
from .serializers import ContestSerializer, BandSerializer, FrequencySerializer, EntryCategorySerializer, \
ReferenceSerializer, QSOSerializer, ShadowCallSerializer, UserSerializer
from contest.models import Contest, Band, Frequency, EntryCategory, Reference, QSO, ShadowCall
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(generics.UpdateAPIView, viewsets.GenericViewSet):
permission_classes = [IsAuthenticated]
serializer_class = UserSerializer
def list(self, request, format=None):
user = request.user
serializer = UserSerializer(user)
return Response(serializer.data)
def get_queryset(self):
return self.request.user
class ShadowCallViewSet(viewsets.ReadOnlyModelViewSet):
permission_classes = [IsAdminUser]
queryset = ShadowCall.objects.all()
serializer_class = ShadowCallSerializer
filterset_fields = ['username', 'ref']

@ -1,8 +1,6 @@
#!/usr/bin/env python
from __future__ import print_function
import datetime
# prepare environment
import sys
sys.path.append("..")
@ -11,11 +9,15 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cqtu.settings")
import django
django.setup()
confirm = raw_input("Do are you sure you want to clear all contest data? Answer with uppercase YES: ")
confirm_msg = "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":
print("Aborting")
sys.exit(1)
print("Aborting")
sys.exit(1)
from contest.models import QSO, ShadowCall, Reference, User

@ -93,8 +93,6 @@ class QSOForm(forms.ModelForm):
def clean_call(self):
data = self.cleaned_data["call"].upper().strip()
if Reference.objects.filter(name=data).count() > 0:
raise forms.ValidationError("Reference already exists")
try:
CallLogValidator()(data)

@ -4,7 +4,7 @@ import datetime
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.core.validators import RegexValidator
from django.core.validators import RegexValidator, MinValueValidator, MaxValueValidator
from django.db.models import Q, signals
from .validators import CallUsernameValidator
@ -22,6 +22,11 @@ class Contest(models.Model):
def __str__(self):
return self.name
@classmethod
def get_current_contest(cls):
return cls.objects.get(id=1)
class Reference(models.Model):
name = models.CharField(max_length=20, unique=True, db_index=True)
description = models.TextField()
@ -53,7 +58,7 @@ class User(AbstractUser):
location = 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, blank=True)
# because of cbr parsing bug, we sometimes have users who only have 70cm qsos
# we ignore the band for them when checking QSOs
@ -141,6 +146,7 @@ class Frequency(models.Model):
return "Channel %s: %s MHz" % (self.channel, self.qrg)
class QSO(models.Model):
MAX_NO_VALUE = 1000000
reportValidator = RegexValidator("[1-5][1-9]")
class Meta:
@ -157,8 +163,9 @@ class QSO(models.Model):
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])
ownNo = models.IntegerField(verbose_name='No')
otherNo = models.IntegerField(verbose_name='No-R', null=True, blank=True)
ownNo = models.IntegerField(verbose_name='No', validators=[MinValueValidator(1), MaxValueValidator(MAX_NO_VALUE)])
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")
ref = models.ForeignKey(Reference, models.SET_NULL, null=True, blank=True)

@ -25,4 +25,3 @@ class CallLogValidator(validators.RegexValidator):
'e.g. DL7BST, DN1BER-1, DL/OE1FOO, DN1FTW-1/p'
)
flags = re.ASCII if six.PY3 else 0

@ -41,9 +41,12 @@ INSTALLED_APPS = [
'django.contrib.messages',
'django.contrib.staticfiles',
'crispy_forms',
'rest_framework',
'django_filters',
# local
'contest',
'api',
]
MIDDLEWARE = [
@ -141,3 +144,7 @@ CRISPY_TEMPLATE_PACK = 'bootstrap3'
MESSAGE_TAGS = {
messages.ERROR: 'danger',
}
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend']
}

@ -34,6 +34,7 @@ urlpatterns = [
url(r'^logout/$', auth_views.logout, {'next_page': '/'}, name='logout'),
url(r'^register/$', register, name='register'),
url(r'^profile/$', profile, name='profile'),
url(r'^api/', include('api.urls')),
#url(r'^register/$', CreateView.as_view(
# template_name='registration/register.html',
# form_class=CustomUserCreationForm,

@ -1,2 +1,4 @@
Django==1.10
Django<1.12
django-crispy-forms
django-rest-framework
django-filter

@ -10,8 +10,11 @@
<div class="panel-body">
<p>
Please register with your (uppercase) Callsign as Usernames.
For DN-Calls, -[0-9] is allowed.
For DN-Calls, -[0-9] is allowed (e.g. DN1ABC-2 for the second group).
</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' %}">
{% csrf_token %}
{{ form|crispy }}

Loading…
Cancel
Save