Source code for pretalx.submission.domain.review

# SPDX-FileCopyrightText: 2026-present Tobias Kunze
# SPDX-License-Identifier: AGPL-3.0-only WITH LicenseRef-Pretalx-AGPL-3.0-Terms

import itertools

from django.core.exceptions import ValidationError
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _


[docs] def update_review_score(review): """Recompute and persist ``review.score`` from its m2m ``scores``. Filters by the submission's currently applicable score categories (which depend on the submission's track) and writes the result back. """ scores = list( review.scores.select_related("category").filter( category__in=review.submission.score_categories ) ) review.score = sum(s.value * s.category.weight for s in scores) if scores else None review.save()
def recalculate_event_scores(event): for review in event.reviews.all(): update_review_score(review)
[docs] def recalculate_submission_scores(submission): for review in submission.reviews.all(): update_review_score(review)
def validate_review_phases(event): review_phases = list(event.review_phases.all()) for phase, next_phase in itertools.pairwise(review_phases): if not phase.end: raise ValidationError(_("Only the last review phase may be open-ended.")) if not next_phase.start: raise ValidationError( _("All review phases except for the first one need a start date.") ) if phase.end > next_phase.start: raise ValidationError( _( "The review phases '{phase1}' and '{phase2}' overlap. " "Please make sure that review phases do not overlap, then save again." ).format(phase1=phase.name, phase2=next_phase.name) )
[docs] def activate_review_phase(phase, *, person=None): phase.event.review_phases.all().update(is_active=False) phase.is_active = True phase.save() phase.log_action( ".activate", person=person, orga=person is not None, data={"name": phase.name} )
def _is_within_window(phase, _now): return (phase.start is None or phase.start <= _now) and ( phase.end is None or phase.end >= _now ) def update_review_phase(event): """Advance ``event`` to the next review phase if the current one has ended (or has not started yet). Returns the now-active phase, or ``None`` when no phase is active. """ _now = now() phase = event.active_review_phase if phase: if _is_within_window(phase, _now): return phase phase.is_active = False phase.save() next_phase = next( (p for p in event.review_phases.all() if _is_within_window(p, _now)), None ) if next_phase: activate_review_phase(next_phase) return next_phase