Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 37 additions & 2 deletions oioioi/scoresreveal/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

from oioioi.base import admin
from oioioi.base.forms import AlwaysChangedModelForm
from oioioi.contests.admin import ProblemInstanceAdmin, SubmissionAdmin
from oioioi.contests.admin import ContestAdmin, ProblemInstanceAdmin, SubmissionAdmin
from oioioi.scoresreveal.models import ScoreRevealConfig
from oioioi.scoresreveal.utils import is_revealed
from oioioi.scoresreveal.utils import get_scores_reveal_config_universal, is_revealed


class RevealedFilter(SimpleListFilter):
Expand All @@ -28,6 +28,7 @@
can_delete = True
extra = 0
form = AlwaysChangedModelForm
exclude = ("contest",)


class ScoresRevealProblemInstanceAdminMixin:
Expand All @@ -37,10 +38,44 @@
super().__init__(*args, **kwargs)
self.inlines = tuple(self.inlines) + (ScoresRevealConfigInline,)

def get_inline_instances(self, request, obj=None):

Check failure on line 41 in oioioi/scoresreveal/admin.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to not always return the same value.

See more on https://sonarcloud.io/project/issues?id=sio2project_oioioi&issues=AZ6hlDZjWneQtRL6ZAX2&open=AZ6hlDZjWneQtRL6ZAX2&pullRequest=762
inline_instances = super().get_inline_instances(request, obj)

contest = getattr(obj, "contest", None)
if not contest:
return inline_instances
if get_scores_reveal_config_universal(contest) is None:
additional_description = _(" (no contest-wide default set)")
else:
additional_description = _(" (overriding contest-wide default)")

for inline in inline_instances:
if isinstance(inline, ScoresRevealConfigInline):
inline.verbose_name += additional_description
inline.verbose_name_plural += additional_description
return inline_instances


ProblemInstanceAdmin.mix_in(ScoresRevealProblemInstanceAdminMixin)


class ScoresRevealContestConfigInline(admin.TabularInline):
model = ScoreRevealConfig
extra = 0
form = AlwaysChangedModelForm
category = _("Advanced")
exclude = ("problem_instance",)


class ScoresRevealContestConfigAdminMixin:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.inlines = tuple(self.inlines) + (ScoresRevealContestConfigInline,)


ContestAdmin.mix_in(ScoresRevealContestConfigAdminMixin)


class ScoresRevealSubmissionAdminMixin:
"""Adds reveal info and filter to an admin panel."""

Expand Down
6 changes: 3 additions & 3 deletions oioioi/scoresreveal/controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from oioioi.contests.models import Submission
from oioioi.programs.controllers import ProgrammingContestController
from oioioi.scoresreveal.models import ScoreReveal
from oioioi.scoresreveal.utils import has_scores_reveal, is_revealed
from oioioi.scoresreveal.utils import get_scores_reveal_config_for_problem_instance, has_scores_reveal, is_revealed


class ScoresRevealContestControllerMixin:
Expand All @@ -28,10 +28,10 @@ def get_revealed_submissions(self, user, problem_instance):
return Submission.objects.filter(user=user, problem_instance=problem_instance, revealed__isnull=False)

def get_scores_reveals_disable_time(self, problem_instance):
return problem_instance.scores_reveal_config.disable_time
return get_scores_reveal_config_for_problem_instance(problem_instance).disable_time

def get_scores_reveals_limit(self, problem_instance):
return problem_instance.scores_reveal_config.reveal_limit
return get_scores_reveal_config_for_problem_instance(problem_instance).reveal_limit

def is_scores_reveals_limit_reached(self, user, problem_instance):
limit = self.get_scores_reveals_limit(problem_instance)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Generated by Django 5.2.12 on 2026-06-03 11:37

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('contests', '0025_merge_0017_submission_max_score_0024_roundstartdelay'),
('scoresreveal', '0005_auto_20211123_1728'),
]

operations = [
migrations.AddField(
model_name='scorerevealconfig',
name='contest',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='scores_reveal_config', to='contests.contest', verbose_name='contest'),
),
migrations.AlterField(
model_name='scorerevealconfig',
name='problem_instance',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='scores_reveal_config', to='contests.probleminstance', verbose_name='problem instance'),
),
migrations.AlterField(
model_name='scorerevealconfig',
name='reveal_limit',
field=models.IntegerField(blank=True, help_text='If empty, all submissions are revealed automatically. A value of 0 disables reveals.', null=True, verbose_name='reveal limit'),
),
]
22 changes: 20 additions & 2 deletions oioioi/scoresreveal/models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext_lazy as _

from oioioi.contests.models import ProblemInstance, Submission
from oioioi.contests.models import Contest, ProblemInstance, Submission


class ScoreReveal(models.Model):
Expand All @@ -23,10 +24,20 @@ class ScoreRevealConfig(models.Model):
verbose_name=_("problem instance"),
related_name="scores_reveal_config",
on_delete=models.CASCADE,
blank=True,
null=True,
)
contest = models.OneToOneField(
Contest,
verbose_name=_("contest"),
related_name="scores_reveal_config",
on_delete=models.CASCADE,
blank=True,
null=True,
)
reveal_limit = models.IntegerField(
verbose_name=_("reveal limit"),
help_text=_("If empty, all submissions are revealed automatically."),
help_text=_("If empty, all submissions are revealed automatically. A value of 0 disables reveals."),
blank=True,
null=True,
)
Expand All @@ -35,3 +46,10 @@ class ScoreRevealConfig(models.Model):
class Meta:
verbose_name = _("score reveal config")
verbose_name_plural = _("score reveal configs")

def clean(self):
super().clean()
if not self.problem_instance and not self.contest:
raise ValidationError(_("ScoresRevealConfig must be attached to either a contest or a problem instance."))
if self.problem_instance and self.contest:
raise ValidationError(_("ScoresRevealConfig cannot be attached to both a contest and a problem instance simultaneously."))
31 changes: 27 additions & 4 deletions oioioi/scoresreveal/utils.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,34 @@
from oioioi.scoresreveal.models import ScoreReveal, ScoreRevealConfig
from oioioi.scoresreveal.models import ScoreReveal


# Django doesn't cache a lack of the related object, so we do it manually.
def get_scores_reveal_config_universal(obj):
key = "_scores_reveal_config_cache"
if not hasattr(obj, key):
setattr(obj, key, getattr(obj, "scores_reveal_config", None))
return getattr(obj, key)


def get_scores_reveal_config_for_problem_instance(problem_instance):
pi_config = get_scores_reveal_config_universal(problem_instance)
if pi_config is not None:
return pi_config

if problem_instance.contest_id is None:
return None
# This performs well for many problem instances if the contest object is shared across
# them (e.g. in problem list view), so prefetch_related or annotate_known_related for
# the contest are needed in such places.
return get_scores_reveal_config_universal(problem_instance.contest)


def has_scores_reveal(problem_instance):
try:
return bool(problem_instance.scores_reveal_config)
except ScoreRevealConfig.DoesNotExist:
scores_reveal_config = get_scores_reveal_config_for_problem_instance(problem_instance)
if scores_reveal_config is None:
return False
reveal_limit = scores_reveal_config.reveal_limit
# None means auto-reveal.
return reveal_limit is None or reveal_limit > 0


def is_revealed(submission):
Expand Down
Loading