Coverage for src/mesh/views/components/button.py: 52%

95 statements  

« prev     ^ index     » next       coverage.py v7.8.2, created at 2025-06-03 13:52 +0000

1from dataclasses import dataclass, field 

2 

3from django import forms 

4from django.urls import reverse, reverse_lazy 

5from django.utils.translation import gettext_lazy as _ 

6from ptf.url_utils import add_query_parameters_to_url 

7 

8from mesh.model.roles.role_handler import RoleHandler 

9 

10from ...models.review_models import RecommendationValue, Review, ReviewState 

11from ...models.submission_models import Submission 

12 

13 

14@dataclass 

15class Button: 

16 """ 

17 Interface for a button component. 

18 """ 

19 

20 # Unique ID for the button - not used for now 

21 id: str 

22 # Title to display as text in the button 

23 title: str 

24 # Optional font-awesome 6 icon code/class to be added next to the title. 

25 icon_class: str = field(default="") 

26 # Additional HTML attributes (ex: `{"type": ["submit"]})` 

27 attrs: dict[str, list[str]] = field(default_factory=dict) 

28 # A Django form - If not None, the button will be rendered as a full form 

29 # with this unique button. You must provide the "href" HTML attribute for the form 

30 # to be considered as one. 

31 form: forms.Form | None = field(default=None) 

32 

33 def is_form(self) -> bool: 

34 """ 

35 Whether the button should be displayed as a form. 

36 """ 

37 return bool(self.form) and bool(self.attrs.get("href", False)) 

38 

39 def is_link(self) -> bool: 

40 """ 

41 Whether the button is a link. 

42 """ 

43 return bool(self.attrs.get("href", False) or self.attrs.get("data-modal-href", False)) 

44 

45 def is_modal_link(self): 

46 """ 

47 Whether the button is a link. 

48 """ 

49 return bool(self.attrs.get("data-modal-href", False)) 

50 

51 def add_attr(self, attr: str, values: list[str]): 

52 """ 

53 Add the given value to the given attribute values array. 

54 

55 Ex: 

56 `button.add_attr("class", ["btn", "btn-primary"])` 

57 `button.add_attr("class", ["btn"])` 

58 """ 

59 if attr not in self.attrs: 

60 self.attrs[attr] = [] 

61 

62 for value in values: 

63 if value in self.attrs[attr]: 

64 continue 

65 self.attrs[attr].append(value) 

66 

67 def set_attr(self, attr: str, values: list[str]): 

68 """ 

69 Set the given value for the given attribute. 

70 

71 Ex: 

72 `button.set_attr("class", ["btn", "btn-primary"])` 

73 `button.set_attr("href", ["http://myserver"])` 

74 """ 

75 self.attrs[attr] = values 

76 

77 def remove_attr(self, attr: str): 

78 """ 

79 Remove the given attribute from the attributes array. 

80 """ 

81 if attr in self.attrs: 

82 del self.attrs[attr] 

83 

84 

85@dataclass 

86class SubmissionActionList: 

87 title: str 

88 actions: list[Button] = field(default_factory=list) 

89 

90 

91def build_submission_actions( 

92 submission: Submission, role_handler: RoleHandler, *args, **kwargs 

93) -> list[SubmissionActionList]: 

94 """ 

95 Returns the complete list of available actions to the current user. 

96 """ 

97 article_actions = [] 

98 submission_actions: list[Button] = [] 

99 review_actions = [] 

100 manage_actions: list[Button] = [] 

101 btn_classes = ["as-button"] if kwargs.get("display_as_btn", False) else [] 

102 shortlist = kwargs.get("shortlist", True) 

103 

104 if role_handler.check_rights("can_access_submission_log", submission) and shortlist: 104 ↛ 121line 104 didn't jump to line 121 because the condition on line 104 was always true

105 submission_actions.append( 

106 Button( 

107 id=f"submission-view-{submission.pk}", 

108 title=_("VIEW SUBMISSION"), 

109 icon_class="fa-eye", 

110 attrs={ 

111 "href": [ 

112 reverse_lazy("mesh:submission_details", kwargs={"pk": submission.pk}) 

113 ], 

114 "class": btn_classes, 

115 }, 

116 ) 

117 ) 

