feat: added judges section

This commit is contained in:
Rushil Umaretiya 2021-02-19 14:47:07 -05:00
parent 3a97b74639
commit 55fc02219f
No known key found for this signature in database
GPG Key ID: 4E8FAF9C926AF959
18 changed files with 427 additions and 15 deletions

View File

@ -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

View File

@ -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),
]

View File

@ -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):

View File

@ -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
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']

View File

@ -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',
},
),
]

View File

@ -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,
),
]

View File

@ -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',
},
),
]

View File

@ -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)

View File

@ -0,0 +1,6 @@
h3 small {
font-size: .66em;
}
.text-success {
color: rgb(25, 207, 34) !important
}

View File

@ -0,0 +1,121 @@
{% extends 'launchx/base.html' %}
{% load launchx_extras %}
{% block content %}
<h1>Feedback for Team {{ team.number }}: {{ team.name }}</h1>
<div class="d-flex">
<form method="POST">
{% csrf_token %}
{{ form.non_field_errors }}
{{ form.errors }}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
<input type="hidden" id="team_number" name="team_number" value="{{ team.number }}">
<div class="container">
<h2><span class="border-bottom border-3">Product</span></h2>
<div class="col">
<div class="row">
<h3>Innovation</h3>
</div>
<div class="row">
<div class="col-6">
<p>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) ? </p>
</div>
<div class="col-6">
{{ form.innovation }}/30
</div>
</div>
</div>
<div class="col">
<div class="row">
<h3>Marketability / Need</h3>
</div>
<div class="row">
<div class="col-6">
<p>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) ?</p>
</div>
<div class="col-6">
{{ form.need }}/35
</div>
</div>
</div>
<div class="col">
<div class="row">
<h3>Finances</h3>
</div>
<div class="row">
<div class="col-6">
<p>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)?</p>
</div>
<div class="col-6">
{{ form.finances }}/25
</div>
</div>
</div>
<div class="col">
<div class="row">
<h3>Creativity / Professionalism </h3>
</div>
<div class="row">
<div class="col-6">
<p>Is their presentation creative AND professional (5 pts each) ?</p>
</div>
<div class="col-6">
{{ form.creativity }}/10
</div>
</div>
</div>
</div>
</div>
<div class="container mt-md-3">
<h2><span class="border-bottom border-3">Delivery</span></h2>
<div class="row">
<div class="col-6">
<h3>Q&A Session</h3>
</div>
<div class="col-6">
{{ form.qna }}/25
</div>
</div>
<div class="row">
<div class="col-6">
<h3>Speaking Quality </h3>
</div>
<div class="col-6">
{{ form.speaking }}/10
</div>
</div>
<div class="row">
<div class="col-6">
<h3>Persuasiveness</h3>
</div>
<div class="col-6">
{{ form.persuasiveness }}/10
</div>
</div>
<div class="row">
<div class="col-6">
<h3>Professionalism</h3>
</div>
<div class="col-6">
{{ form.professionalism }}/5
</div>
</div>
</div>
<div class="container mt-md-3">
<div class="col">
<div class="row">
<h2>Feedback</h2>
</div>
<div class="row">
{{ form.feedback }}
</div>
</div>
</div>
<div class="form-group mt-md-3">
<button class="btn btn-light btn-block" type="submit">Submit Score</button>
</div>
</form>
</div>
{% endblock %}

View File

@ -0,0 +1,31 @@
{% extends 'launchx/base.html' %}
{% load launchx_extras %}
{% block styles %}
<link rel="stylesheet" href="{% static 'innovate/portal.css' %}">
{% endblock %}
{% block content %}
<div class="container mt-md-3 w-75">
<h1>Welcome Judge {{ request.user.last_name }},</h1>
<h5>Thanks for judging at InnovateTJ, now time to score!</h5>
<hr>
</div>
<div class="containter text-center">
<h4>{{ completed_teams_count }} out of {{ teams_count }} teams scored</h4>
</div>
<div class="container w-50">
{% for team in teams %}
<div class="border border-2 border-secondary rounded-3 mt-md-4 p-md-4">
<h3>Team {{ team.number }}: {{ team.name }} <small class="text-secondary">Unscored</small></h3>
<a href="{% url '/innovate/judges/feedback' %}?team={{ team.number | urlencode }}"><button class="btn btn-light w-100">Score Team {{ team.number }}</button></a>
</div>
{% endfor %}
{% for team, score in completed_teams %}
<div class="border border-2 border-secondary rounded-3 mt-md-4 p-md-2">
<h3>Team {{ team.number }}: {{ team.name }} <small><span class="text-success">Scored</span> {{ score.get_total_score }}/150</small></h3>
</div>
{% endfor %}
</div>
{% endblock %}

View File

@ -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')
]

View File

@ -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')

View File

@ -27,11 +27,14 @@
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarToggle">
<div class="navbar-nav mr-auto">
<div class="navbar-nav me-auto mr-auto">
<a class="nav-item nav-link {% if request.resolver_match.url_name == 'landing' %}active{% endif %}" href="{% url '/' %}">Home</a>
<a class="nav-item nav-link {% if request.resolver_match.url_name == 'innovate-home' %}active{% endif %}" href="{% url '/innovate' %}">InnovateTJ</a>
<a class="nav-item nav-link {% if request.resolver_match.url_name == 'calendar' %}active{% endif %}" href="{% url '/calendar' %}">Calendar</a>
<a class="nav-item nav-link {% if request.resolver_match.url_name == 'officers' %}active{% endif %}" href="{% url '/officers' %}">Officers</a>
{% if user.is_authenticated %}
<a class="nav-item nav-link {% if request.resolver_match.url_name == 'judges-portal' %}active{% endif %}" href="{% url '/innovate/judges' %}">Judging Portal</a>
{% endif %}
</div>
<!-- Navbar Right Side -->
<div class="navbar-nav">
@ -57,3 +60,5 @@
{% block scripts %}{% endblock %}
</body>
</html>

View File

@ -0,0 +1,3 @@
form {
color: white
}

View File

@ -1,4 +1,18 @@
{% extends 'users/base.html' %}
{% load crispy_forms_tags %}
{% load launchx_extras %}
{% block styles %}
<link rel="stylesheet" type="text/css" href="{% static 'users/login.css' %}">
{% endblock %}
{% block content %}
<h1 style="color: white">Login page also in development</h1>
{% endblock %}
<div class="d-flex flex-column min-vh-100 justify-content-center align-items-center">
<form method="POST">
{% csrf_token %}
<legend class="">Judges Log In</legend>
{{ form|crispy }}
<div class="form-group mt-3">
<button class="btn btn-light btn-block" type="submit">Login</button>
</div>
</form>
</div>
{% endblock content %}

View File

@ -0,0 +1,6 @@
{% extends 'launchx/base.html' %}
{% load launchx_extras %}
{% block content %}
<h1 style="color: white">You've logged out!</h1>
<a href="{% url '/' %}"><button class="btn btn-light">Back</button></a>
{% endblock %}

View File

@ -1,5 +1,15 @@
from django.shortcuts import render
from django.contrib.auth import login as auth_login
from django.contrib.auth import logout as auth_logout
from django.contrib.auth.decorators import login_required
# Create your views here.
def login(request):
return render(request, 'users/login.html')
return render(request, 'users/login.html')
@login_required
def logout(request):
auth_logout(request)
return render(request, 'users/logout.html')