Coverage for apps/comments_views/core/views.py: 74%

146 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-06-19 08:36 +0000

1from typing import Any 

2 

3from django.contrib import messages 

4from django.http import HttpRequest 

5from django.http import HttpResponseRedirect 

6from django.urls import reverse_lazy 

7from django.views.generic import TemplateView 

8from django.views.generic import View 

9 

10from comments_api.constants import PARAM_BOOLEAN_VALUE 

11from comments_api.constants import PARAM_DASHBOARD 

12from comments_api.constants import STATUS_REJECTED 

13from comments_api.constants import STATUS_SOFT_DELETED 

14from comments_api.constants import STATUS_SUBMITTED 

15from comments_api.constants import STATUS_VALIDATED 

16from ptf.model_helpers import get_article_by_doi 

17from ptf.url_utils import format_url_with_params 

18from ptf.utils import send_email_from_template 

19 

20from .app_settings import app_settings 

21from .forms import BaseCommentStatusForm 

22from .forms import CommentFormAutogrow 

23from .forms import DetailedCommentStatusForm 

24from .forms import format_form_errors 

25from .mixins import AbstractCommentRightsMixin 

26from .utils import comments_credentials 

27from .utils import comments_server_url 

28from .utils import format_comment 

29from .utils import get_comment 

30from .utils import get_user_dict 

31from .utils import make_api_request 

32 

33 

34class CommentDashboardListView(AbstractCommentRightsMixin, TemplateView): 

35 """ 

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

37 """ 

38 

39 template_name = "dashboard/comment_list.html" 

40 

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

42 """ 

43 Get all the comments to display to the user. 

44 Split them by status (submitted vs validated) 

45 """ 

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

47 

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

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

50 query_params = rights.comment_rights_query_params() 

51 query_params[PARAM_DASHBOARD] = PARAM_BOOLEAN_VALUE 

52 

53 error, comments = make_api_request( 

54 "GET", 

55 comments_server_url(query_params), 

56 request_for_message=self.request, 

57 auth=comments_credentials(), 

58 ) 

59 

60 if error: 

61 return context 

62 

63 submitted_comments = [] 

64 processed_comments = [] 

65 users = get_user_dict() 

66 for comment in comments: 

67 format_comment(comment, rights, users) 

68 status_code = comment.get("status") 

69 if status_code == STATUS_SUBMITTED: 

70 submitted_comments.append(comment) 

71 else: 

72 processed_comments.append(comment) 

73 

74 context["display_moderators"] = ( 

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

76 ) or self.request.user.is_superuser 

77 

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

79 context["submitted_comments"] = submitted_comments 

80 context["processed_comments"] = processed_comments 

81 

82 # Default forms for quick moderation actions 

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

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

85 return context 

86 

87 

88class CommentDashboardDetailsView(AbstractCommentRightsMixin, TemplateView): 

89 template_name = "dashboard/comment_details.html" 

90 

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

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

93 pk = kwargs["pk"] 

94 

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

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

97 query_params = rights.comment_rights_query_params() 

98 query_params[PARAM_DASHBOARD] = PARAM_BOOLEAN_VALUE 

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

100 

101 if error: 

102 return context 

103 

104 context["display_moderators"] = ( 

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

106 ) 

107 users = get_user_dict() 

108 format_comment(comment, rights, users) 

109 context["comment"] = comment 

110 

111 edit = kwargs.get("edit") 

112 if edit and rights.comment_can_edit(comment): 

113 context["edit"] = True 

114 context["show_edit"] = True 

115 post_query_params = { 

116 "redirect_url": self.request.build_absolute_uri( 

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

118 ) 

119 } 

120 

121 context["post_url"] = format_url_with_params( 

122 reverse_lazy(rights.COMMENT_POST_URL), post_query_params 

123 ) 

124 # Deep copy the comment data 

125 initial_data = dict(comment) 

126 initial_data["content"] = initial_data["sanitized_html"] 

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

128 

129 else: 

130 moderate_form = DetailedCommentStatusForm( 

131 initial={ 

132 "editorial_team_comment": comment["editorial_team_comment"], 

133 "article_author_comment": comment["article_author_comment"], 

134 "hide_author_name": comment["hide_author_name"], 

135 } 

136 ) 

137 context["comment_status_validated"] = STATUS_VALIDATED 

