Skip to content

Commit 960a5ba

Browse files
ajrbyersmauromsl
authored andcommitted
feature: openlibhums#3255 move preprint comment moderation to repository managers
1 parent 3316a03 commit 960a5ba

17 files changed

Lines changed: 591 additions & 142 deletions

src/events/logic.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,10 @@ class Events:
257257
# raised when a new comment is submitted for a preprint
258258
ON_PREPRINT_COMMENT = "on_preprint_comment"
259259

260+
# kwargs: request, preprint, comment
261+
# raised when a preprint comment is approved and made public
262+
ON_PREPRINT_COMMENT_PUBLISHED = "on_preprint_comment_published"
263+
260264
# kwargs: request, pending_update, action, reason (optional)
261265
# raised when an PreprintVersion is approved or declined
262266
ON_PREPRINT_VERSION_UPDATE = "on_preprint_version_update"

src/events/registration.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,10 @@
230230
event_logic.Events.register_for_event(
231231
event_logic.Events.ON_PREPRINT_COMMENT, transactional_emails.preprint_comment
232232
)
233+
event_logic.Events.register_for_event(
234+
event_logic.Events.ON_PREPRINT_COMMENT_PUBLISHED,
235+
transactional_emails.preprint_comment_published,
236+
)
233237
event_logic.Events.register_for_event(
234238
event_logic.Events.ON_PREPRINT_VERSION_UPDATE,
235239
transactional_emails.preprint_version_update,

src/repository/forms.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,8 @@ class Meta:
568568
"accept_version",
569569
"decline_version",
570570
"new_comment",
571+
"comment_published",
572+
"comment_approved",
571573
"review_invitation",
572574
"manager_review_status_change",
573575
"reviewer_review_status_change",
@@ -581,6 +583,8 @@ class Meta:
581583
"accept_version": TinyMCE,
582584
"decline_version": TinyMCE,
583585
"new_comment": TinyMCE,
586+
"comment_published": TinyMCE,
587+
"comment_approved": TinyMCE,
584588
"review_invitation": TinyMCE,
585589
"manager_review_status_change": TinyMCE,
586590
"reviewer_review_status_change": TinyMCE,

src/repository/logic.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -133,24 +133,25 @@ def comment_manager_post(request, preprint):
133133
)
134134

135135
if "comment_public" in request.POST:
136+
was_public = comment.is_public
136137
comment.toggle_public()
138+
if not was_public and comment.is_public:
139+
event_logic.Events.raise_event(
140+
event_logic.Events.ON_PREPRINT_COMMENT_PUBLISHED,
141+
request=request,
142+
preprint=comment.preprint,
143+
comment=comment,
144+
)
137145
elif "comment_reviewed" in request.POST:
138146
comment.mark_reviewed()
139147

140148
if "comment_delete" in request.POST:
141-
if request.user in request.repository.managers.all():
142-
comment.delete()
143-
messages.add_message(
144-
request,
145-
messages.SUCCESS,
146-
"Comment deleted",
147-
)
148-
else:
149-
messages.add_message(
150-
request,
151-
messages.WARNING,
152-
"You do not have permission to delete this comment.",
153-
)
149+
comment.delete()
150+
messages.add_message(
151+
request,
152+
messages.SUCCESS,
153+
"Comment deleted",
154+
)
154155

155156

156157
# TODO: Update this implementation
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import json
2+
import os
3+
4+
from django.conf import settings
5+
from django.db import migrations
6+
import core.model_utils
7+
8+
9+
def get_repository_settings():
10+
settings_path = os.path.join(
11+
settings.BASE_DIR,
12+
"utils/install/repository_settings.json",
13+
)
14+
with open(settings_path) as f:
15+
return json.load(f)[0]
16+
17+
18+
def set_comment_email_defaults(apps, schema_editor):
19+
defaults = get_repository_settings()
20+
Repository = apps.get_model("repository", "Repository")
21+
Repository.objects.all().update(
22+
new_comment=defaults["new_comment"],
23+
comment_published=defaults["comment_published"],
24+
comment_approved=defaults["comment_approved"],
25+
)
26+
27+
28+
class Migration(migrations.Migration):
29+
dependencies = [
30+
("repository", "0047_remove_preprintauthor_affiliation_and_more"),
31+
]
32+
33+
operations = [
34+
migrations.AddField(
35+
model_name="repository",
36+
name="comment_published",
37+
field=core.model_utils.JanewayBleachField(blank=True, null=True),
38+
),
39+
migrations.AddField(
40+
model_name="historicalrepository",
41+
name="comment_published",
42+
field=core.model_utils.JanewayBleachField(blank=True, null=True),
43+
),
44+
migrations.AddField(
45+
model_name="repository",
46+
name="comment_approved",
47+
field=core.model_utils.JanewayBleachField(blank=True, null=True),
48+
),
49+
migrations.AddField(
50+
model_name="historicalrepository",
51+
name="comment_approved",
52+
field=core.model_utils.JanewayBleachField(blank=True, null=True),
53+
),
54+
migrations.RunPython(
55+
set_comment_email_defaults,
56+
reverse_code=migrations.RunPython.noop,
57+
),
58+
]

src/repository/models.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,25 @@ class Repository(model_utils.AbstractSiteModel):
198198
default=True,
199199
help_text="Enable to display the invited comments interface.",
200200
)
201-
new_comment = model_utils.JanewayBleachField(blank=True, null=True)
201+
new_comment = model_utils.JanewayBleachField(
202+
blank=True,
203+
null=True,
204+
help_text="Sent to repository managers when a new comment is submitted "
205+
"and awaiting moderation. "
206+
"Available variables: {{ preprint.title }}, {{ manager.full_name }}, {{ url }}.",
207+
)
208+
comment_published = model_utils.JanewayBleachField(
209+
blank=True,
210+
null=True,
211+
help_text="Sent to the submission author when a comment on their work is approved. "
212+
"Available variables: {{ preprint.title }}, {{ preprint.owner.full_name }}, {{ url }}.",
213+
)
214+
comment_approved = model_utils.JanewayBleachField(
215+
blank=True,
216+
null=True,
217+
help_text="Sent to the commenter when their comment is approved. "
218+
"Available variables: {{ preprint.title }}, {{ comment.author.full_name }}, {{ url }}.",
219+
)
202220
review_invitation = model_utils.JanewayBleachField(blank=True, null=True)
203221
review_helper = model_utils.JanewayBleachField(blank=True, null=True)
204222
manager_review_status_change = model_utils.JanewayBleachField(blank=True, null=True)
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
__copyright__ = "Copyright 2017 Birkbeck, University of London"
2+
__author__ = "Birkbeck Centre for Technology and Publishing"
3+
__license__ = "AGPL v3"
4+
__maintainer__ = "Birkbeck Centre for Technology and Publishing"
5+
6+
from django.core import mail
7+
from django.test import TestCase, override_settings
8+
from django.urls import reverse
9+
from django.utils import timezone
10+
11+
from press import models as press_models
12+
from repository import models as repo_models
13+
from utils.testing import helpers
14+
15+
16+
@override_settings(
17+
CAPTCHA_TYPE=None,
18+
URL_CONFIG="domain",
19+
)
20+
class CommentModerationTests(TestCase):
21+
@classmethod
22+
def setUpTestData(cls):
23+
cls.press = press_models.Press.objects.create(
24+
name="Test Press",
25+
domain="testserver",
26+
)
27+
cls.manager = helpers.create_user("manager@example.com")
28+
cls.manager.is_active = True
29+
cls.manager.save()
30+
31+
cls.author = helpers.create_user("author@example.com")
32+
cls.author.is_active = True
33+
cls.author.save()
34+
35+
cls.commenter = helpers.create_user("commenter@example.com")
36+
cls.commenter.is_active = True
37+
cls.commenter.save()
38+
39+
cls.repository, cls.subject = helpers.create_repository(
40+
cls.press,
41+
[cls.manager],
42+
[],
43+
domain="repo.example.com",
44+
)
45+
cls.repository.enable_comments = True
46+
cls.repository.new_comment = (
47+
"New comment on {{ preprint.title }} for {{ manager.full_name }}"
48+
)
49+
cls.repository.comment_published = "Comment published on {{ preprint.title }}"
50+
cls.repository.comment_approved = (
51+
"Your comment on {{ preprint.title }} was approved"
52+
)
53+
cls.repository.save()
54+
55+
cls.preprint = helpers.create_preprint(
56+
repository=cls.repository,
57+
author=cls.author,
58+
subject=cls.subject,
59+
)
60+
cls.preprint.date_published = timezone.now()
61+
cls.preprint.save()
62+
63+
def _make_comment(self, is_reviewed=False, is_public=False):
64+
return repo_models.Comment.objects.create(
65+
author=self.commenter,
66+
preprint=self.preprint,
67+
body="A test comment.",
68+
is_reviewed=is_reviewed,
69+
is_public=is_public,
70+
)
71+
72+
def test_new_comment_notifies_managers_not_owner(self):
73+
self.client.force_login(self.commenter)
74+
self.client.post(
75+
reverse("repository_preprint", kwargs={"preprint_id": self.preprint.pk}),
76+
{"body": "A new comment."},
77+
SERVER_NAME=self.repository.domain,
78+
)
79+
recipients = [m.to[0] for m in mail.outbox]
80+
self.assertIn(self.manager.email, recipients)
81+
self.assertNotIn(self.author.email, recipients)
82+
83+
def test_comment_approval_notifies_owner_and_commenter(self):
84+
comment = self._make_comment()
85+
mail.outbox.clear()
86+
self.client.force_login(self.manager)
87+
self.client.post(
88+
reverse("repository_manager_comment_list"),
89+
{
90+
"preprint_id": self.preprint.pk,
91+
"comment_public": comment.pk,
92+
},
93+
SERVER_NAME=self.repository.domain,
94+
)
95+
recipients = [m.to[0] for m in mail.outbox]
96+
self.assertIn(self.author.email, recipients)
97+
self.assertIn(self.commenter.email, recipients)
98+
99+
def test_comment_approval_does_not_double_notify_owner_who_is_commenter(self):
100+
comment = repo_models.Comment.objects.create(
101+
author=self.author,
102+
preprint=self.preprint,
103+
body="Owner commenting on own submission.",
104+
)
105+
mail.outbox.clear()
106+
self.client.force_login(self.manager)
107+
self.client.post(
108+
reverse("repository_manager_comment_list"),
109+
{
110+
"preprint_id": self.preprint.pk,
111+
"comment_public": comment.pk,
112+
},
113+
SERVER_NAME=self.repository.domain,
114+
)
115+
owner_emails = [m for m in mail.outbox if self.author.email in m.to]
116+
self.assertEqual(len(owner_emails), 1)
117+
118+
def test_making_comment_private_does_not_send_notification(self):
119+
comment = self._make_comment(is_reviewed=True, is_public=True)
120+
mail.outbox.clear()
121+
self.client.force_login(self.manager)
122+
self.client.post(
123+
reverse("repository_manager_comment_list"),
124+
{
125+
"preprint_id": self.preprint.pk,
126+
"comment_public": comment.pk,
127+
},
128+
SERVER_NAME=self.repository.domain,
129+
)
130+
self.assertEqual(len(mail.outbox), 0)
131+
132+
def test_unapproved_comment_not_visible_on_public_page(self):
133+
self._make_comment(is_reviewed=False, is_public=False)
134+
response = self.client.get(
135+
reverse("repository_preprint", kwargs={"preprint_id": self.preprint.pk}),
136+
SERVER_NAME=self.repository.domain,
137+
)
138+
self.assertNotContains(response, "A test comment.")
139+
140+
def test_approved_comment_visible_on_public_page(self):
141+
self._make_comment(is_reviewed=True, is_public=True)
142+
response = self.client.get(
143+
reverse("repository_preprint", kwargs={"preprint_id": self.preprint.pk}),
144+
SERVER_NAME=self.repository.domain,
145+
)
146+
self.assertContains(response, "A test comment.")
147+
148+
def test_manager_can_access_comment_list(self):
149+
self.client.force_login(self.manager)
150+
response = self.client.get(
151+
reverse("repository_manager_comment_list"),
152+
SERVER_NAME=self.repository.domain,
153+
)
154+
self.assertEqual(response.status_code, 200)
155+
156+
def test_non_manager_cannot_access_comment_list(self):
157+
self.client.force_login(self.commenter)
158+
response = self.client.get(
159+
reverse("repository_manager_comment_list"),
160+
SERVER_NAME=self.repository.domain,
161+
)
162+
self.assertEqual(response.status_code, 403)
163+
164+
def test_pending_comments_appear_in_queue(self):
165+
comment = self._make_comment(is_reviewed=False)
166+
self.client.force_login(self.manager)
167+
response = self.client.get(
168+
reverse("repository_manager_comment_list"),
169+
SERVER_NAME=self.repository.domain,
170+
)
171+
self.assertIn(comment, response.context["comments"])
172+
173+
def test_reviewed_comments_not_in_pending_queue(self):
174+
comment = self._make_comment(is_reviewed=True, is_public=True)
175+
self.client.force_login(self.manager)
176+
response = self.client.get(
177+
reverse("repository_manager_comment_list"),
178+
SERVER_NAME=self.repository.domain,
179+
)
180+
self.assertNotIn(comment, response.context["comments"])
181+
182+
def test_reviewed_comments_appear_in_reviewed_view(self):
183+
comment = self._make_comment(is_reviewed=True, is_public=True)
184+
self.client.force_login(self.manager)
185+
response = self.client.get(
186+
reverse("repository_manager_comment_list_reviewed"),
187+
SERVER_NAME=self.repository.domain,
188+
)
189+
self.assertIn(comment, response.context["comments"])
190+
191+
def test_filtered_view_scopes_to_preprint(self):
192+
other_author = helpers.create_user("other@example.com")
193+
other_author.is_active = True
194+
other_author.save()
195+
other_preprint = helpers.create_preprint(
196+
repository=self.repository,
197+
author=other_author,
198+
subject=self.subject,
199+
title="Another Preprint",
200+
)
201+
comment_this = self._make_comment()
202+
comment_other = repo_models.Comment.objects.create(
203+
author=self.commenter,
204+
preprint=other_preprint,
205+
body="Comment on other preprint.",
206+
)
207+
self.client.force_login(self.manager)
208+
response = self.client.get(
209+
reverse(
210+
"repository_manager_comment_list_filtered",
211+
kwargs={"preprint_id": self.preprint.pk},
212+
),
213+
SERVER_NAME=self.repository.domain,
214+
)
215+
self.assertIn(comment_this, response.context["comments"])
216+
self.assertNotIn(comment_other, response.context["comments"])

0 commit comments

Comments
 (0)