Пример #1
0
def calendar_settings_handler(request, package_id=None, branch=None, version_guid=None, block=None, tag=None):
    locator, course_module = _get_locator_and_course(
        package_id, branch, version_guid, block, request.user
    )
    if 'text/html' in request.META.get('HTTP_ACCEPT', '') and request.method == 'GET':

        return render_to_response('settings_calendar.html', {
            'package_id': package_id,
            'context_course': course_module,
            'advanced_dict': json.dumps(CourseMetadata.fetch(course_module)),
            'advanced_settings_url': locator.url_reverse('settings/calendar')
        })
    elif 'application/json' in request.META.get('HTTP_ACCEPT', ''):
        if request.method == 'GET':
            return JsonResponse(CourseMetadata.fetch(course_module))
        else:
            # Whether or not to filter the tabs key out of the settings metadata
            filter_tabs = _config_course_advanced_components(request, course_module)
            try:
                return JsonResponse(CourseMetadata.update_from_json(
                    course_module,
                    request.json,
                    filter_tabs=filter_tabs,
                    user=request.user,
                ))
            except (TypeError, ValueError) as err:
                return HttpResponseBadRequest(
                    "Incorrect setting format. {}".format(err),
                    content_type="text/plain"
                )
Пример #2
0
def _delete_entrance_exam(request, course_key):
    """
    Internal workflow operation to remove an entrance exam
    """
    store = modulestore()
    course = store.get_course(course_key)
    if course is None:
        return HttpResponse(status=400)

    remove_entrance_exam_milestone_reference(request, course_key)

    # Reset the entrance exam flags on the course
    # Reload the course so we have the latest state
    course = store.get_course(course_key)
    if course.entrance_exam_id:
        metadata = {
            'entrance_exam_enabled': False,
            'entrance_exam_minimum_score_pct': None,
            'entrance_exam_id': None,
        }
        CourseMetadata.update_from_dict(metadata, course, request.user)

        # Clean up any pre-existing entrance exam graders
        remove_entrance_exam_graders(course_key, request.user)

    return HttpResponse(status=204)
Пример #3
0
 def test_update_from_json(self):
     test_model = CourseMetadata.update_from_json(self.course, {
         "advertised_start": "start A",
         "days_early_for_beta": 2,
     },
                                                  user=self.user)
     self.update_check(test_model)
     # try fresh fetch to ensure persistence
     fresh = modulestore().get_item(self.course_location)
     test_model = CourseMetadata.fetch(fresh)
     self.update_check(test_model)
     # now change some of the existing metadata
     test_model = CourseMetadata.update_from_json(
         fresh, {
             "advertised_start": "start B",
             "display_name": "jolly roger",
         },
         user=self.user)
     self.assertIn('display_name', test_model,
                   'Missing editable metadata field')
     self.assertEqual(test_model['display_name'], 'jolly roger',
                      "not expected value")
     self.assertIn('advertised_start', test_model,
                   'Missing revised advertised_start metadata field')
     self.assertEqual(test_model['advertised_start'], 'start B',
                      "advertised_start not expected value")
Пример #4
0
    def test_contentstore_views_entrance_exam_get_bogus_exam(self):
        """
        Unit Test: test_contentstore_views_entrance_exam_get_bogus_exam
        """
        resp = self.client.post(
            self.exam_url,
            {'entrance_exam_minimum_score_pct': '50'},
            http_accept='application/json'
        )
        self.assertEqual(resp.status_code, 201)
        resp = self.client.get(self.exam_url)
        self.assertEqual(resp.status_code, 200)
        self.course = modulestore().get_course(self.course.id)

        # Should raise an ItemNotFoundError and return a 404
        updated_metadata = {'entrance_exam_id': 'i4x://org.4/course_4/chapter/ed7c4c6a4d68409998e2c8554c4629d1'}
        CourseMetadata.update_from_dict(
            updated_metadata,
            self.course,
            self.user,
        )
        self.course = modulestore().get_course(self.course.id)
        resp = self.client.get(self.exam_url)
        self.assertEqual(resp.status_code, 404)

        # Should raise an InvalidKeyError and return a 404
        updated_metadata = {'entrance_exam_id': '123afsdfsad90f87'}
        CourseMetadata.update_from_dict(
            updated_metadata,
            self.course,
            self.user,
        )
        self.course = modulestore().get_course(self.course.id)
        resp = self.client.get(self.exam_url)
        self.assertEqual(resp.status_code, 404)
    def test_validate_and_update_from_json_wrong_inputs(self):
        # input incorrectly formatted data
        is_valid, errors, test_model = CourseMetadata.validate_and_update_from_json(
            self.course,
            {
                "advertised_start": {"value": 1, "display_name": "Course Advertised Start Date", },
                "days_early_for_beta": {"value": "supposed to be an integer",
                                        "display_name": "Days Early for Beta Users", },
                "advanced_modules": {"value": 1, "display_name": "Advanced Module List", },
            },
            user=self.user
        )

        # Check valid results from validate_and_update_from_json
        self.assertFalse(is_valid)
        self.assertEqual(len(errors), 3)
        self.assertFalse(test_model)

        error_keys = set([error_obj['model']['display_name'] for error_obj in errors])
        test_keys = set(['Advanced Module List', 'Course Advertised Start Date', 'Days Early for Beta Users'])
        self.assertEqual(error_keys, test_keys)

        # try fresh fetch to ensure no update happened
        fresh = modulestore().get_course(self.course.id)
        test_model = CourseMetadata.fetch(fresh)

        self.assertNotEqual(test_model['advertised_start']['value'], 1, 'advertised_start should not be updated to a wrong value')
        self.assertNotEqual(test_model['days_early_for_beta']['value'], "supposed to be an integer",
                            'days_early_for beta should not be updated to a wrong value')
        def test_contentstore_views_entrance_exam_get_bogus_exam(self):
            """
            Unit Test: test_contentstore_views_entrance_exam_get_bogus_exam
            """
            resp = self.client.post(
                self.exam_url,
                {'entrance_exam_minimum_score_pct': '50'},
                http_accept='application/json'
            )
            self.assertEqual(resp.status_code, 201)
            resp = self.client.get(self.exam_url)
            self.assertEqual(resp.status_code, 200)
            self.course = modulestore().get_course(self.course.id)

            # Should raise an ItemNotFoundError and return a 404
            updated_metadata = {'entrance_exam_id': 'i4x://org.4/course_4/chapter/ed7c4c6a4d68409998e2c8554c4629d1'}
            CourseMetadata.update_from_dict(
                updated_metadata,
                self.course,
                self.user,
            )
            self.course = modulestore().get_course(self.course.id)
            resp = self.client.get(self.exam_url)
            self.assertEqual(resp.status_code, 404)

            # Should raise an InvalidKeyError and return a 404
            updated_metadata = {'entrance_exam_id': '123afsdfsad90f87'}
            CourseMetadata.update_from_dict(
                updated_metadata,
                self.course,
                self.user,
            )
            self.course = modulestore().get_course(self.course.id)
            resp = self.client.get(self.exam_url)
            self.assertEqual(resp.status_code, 404)
Пример #7
0
def _delete_entrance_exam(request, course_key):
    """
    Internal workflow operation to remove an entrance exam
    """
    store = modulestore()
    course = store.get_course(course_key)
    if course is None:
        return HttpResponse(status=400)

    remove_entrance_exam_milestone_reference(request, course_key)

    # Reset the entrance exam flags on the course
    # Reload the course so we have the latest state
    course = store.get_course(course_key)
    if course.entrance_exam_id:
        metadata = {
            'entrance_exam_enabled': False,
            'entrance_exam_minimum_score_pct': None,
            'entrance_exam_id': None,
        }
        CourseMetadata.update_from_dict(metadata, course, request.user)

        # Clean up any pre-existing entrance exam graders
        remove_entrance_exam_graders(course_key, request.user)

    return HttpResponse(status=204)
