Coverage for src / mesh / views / views_editorial.py: 61%
181 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 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.shortcuts import render
8from django.urls import reverse
9from django.utils.translation import gettext_lazy as _
10from django.views.generic import CreateView, FormView, TemplateView, UpdateView, View
12from mesh.models.file_helpers import post_delete_model_file
13from mesh.models.orm.editorial_models import EditorialDecision, EditorSubmissionRight
14from mesh.models.orm.submission_models import Submission, SubmissionLog
15from mesh.models.orm.user_models import User
16from mesh.models.roles.editor import get_section_editors
17from mesh.views.components.breadcrumb import get_submission_breadcrumb
18from mesh.views.components.button import Button
19from mesh.views.forms.editorial_forms import (
20 EditorialDecisionCreateForm,
21 EditorialDecisionUpdateForm,
22 StartReviewProcessForm,
23)
24from mesh.views.forms.user_forms import UserForm
25from mesh.views.utils import assign_editor, remove_editor
26from mesh.views.views_base import MeshObjectMixin
29class SendToReviewView(LoginRequiredMixin, MeshObjectMixin, 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 setup(self, request, *args, **kwargs):
42 super().setup(request, *args, **kwargs)
43 self.submission = self.get_submission()
44 if not self.request.current_role.can_start_review_process(self.submission):
45 raise PermissionError
47 def get_success_url(self) -> str:
48 return reverse("mesh:submission_details", kwargs={"submission_pk": self.submission.pk})
50 def form_valid(self, form: Any) -> HttpResponse:
51 self.submission.start_review_process(self.request.user)
52 messages.success(self.request, _("The submission has been sent to review."))
53 return super().form_valid(form)
56class EditorialDecisionCreateView(LoginRequiredMixin, MeshObjectMixin, CreateView):
57 """
58 View for creating an editorial decision.
59 """
61 model = EditorialDecision
62 form_class = EditorialDecisionCreateForm
63 template_name = "mesh/forms/form_full_page.html"
64 # restricted_roles = [JournalManager, Editor]
65 submission: Submission
67 def setup(self, request, *args, **kwargs):
68 super().setup(request, *args, **kwargs)
69 self.submission = self.get_submission()
70 if not self.request.current_role.can_create_editorial_decision(self.submission):
71 raise PermissionError
73 def get_success_url(self) -> str:
74 return reverse("mesh:submission_details", kwargs={"submission_pk": self.submission.pk})
76 def form_valid(self, form) -> HttpResponse:
77 form.instance._user = self.request.user
78 form.instance.version = self.submission.get_current_version()
80 response = super().form_valid(form)
82 self.submission.apply_editorial_decision(self.object, self.request.user)
84 messages.success(self.request, _("Editorial decision successfully submitted"))
86 return response
88 def get_context_data(self, *args, **kwargs):
89 context = super().get_context_data(*args, **kwargs)
91 context["page_title"] = _("New editorial decision")
93 submission_url = reverse(
94 "mesh:submission_details", kwargs={"submission_pk": self.submission.pk}
95 )
96 description = "Please fill the form below to submit your editorial decision "
97 description += (
98 f"for the submission <a href='{submission_url}'><i>{self.submission.name}</i></a>."
99 )
100 context["form_description"] = [description]
102 # Breadcrumnb
103 breadcrumb = get_submission_breadcrumb(self.submission)
104 breadcrumb.add_item(
105 title=_("New editorial decision"),
106 url=reverse(
107 "mesh:editorial_decision_create",
108 kwargs={"submission_pk": self.submission.pk},
109 ),
110 )
111 context["breadcrumb"] = breadcrumb
113 return context
116class EditorialDecisionUpdateView(LoginRequiredMixin, MeshObjectMixin, UpdateView):
117 """
118 View for updating an existing editorial decision.
119 """
121 model = EditorialDecision
122 form_class = EditorialDecisionUpdateForm
123 template_name = "mesh/forms/form_full_page.html"
124 # restricted_roled = [JournalManager, Editor]
125 decision: EditorialDecision
127 def setup(self, request, *args, **kwargs):
128 super().setup(request, *args, **kwargs)
129 self.decision = self.get_decision()
130 if not self.request.current_role.can_edit_editorial_decision(self.decision):
131 raise PermissionError
133 def get_object(self, *args, **kwargs) -> EditorialDecision:
134 return self.decision
136 def get_success_url(self) -> str:
137 return reverse(
138 "mesh:submission_details",
139 kwargs={"submission_pk": self.decision.version.submission.pk},
140 )
142 def form_valid(self, form: EditorialDecisionUpdateForm) -> HttpResponse:
143 form.instance._user = self.request.user
144 if not form.changed_data:
145 form.instance.do_not_update = True
147 response = super().form_valid(form)
149 decision_str = self.decision.get_value_display() # type: ignore
151 if form.has_changed():
152 SubmissionLog.add_message(
153 self.decision.version.submission,
154 _("Editorial decision updated") + f": {decision_str}",
155 f"Editorial decision updated: {decision_str}",
156 user=self.request.user,
157 )
158 messages.success(self.request, _("Editorial decision successfully updated"))
160 return response
162 def get_context_data(self, *args, **kwargs):
163 context = super().get_context_data(*args, **kwargs)
165 context["page_title"] = _("Edit editorial decision")
167 submission_url = reverse(
168 "mesh:submission_details",
169 kwargs={"submission_pk": self.decision.version.submission.pk},
170 )
171 description = "Please fill the form below to edit your editorial decision "
172 description += f"for the submission <a href='{submission_url}'><i>{self.decision.version.submission.name}</i></a>."
173 context["form_description"] = [description]
175 # Breadcrumnb
176 breadcrumb = get_submission_breadcrumb(self.decision.version.submission)
177 breadcrumb.add_item(
178 title=_("New editorial decision"),
179 url=reverse(
180 "mesh:editorial_decision_create",
181 kwargs={"submission_pk": self.decision.version.submission.pk},
182 ),
183 )
184 context["breadcrumb"] = breadcrumb
186 return context
188 def post(self, request: HttpRequest, *args: str, **kwargs: Any) -> HttpResponse:
189 """
190 View for updating the editorial decision or deleting one of
191 the associated files.
192 """
193 deletion_requested, _ = post_delete_model_file(
194 self.decision, request, self.request.current_role
195 )
197 if deletion_requested:
198 # Update the submission object.
199 self.decision = self.get_decision()
200 return self.get(request, *args, **kwargs)
202 return super().post(request, *args, **kwargs)
205class AssignEditorView(LoginRequiredMixin, MeshObjectMixin, TemplateView):
206 """
207 View to add/remove editors assigned to a given submission.
208 """
210 submission: Submission
211 template_name = "mesh/forms/assign_editor.html"
213 def setup(self, request, *args, **kwargs):
214 super().setup(request, *args, **kwargs)
215 self.submission = self.get_submission()
216 if not self.request.current_role.can_assign_editor(self.submission):
217 raise PermissionError
219 def get_context_data(self, *args, **kwargs):
220 """
221 2 models are used to compute editor rights over a submission.
222 Section rights & Submission rights.
223 This view will display all editors having right over the given submission.
224 """
225 context = super().get_context_data(*args, **kwargs)
226 context["submission"] = self.submission
228 direct_editors = User.objects.filter(editor_submissions__submission=self.submission)
229 context["assigned_editors"] = [
230 {
231 "user": editor,
232 "buttons": [
233 Button(
234 id=f"remove-editor-{editor.pk}",
235 title=_("Remove"),
236 icon_class="fa-trash",
237 form=UserForm(
238 _users=direct_editors,
239 _hide_user=True,
240 initial={"user": editor},
241 ),
242 attrs={
243 "href": [
244 reverse(
245 "mesh:submission_editors",
246 kwargs={"pk": self.submission.pk},
247 )
248 ],
249 "class": ["button-error"],
250 "type": ["submit"],
251 "name": ["_action_remove"],
252 },
253 )
254 ],
255 }
256 for editor in direct_editors
257 ]
259 excluded_user_pks = [e.pk for e in direct_editors]
260 context["form"] = UserForm(
261 _users=User.objects.exclude(pk__in=excluded_user_pks), _user_label=_("User")
262 )
264 section_editors = []
265 for editor in get_section_editors(self.submission):
266 editor_data: dict[str, Any] = {"user": editor, "buttons": []}
267 if editor not in direct_editors:
268 editor_data["buttons"].append(
269 Button(
270 id=f"assign-editor-{editor.pk}",
271 title=_("Assign"),
272 icon_class="fa-plus",
273 form=UserForm(
274 _users=direct_editors,
275 _hide_user=True,
276 initial={"user": editor},
277 ),
278 attrs={
279 "href": [
280 reverse(
281 "mesh:submission_editors",
282 kwargs={"pk": self.submission.pk},
283 )
284 ],
285 "type": ["submit"],
286 },
287 )
288 )
289 else:
290 editor_data["buttons"].append(
291 Button(
292 id=f"assigned-editor-{editor.pk}",
293 title=_("Assigned"),
294 icon_class="fa-check",
295 attrs={"class": ["inactive"]},
296 )
297 )
298 section_editors.append(editor_data)
299 context["section_editors"] = section_editors
301 context["form_save_title"] = _("Add editor")
302 context["form_save_icon"] = "fa-plus"
303 context["page_title"] = _("Assigned editors")
305 # Breadcrumnb
306 breadcrumb = get_submission_breadcrumb(self.submission)
307 breadcrumb.add_item(
308 title=_("Assigned editors"),
309 url=reverse("mesh:submission_editors", kwargs={"submission_pk": self.submission.pk}),
310 )
311 context["breadcrumb"] = breadcrumb
313 return context
315 def post(self, request: HttpRequest, *args, **kwargs):
316 remove_form = "_action_remove" in request.POST
317 if remove_form:
318 users = User.objects.filter(editor_submissions__submission=self.submission)
319 else:
320 users = User.objects.exclude(editor_submissions__submission=self.submission)
322 response = HttpResponseRedirect(
323 reverse("mesh:submission_details", kwargs={"submission_pk": self.submission.pk})
324 )
325 form = UserForm(request.POST, _users=users)
326 if not form.is_valid():
327 messages.error(request, "ERROR")
328 return response
330 user = form.cleaned_data["user"]
332 if remove_form:
333 EditorSubmissionRight.objects.get(submission=self.submission, user=user).delete()
334 messages.success(
335 request,
336 _(f"{user} was successfully removed from the assigned editors of the submission."),
337 )
338 SubmissionLog.add_message(
339 self.submission,
340 content=_(f"{user} removed from the assigned editors."),
341 content_en=f"{user} removed from the assigned editors.",
342 user=self.request.user,
343 significant=True,
344 )
345 else:
346 EditorSubmissionRight.objects.create(submission=self.submission, user=user)
347 messages.success(
348 request,
349 _(f"{user} was successfully assigned as an editor for the submission."),
350 )
351 SubmissionLog.add_message(
352 self.submission,
353 content=_(f"{user} assigned as an editor."),
354 content_en=f"{user} assigned as an editor.",
355 user=self.request.user,
356 significant=True,
357 )
359 return response
362class AssignEditorAPIView(LoginRequiredMixin, MeshObjectMixin, View):
363 def setup(self, request, *args, **kwargs):
364 super().setup(request, *args, **kwargs)
365 self.submission = self.get_submission()
366 if not self.request.current_role.can_assign_editor(self.submission):
367 raise PermissionError
369 def get(self, request, *args, **kwargs):
370 users = User.objects.all()
371 editors = User.objects.filter(editor_submissions__submission=self.submission)
372 for user in users:
373 if user in editors:
374 user.selected = True
376 return render(
377 request,
378 "mesh/forms/assign_editor_modal.html",
379 {"users": users, "submission_proxy_pk": kwargs["submission_pk"]},
380 )
382 def post(self, request, *args, **kwargs):
383 existing_pks = User.objects.filter(
384 editor_submissions__submission=self.submission
385 ).values_list("pk", flat=True)
387 editors_data = request.POST.get("select-editors", "")
388 editors = json.loads(editors_data)
389 new_pks = [int(pk) for pk in editors]
391 for pk in existing_pks:
392 if pk not in new_pks:
393 user = User.objects.get(pk=pk)
394 remove_editor(self.submission, user, self.request.user)
396 for pk in new_pks:
397 if pk not in existing_pks:
398 user = User.objects.get(pk=pk)
399 assign_editor(self.submission, user, self.request.user)
401 return JsonResponse({"message": "OK"})