diff --git a/contest/admin.py b/contest/admin.py index 8c6708a..b821db4 100644 --- a/contest/admin.py +++ b/contest/admin.py @@ -1,8 +1,9 @@ from django.contrib import admin -from .models import Frequency, Band, Reference, QSO, User +from .models import Frequency, Band, Reference, QSO, User, Contest admin.site.register(User) admin.site.register(QSO) admin.site.register(Band) admin.site.register(Frequency) admin.site.register(Reference) +admin.site.register(Contest) diff --git a/contest/models.py b/contest/models.py index 86627fa..657389b 100644 --- a/contest/models.py +++ b/contest/models.py @@ -3,25 +3,33 @@ from __future__ import unicode_literals from django.db import models from django.contrib.auth.models import AbstractUser +from .validators import CallUsernameValidator + +class Contest(models.Model): + name = models.CharField(max_length=20) + shortName = models.CharField(max_length=20, unique=True) + callQrg = models.ForeignKey("Frequency", null=True) + + def __str__(self): + return self.name + class Reference(models.Model): name = models.CharField(max_length=20, unique=True) description = models.TextField() + def __str__(self): + return self.name + class User(AbstractUser): ref = models.ForeignKey(Reference, null=True, blank=True) -class QSO(models.Model): - owner = models.ForeignKey(User) - time = models.DateTimeField() - call = models.CharField(max_length=20) - - reportTX = models.CharField(max_length=7) - reportRX = models.CharField(max_length=7) - - remarks = models.TextField() + def __init__(self, *args, **kwargs): + super(User, self).__init__(*args, **kwargs) + self._meta.get_field("username").validators = [CallUsernameValidator()] class Band(models.Model): name = models.CharField(max_length=10) + contest = models.ForeignKey(Contest) def __str__(self): return self.name @@ -33,6 +41,24 @@ class Frequency(models.Model): qrg = models.DecimalField(max_digits=7, decimal_places=3) band = models.ForeignKey(Band) + note = models.CharField(max_length=50) + def __str__(self): return "Channel %s: %s MHz" % (self.channel, self.qrg) +class QSO(models.Model): + owner = models.ForeignKey(User) + time = models.DateTimeField(blank=True) + call = models.CharField(max_length=20) + band = models.ForeignKey(Band) + + reportTX = models.CharField(max_length=7, default=59) + reportRX = models.CharField(max_length=7, default=59) + + ownNo = models.IntegerField() + otherNo = models.IntegerField() + + refStr = models.CharField(max_length=20) + ref = models.ForeignKey(Reference, null=True, blank=True) + + remarks = models.CharField(max_length=50, blank=True) diff --git a/contest/urls.py b/contest/urls.py index 4d3e25b..e587faf 100644 --- a/contest/urls.py +++ b/contest/urls.py @@ -16,9 +16,14 @@ Including another URLconf from django.conf.urls import url -from contest.views import index, registerRefs +import contest.views as contest_views urlpatterns = [ - url('^$', index), - url('^regref/', registerRefs, name='registerRefs'), + url(r'^$', contest_views.contestIndex, name='index'), + url(r'^regref/$', contest_views.registerRefs, name='registerRefs'), + url(r'^regref/edit/(?P\d+)/$', contest_views.updateRef, name='updateRef'), + url(r'^overview/$', contest_views.overview, name='overview'), + url(r'^log/$', contest_views.log, name='log'), + url(r'^log/edit/(?P\d+)/$', contest_views.logEdit, name='logEdit'), + url(r'^log/delete/(?P\d+)/$', contest_views.logDelete, name='logDelete'), ] diff --git a/contest/views.py b/contest/views.py index a9e3ce1..6904213 100644 --- a/contest/views.py +++ b/contest/views.py @@ -1,14 +1,141 @@ from django.shortcuts import render -from django.contrib.admin.views.decorators import staff_member_required, login_required +from django.contrib.auth.decorators import login_required +from django.contrib.admin.views.decorators import staff_member_required +from django.db.models import Q +from django.contrib.auth.forms import AuthenticationForm +from django.http import HttpResponseRedirect +from django.contrib import messages +from django.urls import reverse -from .models import User +import datetime + +from .models import User, Contest, Frequency, Reference, QSO +from .forms import UpdateRefForm, QSOForm -@login_required def index(request): - return render(request, 'index.html', {}) + if request.user.is_authenticated(): + return HttpResponseRedirect(reverse("contest:index")) + + return render(request, "index.html", {"loginForm": AuthenticationForm()}) + +@login_required +def contestIndex(request): + #messages.debug(request, "Debug GLITCHHHHH") + #messages.info(request, "This info is very educational") + #messages.warning(request, "You got a warning") + #messages.error(request, "Error!!!") + #messages.success(request, "Great Success") + qsoform = QSOForm(request.user) + + return render(request, 'contest/index.html', {"qsoform": qsoform}) + +@login_required +def log(request): + form = None + + qsos = QSO.objects.filter(owner=request.user).order_by("-ownNo") + + if request.method == 'POST': + form = QSOForm(user=request.user, data=request.POST) + if form.is_valid(): + l = form.instance + if not l.time: + # set current time + l.time = datetime.datetime.now() + + l.owner = request.user + l.save() + + messages.success(request, "QSO saved!") + + return HttpResponseRedirect(reverse("contest:log")) + else: + # FIXME: data initial my qso number + data = { + "ownNo": qsos[0].ownNo + 1 if len(qsos) > 0 else 1, + "reportRX": "59", + "reportTX": "59", + } + form = QSOForm(request.user, initial=data) + + + return render(request, 'contest/log.html', {'form': form, 'qsos': qsos}) + +@login_required +def logEdit(request, qsoid): + qso = QSO.objects.get(id=qsoid, owner=request.user) + form = None + + if request.method == 'POST': + form = QSOForm(user=request.user, instance=qso, data=request.POST) + if form.is_valid(): + form.instance.save() + + messages.info(request, "QSO has been edited") + return HttpResponseRedirect(reverse("contest:log")) + else: + form = QSOForm(user=request.user, instance=qso) + + return render(request, 'contest/logEdit.html', {'form': form, "qso": qso}) + +def logDelete(request, qsoid): + qso = QSO.objects.get(id=qsoid, owner=request.user) + + if request.method == 'POST': + if "delete" in request.POST: + if request.POST["delete"] == "yes": + qso.delete() + messages.info(request, "QSO has been deleted") + return HttpResponseRedirect(reverse("contest:log")) + elif request.POST["delete"] == "no": + return HttpResponseRedirect(reverse("contest:log")) + + return render(request, 'contest/logDelete.html', {"qso": qso}) + + @staff_member_required def registerRefs(request): + allUser = User.objects.all() + refsMissingUser = User.objects.filter(ref=None).order_by("username") + refsNotMissingUser = User.objects.filter(~Q(ref=None)).order_by("username") + + qsos = QSO.objects.all().order_by("-time") + + return render(request, 'contest/registerRefs.html', {'alluser': allUser, 'refsMissingUser': refsMissingUser, "refsNotMissinguser": refsNotMissingUser, "qsos": qsos}) + +@staff_member_required +def updateRef(request, uid): + user = User.objects.get(id=uid) + form = None + + if request.method == 'POST': + form = UpdateRefForm(data=request.POST) + if form.is_valid(): + ref = None + if form.cleaned_data["existingRef"]: + print("Got an existing Ref") + ref = form.cleaned_data["existingRef"] + else: + ref = Reference(name=form.cleaned_data["newRefName"]) + ref.save() + messages.info(request, "New Ref '%s' created" % ref) + + user.ref = ref + user.save() + messages.success(request, "%s ref set to %s" % (user, ref)) + return HttpResponseRedirect(reverse("contest:registerRefs")) + else: + form = UpdateRefForm() + + return render(request, 'contest/updateRef.html', {'user': user, 'form': form}) + +def overview(request): + # FIXME: Hardcoded for cqtu... everywhere + c = Contest.objects.get(id=1) + qrgs = Frequency.objects.filter(band__contest=c).order_by("channel") + return render(request, 'contest/overview.html', {'contest': c, 'qrgs': qrgs}) - return render(request, 'register_refs.html', {'alluser': User.objects.all()}) +def register(request): + return render(request, 'registration/register.html', {}) diff --git a/cqtu/settings.py b/cqtu/settings.py index 8a36877..184db36 100644 --- a/cqtu/settings.py +++ b/cqtu/settings.py @@ -11,6 +11,8 @@ https://docs.djangoproject.com/en/1.10/ref/settings/ """ import os +from django.contrib.messages import constants as messages + # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -31,13 +33,17 @@ ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [ + # default 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + + 'crispy_forms', + # local 'contest', ] @@ -73,6 +79,7 @@ WSGI_APPLICATION = 'cqtu.wsgi.application' AUTH_USER_MODEL = 'contest.User' LOGIN_REDIRECT_URL = '/' +LOGIN_URL = '/login/' # Database # https://docs.djangoproject.com/en/1.10/ref/settings/#databases @@ -127,3 +134,7 @@ STATICFILES_DIRS = [ os.path.join(BASE_DIR, "static"), ] +MESSAGE_TAGS = { + messages.ERROR: 'danger', +} + diff --git a/cqtu/urls.py b/cqtu/urls.py index bf496b9..f48a098 100644 --- a/cqtu/urls.py +++ b/cqtu/urls.py @@ -17,14 +17,25 @@ from django.conf.urls import url, include 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 + + urlpatterns = [ - url('^$', index), + url('^$', index, name="index"), url('^contest/', include('contest.urls', namespace='contest')), url(r'^admin/', admin.site.urls), - url(r'^login/$', auth_views.login), - url(r'^logout/$', auth_views.logout), + 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'^register/$', CreateView.as_view( + template_name='registration/register.html', + form_class=CustomUserCreationForm, + success_url='/', + ), name='register'), ] diff --git a/static/style.css b/static/style.css index b48cc14..8fc6635 100644 --- a/static/style.css +++ b/static/style.css @@ -1,7 +1,37 @@ body { - min-height: 2000px; + min-height: 200px; } .navbar-static-top { margin-bottom: 19px; } + +html { + position: relative; + min-height: 100%; +} +body { + /* Margin bottom by footer height */ + margin-bottom: 60px; +} +.footer { + position: absolute; + bottom: 0; + width: 100%; + /* Set the fixed height of the footer here */ + height: 60px; + background-color: #f5f5f5; +} + + +.container { + width: auto; + padding: 0 15px; +} +.container .text-muted { + margin: 20px 0; +} + +.asteriskField { + display: none; +} diff --git a/templates/base.html b/templates/base.html index 69e2149..b7cc4c0 100644 --- a/templates/base.html +++ b/templates/base.html @@ -14,6 +14,8 @@ + + @@ -32,25 +34,23 @@ - CQ TU 2017 + CQ TU 2017