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

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

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 seq=seq, 

189 ) 

190 suggestion.save() 

191 

192 return suggestion 

193 

194 

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

200 

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

202 submission = None 

203 

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

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

206 

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) 

210 

211 def get_success_url(self): 

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

213 

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

218 

219 context["submission"] = self.submission 

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

221 

222 return context 

223 

224 def form_valid(self, form): 

225 result = super().form_valid(form) 

226 

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 ) 

234 

235 return result 

236 

237 

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

243 

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

245 

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

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

248 

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

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

251 

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) 

257 

258 return context 

259 

260 def add_suggestion(self): 

261 if not self.qs.exists(): 

262 suggested_user = suggested_reviewer = None 

263 

264 if self.user_type == "user": 

265 suggested_user = self.reviewer 

266 else: 

267 suggested_reviewer = self.reviewer 

268 

269 add_suggestion( 

270 submission=self.submission, 

271 suggested_user=suggested_user, 

272 suggested_reviewer=suggested_reviewer, 

273 ) 

274 

275 def remove_suggestion(self): 

276 if self.qs.exists(): 

277 suggestion = self.qs.first() 

278 suggestion.delete() 

279 

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

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

282 

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 ) 

294 

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

296 self.add_suggestion() 

297 else: 

298 self.remove_suggestion() 

299 

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