예제 #1
0
 def test_error_module_xml_rendering(self):
     descriptor = ErrorDescriptor.from_xml(
         self.valid_xml, self.system, self.org, self.course, self.error_msg)
     self.assertIsInstance(descriptor, ErrorDescriptor)
     descriptor.xmodule_runtime = self.system
     context_repr = self.system.render(descriptor, 'student_view').content
     self.assertIn(self.error_msg, context_repr)
     self.assertIn(repr(self.valid_xml), context_repr)
 def test_error_module_xml_rendering(self):
     descriptor = ErrorDescriptor.from_xml(
         self.valid_xml, self.system, CourseLocationManager(self.course_id),
         self.error_msg)
     self.assertIsInstance(descriptor, ErrorDescriptor)
     descriptor.xmodule_runtime = self.system
     context_repr = self.system.render(descriptor, STUDENT_VIEW).content
     self.assertIn(self.error_msg, context_repr)
     self.assertIn(repr(self.valid_xml), context_repr)
예제 #3
0
 def test_error_module_xml_rendering(self):
     descriptor = ErrorDescriptor.from_xml(
         self.valid_xml,
         self.system,
         CourseLocationManager(self.course_id),
         self.error_msg
     )
     self.assertIsInstance(descriptor, ErrorDescriptor)
     descriptor.xmodule_runtime = self.system
     context_repr = self.system.render(descriptor, STUDENT_VIEW).content
     self.assertIn(self.error_msg, context_repr)
     self.assertIn(repr(self.valid_xml), context_repr)
예제 #4
0
    def test_course_error(self):
        """
        Ensure the view still returns results even if get_courses() returns an ErrorDescriptor. The ErrorDescriptor
        should be filtered out.
        """

        error_descriptor = ErrorDescriptor.from_xml(
            '<course></course>', get_test_system(),
            CourseLocationManager(
                CourseLocator(org='org', course='course', run='run')), None)

        descriptors = [error_descriptor, self.empty_course, self.course]

        with patch('xmodule.modulestore.mixed.MixedModuleStore.get_courses',
                   Mock(return_value=descriptors)):
            self.test_get()
예제 #5
0
    def test_has_staff_access_to_preview_mode(self):
        """
        Tests users have right access to content in preview mode.
        """
        course_key = self.course.id
        usage_key = self.course.scope_ids.usage_id
        chapter = ItemFactory.create(category="chapter",
                                     parent_location=self.course.location)
        overview = CourseOverview.get_from_id(course_key)
        test_system = get_test_system()

        ccx = CcxFactory(course_id=course_key)
        ccx_locator = CCXLocator.from_course_locator(course_key, ccx.id)

        error_descriptor = ErrorDescriptor.from_xml(
            u"<problem>ABC \N{SNOWMAN}</problem>", test_system,
            CourseLocationManager(course_key), "error msg")
        # Enroll student to the course
        CourseEnrollmentFactory(user=self.student, course_id=self.course.id)

        modules = [
            self.course,
            overview,
            chapter,
            ccx_locator,
            error_descriptor,
            course_key,
            usage_key,
        ]
        # Course key is not None
        self.assertTrue(
            bool(
                access.has_staff_access_to_preview_mode(
                    self.global_staff, obj=self.course,
                    course_key=course_key)))

        for user in [
                self.global_staff, self.course_staff, self.course_instructor
        ]:
            for obj in modules:
                self.assertTrue(
                    bool(access.has_staff_access_to_preview_mode(user,
                                                                 obj=obj)))
                self.assertFalse(
                    bool(
                        access.has_staff_access_to_preview_mode(self.student,
                                                                obj=obj)))
예제 #6
0
    def test_course_error(self):
        """
        Ensure the view still returns results even if get_courses() returns an ErrorDescriptor. The ErrorDescriptor
        should be filtered out.
        """

        error_descriptor = ErrorDescriptor.from_xml(
            '<course></course>',
            get_test_system(),
            CourseLocationManager(CourseLocator(org='org', course='course', run='run')),
            None
        )

        descriptors = [error_descriptor, self.empty_course, self.course]

        with patch('xmodule.modulestore.mixed.MixedModuleStore.get_courses', Mock(return_value=descriptors)):
            self.test_get()
