From 55fc02219f61b86ba9af68cbc342c42542f42d7f Mon Sep 17 00:00:00 2001 From: Rushil Umaretiya Date: Fri, 19 Feb 2021 14:47:07 -0500 Subject: [PATCH] feat: added judges section --- config/settings.py | 2 +- config/urls.py | 5 +- innovate/admin.py | 5 +- innovate/forms.py | 23 +++- innovate/migrations/0004_judge.py | 27 +++++ innovate/migrations/0005_judge_iseven.py | 19 ++++ innovate/migrations/0006_score.py | 36 +++++++ innovate/models.py | 51 ++++++++- innovate/static/innovate/portal.css | 6 ++ innovate/templates/innovate/feedback.html | 121 ++++++++++++++++++++++ innovate/templates/innovate/portal.html | 31 ++++++ innovate/urls.py | 2 + innovate/views.py | 68 +++++++++++- launchx/templates/launchx/base.html | 7 +- users/static/users/login.css | 3 + users/templates/users/login.html | 18 +++- users/templates/users/logout.html | 6 ++ users/views.py | 12 ++- 18 files changed, 427 insertions(+), 15 deletions(-) create mode 100644 innovate/migrations/0004_judge.py create mode 100644 innovate/migrations/0005_judge_iseven.py create mode 100644 innovate/migrations/0006_score.py create mode 100644 innovate/static/innovate/portal.css create mode 100644 innovate/templates/innovate/feedback.html create mode 100644 innovate/templates/innovate/portal.html create mode 100644 users/static/users/login.css create mode 100644 users/templates/users/logout.html diff --git a/config/settings.py b/config/settings.py index f1ca51b..c6effd8 100644 --- a/config/settings.py +++ b/config/settings.py @@ -160,7 +160,7 @@ CRISPY_TEMPLATE_PACK = 'bootstrap4' MEDIA_ROOT = os.path.join(BASE_DIR, 'media') MEDIA_URL = '/media/' -LOGIN_REDIRECT_URL = 'blog-home' +LOGIN_REDIRECT_URL = 'judges-portal' LOGIN_URL = 'login' # Email diff --git a/config/urls.py b/config/urls.py index 27be4df..52ee564 100644 --- a/config/urls.py +++ b/config/urls.py @@ -19,12 +19,13 @@ from django.contrib import admin from django.urls import path, include from users import views as user_views from innovate.admin import admin_site as innovate_admin +from django.contrib.auth import views as auth_views urlpatterns = [ path('', include('launchx.urls')), path('innovate/', include('innovate.urls'), name='innovate'), - path('login/', user_views.login, name='login'), - path('logout/', user_views.login, name='logout'), + path('login/', auth_views.LoginView.as_view(template_name='users/login.html'), name='login'), + path('logout/', user_views.logout, name='logout'), path('admin/', innovate_admin.urls), ] diff --git a/innovate/admin.py b/innovate/admin.py index 503719a..8c3caa4 100644 --- a/innovate/admin.py +++ b/innovate/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin -from .models import Team, Competitor +from .models import Team, Competitor, Judge, Score from django.contrib.auth.models import User, Group # Register your models here. @@ -14,6 +14,9 @@ admin_site = LaunchXAdminSite(name='launchx-admin') admin_site.register(User) admin_site.register(Group) +admin_site.register(Judge) +admin_site.register(Score) + admin_site.register(Competitor) class CompetitorInline(admin.TabularInline): diff --git a/innovate/forms.py b/innovate/forms.py index 80cde77..6ad64c3 100644 --- a/innovate/forms.py +++ b/innovate/forms.py @@ -1,7 +1,7 @@ from django import forms from django.forms import modelformset_factory -from .models import Competitor, Team +from .models import Competitor, Team, Score class CompetitorForm(forms.ModelForm): name = forms.CharField(label='Full Name', widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'John Doe'})) @@ -24,7 +24,7 @@ CompetitorFormset = modelformset_factory( min_num=2, max_num=4) class TeamForm(forms.ModelForm): - name = forms.CharField(required=False, label="Team Name", widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'BusinessX'})) + name = forms.CharField(required=True, label="Team Name", widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'BusinessX'})) reciept = forms.FileField(required=False) class Meta: @@ -40,4 +40,21 @@ class TeamForm(forms.ModelForm): m.number = Team.objects.all().count() + 1 if commit: m.save() - return m \ No newline at end of file + return m + +class ScoreForm(forms.ModelForm): + innovation = forms.IntegerField(min_value=0, max_value=30, widget=forms.NumberInput(attrs={'placeholder': '30'})) + need = forms.IntegerField(min_value=0, max_value=35, widget=forms.NumberInput(attrs={'placeholder': '35'})) + finances = forms.IntegerField(min_value=0, max_value=25, widget=forms.NumberInput(attrs={'placeholder': '25'})) + creativity = forms.IntegerField(min_value=0, max_value=10, widget=forms.NumberInput(attrs={'placeholder': '10'})) + + qna = forms.IntegerField(min_value=0, max_value=25, widget=forms.NumberInput(attrs={'placeholder': '25'})) + speaking = forms.IntegerField(min_value=0, max_value=10, widget=forms.NumberInput(attrs={'placeholder': '10'})) + persuasiveness = forms.IntegerField(min_value=0, max_value=10, widget=forms.NumberInput(attrs={'placeholder': '10'})) + professionalism = forms.IntegerField(min_value=0, max_value=5, widget=forms.NumberInput(attrs={'placeholder': '5'})) + + feedback = forms.CharField(required=False, widget=forms.Textarea(attrs={'placeholder': 'Enter feedback here (optional)', 'class': 'form-control'})) + + class Meta: + model = Score + fields = ['innovation', 'need', 'finances', 'creativity', 'qna', 'speaking', 'persuasiveness', 'professionalism'] \ No newline at end of file diff --git a/innovate/migrations/0004_judge.py b/innovate/migrations/0004_judge.py new file mode 100644 index 0000000..c469d73 --- /dev/null +++ b/innovate/migrations/0004_judge.py @@ -0,0 +1,27 @@ +# Generated by Django 3.1.6 on 2021-02-19 05:04 + +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), + ('innovate', '0003_auto_20210211_0122'), + ] + + operations = [ + migrations.CreateModel( + name='Judge', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'Judge', + 'verbose_name_plural': 'Judges', + }, + ), + ] diff --git a/innovate/migrations/0005_judge_iseven.py b/innovate/migrations/0005_judge_iseven.py new file mode 100644 index 0000000..46c9f91 --- /dev/null +++ b/innovate/migrations/0005_judge_iseven.py @@ -0,0 +1,19 @@ +# Generated by Django 3.1.6 on 2021-02-19 05:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('innovate', '0004_judge'), + ] + + operations = [ + migrations.AddField( + model_name='judge', + name='isEven', + field=models.BooleanField(default=False), + preserve_default=False, + ), + ] diff --git a/innovate/migrations/0006_score.py b/innovate/migrations/0006_score.py new file mode 100644 index 0000000..a2a202e --- /dev/null +++ b/innovate/migrations/0006_score.py @@ -0,0 +1,36 @@ +# Generated by Django 3.1.6 on 2021-02-19 17:20 + +from django.db import migrations, models +import django.db.models.deletion +import innovate.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('innovate', '0005_judge_iseven'), + ] + + operations = [ + migrations.CreateModel( + name='Score', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('innovation', innovate.models.IntegerRangeField()), + ('need', innovate.models.IntegerRangeField()), + ('finances', innovate.models.IntegerRangeField()), + ('creativity', innovate.models.IntegerRangeField()), + ('qna', innovate.models.IntegerRangeField()), + ('speaking', innovate.models.IntegerRangeField()), + ('persuasiveness', innovate.models.IntegerRangeField()), + ('professionalism', innovate.models.IntegerRangeField()), + ('feedback', models.TextField(blank=True, null=True)), + ('judge', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='innovate.judge')), + ('team', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='innovate.team')), + ], + options={ + 'verbose_name': 'Feedback', + 'verbose_name_plural': 'Feedback', + }, + ), + ] diff --git a/innovate/models.py b/innovate/models.py index ce34442..567e15a 100644 --- a/innovate/models.py +++ b/innovate/models.py @@ -1,7 +1,15 @@ from django.db import models +from django.contrib.auth.models import User from django.core.exceptions import ValidationError -# Create your models here. +class IntegerRangeField(models.IntegerField): + def __init__(self, verbose_name=None, name=None, min_value=None, max_value=None, **kwargs): + self.min_value, self.max_value = min_value, max_value + models.IntegerField.__init__(self, verbose_name, name, **kwargs) + def formfield(self, **kwargs): + defaults = {'min_value': self.min_value, 'max_value':self.max_value} + defaults.update(kwargs) + return super(IntegerRangeField, self).formfield(**defaults) class Team(models.Model): number = models.IntegerField() @@ -43,3 +51,44 @@ class Competitor(models.Model): # if Competitor.objects.filter(email=self.email).count() > 0: # raise ValidationError({'email': 'Somebody with that email is already registered!'}) + +class Judge(models.Model): + user = models.ForeignKey(User, on_delete=models.CASCADE) + isEven = models.BooleanField() + + class Meta: + verbose_name = "Judge" + verbose_name_plural = "Judges" + + def __str__(self): + return f'Judge {self.user.last_name}' + +class Score(models.Model): + judge = models.ForeignKey(Judge, on_delete=models.CASCADE) + team = models.ForeignKey(Team, on_delete=models.CASCADE) + + # product scores + innovation = IntegerRangeField(min_value=0, max_value=30) + need = IntegerRangeField(min_value=0, max_value=35) + finances = IntegerRangeField(min_value=0, max_value=25) + creativity = IntegerRangeField(min_value=0, max_value=10) + + # delivery scores + qna = IntegerRangeField(min_value=0, max_value=25) + speaking = IntegerRangeField(min_value=0, max_value=10) + persuasiveness = IntegerRangeField(min_value=0, max_value=10) + professionalism = IntegerRangeField(min_value=0, max_value=5) + fields = [innovation, need, finances, creativity, qna, speaking, persuasiveness, professionalism] + + feedback = models.TextField(blank=True, null=True) + + class Meta: + verbose_name = "Feedback" + verbose_name_plural = "Feedback" + + def __str__(self): + return f'Team {self.team.number}\'s Feedback from {self.judge.user.get_full_name()}' + + def get_total_score(self): + fields = [self.innovation, self.need, self.finances, self.creativity, self.qna, self.speaking, self.persuasiveness, self.professionalism] + return sum(fields) \ No newline at end of file diff --git a/innovate/static/innovate/portal.css b/innovate/static/innovate/portal.css new file mode 100644 index 0000000..2b2cf31 --- /dev/null +++ b/innovate/static/innovate/portal.css @@ -0,0 +1,6 @@ +h3 small { + font-size: .66em; +} +.text-success { + color: rgb(25, 207, 34) !important +} \ No newline at end of file diff --git a/innovate/templates/innovate/feedback.html b/innovate/templates/innovate/feedback.html new file mode 100644 index 0000000..fdab8dc --- /dev/null +++ b/innovate/templates/innovate/feedback.html @@ -0,0 +1,121 @@ +{% extends 'launchx/base.html' %} +{% load launchx_extras %} +{% block content %} +

