You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

343 lines
12 KiB

from django.contrib import admin
from django.utils.translation import gettext_lazy as _
from django.urls import reverse
from django.utils.html import format_html
from django.db import models
from ajaxdatatable.admin import AjaxDatatable
from unfold.admin import ModelAdmin, StackedInline, TabularInline
from django.contrib.admin import SimpleListFilter
from unfold.widgets import UnfoldAdminSelectWidget
from unfold.decorators import display, action
from django import forms
from utils.admin import dovoodi_admin_site
from unfold.sections import TableSection
from apps.video.models import *
class VideoPlaylistInCollectionInlineForCollection(TabularInline):
model = VideoPlaylistInCollection
extra = 1
autocomplete_fields = ('playlist',)
fields = ('playlist', 'order')
ordering = ('order',)
verbose_name = _('Playlist')
verbose_name_plural = _('Playlists')
tab = True
class VideoCollectionAdminBase(ModelAdmin):
list_display = ('get_title', 'status', 'order', 'count_playlists')
list_filter = ('status', 'order')
search_fields = ('title',)
ordering = ('order',)
list_filter_submit = True
warn_unsaved_form = True
change_form_show_cancel_button = True
inlines = [VideoPlaylistInCollectionInlineForCollection]
fieldsets = (
(None, {
'fields': ('title', 'summary', 'thumbnail' , 'status', 'pin_top', 'order')
}),
)
exclude = ('display_position',)
@display(description=_('Title'))
def get_title(self, obj):
return str(obj.title)
@display(description=_('Number of Playlists'))
def count_playlists(self, obj):
count = obj.related_playlists.count()
if count > 0:
url = reverse('admin:video_videoplaylist_changelist') + f'?collections__id__exact={obj.id}'
return format_html('<a href="{}">{}</a>', url, count)
return count
class PinnedVideoCollectionForm(forms.ModelForm):
class Meta:
model = PinnedVideoCollection
# fields = '__all__'
exclude = ('slug',)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['thumbnail'].required = True
class PinnedVideoCollectionAdmin(VideoCollectionAdminBase):
form = PinnedVideoCollectionForm
def get_queryset(self, request):
return super().get_queryset(request).filter(display_position=VideoCollection.DisplayPosition.PINNED)
def save_model(self, request, obj, form, change):
obj.display_position = VideoCollection.DisplayPosition.PINNED
super().save_model(request, obj, form, change)
@display(description=_('Title'))
def get_title(self, obj):
from django.templatetags.static import static
thumbnail_path = obj.thumbnail.url if obj.thumbnail else None
return obj.title
# return [
# obj.title,
# None,
# None,
# {
# "path": thumbnail_path,
# "height": 30,
# "width": 50,
# "borderless": True,
# # "squared": True,
# },
# ]
class MiddleVideoCollectionAdmin(VideoCollectionAdminBase):
fieldsets = (
(None, {
'fields': ('title', 'status', 'pin_top', 'order')
}),
)
def get_queryset(self, request):
return super().get_queryset(request).filter(display_position=VideoCollection.DisplayPosition.MIDDLE)
def save_model(self, request, obj, form, change):
obj.display_position = VideoCollection.DisplayPosition.MIDDLE
super().save_model(request, obj, form, change)
class VideoCategoryAdmin(ModelAdmin):
list_display = ('title', 'slug', 'status', 'order', 'count_videos', 'created_at')
list_filter = ('status', 'created_at', 'updated_at')
search_fields = ('title', 'slug')
@admin.display(description=_('Number of Videos'))
def count_videos(self, obj):
# Count videos through playlists: Category -> Playlist -> PlaylistItem -> Video
count = Video.objects.filter(
playlist_appearances__playlist__categories=obj
).distinct().count()
if count > 0:
# Note: Direct filtering by category in admin might not work due to the relationship
# We'll just display the count without a clickable link for now
return count
return count
def get_form(self, request, obj=None, change=False, **kwargs):
form = super().get_form(request, obj, change, **kwargs)
if form.base_fields.get('slug'):
form.base_fields['slug'].required = False
return form
class VideoAdmin(ModelAdmin):
list_display = ('title', 'slug', 'video_type', 'status', 'view_count', 'created_at')
list_filter = ('status', 'video_type', 'created_at', 'updated_at')
search_fields = ('title', 'slug', 'description')
conditional_fields = {
'video_file': "video_type == 'video_file'",
'video_url': "video_type == 'youtube_link'",
}
radio_fields = {
"video_type": admin.HORIZONTAL,
}
save_as = True
search_help_text = _("Search by title, slug, or description")
search_fields_placeholder = _("Search videos")
fieldsets = (
(None, {
'fields': ('title', 'slug', 'description', 'thumbnail')
}),
(_('Video Information'), {
'fields': ('video_type', 'video_file', 'video_url', 'video_time')
}),
(_('Status'), {
'fields': ('status',)
}),
(_('Statistics'), {
'fields': ('view_count',)
}),
)
def get_form(self, request, obj=None, change=False, **kwargs):
form = super().get_form(request, obj, change, **kwargs)
if form.base_fields.get('slug'):
form.base_fields['slug'].required = False
if form.base_fields.get('thumbnail'):
form.base_fields['thumbnail'].required = True
if form.base_fields.get('video_type') and not obj:
form.base_fields['video_type'].initial = 'youtube_link'
return form
class PlaylistItemForm(forms.ModelForm):
class Meta:
model = PlaylistItem
fields = ('video', 'priority')
def clean_video(self):
video = self.cleaned_data.get('video')
if not video:
return video
# If we're editing, exclude the current instance from the check
instance = getattr(self, 'instance', None)
if instance and instance.pk and instance.video == video:
return video
# Check if this video exists in another playlist
existing_item = PlaylistItem.objects.filter(video=video).first()
if existing_item:
playlist_name = existing_item.playlist.title
raise forms.ValidationError(
_('This video is already used in playlist "{}". Each video can only be in one playlist.').format(playlist_name)
)
return video
class PlaylistItemInline(StackedInline):
model = PlaylistItem
form = PlaylistItemForm
extra = 1
autocomplete_fields = ('video',)
fields = ('video', 'priority')
ordering = ('priority',)
verbose_name = _('Playlist Item')
verbose_name_plural = _('Playlist Items')
class VideoPlaylistInCollectionInline(TabularInline):
model = VideoPlaylistInCollection
extra = 1
raw_id_fields = ('collection',)
fields = ('collection', 'order')
ordering = ('order',)
verbose_name = _('Collection')
verbose_name_plural = _('Collections')
tab = True
class VideoPlaylistAdmin(ModelAdmin):
list_display = ('title', 'slug', 'status', 'order', 'view_count', 'count_videos', 'created_at')
list_filter = ('status', 'created_at', 'categories')
search_fields = ('title', 'slug', 'slogan', 'description')
autocomplete_fields = ('categories',)
list_filter_submit = True
warn_unsaved_form = True
change_form_show_cancel_button = True
inlines = [PlaylistItemInline, VideoPlaylistInCollectionInline]
fieldsets = (
(None, {
'fields': ('title', 'slug', 'slogan', 'description', 'thumbnail')
}),
(_('Categories'), {
'fields': ('categories',)
}),
(_('Display Settings'), {
'fields': ('order', 'status')
}),
(_('Statistics'), {
'fields': ('view_count', 'total_time')
}),
)
def get_form(self, request, obj=None, change=False, **kwargs):
form = super().get_form(request, obj, change, **kwargs)
if form.base_fields.get('slug'):
form.base_fields['slug'].required = False
if form.base_fields.get('thumbnail'):
form.base_fields['thumbnail'].required = False
if form.base_fields.get('total_time'):
form.base_fields['total_time'].required = False
form.base_fields['total_time'].help_text = _('Will be auto-calculated from videos')
return form
@display(description=_('Number of Videos'))
def count_videos(self, obj):
count = obj.playlist_items.count()
if count > 0:
return format_html('<span>{}</span>', count)
return count
def save_model(self, request, obj, form, change):
super().save_model(request, obj, form, change)
# Auto-calculate total_time
obj.total_time = obj.calculate_total_time()
obj.save(update_fields=['total_time'])
def save_formset(self, request, form, formset, change):
"""
Additional validation to ensure each video is used in only one playlist
"""
instances = formset.save(commit=False)
# Collect all videos that are being saved
videos_to_save = []
for instance in instances:
if instance.video:
videos_to_save.append(instance.video)
# Check for duplicate videos in this formset
video_counts = {}
for video in videos_to_save:
video_counts[video.id] = video_counts.get(video.id, 0) + 1
duplicate_videos = [video_id for video_id, count in video_counts.items() if count > 1]
if duplicate_videos:
# If there are duplicate videos in this form, show an error
formset._non_form_errors = formset.error_class(
[_('A video cannot be used multiple times in the same playlist.')]
)
return
# Check if videos are used in other playlists
for instance in instances:
if instance.video: # For both new and edited items
playlist_id = form.instance.pk
query = PlaylistItem.objects.filter(
video=instance.video
).exclude(
playlist_id=playlist_id
)
# If we're editing an existing item, exclude it from the check
if instance.pk:
query = query.exclude(pk=instance.pk)
existing_item = query.first()
if existing_item:
playlist_name = existing_item.playlist.title
formset._non_form_errors = formset.error_class(
[_('Video "{}" is already used in playlist "{}". Each video can only be in one playlist.').format(
instance.video.title, playlist_name
)]
)
return
# If all validations pass, save the formset
super().save_formset(request, form, formset, change)
dovoodi_admin_site.register(VideoCategory, VideoCategoryAdmin)
dovoodi_admin_site.register(Video, VideoAdmin)
dovoodi_admin_site.register(PinnedVideoCollection, PinnedVideoCollectionAdmin)
dovoodi_admin_site.register(MiddleVideoCollection, MiddleVideoCollectionAdmin)
dovoodi_admin_site.register(VideoPlaylist, VideoPlaylistAdmin)