Exemplo n.º 1
0
def get_allowed_block_types(library_key):  # pylint: disable=unused-argument
    """
    Get a list of XBlock types that can be added to the specified content
    library.
    """
    # This import breaks in the LMS so keep it here. The LMS doesn't generally
    # use content libraries APIs directly but some tests may want to use them to
    # create libraries and then test library learning or course-library integration.
    from cms.djangoapps.contentstore.views.helpers import xblock_type_display_name
    # TODO: return support status and template options
    # See cms/djangoapps/contentstore/views/component.py
    block_types = sorted(name for name, class_ in XBlock.load_classes())
    lib = get_library(library_key)
    if lib.type != COMPLEX:
        # Problem and Video libraries only permit XBlocks of the same name.
        block_types = (name for name in block_types if name == lib.type)
    info = []
    for block_type in block_types:
        display_name = xblock_type_display_name(block_type, None)
        # For now as a crude heuristic, we exclude blocks that don't have a display_name
        if display_name:
            info.append(
                LibraryXBlockType(block_type=block_type,
                                  display_name=display_name))
    return info
Exemplo n.º 2
0
def test_all_scenarios():
    """Load the home page, get every URL, make a test from it."""
    client = Client()
    response = client.get("/")
    assert response.status_code == 200
    html = lxml.html.fromstring(response.content)
    a_tags = list(html.xpath('//a'))
    for a_tag in a_tags:
        yield try_scenario, a_tag.get('href'), a_tag.text

    # Load the scenarios from the classes.
    scenarios = []
    for _, cls in XBlock.load_classes():
        if hasattr(cls, "workbench_scenarios"):
            for _, xml in cls.workbench_scenarios():
                scenarios.append(xml)

    # We should have an <a> tag for each scenario.
    assert_equals(len(a_tags), len(scenarios))

    # We should have at least one scenario with a vertical tag, since we use
    # empty verticals as our canary in the coal mine that something has gone
    # horribly wrong with loading the scenarios.
    assert any("<vertical_demo>" in xml for xml in scenarios)

    # Since we are claiming in try_scenario that no vertical is empty, let's
    # eliminate the possibility that a scenario has an actual empty vertical.
    assert all("<vertical_demo></vertical_demo>" not in xml
               for xml in scenarios)
    assert all("<vertical_demo/>" not in xml for xml in scenarios)
Exemplo n.º 3
0
def test_all_scenarios():
    """Load the home page, get every URL, make a test from it."""
    client = Client()
    response = client.get("/")
    assert response.status_code == 200
    html = lxml.html.fromstring(response.content)
    a_tags = list(html.xpath('//a'))
    for a_tag in a_tags:
        yield try_scenario, a_tag.get('href'), a_tag.text

    # Load the scenarios from the classes.
    scenarios = []
    for _, cls in XBlock.load_classes():
        if hasattr(cls, "workbench_scenarios"):
            for _, xml in cls.workbench_scenarios():
                scenarios.append(xml)

    # We should have an <a> tag for each scenario.
    assert_equals(len(a_tags), len(scenarios))

    # We should have at least one scenario with a vertical tag, since we use
    # empty verticals as our canary in the coal mine that something has gone
    # horribly wrong with loading the scenarios.
    assert any("<vertical>" in xml for xml in scenarios)

    # Since we are claiming in try_scenario that no vertical is empty, let's
    # eliminate the possibility that a scenario has an actual empty vertical.
    assert all("<vertical></vertical>" not in xml for xml in scenarios)
    assert all("<vertical/>" not in xml for xml in scenarios)
Exemplo n.º 4
0
def _do_once():
    """
    Called once when the module is imported to create the global scenarios.
    """
    # Get all the XBlock classes, and add their scenarios.
    for class_name, cls in XBlock.load_classes():
        add_class_scenarios(class_name, cls)
Exemplo n.º 5
0
def xblock_specs_from_categories(categories):
    """
    Return NestedXBlockSpecs for available XBlocks from categories.
    """
    return (NestedXBlockSpec(class_,
                             category=category,
                             label=class_.display_name.default)
            for category, class_ in XBlock.load_classes()
            if category in categories)
