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
« prev ^ index » next coverage.py v7.13.1, created at 2026-03-26 09:28 +0000
1import json
2from typing import Any
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
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
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
48class SubmissionCreateView(RoleMixin, CreateView):
49 """
50 View for creating a new submission with a preprint id.
51 """
53 model = Submission
54 form_class = SubmissionCreateForm
55 template_name = "mesh/forms/form_full_page.html"
56 restricted_roles = [Author]
58 def restrict_dispatch(self, request: HttpRequest, *args, **kwargs):
59 self.submission = None
60 return not self.role_handler.check_rights("can_create_submission")
62 def get_success_url(self) -> str:
63 """
64 Redirects to the SubmissionAuthor view to add authors to the
65 newly created submission.
66 """
68 return reverse_lazy(
69 "mesh:submission_edit_article_metadata", kwargs={"pk": self.submission.pk}
70 )
72 def form_valid(self, form: SubmissionCreateForm) -> HttpResponse:
73 preprint_id = form.cleaned_data["preprint_id"]
75 title = ""
76 abstract = ""
77 article_data = None
78 if preprint_id != "":
79 article_data = fetch_article(preprint_id, with_bib=False)
81 title = article_data.title_html
82 abstract = ""
83 if len(article_data.abstracts) > 0:
84 abstract = article_data.abstracts[0]["value_html"]
86 self.submission = Submission(name=title, abstract=abstract, author_agreement=False)
87 self.submission._user = self.request.user
88 self.submission.save()
90 version = SubmissionVersion(submission=self.submission)
91 version._user = self.request.user
92 version.save()
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()
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 )
113 return HttpResponseRedirect(self.get_success_url())
115 def get_context_data(self, *args, **kwargs):
116 context = super().get_context_data(*args, **kwargs)
117 context["page_title"] = _("New submission")
119 # context["data-get-url"] = reverse_lazy("mesh:submission_create_vuejs")
121 # Stepper
122 step_id = "preprint"
123 stepper = get_submission_stepper(None)
124 stepper.set_active_step(step_id)
125 context["stepper"] = stepper
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()
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
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
145class SubmissionPreprintUpdateView(RoleMixin, UpdateView):
146 """
147 View for updating a submission with a new preprint id.
148 """
150 model = Submission
151 form_class = SubmissionCreateForm
152 template_name = "mesh/forms/form_full_page.html"
153 restricted_roles = [Author]
155 def restrict_dispatch(self, request: HttpRequest, *args, **kwargs):
156 pk = kwargs["pk"]
157 self.submission = get_object_or_404(self.model, pk=pk)
159 return not self.role_handler.check_rights("can_edit_submission", self.submission)
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
168 def get_success_url(self) -> str:
169 """
170 Redirects to the SubmissionAuthor view to add authors to the
171 newly created submission.
172 """
174 return reverse_lazy("mesh:submission_edit_article_metadata", kwargs={"pk": self.object.pk})
176 def form_valid(self, form: SubmissionCreateForm) -> HttpResponse:
177 preprint_id = form.cleaned_data["preprint_id"]
179 if preprint_id != "":
180 article_data = fetch_article(preprint_id, with_bib=False)
182 title = article_data.title_html
183 self.submission.name = title
185 if len(article_data.abstracts) > 0:
186 abstract = article_data.abstracts[0]["value_tex"]
187 self.submission.abstract = abstract
189 self.submission._user = self.request.user
190 self.submission.save()
192 current_version = self.submission.current_version
193 current_version._user = self.request.user
194 current_version.save()
196 SubmissionAuthor.objects.filter(submission=self.submission).delete()
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()
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 )
216 return HttpResponseRedirect(self.get_success_url())
218 def get_context_data(self, *args, **kwargs):
219 context = super().get_context_data(*args, **kwargs)
220 context["page_title"] = _("Update submission preprint id")
222 # context["data-get-url"] = reverse_lazy("mesh:submission_create_vuejs")
224 # Stepper
225 step_id = "preprint"
226 stepper = get_submission_stepper(self.submission)
227 stepper.set_active_step(step_id)
228 context["stepper"] = stepper
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()
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
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
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"]
255 self.submission = None
256 self.version = None
257 self.submission_main_file = None
259 def restrict_dispatch(self, request: HttpRequest, *args, **kwargs):
260 pk = kwargs["pk"]
261 self.submission = get_object_or_404(Submission, pk=pk)
263 return not self.role_handler.check_rights("can_edit_submission", self.submission)
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
271 def obj_to_dict(obj):
272 return obj.__dict__
274 data["article"]["colid"] = colid
275 dump = json.dumps(data, default=obj_to_dict)
276 return HttpResponse(dump, content_type="application/json")
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"
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()
291 submission_main_file = SubmissionMainFile(
292 attached_to=self.submission.current_version, file=self.request.FILES["pdf"]
293 )
294 submission_main_file.save()
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)
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)
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)
324 def post(self, request, *args, **kwargs):
325 body_unicode = request.POST.get("data", "")
326 data = json.loads(body_unicode)
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})
336 data["colid"] = app_settings.COLID
338 article_data = self.convert_data_from_vuejs3(data)
339 title = article_data.title_html
341 abstract = article_data.abstracts[0]["value_html"]
343 self.submission.name = title
344 self.submission.abstract = abstract
345 self.submission._user = request.user
346 self.submission.save()
348 current_version = self.submission.current_version
349 current_version._user = request.user
350 current_version.save()
352 SubmissionAuthor.objects.filter(submission=self.submission).delete()
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()
364 data["redirect"] = {
365 "redirect": True,
366 "url": reverse_lazy("mesh:submission_info_update", kwargs={"pk": self.submission.pk}),
367 }
369 return JsonResponse(data)
372class SubmissionRedirectFromVue(TemplateView):
373 template_name = "mesh/forms/form_full_page_redirect.html"
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
382 def get(self, request, *args, **kwargs):
383 """
384 Override the get method to set the form action to "create".
385 """
387 messages.success(self.request, _("New submission successfully created."))
389 return super().get(request, *args, **kwargs)
392class SubmissionInfoView(RoleMixin, UpdateView):
393 """
394 View for updating the submission info.
395 """
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
404 def restrict_dispatch(self, request: HttpRequest, *args, **kwargs):
405 pk = kwargs["pk"]
406 self.submission = get_object_or_404(self.model, pk=pk)
408 return not self.role_handler.check_rights("can_edit_submission", self.submission)
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})
414 return reverse_lazy("mesh:submission_confirm", kwargs={"pk": self.submission.pk})
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()
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
429 def get_context_data(self, *args, **kwargs):
430 context = super().get_context_data(*args, **kwargs)
431 context["page_title"] = _("Edit submission info")
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
439 # No Save/Next button in the form, they are put in the stepper
440 context["form"].buttons = [] # = submission_stepper_form_buttons()
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
454 return context
456 def form_valid(self, form):
457 form.instance._user = self.request.user
459 Suggestion.objects.filter(submission=self.submission).delete()
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 }
468 if person_info["email"] != "":
469 add_suggestion_from_person(
470 self.submission, person_info, suggest_to_avoid=prefix == "avoid"
471 )
473 return super().form_valid(form)
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 """
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
490 def restrict_dispatch(self, request: HttpRequest, *args, **kwargs):
491 pk = kwargs["pk"]
492 self.submission = get_object_or_404(self.model, pk=pk)
494 return not self.role_handler.check_rights("can_edit_submission", self.submission)
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()
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
510 def get_context_data(self, *args, **kwargs):
511 context = super().get_context_data(*args, **kwargs)
512 context["page_title"] = _("Edit article metadata")
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 )
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
526 # No Save/Next button in the form, they are put in the stepper
527 context["form"].buttons = [] # = submission_stepper_form_buttons()
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
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})
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
561class SubmissionVersionCreateView(RoleMixin, SubmittableModelFormMixin, CreateView):
562 """
563 View for creating a new submission version.
564 """
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
574 def get_form_kwargs(self):
575 kwargs = super().get_form_kwargs()
576 kwargs["submission"] = self.submission
577 return kwargs
579 def form_valid(self, form):
580 form.instance.submission = self.submission
581 return super().form_valid(form)
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)
587 return not self.role_handler.check_rights("can_create_version", self.submission)
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})
595 def submit_url(self) -> str:
596 return reverse_lazy("mesh:submission_confirm", kwargs={"pk": self.submission.pk})
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
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}"
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]
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 = []
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
633 # def form_valid(self, form):
634 # result = super().form_valid(form)
635 # return result
638class SubmissionVersionUpdateView(RoleMixin, SubmittableModelFormMixin, UpdateView):
639 """
640 View for updating a submission version.
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 """
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
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)
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()
667 def get_object(self, *args, **kwargs) -> SubmissionVersion:
668 """
669 Returns the already fetched version.
670 """
671 return self.version
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
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})
684 def submit_url(self) -> str:
685 return reverse_lazy("mesh:submission_confirm", kwargs={"pk": self.version.submission.pk})
687 def form_pre_save(self, form: SubmissionVersionForm) -> None:
688 form.instance._user = self.request.user
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}")
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]
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 = []
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
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)
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)
727 return super().post(request, *args, **kwargs)
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.
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 """
743 submission: Submission
744 restricted_roles = [Author]
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)
750 def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
751 stepper = get_submission_stepper(self.submission)
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)
759 return HttpResponseRedirect(reverse_lazy("mesh:submission_create"))
762class SubmissionAuthorView(RoleMixin, TemplateView):
763 """
764 View to add/remove a submission author from a given submission.
765 """
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"
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)
777 @property
778 def authors(self) -> QuerySet[SubmissionAuthor]:
779 return SubmissionAuthor.objects.filter(submission=self.submission)
781 def get_context_data(self, *args, **kwargs):
782 context = super().get_context_data(*args, **kwargs)
784 context["submission"] = self.submission
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 ]
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 )
858 form = self.init_form or SubmissionAuthorForm(
859 submission=self.submission, initial=initial or None
860 )
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 ]
871 context["page_title"] = _("Authors")
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 # )
901 context["form"] = form
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
911 return context
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)
919 return self.add_author(request, *args, **kwargs)
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)
933 form.instance._user = self.request.user
934 form.instance.submission = self.submission
935 form.save()
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 )
951 return response
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
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
972 author: SubmissionAuthor = form.cleaned_data["instance"]
973 author_string = str(author)
974 author.delete()
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
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
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 )
1007 return response
1010class SubmissionConfirmView(RoleMixin, FormView):
1011 """
1012 View to confirm the submission the current SubmissionVersion.
1014 It's used both when submitting a Submission for the first time and when
1015 submitting a revised SubmissionVersion.
1016 """
1018 template_name = "mesh/submission/version_confirm.html"
1019 submission: Submission
1020 form_class = SubmissionConfirmForm
1022 def restrict_dispatch(self, request: HttpRequest, *args, **kwargs) -> bool:
1023 self.submission = get_object_or_404(Submission, pk=kwargs["pk"])
1025 if not self.role_handler.check_rights("can_edit_submission", self.submission):
1026 return True
1028 return False
1030 def get_success_url(self):
1031 return reverse_lazy("mesh:submission_details", kwargs={"pk": self.submission.pk})
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)
1044 def get_object(self, queryset=None) -> SubmissionVersion:
1045 return self.submission.current_version
1047 def get_context_data(self, *args, **kwargs):
1048 context = super().get_context_data(*args, **kwargs)
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 ]
1062 if self.submission.is_draft:
1063 stepper = get_submission_stepper(self.submission)
1064 stepper.set_active_step("confirm")
1065 context["stepper"] = stepper
1067 context["submission_proxy"] = SubmissionProxy(self.submission, self.role_handler)
1069 files = []
1070 files.append(
1071 {
1072 "file_wrapper": self.submission.current_version.main_file,
1073 "type": "Main",
1074 }
1075 )
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
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
1089 return context
1091 def form_valid(self, form: SubmissionConfirmForm) -> HttpResponse:
1092 """
1093 Submit the submission's current version.
1094 """
1095 self.submission.submit(self.request.user)
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."))
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 )
1123 return HttpResponseRedirect(self.get_success_url())
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
1134 def restrict_dispatch(self, request: HttpRequest, *args, **kwargs):
1135 pk = kwargs["pk"]
1136 self.submission = get_object_or_404(self.model, pk=pk)
1138 return not self.role_handler.check_rights("can_edit_submission", self.submission)
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
1147 def get_success_url(self):
1148 return reverse_lazy("mesh:submission_list")
1150 def get_context_data(self, *args, **kwargs):
1151 context = super().get_context_data(*args, **kwargs)
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 ]
1166 return context