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