Coverage for src/comments_views/core/views.py: 12%

131 statements  

« prev     ^ index     » next       coverage.py v7.7.0, created at 2025-04-09 14:49 +0000

1from typing import Any 

2 

3from django.contrib import messages 

4from django.http import HttpRequest, HttpResponseRedirect 

5from django.urls import reverse_lazy 

6from django.views.generic import TemplateView, View 

7from ptf.model_helpers import get_article_by_doi 

8from ptf.url_utils import format_url_with_params 

9from ptf.utils import send_email_from_template 

10 

11from comments_api.constants import ( 

12 PARAM_BOOLEAN_VALUE, 

13 PARAM_DASHBOARD, 

14 STATUS_REJECTED, 

15 STATUS_SOFT_DELETED, 

16 STATUS_SUBMITTED, 

17 STATUS_VALIDATED, 

18) 

19 

20from .app_settings import app_settings 

21from .forms import ( 

22 BaseCommentStatusForm, 

23 CommentFormAutogrow, 

24 DetailedCommentStatusForm, 

25 format_form_errors, 

26) 

27from .mixins import AbstractCommentRightsMixin 

28from .utils import ( 

29 comments_credentials, 

30 comments_server_url, 

31 format_comment, 

32 get_comment, 

33 get_user_dict, 

34 make_api_request, 

35) 

36 

37 

38class CommentDashboardListView(AbstractCommentRightsMixin, TemplateView): 

39 """ 

40 View for displaying the lists of pending and processed comments. 

41 """ 

42 

43 template_name = "dashboard/comment_list.html" 

44 

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

46 """ 

47 Get all the comments to display to the user. 

48 Split them by status (submitted vs validated) 

49 """ 

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

51 

52 # The request to the comment API is adapted with the user permissions: 

53 rights = self.get_rights(self.request.user) 

54 query_params = rights.comment_rights_query_params() 

55 query_params[PARAM_DASHBOARD] = PARAM_BOOLEAN_VALUE 

56 

57 error, comments = make_api_request( 

58 "GET", 

59 comments_server_url(query_params), 

60 request_for_message=self.request, 

61 auth=comments_credentials(), 

62 ) 

63 

64 if error: 

65 return context 

66 

67 submitted_comments = [] 

68 processed_comments = [] 

69 users = get_user_dict() 

70 for comment in comments: 

71 format_comment(comment, rights, users) 

72 status_code = comment.get("status") 

73 if status_code == STATUS_SUBMITTED: 

74 submitted_comments.append(comment) 

75 else: 

76 processed_comments.append(comment) 

77 

78 context["display_moderators"] = ( 

79 len(rights.get_user_admin_collections() + rights.get_user_staff_collections()) > 0 

80 ) or self.request.user.is_superuser 

81 

82 # Table sorting is purely handled by front end, we don't need to enfore it here. 

83 context["submitted_comments"] = submitted_comments 

84 context["processed_comments"] = processed_comments 

85 

86 # Default forms for quick moderation actions 

87 context["validate_form"] = BaseCommentStatusForm(initial={"status": STATUS_VALIDATED}) 

88 context["reject_form"] = BaseCommentStatusForm(initial={"status": STATUS_REJECTED}) 

89 return context 

90 

91 

92class CommentDashboardDetailsView(AbstractCommentRightsMixin, TemplateView): 

93 template_name = "dashboard/comment_details.html" 

94 

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

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

97 pk = kwargs["pk"] 

98 

99 # The request is adapted related to the user permissions: 

100 rights = self.get_rights(self.request.user) 

101 query_params = rights.comment_rights_query_params() 

102 query_params[PARAM_DASHBOARD] = PARAM_BOOLEAN_VALUE 

103 error, comment = get_comment(query_params, pk, request_for_message=self.request) 

104 

105 if error: 

106 return context 

107 

108 context["display_moderators"] = ( 

109 rights.comment_can_manage_moderators(comment) or self.request.user.is_superuser 

110 ) 

111 users = get_user_dict() 

112 format_comment(comment, rights, users) 

113 context["comment"] = comment 

114 

115 edit = kwargs.get("edit") 

116 if edit and rights.comment_can_edit(comment): 

117 context["edit"] = True 

118 context["show_edit"] = True 

119 post_query_params = { 

120 "redirect_url": self.request.build_absolute_uri( 

121 reverse_lazy("comment_details", kwargs={"pk": pk}) 

122 ) 

123 } 

124 

125 context["post_url"] = format_url_with_params( 

126 reverse_lazy(rights.COMMENT_POST_URL), post_query_params 

127 ) 

128 # Deep copy the comment data 

129 initial_data = dict(comment) 

130 initial_data["content"] = initial_data["sanitized_html"] 

131 context["comment_form"] = CommentFormAutogrow(initial=initial_data) 

132 

133 else: 

134 moderate_form = DetailedCommentStatusForm( 

135 initial={ 

136 "editorial_team_comment": comment["editorial_team_comment"], 

137 "article_author_comment": comment["article_author_comment"], 

138 "hide_author_name": comment["hide_author_name"], 

139 } 

140 ) 

141 context["comment_status_validated"] = STATUS_VALIDATED 

142 context["comment_status_rejected"] = STATUS_REJECTED 

143 if rights.comment_can_delete(comment): 

