diff --git a/.gitignore b/.gitignore index 4e24bc0b..8e1f31eb 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,4 @@ settings.json .gitpod.yml venv/ .idea/ -MYNOTES.md \ No newline at end of file +MYNOTES.md diff --git a/hackathon/lists.py b/hackathon/lists.py index db1a7bb6..bfafa2b3 100644 --- a/hackathon/lists.py +++ b/hackathon/lists.py @@ -7,3 +7,9 @@ ('published', 'Published'), ('deleted', 'Deleted'), ) + +JUDGING_STATUS_CHOICES = ( + ('not_yet_started', "Hasn't started"), + ('open', "Open"), + ('closed', "Closed"), +) \ No newline at end of file diff --git a/hackathon/migrations/0017_auto_20201025_1211.py b/hackathon/migrations/0017_auto_20201025_1211.py new file mode 100644 index 00000000..89ce08e5 --- /dev/null +++ b/hackathon/migrations/0017_auto_20201025_1211.py @@ -0,0 +1,26 @@ +# Generated by Django 3.1.1 on 2020-10-25 12:11 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('hackathon', '0016_merge_20201024_1240'), + ] + + operations = [ + migrations.AlterField( + model_name='hackproject', + name='created_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='hackproject', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='hackprojectscore', + name='created_by', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='hackprojectscores', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/hackathon/migrations/0018_auto_20201025_1933.py b/hackathon/migrations/0018_auto_20201025_1933.py new file mode 100644 index 00000000..bf6832da --- /dev/null +++ b/hackathon/migrations/0018_auto_20201025_1933.py @@ -0,0 +1,21 @@ +# Generated by Django 3.1.1 on 2020-10-25 19:33 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('hackathon', '0017_auto_20201025_1211'), + ] + + operations = [ + migrations.AlterField( + model_name='hackprojectscore', + name='judge', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/hackathon/migrations/0021_merge_20201029_1407.py b/hackathon/migrations/0021_merge_20201029_1407.py new file mode 100644 index 00000000..b2c4fc77 --- /dev/null +++ b/hackathon/migrations/0021_merge_20201029_1407.py @@ -0,0 +1,14 @@ +# Generated by Django 3.1.1 on 2020-10-29 14:07 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('hackathon', '0020_merge_20201026_2056'), + ('hackathon', '0018_auto_20201025_1933'), + ] + + operations = [ + ] diff --git a/hackathon/migrations/0022_hackathon_judging_status.py b/hackathon/migrations/0022_hackathon_judging_status.py new file mode 100644 index 00000000..25659d3a --- /dev/null +++ b/hackathon/migrations/0022_hackathon_judging_status.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1.1 on 2020-10-29 14:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hackathon', '0021_merge_20201029_1407'), + ] + + operations = [ + migrations.AddField( + model_name='hackathon', + name='judging_status', + field=models.CharField(choices=[('not_yet_started', "Hasn't started"), ('open', 'Open'), ('closed', 'Closed')], default='not_yet_started', max_length=16), + ), + ] diff --git a/hackathon/models.py b/hackathon/models.py index b9d0705b..42b0d77f 100644 --- a/hackathon/models.py +++ b/hackathon/models.py @@ -3,7 +3,7 @@ from accounts.models import CustomUser as User from accounts.models import Organisation -from .lists import STATUS_TYPES_CHOICES +from .lists import STATUS_TYPES_CHOICES, JUDGING_STATUS_CHOICES # Optional fields are ony set to deal with object deletion issues. # If this isn't a problem, they can all be changed to required fields. @@ -55,6 +55,13 @@ class Hackathon(models.Model): default='draft', choices=STATUS_TYPES_CHOICES ) + judging_status = models.CharField( + max_length=16, + blank=False, + default='not_yet_started', + choices=JUDGING_STATUS_CHOICES + ) + def __str__(self): return self.display_name @@ -161,8 +168,8 @@ class HackProjectScore(models.Model): created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name="hackprojectscores") - # One Judge can give one score - One to One - judge = models.OneToOneField(User, on_delete=models.CASCADE) + # One Judge scores several scorecategories - One to Many + judge = models.ForeignKey(User, on_delete=models.CASCADE) # One score is for one project, a project has numerous scores: One to Many project = models.ForeignKey(HackProject, on_delete=models.CASCADE, diff --git a/hackathon/static/hackathon/css/hackathon.css b/hackathon/static/hackathon/css/hackathon.css new file mode 100644 index 00000000..437507d1 --- /dev/null +++ b/hackathon/static/hackathon/css/hackathon.css @@ -0,0 +1,20 @@ +.hack-score-form { + width: 100%; + margin-right: 0px; + margin-left: 0px; +} + +.hack-score-cat-name { + min-height: 60px; + margin-bottom: 0; +} + +@media screen and (max-width: 720px) { + .hack-score-cat-name { + min-height: 30px; + } + /* .hack-score-cat-name, h5 { + font-size: 1rem; + } */ + +} \ No newline at end of file diff --git a/hackathon/templates/hackathon/judging.html b/hackathon/templates/hackathon/judging.html new file mode 100644 index 00000000..c3bb62c8 --- /dev/null +++ b/hackathon/templates/hackathon/judging.html @@ -0,0 +1,74 @@ +{% extends 'base.html' %} + +{% load my_tags %} +{% load static %} +{% block css %} + +{% endblock %} + +{% block content %} +
+
+

