diff --git a/dmoj/urls.py b/dmoj/urls.py index d2c2c12..6292098 100644 --- a/dmoj/urls.py +++ b/dmoj/urls.py @@ -1099,7 +1099,7 @@ urlpatterns = [ ] ), ), - url(r"^resolver/(?P\w+)",resolver.Resolver.as_view(), name="resolver") + url(r"^resolver/(?P\w+)", resolver.Resolver.as_view(), name="resolver"), ] + url_static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) # if hasattr(settings, "INTERNAL_IPS"): diff --git a/judge/admin/contest.py b/judge/admin/contest.py index 3a57793..a7441b0 100644 --- a/judge/admin/contest.py +++ b/judge/admin/contest.py @@ -3,7 +3,7 @@ from django.contrib import admin from django.core.exceptions import PermissionDenied from django.db import connection, transaction from django.db.models import Q, TextField -from django.forms import ModelForm, ModelMultipleChoiceField +from django.forms import ModelForm, ModelMultipleChoiceField, TextInput from django.http import Http404, HttpResponseRedirect from django.shortcuts import get_object_or_404 from django.urls import reverse, reverse_lazy @@ -66,7 +66,13 @@ class ContestTagAdmin(admin.ModelAdmin): class ContestProblemInlineForm(ModelForm): class Meta: - widgets = {"problem": AdminHeavySelect2Widget(data_view="problem_select2")} + widgets = { + "problem": AdminHeavySelect2Widget(data_view="problem_select2"), + "frozen_subtasks": TextInput(attrs={"size": "3"}), + "points": TextInput(attrs={"size": "1"}), + "order": TextInput(attrs={"size": "1"}), + "output_prefix_override": TextInput(attrs={"size": "1"}), + } class ContestProblemInline(admin.TabularInline): @@ -79,6 +85,7 @@ class ContestProblemInline(admin.TabularInline): "partial", "is_pretested", "max_submissions", + "frozen_subtasks", "output_prefix_override", "order", "rejudge_column", diff --git a/judge/caching.py b/judge/caching.py index b45e910..99bbf81 100644 --- a/judge/caching.py +++ b/judge/caching.py @@ -8,4 +8,3 @@ def finished_submission(sub): keys += ["contest_complete:%d" % participation.id] keys += ["contest_attempted:%d" % participation.id] cache.delete_many(keys) - diff --git a/judge/contest_format/base.py b/judge/contest_format/base.py index 848d81d..05c6391 100644 --- a/judge/contest_format/base.py +++ b/judge/contest_format/base.py @@ -98,15 +98,22 @@ class BaseContestFormat(metaclass=ABCMeta): return "partial-score" def handle_frozen_state(self, participation, format_data): - if not self.contest.freeze_after: - return + frozen_subtasks = {} + if hasattr(self, "get_frozen_subtasks"): + frozen_subtasks = self.get_frozen_subtasks() + queryset = participation.submissions.values("problem_id").annotate( time=Max("submission__date") ) for result in queryset: problem = str(result["problem_id"]) if format_data.get(problem): - if result["time"] >= self.contest.freeze_after + participation.start: + is_after_freeze = ( + self.contest.freeze_after + and result["time"] + >= self.contest.freeze_after + participation.start + ) + if is_after_freeze or frozen_subtasks.get(problem): format_data[problem]["frozen"] = True else: format_data[problem] = {"time": 0, "points": 0, "frozen": True} diff --git a/judge/contest_format/ioi.py b/judge/contest_format/ioi.py index 2f51eb7..5c2502c 100644 --- a/judge/contest_format/ioi.py +++ b/judge/contest_format/ioi.py @@ -111,7 +111,9 @@ class IOIContestFormat(DefaultContestFormat): contest_problem.problem.code, ], ), - points=floatformat(format_data["points"]), + points=floatformat( + format_data["points"], -self.contest.points_precision + ), time=nice_repr(timedelta(seconds=format_data["time"]), "noday") if self.config["cumtime"] else "", @@ -122,7 +124,7 @@ class IOIContestFormat(DefaultContestFormat): def display_participation_result(self, participation): return format_html( '{points}
{cumtime}
', - points=floatformat(participation.score), + points=floatformat(participation.score, -self.contest.points_precision), cumtime=nice_repr(timedelta(seconds=participation.cumtime), "noday") if self.config["cumtime"] else "", diff --git a/judge/contest_format/new_ioi.py b/judge/contest_format/new_ioi.py index 848ea7f..9571c34 100644 --- a/judge/contest_format/new_ioi.py +++ b/judge/contest_format/new_ioi.py @@ -14,6 +14,21 @@ class NewIOIContestFormat(IOIContestFormat): cumtime: Specify True if time penalties are to be computed. Defaults to False. """ + def get_frozen_subtasks(self): + queryset = self.contest.contest_problems.values_list("id", "frozen_subtasks") + res = {} + for problem_id, frozen_subtasks in queryset: + subtasks = set() + if frozen_subtasks: + frozen_subtasks = frozen_subtasks.split(",") + for i in frozen_subtasks: + try: + subtasks.add(int(i)) + except Exception as e: + pass + res[str(problem_id)] = subtasks + return res + def get_results_by_subtask(self, participation, include_frozen=False): frozen_time = self.contest.end_time if self.contest.freeze_after and not include_frozen: @@ -65,6 +80,7 @@ class NewIOIContestFormat(IOIContestFormat): ON (sub.id = cs.submission_id AND sub.status = 'D') INNER JOIN judge_submissiontestcase tc ON sub.id = tc.submission_id + WHERE sub.date < %s GROUP BY cp.id, tc.batch, sub.id ) r GROUP BY prob, batch @@ -73,7 +89,12 @@ class NewIOIContestFormat(IOIContestFormat): WHERE p.max_batch_points = q.batch_points GROUP BY q.prob, q.batch """, - (participation.id, to_database_time(frozen_time), participation.id), + ( + participation.id, + to_database_time(frozen_time), + participation.id, + to_database_time(frozen_time), + ), ) return cursor.fetchall() @@ -82,6 +103,7 @@ class NewIOIContestFormat(IOIContestFormat): cumtime = 0 score = 0 format_data = {} + frozen_subtasks = self.get_frozen_subtasks() for ( problem_id, @@ -101,10 +123,11 @@ class NewIOIContestFormat(IOIContestFormat): if format_data.get(problem_id) is None: format_data[problem_id] = {"points": 0, "time": 0, "total_points": 0} - format_data[problem_id]["points"] += subtask_points + if subtask not in frozen_subtasks.get(problem_id, set()): + 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]["problem_points"] = problem_points - format_data[problem_id]["total_points"] += total_subtask_points for problem_data in format_data.values(): if not problem_data["total_points"]: diff --git a/judge/middleware.py b/judge/middleware.py index 3b2fe9f..940b486 100644 --- a/judge/middleware.py +++ b/judge/middleware.py @@ -81,4 +81,4 @@ class DarkModeMiddleware(object): return HttpResponseRedirect( reverse("toggle_darkmode") + "?next=" + urlquote(request.path) ) - return self.get_response(request) \ No newline at end of file + return self.get_response(request) diff --git a/judge/migrations/0141_contestproblem_frozen_subtasks.py b/judge/migrations/0141_contestproblem_frozen_subtasks.py new file mode 100644 index 0000000..c85dc49 --- /dev/null +++ b/judge/migrations/0141_contestproblem_frozen_subtasks.py @@ -0,0 +1,24 @@ +# Generated by Django 3.2.16 on 2022-12-20 06:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("judge", "0140_alter_contest_format_name"), + ] + + operations = [ + migrations.AddField( + model_name="contestproblem", + name="frozen_subtasks", + field=models.CharField( + blank=True, + help_text="Only for format new IOI. Separated by commas, e.g: 2, 3", + max_length=20, + null=True, + verbose_name="frozen subtasks", + ), + ), + ] diff --git a/judge/models/contest.py b/judge/models/contest.py index 0a7389b..18f4c71 100644 --- a/judge/models/contest.py +++ b/judge/models/contest.py @@ -774,6 +774,13 @@ class ContestProblem(models.Model): MinValueValidator(0, _("Why include a problem you " "can't submit to?")) ], ) + frozen_subtasks = models.CharField( + help_text=_("Only for format new IOI. Separated by commas, e.g: 2, 3"), + verbose_name=_("frozen subtasks"), + null=True, + blank=True, + max_length=20, + ) @property def clarifications(self): diff --git a/judge/views/resolver.py b/judge/views/resolver.py index cdbe4af..db0c140 100644 --- a/judge/views/resolver.py +++ b/judge/views/resolver.py @@ -2,6 +2,7 @@ from django.views.generic import TemplateView from django.utils.translation import gettext as _ from django.http import HttpResponseForbidden + class Resolver(TemplateView): title = _("Resolver") template_name = "resolver/resolver.html" @@ -15,5 +16,3 @@ class Resolver(TemplateView): if request.user.is_superuser: return super(Resolver, self).get(request, *args, **kwargs) return HttpResponseForbidden() - - \ No newline at end of file diff --git a/judge/views/submission.py b/judge/views/submission.py index d783c9b..83c4216 100644 --- a/judge/views/submission.py +++ b/judge/views/submission.py @@ -842,6 +842,7 @@ class UserContestSubmissionsAjax(UserContestSubmissions): total_points = 0 problem_points = 0 achieved_points = 0 + frozen_subtasks = self.contest.format.get_frozen_subtasks() for ( problem_id, @@ -860,25 +861,38 @@ class UserContestSubmissionsAjax(UserContestSubmissions): subtask = 0 problem_points = pp submission = Submission.objects.get(id=sub_id) - best_subtasks[subtask] = { - "submission": submission, - "contest_time": nice_repr(self.contest_time(submission), "noday"), - "points": subtask_points, - "total": total_subtask_points, - } + if subtask in frozen_subtasks.get(str(problem_id), set()): + best_subtasks[subtask] = { + "submission": submission, + "contest_time": nice_repr( + self.contest_time(submission), "noday" + ), + "points": "???", + "total": total_subtask_points, + } + else: + best_subtasks[subtask] = { + "submission": submission, + "contest_time": nice_repr( + self.contest_time(submission), "noday" + ), + "points": subtask_points, + "total": total_subtask_points, + } + achieved_points += subtask_points total_points += total_subtask_points - achieved_points += subtask_points for subtask in best_subtasks.values(): - subtask["points"] = floatformat( - subtask["points"] / total_points * problem_points, - -self.contest.points_precision, - ) + if subtask["points"] != "???": + subtask["points"] = floatformat( + subtask["points"] / total_points * problem_points, + -self.contest.points_precision, + ) subtask["total"] = floatformat( subtask["total"] / total_points * problem_points, -self.contest.points_precision, ) - achieved_points = achieved_points / total_points * problem_points - if best_subtasks: + if total_points > 0 and best_subtasks: + achieved_points = achieved_points / total_points * problem_points return best_subtasks, achieved_points, problem_points return None @@ -914,9 +928,10 @@ class UserContestSubmissionsAjax(UserContestSubmissions): context["points"], context["total"], ) = best_subtasks - context["points"] = floatformat( - context["points"], -self.contest.points_precision - ) + if context["points"] != "???": + context["points"] = floatformat( + context["points"], -self.contest.points_precision + ) context["total"] = floatformat( context["total"], -self.contest.points_precision ) diff --git a/judge/views/user.py b/judge/views/user.py index b22a692..7cb98ec 100644 --- a/judge/views/user.py +++ b/judge/views/user.py @@ -630,4 +630,4 @@ def toggle_darkmode(request): if not path: return HttpResponseBadRequest() request.session["darkmode"] = not request.session.get("darkmode", False) - return HttpResponseRedirect(path) \ No newline at end of file + return HttpResponseRedirect(path) diff --git a/templates/submission/user-ajax.html b/templates/submission/user-ajax.html index e16bb6c..bc237fb 100644 --- a/templates/submission/user-ajax.html +++ b/templates/submission/user-ajax.html @@ -10,7 +10,7 @@ {{_('Subtask')}} {{subtask}}: - + {{ cur_subtask.points }} / {{ cur_subtask.total }} @@ -28,7 +28,7 @@ {{_('Total')}}: - + {{ points }} / {{ total }}