예제 #7
0
    def test_has_staff_access_to_preview_mode(self):
        """
        Tests users have right access to content in preview mode.
        """
        course_key = self.course.id
        usage_key = self.course.scope_ids.usage_id
        chapter = ItemFactory.create(category="chapter", parent_location=self.course.location)
        overview = CourseOverview.get_from_id(course_key)
        test_system = get_test_system()

        ccx = CcxFactory(course_id=course_key)
        ccx_locator = CCXLocator.from_course_locator(course_key, ccx.id)

        error_descriptor = ErrorDescriptor.from_xml(
            u"<problem>ABC \N{SNOWMAN}</problem>",
            test_system,
            CourseLocationManager(course_key),
            "error msg"
        )
        # Enroll student to the course
        CourseEnrollmentFactory(user=self.student, course_id=self.course.id)

        modules = [
            self.course,
            overview,
            chapter,
            ccx_locator,
            error_descriptor,
            course_key,
            usage_key,
        ]
        # Course key is not None
        self.assertTrue(
            bool(access.has_staff_access_to_preview_mode(self.global_staff, obj=self.course, course_key=course_key))
        )

        for user in [self.global_staff, self.course_staff, self.course_instructor]:
            for obj in modules:
                self.assertTrue(bool(access.has_staff_access_to_preview_mode(user, obj=obj)))
                self.assertFalse(bool(access.has_staff_access_to_preview_mode(self.student, obj=obj)))
