Commit 67c3b083 by Calen Pennington Committed by GitHub

Merge pull request #14366 from edx/release-candidate

Merge Release candidate to Release
parents e4c0c61d a432f15c
...@@ -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;
......
...@@ -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();
......
...@@ -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);
......
...@@ -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);
}; };
......
...@@ -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">
......
...@@ -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)
......
""" """
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
...@@ -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
......
...@@ -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)
......
...@@ -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(),
......
...@@ -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');
});
}); });
}); });
}); });
...@@ -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')
......
...@@ -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
......
...@@ -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)}"
......
...@@ -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
......
...@@ -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%;
......
...@@ -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),
......
...@@ -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
......
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