def get_course_info(course):
    course_info = {}
    course_info['course_id'] = course.course_id
    course_info['name'] = course.name
    course_info['thumbnail'] = course.thumbnail
    course_info['course_num'] = course.course_num
    course_info['modified'] = naturalFormatDate(course.modified)
    course_info['enrollment'] = course_stat.stat.enrollment_total_count(
        SlashSeparatedCourseKey.from_deprecated_string(course.course_id))
    course_info['comment'] = course_stat.stat.comment_total_count(
        SlashSeparatedCourseKey.from_deprecated_string(course.course_id))
    course_info['subtitle'] = course.subtitle
    course_info['about'] = reverse('about_course', args=[course.course_id])
    course_info['start_time'] = time_format(course.start)
    course_info['end_time'] = time_format(course.end)

    try:
        staff = course.staff.all()[0]
        course_info['staff_avatar'] = staff.avartar
        course_info['staff_name'] = staff.name
        course_info['staff_title'] = "%s %s %s" % (
            staff.company, staff.department, staff.position)
    except:
        course_info['staff_avatar'] = ''
        course_info['staff_name'] = ''
        course_info['staff_title'] = ''

    return course_info
Example #2
0
class TestInstructorEnrollmentStudentModule(TestCase):
    """ Test student module manipulations. """

    def setUp(self):
        super(TestInstructorEnrollmentStudentModule, self).setUp()
        self.course_key = SlashSeparatedCourseKey("fake", "course", "id")

    def test_reset_student_attempts(self):
        user = UserFactory()
        msk = self.course_key.make_usage_key("dummy", "module")
        original_state = json.dumps({"attempts": 32, "otherstuff": "alsorobots"})
        StudentModule.objects.create(
            student=user, course_id=self.course_key, module_state_key=msk, state=original_state
        )
        # lambda to reload the module state from the database
        module = lambda: StudentModule.objects.get(student=user, course_id=self.course_key, module_state_key=msk)
        self.assertEqual(json.loads(module().state)["attempts"], 32)
        reset_student_attempts(self.course_key, user, msk)
        self.assertEqual(json.loads(module().state)["attempts"], 0)

    def test_delete_student_attempts(self):
        user = UserFactory()
        msk = self.course_key.make_usage_key("dummy", "module")
        original_state = json.dumps({"attempts": 32, "otherstuff": "alsorobots"})
        StudentModule.objects.create(
            student=user, course_id=self.course_key, module_state_key=msk, state=original_state
        )
        self.assertEqual(
            StudentModule.objects.filter(student=user, course_id=self.course_key, module_state_key=msk).count(), 1
        )
        reset_student_attempts(self.course_key, user, msk, delete_module=True)
        self.assertEqual(
            StudentModule.objects.filter(student=user, course_id=self.course_key, module_state_key=msk).count(), 0
        )

    def test_delete_submission_scores(self):
        user = UserFactory()
        problem_location = self.course_key.make_usage_key("dummy", "module")

        # Create a student module for the user
        StudentModule.objects.create(
            student=user, course_id=self.course_key, module_state_key=problem_location, state=json.dumps({})
        )

        # Create a submission and score for the student using the submissions API
        student_item = {
            "student_id": anonymous_id_for_user(user, self.course_key),
            "course_id": self.course_key.to_deprecated_string(),
            "item_id": problem_location.to_deprecated_string(),
            "item_type": "openassessment",
        }
        submission = sub_api.create_submission(student_item, "test answer")
        sub_api.set_score(submission["uuid"], 1, 2)

        # Delete student state using the instructor dash
        reset_student_attempts(self.course_key, user, problem_location, delete_module=True)

        # Verify that the student's scores have been reset in the submissions API
        score = sub_api.get_score(student_item)
        self.assertIs(score, None)
Example #3
0
    def is_enrolled_by_partial(cls, user, course_id_partial):
        """
        Returns `True` if the user is enrolled in a course that starts with
        `course_id_partial`. Otherwise, returns False.

        Can be used to determine whether a student is enrolled in a course
        whose run name is unknown.

        `user` is a Django User object. If it hasn't been saved yet (no `.id`
               attribute), this method will automatically save it before
               adding an enrollment for it.

        `course_id_partial` (CourseKey) is missing the run component
        """
        assert isinstance(course_id_partial, SlashSeparatedCourseKey)
        assert not course_id_partial.run  # None or empty string
        course_key = SlashSeparatedCourseKey(course_id_partial.org, course_id_partial.course, '')
        querystring = unicode(course_key.to_deprecated_string())
        try:
            return CourseEnrollment.objects.filter(
                user=user,
                course_id__startswith=querystring,
                is_active=1
            ).exists()
        except cls.DoesNotExist:
            return False
Example #4
0
 def _decorated(request, course_id, *args, **kwargs):
     try:
         SlashSeparatedCourseKey.from_deprecated_string(course_id)
     except InvalidKeyError:
         raise Http404
     response = view_func(request, course_id, *args, **kwargs)
     return response
Example #5
0
    def test_delete_course_map(self):
        """
        Test that course location is properly remove from loc_mapper and cache when course is deleted
        """
        org = u"foo_org"
        course = u"bar_course"
        run = u"baz_run"
        course_location = SlashSeparatedCourseKey(org, course, run)
        loc_mapper().create_map_entry(course_location)
        # pylint: disable=protected-access
        entry = loc_mapper().location_map.find_one({"_id": loc_mapper()._construct_course_son(course_location)})
        self.assertIsNotNone(entry, "Entry not found in loc_mapper")
        self.assertEqual(entry["offering"], u"{1}.{2}".format(org, course, run))

        # now delete course location from loc_mapper and cache and test that course location no longer
        # exists in loca_mapper and cache
        loc_mapper().delete_course_mapping(course_location)
        # pylint: disable=protected-access
        entry = loc_mapper().location_map.find_one({"_id": loc_mapper()._construct_course_son(course_location)})
        self.assertIsNone(entry, "Entry found in loc_mapper")
        # pylint: disable=protected-access
        cached_value = loc_mapper()._get_location_from_cache(course_location.make_usage_key("course", run))
        self.assertIsNone(cached_value, "course_locator found in cache")
        # pylint: disable=protected-access
        cached_value = loc_mapper()._get_course_location_from_cache(course_location)
        self.assertIsNone(cached_value, "Entry found in cache")
    def test_round_trip(self, source_builder, dest_builder, source_content_builder, dest_content_builder, course_data_name):
        source_course_key = SlashSeparatedCourseKey('source', 'course', 'key')
        dest_course_key = SlashSeparatedCourseKey('dest', 'course', 'key')

        # Construct the contentstore for storing the first import
        with source_content_builder.build() as source_content:
            # Construct the modulestore for storing the first import (using the previously created contentstore)
            with source_builder.build(source_content) as source_store:
                # Construct the contentstore for storing the second import
                with dest_content_builder.build() as dest_content:
                    # Construct the modulestore for storing the second import (using the second contentstore)
                    with dest_builder.build(dest_content) as dest_store:
                        import_from_xml(
                            source_store,
                            'test_user',
                            'common/test/data',
                            course_dirs=[course_data_name],
                            static_content_store=source_content,
                            target_course_id=source_course_key,
                            create_new_course_if_not_present=True,
                        )

                        export_to_xml(
                            source_store,
                            source_content,
                            source_course_key,
                            self.export_dir,
                            'exported_course',
                        )

                        import_from_xml(
                            dest_store,
                            'test_user',
                            self.export_dir,
                            static_content_store=dest_content,
                            target_course_id=dest_course_key,
                            create_new_course_if_not_present=True,
                        )

                        self.exclude_field(source_course_key.make_usage_key('course', 'key'), 'wiki_slug')
                        self.exclude_field(None, 'xml_attributes')
                        self.ignore_asset_key('_id')
                        self.ignore_asset_key('uploadDate')
                        self.ignore_asset_key('content_son')
                        self.ignore_asset_key('thumbnail_location')

                        self.assertCoursesEqual(
                            source_store,
                            source_course_key,
                            dest_store,
                            dest_course_key,
                        )

                        self.assertAssetsEqual(
                            source_content,
                            source_course_key,
                            dest_content,
                            dest_course_key,
                        )
Example #7
0
 def setUp(self):
     course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall')
     self.course = course_key.make_usage_key('course', course_key.run)
     self.anonymous_user = AnonymousUserFactory()
     self.student = UserFactory()
     self.global_staff = UserFactory(is_staff=True)
     self.course_staff = StaffFactory(course_key=self.course.course_key)
     self.course_instructor = InstructorFactory(course_key=self.course.course_key)
Example #8
0
 def setUp(self):
     system = get_test_descriptor_system()
     course_key = SlashSeparatedCourseKey("org", "course", "run")
     usage_key = course_key.make_usage_key("video", "name")
     self.descriptor = system.construct_xblock_from_class(
         VideoDescriptor, scope_ids=ScopeIds(None, None, usage_key, usage_key), field_data=DictFieldData({})
     )
     self.descriptor.runtime.handler_url = MagicMock()
Example #9
0
def i_click_on_error_dialog(step):
    world.click_link_by_text('Correct failed component')
    assert_true(world.css_html("span.inline-error").startswith("Problem i4x://MITx/999/problem"))
    course_key = SlashSeparatedCourseKey("MITx", "999", "Robot_Super_Course")
    # we don't know the actual ID of the vertical. So just check that we did go to a
    # vertical page in the course (there should only be one).
    vertical_usage_key = course_key.make_usage_key("vertical", "")
    vertical_url = reverse_usage_url('unit_handler', vertical_usage_key)
    assert_equal(1, world.browser.url.count(vertical_url))
    def setup_xml_course(self):
        """
        Define the XML backed course to use.
        Toy courses are already loaded in XML and mixed modulestores.
        """
        course_key = SlashSeparatedCourseKey("edX", "toy", "2012_Fall")
        location = course_key.make_usage_key("chapter", "Overview")
        descriptor = modulestore().get_item(location)

        self.module = self._get_module(course_key, descriptor, location)
Example #11
0
def instantiate_descriptor(**field_data):
    """
    Instantiate descriptor with most properties.
    """
    system = get_test_descriptor_system()
    course_key = SlashSeparatedCourseKey("org", "course", "run")
    usage_key = course_key.make_usage_key("html", "SampleHtml")
    return system.construct_xblock_from_class(
        HtmlDescriptor, scope_ids=ScopeIds(None, None, usage_key, usage_key), field_data=DictFieldData(field_data)
    )
 def test_deprecated_replace(self):
     with self.assertDeprecationWarning(count=3):
         ssck = SlashSeparatedCourseKey("foo", "bar", "baz")
         ssck_boo = ssck.replace(org='boo')
         ssck_copy = ssck.replace()
     self.assertTrue(isinstance(ssck_boo, CourseLocator))
     self.assertTrue(ssck_boo.deprecated)
     self.assertNotEqual(id(ssck), id(ssck_boo))
     self.assertNotEqual(id(ssck), id(ssck_copy))
     self.assertNotEqual(ssck, ssck_boo)
     self.assertEqual(ssck, ssck_copy)
Example #13
0
 def setUp(self):
     super(AccessTestCase, self).setUp()
     course_key = SlashSeparatedCourseKey("edX", "toy", "2012_Fall")
     self.course = course_key.make_usage_key("course", course_key.run)
     self.anonymous_user = AnonymousUserFactory()
     self.beta_user = BetaTesterFactory(course_key=self.course.course_key)
     self.student = UserFactory()
     self.global_staff = UserFactory(is_staff=True)
     self.course_staff = StaffFactory(course_key=self.course.course_key)
     self.course_instructor = InstructorFactory(course_key=self.course.course_key)
     self.staff = GlobalStaffFactory()
Example #14
0
def instantiate_descriptor(**field_data):
    """
    Instantiate descriptor with most properties.
    """
    system = get_test_descriptor_system()
    course_key = SlashSeparatedCourseKey('org', 'course', 'run')
    usage_key = course_key.make_usage_key('video', 'SampleProblem')
    return system.construct_xblock_from_class(
        VideoDescriptor,
        scope_ids=ScopeIds(None, None, usage_key, usage_key),
        field_data=DictFieldData(field_data),
    )
