django-minify-compress-staticfiles 1.1
Django package for minifying and compressing static files
Requires Python: >=3.10
Keywords: django staticfiles minification compression optimization

Django Minify Compress StaticFiles
==================================

.. image:: https://github.com/openwisp/django-minify-compress-staticfiles/workflows/CI/badge.svg
    :target: https://github.com/openwisp/django-minify-compress-staticfiles/actions
    :alt: CI build status

.. image:: https://coveralls.io/repos/github/openwisp/django-minify-compress-staticfiles/badge.svg?branch=master
    :target: https://coveralls.io/github/openwisp/django-minify-compress-staticfiles?branch=master
    :alt: Coverage

.. image:: https://badge.fury.io/py/django-minify-compress-staticfiles.svg
    :target: https://badge.fury.io/py/django-minify-compress-staticfiles
    :alt: PyPI Version

.. image:: https://img.shields.io/badge/License-BSD%203--Clause-blue.svg
    :target: https://opensource.org/licenses/BSD-3-Clause
    :alt: License

A modern Django package for minifying and compressing static files during
``collectstatic`` with minimal configuration.

Features
--------

- **CSS/JS Minification**: Uses ``rjsmin`` and ``rcssmin`` for fast
  minification
- **Dual Compression**: Gzip and Brotli compression support
- **Django Integration**: Seamless integration with Django's static file
  system
- **Selective Processing**: Only processes appropriate file types
- **Minified Filename Format**: Preserves Django's hash and adds ``.min``
  before the extension: ``name.{hash}.min.ext``. This allows precompressed
  files to be properly served as ``name.{hash}.min.ext.gz`` and
  ``name.{hash}.min.ext.br``.
- **Configurable**: Fine-grained control over processing options

Installation
------------

Install from PyPI:

.. code-block:: bash

    pip install django-minify-compress-staticfiles

Configuration
-------------

For **Django 4.2+**, update your ``STORAGES`` setting:

.. code-block:: python

    STORAGES = {
        "default": {
            "BACKEND": "django.core.files.storage.FileSystemStorage",
        },
        "staticfiles": {
            "BACKEND": "django_minify_compress_staticfiles.storage.MinicompressStorage",
        },
    }

For **Django < 4.2**, use the legacy setting:

.. code-block:: python

    STATICFILES_STORAGE = "django_minify_compress_staticfiles.storage.MinicompressStorage"

Settings
--------

All settings use the ``MINICOMPRESS_`` prefix:

``MINICOMPRESS_ENABLED``
    Enable/disable processing (default: ``True``)

``MINICOMPRESS_MINIFY_FILES``
    Enable CSS/JS minification (default: ``True``)

``MINICOMPRESS_GZIP_COMPRESSION``
    Enable Gzip compression (default: ``True``)

``MINICOMPRESS_BROTLI_COMPRESSION``
    Enable Brotli compression (default: ``True``)

``MINICOMPRESS_MIN_FILE_SIZE``
    Minimum file size for compression in bytes (default: ``200``)

``MINICOMPRESS_MAX_FILE_SIZE``
    Maximum file size for processing in bytes (default: ``10485760``,
    i.e., 10MB) Files larger than this are skipped to prevent memory
    exhaustion. Adjust based on your available memory and security
    requirements.

``MINICOMPRESS_MAX_FILES_PER_RUN``
    Maximum number of files to process per ``collectstatic`` run (default:
    ``1000``) Prevents CPU and memory exhaustion when processing large
    numbers of files. Increase only if you have verified your system can
    handle it.

``MINICOMPRESS_COMPRESSION_LEVEL_GZIP``
    Gzip compression level (default: ``6``, range: 0-9) Level 6 provides a
    good balance between compression ratio and CPU usage. Higher values
    (8-9) consume significantly more CPU with diminishing returns. Lower
    values (0-5) are faster but produce larger compressed files.

``MINICOMPRESS_COMPRESSION_LEVEL_BROTLI``
    Brotli compression quality (default: ``4``, range: 0-11) Level 4
    offers excellent compression with reasonable CPU usage. Higher values
    (8-11) can cause severe CPU spikes during ``collectstatic``. Lower
    values (0-3) are faster but less effective compression.

``MINICOMPRESS_PRESERVE_COMMENTS``
    Preserve bang comments in CSS/JS (default: ``True``)