Exemplo n.º 6
0
 def __init__(self, *args, **kwargs):
     super(XBlockPipelineFinder, self).__init__(*args, **kwargs)
     xblock_classes = set()
     for __, xblock_class in XBlock.load_classes():
         xblock_classes.add(xblock_class)
     self.package_storages = [
         XBlockPackageStorage(xblock_class.__module__, xblock_class.get_resources_dir())
         for xblock_class in xblock_classes
     ]
Exemplo n.º 7
0
def init_scenarios():
    """
    Create all the scenarios declared in all the XBlock classes.
    """
    # Clear any existing scenarios, since this is used repeatedly during testing.
    SCENARIOS.clear()

    # Get all the XBlock classes, and add their scenarios.
    for class_name, cls in XBlock.load_classes():
        add_class_scenarios(class_name, cls)
Exemplo n.º 8
0
 def __init__(self, *args, **kwargs):
     super(XBlockPipelineFinder, self).__init__(*args, **kwargs)
     xblock_classes = set()
     for __, xblock_class in XBlock.load_classes():
         xblock_classes.add(xblock_class)
     self.package_storages = [
         XBlockPackageStorage(xblock_class.__module__,
                              xblock_class.get_resources_dir())
         for xblock_class in xblock_classes
     ]
Exemplo n.º 9
0
def all_templates():
    """
    Returns all templates for enabled modules, grouped by descriptor type
    """
    # TODO use memcache to memoize w/ expiration
    templates = defaultdict(list)
    for category, descriptor in XBlock.load_classes():
        if not hasattr(descriptor, 'templates'):
            continue
        templates[category] = descriptor.templates()

    return templates
Exemplo n.º 10
0
def block_types_possibly_scored():
    """
    Returns the block types that could have a score.

    Something might be a scored item if it is capable of storing a score
    (has_score=True). We also have to include anything that can have children,
    since those children might have scores. We can avoid things like Videos,
    which have state but cannot ever impact someone's grade.
    """
    return frozenset(cat for (cat, xblock_class) in XBlock.load_classes()
                     if (getattr(xblock_class, 'has_score', False)
                         or getattr(xblock_class, 'has_children', False)))
Exemplo n.º 11
0
def block_types_with_scores():
    """
    Returns the block types that could have a score.

    Something might be a scored item if it is capable of storing a score
    (has_score=True). We also have to include anything that can have children,
    since those children might have scores. We can avoid things like Videos,
    which have state but cannot ever impact someone's grade.
    """
    return frozenset(
        cat for (cat, xblock_class) in XBlock.load_classes() if (
            getattr(xblock_class, 'has_score', False) or getattr(xblock_class, 'has_children', False)
        )
    )
Exemplo n.º 12
0
def init_scenarios():
    """
    Create all the scenarios declared in all the XBlock classes.
    """
    # Clear any existing scenarios, since this is used repeatedly during testing.
    SCENARIOS.clear()
    if settings.WORKBENCH['reset_state_on_restart']:
        WORKBENCH_KVS.clear()
    else:
        WORKBENCH_KVS.prep_for_scenario_loading()

    # Get all the XBlock classes, and add their scenarios.
    for class_name, cls in sorted(XBlock.load_classes()):
        add_class_scenarios(class_name, cls)
Exemplo n.º 13
0
def init_scenarios():
    """
    Create all the scenarios declared in all the XBlock classes.
    """
    # Clear any existing scenarios, since this is used repeatedly during testing.
    SCENARIOS.clear()
    if settings.WORKBENCH["reset_state_on_restart"]:
        WORKBENCH_KVS.clear()
    else:
        WORKBENCH_KVS.prep_for_scenario_loading()

    # Get all the XBlock classes, and add their scenarios.
    for class_name, cls in sorted(XBlock.load_classes(fail_silently=False)):
        add_class_scenarios(class_name, cls, fail_silently=False)