Example #15
0
    def import_and_populate_course(self):
        """
        Imports the test toy course and populates it with additional test data
        """
        content_store = contentstore()
        import_from_xml(self.store, self.user.id, 'common/test/data/', ['toy'], static_content_store=content_store)
        course_id = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall')

        # create an Orphan
        # We had a bug where orphaned draft nodes caused export to fail. This is here to cover that case.
        vertical = self.store.get_item(course_id.make_usage_key('vertical', self.TEST_VERTICAL), depth=1)
        vertical.location = vertical.location.replace(name='no_references')
        self.store.update_item(vertical, self.user.id, allow_not_found=True)
        orphan_vertical = self.store.get_item(vertical.location)
        self.assertEqual(orphan_vertical.location.name, 'no_references')
        self.assertEqual(len(orphan_vertical.children), len(vertical.children))

        # create a Draft vertical
        vertical = self.store.get_item(course_id.make_usage_key('vertical', self.TEST_VERTICAL), depth=1)
        draft_vertical = self.store.convert_to_draft(vertical.location, self.user.id)
        self.assertEqual(self.store.compute_publish_state(draft_vertical), PublishState.draft)

        # create a Private (draft only) vertical
        private_vertical = self.store.create_item(self.user.id, course_id, 'vertical', self.PRIVATE_VERTICAL)
        self.assertEqual(self.store.compute_publish_state(private_vertical), PublishState.private)

        # create a Published (no draft) vertical
        public_vertical = self.store.create_item(self.user.id, course_id, 'vertical', self.PUBLISHED_VERTICAL)
        public_vertical = self.store.publish(public_vertical.location, self.user.id)
        self.assertEqual(self.store.compute_publish_state(public_vertical), PublishState.public)

        # add the new private and new public as children of the sequential
        sequential = self.store.get_item(course_id.make_usage_key('sequential', self.SEQUENTIAL))
        sequential.children.append(private_vertical.location)
        sequential.children.append(public_vertical.location)
        self.store.update_item(sequential, self.user.id)

        # lock an asset
        content_store.set_attr(self.LOCKED_ASSET_KEY, 'locked', True)

        # create a non-portable link - should be rewritten in new courses
        html_module = self.store.get_item(course_id.make_usage_key('html', 'nonportable'))
        new_data = html_module.data = html_module.data.replace(
            '/static/',
            '/c4x/{0}/{1}/asset/'.format(course_id.org, course_id.course)
        )
        self.store.update_item(html_module, self.user.id)

        html_module = self.store.get_item(html_module.location)
        self.assertEqual(new_data, html_module.data)

        return course_id
def i_click_on_error_dialog(step):
    world.click_link_by_text('Correct failed component')
    assert_true(world.css_html("span.inline-error").startswith("Problem i4x://MITx/999/problem"))
    course_key = SlashSeparatedCourseKey("MITx", "999", "Robot_Super_Course")
    # we don't know the actual ID of the vertical. So just check that we did go to a
    # vertical page in the course (there should only be one).
    vertical_usage_key = course_key.make_usage_key("vertical", None)
    vertical_url = reverse_usage_url('container_handler', vertical_usage_key)
    # Remove the trailing "/None" from the URL - we don't know the course ID, so we just want to
    # check that we visited a vertical URL.
    if vertical_url.endswith("/None"):
        vertical_url = vertical_url[:-5]
    assert_equal(1, world.browser.url.count(vertical_url))
Example #17
0
    def create_course(self, org, offering, user_id=None, fields=None, **kwargs):
        """
        Creates and returns the course.

        Args:
            org (str): the organization that owns the course
            offering (str): the name of the course offering
            user_id: id of the user creating the course
            fields (dict): Fields to set on the course at initialization
            kwargs: Any optional arguments understood by a subset of modulestores to customize instantiation

        Returns: a CourseDescriptor

        Raises:
            InvalidLocationError: If a course with the same org and offering already exists
        """

        course, _, run = offering.partition('/')
        course_id = SlashSeparatedCourseKey(org, course, run)

        # Check if a course with this org/course has been defined before (case-insensitive)
        course_search_location = SON([
            ('_id.tag', 'i4x'),
            ('_id.org', re.compile(u'^{}$'.format(course_id.org), re.IGNORECASE)),
            ('_id.course', re.compile(u'^{}$'.format(course_id.course), re.IGNORECASE)),
            ('_id.category', 'course'),
        ])
        courses = self.collection.find(course_search_location, fields=('_id'))
        if courses.count() > 0:
            raise InvalidLocationError(
                "There are already courses with the given org and course id: {}".format([
                    course['_id'] for course in courses
                ]))

        location = course_id.make_usage_key('course', course_id.run)
        course = self.create_and_save_xmodule(location, user_id, fields=fields, **kwargs)

        # clone a default 'about' overview module as well
        about_location = location.replace(
            category='about',
            name='overview'
        )
        overview_template = AboutDescriptor.get_template('overview.yaml')
        self.create_and_save_xmodule(
            about_location,
            user_id,
            definition_data=overview_template.get('data'),
            runtime=course.system
        )

        return course
    def post(self, request, course_id, format=None):
        """ Enroll in Course """
        user = request.user
        err = {}
        try:
            course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)

            course = course_from_key(course_key)
        except ItemNotFoundError:
            err['err_type'] = 'InvalidCourseId'
            err['err_msg'] = _("Course id is invalid")
            return Response(err, status=status.HTTP_400_BAD_REQUEST)

        if not has_access(user, 'enroll', course):
            err['err_type'] = 'InvalidEnrollment'
            err['err_msg'] = _("Enrollment is closed")
            return Response(err, status=status.HTTP_400_BAD_REQUEST)

        # see if we have already filled up all allowed enrollments
        is_course_full = CourseEnrollment.is_course_full(course)

        if is_course_full:
            err['err_type'] = 'InvalidEnrollment'
            err['err_msg'] = _("Course is full")
            return Response(err, status=status.HTTP_400_BAD_REQUEST)

        # If this course is available in multiple modes, redirect them to a page
        # where they can choose which mode they want.
        available_modes = CourseMode.modes_for_course(course_id)
        available_modes_dict = CourseMode.modes_for_course_dict(course_id, available_modes)
        if CourseMode.has_verified_mode(available_modes_dict):
            err['err_type'] = 'InvalidEnrollment'
            err['err_msg'] = _("Missing course mode")
            return Response(err, status=status.HTTP_400_BAD_REQUEST)

        current_mode = available_modes[0]
        course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
        dog_stats_api.increment(
            "common.student.enrollment",
            tags=[u"org:{0}".format(course_key.org),
                  u"course:{0}".format(course_key.course),
                  u"run:{0}".format(course_key.run)]
        )
        server_track(request, 'api.course.enrollment', {
            'username': user.username,
            'course_id': course_id,
        })

        CourseEnrollment.enroll(user, course.id, mode=current_mode.slug)
        return Response()
Example #19
0
    def test_graphicslidertool_import(self):
        '''
        Check to see if definition_from_xml in gst_module.py
        works properly.  Pulls data from the graphic_slider_tool directory
        in the test data directory.
        '''
        modulestore = XMLModuleStore(DATA_DIR, course_dirs=['graphic_slider_tool'])

        sa_id = SlashSeparatedCourseKey("edX", "gst_test", "2012_Fall")
        location = sa_id.make_usage_key("graphical_slider_tool", "sample_gst")
        gst_sample = modulestore.get_item(location)
        render_string_from_sample_gst_xml = """
        <slider var="a" style="width:400px;float:left;"/>\
<plot style="margin-top:15px;margin-bottom:15px;"/>""".strip()
        self.assertIn(render_string_from_sample_gst_xml, gst_sample.data)
Example #20
0
    def lms_link_test(self):
        """ Tests get_lms_link_for_item. """
        course_key = SlashSeparatedCourseKey("mitX", "101", "test")
        location = course_key.make_usage_key("vertical", "contacting_us")
        link = utils.get_lms_link_for_item(location, False)
        self.assertEquals(link, "//localhost:8000/courses/mitX/101/test/jump_to/i4x://mitX/101/vertical/contacting_us")

        # test preview
        link = utils.get_lms_link_for_item(location, True)
        self.assertEquals(link, "//preview/courses/mitX/101/test/jump_to/i4x://mitX/101/vertical/contacting_us")

        # now test with the course' location
        location = course_key.make_usage_key("course", "test")
        link = utils.get_lms_link_for_item(location)
        self.assertEquals(link, "//localhost:8000/courses/mitX/101/test/jump_to/i4x://mitX/101/course/test")
Example #21
0
def static_tab(request, course_id, tab_slug):
    """
    Display the courses tab with the given name.

    Assumes the course_id is in a valid format.
    """
    try:
        course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
    except InvalidKeyError:
        raise Http404

    course = get_course_with_access(request.user, 'load', course_key)

    tab = CourseTabList.get_tab_by_slug(course.tabs, tab_slug)
    if tab is None:
        raise Http404

    contents = get_static_tab_contents(
        request,
        course,
        tab
    )
    if contents is None:
        raise Http404

    return render_to_response('courseware/static_tab.html', {
        'course': course,
        'tab': tab,
        'tab_contents': contents,
    })
Example #22
0
    def clean_course_id(self):
        """Validate the course id"""
        cleaned_id = self.cleaned_data["course_id"]
        try:
            course_key = CourseKey.from_string(cleaned_id)
        except InvalidKeyError:
            try:
                course_key = SlashSeparatedCourseKey.from_deprecated_string(cleaned_id)
            except InvalidKeyError:
                msg = u'Course id invalid.'
                msg += u' --- Entered course id was: "{0}". '.format(cleaned_id)
                msg += 'Please recheck that you have supplied a valid course id.'
                raise forms.ValidationError(msg)

        if not modulestore().has_course(course_key):
            msg = u'COURSE NOT FOUND'
            msg += u' --- Entered course id was: "{0}". '.format(course_key.to_deprecated_string())
            msg += 'Please recheck that you have supplied a valid course id.'
            raise forms.ValidationError(msg)

        # Now, try and discern if it is a Studio course - HTML editor doesn't work with XML courses
        is_studio_course = modulestore().get_modulestore_type(course_key) != ModuleStoreEnum.Type.xml
        if not is_studio_course:
            msg = "Course Email feature is only available for courses authored in Studio. "
            msg += '"{0}" appears to be an XML backed course.'.format(course_key.to_deprecated_string())
            raise forms.ValidationError(msg)

        return course_key
Example #23
0
def request_certificate(request):
    """Request the on-demand creation of a certificate for some user, course.

    A request doesn't imply a guarantee that such a creation will take place.
    We intentionally use the same machinery as is used for doing certification
    at the end of a course run, so that we can be sure users get graded and
    then if and only if they pass, do they get a certificate issued.
    """
    if request.method == "POST":
        if request.user.is_authenticated():
            xqci = XQueueCertInterface()
            username = request.user.username
            student = User.objects.get(username=username)
            course_key = SlashSeparatedCourseKey.from_deprecated_string(request.POST.get("course_id"))
            course = modulestore().get_course(course_key, depth=2)

            status = certificate_status_for_student(student, course_key)["status"]
            if status in [CertificateStatuses.unavailable, CertificateStatuses.notpassing, CertificateStatuses.error]:
                logger.info(
                    "Grading and certification requested for user {} in course {} via /request_certificate call".format(
                        username, course_key
                    )
                )
                status = xqci.add_cert(student, course_key, course=course)
            return HttpResponse(json.dumps({"add_status": status}), mimetype="application/json")
        return HttpResponse(json.dumps({"add_status": "ERRORANONYMOUSUSER"}), mimetype="application/json")
Example #24
0
def jump_to(request, course_id, location):
    """
    Show the page that contains a specific location.

    If the location is invalid or not in any class, return a 404.

    Otherwise, delegates to the index view to figure out whether this user
    has access, and what they should see.
    """
    try:
        course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
        usage_key = course_key.make_usage_key_from_deprecated_string(location)
    except InvalidKeyError:
        raise Http404(u"Invalid course_key or usage_key")
    try:
        (course_key, chapter, section, position) = path_to_location(modulestore(), usage_key)
    except ItemNotFoundError:
        raise Http404(u"No data at this location: {0}".format(usage_key))
    except NoPathToItem:
        raise Http404(u"This location is not in any class: {0}".format(usage_key))

    # choose the appropriate view (and provide the necessary args) based on the
    # args provided by the redirect.
    # Rely on index to do all error handling and access control.
    if chapter is None:
        return redirect('courseware', course_id=course_key.to_deprecated_string())
    elif section is None:
        return redirect('courseware_chapter', course_id=course_key.to_deprecated_string(), chapter=chapter)
    elif position is None:
        return redirect('courseware_section', course_id=course_key.to_deprecated_string(), chapter=chapter, section=section)
    else:
        return redirect('courseware_position', course_id=course_key.to_deprecated_string(), chapter=chapter, section=section, position=position)
Example #25
0
def course_info(request, course_id):
    """
    Display the course's info.html, or 404 if there is no such course.

    Assumes the course_id is in a valid format.
    """
    course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
    course = get_course_with_access(request.user, 'load', course_key)
    staff_access = has_access(request.user, 'staff', course)
    masq = setup_masquerade(request, staff_access)    # allow staff to toggle masquerade on info page
    reverifications = fetch_reverify_banner_info(request, course_key)
    studio_url = get_studio_url(course_key, 'course_info')

    context = {
        'request': request,
        'course_id': course_key.to_deprecated_string(),
        'cache': None,
        'course': course,
        'staff_access': staff_access,
        'masquerade': masq,
        'studio_url': studio_url,
        'reverifications': reverifications,
    }

    return render_to_response('courseware/info.html', context)
Example #26
0
    def setUp(self):
        self.student = '*****@*****.**'
        self.instructor = '*****@*****.**'
        self.password = '******'
        self.create_account('u1', self.student, self.password)
        self.create_account('u2', self.instructor, self.password)
        self.activate_user(self.student)
        self.activate_user(self.instructor)

        self.course_id = SlashSeparatedCourseKey("edX", "toy", "2012_Fall")
        self.location_string = self.course_id.make_usage_key('html', 'TestLocation').to_deprecated_string()
        self.toy = modulestore().get_course(self.course_id)
        location = "i4x://edX/toy/peergrading/init"
        field_data = DictFieldData({'data': "<peergrading/>", 'location': location, 'category': 'peergrading'})
        self.mock_service = peer_grading_service.MockPeerGradingService()
        self.system = LmsModuleSystem(
            static_url=settings.STATIC_URL,
            track_function=None,
            get_module=None,
            render_template=render_to_string,
            replace_urls=None,
            s3_interface=test_util_open_ended.S3_INTERFACE,
            open_ended_grading_interface=test_util_open_ended.OPEN_ENDED_GRADING_INTERFACE,
            mixins=settings.XBLOCK_MIXINS,
            error_descriptor_class=ErrorDescriptor,
            descriptor_runtime=None,
        )
        self.descriptor = peer_grading_module.PeerGradingDescriptor(self.system, field_data, ScopeIds(None, None, None, None))
        self.descriptor.xmodule_runtime = self.system
        self.peer_module = self.descriptor
        self.peer_module.peer_gs = self.mock_service
        self.logout()
Example #27
0
def progress(request, course_id, student_id=None):
    """
    Wraps "_progress" with the manual_transaction context manager just in case
    there are unanticipated errors.
    """
    with grades.manual_transaction():
        return _progress(request, SlashSeparatedCourseKey.from_deprecated_string(course_id), student_id)
Example #28
0
    def __init__(
        self,
        data_dir,
        default_class=None,
        course_dirs=None,
        course_ids=None,
        load_error_modules=True,
        i18n_service=None,
        **kwargs
    ):
        """
        Initialize an XMLModuleStore from data_dir

        Args:
            data_dir (str): path to data directory containing the course directories

            default_class (str): dot-separated string defining the default descriptor
                class to use if none is specified in entry_points

            course_dirs or course_ids (list of str): If specified, the list of course_dirs or course_ids to load. Otherwise,
                load all courses. Note, providing both
        """
        super(XMLModuleStore, self).__init__(**kwargs)

        self.data_dir = path(data_dir)
        self.modules = defaultdict(dict)  # course_id -> dict(location -> XBlock)
        self.courses = {}  # course_dir -> XBlock for the course
        self.errored_courses = {}  # course_dir -> errorlog, for dirs that failed to load

        if course_ids is not None:
            course_ids = [SlashSeparatedCourseKey.from_deprecated_string(course_id) for course_id in course_ids]

        self.load_error_modules = load_error_modules

        if default_class is None:
            self.default_class = None
        else:
            module_path, _, class_name = default_class.rpartition(".")
            class_ = getattr(import_module(module_path), class_name)
            self.default_class = class_

        self.parent_trackers = defaultdict(ParentTracker)
        self.reference_type = Location

        # All field data will be stored in an inheriting field data.
        self.field_data = inheriting_field_data(kvs=DictKeyValueStore())

        self.i18n_service = i18n_service

        # If we are specifically asked for missing courses, that should
        # be an error.  If we are asked for "all" courses, find the ones
        # that have a course.xml. We sort the dirs in alpha order so we always
        # read things in the same order (OS differences in load order have
        # bitten us in the past.)
        if course_dirs is None:
            course_dirs = sorted(
                [d for d in os.listdir(self.data_dir) if os.path.exists(self.data_dir / d / "course.xml")]
            )
        for course_dir in course_dirs:
            self.try_load_course(course_dir, course_ids)
Example #29
0
    def handle(self, *args, **options):
        if not options['course']:
            raise CommandError(Command.course_option.help)

        try:
            course_key = CourseKey.from_string(options['course'])
        except InvalidKeyError:
            course_key = SlashSeparatedCourseKey.from_deprecated_string(options['course'])

        course = get_course_by_id(course_key)

        print 'Warning: this command directly edits the list of course tabs in mongo.'
        print 'Tabs before any changes:'
        print_course(course)

        try:
            if options['delete']:
                if len(args) != 1:
                    raise CommandError(Command.delete_option.help)
                num = int(args[0])
                if query_yes_no('Deleting tab {0} Confirm?'.format(num), default='no'):
                    primitive_delete(course, num - 1)  # -1 for 0-based indexing
            elif options['insert']:
                if len(args) != 3:
                    raise CommandError(Command.insert_option.help)
                num = int(args[0])
                tab_type = args[1]
                name = args[2]
                if query_yes_no('Inserting tab {0} "{1}" "{2}" Confirm?'.format(num, tab_type, name), default='no'):
                    primitive_insert(course, num - 1, tab_type, name)  # -1 as above
        except ValueError as e:
            # Cute: translate to CommandError so the CLI error prints nicely.
            raise CommandError(e)
def set_course_mode_price(request, course_id):
    """
    set the new course price and add new entry in the CourseModesArchive Table
    """
    try:
        course_price = int(request.POST['course_price'])
    except ValueError:
        return JsonResponse(
            {'message': _("Please Enter the numeric value for the course price")},
            status=400)  # status code 400: Bad Request

    currency = request.POST['currency']
    course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)

    course_honor_mode = CourseMode.objects.filter(mode_slug='honor', course_id=course_key)
    if not course_honor_mode:
        return JsonResponse(
            {'message': _("CourseMode with the mode slug({mode_slug}) DoesNotExist").format(mode_slug='honor')},
            status=400)  # status code 400: Bad Request

    CourseModesArchive.objects.create(
        course_id=course_id, mode_slug='honor', mode_display_name='Honor Code Certificate',
        min_price=course_honor_mode[0].min_price, currency=course_honor_mode[0].currency,
        expiration_datetime=datetime.datetime.now(pytz.utc), expiration_date=datetime.date.today()
    )
    course_honor_mode.update(
        min_price=course_price,
        currency=currency
    )
    return JsonResponse({'message': _("CourseMode price updated successfully")})
Example #31
0
 def setUp(self):
     self.course_key = SlashSeparatedCourseKey('Test', 'TestCourse',
                                               'TestCourseRun')
     CourseMode.objects.all().delete()
Example #32
0
def combined_notifications(request, course_id):
    """
    Gets combined notifications from the grading controller and displays them
    """
    course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
    course = get_course_with_access(request.user, 'load', course_key)
    user = request.user
    notifications = open_ended_notifications.combined_notifications(
        course, user)
    response = notifications['response']
    notification_tuples = open_ended_notifications.NOTIFICATION_TYPES

    notification_list = []
    for response_num in xrange(len(notification_tuples)):
        tag = notification_tuples[response_num][0]
        if tag in response:
            url_name = notification_tuples[response_num][1]
            human_name = notification_tuples[response_num][2]
            url = _reverse_without_slash(url_name, course_key)
            has_img = response[tag]

            # check to make sure we have descriptions and alert messages
            if human_name in DESCRIPTION_DICT:
                description = DESCRIPTION_DICT[human_name]
            else:
                description = ""

            if human_name in ALERT_DICT:
                alert_message = ALERT_DICT[human_name]
            else:
                alert_message = ""

            notification_item = {
                'url': url,
                'name': human_name,
                'alert': has_img,
                'description': description,
                'alert_message': alert_message
            }
            #The open ended panel will need to link the "peer grading" button in the panel to a peer grading
            #xmodule defined in the course.  This checks to see if the human name of the server notification
            #that we are currently processing is "peer grading".  If it is, it looks for a peer grading
            #module in the course.  If none exists, it removes the peer grading item from the panel.
            if human_name == "Peer Grading":
                found_module, problem_url = find_peer_grading_module(course)
                if found_module:
                    notification_list.append(notification_item)
            else:
                notification_list.append(notification_item)

    ajax_url = _reverse_with_slash('open_ended_notifications', course_key)
    combined_dict = {
        'error_text': "",
        'notification_list': notification_list,
        'course': course,
        'success': True,
        'ajax_url': ajax_url,
    }

    return render_to_response(
        'open_ended_problems/combined_notifications.html', combined_dict)
Example #33
0
 def test_toy_course_loads(self):
     # Load one of the XML based courses
     # Our test mapping rules allow the MixedModuleStore
     # to load this course from XML, not Mongo.
     self.check_all_pages_load(
         SlashSeparatedCourseKey('edX', 'toy', '2012_Fall'))
Example #34
0
def course_about(request, course_id):
    """
    Display the course's about page.

    Assumes the course_id is in a valid format.
    """

    if microsite.get_value('ENABLE_MKTG_SITE',
                           settings.FEATURES.get('ENABLE_MKTG_SITE', False)):
        raise Http404
    course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
    course = get_course_with_access(request.user, 'see_exists', course_key)
    registered = registered_for_course(course, request.user)
    staff_access = has_access(request.user, 'staff', course)
    studio_url = get_studio_url(course_key, 'settings/details')

    if has_access(request.user, 'load', course):
        course_target = reverse('info',
                                args=[course.id.to_deprecated_string()])
    else:
        course_target = reverse('about_course',
                                args=[course.id.to_deprecated_string()])

    show_courseware_link = (has_access(request.user, 'load', course)
                            or settings.FEATURES.get('ENABLE_LMS_MIGRATION'))

    # Note: this is a flow for payment for course registration, not the Verified Certificate flow.
    registration_price = 0
    in_cart = False
    reg_then_add_to_cart_link = ""
    if (settings.FEATURES.get('ENABLE_SHOPPING_CART')
            and settings.FEATURES.get('ENABLE_PAID_COURSE_REGISTRATION')):
        registration_price = CourseMode.min_course_price_for_currency(
            course_key, settings.PAID_COURSE_REGISTRATION_CURRENCY[0])
        if request.user.is_authenticated():
            cart = shoppingcart.models.Order.get_cart_for_user(request.user)
            in_cart = shoppingcart.models.PaidCourseRegistration.contained_in_order(
                cart, course_key)

        reg_then_add_to_cart_link = "{reg_url}?course_id={course_id}&enrollment_action=add_to_cart".format(
            reg_url=reverse('register_user'),
            course_id=course.id.to_deprecated_string())

    # Used to provide context to message to student if enrollment not allowed
    can_enroll = has_access(request.user, 'enroll', course)
    invitation_only = course.invitation_only
    is_course_full = CourseEnrollment.is_course_full(course)

    # Register button should be disabled if one of the following is true:
    # - Student is already registered for course
    # - Course is already full
    # - Student cannot enroll in course
    active_reg_button = not (registered or is_course_full or not can_enroll)

    is_shib_course = uses_shib(course)

    return render_to_response(
        'courseware/course_about.html', {
            'course': course,
            'staff_access': staff_access,
            'studio_url': studio_url,
            'registered': registered,
            'course_target': course_target,
            'registration_price': registration_price,
            'in_cart': in_cart,
            'reg_then_add_to_cart_link': reg_then_add_to_cart_link,
            'show_courseware_link': show_courseware_link,
            'is_course_full': is_course_full,
            'can_enroll': can_enroll,
            'invitation_only': invitation_only,
            'active_reg_button': active_reg_button,
            'is_shib_course': is_shib_course,
        })