``MINICOMPRESS_SUPPORTED_EXTENSIONS``
    Dictionary of file extensions to process (default: css, js, txt, xml,
    json, svg, md, rst, html, htm)

``MINICOMPRESS_EXCLUDE_PATTERNS``
    List of glob patterns to exclude from processing (default:
    ``["*.min.*", "*-min.*", "*.gz", "*.br", "*.zip"]``) Pre-compressed
    files (e.g., ``.gz``, ``.br``, ``.zip``) are excluded by default to
    prevent double-compression and security issues.

Usage
-----

Run ``collectstatic`` as usual:

.. code-block:: bash

    python manage.py collectstatic --noinput

The package will automatically:

- Minify CSS and JavaScript files
- Create ``.gz`` and ``.br`` compressed versions
- Update Django's manifest with minified file paths
- Skip already processed files and patterns

Supported File Types
--------------------

**Minification**: CSS, JavaScript

**Compression**: CSS, JS, TXT, XML, JSON, SVG, MD, RST, HTML, HTM

Files matching ``*.min.*`` or ``*-min.*`` patterns are excluded from
processing.

Security and Performance Considerations
---------------------------------------

The package implements the following safeguards to mitigate common attack
vectors and ensure resource stability.

Path Traversal Protection
~~~~~~~~~~~~~~~~~~~~~~~~~

To prevent directory traversal attacks (e.g., ``../etc/passwd``), all file
paths undergo strict validation. The system enforces a boundary check
ensuring no read or write operations occur outside the defined
``STATIC_ROOT``. Any attempt to access parent directories via relative
paths is intercepted and blocked.

Memory Exhaustion Prevention
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

To prevent memory exhaustion, the ``MAX_FILE_SIZE`` setting enforces a
hard cap on file processing. This prevents the application from attempting
to buffer or process excessively large files that could lead to
Out-Of-Memory (OOM) errors.

CPU Exhaustion & Resource Throttling
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Compression is a CPU-intensive task. To balance throughput with system
stability, the default compression levels are tuned for efficiency:

- **Gzip:** Level 6
- **Brotli:** Level 4

These defaults prevent "CPU pinning" where a single request monopolizes
processor cycles.

Compression Bomb Protection
~~~~~~~~~~~~~~~~~~~~~~~~~~~

The processor automatically excludes files that are already compressed
(e.g., ``.gz``, ``.br``, ``.zip``, ``.png``). This prevents recursive
compression cycles and "Zip Bomb" style attacks that could lead to
exponential CPU and disk space consumption.

Integrity & Cache Validation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

File fingerprinting uses **MD5** hashing to match Django's
``ManifestFilesMixin`` algorithm. This ensures consistency between
Django's hashed filenames and our minified filenames, allowing the
manifest to correctly map original files to their minified versions.

Recommended Settings for Production
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

For production deployments with high security requirements:

.. code-block:: python

    MINICOMPRESS_MAX_FILE_SIZE = 2097152  # 2MB
    MINICOMPRESS_MAX_FILES_PER_RUN = 500
    MINICOMPRESS_COMPRESSION_LEVEL_GZIP = 6
    MINICOMPRESS_COMPRESSION_LEVEL_BROTLI = 4

For development environments with faster builds:

.. code-block:: python

    MINICOMPRESS_COMPRESSION_LEVEL_GZIP = 1
    MINICOMPRESS_COMPRESSION_LEVEL_BROTLI = 0
    MINICOMPRESS_BROTLI_COMPRESSION = False  # Disable for faster builds

Dependencies
------------

**Required**:

- Django >= 4.2
- Python >= 3.10
- ``brotli`` >= 1.0.0
- ``rjsmin`` >= 1.2.0
- ``rcssmin`` >= 1.1.0

License
-------

BSD 3-Clause License. See ``LICENSE`` file for details.

Contributing
------------

Contributions are welcome! Please see the `OpenWISP contributing
guidelines`_ for more information.

.. _openwisp contributing guidelines: https://openwisp.io/docs/stable/developer/contributing.html

Support
-------

- `Issue Tracker`_
- `OpenWISP Support`_

.. _issue tracker: https://github.com/openwisp/django-minify-compress-staticfiles/issues

.. _openwisp support: https://openwisp.org/support/