import os import posixpath import time import mimetypes from os import PathLike from pathlib import Path from urllib.parse import urlparse from urllib.request import urlretrieve from django.conf import settings from django.core.mail import EmailMultiAlternatives from foerderbarometer.constants import * PathList = list[Path] def ensure_dir(directory: PathLike) -> Path: """ Ensure that the given directory exists. Creates it recursively if it doesn't. """ directory = Path(directory) directory.mkdir(parents=True, exist_ok=True) return directory def is_fresh(path: Path, ttl_seconds: int) -> bool: """ Check if the cached file exists and is still fresh within TTL. """ try: mtime = path.stat().st_mtime except FileNotFoundError: return False else: return time.time() - mtime < ttl_seconds def get_attachment(url: str) -> Path: filepath = urlparse(url).path filename = posixpath.basename(filepath) destination = ensure_dir(settings.MAIL_ATTACHMENT_CACHE_DIR) / filename if is_fresh(destination, settings.MAIL_ATTACHMENT_TTL_SECONDS): return destination return download_attachment(url, destination) def download_attachment(url: str, destination: Path) -> Path: filepath = destination.with_suffix('.tmp') try: urlretrieve(url, filepath) os.replace(filepath, destination) finally: filepath.unlink(missing_ok=True) return destination def collect_attachment_paths(recipient: str, type_code: str) -> PathList: assert recipient in RECIPIENTS assert type_code in TYPES config = settings.MAIL_ATTACHMENT_URLS[recipient] urls = [*config[TYPE_ALL], *config.get(type_code, [])] return [get_attachment(url) for url in urls] def get_mime_type(path: Path) -> str: mime_type, encoding = mimetypes.guess_type(path) return mime_type or 'application/octet-stream' def attach_files(message: EmailMultiAlternatives, files: list[Path]): """ Attach files to the EmailMultiAlternatives message. MIME type is guessed from path; falls back to application/octet-stream. """ for path in files: mime_type = get_mime_type(path) with open(path, 'rb') as fp: message.attach(path.name, fp.read(), mime_type)