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

1from typing import ClassVar 

2 

3from django.db.models import Value 

4 

5# from django.urls import reverse 

6# from django.urls import reverse_lazy 

7from django.utils.translation import gettext as _ 

8from opentelemetry import trace 

9 

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) 

18 

19# from ..views.components.button import Button 

20from mesh.models.roles.base_role import Role 

21from mesh.models.submission_status import SubmissionStatus, SubmissionStatusData 

22 

23# from ptf.url_utils import add_query_parameters_to_url 

24 

25tracer = trace.get_tracer(__name__) 

26 

27 

28class ReviewerRights: 

29 """ 

30 Rights implementation for reviewer role. 

31 """ 

32 

33 

34class Reviewer(Role): 

35 """ 

36 Reviewer role. 

37 """ 

38 

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 

44 

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 ) 

57 

58 def _get_is_active(self): 

59 return Review.objects.filter(reviewer=self.user).exists() 

60 

61 def get_submissions(self): 

62 return self._annotate_submission_query(self.submissions_queryset).annotate( 

63 user_is_editor=Value(False) 

64 ) 

65 

66 def can_access_submission(self, submission: Submission) -> bool: 

67 return submission.versions.filter(reviews__reviewer=self.user).exists() 

68 

69 def can_access_reviews(self, version: SubmissionVersion) -> bool: 

70 # Reviews should be censored anyways 

71 return True 

72 

73 def can_access_review(self, submission, review) -> bool: 

74 return review.reviewer == self.user 

75 

76 def can_access_review_file(self, submission, file: ReviewAdditionalFile) -> bool: 

77 return self.can_access_review(submission, file.attached_to) 

78 

79 def can_access_review_details(self, submission, review: Review) -> bool: 

80 return self.can_access_review(submission, review) 

81 

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 ) 

87 

88 def can_edit_review(self, submission, review: Review) -> bool: 

89 return review.is_editable() and self.can_access_review(submission, review) 

90 

91 def can_submit_review(self, submission, review: Review) -> bool: 

92 return review.is_submittable() and self.can_access_review(submission, review) 

93 

94 def can_access_version(self, version): 

95 return version.reviews.all().filter(reviewer=self.user).exists() 

96 

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. 

122 

123 review = next((r for r in current_version.reviews_censored), None) 

124 

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 )