Coverage for src/mesh/model/roles/reviewer.py: 92%
73 statements
« prev ^ index » next coverage.py v7.7.0, created at 2025-04-28 07:45 +0000
« prev ^ index » next coverage.py v7.7.0, created at 2025-04-28 07:45 +0000
1from functools import cached_property
2from typing import ClassVar
4from django.db.models import QuerySet
6# from django.urls import reverse
7# from django.urls import reverse_lazy
8from django.utils.translation import gettext as _
10from mesh.app_settings import BlindMode, app_settings
11from mesh.models.review_models import Review, ReviewAdditionalFile
12from mesh.models.submission_models import Submission, SubmissionState, SubmissionVersion
13from mesh.models.user_models import User
15from ..submission_status import SubmissionStatus, SubmissionStatusData
17# from ..views.components.button import Button
18from .base_role import Role, RoleRights
20# from ptf.url_utils import add_query_parameters_to_url
23class ReviewerRights(RoleRights):
24 """
25 Rights implementation for reviewer role.
26 """
28 @cached_property
29 def reviews(self) -> QuerySet[Review]:
30 return Review.objects.filter(reviewer=self.user)
32 @cached_property
33 def submissions(self) -> QuerySet[Submission]:
34 return (
35 Submission.objects.get_submissions()
36 .filter(versions__reviews__in=self.reviews)
37 .distinct()
38 )
40 def can_access_submission(self, submission: Submission) -> bool:
41 return submission in self.submissions
43 def can_access_reviews(self, version: SubmissionVersion) -> bool:
44 return self.can_access_version(version)
46 def can_access_review(self, review: Review) -> bool:
47 return review in self.reviews
49 def can_access_review_author(self, review: Review) -> bool:
50 return self.can_access_review(review)
52 def can_access_review_file(self, file: ReviewAdditionalFile) -> bool:
53 return self.can_access_review(file.attached_to)
55 def can_access_review_details(self, review: Review) -> bool:
56 return self.can_access_review(review)
58 def get_current_open_review(self, version: SubmissionVersion | None) -> Review | None:
59 if not version: 59 ↛ 60line 59 didn't jump to line 60 because the condition on line 59 was never true
60 return None
62 current_reviews = [
63 r
64 for r in self.reviews
65 if r.version == version and r.version.review_open and not r.submitted
66 ]
67 if current_reviews:
68 return current_reviews[0]
69 return None
71 def can_edit_review(self, review: Review) -> bool:
72 return review.is_editable and self.can_access_review(review)
74 def can_submit_review(self, review: Review) -> bool:
75 return review.is_submittable and self.can_access_review(review)
77 def can_access_submission_author(self, submission: Submission) -> bool:
78 return (
79 self.can_access_submission(submission)
80 and app_settings.BLIND_MODE != BlindMode.DOUBLE_BLIND
81 )
83 def can_access_version(self, version: SubmissionVersion) -> bool:
84 return version in [r.version for r in self.reviews]
86 def get_submission_status(self, submission: Submission) -> SubmissionStatusData:
87 """
88 For a reviewer, the submission status depends on the submission state and the
89 (optional) review for the submission's current version.
90 """
91 if not self.can_access_submission(submission):
92 return SubmissionStatusData(
93 submission=submission, status=SubmissionStatus.ERROR, description=""
94 )
95 if submission.state in [
96 SubmissionState.ACCEPTED.value,
97 SubmissionState.REJECTED.value,
98 ]:
99 return SubmissionStatusData(
100 submission=submission, status=SubmissionStatus.DONE, description=""
101 )
102 # Get the first review attached to the submission's current version.
103 # There is max. 1 review per user per version.
104 review = next((r for r in self.reviews if r.version == submission.current_version), None)
105 if not review or not submission.current_version.review_open: # type:ignore
106 return SubmissionStatusData(
107 submission=submission,
108 status=SubmissionStatus.WAITING,
109 description=_("The round for your review is closed."),
110 )
111 elif review.submitted:
112 return SubmissionStatusData(
113 submission=submission,
114 status=SubmissionStatus.WAITING,
115 description=_("Review submitted"),
116 )
117 elif review.accepted is False:
118 return SubmissionStatusData(
119 submission=submission,
120 status=SubmissionStatus.DONE,
121 description=_("Review declined"),
122 )
123 elif review.accepted is None:
124 status_data = SubmissionStatusData(
125 submission=submission,
126 status=SubmissionStatus.TODO,
127 description=_("Accept or decline the review request.")
128 + " "
129 + _("Due date")
130 + f": {review.date_response_due.strftime('%Y-%m-%d')}",
131 # shortcut_actions=[
132 # Button(
133 # id=f"review-accept-{review.pk}",
134 # title=_("Accept"),
135 # icon_class="fa-check",
136 # attrs={
137 # "href": [
138 # add_query_parameters_to_url(
139 # reverse("mesh:review_accept", kwargs={"pk": review.pk}),
140 # {"accepted": ["true"]},
141 # )
142 # ],
143 # "class": ["as-button", "button-success"],
144 # },
145 # ),
146 # Button(
147 # id=f"review-accept-{review.pk}",
148 # title=_("Decline"),
149 # icon_class="fa-xmark",
150 # attrs={
151 # "href": [
152 # add_query_parameters_to_url(
153 # reverse("mesh:review_accept", kwargs={"pk": review.pk}),
154 # {"accepted": ["false"]},
155 # )
156 # ],
157 # "class": ["as-button", "button-error"],
158 # },
159 # ),
160 # ],
161 )
162 # status_data.shortcut_actions.append(status_data.default_action_button)
163 return status_data
164 # The review is accepted but not submitted yet;
165 else:
166 return SubmissionStatusData(
167 submission=submission,
168 status=SubmissionStatus.TODO,
169 description=_("Submit your review report.")
170 + " "
171 + _("Due date")
172 + f": {review.date_review_due.strftime('%Y-%m-%d')}",
173 # shortcut_actions=[
174 # Button(
175 # id=f"review-submit-{review.pk}",
176 # title=_("Submit review"),
177 # icon_class="fa-pen-to-square",
178 # attrs={
179 # "href": [reverse_lazy("mesh:review_update", kwargs={"pk": review.pk})],
180 # "class": ["as-button"],
181 # },
182 # )
183 # ],
184 )
187class Reviewer(Role):
188 """
189 Reviewer role.
190 """
192 _CODE: ClassVar[str] = "reviewer"
193 _NAME: ClassVar[str] = _("Reviewer")
194 _ICON_CLASS: ClassVar[str] = "fa-user-group"
195 _SUBMISSION_LIST_TITLE: ClassVar[str] = _("My assignments")
196 rights: ReviewerRights
198 def __init__(self, user: User) -> None:
199 super().__init__(user)
201 @property
202 def active(self) -> bool:
203 return len(self.rights.reviews) > 0
205 def get_rights(self) -> ReviewerRights:
206 return ReviewerRights(self.user)