Coverage for src / mesh / models / roles / author.py: 95%

74 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-08-11 09:47 +0000

1from typing import ClassVar 

2 

3# from django.urls import reverse_lazy 

4from django.db.models import Case, CharField, Value, When 

5from django.utils.translation import gettext as _ 

6from opentelemetry import trace 

7 

8from mesh.app_settings import BlindMode, app_settings 

9from mesh.models.orm.review_models import Review 

10from mesh.models.orm.submission_models import Submission, SubmissionState, SubmissionVersion 

11 

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

13from mesh.models.roles.base_role import Role 

14from mesh.models.submission_status import SubmissionStatus, SubmissionStatusData 

15 

16tracer = trace.get_tracer(__name__) 

17 

18 

19class Author(Role): 

20 """ 

21 Author role. 

22 This is the base role of mesh app, always active. 

23 """ 

24 

25 _CODE: ClassVar[str] = "author" 

26 _NAME: ClassVar[str] = _("Author") 

27 _ICON_CLASS: ClassVar[str] = "fa-pen-nib" 

28 _SUBMISSION_LIST_TITLE: ClassVar[str] = _("My submissions") 

29 

30 @tracer.start_as_current_span("Author.__init__") 

31 def __init__(self, user) -> None: 

32 super().__init__(user) 

33 self.reviews_queryset = Review.objects.filter( 

34 version__review_open=False, 

35 submitted=True, 

36 accepted=True, 

37 recommendation__isnull=False, 

38 additional_files__author_access=True, 

39 ) 

40 

41 if app_settings.BLIND_MODE != BlindMode.NO_BLIND: 

42 self.reviewer_censored_annotate = Value("****, ****") 

43 

44 self.versions_queryset = SubmissionVersion.objects.all() 

45 self.submissions_queryset = Submission.objects.filter(created_by=self.user) 

46 

47 def _get_is_active(self): 

48 return True 

49 

50 def get_submissions(self): 

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

52 user_is_editor=Value(False) 

53 ) 

54 

55 def show_no_actions_message(self, submission: Submission) -> bool: 

56 return not self.can_edit_submission(submission) 

57 

58 def can_access_submission(self, submission): 

59 return submission.created_by == self.user 

60 

61 def can_edit_submission(self, submission): 

62 result = self.can_access_submission(submission) 

63 result = ( 

64 result 

65 and submission.is_draft 

66 or submission.state == SubmissionState.REVISION_REQUESTED.value 

67 ) 

68 return result 

69 

70 def can_create_submission(self): 

71 return True 

72 

73 def can_create_version(self, submission): 

74 if not self.can_access_submission(submission): 

75 return False 

76 current_version = submission.versions.first() 

77 # The author must have entered at least 1 submission author to proceed 

78 # with the submission version. 

79 if submission.is_draft and current_version is None: 

80 return submission.authors.exists() 

81 return ( 

82 submission.state == SubmissionState.REVISION_REQUESTED.value 

83 and current_version is not None 

84 and current_version.submitted 

85 ) 

86 

87 def can_access_version(self, version): 

88 return self.can_access_submission(version.submission) 

89 

90 def can_edit_version(self, version): 

91 return not version.submitted and self.can_access_submission(version.submission) 

92 

93 def can_access_reviews(self, version): 

94 """ 

95 An author can access reviews when the review process is closed. 

96 """ 

97 return not version.review_open and self.can_access_submission(version.submission) 

98 

99 def can_access_review(self, submission, review): 

100 """ 

101 An author can access review with at least one accessible file. 

102 """ 

103 return ( 

104 self.can_access_reviews(review.version) 

105 and review.submitted 

106 and review.recommendation is not None 

107 and any(self.can_access_review_file(f) for f in review.additional_files.all()) 

108 ) 

109 

110 def can_access_review_file(self, file): 

111 return file.author_access 

112 

113 def get_submission_status(self, submission): 

114 """ 

115 For an author, the submission status is one of the following: 

116 - "Submission is still in draft", True - when STATUS_OPENED 

117 - "A new version must be submitted", True - when STATUS_REVISIONS_REQUESTED 

118 - "Submission accepted", False - when STATUS_ACCEPTED 

119 - "Submission declined", False - when STATUS_DECLINED 

120 - "Waiting for editorial actions" - when STATUS_SUBMITTED or STATUS_REVISIONS_SUBMITTED 

121 - "Under review", False - For other state 

122 """ 

123 # Should not happen ? 

124 if not self.can_access_submission(submission): 

125 return SubmissionStatusData( 

126 submission=submission, status=SubmissionStatus.ERROR, description="" 

127 ) 

128 

