Skip to content

Equip Django projects with cache-busted static files#3

Open
audreyfeldroy wants to merge 7 commits intomainfrom
django-support
Open

Equip Django projects with cache-busted static files#3
audreyfeldroy wants to merge 7 commits intomainfrom
django-support

Conversation

@audreyfeldroy
Copy link
Copy Markdown
Member

Summary

Django projects can now use staticware by adding staticware.contrib.django
to INSTALLED_APPS and setting STATICWARE_DIRECTORY. The integration provides
a {% hashed_static "path" %} template tag and a get_asgi_application() that
combines Django's ASGI handler with static file serving and automatic HTML
rewriting in one call.

The middleware also gained case-insensitive header matching. The ASGI spec says
header names should be lowercase, but Django sends mixed-case headers like
Content-Type. Making the middleware tolerant at both comparison sites
(content-type detection and content-length update after rewriting) means it
works with any ASGI framework regardless of header casing, and the Django
integration doesn't need a normalization layer.

Test plan

  • 12 Django integration tests: get_static() singleton, settings fallback,
    ImproperlyConfigured, template tag rendering, ASGI static serving, ASGI HTML
    rewriting, AppConfig attribute guardrails
  • 1 new core middleware test: mixed-case Content-Type and Content-Length,
    verifying both HTML rewriting and content-length accuracy
  • 41 existing core tests still pass
  • All 54 tests pass: uv run --group django pytest tests/ -q
  • Ruff lint clean on all new files

Django projects can now add "staticware.contrib.django" to
INSTALLED_APPS, set STATICWARE_DIRECTORY in settings, and get two
things: a {% hashed_static "path" %} template tag for cache-busted
URLs, and a get_asgi_application() that serves hashed files and
rewrites HTML in one call.

Key design decisions:

- get_static() is a lazy singleton that reads Django settings on
  first call, so the HashedStatic instance is created once and
  reused across template renders and ASGI requests.

- Django's ASGI handler spawns a background task that calls
  receive() a second time to listen for client disconnect. The
  combined app blocks that second call with asyncio.Future() so
  Django can cancel it cleanly after the response is sent.

- STATICWARE_DIRECTORY is required (raises ImproperlyConfigured).
  STATICWARE_PREFIX falls back to STATIC_URL then "/static".
default_auto_field configures implicit primary keys for models, but
this app has no models. default_app_config was the pre-3.2 mechanism
for AppConfig discovery, deprecated in 3.2 and removed in 5.1. Since
staticware requires django>=4.2, both lines were dead code.

Tests assert neither attribute reappears, so this stays clean if
future tooling regenerates the scaffolding.
…pper

The ASGI spec says header names should be lowercase bytes, but Django
sends Content-Type and Content-Length with capital letters. The
middleware now compares with .lower() at both header-reading sites:
content-type detection and content-length update after rewriting.

This made the _normalized_django wrapper in the Django integration
redundant. The wrapper existed solely to lowercase Django's headers
before they reached the middleware. With the fix in the middleware
itself, django_app passes directly to StaticRewriteMiddleware and
every ASGI framework benefits, not just Django.
The ty type checker runs without Django installed (it's an optional
dependency), so the Django contrib imports fail resolution. Excluding
that directory from ty's src scope lets CI pass without pulling Django
into the typecheck group. The ruff formatter also needed two files
reformatted (string concatenation and dict literal style).
Django is an optional dependency, so the default test job doesn't have
it installed. The tests/conftest.py now skips the django/ directory
when Django isn't importable, and a new test-django CI job runs with
--group django to cover the integration tests.
@github-advanced-security
Copy link
Copy Markdown

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

Both directories import Django, which is an optional dependency not
available in the typecheck environment. Tests are covered by pytest
at runtime; ty only needs to check the core library.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants