Coverage for src / mesh / views / views_submission_edit.py: 30%

515 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-03-26 09:28 +0000

1import json 

2from typing import Any 

3 

4from django.contrib import messages 

5from django.db.models import QuerySet 

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

7from django.shortcuts import get_object_or_404 

8from django.urls import reverse_lazy 

9from django.utils.translation import gettext_lazy as _ 

10from django.views.generic import FormView, TemplateView, View 

11from django.views.generic.edit import CreateView, DeleteView, UpdateView 

12from matching_back.views import ArticleEditFormWithVueAPIView 

13from ptf.external.fetch_metadata import fetch_article 

14from ptf.model_data import create_articledata, create_contributor 

15 

16from mesh.model.file_helpers import post_delete_model_file 

17from mesh.model.roles.author import Author 

18from mesh.views.forms.base_forms import FormAction, HiddenModelChoiceForm 

19from mesh.views.forms.submission_forms import ( 

20 SubmissionAuthorForm, 

21 SubmissionConfirmForm, 

22 SubmissionCreateForm, 

23 SubmissionDeleteForm, 

24 SubmissionEditArticleMetadataForm, 

25 SubmissionInfoForm, 

26 SubmissionVersionForm, 

27) 

28from mesh.views.mixins import RoleMixin 

29 

30from ..app_settings import app_settings 

31from ..model.roles.journal_manager import JournalManager 

32from ..models.review_models import ReviewState 

33from ..models.submission_models import ( 

34 Submission, 

35 SubmissionAuthor, 

36 SubmissionLog, 

37 SubmissionMainFile, 

38 SubmissionVersion, 

39) 

40from ..models.user_models import Suggestion 

41from ..views.views_base import SUBMIT_QUERY_PARAMETER, SubmittableModelFormMixin 

42from ..views.views_reviewer import add_suggestion, add_suggestion_from_person 

43from .components.button import Button 

44from .components.stepper import get_submission_stepper 

45from .model_proxy import SubmissionProxy 

46 

47 

48class SubmissionCreateView(RoleMixin, CreateView): 

49 """ 

50 View for creating a new submission with a preprint id. 

51 """ 

52 

53 model = Submission 

54 form_class = SubmissionCreateForm 

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

56 restricted_roles = [Author] 

57 

58 def restrict_dispatch(self, request: HttpRequest, *args, **kwargs): 

59 self.submission = None 

60 return not self.role_handler.check_rights("can_create_submission") 

61 

62 def get_success_url(self) -> str: 

63 """ 

64 Redirects to the SubmissionAuthor view to add authors to the 

65 newly created submission. 

66 """ 

67 

68 return reverse_lazy( 

69 "mesh:submission_edit_article_metadata", kwargs={"pk": self.submission.pk} 

70 ) 

71 

72 def form_valid(self, form: SubmissionCreateForm) -> HttpResponse: 

73 preprint_id = form.cleaned_data["preprint_id"] 

74 

75 title = "" 

76 abstract = "" 

77 article_data = None 

78 if preprint_id != "": 

79 article_data = fetch_article(preprint_id, with_bib=False) 

80 

81 title = article_data.title_html 

82 abstract = "" 

83 if len(article_data.abstracts) > 0: 

84 abstract = article_data.abstracts[0]["value_html"] 

85 

86 self.submission = Submission(name=title, abstract=abstract, author_agreement=False) 

87 self.submission._user = self.request.user 

88 self.submission.save() 

89 

90 version = SubmissionVersion(submission=self.submission) 

91 version._user = self.request.user 

92 version.save() 

93 

94 if article_data is not None: 

95 for contrib in article_data.contributors: 

96 author = SubmissionAuthor( 

97 submission=self.submission, 

98 first_name=contrib["first_name"], 

99 last_name=contrib["last_name"], 

100 email=contrib["email"], 

101 corresponding=contrib["corresponding"], 

102 ) 

103 author.save() 

104 

105 SubmissionLog.add_message( 

106 self.submission, 

107 content=_("Creation of the submission"), 

108 content_en="Creation of the submission", 

109 user=self.request.user, 

110 significant=True, 

111 ) 

112 

113 return HttpResponseRedirect(self.get_success_url()) 

114 

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

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

117 context["page_title"] = _("New submission") 

118 

119 # context["data-get-url"] = reverse_lazy("mesh:submission_create_vuejs") 

120 

121 # Stepper 

122 step_id = "preprint" 

123 stepper = get_submission_stepper(None) 

124 stepper.set_active_step(step_id) 

125 context["stepper"] = stepper 

126 

127 # Form buttons 

128 # No Save/Next button in the form, they are put in the stepper 

129 context["form"].buttons = [] # = submission_stepper_form_buttons() 

130 

131 descriptions = [ 

132 "You are about to start the submit process.", 

133 'Please read our guidelines <a href="">here</a>', 

134 "If you have a preprint, you can enter its id below. Leave the field blank otherwise.", 

135 ] 

136 context["form_description"] = descriptions 

137 

138 # # Generate breadcrumb data 

139 # breadcrumb = get_base_breadcrumb() 

