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
bfe54744
Commit
bfe54744
authored
Jun 01, 2015
by
E. Kolpakov
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Option to export only cohorted discussions
parent
21b47de5
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
59 additions
and
21 deletions
+59
-21
lms/djangoapps/django_comment_client/management/commands/export_discussion_participation.py
+30
-5
lms/djangoapps/django_comment_client/tests/management/test_export_discussion_participation.py
+23
-12
lms/lib/comment_client/user.py
+6
-4
No files found.
lms/djangoapps/django_comment_client/management/commands/export_discussion_participation.py
View file @
bfe54744
...
@@ -53,6 +53,9 @@ class Command(BaseCommand):
...
@@ -53,6 +53,9 @@ class Command(BaseCommand):
* thread-type - one of {discussion, question}. Filters discussion participation stats by discussion thread type.
* thread-type - one of {discussion, question}. Filters discussion participation stats by discussion thread type.
* end-date - date time in iso8601 format (YYYY-MM-DD hh:mm:ss). Filters discussion participation stats
* end-date - date time in iso8601 format (YYYY-MM-DD hh:mm:ss). Filters discussion participation stats
by creation date: no threads/comments/replies created *after* this date is included in calculation
by creation date: no threads/comments/replies created *after* this date is included in calculation
FLAGS:
* cohorted_only - only dump cohorted inline discussion threads
* all - Dump all social stats at once into a particular directory.
* all - Dump all social stats at once into a particular directory.
Examples:
Examples:
...
@@ -68,10 +71,13 @@ class Command(BaseCommand):
...
@@ -68,10 +71,13 @@ class Command(BaseCommand):
* `./manage.py lms export_discussion_participation <course_key> --end-date=<iso8601 datetime>
* `./manage.py lms export_discussion_participation <course_key> --end-date=<iso8601 datetime>
--thread-type=[discussion|question]` - exports discussion participation stats for a course for
--thread-type=[discussion|question]` - exports discussion participation stats for a course for
threads/comments/replies created before specified date, including only threads of specified type
threads/comments/replies created before specified date, including only threads of specified type
* `./manage.py lms export_discussion_participation <course_key> --cohorted_only` - exports only cohorted discussion
participation stats for a course; output is written to default location (same folder, auto-generated file name)
"""
"""
THREAD_TYPE_PARAMETER
=
'thread_type'
THREAD_TYPE_PARAMETER
=
'thread_type'
END_DATE_PARAMETER
=
'end_date'
END_DATE_PARAMETER
=
'end_date'
ALL_PARAMETER
=
'all'
ALL_PARAMETER
=
'all'
COHORTED_ONLY_PARAMETER
=
'cohorted_only'
args
=
"<course_id> <output_file_location>"
args
=
"<course_id> <output_file_location>"
...
@@ -98,6 +104,12 @@ class Command(BaseCommand):
...
@@ -98,6 +104,12 @@ class Command(BaseCommand):
action
=
'store_true'
,
action
=
'store_true'
,
dest
=
ALL_PARAMETER
,
dest
=
ALL_PARAMETER
,
default
=
False
,
default
=
False
,
),
make_option
(
'--cohorted_only'
,
action
=
'store_true'
,
dest
=
COHORTED_ONLY_PARAMETER
,
default
=
False
,
)
)
)
)
...
@@ -164,12 +176,22 @@ class Command(BaseCommand):
...
@@ -164,12 +176,22 @@ class Command(BaseCommand):
if
not
course
:
if
not
course
:
raise
CommandError
(
"Invalid course id: {}"
.
format
(
course_key
))
raise
CommandError
(
"Invalid course id: {}"
.
format
(
course_key
))
target_discussion_ids
=
None
if
options
.
get
(
self
.
COHORTED_ONLY_PARAMETER
,
False
):
cohorted_discussions
=
course
.
cohort_config
.
get
(
'cohorted_inline_discussions'
,
None
)
if
not
cohorted_discussions
:
raise
CommandError
(
"Only cohorted discussions are marked for export, "
"but no cohorted discussions found for the course"
)
else
:
target_discussion_ids
=
cohorted_discussions
raw_end_date
=
options
.
get
(
self
.
END_DATE_PARAMETER
,
None
)
raw_end_date
=
options
.
get
(
self
.
END_DATE_PARAMETER
,
None
)
end_date
=
dateutil
.
parser
.
parse
(
raw_end_date
)
if
raw_end_date
else
None
end_date
=
dateutil
.
parser
.
parse
(
raw_end_date
)
if
raw_end_date
else
None
data
=
Extractor
()
.
extract
(
data
=
Extractor
()
.
extract
(
course_key
,
course_key
,
end_date
=
end_date
,
end_date
=
end_date
,
thread_type
=
(
options
.
get
(
self
.
THREAD_TYPE_PARAMETER
,
None
))
thread_type
=
(
options
.
get
(
self
.
THREAD_TYPE_PARAMETER
,
None
)),
thread_ids
=
target_discussion_ids
,
)
)
filter_str
=
self
.
_get_filter_string_representation
(
options
)
filter_str
=
self
.
_get_filter_string_representation
(
options
)
...
@@ -210,11 +232,13 @@ class Extractor(object):
...
@@ -210,11 +232,13 @@ class Extractor(object):
users
=
CourseEnrollment
.
users_enrolled_in
(
course_key
)
users
=
CourseEnrollment
.
users_enrolled_in
(
course_key
)
return
{
user
.
id
:
user
for
user
in
users
}
return
{
user
.
id
:
user
for
user
in
users
}
def
_get_social_stats
(
self
,
course_key
,
end_date
=
None
,
thread_type
=
None
):
def
_get_social_stats
(
self
,
course_key
,
end_date
=
None
,
thread_type
=
None
,
thread_ids
=
None
):
""" Gets social stats for course with specified filter parameters """
""" Gets social stats for course with specified filter parameters """
return
{
return
{
int
(
user_id
):
data
for
user_id
,
data
int
(
user_id
):
data
for
user_id
,
data
in
User
.
all_social_stats
(
str
(
course_key
),
end_date
=
end_date
,
thread_type
=
thread_type
)
.
iteritems
()
in
User
.
all_social_stats
(
str
(
course_key
),
end_date
=
end_date
,
thread_type
=
thread_type
,
thread_ids
=
thread_ids
)
.
iteritems
()
}
}
def
_merge_user_data_and_social_stats
(
self
,
userdata
,
social_stats
):
def
_merge_user_data_and_social_stats
(
self
,
userdata
,
social_stats
):
...
@@ -232,13 +256,14 @@ class Extractor(object):
...
@@ -232,13 +256,14 @@ class Extractor(object):
result
.
append
(
utils
.
merge_dict
(
user_record
,
stats
))
result
.
append
(
utils
.
merge_dict
(
user_record
,
stats
))
return
result
return
result
def
extract
(
self
,
course_key
,
end_date
=
None
,
thread_type
=
None
):
def
extract
(
self
,
course_key
,
end_date
=
None
,
thread_type
=
None
,
thread_ids
=
None
):
""" Extracts and merges data according to course key and filter parameters """
""" Extracts and merges data according to course key and filter parameters """
users
=
self
.
_get_users
(
course_key
)
users
=
self
.
_get_users
(
course_key
)
social_stats
=
self
.
_get_social_stats
(
social_stats
=
self
.
_get_social_stats
(
course_key
,
course_key
,
end_date
=
end_date
,
end_date
=
end_date
,
thread_type
=
thread_type
thread_type
=
thread_type
,
thread_ids
=
thread_ids
)
)
return
self
.
_merge_user_data_and_social_stats
(
users
,
social_stats
)
return
self
.
_merge_user_data_and_social_stats
(
users
,
social_stats
)
...
...
lms/djangoapps/django_comment_client/tests/management/test_export_discussion_participation.py
View file @
bfe54744
...
@@ -19,11 +19,13 @@ from datetime import datetime
...
@@ -19,11 +19,13 @@ from datetime import datetime
_target_module
=
"django_comment_client.management.commands.export_discussion_participation"
_target_module
=
"django_comment_client.management.commands.export_discussion_participation"
_std_parameters_list
=
(
_std_parameters_list
=
(
(
CourseLocator
(
org
=
"edX"
,
course
=
"demoX"
,
run
=
"now"
),
None
,
None
),
(
CourseLocator
(
org
=
"edX"
,
course
=
"demoX"
,
run
=
"now"
),
None
,
None
,
None
),
(
CourseLocator
(
org
=
"otherX"
,
course
=
"courseX"
,
run
=
"later"
),
datetime
(
2015
,
2
,
12
),
None
),
(
CourseLocator
(
org
=
"otherX"
,
course
=
"courseX"
,
run
=
"later"
),
datetime
(
2015
,
2
,
12
),
None
,
None
),
(
CourseLocator
(
org
=
"NotAX"
,
course
=
"NotADemo"
,
run
=
"anyyear"
),
None
,
'discussion'
),
(
CourseLocator
(
org
=
"NotAX"
,
course
=
"NotADemo"
,
run
=
"anyyear"
),
None
,
'discussion'
,
None
),
(
CourseLocator
(
org
=
"YeaAX"
,
course
=
"YesADemo"
,
run
=
"anyday"
),
None
,
'question'
),
(
CourseLocator
(
org
=
"YeaAX"
,
course
=
"YesADemo"
,
run
=
"anyday"
),
None
,
'question'
,
None
),
(
CourseLocator
(
org
=
"WhatX"
,
course
=
"WhatADemo"
,
run
=
"last_year"
),
datetime
(
2014
,
3
,
17
),
'question'
)
(
CourseLocator
(
org
=
"WhatX"
,
course
=
"WhatADemo"
,
run
=
"last_year"
),
datetime
(
2014
,
3
,
17
),
'question'
,
None
),
(
CourseLocator
(
org
=
"WhatX"
,
course
=
"WhatADemo"
,
run
=
"last_year"
),
datetime
(
2014
,
3
,
17
),
None
,
[
'123'
]),
(
CourseLocator
(
org
=
"WhatX"
,
course
=
"WhatADemo"
,
run
=
"last_year"
),
datetime
(
2014
,
3
,
17
),
'question'
,
[
'1'
,
'2'
]),
)
)
# pylint: enable=invalid-name
# pylint: enable=invalid-name
...
@@ -44,7 +46,7 @@ class CommandTest(TestCase):
...
@@ -44,7 +46,7 @@ class CommandTest(TestCase):
def
set_up_default_mocks
(
self
,
patched_get_course
):
def
set_up_default_mocks
(
self
,
patched_get_course
):
""" Sets up default mocks passed via class decorator """
""" Sets up default mocks passed via class decorator """
patched_get_course
.
return_value
=
CourseLocator
(
"edX"
,
"demoX"
,
"now"
)
patched_get_course
.
return_value
=
mock
.
Mock
(
spec
=
CourseLocator
)
# pylint:disable=unused-argument
# pylint:disable=unused-argument
def
test_handle_given_no_arguments_raises_command_error
(
self
,
patched_get_course
):
def
test_handle_given_no_arguments_raises_command_error
(
self
,
patched_get_course
):
...
@@ -150,10 +152,14 @@ class CommandTest(TestCase):
...
@@ -150,10 +152,14 @@ class CommandTest(TestCase):
@ddt.unpack
@ddt.unpack
@ddt.data
(
*
_std_parameters_list
)
@ddt.data
(
*
_std_parameters_list
)
def
test_handle_passes_correct_parameters_to_extractor
(
def
test_handle_passes_correct_parameters_to_extractor
(
self
,
course_key
,
end_date
,
thread_type
,
patched_get_course
self
,
course_key
,
end_date
,
thread_type
,
cohorted_thread_ids
,
patched_get_course
):
):
""" Tests that when no explicit filename is given data is exported to default location """
""" Tests that when no explicit filename is given data is exported to default location """
self
.
set_up_default_mocks
(
patched_get_course
)
self
.
set_up_default_mocks
(
patched_get_course
)
if
cohorted_thread_ids
:
type
(
patched_get_course
.
return_value
)
.
cohort_config
=
mock
.
PropertyMock
(
return_value
=
{
'cohorted_inline_discussions'
:
cohorted_thread_ids
}
)
patched_open
=
mock
.
mock_open
()
patched_open
=
mock
.
mock_open
()
with
mock
.
patch
(
"{}.open"
.
format
(
_target_module
),
patched_open
,
create
=
True
),
\
with
mock
.
patch
(
"{}.open"
.
format
(
_target_module
),
patched_open
,
create
=
True
),
\
mock
.
patch
(
_target_module
+
".Extractor.extract"
)
as
patched_extractor
:
mock
.
patch
(
_target_module
+
".Extractor.extract"
)
as
patched_extractor
:
...
@@ -161,9 +167,12 @@ class CommandTest(TestCase):
...
@@ -161,9 +167,12 @@ class CommandTest(TestCase):
self
.
command
.
handle
(
self
.
command
.
handle
(
str
(
course_key
),
str
(
course_key
),
end_date
=
end_date
.
isoformat
()
if
end_date
else
end_date
,
end_date
=
end_date
.
isoformat
()
if
end_date
else
end_date
,
thread_type
=
thread_type
thread_type
=
thread_type
,
cohorted_only
=
True
if
cohorted_thread_ids
else
False
)
patched_extractor
.
assert_called_with
(
course_key
,
end_date
=
end_date
,
thread_type
=
thread_type
,
thread_ids
=
cohorted_thread_ids
)
)
patched_extractor
.
assert_called_with
(
course_key
,
end_date
=
end_date
,
thread_type
=
thread_type
)
def
_make_user_mock
(
user_id
,
username
=
""
,
email
=
""
,
first_name
=
""
,
last_name
=
""
):
def
_make_user_mock
(
user_id
,
username
=
""
,
email
=
""
,
first_name
=
""
,
last_name
=
""
):
...
@@ -221,15 +230,17 @@ class ExtractorTest(TestCase):
...
@@ -221,15 +230,17 @@ class ExtractorTest(TestCase):
@ddt.unpack
@ddt.unpack
@ddt.data
(
*
_std_parameters_list
)
@ddt.data
(
*
_std_parameters_list
)
def
test_extract_invokes_correct_data_extraction_methods
(
self
,
course_key
,
end_date
,
thread_type
):
def
test_extract_invokes_correct_data_extraction_methods
(
self
,
course_key
,
end_date
,
thread_type
,
thread_ids
):
""" Tests that correct underlying extractors are called with proper arguments """
""" Tests that correct underlying extractors are called with proper arguments """
with
mock
.
patch
(
_target_module
+
'.CourseEnrollment.users_enrolled_in'
)
as
patched_users_enrolled_in
,
\
with
mock
.
patch
(
_target_module
+
'.CourseEnrollment.users_enrolled_in'
)
as
patched_users_enrolled_in
,
\
mock
.
patch
(
_target_module
+
".User.all_social_stats"
)
as
patched_all_social_stats
:
mock
.
patch
(
_target_module
+
".User.all_social_stats"
)
as
patched_all_social_stats
:
self
.
extractor
.
extract
(
course_key
,
end_date
=
end_date
,
thread_type
=
thread_type
)
self
.
extractor
.
extract
(
course_key
,
end_date
=
end_date
,
thread_type
=
thread_type
,
thread_ids
=
thread_ids
)
patched_users_enrolled_in
.
return_value
=
[]
patched_users_enrolled_in
.
return_value
=
[]
patched_users_enrolled_in
.
patched_all_social_stats
=
{}
patched_users_enrolled_in
.
patched_all_social_stats
=
{}
patched_users_enrolled_in
.
assert_called_with
(
course_key
)
patched_users_enrolled_in
.
assert_called_with
(
course_key
)
patched_all_social_stats
.
assert_called_with
(
str
(
course_key
),
end_date
=
end_date
,
thread_type
=
thread_type
)
patched_all_social_stats
.
assert_called_with
(
str
(
course_key
),
end_date
=
end_date
,
thread_type
=
thread_type
,
thread_ids
=
thread_ids
)
@ddt.unpack
@ddt.unpack
@ddt.data
(
@ddt.data
(
...
...
lms/lib/comment_client/user.py
View file @
bfe54744
...
@@ -119,9 +119,9 @@ class User(models.Model):
...
@@ -119,9 +119,9 @@ class User(models.Model):
return
get_user_social_stats
(
self
.
id
,
self
.
course_id
,
end_date
=
end_date
)
return
get_user_social_stats
(
self
.
id
,
self
.
course_id
,
end_date
=
end_date
)
@classmethod
@classmethod
def
all_social_stats
(
cls
,
course_id
,
end_date
=
None
,
thread_type
=
None
):
def
all_social_stats
(
cls
,
course_id
,
end_date
=
None
,
thread_type
=
None
,
thread_ids
=
None
):
""" Get social stats for all users participating in a course """
""" Get social stats for all users participating in a course """
return
get_user_social_stats
(
'*'
,
course_id
,
end_date
=
end_date
,
thread_type
=
thread_type
)
return
get_user_social_stats
(
'*'
,
course_id
,
end_date
=
end_date
,
thread_type
=
thread_type
,
thread_ids
=
thread_ids
)
def
_retrieve
(
self
,
*
args
,
**
kwargs
):
def
_retrieve
(
self
,
*
args
,
**
kwargs
):
url
=
self
.
url
(
action
=
'get'
,
params
=
self
.
attributes
)
url
=
self
.
url
(
action
=
'get'
,
params
=
self
.
attributes
)
...
@@ -156,7 +156,7 @@ class User(models.Model):
...
@@ -156,7 +156,7 @@ class User(models.Model):
self
.
_update_from_response
(
response
)
self
.
_update_from_response
(
response
)
def
get_user_social_stats
(
user_id
,
course_id
,
end_date
=
None
,
thread_type
=
None
):
def
get_user_social_stats
(
user_id
,
course_id
,
end_date
=
None
,
thread_type
=
None
,
thread_ids
=
None
):
""" Queries cs_comments_service for social_stats """
""" Queries cs_comments_service for social_stats """
if
not
course_id
:
if
not
course_id
:
raise
CommentClientRequestError
(
"Must provide course_id when retrieving social stats for the user"
)
raise
CommentClientRequestError
(
"Must provide course_id when retrieving social stats for the user"
)
...
@@ -167,9 +167,11 @@ def get_user_social_stats(user_id, course_id, end_date=None, thread_type=None):
...
@@ -167,9 +167,11 @@ def get_user_social_stats(user_id, course_id, end_date=None, thread_type=None):
params
.
update
({
'end_date'
:
end_date
.
isoformat
()})
params
.
update
({
'end_date'
:
end_date
.
isoformat
()})
if
thread_type
:
if
thread_type
:
params
.
update
({
'thread_type'
:
thread_type
})
params
.
update
({
'thread_type'
:
thread_type
})
if
thread_ids
:
params
.
update
({
'thread_ids'
:
","
.
join
(
thread_ids
)})
response
=
perform_request
(
response
=
perform_request
(
'
ge
t'
,
'
pos
t'
,
url
,
url
,
params
params
)
)
...
...
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