118 

119 #### [BEGIN] Author actions #### 

120 # Resume submit process 

121 can_submit_submission = role_handler.check_rights("can_submit_submission", submission) 

122 if can_submit_submission: 122 ↛ 123line 122 didn't jump to line 123 because the condition on line 122 was never true

123 submission_actions.append( 

124 Button( 

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

126 title=_("RESUME SUBMISSION"), 

127 icon_class="fa-list-check", 

128 attrs={ 

129 "href": [reverse_lazy("mesh:submission_resume", kwargs={"pk": submission.pk})], 

130 "class": btn_classes, 

131 }, 

132 ) 

133 ) 

134 

135 # Edit submission metadata & authors 

136 elif role_handler.check_rights("can_edit_submission", submission): 136 ↛ 166line 136 didn't jump to line 166 because the condition on line 136 was always true

137 if not shortlist or can_submit_submission: 137 ↛ 138line 137 didn't jump to line 138 because the condition on line 137 was never true

138 article_actions.append( 

139 Button( 

140 id=f"submission-edit-metadata-{submission.pk}", 

141 title=_("EDIT METADATA"), 

142 icon_class="fa-pen-to-square", 

143 attrs={ 

144 "href": [ 

145 reverse_lazy("mesh:submission_update", kwargs={"pk": submission.pk}) 

146 ], 

147 "class": btn_classes, 

148 }, 

149 ) 

150 ) 

151 article_actions.append( 

152 Button( 

153 id=f"submission-edit-authors-{submission.pk}", 

154 title=_("EDIT AUTHOR"), 

155 icon_class="fa-pen-to-square", 

156 attrs={ 

157 "href": [ 

158 reverse_lazy("mesh:submission_authors", kwargs={"pk": submission.pk}) 

159 ], 

160 "class": btn_classes, 

161 }, 

162 ) 

163 ) 

164 

165 # Create a new submission version 

166 if not can_submit_submission and role_handler.check_rights("can_create_version", submission): 166 ↛ 167line 166 didn't jump to line 167 because the condition on line 166 was never true

167 submission_actions.append( 

168 Button( 

169 id=f"submission-create-version-{submission.pk}", 

170 title=_("SUBMIT REVISIONS"), 

171 icon_class="fa-plus", 

172 attrs={ 

173 "href": [ 

174 reverse_lazy( 

175 "mesh:submission_version_create", 

176 kwargs={"submission_pk": submission.pk}, 

177 ) 

178 ], 

179 "class": btn_classes, 

180 }, 

181 ) 

182 ) 

183 

184 # Edit the current submission version 

185 current_version = submission.current_version 

186 if ( 186 ↛ 191line 186 didn't jump to line 191 because the condition on line 186 was never true

187 not can_submit_submission 

188 and current_version 

189 and role_handler.check_rights("can_edit_version", current_version) 

190 ): 

191 submission_actions.append( 

192 Button( 

193 id=f"submission-edit-version-{submission.pk}", 

194 title=_("RESUME REVISIONS"), 

195 icon_class="fa-list-check", 

196 attrs={ 

197 "href": [ 

198 reverse_lazy( 

199 "mesh:submission_version_update", 

200 kwargs={"pk": current_version.pk}, 

201 ) 

202 ], 

203 "class": btn_classes, 

204 }, 

205 ) 

206 ) 

207 #### [END] Author actions #### 

208 

209 #### [BEGIN] Editor actions #### 

210 if role_handler.check_rights("can_assign_editor", submission): 210 ↛ 247line 210 didn't jump to line 247 because the condition on line 210 was always true

211 """ 

212 <a id="assigned-editors-edit" class="as-button" href="{% url "mesh:submission_editors" pk=submission.pk%}"> 

213 <i class="fa-solid fa-plus"></i> 

214 {% translate "Assign editor" %} 

215 </a> 

216 """ 