140 # breadcrumb.add_item(title=_("New submission"), url=reverse_lazy("mesh:submission_create")) 

141 # context["breadcrumb"] = breadcrumb 

142 return context 

143 

144 

145class SubmissionPreprintUpdateView(RoleMixin, UpdateView): 

146 """ 

147 View for updating a submission with a new preprint id. 

148 """ 

149 

150 model = Submission 

151 form_class = SubmissionCreateForm 

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

153 restricted_roles = [Author] 

154 

155 def restrict_dispatch(self, request: HttpRequest, *args, **kwargs): 

156 pk = kwargs["pk"] 

157 self.submission = get_object_or_404(self.model, pk=pk) 

158 

159 return not self.role_handler.check_rights("can_edit_submission", self.submission) 

160 

161 def get_object(self, *args, **kwargs) -> Submission: 

162 """ 

163 Override `get_object` built-in method to avoid an additional look-up when 

164 we already have the object loaded. 

165 """ 

166 return self.submission 

167 

168 def get_success_url(self) -> str: 

169 """ 

170 Redirects to the SubmissionAuthor view to add authors to the 

171 newly created submission. 

172 """ 

173 

174 return reverse_lazy("mesh:submission_edit_article_metadata", kwargs={"pk": self.object.pk}) 

175 

176 def form_valid(self, form: SubmissionCreateForm) -> HttpResponse: 

177 preprint_id = form.cleaned_data["preprint_id"] 

178 

179 if preprint_id != "": 

180 article_data = fetch_article(preprint_id, with_bib=False) 

181 

182 title = article_data.title_html 

183 self.submission.name = title 

184 

185 if len(article_data.abstracts) > 0: 

186 abstract = article_data.abstracts[0]["value_tex"] 

187 self.submission.abstract = abstract 

188 

189 self.submission._user = self.request.user 

190 self.submission.save() 

191 

192 current_version = self.submission.current_version 

193 current_version._user = self.request.user 

194 current_version.save() 

195 

196 SubmissionAuthor.objects.filter(submission=self.submission).delete() 

197 

198 for contrib in article_data.contributors: 

199 author = SubmissionAuthor( 

200 submission=self.submission, 

201 first_name=contrib["first_name"], 

202 last_name=contrib["last_name"], 

203 email=contrib["email"], 

204 corresponding=contrib["corresponding"], 

205 ) 

206 author.save() 

207 

208 SubmissionLog.add_message( 

209 self.submission, 

210 content=_("Update of the submission with a preprint id"), 

211 content_en="Update of the submission with a preprint id", 

212 user=self.request.user, 

213 significant=True, 

214 ) 

215 

216 return HttpResponseRedirect(self.get_success_url()) 

217 

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

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

220 context["page_title"] = _("Update submission preprint id") 

221 

222 # context["data-get-url"] = reverse_lazy("mesh:submission_create_vuejs") 

223 

224 # Stepper 

225 step_id = "preprint" 

226 stepper = get_submission_stepper(self.submission) 

227 stepper.set_active_step(step_id) 

228 context["stepper"] = stepper 

229 

230 # Form buttons 

231 # No Save/Next button in the form, they are put in the stepper 

232 context["form"].buttons = [] # = submission_stepper_form_buttons() 

233 

234 descriptions = [ 

235 "You are about to start the submit process.", 

236 'Please read our guidelines <a href="">here</a>', 

237 "If you have a preprint, you can enter its id below. Leave the field blank otherwise.", 

238 ] 

239 context["form_description"] = descriptions 

240 

241 # # Generate breadcrumb data 

242 # breadcrumb = get_base_breadcrumb() 

243 # breadcrumb.add_item(title=_("New submission"), url=reverse_lazy("mesh:submission_create")) 

244 # context["breadcrumb"] = breadcrumb 

245 return context 

246 

247 

248class SubmissionEditArticleMetadataVuejsAPIView(RoleMixin, ArticleEditFormWithVueAPIView): 

249 def __init__(self, **kwargs): 

250 super().__init__(**kwargs) 

251 self.fields_to_update = ["contributors", "abstracts", "titles", "pdf"] 

252 self.editorial_tools = ["light_authors", "inline_ckeditor"] 

253 self.mandatory_fields = ["contributors", "abstract", "title", "pdf"] 

254 

255 self.submission = None 

256 self.version = None 

257 self.submission_main_file = None 

258 

259 def restrict_dispatch(self, request: HttpRequest, *args, **kwargs): 

260 pk = kwargs["pk"] 

261 self.submission = get_object_or_404(Submission, pk=pk) 

262 

263 return not self.role_handler.check_rights("can_edit_submission", self.submission) 

264 

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

266 colid = app_settings.COLID 

267 data_article = create_articledata() 

268 data = self.convert_data_for_vuejs3(data_article, colid=colid) 

269 data["editorial_tools"] = self.editorial_tools 

270 

271 def obj_to_dict(obj): 

272 return obj.__dict__ 

273 

274 data["article"]["colid"] = colid 

275 dump = json.dumps(data, default=obj_to_dict) 