129 if submission.state in [ 

130 SubmissionState.SUBMITTED.value, 

131 SubmissionState.REVISION_SUBMITTED.value, 

132 ]: 

133 return SubmissionStatusData( 

134 submission=submission, 

135 status=SubmissionStatus.WAITING, 

136 description=_("Under review"), 

137 ) 

138 elif submission.state == SubmissionState.OPENED.value: 

139 status_data = SubmissionStatusData( 

140 submission=submission, 

141 status=SubmissionStatus.TODO, 

142 description=_("Complete your submission"), 

143 ) 

144 return status_data 

145 

146 elif submission.state == SubmissionState.REVISION_REQUESTED.value: 

147 status_data = SubmissionStatusData( 

148 submission=submission, 

149 status=SubmissionStatus.TODO, 

150 description=_("Submit a new version"), 

151 ) 

152 # if submission.state == SubmissionState.OPENED.value and self.can_submit_submission( 

153 # submission 

154 # ): 

155 # status_data.shortcut_actions.append( 

156 # Button( 

157 # id=f"submission-submit-{submission.pk}", 

158 # title=_("Resume submission"), 

159 # icon_class="fa-list-check", 

160 # attrs={ 

161 # "href": [ 

162 # reverse_lazy( 

163 # "mesh:submission_resume", kwargs={"pk": submission.pk} 

164 # ) 

165 # ], 

166 # "class": ["as-button"], 

167 # }, 

168 # ) 

169 # ) 

170 # elif ( 

171 # submission.state == SubmissionState.REVISION_REQUESTED.value 

172 # and self.can_create_version(submission) 

173 # ): 

174 # status_data.shortcut_actions.append( 

175 # Button( 

176 # id=f"submission-submit-{submission.pk}", 

177 # title=_("Submit revisions"), 

178 # icon_class="fa-plus", 

179 # attrs={ 

180 # "href": [ 

181 # reverse_lazy( 

182 # "mesh:submission_version_create", 

183 # kwargs={"submission_pk": submission.pk}, 

184 # ) 

185 # ], 

186 # "class": ["as-button"], 

187 # }, 

188 # ) 

189 # ) 

190 # elif ( 

191 # submission.state == SubmissionState.REVISION_REQUESTED.value 

192 # and submission.current_version 

193 # and not submission.current_version.submitted 

194 # ): 

195 # status_data.shortcut_actions.append( 

196 # Button( 

197 # id=f"submission-submit-{submission.pk}", 

198 # title=_("Resume revisions"), 

199 # icon_class="fa-list-check", 

200 # attrs={ 

201 # "href": [ 

202 # reverse_lazy( 

203 # "mesh:submission_version_update", 

204 # kwargs={"pk": submission.current_version.pk}, 

205 # ) 

206 # ], 

207 # "class": ["as-button"], 

208 # }, 

209 # ) 

210 # ) 

211 return status_data 

212 

213 elif submission.state == SubmissionState.ON_REVIEW.value: 

214 return SubmissionStatusData( 

215 submission=submission, 

216 status=SubmissionStatus.WAITING, 

217 description=_("Submission is under review"), 

218 ) 

219 elif submission.state in [ 

220 SubmissionState.ACCEPTED.value, 

221 SubmissionState.REJECTED.value, 

222 ]: 

223 return SubmissionStatusData( 

224 submission=submission, 

225 status=SubmissionStatus.DONE, 

226 description=_("Submission") + " " + submission.get_state_display(), 

227 ) 

228 

229 return SubmissionStatusData( 

230 submission=submission, status=SubmissionStatus.ERROR, description="" 

231 ) 

232 

233 def annotate_submission_status(self, qs): 

234 return qs.annotate( 

235 computed_status=Case( 

236 When( 

237 state__in=[ 

238 SubmissionState.OPENED.value, 

239 SubmissionState.REVISION_REQUESTED.value, 

240 ], 

241 then=Value(SubmissionStatus.TODO.value), 

242 ), 

243 When( 

244 state__in=[ 

245 SubmissionState.SUBMITTED.value, 

246 SubmissionState.REVISION_SUBMITTED.value, 

247 SubmissionState.ON_REVIEW.value, 

248 ], 

249 then=Value(SubmissionStatus.WAITING.value), 

250 ), 

251 When( 

252 state__in=[ 

253 SubmissionState.ACCEPTED.value, 

254 SubmissionState.REJECTED.value, 

255 ], 

256 then=Value(SubmissionStatus.DONE.value), 

257 ), 

258 default=Value(SubmissionStatus.ERROR.value), 

259 output_field=CharField(), 

260 ) 

261 ) 

262 

263 def can_access_last_activity(self) -> bool: 

264 return False