217 submission_actions.append( 

218 Button( 

219 id=f"assigned-editors-edit-{submission.pk}", 

220 title=_("ASSIGN EDITOR"), 

221 icon_class="fa-plus", 

222 attrs={ 

223 "class": btn_classes, 

224 "data-modal-href": [ 

225 reverse_lazy("mesh:async_assign_editor", kwargs={"pk": submission.pk}) 

226 ], 

227 "data-reload": ["true"] if kwargs.get("reload_page", False) else ["false"], 

228 "data-reload-id": [kwargs.get("reload_id", "")], 

229 "data-reload-href": ( 

230 [ 

231 reverse_lazy( 

232 "mesh:api_fetch_submission", 

233 kwargs={ 

234 "pk": submission.pk, 

235 "row_id": kwargs.get("reload_id", ""), 

236 }, 

237 ) 

238 ] 

239 if kwargs.get("reload_id", "") != "" 

240 else [""] 

241 ), 

242 }, 

243 ) 

244 ) 

245 

246 # Send the submission version to review 

247 if role_handler.check_rights("can_start_review_process", submission): 247 ↛ 248line 247 didn't jump to line 248 because the condition on line 247 was never true

248 from ...views.forms.editorial_forms import StartReviewProcessForm 

249 

250 form = StartReviewProcessForm(initial={"process": True}) 

251 

252 review_actions.append( 

253 Button( 

254 id=f"submission-send-to-review-{submission.pk}", 

255 title=_("SEND TO REVIEW"), 

256 icon_class="fa-file-import", 

257 form=form, 

258 attrs={ 

259 "href": [ 

260 reverse_lazy( 

261 "mesh:submission_start_review_process", 

262 kwargs={"pk": submission.pk}, 

263 ) 

264 ], 

265 "class": ["as-link"], 

266 }, 

267 ) 

268 ) 

269 

270 # Make an editorial decision 

271 if role_handler.check_rights("can_create_editorial_decision", submission): 271 ↛ 290line 271 didn't jump to line 290 because the condition on line 271 was always true

272 submission_actions.append( 

273 Button( 

274 id=f"submission-editorial-decision-{submission.pk}", 

275 title=_("EDITORIAL DECISION"), 

276 icon_class="fa-plus", 

277 attrs={ 

278 "href": [ 

279 reverse_lazy( 

280 "mesh:editorial_decision_create", 

281 kwargs={"submission_pk": submission.pk}, 

282 ) 

283 ], 

284 "class": btn_classes, 

285 }, 

286 ) 

287 ) 

288 

289 # Request a new review 

290 if current_version and role_handler.check_rights("can_invite_reviewer", current_version): 290 ↛ 291line 290 didn't jump to line 291 because the condition on line 290 was never true

291 review_actions.append( 

292 Button( 

293 id=f"submission-referee-request-{submission.pk}", 

294 title=_("REQUEST REVIEW"), 

295 icon_class="fa-user-group", 

296 attrs={ 

297 "href": [ 

298 reverse_lazy( 

299 "mesh:review_create", 

300 kwargs={"version_pk": submission.current_version.pk}, # type:ignore 

301 ) 

302 ], 

303 "class": btn_classes, 

304 }, 

305 ) 

306 ) 

307 

308 if current_version.number > 1: 

309 # Auto re-assign reviewers, only available after round 1 

310 existing_reviewers = [review.reviewer for review in current_version.reviews.all()] 

311 

312 previous_round_number = current_version.number - 1 

313 previous_round = submission.versions.get(number=previous_round_number) 

314 reviews_that_can_be_auto_reassigned = previous_round.reviews.filter( 

315 state=ReviewState.SUBMITTED.value 

316 ).exclude( 

317 recommendation__in=[ 

318 RecommendationValue.REJECTED.value, 

319 RecommendationValue.RESUBMIT_SOMEWHERE_ELSE.value, 

320 ] 

321 ) 

322 previous_reviewers = [ 

323 review.reviewer 

324 for review in reviews_that_can_be_auto_reassigned 

325 if review.reviewer not in existing_reviewers 

326 ] 

327 

328 if len(previous_reviewers) > 0: 

