Skip to main content

django-suppressor

HTML asset capture and runtime bundling for Django.

Suppresses individual <link> and <script> tags in your templates and replaces them with bundled equivalents at runtime.

Installation

pip install django-suppressor

Rationale

Django templates scatter <link> and <script> tags across base templates, child templates, and includes. Each tag means a separate HTTP request. Existing bundling tools either require you to rewrite all asset references as custom tags or operate as offline build steps disconnected from the template system.

Suppressor takes a different approach: wrap a template region, let Django render it normally, then parse the resulting HTML fragment to find asset tags. Captured tags are removed from the fragment and their contents are concatenated into a single content-addressed bundle served at runtime. Child templates keep writing plain HTML — no special asset registration required.

This is deliberately scoped to explicit template regions rather than full-page rewriting: safer, faster, predictable ordering, and easy to debug.

Quick Start

  1. Add suppressor to your installed apps and middleware:
INSTALLED_APPS = [
    "django.contrib.staticfiles",
    "suppressor",
    ...
]

MIDDLEWARE = [
    ...
    "suppressor.middleware.SuppressorMiddleware",  # near the end
]
  1. Include the suppressor URLs in your root URL config:
from django.urls import include, path

urlpatterns = [
    path("", include("suppressor.urls")),
    ...
]
  1. Wrap asset regions in your base template:
{% load suppressor_capture %}

<head>
    {% suppressor_emit_css "head" %}

    {% suppressor_capture "head" only_css %}
        <link rel="stylesheet" href="{% static 'css/base.css' %}">
        <link rel="stylesheet" href="{% static 'css/theme.css' %}">
        {% block extra_css %}{% endblock %}
    {% endsuppressor_capture %}
</head>

<body>
    ...

    {% suppressor_capture "body_js" only_js %}
        <script src="{% static 'js/vendor.js' %}"></script>
        <script src="{% static 'js/app.js' %}"></script>
        {% block extra_js %}{% endblock %}
    {% endsuppressor_capture %}

    {% suppressor_emit_js "body_js" %}
</body>

Child templates keep using plain <link> and <script> tags inside the inherited blocks. Suppressor captures them, bundles by type, and emits a single content-addressed URL per region.

  1. Run collectstatic before use — suppressor reads from staticfiles storage, not finders:
python manage.py collectstatic

Configuration

Bundling is controlled by SUPPRESSOR_ENABLED, which defaults to not DEBUG. Override it to test bundling locally:

SUPPRESSOR_ENABLED = True

Bundle content is stored using Django's cache framework under the suppressor alias. A FileBasedCache default is auto-configured if you don't define one. To use a different backend:

CACHES = {
    ...
    "suppressor": {
        "BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
        "LOCATION": "127.0.0.1:11211",
    },
}

Limitations

  • Only processes explicitly wrapped template regions (no full-page rewriting)
  • Bundles plain external <script src> / <link rel="stylesheet"> and plain inline <script> / <style>
  • Any tag carrying extra attributes (async, defer, type="module", nomodule, integrity, media/title/disabled, nonce, crossorigin, referrerpolicy, id, data-*, …) or a cross-origin / non-http(s) URL is preserved unchanged
  • CSS relative url(...) references are rewritten to absolute paths; bare @import "file.css" (without url()) is not rewritten
  • StreamingHttpResponse is not processed
  • See also "TODO" section below.

Tested against

CI runs the test suite across Python 3.10–3.14 and Django 4.2, 5.0, 5.1, 5.2 and 6.0 (only supported combinations of each).

TODO

  • improve template ergonomics
  • preserved (ignored) tags can reorder CSS cascade / JS execution vs the bundle

Status

Under development. See SPEC-0-1-0.md for the original design specification and rationale — it is the historical founding spec the project evolves from, not a description of current behavior.

Metadata

Release files for django-suppressor 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-suppressor 0.2.0
File Size Uploaded Provenance Actions
django_suppressor-0.2.0.tar.gz 36.1 kB With provenance Details for django_suppressor-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for django-suppressor 0.2.0
File Interpreter ABI Platform Actions
django_suppressor-0.2.0-py3-none-any.whl Python 3 none any Details for django_suppressor-0.2.0-py3-none-any.whl

Total release size: 51.5 kB

Release files / django_suppressor-0.2.0.tar.gz

Download URL django_suppressor-0.2.0.tar.gz
Size 36.1 kB
Tags Source
SHA-256 checksum
How to use checksums
6e6c6ee2730ecc412512f580d973b0151523e95ffa58be5072070bfdb76800b3
BLAKE2b-256 checksum
How to use checksums
8cb8a994599803fac1024074069185ee28b9c93718d29b9efd4172dbf5d59efb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on May 26, 2026.

Transparency log

Release files / django_suppressor-0.2.0-py3-none-any.whl

Download URL django_suppressor-0.2.0-py3-none-any.whl
Size 15.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e9ffb948aedd084860c4e755c79db5f7e188c439ed5d9802e2938bf8899257b5
BLAKE2b-256 checksum
How to use checksums
c38e7258459078822fc9895e3b2f04c62dfe31626885945d9182f8d9ef3782f8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on May 26, 2026.

Transparency log

Security for django-suppressor 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for django_suppressor-0.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on May 26, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page