from django.db import models from django.utils.translation import gettext_lazy as _ from django.conf import settings from filer.fields.image import FilerImageField from utils import generate_slug_for_model from django.core.validators import FileExtensionValidator from django.core.exceptions import ValidationError class VideoCategory(models.Model): title = models.CharField(max_length=255, verbose_name=_('title')) slug = models.SlugField(allow_unicode=True, unique=True, verbose_name=_('slug')) status = models.BooleanField(default=True, verbose_name=_('status')) order = models.PositiveIntegerField(default=0, verbose_name=_('order')) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at')) updated_at = models.DateTimeField(auto_now=True, verbose_name=_('updated at')) def __str__(self): return self.title def save(self, *args, **kwargs): if not self.slug: self.slug = generate_slug_for_model(VideoCategory, self.title) super().save(*args, **kwargs) class Meta: verbose_name = _('Video Category') verbose_name_plural = _('Video Categories') ordering = ['order'] class VideoCollection(models.Model): class DisplayPosition(models.TextChoices): PINNED = 'pinned', _('Pinned') MIDDLE = 'middle', _('Middle Section') title = models.CharField(max_length=255) slug = models.SlugField(max_length=255, unique=True) summary = models.CharField(max_length=512, null=True, blank=True, help_text=_('could be null')) pin_top = models.BooleanField(_('pin top'), default=True) thumbnail = models.ImageField(upload_to='video/collection/', null=True, blank=True, help_text=_('image allowed')) order = models.IntegerField(default=0, verbose_name=_('order')) status = models.BooleanField(default=True, verbose_name=_('status')) display_position = models.CharField( max_length=20, choices=DisplayPosition.choices, default=DisplayPosition.PINNED, verbose_name=_('Display Position') ) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at')) updated_at = models.DateTimeField(auto_now=True, verbose_name=_('updated at')) def __str__(self): return f'Collection #{self.id}/{self.title}' def save(self, *args, **kwargs): if not self.slug: self.slug = generate_slug_for_model(VideoCollection, self.title) super().save(*args, **kwargs) class Meta: verbose_name = _('Video Collection') verbose_name_plural = _('Video Collections') class PinnedVideoCollection(VideoCollection): class Meta: proxy = True verbose_name = _('Pinned Video Collection') verbose_name_plural = _('Pinned Video Collections') class MiddleVideoCollection(VideoCollection): class Meta: proxy = True verbose_name = _('Middle Section Video Collection') verbose_name_plural = _('Middle Section Video Collections') class Video(models.Model): class VedioTypeChoices(models.TextChoices): YOUTUBE_LINK = 'youtube_link', 'Youtube Link' VIDEO_FILE = 'video_file', 'Video File' title = models.CharField(max_length=255, null=True) slug = models.SlugField(allow_unicode=True, unique=True) thumbnail = models.ImageField(upload_to='video/thumbnails/', null=True, blank=True, help_text=_('image allowed')) description = models.TextField(null=True) video_type = models.CharField(max_length=255, choices=VedioTypeChoices.choices) video_file = models.FileField(upload_to='video/videos/', null=True, blank=True ,validators=[ FileExtensionValidator( allowed_extensions=['mp4', 'mov', 'avi', 'mkv', 'webm'] ) ]) video_url = models.CharField(max_length=655, null=True, blank=True) video_time = models.TimeField() view_count = models.PositiveBigIntegerField(default=0, verbose_name=_('view count')) status = models.BooleanField(default=True, verbose_name=_('status')) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at')) updated_at = models.DateTimeField(auto_now=True, verbose_name=_('updated at')) def __str__(self): return self.title @property def share_link(self): if self.slug: return f"{settings.DOVODI_DOMAIN}/videos/{self.slug}" return None def increment_view_count(self): """Increment the view count for this video""" self.view_count += 1 self.save(update_fields=['view_count']) return self.view_count def clean(self): super().clean() if self.video_type == self.VedioTypeChoices.YOUTUBE_LINK and not self.video_url: raise ValidationError({ 'video_url': _('This field is required when video type is Link.') }) if self.video_type == self.VedioTypeChoices.VIDEO_FILE and not self.video_file: raise ValidationError({ 'video_file': _('This field is required when video type is File.') }) def save(self, *args, **kwargs): if not self.slug: self.slug = generate_slug_for_model(Video, self.title) super().save(*args, **kwargs) class VideoPlaylist(models.Model): title = models.CharField(max_length=255, verbose_name=_('title')) slug = models.SlugField(allow_unicode=True, unique=True, null=True, blank=True, verbose_name=_('slug')) slogan = models.CharField(max_length=512, null=True, blank=True, verbose_name=_('slogan')) description = models.TextField(null=True, blank=True, verbose_name=_('description')) thumbnail = models.ImageField(upload_to='video/playlist/thumbnails/', null=True, blank=True, verbose_name=_('thumbnail')) categories = models.ManyToManyField( VideoCategory, related_name='playlists', verbose_name=_('categories'), blank=True, ) collections = models.ManyToManyField( VideoCollection, through='VideoPlaylistInCollection', related_name='related_playlists', verbose_name=_('collections'), blank=True ) order = models.PositiveIntegerField(default=0, verbose_name=_('order')) status = models.BooleanField(default=True, verbose_name=_('status')) view_count = models.PositiveBigIntegerField(default=0, verbose_name=_('view count')) total_time = models.DurationField(null=True, blank=True, verbose_name=_('total time')) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at')) updated_at = models.DateTimeField(auto_now=True, verbose_name=_('updated at')) def __str__(self): return self.title def increment_view_count(self): """Increment the view count for this playlist""" self.view_count += 1 self.save(update_fields=['view_count']) return self.view_count def calculate_total_time(self): """Calculate total duration of all videos in this playlist""" from datetime import timedelta total_seconds = 0 for item in self.playlist_items.select_related('video'): video_time = item.video.video_time total_seconds += video_time.hour * 3600 + video_time.minute * 60 + video_time.second return timedelta(seconds=total_seconds) def save(self, *args, **kwargs): if not self.slug: self.slug = generate_slug_for_model(VideoPlaylist, self.title) super().save(*args, **kwargs) class Meta: verbose_name = _('Video Playlist') verbose_name_plural = _('Video Playlists') ordering = ['order', '-created_at'] class VideoPlaylistInCollection(models.Model): collection = models.ForeignKey( VideoCollection, on_delete=models.CASCADE, related_name='collection_playlists', verbose_name=_('collection') ) playlist = models.ForeignKey( VideoPlaylist, on_delete=models.CASCADE, related_name='playlist_collections', verbose_name=_('playlist') ) order = models.PositiveIntegerField(default=0, verbose_name=_('order')) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at')) updated_at = models.DateTimeField(auto_now=True, verbose_name=_('updated at')) def __str__(self): return f"{self.collection.title} - {self.playlist.title}" class Meta: verbose_name = _('Video Playlist in Collection') verbose_name_plural = _('Video Playlists in Collections') ordering = ['order'] unique_together = ['collection', 'playlist'] class PlaylistItem(models.Model): playlist = models.ForeignKey( VideoPlaylist, on_delete=models.CASCADE, related_name='playlist_items', verbose_name=_('playlist') ) video = models.ForeignKey( Video, on_delete=models.CASCADE, related_name='playlist_appearances', verbose_name=_('video') ) priority = models.PositiveIntegerField(default=0, verbose_name=_('priority')) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at')) updated_at = models.DateTimeField(auto_now=True, verbose_name=_('updated at')) def __str__(self): return f"{self.playlist.title} - {self.video.title} (Priority: {self.priority})" class Meta: verbose_name = _('Playlist Item') verbose_name_plural = _('Playlist Items') ordering = ['priority'] unique_together = ['playlist', 'video']