Coverage for src / mesh / views / views_submission_edit.py: 48%
503 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-05-04 12:41 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-05-04 12:41 +0000
1import json
2from typing import Any
4from django.contrib import messages
5from django.contrib.auth.mixins import LoginRequiredMixin
6from django.http import HttpRequest, HttpResponse, HttpResponseRedirect, JsonResponse
7from django.urls.base import reverse
8from django.utils.translation import gettext_lazy as _
9from django.views.generic import FormView, TemplateView, View
10from django.views.generic.edit import CreateView, DeleteView, UpdateView
11from matching_back.views import ArticleEditFormWithVueAPIView
12from ptf.external.fetch_metadata import fetch_article
13from ptf.model_data import create_articledata, create_contributor
15from mesh.app_settings import app_settings
16from mesh.models.crud import create_submission
17from mesh.models.file_helpers import post_delete_model_file
18from mesh.models.orm.review_models import ReviewState
19from mesh.models.orm.submission_models import (
20 Submission,
21 SubmissionAuthor,
22 SubmissionLog,
23 SubmissionMainFile,
24 SubmissionVersion,
25)
26from mesh.models.orm.suggestion_model import Suggestion
27from mesh.models.roles.author import Author
28from mesh.models.roles.journal_manager import JournalManager
29from mesh.views.components.button import Button
30from mesh.views.components.stepper import get_submission_stepper
31from mesh.views.forms.base_forms import (
32 SUBMIT_QUERY_PARAMETER,
33 FormAction,
34 HiddenModelChoiceForm,
35)
36from mesh.views.forms.submission_forms import (
37 SubmissionAuthorForm,
38 SubmissionConfirmForm,
39 SubmissionCreateForm,
40 SubmissionDeleteForm,
41 SubmissionEditArticleMetadataForm,
42 SubmissionInfoForm,
43 SubmissionVersionForm,
44)
45from mesh.views.viewmodel import SubmissionProxy
46from mesh.views.views_base import (
47 MeshObjectMixin,
48 SubmittableModelFormMixin,
49)
50from mesh.views.views_reviewer import add_suggestion, add_suggestion_from_person
53class SubmissionCreateView(LoginRequiredMixin, CreateView):
54 """
55 View for creating a new submission with a preprint id.
56 """
58 model = Submission
59 form_class = SubmissionCreateForm
60 template_name = "mesh/forms/form_full_page.html"
61 restricted_roles = [Author]
63 def setup(self, request, *args, **kwargs):
64 super().setup(request, *args, **kwargs)
65 if not self.request.current_role.can_create_submission():
66 raise PermissionError
67 self.submission = None
69 def get_success_url(self) -> str:
70 """
71 Redirects to the SubmissionAuthor view to add authors to the
72 newly created submission.
73 """
75 return reverse(
76 "mesh:submission_edit_article_metadata", kwargs={"submission_pk": self.submission.pk}
77 )
79 def form_valid(self, form) -> HttpResponse:
80 # raise NotImplementedError("TODO : FIX FORM VALIDATION : split logic")
81 self.submission = create_submission(
82 user=self.request.user, preprint_id=form.cleaned_data["preprint_id"]
83 )
84 return HttpResponseRedirect(self.get_success_url())
86 def get_context_data(self, *args, **kwargs):
87 context = super().get_context_data(*args, **kwargs)
88 context["page_title"] = _("New submission")
90 # context["data-get-url"] = reverse("mesh:submission_create_vuejs")
92 # Stepper
93 step_id = "preprint"
94 stepper = get_submission_stepper(None)
95 stepper.set_active_step(step_id)
96 context["stepper"] = stepper
98 # Form buttons
99 # No Save/Next button in the form, they are put in the stepper
100 context["form"].buttons = [] # = submission_stepper_form_buttons()
102 descriptions = [
103 "You are about to start the submit process.",
104 'Please read our guidelines <a href="">here</a>',
105 "If you have a preprint, you can enter its id below. Leave the field blank otherwise.",
106 ]
107 context["form_description"] = descriptions
109 # # Generate breadcrumb data
110 # breadcrumb = get_base_breadcrumb()
111 # breadcrumb.add_item(title=_("New submission"), url=reverse("mesh:submission_create"))
112 # context["breadcrumb"] = breadcrumb
113 return context
116class SubmissionPreprintUpdateView(LoginRequiredMixin, MeshObjectMixin, UpdateView):
117 """
118 View for updating a submission with a new preprint id.
119 """
121 model = Submission
122 form_class = SubmissionCreateForm
123 template_name = "mesh/forms/form_full_page.html"
124 restricted_roles = [Author]
126 def setup(self, request, *args, **kwargs):
127 super().setup(request, *args, **kwargs)
128 self.submission = self.get_submission()
129 if not self.request.current_role.can_edit_submission(self.submission):
130 raise PermissionError
132 def get_object(self, *args, **kwargs) -> Submission:
133 """
134 Override `get_object` built-in method to avoid an additional look-up when
135 we already have the object loaded.
136 """
137 return self.submission
139 def get_success_url(self) -> str:
140 """
141 Redirects to the SubmissionAuthor view to add authors to the
142 newly created submission.
143 """
145 return reverse(
146 "mesh:submission_edit_article_metadata", kwargs={"submission_pk": self.object.pk}
147 )
149 def form_valid(self, form):
150 preprint_id = form.cleaned_data["preprint_id"]
152 if preprint_id != "":
153 article_data = fetch_article(preprint_id, with_bib=False)
155 title = article_data.title_html
156 self.submission.name = title
158 if len(article_data.abstracts) > 0:
159 abstract = article_data.abstracts[0]["value_tex"]
160 self.submission.abstract = abstract
162 self.submission._user = self.request.user
163 self.submission.save()
165 current_version = self.submission.get_current_version()
166 current_version._user = self.request.user
167 current_version.save()
169 self.submission.authors.all().delete()
171 for contrib in article_data.contributors:
172 author = SubmissionAuthor(
173 submission=self.submission,
174 first_name=contrib["first_name"],
175 last_name=contrib["last_name"],
176 email=contrib["email"],
177 corresponding=contrib["corresponding"],
178 )
179 author.save()
181 SubmissionLog.add_message(
182 self.submission,
183 content=_("Update of the submission with a preprint id"),
184 content_en="Update of the submission with a preprint id",
185 user=self.request.user,
186 significant=True,
187 )
189 return HttpResponseRedirect(self.get_success_url())
191 def get_context_data(self, *args, **kwargs):
192 context = super().get_context_data(*args, **kwargs)
193 context["page_title"] = _("Update submission preprint id")
195 # context["data-get-url"] = reverse("mesh:submission_create_vuejs")
197 # Stepper
198 step_id = "preprint"
199 stepper = get_submission_stepper(self.submission)
200 stepper.set_active_step(step_id)
201 context["stepper"] = stepper
203 # Form buttons
204 # No Save/Next button in the form, they are put in the stepper
205 context["form"].buttons = [] # = submission_stepper_form_buttons()
207 descriptions = [
208 "You are about to start the submit process.",
209 'Please read our guidelines <a href="">here</a>',
210 "If you have a preprint, you can enter its id below. Leave the field blank otherwise.",
211 ]
212 context["form_description"] = descriptions
214 # # Generate breadcrumb data
215 # breadcrumb = get_base_breadcrumb()
216 # breadcrumb.add_item(title=_("New submission"), url=reverse("mesh:submission_create"))
217 # context["breadcrumb"] = breadcrumb
218 return context
221class SubmissionEditArticleMetadataVuejsAPIView(
222 LoginRequiredMixin, MeshObjectMixin, ArticleEditFormWithVueAPIView
223):
224 def __init__(self, **kwargs):
225 super().__init__(**kwargs)
226 self.fields_to_update = ["contributors", "abstracts", "titles", "pdf"]
227 self.editorial_tools = ["light_authors", "inline_ckeditor"]
228 self.mandatory_fields = ["contributors", "abstract", "title", "pdf"]
230 self.submission = None
231 self.version = None
232 self.submission_main_file = None
234 def setup(self, request, *args, **kwargs):
235 super().setup(request, *args, **kwargs)
236 self.submission = self.get_submission()
237 if not self.request.current_role.can_edit_submission(self.submission):
238 raise PermissionError
240 def get(self, request, *args, **kwargs):
241 colid = app_settings.COLID
242 data_article = create_articledata()
243 data = self.convert_data_for_vuejs3(data_article, colid=colid)
244 data["editorial_tools"] = self.editorial_tools
246 def obj_to_dict(obj):
247 return obj.__dict__
249 data["article"]["colid"] = colid
250 dump = json.dumps(data, default=obj_to_dict)
251 return HttpResponse(dump, content_type="application/json")
253 def handle_pdf_for_vuejs3(self, data, data_article, method=""):
254 if method == "get":
255 submission_main_file = getattr(
256 self.submission.get_current_version(), "main_file", None
257 )
258 if submission_main_file is not None:
259 data["article"]["pdf_name"] = submission_main_file.file.name
260 else:
261 data["article"]["pdf_name"] = "No file uploaded"
263 elif method == "post" and "pdf" in self.request.FILES:
264 submission_main_file = getattr(
265 self.submission.get_current_version(), "main_file", None
266 )
267 if submission_main_file is not None:
268 submission_main_file.delete()
270 submission_main_file = SubmissionMainFile(
271 attached_to=self.submission.get_current_version(), file=self.request.FILES["pdf"]
272 )
273 submission_main_file.save()
275 def handle_titles_for_vuejs3(self, data, data_article, method=""):
276 if method == "get":
277 data["article"]["titles"] = [
278 {"lang": data_article.lang, "value": self.submission.name}
279 ]
280 else:
281 super().handle_titles_for_vuejs3(data, data_article, method)
283 def handle_contributors_for_vuejs3(self, data, data_article, method=""):
284 if method == "get":
285 contributors = []
286 for author in self.submission.authors.all():
287 contrib = create_contributor()
288 contrib["first_name"] = author.first_name
289 contrib["last_name"] = author.last_name
290 contrib["email"] = author.email
291 contrib["corresponding"] = author.corresponding
292 contributors.append(contrib)
293 data["article"]["contributors"] = contributors
294 else:
295 super().handle_contributors_for_vuejs3(data, data_article, method)
297 def handle_abstracts_for_vuejs3(self, data, data_article, method=""):
298 if method == "get":
299 data["article"]["abstracts"] = [{"lang": "en", "value": self.submission.abstract}]
300 else:
301 super().handle_abstracts_for_vuejs3(data, data_article, method)
303 def post(self, request, *args, **kwargs):
304 body_unicode = request.POST.get("data", "")
305 data = json.loads(body_unicode)
307 if (
308 data["title_html"][0]["value"] == ""
309 or data["abstracts"][0]["value"] == ""
310 or len(data["contributors"]) == 0
311 or ("pdf" not in self.request.FILES and data["pdf"]["pdf_name"] == "No file uploaded")
312 ):
313 return JsonResponse({"status_code": 400})
315 data["colid"] = app_settings.COLID
317 article_data = self.convert_data_from_vuejs3(data)
318 title = article_data.title_html
320 abstract = article_data.abstracts[0]["value_html"]
322 self.submission.name = title
323 self.submission.abstract = abstract
324 self.submission._user = request.user
325 self.submission.save()
327 current_version = self.submission.get_current_version()
328 current_version._user = request.user
329 current_version.save()
331 SubmissionAuthor.objects.filter(submission=self.submission).delete()
333 for contrib in article_data.contributors:
334 author = SubmissionAuthor(
335 submission=self.submission,
336 first_name=contrib["first_name"],
337 last_name=contrib["last_name"],
338 email=contrib["email"],
339 corresponding=contrib["corresponding"],
340 )
341 author.save()
343 data["redirect"] = {
344 "redirect": True,
345 "url": reverse(
346 "mesh:submission_info_update", kwargs={"submission_pk": self.submission.pk}
347 ),
348 }
350 return JsonResponse(data)
353class SubmissionRedirectFromVue(TemplateView):
354 template_name = "mesh/forms/form_full_page_redirect.html"
356 def get_context_data(self, **kwargs):
357 context = super().get_context_data(**kwargs)
358 context["message"] = (
359 "La sauvegarde dans Vuejs puis la redirection sur une autre page fonctionne"
360 )
361 return context
363 def get(self, request, *args, **kwargs):
364 """
365 Override the get method to set the form action to "create".
366 """
368 messages.success(self.request, _("New submission successfully created."))
370 return super().get(request, *args, **kwargs)
373class SubmissionInfoView(LoginRequiredMixin, MeshObjectMixin, UpdateView):
374 """
375 View for updating the submission info.
376 """
378 model = Submission
379 form_class = SubmissionInfoForm
380 template_name = "mesh/forms/submission_info.html"
381 submission: Submission
382 restricted_roles = [Author]
383 message_on_restrict = False
385 def setup(self, request, *args, **kwargs):
386 super().setup(request, *args, **kwargs)
387 self.submission = self.get_submission()
388 if not self.request.current_role.can_edit_submission(self.submission):
389 raise PermissionError
391 def get_success_url(self) -> str:
392 # if FormAction.NEXT.value in self.request.POST:
393 # return reverse_lazy("mesh:submission_authors", kwargs={"pk": self.submission.pk})
395 return reverse("mesh:submission_confirm", kwargs={"submission_pk": self.submission.pk})
397 def get_fail_redirect_uri(self) -> str:
398 """
399 Return to the submission details page if the user cannot update the submission.
400 """
401 return self.get_success_url()
403 def get_object(self, *args, **kwargs) -> Submission:
404 """
405 Override `get_object` built-in method to avoid an additional look-up when
406 we already have the object loaded.
407 """
408 return self.submission
410 def get_context_data(self, *args, **kwargs):
411 context = super().get_context_data(*args, **kwargs)
412 context["page_title"] = _("Edit submission info")
414 if self.submission.is_draft:
415 step_id = "info"
416 stepper = get_submission_stepper(self.submission)
417 stepper.set_active_step(step_id)
418 context["stepper"] = stepper
420 # No Save/Next button in the form, they are put in the stepper
421 context["form"].buttons = [] # = submission_stepper_form_buttons()
423 suggestions = self.submission.suggestions_for_reviewer.all()
424 for suggestion in suggestions:
425 reviewer = (
426 suggestion.suggested_reviewer
427 if suggestion.suggested_reviewer is not None
428 else suggestion.suggested_user
429 )
430 if suggestion.suggest_to_avoid:
431 context["avoid_reviewer"] = reviewer
432 else:
433 context["suggested_reviewer"] = reviewer
435 return context
437 def form_valid(self, form):
438 form.instance._user = self.request.user
440 Suggestion.objects.filter(submission=self.submission).delete()
442 for prefix in ["reviewer", "avoid"]:
443 person_info = {
444 "first_name": form.data.get(f"{prefix}_first_name", ""),
445 "last_name": form.data.get(f"{prefix}_last_name", ""),
446 "email": form.data.get(f"{prefix}_email", ""),
447 }
449 if person_info["email"] != "":
450 add_suggestion_from_person(
451 self.submission, person_info, suggest_to_avoid=prefix == "avoid"
452 )
454 return super().form_valid(form)
457class SubmissionEditArticleMetadataView(LoginRequiredMixin, MeshObjectMixin, UpdateView):
458 """
459 View for updating a submission (only the metadata).
460 Only the GET is used to display the form (with VueJS)
461 The POST is handled by SubmissionEditArticleMetadataVuejsAPIView
462 """
464 model = Submission
465 form_class = SubmissionEditArticleMetadataForm
466 template_name = "mesh/forms/form_full_page.html"
467 submission: Submission
468 restricted_roles = [Author]
469 message_on_restrict = False
471 def setup(self, request, *args, **kwargs):
472 super().setup(request, *args, **kwargs)
473 self.submission = self.get_submission()
474 if not self.request.current_role.can_edit_submission(self.submission):
475 raise PermissionError
477 # def get_fail_redirect_uri(self) -> str:
478 # """
479 # Return to the submission details page if the user cannot update the submission.
480 # Not used with VueJS
481 # """
482 # return self.get_success_url()
484 def get_object(self, *args, **kwargs) -> Submission:
485 """
486 Override `get_object` built-in method to avoid an additional look-up when
487 we already have the object loaded.
488 """
489 return self.submission
491 def get_context_data(self, *args, **kwargs):
492 context = super().get_context_data(*args, **kwargs)
493 context["page_title"] = _("Edit article metadata")
495 # data-get-url is used by VueJS to know the GET url to call
496 context["data_get_url"] = reverse(
497 "mesh:submission_update_vuejs",
498 kwargs={"submission_pk": self.submission.pk},
499 )
501 if self.submission.is_draft:
502 step_id = "metadata"
503 stepper = get_submission_stepper(self.submission)
504 stepper.set_active_step(step_id)
505 context["stepper"] = stepper
507 # No Save/Next button in the form, they are put in the stepper
508 context["form"].buttons = [] # = submission_stepper_form_buttons()
510 # Generate breadcrumb data
511 # breadcrumb = get_submission_breadcrumb(self.submission)
512 # breadcrumb.add_item(
513 # title=_("Edit metadata"),
514 # url=reverse_lazy("mesh:submission_edit_article_metadata", kwargs={"pk": self.submission.pk}),
515 # )
516 # context["breadcrumb"] = breadcrumb
517 return context
519 def get_success_url(self) -> str:
520 if FormAction.NEXT.value in self.request.POST:
521 # No POST with VueJS. We shouldn't reach this clause
522 return reverse("mesh:submission_authors", kwargs={"submission_pk": self.submission.pk})
523 return reverse("mesh:submission_details", kwargs={"submission_pk": self.submission.pk})
525 # def form_valid(self, form: SubmissionUpdateForm) -> HttpResponse:
526 # form.instance._user = self.request.user
527 # if not form.changed_data:
528 # form.instance.do_not_update = True
529 #
530 # response = super().form_valid(form)
531 #
532 # if form.has_changed():
533 # SubmissionLog.add_message(
534 # self.submission,
535 # content=_("Modification of the submission's metadata"),
536 # content_en="Modification of the submission's metadata",
537 # user=self.request.user,
538 # )
539 # return response
542class SubmissionVersionCreateView(
543 LoginRequiredMixin, MeshObjectMixin, SubmittableModelFormMixin, CreateView
544):
545 """
546 View for creating a new submission version.
547 """
549 model = SubmissionVersion
550 form_class = SubmissionVersionForm
551 template_name = "mesh/forms/form_full_page.html"
552 submission: Submission
553 restricted_roles = [Author]
554 save_submission = False
555 add_confirm_message = False
557 def setup(self, request, *args, **kwargs):
558 super().setup(request, *args, **kwargs)
559 self.submission = self.get_submission()
560 if not self.request.current_role.can_create_version(self.submission):
561 raise PermissionError
563 def get_form_kwargs(self):
564 kwargs = super().get_form_kwargs()
565 kwargs["submission"] = self.submission
566 return kwargs
568 def form_valid(self, form):
569 form.instance.submission = self.submission
570 return super().form_valid(form)
572 def get_success_url(self) -> str:
573 if FormAction.NEXT.value in self.request.POST:
574 # No POST with VueJS. We shouldn't reach this clause
575 return self.submit_url()
576 return reverse("mesh:submission_details", kwargs={"submission_pk": self.submission.pk})
578 def submit_url(self) -> str:
579 return reverse("mesh:submission_confirm", kwargs={"submission_pk": self.submission.pk})
581 # def form_pre_save(self, form: SubmissionVersionForm):
582 # Not used with VueJS
583 # form.instance._user = self.request.user
584 # form.instance.submission = self.submission
586 def get_context_data(self, *args, **kwargs):
587 context = super().get_context_data(*args, **kwargs)
588 current_version = self.submission.get_current_version()
589 version = current_version.number + 1 if current_version else 1
590 context["page_title"] = _("Submission files")
591 if not self.submission.is_draft:
592 context["page_title"] += f" - Version {version}"
594 submission_url = reverse(
595 "mesh:submission_details", kwargs={"submission_pk": self.submission.pk}
596 )
597 description = "Please fill the form below to submit your files for the submission "
598 description += f"<a href='{submission_url}'><i>{self.submission.name}</i></a>.<br>"
599 context["form_description"] = [description]
601 if self.submission.is_draft:
602 stepper = get_submission_stepper(self.submission)
603 stepper.set_active_step("version")
604 context["stepper"] = stepper
605 context["form"].buttons = []
607 # # Generate breadcrumb data
608 # breadcrumb = get_submission_breadcrumb(self.submission)
609 # breadcrumb.add_item(
610 # title=_("New version"),
611 # url=reverse_lazy(
612 # "mesh:submission_version_create", kwargs={"submission_pk": self.submission.pk}
613 # ),
614 # )
615 # context["breadcrumb"] = breadcrumb
616 return context
618 # def form_valid(self, form):
619 # result = super().form_valid(form)
620 # return result
623class SubmissionVersionUpdateView(
624 LoginRequiredMixin, MeshObjectMixin, SubmittableModelFormMixin, UpdateView
625):
626 """
627 View for updating a submission version.
629 Submitting a version = submitting the whole submission.
630 This requires extra care about checking that all previous steps have been
631 taken when this is the first version.
632 """
634 model = SubmissionVersion
635 form_class = SubmissionVersionForm
636 template_name = "mesh/forms/form_full_page.html"
638 restricted_roles = [Author]
639 message_on_restrict = False
640 save_submission = False
641 add_confirm_message = False
643 def setup(self, request, *args, **kwargs):
644 super().setup(request, *args, **kwargs)
645 self.version = self.get_version()
646 if not self.request.current_role.can_edit_version(self.submission):
647 raise PermissionError
649 def get_fail_redirect_uri(self) -> str:
650 """
651 Redirects to the submission details page if the user cannot update the version.
652 """
653 return self.get_success_url()
655 def get_object(self, *args, **kwargs) -> SubmissionVersion:
656 """
657 Returns the already fetched version.
658 """
659 return self.version
661 def get_form_kwargs(self) -> dict[str, Any]:
662 kwargs = super().get_form_kwargs()
663 if self.request.GET.get(SUBMIT_QUERY_PARAMETER, None) == "true":
664 kwargs[SUBMIT_QUERY_PARAMETER] = True
665 return kwargs
667 def get_success_url(self) -> str:
668 if FormAction.NEXT.value in self.request.POST:
669 return self.submit_url()
670 return reverse(
671 "mesh:submission_details", kwargs={"submission_pk": self.version.submission.pk}
672 )
674 def submit_url(self) -> str:
675 return reverse(
676 "mesh:submission_confirm", kwargs={"submission_pk": self.version.submission.pk}
677 )
679 def form_pre_save(self, form: SubmissionVersionForm) -> None:
680 form.instance._user = self.request.user
682 def get_context_data(self, *args, **kwargs):
683 context = super().get_context_data(*args, **kwargs)
684 context["page_title"] = _(f"Submission files - Version {self.version.number}")
686 submission_url = reverse(
687 "mesh:submission_details", kwargs={"submission_pk": self.version.submission.pk}
688 )
689 description = "Please fill the form below to submit your files for the submission "
690 description += f"<a href='{submission_url}'><i>{self.version.submission.name}</i></a>.<br>"
691 context["form_description"] = [description]
693 if self.version.submission.is_draft:
694 stepper = get_submission_stepper(self.version.submission)
695 stepper.set_active_step("version")
696 context["stepper"] = stepper
697 context["form"].buttons = []
699 # # Generate breadcrumb data
700 # breadcrumb = get_submission_breadcrumb(self.version.submission)
701 # breadcrumb.add_item(
702 # title=_("Edit version") + f" #{self.version.number}",
703 # url=reverse("mesh:submission_version_update", kwargs={"pk": self.version.pk}),
704 # )
705 # context["breadcrumb"] = breadcrumb
706 return context
708 def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
709 """
710 View for updating the submission or deleting one of the associated files.
711 """
712 deletion_requested, _ = post_delete_model_file(self.version, request, self.role_handler)
714 if deletion_requested:
715 # Update the submission object.
716 self.version = self.get_version()
717 return self.get(request, *args, **kwargs)
719 return super().post(request, *args, **kwargs)
722class SubmissionResumeView(LoginRequiredMixin, MeshObjectMixin, View):
723 """
724 View handling the first submit of a submission. It resumes the submission process
725 at the correct edit view according to the missing data.
727 Handles the submission stepper. It redirects to the correct step according to
728 the submission status.
729 - STEP 1: Preprint id
730 - STEP 2: Article Metadata
731 - STEP 3: Submission Info
732 - STEP 4: Confirmation
733 """
735 submission: Submission
736 restricted_roles = [Author]
738 def setup(self, request, *args, **kwargs):
739 super().setup(request, *args, **kwargs)
740 self.submission = self.get_submission()
741 if not self.request.current_role.can_submit_submission(self.submission):
742 raise PermissionError
744 def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
745 stepper = get_submission_stepper(self.submission)
747 # Redirects to the last active step
748 for i in range(len(stepper.steps)):
749 step = stepper.steps[-(i + 1)]
750 if step.href and step.can_navigate:
751 return HttpResponseRedirect(step.href)
753 return HttpResponseRedirect(reverse("mesh:submission_create"))
756class SubmissionAuthorView(LoginRequiredMixin, MeshObjectMixin, TemplateView):
757 """
758 View to add/remove a submission author from a given submission.
759 """
761 submission: Submission
762 template_name = "mesh/forms/submission_author.html"
763 restricted_roles = [Author]
764 init_form: SubmissionAuthorForm | None = None
765 _FORM_ACTION_CORRESPONDING = "_action_toggle_corresponding"
767 def setup(self, request, *args, **kwargs):
768 super().setup(request, *args, **kwargs)
769 self.submission = self.get_submission()
770 if not self.request.current_role.can_edit_submission(self.submission):
771 raise PermissionError
773 def get_context_data(self, *args, **kwargs):
774 context = super().get_context_data(*args, **kwargs)
776 context["submission"] = self.submission
778 authors = self.submission.authors_censored
779 context["authors"] = [
780 {
781 "author": author,
782 "delete_form": HiddenModelChoiceForm(
783 _queryset=authors,
784 form_action=FormAction.DELETE.value,
785 initial={"instance": author},
786 ),
787 "corresponding_form": HiddenModelChoiceForm(
788 _queryset=authors,
789 form_action="_action_toggle_corresponding",
790 initial={"instance": author},
791 ),
792 "buttons": [
793 Button(
794 id=f"author-corresponding-{author.pk}",
795 title=_("Corresponding"),
796 icon_class=("fa-toggle-on" if author.corresponding else "fa-toggle-off"),
797 form=HiddenModelChoiceForm(
798 _queryset=authors, initial={"instance": author}
799 ),
800 attrs={
801 "href": [
802 reverse(
803 "mesh:submission_authors",
804 kwargs={"submission_pk": self.submission.pk},
805 )
806 ],
807 "type": ["submit"],
808 "class": ["primary" if author.corresponding else "inactive"],
809 "name": [self._FORM_ACTION_CORRESPONDING],
810 "data-tooltip": [
811 _("Click to toggle whether the author is a corresponding contact.")
812 ],
813 },
814 ),
815 Button(
816 id=f"author-delete-{author.pk}",
817 title=_("Remove"),
818 icon_class="fa-trash",
819 form=HiddenModelChoiceForm(
820 _queryset=authors, initial={"instance": author}
821 ),
822 attrs={
823 "href": [
824 reverse(
825 "mesh:submission_authors",
826 kwargs={"submission_pk": self.submission.pk},
827 )
828 ],
829 "type": ["submit"],
830 "class": ["button-error"],
831 "name": [FormAction.DELETE.value],
832 },
833 ),
834 ],
835 }
836 for author in authors
837 ]
839 initial = {}
840 if not authors:
841 initial.update(
842 {
843 "first_name": self.request.user.first_name,
844 "last_name": self.request.user.last_name,
845 "email": self.request.user.email,
846 "primary": True,
847 }
848 )
850 form = self.init_form or SubmissionAuthorForm(
851 submission=self.submission, initial=initial or None
852 )
854 form.buttons = [
855 Button(
856 id="form_save",
857 title=_("Add author"),
858 icon_class="fa-plus",
859 attrs={"type": ["submit"], "class": ["save-button"]},
860 )
861 ]
863 context["page_title"] = _("Authors")
865 if self.submission.is_draft:
866 step_id = "authors"
867 stepper = get_submission_stepper(self.submission)
868 stepper.set_active_step(step_id)
869 context["stepper"] = stepper
870 # version_step = stepper.get_step("version")
871 # if version_step and version_step.can_navigate and version_step.href:
872 # button = Button(
873 # id="next",
874 # title=_("Next"),
875 # icon_class="fa-right-long",
876 # attrs={"href": [version_step.href], "class": ["as-button"]},
877 # )
878 # context["title_buttons"] = [button]
879 #
880 # form.buttons.append(
881 # Button(
882 # id="form_next",
883 # title=_("Next"),
884 # icon_class="fa-right-long",
885 # attrs={
886 # "href": [version_step.href],
887 # "type": ["submit"],
888 # "class": ["as-button", "save-button"],
889 # },
890 # )
891 # )
893 context["form"] = form
895 # # Generate breadcrumb data
896 # breadcrumb = get_submission_breadcrumb(self.submission)
897 # breadcrumb.add_item(
898 # title=_("Authors"),
899 # url=reverse("mesh:submission_authors", kwargs={"pk": self.submission.pk}),
900 # )
901 # context["breadcrumb"] = breadcrumb
903 return context
905 def post(self, request, *args, **kwargs):
906 if FormAction.DELETE.value in request.POST:
907 return self.remove_author(request)
908 elif self._FORM_ACTION_CORRESPONDING in request.POST:
909 return self.toggle_primary_author(request)
911 return self.add_author(request, *args, **kwargs)
913 def add_author(self, request, *args, **kwargs):
914 """
915 Add an `SubmissionAuthor` to the submission.
916 """
917 response = HttpResponseRedirect(
918 reverse("mesh:submission_authors", kwargs={"submission_pk": self.submission.pk})
919 )
920 form = SubmissionAuthorForm(request.POST, submission=self.submission)
921 if not form.is_valid():
922 self.init_form = form
923 return self.get(request, *args, **kwargs)
925 form.instance._user = self.request.user
926 form.instance.submission = self.submission
927 form.save()
929 SubmissionLog.add_message(
930 self.submission,
931 content=f"Author added: {form.instance.first_name} {form.instance.last_name} ({form.instance.email})",
932 content_en=_("Author added")
933 + f": {form.instance.first_name} {form.instance.last_name} ({form.instance.email})",
934 user=request.user,
935 significant=True,
936 )
937 messages.success(
938 request,
939 _("Author added")
940 + f": {form.instance.first_name} {form.instance.last_name} ({form.instance.email})",
941 )
943 return response
945 def remove_author(self, request: HttpRequest) -> HttpResponse:
946 """
947 Remove a `SubmissionAuthor` from the submission.
948 """
949 response = HttpResponseRedirect(
950 reverse("mesh:submission_authors", kwargs={"submission_pk": self.submission.pk})
951 )
952 form = HiddenModelChoiceForm(request.POST, _queryset=self.submission.authors_censored)
953 if not form.is_valid():
954 messages.error(request, _("Something went wrong. Please try again."))
955 return response
957 if len(self.submission.authors_censored) < 2:
958 messages.error(
959 request,
960 _("There must be at least 1 author attached to the submission."),
961 )
962 return response
964 author: SubmissionAuthor = form.cleaned_data["instance"]
965 author_string = str(author)
966 author.delete()
968 SubmissionLog.add_message(
969 self.submission,
970 content=f"Author removed: {author_string}",
971 content_en=_("Author added") + f": {author_string}",
972 request=request.user,
973 significant=True,
974 )
975 messages.success(request, _("The author has been removed."))
976 return response
978 def toggle_primary_author(self, request: HttpRequest) -> HttpResponse:
979 """
980 Toggle the `corresponding` boolean of a `SubmissionAuthor` from the submission.
981 """
982 response = HttpResponseRedirect(
983 reverse("mesh:submission_authors", kwargs={"submission_pk": self.submission.pk})
984 )
985 form = HiddenModelChoiceForm(request.POST, _queryset=self.submission.authors_censored)
986 if not form.is_valid():
987 messages.error(request, _("Something went wrong. Please try again."))
988 return response
990 author: SubmissionAuthor = form.cleaned_data["instance"]
991 author.corresponding = not author.corresponding
992 author.save()
993 word = "marked" if author.corresponding else "unmarked"
994 messages.success(
995 request,
996 _(f"Author {author} has been {word} as a corresponding contact."),
997 )
999 return response
1002class SubmissionConfirmView(LoginRequiredMixin, MeshObjectMixin, FormView):
1003 """
1004 View to confirm the submission the current SubmissionVersion.
1006 It's used both when submitting a Submission for the first time and when
1007 submitting a revised SubmissionVersion.
1008 """
1010 template_name = "mesh/submission/version_confirm.html"
1011 submission: Submission
1012 form_class = SubmissionConfirmForm
1014 def setup(self, request, *args, **kwargs):
1015 super().setup(request, *args, **kwargs)
1016 self.submission = self.get_submission()
1017 if not self.request.current_role.can_edit_submission(self.submission):
1018 raise PermissionError
1020 def get_success_url(self):
1021 return reverse("mesh:submission_details", kwargs={"submission_pk": self.submission.pk})
1023 def get(self, request, *args, **kwargs):
1024 if not self.submission.is_submittable():
1025 # TODO : Enhance error message
1026 # We theorically shouldn't even be able to get there with an invalid submission
1027 # But real examples have shown that it can happen
1028 messages.error(
1029 request,
1030 _("There must be at least 1 author attached to the submission."),
1031 )
1032 return super().get(request, *args, **kwargs)
1034 def get_object(self, queryset=None) -> SubmissionVersion:
1035 return self.submission.get_current_version()
1037 def get_context_data(self, *args, **kwargs):
1038 context = super().get_context_data(*args, **kwargs)
1040 context["form"].buttons = [
1041 Button(
1042 id="form_save",
1043 title=_("Submit"),
1044 icon_class="fa-check",
1045 attrs={
1046 "type": ["submit"],
1047 "class": ["save-button", "button-highlight"],
1048 },
1049 )
1050 ]
1052 if self.submission.is_draft:
1053 stepper = get_submission_stepper(self.submission)
1054 stepper.set_active_step("confirm")
1055 context["stepper"] = stepper
1057 context["submission_proxy"] = SubmissionProxy(self.submission, self.request.current_role)
1059 files = []
1060 files.append(
1061 {
1062 "file_wrapper": self.submission.get_current_version().main_file,
1063 "type": "Main",
1064 }
1065 )
1067 for file_wrapper in self.submission.get_current_version().additional_files.all():
1068 files.append({"file_wrapper": file_wrapper, "type": "Additional"})
1069 context["submission_files"] = files
1071 # # Breadcrumb
1072 # breadcrumb = get_submission_breadcrumb(self.submission)
1073 # breadcrumb.add_item(
1074 # title=_("Authors"),
1075 # url=reverse("mesh:submission_confirm", kwargs={"pk": self.submission.pk}),
1076 # )
1077 # context["breadcrumb"] = breadcrumb
1079 return context
1081 def form_valid(self, form: SubmissionConfirmForm) -> HttpResponse:
1082 """
1083 Submit the submission's current version.
1084 """
1085 self.submission.submit(self.request.user)
1087 if self.submission.get_current_version().number == 1:
1088 messages.success(
1089 self.request,
1090 _("Your submission has been successfully saved and confirmed."),
1091 )
1092 else:
1093 messages.success(self.request, _("Your revisions were successfully submitted."))
1095 previous_round_number = self.submission.get_current_version().number - 1
1096 if previous_round_number > 0:
1097 previous_round = self.submission.versions.get(number=previous_round_number)
1098 previous_reviewers = [
1099 review.reviewer
1100 for review in previous_round.reviews.filter(state=ReviewState.SUBMITTED.value)
1101 ]
1102 for previous_reviewer in previous_reviewers:
1103 qs = Suggestion.objects.filter(
1104 submission=self.submission, suggested_user=previous_reviewer
1105 )
1106 if not qs.exists():
1107 add_suggestion(
1108 submission=self.submission,
1109 suggested_user=previous_reviewer,
1110 suggested_reviewer=None,
1111 )
1113 return HttpResponseRedirect(self.get_success_url())
1116class SubmissionDeleteView(LoginRequiredMixin, MeshObjectMixin, DeleteView):
1117 model = Submission
1118 submission: Submission
1119 form_class = SubmissionDeleteForm
1120 template_name = "mesh/forms/submission_delete.html"
1121 restricted_roles = [JournalManager, Author]
1122 message_on_restrict = False
1124 def setup(self, request, *args, **kwargs):
1125 super().setup(request, *args, **kwargs)
1126 self.submission = self.get_submission()
1127 if not self.request.current_role.can_edit_submission(self.submission):
1128 raise PermissionError
1130 def get_object(self, *args, **kwargs) -> Submission:
1131 """
1132 Override `get_object` built-in method to avoid an additional look-up when
1133 we already have the object loaded.
1134 """
1135 return self.submission
1137 def get_success_url(self):
1138 return reverse("mesh:submission_list")
1140 def get_context_data(self, *args, **kwargs):
1141 context = super().get_context_data(*args, **kwargs)
1143 context["submission"] = self.submission
1144 context["form"].buttons = [
1145 Button(
1146 id="form_save",
1147 title=_("Delete"),
1148 icon_class="fa-trash",
1149 attrs={
1150 "type": ["submit"],
1151 "class": ["save-button", "button-highlight"],
1152 },
1153 )
1154 ]
1156 return context