Coverage for src/mesh/views/views_editorial.py: 28%
174 statements
« prev ^ index » next coverage.py v7.7.0, created at 2025-04-28 07:45 +0000
« prev ^ index » next coverage.py v7.7.0, created at 2025-04-28 07:45 +0000
1import json
2from typing import Any
4from django.contrib import messages
5from django.http import HttpRequest, HttpResponse, HttpResponseRedirect, JsonResponse
6from django.shortcuts import get_object_or_404, render
7from django.urls import reverse_lazy
8from django.utils.translation import gettext_lazy as _
9from django.views.generic import CreateView, FormView, TemplateView, UpdateView, View
11from mesh.model.file_helpers import post_delete_model_file
12from mesh.model.roles.editor import Editor, get_section_editors
13from mesh.model.roles.journal_manager import JournalManager
14from mesh.views.forms.editorial_forms import (
15 EditorialDecisionCreateForm,
16 EditorialDecisionUpdateForm,
17 StartReviewProcessForm,
18)
19from mesh.views.forms.user_forms import UserForm
20from mesh.views.mixins import RoleMixin
22from ..models.editorial_models import EditorialDecision, EditorSubmissionRight
23from ..models.submission_models import Submission, SubmissionLog
24from ..models.user_models import User
25from .components.breadcrumb import get_submission_breadcrumb
26from .components.button import Button
29class SendToReviewView(RoleMixin, FormView):
30 """
31 View to send a submission with its curent version to the Review process.
33 It sets the version's boolean `review_open = True` and switch the submission
34 state to `on_review`.
35 """
37 submission: Submission
38 restricted_roles = [Editor, JournalManager]
39 form_class = StartReviewProcessForm
41 def restrict_dispatch(self, request: HttpRequest, *args, **kwargs) -> bool:
42 self.submission = get_object_or_404(Submission, pk=kwargs["pk"])
43 return not self.role_handler.check_rights("can_start_review_process", self.submission)
45 def get_success_url(self) -> str:
46 return reverse_lazy("mesh:submission_details", kwargs={"pk": self.submission.pk})
48 def form_valid(self, form: Any) -> HttpResponse:
49 self.submission.start_review_process(self.request.user)
50 messages.success(self.request, _("The submission has been sent to review."))
51 return super().form_valid(form)
54class EditorialDecisionCreateView(RoleMixin, CreateView):
55 """
56 View for creating an editorial decision.
57 """
59 model = EditorialDecision
60 form_class = EditorialDecisionCreateForm
61 template_name = "mesh/forms/form_full_page.html"
62 restricted_roles = [JournalManager, Editor]
63 submission: Submission
65 def restrict_dispatch(self, request: HttpRequest, *args, **kwargs) -> bool:
66 self.submission = get_object_or_404(Submission, pk=kwargs["submission_pk"])
68 return not self.role_handler.check_rights("can_create_editorial_decision", self.submission)
70 def get_success_url(self) -> str:
71 return reverse_lazy("mesh:submission_details", kwargs={"pk": self.submission.pk})
73 def form_valid(self, form: EditorialDecisionCreateForm) -> HttpResponse:
74 form.instance._user = self.request.user
75 form.instance.version = self.submission.current_version
77 response = super().form_valid(form)
79 self.submission.apply_editorial_decision(self.object, self.request.user) # type:ignore
81 messages.success(self.request, _("Editorial decision successfully submitted"))
83 return response
85 def get_context_data(self, *args, **kwargs):
86 context = super().get_context_data(*args, **kwargs)
88 context["page_title"] = _("New editorial decision")
90 submission_url = reverse_lazy("mesh:submission_details", kwargs={"pk": self.submission.pk})
91 description = "Please fill the form below to submit your editorial decision "
92 description += (
93 f"for the submission <a href='{submission_url}'><i>{self.submission.name}</i></a>."
94 )
95 context["form_description"] = [description]
97 # Breadcrumnb
98 breadcrumb = get_submission_breadcrumb(self.submission)
99 breadcrumb.add_item(
100 title=_("New editorial decision"),
101 url=reverse_lazy(
102 "mesh:editorial_decision_create",
103 kwargs={"submission_pk": self.submission.pk},
104 ),
105 )
106 context["breadcrumb"] = breadcrumb
108 return context
111class EditorialDecisionUpdateView(RoleMixin, UpdateView):
112 """
113 View for updating an existing editorial decision.
114 """
116 model: type[EditorialDecision] = EditorialDecision
117 form_class = EditorialDecisionUpdateForm
118 template_name = "mesh/forms/form_full_page.html"
119 restricted_roled = [JournalManager, Editor]
120 decision: EditorialDecision
122 def restrict_dispatch(self, request: HttpRequest, *args, **kwargs) -> bool:
123 self.decision = get_object_or_404(self.model, pk=kwargs["pk"])
125 return not self.role_handler.check_rights("can_edit_editorial_decision", self.decision)
127 def get_success_url(self) -> str:
128 return reverse_lazy(
129 "mesh:submission_details", kwargs={"pk": self.decision.version.submission.pk}
130 )
132 def get_object(self, *args, **kwargs) -> EditorialDecision:
133 return self.decision
135 def form_valid(self, form: EditorialDecisionUpdateForm) -> HttpResponse:
136 form.instance._user = self.request.user
137 if not form.changed_data:
138 form.instance.do_not_update = True
140 response = super().form_valid(form)
142 decision_str = self.decision.get_value_display() # type: ignore
144 if form.has_changed():
145 SubmissionLog.add_message(
146 self.decision.version.submission,
147 _("Editorial decision updated") + f": {decision_str}",
148 f"Editorial decision updated: {decision_str}",
149 user=self.request.user,
150 )
151 messages.success(self.request, _("Editorial decision successfully updated"))
153 return response
155 def get_context_data(self, *args, **kwargs):
156 context = super().get_context_data(*args, **kwargs)
158 context["page_title"] = _("Edit editorial decision")
160 submission_url = reverse_lazy(
161 "mesh:submission_details", kwargs={"pk": self.decision.version.submission.pk}
162 )
163 description = "Please fill the form below to edit your editorial decision "
164 description += f"for the submission <a href='{submission_url}'><i>{self.decision.version.submission.name}</i></a>."
165 context["form_description"] = [description]
167 # Breadcrumnb
168 breadcrumb = get_submission_breadcrumb(self.decision.version.submission)
169 breadcrumb.add_item(
170 title=_("New editorial decision"),
171 url=reverse_lazy(
172 "mesh:editorial_decision_create",
173 kwargs={"submission_pk": self.decision.version.submission.pk},
174 ),
175 )
176 context["breadcrumb"] = breadcrumb
178 return context
180 def post(self, request: HttpRequest, *args: str, **kwargs: Any) -> HttpResponse:
181 """
182 View for updating the editorial decision or deleting one of
183 the associated files.
184 """
185 deletion_requested, _ = post_delete_model_file(self.decision, request, self.role_handler)
187 if deletion_requested:
188 # Update the submission object.
189 self.decision = get_object_or_404(self.model, pk=self.decision.pk)
190 return self.get(request, *args, **kwargs)
192 return super().post(request, *args, **kwargs)
195class AssignEditorView(RoleMixin, TemplateView):
196 """
197 View to add/remove editors assigned to a given submission.
198 """
200 submission: Submission
201 template_name = "mesh/forms/assign_editor.html"
203 def restrict_dispatch(self, request: HttpRequest, *args, **kwargs) -> bool:
204 self.submission = get_object_or_404(Submission, pk=kwargs["pk"])
205 return not self.role_handler.check_rights("can_assign_editor", self.submission)
207 def get_context_data(self, *args, **kwargs):
208 """
209 2 models are used to compute editor rights over a submission.
210 Section rights & Submission rights.
211 This view will display all editors having right over the given submission.
212 """
213 context = super().get_context_data(*args, **kwargs)
214 context["submission"] = self.submission
216 direct_editors = User.objects.filter(editor_submissions__submission=self.submission)
217 context["assigned_editors"] = [
218 {
219 "user": editor,
220 "buttons": [
221 Button(
222 id=f"remove-editor-{editor.pk}",
223 title=_("Remove"),
224 icon_class="fa-trash",
225 form=UserForm(
226 _users=direct_editors,
227 _hide_user=True,
228 initial={"user": editor},
229 ),
230 attrs={
231 "href": [
232 reverse_lazy(
233 "mesh:submission_editors",
234 kwargs={"pk": self.submission.pk},
235 )
236 ],
237 "class": ["button-error"],
238 "type": ["submit"],
239 "name": ["_action_remove"],
240 },
241 )
242 ],
243 }
244 for editor in direct_editors
245 ]
247 excluded_user_pks = [e.pk for e in direct_editors]
248 context["form"] = UserForm(
249 _users=User.objects.exclude(pk__in=excluded_user_pks), _user_label=_("User")
250 )
252 section_editors = []
253 for editor in get_section_editors(self.submission):
254 editor_data: dict[str, Any] = {"user": editor, "buttons": []}
255 if editor not in direct_editors:
256 editor_data["buttons"].append(
257 Button(
258 id=f"assign-editor-{editor.pk}",
259 title=_("Assign"),
260 icon_class="fa-plus",
261 form=UserForm(
262 _users=direct_editors,
263 _hide_user=True,
264 initial={"user": editor},
265 ),
266 attrs={
267 "href": [
268 reverse_lazy(
269 "mesh:submission_editors",
270 kwargs={"pk": self.submission.pk},
271 )
272 ],
273 "type": ["submit"],
274 },
275 )
276 )
277 else:
278 editor_data["buttons"].append(
279 Button(
280 id=f"assigned-editor-{editor.pk}",
281 title=_("Assigned"),
282 icon_class="fa-check",
283 attrs={"class": ["inactive"]},
284 )
285 )
286 section_editors.append(editor_data)
287 context["section_editors"] = section_editors
289 context["form_save_title"] = _("Add editor")
290 context["form_save_icon"] = "fa-plus"
291 context["page_title"] = _("Assigned editors")
293 # Breadcrumnb
294 breadcrumb = get_submission_breadcrumb(self.submission)
295 breadcrumb.add_item(
296 title=_("Assigned editors"),
297 url=reverse_lazy("mesh:submission_editors", kwargs={"pk": self.submission.pk}),
298 )
299 context["breadcrumb"] = breadcrumb
301 return context
303 def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
304 remove_form = "_action_remove" in request.POST
305 if remove_form:
306 users = User.objects.filter(editor_submissions__submission=self.submission)
307 else:
308 users = User.objects.exclude(editor_submissions__submission=self.submission)
310 response = HttpResponseRedirect(
311 reverse_lazy("mesh:submission_details", kwargs={"pk": self.submission.pk})
312 )
313 form = UserForm(request.POST, _users=users)
314 if not form.is_valid():
315 messages.error(request, "ERROR")
316 return response
318 user = form.cleaned_data["user"]
320 if remove_form:
321 EditorSubmissionRight.objects.get(submission=self.submission, user=user).delete()
322 messages.success(
323 request,
324 _(f"{user} was successfully removed from the assigned editors of the submission."),
325 )
326 SubmissionLog.add_message(
327 self.submission,
328 content=_(f"{user} removed from the assigned editors."),
329 content_en=f"{user} removed from the assigned editors.",
330 user=self.request.user,
331 significant=True,
332 )
333 else:
334 EditorSubmissionRight.objects.create(submission=self.submission, user=user)
335 messages.success(
336 request,
337 _(f"{user} was successfully assigned as an editor for the submission."),
338 )
339 SubmissionLog.add_message(
340 self.submission,
341 content=_(f"{user} assigned as an editor."),
342 content_en=f"{user} assigned as an editor.",
343 user=self.request.user,
344 significant=True,
345 )
347 return response
350class AssignEditorAPIView(View):
351 def get(self, request, *args, **kwargs):
352 submission = get_object_or_404(Submission, pk=kwargs["pk"])
353 users = User.objects.all()
354 editors = User.objects.filter(editor_submissions__submission=submission)
355 for user in users:
356 if user in editors:
357 user.selected = True
359 return render(
360 request,
361 "mesh/forms/assign_editor_modal.html",
362 {"users": users, "submission_proxy_pk": kwargs["pk"]},
363 )
365 def post(self, request, *args, **kwargs):
366 submission = get_object_or_404(Submission, pk=kwargs["pk"])
367 existing_pks = User.objects.filter(editor_submissions__submission=submission).values_list(
368 "pk", flat=True
369 )
371 editors_data = request.POST.get("select-editors", "")
372 editors = json.loads(editors_data)
373 new_pks = [int(pk) for pk in editors]
375 for pk in existing_pks:
376 if pk not in new_pks:
377 user = User.objects.get(pk=pk)
378 EditorSubmissionRight.objects.get(submission=submission, user=user).delete()
380 SubmissionLog.add_message(
381 submission,
382 content=_(f"{user} removed from the assigned editors."),
383 content_en=f"{user} removed from the assigned editors.",
384 user=self.request.user,
385 significant=True,
386 )
388 for pk in new_pks:
389 if pk not in existing_pks:
390 user = User.objects.get(pk=pk)
392 EditorSubmissionRight.objects.create(submission=submission, user=user)
394 SubmissionLog.add_message(
395 submission,
396 content=_(f"{user} assigned as an editor."),
397 content_en=f"{user} assigned as an editor.",
398 user=self.request.user,
399 significant=True,
400 )
402 return JsonResponse({"message": "OK"})