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
3ce494f5
Commit
3ce494f5
authored
Feb 11, 2015
by
Muhammad Ammar
Committed by
Usman Khalid
Mar 23, 2015
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Enable/disable cohorts from the instructor dashboard and move cohorts management to its own tab
TNL-1268
parent
d8396924
Show whitespace changes
Inline
Side-by-side
Showing
27 changed files
with
1010 additions
and
397 deletions
+1010
-397
common/test/acceptance/pages/lms/instructor_dashboard.py
+42
-13
common/test/acceptance/tests/discussion/test_cohort_management.py
+25
-5
common/test/acceptance/tests/test_cohorted_courseware.py
+1
-2
lms/djangoapps/instructor/views/instructor_dashboard.py
+18
-4
lms/static/coffee/src/instructor_dashboard/instructor_dashboard.coffee
+3
-0
lms/static/js/factories/cohorts_factory.js
+35
-0
lms/static/js/groups/models/course_cohort_settings.js
+16
-0
lms/static/js/groups/views/cohorts.js
+38
-5
lms/static/js/groups/views/course_cohort_settings_notification.js
+35
-0
lms/static/js/instructor_dashboard/cohort_management.js
+29
-0
lms/static/js/spec/groups/views/cohorts_spec.js
+81
-11
lms/static/js/spec/main.js
+10
-0
lms/static/sass/course/instructor/_instructor_2.scss
+243
-239
lms/templates/instructor/instructor_dashboard_2/cohort-form.underscore
+2
-2
lms/templates/instructor/instructor_dashboard_2/cohort-state.underscore
+4
-0
lms/templates/instructor/instructor_dashboard_2/cohort_management.html
+45
-0
lms/templates/instructor/instructor_dashboard_2/cohorts.underscore
+14
-13
lms/templates/instructor/instructor_dashboard_2/instructor_dashboard_2.html
+5
-1
lms/templates/instructor/instructor_dashboard_2/membership.html
+0
-50
lms/urls.py
+3
-0
openedx/core/djangoapps/course_groups/cohorts.py
+49
-4
openedx/core/djangoapps/course_groups/migrations/0004_auto__del_field_coursecohortssettings_cohorted_discussions__add_field_.py
+92
-0
openedx/core/djangoapps/course_groups/models.py
+16
-1
openedx/core/djangoapps/course_groups/tests/helpers.py
+16
-1
openedx/core/djangoapps/course_groups/tests/test_cohorts.py
+36
-5
openedx/core/djangoapps/course_groups/tests/test_views.py
+106
-40
openedx/core/djangoapps/course_groups/views.py
+46
-1
No files found.
common/test/acceptance/pages/lms/instructor_dashboard.py
View file @
3ce494f5
...
@@ -28,6 +28,15 @@ class InstructorDashboardPage(CoursePage):
...
@@ -28,6 +28,15 @@ class InstructorDashboardPage(CoursePage):
membership_section
.
wait_for_page
()
membership_section
.
wait_for_page
()
return
membership_section
return
membership_section
def
select_cohort_management
(
self
):
"""
Selects the cohort management tab and returns the CohortManagementSection
"""
self
.
q
(
css
=
'a[data-section=cohort_management]'
)
.
first
.
click
()
cohort_management_section
=
CohortManagementSection
(
self
.
browser
)
cohort_management_section
.
wait_for_page
()
return
cohort_management_section
def
select_data_download
(
self
):
def
select_data_download
(
self
):
"""
"""
Selects the data download tab and returns a DataDownloadPage.
Selects the data download tab and returns a DataDownloadPage.
...
@@ -84,16 +93,10 @@ class MembershipPage(PageObject):
...
@@ -84,16 +93,10 @@ class MembershipPage(PageObject):
"""
"""
return
MembershipPageAutoEnrollSection
(
self
.
browser
)
return
MembershipPageAutoEnrollSection
(
self
.
browser
)
def
select_cohort_management_section
(
self
):
"""
Returns the MembershipPageCohortManagementSection page object.
"""
return
MembershipPageCohortManagementSection
(
self
.
browser
)
class
MembershipPage
CohortManagementSection
(
PageObject
):
class
CohortManagementSection
(
PageObject
):
"""
"""
The
cohort management subsection of the Membership
section of the Instructor dashboard.
The
Cohort Management
section of the Instructor dashboard.
"""
"""
url
=
None
url
=
None
csv_browse_button_selector_css
=
'.csv-upload #file-upload-form-file'
csv_browse_button_selector_css
=
'.csv-upload #file-upload-form-file'
...
@@ -104,13 +107,13 @@ class MembershipPageCohortManagementSection(PageObject):
...
@@ -104,13 +107,13 @@ class MembershipPageCohortManagementSection(PageObject):
assignment_type_buttons_css
=
'.cohort-management-assignment-type-settings input'
assignment_type_buttons_css
=
'.cohort-management-assignment-type-settings input'
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
return
self
.
q
(
css
=
'.cohort-management
.membership-section
'
)
.
present
return
self
.
q
(
css
=
'.cohort-management'
)
.
present
def
_bounded_selector
(
self
,
selector
):
def
_bounded_selector
(
self
,
selector
):
"""
"""
Return `selector`, but limited to the cohort management context.
Return `selector`, but limited to the cohort management context.
"""
"""
return
'.cohort-management
.membership-section
{}'
.
format
(
selector
)
return
'.cohort-management {}'
.
format
(
selector
)
def
_get_cohort_options
(
self
):
def
_get_cohort_options
(
self
):
"""
"""
...
@@ -158,9 +161,9 @@ class MembershipPageCohortManagementSection(PageObject):
...
@@ -158,9 +161,9 @@ class MembershipPageCohortManagementSection(PageObject):
Return assignment settings disabled message in case of default cohort.
Return assignment settings disabled message in case of default cohort.
"""
"""
query
=
self
.
q
(
css
=
self
.
_bounded_selector
(
'.copy-error'
))
query
=
self
.
q
(
css
=
self
.
_bounded_selector
(
'.copy-error'
))
if
query
.
present
:
if
query
.
visible
:
return
query
.
text
[
0
]
return
query
.
text
[
0
]
else
:
return
''
return
''
@property
@property
...
@@ -232,7 +235,11 @@ class MembershipPageCohortManagementSection(PageObject):
...
@@ -232,7 +235,11 @@ class MembershipPageCohortManagementSection(PageObject):
Adds a new manual cohort with the specified name.
Adds a new manual cohort with the specified name.
If a content group should also be associated, the name of the content group should be specified.
If a content group should also be associated, the name of the content group should be specified.
"""
"""
create_buttons
=
self
.
q
(
css
=
self
.
_bounded_selector
(
".action-create"
))
add_cohort_selector
=
self
.
_bounded_selector
(
".action-create"
)
# We need to wait because sometime add cohort button is not in a state to be clickable.
self
.
wait_for_element_presence
(
add_cohort_selector
,
'Add Cohort button is present.'
)
create_buttons
=
self
.
q
(
css
=
add_cohort_selector
)
# There are 2 create buttons on the page. The second one is only present when no cohort yet exists
# There are 2 create buttons on the page. The second one is only present when no cohort yet exists
# (in which case the first is not visible). Click on the last present create button.
# (in which case the first is not visible). Click on the last present create button.
create_buttons
.
results
[
len
(
create_buttons
.
results
)
-
1
]
.
click
()
create_buttons
.
results
[
len
(
create_buttons
.
results
)
-
1
]
.
click
()
...
@@ -444,6 +451,28 @@ class MembershipPageCohortManagementSection(PageObject):
...
@@ -444,6 +451,28 @@ class MembershipPageCohortManagementSection(PageObject):
file_input
.
send_keys
(
path
)
file_input
.
send_keys
(
path
)
self
.
q
(
css
=
self
.
_bounded_selector
(
self
.
csv_upload_button_selector_css
))
.
first
.
click
()
self
.
q
(
css
=
self
.
_bounded_selector
(
self
.
csv_upload_button_selector_css
))
.
first
.
click
()
@property
def
is_cohorted
(
self
):
"""
Returns the state of `Enable Cohorts` checkbox state.
"""
return
self
.
q
(
css
=
self
.
_bounded_selector
(
'.cohorts-state'
))
.
selected
@is_cohorted.setter
def
is_cohorted
(
self
,
state
):
"""
Check/Uncheck the `Enable Cohorts` checkbox state.
"""
if
state
!=
self
.
is_cohorted
:
self
.
q
(
css
=
self
.
_bounded_selector
(
'.cohorts-state'
))
.
first
.
click
()
def
cohort_management_controls_visible
(
self
):
"""
Return the visibility status of cohort management controls(cohort selector section etc).
"""
return
(
self
.
q
(
css
=
self
.
_bounded_selector
(
'.cohort-management-nav'
))
.
visible
and
self
.
q
(
css
=
self
.
_bounded_selector
(
'.wrapper-cohort-supplemental'
))
.
visible
)
class
MembershipPageAutoEnrollSection
(
PageObject
):
class
MembershipPageAutoEnrollSection
(
PageObject
):
"""
"""
...
...
common/test/acceptance/tests/discussion/test_cohort_management.py
View file @
3ce494f5
...
@@ -63,8 +63,7 @@ class CohortConfigurationTest(EventsTestMixin, UniqueCourseTest, CohortTestMixin
...
@@ -63,8 +63,7 @@ class CohortConfigurationTest(EventsTestMixin, UniqueCourseTest, CohortTestMixin
# go to the membership page on the instructor dashboard
# go to the membership page on the instructor dashboard
self
.
instructor_dashboard_page
=
InstructorDashboardPage
(
self
.
browser
,
self
.
course_id
)
self
.
instructor_dashboard_page
=
InstructorDashboardPage
(
self
.
browser
,
self
.
course_id
)
self
.
instructor_dashboard_page
.
visit
()
self
.
instructor_dashboard_page
.
visit
()
membership_page
=
self
.
instructor_dashboard_page
.
select_membership
()
self
.
cohort_management_page
=
self
.
instructor_dashboard_page
.
select_cohort_management
()
self
.
cohort_management_page
=
membership_page
.
select_cohort_management_section
()
def
verify_cohort_description
(
self
,
cohort_name
,
expected_description
):
def
verify_cohort_description
(
self
,
cohort_name
,
expected_description
):
"""
"""
...
@@ -441,9 +440,31 @@ class CohortConfigurationTest(EventsTestMixin, UniqueCourseTest, CohortTestMixin
...
@@ -441,9 +440,31 @@ class CohortConfigurationTest(EventsTestMixin, UniqueCourseTest, CohortTestMixin
self
.
assertTrue
(
self
.
cohort_management_page
.
is_assignment_settings_disabled
)
self
.
assertTrue
(
self
.
cohort_management_page
.
is_assignment_settings_disabled
)
message
=
"There must be one cohort to which students can
be randomly
assigned."
message
=
"There must be one cohort to which students can
automatically be
assigned."
self
.
assertEqual
(
message
,
self
.
cohort_management_page
.
assignment_settings_message
)
self
.
assertEqual
(
message
,
self
.
cohort_management_page
.
assignment_settings_message
)
def
test_cohort_enable_disable
(
self
):
"""
Scenario: Cohort Enable/Disable checkbox related functionality is working as intended.
Given I have a cohorted course with a user.
And I can see the `Enable Cohorts` checkbox is checked.
And cohort management controls are visible.
When I uncheck the `Enable Cohorts` checkbox.
Then I cohort management controls are not visible.
And When I reload the page.
Then I can see the `Enable Cohorts` checkbox is unchecked.
And cohort management controls are not visible.
"""
self
.
assertTrue
(
self
.
cohort_management_page
.
is_cohorted
)
self
.
assertTrue
(
self
.
cohort_management_page
.
cohort_management_controls_visible
())
self
.
cohort_management_page
.
is_cohorted
=
False
self
.
assertFalse
(
self
.
cohort_management_page
.
cohort_management_controls_visible
())
self
.
browser
.
refresh
()
self
.
cohort_management_page
.
wait_for_page
()
self
.
assertFalse
(
self
.
cohort_management_page
.
is_cohorted
)
self
.
assertFalse
(
self
.
cohort_management_page
.
cohort_management_controls_visible
())
def
test_link_to_data_download
(
self
):
def
test_link_to_data_download
(
self
):
"""
"""
Scenario: a link is present from the cohort configuration in
Scenario: a link is present from the cohort configuration in
...
@@ -656,8 +677,7 @@ class CohortContentGroupAssociationTest(UniqueCourseTest, CohortTestMixin):
...
@@ -656,8 +677,7 @@ class CohortContentGroupAssociationTest(UniqueCourseTest, CohortTestMixin):
# go to the membership page on the instructor dashboard
# go to the membership page on the instructor dashboard
self
.
instructor_dashboard_page
=
InstructorDashboardPage
(
self
.
browser
,
self
.
course_id
)
self
.
instructor_dashboard_page
=
InstructorDashboardPage
(
self
.
browser
,
self
.
course_id
)
self
.
instructor_dashboard_page
.
visit
()
self
.
instructor_dashboard_page
.
visit
()
membership_page
=
self
.
instructor_dashboard_page
.
select_membership
()
self
.
cohort_management_page
=
self
.
instructor_dashboard_page
.
select_cohort_management
()
self
.
cohort_management_page
=
membership_page
.
select_cohort_management_section
()
def
test_no_content_group_linked
(
self
):
def
test_no_content_group_linked
(
self
):
"""
"""
...
...
common/test/acceptance/tests/test_cohorted_courseware.py
View file @
3ce494f5
...
@@ -154,8 +154,7 @@ class EndToEndCohortedCoursewareTest(ContainerBase):
...
@@ -154,8 +154,7 @@ class EndToEndCohortedCoursewareTest(ContainerBase):
"""
"""
instructor_dashboard_page
=
InstructorDashboardPage
(
self
.
browser
,
self
.
course_id
)
instructor_dashboard_page
=
InstructorDashboardPage
(
self
.
browser
,
self
.
course_id
)
instructor_dashboard_page
.
visit
()
instructor_dashboard_page
.
visit
()
membership_page
=
instructor_dashboard_page
.
select_membership
()
cohort_management_page
=
instructor_dashboard_page
.
select_cohort_management
()
cohort_management_page
=
membership_page
.
select_cohort_management_section
()
def
add_cohort_with_student
(
cohort_name
,
content_group
,
student
):
def
add_cohort_with_student
(
cohort_name
,
content_group
,
student
):
cohort_management_page
.
add_cohort
(
cohort_name
,
content_group
=
content_group
)
cohort_management_page
.
add_cohort
(
cohort_name
,
content_group
=
content_group
)
...
...
lms/djangoapps/instructor/views/instructor_dashboard.py
View file @
3ce494f5
...
@@ -65,9 +65,7 @@ def instructor_dashboard_2(request, course_id):
...
@@ -65,9 +65,7 @@ def instructor_dashboard_2(request, course_id):
'finance_admin'
:
CourseFinanceAdminRole
(
course_key
)
.
has_user
(
request
.
user
),
'finance_admin'
:
CourseFinanceAdminRole
(
course_key
)
.
has_user
(
request
.
user
),
'sales_admin'
:
CourseSalesAdminRole
(
course_key
)
.
has_user
(
request
.
user
),
'sales_admin'
:
CourseSalesAdminRole
(
course_key
)
.
has_user
(
request
.
user
),
'staff'
:
has_access
(
request
.
user
,
'staff'
,
course
),
'staff'
:
has_access
(
request
.
user
,
'staff'
,
course
),
'forum_admin'
:
has_forum_access
(
'forum_admin'
:
has_forum_access
(
request
.
user
,
course_key
,
FORUM_ROLE_ADMINISTRATOR
),
request
.
user
,
course_key
,
FORUM_ROLE_ADMINISTRATOR
),
}
}
if
not
access
[
'staff'
]:
if
not
access
[
'staff'
]:
...
@@ -79,6 +77,7 @@ def instructor_dashboard_2(request, course_id):
...
@@ -79,6 +77,7 @@ def instructor_dashboard_2(request, course_id):
_section_student_admin
(
course
,
access
),
_section_student_admin
(
course
,
access
),
_section_data_download
(
course
,
access
),
_section_data_download
(
course
,
access
),
_section_analytics
(
course
,
access
),
_section_analytics
(
course
,
access
),
_section_cohort_management
(
course
,
access
),
]
]
#check if there is corresponding entry in the CourseMode Table related to the Instructor Dashboard course
#check if there is corresponding entry in the CourseMode Table related to the Instructor Dashboard course
...
@@ -330,7 +329,22 @@ def _section_membership(course, access):
...
@@ -330,7 +329,22 @@ def _section_membership(course, access):
'modify_access_url'
:
reverse
(
'modify_access'
,
kwargs
=
{
'course_id'
:
unicode
(
course_key
)}),
'modify_access_url'
:
reverse
(
'modify_access'
,
kwargs
=
{
'course_id'
:
unicode
(
course_key
)}),
'list_forum_members_url'
:
reverse
(
'list_forum_members'
,
kwargs
=
{
'course_id'
:
unicode
(
course_key
)}),
'list_forum_members_url'
:
reverse
(
'list_forum_members'
,
kwargs
=
{
'course_id'
:
unicode
(
course_key
)}),
'update_forum_role_membership_url'
:
reverse
(
'update_forum_role_membership'
,
kwargs
=
{
'course_id'
:
unicode
(
course_key
)}),
'update_forum_role_membership_url'
:
reverse
(
'update_forum_role_membership'
,
kwargs
=
{
'course_id'
:
unicode
(
course_key
)}),
'cohorts_ajax_url'
:
reverse
(
'cohorts'
,
kwargs
=
{
'course_key_string'
:
unicode
(
course_key
)}),
}
return
section_data
def
_section_cohort_management
(
course
,
access
):
""" Provide data for the corresponding cohort management section """
course_key
=
course
.
id
section_data
=
{
'section_key'
:
'cohort_management'
,
'section_display_name'
:
_
(
'Cohort Management'
),
'access'
:
access
,
'course_cohort_settings_url'
:
reverse
(
'course_cohort_settings'
,
kwargs
=
{
'course_key_string'
:
unicode
(
course_key
)}
),
'cohorts_url'
:
reverse
(
'cohorts'
,
kwargs
=
{
'course_key_string'
:
unicode
(
course_key
)}),
'advanced_settings_url'
:
get_studio_url
(
course
,
'settings/advanced'
),
'advanced_settings_url'
:
get_studio_url
(
course
,
'settings/advanced'
),
'upload_cohorts_csv_url'
:
reverse
(
'add_users_to_cohorts'
,
kwargs
=
{
'course_id'
:
unicode
(
course_key
)}),
'upload_cohorts_csv_url'
:
reverse
(
'add_users_to_cohorts'
,
kwargs
=
{
'course_id'
:
unicode
(
course_key
)}),
}
}
...
...
lms/static/coffee/src/instructor_dashboard/instructor_dashboard.coffee
View file @
3ce494f5
...
@@ -176,6 +176,9 @@ setup_instructor_dashboard_sections = (idash_content) ->
...
@@ -176,6 +176,9 @@ setup_instructor_dashboard_sections = (idash_content) ->
,
,
constructor
:
window
.
InstructorDashboard
.
sections
.
Metrics
constructor
:
window
.
InstructorDashboard
.
sections
.
Metrics
$element
:
idash_content
.
find
".
#{
CSS_IDASH_SECTION
}
#metrics"
$element
:
idash_content
.
find
".
#{
CSS_IDASH_SECTION
}
#metrics"
,
constructor
:
window
.
InstructorDashboard
.
sections
.
CohortManagement
$element
:
idash_content
.
find
".
#{
CSS_IDASH_SECTION
}
#cohort_management"
]
]
sections_to_initialize
.
map
({
constructor
,
$element
})
->
sections_to_initialize
.
map
({
constructor
,
$element
})
->
...
...
lms/static/js/factories/cohorts_factory.js
0 → 100644
View file @
3ce494f5
;(
function
(
define
,
undefined
)
{
'use strict'
;
define
([
'jquery'
,
'js/groups/views/cohorts'
,
'js/groups/collections/cohort'
,
'js/groups/models/course_cohort_settings'
],
function
(
$
)
{
return
function
(
contentGroups
,
studioGroupConfigurationsUrl
)
{
var
cohorts
=
new
edx
.
groups
.
CohortCollection
(),
courseCohortSettings
=
new
edx
.
groups
.
CourseCohortSettingsModel
();
var
cohortManagementElement
=
$
(
'.cohort-management'
);
cohorts
.
url
=
cohortManagementElement
.
data
(
'cohorts_url'
);
courseCohortSettings
.
url
=
cohortManagementElement
.
data
(
'course_cohort_settings_url'
);
var
cohortsView
=
new
edx
.
groups
.
CohortsView
({
el
:
cohortManagementElement
,
model
:
cohorts
,
contentGroups
:
contentGroups
,
cohortSettings
:
courseCohortSettings
,
context
:
{
uploadCohortsCsvUrl
:
cohortManagementElement
.
data
(
'upload_cohorts_csv_url'
),
studioAdvancedSettingsUrl
:
cohortManagementElement
.
data
(
'advanced-settings-url'
),
studioGroupConfigurationsUrl
:
studioGroupConfigurationsUrl
}
});
cohorts
.
fetch
().
done
(
function
()
{
courseCohortSettings
.
fetch
().
done
(
function
()
{
cohortsView
.
render
();
})
});
};
});
}).
call
(
this
,
define
||
RequireJS
.
define
);
lms/static/js/groups/models/course_cohort_settings.js
0 → 100644
View file @
3ce494f5
var
edx
=
edx
||
{};
(
function
(
Backbone
)
{
'use strict'
;
edx
.
groups
=
edx
.
groups
||
{};
edx
.
groups
.
CourseCohortSettingsModel
=
Backbone
.
Model
.
extend
({
idAttribute
:
'id'
,
defaults
:
{
is_cohorted
:
false
,
cohorted_discussions
:
[],
always_cohort_inline_discussions
:
true
}
});
}).
call
(
this
,
Backbone
);
lms/static/js/groups/views/cohorts.js
View file @
3ce494f5
var
edx
=
edx
||
{};
var
edx
=
edx
||
{};
(
function
(
$
,
_
,
Backbone
,
gettext
,
interpolate_text
,
CohortModel
,
CohortEditorView
,
CohortFormView
,
(
function
(
$
,
_
,
Backbone
,
gettext
,
interpolate_text
,
CohortModel
,
CohortEditorView
,
CohortFormView
,
NotificationModel
,
NotificationView
,
FileUploaderView
)
{
CourseCohortSettingsNotificationView
,
NotificationModel
,
NotificationView
,
FileUploaderView
)
{
'use strict'
;
'use strict'
;
var
hiddenClass
=
'is-hidden'
,
var
hiddenClass
=
'is-hidden'
,
...
@@ -12,6 +12,7 @@ var edx = edx || {};
...
@@ -12,6 +12,7 @@ var edx = edx || {};
edx
.
groups
.
CohortsView
=
Backbone
.
View
.
extend
({
edx
.
groups
.
CohortsView
=
Backbone
.
View
.
extend
({
events
:
{
events
:
{
'change .cohort-select'
:
'onCohortSelected'
,
'change .cohort-select'
:
'onCohortSelected'
,
'change .cohorts-state'
:
'onCohortsEnabledChanged'
,
'click .action-create'
:
'showAddCohortForm'
,
'click .action-create'
:
'showAddCohortForm'
,
'click .cohort-management-add-form .action-save'
:
'saveAddCohortForm'
,
'click .cohort-management-add-form .action-save'
:
'saveAddCohortForm'
,
'click .cohort-management-add-form .action-cancel'
:
'cancelAddCohortForm'
,
'click .cohort-management-add-form .action-cancel'
:
'cancelAddCohortForm'
,
...
@@ -26,19 +27,21 @@ var edx = edx || {};
...
@@ -26,19 +27,21 @@ var edx = edx || {};
this
.
selectorTemplate
=
_
.
template
(
$
(
'#cohort-selector-tpl'
).
text
());
this
.
selectorTemplate
=
_
.
template
(
$
(
'#cohort-selector-tpl'
).
text
());
this
.
context
=
options
.
context
;
this
.
context
=
options
.
context
;
this
.
contentGroups
=
options
.
contentGroups
;
this
.
contentGroups
=
options
.
contentGroups
;
this
.
cohortSettings
=
options
.
cohortSettings
;
model
.
on
(
'sync'
,
this
.
onSync
,
this
);
model
.
on
(
'sync'
,
this
.
onSync
,
this
);
// Update cohort counts when the user clicks back on the
membership
tab
// Update cohort counts when the user clicks back on the
cohort management
tab
// (for example, after uploading a csv file of cohort assignments and then
// (for example, after uploading a csv file of cohort assignments and then
// checking results on data download tab).
// checking results on data download tab).
$
(
this
.
getSectionCss
(
'
membership
'
)).
click
(
function
()
{
$
(
this
.
getSectionCss
(
'
cohort_management
'
)).
click
(
function
()
{
model
.
fetch
();
model
.
fetch
();
});
});
},
},
render
:
function
()
{
render
:
function
()
{
this
.
$el
.
html
(
this
.
template
({
this
.
$el
.
html
(
this
.
template
({
cohorts
:
this
.
model
.
models
cohorts
:
this
.
model
.
models
,
cohortsEnabled
:
this
.
cohortSettings
.
get
(
'is_cohorted'
)
}));
}));
this
.
onSync
();
this
.
onSync
();
return
this
;
return
this
;
...
@@ -51,6 +54,13 @@ var edx = edx || {};
...
@@ -51,6 +54,13 @@ var edx = edx || {};
}));
}));
},
},
renderCourseCohortSettingsNotificationView
:
function
()
{
var
cohortStateMessageNotificationView
=
new
CourseCohortSettingsNotificationView
({
el
:
$
(
'.cohort-state-message'
),
cohortEnabled
:
this
.
getCohortsEnabled
()});
cohortStateMessageNotificationView
.
render
();
},
onSync
:
function
(
model
,
response
,
options
)
{
onSync
:
function
(
model
,
response
,
options
)
{
var
selectedCohort
=
this
.
lastSelectedCohortId
&&
this
.
model
.
get
(
this
.
lastSelectedCohortId
),
var
selectedCohort
=
this
.
lastSelectedCohortId
&&
this
.
model
.
get
(
this
.
lastSelectedCohortId
),
hasCohorts
=
this
.
model
.
length
>
0
,
hasCohorts
=
this
.
model
.
length
>
0
,
...
@@ -98,6 +108,28 @@ var edx = edx || {};
...
@@ -98,6 +108,28 @@ var edx = edx || {};
this
.
showCohortEditor
(
selectedCohort
);
this
.
showCohortEditor
(
selectedCohort
);
},
},
onCohortsEnabledChanged
:
function
(
event
)
{
event
.
preventDefault
();
this
.
saveCohortSettings
();
},
saveCohortSettings
:
function
()
{
var
self
=
this
,
cohortSettings
,
fieldData
=
{
is_cohorted
:
this
.
getCohortsEnabled
()};
cohortSettings
=
this
.
cohortSettings
;
cohortSettings
.
save
(
fieldData
,
{
wait
:
true
}
).
done
(
function
()
{
self
.
render
();
self
.
renderCourseCohortSettingsNotificationView
();
});
},
getCohortsEnabled
:
function
()
{
return
this
.
$
(
'.cohorts-state'
).
prop
(
'checked'
);
},
showCohortEditor
:
function
(
cohort
)
{
showCohortEditor
:
function
(
cohort
)
{
this
.
removeNotification
();
this
.
removeNotification
();
if
(
this
.
editor
)
{
if
(
this
.
editor
)
{
...
@@ -242,4 +274,5 @@ var edx = edx || {};
...
@@ -242,4 +274,5 @@ var edx = edx || {};
}
}
});
});
}).
call
(
this
,
$
,
_
,
Backbone
,
gettext
,
interpolate_text
,
edx
.
groups
.
CohortModel
,
edx
.
groups
.
CohortEditorView
,
}).
call
(
this
,
$
,
_
,
Backbone
,
gettext
,
interpolate_text
,
edx
.
groups
.
CohortModel
,
edx
.
groups
.
CohortEditorView
,
edx
.
groups
.
CohortFormView
,
NotificationModel
,
NotificationView
,
FileUploaderView
);
edx
.
groups
.
CohortFormView
,
edx
.
groups
.
CourseCohortSettingsNotificationView
,
NotificationModel
,
NotificationView
,
FileUploaderView
);
lms/static/js/groups/views/course_cohort_settings_notification.js
0 → 100644
View file @
3ce494f5
var
edx
=
edx
||
{};
(
function
(
$
,
_
,
Backbone
,
gettext
)
{
'use strict'
;
edx
.
groups
=
edx
.
groups
||
{};
edx
.
groups
.
CourseCohortSettingsNotificationView
=
Backbone
.
View
.
extend
({
initialize
:
function
(
options
)
{
this
.
template
=
_
.
template
(
$
(
'#cohort-state-tpl'
).
text
());
this
.
cohortEnabled
=
options
.
cohortEnabled
;
},
render
:
function
()
{
this
.
$el
.
html
(
this
.
template
({}));
this
.
showCohortStateMessage
();
return
this
;
},
showCohortStateMessage
:
function
()
{
var
actionToggleMessage
=
this
.
$
(
'.action-toggle-message'
);
// The following lines are necessary to re-trigger the CSS animation on span.action-toggle-message
actionToggleMessage
.
removeClass
(
'is-fleeting'
);
actionToggleMessage
.
offset
().
width
=
actionToggleMessage
.
offset
().
width
;
actionToggleMessage
.
addClass
(
'is-fleeting'
);
if
(
this
.
cohortEnabled
)
{
actionToggleMessage
.
text
(
gettext
(
'Cohorts Enabled'
));
}
else
{
actionToggleMessage
.
text
(
gettext
(
'Cohorts Disabled'
));
}
}
});
}).
call
(
this
,
$
,
_
,
Backbone
,
gettext
);
lms/static/js/instructor_dashboard/cohort_management.js
0 → 100644
View file @
3ce494f5
(
function
()
{
var
CohortManagement
;
CohortManagement
=
(
function
()
{
function
CohortManagement
(
$section
)
{
this
.
$section
=
$section
;
this
.
$section
.
data
(
'wrapper'
,
this
);
}
CohortManagement
.
prototype
.
onClickTitle
=
function
()
{};
return
CohortManagement
;
})();
_
.
defaults
(
window
,
{
InstructorDashboard
:
{}
});
_
.
defaults
(
window
.
InstructorDashboard
,
{
sections
:
{}
});
_
.
defaults
(
window
.
InstructorDashboard
.
sections
,
{
CohortManagement
:
CohortManagement
});
}).
call
(
this
);
lms/static/js/spec/groups/views/cohorts_spec.js
View file @
3ce494f5
define
([
'backbone'
,
'jquery'
,
'js/common_helpers/ajax_helpers'
,
'js/common_helpers/template_helpers'
,
define
([
'backbone'
,
'jquery'
,
'js/common_helpers/ajax_helpers'
,
'js/common_helpers/template_helpers'
,
'js/groups/views/cohorts'
,
'js/groups/collections/cohort'
,
'js/groups/models/content_group'
],
'js/groups/views/cohorts'
,
'js/groups/collections/cohort'
,
'js/groups/models/content_group'
,
function
(
Backbone
,
$
,
AjaxHelpers
,
TemplateHelpers
,
CohortsView
,
CohortCollection
,
ContentGroupModel
)
{
'js/groups/models/course_cohort_settings'
,
'js/groups/views/course_cohort_settings_notification'
],
function
(
Backbone
,
$
,
AjaxHelpers
,
TemplateHelpers
,
CohortsView
,
CohortCollection
,
ContentGroupModel
,
CourseCohortSettingsModel
,
CourseCohortSettingsNotificationView
)
{
'use strict'
;
'use strict'
;
describe
(
"Cohorts View"
,
function
()
{
describe
(
"Cohorts View"
,
function
()
{
var
catLoversInitialCount
=
123
,
dogLoversInitialCount
=
456
,
unknownUserMessage
,
var
catLoversInitialCount
=
123
,
dogLoversInitialCount
=
456
,
unknownUserMessage
,
createMockCohort
,
createMockCohorts
,
createMockContentGroups
,
createCohortsView
,
cohortsView
,
createMockCohort
,
createMockCohorts
,
createMockContentGroups
,
createCohortSettings
,
createCohortsView
,
requests
,
respondToRefresh
,
verifyMessage
,
verifyNoMessage
,
verifyDetailedMessage
,
verifyHeader
,
cohortsView
,
requests
,
respondToRefresh
,
verifyMessage
,
verifyNoMessage
,
verifyDetailedMessage
,
expectCohortAddRequest
,
getAddModal
,
selectContentGroup
,
clearContentGroup
,
saveFormAndExpectErrors
,
verifyHeader
,
expectCohortAddRequest
,
getAddModal
,
selectContentGroup
,
clearContentGroup
,
MOCK_COHORTED_USER_PARTITION_ID
,
MOCK_UPLOAD_COHORTS_CSV_URL
,
MOCK_STUDIO_ADVANCED_SETTINGS_URL
,
saveFormAndExpectErrors
,
createMockCohortSettings
,
MOCK_COHORTED_USER_PARTITION_ID
,
MOCK_STUDIO_GROUP_CONFIGURATIONS_URL
,
MOCK_MANUAL_ASSIGNMENT
,
MOCK_RANDOM_ASSIGNMENT
;
MOCK_UPLOAD_COHORTS_CSV_URL
,
MOCK_STUDIO_ADVANCED_SETTINGS_URL
,
MOCK_STUDIO_GROUP_CONFIGURATIONS_URL
,
MOCK_MANUAL_ASSIGNMENT
,
MOCK_RANDOM_ASSIGNMENT
;
MOCK_MANUAL_ASSIGNMENT
=
'manual'
;
MOCK_MANUAL_ASSIGNMENT
=
'manual'
;
MOCK_RANDOM_ASSIGNMENT
=
'random'
;
MOCK_RANDOM_ASSIGNMENT
=
'random'
;
...
@@ -49,17 +52,35 @@ define(['backbone', 'jquery', 'js/common_helpers/ajax_helpers', 'js/common_helpe
...
@@ -49,17 +52,35 @@ define(['backbone', 'jquery', 'js/common_helpers/ajax_helpers', 'js/common_helpe
];
];
};
};
createMockCohortSettings
=
function
(
isCohorted
,
cohortedDiscussions
,
alwaysCohortInlineDiscussions
)
{
return
{
id
:
0
,
is_cohorted
:
isCohorted
||
false
,
cohorted_discussions
:
cohortedDiscussions
||
[],
always_cohort_inline_discussions
:
alwaysCohortInlineDiscussions
||
true
};
};
createCohortSettings
=
function
(
isCohorted
,
cohortedDiscussions
,
alwaysCohortInlineDiscussions
)
{
return
new
CourseCohortSettingsModel
(
createMockCohortSettings
(
isCohorted
,
cohortedDiscussions
,
alwaysCohortInlineDiscussions
)
);
};
createCohortsView
=
function
(
test
,
options
)
{
createCohortsView
=
function
(
test
,
options
)
{
var
cohortsJson
,
cohorts
,
contentGroups
;
var
cohortsJson
,
cohorts
,
contentGroups
,
cohortSettings
;
options
=
options
||
{};
options
=
options
||
{};
cohortsJson
=
options
.
cohorts
?
{
cohorts
:
options
.
cohorts
}
:
createMockCohorts
();
cohortsJson
=
options
.
cohorts
?
{
cohorts
:
options
.
cohorts
}
:
createMockCohorts
();
cohorts
=
new
CohortCollection
(
cohortsJson
,
{
parse
:
true
});
cohorts
=
new
CohortCollection
(
cohortsJson
,
{
parse
:
true
});
contentGroups
=
options
.
contentGroups
||
createMockContentGroups
();
contentGroups
=
options
.
contentGroups
||
createMockContentGroups
();
cohortSettings
=
options
.
cohortSettings
||
createCohortSettings
(
true
);
cohortSettings
.
url
=
'/mock_service/cohorts/settings'
;
cohorts
.
url
=
'/mock_service/cohorts'
;
cohorts
.
url
=
'/mock_service/cohorts'
;
requests
=
AjaxHelpers
.
requests
(
test
);
requests
=
AjaxHelpers
.
requests
(
test
);
cohortsView
=
new
CohortsView
({
cohortsView
=
new
CohortsView
({
model
:
cohorts
,
model
:
cohorts
,
contentGroups
:
contentGroups
,
contentGroups
:
contentGroups
,
cohortSettings
:
cohortSettings
,
context
:
{
context
:
{
uploadCohortsCsvUrl
:
MOCK_UPLOAD_COHORTS_CSV_URL
,
uploadCohortsCsvUrl
:
MOCK_UPLOAD_COHORTS_CSV_URL
,
studioAdvancedSettingsUrl
:
MOCK_STUDIO_ADVANCED_SETTINGS_URL
,
studioAdvancedSettingsUrl
:
MOCK_STUDIO_ADVANCED_SETTINGS_URL
,
...
@@ -177,13 +198,14 @@ define(['backbone', 'jquery', 'js/common_helpers/ajax_helpers', 'js/common_helpe
...
@@ -177,13 +198,14 @@ define(['backbone', 'jquery', 'js/common_helpers/ajax_helpers', 'js/common_helpe
};
};
beforeEach
(
function
()
{
beforeEach
(
function
()
{
setFixtures
(
'<ul class="instructor-nav"><li class="nav-item"><<a href data-section="
membership" class="active-section">Membership</a></li></ul><div
></div>'
);
setFixtures
(
'<ul class="instructor-nav"><li class="nav-item"><<a href data-section="
cohort_management" class="active-section">Cohort Management</a></li></ul><div></div><div class="cohort-state-message"
></div>'
);
TemplateHelpers
.
installTemplate
(
'templates/instructor/instructor_dashboard_2/cohorts'
);
TemplateHelpers
.
installTemplate
(
'templates/instructor/instructor_dashboard_2/cohorts'
);
TemplateHelpers
.
installTemplate
(
'templates/instructor/instructor_dashboard_2/cohort-form'
);
TemplateHelpers
.
installTemplate
(
'templates/instructor/instructor_dashboard_2/cohort-form'
);
TemplateHelpers
.
installTemplate
(
'templates/instructor/instructor_dashboard_2/cohort-selector'
);
TemplateHelpers
.
installTemplate
(
'templates/instructor/instructor_dashboard_2/cohort-selector'
);
TemplateHelpers
.
installTemplate
(
'templates/instructor/instructor_dashboard_2/cohort-editor'
);
TemplateHelpers
.
installTemplate
(
'templates/instructor/instructor_dashboard_2/cohort-editor'
);
TemplateHelpers
.
installTemplate
(
'templates/instructor/instructor_dashboard_2/cohort-group-header'
);
TemplateHelpers
.
installTemplate
(
'templates/instructor/instructor_dashboard_2/cohort-group-header'
);
TemplateHelpers
.
installTemplate
(
'templates/instructor/instructor_dashboard_2/notification'
);
TemplateHelpers
.
installTemplate
(
'templates/instructor/instructor_dashboard_2/notification'
);
TemplateHelpers
.
installTemplate
(
'templates/instructor/instructor_dashboard_2/cohort-state'
);
TemplateHelpers
.
installTemplate
(
'templates/file-upload'
);
TemplateHelpers
.
installTemplate
(
'templates/file-upload'
);
});
});
...
@@ -202,7 +224,7 @@ define(['backbone', 'jquery', 'js/common_helpers/ajax_helpers', 'js/common_helpe
...
@@ -202,7 +224,7 @@ define(['backbone', 'jquery', 'js/common_helpers/ajax_helpers', 'js/common_helpe
it
(
"syncs data when membership tab is clicked"
,
function
()
{
it
(
"syncs data when membership tab is clicked"
,
function
()
{
createCohortsView
(
this
,
{
selectCohort
:
1
});
createCohortsView
(
this
,
{
selectCohort
:
1
});
verifyHeader
(
1
,
'Cat Lovers'
,
catLoversInitialCount
);
verifyHeader
(
1
,
'Cat Lovers'
,
catLoversInitialCount
);
$
(
cohortsView
.
getSectionCss
(
"
membership
"
)).
click
();
$
(
cohortsView
.
getSectionCss
(
"
cohort_management
"
)).
click
();
AjaxHelpers
.
expectRequest
(
requests
,
'GET'
,
'/mock_service/cohorts'
);
AjaxHelpers
.
expectRequest
(
requests
,
'GET'
,
'/mock_service/cohorts'
);
respondToRefresh
(
1001
,
2
);
respondToRefresh
(
1001
,
2
);
verifyHeader
(
1
,
'Cat Lovers'
,
1001
);
verifyHeader
(
1
,
'Cat Lovers'
,
1001
);
...
@@ -255,6 +277,54 @@ define(['backbone', 'jquery', 'js/common_helpers/ajax_helpers', 'js/common_helpe
...
@@ -255,6 +277,54 @@ define(['backbone', 'jquery', 'js/common_helpers/ajax_helpers', 'js/common_helpe
});
});
});
});
describe
(
"Course Cohort Settings"
,
function
()
{
it
(
'enable/disable working correctly'
,
function
()
{
createCohortsView
(
this
,
{
cohortSettings
:
createCohortSettings
(
false
)});
expect
(
cohortsView
.
$
(
'.cohorts-state'
).
prop
(
'checked'
)).
toBeFalsy
();
cohortsView
.
$
(
'.cohorts-state'
).
prop
(
'checked'
,
true
).
change
();
AjaxHelpers
.
expectJsonRequest
(
requests
,
'PUT'
,
'/mock_service/cohorts/settings'
,
createMockCohortSettings
(
true
,
[],
true
)
);
AjaxHelpers
.
respondWithJson
(
requests
,
createMockCohortSettings
(
true
)
);
expect
(
cohortsView
.
$
(
'.cohorts-state'
).
prop
(
'checked'
)).
toBeTruthy
();
cohortsView
.
$
(
'.cohorts-state'
).
prop
(
'checked'
,
false
).
change
();
AjaxHelpers
.
expectJsonRequest
(
requests
,
'PUT'
,
'/mock_service/cohorts/settings'
,
createMockCohortSettings
(
false
,
[],
true
)
);
AjaxHelpers
.
respondWithJson
(
requests
,
createMockCohortSettings
(
false
)
);
expect
(
cohortsView
.
$
(
'.cohorts-state'
).
prop
(
'checked'
)).
toBeFalsy
();
});
it
(
'Course Cohort Settings Notification View renders correctly'
,
function
()
{
var
createCourseCohortSettingsNotificationView
=
function
(
is_cohorted
)
{
var
notificationView
=
new
CourseCohortSettingsNotificationView
({
el
:
$
(
'.cohort-state-message'
),
cohortEnabled
:
is_cohorted
});
notificationView
.
render
();
return
notificationView
;
};
var
notificationView
=
createCourseCohortSettingsNotificationView
(
true
);
expect
(
notificationView
.
$
(
'.action-toggle-message'
).
text
().
trim
()).
toBe
(
'Cohorts Enabled'
);
notificationView
=
createCourseCohortSettingsNotificationView
(
false
);
expect
(
notificationView
.
$
(
'.action-toggle-message'
).
text
().
trim
()).
toBe
(
'Cohorts Disabled'
);
});
});
describe
(
"Cohort Group Header"
,
function
()
{
describe
(
"Cohort Group Header"
,
function
()
{
it
(
"renders header correctly"
,
function
()
{
it
(
"renders header correctly"
,
function
()
{
var
cohortName
=
'Transformers'
,
var
cohortName
=
'Transformers'
,
...
@@ -867,7 +937,7 @@ define(['backbone', 'jquery', 'js/common_helpers/ajax_helpers', 'js/common_helpe
...
@@ -867,7 +937,7 @@ define(['backbone', 'jquery', 'js/common_helpers/ajax_helpers', 'js/common_helpe
// We have a single random cohort so we should not be allowed to change it assignment type
// We have a single random cohort so we should not be allowed to change it assignment type
expect
(
cohortsView
.
$
(
'.cohort-management-assignment-type-settings'
)).
toHaveClass
(
'is-disabled'
);
expect
(
cohortsView
.
$
(
'.cohort-management-assignment-type-settings'
)).
toHaveClass
(
'is-disabled'
);
expect
(
cohortsView
.
$
(
'.copy-error'
).
text
()).
toContain
(
"There must be one cohort to which students can
be randomly
assigned."
);
expect
(
cohortsView
.
$
(
'.copy-error'
).
text
()).
toContain
(
"There must be one cohort to which students can
automatically be
assigned."
);
});
});
it
(
"cancel settings works"
,
function
()
{
it
(
"cancel settings works"
,
function
()
{
...
...
lms/static/js/spec/main.js
View file @
3ce494f5
...
@@ -67,6 +67,8 @@
...
@@ -67,6 +67,8 @@
'js/views/notification'
:
'js/views/notification'
,
'js/views/notification'
:
'js/views/notification'
,
'js/groups/models/cohort'
:
'js/groups/models/cohort'
,
'js/groups/models/cohort'
:
'js/groups/models/cohort'
,
'js/groups/models/content_group'
:
'js/groups/models/content_group'
,
'js/groups/models/content_group'
:
'js/groups/models/content_group'
,
'js/groups/models/course_cohort_settings'
:
'js/groups/models/course_cohort_settings'
,
'js/groups/views/course_cohort_settings_notification'
:
'js/groups/views/course_cohort_settings_notification'
,
'js/groups/collections/cohort'
:
'js/groups/collections/cohort'
,
'js/groups/collections/cohort'
:
'js/groups/collections/cohort'
,
'js/groups/views/cohort_editor'
:
'js/groups/views/cohort_editor'
,
'js/groups/views/cohort_editor'
:
'js/groups/views/cohort_editor'
,
'js/groups/views/cohort_form'
:
'js/groups/views/cohort_form'
,
'js/groups/views/cohort_form'
:
'js/groups/views/cohort_form'
,
...
@@ -294,6 +296,14 @@
...
@@ -294,6 +296,14 @@
exports
:
'edx.groups.ContentGroupModel'
,
exports
:
'edx.groups.ContentGroupModel'
,
deps
:
[
'backbone'
]
deps
:
[
'backbone'
]
},
},
'js/groups/models/course_cohort_settings'
:
{
exports
:
'edx.groups.CourseCohortSettingsModel'
,
deps
:
[
'backbone'
]
},
'js/groups/views/course_cohort_settings_notification'
:
{
exports
:
'edx.groups.CourseCohortSettingsNotificationView'
,
deps
:
[
'backbone'
]
},
'js/groups/collections/cohort'
:
{
'js/groups/collections/cohort'
:
{
exports
:
'edx.groups.CohortCollection'
,
exports
:
'edx.groups.CohortCollection'
,
deps
:
[
'backbone'
,
'js/groups/models/cohort'
]
deps
:
[
'backbone'
,
'js/groups/models/cohort'
]
...
...
lms/static/sass/course/instructor/_instructor_2.scss
View file @
3ce494f5
...
@@ -435,6 +435,248 @@
...
@@ -435,6 +435,248 @@
}
}
}
}
.batch-enrollment
,
.batch-beta-testers
{
textarea
{
margin-top
:
0
.2em
;
height
:
auto
;
width
:
90%
;
}
input
{
margin-right
:
(
$baseline
/
4
);
}
.request-res-section
{
margin-top
:
1
.5em
;
h3
{
color
:
#646464
;
}
ul
{
margin
:
0
;
margin-top
:
0
.5em
;
padding
:
0
;
list-style-type
:
none
;
line-height
:
1
.5em
;
}
}
}
// Auto Enroll Csv Section
.auto_enroll_csv
{
.results
{
}
.enrollment_signup_button
{
@include
margin-right
(
$baseline
/
4
);
}
// Custom File upload
.customBrowseBtn
{
margin
:
(
$baseline
/
2
)
0
;
display
:
inline-block
;
.file-browse
{
position
:relative
;
overflow
:hidden
;
display
:
inline
;
@include
margin-left
(
-5px
);
span
.browse
{
@include
button
(
simple
,
$blue
);
@include
margin-right
(
$baseline
);
padding
:
6px
(
$baseline
/
2
);
font-size
:
12px
;
border-radius
:
0
3px
3px
0
;
}
input
.file_field
{
position
:absolute
;
@include
right
(
0
);
top
:
0
;
margin
:
0
;
padding
:
0
;
cursor
:pointer
;
opacity
:
0
;
filter
:alpha
(
opacity
=
0
)
;
}
}
&
>
span
,
&
input
[
disabled
]
{
vertical-align
:
middle
;
}
input
[
disabled
]
{
@include
border-radius
(
4px
0
0
4px
);
@include
padding
(
6px
6px
5px
);
border
:
1px
solid
$lightGrey1
;
cursor
:
not
-
allowed
;
}
}
}
.enroll-option
{
margin
:
(
$baseline
/
2
)
0
;
position
:
relative
;
label
{
border-bottom
:
1px
dotted
$base-font-color
;
}
.hint
{
@extend
%t-copy-sub2
;
display
:
none
;
position
:
absolute
;
top
:
15px
;
@include
left
(
$baseline
*
10
);
padding
:
(
$baseline
/
2
);
width
:
50%
;
background-color
:
$light-gray
;
box-shadow
:
2px
2px
3px
$shadow
;
.hint-caret
{
display
:
block
;
position
:
absolute
;
top
:
0
;
@include
left
(
-15px
);
@include
border-right
(
8px
solid
$light-gray
);
@include
border-left
(
8px
solid
transparent
);
border-top
:
8px
solid
$light-gray
;
border-bottom
:
8px
solid
transparent
;
}
}
}
label
[
for
=
"auto-enroll"
]
:hover
+
.auto-enroll-hint
{
display
:
block
;
}
label
[
for
=
"auto-enroll-beta"
]
:hover
+
.auto-enroll-beta-hint
{
width
:
30%
;
display
:
block
;
}
label
[
for
=
"email-students"
]
:hover
+
.email-students-hint
{
display
:
block
;
}
label
[
for
=
"email-students-beta"
]
:hover
+
.email-students-beta-hint
{
width
:
30%
;
display
:
block
;
}
.enroll-actions
{
margin-top
:
$baseline
;
}
.member-lists-management
{
.wrapper-member-select
{
padding
:
(
$baseline
/
2
);
background-color
:
$light-gray
;
}
.member-lists-selector
{
display
:
block
;
margin
:
(
$baseline
/
4
)
0
;
padding
:
(
$baseline
/
4
);
}
.auth-list-container
{
display
:
none
;
margin-bottom
:
(
$baseline
*
1
.5
);
&
.active
{
display
:
block
;
}
.member-list-widget
{
.header
{
@include
box-sizing
(
border-box
);
@include
border-top-radius
(
3
);
position
:
relative
;
padding
:
(
$baseline
/
2
);
background-color
:
#efefef
;
border
:
1px
solid
$light-gray
;
display
:
none
;
// hiding to prefer dropdown as header
}
.title
{
@include
font-size
(
16
);
}
.label
,
.form-label
{
@extend
%t-copy-sub1
;
color
:
$lighter-base-font-color
;
}
.info
{
@include
box-sizing
(
border-box
);
padding
:
(
$baseline
/
2
);
border
:
1px
solid
$light-gray
;
color
:
$lighter-base-font-color
;
line-height
:
1
.3em
;
font-size
:
.85em
;
}
.member-list
{
@include
box-sizing
(
border-box
);
table
{
width
:
100%
;
}
thead
{
background-color
:
$light-gray
;
}
tr
{
border-bottom
:
1px
solid
$light-gray
;
}
td
{
@extend
%t-copy-sub1
;
vertical-align
:
middle
;
padding
:
(
$baseline
/
2
)
(
$baseline
/
4
);
@include
border-left
(
1px
solid
$light-gray
);
@include
border-right
(
1px
solid
$light-gray
);
word-wrap
:
break-word
;
}
}
.bottom-bar
{
@include
box-sizing
(
border-box
);
@include
border-bottom-radius
(
3
);
position
:
relative
;
padding
:
(
$baseline
/
2
);
margin-top
:
-1px
;
border
:
1px
solid
$light-gray
;
background-color
:
#efefef
;
box-shadow
:
inset
#bbb
0px
1px
1px
0px
;
}
// .add-field
input
[
type
=
"button"
]
.add
{
@include
idashbutton
(
$blue
);
position
:
absolute
;
@include
right
(
$baseline
);
}
}
.revoke
{
color
:
$lighter-base-font-color
;
cursor
:
pointer
;
&
:hover
,
&
:focus
{
color
:
$alert-color
;
}
}
}
}
}
// view - cohort management
// --------------------
.instructor-dashboard-wrapper-2
section
.idash-section
#cohort_management
{
// cohort management
// cohort management
%cohort-management-form
{
%cohort-management-form
{
...
@@ -733,245 +975,6 @@
...
@@ -733,245 +975,6 @@
}
}
}
}
.batch-enrollment
,
.batch-beta-testers
{
textarea
{
margin-top
:
0
.2em
;
height
:
auto
;
width
:
90%
;
}
input
{
margin-right
:
(
$baseline
/
4
);
}
.request-res-section
{
margin-top
:
1
.5em
;
h3
{
color
:
#646464
;
}
ul
{
margin
:
0
;
margin-top
:
0
.5em
;
padding
:
0
;
list-style-type
:
none
;
line-height
:
1
.5em
;
}
}
}
// Auto Enroll Csv Section
.auto_enroll_csv
{
.results
{
}
.enrollment_signup_button
{
@include
margin-right
(
$baseline
/
4
);
}
// Custom File upload
.customBrowseBtn
{
margin
:
(
$baseline
/
2
)
0
;
display
:
inline-block
;
.file-browse
{
position
:relative
;
overflow
:hidden
;
display
:
inline
;
@include
margin-left
(
-5px
);
span
.browse
{
@include
button
(
simple
,
$blue
);
@include
margin-right
(
$baseline
);
padding
:
6px
(
$baseline
/
2
);
font-size
:
12px
;
border-radius
:
0
3px
3px
0
;
}
input
.file_field
{
position
:absolute
;
@include
right
(
0
);
top
:
0
;
margin
:
0
;
padding
:
0
;
cursor
:pointer
;
opacity
:
0
;
filter
:alpha
(
opacity
=
0
)
;
}
}
&
>
span
,
&
input
[
disabled
]
{
vertical-align
:
middle
;
}
input
[
disabled
]
{
@include
border-radius
(
4px
0
0
4px
);
@include
padding
(
6px
6px
5px
);
border
:
1px
solid
$lightGrey1
;
cursor
:
not
-
allowed
;
}
}
}
.enroll-option
{
margin
:
(
$baseline
/
2
)
0
;
position
:
relative
;
label
{
border-bottom
:
1px
dotted
$base-font-color
;
}
.hint
{
@extend
%t-copy-sub2
;
display
:
none
;
position
:
absolute
;
top
:
15px
;
@include
left
(
$baseline
*
10
);
padding
:
(
$baseline
/
2
);
width
:
50%
;
background-color
:
$light-gray
;
box-shadow
:
2px
2px
3px
$shadow
;
.hint-caret
{
display
:
block
;
position
:
absolute
;
top
:
0
;
@include
left
(
-15px
);
@include
border-right
(
8px
solid
$light-gray
);
@include
border-left
(
8px
solid
transparent
);
border-top
:
8px
solid
$light-gray
;
border-bottom
:
8px
solid
transparent
;
}
}
}
label
[
for
=
"auto-enroll"
]
:hover
+
.auto-enroll-hint
{
display
:
block
;
}
label
[
for
=
"auto-enroll-beta"
]
:hover
+
.auto-enroll-beta-hint
{
width
:
30%
;
display
:
block
;
}
label
[
for
=
"email-students"
]
:hover
+
.email-students-hint
{
display
:
block
;
}
label
[
for
=
"email-students-beta"
]
:hover
+
.email-students-beta-hint
{
width
:
30%
;
display
:
block
;
}
.enroll-actions
{
margin-top
:
$baseline
;
}
.member-lists-management
{
.wrapper-member-select
{
padding
:
(
$baseline
/
2
);
background-color
:
$light-gray
;
}
.member-lists-selector
{
display
:
block
;
margin
:
(
$baseline
/
4
)
0
;
padding
:
(
$baseline
/
4
);
}
.auth-list-container
{
display
:
none
;
margin-bottom
:
(
$baseline
*
1
.5
);
&
.active
{
display
:
block
;
}
.member-list-widget
{
.header
{
@include
box-sizing
(
border-box
);
@include
border-top-radius
(
3
);
position
:
relative
;
padding
:
(
$baseline
/
2
);
background-color
:
#efefef
;
border
:
1px
solid
$light-gray
;
display
:
none
;
// hiding to prefer dropdown as header
}
.title
{
@include
font-size
(
16
);
}
.label
,
.form-label
{
@extend
%t-copy-sub1
;
color
:
$lighter-base-font-color
;
}
.info
{
@include
box-sizing
(
border-box
);
padding
:
(
$baseline
/
2
);
border
:
1px
solid
$light-gray
;
color
:
$lighter-base-font-color
;
line-height
:
1
.3em
;
font-size
:
.85em
;
}
.member-list
{
@include
box-sizing
(
border-box
);
table
{
width
:
100%
;
}
thead
{
background-color
:
$light-gray
;
}
tr
{
border-bottom
:
1px
solid
$light-gray
;
}
td
{
@extend
%t-copy-sub1
;
vertical-align
:
middle
;
padding
:
(
$baseline
/
2
)
(
$baseline
/
4
);
@include
border-left
(
1px
solid
$light-gray
);
@include
border-right
(
1px
solid
$light-gray
);
word-wrap
:
break-word
;
}
}
.bottom-bar
{
@include
box-sizing
(
border-box
);
@include
border-bottom-radius
(
3
);
position
:
relative
;
padding
:
(
$baseline
/
2
);
margin-top
:
-1px
;
border
:
1px
solid
$light-gray
;
background-color
:
#efefef
;
box-shadow
:
inset
#bbb
0px
1px
1px
0px
;
}
// .add-field
input
[
type
=
"button"
]
.add
{
@include
idashbutton
(
$blue
);
position
:
absolute
;
@include
right
(
$baseline
);
}
}
.revoke
{
color
:
$lighter-base-font-color
;
cursor
:
pointer
;
&
:hover
,
&
:focus
{
color
:
$alert-color
;
}
}
}
}
.has-other-input-text
{
// Given to groups which have an 'other' input that appears when needed
.has-other-input-text
{
// Given to groups which have an 'other' input that appears when needed
display
:
inline-block
;
display
:
inline-block
;
...
@@ -1118,6 +1121,7 @@
...
@@ -1118,6 +1121,7 @@
}
}
}
}
// view - student admin
// view - student admin
// --------------------
// --------------------
.instructor-dashboard-wrapper-2
section
.idash-section
#student_admin
>
{
.instructor-dashboard-wrapper-2
section
.idash-section
#student_admin
>
{
...
...
lms/templates/instructor/instructor_dashboard_2/cohort-form.underscore
View file @
3ce494f5
...
@@ -38,7 +38,7 @@
...
@@ -38,7 +38,7 @@
<%- gettext('Students in this cohort are:') %>
<%- gettext('Students in this cohort are:') %>
</h4>
</h4>
<label>
<label>
<input type="radio" class="type-random" name="cohort-assignment-type" value="random" <%- assignment_type == 'random' ? 'checked="checked"' : '' %>/> <%- gettext("
Random
ly Assigned") %>
<input type="radio" class="type-random" name="cohort-assignment-type" value="random" <%- assignment_type == 'random' ? 'checked="checked"' : '' %>/> <%- gettext("
Automatical
ly Assigned") %>
</label>
</label>
<label>
<label>
<input type="radio" class="type-manual" name="cohort-assignment-type" value="manual" <%- assignment_type == 'manual' || isNewCohort ? 'checked="checked"' : '' %>/> <%- gettext("Manually Assigned") %>
<input type="radio" class="type-manual" name="cohort-assignment-type" value="manual" <%- assignment_type == 'manual' || isNewCohort ? 'checked="checked"' : '' %>/> <%- gettext("Manually Assigned") %>
...
@@ -47,7 +47,7 @@
...
@@ -47,7 +47,7 @@
<% if (isDefaultCohort) { %>
<% if (isDefaultCohort) { %>
<p class="copy-error">
<p class="copy-error">
<i class="icon fa fa-exclamation-triangle" aria-hidden="true"></i>
<i class="icon fa fa-exclamation-triangle" aria-hidden="true"></i>
<%- gettext("There must be one cohort to which students can
be randomly
assigned.") %>
<%- gettext("There must be one cohort to which students can
automatically be
assigned.") %>
</p>
</p>
<% } %>
<% } %>
<hr class="divider divider-lv1">
<hr class="divider divider-lv1">
...
...
lms/templates/instructor/instructor_dashboard_2/cohort-state.underscore
0 → 100644
View file @
3ce494f5
<div class="nav-utilities">
<span class="action-toggle-message" aria-live="polite"></span>
</div>
\ No newline at end of file
lms/templates/instructor/instructor_dashboard_2/cohort_management.html
0 → 100644
View file @
3ce494f5
<
%!
from
django
.
utils
.
translation
import
ugettext
as
_
%
>
<
%
page
args=
"section_data"
/>
<
%!
from
courseware
.
courses
import
get_studio_url
%
>
<
%!
from
microsite_configuration
import
microsite
%
>
<
%!
from
openedx
.
core
.
djangoapps
.
course_groups
.
partition_scheme
import
get_cohorted_user_partition
%
>
<div
class=
"cohort-management"
data-cohorts_url=
"${section_data['cohorts_url']}"
data-advanced-settings-url=
"${section_data['advanced_settings_url']}"
data-upload_cohorts_csv_url=
"${section_data['upload_cohorts_csv_url']}"
data-course_cohort_settings_url=
"${section_data['course_cohort_settings_url']}"
>
</div>
<
%
block
name=
"headextra"
>
<
%
cohorted_user_partition =
get_cohorted_user_partition(course.id)
content_groups =
cohorted_user_partition.groups
if
cohorted_user_partition
else
[]
%
>
<script
type=
"text/javascript"
>
$
(
document
).
ready
(
function
()
{
var
cohortUserPartitionId
=
$
{
cohorted_user_partition
.
id
if
cohorted_user_partition
else
'null'
},
contentGroups
=
[
%
for
content_group
in
content_groups
:
new
edx
.
groups
.
ContentGroupModel
({
id
:
$
{
content_group
.
id
},
name
:
"${content_group.name | h}"
,
user_partition_id
:
cohortUserPartitionId
}),
%
endfor
];
(
function
(
require
)
{
require
([
'js/factories/cohorts_factory'
],
function
(
CohortsFactory
)
{
CohortsFactory
(
contentGroups
,
'${get_studio_url(course, '
group_configurations
') | h}'
);
});
}).
call
(
this
,
require
||
RequireJS
.
require
);
});
</script>
</
%
block>
<div
class=
"cohort-state-message"
></div>
lms/templates/instructor/instructor_dashboard_2/cohorts.underscore
View file @
3ce494f5
<h2 class="section-title">
<div class="cohorts-state-section">
<span class="value"><%- gettext('Cohort Management') %></span>
<label> <input type="checkbox" class="cohorts-state" value="Cohorts-State" <%- cohortsEnabled ? 'checked="checked"' : '' %> /> <%- gettext('Enable Cohorts') %></label>
<span class="description"></span>
</div>
</h2>
<div class="cohort-management-nav">
<% if (cohortsEnabled) { %>
<h3 class="subsection-title"><%- gettext('Assign students to cohorts manually') %></h3>
<div class="cohort-management-nav">
<hr class="divider divider-lv1" />
<form action="" method="post" name="" id="cohort-management-nav-form" class="cohort-management-nav-form">
<form action="" method="post" name="" id="cohort-management-nav-form" class="cohort-management-nav-form">
<div class="cohort-management-nav-form-select field field-select">
<div class="cohort-management-nav-form-select field field-select">
...
@@ -21,15 +21,15 @@
...
@@ -21,15 +21,15 @@
<i class="icon fa fa-plus" aria-hidden="true"></i>
<i class="icon fa fa-plus" aria-hidden="true"></i>
<%- gettext('Add Cohort') %>
<%- gettext('Add Cohort') %>
</a>
</a>
</div>
</div>
<!-- Add modal -->
<!-- Add modal -->
<div class="cohort-management-add-form"></div>
<div class="cohort-management-add-form"></div>
<!-- individual group -->
<!-- individual group -->
<div class="cohort-management-group"></div>
<div class="cohort-management-group"></div>
<div class="wrapper-cohort-supplemental">
<div class="wrapper-cohort-supplemental">
<hr class="divider divider-lv1" />
<hr class="divider divider-lv1" />
...
@@ -47,4 +47,5 @@
...
@@ -47,4 +47,5 @@
) %>
) %>
</p>
</p>
</div>
</div>
</div>
</div>
<% } %>
lms/templates/instructor/instructor_dashboard_2/instructor_dashboard_2.html
View file @
3ce494f5
...
@@ -53,12 +53,16 @@
...
@@ -53,12 +53,16 @@
<
%
static:js
group=
'application'
/>
<
%
static:js
group=
'application'
/>
## Backbone classes declared explicitly until RequireJS is supported
## Backbone classes declared explicitly until RequireJS is supported
<script
type=
"text/javascript"
src=
"${static.url('js/instructor_dashboard/ecommerce.js')}"
></script>
<script
type=
"text/javascript"
src=
"${static.url('js/instructor_dashboard/cohort_management.js')}"
></script>
<script
type=
"text/javascript"
src=
"${static.url('js/models/notification.js')}"
></script>
<script
type=
"text/javascript"
src=
"${static.url('js/models/notification.js')}"
></script>
<script
type=
"text/javascript"
src=
"${static.url('js/views/notification.js')}"
></script>
<script
type=
"text/javascript"
src=
"${static.url('js/views/notification.js')}"
></script>
<script
type=
"text/javascript"
src=
"${static.url('js/views/file_uploader.js')}"
></script>
<script
type=
"text/javascript"
src=
"${static.url('js/views/file_uploader.js')}"
></script>
<script
type=
"text/javascript"
src=
"${static.url('js/groups/models/cohort.js')}"
></script>
<script
type=
"text/javascript"
src=
"${static.url('js/groups/models/cohort.js')}"
></script>
<script
type=
"text/javascript"
src=
"${static.url('js/groups/models/content_group.js')}"
></script>
<script
type=
"text/javascript"
src=
"${static.url('js/groups/models/content_group.js')}"
></script>
<script
type=
"text/javascript"
src=
"${static.url('js/groups/models/course_cohort_settings.js')}"
></script>
<script
type=
"text/javascript"
src=
"${static.url('js/groups/collections/cohort.js')}"
></script>
<script
type=
"text/javascript"
src=
"${static.url('js/groups/collections/cohort.js')}"
></script>
<script
type=
"text/javascript"
src=
"${static.url('js/groups/views/course_cohort_settings_notification.js')}"
></script>
<script
type=
"text/javascript"
src=
"${static.url('js/groups/views/cohort_form.js')}"
></script>
<script
type=
"text/javascript"
src=
"${static.url('js/groups/views/cohort_form.js')}"
></script>
<script
type=
"text/javascript"
src=
"${static.url('js/groups/views/cohort_editor.js')}"
></script>
<script
type=
"text/javascript"
src=
"${static.url('js/groups/views/cohort_editor.js')}"
></script>
<script
type=
"text/javascript"
src=
"${static.url('js/groups/views/cohorts.js')}"
></script>
<script
type=
"text/javascript"
src=
"${static.url('js/groups/views/cohorts.js')}"
></script>
...
@@ -66,7 +70,7 @@
...
@@ -66,7 +70,7 @@
## Include Underscore templates
## Include Underscore templates
<
%
block
name=
"header_extras"
>
<
%
block
name=
"header_extras"
>
% for template_name in ["cohorts", "cohort-editor", "cohort-group-header", "cohort-selector", "cohort-form", "notification"]:
% for template_name in ["cohorts", "cohort-editor", "cohort-group-header", "cohort-selector", "cohort-form", "notification"
, "cohort-state"
]:
<script
type=
"text/template"
id=
"${template_name}-tpl"
>
<script
type=
"text/template"
id=
"${template_name}-tpl"
>
<%
static
:
include
path
=
"instructor/instructor_dashboard_2/${template_name}.underscore"
/>
<%
static
:
include
path
=
"instructor/instructor_dashboard_2/${template_name}.underscore"
/>
</script>
</script>
...
...
lms/templates/instructor/instructor_dashboard_2/membership.html
View file @
3ce494f5
...
@@ -245,53 +245,3 @@
...
@@ -245,53 +245,3 @@
%endif
%endif
</div>
</div>
% if course.is_cohorted:
<hr
class=
"divider"
/>
<div
class=
"cohort-management membership-section"
data-ajax_url=
"${section_data['cohorts_ajax_url']}"
data-advanced-settings-url=
"${section_data['advanced_settings_url']}"
data-upload_cohorts_csv_url=
"${section_data['upload_cohorts_csv_url']}"
>
</div>
<
%
block
name=
"headextra"
>
<
%
cohorted_user_partition =
get_cohorted_user_partition(course.id)
content_groups =
cohorted_user_partition.groups
if
cohorted_user_partition
else
[]
%
>
<script>
$
(
document
).
ready
(
function
()
{
var
cohortManagementElement
=
$
(
'.cohort-management'
);
if
(
cohortManagementElement
.
length
>
0
)
{
var
cohorts
=
new
edx
.
groups
.
CohortCollection
(),
cohortUserPartitionId
=
$
{
cohorted_user_partition
.
id
if
cohorted_user_partition
else
'null'
},
contentGroups
=
[
%
for
content_group
in
content_groups
:
new
edx
.
groups
.
ContentGroupModel
({
id
:
$
{
content_group
.
id
},
name
:
"${content_group.name | h}"
,
user_partition_id
:
cohortUserPartitionId
}),
%
endfor
];
cohorts
.
url
=
cohortManagementElement
.
data
(
'ajax_url'
);
var
cohortsView
=
new
edx
.
groups
.
CohortsView
({
el
:
cohortManagementElement
,
model
:
cohorts
,
contentGroups
:
contentGroups
,
context
:
{
uploadCohortsCsvUrl
:
cohortManagementElement
.
data
(
'upload_cohorts_csv_url'
),
studioAdvancedSettingsUrl
:
cohortManagementElement
.
data
(
'advanced-settings-url'
),
studioGroupConfigurationsUrl
:
'${get_studio_url(course, '
group_configurations
') | h}'
}
});
cohorts
.
fetch
().
done
(
function
()
{
cohortsView
.
render
();
});
}
});
</script>
</
%
block>
% endif
lms/urls.py
View file @
3ce494f5
...
@@ -379,6 +379,9 @@ if settings.COURSEWARE_ENABLED:
...
@@ -379,6 +379,9 @@ if settings.COURSEWARE_ENABLED:
'open_ended_grading.views.take_action_on_flags'
,
name
=
'open_ended_flagged_problems_take_action'
),
'open_ended_grading.views.take_action_on_flags'
,
name
=
'open_ended_flagged_problems_take_action'
),
# Cohorts management
# Cohorts management
url
(
r'^courses/{}/cohorts/settings$'
.
format
(
settings
.
COURSE_KEY_PATTERN
),
'openedx.core.djangoapps.course_groups.views.course_cohort_settings_handler'
,
name
=
"course_cohort_settings"
),
url
(
r'^courses/{}/cohorts/(?P<cohort_id>[0-9]+)?$'
.
format
(
settings
.
COURSE_KEY_PATTERN
),
url
(
r'^courses/{}/cohorts/(?P<cohort_id>[0-9]+)?$'
.
format
(
settings
.
COURSE_KEY_PATTERN
),
'openedx.core.djangoapps.course_groups.views.cohort_handler'
,
name
=
"cohorts"
),
'openedx.core.djangoapps.course_groups.views.cohort_handler'
,
name
=
"cohorts"
),
url
(
r'^courses/{}/cohorts/(?P<cohort_id>[0-9]+)$'
.
format
(
settings
.
COURSE_KEY_PATTERN
),
url
(
r'^courses/{}/cohorts/(?P<cohort_id>[0-9]+)$'
.
format
(
settings
.
COURSE_KEY_PATTERN
),
...
...
openedx/core/djangoapps/course_groups/cohorts.py
View file @
3ce494f5
...
@@ -5,7 +5,6 @@ forums, and to the cohort admin views.
...
@@ -5,7 +5,6 @@ forums, and to the cohort admin views.
import
logging
import
logging
import
random
import
random
import
json
from
django.db
import
transaction
from
django.db
import
transaction
from
django.db.models.signals
import
post_save
,
m2m_changed
from
django.db.models.signals
import
post_save
,
m2m_changed
...
@@ -238,7 +237,7 @@ def migrate_cohort_settings(course):
...
@@ -238,7 +237,7 @@ def migrate_cohort_settings(course):
course_id
=
course_id
,
course_id
=
course_id
,
defaults
=
{
defaults
=
{
'is_cohorted'
:
course
.
is_cohorted
,
'is_cohorted'
:
course
.
is_cohorted
,
'cohorted_discussions'
:
json
.
dumps
(
list
(
course
.
cohorted_discussions
)
),
'cohorted_discussions'
:
list
(
course
.
cohorted_discussions
),
'always_cohort_inline_discussions'
:
course
.
always_cohort_inline_discussions
'always_cohort_inline_discussions'
:
course
.
always_cohort_inline_discussions
}
}
)
)
...
@@ -324,7 +323,8 @@ def add_cohort(course_key, name, assignment_type):
...
@@ -324,7 +323,8 @@ def add_cohort(course_key, name, assignment_type):
raise
ValueError
(
"Invalid course_key"
)
raise
ValueError
(
"Invalid course_key"
)
cohort
=
CourseCohort
.
create
(
cohort
=
CourseCohort
.
create
(
cohort_name
=
name
,
course_id
=
course
.
id
,
cohort_name
=
name
,
course_id
=
course
.
id
,
assignment_type
=
assignment_type
assignment_type
=
assignment_type
)
.
course_user_group
)
.
course_user_group
...
@@ -413,7 +413,7 @@ def set_assignment_type(user_group, assignment_type):
...
@@ -413,7 +413,7 @@ def set_assignment_type(user_group, assignment_type):
course_cohort
=
user_group
.
cohort
course_cohort
=
user_group
.
cohort
if
is_default_cohort
(
user_group
)
and
course_cohort
.
assignment_type
!=
assignment_type
:
if
is_default_cohort
(
user_group
)
and
course_cohort
.
assignment_type
!=
assignment_type
:
raise
ValueError
(
_
(
"There must be one cohort to which students can
be randomly
assigned."
))
raise
ValueError
(
_
(
"There must be one cohort to which students can
automatically be
assigned."
))
course_cohort
.
assignment_type
=
assignment_type
course_cohort
.
assignment_type
=
assignment_type
course_cohort
.
save
()
course_cohort
.
save
()
...
@@ -438,3 +438,48 @@ def is_default_cohort(user_group):
...
@@ -438,3 +438,48 @@ def is_default_cohort(user_group):
)
)
return
len
(
random_cohorts
)
==
1
and
random_cohorts
[
0
]
.
name
==
user_group
.
name
return
len
(
random_cohorts
)
==
1
and
random_cohorts
[
0
]
.
name
==
user_group
.
name
def
set_course_cohort_settings
(
course_key
,
**
kwargs
):
"""
Set cohort settings for a course.
Arguments:
course_key: CourseKey
is_cohorted (bool): If the course should be cohorted.
always_cohort_inline_discussions (bool): If inline discussions should always be cohorted.
cohorted_discussions (list): List of discussion ids.
Returns:
A CourseCohortSettings object.
Raises:
ValueError if course_key is invalid.
"""
course_cohort_settings
=
get_course_cohort_settings
(
course_key
)
for
field
in
(
'is_cohorted'
,
'always_cohort_inline_discussions'
,
'cohorted_discussions'
):
if
field
in
kwargs
:
setattr
(
course_cohort_settings
,
field
,
kwargs
[
field
])
course_cohort_settings
.
save
()
return
course_cohort_settings
def
get_course_cohort_settings
(
course_key
):
"""
Return cohort settings for a course.
Arguments:
course_key: CourseKey
Returns:
A CourseCohortSettings object.
Raises:
ValueError if course_key is invalid.
"""
try
:
course_cohort_settings
=
CourseCohortsSettings
.
objects
.
get
(
course_id
=
course_key
)
except
CourseCohortsSettings
.
DoesNotExist
:
course
=
courses
.
get_course
(
course_key
)
course_cohort_settings
=
migrate_cohort_settings
(
course
)
return
course_cohort_settings
openedx/core/djangoapps/course_groups/migrations/0004_auto__del_field_coursecohortssettings_cohorted_discussions__add_field_.py
0 → 100644
View file @
3ce494f5
# -*- coding: utf-8 -*-
import
datetime
from
south.db
import
db
from
south.v2
import
SchemaMigration
from
django.db
import
models
class
Migration
(
SchemaMigration
):
def
forwards
(
self
,
orm
):
# Changed 'CourseCohortsSettings.cohorted_discussions' to 'CourseCohortsSettings._cohorted_discussions' without
# changing db column name
pass
def
backwards
(
self
,
orm
):
# Changed 'CourseCohortsSettings.cohorted_discussions' to 'CourseCohortsSettings._cohorted_discussions' without
# changing db column name
pass
models
=
{
'auth.group'
:
{
'Meta'
:
{
'object_name'
:
'Group'
},
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'80'
}),
'permissions'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
"orm['auth.Permission']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
})
},
'auth.permission'
:
{
'Meta'
:
{
'ordering'
:
"('content_type__app_label', 'content_type__model', 'codename')"
,
'unique_together'
:
"(('content_type', 'codename'),)"
,
'object_name'
:
'Permission'
},
'codename'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'content_type'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['contenttypes.ContentType']"
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'50'
})
},
'auth.user'
:
{
'Meta'
:
{
'object_name'
:
'User'
},
'date_joined'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'email'
:
(
'django.db.models.fields.EmailField'
,
[],
{
'max_length'
:
'75'
,
'blank'
:
'True'
}),
'first_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'30'
,
'blank'
:
'True'
}),
'groups'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
"orm['auth.Group']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'is_active'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'True'
}),
'is_staff'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'is_superuser'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'last_login'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'last_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'30'
,
'blank'
:
'True'
}),
'password'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
}),
'user_permissions'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
"orm['auth.Permission']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
}),
'username'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'30'
})
},
'contenttypes.contenttype'
:
{
'Meta'
:
{
'ordering'
:
"('name',)"
,
'unique_together'
:
"(('app_label', 'model'),)"
,
'object_name'
:
'ContentType'
,
'db_table'
:
"'django_content_type'"
},
'app_label'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'model'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
})
},
'course_groups.coursecohort'
:
{
'Meta'
:
{
'object_name'
:
'CourseCohort'
},
'assignment_type'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"'manual'"
,
'max_length'
:
'20'
}),
'course_user_group'
:
(
'django.db.models.fields.related.OneToOneField'
,
[],
{
'related_name'
:
"'cohort'"
,
'unique'
:
'True'
,
'to'
:
"orm['course_groups.CourseUserGroup']"
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
})
},
'course_groups.coursecohortssettings'
:
{
'Meta'
:
{
'object_name'
:
'CourseCohortsSettings'
},
'_cohorted_discussions'
:
(
'django.db.models.fields.TextField'
,
[],
{
'null'
:
'True'
,
'db_column'
:
"'cohorted_discussions'"
,
'blank'
:
'True'
}),
'always_cohort_inline_discussions'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'True'
}),
'course_id'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'255'
,
'db_index'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'is_cohorted'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
})
},
'course_groups.courseusergroup'
:
{
'Meta'
:
{
'unique_together'
:
"(('name', 'course_id'),)"
,
'object_name'
:
'CourseUserGroup'
},
'course_id'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'max_length'
:
'255'
,
'db_index'
:
'True'
}),
'group_type'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'20'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
}),
'users'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'db_index'
:
'True'
,
'related_name'
:
"'course_groups'"
,
'symmetrical'
:
'False'
,
'to'
:
"orm['auth.User']"
})
},
'course_groups.courseusergrouppartitiongroup'
:
{
'Meta'
:
{
'object_name'
:
'CourseUserGroupPartitionGroup'
},
'course_user_group'
:
(
'django.db.models.fields.related.OneToOneField'
,
[],
{
'to'
:
"orm['course_groups.CourseUserGroup']"
,
'unique'
:
'True'
}),
'created_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'group_id'
:
(
'django.db.models.fields.IntegerField'
,
[],
{}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'partition_id'
:
(
'django.db.models.fields.IntegerField'
,
[],
{}),
'updated_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'blank'
:
'True'
})
}
}
complete_apps
=
[
'course_groups'
]
\ No newline at end of file
openedx/core/djangoapps/course_groups/models.py
View file @
3ce494f5
"""
Django models related to course groups functionality.
"""
import
json
import
logging
import
logging
from
django.contrib.auth.models
import
User
from
django.contrib.auth.models
import
User
...
@@ -81,11 +86,21 @@ class CourseCohortsSettings(models.Model):
...
@@ -81,11 +86,21 @@ class CourseCohortsSettings(models.Model):
help_text
=
"Which course are these settings associated with?"
,
help_text
=
"Which course are these settings associated with?"
,
)
)
cohorted_discussions
=
models
.
TextField
(
null
=
True
,
blank
=
True
)
# JSON list
_cohorted_discussions
=
models
.
TextField
(
db_column
=
'cohorted_discussions'
,
null
=
True
,
blank
=
True
)
# JSON list
# pylint: disable=invalid-name
# pylint: disable=invalid-name
always_cohort_inline_discussions
=
models
.
BooleanField
(
default
=
True
)
always_cohort_inline_discussions
=
models
.
BooleanField
(
default
=
True
)
@property
def
cohorted_discussions
(
self
):
"""Jsonfiy the cohorted_discussions"""
return
json
.
loads
(
self
.
_cohorted_discussions
)
@cohorted_discussions.setter
def
cohorted_discussions
(
self
,
value
):
"""UnJsonfiy the cohorted_discussions"""
self
.
_cohorted_discussions
=
json
.
dumps
(
value
)
class
CourseCohort
(
models
.
Model
):
class
CourseCohort
(
models
.
Model
):
"""
"""
...
...
openedx/core/djangoapps/course_groups/tests/helpers.py
View file @
3ce494f5
...
@@ -8,7 +8,9 @@ from opaque_keys.edx.locations import SlashSeparatedCourseKey
...
@@ -8,7 +8,9 @@ from opaque_keys.edx.locations import SlashSeparatedCourseKey
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore
import
ModuleStoreEnum
from
..models
import
CourseUserGroup
,
CourseCohort
from
..models
import
CourseUserGroup
,
CourseCohort
,
CourseCohortsSettings
import
json
class
CohortFactory
(
DjangoModelFactory
):
class
CohortFactory
(
DjangoModelFactory
):
...
@@ -40,6 +42,19 @@ class CourseCohortFactory(DjangoModelFactory):
...
@@ -40,6 +42,19 @@ class CourseCohortFactory(DjangoModelFactory):
assignment_type
=
'manual'
assignment_type
=
'manual'
class
CourseCohortSettingsFactory
(
DjangoModelFactory
):
"""
Factory for constructing mock course cohort settings.
"""
FACTORY_FOR
=
CourseCohortsSettings
is_cohorted
=
False
course_id
=
SlashSeparatedCourseKey
(
"dummy"
,
"dummy"
,
"dummy"
)
cohorted_discussions
=
json
.
dumps
([])
# pylint: disable=invalid-name
always_cohort_inline_discussions
=
True
def
topic_name_to_id
(
course
,
name
):
def
topic_name_to_id
(
course
,
name
):
"""
"""
Given a discussion topic name, return an id for that name (includes
Given a discussion topic name, return an id for that name (includes
...
...
openedx/core/djangoapps/course_groups/tests/test_cohorts.py
View file @
3ce494f5
...
@@ -3,7 +3,6 @@ Tests for cohorts
...
@@ -3,7 +3,6 @@ Tests for cohorts
"""
"""
# pylint: disable=no-member
# pylint: disable=no-member
from
django.conf
import
settings
from
django.contrib.auth.models
import
User
from
django.contrib.auth.models
import
User
from
django.db
import
IntegrityError
from
django.db
import
IntegrityError
from
django.http
import
Http404
from
django.http
import
Http404
...
@@ -19,8 +18,9 @@ from xmodule.modulestore.tests.django_utils import TEST_DATA_MIXED_TOY_MODULESTO
...
@@ -19,8 +18,9 @@ from xmodule.modulestore.tests.django_utils import TEST_DATA_MIXED_TOY_MODULESTO
from
..models
import
CourseUserGroup
,
CourseCohort
,
CourseUserGroupPartitionGroup
from
..models
import
CourseUserGroup
,
CourseCohort
,
CourseUserGroupPartitionGroup
from
..
import
cohorts
from
..
import
cohorts
from
..tests.helpers
import
topic_name_to_id
,
config_course_cohorts
,
CohortFactory
,
CourseCohortFactory
from
..tests.helpers
import
(
topic_name_to_id
,
config_course_cohorts
,
CohortFactory
,
CourseCohortFactory
,
CourseCohortSettingsFactory
)
@patch
(
"openedx.core.djangoapps.course_groups.cohorts.tracker"
)
@patch
(
"openedx.core.djangoapps.course_groups.cohorts.tracker"
)
class
TestCohortSignals
(
TestCase
):
class
TestCohortSignals
(
TestCase
):
...
@@ -209,7 +209,7 @@ class TestCohorts(ModuleStoreTestCase):
...
@@ -209,7 +209,7 @@ class TestCohorts(ModuleStoreTestCase):
self
.
assertEqual
(
cohorts
.
get_assignment_type
(
cohort
),
CourseCohort
.
RANDOM
)
self
.
assertEqual
(
cohorts
.
get_assignment_type
(
cohort
),
CourseCohort
.
RANDOM
)
exception_msg
=
"There must be one cohort to which students can
be randomly
assigned."
exception_msg
=
"There must be one cohort to which students can
automatically be
assigned."
with
self
.
assertRaises
(
ValueError
)
as
context_manager
:
with
self
.
assertRaises
(
ValueError
)
as
context_manager
:
cohorts
.
set_assignment_type
(
cohort
,
CourseCohort
.
MANUAL
)
cohorts
.
set_assignment_type
(
cohort
,
CourseCohort
.
MANUAL
)
...
@@ -685,12 +685,43 @@ class TestCohorts(ModuleStoreTestCase):
...
@@ -685,12 +685,43 @@ class TestCohorts(ModuleStoreTestCase):
lambda
:
cohorts
.
add_user_to_cohort
(
first_cohort
,
"non_existent_username"
)
lambda
:
cohorts
.
add_user_to_cohort
(
first_cohort
,
"non_existent_username"
)
)
)
def
test_get_course_cohort_settings
(
self
):
"""
Test that cohorts.get_course_cohort_settings is working as expected.
"""
course
=
modulestore
()
.
get_course
(
self
.
toy_course_key
)
course_cohort_settings
=
cohorts
.
get_course_cohort_settings
(
course
.
id
)
self
.
assertFalse
(
course_cohort_settings
.
is_cohorted
)
self
.
assertEqual
(
course_cohort_settings
.
cohorted_discussions
,
[])
self
.
assertTrue
(
course_cohort_settings
.
always_cohort_inline_discussions
)
def
test_update_course_cohort_settings
(
self
):
"""
Test that cohorts.set_course_cohort_settings is working as expected.
"""
course
=
modulestore
()
.
get_course
(
self
.
toy_course_key
)
CourseCohortSettingsFactory
(
course_id
=
course
.
id
)
cohorts
.
set_course_cohort_settings
(
course
.
id
,
is_cohorted
=
False
,
cohorted_discussions
=
[
'topic a id'
,
'topic b id'
],
always_cohort_inline_discussions
=
False
)
course_cohort_settings
=
cohorts
.
get_course_cohort_settings
(
course
.
id
)
self
.
assertFalse
(
course_cohort_settings
.
is_cohorted
)
self
.
assertEqual
(
course_cohort_settings
.
cohorted_discussions
,
[
'topic a id'
,
'topic b id'
])
self
.
assertFalse
(
course_cohort_settings
.
always_cohort_inline_discussions
)
class
TestCohortsAndPartitionGroups
(
ModuleStoreTestCase
):
class
TestCohortsAndPartitionGroups
(
ModuleStoreTestCase
):
MODULESTORE
=
TEST_DATA_MIXED_TOY_MODULESTORE
"""
"""
Test Cohorts and Partitions Groups.
Test Cohorts and Partitions Groups.
"""
"""
MODULESTORE
=
TEST_DATA_MIXED_TOY_MODULESTORE
def
setUp
(
self
):
def
setUp
(
self
):
"""
"""
...
...
openedx/core/djangoapps/course_groups/tests/test_views.py
View file @
3ce494f5
...
@@ -20,13 +20,14 @@ from opaque_keys.edx.locations import SlashSeparatedCourseKey
...
@@ -20,13 +20,14 @@ from opaque_keys.edx.locations import SlashSeparatedCourseKey
from
..models
import
CourseUserGroup
,
CourseCohort
from
..models
import
CourseUserGroup
,
CourseCohort
from
..views
import
(
from
..views
import
(
cohort_handler
,
users_in_cohort
,
add_users_to_cohort
,
remove_user_from_cohort
,
link_cohort_to_partition_group
course_cohort_settings_handler
,
cohort_handler
,
users_in_cohort
,
add_users_to_cohort
,
remove_user_from_cohort
,
link_cohort_to_partition_group
)
)
from
..cohorts
import
(
from
..cohorts
import
(
get_cohort
,
get_cohort_by_name
,
get_cohort_by_id
,
get_cohort
,
get_cohort_by_name
,
get_cohort_by_id
,
DEFAULT_COHORT_NAME
,
get_group_info_for_cohort
DEFAULT_COHORT_NAME
,
get_group_info_for_cohort
)
)
from
.helpers
import
config_course_cohorts
,
CohortFactory
,
CourseCohortFactory
from
.helpers
import
config_course_cohorts
,
CohortFactory
,
CourseCohortFactory
,
topic_name_to_id
class
CohortViewsTestCase
(
ModuleStoreTestCase
):
class
CohortViewsTestCase
(
ModuleStoreTestCase
):
...
@@ -90,49 +91,114 @@ class CohortViewsTestCase(ModuleStoreTestCase):
...
@@ -90,49 +91,114 @@ class CohortViewsTestCase(ModuleStoreTestCase):
view_args
.
insert
(
0
,
request
)
view_args
.
insert
(
0
,
request
)
self
.
assertRaises
(
Http404
,
view
,
*
view_args
)
self
.
assertRaises
(
Http404
,
view
,
*
view_args
)
def
get_handler
(
self
,
course
,
cohort
=
None
,
expected_response_code
=
200
,
handler
=
cohort_handler
):
class
CohortHandlerTestCase
(
CohortViewsTestCase
):
"""
Tests the `cohort_handler` view.
"""
def
get_cohort_handler
(
self
,
course
,
cohort
=
None
):
"""
"""
Call a GET on `
cohort_handler` for a given `course` and return its response as a
Call a GET on `
handler` for a given `course` and return its response as a dict.
dict. If `cohort` is specified, only information for that specific cohort is return
ed.
Raise an exception if response status code is not as expect
ed.
"""
"""
request
=
RequestFactory
()
.
get
(
"dummy_url"
)
request
=
RequestFactory
()
.
get
(
"dummy_url"
)
request
.
user
=
self
.
staff_user
request
.
user
=
self
.
staff_user
if
cohort
:
if
cohort
:
response
=
cohort_
handler
(
request
,
unicode
(
course
.
id
),
cohort
.
id
)
response
=
handler
(
request
,
unicode
(
course
.
id
),
cohort
.
id
)
else
:
else
:
response
=
cohort_
handler
(
request
,
unicode
(
course
.
id
))
response
=
handler
(
request
,
unicode
(
course
.
id
))
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
status_code
,
expected_response_code
)
return
json
.
loads
(
response
.
content
)
return
json
.
loads
(
response
.
content
)
def
put_
cohort_handler
(
self
,
course
,
cohort
=
None
,
data
=
None
,
expected_response_code
=
200
):
def
put_
handler
(
self
,
course
,
cohort
=
None
,
data
=
None
,
expected_response_code
=
200
,
handler
=
cohort_handler
):
"""
"""
Call a PUT on `cohort_handler` for a given `course` and return its response as a
Call a PUT on `handler` for a given `course` and return its response as a dict.
dict. If `cohort` is not specified, a new cohort is created. If `cohort` is specified,
Raise an exception if response status code is not as expected.
the existing cohort is updated.
"""
"""
if
not
isinstance
(
data
,
basestring
):
if
not
isinstance
(
data
,
basestring
):
data
=
json
.
dumps
(
data
or
{})
data
=
json
.
dumps
(
data
or
{})
request
=
RequestFactory
()
.
put
(
path
=
"dummy path"
,
data
=
data
,
content_type
=
"application/json"
)
request
=
RequestFactory
()
.
put
(
path
=
"dummy path"
,
data
=
data
,
content_type
=
"application/json"
)
request
.
user
=
self
.
staff_user
request
.
user
=
self
.
staff_user
if
cohort
:
if
cohort
:
response
=
cohort_
handler
(
request
,
unicode
(
course
.
id
),
cohort
.
id
)
response
=
handler
(
request
,
unicode
(
course
.
id
),
cohort
.
id
)
else
:
else
:
response
=
cohort_
handler
(
request
,
unicode
(
course
.
id
))
response
=
handler
(
request
,
unicode
(
course
.
id
))
self
.
assertEqual
(
response
.
status_code
,
expected_response_code
)
self
.
assertEqual
(
response
.
status_code
,
expected_response_code
)
return
json
.
loads
(
response
.
content
)
return
json
.
loads
(
response
.
content
)
class
CourseCohortSettingsHandlerTestCase
(
CohortViewsTestCase
):
"""
Tests the `course_cohort_settings_handler` view.
"""
def
test_non_staff
(
self
):
"""
Verify that we cannot access course_cohort_settings_handler if we're a non-staff user.
"""
self
.
_verify_non_staff_cannot_access
(
course_cohort_settings_handler
,
"GET"
,
[
unicode
(
self
.
course
.
id
)])
self
.
_verify_non_staff_cannot_access
(
course_cohort_settings_handler
,
"PUT"
,
[
unicode
(
self
.
course
.
id
)])
def
test_get_settings
(
self
):
"""
Verify that course_cohort_settings_handler is working for HTTP GET.
"""
cohorted_discussions
=
[
'Topic A'
,
'Topic B'
]
config_course_cohorts
(
self
.
course
,
[],
cohorted
=
True
,
cohorted_discussions
=
cohorted_discussions
)
response
=
self
.
get_handler
(
self
.
course
,
handler
=
course_cohort_settings_handler
)
response
[
'cohorted_discussions'
]
.
sort
()
expected_response
=
{
'is_cohorted'
:
True
,
'always_cohort_inline_discussions'
:
True
,
'cohorted_discussions'
:
[
topic_name_to_id
(
self
.
course
,
name
)
for
name
in
cohorted_discussions
],
'id'
:
1
}
expected_response
[
'cohorted_discussions'
]
.
sort
()
self
.
assertEqual
(
response
,
expected_response
)
def
test_update_settings
(
self
):
"""
Verify that course_cohort_settings_handler is working for HTTP POST.
"""
config_course_cohorts
(
self
.
course
,
[],
cohorted
=
True
)
response
=
self
.
get_handler
(
self
.
course
,
handler
=
course_cohort_settings_handler
)
expected_response
=
{
'is_cohorted'
:
True
,
'always_cohort_inline_discussions'
:
True
,
'cohorted_discussions'
:
[],
'id'
:
1
}
self
.
assertEqual
(
response
,
expected_response
)
expected_response
[
'is_cohorted'
]
=
False
response
=
self
.
put_handler
(
self
.
course
,
data
=
expected_response
,
handler
=
course_cohort_settings_handler
)
self
.
assertEqual
(
response
,
expected_response
)
def
test_update_settings_with_missing_field
(
self
):
"""
Verify that course_cohort_settings_handler return HTTP 400 if required data field is missing from post data.
"""
config_course_cohorts
(
self
.
course
,
[],
cohorted
=
True
)
# Get the cohorts from the course. This will run the migrations.
# And due to migrations CourseCohortsSettings object will be created.
self
.
get_handler
(
self
.
course
)
response
=
self
.
put_handler
(
self
.
course
,
expected_response_code
=
400
,
handler
=
course_cohort_settings_handler
)
self
.
assertEqual
(
"Bad Request"
,
response
.
get
(
"error"
))
class
CohortHandlerTestCase
(
CohortViewsTestCase
):
"""
Tests the `cohort_handler` view.
"""
def
verify_lists_expected_cohorts
(
self
,
expected_cohorts
,
response_dict
=
None
):
def
verify_lists_expected_cohorts
(
self
,
expected_cohorts
,
response_dict
=
None
):
"""
"""
Verify that the server response contains the expected_cohorts.
Verify that the server response contains the expected_cohorts.
If response_dict is None, the list of cohorts is requested from the server.
If response_dict is None, the list of cohorts is requested from the server.
"""
"""
if
response_dict
is
None
:
if
response_dict
is
None
:
response_dict
=
self
.
get_
cohort_
handler
(
self
.
course
)
response_dict
=
self
.
get_handler
(
self
.
course
)
self
.
assertEqual
(
self
.
assertEqual
(
response_dict
.
get
(
"cohorts"
),
response_dict
.
get
(
"cohorts"
),
...
@@ -197,7 +263,7 @@ class CohortHandlerTestCase(CohortViewsTestCase):
...
@@ -197,7 +263,7 @@ class CohortHandlerTestCase(CohortViewsTestCase):
# Will create cohort1, cohort2, and cohort3. Auto cohorts remain uncreated.
# Will create cohort1, cohort2, and cohort3. Auto cohorts remain uncreated.
self
.
_create_cohorts
()
self
.
_create_cohorts
()
# Get the cohorts from the course, which will cause auto cohorts to be created.
# Get the cohorts from the course, which will cause auto cohorts to be created.
actual_cohorts
=
self
.
get_
cohort_
handler
(
self
.
course
)
actual_cohorts
=
self
.
get_handler
(
self
.
course
)
# Get references to the created auto cohorts.
# Get references to the created auto cohorts.
auto_cohort_1
=
get_cohort_by_name
(
self
.
course
.
id
,
"AutoGroup1"
)
auto_cohort_1
=
get_cohort_by_name
(
self
.
course
.
id
,
"AutoGroup1"
)
auto_cohort_2
=
get_cohort_by_name
(
self
.
course
.
id
,
"AutoGroup2"
)
auto_cohort_2
=
get_cohort_by_name
(
self
.
course
.
id
,
"AutoGroup2"
)
...
@@ -235,7 +301,7 @@ class CohortHandlerTestCase(CohortViewsTestCase):
...
@@ -235,7 +301,7 @@ class CohortHandlerTestCase(CohortViewsTestCase):
# verify the default cohort is automatically created
# verify the default cohort is automatically created
default_cohort
=
get_cohort_by_name
(
self
.
course
.
id
,
DEFAULT_COHORT_NAME
)
default_cohort
=
get_cohort_by_name
(
self
.
course
.
id
,
DEFAULT_COHORT_NAME
)
actual_cohorts
=
self
.
get_
cohort_
handler
(
self
.
course
)
actual_cohorts
=
self
.
get_handler
(
self
.
course
)
self
.
verify_lists_expected_cohorts
(
self
.
verify_lists_expected_cohorts
(
[
CohortHandlerTestCase
.
create_expected_cohort
(
default_cohort
,
len
(
users
),
CourseCohort
.
RANDOM
)],
[
CohortHandlerTestCase
.
create_expected_cohort
(
default_cohort
,
len
(
users
),
CourseCohort
.
RANDOM
)],
actual_cohorts
,
actual_cohorts
,
...
@@ -255,7 +321,7 @@ class CohortHandlerTestCase(CohortViewsTestCase):
...
@@ -255,7 +321,7 @@ class CohortHandlerTestCase(CohortViewsTestCase):
Tests that information for just a single cohort can be requested.
Tests that information for just a single cohort can be requested.
"""
"""
self
.
_create_cohorts
()
self
.
_create_cohorts
()
response_dict
=
self
.
get_
cohort_
handler
(
self
.
course
,
self
.
cohort2
)
response_dict
=
self
.
get_handler
(
self
.
course
,
self
.
cohort2
)
self
.
assertEqual
(
self
.
assertEqual
(
response_dict
,
response_dict
,
{
{
...
@@ -298,7 +364,7 @@ class CohortHandlerTestCase(CohortViewsTestCase):
...
@@ -298,7 +364,7 @@ class CohortHandlerTestCase(CohortViewsTestCase):
"""
"""
new_cohort_name
=
"New cohort unassociated to content groups"
new_cohort_name
=
"New cohort unassociated to content groups"
request_data
=
{
'name'
:
new_cohort_name
,
'assignment_type'
:
CourseCohort
.
RANDOM
}
request_data
=
{
'name'
:
new_cohort_name
,
'assignment_type'
:
CourseCohort
.
RANDOM
}
response_dict
=
self
.
put_
cohort_
handler
(
self
.
course
,
data
=
request_data
)
response_dict
=
self
.
put_handler
(
self
.
course
,
data
=
request_data
)
self
.
verify_contains_added_cohort
(
response_dict
,
new_cohort_name
,
assignment_type
=
CourseCohort
.
RANDOM
)
self
.
verify_contains_added_cohort
(
response_dict
,
new_cohort_name
,
assignment_type
=
CourseCohort
.
RANDOM
)
new_cohort_name
=
"New cohort linked to group"
new_cohort_name
=
"New cohort linked to group"
...
@@ -308,7 +374,7 @@ class CohortHandlerTestCase(CohortViewsTestCase):
...
@@ -308,7 +374,7 @@ class CohortHandlerTestCase(CohortViewsTestCase):
'user_partition_id'
:
1
,
'user_partition_id'
:
1
,
'group_id'
:
2
'group_id'
:
2
}
}
response_dict
=
self
.
put_
cohort_
handler
(
self
.
course
,
data
=
data
)
response_dict
=
self
.
put_handler
(
self
.
course
,
data
=
data
)
self
.
verify_contains_added_cohort
(
self
.
verify_contains_added_cohort
(
response_dict
,
response_dict
,
new_cohort_name
,
new_cohort_name
,
...
@@ -320,14 +386,14 @@ class CohortHandlerTestCase(CohortViewsTestCase):
...
@@ -320,14 +386,14 @@ class CohortHandlerTestCase(CohortViewsTestCase):
"""
"""
Verify that we cannot create a cohort without specifying a name.
Verify that we cannot create a cohort without specifying a name.
"""
"""
response_dict
=
self
.
put_
cohort_
handler
(
self
.
course
,
expected_response_code
=
400
)
response_dict
=
self
.
put_handler
(
self
.
course
,
expected_response_code
=
400
)
self
.
assertEqual
(
"Cohort name must be specified."
,
response_dict
.
get
(
"error"
))
self
.
assertEqual
(
"Cohort name must be specified."
,
response_dict
.
get
(
"error"
))
def
test_create_new_cohort_missing_assignment_type
(
self
):
def
test_create_new_cohort_missing_assignment_type
(
self
):
"""
"""
Verify that we cannot create a cohort without specifying an assignment type.
Verify that we cannot create a cohort without specifying an assignment type.
"""
"""
response_dict
=
self
.
put_
cohort_
handler
(
self
.
course
,
data
=
{
'name'
:
'COHORT NAME'
},
expected_response_code
=
400
)
response_dict
=
self
.
put_handler
(
self
.
course
,
data
=
{
'name'
:
'COHORT NAME'
},
expected_response_code
=
400
)
self
.
assertEqual
(
"Assignment type must be specified."
,
response_dict
.
get
(
"error"
))
self
.
assertEqual
(
"Assignment type must be specified."
,
response_dict
.
get
(
"error"
))
def
test_create_new_cohort_existing_name
(
self
):
def
test_create_new_cohort_existing_name
(
self
):
...
@@ -335,7 +401,7 @@ class CohortHandlerTestCase(CohortViewsTestCase):
...
@@ -335,7 +401,7 @@ class CohortHandlerTestCase(CohortViewsTestCase):
Verify that we cannot add a cohort with the same name as an existing cohort.
Verify that we cannot add a cohort with the same name as an existing cohort.
"""
"""
self
.
_create_cohorts
()
self
.
_create_cohorts
()
response_dict
=
self
.
put_
cohort_
handler
(
response_dict
=
self
.
put_handler
(
self
.
course
,
data
=
{
'name'
:
self
.
cohort1
.
name
,
'assignment_type'
:
CourseCohort
.
MANUAL
},
self
.
course
,
data
=
{
'name'
:
self
.
cohort1
.
name
,
'assignment_type'
:
CourseCohort
.
MANUAL
},
expected_response_code
=
400
expected_response_code
=
400
)
)
...
@@ -346,7 +412,7 @@ class CohortHandlerTestCase(CohortViewsTestCase):
...
@@ -346,7 +412,7 @@ class CohortHandlerTestCase(CohortViewsTestCase):
Verify that we cannot create a cohort with a group_id if the user_partition_id is not also specified.
Verify that we cannot create a cohort with a group_id if the user_partition_id is not also specified.
"""
"""
data
=
{
'name'
:
"Cohort missing user_partition_id"
,
'assignment_type'
:
CourseCohort
.
MANUAL
,
'group_id'
:
2
}
data
=
{
'name'
:
"Cohort missing user_partition_id"
,
'assignment_type'
:
CourseCohort
.
MANUAL
,
'group_id'
:
2
}
response_dict
=
self
.
put_
cohort_
handler
(
self
.
course
,
data
=
data
,
expected_response_code
=
400
)
response_dict
=
self
.
put_handler
(
self
.
course
,
data
=
data
,
expected_response_code
=
400
)
self
.
assertEqual
(
self
.
assertEqual
(
"If group_id is specified, user_partition_id must also be specified."
,
response_dict
.
get
(
"error"
)
"If group_id is specified, user_partition_id must also be specified."
,
response_dict
.
get
(
"error"
)
)
)
...
@@ -360,7 +426,7 @@ class CohortHandlerTestCase(CohortViewsTestCase):
...
@@ -360,7 +426,7 @@ class CohortHandlerTestCase(CohortViewsTestCase):
self
.
_create_cohorts
()
self
.
_create_cohorts
()
updated_name
=
self
.
cohort1
.
name
+
"_updated"
updated_name
=
self
.
cohort1
.
name
+
"_updated"
data
=
{
'name'
:
updated_name
,
'assignment_type'
:
CourseCohort
.
MANUAL
}
data
=
{
'name'
:
updated_name
,
'assignment_type'
:
CourseCohort
.
MANUAL
}
response_dict
=
self
.
put_
cohort_
handler
(
self
.
course
,
self
.
cohort1
,
data
=
data
)
response_dict
=
self
.
put_handler
(
self
.
course
,
self
.
cohort1
,
data
=
data
)
self
.
assertEqual
(
updated_name
,
get_cohort_by_id
(
self
.
course
.
id
,
self
.
cohort1
.
id
)
.
name
)
self
.
assertEqual
(
updated_name
,
get_cohort_by_id
(
self
.
course
.
id
,
self
.
cohort1
.
id
)
.
name
)
self
.
assertEqual
(
updated_name
,
response_dict
.
get
(
"name"
))
self
.
assertEqual
(
updated_name
,
response_dict
.
get
(
"name"
))
self
.
assertEqual
(
CourseCohort
.
MANUAL
,
response_dict
.
get
(
"assignment_type"
))
self
.
assertEqual
(
CourseCohort
.
MANUAL
,
response_dict
.
get
(
"assignment_type"
))
...
@@ -372,7 +438,7 @@ class CohortHandlerTestCase(CohortViewsTestCase):
...
@@ -372,7 +438,7 @@ class CohortHandlerTestCase(CohortViewsTestCase):
# Create a new cohort with random assignment
# Create a new cohort with random assignment
cohort_name
=
'I AM A RANDOM COHORT'
cohort_name
=
'I AM A RANDOM COHORT'
data
=
{
'name'
:
cohort_name
,
'assignment_type'
:
CourseCohort
.
RANDOM
}
data
=
{
'name'
:
cohort_name
,
'assignment_type'
:
CourseCohort
.
RANDOM
}
response_dict
=
self
.
put_
cohort_
handler
(
self
.
course
,
data
=
data
)
response_dict
=
self
.
put_handler
(
self
.
course
,
data
=
data
)
self
.
assertEqual
(
cohort_name
,
response_dict
.
get
(
"name"
))
self
.
assertEqual
(
cohort_name
,
response_dict
.
get
(
"name"
))
self
.
assertEqual
(
CourseCohort
.
RANDOM
,
response_dict
.
get
(
"assignment_type"
))
self
.
assertEqual
(
CourseCohort
.
RANDOM
,
response_dict
.
get
(
"assignment_type"
))
...
@@ -381,7 +447,7 @@ class CohortHandlerTestCase(CohortViewsTestCase):
...
@@ -381,7 +447,7 @@ class CohortHandlerTestCase(CohortViewsTestCase):
newly_created_cohort
=
get_cohort_by_name
(
self
.
course
.
id
,
cohort_name
)
newly_created_cohort
=
get_cohort_by_name
(
self
.
course
.
id
,
cohort_name
)
cohort_name
=
'I AM AN UPDATED RANDOM COHORT'
cohort_name
=
'I AM AN UPDATED RANDOM COHORT'
data
=
{
'name'
:
cohort_name
,
'assignment_type'
:
CourseCohort
.
RANDOM
}
data
=
{
'name'
:
cohort_name
,
'assignment_type'
:
CourseCohort
.
RANDOM
}
response_dict
=
self
.
put_
cohort_
handler
(
self
.
course
,
newly_created_cohort
,
data
=
data
)
response_dict
=
self
.
put_handler
(
self
.
course
,
newly_created_cohort
,
data
=
data
)
self
.
assertEqual
(
cohort_name
,
get_cohort_by_id
(
self
.
course
.
id
,
newly_created_cohort
.
id
)
.
name
)
self
.
assertEqual
(
cohort_name
,
get_cohort_by_id
(
self
.
course
.
id
,
newly_created_cohort
.
id
)
.
name
)
self
.
assertEqual
(
cohort_name
,
response_dict
.
get
(
"name"
))
self
.
assertEqual
(
cohort_name
,
response_dict
.
get
(
"name"
))
...
@@ -394,7 +460,7 @@ class CohortHandlerTestCase(CohortViewsTestCase):
...
@@ -394,7 +460,7 @@ class CohortHandlerTestCase(CohortViewsTestCase):
# Create a new cohort with random assignment
# Create a new cohort with random assignment
cohort_name
=
'I AM A RANDOM COHORT'
cohort_name
=
'I AM A RANDOM COHORT'
data
=
{
'name'
:
cohort_name
,
'assignment_type'
:
CourseCohort
.
RANDOM
}
data
=
{
'name'
:
cohort_name
,
'assignment_type'
:
CourseCohort
.
RANDOM
}
response_dict
=
self
.
put_
cohort_
handler
(
self
.
course
,
data
=
data
)
response_dict
=
self
.
put_handler
(
self
.
course
,
data
=
data
)
self
.
assertEqual
(
cohort_name
,
response_dict
.
get
(
"name"
))
self
.
assertEqual
(
cohort_name
,
response_dict
.
get
(
"name"
))
self
.
assertEqual
(
CourseCohort
.
RANDOM
,
response_dict
.
get
(
"assignment_type"
))
self
.
assertEqual
(
CourseCohort
.
RANDOM
,
response_dict
.
get
(
"assignment_type"
))
...
@@ -402,9 +468,9 @@ class CohortHandlerTestCase(CohortViewsTestCase):
...
@@ -402,9 +468,9 @@ class CohortHandlerTestCase(CohortViewsTestCase):
# Try to update the assignment type of newly created random cohort
# Try to update the assignment type of newly created random cohort
cohort
=
get_cohort_by_name
(
self
.
course
.
id
,
cohort_name
)
cohort
=
get_cohort_by_name
(
self
.
course
.
id
,
cohort_name
)
data
=
{
'name'
:
cohort_name
,
'assignment_type'
:
CourseCohort
.
MANUAL
}
data
=
{
'name'
:
cohort_name
,
'assignment_type'
:
CourseCohort
.
MANUAL
}
response_dict
=
self
.
put_
cohort_
handler
(
self
.
course
,
cohort
,
data
=
data
,
expected_response_code
=
400
)
response_dict
=
self
.
put_handler
(
self
.
course
,
cohort
,
data
=
data
,
expected_response_code
=
400
)
self
.
assertEqual
(
self
.
assertEqual
(
'There must be one cohort to which students can
be randomly
assigned.'
,
response_dict
.
get
(
"error"
)
'There must be one cohort to which students can
automatically be
assigned.'
,
response_dict
.
get
(
"error"
)
)
)
def
test_update_cohort_group_id
(
self
):
def
test_update_cohort_group_id
(
self
):
...
@@ -419,7 +485,7 @@ class CohortHandlerTestCase(CohortViewsTestCase):
...
@@ -419,7 +485,7 @@ class CohortHandlerTestCase(CohortViewsTestCase):
'group_id'
:
2
,
'group_id'
:
2
,
'user_partition_id'
:
3
'user_partition_id'
:
3
}
}
response_dict
=
self
.
put_
cohort_
handler
(
self
.
course
,
self
.
cohort1
,
data
=
data
)
response_dict
=
self
.
put_handler
(
self
.
course
,
self
.
cohort1
,
data
=
data
)
self
.
assertEqual
((
2
,
3
),
get_group_info_for_cohort
(
self
.
cohort1
))
self
.
assertEqual
((
2
,
3
),
get_group_info_for_cohort
(
self
.
cohort1
))
self
.
assertEqual
(
2
,
response_dict
.
get
(
"group_id"
))
self
.
assertEqual
(
2
,
response_dict
.
get
(
"group_id"
))
self
.
assertEqual
(
3
,
response_dict
.
get
(
"user_partition_id"
))
self
.
assertEqual
(
3
,
response_dict
.
get
(
"user_partition_id"
))
...
@@ -434,7 +500,7 @@ class CohortHandlerTestCase(CohortViewsTestCase):
...
@@ -434,7 +500,7 @@ class CohortHandlerTestCase(CohortViewsTestCase):
link_cohort_to_partition_group
(
self
.
cohort1
,
5
,
0
)
link_cohort_to_partition_group
(
self
.
cohort1
,
5
,
0
)
self
.
assertEqual
((
0
,
5
),
get_group_info_for_cohort
(
self
.
cohort1
))
self
.
assertEqual
((
0
,
5
),
get_group_info_for_cohort
(
self
.
cohort1
))
data
=
{
'name'
:
self
.
cohort1
.
name
,
'assignment_type'
:
CourseCohort
.
RANDOM
,
'group_id'
:
None
}
data
=
{
'name'
:
self
.
cohort1
.
name
,
'assignment_type'
:
CourseCohort
.
RANDOM
,
'group_id'
:
None
}
response_dict
=
self
.
put_
cohort_
handler
(
self
.
course
,
self
.
cohort1
,
data
=
data
)
response_dict
=
self
.
put_handler
(
self
.
course
,
self
.
cohort1
,
data
=
data
)
self
.
assertEqual
((
None
,
None
),
get_group_info_for_cohort
(
self
.
cohort1
))
self
.
assertEqual
((
None
,
None
),
get_group_info_for_cohort
(
self
.
cohort1
))
self
.
assertIsNone
(
response_dict
.
get
(
"group_id"
))
self
.
assertIsNone
(
response_dict
.
get
(
"group_id"
))
self
.
assertIsNone
(
response_dict
.
get
(
"user_partition_id"
))
self
.
assertIsNone
(
response_dict
.
get
(
"user_partition_id"
))
...
@@ -452,7 +518,7 @@ class CohortHandlerTestCase(CohortViewsTestCase):
...
@@ -452,7 +518,7 @@ class CohortHandlerTestCase(CohortViewsTestCase):
'group_id'
:
2
,
'group_id'
:
2
,
'user_partition_id'
:
3
'user_partition_id'
:
3
}
}
self
.
put_
cohort_
handler
(
self
.
course
,
self
.
cohort4
,
data
=
data
)
self
.
put_handler
(
self
.
course
,
self
.
cohort4
,
data
=
data
)
self
.
assertEqual
((
2
,
3
),
get_group_info_for_cohort
(
self
.
cohort4
))
self
.
assertEqual
((
2
,
3
),
get_group_info_for_cohort
(
self
.
cohort4
))
data
=
{
data
=
{
...
@@ -461,7 +527,7 @@ class CohortHandlerTestCase(CohortViewsTestCase):
...
@@ -461,7 +527,7 @@ class CohortHandlerTestCase(CohortViewsTestCase):
'group_id'
:
1
,
'group_id'
:
1
,
'user_partition_id'
:
3
'user_partition_id'
:
3
}
}
self
.
put_
cohort_
handler
(
self
.
course
,
self
.
cohort4
,
data
=
data
)
self
.
put_handler
(
self
.
course
,
self
.
cohort4
,
data
=
data
)
self
.
assertEqual
((
1
,
3
),
get_group_info_for_cohort
(
self
.
cohort4
))
self
.
assertEqual
((
1
,
3
),
get_group_info_for_cohort
(
self
.
cohort4
))
def
test_update_cohort_missing_user_partition_id
(
self
):
def
test_update_cohort_missing_user_partition_id
(
self
):
...
@@ -470,7 +536,7 @@ class CohortHandlerTestCase(CohortViewsTestCase):
...
@@ -470,7 +536,7 @@ class CohortHandlerTestCase(CohortViewsTestCase):
"""
"""
self
.
_create_cohorts
()
self
.
_create_cohorts
()
data
=
{
'name'
:
self
.
cohort1
.
name
,
'assignment_type'
:
CourseCohort
.
RANDOM
,
'group_id'
:
2
}
data
=
{
'name'
:
self
.
cohort1
.
name
,
'assignment_type'
:
CourseCohort
.
RANDOM
,
'group_id'
:
2
}
response_dict
=
self
.
put_
cohort_
handler
(
self
.
course
,
self
.
cohort1
,
data
=
data
,
expected_response_code
=
400
)
response_dict
=
self
.
put_handler
(
self
.
course
,
self
.
cohort1
,
data
=
data
,
expected_response_code
=
400
)
self
.
assertEqual
(
self
.
assertEqual
(
"If group_id is specified, user_partition_id must also be specified."
,
response_dict
.
get
(
"error"
)
"If group_id is specified, user_partition_id must also be specified."
,
response_dict
.
get
(
"error"
)
)
)
...
...
openedx/core/djangoapps/course_groups/views.py
View file @
3ce494f5
"""
Views related to course groups functionality.
"""
from
django_future.csrf
import
ensure_csrf_cookie
from
django_future.csrf
import
ensure_csrf_cookie
from
django.views.decorators.http
import
require_POST
from
django.views.decorators.http
import
require_POST
from
django.contrib.auth.models
import
User
from
django.contrib.auth.models
import
User
...
@@ -12,6 +16,7 @@ from django.utils.translation import ugettext
...
@@ -12,6 +16,7 @@ from django.utils.translation import ugettext
import
logging
import
logging
import
re
import
re
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
courseware.courses
import
get_course_with_access
from
courseware.courses
import
get_course_with_access
from
edxmako.shortcuts
import
render_to_response
from
edxmako.shortcuts
import
render_to_response
...
@@ -55,6 +60,19 @@ def unlink_cohort_partition_group(cohort):
...
@@ -55,6 +60,19 @@ def unlink_cohort_partition_group(cohort):
CourseUserGroupPartitionGroup
.
objects
.
filter
(
course_user_group
=
cohort
)
.
delete
()
CourseUserGroupPartitionGroup
.
objects
.
filter
(
course_user_group
=
cohort
)
.
delete
()
# pylint: disable=invalid-name
def
_get_course_cohort_settings_representation
(
course_cohort_settings
):
"""
Returns a JSON representation of a course cohort settings.
"""
return
{
'id'
:
course_cohort_settings
.
id
,
'is_cohorted'
:
course_cohort_settings
.
is_cohorted
,
'cohorted_discussions'
:
course_cohort_settings
.
cohorted_discussions
,
'always_cohort_inline_discussions'
:
course_cohort_settings
.
always_cohort_inline_discussions
,
}
def
_get_cohort_representation
(
cohort
,
course
):
def
_get_cohort_representation
(
cohort
,
course
):
"""
"""
Returns a JSON representation of a cohort.
Returns a JSON representation of a cohort.
...
@@ -71,6 +89,33 @@ def _get_cohort_representation(cohort, course):
...
@@ -71,6 +89,33 @@ def _get_cohort_representation(cohort, course):
}
}
@require_http_methods
((
"GET"
,
"PUT"
,
"POST"
))
@ensure_csrf_cookie
@expect_json
@login_required
def
course_cohort_settings_handler
(
request
,
course_key_string
):
"""
The restful handler for cohort setting requests. Requires JSON.
This will raise 404 if user is not staff.
GET
Returns the JSON representation of cohort settings for the course.
PUT or POST
Updates the cohort settings for the course. Returns the JSON representation of updated settings.
"""
course_key
=
CourseKey
.
from_string
(
course_key_string
)
get_course_with_access
(
request
.
user
,
'staff'
,
course_key
)
if
request
.
method
==
'GET'
:
cohort_settings
=
cohorts
.
get_course_cohort_settings
(
course_key
)
return
JsonResponse
(
_get_course_cohort_settings_representation
(
cohort_settings
))
else
:
is_cohorted
=
request
.
json
.
get
(
'is_cohorted'
)
if
is_cohorted
is
None
:
# Note: error message not translated because it is not exposed to the user (UI prevents this state).
return
JsonResponse
({
"error"
:
"Bad Request"
},
400
)
cohort_settings
=
cohorts
.
set_course_cohort_settings
(
course_key
,
is_cohorted
=
is_cohorted
)
return
JsonResponse
(
_get_course_cohort_settings_representation
(
cohort_settings
))
@require_http_methods
((
"GET"
,
"PUT"
,
"POST"
,
"PATCH"
))
@require_http_methods
((
"GET"
,
"PUT"
,
"POST"
,
"PATCH"
))
@ensure_csrf_cookie
@ensure_csrf_cookie
@expect_json
@expect_json
...
@@ -306,7 +351,7 @@ def debug_cohort_mgmt(request, course_key_string):
...
@@ -306,7 +351,7 @@ def debug_cohort_mgmt(request, course_key_string):
# add staff check to make sure it's safe if it's accidentally deployed.
# add staff check to make sure it's safe if it's accidentally deployed.
get_course_with_access
(
request
.
user
,
'staff'
,
course_key
)
get_course_with_access
(
request
.
user
,
'staff'
,
course_key
)
context
=
{
'cohorts_
ajax_
url'
:
reverse
(
context
=
{
'cohorts_url'
:
reverse
(
'cohorts'
,
'cohorts'
,
kwargs
=
{
'course_key'
:
course_key
.
to_deprecated_string
()}
kwargs
=
{
'course_key'
:
course_key
.
to_deprecated_string
()}
)}
)}
...
...
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