Exemplo n.º 14
0
def get_allowed_block_types(library_key):  # pylint: disable=unused-argument
    """
    Get a list of XBlock types that can be added to the specified content
    library. For now, the result is the same regardless of which library is
    specified, but that may change in the future.
    """
    # TODO: return support status and template options
    # See cms/djangoapps/contentstore/views/component.py
    block_types = sorted(name for name, class_ in XBlock.load_classes())
    info = []
    for block_type in block_types:
        display_name = xblock_type_display_name(block_type, None)
        # For now as a crude heuristic, we exclude blocks that don't have a display_name
        if display_name:
            info.append(LibraryXBlockType(block_type=block_type, display_name=display_name))
    return info
Exemplo n.º 15
0
    def __init__(self, *args, **kwargs):
        """
        The XBlockPipelineFinder creates a separate XBlockPackageStorage for
        every installed XBlock package when its initialized. After that
        initialization happens, we just proxy all list()/find() requests by
        iterating through the XBlockPackageStorage objects.
        """
        super(XBlockPipelineFinder, self).__init__(*args, **kwargs)  # lint-amnesty, pylint: disable=super-with-arguments

        # xblock_resource_info holds (package_name, resources_dir) tuples. While
        # it never happens in practice, the XBlock API does allow different
        # XBlocks installed with the same setup.py to refer to their shared
        # static assets using different prefixes.
        xblock_resource_info = {(xblock_resource_pkg(xblock_class),
                                 xblock_class.get_resources_dir())
                                for __, xblock_class in XBlock.load_classes()}
        self.package_storages = [
            XBlockPackageStorage(pkg_name, resources_dir)
            for pkg_name, resources_dir in xblock_resource_info
        ]
Exemplo n.º 16
0
    def __init__(self, *args, **kwargs):
        """
        The XBlockPipelineFinder creates a separate XBlockPackageStorage for
        every installed XBlock package when its initialized. After that
        initialization happens, we just proxy all list()/find() requests by
        iterating through the XBlockPackageStorage objects.
        """
        super(XBlockPipelineFinder, self).__init__(*args, **kwargs)

        # xblock_resource_info holds (package_name, resources_dir) tuples. While
        # it never happens in practice, the XBlock API does allow different
        # XBlocks installed with the same setup.py to refer to their shared
        # static assets using different prefixes.
        xblock_resource_info = {
            (xblock_resource_pkg(xblock_class), xblock_class.get_resources_dir())
            for __, xblock_class in XBlock.load_classes()
        }
        self.package_storages = [
            XBlockPackageStorage(pkg_name, resources_dir)
            for pkg_name, resources_dir in xblock_resource_info
        ]
Exemplo n.º 17
0
def test_broken_plugin():
    plugins = XBlock.load_classes()
    assert_equals(list(plugins), [])
