GP Ranking (#90)

This commit is contained in:
Phuoc Anh Kha Le 2023-10-06 03:54:37 -05:00 committed by GitHub
parent 9decd11218
commit b4c1620497
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 223 additions and 1 deletions

View file

@ -517,6 +517,11 @@ urlpatterns = [
), ),
), ),
url(r"^contests/", paged_list_view(contests.ContestList, "contest_list")), url(r"^contests/", paged_list_view(contests.ContestList, "contest_list")),
url(
r"^contests/summary/(?P<key>\w+)$",
contests.contests_summary_view,
name="contests_summary",
),
url(r"^course/", paged_list_view(course.CourseList, "course_list")), url(r"^course/", paged_list_view(course.CourseList, "course_list")),
url( url(
r"^contests/(?P<year>\d+)/(?P<month>\d+)/$", r"^contests/(?P<year>\d+)/(?P<month>\d+)/$",

View file

@ -3,7 +3,12 @@ from django.contrib.admin.models import LogEntry
from django.contrib.auth.models import User from django.contrib.auth.models import User
from judge.admin.comments import CommentAdmin from judge.admin.comments import CommentAdmin
from judge.admin.contest import ContestAdmin, ContestParticipationAdmin, ContestTagAdmin from judge.admin.contest import (
ContestAdmin,
ContestParticipationAdmin,
ContestTagAdmin,
ContestsSummaryAdmin,
)
from judge.admin.interface import ( from judge.admin.interface import (
BlogPostAdmin, BlogPostAdmin,
LicenseAdmin, LicenseAdmin,
@ -41,6 +46,7 @@ from judge.models import (
Ticket, Ticket,
VolunteerProblemVote, VolunteerProblemVote,
Course, Course,
ContestsSummary,
) )
@ -69,3 +75,4 @@ admin.site.register(VolunteerProblemVote, VolunteerProblemVoteAdmin)
admin.site.register(Course) admin.site.register(Course)
admin.site.unregister(User) admin.site.unregister(User)
admin.site.register(User, UserAdmin) admin.site.register(User, UserAdmin)
admin.site.register(ContestsSummary, ContestsSummaryAdmin)

View file

@ -502,3 +502,19 @@ class ContestParticipationAdmin(admin.ModelAdmin):
show_virtual.short_description = _("virtual") show_virtual.short_description = _("virtual")
show_virtual.admin_order_field = "virtual" show_virtual.admin_order_field = "virtual"
class ContestsSummaryForm(ModelForm):
class Meta:
widgets = {
"contests": AdminHeavySelect2MultipleWidget(
data_view="contest_select2", attrs={"style": "width: 100%"}
),
}
class ContestsSummaryAdmin(admin.ModelAdmin):
fields = ("key", "contests", "scores")
list_display = ("key",)
search_fields = ("key", "contests__key")
form = ContestsSummaryForm

View file

@ -1,6 +1,7 @@
from inspect import signature from inspect import signature
from django.core.cache import cache from django.core.cache import cache
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from django.core.handlers.wsgi import WSGIRequest
import hashlib import hashlib
@ -18,10 +19,14 @@ def cache_wrapper(prefix, timeout=None):
return str(arg)[:MAX_NUM_CHAR] return str(arg)[:MAX_NUM_CHAR]
return str(arg) return str(arg)
def filter_args(args_list):
return [x for x in args_list if not isinstance(x, WSGIRequest)]
def get_key(func, *args, **kwargs): def get_key(func, *args, **kwargs):
args_list = list(args) args_list = list(args)
signature_args = list(signature(func).parameters.keys()) signature_args = list(signature(func).parameters.keys())
args_list += [kwargs.get(k) for k in signature_args[len(args) :]] args_list += [kwargs.get(k) for k in signature_args[len(args) :]]
args_list = filter_args(args_list)
args_list = [arg_to_str(i) for i in args_list] args_list = [arg_to_str(i) for i in args_list]
key = prefix + ":" + ":".join(args_list) key = prefix + ":" + ":".join(args_list)
key = key.replace(" ", "_") key = key.replace(" ", "_")

View file

@ -0,0 +1,30 @@
# Generated by Django 3.2.21 on 2023-10-02 03:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("judge", "0169_public_scoreboard"),
]
operations = [
migrations.CreateModel(
name="ContestsSummary",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("scores", models.JSONField(blank=True, null=True)),
("key", models.CharField(max_length=20, unique=True)),
("contests", models.ManyToManyField(to="judge.Contest")),
],
),
]

View file

@ -16,6 +16,7 @@ from judge.models.contest import (
ContestTag, ContestTag,
Rating, Rating,
ContestProblemClarification, ContestProblemClarification,
ContestsSummary,
) )
from judge.models.interface import BlogPost, MiscConfig, NavigationBar, validate_regex from judge.models.interface import BlogPost, MiscConfig, NavigationBar, validate_regex
from judge.models.message import PrivateMessage, PrivateMessageThread from judge.models.message import PrivateMessage, PrivateMessageThread