276 return HttpResponse(dump, content_type="application/json") 

277 

278 def handle_pdf_for_vuejs3(self, data, data_article, method=""): 

279 if method == "get": 

280 submission_main_file = getattr(self.submission.current_version, "main_file", None) 

281 if submission_main_file is not None: 

282 data["article"]["pdf_name"] = submission_main_file.file.name 

283 else: 

284 data["article"]["pdf_name"] = "No file uploaded" 

285 

286 elif method == "post" and "pdf" in self.request.FILES: 

287 submission_main_file = getattr(self.submission.current_version, "main_file", None) 

288 if submission_main_file is not None: 

289 submission_main_file.delete() 

290 

291 submission_main_file = SubmissionMainFile( 

292 attached_to=self.submission.current_version, file=self.request.FILES["pdf"] 

293 ) 

294 submission_main_file.save() 

295 

296 def handle_titles_for_vuejs3(self, data, data_article, method=""): 

297 if method == "get": 

298 data["article"]["titles"] = [ 

299 {"lang": data_article.lang, "value": self.submission.name} 

300 ] 

301 else: 

302 super().handle_titles_for_vuejs3(data, data_article, method) 

303 

304 def handle_contributors_for_vuejs3(self, data, data_article, method=""): 

305 if method == "get": 

306 contributors = [] 

307 for author in self.submission.authors.all(): 

308 contrib = create_contributor() 

309 contrib["first_name"] = author.first_name 

310 contrib["last_name"] = author.last_name 

311 contrib["email"] = author.email 

312 contrib["corresponding"] = author.corresponding 

313 contributors.append(contrib) 

314 data["article"]["contributors"] = contributors 

315 else: 

316 super().handle_contributors_for_vuejs3(data, data_article, method) 

317 

318 def handle_abstracts_for_vuejs3(self, data, data_article, method=""): 

319 if method == "get": 

320 data["article"]["abstracts"] = [{"lang": "en", "value": self.submission.abstract}] 

321 else: 

322 super().handle_abstracts_for_vuejs3(data, data_article, method) 

323 

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

325 body_unicode = request.POST.get("data", "") 

326 data = json.loads(body_unicode) 

327 

328 if ( 

329 data["title_html"][0]["value"] == "" 

330 or data["abstracts"][0]["value_html"] == "" 

331 or len(data["contributors"]) == 0 

332 or ("pdf" not in self.request.FILES and data["pdf"]["pdf_name"] == "No file uploaded") 

333 ): 

334 return JsonResponse({"status_code": 400}) 

335 

336 data["colid"] = app_settings.COLID 

337 

338 article_data = self.convert_data_from_vuejs3(data) 

339 title = article_data.title_html 

340 

341 abstract = article_data.abstracts[0]["value_html"] 

342 

343 self.submission.name = title 

344 self.submission.abstract = abstract 

345 self.submission._user = request.user 

346 self.submission.save() 

347 

348 current_version = self.submission.current_version 

349 current_version._user = request.user 

350 current_version.save() 

351 

352 SubmissionAuthor.objects.filter(submission=self.submission).delete() 

353 

354 for contrib in article_data.contributors: 

355 author = SubmissionAuthor( 

356 submission=self.submission, 

357 first_name=contrib["first_name"], 

358 last_name=contrib["last_name"], 

359 email=contrib["email"], 

360 corresponding=contrib["corresponding"], 

361 ) 

362 author.save() 

363 

364 data["redirect"] = { 

365 "redirect": True, 

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

367 } 

368 

369 return JsonResponse(data) 

370 

371 

372class SubmissionRedirectFromVue(TemplateView): 

373 template_name = "mesh/forms/form_full_page_redirect.html" 

374 

375 def get_context_data(self, **kwargs): 

376 context = super().get_context_data(**kwargs) 

377 context["message"] = ( 

378 "La sauvegarde dans Vuejs puis la redirection sur une autre page fonctionne" 

379 ) 

380 return context 

381 

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

383 """ 

384 Override the get method to set the form action to "create". 

385 """ 

386 

387 messages.success(self.request, _("New submission successfully created.")) 

388 

389 return super().get(request, *args, **kwargs) 

390 

391 

392class SubmissionInfoView(RoleMixin, UpdateView): 

393 """ 

394 View for updating the submission info. 

395 """ 

396 

397 model: type[Submission] = Submission 

398 form_class = SubmissionInfoForm 

399 template_name = "mesh/forms/submission_info.html" 

400 submission: Submission 

401 restricted_roles = [Author] 

402 message_on_restrict = False 

403 

404 def restrict_dispatch(self, request: HttpRequest, *args, **kwargs): 

405 pk = kwargs["pk"] 

406 self.submission = get_object_or_404(self.model, pk=pk) 

407 

408 return not self.role_handler.check_rights("can_edit_submission", self.submission) 

409 

410 def get_success_url(self) -> str: 

411 # if FormAction.NEXT.value in self.request.POST: 

412 # return reverse_lazy("mesh:submission_authors", kwargs={"pk": self.submission.pk}) 

413 

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

415 

416 def get_fail_redirect_uri(self) -> str: 

417 """ 

418 Return to the submission details page if the user cannot update the submission. 

419 """ 

420 return self.get_success_url() 

421 

422 def get_object(self, *args, **kwargs) -> Submission: 

423 """ 

424 Override `get_object` built-in method to avoid an additional look-up when 

425 we already have the object loaded. 

426 """ 

427 return self.submission 

428 

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

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

431 context["page_title"] = _("Edit submission info") 

432 

433 if self.submission.is_draft: 

434 step_id = "info" 

435 stepper = get_submission_stepper(self.submission) 

436 stepper.set_active_step(step_id) 

437 context["stepper"] = stepper 

438 

439 # No Save/Next button in the form, they are put in the stepper 

440 context["form"].buttons = [] # = submission_stepper_form_buttons() 

441 

442 suggestions = self.submission.suggestions_for_reviewer.all() 

443 for suggestion in suggestions: 

444 reviewer = ( 

445 suggestion.suggested_reviewer 

446 if suggestion.suggested_reviewer is not None 

447 else suggestion.suggested_user 

448 ) 

449 if suggestion.suggest_to_avoid: 

450 context["avoid_reviewer"] = reviewer 

451 else: 

452 context["suggested_reviewer"] = reviewer 

453 

454 return context 

455 

456 def form_valid(self, form): 

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

458 

459 Suggestion.objects.filter(submission=self.submission).delete() 

460 

461 for prefix in ["reviewer", "avoid"]: 

462 person_info = { 

463 "first_name": form.data.get(f"{prefix}_first_name", ""), 

464 "last_name": form.data.get(f"{prefix}_last_name", ""), 

465 "email": form.data.get(f"{prefix}_email", ""), 

466 } 

467 

468 if person_info["email"] != "": 

469 add_suggestion_from_person( 

470 self.submission, person_info, suggest_to_avoid=prefix == "avoid" 

471 ) 

472 

473 return super().form_valid(form) 

474 

475 

476class SubmissionEditArticleMetadataView(RoleMixin, UpdateView): 

477 """ 

478 View for updating a submission (only the metadata). 

479 Only the GET is used to display the form (with VueJS) 

480 The POST is handled by SubmissionEditArticleMetadataVuejsAPIView 

481 """ 

482 

483 model: type[Submission] = Submission 

484 form_class = SubmissionEditArticleMetadataForm 

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

486 submission: Submission 

487 restricted_roles = [Author] 

488 message_on_restrict = False 

489 

490 def restrict_dispatch(self, request: HttpRequest, *args, **kwargs): 

491 pk = kwargs["pk"] 

492 self.submission = get_object_or_404(self.model, pk=pk) 

493 

494 return not self.role_handler.check_rights("can_edit_submission", self.submission) 

495 

496 # def get_fail_redirect_uri(self) -> str: 

497 # """ 

498 # Return to the submission details page if the user cannot update the submission. 

499 # Not used with VueJS 

500 # """ 

501 # return self.get_success_url() 

502 

503 def get_object(self, *args, **kwargs) -> Submission: 

504 """ 

505 Override `get_object` built-in method to avoid an additional look-up when 

506 we already have the object loaded. 

507 """ 

508 return self.submission 

509 

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

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

512 context["page_title"] = _("Edit article metadata") 

513 

514 # data-get-url is used by VueJS to know the GET url to call 

515 context["data_get_url"] = reverse_lazy( 

516 "mesh:submission_update_vuejs", 

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

518 ) 

519 

520 if self.submission.is_draft: 

521 step_id = "metadata" 

522 stepper = get_submission_stepper(self.submission) 

523 stepper.set_active_step(step_id) 

524 context["stepper"] = stepper 

525 

526 # No Save/Next button in the form, they are put in the stepper 

527 context["form"].buttons = [] # = submission_stepper_form_buttons() 

528 

529 # Generate breadcrumb data 

530 # breadcrumb = get_submission_breadcrumb(self.submission) 

531 # breadcrumb.add_item( 

532 # title=_("Edit metadata"), 

533 # url=reverse_lazy("mesh:submission_edit_article_metadata", kwargs={"pk": self.submission.pk}), 

534 # ) 

535 # context["breadcrumb"] = breadcrumb 

536 return context 

537 

538 def get_success_url(self) -> str: 

539 if FormAction.NEXT.value in self.request.POST: 

540 # No POST with VueJS. We shouldn't reach this clause 

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

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

543 

544 # def form_valid(self, form: SubmissionUpdateForm) -> HttpResponse: 

545 # form.instance._user = self.request.user 

546 # if not form.changed_data: 

547 # form.instance.do_not_update = True 

548 # 

549 # response = super().form_valid(form) 

550 # 

551 # if form.has_changed(): 

552 # SubmissionLog.add_message( 

553 # self.submission, 

554 # content=_("Modification of the submission's metadata"), 

555 # content_en="Modification of the submission's metadata", 

556 # user=self.request.user, 

557 # ) 

558 # return response 

559 

560 

