Coverage for src / mesh / views / views_reviewer.py: 61%

175 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-07-09 13:14 +0000

1from typing import Any 

2 

3from django.contrib import messages 

4from django.contrib.auth.mixins import LoginRequiredMixin 

5from django.http import HttpResponseRedirect, 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.utils.translation import gettext_lazy as _ 

11from django.views.decorators.csrf import csrf_exempt 

12from django.views.generic.base import TemplateView 

13from django.views.generic.edit import CreateView, DeleteView, UpdateView 

14from ptf.views.components.breadcrumb import get_base_breadcrumb 

15 

16from mesh.crud.reviewer_list import create_suggested_reviewer, get_all_reviewers 

17from mesh.models.orm.submission_models import Submission 

18from mesh.models.orm.suggestion_model import Suggestion 

19from mesh.models.orm.user_models import SuggestedReviewer, User 

20from mesh.views.views_base import MeshObjectMixin 

21 

22from .forms.reviewer_forms import SimpleReviewerForm, SuggestedReviewerForm 

23from .utils import get_suggestion 

24 

25 

26def busy(reviewer): 

27 return reviewer.pending > 0 

28 

29 

30class ReviewerListView(LoginRequiredMixin, CreateView): 

31 """ 

32 View for the list of reviewers and suggested reviewers. 

33 A reviewer is a user with an account (which might have been created by an editor with Assign Reviewer), 

34 or a SuggestedReviewer (a person which does not have an account yet) 

35 This view can be called for all the reviewers in the system, or to create a shortlist for a submission 

36 """ 

37 

38 model = SuggestedReviewer 

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

40 form_class = SuggestedReviewerForm 

41 

42 def setup(self, request, *args, **kwargs): 

43 super().setup(request, *args, **kwargs) 

44 if not self.request.current_role.can_access_journal_sections(): 

45 raise PermissionError 

46 

47 def get_success_url(self): 

48 return reverse("mesh:reviewer_list") 

49 

50 def set_success_message(self): 

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

52 pass 

53 

54 def get_form_kwargs(self): 

55 kwargs = super().get_form_kwargs() 

56 kwargs["current_user"] = self.request.user 

57 return kwargs 

58 

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

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

61 context["reviewers"] = get_all_reviewers() 

62 context["reviewer_form_action"] = reverse("mesh:reviewer_list") 

63 

64 breadcrumb = get_base_breadcrumb() 

65 breadcrumb.add_item(title=_("Reviewers"), url=reverse("mesh:reviewer_list")) 

66 context["breadcrumb"] = breadcrumb 

67 

68 context["page_title"] = _("Reviewers") 

69 return context 

70 

71 def form_valid(self, form): 

72 create_suggested_reviewer(form) 

73 return HttpResponseRedirect(self.get_success_url()) 

74 

75 def form_invalid(self, form): 

76 for key, value in form.errors.items(): 

77 messages.error(self.request, str(value[0])) 

78 return super().form_invalid(form) 

79 

80 

81class ReviewerDeleteView(LoginRequiredMixin, DeleteView): 

82 model = SuggestedReviewer 

83 success_url = reverse_lazy("mesh:reviewer_list") 

84 

85 def setup(self, request, *args, **kwargs): 

86 super().setup(request, *args, **kwargs) 

87 if not self.request.current_role.can_access_journal_sections(): 

88 raise PermissionError 

89 

90 

91class ReviewerEditAPIView(LoginRequiredMixin, UpdateView): 

92 """ 

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

94 """ 

95 

96 model = SuggestedReviewer 

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

98 form_class = SuggestedReviewerForm 

99 success_url = reverse_lazy("mesh:home") 

100 

101 def setup(self, request, *args, **kwargs): 

102 super().setup(request, *args, **kwargs) 

103 if not self.request.current_role.can_access_journal_sections(): 

104 raise PermissionError 

105 

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

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

108 context["row_number"] = self.kwargs.get("row_number", -1) 

109 context["user_type"] = self.kwargs.get("user_type", "reviewer") 

110 return context 

111 

112 def get(self, request, *args, **kwargs): 

113 user_type = self.kwargs.get("user_type", "reviewer") 

114 if user_type == "user": 

115 self.form_class = SimpleReviewerForm 

116 self.model = User 

117 

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

119 return response 

120 

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

122 user_type = self.kwargs.get("user_type", "reviewer") 

123 if user_type == "user": 

124 self.form_class = SimpleReviewerForm 

125 self.model = User 

126 

127 super().post(request, *args, **kwargs) 

128 if user_type == "user": 

129 reviewer = get_object_or_404(User, pk=kwargs["pk"]) 

130 else: 

131 reviewer = get_object_or_404(SuggestedReviewer, pk=kwargs["pk"]) 

132 row_number = self.kwargs.get("row_number", -1) 

133 

134 the_messages = messages.get_messages(request) 

135 message_html = ( 

136 render_to_string( 

137 "mesh/messages.html", {"messages": messages.get_messages(request)}, request=request 

138 ) 

139 if len(the_messages) > 0 

140 else "" 

141 ) 

142 

143 return JsonResponse( 

144 { 

145 "row_data": [ 

146 reviewer.first_name, 

147 reviewer.last_name, 

148 reviewer.email, 

149 reviewer.keywords, 

150 ], 

151 "row_number": row_number, 

152 "messages": message_html, 

153 } 

154 ) 

155 

156 def form_valid(self, form): 

157 return super().form_valid(form) 

158 

159 def form_invalid(self, form): 

160 for key, value in form.errors.items(): 

161 messages.error(self.request, str(value[0])) 

162 return super().form_invalid(form) 

163 

164 

165def add_suggestion(submission, suggested_user, suggested_reviewer, suggest_to_avoid=False): 

166 qs = submission.suggestions_for_reviewer.order_by("-seq") 

167 seq = qs.first().seq + 1 if qs.exists() else 0 

168 

169 suggestion = Suggestion( 

170 submission=submission, 

171 suggested_user=suggested_user, 

172 suggested_reviewer=suggested_reviewer, 

173 suggest_to_avoid=suggest_to_avoid, 

174 seq=seq, 

175 ) 

176 suggestion.save() 

177 

178 return suggestion 

179 

180 

181def add_suggestion_from_person(submission, person_info, suggest_to_avoid): 

182 if submission is not None and person_info is not None and person_info["email"] != "": 

183 suggested_user = suggested_reviewer = None 

184 qs = User.objects.filter(email=person_info["email"]) 

185 if qs.exists(): 

186 suggested_user = qs.first() 

187 else: 

188 qs = SuggestedReviewer.objects.filter(email=person_info["email"]) 

189 if qs.exists(): 

190 suggested_reviewer = qs.first() 

191 else: 

192 suggested_reviewer = SuggestedReviewer( 

193 first_name=person_info["first_name"], 

194 last_name=person_info["last_name"], 

195 email=person_info["email"], 

196 ) 

197 suggested_reviewer.save() 

198 

199 add_suggestion( 

200 submission=submission, 

201 suggested_user=suggested_user, 

202 suggested_reviewer=suggested_reviewer, 

203 suggest_to_avoid=suggest_to_avoid, 

204 ) 

205 

206 

207@method_decorator([csrf_exempt], name="dispatch") 

208class SuggestionView(MeshObjectMixin, ReviewerListView): 

209 """ 

210 Page to add/delete reviewers AND to add/remove reviewers to a shortlist (= Suggestion) 

211 """ 

212 

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

214 submission: Submission 

215 

216 def setup(self, request, *args, **kwargs): 

217 super().setup(request, *args, **kwargs) 

218 if not self.request.current_role.can_access_journal_sections(): 

219 raise PermissionError 

220 self.submission = self.get_submission() 

221 

222 def get_success_url(self): 

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

224 

225 def get_context_data(self, **kwargs): 

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

227 # if "submission" in request.POST: 

228 # self.submission = Submission.objects.get(pk=int(kwargs.get"submission"])) 

229 

230 context["submission"] = self.submission 

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

232 context["reviewer_form_action"] = reverse( 

233 "mesh:submission_shortlist", kwargs={"submission_pk": self.submission.pk} 

234 ) 

235 return context 

236 

237 def form_valid(self, form): 

238 create_suggested_reviewer(form) 

239 return HttpResponseRedirect(self.get_success_url()) 

240 

241 

242@method_decorator([csrf_exempt], name="dispatch") 

243class SuggestionAPIView(MeshObjectMixin, TemplateView): 

244 """ 

245 API to add/remove users to a shortlist. It ends up adding or removing a Suggestion. 

246 """ 

247 

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

249 

250 def setup(self, request, *args, **kwargs): 

251 super().setup(request, *args, **kwargs) 

252 if not self.request.current_role.can_access_journal_sections(): 

253 raise PermissionError 

254 self.submission = self.get_submission() 

255 self.user_type = self.kwargs.get("user_type", "reviewer") 

256 

257 def add_suggestion(self): 

258 if not self.qs.exists(): 

259 suggested_user = suggested_reviewer = None 

260 

261 if self.user_type == "user": 

262 suggested_user = self.reviewer 

263 else: 

264 suggested_reviewer = self.reviewer 

265 

266 add_suggestion( 

267 submission=self.submission, 

268 suggested_user=suggested_user, 

269 suggested_reviewer=suggested_reviewer, 

270 ) 

271 

272 def remove_suggestion(self): 

273 suggestion = self.qs.first() 

274 if suggestion: 

275 suggestion.delete() 

276 

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

278 if self.user_type == "user": 

279 self.reviewer = get_object_or_404(User, pk=kwargs["user_pk"]) 

280 self.qs = Suggestion.objects.filter( 

281 submission=self.submission, suggested_user=self.reviewer 

282 ) 

283 else: 

284 self.reviewer = get_object_or_404(SuggestedReviewer, pk=kwargs["user_pk"]) 

285 self.qs = Suggestion.objects.filter( 

286 submission=self.submission, suggested_reviewer=self.reviewer 

287 ) 

288 

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

290 self.add_suggestion() 

291 else: 

292 self.remove_suggestion() 

293 

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

295 

296 def get_context_data(self, **kwargs): 

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

298 context["submission"] = self.submission 

299 context["reviewer"] = self.reviewer 

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

301 

302 return context