Use parent_id returned from the comments service
This depends on cs_comments_service commit 0487891.
This commit is contained in:
@@ -41,7 +41,7 @@ def _get_course_or_404(course_key, user):
|
|||||||
return course
|
return course
|
||||||
|
|
||||||
|
|
||||||
def _get_thread_and_context(request, thread_id, parent_id=None, retrieve_kwargs=None):
|
def _get_thread_and_context(request, thread_id, retrieve_kwargs=None):
|
||||||
"""
|
"""
|
||||||
Retrieve the given thread and build a serializer context for it, returning
|
Retrieve the given thread and build a serializer context for it, returning
|
||||||
both. This function also enforces access control for the thread (checking
|
both. This function also enforces access control for the thread (checking
|
||||||
@@ -56,7 +56,7 @@ def _get_thread_and_context(request, thread_id, parent_id=None, retrieve_kwargs=
|
|||||||
cc_thread = Thread(id=thread_id).retrieve(**retrieve_kwargs)
|
cc_thread = Thread(id=thread_id).retrieve(**retrieve_kwargs)
|
||||||
course_key = CourseKey.from_string(cc_thread["course_id"])
|
course_key = CourseKey.from_string(cc_thread["course_id"])
|
||||||
course = _get_course_or_404(course_key, request.user)
|
course = _get_course_or_404(course_key, request.user)
|
||||||
context = get_context(course, request, cc_thread, parent_id)
|
context = get_context(course, request, cc_thread)
|
||||||
if (
|
if (
|
||||||
not context["is_requester_privileged"] and
|
not context["is_requester_privileged"] and
|
||||||
cc_thread["group_id"] and
|
cc_thread["group_id"] and
|
||||||
@@ -350,11 +350,10 @@ def create_comment(request, comment_data):
|
|||||||
detail.
|
detail.
|
||||||
"""
|
"""
|
||||||
thread_id = comment_data.get("thread_id")
|
thread_id = comment_data.get("thread_id")
|
||||||
parent_id = comment_data.get("parent_id")
|
|
||||||
if not thread_id:
|
if not thread_id:
|
||||||
raise ValidationError({"thread_id": ["This field is required."]})
|
raise ValidationError({"thread_id": ["This field is required."]})
|
||||||
try:
|
try:
|
||||||
cc_thread, context = _get_thread_and_context(request, thread_id, parent_id)
|
cc_thread, context = _get_thread_and_context(request, thread_id)
|
||||||
except Http404:
|
except Http404:
|
||||||
raise ValidationError({"thread_id": ["Invalid value."]})
|
raise ValidationError({"thread_id": ["Invalid value."]})
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ from openedx.core.djangoapps.course_groups.cohorts import get_cohort_names
|
|||||||
from openedx.core.lib.api.fields import NonEmptyCharField
|
from openedx.core.lib.api.fields import NonEmptyCharField
|
||||||
|
|
||||||
|
|
||||||
def get_context(course, request, thread=None, parent_id=None):
|
def get_context(course, request, thread=None):
|
||||||
"""
|
"""
|
||||||
Returns a context appropriate for use with ThreadSerializer or
|
Returns a context appropriate for use with ThreadSerializer or
|
||||||
(if thread is provided) CommentSerializer.
|
(if thread is provided) CommentSerializer.
|
||||||
@@ -48,7 +48,6 @@ def get_context(course, request, thread=None, parent_id=None):
|
|||||||
"course": course,
|
"course": course,
|
||||||
"request": request,
|
"request": request,
|
||||||
"thread": thread,
|
"thread": thread,
|
||||||
"parent_id": parent_id,
|
|
||||||
# For now, the only groups are cohorts
|
# For now, the only groups are cohorts
|
||||||
"group_ids_to_names": get_cohort_names(course),
|
"group_ids_to_names": get_cohort_names(course),
|
||||||
"is_requester_privileged": requester.id in staff_user_ids or requester.id in ta_user_ids,
|
"is_requester_privileged": requester.id in staff_user_ids or requester.id in ta_user_ids,
|
||||||
@@ -219,25 +218,18 @@ class CommentSerializer(_ContentSerializer):
|
|||||||
"""
|
"""
|
||||||
A serializer for comment data.
|
A serializer for comment data.
|
||||||
|
|
||||||
Because it is not a field in the underlying data, parent_id must be provided
|
|
||||||
in the context for both serialization and deserialization.
|
|
||||||
|
|
||||||
N.B. This should not be used with a comment_client Comment object that has
|
N.B. This should not be used with a comment_client Comment object that has
|
||||||
not had retrieve() called, because of the interaction between DRF's attempts
|
not had retrieve() called, because of the interaction between DRF's attempts
|
||||||
at introspection and Comment's __getattr__.
|
at introspection and Comment's __getattr__.
|
||||||
"""
|
"""
|
||||||
thread_id = serializers.CharField()
|
thread_id = serializers.CharField()
|
||||||
parent_id = serializers.SerializerMethodField("get_parent_id")
|
parent_id = serializers.CharField(required=False)
|
||||||
endorsed = serializers.BooleanField(read_only=True)
|
endorsed = serializers.BooleanField(read_only=True)
|
||||||
endorsed_by = serializers.SerializerMethodField("get_endorsed_by")
|
endorsed_by = serializers.SerializerMethodField("get_endorsed_by")
|
||||||
endorsed_by_label = serializers.SerializerMethodField("get_endorsed_by_label")
|
endorsed_by_label = serializers.SerializerMethodField("get_endorsed_by_label")
|
||||||
endorsed_at = serializers.SerializerMethodField("get_endorsed_at")
|
endorsed_at = serializers.SerializerMethodField("get_endorsed_at")
|
||||||
children = serializers.SerializerMethodField("get_children")
|
children = serializers.SerializerMethodField("get_children")
|
||||||
|
|
||||||
def get_parent_id(self, _obj):
|
|
||||||
"""Returns the comment's parent's id (taken from the context)."""
|
|
||||||
return self.context["parent_id"]
|
|
||||||
|
|
||||||
def get_endorsed_by(self, obj):
|
def get_endorsed_by(self, obj):
|
||||||
"""
|
"""
|
||||||
Returns the username of the endorsing user, if the information is
|
Returns the username of the endorsing user, if the information is
|
||||||
@@ -272,11 +264,8 @@ class CommentSerializer(_ContentSerializer):
|
|||||||
return endorsement["time"] if endorsement else None
|
return endorsement["time"] if endorsement else None
|
||||||
|
|
||||||
def get_children(self, obj):
|
def get_children(self, obj):
|
||||||
"""Returns the list of the comment's children, serialized."""
|
|
||||||
child_context = dict(self.context)
|
|
||||||
child_context["parent_id"] = obj["id"]
|
|
||||||
return [
|
return [
|
||||||
CommentSerializer(child, context=child_context).data
|
CommentSerializer(child, context=self.context).data
|
||||||
for child in obj.get("children", [])
|
for child in obj.get("children", [])
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -285,7 +274,7 @@ class CommentSerializer(_ContentSerializer):
|
|||||||
Ensure that parent_id identifies a comment that is actually in the
|
Ensure that parent_id identifies a comment that is actually in the
|
||||||
thread identified by thread_id.
|
thread identified by thread_id.
|
||||||
"""
|
"""
|
||||||
parent_id = self.context["parent_id"]
|
parent_id = attrs.get("parent_id")
|
||||||
if parent_id:
|
if parent_id:
|
||||||
parent = None
|
parent = None
|
||||||
try:
|
try:
|
||||||
@@ -303,7 +292,6 @@ class CommentSerializer(_ContentSerializer):
|
|||||||
raise ValueError("CommentSerializer cannot be used for updates.")
|
raise ValueError("CommentSerializer cannot be used for updates.")
|
||||||
return Comment(
|
return Comment(
|
||||||
course_id=self.context["thread"]["course_id"],
|
course_id=self.context["thread"]["course_id"],
|
||||||
parent_id=self.context["parent_id"],
|
|
||||||
user_id=self.context["cc_requester"]["id"],
|
user_id=self.context["cc_requester"]["id"],
|
||||||
**attrs
|
**attrs
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1220,12 +1220,11 @@ class CreateCommentTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest
|
|||||||
self.register_post_comment_response(
|
self.register_post_comment_response(
|
||||||
{
|
{
|
||||||
"id": "test_comment",
|
"id": "test_comment",
|
||||||
"thread_id": "test_thread",
|
|
||||||
"username": self.user.username,
|
"username": self.user.username,
|
||||||
"created_at": "2015-05-27T00:00:00Z",
|
"created_at": "2015-05-27T00:00:00Z",
|
||||||
"updated_at": "2015-05-27T00:00:00Z",
|
"updated_at": "2015-05-27T00:00:00Z",
|
||||||
},
|
},
|
||||||
thread_id=(None if parent_id else "test_thread"),
|
thread_id="test_thread",
|
||||||
parent_id=parent_id
|
parent_id=parent_id
|
||||||
)
|
)
|
||||||
data = self.minimal_data.copy()
|
data = self.minimal_data.copy()
|
||||||
|
|||||||
@@ -354,10 +354,17 @@ class CommentSerializerTest(SerializerTestMixin, ModuleStoreTestCase):
|
|||||||
"children": [
|
"children": [
|
||||||
self.make_cs_content({
|
self.make_cs_content({
|
||||||
"id": "test_child_1",
|
"id": "test_child_1",
|
||||||
|
"parent_id": "test_root",
|
||||||
}),
|
}),
|
||||||
self.make_cs_content({
|
self.make_cs_content({
|
||||||
"id": "test_child_2",
|
"id": "test_child_2",
|
||||||
"children": [self.make_cs_content({"id": "test_grandchild"})],
|
"parent_id": "test_root",
|
||||||
|
"children": [
|
||||||
|
self.make_cs_content({
|
||||||
|
"id": "test_grandchild",
|
||||||
|
"parent_id": "test_child_2"
|
||||||
|
})
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
@@ -547,7 +554,7 @@ class CommentSerializerDeserializationTest(CommentsServiceMockMixin, ModuleStore
|
|||||||
"raw_body": "Test body",
|
"raw_body": "Test body",
|
||||||
}
|
}
|
||||||
|
|
||||||
def save_and_reserialize(self, data, parent_id=None):
|
def save_and_reserialize(self, data):
|
||||||
"""
|
"""
|
||||||
Create a serializer with the given data, ensure that it is valid, save
|
Create a serializer with the given data, ensure that it is valid, save
|
||||||
the result, and return the full comment data from the serializer.
|
the result, and return the full comment data from the serializer.
|
||||||
@@ -555,8 +562,7 @@ class CommentSerializerDeserializationTest(CommentsServiceMockMixin, ModuleStore
|
|||||||
context = get_context(
|
context = get_context(
|
||||||
self.course,
|
self.course,
|
||||||
self.request,
|
self.request,
|
||||||
make_minimal_cs_thread({"course_id": unicode(self.course.id)}),
|
make_minimal_cs_thread({"course_id": unicode(self.course.id)})
|
||||||
parent_id
|
|
||||||
)
|
)
|
||||||
serializer = CommentSerializer(data=data, context=context)
|
serializer = CommentSerializer(data=data, context=context)
|
||||||
self.assertTrue(serializer.is_valid())
|
self.assertTrue(serializer.is_valid())
|
||||||
@@ -565,14 +571,16 @@ class CommentSerializerDeserializationTest(CommentsServiceMockMixin, ModuleStore
|
|||||||
|
|
||||||
@ddt.data(None, "test_parent")
|
@ddt.data(None, "test_parent")
|
||||||
def test_success(self, parent_id):
|
def test_success(self, parent_id):
|
||||||
|
data = self.minimal_data.copy()
|
||||||
if parent_id:
|
if parent_id:
|
||||||
|
data["parent_id"] = parent_id
|
||||||
self.register_get_comment_response({"thread_id": "test_thread", "id": parent_id})
|
self.register_get_comment_response({"thread_id": "test_thread", "id": parent_id})
|
||||||
self.register_post_comment_response(
|
self.register_post_comment_response(
|
||||||
{"id": "test_comment"},
|
{"id": "test_comment"},
|
||||||
thread_id=(None if parent_id else "test_thread"),
|
thread_id="test_thread",
|
||||||
parent_id=parent_id
|
parent_id=parent_id
|
||||||
)
|
)
|
||||||
saved = self.save_and_reserialize(self.minimal_data, parent_id=parent_id)
|
saved = self.save_and_reserialize(data)
|
||||||
expected_url = (
|
expected_url = (
|
||||||
"/api/v1/comments/{}".format(parent_id) if parent_id else
|
"/api/v1/comments/{}".format(parent_id) if parent_id else
|
||||||
"/api/v1/threads/test_thread/comments"
|
"/api/v1/threads/test_thread/comments"
|
||||||
@@ -591,8 +599,10 @@ class CommentSerializerDeserializationTest(CommentsServiceMockMixin, ModuleStore
|
|||||||
|
|
||||||
def test_parent_id_nonexistent(self):
|
def test_parent_id_nonexistent(self):
|
||||||
self.register_get_comment_error_response("bad_parent", 404)
|
self.register_get_comment_error_response("bad_parent", 404)
|
||||||
context = get_context(self.course, self.request, make_minimal_cs_thread(), "bad_parent")
|
data = self.minimal_data.copy()
|
||||||
serializer = CommentSerializer(data=self.minimal_data, context=context)
|
data["parent_id"] = "bad_parent"
|
||||||
|
context = get_context(self.course, self.request, make_minimal_cs_thread())
|
||||||
|
serializer = CommentSerializer(data=data, context=context)
|
||||||
self.assertFalse(serializer.is_valid())
|
self.assertFalse(serializer.is_valid())
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
serializer.errors,
|
serializer.errors,
|
||||||
@@ -605,8 +615,10 @@ class CommentSerializerDeserializationTest(CommentsServiceMockMixin, ModuleStore
|
|||||||
|
|
||||||
def test_parent_id_wrong_thread(self):
|
def test_parent_id_wrong_thread(self):
|
||||||
self.register_get_comment_response({"thread_id": "different_thread", "id": "test_parent"})
|
self.register_get_comment_response({"thread_id": "different_thread", "id": "test_parent"})
|
||||||
context = get_context(self.course, self.request, make_minimal_cs_thread(), "test_parent")
|
data = self.minimal_data.copy()
|
||||||
serializer = CommentSerializer(data=self.minimal_data, context=context)
|
data["parent_id"] = "test_parent"
|
||||||
|
context = get_context(self.course, self.request, make_minimal_cs_thread())
|
||||||
|
serializer = CommentSerializer(data=data, context=context)
|
||||||
self.assertFalse(serializer.is_valid())
|
self.assertFalse(serializer.is_valid())
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
serializer.errors,
|
serializer.errors,
|
||||||
|
|||||||
@@ -539,7 +539,6 @@ class CommentViewSetCreateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
|
|||||||
self.register_post_comment_response(
|
self.register_post_comment_response(
|
||||||
{
|
{
|
||||||
"id": "test_comment",
|
"id": "test_comment",
|
||||||
"thread_id": "test_thread",
|
|
||||||
"username": self.user.username,
|
"username": self.user.username,
|
||||||
"created_at": "2015-05-27T00:00:00Z",
|
"created_at": "2015-05-27T00:00:00Z",
|
||||||
"updated_at": "2015-05-27T00:00:00Z",
|
"updated_at": "2015-05-27T00:00:00Z",
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ class CommentsServiceMockMixin(object):
|
|||||||
status=200
|
status=200
|
||||||
)
|
)
|
||||||
|
|
||||||
def register_post_comment_response(self, response_overrides, thread_id=None, parent_id=None):
|
def register_post_comment_response(self, response_overrides, thread_id, parent_id=None):
|
||||||
"""
|
"""
|
||||||
Register a mock response for POST on the CS comments endpoint for the
|
Register a mock response for POST on the CS comments endpoint for the
|
||||||
given thread or parent; exactly one of thread_id and parent_id must be
|
given thread or parent; exactly one of thread_id and parent_id must be
|
||||||
@@ -99,14 +99,16 @@ class CommentsServiceMockMixin(object):
|
|||||||
{key: val[0] for key, val in request.parsed_body.items()}
|
{key: val[0] for key, val in request.parsed_body.items()}
|
||||||
)
|
)
|
||||||
response_data.update(response_overrides or {})
|
response_data.update(response_overrides or {})
|
||||||
|
# thread_id and parent_id are not included in request payload but
|
||||||
|
# are returned by the comments service
|
||||||
|
response_data["thread_id"] = thread_id
|
||||||
|
response_data["parent_id"] = parent_id
|
||||||
return (200, headers, json.dumps(response_data))
|
return (200, headers, json.dumps(response_data))
|
||||||
|
|
||||||
if thread_id and not parent_id:
|
if parent_id:
|
||||||
url = "http://localhost:4567/api/v1/threads/{}/comments".format(thread_id)
|
|
||||||
elif parent_id and not thread_id:
|
|
||||||
url = "http://localhost:4567/api/v1/comments/{}".format(parent_id)
|
url = "http://localhost:4567/api/v1/comments/{}".format(parent_id)
|
||||||
else: # pragma: no cover
|
else:
|
||||||
raise ValueError("Exactly one of thread_id and parent_id must be provided.")
|
url = "http://localhost:4567/api/v1/threads/{}/comments".format(thread_id)
|
||||||
|
|
||||||
httpretty.register_uri(httpretty.POST, url, body=callback)
|
httpretty.register_uri(httpretty.POST, url, body=callback)
|
||||||
|
|
||||||
@@ -228,6 +230,7 @@ def make_minimal_cs_comment(overrides=None):
|
|||||||
ret = {
|
ret = {
|
||||||
"id": "dummy",
|
"id": "dummy",
|
||||||
"thread_id": "dummy",
|
"thread_id": "dummy",
|
||||||
|
"parent_id": None,
|
||||||
"user_id": "0",
|
"user_id": "0",
|
||||||
"username": "dummy",
|
"username": "dummy",
|
||||||
"anonymous": False,
|
"anonymous": False,
|
||||||
|
|||||||
Reference in New Issue
Block a user