generated from westfarn/web_django_template
Initial commit
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
"""Helpers for social post media (StoredFile-backed)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from django.conf import settings
|
||||
from django.urls import reverse
|
||||
|
||||
from core.models import StoredFile
|
||||
|
||||
ALLOWED_IMAGE_TYPES = frozenset(
|
||||
{"image/jpeg", "image/jpg", "image/png", "image/gif", "image/webp"}
|
||||
)
|
||||
ALLOWED_VIDEO_TYPES = frozenset(
|
||||
{"video/mp4", "video/quicktime", "video/webm"}
|
||||
)
|
||||
MAX_IMAGE_BYTES = 8 * 1024 * 1024 # 8 MB
|
||||
MAX_VIDEO_BYTES = 100 * 1024 * 1024 # 100 MB
|
||||
MAX_IMAGES = 10
|
||||
|
||||
|
||||
def public_file_url(file_id: str) -> str:
|
||||
"""Absolute URL Meta/LinkedIn can fetch (UUID = capability token)."""
|
||||
base = (settings.PUBLIC_SITE_URL or "").rstrip("/")
|
||||
path = reverse("core:stored_file", kwargs={"pk": file_id})
|
||||
if not base:
|
||||
return path
|
||||
return f"{base}{path}"
|
||||
|
||||
|
||||
def media_item_from_stored(stored: StoredFile) -> dict[str, Any]:
|
||||
kind = stored.kind
|
||||
media_type = "video" if kind == StoredFile.Kind.SOCIAL_VIDEO else "image"
|
||||
return {
|
||||
"id": str(stored.pk),
|
||||
"type": media_type,
|
||||
"url": public_file_url(str(stored.pk)),
|
||||
"content_type": stored.content_type,
|
||||
"filename": stored.filename or "",
|
||||
"size": stored.size,
|
||||
}
|
||||
|
||||
|
||||
def parse_media_json(raw: str) -> list[dict[str, Any]]:
|
||||
"""Parse composer hidden media JSON into a cleaned list."""
|
||||
raw = (raw or "").strip()
|
||||
if not raw:
|
||||
return []
|
||||
try:
|
||||
data = json.loads(raw)
|
||||
except json.JSONDecodeError as exc:
|
||||
raise ValueError("Invalid media payload.") from exc
|
||||
if not isinstance(data, list):
|
||||
raise ValueError("Media payload must be a list.")
|
||||
|
||||
items: list[dict[str, Any]] = []
|
||||
for entry in data:
|
||||
if not isinstance(entry, dict):
|
||||
continue
|
||||
file_id = str(entry.get("id") or "").strip()
|
||||
if not file_id:
|
||||
continue
|
||||
try:
|
||||
stored = StoredFile.objects.get(pk=file_id)
|
||||
except (StoredFile.DoesNotExist, ValueError) as exc:
|
||||
raise ValueError(f"Unknown media file: {file_id}") from exc
|
||||
if stored.kind not in {
|
||||
StoredFile.Kind.SOCIAL_IMAGE,
|
||||
StoredFile.Kind.SOCIAL_VIDEO,
|
||||
StoredFile.Kind.CAMPAIGN_IMAGE,
|
||||
}:
|
||||
raise ValueError("File is not a social media attachment.")
|
||||
items.append(media_item_from_stored(stored))
|
||||
|
||||
videos = [m for m in items if m["type"] == "video"]
|
||||
images = [m for m in items if m["type"] == "image"]
|
||||
if len(videos) > 1:
|
||||
raise ValueError("Attach at most one video per post.")
|
||||
if videos and images:
|
||||
raise ValueError("Use either images or one video — not both.")
|
||||
if len(images) > MAX_IMAGES:
|
||||
raise ValueError(f"Attach at most {MAX_IMAGES} images.")
|
||||
return items
|
||||
|
||||
|
||||
def split_media(media: list[dict[str, Any]]) -> tuple[list[dict], list[dict]]:
|
||||
images = [m for m in media if m.get("type") == "image"]
|
||||
videos = [m for m in media if m.get("type") == "video"]
|
||||
return images, videos
|
||||
Reference in New Issue
Block a user