Compare commits
18 Commits
fcd22f8231
...
7ae53b6150
Author | SHA1 | Date |
---|---|---|
Sebastian Lohff | 7ae53b6150 | 2 years ago |
Sebastian Lohff | 13afffda02 | 2 years ago |
Sebastian Lohff | 1f39cc5b40 | 2 years ago |
Sebastian Lohff | 933a337eac | 2 years ago |
Sebastian Lohff | 635289ed8f | 2 years ago |
Sebastian Lohff | 970a3ae517 | 2 years ago |
Sebastian Lohff | e5465632b4 | 2 years ago |
Sebastian Lohff | 613cf9c099 | 2 years ago |
Sebastian Lohff | fc78b91c51 | 2 years ago |
Sebastian Lohff | 3c6936ba44 | 2 years ago |
Sebastian Lohff | a7f303e651 | 2 years ago |
Sebastian Lohff | 0c8ba5eb87 | 2 years ago |
Sebastian Lohff | b528d4dbb4 | 2 years ago |
Sebastian Lohff | b0d7f9f2ec | 2 years ago |
Sebastian Lohff | c41a30c66b | 2 years ago |
Sebastian Lohff | a00ae715ac | 2 years ago |
Sebastian Lohff | ba9c99e0ce | 2 years ago |
Sebastian Lohff | c9cff07432 | 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.urls import include, path
|
||||
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 = [
|
||||
path('', include(router.urls)),
|
||||
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
|
||||
]
|
@ -0,0 +1,91 @@
|
||||
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']
|
@ -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'),
|
||||
),
|
||||
]
|
@ -1,35 +1,22 @@
|
||||
"""cqtu URL Configuration
|
||||
|
||||
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
|
||||
from django.urls import re_path
|
||||
|
||||
|
||||
import contest.views as contest_views
|
||||
from contest.cbrparser import uploadCBR
|
||||
|
||||
app_name = 'context'
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', contest_views.contestIndex, name='index'),
|
||||
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/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/user/(?P<uid>\d+)/$', contest_views.viewUserQSOs, name='viewUserQSOs'),
|
||||
url(r'^overview/$', contest_views.overview, name='overview'),
|
||||
url(r'^log/$', contest_views.log, name='log'),
|
||||
url(r'^log/edit/(?P<qsoid>\d+)/$', contest_views.logEdit, name='logEdit'),
|
||||
url(r'^log/delete/(?P<qsoid>\d+)/$', contest_views.logDelete, name='logDelete'),
|
||||
url(r'^uploadcbr/$', uploadCBR, name='uploadCBR'),
|
||||
url(r'^regref/recheckqsos/$', contest_views.recheckAllQSOs, name='recheckAllQSOs'),
|
||||
re_path(r'^$', contest_views.contestIndex, name='index'),
|
||||
re_path(r'^regref/$', contest_views.registerRefs, name='registerRefs'),
|
||||
re_path(r'^regref/edit/(?P<uid>\d+)/$', contest_views.updateRef, {"shadow": False}, name='updateRef'),
|
||||
re_path(r'^regref/shadow/edit/(?P<uid>\d+)/$', contest_views.updateRef, {"shadow": True}, name='updateShadowRef'),
|
||||
re_path(r'^regref/qsos/all/$', contest_views.viewAllQSOs, name='viewAllQSOs'),
|
||||
re_path(r'^regref/qsos/user/(?P<uid>\d+)/$', contest_views.viewUserQSOs, name='viewUserQSOs'),
|
||||
re_path(r'^overview/$', contest_views.overview, name='overview'),
|
||||
re_path(r'^log/$', contest_views.log, name='log'),
|
||||
re_path(r'^log/edit/(?P<qsoid>\d+)/$', contest_views.logEdit, name='logEdit'),
|
||||
re_path(r'^log/delete/(?P<qsoid>\d+)/$', contest_views.logDelete, name='logDelete'),
|
||||
re_path(r'^uploadcbr/$', uploadCBR, name='uploadCBR'),
|
||||
re_path(r'^regref/recheckqsos/$', contest_views.recheckAllQSOs, name='recheckAllQSOs'),
|
||||
]
|
||||
|
@ -1,28 +1,25 @@
|
||||
from django.core import validators
|
||||
from django.utils import six
|
||||
from django.utils.deconstruct import deconstructible
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
import re
|
||||
|
||||
|
||||
@deconstructible
|
||||
class CallUsernameValidator(validators.RegexValidator):
|
||||
#regex = r'^[\w.@+-]+$'
|
||||
regex = r'^(?:[A-Z]+/)?[A-Z]{1,2}[0-9][A-Z]{1,4}(?:-[0-9])??$'
|
||||
message = _(
|
||||
'Enter a valid Callsign as Username, ALL UPPERCASE, if needed with -1 / -2,'
|
||||
'e.g. DL7BST, DN1BER-1, DL/OE1FOO.'
|
||||
)
|
||||
flags = re.ASCII if six.PY3 else 0
|
||||
flags = re.ASCII
|
||||
|
||||
|
||||
@deconstructible
|
||||
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})?$'
|
||||
message = _(
|
||||
'Enter a valid callsign, ALL UPPERCASE, if needed with -1 / -2,'
|
||||
'e.g. DL7BST, DN1BER-1, DL/OE1FOO, DN1FTW-1/p'
|
||||
)
|
||||
flags = re.ASCII if six.PY3 else 0
|
||||
|
||||
flags = re.ASCII
|
||||
|
@ -1,42 +1,18 @@
|
||||
"""cqtu URL Configuration
|
||||
|
||||
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.urls import include, path
|
||||
from django.contrib import admin
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url('^$', index, name="index"),
|
||||
url('^cqtufm2019/', include('contest.urls', namespace='contest')),
|
||||
path('', index, name="index"),
|
||||
path('cqtufm2019/', include('contest.urls', namespace='contest')),
|
||||
|
||||
url(r'^admin/', admin.site.urls),
|
||||
url(r'^login/$', auth_views.login, name='login'),
|
||||
url(r'^logout/$', auth_views.logout, {'next_page': '/'}, name='logout'),
|
||||
url(r'^register/$', register, name='register'),
|
||||
url(r'^profile/$', profile, name='profile'),
|
||||
#url(r'^register/$', CreateView.as_view(
|
||||
# template_name='registration/register.html',
|
||||
# form_class=CustomUserCreationForm,
|
||||
# success_url='/',
|
||||
#), name='register'),
|
||||
path('admin/', admin.site.urls),
|
||||
path('login/', auth_views.LoginView.as_view(), name='login'),
|
||||
path('logout/', auth_views.LogoutView.as_view(), {'next_page': '/'}, name='logout'),
|
||||
path('register/', register, name='register'),
|
||||
path('profile/', profile, name='profile'),
|
||||
path('api/', include('api.urls')),
|
||||
]
|
||||
|
@ -1,2 +1,4 @@
|
||||
Django==1.10
|
||||
Django==4.0.1
|
||||
django-crispy-forms
|
||||
django-rest-framework
|
||||
django-filter
|
||||
|
Loading…
Reference in new issue