Cleaned up milestones API references

This commit is contained in:
Matt Drayer
2015-03-11 23:15:46 -04:00
parent cce00e6933
commit cd05391335
9 changed files with 414 additions and 181 deletions

View File

@@ -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',

View File

@@ -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

View File

@@ -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, {})