Browse Source

EEEVERYTHING!

Sebastian Lohff 3 years ago
parent
commit
ee05f845cb
11 changed files with 328 additions and 107 deletions
  1. 2
    1
      contest/admin.py
  2. 35
    9
      contest/models.py
  3. 8
    3
      contest/urls.py
  4. 132
    5
      contest/views.py
  5. 11
    0
      cqtu/settings.py
  6. 14
    3
      cqtu/urls.py
  7. 31
    1
      static/style.css
  8. 34
    16
      templates/base.html
  9. 29
    35
      templates/index.html
  10. 0
    29
      templates/register_refs.html
  11. 32
    5
      templates/registration/login.html

+ 2
- 1
contest/admin.py View File

@@ -1,8 +1,9 @@
1 1
 from django.contrib import admin
2
-from .models import Frequency, Band, Reference, QSO, User
2
+from .models import Frequency, Band, Reference, QSO, User, Contest
3 3
 
4 4
 admin.site.register(User)
5 5
 admin.site.register(QSO)
6 6
 admin.site.register(Band)
7 7
 admin.site.register(Frequency)
8 8
 admin.site.register(Reference)
9
+admin.site.register(Contest)

+ 35
- 9
contest/models.py View File

@@ -3,25 +3,33 @@ from __future__ import unicode_literals
3 3
 from django.db import models
4 4
 from django.contrib.auth.models import AbstractUser
5 5
 
6
+from .validators import CallUsernameValidator
7
+
8
+class Contest(models.Model):
9
+	name = models.CharField(max_length=20)
10
+	shortName = models.CharField(max_length=20, unique=True)
11
+	callQrg = models.ForeignKey("Frequency", null=True)
12
+
13
+	def __str__(self):
14
+		return self.name
15
+
6 16
 class Reference(models.Model):
7 17
 	name = models.CharField(max_length=20, unique=True)
8 18
 	description = models.TextField()
9 19
 
20
+	def __str__(self):
21
+		return self.name
22
+
10 23
 class User(AbstractUser):
11 24
 	ref = models.ForeignKey(Reference, null=True, blank=True)
12 25
 
13
-class QSO(models.Model):
14
-	owner = models.ForeignKey(User)
15
-	time = models.DateTimeField()
16
-	call = models.CharField(max_length=20)
17
-
18
-	reportTX = models.CharField(max_length=7)
19
-	reportRX = models.CharField(max_length=7)
20
-
21
-	remarks = models.TextField()
26
+	def __init__(self, *args, **kwargs):
27
+		super(User, self).__init__(*args, **kwargs)
28
+		self._meta.get_field("username").validators = [CallUsernameValidator()]
22 29
 
23 30
 class Band(models.Model):
24 31
 	name = models.CharField(max_length=10)
32
+	contest = models.ForeignKey(Contest)
25 33
 
26 34
 	def __str__(self):
27 35
 		return self.name
@@ -33,6 +41,24 @@ class Frequency(models.Model):
33 41
 	qrg = models.DecimalField(max_digits=7, decimal_places=3)
34 42
 	band = models.ForeignKey(Band)
35 43
 
44
+	note = models.CharField(max_length=50)
45
+
36 46
 	def __str__(self):
37 47
 		return "Channel %s: %s MHz" % (self.channel, self.qrg)
38 48
 
49
+class QSO(models.Model):
50
+	owner = models.ForeignKey(User)
51
+	time = models.DateTimeField(blank=True)
52
+	call = models.CharField(max_length=20)
53
+	band = models.ForeignKey(Band)
54
+
55
+	reportTX = models.CharField(max_length=7, default=59)
56
+	reportRX = models.CharField(max_length=7, default=59)
57
+
58
+	ownNo = models.IntegerField()
59
+	otherNo = models.IntegerField()
60
+
61
+	refStr = models.CharField(max_length=20)
62
+	ref = models.ForeignKey(Reference, null=True, blank=True)
63
+
64
+	remarks = models.CharField(max_length=50, blank=True)

+ 8
- 3
contest/urls.py View File

@@ -16,9 +16,14 @@ Including another URLconf
16 16
 from django.conf.urls import url
