Files
training-software/cms/upload.py
Paperclip CTO 333c7b8e11
All checks were successful
CI / lint (push) Successful in 6s
CI / test (push) Successful in 22s
CI / build-container (push) Successful in 1m21s
fix(ci): run tests without redis and align permission/upload expectations
2026-05-18 15:09:16 +02:00

73 lines
2.1 KiB
Python

"""
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