Feedback for Team {{ team.number }}: {{ team.name }}

+
+
+ {% csrf_token %} + {{ form.non_field_errors }} + {{ form.errors }} + {% for hidden in form.hidden_fields %} + {{ hidden }} + {% endfor %} + +
+

Product

+
+
+

Innovation

+
+
+
+

Does the product have a unique value proposition (15 pts) ? i.e. does it use novel, innovative technologies, or combine existing technologies in a unique way (10 pts) ? How does their solution fit under the theme of innovation (5 pts) ?

+
+
+ {{ form.innovation }}/30 +
+
+
+
+
+

Marketability / Need

+
+
+
+

Is there a need for this product (10 pts) ? Does the team know the intended market (5 pts) ? Do they complete a market analysis or examine their servicable avaible market (15 pts) ? Is the innovation practical (5 pts) ?

+
+
+ {{ form.need }}/35 +
+
+
+
+
+

Finances

+
+
+
+

Does the team have a business model (10 pts) ? Do they know who their intended customer is (5 pts) ? Does the team perform a competitive analysis of their market (explain the differentiators between their innovation and current products/services in the market) (10 pts)?

+
+
+ {{ form.finances }}/25 +
+
+
+
+
+

Creativity / Professionalism

+
+
+
+

Is their presentation creative AND professional (5 pts each) ?

