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/summary/(?P<key>\w+)$",
contests.contests_summary_view,
name="contests_summary",
),
url(r"^course/", paged_list_view(course.CourseList, "course_list")),
url(
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 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 (
BlogPostAdmin,
LicenseAdmin,
@ -41,6 +46,7 @@ from judge.models import (
Ticket,
VolunteerProblemVote,
Course,
ContestsSummary,
)
@ -69,3 +75,4 @@ admin.site.register(VolunteerProblemVote, VolunteerProblemVoteAdmin)
admin.site.register(Course)
admin.site.unregister(User)
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.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 django.core.cache import cache
from django.db.models.query import QuerySet
from django.core.handlers.wsgi import WSGIRequest
import hashlib
@ -18,10 +19,14 @@ def cache_wrapper(prefix, timeout=None):
return str(arg)[:MAX_NUM_CHAR]
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):
args_list = list(args)
signature_args = list(signature(func).parameters.keys())
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]
key = prefix + ":" + ":".join(args_list)
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,
Rating,
ContestProblemClarification,
ContestsSummary,
)
from judge.models.interface import BlogPost, MiscConfig, NavigationBar, validate_regex
from judge.models.message import PrivateMessage, PrivateMessageThread

View file

@ -33,6 +33,7 @@ __all__ = [
"ContestSubmission",
"Rating",
"ContestProblemClarification",
"ContestsSummary",
]
@ -900,3 +901,24 @@ class ContestProblemClarification(models.Model):
date = models.DateTimeField(
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,
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.http import (
Http404,
@ -67,6 +69,7 @@ from judge.models import (
Profile,
Submission,
ContestProblemClarification,
ContestsSummary,
)
from judge.tasks import run_moss
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)
request.session["contest_mode"] = not old_mode
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 %}