from django.contrib import admin from django import forms from django.utils.translation import gettext_lazy as _ from unfold.admin import ModelAdmin, TabularInline from unfold.decorators import display from unfold.widgets import UnfoldAdminTextInputWidget, UnfoldAdminTextareaWidget, UnfoldAdminExpandableTextareaWidget from utils.multilang_json_widget import MultiLanguageJSONWidget import json # Import your custom admin site from utils.admin import dovoodi_admin_site # Import your models from ..models import ( BookReference, BookReferenceImage, BookAuthor, BookAttribute, BookSubjectArea, BookType ) # ----------------------------------------------------------------------------- # Custom Forms for JSON Fields # ----------------------------------------------------------------------------- class BookReferenceAdminForm(forms.ModelForm): """Custom form for BookReference with JSON editor widgets""" class Meta: model = BookReference fields = '__all__' widgets = { 'title': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextInputWidget), 'description': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextareaWidget), 'language': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextInputWidget), 'publisher': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextInputWidget), } class BookAttributeAdminForm(forms.ModelForm): """Custom form for BookAttribute with JSON editor widgets""" class Meta: model = BookAttribute fields = '__all__' widgets = { 'title': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextInputWidget), 'value': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextInputWidget), } class BookAuthorAdminForm(forms.ModelForm): """Custom form for BookAuthor with JSON editor widgets""" class Meta: model = BookAuthor fields = '__all__' widgets = { 'name': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextInputWidget), } class BookReferenceImageAdminForm(forms.ModelForm): """Custom form for BookReferenceImage with JSON editor widgets""" class Meta: model = BookReferenceImage fields = '__all__' widgets = { 'description': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextareaWidget), } class BookSubjectAreaAdminForm(forms.ModelForm): """Custom form for BookSubjectArea with JSON editor widgets""" class Meta: model = BookSubjectArea fields = '__all__' widgets = { 'title': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextInputWidget), } class BookTypeAdminForm(forms.ModelForm): """Custom form for BookType with JSON editor widgets""" class Meta: model = BookType fields = '__all__' widgets = { 'title': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextInputWidget), } # ----------------------------------------------------------------------------- # 1. Inlines # ----------------------------------------------------------------------------- class BookReferenceImageInline(TabularInline): """ Inline for managing Book Images directly inside the BookReference page. """ model = BookReferenceImage form = BookReferenceImageAdminForm extra = 0 fields = ('image', 'order', 'description') readonly_fields = ('created_at',) class BookAttributeInline(TabularInline): """ Inline for managing Book Attributes (Key-Value pairs) inside BookReference. """ model = BookAttribute form = BookAttributeAdminForm extra = 0 fields = ('title', 'value') readonly_fields = ('created_at',) # ----------------------------------------------------------------------------- # 2. Main Admins # ----------------------------------------------------------------------------- class BookReferenceAdmin(ModelAdmin): """Admin for BookReference model""" form = BookReferenceAdminForm # Use custom methods for JSON fields to show readable text list_display = ( 'get_title_display', 'slug', 'get_publisher_display', 'year_of_publication', 'rate', 'created_at' ) list_filter = ('year_of_publication', 'rate', 'created_at', 'type') # Searching by JSON fields via string is limited in Django, # so we focus on standard fields search_fields = ('isbn', 'slug', 'volume') readonly_fields = ('created_at', 'updated_at') # Add the inlines to manage images and attributes on the same page inlines = [BookReferenceImageInline, BookAttributeInline] # Use filter_horizontal for ManyToMany fields to make selection easier filter_horizontal = ('subject_area',) fieldsets = ( (_('Basic Info'), { 'fields': ('title', 'description', 'slug', 'language') }), (_('Publication Info'), { 'fields': ('publisher', 'isbn', 'year_of_publication', 'number_page', 'volume') }), (_('Rating & Stats'), { 'fields': ('rate', 'created_at', 'updated_at') }), ) # --- Custom Display Methods --- @display(description=_('Title'), ordering='title') def get_title_display(self, obj): return self._extract_first_text(obj.title) @display(description=_('Publisher')) def get_publisher_display(self, obj): return self._extract_first_text(obj.publisher) def _extract_first_text(self, json_data): """Helper to safely extract the first 'title' from a JSON list""" if json_data and isinstance(json_data, list) and len(json_data) > 0: first_item = json_data[0] if isinstance(first_item, dict): return first_item.get('title', '-') return '-' class BookAuthorAdmin(ModelAdmin): """Admin for BookAuthor model""" form = BookAuthorAdminForm list_display = ('get_name_display', 'birth_year_hijri', 'death_year_hijri', 'birth_year_miladi', 'death_year_miladi', 'created_at', 'updated_at') search_fields = ('name',) # Note: Search works best on exact text matches readonly_fields = ('created_at', 'updated_at') list_filter = ('birth_year_hijri', 'death_year_hijri', 'birth_year_miladi', 'death_year_miladi', 'created_at') # Use filter_horizontal for ManyToMany fields to make selection easier filter_horizontal = ('book_references',) fieldsets = ( (_('Basic Info'), { 'fields': ('name',) }), (_('Dates (Hijri)'), { 'fields': ('birth_year_hijri', 'death_year_hijri') }), (_('Dates (Miladi)'), { 'fields': ('birth_year_miladi', 'death_year_miladi') }), (_('References'), { 'fields': ('book_references',) }), (_('Timestamps'), { 'fields': ('created_at', 'updated_at'), 'classes': ('collapse',) }), ) @display(description=_('Name'), ordering='name') def get_name_display(self, obj): if obj.name and isinstance(obj.name, list) and len(obj.name) > 0: first = obj.name[0] if isinstance(first, dict): return first.get('title', '-') return '-' class BookReferenceImageAdmin(ModelAdmin): """Admin for BookReferenceImage model""" form = BookReferenceImageAdminForm # Display the custom string, plus the raw order and book link for convenience list_display = ("display_name", "order", "book_reference") # optimize database queries since we are accessing foreign key data (book_reference) list_select_related = ("book_reference",) def display_name(self, obj): # Implements: f"{self.book_reference.title[0]['title']} - Image {self.order}" try: # We use safe navigation to prevent admin crashes if data is missing book_title = obj.book_reference.title[0]['title'] return f"{book_title} - Image {obj.order}" except (AttributeError, IndexError, KeyError, TypeError): # Fallback if the title structure isn't exactly as expected return f"Unknown Book - Image {obj.order}" # Sets the column header name in the admin panel display_name.short_description = "Image Reference" class BookAttributeAdmin(ModelAdmin): """ Admin for managing Attributes independently. Useful if you want to see all attributes across all books. """ form = BookAttributeAdminForm list_display = ('get_title_display', 'get_value_display', 'get_book_display', 'created_at') list_filter = ('created_at',) search_fields = ('book_reference__slug',) @display(description=_('Title'), ordering='title') def get_title_display(self, obj): return self._extract_first_text(obj.title) @display(description=_('Value'), ordering='value') def get_value_display(self, obj): return self._extract_first_text(obj.value) @display(description=_('Book Reference'), ordering='book_reference') def get_book_display(self, obj): if obj.book_reference: return self._extract_first_text(obj.book_reference.title) return '-' def _extract_first_text(self, json_data): if json_data and isinstance(json_data, list) and len(json_data) > 0: first = json_data[0] if isinstance(first, dict): return first.get('title', '-') return '-' # class BookSubjectAreaAdmin(ModelAdmin): # """Admin for BookSubjectArea model""" # form = BookSubjectAreaAdminForm # list_display = ('get_title_display', 'created_at', 'updated_at') # search_fields = ('title',) # readonly_fields = ('created_at', 'updated_at') # @display(description=_('Title'), ordering='title') # def get_title_display(self, obj): # return self._extract_first_text(obj.title) # def _extract_first_text(self, json_data): # """Helper to safely extract the first 'text' from a JSON list""" # if json_data and isinstance(json_data, list) and len(json_data) > 0: # first_item = json_data[0] # if isinstance(first_item, dict): # return first_item.get('text', '-') # return '-' # class BookTypeAdmin(ModelAdmin): # """Admin for BookType model""" # form = BookTypeAdminForm # list_display = ('get_title_display', 'created_at', 'updated_at') # search_fields = ('title',) # readonly_fields = ('created_at', 'updated_at') # @display(description=_('Title'), ordering='title') # def get_title_display(self, obj): # return self._extract_first_text(obj.title) # def _extract_first_text(self, json_data): # """Helper to safely extract the first 'text' from a JSON list""" # if json_data and isinstance(json_data, list) and len(json_data) > 0: # first_item = json_data[0] # if isinstance(first_item, dict): # return first_item.get('text', '-') # return '-' # ----------------------------------------------------------------------------- # 3. Registration # ----------------------------------------------------------------------------- dovoodi_admin_site.register(BookReference, BookReferenceAdmin) dovoodi_admin_site.register(BookAuthor, BookAuthorAdmin) dovoodi_admin_site.register(BookAttribute, BookAttributeAdmin) dovoodi_admin_site.register(BookReferenceImage, BookReferenceImageAdmin) # dovoodi_admin_site.register(BookSubjectArea, BookSubjectAreaAdmin) # dovoodi_admin_site.register(BookType, BookTypeAdmin)