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']), ]