예제 #8
0
        def process_xml(xml):
            """Takes an xml string, and returns a XBlock created from
            that xml.
            """
            def make_name_unique(xml_data):
                """
                Make sure that the url_name of xml_data is unique.  If a previously loaded
                unnamed descriptor stole this element's url_name, create a new one.

                Removes 'slug' attribute if present, and adds or overwrites the 'url_name' attribute.
                """
                # VS[compat]. Take this out once course conversion is done (perhaps leave the uniqueness check)

                # tags that really need unique names--they store (or should store) state.
                need_uniq_names = ('problem', 'sequential', 'video', 'course',
                                   'chapter', 'videosequence', 'poll_question',
                                   'vertical')

                attr = xml_data.attrib
                tag = xml_data.tag
                id = lambda x: x
                # Things to try to get a name, in order  (key, cleaning function, remove key after reading?)
                lookups = [('url_name', id, False), ('slug', id, True),
                           ('name', Location.clean, False),
                           ('display_name', Location.clean, False)]

                url_name = None
                for key, clean, remove in lookups:
                    if key in attr:
                        url_name = clean(attr[key])
                        if remove:
                            del attr[key]
                        break

                def looks_like_fallback(url_name):
                    """Does this look like something that came from fallback_name()?"""
                    return (url_name is not None and url_name.startswith(tag)
                            and re.search('[0-9a-fA-F]{12}$', url_name))

                def fallback_name(orig_name=None):
                    """Return the fallback name for this module.  This is a function instead of a variable
                    because we want it to be lazy."""
                    dog_stats_api.increment(
                        DEPRECATION_VSCOMPAT_EVENT,
                        tags=(
                            "location:import_system_fallback_name",
                            u"name:{}".format(orig_name),
                        ))

                    if looks_like_fallback(orig_name):
                        # We're about to re-hash, in case something changed, so get rid of the tag_ and hash
                        orig_name = orig_name[len(tag) + 1:-12]
                    # append the hash of the content--the first 12 bytes should be plenty.
                    orig_name = "_" + orig_name if orig_name not in (
                        None, "") else ""
                    xml_bytes = xml.encode('utf8')
                    return tag + orig_name + "_" + hashlib.sha1(
                        xml_bytes).hexdigest()[:12]

                # Fallback if there was nothing we could use:
                if url_name is None or url_name == "":
                    url_name = fallback_name()
                    # Don't log a warning--we don't need this in the log.  Do
                    # put it in the error tracker--content folks need to see it.

                    if tag in need_uniq_names:
                        error_tracker(
                            u"PROBLEM: no name of any kind specified for {tag}.  Student "
                            u"state will not be properly tracked for this module.  Problem xml:"
                            u" '{xml}...'".format(tag=tag, xml=xml[:100]))
                    else:
                        # TODO (vshnayder): We may want to enable this once course repos are cleaned up.
                        # (or we may want to give up on the requirement for non-state-relevant issues...)
                        # error_tracker("WARNING: no name specified for module. xml='{0}...'".format(xml[:100]))
                        pass

                # Make sure everything is unique
                if url_name in self.used_names[tag]:
                    # Always complain about modules that store state.  If it
                    # doesn't store state, don't complain about things that are
                    # hashed.
                    if tag in need_uniq_names:
                        msg = (
                            u"Non-unique url_name in xml.  This may break state tracking for content."
                            u"  url_name={0}.  Content={1}".format(
                                url_name, xml[:100]))
                        error_tracker("PROBLEM: " + msg)
                        log.warning(msg)
                        # Just set name to fallback_name--if there are multiple things with the same fallback name,
                        # they are actually identical, so it's fragile, but not immediately broken.

                        # TODO (vshnayder): if the tag is a pointer tag, this will
                        # break the content because we won't have the right link.
                        # That's also a legitimate attempt to reuse the same content
                        # from multiple places.  Once we actually allow that, we'll
                        # need to update this to complain about non-unique names for
                        # definitions, but allow multiple uses.
                        url_name = fallback_name(url_name)

                self.used_names[tag].add(url_name)
                xml_data.set('url_name', url_name)

            try:
                # VS[compat]
                # TODO (cpennington): Remove this once all fall 2012 courses
                # have been imported into the cms from xml
                xml = clean_out_mako_templating(xml)
                xml_data = etree.fromstring(xml)

                make_name_unique(xml_data)

                descriptor = self.xblock_from_node(
                    xml_data,
                    None,  # parent_id
                    id_manager,
                )
            except Exception as err:  # pylint: disable=broad-except
                if not self.load_error_modules:
                    raise

                # Didn't load properly.  Fall back on loading as an error
                # descriptor.  This should never error due to formatting.

                msg = "Error loading from xml. %s"
                log.warning(
                    msg,
                    unicode(err)[:200],
                    # Normally, we don't want lots of exception traces in our logs from common
                    # content problems.  But if you're debugging the xml loading code itself,
                    # uncomment the next line.
                    # exc_info=True
                )

                msg = msg % (unicode(err)[:200])

                self.error_tracker(msg)
                err_msg = msg + "\n" + exc_info_to_str(sys.exc_info())
                descriptor = ErrorDescriptor.from_xml(xml, self, id_manager,
                                                      err_msg)

            descriptor.data_dir = course_dir

            if descriptor.scope_ids.usage_id in xmlstore.modules[course_id]:
                # keep the parent pointer if any but allow everything else to overwrite
                other_copy = xmlstore.modules[course_id][
                    descriptor.scope_ids.usage_id]
                descriptor.parent = other_copy.parent
                if descriptor != other_copy:
                    log.warning("%s has more than one definition",
                                descriptor.scope_ids.usage_id)
            xmlstore.modules[course_id][
                descriptor.scope_ids.usage_id] = descriptor

            if descriptor.has_children:
                for child in descriptor.get_children():
                    # parent is alphabetically least
                    if child.parent is None or child.parent > descriptor.scope_ids.usage_id:
                        child.parent = descriptor.location
                        child.save()

            # After setting up the descriptor, save any changes that we have
            # made to attributes on the descriptor to the underlying KeyValueStore.
            descriptor.save()
            return descriptor
