Source code for

from django.db import models
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from django_scopes import ScopedManager

from pretalx.common.urls import EventUrls

[docs]class Review(models.Model): """Reviews model the opinion of reviewers of a :class:`~pretalx.submission.models.submission.Submission`. They can, but don't have to, include a score and a text. :param text: The review itself. May be empty. :param score: The upper and lower bounds of this value are defined in an event's settings. :param override_vote: If this field is ``True`` or ``False``, it indicates that the reviewer has spent one of their override votes to emphasize their opinion of the review. It is ``None`` otherwise. """ submission = models.ForeignKey( to='submission.Submission', related_name='reviews', on_delete=models.CASCADE ) user = models.ForeignKey( to='person.User', related_name='reviews', on_delete=models.CASCADE ) text = models.TextField(verbose_name=_('What do you think?'), null=True, blank=True) score = models.IntegerField(verbose_name=_('Score'), null=True, blank=True) override_vote = models.BooleanField(default=None, null=True, blank=True) created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) objects = ScopedManager(event='submission__event') def __str__(self): return f'Review(event={self.submission.event.slug}, submission={self.submission.title}, user={self.user.get_display_name}, score={self.score})'
[docs] @classmethod def find_missing_reviews(cls, event, user, ignore=None): """ Returns all :class:`~pretalx.submission.models.submission.Submission` objects this :class:`~pretalx.person.models.user.User` still has to review for the given :class:`~pretalx.event.models.event.Event`. Excludes submissions this user has submitted, and takes track :class:`~pretalx.event.models.organiser.Team` permissions into account. The result is ordered by review count. :type event: :class:`~pretalx.event.models.event.Event` :type user: :class:`~pretalx.person.models.user.User` :rtype: Queryset of :class:`~pretalx.submission.models.submission.Submission` objects """ from pretalx.submission.models import SubmissionStates queryset = ( event.submissions.filter(state=SubmissionStates.SUBMITTED) .exclude(reviews__user=user) .exclude(speakers__in=[user]) .annotate(review_count=models.Count('reviews')) ) limit_tracks = user.teams.filter( models.Q(all_events=True) | models.Q( models.Q(all_events=False) & models.Q(limit_events__in=[event]) ), limit_tracks__isnull=False, ) if limit_tracks.exists(): tracks = set() for team in limit_tracks: tracks.update(team.limit_tracks.filter(event=event)) queryset = queryset.filter(track__in=tracks) if ignore: queryset = queryset.exclude(pk__in=[ for submission in ignore]) return queryset.order_by('review_count', '?')
@cached_property def event(self): return self.submission.event @cached_property def display_score(self) -> str: """Helper method to get a display string of the review's score.""" if self.override_vote is True: return _('Positive override') if self.override_vote is False: return _('Negative override (Veto)') if self.score is None: return '×' return self.submission.event.settings.get( f'review_score_name_{self.score}' ) or str(self.score) class urls(EventUrls): base = '{}' delete = '{base}{}/delete'
[docs]class ReviewPhase(models.Model): """ReviewPhases determine reviewer access rights during a (potentially open) timeframe. :param is_active: Is this phase currently active? There can be only one active phase per event. Use the ``activate`` method to activate a review phase, as it will take care of this limitation. :param position: Helper field to deal with relative positioning of review phases next to each other. """ event = models.ForeignKey( to='event.Event', related_name='review_phases', on_delete=models.CASCADE ) name = models.CharField(verbose_name=_('Name'), max_length=100) start = models.DateTimeField(verbose_name=_('Phase start'), null=True, blank=True) end = models.DateTimeField(verbose_name=_('Phase end'), null=True, blank=True) position = models.PositiveIntegerField(default=0) is_active = models.BooleanField(default=False) can_review = models.BooleanField( verbose_name=_('Reviewers can write and edit reviews'), default=True, ) can_see_other_reviews = models.CharField( verbose_name=_('Reviewers can see other reviews'), max_length=12, choices=(('always', _('Always')), ('never', _('Never')), ('after_review', _('After reviewing the submission'))), default='after_review', ) can_see_speaker_names = models.BooleanField( verbose_name=_('Reviewers can see speaker names'), default=True, ) can_change_submission_state = models.BooleanField( verbose_name=_('Reviewers can accept and reject submissions'), default=False, ) speakers_can_change_submissions = models.BooleanField( verbose_name=_('Speakers can modify their submissions before acceptance'), help_text=_('By default, modification of submissions is locked after the CfP ends, and is re-enabled once the submission was accepted.'), default=False, ) objects = ScopedManager(event='event') class Meta: ordering = ('position', ) class urls(EventUrls): base = '{self.event.orga_urls.review_settings}phase/{}/' delete = '{base}delete' up = '{base}up' down = '{base}down' activate = '{base}activate'
[docs] def activate(self) -> None: """Activates this review phase and deactivates all others in this event.""" self.event.review_phases.all().update(is_active=False) self.is_active = True