329 review_actions.append( 

330 Button( 

331 id=f"submission-referee-auto-request-{submission.pk}", 

332 title=_("ASSIGN REVIEWERS OF THE PREVIOUS ROUND"), 

333 icon_class="fa-user-group", 

334 attrs={ 

335 "href": [ 

336 reverse_lazy( 

337 "mesh:review_auto_create", 

338 kwargs={"version_pk": current_version.pk}, # type:ignore 

339 ) 

340 ], 

341 "class": btn_classes, 

342 }, 

343 ) 

344 ) 

345 

346 # if role_handler.check_rights("can_access_submission_log", submission) and not shortlist: 

347 # manage_actions.append( 

348 # Button( 

349 # id=f"submission-log-{submission.pk}", 

350 # title=_("VIEW HISTORY"), 

351 # icon_class="fa-clock-rotate-left", 

352 # attrs={ 

353 # "href": [reverse_lazy("mesh:submission_log", kwargs={"pk": submission.pk})], 

354 # "class": btn_classes, 

355 # }, 

356 # ) 

357 # ) 

358 #### [END] Editor actions #### 

359 

360 #### [BEGIN] Reviewer actions #### 

361 # Submit / Edit the user current review 

362 current_review: Review | None = role_handler.get_from_rights( 

363 "get_current_open_review", current_version 

364 ) 

365 if current_review is not None and current_review.accepted is None: 365 ↛ 367line 365 didn't jump to line 367 because the condition on line 365 was never true

366 # Display 2 buttons: Accept & Decline if the review has not been accepted yet. 

367 review_actions.extend( 

368 [ 

369 Button( 

370 id=f"review-accept-{current_review.pk}", 

371 title=_("Accept review"), 

372 icon_class="fa-check", 

373 attrs={ 

374 "href": [ 

375 add_query_parameters_to_url( 

376 reverse( 

377 "mesh:review_accept", 

378 kwargs={"pk": current_review.pk}, 

379 ), 

380 {"accepted": ["true"]}, 

381 ) 

382 ], 

383 "class": ["as-button", "button-success"], 

384 }, 

385 ), 

386 Button( 

387 id=f"review-accept-{current_review.pk}", 

388 title=_("Decline review"), 

389 icon_class="fa-xmark", 

390 attrs={ 

391 "href": [ 

392 add_query_parameters_to_url( 

393 reverse( 

394 "mesh:review_accept", 

395 kwargs={"pk": current_review.pk}, 

396 ), 

397 {"accepted": ["false"]}, 

398 ) 

399 ], 

400 "class": ["as-button", "button-error"], 

401 }, 

402 ), 

403 ] 

404 ) 

405 elif current_review is not None: 405 ↛ 406line 405 didn't jump to line 406 because the condition on line 405 was never true

406 action_name = _("SUBMIT REVIEW") 

407 if current_review.state in [ 

408 ReviewState.SUBMITTED.value, 

409 ReviewState.DECLINED.value, 

410 ]: 

411 action_name = _("EDIT REVIEW") 

412 review_actions.append( 

413 Button( 

414 id=f"review-{current_review.pk}", 

415 title=action_name, 

416 icon_class="fa-pen-to-square", 

417 attrs={ 

418 "href": [reverse_lazy("mesh:review_update", kwargs={"pk": current_review.pk})], 

419 "class": btn_classes, 

420 }, 

421 ) 

422 ) 

423 #### [END] Reviewer actions #### 

424 action_lists = [] 

425 

426 if article_actions: 426 ↛ 427line 426 didn't jump to line 427 because the condition on line 426 was never true

427 action_lists.append(SubmissionActionList(title=_("Article"), actions=article_actions)) 

428 

429 if submission_actions: 429 ↛ 434line 429 didn't jump to line 434 because the condition on line 429 was always true

430 action_lists.append( 

431 SubmissionActionList(title=_("Submission"), actions=submission_actions) 

432 ) 

433 

434 if review_actions: 434 ↛ 435line 434 didn't jump to line 435 because the condition on line 434 was never true

435 action_lists.append(SubmissionActionList(title=_("Review"), actions=review_actions)) 

436 

437 if manage_actions: 437 ↛ 438line 437 didn't jump to line 438 because the condition on line 437 was never true

438 action_lists.append(SubmissionActionList(title=_("Manage"), actions=manage_actions)) 

439 

440 return action_lists