Exemplo n.º 18
0
    def _compute_metadata_inheritance_tree(self, course_id):
        '''
        TODO (cdodge) This method can be deleted when the 'split module store' work has been completed
        '''
        # get all collections in the course, this query should not return any leaf nodes
        # note this is a bit ugly as when we add new categories of containers, we have to add it here

        block_types_with_children = set(
            name for name, class_ in XBlock.load_classes() if getattr(class_, 'has_children', False)
        )
        query = SON([
            ('_id.tag', 'i4x'),
            ('_id.org', course_id.org),
            ('_id.course', course_id.course),
            ('_id.category', {'$in': list(block_types_with_children)})
        ])
        # we just want the Location, children, and inheritable metadata
        record_filter = {'_id': 1, 'definition.children': 1}

        # just get the inheritable metadata since that is all we need for the computation
        # this minimizes both data pushed over the wire
        for field_name in InheritanceMixin.fields:
            record_filter['metadata.{0}'.format(field_name)] = 1

        # call out to the DB
        resultset = self.collection.find(query, record_filter)

        # it's ok to keep these as deprecated strings b/c the overall cache is indexed by course_key and this
        # is a dictionary relative to that course
        results_by_url = {}
        root = None

        # now go through the results and order them by the location url
        for result in resultset:
            # manually pick it apart b/c the db has tag and we want revision = None regardless
            location = Location._from_deprecated_son(result['_id'], course_id.run).replace(revision=None)

            location_url = location.to_deprecated_string()
            if location_url in results_by_url:
                # found either draft or live to complement the other revision
                existing_children = results_by_url[location_url].get('definition', {}).get('children', [])
                additional_children = result.get('definition', {}).get('children', [])
                total_children = existing_children + additional_children
                results_by_url[location_url].setdefault('definition', {})['children'] = total_children
            results_by_url[location_url] = result
            if location.category == 'course':
                root = location_url

        # now traverse the tree and compute down the inherited metadata
        metadata_to_inherit = {}

        def _compute_inherited_metadata(url):
            """
            Helper method for computing inherited metadata for a specific location url
            """
            my_metadata = results_by_url[url].get('metadata', {})

            # go through all the children and recurse, but only if we have
            # in the result set. Remember results will not contain leaf nodes
            for child in results_by_url[url].get('definition', {}).get('children', []):
                if child in results_by_url:
                    new_child_metadata = copy.deepcopy(my_metadata)
                    new_child_metadata.update(results_by_url[child].get('metadata', {}))
                    results_by_url[child]['metadata'] = new_child_metadata
                    metadata_to_inherit[child] = new_child_metadata
                    _compute_inherited_metadata(child)
                else:
                    # this is likely a leaf node, so let's record what metadata we need to inherit
                    metadata_to_inherit[child] = my_metadata

        if root is not None:
            _compute_inherited_metadata(root)

        return metadata_to_inherit
Exemplo n.º 19
0
def test_broken_plugin():
    plugins = XBlock.load_classes()
    assert list(plugins) == []
Exemplo n.º 20
0
           'subsection_handler',
           'unit_handler',
           'container_handler',
           'component_handler'
           ]

log = logging.getLogger(__name__)

# NOTE: unit_handler assumes this list is disjoint from ADVANCED_COMPONENT_TYPES
COMPONENT_TYPES = ['discussion', 'html', 'problem', 'video']

OPEN_ENDED_COMPONENT_TYPES = ["combinedopenended", "peergrading"]
NOTE_COMPONENT_TYPES = ['notes']

if settings.FEATURES.get('ALLOW_ALL_ADVANCED_COMPONENTS'):
    ADVANCED_COMPONENT_TYPES = sorted(set(name for name, class_ in XBlock.load_classes()) - set(COMPONENT_TYPES))
else:

    ADVANCED_COMPONENT_TYPES = [
        'annotatable',
        'textannotation',  # module for annotating text (with annotation table)
        'videoannotation',  # module for annotating video (with annotation table)
        'imageannotation',  # module for annotating image (with annotation table)
        'word_cloud',
        'graphical_slider_tool',
        'lti',
        'concept',
        'openassessment',  # edx-ora2
    ] + OPEN_ENDED_COMPONENT_TYPES + NOTE_COMPONENT_TYPES

ADVANCED_COMPONENT_CATEGORY = 'advanced'
Exemplo n.º 21
0
"""

from collections import namedtuple

from xblock.core import XBlock
from xblock.parse import parse_xml_string
from .runtime import Usage

# Build the scenarios, which are named trees of usages.

Scenario = namedtuple("Scenario", "description usage")

SCENARIOS = {}

for class_name, cls in XBlock.load_classes():
    # Each XBlock class can provide scenarios to display in the workbench.
    if hasattr(cls, "workbench_scenarios"):
        for i, (desc, xml) in enumerate(cls.workbench_scenarios()):
            scname = "%s.%d" % (class_name, i)
            usage = parse_xml_string(xml, Usage)
            SCENARIOS[scname] = Scenario(desc, usage)
    else:
        # No specific scenarios, just show it with three generic children.
        default_children = [Usage("debugchild", []) for _ in xrange(3)]
        scname = "%s.0" % class_name
        usage = Usage(class_name, default_children)
        SCENARIOS[scname] = Scenario(class_name, usage)

SCENARIOS.update({
    'gettysburg':
Exemplo n.º 22
0
def test_broken_plugin():
    plugins = XBlock.load_classes()
    assert_equals(list(plugins), [])
Exemplo n.º 23
0
    'ADVANCED_COMPONENT_POLICY_KEY',
    'container_handler',
    'component_handler'
]

log = logging.getLogger(__name__)

# NOTE: it is assumed that this list is disjoint from ADVANCED_COMPONENT_TYPES
COMPONENT_TYPES = ['discussion', 'html', 'problem', 'video']

# Constants for determining if these components should be enabled for this course
SPLIT_TEST_COMPONENT_TYPE = 'split_test'
NOTE_COMPONENT_TYPES = ['notes']

if settings.FEATURES.get('ALLOW_ALL_ADVANCED_COMPONENTS'):
    ADVANCED_COMPONENT_TYPES = sorted(set(name for name, class_ in XBlock.load_classes()) - set(COMPONENT_TYPES))
else:
    ADVANCED_COMPONENT_TYPES = settings.ADVANCED_COMPONENT_TYPES

ADVANCED_COMPONENT_CATEGORY = 'advanced'
ADVANCED_COMPONENT_POLICY_KEY = 'advanced_modules'

ADVANCED_PROBLEM_TYPES = settings.ADVANCED_PROBLEM_TYPES


CONTAINER_TEMPLATES = [
    "basic-modal", "modal-button", "edit-xblock-modal",
    "editor-mode-button", "upload-dialog",
    "add-xblock-component", "add-xblock-component-button", "add-xblock-component-menu",
    "add-xblock-component-menu-problem", "xblock-string-field-editor", "publish-xblock", "publish-history",
    "unit-outline", "container-message", "license-selector",
Exemplo n.º 24
0
    def _compute_metadata_inheritance_tree(self, course_id):
        '''
        TODO (cdodge) This method can be deleted when the 'split module store' work has been completed
        '''
        # get all collections in the course, this query should not return any leaf nodes
        # note this is a bit ugly as when we add new categories of containers, we have to add it here

        block_types_with_children = set(
            name for name, class_ in XBlock.load_classes() if getattr(class_, 'has_children', False)
        )
        query = SON([
            ('_id.tag', 'i4x'),
            ('_id.org', course_id.org),
            ('_id.course', course_id.course),
            ('_id.category', {'$in': list(block_types_with_children)})
        ])
        # we just want the Location, children, and inheritable metadata
        record_filter = {'_id': 1, 'definition.children': 1}

        # just get the inheritable metadata since that is all we need for the computation
        # this minimizes both data pushed over the wire
        for field_name in InheritanceMixin.fields:
            record_filter['metadata.{0}'.format(field_name)] = 1

        # call out to the DB
        resultset = self.collection.find(query, record_filter)

        # it's ok to keep these as deprecated strings b/c the overall cache is indexed by course_key and this
        # is a dictionary relative to that course
        results_by_url = {}
        root = None

        # now go through the results and order them by the location url
        for result in resultset:
            # manually pick it apart b/c the db has tag and we want revision = None regardless
            location = Location._from_deprecated_son(result['_id'], course_id.run).replace(revision=None)

            location_url = location.to_deprecated_string()
            if location_url in results_by_url:
                # found either draft or live to complement the other revision
                existing_children = results_by_url[location_url].get('definition', {}).get('children', [])
                additional_children = result.get('definition', {}).get('children', [])
                total_children = existing_children + additional_children
                results_by_url[location_url].setdefault('definition', {})['children'] = total_children
            results_by_url[location_url] = result
            if location.category == 'course':
                root = location_url

        # now traverse the tree and compute down the inherited metadata
        metadata_to_inherit = {}

        def _compute_inherited_metadata(url):
            """
            Helper method for computing inherited metadata for a specific location url
            """
            my_metadata = results_by_url[url].get('metadata', {})

            # go through all the children and recurse, but only if we have
            # in the result set. Remember results will not contain leaf nodes
            for child in results_by_url[url].get('definition', {}).get('children', []):
                if child in results_by_url:
                    new_child_metadata = copy.deepcopy(my_metadata)
                    new_child_metadata.update(results_by_url[child].get('metadata', {}))
                    results_by_url[child]['metadata'] = new_child_metadata
                    metadata_to_inherit[child] = new_child_metadata
                    _compute_inherited_metadata(child)
                else:
                    # this is likely a leaf node, so let's record what metadata we need to inherit
                    metadata_to_inherit[child] = my_metadata

        if root is not None:
            _compute_inherited_metadata(root)

        return metadata_to_inherit
Exemplo n.º 25
0
    def compute_metadata_inheritance_tree(self, location):
        '''
        TODO (cdodge) This method can be deleted when the 'split module store' work has been completed
        '''
        # get all collections in the course, this query should not return any leaf nodes
        # note this is a bit ugly as when we add new categories of containers, we have to add it here

        block_types_with_children = set(
            name for name, class_ in XBlock.load_classes()
            if getattr(class_, 'has_children', False))
        query = {
            '_id.org': location.org,
            '_id.course': location.course,
            '_id.category': {
                '$in': list(block_types_with_children)
            }
        }
        # we just want the Location, children, and inheritable metadata
        record_filter = {'_id': 1, 'definition.children': 1}

        # just get the inheritable metadata since that is all we need for the computation
        # this minimizes both data pushed over the wire
        for field_name in InheritanceMixin.fields:
            record_filter['metadata.{0}'.format(field_name)] = 1

        # call out to the DB
        resultset = self.collection.find(query, record_filter)

        results_by_url = {}
        root = None

        # now go through the results and order them by the location url
        for result in resultset:
            location = Location(result['_id'])
            # We need to collate between draft and non-draft
            # i.e. draft verticals will have draft children but will have non-draft parents currently
            location = location.replace(revision=None)
            location_url = location.url()
            if location_url in results_by_url:
                existing_children = results_by_url[location_url].get(
                    'definition', {}).get('children', [])
                additional_children = result.get('definition',
                                                 {}).get('children', [])
                total_children = existing_children + additional_children
                results_by_url[location_url].setdefault(
                    'definition', {})['children'] = total_children
            results_by_url[location.url()] = result
            if location.category == 'course':
                root = location.url()

        # now traverse the tree and compute down the inherited metadata
        metadata_to_inherit = {}

        def _compute_inherited_metadata(url):
            """
            Helper method for computing inherited metadata for a specific location url
            """
            my_metadata = results_by_url[url].get('metadata', {})

            # go through all the children and recurse, but only if we have
            # in the result set. Remember results will not contain leaf nodes
            for child in results_by_url[url].get('definition',
                                                 {}).get('children', []):
                if child in results_by_url:
                    new_child_metadata = copy.deepcopy(my_metadata)
                    new_child_metadata.update(results_by_url[child].get(
                        'metadata', {}))
                    results_by_url[child]['metadata'] = new_child_metadata
                    metadata_to_inherit[child] = new_child_metadata
                    _compute_inherited_metadata(child)
                else:
                    # this is likely a leaf node, so let's record what metadata we need to inherit
                    metadata_to_inherit[child] = my_metadata

        if root is not None:
            _compute_inherited_metadata(root)

        return metadata_to_inherit
Exemplo n.º 26
0
from opaque_keys.edx.keys import UsageKey

from student.auth import has_course_author_access
from django.utils.translation import ugettext as _
from xblock_django.models import XBlockDisableConfig

__all__ = ['container_handler', 'component_handler']

log = logging.getLogger(__name__)

# NOTE: This list is disjoint from ADVANCED_COMPONENT_TYPES
COMPONENT_TYPES = ['discussion', 'html', 'problem', 'video']

ADVANCED_COMPONENT_TYPES = sorted(
    set(name for name, class_ in XBlock.load_classes()) - set(COMPONENT_TYPES))

ADVANCED_PROBLEM_TYPES = settings.ADVANCED_PROBLEM_TYPES

CONTAINER_TEMPLATES = [
    "basic-modal",
    "modal-button",
    "edit-xblock-modal",
    "editor-mode-button",
    "upload-dialog",
    "add-xblock-component",
    "add-xblock-component-button",
    "add-xblock-component-menu",
    "add-xblock-component-menu-problem",
    "xblock-string-field-editor",
    "publish-xblock",
Exemplo n.º 27
0
"""

