Coverage for src/mesh/views/views_editorial.py: 28%

174 statements  

« prev     ^ index     » next       coverage.py v7.7.0, created at 2025-04-28 07:45 +0000

1import json 

2from typing import Any 

3 

4from django.contrib import messages 

5from django.http import HttpRequest, HttpResponse, HttpResponseRedirect, JsonResponse 

6from django.shortcuts import get_object_or_404, render 

7from django.urls import reverse_lazy 

8from django.utils.translation import gettext_lazy as _ 

9from django.views.generic import CreateView, FormView, TemplateView, UpdateView, View 

10 

11from mesh.model.file_helpers import post_delete_model_file 

12from mesh.model.roles.editor import Editor, get_section_editors 

13from mesh.model.roles.journal_manager import JournalManager 

14from mesh.views.forms.editorial_forms import ( 

15 EditorialDecisionCreateForm, 

16 EditorialDecisionUpdateForm, 

17 StartReviewProcessForm, 

18) 

19from mesh.views.forms.user_forms import UserForm 

20from mesh.views.mixins import RoleMixin 

21 

22from ..models.editorial_models import EditorialDecision, EditorSubmissionRight 

23from ..models.submission_models import Submission, SubmissionLog 

24from ..models.user_models import User 

25from .components.breadcrumb import get_submission_breadcrumb 

26from .components.button import Button 

27 

28 

29class SendToReviewView(RoleMixin, FormView): 

30 """ 

31 View to send a submission with its curent version to the Review process. 

32 

33 It sets the version's boolean `review_open = True` and switch the submission 

34 state to `on_review`. 

35 """ 

36 

37 submission: Submission 

38 restricted_roles = [Editor, JournalManager] 

39 form_class = StartReviewProcessForm 

40 

41 def restrict_dispatch(self, request: HttpRequest, *args, **kwargs) -> bool: 

42 self.submission = get_object_or_404(Submission, pk=kwargs["pk"]) 

43 return not self.role_handler.check_rights("can_start_review_process", self.submission) 

44 

45 def get_success_url(self) -> str: 

46 return reverse_lazy("mesh:submission_details", kwargs={"pk": self.submission.pk}) 

47 

48 def form_valid(self, form: Any) -> HttpResponse: 

49 self.submission.start_review_process(self.request.user) 

50 messages.success(self.request, _("The submission has been sent to review.")) 

51 return super().form_valid(form) 

52 

53 

54class EditorialDecisionCreateView(RoleMixin, CreateView): 

55 """ 

56 View for creating an editorial decision. 

57 """ 

58 

59 model = EditorialDecision 

60 form_class = EditorialDecisionCreateForm 

61 template_name = "mesh/forms/form_full_page.html" 

62 restricted_roles = [JournalManager, Editor] 

63 submission: Submission 

64 

65 def restrict_dispatch(self, request: HttpRequest, *args, **kwargs) -> bool: 

66 self.submission = get_object_or_404(Submission, pk=kwargs["submission_pk"]) 

67 

68 return not self.role_handler.check_rights("can_create_editorial_decision", self.submission) 

69 

70 def get_success_url(self) -> str: 

71 return reverse_lazy("mesh:submission_details", kwargs={"pk": self.submission.pk}) 

72 

73 def form_valid(self, form: EditorialDecisionCreateForm) -> HttpResponse: 

74 form.instance._user = self.request.user 

75 form.instance.version = self.submission.current_version 

76 

77 response = super().form_valid(form) 

78 

79 self.submission.apply_editorial_decision(self.object, self.request.user) # type:ignore 

80 

81 messages.success(self.request, _("Editorial decision successfully submitted")) 

82 

83 return response 

84 

85 def get_context_data(self, *args, **kwargs): 

86 context = super().get_context_data(*args, **kwargs) 

87 

88 context["page_title"] = _("New editorial decision") 

89 

90 submission_url = reverse_lazy("mesh:submission_details", kwargs={"pk": self.submission.pk}) 

91 description = "Please fill the form below to submit your editorial decision " 

92 description += ( 

93 f"for the submission <a href='{submission_url}'><i>{self.submission.name}</i></a>." 

94 ) 

95 context["form_description"] = [description] 

96 

97 # Breadcrumnb 

98 breadcrumb = get_submission_breadcrumb(self.submission) 

99 breadcrumb.add_item( 

100 title=_("New editorial decision"), 

101 url=reverse_lazy( 

102 "mesh:editorial_decision_create", 

103 kwargs={"submission_pk": self.submission.pk}, 

104 ), 

105 ) 

106 context["breadcrumb"] = breadcrumb 

107 

108 return context 

109 

110 

111class EditorialDecisionUpdateView(RoleMixin, UpdateView): 

112 """ 

113 View for updating an existing editorial decision. 

114 """ 

115 

116 model: type[EditorialDecision] = EditorialDecision 

117 form_class = EditorialDecisionUpdateForm 

118 template_name = "mesh/forms/form_full_page.html" 

119 restricted_roled = [JournalManager, Editor] 