+
+
+ {{ form.creativity }}/10 +
+
+
+
+
+ +
+

Delivery

+
+
+

Q&A Session

+
+
+ {{ form.qna }}/25 +
+
+
+
+

Speaking Quality

+
+
+ {{ form.speaking }}/10 +
+
+
+
+

Persuasiveness

+
+
+ {{ form.persuasiveness }}/10 +
+
+
+
+

Professionalism

+
+
+ {{ form.professionalism }}/5 +
+
+
+
+
+
+

Feedback

+
+
+ {{ form.feedback }} +
+
+
+
+ +
+ + +{% endblock %} \ No newline at end of file diff --git a/innovate/templates/innovate/portal.html b/innovate/templates/innovate/portal.html new file mode 100644 index 0000000..f316b71 --- /dev/null +++ b/innovate/templates/innovate/portal.html @@ -0,0 +1,31 @@ +{% extends 'launchx/base.html' %} +{% load launchx_extras %} + +{% block styles %} + +{% endblock %} + +{% block content %} +
+

Welcome Judge {{ request.user.last_name }},

+
Thanks for judging at InnovateTJ, now time to score!
+
+
+
+

{{ completed_teams_count }} out of {{ teams_count }} teams scored

+
+
+ {% for team in teams %} +
+

Team {{ team.number }}: {{ team.name }} Unscored

