diff --git a/Products/CMFPlone/resources/browser/resource.py b/Products/CMFPlone/resources/browser/resource.py index aba363c73d..62a8ae1278 100644 --- a/Products/CMFPlone/resources/browser/resource.py +++ b/Products/CMFPlone/resources/browser/resource.py @@ -94,6 +94,11 @@ def update(self): # collect names js_names = [name for name, rec in records.items() if rec.jscompilation] css_names = [name for name, rec in records.items() if rec.csscompilation] + # Allow bundles to have "theme" resp. "theme" and "custom" as dependency + # so that bundles can be rendered after theme and custom resources. + js_names += ["theme"] + css_names += ["theme", "custom"] + all_names = [ name for name, rec in records.items() diff --git a/Products/CMFPlone/tests/testResourceRegistries.py b/Products/CMFPlone/tests/testResourceRegistries.py index 3f3fbc0838..2a1f12e742 100644 --- a/Products/CMFPlone/tests/testResourceRegistries.py +++ b/Products/CMFPlone/tests/testResourceRegistries.py @@ -165,6 +165,34 @@ def test_bundle_depends(self): results = view.render() self.assertIn("http://foo.bar/foobar.js", results) + def test_bundle_depends_theme(self): + from lxml import etree + + # Bluntly set a JavaScript file for the theme to ensure the "theme" + # dependency exists. + from plone.app.theming.utils import theming_policy + theme = theming_policy(self.layer["request"]).get_theme() + theme.production_js = "theme.js" + + bundle = self._make_test_bundle() + + # This bundle should depend on "theme" + bundle.depends = "theme" + + view = ScriptsView(self.app, self.app.REQUEST, None, None) + view.update() + results = view.render() + + parser = etree.HTMLParser() + parsed = etree.fromstring(results, parser) + scripts = parsed.xpath("//script") + + # The last element is our script bundle depending on "theme" + self.assertEqual("http://foo.bar/foobar.js", scripts[-1].attrib["src"]) + + # The second last element is the theme javascript itself. + self.assertIn("theme.js", scripts[-2].attrib["src"]) + def test_bundle_depends_on_multiple(self): bundle = self._make_test_bundle() bundle.depends = "plone,eventedit" @@ -217,6 +245,19 @@ def test_relative_uri_resource(self): class TestStylesViewlet(PloneTestCase.PloneTestCase): + def _make_test_bundle(self): + registry = getUtility(IRegistry) + + bundles = registry.collectionOfInterface( + IBundleRegistry, prefix="plone.bundles" + ) + bundle = bundles.add("foobar") + bundle.name = "foobar" + bundle.jscompilation = "http://foo.bar/foobar.js" + bundle.csscompilation = "http://foo.bar/foobar.css" + bundle.resources = ["foobar"] + return bundle + def test_styles_viewlet(self): styles = StylesView(self.layer["portal"], self.layer["request"], None) styles.update() @@ -323,6 +364,34 @@ def test_remove_bundle_on_request_with_subrequest(self): result = scripts.render() self.assertNotIn("http://test.foo/test.min.js", result) + def test_bundle_depends_theme(self): + from lxml import etree + + # Bluntly set a JavaScript file for the theme to ensure the "theme" + # dependency exists. + from plone.app.theming.utils import theming_policy + theme = theming_policy(self.layer["request"]).get_theme() + theme.production_css = "theme.css" + + bundle = self._make_test_bundle() + + # This bundle should depend on "theme" + bundle.depends = "theme" + + view = StylesView(self.app, self.app.REQUEST, None, None) + view.update() + results = view.render() + + parser = etree.HTMLParser() + parsed = etree.fromstring(results, parser) + styles = parsed.xpath("//link") + + # The last element is our script bundle depending on "theme" + self.assertEqual("http://foo.bar/foobar.css", styles[-1].attrib["href"]) + + # The second last element is the theme javascript itself. + self.assertIn("theme.css", styles[-2].attrib["href"]) + class TestExpressions(PloneTestCase.PloneTestCase): def logout(self): diff --git a/news/4054.feature b/news/4054.feature new file mode 100644 index 0000000000..52b6492c3e --- /dev/null +++ b/news/4054.feature @@ -0,0 +1,11 @@ +Allow bundles to be rendered after "theme" and "custom" bundles. + +Bundles can now depend on the automatically added "theme" and "custom" bundles +from plone.app.theming and be rendered after those (e.g. after the +plonetheme.barceloneta CSS). + +This allows to override a theme with custom CSS from a bundle instead of having +to add the CSS customizations to the registry via the "custom_css" settings. +As a consequence, theme customization can now be done in the filesystem in +ordinary CSS files instead of being bound to a time consuming workflow which +involces upgrading the registry after every change.