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

1from typing import Any 

2 

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 

13 

14from mesh.views.mixins import RoleMixin 

15 

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 

21 

22 

23def busy(reviewer): 

24 return reviewer.pending > 0 

25 

26 

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 """ 

34 

35 model = SuggestedReviewer 

36 template_name = "mesh/reviewer/reviewer_list_page.html" 

37 form_class = SuggestedReviewerForm 

38 

39 def dispatch(self, request, *args, **kwargs): 

40 return super().dispatch(request, *args, **kwargs) 

41 

42 def restrict_dispatch(self, request: HttpRequest, *args, **kwargs) -> bool: 

43 return not self.role_handler.check_rights("can_access_journal_sections") 

44 

45 def get_success_url(self): 

46 return reverse("mesh:reviewer_list") 

47 

48 def set_success_message(self): # pylint: disable=no-self-use 

49 # messages.success(self.request, "Le fascicule a été modifié") 

50 pass 

51 

52 def get_form_kwargs(self): 

53 kwargs = super().get_form_kwargs() 

54 # Update self.kwargs if needed 

55 return kwargs 

56 

57 def get_context_data(self, **kwargs) -> dict[str, Any]: 

58 context = super().get_context_data(**kwargs) 

59 

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)) 

65 

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) 

76 

77 for reviewer in SuggestedReviewer.objects.all(): 

78 reviewer.type = "reviewer" 

79 reviewers.append(reviewer) 

80 context["reviewers"] = reviewers 

81 

82 return context 

83 

84 def form_valid(self, form): 

85 # Create/Update a SuggestedReviewer 

86 self.set_success_message() 

87 result = super().form_valid(form) 

88 

89 return result 

90 

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) 

95 

96 

97class ReviewerDeleteView(RoleMixin, DeleteView): 

98 model = SuggestedReviewer 

99 success_url = reverse_lazy("mesh:reviewer_list") 

100 

101 def restrict_dispatch(self, request: HttpRequest, *args, **kwargs) -> bool: 

102 return not self.role_handler.check_rights("can_access_journal_sections") 

103 

104 

105class ReviewerEditAPIView(UpdateView): 

106 """ 

107 Modal to add a new (suggested) reviewer, used in the ReviewerListView 

108 """ 

109 

110 model = SuggestedReviewer 

111 template_name = "mesh/reviewer/suggested_reviewer_modal.html" 

112 form_class = SuggestedReviewerForm 

113 success_url = reverse_lazy("mesh:home") 

114 

115 def dispatch(self, request, *args, **kwargs): 

116 return super().dispatch(request, *args, **kwargs) 

117 

118 def restrict_dispatch(self, request: HttpRequest, *args, **kwargs) -> bool: 

119 return not self.role_handler.check_rights("can_access_journal_sections") 

120 

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 

126 

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 

132 

133 response = super().get(request, *args, **kwargs) 

134 return response 

135 

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 

141 

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) 

148 

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 ) 

157 

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 ) 

170 

171 def form_valid(self, form): 

172 return super().form_valid(form) 

173 

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) 

178 

179 

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 

183 

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() 

192 

193 return suggestion 

194 

195 

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() 

213 

214 add_suggestion( 

215 submission=submission, 

216 suggested_user=suggested_user, 

217 suggested_reviewer=suggested_reviewer, 

218 suggest_to_avoid=suggest_to_avoid, 

219 ) 

220 

221 

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 """ 

227 

228 template_name = "mesh/reviewer/suggestion_page.html" 

229 submission = None 

230 

231 def restrict_dispatch(self, request: HttpRequest, *args, **kwargs) -> bool: 

232 return not self.role_handler.check_rights("can_access_journal_sections") 

233 

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) 

237 

238 def get_success_url(self): 

239 return reverse("mesh:submission_shortlist", kwargs={"pk": self.submission.pk}) 

240 

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"])) 

245 

246 context["submission"] = self.submission 

247 context["selected_reviewers"] = get_suggestion(self.submission) 

248 

249 return context 

250 

251 def form_valid(self, form): 

252 result = super().form_valid(form) 

253 

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 ) 

261 

262 return result 

263 

264 

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 """ 

270 

271 template_name = "mesh/reviewer/shortlist_content.html" 

272 

273 def restrict_dispatch(self, request: HttpRequest, *args, **kwargs) -> bool: 

274 return not self.role_handler.check_rights("can_access_journal_sections") 

275 

276 def dispatch(self, request, *args, **kwargs): 

277 return super().dispatch(request, *args, **kwargs) 

278 

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) 

284 

285 return context 

286 

287 def add_suggestion(self): 

288 if not self.qs.exists(): 

289 suggested_user = suggested_reviewer = None 

290 

291 if self.user_type == "user": 

292 suggested_user = self.reviewer 

293 else: 

294 suggested_reviewer = self.reviewer 

295 

296 add_suggestion( 

297 submission=self.submission, 

298 suggested_user=suggested_user, 

299 suggested_reviewer=suggested_reviewer, 

300 ) 

301 

302 def remove_suggestion(self): 

303 if self.qs.exists(): 

304 suggestion = self.qs.first() 

305 suggestion.delete() 

306 

307 def post(self, request, *args, **kwargs): 

308 self.submission = get_object_or_404(Submission, pk=kwargs["pk"]) 

309 

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 ) 

321 

322 if self.kwargs.get("action", "add") == "add": 

323 self.add_suggestion() 

324 else: 

325 self.remove_suggestion() 

326 

327 return super().get(request, *args, **kwargs)