120 decision: EditorialDecision 

121 

122 def restrict_dispatch(self, request: HttpRequest, *args, **kwargs) -> bool: 

123 self.decision = get_object_or_404(self.model, pk=kwargs["pk"]) 

124 

125 return not self.role_handler.check_rights("can_edit_editorial_decision", self.decision) 

126 

127 def get_success_url(self) -> str: 

128 return reverse_lazy( 

129 "mesh:submission_details", kwargs={"pk": self.decision.version.submission.pk} 

130 ) 

131 

132 def get_object(self, *args, **kwargs) -> EditorialDecision: 

133 return self.decision 

134 

135 def form_valid(self, form: EditorialDecisionUpdateForm) -> HttpResponse: 

136 form.instance._user = self.request.user 

137 if not form.changed_data: 

138 form.instance.do_not_update = True 

139 

140 response = super().form_valid(form) 

141 

142 decision_str = self.decision.get_value_display() # type: ignore 

143 

144 if form.has_changed(): 

145 SubmissionLog.add_message( 

146 self.decision.version.submission, 

147 _("Editorial decision updated") + f": {decision_str}", 

148 f"Editorial decision updated: {decision_str}", 

149 user=self.request.user, 

150 ) 

151 messages.success(self.request, _("Editorial decision successfully updated")) 

152 

153 return response 

154 

155 def get_context_data(self, *args, **kwargs): 

156 context = super().get_context_data(*args, **kwargs) 

157 

158 context["page_title"] = _("Edit editorial decision") 

159 

160 submission_url = reverse_lazy( 

161 "mesh:submission_details", kwargs={"pk": self.decision.version.submission.pk} 

162 ) 

163 description = "Please fill the form below to edit your editorial decision " 

164 description += f"for the submission <a href='{submission_url}'><i>{self.decision.version.submission.name}</i></a>." 

165 context["form_description"] = [description] 

166 

167 # Breadcrumnb 

168 breadcrumb = get_submission_breadcrumb(self.decision.version.submission) 

169 breadcrumb.add_item( 

170 title=_("New editorial decision"), 

171 url=reverse_lazy( 

172 "mesh:editorial_decision_create", 

173 kwargs={"submission_pk": self.decision.version.submission.pk}, 

174 ), 

175 ) 

176 context["breadcrumb"] = breadcrumb 

177 

178 return context 

179 

180 def post(self, request: HttpRequest, *args: str, **kwargs: Any) -> HttpResponse: 

181 """ 

182 View for updating the editorial decision or deleting one of 

183 the associated files. 

184 """ 

185 deletion_requested, _ = post_delete_model_file(self.decision, request, self.role_handler) 

186 

187 if deletion_requested: 

188 # Update the submission object. 

189 self.decision = get_object_or_404(self.model, pk=self.decision.pk) 

190 return self.get(request, *args, **kwargs) 

191 

192 return super().post(request, *args, **kwargs) 

193 

194 

195class AssignEditorView(RoleMixin, TemplateView): 

196 """ 

197 View to add/remove editors assigned to a given submission. 

198 """ 

199 

200 submission: Submission 

201 template_name = "mesh/forms/assign_editor.html" 

202 

203 def restrict_dispatch(self, request: HttpRequest, *args, **kwargs) -> bool: 

204 self.submission = get_object_or_404(Submission, pk=kwargs["pk"]) 

205 return not self.role_handler.check_rights("can_assign_editor", self.submission) 

206 

207 def get_context_data(self, *args, **kwargs): 

208 """ 

209 2 models are used to compute editor rights over a submission. 

210 Section rights & Submission rights. 

211 This view will display all editors having right over the given submission. 

212 """ 

213 context = super().get_context_data(*args, **kwargs) 

214 context["submission"] = self.submission 

215 

216 direct_editors = User.objects.filter(editor_submissions__submission=self.submission) 

217 context["assigned_editors"] = [ 

218 { 

219 "user": editor, 

220 "buttons": [ 

221 Button( 

222 id=f"remove-editor-{editor.pk}", 

223 title=_("Remove"), 

224 icon_class="fa-trash", 

225 form=UserForm( 

226 _users=direct_editors, 

227 _hide_user=True, 

228 initial={"user": editor}, 

229 ), 

230 attrs={ 

231 "href": [ 

232 reverse_lazy( 

233 "mesh:submission_editors", 

234 kwargs={"pk": self.submission.pk}, 

235 ) 

236 ], 

237 "class": ["button-error"], 

238 "type": ["submit"], 

239 "name": ["_action_remove"], 

240 }, 

241 ) 

242 ], 

243 } 

244 for editor in direct_editors 

245 ] 

246 

247 excluded_user_pks = [e.pk for e in direct_editors] 

248 context["form"] = UserForm( 

249 _users=User.objects.exclude(pk__in=excluded_user_pks), _user_label=_("User") 

250 ) 

251 

252 section_editors = [] 

253 for editor in get_section_editors(self.submission): 

254 editor_data: dict[str, Any] = {"user": editor, "buttons": []} 

