Cleaned up milestones API references
This commit is contained in:
@@ -6,6 +6,7 @@ import json
|
||||
import copy
|
||||
import mock
|
||||
from mock import patch
|
||||
import unittest
|
||||
|
||||
from django.utils.timezone import UTC
|
||||
from django.test.utils import override_settings
|
||||
@@ -139,19 +140,9 @@ class CourseDetailsTestCase(CourseTestCase):
|
||||
self.assertNotContains(response, "Course Introduction Video")
|
||||
self.assertNotContains(response, "Requirements")
|
||||
|
||||
def _seed_milestone_relationship_types(self):
|
||||
"""
|
||||
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})
|
||||
@unittest.skipUnless(settings.FEATURES.get('ENTRANCE_EXAMS', False), True)
|
||||
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)
|
||||
data = {
|
||||
'entrance_exam_enabled': 'true',
|
||||
@@ -196,13 +187,13 @@ class CourseDetailsTestCase(CourseTestCase):
|
||||
self.assertFalse(course.entrance_exam_enabled)
|
||||
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):
|
||||
"""
|
||||
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
|
||||
"""
|
||||
self._seed_milestone_relationship_types()
|
||||
seed_milestone_relationship_types()
|
||||
settings_details_url = get_url(self.course.id)
|
||||
test_data_1 = {
|
||||
'entrance_exam_enabled': 'true',
|
||||
|
||||
@@ -2,22 +2,22 @@
|
||||
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
|
||||
"""
|
||||
from functools import wraps
|
||||
import json
|
||||
import logging
|
||||
|
||||
from django.contrib.auth.decorators import login_required
|
||||
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 contentstore.views.helpers import create_xblock
|
||||
from contentstore.views.item import delete_item
|
||||
from milestones import api as milestones_api
|
||||
from models.settings.course_metadata import CourseMetadata
|
||||
from opaque_keys.edx.keys import CourseKey, UsageKey
|
||||
from opaque_keys import InvalidKeyError
|
||||
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.exceptions import ItemNotFoundError
|
||||
from django.conf import settings
|
||||
@@ -40,8 +40,24 @@ def _get_default_entrance_exam_minimum_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
|
||||
@ensure_csrf_cookie
|
||||
@check_feature_enabled(feature_name='ENTRANCE_EXAMS')
|
||||
def entrance_exam(request, course_key_string):
|
||||
"""
|
||||
The restful handler for entrance exams.
|
||||
@@ -88,6 +104,7 @@ def entrance_exam(request, course_key_string):
|
||||
return HttpResponse(status=405)
|
||||
|
||||
|
||||
@check_feature_enabled(feature_name='ENTRANCE_EXAMS')
|
||||
def create_entrance_exam(request, course_key, entrance_exam_minimum_score_pct):
|
||||
"""
|
||||
api method to create an entrance exam.
|
||||
@@ -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
|
||||
milestone_namespace = generate_milestone_namespace(
|
||||
NAMESPACE_CHOICES['ENTRANCE_EXAM'],
|
||||
namespace_choices = milestones_helpers.get_namespace_choices()
|
||||
milestone_namespace = milestones_helpers.generate_milestone_namespace(
|
||||
namespace_choices.get('ENTRANCE_EXAM'),
|
||||
course_key
|
||||
)
|
||||
milestones = milestones_api.get_milestones(milestone_namespace)
|
||||
milestones = milestones_helpers.get_milestones(milestone_namespace)
|
||||
if len(milestones):
|
||||
milestone = milestones[0]
|
||||
else:
|
||||
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',
|
||||
'namespace': milestone_namespace,
|
||||
'description': description
|
||||
})
|
||||
relationship_types = milestones_api.get_milestone_relationship_types()
|
||||
milestones_api.add_course_milestone(
|
||||
relationship_types = milestones_helpers.get_milestone_relationship_types()
|
||||
milestones_helpers.add_course_milestone(
|
||||
unicode(course.id),
|
||||
relationship_types['REQUIRES'],
|
||||
milestone
|
||||
)
|
||||
milestones_api.add_course_content_milestone(
|
||||
milestones_helpers.add_course_content_milestone(
|
||||
unicode(course.id),
|
||||
unicode(created_block.location),
|
||||
relationship_types['FULFILLS'],
|
||||
@@ -202,6 +220,7 @@ def _get_entrance_exam(request, course_key): # pylint: disable=W0613
|
||||
return HttpResponse(status=404)
|
||||
|
||||
|
||||
@check_feature_enabled(feature_name='ENTRANCE_EXAMS')
|
||||
def update_entrance_exam(request, course_key, exam_data):
|
||||
"""
|
||||
Operation to update course fields pertaining to entrance exams
|
||||
@@ -215,6 +234,7 @@ def update_entrance_exam(request, course_key, exam_data):
|
||||
CourseMetadata.update_from_dict(metadata, course, request.user)
|
||||
|
||||
|
||||
@check_feature_enabled(feature_name='ENTRANCE_EXAMS')
|
||||
def delete_entrance_exam(request, course_key):
|
||||
"""
|
||||
api method to delete an entrance exam
|
||||
@@ -238,7 +258,7 @@ def _delete_entrance_exam(request, course_key):
|
||||
for course_child in course_children:
|
||||
if course_child.is_entrance_exam:
|
||||
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
|
||||
# Reload the course so we have the latest state
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
Test module for Entrance Exams AJAX callback handler workflows
|
||||
"""
|
||||
import json
|
||||
from mock import patch
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
@@ -9,18 +10,14 @@ from django.test.client import RequestFactory
|
||||
|
||||
from contentstore.tests.utils import AjaxEnabledTestClient, CourseTestCase
|
||||
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_metadata import CourseMetadata
|
||||
from opaque_keys.edx.keys import UsageKey
|
||||
from student.tests.factories import UserFactory
|
||||
from util import milestones_helpers
|
||||
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):
|
||||
"""
|
||||
@@ -36,9 +33,8 @@ class EntranceExamHandlerTests(CourseTestCase):
|
||||
self.usage_key = self.course.location
|
||||
self.course_url = '/course/{}'.format(unicode(self.course.id))
|
||||
self.exam_url = '/course/{}/entrance_exam/'.format(unicode(self.course.id))
|
||||
MilestoneRelationshipType.objects.create(name='requires', active=True)
|
||||
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_relationship_types = milestones_helpers.get_milestone_relationship_types()
|
||||
|
||||
def test_contentstore_views_entrance_exam_post(self):
|
||||
"""
|
||||
@@ -55,8 +51,8 @@ class EntranceExamHandlerTests(CourseTestCase):
|
||||
self.assertTrue(metadata['entrance_exam_enabled'])
|
||||
self.assertIsNotNone(metadata['entrance_exam_minimum_score_pct'])
|
||||
self.assertIsNotNone(metadata['entrance_exam_id']['value'])
|
||||
self.assertTrue(len(milestones_api.get_course_milestones(unicode(self.course.id))))
|
||||
content_milestones = milestones_api.get_course_content_milestones(
|
||||
self.assertTrue(len(milestones_helpers.get_course_milestones(unicode(self.course.id))))
|
||||
content_milestones = milestones_helpers.get_course_content_milestones(
|
||||
unicode(self.course.id),
|
||||
metadata['entrance_exam_id']['value'],
|
||||
self.milestone_relationship_types['FULFILLS']
|
||||
@@ -123,12 +119,12 @@ class EntranceExamHandlerTests(CourseTestCase):
|
||||
)
|
||||
user.set_password('test')
|
||||
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)
|
||||
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),
|
||||
serialize_user(user)
|
||||
milestones_helpers.serialize_user(user)
|
||||
)
|
||||
|
||||
# What we have now is a course milestone requirement and no valid fulfillment
|
||||
@@ -250,3 +246,22 @@ class EntranceExamHandlerTests(CourseTestCase):
|
||||
|
||||
resp = create_entrance_exam(request, self.course.id, None)
|
||||
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, {})
|
||||
|
||||
Reference in New Issue
Block a user