The Webpack configuration file for built-in XBlock JS used to be generated at build time and git-ignored. It lived at common/static/xmodule/webpack.xmodule.config.js. It was generated because the JS that it referred to was also generated at build-time, and the filenames of those JS modules were not static. Now that its contents have been made entirely static [1], there is no reason we need to continue generating this Webpack configuration file. So, we check it into edx-platform under the name ./webpack.builtinblocks.config.js. We choose to put it in the repo's root directory because the paths contained in the config file are relative to the repo's root. This allows us to behead both the xmodule/static_content.py (`xmodule_assets`) script andthe `process_xmodule_assets` paver task, a major step in removing the need for Python in the edx-platform asset build [2]. It also allows us to delete the `HTMLSnippet` class and all associated attributes, which were exclusively used by xmodule/static_content.py.. We leave `xmodule_assets` and `process_xmodule_assets` in as stubs for now in order to avoid breaking external code (like Tutor) which calls Paver; the entire pavelib/assets.py function will be eventually removed soon anyway [3]. Further, to avoid extraneous refactoring, we keep one method of `HTMLSnippet` around on a few of its former subclasses: `get_html`. This method was originally part of the XModule framework; now, it is left over on a few classes as a simple internal helper method. References: 1. https://github.com/openedx/edx-platform/pull/32480 2. https://github.com/openedx/edx-platform/issues/31800 3. https://github.com/openedx/edx-platform/issues/31895 Part of: https://github.com/openedx/edx-platform/issues/32481
124 lines
3.5 KiB
Python
124 lines
3.5 KiB
Python
# lint-amnesty, pylint: disable=missing-module-docstring
|
|
|
|
import logging
|
|
import random
|
|
|
|
from django.utils.functional import cached_property
|
|
from lxml import etree
|
|
from web_fragments.fragment import Fragment
|
|
from xblock.fields import Integer, Scope
|
|
from xmodule.mako_block import MakoTemplateBlockBase
|
|
from xmodule.seq_block import SequenceMixin
|
|
from xmodule.xml_block import XmlMixin
|
|
from xmodule.x_module import (
|
|
ResourceTemplates,
|
|
STUDENT_VIEW,
|
|
XModuleMixin,
|
|
XModuleToXBlockMixin,
|
|
)
|
|
|
|
log = logging.getLogger('edx.' + __name__)
|
|
|
|
|
|
class RandomizeBlock(
|
|
SequenceMixin,
|
|
MakoTemplateBlockBase,
|
|
XmlMixin,
|
|
XModuleToXBlockMixin,
|
|
ResourceTemplates,
|
|
XModuleMixin,
|
|
):
|
|
"""
|
|
Chooses a random child xblock. Chooses the same one every time for each student.
|
|
|
|
Example:
|
|
<randomize>
|
|
<problem url_name="problem1" />
|
|
<problem url_name="problem2" />
|
|
<problem url_name="problem3" />
|
|
</randomize>
|
|
|
|
User notes:
|
|
|
|
- If you're randomizing amongst graded blocks, each of them MUST be worth the same
|
|
number of points. Otherwise, the earth will be overrun by monsters from the
|
|
deeps. You have been warned.
|
|
|
|
Technical notes:
|
|
- There is more dark magic in this code than I'd like. The whole varying-children +
|
|
grading interaction is a tangle between super and subclasses of descriptors and
|
|
blocks.
|
|
"""
|
|
choice = Integer(help="Which random child was chosen", scope=Scope.user_state)
|
|
|
|
resources_dir = None
|
|
|
|
filename_extension = "xml"
|
|
|
|
show_in_read_only_mode = True
|
|
|
|
@cached_property
|
|
def child(self):
|
|
""" Return XBlock instance of selected choice """
|
|
num_choices = len(self.get_children())
|
|
|
|
if self.choice is not None and self.choice > num_choices:
|
|
# Oops. Children changed. Reset.
|
|
self.choice = None
|
|
|
|
if self.choice is None:
|
|
# choose one based on the system seed, or randomly if that's not available
|
|
if num_choices > 0:
|
|
if self.runtime.seed is not None:
|
|
self.choice = self.runtime.seed % num_choices
|
|
else:
|
|
self.choice = random.randrange(0, num_choices)
|
|
|
|
if self.choice is None:
|
|
return None
|
|
child = self.get_children()[self.choice]
|
|
|
|
if self.choice is not None:
|
|
log.debug("children of randomize block (should be only 1): %s", child)
|
|
|
|
return child
|
|
|
|
def get_child_blocks(self):
|
|
"""
|
|
For grading--return just the chosen child.
|
|
"""
|
|
if self.child is None:
|
|
return []
|
|
|
|
return [self.child]
|
|
|
|
def student_view(self, context):
|
|
"""
|
|
The student view.
|
|
"""
|
|
if self.child is None:
|
|
# raise error instead? In fact, could complain on block load...
|
|
return Fragment(content="<div>Nothing to randomize between</div>")
|
|
|
|
return self.child.render(STUDENT_VIEW, context)
|
|
|
|
def get_html(self):
|
|
return self.studio_view(None).content
|
|
|
|
def get_icon_class(self):
|
|
return self.child.get_icon_class() if self.child else 'other'
|
|
|
|
def definition_to_xml(self, resource_fs):
|
|
|
|
xml_object = etree.Element('randomize')
|
|
for child in self.get_children():
|
|
self.runtime.add_block_as_child_node(child, xml_object)
|
|
return xml_object
|
|
|
|
def has_dynamic_children(self):
|
|
"""
|
|
Grading needs to know that only one of the children is actually "real". This
|
|
makes it use block.get_child_blocks().
|
|
"""
|
|
return True
|