Example #35
0
def index(request, course_id, chapter=None, section=None, position=None):
    """
    Displays courseware accordion and associated content.  If course, chapter,
    and section are all specified, renders the page, or returns an error if they
    are invalid.

    If section is not specified, displays the accordion opened to the right chapter.

    If neither chapter or section are specified, redirects to user's most recent
    chapter, or the first chapter if this is the user's first visit.

    Arguments:

     - request    : HTTP request
     - course_id  : course id (str: ORG/course/URL_NAME)
     - chapter    : chapter url_name (str)
     - section    : section url_name (str)
     - position   : position in module, eg of <sequential> module (str)

    Returns:

     - HTTPresponse
    """
    course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
    user = User.objects.prefetch_related("groups").get(id=request.user.id)
    request.user = user  # keep just one instance of User
    course = get_course_with_access(user, 'load', course_key, depth=2)
    staff_access = has_access(user, 'staff', course)
    registered = registered_for_course(course, user)
    if not registered:
        # TODO (vshnayder): do course instructors need to be registered to see course?
        log.debug(u'User %s tried to view course %s but is not enrolled', user,
                  course.location.to_deprecated_string())
        return redirect(
            reverse('about_course', args=[course_key.to_deprecated_string()]))

    masq = setup_masquerade(request, staff_access)

    try:
        field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
            course_key, user, course, depth=2)

        course_module = get_module_for_descriptor(user, request, course,
                                                  field_data_cache, course_key)
        if course_module is None:
            log.warning(
                u'If you see this, something went wrong: if we got this'
                u' far, should have gotten a course module for this user')
            return redirect(
                reverse('about_course',
                        args=[course_key.to_deprecated_string()]))

        studio_url = get_studio_url(course_key, 'course')

        context = {
            'csrf':
            csrf(request)['csrf_token'],
            'accordion':
            render_accordion(request, course, chapter, section,
                             field_data_cache),
            'COURSE_TITLE':
            course.display_name_with_default,
            'course':
            course,
            'init':
            '',
            'fragment':
            Fragment(),
            'staff_access':
            staff_access,
            'studio_url':
            studio_url,
            'masquerade':
            masq,
            'xqa_server':
            settings.FEATURES.get(
                'USE_XQA_SERVER',
                'http://*****:*****@content-qa.mitx.mit.edu/xqa'),
            'reverifications':
            fetch_reverify_banner_info(request, course_key),
        }

        has_content = course.has_children_at_depth(CONTENT_DEPTH)
        if not has_content:
            # Show empty courseware for a course with no units
            return render_to_response('courseware/courseware.html', context)
        elif chapter is None:
            # passing CONTENT_DEPTH avoids returning 404 for a course with an
            # empty first section and a second section with content
            return redirect_to_course_position(course_module, CONTENT_DEPTH)

        # Only show the chat if it's enabled by the course and in the
        # settings.
        show_chat = course.show_chat and settings.FEATURES['ENABLE_CHAT']
        if show_chat:
            context['chat'] = chat_settings(course, user)
            # If we couldn't load the chat settings, then don't show
            # the widget in the courseware.
            if context['chat'] is None:
                show_chat = False

        context['show_chat'] = show_chat

        chapter_descriptor = course.get_child_by(
            lambda m: m.location.name == chapter)
        if chapter_descriptor is not None:
            save_child_position(course_module, chapter)
        else:
            raise Http404(
                'No chapter descriptor found with name {}'.format(chapter))

        chapter_module = course_module.get_child_by(
            lambda m: m.location.name == chapter)
        if chapter_module is None:
            # User may be trying to access a chapter that isn't live yet
            if masq == 'student':  # if staff is masquerading as student be kinder, don't 404
                log.debug('staff masq as student: no chapter %s' % chapter)
                return redirect(
                    reverse('courseware',
                            args=[course.id.to_deprecated_string()]))
            raise Http404

        if section is not None:
            section_descriptor = chapter_descriptor.get_child_by(
                lambda m: m.location.name == section)

            if section_descriptor is None:
                # Specifically asked-for section doesn't exist
                if masq == 'student':  # if staff is masquerading as student be kinder, don't 404
                    log.debug('staff masq as student: no section %s' % section)
                    return redirect(
                        reverse('courseware',
                                args=[course.id.to_deprecated_string()]))
                raise Http404

            ## Allow chromeless operation
            if section_descriptor.chrome:
                chrome = [
                    s.strip()
                    for s in section_descriptor.chrome.lower().split(",")
                ]
                if 'accordion' not in chrome:
                    context['disable_accordion'] = True
                if 'tabs' not in chrome:
                    context['disable_tabs'] = True

            if section_descriptor.default_tab:
                context['default_tab'] = section_descriptor.default_tab

            # cdodge: this looks silly, but let's refetch the section_descriptor with depth=None
            # which will prefetch the children more efficiently than doing a recursive load
            section_descriptor = modulestore().get_item(
                section_descriptor.location, depth=None)

            # Load all descendants of the section, because we're going to display its
            # html, which in general will need all of its children
            section_field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
                course_key, user, section_descriptor, depth=None)

            # Verify that position a string is in fact an int
            if position is not None:
                try:
                    int(position)
                except ValueError:
                    raise Http404(
                        "Position {} is not an integer!".format(position))

            section_module = get_module_for_descriptor(
                request.user, request, section_descriptor,
                section_field_data_cache, course_key, position)

            if section_module is None:
                # User may be trying to be clever and access something
                # they don't have access to.
                raise Http404

            # Save where we are in the chapter
            save_child_position(chapter_module, section)
            context['fragment'] = section_module.render(STUDENT_VIEW)
            context[
                'section_title'] = section_descriptor.display_name_with_default
        else:
            # section is none, so display a message
            studio_url = get_studio_url(course_key, 'course')
            prev_section = get_current_child(chapter_module)
            if prev_section is None:
                # Something went wrong -- perhaps this chapter has no sections visible to the user
                raise Http404
            prev_section_url = reverse('courseware_section',
                                       kwargs={
                                           'course_id':
                                           course_key.to_deprecated_string(),
                                           'chapter':
                                           chapter_descriptor.url_name,
                                           'section':
                                           prev_section.url_name
                                       })
            context['fragment'] = Fragment(content=render_to_string(
                'courseware/welcome-back.html', {
                    'course': course,
                    'studio_url': studio_url,
                    'chapter_module': chapter_module,
                    'prev_section': prev_section,
                    'prev_section_url': prev_section_url
                }))

        result = render_to_response('courseware/courseware.html', context)
    except Exception as e:

        # Doesn't bar Unicode characters from URL, but if Unicode characters do
        # cause an error it is a graceful failure.
        if isinstance(e, UnicodeEncodeError):
            raise Http404("URL contains Unicode characters")

        if isinstance(e, Http404):
            # let it propagate
            raise

        # In production, don't want to let a 500 out for any reason
        if settings.DEBUG:
            raise
        else:
            log.exception(
                u"Error in index view: user={user}, course={course}, chapter={chapter}"
                u" section={section} position={position}".format(
                    user=user,
                    course=course,
                    chapter=chapter,
                    section=section,
                    position=position))
            try:
                result = render_to_response('courseware/courseware-error.html',
                                            {
                                                'staff_access': staff_access,
                                                'course': course
                                            })
            except:
                # Let the exception propagate, relying on global config to at
                # at least return a nice error message
                log.exception("Error while rendering courseware-error page")
                raise

    return result
Example #36
0
 def get_about_page_link(self):
     """ create mock course and return the about page link """
     course_key = SlashSeparatedCourseKey('mitX', '101', 'test')
     return utils.get_lms_link_for_about_page(course_key)
 def setUp(self):
     super(TestPartitionService, self).setUp()
     self.course = Mock(
         id=SlashSeparatedCourseKey('org_0', 'course_0', 'run_0'))
     self.partition_service = self._create_service("ma")
Example #38
0
    def test_get_items_with_course_items(self):
        store = modulestore()

        # fix was to allow get_items() to take the course_id parameter
        store.get_items(SlashSeparatedCourseKey('a', 'b', 'c'), qualifiers={'category': 'vertical'})
Example #39
0
    def get_titles(self):

        if self.course_key is None:
            self.course_key = SlashSeparatedCourseKey.from_deprecated_string(
                self.course_id)

        #get all course structure
        course_usage_key = modulestore().make_course_usage_key(self.course_key)
        blocks = get_blocks(self.request,
                            course_usage_key,
                            depth='all',
                            requested_fields=['display_name', 'children'])
        _root = blocks['root']
        blocks_overviews = []
        #return unit title and component root
        try:
            children = blocks['blocks'][_root]['children']
            for z in children:
                child = blocks['blocks'][z]
                try:
                    sub_section = child['children']
                    for s in sub_section:
                        sub_ = blocks['blocks'][s]
                        vertical = sub_['children']
                        try:
                            for v in vertical:
                                unit = blocks['blocks'][v]
                                w = {}
                                w['id'] = unit['id']
                                w['display_name'] = unit['display_name']
                                try:
                                    w['children'] = unit['children']
                                except:
                                    pass
                                blocks_overviews.append(w)
                        except:
                            pass
                except:
                    pass
        except:
            pass

        studentmodule = StudentModule.objects.raw(
            "SELECT id,course_id,module_id FROM courseware_studentmodule WHERE course_id = %s AND max_grade IS NOT NULL AND grade <= max_grade GROUP BY module_id ORDER BY created",
            [self.course_id])

        title = []

        for n in studentmodule:

            usage_key = n.module_state_key
            _current = get_blocks(self.request,
                                  usage_key,
                                  depth='all',
                                  requested_fields=['display_name'])
            root = _current['root']

            unit_name = ''

            for over in blocks_overviews:
                if str(root) in over.get('children'):
                    unit_name = over.get('display_name')

            q = {
                "title": _current['blocks'][root]['display_name'],
                "root": root,
                'unit': unit_name
            }
            title.append(q)

        return title
Example #40
0
    def post(self, request, course_id):
        """Takes the form submission from the page and parses it.

        Args:
            request (`Request`): The Django Request object.
            course_id (unicode): The slash-separated course key.

        Returns:
            Status code 400 when the requested mode is unsupported. When the honor mode
            is selected, redirects to the dashboard. When the verified mode is selected,
            returns error messages if the indicated contribution amount is invalid or
            below the minimum, otherwise redirects to the verification flow.

        """
        course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
        user = request.user

        # This is a bit redundant with logic in student.views.change_enrollment,
        # but I don't really have the time to refactor it more nicely and test.
        course = modulestore().get_course(course_key)
        if not has_access(user, 'enroll', course):
            error_msg = _("Enrollment is closed")
            return self.get(request, course_id, error=error_msg)

        requested_mode = self._get_requested_mode(request.POST)

        allowed_modes = CourseMode.modes_for_course_dict(course_key)
        if requested_mode not in allowed_modes:
            return HttpResponseBadRequest(_("Enrollment mode not supported"))

        if requested_mode == 'audit':
            # The user will have already been enrolled in the audit mode at this
            # point, so we just redirect them to the dashboard, thereby avoiding
            # hitting the database a second time attempting to enroll them.
            return redirect(reverse('dashboard'))

        if requested_mode == 'honor':
            CourseEnrollment.enroll(user, course_key, mode=requested_mode)
            return redirect(reverse('dashboard'))

        mode_info = allowed_modes[requested_mode]

        if requested_mode == 'verified':
            amount = request.POST.get("contribution") or \
                request.POST.get("contribution-other-amt") or 0

            try:
                # Validate the amount passed in and force it into two digits
                amount_value = decimal.Decimal(amount).quantize(
                    decimal.Decimal('.01'), rounding=decimal.ROUND_DOWN)
            except decimal.InvalidOperation:
                error_msg = _("Invalid amount selected.")
                return self.get(request, course_id, error=error_msg)

            # Check for minimum pricing
            if amount_value < mode_info.min_price:
                error_msg = _(
                    "No selected price or selected price is too low.")
                return self.get(request, course_id, error=error_msg)

            donation_for_course = request.session.get("donation_for_course",
                                                      {})
            donation_for_course[unicode(course_key)] = amount_value
            request.session["donation_for_course"] = donation_for_course

            return redirect(
                reverse('verify_student_start_flow',
                        kwargs={'course_id': unicode(course_key)}))
