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
bc2892f2
Commit
bc2892f2
authored
Jul 31, 2015
by
Diana Huang
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #9142 from edx/diana/context-aware-comment-client
Make context for threads more implicit.
parents
9d3e2c1a
c12c2933
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
96 additions
and
33 deletions
+96
-33
common/djangoapps/django_comment_common/utils.py
+7
-0
lms/djangoapps/django_comment_client/base/tests.py
+19
-8
lms/djangoapps/django_comment_client/base/views.py
+7
-3
lms/djangoapps/django_comment_client/forum/tests.py
+31
-13
lms/djangoapps/django_comment_client/forum/views.py
+6
-2
lms/djangoapps/django_comment_client/permissions.py
+26
-7
No files found.
common/djangoapps/django_comment_common/utils.py
View file @
bc2892f2
from
django_comment_common.models
import
Role
class
ThreadContext
(
object
):
""" An enumeration that represents the context of a thread. Used primarily by the comments service. """
STANDALONE
=
'standalone'
COURSE
=
'course'
_STUDENT_ROLE_PERMISSIONS
=
[
"vote"
,
"update_thread"
,
"follow_thread"
,
"unfollow_thread"
,
"update_comment"
,
"create_sub_comment"
,
"unvote"
,
"create_thread"
,
"follow_commentable"
,
"unfollow_commentable"
,
"create_comment"
,
]
...
...
lms/djangoapps/django_comment_client/base/tests.py
View file @
bc2892f2
...
...
@@ -19,7 +19,7 @@ from django_comment_client.tests.group_id import CohortedTopicGroupIdTestMixin,
from
django_comment_client.tests.utils
import
CohortedTestCase
from
django_comment_client.tests.unicode
import
UnicodeTestMixin
from
django_comment_common.models
import
Role
from
django_comment_common.utils
import
seed_permissions_roles
from
django_comment_common.utils
import
seed_permissions_roles
,
ThreadContext
from
student.tests.factories
import
CourseEnrollmentFactory
,
UserFactory
,
CourseAccessRoleFactory
from
util.testing
import
UrlResetMixin
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
...
...
@@ -240,7 +240,7 @@ class ViewsTestCaseMixin(object):
data
[
"depth"
]
=
0
self
.
_set_mock_request_data
(
mock_request
,
data
)
def
create_thread_helper
(
self
,
mock_request
,
extra_data
=
None
):
def
create_thread_helper
(
self
,
mock_request
,
extra_
request_data
=
None
,
extra_response_
data
=
None
):
"""
Issues a request to create a thread and verifies the result.
"""
...
...
@@ -283,8 +283,8 @@ class ViewsTestCaseMixin(object):
"anonymous"
:
[
"false"
],
"title"
:
[
"Hello"
],
}
if
extra_data
:
thread
.
update
(
extra_data
)
if
extra_
request_
data
:
thread
.
update
(
extra_
request_
data
)
url
=
reverse
(
'create_thread'
,
kwargs
=
{
'commentable_id'
:
'i4x-MITx-999-course-Robot_Super_Course'
,
'course_id'
:
self
.
course_id
.
to_deprecated_string
()})
response
=
self
.
client
.
post
(
url
,
data
=
thread
)
...
...
@@ -292,14 +292,15 @@ class ViewsTestCaseMixin(object):
expected_data
=
{
'thread_type'
:
'discussion'
,
'body'
:
u'this is a post'
,
'context'
:
ThreadContext
.
COURSE
,
'anonymous_to_peers'
:
False
,
'user_id'
:
1
,
'title'
:
u'Hello'
,
'commentable_id'
:
u'i4x-MITx-999-course-Robot_Super_Course'
,
'anonymous'
:
False
,
'course_id'
:
unicode
(
self
.
course_id
),
}
if
extra_data
:
expected_data
.
update
(
extra_data
)
if
extra_
response_
data
:
expected_data
.
update
(
extra_
response_
data
)
mock_request
.
assert_called_with
(
'post'
,
'{prefix}/i4x-MITx-999-course-Robot_Super_Course/threads'
.
format
(
prefix
=
CS_PREFIX
),
...
...
@@ -387,9 +388,19 @@ class ViewsTestCase(UrlResetMixin, ModuleStoreTestCase, MockRequestSetupMixin, V
def
test_create_thread
(
self
,
mock_request
):
self
.
create_thread_helper
(
mock_request
)
def
test_create_thread_with_context
(
self
,
mock_request
):
def
test_create_thread_standalone
(
self
,
mock_request
):
team
=
CourseTeamFactory
.
create
(
name
=
"A Team"
,
course_id
=
self
.
course_id
,
topic_id
=
'topic_id'
,
discussion_topic_id
=
"i4x-MITx-999-course-Robot_Super_Course"
)
# Add the student to the team so they can post to the commentable.
team
.
add_user
(
self
.
student
)
# create_thread_helper verifies that extra data are passed through to the comments service
self
.
create_thread_helper
(
mock_request
,
extra_
data
=
{
'context'
:
'standalone'
})
self
.
create_thread_helper
(
mock_request
,
extra_
response_data
=
{
'context'
:
ThreadContext
.
STANDALONE
})
def
test_delete_comment
(
self
,
mock_request
):
self
.
_set_mock_request_data
(
mock_request
,
{
...
...
lms/djangoapps/django_comment_client/base/views.py
View file @
bc2892f2
...
...
@@ -18,6 +18,7 @@ from courseware.access import has_access
from
util.file
import
store_uploaded_file
from
courseware.courses
import
get_course_with_access
,
get_course_by_id
import
django_comment_client.settings
as
cc_settings
from
django_comment_common.utils
import
ThreadContext
from
django_comment_client.utils
import
(
add_courseware_context
,
get_annotated_content_info
,
...
...
@@ -30,7 +31,7 @@ from django_comment_client.utils import (
discussion_category_id_access
,
get_cached_discussion_id_map
,
)
from
django_comment_client.permissions
import
check_permissions_by_view
,
has_permission
from
django_comment_client.permissions
import
check_permissions_by_view
,
has_permission
,
get_team
from
eventtracking
import
tracker
import
lms.lib.comment_client
as
cc
...
...
@@ -187,8 +188,11 @@ def create_thread(request, course_id, commentable_id):
'title'
:
post
[
"title"
],
}
if
'context'
in
post
:
params
[
'context'
]
=
post
[
'context'
]
# Check for whether this commentable belongs to a team, and add the right context
if
get_team
(
commentable_id
)
is
not
None
:
params
[
'context'
]
=
ThreadContext
.
STANDALONE
else
:
params
[
'context'
]
=
ThreadContext
.
COURSE
thread
=
cc
.
Thread
(
**
params
)
...
...
lms/djangoapps/django_comment_client/forum/tests.py
View file @
bc2892f2
...
...
@@ -8,7 +8,9 @@ from django.test.client import Client, RequestFactory
from
django.test.utils
import
override_settings
from
edxmako.tests
import
mako_middleware_process_request
from
django_comment_common.utils
import
ThreadContext
from
django_comment_client.forum
import
views
from
django_comment_client.permissions
import
get_team
from
django_comment_client.tests.group_id
import
(
CohortedTopicGroupIdTestMixin
,
NonCohortedTopicGroupIdTestMixin
...
...
@@ -33,6 +35,8 @@ from mock import patch, Mock, ANY, call
from
openedx.core.djangoapps.course_groups.models
import
CourseUserGroup
from
teams.tests.factories
import
CourseTeamFactory
log
=
logging
.
getLogger
(
__name__
)
# pylint: disable=missing-docstring
...
...
@@ -112,21 +116,23 @@ def make_mock_thread_data(
group_id
=
None
,
group_name
=
None
,
commentable_id
=
None
,
context
=
None
):
data_commentable_id
=
(
commentable_id
or
course
.
discussion_topics
.
get
(
'General'
,
{})
.
get
(
'id'
)
or
"dummy_commentable_id"
)
thread_data
=
{
"id"
:
thread_id
,
"type"
:
"thread"
,
"title"
:
text
,
"body"
:
text
,
"commentable_id"
:
(
commentable_id
or
course
.
discussion_topics
.
get
(
'General'
,
{})
.
get
(
'id'
)
or
"dummy_commentable_id"
),
"commentable_id"
:
data_commentable_id
,
"resp_total"
:
42
,
"resp_skip"
:
25
,
"resp_limit"
:
5
,
"group_id"
:
group_id
,
"context"
:
context
if
context
else
"course"
"context"
:
(
ThreadContext
.
COURSE
if
get_team
(
data_commentable_id
)
is
None
else
ThreadContext
.
STANDALONE
)
}
if
group_id
is
not
None
:
thread_data
[
'group_name'
]
=
group_name
...
...
@@ -146,7 +152,6 @@ def make_mock_request_impl(
group_id
=
None
,
commentable_id
=
None
,
num_thread_responses
=
1
,
context
=
None
):
def
mock_request_impl
(
*
args
,
**
kwargs
):
url
=
args
[
1
]
...
...
@@ -161,7 +166,6 @@ def make_mock_request_impl(
num_children
=
None
,
group_id
=
group_id
,
commentable_id
=
commentable_id
,
context
=
context
)
]
}
...
...
@@ -172,7 +176,6 @@ def make_mock_request_impl(
thread_id
=
thread_id
,
num_children
=
num_thread_responses
,
group_id
=
group_id
,
context
=
context
)
elif
"/users/"
in
url
:
data
=
{
...
...
@@ -672,12 +675,20 @@ class InlineDiscussionContextTestCase(ModuleStoreTestCase):
super
(
InlineDiscussionContextTestCase
,
self
)
.
setUp
()
self
.
course
=
CourseFactory
.
create
()
CourseEnrollmentFactory
(
user
=
self
.
user
,
course_id
=
self
.
course
.
id
)
self
.
discussion_topic_id
=
"dummy_topic"
self
.
team
=
CourseTeamFactory
(
name
=
"A team"
,
course_id
=
self
.
course
.
id
,
topic_id
=
'topic_id'
,
discussion_topic_id
=
self
.
discussion_topic_id
)
self
.
team
.
add_user
(
self
.
user
)
# pylint: disable=no-member
def
test_context_can_be_standalone
(
self
,
mock_request
):
mock_request
.
side_effect
=
make_mock_request_impl
(
course
=
self
.
course
,
text
=
"dummy text"
,
co
ntext
=
"standalone"
co
mmentable_id
=
self
.
discussion_topic_id
)
request
=
RequestFactory
()
.
get
(
"dummy_url"
)
...
...
@@ -686,11 +697,11 @@ class InlineDiscussionContextTestCase(ModuleStoreTestCase):
response
=
views
.
inline_discussion
(
request
,
unicode
(
self
.
course
.
id
),
"dummy_topic"
,
self
.
discussion_topic_id
,
)
json_response
=
json
.
loads
(
response
.
content
)
self
.
assertEqual
(
json_response
[
'discussion_data'
][
0
][
'context'
],
'standalone'
)
self
.
assertEqual
(
json_response
[
'discussion_data'
][
0
][
'context'
],
ThreadContext
.
STANDALONE
)
@patch
(
'lms.lib.comment_client.utils.requests.request'
)
...
...
@@ -1041,8 +1052,15 @@ class InlineDiscussionTestCase(ModuleStoreTestCase):
self
.
verify_response
(
self
.
send_request
(
mock_request
))
def
test_context
(
self
,
mock_request
):
response
=
self
.
send_request
(
mock_request
,
{
'context'
:
'standalone'
})
self
.
assertEqual
(
mock_request
.
call_args
[
1
][
'params'
][
'context'
],
'standalone'
)
team
=
CourseTeamFactory
(
name
=
'Team Name'
,
topic_id
=
'A topic'
,
course_id
=
self
.
course
.
id
,
discussion_topic_id
=
self
.
discussion1
.
discussion_id
)
team
.
add_user
(
self
.
student
)
# pylint: disable=no-member
response
=
self
.
send_request
(
mock_request
)
self
.
assertEqual
(
mock_request
.
call_args
[
1
][
'params'
][
'context'
],
ThreadContext
.
STANDALONE
)
self
.
verify_response
(
response
)
...
...
lms/djangoapps/django_comment_client/forum/views.py
View file @
bc2892f2
...
...
@@ -30,7 +30,8 @@ from courseware.access import has_access
from
xmodule.modulestore.django
import
modulestore
from
ccx.overrides
import
get_current_ccx
from
django_comment_client.permissions
import
has_permission
from
django_comment_common.utils
import
ThreadContext
from
django_comment_client.permissions
import
has_permission
,
get_team
from
django_comment_client.utils
import
(
merge_dict
,
extract
,
...
...
@@ -111,6 +112,7 @@ def get_threads(request, course, discussion_id=None, per_page=THREADS_PER_PAGE):
'text'
:
''
,
'course_id'
:
unicode
(
course
.
id
),
'user_id'
:
request
.
user
.
id
,
'context'
:
ThreadContext
.
COURSE
,
'group_id'
:
get_group_id_for_comments_service
(
request
,
course
.
id
,
discussion_id
),
# may raise ValueError
}
...
...
@@ -118,6 +120,9 @@ def get_threads(request, course, discussion_id=None, per_page=THREADS_PER_PAGE):
# comments_service.
if
discussion_id
is
not
None
:
default_query_params
[
'commentable_id'
]
=
discussion_id
# Use the discussion id/commentable id to determine the context we are going to pass through to the backend.
if
get_team
(
discussion_id
)
is
not
None
:
default_query_params
[
'context'
]
=
ThreadContext
.
STANDALONE
if
not
request
.
GET
.
get
(
'sort_key'
):
# If the user did not select a sort key, use their last used sort key
...
...
@@ -149,7 +154,6 @@ def get_threads(request, course, discussion_id=None, per_page=THREADS_PER_PAGE):
'flagged'
,
'unread'
,
'unanswered'
,
'context'
,
]
)
)
...
...
lms/djangoapps/django_comment_client/permissions.py
View file @
bc2892f2
...
...
@@ -31,20 +31,40 @@ def has_permission(user, permission, course_id=None):
CONDITIONS
=
[
'is_open'
,
'is_author'
,
'is_question_author'
,
'is_team_member_if_applicable'
]
def
get_team
(
commentable_id
):
""" Returns the team that the commentable_id belongs to if it exists. Returns None otherwise. """
request_cache_dict
=
RequestCache
.
get_request_cache
()
.
data
cache_key
=
"django_comment_client.team_commentable.{}"
.
format
(
commentable_id
)
if
cache_key
in
request_cache_dict
:
return
request_cache_dict
[
cache_key
]
try
:
team
=
CourseTeam
.
objects
.
get
(
discussion_topic_id
=
commentable_id
)
except
CourseTeam
.
DoesNotExist
:
team
=
None
request_cache_dict
[
cache_key
]
=
team
return
team
def
_check_condition
(
user
,
condition
,
content
):
def
check_open
(
user
,
content
):
""" Check whether or not the given condition applies for the given user and content. """
def
check_open
(
_user
,
content
):
""" Check whether the content is open. """
try
:
return
content
and
not
content
[
'closed'
]
except
KeyError
:
return
False
def
check_author
(
user
,
content
):
""" Check if the given user is the author of the content. """
try
:
return
content
and
content
[
'user_id'
]
==
str
(
user
.
id
)
except
KeyError
:
return
False
def
check_question_author
(
user
,
content
):
""" Check if the given user is the author of the original question for both threads and comments. """
if
not
content
:
return
False
try
:
...
...
@@ -69,17 +89,16 @@ def _check_condition(user, condition, content):
cache_key
=
"django_comment_client.check_team_member.{}.{}"
.
format
(
user
.
id
,
commentable_id
)
if
cache_key
in
request_cache_dict
:
return
request_cache_dict
[
cache_key
]
team
=
CourseTeam
.
objects
.
get
(
discussion_topic_id
=
commentable_id
)
passes_condition
=
team
.
users
.
filter
(
id
=
user
.
id
)
.
count
()
>
0
team
=
get_team
(
commentable_id
)
if
team
is
None
:
passes_condition
=
True
else
:
passes_condition
=
team
.
users
.
filter
(
id
=
user
.
id
)
.
exists
()
request_cache_dict
[
cache_key
]
=
passes_condition
except
KeyError
:
# We do not expect KeyError in production-- it usually indicates an improper test mock.
logging
.
warning
(
"Did not find key commentable_id in content."
)
passes_condition
=
False
except
CourseTeam
.
DoesNotExist
:
passes_condition
=
True
request_cache_dict
[
cache_key
]
=
passes_condition
return
passes_condition
handlers
=
{
...
...
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