Пример #8
0
    def test_validate_and_update_from_json_correct_inputs(self):
        is_valid, errors, test_model = CourseMetadata.validate_and_update_from_json(
            self.course, {
                "advertised_start": {
                    "value": "start A"
                },
                "days_early_for_beta": {
                    "value": 2
                },
                "advanced_modules": {
                    "value": ['combinedopenended']
                },
            },
            user=self.user)
        self.assertTrue(is_valid)
        self.assertTrue(len(errors) == 0)
        self.update_check(test_model)

        # fresh fetch to ensure persistence
        fresh = modulestore().get_course(self.course.id)
        test_model = CourseMetadata.fetch(fresh)
        self.update_check(test_model)

        # Tab gets tested in test_advanced_settings_munge_tabs
        self.assertIn('advanced_modules', test_model,
                      'Missing advanced_modules')
        self.assertEqual(test_model['advanced_modules']['value'],
                         ['combinedopenended'],
                         'advanced_module is not updated')
 def test_update_from_json(self):
     test_model = CourseMetadata.update_from_json(
         self.course,
         {
             "advertised_start": {"value": "start A"},
             "days_early_for_beta": {"value": 2},
         },
         user=self.user
     )
     self.update_check(test_model)
     # try fresh fetch to ensure persistence
     fresh = modulestore().get_course(self.course.id)
     test_model = CourseMetadata.fetch(fresh)
     self.update_check(test_model)
     # now change some of the existing metadata
     test_model = CourseMetadata.update_from_json(
         fresh,
         {
             "advertised_start": {"value": "start B"},
             "display_name": {"value": "jolly roger"},
         },
         user=self.user
     )
     self.assertIn('display_name', test_model, 'Missing editable metadata field')
     self.assertEqual(test_model['display_name']['value'], 'jolly roger', "not expected value")
     self.assertIn('advertised_start', test_model, 'Missing revised advertised_start metadata field')
     self.assertEqual(test_model['advertised_start']['value'], 'start B', "advertised_start not expected value")
Пример #10
0
def course_audit_api(request, course_id, operation):
    re_json = {"success": False}

    request_method = request.method
    if request_method != "POST":
        return JsonResponse(re_json)
    # get course location and module infomation
    try:
        course_location_info = course_id.split('.')
        locator = BlockUsageLocator(package_id=course_id, branch='draft', version_guid=None, block_id=course_location_info[-1])
        course_location = loc_mapper().translate_locator_to_location(locator)
        course_module = get_modulestore(course_location).get_item(course_location)

        instructors = CourseInstructorRole(locator).users_with_role()
        if len(instructors) <= 0:
            return JsonResponse(re_json)

        user = instructors[0]

        meta_json = {}
        if operation == "pass":
            meta_json["course_audit"] = 1
        elif operation == "offline":
            meta_json["course_audit"] = 0
        else:
            return JsonResponse(re_json)

        re_json["success"] = True
        CourseMetadata.update_from_json(course_module, meta_json, True, user)
        return JsonResponse(re_json)
    except:
        return JsonResponse(re_json)