from collections import namedtuple

from xblock.core import XBlock
from xblock.parse import parse_xml_string
from .runtime import Usage

# Build the scenarios, which are named trees of usages.

Scenario = namedtuple("Scenario", "description usage")  # pylint: disable=C0103

SCENARIOS = {}

for class_name, cls in XBlock.load_classes():
    # Each XBlock class can provide scenarios to display in the workbench.
    if hasattr(cls, "workbench_scenarios"):
        for i, (desc, xml) in enumerate(cls.workbench_scenarios()):
            scname = "%s.%d" % (class_name, i)
            usage = parse_xml_string(xml, Usage)
            SCENARIOS[scname] = Scenario(desc, usage)
    else:
        # No specific scenarios, just show it with three generic children.
        default_children = [Usage("debugchild", []) for _ in xrange(3)]
        scname = "%s.0" % class_name
        usage = Usage(class_name, default_children)
        SCENARIOS[scname] = Scenario(class_name, usage)

SCENARIOS.update({
    'gettysburg': Scenario(
Exemplo n.º 28
0
    def compute_metadata_inheritance_tree(self, location):
        '''
        TODO (cdodge) This method can be deleted when the 'split module store' work has been completed
        '''
        # get all collections in the course, this query should not return any leaf nodes
        # note this is a bit ugly as when we add new categories of containers, we have to add it here

        block_types_with_children = set(name for name, class_ in XBlock.load_classes() if getattr(class_, 'has_children', False))
        query = {'_id.org': location.org,
                 '_id.course': location.course,
                 '_id.category': {'$in': list(block_types_with_children)}
                 }
        # we just want the Location, children, and inheritable metadata
        record_filter = {'_id': 1, 'definition.children': 1}

        # just get the inheritable metadata since that is all we need for the computation
        # this minimizes both data pushed over the wire
        for field_name in InheritanceMixin.fields:
            record_filter['metadata.{0}'.format(field_name)] = 1

        # call out to the DB
        resultset = self.collection.find(query, record_filter)

        results_by_url = {}
        root = None

        # now go through the results and order them by the location url
        for result in resultset:
            location = Location(result['_id'])
            # We need to collate between draft and non-draft
            # i.e. draft verticals will have draft children but will have non-draft parents currently
            location = location.replace(revision=None)
            location_url = location.url()
            if location_url in results_by_url:
                existing_children = results_by_url[location_url].get('definition', {}).get('children', [])
                additional_children = result.get('definition', {}).get('children', [])
                total_children = existing_children + additional_children
                results_by_url[location_url].setdefault('definition', {})['children'] = total_children
            results_by_url[location.url()] = result
            if location.category == 'course':
                root = location.url()

        # now traverse the tree and compute down the inherited metadata
        metadata_to_inherit = {}

        def _compute_inherited_metadata(url):
            """
            Helper method for computing inherited metadata for a specific location url
            """
            my_metadata = results_by_url[url].get('metadata', {})

            # go through all the children and recurse, but only if we have
            # in the result set. Remember results will not contain leaf nodes
            for child in results_by_url[url].get('definition', {}).get('children', []):
                if child in results_by_url:
                    new_child_metadata = copy.deepcopy(my_metadata)
                    new_child_metadata.update(results_by_url[child].get('metadata', {}))
                    results_by_url[child]['metadata'] = new_child_metadata
                    metadata_to_inherit[child] = new_child_metadata
                    _compute_inherited_metadata(child)
                else:
                    # this is likely a leaf node, so let's record what metadata we need to inherit
                    metadata_to_inherit[child] = my_metadata

        if root is not None:
            _compute_inherited_metadata(root)

        return metadata_to_inherit
Exemplo n.º 29
0
def test_broken_plugin():
    plugins = XBlock.load_classes()
    assert list(plugins) == []