Skip to content

Commit

Permalink
Merge pull request #8 from edx/resume-button-completion-side
Browse files Browse the repository at this point in the history
EDUCATOR 2190: completion-side resume button work
  • Loading branch information
Alessandro Roux authored Feb 28, 2018
2 parents ee87020 + e97a512 commit a0ac2b8
Show file tree
Hide file tree
Showing 9 changed files with 137 additions and 27 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
Alex Dusenbery <[email protected]>
Simon Chen <[email protected]>
Gregory Martin <[email protected]>
Alessandro Roux <[email protected]>
10 changes: 9 additions & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,18 @@ Unreleased
~~~~~~~~~~
* Add query method for all completions by course

[0.0.9] - 2018-02-27
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* Added "utilities.py", which houses methods for working with BlockCompletion
data.

[0.0.8] - 2018-03-01
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* Nothing
* Add model method for superlative “last completed block” - for site awareness
include every last completed block by course, for later sorting in business
layer.

[0.0.7] - 2018-02-15
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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.0.8'
__version__ = '0.0.9'

default_app_config = 'completion.apps.CompletionAppConfig' # pylint: disable=invalid-name
15 changes: 15 additions & 0 deletions completion/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'''
This file contains custom exceptions used by the completion repository.
'''
from __future__ import unicode_literals, absolute_import


class UnavailableCompletionData(Exception):
'''
UnavailableCompletionData should be raised when a method is unable to gather
completion data from BlockCompletion.
'''

def __init__(self, course_key):
Exception.__init__(
self, "The learner does not have completion data within course {}".format(course_key))
16 changes: 16 additions & 0 deletions completion/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,21 @@
from .models import BlockCompletion


def submit_completions_for_testing(user, course_key, block_keys):
'''
Allows tests to submit completion data for a given user, course_key, and
list of block_keys. The method completes the "block_keys" by adding them
to the BlockCompletion model.
'''
for idx, block_key in enumerate(block_keys):
BlockCompletion.objects.submit_completion(
user=user,
course_key=course_key,
block_key=block_key,
completion=1.0 - (0.2 * idx),
)


class UserFactory(DjangoModelFactory):
"""
A Factory for User objects.
Expand All @@ -43,6 +58,7 @@ class CompletionWaffleTestMixin(object):
"""
Mixin to provide waffle switch overriding ability to child TestCase classes.
"""

def override_waffle_switch(self, override):
"""
Override the setting of the ENABLE_COMPLETION_TRACKING waffle switch
Expand Down
35 changes: 12 additions & 23 deletions completion/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@
from opaque_keys.edx.keys import CourseKey, UsageKey

from .. import models
from ..test_utils import CompletionSetUpMixin, UserFactory
from ..test_utils import CompletionSetUpMixin, UserFactory, submit_completions_for_testing


class PercentValidatorTestCase(TestCase):
"""
Test that validate_percent only allows floats (and ints) between 0.0 and 1.0.
"""

def test_valid_percents(self):
for value in [1.0, 0.0, 1, 0, 0.5, 0.333081348071397813987230871]:
models.validate_percent(value)
Expand Down Expand Up @@ -168,37 +169,25 @@ def setUp(self):
self.block_keys_two = [
UsageKey.from_string("i4x://edX/MOOC101/video/{}".format(number)) for number in range(5)
]
self.submit_fake_completions()

def submit_fake_completions(self):
"""
Submit completions for given runtime, run at setup
"""
test_date = 1
the_completion_date = datetime.datetime(2050, 1, 1, tzinfo=UTC)
for idx, block_key in enumerate(self.block_keys[:3]):
with freeze_time(datetime.datetime(2050, 1, test_date, tzinfo=UTC)):
with freeze_time(the_completion_date):
models.BlockCompletion.objects.submit_completion(
user=self.user_one,
course_key=self.course_key_one,
block_key=block_key,
completion=1.0 - (0.2 * idx),
)
test_date += 1
the_completion_date += datetime.timedelta(days=1)

for idx, block_key in enumerate(self.block_keys[2:]): # Wrong user
models.BlockCompletion.objects.submit_completion(
user=self.user_two,
course_key=self.course_key_one,
block_key=block_key,
completion=0.9 - (0.2 * idx),
)
with freeze_time(datetime.datetime(2050, 1, 10, tzinfo=UTC)):
models.BlockCompletion.objects.submit_completion( # Wrong course
user=self.user_one,
course_key=self.course_key_two,
block_key=self.block_keys[4],
completion=0.75,
)
# Wrong user
submit_completions_for_testing(self.user_two, self.course_key_one, self.block_keys[2:])