561class SubmissionVersionCreateView(RoleMixin, SubmittableModelFormMixin, CreateView): 

562 """ 

563 View for creating a new submission version. 

564 """ 

565 

566 model = SubmissionVersion 

567 form_class = SubmissionVersionForm 

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

569 submission: Submission 

570 restricted_roles = [Author] 

571 save_submission = False 

572 add_confirm_message = False 

573 

574 def get_form_kwargs(self): 

575 kwargs = super().get_form_kwargs() 

576 kwargs["submission"] = self.submission 

577 return kwargs 

578 

579 def form_valid(self, form): 

580 form.instance.submission = self.submission 

581 return super().form_valid(form) 

582 

583 def restrict_dispatch(self, request: HttpRequest, *args, **kwargs): 

584 submission_pk = kwargs["submission_pk"] 

585 self.submission = get_object_or_404(Submission, pk=submission_pk) 

586 

587 return not self.role_handler.check_rights("can_create_version", self.submission) 

588 

589 def get_success_url(self) -> str: 

590 if FormAction.NEXT.value in self.request.POST: 

591 # No POST with VueJS. We shouldn't reach this clause 

592 return self.submit_url() 

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

594 

595 def submit_url(self) -> str: 

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

597 

598 # def form_pre_save(self, form: SubmissionVersionForm): 

599 # Not used with VueJS 

600 # form.instance._user = self.request.user 

601 # form.instance.submission = self.submission 

602 

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

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

605 current_version = self.submission.current_version 

606 version = current_version.number + 1 if current_version else 1 

607 context["page_title"] = _("Submission files") 

608 if not self.submission.is_draft: 

609 context["page_title"] += f" - Version {version}" 

610 

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

612 description = "Please fill the form below to submit your files for the submission " 

613 description += f"<a href='{submission_url}'><i>{self.submission.name}</i></a>.<br>" 

614 context["form_description"] = [description] 

615 

616 if self.submission.is_draft: 

617 stepper = get_submission_stepper(self.submission) 

618 stepper.set_active_step("version") 

619 context["stepper"] = stepper 

620 context["form"].buttons = [] 

621 

622 # # Generate breadcrumb data 

623 # breadcrumb = get_submission_breadcrumb(self.submission) 

624 # breadcrumb.add_item( 

625 # title=_("New version"), 

626 # url=reverse_lazy( 

627 # "mesh:submission_version_create", kwargs={"submission_pk": self.submission.pk} 

628 # ), 

629 # ) 

630 # context["breadcrumb"] = breadcrumb 

631 return context 

632 

633 # def form_valid(self, form): 

634 # result = super().form_valid(form) 

635 # return result 

636 

637 

638class SubmissionVersionUpdateView(RoleMixin, SubmittableModelFormMixin, UpdateView): 

639 """ 

640 View for updating a submission version. 

641 

642 Submitting a version = submitting the whole submission. 

643 This requires extra care about checking that all previous steps have been 

644 taken when this is the first version. 

645 """ 

646 

647 model: type[SubmissionVersion] = SubmissionVersion 

648 form_class = SubmissionVersionForm 

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

650 version: SubmissionVersion 

651 restricted_roles = [Author] 

652 message_on_restrict = False 

653 save_submission = False 

654 add_confirm_message = False 

655 

656 def restrict_dispatch(self, request: HttpRequest, *args, **kwargs): 

657 pk = kwargs["pk"] 

658 self.version = get_object_or_404(self.model, pk=pk) 

659 return not self.role_handler.check_rights("can_edit_version", self.version) 

660 

661 def get_fail_redirect_uri(self) -> str: 

662 """ 

663 Redirects to the submission details page if the user cannot update the version. 

664 """ 

665 return self.get_success_url() 

666 

667 def get_object(self, *args, **kwargs) -> SubmissionVersion: 

668 """ 

669 Returns the already fetched version. 

670 """ 

671 return self.version 

672 

673 def get_form_kwargs(self) -> dict[str, Any]: 

674 kwargs = super().get_form_kwargs() 

675 if self.request.GET.get(SUBMIT_QUERY_PARAMETER, None) == "true": 

676 kwargs[SUBMIT_QUERY_PARAMETER] = True 

677 return kwargs 

678 

679 def get_success_url(self) -> str: 

680 if FormAction.NEXT.value in self.request.POST: 

681 return self.submit_url() 

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

683 

684 def submit_url(self) -> str: 

685 return reverse_lazy("mesh:submission_confirm", kwargs={"pk": self.version.submission.pk}) 

686 

687 def form_pre_save(self, form: SubmissionVersionForm) -> None: 

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

689 

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

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

692 context["page_title"] = _(f"Submission files - Version {self.version.number}") 

693 

694 submission_url = reverse_lazy( 

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

696 ) 

697 description = "Please fill the form below to submit your files for the submission " 

698 description += f"<a href='{submission_url}'><i>{self.version.submission.name}</i></a>.<br>" 

699 context["form_description"] = [description] 

700 

701 if self.version.submission.is_draft: 

702 stepper = get_submission_stepper(self.version.submission) 

703 stepper.set_active_step("version") 

704 context["stepper"] = stepper 

705 context["form"].buttons = [] 

706 

707 # # Generate breadcrumb data 

708 # breadcrumb = get_submission_breadcrumb(self.version.submission) 

709 # breadcrumb.add_item( 

710 # title=_("Edit version") + f" #{self.version.number}", 

711 # url=reverse_lazy("mesh:submission_version_update", kwargs={"pk": self.version.pk}), 

712 # ) 

713 # context["breadcrumb"] = breadcrumb 

714 return context 

715 

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

717 """ 

718 View for updating the submission or deleting one of the associated files. 

719 """ 

720 deletion_requested, _ = post_delete_model_file(self.version, request, self.role_handler) 

721 

722 if deletion_requested: 

723 # Update the submission object. 

724 self.version = get_object_or_404(self.model, pk=self.version.pk) 

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

726 

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

728 

729 

730class SubmissionResumeView(RoleMixin, View): 

731 """ 

732 View handling the first submit of a submission. It resumes the submission process 

733 at the correct edit view according to the missing data. 

734 

735 Handles the submission stepper. It redirects to the correct step according to 

736 the submission status. 

737 - STEP 1: Preprint id 

738 - STEP 2: Article Metadata 

739 - STEP 3: Submission Info 

740 - STEP 4: Confirmation 

741 """ 

742 

743 submission: Submission 

744 restricted_roles = [Author] 

745 

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

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

748 return not self.role_handler.check_rights("can_submit_submission", self.submission) 

749 

750 def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: 

751 stepper = get_submission_stepper(self.submission) 

752 

753 # Redirects to the last active step 

754 for i in range(len(stepper.steps)): 

755 step = stepper.steps[-(i + 1)] 

756 if step.href and step.can_navigate: 

757 return HttpResponseRedirect(step.href) 

758 

759 return HttpResponseRedirect(reverse_lazy("mesh:submission_create")) 

760 

761 

762class SubmissionAuthorView(RoleMixin, TemplateView): 

763 """ 

764 View to add/remove a submission author from a given submission. 

765 """ 

766 

767 submission: Submission 

768 template_name = "mesh/forms/submission_author.html" 

769 restricted_roles = [Author] 

770 init_form: SubmissionAuthorForm | None = None 

771 _FORM_ACTION_CORRESPONDING = "_action_toggle_corresponding" 

772 

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

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

775 return not self.role_handler.check_rights("can_edit_submission", self.submission) 

776 

777 @property 

778 def authors(self) -> QuerySet[SubmissionAuthor]: 

779 return SubmissionAuthor.objects.filter(submission=self.submission) 

780 

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

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

783 

784 context["submission"] = self.submission 

785 

786 authors = self.authors 

787 context["authors"] = [ 

788 { 

789 "author": author, 

790 "delete_form": HiddenModelChoiceForm( 

791 _queryset=authors, 

792 form_action=FormAction.DELETE.value, 

793 initial={"instance": author}, 

794 ), 

795 "corresponding_form": HiddenModelChoiceForm( 

796 _queryset=authors, 

797 form_action="_action_toggle_corresponding", 

798 initial={"instance": author}, 

799 ), 

800 "buttons": [ 

801 Button( 

802 id=f"author-corresponding-{author.pk}", 

803 title=_("Corresponding"), 

804 icon_class=("fa-toggle-on" if author.corresponding else "fa-toggle-off"), 

805 form=HiddenModelChoiceForm( 

806 _queryset=authors, initial={"instance": author} 

807 ), 

808 attrs={ 

809 "href": [ 

810 reverse_lazy( 

811 "mesh:submission_authors", 

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

813 ) 

814 ], 

815 "type": ["submit"], 

816 "class": ["primary" if author.corresponding else "inactive"], 

817 "name": [self._FORM_ACTION_CORRESPONDING], 

818 "data-tooltip": [ 

819 _("Click to toggle whether the author is a corresponding contact.") 

820 ], 

821 }, 

822 ), 

823 Button( 

824 id=f"author-delete-{author.pk}", 

825 title=_("Remove"), 

826 icon_class="fa-trash", 

827 form=HiddenModelChoiceForm( 

828 _queryset=authors, initial={"instance": author} 

829 ), 

830 attrs={ 

831 "href": [ 

832 reverse_lazy( 

833 "mesh:submission_authors", 

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

835 ) 

836 ], 

837 "type": ["submit"], 

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

839 "name": [FormAction.DELETE.value], 

840 }, 

841 ), 

842 ], 

843 } 

844 for author in authors 

845 ] 

846 

847 initial = {} 

848 if not authors: 

849 initial.update( 

850 { 

851 "first_name": self.request.user.first_name, 

852 "last_name": self.request.user.last_name, 

853 "email": self.request.user.email, 

854 "primary": True, 

855 } 

856 ) 

857 

858 form = self.init_form or SubmissionAuthorForm( 

859 submission=self.submission, initial=initial or None 

860 ) 

861 

862 form.buttons = [ 

863 Button( 

864 id="form_save", 

865 title=_("Add author"), 

866 icon_class="fa-plus", 

867 attrs={"type": ["submit"], "class": ["save-button"]}, 

868 ) 

869 ] 