Пример #11
0
def _delete_entrance_exam(request, course_key):
    """
    Internal workflow operation to remove an entrance exam
    """
    store = modulestore()
    course = store.get_course(course_key)
    if course is None:
        return HttpResponse(status=400)

    course_children = store.get_items(
        course_key,
        qualifiers={'category': 'chapter'}
    )
    for course_child in course_children:
        if course_child.is_entrance_exam:
            delete_item(request, 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
    course = store.get_course(course_key)
    if getattr(course, 'entrance_exam_id'):
        metadata = {
            'entrance_exam_enabled': False,
            'entrance_exam_minimum_score_pct': None,
            'entrance_exam_id': None,
        }
        CourseMetadata.update_from_dict(metadata, course, request.user)

        # Clean up any pre-existing entrance exam graders
        remove_entrance_exam_graders(course_key, request.user)

    return HttpResponse(status=204)
Пример #12
0
 def test_update_from_json(self):
     test_model = CourseMetadata.update_from_json(
         self.course_location, {
             "advertised_start": "start A",
             "testcenter_info": {
                 "c": "test"
             },
             "days_early_for_beta": 2
         })
     self.update_check(test_model)
     # try fresh fetch to ensure persistence
     test_model = CourseMetadata.fetch(self.course_location)
     self.update_check(test_model)
     # now change some of the existing metadata
     test_model = CourseMetadata.update_from_json(
         self.course_location, {
             "advertised_start": "start B",
             "display_name": "jolly roger"
         })
     self.assertIn('display_name', test_model,
                   'Missing editable metadata field')
     self.assertEqual(test_model['display_name'], 'jolly roger',
                      "not expected value")
     self.assertIn('advertised_start', test_model,
                   'Missing revised advertised_start metadata field')
     self.assertEqual(test_model['advertised_start'], 'start B',
                      "advertised_start not expected value")
Пример #13
0
def advanced_settings_handler(request, package_id=None, branch=None, version_guid=None, block=None, tag=None):
    """
    Course settings configuration
    GET
        html: get the page
        json: get the model
    PUT, POST
        json: update the Course's settings. The payload is a json rep of the
            metadata dicts. The dict can include a "unsetKeys" entry which is a list
            of keys whose values to unset: i.e., revert to default
    """
    locator, course_module = _get_locator_and_course(package_id, branch, version_guid, block, request.user)
    if "text/html" in request.META.get("HTTP_ACCEPT", "") and request.method == "GET":

        return render_to_response(
            "settings_advanced.html",
            {
                "context_course": course_module,
                "advanced_dict": json.dumps(CourseMetadata.fetch(course_module)),
                "advanced_settings_url": locator.url_reverse("settings/advanced"),
            },
        )
    elif "application/json" in request.META.get("HTTP_ACCEPT", ""):
        if request.method == "GET":
            return JsonResponse(CourseMetadata.fetch(course_module))
        else:
            # Whether or not to filter the tabs key out of the settings metadata
            filter_tabs = _config_course_advanced_components(request, course_module)
            try:
                return JsonResponse(
                    CourseMetadata.update_from_json(course_module, request.json, filter_tabs=filter_tabs)
                )
            except (TypeError, ValueError) as err:
                return HttpResponseBadRequest("Incorrect setting format. {}".format(err), content_type="text/plain")
Пример #14
0
    def test_validate_and_update_from_json_wrong_inputs(self):
        # input incorrectly formatted data
        is_valid, errors, test_model = CourseMetadata.validate_and_update_from_json(
            self.course,
            {
                "advertised_start": {"value": 1, "display_name": "Course Advertised Start Date", },
                "days_early_for_beta": {"value": "supposed to be an integer",
                                        "display_name": "Days Early for Beta Users", },
                "advanced_modules": {"value": 1, "display_name": "Advanced Module List", },
            },
            user=self.user
        )

        # Check valid results from validate_and_update_from_json
        self.assertFalse(is_valid)
        self.assertEqual(len(errors), 3)
        self.assertFalse(test_model)

        error_keys = set([error_obj['model']['display_name'] for error_obj in errors])
        test_keys = set(['Advanced Module List', 'Course Advertised Start Date', 'Days Early for Beta Users'])
        self.assertEqual(error_keys, test_keys)

        # try fresh fetch to ensure no update happened
        fresh = modulestore().get_course(self.course.id)
        test_model = CourseMetadata.fetch(fresh)

        self.assertNotEqual(test_model['advertised_start']['value'], 1, 'advertised_start should not be updated to a wrong value')
        self.assertNotEqual(test_model['days_early_for_beta']['value'], "supposed to be an integer",
                            'days_early_for beta should not be updated to a wrong value')
Пример #15
0
def _delete_entrance_exam(request, course_key):
    """
    Internal workflow operation to remove an entrance exam
    """
    store = modulestore()
    course = store.get_course(course_key)
    if course is None:
        return HttpResponse(status=400)

    course_children = store.get_items(course_key,
                                      qualifiers={'category': 'chapter'})
    for course_child in course_children:
        if course_child.is_entrance_exam:
            delete_item(request, 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
    course = store.get_course(course_key)
    if getattr(course, 'entrance_exam_id'):
        metadata = {
            'entrance_exam_enabled': False,
            'entrance_exam_minimum_score_pct': None,
            'entrance_exam_id': None,
        }
        CourseMetadata.update_from_dict(metadata, course, request.user)

        # Clean up any pre-existing entrance exam graders
        remove_entrance_exam_graders(course_key, request.user)

    return HttpResponse(status=204)
Пример #16
0
def update_entrance_exam(request, course_key, exam_data):
    """
    Operation to update course fields pertaining to entrance exams
    The update operation is not currently exposed directly via the API
    Because the operation is not exposed directly, we do not return a 200 response
    But we do return a 400 in the error case because the workflow is executed in a request context
    """
    course = modulestore().get_course(course_key)
    if course:
        metadata = exam_data
        CourseMetadata.update_from_dict(metadata, course, request.user)
Пример #17
0
def update_entrance_exam(request, course_key, exam_data):
    """
    Operation to update course fields pertaining to entrance exams
    The update operation is not currently exposed directly via the API
    Because the operation is not exposed directly, we do not return a 200 response
    But we do return a 400 in the error case because the workflow is executed in a request context
    """
    course = modulestore().get_course(course_key)
    if course:
        metadata = exam_data
        CourseMetadata.update_from_dict(metadata, course, request.user)
    def test_fetch_initial_fields(self):
        test_model = CourseMetadata.fetch(self.course.location)
        self.assertIn("display_name", test_model, "Missing editable metadata field")
        self.assertEqual(test_model["display_name"], "Robot Super Course", "not expected value")

        test_model = CourseMetadata.fetch(self.fullcourse_location)
        self.assertNotIn("graceperiod", test_model, "blacklisted field leaked in")
        self.assertIn("display_name", test_model, "full missing editable metadata field")
        self.assertEqual(test_model["display_name"], "Robot Super Course", "not expected value")
        self.assertIn("rerandomize", test_model, "Missing rerandomize metadata field")
        self.assertIn("showanswer", test_model, "showanswer field ")
        self.assertIn("xqa_key", test_model, "xqa_key field ")
Пример #19
0
def _create_entrance_exam(request, course_key, entrance_exam_minimum_score_pct=None):
    """
    Internal workflow operation to create an entrance exam
    """
    # Provide a default value for the minimum score percent if nothing specified
    if entrance_exam_minimum_score_pct is None:
        entrance_exam_minimum_score_pct = float(settings.ENTRANCE_EXAM_MIN_SCORE_PCT)

    # Confirm the course exists
    course = modulestore().get_course(course_key)
    if course is None:
        return HttpResponse(status=400)

    # Create the entrance exam item (currently it's just a chapter)
    payload = {
        "category": "chapter",
        "display_name": "Entrance Exam",
        "parent_locator": unicode(course.location),
        "is_entrance_exam": True,
        "in_entrance_exam": True,
    }
    factory = RequestFactory()
    internal_request = factory.post("/", json.dumps(payload), content_type="application/json")
    internal_request.user = request.user
    created_item = json.loads(create_item(internal_request).content)

    # Set the entrance exam metadata flags for this course
    # Reload the course so we don't overwrite the new child reference
    course = modulestore().get_course(course_key)
    metadata = {
        "entrance_exam_enabled": True,
        "entrance_exam_minimum_score_pct": entrance_exam_minimum_score_pct / 100,
        "entrance_exam_id": created_item["locator"],
    }
    CourseMetadata.update_from_dict(metadata, course, request.user)

    # Add an entrance exam milestone if one does not already exist
    milestone_namespace = generate_milestone_namespace(NAMESPACE_CHOICES["ENTRANCE_EXAM"], course_key)
    milestones = milestones_api.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(
            {"name": "Completed Course Entrance Exam", "namespace": milestone_namespace, "description": description}
        )
    relationship_types = milestones_api.get_milestone_relationship_types()
    milestones_api.add_course_milestone(unicode(course.id), relationship_types["REQUIRES"], milestone)
    milestones_api.add_course_content_milestone(
        unicode(course.id), created_item["locator"], relationship_types["FULFILLS"], milestone
    )

    return HttpResponse(status=201)
Пример #20
0
    def test_fetch_initial_fields(self):
        test_model = CourseMetadata.fetch(self.course.location)
        self.assertIn('display_name', test_model, 'Missing editable metadata field')
        self.assertEqual(test_model['display_name'], 'Robot Super Course', "not expected value")

        test_model = CourseMetadata.fetch(self.fullcourse_location)
        self.assertNotIn('graceperiod', test_model, 'blacklisted field leaked in')
        self.assertIn('display_name', test_model, 'full missing editable metadata field')
        self.assertEqual(test_model['display_name'], 'Testing', "not expected value")
        self.assertIn('rerandomize', test_model, 'Missing rerandomize metadata field')
        self.assertIn('showanswer', test_model, 'showanswer field ')
        self.assertIn('xqa_key', test_model, 'xqa_key field ')
Пример #21
0
    def test_fetch_initial_fields(self):
        test_model = CourseMetadata.fetch(self.course)
        self.assertIn('display_name', test_model, 'Missing editable metadata field')
        self.assertEqual(test_model['display_name']['value'], self.course.display_name)

        test_model = CourseMetadata.fetch(self.fullcourse)
        self.assertNotIn('graceperiod', test_model, 'blacklisted field leaked in')
        self.assertIn('display_name', test_model, 'full missing editable metadata field')
        self.assertEqual(test_model['display_name']['value'], self.fullcourse.display_name)
        self.assertIn('rerandomize', test_model, 'Missing rerandomize metadata field')
        self.assertIn('showanswer', test_model, 'showanswer field ')
        self.assertIn('xqa_key', test_model, 'xqa_key field ')
Пример #22
0
    def test_fetch_initial_fields(self):
        test_model = CourseMetadata.fetch(self.course)
        self.assertIn('display_name', test_model, 'Missing editable metadata field')
        self.assertEqual(test_model['display_name']['value'], self.course.display_name)

        test_model = CourseMetadata.fetch(self.fullcourse)
        self.assertNotIn('graceperiod', test_model, 'blacklisted field leaked in')
        self.assertIn('display_name', test_model, 'full missing editable metadata field')
        self.assertEqual(test_model['display_name']['value'], self.fullcourse.display_name)
        self.assertIn('rerandomize', test_model, 'Missing rerandomize metadata field')
        self.assertIn('showanswer', test_model, 'showanswer field ')
        self.assertIn('xqa_key', test_model, 'xqa_key field ')
Пример #23
0
def _create_entrance_exam(request, course_key, entrance_exam_minimum_score_pct=None):
    """
    Internal workflow operation to create an entrance exam
    """
    # Provide a default value for the minimum score percent if nothing specified
    if entrance_exam_minimum_score_pct is None:
        entrance_exam_minimum_score_pct = _get_default_entrance_exam_minimum_pct()

    # Confirm the course exists
    course = modulestore().get_course(course_key)
    if course is None:
        return HttpResponse(status=400)

    # Create the entrance exam item (currently it's just a chapter)
    payload = {
        'category': "chapter",
        'display_name': _("Entrance Exam"),
        'parent_locator': unicode(course.location),
        'is_entrance_exam': True,
        'in_entrance_exam': True,
    }
    parent_locator = unicode(course.location)
    created_block = create_xblock(
        parent_locator=parent_locator,
        user=request.user,
        category='chapter',
        display_name=_('Entrance Exam'),
        is_entrance_exam=True
    )

    # Set the entrance exam metadata flags for this course
    # Reload the course so we don't overwrite the new child reference
    course = modulestore().get_course(course_key)
    metadata = {
        'entrance_exam_enabled': True,
        'entrance_exam_minimum_score_pct': unicode(entrance_exam_minimum_score_pct),
        'entrance_exam_id': unicode(created_block.location),
    }
    CourseMetadata.update_from_dict(metadata, course, request.user)

    # Create the entrance exam section item.
    create_xblock(
        parent_locator=unicode(created_block.location),
        user=request.user,
        category='sequential',
        display_name=_('Entrance Exam - Subsection')
    )
    add_entrance_exam_milestone(course.id, created_block)

    return HttpResponse(status=201)
Пример #24
0
def _create_entrance_exam(request,
                          course_key,
                          entrance_exam_minimum_score_pct=None):
    """
    Internal workflow operation to create an entrance exam
    """
    # Provide a default value for the minimum score percent if nothing specified
    if entrance_exam_minimum_score_pct is None:
        entrance_exam_minimum_score_pct = _get_default_entrance_exam_minimum_pct(
        )

    # Confirm the course exists
    course = modulestore().get_course(course_key)
    if course is None:
        return HttpResponse(status=400)

    # Create the entrance exam item (currently it's just a chapter)
    payload = {
        'category': "chapter",
        'display_name': _("Entrance Exam"),
        'parent_locator': unicode(course.location),
        'is_entrance_exam': True,
        'in_entrance_exam': True,
    }
    parent_locator = unicode(course.location)
    created_block = create_xblock(parent_locator=parent_locator,
                                  user=request.user,
                                  category='chapter',
                                  display_name=_('Entrance Exam'),
                                  is_entrance_exam=True)

    # Set the entrance exam metadata flags for this course
    # Reload the course so we don't overwrite the new child reference
    course = modulestore().get_course(course_key)
    metadata = {
        'entrance_exam_enabled': True,
        'entrance_exam_minimum_score_pct':
        unicode(entrance_exam_minimum_score_pct),
        'entrance_exam_id': unicode(created_block.location),
    }
    CourseMetadata.update_from_dict(metadata, course, request.user)

    # Create the entrance exam section item.
    create_xblock(parent_locator=unicode(created_block.location),
                  user=request.user,
                  category='sequential',
                  display_name=_('Entrance Exam - Subsection'))
    add_entrance_exam_milestone(course.id, created_block)

    return HttpResponse(status=201)
Пример #25
0
def customize_settings(request, course_key_string):

    course_key = SlashSeparatedCourseKey.from_deprecated_string(
        course_key_string)
    with modulestore().bulk_operations(course_key):
        course_module = get_course_and_check_access(course_key, request.user)
        additional_info = {
            'is_new': request.POST.get('is_new', False),
            'invitation_only': request.POST.get('invitation_only', False),
            'manager_only': request.POST.get('manager_only', False)
        }
        CourseMetadata.update_from_dict(additional_info, course_module,
                                        request.user)
        return JsonResponse({'data': 'data'})
Пример #26
0
def advanced_settings_handler(request,
                              package_id=None,
                              branch=None,
                              version_guid=None,
                              block=None,
                              tag=None):
    """
    Course settings configuration
    GET
        html: get the page
        json: get the model
    PUT, POST
        json: update the Course's settings. The payload is a json rep of the
            metadata dicts. The dict can include a "unsetKeys" entry which is a list
            of keys whose values to unset: i.e., revert to default
    """
    locator, course_module = _get_locator_and_course(package_id, branch,
                                                     version_guid, block,
                                                     request.user)
    if 'text/html' in request.META.get('HTTP_ACCEPT',
                                       '') and request.method == 'GET':

        return render_to_response(
            'settings_advanced.html', {
                'context_course': course_module,
                'advanced_dict': json.dumps(
                    CourseMetadata.fetch(course_module)),
                'advanced_settings_url':
                locator.url_reverse('settings/advanced')
            })
    elif 'application/json' in request.META.get('HTTP_ACCEPT', ''):
        if request.method == 'GET':
            return JsonResponse(CourseMetadata.fetch(course_module))
        else:
            # Whether or not to filter the tabs key out of the settings metadata
            filter_tabs = _config_course_advanced_components(
                request, course_module)
            try:
                return JsonResponse(
                    CourseMetadata.update_from_json(
                        course_module,
                        request.json,
                        filter_tabs=filter_tabs,
                        user=request.user,
                    ))
            except (TypeError, ValueError) as err:
                return HttpResponseBadRequest(
                    "Incorrect setting format. {}".format(err),
                    content_type="text/plain")
Пример #27
0
    def test_import_delete_pre_exiting_entrance_exam(self):
        """
        Check that pre existed entrance exam content should be overwrite with the imported course.
        """
        exam_url = '/course/{}/entrance_exam/'.format(unicode(self.course.id))
        resp = self.client.post(exam_url, {'entrance_exam_minimum_score_pct': 0.5}, http_accept='application/json')
        self.assertEqual(resp.status_code, 201)

        # Reload the test course now that the exam module has been added
        self.course = modulestore().get_course(self.course.id)
        metadata = CourseMetadata.fetch_all(self.course)
        self.assertTrue(metadata['entrance_exam_enabled'])
        self.assertIsNotNone(metadata['entrance_exam_minimum_score_pct'])
        self.assertEqual(metadata['entrance_exam_minimum_score_pct']['value'], 0.5)
        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'],
            milestones_helpers.get_milestone_relationship_types()['FULFILLS']
        )
        self.assertTrue(len(content_milestones))

        # Now import entrance exam course
        with open(self.entrance_exam_tar) as gtar:
            args = {"name": self.entrance_exam_tar, "course-data": [gtar]}
            resp = self.client.post(self.url, args)
        self.assertEquals(resp.status_code, 200)
        course = self.store.get_course(self.course.id)
        self.assertIsNotNone(course)
        self.assertEquals(course.entrance_exam_enabled, True)
        self.assertEquals(course.entrance_exam_minimum_score_pct, 0.7)
Пример #28
0
    def test_contentstore_views_entrance_exam_post(self):
        """
        Unit Test: test_contentstore_views_entrance_exam_post
        """
        resp = self.client.post(self.exam_url, {},
                                http_accept='application/json')
        self.assertEqual(resp.status_code, 201)
        resp = self.client.get(self.exam_url)
        self.assertEqual(resp.status_code, 200)

        # Reload the test course now that the exam module has been added
        self.course = modulestore().get_course(self.course.id)
        metadata = CourseMetadata.fetch_all(self.course)
        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_helpers.get_course_milestones(
                    six.text_type(self.course.id))))
        content_milestones = milestones_helpers.get_course_content_milestones(
            six.text_type(self.course.id),
            metadata['entrance_exam_id']['value'],
            self.milestone_relationship_types['FULFILLS'])
        self.assertTrue(len(content_milestones))
