Coverage for src / mesh / views / views_review.py: 67%
369 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 datetime
2import re
3from typing import Any
5from django.contrib import messages
6from django.contrib.auth.mixins import LoginRequiredMixin
7from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
8from django.urls import reverse
9from django.utils.translation import gettext_lazy as _
10from django.views.generic import FormView, TemplateView, View
11from django.views.generic.edit import CreateView, UpdateView
12from ptf.url_utils import format_url_with_params
14from mesh.models.crud import send_review_request
15from mesh.models.file_helpers import post_delete_model_file
16from mesh.models.orm.review_models import (
17 RecommendationValue,
18 Review,
19 ReviewAdditionalFile,
20 ReviewState,
21)
22from mesh.models.orm.submission_models import SubmissionLog, SubmissionVersion
23from mesh.models.orm.suggestion_model import Suggestion
24from mesh.models.orm.user_models import User
25from mesh.models.roles.editor import Editor
26from mesh.models.roles.journal_manager import JournalManager
27from mesh.models.roles.reviewer import Reviewer
28from mesh.views.components.breadcrumb import get_submission_breadcrumb
29from mesh.views.components.button import Button
30from mesh.views.forms.base_forms import FormAction
31from mesh.views.forms.review_forms import (
32 ReviewAcceptForm,
33 ReviewAutoCreateForm,
34 ReviewConfirmForm,
35 ReviewCreateForm,
36 ReviewDeclineForm,
37 ReviewSubmitForm,
38)
39from mesh.views.utils import create_new_user, get_review_request_email, send_review_request_email
40from mesh.views.viewmodel import ReviewProxy
41from mesh.views.views_base import MeshObjectMixin, SubmittableModelFormMixin
42from mesh.views.views_reviewer import add_suggestion_from_person
45class ReviewCreateView(LoginRequiredMixin, MeshObjectMixin, FormView):
46 """
47 View to invite a new reviewer for a given round.
48 The review form is filled with the submission version from the URL.
49 """
51 model = Review
52 form_class = ReviewCreateForm
53 template_name = "mesh/review/review_create.html"
54 # template_name = "mesh/forms/form_full_page.html"
55 version: SubmissionVersion
57 # restricted_roles = [JournalManager, Editor]
58 def setup(self, request, *args, **kwargs):
59 super().setup(request, *args, **kwargs)
60 self.version = self.get_version()
61 if not self.request.current_role.can_invite_reviewer(self.version):
62 raise PermissionError
64 def get_form_kwargs(self) -> dict[str, Any]:
65 kwargs = super().get_form_kwargs()
66 suggestions = Suggestion.objects.filter(submission=self.version.submission).order_by("seq")
67 choices = [(suggestion.pk, "") for suggestion in suggestions]
68 kwargs["_choices"] = choices
69 kwargs["_version"] = self.version
70 # # submission = self.version.submission
71 # # self.suggestions = Suggestion.objects.filter(submission=submission)
72 # # self.existing_reviewers = [review.reviewer for review in self.version.reviews.all()]
73 # # kwargs["_suggestions"] = self.suggestions
74 # # kwargs["_existing_reviewers"] = self.existing_reviewers
75 #
76 # # current_reviewers = list(
77 # # Review.objects.filter(version=self.version).values_list("reviewer__pk", flat=True)
78 # # )
79 # # kwargs["_reviewers"] = User.objects.exclude(pk__in=current_reviewers)
81 return kwargs
83 def get_initial(self) -> dict[str, Any]:
84 return {
85 "request_email": get_review_request_email(
86 self.version.submission, self.request.build_absolute_uri("/")
87 ),
88 "request_email_subject": _("New referee request"),
89 "quick_request_email": get_review_request_email(
90 self.version.submission,
91 self.request.build_absolute_uri("/"),
92 "mesh/emails/review/referee_quick_request.html",
93 ),
94 "quick_request_email_subject": _("New referee request for a quick review"),
95 }
97 def get_context_data(self, *args, **kwargs):
98 context = super().get_context_data(*args, **kwargs)
99 context["page_title"] = "Request review"
100 context["version"] = self.version
102 submission = self.version.submission
103 submission_url = reverse(
104 "mesh:submission_details", kwargs={"submission_pk": submission.pk}
105 )
106 description = "Please fill the form below to request an additional review for "
107 description += f"round #{self.version.number} of submission "
108 description += f"<a href='{submission_url}'><i>{submission.name}</i></a>."
110 shortlist_url = reverse(
111 "mesh:submission_shortlist", kwargs={"submission_pk": submission.pk}
112 )
113 action = f"<a href='{shortlist_url}'><button>Edit Shortlist</button></a>"
114 context["submission"] = submission
116 self.suggestions = Suggestion.objects.filter(submission=submission).order_by("seq")
117 context["suggestions"] = self.suggestions
119 self.existing_reviewers = [review.reviewer for review in self.version.reviews_censored]
120 context["existing_reviewers"] = self.existing_reviewers
122 choices = []
123 has_selected_choice = False
124 for suggestion in self.suggestions:
125 suggested_user = (
126 suggestion.suggested_user
127 if suggestion.suggested_user is not None
128 else suggestion.suggested_reviewer
129 )
130 if suggested_user in self.existing_reviewers:
131 choices.append(
132 {
133 "value": suggestion.pk,
134 "label": str(suggested_user),
135 "disabled": True,
136 "avoid": suggestion.suggest_to_avoid,
137 }
138 )
139 else:
140 choice = {
141 "value": suggestion.pk,
142 "label": str(suggested_user),
143 "avoid": suggestion.suggest_to_avoid,
144 }
145 if not has_selected_choice:
146 has_selected_choice = True
147 choice["selected"] = True
148 choices.append(choice)
149 context["suggested_users"] = choices
151 context["form_description"] = [description, action]
153 # Generate breadcrumb data
154 breadcrumb = get_submission_breadcrumb(submission)
155 breadcrumb.add_item(
156 title=_("Request review"),
157 url=reverse(
158 "mesh:review_create",
159 kwargs={
160 "submission_pk": self.version.submission.pk,
161 "version_pk": self.version.pk,
162 },
163 ),
164 )
165 context["breadcrumb"] = breadcrumb
167 return context
169 def form_valid(self, form) -> HttpResponse:
170 # Check if the review is attached to an existing user or if we should create a new one.
171 reviewer = None
172 suggestion = None
173 old_suggested_user = None
174 email = first_name = last_name = ""
176 reviewer_select = form.cleaned_data["reviewer_select"]
177 if reviewer_select == "shortlist":
178 # someone from the shortlist was selected.
179 # Get the associated suggestion
180 suggestion_pk = form.cleaned_data["suggested_user"]
181 suggestion = Suggestion.objects.get(pk=int(suggestion_pk))
183 # A suggestion was made either on an existing user or with first name/last name/email
184 if suggestion.suggested_user is not None:
185 # An existing User was selected. No need to create a user
186 reviewer = suggestion.suggested_user
187 else:
188 old_suggested_user = suggestion.suggested_reviewer
189 first_name = suggestion.suggested_reviewer.first_name
190 last_name = suggestion.suggested_reviewer.last_name
191 email = suggestion.suggested_reviewer.email
192 else:
193 # First name, last name and email were typed in the form
194 first_name = form.cleaned_data["reviewer_first_name"]
195 last_name = form.cleaned_data["reviewer_last_name"]
196 email = form.cleaned_data["reviewer_email"]
198 if reviewer is None:
199 # Even if first/last/email were typed (some time ago in the case of a suggestion)
200 # There might now exists a User with the same email
201 reviewer = User.objects.filter(email=email).first()
202 if reviewer is None:
203 reviewer = create_new_user(email, first_name, last_name)
204 Suggestion.objects.filter(suggested_reviewer__email=email).update(
205 suggested_reviewer=None, suggested_user=reviewer
206 )
208 if old_suggested_user is not None:
209 # reviewer is now a User. We need to merge data from the suggested_reviewer
210 reviewer.keywords = old_suggested_user.keywords
211 reviewer.save()
213 old_suggested_user.delete()
215 # We now know the reviewer. Set the form.instance so that Django can create a Review
217 review = send_review_request(self.request, reviewer, self.version, form)
218 # review = send_review_request(self.request.user, reviewer, self.version, form)
220 messages.success(
221 self.request,
222 _(f"Referee request successfully submitted to {review.reviewer}"),
223 )
224 # Redirect
225 return super().form_valid(form)
227 def get_success_url(self) -> str:
228 return reverse(
229 "mesh:submission_details", kwargs={"submission_pk": self.version.submission.pk}
230 )
233class ReviewEditBaseView(LoginRequiredMixin, MeshObjectMixin, UpdateView):
234 """
235 Base view for a reviewer to edit a review.
236 """
238 model = Review
239 template_name = "mesh/forms/form_full_page.html"
240 review: Review
241 restricted_roles = [Reviewer]
243 def setup(self, request, *args, **kwargs):
244 super().setup(request, *args, **kwargs)
245 self.review = self.get_review()
246 if not self.request.current_role.can_edit_review(self.get_submission(), self.review):
247 raise PermissionError
249 def get_object(self, *args, **kwargs):
250 return self.review
252 def get_initial(self) -> dict[str, Any]:
253 return {
254 "date_response_due_display": self.review.date_response_due,
255 "date_review_due_display": self.review.date_review_due,
256 }
259class ReviewAnswerBaseView(ReviewEditBaseView):
260 def get_context_data(self, *args, **kwargs):
261 context = super().get_context_data(*args, **kwargs)
262 submission = self.review.version.submission
263 submission_url = reverse(
264 "mesh:submission_details", kwargs={"submission_pk": submission.pk}
265 )
266 # TODO: Create a custom template instead of passing HTML in variable ?
267 description_1 = f"{self.review.created_by} requested your review for the submission "
268 description_1 += f"<a href='{submission_url}'><i>{submission.name}</i></a>."
269 description_2 = "Please fill the below form."
270 context["form_description"] = [description_1, description_2]
272 return context
274 def get_success_url(self) -> str:
275 return reverse(
276 "mesh:submission_details", kwargs={"submission_pk": self.review.version.submission.pk}
277 )
280class ReviewAcceptView(ReviewAnswerBaseView):
281 """
282 Preliminary view for a reviewer to accept a requested review.
283 """
285 form_class = ReviewAcceptForm
286 template_name = "mesh/review/review_accept.html"
288 def get_context_data(self, *args, **kwargs):
289 context = super().get_context_data(*args, **kwargs)
290 context["page_title"] = "Accept review request"
292 submission = self.review.version.submission
293 # Generate breadcrumb data
294 breadcrumb = get_submission_breadcrumb(submission)
295 breadcrumb.add_item(
296 title=_("Accept review"),
297 url=reverse(
298 "mesh:review_accept",
299 kwargs={
300 "submission_pk": self.review.version.submission.pk,
301 "version_pk": self.review.version.pk,
302 "review_pk": self.review.pk,
303 },
304 ),
305 )
306 context["breadcrumb"] = breadcrumb
308 return context
310 def get_success_url(self) -> str:
311 if self.review.state == ReviewState.PENDING.value:
312 return reverse(
313 "mesh:review_update",
314 kwargs={
315 "submission_pk": self.review.version.submission.pk,
316 "version_pk": self.review.version.pk,
317 "review_pk": self.review.pk,
318 },
319 )
321 return super().get_success_url()
323 def form_valid(self, form):
324 accept_comment = form.cleaned_data["accept_comment"]
325 self.review.accept(True, accept_comment, user=self.request.user)
327 messages.success(self.request, _("Review request successfully accepted"))
329 return HttpResponseRedirect(self.get_success_url())
332class ReviewDeclineView(ReviewAnswerBaseView):
333 """
334 Preliminary view for a reviewer to decline a requested review.
335 """
337 form_class = ReviewDeclineForm
338 template_name = "mesh/review/review_decline.html"
340 def get_context_data(self, *args, **kwargs):
341 context = super().get_context_data(*args, **kwargs)
342 context["page_title"] = "Decline review request"
344 submission = self.review.version.submission
345 # Generate breadcrumb data
346 breadcrumb = get_submission_breadcrumb(submission)
347 breadcrumb.add_item(
348 title=_("Decline review"),
349 url=reverse(
350 "mesh:review_decline",
351 kwargs={
352 "submission_pk": submission.pk,
353 "version_pk": self.review.version.pk,
354 "review_pk": self.review.pk,
355 },
356 ),
357 )
358 context["breadcrumb"] = breadcrumb
360 return context
362 def form_valid(self, form):
363 person_info = {
364 "first_name": form.data.get("reviewer_first_name", ""),
365 "last_name": form.data.get("reviewer_last_name", ""),
366 "email": form.data.get("reviewer_email", ""),
367 }
368 submission = self.review.version.submission
369 add_suggestion_from_person(submission, person_info, suggest_to_avoid=False)
371 self.review.accept(accept_value=False, accept_comment="", user=self.request.user)
373 messages.success(self.request, _("Review request successfully declined"))
375 return HttpResponseRedirect(self.get_success_url())
378class ReviewSubmitView(SubmittableModelFormMixin, ReviewEditBaseView):
379 """
380 View for performing a review on a submission version.
381 """
383 form_class = ReviewSubmitForm
384 add_confirm_message = False
386 def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
387 """
388 Redirect to the accept form if the review has not been accepted yet.
389 """
390 if self.review.state in [
391 ReviewState.AWAITING_ACCEPTANCE.value,
392 ReviewState.DECLINED.value,
393 ]:
394 # Forward all query parameters
395 return HttpResponseRedirect(
396 format_url_with_params(
397 reverse(
398 "mesh:review_accept",
399 kwargs={
400 "submission_pk": self.review.version.submission.pk,
401 "version_pk": self.review.version.pk,
402 "review_pk": self.review.pk,
403 },
404 ),
405 {k: v for k, v in request.GET.lists()},
406 )
407 )
408 return super().get(request, *args, **kwargs)
410 def get_success_url(self) -> str:
411 if FormAction.SUBMIT.value in self.request.POST:
412 return self.submit_url()
413 return reverse(
414 "mesh:submission_details", kwargs={"submission_pk": self.review.version.submission.pk}
415 )
417 def submit_url(self) -> str:
418 return reverse(
419 "mesh:review_confirm",
420 kwargs={
421 "submission_pk": self.review.version.submission.pk,
422 "version_pk": self.review.version.pk,
423 "review_pk": self.review.pk,
424 },
425 )
427 def get_context_data(self, *args, **kwargs):
428 context = super().get_context_data(*args, **kwargs)
429 context["page_title"] = "Submit review"
431 submission = self.review.version.submission
432 submission_url = reverse(
433 "mesh:submission_details", kwargs={"submission_pk": submission.pk}
434 )
435 # TODO: Create a custom template instead of passing HTML in variable
436 description = f"Please submit your review for round #{self.review.version.number} "
437 description += "of submission "
438 description += f"<a href='{submission_url}'><i>{submission.name}</i></a> "
439 description += "by filling the below form."
440 context["form_description"] = [description]
442 # Generate breadcrumb data
443 breadcrumb = get_submission_breadcrumb(submission)
444 breadcrumb.add_item(
445 title=_("Submit review"),
446 url=reverse(
447 "mesh:review_update",
448 kwargs={
449 "submission_pk": self.review.version.submission.pk,
450 "version_pk": self.review.version.pk,
451 "review_pk": self.review.pk,
452 },
453 ),
454 )
455 context["breadcrumb"] = breadcrumb
457 return context
459 def form_pre_save(self, form) -> None:
460 form.instance._user = self.request.user
462 def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
463 """
464 View for updating the review or deleting one of the associated files.
465 """
466 deletion_requested, _ = post_delete_model_file(
467 self.review, request, self.request.current_role
468 )
470 if deletion_requested:
471 self.review = self.get_review()
472 return self.get(request, *args, **kwargs)
474 return super().post(request, *args, **kwargs)
477class ReviewConfirmView(LoginRequiredMixin, MeshObjectMixin, FormView):
478 """
479 View to confirm the submission of a review.
480 """
482 review: Review
483 template_name = "mesh/review/review_confirm.html"
484 form_class = ReviewConfirmForm
486 def setup(self, request, *args, **kwargs):
487 super().setup(request, *args, **kwargs)
488 submission = self.get_submission()
489 self.review = self.get_review()
490 if not self.request.current_role.can_submit_review(submission, self.review):
491 raise PermissionError
493 def get_success_url(self):
494 return reverse(
495 "mesh:submission_details", kwargs={"submission_pk": self.review.version.submission.pk}
496 )
498 def get_context_data(self, *args, **kwargs):
499 context = super().get_context_data(*args, **kwargs)
501 context["form"].buttons = [
502 Button(
503 id="form_save",
504 title=_("Submit"),
505 icon_class="fa-check",
506 attrs={"type": ["submit"], "class": ["save-button"]},
507 )
508 ]
510 context["review_proxy"] = ReviewProxy(self.review, self.request.current_role)
512 files = []
513 for file_wrapper in self.review.additional_files.all():
514 files.append({"file_wrapper": file_wrapper, "type": "Review file"})
515 context["review_files"] = files
517 # Breadcrumb
518 breadcrumb = get_submission_breadcrumb(self.review.version.submission)
519 breadcrumb.add_item(
520 title=_("Submit review"),
521 url=reverse("mesh:review_update", kwargs={"pk": self.review.pk}),
522 )
523 context["breadcrumb"] = breadcrumb
525 return context
527 def form_valid(self, form: ReviewConfirmForm) -> HttpResponse:
528 self.review.submit(self.request.user)
529 # self.review.submit()
531 messages.success(self.request, _("Your review has been successfully submitted."))
533 return HttpResponseRedirect(self.get_success_url())
536REVIEW_FILE_INPUT_PREFIX = "review_file_"
537REVIEW_FILE_INPUT_VALUE = "true"
540class ReviewDetails(LoginRequiredMixin, MeshObjectMixin, TemplateView):
541 """
542 Details view for a review.
543 """
545 template_name = "mesh/review/review_details.html"
546 review: Review
548 def setup(self, request, *args, **kwargs):
549 super().setup(request, *args, **kwargs)
550 self.review = self.get_review()
551 self.version = self.get_version()
552 self.submission = self.get_submission()
553 if not self.request.current_role.can_access_review(self.submission, self.review):
554 raise PermissionError
556 def get_context_data(self, *args, **kwargs) -> dict[str, Any]:
557 context = super().get_context_data(*args, **kwargs)
558 context["review_proxy"] = ReviewProxy(
559 self.review, self.request.current_role, version=self.version
560 )
561 context["input_prefix"] = REVIEW_FILE_INPUT_PREFIX
562 context["input_value"] = REVIEW_FILE_INPUT_VALUE
564 breadcrumb = get_submission_breadcrumb(self.review.version.submission)
565 breadcrumb.add_item(
566 title=_("Review details") + f" - {self.review.reviewer}",
567 url=reverse(
568 "mesh:review_details",
569 kwargs={
570 "submission_pk": self.review.version.submission.pk,
571 "version_pk": self.review.version.pk,
572 "review_pk": self.review.pk,
573 },
574 ),
575 )
576 context["breadcrumb"] = breadcrumb
577 return context
580class ReviewFileAccessUpdate(LoginRequiredMixin, MeshObjectMixin, View):
581 """
582 View to edit the author access to a review's files.
583 """
585 form_cache: str
587 def setup(self, request, *args, **kwargs):
588 super().setup(request, *args, **kwargs)
589 review = self.get_review()
591 if not self.request.current_role.can_edit_review_file_right(review):
592 raise PermissionError
594 def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
595 submission = self.get_submission()
596 version = self.get_version()
597 review = self.get_review()
598 response = HttpResponseRedirect(
599 reverse(
600 "mesh:review_details",
601 kwargs={
602 "submission_pk": submission.pk,
603 "version_pk": version.pk,
604 "review_pk": review.pk,
605 },
606 )
607 )
608 files_qs = review.additional_files.all()
609 if not files_qs:
610 return response
611 files = {f.pk: f for f in files_qs}
613 # Check the POST data and look for the file inputs. Update data accordingly
614 # TODO NathanTY : wtf
615 field_regex = re.compile(f"{REVIEW_FILE_INPUT_PREFIX}(?P<pk>[0-9]+)$")
616 files_handled: list[int] = []
617 files_changed: list[ReviewAdditionalFile] = []
618 for field, values in request.POST.lists():
619 field_match = field_regex.match(field)
620 if not field_match:
621 continue
622 if values[0] != REVIEW_FILE_INPUT_VALUE:
623 continue
624 file_pk = int(field_match.group("pk"))
625 file = files.get(file_pk)
626 if file is None:
627 continue
628 # The boolean already has the correct value
629 if file.author_access:
630 files_handled.append(file_pk)
631 continue
632 file.author_access = True
633 file.save()
634 files_handled.append(file_pk)
635 files_changed.append(file)
637 # There is no input in the POST data for unchecked checkboxes
638 for file_pk, file in files.items():
639 if file_pk not in files_handled:
640 if not file.author_access:
641 continue
642 file.author_access = False
643 file.save()
644 files_changed.append(file)
646 messages.success(request, _("Successfully updated the author access right."))
648 changed_files_str = ", ".join([f.name for f in files_changed])
649 if changed_files_str:
650 message = f"Changed author access right to Round #{version.number}"
651 message += f" files: {changed_files_str}"
652 SubmissionLog.add_message(
653 version.submission,
654 content=message,
655 content_en=message,
656 user=request.user,
657 significant=True,
658 )
660 return response
663class ReviewAutoCreateView(LoginRequiredMixin, MeshObjectMixin, CreateView):
664 """
665 View to invite a new reviewer for a given round.
666 The review form is filled with the submission version from the URL.
667 """
669 model = Review
670 form_class = ReviewAutoCreateForm
671 template_name = "mesh/review/review_auto_create.html"
672 version: SubmissionVersion
673 previous_reviewers = [] # Reviewers of the previous round that will be assigned to the current round
674 existing_reviewers = [] # Reviewers already assigned to this round
675 restricted_roles = [JournalManager, Editor]
677 def setup(self, request, *args, **kwargs):
678 super().setup(request, *args, **kwargs)
679 self.version = self.get_version()
680 if not self.request.current_role.can_invite_reviewer(self.version):
681 raise PermissionError
683 # Reviewers already assigned to this round
684 self.existing_reviewers = [review.reviewer for review in self.version.reviews_censored]
686 submission = self.version.submission
687 previous_round_number = self.version.number - 1
688 previous_round = submission.versions.get(number=previous_round_number)
689 reviews_that_can_be_auto_reassigned = previous_round.reviews.filter(
690 state=ReviewState.SUBMITTED.value
691 ).exclude(
692 recommendation__in=[
693 RecommendationValue.REJECTED.value,
694 RecommendationValue.RESUBMIT_SOMEWHERE_ELSE.value,
695 ]
696 )
698 # Reviewers of the previous round that submitted a "positive" recommendation (Accept or Revision requested)
699 # not already assigned to this round.
700 # These reviewers will be assigned to the current round if the editor validates the form.
701 self.previous_reviewers = [
702 review.reviewer
703 for review in reviews_that_can_be_auto_reassigned
704 if review.reviewer not in self.existing_reviewers
705 ]
707 def get_initial(self) -> dict[str, Any]:
708 return {
709 "request_email": get_review_request_email(
710 self.version.submission, self.request.build_absolute_uri("/")
711 ),
712 "request_email_subject": _("Referee request after revision"),
713 }
715 def get_context_data(self, *args, **kwargs):
716 context = super().get_context_data(*args, **kwargs)
717 context["page_title"] = "Request review"
719 submission = self.version.submission
720 submission_url = reverse(
721 "mesh:submission_details", kwargs={"submission_pk": submission.pk}
722 )
723 description = "Please fill the form below to request additional reviews for "
724 description += f"round #{self.version.number} of submission "
725 description += f"<a href='{submission_url}'><i>{submission.name}</i></a>."
727 context["submission"] = submission
729 # Reviewers already assigned to this round
730 context["existing_reviewers"] = self.existing_reviewers
732 # Reviewers of the previous round that submitted a "positive" recommendation (Accept or Revision requested)
733 # not already assigned to this round.
734 # These reviewers will be assigned to the current round if the editor validates the form.
735 context["previous_reviewers"] = self.previous_reviewers
737 context["form_description"] = [description]
739 # Generate breadcrumb data
740 breadcrumb = get_submission_breadcrumb(submission)
741 breadcrumb.add_item(
742 title=_("Request review"),
743 url=reverse(
744 "mesh:review_create",
745 kwargs={
746 "submission_pk": submission.pk,
747 "version_pk": self.version.pk,
748 },
749 ),
750 )
751 context["breadcrumb"] = breadcrumb
753 return context
755 def form_valid(self, form):
756 """
757 Inject required data to the form instance before saving model.
758 """
759 form.instance.version = self.version
760 form.instance._user = self.request.user
762 if len(self.previous_reviewers) > 0:
763 today = datetime.date.today()
765 reviewer = self.previous_reviewers[0]
766 form.instance.reviewer = reviewer
767 form.instance.date_response_due = today
768 form.instance.state = ReviewState.PENDING.value
769 response = super().form_valid(form)
771 review = self.object
772 send_review_request_email(
773 review,
774 form.cleaned_data["request_email"],
775 form.cleaned_data["request_email_subject"],
776 self.request.build_absolute_uri("/"),
777 )
779 for reviewer in self.previous_reviewers[1:]:
780 review = Review(
781 version=self.version,
782 reviewer=reviewer,
783 date_response_due=today,
784 date_review_due=form.instance.date_review_due,
785 state=ReviewState.PENDING.value,
786 request_email=form.instance.request_email,
787 )
788 review._user = self.request.user
789 review.save()
791 send_review_request_email(
792 review,
793 form.cleaned_data["request_email"],
794 form.cleaned_data["request_email_subject"],
795 self.request.build_absolute_uri("/"),
796 )
798 messages.success(
799 self.request,
800 _("Referee request successfully submitted"),
801 )
803 # SubmissionLog.add_message(
804 # self.version.submission,
805 # content=_("Referee request sent to") + f" {review.reviewer}",
806 # content_en=f"Referee request sent to {review.reviewer}",
807 # user=self.request.user,
808 # significant=True,
809 # )
811 else:
812 response = super().form_invalid(form)
814 return response
816 def get_success_url(self) -> str:
817 return reverse(
818 "mesh:submission_details", kwargs={"submission_pk": self.version.submission.pk}
819 )