138 context["comment_status_rejected"] = STATUS_REJECTED 

139 if rights.comment_can_delete(comment): 

140 context["delete_form"] = BaseCommentStatusForm( 

141 initial={"status": STATUS_SOFT_DELETED} 

142 ) 

143 

144 comment_status = comment.get("status") 

145 if comment_status == STATUS_SUBMITTED: 

146 if rights.comment_can_edit(comment): 

147 context["show_edit"] = True 

148 else: 

149 context["display_moderated_by"] = True 

150 if comment_status == STATUS_VALIDATED: 150 ↛ 153line 150 didn't jump to line 153, because the condition on line 150 was never false

151 context["show_context"] = True 

152 

153 context["moderate_form"] = moderate_form 

154 

155 return context 

156 

157 

158class CommentChangeStatusView(AbstractCommentRightsMixin, View): 

159 """ 

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

161 

162 Used by: 

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

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

165 """ 

166 

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

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

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

170 response = HttpResponseRedirect(redirect_url) 

171 

172 if request.path.endswith("/light/"): 172 ↛ 175line 172 didn't jump to line 175, because the condition on line 172 was never false

173 status_form = BaseCommentStatusForm(request.POST) 

174 else: 

175 status_form = DetailedCommentStatusForm(request.POST) 

176 

177 if not status_form.is_valid(): 177 ↛ 178line 177 didn't jump to line 178, because the condition on line 177 was never true

178 for error in format_form_errors(status_form): 

179 messages.error(request, error) 

180 

181 return response 

182 

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

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

185 query_params = rights.comment_rights_query_params() 

186 query_params[PARAM_DASHBOARD] = PARAM_BOOLEAN_VALUE 

187 

188 comment_id = kwargs["pk"] 

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

190 if error: 

191 return response 

192 

193 status = status_form.cleaned_data["status"] 

194 if status == STATUS_SOFT_DELETED: 194 ↛ 204line 194 didn't jump to line 204, because the condition on line 194 was never false

195 if not rights.comment_can_delete(initial_comment): 195 ↛ 196line 195 didn't jump to line 196, because the condition on line 195 was never true

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

197 return response 

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

199 # exist anymore if the deletion succeeds. 

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

201 

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

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

204 elif not rights.comment_can_moderate(initial_comment): 

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

206 return response 

207 

208 # Update the comment status. 

209 error, comment = make_api_request( 

210 "PUT", 

211 comments_server_url(query_params, item_id=comment_id), 

212 request_for_message=request, 

213 auth=comments_credentials(), 

214 json=status_form.cleaned_data, 

215 ) 

216 

217 if error: 

218 return response 

219 

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

221 

222 # Post status update processing (e-mails) 

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

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

225 # Only if the status has been changed 

226 if status in [STATUS_VALIDATED, STATUS_REJECTED] and initial_comment["status"] != status: 226 ↛ 227line 226 didn't jump to line 227, because the condition on line 226 was never true

227 format_comment(comment) 

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

229 article_name = article.title_tex if article else "" 

230 context_data = { 

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

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

233 "article_name": article_name, 

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

235 "email_signature": "The editorial team", 

236 } 

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

238 if status == STATUS_VALIDATED: 

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

240 send_email_from_template( 

241 "mail/comment_validated.html", 

242 context_data, 

243 subject, 

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

245 from_collection=comment["site_name"], 

246 ) 

247 

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

249 # has not been sent yet. 

250 if ( 

251 app_settings.EMAIL_AUTHOR 

252 and article 

253 and initial_comment["validation_email_sent"] is False 

254 ): 

255 author_data = [] 

256 author_contributions = article.get_author_contributions() 

257 for contribution in author_contributions: 

258 if contribution.corresponding and contribution.email: 

259 author_data.append(contribution) 

260 for author in author_data: 

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

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

263 send_email_from_template( 

264 "mail/comment_new_article_author.html", 

265 context_data, 

266 subject, 

267 to=[author.email], 

268 from_collection=comment["site_name"], 

269 ) 

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

271 elif status == STATUS_REJECTED: 

272 subject = f"{subject_prefix} Comment rejected" 

273 send_email_from_template( 

274 "mail/comment_rejected.html", 

275 context_data, 

276 subject, 

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

278 from_collection=comment["site_name"], 

279 ) 

280 return response