Example #41
0
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from opaque_keys.edx.locations import Location, SlashSeparatedCourseKey

from student.tests.factories import CourseEnrollmentFactory, UserFactory
from courseware.model_data import StudentModule
from courseware.tests.tests import LoginEnrollmentTestCase, TEST_DATA_MIXED_MODULESTORE

from instructor_task.api_helper import encode_problem_and_student_input
from instructor_task.models import PROGRESS, QUEUING
from instructor_task.tests.factories import InstructorTaskFactory
from instructor_task.views import instructor_task_status

TEST_COURSE_ORG = 'edx'
TEST_COURSE_NAME = 'test_course'
TEST_COURSE_NUMBER = '1.23x'
TEST_COURSE_KEY = SlashSeparatedCourseKey(TEST_COURSE_ORG, TEST_COURSE_NUMBER,
                                          TEST_COURSE_NAME)
TEST_SECTION_NAME = "Problem"

TEST_FAILURE_MESSAGE = 'task failed horribly'
TEST_FAILURE_EXCEPTION = 'RandomCauseError'

OPTION_1 = 'Option 1'
OPTION_2 = 'Option 2'


class InstructorTaskTestCase(TestCase):
    """
    Tests API and view methods that involve the reporting of status for background tasks.
    """
    def setUp(self):
        self.student = UserFactory.create(username="******",
Example #42
0
    def __init__(
            self,
            data_dir,
            default_class=None,
            source_dirs=None,
            course_ids=None,
            load_error_modules=True,
            i18n_service=None,
            fs_service=None,
            user_service=None,
            signal_handler=None,
            target_course_id=None,
            **kwargs  # pylint: disable=unused-argument
    ):
        """
        Initialize an XMLModuleStore from data_dir

        Args:
            data_dir (str): path to data directory containing the course directories

            default_class (str): dot-separated string defining the default descriptor
                class to use if none is specified in entry_points

            source_dirs or course_ids (list of str): If specified, the list of source_dirs or course_ids to load.
                Otherwise, load all courses. Note, providing both
        """
        super(XMLModuleStore, self).__init__(**kwargs)

        self.data_dir = path(data_dir)
        self.modules = defaultdict(
            dict)  # course_id -> dict(location -> XBlock)
        self.courses = {}  # course_dir -> XBlock for the course
        self.errored_courses = {
        }  # course_dir -> errorlog, for dirs that failed to load

        if course_ids is not None:
            course_ids = [
                SlashSeparatedCourseKey.from_deprecated_string(course_id)
                for course_id in course_ids
            ]

        self.load_error_modules = load_error_modules

        if default_class is None:
            self.default_class = None
        else:
            module_path, _, class_name = default_class.rpartition('.')
            class_ = getattr(import_module(module_path), class_name)
            self.default_class = class_

        # All field data will be stored in an inheriting field data.
        self.field_data = inheriting_field_data(kvs=DictKeyValueStore())

        self.i18n_service = i18n_service
        self.fs_service = fs_service
        self.user_service = user_service

        # If we are specifically asked for missing courses, that should
        # be an error.  If we are asked for "all" courses, find the ones
        # that have a course.xml. We sort the dirs in alpha order so we always
        # read things in the same order (OS differences in load order have
        # bitten us in the past.)

        if source_dirs is None:
            source_dirs = sorted([
                d for d in os.listdir(self.data_dir)
                if os.path.exists(self.data_dir / d / self.parent_xml)
            ])
        for course_dir in source_dirs:
            self.try_load_course(course_dir, course_ids, target_course_id)
Example #43
0
    def handle(self, *args, **options):

        # Scrub the username from the log message
        cleaned_options = copy.copy(options)
        if 'username' in cleaned_options:
            cleaned_options['username'] = '******'
        LOGGER.info((u"Starting to create tasks to regenerate certificates "
                     u"with arguments %s and options %s"), unicode(args),
                    unicode(cleaned_options))

        if options['course']:
            # try to parse out the course from the serialized form
            try:
                course_id = CourseKey.from_string(options['course'])
            except InvalidKeyError:
                LOGGER.warning((
                    u"Course id %s could not be parsed as a CourseKey; "
                    u"falling back to SlashSeparatedCourseKey.from_deprecated_string()"
                ), options['course'])
                course_id = SlashSeparatedCourseKey.from_deprecated_string(
                    options['course'])
        else:
            raise CommandError("You must specify a course")

        user = options['username']
        if not (course_id and user):
            raise CommandError(
                'both course id and student username are required')

        student = None
        if '@' in user:
            student = User.objects.get(email=user,
                                       courseenrollment__course_id=course_id)
        else:
            student = User.objects.get(username=user,
                                       courseenrollment__course_id=course_id)

        course = modulestore().get_course(course_id, depth=2)

        if not options['noop']:
            LOGGER.info(
                (u"Adding task to the XQueue to generate a certificate "
                 u"for student %s in course '%s'."), student.id, course_id)

            if course.issue_badges:
                badge_class = get_completion_badge(course_id, student)
                badge = badge_class.get_for_user(student)

                if badge:
                    badge.delete()
                    LOGGER.info(u"Cleared badge for student %s.", student.id)

            # Add the certificate request to the queue
            ret = regenerate_user_certificates(
                student,
                course_id,
                course=course,
                forced_grade=options['grade_value'],
                template_file=options['template_file'],
                insecure=options['insecure'])

            LOGGER.info(
                (u"Added a certificate regeneration task to the XQueue "
                 u"for student %s in course '%s'. "
                 u"The new certificate status is '%s'."), student.id,
                unicode(course_id), ret)

        else:
            LOGGER.info((u"Skipping certificate generation for "
                         u"student %s in course '%s' "
                         u"because the noop flag is set."), student.id,
                        unicode(course_id))

        LOGGER.info((u"Finished regenerating certificates command for "
                     u"user %s and course '%s'."), student.id,
                    unicode(course_id))
Example #44
0
class SysadminBaseTestCase(SharedModuleStoreTestCase):
    """
    Base class with common methods used in XML and Mongo tests
    """

    TEST_REPO = 'https://github.com/mitocw/edx4edx_lite.git'
    TEST_BRANCH = 'testing_do_not_delete'
    TEST_BRANCH_COURSE = SlashSeparatedCourseKey('MITx', 'edx4edx_branch',
                                                 'edx4edx')

    def setUp(self):
        """Setup test case by adding primary user."""
        super(SysadminBaseTestCase, self).setUp()
        self.user = UserFactory.create(username='******',
                                       email='*****@*****.**',
                                       password='******')
        self.client = Client()

    def _setstaff_login(self):
        """Makes the test user staff and logs them in"""
        GlobalStaff().add_users(self.user)
        self.client.login(username=self.user.username, password='******')

    def _add_edx4edx(self, branch=None):
        """Adds the edx4edx sample course"""
        post_dict = {
            'repo_location': self.TEST_REPO,
            'action': 'add_course',
        }
        if branch:
            post_dict['repo_branch'] = branch
        return self.client.post(reverse('sysadmin_courses'), post_dict)

    def _rm_edx4edx(self):
        """Deletes the sample course from the XML store"""
        def_ms = modulestore()
        course_path = '{0}/edx4edx_lite'.format(
            os.path.abspath(settings.DATA_DIR))
        try:
            # using XML store
            course = def_ms.courses.get(course_path, None)
        except AttributeError:
            # Using mongo store
            course = def_ms.get_course(
                SlashSeparatedCourseKey('MITx', 'edx4edx', 'edx4edx'))

        # Delete git loaded course
        response = self.client.post(
            reverse('sysadmin_courses'), {
                'course_id': course.id.to_deprecated_string(),
                'action': 'del_course',
            })
        self.addCleanup(self._rm_glob, '{0}_deleted_*'.format(course_path))

        return response

    def _rm_glob(self, path):
        """
        Create a shell expansion of passed in parameter and iteratively
        remove them.  Must only expand to directories.
        """
        for path in glob.glob(path):
            shutil.rmtree(path)

    def _mkdir(self, path):
        """
        Create directory and add the cleanup for it.
        """
        os.mkdir(path)
        self.addCleanup(shutil.rmtree, path)
Example #45
0
def course_wiki_redirect(request, course_id):  # pylint: disable=unused-argument
    """
    This redirects to whatever page on the wiki that the course designates
    as it's home page. A course's wiki must be an article on the root (for
    example, "/6.002x") to keep things simple.
    """
    course = get_course_by_id(
        SlashSeparatedCourseKey.from_deprecated_string(course_id))
    course_slug = course_wiki_slug(course)

    valid_slug = True
    if not course_slug:
        log.exception(
            "This course is improperly configured. The slug cannot be empty.")
        valid_slug = False
    if re.match(r'^[-\w\.]+$', course_slug) is None:
        log.exception(
            "This course is improperly configured. The slug can only contain letters, numbers, periods or hyphens."
        )
        valid_slug = False

    if not valid_slug:
        return redirect("wiki:get", path="")

    # The wiki needs a Site object created. We make sure it exists here
    try:
        Site.objects.get_current()
    except Site.DoesNotExist:
        new_site = Site()
        new_site.domain = settings.SITE_NAME
        new_site.name = "edX"
        new_site.save()
        site_id = str(new_site.id)
        if site_id != str(settings.SITE_ID):
            raise ImproperlyConfigured(
                "No site object was created and the SITE_ID doesn't match the newly created one. {} != {}"
                .format(site_id, settings.SITE_ID))

    try:
        urlpath = URLPath.get_by_path(course_slug, select_related=True)

        results = list(Article.objects.filter(id=urlpath.article.id))
        if results:
            article = results[0]
        else:
            article = None

    except (NoRootURL, URLPath.DoesNotExist):
        # We will create it in the next block
        urlpath = None
        article = None

    if not article:
        # create it
        root = get_or_create_root()

        if urlpath:
            # Somehow we got a urlpath without an article. Just delete it and
            # recerate it.
            urlpath.delete()

        content = cgi.escape(
            # Translators: this string includes wiki markup.  Leave the ** and the _ alone.
            _("This is the wiki for **{organization}**'s _{course_name}_."
              ).format(
                  organization=course.display_org_with_default,
                  course_name=course.display_name_with_default_escaped,
              ))
        urlpath = URLPath.create_article(
            root,
            course_slug,
            title=course_slug,
            content=content,
            user_message=_("Course page automatically created."),
            user=None,
            ip_address=None,
            article_kwargs={
                'owner': None,
                'group': None,
                'group_read': True,
                'group_write': True,
                'other_read': True,
                'other_write': True,
            })

    return redirect("wiki:get", path=urlpath.path)
Example #46
0
    def get(self, request, *args, **kwargs):
        """Shows logs of imports that happened as a result of a git import"""

        course_id = kwargs.get('course_id')
        if course_id:
            course_id = SlashSeparatedCourseKey.from_deprecated_string(
                course_id)

        page_size = 10

        # Set mongodb defaults even if it isn't defined in settings
        mongo_db = {
            'host': 'localhost',
            'user': '',
            'password': '',
            'db': 'xlog',
        }

        # Allow overrides
        if hasattr(settings, 'MONGODB_LOG'):
            for config_item in [
                    'host',
                    'user',
                    'password',
                    'db',
            ]:
                mongo_db[config_item] = settings.MONGODB_LOG.get(
                    config_item, mongo_db[config_item])

        mongouri = 'mongodb://{user}:{password}@{host}/{db}'.format(**mongo_db)

        error_msg = ''

        try:
            if mongo_db['user'] and mongo_db['password']:
                mdb = mongoengine.connect(mongo_db['db'], host=mongouri)
            else:
                mdb = mongoengine.connect(mongo_db['db'],
                                          host=mongo_db['host'])
        except mongoengine.connection.ConnectionError:
            log.exception('Unable to connect to mongodb to save log, '
                          'please check MONGODB_LOG settings.')

        if course_id is None:
            # Require staff if not going to specific course
            if not request.user.is_staff:
                raise Http404
            cilset = CourseImportLog.objects.order_by('-created')
        else:
            try:
                course = get_course_by_id(course_id)
            except Exception:
                log.info('Cannot find course %s', course_id)
                raise Http404

            # Allow only course team, instructors, and staff
            if not (request.user.is_staff
                    or CourseInstructorRole(course.id).has_user(request.user)
                    or CourseStaffRole(course.id).has_user(request.user)):
                raise Http404
            log.debug('course_id=%s', course_id)
            cilset = CourseImportLog.objects.filter(
                course_id=course_id).order_by('-created')
            log.debug('cilset length=%s', len(cilset))

        # Paginate the query set
        paginator = Paginator(cilset, page_size)
        try:
            logs = paginator.page(request.GET.get('page'))
        except PageNotAnInteger:
            logs = paginator.page(1)
        except EmptyPage:
            # If the page is too high or low
            given_page = int(request.GET.get('page'))
            page = min(max(1, given_page), paginator.num_pages)
            logs = paginator.page(page)

        mdb.disconnect()
        context = {
            'logs': logs,
            'course_id':
            course_id.to_deprecated_string() if course_id else None,
            'error_msg': error_msg,
            'page_size': page_size
        }

        return render_to_response(self.template_name, context)
Example #47
0
class CourseEnrollmentAllowedFactory(DjangoModelFactory):
    class Meta(object):
        model = CourseEnrollmentAllowed

    email = '*****@*****.**'
    course_id = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall')
Example #48
0
 def setUp(self):
     self.user = UserFactory.create()
     self.course_id = SlashSeparatedCourseKey('test_org',
                                              'test_course_number',
                                              'test_run')
     self.test_key = 'test_key'
Example #49
0
from static_replace import (replace_static_urls, replace_course_urls,
                            _url_replace_regex, process_static_urls,
                            make_static_urls_absolute)
from mock import patch, Mock

from opaque_keys.edx.locations import SlashSeparatedCourseKey
from xmodule.contentstore.content import StaticContent
from xmodule.contentstore.django import contentstore
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.mongo import MongoModuleStore
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, check_mongo_calls
from xmodule.modulestore.xml import XMLModuleStore

DATA_DIRECTORY = 'data_dir'
COURSE_KEY = SlashSeparatedCourseKey('org', 'course', 'run')
STATIC_SOURCE = '"/static/file.png"'


def test_multi_replace():
    course_source = '"/course/file.png"'

    assert_equals(
        replace_static_urls(STATIC_SOURCE, DATA_DIRECTORY),
        replace_static_urls(replace_static_urls(STATIC_SOURCE, DATA_DIRECTORY),
                            DATA_DIRECTORY))
    assert_equals(
        replace_course_urls(course_source, COURSE_KEY),
        replace_course_urls(replace_course_urls(course_source, COURSE_KEY),
                            COURSE_KEY))
Example #50
0
def cohort_handler(request, course_key_string, cohort_id=None):
    """
    The restful handler for cohort requests. Requires JSON.
    GET
        If a cohort ID is specified, returns a JSON representation of the cohort
            (name, id, user_count, assignment_type, user_partition_id, group_id).
        If no cohort ID is specified, returns the JSON representation of all cohorts.
           This is returned as a dict with the list of cohort information stored under the
           key `cohorts`.
    PUT or POST or PATCH
        If a cohort ID is specified, updates the cohort with the specified ID. Currently the only
        properties that can be updated are `name`, `user_partition_id` and `group_id`.
        Returns the JSON representation of the updated cohort.
        If no cohort ID is specified, creates a new cohort and returns the JSON representation of the updated
        cohort.
    """
    course_key = SlashSeparatedCourseKey.from_deprecated_string(
        course_key_string)
    course = get_course_with_access(request.user, 'staff', course_key)
    if request.method == 'GET':
        if not cohort_id:
            all_cohorts = [
                _get_cohort_representation(c, course)
                for c in cohorts.get_course_cohorts(course)
            ]
            return JsonResponse({'cohorts': all_cohorts})
        else:
            cohort = cohorts.get_cohort_by_id(course_key, cohort_id)
            return JsonResponse(_get_cohort_representation(cohort, course))
    else:
        # If cohort_id is specified, update the existing cohort. Otherwise, create a new cohort.
        if cohort_id:
            cohort = cohorts.get_cohort_by_id(course_key, cohort_id)
            name = request.json.get('name')
            if name != cohort.name:
                if cohorts.CohortAssignmentType.get(
                        cohort, course) == cohorts.CohortAssignmentType.RANDOM:
                    return JsonResponse(
                        # Note: error message not translated because it is not exposed to the user (UI prevents).
                        {
                            "error":
                            "Renaming of random cohorts is not supported at this time."
                        },
                        400)
                cohort.name = name
                cohort.save()
        else:
            name = request.json.get('name')
            if not name:
                # Note: error message not translated because it is not exposed to the user (UI prevents this state).
                return JsonResponse(
                    {
                        "error":
                        "In order to create a cohort, a name must be specified."
                    }, 400)
            try:
                cohort = cohorts.add_cohort(course_key, name)
            except ValueError as err:
                return JsonResponse({"error": unicode(err)}, 400)

        group_id = request.json.get('group_id')
        if group_id is not None:
            user_partition_id = request.json.get('user_partition_id')
            if user_partition_id is None:
                # Note: error message not translated because it is not exposed to the user (UI prevents this state).
                return JsonResponse(
                    {
                        "error":
                        "If group_id is specified, user_partition_id must also be specified."
                    }, 400)
            existing_group_id, existing_partition_id = cohorts.get_group_info_for_cohort(
                cohort)
            if group_id != existing_group_id or user_partition_id != existing_partition_id:
                unlink_cohort_partition_group(cohort)
                link_cohort_to_partition_group(cohort, user_partition_id,
                                               group_id)
        else:
            # If group_id was specified as None, unlink the cohort if it previously was associated with a group.
            existing_group_id, _ = cohorts.get_group_info_for_cohort(cohort)
            if existing_group_id is not None:
                unlink_cohort_partition_group(cohort)

        return JsonResponse(_get_cohort_representation(cohort, course))
Example #51
0
    def test_static_url_generation(self):

        course_key = SlashSeparatedCourseKey('org', 'class', 'run')
        location = course_key.make_asset_key('asset', 'my_file_name.jpg')
        path = StaticContent.get_static_path_from_location(location)
        self.assertEquals(path, '/static/my_file_name.jpg')
Example #52
0
    def import_and_populate_course(self):
        """
        Imports the test toy course and populates it with additional test data
        """
        content_store = contentstore()
        import_course_from_xml(self.store,
                               self.user.id,
                               TEST_DATA_DIR, ['toy'],
                               static_content_store=content_store)
        course_id = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall')

        # create an Orphan
        # We had a bug where orphaned draft nodes caused export to fail. This is here to cover that case.
        vertical = self.store.get_item(course_id.make_usage_key(
            'vertical', self.TEST_VERTICAL),
                                       depth=1)
        vertical.location = vertical.location.replace(name='no_references')
        self.store.update_item(vertical, self.user.id, allow_not_found=True)
        orphan_vertical = self.store.get_item(vertical.location)
        self.assertEqual(orphan_vertical.location.name, 'no_references')
        self.assertEqual(len(orphan_vertical.children), len(vertical.children))

        # create an orphan vertical and html; we already don't try to import
        # the orphaned vertical, but we should make sure we don't import
        # the orphaned vertical's child html, too
        orphan_draft_vertical = self.store.create_item(
            self.user.id, course_id, 'vertical', self.ORPHAN_DRAFT_VERTICAL)
        orphan_draft_html = self.store.create_item(self.user.id, course_id,
                                                   'html',
                                                   self.ORPHAN_DRAFT_HTML)
        orphan_draft_vertical.children.append(orphan_draft_html.location)
        self.store.update_item(orphan_draft_vertical, self.user.id)

        # create a Draft vertical
        vertical = self.store.get_item(course_id.make_usage_key(
            'vertical', self.TEST_VERTICAL),
                                       depth=1)
        draft_vertical = self.store.convert_to_draft(vertical.location,
                                                     self.user.id)
        self.assertTrue(self.store.has_published_version(draft_vertical))

        # create a Private (draft only) vertical
        private_vertical = self.store.create_item(self.user.id, course_id,
                                                  'vertical',
                                                  self.PRIVATE_VERTICAL)
        self.assertFalse(self.store.has_published_version(private_vertical))

        # create a Published (no draft) vertical
        public_vertical = self.store.create_item(self.user.id, course_id,
                                                 'vertical',
                                                 self.PUBLISHED_VERTICAL)
        public_vertical = self.store.publish(public_vertical.location,
                                             self.user.id)
        self.assertTrue(self.store.has_published_version(public_vertical))

        # add the new private and new public as children of the sequential
        sequential = self.store.get_item(
            course_id.make_usage_key('sequential', self.SEQUENTIAL))
        sequential.children.append(private_vertical.location)
        sequential.children.append(public_vertical.location)
        self.store.update_item(sequential, self.user.id)

        # create an html and video component to make drafts:
        draft_html = self.store.create_item(self.user.id, course_id, 'html',
                                            self.DRAFT_HTML)
        draft_video = self.store.create_item(self.user.id, course_id, 'video',
                                             self.DRAFT_VIDEO)

        # add them as children to the public_vertical
        public_vertical.children.append(draft_html.location)
        public_vertical.children.append(draft_video.location)
        self.store.update_item(public_vertical, self.user.id)
        # publish changes to vertical
        self.store.publish(public_vertical.location, self.user.id)
        # convert html/video to draft
        self.store.convert_to_draft(draft_html.location, self.user.id)
        self.store.convert_to_draft(draft_video.location, self.user.id)

        # lock an asset
        content_store.set_attr(self.LOCKED_ASSET_KEY, 'locked', True)

        # create a non-portable link - should be rewritten in new courses
        html_module = self.store.get_item(
            course_id.make_usage_key('html', 'nonportable'))
        new_data = html_module.data = html_module.data.replace(
            '/static/', '/c4x/{0}/{1}/asset/'.format(course_id.org,
                                                     course_id.course))
        self.store.update_item(html_module, self.user.id)

        html_module = self.store.get_item(html_module.location)
        self.assertEqual(new_data, html_module.data)

        return course_id
Example #53
0
def _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, user):
    """
    Invoke an XBlock handler, either authenticated or not.

    Arguments:
        request (HttpRequest): the current request
        course_id (str): A string of the form org/course/run
        usage_id (str): A string of the form i4x://org/course/category/name@revision
        handler (str): The name of the handler to invoke
        suffix (str): The suffix to pass to the handler when invoked
        user (User): The currently logged in user

    """
    try:
        course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id)
        usage_key = course_id.make_usage_key_from_deprecated_string(unquote_slashes(usage_id))
    except InvalidKeyError:
        raise Http404("Invalid location")

    # Check submitted files
    files = request.FILES or {}
    error_msg = _check_files_limits(files)
    if error_msg:
        return HttpResponse(json.dumps({'success': error_msg}))

    try:
        descriptor = modulestore().get_item(usage_key)
    except ItemNotFoundError:
        log.warn(
            "Invalid location for course id {course_id}: {usage_key}".format(
                course_id=usage_key.course_key,
                usage_key=usage_key
            )
        )
        raise Http404

    tracking_context_name = 'module_callback_handler'
    tracking_context = {
        'module': {
            'display_name': descriptor.display_name_with_default,
        }
    }

    field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
        course_id,
        user,
        descriptor
    )
    instance = get_module(user, request, usage_key, field_data_cache, grade_bucket_type='ajax')
    if instance is None:
        # Either permissions just changed, or someone is trying to be clever
        # and load something they shouldn't have access to.
        log.debug("No module %s for user %s -- access denied?", usage_key, user)
        raise Http404

    req = django_to_webob_request(request)
    try:
        with tracker.get_tracker().context(tracking_context_name, tracking_context):
            resp = instance.handle(handler, req, suffix)

    except NoSuchHandlerError:
        log.exception("XBlock %s attempted to access missing handler %r", instance, handler)
        raise Http404

    # If we can't find the module, respond with a 404
    except NotFoundError:
        log.exception("Module indicating to user that request doesn't exist")
        raise Http404

    # For XModule-specific errors, we log the error and respond with an error message
    except ProcessingError as err:
        log.warning("Module encountered an error while processing AJAX call",
                    exc_info=True)
        return JsonResponse(object={'success': err.args[0]}, status=200)

    # If any other error occurred, re-raise it to trigger a 500 response
    except Exception:
        log.exception("error executing xblock handler")
        raise

    return webob_to_django_response(resp)
