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
8951ac8c
Commit
8951ac8c
authored
May 03, 2017
by
cahrens
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactor server-size code to enable enrollment tracks.
EDUCATOR-11
parent
0d9146a2
Show whitespace changes
Inline
Side-by-side
Showing
36 changed files
with
856 additions
and
365 deletions
+856
-365
common/djangoapps/django_comment_common/migrations/0005_coursediscussionsettings.py
+25
-0
common/djangoapps/django_comment_common/models.py
+28
-0
common/djangoapps/django_comment_common/tests.py
+81
-2
common/djangoapps/django_comment_common/utils.py
+48
-0
common/lib/xmodule/xmodule/partitions/partitions_service.py
+2
-2
common/static/common/js/discussion/views/discussion_thread_list_view.js
+6
-6
common/static/common/js/discussion/views/discussion_topic_menu_view.js
+1
-1
common/static/common/js/discussion/views/new_post_view.js
+8
-8
common/static/common/js/spec/discussion/view/discussion_thread_list_view_spec.js
+9
-9
common/static/common/js/spec/discussion/view/new_post_view_spec.js
+21
-21
common/static/common/js/spec_helpers/discussion_spec_helper.js
+7
-7
common/static/common/templates/discussion/new-post-menu-entry.underscore
+1
-1
common/static/common/templates/discussion/new-post.underscore
+3
-3
lms/djangoapps/discussion/static/discussion/js/spec/discussion_board_factory_spec.js
+2
-2
lms/djangoapps/discussion/tests/test_views.py
+10
-2
lms/djangoapps/discussion/views.py
+20
-14
lms/djangoapps/discussion_api/api.py
+9
-5
lms/djangoapps/discussion_api/permissions.py
+1
-1
lms/djangoapps/discussion_api/serializers.py
+5
-3
lms/djangoapps/discussion_api/tests/test_api.py
+5
-0
lms/djangoapps/discussion_api/tests/test_permissions.py
+1
-0
lms/djangoapps/discussion_api/tests/test_serializers.py
+4
-0
lms/djangoapps/django_comment_client/tests/group_id.py
+19
-3
lms/djangoapps/django_comment_client/tests/test_utils.py
+251
-37
lms/djangoapps/django_comment_client/utils.py
+147
-36
lms/djangoapps/instructor/tests/test_api.py
+2
-2
lms/templates/discussion/_filter_dropdown.html
+1
-1
lms/templates/discussion/_thread_list_template.html
+7
-7
openedx/core/djangoapps/course_groups/cohorts.py
+45
-53
openedx/core/djangoapps/course_groups/models.py
+10
-2
openedx/core/djangoapps/course_groups/tests/helpers.py
+13
-10
openedx/core/djangoapps/course_groups/tests/test_cohorts.py
+6
-87
openedx/core/djangoapps/course_groups/tests/test_views.py
+13
-2
openedx/core/djangoapps/course_groups/views.py
+42
-35
openedx/core/djangoapps/verified_track_content/partition_scheme.py
+1
-1
openedx/core/djangoapps/verified_track_content/tests/test_models.py
+2
-2
No files found.
common/djangoapps/django_comment_common/migrations/0005_coursediscussionsettings.py
0 → 100644
View file @
8951ac8c
# -*- coding: utf-8 -*-
from
__future__
import
unicode_literals
from
django.db
import
migrations
,
models
import
openedx.core.djangoapps.xmodule_django.models
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'django_comment_common'
,
'0004_auto_20161117_1209'
),
]
operations
=
[
migrations
.
CreateModel
(
name
=
'CourseDiscussionSettings'
,
fields
=
[
(
'id'
,
models
.
AutoField
(
verbose_name
=
'ID'
,
serialize
=
False
,
auto_created
=
True
,
primary_key
=
True
)),
(
'course_id'
,
openedx
.
core
.
djangoapps
.
xmodule_django
.
models
.
CourseKeyField
(
help_text
=
b
'Which course are these settings associated with?'
,
unique
=
True
,
max_length
=
255
,
db_index
=
True
)),
(
'always_divide_inline_discussions'
,
models
.
BooleanField
(
default
=
False
)),
(
'_divided_discussions'
,
models
.
TextField
(
null
=
True
,
db_column
=
b
'divided_discussions'
,
blank
=
True
)),
(
'division_scheme'
,
models
.
CharField
(
default
=
b
'none'
,
max_length
=
20
,
choices
=
[(
b
'none'
,
b
'None'
),
(
b
'cohort'
,
b
'Cohort'
),
(
b
'enrollment_track'
,
b
'Enrollment Track'
)])),
],
),
]
common/djangoapps/django_comment_common/models.py
View file @
8951ac8c
import
json
import
logging
import
logging
from
config_models.models
import
ConfigurationModel
from
config_models.models
import
ConfigurationModel
...
@@ -162,3 +163,30 @@ class ForumsConfig(ConfigurationModel):
...
@@ -162,3 +163,30 @@ class ForumsConfig(ConfigurationModel):
def
__unicode__
(
self
):
def
__unicode__
(
self
):
"""Simple representation so the admin screen looks less ugly."""
"""Simple representation so the admin screen looks less ugly."""
return
u"ForumsConfig: timeout={}"
.
format
(
self
.
connection_timeout
)
return
u"ForumsConfig: timeout={}"
.
format
(
self
.
connection_timeout
)
class
CourseDiscussionSettings
(
models
.
Model
):
course_id
=
CourseKeyField
(
unique
=
True
,
max_length
=
255
,
db_index
=
True
,
help_text
=
"Which course are these settings associated with?"
,
)
always_divide_inline_discussions
=
models
.
BooleanField
(
default
=
False
)
_divided_discussions
=
models
.
TextField
(
db_column
=
'divided_discussions'
,
null
=
True
,
blank
=
True
)
# JSON list
COHORT
=
'cohort'
ENROLLMENT_TRACK
=
'enrollment_track'
NONE
=
'none'
ASSIGNMENT_TYPE_CHOICES
=
((
NONE
,
'None'
),
(
COHORT
,
'Cohort'
),
(
ENROLLMENT_TRACK
,
'Enrollment Track'
))
division_scheme
=
models
.
CharField
(
max_length
=
20
,
choices
=
ASSIGNMENT_TYPE_CHOICES
,
default
=
NONE
)
@property
def
divided_discussions
(
self
):
"""Jsonify the divided_discussions"""
return
json
.
loads
(
self
.
_divided_discussions
)
@divided_discussions.setter
def
divided_discussions
(
self
,
value
):
"""Un-Jsonify the divided_discussions"""
self
.
_divided_discussions
=
json
.
dumps
(
value
)
common/djangoapps/django_comment_common/tests.py
View file @
8951ac8c
from
django.test
import
TestCase
from
nose.plugins.attrib
import
attr
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
django.test
import
TestCase
from
django_comment_common.models
import
Role
from
django_comment_common.models
import
Role
from
models
import
CourseDiscussionSettings
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
openedx.core.djangoapps.course_groups.cohorts
import
CourseCohortsSettings
from
student.models
import
CourseEnrollment
,
User
from
student.models
import
CourseEnrollment
,
User
from
utils
import
get_course_discussion_settings
,
set_course_discussion_settings
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
@attr
(
shard
=
1
)
class
RoleAssignmentTest
(
TestCase
):
class
RoleAssignmentTest
(
TestCase
):
"""
"""
Basic checks to make sure our Roles get assigned and unassigned as students
Basic checks to make sure our Roles get assigned and unassigned as students
...
@@ -55,3 +64,73 @@ class RoleAssignmentTest(TestCase):
...
@@ -55,3 +64,73 @@ class RoleAssignmentTest(TestCase):
# )
# )
# self.assertNotIn(student_role, self.student_user.roles.all())
# self.assertNotIn(student_role, self.student_user.roles.all())
# self.assertIn(student_role, another_student.roles.all())
# self.assertIn(student_role, another_student.roles.all())
@attr
(
shard
=
1
)
class
CourseDiscussionSettingsTest
(
ModuleStoreTestCase
):
def
setUp
(
self
):
super
(
CourseDiscussionSettingsTest
,
self
)
.
setUp
()
self
.
course
=
CourseFactory
.
create
()
def
test_get_course_discussion_settings
(
self
):
discussion_settings
=
get_course_discussion_settings
(
self
.
course
.
id
)
self
.
assertEqual
(
CourseDiscussionSettings
.
NONE
,
discussion_settings
.
division_scheme
)
self
.
assertEqual
([],
discussion_settings
.
divided_discussions
)
self
.
assertFalse
(
discussion_settings
.
always_divide_inline_discussions
)
def
test_get_course_discussion_settings_legacy_settings
(
self
):
self
.
course
.
cohort_config
=
{
'cohorted'
:
True
,
'always_cohort_inline_discussions'
:
True
,
'cohorted_discussions'
:
[
'foo'
]
}
modulestore
()
.
update_item
(
self
.
course
,
ModuleStoreEnum
.
UserID
.
system
)
discussion_settings
=
get_course_discussion_settings
(
self
.
course
.
id
)
self
.
assertEqual
(
CourseDiscussionSettings
.
COHORT
,
discussion_settings
.
division_scheme
)
self
.
assertEqual
([
'foo'
],
discussion_settings
.
divided_discussions
)
self
.
assertTrue
(
discussion_settings
.
always_divide_inline_discussions
)
def
test_get_course_discussion_settings_cohort_settings
(
self
):
CourseCohortsSettings
.
objects
.
get_or_create
(
course_id
=
self
.
course
.
id
,
defaults
=
{
'is_cohorted'
:
True
,
'always_cohort_inline_discussions'
:
True
,
'cohorted_discussions'
:
[
'foo'
,
'bar'
]
}
)
discussion_settings
=
get_course_discussion_settings
(
self
.
course
.
id
)
self
.
assertEqual
(
CourseDiscussionSettings
.
COHORT
,
discussion_settings
.
division_scheme
)
self
.
assertEqual
([
'foo'
,
'bar'
],
discussion_settings
.
divided_discussions
)
self
.
assertTrue
(
discussion_settings
.
always_divide_inline_discussions
)
def
test_set_course_discussion_settings
(
self
):
set_course_discussion_settings
(
course_key
=
self
.
course
.
id
,
divided_discussions
=
[
'cohorted_topic'
],
division_scheme
=
CourseDiscussionSettings
.
ENROLLMENT_TRACK
,
always_divide_inline_discussions
=
True
,
)
discussion_settings
=
get_course_discussion_settings
(
self
.
course
.
id
)
self
.
assertEqual
(
CourseDiscussionSettings
.
ENROLLMENT_TRACK
,
discussion_settings
.
division_scheme
)
self
.
assertEqual
([
'cohorted_topic'
],
discussion_settings
.
divided_discussions
)
self
.
assertTrue
(
discussion_settings
.
always_divide_inline_discussions
)
def
test_invalid_data_types
(
self
):
exception_msg_template
=
"Incorrect field type for `{}`. Type must be `{}`"
fields
=
[
{
'name'
:
'division_scheme'
,
'type'
:
str
},
{
'name'
:
'always_divide_inline_discussions'
,
'type'
:
bool
},
{
'name'
:
'divided_discussions'
,
'type'
:
list
}
]
invalid_value
=
3.14
for
field
in
fields
:
with
self
.
assertRaises
(
ValueError
)
as
value_error
:
set_course_discussion_settings
(
self
.
course
.
id
,
**
{
field
[
'name'
]:
invalid_value
})
self
.
assertEqual
(
value_error
.
exception
.
message
,
exception_msg_template
.
format
(
field
[
'name'
],
field
[
'type'
]
.
__name__
)
)
common/djangoapps/django_comment_common/utils.py
View file @
8951ac8c
...
@@ -9,6 +9,10 @@ from django_comment_common.models import (
...
@@ -9,6 +9,10 @@ from django_comment_common.models import (
FORUM_ROLE_STUDENT
,
FORUM_ROLE_STUDENT
,
Role
Role
)
)
from
openedx.core.djangoapps.course_groups.cohorts
import
get_legacy_discussion_settings
from
request_cache.middleware
import
request_cached
from
.models
import
CourseDiscussionSettings
class
ThreadContext
(
object
):
class
ThreadContext
(
object
):
...
@@ -91,3 +95,47 @@ def are_permissions_roles_seeded(course_id):
...
@@ -91,3 +95,47 @@ def are_permissions_roles_seeded(course_id):
return
False
return
False
return
True
return
True
@request_cached
def
get_course_discussion_settings
(
course_key
):
try
:
course_discussion_settings
=
CourseDiscussionSettings
.
objects
.
get
(
course_id
=
course_key
)
except
CourseDiscussionSettings
.
DoesNotExist
:
legacy_discussion_settings
=
get_legacy_discussion_settings
(
course_key
)
course_discussion_settings
,
_
=
CourseDiscussionSettings
.
objects
.
get_or_create
(
course_id
=
course_key
,
defaults
=
{
'always_divide_inline_discussions'
:
legacy_discussion_settings
[
'always_cohort_inline_discussions'
],
'divided_discussions'
:
legacy_discussion_settings
[
'cohorted_discussions'
],
'division_scheme'
:
CourseDiscussionSettings
.
COHORT
if
legacy_discussion_settings
[
'is_cohorted'
]
else
CourseDiscussionSettings
.
NONE
}
)
return
course_discussion_settings
def
set_course_discussion_settings
(
course_key
,
**
kwargs
):
"""
Set discussion settings for a course.
Arguments:
course_key: CourseKey
always_divide_inline_discussions (bool): If inline discussions should always be divided.
divided_discussions (list): List of discussion ids.
division_scheme (str): `CourseDiscussionSettings.NONE`, `CourseDiscussionSettings.COHORT`,
or `CourseDiscussionSettings.ENROLLMENT_TRACK`
Returns:
A CourseDiscussionSettings object.
"""
fields
=
{
'division_scheme'
:
str
,
'always_divide_inline_discussions'
:
bool
,
'divided_discussions'
:
list
}
course_discussion_settings
=
get_course_discussion_settings
(
course_key
)
for
field
,
field_type
in
fields
.
items
():
if
field
in
kwargs
:
if
not
isinstance
(
kwargs
[
field
],
field_type
):
raise
ValueError
(
"Incorrect field type for `{}`. Type must be `{}`"
.
format
(
field
,
field_type
.
__name__
))
setattr
(
course_discussion_settings
,
field
,
kwargs
[
field
])
course_discussion_settings
.
save
()
return
course_discussion_settings
common/lib/xmodule/xmodule/partitions/partitions_service.py
View file @
8951ac8c
...
@@ -133,7 +133,7 @@ class PartitionService(object):
...
@@ -133,7 +133,7 @@ class PartitionService(object):
if
self
.
_cache
and
(
cache_key
in
self
.
_cache
):
if
self
.
_cache
and
(
cache_key
in
self
.
_cache
):
return
self
.
_cache
[
cache_key
]
return
self
.
_cache
[
cache_key
]
user_partition
=
self
.
_
get_user_partition
(
user_partition_id
)
user_partition
=
self
.
get_user_partition
(
user_partition_id
)
if
user_partition
is
None
:
if
user_partition
is
None
:
raise
ValueError
(
raise
ValueError
(
"Configuration problem! No user_partition with id {0} "
"Configuration problem! No user_partition with id {0} "
...
@@ -148,7 +148,7 @@ class PartitionService(object):
...
@@ -148,7 +148,7 @@ class PartitionService(object):
return
group_id
return
group_id
def
_
get_user_partition
(
self
,
user_partition_id
):
def
get_user_partition
(
self
,
user_partition_id
):
"""
"""
Look for a user partition with a matching id in the course's partitions.
Look for a user partition with a matching id in the course's partitions.
Note that this method can return an inactive user partition.
Note that this method can return an inactive user partition.
...
...
common/static/common/js/discussion/views/discussion_thread_list_view.js
View file @
8951ac8c
...
@@ -32,8 +32,8 @@
...
@@ -32,8 +32,8 @@
this
.
retrieveFollowed
=
function
()
{
this
.
retrieveFollowed
=
function
()
{
return
DiscussionThreadListView
.
prototype
.
retrieveFollowed
.
apply
(
self
,
arguments
);
return
DiscussionThreadListView
.
prototype
.
retrieveFollowed
.
apply
(
self
,
arguments
);
};
};
this
.
choose
Cohort
=
function
()
{
this
.
choose
Group
=
function
()
{
return
DiscussionThreadListView
.
prototype
.
choose
Cohort
.
apply
(
self
,
arguments
);
return
DiscussionThreadListView
.
prototype
.
choose
Group
.
apply
(
self
,
arguments
);
};
};
this
.
chooseFilter
=
function
()
{
this
.
chooseFilter
=
function
()
{
return
DiscussionThreadListView
.
prototype
.
chooseFilter
.
apply
(
self
,
arguments
);
return
DiscussionThreadListView
.
prototype
.
chooseFilter
.
apply
(
self
,
arguments
);
...
@@ -85,7 +85,7 @@
...
@@ -85,7 +85,7 @@
'click .forum-nav-thread-link'
:
'threadSelected'
,
'click .forum-nav-thread-link'
:
'threadSelected'
,
'click .forum-nav-load-more-link'
:
'loadMorePages'
,
'click .forum-nav-load-more-link'
:
'loadMorePages'
,
'change .forum-nav-filter-main-control'
:
'chooseFilter'
,
'change .forum-nav-filter-main-control'
:
'chooseFilter'
,
'change .forum-nav-filter-cohort-control'
:
'choose
Cohort
'
'change .forum-nav-filter-cohort-control'
:
'choose
Group
'
};
};
DiscussionThreadListView
.
prototype
.
initialize
=
function
(
options
)
{
DiscussionThreadListView
.
prototype
.
initialize
=
function
(
options
)
{
...
@@ -194,7 +194,7 @@
...
@@ -194,7 +194,7 @@
edx
.
HtmlUtils
.
append
(
edx
.
HtmlUtils
.
append
(
this
.
$el
,
this
.
$el
,
this
.
template
({
this
.
template
({
is
Cohorted
:
this
.
courseSettings
.
get
(
'is_cohort
ed'
),
is
DiscussionDivisionEnabled
:
this
.
courseSettings
.
get
(
'is_discussion_division_enabl
ed'
),
isPrivilegedUser
:
DiscussionUtil
.
isPrivilegedUser
()
isPrivilegedUser
:
DiscussionUtil
.
isPrivilegedUser
()
})
})
);
);
...
@@ -404,7 +404,7 @@
...
@@ -404,7 +404,7 @@
return
$
(
elem
).
data
(
'discussion-id'
);
return
$
(
elem
).
data
(
'discussion-id'
);
}).
get
();
}).
get
();
this
.
retrieveDiscussions
(
discussionIds
);
this
.
retrieveDiscussions
(
discussionIds
);
return
this
.
$
(
'.forum-nav-filter-cohort'
).
toggle
(
$item
.
data
(
'
cohort
ed'
)
===
true
);
return
this
.
$
(
'.forum-nav-filter-cohort'
).
toggle
(
$item
.
data
(
'
divid
ed'
)
===
true
);
}
}
};
};
...
@@ -413,7 +413,7 @@
...
@@ -413,7 +413,7 @@
return
this
.
retrieveFirstPage
();
return
this
.
retrieveFirstPage
();
};
};
DiscussionThreadListView
.
prototype
.
choose
Cohort
=
function
()
{
DiscussionThreadListView
.
prototype
.
choose
Group
=
function
()
{
this
.
group_id
=
this
.
$
(
'.forum-nav-filter-cohort-control :selected'
).
val
();
this
.
group_id
=
this
.
$
(
'.forum-nav-filter-cohort-control :selected'
).
val
();
return
this
.
retrieveFirstPage
();
return
this
.
retrieveFirstPage
();
};
};
...
...
common/static/common/js/discussion/views/discussion_topic_menu_view.js
View file @
8951ac8c
...
@@ -35,7 +35,7 @@
...
@@ -35,7 +35,7 @@
'[data-discussion-id="'
+
this
.
getCurrentTopicId
()
+
'"]'
'[data-discussion-id="'
+
this
.
getCurrentTopicId
()
+
'"]'
));
));
}
else
if
(
$general
.
length
>
0
)
{
}
else
if
(
$general
.
length
>
0
)
{
this
.
setTopic
(
$general
);
this
.
setTopic
(
$general
.
first
()
);
}
else
{
}
else
{
this
.
setTopic
(
this
.
$
(
'.post-topic option'
).
first
());
this
.
setTopic
(
this
.
$
(
'.post-topic option'
).
first
());
}
}
...
...
common/static/common/js/discussion/views/new_post_view.js
View file @
8951ac8c
...
@@ -51,7 +51,7 @@
...
@@ -51,7 +51,7 @@
threadTypeTemplate
;
threadTypeTemplate
;
context
=
_
.
clone
(
this
.
course_settings
.
attributes
);
context
=
_
.
clone
(
this
.
course_settings
.
attributes
);
_
.
extend
(
context
,
{
_
.
extend
(
context
,
{
cohort_options
:
this
.
getCohort
Options
(),
group_options
:
this
.
getGroup
Options
(),
is_commentable_divided
:
this
.
is_commentable_divided
,
is_commentable_divided
:
this
.
is_commentable_divided
,
mode
:
this
.
mode
,
mode
:
this
.
mode
,
startHeader
:
this
.
startHeader
,
startHeader
:
this
.
startHeader
,
...
@@ -84,15 +84,15 @@
...
@@ -84,15 +84,15 @@
return
this
.
mode
===
'tab'
;
return
this
.
mode
===
'tab'
;
};
};
NewPostView
.
prototype
.
get
Cohort
Options
=
function
()
{
NewPostView
.
prototype
.
get
Group
Options
=
function
()
{
var
userGroupId
;
var
userGroupId
;
if
(
this
.
course_settings
.
get
(
'is_
cohort
ed'
)
&&
DiscussionUtil
.
isPrivilegedUser
())
{
if
(
this
.
course_settings
.
get
(
'is_
discussion_division_enabl
ed'
)
&&
DiscussionUtil
.
isPrivilegedUser
())
{
userGroupId
=
$
(
'#discussion-container'
).
data
(
'user-group-id'
);
userGroupId
=
$
(
'#discussion-container'
).
data
(
'user-group-id'
);
return
_
.
map
(
this
.
course_settings
.
get
(
'
cohorts'
),
function
(
cohort
)
{
return
_
.
map
(
this
.
course_settings
.
get
(
'
groups'
),
function
(
group
)
{
return
{
return
{
value
:
cohort
.
id
,
value
:
group
.
id
,
text
:
cohort
.
name
,
text
:
group
.
name
,
selected
:
cohort
.
id
===
userGroupId
selected
:
group
.
id
===
userGroupId
};
};
});
});
}
else
{
}
else
{
...
@@ -112,7 +112,7 @@
...
@@ -112,7 +112,7 @@
};
};
NewPostView
.
prototype
.
toggleGroupDropdown
=
function
(
$target
)
{
NewPostView
.
prototype
.
toggleGroupDropdown
=
function
(
$target
)
{
if
(
$target
.
data
(
'
cohort
ed'
))
{
if
(
$target
.
data
(
'
divid
ed'
))
{
$
(
'.js-group-select'
).
prop
(
'disabled'
,
false
);
$
(
'.js-group-select'
).
prop
(
'disabled'
,
false
);
return
$
(
'.group-selector-wrapper'
).
removeClass
(
'disabled'
);
return
$
(
'.group-selector-wrapper'
).
removeClass
(
'disabled'
);
}
else
{
}
else
{
...
...
common/static/common/js/spec/discussion/view/discussion_thread_list_view_spec.js
View file @
8951ac8c
...
@@ -62,7 +62,7 @@
...
@@ -62,7 +62,7 @@
' <li'
+
' <li'
+
' class="forum-nav-browse-menu-item"'
+
' class="forum-nav-browse-menu-item"'
+
' data-discussion-id="child"'
+
' data-discussion-id="child"'
+
' data-
cohort
ed="false"'
+
' data-
divid
ed="false"'
+
' >'
+
' >'
+
' <a href="#" class="forum-nav-browse-title">Child</a>'
+
' <a href="#" class="forum-nav-browse-title">Child</a>'
+
' </li>'
+
' </li>'
+
...
@@ -70,7 +70,7 @@
...
@@ -70,7 +70,7 @@
' <li'
+
' <li'
+
' class="forum-nav-browse-menu-item"'
+
' class="forum-nav-browse-menu-item"'
+
' data-discussion-id="sibling"'
+
' data-discussion-id="sibling"'
+
' data-
cohort
ed="false"'
+
' data-
divid
ed="false"'
+
' >'
+
' >'
+
' <a href="#" class="forum-nav-browse-title">Sibling</a>'
+
' <a href="#" class="forum-nav-browse-title">Sibling</a>'
+
' </li>'
+
' </li>'
+
...
@@ -79,7 +79,7 @@
...
@@ -79,7 +79,7 @@
' <li'
+
' <li'
+
' class="forum-nav-browse-menu-item"'
+
' class="forum-nav-browse-menu-item"'
+
' data-discussion-id="other"'
+
' data-discussion-id="other"'
+
' data-
cohort
ed="true"'
+
' data-
divid
ed="true"'
+
' >'
+
' >'
+
' <a href="#" class="forum-nav-browse-title">Other Category</a>'
+
' <a href="#" class="forum-nav-browse-title">Other Category</a>'
+
' </li>'
+
' </li>'
+
...
@@ -95,11 +95,11 @@
...
@@ -95,11 +95,11 @@
' <option value="flagged">Flagged</option>'
+
' <option value="flagged">Flagged</option>'
+
' </select>'
+
' </select>'
+
' </label>'
+
' </label>'
+
' <% if (is
Cohort
ed && isPrivilegedUser) { %>'
+
' <% if (is
DiscussionDivisionEnabl
ed && isPrivilegedUser) { %>'
+
' <label class="forum-nav-filter-cohort">'
+
' <label class="forum-nav-filter-cohort">'
+
' <span class="sr">
Cohort
:</span>'
+
' <span class="sr">
Group
:</span>'
+
' <select class="forum-nav-filter-cohort-control">'
+
' <select class="forum-nav-filter-cohort-control">'
+
' <option value="">in all
cohort
s</option>'
+
' <option value="">in all
group
s</option>'
+
' <option value="1">Cohort1</option>'
+
' <option value="1">Cohort1</option>'
+
' <option value="2">Cohort2</option>'
+
' <option value="2">Cohort2</option>'
+
' </select>'
+
' </select>'
+
...
@@ -164,7 +164,7 @@
...
@@ -164,7 +164,7 @@
collection
:
this
.
discussion
,
collection
:
this
.
discussion
,
el
:
$
(
'#fixture-element'
),
el
:
$
(
'#fixture-element'
),
courseSettings
:
new
DiscussionCourseSettings
({
courseSettings
:
new
DiscussionCourseSettings
({
is_
cohort
ed
:
true
is_
discussion_division_enabl
ed
:
true
})
})
});
});
return
this
.
view
.
render
();
return
this
.
view
.
render
();
...
@@ -199,7 +199,7 @@
...
@@ -199,7 +199,7 @@
collection
:
discussion
,
collection
:
discussion
,
showThreadPreview
:
true
,
showThreadPreview
:
true
,
courseSettings
:
new
DiscussionCourseSettings
({
courseSettings
:
new
DiscussionCourseSettings
({
is_
cohort
ed
:
true
is_
discussion_division_enabl
ed
:
true
})
})
},
},
props
props
...
@@ -233,7 +233,7 @@
...
@@ -233,7 +233,7 @@
});
});
});
});
describe
(
'
cohort
selector'
,
function
()
{
describe
(
'
group
selector'
,
function
()
{
it
(
'should not be visible to students'
,
function
()
{
it
(
'should not be visible to students'
,
function
()
{
return
expect
(
this
.
view
.
$
(
'.forum-nav-filter-cohort-control:visible'
)).
not
.
toExist
();
return
expect
(
this
.
view
.
$
(
'.forum-nav-filter-cohort-control:visible'
)).
not
.
toExist
();
});
});
...
...
common/static/common/js/spec/discussion/view/new_post_view_spec.js
View file @
8951ac8c
...
@@ -31,7 +31,7 @@
...
@@ -31,7 +31,7 @@
return
expect
(
group_disabled
).
toEqual
(
true
);
return
expect
(
group_disabled
).
toEqual
(
true
);
}
}
};
};
describe
(
'
cohort
selector'
,
function
()
{
describe
(
'
group
selector'
,
function
()
{
beforeEach
(
function
()
{
beforeEach
(
function
()
{
this
.
course_settings
=
new
DiscussionCourseSettings
({
this
.
course_settings
=
new
DiscussionCourseSettings
({
category_map
:
{
category_map
:
{
...
@@ -53,8 +53,8 @@
...
@@ -53,8 +53,8 @@
},
},
allow_anonymous
:
false
,
allow_anonymous
:
false
,
allow_anonymous_to_peers
:
false
,
allow_anonymous_to_peers
:
false
,
is_
cohort
ed
:
true
,
is_
discussion_division_enabl
ed
:
true
,
cohort
s
:
[
group
s
:
[
{
{
id
:
1
,
id
:
1
,
name
:
'Cohort1'
name
:
'Cohort1'
...
@@ -75,15 +75,15 @@
...
@@ -75,15 +75,15 @@
it
(
'is not visible to students'
,
function
()
{
it
(
'is not visible to students'
,
function
()
{
return
checkVisibility
(
this
.
view
,
false
,
false
,
true
);
return
checkVisibility
(
this
.
view
,
false
,
false
,
true
);
});
});
it
(
'allows TAs to see the
cohort selector when the topic is cohort
ed'
,
function
()
{
it
(
'allows TAs to see the
group selector when the topic is divid
ed'
,
function
()
{
DiscussionSpecHelper
.
makeTA
();
DiscussionSpecHelper
.
makeTA
();
return
checkVisibility
(
this
.
view
,
true
,
false
,
true
);
return
checkVisibility
(
this
.
view
,
true
,
false
,
true
);
});
});
it
(
'allows moderators to see the
cohort selector when the topic is cohort
ed'
,
function
()
{
it
(
'allows moderators to see the
group selector when the topic is divid
ed'
,
function
()
{
DiscussionSpecHelper
.
makeModerator
();
DiscussionSpecHelper
.
makeModerator
();
return
checkVisibility
(
this
.
view
,
true
,
false
,
true
);
return
checkVisibility
(
this
.
view
,
true
,
false
,
true
);
});
});
it
(
'only enables the
cohort
selector when applicable'
,
function
()
{
it
(
'only enables the
group
selector when applicable'
,
function
()
{
DiscussionSpecHelper
.
makeModerator
();
DiscussionSpecHelper
.
makeModerator
();
checkVisibility
(
this
.
view
,
true
,
false
,
true
);
checkVisibility
(
this
.
view
,
true
,
false
,
true
);
...
@@ -95,7 +95,7 @@
...
@@ -95,7 +95,7 @@
$
(
'.post-topic'
).
trigger
(
'change'
);
$
(
'.post-topic'
).
trigger
(
'change'
);
return
checkVisibility
(
this
.
view
,
true
,
false
,
false
);
return
checkVisibility
(
this
.
view
,
true
,
false
,
false
);
});
});
it
(
'allows the user to make a
cohort
selection'
,
function
()
{
it
(
'allows the user to make a
group
selection'
,
function
()
{
var
expectedGroupId
,
var
expectedGroupId
,
self
=
this
;
self
=
this
;
DiscussionSpecHelper
.
makeModerator
();
DiscussionSpecHelper
.
makeModerator
();
...
@@ -116,23 +116,23 @@
...
@@ -116,23 +116,23 @@
});
});
});
});
});
});
describe
(
'always
cohort
inline discussions '
,
function
()
{
describe
(
'always
divide
inline discussions '
,
function
()
{
beforeEach
(
function
()
{
beforeEach
(
function
()
{
this
.
course_settings
=
new
DiscussionCourseSettings
({
this
.
course_settings
=
new
DiscussionCourseSettings
({
'category_map'
:
{
category_map
:
{
'children'
:
[],
children
:
[],
'entries'
:
{}
entries
:
{}
},
},
'allow_anonymous'
:
false
,
allow_anonymous
:
false
,
'allow_anonymous_to_peers'
:
false
,
allow_anonymous_to_peers
:
false
,
'is_cohorted'
:
true
,
is_discussion_division_enabled
:
true
,
'cohorts'
:
[
groups
:
[
{
{
'id'
:
1
,
id
:
1
,
'name'
:
'Cohort1'
name
:
'Cohort1'
},
{
},
{
'id'
:
2
,
id
:
2
,
'name'
:
'Cohort2'
name
:
'Cohort2'
}
}
]
]
});
});
...
@@ -143,12 +143,12 @@
...
@@ -143,12 +143,12 @@
mode
:
'tab'
mode
:
'tab'
});
});
});
});
it
(
'disables the
cohort
menu if it is set false'
,
function
()
{
it
(
'disables the
group
menu if it is set false'
,
function
()
{
DiscussionSpecHelper
.
makeModerator
();
DiscussionSpecHelper
.
makeModerator
();
this
.
view
.
is_commentable_divided
=
false
;
this
.
view
.
is_commentable_divided
=
false
;
return
checkVisibility
(
this
.
view
,
true
,
true
,
true
);
return
checkVisibility
(
this
.
view
,
true
,
true
,
true
);
});
});
it
(
'enables the
cohort
menu if it is set true'
,
function
()
{
it
(
'enables the
group
menu if it is set true'
,
function
()
{
DiscussionSpecHelper
.
makeModerator
();
DiscussionSpecHelper
.
makeModerator
();
this
.
view
.
is_commentable_divided
=
true
;
this
.
view
.
is_commentable_divided
=
true
;
return
checkVisibility
(
this
.
view
,
true
,
false
,
true
);
return
checkVisibility
(
this
.
view
,
true
,
false
,
true
);
...
...
common/static/common/js/spec_helpers/discussion_spec_helper.js
View file @
8951ac8c
...
@@ -67,7 +67,7 @@
...
@@ -67,7 +67,7 @@
}
}
}
}
},
},
is_
cohort
ed
:
true
,
is_
discussion_division_enabl
ed
:
true
,
allow_anonymous
:
false
,
allow_anonymous
:
false
,
allow_anonymous_to_peers
:
false
allow_anonymous_to_peers
:
false
},
},
...
@@ -170,7 +170,7 @@
...
@@ -170,7 +170,7 @@
' <li'
+
' <li'
+
' class="forum-nav-browse-menu-item"'
+
' class="forum-nav-browse-menu-item"'
+
' data-discussion-id="child"'
+
' data-discussion-id="child"'
+
' data-
cohort
ed="false"'
+
' data-
divid
ed="false"'
+
' >'
+
' >'
+
' <a href="#" class="forum-nav-browse-title">Child</a>'
+
' <a href="#" class="forum-nav-browse-title">Child</a>'
+
' </li>'
+
' </li>'
+
...
@@ -178,7 +178,7 @@
...
@@ -178,7 +178,7 @@
' <li'
+
' <li'
+
' class="forum-nav-browse-menu-item"'
+
' class="forum-nav-browse-menu-item"'
+
' data-discussion-id="sibling"'
+
' data-discussion-id="sibling"'
+
' data-
cohort
ed="false"'
+
' data-
divid
ed="false"'
+
' >'
+
' >'
+
' <a href="#" class="forum-nav-browse-title">Sibling</a>'
+
' <a href="#" class="forum-nav-browse-title">Sibling</a>'
+
' </li>'
+
' </li>'
+
...
@@ -187,7 +187,7 @@
...
@@ -187,7 +187,7 @@
' <li'
+
' <li'
+
' class="forum-nav-browse-menu-item"'
+
' class="forum-nav-browse-menu-item"'
+
' data-discussion-id="other"'
+
' data-discussion-id="other"'
+
' data-
cohort
ed="true"'
+
' data-
divid
ed="true"'
+
' >'
+
' >'
+
' <a href="#" class="forum-nav-browse-title">Other Category</a>'
+
' <a href="#" class="forum-nav-browse-title">Other Category</a>'
+
' </li>'
+
' </li>'
+
...
@@ -203,11 +203,11 @@
...
@@ -203,11 +203,11 @@
' <option value="flagged">Flagged</option>'
+
' <option value="flagged">Flagged</option>'
+
' </select>'
+
' </select>'
+
' </label>'
+
' </label>'
+
' <% if (is
Cohort
ed && isPrivilegedUser) { %>'
+
' <% if (is
DiscussionDivisionEnabl
ed && isPrivilegedUser) { %>'
+
' <label class="forum-nav-filter-cohort">'
+
' <label class="forum-nav-filter-cohort">'
+
' <span class="sr">
Cohort
:</span>'
+
' <span class="sr">
Group
:</span>'
+
' <select class="forum-nav-filter-cohort-control">'
+
' <select class="forum-nav-filter-cohort-control">'
+
' <option value="">in all
cohort
s</option>'
+
' <option value="">in all
group
s</option>'
+
' <option value="1">Cohort1</option>'
+
' <option value="1">Cohort1</option>'
+
' <option value="2">Cohort2</option>'
+
' <option value="2">Cohort2</option>'
+
' </select>'
+
' </select>'
+
...
...
common/static/common/templates/discussion/new-post-menu-entry.underscore
View file @
8951ac8c
<option class="topic-title" data-discussion-id="<%- id %>" data-
cohort
ed="<%- is_divided %>"><%- text %></option>
<option class="topic-title" data-discussion-id="<%- id %>" data-
divid
ed="<%- is_divided %>"><%- text %></option>
common/static/common/templates/discussion/new-post.underscore
View file @
8951ac8c
...
@@ -9,7 +9,7 @@
...
@@ -9,7 +9,7 @@
<% } %>
<% } %>
<ul class="post-errors" style="display: none"></ul>
<ul class="post-errors" style="display: none"></ul>
<div class="forum-new-post-form-wrapper"></div>
<div class="forum-new-post-form-wrapper"></div>
<% if (
cohort
_options) { %>
<% if (
group
_options) { %>
<div class="post-field group-selector-wrapper <% if (!is_commentable_divided) { print('disabled'); } %>">
<div class="post-field group-selector-wrapper <% if (!is_commentable_divided) { print('disabled'); } %>">
<label class="field-label">
<label class="field-label">
<span class="field-label-text">
<span class="field-label-text">
...
@@ -17,12 +17,12 @@
...
@@ -17,12 +17,12 @@
<%- gettext("Visible to") %>
<%- gettext("Visible to") %>
</span>
</span>
<div class="field-help" id="field_help_visible_to">
<div class="field-help" id="field_help_visible_to">
<%- gettext("Discussion admins, moderators, and TAs can make their posts visible to all students or specify a single
cohort
.") %>
<%- gettext("Discussion admins, moderators, and TAs can make their posts visible to all students or specify a single
group
.") %>
</div>
</div>
<div class="field-input">
<div class="field-input">
<select aria-describedby="field_help_visible_to" class="post-topic field-input js-group-select" name="group_id" <% if (!is_commentable_divided) { print("disabled"); } %>>
<select aria-describedby="field_help_visible_to" class="post-topic field-input js-group-select" name="group_id" <% if (!is_commentable_divided) { print("disabled"); } %>>
<option value=""><%- gettext("All Groups") %></option>
<option value=""><%- gettext("All Groups") %></option>
<% _.each(
cohort
_options, function(opt) { %>
<% _.each(
group
_options, function(opt) { %>
<option value="<%- opt.value %>" <% if (opt.selected) { print("selected"); } %>><%- opt.text %></option>
<option value="<%- opt.value %>" <% if (opt.selected) { print("selected"); } %>><%- opt.text %></option>
<% }); %>
<% }); %>
</select>
</select>
...
...
lms/djangoapps/discussion/static/discussion/js/spec/discussion_board_factory_spec.js
View file @
8951ac8c
...
@@ -41,10 +41,10 @@ define(
...
@@ -41,10 +41,10 @@ define(
thread_pages
:
[],
thread_pages
:
[],
contentInfo
:
null
,
contentInfo
:
null
,
courseSettings
:
{
courseSettings
:
{
is_
cohort
ed
:
false
,
is_
discussion_division_enabl
ed
:
false
,
allow_anonymous
:
false
,
allow_anonymous
:
false
,
allow_anonymous_to_peers
:
false
,
allow_anonymous_to_peers
:
false
,
cohort
s
:
[],
group
s
:
[],
category_map
:
{}
category_map
:
{}
}
}
});
});
...
...
lms/djangoapps/discussion/tests/test_views.py
View file @
8951ac8c
...
@@ -125,6 +125,7 @@ def make_mock_thread_data(
...
@@ -125,6 +125,7 @@ def make_mock_thread_data(
group_id
=
None
,
group_id
=
None
,
group_name
=
None
,
group_name
=
None
,
commentable_id
=
None
,
commentable_id
=
None
,
is_commentable_divided
=
None
,
):
):
data_commentable_id
=
(
data_commentable_id
=
(
commentable_id
or
course
.
discussion_topics
.
get
(
'General'
,
{})
.
get
(
'id'
)
or
"dummy_commentable_id"
commentable_id
or
course
.
discussion_topics
.
get
(
'General'
,
{})
.
get
(
'id'
)
or
"dummy_commentable_id"
...
@@ -145,6 +146,8 @@ def make_mock_thread_data(
...
@@ -145,6 +146,8 @@ def make_mock_thread_data(
}
}
if
group_id
is
not
None
:
if
group_id
is
not
None
:
thread_data
[
'group_name'
]
=
group_name
thread_data
[
'group_name'
]
=
group_name
if
is_commentable_divided
is
not
None
:
thread_data
[
'is_commentable_divided'
]
=
is_commentable_divided
if
num_children
is
not
None
:
if
num_children
is
not
None
:
thread_data
[
"children"
]
=
[{
thread_data
[
"children"
]
=
[{
"id"
:
"dummy_comment_id_{}"
.
format
(
i
),
"id"
:
"dummy_comment_id_{}"
.
format
(
i
),
...
@@ -429,7 +432,10 @@ class SingleCohortedThreadTestCase(CohortedTestCase):
...
@@ -429,7 +432,10 @@ class SingleCohortedThreadTestCase(CohortedTestCase):
self
.
mock_text
=
"dummy content"
self
.
mock_text
=
"dummy content"
self
.
mock_thread_id
=
"test_thread_id"
self
.
mock_thread_id
=
"test_thread_id"
mock_request
.
side_effect
=
make_mock_request_impl
(
mock_request
.
side_effect
=
make_mock_request_impl
(
course
=
self
.
course
,
text
=
self
.
mock_text
,
thread_id
=
self
.
mock_thread_id
,
group_id
=
self
.
student_cohort
.
id
course
=
self
.
course
,
text
=
self
.
mock_text
,
thread_id
=
self
.
mock_thread_id
,
group_id
=
self
.
student_cohort
.
id
,
commentable_id
=
"cohorted_topic"
,
)
)
def
test_ajax
(
self
,
mock_request
):
def
test_ajax
(
self
,
mock_request
):
...
@@ -453,11 +459,13 @@ class SingleCohortedThreadTestCase(CohortedTestCase):
...
@@ -453,11 +459,13 @@ class SingleCohortedThreadTestCase(CohortedTestCase):
response_data
[
"content"
],
response_data
[
"content"
],
make_mock_thread_data
(
make_mock_thread_data
(
course
=
self
.
course
,
course
=
self
.
course
,
commentable_id
=
"cohorted_topic"
,
text
=
self
.
mock_text
,
text
=
self
.
mock_text
,
thread_id
=
self
.
mock_thread_id
,
thread_id
=
self
.
mock_thread_id
,
num_children
=
1
,
num_children
=
1
,
group_id
=
self
.
student_cohort
.
id
,
group_id
=
self
.
student_cohort
.
id
,
group_name
=
self
.
student_cohort
.
name
group_name
=
self
.
student_cohort
.
name
,
is_commentable_divided
=
True
,
)
)
)
)
...
...
lms/djangoapps/discussion/views.py
View file @
8951ac8c
...
@@ -30,17 +30,14 @@ from web_fragments.fragment import Fragment
...
@@ -30,17 +30,14 @@ from web_fragments.fragment import Fragment
from
courseware.courses
import
get_course_with_access
from
courseware.courses
import
get_course_with_access
from
courseware.views.views
import
CourseTabView
from
courseware.views.views
import
CourseTabView
from
openedx.core.djangoapps.course_groups.cohorts
import
(
is_course_cohorted
,
get_course_cohorts
,
)
from
openedx.core.djangoapps.plugin_api.views
import
EdxFragmentView
from
openedx.core.djangoapps.plugin_api.views
import
EdxFragmentView
from
courseware.access
import
has_access
from
courseware.access
import
has_access
from
student.models
import
CourseEnrollment
from
student.models
import
CourseEnrollment
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
from
django_comment_common.utils
import
ThreadContext
from
django_comment_common.utils
import
ThreadContext
,
get_course_discussion_settings
from
django_comment_client.permissions
import
has_permission
,
get_team
from
django_comment_client.permissions
import
has_permission
,
get_team
from
django_comment_client.utils
import
(
from
django_comment_client.utils
import
(
merge_dict
,
merge_dict
,
...
@@ -48,6 +45,8 @@ from django_comment_client.utils import (
...
@@ -48,6 +45,8 @@ from django_comment_client.utils import (
strip_none
,
strip_none
,
add_courseware_context
,
add_courseware_context
,
get_group_id_for_comments_service
,
get_group_id_for_comments_service
,
course_discussion_division_enabled
,
get_group_names_by_id
,
is_commentable_divided
,
is_commentable_divided
,
get_group_id_for_user
,
get_group_id_for_user
,
)
)
...
@@ -83,11 +82,15 @@ def make_course_settings(course, user):
...
@@ -83,11 +82,15 @@ def make_course_settings(course, user):
Generate a JSON-serializable model for course settings, which will be used to initialize a
Generate a JSON-serializable model for course settings, which will be used to initialize a
DiscussionCourseSettings object on the client.
DiscussionCourseSettings object on the client.
"""
"""
course_discussion_settings
=
get_course_discussion_settings
(
course
.
id
)
group_names_by_id
=
get_group_names_by_id
(
course_discussion_settings
)
return
{
return
{
'is_
cohorted'
:
is_course_cohorted
(
course
.
id
),
'is_
discussion_division_enabled'
:
course_discussion_division_enabled
(
course_discussion_settings
),
'allow_anonymous'
:
course
.
allow_anonymous
,
'allow_anonymous'
:
course
.
allow_anonymous
,
'allow_anonymous_to_peers'
:
course
.
allow_anonymous_to_peers
,
'allow_anonymous_to_peers'
:
course
.
allow_anonymous_to_peers
,
'cohorts'
:
[{
"id"
:
str
(
g
.
id
),
"name"
:
g
.
name
}
for
g
in
get_course_cohorts
(
course
)],
'groups'
:
[
{
"id"
:
str
(
group_id
),
"name"
:
group_name
}
for
group_id
,
group_name
in
group_names_by_id
.
iteritems
()
],
'category_map'
:
utils
.
get_discussion_category_map
(
course
,
user
)
'category_map'
:
utils
.
get_discussion_category_map
(
course
,
user
)
}
}
...
@@ -346,10 +349,11 @@ def _find_thread(request, course, discussion_id, thread_id):
...
@@ -346,10 +349,11 @@ def _find_thread(request, course, discussion_id, thread_id):
if
thread_context
==
"course"
and
not
utils
.
discussion_category_id_access
(
course
,
request
.
user
,
discussion_id
):
if
thread_context
==
"course"
and
not
utils
.
discussion_category_id_access
(
course
,
request
.
user
,
discussion_id
):
return
None
return
None
# verify that the thread belongs to the requesting student's
cohort
# verify that the thread belongs to the requesting student's
group
is_moderator
=
has_permission
(
request
.
user
,
"see_all_cohorts"
,
course
.
id
)
is_moderator
=
has_permission
(
request
.
user
,
"see_all_cohorts"
,
course
.
id
)
if
is_commentable_divided
(
course
.
id
,
discussion_id
)
and
not
is_moderator
:
course_discussion_settings
=
get_course_discussion_settings
(
course
.
id
)
user_group_id
=
get_group_id_for_user
(
request
.
user
,
course
.
id
)
if
is_commentable_divided
(
course
.
id
,
discussion_id
,
course_discussion_settings
)
and
not
is_moderator
:
user_group_id
=
get_group_id_for_user
(
request
.
user
,
course_discussion_settings
)
if
getattr
(
thread
,
"group_id"
,
None
)
is
not
None
and
user_group_id
!=
thread
.
group_id
:
if
getattr
(
thread
,
"group_id"
,
None
)
is
not
None
and
user_group_id
!=
thread
.
group_id
:
return
None
return
None
...
@@ -424,7 +428,8 @@ def _create_discussion_board_context(request, course_key, discussion_id=None, th
...
@@ -424,7 +428,8 @@ def _create_discussion_board_context(request, course_key, discussion_id=None, th
add_courseware_context
(
threads
,
course
,
user
)
add_courseware_context
(
threads
,
course
,
user
)
with
newrelic_function_trace
(
"get_cohort_info"
):
with
newrelic_function_trace
(
"get_cohort_info"
):
user_group_id
=
get_group_id_for_user
(
user
,
course_key
)
course_discussion_settings
=
get_course_discussion_settings
(
course_key
)
user_group_id
=
get_group_id_for_user
(
user
,
course_discussion_settings
)
context
.
update
({
context
.
update
({
'root_url'
:
root_url
,
'root_url'
:
root_url
,
...
@@ -434,12 +439,12 @@ def _create_discussion_board_context(request, course_key, discussion_id=None, th
...
@@ -434,12 +439,12 @@ def _create_discussion_board_context(request, course_key, discussion_id=None, th
'thread_pages'
:
thread_pages
,
'thread_pages'
:
thread_pages
,
'annotated_content_info'
:
annotated_content_info
,
'annotated_content_info'
:
annotated_content_info
,
'is_moderator'
:
has_permission
(
user
,
"see_all_cohorts"
,
course_key
),
'is_moderator'
:
has_permission
(
user
,
"see_all_cohorts"
,
course_key
),
'
cohorts'
:
course_settings
[
"cohort
s"
],
# still needed to render _thread_list_template
'
groups'
:
course_settings
[
"group
s"
],
# still needed to render _thread_list_template
'user_group_id'
:
user_group_id
,
# read from container in NewPostView
'user_group_id'
:
user_group_id
,
# read from container in NewPostView
'sort_preference'
:
cc_user
.
default_sort_key
,
'sort_preference'
:
cc_user
.
default_sort_key
,
'category_map'
:
course_settings
[
"category_map"
],
'category_map'
:
course_settings
[
"category_map"
],
'course_settings'
:
course_settings
,
'course_settings'
:
course_settings
,
'is_commentable_divided'
:
is_commentable_divided
(
course_key
,
discussion_id
)
'is_commentable_divided'
:
is_commentable_divided
(
course_key
,
discussion_id
,
course_discussion_settings
)
})
})
return
context
return
context
...
@@ -501,7 +506,8 @@ def user_profile(request, course_key, user_id):
...
@@ -501,7 +506,8 @@ def user_profile(request, course_key, user_id):
)
.
order_by
(
"name"
)
.
values_list
(
"name"
,
flat
=
True
)
.
distinct
()
)
.
order_by
(
"name"
)
.
values_list
(
"name"
,
flat
=
True
)
.
distinct
()
with
newrelic_function_trace
(
"get_cohort_info"
):
with
newrelic_function_trace
(
"get_cohort_info"
):
user_group_id
=
get_group_id_for_user
(
request
.
user
,
course_key
)
course_discussion_settings
=
get_course_discussion_settings
(
course_key
)
user_group_id
=
get_group_id_for_user
(
request
.
user
,
course_discussion_settings
)
context
=
_create_base_discussion_view_context
(
request
,
course_key
)
context
=
_create_base_discussion_view_context
(
request
,
course_key
)
context
.
update
({
context
.
update
({
...
...
lms/djangoapps/discussion_api/api.py
View file @
8951ac8c
...
@@ -43,6 +43,8 @@ from django_comment_common.signals import (
...
@@ -43,6 +43,8 @@ from django_comment_common.signals import (
comment_voted
,
comment_voted
,
comment_deleted
,
comment_deleted
,
)
)
from
django_comment_common.utils
import
get_course_discussion_settings
from
django_comment_client.utils
import
get_accessible_discussion_xblocks
,
is_commentable_divided
,
get_group_id_for_user
from
django_comment_client.utils
import
get_accessible_discussion_xblocks
,
is_commentable_divided
,
get_group_id_for_user
from
lms.djangoapps.discussion_api.pagination
import
DiscussionAPIPagination
from
lms.djangoapps.discussion_api.pagination
import
DiscussionAPIPagination
from
lms.lib.comment_client.comment
import
Comment
from
lms.lib.comment_client.comment
import
Comment
...
@@ -109,12 +111,13 @@ def _get_thread_and_context(request, thread_id, retrieve_kwargs=None):
...
@@ -109,12 +111,13 @@ def _get_thread_and_context(request, thread_id, retrieve_kwargs=None):
course_key
=
CourseKey
.
from_string
(
cc_thread
[
"course_id"
])
course_key
=
CourseKey
.
from_string
(
cc_thread
[
"course_id"
])
course
=
_get_course
(
course_key
,
request
.
user
)
course
=
_get_course
(
course_key
,
request
.
user
)
context
=
get_context
(
course
,
request
,
cc_thread
)
context
=
get_context
(
course
,
request
,
cc_thread
)
course_discussion_settings
=
get_course_discussion_settings
(
course_key
)
if
(
if
(
not
context
[
"is_requester_privileged"
]
and
not
context
[
"is_requester_privileged"
]
and
cc_thread
[
"group_id"
]
and
cc_thread
[
"group_id"
]
and
is_commentable_divided
(
course
.
id
,
cc_thread
[
"commentable_id"
])
is_commentable_divided
(
course
.
id
,
cc_thread
[
"commentable_id"
]
,
course_discussion_settings
)
):
):
requester_group_id
=
get_group_id_for_user
(
request
.
user
,
course
.
id
)
requester_group_id
=
get_group_id_for_user
(
request
.
user
,
course
_discussion_settings
)
if
requester_group_id
is
not
None
and
cc_thread
[
"group_id"
]
!=
requester_group_id
:
if
requester_group_id
is
not
None
and
cc_thread
[
"group_id"
]
!=
requester_group_id
:
raise
ThreadNotFoundError
(
"Thread not found."
)
raise
ThreadNotFoundError
(
"Thread not found."
)
return
cc_thread
,
context
return
cc_thread
,
context
...
@@ -546,7 +549,7 @@ def get_thread_list(
...
@@ -546,7 +549,7 @@ def get_thread_list(
"user_id"
:
unicode
(
request
.
user
.
id
),
"user_id"
:
unicode
(
request
.
user
.
id
),
"group_id"
:
(
"group_id"
:
(
None
if
context
[
"is_requester_privileged"
]
else
None
if
context
[
"is_requester_privileged"
]
else
get_group_id_for_user
(
request
.
user
,
course
.
id
)
get_group_id_for_user
(
request
.
user
,
get_course_discussion_settings
(
course
.
id
)
)
),
),
"page"
:
page
,
"page"
:
page
,
"per_page"
:
page_size
,
"per_page"
:
page_size
,
...
@@ -828,12 +831,13 @@ def create_thread(request, thread_data):
...
@@ -828,12 +831,13 @@ def create_thread(request, thread_data):
context
=
get_context
(
course
,
request
)
context
=
get_context
(
course
,
request
)
_check_initializable_thread_fields
(
thread_data
,
context
)
_check_initializable_thread_fields
(
thread_data
,
context
)
discussion_settings
=
get_course_discussion_settings
(
course_key
)
if
(
if
(
"group_id"
not
in
thread_data
and
"group_id"
not
in
thread_data
and
is_commentable_divided
(
course_key
,
thread_data
.
get
(
"topic_id"
))
is_commentable_divided
(
course_key
,
thread_data
.
get
(
"topic_id"
)
,
discussion_settings
)
):
):
thread_data
=
thread_data
.
copy
()
thread_data
=
thread_data
.
copy
()
thread_data
[
"group_id"
]
=
get_group_id_for_user
(
user
,
course_key
)
thread_data
[
"group_id"
]
=
get_group_id_for_user
(
user
,
discussion_settings
)
serializer
=
ThreadSerializer
(
data
=
thread_data
,
context
=
context
)
serializer
=
ThreadSerializer
(
data
=
thread_data
,
context
=
context
)
actions_form
=
ThreadActionsForm
(
thread_data
)
actions_form
=
ThreadActionsForm
(
thread_data
)
if
not
(
serializer
.
is_valid
()
and
actions_form
.
is_valid
()):
if
not
(
serializer
.
is_valid
()
and
actions_form
.
is_valid
()):
...
...
lms/djangoapps/discussion_api/permissions.py
View file @
8951ac8c
...
@@ -77,7 +77,7 @@ def get_editable_fields(cc_content, context):
...
@@ -77,7 +77,7 @@ def get_editable_fields(cc_content, context):
ret
|=
{
"following"
,
"read"
}
ret
|=
{
"following"
,
"read"
}
if
_is_author_or_privileged
(
cc_content
,
context
):
if
_is_author_or_privileged
(
cc_content
,
context
):
ret
|=
{
"topic_id"
,
"type"
,
"title"
}
ret
|=
{
"topic_id"
,
"type"
,
"title"
}
if
context
[
"is_requester_privileged"
]
and
context
[
"
course"
]
.
is_cohorted
:
if
context
[
"is_requester_privileged"
]
and
context
[
"
discussion_division_enabled"
]
:
ret
|=
{
"group_id"
}
ret
|=
{
"group_id"
}
# Comment fields
# Comment fields
...
...
lms/djangoapps/discussion_api/serializers.py
View file @
8951ac8c
...
@@ -17,6 +17,7 @@ from discussion_api.permissions import (
...
@@ -17,6 +17,7 @@ from discussion_api.permissions import (
)
)
from
discussion_api.render
import
render_body
from
discussion_api.render
import
render_body
from
django_comment_client.utils
import
is_comment_too_deep
from
django_comment_client.utils
import
is_comment_too_deep
from
django_comment_common.utils
import
get_course_discussion_settings
from
django_comment_common.models
import
(
from
django_comment_common.models
import
(
FORUM_ROLE_ADMINISTRATOR
,
FORUM_ROLE_ADMINISTRATOR
,
FORUM_ROLE_COMMUNITY_TA
,
FORUM_ROLE_COMMUNITY_TA
,
...
@@ -27,7 +28,7 @@ from lms.lib.comment_client.comment import Comment
...
@@ -27,7 +28,7 @@ from lms.lib.comment_client.comment import Comment
from
lms.lib.comment_client.thread
import
Thread
from
lms.lib.comment_client.thread
import
Thread
from
lms.lib.comment_client.user
import
User
as
CommentClientUser
from
lms.lib.comment_client.user
import
User
as
CommentClientUser
from
lms.lib.comment_client.utils
import
CommentClientRequestError
from
lms.lib.comment_client.utils
import
CommentClientRequestError
from
openedx.core.djangoapps.course_groups.cohorts
import
get_cohort_names
from
lms.djangoapps.django_comment_client.utils
import
course_discussion_division_enabled
,
get_group_names_by_id
def
get_context
(
course
,
request
,
thread
=
None
):
def
get_context
(
course
,
request
,
thread
=
None
):
...
@@ -52,12 +53,13 @@ def get_context(course, request, thread=None):
...
@@ -52,12 +53,13 @@ def get_context(course, request, thread=None):
requester
=
request
.
user
requester
=
request
.
user
cc_requester
=
CommentClientUser
.
from_django_user
(
requester
)
.
retrieve
()
cc_requester
=
CommentClientUser
.
from_django_user
(
requester
)
.
retrieve
()
cc_requester
[
"course_id"
]
=
course
.
id
cc_requester
[
"course_id"
]
=
course
.
id
course_discussion_settings
=
get_course_discussion_settings
(
course
.
id
)
return
{
return
{
"course"
:
course
,
"course"
:
course
,
"request"
:
request
,
"request"
:
request
,
"thread"
:
thread
,
"thread"
:
thread
,
# For now, the only groups are cohorts
"discussion_division_enabled"
:
course_discussion_division_enabled
(
course_discussion_settings
),
"group_ids_to_names"
:
get_
cohort_names
(
course
),
"group_ids_to_names"
:
get_
group_names_by_id
(
course_discussion_settings
),
"is_requester_privileged"
:
requester
.
id
in
staff_user_ids
or
requester
.
id
in
ta_user_ids
,
"is_requester_privileged"
:
requester
.
id
in
staff_user_ids
or
requester
.
id
in
ta_user_ids
,
"staff_user_ids"
:
staff_user_ids
,
"staff_user_ids"
:
staff_user_ids
,
"ta_user_ids"
:
ta_user_ids
,
"ta_user_ids"
:
ta_user_ids
,
...
...
lms/djangoapps/discussion_api/tests/test_api.py
View file @
8951ac8c
...
@@ -55,6 +55,7 @@ from openedx.core.djangoapps.course_groups.tests.helpers import CohortFactory
...
@@ -55,6 +55,7 @@ from openedx.core.djangoapps.course_groups.tests.helpers import CohortFactory
from
openedx.core.lib.exceptions
import
CourseNotFoundError
,
PageNotFoundError
from
openedx.core.lib.exceptions
import
CourseNotFoundError
,
PageNotFoundError
from
student.tests.factories
import
CourseEnrollmentFactory
,
UserFactory
from
student.tests.factories
import
CourseEnrollmentFactory
,
UserFactory
from
util.testing
import
UrlResetMixin
from
util.testing
import
UrlResetMixin
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
,
SharedModuleStoreTestCase
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
,
SharedModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
...
@@ -591,6 +592,8 @@ class GetThreadListTest(ForumsEnableMixin, CommentsServiceMockMixin, UrlResetMix
...
@@ -591,6 +592,8 @@ class GetThreadListTest(ForumsEnableMixin, CommentsServiceMockMixin, UrlResetMix
self
.
request
.
user
=
self
.
user
self
.
request
.
user
=
self
.
user
CourseEnrollmentFactory
.
create
(
user
=
self
.
user
,
course_id
=
self
.
course
.
id
)
CourseEnrollmentFactory
.
create
(
user
=
self
.
user
,
course_id
=
self
.
course
.
id
)
self
.
author
=
UserFactory
.
create
()
self
.
author
=
UserFactory
.
create
()
self
.
course
.
cohort_config
=
{
"cohorted"
:
False
}
modulestore
()
.
update_item
(
self
.
course
,
ModuleStoreEnum
.
UserID
.
test
)
self
.
cohort
=
CohortFactory
.
create
(
course_id
=
self
.
course
.
id
)
self
.
cohort
=
CohortFactory
.
create
(
course_id
=
self
.
course
.
id
)
def
get_thread_list
(
def
get_thread_list
(
...
@@ -662,6 +665,8 @@ class GetThreadListTest(ForumsEnableMixin, CommentsServiceMockMixin, UrlResetMix
...
@@ -662,6 +665,8 @@ class GetThreadListTest(ForumsEnableMixin, CommentsServiceMockMixin, UrlResetMix
})
})
def
test_thread_content
(
self
):
def
test_thread_content
(
self
):
self
.
course
.
cohort_config
=
{
"cohorted"
:
True
}
modulestore
()
.
update_item
(
self
.
course
,
ModuleStoreEnum
.
UserID
.
test
)
source_threads
=
[
source_threads
=
[
make_minimal_cs_thread
({
make_minimal_cs_thread
({
"id"
:
"test_thread_id_0"
,
"id"
:
"test_thread_id_0"
,
...
...
lms/djangoapps/discussion_api/tests/test_permissions.py
View file @
8951ac8c
...
@@ -25,6 +25,7 @@ def _get_context(requester_id, is_requester_privileged, is_cohorted=False, threa
...
@@ -25,6 +25,7 @@ def _get_context(requester_id, is_requester_privileged, is_cohorted=False, threa
"cc_requester"
:
User
(
id
=
requester_id
),
"cc_requester"
:
User
(
id
=
requester_id
),
"is_requester_privileged"
:
is_requester_privileged
,
"is_requester_privileged"
:
is_requester_privileged
,
"course"
:
CourseFactory
(
cohort_config
=
{
"cohorted"
:
is_cohorted
}),
"course"
:
CourseFactory
(
cohort_config
=
{
"cohorted"
:
is_cohorted
}),
"discussion_division_enabled"
:
is_cohorted
,
"thread"
:
thread
,
"thread"
:
thread
,
}
}
...
...
lms/djangoapps/discussion_api/tests/test_serializers.py
View file @
8951ac8c
...
@@ -28,6 +28,8 @@ from lms.lib.comment_client.comment import Comment
...
@@ -28,6 +28,8 @@ from lms.lib.comment_client.comment import Comment
from
lms.lib.comment_client.thread
import
Thread
from
lms.lib.comment_client.thread
import
Thread
from
student.tests.factories
import
UserFactory
from
student.tests.factories
import
UserFactory
from
util.testing
import
UrlResetMixin
from
util.testing
import
UrlResetMixin
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.tests.django_utils
import
SharedModuleStoreTestCase
from
xmodule.modulestore.tests.django_utils
import
SharedModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
openedx.core.djangoapps.course_groups.tests.helpers
import
CohortFactory
from
openedx.core.djangoapps.course_groups.tests.helpers
import
CohortFactory
...
@@ -209,6 +211,8 @@ class ThreadSerializerSerializationTest(SerializerTestMixin, SharedModuleStoreTe
...
@@ -209,6 +211,8 @@ class ThreadSerializerSerializationTest(SerializerTestMixin, SharedModuleStoreTe
self
.
assertEqual
(
serialized
[
"pinned"
],
False
)
self
.
assertEqual
(
serialized
[
"pinned"
],
False
)
def
test_group
(
self
):
def
test_group
(
self
):
self
.
course
.
cohort_config
=
{
"cohorted"
:
True
}
modulestore
()
.
update_item
(
self
.
course
,
ModuleStoreEnum
.
UserID
.
test
)
cohort
=
CohortFactory
.
create
(
course_id
=
self
.
course
.
id
)
cohort
=
CohortFactory
.
create
(
course_id
=
self
.
course
.
id
)
serialized
=
self
.
serialize
(
self
.
make_cs_content
({
"group_id"
:
cohort
.
id
}))
serialized
=
self
.
serialize
(
self
.
make_cs_content
({
"group_id"
:
cohort
.
id
}))
self
.
assertEqual
(
serialized
[
"group_id"
],
cohort
.
id
)
self
.
assertEqual
(
serialized
[
"group_id"
],
cohort
.
id
)
...
...
lms/djangoapps/django_comment_client/tests/group_id.py
View file @
8951ac8c
import
json
import
json
import
re
import
re
from
course_modes.models
import
CourseMode
from
course_modes.tests.factories
import
CourseModeFactory
from
django_comment_common.models
import
CourseDiscussionSettings
from
django_comment_common.utils
import
set_course_discussion_settings
from
lms.djangoapps.teams.tests.factories
import
CourseTeamFactory
from
lms.djangoapps.teams.tests.factories
import
CourseTeamFactory
...
@@ -94,11 +99,22 @@ class CohortedTopicGroupIdTestMixin(GroupIdAssertionMixin):
...
@@ -94,11 +99,22 @@ class CohortedTopicGroupIdTestMixin(GroupIdAssertionMixin):
def
test_cohorted_topic_moderator_with_invalid_group_id
(
self
,
mock_request
):
def
test_cohorted_topic_moderator_with_invalid_group_id
(
self
,
mock_request
):
invalid_id
=
self
.
student_cohort
.
id
+
self
.
moderator_cohort
.
id
invalid_id
=
self
.
student_cohort
.
id
+
self
.
moderator_cohort
.
id
try
:
response
=
self
.
call_view
(
mock_request
,
"cohorted_topic"
,
self
.
moderator
,
invalid_id
)
response
=
self
.
call_view
(
mock_request
,
"cohorted_topic"
,
self
.
moderator
,
invalid_id
)
self
.
assertEqual
(
response
.
status_code
,
500
)
self
.
assertEqual
(
response
.
status_code
,
500
)
except
ValueError
:
pass
# In mock request mode, server errors are not captured
def
test_cohorted_topic_enrollment_track_invalid_group_id
(
self
,
mock_request
):
CourseModeFactory
.
create
(
course_id
=
self
.
course
.
id
,
mode_slug
=
CourseMode
.
AUDIT
)
CourseModeFactory
.
create
(
course_id
=
self
.
course
.
id
,
mode_slug
=
CourseMode
.
VERIFIED
)
set_course_discussion_settings
(
course_key
=
self
.
course
.
id
,
divided_discussions
=
[
'cohorted_topic'
],
division_scheme
=
CourseDiscussionSettings
.
ENROLLMENT_TRACK
,
always_divide_inline_discussions
=
True
,
)
invalid_id
=
-
1000
response
=
self
.
call_view
(
mock_request
,
"cohorted_topic"
,
self
.
moderator
,
invalid_id
)
self
.
assertEqual
(
response
.
status_code
,
500
)
class
NonCohortedTopicGroupIdTestMixin
(
GroupIdAssertionMixin
):
class
NonCohortedTopicGroupIdTestMixin
(
GroupIdAssertionMixin
):
...
...
lms/djangoapps/django_comment_client/tests/test_utils.py
View file @
8951ac8c
...
@@ -17,13 +17,16 @@ from django_comment_client.tests.unicode import UnicodeTestMixin
...
@@ -17,13 +17,16 @@ from django_comment_client.tests.unicode import UnicodeTestMixin
from
django_comment_client.constants
import
TYPE_ENTRY
,
TYPE_SUBCATEGORY
from
django_comment_client.constants
import
TYPE_ENTRY
,
TYPE_SUBCATEGORY
import
django_comment_client.utils
as
utils
import
django_comment_client.utils
as
utils
from
lms.lib.comment_client.utils
import
perform_request
,
CommentClientMaintenanceError
from
lms.lib.comment_client.utils
import
perform_request
,
CommentClientMaintenanceError
from
django_comment_common.models
import
ForumsConfig
from
django_comment_common.models
import
ForumsConfig
,
CourseDiscussionSettings
from
django_comment_common.utils
import
get_course_discussion_settings
,
set_course_discussion_settings
from
course_modes.models
import
CourseMode
from
course_modes.tests.factories
import
CourseModeFactory
from
courseware.tests.factories
import
InstructorFactory
from
courseware.tests.factories
import
InstructorFactory
from
courseware.tabs
import
get_course_tab_list
from
courseware.tabs
import
get_course_tab_list
from
openedx.core.djangoapps.course_groups
import
cohorts
from
openedx.core.djangoapps.course_groups
import
cohorts
from
openedx.core.djangoapps.course_groups.cohorts
import
set_course_cohort
_settings
from
openedx.core.djangoapps.course_groups.cohorts
import
set_course_cohort
ed
from
openedx.core.djangoapps.course_groups.tests.helpers
import
config_course_cohorts
,
topic_name_to_id
from
openedx.core.djangoapps.course_groups.tests.helpers
import
config_course_cohorts
,
topic_name_to_id
,
CohortFactory
from
student.tests.factories
import
UserFactory
,
AdminFactory
,
CourseEnrollmentFactory
from
student.tests.factories
import
UserFactory
,
AdminFactory
,
CourseEnrollmentFactory
from
openedx.core.djangoapps.content.course_structures.models
import
CourseStructure
from
openedx.core.djangoapps.content.course_structures.models
import
CourseStructure
from
openedx.core.djangoapps.util.testing
import
ContentGroupTestCase
from
openedx.core.djangoapps.util.testing
import
ContentGroupTestCase
...
@@ -374,7 +377,6 @@ class CategoryMapTestCase(CategoryMapTestMixin, ModuleStoreTestCase):
...
@@ -374,7 +377,6 @@ class CategoryMapTestCase(CategoryMapTestMixin, ModuleStoreTestCase):
)
)
# Courses get a default discussion topic on creation, so remove it
# Courses get a default discussion topic on creation, so remove it
self
.
course
.
discussion_topics
=
{}
self
.
course
.
discussion_topics
=
{}
self
.
course
.
save
()
self
.
discussion_num
=
0
self
.
discussion_num
=
0
self
.
instructor
=
InstructorFactory
(
course_key
=
self
.
course
.
id
)
self
.
instructor
=
InstructorFactory
(
course_key
=
self
.
course
.
id
)
self
.
maxDiff
=
None
# pylint: disable=invalid-name
self
.
maxDiff
=
None
# pylint: disable=invalid-name
...
@@ -426,34 +428,31 @@ class CategoryMapTestCase(CategoryMapTestMixin, ModuleStoreTestCase):
...
@@ -426,34 +428,31 @@ class CategoryMapTestCase(CategoryMapTestMixin, ModuleStoreTestCase):
check_cohorted_topics
([])
# default (empty) cohort config
check_cohorted_topics
([])
# default (empty) cohort config
set_
course_cohort_settings
(
course_key
=
self
.
course
.
id
,
is_cohorted
=
False
,
cohorted_discussions
=
[]
)
set_
discussion_division_settings
(
self
.
course
.
id
,
enable_cohorts
=
False
)
check_cohorted_topics
([])
check_cohorted_topics
([])
set_
course_cohort_settings
(
course_key
=
self
.
course
.
id
,
is_cohorted
=
True
,
cohorted_discussions
=
[]
)
set_
discussion_division_settings
(
self
.
course
.
id
,
enable_cohorts
=
True
)
check_cohorted_topics
([])
check_cohorted_topics
([])
set_course_cohort_settings
(
set_discussion_division_settings
(
course_key
=
self
.
course
.
id
,
self
.
course
.
id
,
is_cohorted
=
True
,
enable_cohorts
=
True
,
cohorted_discussions
=
[
"Topic_B"
,
"Topic_C"
],
divided_discussions
=
[
"Topic_B"
,
"Topic_C"
]
always_cohort_inline_discussions
=
False
,
)
)
check_cohorted_topics
([
"Topic_B"
,
"Topic_C"
])
check_cohorted_topics
([
"Topic_B"
,
"Topic_C"
])
set_course_cohort_settings
(
set_discussion_division_settings
(
course_key
=
self
.
course
.
id
,
self
.
course
.
id
,
is_cohorted
=
True
,
enable_cohorts
=
True
,
cohorted_discussions
=
[
"Topic_A"
,
"Some_Other_Topic"
],
divided_discussions
=
[
"Topic_A"
,
"Some_Other_Topic"
]
always_cohort_inline_discussions
=
False
,
)
)
check_cohorted_topics
([
"Topic_A"
])
check_cohorted_topics
([
"Topic_A"
])
# unlikely case, but make sure it works.
# unlikely case, but make sure it works.
set_course_cohort_settings
(
set_discussion_division_settings
(
course_key
=
self
.
course
.
id
,
self
.
course
.
id
,
is_cohorted
=
False
,
enable_cohorts
=
False
,
cohorted_discussions
=
[
"Topic_A"
],
divided_discussions
=
[
"Topic_A"
]
always_cohort_inline_discussions
=
False
,
)
)
check_cohorted_topics
([])
check_cohorted_topics
([])
...
@@ -481,7 +480,7 @@ class CategoryMapTestCase(CategoryMapTestMixin, ModuleStoreTestCase):
...
@@ -481,7 +480,7 @@ class CategoryMapTestCase(CategoryMapTestMixin, ModuleStoreTestCase):
def
test_inline_with_always_cohort_inline_discussion_flag
(
self
):
def
test_inline_with_always_cohort_inline_discussion_flag
(
self
):
self
.
create_discussion
(
"Chapter"
,
"Discussion"
)
self
.
create_discussion
(
"Chapter"
,
"Discussion"
)
set_
course_cohort_settings
(
course_key
=
self
.
course
.
id
,
is_cohorted
=
True
,
always_cohort
_inline_discussions
=
True
)
set_
discussion_division_settings
(
self
.
course
.
id
,
enable_cohorts
=
True
,
always_divide
_inline_discussions
=
True
)
self
.
assert_category_map_equals
(
self
.
assert_category_map_equals
(
{
{
...
@@ -505,7 +504,7 @@ class CategoryMapTestCase(CategoryMapTestMixin, ModuleStoreTestCase):
...
@@ -505,7 +504,7 @@ class CategoryMapTestCase(CategoryMapTestMixin, ModuleStoreTestCase):
def
test_inline_without_always_cohort_inline_discussion_flag
(
self
):
def
test_inline_without_always_cohort_inline_discussion_flag
(
self
):
self
.
create_discussion
(
"Chapter"
,
"Discussion"
)
self
.
create_discussion
(
"Chapter"
,
"Discussion"
)
set_
course_cohort_settings
(
course_key
=
self
.
course
.
id
,
is_cohorted
=
True
)
set_
discussion_division_settings
(
self
.
course
.
id
,
enable_cohorts
=
True
)
self
.
assert_category_map_equals
(
self
.
assert_category_map_equals
(
{
{
...
@@ -566,7 +565,7 @@ class CategoryMapTestCase(CategoryMapTestMixin, ModuleStoreTestCase):
...
@@ -566,7 +565,7 @@ class CategoryMapTestCase(CategoryMapTestMixin, ModuleStoreTestCase):
self
.
create_discussion
(
"Chapter 2 / Section 1 / Subsection 2"
,
"Discussion"
)
self
.
create_discussion
(
"Chapter 2 / Section 1 / Subsection 2"
,
"Discussion"
)
self
.
create_discussion
(
"Chapter 3 / Section 1"
,
"Discussion"
)
self
.
create_discussion
(
"Chapter 3 / Section 1"
,
"Discussion"
)
def
check_
cohort
ed
(
is_divided
):
def
check_
divid
ed
(
is_divided
):
self
.
assert_category_map_equals
(
self
.
assert_category_map_equals
(
{
{
...
@@ -652,15 +651,15 @@ class CategoryMapTestCase(CategoryMapTestMixin, ModuleStoreTestCase):
...
@@ -652,15 +651,15 @@ class CategoryMapTestCase(CategoryMapTestMixin, ModuleStoreTestCase):
)
)
# empty / default config
# empty / default config
check_
cohort
ed
(
False
)
check_
divid
ed
(
False
)
# explicitly disabled cohorting
# explicitly disabled cohorting
set_
course_cohort_settings
(
course_key
=
self
.
course
.
id
,
is_cohorted
=
False
)
set_
discussion_division_settings
(
self
.
course
.
id
,
enable_cohorts
=
False
)
check_
cohort
ed
(
False
)
check_
divid
ed
(
False
)
# explicitly enable
d cohorting with inline discusssions also cohort
ed.
# explicitly enable
courses divided by Cohort with inline discusssions also divid
ed.
set_
course_cohort_settings
(
course_key
=
self
.
course
.
id
,
is_cohorted
=
True
,
always_cohort
_inline_discussions
=
True
)
set_
discussion_division_settings
(
self
.
course
.
id
,
enable_cohorts
=
True
,
always_divide
_inline_discussions
=
True
)
check_
cohort
ed
(
True
)
check_
divid
ed
(
True
)
def
test_tree_with_duplicate_targets
(
self
):
def
test_tree_with_duplicate_targets
(
self
):
self
.
create_discussion
(
"Chapter 1"
,
"Discussion A"
)
self
.
create_discussion
(
"Chapter 1"
,
"Discussion A"
)
...
@@ -730,7 +729,6 @@ class CategoryMapTestCase(CategoryMapTestMixin, ModuleStoreTestCase):
...
@@ -730,7 +729,6 @@ class CategoryMapTestCase(CategoryMapTestMixin, ModuleStoreTestCase):
def
test_self_paced_start_date_filter
(
self
):
def
test_self_paced_start_date_filter
(
self
):
self
.
course
.
self_paced
=
True
self
.
course
.
self_paced
=
True
self
.
course
.
save
()
now
=
datetime
.
datetime
.
now
()
now
=
datetime
.
datetime
.
now
()
later
=
datetime
.
datetime
.
max
later
=
datetime
.
datetime
.
max
...
@@ -898,7 +896,6 @@ class CategoryMapTestCase(CategoryMapTestMixin, ModuleStoreTestCase):
...
@@ -898,7 +896,6 @@ class CategoryMapTestCase(CategoryMapTestMixin, ModuleStoreTestCase):
def
test_sort_alpha
(
self
):
def
test_sort_alpha
(
self
):
self
.
course
.
discussion_sort_alpha
=
True
self
.
course
.
discussion_sort_alpha
=
True
self
.
course
.
save
()
self
.
create_discussion
(
"Chapter"
,
"Discussion D"
)
self
.
create_discussion
(
"Chapter"
,
"Discussion D"
)
self
.
create_discussion
(
"Chapter"
,
"Discussion A"
)
self
.
create_discussion
(
"Chapter"
,
"Discussion A"
)
self
.
create_discussion
(
"Chapter"
,
"Discussion E"
)
self
.
create_discussion
(
"Chapter"
,
"Discussion E"
)
...
@@ -1325,7 +1322,7 @@ class IsCommentableDividedTestCase(ModuleStoreTestCase):
...
@@ -1325,7 +1322,7 @@ class IsCommentableDividedTestCase(ModuleStoreTestCase):
course
,
course
,
is_cohorted
=
True
,
is_cohorted
=
True
,
discussion_topics
=
[
"General"
,
"Feedback"
],
discussion_topics
=
[
"General"
,
"Feedback"
],
cohort
ed_discussions
=
[
"Feedback"
]
divid
ed_discussions
=
[
"Feedback"
]
)
)
self
.
assertTrue
(
cohorts
.
is_course_cohorted
(
course
.
id
))
self
.
assertTrue
(
cohorts
.
is_course_cohorted
(
course
.
id
))
...
@@ -1349,7 +1346,7 @@ class IsCommentableDividedTestCase(ModuleStoreTestCase):
...
@@ -1349,7 +1346,7 @@ class IsCommentableDividedTestCase(ModuleStoreTestCase):
course
,
course
,
is_cohorted
=
True
,
is_cohorted
=
True
,
discussion_topics
=
[
"General"
,
"Feedback"
],
discussion_topics
=
[
"General"
,
"Feedback"
],
cohort
ed_discussions
=
[
"Feedback"
,
"random_inline"
]
divid
ed_discussions
=
[
"Feedback"
,
"random_inline"
]
)
)
self
.
assertFalse
(
self
.
assertFalse
(
utils
.
is_commentable_divided
(
course
.
id
,
to_id
(
"random"
)),
utils
.
is_commentable_divided
(
course
.
id
,
to_id
(
"random"
)),
...
@@ -1362,8 +1359,8 @@ class IsCommentableDividedTestCase(ModuleStoreTestCase):
...
@@ -1362,8 +1359,8 @@ class IsCommentableDividedTestCase(ModuleStoreTestCase):
course
,
course
,
is_cohorted
=
True
,
is_cohorted
=
True
,
discussion_topics
=
[
"General"
,
"Feedback"
],
discussion_topics
=
[
"General"
,
"Feedback"
],
cohort
ed_discussions
=
[
"Feedback"
,
"random_inline"
],
divid
ed_discussions
=
[
"Feedback"
,
"random_inline"
],
always_
cohort
_inline_discussions
=
False
always_
divide
_inline_discussions
=
False
)
)
self
.
assertFalse
(
self
.
assertFalse
(
utils
.
is_commentable_divided
(
course
.
id
,
to_id
(
"random"
)),
utils
.
is_commentable_divided
(
course
.
id
,
to_id
(
"random"
)),
...
@@ -1383,7 +1380,7 @@ class IsCommentableDividedTestCase(ModuleStoreTestCase):
...
@@ -1383,7 +1380,7 @@ class IsCommentableDividedTestCase(ModuleStoreTestCase):
course
=
modulestore
()
.
get_course
(
self
.
toy_course_key
)
course
=
modulestore
()
.
get_course
(
self
.
toy_course_key
)
self
.
assertFalse
(
cohorts
.
is_course_cohorted
(
course
.
id
))
self
.
assertFalse
(
cohorts
.
is_course_cohorted
(
course
.
id
))
config_course_cohorts
(
course
,
is_cohorted
=
True
,
always_
cohort
_inline_discussions
=
True
)
config_course_cohorts
(
course
,
is_cohorted
=
True
,
always_
divide
_inline_discussions
=
True
)
team
=
CourseTeamFactory
(
course_id
=
course
.
id
)
team
=
CourseTeamFactory
(
course_id
=
course
.
id
)
# Verify that team discussions are not cohorted, but other discussions are
# Verify that team discussions are not cohorted, but other discussions are
...
@@ -1391,6 +1388,205 @@ class IsCommentableDividedTestCase(ModuleStoreTestCase):
...
@@ -1391,6 +1388,205 @@ class IsCommentableDividedTestCase(ModuleStoreTestCase):
self
.
assertFalse
(
utils
.
is_commentable_divided
(
course
.
id
,
team
.
discussion_topic_id
))
self
.
assertFalse
(
utils
.
is_commentable_divided
(
course
.
id
,
team
.
discussion_topic_id
))
self
.
assertTrue
(
utils
.
is_commentable_divided
(
course
.
id
,
"random"
))
self
.
assertTrue
(
utils
.
is_commentable_divided
(
course
.
id
,
"random"
))
def
test_is_commentable_divided_cohorts
(
self
):
course
=
modulestore
()
.
get_course
(
self
.
toy_course_key
)
set_discussion_division_settings
(
course
.
id
,
enable_cohorts
=
True
,
divided_discussions
=
[],
always_divide_inline_discussions
=
True
,
division_scheme
=
CourseDiscussionSettings
.
NONE
,
)
# Although Cohorts are enabled, discussion division is explicitly disabled.
self
.
assertFalse
(
utils
.
is_commentable_divided
(
course
.
id
,
"random"
))
# Now set the discussion division scheme.
set_discussion_division_settings
(
course
.
id
,
enable_cohorts
=
True
,
divided_discussions
=
[],
always_divide_inline_discussions
=
True
,
division_scheme
=
CourseDiscussionSettings
.
COHORT
,
)
self
.
assertTrue
(
utils
.
is_commentable_divided
(
course
.
id
,
"random"
))
def
test_is_commentable_divided_enrollment_track
(
self
):
course
=
modulestore
()
.
get_course
(
self
.
toy_course_key
)
set_discussion_division_settings
(
course
.
id
,
divided_discussions
=
[],
always_divide_inline_discussions
=
True
,
division_scheme
=
CourseDiscussionSettings
.
ENROLLMENT_TRACK
,
)
# Although division scheme is set to ENROLLMENT_TRACK, divided returns
# False because there is only a single enrollment mode.
self
.
assertFalse
(
utils
.
is_commentable_divided
(
course
.
id
,
"random"
))
# Now create 2 explicit course modes.
CourseModeFactory
.
create
(
course_id
=
course
.
id
,
mode_slug
=
CourseMode
.
AUDIT
)
CourseModeFactory
.
create
(
course_id
=
course
.
id
,
mode_slug
=
CourseMode
.
VERIFIED
)
self
.
assertTrue
(
utils
.
is_commentable_divided
(
course
.
id
,
"random"
))
@attr
(
shard
=
1
)
class
GroupIdForUserTestCase
(
ModuleStoreTestCase
):
""" Test the get_group_id_for_user method. """
def
setUp
(
self
):
super
(
GroupIdForUserTestCase
,
self
)
.
setUp
()
self
.
course
=
CourseFactory
.
create
()
CourseModeFactory
.
create
(
course_id
=
self
.
course
.
id
,
mode_slug
=
CourseMode
.
AUDIT
)
CourseModeFactory
.
create
(
course_id
=
self
.
course
.
id
,
mode_slug
=
CourseMode
.
VERIFIED
)
self
.
test_user
=
UserFactory
.
create
()
CourseEnrollmentFactory
.
create
(
mode
=
CourseMode
.
VERIFIED
,
user
=
self
.
test_user
,
course_id
=
self
.
course
.
id
)
self
.
test_cohort
=
CohortFactory
(
course_id
=
self
.
course
.
id
,
name
=
'Test Cohort'
,
users
=
[
self
.
test_user
]
)
def
test_discussion_division_disabled
(
self
):
course_discussion_settings
=
get_course_discussion_settings
(
self
.
course
.
id
)
self
.
assertEqual
(
CourseDiscussionSettings
.
NONE
,
course_discussion_settings
.
division_scheme
)
self
.
assertIsNone
(
utils
.
get_group_id_for_user
(
self
.
test_user
,
course_discussion_settings
))
def
test_discussion_division_by_cohort
(
self
):
set_discussion_division_settings
(
self
.
course
.
id
,
enable_cohorts
=
True
,
division_scheme
=
CourseDiscussionSettings
.
COHORT
)
course_discussion_settings
=
get_course_discussion_settings
(
self
.
course
.
id
)
self
.
assertEqual
(
CourseDiscussionSettings
.
COHORT
,
course_discussion_settings
.
division_scheme
)
self
.
assertEqual
(
self
.
test_cohort
.
id
,
utils
.
get_group_id_for_user
(
self
.
test_user
,
course_discussion_settings
)
)
def
test_discussion_division_by_enrollment_track
(
self
):
set_discussion_division_settings
(
self
.
course
.
id
,
division_scheme
=
CourseDiscussionSettings
.
ENROLLMENT_TRACK
)
course_discussion_settings
=
get_course_discussion_settings
(
self
.
course
.
id
)
self
.
assertEqual
(
CourseDiscussionSettings
.
ENROLLMENT_TRACK
,
course_discussion_settings
.
division_scheme
)
self
.
assertEqual
(
-
2
,
# Verified has group ID 2, and we negate that value to ensure unique IDs
utils
.
get_group_id_for_user
(
self
.
test_user
,
course_discussion_settings
)
)
@attr
(
shard
=
1
)
class
CourseDiscussionDivisionEnabledTestCase
(
ModuleStoreTestCase
):
""" Test the course_discussion_division_enabled method. """
def
setUp
(
self
):
super
(
CourseDiscussionDivisionEnabledTestCase
,
self
)
.
setUp
()
self
.
course
=
CourseFactory
.
create
()
CourseModeFactory
.
create
(
course_id
=
self
.
course
.
id
,
mode_slug
=
CourseMode
.
AUDIT
)
self
.
test_cohort
=
CohortFactory
(
course_id
=
self
.
course
.
id
,
name
=
'Test Cohort'
,
users
=
[]
)
def
test_discussion_division_disabled
(
self
):
course_discussion_settings
=
get_course_discussion_settings
(
self
.
course
.
id
)
self
.
assertFalse
(
utils
.
course_discussion_division_enabled
(
course_discussion_settings
))
def
test_discussion_division_by_cohort
(
self
):
set_discussion_division_settings
(
self
.
course
.
id
,
enable_cohorts
=
False
,
division_scheme
=
CourseDiscussionSettings
.
COHORT
)
# Because cohorts are disabled, discussion division is not enabled.
self
.
assertFalse
(
utils
.
course_discussion_division_enabled
(
get_course_discussion_settings
(
self
.
course
.
id
)))
# Now enable cohorts, which will cause discussions to be divided.
set_discussion_division_settings
(
self
.
course
.
id
,
enable_cohorts
=
True
,
division_scheme
=
CourseDiscussionSettings
.
COHORT
)
self
.
assertTrue
(
utils
.
course_discussion_division_enabled
(
get_course_discussion_settings
(
self
.
course
.
id
)))
def
test_discussion_division_by_enrollment_track
(
self
):
set_discussion_division_settings
(
self
.
course
.
id
,
division_scheme
=
CourseDiscussionSettings
.
ENROLLMENT_TRACK
)
# Only a single enrollment track exists, so discussion division is not enabled.
self
.
assertFalse
(
utils
.
course_discussion_division_enabled
(
get_course_discussion_settings
(
self
.
course
.
id
)))
# Now create a second CourseMode, which will cause discussions to be divided.
CourseModeFactory
.
create
(
course_id
=
self
.
course
.
id
,
mode_slug
=
CourseMode
.
VERIFIED
)
self
.
assertTrue
(
utils
.
course_discussion_division_enabled
(
get_course_discussion_settings
(
self
.
course
.
id
)))
@attr
(
shard
=
1
)
class
GroupNameTestCase
(
ModuleStoreTestCase
):
""" Test the get_group_name and get_group_names_by_id methods. """
def
setUp
(
self
):
super
(
GroupNameTestCase
,
self
)
.
setUp
()
self
.
course
=
CourseFactory
.
create
()
CourseModeFactory
.
create
(
course_id
=
self
.
course
.
id
,
mode_slug
=
CourseMode
.
AUDIT
)
CourseModeFactory
.
create
(
course_id
=
self
.
course
.
id
,
mode_slug
=
CourseMode
.
VERIFIED
)
self
.
test_cohort_1
=
CohortFactory
(
course_id
=
self
.
course
.
id
,
name
=
'Cohort 1'
,
users
=
[]
)
self
.
test_cohort_2
=
CohortFactory
(
course_id
=
self
.
course
.
id
,
name
=
'Cohort 2'
,
users
=
[]
)
def
test_discussion_division_disabled
(
self
):
course_discussion_settings
=
get_course_discussion_settings
(
self
.
course
.
id
)
self
.
assertEqual
({},
utils
.
get_group_names_by_id
(
course_discussion_settings
))
self
.
assertIsNone
(
utils
.
get_group_name
(
-
1000
,
course_discussion_settings
))
def
test_discussion_division_by_cohort
(
self
):
set_discussion_division_settings
(
self
.
course
.
id
,
enable_cohorts
=
True
,
division_scheme
=
CourseDiscussionSettings
.
COHORT
)
course_discussion_settings
=
get_course_discussion_settings
(
self
.
course
.
id
)
self
.
assertEqual
(
{
self
.
test_cohort_1
.
id
:
self
.
test_cohort_1
.
name
,
self
.
test_cohort_2
.
id
:
self
.
test_cohort_2
.
name
},
utils
.
get_group_names_by_id
(
course_discussion_settings
)
)
self
.
assertEqual
(
self
.
test_cohort_2
.
name
,
utils
.
get_group_name
(
self
.
test_cohort_2
.
id
,
course_discussion_settings
)
)
# Test also with a group_id that doesn't exist.
self
.
assertIsNone
(
utils
.
get_group_name
(
-
1000
,
course_discussion_settings
)
)
def
test_discussion_division_by_enrollment_track
(
self
):
set_discussion_division_settings
(
self
.
course
.
id
,
division_scheme
=
CourseDiscussionSettings
.
ENROLLMENT_TRACK
)
course_discussion_settings
=
get_course_discussion_settings
(
self
.
course
.
id
)
self
.
assertEqual
(
{
-
1
:
"audit course"
,
-
2
:
"verified course"
},
utils
.
get_group_names_by_id
(
course_discussion_settings
)
)
self
.
assertEqual
(
"verified course"
,
utils
.
get_group_name
(
-
2
,
course_discussion_settings
)
)
# Test also with a group_id that doesn't exist.
self
.
assertIsNone
(
utils
.
get_group_name
(
-
1000
,
course_discussion_settings
)
)
class
PermissionsTestCase
(
ModuleStoreTestCase
):
class
PermissionsTestCase
(
ModuleStoreTestCase
):
"""Test utils functionality related to forums "abilities" (permissions)"""
"""Test utils functionality related to forums "abilities" (permissions)"""
...
@@ -1497,3 +1693,21 @@ class ClientConfigurationTestCase(TestCase):
...
@@ -1497,3 +1693,21 @@ class ClientConfigurationTestCase(TestCase):
result
=
perform_request
(
'GET'
,
'http://www.google.com'
)
result
=
perform_request
(
'GET'
,
'http://www.google.com'
)
self
.
assertEqual
(
result
,
{})
self
.
assertEqual
(
result
,
{})
def
set_discussion_division_settings
(
course_key
,
enable_cohorts
=
False
,
always_divide_inline_discussions
=
False
,
divided_discussions
=
[],
division_scheme
=
CourseDiscussionSettings
.
COHORT
):
"""
Convenience method for setting cohort enablement and discussion settings.
COHORT is the default division_scheme, as no other schemes were supported at
the time that the unit tests were originally written.
"""
set_course_discussion_settings
(
course_key
=
course_key
,
divided_discussions
=
divided_discussions
,
division_scheme
=
division_scheme
,
always_divide_inline_discussions
=
always_divide_inline_discussions
,
)
set_course_cohorted
(
course_key
,
enable_cohorts
)
lms/djangoapps/django_comment_client/utils.py
View file @
8951ac8c
...
@@ -14,20 +14,22 @@ import pystache_custom as pystache
...
@@ -14,20 +14,22 @@ import pystache_custom as pystache
from
opaque_keys.edx.locations
import
i4xEncoder
from
opaque_keys.edx.locations
import
i4xEncoder
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys.edx.keys
import
CourseKey
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
from
xmodule.partitions.partitions_service
import
PartitionService
from
xmodule.partitions.partitions
import
ENROLLMENT_TRACK_PARTITION_ID
from
django_comment_c
ommon.models
import
Role
,
FORUM_ROLE_STUDENT
from
django_comment_c
lient.constants
import
TYPE_ENTRY
,
TYPE_SUBCATEGORY
from
django_comment_client.permissions
import
check_permissions_by_view
,
has_permission
,
get_team
from
django_comment_client.permissions
import
check_permissions_by_view
,
has_permission
,
get_team
from
django_comment_client.settings
import
MAX_COMMENT_DEPTH
from
django_comment_client.settings
import
MAX_COMMENT_DEPTH
from
django_comment_client.constants
import
TYPE_ENTRY
,
TYPE_SUBCATEGORY
from
django_comment_common.models
import
Role
,
FORUM_ROLE_STUDENT
,
CourseDiscussionSettings
from
django_comment_common.utils
import
get_course_discussion_settings
from
edxmako
import
lookup_template
from
edxmako
import
lookup_template
from
courseware
import
courses
from
courseware
import
courses
from
courseware.access
import
has_access
from
courseware.access
import
has_access
from
openedx.core.djangoapps.content.course_structures.models
import
CourseStructure
from
openedx.core.djangoapps.content.course_structures.models
import
CourseStructure
from
openedx.core.djangoapps.course_groups.cohorts
import
(
from
openedx.core.djangoapps.course_groups.cohorts
import
(
get_course_cohort_settings
,
get_cohort_by_id
,
get_cohort_id
,
is_course_cohorted
is_course_cohorted
,
get_cohort_id
,
get_cohort_names
)
)
from
openedx.core.djangoapps.course_groups.models
import
CourseUserGroup
from
request_cache.middleware
import
request_cached
from
request_cache.middleware
import
request_cached
from
student.roles
import
GlobalStaff
from
student.roles
import
GlobalStaff
...
@@ -267,7 +269,7 @@ def get_discussion_category_map(course, user, divided_only_if_explicit=False, ex
...
@@ -267,7 +269,7 @@ def get_discussion_category_map(course, user, divided_only_if_explicit=False, ex
course: Course for which to get the ids.
course: Course for which to get the ids.
user: User to check for access.
user: User to check for access.
divided_only_if_explicit (bool): If True, inline topics are marked is_divided only if they are
divided_only_if_explicit (bool): If True, inline topics are marked is_divided only if they are
explicitly listed in
course_cohort_s
ettings.discussion_topics.
explicitly listed in
CourseDiscussionS
ettings.discussion_topics.
Example:
Example:
>>> example = {
>>> example = {
...
@@ -310,7 +312,9 @@ def get_discussion_category_map(course, user, divided_only_if_explicit=False, ex
...
@@ -310,7 +312,9 @@ def get_discussion_category_map(course, user, divided_only_if_explicit=False, ex
xblocks
=
get_accessible_discussion_xblocks
(
course
,
user
)
xblocks
=
get_accessible_discussion_xblocks
(
course
,
user
)
course_cohort_settings
=
get_course_cohort_settings
(
course
.
id
)
discussion_settings
=
get_course_discussion_settings
(
course
.
id
)
discussion_division_enabled
=
course_discussion_division_enabled
(
discussion_settings
)
divided_discussion_ids
=
discussion_settings
.
divided_discussions
for
xblock
in
xblocks
:
for
xblock
in
xblocks
:
discussion_id
=
xblock
.
discussion_id
discussion_id
=
xblock
.
discussion_id
...
@@ -357,13 +361,13 @@ def get_discussion_category_map(course, user, divided_only_if_explicit=False, ex
...
@@ -357,13 +361,13 @@ def get_discussion_category_map(course, user, divided_only_if_explicit=False, ex
node
[
level
][
"start_date"
]
=
category_start_date
node
[
level
][
"start_date"
]
=
category_start_date
divide_all_inline_discussions
=
(
# pylint: disable=invalid-name
divide_all_inline_discussions
=
(
# pylint: disable=invalid-name
not
divided_only_if_explicit
and
course_cohort_settings
.
always_cohort
_inline_discussions
not
divided_only_if_explicit
and
discussion_settings
.
always_divide
_inline_discussions
)
)
dupe_counters
=
defaultdict
(
lambda
:
0
)
# counts the number of times we see each title
dupe_counters
=
defaultdict
(
lambda
:
0
)
# counts the number of times we see each title
for
entry
in
entries
:
for
entry
in
entries
:
is_entry_divided
=
(
is_entry_divided
=
(
course_cohort_settings
.
is_cohort
ed
and
(
discussion_division_enabl
ed
and
(
divide_all_inline_discussions
or
entry
[
"id"
]
in
course_cohort_settings
.
cohorted_discussion
s
divide_all_inline_discussions
or
entry
[
"id"
]
in
divided_discussion_id
s
)
)
)
)
...
@@ -387,7 +391,7 @@ def get_discussion_category_map(course, user, divided_only_if_explicit=False, ex
...
@@ -387,7 +391,7 @@ def get_discussion_category_map(course, user, divided_only_if_explicit=False, ex
"sort_key"
:
entry
.
get
(
"sort_key"
,
topic
),
"sort_key"
:
entry
.
get
(
"sort_key"
,
topic
),
"start_date"
:
datetime
.
now
(
UTC
()),
"start_date"
:
datetime
.
now
(
UTC
()),
"is_divided"
:
(
"is_divided"
:
(
course_cohort_settings
.
is_cohorted
and
entry
[
"id"
]
in
course_cohort_settings
.
cohorted_discussion
s
discussion_division_enabled
and
entry
[
"id"
]
in
divided_discussion_id
s
)
)
}
}
...
@@ -652,7 +656,7 @@ def add_courseware_context(content_list, course, user, id_map=None):
...
@@ -652,7 +656,7 @@ def add_courseware_context(content_list, course, user, id_map=None):
content
.
update
({
"courseware_url"
:
url
,
"courseware_title"
:
title
})
content
.
update
({
"courseware_url"
:
url
,
"courseware_title"
:
title
})
def
prepare_content
(
content
,
course_key
,
is_staff
=
False
,
course_is_cohort
ed
=
None
):
def
prepare_content
(
content
,
course_key
,
is_staff
=
False
,
discussion_division_enabl
ed
=
None
):
"""
"""
This function is used to pre-process thread and comment models in various
This function is used to pre-process thread and comment models in various
ways before adding them to the HTTP response. This includes fixing empty
ways before adding them to the HTTP response. This includes fixing empty
...
@@ -666,7 +670,9 @@ def prepare_content(content, course_key, is_staff=False, course_is_cohorted=None
...
@@ -666,7 +670,9 @@ def prepare_content(content, course_key, is_staff=False, course_is_cohorted=None
content (dict): A thread or comment.
content (dict): A thread or comment.
course_key (CourseKey): The course key of the course.
course_key (CourseKey): The course key of the course.
is_staff (bool): Whether the user is a staff member.
is_staff (bool): Whether the user is a staff member.
course_is_cohorted (bool): Whether the course is cohorted.
discussion_division_enabled (bool): Whether division of course discussions is enabled.
Note that callers of this method do not need to provide this value (it defaults to None)--
it is calculated and then passed to recursive calls of this method.
"""
"""
fields
=
[
fields
=
[
'id'
,
'title'
,
'body'
,
'course_id'
,
'anonymous'
,
'anonymous_to_peers'
,
'id'
,
'title'
,
'body'
,
'course_id'
,
'anonymous'
,
'anonymous_to_peers'
,
...
@@ -707,23 +713,27 @@ def prepare_content(content, course_key, is_staff=False, course_is_cohorted=None
...
@@ -707,23 +713,27 @@ def prepare_content(content, course_key, is_staff=False, course_is_cohorted=None
else
:
else
:
del
endorsement
[
"user_id"
]
del
endorsement
[
"user_id"
]
if
course_is_cohort
ed
is
None
:
if
discussion_division_enabl
ed
is
None
:
course_is_cohorted
=
is_course_cohorted
(
course_key
)
discussion_division_enabled
=
course_discussion_division_enabled
(
get_course_discussion_settings
(
course_key
)
)
for
child_content_key
in
[
"children"
,
"endorsed_responses"
,
"non_endorsed_responses"
]:
for
child_content_key
in
[
"children"
,
"endorsed_responses"
,
"non_endorsed_responses"
]:
if
child_content_key
in
content
:
if
child_content_key
in
content
:
children
=
[
children
=
[
prepare_content
(
child
,
course_key
,
is_staff
,
course_is_cohorted
=
course_is_cohort
ed
)
prepare_content
(
child
,
course_key
,
is_staff
,
discussion_division_enabled
=
discussion_division_enabl
ed
)
for
child
in
content
[
child_content_key
]
for
child
in
content
[
child_content_key
]
]
]
content
[
child_content_key
]
=
children
content
[
child_content_key
]
=
children
if
course_is_cohort
ed
:
if
discussion_division_enabl
ed
:
# Augment the specified thread info to include the group name if a group id is present.
# Augment the specified thread info to include the group name if a group id is present.
if
content
.
get
(
'group_id'
)
is
not
None
:
if
content
.
get
(
'group_id'
)
is
not
None
:
content
[
'group_name'
]
=
get_cohort_by_id
(
course_key
,
content
.
get
(
'group_id'
))
.
name
course_discussion_settings
=
get_course_discussion_settings
(
course_key
)
content
[
'group_name'
]
=
get_group_name
(
content
.
get
(
'group_id'
),
course_discussion_settings
)
content
[
'is_commentable_divided'
]
=
is_commentable_divided
(
course_key
,
content
[
'commentable_id'
],
course_discussion_settings
)
else
:
else
:
# Remove any
cohort information that might remain if the course had previously been cohort
ed.
# Remove any
group information that might remain if the course had previously been divid
ed.
content
.
pop
(
'group_id'
,
None
)
content
.
pop
(
'group_id'
,
None
)
return
content
return
content
...
@@ -741,7 +751,8 @@ def get_group_id_for_comments_service(request, course_key, commentable_id=None):
...
@@ -741,7 +751,8 @@ def get_group_id_for_comments_service(request, course_key, commentable_id=None):
Raises:
Raises:
ValueError if the requested group_id is invalid
ValueError if the requested group_id is invalid
"""
"""
if
commentable_id
is
None
or
is_commentable_divided
(
course_key
,
commentable_id
):
course_discussion_settings
=
get_course_discussion_settings
(
course_key
)
if
commentable_id
is
None
or
is_commentable_divided
(
course_key
,
commentable_id
,
course_discussion_settings
):
if
request
.
method
==
"GET"
:
if
request
.
method
==
"GET"
:
requested_group_id
=
request
.
GET
.
get
(
'group_id'
)
requested_group_id
=
request
.
GET
.
get
(
'group_id'
)
elif
request
.
method
==
"POST"
:
elif
request
.
method
==
"POST"
:
...
@@ -749,27 +760,35 @@ def get_group_id_for_comments_service(request, course_key, commentable_id=None):
...
@@ -749,27 +760,35 @@ def get_group_id_for_comments_service(request, course_key, commentable_id=None):
if
has_permission
(
request
.
user
,
"see_all_cohorts"
,
course_key
):
if
has_permission
(
request
.
user
,
"see_all_cohorts"
,
course_key
):
if
not
requested_group_id
:
if
not
requested_group_id
:
return
None
return
None
try
:
group_id
=
int
(
requested_group_id
)
group_id
=
int
(
requested_group_id
)
get_cohort_by_id
(
course_key
,
group_id
)
_verify_group_exists
(
group_id
,
course_discussion_settings
)
except
CourseUserGroup
.
DoesNotExist
:
raise
ValueError
else
:
else
:
# regular users always query with their own id.
# regular users always query with their own id.
group_id
=
get_group_id_for_user
(
request
.
user
,
course_
key
)
group_id
=
get_group_id_for_user
(
request
.
user
,
course_
discussion_settings
)
return
group_id
return
group_id
else
:
else
:
# Never pass a group_id to the comments service for a non-
cohort
ed
# Never pass a group_id to the comments service for a non-
divid
ed
# commentable
# commentable
return
None
return
None
def
get_group_id_for_user
(
user
,
course_
key
):
def
get_group_id_for_user
(
user
,
course_
discussion_settings
):
"""
"""
This method will be modified to consider Enrollment Tracks.
Given a user, return the group_id for that user according to the course_discussion_settings.
Currently pass-through to get_cohort_id.
If discussions are not divided, this method will return None.
It will also return None if the user is in no group within the specified division_scheme.
"""
"""
return
get_cohort_id
(
user
,
course_key
)
division_scheme
=
_get_course_division_scheme
(
course_discussion_settings
)
if
division_scheme
==
CourseDiscussionSettings
.
COHORT
:
return
get_cohort_id
(
user
,
course_discussion_settings
.
course_id
)
elif
division_scheme
==
CourseDiscussionSettings
.
ENROLLMENT_TRACK
:
partition_service
=
PartitionService
(
course_discussion_settings
.
course_id
)
group_id
=
partition_service
.
get_user_group_id_for_partition
(
user
,
ENROLLMENT_TRACK_PARTITION_ID
)
# We negate the group_ids from dynamic partitions so that they will not conflict
# with cohort IDs (which are an auto-incrementing integer field, starting at 1).
return
-
1
*
group_id
if
group_id
is
not
None
else
None
else
:
return
None
def
is_comment_too_deep
(
parent
):
def
is_comment_too_deep
(
parent
):
...
@@ -786,11 +805,13 @@ def is_comment_too_deep(parent):
...
@@ -786,11 +805,13 @@ def is_comment_too_deep(parent):
)
)
def
is_commentable_divided
(
course_key
,
commentable_id
):
def
is_commentable_divided
(
course_key
,
commentable_id
,
course_discussion_settings
=
None
):
"""
"""
Args:
Args:
course_key: CourseKey
course_key: CourseKey
commentable_id: string
commentable_id: string
course_discussion_settings: CourseDiscussionSettings model instance (optional). If not
supplied, it will be retrieved via the course_key.
Returns:
Returns:
Bool: is this commentable divided, meaning that learners are divided into
Bool: is this commentable divided, meaning that learners are divided into
...
@@ -799,28 +820,118 @@ def is_commentable_divided(course_key, commentable_id):
...
@@ -799,28 +820,118 @@ def is_commentable_divided(course_key, commentable_id):
Raises:
Raises:
Http404 if the course doesn't exist.
Http404 if the course doesn't exist.
"""
"""
if
not
course_discussion_settings
:
course_discussion_settings
=
get_course_discussion_settings
(
course_key
)
course
=
courses
.
get_course_by_id
(
course_key
)
course
=
courses
.
get_course_by_id
(
course_key
)
course_cohort_settings
=
get_course_cohort_settings
(
course_key
)
if
not
course_
cohort_settings
.
is_cohorted
or
get_team
(
commentable_id
):
if
not
course_
discussion_division_enabled
(
course_discussion_settings
)
or
get_team
(
commentable_id
):
# this is the easy case :)
# this is the easy case :)
ans
=
False
ans
=
False
elif
(
elif
(
commentable_id
in
course
.
top_level_discussion_topic_ids
or
commentable_id
in
course
.
top_level_discussion_topic_ids
or
course_cohort_settings
.
always_cohort
_inline_discussions
is
False
course_discussion_settings
.
always_divide
_inline_discussions
is
False
):
):
# top level discussions have to be manually configured as
cohort
ed
# top level discussions have to be manually configured as
divid
ed
# (default is not).
# (default is not).
# Same thing for inline discussions if the default is explicitly set to False in settings
# Same thing for inline discussions if the default is explicitly set to False in settings
ans
=
commentable_id
in
course_
cohort_settings
.
cohort
ed_discussions
ans
=
commentable_id
in
course_
discussion_settings
.
divid
ed_discussions
else
:
else
:
# inline discussions are
cohort
ed by default
# inline discussions are
divid
ed by default
ans
=
True
ans
=
True
log
.
debug
(
u"is_commentable_divided(
%
s,
%
s) = {
%
s}"
,
course_key
,
commentable_id
,
ans
)
log
.
debug
(
u"is_commentable_divided(
%
s,
%
s) = {
%
s}"
,
course_key
,
commentable_id
,
ans
)
return
ans
return
ans
def
course_discussion_division_enabled
(
course_discussion_settings
):
"""
Are discussions divided for the course represented by this instance of
course_discussion_settings? This method looks both at
course_discussion_settings.division_scheme, and information about the course
state itself (For example, are cohorts enabled? And are there multiple
enrollment tracks?).
Args:
course_discussion_settings: CourseDiscussionSettings model instance
Returns: True if discussion division is enabled for the course, else False
"""
return
_get_course_division_scheme
(
course_discussion_settings
)
!=
CourseDiscussionSettings
.
NONE
def
_get_course_division_scheme
(
course_discussion_settings
):
division_scheme
=
course_discussion_settings
.
division_scheme
if
(
division_scheme
==
CourseDiscussionSettings
.
COHORT
and
not
is_course_cohorted
(
course_discussion_settings
.
course_id
)
):
division_scheme
=
CourseDiscussionSettings
.
NONE
elif
(
division_scheme
==
CourseDiscussionSettings
.
ENROLLMENT_TRACK
and
len
(
_get_enrollment_track_groups
(
course_discussion_settings
.
course_id
))
<=
1
):
division_scheme
=
CourseDiscussionSettings
.
NONE
return
division_scheme
def
get_group_name
(
group_id
,
course_discussion_settings
):
"""
Given a specified comments_service group_id, returns the learner-facing
name of the Group. If no such Group exists for the specified group_id
(taking into account the division_scheme and course specified by course_discussion_settings),
returns None.
Args:
group_id: the group_id as used by the comments_service code
course_discussion_settings: CourseDiscussionSettings model instance
Returns: learner-facing name of the Group, or None if no such group exists
"""
group_names_by_id
=
get_group_names_by_id
(
course_discussion_settings
)
return
group_names_by_id
[
group_id
]
if
group_id
in
group_names_by_id
else
None
def
get_group_names_by_id
(
course_discussion_settings
):
"""
Creates of a dict of group_id to learner-facing group names, for the division_scheme
in use as specified by course_discussion_settings.
Args:
course_discussion_settings: CourseDiscussionSettings model instance
Returns: dict of group_id to learner-facing group names. If no division_scheme
is in use, returns an empty dict.
"""
division_scheme
=
_get_course_division_scheme
(
course_discussion_settings
)
course_key
=
course_discussion_settings
.
course_id
if
division_scheme
==
CourseDiscussionSettings
.
COHORT
:
return
get_cohort_names
(
courses
.
get_course_by_id
(
course_key
))
elif
division_scheme
==
CourseDiscussionSettings
.
ENROLLMENT_TRACK
:
# We negate the group_ids from dynamic partitions so that they will not conflict
# with cohort IDs (which are an auto-incrementing integer field, starting at 1).
return
{
-
1
*
group
.
id
:
group
.
name
for
group
in
_get_enrollment_track_groups
(
course_key
)}
else
:
return
{}
def
_get_enrollment_track_groups
(
course_key
):
"""
Helper method that returns an array of the Groups in the EnrollmentTrackUserPartition for the given course.
If no such partition exists on the course, an empty array is returned.
"""
partition_service
=
PartitionService
(
course_key
)
partition
=
partition_service
.
get_user_partition
(
ENROLLMENT_TRACK_PARTITION_ID
)
return
partition
.
groups
if
partition
else
[]
def
_verify_group_exists
(
group_id
,
course_discussion_settings
):
"""
Helper method that verifies the given group_id corresponds to a Group in the
division scheme being used. If it does not, a ValueError will be raised.
"""
if
get_group_name
(
group_id
,
course_discussion_settings
)
is
None
:
raise
ValueError
def
is_discussion_enabled
(
course_id
):
def
is_discussion_enabled
(
course_id
):
"""
"""
Return True if discussions are enabled; else False
Return True if discussions are enabled; else False
...
...
lms/djangoapps/instructor/tests/test_api.py
View file @
8951ac8c
...
@@ -67,7 +67,7 @@ from lms.djangoapps.instructor_task.api_helper import AlreadyRunningError
...
@@ -67,7 +67,7 @@ from lms.djangoapps.instructor_task.api_helper import AlreadyRunningError
from
certificates.tests.factories
import
GeneratedCertificateFactory
from
certificates.tests.factories
import
GeneratedCertificateFactory
from
certificates.models
import
CertificateStatuses
from
certificates.models
import
CertificateStatuses
from
openedx.core.djangoapps.course_groups.cohorts
import
set_course_cohort
_settings
from
openedx.core.djangoapps.course_groups.cohorts
import
set_course_cohort
ed
from
openedx.core.lib.xblock_utils
import
grade_histogram
from
openedx.core.lib.xblock_utils
import
grade_histogram
from
openedx.core.djangoapps.site_configuration
import
helpers
as
configuration_helpers
from
openedx.core.djangoapps.site_configuration
import
helpers
as
configuration_helpers
from
openedx.core.djangoapps.site_configuration.tests.mixins
import
SiteMixin
from
openedx.core.djangoapps.site_configuration.tests.mixins
import
SiteMixin
...
@@ -2701,7 +2701,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
...
@@ -2701,7 +2701,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
cohorted, and does not when the course is not cohorted.
cohorted, and does not when the course is not cohorted.
"""
"""
url
=
reverse
(
'get_students_features'
,
kwargs
=
{
'course_id'
:
unicode
(
self
.
course
.
id
)})
url
=
reverse
(
'get_students_features'
,
kwargs
=
{
'course_id'
:
unicode
(
self
.
course
.
id
)})
set_course_cohort
_settings
(
self
.
course
.
id
,
is_cohorted
=
is_cohorted
)
set_course_cohort
ed
(
self
.
course
.
id
,
is_cohorted
)
response
=
self
.
client
.
post
(
url
,
{})
response
=
self
.
client
.
post
(
url
,
{})
res_json
=
json
.
loads
(
response
.
content
)
res_json
=
json
.
loads
(
response
.
content
)
...
...
lms/templates/discussion/_filter_dropdown.html
View file @
8951ac8c
...
@@ -20,7 +20,7 @@ from openedx.core.djangolib.markup import HTML
...
@@ -20,7 +20,7 @@ from openedx.core.djangolib.markup import HTML
class=
"forum-nav-browse-menu-item"
class=
"forum-nav-browse-menu-item"
data-discussion-id=
'${entries[entry]["id"]}'
data-discussion-id=
'${entries[entry]["id"]}'
id=
'${entries[entry]["id"]}'
id=
'${entries[entry]["id"]}'
data-
cohort
ed=
"${str(entries[entry]['is_divided']).lower()}"
data-
divid
ed=
"${str(entries[entry]['is_divided']).lower()}"
role=
"option"
role=
"option"
>
>
% if entry:
% if entry:
...
...
lms/templates/discussion/_thread_list_template.html
View file @
8951ac8c
...
@@ -21,14 +21,14 @@
...
@@ -21,14 +21,14 @@
%
endif
%
endif
<
/select
>
<
/select
>
##
safe
-
lint
:
disable
=
python
-
parse
-
error
,
python
-
wrap
-
html
##
safe
-
lint
:
disable
=
python
-
parse
-
error
,
python
-
wrap
-
html
<
/label>${"<% if
(
is
Cohort
ed && isPrivilegedUser
)
{ %>" | n, decode.utf8}<label class="forum-nav-filter-cohort"
>
<
/label>${"<% if
(
is
DiscussionDivisionEnabl
ed && isPrivilegedUser
)
{ %>" | n, decode.utf8}<label class="forum-nav-filter-cohort"
>
##
Translators
:
This
labels
a
cohort
menu
in
forum
navigation
##
Translators
:
This
labels
a
group
menu
in
forum
navigation
<
span
class
=
"sr"
>
$
{
_
(
"
Cohort
:"
)}
<
/span
>
<
span
class
=
"sr"
>
$
{
_
(
"
Group
:"
)}
<
/span
>
<
select
class
=
"forum-nav-filter-cohort-control"
>
<
select
class
=
"forum-nav-filter-cohort-control"
>
<
option
value
=
""
>
$
{
_
(
"in all
cohort
s"
)}
<
/option
>
<
option
value
=
""
>
$
{
_
(
"in all
group
s"
)}
<
/option
>
##
cohort
s
is
not
iterable
sometimes
because
inline
discussions
xmodule
doesn
't pass it
##
group
s
is
not
iterable
sometimes
because
inline
discussions
xmodule
doesn
't pass it
%for
c in (cohort
s or []):
%for
group in (group
s or []):
<option value="${
c['
id
']}">${c
['
name
']}</option>
<option value="${
group['
id
']}">${group
['
name
']}</option>
%endfor
%endfor
</select>
</select>
## safe-lint: disable=python-parse-error,python-wrap-html
## safe-lint: disable=python-parse-error,python-wrap-html
...
...
openedx/core/djangoapps/course_groups/cohorts.py
View file @
8951ac8c
...
@@ -118,7 +118,21 @@ def is_course_cohorted(course_key):
...
@@ -118,7 +118,21 @@ def is_course_cohorted(course_key):
Raises:
Raises:
Http404 if the course doesn't exist.
Http404 if the course doesn't exist.
"""
"""
return
get_course_cohort_settings
(
course_key
)
.
is_cohorted
return
_get_course_cohort_settings
(
course_key
)
.
is_cohorted
def
set_course_cohorted
(
course_key
,
cohorted
):
"""
Given a course course and a boolean, sets whether or not the course is cohorted.
Raises:
Value error if `cohorted` is not a boolean
"""
if
not
isinstance
(
cohorted
,
bool
):
raise
ValueError
(
"Cohorted must be a boolean"
)
course_cohort_settings
=
_get_course_cohort_settings
(
course_key
)
course_cohort_settings
.
is_cohorted
=
cohorted
course_cohort_settings
.
save
()
def
get_cohort_id
(
user
,
course_key
,
use_cached
=
False
):
def
get_cohort_id
(
user
,
course_key
,
use_cached
=
False
):
...
@@ -130,22 +144,6 @@ def get_cohort_id(user, course_key, use_cached=False):
...
@@ -130,22 +144,6 @@ def get_cohort_id(user, course_key, use_cached=False):
return
None
if
cohort
is
None
else
cohort
.
id
return
None
if
cohort
is
None
else
cohort
.
id
def
get_cohorted_commentables
(
course_key
):
"""
Given a course_key return a set of strings representing cohorted commentables.
"""
course_cohort_settings
=
get_course_cohort_settings
(
course_key
)
if
not
course_cohort_settings
.
is_cohorted
:
# this is the easy case :)
ans
=
set
()
else
:
ans
=
set
(
course_cohort_settings
.
cohorted_discussions
)
return
ans
COHORT_CACHE_NAMESPACE
=
u"cohorts.get_cohort"
COHORT_CACHE_NAMESPACE
=
u"cohorts.get_cohort"
...
@@ -213,8 +211,7 @@ def get_cohort(user, course_key, assign=True, use_cached=False):
...
@@ -213,8 +211,7 @@ def get_cohort(user, course_key, assign=True, use_cached=False):
# First check whether the course is cohorted (users shouldn't be in a cohort
# First check whether the course is cohorted (users shouldn't be in a cohort
# in non-cohorted courses, but settings can change after course starts)
# in non-cohorted courses, but settings can change after course starts)
course_cohort_settings
=
get_course_cohort_settings
(
course_key
)
if
not
is_course_cohorted
(
course_key
):
if
not
course_cohort_settings
.
is_cohorted
:
return
cache
.
setdefault
(
cache_key
,
None
)
return
cache
.
setdefault
(
cache_key
,
None
)
# If course is cohorted, check if the user already has a cohort.
# If course is cohorted, check if the user already has a cohort.
...
@@ -277,11 +274,7 @@ def migrate_cohort_settings(course):
...
@@ -277,11 +274,7 @@ def migrate_cohort_settings(course):
"""
"""
cohort_settings
,
created
=
CourseCohortsSettings
.
objects
.
get_or_create
(
cohort_settings
,
created
=
CourseCohortsSettings
.
objects
.
get_or_create
(
course_id
=
course
.
id
,
course_id
=
course
.
id
,
defaults
=
{
defaults
=
_get_cohort_settings_from_modulestore
(
course
)
'is_cohorted'
:
course
.
is_cohorted
,
'cohorted_discussions'
:
list
(
course
.
cohorted_discussions
),
'always_cohort_inline_discussions'
:
course
.
always_cohort_inline_discussions
}
)
)
# Add the new and update the existing cohorts
# Add the new and update the existing cohorts
...
@@ -507,50 +500,49 @@ def is_last_random_cohort(user_group):
...
@@ -507,50 +500,49 @@ def is_last_random_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
):
@request_cached
def
_get_course_cohort_settings
(
course_key
):
"""
"""
Set cohort settings for a course.
Return cohort settings for a course. NOTE that the only non-deprecated fields in
CourseCohortSettings are `course_id` and `is_cohorted`. Other fields should only be used for
migration purposes.
Arguments:
Arguments:
course_key: CourseKey
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:
Returns:
A CourseCohortSettings object.
A CourseCohortSettings object. NOTE that the only non-deprecated field in
CourseCohortSettings are `course_id` and `is_cohorted`. Other fields should only be used
for migration purposes.
Raises:
Raises:
Http404 if course_key is invalid.
Http404 if course_key is invalid.
"""
"""
fields
=
{
'is_cohorted'
:
bool
,
'always_cohort_inline_discussions'
:
bool
,
'cohorted_discussions'
:
list
}
try
:
course_cohort_settings
=
get_course_cohort_settings
(
course_key
)
course_cohort_settings
=
CourseCohortsSettings
.
objects
.
get
(
course_id
=
course_key
)
for
field
,
field_type
in
fields
.
items
():
except
CourseCohortsSettings
.
DoesNotExist
:
if
field
in
kwargs
:
course
=
courses
.
get_course_by_id
(
course_key
)
if
not
isinstance
(
kwargs
[
field
],
field_type
):
course_cohort_settings
=
migrate_cohort_settings
(
course
)
raise
ValueError
(
"Incorrect field type for `{}`. Type must be `{}`"
.
format
(
field
,
field_type
.
__name__
))
setattr
(
course_cohort_settings
,
field
,
kwargs
[
field
])
course_cohort_settings
.
save
()
return
course_cohort_settings
return
course_cohort_settings
@request_cached
def
get_legacy_discussion_settings
(
course_key
):
def
get_course_cohort_settings
(
course_key
):
"""
Return cohort settings for a course.
Arguments:
course_key: CourseKey
Returns:
A CourseCohortSettings object.
Raises:
Http404 if course_key is invalid.
"""
try
:
try
:
course_cohort_settings
=
CourseCohortsSettings
.
objects
.
get
(
course_id
=
course_key
)
course_cohort_settings
=
CourseCohortsSettings
.
objects
.
get
(
course_id
=
course_key
)
return
{
'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
}
except
CourseCohortsSettings
.
DoesNotExist
:
except
CourseCohortsSettings
.
DoesNotExist
:
course
=
courses
.
get_course_by_id
(
course_key
)
course
=
courses
.
get_course_by_id
(
course_key
)
course_cohort_settings
=
migrate_cohort_settings
(
course
)
return
_get_cohort_settings_from_modulestore
(
course
)
return
course_cohort_settings
def
_get_cohort_settings_from_modulestore
(
course
):
return
{
'is_cohorted'
:
course
.
is_cohorted
,
'cohorted_discussions'
:
list
(
course
.
cohorted_discussions
),
'always_cohort_inline_discussions'
:
course
.
always_cohort_inline_discussions
}
openedx/core/djangoapps/course_groups/models.py
View file @
8951ac8c
...
@@ -168,6 +168,7 @@ class CourseUserGroupPartitionGroup(models.Model):
...
@@ -168,6 +168,7 @@ class CourseUserGroupPartitionGroup(models.Model):
class
CourseCohortsSettings
(
models
.
Model
):
class
CourseCohortsSettings
(
models
.
Model
):
"""
"""
This model represents cohort settings for courses.
This model represents cohort settings for courses.
The only non-deprecated fields are `is_cohorted` and `course_id`.
"""
"""
is_cohorted
=
models
.
BooleanField
(
default
=
False
)
is_cohorted
=
models
.
BooleanField
(
default
=
False
)
...
@@ -184,16 +185,23 @@ class CourseCohortsSettings(models.Model):
...
@@ -184,16 +185,23 @@ class CourseCohortsSettings(models.Model):
# in reality the default value at the time that cohorting is enabled for a course comes from
# in reality the default value at the time that cohorting is enabled for a course comes from
# course_module.always_cohort_inline_discussions (via `migrate_cohort_settings`).
# course_module.always_cohort_inline_discussions (via `migrate_cohort_settings`).
# pylint: disable=invalid-name
# pylint: disable=invalid-name
# DEPRECATED-- DO NOT USE: Instead use `CourseDiscussionSettings.always_divide_inline_discussions`
# via `get_course_discussion_settings` or `set_course_discussion_settings`.
always_cohort_inline_discussions
=
models
.
BooleanField
(
default
=
False
)
always_cohort_inline_discussions
=
models
.
BooleanField
(
default
=
False
)
@property
@property
def
cohorted_discussions
(
self
):
def
cohorted_discussions
(
self
):
"""Jsonify the cohorted_discussions"""
"""
DEPRECATED-- DO NOT USE. Instead use `CourseDiscussionSettings.divided_discussions`
via `get_course_discussion_settings`.
"""
return
json
.
loads
(
self
.
_cohorted_discussions
)
return
json
.
loads
(
self
.
_cohorted_discussions
)
@cohorted_discussions.setter
@cohorted_discussions.setter
def
cohorted_discussions
(
self
,
value
):
def
cohorted_discussions
(
self
,
value
):
"""Un-Jsonify the cohorted_discussions"""
"""
DEPRECATED-- DO NOT USE. Instead use `CourseDiscussionSettings` via `set_course_discussion_settings`.
"""
self
.
_cohorted_discussions
=
json
.
dumps
(
value
)
self
.
_cohorted_discussions
=
json
.
dumps
(
value
)
...
...
openedx/core/djangoapps/course_groups/tests/helpers.py
View file @
8951ac8c
...
@@ -10,7 +10,9 @@ from opaque_keys.edx.locations import SlashSeparatedCourseKey
...
@@ -10,7 +10,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
..cohorts
import
set_course_cohort_settings
from
..cohorts
import
set_course_cohorted
from
django_comment_common.models
import
CourseDiscussionSettings
from
django_comment_common.utils
import
set_course_discussion_settings
from
..models
import
CourseUserGroup
,
CourseCohort
,
CourseCohortsSettings
,
CohortMembership
from
..models
import
CourseUserGroup
,
CourseCohort
,
CourseCohortsSettings
,
CohortMembership
...
@@ -140,8 +142,8 @@ def config_course_cohorts(
...
@@ -140,8 +142,8 @@ def config_course_cohorts(
auto_cohorts
=
[],
auto_cohorts
=
[],
manual_cohorts
=
[],
manual_cohorts
=
[],
discussion_topics
=
[],
discussion_topics
=
[],
cohort
ed_discussions
=
[],
divid
ed_discussions
=
[],
always_
cohort
_inline_discussions
=
False
always_
divide
_inline_discussions
=
False
):
):
"""
"""
Set discussions and configure cohorts for a course.
Set discussions and configure cohorts for a course.
...
@@ -153,10 +155,10 @@ def config_course_cohorts(
...
@@ -153,10 +155,10 @@ def config_course_cohorts(
manual_cohorts (list): Names of manual cohorts to create.
manual_cohorts (list): Names of manual cohorts to create.
discussion_topics (list): Discussion topic names. Picks ids and
discussion_topics (list): Discussion topic names. Picks ids and
sort_keys automatically.
sort_keys automatically.
cohorted_discussions: Discussion topics to cohort
. Converts the
divided_discussions: Discussion topics to divide
. Converts the
list to use the same ids as discussion topic names.
list to use the same ids as discussion topic names.
always_
cohort
_inline_discussions (bool): Whether inline discussions
always_
divide
_inline_discussions (bool): Whether inline discussions
should be
cohort
ed by default.
should be
divid
ed by default.
Returns:
Returns:
Nothing -- modifies course in place.
Nothing -- modifies course in place.
...
@@ -165,11 +167,12 @@ def config_course_cohorts(
...
@@ -165,11 +167,12 @@ def config_course_cohorts(
"""Convert name to id."""
"""Convert name to id."""
return
topic_name_to_id
(
course
,
name
)
return
topic_name_to_id
(
course
,
name
)
set_course_cohort_settings
(
set_course_cohorted
(
course
.
id
,
is_cohorted
)
set_course_discussion_settings
(
course
.
id
,
course
.
id
,
is_cohorted
=
is_cohorted
,
divided_discussions
=
[
to_id
(
name
)
for
name
in
divided_discussions
]
,
cohorted_discussions
=
[
to_id
(
name
)
for
name
in
cohorted_discussions
]
,
always_divide_inline_discussions
=
always_divide_inline_discussions
,
always_cohort_inline_discussions
=
always_cohort_inline_discussions
division_scheme
=
CourseDiscussionSettings
.
COHORT
,
)
)
for
cohort_name
in
auto_cohorts
:
for
cohort_name
in
auto_cohorts
:
...
...
openedx/core/djangoapps/course_groups/tests/test_cohorts.py
View file @
8951ac8c
...
@@ -22,8 +22,8 @@ from xmodule.modulestore.tests.factories import ToyCourseFactory
...
@@ -22,8 +22,8 @@ from xmodule.modulestore.tests.factories import ToyCourseFactory
from
..models
import
CourseUserGroup
,
CourseCohort
,
CourseUserGroupPartitionGroup
from
..models
import
CourseUserGroup
,
CourseCohort
,
CourseUserGroupPartitionGroup
from
..
import
cohorts
from
..
import
cohorts
from
..tests.helpers
import
(
from
..tests.helpers
import
(
topic_name_to_id
,
config_course_cohorts
,
config_course_cohorts_legacy
,
config_course_cohorts
,
config_course_cohorts_legacy
,
CohortFactory
,
CourseCohortFactory
,
CourseCohortSettingsFactory
CohortFactory
,
CourseCohortFactory
)
)
...
@@ -475,44 +475,6 @@ class TestCohorts(ModuleStoreTestCase):
...
@@ -475,44 +475,6 @@ class TestCohorts(ModuleStoreTestCase):
{
cohort1
.
id
:
cohort1
.
name
,
cohort2
.
id
:
cohort2
.
name
}
{
cohort1
.
id
:
cohort1
.
name
,
cohort2
.
id
:
cohort2
.
name
}
)
)
def
test_get_cohorted_commentables
(
self
):
"""
Make sure cohorts.get_cohorted_commentables() correctly returns a list of strings representing cohorted
commentables. Also verify that we can't get the cohorted commentables from a course which does not exist.
"""
course
=
modulestore
()
.
get_course
(
self
.
toy_course_key
)
self
.
assertEqual
(
cohorts
.
get_cohorted_commentables
(
course
.
id
),
set
())
config_course_cohorts
(
course
,
is_cohorted
=
True
)
self
.
assertEqual
(
cohorts
.
get_cohorted_commentables
(
course
.
id
),
set
())
config_course_cohorts
(
course
,
is_cohorted
=
True
,
discussion_topics
=
[
"General"
,
"Feedback"
],
cohorted_discussions
=
[
"Feedback"
]
)
self
.
assertItemsEqual
(
cohorts
.
get_cohorted_commentables
(
course
.
id
),
set
([
topic_name_to_id
(
course
,
"Feedback"
)])
)
config_course_cohorts
(
course
,
is_cohorted
=
True
,
discussion_topics
=
[
"General"
,
"Feedback"
],
cohorted_discussions
=
[
"General"
,
"Feedback"
]
)
self
.
assertItemsEqual
(
cohorts
.
get_cohorted_commentables
(
course
.
id
),
set
([
topic_name_to_id
(
course
,
"General"
),
topic_name_to_id
(
course
,
"Feedback"
)])
)
self
.
assertRaises
(
Http404
,
lambda
:
cohorts
.
get_cohorted_commentables
(
SlashSeparatedCourseKey
(
"course"
,
"does_not"
,
"exist"
))
)
def
test_get_cohort_by_name
(
self
):
def
test_get_cohort_by_name
(
self
):
"""
"""
Make sure cohorts.get_cohort_by_name() properly finds a cohort by name for a given course. Also verify that it
Make sure cohorts.get_cohort_by_name() properly finds a cohort by name for a given course. Also verify that it
...
@@ -672,59 +634,16 @@ class TestCohorts(ModuleStoreTestCase):
...
@@ -672,59 +634,16 @@ class TestCohorts(ModuleStoreTestCase):
# Note that the following get() will fail with MultipleObjectsReturned if race condition is not handled.
# Note that the following get() will fail with MultipleObjectsReturned if race condition is not handled.
self
.
assertEqual
(
first_cohort
.
users
.
get
(),
course_user
)
self
.
assertEqual
(
first_cohort
.
users
.
get
(),
course_user
)
def
test_get_course_cohort_settings
(
self
):
def
test_set_cohorted_with_invalid_data_type
(
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
.
assertFalse
(
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
.
Test that cohorts.set_course_cohort
ed raises exception if argument is not a boolean
.
"""
"""
course
=
modulestore
()
.
get_course
(
self
.
toy_course_key
)
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
=
True
)
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
.
assertTrue
(
course_cohort_settings
.
always_cohort_inline_discussions
)
def
test_update_course_cohort_settings_with_invalid_data_type
(
self
):
"""
Test that cohorts.set_course_cohort_settings raises exception if fields have incorrect data type.
"""
course
=
modulestore
()
.
get_course
(
self
.
toy_course_key
)
CourseCohortSettingsFactory
(
course_id
=
course
.
id
)
exception_msg_tpl
=
"Incorrect field type for `{}`. Type must be `{}`"
fields
=
[
{
'name'
:
'is_cohorted'
,
'type'
:
bool
},
{
'name'
:
'always_cohort_inline_discussions'
,
'type'
:
bool
},
{
'name'
:
'cohorted_discussions'
,
'type'
:
list
}
]
for
field
in
fields
:
with
self
.
assertRaises
(
ValueError
)
as
value_error
:
with
self
.
assertRaises
(
ValueError
)
as
value_error
:
cohorts
.
set_course_cohort_settings
(
course
.
id
,
**
{
field
[
'name'
]:
''
}
)
cohorts
.
set_course_cohorted
(
course
.
id
,
'not a boolean'
)
self
.
assertEqual
(
self
.
assertEqual
(
"Cohorted must be a boolean"
,
value_error
.
exception
.
message
)
value_error
.
exception
.
message
,
exception_msg_tpl
.
format
(
field
[
'name'
],
field
[
'type'
]
.
__name__
)
)
@attr
(
shard
=
2
)
@attr
(
shard
=
2
)
...
...
openedx/core/djangoapps/course_groups/tests/test_views.py
View file @
8951ac8c
...
@@ -125,7 +125,7 @@ class CohortViewsTestCase(ModuleStoreTestCase):
...
@@ -125,7 +125,7 @@ class CohortViewsTestCase(ModuleStoreTestCase):
self
.
course
,
self
.
course
,
is_cohorted
=
True
,
is_cohorted
=
True
,
discussion_topics
=
discussion_topics
,
discussion_topics
=
discussion_topics
,
cohort
ed_discussions
=
cohorted_discussions
divid
ed_discussions
=
cohorted_discussions
)
)
return
cohorted_inline_discussions
,
cohorted_course_wide_discussions
return
cohorted_inline_discussions
,
cohorted_course_wide_discussions
...
@@ -317,12 +317,23 @@ class CourseCohortSettingsHandlerTestCase(CohortViewsTestCase):
...
@@ -317,12 +317,23 @@ class CourseCohortSettingsHandlerTestCase(CohortViewsTestCase):
response
=
self
.
patch_handler
(
response
=
self
.
patch_handler
(
self
.
course
,
self
.
course
,
data
=
{
'always_cohort_inline_discussions'
:
''
},
expected_response_code
=
400
,
handler
=
course_cohort_settings_handler
)
self
.
assertEqual
(
"Incorrect field type for `{}`. Type must be `{}`"
.
format
(
'always_divide_inline_discussions'
,
bool
.
__name__
),
response
.
get
(
"error"
)
)
response
=
self
.
patch_handler
(
self
.
course
,
data
=
{
'is_cohorted'
:
''
},
data
=
{
'is_cohorted'
:
''
},
expected_response_code
=
400
,
expected_response_code
=
400
,
handler
=
course_cohort_settings_handler
handler
=
course_cohort_settings_handler
)
)
self
.
assertEqual
(
self
.
assertEqual
(
"
Incorrect field type for `{}`. Type must be `{}`"
.
format
(
'is_cohorted'
,
bool
.
__name__
)
,
"
Cohorted must be a boolean"
,
response
.
get
(
"error"
)
response
.
get
(
"error"
)
)
)
...
...
openedx/core/djangoapps/course_groups/views.py
View file @
8951ac8c
...
@@ -5,6 +5,7 @@ Views related to course groups functionality.
...
@@ -5,6 +5,7 @@ Views related to course groups functionality.
import
logging
import
logging
import
re
import
re
from
courseware.courses
import
get_course_with_access
from
django.contrib.auth.decorators
import
login_required
from
django.contrib.auth.decorators
import
login_required
from
django.contrib.auth.models
import
User
from
django.contrib.auth.models
import
User
from
django.core.paginator
import
EmptyPage
,
Paginator
from
django.core.paginator
import
EmptyPage
,
Paginator
...
@@ -14,13 +15,13 @@ from django.http import Http404, HttpResponseBadRequest
...
@@ -14,13 +15,13 @@ from django.http import Http404, HttpResponseBadRequest
from
django.utils.translation
import
ugettext
from
django.utils.translation
import
ugettext
from
django.views.decorators.csrf
import
ensure_csrf_cookie
from
django.views.decorators.csrf
import
ensure_csrf_cookie
from
django.views.decorators.http
import
require_http_methods
,
require_POST
from
django.views.decorators.http
import
require_http_methods
,
require_POST
from
opaque_keys.edx.keys
import
CourseKey
from
django_comment_common.models
import
CourseDiscussionSettings
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
django_comment_common.utils
import
get_course_discussion_settings
,
set_course_discussion_settings
from
courseware.courses
import
get_course_with_access
from
edxmako.shortcuts
import
render_to_response
from
edxmako.shortcuts
import
render_to_response
from
lms.djangoapps.django_comment_client.constants
import
TYPE_ENTRY
from
lms.djangoapps.django_comment_client.constants
import
TYPE_ENTRY
from
lms.djangoapps.django_comment_client.utils
import
get_discussion_categories_ids
,
get_discussion_category_map
from
lms.djangoapps.django_comment_client.utils
import
get_discussion_categories_ids
,
get_discussion_category_map
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
util.json_request
import
JsonResponse
,
expect_json
from
util.json_request
import
JsonResponse
,
expect_json
from
.
import
cohorts
from
.
import
cohorts
...
@@ -63,20 +64,20 @@ def unlink_cohort_partition_group(cohort):
...
@@ -63,20 +64,20 @@ def unlink_cohort_partition_group(cohort):
# pylint: disable=invalid-name
# pylint: disable=invalid-name
def
_get_course_cohort_settings_representation
(
course
,
course_cohort
_settings
):
def
_get_course_cohort_settings_representation
(
course
,
is_cohorted
,
course_discussion
_settings
):
"""
"""
Returns a JSON representation of a course cohort settings.
Returns a JSON representation of a course cohort settings.
"""
"""
cohorted_course_wide_discussions
,
cohorted_inline_discussions
=
get_cohort
ed_discussions
(
divided_course_wide_discussions
,
divided_inline_discussions
=
get_divid
ed_discussions
(
course
,
course_
cohort
_settings
course
,
course_
discussion
_settings
)
)
return
{
return
{
'id'
:
course_
cohort
_settings
.
id
,
'id'
:
course_
discussion
_settings
.
id
,
'is_cohorted'
:
course_cohort_settings
.
is_cohorted
,
'is_cohorted'
:
is_cohorted
,
'cohorted_inline_discussions'
:
cohort
ed_inline_discussions
,
'cohorted_inline_discussions'
:
divid
ed_inline_discussions
,
'cohorted_course_wide_discussions'
:
cohort
ed_course_wide_discussions
,
'cohorted_course_wide_discussions'
:
divid
ed_course_wide_discussions
,
'always_cohort_inline_discussions'
:
course_
cohort_settings
.
always_cohort
_inline_discussions
,
'always_cohort_inline_discussions'
:
course_
discussion_settings
.
always_divide
_inline_discussions
,
}
}
...
@@ -97,23 +98,23 @@ def _get_cohort_representation(cohort, course):
...
@@ -97,23 +98,23 @@ def _get_cohort_representation(cohort, course):
}
}
def
get_
cohorted_discussions
(
course
,
course
_settings
):
def
get_
divided_discussions
(
course
,
discussion
_settings
):
"""
"""
Returns the course-wide and inline
cohort
ed discussion ids separately.
Returns the course-wide and inline
divid
ed discussion ids separately.
"""
"""
cohort
ed_course_wide_discussions
=
[]
divid
ed_course_wide_discussions
=
[]
cohort
ed_inline_discussions
=
[]
divid
ed_inline_discussions
=
[]
course_wide_discussions
=
[
topic
[
'id'
]
for
__
,
topic
in
course
.
discussion_topics
.
items
()]
course_wide_discussions
=
[
topic
[
'id'
]
for
__
,
topic
in
course
.
discussion_topics
.
items
()]
all_discussions
=
get_discussion_categories_ids
(
course
,
None
,
include_all
=
True
)
all_discussions
=
get_discussion_categories_ids
(
course
,
None
,
include_all
=
True
)
for
cohorted_discussion_id
in
course_settings
.
cohort
ed_discussions
:
for
divided_discussion_id
in
discussion_settings
.
divid
ed_discussions
:
if
cohort
ed_discussion_id
in
course_wide_discussions
:
if
divid
ed_discussion_id
in
course_wide_discussions
:
cohorted_course_wide_discussions
.
append
(
cohort
ed_discussion_id
)
divided_course_wide_discussions
.
append
(
divid
ed_discussion_id
)
elif
cohort
ed_discussion_id
in
all_discussions
:
elif
divid
ed_discussion_id
in
all_discussions
:
cohorted_inline_discussions
.
append
(
cohort
ed_discussion_id
)
divided_inline_discussions
.
append
(
divid
ed_discussion_id
)
return
cohorted_course_wide_discussions
,
cohort
ed_inline_discussions
return
divided_course_wide_discussions
,
divid
ed_inline_discussions
@require_http_methods
((
"GET"
,
"PATCH"
))
@require_http_methods
((
"GET"
,
"PATCH"
))
...
@@ -131,11 +132,12 @@ def course_cohort_settings_handler(request, course_key_string):
...
@@ -131,11 +132,12 @@ def course_cohort_settings_handler(request, course_key_string):
"""
"""
course_key
=
CourseKey
.
from_string
(
course_key_string
)
course_key
=
CourseKey
.
from_string
(
course_key_string
)
course
=
get_course_with_access
(
request
.
user
,
'staff'
,
course_key
)
course
=
get_course_with_access
(
request
.
user
,
'staff'
,
course_key
)
cohort_settings
=
cohorts
.
get_course_cohort_settings
(
course_key
)
is_cohorted
=
cohorts
.
is_course_cohorted
(
course_key
)
discussion_settings
=
get_course_discussion_settings
(
course_key
)
if
request
.
method
==
'PATCH'
:
if
request
.
method
==
'PATCH'
:
cohorted_course_wide_discussions
,
cohorted_inline_discussions
=
get_cohort
ed_discussions
(
divided_course_wide_discussions
,
divided_inline_discussions
=
get_divid
ed_discussions
(
course
,
cohort
_settings
course
,
discussion
_settings
)
)
settings_to_change
=
{}
settings_to_change
=
{}
...
@@ -144,16 +146,16 @@ def course_cohort_settings_handler(request, course_key_string):
...
@@ -144,16 +146,16 @@ def course_cohort_settings_handler(request, course_key_string):
settings_to_change
[
'is_cohorted'
]
=
request
.
json
.
get
(
'is_cohorted'
)
settings_to_change
[
'is_cohorted'
]
=
request
.
json
.
get
(
'is_cohorted'
)
if
'cohorted_course_wide_discussions'
in
request
.
json
or
'cohorted_inline_discussions'
in
request
.
json
:
if
'cohorted_course_wide_discussions'
in
request
.
json
or
'cohorted_inline_discussions'
in
request
.
json
:
cohort
ed_course_wide_discussions
=
request
.
json
.
get
(
divid
ed_course_wide_discussions
=
request
.
json
.
get
(
'cohorted_course_wide_discussions'
,
cohort
ed_course_wide_discussions
'cohorted_course_wide_discussions'
,
divid
ed_course_wide_discussions
)
)
cohort
ed_inline_discussions
=
request
.
json
.
get
(
divid
ed_inline_discussions
=
request
.
json
.
get
(
'cohorted_inline_discussions'
,
cohort
ed_inline_discussions
'cohorted_inline_discussions'
,
divid
ed_inline_discussions
)
)
settings_to_change
[
'
cohorted_discussions'
]
=
cohorted_course_wide_discussions
+
cohort
ed_inline_discussions
settings_to_change
[
'
divided_discussions'
]
=
divided_course_wide_discussions
+
divid
ed_inline_discussions
if
'always_cohort_inline_discussions'
in
request
.
json
:
if
'always_cohort_inline_discussions'
in
request
.
json
:
settings_to_change
[
'always_
cohort
_inline_discussions'
]
=
request
.
json
.
get
(
settings_to_change
[
'always_
divide
_inline_discussions'
]
=
request
.
json
.
get
(
'always_cohort_inline_discussions'
'always_cohort_inline_discussions'
)
)
...
@@ -161,14 +163,19 @@ def course_cohort_settings_handler(request, course_key_string):
...
@@ -161,14 +163,19 @@ def course_cohort_settings_handler(request, course_key_string):
return
JsonResponse
({
"error"
:
unicode
(
"Bad Request"
)},
400
)
return
JsonResponse
({
"error"
:
unicode
(
"Bad Request"
)},
400
)
try
:
try
:
cohort_settings
=
cohorts
.
set_course_cohort_settings
(
if
'is_cohorted'
in
settings_to_change
:
course_key
,
**
settings_to_change
is_cohorted
=
settings_to_change
[
'is_cohorted'
]
)
cohorts
.
set_course_cohorted
(
course_key
,
is_cohorted
)
del
settings_to_change
[
'is_cohorted'
]
settings_to_change
[
'division_scheme'
]
=
CourseDiscussionSettings
.
COHORT
if
is_cohorted
\
else
CourseDiscussionSettings
.
NONE
if
settings_to_change
:
discussion_settings
=
set_course_discussion_settings
(
course_key
,
**
settings_to_change
)
except
ValueError
as
err
:
except
ValueError
as
err
:
# Note: error message not translated because it is not exposed to the user (UI prevents this state).
# Note: error message not translated because it is not exposed to the user (UI prevents this state).
return
JsonResponse
({
"error"
:
unicode
(
err
)},
400
)
return
JsonResponse
({
"error"
:
unicode
(
err
)},
400
)
return
JsonResponse
(
_get_course_cohort_settings_representation
(
course
,
cohort
_settings
))
return
JsonResponse
(
_get_course_cohort_settings_representation
(
course
,
is_cohorted
,
discussion
_settings
))
@require_http_methods
((
"GET"
,
"PUT"
,
"POST"
,
"PATCH"
))
@require_http_methods
((
"GET"
,
"PUT"
,
"POST"
,
"PATCH"
))
...
...
openedx/core/djangoapps/verified_track_content/partition_scheme.py
View file @
8951ac8c
...
@@ -12,7 +12,7 @@ from course_modes.models import CourseMode
...
@@ -12,7 +12,7 @@ from course_modes.models import CourseMode
from
student.models
import
CourseEnrollment
from
student.models
import
CourseEnrollment
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys.edx.keys
import
CourseKey
from
openedx.core.djangoapps.verified_track_content.models
import
VerifiedTrackCohortedCourse
from
openedx.core.djangoapps.verified_track_content.models
import
VerifiedTrackCohortedCourse
from
xmodule.partitions.partitions
import
NoSuchUserPartitionGroupError
,
Group
,
UserPartition
from
xmodule.partitions.partitions
import
Group
,
UserPartition
# These IDs must be less than 100 so that they do not overlap with Groups in
# These IDs must be less than 100 so that they do not overlap with Groups in
...
...
openedx/core/djangoapps/verified_track_content/tests/test_models.py
View file @
8951ac8c
...
@@ -18,7 +18,7 @@ from xmodule.modulestore.tests.factories import CourseFactory
...
@@ -18,7 +18,7 @@ from xmodule.modulestore.tests.factories import CourseFactory
from
..models
import
VerifiedTrackCohortedCourse
,
DEFAULT_VERIFIED_COHORT_NAME
from
..models
import
VerifiedTrackCohortedCourse
,
DEFAULT_VERIFIED_COHORT_NAME
from
..tasks
import
sync_cohort_with_mode
from
..tasks
import
sync_cohort_with_mode
from
openedx.core.djangoapps.course_groups.cohorts
import
(
from
openedx.core.djangoapps.course_groups.cohorts
import
(
set_course_cohort
_settings
,
add_cohort
,
CourseCohort
,
DEFAULT_COHORT_NAME
set_course_cohort
ed
,
add_cohort
,
CourseCohort
,
DEFAULT_COHORT_NAME
)
)
from
openedx.core.djangolib.testing.utils
import
skip_unless_lms
from
openedx.core.djangolib.testing.utils
import
skip_unless_lms
...
@@ -88,7 +88,7 @@ class TestMoveToVerified(SharedModuleStoreTestCase):
...
@@ -88,7 +88,7 @@ class TestMoveToVerified(SharedModuleStoreTestCase):
def
_enable_cohorting
(
self
):
def
_enable_cohorting
(
self
):
""" Turn on cohorting in the course. """
""" Turn on cohorting in the course. """
set_course_cohort
_settings
(
self
.
course
.
id
,
is_cohorted
=
True
)
set_course_cohort
ed
(
self
.
course
.
id
,
True
)
def
_create_verified_cohort
(
self
,
name
=
DEFAULT_VERIFIED_COHORT_NAME
):
def
_create_verified_cohort
(
self
,
name
=
DEFAULT_VERIFIED_COHORT_NAME
):
""" Create a verified cohort. """
""" Create a verified cohort. """
...
...
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