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.
 
 

389 lines
13 KiB

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 utils.json_editor_field import JsonEditorWidget
import json
# Import your custom admin site
from utils.admin import dovoodi_admin_site
# Import your models
from ..models import (
BookReference,
BookReferenceImage,
BookAuthor,
BookAttribute
)
# -----------------------------------------------------------------------------
# Custom Forms for JSON Fields
# -----------------------------------------------------------------------------
class BookReferenceAdminForm(forms.ModelForm):
"""Custom form for BookReference with JSON editor widgets"""
class Meta:
model = BookReference
fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Schema for title JSON field
title_schema = {
"type": "array",
"title": "Titles",
"items": {
"type": "object",
"title": "Title",
"properties": {
"language_code": {
"type": "string",
"title": "Language Code",
"enum": ["en", "fa", "ar", "ur", "ru"],
"options": {
"enum_titles": ["English", "Persian", "Arabic", "Urdu", "Russian"]
}
},
"text": {
"type": "string",
"title": "Title Text"
}
},
"required": ["language_code", "text"]
}
}
# Schema for description JSON field
description_schema = {
"type": "array",
"title": "Descriptions",
"items": {
"type": "object",
"title": "Description",
"properties": {
"language_code": {
"type": "string",
"title": "Language Code",
"enum": ["en", "fa", "ar", "ur", "ru"],
"options": {
"enum_titles": ["English", "Persian", "Arabic", "Urdu", "Russian"]
}
},
"text": {
"type": "string",
"title": "Description Text"
}
},
"required": ["language_code", "text"]
}
}
# Schema for language JSON field
language_schema = {
"type": "array",
"title": "Languages",
"items": {
"type": "object",
"title": "Language",
"properties": {
"language_code": {
"type": "string",
"title": "Language Code",
"enum": ["en", "fa", "ar", "ur", "ru"],
"options": {
"enum_titles": ["English", "Persian", "Arabic", "Urdu", "Russian"]
}
},
"text": {
"type": "string",
"title": "Language Text"
}
},
"required": ["language_code", "text"]
}
}
# Schema for publisher JSON field
publisher_schema = {
"type": "array",
"title": "Publishers",
"items": {
"type": "object",
"title": "Publisher",
"properties": {
"language_code": {
"type": "string",
"title": "Language Code",
"enum": ["en", "fa", "ar", "ur", "ru"],
"options": {
"enum_titles": ["English", "Persian", "Arabic", "Urdu", "Russian"]
}
},
"text": {
"type": "string",
"title": "Publisher Text"
}
},
"required": ["language_code", "text"]
}
}
# Apply JSON editor widgets
self.fields['title'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(title_schema),
'title': 'Titles'
})
self.fields['description'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(description_schema),
'title': 'Descriptions'
})
self.fields['language'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(language_schema),
'title': 'Languages'
})
self.fields['publisher'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(publisher_schema),
'title': 'Publishers'
})
class BookAttributeAdminForm(forms.ModelForm):
"""Custom form for BookAttribute with JSON editor widgets"""
class Meta:
model = BookAttribute
fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Schema for title JSON field
title_schema = {
"type": "array",
"title": "Titles",
"items": {
"type": "object",
"title": "Title",
"properties": {
"language_code": {
"type": "string",
"title": "Language Code",
"enum": ["en", "fa", "ar", "ur", "ru"],
"options": {
"enum_titles": ["English", "Persian", "Arabic", "Urdu", "Russian"]
}
},
"text": {
"type": "string",
"title": "Title Text"
}
},
"required": ["language_code", "text"]
}
}
# Schema for value JSON field
value_schema = {
"type": "array",
"title": "Values",
"items": {
"type": "object",
"title": "Value",
"properties": {
"language_code": {
"type": "string",
"title": "Language Code",
"enum": ["en", "fa", "ar", "ur", "ru"],
"options": {
"enum_titles": ["English", "Persian", "Arabic", "Urdu", "Russian"]
}
},
"text": {
"type": "string",
"title": "Value Text"
}
},
"required": ["language_code", "text"]
}
}
# Apply JSON editor widgets
self.fields['title'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(title_schema),
'title': 'Titles'
})
self.fields['value'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(value_schema),
'title': 'Values'
})
# -----------------------------------------------------------------------------
# 1. Inlines
# -----------------------------------------------------------------------------
class BookReferenceImageInline(TabularInline):
"""
Inline for managing Book Images directly inside the BookReference page.
"""
model = BookReferenceImage
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
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')
# 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]
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 '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 BookAuthorAdmin(ModelAdmin):
"""Admin for BookAuthor model"""
list_display = ('get_name_display', 'created_at', 'updated_at')
search_fields = ('name',) # Note: Search works best on exact text matches
readonly_fields = ('created_at', 'updated_at')
# Use filter_horizontal for ManyToMany fields to make selection easier
filter_horizontal = ('book_references',)
@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('text', '-')
return '-'
class BookReferenceImageAdmin(ModelAdmin):
# 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]['text']} - Image {self.order}"
try:
# We use safe navigation to prevent admin crashes if data is missing
book_title = obj.book_reference.title[0]['text']
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('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)