Judging Team {{ team }}

+ +
+ +
+ +
+ {% csrf_token %} +
+ {% for category in score_categories %} +
+
+
+
+ +
+
+
{{ category }}
+
+
+
+
+

some description here... probably something one liner or max two

+

+ descr. field needed in ScoreCat model +

+
+
+ + + +
+
+ {% endfor %} +
+
+
+ +
+
+ +
+ +
+ + +
+{% endblock %} diff --git a/hackathon/templatetags/__init__.py b/hackathon/templatetags/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/hackathon/templatetags/my_tags.py b/hackathon/templatetags/my_tags.py new file mode 100644 index 00000000..2a99c859 --- /dev/null +++ b/hackathon/templatetags/my_tags.py @@ -0,0 +1,27 @@ + +# range snippet from: https://www.djangosnippets.org/snippets/1357/ +# adjusted to current project needs based on https://docs.djangoproject.com/en/3.1/howto/custom-template-tags/ +from django.template import Library + +register = Library() + +@register.filter +def get_range(value, start): + """ + Filter - returns a list containing range made from given value + Usage (in template): + + + + Results with the HTML: + + + Instead of 3 one may use the variable set in the views + """ + return range(start, value+1, 1) diff --git a/hackathon/urls.py b/hackathon/urls.py index 124d755c..06d208e5 100644 --- a/hackathon/urls.py +++ b/hackathon/urls.py @@ -1,12 +1,13 @@ from django.urls import path - from .views import ( - HackathonListView, create_hackathon, update_hackathon, delete_hackathon + HackathonListView, create_hackathon, update_hackathon, delete_hackathon, judging ) urlpatterns = [ - path('', HackathonListView.as_view(), name='hackathon-list'), + path('', HackathonListView.as_view(), name="hackathon-list"), + path("/team//judging/", judging, name="judging"), path("create_hackathon", create_hackathon, name='create_hackathon'), path("/update_hackathon", update_hackathon, name="update_hackathon"), path("/delete_hackathon", delete_hackathon, name="delete_hackathon"), ] + diff --git a/hackathon/views.py b/hackathon/views.py index 3ec76d2d..be28774f 100644 --- a/hackathon/views.py +++ b/hackathon/views.py @@ -1,13 +1,14 @@ from datetime import datetime - from django.views.generic import ListView from django.shortcuts import render, get_object_or_404, redirect from django.contrib import messages from django.contrib.auth.decorators import login_required +from django.utils import timezone -from .models import Hackathon +from .models import Hackathon, HackTeam, HackProject, HackProjectScore, HackProjectScoreCategory from .forms import HackathonForm +# Create your views here. class HackathonListView(ListView): """Renders a page with a list of Hackathons.""" @@ -30,6 +31,65 @@ def get_context_data(self, **kwargs): @login_required +def judging(request, hack_id, team_id): + """Displays the judging page for the judge to save their scores + for the selected project - determined by hackathon id and team id""" + + # HackProjectScoreCategories: + score_categories = HackProjectScoreCategory.objects.all() + + hackathon = get_object_or_404(Hackathon, pk=hack_id) + team = get_object_or_404(HackTeam, pk=team_id) + + # verify whether user is judge for the hackathon + if hackathon not in Hackathon.objects.filter(judges=request.user): + messages.error(request, "You are not a judge for that event!") + return render(request, 'home/index.html') + + # verify if hackathon is ready to be judged (judging_status == 'open') + if hackathon.judging_status != 'open': + messages.error(request, f"Judging is not open! {hackathon.judging_status}!") + return render(request, 'home/index.html') + + # verify that the selected team belongs to the selected hackathon + if team.hackathon != hackathon: + messages.error(request, f"Nice try! That team is not part of the event...") + return render(request, 'home/index.html') + + # check if the judge has already scored the requested team's Project + project = get_object_or_404(HackTeam, pk=team_id).project + if not project: + messages.error(request, f"The team doesn't have a project yet, check back later...") + return render(request, 'home/index.html') + judge_has_scored_this = False + if HackProjectScore.objects.filter(judge=request.user, project=project): + messages.error(request, f"Oooops, sorry! Something went wrong, you have already scored that team...") + return render(request, 'home/index.html') + + if request.method == 'POST': + # judge score submitted for a team + for score_category in score_categories: + score_cat_id = f"score_{score_category.id}" + team_score = HackProjectScore( + created_by = request.user, + judge = request.user, + project = get_object_or_404(HackTeam, pk=team_id).project, + score = request.POST.get(score_cat_id), + hack_project_score_category = score_category, + ) + team_score.save() + + return redirect("hackathon:hackathon-list") + + context = { + 'hackathon': hackathon, + 'score_categories': score_categories, + 'team': team, + 'project': project, + } + return render(request, 'hackathon/judging.html', context) + + def create_hackathon(request): """ Allow users to create hackathon event """