Coverage for src / mesh / models / roles / reviewer.py: 96%
68 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-05-04 12:41 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-05-04 12:41 +0000
1from typing import ClassVar
3from django.db.models import Value
5# from django.urls import reverse
6# from django.urls import reverse_lazy
7from django.utils.translation import gettext as _
8from opentelemetry import trace
10from mesh.app_settings import BlindMode, app_settings
11from mesh.models.orm.review_models import Review, ReviewAdditionalFile
12from mesh.models.orm.submission_models import (
13 Submission,
14 SubmissionAuthor,
15 SubmissionState,
16 SubmissionVersion,
17)
19# from ..views.components.button import Button
20from mesh.models.roles.base_role import Role
21from mesh.models.submission_status import SubmissionStatus, SubmissionStatusData
23# from ptf.url_utils import add_query_parameters_to_url
25tracer = trace.get_tracer(__name__)
28class ReviewerRights:
29 """
30 Rights implementation for reviewer role.
31 """
34class Reviewer(Role):
35 """
36 Reviewer role.
37 """
39 _CODE: ClassVar[str] = "reviewer"
40 _NAME: ClassVar[str] = _("Reviewer")
41 _ICON_CLASS: ClassVar[str] = "fa-user-group"
42 _SUBMISSION_LIST_TITLE: ClassVar[str] = _("My assignments")
43 rights: ReviewerRights
45 @tracer.start_as_current_span("Reviewer.__init__")
46 def __init__(self, user) -> None:
47 super().__init__(user)
48 if app_settings.BLIND_MODE == BlindMode.DOUBLE_BLIND:
49 self.created_by_censored_annotate = Value("**** ****")
50 self.authors_string_annotate = Value("**** ****")
51 self.authors_queryset = SubmissionAuthor.objects.none()
52 self.reviews_queryset = Review.objects.filter(reviewer=self.user)
53 self.versions_queryset = SubmissionVersion.objects.filter(reviews__reviewer=self.user)
54 self.submissions_queryset = Submission.objects.filter(
55 versions__reviews__reviewer=self.user
56 )
58 def _get_is_active(self):
59 return Review.objects.filter(reviewer=self.user).exists()
61 def get_submissions(self):
62 return self._annotate_submission_query(self.submissions_queryset).annotate(
63 user_is_editor=Value(False)
64 )
66 def can_access_submission(self, submission: Submission) -> bool:
67 return submission.versions.filter(reviews__reviewer=self.user).exists()
69 def can_access_reviews(self, version: SubmissionVersion) -> bool:
70 # Reviews should be censored anyways
71 return True
73 def can_access_review(self, submission, review) -> bool:
74 return review.reviewer == self.user
76 def can_access_review_file(self, submission, file: ReviewAdditionalFile) -> bool:
77 return self.can_access_review(submission, file.attached_to)
79 def can_access_review_details(self, submission, review: Review) -> bool:
80 return self.can_access_review(submission, review)
82 def get_current_open_review(self, version) -> Review | None:
83 return next(
84 (r for r in version.reviews_censored if r.version.review_open and not r.submitted),
85 None,
86 )
88 def can_edit_review(self, submission, review: Review) -> bool:
89 return review.is_editable() and self.can_access_review(submission, review)
91 def can_submit_review(self, submission, review: Review) -> bool:
92 return review.is_submittable() and self.can_access_review(submission, review)
94 def can_access_version(self, version):
95 return version.reviews.all().filter(reviewer=self.user).exists()
97 def get_submission_status(self, submission: Submission) -> SubmissionStatusData:
98 """
99 For a reviewer, the submission status depends on the submission state and the
100 (optional) review for the submission's current version.
101 """
102 current_version = submission.get_current_version()
103 if not current_version:
104 return SubmissionStatusData(
105 submission=submission,
106 status=SubmissionStatus.ERROR,
107 description="Submission does not have a current version",
108 )
109 if not self.can_access_submission(submission):
110 return SubmissionStatusData(
111 submission=submission, status=SubmissionStatus.ERROR, description=""
112 )
113 if submission.state in [
114 SubmissionState.ACCEPTED.value,
115 SubmissionState.REJECTED.value,
116 ]:
117 return SubmissionStatusData(
118 submission=submission, status=SubmissionStatus.DONE, description=""
119 )
120 # Get the first review attached to the submission's current version.
121 # There is max. 1 review per user per version.
123 review = next((r for r in current_version.reviews_censored), None)
125 if not review or not current_version.review_open:
126 return SubmissionStatusData(
127 submission=submission,
128 status=SubmissionStatus.WAITING,
129 description=_("The round for your review is closed."),
130 )
131 elif review.submitted:
132 return SubmissionStatusData(
133 submission=submission,
134 status=SubmissionStatus.WAITING,
135 description=_("Review submitted"),
136 )
137 elif review.accepted is False:
138 return SubmissionStatusData(
139 submission=submission,
140 status=SubmissionStatus.DONE,
141 description=_("Review declined"),
142 )
143 elif review.accepted is None:
144 status_data = SubmissionStatusData(
145 submission=submission,
146 status=SubmissionStatus.TODO,
147 description=_("Accept or decline the review request.")
148 + " "
149 + _("Due date")
150 + f": {review.date_response_due.strftime('%Y-%m-%d')}",
151 # shortcut_actions=[
152 # Button(
153 # id=f"review-accept-{review.pk}",
154 # title=_("Accept"),
155 # icon_class="fa-check",
156 # attrs={
157 # "href": [
158 # add_query_parameters_to_url(
159 # reverse("mesh:review_accept", kwargs={"pk": review.pk}),
160 # {"accepted": ["true"]},
161 # )
162 # ],
163 # "class": ["as-button", "button-success"],
164 # },
165 # ),
166 # Button(
167 # id=f"review-accept-{review.pk}",
168 # title=_("Decline"),
169 # icon_class="fa-xmark",
170 # attrs={
171 # "href": [
172 # add_query_parameters_to_url(
173 # reverse("mesh:review_accept", kwargs={"pk": review.pk}),
174 # {"accepted": ["false"]},
175 # )
176 # ],
177 # "class": ["as-button", "button-error"],
178 # },
179 # ),
180 # ],
181 )
182 # status_data.shortcut_actions.append(status_data.default_action_button)
183 return status_data
184 # The review is accepted but not submitted yet;
185 else:
186 return SubmissionStatusData(
187 submission=submission,
188 status=SubmissionStatus.TODO,
189 description=_("Submit your review report.")
190 + " "
191 + _("Due date")
192 + f": {review.date_review_due.strftime('%Y-%m-%d')}",
193 # shortcut_actions=[
194 # Button(
195 # id=f"review-submit-{review.pk}",
196 # title=_("Submit review"),
197 # icon_class="fa-pen-to-square",
198 # attrs={
199 # "href": [reverse_lazy("mesh:review_update", kwargs={"pk": review.pk})],
200 # "class": ["as-button"],
201 # },
202 # )
203 # ],
204 )