Commit 52a3306e by Greg Price Committed by christopher lee

Add abuse flagging to discussion API

Flagging/unflagging is done by issuing a PATCH request on a thread or
comment endpoint with the "abuse_flagged" field set.
parent 9a94c601
......@@ -368,6 +368,11 @@ def _do_extra_actions(api_content, cc_content, request_fields, actions_form, con
context["cc_requester"].follow(cc_content)
else:
context["cc_requester"].unfollow(cc_content)
elif field == "abuse_flagged":
if form_value:
cc_content.flagAbuse(context["cc_requester"], cc_content)
else:
cc_content.unFlagAbuse(context["cc_requester"], cc_content, removeAll=False)
else:
assert field == "voted"
if form_value:
......
......@@ -80,6 +80,7 @@ class ThreadActionsForm(Form):
"""
following = BooleanField(required=False)
voted = BooleanField(required=False)
abuse_flagged = BooleanField(required=False)
class CommentListGetForm(_PaginationForm):
......@@ -98,3 +99,4 @@ class CommentActionsForm(Form):
interactions with the comments service.
"""
voted = BooleanField(required=False)
abuse_flagged = BooleanField(required=False)
......@@ -23,7 +23,7 @@ def get_editable_fields(cc_content, context):
Return the set of fields that the requester can edit on the given content
"""
# Shared fields
ret = {"voted"}
ret = {"abuse_flagged", "voted"}
if _is_author_or_privileged(cc_content, context):
ret |= {"raw_body"}
......
......@@ -611,7 +611,7 @@ class GetThreadListTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest
"comment_list_url": "http://testserver/api/discussion/v1/comments/?thread_id=test_thread_id_0",
"endorsed_comment_list_url": None,
"non_endorsed_comment_list_url": None,
"editable_fields": ["following", "voted"],
"editable_fields": ["abuse_flagged", "following", "voted"],
},
{
"id": "test_thread_id_1",
......@@ -642,7 +642,7 @@ class GetThreadListTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest
"non_endorsed_comment_list_url": (
"http://testserver/api/discussion/v1/comments/?thread_id=test_thread_id_1&endorsed=False"
),
"editable_fields": ["following", "voted"],
"editable_fields": ["abuse_flagged", "following", "voted"],
},
]
self.assertEqual(
......@@ -970,7 +970,7 @@ class GetCommentListTest(CommentsServiceMockMixin, ModuleStoreTestCase):
"voted": False,
"vote_count": 4,
"children": [],
"editable_fields": ["voted"],
"editable_fields": ["abuse_flagged", "voted"],
},
{
"id": "test_comment_2",
......@@ -990,7 +990,7 @@ class GetCommentListTest(CommentsServiceMockMixin, ModuleStoreTestCase):
"voted": False,
"vote_count": 7,
"children": [],
"editable_fields": ["voted"],
"editable_fields": ["abuse_flagged", "voted"],
},
]
actual_comments = self.get_comment_list(
......@@ -1210,7 +1210,7 @@ class CreateThreadTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTestC
"comment_list_url": "http://testserver/api/discussion/v1/comments/?thread_id=test_id",
"endorsed_comment_list_url": None,
"non_endorsed_comment_list_url": None,
"editable_fields": ["following", "raw_body", "title", "topic_id", "type", "voted"],
"editable_fields": ["abuse_flagged", "following", "raw_body", "title", "topic_id", "type", "voted"],
}
self.assertEqual(actual, expected)
self.assertEqual(
......@@ -1278,6 +1278,18 @@ class CreateThreadTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTestC
{"user_id": [str(self.user.id)], "value": ["up"]}
)
def test_abuse_flagged(self):
self.register_post_thread_response({"id": "test_id"})
self.register_thread_flag_response("test_id")
data = self.minimal_data.copy()
data["abuse_flagged"] = "True"
result = create_thread(self.request, data)
self.assertEqual(result["abuse_flagged"], True)
cs_request = httpretty.last_request()
self.assertEqual(urlparse(cs_request.path).path, "/api/v1/threads/test_id/abuse_flag")
self.assertEqual(cs_request.method, "PUT")
self.assertEqual(cs_request.parsed_body, {"user_id": [str(self.user.id)]})
def test_course_id_missing(self):
with self.assertRaises(ValidationError) as assertion:
create_thread(self.request, {})
......@@ -1376,7 +1388,7 @@ class CreateCommentTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest
"voted": False,
"vote_count": 0,
"children": [],
"editable_fields": ["raw_body", "voted"]
"editable_fields": ["abuse_flagged", "raw_body", "voted"]
}
self.assertEqual(actual, expected)
expected_url = (
......@@ -1431,6 +1443,18 @@ class CreateCommentTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest
{"user_id": [str(self.user.id)], "value": ["up"]}
)
def test_abuse_flagged(self):
self.register_post_comment_response({"id": "test_comment"}, "test_thread")
self.register_comment_flag_response("test_comment")
data = self.minimal_data.copy()
data["abuse_flagged"] = "True"
result = create_comment(self.request, data)
self.assertEqual(result["abuse_flagged"], True)
cs_request = httpretty.last_request()
self.assertEqual(urlparse(cs_request.path).path, "/api/v1/comments/test_comment/abuse_flag")
self.assertEqual(cs_request.method, "PUT")
self.assertEqual(cs_request.parsed_body, {"user_id": [str(self.user.id)]})
def test_thread_id_missing(self):
with self.assertRaises(ValidationError) as assertion:
create_comment(self.request, {})
......@@ -1590,7 +1614,7 @@ class UpdateThreadTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTestC
"comment_list_url": "http://testserver/api/discussion/v1/comments/?thread_id=test_thread",
"endorsed_comment_list_url": None,
"non_endorsed_comment_list_url": None,
"editable_fields": ["following", "raw_body", "title", "topic_id", "type", "voted"],
"editable_fields": ["abuse_flagged", "following", "raw_body", "title", "topic_id", "type", "voted"],
}
self.assertEqual(actual, expected)
self.assertEqual(
......@@ -1770,6 +1794,41 @@ class UpdateThreadTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTestC
expected_request_data["value"] = ["up"]
self.assertEqual(actual_request_data, expected_request_data)
@ddt.data(*itertools.product([True, False], [True, False]))
@ddt.unpack
def test_abuse_flagged(self, old_flagged, new_flagged):
"""
Test attempts to edit the "abuse_flagged" field.
old_flagged indicates whether the thread should be flagged at the start
of the test. new_flagged indicates the value for the "abuse_flagged"
field in the update. If old_flagged and new_flagged are the same, no
update should be made. Otherwise, a PUT should be made to the flag or
or unflag endpoint according to the new_flagged value.
"""
self.register_get_user_response(self.user)
self.register_thread_flag_response("test_thread")
self.register_thread({"abuse_flaggers": [str(self.user.id)] if old_flagged else []})
data = {"abuse_flagged": new_flagged}
result = update_thread(self.request, "test_thread", data)
self.assertEqual(result["abuse_flagged"], new_flagged)
last_request_path = urlparse(httpretty.last_request().path).path
flag_url = "/api/v1/threads/test_thread/abuse_flag"
unflag_url = "/api/v1/threads/test_thread/abuse_unflag"
if old_flagged == new_flagged:
self.assertNotEqual(last_request_path, flag_url)
self.assertNotEqual(last_request_path, unflag_url)
else:
self.assertEqual(
last_request_path,
flag_url if new_flagged else unflag_url
)
self.assertEqual(httpretty.last_request().method, "PUT")
self.assertEqual(
httpretty.last_request().parsed_body,
{"user_id": [str(self.user.id)]}
)
def test_invalid_field(self):
self.register_thread()
with self.assertRaises(ValidationError) as assertion:
......@@ -1852,7 +1911,7 @@ class UpdateCommentTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest
"voted": False,
"vote_count": 0,
"children": [],
"editable_fields": ["raw_body", "voted"]
"editable_fields": ["abuse_flagged", "raw_body", "voted"]
}
self.assertEqual(actual, expected)
self.assertEqual(
......@@ -2038,6 +2097,41 @@ class UpdateCommentTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest
expected_request_data["value"] = ["up"]
self.assertEqual(actual_request_data, expected_request_data)
@ddt.data(*itertools.product([True, False], [True, False]))
@ddt.unpack
def test_abuse_flagged(self, old_flagged, new_flagged):
"""
Test attempts to edit the "abuse_flagged" field.
old_flagged indicates whether the comment should be flagged at the start
of the test. new_flagged indicates the value for the "abuse_flagged"
field in the update. If old_flagged and new_flagged are the same, no
update should be made. Otherwise, a PUT should be made to the flag or
or unflag endpoint according to the new_flagged value.
"""
self.register_get_user_response(self.user)
self.register_comment_flag_response("test_comment")
self.register_comment({"abuse_flaggers": [str(self.user.id)] if old_flagged else []})
data = {"abuse_flagged": new_flagged}
result = update_comment(self.request, "test_comment", data)
self.assertEqual(result["abuse_flagged"], new_flagged)
last_request_path = urlparse(httpretty.last_request().path).path
flag_url = "/api/v1/comments/test_comment/abuse_flag"
unflag_url = "/api/v1/comments/test_comment/abuse_unflag"
if old_flagged == new_flagged:
self.assertNotEqual(last_request_path, flag_url)
self.assertNotEqual(last_request_path, unflag_url)
else:
self.assertEqual(
last_request_path,
flag_url if new_flagged else unflag_url
)
self.assertEqual(httpretty.last_request().method, "PUT")
self.assertEqual(
httpretty.last_request().parsed_body,
{"user_id": [str(self.user.id)]}
)
@ddt.ddt
class DeleteThreadTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTestCase):
......
......@@ -30,7 +30,7 @@ class GetEditableFieldsTest(TestCase):
thread = Thread(user_id="5" if is_author else "6", type="thread")
context = _get_context(requester_id="5", is_requester_privileged=is_privileged)
actual = get_editable_fields(thread, context)
expected = {"following", "voted"}
expected = {"abuse_flagged", "following", "voted"}
if is_author or is_privileged:
expected |= {"topic_id", "type", "title", "raw_body"}
self.assertEqual(actual, expected)
......@@ -45,7 +45,7 @@ class GetEditableFieldsTest(TestCase):
thread=Thread(user_id="5" if is_thread_author else "6", thread_type=thread_type)
)
actual = get_editable_fields(comment, context)
expected = {"voted"}
expected = {"abuse_flagged", "voted"}
if is_author or is_privileged:
expected |= {"raw_body"}
if (is_thread_author and thread_type == "question") or is_privileged:
......
......@@ -197,7 +197,7 @@ class ThreadSerializerSerializationTest(SerializerTestMixin, ModuleStoreTestCase
"comment_list_url": "http://testserver/api/discussion/v1/comments/?thread_id=test_thread",
"endorsed_comment_list_url": None,
"non_endorsed_comment_list_url": None,
"editable_fields": ["following", "voted"],
"editable_fields": ["abuse_flagged", "following", "voted"],
}
self.assertEqual(self.serialize(thread), expected)
......@@ -305,7 +305,7 @@ class CommentSerializerTest(SerializerTestMixin, ModuleStoreTestCase):
"voted": False,
"vote_count": 4,
"children": [],
"editable_fields": ["voted"],
"editable_fields": ["abuse_flagged", "voted"],
}
self.assertEqual(self.serialize(comment), expected)
......
......@@ -204,7 +204,7 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
"comment_list_url": "http://testserver/api/discussion/v1/comments/?thread_id=test_thread",
"endorsed_comment_list_url": None,
"non_endorsed_comment_list_url": None,
"editable_fields": ["following", "voted"],
"editable_fields": ["abuse_flagged", "following", "voted"],
}]
self.register_get_threads_response(source_threads, page=1, num_pages=2)
response = self.client.get(self.url, {"course_id": unicode(self.course.id)})
......@@ -318,7 +318,7 @@ class ThreadViewSetCreateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
"comment_list_url": "http://testserver/api/discussion/v1/comments/?thread_id=test_thread",
"endorsed_comment_list_url": None,
"non_endorsed_comment_list_url": None,
"editable_fields": ["following", "raw_body", "title", "topic_id", "type", "voted"],
"editable_fields": ["abuse_flagged", "following", "raw_body", "title", "topic_id", "type", "voted"],
}
response = self.client.post(
self.url,
......@@ -409,7 +409,7 @@ class ThreadViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTest
"comment_list_url": "http://testserver/api/discussion/v1/comments/?thread_id=test_thread",
"endorsed_comment_list_url": None,
"non_endorsed_comment_list_url": None,
"editable_fields": ["following", "raw_body", "title", "topic_id", "type", "voted"],
"editable_fields": ["abuse_flagged", "following", "raw_body", "title", "topic_id", "type", "voted"],
}
response = self.client.patch( # pylint: disable=no-member
self.url,
......@@ -553,7 +553,7 @@ class CommentViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
"voted": True,
"vote_count": 4,
"children": [],
"editable_fields": ["voted"],
"editable_fields": ["abuse_flagged", "voted"],
}]
self.register_get_thread_response({
"id": self.thread_id,
......@@ -707,7 +707,7 @@ class CommentViewSetCreateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
"voted": False,
"vote_count": 0,
"children": [],
"editable_fields": ["raw_body", "voted"],
"editable_fields": ["abuse_flagged", "raw_body", "voted"],
}
response = self.client.post(
self.url,
......@@ -791,7 +791,7 @@ class CommentViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTes
"voted": False,
"vote_count": 0,
"children": [],
"editable_fields": ["raw_body", "voted"],
"editable_fields": ["abuse_flagged", "raw_body", "voted"],
}
response = self.client.patch( # pylint: disable=no-member
self.url,
......
......@@ -230,6 +230,28 @@ class CommentsServiceMockMixin(object):
status=200
)
def register_flag_response(self, content_type, content_id):
"""Register a mock response for PUT on the CS flag endpoints"""
for path in ["abuse_flag", "abuse_unflag"]:
httpretty.register_uri(
"PUT",
"http://localhost:4567/api/v1/{content_type}s/{content_id}/{path}".format(
content_type=content_type,
content_id=content_id,
path=path
),
body=json.dumps({}), # body is unused
status=200
)
def register_thread_flag_response(self, thread_id):
"""Register a mock response for PUT on the CS thread flag endpoints"""
self.register_flag_response("thread", thread_id)
def register_comment_flag_response(self, comment_id):
"""Register a mock response for PUT on the CS comment flag endpoints"""
self.register_flag_response("comment", comment_id)
def register_delete_thread_response(self, thread_id):
"""
Register a mock response for DELETE on the CS thread instance endpoint
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment