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.
290 lines
10 KiB
290 lines
10 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 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 project_admin_site
|
|
from unfold.sections import TableSection
|
|
|
|
from apps.podcast.models import *
|
|
|
|
|
|
class PodcastInCollectionInline(TabularInline):
|
|
model = PodcastInCollection
|
|
extra = 1
|
|
autocomplete_fields = ('podcast',)
|
|
fields = ('podcast', 'order')
|
|
ordering = ('order',)
|
|
verbose_name = _('Podcast')
|
|
verbose_name_plural = _('Podcasts')
|
|
|
|
|
|
class PodcastCollectionAdminBase(ModelAdmin):
|
|
list_display = ('get_title', 'status', 'order', 'count_podcasts')
|
|
list_filter = ('status', 'order')
|
|
search_fields = ('title',)
|
|
ordering = ('order',)
|
|
list_filter_submit = True
|
|
warn_unsaved_form = True
|
|
change_form_show_cancel_button = True
|
|
inlines = [PodcastInCollectionInline]
|
|
|
|
|
|
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 Podcasts'))
|
|
def count_podcasts(self, obj):
|
|
count = obj.related_podcasts.count()
|
|
if count > 0:
|
|
url = reverse('admin:podcast_podcast_changelist') + f'?collections__id__exact={obj.id}'
|
|
return format_html('<a href="{}">{}</a>', url, count)
|
|
return count
|
|
|
|
|
|
class PinnedPodcastCollectionForm(forms.ModelForm):
|
|
class Meta:
|
|
model = PinnedPodcastCollection
|
|
exclude = ('slug',)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.fields['thumbnail'].required = True
|
|
|
|
class PinnedPodcastCollectionAdmin(PodcastCollectionAdminBase):
|
|
form = PinnedPodcastCollectionForm
|
|
|
|
def get_queryset(self, request):
|
|
return super().get_queryset(request).filter(display_position=PodcastCollection.DisplayPosition.PINNED)
|
|
|
|
def save_model(self, request, obj, form, change):
|
|
obj.display_position = PodcastCollection.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
|
|
|
|
class MiddlePodcastCollectionAdmin(PodcastCollectionAdminBase):
|
|
|
|
fieldsets = (
|
|
(None, {
|
|
'fields': ('title', 'status', 'pin_top', 'order')
|
|
}),
|
|
)
|
|
|
|
def get_queryset(self, request):
|
|
return super().get_queryset(request).filter(display_position=PodcastCollection.DisplayPosition.MIDDLE)
|
|
|
|
def save_model(self, request, obj, form, change):
|
|
obj.display_position = PodcastCollection.DisplayPosition.MIDDLE
|
|
super().save_model(request, obj, form, change)
|
|
|
|
|
|
class PodcastCategoryAdmin(ModelAdmin):
|
|
list_display = ('title', 'slug', 'status', 'order', 'count_podcasts', 'created_at')
|
|
list_filter = ('status', 'created_at', 'updated_at')
|
|
search_fields = ('title', 'slug')
|
|
search_help_text = _("Search by title or slug")
|
|
search_fields_placeholder = _("Search categories")
|
|
|
|
|
|
@admin.display(description=_('Number of Podcasts'))
|
|
def count_podcasts(self, obj):
|
|
count = obj.podcasts.count()
|
|
if count > 0:
|
|
url = reverse('admin:podcast_podcast_changelist') + f'?categories__id__exact={obj.id}'
|
|
return format_html('<a href="{}">{}</a>', url, 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 PodcastAdmin(ModelAdmin):
|
|
list_display = ('title', 'slug', 'status', 'view_count', 'created_at')
|
|
list_filter = ('status', 'created_at', 'updated_at')
|
|
search_fields = ('title', 'slug', 'description')
|
|
autocomplete_fields = ('categories',)
|
|
save_as = True
|
|
search_help_text = _("Search by title, slug, or description")
|
|
search_fields_placeholder = _("Search podcasts")
|
|
|
|
fieldsets = (
|
|
(None, {
|
|
'fields': ('title', 'slug', 'description', 'thumbnail', 'categories')
|
|
}),
|
|
(_('Audio Information'), {
|
|
'fields': ('audio_file', 'audio_time')
|
|
}),
|
|
(_('Status'), {
|
|
'fields': ('status',)
|
|
}),
|
|
(_('Statistics'), {
|
|
'fields': ('view_count', 'download_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
|
|
return form
|
|
|
|
|
|
class PodcastPlaylistItemForm(forms.ModelForm):
|
|
class Meta:
|
|
model = PlaylistItem
|
|
fields = ('podcast', 'priority')
|
|
|
|
def clean_podcast(self):
|
|
podcast = self.cleaned_data.get('podcast')
|
|
if not podcast:
|
|
return podcast
|
|
|
|
# If we're editing, exclude the current instance from the check
|
|
instance = getattr(self, 'instance', None)
|
|
if instance and instance.pk and instance.podcast == podcast:
|
|
return podcast
|
|
|
|
# Check if this podcast exists in another playlist
|
|
existing_item = PlaylistItem.objects.filter(podcast=podcast).first()
|
|
if existing_item:
|
|
playlist_name = existing_item.playlist.title
|
|
raise forms.ValidationError(
|
|
_('This podcast is already used in playlist "{}". Each podcast can only be in one playlist.').format(playlist_name)
|
|
)
|
|
return podcast
|
|
|
|
|
|
class PodcastPlaylistItemInline(StackedInline):
|
|
model = PlaylistItem
|
|
form = PodcastPlaylistItemForm
|
|
extra = 1
|
|
autocomplete_fields = ('podcast',)
|
|
fields = ('podcast', 'priority')
|
|
ordering = ('priority',)
|
|
verbose_name = _('Playlist Item')
|
|
verbose_name_plural = _('Playlist Items')
|
|
tab = True
|
|
|
|
class PodcastPlaylistAdmin(ModelAdmin):
|
|
list_display = ('title', 'count_podcasts', 'created_at')
|
|
list_filter = ('created_at',)
|
|
search_fields = ('title', )
|
|
list_filter_submit = True
|
|
warn_unsaved_form = True
|
|
change_form_show_cancel_button = True
|
|
inlines = [PodcastPlaylistItemInline]
|
|
|
|
fieldsets = (
|
|
(None, {
|
|
'fields': ('title',)
|
|
}),
|
|
)
|
|
|
|
|
|
@display(description=_('Number of Podcasts'))
|
|
def count_podcasts(self, obj):
|
|
count = obj.playlist_items.count()
|
|
if count > 0:
|
|
return format_html('<span>{}</span>', count)
|
|
return count
|
|
|
|
def save_formset(self, request, form, formset, change):
|
|
"""
|
|
Additional validation to ensure each podcast is used in only one playlist
|
|
"""
|
|
instances = formset.save(commit=False)
|
|
|
|
# Collect all podcasts that are being saved
|
|
podcasts_to_save = []
|
|
for instance in instances:
|
|
if instance.podcast:
|
|
podcasts_to_save.append(instance.podcast)
|
|
|
|
# Check for duplicate podcasts in this formset
|
|
podcast_counts = {}
|
|
for podcast in podcasts_to_save:
|
|
podcast_counts[podcast.id] = podcast_counts.get(podcast.id, 0) + 1
|
|
|
|
duplicate_podcasts = [podcast_id for podcast_id, count in podcast_counts.items() if count > 1]
|
|
if duplicate_podcasts:
|
|
# If there are duplicate podcasts in this form, show an error
|
|
formset._non_form_errors = formset.error_class(
|
|
[_('A podcast cannot be used multiple times in the same playlist.')]
|
|
)
|
|
return
|
|
|
|
# Check if podcasts are used in other playlists
|
|
for instance in instances:
|
|
if instance.podcast: # For both new and edited items
|
|
playlist_id = form.instance.pk
|
|
query = PlaylistItem.objects.filter(
|
|
podcast=instance.podcast
|
|
).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(
|
|
[_('Podcast "{}" is already used in playlist "{}". Each podcast can only be in one playlist.').format(
|
|
instance.podcast.title, playlist_name
|
|
)]
|
|
)
|
|
return
|
|
|
|
# If all validations pass, save the formset
|
|
super().save_formset(request, form, formset, change)
|
|
|
|
|
|
class UserPlaylistAdmin(ModelAdmin):
|
|
list_display = ('user', 'podcast', 'status', 'created_at', 'updated_at')
|
|
list_filter = ('status', 'created_at', 'updated_at')
|
|
search_fields = ('user__username', 'podcast__title')
|
|
autocomplete_fields = ('user', 'podcast')
|
|
|
|
fieldsets = (
|
|
(None, {
|
|
'fields': ('user', 'podcast', 'status')
|
|
}),
|
|
)
|
|
|
|
|
|
project_admin_site.register(PodcastCategory, PodcastCategoryAdmin)
|
|
project_admin_site.register(Podcast, PodcastAdmin)
|
|
project_admin_site.register(PinnedPodcastCollection, PinnedPodcastCollectionAdmin)
|
|
project_admin_site.register(MiddlePodcastCollection, MiddlePodcastCollectionAdmin)
|
|
project_admin_site.register(PodcastPlaylist, PodcastPlaylistAdmin)
|
|
project_admin_site.register(UserPlaylist, UserPlaylistAdmin)
|