17 17
 
18 18
 
19
-from contest.views import index, registerRefs
19
+import contest.views as contest_views
20 20
 
21 21
 urlpatterns = [
22
-	url('^$', index),
23
-	url('^regref/', registerRefs, name='registerRefs'),
22
+	url(r'^$', contest_views.contestIndex, name='index'),
23
+	url(r'^regref/$', contest_views.registerRefs, name='registerRefs'),
24
+	url(r'^regref/edit/(?P<uid>\d+)/$', contest_views.updateRef, name='updateRef'),
25
+	url(r'^overview/$', contest_views.overview, name='overview'),
26
+	url(r'^log/$', contest_views.log, name='log'),
27
+	url(r'^log/edit/(?P<qsoid>\d+)/$', contest_views.logEdit, name='logEdit'),
28
+	url(r'^log/delete/(?P<qsoid>\d+)/$', contest_views.logDelete, name='logDelete'),
24 29
 ]

+ 132
- 5
contest/views.py View File

@@ -1,14 +1,141 @@
1 1
 from django.shortcuts import render
2 2
 
3
-from django.contrib.admin.views.decorators import staff_member_required, login_required
3
+from django.contrib.auth.decorators import login_required
4
+from django.contrib.admin.views.decorators import staff_member_required
5
+from django.db.models import Q
6
+from django.contrib.auth.forms import AuthenticationForm
7
+from django.http import HttpResponseRedirect
8
+from django.contrib import messages
9
+from django.urls import reverse
4 10
 
5
-from .models import User
11
+import datetime
12
+
13
+from .models import User, Contest, Frequency, Reference, QSO
14
+from .forms import UpdateRefForm, QSOForm
6 15
 
7
-@login_required
8 16
 def index(request):
9
-	return render(request, 'index.html', {})
17
+	if request.user.is_authenticated():
18
+		return HttpResponseRedirect(reverse("contest:index"))
19
+
20
+	return render(request, "index.html", {"loginForm": AuthenticationForm()})
21
+
22
+@login_required
23
+def contestIndex(request):
24
+	#messages.debug(request, "Debug GLITCHHHHH")
25
+	#messages.info(request, "This info is very educational")
26
+	#messages.warning(request, "You got a warning")
27
+	#messages.error(request, "Error!!!")
28
+	#messages.success(request, "Great Success")
29
+	qsoform = QSOForm(request.user)
30
+
31
+	return render(request, 'contest/index.html', {"qsoform": qsoform})
32
+
33
+@login_required
34
+def log(request):
35
+	form = None
36
+
37
+	qsos = QSO.objects.filter(owner=request.user).order_by("-ownNo")
38
+
39
+	if request.method == 'POST':
40
+		form = QSOForm(user=request.user, data=request.POST)
41
+		if form.is_valid():
42
+			l = form.instance
43
+			if not l.time:
44
+				# set current time
45
+				l.time = datetime.datetime.now()
46
+
47
+			l.owner = request.user
48
+			l.save()
49
+
50
+			messages.success(request, "QSO saved!")
51
+
52
+			return HttpResponseRedirect(reverse("contest:log"))
53
+	else:
54
+		# FIXME: data initial my qso number
55
+		data = {
56
+			"ownNo": qsos[0].ownNo + 1 if len(qsos) > 0 else 1,
57
+			"reportRX": "59",
58
+			"reportTX": "59",
59
+		}
60
+		form = QSOForm(request.user, initial=data)
61
+
62
+
63
+	return render(request, 'contest/log.html', {'form': form, 'qsos': qsos})
64
+
65
+@login_required
66
+def logEdit(request, qsoid):
67
+	qso = QSO.objects.get(id=qsoid, owner=request.user)
68
+	form = None
69
+
70
+	if request.method == 'POST':
71
+		form = QSOForm(user=request.user, instance=qso, data=request.POST)
72
+		if form.is_valid():
73
+			form.instance.save()
74
+
75
+			messages.info(request, "QSO has been edited")
76
+			return HttpResponseRedirect(reverse("contest:log"))
77
+	else:
78
+		form = QSOForm(user=request.user, instance=qso)
79
+
80
+	return render(request, 'contest/logEdit.html', {'form': form, "qso": qso})
81
+
82
+def logDelete(request, qsoid):
83
+	qso = QSO.objects.get(id=qsoid, owner=request.user)
84
+
85
+	if request.method == 'POST':
86
+		if "delete" in request.POST:
87
+			if request.POST["delete"] == "yes":
88
+				qso.delete()
89
+				messages.info(request, "QSO has been deleted")
90
+				return HttpResponseRedirect(reverse("contest:log"))
91
+			elif request.POST["delete"] == "no":
92
+				return HttpResponseRedirect(reverse("contest:log"))
93
+
94
+	return render(request, 'contest/logDelete.html', {"qso": qso})
95
+
96
+
10 97
 