870 

871 context["page_title"] = _("Authors") 

872 

873 if self.submission.is_draft: 

874 step_id = "authors" 

875 stepper = get_submission_stepper(self.submission) 

876 stepper.set_active_step(step_id) 

877 context["stepper"] = stepper 

878 # version_step = stepper.get_step("version") 

879 # if version_step and version_step.can_navigate and version_step.href: 

880 # button = Button( 

881 # id="next", 

882 # title=_("Next"), 

883 # icon_class="fa-right-long", 

884 # attrs={"href": [version_step.href], "class": ["as-button"]}, 

885 # ) 

886 # context["title_buttons"] = [button] 

887 # 

888 # form.buttons.append( 

889 # Button( 

890 # id="form_next", 

891 # title=_("Next"), 

892 # icon_class="fa-right-long", 

893 # attrs={ 

894 # "href": [version_step.href], 

895 # "type": ["submit"], 

896 # "class": ["as-button", "save-button"], 

897 # }, 

898 # ) 

899 # ) 

900 

901 context["form"] = form 

902 

903 # # Generate breadcrumb data 

904 # breadcrumb = get_submission_breadcrumb(self.submission) 

905 # breadcrumb.add_item( 

906 # title=_("Authors"), 

907 # url=reverse_lazy("mesh:submission_authors", kwargs={"pk": self.submission.pk}), 

908 # ) 

909 # context["breadcrumb"] = breadcrumb 

910 

911 return context 

912 

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

914 if FormAction.DELETE.value in request.POST: 

915 return self.remove_author(request) 

916 elif self._FORM_ACTION_CORRESPONDING in request.POST: 

917 return self.toggle_primary_author(request) 

918 

919 return self.add_author(request, *args, **kwargs) 

920 

