feat: add anonymous settings to discussions api (#28981)
If course allows anonymous posts, and user is an author of a post, add `anonymous` to posts' editable fields. If course allows anonymous to peers posts, and user is an author of a post, add `anonymous_to_peers` to posts' editable fields. Course endpoint response to get request will now include course's `allow_anonymous` and `allow_anonymous_to_peers` settings. Added `anonymous` and `anonymous_to_peers` fields to the content serializer, from which ThreadSerializer and CommentSerializer inherit. Both fields have a default value of False, because there are cases where course configuration doesn't allow them to be initialized (if a course doesn't allow anonymous posts, the fields won't be included in the list of initializable/editable fields).
This commit is contained in:
@@ -260,7 +260,9 @@ def get_course(request, course_key):
|
||||
"following_thread_list_url": get_thread_list_url(request, course_key, following=True),
|
||||
"topics_url": request.build_absolute_uri(
|
||||
reverse("course_topics", kwargs={"course_id": course_key})
|
||||
)
|
||||
),
|
||||
"allow_anonymous": course.allow_anonymous,
|
||||
"allow_anonymous_to_peers": course.allow_anonymous_to_peers,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -105,7 +105,9 @@ def get_editable_fields(cc_content: Union[Thread, Comment], context: Dict) -> Se
|
||||
is_comment and
|
||||
(is_privileged or
|
||||
(_is_author(context["thread"], context) and context["thread"]["thread_type"] == "question"))
|
||||
)
|
||||
),
|
||||
"anonymous": is_author and context["course"].allow_anonymous,
|
||||
"anonymous_to_peers": is_author and context["course"].allow_anonymous_to_peers,
|
||||
})
|
||||
# Return only editable fields
|
||||
return _filter_fields(editable_fields)
|
||||
|
||||
@@ -122,6 +122,8 @@ class _ContentSerializer(serializers.Serializer):
|
||||
vote_count = serializers.SerializerMethodField()
|
||||
editable_fields = serializers.SerializerMethodField()
|
||||
can_delete = serializers.SerializerMethodField()
|
||||
anonymous = serializers.BooleanField(default=False)
|
||||
anonymous_to_peers = serializers.BooleanField(default=False)
|
||||
|
||||
non_updatable_fields = set()
|
||||
|
||||
|
||||
@@ -176,7 +176,9 @@ class GetCourseTest(ForumsEnableMixin, UrlResetMixin, SharedModuleStoreTestCase)
|
||||
'thread_list_url': 'http://testserver/api/discussion/v1/threads/?course_id=x%2Fy%2Fz',
|
||||
'following_thread_list_url':
|
||||
'http://testserver/api/discussion/v1/threads/?course_id=x%2Fy%2Fz&following=True',
|
||||
'topics_url': 'http://testserver/api/discussion/v1/course_topics/x/y/z'
|
||||
'topics_url': 'http://testserver/api/discussion/v1/course_topics/x/y/z',
|
||||
'allow_anonymous': True,
|
||||
'allow_anonymous_to_peers': False,
|
||||
}
|
||||
|
||||
|
||||
@@ -1379,6 +1381,8 @@ class GetCommentListTest(ForumsEnableMixin, CommentsServiceMockMixin, SharedModu
|
||||
"child_count": 0,
|
||||
"children": [],
|
||||
"can_delete": False,
|
||||
"anonymous": False,
|
||||
"anonymous_to_peers": False,
|
||||
},
|
||||
{
|
||||
"id": "test_comment_2",
|
||||
@@ -1402,6 +1406,8 @@ class GetCommentListTest(ForumsEnableMixin, CommentsServiceMockMixin, SharedModu
|
||||
"child_count": 0,
|
||||
"children": [],
|
||||
"can_delete": False,
|
||||
"anonymous": True,
|
||||
"anonymous_to_peers": False,
|
||||
},
|
||||
]
|
||||
actual_comments = self.get_comment_list(
|
||||
@@ -1659,7 +1665,9 @@ class CreateThreadTest(
|
||||
'thread_type': ['discussion'],
|
||||
'title': ['Test Title'],
|
||||
'body': ['Test body'],
|
||||
'user_id': [str(self.user.id)]
|
||||
'user_id': [str(self.user.id)],
|
||||
'anonymous': ['False'],
|
||||
'anonymous_to_peers': ['False'],
|
||||
}
|
||||
event_name, event_data = mock_emit.call_args[0]
|
||||
assert event_name == 'edx.forum.thread.created'
|
||||
@@ -1716,8 +1724,8 @@ class CreateThreadTest(
|
||||
"comment_list_url": "http://testserver/api/discussion/v1/comments/?thread_id=test_id",
|
||||
"read": True,
|
||||
"editable_fields": [
|
||||
"abuse_flagged", "closed", "following", "pinned", "raw_body", "read", "title", "topic_id", "type",
|
||||
"voted"
|
||||
"abuse_flagged", "anonymous", "closed", "following", "pinned",
|
||||
"raw_body", "read", "title", "topic_id", "type", "voted"
|
||||
],
|
||||
})
|
||||
assert actual == expected
|
||||
@@ -1730,6 +1738,8 @@ class CreateThreadTest(
|
||||
"title": ["Test Title"],
|
||||
"body": ["Test body"],
|
||||
"user_id": [str(self.user.id)],
|
||||
"anonymous": ["False"],
|
||||
"anonymous_to_peers": ["False"],
|
||||
}
|
||||
)
|
||||
event_name, event_data = mock_emit.call_args[0]
|
||||
@@ -1999,9 +2009,11 @@ class CreateCommentTest(
|
||||
"voted": False,
|
||||
"vote_count": 0,
|
||||
"children": [],
|
||||
"editable_fields": ["abuse_flagged", "raw_body", "voted"],
|
||||
"editable_fields": ["abuse_flagged", "anonymous", "raw_body", "voted"],
|
||||
"child_count": 0,
|
||||
"can_delete": True,
|
||||
"anonymous": False,
|
||||
"anonymous_to_peers": False,
|
||||
}
|
||||
assert actual == expected
|
||||
expected_url = (
|
||||
@@ -2012,7 +2024,9 @@ class CreateCommentTest(
|
||||
assert httpretty.last_request().parsed_body == { # lint-amnesty, pylint: disable=no-member
|
||||
'course_id': [str(self.course.id)],
|
||||
'body': ['Test body'],
|
||||
'user_id': [str(self.user.id)]
|
||||
'user_id': [str(self.user.id)],
|
||||
'anonymous': ['False'],
|
||||
'anonymous_to_peers': ['False'],
|
||||
}
|
||||
expected_event_name = (
|
||||
"edx.forum.comment.created" if parent_id else
|
||||
@@ -2081,9 +2095,11 @@ class CreateCommentTest(
|
||||
"voted": False,
|
||||
"vote_count": 0,
|
||||
"children": [],
|
||||
"editable_fields": ["abuse_flagged", "endorsed", "raw_body", "voted"],
|
||||
"editable_fields": ["abuse_flagged", "anonymous", "endorsed", "raw_body", "voted"],
|
||||
"child_count": 0,
|
||||
"can_delete": True,
|
||||
"anonymous": False,
|
||||
"anonymous_to_peers": False,
|
||||
}
|
||||
assert actual == expected
|
||||
expected_url = (
|
||||
@@ -2094,7 +2110,9 @@ class CreateCommentTest(
|
||||
assert httpretty.last_request().parsed_body == { # pylint: disable=no-member
|
||||
"course_id": [str(self.course.id)],
|
||||
"body": ["Test body"],
|
||||
"user_id": [str(self.user.id)]
|
||||
"user_id": [str(self.user.id)],
|
||||
"anonymous": ['False'],
|
||||
"anonymous_to_peers": ['False'],
|
||||
}
|
||||
|
||||
expected_event_name = (
|
||||
@@ -2698,6 +2716,8 @@ class UpdateCommentTest(
|
||||
with self.assert_signal_sent(api, 'comment_edited', sender=None, user=self.user, exclude_args=('post',)):
|
||||
actual = update_comment(self.request, "test_comment", {"raw_body": "Edited body"})
|
||||
expected = {
|
||||
"anonymous": False,
|
||||
"anonymous_to_peers": False,
|
||||
"id": "test_comment",
|
||||
"thread_id": "test_thread",
|
||||
"parent_id": parent_id,
|
||||
@@ -2716,7 +2736,7 @@ class UpdateCommentTest(
|
||||
"voted": False,
|
||||
"vote_count": 0,
|
||||
"children": [],
|
||||
"editable_fields": ["abuse_flagged", "raw_body", "voted"],
|
||||
"editable_fields": ["abuse_flagged", "anonymous", "raw_body", "voted"],
|
||||
"child_count": 0,
|
||||
"can_delete": True,
|
||||
}
|
||||
|
||||
@@ -20,12 +20,23 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
|
||||
def _get_context(requester_id, is_requester_privileged, is_cohorted=False, thread=None):
|
||||
def _get_context(
|
||||
requester_id,
|
||||
is_requester_privileged,
|
||||
is_cohorted=False,
|
||||
thread=None,
|
||||
allow_anonymous=True,
|
||||
allow_anonymous_to_peers=False,
|
||||
):
|
||||
"""Return a context suitable for testing the permissions module"""
|
||||
return {
|
||||
"cc_requester": User(id=requester_id),
|
||||
"is_requester_privileged": is_requester_privileged,
|
||||
"course": CourseFactory(cohort_config={"cohorted": is_cohorted}),
|
||||
"course": CourseFactory(
|
||||
cohort_config={"cohorted": is_cohorted},
|
||||
allow_anonymous=allow_anonymous,
|
||||
allow_anonymous_to_peers=allow_anonymous_to_peers,
|
||||
),
|
||||
"discussion_division_enabled": is_cohorted,
|
||||
"thread": thread,
|
||||
}
|
||||
@@ -34,13 +45,21 @@ def _get_context(requester_id, is_requester_privileged, is_cohorted=False, threa
|
||||
@ddt.ddt
|
||||
class GetInitializableFieldsTest(ModuleStoreTestCase):
|
||||
"""Tests for get_*_initializable_fields"""
|
||||
@ddt.data(*itertools.product([True, False], [True, False]))
|
||||
@ddt.data(*itertools.product(*[[True, False] for _ in range(4)]))
|
||||
@ddt.unpack
|
||||
def test_thread(self, is_privileged, is_cohorted):
|
||||
def test_thread(
|
||||
self,
|
||||
is_privileged,
|
||||
is_cohorted,
|
||||
allow_anonymous,
|
||||
allow_anonymous_to_peers,
|
||||
):
|
||||
context = _get_context(
|
||||
requester_id="5",
|
||||
is_requester_privileged=is_privileged,
|
||||
is_cohorted=is_cohorted
|
||||
is_cohorted=is_cohorted,
|
||||
allow_anonymous=allow_anonymous,
|
||||
allow_anonymous_to_peers=allow_anonymous_to_peers,
|
||||
)
|
||||
actual = get_initializable_thread_fields(context)
|
||||
expected = {
|
||||
@@ -50,6 +69,10 @@ class GetInitializableFieldsTest(ModuleStoreTestCase):
|
||||
expected |= {"closed", "pinned"}
|
||||
if is_privileged and is_cohorted:
|
||||
expected |= {"group_id"}
|
||||
if allow_anonymous:
|
||||
expected |= {"anonymous"}
|
||||
if allow_anonymous_to_peers:
|
||||
expected |= {"anonymous_to_peers"}
|
||||
assert actual == expected
|
||||
|
||||
@ddt.data(*itertools.product([True, False], ["question", "discussion"], [True, False]))
|
||||
@@ -62,7 +85,7 @@ class GetInitializableFieldsTest(ModuleStoreTestCase):
|
||||
)
|
||||
actual = get_initializable_comment_fields(context)
|
||||
expected = {
|
||||
"abuse_flagged", "parent_id", "raw_body", "thread_id", "voted"
|
||||
"anonymous", "abuse_flagged", "parent_id", "raw_body", "thread_id", "voted"
|
||||
}
|
||||
if (is_thread_author and thread_type == "question") or is_privileged:
|
||||
expected |= {"endorsed"}
|
||||
@@ -72,14 +95,23 @@ class GetInitializableFieldsTest(ModuleStoreTestCase):
|
||||
@ddt.ddt
|
||||
class GetEditableFieldsTest(ModuleStoreTestCase):
|
||||
"""Tests for get_editable_fields"""
|
||||
@ddt.data(*itertools.product([True, False], [True, False], [True, False]))
|
||||
@ddt.data(*itertools.product(*[[True, False] for _ in range(5)]))
|
||||
@ddt.unpack
|
||||
def test_thread(self, is_author, is_privileged, is_cohorted):
|
||||
def test_thread(
|
||||
self,
|
||||
is_author,
|
||||
is_privileged,
|
||||
is_cohorted,
|
||||
allow_anonymous,
|
||||
allow_anonymous_to_peers
|
||||
):
|
||||
thread = Thread(user_id="5" if is_author else "6", type="thread")
|
||||
context = _get_context(
|
||||
requester_id="5",
|
||||
is_requester_privileged=is_privileged,
|
||||
is_cohorted=is_cohorted
|
||||
is_cohorted=is_cohorted,
|
||||
allow_anonymous=allow_anonymous,
|
||||
allow_anonymous_to_peers=allow_anonymous_to_peers,
|
||||
)
|
||||
actual = get_editable_fields(thread, context)
|
||||
expected = {"abuse_flagged", "following", "read", "voted"}
|
||||
@@ -89,16 +121,30 @@ class GetEditableFieldsTest(ModuleStoreTestCase):
|
||||
expected |= {"topic_id", "type", "title", "raw_body"}
|
||||
if is_privileged and is_cohorted:
|
||||
expected |= {"group_id"}
|
||||
if is_author and allow_anonymous:
|
||||
expected |= {"anonymous"}
|
||||
if is_author and allow_anonymous_to_peers:
|
||||
expected |= {"anonymous_to_peers"}
|
||||
assert actual == expected
|
||||
|
||||
@ddt.data(*itertools.product([True, False], [True, False], ["question", "discussion"], [True, False]))
|
||||
@ddt.data(*itertools.product(*[[True, False] for _ in range(5)], ["question", "discussion"]))
|
||||
@ddt.unpack
|
||||
def test_comment(self, is_author, is_thread_author, thread_type, is_privileged):
|
||||
def test_comment(
|
||||
self,
|
||||
is_author,
|
||||
is_thread_author,
|
||||
is_privileged,
|
||||
allow_anonymous,
|
||||
allow_anonymous_to_peers,
|
||||
thread_type
|
||||
):
|
||||
comment = Comment(user_id="5" if is_author else "6", type="comment")
|
||||
context = _get_context(
|
||||
requester_id="5",
|
||||
is_requester_privileged=is_privileged,
|
||||
thread=Thread(user_id="5" if is_thread_author else "6", thread_type=thread_type)
|
||||
thread=Thread(user_id="5" if is_thread_author else "6", thread_type=thread_type),
|
||||
allow_anonymous=allow_anonymous,
|
||||
allow_anonymous_to_peers=allow_anonymous_to_peers,
|
||||
)
|
||||
actual = get_editable_fields(comment, context)
|
||||
expected = {"abuse_flagged", "voted"}
|
||||
@@ -106,6 +152,10 @@ class GetEditableFieldsTest(ModuleStoreTestCase):
|
||||
expected |= {"raw_body"}
|
||||
if (is_thread_author and thread_type == "question") or is_privileged:
|
||||
expected |= {"endorsed"}
|
||||
if is_author and allow_anonymous:
|
||||
expected |= {"anonymous"}
|
||||
if is_author and allow_anonymous_to_peers:
|
||||
expected |= {"anonymous_to_peers"}
|
||||
assert actual == expected
|
||||
|
||||
|
||||
|
||||
@@ -293,6 +293,8 @@ class CommentSerializerTest(SerializerTestMixin, SharedModuleStoreTestCase):
|
||||
"child_count": 0,
|
||||
}
|
||||
expected = {
|
||||
"anonymous": False,
|
||||
"anonymous_to_peers": False,
|
||||
"id": "test_comment",
|
||||
"thread_id": "test_thread",
|
||||
"parent_id": None,
|
||||
@@ -476,7 +478,9 @@ class ThreadSerializerDeserializationTest(
|
||||
'thread_type': ['discussion'],
|
||||
'title': ['Test Title'],
|
||||
'body': ['Test body'],
|
||||
'user_id': [str(self.user.id)]
|
||||
'user_id': [str(self.user.id)],
|
||||
'anonymous': ['False'],
|
||||
'anonymous_to_peers': ['False'],
|
||||
}
|
||||
assert saved['id'] == 'test_id'
|
||||
|
||||
@@ -492,7 +496,9 @@ class ThreadSerializerDeserializationTest(
|
||||
'title': ['Test Title'],
|
||||
'body': ['Test body'],
|
||||
'user_id': [str(self.user.id)],
|
||||
'group_id': ['42']
|
||||
'group_id': ['42'],
|
||||
'anonymous': ['False'],
|
||||
'anonymous_to_peers': ['False'],
|
||||
}
|
||||
|
||||
def test_create_missing_field(self):
|
||||
@@ -523,6 +529,28 @@ class ThreadSerializerDeserializationTest(
|
||||
serializer = ThreadSerializer(data=data)
|
||||
assert not serializer.is_valid()
|
||||
|
||||
def test_create_anonymous(self):
|
||||
"""
|
||||
Test that serializer correctly deserializes the anonymous field when
|
||||
creating a new thread.
|
||||
"""
|
||||
self.register_post_thread_response({"id": "test_id", "username": self.user.username})
|
||||
data = self.minimal_data.copy()
|
||||
data["anonymous"] = True
|
||||
self.save_and_reserialize(data)
|
||||
assert httpretty.last_request().parsed_body["anonymous"] == ['True'] # lint-amnesty, pylint: disable=no-member
|
||||
|
||||
def test_create_anonymous_to_peers(self):
|
||||
"""
|
||||
Test that serializer correctly deserializes the anonymous_to_peers field
|
||||
when creating a new thread.
|
||||
"""
|
||||
self.register_post_thread_response({"id": "test_id", "username": self.user.username})
|
||||
data = self.minimal_data.copy()
|
||||
data["anonymous_to_peers"] = True
|
||||
self.save_and_reserialize(data)
|
||||
assert httpretty.last_request().parsed_body["anonymous_to_peers"] == ['True'] # lint-amnesty, pylint: disable=no-member
|
||||
|
||||
def test_update_empty(self):
|
||||
self.register_put_thread_response(self.existing_thread.attributes)
|
||||
self.save_and_reserialize({}, self.existing_thread)
|
||||
@@ -567,6 +595,30 @@ class ThreadSerializerDeserializationTest(
|
||||
for key in data:
|
||||
assert saved[key] == data[key]
|
||||
|
||||
def test_update_anonymous(self):
|
||||
"""
|
||||
Test that serializer correctly deserializes the anonymous field when
|
||||
updating an existing thread.
|
||||
"""
|
||||
self.register_put_thread_response(self.existing_thread.attributes)
|
||||
data = {
|
||||
"anonymous": True,
|
||||
}
|
||||
self.save_and_reserialize(data, self.existing_thread)
|
||||
assert httpretty.last_request().parsed_body["anonymous"] == ['True'] # lint-amnesty, pylint: disable=no-member
|
||||
|
||||
def test_update_anonymous_to_peers(self):
|
||||
"""
|
||||
Test that serializer correctly deserializes the anonymous_to_peers
|
||||
field when updating an existing thread.
|
||||
"""
|
||||
self.register_put_thread_response(self.existing_thread.attributes)
|
||||
data = {
|
||||
"anonymous_to_peers": True,
|
||||
}
|
||||
self.save_and_reserialize(data, self.existing_thread)
|
||||
assert httpretty.last_request().parsed_body["anonymous_to_peers"] == ['True'] # lint-amnesty, pylint: disable=no-member
|
||||
|
||||
@ddt.data("", " ")
|
||||
def test_update_empty_string(self, value):
|
||||
serializer = ThreadSerializer(
|
||||
@@ -662,7 +714,9 @@ class CommentSerializerDeserializationTest(ForumsEnableMixin, CommentsServiceMoc
|
||||
assert httpretty.last_request().parsed_body == { # lint-amnesty, pylint: disable=no-member
|
||||
'course_id': [str(self.course.id)],
|
||||
'body': ['Test body'],
|
||||
'user_id': [str(self.user.id)]
|
||||
'user_id': [str(self.user.id)],
|
||||
'anonymous': ['False'],
|
||||
'anonymous_to_peers': ['False'],
|
||||
}
|
||||
assert saved['id'] == 'test_comment'
|
||||
assert saved['parent_id'] == parent_id
|
||||
@@ -682,7 +736,9 @@ class CommentSerializerDeserializationTest(ForumsEnableMixin, CommentsServiceMoc
|
||||
'course_id': [str(self.course.id)],
|
||||
'body': ['Test body'],
|
||||
'user_id': [str(self.user.id)],
|
||||
'endorsed': ['True']
|
||||
'endorsed': ['True'],
|
||||
'anonymous': ['False'],
|
||||
'anonymous_to_peers': ['False'],
|
||||
}
|
||||
|
||||
def test_create_parent_id_nonexistent(self):
|
||||
@@ -760,13 +816,37 @@ class CommentSerializerDeserializationTest(ForumsEnableMixin, CommentsServiceMoc
|
||||
'course_id': [str(self.course.id)],
|
||||
'body': ['Test body'],
|
||||
'user_id': [str(self.user.id)],
|
||||
'endorsed': ['True']
|
||||
'endorsed': ['True'],
|
||||
'anonymous': ['False'],
|
||||
'anonymous_to_peers': ['False'],
|
||||
}
|
||||
assert saved['endorsed']
|
||||
assert saved['endorsed_by'] is None
|
||||
assert saved['endorsed_by_label'] is None
|
||||
assert saved['endorsed_at'] is None
|
||||
|
||||
def test_create_anonymous(self):
|
||||
"""
|
||||
Test that serializer correctly deserializes the anonymous field when
|
||||
creating a new comment.
|
||||
"""
|
||||
self.register_post_comment_response({"username": self.user.username}, thread_id="test_thread")
|
||||
data = self.minimal_data.copy()
|
||||
data["anonymous"] = True
|
||||
self.save_and_reserialize(data)
|
||||
assert httpretty.last_request().parsed_body["anonymous"] == ['True'] # lint-amnesty, pylint: disable=no-member
|
||||
|
||||
def test_create_anonymous_to_peers(self):
|
||||
"""
|
||||
Test that serializer correctly deserializes the anonymous_to_peers
|
||||
field when creating a new comment.
|
||||
"""
|
||||
self.register_post_comment_response({"username": self.user.username}, thread_id="test_thread")
|
||||
data = self.minimal_data.copy()
|
||||
data["anonymous_to_peers"] = True
|
||||
self.save_and_reserialize(data)
|
||||
assert httpretty.last_request().parsed_body["anonymous_to_peers"] == ['True'] # lint-amnesty, pylint: disable=no-member
|
||||
|
||||
def test_update_empty(self):
|
||||
self.register_put_comment_response(self.existing_comment.attributes)
|
||||
self.save_and_reserialize({}, instance=self.existing_comment)
|
||||
@@ -813,6 +893,30 @@ class CommentSerializerDeserializationTest(ForumsEnableMixin, CommentsServiceMoc
|
||||
assert not serializer.is_valid()
|
||||
assert serializer.errors == {'raw_body': ['This field may not be blank.']}
|
||||
|
||||
def test_update_anonymous(self):
|
||||
"""
|
||||
Test that serializer correctly deserializes the anonymous field when
|
||||
updating an existing comment.
|
||||
"""
|
||||
self.register_put_comment_response(self.existing_comment.attributes)
|
||||
data = {
|
||||
"anonymous": True,
|
||||
}
|
||||
self.save_and_reserialize(data, self.existing_comment)
|
||||
assert httpretty.last_request().parsed_body["anonymous"] == ['True'] # lint-amnesty, pylint: disable=no-member
|
||||
|
||||
def test_update_anonymous_to_peers(self):
|
||||
"""
|
||||
Test that serializer correctly deserializes the anonymous_to_peers
|
||||
field when updating an existing comment.
|
||||
"""
|
||||
self.register_put_comment_response(self.existing_comment.attributes)
|
||||
data = {
|
||||
"anonymous_to_peers": True,
|
||||
}
|
||||
self.save_and_reserialize(data, self.existing_comment)
|
||||
assert httpretty.last_request().parsed_body["anonymous_to_peers"] == ['True'] # lint-amnesty, pylint: disable=no-member
|
||||
|
||||
@ddt.data("thread_id", "parent_id")
|
||||
def test_update_non_updatable(self, field):
|
||||
serializer = CommentSerializer(
|
||||
|
||||
@@ -163,6 +163,8 @@ class CourseViewTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
||||
"http://testserver/api/discussion/v1/threads/?course_id=x%2Fy%2Fz&following=True"
|
||||
),
|
||||
"topics_url": "http://testserver/api/discussion/v1/course_topics/x/y/z",
|
||||
"allow_anonymous": True,
|
||||
"allow_anonymous_to_peers": False,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -887,8 +889,10 @@ class ThreadViewSetCreateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
||||
'commentable_id': ['test_topic'],
|
||||
'thread_type': ['discussion'],
|
||||
'title': ['Test Title'],
|
||||
"body": ["# Test \n This is a very long body that will be truncated for the preview."],
|
||||
'user_id': [str(self.user.id)]
|
||||
'body': ['# Test \n This is a very long body that will be truncated for the preview.'],
|
||||
'user_id': [str(self.user.id)],
|
||||
'anonymous': ['False'],
|
||||
'anonymous_to_peers': ['False'],
|
||||
}
|
||||
|
||||
def test_error(self):
|
||||
@@ -938,7 +942,10 @@ class ThreadViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTest
|
||||
'raw_body': 'Edited body',
|
||||
'rendered_body': '<p>Edited body</p>',
|
||||
'preview_body': 'Edited body',
|
||||
'editable_fields': ['abuse_flagged', 'following', 'raw_body', 'read', 'title', 'topic_id', 'type', 'voted'],
|
||||
'editable_fields': [
|
||||
'abuse_flagged', 'anonymous', 'following', 'raw_body', 'read',
|
||||
'title', 'topic_id', 'type', 'voted'
|
||||
],
|
||||
'created_at': 'Test Created Date',
|
||||
'updated_at': 'Test Updated Date',
|
||||
'comment_count': 1,
|
||||
@@ -1018,7 +1025,10 @@ class ThreadViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTest
|
||||
assert response_data == self.expected_thread_data({
|
||||
'comment_count': 1,
|
||||
'read': True,
|
||||
'editable_fields': ['abuse_flagged', 'following', 'raw_body', 'read', 'title', 'topic_id', 'type', 'voted'],
|
||||
'editable_fields': [
|
||||
'abuse_flagged', 'anonymous', 'following', 'raw_body', 'read',
|
||||
'title', 'topic_id', 'type', 'voted'
|
||||
],
|
||||
'response_count': 2
|
||||
})
|
||||
|
||||
@@ -1145,6 +1155,8 @@ class CommentViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase, Pr
|
||||
"editable_fields": ["abuse_flagged", "voted"],
|
||||
"child_count": 0,
|
||||
"can_delete": True,
|
||||
"anonymous": False,
|
||||
"anonymous_to_peers": False,
|
||||
}
|
||||
response_data.update(overrides or {})
|
||||
return response_data
|
||||
@@ -1532,9 +1544,11 @@ class CommentViewSetCreateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
||||
"voted": False,
|
||||
"vote_count": 0,
|
||||
"children": [],
|
||||
"editable_fields": ["abuse_flagged", "raw_body", "voted"],
|
||||
"editable_fields": ["abuse_flagged", "anonymous", "raw_body", "voted"],
|
||||
"child_count": 0,
|
||||
"can_delete": True,
|
||||
"anonymous": False,
|
||||
"anonymous_to_peers": False,
|
||||
}
|
||||
response = self.client.post(
|
||||
self.url,
|
||||
@@ -1548,7 +1562,9 @@ class CommentViewSetCreateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
||||
assert httpretty.last_request().parsed_body == { # lint-amnesty, pylint: disable=no-member
|
||||
'course_id': [str(self.course.id)],
|
||||
'body': ['Test body'],
|
||||
'user_id': [str(self.user.id)]
|
||||
'user_id': [str(self.user.id)],
|
||||
'anonymous': ['False'],
|
||||
'anonymous_to_peers': ['False'],
|
||||
}
|
||||
|
||||
def test_error(self):
|
||||
@@ -1621,6 +1637,8 @@ class CommentViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTes
|
||||
"editable_fields": [],
|
||||
"child_count": 0,
|
||||
"can_delete": True,
|
||||
"anonymous": False,
|
||||
"anonymous_to_peers": False,
|
||||
}
|
||||
response_data.update(overrides or {})
|
||||
return response_data
|
||||
@@ -1635,7 +1653,7 @@ class CommentViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTes
|
||||
assert response_data == self.expected_response_data({
|
||||
'raw_body': 'Edited body',
|
||||
'rendered_body': '<p>Edited body</p>',
|
||||
'editable_fields': ['abuse_flagged', 'raw_body', 'voted'],
|
||||
'editable_fields': ['abuse_flagged', 'anonymous', 'raw_body', 'voted'],
|
||||
'created_at': 'Test Created Date',
|
||||
'updated_at': 'Test Updated Date'
|
||||
})
|
||||
@@ -1803,9 +1821,11 @@ class CommentViewSetRetrieveTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase
|
||||
"vote_count": 0,
|
||||
"abuse_flagged": False,
|
||||
"abuse_flagged_any_user": None,
|
||||
"editable_fields": ["abuse_flagged", "raw_body", "voted"],
|
||||
"editable_fields": ["abuse_flagged", "anonymous", "raw_body", "voted"],
|
||||
"child_count": 0,
|
||||
"can_delete": True,
|
||||
"anonymous": False,
|
||||
"anonymous_to_peers": False,
|
||||
}
|
||||
|
||||
response = self.client.get(self.url)
|
||||
|
||||
@@ -392,6 +392,8 @@ class CommentsServiceMockMixin:
|
||||
Returns expected thread data in API response
|
||||
"""
|
||||
response_data = {
|
||||
"anonymous": False,
|
||||
"anonymous_to_peers": False,
|
||||
"author": self.user.username,
|
||||
"author_label": None,
|
||||
"created_at": "1970-01-01T00:00:00Z",
|
||||
@@ -403,7 +405,10 @@ class CommentsServiceMockMixin:
|
||||
"abuse_flagged_count": None,
|
||||
"voted": False,
|
||||
"vote_count": 0,
|
||||
"editable_fields": ["abuse_flagged", "following", "raw_body", "read", "title", "topic_id", "type", "voted"],
|
||||
"editable_fields": [
|
||||
"abuse_flagged", "anonymous", "following", "raw_body", "read",
|
||||
"title", "topic_id", "type", "voted"
|
||||
],
|
||||
"course_id": str(self.course.id),
|
||||
"topic_id": "test_topic",
|
||||
"group_id": None,
|
||||
|
||||
@@ -87,6 +87,12 @@ class CourseView(DeveloperErrorViewMixin, APIView):
|
||||
* following_thread_list_url: thread_list_url with parameter following=True
|
||||
|
||||
* topics_url: The URL of the topic listing for the course.
|
||||
|
||||
* allow_anonymous: A boolean which indicating whether anonymous posts
|
||||
are allowed or not.
|
||||
|
||||
* allow_anonymous_to_peers: A boolean which indicating whether posts
|
||||
anonymous to peers are allowed or not.
|
||||
"""
|
||||
def get(self, request, course_id):
|
||||
"""Implements the GET method as described in the class docstring."""
|
||||
@@ -239,6 +245,12 @@ class ThreadViewSet(DeveloperErrorViewMixin, ViewSet):
|
||||
* following (optional): A boolean indicating whether the user should
|
||||
follow the thread upon its creation; defaults to false
|
||||
|
||||
* anonymous (optional): A boolean indicating whether the post is
|
||||
anonymous; defaults to false
|
||||
|
||||
* anonymous_to_peers (optional): A boolean indicating whether the post
|
||||
is anonymous to peers; defaults to false
|
||||
|
||||
**PATCH Parameters**:
|
||||
|
||||
* abuse_flagged (optional): A boolean to mark thread as abusive
|
||||
@@ -247,8 +259,8 @@ class ThreadViewSet(DeveloperErrorViewMixin, ViewSet):
|
||||
|
||||
* read (optional): A boolean to mark thread as read
|
||||
|
||||
* topic_id, type, title, and raw_body are accepted with the same meaning
|
||||
as in a POST request
|
||||
* topic_id, type, title, raw_body, anonymous, and anonymous_to_peers
|
||||
are accepted with the same meaning as in a POST request
|
||||
|
||||
If "application/merge-patch+json" is not the specified content type,
|
||||
a 415 error is returned.
|
||||
@@ -311,6 +323,11 @@ class ThreadViewSet(DeveloperErrorViewMixin, ViewSet):
|
||||
* abuse_flagged_count: The number of flags(reports) on and within the
|
||||
thread. Returns null if requesting user is not a moderator
|
||||
|
||||
* anonymous: A boolean indicating whether the post is anonymous
|
||||
|
||||
* anonymous_to_peers: A boolean indicating whether the post is
|
||||
anonymous to peers
|
||||
|
||||
**DELETE response values:
|
||||
|
||||
No content is returned for a DELETE request
|
||||
@@ -446,9 +463,16 @@ class CommentViewSet(DeveloperErrorViewMixin, ViewSet):
|
||||
|
||||
* raw_body: The comment's raw body text
|
||||
|
||||
* anonymous (optional): A boolean indicating whether the comment is
|
||||
anonymous; defaults to false
|
||||
|
||||
* anonymous_to_peers (optional): A boolean indicating whether the
|
||||
comment is anonymous to peers; defaults to false
|
||||
|
||||
**PATCH Parameters**:
|
||||
|
||||
raw_body is accepted with the same meaning as in a POST request
|
||||
* raw_body, anonymous and anonymous_to_peers are accepted with the same
|
||||
meaning as in a POST request
|
||||
|
||||
If "application/merge-patch+json" is not the specified content type,
|
||||
a 415 error is returned.
|
||||
@@ -513,6 +537,11 @@ class CommentViewSet(DeveloperErrorViewMixin, ViewSet):
|
||||
* editable_fields: The fields that the requesting user is allowed to
|
||||
modify with a PATCH request
|
||||
|
||||
* anonymous: A boolean indicating whether the comment is anonymous
|
||||
|
||||
* anonymous_to_peers: A boolean indicating whether the comment is
|
||||
anonymous to peers
|
||||
|
||||
**DELETE Response Value**
|
||||
|
||||
No content is returned for a DELETE request
|
||||
|
||||
Reference in New Issue
Block a user