11 98
 @staff_member_required
12 99
 def registerRefs(request):
100
+	allUser = User.objects.all()
101
+	refsMissingUser = User.objects.filter(ref=None).order_by("username")
102
+	refsNotMissingUser = User.objects.filter(~Q(ref=None)).order_by("username")
103
+
104
+	qsos = QSO.objects.all().order_by("-time")
105
+
106
+	return render(request, 'contest/registerRefs.html', {'alluser': allUser, 'refsMissingUser': refsMissingUser, "refsNotMissinguser": refsNotMissingUser, "qsos": qsos})
107
+
108
+@staff_member_required
109
+def updateRef(request, uid):
110
+	user = User.objects.get(id=uid)
111
+	form = None
112
+
113
+	if request.method == 'POST':
114
+		form = UpdateRefForm(data=request.POST)
115
+		if form.is_valid():
116
+			ref = None
117
+			if form.cleaned_data["existingRef"]:
118
+				print("Got an existing Ref")
119
+				ref = form.cleaned_data["existingRef"]
120
+			else:
121
+				ref = Reference(name=form.cleaned_data["newRefName"])
122
+				ref.save()
123
+				messages.info(request, "New Ref '%s' created" % ref)
124
+
125
+			user.ref = ref
126
+			user.save()
127
+			messages.success(request, "%s ref set to %s" % (user, ref))
128
+			return HttpResponseRedirect(reverse("contest:registerRefs"))
129
+	else:
130
+		form = UpdateRefForm()
131
+
132
+	return render(request, 'contest/updateRef.html', {'user': user, 'form': form})
133
+
134
+def overview(request):
135
+	# FIXME: Hardcoded for cqtu... everywhere
136
+	c = Contest.objects.get(id=1)
137
+	qrgs = Frequency.objects.filter(band__contest=c).order_by("channel")
138
+	return render(request, 'contest/overview.html', {'contest': c, 'qrgs': qrgs})
13 139
 
14
-	return render(request, 'register_refs.html', {'alluser': User.objects.all()})
140
+def register(request):
141
+	return render(request, 'registration/register.html', {})

+ 11
- 0
cqtu/settings.py View File

