Coverage for src/mesh/views/views_reviewer.py: 32%
178 statements
« prev ^ index » next coverage.py v7.9.0, created at 2025-09-10 11:20 +0000
« prev ^ index » next coverage.py v7.9.0, created at 2025-09-10 11:20 +0000
1from typing import Any
3from django.contrib import messages
4from django.db.models import Exists, OuterRef
5from django.http import HttpRequest, JsonResponse
6from django.shortcuts import get_object_or_404
7from django.template.loader import render_to_string
8from django.urls import reverse, reverse_lazy
9from django.utils.decorators import method_decorator
10from django.views.decorators.csrf import csrf_exempt
11from django.views.generic.base import TemplateView
12from django.views.generic.edit import CreateView, DeleteView, UpdateView
14from mesh.views.mixins import RoleMixin
16from ..models.review_models import Review
17from ..models.submission_models import Submission
18from ..models.user_models import SuggestedReviewer, Suggestion, User
19from .forms.reviewer_forms import SimpleReviewerForm, SuggestedReviewerForm
20from .utils import get_suggestion
23def busy(reviewer):
24 return reviewer.pending > 0
27class ReviewerListView(RoleMixin, CreateView):
28 """
29 View for the list of reviewers and suggested reviewers.
30 A reviewer is a user with an account (which might have been created by an editor with Assign Reviewer),
31 or a SuggestedReviewer (a person which does not have an account yet)
32 This view can be called for all the reviewers in the system, or to create a shortlist for a submission
33 """
35 model = SuggestedReviewer
36 template_name = "mesh/reviewer/reviewer_list_page.html"
37 form_class = SuggestedReviewerForm
39 def dispatch(self, request, *args, **kwargs):
40 return super().dispatch(request, *args, **kwargs)
42 def restrict_dispatch(self, request: HttpRequest, *args, **kwargs) -> bool:
43 return not self.role_handler.check_rights("can_access_journal_sections")
45 def get_success_url(self):
46 return reverse("mesh:reviewer_list")
48 def set_success_message(self): # pylint: disable=no-self-use
49 # messages.success(self.request, "Le fascicule a été modifié")
50 pass
52 def get_form_kwargs(self):
53 kwargs = super().get_form_kwargs()
54 # Update self.kwargs if needed
55 return kwargs
57 def get_context_data(self, **kwargs) -> dict[str, Any]:
58 context = super().get_context_data(**kwargs)
60 # User.objects.filter(review__isnull=False) is not equivalent to User.objects.exclude(review__isnull=True)
61 # filter(review__isnull=False) does a simple INNER JOIN and you will get duplicates
62 # adding a .distinct() would work but is less efficient
63 subquery = Review.objects.filter(reviewer=OuterRef("pk")).order_by().values("reviewer")
64 user_reviewers = User.objects.filter(Exists(subquery))
66 reviewers = []
67 for reviewer in user_reviewers:
68 reviewer.type = "user"
69 reviewer.total = reviewer.review_set.count()
70 reviewer.pending = reviewer.review_set.exclude(
71 recommendation__isnull=False, accepted__isnull=False
72 ).count()
73 reviewer.accepted = reviewer.review_set.filter(accepted=True).count()
74 reviewer.declined = reviewer.review_set.filter(accepted=False).count()
75 reviewers.append(reviewer)
77 for reviewer in SuggestedReviewer.objects.all():
78 reviewer.type = "reviewer"
79 reviewers.append(reviewer)
80 context["reviewers"] = reviewers
82 return context
84 def form_valid(self, form):
85 # Create/Update a SuggestedReviewer
86 self.set_success_message()
87 result = super().form_valid(form)
89 return result
91 def form_invalid(self, form):
92 for key, value in form.errors.items():
93 messages.error(self.request, str(value[0]))
94 return super().form_invalid(form)
97class ReviewerDeleteView(RoleMixin, DeleteView):
98 model = SuggestedReviewer
99 success_url = reverse_lazy("mesh:reviewer_list")
101 def restrict_dispatch(self, request: HttpRequest, *args, **kwargs) -> bool:
102 return not self.role_handler.check_rights("can_access_journal_sections")
105class ReviewerEditAPIView(UpdateView):
106 """
107 Modal to add a new (suggested) reviewer, used in the ReviewerListView
108 """
110 model = SuggestedReviewer
111 template_name = "mesh/reviewer/suggested_reviewer_modal.html"
112 form_class = SuggestedReviewerForm
113 success_url = reverse_lazy("mesh:home")
115 def dispatch(self, request, *args, **kwargs):
116 return super().dispatch(request, *args, **kwargs)
118 def restrict_dispatch(self, request: HttpRequest, *args, **kwargs) -> bool:
119 return not self.role_handler.check_rights("can_access_journal_sections")
121 def get_context_data(self, **kwargs) -> dict[str, Any]:
122 context = super().get_context_data(**kwargs)
123 context["row_number"] = self.kwargs.get("row_number", -1)
124 context["user_type"] = self.kwargs.get("user_type", "reviewer")
125 return context
127 def get(self, request, *args, **kwargs):
128 user_type = self.kwargs.get("user_type", "reviewer")
129 if user_type == "user":
130 self.form_class = SimpleReviewerForm
131 self.model = User
133 response = super().get(request, *args, **kwargs)
134 return response
136 def post(self, request, *args, **kwargs):
137 user_type = self.kwargs.get("user_type", "reviewer")
138 if user_type == "user":
139 self.form_class = SimpleReviewerForm
140 self.model = User
142 super().post(request, *args, **kwargs)
143 if user_type == "user":
144 reviewer = get_object_or_404(User, pk=kwargs["pk"])
145 else:
146 reviewer = get_object_or_404(SuggestedReviewer, pk=kwargs["pk"])
147 row_number = self.kwargs.get("row_number", -1)
149 the_messages = messages.get_messages(request)
150 message_html = (
151 render_to_string(
152 "mesh/messages.html", {"messages": messages.get_messages(request)}, request=request
153 )
154 if len(the_messages) > 0
155 else ""
156 )
158 return JsonResponse(
159 {
160 "row_data": [
161 reviewer.first_name,
162 reviewer.last_name,
163 reviewer.email,
164 reviewer.keywords,
165 ],
166 "row_number": row_number,
167 "messages": message_html,
168 }
169 )
171 def form_valid(self, form):
172 return super().form_valid(form)
174 def form_invalid(self, form):
175 for key, value in form.errors.items():
176 messages.error(self.request, str(value[0]))
177 return super().form_invalid(form)
180def add_suggestion(submission, suggested_user, suggested_reviewer, suggest_to_avoid=False):
181 qs = submission.suggestions_for_reviewer.order_by("-seq")
182 seq = qs.first().seq + 1 if qs.exists() else 0
184 suggestion = Suggestion(
185 submission=submission,
186 suggested_user=suggested_user,
187 suggested_reviewer=suggested_reviewer,
188 suggest_to_avoid=suggest_to_avoid,
189 seq=seq,
190 )
191 suggestion.save()
193 return suggestion
196def add_suggestion_from_person(submission, person_info, suggest_to_avoid):
197 if submission is not None and person_info is not None and person_info["email"] != "":
198 suggested_user = suggested_reviewer = None
199 qs = User.objects.filter(email=person_info["email"])
200 if qs.exists():
201 suggested_user = qs.first()
202 else:
203 qs = SuggestedReviewer.objects.filter(email=person_info["email"])
204 if qs.exists():
205 suggested_reviewer = qs.first()
206 else:
207 suggested_reviewer = SuggestedReviewer(
208 first_name=person_info["first_name"],
209 last_name=person_info["last_name"],
210 email=person_info["email"],
211 )
212 suggested_reviewer.save()
214 add_suggestion(
215 submission=submission,
216 suggested_user=suggested_user,
217 suggested_reviewer=suggested_reviewer,
218 suggest_to_avoid=suggest_to_avoid,
219 )
222@method_decorator([csrf_exempt], name="dispatch")
223class SuggestionView(ReviewerListView):
224 """
225 Page to add/delete reviewers AND to add/remove reviewers to a shortlist (= Suggestion)
226 """
228 template_name = "mesh/reviewer/suggestion_page.html"
229 submission = None
231 def restrict_dispatch(self, request: HttpRequest, *args, **kwargs) -> bool:
232 return not self.role_handler.check_rights("can_access_journal_sections")
234 def dispatch(self, request, *args, **kwargs):
235 self.submission = get_object_or_404(Submission, pk=kwargs["pk"])
236 return super().dispatch(request, *args, **kwargs)
238 def get_success_url(self):
239 return reverse("mesh:submission_shortlist", kwargs={"pk": self.submission.pk})
241 def get_context_data(self, **kwargs):
242 context = super().get_context_data(**kwargs)
243 # if "submission" in request.POST:
244 # self.submission = Submission.objects.get(pk=int(kwargs.get"submission"]))
246 context["submission"] = self.submission
247 context["selected_reviewers"] = get_suggestion(self.submission)
249 return context
251 def form_valid(self, form):
252 result = super().form_valid(form)
254 # Adding a suggested reviewer to a shortlist
255 # (the Add/Edit shortlist button was pressed in the Assign Reviewer page)
256 # The submission object was passed as a hidden value in the form
257 # submission = Submission.objects.get(pk=int(form.data["submission"]))
258 add_suggestion(
259 submission=self.submission, suggested_user=None, suggested_reviewer=form.instance
260 )
262 return result
265@method_decorator([csrf_exempt], name="dispatch")
266class SuggestionAPIView(TemplateView):
267 """
268 API to add/remove users to a shortlist. It ends up adding or removing a Suggestion.
269 """
271 template_name = "mesh/reviewer/shortlist_content.html"
273 def restrict_dispatch(self, request: HttpRequest, *args, **kwargs) -> bool:
274 return not self.role_handler.check_rights("can_access_journal_sections")
276 def dispatch(self, request, *args, **kwargs):
277 return super().dispatch(request, *args, **kwargs)
279 def get_context_data(self, **kwargs):
280 context = super().get_context_data(**kwargs)
281 context["submission"] = self.submission
282 context["reviewer"] = self.reviewer
283 context["selected_reviewers"] = get_suggestion(self.submission)
285 return context
287 def add_suggestion(self):
288 if not self.qs.exists():
289 suggested_user = suggested_reviewer = None
291 if self.user_type == "user":
292 suggested_user = self.reviewer
293 else:
294 suggested_reviewer = self.reviewer
296 add_suggestion(
297 submission=self.submission,
298 suggested_user=suggested_user,
299 suggested_reviewer=suggested_reviewer,
300 )
302 def remove_suggestion(self):
303 if self.qs.exists():
304 suggestion = self.qs.first()
305 suggestion.delete()
307 def post(self, request, *args, **kwargs):
308 self.submission = get_object_or_404(Submission, pk=kwargs["pk"])
310 self.user_type = self.kwargs.get("user_type", "reviewer")
311 if self.user_type == "user":
312 self.reviewer = get_object_or_404(User, pk=kwargs["user_pk"])
313 self.qs = Suggestion.objects.filter(
314 submission=self.submission, suggested_user=self.reviewer
315 )
316 else:
317 self.reviewer = get_object_or_404(SuggestedReviewer, pk=kwargs["user_pk"])
318 self.qs = Suggestion.objects.filter(
319 submission=self.submission, suggested_reviewer=self.reviewer
320 )
322 if self.kwargs.get("action", "add") == "add":
323 self.add_suggestion()
324 else:
325 self.remove_suggestion()
327 return super().get(request, *args, **kwargs)