+ +
+ {% endfor %} + + {% for team, score in completed_teams %} +
+

Team {{ team.number }}: {{ team.name }} Scored {{ score.get_total_score }}/150

+
+ {% endfor %} +
+{% endblock %} \ No newline at end of file diff --git a/innovate/urls.py b/innovate/urls.py index d9ae33c..4ce97d8 100644 --- a/innovate/urls.py +++ b/innovate/urls.py @@ -5,4 +5,6 @@ urlpatterns = [ path('', views.home, name='innovate-home'), path('signup/', views.signup, name='competitor-signup'), path('signup/confirm/', views.confirm, name='competitor-signup-confirm'), + path('judges/', views.portal, name='judges-portal'), + path('judges/feedback/', views.feedback, name='judges-feedback') ] \ No newline at end of file diff --git a/innovate/views.py b/innovate/views.py index db563b3..3eaa3bc 100644 --- a/innovate/views.py +++ b/innovate/views.py @@ -1,13 +1,16 @@ from django.shortcuts import render, redirect -from .forms import CompetitorFormset, CompetitorForm, TeamForm -from .models import Competitor +from .forms import CompetitorFormset, CompetitorForm, TeamForm, ScoreForm +from .models import Competitor, Team, Judge, Score + +from django.contrib.auth.decorators import login_required from django.template.loader import render_to_string from django.utils.html import strip_tags -from config.settings import EMAIL_HOST_USER from django.core.mail import EmailMultiAlternatives, send_mail +from config.settings import EMAIL_HOST_USER + # Create your views here. def home(request): return render(request, 'innovate/index.html') @@ -66,3 +69,62 @@ def send_confirmation(request, team, members): def confirm(request): return render(request, 'innovate/confirm.html') + +@login_required +def portal(request): + judge = Judge.objects.get(user=request.user) + team_numbers = [team.number for team in Team.objects.all() if ((team.number % 2 == 0 and judge.isEven) or (team.number % 2 != 0 and not judge.isEven))] + teams, completed_teams = [], [] + for number in team_numbers: + if Score.objects.filter(team=Team.objects.get(number=number), judge=judge).count() == 0: + teams.append(Team.objects.get(number=number)) + else: + completed_teams.append((Team.objects.get(number=number), Score.objects.get(team=Team.objects.get(number=number), judge=judge))) + + context = { + 'teams': teams, + 'completed_teams': completed_teams, + 'completed_teams_count': len(completed_teams), + 'teams_count': len(team_numbers) + } + + return render(request, 'innovate/portal.html', context=context) + +@login_required +def feedback(request): + form = ScoreForm() + judge = Judge.objects.get(user=request.user) + if type(judge) != Judge: + print('no team or judge') + return redirect('judges-portal') + + if request.method == 'POST': + form = ScoreForm(request.POST) + if form.is_valid(): + team = Team.objects.get(number=request.POST['team_number']) + if ((team.number % 2 != 0 and judge.isEven) or (team.number % 2 == 0 and not judge.isEven)): + print('judge not approved') + return redirect('judges-portal') + score = form.save(commit=False) + score.judge = judge + score.team = team + score.feedback = form.cleaned_data['feedback'] + score.save() + return redirect('judges-portal') + elif request.GET.get('team'): + team = Team.objects.get(number=request.GET['team']) + if team == None: + print('no team or judge') + elif ((team.number % 2 != 0 and judge.isEven) or (team.number % 2 == 0 and not judge.isEven)): + print('judge not approved') + elif Score.objects.filter(team=team, judge=judge).count() != 0: + print('feedback already exists') + else: + context = { + 'team': team, + 'judge': judge, + 'form': form, + } + return render(request, 'innovate/feedback.html', context=context) + return redirect('judges-portal') + diff --git a/launchx/templates/launchx/base.html b/launchx/templates/launchx/base.html index a90b2c5..acb0761 100644 --- a/launchx/templates/launchx/base.html +++ b/launchx/templates/launchx/base.html @@ -27,11 +27,14 @@