255 if editor not in direct_editors: 

256 editor_data["buttons"].append( 

257 Button( 

258 id=f"assign-editor-{editor.pk}", 

259 title=_("Assign"), 

260 icon_class="fa-plus", 

261 form=UserForm( 

262 _users=direct_editors, 

263 _hide_user=True, 

264 initial={"user": editor}, 

265 ), 

266 attrs={ 

267 "href": [ 

268 reverse_lazy( 

269 "mesh:submission_editors", 

270 kwargs={"pk": self.submission.pk}, 

271 ) 

272 ], 

273 "type": ["submit"], 

274 }, 

275 ) 

276 ) 

277 else: 

278 editor_data["buttons"].append( 

279 Button( 

280 id=f"assigned-editor-{editor.pk}", 

281 title=_("Assigned"), 

282 icon_class="fa-check", 

283 attrs={"class": ["inactive"]}, 

284 ) 

285 ) 

286 section_editors.append(editor_data) 

287 context["section_editors"] = section_editors 

288 

289 context["form_save_title"] = _("Add editor") 

290 context["form_save_icon"] = "fa-plus" 

291 context["page_title"] = _("Assigned editors") 

292 

293 # Breadcrumnb 

294 breadcrumb = get_submission_breadcrumb(self.submission) 

295 breadcrumb.add_item( 

296 title=_("Assigned editors"), 

297 url=reverse_lazy("mesh:submission_editors", kwargs={"pk": self.submission.pk}), 

298 ) 

299 context["breadcrumb"] = breadcrumb 

300 

301 return context 

302 

303 def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: 

304 remove_form = "_action_remove" in request.POST 

305 if remove_form: 

306 users = User.objects.filter(editor_submissions__submission=self.submission) 

307 else: 

308 users = User.objects.exclude(editor_submissions__submission=self.submission) 

309 

310 response = HttpResponseRedirect( 

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

312 ) 

313 form = UserForm(request.POST, _users=users) 

314 if not form.is_valid(): 

315 messages.error(request, "ERROR") 

316 return response 

317 

318 user = form.cleaned_data["user"] 

319 

320 if remove_form: 

321 EditorSubmissionRight.objects.get(submission=self.submission, user=user).delete() 

322 messages.success( 

323 request, 

324 _(f"{user} was successfully removed from the assigned editors of the submission."), 

325 ) 

326 SubmissionLog.add_message( 

327 self.submission, 

328 content=_(f"{user} removed from the assigned editors."), 

329 content_en=f"{user} removed from the assigned editors.", 

330 user=self.request.user, 

331 significant=True, 

332 ) 

333 else: 

334 EditorSubmissionRight.objects.create(submission=self.submission, user=user) 

335 messages.success( 

336 request, 

337 _(f"{user} was successfully assigned as an editor for the submission."), 

338 ) 

339 SubmissionLog.add_message( 

340 self.submission, 

341 content=_(f"{user} assigned as an editor."), 

342 content_en=f"{user} assigned as an editor.", 

343 user=self.request.user, 

344 significant=True, 

345 ) 

346 

347 return response 

348 

349 

350class AssignEditorAPIView(View): 

351 def get(self, request, *args, **kwargs): 

352 submission = get_object_or_404(Submission, pk=kwargs["pk"]) 

353 users = User.objects.all() 

354 editors = User.objects.filter(editor_submissions__submission=submission) 

355 for user in users: 

356 if user in editors: 

357 user.selected = True 

358 

359 return render( 

360 request, 

361 "mesh/forms/assign_editor_modal.html", 

362 {"users": users, "submission_proxy_pk": kwargs["pk"]}, 

363 ) 

364 

365 def post(self, request, *args, **kwargs): 

366 submission = get_object_or_404(Submission, pk=kwargs["pk"]) 

367 existing_pks = User.objects.filter(editor_submissions__submission=submission).values_list( 

368 "pk", flat=True 

369 ) 

370 

371 editors_data = request.POST.get("select-editors", "") 

372 editors = json.loads(editors_data) 

373 new_pks = [int(pk) for pk in editors] 

374 

375 for pk in existing_pks: 

376 if pk not in new_pks: 

377 user = User.objects.get(pk=pk) 

378 EditorSubmissionRight.objects.get(submission=submission, user=user).delete() 

379 

380 SubmissionLog.add_message( 

381 submission, 

382 content=_(f"{user} removed from the assigned editors."), 

383 content_en=f"{user} removed from the assigned editors.", 

384 user=self.request.user, 

385 significant=True, 

386 ) 

387 

388 for pk in new_pks: 

389 if pk not in existing_pks: 

390 user = User.objects.get(pk=pk) 

391 

392 EditorSubmissionRight.objects.create(submission=submission, user=user) 

393 

394 SubmissionLog.add_message( 

395 submission, 

396 content=_(f"{user} assigned as an editor."), 

397 content_en=f"{user} assigned as an editor.", 

398 user=self.request.user, 

399 significant=True, 

400 ) 

401 

402 return JsonResponse({"message": "OK"})