Example #54
0
def add_users_to_cohort(request, course_key_string, cohort_id):
    """
    Return json dict of:

    {'success': True,
     'added': [{'username': ...,
                'name': ...,
                'email': ...}, ...],
     'changed': [{'username': ...,
                  'name': ...,
                  'email': ...,
                  'previous_cohort': ...}, ...],
     'present': [str1, str2, ...],    # already there
     'unknown': [str1, str2, ...]}

     Raises Http404 if the cohort cannot be found for the given course.
    """
    # this is a string when we get it here
    course_key = SlashSeparatedCourseKey.from_deprecated_string(
        course_key_string)
    get_course_with_access(request.user, 'staff', course_key)

    try:
        cohort = cohorts.get_cohort_by_id(course_key, cohort_id)
    except CourseUserGroup.DoesNotExist:
        raise Http404(
            "Cohort (ID {cohort_id}) not found for {course_key_string}".format(
                cohort_id=cohort_id, course_key_string=course_key_string))

    users = request.POST.get('users', '')
    added = []
    changed = []
    present = []
    unknown = []
    for username_or_email in split_by_comma_and_whitespace(users):
        if not username_or_email:
            continue

        try:
            (user, previous_cohort) = cohorts.add_user_to_cohort(
                cohort, username_or_email)
            info = {
                'username': user.username,
                'name': user.profile.name,
                'email': user.email,
            }
            if previous_cohort:
                info['previous_cohort'] = previous_cohort
                changed.append(info)
            else:
                added.append(info)
        except ValueError:
            present.append(username_or_email)
        except User.DoesNotExist:
            unknown.append(username_or_email)

    return json_http_response({
        'success': True,
        'added': added,
        'changed': changed,
        'present': present,
        'unknown': unknown
    })
