diff --git a/lms/djangoapps/courseware/tests/test_discussion_xblock.py b/lms/djangoapps/courseware/tests/test_discussion_xblock.py index bbe0e68bdcd1..573dbf6fce6f 100644 --- a/lms/djangoapps/courseware/tests/test_discussion_xblock.py +++ b/lms/djangoapps/courseware/tests/test_discussion_xblock.py @@ -221,7 +221,7 @@ def test_has_permission(self): """ permission_canary = object() with mock.patch( - 'xmodule.discussion_block.has_permission', + 'lms.djangoapps.discussion.django_comment_client.permissions.has_permission', return_value=permission_canary, ) as has_perm: actual_permission = self.block.has_permission("test_permission") diff --git a/pavelib/assets.py b/pavelib/assets.py index 8f950fe3f647..566092a2666e 100644 --- a/pavelib/assets.py +++ b/pavelib/assets.py @@ -323,6 +323,7 @@ class SassWatcher(PatternMatchingEventHandler): """ ignore_directories = True patterns = ['*.scss'] + ignore_patterns = ['common/static/xmodule/*'] def register(self, observer, directories): """ @@ -351,6 +352,47 @@ def on_any_event(self, event): traceback.print_exc() +class XModuleSassWatcher(SassWatcher): + """ + Watches for sass file changes + """ + ignore_directories = True + ignore_patterns = [] + + @debounce() + def on_any_event(self, event): + print('\tCHANGED:', event.src_path) + try: + process_xmodule_assets() + except Exception: # pylint: disable=broad-except + traceback.print_exc() + + +class XModuleAssetsWatcher(PatternMatchingEventHandler): + """ + Watches for css and js file changes + """ + ignore_directories = True + patterns = ['*.css', '*.js'] + + def register(self, observer): + """ + Register files with observer + """ + observer.schedule(self, 'xmodule/', recursive=True) + + @debounce() + def on_any_event(self, event): + print('\tCHANGED:', event.src_path) + try: + process_xmodule_assets() + except Exception: # pylint: disable=broad-except + traceback.print_exc() + + # To refresh the hash values of static xmodule content + restart_django_servers() + + @task @no_help @cmdopts([ @@ -573,13 +615,16 @@ def process_npm_assets(): @task +@needs( + 'pavelib.prereqs.install_python_prereqs', +) @no_help def process_xmodule_assets(): """ Process XModule static assets. """ - print("\t\tProcessing xmodule assets is no longer needed. This task is now a no-op.") - print("\t\tWhen paver is removed from edx-platform, this step will not replaced.") + sh('xmodule_assets common/static/xmodule') + print("\t\tFinished processing xmodule assets.") def restart_django_servers(): @@ -777,6 +822,8 @@ def watch_assets(options): observer = Observer(timeout=wait) SassWatcher().register(observer, sass_directories) + XModuleSassWatcher().register(observer, ['xmodule/']) + XModuleAssetsWatcher().register(observer) print("Starting asset watcher...") observer.start() @@ -798,7 +845,6 @@ def watch_assets(options): @task @needs( 'pavelib.prereqs.install_node_prereqs', - 'pavelib.prereqs.install_python_prereqs', ) @consume_args @timed @@ -850,6 +896,8 @@ def update_assets(args): args = parser.parse_args(args) collect_log_args = {} + process_xmodule_assets() + # Build Webpack call_task('pavelib.assets.webpack', options={'settings': args.settings}) diff --git a/pavelib/js_test.py b/pavelib/js_test.py index 3f84a3361dba..fed1f0fe102f 100644 --- a/pavelib/js_test.py +++ b/pavelib/js_test.py @@ -26,6 +26,7 @@ @needs( 'pavelib.prereqs.install_node_prereqs', 'pavelib.utils.test.utils.clean_reports_dir', + 'pavelib.assets.process_xmodule_assets', ) @cmdopts([ ("suite=", "s", "Test suite to run"), diff --git a/pavelib/paver_tests/test_servers.py b/pavelib/paver_tests/test_servers.py index e9a07b94f0c6..dbbab3b00b6b 100644 --- a/pavelib/paver_tests/test_servers.py +++ b/pavelib/paver_tests/test_servers.py @@ -214,6 +214,7 @@ def verify_server_task(self, task_name, options): expected_asset_settings = "test_static_optimized" expected_collect_static = not is_fast and expected_settings != Env.DEVSTACK_SETTINGS if not is_fast: + expected_messages.append("xmodule_assets common/static/xmodule") expected_messages.append("install npm_assets") expected_messages.extend( [c.format(settings=expected_asset_settings, @@ -258,6 +259,7 @@ def verify_run_all_servers_task(self, options): expected_collect_static = not is_fast and expected_settings != Env.DEVSTACK_SETTINGS expected_messages = [] if not is_fast: + expected_messages.append("xmodule_assets common/static/xmodule") expected_messages.append("install npm_assets") expected_messages.extend( [c.format(settings=expected_asset_settings, diff --git a/webpack.builtinblocks.config.js b/webpack.builtinblocks.config.js deleted file mode 100644 index 30f5bdb2a507..000000000000 --- a/webpack.builtinblocks.config.js +++ /dev/null @@ -1,136 +0,0 @@ -module.exports = { - "entry": { - "AboutBlockDisplay": [ - "./xmodule/js/src/xmodule.js", - "./xmodule/js/src/html/display.js", - "./xmodule/js/src/javascript_loader.js", - "./xmodule/js/src/collapsible.js", - "./xmodule/js/src/html/imageModal.js", - "./xmodule/js/common_static/js/vendor/draggabilly.js" - ], - "AboutBlockEditor": [ - "./xmodule/js/src/xmodule.js", - "./xmodule/js/src/html/edit.js" - ], - "AnnotatableBlockDisplay": [ - "./xmodule/js/src/xmodule.js", - "./xmodule/js/src/html/display.js", - "./xmodule/js/src/annotatable/display.js", - "./xmodule/js/src/javascript_loader.js", - "./xmodule/js/src/collapsible.js" - ], - "AnnotatableBlockEditor": [ - "./xmodule/js/src/xmodule.js", - "./xmodule/js/src/raw/edit/xml.js" - ], - "ConditionalBlockDisplay": [ - "./xmodule/js/src/xmodule.js", - "./xmodule/js/src/conditional/display.js", - "./xmodule/js/src/javascript_loader.js", - "./xmodule/js/src/collapsible.js" - ], - "ConditionalBlockEditor": [ - "./xmodule/js/src/xmodule.js", - "./xmodule/js/src/sequence/edit.js" - ], - "CourseInfoBlockDisplay": [ - "./xmodule/js/src/xmodule.js", - "./xmodule/js/src/html/display.js", - "./xmodule/js/src/javascript_loader.js", - "./xmodule/js/src/collapsible.js", - "./xmodule/js/src/html/imageModal.js", - "./xmodule/js/common_static/js/vendor/draggabilly.js" - ], - "CourseInfoBlockEditor": [ - "./xmodule/js/src/xmodule.js", - "./xmodule/js/src/html/edit.js" - ], - "CustomTagBlockDisplay": "./xmodule/js/src/xmodule.js", - "CustomTagBlockEditor": [ - "./xmodule/js/src/xmodule.js", - "./xmodule/js/src/raw/edit/xml.js" - ], - "HtmlBlockDisplay": [ - "./xmodule/js/src/xmodule.js", - "./xmodule/js/src/html/display.js", - "./xmodule/js/src/javascript_loader.js", - "./xmodule/js/src/collapsible.js", - "./xmodule/js/src/html/imageModal.js", - "./xmodule/js/common_static/js/vendor/draggabilly.js" - ], - "HtmlBlockEditor": [ - "./xmodule/js/src/xmodule.js", - "./xmodule/js/src/html/edit.js" - ], - "LTIBlockDisplay": [ - "./xmodule/js/src/xmodule.js", - "./xmodule/js/src/lti/lti.js" - ], - "LTIBlockEditor": [ - "./xmodule/js/src/xmodule.js", - "./xmodule/js/src/raw/edit/metadata-only.js" - ], - "LibraryContentBlockDisplay": "./xmodule/js/src/xmodule.js", - "LibraryContentBlockEditor": [ - "./xmodule/js/src/xmodule.js", - "./xmodule/js/src/vertical/edit.js" - ], - "PollBlockDisplay": [ - "./xmodule/js/src/xmodule.js", - "./xmodule/js/src/javascript_loader.js", - "./xmodule/js/src/poll/poll.js", - "./xmodule/js/src/poll/poll_main.js" - ], - "PollBlockEditor": "./xmodule/js/src/xmodule.js", - "ProblemBlockDisplay": [ - "./xmodule/js/src/xmodule.js", - "./xmodule/js/src/javascript_loader.js", - "./xmodule/js/src/capa/display.js", - "./xmodule/js/src/collapsible.js", - "./xmodule/js/src/capa/imageinput.js", - "./xmodule/js/src/capa/schematic.js" - ], - "ProblemBlockEditor": [ - "./xmodule/js/src/xmodule.js", - "./xmodule/js/src/problem/edit.js" - ], - "SequenceBlockDisplay": [ - "./xmodule/js/src/xmodule.js", - "./xmodule/js/src/sequence/display.js" - ], - "SequenceBlockEditor": "./xmodule/js/src/xmodule.js", - "SplitTestBlockDisplay": "./xmodule/js/src/xmodule.js", - "SplitTestBlockEditor": [ - "./xmodule/js/src/xmodule.js", - "./xmodule/js/src/sequence/edit.js" - ], - "StaticTabBlockDisplay": [ - "./xmodule/js/src/xmodule.js", - "./xmodule/js/src/html/display.js", - "./xmodule/js/src/javascript_loader.js", - "./xmodule/js/src/collapsible.js", - "./xmodule/js/src/html/imageModal.js", - "./xmodule/js/common_static/js/vendor/draggabilly.js" - ], - "StaticTabBlockEditor": [ - "./xmodule/js/src/xmodule.js", - "./xmodule/js/src/html/edit.js" - ], - "VideoBlockDisplay": [ - "./xmodule/js/src/xmodule.js", - "./xmodule/js/src/video/10_main.js" - ], - "VideoBlockEditor": [ - "./xmodule/js/src/xmodule.js", - "./xmodule/js/src/tabs/tabs-aggregator.js" - ], - "WordCloudBlockDisplay": [ - "./xmodule/js/src/xmodule.js", - "./xmodule/assets/word_cloud/src/js/word_cloud.js" - ], - "WordCloudBlockEditor": [ - "./xmodule/js/src/xmodule.js", - "./xmodule/js/src/raw/edit/metadata-only.js" - ] - } -}; diff --git a/webpack.common.config.js b/webpack.common.config.js index d81a36b28777..1f84d78cd8e9 100644 --- a/webpack.common.config.js +++ b/webpack.common.config.js @@ -9,7 +9,7 @@ var StringReplace = require('string-replace-webpack-plugin'); var Merge = require('webpack-merge'); var files = require('./webpack-config/file-lists.js'); -var builtinBlocksJS = require('./webpack.builtinblocks.config.js'); +var xmoduleJS = require('./common/static/xmodule/webpack.xmodule.config.js'); var filesWithRequireJSBlocks = [ path.resolve(__dirname, 'common/static/common/js/components/utils/view_utils.js'), @@ -553,4 +553,4 @@ module.exports = Merge.smart({ } } -}, {web: builtinBlocksJS}, workerConfig()); +}, {web: xmoduleJS}, workerConfig()); diff --git a/xmodule/annotatable_block.py b/xmodule/annotatable_block.py index 95f68d4370b6..3c78a3b7c0e7 100644 --- a/xmodule/annotatable_block.py +++ b/xmodule/annotatable_block.py @@ -4,6 +4,7 @@ import textwrap from lxml import etree +from pkg_resources import resource_filename from web_fragments.fragment import Fragment from xblock.core import XBlock from xblock.fields import Scope, String @@ -14,6 +15,7 @@ from xmodule.util.builtin_assets import add_webpack_js_to_fragment, add_sass_to_fragment from xmodule.xml_block import XmlMixin from xmodule.x_module import ( + HTMLSnippet, ResourceTemplates, shim_xmodule_js, XModuleMixin, @@ -33,6 +35,7 @@ class AnnotatableBlock( XmlMixin, EditingMixin, XModuleToXBlockMixin, + HTMLSnippet, ResourceTemplates, XModuleMixin, ): @@ -70,6 +73,22 @@ class AnnotatableBlock( uses_xmodule_styles_setup = True + preview_view_js = { + 'js': [ + resource_filename(__name__, 'js/src/html/display.js'), + resource_filename(__name__, 'js/src/annotatable/display.js'), + resource_filename(__name__, 'js/src/javascript_loader.js'), + resource_filename(__name__, 'js/src/collapsible.js'), + ], + 'xmodule_js': resource_filename(__name__, 'js/src/xmodule.js'), + } + + studio_view_js = { + 'js': [ + resource_filename(__name__, 'js/src/raw/edit/xml.js'), + ], + 'xmodule_js': resource_filename(__name__, 'js/src/xmodule.js'), + } studio_js_module_name = "XMLEditingDescriptor" mako_template = "widgets/raw-edit.html" diff --git a/xmodule/assets/README.rst b/xmodule/assets/README.rst index a39b389a357a..54ed8a92a078 100644 --- a/xmodule/assets/README.rst +++ b/xmodule/assets/README.rst @@ -41,8 +41,8 @@ the corresponding folders in any enabled themes, as part of the edx-platform bui It is collected into the static root, and then linked to from XBlock fragments by the ``add_sass_to_fragment`` function in `builtin_assets.py`_. -.. _AnnotatableBlockDisplay.scss: https://github.com/openedx/edx-platform/tree/master/xmodule/assets/AnnotatableBlockDisplay.scss -.. _AnnotatableBlockEditor.scss: https://github.com/openedx/edx-platform/tree/master/xmodule/assets/AnnotatableBlockEditor.scss +.. _AnnotatableBlockDisplay: https://github.com/openedx/edx-platform/tree/master/xmodule/assets/AnnotatableBlockDisplay.scss +.. _AnnotatableBlockEditor: https://github.com/openedx/edx-platform/tree/master/xmodule/assets/AnnotatableBlockEditor.scss .. _annotatable/_display.scss: https://github.com/openedx/edx-platform/tree/master/xmodule/assets/annotatable/_display.scss .. _simplify things: https://github.com/openedx/edx-platform/issues/32621 @@ -59,7 +59,7 @@ Currently, edx-platform XBlock JS is defined both here in `xmodule/assets`_ and * For many older blocks, their JS is: - * bundled using a `webpack.builtinblocks.config.js`_, + * bundled using a generated Webpack config at ``common/static/xmodule/webpack.xmodule.config.js``, * which is included into `webpack.common.config.js`_, * allowing it to be included into XBlock fragments using ``add_webpack_js_to_fragment`` from `builtin_assets.py`_. @@ -74,7 +74,11 @@ Currently, edx-platform XBlock JS is defined both here in `xmodule/assets`_ and * `VerticalBlock`_ * `LibrarySourcedBlock`_ -As part of an `active build refactoring`_, we will soon consolidate all edx-platform XBlock JS here in `xmodule/assets`_. +As part of an `active build refactoring`_: + +* We will move ``webpack.xmodule.config.js`` here instead of generating it. +* We will consolidate all edx-platform XBlock JS here in `xmodule/assets`_. +* We will delete the ``xmodule_assets`` script. .. _xmodule/assets: https://github.com/openedx/edx-platform/tree/master/xmodule/assets .. _xmodule/js: https://github.com/openedx/edx-platform/tree/master/xmodule/js @@ -87,5 +91,4 @@ As part of an `active build refactoring`_, we will soon consolidate all edx-plat .. _builtin_assets.py: https://github.com/openedx/edx-platform/tree/master/xmodule/util/builtin_assets.py .. _static_content.py: https://github.com/openedx/edx-platform/blob/master/xmodule/static_content.py .. _library_source_block/style.css: https://github.com/openedx/edx-platform/blob/master/xmodule/assets/library_source_block/style.css -.. _webpack.builtinblocks.config.js: https://github.com/openedx/edx-platform/blob/master/webpack.builtinblocks.config.js .. _webpack.common.config.js: https://github.com/openedx/edx-platform/blob/master/webpack.common.config.js diff --git a/xmodule/capa_block.py b/xmodule/capa_block.py index 9b69556e505d..43b5bb3efa42 100644 --- a/xmodule/capa_block.py +++ b/xmodule/capa_block.py @@ -19,6 +19,7 @@ from django.utils.encoding import smart_str from django.utils.functional import cached_property from lxml import etree +from pkg_resources import resource_filename from pytz import utc from web_fragments.fragment import Fragment from xblock.core import XBlock @@ -38,6 +39,7 @@ from xmodule.util.sandboxing import SandboxService from xmodule.util.builtin_assets import add_webpack_js_to_fragment, add_sass_to_fragment from xmodule.x_module import ( + HTMLSnippet, ResourceTemplates, XModuleMixin, XModuleToXBlockMixin, @@ -129,6 +131,7 @@ class ProblemBlock( XmlMixin, EditingMixin, XModuleToXBlockMixin, + HTMLSnippet, ResourceTemplates, XModuleMixin, ): @@ -162,6 +165,24 @@ class ProblemBlock( uses_xmodule_styles_setup = True + preview_view_js = { + 'js': [ + resource_filename(__name__, 'js/src/javascript_loader.js'), + resource_filename(__name__, 'js/src/capa/display.js'), + resource_filename(__name__, 'js/src/collapsible.js'), + resource_filename(__name__, 'js/src/capa/imageinput.js'), + resource_filename(__name__, 'js/src/capa/schematic.js'), + ], + 'xmodule_js': resource_filename(__name__, 'js/src/xmodule.js') + } + + studio_view_js = { + 'js': [ + resource_filename(__name__, 'js/src/problem/edit.js'), + ], + 'xmodule_js': resource_filename(__name__, 'js/src/xmodule.js'), + } + display_name = String( display_name=_("Display Name"), help=_("The display name for this component."), diff --git a/xmodule/conditional_block.py b/xmodule/conditional_block.py index 03662deb01e9..5f484050d438 100644 --- a/xmodule/conditional_block.py +++ b/xmodule/conditional_block.py @@ -9,6 +9,7 @@ from lazy import lazy from lxml import etree from opaque_keys.edx.locator import BlockUsageLocator +from pkg_resources import resource_filename from web_fragments.fragment import Fragment from xblock.core import XBlock from xblock.fields import ReferenceList, Scope, String @@ -22,6 +23,7 @@ from xmodule.validation import StudioValidation, StudioValidationMessage from xmodule.xml_block import XmlMixin from xmodule.x_module import ( + HTMLSnippet, ResourceTemplates, shim_xmodule_js, STUDENT_VIEW, @@ -42,6 +44,7 @@ class ConditionalBlock( MakoTemplateBlockBase, XmlMixin, XModuleToXBlockMixin, + HTMLSnippet, ResourceTemplates, XModuleMixin, StudioEditableBlock, @@ -143,8 +146,21 @@ class ConditionalBlock( show_in_read_only_mode = True + preview_view_js = { + 'js': [ + resource_filename(__name__, 'js/src/conditional/display.js'), + resource_filename(__name__, 'js/src/javascript_loader.js'), + resource_filename(__name__, 'js/src/collapsible.js'), + ], + 'xmodule_js': resource_filename(__name__, 'js/src/xmodule.js'), + } + mako_template = 'widgets/metadata-edit.html' studio_js_module_name = 'SequenceDescriptor' + studio_view_js = { + 'js': [resource_filename(__name__, 'js/src/sequence/edit.js')], + 'xmodule_js': resource_filename(__name__, 'js/src/xmodule.js'), + } # Map # key: diff --git a/xmodule/discussion_block.py b/xmodule/discussion_block.py index 7433b51c9f94..2df7818157b2 100644 --- a/xmodule/discussion_block.py +++ b/xmodule/discussion_block.py @@ -15,13 +15,11 @@ from xblockutils.resources import ResourceLoader from xblockutils.studio_editable import StudioEditableXBlockMixin -from lms.djangoapps.discussion.django_comment_client.permissions import has_permission from openedx.core.djangoapps.discussions.models import DiscussionsConfiguration, Provider from openedx.core.djangolib.markup import HTML, Text from openedx.core.lib.xblock_utils import get_css_dependencies, get_js_dependencies from xmodule.xml_block import XmlMixin - log = logging.getLogger(__name__) loader = ResourceLoader(__name__) # pylint: disable=invalid-name @@ -156,6 +154,9 @@ def has_permission(self, permission): :param str permission: Permission :rtype: bool """ + # normal import causes the xmodule_assets command to fail due to circular import - hence importing locally + from lms.djangoapps.discussion.django_comment_client.permissions import has_permission + return has_permission(self.django_user, permission, self.course_key) def student_view(self, context=None): diff --git a/xmodule/error_block.py b/xmodule/error_block.py index a3379007e19f..fcebf8f744ff 100644 --- a/xmodule/error_block.py +++ b/xmodule/error_block.py @@ -18,6 +18,7 @@ from xmodule.errortracker import exc_info_to_str from xmodule.modulestore import EdxJSONEncoder from xmodule.x_module import ( + HTMLSnippet, ResourceTemplates, XModuleMixin, XModuleToXBlockMixin, @@ -46,6 +47,7 @@ class ErrorFields: class ErrorBlock( ErrorFields, XModuleToXBlockMixin, + HTMLSnippet, ResourceTemplates, XModuleMixin, ): # pylint: disable=abstract-method diff --git a/xmodule/html_block.py b/xmodule/html_block.py index 688e515c80e9..3027c24b2ba8 100644 --- a/xmodule/html_block.py +++ b/xmodule/html_block.py @@ -8,6 +8,8 @@ import textwrap from datetime import datetime +from pkg_resources import resource_filename + from django.conf import settings from fs.errors import ResourceNotFound from lxml import etree @@ -25,6 +27,7 @@ from xmodule.util.misc import escape_html_characters from xmodule.util.builtin_assets import add_webpack_js_to_fragment, add_sass_to_fragment from xmodule.x_module import ( + HTMLSnippet, ResourceTemplates, shim_xmodule_js, XModuleMixin, @@ -44,7 +47,7 @@ @XBlock.needs("user") class HtmlBlockMixin( # lint-amnesty, pylint: disable=abstract-method XmlMixin, EditingMixin, - XModuleToXBlockMixin, ResourceTemplates, XModuleMixin, + XModuleToXBlockMixin, HTMLSnippet, ResourceTemplates, XModuleMixin, ): """ The HTML XBlock mixin. @@ -141,6 +144,17 @@ def studio_view(self, _context): shim_xmodule_js(fragment, 'HTMLEditingDescriptor') return fragment + preview_view_js = { + 'js': [ + resource_filename(__name__, 'js/src/html/display.js'), + resource_filename(__name__, 'js/src/javascript_loader.js'), + resource_filename(__name__, 'js/src/collapsible.js'), + resource_filename(__name__, 'js/src/html/imageModal.js'), + resource_filename(__name__, 'js/common_static/js/vendor/draggabilly.js'), + ], + 'xmodule_js': resource_filename(__name__, 'js/src/xmodule.js'), + } + uses_xmodule_styles_setup = True mako_template = "widgets/html-edit.html" @@ -149,6 +163,13 @@ def studio_view(self, _context): template_dir_name = "html" show_in_read_only_mode = True + studio_view_js = { + 'js': [ + resource_filename(__name__, 'js/src/html/edit.js') + ], + 'xmodule_js': resource_filename(__name__, 'js/src/xmodule.js'), + } + # VS[compat] TODO (cpennington): Delete this method once all fall 2012 course # are being edited in the cms @classmethod diff --git a/xmodule/library_content_block.py b/xmodule/library_content_block.py index 047d36abca52..74ab77630626 100644 --- a/xmodule/library_content_block.py +++ b/xmodule/library_content_block.py @@ -17,6 +17,7 @@ from lxml import etree from lxml.etree import XMLSyntaxError from opaque_keys.edx.locator import LibraryLocator +from pkg_resources import resource_filename from web_fragments.fragment import Fragment from webob import Response from xblock.completable import XBlockCompletionMode @@ -30,6 +31,7 @@ from xmodule.validation import StudioValidation, StudioValidationMessage from xmodule.xml_block import XmlMixin from xmodule.x_module import ( + HTMLSnippet, ResourceTemplates, shim_xmodule_js, STUDENT_VIEW, @@ -74,6 +76,7 @@ class LibraryContentBlock( MakoTemplateBlockBase, XmlMixin, XModuleToXBlockMixin, + HTMLSnippet, ResourceTemplates, XModuleMixin, StudioEditableBlock, @@ -92,8 +95,19 @@ class LibraryContentBlock( resources_dir = 'assets/library_content' + preview_view_js = { + 'js': [], + 'xmodule_js': resource_filename(__name__, 'js/src/xmodule.js'), + } + mako_template = 'widgets/metadata-edit.html' studio_js_module_name = "VerticalDescriptor" + studio_view_js = { + 'js': [ + resource_filename(__name__, 'js/src/vertical/edit.js'), + ], + 'xmodule_js': resource_filename(__name__, 'js/src/xmodule.js'), + } show_in_read_only_mode = True diff --git a/xmodule/lti_block.py b/xmodule/lti_block.py index 4228aec74393..80afed7a0887 100644 --- a/xmodule/lti_block.py +++ b/xmodule/lti_block.py @@ -68,6 +68,7 @@ from django.conf import settings from lxml import etree from oauthlib.oauth1.rfc5849 import signature +from pkg_resources import resource_filename from pytz import UTC from webob import Response from web_fragments.fragment import Fragment @@ -87,6 +88,7 @@ from xmodule.util.builtin_assets import add_webpack_js_to_fragment, add_sass_to_fragment from xmodule.xml_block import XmlMixin from xmodule.x_module import ( + HTMLSnippet, ResourceTemplates, shim_xmodule_js, XModuleMixin, @@ -282,6 +284,7 @@ class LTIBlock( EditingMixin, MakoTemplateBlockBase, XModuleToXBlockMixin, + HTMLSnippet, ResourceTemplates, XModuleMixin, ): # pylint: disable=abstract-method @@ -369,9 +372,22 @@ class LTIBlock( resources_dir = None uses_xmodule_styles_setup = True + preview_view_js = { + 'js': [ + resource_filename(__name__, 'js/src/lti/lti.js') + ], + 'xmodule_js': resource_filename(__name__, 'js/src/xmodule.js'), + } + mako_template = 'widgets/metadata-only-edit.html' studio_js_module_name = 'MetadataOnlyEditingDescriptor' + studio_view_js = { + 'js': [ + resource_filename(__name__, 'js/src/raw/edit/metadata-only.js') + ], + 'xmodule_js': resource_filename(__name__, 'js/src/xmodule.js'), + } def studio_view(self, _context): """ diff --git a/xmodule/poll_block.py b/xmodule/poll_block.py index c4d9372883ee..6093237fa3c5 100644 --- a/xmodule/poll_block.py +++ b/xmodule/poll_block.py @@ -13,6 +13,7 @@ from collections import OrderedDict from copy import deepcopy +from pkg_resources import resource_filename from web_fragments.fragment import Fragment from lxml import etree @@ -23,6 +24,7 @@ from xmodule.stringify import stringify_children from xmodule.util.builtin_assets import add_webpack_js_to_fragment, add_sass_to_fragment from xmodule.x_module import ( + HTMLSnippet, ResourceTemplates, shim_xmodule_js, XModuleMixin, @@ -40,6 +42,7 @@ class PollBlock( MakoTemplateBlockBase, XmlMixin, XModuleToXBlockMixin, + HTMLSnippet, ResourceTemplates, XModuleMixin, ): # pylint: disable=abstract-method @@ -81,6 +84,22 @@ class PollBlock( resources_dir = None uses_xmodule_styles_setup = True + preview_view_js = { + 'js': [ + resource_filename(__name__, 'js/src/javascript_loader.js'), + resource_filename(__name__, 'js/src/poll/poll.js'), + resource_filename(__name__, 'js/src/poll/poll_main.js') + ], + 'xmodule_js': resource_filename(__name__, 'js/src/xmodule.js'), + } + + # There is no studio_view() for this XBlock but this is needed to make the + # the static_content command happy. + studio_view_js = { + 'js': [], + 'xmodule_js': resource_filename(__name__, 'js/src/xmodule.js') + } + def handle_ajax(self, dispatch, data): # lint-amnesty, pylint: disable=unused-argument """Ajax handler. diff --git a/xmodule/randomize_block.py b/xmodule/randomize_block.py index b8a1432ff311..c5e1f2c4747f 100644 --- a/xmodule/randomize_block.py +++ b/xmodule/randomize_block.py @@ -11,6 +11,7 @@ from xmodule.seq_block import SequenceMixin from xmodule.xml_block import XmlMixin from xmodule.x_module import ( + HTMLSnippet, ResourceTemplates, STUDENT_VIEW, XModuleMixin, @@ -25,6 +26,7 @@ class RandomizeBlock( MakoTemplateBlockBase, XmlMixin, XModuleToXBlockMixin, + HTMLSnippet, ResourceTemplates, XModuleMixin, ): diff --git a/xmodule/seq_block.py b/xmodule/seq_block.py index 5561d034da87..b0a2789fd273 100644 --- a/xmodule/seq_block.py +++ b/xmodule/seq_block.py @@ -14,6 +14,7 @@ from lxml import etree from opaque_keys.edx.keys import UsageKey +from pkg_resources import resource_filename from pytz import UTC from web_fragments.fragment import Fragment from xblock.completable import XBlockCompletionMode @@ -24,6 +25,7 @@ from edx_toggles.toggles import WaffleFlag, SettingDictToggle from xmodule.util.builtin_assets import add_webpack_js_to_fragment, add_sass_to_fragment from xmodule.x_module import ( + HTMLSnippet, ResourceTemplates, shim_xmodule_js, STUDENT_VIEW, @@ -256,6 +258,7 @@ class SequenceBlock( MakoTemplateBlockBase, XmlMixin, XModuleToXBlockMixin, + HTMLSnippet, ResourceTemplates, XModuleMixin, ): @@ -268,6 +271,20 @@ class SequenceBlock( show_in_read_only_mode = True uses_xmodule_styles_setup = True + preview_view_js = { + 'js': [ + resource_filename(__name__, 'js/src/sequence/display.js'), + ], + 'xmodule_js': resource_filename(__name__, 'js/src/xmodule.js') + } + + # There is no studio_view() for this XBlock but this is needed to make the + # the static_content command happy. + studio_view_js = { + 'js': [], + 'xmodule_js': resource_filename(__name__, 'js/src/xmodule.js') + } + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/xmodule/split_test_block.py b/xmodule/split_test_block.py index 8984dc62dbb5..5984ae531efc 100644 --- a/xmodule/split_test_block.py +++ b/xmodule/split_test_block.py @@ -12,6 +12,7 @@ from django.utils.functional import cached_property from lxml import etree +from pkg_resources import resource_filename from web_fragments.fragment import Fragment from webob import Response from xblock.core import XBlock @@ -25,6 +26,7 @@ from xmodule.validation import StudioValidation, StudioValidationMessage from xmodule.xml_block import XmlMixin from xmodule.x_module import ( + HTMLSnippet, ResourceTemplates, shim_xmodule_js, STUDENT_VIEW, @@ -130,6 +132,7 @@ class SplitTestBlock( # lint-amnesty, pylint: disable=abstract-method MakoTemplateBlockBase, XmlMixin, XModuleToXBlockMixin, + HTMLSnippet, ResourceTemplates, XModuleMixin, StudioEditableBlock, @@ -155,8 +158,17 @@ class SplitTestBlock( # lint-amnesty, pylint: disable=abstract-method show_in_read_only_mode = True + preview_view_js = { + 'js': [], + 'xmodule_js': resource_filename(__name__, 'js/src/xmodule.js'), + } + mako_template = "widgets/metadata-only-edit.html" studio_js_module_name = 'SequenceDescriptor' + studio_view_js = { + 'js': [resource_filename(__name__, 'js/src/sequence/edit.js')], + 'xmodule_js': resource_filename(__name__, 'js/src/xmodule.js'), + } @cached_property def child_block(self): diff --git a/xmodule/static_content.py b/xmodule/static_content.py index e6c1aed23530..ec2a6b2524a6 100755 --- a/xmodule/static_content.py +++ b/xmodule/static_content.py @@ -1,25 +1,204 @@ # /usr/bin/env python """ -This module used to hold a CLI utility for gathering up the JS and Sass used by several built-in XBlocks. +Generate /webpack.xmodule.config.js, with a display & editor Webpack bundle for each builtin block. -It now remains as a stub, just for backwards compatibility. +It looks like this: -It will soon be removed as part of https://github.com/openedx/edx-platform/issues/31798. + module.exports = { + "entry": { + "AboutBlockDisplay": [ + "./xmodule/js/src/xmodule.js", + "./xmodule/js/src/html/display.js", + "./xmodule/js/src/javascript_loader.js", + "./xmodule/js/src/collapsible.js", + "./xmodule/js/src/html/imageModal.js", + "./xmodule/js/common_static/js/vendor/draggabilly.js" + ], + "AboutBlockEditor": [ + "./xmodule/js/src/xmodule.js", + "./xmodule/js/src/html/edit.js" + ], + "AnnotatableBlockDisplay": [ + "./xmodule/js/src/xmodule.js", + "./xmodule/js/src/html/display.js", + "./xmodule/js/src/annotatable/display.js", + "./xmodule/js/src/javascript_loader.js", + "./xmodule/js/src/collapsible.js" + ], + ... etc. + } + } + +Don't add to this! It will soon be removed as part of: https://github.com/openedx/edx-platform/issues/32481 """ + + +import errno +import json import logging +import os import sys +import textwrap +from pkg_resources import resource_filename + +import django +from pathlib import Path as path + +from xmodule.annotatable_block import AnnotatableBlock +from xmodule.capa_block import ProblemBlock +from xmodule.conditional_block import ConditionalBlock +from xmodule.html_block import AboutBlock, CourseInfoBlock, HtmlBlock, StaticTabBlock +from xmodule.library_content_block import LibraryContentBlock +from xmodule.lti_block import LTIBlock +from xmodule.poll_block import PollBlock +from xmodule.seq_block import SequenceBlock +from xmodule.split_test_block import SplitTestBlock +from xmodule.template_block import CustomTagBlock +from xmodule.word_cloud_block import WordCloudBlock +from xmodule.x_module import HTMLSnippet + +LOG = logging.getLogger(__name__) + + +class VideoBlock(HTMLSnippet): # lint-amnesty, pylint: disable=abstract-method + """ + Static assets for VideoBlock. + Kept here because importing VideoBlock code requires Django to be setup. + """ + + preview_view_js = { + 'js': [ + resource_filename(__name__, 'js/src/video/10_main.js'), + ], + 'xmodule_js': resource_filename(__name__, 'js/src/xmodule.js') + } + + studio_view_js = { + 'js': [ + resource_filename(__name__, 'js/src/tabs/tabs-aggregator.js'), + ], + 'xmodule_js': resource_filename(__name__, 'js/src/xmodule.js'), + } + + +# List of XBlocks which use this static content setup. +# Should only be used for XModules being converted to XBlocks. +XBLOCK_CLASSES = [ + AboutBlock, + AnnotatableBlock, + ConditionalBlock, + CourseInfoBlock, + CustomTagBlock, + HtmlBlock, + LibraryContentBlock, + LTIBlock, + PollBlock, + ProblemBlock, + SequenceBlock, + SplitTestBlock, + StaticTabBlock, + VideoBlock, + WordCloudBlock, +] + + +def _ensure_dir(directory): + """Ensure that `directory` exists.""" + try: + os.makedirs(directory) + except OSError as exc: + if exc.errno == errno.EEXIST: + pass + else: + raise + + +def write_webpack(output_file, module_files, descriptor_files): + """ + Write all xmodule and xmodule descriptor javascript into module-specific bundles. + + The output format should be suitable for smart-merging into an existing webpack configuration. + """ + _ensure_dir(output_file.parent) + + config = { + 'entry': {} + } + for (owner, unique_files) in list(module_files.items()) + list(descriptor_files.items()): + if len(unique_files) == 1: + unique_files = unique_files[0] + config['entry'][owner] = unique_files + with output_file.open('w') as outfile: + outfile.write( + textwrap.dedent("""\ + module.exports = {config_json}; + """).format( + config_json=json.dumps( + config, + indent=4, + sort_keys=True, + ) + ) + ) def main(): """ - Warn that this script is now a stub, and return success (zero). + Generate the weback config. + + Usage: static_content.py """ - logging.warning( - "xmodule/static_content.py, aka xmodule_assets, is now a no-op. " - "Please remove calls to it from your build pipeline. It will soon be deleted.", + from django.conf import settings + # Install only the apps whose models are imported when this runs + installed_apps = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'config_models', + 'openedx.core.djangoapps.video_config', + 'openedx.core.djangoapps.video_pipeline', ) - return 0 + try: + import edxval # lint-amnesty, pylint: disable=unused-import + installed_apps += ('edxval',) + except ImportError: + pass + if not settings.configured: + settings.configure( + INSTALLED_APPS=installed_apps, + ) + django.setup() + + try: + root = path(sys.argv[1]) + except IndexError: + sys.exit(main.__doc__) + + # We assume this module is located at edx-platform/xmodule/static_content.py. + # Not the most robust assumption, but this script will be gone soon. + repo_root = path(__file__).parent.parent + + module_files = { + class_.get_preview_view_js_bundle_name(): [ + "./" + str(path(fragment_path).relative_to(repo_root)) + for fragment_path in [ + class_.get_preview_view_js()['xmodule_js'], + *class_.get_preview_view_js().get('js', []), + ] + ] + for class_ in XBLOCK_CLASSES + } + descriptor_files = { + class_.get_studio_view_js_bundle_name(): [ + "./" + str(path(fragment_path).relative_to(repo_root)) + for fragment_path in [ + class_.get_studio_view_js()['xmodule_js'], + *class_.get_studio_view_js().get('js', []), + ] + ] + for class_ in XBLOCK_CLASSES + } + write_webpack(root / 'webpack.xmodule.config.js', module_files, descriptor_files) -if __name__ == "__main__": +if __name__ == '__main__': sys.exit(main()) diff --git a/xmodule/template_block.py b/xmodule/template_block.py index fd5373386061..a27b92cb2c7e 100644 --- a/xmodule/template_block.py +++ b/xmodule/template_block.py @@ -6,11 +6,13 @@ from xblock.core import XBlock from lxml import etree +from pkg_resources import resource_filename from web_fragments.fragment import Fragment from xmodule.editing_block import EditingMixin from xmodule.raw_block import RawMixin from xmodule.util.builtin_assets import add_webpack_js_to_fragment, add_sass_to_fragment from xmodule.x_module import ( + HTMLSnippet, ResourceTemplates, shim_xmodule_js, XModuleMixin, @@ -26,6 +28,7 @@ class CustomTagTemplateBlock( # pylint: disable=abstract-method XmlMixin, EditingMixin, XModuleToXBlockMixin, + HTMLSnippet, ResourceTemplates, XModuleMixin, ): @@ -62,6 +65,15 @@ class CustomTagBlock(CustomTagTemplateBlock): # pylint: disable=abstract-method resources_dir = None template_dir_name = 'customtag' + preview_view_js = { + 'js': [], + 'xmodule_js': resource_filename(__name__, 'js/src/xmodule.js'), + } + studio_view_js = { + 'js': [resource_filename(__name__, 'js/src/raw/edit/xml.js')], + 'xmodule_js': resource_filename(__name__, 'js/src/xmodule.js'), + } + def studio_view(self, _context): """ Return the studio view. diff --git a/xmodule/video_block/video_block.py b/xmodule/video_block/video_block.py index 5bf2b3c28cf1..97c41c2f6412 100644 --- a/xmodule/video_block/video_block.py +++ b/xmodule/video_block/video_block.py @@ -52,7 +52,7 @@ from xmodule.video_block import manage_video_subtitles_save from xmodule.x_module import ( PUBLIC_VIEW, STUDENT_VIEW, - ResourceTemplates, shim_xmodule_js, + HTMLSnippet, ResourceTemplates, shim_xmodule_js, XModuleMixin, XModuleToXBlockMixin, ) from xmodule.xml_block import XmlMixin, deserialize_field, is_pointer_tag, name_to_pathname @@ -121,7 +121,7 @@ @XBlock.needs('mako', 'user') class VideoBlock( VideoFields, VideoTranscriptsMixin, VideoStudioViewHandlers, VideoStudentViewHandlers, - EmptyDataRawMixin, XmlMixin, EditingMixin, XModuleToXBlockMixin, + EmptyDataRawMixin, XmlMixin, EditingMixin, XModuleToXBlockMixin, HTMLSnippet, ResourceTemplates, XModuleMixin, LicenseMixin): """ XML source example: @@ -282,9 +282,6 @@ def public_view(self, context): return fragment def get_html(self, view=STUDENT_VIEW, context=None): # lint-amnesty, pylint: disable=arguments-differ, too-many-statements - """ - Return html for a given view of this block. - """ context = context or {} track_status = (self.download_track and self.track) transcript_download_format = self.transcript_download_format if not track_status else None diff --git a/xmodule/word_cloud_block.py b/xmodule/word_cloud_block.py index 6a31da5ef811..8be08242dbae 100644 --- a/xmodule/word_cloud_block.py +++ b/xmodule/word_cloud_block.py @@ -10,6 +10,8 @@ import json import logging +from pkg_resources import resource_filename + from web_fragments.fragment import Fragment from xblock.core import XBlock from xblock.fields import Boolean, Dict, Integer, List, Scope, String @@ -18,6 +20,7 @@ from xmodule.util.builtin_assets import add_webpack_js_to_fragment, add_sass_to_fragment from xmodule.xml_block import XmlMixin from xmodule.x_module import ( + HTMLSnippet, ResourceTemplates, shim_xmodule_js, XModuleMixin, @@ -46,6 +49,7 @@ class WordCloudBlock( # pylint: disable=abstract-method XmlMixin, EditingMixin, XModuleToXBlockMixin, + HTMLSnippet, ResourceTemplates, XModuleMixin, ): @@ -108,6 +112,19 @@ class WordCloudBlock( # pylint: disable=abstract-method resources_dir = 'assets/word_cloud' template_dir_name = 'word_cloud' + preview_view_js = { + 'js': [ + resource_filename(__name__, 'assets/word_cloud/src/js/word_cloud.js'), + ], + 'xmodule_js': resource_filename(__name__, 'js/src/xmodule.js'), + } + + studio_view_js = { + 'js': [ + resource_filename(__name__, 'js/src/raw/edit/metadata-only.js'), + ], + 'xmodule_js': resource_filename(__name__, 'js/src/xmodule.js'), + } studio_js_module_name = "MetadataOnlyEditingDescriptor" mako_template = "widgets/metadata-only-edit.html" diff --git a/xmodule/x_module.py b/xmodule/x_module.py index fd974c6928f8..1c44b2cc0b8d 100644 --- a/xmodule/x_module.py +++ b/xmodule/x_module.py @@ -206,6 +206,40 @@ def create_definition(self, block_type, slug=None): raise NotImplementedError("Specific Modulestores must provide implementations of create_definition") +class HTMLSnippet: + """ + A base class defining an interface for an object that is able to present an + html snippet, along with associated javascript and css + """ + + preview_view_js = {} + studio_view_js = {} + + @classmethod + def get_preview_view_js(cls): + return cls.preview_view_js + + @classmethod + def get_preview_view_js_bundle_name(cls): + return cls.__name__ + 'Display' + + @classmethod + def get_studio_view_js(cls): + return cls.studio_view_js + + @classmethod + def get_studio_view_js_bundle_name(cls): + return cls.__name__ + 'Editor' + + def get_html(self): + """ + Return the html used to display this snippet + """ + raise NotImplementedError( + "get_html() must be provided by specific modules - not present in {}" + .format(self.__class__)) + + def shim_xmodule_js(fragment, js_module_name): """ Set up the XBlock -> XModule shim on the supplied :class:`web_fragments.fragment.Fragment`