Commit 5d8d1619 by christopher lee

Added unanswered/unread query params for thread in discussion api

Discusion API now takes a view parameter when getting a thread list.
This view parameter only takes "unread" or "unanswered" as possible
values.
parent 81e677b7
...@@ -231,7 +231,15 @@ def get_course_topics(request, course_key): ...@@ -231,7 +231,15 @@ def get_course_topics(request, course_key):
} }
def get_thread_list(request, course_key, page, page_size, topic_id_list=None, text_search=None, following=False): def get_thread_list(
request,
course_key,
page,
page_size,
topic_id_list=None,
text_search=None,
following=False,
view=None):
""" """
Return the list of all discussion threads pertaining to the given course Return the list of all discussion threads pertaining to the given course
...@@ -244,6 +252,7 @@ def get_thread_list(request, course_key, page, page_size, topic_id_list=None, te ...@@ -244,6 +252,7 @@ def get_thread_list(request, course_key, page, page_size, topic_id_list=None, te
topic_id_list: The list of topic_ids to get the discussion threads for topic_id_list: The list of topic_ids to get the discussion threads for
text_search A text search query string to match text_search A text search query string to match
following: If true, retrieve only threads the requester is following following: If true, retrieve only threads the requester is following
view: filters for either "unread" or "unanswered" threads
Note that topic_id_list, text_search, and following are mutually exclusive. Note that topic_id_list, text_search, and following are mutually exclusive.
...@@ -254,6 +263,7 @@ def get_thread_list(request, course_key, page, page_size, topic_id_list=None, te ...@@ -254,6 +263,7 @@ def get_thread_list(request, course_key, page, page_size, topic_id_list=None, te
Raises: Raises:
ValidationError: if an invalid value is passed for a field
ValueError: if more than one of the mutually exclusive parameters is ValueError: if more than one of the mutually exclusive parameters is
provided provided
Http404: if the requesting user does not have access to the requested course Http404: if the requesting user does not have access to the requested course
...@@ -266,6 +276,7 @@ def get_thread_list(request, course_key, page, page_size, topic_id_list=None, te ...@@ -266,6 +276,7 @@ def get_thread_list(request, course_key, page, page_size, topic_id_list=None, te
course = _get_course_or_404(course_key, request.user) course = _get_course_or_404(course_key, request.user)
context = get_context(course, request) context = get_context(course, request)
query_params = { query_params = {
"user_id": unicode(request.user.id),
"group_id": ( "group_id": (
None if context["is_requester_privileged"] else None if context["is_requester_privileged"] else
get_cohort_id(request.user, course.id) get_cohort_id(request.user, course.id)
...@@ -276,7 +287,17 @@ def get_thread_list(request, course_key, page, page_size, topic_id_list=None, te ...@@ -276,7 +287,17 @@ def get_thread_list(request, course_key, page, page_size, topic_id_list=None, te
"per_page": page_size, "per_page": page_size,
"text": text_search, "text": text_search,
} }
text_search_rewrite = None text_search_rewrite = None
if view:
if view in ["unread", "unanswered"]:
query_params[view] = "true"
else:
ValidationError({
"view": ["Invalid value. '{}' must be 'unread' or 'unanswered'".format(view)]
})
if following: if following:
threads, result_page, num_pages = context["cc_requester"].subscribed_threads(query_params) threads, result_page, num_pages = context["cc_requester"].subscribed_threads(query_params)
else: else:
......
...@@ -5,6 +5,7 @@ from django.core.exceptions import ValidationError ...@@ -5,6 +5,7 @@ from django.core.exceptions import ValidationError
from django.forms import ( from django.forms import (
BooleanField, BooleanField,
CharField, CharField,
ChoiceField,
Field, Field,
Form, Form,
IntegerField, IntegerField,
...@@ -51,6 +52,10 @@ class ThreadListGetForm(_PaginationForm): ...@@ -51,6 +52,10 @@ class ThreadListGetForm(_PaginationForm):
topic_id = TopicIdField(required=False) topic_id = TopicIdField(required=False)
text_search = CharField(required=False) text_search = CharField(required=False)
following = NullBooleanField(required=False) following = NullBooleanField(required=False)
view = ChoiceField(
choices=[(choice, choice) for choice in ["unread", "unanswered"]],
required=False
)
def clean_course_id(self): def clean_course_id(self):
"""Validate course_id""" """Validate course_id"""
......
...@@ -185,6 +185,8 @@ class ThreadSerializer(_ContentSerializer): ...@@ -185,6 +185,8 @@ class ThreadSerializer(_ContentSerializer):
comment_list_url = serializers.SerializerMethodField("get_comment_list_url") comment_list_url = serializers.SerializerMethodField("get_comment_list_url")
endorsed_comment_list_url = serializers.SerializerMethodField("get_endorsed_comment_list_url") endorsed_comment_list_url = serializers.SerializerMethodField("get_endorsed_comment_list_url")
non_endorsed_comment_list_url = serializers.SerializerMethodField("get_non_endorsed_comment_list_url") non_endorsed_comment_list_url = serializers.SerializerMethodField("get_non_endorsed_comment_list_url")
read = serializers.BooleanField(read_only=True)
has_endorsed = serializers.BooleanField(read_only=True, source="endorsed")
non_updatable_fields = NON_UPDATABLE_THREAD_FIELDS non_updatable_fields = NON_UPDATABLE_THREAD_FIELDS
......
...@@ -521,6 +521,7 @@ class GetThreadListTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest ...@@ -521,6 +521,7 @@ class GetThreadListTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest
self.get_thread_list([], topic_id_list=["topic_x", "topic_meow"]) self.get_thread_list([], topic_id_list=["topic_x", "topic_meow"])
self.assertEqual(urlparse(httpretty.last_request().path).path, "/api/v1/threads") self.assertEqual(urlparse(httpretty.last_request().path).path, "/api/v1/threads")
self.assert_last_query_params({ self.assert_last_query_params({
"user_id": [unicode(self.user.id)],
"course_id": [unicode(self.course.id)], "course_id": [unicode(self.course.id)],
"sort_key": ["date"], "sort_key": ["date"],
"sort_order": ["desc"], "sort_order": ["desc"],
...@@ -533,6 +534,7 @@ class GetThreadListTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest ...@@ -533,6 +534,7 @@ class GetThreadListTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest
def test_basic_query_params(self): def test_basic_query_params(self):
self.get_thread_list([], page=6, page_size=14) self.get_thread_list([], page=6, page_size=14)
self.assert_last_query_params({ self.assert_last_query_params({
"user_id": [unicode(self.user.id)],
"course_id": [unicode(self.course.id)], "course_id": [unicode(self.course.id)],
"sort_key": ["date"], "sort_key": ["date"],
"sort_order": ["desc"], "sort_order": ["desc"],
...@@ -564,6 +566,8 @@ class GetThreadListTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest ...@@ -564,6 +566,8 @@ class GetThreadListTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest
"votes": {"up_count": 4}, "votes": {"up_count": 4},
"comments_count": 5, "comments_count": 5,
"unread_comments_count": 3, "unread_comments_count": 3,
"endorsed": True,
"read": True,
}, },
{ {
"type": "thread", "type": "thread",
...@@ -586,6 +590,8 @@ class GetThreadListTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest ...@@ -586,6 +590,8 @@ class GetThreadListTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest
"votes": {"up_count": 9}, "votes": {"up_count": 9},
"comments_count": 18, "comments_count": 18,
"unread_comments_count": 0, "unread_comments_count": 0,
"endorsed": False,
"read": False,
}, },
] ]
expected_threads = [ expected_threads = [
...@@ -615,6 +621,8 @@ class GetThreadListTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest ...@@ -615,6 +621,8 @@ class GetThreadListTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest
"endorsed_comment_list_url": None, "endorsed_comment_list_url": None,
"non_endorsed_comment_list_url": None, "non_endorsed_comment_list_url": None,
"editable_fields": ["abuse_flagged", "following", "voted"], "editable_fields": ["abuse_flagged", "following", "voted"],
"has_endorsed": True,
"read": True,
}, },
{ {
"id": "test_thread_id_1", "id": "test_thread_id_1",
...@@ -646,6 +654,8 @@ class GetThreadListTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest ...@@ -646,6 +654,8 @@ class GetThreadListTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest
"http://testserver/api/discussion/v1/comments/?thread_id=test_thread_id_1&endorsed=False" "http://testserver/api/discussion/v1/comments/?thread_id=test_thread_id_1&endorsed=False"
), ),
"editable_fields": ["abuse_flagged", "following", "voted"], "editable_fields": ["abuse_flagged", "following", "voted"],
"has_endorsed": False,
"read": False,
}, },
] ]
self.assertEqual( self.assertEqual(
...@@ -735,6 +745,7 @@ class GetThreadListTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest ...@@ -735,6 +745,7 @@ class GetThreadListTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest
} }
) )
self.assert_last_query_params({ self.assert_last_query_params({
"user_id": [unicode(self.user.id)],
"course_id": [unicode(self.course.id)], "course_id": [unicode(self.course.id)],
"sort_key": ["date"], "sort_key": ["date"],
"sort_order": ["desc"], "sort_order": ["desc"],
...@@ -762,6 +773,7 @@ class GetThreadListTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest ...@@ -762,6 +773,7 @@ class GetThreadListTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest
"/api/v1/users/{}/subscribed_threads".format(self.user.id) "/api/v1/users/{}/subscribed_threads".format(self.user.id)
) )
self.assert_last_query_params({ self.assert_last_query_params({
"user_id": [unicode(self.user.id)],
"course_id": [unicode(self.course.id)], "course_id": [unicode(self.course.id)],
"sort_key": ["date"], "sort_key": ["date"],
"sort_order": ["desc"], "sort_order": ["desc"],
...@@ -769,6 +781,35 @@ class GetThreadListTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest ...@@ -769,6 +781,35 @@ class GetThreadListTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest
"per_page": ["11"], "per_page": ["11"],
}) })
@ddt.data("unanswered", "unread")
def test_view_query(self, query):
self.register_get_threads_response([], page=1, num_pages=1)
result = get_thread_list(
self.request,
self.course.id,
page=1,
page_size=11,
view=query,
)
self.assertEqual(
result,
{"results": [], "next": None, "previous": None, "text_search_rewrite": None}
)
self.assertEqual(
urlparse(httpretty.last_request().path).path,
"/api/v1/threads"
)
self.assert_last_query_params({
"user_id": [unicode(self.user.id)],
"course_id": [unicode(self.course.id)],
"sort_key": ["date"],
"sort_order": ["desc"],
"page": ["1"],
"per_page": ["11"],
"recursive": ["False"],
query: ["true"],
})
@ddt.ddt @ddt.ddt
class GetCommentListTest(CommentsServiceMockMixin, ModuleStoreTestCase): class GetCommentListTest(CommentsServiceMockMixin, ModuleStoreTestCase):
...@@ -1240,6 +1281,8 @@ class CreateThreadTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTestC ...@@ -1240,6 +1281,8 @@ class CreateThreadTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTestC
"endorsed_comment_list_url": None, "endorsed_comment_list_url": None,
"non_endorsed_comment_list_url": None, "non_endorsed_comment_list_url": None,
"editable_fields": ["abuse_flagged", "following", "raw_body", "title", "topic_id", "type", "voted"], "editable_fields": ["abuse_flagged", "following", "raw_body", "title", "topic_id", "type", "voted"],
'read': False,
'has_endorsed': False
} }
self.assertEqual(actual, expected) self.assertEqual(actual, expected)
self.assertEqual( self.assertEqual(
...@@ -1745,6 +1788,8 @@ class UpdateThreadTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTestC ...@@ -1745,6 +1788,8 @@ class UpdateThreadTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTestC
"endorsed_comment_list_url": None, "endorsed_comment_list_url": None,
"non_endorsed_comment_list_url": None, "non_endorsed_comment_list_url": None,
"editable_fields": ["abuse_flagged", "following", "raw_body", "title", "topic_id", "type", "voted"], "editable_fields": ["abuse_flagged", "following", "raw_body", "title", "topic_id", "type", "voted"],
'read': False,
'has_endorsed': False,
} }
self.assertEqual(actual, expected) self.assertEqual(actual, expected)
self.assertEqual( self.assertEqual(
......
...@@ -95,6 +95,7 @@ class ThreadListGetFormTest(FormTestMixin, PaginationTestMixin, TestCase): ...@@ -95,6 +95,7 @@ class ThreadListGetFormTest(FormTestMixin, PaginationTestMixin, TestCase):
"topic_id": [], "topic_id": [],
"text_search": "", "text_search": "",
"following": None, "following": None,
"view": ""
} }
) )
...@@ -142,6 +143,10 @@ class ThreadListGetFormTest(FormTestMixin, PaginationTestMixin, TestCase): ...@@ -142,6 +143,10 @@ class ThreadListGetFormTest(FormTestMixin, PaginationTestMixin, TestCase):
"The following query parameters are mutually exclusive: topic_id, text_search, following" "The following query parameters are mutually exclusive: topic_id, text_search, following"
) )
def test_invalid_view_choice(self):
self.form_data["view"] = "not_a_valid_choice"
self.assert_error("view", "Select a valid choice. not_a_valid_choice is not one of the available choices.")
class CommentListGetFormTest(FormTestMixin, PaginationTestMixin, TestCase): class CommentListGetFormTest(FormTestMixin, PaginationTestMixin, TestCase):
"""Tests for CommentListGetForm""" """Tests for CommentListGetForm"""
......
...@@ -138,6 +138,8 @@ class ThreadSerializerSerializationTest(SerializerTestMixin, ModuleStoreTestCase ...@@ -138,6 +138,8 @@ class ThreadSerializerSerializationTest(SerializerTestMixin, ModuleStoreTestCase
"course_id": unicode(self.course.id), "course_id": unicode(self.course.id),
"user_id": str(self.author.id), "user_id": str(self.author.id),
"username": self.author.username, "username": self.author.username,
"read": True,
"endorsed": True
} }
merged_overrides.update(overrides) merged_overrides.update(overrides)
return make_minimal_cs_thread(merged_overrides) return make_minimal_cs_thread(merged_overrides)
...@@ -171,6 +173,8 @@ class ThreadSerializerSerializationTest(SerializerTestMixin, ModuleStoreTestCase ...@@ -171,6 +173,8 @@ class ThreadSerializerSerializationTest(SerializerTestMixin, ModuleStoreTestCase
"votes": {"up_count": 4}, "votes": {"up_count": 4},
"comments_count": 5, "comments_count": 5,
"unread_comments_count": 3, "unread_comments_count": 3,
"read": False,
"endorsed": False
} }
expected = { expected = {
"id": "test_thread", "id": "test_thread",
...@@ -198,6 +202,8 @@ class ThreadSerializerSerializationTest(SerializerTestMixin, ModuleStoreTestCase ...@@ -198,6 +202,8 @@ class ThreadSerializerSerializationTest(SerializerTestMixin, ModuleStoreTestCase
"endorsed_comment_list_url": None, "endorsed_comment_list_url": None,
"non_endorsed_comment_list_url": None, "non_endorsed_comment_list_url": None,
"editable_fields": ["abuse_flagged", "following", "voted"], "editable_fields": ["abuse_flagged", "following", "voted"],
"read": False,
"has_endorsed": False
} }
self.assertEqual(self.serialize(thread), expected) self.assertEqual(self.serialize(thread), expected)
...@@ -424,6 +430,8 @@ class ThreadSerializerDeserializationTest(CommentsServiceMockMixin, UrlResetMixi ...@@ -424,6 +430,8 @@ class ThreadSerializerDeserializationTest(CommentsServiceMockMixin, UrlResetMixi
"title": "Original Title", "title": "Original Title",
"body": "Original body", "body": "Original body",
"user_id": str(self.user.id), "user_id": str(self.user.id),
"read": "False",
"endorsed": "False"
})) }))
def save_and_reserialize(self, data, instance=None): def save_and_reserialize(self, data, instance=None):
......
...@@ -5,6 +5,7 @@ from datetime import datetime ...@@ -5,6 +5,7 @@ from datetime import datetime
import json import json
from urlparse import urlparse from urlparse import urlparse
import ddt
import httpretty import httpretty
import mock import mock
from pytz import UTC from pytz import UTC
...@@ -134,6 +135,7 @@ class CourseTopicsViewTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): ...@@ -134,6 +135,7 @@ class CourseTopicsViewTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
) )
@ddt.ddt
@httpretty.activate @httpretty.activate
class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
"""Tests for ThreadViewSet list""" """Tests for ThreadViewSet list"""
...@@ -181,6 +183,8 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): ...@@ -181,6 +183,8 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
"votes": {"up_count": 4}, "votes": {"up_count": 4},
"comments_count": 5, "comments_count": 5,
"unread_comments_count": 3, "unread_comments_count": 3,
"read": False,
"endorsed": False
}] }]
expected_threads = [{ expected_threads = [{
"id": "test_thread", "id": "test_thread",
...@@ -208,6 +212,8 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): ...@@ -208,6 +212,8 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
"endorsed_comment_list_url": None, "endorsed_comment_list_url": None,
"non_endorsed_comment_list_url": None, "non_endorsed_comment_list_url": None,
"editable_fields": ["abuse_flagged", "following", "voted"], "editable_fields": ["abuse_flagged", "following", "voted"],
"read": False,
"has_endorsed": False
}] }]
self.register_get_threads_response(source_threads, page=1, num_pages=2) self.register_get_threads_response(source_threads, page=1, num_pages=2)
response = self.client.get(self.url, {"course_id": unicode(self.course.id)}) response = self.client.get(self.url, {"course_id": unicode(self.course.id)})
...@@ -222,6 +228,7 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): ...@@ -222,6 +228,7 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
} }
) )
self.assert_last_query_params({ self.assert_last_query_params({
"user_id": [unicode(self.user.id)],
"course_id": [unicode(self.course.id)], "course_id": [unicode(self.course.id)],
"sort_key": ["date"], "sort_key": ["date"],
"sort_order": ["desc"], "sort_order": ["desc"],
...@@ -230,6 +237,29 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): ...@@ -230,6 +237,29 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
"recursive": ["False"], "recursive": ["False"],
}) })
@ddt.data("unread", "unanswered")
def test_view_query(self, query):
threads = [make_minimal_cs_thread()]
self.register_get_user_response(self.user)
self.register_get_threads_response(threads, page=1, num_pages=1)
self.client.get(
self.url,
{
"course_id": unicode(self.course.id),
"view": query,
}
)
self.assert_last_query_params({
"user_id": [unicode(self.user.id)],
"course_id": [unicode(self.course.id)],
"sort_key": ["date"],
"sort_order": ["desc"],
"recursive": ["False"],
"page": ["1"],
"per_page": ["10"],
query: ["true"],
})
def test_pagination(self): def test_pagination(self):
self.register_get_user_response(self.user) self.register_get_user_response(self.user)
self.register_get_threads_response([], page=1, num_pages=1) self.register_get_threads_response([], page=1, num_pages=1)
...@@ -243,6 +273,7 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): ...@@ -243,6 +273,7 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
{"developer_message": "Not found."} {"developer_message": "Not found."}
) )
self.assert_last_query_params({ self.assert_last_query_params({
"user_id": [unicode(self.user.id)],
"course_id": [unicode(self.course.id)], "course_id": [unicode(self.course.id)],
"sort_key": ["date"], "sort_key": ["date"],
"sort_order": ["desc"], "sort_order": ["desc"],
...@@ -264,6 +295,7 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): ...@@ -264,6 +295,7 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
{"results": [], "next": None, "previous": None, "text_search_rewrite": None} {"results": [], "next": None, "previous": None, "text_search_rewrite": None}
) )
self.assert_last_query_params({ self.assert_last_query_params({
"user_id": [unicode(self.user.id)],
"course_id": [unicode(self.course.id)], "course_id": [unicode(self.course.id)],
"sort_key": ["date"], "sort_key": ["date"],
"sort_order": ["desc"], "sort_order": ["desc"],
...@@ -344,6 +376,8 @@ class ThreadViewSetCreateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): ...@@ -344,6 +376,8 @@ class ThreadViewSetCreateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
"endorsed_comment_list_url": None, "endorsed_comment_list_url": None,
"non_endorsed_comment_list_url": None, "non_endorsed_comment_list_url": None,
"editable_fields": ["abuse_flagged", "following", "raw_body", "title", "topic_id", "type", "voted"], "editable_fields": ["abuse_flagged", "following", "raw_body", "title", "topic_id", "type", "voted"],
"read": False,
"has_endorsed": False
} }
response = self.client.post( response = self.client.post(
self.url, self.url,
...@@ -435,6 +469,8 @@ class ThreadViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTest ...@@ -435,6 +469,8 @@ class ThreadViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTest
"endorsed_comment_list_url": None, "endorsed_comment_list_url": None,
"non_endorsed_comment_list_url": None, "non_endorsed_comment_list_url": None,
"editable_fields": ["abuse_flagged", "following", "raw_body", "title", "topic_id", "type", "voted"], "editable_fields": ["abuse_flagged", "following", "raw_body", "title", "topic_id", "type", "voted"],
"read": False,
"has_endorsed": False
} }
response = self.client.patch( # pylint: disable=no-member response = self.client.patch( # pylint: disable=no-member
self.url, self.url,
......
...@@ -330,6 +330,8 @@ def make_minimal_cs_thread(overrides=None): ...@@ -330,6 +330,8 @@ def make_minimal_cs_thread(overrides=None):
"unread_comments_count": 0, "unread_comments_count": 0,
"children": [], "children": [],
"resp_total": 0, "resp_total": 0,
"read": False,
"endorsed": False,
} }
ret.update(overrides or {}) ret.update(overrides or {})
return ret return ret
......
...@@ -117,7 +117,7 @@ class ThreadViewSet(_ViewMixin, DeveloperErrorViewMixin, ViewSet): ...@@ -117,7 +117,7 @@ class ThreadViewSet(_ViewMixin, DeveloperErrorViewMixin, ViewSet):
"topic_id": "quux", "topic_id": "quux",
"type": "discussion", "type": "discussion",
"title": "Title text", "title": "Title text",
"body": "Body text" "raw_body": "Body text"
} }
PATCH /api/discussion/v1/threads/thread_id PATCH /api/discussion/v1/threads/thread_id
...@@ -144,6 +144,10 @@ class ThreadViewSet(_ViewMixin, DeveloperErrorViewMixin, ViewSet): ...@@ -144,6 +144,10 @@ class ThreadViewSet(_ViewMixin, DeveloperErrorViewMixin, ViewSet):
* following: If true, retrieve only threads the requesting user is * following: If true, retrieve only threads the requesting user is
following following
* view: "unread" for threads the requesting user has not read, or
"unanswered" for question threads with no marked answer. Only one
can be selected.
The topic_id, text_search, and following parameters are mutually The topic_id, text_search, and following parameters are mutually
exclusive (i.e. only one may be specified in a request) exclusive (i.e. only one may be specified in a request)
...@@ -212,6 +216,10 @@ class ThreadViewSet(_ViewMixin, DeveloperErrorViewMixin, ViewSet): ...@@ -212,6 +216,10 @@ class ThreadViewSet(_ViewMixin, DeveloperErrorViewMixin, ViewSet):
* editable_fields: The fields that the requesting user is allowed to * editable_fields: The fields that the requesting user is allowed to
modify with a PATCH request modify with a PATCH request
* read: Boolean indicating whether the user has read this thread
* has_endorsed: Boolean indicating whether this thread has been answered
**DELETE response values: **DELETE response values:
No content is returned for a DELETE request No content is returned for a DELETE request
...@@ -236,6 +244,7 @@ class ThreadViewSet(_ViewMixin, DeveloperErrorViewMixin, ViewSet): ...@@ -236,6 +244,7 @@ class ThreadViewSet(_ViewMixin, DeveloperErrorViewMixin, ViewSet):
form.cleaned_data["topic_id"], form.cleaned_data["topic_id"],
form.cleaned_data["text_search"], form.cleaned_data["text_search"],
form.cleaned_data["following"], form.cleaned_data["following"],
form.cleaned_data["view"],
) )
) )
......
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