Example #55
0
def instructor_dashboard_2(request, course_id):
    """ Display the instructor dashboard for a course. """
    course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
    course = get_course_by_id(course_key, depth=None)

    access = {
        'admin':
        request.user.is_staff,
        'instructor':
        has_access(request.user, 'instructor', course),
        'finance_admin':
        CourseFinanceAdminRole(course_key).has_user(request.user),
        'staff':
        has_access(request.user, 'staff', course),
        'forum_admin':
        has_forum_access(request.user, course_key, FORUM_ROLE_ADMINISTRATOR),
    }

    if not access['staff']:
        raise Http404()

    sections = [
        _section_course_info(course, access),
        _section_membership(course, access),
        _section_student_admin(course, access),
        _section_data_download(course, access),
        _section_analytics(course, access),
    ]

    #check if there is corresponding entry in the CourseMode Table related to the Instructor Dashboard course
    course_honor_mode = CourseMode.mode_for_course(course_key, 'honor')
    course_mode_has_price = False
    if course_honor_mode and course_honor_mode.min_price > 0:
        course_mode_has_price = True

    if (settings.FEATURES.get('INDIVIDUAL_DUE_DATES')
            and access['instructor']):
        sections.insert(3, _section_extensions(course))

    # Gate access to course email by feature flag & by course-specific authorization
    if bulk_email_is_enabled_for_course(course_key):
        sections.append(_section_send_email(course, access))

    # Gate access to Metrics tab by featue flag and staff authorization
    if settings.FEATURES['CLASS_DASHBOARD'] and access['staff']:
        sections.append(_section_metrics(course, access))

    # Gate access to Ecommerce tab
    if course_mode_has_price:
        sections.append(_section_e_commerce(course, access))

    disable_buttons = not _is_small_course(course_key)

    analytics_dashboard_message = None
    if settings.ANALYTICS_DASHBOARD_URL:
        # Construct a URL to the external analytics dashboard
        analytics_dashboard_url = '{0}/courses/{1}'.format(
            settings.ANALYTICS_DASHBOARD_URL, unicode(course_key))
        link_start = "<a href=\"{}\" target=\"_blank\">".format(
            analytics_dashboard_url)
        analytics_dashboard_message = _(
            "To gain insights into student enrollment and participation {link_start}visit {analytics_dashboard_name}, our new course analytics product{link_end}."
        )
        analytics_dashboard_message = analytics_dashboard_message.format(
            link_start=link_start,
            link_end="</a>",
            analytics_dashboard_name=settings.ANALYTICS_DASHBOARD_NAME)

    context = {
        'course':
        course,
        'old_dashboard_url':
        reverse('instructor_dashboard_legacy',
                kwargs={'course_id': course_key.to_deprecated_string()}),
        'studio_url':
        get_studio_url(course, 'course'),
        'sections':
        sections,
        'disable_buttons':
        disable_buttons,
        'analytics_dashboard_message':
        analytics_dashboard_message
    }

    return render_to_response(
        'instructor/instructor_dashboard_2/instructor_dashboard_2.html',
        context)
Example #56
0
    def test_get_items_with_course_items(self):
        store = modulestore()

        # fix was to allow get_items() to take the course_id parameter
        store.get_items(SlashSeparatedCourseKey('abc', 'def', 'ghi'),
                        category='vertical')
Example #57
0
def validate_forus_params_values(params):
    errors = defaultdict(lambda: [])

    def mark_as_invalid(field, field_label):
        # Translators: This is for the ForUs API
        errors[field].append(
            _('Invalid {field_label} has been provided').format(
                field_label=field_label, ))

    try:
        validate_email(params.get('email'))

        try:
            user = User.objects.get(email=params.get('email'))

            if user.is_staff or user.is_superuser:
                errors['email'].append(
                    _("ForUs profile cannot be created for admins and staff."))
        except User.DoesNotExist:
            pass
    except ValidationError:
        # Translators: This is for the ForUs API
        errors['email'].append(_("The provided email format is invalid"))

    if params.get('gender') not in dict(UserProfile.GENDER_CHOICES):
        # Translators: This is for the ForUs API
        mark_as_invalid('gender', _('gender'))

    if not is_enabled_language(params.get('lang')):
        # Translators: This is for the ForUs API
        mark_as_invalid('lang', _('language'))

    if params.get('country') not in dict(countries):
        # Translators: This is for the ForUs API
        mark_as_invalid('lang', _('country'))

    if params.get('level_of_education') not in dict(
            UserProfile.LEVEL_OF_EDUCATION_CHOICES):
        # Translators: This is for the ForUs API
        mark_as_invalid('lang', _('level of education'))

    try:
        course_key = SlashSeparatedCourseKey.from_deprecated_string(
            params.get('course_id'))
        course = modulestore().get_course(course_key)

        if not course:
            raise ItemNotFoundError()

        if not course.is_self_paced():
            if not course.enrollment_has_started():

                # Translators: This is for the ForUs API
                errors['course_id'].append(
                    _('The course has not yet been opened for enrollment'))

            if course.enrollment_has_ended():
                # Translators: This is for the ForUs API
                errors['course_id'].append(
                    _('Enrollment for this course has been closed'))

    except InvalidKeyError:
        log.warning(
            u"User {username} tried to {action} with invalid course id: {course_id}"
            .format(
                username=params.get('username'),
                action=params.get('enrollment_action'),
                course_id=params.get('course_id'),
            ))

        mark_as_invalid('course_id', _('course id'))
    except ItemNotFoundError:
        # Translators: This is for the ForUs API
        errors['course_id'].append(_('The requested course does not exist'))

    try:
        if int(params['year_of_birth']) not in UserProfile.VALID_YEARS:
            # Translators: This is for the ForUs API
            mark_as_invalid('year_of_birth', _('birth year'))
    except ValueError:
        # Translators: This is for the ForUs API
        mark_as_invalid('year_of_birth', _('birth year'))

    try:
        time = datetime.strptime(params.get('time'), DATE_TIME_FORMAT)
        now = datetime.utcnow()

        if time > now:
            # Translators: This is for the ForUs API
            errors['time'].append(_('future date has been provided'))

        if time < (now - timedelta(days=1)):
            # Translators: This is for the ForUs API
            errors['time'].append(_('Request has expired'))

    except ValueError:
        # Translators: This is for the ForUs API
        mark_as_invalid('time', _('date format'))

    if len(errors):
        raise ValidationError(errors)
Example #58
0
def create_new_course(request):
    """
    Create a new course.

    Returns the URL for the course overview page.
    """
    if not auth.has_access(request.user, CourseCreatorRole()):
        raise PermissionDenied()

    org = request.json.get('org')
    number = request.json.get('number')
    display_name = request.json.get('display_name')
    run = request.json.get('run')

    # allow/disable unicode characters in course_id according to settings
    if not settings.FEATURES.get('ALLOW_UNICODE_COURSE_ID'):
        if _has_non_ascii_characters(org) or _has_non_ascii_characters(
                number) or _has_non_ascii_characters(run):
            return JsonResponse(
                {
                    'error':
                    _('Special characters not allowed in organization, course number, and course run.'
                      )
                },
                status=400)

    try:
        course_key = SlashSeparatedCourseKey(org, number, run)

        # instantiate the CourseDescriptor and then persist it
        # note: no system to pass
        if display_name is None:
            metadata = {}
        else:
            metadata = {'display_name': display_name}

        # Set a unique wiki_slug for newly created courses. To maintain active wiki_slugs for
        # existing xml courses this cannot be changed in CourseDescriptor.
        # # TODO get rid of defining wiki slug in this org/course/run specific way and reconcile
        # w/ xmodule.course_module.CourseDescriptor.__init__
        wiki_slug = u"{0}.{1}.{2}".format(course_key.org, course_key.course,
                                          course_key.run)
        definition_data = {'wiki_slug': wiki_slug}

        # Create the course then fetch it from the modulestore
        # Check if role permissions group for a course named like this already exists
        # Important because role groups are case insensitive
        if CourseRole.course_group_already_exists(course_key):
            raise InvalidLocationError()

        fields = {}
        fields.update(definition_data)
        fields.update(metadata)

        # Creating the course raises InvalidLocationError if an existing course with this org/name is found
        new_course = modulestore('direct').create_course(
            course_key.org,
            course_key.offering,
            fields=fields,
        )

        # can't use auth.add_users here b/c it requires request.user to already have Instructor perms in this course
        # however, we can assume that b/c this user had authority to create the course, the user can add themselves
        CourseInstructorRole(new_course.id).add_users(request.user)
        auth.add_users(request.user, CourseStaffRole(new_course.id),
                       request.user)

        # seed the forums
        seed_permissions_roles(new_course.id)

        # auto-enroll the course creator in the course so that "View Live" will
        # work.
        CourseEnrollment.enroll(request.user, new_course.id)
        _users_assign_default_role(new_course.id)

        return JsonResponse(
            {'url': reverse_course_url('course_handler', new_course.id)})

    except InvalidLocationError:
        return JsonResponse({
            'ErrMsg':
            _('There is already a course defined with the same '
              'organization, course number, and course run. Please '
              'change either organization or course number to be unique.'),
            'OrgErrMsg':
            _('Please change either the organization or '
              'course number so that it is unique.'),
            'CourseErrMsg':
            _('Please change either the organization or '
              'course number so that it is unique.'),
        })
    except InvalidKeyError as error:
        return JsonResponse({
            "ErrMsg":
            _("Unable to create course '{name}'.\n\n{err}").format(
                name=display_name, err=error.message)
        })