Пример #29
0
    def test_import_delete_pre_exiting_entrance_exam(self):
        """
        Check that pre existed entrance exam content should be overwrite with the imported course.
        """
        exam_url = '/course/{}/entrance_exam/'.format(unicode(self.course.id))
        resp = self.client.post(exam_url,
                                {'entrance_exam_minimum_score_pct': 0.5},
                                http_accept='application/json')
        self.assertEqual(resp.status_code, 201)

        # Reload the test course now that the exam module has been added
        self.course = modulestore().get_course(self.course.id)
        metadata = CourseMetadata.fetch_all(self.course)
        self.assertTrue(metadata['entrance_exam_enabled'])
        self.assertIsNotNone(metadata['entrance_exam_minimum_score_pct'])
        self.assertEqual(metadata['entrance_exam_minimum_score_pct']['value'],
                         0.5)
        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'],
            milestones_helpers.get_milestone_relationship_types()['FULFILLS'])
        self.assertTrue(len(content_milestones))

        # Now import entrance exam course
        with open(self.entrance_exam_tar) as gtar:
            args = {"name": self.entrance_exam_tar, "course-data": [gtar]}
            resp = self.client.post(self.url, args)
        self.assertEquals(resp.status_code, 200)
        course = self.store.get_course(self.course.id)
        self.assertIsNotNone(course)
        self.assertEquals(course.entrance_exam_enabled, True)
        self.assertEquals(course.entrance_exam_minimum_score_pct, 0.7)
