nexus-5/core/models/session_video.py
2026-01-26 11:09:40 -05:00

164 lines
5.5 KiB
Python

import os
import uuid
from django.db import models
from core.models.base import BaseModel
from core.models.session import ServiceSession, ProjectSession
def _service_session_video_upload_to(instance: "ServiceSessionVideo", filename: str) -> str:
"""Upload path for service session videos."""
base, ext = os.path.splitext(filename)
ext = ext.lower() or ".mp4"
sid = instance.service_session_id or "unassigned"
return f"videos/service_session/{sid}/{uuid.uuid4().hex}{ext}"
def _service_session_video_thumb_upload_to(instance: "ServiceSessionVideo", _filename: str) -> str:
"""Upload path for service session video thumbnails."""
sid = instance.service_session_id or "unassigned"
return f"videos/service_session/{sid}/thumb/{uuid.uuid4().hex}.jpg"
def _project_session_video_upload_to(instance: "ProjectSessionVideo", filename: str) -> str:
"""Upload path for project session videos."""
base, ext = os.path.splitext(filename)
ext = ext.lower() or ".mp4"
sid = instance.project_session_id or "unassigned"
return f"videos/project_session/{sid}/{uuid.uuid4().hex}{ext}"
def _project_session_video_thumb_upload_to(instance: "ProjectSessionVideo", _filename: str) -> str:
"""Upload path for project session video thumbnails."""
sid = instance.project_session_id or "unassigned"
return f"videos/project_session/{sid}/thumb/{uuid.uuid4().hex}.jpg"
class Video(BaseModel):
"""
Abstract base for video-bearing models.
Features:
- Stores original video file with metadata
- Optional thumbnail image (can be extracted from video or uploaded separately)
- Captures dimensions, duration, file size, and content_type
- Tracks the uploading team profile (optional)
- Storage-agnostic (respects DEFAULT_FILE_STORAGE)
"""
title = models.CharField(max_length=255, blank=True)
video = models.FileField(upload_to="videos/") # Override in subclasses
thumbnail = models.ImageField(upload_to="videos/thumbs/", blank=True, null=True)
content_type = models.CharField(max_length=100, blank=True)
# Video-specific metadata
duration_seconds = models.PositiveIntegerField(
default=0,
help_text="Video duration in seconds"
)
file_size_bytes = models.PositiveBigIntegerField(
default=0,
help_text="File size in bytes"
)
width = models.PositiveIntegerField(default=0)
height = models.PositiveIntegerField(default=0)
uploaded_by_team_profile = models.ForeignKey(
'TeamProfile',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="%(class)s_videos"
)
notes = models.TextField(blank=True)
internal = models.BooleanField(default=True)
class Meta:
abstract = True
ordering = ('-created_at',)
def __str__(self) -> str:
return self.title or str(self.id)
def save(self, *args, **kwargs):
"""
Save and capture file size on creation.
Video metadata (duration, dimensions) should be set before save
by the upload handler using video processing utilities.
"""
if self._state.adding and self.video and hasattr(self.video, 'size'):
self.file_size_bytes = self.video.size
super().save(*args, **kwargs)
def delete(self, *args, **kwargs):
"""
Delete the model and its associated files from storage.
"""
# Store file names before delete
video_name = self.video.name if self.video else None
thumbnail_name = self.thumbnail.name if self.thumbnail else None
# Delete the model instance
super().delete(*args, **kwargs)
# Delete files from storage
if video_name:
try:
self.video.storage.delete(video_name)
except Exception:
pass # File may already be deleted or inaccessible
if thumbnail_name:
try:
self.thumbnail.storage.delete(thumbnail_name)
except Exception:
pass # File may already be deleted or inaccessible
class ServiceSessionVideo(Video):
"""Video attached to a ServiceSession for documentation."""
service_session = models.ForeignKey(
ServiceSession,
on_delete=models.PROTECT,
related_name='service_session_videos'
)
video = models.FileField(upload_to=_service_session_video_upload_to)
thumbnail = models.ImageField(
upload_to=_service_session_video_thumb_upload_to,
blank=True,
null=True
)
def __str__(self) -> str:
return self.title or f"ServiceSessionVideo {self.id}"
class Meta:
ordering = ('-created_at',)
indexes = [
models.Index(fields=['service_session', 'created_at']),
models.Index(fields=['created_at']),
]
class ProjectSessionVideo(Video):
"""Video attached to a ProjectSession for documentation."""
project_session = models.ForeignKey(
ProjectSession,
on_delete=models.PROTECT,
related_name='project_session_videos'
)
video = models.FileField(upload_to=_project_session_video_upload_to)
thumbnail = models.ImageField(
upload_to=_project_session_video_thumb_upload_to,
blank=True,
null=True
)
def __str__(self) -> str:
return self.title or f"ProjectSessionVideo {self.id}"
class Meta:
ordering = ('-created_at',)
indexes = [
models.Index(fields=['project_session', 'created_at']),
models.Index(fields=['created_at']),
]