144 context["delete_form"] = BaseCommentStatusForm( 

145 initial={"status": STATUS_SOFT_DELETED} 

146 ) 

147 

148 comment_status = comment.get("status") 

149 if comment_status == STATUS_SUBMITTED: 

150 if rights.comment_can_edit(comment): 

151 context["show_edit"] = True 

152 else: 

153 context["display_moderated_by"] = True 

154 if comment_status == STATUS_VALIDATED: 

155 context["show_context"] = True 

156 

157 context["moderate_form"] = moderate_form 

158 

159 return context 

160 

161 

162class CommentChangeStatusView(AbstractCommentRightsMixin, View): 

163 """ 

164 View used to modify the status of an existing comment. 

165 

166 Used by: 

167 - a comment's author to delete his/her own comment 

168 - a comment moderator to validate/reject a comment he/she has access to. 

169 """ 

170 

171 def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponseRedirect: 

172 redirect_url = request.headers.get("referer") 

173 redirect_url = redirect_url if redirect_url else reverse_lazy("comment_list") 

174 response = HttpResponseRedirect(redirect_url) 

175 

176 if request.path.endswith("/light/"): 

177 status_form = BaseCommentStatusForm(request.POST) 

178 else: 

179 status_form = DetailedCommentStatusForm(request.POST) 

180 

181 if not status_form.is_valid(): 

182 for error in format_form_errors(status_form): 

183 messages.error(request, error) 

184 

185 return response 

186 

187 # Get the initial comment and check user rights for the requested action. 

188 rights = self.get_rights(self.request.user) 

189 query_params = rights.comment_rights_query_params() 

190 query_params[PARAM_DASHBOARD] = PARAM_BOOLEAN_VALUE 

191 

192 comment_id = kwargs["pk"] 

193 error, initial_comment = get_comment(query_params, comment_id, request_for_message=request) 

194 if error: 

195 return response 

196 

197 status = status_form.cleaned_data["status"] 

198 if status == STATUS_SOFT_DELETED: 

199 if not rights.comment_can_delete(initial_comment): 

200 messages.error(request, "You can't delete the comment.") 

201 return response 

202 # Redirect to the comment list in case of a deletion. The current URL will not 

203 # exist anymore if the deletion succeeds. 

204 response = HttpResponseRedirect(reverse_lazy("comment_list")) 

205 

206 # If it's not a deletion, it's a moderation. We can check now if the user 

207 # can moderate the comment before making the request to the comment server. 

208 elif not rights.comment_can_moderate(initial_comment): 

209 messages.error(request, "You can't moderate the comment.") 

210 return response 

211 

212 # Update the comment status. 

213 error, comment = make_api_request( 

214 "PUT", 

215 comments_server_url(query_params, item_id=comment_id), 

216 request_for_message=request, 

217 auth=comments_credentials(), 

218 json=status_form.cleaned_data, 

219 ) 

220 

221 if error: 

222 return response 

223 

224 messages.success(request, f"The comment has been successfully {status}.") 

225 

226 # Post status update processing (e-mails) 

227 subject_prefix = f"[{comment['site_name'].upper()}]" 

228 # Send e-mails according to the status change. 

229 # Only if the status has been changed 

230 if status in [STATUS_VALIDATED, STATUS_REJECTED] and initial_comment["status"] != status: 

231 format_comment(comment) 

232 article = get_article_by_doi(comment["doi"]) 

233 article_name = article.title_tex if article else "" 

234 context_data = { 

235 "full_name": comment["author_name"], 

236 "article_url": comment["base_url"], 

237 "article_name": article_name, 

238 "comment_url": f"{comment['base_url']}#article-comment-{comment['id']}", 

239 "email_signature": "The editorial team", 

240 } 

241 # Send an e-mail to the comment's author and the article's authors 

242 if status == STATUS_VALIDATED: 

243 subject = f"{subject_prefix} New published comment" 

244 send_email_from_template( 

245 "mail/comment_validated.html", 

246 context_data, 

247 subject, 

248 to=[comment["author_email"]], 

249 from_collection=comment["site_name"], 

250 ) 

251 

252 # Send e-mail to the article's authors if the validation email 

253 # has not been sent yet. 

254 if ( 

255 app_settings.EMAIL_AUTHOR 

256 and article 

257 and initial_comment["validation_email_sent"] is False 

258 ): 

259 author_data = [] 

260 author_contributions = article.get_author_contributions() 

261 for contribution in author_contributions: 

262 if contribution.corresponding and contribution.email: 

263 author_data.append(contribution) 

264 for author in author_data: 

265 context_data["article_author_name"] = author.display_name() 

266 subject = f"{subject_prefix} New published comment on your article" 

267 send_email_from_template( 

268 "mail/comment_new_article_author.html", 

269 context_data, 

270 subject, 

271 to=[author.email], 

272 from_collection=comment["site_name"], 

273 ) 

274 # Only send an e-mail to the comment's author 

275 elif status == STATUS_REJECTED: 

276 subject = f"{subject_prefix} Comment rejected" 

277 send_email_from_template( 

278 "mail/comment_rejected.html", 

279 context_data, 

280 subject, 

281 to=[comment["author_email"]], 

282 from_collection=comment["site_name"], 

283 ) 

284 return response