Add final ranking for superuser (ioi16 only)
This commit is contained in:
parent
6635c3ee99
commit
309b6298e2
16 changed files with 424 additions and 336 deletions
|
@ -523,6 +523,11 @@ urlpatterns = [
|
|||
contests.ContestRanking.as_view(),
|
||||
name="contest_ranking",
|
||||
),
|
||||
url(
|
||||
r"^/final_ranking/$",
|
||||
contests.ContestFinalRanking.as_view(),
|
||||
name="contest_final_ranking",
|
||||
),
|
||||
url(
|
||||
r"^/ranking/ajax$",
|
||||
contests.contest_ranking_ajax,
|
||||
|
|
|
@ -112,7 +112,7 @@ class AtCoderContestFormat(DefaultContestFormat):
|
|||
participation.format_data = format_data
|
||||
participation.save()
|
||||
|
||||
def display_user_problem(self, participation, contest_problem):
|
||||
def display_user_problem(self, participation, contest_problem, show_final=False):
|
||||
format_data = (participation.format_data or {}).get(str(contest_problem.id))
|
||||
if format_data:
|
||||
penalty = (
|
||||
|
|
|
@ -48,7 +48,7 @@ class BaseContestFormat(metaclass=ABCMeta):
|
|||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
def display_user_problem(self, participation, contest_problem):
|
||||
def display_user_problem(self, participation, contest_problem, show_final):
|
||||
"""
|
||||
Returns the HTML fragment to show a user's performance on an individual problem. This is expected to use
|
||||
information from the format_data field instead of computing it from scratch.
|
||||
|
@ -60,7 +60,7 @@ class BaseContestFormat(metaclass=ABCMeta):
|
|||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
def display_participation_result(self, participation):
|
||||
def display_participation_result(self, participation, show_final):
|
||||
"""
|
||||
Returns the HTML fragment to show a user's performance on the whole contest. This is expected to use
|
||||
information from the format_data field instead of computing it from scratch.
|
||||
|
|
|
@ -61,7 +61,7 @@ class DefaultContestFormat(BaseContestFormat):
|
|||
participation.format_data = format_data
|
||||
participation.save()
|
||||
|
||||
def display_user_problem(self, participation, contest_problem):
|
||||
def display_user_problem(self, participation, contest_problem, show_final=False):
|
||||
format_data = (participation.format_data or {}).get(str(contest_problem.id))
|
||||
if format_data:
|
||||
return format_html(
|
||||
|
@ -94,7 +94,7 @@ class DefaultContestFormat(BaseContestFormat):
|
|||
else:
|
||||
return mark_safe('<td class="problem-score-col"></td>')
|
||||
|
||||
def display_participation_result(self, participation):
|
||||
def display_participation_result(self, participation, show_final=False):
|
||||
return format_html(
|
||||
'<td class="user-points">{points}<div class="solving-time">{cumtime}</div></td>',
|
||||
points=floatformat(participation.score, -self.contest.points_precision),
|
||||
|
|
|
@ -122,7 +122,7 @@ class ECOOContestFormat(DefaultContestFormat):
|
|||
participation.format_data = format_data
|
||||
participation.save()
|
||||
|
||||
def display_user_problem(self, participation, contest_problem):
|
||||
def display_user_problem(self, participation, contest_problem, show_final=False):
|
||||
format_data = (participation.format_data or {}).get(str(contest_problem.id))
|
||||
if format_data:
|
||||
bonus = (
|
||||
|
@ -162,7 +162,7 @@ class ECOOContestFormat(DefaultContestFormat):
|
|||
else:
|
||||
return mark_safe("<td></td>")
|
||||
|
||||
def display_participation_result(self, participation):
|
||||
def display_participation_result(self, participation, show_final=False):
|
||||
return format_html(
|
||||
'<td class="user-points">{points}<div class="solving-time">{cumtime}</div></td>',
|
||||
points=floatformat(participation.score),
|
||||
|
|
|
@ -114,7 +114,7 @@ class ICPCContestFormat(DefaultContestFormat):
|
|||
participation.format_data = format_data
|
||||
participation.save()
|
||||
|
||||
def display_user_problem(self, participation, contest_problem):
|
||||
def display_user_problem(self, participation, contest_problem, show_final=False):
|
||||
format_data = (participation.format_data or {}).get(str(contest_problem.id))
|
||||
if format_data:
|
||||
penalty = (
|
||||
|
|
|
@ -86,7 +86,12 @@ class IOIContestFormat(DefaultContestFormat):
|
|||
participation.format_data = format_data
|
||||
participation.save()
|
||||
|
||||
def display_user_problem(self, participation, contest_problem):
|
||||
def display_user_problem(self, participation, contest_problem, show_final=False):
|
||||
if show_final:
|
||||
format_data = (participation.format_data_final or {}).get(
|
||||
str(contest_problem.id)
|
||||
)
|
||||
else:
|
||||
format_data = (participation.format_data or {}).get(str(contest_problem.id))
|
||||
if format_data:
|
||||
return format_html(
|
||||
|
@ -121,11 +126,17 @@ class IOIContestFormat(DefaultContestFormat):
|
|||
else:
|
||||
return mark_safe('<td class="problem-score-col"></td>')
|
||||
|
||||
def display_participation_result(self, participation):
|
||||
def display_participation_result(self, participation, show_final=False):
|
||||
if show_final:
|
||||
score = participation.score_final
|
||||
cumtime = participation.cumtime_final
|
||||
else:
|
||||
score = participation.score
|
||||
cumtime = participation.cumtime
|
||||
return format_html(
|
||||
'<td class="user-points">{points}<div class="solving-time">{cumtime}</div></td>',
|
||||
points=floatformat(participation.score, -self.contest.points_precision),
|
||||
cumtime=nice_repr(timedelta(seconds=participation.cumtime), "noday")
|
||||
points=floatformat(score, -self.contest.points_precision),
|
||||
cumtime=nice_repr(timedelta(seconds=cumtime), "noday")
|
||||
if self.config["cumtime"]
|
||||
else "",
|
||||
)
|
||||
|
|
|
@ -100,11 +100,10 @@ class NewIOIContestFormat(IOIContestFormat):
|
|||
return cursor.fetchall()
|
||||
|
||||
def update_participation(self, participation):
|
||||
cumtime = 0
|
||||
score = 0
|
||||
format_data = {}
|
||||
frozen_subtasks = self.get_frozen_subtasks()
|
||||
|
||||
def calculate_format_data(participation, include_frozen):
|
||||
format_data = {}
|
||||
for (
|
||||
problem_id,
|
||||
problem_points,
|
||||
|
@ -113,7 +112,7 @@ class NewIOIContestFormat(IOIContestFormat):
|
|||
total_subtask_points,
|
||||
subtask,
|
||||
sub_id,
|
||||
) in self.get_results_by_subtask(participation):
|
||||
) in self.get_results_by_subtask(participation, include_frozen):
|
||||
problem_id = str(problem_id)
|
||||
time = from_database_time(time)
|
||||
if self.config["cumtime"]:
|
||||
|
@ -122,13 +121,27 @@ class NewIOIContestFormat(IOIContestFormat):
|
|||
dt = 0
|
||||
|
||||
if format_data.get(problem_id) is None:
|
||||
format_data[problem_id] = {"points": 0, "time": 0, "total_points": 0}
|
||||
if subtask not in frozen_subtasks.get(problem_id, set()):
|
||||
format_data[problem_id] = {
|
||||
"points": 0,
|
||||
"time": 0,
|
||||
"total_points": 0,
|
||||
}
|
||||
if (
|
||||
subtask not in frozen_subtasks.get(problem_id, set())
|
||||
or include_frozen
|
||||
):
|
||||
format_data[problem_id]["points"] += subtask_points
|
||||
format_data[problem_id]["total_points"] += total_subtask_points
|
||||
format_data[problem_id]["time"] = max(dt, format_data[problem_id]["time"])
|
||||
format_data[problem_id]["time"] = max(
|
||||
dt, format_data[problem_id]["time"]
|
||||
)
|
||||
format_data[problem_id]["problem_points"] = problem_points
|
||||
|
||||
return format_data
|
||||
|
||||
def recalculate_results(format_data):
|
||||
cumtime = 0
|
||||
score = 0
|
||||
for problem_data in format_data.values():
|
||||
if not problem_data["total_points"]:
|
||||
continue
|
||||
|
@ -141,10 +154,19 @@ class NewIOIContestFormat(IOIContestFormat):
|
|||
if self.config["cumtime"] and problem_data["points"]:
|
||||
cumtime += penalty
|
||||
score += problem_data["points"]
|
||||
return score, cumtime
|
||||
|
||||
format_data = calculate_format_data(participation, False)
|
||||
score, cumtime = recalculate_results(format_data)
|
||||
self.handle_frozen_state(participation, format_data)
|
||||
participation.cumtime = max(cumtime, 0)
|
||||
participation.score = round(score, self.contest.points_precision)
|
||||
participation.tiebreaker = 0
|
||||
participation.format_data = format_data
|
||||
|
||||
format_data_final = calculate_format_data(participation, True)
|
||||
score_final, cumtime_final = recalculate_results(format_data_final)
|
||||
participation.cumtime_final = max(cumtime_final, 0)
|
||||
participation.score_final = round(score_final, self.contest.points_precision)
|
||||
participation.format_data_final = format_data_final
|
||||
participation.save()
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 3.2.16 on 2022-12-28 18:36
|
||||
|
||||
from django.db import migrations
|
||||
import jsonfield.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("judge", "0141_contestproblem_frozen_subtasks"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="contestparticipation",
|
||||
name="format_data_final",
|
||||
field=jsonfield.fields.JSONField(
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name="same as format_data, but including frozen results",
|
||||
),
|
||||
),
|
||||
]
|
25
judge/migrations/0143_auto_20221229_0153.py
Normal file
25
judge/migrations/0143_auto_20221229_0153.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
# Generated by Django 3.2.16 on 2022-12-28 18:53
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("judge", "0142_contestparticipation_format_data_final"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="contestparticipation",
|
||||
name="cumtime_final",
|
||||
field=models.PositiveIntegerField(
|
||||
default=0, verbose_name="final cumulative time"
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="contestparticipation",
|
||||
name="score_final",
|
||||
field=models.FloatField(default=0, verbose_name="final score"),
|
||||
),
|
||||
]
|
|
@ -650,6 +650,15 @@ class ContestParticipation(models.Model):
|
|||
format_data = JSONField(
|
||||
verbose_name=_("contest format specific data"), null=True, blank=True
|
||||
)
|
||||
format_data_final = JSONField(
|
||||
verbose_name=_("same as format_data, but including frozen results"),
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
score_final = models.FloatField(verbose_name=_("final score"), default=0)
|
||||
cumtime_final = models.PositiveIntegerField(
|
||||
verbose_name=_("final cumulative time"), default=0
|
||||
)
|
||||
|
||||
def recompute_results(self):
|
||||
with transaction.atomic():
|
||||
|
|
|
@ -877,47 +877,72 @@ ContestRankingProfile = namedtuple(
|
|||
BestSolutionData = namedtuple("BestSolutionData", "code points time state is_pretested")
|
||||
|
||||
|
||||
def make_contest_ranking_profile(contest, participation, contest_problems):
|
||||
def make_contest_ranking_profile(
|
||||
contest, participation, contest_problems, show_final=False
|
||||
):
|
||||
if not show_final:
|
||||
points = participation.score
|
||||
cumtime = participation.cumtime
|
||||
else:
|
||||
points = participation.score_final
|
||||
cumtime = participation.cumtime_final
|
||||
|
||||
user = participation.user
|
||||
return ContestRankingProfile(
|
||||
id=user.id,
|
||||
user=user.user,
|
||||
css_class=user.css_class,
|
||||
username=user.username,
|
||||
points=participation.score,
|
||||
cumtime=participation.cumtime,
|
||||
points=points,
|
||||
cumtime=cumtime,
|
||||
tiebreaker=participation.tiebreaker,
|
||||
organization=user.organization,
|
||||
participation_rating=participation.rating.rating
|
||||
if hasattr(participation, "rating")
|
||||
else None,
|
||||
problem_cells=[
|
||||
contest.format.display_user_problem(participation, contest_problem)
|
||||
contest.format.display_user_problem(
|
||||
participation, contest_problem, show_final
|
||||
)
|
||||
for contest_problem in contest_problems
|
||||
],
|
||||
result_cell=contest.format.display_participation_result(participation),
|
||||
result_cell=contest.format.display_participation_result(
|
||||
participation, show_final
|
||||
),
|
||||
participation=participation,
|
||||
)
|
||||
|
||||
|
||||
def base_contest_ranking_list(contest, problems, queryset):
|
||||
def base_contest_ranking_list(contest, problems, queryset, show_final=False):
|
||||
return [
|
||||
make_contest_ranking_profile(contest, participation, problems)
|
||||
make_contest_ranking_profile(contest, participation, problems, show_final)
|
||||
for participation in queryset.select_related("user__user", "rating").defer(
|
||||
"user__about", "user__organizations__about"
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def contest_ranking_list(contest, problems, queryset=None):
|
||||
def contest_ranking_list(contest, problems, queryset=None, show_final=False):
|
||||
if not queryset:
|
||||
queryset = contest.users.filter(virtual=0)
|
||||
|
||||
if not show_final:
|
||||
return base_contest_ranking_list(
|
||||
contest,
|
||||
problems,
|
||||
queryset.prefetch_related("user__organizations")
|
||||
.extra(select={"round_score": "round(score, 6)"})
|
||||
.order_by("is_disqualified", "-round_score", "cumtime", "tiebreaker"),
|
||||
show_final,
|
||||
)
|
||||
else:
|
||||
return base_contest_ranking_list(
|
||||
contest,
|
||||
problems,
|
||||
queryset.prefetch_related("user__organizations")
|
||||
.extra(select={"round_score": "round(score_final, 6)"})
|
||||
.order_by("is_disqualified", "-round_score", "cumtime_final", "tiebreaker"),
|
||||
show_final,
|
||||
)
|
||||
|
||||
|
||||
|
@ -928,6 +953,7 @@ def get_contest_ranking_list(
|
|||
ranking_list=contest_ranking_list,
|
||||
show_current_virtual=False,
|
||||
ranker=ranker,
|
||||
show_final=False,
|
||||
):
|
||||
problems = list(
|
||||
contest.contest_problems.select_related("problem")
|
||||
|
@ -936,7 +962,7 @@ def get_contest_ranking_list(
|
|||
)
|
||||
|
||||
users = ranker(
|
||||
ranking_list(contest, problems),
|
||||
ranking_list(contest, problems, show_final=show_final),
|
||||
key=attrgetter("points", "cumtime", "tiebreaker"),
|
||||
)
|
||||
|
||||
|
@ -955,12 +981,17 @@ def get_contest_ranking_list(
|
|||
|
||||
def contest_ranking_ajax(request, contest, participation=None):
|
||||
contest, exists = _find_contest(request, contest)
|
||||
show_final = bool(request.GET.get("final", False))
|
||||
if not exists:
|
||||
return HttpResponseBadRequest("Invalid contest", content_type="text/plain")
|
||||
|
||||
if not contest.can_see_full_scoreboard(request.user):
|
||||
raise Http404()
|
||||
|
||||
if show_final:
|
||||
if not request.user.is_superuser or contest.format_name != "ioi16":
|
||||
raise Http404()
|
||||
|
||||
queryset = contest.users.filter(virtual__gte=0)
|
||||
if request.GET.get("friend") == "true" and request.profile:
|
||||
friends = list(request.profile.get_friends())
|
||||
|
@ -973,6 +1004,7 @@ def contest_ranking_ajax(request, contest, participation=None):
|
|||
contest,
|
||||
participation,
|
||||
ranking_list=partial(contest_ranking_list, queryset=queryset),
|
||||
show_final=show_final,
|
||||
)
|
||||
return render(
|
||||
request,
|
||||
|
@ -1039,6 +1071,18 @@ class ContestRanking(ContestRankingBase):
|
|||
return context
|
||||
|
||||
|
||||
class ContestFinalRanking(LoginRequiredMixin, ContestRanking):
|
||||
page_type = "final_ranking"
|
||||
|
||||
def get_ranking_list(self):
|
||||
if not self.request.user.is_superuser:
|
||||
raise Http404()
|
||||
if self.object.format_name != "ioi16":
|
||||
raise Http404()
|
||||
|
||||
return get_contest_ranking_list(self.request, self.object, show_final=True)
|
||||
|
||||
|
||||
class ContestParticipationList(LoginRequiredMixin, ContestRankingBase):
|
||||
page_type = "participation"
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -24,6 +24,7 @@
|
|||
{% endif %}
|
||||
{% if request.user.is_superuser and can_use_resolver %}
|
||||
{{ make_tab_item('resolver', 'fa fa-check', url('resolver', contest.key), _('Resolver')) }}
|
||||
{{ make_tab_item('final_ranking', 'fa fa-bar-chart', url('contest_final_ranking', contest.key), _('Final rankings')) }}
|
||||
{% endif %}
|
||||
{% if can_edit %}
|
||||
{% if perms.judge.moss_contest and has_moss_api_key %}
|
||||
|
|
|
@ -118,8 +118,12 @@
|
|||
var friend = $('#show-friends-checkbox').is(':checked');
|
||||
var virtual = $('#show-virtual-checkbox').is(':checked');
|
||||
$('#loading-gif').show();
|
||||
var url = `{{url('contest_ranking_ajax', contest.key)}}?friend=${friend}&virtual=${virtual}`;
|
||||
{% if page_type == 'final_ranking' %}
|
||||
url += "&final=true";
|
||||
{% endif %}
|
||||
$.get({
|
||||
url: `{{url('contest_ranking_ajax', contest.key)}}?friend=${friend}&virtual=${virtual}`,
|
||||
url: url,
|
||||
success: function(HTML) {
|
||||
$('#users-table').html(HTML);
|
||||
highlightFirstSolve();
|
||||
|
@ -199,7 +203,8 @@
|
|||
$('#show-virtual-label').hide();
|
||||
{% else %}
|
||||
{% if request.in_contest %}
|
||||
setInterval(update_ranking, 60 * 1000);
|
||||
clearInterval(window.rankingInterval);
|
||||
window.rankingInterval = setInterval(update_ranking, 60 * 1000);
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
});
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
{% if page_type == 'ranking' %}
|
||||
{% if page_type == 'ranking' or page_type == 'final_ranking' %}
|
||||
<script type="text/javascript">
|
||||
$.fn.ignore = function(sel) {
|
||||
return this.clone().find(sel || '>*').remove().end();
|
||||
|
|
Loading…
Reference in a new issue