Commit cd053913 by Matt Drayer

Cleaned up milestones API references

parent cce00e69
...@@ -6,6 +6,7 @@ import json ...@@ -6,6 +6,7 @@ import json
import copy import copy
import mock import mock
from mock import patch from mock import patch
import unittest
from django.utils.timezone import UTC from django.utils.timezone import UTC
from django.test.utils import override_settings from django.test.utils import override_settings
...@@ -139,19 +140,9 @@ class CourseDetailsTestCase(CourseTestCase): ...@@ -139,19 +140,9 @@ class CourseDetailsTestCase(CourseTestCase):
self.assertNotContains(response, "Course Introduction Video") self.assertNotContains(response, "Course Introduction Video")
self.assertNotContains(response, "Requirements") self.assertNotContains(response, "Requirements")
def _seed_milestone_relationship_types(self): @unittest.skipUnless(settings.FEATURES.get('ENTRANCE_EXAMS', False), True)
"""
Helper method to prepopulate MRTs so the tests can run
Note the settings check -- exams feature must be enabled for the tests to run correctly
"""
if settings.FEATURES.get('ENTRANCE_EXAMS', False):
from milestones.models import MilestoneRelationshipType
MilestoneRelationshipType.objects.create(name='requires')
MilestoneRelationshipType.objects.create(name='fulfills')
@patch.dict(settings.FEATURES, {'ENTRANCE_EXAMS': True})
def test_entrance_exam_created_updated_and_deleted_successfully(self): def test_entrance_exam_created_updated_and_deleted_successfully(self):
self._seed_milestone_relationship_types() seed_milestone_relationship_types()
settings_details_url = get_url(self.course.id) settings_details_url = get_url(self.course.id)
data = { data = {
'entrance_exam_enabled': 'true', 'entrance_exam_enabled': 'true',
...@@ -196,13 +187,13 @@ class CourseDetailsTestCase(CourseTestCase): ...@@ -196,13 +187,13 @@ class CourseDetailsTestCase(CourseTestCase):
self.assertFalse(course.entrance_exam_enabled) self.assertFalse(course.entrance_exam_enabled)
self.assertEquals(course.entrance_exam_minimum_score_pct, None) self.assertEquals(course.entrance_exam_minimum_score_pct, None)
@patch.dict(settings.FEATURES, {'ENTRANCE_EXAMS': True}) @unittest.skipUnless(settings.FEATURES.get('ENTRANCE_EXAMS', False), True)
def test_entrance_exam_store_default_min_score(self): def test_entrance_exam_store_default_min_score(self):
""" """
test that creating an entrance exam should store the default value, if key missing in json request test that creating an entrance exam should store the default value, if key missing in json request
or entrance_exam_minimum_score_pct is an empty string or entrance_exam_minimum_score_pct is an empty string
""" """
self._seed_milestone_relationship_types() seed_milestone_relationship_types()
settings_details_url = get_url(self.course.id) settings_details_url = get_url(self.course.id)
test_data_1 = { test_data_1 = {
'entrance_exam_enabled': 'true', 'entrance_exam_enabled': 'true',
......
...@@ -2,22 +2,22 @@ ...@@ -2,22 +2,22 @@
Entrance Exams view module -- handles all requests related to entrance exam management via Studio Entrance Exams view module -- handles all requests related to entrance exam management via Studio
Intended to be utilized as an AJAX callback handler, versus a proper view/screen Intended to be utilized as an AJAX callback handler, versus a proper view/screen
""" """
from functools import wraps
import json import json
import logging import logging
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django_future.csrf import ensure_csrf_cookie from django_future.csrf import ensure_csrf_cookie
from django.http import HttpResponse from django.http import HttpResponse, HttpResponseBadRequest
from django.test import RequestFactory from django.test import RequestFactory
from contentstore.views.helpers import create_xblock from contentstore.views.helpers import create_xblock
from contentstore.views.item import delete_item from contentstore.views.item import delete_item
from milestones import api as milestones_api
from models.settings.course_metadata import CourseMetadata from models.settings.course_metadata import CourseMetadata
from opaque_keys.edx.keys import CourseKey, UsageKey from opaque_keys.edx.keys import CourseKey, UsageKey
from opaque_keys import InvalidKeyError from opaque_keys import InvalidKeyError
from student.auth import has_course_author_access from student.auth import has_course_author_access
from util.milestones_helpers import generate_milestone_namespace, NAMESPACE_CHOICES from util import milestones_helpers
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore.exceptions import ItemNotFoundError
from django.conf import settings from django.conf import settings
...@@ -40,8 +40,24 @@ def _get_default_entrance_exam_minimum_pct(): ...@@ -40,8 +40,24 @@ def _get_default_entrance_exam_minimum_pct():
return entrance_exam_minimum_score_pct return entrance_exam_minimum_score_pct
# pylint: disable=missing-docstring
def check_feature_enabled(feature_name):
"""
Ensure the specified feature is turned on. Return an HTTP 400 code if not.
"""
def _check_feature_enabled(view_func):
def _decorator(request, *args, **kwargs):
# Deny access if the entrance exam feature is disabled
if not settings.FEATURES.get(feature_name, False):
return HttpResponseBadRequest()
return view_func(request, *args, **kwargs)
return wraps(view_func)(_decorator)
return _check_feature_enabled
@login_required @login_required
@ensure_csrf_cookie @ensure_csrf_cookie
@check_feature_enabled(feature_name='ENTRANCE_EXAMS')
def entrance_exam(request, course_key_string): def entrance_exam(request, course_key_string):
""" """
The restful handler for entrance exams. The restful handler for entrance exams.
...@@ -88,6 +104,7 @@ def entrance_exam(request, course_key_string): ...@@ -88,6 +104,7 @@ def entrance_exam(request, course_key_string):
return HttpResponse(status=405) return HttpResponse(status=405)
@check_feature_enabled(feature_name='ENTRANCE_EXAMS')
def create_entrance_exam(request, course_key, entrance_exam_minimum_score_pct): def create_entrance_exam(request, course_key, entrance_exam_minimum_score_pct):
""" """
api method to create an entrance exam. api method to create an entrance exam.
...@@ -150,27 +167,28 @@ def _create_entrance_exam(request, course_key, entrance_exam_minimum_score_pct=N ...@@ -150,27 +167,28 @@ def _create_entrance_exam(request, course_key, entrance_exam_minimum_score_pct=N
) )
# Add an entrance exam milestone if one does not already exist # Add an entrance exam milestone if one does not already exist
milestone_namespace = generate_milestone_namespace( namespace_choices = milestones_helpers.get_namespace_choices()
NAMESPACE_CHOICES['ENTRANCE_EXAM'], milestone_namespace = milestones_helpers.generate_milestone_namespace(
namespace_choices.get('ENTRANCE_EXAM'),
course_key course_key
) )
milestones = milestones_api.get_milestones(milestone_namespace) milestones = milestones_helpers.get_milestones(milestone_namespace)
if len(milestones): if len(milestones):
milestone = milestones[0] milestone = milestones[0]
else: else:
description = 'Autogenerated during {} entrance exam creation.'.format(unicode(course.id)) description = 'Autogenerated during {} entrance exam creation.'.format(unicode(course.id))
milestone = milestones_api.add_milestone({ milestone = milestones_helpers.add_milestone({
'name': 'Completed Course Entrance Exam', 'name': 'Completed Course Entrance Exam',
'namespace': milestone_namespace, 'namespace': milestone_namespace,
'description': description 'description': description
}) })
relationship_types = milestones_api.get_milestone_relationship_types() relationship_types = milestones_helpers.get_milestone_relationship_types()
milestones_api.add_course_milestone( milestones_helpers.add_course_milestone(
unicode(course.id), unicode(course.id),
relationship_types['REQUIRES'], relationship_types['REQUIRES'],
milestone milestone
) )
milestones_api.add_course_content_milestone( milestones_helpers.add_course_content_milestone(
unicode(course.id), unicode(course.id),
unicode(created_block.location), unicode(created_block.location),
relationship_types['FULFILLS'], relationship_types['FULFILLS'],
...@@ -202,6 +220,7 @@ def _get_entrance_exam(request, course_key): # pylint: disable=W0613 ...@@ -202,6 +220,7 @@ def _get_entrance_exam(request, course_key): # pylint: disable=W0613
return HttpResponse(status=404) return HttpResponse(status=404)
@check_feature_enabled(feature_name='ENTRANCE_EXAMS')
def update_entrance_exam(request, course_key, exam_data): def update_entrance_exam(request, course_key, exam_data):
""" """
Operation to update course fields pertaining to entrance exams Operation to update course fields pertaining to entrance exams
...@@ -215,6 +234,7 @@ def update_entrance_exam(request, course_key, exam_data): ...@@ -215,6 +234,7 @@ def update_entrance_exam(request, course_key, exam_data):
CourseMetadata.update_from_dict(metadata, course, request.user) CourseMetadata.update_from_dict(metadata, course, request.user)
@check_feature_enabled(feature_name='ENTRANCE_EXAMS')
def delete_entrance_exam(request, course_key): def delete_entrance_exam(request, course_key):
""" """
api method to delete an entrance exam api method to delete an entrance exam
...@@ -238,7 +258,7 @@ def _delete_entrance_exam(request, course_key): ...@@ -238,7 +258,7 @@ def _delete_entrance_exam(request, course_key):
for course_child in course_children: for course_child in course_children:
if course_child.is_entrance_exam: if course_child.is_entrance_exam:
delete_item(request, course_child.scope_ids.usage_id) delete_item(request, course_child.scope_ids.usage_id)
milestones_api.remove_content_references(unicode(course_child.scope_ids.usage_id)) milestones_helpers.remove_content_references(unicode(course_child.scope_ids.usage_id))
# Reset the entrance exam flags on the course # Reset the entrance exam flags on the course
# Reload the course so we have the latest state # Reload the course so we have the latest state
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
Test module for Entrance Exams AJAX callback handler workflows Test module for Entrance Exams AJAX callback handler workflows
""" """
import json import json
from mock import patch
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
...@@ -9,18 +10,14 @@ from django.test.client import RequestFactory ...@@ -9,18 +10,14 @@ from django.test.client import RequestFactory
from contentstore.tests.utils import AjaxEnabledTestClient, CourseTestCase from contentstore.tests.utils import AjaxEnabledTestClient, CourseTestCase
from contentstore.utils import reverse_url from contentstore.utils import reverse_url
from contentstore.views.entrance_exam import create_entrance_exam from contentstore.views.entrance_exam import create_entrance_exam, update_entrance_exam, delete_entrance_exam
from models.settings.course_grading import CourseGradingModel from models.settings.course_grading import CourseGradingModel
from models.settings.course_metadata import CourseMetadata from models.settings.course_metadata import CourseMetadata
from opaque_keys.edx.keys import UsageKey from opaque_keys.edx.keys import UsageKey
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
from util import milestones_helpers
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
if settings.FEATURES.get('MILESTONES_APP', False):
from milestones import api as milestones_api
from milestones.models import MilestoneRelationshipType
from util.milestones_helpers import serialize_user
class EntranceExamHandlerTests(CourseTestCase): class EntranceExamHandlerTests(CourseTestCase):
""" """
...@@ -36,9 +33,8 @@ class EntranceExamHandlerTests(CourseTestCase): ...@@ -36,9 +33,8 @@ class EntranceExamHandlerTests(CourseTestCase):
self.usage_key = self.course.location self.usage_key = self.course.location
self.course_url = '/course/{}'.format(unicode(self.course.id)) self.course_url = '/course/{}'.format(unicode(self.course.id))
self.exam_url = '/course/{}/entrance_exam/'.format(unicode(self.course.id)) self.exam_url = '/course/{}/entrance_exam/'.format(unicode(self.course.id))
MilestoneRelationshipType.objects.create(name='requires', active=True) milestones_helpers.seed_milestone_relationship_types()
MilestoneRelationshipType.objects.create(name='fulfills', active=True) self.milestone_relationship_types = milestones_helpers.get_milestone_relationship_types()
self.milestone_relationship_types = milestones_api.get_milestone_relationship_types()
def test_contentstore_views_entrance_exam_post(self): def test_contentstore_views_entrance_exam_post(self):
""" """
...@@ -55,8 +51,8 @@ class EntranceExamHandlerTests(CourseTestCase): ...@@ -55,8 +51,8 @@ class EntranceExamHandlerTests(CourseTestCase):
self.assertTrue(metadata['entrance_exam_enabled']) self.assertTrue(metadata['entrance_exam_enabled'])
self.assertIsNotNone(metadata['entrance_exam_minimum_score_pct']) self.assertIsNotNone(metadata['entrance_exam_minimum_score_pct'])
self.assertIsNotNone(metadata['entrance_exam_id']['value']) self.assertIsNotNone(metadata['entrance_exam_id']['value'])
self.assertTrue(len(milestones_api.get_course_milestones(unicode(self.course.id)))) self.assertTrue(len(milestones_helpers.get_course_milestones(unicode(self.course.id))))
content_milestones = milestones_api.get_course_content_milestones( content_milestones = milestones_helpers.get_course_content_milestones(
unicode(self.course.id), unicode(self.course.id),
metadata['entrance_exam_id']['value'], metadata['entrance_exam_id']['value'],
self.milestone_relationship_types['FULFILLS'] self.milestone_relationship_types['FULFILLS']
...@@ -123,12 +119,12 @@ class EntranceExamHandlerTests(CourseTestCase): ...@@ -123,12 +119,12 @@ class EntranceExamHandlerTests(CourseTestCase):
) )
user.set_password('test') user.set_password('test')
user.save() user.save()
milestones = milestones_api.get_course_milestones(unicode(self.course_key)) milestones = milestones_helpers.get_course_milestones(unicode(self.course_key))
self.assertEqual(len(milestones), 1) self.assertEqual(len(milestones), 1)
milestone_key = '{}.{}'.format(milestones[0]['namespace'], milestones[0]['name']) milestone_key = '{}.{}'.format(milestones[0]['namespace'], milestones[0]['name'])
paths = milestones_api.get_course_milestones_fulfillment_paths( paths = milestones_helpers.get_course_milestones_fulfillment_paths(
unicode(self.course_key), unicode(self.course_key),
serialize_user(user) milestones_helpers.serialize_user(user)
) )
# What we have now is a course milestone requirement and no valid fulfillment # What we have now is a course milestone requirement and no valid fulfillment
...@@ -250,3 +246,22 @@ class EntranceExamHandlerTests(CourseTestCase): ...@@ -250,3 +246,22 @@ class EntranceExamHandlerTests(CourseTestCase):
resp = create_entrance_exam(request, self.course.id, None) resp = create_entrance_exam(request, self.course.id, None)
self.assertEqual(resp.status_code, 201) self.assertEqual(resp.status_code, 201)
@patch.dict('django.conf.settings.FEATURES', {'ENTRANCE_EXAMS': False})
def test_entrance_exam_feature_flag_gating(self):
user = UserFactory()
user.is_staff = True
request = RequestFactory()
request.user = user
resp = self.client.get(self.exam_url)
self.assertEqual(resp.status_code, 400)
resp = create_entrance_exam(request, self.course.id, None)
self.assertEqual(resp.status_code, 400)
resp = delete_entrance_exam(request, self.course.id)
self.assertEqual(resp.status_code, 400)
# No return, so we'll just ensure no exception is thrown
update_entrance_exam(request, self.course.id, {})
...@@ -5,30 +5,24 @@ Utility library for working with the edx-milestones app ...@@ -5,30 +5,24 @@ Utility library for working with the edx-milestones app
from django.conf import settings from django.conf import settings
from django.utils.translation import ugettext as _ 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 courseware.models import StudentModule
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey, UsageKey
from xmodule.modulestore.django import modulestore 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 = { NAMESPACE_CHOICES = {
'ENTRANCE_EXAM': 'entrance_exams' '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): def add_prerequisite_course(course_key, prerequisite_course_key):
""" """
It would create a milestone, then it would set newly created It would create a milestone, then it would set newly created
...@@ -36,18 +30,23 @@ def add_prerequisite_course(course_key, prerequisite_course_key): ...@@ -36,18 +30,23 @@ def add_prerequisite_course(course_key, prerequisite_course_key):
and it would set newly created milestone as fulfilment and it would set newly created milestone as fulfilment
milestone for course referred by `prerequisite_course_key`. milestone for course referred by `prerequisite_course_key`.
""" """
if settings.FEATURES.get('MILESTONES_APP', False): if not settings.FEATURES.get('ENABLE_PREREQUISITE_COURSES', False):
# create a milestone return None
milestone = add_milestone({ from milestones import api as milestones_api
'name': _('Course {} requires {}'.format(unicode(course_key), unicode(prerequisite_course_key))), milestone_name = _('Course {course_id} requires {prerequisite_course_id}').format(
'namespace': unicode(prerequisite_course_key), course_id=unicode(course_key),
'description': _('System defined milestone'), prerequisite_course_id=unicode(prerequisite_course_key)
}) )
# add requirement course milestone milestone = milestones_api.add_milestone({
add_course_milestone(course_key, 'requires', 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 fulfillment course milestone
add_course_milestone(prerequisite_course_key, 'fulfills', milestone) milestones_api.add_course_milestone(prerequisite_course_key, 'fulfills', milestone)
def remove_prerequisite_course(course_key, milestone): def remove_prerequisite_course(course_key, milestone):
...@@ -55,11 +54,13 @@ 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 It would remove pre-requisite course milestone for course
referred by `course_key`. referred by `course_key`.
""" """
if settings.FEATURES.get('MILESTONES_APP', False): if not settings.FEATURES.get('ENABLE_PREREQUISITE_COURSES', False):
remove_course_milestone( return None
course_key, from milestones import api as milestones_api
milestone, milestones_api.remove_course_milestone(
) course_key,
milestone,
)
def set_prerequisite_courses(course_key, prerequisite_course_keys): def set_prerequisite_courses(course_key, prerequisite_course_keys):
...@@ -69,18 +70,20 @@ 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 To only remove course milestones pass `course_key` and empty list or
None as `prerequisite_course_keys` . None as `prerequisite_course_keys` .
""" """
if settings.FEATURES.get('MILESTONES_APP', False): if not settings.FEATURES.get('ENABLE_PREREQUISITE_COURSES', False):
#remove any existing requirement milestones with this pre-requisite course as requirement return None
course_milestones = get_course_milestones(course_key=course_key, relationship="requires") from milestones import api as milestones_api
if course_milestones: #remove any existing requirement milestones with this pre-requisite course as requirement
for milestone in course_milestones: course_milestones = milestones_api.get_course_milestones(course_key=course_key, relationship="requires")
remove_prerequisite_course(course_key, milestone) if course_milestones:
for milestone in course_milestones:
remove_prerequisite_course(course_key, milestone)
# add milestones if pre-requisite course is selected # add milestones if pre-requisite course is selected
if prerequisite_course_keys: if prerequisite_course_keys:
for prerequisite_course_key_string in prerequisite_course_keys: for prerequisite_course_key_string in prerequisite_course_keys:
prerequisite_course_key = CourseKey.from_string(prerequisite_course_key_string) prerequisite_course_key = CourseKey.from_string(prerequisite_course_key_string)
add_prerequisite_course(course_key, prerequisite_course_key) add_prerequisite_course(course_key, prerequisite_course_key)
def get_pre_requisite_courses_not_completed(user, enrolled_courses): def get_pre_requisite_courses_not_completed(user, enrolled_courses):
...@@ -91,10 +94,11 @@ 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. prerequisite courses yet to be completed.
""" """
pre_requisite_courses = {} 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: for course_key in enrolled_courses:
required_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 milestone_key, milestone_value in fulfilment_paths.items(): # pylint: disable=unused-variable
for key, value in milestone_value.items(): for key, value in milestone_value.items():
if key == 'courses' and value: if key == 'courses' and value:
...@@ -146,10 +150,12 @@ def fulfill_course_milestone(course_key, user): ...@@ -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. 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 any other courses require this course as a prerequisite, their milestones will be appropriately updated.
""" """
if settings.FEATURES.get('MILESTONES_APP', False): if not settings.FEATURES.get('MILESTONES_APP', False):
course_milestones = get_course_milestones(course_key=course_key, relationship="fulfills") return None
for milestone in course_milestones: from milestones import api as milestones_api
add_user_milestone({'id': user.id}, milestone) 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): def get_required_content(course, user):
...@@ -159,9 +165,12 @@ def get_required_content(course, user): ...@@ -159,9 +165,12 @@ def get_required_content(course, user):
""" """
required_content = [] required_content = []
if settings.FEATURES.get('MILESTONES_APP', False): 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 # Get all of the outstanding milestones for this course, for this user
try: try:
milestone_paths = get_course_milestones_fulfillment_paths( milestone_paths = milestones_api.get_course_milestones_fulfillment_paths(
unicode(course.id), unicode(course.id),
serialize_user(user) serialize_user(user)
) )
...@@ -221,8 +230,10 @@ def milestones_achieved_by_user(user, namespace): ...@@ -221,8 +230,10 @@ def milestones_achieved_by_user(user, namespace):
""" """
It would fetch list of milestones completed by user It would fetch list of milestones completed by user
""" """
if settings.FEATURES.get('MILESTONES_APP', False): if not settings.FEATURES.get('MILESTONES_APP', False):
return get_user_milestones({'id': user.id}, namespace) 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): def is_valid_course_key(key):
...@@ -240,9 +251,11 @@ def seed_milestone_relationship_types(): ...@@ -240,9 +251,11 @@ def seed_milestone_relationship_types():
""" """
Helper method to pre-populate MRTs so the tests can run Helper method to pre-populate MRTs so the tests can run
""" """
if settings.FEATURES.get('MILESTONES_APP', False): if not settings.FEATURES.get('MILESTONES_APP', False):
MilestoneRelationshipType.objects.create(name='requires') return None
MilestoneRelationshipType.objects.create(name='fulfills') from milestones.models import MilestoneRelationshipType
MilestoneRelationshipType.objects.create(name='requires')
MilestoneRelationshipType.objects.create(name='fulfills')
def generate_milestone_namespace(namespace, course_key=None): def generate_milestone_namespace(namespace, course_key=None):
...@@ -261,3 +274,106 @@ def serialize_user(user): ...@@ -261,3 +274,106 @@ def serialize_user(user):
return { return {
'id': user.id, '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)
"""
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)
...@@ -63,10 +63,8 @@ from xmodule.x_module import XModuleDescriptor ...@@ -63,10 +63,8 @@ from xmodule.x_module import XModuleDescriptor
from xblock_django.user_service import DjangoXBlockUserService from xblock_django.user_service import DjangoXBlockUserService
from util.json_request import JsonResponse from util.json_request import JsonResponse
from util.sandboxing import can_execute_unsafe_code, get_python_lib_zip from util.sandboxing import can_execute_unsafe_code, get_python_lib_zip
if settings.FEATURES.get('MILESTONES_APP', False): from util import milestones_helpers
from milestones import api as milestones_api from util.module_utils import yield_dynamic_descriptor_descendents
from util.milestones_helpers import calculate_entrance_exam_score, get_required_content
from util.module_utils import yield_dynamic_descriptor_descendents
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -136,14 +134,14 @@ def toc_for_course(request, course, active_chapter, active_section, field_data_c ...@@ -136,14 +134,14 @@ def toc_for_course(request, course, active_chapter, active_section, field_data_c
return None return None
# Check to see if the course is gated on milestone-required content (such as an Entrance Exam) # Check to see if the course is gated on milestone-required content (such as an Entrance Exam)
required_content = get_required_content(course, request.user) required_content = milestones_helpers.get_required_content(course, request.user)
chapters = list() chapters = list()
for chapter in course_module.get_display_items(): for chapter in course_module.get_display_items():
# Only show required content, if there is required content # Only show required content, if there is required content
# chapter.hide_from_toc is read-only (boo) # chapter.hide_from_toc is read-only (boo)
local_hide_from_toc = False local_hide_from_toc = False
if len(required_content): if required_content:
if unicode(chapter.location) not in required_content: if unicode(chapter.location) not in required_content:
local_hide_from_toc = True local_hide_from_toc = True
...@@ -375,7 +373,7 @@ def get_module_system_for_user(user, field_data_cache, ...@@ -375,7 +373,7 @@ def get_module_system_for_user(user, field_data_cache,
inner_get_module inner_get_module
) )
exam_modules = [module for module in exam_module_generators] exam_modules = [module for module in exam_module_generators]
exam_score = calculate_entrance_exam_score(user, course_descriptor, exam_modules) exam_score = milestones_helpers.calculate_entrance_exam_score(user, course_descriptor, exam_modules)
return exam_score return exam_score
def _fulfill_content_milestones(user, course_key, content_key): def _fulfill_content_milestones(user, course_key, content_key):
...@@ -394,8 +392,8 @@ def get_module_system_for_user(user, field_data_cache, ...@@ -394,8 +392,8 @@ def get_module_system_for_user(user, field_data_cache,
exam_pct = _calculate_entrance_exam_score(user, course) exam_pct = _calculate_entrance_exam_score(user, course)
if exam_pct >= course.entrance_exam_minimum_score_pct: if exam_pct >= course.entrance_exam_minimum_score_pct:
exam_key = UsageKey.from_string(course.entrance_exam_id) exam_key = UsageKey.from_string(course.entrance_exam_id)
relationship_types = milestones_api.get_milestone_relationship_types() relationship_types = milestones_helpers.get_milestone_relationship_types()
content_milestones = milestones_api.get_course_content_milestones( content_milestones = milestones_helpers.get_course_content_milestones(
course_key, course_key,
exam_key, exam_key,
relationship=relationship_types['FULFILLS'] relationship=relationship_types['FULFILLS']
...@@ -403,7 +401,7 @@ def get_module_system_for_user(user, field_data_cache, ...@@ -403,7 +401,7 @@ def get_module_system_for_user(user, field_data_cache,
# Add each milestone to the user's set... # Add each milestone to the user's set...
user = {'id': user.id} user = {'id': user.id}
for milestone in content_milestones: for milestone in content_milestones:
milestones_api.add_user_milestone(user, milestone) milestones_helpers.add_user_milestone(user, milestone)
def handle_grade_event(block, event_type, event): # pylint: disable=unused-argument def handle_grade_event(block, event_type, event): # pylint: disable=unused-argument
""" """
......
...@@ -9,9 +9,7 @@ from courseware.access import has_access ...@@ -9,9 +9,7 @@ from courseware.access import has_access
from student.models import CourseEnrollment, EntranceExamConfiguration from student.models import CourseEnrollment, EntranceExamConfiguration
from xmodule.tabs import CourseTabList from xmodule.tabs import CourseTabList
if settings.FEATURES.get('MILESTONES_APP', False): from util import milestones_helpers
from milestones.api import get_course_milestones_fulfillment_paths
from util.milestones_helpers import serialize_user
def get_course_tab_list(course, user): def get_course_tab_list(course, user):
...@@ -33,9 +31,9 @@ def get_course_tab_list(course, user): ...@@ -33,9 +31,9 @@ def get_course_tab_list(course, user):
entrance_exam_mode = False entrance_exam_mode = False
if settings.FEATURES.get('ENTRANCE_EXAMS', False): if settings.FEATURES.get('ENTRANCE_EXAMS', False):
if getattr(course, 'entrance_exam_enabled', False): if getattr(course, 'entrance_exam_enabled', False):
course_milestones_paths = get_course_milestones_fulfillment_paths( course_milestones_paths = milestones_helpers.get_course_milestones_fulfillment_paths(
unicode(course.id), unicode(course.id),
serialize_user(user) milestones_helpers.serialize_user(user)
) )
for __, value in course_milestones_paths.iteritems(): for __, value in course_milestones_paths.iteritems():
if len(value.get('content', [])): if len(value.get('content', [])):
......
""" """
Tests use cases related to LMS Entrance Exam behavior, such as gated content access (TOC) Tests use cases related to LMS Entrance Exam behavior, such as gated content access (TOC)
""" """
from django.conf import settings
from django.test.client import RequestFactory from django.test.client import RequestFactory
from django.test.utils import override_settings from django.test.utils import override_settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
...@@ -9,12 +10,11 @@ from courseware.model_data import FieldDataCache ...@@ -9,12 +10,11 @@ from courseware.model_data import FieldDataCache
from courseware.module_render import get_module, toc_for_course from courseware.module_render import get_module, toc_for_course
from courseware.tests.factories import UserFactory, InstructorFactory from courseware.tests.factories import UserFactory, InstructorFactory
from courseware.courses import get_entrance_exam_content_info, get_entrance_exam_score from courseware.courses import get_entrance_exam_content_info, get_entrance_exam_score
from milestones import api as milestones_api
from milestones.models import MilestoneRelationshipType from milestones.models import MilestoneRelationshipType
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, TEST_DATA_MOCK_MODULESTORE from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, TEST_DATA_MOCK_MODULESTORE
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from util.milestones_helpers import generate_milestone_namespace, NAMESPACE_CHOICES from util import milestones_helpers
from student.models import CourseEnrollment from student.models import CourseEnrollment
from mock import patch from mock import patch
import mock import mock
...@@ -23,8 +23,10 @@ import mock ...@@ -23,8 +23,10 @@ import mock
class EntranceExamTestCases(ModuleStoreTestCase): class EntranceExamTestCases(ModuleStoreTestCase):
""" """
Check that content is properly gated. Create a test course from scratch to mess with. Check that content is properly gated. Create a test course from scratch to mess with.
We typically assume that the Entrance Exam feature flag is set to True in test.py
However, the tests below are designed to execute workflows regardless of the setting
If set to False, we are essentially confirming that the workflows do not cause exceptions
""" """
def setUp(self): def setUp(self):
""" """
Test case scaffolding Test case scaffolding
...@@ -109,30 +111,31 @@ class EntranceExamTestCases(ModuleStoreTestCase): ...@@ -109,30 +111,31 @@ class EntranceExamTestCases(ModuleStoreTestCase):
category="problem", category="problem",
display_name="Exam Problem - Problem 3" display_name="Exam Problem - Problem 3"
) )
milestone_namespace = generate_milestone_namespace( if settings.FEATURES.get('ENTRANCE_EXAMS', False):
NAMESPACE_CHOICES['ENTRANCE_EXAM'], namespace_choices = milestones_helpers.get_namespace_choices()
self.course.id milestone_namespace = milestones_helpers.generate_milestone_namespace(
) namespace_choices.get('ENTRANCE_EXAM'),
self.milestone = { self.course.id
'name': 'Test Milestone', )
'namespace': milestone_namespace, self.milestone = {
'description': 'Testing Courseware Entrance Exam Chapter', 'name': 'Test Milestone',
} 'namespace': milestone_namespace,
MilestoneRelationshipType.objects.create(name='requires', active=True) 'description': 'Testing Courseware Entrance Exam Chapter',
MilestoneRelationshipType.objects.create(name='fulfills', active=True) }
self.milestone_relationship_types = milestones_api.get_milestone_relationship_types() milestones_helpers.seed_milestone_relationship_types()
self.milestone = milestones_api.add_milestone(self.milestone) self.milestone_relationship_types = milestones_helpers.get_milestone_relationship_types()
milestones_api.add_course_milestone( self.milestone = milestones_helpers.add_milestone(self.milestone)
unicode(self.course.id), milestones_helpers.add_course_milestone(
self.milestone_relationship_types['REQUIRES'], unicode(self.course.id),
self.milestone self.milestone_relationship_types['REQUIRES'],
) self.milestone
milestones_api.add_course_content_milestone( )
unicode(self.course.id), milestones_helpers.add_course_content_milestone(
unicode(self.entrance_exam.location), unicode(self.course.id),
self.milestone_relationship_types['FULFILLS'], unicode(self.entrance_exam.location),
self.milestone self.milestone_relationship_types['FULFILLS'],
) self.milestone
)
user = UserFactory() user = UserFactory()
self.request = RequestFactory() self.request = RequestFactory()
self.request.user = user self.request.user = user
...@@ -241,7 +244,8 @@ class EntranceExamTestCases(ModuleStoreTestCase): ...@@ -241,7 +244,8 @@ class EntranceExamTestCases(ModuleStoreTestCase):
'section': self.exam_1.location.name 'section': self.exam_1.location.name
}) })
resp = self.client.get(url) resp = self.client.get(url)
self.assertRedirects(resp, expected_url, status_code=302, target_status_code=200) if settings.FEATURES.get('ENTRANCE_EXAMS', False):
self.assertRedirects(resp, expected_url, status_code=302, target_status_code=200)
@patch.dict('django.conf.settings.FEATURES', {'ENTRANCE_EXAMS': False}) @patch.dict('django.conf.settings.FEATURES', {'ENTRANCE_EXAMS': False})
def test_entrance_exam_content_absence(self): def test_entrance_exam_content_absence(self):
...@@ -261,7 +265,6 @@ class EntranceExamTestCases(ModuleStoreTestCase): ...@@ -261,7 +265,6 @@ class EntranceExamTestCases(ModuleStoreTestCase):
self.assertNotIn('Exam Problem - Problem 1', resp.content) self.assertNotIn('Exam Problem - Problem 1', resp.content)
self.assertNotIn('Exam Problem - Problem 2', resp.content) self.assertNotIn('Exam Problem - Problem 2', resp.content)
@patch.dict('django.conf.settings.FEATURES', {'ENTRANCE_EXAMS': True})
def test_entrance_exam_content_presence(self): def test_entrance_exam_content_presence(self):
""" """
Unit Test: If entrance exam is enabled then its content e.g. problems should be loaded and redirection will Unit Test: If entrance exam is enabled then its content e.g. problems should be loaded and redirection will
...@@ -275,41 +278,44 @@ class EntranceExamTestCases(ModuleStoreTestCase): ...@@ -275,41 +278,44 @@ class EntranceExamTestCases(ModuleStoreTestCase):
'section': self.exam_1.location.name 'section': self.exam_1.location.name
}) })
resp = self.client.get(url) resp = self.client.get(url)
self.assertRedirects(resp, expected_url, status_code=302, target_status_code=200) if settings.FEATURES.get('ENTRANCE_EXAMS', False):
resp = self.client.get(expected_url) self.assertRedirects(resp, expected_url, status_code=302, target_status_code=200)
self.assertIn('Exam Problem - Problem 1', resp.content) resp = self.client.get(expected_url)
self.assertIn('Exam Problem - Problem 2', resp.content) self.assertIn('Exam Problem - Problem 1', resp.content)
self.assertIn('Exam Problem - Problem 2', resp.content)
def test_entrance_exam_content_info(self): def test_entrance_exam_content_info(self):
""" """
test entrance exam content info method test entrance exam content info method
""" """
exam_chapter, is_exam_passed = get_entrance_exam_content_info(self.request, self.course) exam_chapter, is_exam_passed = get_entrance_exam_content_info(self.request, self.course)
self.assertEqual(exam_chapter.url_name, self.entrance_exam.url_name) if settings.FEATURES.get('ENTRANCE_EXAMS', False):
self.assertEqual(is_exam_passed, False) self.assertEqual(exam_chapter.url_name, self.entrance_exam.url_name)
self.assertEqual(is_exam_passed, False)
# Pass the entrance exam # Pass the entrance exam
# pylint: disable=maybe-no-member,no-member # pylint: disable=maybe-no-member,no-member
grade_dict = {'value': 1, 'max_value': 1, 'user_id': self.request.user.id} grade_dict = {'value': 1, 'max_value': 1, 'user_id': self.request.user.id}
field_data_cache = FieldDataCache.cache_for_descriptor_descendents( field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
self.course.id, self.course.id,
self.request.user, self.request.user,
self.course, self.course,
depth=2 depth=2
) )
# pylint: disable=protected-access # pylint: disable=protected-access
module = get_module( module = get_module(
self.request.user, self.request.user,
self.request, self.request,
self.problem_1.scope_ids.usage_id, self.problem_1.scope_ids.usage_id,
field_data_cache, field_data_cache,
)._xmodule )._xmodule
module.system.publish(self.problem_1, 'grade', grade_dict) module.system.publish(self.problem_1, 'grade', grade_dict)
exam_chapter, is_exam_passed = get_entrance_exam_content_info(self.request, self.course) exam_chapter, is_exam_passed = get_entrance_exam_content_info(self.request, self.course)
self.assertEqual(exam_chapter, None) self.assertEqual(exam_chapter, None)
self.assertEqual(is_exam_passed, True) self.assertEqual(is_exam_passed, True)
@patch.dict('django.conf.settings.FEATURES', {'ENTRANCE_EXAMS': True})
def test_entrance_exam_score(self): def test_entrance_exam_score(self):
""" """
test entrance exam score. we will hit the method get_entrance_exam_score to verify exam score. test entrance exam score. we will hit the method get_entrance_exam_score to verify exam score.
...@@ -352,8 +358,9 @@ class EntranceExamTestCases(ModuleStoreTestCase): ...@@ -352,8 +358,9 @@ class EntranceExamTestCases(ModuleStoreTestCase):
} }
) )
resp = self.client.get(url) resp = self.client.get(url)
self.assertEqual(resp.status_code, 200) if settings.FEATURES.get('ENTRANCE_EXAMS', False):
self.assertIn('To access course materials, you must score', resp.content) self.assertEqual(resp.status_code, 200)
self.assertIn('To access course materials, you must score', resp.content)
def test_entrance_exam_requirement_message_hidden(self): def test_entrance_exam_requirement_message_hidden(self):
""" """
...@@ -369,8 +376,9 @@ class EntranceExamTestCases(ModuleStoreTestCase): ...@@ -369,8 +376,9 @@ class EntranceExamTestCases(ModuleStoreTestCase):
) )
resp = self.client.get(url) resp = self.client.get(url)
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
self.assertNotIn('To access course materials, you must score', resp.content) if settings.FEATURES.get('ENTRANCE_EXAMS', False):
self.assertNotIn('You have passed the entrance exam.', resp.content) self.assertNotIn('To access course materials, you must score', resp.content)
self.assertNotIn('You have passed the entrance exam.', resp.content)
def test_entrance_exam_passed_message_and_course_content(self): def test_entrance_exam_passed_message_and_course_content(self):
""" """
...@@ -404,10 +412,12 @@ class EntranceExamTestCases(ModuleStoreTestCase): ...@@ -404,10 +412,12 @@ class EntranceExamTestCases(ModuleStoreTestCase):
module.system.publish(self.problem_1, 'grade', grade_dict) module.system.publish(self.problem_1, 'grade', grade_dict)
resp = self.client.get(url) resp = self.client.get(url)
self.assertNotIn('To access course materials, you must score', resp.content) if settings.FEATURES.get('ENTRANCE_EXAMS', False):
self.assertIn('You have passed the entrance exam.', resp.content) self.assertNotIn('To access course materials, you must score', resp.content)
self.assertIn('Lesson 1', resp.content) self.assertIn('You have passed the entrance exam.', resp.content)
self.assertIn('Lesson 1', resp.content)
@patch.dict('django.conf.settings.FEATURES', {'ENTRANCE_EXAMS': True})
def test_entrance_exam_gating(self): def test_entrance_exam_gating(self):
""" """
Unit Test: test_entrance_exam_gating Unit Test: test_entrance_exam_gating
...@@ -477,6 +487,7 @@ class EntranceExamTestCases(ModuleStoreTestCase): ...@@ -477,6 +487,7 @@ class EntranceExamTestCases(ModuleStoreTestCase):
for toc_section in self.expected_unlocked_toc: for toc_section in self.expected_unlocked_toc:
self.assertIn(toc_section, unlocked_toc) self.assertIn(toc_section, unlocked_toc)
@patch.dict('django.conf.settings.FEATURES', {'ENTRANCE_EXAMS': True})
def test_skip_entrance_exame_gating(self): def test_skip_entrance_exame_gating(self):
""" """
Tests gating is disabled if skip entrance exam is set for a user. Tests gating is disabled if skip entrance exam is set for a user.
......
...@@ -15,16 +15,14 @@ from xmodule import tabs ...@@ -15,16 +15,14 @@ from xmodule import tabs
from xmodule.modulestore.tests.django_utils import ( from xmodule.modulestore.tests.django_utils import (
TEST_DATA_MIXED_TOY_MODULESTORE, TEST_DATA_MIXED_CLOSED_MODULESTORE TEST_DATA_MIXED_TOY_MODULESTORE, TEST_DATA_MIXED_CLOSED_MODULESTORE
) )
from courseware.tabs import get_course_tab_list
from courseware.views import get_static_tab_contents, static_tab from courseware.views import get_static_tab_contents, static_tab
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
from util import milestones_helpers
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
if settings.FEATURES.get('MILESTONES_APP', False):
from courseware.tabs import get_course_tab_list
from milestones import api as milestones_api
from milestones.models import MilestoneRelationshipType
class StaticTabDateTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase): class StaticTabDateTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase):
"""Test cases for Static Tab Dates.""" """Test cases for Static Tab Dates."""
...@@ -140,9 +138,8 @@ class EntranceExamsTabsTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase): ...@@ -140,9 +138,8 @@ class EntranceExamsTabsTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase):
self.setup_user() self.setup_user()
self.enroll(self.course) self.enroll(self.course)
self.user.is_staff = True self.user.is_staff = True
self.relationship_types = milestones_api.get_milestone_relationship_types() self.relationship_types = milestones_helpers.get_milestone_relationship_types()
MilestoneRelationshipType.objects.create(name='requires') milestones_helpers.seed_milestone_relationship_types()
MilestoneRelationshipType.objects.create(name='fulfills')
def test_get_course_tabs_list_entrance_exam_enabled(self): def test_get_course_tabs_list_entrance_exam_enabled(self):
""" """
...@@ -160,13 +157,13 @@ class EntranceExamsTabsTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase): ...@@ -160,13 +157,13 @@ class EntranceExamsTabsTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase):
} }
self.course.entrance_exam_enabled = True self.course.entrance_exam_enabled = True
self.course.entrance_exam_id = unicode(entrance_exam.location) self.course.entrance_exam_id = unicode(entrance_exam.location)
milestone = milestones_api.add_milestone(milestone) milestone = milestones_helpers.add_milestone(milestone)
milestones_api.add_course_milestone( milestones_helpers.add_course_milestone(
unicode(self.course.id), unicode(self.course.id),
self.relationship_types['REQUIRES'], self.relationship_types['REQUIRES'],
milestone milestone
) )
milestones_api.add_course_content_milestone( milestones_helpers.add_course_content_milestone(
unicode(self.course.id), unicode(self.course.id),
unicode(entrance_exam.location), unicode(entrance_exam.location),
self.relationship_types['FULFILLS'], self.relationship_types['FULFILLS'],
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment