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

94 statements  

« prev     ^ index     » next       coverage.py v7.9.0, created at 2025-09-10 11:20 +0000

1from dataclasses import dataclass, field 

2 

3from django import forms 

4from django.urls import reverse_lazy 

5from django.utils.translation import gettext_lazy as _ 

6 

7from mesh.model.roles.role_handler import RoleHandler 

8 

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

10from ...models.submission_models import Submission 

11 

12 

13@dataclass 

14class Button: 

15 """ 

16 Interface for a button component. 

17 """ 

18 

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

20 id: str 

21 # Title to display as text in the button 

22 title: str 

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

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

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

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

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

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

29 # to be considered as one. 

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

31 

32 def is_form(self) -> bool: 

33 """ 

34 Whether the button should be displayed as a form. 

35 """ 

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

37 

38 def is_link(self) -> bool: 

39 """ 

40 Whether the button is a link. 

41 """ 

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

43 

44 def is_modal_link(self): 

45 """ 

46 Whether the button is a link. 

47 """ 

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

49 

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

51 """ 

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

53 

54 Ex: 

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

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

57 """ 

58 if attr not in self.attrs: 

59 self.attrs[attr] = [] 

60 

61 for value in values: 

62 if value in self.attrs[attr]: 

63 continue 

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

65 

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

67 """ 

68 Set the given value for the given attribute. 

69 

70 Ex: 

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

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

73 """ 

74 self.attrs[attr] = values 

75 

76 def remove_attr(self, attr: str): 

77 """ 

78 Remove the given attribute from the attributes array. 

79 """ 

80 if attr in self.attrs: 

81 del self.attrs[attr] 

82 

83 

84@dataclass 

85class SubmissionActionList: 

86 title: str 

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

88 

89 

90def build_submission_actions( 

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

92) -> list[SubmissionActionList]: 

93 """ 

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

95 """ 

96 article_actions = [] 

97 submission_actions: list[Button] = [] 

98 review_actions = [] 

99 manage_actions: list[Button] = [] 

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

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

102 

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

104 submission_actions.append( 

105 Button( 

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

107 title=_("VIEW SUBMISSION"), 

108 icon_class="fa-eye", 

109 attrs={ 

110 "href": [ 

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

112 ], 

113 "class": btn_classes, 

114 }, 

115 ) 

116 ) 

117 

118 #### [BEGIN] Author actions #### 

119 # Resume submit process 

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

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

122 submission_actions.append( 

123 Button( 

124 id=f"submission-delete-{submission.pk}", 

125 title=_("DELETE SUBMISSION"), 

126 icon_class="fa-trash", 

127 attrs={ 

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

129 "class": btn_classes, 

130 }, 

131 ) 

132 ) 

133 

134 # submission_actions.append( 

135 # Button( 

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

137 # title=_("RESUME SUBMISSION"), 

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

139 # attrs={ 

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

141 # "class": btn_classes, 

142 # }, 

143 # ) 

144 # ) 

145 

146 # Edit submission metadata & authors 

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

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

149 article_actions.append( 

150 Button( 

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

152 title=_("EDIT METADATA"), 

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

154 attrs={ 

155 "href": [ 

156 reverse_lazy( 

157 "mesh:submission_edit_article_metadata", 

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

159 ) 

160 ], 

161 "class": btn_classes, 

162 }, 

163 ) 

164 ) 

165 article_actions.append( 

166 Button( 

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

168 title=_("EDIT AUTHOR"), 

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

170 attrs={ 

171 "href": [ 

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

173 ], 

174 "class": btn_classes, 

175 }, 

176 ) 

177 ) 

178 

179 # Create a new submission version 

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

181 submission_actions.append( 

182 Button( 

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

184 title=_("SUBMIT REVISIONS"), 

185 icon_class="fa-plus", 

186 attrs={ 

187 "href": [ 

188 reverse_lazy( 

189 "mesh:submission_version_create", 

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

191 ) 

192 ], 

193 "class": btn_classes, 

194 }, 

195 ) 

196 ) 

197 

198 # Edit the current submission version 

199 current_version = submission.current_version 

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

201 not can_submit_submission 

202 and current_version 

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

204 ): 

205 submission_actions.append( 

206 Button( 

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

208 title=_("RESUME REVISIONS"), 

209 icon_class="fa-list-check", 

210 attrs={ 

211 "href": [ 

212 reverse_lazy( 

213 "mesh:submission_version_update", 

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

215 ) 

216 ], 

217 "class": btn_classes, 

218 }, 

219 ) 

220 ) 

221 #### [END] Author actions #### 

222 

223 #### [BEGIN] Editor actions #### 

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

225 """ 

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

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

228 {% translate "Assign editor" %} 

229 </a> 

230 """ 

