Coverage for src / mesh / views / views_submission_edit.py: 48%
503 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-09-11 12:27 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-09-11 12:27 +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.validation = {
229 "required_checks": ["contributors", "abstract", "title", "pdf"],
230 "blocking_checks": [],
231 }
233 self.submission = None
234 self.version = None
235 self.submission_main_file = None
237 def setup(self, request, *args, **kwargs):
238 super().setup(request, *args, **kwargs)
239 self.submission = self.get_submission()
240 if not self.request.current_role.can_edit_submission(self.submission):
241 raise PermissionError
243 def get(self, request, *args, **kwargs):
244 colid = app_settings.COLID
245 data_article = create_articledata()
246 data = self.convert_data_for_vuejs3(data_article, colid=colid)
247 data["editorial_tools"] = self.editorial_tools
249 def obj_to_dict(obj):
250 return obj.__dict__
252 data["article"]["colid"] = colid
253 dump = json.dumps(data, default=obj_to_dict)
254 return HttpResponse(dump, content_type="application/json")
256 def handle_pdf_for_vuejs3(self, data, data_article, method=""):
257 if method == "get":
258 submission_main_file = getattr(
259 self.submission.get_current_version(), "main_file", None
260 )
261 if submission_main_file is not None:
262 data["article"]["pdf_name"] = submission_main_file.file.name
263 else:
264 data["article"]["pdf_name"] = "No file uploaded"
266 elif method == "post" and "pdf" in self.request.FILES:
267 submission_main_file = getattr(
268 self.submission.get_current_version(), "main_file", None
269 )
270 if submission_main_file is not None:
271 submission_main_file.delete()
273 submission_main_file = SubmissionMainFile(
274 attached_to=self.submission.get_current_version(), file=self.request.FILES["pdf"]
275 )
276 submission_main_file.save()
278 def handle_titles_for_vuejs3(self, data, data_article, method=""):
279 if method == "get":
280 data["article"]["titles"] = [
281 {"lang": data_article.lang, "value": self.submission.name}
282 ]
283 else:
284 super().handle_titles_for_vuejs3(data, data_article, method)
286 def handle_contributors_for_vuejs3(self, data, data_article, method=""):
287 if method == "get":
288 contributors = []
289 for author in self.submission.authors.all():
290 contrib = create_contributor()
291 contrib["first_name"] = author.first_name
292 contrib["last_name"] = author.last_name
293 contrib["email"] = author.email
294 contrib["corresponding"] = author.corresponding
295 contributors.append(contrib)
296 data["article"]["contributors"] = contributors
297 else:
298 super().handle_contributors_for_vuejs3(data, data_article, method)
300 def handle_abstracts_for_vuejs3(self, data, data_article, method=""):
301 if method == "get":
302 data["article"]["abstracts"] = [{"lang": "en", "value": self.submission.abstract}]
303 else:
304 super().handle_abstracts_for_vuejs3(data, data_article, method)
306 def post(self, request, *args, **kwargs):
307 body_unicode = request.POST.get("data", "")
308 data = json.loads(body_unicode)
310 if (
311 data["title_html"][0]["value"] == ""
312 or data["abstracts"][0]["value"] == ""
313 or len(data["contributors"]) == 0
314 or ("pdf" not in self.request.FILES and data["pdf"]["pdf_name"] == "No file uploaded")
315 ):
316 return JsonResponse({"status_code": 400})
318 data["colid"] = app_settings.COLID
320 article_data = self.convert_data_from_vuejs3(data)
321 title = article_data.title_html
323 abstract = article_data.abstracts[0]["value_html"]
325 self.submission.name = title
326 self.submission.abstract = abstract
327 self.submission._user = request.user
328 self.submission.save()
330 current_version = self.submission.get_current_version()
331 current_version._user = request.user
332 current_version.save()
334 SubmissionAuthor.objects.filter(submission=self.submission).delete()
336 for contrib in article_data.contributors:
337 author = SubmissionAuthor(
338 submission=self.submission,
339 first_name=contrib["first_name"],
340 last_name=contrib["last_name"],
341 email=contrib["email"],
342 corresponding=contrib["corresponding"],
343 )
344 author.save()
346 data["redirect"] = {
347 "redirect": True,
348 "url": reverse(
349 "mesh:submission_info_update", kwargs={"submission_pk": self.submission.pk}
350 ),
351 }
353 return JsonResponse(data)
356class SubmissionRedirectFromVue(TemplateView):
357 template_name = "mesh/forms/form_full_page_redirect.html"
359 def get_context_data(self, **kwargs):
360 context = super().get_context_data(**kwargs)
361 context["message"] = (
362 "La sauvegarde dans Vuejs puis la redirection sur une autre page fonctionne"
363 )
364 return context
366 def get(self, request, *args, **kwargs):
367 """
368 Override the get method to set the form action to "create".
369 """
371 messages.success(self.request, _("New submission successfully created."))
373 return super().get(request, *args, **kwargs)
376class SubmissionInfoView(LoginRequiredMixin, MeshObjectMixin, UpdateView):
377 """
378 View for updating the submission info.
379 """
381 model = Submission
382 form_class = SubmissionInfoForm
383 template_name = "mesh/forms/submission_info.html"
384 submission: Submission
385 restricted_roles = [Author]
386 message_on_restrict = False
388 def setup(self, request, *args, **kwargs):
389 super().setup(request, *args, **kwargs)
390 self.submission = self.get_submission()
391 if not self.request.current_role.can_edit_submission(self.submission):
392 raise PermissionError
394 def get_success_url(self) -> str:
395 # if FormAction.NEXT.value in self.request.POST:
396 # return reverse_lazy("mesh:submission_authors", kwargs={"pk": self.submission.pk})
398 return reverse("mesh:submission_confirm", kwargs={"submission_pk": self.submission.pk})
400 def get_fail_redirect_uri(self) -> str:
401 """
402 Return to the submission details page if the user cannot update the submission.
403 """
404 return self.get_success_url()
406 def get_object(self, *args, **kwargs) -> Submission:
407 """
408 Override `get_object` built-in method to avoid an additional look-up when
409 we already have the object loaded.
410 """
411 return self.submission
413 def get_context_data(self, *args, **kwargs):
414 context = super().get_context_data(*args, **kwargs)
415 context["page_title"] = _("Edit submission info")
417 if self.submission.is_draft:
418 step_id = "info"
419 stepper = get_submission_stepper(self.submission)
420 stepper.set_active_step(step_id)
421 context["stepper"] = stepper
423 # No Save/Next button in the form, they are put in the stepper
424 context["form"].buttons = [] # = submission_stepper_form_buttons()
426 suggestions = self.submission.suggestions_for_reviewer.all()
427 for suggestion in suggestions:
428 reviewer = (
429 suggestion.suggested_reviewer
430 if suggestion.suggested_reviewer is not None
431 else suggestion.suggested_user
432 )
433 if suggestion.suggest_to_avoid:
434 context["avoid_reviewer"] = reviewer
435 else:
436 context["suggested_reviewer"] = reviewer
438 return context
440 def form_valid(self, form):
441 form.instance._user = self.request.user
443 Suggestion.objects.filter(submission=self.submission).delete()
445 for prefix in ["reviewer", "avoid"]:
446 person_info = {
447 "first_name": form.data.get(f"{prefix}_first_name", ""),
448 "last_name": form.data.get(f"{prefix}_last_name", ""),
449 "email": form.data.get(f"{prefix}_email", ""),
450 }
452 if person_info["email"] != "":
453 add_suggestion_from_person(
454 self.submission, person_info, suggest_to_avoid=prefix == "avoid"
455 )
457 return super().form_valid(form)
460class SubmissionEditArticleMetadataView(LoginRequiredMixin, MeshObjectMixin, UpdateView):
461 """
462 View for updating a submission (only the metadata).
463 Only the GET is used to display the form (with VueJS)
464 The POST is handled by SubmissionEditArticleMetadataVuejsAPIView
465 """
467 model = Submission
468 form_class = SubmissionEditArticleMetadataForm
469 template_name = "mesh/forms/form_full_page.html"
470 submission: Submission
471 restricted_roles = [Author]
472 message_on_restrict = False
474 def setup(self, request, *args, **kwargs):
475 super().setup(request, *args, **kwargs)
476 self.submission = self.get_submission()
477 if not self.request.current_role.can_edit_submission(self.submission):
478 raise PermissionError
480 # def get_fail_redirect_uri(self) -> str:
481 # """
482 # Return to the submission details page if the user cannot update the submission.
483 # Not used with VueJS
484 # """
485 # return self.get_success_url()
487 def get_object(self, *args, **kwargs) -> Submission:
488 """
489 Override `get_object` built-in method to avoid an additional look-up when
490 we already have the object loaded.
491 """
492 return self.submission
494 def get_context_data(self, *args, **kwargs):
495 context = super().get_context_data(*args, **kwargs)
496 context["page_title"] = _("Edit article metadata")
498 # data-get-url is used by VueJS to know the GET url to call
499 context["data_get_url"] = reverse(
500 "mesh:submission_update_vuejs",
501 kwargs={"submission_pk": self.submission.pk},
502 )
504 if self.submission.is_draft:
505 step_id = "metadata"
506 stepper = get_submission_stepper(self.submission)
507 stepper.set_active_step(step_id)
508 context["stepper"] = stepper
510 # No Save/Next button in the form, they are put in the stepper
511 context["form"].buttons = [] # = submission_stepper_form_buttons()
513 # Generate breadcrumb data
514 # breadcrumb = get_submission_breadcrumb(self.submission)
515 # breadcrumb.add_item(
516 # title=_("Edit metadata"),
517 # url=reverse_lazy("mesh:submission_edit_article_metadata", kwargs={"pk": self.submission.pk}),
518 # )
519 # context["breadcrumb"] = breadcrumb
520 return context
522 def get_success_url(self) -> str:
523 if FormAction.NEXT.value in self.request.POST:
524 # No POST with VueJS. We shouldn't reach this clause
525 return reverse("mesh:submission_authors", kwargs={"submission_pk": self.submission.pk})
526 return reverse("mesh:submission_details", kwargs={"submission_pk": self.submission.pk})
528 # def form_valid(self, form: SubmissionUpdateForm) -> HttpResponse:
529 # form.instance._user = self.request.user
530 # if not form.changed_data:
531 # form.instance.do_not_update = True
532 #
533 # response = super().form_valid(form)
534 #
535 # if form.has_changed():
536 # SubmissionLog.add_message(
537 # self.submission,
538 # content=_("Modification of the submission's metadata"),
539 # content_en="Modification of the submission's metadata",
540 # user=self.request.user,
541 # )
542 # return response
545class SubmissionVersionCreateView(
546 LoginRequiredMixin, MeshObjectMixin, SubmittableModelFormMixin, CreateView
547):
548 """
549 View for creating a new submission version.
550 """
552 model = SubmissionVersion
553 form_class = SubmissionVersionForm
554 template_name = "mesh/forms/form_full_page.html"
555 submission: Submission
556 restricted_roles = [Author]
557 save_submission = False
558 add_confirm_message = False
560 def setup(self, request, *args, **kwargs):
561 super().setup(request, *args, **kwargs)
562 self.submission = self.get_submission()
563 if not self.request.current_role.can_create_version(self.submission):
564 raise PermissionError
566 def get_form_kwargs(self):
567 kwargs = super().get_form_kwargs()
568 kwargs["submission"] = self.submission
569 return kwargs
571 def form_valid(self, form):
572 form.instance.submission = self.submission
573 return super().form_valid(form)
575 def get_success_url(self) -> str:
576 if FormAction.NEXT.value in self.request.POST:
577 # No POST with VueJS. We shouldn't reach this clause
578 return self.submit_url()
579 return reverse("mesh:submission_details", kwargs={"submission_pk": self.submission.pk})
581 def submit_url(self) -> str:
582 return reverse("mesh:submission_confirm", kwargs={"submission_pk": self.submission.pk})
584 # def form_pre_save(self, form: SubmissionVersionForm):
585 # Not used with VueJS
586 # form.instance._user = self.request.user
587 # form.instance.submission = self.submission
589 def get_context_data(self, *args, **kwargs):
590 context = super().get_context_data(*args, **kwargs)
591 current_version = self.submission.get_current_version()
592 version = current_version.number + 1 if current_version else 1
593 context["page_title"] = _("Submission files")
594 if not self.submission.is_draft:
595 context["page_title"] += f" - Version {version}"
597 submission_url = reverse(
598 "mesh:submission_details", kwargs={"submission_pk": self.submission.pk}
599 )
600 description = "Please fill the form below to submit your files for the submission "
601 description += f"<a href='{submission_url}'><i>{self.submission.name}</i></a>.<br>"
602 context["form_description"] = [description]
604 if self.submission.is_draft:
605 stepper = get_submission_stepper(self.submission)
606 stepper.set_active_step("version")
607 context["stepper"] = stepper
608 context["form"].buttons = []
610 # # Generate breadcrumb data
611 # breadcrumb = get_submission_breadcrumb(self.submission)
612 # breadcrumb.add_item(
613 # title=_("New version"),
614 # url=reverse_lazy(
615 # "mesh:submission_version_create", kwargs={"submission_pk": self.submission.pk}
616 # ),
617 # )
618 # context["breadcrumb"] = breadcrumb
619 return context
621 # def form_valid(self, form):
622 # result = super().form_valid(form)
623 # return result
626class SubmissionVersionUpdateView(
627 LoginRequiredMixin, MeshObjectMixin, SubmittableModelFormMixin, UpdateView
628):
629 """
630 View for updating a submission version.
632 Submitting a version = submitting the whole submission.
633 This requires extra care about checking that all previous steps have been
634 taken when this is the first version.
635 """
637 model = SubmissionVersion
638 form_class = SubmissionVersionForm
639 template_name = "mesh/forms/form_full_page.html"
641 restricted_roles = [Author]
642 message_on_restrict = False
643 save_submission = False
644 add_confirm_message = False
646 def setup(self, request, *args, **kwargs):
647 super().setup(request, *args, **kwargs)
648 self.version = self.get_version()
649 if not self.request.current_role.can_edit_version(self.submission):
650 raise PermissionError
652 def get_fail_redirect_uri(self) -> str:
653 """
654 Redirects to the submission details page if the user cannot update the version.
655 """
656 return self.get_success_url()
658 def get_object(self, *args, **kwargs) -> SubmissionVersion:
659 """
660 Returns the already fetched version.
661 """
662 return self.version
664 def get_form_kwargs(self) -> dict[str, Any]:
665 kwargs = super().get_form_kwargs()
666 if self.request.GET.get(SUBMIT_QUERY_PARAMETER, None) == "true":
667 kwargs[SUBMIT_QUERY_PARAMETER] = True
668 return kwargs
670 def get_success_url(self) -> str:
671 if FormAction.NEXT.value in self.request.POST:
672 return self.submit_url()
673 return reverse(
674 "mesh:submission_details", kwargs={"submission_pk": self.version.submission.pk}
675 )
677 def submit_url(self) -> str:
678 return reverse(
679 "mesh:submission_confirm", kwargs={"submission_pk": self.version.submission.pk}
680 )
682 def form_pre_save(self, form: SubmissionVersionForm) -> None:
683 form.instance._user = self.request.user
685 def get_context_data(self, *args, **kwargs):
686 context = super().get_context_data(*args, **kwargs)
687 context["page_title"] = _(f"Submission files - Version {self.version.number}")
689 submission_url = reverse(
690 "mesh:submission_details", kwargs={"submission_pk": self.version.submission.pk}
691 )
692 description = "Please fill the form below to submit your files for the submission "
693 description += f"<a href='{submission_url}'><i>{self.version.submission.name}</i></a>.<br>"
694 context["form_description"] = [description]
696 if self.version.submission.is_draft:
697 stepper = get_submission_stepper(self.version.submission)
698 stepper.set_active_step("version")
699 context["stepper"] = stepper
700 context["form"].buttons = []
702 # # Generate breadcrumb data
703 # breadcrumb = get_submission_breadcrumb(self.version.submission)
704 # breadcrumb.add_item(
705 # title=_("Edit version") + f" #{self.version.number}",
706 # url=reverse("mesh:submission_version_update", kwargs={"pk": self.version.pk}),
707 # )
708 # context["breadcrumb"] = breadcrumb
709 return context
711 def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
712 """
713 View for updating the submission or deleting one of the associated files.
714 """
715 deletion_requested, _ = post_delete_model_file(self.version, request, self.role_handler)
717 if deletion_requested:
718 # Update the submission object.
719 self.version = self.get_version()
720 return self.get(request, *args, **kwargs)
722 return super().post(request, *args, **kwargs)
725class SubmissionResumeView(LoginRequiredMixin, MeshObjectMixin, View):
726 """
727 View handling the first submit of a submission. It resumes the submission process
728 at the correct edit view according to the missing data.
730 Handles the submission stepper. It redirects to the correct step according to
731 the submission status.
732 - STEP 1: Preprint id
733 - STEP 2: Article Metadata
734 - STEP 3: Submission Info
735 - STEP 4: Confirmation
736 """
738 submission: Submission
739 restricted_roles = [Author]
741 def setup(self, request, *args, **kwargs):
742 super().setup(request, *args, **kwargs)
743 self.submission = self.get_submission()
744 if not self.request.current_role.can_submit_submission(self.submission):
745 raise PermissionError
747 def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
748 stepper = get_submission_stepper(self.submission)
750 # Redirects to the last active step
751 for i in range(len(stepper.steps)):
752 step = stepper.steps[-(i + 1)]
753 if step.href and step.can_navigate:
754 return HttpResponseRedirect(step.href)
756 return HttpResponseRedirect(reverse("mesh:submission_create"))
759class SubmissionAuthorView(LoginRequiredMixin, MeshObjectMixin, TemplateView):
760 """
761 View to add/remove a submission author from a given submission.
762 """
764 submission: Submission
765 template_name = "mesh/forms/submission_author.html"
766 restricted_roles = [Author]
767 init_form: SubmissionAuthorForm | None = None
768 _FORM_ACTION_CORRESPONDING = "_action_toggle_corresponding"
770 def setup(self, request, *args, **kwargs):
771 super().setup(request, *args, **kwargs)
772 self.submission = self.get_submission()
773 if not self.request.current_role.can_edit_submission(self.submission):
774 raise PermissionError
776 def get_context_data(self, *args, **kwargs):
777 context = super().get_context_data(*args, **kwargs)
779 context["submission"] = self.submission
781 authors_qs = self.submission.authors.all()
782 context["authors"] = [
783 {
784 "author": author,
785 "delete_form": HiddenModelChoiceForm(
786 form_action=FormAction.DELETE.value,
787 initial={"instance": author},
788 _queryset=authors_qs,
789 ),
790 "corresponding_form": HiddenModelChoiceForm(
791 form_action="_action_toggle_corresponding",
792 initial={"instance": author},
793 _queryset=authors_qs,
794 ),
795 "buttons": [
796 Button(
797 id=f"author-corresponding-{author.pk}",
798 title=_("Corresponding"),
799 icon_class=("fa-toggle-on" if author.corresponding else "fa-toggle-off"),
800 form=HiddenModelChoiceForm(
801 initial={"instance": author}, _queryset=authors_qs
802 ),
803 attrs={
804 "href": [
805 reverse(
806 "mesh:submission_authors",
807 kwargs={"submission_pk": self.submission.pk},
808 )
809 ],
810 "type": ["submit"],
811 "class": ["primary" if author.corresponding else "inactive"],
812 "name": [self._FORM_ACTION_CORRESPONDING],
813 "data-tooltip": [
814 _("Click to toggle whether the author is a corresponding contact.")
815 ],
816 },
817 ),
818 Button(
819 id=f"author-delete-{author.pk}",
820 title=_("Remove"),
821 icon_class="fa-trash",
822 form=HiddenModelChoiceForm(
823 initial={"instance": author}, _queryset=authors_qs
824 ),
825 attrs={
826 "href": [
827 reverse(
828 "mesh:submission_authors",
829 kwargs={"submission_pk": self.submission.pk},
830 )
831 ],
832 "type": ["submit"],
833 "class": ["button-error"],
834 "name": [FormAction.DELETE.value],
835 },
836 ),
837 ],
838 }
839 for author in self.submission.authors_censored
840 ]
842 initial = {}
843 if not self.submission.authors_censored:
844 initial.update(
845 {
846 "first_name": self.request.user.first_name,
847 "last_name": self.request.user.last_name,
848 "email": self.request.user.email,
849 "primary": True,
850 }
851 )
853 form = self.init_form or SubmissionAuthorForm(
854 submission=self.submission, initial=initial or None
855 )
857 form.buttons = [
858 Button(
859 id="form_save",
860 title=_("Add author"),
861 icon_class="fa-plus",
862 attrs={"type": ["submit"], "class": ["btn-save-form"]},
863 )
864 ]
866 context["page_title"] = _("Authors")
868 if self.submission.is_draft:
869 step_id = "authors"
870 stepper = get_submission_stepper(self.submission)
871 stepper.set_active_step(step_id)
872 context["stepper"] = stepper
873 # version_step = stepper.get_step("version")
874 # if version_step and version_step.can_navigate and version_step.href:
875 # button = Button(
876 # id="next",
877 # title=_("Next"),
878 # icon_class="fa-right-long",
879 # attrs={"href": [version_step.href], "class": ["as-button"]},
880 # )
881 # context["title_buttons"] = [button]
882 #
883 # form.buttons.append(
884 # Button(
885 # id="form_next",
886 # title=_("Next"),
887 # icon_class="fa-right-long",
888 # attrs={
889 # "href": [version_step.href],
890 # "type": ["submit"],
891 # "class": ["as-button", "btn-save-form"],
892 # },
893 # )
894 # )
896 context["form"] = form
898 # # Generate breadcrumb data
899 # breadcrumb = get_submission_breadcrumb(self.submission)
900 # breadcrumb.add_item(
901 # title=_("Authors"),
902 # url=reverse("mesh:submission_authors", kwargs={"pk": self.submission.pk}),
903 # )
904 # context["breadcrumb"] = breadcrumb
906 return context
908 def post(self, request, *args, **kwargs):
909 if FormAction.DELETE.value in request.POST:
910 return self.remove_author(request)
911 elif self._FORM_ACTION_CORRESPONDING in request.POST:
912 return self.toggle_primary_author(request)
914 return self.add_author(request, *args, **kwargs)
916 def add_author(self, request, *args, **kwargs):
917 """
918 Add an `SubmissionAuthor` to the submission.
919 """
920 response = HttpResponseRedirect(
921 reverse("mesh:submission_authors", kwargs={"submission_pk": self.submission.pk})
922 )
923 form = SubmissionAuthorForm(request.POST, submission=self.submission)
924 if not form.is_valid():
925 self.init_form = form
926 return self.get(request, *args, **kwargs)
928 form.instance._user = self.request.user
929 form.instance.submission = self.submission
930 form.save()
932 SubmissionLog.add_message(
933 self.submission,
934 content=f"Author added: {form.instance.first_name} {form.instance.last_name} ({form.instance.email})",
935 content_en=_("Author added")
936 + f": {form.instance.first_name} {form.instance.last_name} ({form.instance.email})",
937 user=request.user,
938 significant=True,
939 )
940 messages.success(
941 request,
942 _("Author added")
943 + f": {form.instance.first_name} {form.instance.last_name} ({form.instance.email})",
944 )
946 return response
948 def remove_author(self, request: HttpRequest) -> HttpResponse:
949 """
950 Remove a `SubmissionAuthor` from the submission.
951 """
952 response = HttpResponseRedirect(
953 reverse("mesh:submission_authors", kwargs={"submission_pk": self.submission.pk})
954 )
955 form = HiddenModelChoiceForm(request.POST, _queryset=self.submission.authors.all())
956 if not form.is_valid():
957 messages.error(request, _("Something went wrong. Please try again."))
958 return response
960 if len(self.submission.authors_censored) < 2:
961 messages.error(
962 request,
963 _("There must be at least 1 author attached to the submission."),
964 )
965 return response
967 author: SubmissionAuthor = form.cleaned_data["instance"]
968 author_string = str(author)
969 author.delete()
971 SubmissionLog.add_message(
972 self.submission,
973 content=f"Author removed: {author_string}",
974 content_en=_("Author added") + f": {author_string}",
975 user=request.user,
976 significant=True,
977 )
978 messages.success(request, _("The author has been removed."))
979 return response
981 def toggle_primary_author(self, request: HttpRequest) -> HttpResponse:
982 """
983 Toggle the `corresponding` boolean of a `SubmissionAuthor` from the submission.
984 """
985 response = HttpResponseRedirect(
986 reverse("mesh:submission_authors", kwargs={"submission_pk": self.submission.pk})
987 )
988 form = HiddenModelChoiceForm(request.POST, _queryset=self.submission.authors.all())
989 if not form.is_valid():
990 messages.error(request, _("Something went wrong. Please try again."))
991 return response
993 author: SubmissionAuthor = form.cleaned_data["instance"]
994 author.corresponding = not author.corresponding
995 author.save()
996 word = "marked" if author.corresponding else "unmarked"
997 messages.success(
998 request,
999 _(f"Author {author} has been {word} as a corresponding contact."),
1000 )
1002 return response
1005class SubmissionConfirmView(LoginRequiredMixin, MeshObjectMixin, FormView):
1006 """
1007 View to confirm the submission the current SubmissionVersion.
1009 It's used both when submitting a Submission for the first time and when
1010 submitting a revised SubmissionVersion.
1011 """
1013 template_name = "mesh/submission/version_confirm.html"
1014 submission: Submission
1015 form_class = SubmissionConfirmForm
1017 def setup(self, request, *args, **kwargs):
1018 super().setup(request, *args, **kwargs)
1019 self.submission = self.get_submission()
1020 if not self.request.current_role.can_edit_submission(self.submission):
1021 raise PermissionError
1023 def get_success_url(self):
1024 return reverse("mesh:submission_details", kwargs={"submission_pk": self.submission.pk})
1026 def get(self, request, *args, **kwargs):
1027 if not self.submission.is_submittable():
1028 # TODO : Enhance error message
1029 # We theorically shouldn't even be able to get there with an invalid submission
1030 # But real examples have shown that it can happen
1031 messages.error(
1032 request,
1033 _("There must be at least 1 author attached to the submission."),
1034 )
1035 return super().get(request, *args, **kwargs)
1037 def get_object(self, queryset=None) -> SubmissionVersion:
1038 return self.submission.get_current_version()
1040 def get_context_data(self, *args, **kwargs):
1041 context = super().get_context_data(*args, **kwargs)
1043 context["form"].buttons = [
1044 Button(
1045 id="form_save",
1046 title=_("Submit"),
1047 icon_class="fa-check",
1048 attrs={
1049 "type": ["submit"],
1050 "class": ["btn-save-form", "button-highlight"],
1051 },
1052 )
1053 ]
1055 if self.submission.is_draft:
1056 stepper = get_submission_stepper(self.submission)
1057 stepper.set_active_step("confirm")
1058 context["stepper"] = stepper
1060 context["submission_proxy"] = SubmissionProxy(self.submission, self.request.current_role)
1062 files = []
1063 files.append(
1064 {
1065 "file_wrapper": self.submission.get_current_version().main_file,
1066 "type": "Main",
1067 }
1068 )
1070 for file_wrapper in self.submission.get_current_version().additional_files.all():
1071 files.append({"file_wrapper": file_wrapper, "type": "Additional"})
1072 context["submission_files"] = files
1074 # # Breadcrumb
1075 # breadcrumb = get_submission_breadcrumb(self.submission)
1076 # breadcrumb.add_item(
1077 # title=_("Authors"),
1078 # url=reverse("mesh:submission_confirm", kwargs={"pk": self.submission.pk}),
1079 # )
1080 # context["breadcrumb"] = breadcrumb
1082 return context
1084 def form_valid(self, form: SubmissionConfirmForm) -> HttpResponse:
1085 """
1086 Submit the submission's current version.
1087 """
1088 self.submission.submit(self.request.user)
1090 if self.submission.get_current_version().number == 1:
1091 messages.success(
1092 self.request,
1093 _("Your submission has been successfully saved and confirmed."),
1094 )
1095 else:
1096 messages.success(self.request, _("Your revisions were successfully submitted."))
1098 previous_round_number = self.submission.get_current_version().number - 1
1099 if previous_round_number > 0:
1100 previous_round = self.submission.versions.get(number=previous_round_number)
1101 previous_reviewers = [
1102 review.reviewer
1103 for review in previous_round.reviews.filter(state=ReviewState.SUBMITTED.value)
1104 ]
1105 for previous_reviewer in previous_reviewers:
1106 qs = Suggestion.objects.filter(
1107 submission=self.submission, suggested_user=previous_reviewer
1108 )
1109 if not qs.exists():
1110 add_suggestion(
1111 submission=self.submission,
1112 suggested_user=previous_reviewer,
1113 suggested_reviewer=None,
1114 )
1116 return HttpResponseRedirect(self.get_success_url())
1119class SubmissionDeleteView(LoginRequiredMixin, MeshObjectMixin, DeleteView):
1120 model = Submission
1121 submission: Submission
1122 form_class = SubmissionDeleteForm
1123 template_name = "mesh/forms/submission_delete.html"
1124 restricted_roles = [JournalManager, Author]
1125 message_on_restrict = False
1127 def setup(self, request, *args, **kwargs):
1128 super().setup(request, *args, **kwargs)
1129 self.submission = self.get_submission()
1130 if not self.request.current_role.can_edit_submission(self.submission):
1131 raise PermissionError
1133 def get_object(self, *args, **kwargs) -> Submission:
1134 """
1135 Override `get_object` built-in method to avoid an additional look-up when
1136 we already have the object loaded.
1137 """
1138 return self.submission
1140 def get_success_url(self):
1141 return reverse("mesh:submission_list")
1143 def get_context_data(self, *args, **kwargs):
1144 context = super().get_context_data(*args, **kwargs)
1146 context["submission"] = self.submission
1147 context["form"].buttons = [
1148 Button(
1149 id="form_save",
1150 title=_("Delete"),
1151 icon_class="fa-trash",
1152 attrs={
1153 "type": ["submit"],
1154 "class": ["btn-save-form", "button-highlight"],
1155 },
1156 )
1157 ]
1159 return context