├── .github
├── ISSUE_TEMPLATE
│ └── bug_report.md
└── workflows
│ └── test.yml
├── .gitignore
├── CHANGELOG.txt
├── LICENSE
├── MANIFEST.in
├── README.rst
├── django_libsass.py
├── runtests.py
├── setup.cfg
├── setup.py
├── tests
├── __init__.py
├── extra_static
│ └── extra.scss
├── settings.py
├── static
│ └── css
│ │ ├── imported.scss
│ │ ├── index.scss
│ │ ├── raw1.css
│ │ ├── raw2.css
│ │ ├── with_extra_include.scss
│ │ ├── with_import.scss
│ │ ├── with_raw_css_import.scss
│ │ └── with_static.scss
├── templates
│ └── index.html
├── tests
│ ├── __init__.py
│ └── test_sass.py
├── urls.py
└── views.py
└── tox.ini
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | Please see https://github.com/torchbox/django-libsass#reporting-bugs before reporting. Any bug reports relating to a third-party CSS framework are liable to be closed without further investigation.
11 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Unit tests
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | pull_request:
7 | branches: [ main ]
8 |
9 | jobs:
10 | build:
11 | name: Python ${{ matrix.python-version }}, django ${{ matrix.django-version }}
12 | runs-on: ubuntu-latest
13 | strategy:
14 | matrix: # https://docs.djangoproject.com/en/5.0/faq/install/#faq-python-version-support
15 | django-version: ["42", "50", "51"]
16 | python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
17 |
18 | exclude:
19 | - django-version: "42"
20 | python-version: "3.13"
21 | - django-version: "50"
22 | python-version: "3.9"
23 | - django-version: "50"
24 | python-version: "3.13"
25 | - django-version: "51"
26 | python-version: "3.9"
27 |
28 | steps:
29 | - uses: actions/checkout@v4
30 | - name: setup python
31 | uses: actions/setup-python@v5
32 | with:
33 | python-version: ${{ matrix.python-version }}
34 | - name: Install tox
35 | run: pip install tox
36 | - name: Run Tests
37 | env:
38 | TOXENV: django${{ matrix.django-version }}
39 | run: tox
40 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | build/
3 | dist/
4 | django_libsass.egg-info
5 | MANIFEST
6 |
--------------------------------------------------------------------------------
/CHANGELOG.txt:
--------------------------------------------------------------------------------
1 | Changelog
2 | =========
3 |
4 | Unreleased
5 | ~~~~~~~~~~~~~~~~
6 |
7 | * Added Django 4.1 & 4.2 support
8 | * Added Python 3.10 & 3.11 support
9 | * Drop support for Python 2.7, 3.4, 3.5, 3.6 & 3.7 (EOL)
10 | * Drop support for Django 1.8, 1.9, 1.10, 1.11, 2.2, 3.0 & 3.1 (EOL)
11 |
12 | 0.9 (08.07.2021)
13 | ~~~~~~~~~~~~~~~~
14 | * Added Django 3.1 and 3.2 support (Awais Qureshi)
15 | * Added tox and enable Github actions for testing across different Django and Python versions (Awais Qureshi)
16 |
17 | 0.8 (06.01.2020)
18 | ~~~~~~~~~~~~~~~~
19 | * Added Django 3.0 support (Tobias Kunze, Dan Kingston)
20 | * Included LICENSE file in distribution (Dougal J. Sutherland)
21 | * Added LIBSASS_ADDITIONAL_INCLUDE_PATHS setting (Iwan Trofimtschuk)
22 | * Removed unnecessary quoting from output of `static` function
23 |
24 | 0.7 (02.05.2016)
25 | ~~~~~~~~~~~~~~~~
26 | * Added LIBSASS_PRECISION setting (Liang-Bo Wang)
27 |
28 | 0.6 (21.11.2015)
29 | ~~~~~~~~~~~~~~~~
30 | * Fixed call to FilterBase super for django-compressor 1.6 compatibility (Dennis Vermeulen)
31 |
32 | 0.5 (18.11.2015)
33 | ~~~~~~~~~~~~~~~~
34 | * Added sourcemap support (Saulius Žemaitaitis)
35 | * Updated the `static` function to use the more versatile django.contrib.staticfiles implementation (Carl Johnson)
36 |
37 | 0.4 (24.08.2015)
38 | ~~~~~~~~~~~~~~~~
39 | * Added support for custom functions (Alexandre Pocquet)
40 | * Added a `static` function to generate paths to assets such as images and fonts (Alexandre Pocquet)
41 |
42 | 0.3 (27.04.2015)
43 | ~~~~~~~~~~~~~~~~
44 | * Enabled source comments when DEBUG is True; can be overridden with the LIBSASS_SOURCE_COMMENTS setting
45 | * Added LIBSASS_OUTPUT_STYLE setting
46 |
47 | 0.2 (22.05.2014)
48 | ~~~~~~~~~~~~~~~~
49 | * Made compatible with django-compressor 1.4 and Python 3.
50 |
51 | 0.1 (05.03.2014)
52 | ~~~~~~~~~~~~~~~~
53 | * Initial release.
54 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 Torchbox Ltd and individual contributors.
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification,
5 | are permitted provided that the following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice,
8 | this list of conditions and the following disclaimer.
9 |
10 | 2. Redistributions in binary form must reproduce the above copyright
11 | notice, this list of conditions and the following disclaimer in the
12 | documentation and/or other materials provided with the distribution.
13 |
14 | 3. Neither the name of Torchbox nor the names of its contributors may be used
15 | to endorse or promote products derived from this software without
16 | specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include LICENSE
2 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | django-libsass
2 | ==============
3 |
4 | A django-compressor filter to compile Sass files using libsass.
5 |
6 | Installation
7 | ~~~~~~~~~~~~
8 |
9 | Starting from a Django project with `django-compressor `_ set up::
10 |
11 | pip install django-libsass
12 |
13 | and add django_libsass.SassCompiler to your COMPRESS_PRECOMPILERS setting::
14 |
15 | COMPRESS_PRECOMPILERS = (
16 | ('text/x-scss', 'django_libsass.SassCompiler'),
17 | )
18 |
19 | You can now use the content type text/x-scss on your stylesheets, and have them
20 | compiled seamlessly into CSS::
21 |
22 | {% load compress %}
23 |
24 | {% compress css %}
25 |
26 | {% endcompress %}
27 |
28 |
29 | Imports
30 | ~~~~~~~
31 |
32 | Relative paths in @import lines are followed as you would expect::
33 |
34 | @import "../variables.scss";
35 |
36 | Additionally, Django's STATICFILES_FINDERS setting is consulted, and all possible locations
37 | for static files *on the local filesystem* are included on the search path. This makes it
38 | possible to import files across different apps::
39 |
40 | @import "myotherapp/css/widget.scss"
41 |
42 |
43 | Settings
44 | ~~~~~~~~
45 |
46 | The following settings can be used to control django-libsass's behaviour:
47 |
48 | * ``LIBSASS_SOURCE_COMMENTS`` - whether to enable SASS source comments (adds comments about source lines). Defaults to ``True`` when Django's ``DEBUG`` is ``True``, ``False`` otherwise.
49 | * ``LIBSASS_OUTPUT_STYLE`` - SASS output style. Options are ``'nested'``, ``'expanded'``, ``'compact'`` and ``'compressed'``, although as of libsass 3.0.2 only ``'nested'`` and ``'compressed'`` are implemented. Default is 'nested'. See `SASS documentation for output styles `_. Note that `django-compressor's settings `_ may also affect the formatting of the resulting CSS.
50 | * ``LIBSASS_CUSTOM_FUNCTIONS`` - A mapping of custom functions to be made available within the SASS compiler. By default, a ``static`` function is provided, analogous to Django's ``static`` template tag.
51 | * ``LIBSASS_SOURCEMAPS`` - Enable embedding sourcemaps into file output (default: False)
52 | * ``LIBSASS_PRECISION`` - Number of digits of numerical precision (default: 5)
53 | * ``LIBSASS_ADDITIONAL_INCLUDE_PATHS`` - a list of base paths to be recognised in @import lines, in addition to Django's recognised static file locations
54 |
55 |
56 | Custom functions
57 | ~~~~~~~~~~~~~~~~
58 |
59 | The SASS compiler can be extended with custom Python functions defined in the ``LIBSASS_CUSTOM_FUNCTIONS`` setting. By default, a ``static`` function is provided, for generating static paths to resources such as images and fonts::
60 |
61 | .foo {
62 | background: url(static("myapp/image/bar.png"));
63 | }
64 |
65 | If your ``STATIC_URL`` is '/static/', this will be rendered as::
66 |
67 | .foo {
68 | background: url("/static/myapp/image/bar.png"));
69 | }
70 |
71 | Why django-libsass?
72 | ~~~~~~~~~~~~~~~~~~~
73 |
74 | We wanted to use Sass in a Django project without introducing any external (non pip-installable)
75 | dependencies. (Actually, we wanted to use Less, but the same arguments apply...) There are a few
76 | pure Python implementations of Sass and Less, but we found that they invariably didn't match the
77 | behaviour of the reference compilers, either in their handling of @imports or lesser-used CSS
78 | features such as media queries.
79 |
80 | `libsass `_ is a mature C/C++ port of the Sass engine, co-developed by the
81 | original creator of Sass, and we can reasonably rely on it to stay in sync with the reference
82 | Sass compiler - and, being C/C++, it's fast. Thanks to Hong Minhee's
83 | `libsass-python `_ project, it has Python bindings and
84 | installs straight from pip.
85 |
86 | django-libsass builds on libsass-python to make @import paths aware of Django's staticfiles
87 | mechanism, and provides a filter module for django-compressor which uses the libsass-python API
88 | directly, avoiding the overheads of calling an external executable to do the compilation.
89 |
90 |
91 | Reporting bugs
92 | ~~~~~~~~~~~~~~
93 |
94 | Please see the `troubleshooting `_ page for help with some common setup issues.
95 |
96 | I do not provide support for getting django-libsass working with your CSS framework of choice. If you believe you've found a bug, please try to isolate it as a minimal reproducible test case before reporting it - ideally this will consist of a few edits / additions to the `hello-django-libsass `_ example project. If you cannot demonstrate the problem in a few standalone SCSS files, it is almost certainly not a django-libsass bug - any bug reports that relate to a third-party CSS framework are likely to be closed without further investigation.
97 |
98 |
99 | Author
100 | ~~~~~~
101 |
102 | Matt Westcott matthew.westcott@torchbox.com
103 |
--------------------------------------------------------------------------------
/django_libsass.py:
--------------------------------------------------------------------------------
1 | import base64
2 | import json
3 | import os
4 | import re
5 |
6 | import sass
7 | from compressor.filters.base import FilterBase
8 | from django.conf import settings
9 | from django.contrib.staticfiles.finders import get_finders
10 | from django.templatetags.static import static as django_static
11 |
12 |
13 | def static(path):
14 | """
15 | Use the Django builtin static file resolver to return an absolute path
16 | usable as CSS url() argument. Sass equivalent of the 'static' template
17 | tag.
18 | """
19 | return django_static(path)
20 |
21 |
22 | OUTPUT_STYLE = getattr(settings, 'LIBSASS_OUTPUT_STYLE', 'nested')
23 | SOURCE_COMMENTS = getattr(settings, 'LIBSASS_SOURCE_COMMENTS', settings.DEBUG)
24 | CUSTOM_FUNCTIONS = getattr(settings, 'LIBSASS_CUSTOM_FUNCTIONS', {'static': static})
25 | SOURCEMAPS = getattr(settings, 'LIBSASS_SOURCEMAPS', False)
26 | PRECISION = getattr(settings, 'LIBSASS_PRECISION', None) # None use libsass default
27 | ADDITIONAL_INCLUDE_PATHS = getattr(settings, 'LIBSASS_ADDITIONAL_INCLUDE_PATHS', None)
28 |
29 |
30 | INCLUDE_PATHS = None # populate this on first call to 'get_include_paths'
31 |
32 |
33 | def get_include_paths():
34 | """
35 | Generate a list of include paths that libsass should use to find files
36 | mentioned in @import lines.
37 | """
38 | global INCLUDE_PATHS
39 | if INCLUDE_PATHS is not None:
40 | return INCLUDE_PATHS
41 |
42 | include_paths = []
43 |
44 | # Look for staticfile finders that define 'storages'
45 | for finder in get_finders():
46 | try:
47 | storages = finder.storages
48 | except AttributeError:
49 | continue
50 |
51 | for storage in storages.values():
52 | try:
53 | include_paths.append(storage.path('.'))
54 | except NotImplementedError:
55 | # storages that do not implement 'path' do not store files locally,
56 | # and thus cannot provide an include path
57 | pass
58 |
59 | global ADDITIONAL_INCLUDE_PATHS
60 | if ADDITIONAL_INCLUDE_PATHS:
61 | include_paths.extend(ADDITIONAL_INCLUDE_PATHS)
62 |
63 | INCLUDE_PATHS = include_paths
64 | return include_paths
65 |
66 |
67 | def prefix_sourcemap(sourcemap, base_path):
68 | decoded_sourcemap = json.loads(sourcemap)
69 | source_urls = []
70 | include_paths = get_include_paths()
71 |
72 | for source_filename in decoded_sourcemap['sources']:
73 | # expand source_filename into an absolute file path
74 | full_source_path = os.path.normpath(os.path.join(base_path, source_filename))
75 |
76 | # look for a path in include_paths that is a prefix of full_source_path
77 | for path in include_paths:
78 | if full_source_path.startswith(path):
79 | # A matching path has been found; take the remainder as a relative path.
80 | # include_paths entries do not include a trailing slash;
81 | # [len(path) + 1:] ensures that we trim the path plus trailing slash
82 | remainder = full_source_path[len(path) + 1:]
83 |
84 | # Invoke the 'static' template tag to turn the relative path into a URL
85 | source_urls.append(django_static(remainder))
86 | break
87 | else:
88 | # no matching path was found in include_paths; return the original source filename
89 | # as a fallback
90 | source_urls.append(source_filename)
91 |
92 | decoded_sourcemap['sources'] = source_urls
93 | return json.dumps(decoded_sourcemap)
94 |
95 |
96 | def embed_sourcemap(output, sourcemap):
97 | encoded_sourcemap = base64.standard_b64encode(
98 | sourcemap.encode('utf-8')
99 | )
100 | sourcemap_fragment = 'sourceMappingURL=data:application/json;base64,{} '\
101 | .format(encoded_sourcemap.decode('utf-8'))
102 | url_re = re.compile(r'sourceMappingURL=[^\s]+', re.M)
103 | output = url_re.sub(sourcemap_fragment, output)
104 |
105 | return output
106 |
107 |
108 | def compile(**kwargs):
109 | """Perform sass.compile, but with the appropriate include_paths for Django added"""
110 | kwargs = kwargs.copy()
111 | if PRECISION is not None:
112 | kwargs['precision'] = PRECISION
113 | kwargs['include_paths'] = (kwargs.get('include_paths') or []) + get_include_paths()
114 |
115 | custom_functions = CUSTOM_FUNCTIONS.copy()
116 | custom_functions.update(kwargs.get('custom_functions', {}))
117 | kwargs['custom_functions'] = custom_functions
118 |
119 | if SOURCEMAPS and kwargs.get('filename', None):
120 | # We need to pass source_map_file to libsass so it generates
121 | # correct paths to source files.
122 | base_path = os.path.dirname(kwargs['filename'])
123 | sourcemap_filename = os.path.join(base_path, 'sourcemap.map')
124 | kwargs['source_map_filename'] = sourcemap_filename
125 |
126 | libsass_output, sourcemap = sass.compile(**kwargs)
127 | sourcemap = prefix_sourcemap(sourcemap, base_path)
128 | output = embed_sourcemap(libsass_output, sourcemap)
129 | else:
130 | output = sass.compile(**kwargs)
131 | return output
132 |
133 |
134 | class SassCompiler(FilterBase):
135 | def __init__(self, content, attrs=None, filter_type=None, charset=None, filename=None):
136 | # FilterBase doesn't handle being passed attrs, so fiddle the signature
137 | super(SassCompiler, self).__init__(content=content,
138 | filter_type=filter_type,
139 | filename=filename)
140 |
141 | def input(self, **kwargs):
142 | if self.filename:
143 | return compile(filename=self.filename,
144 | output_style=OUTPUT_STYLE,
145 | source_comments=SOURCE_COMMENTS)
146 | else:
147 | return compile(string=self.content,
148 | output_style=OUTPUT_STYLE)
149 |
--------------------------------------------------------------------------------
/runtests.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import argparse
4 | import os
5 | import shutil
6 | import sys
7 | import warnings
8 |
9 | from django.core.management import execute_from_command_line
10 |
11 | os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.settings'
12 |
13 |
14 | def make_parser():
15 | parser = argparse.ArgumentParser()
16 | parser.add_argument('--deprecation', choices=['pending', 'imminent', 'none'], default='imminent')
17 | return parser
18 |
19 |
20 | def parse_args(args=None):
21 | return make_parser().parse_known_args(args)
22 |
23 |
24 | def runtests():
25 | args, rest = parse_args()
26 |
27 | if args.deprecation == 'pending':
28 | # Show all deprecation warnings
29 | warnings.simplefilter('default', DeprecationWarning)
30 | warnings.simplefilter('default', PendingDeprecationWarning)
31 | elif args.deprecation == 'imminent':
32 | # Show only imminent deprecation warnings
33 | warnings.simplefilter('default', DeprecationWarning)
34 | elif args.deprecation == 'none':
35 | # Deprecation warnings are ignored by default
36 | pass
37 |
38 | argv = [sys.argv[0], 'test'] + rest
39 |
40 | try:
41 | execute_from_command_line(argv)
42 | finally:
43 | from tests.settings import STATIC_ROOT
44 | shutil.rmtree(STATIC_ROOT, ignore_errors=True)
45 |
46 |
47 | if __name__ == '__main__':
48 | runtests()
49 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | license-file = LICENSE
3 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | try:
4 | from setuptools import setup
5 | except ImportError:
6 | from distutils.core import setup
7 |
8 | setup(
9 | name='django-libsass',
10 | version='0.9',
11 | description="A django-compressor filter to compile SASS files using libsass",
12 | author='Matt Westcott',
13 | author_email='matthew.westcott@torchbox.com',
14 | url='https://github.com/torchbox/django-libsass',
15 | py_modules=['django_libsass'],
16 | license='BSD',
17 | long_description=open('README.rst').read(),
18 | classifiers=[
19 | 'Development Status :: 5 - Production/Stable',
20 | 'Environment :: Web Environment',
21 | 'Intended Audience :: Developers',
22 | 'License :: OSI Approved :: BSD License',
23 | 'Operating System :: OS Independent',
24 | 'Programming Language :: Python',
25 | 'Programming Language :: Python :: 3',
26 | 'Programming Language :: Python :: 3.9',
27 | 'Programming Language :: Python :: 3.10',
28 | 'Programming Language :: Python :: 3.11',
29 | 'Programming Language :: Python :: 3.12',
30 | 'Programming Language :: Python :: 3.13',
31 | 'Framework :: Django',
32 | 'Framework :: Django :: 4.2',
33 | 'Framework :: Django :: 5.0',
34 | 'Framework :: Django :: 5.1',
35 | ],
36 | python_requires='>=3.9',
37 | install_requires=[
38 | "django-compressor>=1.3",
39 | "libsass>=0.7.0,<1",
40 | ],
41 | )
42 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torchbox/django-libsass/8fb6d21cf975645f84d893a5c7ace7311a97a9bc/tests/__init__.py
--------------------------------------------------------------------------------
/tests/extra_static/extra.scss:
--------------------------------------------------------------------------------
1 | .extra-style {
2 | color: blue;
3 | }
4 |
--------------------------------------------------------------------------------
/tests/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for django_libsass tests.
3 |
4 | Generated by 'django-admin startproject' using Django 2.0.13.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/2.0/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/2.0/ref/settings/
11 | """
12 |
13 | import os
14 |
15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
17 |
18 |
19 | # Quick-start development settings - unsuitable for production
20 | # See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/
21 |
22 | # SECURITY WARNING: keep the secret key used in production secret!
23 | SECRET_KEY = 'snbicjfchnkjnyhcojinbcojnghjn=*uv2e$gr5-7w2^j2y9y!'
24 |
25 | # SECURITY WARNING: don't run with debug turned on in production!
26 | DEBUG = True
27 |
28 | ALLOWED_HOSTS = []
29 |
30 |
31 | # Application definition
32 |
33 | INSTALLED_APPS = [
34 | 'tests',
35 |
36 | 'compressor',
37 |
38 | 'django.contrib.admin',
39 | 'django.contrib.auth',
40 | 'django.contrib.contenttypes',
41 | 'django.contrib.sessions',
42 | 'django.contrib.messages',
43 | 'django.contrib.staticfiles',
44 | ]
45 |
46 | MIDDLEWARE = [
47 | 'django.middleware.security.SecurityMiddleware',
48 | 'django.contrib.sessions.middleware.SessionMiddleware',
49 | 'django.middleware.common.CommonMiddleware',
50 | 'django.middleware.csrf.CsrfViewMiddleware',
51 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
52 | 'django.contrib.messages.middleware.MessageMiddleware',
53 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
54 | ]
55 |
56 | ROOT_URLCONF = 'tests.urls'
57 |
58 | TEMPLATES = [
59 | {
60 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
61 | 'DIRS': [],
62 | 'APP_DIRS': True,
63 | 'OPTIONS': {
64 | 'context_processors': [
65 | 'django.template.context_processors.debug',
66 | 'django.template.context_processors.request',
67 | 'django.contrib.auth.context_processors.auth',
68 | 'django.contrib.messages.context_processors.messages',
69 | ],
70 | },
71 | },
72 | ]
73 |
74 | # Database
75 | # https://docs.djangoproject.com/en/2.0/ref/settings/#databases
76 |
77 | DATABASES = {
78 | 'default': {
79 | 'ENGINE': 'django.db.backends.sqlite3',
80 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
81 | }
82 | }
83 |
84 |
85 | # Password validation
86 | # https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators
87 |
88 | AUTH_PASSWORD_VALIDATORS = [
89 | {
90 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
91 | },
92 | {
93 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
94 | },
95 | {
96 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
97 | },
98 | {
99 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
100 | },
101 | ]
102 |
103 |
104 | # Internationalization
105 | # https://docs.djangoproject.com/en/2.0/topics/i18n/
106 |
107 | LANGUAGE_CODE = 'en-us'
108 |
109 | TIME_ZONE = 'UTC'
110 |
111 | USE_I18N = True
112 |
113 | USE_L10N = True
114 |
115 | USE_TZ = True
116 |
117 |
118 | # Static files (CSS, JavaScript, Images)
119 | # https://docs.djangoproject.com/en/2.0/howto/static-files/
120 |
121 | STATIC_URL = '/static/'
122 | STATIC_ROOT = os.path.join(BASE_DIR, 'test-static')
123 |
124 | # List of finder classes that know how to find static files in
125 | # various locations.
126 | STATICFILES_FINDERS = (
127 | 'django.contrib.staticfiles.finders.FileSystemFinder',
128 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
129 | # 'django.contrib.staticfiles.finders.DefaultStorageFinder',
130 | 'compressor.finders.CompressorFinder',
131 | )
132 |
133 | COMPRESS_PRECOMPILERS = (
134 | ('text/x-scss', 'django_libsass.SassCompiler'),
135 | )
136 |
137 | LIBSASS_ADDITIONAL_INCLUDE_PATHS = [
138 | os.path.join(BASE_DIR, 'tests', 'extra_static'),
139 | ]
140 | LIBSASS_SOURCEMAPS = True
141 |
--------------------------------------------------------------------------------
/tests/static/css/imported.scss:
--------------------------------------------------------------------------------
1 | .imported-style {
2 | color: red;
3 | }
4 |
--------------------------------------------------------------------------------
/tests/static/css/index.scss:
--------------------------------------------------------------------------------
1 | $background-color: #ddffdd;
2 | $foreground-color: #008800;
3 |
4 | body {
5 | background-color: $background-color;
6 |
7 | h1 {
8 | color: $foreground-color;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/tests/static/css/raw1.css:
--------------------------------------------------------------------------------
1 | .raw-style-1 {
2 | color: yellow;
3 | }
4 |
--------------------------------------------------------------------------------
/tests/static/css/raw2.css:
--------------------------------------------------------------------------------
1 | .raw-style-2 {
2 | color: green;
3 | }
4 |
--------------------------------------------------------------------------------
/tests/static/css/with_extra_include.scss:
--------------------------------------------------------------------------------
1 | @import 'extra';
2 |
--------------------------------------------------------------------------------
/tests/static/css/with_import.scss:
--------------------------------------------------------------------------------
1 | @import 'imported';
2 |
--------------------------------------------------------------------------------
/tests/static/css/with_raw_css_import.scss:
--------------------------------------------------------------------------------
1 | @import 'raw1.css';
2 | @import 'raw2';
3 |
--------------------------------------------------------------------------------
/tests/static/css/with_static.scss:
--------------------------------------------------------------------------------
1 | body {
2 | background-image: url(static('images/my image.jpg'));
3 | }
4 |
--------------------------------------------------------------------------------
/tests/templates/index.html:
--------------------------------------------------------------------------------
1 | {% load compress static %}
2 |
3 |
4 |
5 | Hello django-libsass
6 | {% compress css %}
7 |
8 | {% endcompress %}
9 |
10 |
11 | It worked!
12 |
13 |
14 |
--------------------------------------------------------------------------------
/tests/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torchbox/django-libsass/8fb6d21cf975645f84d893a5c7ace7311a97a9bc/tests/tests/__init__.py
--------------------------------------------------------------------------------
/tests/tests/test_sass.py:
--------------------------------------------------------------------------------
1 | import os.path
2 | from django.conf import settings
3 | from django.test import TestCase
4 |
5 | from django_libsass import compile
6 |
7 | class TestSass(TestCase):
8 | def test_invocation(self):
9 | response = self.client.get('/')
10 | self.assertEqual(response.status_code, 200)
11 |
12 | def test_import(self):
13 | result = compile(filename=os.path.join(settings.BASE_DIR, 'tests', 'static', 'css', 'with_import.scss'))
14 | self.assertIn('.imported-style', result)
15 |
16 | def test_extra_include_path(self):
17 | result = compile(filename=os.path.join(settings.BASE_DIR, 'tests', 'static', 'css', 'with_extra_include.scss'))
18 | self.assertIn('.extra-style', result)
19 |
20 | def test_raw_css_import(self):
21 | result = compile(filename=os.path.join(settings.BASE_DIR, 'tests', 'static', 'css', 'with_raw_css_import.scss'))
22 | self.assertIn('@import url(raw1.css);', result)
23 | self.assertIn('.raw-style-2', result)
24 |
25 | def test_static_function(self):
26 | result = compile(filename=os.path.join(settings.BASE_DIR, 'tests', 'static', 'css', 'with_static.scss'))
27 | self.assertIn(r'background-image: url(/static/images/my%20image.jpg);', result)
28 |
--------------------------------------------------------------------------------
/tests/urls.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, unicode_literals
2 |
3 | """hello_django_libsass URL Configuration
4 |
5 | The `urlpatterns` list routes URLs to views. For more information please see:
6 | https://docs.djangoproject.com/en/2.0/topics/http/urls/
7 | Examples:
8 | Function views
9 | 1. Add an import: from my_app import views
10 | 2. Add a URL to urlpatterns: path('', views.home, name='home')
11 | Class-based views
12 | 1. Add an import: from other_app.views import Home
13 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
14 | Including another URLconf
15 | 1. Import the include() function: from django.urls import include, path
16 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
17 | """
18 |
19 | from django.urls import path
20 |
21 | from tests.views import index
22 |
23 |
24 | urlpatterns = [
25 | path('', index),
26 | ]
27 |
--------------------------------------------------------------------------------
/tests/views.py:
--------------------------------------------------------------------------------
1 | from django.shortcuts import render
2 |
3 |
4 | def index(request):
5 | return render(request, 'index.html')
6 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist = django{42,50,51}
3 |
4 | [testenv]
5 | commands =
6 | python runtests.py
7 | deps =
8 | django42: Django>=4.2,<5.0
9 | django50: Django>=5.0,<5.1
10 | django51: Django>=5.1,<5.2
11 |
--------------------------------------------------------------------------------