Example #59
0
class ApiTest(TestCase):
    def setUp(self):
        super(ApiTest, self).setUp()
        self.client = Client()

        # Mocks
        patcher = patch.object(api, 'api_enabled', Mock(return_value=True))
        patcher.start()
        self.addCleanup(patcher.stop)

        # Create two accounts
        self.password = '******'
        self.student = User.objects.create_user('student', '*****@*****.**',
                                                self.password)
        self.student2 = User.objects.create_user('student2',
                                                 '*****@*****.**',
                                                 self.password)
        self.instructor = User.objects.create_user('instructor',
                                                   '*****@*****.**',
                                                   self.password)
        self.course_key = SlashSeparatedCourseKey('HarvardX', 'CB22x',
                                                  'The_Ancient_Greek_Hero')
        self.note = {
            'user': self.student,
            'course_id': self.course_key,
            'uri': '/',
            'text': 'foo',
            'quote': 'bar',
            'range_start': 0,
            'range_start_offset': 0,
            'range_end': 100,
            'range_end_offset': 0,
            'tags': 'a,b,c'
        }

        # Make sure no note with this ID ever exists for testing purposes
        self.NOTE_ID_DOES_NOT_EXIST = 99999

    def login(self, as_student=None):
        username = None
        password = self.password

        if as_student is None:
            username = self.student.username
        else:
            username = as_student.username

        self.client.login(username=username, password=password)

    def url(self, name, args={}):
        args.update({'course_id': self.course_key.to_deprecated_string()})
        return reverse(name, kwargs=args)

    def create_notes(self, num_notes, create=True):
        notes = []
        for __ in range(num_notes):
            note = models.Note(**self.note)
            if create:
                note.save()
            notes.append(note)
        return notes

    def test_root(self):
        self.login()

        resp = self.client.get(self.url('notes_api_root'))
        self.assertEqual(resp.status_code, 200)
        self.assertNotEqual(resp.content, '')

        content = json.loads(resp.content)

        self.assertEqual(set(('name', 'version')), set(content.keys()))
        self.assertIsInstance(content['version'], int)
        self.assertEqual(content['name'], 'Notes API')

    def test_index_empty(self):
        self.login()

        resp = self.client.get(self.url('notes_api_notes'))
        self.assertEqual(resp.status_code, 200)
        self.assertNotEqual(resp.content, '')

        content = json.loads(resp.content)
        self.assertEqual(len(content), 0)

    def test_index_with_notes(self):
        num_notes = 3
        self.login()
        self.create_notes(num_notes)

        resp = self.client.get(self.url('notes_api_notes'))
        self.assertEqual(resp.status_code, 200)
        self.assertNotEqual(resp.content, '')

        content = json.loads(resp.content)
        self.assertIsInstance(content, list)
        self.assertEqual(len(content), num_notes)

    def test_index_max_notes(self):
        self.login()

        MAX_LIMIT = api.API_SETTINGS.get('MAX_NOTE_LIMIT')
        num_notes = MAX_LIMIT + 1
        self.create_notes(num_notes)

        resp = self.client.get(self.url('notes_api_notes'))
        self.assertEqual(resp.status_code, 200)
        self.assertNotEqual(resp.content, '')

        content = json.loads(resp.content)
        self.assertIsInstance(content, list)
        self.assertEqual(len(content), MAX_LIMIT)

    def test_create_note(self):
        self.login()

        notes = self.create_notes(1)
        self.assertEqual(len(notes), 1)

        note_dict = notes[0].as_dict()
        excluded_fields = ['id', 'user_id', 'created', 'updated']
        note = dict([(k, v) for k, v in note_dict.items()
                     if k not in excluded_fields])

        resp = self.client.post(self.url('notes_api_notes'),
                                json.dumps(note),
                                content_type='application/json',
                                HTTP_X_REQUESTED_WITH='XMLHttpRequest')

        self.assertEqual(resp.status_code, 303)
        self.assertEqual(len(resp.content), 0)

    def test_create_empty_notes(self):
        self.login()

        for empty_test in [None, [], '']:
            resp = self.client.post(self.url('notes_api_notes'),
                                    json.dumps(empty_test),
                                    content_type='application/json',
                                    HTTP_X_REQUESTED_WITH='XMLHttpRequest')
            self.assertEqual(resp.status_code, 400)

    def test_create_note_missing_ranges(self):
        self.login()

        notes = self.create_notes(1)
        self.assertEqual(len(notes), 1)
        note_dict = notes[0].as_dict()

        excluded_fields = ['id', 'user_id', 'created', 'updated'] + ['ranges']
        note = dict([(k, v) for k, v in note_dict.items()
                     if k not in excluded_fields])

        resp = self.client.post(self.url('notes_api_notes'),
                                json.dumps(note),
                                content_type='application/json',
                                HTTP_X_REQUESTED_WITH='XMLHttpRequest')
        self.assertEqual(resp.status_code, 400)

    def test_read_note(self):
        self.login()

        notes = self.create_notes(3)
        self.assertEqual(len(notes), 3)

        for note in notes:
            resp = self.client.get(
                self.url('notes_api_note', {'note_id': note.pk}))
            self.assertEqual(resp.status_code, 200)
            self.assertNotEqual(resp.content, '')

            content = json.loads(resp.content)
            self.assertEqual(content['id'], note.pk)
            self.assertEqual(content['user_id'], note.user_id)

    def test_note_doesnt_exist_to_read(self):
        self.login()
        resp = self.client.get(
            self.url('notes_api_note',
                     {'note_id': self.NOTE_ID_DOES_NOT_EXIST}))
        self.assertEqual(resp.status_code, 404)
        self.assertEqual(resp.content, '')

    def test_student_doesnt_have_permission_to_read_note(self):
        notes = self.create_notes(1)
        self.assertEqual(len(notes), 1)
        note = notes[0]

        # set the student id to a different student (not the one that created the notes)
        self.login(as_student=self.student2)
        resp = self.client.get(self.url('notes_api_note',
                                        {'note_id': note.pk}))
        self.assertEqual(resp.status_code, 403)
        self.assertEqual(resp.content, '')

    def test_delete_note(self):
        self.login()

        notes = self.create_notes(1)
        self.assertEqual(len(notes), 1)
        note = notes[0]

        resp = self.client.delete(
            self.url('notes_api_note', {'note_id': note.pk}))
        self.assertEqual(resp.status_code, 204)
        self.assertEqual(resp.content, '')

        with self.assertRaises(models.Note.DoesNotExist):
            models.Note.objects.get(pk=note.pk)

    def test_note_does_not_exist_to_delete(self):
        self.login()

        resp = self.client.delete(
            self.url('notes_api_note',
                     {'note_id': self.NOTE_ID_DOES_NOT_EXIST}))
        self.assertEqual(resp.status_code, 404)
        self.assertEqual(resp.content, '')

    def test_student_doesnt_have_permission_to_delete_note(self):
        notes = self.create_notes(1)
        self.assertEqual(len(notes), 1)
        note = notes[0]

        self.login(as_student=self.student2)
        resp = self.client.delete(
            self.url('notes_api_note', {'note_id': note.pk}))
        self.assertEqual(resp.status_code, 403)
        self.assertEqual(resp.content, '')

        try:
            models.Note.objects.get(pk=note.pk)
        except models.Note.DoesNotExist:
            self.fail(
                'note should exist and not be deleted because the student does not have permission to do so'
            )

    def test_update_note(self):
        notes = self.create_notes(1)
        note = notes[0]

        updated_dict = note.as_dict()
        updated_dict.update({
            'text': 'itchy and scratchy',
            'tags': ['simpsons', 'cartoons', 'animation']
        })

        self.login()
        resp = self.client.put(self.url('notes_api_note',
                                        {'note_id': note.pk}),
                               json.dumps(updated_dict),
                               content_type='application/json',
                               HTTP_X_REQUESTED_WITH='XMLHttpRequest')
        self.assertEqual(resp.status_code, 303)
        self.assertEqual(resp.content, '')

        actual = models.Note.objects.get(pk=note.pk)
        actual_dict = actual.as_dict()
        for field in ['text', 'tags']:
            self.assertEqual(actual_dict[field], updated_dict[field])

    def test_search_note_params(self):
        self.login()

        total = 3
        notes = self.create_notes(total)
        invalid_uri = ''.join([note.uri for note in notes])

        tests = [{
            'limit': 0,
            'offset': 0,
            'expected_rows': total
        }, {
            'limit': 0,
            'offset': 2,
            'expected_rows': total - 2
        }, {
            'limit': 0,
            'offset': total,
            'expected_rows': 0
        }, {
            'limit': 1,
            'offset': 0,
            'expected_rows': 1
        }, {
            'limit': 2,
            'offset': 0,
            'expected_rows': 2
        }, {
            'limit': total,
            'offset': 2,
            'expected_rows': 1
        }, {
            'limit': total,
            'offset': total,
            'expected_rows': 0
        }, {
            'limit': total + 1,
            'offset': total + 1,
            'expected_rows': 0
        }, {
            'limit': total + 1,
            'offset': 0,
            'expected_rows': total
        }, {
            'limit': 0,
            'offset': 0,
            'uri': invalid_uri,
            'expected_rows': 0,
            'expected_total': 0
        }]

        for test in tests:
            params = dict([(k, str(test[k]))
                           for k in ('limit', 'offset', 'uri') if k in test])
            resp = self.client.get(self.url('notes_api_search'),
                                   params,
                                   content_type='application/json',
                                   HTTP_X_REQUESTED_WITH='XMLHttpRequest')

            self.assertEqual(resp.status_code, 200)
            self.assertNotEqual(resp.content, '')

            content = json.loads(resp.content)

            for expected_key in ('total', 'rows'):
                self.assertIn(expected_key, content)

            if 'expected_total' in test:
                self.assertEqual(content['total'], test['expected_total'])
            else:
                self.assertEqual(content['total'], total)

            self.assertEqual(len(content['rows']), test['expected_rows'])

            for row in content['rows']:
                self.assertIn('id', row)
Example #60
0
    def handle(self, *args, **options):
        if os.path.exists(options['output']):
            raise CommandError("File {0} already exists".format(
                options['output']))

        STATUS_INTERVAL = 100

        # parse out the course into a coursekey
        if options['course']:
            try:
                course_key = CourseKey.from_string(options['course'])
            # if it's not a new-style course key, parse it from an old-style
            # course key
            except InvalidKeyError:
                course_key = SlashSeparatedCourseKey.from_deprecated_string(
                    options['course'])

        print "Fetching enrolled students for {0}".format(course_key)
        enrolled_students = User.objects.filter(
            courseenrollment__course_id=course_key)
        factory = RequestMock()
        request = factory.get('/')

        total = enrolled_students.count()
        print "Total enrolled: {0}".format(total)
        course = courses.get_course_by_id(course_key)
        total = enrolled_students.count()
        start = datetime.datetime.now()
        rows = []
        header = None
        print "Fetching certificate data"
        cert_grades = {
            cert.user.username: cert.grade
            for cert in list(
                GeneratedCertificate.objects.filter(  # pylint: disable=no-member
                    course_id=course_key).prefetch_related('user'))
        }
        print "Grading students"
        for count, student in enumerate(enrolled_students):
            count += 1
            if count % STATUS_INTERVAL == 0:
                # Print a status update with an approximation of
                # how much time is left based on how long the last
                # interval took
                diff = datetime.datetime.now() - start
                timeleft = diff * (total - count) / STATUS_INTERVAL
                hours, remainder = divmod(timeleft.seconds, 3600)
                minutes, seconds = divmod(remainder, 60)
                print "{0}/{1} completed ~{2:02}:{3:02}m remaining".format(
                    count, total, hours, minutes)
                start = datetime.datetime.now()
            request.user = student
            grade = course_grades.summary(student, request, course)
            if not header:
                header = [
                    section['label'] for section in grade[u'section_breakdown']
                ]
                rows.append(
                    ["email", "username", "certificate-grade", "grade"] +
                    header)
            percents = {
                section['label']: section['percent']
                for section in grade[u'section_breakdown']
            }
            row_percents = [percents[label] for label in header]
            if student.username in cert_grades:
                rows.append([
                    student.email, student.username, cert_grades[
                        student.username], grade['percent']
                ] + row_percents)
            else:
                rows.append(
                    [student.email, student.username, "N/A", grade['percent']
                     ] + row_percents)
        with open(options['output'], 'wb') as f:
            writer = csv.writer(f)
            writer.writerows(rows)