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,11 +11,7 @@ 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")
|
||||||
|
|
|
@ -93,6 +93,8 @@ class QSOForm(forms.ModelForm):
|
||||||
|
|
||||||
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)
|
||||||
|
|
|
@ -4,7 +4,7 @@ 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
|
||||||
|
@ -22,11 +22,6 @@ class Contest(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_current_contest(cls):
|
|
||||||
return cls.objects.get(id=1)
|
|
||||||
|
|
||||||
|
|
||||||
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()
|
||||||
|
@ -58,7 +53,7 @@ class User(AbstractUser):
|
||||||
|
|
||||||
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
|
||||||
|
@ -146,7 +141,6 @@ class Frequency(models.Model):
|
||||||
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:
|
||||||
|
@ -163,9 +157,8 @@ class QSO(models.Model):
|
||||||
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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -40,13 +40,11 @@ INSTALLED_APPS = [
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
|
|
||||||
'crispy_forms',
|
'crispy_forms',
|
||||||
'rest_framework',
|
|
||||||
'django_filters',
|
|
||||||
|
|
||||||
# local
|
# local
|
||||||
'contest',
|
'contest',
|
||||||
'api',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
@ -145,6 +143,3 @@ MESSAGE_TAGS = {
|
||||||
messages.ERROR: 'danger',
|
messages.ERROR: 'danger',
|
||||||
}
|
}
|
||||||
|
|
||||||
REST_FRAMEWORK = {
|
|
||||||
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend']
|
|
||||||
}
|
|
||||||
|
|
|
@ -34,7 +34,6 @@ urlpatterns = [
|
||||||
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,
|
||||||
|
|
|
@ -1,4 +1,2 @@
|
||||||
Django<1.12
|
Django==1.10
|
||||||
django-crispy-forms
|
django-crispy-forms
|
||||||
django-rest-framework
|
|
||||||
django-filter
|
|
||||||
|
|
|
@ -10,10 +10,7 @@
|
||||||
<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>
|
|
||||||
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>
|
</p>
|
||||||
<form method="POST" action="{% url 'register' %}">
|
<form method="POST" action="{% url 'register' %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
Loading…
Reference in New Issue