From aed84b6e11aca68f0dfcf65f7dea9294d1fc3982 Mon Sep 17 00:00:00 2001 From: Ryan Westfall Date: Sun, 9 Aug 2026 06:46:07 -0500 Subject: [PATCH] Fix email send crash when worker lacks staticfiles manifest. Fall back to an unhashed logo URL if ManifestStaticFilesStorage misses, and run collectstatic in the worker entrypoint. --- scripts/worker-entrypoint.sh | 5 +++++ site/public/email_branding.py | 33 ++++++++++++++++++++++++++++----- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/scripts/worker-entrypoint.sh b/scripts/worker-entrypoint.sh index 995bae1..5f78b44 100755 --- a/scripts/worker-entrypoint.sh +++ b/scripts/worker-entrypoint.sh @@ -42,5 +42,10 @@ wait_for_database uv run python manage.py migrate --noinput +# Email branding (and anything else that resolves static URLs) needs the +# ManifestStaticFilesStorage map. Web entrypoint already collectstatics; +# worker must too or hashed lookups raise ValueError. +uv run python manage.py collectstatic --noinput + # dj-queue supervisor (workers + dispatcher + scheduler). Run on ONE host only. exec uv run python manage.py dj_queue diff --git a/site/public/email_branding.py b/site/public/email_branding.py index 267b310..013786a 100644 --- a/site/public/email_branding.py +++ b/site/public/email_branding.py @@ -3,13 +3,40 @@ from __future__ import annotations import html +import logging import re from django.conf import settings from django.contrib.staticfiles.storage import staticfiles_storage +logger = logging.getLogger(__name__) _URL_RE = re.compile(r"(https?://[^\s<]+)") +_LOGO_STATIC_PATH = "brand/exit_logo.png" + + +def _absolute_static_url(site_url: str, relative: str) -> str: + """ + Build an absolute URL for a static asset. + + Prefer the hashed Manifest URL when available. Fall back to the stable + path when the manifest is missing (dj-queue worker does not run + collectstatic) so sending mail never crashes. + """ + try: + path = staticfiles_storage.url(relative) + except ValueError: + logger.debug( + "staticfiles manifest miss for %s; using unhashed URL", relative + ) + static_prefix = settings.STATIC_URL or "/static/" + path = f"{static_prefix}{relative.lstrip('/')}" + + if path.startswith("http://") or path.startswith("https://"): + return path + if not path.startswith("/"): + path = f"/{path}" + return f"{site_url}{path}" def email_brand_context(**extra): @@ -17,11 +44,7 @@ def email_brand_context(**extra): if not site_url: site_url = "https://mkdrealtor.com" - logo_path = staticfiles_storage.url("brand/exit_logo.png") - if logo_path.startswith("http://") or logo_path.startswith("https://"): - logo_url = logo_path - else: - logo_url = f"{site_url}{logo_path}" + logo_url = _absolute_static_url(site_url, _LOGO_STATIC_PATH) brand_name = getattr(settings, "SITE_NAME", None) or "Monica Dhillon" brand_legal = getattr(settings, "CREDIT_NAME", None) or brand_name