Пример #30
0
    def post(self, request, course_id):
        """ POST handler """
        serializer = ProctoredExamSettingsSerializer if request.user.is_staff else LimitedProctoredExamSettingsSerializer
        exam_config = serializer(
            data=request.data.get('proctored_exam_settings', {}))
        valid_request = exam_config.is_valid()
        if not request.user.is_staff and valid_request and ProctoredExamSettingsSerializer(
                data=request.data.get('proctored_exam_settings',
                                      {})).is_valid():
            return Response(status=status.HTTP_403_FORBIDDEN)

        with modulestore().bulk_operations(CourseKey.from_string(course_id)):
            course_module = self._get_and_validate_course_access(
                request.user, course_id)
            course_metadata = CourseMetadata().fetch_all(course_module)

            models_to_update = {}
            for setting_key, value in exam_config.data.items():
                model = course_metadata.get(setting_key)
                if model:
                    models_to_update[setting_key] = copy.deepcopy(model)
                    models_to_update[setting_key]['value'] = value

            # validate data formats and update the course module object
            is_valid, errors, updated_data = CourseMetadata.validate_and_update_from_json(
                course_module,
                models_to_update,
                user=request.user,
            )

            if not is_valid:
                error_messages = [{
                    error.get('key'): error.get('message')
                } for error in errors]
                return Response({'detail': error_messages},
                                status=status.HTTP_400_BAD_REQUEST)

            # save to mongo
            modulestore().update_item(course_module, request.user.id)

            # merge updated settings with all existing settings.
            # do this because fields that could not be modified are excluded from the result
            course_metadata = {**course_metadata, **updated_data}
            updated_setttings = self._get_proctored_exam_setting_values(
                course_metadata)
            serializer = ProctoredExamSettingsSerializer(updated_setttings)
            return Response({'proctored_exam_settings': serializer.data})
 def test_update_from_json(self):
     test_model = CourseMetadata.update_from_json(
         self.course.location,
         {"advertised_start": "start A", "testcenter_info": {"c": "test"}, "days_early_for_beta": 2},
     )
     self.update_check(test_model)
     # try fresh fetch to ensure persistence
     test_model = CourseMetadata.fetch(self.course.location)
     self.update_check(test_model)
     # now change some of the existing metadata
     test_model = CourseMetadata.update_from_json(
         self.course.location, {"advertised_start": "start B", "display_name": "jolly roger"}
     )
     self.assertIn("display_name", test_model, "Missing editable metadata field")
     self.assertEqual(test_model["display_name"], "jolly roger", "not expected value")
     self.assertIn("advertised_start", test_model, "Missing revised advertised_start metadata field")
     self.assertEqual(test_model["advertised_start"], "start B", "advertised_start not expected value")
Пример #32
0
    def test_fetch_initial_fields(self):
        test_model = CourseMetadata.fetch(self.course_location)
        self.assertIn('display_name', test_model,
                      'Missing editable metadata field')
        self.assertEqual(test_model['display_name'], 'Robot Super Course',
                         "not expected value")

        test_model = CourseMetadata.fetch(self.fullcourse_location)
        self.assertNotIn('graceperiod', test_model,
                         'blacklisted field leaked in')
        self.assertIn('display_name', test_model,
                      'full missing editable metadata field')
        self.assertEqual(test_model['display_name'], 'Testing',
                         "not expected value")
        self.assertIn('rerandomize', test_model,
                      'Missing rerandomize metadata field')
        self.assertIn('showanswer', test_model, 'showanswer field ')
        self.assertIn('xqa_key', test_model, 'xqa_key field ')
Пример #33
0
def advanced_settings_handler(request, course_key_string):
    """
    Course settings configuration
    GET
        html: get the page
        json: get the model
    PUT, POST
        json: update the Course's settings. The payload is a json rep of the
            metadata dicts.
    """
    course_key = CourseKey.from_string(course_key_string)
    course_module = _get_course_module(course_key, request.user)
    if 'text/html' in request.META.get('HTTP_ACCEPT',
                                       '') and request.method == 'GET':

        return render_to_response(
            'settings_advanced.html', {
                'context_course':
                course_module,
                'advanced_dict':
                json.dumps(CourseMetadata.fetch(course_module)),
                'advanced_settings_url':
                reverse_course_url('advanced_settings_handler', course_key)
            })
    elif 'application/json' in request.META.get('HTTP_ACCEPT', ''):
        if request.method == 'GET':
            return JsonResponse(CourseMetadata.fetch(course_module))
        else:
            try:
                # Whether or not to filter the tabs key out of the settings metadata
                filter_tabs = _config_course_advanced_components(
                    request, course_module)
                return JsonResponse(
                    CourseMetadata.update_from_json(
                        course_module,
                        request.json,
                        filter_tabs=filter_tabs,
                        user=request.user,
                    ))
            except (TypeError, ValueError) as err:
                return HttpResponseBadRequest(django.utils.html.escape(
                    err.message),
                                              content_type="text/plain")
Пример #34
0
 def test_delete_key(self):
     test_model = CourseMetadata.update_from_json(self.fullcourse, {"unsetKeys": ["showanswer", "xqa_key"]})
     # ensure no harm
     self.assertNotIn("graceperiod", test_model, "blacklisted field leaked in")
     self.assertIn("display_name", test_model, "full missing editable metadata field")
     self.assertEqual(test_model["display_name"], "Robot Super Course", "not expected value")
     self.assertIn("rerandomize", test_model, "Missing rerandomize metadata field")
     # check for deletion effectiveness
     self.assertEqual("finished", test_model["showanswer"], "showanswer field still in")
     self.assertEqual(None, test_model["xqa_key"], "xqa_key field still in")
 def test_delete_key(self):
     test_model = CourseMetadata.delete_key(self.fullcourse_location, {'deleteKeys': ['doesnt_exist', 'showanswer', 'xqa_key']})
     # ensure no harm
     self.assertNotIn('graceperiod', test_model, 'blacklisted field leaked in')
     self.assertIn('display_name', test_model, 'full missing editable metadata field')
     self.assertEqual(test_model['display_name'], 'Robot Super Course', "not expected value")
     self.assertIn('rerandomize', test_model, 'Missing rerandomize metadata field')
     # check for deletion effectiveness
     self.assertEqual('finished', test_model['showanswer'], 'showanswer field still in')
     self.assertEqual(None, test_model['xqa_key'], 'xqa_key field still in')
