Coverage for src / mesh / views / views_reviewer.py: 59%
182 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
1from typing import Any
3from django.contrib import messages
4from django.contrib.auth.mixins import LoginRequiredMixin
5from django.db.models import Exists, OuterRef
6from django.http import JsonResponse
7from django.shortcuts import get_object_or_404
8from django.template.loader import render_to_string
9from django.urls import reverse, reverse_lazy
10from django.utils.decorators import method_decorator
11from django.views.decorators.csrf import csrf_exempt
12from django.views.generic.base import TemplateView
13from django.views.generic.edit import CreateView, DeleteView, UpdateView
15from mesh.models.orm.review_models import Review
16from mesh.models.orm.submission_models import Submission
17from mesh.models.orm.suggestion_model import Suggestion
18from mesh.models.orm.user_models import SuggestedReviewer, User
19from mesh.views.views_base import MeshObjectMixin
21from .forms.reviewer_forms import SimpleReviewerForm, SuggestedReviewerForm
22from .utils import get_suggestion
25def busy(reviewer):
26 return reviewer.pending > 0
29class ReviewerListView(LoginRequiredMixin, CreateView):
30 """
31 View for the list of reviewers and suggested reviewers.
32 A reviewer is a user with an account (which might have been created by an editor with Assign Reviewer),
33 or a SuggestedReviewer (a person which does not have an account yet)
34 This view can be called for all the reviewers in the system, or to create a shortlist for a submission
35 """
37 model = SuggestedReviewer
38 template_name = "mesh/reviewer/reviewer_list_page.html"
39 form_class = SuggestedReviewerForm
41 def setup(self, request, *args, **kwargs):
42 super().setup(request, *args, **kwargs)
43 if not self.request.current_role.can_access_journal_sections():
44 raise PermissionError
46 def get_success_url(self):
47 return reverse("mesh:reviewer_list")
49 def set_success_message(self):
50 # messages.success(self.request, "Le fascicule a été modifié")
51 pass
53 def get_form_kwargs(self):
54 kwargs = super().get_form_kwargs()
55 # Update self.kwargs if needed
56 return kwargs
58 def get_context_data(self, **kwargs) -> dict[str, Any]:
59 context = super().get_context_data(**kwargs)
61 # User.objects.filter(review__isnull=False) is not equivalent to User.objects.exclude(review__isnull=True)
62 # filter(review__isnull=False) does a simple INNER JOIN and you will get duplicates
63 # adding a .distinct() would work but is less efficient
64 subquery = Review.objects.filter(reviewer=OuterRef("pk")).order_by().values("reviewer")
65 user_reviewers = User.objects.filter(Exists(subquery))
67 reviewers = []
68 for reviewer in user_reviewers:
69 reviewer.type = "user"
70 reviewer.total = reviewer.review_set.count()
71 reviewer.pending = reviewer.review_set.exclude(
72 recommendation__isnull=False, accepted__isnull=False
73 ).count()
74 reviewer.accepted = reviewer.review_set.filter(accepted=True).count()
75 reviewer.declined = reviewer.review_set.filter(accepted=False).count()
76 reviewers.append(reviewer)
78 for reviewer in SuggestedReviewer.objects.all():
79 reviewer.type = "reviewer"
80 reviewers.append(reviewer)
81 context["reviewers"] = reviewers
83 return context
85 def form_valid(self, form):
86 # Create/Update a SuggestedReviewer
87 self.set_success_message()
88 result = super().form_valid(form)
90 return result
92 def form_invalid(self, form):
93 for key, value in form.errors.items():
94 messages.error(self.request, str(value[0]))
95 return super().form_invalid(form)
98class ReviewerDeleteView(LoginRequiredMixin, DeleteView):
99 model = SuggestedReviewer
100 success_url = reverse_lazy("mesh:reviewer_list")
102 def setup(self, request, *args, **kwargs):
103 super().setup(request, *args, **kwargs)
104 if not self.request.current_role.can_access_journal_sections():
105 raise PermissionError
108class ReviewerEditAPIView(LoginRequiredMixin, UpdateView):
109 """
110 Modal to add a new (suggested) reviewer, used in the ReviewerListView
111 """
113 model = SuggestedReviewer
114 template_name = "mesh/reviewer/suggested_reviewer_modal.html"
115 form_class = SuggestedReviewerForm
116 success_url = reverse_lazy("mesh:home")
118 def setup(self, request, *args, **kwargs):
119 super().setup(request, *args, **kwargs)
120 if not self.request.current_role.can_access_journal_sections():
121 raise PermissionError
123 def get_context_data(self, **kwargs) -> dict[str, Any]:
124 context = super().get_context_data(**kwargs)
125 context["row_number"] = self.kwargs.get("row_number", -1)
126 context["user_type"] = self.kwargs.get("user_type", "reviewer")
127 return context
129 def get(self, request, *args, **kwargs):
130 user_type = self.kwargs.get("user_type", "reviewer")
131 if user_type == "user":
132 self.form_class = SimpleReviewerForm
133 self.model = User
135 response = super().get(request, *args, **kwargs)
136 return response
138 def post(self, request, *args, **kwargs):
139 user_type = self.kwargs.get("user_type", "reviewer")
140 if user_type == "user":
141 self.form_class = SimpleReviewerForm
142 self.model = User
144 super().post(request, *args, **kwargs)
145 if user_type == "user":
146 reviewer = get_object_or_404(User, pk=kwargs["pk"])
147 else:
148 reviewer = get_object_or_404(SuggestedReviewer, pk=kwargs["pk"])
149 row_number = self.kwargs.get("row_number", -1)
151 the_messages = messages.get_messages(request)
152 message_html = (
153 render_to_string(
154 "mesh/messages.html", {"messages": messages.get_messages(request)}, request=request
155 )
156 if len(the_messages) > 0
157 else ""
158 )
160 return JsonResponse(
161 {
162 "row_data": [
163 reviewer.first_name,
164 reviewer.last_name,
165 reviewer.email,
166 reviewer.keywords,
167 ],
168 "row_number": row_number,
169 "messages": message_html,
170 }
171 )
173 def form_valid(self, form):
174 return super().form_valid(form)
176 def form_invalid(self, form):
177 for key, value in form.errors.items():
178 messages.error(self.request, str(value[0]))
179 return super().form_invalid(form)
182def add_suggestion(submission, suggested_user, suggested_reviewer, suggest_to_avoid=False):
183 qs = submission.suggestions_for_reviewer.order_by("-seq")
184 seq = qs.first().seq + 1 if qs.exists() else 0
186 suggestion = Suggestion(
187 submission=submission,
188 suggested_user=suggested_user,
189 suggested_reviewer=suggested_reviewer,
190 suggest_to_avoid=suggest_to_avoid,
191 seq=seq,
192 )
193 suggestion.save()
195 return suggestion
198def add_suggestion_from_person(submission, person_info, suggest_to_avoid):
199 if submission is not None and person_info is not None and person_info["email"] != "":
200 suggested_user = suggested_reviewer = None
201 qs = User.objects.filter(email=person_info["email"])
202 if qs.exists():
203 suggested_user = qs.first()
204 else:
205 qs = SuggestedReviewer.objects.filter(email=person_info["email"])
206 if qs.exists():
207 suggested_reviewer = qs.first()
208 else:
209 suggested_reviewer = SuggestedReviewer(
210 first_name=person_info["first_name"],
211 last_name=person_info["last_name"],
212 email=person_info["email"],
213 )
214 suggested_reviewer.save()
216 add_suggestion(
217 submission=submission,
218 suggested_user=suggested_user,
219 suggested_reviewer=suggested_reviewer,
220 suggest_to_avoid=suggest_to_avoid,
221 )
224@method_decorator([csrf_exempt], name="dispatch")
225class SuggestionView(MeshObjectMixin, ReviewerListView):
226 """
227 Page to add/delete reviewers AND to add/remove reviewers to a shortlist (= Suggestion)
228 """
230 template_name = "mesh/reviewer/suggestion_page.html"
231 submission: Submission
233 def setup(self, request, *args, **kwargs):
234 super().setup(request, *args, **kwargs)
235 if not self.request.current_role.can_access_journal_sections():
236 raise PermissionError
237 self.submission = self.get_submission()
239 def get_success_url(self):
240 return reverse("mesh:submission_shortlist", kwargs={"submission_pk": self.submission.pk})
242 def get_context_data(self, **kwargs):
243 context = super().get_context_data(**kwargs)
244 # if "submission" in request.POST:
245 # self.submission = Submission.objects.get(pk=int(kwargs.get"submission"]))
247 context["submission"] = self.submission
248 context["selected_reviewers"] = get_suggestion(self.submission)
250 return context
252 def form_valid(self, form):
253 result = super().form_valid(form)
255 # Adding a suggested reviewer to a shortlist
256 # (the Add/Edit shortlist button was pressed in the Assign Reviewer page)
257 # The submission object was passed as a hidden value in the form
258 # submission = Submission.objects.get(pk=int(form.data["submission"]))
259 add_suggestion(
260 submission=self.submission, suggested_user=None, suggested_reviewer=form.instance
261 )
263 return result
266@method_decorator([csrf_exempt], name="dispatch")
267class SuggestionAPIView(MeshObjectMixin, TemplateView):
268 """
269 API to add/remove users to a shortlist. It ends up adding or removing a Suggestion.
270 """
272 template_name = "mesh/reviewer/shortlist_content.html"
274 def setup(self, request, *args, **kwargs):
275 super().setup(request, *args, **kwargs)
276 if not self.request.current_role.can_access_journal_sections():
277 raise PermissionError
278 self.submission = self.get_submission()
279 self.user_type = self.kwargs.get("user_type", "reviewer")
281 def add_suggestion(self):
282 if not self.qs.exists():
283 suggested_user = suggested_reviewer = None
285 if self.user_type == "user":
286 suggested_user = self.reviewer
287 else:
288 suggested_reviewer = self.reviewer
290 add_suggestion(
291 submission=self.submission,
292 suggested_user=suggested_user,
293 suggested_reviewer=suggested_reviewer,
294 )
296 def remove_suggestion(self):
297 suggestion = self.qs.first()
298 if suggestion:
299 suggestion.delete()
301 def post(self, request, *args, **kwargs):
302 if self.user_type == "user":
303 self.reviewer = get_object_or_404(User, pk=kwargs["user_pk"])
304 self.qs = Suggestion.objects.filter(
305 submission=self.submission, suggested_user=self.reviewer
306 )
307 else:
308 self.reviewer = get_object_or_404(SuggestedReviewer, pk=kwargs["user_pk"])
309 self.qs = Suggestion.objects.filter(
310 submission=self.submission, suggested_reviewer=self.reviewer
311 )
313 if self.kwargs.get("action", "add") == "add":
314 self.add_suggestion()
315 else:
316 self.remove_suggestion()
318 return super().get(request, *args, **kwargs)
320 def get_context_data(self, **kwargs):
321 context = super().get_context_data(**kwargs)
322 context["submission"] = self.submission
323 context["reviewer"] = self.reviewer
324 context["selected_reviewers"] = get_suggestion(self.submission)
326 return context