Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ Alessandro Roux <aroux@edx.org>
Sofiya Semenova <ssemenova@edx.org>
Jeremy Bowman <jbowman@edx.org>
Nimisha Asthagiri <nimisha@edx.org>
Usman Khalid <usman@opencraft.com>

6 changes: 6 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ Change Log

Unreleased
~~~~~~~~~~
[0.1.7] - 2018-06-18
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Added can_mark_block_complete_on_view() and blocks_to_mark_complete_on_view()
methods on CompletionService and renamed get_completion_by_viewing_delay_ms()
to get_complete_on_view_delay_ms().

[0.1.6] - 2018-04-13
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Remove usage of deprecated CourseStructure api.
Expand Down
2 changes: 1 addition & 1 deletion completion/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
from __future__ import unicode_literals


__version__ = '0.1.6'
__version__ = '0.1.7'

default_app_config = 'completion.apps.CompletionAppConfig' # pylint: disable=invalid-name
24 changes: 22 additions & 2 deletions completion/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from __future__ import absolute_import, unicode_literals

from django.conf import settings
from xblock.completable import XBlockCompletionMode

from .models import BlockCompletion
from . import waffle
Expand Down Expand Up @@ -91,9 +92,28 @@ def vertical_is_complete(self, item):
return False
return True

def get_completion_by_viewing_delay_ms(self):
def get_complete_on_view_delay_ms(self):
"""
Do not mark blocks complete-by-viewing until they have been visible for
Do not mark blocks complete-on-view until they have been visible for
the returned amount of time, in milliseconds. Defaults to 5000.
"""
return getattr(settings, 'COMPLETION_BY_VIEWING_DELAY_MS', 5000)

def can_mark_block_complete_on_view(self, block):
"""
Returns True if the xblock can be marked complete on view.
This is true of any non-customized, non-scorable, completable block.
"""
return (
getattr(block, 'completion_mode', XBlockCompletionMode.COMPLETABLE) == XBlockCompletionMode.COMPLETABLE
and not getattr(block, 'has_custom_completion', False)
and not getattr(block, 'has_score', False)
)

def blocks_to_mark_complete_on_view(self, blocks):
"""
Returns a set of blocks which should be marked complete on view and haven't been yet.
"""
blocks = {block for block in blocks if self.can_mark_block_complete_on_view(block)}
completions = self.get_completions({block.location for block in blocks})
return {block for block in blocks if completions.get(block.location, 0) < 1.0}
76 changes: 74 additions & 2 deletions completion/tests/test_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
import ddt
from django.test import TestCase
from django.test.utils import override_settings
from mock import Mock
from opaque_keys.edx.keys import CourseKey, UsageKey
from xblock.completable import XBlockCompletionMode
from xblock.core import XBlock
from xblock.fields import ScopeIds

from ..models import BlockCompletion
from ..services import CompletionService
Expand Down Expand Up @@ -80,6 +84,74 @@ def test_enabled_honors_waffle_switch(self, enabled):
with self.override_completion_switch(enabled):
self.assertEqual(self.completion_service.completion_tracking_enabled(), enabled)

@ddt.data(
(XBlockCompletionMode.COMPLETABLE, False, False, True),
(XBlockCompletionMode.COMPLETABLE, True, False, False),
(XBlockCompletionMode.COMPLETABLE, False, True, False),
(XBlockCompletionMode.AGGREGATOR, False, False, False),
(XBlockCompletionMode.EXCLUDED, False, False, False)
)
@ddt.unpack
def test_can_mark_block_complete_on_view(self, mode, has_score, has_custom_completion, can_mark_complete):
block = XBlock(Mock(), scope_ids=Mock(spec=ScopeIds))
block.completion_mode = mode
block.has_score = has_score
if has_custom_completion:
block.has_custom_completion = True

self.assertEqual(self.completion_service.can_mark_block_complete_on_view(block), can_mark_complete)

def test_blocks_to_mark_complete_on_view(self):

completable_block_1 = XBlock(Mock(), scope_ids=Mock(spec=ScopeIds))
completable_block_1.location = UsageKey.from_string("i4x://edX/100/a/1").replace(course_key=self.course_key)
completable_block_2 = XBlock(Mock(), scope_ids=Mock(spec=ScopeIds))
completable_block_2.location = UsageKey.from_string("i4x://edX/100/a/2").replace(course_key=self.course_key)
aggregator_block = XBlock(Mock(), scope_ids=Mock(spec=ScopeIds))
aggregator_block.location = UsageKey.from_string("i4x://edX/100/a/3").replace(course_key=self.course_key)
aggregator_block.completion_mode = XBlockCompletionMode.AGGREGATOR

self.assertSetEqual(self.completion_service.blocks_to_mark_complete_on_view({}), set())

self.assertSetEqual(
self.completion_service.blocks_to_mark_complete_on_view({aggregator_block}), set()
)

self.assertSetEqual(
self.completion_service.blocks_to_mark_complete_on_view(
{completable_block_1, completable_block_2, aggregator_block}
),
{completable_block_1, completable_block_2}
)

BlockCompletion.objects.submit_completion(
user=self.user,
course_key=self.course_key,
block_key=completable_block_2.location,
completion=1.0
)

self.assertSetEqual(
self.completion_service.blocks_to_mark_complete_on_view(
{completable_block_1, completable_block_2, aggregator_block}
),
{completable_block_1}
)

BlockCompletion.objects.submit_completion(
user=self.user,
course_key=self.course_key,
block_key=completable_block_1.location,
completion=1.0
)

self.assertSetEqual(
self.completion_service.blocks_to_mark_complete_on_view(
{completable_block_1, completable_block_2, aggregator_block}
),
set()
)


@ddt.ddt
class CompletionDelayTestCase(CompletionSetUpMixin, TestCase):
Expand All @@ -89,7 +161,7 @@ class CompletionDelayTestCase(CompletionSetUpMixin, TestCase):
"""

@ddt.data(1, 1000, 0)
def test_get_completion_by_viewing_delay_ms(self, delay):
def test_get_complete_on_view_delay_ms(self, delay):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you made sure you caught all uses of this method in edx-platform?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup. They are used in two views and there is is bokchoy coverage for both.

service = CompletionService(self.user, self.course_key)
with override_settings(COMPLETION_BY_VIEWING_DELAY_MS=delay):
self.assertEqual(service.get_completion_by_viewing_delay_ms(), delay)
self.assertEqual(service.get_complete_on_view_delay_ms(), delay)