Skip to main content

Include Django Media assets into <head> using template tags

Project description

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 will 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",
        ],
    },
}]

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.

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.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 avialable 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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_include_media-0.1.0.tar.gz (8.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_include_media-0.1.0-py3-none-any.whl (9.5 kB view details)

Uploaded Python 3

File details

Details for the file django_include_media-0.1.0.tar.gz.

File metadata

  • Download URL: django_include_media-0.1.0.tar.gz
  • Upload date:
  • Size: 8.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for django_include_media-0.1.0.tar.gz
Algorithm Hash digest
SHA256 bdd87870b317246d7c96416f47ddb0362cc6d8fb5212a2888a1d4fa678dc66c3
MD5 b2a75d4784cc14b80a352cb9e1b41821
BLAKE2b-256 c164e7afabafe66bf9cca4efb18aa9434c1d85d7cb99c161a748fe73044b3fbf

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_include_media-0.1.0.tar.gz:

Publisher: pypi-release.yml on blighj/django-include-media

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file django_include_media-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for django_include_media-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d52f7bff966406237e8f06b956fff899d9247af734c9bafa6bd928b3fc83e5d6
MD5 661ae3f791f829f913443a5df419f528
BLAKE2b-256 a6140c2cc8871944713aaec776b7f079ad913185e436a2014bb93f95949d713d

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_include_media-0.1.0-py3-none-any.whl:

Publisher: pypi-release.yml on blighj/django-include-media

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page