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
2cadbbad
Commit
2cadbbad
authored
Oct 27, 2017
by
Cliff Dyer
Committed by
GitHub
Oct 27, 2017
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #16186 from open-craft/cliff/complete-scored-blocks
Complete scored blocks
parents
a9448876
69271d04
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
188 additions
and
57 deletions
+188
-57
lms/djangoapps/completion/apps.py
+3
-0
lms/djangoapps/completion/handlers.py
+36
-0
lms/djangoapps/completion/tests/test_handlers.py
+120
-0
lms/djangoapps/completion/tests/test_models.py
+3
-3
lms/djangoapps/courseware/tests/test_module_render.py
+22
-54
lms/djangoapps/grades/signals/signals.py
+4
-0
No files found.
lms/djangoapps/completion/apps.py
View file @
2cadbbad
...
...
@@ -12,3 +12,6 @@ class CompletionAppConfig(AppConfig):
"""
name
=
'lms.djangoapps.completion'
verbose_name
=
'Completion'
def
ready
(
self
):
from
.
import
handlers
# pylint: disable=unused-variable
lms/djangoapps/completion/handlers.py
0 → 100644
View file @
2cadbbad
"""
Signal handlers to trigger completion updates.
"""
from
__future__
import
absolute_import
,
division
,
print_function
,
unicode_literals
from
django.contrib.auth.models
import
User
from
django.dispatch
import
receiver
from
opaque_keys.edx.keys
import
CourseKey
,
UsageKey
from
lms.djangoapps.grades.signals.signals
import
PROBLEM_WEIGHTED_SCORE_CHANGED
from
.models
import
BlockCompletion
from
.
import
waffle
@receiver
(
PROBLEM_WEIGHTED_SCORE_CHANGED
)
def
scorable_block_completion
(
sender
,
**
kwargs
):
# pylint: disable=unused-argument
"""
When a problem is scored, submit a new BlockCompletion for that block.
"""
if
not
waffle
.
waffle
()
.
is_enabled
(
waffle
.
ENABLE_COMPLETION_TRACKING
):
return
user
=
User
.
objects
.
get
(
id
=
kwargs
[
'user_id'
])
course_key
=
CourseKey
.
from_string
(
kwargs
[
'course_id'
])
block_key
=
UsageKey
.
from_string
(
kwargs
[
'usage_id'
])
if
kwargs
.
get
(
'score_deleted'
):
completion
=
0.0
else
:
completion
=
1.0
BlockCompletion
.
objects
.
submit_completion
(
user
=
user
,
course_key
=
course_key
,
block_key
=
block_key
,
completion
=
completion
,
)
lms/djangoapps/completion/tests/test_handlers.py
0 → 100644
View file @
2cadbbad
"""
Test signal handlers.
"""
from
datetime
import
datetime
import
ddt
from
django.test
import
TestCase
from
mock
import
patch
from
opaque_keys.edx.keys
import
CourseKey
from
pytz
import
utc
import
six
from
lms.djangoapps.grades.signals.signals
import
PROBLEM_WEIGHTED_SCORE_CHANGED
from
student.tests.factories
import
UserFactory
from
..
import
handlers
from
..models
import
BlockCompletion
from
..
import
waffle
class
CompletionHandlerMixin
(
object
):
"""
Common functionality for completion handler tests.
"""
def
override_waffle_switch
(
self
,
override
):
"""
Override the setting of the ENABLE_COMPLETION_TRACKING waffle switch
for the course of the test.
Parameters:
override (bool): True if tracking should be enabled.
"""
_waffle_overrider
=
waffle
.
waffle
()
.
override
(
waffle
.
ENABLE_COMPLETION_TRACKING
,
override
)
_waffle_overrider
.
__enter__
()
self
.
addCleanup
(
_waffle_overrider
.
__exit__
,
None
,
None
,
None
)
@ddt.ddt
class
ScorableCompletionHandlerTestCase
(
CompletionHandlerMixin
,
TestCase
):
"""
Test the signal handler
"""
def
setUp
(
self
):
super
(
ScorableCompletionHandlerTestCase
,
self
)
.
setUp
()
self
.
user
=
UserFactory
.
create
()
self
.
course_key
=
CourseKey
.
from_string
(
"course-v1:a+valid+course"
)
self
.
block_key
=
self
.
course_key
.
make_usage_key
(
block_type
=
"video"
,
block_id
=
"mah-video"
)
self
.
override_waffle_switch
(
True
)
@ddt.data
(
({
'score_deleted'
:
True
},
0.0
),
({
'score_deleted'
:
False
},
1.0
),
({},
1.0
),
)
@ddt.unpack
def
test_handler_submits_completion
(
self
,
params
,
expected_completion
):
handlers
.
scorable_block_completion
(
sender
=
self
,
user_id
=
self
.
user
.
id
,
course_id
=
six
.
text_type
(
self
.
course_key
),
usage_id
=
six
.
text_type
(
self
.
block_key
),
weighted_earned
=
0.0
,
weighted_possible
=
3.0
,
modified
=
datetime
.
utcnow
()
.
replace
(
tzinfo
=
utc
),
score_db_table
=
'submissions'
,
**
params
)
completion
=
BlockCompletion
.
objects
.
get
(
user
=
self
.
user
,
course_key
=
self
.
course_key
,
block_key
=
self
.
block_key
)
self
.
assertEqual
(
completion
.
completion
,
expected_completion
)
def
test_signal_calls_handler
(
self
):
user
=
UserFactory
.
create
()
course_key
=
CourseKey
.
from_string
(
"course-v1:a+valid+course"
)
block_key
=
course_key
.
make_usage_key
(
block_type
=
"video"
,
block_id
=
"mah-video"
)
with
patch
(
'lms.djangoapps.completion.handlers.scorable_block_completion'
)
as
mock_handler
:
PROBLEM_WEIGHTED_SCORE_CHANGED
.
send_robust
(
sender
=
self
,
user_id
=
user
.
id
,
course_id
=
six
.
text_type
(
course_key
),
usage_id
=
six
.
text_type
(
block_key
),
weighted_earned
=
0.0
,
weighted_possible
=
3.0
,
modified
=
datetime
.
utcnow
()
.
replace
(
tzinfo
=
utc
),
score_db_table
=
'submissions'
,
)
mock_handler
.
assert_called
()
class
DisabledCompletionHandlerTestCase
(
CompletionHandlerMixin
,
TestCase
):
"""
Test that disabling the ENABLE_COMPLETION_TRACKING waffle switch prevents
the signal handler from submitting a completion.
"""
def
setUp
(
self
):
super
(
DisabledCompletionHandlerTestCase
,
self
)
.
setUp
()
self
.
user
=
UserFactory
.
create
()
self
.
course_key
=
CourseKey
.
from_string
(
"course-v1:a+valid+course"
)
self
.
block_key
=
self
.
course_key
.
make_usage_key
(
block_type
=
"video"
,
block_id
=
"mah-video"
)
self
.
override_waffle_switch
(
False
)
def
test_disabled_handler_does_not_submit_completion
(
self
):
handlers
.
scorable_block_completion
(
sender
=
self
,
user_id
=
self
.
user
.
id
,
course_id
=
six
.
text_type
(
self
.
course_key
),
usage_id
=
six
.
text_type
(
self
.
block_key
),
weighted_earned
=
0.0
,
weighted_possible
=
3.0
,
modified
=
datetime
.
utcnow
()
.
replace
(
tzinfo
=
utc
),
score_db_table
=
'submissions'
,
)
with
self
.
assertRaises
(
BlockCompletion
.
DoesNotExist
):
BlockCompletion
.
objects
.
get
(
user
=
self
.
user
,
course_key
=
self
.
course_key
,
block_key
=
self
.
block_key
)
lms/djangoapps/completion/tests/test_models.py
View file @
2cadbbad
...
...
@@ -47,9 +47,9 @@ class SubmitCompletionTestCase(CompletionSetUpMixin, TestCase):
"""
def
setUp
(
self
):
super
(
SubmitCompletionTestCase
,
self
)
.
setUp
()
self
.
_overrider
=
waffle
.
waffle
()
.
override
(
waffle
.
ENABLE_COMPLETION_TRACKING
,
True
)
self
.
_overrider
.
__enter__
()
self
.
addCleanup
(
self
.
_overrider
.
__exit__
,
None
,
None
,
None
)
_overrider
=
waffle
.
waffle
()
.
override
(
waffle
.
ENABLE_COMPLETION_TRACKING
,
True
)
_overrider
.
__enter__
()
self
.
addCleanup
(
_overrider
.
__exit__
,
None
,
None
,
None
)
self
.
set_up_completion
()
def
test_changed_value
(
self
):
...
...
lms/djangoapps/courseware/tests/test_module_render.py
View file @
2cadbbad
...
...
@@ -136,6 +136,7 @@ class StubCompletableXBlock(XBlock):
def
progress
(
self
,
json_data
,
suffix
):
# pylint: disable=unused-argument
"""
Mark the block as complete using the deprecated progress interface.
New code should use the completion event instead.
"""
return
self
.
runtime
.
publish
(
self
,
'progress'
,
{})
...
...
@@ -425,6 +426,7 @@ class ModuleRenderTestCase(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
@attr
(
shard
=
1
)
@ddt.ddt
class
TestHandleXBlockCallback
(
SharedModuleStoreTestCase
,
LoginEnrollmentTestCase
):
"""
Test the handle_xblock_callback function
...
...
@@ -606,78 +608,44 @@ class TestHandleXBlockCallback(SharedModuleStoreTestCase, LoginEnrollmentTestCas
self
.
assertEquals
(
student_module
.
grade
,
0.75
)
self
.
assertEquals
(
student_module
.
max_grade
,
1
)
@ddt.data
(
(
'complete'
,
{
'completion'
:
0.625
}),
(
'progress'
,
{}),
)
@ddt.unpack
@XBlock.register_temp_plugin
(
StubCompletableXBlock
,
identifier
=
'comp'
)
def
test_completion_event_with_completion_disabled
(
self
):
with
completion_waffle
.
waffle
()
.
override
(
completion_waffle
.
ENABLE_COMPLETION_TRACKING
,
False
):
course
=
CourseFactory
.
create
()
block
=
ItemFactory
.
create
(
category
=
'comp'
,
parent
=
course
)
request
=
self
.
request_factory
.
post
(
'/'
,
data
=
json
.
dumps
({
'completion'
:
0.625
}),
content_type
=
'application/json'
,
)
request
.
user
=
self
.
mock_user
with
self
.
assertRaises
(
Http404
):
result
=
render
.
handle_xblock_callback
(
request
,
unicode
(
course
.
id
),
quote_slashes
(
unicode
(
block
.
scope_ids
.
usage_id
)),
'complete'
,
''
,
)
@XBlock.register_temp_plugin
(
StubCompletableXBlock
,
identifier
=
'comp'
)
def
test_completion_event
(
self
):
with
completion_waffle
.
waffle
()
.
override
(
completion_waffle
.
ENABLE_COMPLETION_TRACKING
,
True
):
course
=
CourseFactory
.
create
()
block
=
ItemFactory
.
create
(
category
=
'comp'
,
parent
=
course
)
request
=
self
.
request_factory
.
post
(
'/'
,
data
=
json
.
dumps
({
'completion'
:
0.625
}),
content_type
=
'application/json'
,
)
request
.
user
=
self
.
mock_user
response
=
render
.
handle_xblock_callback
(
request
,
unicode
(
course
.
id
),
quote_slashes
(
unicode
(
block
.
scope_ids
.
usage_id
)),
'complete'
,
''
,
)
self
.
assertEqual
(
response
.
status_code
,
200
)
completion
=
BlockCompletion
.
objects
.
get
(
block_key
=
block
.
scope_ids
.
usage_id
)
self
.
assertEqual
(
completion
.
completion
,
0.625
)
@XBlock.register_temp_plugin
(
StubCompletableXBlock
,
identifier
=
'comp'
)
def
test_progress_event_with_completion_disabled
(
self
):
def
test_completion_events_with_completion_disabled
(
self
,
signal
,
data
):
with
completion_waffle
.
waffle
()
.
override
(
completion_waffle
.
ENABLE_COMPLETION_TRACKING
,
False
):
course
=
CourseFactory
.
create
()
block
=
ItemFactory
.
create
(
category
=
'comp'
,
parent
=
course
)
request
=
self
.
request_factory
.
post
(
'/'
,
data
=
json
.
dumps
(
{}
),
data
=
json
.
dumps
(
data
),
content_type
=
'application/json'
,
)
request
.
user
=
self
.
mock_user
with
self
.
assertRaises
(
Http404
):
re
sponse
=
re
nder
.
handle_xblock_callback
(
render
.
handle_xblock_callback
(
request
,
unicode
(
course
.
id
),
quote_slashes
(
unicode
(
block
.
scope_ids
.
usage_id
)),
'progress'
,
signal
,
''
,
)
self
.
assertEqual
(
response
.
status_code
,
404
)
raise
Http404
@ddt.data
(
(
'complete'
,
{
'completion'
:
0.625
},
0.625
),
(
'progress'
,
{},
1.0
),
)
@ddt.unpack
@XBlock.register_temp_plugin
(
StubCompletableXBlock
,
identifier
=
'comp'
)
def
test_
progress_event
(
self
):
def
test_
completion_events
(
self
,
signal
,
data
,
expected_completion
):
with
completion_waffle
.
waffle
()
.
override
(
completion_waffle
.
ENABLE_COMPLETION_TRACKING
,
True
):
course
=
CourseFactory
.
create
()
block
=
ItemFactory
.
create
(
category
=
'comp'
,
parent
=
course
)
request
=
self
.
request_factory
.
post
(
'/'
,
data
=
json
.
dumps
(
{}
),
data
=
json
.
dumps
(
data
),
content_type
=
'application/json'
,
)
request
.
user
=
self
.
mock_user
...
...
@@ -685,12 +653,12 @@ class TestHandleXBlockCallback(SharedModuleStoreTestCase, LoginEnrollmentTestCas
request
,
unicode
(
course
.
id
),
quote_slashes
(
unicode
(
block
.
scope_ids
.
usage_id
)),
'progress'
,
signal
,
''
,
)
self
.
assertEqual
(
response
.
status_code
,
200
)
completion
=
BlockCompletion
.
objects
.
get
(
block_key
=
block
.
scope_ids
.
usage_id
)
self
.
assertEqual
(
completion
.
completion
,
1.0
)
self
.
assertEqual
(
response
.
status_code
,
200
)
completion
=
BlockCompletion
.
objects
.
get
(
block_key
=
block
.
scope_ids
.
usage_id
)
self
.
assertEqual
(
completion
.
completion
,
expected_completion
)
@XBlock.register_temp_plugin
(
StubCompletableXBlock
,
identifier
=
'comp'
)
def
test_skip_handlers_for_masquerading_staff
(
self
):
...
...
lms/djangoapps/grades/signals/signals.py
View file @
2cadbbad
...
...
@@ -26,6 +26,8 @@ PROBLEM_RAW_SCORE_CHANGED = Signal(
'modified'
,
# A datetime indicating when the database representation of
# this the problem score was saved.
'score_db_table'
,
# The database table that houses the score that changed.
'score_deleted'
,
# Boolean indicating whether the score changed due to
# the user state being deleted.
]
)
...
...
@@ -49,6 +51,8 @@ PROBLEM_WEIGHTED_SCORE_CHANGED = Signal(
'modified'
,
# A datetime indicating when the database representation of
# this the problem score was saved.
'score_db_table'
,
# The database table that houses the score that changed.
'score_deleted'
,
# Boolean indicating whether the score changed due to
# the user state being deleted.
]
)
...
...
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