From 283219ab5ef4344e3808cd61b4cc0821886acffb Mon Sep 17 00:00:00 2001 From: Radomir Dopieralski Date: Thu, 21 Nov 2024 17:38:39 +0100 Subject: [PATCH] Replace django-pyscss with libsass This is the same as the django-libsass patch, but using our own django filter. This no longer depends on django-libsass, and handles paths the same way as the old code did, respecting static file prefixes. It requires fewer changes, and no longer assumes collectstatic has been run. Depends-On: https://review.opendev.org/c/openstack/requirements/+/934220 Change-Id: I82f802ef33f1e2071c5e42f806a6a8d9ff7e26e4 --- horizon/test/settings.py | 1 - horizon/utils/scss_filter.py | 94 ++++++++++++------- .../django_pyscss_fix/__init__.py | 50 ---------- openstack_dashboard/settings.py | 6 +- .../themes/material/static/_variables.scss | 1 + .../material/static/bootstrap/_variables.scss | 2 + .../material/static/horizon/_icons.scss | 1 - .../material/static/horizon/_styles.scss | 9 -- .../components/_loader_circular_example.scss | 2 +- requirements.txt | 3 +- 10 files changed, 67 insertions(+), 102 deletions(-) delete mode 100644 openstack_dashboard/django_pyscss_fix/__init__.py diff --git a/horizon/test/settings.py b/horizon/test/settings.py index 17031debba..48a3f346bb 100644 --- a/horizon/test/settings.py +++ b/horizon/test/settings.py @@ -51,7 +51,6 @@ INSTALLED_APPS = ( 'django.contrib.humanize', 'django.contrib.auth', 'django.contrib.contenttypes', - 'django_pyscss', 'compressor', 'horizon', 'horizon.test', diff --git a/horizon/utils/scss_filter.py b/horizon/utils/scss_filter.py index 609cb7f38a..724ffc76ac 100644 --- a/horizon/utils/scss_filter.py +++ b/horizon/utils/scss_filter.py @@ -1,44 +1,70 @@ -# (c) Copyright 2015 Hewlett-Packard Development Company, L.P. +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at # -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 # -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. -from django.conf import settings +import os.path -from django_pyscss.compressor import DjangoScssFilter -from django_pyscss import DjangoScssCompiler +from compressor.filters.base import FilterBase +from django.contrib.staticfiles.finders import get_finders -from scss.namespace import Namespace -from scss.types import String +import sass -class HorizonScssFilter(DjangoScssFilter): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) +def importer(path, prev): + if path.startswith('/'): + # An absolute path was used, don't try relative paths. + candidates = [path[1:]] + elif prev == 'stdin': + # The parent is STDIN, so only try absolute paths. + candidates = [path] + else: + # Try both relative and absolute paths, prefer relative. + candidates = [ + os.path.normpath(os.path.join(os.path.dirname(prev), path)), + path, + ] + # Try adding _ in front of the file for partials. + for candidate in candidates[:]: + if '/' in candidate: + candidates.insert(0, '/_'.join(candidate.rsplit('/', 1))) + else: + candidates.insert(0, '_' + candidate) + # Try adding extensions. + for candidate in candidates[:]: + for ext in ['.scss', '.sass', '.css']: + candidates.append(candidate + ext) + for finder in get_finders(): + # We can't use finder.find() because we need the prefixes. + for storage_filename, storage in finder.list([]): + prefix = getattr(storage, "prefix", "") + filename = os.path.join(prefix, storage_filename) + if filename in candidates: + with storage.open(storage_filename) as f: + data = f.read() + return [(filename, data)] - self.namespace = Namespace() - # Add variables to the SCSS Global Namespace Here - self.namespace.set_variable( - '$static_url', - String(settings.STATIC_URL) - ) +class ScssFilter(FilterBase): + def __init__(self, content, attrs=None, filter_type=None, charset=None, + filename=None): + super().__init__( + content=content, filter_type=filter_type, filename=filename) - # Create a compiler with the right namespace - @property - def compiler(self): - return DjangoScssCompiler( - # output_style is 'nested' by default, which is crazy. See: - # https://github.com/Kronuz/pyScss/issues/243 - output_style='compact', # or 'compressed' - namespace=self.namespace - ) + def input(self, **kwargs): + args = { + 'importers': [(0, importer)], + 'output_style': 'compressed', + } + if self.filename: + args['filename'] = self.filename + else: + args['string'] = self.content + return sass.compile(**args) diff --git a/openstack_dashboard/django_pyscss_fix/__init__.py b/openstack_dashboard/django_pyscss_fix/__init__.py deleted file mode 100644 index 4965e56d53..0000000000 --- a/openstack_dashboard/django_pyscss_fix/__init__.py +++ /dev/null @@ -1,50 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import logging -import os - -from django.conf import settings -from scss.grammar.expression import SassExpressionScanner - - -scss_asset_root = os.path.join(settings.STATIC_ROOT, 'scss', 'assets') -LOG = logging.getLogger(__name__) - -""" -This is a workaround for https://bugs.launchpad.net/horizon/+bug/1367590 -It works by creating a path that django_scss will attempt to create -later if it doesn't exist. The django_pyscss code fails -intermittently because of concurrency issues. This code ignores the -exception and if it was anything other than the concurrency issue -django_pyscss will discover the problem later. - -TODO (doug-fish): remove this workaround once fix for -https://github.com/fusionbox/django-pyscss/issues/23 is picked up. -""" -try: - if not os.path.exists(scss_asset_root): - os.makedirs(scss_asset_root) -except Exception as e: - LOG.info("Error precreating path %(root)s, %(exc)s", - {'root': scss_asset_root, 'exc': e}) - -# Fix a syntax error in regular expression, where a flag is not at the -# beginning of the expression. -# This is fixed upstream at -# https://github.com/Kronuz/pyScss/commit -# /73559d047706ccd4593cf6aa092de71f35164723 -# We should remove it once we use that version. - -for index, (name, value) in enumerate(SassExpressionScanner._patterns): - if name == 'OPACITY': - SassExpressionScanner._patterns[index] = ('OPACITY', '(?i)(opacity)') diff --git a/openstack_dashboard/settings.py b/openstack_dashboard/settings.py index ed0c83419b..98db144ead 100644 --- a/openstack_dashboard/settings.py +++ b/openstack_dashboard/settings.py @@ -132,9 +132,10 @@ STATICFILES_FINDERS = ( ) COMPRESS_PRECOMPILERS = ( - ('text/scss', 'horizon.utils.scss_filter.HorizonScssFilter'), + ('text/scss', 'horizon.utils.scss_filter.ScssFilter'), ) + COMPRESS_FILTERS = { 'css': ( 'compressor.filters.css_default.CssAbsoluteFilter', @@ -157,8 +158,6 @@ INSTALLED_APPS = [ 'django.contrib.messages', 'django.contrib.staticfiles', 'django.contrib.humanize', - 'openstack_dashboard.django_pyscss_fix', - 'django_pyscss', 'debreach', 'compressor', 'horizon', @@ -377,7 +376,6 @@ HORIZON_COMPRESS_OFFLINE_CONTEXT_BASE = { if DEBUG: logging.basicConfig(level=logging.DEBUG) - # Here comes the Django settings deprecation section. Being at the very end # of settings.py allows it to catch the settings defined in local_settings.py # or inside one of local_settings.d/ snippets. diff --git a/openstack_dashboard/themes/material/static/_variables.scss b/openstack_dashboard/themes/material/static/_variables.scss index cca9d0bc63..dc6fe2ac7c 100644 --- a/openstack_dashboard/themes/material/static/_variables.scss +++ b/openstack_dashboard/themes/material/static/_variables.scss @@ -1,2 +1,3 @@ +@import "themes/default/variables"; @import "bootstrap/variables"; @import "horizon/variables"; diff --git a/openstack_dashboard/themes/material/static/bootstrap/_variables.scss b/openstack_dashboard/themes/material/static/bootstrap/_variables.scss index f4619f1584..11facd1f0d 100644 --- a/openstack_dashboard/themes/material/static/bootstrap/_variables.scss +++ b/openstack_dashboard/themes/material/static/bootstrap/_variables.scss @@ -1,3 +1,5 @@ +@import "/dashboard/scss/_variables.scss"; + // Override the web font path ... we want to set this ourselves $web-font-path: $static_url + "horizon/lib/roboto_fontface/css/roboto/roboto-fontface.css"; $roboto-font-path: $static_url + "horizon/lib/roboto_fontface/fonts"; diff --git a/openstack_dashboard/themes/material/static/horizon/_icons.scss b/openstack_dashboard/themes/material/static/horizon/_icons.scss index 7f331d6a2d..790eafb099 100644 --- a/openstack_dashboard/themes/material/static/horizon/_icons.scss +++ b/openstack_dashboard/themes/material/static/horizon/_icons.scss @@ -23,7 +23,6 @@ $icon-swap: ( calendar: 'calendar', caret-up: 'menu-up', caret-down: 'menu-down', - caret-up: 'menu-up', check: 'check', chevron-down: 'chevron-down', chevron-left: 'chevron-left', diff --git a/openstack_dashboard/themes/material/static/horizon/_styles.scss b/openstack_dashboard/themes/material/static/horizon/_styles.scss index 8b07757271..047af99431 100644 --- a/openstack_dashboard/themes/material/static/horizon/_styles.scss +++ b/openstack_dashboard/themes/material/static/horizon/_styles.scss @@ -1,12 +1,3 @@ -// NOTE(e0ne): it's temporary workaround to until specified function will -// be supported by pyScss. We need to define this function before any MDI -// usage. -@if not function-exists("selector-append") { - @function selector-append($selector, $to-append) { - @return append-selector($selector, $to-append); - } -} - @import "animations"; @import "icons"; @import "components/checkboxes"; diff --git a/openstack_dashboard/themes/material/static/horizon/components/_loader_circular_example.scss b/openstack_dashboard/themes/material/static/horizon/components/_loader_circular_example.scss index 30f0e5840a..79d16d3622 100644 --- a/openstack_dashboard/themes/material/static/horizon/components/_loader_circular_example.scss +++ b/openstack_dashboard/themes/material/static/horizon/components/_loader_circular_example.scss @@ -13,7 +13,7 @@ stroke-dashoffset: 0; stroke-linecap: round; stroke: #db652d; - @include animation(material-loader-dash 1.5s ease-in-out infinite, material-loader-color 6s ease-in-out infinite); + @include animation("material-loader-dash 1.5s ease-in-out infinite, material-loader-color 6s ease-in-out infinite"); } } diff --git a/requirements.txt b/requirements.txt index e7178e2028..547849d68b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,6 @@ Babel>=2.6.0 # BSD Django>=4.2,<4.3 # BSD django-compressor>=4.4 # MIT django-debreach>=1.4.2 # BSD License (2 clause) -django-pyscss>=2.0.3 # BSD License (2 clause) futurist>=1.2.0 # Apache-2.0 iso8601>=0.1.11 # MIT keystoneauth1>=4.3.1 # Apache-2.0 @@ -21,7 +20,7 @@ oslo.serialization>=4.3.0 # Apache-2.0 oslo.upgradecheck>=1.5.0 # Apache-2.0 oslo.utils>=4.12.0 # Apache-2.0 osprofiler>=3.4.2 # Apache-2.0 -pyScss>=1.4.0 # MIT License +libsass>=0.23.0 # MIT python-cinderclient>=8.0.0 # Apache-2.0 python-glanceclient>=2.8.0 # Apache-2.0 python-keystoneclient>=3.22.0 # Apache-2.0