diff --git a/apps/api/urls.py b/apps/api/urls.py index d694f8e..24ac722 100644 --- a/apps/api/urls.py +++ b/apps/api/urls.py @@ -1,5 +1,5 @@ -from django.urls import path +from django.urls import path,include from .views import HomeView, CountryView, CommentListAPIView from .views.api_views import AppVersionListAPIView diff --git a/apps/hadis/admin/reference.py b/apps/hadis/admin/reference.py new file mode 100644 index 0000000..8dd9232 --- /dev/null +++ b/apps/hadis/admin/reference.py @@ -0,0 +1,156 @@ +from django.contrib import admin +from django.utils.translation import gettext_lazy as _ +from unfold.admin import ModelAdmin, TabularInline +from unfold.decorators import display + +# Import your custom admin site +from utils.admin import project_admin_site + +# Import your models +from ..models import ( + BookReference, + BookReferenceImage, + BookAuthor, + BookAttribute +) + +# ----------------------------------------------------------------------------- +# 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""" + + # 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 BookAttributeAdmin(ModelAdmin): + """ + Admin for managing Attributes independently. + Useful if you want to see all attributes across all books. + """ + 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 +# ----------------------------------------------------------------------------- + +project_admin_site.register(BookReference, BookReferenceAdmin) +project_admin_site.register(BookAuthor, BookAuthorAdmin) +project_admin_site.register(BookAttribute, BookAttributeAdmin) \ No newline at end of file diff --git a/apps/hadis/admin/transmitter.py b/apps/hadis/admin/transmitter.py index e7ccc21..f0fc184 100644 --- a/apps/hadis/admin/transmitter.py +++ b/apps/hadis/admin/transmitter.py @@ -17,7 +17,7 @@ class HadisTransmitterInline(TabularInline): class TransmittersAdmin(ModelAdmin): """Admin for Transmitters model""" - list_display = ('full_name', 'birth_year_hijri', 'death_year_hijri') + list_display = ('get_full_name_display', 'birth_year_hijri', 'death_year_hijri') list_filter = ('birth_year_hijri', 'death_year_hijri') search_fields = ('full_name', 'description') readonly_fields = ('created_at', 'updated_at') @@ -28,7 +28,7 @@ class TransmittersAdmin(ModelAdmin): 'fields': ('full_name', 'birth_year_hijri', 'death_year_hijri') }), (_('Additional Information'), { - 'fields': ('description', 'thumbnail'), + 'fields': ('description',), 'classes': ('collapse',) }), (_('Timestamps'), { @@ -36,6 +36,17 @@ class TransmittersAdmin(ModelAdmin): 'classes': ('collapse',) }), ) + @display(description=_('Full Name'), ordering='full_name') + def get_full_name_display(self, obj): + """ + Parses the JSON full_name and returns the first item's text. + """ + if obj.full_name and isinstance(obj.full_name, list) and len(obj.full_name) > 0: + # Safely get the first item + first_item = obj.full_name[0] + if isinstance(first_item, dict): + return first_item.get('text', '-') + return '-' class HadisTransmitterAdmin(ModelAdmin): diff --git a/apps/hadis/models/hadis.py b/apps/hadis/models/hadis.py index 454ec77..625f61e 100644 --- a/apps/hadis/models/hadis.py +++ b/apps/hadis/models/hadis.py @@ -1,3 +1,4 @@ +from typing import Optional from django.db import models from django.db.models import F, ForeignKey from django.utils.translation import gettext_lazy as _ @@ -66,7 +67,7 @@ class HadisCollection(models.Model): if isinstance(tr, dict) and tr.get('language_code') == lang: return tr.get('text', '') - for tr in self.translation: + for tr in self.summary: if isinstance(tr, dict) and tr.get('language_code') == 'en': return tr.get('text', '') return None @@ -189,7 +190,7 @@ class Hadis(models.Model): def __str__(self): return f"{self.number} - {self.title}" if self.title else f"Hadis {self.number}" - def _get_json_field(self, field_name: str, lang: str | None = None, fallback: str = "en"): + def _get_json_field(self, field_name: str, lang: Optional[str]=None , fallback: str = "en"): """ Generic getter for JSONField in our [{text, language_code}] format. Usage: self._get_json_field('title', 'fa') diff --git a/apps/hadis/models/reference.py b/apps/hadis/models/reference.py index 8641a91..4ffd12f 100644 --- a/apps/hadis/models/reference.py +++ b/apps/hadis/models/reference.py @@ -1,6 +1,7 @@ from django.db import models from django.utils.translation import gettext_lazy as _ from django.utils.text import slugify +from typing import Optional class BookReference(models.Model): @@ -35,9 +36,9 @@ class BookReference(models.Model): ordering = ('-created_at',) def __str__(self): - return self.title + return self.title[0]['text'] - def _get_json_field(self, field_name: str, lang: str | None = None, fallback: str = "en"): + def _get_json_field(self, field_name: str, lang: Optional[str]=None , fallback: str = "en"): """ Generic getter for JSONField in our [{text, language_code}] format. Usage: self._get_json_field('title', 'fa') @@ -81,7 +82,7 @@ class BookReference(models.Model): def save(self, *args, **kwargs): if not self.slug: - base_slug = slugify(self.title, allow_unicode=True) + base_slug = slugify(self.title[0]['text'], allow_unicode=True) slug = base_slug counter = 1 while BookReference.objects.filter(slug=slug).exclude(pk=self.pk).exists(): @@ -129,7 +130,7 @@ class BookReferenceImage(models.Model): return None def __str__(self): - return f"{self.book_reference.title} - Image {self.order}" + return f"{self.book_reference.title[0]['text']} - Image {self.order}" class BookAuthor(models.Model): @@ -153,7 +154,7 @@ class BookAuthor(models.Model): ordering = ['name'] def __str__(self): - return self.name + return self.name[0]['text'] def get_name(self,lang): """ @@ -196,7 +197,7 @@ class BookAttribute(models.Model): ordering = ['title'] def __str__(self): - return f"{self.title}: {self.value}" + return f"{self.title[0]['text']}: {self.value[0]['text']}" def get_title(self,lang): """ diff --git a/apps/hadis/models/transmitter.py b/apps/hadis/models/transmitter.py index 83a4571..f637d63 100644 --- a/apps/hadis/models/transmitter.py +++ b/apps/hadis/models/transmitter.py @@ -4,6 +4,7 @@ from django.db import models from django.utils.translation import gettext_lazy as _ from filer.fields.image import FilerImageField from django.utils.text import slugify +from typing import Optional @@ -151,7 +152,8 @@ class Transmitters(models.Model): def save(self, *args, **kwargs): if not self.slug: - base_slug = slugify(self.full_name, allow_unicode=True) + name = self.full_name[0] + base_slug = slugify(name.get('text'), allow_unicode=True) slug = base_slug counter = 1 while Transmitters.objects.filter(slug=slug).exclude(pk=self.pk).exists(): @@ -160,7 +162,7 @@ class Transmitters(models.Model): self.slug = slug super().save(*args, **kwargs) - def _get_json_field(self, field_name: str, lang: str | None = None, fallback: str = "en"): + def _get_json_field(self, field_name: str, lang: Optional[str]=None , fallback: str = "en"): """ Generic getter for JSONField in our [{text, language_code}] format. Usage: self._get_json_field('title', 'fa') @@ -211,7 +213,8 @@ class Transmitters(models.Model): def __str__(self): - return self.full_name + name = self.full_name[0] + return name.get('text') diff --git a/config/urls.py b/config/urls.py index fb9ec04..36633a3 100644 --- a/config/urls.py +++ b/config/urls.py @@ -102,6 +102,7 @@ urlpatterns = [ # path('test/', include('apps.api.urls')) path('oneapi-translation/', oneapi_translate), path('admin/filer/', include('filer.urls')), + path('filer/', include('filer.urls')), ] # Protected swagger URL patterns @@ -122,6 +123,7 @@ urlpatterns+= i18n_patterns( path('docs/', CustomAPIDocumentationView.as_view(), name='docs-index'), *swagger_urlpatterns, path('admin/filer/', include('filer.urls')), + path('filer/', include('filer.urls')), ) if settings.DEBUG: