Skip to content

Commit

Permalink
Add Poster Score form and Enable linter/poster scores in Contest (#58)
Browse files Browse the repository at this point in the history
* Django template `now` can be used for printing only
and comparison is buggy
* Add Poster score form
* Add options to enable linter/ta to contest model
* Add migration file to fix tests failing
* Display poster and linter score if only enabled
* Do not compute linter score if disabled
* Use pycodestyle instead of pylint for lint checking, update final score after poster updates score
* Fix update of leaderboard
* Display error dialog if error occurred
* Fixed negative ta score issue
  • Loading branch information
prateekkumarweb authored and vishwakftw committed May 1, 2019
1 parent 904bba3 commit cd1256b
Show file tree
Hide file tree
Showing 12 changed files with 222 additions and 92 deletions.
21 changes: 20 additions & 1 deletion judge/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ class NewContestForm(forms.Form):
is_public = forms.BooleanField(label='Is this contest public?', required=False)
"""Contest is_public property"""

enable_linter_score = forms.BooleanField(label='Enable linter scoring',
required=False, initial=True)
"""Contest enable_linter_score property"""

enable_poster_score = forms.BooleanField(label='Enable poster scoring',
required=False, initial=True)
"""Contest enable_poster_score property"""

def clean(self):
cleaned_data = super().clean()
cont_start = cleaned_data.get("contest_start")
Expand All @@ -84,7 +92,7 @@ class AddPersonToContestForm(forms.Form):
emails = MultiEmailField(
label='Emails',
widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
help_text='Enter emails seperated using commas')
help_text='Enter emails separated using commas')
"""Email ID of the person"""


Expand Down Expand Up @@ -266,3 +274,14 @@ class NewCommentForm(forms.Form):
comment = forms.CharField(label='Comment', required=True, widget=forms.Textarea(
attrs={'class': 'form-control', 'rows': 2}))
"""Comment content"""


class AddPosterScoreForm(forms.Form):
"""
Form to add poster score for a submission
"""

score = forms.IntegerField(
label='Poster Score', widget=forms.NumberInput(attrs={'class': 'form-control'}),
initial=0)
"""Score field"""
85 changes: 80 additions & 5 deletions judge/handler.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import pickle

from re import compile
from io import StringIO
Expand All @@ -8,15 +9,15 @@
from datetime import timedelta, datetime
from traceback import print_exc
from csv import writer as csvwriter
from pickle import load as pickle_load
from typing import Tuple, Optional, Dict, Any, List
from django.utils import timezone

from . import models


def process_contest(name: str, start_datetime: datetime, soft_end_datetime: datetime,
hard_end_datetime: datetime, penalty: float, public: bool) -> Tuple[bool, str]:
hard_end_datetime: datetime, penalty: float, public: bool,
enable_linter_score: bool, enable_poster_score: bool) -> Tuple[bool, str]:
"""
Process a New Contest
Only penalty can be None in which case Penalty will be set to 0
Expand All @@ -28,7 +29,9 @@ def process_contest(name: str, start_datetime: datetime, soft_end_datetime: date
c = models.Contest(name=name, start_datetime=start_datetime,
soft_end_datetime=soft_end_datetime,
hard_end_datetime=hard_end_datetime,
penalty=penalty, public=public)
penalty=penalty, public=public,
enable_linter_score=enable_linter_score,
enable_poster_score=enable_poster_score)
c.save()
# Successfully added to Database
return (True, str(c.pk))
Expand Down Expand Up @@ -320,6 +323,40 @@ def process_solution(problem_id: str, participant: str, file_type,
return (True, None)


def update_poster_score(submission_id: str, new_score: int):
"""
Updates the poster score (tascore) for a submission.
Input pk of submission and the new poster score.
Leaderboard is updated if the new score for the person-problem pair has changed.
Returns:
(True, None) or (False, Exception string)
"""
try:
submission = models.Submission.objects.get(pk=submission_id)
submission.final_score -= submission.ta_score
submission.ta_score = new_score
submission.final_score += submission.ta_score
submission.save()

highest_scoring_submission = models.Submission.objects.filter(
problem=submission.problem.pk, participant=submission.participant.pk).\
order_by('-final_score').first()
ppf, _ = models.PersonProblemFinalScore.objects.get_or_create(
person=submission.participant, problem=submission.problem)
old_highscore = ppf.score
ppf.score = highest_scoring_submission.final_score
ppf.save()

if old_highscore != ppf.score:
# Update the leaderboard only if submission imporved the final score
update_leaderboard(submission.problem.contest.pk,
submission.participant.email)
return (True, None)
except Exception as e:
return (False, e.__str__())


def add_person_to_contest(person: str, contest: str,
permission: bool) -> Tuple[bool, Optional[str]]:
"""
Expand Down Expand Up @@ -448,7 +485,7 @@ def get_personcontest_permission(person: Optional[str], contest: int) -> Optiona
return None
p = models.Person.objects.get(email=person)
c = models.Contest.objects.get(pk=contest)
# partcipant and Current datetime < C.date_time -> None
# participant and Current datetime < C.date_time -> None
try:
cp = models.ContestPerson.objects.get(person=p, contest=c)
if cp.role is False and curr < c.start_datetime:
Expand Down Expand Up @@ -715,13 +752,51 @@ def get_leaderboard(contest: int) -> Tuple[bool, Any]:
return (False, 'Leaderboard not yet initialized for this contest.')
try:
with open(leaderboard_path, 'rb') as f:
data = pickle_load(f)
data = pickle.load(f)
return (True, data)
except Exception as e:
print_exc()
return (False, e.__str__())


def update_leaderboard(contest: int, person: str):
"""
Updates the leaderboard for the passed contest for the rank of the person
Pass pk for contest and email for person
Only call this function when some submission for some problem of the contest
has scored more than its previous submission.
Remember to call this function whenever PersonProblemFinalScore is updated.
Returns True if update was successfull else returns False
"""

os.makedirs(os.path.join('content', 'contests'), exist_ok=True)
pickle_path = os.path.join('content', 'contests', str(contest) + '.lb')

status, score = get_personcontest_score(person, contest)

if status:
if not os.path.exists(pickle_path):
with open(pickle_path, 'wb') as f:
data = [[person, score]]
pickle.dump(data, f)
return True
else:
with open(pickle_path, 'rb') as f:
data = pickle.load(f)
with open(pickle_path, 'wb') as f:
for i in range(len(data)):
if data[i][0] == person:
data[i][1] = score
break
else:
data.append([person, score])
data = sorted(data, key=lambda x: x[1])
pickle.dump(data, f)
return True
else:
return False


def process_comment(problem: str, person: str, commenter: str,
timestamp, comment: str) -> Tuple[bool, Optional[str]]:
"""
Expand Down
48 changes: 0 additions & 48 deletions judge/leaderboard.py

This file was deleted.

8 changes: 5 additions & 3 deletions judge/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 2.2 on 2019-04-27 16:18
# Generated by Django 2.2 on 2019-05-01 06:08

import datetime
from django.db import migrations, models
Expand Down Expand Up @@ -26,7 +26,9 @@ class Migration(migrations.Migration):
('soft_end_datetime', models.DateTimeField()),
('hard_end_datetime', models.DateTimeField()),
('penalty', models.FloatField(default=0.0)),
('public', models.BooleanField()),
('public', models.BooleanField(default=False)),
('enable_linter_score', models.BooleanField(default=True)),
('enable_poster_score', models.BooleanField(default=True)),
],
),
migrations.CreateModel(
Expand Down Expand Up @@ -65,8 +67,8 @@ class Migration(migrations.Migration):
('timestamp', models.DateTimeField()),
('judge_score', models.PositiveSmallIntegerField(default=0)),
('ta_score', models.PositiveSmallIntegerField(default=0)),
('final_score', models.FloatField(default=0.0)),
('linter_score', models.FloatField(default=0.0)),
('final_score', models.FloatField(default=0.0)),
('participant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='judge.Person')),
('problem', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='judge.Problem')),
],
Expand Down
10 changes: 8 additions & 2 deletions judge/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,15 @@ class Contest(models.Model):
penalty = models.FloatField(default=0.0)
"""Penalty for late-submission"""

public = models.BooleanField()
public = models.BooleanField(default=False)
"""Is the contest public?"""

enable_linter_score = models.BooleanField(default=True)
"""Enable linter scoring"""

enable_poster_score = models.BooleanField(default=True)
"""Enable poster scoring"""

def __str__(self):
return self.name

Expand Down Expand Up @@ -179,7 +185,7 @@ class Submission(models.Model):
judge_score = models.PositiveSmallIntegerField(default=0)
"""Judge score"""

ta_score = models.PositiveSmallIntegerField(default=0)
ta_score = models.SmallIntegerField(default=0)
"""TA score"""

linter_score = models.FloatField(default=0.0)
Expand Down
2 changes: 1 addition & 1 deletion judge/templates/judge/contest_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ <h2 class="d-inline">{{ contest.name }}</h2>
<div class="row">
<div class="col-12">
{% if type == 'Poster' %}
{% if now < contest.start_datetime %}<a class="btn btn-primary"
{% if curr_time < contest.start_datetime %}<a class="btn btn-primary"
href="{% url 'judge:new_problem' contest.pk %}">Add Problem</a>{% endif %}
<a class="btn btn-primary" href="{% url 'judge:contest_scores_csv' contest.pk %}">Download Scores</a>
{% endif %}
Expand Down
8 changes: 4 additions & 4 deletions judge/templates/judge/problem_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ <h4 class="d-inline mx-2">{{ problem.name }}</h4>
<a href="{% url 'judge:edit_problem' problem.pk %}" class="btn btn-primary">
<i class="fas fa-edit"></i>
</a>
{% if now < problem.contest.start_datetime %}
{% if curr_time < problem.contest.start_datetime %}
<form class="d-inline" action="{% url 'judge:delete_problem' problem.pk %}" method="POST">
{% csrf_token %}
<button type="submit" class="btn btn-danger"><i class="fas fa-trash"></i></button>
Expand Down Expand Up @@ -169,7 +169,7 @@ <h4>Output Format</h4>
<h4>Public test cases</h4>
{% for test in public_tests %}
<h5 class="d-inline">Test Case {{ forloop.counter }}</h5>
{% if type == 'Poster' and now < problem.contest.start_datetime %}
{% if type == 'Poster' and curr_time < problem.contest.start_datetime %}
<div class="d-inline">
<form class="d-inline" action="{% url 'judge:delete_testcase' problem.pk test.2 %}" method="POST">
{% csrf_token %}
Expand Down Expand Up @@ -200,7 +200,7 @@ <h6>Output</h6>
<h4>Private test cases</h4>
{% for test in private_tests %}
<h5 class="d-inline">Test Case {{ forloop.counter }}</h5>
{% if now < problem.contest.start_datetime %}
{% if curr_time < problem.contest.start_datetime %}
<div class="d-inline">
<form class="d-inline" action="{% url 'judge:delete_testcase' problem.pk test.2 %}" method="POST">
{% csrf_token %}
Expand Down Expand Up @@ -304,7 +304,7 @@ <h3>Submit Solution</h3>

{% if type == 'Poster' %}
<div class="row">
{% if now < problem.contest.start_datetime %}
{% if curr_time < problem.contest.start_datetime %}
<div class="col-12 my-4">
<div class="card">
<div class="card-header">
Expand Down
5 changes: 0 additions & 5 deletions judge/templates/judge/problem_submissions.html
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,6 @@ <h6 class="d-inline-block">
</table>
</div>
</div>
<div class="row">
<div class="col-12 my-4">

</div>
</div>
{% endfor %}

{% endblock %}
Loading

0 comments on commit cd1256b

Please sign in to comment.