deployment_timestamp was int(time.time()) per-request, giving every page load a new ?v=... query string on custom.css. Cloudflare treats each unique URL as a new resource, so the CSS was fetched from the VM on every page load — 64 KB over the wire per navigation. Token now tied to static/css/custom.css mtime. The URL only changes when the CSS actually changes, so Cloudflare can hold the file for its full 4h TTL. Degraded-mode fallback preserves today's behaviour if the file isn't on disk. 3 new CacheBustTokenTests; all 68 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
45 lines
1.8 KiB
Python
45 lines
1.8 KiB
Python
# === core/context_processors.py ===
|
|
# Globals injected into every template render.
|
|
#
|
|
# `deployment_timestamp` is a cache-bust token on our CSS URL. Historically
|
|
# it was `int(time.time())` — a new value every second — which defeated
|
|
# Cloudflare's edge cache because every page load saw a different `?v=...`
|
|
# string. We now tie the token to `custom.css`'s mtime, so the URL
|
|
# changes ONLY when the CSS actually changes. Cloudflare can hold the
|
|
# file for its full 4h TTL, and users on repeat visits hit the browser
|
|
# cache (304 Not Modified).
|
|
import os
|
|
import time
|
|
from pathlib import Path
|
|
|
|
from django.conf import settings
|
|
|
|
# Path to the file whose mtime drives the cache-bust token. Module-level
|
|
# constant so tests can monkey-patch it to simulate "file missing".
|
|
_CSS_PATH_FOR_TOKEN = Path(settings.BASE_DIR) / 'static' / 'css' / 'custom.css'
|
|
|
|
|
|
def _compute_cache_bust_token():
|
|
"""Return an integer cache-bust token.
|
|
|
|
Normal path: returns the CSS file's mtime (an integer).
|
|
Fallback: if the file can't be stat'd (doesn't exist / permission
|
|
error / disk issue), returns the current wall-clock time in seconds
|
|
— that degrades to the PRE-FIX behaviour (new token per request)
|
|
rather than crashing the whole request cycle.
|
|
"""
|
|
try:
|
|
return int(os.path.getmtime(_CSS_PATH_FOR_TOKEN))
|
|
except (OSError, FileNotFoundError):
|
|
return int(time.time())
|
|
|
|
|
|
def project_context(request):
|
|
"""Adds project-specific environment variables to the template context globally."""
|
|
return {
|
|
"project_description": os.getenv("PROJECT_DESCRIPTION", ""),
|
|
"project_image_url": os.getenv("PROJECT_IMAGE_URL", ""),
|
|
# Cache-busts static assets — see _compute_cache_bust_token().
|
|
"deployment_timestamp": _compute_cache_bust_token(),
|
|
}
|