Commit 2c7590d1 by Greg Price

Use parent_id returned from the comments service

This depends on cs_comments_service commit 0487891.
parent 77395bdf
...@@ -41,7 +41,7 @@ def _get_course_or_404(course_key, user): ...@@ -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= ...@@ -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): ...@@ -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 ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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 ...@@ -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): ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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,
......
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