231 submission_actions.append( 

232 Button( 

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

234 title=_("ASSIGN EDITOR"), 

235 icon_class="fa-plus", 

236 attrs={ 

237 "class": btn_classes, 

238 "data-modal-href": [ 

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

240 ], 

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

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

243 "data-reload-href": ( 

244 [ 

245 reverse_lazy( 

246 "mesh:api_fetch_submission", 

247 kwargs={ 

248 "pk": submission.pk, 

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

250 }, 

251 ) 

252 ] 

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

254 else [""] 

255 ), 

256 }, 

257 ) 

258 ) 

259 

260 # Send the submission version to review 

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

262 from ...views.forms.editorial_forms import StartReviewProcessForm 

263 

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

265 

266 review_actions.append( 

267 Button( 

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

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

270 icon_class="fa-file-import", 

271 form=form, 

272 attrs={ 

273 "href": [ 

274 reverse_lazy( 

275 "mesh:submission_start_review_process", 

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

277 ) 

278 ], 

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

280 }, 

281 ) 

282 ) 

283 

284 # Make an editorial decision 

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

286 submission_actions.append( 

287 Button( 

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

289 title=_("EDITORIAL DECISION"), 

290 icon_class="fa-plus", 

291 attrs={ 

292 "href": [ 

293 reverse_lazy( 

294 "mesh:editorial_decision_create", 

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

296 ) 

297 ], 

298 "class": btn_classes, 

299 }, 

300 ) 

301 ) 

302 

303 # Request a new review 

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

305 review_actions.append( 

306 Button( 

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

308 title=_("REQUEST REVIEW"), 

309 icon_class="fa-user-group", 

310 attrs={ 

311 "href": [ 

312 reverse_lazy( 

313 "mesh:review_create", 

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

315 ) 

316 ], 

317 "class": btn_classes, 

318 }, 

319 ) 

320 ) 

321 

322 if current_version.number > 1: 

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

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

325 

326 previous_round_number = current_version.number - 1 

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

328 reviews_that_can_be_auto_reassigned = previous_round.reviews.filter( 

329 state=ReviewState.SUBMITTED.value 

330 ).exclude( 

331 recommendation__in=[ 

332 RecommendationValue.REJECTED.value, 

333 RecommendationValue.RESUBMIT_SOMEWHERE_ELSE.value, 

334 ] 

335 ) 

336 previous_reviewers = [ 

337 review.reviewer 

338 for review in reviews_that_can_be_auto_reassigned 

339 if review.reviewer not in existing_reviewers 

340 ] 

341 

342 if len(previous_reviewers) > 0: 

343 review_actions.append( 

344 Button( 

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

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

347 icon_class="fa-user-group", 

348 attrs={ 

349 "href": [ 

350 reverse_lazy( 

351 "mesh:review_auto_create", 

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

353 ) 

354 ], 

355 "class": btn_classes, 

356 }, 

357 ) 

358 ) 

359 

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

361 # manage_actions.append( 

362 # Button( 

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

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

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

366 # attrs={ 

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

368 # "class": btn_classes, 

369 # }, 

370 # ) 

371 # ) 

372 #### [END] Editor actions #### 

373 

374 #### [BEGIN] Reviewer actions #### 

375 # Submit / Edit the user current review 

376 current_review: Review | None = role_handler.get_from_rights( 

377 "get_current_open_review", current_version 

378 ) 

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

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

381 review_actions.extend( 

382 [ 

383 Button( 

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

385 title=_("Accept review"), 

386 icon_class="fa-check", 

387 attrs={ 

388 "href": [ 

389 reverse_lazy("mesh:review_accept", kwargs={"pk": current_review.pk}) 

390 ], 

391 # "href": [ 

392 # add_query_parameters_to_url( 

393 # reverse( 

394 # "mesh:review_accept", 

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

396 # ), 

397 # {"accepted": ["true"]}, 

398 # ) 

399 # ], 

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

401 }, 

402 ), 

403 Button( 

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

405 title=_("Decline review"), 

406 icon_class="fa-xmark", 

407 attrs={ 

408 "href": [ 

409 reverse_lazy("mesh:review_decline", kwargs={"pk": current_review.pk}) 

410 ], 

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

412 }, 

413 ), 

414 ] 

415 ) 

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

417 action_name = _("SUBMIT REVIEW") 

418 if current_review.state in [ 

419 ReviewState.SUBMITTED.value, 

420 ReviewState.DECLINED.value, 

421 ]: 

422 action_name = _("EDIT REVIEW") 

423 review_actions.append( 

424 Button( 

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

426 title=action_name, 

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

428 attrs={ 

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

430 "class": btn_classes, 

431 }, 

432 ) 

433 ) 

434 #### [END] Reviewer actions #### 

435 action_lists = [] 

436 

437 if article_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=_("Article"), actions=article_actions)) 

439 

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

441 action_lists.append( 

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

443 ) 

444 

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

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

447 

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

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

450 

451 return action_lists