""" Upload validation helpers. Validates file extension and size against settings before persisting to storage. """ from __future__ import annotations import os from django.conf import settings class UploadValidationError(Exception): pass def validate_upload(file) -> None: """ Validate an uploaded file against configured size and extension limits. Raises UploadValidationError on violation. """ ext = os.path.splitext(file.name)[1].lower() allowed = [e.lower() for e in getattr(settings, "ALLOWED_UPLOAD_EXTENSIONS", [])] max_size = getattr(settings, "MAX_UPLOAD_SIZE_BYTES", 100 * 1024 * 1024) if allowed and ext not in allowed: raise UploadValidationError( f"File extension '{ext}' is not allowed. Permitted: {', '.join(allowed)}" ) if file.size > max_size: max_mb = max_size // (1024 * 1024) raise UploadValidationError( f"File size {file.size} bytes exceeds limit of {max_mb} MB." ) def save_upload(file, org_id: str, uploader) -> object: """Persist an uploaded file and create a MediaAsset record.""" import uuid import mimetypes from django.conf import settings as s from .models import MediaAsset, ScanStatus ext = os.path.splitext(file.name)[1].lower() storage_root = getattr(s, "MEDIA_ROOT", "/tmp/media") relative_path = os.path.join("uploads", str(org_id), f"{uuid.uuid4().hex}{ext}") full_path = os.path.join(storage_root, relative_path) os.makedirs(os.path.dirname(full_path), exist_ok=True) with open(full_path, "wb") as dest: for chunk in file.chunks(): dest.write(chunk) mime_type, _ = mimetypes.guess_type(file.name) asset = MediaAsset.objects.create( uploaded_by=uploader, org_id=org_id, file_name=file.name, file_path=relative_path, file_size=file.size, mime_type=mime_type or "", extension=ext, scan_status=ScanStatus.PENDING, ) # Enqueue async scan from .tasks import scan_media_asset_task scan_media_asset_task.delay(str(asset.id)) return asset