Coverage for src/mesh/views/views_reviewer.py: 34%
166 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
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):
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 seq=seq,
189 )
190 suggestion.save()
192 return suggestion
195@method_decorator([csrf_exempt], name="dispatch")
196class SuggestionView(ReviewerListView):
197 """
198 Page to add/delete reviewers AND to add/remove reviewers to a shortlist (= Suggestion)
199 """
201 template_name = "mesh/reviewer/suggestion_page.html"
202 submission = None
204 def restrict_dispatch(self, request: HttpRequest, *args, **kwargs) -> bool:
205 return not self.role_handler.check_rights("can_access_journal_sections")
207 def dispatch(self, request, *args, **kwargs):
208 self.submission = get_object_or_404(Submission, pk=kwargs["pk"])
209 return super().dispatch(request, *args, **kwargs)
211 def get_success_url(self):
212 return reverse("mesh:submission_shortlist", kwargs={"pk": self.submission.pk})
214 def get_context_data(self, **kwargs):
215 context = super().get_context_data(**kwargs)
216 # if "submission" in request.POST:
217 # self.submission = Submission.objects.get(pk=int(kwargs.get"submission"]))
219 context["submission"] = self.submission
220 context["selected_reviewers"] = get_suggestion(self.submission)
222 return context
224 def form_valid(self, form):
225 result = super().form_valid(form)
227 # Adding a suggested reviewer to a shortlist
228 # (the Add/Edit shortlist button was pressed in the Assign Reviewer page)
229 # The submission object was passed as a hidden value in the form
230 # submission = Submission.objects.get(pk=int(form.data["submission"]))
231 add_suggestion(
232 submission=self.submission, suggested_user=None, suggested_reviewer=form.instance
233 )
235 return result
238@method_decorator([csrf_exempt], name="dispatch")
239class SuggestionAPIView(TemplateView):
240 """
241 API to add/remove users to a shortlist. It ends up adding or removing a Suggestion.
242 """
244 template_name = "mesh/reviewer/shortlist_content.html"
246 def restrict_dispatch(self, request: HttpRequest, *args, **kwargs) -> bool:
247 return not self.role_handler.check_rights("can_access_journal_sections")
249 def dispatch(self, request, *args, **kwargs):
250 return super().dispatch(request, *args, **kwargs)
252 def get_context_data(self, **kwargs):
253 context = super().get_context_data(**kwargs)
254 context["submission"] = self.submission
255 context["reviewer"] = self.reviewer
256 context["selected_reviewers"] = get_suggestion(self.submission)
258 return context
260 def add_suggestion(self):
261 if not self.qs.exists():
262 suggested_user = suggested_reviewer = None
264 if self.user_type == "user":
265 suggested_user = self.reviewer
266 else:
267 suggested_reviewer = self.reviewer
269 add_suggestion(
270 submission=self.submission,
271 suggested_user=suggested_user,
272 suggested_reviewer=suggested_reviewer,
273 )
275 def remove_suggestion(self):
276 if self.qs.exists():
277 suggestion = self.qs.first()
278 suggestion.delete()
280 def post(self, request, *args, **kwargs):
281 self.submission = get_object_or_404(Submission, pk=kwargs["pk"])
283 self.user_type = self.kwargs.get("user_type", "reviewer")
284 if self.user_type == "user":
285 self.reviewer = get_object_or_404(User, pk=kwargs["user_pk"])
286 self.qs = Suggestion.objects.filter(
287 submission=self.submission, suggested_user=self.reviewer
288 )
289 else:
290 self.reviewer = get_object_or_404(SuggestedReviewer, pk=kwargs["user_pk"])
291 self.qs = Suggestion.objects.filter(
292 submission=self.submission, suggested_reviewer=self.reviewer
293 )
295 if self.kwargs.get("action", "add") == "add":
296 self.add_suggestion()
297 else:
298 self.remove_suggestion()
300 return super().get(request, *args, **kwargs)