# Wrong course
the_completion_date = datetime.datetime(2050, 1, 10, tzinfo=UTC)
with freeze_time(the_completion_date):
submit_completions_for_testing(self.user_one, self.course_key_two, [self.block_keys[4]])

def test_get_course_completions_missing_runs(self):
actual_completions = models.BlockCompletion.get_course_completions(self.user_one, self.course_key_one)
Expand Down
51 changes: 51 additions & 0 deletions completion/tests/test_utilities.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
'''
The unit tests for utilities.py.
'''
from __future__ import unicode_literals
from mock import patch

from django.test import TestCase
from opaque_keys.edx.keys import CourseKey
from six import text_type

from ..exceptions import UnavailableCompletionData
from ..utilities import get_key_to_last_completed_course_block
from ..test_utils import UserFactory, CompletionSetUpMixin, submit_completions_for_testing


class TestCompletionUtilities(CompletionSetUpMixin, TestCase):
'''
Tests methods in completion's external-facing API.
'''

COMPLETION_SWITCH_ENABLED = True

def setUp(self):
super(TestCompletionUtilities, self).setUp()
self.user = UserFactory.create()
self.course_key = CourseKey.from_string("course-v1:edX+MOOC101+2049_T2")
self.block_keys = [
self.course_key.make_usage_key('video', text_type(number))
for number in range(5)
]
submit_completions_for_testing(self.user, self.course_key, self.block_keys)

def test_can_get_key_to_last_completed_course_block(self):
with patch('completion.utilities.visual_progress_enabled', return_value=True):
last_block_key = get_key_to_last_completed_course_block(
self.user,
self.course_key
)

expected_block_usage_key = self.course_key.make_usage_key(
"video",
text_type(4)
)
self.assertEqual(last_block_key, expected_block_usage_key)

def test_getting_last_completed_course_block_in_untouched_enrollment_throws(self):
course_key = CourseKey.from_string("edX/NotACourse/2049_T2")

with patch('completion.utilities.visual_progress_enabled', return_value=True):
with self.assertRaises(UnavailableCompletionData):
get_key_to_last_completed_course_block(self.user, course_key)
31 changes: 31 additions & 0 deletions completion/utilities.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
'''
File is the public API for BlockCompletion. It is the interface that prevents
external users from depending on the BlockCompletion model. Methods working with
the BlockCompletion model should be included here.
'''
from __future__ import unicode_literals, absolute_import

from .exceptions import UnavailableCompletionData
from .models import BlockCompletion
from .waffle import visual_progress_enabled


def get_key_to_last_completed_course_block(user, course_key):
'''
Returns the last block a "user" completed in a course (stated as "course_key").
raises UnavailableCompletionData when the user has not completed blocks in
the course.
raises UnavailableCompletionData when the visual progress waffle flag is
disabled.
'''
if not visual_progress_enabled(course_key):
raise UnavailableCompletionData(course_key)

last_completed_block = BlockCompletion.get_latest_block_completed(user, course_key)

if last_completed_block is not None:
return last_completed_block.block_key

raise UnavailableCompletionData(course_key)
3 changes: 1 addition & 2 deletions requirements/dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ diff-cover==1.0.2
distlib==0.2.6 # via caniusepython3
djangorestframework==3.6.3
django-model-utils==3.0.0
edx-i18n-tools==0.4.3
edx-opaque-keys[django]==0.4.2
first==2.0.1 # via pip-tools
futures==3.2.0 ; python_version == "2.7"
Expand All @@ -26,7 +25,7 @@ jinja2-pluralize==0.3.0 # via diff-cover
jinja2==2.10 # via diff-cover, jinja2-pluralize
markupsafe==1.0 # via jinja2
packaging==16.8 # via caniusepython3
path.py==10.5 # via edx-i18n-tools
path.py>=8.2.1 # via edx-i18n-tools
pip-tools==1.11.0
pkginfo==1.4.1 # via twine
pluggy==0.6.0 # via tox
Expand Down

0 comments on commit a0ac2b8

Please sign in to comment.