Coverage for src / mesh / views / views_review.py: 67%
373 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-08-27 14:14 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-08-27 14:14 +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 = f"{_('Response due date')} : <strong>{self.review.date_response_due.strftime('%d/%m/%Y')}</strong> <br>"
270 description_2 += f"{_('Review due date')} : <strong>{self.review.date_review_due.strftime('%d/%m/%Y')}</strong>"
271 description_3 = "Please fill the below form."
272 context["form_description"] = [description_1, description_2, description_3]
274 return context
276 def get_success_url(self) -> str:
277 return reverse(
278 "mesh:submission_details", kwargs={"submission_pk": self.review.version.submission.pk}
279 )
282class ReviewAcceptView(ReviewAnswerBaseView):
283 """
284 Preliminary view for a reviewer to accept a requested review.
285 """
287 form_class = ReviewAcceptForm
288 template_name = "mesh/review/review_accept.html"
290 def get_context_data(self, *args, **kwargs):
291 context = super().get_context_data(*args, **kwargs)
292 context["page_title"] = "Accept review request"
294 submission = self.review.version.submission
295 # Generate breadcrumb data
296 breadcrumb = get_submission_breadcrumb(submission)
297 breadcrumb.add_item(
298 title=_("Accept review"),
299 url=reverse(
300 "mesh:review_accept",
301 kwargs={
302 "submission_pk": self.review.version.submission.pk,
303 "version_pk": self.review.version.pk,
304 "review_pk": self.review.pk,
305 },
306 ),
307 )
308 context["breadcrumb"] = breadcrumb
310 return context
312 def get_success_url(self) -> str:
313 if self.review.state == ReviewState.PENDING.value:
314 return reverse(
315 "mesh:review_update",
316 kwargs={
317 "submission_pk": self.review.version.submission.pk,
318 "version_pk": self.review.version.pk,
319 "review_pk": self.review.pk,
320 },
321 )
323 return super().get_success_url()
325 def form_valid(self, form):
326 accept_comment = form.cleaned_data["accept_comment"]
327 self.review.accept(True, accept_comment, user=self.request.user)
329 messages.success(self.request, _("Review request successfully accepted"))
331 return HttpResponseRedirect(self.get_success_url())
334class ReviewDeclineView(ReviewAnswerBaseView):
335 """
336 Preliminary view for a reviewer to decline a requested review.
337 """
339 form_class = ReviewDeclineForm
340 template_name = "mesh/review/review_decline.html"
342 def get_context_data(self, *args, **kwargs):
343 context = super().get_context_data(*args, **kwargs)
344 context["page_title"] = "Decline review request"
346 submission = self.review.version.submission
347 # Generate breadcrumb data
348 breadcrumb = get_submission_breadcrumb(submission)
349 breadcrumb.add_item(
350 title=_("Decline review"),
351 url=reverse(
352 "mesh:review_decline",
353 kwargs={
354 "submission_pk": submission.pk,
355 "version_pk": self.review.version.pk,
356 "review_pk": self.review.pk,
357 },
358 ),
359 )
360 context["breadcrumb"] = breadcrumb
362 return context
364 def form_valid(self, form):
365 person_info = {
366 "first_name": form.data.get("reviewer_first_name", ""),
367 "last_name": form.data.get("reviewer_last_name", ""),
368 "email": form.data.get("reviewer_email", ""),
369 }
370 submission = self.review.version.submission
371 add_suggestion_from_person(submission, person_info, suggest_to_avoid=False)
373 self.review.accept(accept_value=False, accept_comment="", user=self.request.user)
375 messages.success(self.request, _("Review request successfully declined"))
377 return HttpResponseRedirect(self.get_success_url())
380class ReviewSubmitView(SubmittableModelFormMixin, ReviewEditBaseView):
381 """
382 View for performing a review on a submission version.
383 """
385 form_class = ReviewSubmitForm
386 add_confirm_message = False
388 def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
389 """
390 Redirect to the accept form if the review has not been accepted yet.
391 """
392 if self.review.state in [
393 ReviewState.AWAITING_ACCEPTANCE.value,
394 ReviewState.DECLINED.value,
395 ]:
396 # Forward all query parameters
397 return HttpResponseRedirect(
398 format_url_with_params(
399 reverse(
400 "mesh:review_accept",
401 kwargs={
402 "submission_pk": self.review.version.submission.pk,
403 "version_pk": self.review.version.pk,
404 "review_pk": self.review.pk,
405 },
406 ),
407 {k: v for k, v in request.GET.lists()},
408 )
409 )
410 return super().get(request, *args, **kwargs)
412 def get_success_url(self) -> str:
413 if FormAction.SUBMIT.value in self.request.POST:
414 return self.submit_url()
415 return reverse(
416 "mesh:submission_details", kwargs={"submission_pk": self.review.version.submission.pk}
417 )
419 def submit_url(self) -> str:
420 return reverse(
421 "mesh:review_confirm",
422 kwargs={
423 "submission_pk": self.review.version.submission.pk,
424 "version_pk": self.review.version.pk,
425 "review_pk": self.review.pk,
426 },
427 )
429 def get_context_data(self, *args, **kwargs):
430 context = super().get_context_data(*args, **kwargs)
431 context["page_title"] = "Submit review"
433 submission = self.review.version.submission
434 submission_url = reverse(
435 "mesh:submission_details", kwargs={"submission_pk": submission.pk}
436 )
437 # TODO: Create a custom template instead of passing HTML in variable
438 description = f"Please submit your review for round #{self.review.version.number} "
439 description += "of submission "
440 description += f"<a href='{submission_url}'><i>{submission.name}</i></a> "
441 description += "by filling the below form."
442 description += f"<br> By default, only the submission's editor, here {self.review.created_by.first_name} {self.review.created_by.last_name} will see your recommendation, review file(s) and comment."
443 context["form_description"] = [description]
445 # Generate breadcrumb data
446 breadcrumb = get_submission_breadcrumb(submission)
447 breadcrumb.add_item(
448 title=_("Submit review"),
449 url=reverse(
450 "mesh:review_update",
451 kwargs={
452 "submission_pk": self.review.version.submission.pk,
453 "version_pk": self.review.version.pk,
454 "review_pk": self.review.pk,
455 },
456 ),
457 )
458 context["breadcrumb"] = breadcrumb
460 return context
462 def form_pre_save(self, form) -> None:
463 form.instance._user = self.request.user
465 def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
466 """
467 View for updating the review or deleting one of the associated files.
468 """
469 deletion_requested, _ = post_delete_model_file(
470 self.review, request, self.request.current_role
471 )
473 if deletion_requested:
474 self.review = self.get_review()
475 return self.get(request, *args, **kwargs)
477 return super().post(request, *args, **kwargs)
480class ReviewConfirmView(LoginRequiredMixin, MeshObjectMixin, FormView):
481 """
482 View to confirm the submission of a review.
483 """
485 review: Review
486 template_name = "mesh/review/review_confirm.html"
487 form_class = ReviewConfirmForm
489 def setup(self, request, *args, **kwargs):
490 super().setup(request, *args, **kwargs)
491 submission = self.get_submission()
492 self.version = self.get_version()
493 self.review = self.get_review()
494 if not self.request.current_role.can_submit_review(submission, self.review):
495 raise PermissionError
497 def get_success_url(self):
498 return reverse(
499 "mesh:submission_details", kwargs={"submission_pk": self.review.version.submission.pk}
500 )
502 def get_context_data(self, *args, **kwargs):
503 context = super().get_context_data(*args, **kwargs)
505 context["form"].buttons = [
506 Button(
507 id="form_save",
508 title=_("Submit"),
509 icon_class="fa-check",
510 attrs={"type": ["submit"], "class": ["btn-save-form"]},
511 )
512 ]
514 context["review_proxy"] = ReviewProxy(
515 self.review, self.request.current_role, version=self.version
516 )
518 files = []
519 for file_wrapper in self.review.additional_files.all():
520 files.append({"file_wrapper": file_wrapper, "type": "Review file"})
521 context["review_files"] = files
523 # Breadcrumb
524 breadcrumb = get_submission_breadcrumb(self.review.version.submission)
525 breadcrumb.add_item(
526 title=_("Submit review"),
527 url=reverse(
528 "mesh:review_update",
529 kwargs={
530 "submission_pk": self.version.submission.pk,
531 "version_pk": self.version.pk,
532 "review_pk": self.review.pk,
533 },
534 ),
535 )
536 context["breadcrumb"] = breadcrumb
538 return context
540 def form_valid(self, form: ReviewConfirmForm) -> HttpResponse:
541 self.review.submit(self.request.user)
542 # self.review.submit()
544 messages.success(self.request, _("Your review has been successfully submitted."))
546 return HttpResponseRedirect(self.get_success_url())
549REVIEW_FILE_INPUT_PREFIX = "review_file_"
550REVIEW_FILE_INPUT_VALUE = "true"
553class ReviewDetails(LoginRequiredMixin, MeshObjectMixin, TemplateView):
554 """
555 Details view for a review.
556 """
558 template_name = "mesh/review/review_details.html"
559 review: Review
561 def setup(self, request, *args, **kwargs):
562 super().setup(request, *args, **kwargs)
563 self.review = self.get_review()
564 self.version = self.get_version()
565 self.submission = self.get_submission()
566 if not self.request.current_role.can_access_review(self.submission, self.review):
567 raise PermissionError
569 def get_context_data(self, *args, **kwargs) -> dict[str, Any]:
570 context = super().get_context_data(*args, **kwargs)
571 context["review_proxy"] = ReviewProxy(
572 self.review, self.request.current_role, version=self.version
573 )
574 context["input_prefix"] = REVIEW_FILE_INPUT_PREFIX
575 context["input_value"] = REVIEW_FILE_INPUT_VALUE
577 breadcrumb = get_submission_breadcrumb(self.review.version.submission)
578 breadcrumb.add_item(
579 title=_("Review details") + f" - {self.review.reviewer}",
580 url=reverse(
581 "mesh:review_details",
582 kwargs={
583 "submission_pk": self.review.version.submission.pk,
584 "version_pk": self.review.version.pk,
585 "review_pk": self.review.pk,
586 },
587 ),
588 )
589 context["breadcrumb"] = breadcrumb
590 return context
593class ReviewFileAccessUpdate(LoginRequiredMixin, MeshObjectMixin, View):
594 """
595 View to edit the author access to a review's files.
596 """
598 form_cache: str
600 def setup(self, request, *args, **kwargs):
601 super().setup(request, *args, **kwargs)
602 review = self.get_review()
604 if not self.request.current_role.can_edit_review_file_right(review):
605 raise PermissionError
607 def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
608 submission = self.get_submission()
609 version = self.get_version()
610 review = self.get_review()
611 response = HttpResponseRedirect(
612 reverse(
613 "mesh:review_details",
614 kwargs={
615 "submission_pk": submission.pk,
616 "version_pk": version.pk,
617 "review_pk": review.pk,
618 },
619 )
620 )
621 files_qs = review.additional_files.all()
622 if not files_qs:
623 return response
624 files = {f.pk: f for f in files_qs}
626 # Check the POST data and look for the file inputs. Update data accordingly
627 # TODO NathanTY : wtf
628 field_regex = re.compile(f"{REVIEW_FILE_INPUT_PREFIX}(?P<pk>[0-9]+)$")
629 files_handled: list[int] = []
630 files_changed: list[ReviewAdditionalFile] = []
631 for field, values in request.POST.lists():
632 field_match = field_regex.match(field)
633 if not field_match:
634 continue
635 if values[0] != REVIEW_FILE_INPUT_VALUE:
636 continue
637 file_pk = int(field_match.group("pk"))
638 file = files.get(file_pk)
639 if file is None:
640 continue
641 # The boolean already has the correct value
642 if file.author_access:
643 files_handled.append(file_pk)
644 continue
645 file.author_access = True
646 file.save()
647 files_handled.append(file_pk)
648 files_changed.append(file)
650 # There is no input in the POST data for unchecked checkboxes
651 for file_pk, file in files.items():
652 if file_pk not in files_handled:
653 if not file.author_access:
654 continue
655 file.author_access = False
656 file.save()
657 files_changed.append(file)
659 messages.success(request, _("Successfully updated the author access right."))
661 changed_files_str = ", ".join([f.name for f in files_changed])
662 if changed_files_str:
663 message = f"Changed author access right to Round #{version.number}"
664 message += f" files: {changed_files_str}"
665 SubmissionLog.add_message(
666 version.submission,
667 content=message,
668 content_en=message,
669 user=request.user,
670 significant=True,
671 )
673 return response
676class ReviewAutoCreateView(LoginRequiredMixin, MeshObjectMixin, CreateView):
677 """
678 View to invite a new reviewer for a given round.
679 The review form is filled with the submission version from the URL.
680 """
682 model = Review
683 form_class = ReviewAutoCreateForm
684 template_name = "mesh/review/review_auto_create.html"
685 version: SubmissionVersion
686 previous_reviewers = [] # Reviewers of the previous round that will be assigned to the current round
687 existing_reviewers = [] # Reviewers already assigned to this round
688 restricted_roles = [JournalManager, Editor]
690 def setup(self, request, *args, **kwargs):
691 super().setup(request, *args, **kwargs)
692 self.version = self.get_version()
693 if not self.request.current_role.can_invite_reviewer(self.version):
694 raise PermissionError
696 # Reviewers already assigned to this round
697 self.existing_reviewers = [review.reviewer for review in self.version.reviews_censored]
699 submission = self.version.submission
700 previous_round_number = self.version.number - 1
701 previous_round = submission.versions.get(number=previous_round_number)
702 reviews_that_can_be_auto_reassigned = previous_round.reviews.filter(
703 state=ReviewState.SUBMITTED.value
704 ).exclude(
705 recommendation__in=[
706 RecommendationValue.REJECTED.value,
707 RecommendationValue.RESUBMIT_SOMEWHERE_ELSE.value,
708 ]
709 )
711 # Reviewers of the previous round that submitted a "positive" recommendation (Accept or Revision requested)
712 # not already assigned to this round.
713 # These reviewers will be assigned to the current round if the editor validates the form.
714 self.previous_reviewers = [
715 review.reviewer
716 for review in reviews_that_can_be_auto_reassigned
717 if review.reviewer not in self.existing_reviewers
718 ]
720 def get_initial(self) -> dict[str, Any]:
721 return {
722 "request_email": get_review_request_email(
723 self.version.submission, self.request.build_absolute_uri("/")
724 ),
725 "request_email_subject": _("Referee request after revision"),
726 }
728 def get_context_data(self, *args, **kwargs):
729 context = super().get_context_data(*args, **kwargs)
730 context["page_title"] = "Request review"
732 submission = self.version.submission
733 submission_url = reverse(
734 "mesh:submission_details", kwargs={"submission_pk": submission.pk}
735 )
736 description = "Please fill the form below to request additional reviews for "
737 description += f"round #{self.version.number} of submission "
738 description += f"<a href='{submission_url}'><i>{submission.name}</i></a>."
740 context["submission"] = submission
742 # Reviewers already assigned to this round
743 context["existing_reviewers"] = self.existing_reviewers
745 # Reviewers of the previous round that submitted a "positive" recommendation (Accept or Revision requested)
746 # not already assigned to this round.
747 # These reviewers will be assigned to the current round if the editor validates the form.
748 context["previous_reviewers"] = self.previous_reviewers
750 context["form_description"] = [description]
752 # Generate breadcrumb data
753 breadcrumb = get_submission_breadcrumb(submission)
754 breadcrumb.add_item(
755 title=_("Request review"),
756 url=reverse(
757 "mesh:review_create",
758 kwargs={
759 "submission_pk": submission.pk,
760 "version_pk": self.version.pk,
761 },
762 ),
763 )
764 context["breadcrumb"] = breadcrumb
766 return context
768 def form_valid(self, form):
769 """
770 Inject required data to the form instance before saving model.
771 """
772 form.instance.version = self.version
773 form.instance._user = self.request.user
775 if len(self.previous_reviewers) > 0:
776 today = datetime.date.today()
778 reviewer = self.previous_reviewers[0]
779 form.instance.reviewer = reviewer
780 form.instance.date_response_due = today
781 form.instance.state = ReviewState.PENDING.value
782 response = super().form_valid(form)
784 review = self.object
785 send_review_request_email(
786 review,
787 form.cleaned_data["request_email"],
788 form.cleaned_data["request_email_subject"],
789 )
791 for reviewer in self.previous_reviewers[1:]:
792 review = Review(
793 version=self.version,
794 reviewer=reviewer,
795 date_response_due=today,
796 date_review_due=form.instance.date_review_due,
797 state=ReviewState.PENDING.value,
798 request_email=form.instance.request_email,
799 )
800 review._user = self.request.user
801 review.save()
803 send_review_request_email(
804 review,
805 form.cleaned_data["request_email"],
806 form.cleaned_data["request_email_subject"],
807 )
809 messages.success(
810 self.request,
811 _("Referee request successfully submitted"),
812 )
814 # SubmissionLog.add_message(
815 # self.version.submission,
816 # content=_("Referee request sent to") + f" {review.reviewer}",
817 # content_en=f"Referee request sent to {review.reviewer}",
818 # user=self.request.user,
819 # significant=True,
820 # )
822 else:
823 response = super().form_invalid(form)
825 return response
827 def get_success_url(self) -> str:
828 return reverse(
829 "mesh:submission_details", kwargs={"submission_pk": self.version.submission.pk}
830 )