Пример #36
0
def advanced_settings_handler(request, course_id=None, branch=None, version_guid=None, block=None, tag=None):
    """
    Course settings configuration
    GET
        html: get the page
        json: get the model
    PUT, POST
        json: update the Course's settings. The payload is a json rep of the
            metadata dicts. The dict can include a "unsetKeys" entry which is a list
            of keys whose values to unset: i.e., revert to default
    """
    locator = BlockUsageLocator(course_id=course_id, branch=branch, version_guid=version_guid, usage_id=block)
    if not has_access(request.user, locator):
        raise PermissionDenied()

    course_old_location = loc_mapper().translate_locator_to_location(locator)
    course_module = modulestore().get_item(course_old_location)

    if 'text/html' in request.META.get('HTTP_ACCEPT', '') and request.method == 'GET':

        return render_to_response('settings_advanced.html', {
            'context_course': course_module,
            'advanced_dict': json.dumps(CourseMetadata.fetch(course_module)),
            'advanced_settings_url': locator.url_reverse('settings/advanced')
        })
    elif 'application/json' in request.META.get('HTTP_ACCEPT', ''):
        if request.method == 'GET':
            return JsonResponse(CourseMetadata.fetch(course_module))
        else:
            # Whether or not to filter the tabs key out of the settings metadata
            filter_tabs = _config_course_advanced_components(request, course_module)
            try:
                return JsonResponse(CourseMetadata.update_from_json(
                    course_module,
                    request.json,
                    filter_tabs=filter_tabs
                ))
            except (TypeError, ValueError) as err:
                return HttpResponseBadRequest(
                    "Incorrect setting format. {}".format(err),
                    content_type="text/plain"
                )
 def test_update_from_json_filtered_off(self):
     """
     If feature flag is on, then giturl must not be updated.
     """
     test_model = CourseMetadata.update_from_json(self.course, {
         "giturl": {
             "value": "http://example.com"
         },
     },
                                                  user=self.user)
     self.assertNotIn('giturl', test_model)
Пример #38
0
 def test_update_from_json_filtered_edxnotes_off(self):
     """
     If feature flag is off, then edxnotes must not be updated.
     """
     test_model = CourseMetadata.update_from_json(
         self.course,
         {
             "edxnotes": {"value": "true"},
         },
         user=self.user
     )
     self.assertNotIn('edxnotes', test_model)
Пример #39
0
 def test_delete_key(self):
     test_model = CourseMetadata.delete_key(
         self.fullcourse_location, {"deleteKeys": ["doesnt_exist", "showanswer", "xqa_key"]}
     )
     # ensure no harm
     self.assertNotIn("graceperiod", test_model, "blacklisted field leaked in")
     self.assertIn("display_name", test_model, "full missing editable metadata field")
     self.assertEqual(test_model["display_name"], "Testing", "not expected value")
     self.assertIn("rerandomize", test_model, "Missing rerandomize metadata field")
     # check for deletion effectiveness
     self.assertEqual("closed", test_model["showanswer"], "showanswer field still in")
     self.assertEqual(None, test_model["xqa_key"], "xqa_key field still in")
Пример #40
0
 def test_update_from_json_filtered_off(self):
     """
     If feature flag is on, then giturl must not be updated.
     """
     test_model = CourseMetadata.update_from_json(
         self.course,
         {
             "giturl": {"value": "http://example.com"},
         },
         user=self.user
     )
     self.assertNotIn('giturl', test_model)
Пример #41
0
 def test_update_from_json_filtered_edxnotes_off(self):
     """
     If feature flag is off, then edxnotes must not be updated.
     """
     test_model = CourseMetadata.update_from_json(
         self.course,
         {
             "edxnotes": {"value": "true"},
         },
         user=self.user
     )
     self.assertNotIn('edxnotes', test_model)
Пример #42
0
 def test_validate_update_filtered_on(self):
     """
     If feature flag is on, then giturl must not be filtered.
     """
     # pylint: disable=unused-variable
     is_valid, errors, test_model = CourseMetadata.validate_and_update_from_json(
         self.course,
         {
             "giturl": {"value": "http://example.com"},
         },
         user=self.user
     )
     self.assertIn('giturl', test_model)
Пример #43
0
 def test_validate_update_filtered_edxnotes_on(self):
     """
     If feature flag is on, then edxnotes must not be filtered.
     """
     # pylint: disable=unused-variable
     is_valid, errors, test_model = CourseMetadata.validate_and_update_from_json(
         self.course,
         {
             "edxnotes": {"value": "true"},
         },
         user=self.user
     )
     self.assertIn('edxnotes', test_model)
Пример #44
0
 def test_validate_update_filtered_edxnotes_on(self):
     """
     If feature flag is on, then edxnotes must not be filtered.
     """
     # pylint: disable=unused-variable
     is_valid, errors, test_model = CourseMetadata.validate_and_update_from_json(
         self.course,
         {
             "edxnotes": {"value": "true"},
         },
         user=self.user
     )
     self.assertIn('edxnotes', test_model)
Пример #45
0
    def test_validate_and_update_from_json_correct_inputs(self):
        is_valid, errors, test_model = CourseMetadata.validate_and_update_from_json(
            self.course,
            {
                "advertised_start": {"value": "start A"},
                "days_early_for_beta": {"value": 2},
                "advanced_modules": {"value": ['combinedopenended']},
            },
            user=self.user
        )
        self.assertTrue(is_valid)
        self.assertTrue(len(errors) == 0)
        self.update_check(test_model)

        # fresh fetch to ensure persistence
        fresh = modulestore().get_course(self.course.id)
        test_model = CourseMetadata.fetch(fresh)
        self.update_check(test_model)

        # Tab gets tested in test_advanced_settings_munge_tabs
        self.assertIn('advanced_modules', test_model, 'Missing advanced_modules')
        self.assertEqual(test_model['advanced_modules']['value'], ['combinedopenended'], 'advanced_module is not updated')
Пример #46
0
 def test_validate_update_filtered_on(self):
     """
     If feature flag is on, then giturl must not be filtered.
     """
     # pylint: disable=unused-variable
     is_valid, errors, test_model = CourseMetadata.validate_and_update_from_json(
         self.course,
         {
             "giturl": {"value": "http://example.com"},
         },
         user=self.user
     )
     self.assertIn('giturl', test_model)
Пример #47
0
def advanced_settings_handler(request, course_key_string):
    """
    Course settings configuration
    GET
        html: get the page
        json: get the model
    PUT, POST
        json: update the Course's settings. The payload is a json rep of the
            metadata dicts. The dict can include a "unsetKeys" entry which is a list
            of keys whose values to unset: i.e., revert to default
    """
    course_key = CourseKey.from_string(course_key_string)
    course_module = _get_course_module(course_key, request.user)
    if 'text/html' in request.META.get('HTTP_ACCEPT', '') and request.method == 'GET':

        return render_to_response('settings_advanced.html', {
            'context_course': course_module,
            'advanced_dict': json.dumps(CourseMetadata.fetch(course_module)),
            'advanced_settings_url': reverse_course_url('advanced_settings_handler', course_key)
        })
    elif 'application/json' in request.META.get('HTTP_ACCEPT', ''):
        if request.method == 'GET':
            return JsonResponse(CourseMetadata.fetch(course_module))
        else:
            # Whether or not to filter the tabs key out of the settings metadata
            filter_tabs = _config_course_advanced_components(request, course_module)
            try:
                return JsonResponse(CourseMetadata.update_from_json(
                    course_module,
                    request.json,
                    filter_tabs=filter_tabs,
                    user=request.user,
                ))
            except (TypeError, ValueError) as err:
                return HttpResponseBadRequest(
                    "Incorrect setting format. {}".format(err),
                    content_type="text/plain"
                )
Пример #48
0
def _delete_entrance_exam(request, course_key):
    """
    Internal workflow operation to remove an entrance exam
    """
    store = modulestore()
    course = store.get_course(course_key)
    if course is None:
        return HttpResponse(status=400)

    course_children = store.get_items(course_key, qualifiers={"category": "chapter"})
    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))

    # Reset the entrance exam flags on the course
    # Reload the course so we have the latest state
    course = store.get_course(course_key)
    if getattr(course, "entrance_exam_id"):
        metadata = {"entrance_exam_enabled": False, "entrance_exam_minimum_score_pct": None, "entrance_exam_id": None}
        CourseMetadata.update_from_dict(metadata, course, request.user)

    return HttpResponse(status=204)
