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 ajaxdatatable.admin import AjaxDatatable from unfold.admin import ModelAdmin from django.contrib.admin import SimpleListFilter from unfold.decorators import display, action from django import forms from utils.admin import project_admin_site from apps.library.models import * class BookCollectionAdmin(ModelAdmin): list_display = ('title', 'display_position', 'status', 'order') list_filter = ('status', 'display_position') search_fields = ('title',) project_admin_site.register(BookCollection, BookCollectionAdmin) class BookAdminForm(forms.ModelForm): class Meta: model = Book fields = '__all__' def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Make thumbnail field required in the form self.fields['thumbnail'].required = True class BookAdmin(ModelAdmin): form = BookAdminForm list_display = ('title', 'display_categories', 'display_collections', 'status', 'view_count', 'created_at') list_filter = ('status', 'pin', 'file_type', 'created_at', 'updated_at') search_fields = ('title', 'slug', 'summary', 'description') # autocomplete_fields = ('categories', 'collections', ) list_filter_submit = True warn_unsaved_form = True change_form_show_cancel_button = True save_as = True @display(description=_('Categories')) def display_categories(self, obj): categories = obj.categories.all() if categories: return ', '.join([category.title for category in categories]) return '-' @display(description=_('Collections')) def display_collections(self, obj): collections = obj.collections.all() if collections: return ', '.join([collection.title for collection in collections]) return '-' fieldsets = ( (None, { 'fields': ('title', 'summary', 'description', 'thumbnail', 'pages_count') }), (_('Status'), { 'fields': ('status', 'pin') }), (_('File Information'), { 'fields': ('file_type', 'book_file') }), (_('Relations'), { 'fields': ('categories', 'collections') }), (_('Statistics'), { 'fields': ('view_count', 'download_count') }), ) class BookCollectionAdminBase(ModelAdmin): """Base admin class for all book collection types""" list_display = ('get_title', 'status', 'order', 'count_books') list_filter = ('status', 'order') search_fields = ('title',) autocomplete_fields = ('books',) ordering = ('order',) list_filter_submit = True warn_unsaved_form = True change_form_show_cancel_button = True fieldsets = ( (None, { 'fields': ('title', 'summary', 'status', 'order') }), (_('Books'), { 'fields': ('books',) }), ) exclude = ('display_position',) @display(description=_('Title')) def get_title(self, obj): return str(obj.title) # @display(description=_("Status"), ordering="status") # def status_badge(self, obj): # if obj.status: # return format_html( # '{}', # _("Active") # ) # return format_html( # '{}', # _("Inactive") # ) @display(description=_('Number of Books')) def count_books(self, obj): count = obj.books.count() if count > 0: url = reverse('admin:library_book_changelist') + f'?collections__id__exact={obj.id}' return format_html('{}', url, count) return count class PinnedBookCollectionAdmin(BookCollectionAdminBase): """Admin for pinned book collections only""" list_before_template = "admin/library/pinnedbookcollection/change_list_before.html" fieldsets = ( (None, { 'fields': ('title', 'summary', 'status', 'order', 'pin_top') }), (_('Books'), { 'fields': ('books',) }), ) def get_queryset(self, request): # Only show pinned collections return super().get_queryset(request).filter(display_position=BookCollection.DisplayPosition.PINNED) def save_model(self, request, obj, form, change): # Ensure the display_position is always set to PINNED obj.display_position = BookCollection.DisplayPosition.PINNED super().save_model(request, obj, form, change) class MiddleBookCollectionAdmin(BookCollectionAdminBase): """Admin for middle section book collections only""" def get_queryset(self, request): # Only show middle section collections return super().get_queryset(request).filter(display_position=BookCollection.DisplayPosition.MIDDLE) # def has_add_permission(self, request): # # Check if a middle collection already exists # exists = BookCollection.objects.filter(display_position=BookCollection.DisplayPosition.MIDDLE).exists() # # Only allow adding if no middle collection exists # return not exists # def has_delete_permission(self, request, obj=None): # # Prevent deletion of the middle collection # return False def save_model(self, request, obj, form, change): # Ensure the display_position is always set to MIDDLE obj.display_position = BookCollection.DisplayPosition.MIDDLE super().save_model(request, obj, form, change) class CategoryAdmin(ModelAdmin): list_display = ('title', 'slug', 'status_badge', 'count_books', 'created_at') list_filter = ('status', 'created_at', 'updated_at') search_fields = ('title', 'slug') list_filter_submit = True warn_unsaved_form = True change_form_show_cancel_button = True # Custom actions actions_list = ['mark_as_active', 'mark_as_inactive'] actions_row = ['toggle_status'] fieldsets = ( (None, { 'fields': ('title', 'slug', 'status') }), (_('Timestamps'), { 'fields': ('created_at', 'updated_at'), 'classes': ('collapse',), }), ) readonly_fields = ('created_at', 'updated_at') @display(description=_("Status"), ordering="status") def status_badge(self, obj): if obj.status: return format_html( '{}', _("Active") ) return format_html( '{}', _("Inactive") ) @display(description=_('Number of Books')) def count_books(self, obj): count = obj.books_count if count > 0: url = reverse('admin:library_book_changelist') + f'?categories__id__exact={obj.id}' return format_html('{}', url, count) return count @action(description=_("Mark selected categories as active")) def mark_as_active(self, request, queryset): updated = queryset.update(status=True) self.message_user(request, _("%(count)d categories were successfully marked as active.") % {"count": updated}) @action(description=_("Mark selected categories as inactive")) def mark_as_inactive(self, request, queryset): updated = queryset.update(status=False) self.message_user(request, _("%(count)d categories were successfully marked as inactive.") % {"count": updated}) @action(description=_("Toggle status")) def toggle_status(self, request, obj): obj.status = not obj.status obj.save(update_fields=["status"]) status_text = _("active") if obj.status else _("inactive") self.message_user(request, _("Category '%(title)s' is now %(status)s.") % {"title": obj.title, "status": status_text}) # Register models with the custom admin site project_admin_site.register(Book, BookAdmin) project_admin_site.register(PinnedBookCollection, PinnedBookCollectionAdmin) project_admin_site.register(MiddleBookCollection, MiddleBookCollectionAdmin) project_admin_site.register(Category, CategoryAdmin)