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