@@ -11,6 +11,8 @@ https://docs.djangoproject.com/en/1.10/ref/settings/
11 11
 """
12 12
 
13 13
 import os
14
+from django.contrib.messages import constants as messages
15
+
14 16
 
15 17
 # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
16 18
 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -31,13 +33,17 @@ ALLOWED_HOSTS = []
31 33
 # Application definition
32 34
 
33 35
 INSTALLED_APPS = [
36
+	# default
34 37
     'django.contrib.admin',
35 38
     'django.contrib.auth',
36 39
     'django.contrib.contenttypes',
37 40
     'django.contrib.sessions',
38 41
     'django.contrib.messages',
39 42
     'django.contrib.staticfiles',
43
+	
44
+	'crispy_forms',
40 45
 
46
+	# local
41 47
 	'contest',
42 48
 ]
43 49
 
@@ -73,6 +79,7 @@ WSGI_APPLICATION = 'cqtu.wsgi.application'
73 79
 
74 80
 AUTH_USER_MODEL = 'contest.User'
75 81
 LOGIN_REDIRECT_URL = '/'
82
+LOGIN_URL = '/login/'
76 83
 
77 84
 # Database
78 85
 # https://docs.djangoproject.com/en/1.10/ref/settings/#databases
@@ -127,3 +134,7 @@ STATICFILES_DIRS = [
127 134
     os.path.join(BASE_DIR, "static"),
128 135
 ]
129 136
 
137
+MESSAGE_TAGS = {
138
+	messages.ERROR: 'danger',
139
+}
140
+

+ 14
- 3
cqtu/urls.py View File

@@ -17,14 +17,25 @@ from django.conf.urls import url, include
17 17
 from django.contrib import admin
18 18
 
19 19
 from django.contrib.auth import views as auth_views
20
+from django.views.generic.edit import CreateView
21
+#from django.contrib.auth.forms import UserCreationForm
20 22
 
23
+from contest.forms import CustomUserCreationForm
21 24
 from contest.views import index
22 25
 
26
+
27
+
23 28
 urlpatterns = [
24
-	url('^$', index),
29
+	url('^$', index, name="index"),
25 30
 	url('^contest/', include('contest.urls', namespace='contest')),
26 31
 
27 32
     url(r'^admin/', admin.site.urls),
28
-	url(r'^login/$', auth_views.login),
29
-	url(r'^logout/$', auth_views.logout),
33
+	url(r'^login/$', auth_views.login, name='login'),
34
+	url(r'^logout/$', auth_views.logout, {'next_page': '/'}, name='logout'),
35
+	#url(r'^register/$', register, name='register'),
36
+	url(r'^register/$', CreateView.as_view(
37
+				template_name='registration/register.html',
38
+				form_class=CustomUserCreationForm,
39
+				success_url='/',
40
+	), name='register'),
30 41
 ]

+ 31
- 1
static/style.css View File

@@ -1,7 +1,37 @@
1 1
 body {
2
-  min-height: 2000px;
2
+  min-height: 200px;
3 3
 }
4 4
 
5 5
 .navbar-static-top {
6 6
   margin-bottom: 19px;
7 7
 }
8
+
9
+html {
10
+  position: relative;
11
+  min-height: 100%;
12
+}
13
+body {
14
+  /* Margin bottom by footer height */
15
+  margin-bottom: 60px;
16
+}
17
+.footer {
18
+  position: absolute;
19
+  bottom: 0;
20
+  width: 100%;
21
+  /* Set the fixed height of the footer here */
22
+  height: 60px;
23
+  background-color: #f5f5f5;
24
+}
25
+
26
+
27
+.container {
28
+  width: auto;
29
+  padding: 0 15px;
30
+}
31
+.container .text-muted {
32
+  margin: 20px 0;
33
+}
34
+
35
+.asteriskField {
36
+    display: none;
37
+}

+ 34
- 16
templates/base.html View File

@@ -14,6 +14,8 @@
14 14
 
15 15
     <!-- Bootstrap core CSS -->
16 16
     <link href="{% static "css/bootstrap.min.css" %}" rel="stylesheet">
17
+    <link href="{% static "css/uni-form.css" %}" rel="stylesheet">
18
+    <link href="{% static "css/style.uni-form.css" %}" rel="stylesheet">
17 19
 
18 20
     <!-- Custom styles for this template -->
19 21
     <link href="{% static "style.css" %}" rel="stylesheet">
@@ -32,25 +34,23 @@
32 34
             <span class="icon-bar"></span>
33 35
             <span class="icon-bar"></span>
34 36
           </button>
35
-          <a class="navbar-brand" href="#">CQ TU 2017</a>
37
+          <a class="navbar-brand" href="{% url "index" %}">CQ TU 2017</a>
36 38
         </div>
37 39
         <div id="navbar" class="navbar-collapse collapse">
38 40
           <ul class="nav navbar-nav">
39
-            <li class="active"><a href="#">Home</a></li>
40
-            <li><a href="#about">Search</a></li>
41
-            <li><a href="#contact">Contact</a></li>
42
-            <li class="dropdown">
43
-              <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a>
44
-              <ul class="dropdown-menu">
45
-                <li><a href="#">Action</a></li>
46
-                <li><a href="#">Another action</a></li>
47
-                <li><a href="#">Something else here</a></li>
48
-                <li role="separator" class="divider"></li>
49
-                <li class="dropdown-header">Nav header</li>
50
-                <li><a href="#">Separated link</a></li>
51
-                <li><a href="#">One more separated link</a></li>
52
-              </ul>
53
-            </li>
41
+{% if user.is_authenticated %}
42
+            <li class="active"><a href="{% url "contest:index" %}">Home</a></li>
43
+{% if user.ref %}
44
+            <li><a href="{% url "contest:log" %}">Log</a></li>
45
+{% endif %}
46
+	{% if user.is_staff %}
47
+            <li><a href="{% url "contest:registerRefs" %}">Register Refs!</a></li>
48
+	{% endif %}
49
+{% else %}
50
+            <li><a href="{% url "index" %}">Home</a></li>
51
+            <li><a href="{% url "register" %}">Register</a></li>
52
+{% endif %}
53
+			<li><a href="{% url "contest:overview" %}">Contest Overview</a></li>
54 54
           </ul>
55 55
           <ul class="nav navbar-nav navbar-right">
56 56
 		  	{% if user.is_staff %}
@@ -68,10 +68,28 @@
68 68
 
69 69
 
70 70
     <div class="container">
71
+{% if messages %}
72
+		<div class="row">
73
+			<div class="col-12">
74
+	{% for message in messages %}
75
+				<div class="alert {% if message.tags %}alert-{{ message.tags }} {% endif %}alert-dismissible">{{ message }}</div>
76
+	{% endfor %}
77
+			</div>
78
+		</div>
79
+{% endif %}
80
+
81
+
71 82
 {% block content %}{% endblock %}
72 83
     </div> <!-- /container -->
73 84
 
74 85
 
86
+    <footer class="footer">
87
+      <div class="container">
88
+        <p class="text-muted">CQ TU 2017, a <a href="http://dk0tu.de/">DK0TU</a> product</p>
89
+      </div>
90
+    </footer>
91
+
92
+
75 93
     <!-- Bootstrap core JavaScript
76 94
     ================================================== -->
77 95
     <!-- Placed at the end of the document so the pages load faster -->

+ 29
- 35
templates/index.html View File

@@ -1,46 +1,40 @@
1 1
 {% extends "base.html" %}
2 2
 
3
+{% load crispy_forms_tags %}
4
+
3 5
 {% block content %}
4
-<h2>Welcome!</h2>
5
-<div class="row">
6
-	<div class="col-sm-12">
7
-		I have {{ call_count }} callsigns in my Database!
8
-	</div>
9
-</div>
10
-{% if user.ref %}
11
-{# user has a reference, we can start logging QSOs! #}
6
+
12 7
 <div class="row">
13 8
 	<div class="col-sm-6">
14
-		Hey, nice! You are a Call at a certain level and you have a ref of {{ user.ref }}. Start doing the QSO foo! GO GO GO GO
9
+		<p class="lead">Hello and welcome to the 2nd DK0TU CQ TU contest, the CQ TU 2107!</p>
10
+		<p>
11
+		What next? Here are some ideas:
12
+		</p>
13
+		<p>
14
+		<ul>
15
+			<li>If you want to review the contest rules, take a look at <a href="{% url "contest:overview" %}">the contest overview page</a></li>
16
+			<li>Need an account and already got a call? <a href="{% url "register" %}">Register here</a>!</li>
17
+			<li>Already have an account? Login on the right.</li>
18
+		</ul>
19
+		</p>
15 20
 	</div>
16 21
 	<div class="col-sm-6">
17
-		<div class="panel panel-default">
18
-			<table class="table table-inverse">
19
-				<thead class="thead-inverse">
20
-					<tr>
21
-						<th>Date</th>
22
-						<th>Source</th>
23
-						<th>Version</th>
24
-					</tr>
25
-				</thead>
26
-				<tbody>
27
-					{% for log in call_import %}
28
-						<tr>
29
-							<td>{{ log.last_version_time }}</td>
30
-							<td>{{ log.datasource }}</td>
31
-							<td>{{ log.version }}</td>
32
-						</tr>
33
-					{% endfor %}
34
-				</tbody>
35
-			</table>
22
+		Login with callsign (uppercase) and password.
23
+		<form method="post" action="{% url "login" %}" class="uniForm">
24
+			{% csrf_token %}
25
+			{{ loginForm|crispy }}
26
+			<button type="submit" class="btn btn-primary">Login</button>
27
+		</form>
36 28
 	</div>
37 29
 </div>
38
-{% else %}
39
-Hey you, you don't have a ref. You should have a ref! Register with contest people! on QRG FOOOO
40
-{% endif %}
41 30
 
42
-{% if user.is_staff %}
43
-Hey, you are staff. Do you want to <a href="{% url "contest:registerRefs" %}">register people</a>?
44
-{% endif %}
31
+<div class="row">
32
+	<div class="col-sm-12">
33
+		Und nun noch ein Wort von unserem Sponsor:
34
+        <blockquote>
35
+            Serverbasierte Loesung finde ich nach eingem Ueberlegen doof.
36
+            <footer>DL7BST</footer>
37
+        </blockquote>
38
+	</div>
39
+</div>
45 40
 {% endblock %}
46
-

+ 0
- 29
templates/register_refs.html View File

@@ -1,29 +0,0 @@
1
-{% extends "base.html" %}
2
-
3
-{% block content %}
4
-<h2>Welcome!</h2>
5
-<div class="row">
6
-	<div class="col-sm-12">
7
-		Register Someone!!!!
8
-		Here be FORM for registering someone without a ref
9
-	</div>
10
-</div>
11
-<div class="row">
12
-	<div class="col-sm-12">
13
-		Here is a Table with all OMs/YLs in the contest!
14
-		<table>
15
-			<tr>
16
-				<th>Call</th>
17
-				<th>Ref</th>
18
-			</tr>
19
-		{% for u in alluser %}
20
-			<tr>
21
-				<td>{{ u.username }}</td>
22
-				<td>{{ u.ref|default:"unknown / unset" }}</td>
23
-			</tr>
24
-		{% endfor %}
25
-		</table>
26
-	</div>
27
-</div>
28
-{% endblock %}
29
-

+ 32
- 5
templates/registration/login.html View File

@@ -1,19 +1,46 @@
1 1
 {% extends "base.html" %}
2 2
 
3
+{% load crispy_forms_tags %}
4
+
3 5
 {% block content %}
4 6
 <h2>Welcome!</h2>
5 7
 <div class="row">
6
-	<div class="col-sm-12">
7
-		Please Login!
8
-		<form action="#" method="post">
9
-			{{ form.as_table }}
8
+	<div class="col-sm-4 col-sm-offset-4">
9
+		<h2 class="text-center">Please login</h2>
10
+		<p>
11
+		Don't have an account? <a href="{% url 'register' %}">Register here!</a>
12
+		</p>
13
+		<p>
14
+		Trouble logging in? Remember that your call is all upper-case.
15
+		</p>
16
+
17
+		<form action="{% url "login" %}" method="post" class="uniForm">
10 18
 			{% csrf_token %}
19
+			{{ form|crispy }}
11 20
 
12
-			<input type="submit" class="btn btn-default" value="Login">
21
+			<button type="submit" class="btn btn-default" value="Login">Login</button>
13 22
 		</form>
14 23
 	</div>
15 24
 </div>
25
+<!--
26
+<div class="row">
27
+	<div class="col-sm-6">
28
+		<blockquote>
29
+			Serverbasierte Loesung finde ich nach eingem Ueberlegen doof.
30
+			<footer>DL7BST</footer>
31
+		</blockquote>
32
+	</div>
33
+	<div class="col-sm-6">
34
+		Please Login! Don't have an account? <a href="{% url 'register' %}">Register here!</a>
35
+		<form action="#" method="post" class="uniForm">
36
+			{% csrf_token %}
37
+			{{ form|crispy }}
16 38
 
39
+			<button type="submit" class="btn btn-default" value="Login">Login</button>
40
+		</form>
41
+	</div>
42
+</div>
43
+-->
17 44
 </div>
18 45
 {% endblock %}
19 46
 

Loading…
Cancel
Save