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:
Maxim Beder
2021-11-02 07:25:40 +01:00
committed by GitHub
parent 60da5db4b6
commit eabd6e8019
9 changed files with 274 additions and 40 deletions

View File

@@ -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,
}

View File

@@ -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)

View File

@@ -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()

View File

@@ -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,
}

View File

@@ -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

View File

@@ -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(

View File

@@ -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)

View File

@@ -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,

View File

@@ -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