Cleaned up milestones API references
This commit is contained in:
@@ -5,30 +5,24 @@ Utility library for working with the edx-milestones app
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext as _
|
||||
from opaque_keys import InvalidKeyError
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
|
||||
from courseware.models import StudentModule
|
||||
from opaque_keys import InvalidKeyError
|
||||
from opaque_keys.edx.keys import CourseKey, UsageKey
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
from milestones.api import (
|
||||
get_course_milestones,
|
||||
add_milestone,
|
||||
add_course_milestone,
|
||||
remove_course_milestone,
|
||||
get_course_milestones_fulfillment_paths,
|
||||
add_user_milestone,
|
||||
get_user_milestones,
|
||||
)
|
||||
from milestones.models import MilestoneRelationshipType
|
||||
from milestones.exceptions import InvalidMilestoneRelationshipTypeException
|
||||
from opaque_keys.edx.keys import UsageKey
|
||||
|
||||
NAMESPACE_CHOICES = {
|
||||
'ENTRANCE_EXAM': 'entrance_exams'
|
||||
}
|
||||
|
||||
|
||||
def get_namespace_choices():
|
||||
"""
|
||||
Return the enum to the caller
|
||||
"""
|
||||
return NAMESPACE_CHOICES
|
||||
|
||||
|
||||
def add_prerequisite_course(course_key, prerequisite_course_key):
|
||||
"""
|
||||
It would create a milestone, then it would set newly created
|
||||
@@ -36,18 +30,23 @@ def add_prerequisite_course(course_key, prerequisite_course_key):
|
||||
and it would set newly created milestone as fulfilment
|
||||
milestone for course referred by `prerequisite_course_key`.
|
||||
"""
|
||||
if settings.FEATURES.get('MILESTONES_APP', False):
|
||||
# create a milestone
|
||||
milestone = add_milestone({
|
||||
'name': _('Course {} requires {}'.format(unicode(course_key), unicode(prerequisite_course_key))),
|
||||
'namespace': unicode(prerequisite_course_key),
|
||||
'description': _('System defined milestone'),
|
||||
})
|
||||
# add requirement course milestone
|
||||
add_course_milestone(course_key, 'requires', milestone)
|
||||
if not settings.FEATURES.get('ENABLE_PREREQUISITE_COURSES', False):
|
||||
return None
|
||||
from milestones import api as milestones_api
|
||||
milestone_name = _('Course {course_id} requires {prerequisite_course_id}').format(
|
||||
course_id=unicode(course_key),
|
||||
prerequisite_course_id=unicode(prerequisite_course_key)
|
||||
)
|
||||
milestone = milestones_api.add_milestone({
|
||||
'name': milestone_name,
|
||||
'namespace': unicode(prerequisite_course_key),
|
||||
'description': _('System defined milestone'),
|
||||
})
|
||||
# add requirement course milestone
|
||||
milestones_api.add_course_milestone(course_key, 'requires', milestone)
|
||||
|
||||
# add fulfillment course milestone
|
||||
add_course_milestone(prerequisite_course_key, 'fulfills', milestone)
|
||||
# add fulfillment course milestone
|
||||
milestones_api.add_course_milestone(prerequisite_course_key, 'fulfills', milestone)
|
||||
|
||||
|
||||
def remove_prerequisite_course(course_key, milestone):
|
||||
@@ -55,11 +54,13 @@ def remove_prerequisite_course(course_key, milestone):
|
||||
It would remove pre-requisite course milestone for course
|
||||
referred by `course_key`.
|
||||
"""
|
||||
if settings.FEATURES.get('MILESTONES_APP', False):
|
||||
remove_course_milestone(
|
||||
course_key,
|
||||
milestone,
|
||||
)
|
||||
if not settings.FEATURES.get('ENABLE_PREREQUISITE_COURSES', False):
|
||||
return None
|
||||
from milestones import api as milestones_api
|
||||
milestones_api.remove_course_milestone(
|
||||
course_key,
|
||||
milestone,
|
||||
)
|
||||
|
||||
|
||||
def set_prerequisite_courses(course_key, prerequisite_course_keys):
|
||||
@@ -69,18 +70,20 @@ def set_prerequisite_courses(course_key, prerequisite_course_keys):
|
||||
To only remove course milestones pass `course_key` and empty list or
|
||||
None as `prerequisite_course_keys` .
|
||||
"""
|
||||
if settings.FEATURES.get('MILESTONES_APP', False):
|
||||
#remove any existing requirement milestones with this pre-requisite course as requirement
|
||||
course_milestones = get_course_milestones(course_key=course_key, relationship="requires")
|
||||
if course_milestones:
|
||||
for milestone in course_milestones:
|
||||
remove_prerequisite_course(course_key, milestone)
|
||||
if not settings.FEATURES.get('ENABLE_PREREQUISITE_COURSES', False):
|
||||
return None
|
||||
from milestones import api as milestones_api
|
||||
#remove any existing requirement milestones with this pre-requisite course as requirement
|
||||
course_milestones = milestones_api.get_course_milestones(course_key=course_key, relationship="requires")
|
||||
if course_milestones:
|
||||
for milestone in course_milestones:
|
||||
remove_prerequisite_course(course_key, milestone)
|
||||
|
||||
# add milestones if pre-requisite course is selected
|
||||
if prerequisite_course_keys:
|
||||
for prerequisite_course_key_string in prerequisite_course_keys:
|
||||
prerequisite_course_key = CourseKey.from_string(prerequisite_course_key_string)
|
||||
add_prerequisite_course(course_key, prerequisite_course_key)
|
||||
# add milestones if pre-requisite course is selected
|
||||
if prerequisite_course_keys:
|
||||
for prerequisite_course_key_string in prerequisite_course_keys:
|
||||
prerequisite_course_key = CourseKey.from_string(prerequisite_course_key_string)
|
||||
add_prerequisite_course(course_key, prerequisite_course_key)
|
||||
|
||||
|
||||
def get_pre_requisite_courses_not_completed(user, enrolled_courses):
|
||||
@@ -91,10 +94,11 @@ def get_pre_requisite_courses_not_completed(user, enrolled_courses):
|
||||
prerequisite courses yet to be completed.
|
||||
"""
|
||||
pre_requisite_courses = {}
|
||||
if settings.FEATURES.get('ENABLE_PREREQUISITE_COURSES'):
|
||||
if settings.FEATURES.get('ENABLE_PREREQUISITE_COURSES', False):
|
||||
from milestones import api as milestones_api
|
||||
for course_key in enrolled_courses:
|
||||
required_courses = []
|
||||
fulfilment_paths = get_course_milestones_fulfillment_paths(course_key, {'id': user.id})
|
||||
fulfilment_paths = milestones_api.get_course_milestones_fulfillment_paths(course_key, {'id': user.id})
|
||||
for milestone_key, milestone_value in fulfilment_paths.items(): # pylint: disable=unused-variable
|
||||
for key, value in milestone_value.items():
|
||||
if key == 'courses' and value:
|
||||
@@ -146,10 +150,12 @@ def fulfill_course_milestone(course_key, user):
|
||||
Marks the course specified by the given course_key as complete for the given user.
|
||||
If any other courses require this course as a prerequisite, their milestones will be appropriately updated.
|
||||
"""
|
||||
if settings.FEATURES.get('MILESTONES_APP', False):
|
||||
course_milestones = get_course_milestones(course_key=course_key, relationship="fulfills")
|
||||
for milestone in course_milestones:
|
||||
add_user_milestone({'id': user.id}, milestone)
|
||||
if not settings.FEATURES.get('MILESTONES_APP', False):
|
||||
return None
|
||||
from milestones import api as milestones_api
|
||||
course_milestones = milestones_api.get_course_milestones(course_key=course_key, relationship="fulfills")
|
||||
for milestone in course_milestones:
|
||||
milestones_api.add_user_milestone({'id': user.id}, milestone)
|
||||
|
||||
|
||||
def get_required_content(course, user):
|
||||
@@ -159,9 +165,12 @@ def get_required_content(course, user):
|
||||
"""
|
||||
required_content = []
|
||||
if settings.FEATURES.get('MILESTONES_APP', False):
|
||||
from milestones import api as milestones_api
|
||||
from milestones.exceptions import InvalidMilestoneRelationshipTypeException
|
||||
|
||||
# Get all of the outstanding milestones for this course, for this user
|
||||
try:
|
||||
milestone_paths = get_course_milestones_fulfillment_paths(
|
||||
milestone_paths = milestones_api.get_course_milestones_fulfillment_paths(
|
||||
unicode(course.id),
|
||||
serialize_user(user)
|
||||
)
|
||||
@@ -221,8 +230,10 @@ def milestones_achieved_by_user(user, namespace):
|
||||
"""
|
||||
It would fetch list of milestones completed by user
|
||||
"""
|
||||
if settings.FEATURES.get('MILESTONES_APP', False):
|
||||
return get_user_milestones({'id': user.id}, namespace)
|
||||
if not settings.FEATURES.get('MILESTONES_APP', False):
|
||||
return None
|
||||
from milestones import api as milestones_api
|
||||
return milestones_api.get_user_milestones({'id': user.id}, namespace)
|
||||
|
||||
|
||||
def is_valid_course_key(key):
|
||||
@@ -240,9 +251,11 @@ def seed_milestone_relationship_types():
|
||||
"""
|
||||
Helper method to pre-populate MRTs so the tests can run
|
||||
"""
|
||||
if settings.FEATURES.get('MILESTONES_APP', False):
|
||||
MilestoneRelationshipType.objects.create(name='requires')
|
||||
MilestoneRelationshipType.objects.create(name='fulfills')
|
||||
if not settings.FEATURES.get('MILESTONES_APP', False):
|
||||
return None
|
||||
from milestones.models import MilestoneRelationshipType
|
||||
MilestoneRelationshipType.objects.create(name='requires')
|
||||
MilestoneRelationshipType.objects.create(name='fulfills')
|
||||
|
||||
|
||||
def generate_milestone_namespace(namespace, course_key=None):
|
||||
@@ -261,3 +274,106 @@ def serialize_user(user):
|
||||
return {
|
||||
'id': user.id,
|
||||
}
|
||||
|
||||
|
||||
def add_milestone(milestone_data):
|
||||
"""
|
||||
Client API operation adapter/wrapper
|
||||
"""
|
||||
if not settings.FEATURES.get('MILESTONES_APP', False):
|
||||
return None
|
||||
from milestones import api as milestones_api
|
||||
return milestones_api.add_milestone(milestone_data)
|
||||
|
||||
|
||||
def get_milestones(namespace):
|
||||
"""
|
||||
Client API operation adapter/wrapper
|
||||
"""
|
||||
if not settings.FEATURES.get('MILESTONES_APP', False):
|
||||
return []
|
||||
from milestones import api as milestones_api
|
||||
return milestones_api.get_milestones(namespace)
|
||||
|
||||
|
||||
def get_milestone_relationship_types():
|
||||
"""
|
||||
Client API operation adapter/wrapper
|
||||
"""
|
||||
if not settings.FEATURES.get('MILESTONES_APP', False):
|
||||
return {}
|
||||
from milestones import api as milestones_api
|
||||
return milestones_api.get_milestone_relationship_types()
|
||||
|
||||
|
||||
def add_course_milestone(course_id, relationship, milestone):
|
||||
"""
|
||||
Client API operation adapter/wrapper
|
||||
"""
|
||||
if not settings.FEATURES.get('MILESTONES_APP', False):
|
||||
return None
|
||||
from milestones import api as milestones_api
|
||||
return milestones_api.add_course_milestone(course_id, relationship, milestone)
|
||||
|
||||
|
||||
def get_course_milestones(course_id):
|
||||
"""
|
||||
Client API operation adapter/wrapper
|
||||
"""
|
||||
if not settings.FEATURES.get('MILESTONES_APP', False):
|
||||
return []
|
||||
from milestones import api as milestones_api
|
||||
return milestones_api.get_course_milestones(course_id)
|
||||
|
||||
|
||||
def add_course_content_milestone(course_id, content_id, relationship, milestone):
|
||||
"""
|
||||
Client API operation adapter/wrapper
|
||||
"""
|
||||
if not settings.FEATURES.get('MILESTONES_APP', False):
|
||||
return None
|
||||
from milestones import api as milestones_api
|
||||
return milestones_api.add_course_content_milestone(course_id, content_id, relationship, milestone)
|
||||
|
||||
|
||||
def get_course_content_milestones(course_id, content_id, relationship):
|
||||
"""
|
||||
Client API operation adapter/wrapper
|
||||
"""
|
||||
if not settings.FEATURES.get('MILESTONES_APP', False):
|
||||
return []
|
||||
from milestones import api as milestones_api
|
||||
return milestones_api.get_course_content_milestones(course_id, content_id, relationship)
|
||||
|
||||
|
||||
def remove_content_references(content_id):
|
||||
"""
|
||||
Client API operation adapter/wrapper
|
||||
"""
|
||||
if not settings.FEATURES.get('MILESTONES_APP', False):
|
||||
return None
|
||||
from milestones import api as milestones_api
|
||||
return milestones_api.remove_content_references(content_id)
|
||||
|
||||
|
||||
def get_course_milestones_fulfillment_paths(course_id, user_id):
|
||||
"""
|
||||
Client API operation adapter/wrapper
|
||||
"""
|
||||
if not settings.FEATURES.get('MILESTONES_APP', False):
|
||||
return None
|
||||
from milestones import api as milestones_api
|
||||
return milestones_api.get_course_milestones_fulfillment_paths(
|
||||
course_id,
|
||||
user_id
|
||||
)
|
||||
|
||||
|
||||
def add_user_milestone(user, milestone):
|
||||
"""
|
||||
Client API operation adapter/wrapper
|
||||
"""
|
||||
if not settings.FEATURES.get('MILESTONES_APP', False):
|
||||
return None
|
||||
from milestones import api as milestones_api
|
||||
return milestones_api.add_user_milestone(user, milestone)
|
||||
|
||||
87
common/djangoapps/util/tests/test_milestones_helpers.py
Normal file
87
common/djangoapps/util/tests/test_milestones_helpers.py
Normal file
@@ -0,0 +1,87 @@
|
||||
"""
|
||||
Tests for the milestones helpers library, which is the integration point for the edx_milestones API
|
||||
"""
|
||||
|
||||
from mock import patch
|
||||
from util import milestones_helpers
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
|
||||
@patch.dict('django.conf.settings.FEATURES', {'MILESTONES_APP': False})
|
||||
class MilestonesHelpersTestCase(ModuleStoreTestCase):
|
||||
"""
|
||||
Main test suite for Milestones API client library
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Test case scaffolding
|
||||
"""
|
||||
super(MilestonesHelpersTestCase, self).setUp(create_user=False)
|
||||
self.course = CourseFactory.create(
|
||||
metadata={
|
||||
'entrance_exam_enabled': True,
|
||||
}
|
||||
)
|
||||
|
||||
self.user = {'id': '123'}
|
||||
|
||||
self.milestone = {
|
||||
'name': 'Test Milestone',
|
||||
'namespace': 'doesnt.matter',
|
||||
'description': 'Testing Milestones Helpers Library',
|
||||
}
|
||||
|
||||
def test_add_milestone_returns_none_when_app_disabled(self):
|
||||
response = milestones_helpers.add_milestone(milestone_data=self.milestone)
|
||||
self.assertIsNone(response)
|
||||
|
||||
def test_get_milestones_returns_none_when_app_disabled(self):
|
||||
response = milestones_helpers.get_milestones(namespace="whatever")
|
||||
self.assertEqual(len(response), 0)
|
||||
|
||||
def test_get_milestone_relationship_types_returns_none_when_app_disabled(self):
|
||||
response = milestones_helpers.get_milestone_relationship_types()
|
||||
self.assertEqual(len(response), 0)
|
||||
|
||||
def test_add_course_milestone_returns_none_when_app_disabled(self):
|
||||
response = milestones_helpers.add_course_milestone(unicode(self.course.id), 'requires', self.milestone)
|
||||
self.assertIsNone(response)
|
||||
|
||||
def test_get_course_milestones_returns_none_when_app_disabled(self):
|
||||
response = milestones_helpers.get_course_milestones(unicode(self.course.id))
|
||||
self.assertEqual(len(response), 0)
|
||||
|
||||
def test_add_course_content_milestone_returns_none_when_app_disabled(self):
|
||||
response = milestones_helpers.add_course_content_milestone(
|
||||
unicode(self.course.id),
|
||||
'i4x://any/content/id',
|
||||
'requires',
|
||||
self.milestone
|
||||
)
|
||||
self.assertIsNone(response)
|
||||
|
||||
def test_get_course_content_milestones_returns_none_when_app_disabled(self):
|
||||
response = milestones_helpers.get_course_content_milestones(
|
||||
unicode(self.course.id),
|
||||
'i4x://doesnt/matter/for/this/test',
|
||||
'requires'
|
||||
)
|
||||
self.assertEqual(len(response), 0)
|
||||
|
||||
def test_remove_content_references_returns_none_when_app_disabled(self):
|
||||
response = milestones_helpers.remove_content_references("i4x://any/content/id/will/do")
|
||||
self.assertIsNone(response)
|
||||
|
||||
def test_get_namespace_choices_returns_values_when_app_disabled(self):
|
||||
response = milestones_helpers.get_namespace_choices()
|
||||
self.assertIn('ENTRANCE_EXAM', response)
|
||||
|
||||
def test_get_course_milestones_fulfillment_paths_returns_none_when_app_disabled(self):
|
||||
response = milestones_helpers.get_course_milestones_fulfillment_paths(unicode(self.course.id), self.user)
|
||||
self.assertIsNone(response)
|
||||
|
||||
def test_add_user_milestone_returns_none_when_app_disabled(self):
|
||||
response = milestones_helpers.add_user_milestone(self.user, self.milestone)
|
||||
self.assertIsNone(response)
|
||||
Reference in New Issue
Block a user