Пример #49
0
def advanced_settings_handler(request, course_key_string):
    """
    Course settings configuration
    GET
        html: get the page
        json: get the model
    PUT, POST
        json: update the Course's settings. The payload is a json rep of the
            metadata dicts.
    """
    course_key = CourseKey.from_string(course_key_string)
    course_module = _get_course_module(course_key, request.user)
    if 'text/html' in request.META.get('HTTP_ACCEPT', '') and request.method == 'GET':

        return render_to_response('settings_advanced.html', {
            'context_course': course_module,
            'advanced_dict': json.dumps(CourseMetadata.fetch(course_module)),
            'advanced_settings_url': reverse_course_url('advanced_settings_handler', course_key)
        })
    elif 'application/json' in request.META.get('HTTP_ACCEPT', ''):
        if request.method == 'GET':
            return JsonResponse(CourseMetadata.fetch(course_module))
        else:
            try:
                # Whether or not to filter the tabs key out of the settings metadata
                filter_tabs = _config_course_advanced_components(request, course_module)
                return JsonResponse(CourseMetadata.update_from_json(
                    course_module,
                    request.json,
                    filter_tabs=filter_tabs,
                    user=request.user,
                ))
            except (TypeError, ValueError) as err:
                return HttpResponseBadRequest(
                    django.utils.html.escape(err.message),
                    content_type="text/plain"
                )
Пример #50
0
def course_config_advanced_page(request, org, course, name):
    """
    Send models and views as well as html for editing the advanced course settings to the client.

    org, course, name: Attributes of the Location for the item to edit
    """
    location = get_location_and_verify_access(request, org, course, name)

    course_module = modulestore().get_item(location)

    return render_to_response('settings_advanced.html', {
        'context_course': course_module,
        'course_location': location,
        'advanced_dict': json.dumps(CourseMetadata.fetch(location)),
    })
Пример #51
0
def course_config_advanced_page(request, org, course, name):
    """
    Send models and views as well as html for editing the advanced course settings to the client.

    org, course, name: Attributes of the Location for the item to edit
    """
    location = get_location_and_verify_access(request, org, course, name)

    course_module = modulestore().get_item(location)

    return render_to_response('settings_advanced.html', {
        'context_course': course_module,
        'course_location': location,
        'advanced_dict': json.dumps(CourseMetadata.fetch(location)),
    })
Пример #52
0
def course_audit_api(request, course_id, operation):
    re_json = {"success": False}

    request_method = request.method
    if request_method != "POST":
        return JsonResponse(re_json)
    # get course location and module infomation
    try:
        course_location_info = course_id.split('.')
        locator = BlockUsageLocator(package_id=course_id,
                                    branch='draft',
                                    version_guid=None,
                                    block_id=course_location_info[-1])
        course_location = loc_mapper().translate_locator_to_location(locator)
        course_module = get_modulestore(course_location).get_item(
            course_location)

        instructors = CourseInstructorRole(locator).users_with_role()
        if len(instructors) <= 0:
            return JsonResponse(re_json)

        user = instructors[0]

        meta_json = {}
        if operation == "pass":
            meta_json["course_audit"] = 1
        elif operation == "offline":
            meta_json["course_audit"] = 0
        else:
            return JsonResponse(re_json)

        re_json["success"] = True
        CourseMetadata.update_from_json(course_module, meta_json, True, user)
        return JsonResponse(re_json)
    except:
        return JsonResponse(re_json)
Пример #53
0
def mobi_get_topics(request, course_id):
    """
    Return course topics
    """
    course_id = course_id.replace('.', '/')
    nr_transaction = newrelic.agent.current_transaction()
    try:
        course = get_course_with_access(request.user, course_id, "load_forum")
    except:
        return JsonResponse({"success": False, 'errmsg': "can not find a course with " + course_id.replace('/', '.') + " id"})


    return JsonResponse({
        "topic-list": CourseMetadata.fetch(course).pop("discussion_topics"),
        "success": True
    })
Пример #54
0
    def get(self, request, course_id):
        """ GET handler """
        with modulestore().bulk_operations(CourseKey.from_string(course_id)):
            course_module = self._get_and_validate_course_access(
                request.user, course_id)
            course_metadata = CourseMetadata().fetch_all(course_module)
            proctored_exam_settings = self._get_proctored_exam_setting_values(
                course_metadata)

            data = {}

            data['proctored_exam_settings'] = proctored_exam_settings
            data['available_proctoring_providers'] = get_available_providers()
            data['course_start_date'] = course_metadata['start'].get('value')

            serializer = ProctoredExamConfigurationSerializer(data)

            return Response(serializer.data)
Пример #55
0
def mobi_get_topics(request, course_id):
    """
    Return course topics
    """
    course_id = course_id.replace('.', '/')
    nr_transaction = newrelic.agent.current_transaction()
    try:
        course = get_course_with_access(request.user, course_id, "load_forum")
    except:
        return JsonResponse({
            "success":
            False,
            'errmsg':
            "can not find a course with " + course_id.replace('/', '.') + " id"
        })

    return JsonResponse({
        "topic-list":
        CourseMetadata.fetch(course).pop("discussion_topics"),
        "success":
        True
    })
Пример #56
0
def _create_entrance_exam(request,
                          course_key,
                          entrance_exam_minimum_score_pct=None):
    """
    Internal workflow operation to create an entrance exam
    """
    # Provide a default value for the minimum score percent if nothing specified
    if entrance_exam_minimum_score_pct is None:
        entrance_exam_minimum_score_pct = _get_default_entrance_exam_minimum_pct(
        )

    # Confirm the course exists
    course = modulestore().get_course(course_key)
    if course is None:
        return HttpResponse(status=400)

    # Create the entrance exam item (currently it's just a chapter)
    payload = {
        'category': "chapter",
        'display_name': _("Entrance Exam"),
        'parent_locator': unicode(course.location),
        'is_entrance_exam': True,
        'in_entrance_exam': True,
    }
    parent_locator = unicode(course.location)
    created_block = create_xblock(parent_locator=parent_locator,
                                  user=request.user,
                                  category='chapter',
                                  display_name=_('Entrance Exam'),
                                  is_entrance_exam=True)

    # Set the entrance exam metadata flags for this course
    # Reload the course so we don't overwrite the new child reference
    course = modulestore().get_course(course_key)
    metadata = {
        'entrance_exam_enabled': True,
        'entrance_exam_minimum_score_pct':
        unicode(entrance_exam_minimum_score_pct),
        'entrance_exam_id': unicode(created_block.location),
    }
    CourseMetadata.update_from_dict(metadata, course, request.user)

    # Create the entrance exam section item.
    create_xblock(parent_locator=unicode(created_block.location),
                  user=request.user,
                  category='sequential',
                  display_name=_('Entrance Exam - Subsection'))

    # Add an entrance exam milestone if one does not already exist
    namespace_choices = milestones_helpers.get_namespace_choices()
    milestone_namespace = milestones_helpers.generate_milestone_namespace(
        namespace_choices.get('ENTRANCE_EXAM'), course_key)
    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_helpers.add_milestone({
            'name':
            _('Completed Course Entrance Exam'),
            'namespace':
            milestone_namespace,
            'description':
            description
        })
    relationship_types = milestones_helpers.get_milestone_relationship_types()
    milestones_helpers.add_course_milestone(unicode(course.id),
                                            relationship_types['REQUIRES'],
                                            milestone)
    milestones_helpers.add_course_content_milestone(
        unicode(course.id), unicode(created_block.location),
        relationship_types['FULFILLS'], milestone)

    return HttpResponse(status=201)
