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
67c3b083
Commit
67c3b083
authored
Jan 23, 2017
by
Calen Pennington
Committed by
GitHub
Jan 23, 2017
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #14366 from edx/release-candidate
Merge Release candidate to Release
parents
e4c0c61d
a432f15c
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
191 additions
and
65 deletions
+191
-65
common/static/common/js/discussion/discussion.js
+5
-1
common/static/common/js/discussion/views/discussion_inline_view.js
+6
-3
common/static/common/js/discussion/views/discussion_thread_list_view.js
+13
-6
common/static/common/js/spec/discussion/view/discussion_thread_list_view_spec.js
+1
-0
common/static/common/templates/discussion/inline-discussion.underscore
+1
-1
lms/djangoapps/courseware/access.py
+12
-2
lms/djangoapps/courseware/tests/helpers.py
+40
-1
lms/djangoapps/courseware/tests/test_access.py
+55
-1
lms/djangoapps/courseware/tests/test_masquerade.py
+16
-19
lms/djangoapps/discussion/static/discussion/js/spec/discussion_board_view_spec.js
+8
-0
lms/djangoapps/discussion/static/discussion/js/spec/views/discussion_user_profile_view_spec.js
+14
-5
lms/djangoapps/discussion/static/discussion/js/views/discussion_board_view.js
+2
-1
lms/djangoapps/discussion/static/discussion/js/views/discussion_user_profile_view.js
+2
-2
lms/djangoapps/discussion/templates/discussion/discussion_profile_page.html
+1
-0
lms/djangoapps/teams/static/teams/js/spec/views/team_profile_spec.js
+2
-1
lms/static/sass/discussion/elements/_navigation.scss
+9
-4
openedx/core/djangoapps/course_groups/tests/test_partition_scheme.py
+3
-17
requirements/edx/github.txt
+1
-1
No files found.
common/static/common/js/discussion/discussion.js
View file @
67c3b083
...
@@ -99,7 +99,7 @@
...
@@ -99,7 +99,7 @@
data
.
text
=
options
.
search_text
;
data
.
text
=
options
.
search_text
;
break
;
break
;
case
'commentables'
:
case
'commentables'
:
url
=
DiscussionUtil
.
urlFor
(
'
search'
);
url
=
DiscussionUtil
.
urlFor
(
'
retrieve_discussion'
,
options
.
commentable_ids
);
data
.
commentable_ids
=
options
.
commentable_ids
;
data
.
commentable_ids
=
options
.
commentable_ids
;
break
;
break
;
case
'all'
:
case
'all'
:
...
@@ -107,6 +107,10 @@
...
@@ -107,6 +107,10 @@
break
;
break
;
case
'followed'
:
case
'followed'
:
url
=
DiscussionUtil
.
urlFor
(
'followed_threads'
,
options
.
user_id
);
url
=
DiscussionUtil
.
urlFor
(
'followed_threads'
,
options
.
user_id
);
break
;
case
'user'
:
url
=
DiscussionUtil
.
urlFor
(
'user_profile'
,
options
.
user_id
);
break
;
}
}
if
(
options
.
group_id
)
{
if
(
options
.
group_id
)
{
data
.
group_id
=
options
.
group_id
;
data
.
group_id
=
options
.
group_id
;
...
...
common/static/common/js/discussion/views/discussion_inline_view.js
View file @
67c3b083
...
@@ -39,6 +39,9 @@
...
@@ -39,6 +39,9 @@
this
.
page
=
1
;
this
.
page
=
1
;
}
}
this
.
defaultSortKey
=
'activity'
;
this
.
defaultSortOrder
=
'desc'
;
// By default the view is displayed in a hidden state. If you want it to be shown by default (e.g. in Teams)
// By default the view is displayed in a hidden state. If you want it to be shown by default (e.g. in Teams)
// pass showByDefault as an option. This code will open it on initialization.
// pass showByDefault as an option. This code will open it on initialization.
if
(
this
.
showByDefault
)
{
if
(
this
.
showByDefault
)
{
...
@@ -48,7 +51,8 @@
...
@@ -48,7 +51,8 @@
loadDiscussions
:
function
(
$elem
,
error
)
{
loadDiscussions
:
function
(
$elem
,
error
)
{
var
discussionId
=
this
.
$el
.
data
(
'discussion-id'
),
var
discussionId
=
this
.
$el
.
data
(
'discussion-id'
),
url
=
DiscussionUtil
.
urlFor
(
'retrieve_discussion'
,
discussionId
)
+
(
'?page='
+
this
.
page
),
url
=
DiscussionUtil
.
urlFor
(
'retrieve_discussion'
,
discussionId
)
+
(
'?page='
+
this
.
page
)
+
(
'&sort_key='
+
this
.
defaultSortKey
)
+
(
'&sort_order='
+
this
.
defaultSortOrder
),
self
=
this
;
self
=
this
;
DiscussionUtil
.
safeAjax
({
DiscussionUtil
.
safeAjax
({
...
@@ -100,8 +104,7 @@
...
@@ -100,8 +104,7 @@
this
.
threadListView
=
new
DiscussionThreadListView
({
this
.
threadListView
=
new
DiscussionThreadListView
({
el
:
this
.
$
(
'.inline-threads'
),
el
:
this
.
$
(
'.inline-threads'
),
collection
:
self
.
discussion
,
collection
:
self
.
discussion
,
courseSettings
:
self
.
course_settings
,
courseSettings
:
self
.
course_settings
hideRefineBar
:
true
// TODO: re-enable the search/filter bar when it works correctly
});
});
this
.
threadListView
.
render
();
this
.
threadListView
.
render
();
...
...
common/static/common/js/discussion/views/discussion_thread_list_view.js
View file @
67c3b083
...
@@ -91,14 +91,13 @@
...
@@ -91,14 +91,13 @@
DiscussionThreadListView
.
prototype
.
initialize
=
function
(
options
)
{
DiscussionThreadListView
.
prototype
.
initialize
=
function
(
options
)
{
var
self
=
this
;
var
self
=
this
;
this
.
courseSettings
=
options
.
courseSettings
;
this
.
courseSettings
=
options
.
courseSettings
;
this
.
hideRefineBar
=
options
.
hideRefineBar
;
this
.
supportsActiveThread
=
options
.
supportsActiveThread
;
this
.
supportsActiveThread
=
options
.
supportsActiveThread
;
this
.
hideReadState
=
options
.
hideReadState
||
false
;
this
.
hideReadState
=
options
.
hideReadState
||
false
;
this
.
displayedCollection
=
new
Discussion
(
this
.
collection
.
models
,
{
this
.
displayedCollection
=
new
Discussion
(
this
.
collection
.
models
,
{
pages
:
this
.
collection
.
pages
pages
:
this
.
collection
.
pages
});
});
this
.
collection
.
on
(
'change'
,
this
.
reloadDisplayedCollection
);
this
.
collection
.
on
(
'change'
,
this
.
reloadDisplayedCollection
);
this
.
discussionIds
=
''
;
this
.
discussionIds
=
this
.
$el
.
data
(
'discussion-id'
)
||
''
;
this
.
collection
.
on
(
'reset'
,
function
(
discussion
)
{
this
.
collection
.
on
(
'reset'
,
function
(
discussion
)
{
self
.
displayedCollection
.
current_page
=
discussion
.
current_page
;
self
.
displayedCollection
.
current_page
=
discussion
.
current_page
;
self
.
displayedCollection
.
pages
=
discussion
.
pages
;
self
.
displayedCollection
.
pages
=
discussion
.
pages
;
...
@@ -109,7 +108,7 @@
...
@@ -109,7 +108,7 @@
this
.
sidebar_padding
=
10
;
this
.
sidebar_padding
=
10
;
this
.
boardName
=
null
;
this
.
boardName
=
null
;
this
.
current_search
=
''
;
this
.
current_search
=
''
;
this
.
mode
=
'all
'
;
this
.
mode
=
options
.
mode
||
'commentables
'
;
this
.
showThreadPreview
=
true
;
this
.
showThreadPreview
=
true
;
this
.
searchAlertCollection
=
new
Backbone
.
Collection
([],
{
this
.
searchAlertCollection
=
new
Backbone
.
Collection
([],
{
model
:
Backbone
.
Model
model
:
Backbone
.
Model
...
@@ -199,6 +198,9 @@
...
@@ -199,6 +198,9 @@
isPrivilegedUser
:
DiscussionUtil
.
isPrivilegedUser
()
isPrivilegedUser
:
DiscussionUtil
.
isPrivilegedUser
()
})
})
);
);
if
(
this
.
hideReadState
)
{
this
.
$
(
'.forum-nav-filter-main'
).
addClass
(
'is-hidden'
);
}
this
.
$
(
'.forum-nav-sort-control option'
).
removeProp
(
'selected'
);
this
.
$
(
'.forum-nav-sort-control option'
).
removeProp
(
'selected'
);
this
.
$
(
'.forum-nav-sort-control option[value='
+
this
.
collection
.
sort_preference
+
']'
)
this
.
$
(
'.forum-nav-sort-control option[value='
+
this
.
collection
.
sort_preference
+
']'
)
.
prop
(
'selected'
,
true
);
.
prop
(
'selected'
,
true
);
...
@@ -223,9 +225,6 @@
...
@@ -223,9 +225,6 @@
}
}
this
.
showMetadataAccordingToSort
();
this
.
showMetadataAccordingToSort
();
this
.
renderMorePages
();
this
.
renderMorePages
();
if
(
this
.
hideRefineBar
)
{
this
.
$
(
'.forum-nav-refine-bar'
).
addClass
(
'is-hidden'
);
}
this
.
trigger
(
'threads:rendered'
);
this
.
trigger
(
'threads:rendered'
);
};
};
...
@@ -284,6 +283,9 @@
...
@@ -284,6 +283,9 @@
case
'followed'
:
case
'followed'
:
options
.
user_id
=
window
.
user
.
id
;
options
.
user_id
=
window
.
user
.
id
;
break
;
break
;
case
'user'
:
options
.
user_id
=
this
.
$el
.
parent
().
data
(
'user-id'
);
break
;
case
'commentables'
:
case
'commentables'
:
options
.
commentable_ids
=
this
.
discussionIds
;
options
.
commentable_ids
=
this
.
discussionIds
;
if
(
this
.
group_id
)
{
if
(
this
.
group_id
)
{
...
@@ -319,6 +321,11 @@
...
@@ -319,6 +321,11 @@
gettext
(
'Additional posts could not be loaded. Refresh the page and try again.'
)
gettext
(
'Additional posts could not be loaded. Refresh the page and try again.'
)
);
);
};
};
/*
The options object is being passed to the function below from discussion/discussion.js
which correspondingly forms the ajax url based on the mode via the DiscussionUtil.urlFor
from discussion/utils.js
*/
return
this
.
collection
.
retrieveAnotherPage
(
this
.
mode
,
options
,
{
return
this
.
collection
.
retrieveAnotherPage
(
this
.
mode
,
options
,
{
sort_key
:
this
.
$
(
'.forum-nav-sort-control'
).
val
()
sort_key
:
this
.
$
(
'.forum-nav-sort-control'
).
val
()
},
error
);
},
error
);
...
...
common/static/common/js/spec/discussion/view/discussion_thread_list_view_spec.js
View file @
67c3b083
...
@@ -364,6 +364,7 @@
...
@@ -364,6 +364,7 @@
});
});
sortControl
.
val
(
newType
).
change
();
sortControl
.
val
(
newType
).
change
();
expect
(
$
.
ajax
).
toHaveBeenCalled
();
expect
(
$
.
ajax
).
toHaveBeenCalled
();
expect
(
view
.
mode
).
toBe
(
'commentables'
);
checkThreadsOrdering
(
view
,
sortOrder
,
newType
);
checkThreadsOrdering
(
view
,
sortOrder
,
newType
);
};
};
...
...
common/static/common/templates/discussion/inline-discussion.underscore
View file @
67c3b083
...
@@ -6,7 +6,7 @@
...
@@ -6,7 +6,7 @@
<article class="new-post-article is-hidden"></article>
<article class="new-post-article is-hidden"></article>
<div class="inline-discussion-thread-container">
<div class="inline-discussion-thread-container">
<section class="inline-threads">
<section class="inline-threads"
data-discussion-id="<%- discussionId %>"
>
</section>
</section>
<div class="inline-thread">
<div class="inline-thread">
...
...
lms/djangoapps/courseware/access.py
View file @
67c3b083
...
@@ -469,6 +469,10 @@ def _has_group_access(descriptor, user, course_key):
...
@@ -469,6 +469,10 @@ def _has_group_access(descriptor, user, course_key):
# via updating the children of the split_test module.
# via updating the children of the split_test module.
return
ACCESS_GRANTED
return
ACCESS_GRANTED
# Allow staff and instructors roles group access, as they are not masquerading as a student.
if
get_user_role
(
user
,
course_key
)
in
[
'staff'
,
'instructor'
]:
return
ACCESS_GRANTED
# use merged_group_access which takes group access on the block's
# use merged_group_access which takes group access on the block's
# parents / ancestors into account
# parents / ancestors into account
merged_access
=
descriptor
.
merged_group_access
merged_access
=
descriptor
.
merged_group_access
...
@@ -550,14 +554,20 @@ def _has_access_descriptor(user, action, descriptor, course_key=None):
...
@@ -550,14 +554,20 @@ def _has_access_descriptor(user, action, descriptor, course_key=None):
students to see modules. If not, views should check the course, so we
students to see modules. If not, views should check the course, so we
don't have to hit the enrollments table on every module load.
don't have to hit the enrollments table on every module load.
"""
"""
# If the user (or the role the user is currently masquerading as) does not have
# access to this content, then deny access. The problem with calling _has_staff_access_to_descriptor
# before this method is that _has_staff_access_to_descriptor short-circuits and returns True
# for staff users in preview mode.
if
not
_has_group_access
(
descriptor
,
user
,
course_key
):
return
ACCESS_DENIED
# If the user has staff access, they can load the module and checks below are not needed.
if
_has_staff_access_to_descriptor
(
user
,
descriptor
,
course_key
):
if
_has_staff_access_to_descriptor
(
user
,
descriptor
,
course_key
):
return
ACCESS_GRANTED
return
ACCESS_GRANTED
# if the user has staff access, they can load the module so this code doesn't need to run
return
(
return
(
_visible_to_nonstaff_users
(
descriptor
)
and
_visible_to_nonstaff_users
(
descriptor
)
and
_can_access_descriptor_with_milestones
(
user
,
descriptor
,
course_key
)
and
_can_access_descriptor_with_milestones
(
user
,
descriptor
,
course_key
)
and
_has_group_access
(
descriptor
,
user
,
course_key
)
and
(
(
_has_detached_class_tag
(
descriptor
)
or
_has_detached_class_tag
(
descriptor
)
or
_can_access_descriptor_with_start_date
(
user
,
descriptor
,
course_key
)
_can_access_descriptor_with_start_date
(
user
,
descriptor
,
course_key
)
...
...
lms/djangoapps/courseware/tests/helpers.py
View file @
67c3b083
"""
"""
Helpers for courseware tests.
Helpers for courseware tests.
"""
"""
import
crum
import
json
import
json
from
django.contrib.auth.models
import
User
from
django.contrib.auth.models
import
User
...
@@ -10,6 +9,10 @@ from django.test import TestCase
...
@@ -10,6 +9,10 @@ from django.test import TestCase
from
django.test.client
import
RequestFactory
from
django.test.client
import
RequestFactory
from
courseware.access
import
has_access
from
courseware.access
import
has_access
from
courseware.masquerade
import
(
handle_ajax
,
setup_masquerade
)
from
openedx.core.djangoapps.content.course_overviews.models
import
CourseOverview
from
openedx.core.djangoapps.content.course_overviews.models
import
CourseOverview
from
student.models
import
Registration
from
student.models
import
Registration
...
@@ -178,3 +181,39 @@ class CourseAccessTestMixin(TestCase):
...
@@ -178,3 +181,39 @@ class CourseAccessTestMixin(TestCase):
"""
"""
self
.
assertFalse
(
has_access
(
user
,
action
,
course
))
self
.
assertFalse
(
has_access
(
user
,
action
,
course
))
self
.
assertFalse
(
has_access
(
user
,
action
,
CourseOverview
.
get_from_id
(
course
.
id
)))
self
.
assertFalse
(
has_access
(
user
,
action
,
CourseOverview
.
get_from_id
(
course
.
id
)))
def
masquerade_as_group_member
(
user
,
course
,
partition_id
,
group_id
):
"""
Installs a masquerade for the specified user and course, to enable
the user to masquerade as belonging to the specific partition/group
combination.
Arguments:
user (User): a user.
course (CourseDescriptor): a course.
partition_id (int): the integer partition id, referring to partitions already
configured in the course.
group_id (int); the integer group id, within the specified partition.
Returns: the status code for the AJAX response to update the user's masquerade for
the specified course.
"""
request
=
_create_mock_json_request
(
user
,
data
=
{
"role"
:
"student"
,
"user_partition_id"
:
partition_id
,
"group_id"
:
group_id
}
)
response
=
handle_ajax
(
request
,
unicode
(
course
.
id
))
setup_masquerade
(
request
,
course
.
id
,
True
)
return
response
.
status_code
def
_create_mock_json_request
(
user
,
data
,
method
=
'POST'
):
"""
Returns a mock JSON request for the specified user.
"""
factory
=
RequestFactory
()
request
=
factory
.
generic
(
method
,
'/'
,
content_type
=
'application/json'
,
data
=
json
.
dumps
(
data
))
request
.
user
=
user
request
.
session
=
{}
return
request
lms/djangoapps/courseware/tests/test_access.py
View file @
67c3b083
...
@@ -27,7 +27,7 @@ from courseware.tests.factories import (
...
@@ -27,7 +27,7 @@ from courseware.tests.factories import (
StaffFactory
,
StaffFactory
,
UserFactory
,
UserFactory
,
)
)
from
courseware.tests.helpers
import
LoginEnrollmentTestCase
from
courseware.tests.helpers
import
LoginEnrollmentTestCase
,
masquerade_as_group_member
from
openedx.core.djangoapps.content.course_overviews.models
import
CourseOverview
from
openedx.core.djangoapps.content.course_overviews.models
import
CourseOverview
from
student.models
import
CourseEnrollment
from
student.models
import
CourseEnrollment
from
student.roles
import
CourseCcxCoachRole
,
CourseStaffRole
from
student.roles
import
CourseCcxCoachRole
,
CourseStaffRole
...
@@ -44,6 +44,9 @@ from xmodule.course_module import (
...
@@ -44,6 +44,9 @@ from xmodule.course_module import (
CATALOG_VISIBILITY_NONE
,
CATALOG_VISIBILITY_NONE
,
)
)
from
xmodule.error_module
import
ErrorDescriptor
from
xmodule.error_module
import
ErrorDescriptor
from
xmodule.partitions.partitions
import
Group
,
UserPartition
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
from
xmodule.modulestore.tests.django_utils
import
(
from
xmodule.modulestore.tests.django_utils
import
(
ModuleStoreTestCase
,
ModuleStoreTestCase
,
...
@@ -293,6 +296,57 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTes
...
@@ -293,6 +296,57 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTes
bool
(
access
.
has_access
(
self
.
student
,
'staff'
,
self
.
course
,
course_key
=
self
.
course
.
id
))
bool
(
access
.
has_access
(
self
.
student
,
'staff'
,
self
.
course
,
course_key
=
self
.
course
.
id
))
)
)
@patch
(
'courseware.access.in_preview_mode'
,
Mock
(
return_value
=
True
))
def
test_has_access_in_preview_mode_with_group
(
self
):
"""
Test that a user masquerading as a member of a group sees appropriate content in preview mode.
"""
partition_id
=
0
group_0_id
=
0
group_1_id
=
1
user_partition
=
UserPartition
(
partition_id
,
'Test User Partition'
,
''
,
[
Group
(
group_0_id
,
'Group 1'
),
Group
(
group_1_id
,
'Group 2'
)],
scheme_id
=
'cohort'
)
self
.
course
.
user_partitions
.
append
(
user_partition
)
self
.
course
.
cohort_config
=
{
'cohorted'
:
True
}
chapter
=
ItemFactory
.
create
(
category
=
"chapter"
,
parent_location
=
self
.
course
.
location
)
chapter
.
group_access
=
{
partition_id
:
[
group_0_id
]}
chapter
.
user_partitions
=
self
.
course
.
user_partitions
modulestore
()
.
update_item
(
self
.
course
,
ModuleStoreEnum
.
UserID
.
test
)
# User should not be able to preview when masquerading as student (and not in the group above).
with
patch
(
'courseware.access.get_user_role'
)
as
mock_user_role
:
mock_user_role
.
return_value
=
'student'
self
.
assertFalse
(
bool
(
access
.
has_access
(
self
.
global_staff
,
'load'
,
chapter
,
course_key
=
self
.
course
.
id
))
)
# Should be able to preview when in staff or instructor role.
for
mocked_role
in
[
'staff'
,
'instructor'
]:
with
patch
(
'courseware.access.get_user_role'
)
as
mock_user_role
:
mock_user_role
.
return_value
=
mocked_role
self
.
assertTrue
(
bool
(
access
.
has_access
(
self
.
global_staff
,
'load'
,
chapter
,
course_key
=
self
.
course
.
id
))
)
# Now install masquerade group and set staff as a member of that.
self
.
assertEqual
(
200
,
masquerade_as_group_member
(
self
.
global_staff
,
self
.
course
,
partition_id
,
group_0_id
))
# Can load the chapter since user is in the group.
self
.
assertTrue
(
bool
(
access
.
has_access
(
self
.
global_staff
,
'load'
,
chapter
,
course_key
=
self
.
course
.
id
))
)
# Move the user to be a part of the second group.
self
.
assertEqual
(
200
,
masquerade_as_group_member
(
self
.
global_staff
,
self
.
course
,
partition_id
,
group_1_id
))
# Cannot load the chapter since user is in a different group.
self
.
assertFalse
(
bool
(
access
.
has_access
(
self
.
global_staff
,
'load'
,
chapter
,
course_key
=
self
.
course
.
id
))
)
def
test_has_access_to_course
(
self
):
def
test_has_access_to_course
(
self
):
self
.
assertFalse
(
access
.
_has_access_to_course
(
self
.
assertFalse
(
access
.
_has_access_to_course
(
None
,
'staff'
,
self
.
course
.
id
None
,
'staff'
,
self
.
course
.
id
...
...
lms/djangoapps/courseware/tests/test_masquerade.py
View file @
67c3b083
...
@@ -8,7 +8,7 @@ from nose.plugins.attrib import attr
...
@@ -8,7 +8,7 @@ from nose.plugins.attrib import attr
from
datetime
import
datetime
from
datetime
import
datetime
from
django.core.urlresolvers
import
reverse
from
django.core.urlresolvers
import
reverse
from
django.test
import
TestCase
,
RequestFactory
from
django.test
import
TestCase
from
django.utils.timezone
import
UTC
from
django.utils.timezone
import
UTC
from
capa.tests.response_xml_factory
import
OptionResponseXMLFactory
from
capa.tests.response_xml_factory
import
OptionResponseXMLFactory
...
@@ -20,7 +20,7 @@ from courseware.masquerade import (
...
@@ -20,7 +20,7 @@ from courseware.masquerade import (
get_masquerading_group_info
get_masquerading_group_info
)
)
from
courseware.tests.factories
import
StaffFactory
from
courseware.tests.factories
import
StaffFactory
from
courseware.tests.helpers
import
LoginEnrollmentTestCase
from
courseware.tests.helpers
import
LoginEnrollmentTestCase
,
masquerade_as_group_member
from
courseware.tests.test_submitting_problems
import
ProblemSubmissionTestMixin
from
courseware.tests.test_submitting_problems
import
ProblemSubmissionTestMixin
from
student.tests.factories
import
UserFactory
from
student.tests.factories
import
UserFactory
from
xblock.runtime
import
DictKeyValueStore
from
xblock.runtime
import
DictKeyValueStore
...
@@ -107,16 +107,6 @@ class MasqueradeTestCase(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -107,16 +107,6 @@ class MasqueradeTestCase(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
)
)
return
self
.
client
.
get
(
url
)
return
self
.
client
.
get
(
url
)
def
_create_mock_json_request
(
self
,
user
,
data
,
method
=
'POST'
,
session
=
None
):
"""
Returns a mock JSON request for the specified user
"""
factory
=
RequestFactory
()
request
=
factory
.
generic
(
method
,
'/'
,
content_type
=
'application/json'
,
data
=
json
.
dumps
(
data
))
request
.
user
=
user
request
.
session
=
session
or
{}
return
request
def
verify_staff_debug_present
(
self
,
staff_debug_expected
):
def
verify_staff_debug_present
(
self
,
staff_debug_expected
):
"""
"""
Verifies that the staff debug control visibility is as expected (for staff only).
Verifies that the staff debug control visibility is as expected (for staff only).
...
@@ -162,6 +152,19 @@ class MasqueradeTestCase(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -162,6 +152,19 @@ class MasqueradeTestCase(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
"Profile link should point to real user"
,
"Profile link should point to real user"
,
)
)
def
ensure_masquerade_as_group_member
(
self
,
partition_id
,
group_id
):
"""
Installs a masquerade for the test_user and test course, to enable the
user to masquerade as belonging to the specific partition/group combination.
Also verifies that the call to install the masquerade was successful.
Arguments:
partition_id (int): the integer partition id, referring to partitions already
configured in the course.
group_id (int); the integer group id, within the specified partition.
"""
self
.
assertEqual
(
200
,
masquerade_as_group_member
(
self
.
test_user
,
self
.
course
,
partition_id
,
group_id
))
@attr
(
shard
=
1
)
@attr
(
shard
=
1
)
class
NormalStudentVisibilityTest
(
MasqueradeTestCase
):
class
NormalStudentVisibilityTest
(
MasqueradeTestCase
):
...
@@ -405,13 +408,7 @@ class TestGetMasqueradingGroupId(StaffMasqueradeTestCase):
...
@@ -405,13 +408,7 @@ class TestGetMasqueradingGroupId(StaffMasqueradeTestCase):
self
.
assertIsNone
(
user_partition_id
)
self
.
assertIsNone
(
user_partition_id
)
# Install a masquerading group
# Install a masquerading group
request
=
self
.
_create_mock_json_request
(
self
.
ensure_masquerade_as_group_member
(
0
,
1
)
self
.
test_user
,
data
=
{
"role"
:
"student"
,
"user_partition_id"
:
0
,
"group_id"
:
1
}
)
response
=
handle_ajax
(
request
,
unicode
(
self
.
course
.
id
))
self
.
assertEquals
(
response
.
status_code
,
200
)
setup_masquerade
(
request
,
self
.
course
.
id
,
True
)
# Verify that the masquerading group is returned
# Verify that the masquerading group is returned
group_id
,
user_partition_id
=
get_masquerading_group_info
(
self
.
test_user
,
self
.
course
.
id
)
group_id
,
user_partition_id
=
get_masquerading_group_info
(
self
.
test_user
,
self
.
course
.
id
)
...
...
lms/djangoapps/discussion/static/discussion/js/spec/discussion_board_view_spec.js
View file @
67c3b083
...
@@ -30,6 +30,14 @@
...
@@ -30,6 +30,14 @@
return
discussionBoardView
;
return
discussionBoardView
;
};
};
describe
(
'Thread List View'
,
function
()
{
it
(
'should ensure the mode is all'
,
function
()
{
var
discussionBoardView
=
createDiscussionBoardView
().
render
(),
threadListView
=
discussionBoardView
.
discussionThreadListView
;
expect
(
threadListView
.
mode
).
toBe
(
'all'
);
});
});
describe
(
'Search events'
,
function
()
{
describe
(
'Search events'
,
function
()
{
it
(
'perform search when enter pressed inside search textfield'
,
function
()
{
it
(
'perform search when enter pressed inside search textfield'
,
function
()
{
var
discussionBoardView
=
createDiscussionBoardView
(),
var
discussionBoardView
=
createDiscussionBoardView
(),
...
...
lms/djangoapps/discussion/static/discussion/js/spec/views/discussion_user_profile_view_spec.js
View file @
67c3b083
...
@@ -30,13 +30,22 @@ DiscussionSpecHelper, DiscussionUserProfileView) {
...
@@ -30,13 +30,22 @@ DiscussionSpecHelper, DiscussionUserProfileView) {
describe
(
'thread list in user profile page'
,
function
()
{
describe
(
'thread list in user profile page'
,
function
()
{
it
(
'should render'
,
function
()
{
it
(
'should render'
,
function
()
{
var
discussionUserProfileView
=
createDiscussionUserProfileView
(),
var
discussionUserProfileView
=
createDiscussionUserProfileView
().
render
(),
threadListView
;
threadListView
=
discussionUserProfileView
.
discussionThreadListView
.
render
();
discussionUserProfileView
.
render
();
threadListView
=
discussionUserProfileView
.
discussionThreadListView
;
threadListView
.
render
();
expect
(
threadListView
.
$
(
'.forum-nav-thread-list'
).
length
).
toBe
(
1
);
expect
(
threadListView
.
$
(
'.forum-nav-thread-list'
).
length
).
toBe
(
1
);
});
});
it
(
'should ensure discussion thread list view mode is all'
,
function
()
{
var
discussionUserProfileView
=
createDiscussionUserProfileView
().
render
(),
threadListView
=
discussionUserProfileView
.
discussionThreadListView
.
render
();
expect
(
threadListView
.
mode
).
toBe
(
'user'
);
});
it
(
'should not show the thread list unread unanswered filter'
,
function
()
{
var
discussionUserProfileView
=
createDiscussionUserProfileView
().
render
(),
threadListView
=
discussionUserProfileView
.
discussionThreadListView
.
render
();
expect
(
threadListView
.
$
(
'.forum-nav-filter-main'
)).
toHaveClass
(
'is-hidden'
);
});
});
});
});
});
});
});
lms/djangoapps/discussion/static/discussion/js/views/discussion_board_view.js
View file @
67c3b083
...
@@ -46,7 +46,8 @@
...
@@ -46,7 +46,8 @@
collection
:
this
.
discussion
,
collection
:
this
.
discussion
,
el
:
this
.
$
(
'.discussion-thread-list-container'
),
el
:
this
.
$
(
'.discussion-thread-list-container'
),
courseSettings
:
this
.
courseSettings
,
courseSettings
:
this
.
courseSettings
,
supportsActiveThread
:
true
supportsActiveThread
:
true
,
mode
:
this
.
mode
}).
render
();
}).
render
();
this
.
searchView
=
new
DiscussionSearchView
({
this
.
searchView
=
new
DiscussionSearchView
({
el
:
this
.
$
(
'.forum-search'
)
el
:
this
.
$
(
'.forum-search'
)
...
...
lms/djangoapps/discussion/static/discussion/js/views/discussion_user_profile_view.js
View file @
67c3b083
...
@@ -26,7 +26,7 @@
...
@@ -26,7 +26,7 @@
initialize
:
function
(
options
)
{
initialize
:
function
(
options
)
{
this
.
courseSettings
=
options
.
courseSettings
;
this
.
courseSettings
=
options
.
courseSettings
;
this
.
discussion
=
options
.
discussion
;
this
.
discussion
=
options
.
discussion
;
this
.
mode
=
'
all
'
;
this
.
mode
=
'
user
'
;
this
.
listenTo
(
this
.
model
,
'change'
,
this
.
render
);
this
.
listenTo
(
this
.
model
,
'change'
,
this
.
render
);
},
},
...
@@ -39,7 +39,7 @@
...
@@ -39,7 +39,7 @@
collection
:
this
.
discussion
,
collection
:
this
.
discussion
,
el
:
this
.
$
(
'.inline-threads'
),
el
:
this
.
$
(
'.inline-threads'
),
courseSettings
:
this
.
courseSettings
,
courseSettings
:
this
.
courseSettings
,
hideRefineBar
:
true
,
// TODO: re-enable the search/filter bar when it works correctly
mode
:
this
.
mode
,
// @TODO: On the profile page, thread read state for the viewing user is not accessible via API.
// @TODO: On the profile page, thread read state for the viewing user is not accessible via API.
// Fix this when the Discussions API can support this query. Until then, hide read state.
// Fix this when the Discussions API can support this query. Until then, hide read state.
hideReadState
:
true
hideReadState
:
true
...
...
lms/djangoapps/discussion/templates/discussion/discussion_profile_page.html
View file @
67c3b083
...
@@ -76,6 +76,7 @@ from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_str
...
@@ -76,6 +76,7 @@ from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_str
data-course-name=
"${course.display_name_with_default}"
data-course-name=
"${course.display_name_with_default}"
data-threads=
"${threads}"
data-threads=
"${threads}"
data-user-info=
"${user_info}"
data-user-info=
"${user_info}"
data-user-id=
"${django_user.id}"
data-page=
"${page}"
data-page=
"${page}"
data-num-pages=
"${num_pages}"
data-num-pages=
"${num_pages}"
data-user-create-comment=
"${json.dumps(can_create_comment)}"
data-user-create-comment=
"${json.dumps(can_create_comment)}"
...
...
lms/djangoapps/teams/static/teams/js/spec/views/team_profile_spec.js
View file @
67c3b083
...
@@ -59,7 +59,8 @@ define([
...
@@ -59,7 +59,8 @@ define([
requests
,
requests
,
'GET'
,
'GET'
,
interpolate
(
interpolate
(
'/courses/%(courseID)s/discussion/forum/%(topicID)s/inline?page=1&ajax=1'
,
'/courses/%(courseID)s/discussion/forum/%(topicID)s/inline'
+
'?page=1&sort_key=activity&sort_order=desc&ajax=1'
,
{
{
courseID
:
TeamSpecHelpers
.
testCourseID
,
courseID
:
TeamSpecHelpers
.
testCourseID
,
topicID
:
TeamSpecHelpers
.
testTeamDiscussionID
topicID
:
TeamSpecHelpers
.
testTeamDiscussionID
...
...
lms/static/sass/discussion/elements/_navigation.scss
View file @
67c3b083
...
@@ -137,21 +137,26 @@
...
@@ -137,21 +137,26 @@
@include
text-align
(
left
);
@include
text-align
(
left
);
@include
float
(
left
);
@include
float
(
left
);
box-sizing
:
border-box
;
box-sizing
:
border-box
;
display
:
inline-block
;
width
:
50%
;
width
:
50%
;
}
}
.forum-nav-filter-cohort
,
.forum-nav-sort
{
.forum-nav-filter-cohort
,
.forum-nav-sort
{
@include
text-align
(
right
);
@include
text-align
(
right
);
@include
float
(
right
);
box-sizing
:
border-box
;
box-sizing
:
border-box
;
display
:
inline-block
;
}
@media
(
min-width
:
$bp-screen-md
)
{
.forum-nav-filter-cohort
{
.discussion-board
&
{
@include
float
(
right
);
@include
text-align
(
right
);
width
:
50%
;
width
:
50%
;
}
}
}
}
.forum-nav-sort
{
@include
float
(
right
);
}
%forum-nav-select
{
%forum-nav-select
{
border
:
none
;
border
:
none
;
max-width
:
100%
;
max-width
:
100%
;
...
...
openedx/core/djangoapps/course_groups/tests/test_partition_scheme.py
View file @
67c3b083
...
@@ -6,7 +6,6 @@ import django.test
...
@@ -6,7 +6,6 @@ import django.test
from
mock
import
patch
from
mock
import
patch
from
nose.plugins.attrib
import
attr
from
nose.plugins.attrib
import
attr
from
courseware.masquerade
import
handle_ajax
,
setup_masquerade
from
courseware.tests.test_masquerade
import
StaffMasqueradeTestCase
from
courseware.tests.test_masquerade
import
StaffMasqueradeTestCase
from
student.tests.factories
import
UserFactory
from
student.tests.factories
import
UserFactory
from
xmodule.partitions.partitions
import
Group
,
UserPartition
,
UserPartitionError
from
xmodule.partitions.partitions
import
Group
,
UserPartition
,
UserPartitionError
...
@@ -341,30 +340,17 @@ class TestMasqueradedGroup(StaffMasqueradeTestCase):
...
@@ -341,30 +340,17 @@ class TestMasqueradedGroup(StaffMasqueradeTestCase):
scheme_id
=
'cohort'
scheme_id
=
'cohort'
)
)
self
.
course
.
user_partitions
.
append
(
self
.
user_partition
)
self
.
course
.
user_partitions
.
append
(
self
.
user_partition
)
self
.
session
=
{}
modulestore
()
.
update_item
(
self
.
course
,
self
.
test_user
.
id
)
modulestore
()
.
update_item
(
self
.
course
,
self
.
test_user
.
id
)
def
_verify_masquerade_for_group
(
self
,
group
):
def
_verify_masquerade_for_group
(
self
,
group
):
"""
"""
Verify that the masquerade works for the specified group id.
Verify that the masquerade works for the specified group id.
"""
"""
# Send the request to set the masquerade
self
.
ensure_masquerade_as_group_member
(
# pylint: disable=no-member
request_json
=
{
self
.
user_partition
.
id
,
"role"
:
"student"
,
group
.
id
if
group
is
not
None
else
None
"user_partition_id"
:
self
.
user_partition
.
id
,
"group_id"
:
group
.
id
if
group
is
not
None
else
None
}
request
=
self
.
_create_mock_json_request
(
self
.
test_user
,
data
=
request_json
,
session
=
self
.
session
)
)
response
=
handle_ajax
(
request
,
unicode
(
self
.
course
.
id
))
# pylint has issues analyzing this class (maybe due to circular imports?)
self
.
assertEquals
(
response
.
status_code
,
200
)
# pylint: disable=no-member
# Now setup the masquerade for the test user
setup_masquerade
(
request
,
self
.
course
.
id
,
True
)
scheme
=
self
.
user_partition
.
scheme
scheme
=
self
.
user_partition
.
scheme
self
.
assertEqual
(
self
.
assertEqual
(
scheme
.
get_group_for_user
(
self
.
course
.
id
,
self
.
test_user
,
self
.
user_partition
),
scheme
.
get_group_for_user
(
self
.
course
.
id
,
self
.
test_user
,
self
.
user_partition
),
...
...
requirements/edx/github.txt
View file @
67c3b083
...
@@ -90,7 +90,7 @@ git+https://github.com/edx/xblock-utils.git@v1.0.3#egg=xblock-utils==1.0.3
...
@@ -90,7 +90,7 @@ git+https://github.com/edx/xblock-utils.git@v1.0.3#egg=xblock-utils==1.0.3
-e git+https://github.com/edx-solutions/xblock-google-drive.git@138e6fa0bf3a2013e904a085b9fed77dab7f3f21#egg=xblock-google-drive
-e git+https://github.com/edx-solutions/xblock-google-drive.git@138e6fa0bf3a2013e904a085b9fed77dab7f3f21#egg=xblock-google-drive
-e git+https://github.com/edx/edx-reverification-block.git@0.0.5#egg=edx-reverification-block==0.0.5
-e git+https://github.com/edx/edx-reverification-block.git@0.0.5#egg=edx-reverification-block==0.0.5
git+https://github.com/edx/edx-user-state-client.git@1.0.1#egg=edx-user-state-client==1.0.1
git+https://github.com/edx/edx-user-state-client.git@1.0.1#egg=edx-user-state-client==1.0.1
git+https://github.com/edx/xblock-lti-consumer.git@v1.1.
0#egg=xblock-lti-consumer==1.1.0
git+https://github.com/edx/xblock-lti-consumer.git@v1.1.
1#egg=xblock-lti-consumer==1.1.1
git+https://github.com/edx/edx-proctoring.git@0.17.0#egg=edx-proctoring==0.17.0
git+https://github.com/edx/edx-proctoring.git@0.17.0#egg=edx-proctoring==0.17.0
# Third Party XBlocks
# Third Party XBlocks
...
...
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