예제 #9
0
        def process_xml(xml):
            """Takes an xml string, and returns a XBlock created from
            that xml.
            """

            def make_name_unique(xml_data):
                """
                Make sure that the url_name of xml_data is unique.  If a previously loaded
                unnamed descriptor stole this element's url_name, create a new one.

                Removes 'slug' attribute if present, and adds or overwrites the 'url_name' attribute.
                """
                # VS[compat]. Take this out once course conversion is done (perhaps leave the uniqueness check)

                # tags that really need unique names--they store (or should store) state.
                need_uniq_names = (
                    "problem",
                    "sequential",
                    "video",
                    "course",
                    "chapter",
                    "videosequence",
                    "poll_question",
                    "vertical",
                )

                attr = xml_data.attrib
                tag = xml_data.tag
                id = lambda x: x
                # Things to try to get a name, in order  (key, cleaning function, remove key after reading?)
                lookups = [
                    ("url_name", id, False),
                    ("slug", id, True),
                    ("name", Location.clean, False),
                    ("display_name", Location.clean, False),
                ]

                url_name = None
                for key, clean, remove in lookups:
                    if key in attr:
                        url_name = clean(attr[key])
                        if remove:
                            del attr[key]
                        break

                def looks_like_fallback(url_name):
                    """Does this look like something that came from fallback_name()?"""
                    return url_name is not None and url_name.startswith(tag) and re.search("[0-9a-fA-F]{12}$", url_name)

                def fallback_name(orig_name=None):
                    """Return the fallback name for this module.  This is a function instead of a variable
                    because we want it to be lazy."""
                    if looks_like_fallback(orig_name):
                        # We're about to re-hash, in case something changed, so get rid of the tag_ and hash
                        orig_name = orig_name[len(tag) + 1 : -12]
                    # append the hash of the content--the first 12 bytes should be plenty.
                    orig_name = "_" + orig_name if orig_name not in (None, "") else ""
                    xml_bytes = xml.encode("utf8")
                    return tag + orig_name + "_" + hashlib.sha1(xml_bytes).hexdigest()[:12]

                # Fallback if there was nothing we could use:
                if url_name is None or url_name == "":
                    url_name = fallback_name()
                    # Don't log a warning--we don't need this in the log.  Do
                    # put it in the error tracker--content folks need to see it.

                    if tag in need_uniq_names:
                        error_tracker(
                            "PROBLEM: no name of any kind specified for {tag}.  Student "
                            "state will not be properly tracked for this module.  Problem xml:"
                            " '{xml}...'".format(tag=tag, xml=xml[:100])
                        )
                    else:
                        # TODO (vshnayder): We may want to enable this once course repos are cleaned up.
                        # (or we may want to give up on the requirement for non-state-relevant issues...)
                        # error_tracker("WARNING: no name specified for module. xml='{0}...'".format(xml[:100]))
                        pass

                # Make sure everything is unique
                if url_name in self.used_names[tag]:
                    # Always complain about modules that store state.  If it
                    # doesn't store state, don't complain about things that are
                    # hashed.
                    if tag in need_uniq_names:
                        msg = (
                            "Non-unique url_name in xml.  This may break state tracking for content."
                            "  url_name={0}.  Content={1}".format(url_name, xml[:100])
                        )
                        error_tracker("PROBLEM: " + msg)
                        log.warning(msg)
                        # Just set name to fallback_name--if there are multiple things with the same fallback name,
                        # they are actually identical, so it's fragile, but not immediately broken.

                        # TODO (vshnayder): if the tag is a pointer tag, this will
                        # break the content because we won't have the right link.
                        # That's also a legitimate attempt to reuse the same content
                        # from multiple places.  Once we actually allow that, we'll
                        # need to update this to complain about non-unique names for
                        # definitions, but allow multiple uses.
                        url_name = fallback_name(url_name)

                self.used_names[tag].add(url_name)
                xml_data.set("url_name", url_name)

            try:
                # VS[compat]
                # TODO (cpennington): Remove this once all fall 2012 courses
                # have been imported into the cms from xml
                xml = clean_out_mako_templating(xml)
                xml_data = etree.fromstring(xml)

                make_name_unique(xml_data)

                descriptor = create_block_from_xml(etree.tostring(xml_data, encoding="unicode"), self, id_generator)
            except Exception as err:  # pylint: disable=broad-except
                if not self.load_error_modules:
                    raise

                # Didn't load properly.  Fall back on loading as an error
                # descriptor.  This should never error due to formatting.

                msg = "Error loading from xml. %s"
                log.warning(
                    msg,
                    unicode(err)[:200],
                    # Normally, we don't want lots of exception traces in our logs from common
                    # content problems.  But if you're debugging the xml loading code itself,
                    # uncomment the next line.
                    # exc_info=True
                )

                msg = msg % (unicode(err)[:200])

                self.error_tracker(msg)
                err_msg = msg + "\n" + exc_info_to_str(sys.exc_info())
                descriptor = ErrorDescriptor.from_xml(xml, self, id_generator, err_msg)

            descriptor.data_dir = course_dir

            xmlstore.modules[course_id][descriptor.scope_ids.usage_id] = descriptor

            if descriptor.has_children:
                for child in descriptor.get_children():
                    parent_tracker.add_parent(child.scope_ids.usage_id, descriptor.scope_ids.usage_id)

            # After setting up the descriptor, save any changes that we have
            # made to attributes on the descriptor to the underlying KeyValueStore.
            descriptor.save()
            return descriptor