Пример #57
0
def course_advanced_updates(request, org, course, name):
    """
    restful CRUD operations on metadata. The payload is a json rep of the metadata dicts. For delete, otoh,
    the payload is either a key or a list of keys to delete.

    org, course: Attributes of the Location for the item to edit
    """
    location = get_location_and_verify_access(request, org, course, name)

    real_method = get_request_method(request)

    if real_method == 'GET':
        return HttpResponse(json.dumps(CourseMetadata.fetch(location)),
                            mimetype="application/json")
    elif real_method == 'DELETE':
        return HttpResponse(json.dumps(
            CourseMetadata.delete_key(location, json.loads(request.body))),
                            mimetype="application/json")
    elif real_method == 'POST' or real_method == 'PUT':
        # NOTE: request.POST is messed up because expect_json
        # cloned_request.POST.copy() is creating a defective entry w/ the whole payload as the key
        request_body = json.loads(request.body)
        # Whether or not to filter the tabs key out of the settings metadata
        filter_tabs = True

        # Check to see if the user instantiated any advanced components. This is a hack
        # that does the following :
        #   1) adds/removes the open ended panel tab to a course automatically if the user
        #   has indicated that they want to edit the combinedopendended or peergrading module
        #   2) adds/removes the notes panel tab to a course automatically if the user has
        #   indicated that they want the notes module enabled in their course
        # TODO refactor the above into distinct advanced policy settings
        if ADVANCED_COMPONENT_POLICY_KEY in request_body:
            # Get the course so that we can scrape current tabs
            course_module = modulestore().get_item(location)

            # Maps tab types to components
            tab_component_map = {
                'open_ended': OPEN_ENDED_COMPONENT_TYPES,
                'notes': NOTE_COMPONENT_TYPES,
            }

            # Check to see if the user instantiated any notes or open ended components
            for tab_type in tab_component_map.keys():
                component_types = tab_component_map.get(tab_type)
                found_ac_type = False
                for ac_type in component_types:
                    if ac_type in request_body[ADVANCED_COMPONENT_POLICY_KEY]:
                        # Add tab to the course if needed
                        changed, new_tabs = add_extra_panel_tab(
                            tab_type, course_module)
                        # If a tab has been added to the course, then send the metadata along to CourseMetadata.update_from_json
                        if changed:
                            course_module.tabs = new_tabs
                            request_body.update({'tabs': new_tabs})
                            # Indicate that tabs should not be filtered out of the metadata
                            filter_tabs = False
                        # Set this flag to avoid the tab removal code below.
                        found_ac_type = True
                        break
                # If we did not find a module type in the advanced settings,
                # we may need to remove the tab from the course.
                if not found_ac_type:
                    # Remove tab from the course if needed
                    changed, new_tabs = remove_extra_panel_tab(
                        tab_type, course_module)
                    if changed:
                        course_module.tabs = new_tabs
                        request_body.update({'tabs': new_tabs})
                        # Indicate that tabs should *not* be filtered out of the metadata
                        filter_tabs = False
        try:
            response_json = json.dumps(
                CourseMetadata.update_from_json(location,
                                                request_body,
                                                filter_tabs=filter_tabs))
        except (TypeError, ValueError), e:
            return HttpResponseBadRequest("Incorrect setting format. " +
                                          str(e),
                                          content_type="text/plain")

        return HttpResponse(response_json, mimetype="application/json")
Пример #58
0
def proctored_exam_settings(request, course_id):
    """
    A view for retrieving information about proctored exam settings for a course.

    Path: ``/api/contentstore/v1/proctored_exam_settings/{course_id}``

    Accepts: [GET]

    ------------------------------------------------------------------------------------
    GET
    ------------------------------------------------------------------------------------

    **Returns**

        * 200: OK - Contains a set of course proctored exam settings.
        * 401: The requesting user is not authenticated.
        * 403: The requesting user lacks access to the course.
        * 404: The requested course does not exist.

    **Response**

        In the case of a 200 response code, the response will proctored exam settings data
        as well as other metadata about the course or the requesting user that are necessary
        for rendering the settings page.

    **Example**

        {
            "proctored_exam_settings": {
                "enable_proctored_exams": true,
                "allow_proctoring_opt_out": true,
                "proctoring_provider": "mockprock",
                "proctoring_escalation_email": null,
                "create_zendesk_tickets": true
            },
            "available_proctoring_providers": [
                "mockprock",
                "proctortrack"
            ],
            "course_start_date": "2013-02-05T05:00:00Z",
            "is_staff": true
        }
    """
    course_key = CourseKey.from_string(course_id)
    with modulestore().bulk_operations(course_key):
        if request.method == 'GET':
            course_module = get_course_and_check_access(
                course_key, request.user)

            if not course_module:
                return Response(
                    'Course with course_id {} does not exist.'.format(
                        course_id),
                    status=status.HTTP_404_NOT_FOUND)

            course_metadata = CourseMetadata().fetch_all(course_module)
            data = {}

            # specify only the advanced settings we want to return
            proctored_exam_settings_advanced_settings_keys = [
                'enable_proctored_exams', 'allow_proctoring_opt_out',
                'proctoring_provider', 'proctoring_escalation_email',
                'create_zendesk_tickets', 'start'
            ]
            proctored_exam_settings_data = {
                setting_key: setting_value.get('value')
                for (setting_key, setting_value) in course_metadata.items() if
                setting_key in proctored_exam_settings_advanced_settings_keys
            }

            data['proctored_exam_settings'] = proctored_exam_settings_data
            data['available_proctoring_providers'] = get_available_providers()

            # move start key:value out of proctored_exam_settings dictionary and change key
            data['course_start_date'] = proctored_exam_settings_data['start']
            del data['proctored_exam_settings']['start']

            data['is_staff'] = request.user.is_staff

            serializer = ProctoredExamConfigurationSerializer(data)

            return Response(serializer.data)
Пример #59
0
def _create_entrance_exam(request,
                          course_key,
                          entrance_exam_minimum_score_pct=None):
    """
    Internal workflow operation to create an entrance exam
    """
    # Provide a default value for the minimum score percent if nothing specified
    if entrance_exam_minimum_score_pct is None:
        entrance_exam_minimum_score_pct = float(
            settings.ENTRANCE_EXAM_MIN_SCORE_PCT)

    # Confirm the course exists
    course = modulestore().get_course(course_key)
    if course is None:
        return HttpResponse(status=400)

    # Create the entrance exam item (currently it's just a chapter)
    payload = {
        'category': "chapter",
        'display_name': "Entrance Exam",
        'parent_locator': unicode(course.location),
        'is_entrance_exam': True,
        'in_entrance_exam': True,
    }
    factory = RequestFactory()
    internal_request = factory.post('/',
                                    json.dumps(payload),
                                    content_type="application/json")
    internal_request.user = request.user
    created_item = json.loads(create_item(internal_request).content)

    # Set the entrance exam metadata flags for this course
    # Reload the course so we don't overwrite the new child reference
    course = modulestore().get_course(course_key)
    metadata = {
        'entrance_exam_enabled': True,
        'entrance_exam_minimum_score_pct':
        entrance_exam_minimum_score_pct / 100,
        'entrance_exam_id': created_item['locator'],
    }
    CourseMetadata.update_from_dict(metadata, course, request.user)

    # Add an entrance exam milestone if one does not already exist
    milestone_namespace = generate_milestone_namespace(
        NAMESPACE_CHOICES['ENTRANCE_EXAM'], course_key)
    milestones = milestones_api.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({
            'name': 'Completed Course Entrance Exam',
            'namespace': milestone_namespace,
            'description': description
        })
    relationship_types = milestones_api.get_milestone_relationship_types()
    milestones_api.add_course_milestone(unicode(course.id),
                                        relationship_types['REQUIRES'],
                                        milestone)
    milestones_api.add_course_content_milestone(unicode(course.id),
                                                created_item['locator'],
                                                relationship_types['FULFILLS'],
                                                milestone)

    return HttpResponse(status=201)