Skip to main content

Django include media

An app for Django that allows templates and views to add Script/Stylesheets to a page using Django's forms.Media object, with automatic collection and deduplication, outputting assets into <head>. Inspired by django-sekizai.

Installation

pip install django-include-media

Add to INSTALLED_APPS:

INSTALLED_APPS = [
    ...
    "include_media",
]

Usage

Place {% include_media %} in <head> of your base template. Then use the use_media templatetag or page_media context to add the assets you need.

All your sub-templates or templates from templatetags can now reliably add assets to the page.

{# base.html #}
{% load include_media_tags %}
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    {% include_media %}
</head>
<body>
    {% block content %}{% endblock %}
</body>
</html>

Component and template assets

Declare assets inline with {% use_media %}. Assets are deduplicated by object identity, including the same component twice only renders its assets once:

{% load include_media_tags %}
{% use_media form.media %}
{% use_media css="myapp/widget.css" %}
{% use_media js="myapp/script.js" %}

Extra HTML attributes can be passed as keyword arguments and are forwarded to the rendered tag. Add csp_nonce_attr to opt a specific asset into Django's CSP nonce (Django 6.0+); the nonce is applied if csp_nonce is present in the template context and is a no-op otherwise:

{% use_media js="myapp/widget.js" type="module" %}
{% use_media js="myapp/widget.js" type="module" csp_nonce_attr %}
{% use_media css="myapp/widget.css" media="print" %}
{% use_media form.media csp_nonce_attr %}

View-level assets

Pass page_media via get_context_data. If a site-wide context processor also sets page_media, the two are merged automatically:

from django.forms import Media
from django.forms.widgets import Script
from include_media import Stylesheet
from django.views.generic import TemplateView

class DatePickerView(TemplateView):
    template_name = "datepicker.html"

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        ctx["page_media"] = Media(
            css={"all":[Stylesheet("datepicker/datepicker.css")]},
            js=[Script("datepicker/datepicker.js", type="module")],
        )
        return ctx

Site-wide assets

Declare assets required on every page in a context processor:

# myproject/context_processors.py
from django.forms import Media
from django.forms.widgets import Script
from include_media import Stylesheet

def site_media(request):
    nonce = getattr(request, "csp_nonce", None)
    attrs = {"nonce": nonce} if nonce else {}
    media = Media(
        css={"all":[Stylesheet("base.css", **attrs)]},
        js=[Script("base.js", type="module", **attrs)],
    )
    if request.user.is_authenticated:
        media += Media(js=[Script("dashboard.js", type="module", **attrs)])
    return {"page_media": media}
TEMPLATES = [{
    "OPTIONS": {
        "context_processors": [
            ...
            "myproject.context_processors.site_media",
        ],
    },
}]

App-level registration

Apps can use the register() method to add app level assets that are linked to a registered_media context_processor.

# myapp/apps.py
from django.apps import AppConfig

class MyAppConfig(AppConfig):
    name = "myapp"

    def ready(self):
        from django.forms import Media
        from django.forms.widgets import Script
        from include_media import register, Stylesheet

        register(Media(
            css={"all": [Stylesheet("myapp/base.css")]},
            js=[Script("myapp/base.js", type="module")],
        ))

Add the single bundled context processor to TEMPLATES once, regardless of how many apps use register():

TEMPLATES = [{
    "OPTIONS": {
        "context_processors": [
            ...
            "include_media.context_processors.registered_media",
        ],
    },
}]

Import maps

{% include_media %} can generate a <script type="importmap"> tag, letting templates and reusable components declare their ES module specifiers in the same places they already declare CSS and JS assets. All entries from across the template hierarchy are merged into a single importmap tag, placed before other assets in <head>.

Template tag — for one-off or inline declarations:

{% use_media js="vendor/htmx.js" importmap="htmx" %}
{% use_media js="https://cdn.example.com/lodash.js" importmap="lodash" %}

ImportmapScript — for reusable widgets and forms that need a module specifier wherever they are used:

from django.forms import Form
from include_media import ImportmapScript

class DatePickerForm(Form):
    class Media:
        js = [
            ImportmapScript("vendor/pikaday.js", specifier="pikaday"),
            Script("datepicker/widget.js", type="module"),
        ]

To hook up a JS build system's manifest, you could add ImportmapScript entries to page_media from a context processor:

import json
from pathlib import Path
from django.forms import Media
from include_media import ImportmapScript

_manifest = json.loads((BASE_DIR / "static/dist/manifest.json").read_text())

def importmap(request):
    return {
        "page_media": Media(js=[
            ImportmapScript(f"/static/dist/{entry['file']}", specifier=name)
            for name, entry in _manifest.items()
        ])
    }

Merging and precedence — all sources (template tags, ImportmapScript in page_media) are merged into one <script type="importmap"> tag with first-wins semantics: page_media is processed before template tags, so view-level declarations take precedence when the same specifier appears in multiple places.

Asset post-processing

Set INCLUDE_MEDIA_POSTPROCESSOR to a dotted Python path to intercept the collected asset HTML before it is written to the page. The callable receives the fully-rendered asset tags — after deduplication and nonce application — and must return a string:

# settings.py
INCLUDE_MEDIA_POSTPROCESSOR = "myproject.assets.postprocess"
# myproject/assets.py
def postprocess(assets_html: str, context) -> str:
    # assets_html contains the rendered <link> / <script> / importmap tags.
    # context is the template Context, giving access to request, user, etc.
    return assets_html  # return a modified string

context is the full Django template Context. Access the current request via context.get("request") if you need per-request information (user, headers, etc.).

The setting is validated at startup by Django's system check framework; a misconfigured path or non-callable target raises an error before the first request is served.

Compatibility

  • Python 3.10+
  • Django 5.2, 6.0+ (Stylesheet is backported for Django < 6.1)

Contributing

Contributions are welcome! Please feel free to submit a Pull Request. The aim of this repo is to explore this idea and if it feels right to propose it back to django core, where it could be implemented cleaner. Any feedback is welcome.

License

BSD 3-Clause License

Changelog

0.2.0

  • Add a post processing hook
  • Add an app level register for media assets

0.1.0 — (Initial version)

  • Provide template tags {% include_media %} and {% use_media %} for collecting Django Media assets into <head>
  • Supports CSS <link rel="stylesheet"> and JavaScript <script> tags and setting any attributes.
  • Sets correct path with django.contrib.staticfiles if relevant.
  • CSP nonce support available via csp_nonce_attr flag on {% use_media %}
  • Import map support via importmap= keyword and ImportmapScript
  • Site-wide media via page_media context variable; merges with template-level assets
  • {% use_media %} falls back to rendering inline with debug-mode warning, if the include is not there.

Metadata

Release files for django-include-media 0.2.1

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-include-media 0.2.1
File Size Uploaded Provenance
django_include_media-0.2.1.tar.gz 9.9 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for django-include-media 0.2.1
File Interpreter ABI Platform
django_include_media-0.2.1-py3-none-any.whl Python 3 none any Details

Total release size: 21.4 kB

Release files / django_include_media-0.2.1.tar.gz

Download URL django_include_media-0.2.1.tar.gz
Size 9.9 kB
Tags Source
SHA-256 checksum
How to use checksums
c188256630dd40d444cc6e5356e661ab736a523b85486a68dcc1052d9744e255
BLAKE2b-256 checksum
How to use checksums
7c7a21f3315a670440274569c31aa505371db041622ef8192fe26ab1af90c395
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 Jun 11, 2026.

Transparency log

Release files / django_include_media-0.2.1-py3-none-any.whl

Download URL django_include_media-0.2.1-py3-none-any.whl
Size 11.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
60f8d23c927819a2c0d96a3870df3bc0f63439961d4b7e88cdabf77c7efae7a2
BLAKE2b-256 checksum
How to use checksums
3dbf087f698afa3277ed21eb8702bf052545b5709257404438bf33fac0f03580
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 Jun 11, 2026.

Transparency log

Security for django-include-media 0.2.1

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

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for django_include_media-0.2.1.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 Jun 11, 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.

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