Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
0998df8c
Commit
0998df8c
authored
Jun 16, 2015
by
Greg Price
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add comment voting to discussion API
parent
905512b6
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
102 additions
and
20 deletions
+102
-20
lms/djangoapps/discussion_api/api.py
+26
-19
lms/djangoapps/discussion_api/forms.py
+9
-1
lms/djangoapps/discussion_api/tests/test_api.py
+54
-0
lms/djangoapps/discussion_api/tests/utils.py
+13
-0
No files found.
lms/djangoapps/discussion_api/api.py
View file @
0998df8c
...
...
@@ -15,7 +15,7 @@ from opaque_keys import InvalidKeyError
from
opaque_keys.edx.locator
import
CourseKey
from
courseware.courses
import
get_course_with_access
from
discussion_api.forms
import
ThreadActionsForm
from
discussion_api.forms
import
CommentActionsForm
,
ThreadActionsForm
from
discussion_api.pagination
import
get_paginated_data
from
discussion_api.serializers
import
CommentSerializer
,
ThreadSerializer
,
get_context
from
django_comment_client.base.views
import
(
...
...
@@ -335,25 +335,25 @@ def get_comment_list(request, thread_id, endorsed, page, page_size):
return
get_paginated_data
(
request
,
results
,
page
,
num_pages
)
def
_do_extra_
thread_actions
(
api_thread
,
cc_thread
,
request_fields
,
actions_form
,
context
):
def
_do_extra_
actions
(
api_content
,
cc_content
,
request_fields
,
actions_form
,
context
):
"""
Perform any necessary additional actions related to
thread
creation or
Perform any necessary additional actions related to
content
creation or
update that require a separate comments service request.
"""
for
field
,
form_value
in
actions_form
.
cleaned_data
.
items
():
if
field
in
request_fields
and
form_value
!=
api_
thread
[
field
]:
api_
thread
[
field
]
=
form_value
if
field
in
request_fields
and
form_value
!=
api_
content
[
field
]:
api_
content
[
field
]
=
form_value
if
field
==
"following"
:
if
form_value
:
context
[
"cc_requester"
]
.
follow
(
cc_
thread
)
context
[
"cc_requester"
]
.
follow
(
cc_
content
)
else
:
context
[
"cc_requester"
]
.
unfollow
(
cc_
thread
)
context
[
"cc_requester"
]
.
unfollow
(
cc_
content
)
else
:
assert
field
==
"voted"
if
form_value
:
context
[
"cc_requester"
]
.
vote
(
cc_
thread
,
"up"
)
context
[
"cc_requester"
]
.
vote
(
cc_
content
,
"up"
)
else
:
context
[
"cc_requester"
]
.
unvote
(
cc_
thread
)
context
[
"cc_requester"
]
.
unvote
(
cc_
content
)
def
create_thread
(
request
,
thread_data
):
...
...
@@ -390,7 +390,7 @@ def create_thread(request, thread_data):
cc_thread
=
serializer
.
object
api_thread
=
serializer
.
data
_do_extra_
thread_
actions
(
api_thread
,
cc_thread
,
thread_data
.
keys
(),
actions_form
,
context
)
_do_extra_actions
(
api_thread
,
cc_thread
,
thread_data
.
keys
(),
actions_form
,
context
)
track_forum_event
(
request
,
...
...
@@ -428,11 +428,15 @@ def create_comment(request, comment_data):
raise
ValidationError
({
"thread_id"
:
[
"Invalid value."
]})
serializer
=
CommentSerializer
(
data
=
comment_data
,
context
=
context
)
if
not
serializer
.
is_valid
():
raise
ValidationError
(
serializer
.
errors
)
actions_form
=
CommentActionsForm
(
comment_data
)
if
not
(
serializer
.
is_valid
()
and
actions_form
.
is_valid
()):
raise
ValidationError
(
dict
(
serializer
.
errors
.
items
()
+
actions_form
.
errors
.
items
()))
serializer
.
save
()
cc_comment
=
serializer
.
object
api_comment
=
serializer
.
data
_do_extra_actions
(
api_comment
,
cc_comment
,
comment_data
.
keys
(),
actions_form
,
context
)
track_forum_event
(
request
,
get_comment_created_event_name
(
cc_comment
),
...
...
@@ -501,7 +505,7 @@ def update_thread(request, thread_id, update_data):
if
set
(
update_data
)
-
set
(
actions_form
.
fields
):
serializer
.
save
()
api_thread
=
serializer
.
data
_do_extra_
thread_
actions
(
api_thread
,
cc_thread
,
update_data
.
keys
(),
actions_form
,
context
)
_do_extra_actions
(
api_thread
,
cc_thread
,
update_data
.
keys
(),
actions_form
,
context
)
return
api_thread
...
...
@@ -513,7 +517,7 @@ def _get_comment_editable_fields(cc_comment, context):
"""
Get the list of editable fields for the given comment in the given context
"""
ret
=
set
()
ret
=
{
"voted"
}
if
_is_user_author_or_privileged
(
cc_comment
,
context
):
ret
|=
_COMMENT_EDITABLE_BY_AUTHOR
if
_is_user_author_or_privileged
(
context
[
"thread"
],
context
):
...
...
@@ -554,12 +558,15 @@ def update_comment(request, comment_id, update_data):
editable_fields
=
_get_comment_editable_fields
(
cc_comment
,
context
)
_check_editable_fields
(
editable_fields
,
update_data
)
serializer
=
CommentSerializer
(
cc_comment
,
data
=
update_data
,
partial
=
True
,
context
=
context
)
if
not
serializer
.
is_valid
():
raise
ValidationError
(
serializer
.
errors
)
# Only save comment object if the comment is actually modified
if
update_data
:
actions_form
=
CommentActionsForm
(
update_data
)
if
not
(
serializer
.
is_valid
()
and
actions_form
.
is_valid
()):
raise
ValidationError
(
dict
(
serializer
.
errors
.
items
()
+
actions_form
.
errors
.
items
()))
# Only save thread object if some of the edited fields are in the thread data, not extra actions
if
set
(
update_data
)
-
set
(
actions_form
.
fields
):
serializer
.
save
()
return
serializer
.
data
api_comment
=
serializer
.
data
_do_extra_actions
(
api_comment
,
cc_comment
,
update_data
.
keys
(),
actions_form
,
context
)
return
api_comment
def
delete_thread
(
request
,
thread_id
):
...
...
lms/djangoapps/discussion_api/forms.py
View file @
0998df8c
...
...
@@ -59,7 +59,7 @@ class ThreadListGetForm(_PaginationForm):
class
ThreadActionsForm
(
Form
):
"""
A form to handle fields in thread creation that require separate
A form to handle fields in thread creation
/update
that require separate
interactions with the comments service.
"""
following
=
BooleanField
(
required
=
False
)
...
...
@@ -74,3 +74,11 @@ class CommentListGetForm(_PaginationForm):
# TODO: should we use something better here? This only accepts "True",
# "False", "1", and "0"
endorsed
=
NullBooleanField
(
required
=
False
)
class
CommentActionsForm
(
Form
):
"""
A form to handle fields in comment creation/update that require separate
interactions with the comments service.
"""
voted
=
BooleanField
(
required
=
False
)
lms/djangoapps/discussion_api/tests/test_api.py
View file @
0998df8c
...
...
@@ -1359,6 +1359,21 @@ class CreateCommentTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest
self
.
assertEqual
(
actual_event_name
,
expected_event_name
)
self
.
assertEqual
(
actual_event_data
,
expected_event_data
)
def
test_voted
(
self
):
self
.
register_post_comment_response
({
"id"
:
"test_comment"
},
"test_thread"
)
self
.
register_comment_votes_response
(
"test_comment"
)
data
=
self
.
minimal_data
.
copy
()
data
[
"voted"
]
=
"True"
result
=
create_comment
(
self
.
request
,
data
)
self
.
assertEqual
(
result
[
"voted"
],
True
)
cs_request
=
httpretty
.
last_request
()
self
.
assertEqual
(
urlparse
(
cs_request
.
path
)
.
path
,
"/api/v1/comments/test_comment/votes"
)
self
.
assertEqual
(
cs_request
.
method
,
"PUT"
)
self
.
assertEqual
(
cs_request
.
parsed_body
,
{
"user_id"
:
[
str
(
self
.
user
.
id
)],
"value"
:
[
"up"
]}
)
def
test_thread_id_missing
(
self
):
with
self
.
assertRaises
(
ValidationError
)
as
assertion
:
create_comment
(
self
.
request
,
{})
...
...
@@ -1918,6 +1933,45 @@ class UpdateCommentTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest
{
"endorsed"
:
[
"This field is not editable."
]}
)
@ddt.data
(
*
itertools
.
product
([
True
,
False
],
[
True
,
False
]))
@ddt.unpack
def
test_voted
(
self
,
old_voted
,
new_voted
):
"""
Test attempts to edit the "voted" field.
old_voted indicates whether the comment should be upvoted at the start of
the test. new_voted indicates the value for the "voted" field in the
update. If old_voted and new_voted are the same, no update should be
made. Otherwise, a vote should be PUT or DELETEd according to the
new_voted value.
"""
if
old_voted
:
self
.
register_get_user_response
(
self
.
user
,
upvoted_ids
=
[
"test_comment"
])
self
.
register_comment_votes_response
(
"test_comment"
)
self
.
register_comment
()
data
=
{
"voted"
:
new_voted
}
result
=
update_comment
(
self
.
request
,
"test_comment"
,
data
)
self
.
assertEqual
(
result
[
"voted"
],
new_voted
)
last_request_path
=
urlparse
(
httpretty
.
last_request
()
.
path
)
.
path
votes_url
=
"/api/v1/comments/test_comment/votes"
if
old_voted
==
new_voted
:
self
.
assertNotEqual
(
last_request_path
,
votes_url
)
else
:
self
.
assertEqual
(
last_request_path
,
votes_url
)
self
.
assertEqual
(
httpretty
.
last_request
()
.
method
,
"PUT"
if
new_voted
else
"DELETE"
)
actual_request_data
=
(
httpretty
.
last_request
()
.
parsed_body
if
new_voted
else
parse_qs
(
urlparse
(
httpretty
.
last_request
()
.
path
)
.
query
)
)
actual_request_data
.
pop
(
"request_id"
,
None
)
expected_request_data
=
{
"user_id"
:
[
str
(
self
.
user
.
id
)]}
if
new_voted
:
expected_request_data
[
"value"
]
=
[
"up"
]
self
.
assertEqual
(
actual_request_data
,
expected_request_data
)
@ddt.ddt
class
DeleteThreadTest
(
CommentsServiceMockMixin
,
UrlResetMixin
,
ModuleStoreTestCase
):
...
...
lms/djangoapps/discussion_api/tests/utils.py
View file @
0998df8c
...
...
@@ -203,6 +203,19 @@ class CommentsServiceMockMixin(object):
status
=
200
)
def
register_comment_votes_response
(
self
,
comment_id
):
"""
Register a mock response for PUT and DELETE on the CS comment votes
endpoint
"""
for
method
in
[
httpretty
.
PUT
,
httpretty
.
DELETE
]:
httpretty
.
register_uri
(
method
,
"http://localhost:4567/api/v1/comments/{}/votes"
.
format
(
comment_id
),
body
=
json
.
dumps
({}),
# body is unused
status
=
200
)
def
register_delete_thread_response
(
self
,
thread_id
):
"""
Register a mock response for DELETE on the CS thread instance endpoint
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment