Coverage for apps/comments_views/core/views.py: 74%
146 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-05-19 19:20 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2024-05-19 19:20 +0000
1from typing import Any
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
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
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
34class CommentDashboardListView(AbstractCommentRightsMixin, TemplateView):
35 """
36 View for displaying the lists of pending and processed comments.
37 """
39 template_name = "dashboard/comment_list.html"
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)
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
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 )
60 if error:
61 return context
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)
74 context["display_moderators"] = (
75 len(rights.get_user_admin_collections() + rights.get_user_staff_collections()) > 0
76 ) or self.request.user.is_superuser
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
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
88class CommentDashboardDetailsView(AbstractCommentRightsMixin, TemplateView):
89 template_name = "dashboard/comment_details.html"
91 def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
92 context = super().get_context_data(**kwargs)
93 pk = kwargs["pk"]
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)
101 if error:
102 return context
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
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 }
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)
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 )
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
153 context["moderate_form"] = moderate_form
155 return context
158class CommentChangeStatusView(AbstractCommentRightsMixin, View):
159 """
160 View used to modify the status of an existing comment.
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 """
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)
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)
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)
181 return response
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
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
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"))
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
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 )
217 if error:
218 return response
220 messages.success(request, f"The comment has been successfully {status}.")
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 )
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