View file

@ -33,6 +33,7 @@ __all__ = [
"ContestSubmission", "ContestSubmission",
"Rating", "Rating",
"ContestProblemClarification", "ContestProblemClarification",
"ContestsSummary",
] ]
@ -900,3 +901,24 @@ class ContestProblemClarification(models.Model):
date = models.DateTimeField( date = models.DateTimeField(
verbose_name=_("clarification timestamp"), auto_now_add=True verbose_name=_("clarification timestamp"), auto_now_add=True
) )
class ContestsSummary(models.Model):
contests = models.ManyToManyField(
Contest,
)
scores = models.JSONField(
null=True,
blank=True,
)
key = models.CharField(
max_length=20,
unique=True,
)
class Meta:
verbose_name = _("contests summary")
verbose_name_plural = _("contests summaries")
def __str__(self):
return self.key

View file

@ -27,6 +27,8 @@ from django.db.models import (
Value, Value,
When, When,
) )
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from django.db.models.expressions import CombinedExpression from django.db.models.expressions import CombinedExpression
from django.http import ( from django.http import (
Http404, Http404,
@ -67,6 +69,7 @@ from judge.models import (
Profile, Profile,
Submission, Submission,
ContestProblemClarification, ContestProblemClarification,
ContestsSummary,
) )
from judge.tasks import run_moss from judge.tasks import run_moss
from judge.utils.celery import redirect_to_task_status from judge.utils.celery import redirect_to_task_status
@ -1380,3 +1383,65 @@ def update_contest_mode(request):
old_mode = request.session.get("contest_mode", True) old_mode = request.session.get("contest_mode", True)
request.session["contest_mode"] = not old_mode request.session["contest_mode"] = not old_mode
return HttpResponse() return HttpResponse()
ContestsSummaryData = namedtuple(
"ContestsSummaryData",
"user points point_contests css_class",
)
def contests_summary_view(request, key):
try:
contests_summary = ContestsSummary.objects.get(key=key)
except:
raise Http404()
cache_key = "csv:" + key
context = cache.get(cache_key)
if context:
return render(request, "contest/contests_summary.html", context)
scores_system = contests_summary.scores
contests = contests_summary.contests.all()
total_points = defaultdict(int)
result_per_contest = defaultdict(lambda: [(0, 0)] * len(contests))
user_css_class = {}
for i in range(len(contests)):
contest = contests[i]
users, problems = get_contest_ranking_list(request, contest)
for rank, user in users:
curr_score = 0
if rank - 1 < len(scores_system):
curr_score = scores_system[rank - 1]
total_points[user.user] += curr_score
result_per_contest[user.user][i] = (curr_score, rank)
user_css_class[user.user] = user.css_class
sorted_total_points = [
ContestsSummaryData(
user=user,
points=total_points[user],
point_contests=result_per_contest[user],
css_class=user_css_class[user],
)
for user in total_points
]
sorted_total_points.sort(key=lambda x: x.points, reverse=True)
total_rank = ranker(sorted_total_points)
context = {
"total_rank": list(total_rank),
"title": _("Contests Summary"),
"contests": contests,
}
cache.set(cache_key, context)
return render(request, "contest/contests_summary.html", context)
@receiver([post_save, post_delete], sender=ContestsSummary)
def clear_cache(sender, instance, **kwargs):
cache.delete("csv:" + instance.key)

View file

@ -0,0 +1,71 @@
{% extends "base.html" %}
{% block title_row %}{% endblock %}
{% block title_ruler %}{% endblock %}
{% block media %}
<style>
table {
width: 100%;
border-collapse: collapse;
border: 1px solid #ddd;
font-size: 1em;
}
th, td {
padding: 12px 15px;
border: 1px solid #595656;
padding: 15px;
text-align: center;
}
th {
font-size: 15px;
}
tr:hover {
cursor: pointer;
}
</style>
{% endblock %}
{% block body %}
<table class="table" id="users-table">
<thead>
<tr>
<th>{{_('Rank')}}</th>
<th>{{_('Name')}}</th>
{% for contest in contests %}
<th><a href="{{url('contest_view', contest.key)}}" title="{{contest.name}}">{{ loop.index }}</a></th>
{% endfor %}
<th>{{_('Points')}}</th>
</tr>
</thead>
<tbody>
{% for rank, item in total_rank %}
<tr>
<td>
{{ rank }}
</td>
<td>
<div>
<span class="username {{ item.css_class }} wrapline" href="{{url('user_page', item.user.username)}}" >{{item.user.username}}</span>
</div>
<div>{{item.user.first_name}}</div>
</td>
{% for point_contest, rank_contest in item.point_contests %}
<td>
<div>{{ point_contest }}</div>
{% if rank_contest %}
<div><small>(#{{ rank_contest }})</small></div>
{% endif %}
</td>
{% endfor %}
<td>
<b>{{ item.points }}</b>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}