921 def add_author(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: 

922 """ 

923 Add an `SubmissionAuthor` to the submission. 

924 """ 

925 response = HttpResponseRedirect( 

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

927 ) 

928 form = SubmissionAuthorForm(request.POST, submission=self.submission) 

929 if not form.is_valid(): 

930 self.init_form = form 

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

932 

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

934 form.instance.submission = self.submission 

935 form.save() 

936 

937 SubmissionLog.add_message( 

938 self.submission, 

939 content=f"Author added: {form.instance.first_name} {form.instance.last_name} ({form.instance.email})", 

940 content_en=_("Author added") 

941 + f": {form.instance.first_name} {form.instance.last_name} ({form.instance.email})", 

942 user=request.user, 

943 significant=True, 

944 ) 

945 messages.success( 

946 request, 

947 _("Author added") 

948 + f": {form.instance.first_name} {form.instance.last_name} ({form.instance.email})", 

949 ) 

950 

951 return response 

952 

953 def remove_author(self, request: HttpRequest) -> HttpResponse: 

954 """ 

955 Remove a `SubmissionAuthor` from the submission. 

956 """ 

957 response = HttpResponseRedirect( 

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

959 ) 

960 form = HiddenModelChoiceForm(request.POST, _queryset=self.authors) 

961 if not form.is_valid(): 

962 messages.error(request, _("Something went wrong. Please try again.")) 

963 return response 

964 

965 if len(self.authors) < 2: 

966 messages.error( 

967 request, 

968 _("There must be at least 1 author attached to the submission."), 

969 ) 

970 return response 

971 

972 author: SubmissionAuthor = form.cleaned_data["instance"] 

973 author_string = str(author) 

974 author.delete() 

975 

976 SubmissionLog.add_message( 

977 self.submission, 

978 content=f"Author removed: {author_string}", 

979 content_en=_("Author added") + f": {author_string}", 

980 request=request.user, 

981 significant=True, 

982 ) 

983 messages.success(request, _("The author has been removed.")) 

984 return response 

985 

986 def toggle_primary_author(self, request: HttpRequest) -> HttpResponse: 

987 """ 

988 Toggle the `corresponding` boolean of a `SubmissionAuthor` from the submission. 

989 """ 

990 response = HttpResponseRedirect( 

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

992 ) 

993 form = HiddenModelChoiceForm(request.POST, _queryset=self.authors) 

994 if not form.is_valid(): 

995 messages.error(request, _("Something went wrong. Please try again.")) 

996 return response 

997 

998 author: SubmissionAuthor = form.cleaned_data["instance"] 

999 author.corresponding = not author.corresponding 

1000 author.save() 

1001 word = "marked" if author.corresponding else "unmarked" 

1002 messages.success( 

1003 request, 

1004 _(f"Author {author} has been {word} as a corresponding contact."), 

1005 ) 

1006 

1007 return response 

1008 

1009 

1010class SubmissionConfirmView(RoleMixin, FormView): 

1011 """ 

1012 View to confirm the submission the current SubmissionVersion. 

1013 

1014 It's used both when submitting a Submission for the first time and when 

1015 submitting a revised SubmissionVersion. 

1016 """ 

1017 

1018 template_name = "mesh/submission/version_confirm.html" 

1019 submission: Submission 

1020 form_class = SubmissionConfirmForm 

1021 

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

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

1024 

1025 if not self.role_handler.check_rights("can_edit_submission", self.submission): 

1026 return True 

1027 

1028 return False 

1029 

1030 def get_success_url(self): 

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

1032 

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

1034 if not self.submission.is_submittable: 

1035 # TODO : Enhance error message 

1036 # We theorically shouldn't even be able to get there with an invalid submission 

1037 # But real examples have shown that it can happen 

1038 messages.error( 

1039 request, 

1040 _("There must be at least 1 author attached to the submission."), 

1041 ) 

1042 return super().get(request, *args, **kwargs) 

1043 

1044 def get_object(self, queryset=None) -> SubmissionVersion: 

1045 return self.submission.current_version 

1046 

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

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

1049 

1050 context["form"].buttons = [ 

1051 Button( 

1052 id="form_save", 

1053 title=_("Submit"), 

1054 icon_class="fa-check", 

1055 attrs={ 

1056 "type": ["submit"], 

1057 "class": ["save-button", "button-highlight"], 

1058 }, 

1059 ) 

1060 ] 

1061 

1062 if self.submission.is_draft: 

1063 stepper = get_submission_stepper(self.submission) 

1064 stepper.set_active_step("confirm") 

1065 context["stepper"] = stepper 

1066 

1067 context["submission_proxy"] = SubmissionProxy(self.submission, self.role_handler) 

1068 

1069 files = [] 

1070 files.append( 

1071 { 

1072 "file_wrapper": self.submission.current_version.main_file, 

1073 "type": "Main", 

1074 } 

1075 ) 

1076 

1077 for file_wrapper in self.submission.current_version.additional_files.all(): 

1078 files.append({"file_wrapper": file_wrapper, "type": "Additional"}) 

1079 context["submission_files"] = files 

1080 

1081 # # Breadcrumb 

1082 # breadcrumb = get_submission_breadcrumb(self.submission) 

1083 # breadcrumb.add_item( 

1084 # title=_("Authors"), 

1085 # url=reverse_lazy("mesh:submission_confirm", kwargs={"pk": self.submission.pk}), 

1086 # ) 

1087 # context["breadcrumb"] = breadcrumb 

1088 

1089 return context 

1090 

1091 def form_valid(self, form: SubmissionConfirmForm) -> HttpResponse: 

1092 """ 

1093 Submit the submission's current version. 

1094 """ 

1095 self.submission.submit(self.request.user) 

1096 

1097 if self.submission.current_version.number == 1: 

1098 messages.success( 

1099 self.request, 

1100 _("Your submission has been successfully saved and confirmed."), 

1101 ) 

1102 else: 

1103 messages.success(self.request, _("Your revisions were successfully submitted.")) 

1104 

1105 previous_round_number = self.submission.current_version.number - 1 

1106 if previous_round_number > 0: 

1107 previous_round = self.submission.versions.get(number=previous_round_number) 

1108 previous_reviewers = [ 

1109 review.reviewer 

1110 for review in previous_round.reviews.filter(state=ReviewState.SUBMITTED.value) 

1111 ] 

1112 for previous_reviewer in previous_reviewers: 

1113 qs = Suggestion.objects.filter( 

1114 submission=self.submission, suggested_user=previous_reviewer 

1115 ) 

1116 if not qs.exists(): 

1117 add_suggestion( 

1118 submission=self.submission, 

1119 suggested_user=previous_reviewer, 

1120 suggested_reviewer=None, 

1121 ) 

1122 

1123 return HttpResponseRedirect(self.get_success_url()) 

1124 

1125 

1126class SubmissionDeleteView(RoleMixin, DeleteView): 

1127 model: type[Submission] = Submission 

1128 submission: Submission 

1129 form_class = SubmissionDeleteForm 

1130 template_name = "mesh/forms/submission_delete.html" 

1131 restricted_roles = [JournalManager, Author] 

1132 message_on_restrict = False 

1133 

1134 def restrict_dispatch(self, request: HttpRequest, *args, **kwargs): 

1135 pk = kwargs["pk"] 

1136 self.submission = get_object_or_404(self.model, pk=pk) 

1137 

1138 return not self.role_handler.check_rights("can_edit_submission", self.submission) 

1139 

1140 def get_object(self, *args, **kwargs) -> Submission: 

1141 """ 

1142 Override `get_object` built-in method to avoid an additional look-up when 

1143 we already have the object loaded. 

1144 """ 

1145 return self.submission 

1146 

1147 def get_success_url(self): 

1148 return reverse_lazy("mesh:submission_list") 

1149 

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

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

1152 

1153 context["submission"] = self.submission 

1154 context["form"].buttons = [ 

1155 Button( 

1156 id="form_save", 

1157 title=_("Delete"), 

1158 icon_class="fa-trash", 

1159 attrs={ 

1160 "type": ["submit"], 

1161 "class": ["save-button", "button-highlight"], 

1162 }, 

1163 ) 

1164 ] 

1165 

1166 return context