Browse Source

hadis multilanguage fields refactor

hadis data synced with projects language map with migrate_json_key script command

all the views , serializers , admin methods and models updated with this data schema update .
master
Mohsen Taba 14 hours ago
parent
commit
6c13b951c0
  1. 139
      apps/hadis/admin/category.py
  2. 614
      apps/hadis/admin/hadis.py
  3. 349
      apps/hadis/admin/reference.py
  4. 422
      apps/hadis/admin/transmitter.py
  5. 69
      apps/hadis/management/commands/migrate_json_keys.py
  6. 22
      apps/hadis/models/category.py
  7. 46
      apps/hadis/models/hadis.py
  8. 44
      apps/hadis/models/reference.py
  9. 60
      apps/hadis/models/transmitter.py
  10. 16
      apps/hadis/serializers/category.py
  11. 6
      apps/hadis/views/category.py

139
apps/hadis/admin/category.py

@ -5,7 +5,8 @@ from django.utils.html import format_html
from unfold.admin import ModelAdmin from unfold.admin import ModelAdmin
from unfold.decorators import display, action from unfold.decorators import display, action
from mptt.admin import DraggableMPTTAdmin from mptt.admin import DraggableMPTTAdmin
from utils.json_editor_field import JsonEditorWidget
from unfold.widgets import UnfoldAdminTextInputWidget, UnfoldAdminTextareaWidget, UnfoldAdminExpandableTextareaWidget
from utils.multilang_json_widget import MultiLanguageJSONWidget
import json import json
from utils.admin import dovoodi_admin_site from utils.admin import dovoodi_admin_site
@ -19,71 +20,11 @@ class HadisSectAdminForm(forms.ModelForm):
class Meta: class Meta:
model = HadisSect model = HadisSect
fields = '__all__' 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"]
}
widgets = {
'title': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextInputWidget),
'description': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextareaWidget),
} }
# 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'
})
class HadisCategoryAdminForm(forms.ModelForm): class HadisCategoryAdminForm(forms.ModelForm):
"""Custom form for HadisCategory with JSON editor widgets""" """Custom form for HadisCategory with JSON editor widgets"""
@ -91,71 +32,11 @@ class HadisCategoryAdminForm(forms.ModelForm):
class Meta: class Meta:
model = HadisCategory model = HadisCategory
fields = '__all__' 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"]
}
widgets = {
'title': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextInputWidget),
'description': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextareaWidget),
} }
# 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'
})
class HadisSectAdmin(ModelAdmin): class HadisSectAdmin(ModelAdmin):
"""Admin for HadisSect model""" """Admin for HadisSect model"""
@ -174,7 +55,7 @@ class HadisSectAdmin(ModelAdmin):
def display_title(self, obj): def display_title(self, obj):
"""Extracts text from the title JSON list""" """Extracts text from the title JSON list"""
try: try:
return obj.title[0]['text']
return obj.title[0]['title']
except (IndexError, KeyError, TypeError, AttributeError): except (IndexError, KeyError, TypeError, AttributeError):
return "No Title" return "No Title"
@ -205,7 +86,7 @@ class HadisCategoryAdmin(ModelAdmin):
"""Display indented title for tree structure using JSON text""" """Display indented title for tree structure using JSON text"""
try: try:
# Extract text from the first element of the title list # Extract text from the first element of the title list
title_text = instance.title[0]['text']
title_text = instance.title[0]['title']
except (IndexError, KeyError, TypeError, AttributeError): except (IndexError, KeyError, TypeError, AttributeError):
title_text = "No Title" title_text = "No Title"

614
apps/hadis/admin/hadis.py

@ -4,10 +4,12 @@ from django.utils.translation import gettext_lazy as _
from unfold.admin import ModelAdmin, TabularInline from unfold.admin import ModelAdmin, TabularInline
from unfold.contrib.forms.widgets import WysiwygWidget from unfold.contrib.forms.widgets import WysiwygWidget
from unfold.decorators import display, action from unfold.decorators import display, action
from unfold.widgets import UnfoldAdminTextInputWidget, UnfoldAdminTextareaWidget
from utils.multilang_json_widget import MultiLanguageJSONWidget, MultiLanguageAddressWidget, LinksJSONWidget
from utils.json_editor_field import JsonEditorWidget from utils.json_editor_field import JsonEditorWidget
import json import json
from utils.admin import dovoodi_admin_site,dovoodi_admin_site
from utils.admin import dovoodi_admin_site
from ..models import ( from ..models import (
Hadis, HadisReference, HadisTag, HadisStatus, ReferenceImage, Hadis, HadisReference, HadisTag, HadisStatus, ReferenceImage,
HadisCollection, HadisInCollection, HadisCorrection HadisCollection, HadisInCollection, HadisCorrection
@ -22,189 +24,18 @@ class HadisAdminForm(forms.ModelForm):
model = Hadis model = Hadis
fields = '__all__' fields = '__all__'
widgets = { widgets = {
'explanation': WysiwygWidget(),
'title': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextInputWidget),
'title_narrator': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextInputWidget),
'description': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextareaWidget),
'translation': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextareaWidget),
'address': MultiLanguageAddressWidget(),
'links': LinksJSONWidget(),
} }
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
# Schema for translation JSON field
translation_schema = {
"type": "array",
"title": "Translations",
"items": {
"type": "object",
"title": "Translation",
"properties": {
"language_code": {
"type": "string",
"title": "Language Code",
"enum": ["en", "fa", "ar", "ur"],
"options": {
"enum_titles": ["English", "Persian", "Arabic", "Urdu"]
}
},
"text": { # <‑‑ use text, not title
"type": "string",
"title": "Translation Text"
}
},
"required": ["language_code", "text"] # <‑‑ update required key
}
}
# Schema for links JSON field (array of objects with title and link)
links_schema = {
"type": "array",
"title": "Links",
"items": {
"type": "object",
"title": "Link",
"properties": {
"title": {
"type": "string",
"title": "Link Title"
},
"link": {
"type": "string",
"title": "URL",
"format": "uri"
}
},
"required": ["title", "link"]
}
}
# Schema for title_narrator JSON field
title_narrator_schema = {
"type": "array",
"title": "Title Narrators",
"items": {
"type": "object",
"title": "Title Narrator",
"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 Narrator Text"
}
},
"required": ["language_code", "text"]
}
}
# 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 hadis_status_text JSON field
hadis_status_text_schema = {
"type": "array",
"title": "Status Texts",
"items": {
"type": "object",
"title": "Status Text",
"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": "Status Text"
}
},
"required": ["language_code", "text"]
}
}
# Schema for address JSON field (text is an array of strings)
address_schema = {
"type": "array",
"title": "Addresses",
"items": {
"type": "object",
"title": "Address",
"properties": {
"language_code": {
"type": "string",
"title": "Language Code",
"enum": ["en", "fa", "ar", "ur", "ru"],
"options": {
"enum_titles": ["English", "Persian", "Arabic", "Urdu", "Russian"]
}
},
"text": {
"type": "array",
"title": "Address Parts",
"items": {
"type": "string",
"title": "Address Part"
}
}
},
"required": ["language_code", "text"]
}
}
# Schema for explanation JSON field (text is an array of objects with title and detail)
# Schema for explanation JSON field (title is an array of objects with title and detail)
explanation_schema = { explanation_schema = {
"type": "array", "type": "array",
"title": "Explanations", "title": "Explanations",
@ -220,7 +51,7 @@ class HadisAdminForm(forms.ModelForm):
"enum_titles": ["English", "Persian", "Arabic", "Urdu", "Russian"] "enum_titles": ["English", "Persian", "Arabic", "Urdu", "Russian"]
} }
}, },
"text": {
"title": {
"type": "array", "type": "array",
"title": "Explanation Items", "title": "Explanation Items",
"items": { "items": {
@ -241,111 +72,15 @@ class HadisAdminForm(forms.ModelForm):
} }
} }
}, },
"required": ["language_code", "text"]
"required": ["language_code", "title"]
} }
} }
# Schema for explanations JSON field (array of objects with title and description)
# explanations_schema = {
# "type": "array",
# "title": "Explanations",
# "items": {
# "type": "object",
# "title": "Explanation",
# "properties": {
# "language_code": {
# "type": "string",
# "title": "Language Code",
# "enum": ["en", "fa", "ar", "ur", "ru"],
# "options": {
# "enum_titles": ["English", "Persian", "Arabic", "Urdu", "Russian"]
# }
# },
# "title": {
# "type": "string",
# "title": "Title"
# },
# "description": {
# "type": "string",
# "title": "Description"
# }
# },
# "required": ["language_code", "title", "description"]
# }
# }
# Schema for address_details JSON field (array of objects with text and priority)
# address_details_schema = {
# "type": "array",
# "title": "Address Details",
# "items": {
# "type": "object",
# "title": "Address Detail",
# "properties": {
# "text": {
# "type": "string",
# "title": "Address Text"
# },
# "priority": {
# "type": "integer",
# "title": "Priority",
# "minimum": 0
# }
# },
# "required": ["text", "priority"]
# }
# }
# Apply JSON editor widgets
self.fields['translation'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(translation_schema),
'title': 'Translations'
})
self.fields['links'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(links_schema),
'title': 'Links'
})
self.fields['title_narrator'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(title_narrator_schema),
'title': 'Title Narrators'
})
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['hadis_status_text'].widget = JsonEditorWidget(attrs={
# 'schema': json.dumps(hadis_status_text_schema),
# 'title': 'Status Texts'
# })
self.fields['address'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(address_schema),
'title': 'Addresses'
})
self.fields['explanation'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(explanation_schema),
'title': 'Explanations'
})
# self.fields['explanations'].widget = JsonEditorWidget(attrs={
# 'schema': json.dumps(explanations_schema),
# self.fields['explanation'].widget = JsonEditorWidget(attrs={
# 'schema': json.dumps(explanation_schema),
# 'title': 'Explanations' # 'title': 'Explanations'
# }) # })
# self.fields['address_details'].widget = JsonEditorWidget(attrs={
# 'schema': json.dumps(address_details_schema),
# 'title': 'Address Details'
# })
# Custom Forms for JSON Fields # Custom Forms for JSON Fields
@ -355,71 +90,11 @@ class HadisCollectionAdminForm(forms.ModelForm):
class Meta: class Meta:
model = HadisCollection model = HadisCollection
fields = '__all__' 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 summary JSON field
summary_schema = {
"type": "array",
"title": "Summaries",
"items": {
"type": "object",
"title": "Summary",
"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": "Summary Text"
}
},
"required": ["language_code", "text"]
}
widgets = {
'title': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextInputWidget),
'summary': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextareaWidget),
} }
# Apply JSON editor widgets
self.fields['title'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(title_schema),
'title': 'Titles'
})
self.fields['summary'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(summary_schema),
'title': 'Summaries'
})
class HadisTagAdminForm(forms.ModelForm): class HadisTagAdminForm(forms.ModelForm):
"""Custom form for HadisTag with JSON editor widgets""" """Custom form for HadisTag with JSON editor widgets"""
@ -427,41 +102,10 @@ class HadisTagAdminForm(forms.ModelForm):
class Meta: class Meta:
model = HadisTag model = HadisTag
fields = '__all__' 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"]
}
widgets = {
'title': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextInputWidget),
} }
# Apply JSON editor widgets
self.fields['title'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(title_schema),
'title': 'Titles'
})
class HadisStatusAdminForm(forms.ModelForm): class HadisStatusAdminForm(forms.ModelForm):
"""Custom form for HadisStatus with JSON editor widgets""" """Custom form for HadisStatus with JSON editor widgets"""
@ -469,71 +113,11 @@ class HadisStatusAdminForm(forms.ModelForm):
class Meta: class Meta:
model = HadisStatus model = HadisStatus
fields = '__all__' 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"]
}
widgets = {
'title': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextInputWidget),
'description': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextareaWidget),
} }
# 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'
})
class HadisReferenceAdminForm(forms.ModelForm): class HadisReferenceAdminForm(forms.ModelForm):
"""Custom form for HadisReference with JSON editor widgets""" """Custom form for HadisReference with JSON editor widgets"""
@ -541,41 +125,10 @@ class HadisReferenceAdminForm(forms.ModelForm):
class Meta: class Meta:
model = HadisReference model = HadisReference
fields = '__all__' fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# 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"]
}
widgets = {
'description': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextareaWidget),
} }
# Apply JSON editor widgets
self.fields['description'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(description_schema),
'title': 'Descriptions'
})
# Inline Admin Classes # Inline Admin Classes
class ReferenceImageInline(TabularInline): class ReferenceImageInline(TabularInline):
@ -619,11 +172,11 @@ class HadisTagAdmin(ModelAdmin):
return self._extract_first_text(obj.title) return self._extract_first_text(obj.title)
def _extract_first_text(self, json_data): def _extract_first_text(self, json_data):
"""Helper to safely extract the first 'text' from a JSON list"""
"""Helper to safely extract the first 'title' from a JSON list"""
if json_data and isinstance(json_data, list) and len(json_data) > 0: if json_data and isinstance(json_data, list) and len(json_data) > 0:
first_item = json_data[0] first_item = json_data[0]
if isinstance(first_item, dict): if isinstance(first_item, dict):
return first_item.get('text', '-')
return first_item.get('title', '-')
return '-' return '-'
@ -651,11 +204,11 @@ class HadisStatusAdmin(ModelAdmin):
return self._extract_first_text(obj.description) return self._extract_first_text(obj.description)
def _extract_first_text(self, json_data): def _extract_first_text(self, json_data):
"""Helper to safely extract the first 'text' from a JSON list"""
"""Helper to safely extract the first 'title' from a JSON list"""
if json_data and isinstance(json_data, list) and len(json_data) > 0: if json_data and isinstance(json_data, list) and len(json_data) > 0:
first_item = json_data[0] first_item = json_data[0]
if isinstance(first_item, dict): if isinstance(first_item, dict):
return first_item.get('text', '-')
return first_item.get('title', '-')
return '-' return '-'
@ -675,7 +228,7 @@ class HadisAdmin(ModelAdmin):
'fields': ('category', 'number', 'title', 'title_narrator', 'status', 'slug') 'fields': ('category', 'number', 'title', 'title_narrator', 'status', 'slug')
}), }),
(_('Content'), { (_('Content'), {
'fields': ('text', 'translation', 'explanation','description')
'fields': ('text', 'translation','description')
}), }),
(_('Status & Classification'), { (_('Status & Classification'), {
'fields': ('hadis_status','tags') 'fields': ('hadis_status','tags')
@ -695,11 +248,11 @@ class HadisAdmin(ModelAdmin):
return self._extract_first_text(obj.title) return self._extract_first_text(obj.title)
def _extract_first_text(self, json_data): def _extract_first_text(self, json_data):
"""Helper to safely extract the first 'text' from a JSON list"""
"""Helper to safely extract the first 'title' from a JSON list"""
if json_data and isinstance(json_data, list) and len(json_data) > 0: if json_data and isinstance(json_data, list) and len(json_data) > 0:
first_item = json_data[0] first_item = json_data[0]
if isinstance(first_item, dict): if isinstance(first_item, dict):
return first_item.get('text', '-')
return first_item.get('title', '-')
return '-' return '-'
@ -770,11 +323,11 @@ class HadisCollectionAdmin(ModelAdmin):
return self._extract_first_text(obj.title) return self._extract_first_text(obj.title)
def _extract_first_text(self, json_data): def _extract_first_text(self, json_data):
"""Helper to safely extract the first 'text' from a JSON list"""
"""Helper to safely extract the first 'title' from a JSON list"""
if json_data and isinstance(json_data, list) and len(json_data) > 0: if json_data and isinstance(json_data, list) and len(json_data) > 0:
first_item = json_data[0] first_item = json_data[0]
if isinstance(first_item, dict): if isinstance(first_item, dict):
return first_item.get('text', '-')
return first_item.get('title', '-')
return '-' return '-'
@display(description=_("Collection"), header=True) @display(description=_("Collection"), header=True)
@ -824,101 +377,12 @@ class HadisCorrectionAdminForm(forms.ModelForm):
class Meta: class Meta:
model = HadisCorrection model = HadisCorrection
fields = '__all__' 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 translation JSON field
translation_schema = {
"type": "array",
"title": "Translations",
"items": {
"type": "object",
"title": "Translation",
"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": "Translation Text"
}
},
"required": ["language_code", "text"]
}
widgets = {
'title': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextInputWidget),
'description': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextareaWidget),
'translation': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextareaWidget),
} }
# 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['translation'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(translation_schema),
'title': 'Translations'
})
class HadisCorrectionAdmin(ModelAdmin): class HadisCorrectionAdmin(ModelAdmin):
"""Admin for HadisCorrection model""" """Admin for HadisCorrection model"""
@ -951,11 +415,11 @@ class HadisCorrectionAdmin(ModelAdmin):
return self._extract_first_text(obj.title) return self._extract_first_text(obj.title)
def _extract_first_text(self, json_data): def _extract_first_text(self, json_data):
"""Helper to safely extract the first 'text' from a JSON list"""
"""Helper to safely extract the first 'title' from a JSON list"""
if json_data and isinstance(json_data, list) and len(json_data) > 0: if json_data and isinstance(json_data, list) and len(json_data) > 0:
first_item = json_data[0] first_item = json_data[0]
if isinstance(first_item, dict): if isinstance(first_item, dict):
return first_item.get('text', '-')
return first_item.get('title', '-')
return '-' return '-'

349
apps/hadis/admin/reference.py

@ -3,7 +3,8 @@ from django import forms
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from unfold.admin import ModelAdmin, TabularInline from unfold.admin import ModelAdmin, TabularInline
from unfold.decorators import display from unfold.decorators import display
from utils.json_editor_field import JsonEditorWidget
from unfold.widgets import UnfoldAdminTextInputWidget, UnfoldAdminTextareaWidget, UnfoldAdminExpandableTextareaWidget
from utils.multilang_json_widget import MultiLanguageJSONWidget
import json import json
# Import your custom admin site # Import your custom admin site
@ -29,131 +30,13 @@ class BookReferenceAdminForm(forms.ModelForm):
class Meta: class Meta:
model = BookReference model = BookReference
fields = '__all__' 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"]
}
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),
} }
# 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): class BookAttributeAdminForm(forms.ModelForm):
"""Custom form for BookAttribute with JSON editor widgets""" """Custom form for BookAttribute with JSON editor widgets"""
@ -161,71 +44,11 @@ class BookAttributeAdminForm(forms.ModelForm):
class Meta: class Meta:
model = BookAttribute model = BookAttribute
fields = '__all__' 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"]
}
widgets = {
'title': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextInputWidget),
'value': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextInputWidget),
} }
# 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'
})
class BookAuthorAdminForm(forms.ModelForm): class BookAuthorAdminForm(forms.ModelForm):
"""Custom form for BookAuthor with JSON editor widgets""" """Custom form for BookAuthor with JSON editor widgets"""
@ -233,41 +56,10 @@ class BookAuthorAdminForm(forms.ModelForm):
class Meta: class Meta:
model = BookAuthor model = BookAuthor
fields = '__all__' fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Schema for name JSON field
name_schema = {
"type": "array",
"title": "Names",
"items": {
"type": "object",
"title": "Name",
"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": "Name Text"
}
},
"required": ["language_code", "text"]
}
widgets = {
'name': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextInputWidget),
} }
# Apply JSON editor widgets
self.fields['name'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(name_schema),
'title': 'Names'
})
class BookReferenceImageAdminForm(forms.ModelForm): class BookReferenceImageAdminForm(forms.ModelForm):
"""Custom form for BookReferenceImage with JSON editor widgets""" """Custom form for BookReferenceImage with JSON editor widgets"""
@ -275,41 +67,10 @@ class BookReferenceImageAdminForm(forms.ModelForm):
class Meta: class Meta:
model = BookReferenceImage model = BookReferenceImage
fields = '__all__' fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# 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"]
}
widgets = {
'description': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextareaWidget),
} }
# Apply JSON editor widgets
self.fields['description'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(description_schema),
'title': 'Descriptions'
})
class BookSubjectAreaAdminForm(forms.ModelForm): class BookSubjectAreaAdminForm(forms.ModelForm):
"""Custom form for BookSubjectArea with JSON editor widgets""" """Custom form for BookSubjectArea with JSON editor widgets"""
@ -317,41 +78,10 @@ class BookSubjectAreaAdminForm(forms.ModelForm):
class Meta: class Meta:
model = BookSubjectArea model = BookSubjectArea
fields = '__all__' 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"]
}
widgets = {
'title': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextInputWidget),
} }
# Apply JSON editor widgets
self.fields['title'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(title_schema),
'title': 'Titles'
})
class BookTypeAdminForm(forms.ModelForm): class BookTypeAdminForm(forms.ModelForm):
"""Custom form for BookType with JSON editor widgets""" """Custom form for BookType with JSON editor widgets"""
@ -359,41 +89,10 @@ class BookTypeAdminForm(forms.ModelForm):
class Meta: class Meta:
model = BookType model = BookType
fields = '__all__' 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"]
}
widgets = {
'title': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextInputWidget),
} }
# Apply JSON editor widgets
self.fields['title'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(title_schema),
'title': 'Titles'
})
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 1. Inlines # 1. Inlines
@ -476,11 +175,11 @@ class BookReferenceAdmin(ModelAdmin):
return self._extract_first_text(obj.publisher) return self._extract_first_text(obj.publisher)
def _extract_first_text(self, json_data): def _extract_first_text(self, json_data):
"""Helper to safely extract the first 'text' from a JSON list"""
"""Helper to safely extract the first 'title' from a JSON list"""
if json_data and isinstance(json_data, list) and len(json_data) > 0: if json_data and isinstance(json_data, list) and len(json_data) > 0:
first_item = json_data[0] first_item = json_data[0]
if isinstance(first_item, dict): if isinstance(first_item, dict):
return first_item.get('text', '-')
return first_item.get('title', '-')
return '-' return '-'
@ -520,7 +219,7 @@ class BookAuthorAdmin(ModelAdmin):
if obj.name and isinstance(obj.name, list) and len(obj.name) > 0: if obj.name and isinstance(obj.name, list) and len(obj.name) > 0:
first = obj.name[0] first = obj.name[0]
if isinstance(first, dict): if isinstance(first, dict):
return first.get('text', '-')
return first.get('title', '-')
return '-' return '-'
class BookReferenceImageAdmin(ModelAdmin): class BookReferenceImageAdmin(ModelAdmin):
@ -534,10 +233,10 @@ class BookReferenceImageAdmin(ModelAdmin):
list_select_related = ("book_reference",) list_select_related = ("book_reference",)
def display_name(self, obj): def display_name(self, obj):
# Implements: f"{self.book_reference.title[0]['text']} - Image {self.order}"
# Implements: f"{self.book_reference.title[0]['title']} - Image {self.order}"
try: try:
# We use safe navigation to prevent admin crashes if data is missing # We use safe navigation to prevent admin crashes if data is missing
book_title = obj.book_reference.title[0]['text']
book_title = obj.book_reference.title[0]['title']
return f"{book_title} - Image {obj.order}" return f"{book_title} - Image {obj.order}"
except (AttributeError, IndexError, KeyError, TypeError): except (AttributeError, IndexError, KeyError, TypeError):
# Fallback if the title structure isn't exactly as expected # Fallback if the title structure isn't exactly as expected
@ -574,7 +273,7 @@ class BookAttributeAdmin(ModelAdmin):
if json_data and isinstance(json_data, list) and len(json_data) > 0: if json_data and isinstance(json_data, list) and len(json_data) > 0:
first = json_data[0] first = json_data[0]
if isinstance(first, dict): if isinstance(first, dict):
return first.get('text', '-')
return first.get('title', '-')
return '-' return '-'

422
apps/hadis/admin/transmitter.py

@ -4,7 +4,8 @@ from django.utils.translation import gettext_lazy as _
from unfold.admin import ModelAdmin, TabularInline from unfold.admin import ModelAdmin, TabularInline
from unfold.decorators import display, action from unfold.decorators import display, action
from unfold.contrib.forms.widgets import WysiwygWidget from unfold.contrib.forms.widgets import WysiwygWidget
from utils.json_editor_field import JsonEditorWidget
from unfold.widgets import UnfoldAdminTextInputWidget, UnfoldAdminTextareaWidget, UnfoldAdminExpandableTextareaWidget
from utils.multilang_json_widget import MultiLanguageJSONWidget
import json import json
from utils.admin import dovoodi_admin_site from utils.admin import dovoodi_admin_site
@ -20,9 +21,6 @@ class HadisTransmitterInline(TabularInline):
fields = ('hadis', 'order') fields = ('hadis', 'order')
# (TransmitterOpinionInline and TransmitterOriginalTextInline moved after their forms)
# Custom Forms for JSON Fields # Custom Forms for JSON Fields
class TransmittersAdminForm(forms.ModelForm): class TransmittersAdminForm(forms.ModelForm):
"""Custom form for Transmitters with JSON editor widgets""" """Custom form for Transmitters with JSON editor widgets"""
@ -30,89 +28,16 @@ class TransmittersAdminForm(forms.ModelForm):
class Meta: class Meta:
model = Transmitters model = Transmitters
fields = '__all__' fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Helper function to create standard language schema
def create_language_schema(title_name, text_title):
return {
"type": "array",
"title": title_name,
"items": {
"type": "object",
"title": title_name[:-1] if title_name.endswith('s') else title_name,
"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": text_title
}
},
"required": ["language_code", "text"]
}
}
# Create schemas for all JSON fields
full_name_schema = create_language_schema("Full Names", "Full Name Text")
kunya_schema = create_language_schema("Kunyas", "Kunya Text")
known_as_schema = create_language_schema("Known As", "Known As Text")
nickname_schema = create_language_schema("Nicknames", "Nickname Text")
origin_schema = create_language_schema("Origins", "Origin Text")
lived_in_schema = create_language_schema("Lived In", "Lived In Text")
died_in_schema = create_language_schema("Died In", "Died In Text")
description_schema = create_language_schema("Descriptions", "Description Text")
# Apply JSON editor widgets
self.fields['full_name'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(full_name_schema),
'title': 'Full Names'
})
self.fields['kunya'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(kunya_schema),
'title': 'Kunyas'
})
self.fields['known_as'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(known_as_schema),
'title': 'Known As'
})
self.fields['nickname'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(nickname_schema),
'title': 'Nicknames'
})
self.fields['origin'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(origin_schema),
'title': 'Origins'
})
self.fields['lived_in'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(lived_in_schema),
'title': 'Lived In'
})
self.fields['died_in'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(died_in_schema),
'title': 'Died In'
})
self.fields['description'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(description_schema),
'title': 'Descriptions'
})
# (Admins moved down to avoid NameError)
widgets = {
'full_name': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextInputWidget),
'kunya': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextInputWidget),
'known_as': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextInputWidget),
'nickname': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextInputWidget),
'origin': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextInputWidget),
'lived_in': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextInputWidget),
'died_in': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextInputWidget),
'description': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextareaWidget),
}
# Custom Forms for JSON Fields # Custom Forms for JSON Fields
@ -122,71 +47,11 @@ class NarratorLayerAdminForm(forms.ModelForm):
class Meta: class Meta:
model = NarratorLayer model = NarratorLayer
fields = '__all__' fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Schema for name JSON field
name_schema = {
"type": "array",
"title": "Names",
"items": {
"type": "object",
"title": "Name",
"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": "Name 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"]
}
widgets = {
'name': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextInputWidget),
'description': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextareaWidget),
} }
# Apply JSON editor widgets
self.fields['name'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(name_schema),
'title': 'Names'
})
self.fields['description'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(description_schema),
'title': 'Descriptions'
})
class TransmitterReliabilityAdminForm(forms.ModelForm): class TransmitterReliabilityAdminForm(forms.ModelForm):
"""Custom form for TransmitterReliability with JSON editor widgets""" """Custom form for TransmitterReliability with JSON editor widgets"""
@ -194,41 +59,10 @@ class TransmitterReliabilityAdminForm(forms.ModelForm):
class Meta: class Meta:
model = TransmitterReliability model = TransmitterReliability
fields = '__all__' 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"]
}
widgets = {
'title': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextInputWidget),
} }
# Apply JSON editor widgets
self.fields['title'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(title_schema),
'title': 'Titles'
})
class OpinionStatusAdminForm(forms.ModelForm): class OpinionStatusAdminForm(forms.ModelForm):
"""Custom form for OpinionStatus with JSON editor widgets""" """Custom form for OpinionStatus with JSON editor widgets"""
@ -236,41 +70,10 @@ class OpinionStatusAdminForm(forms.ModelForm):
class Meta: class Meta:
model = OpinionStatus model = OpinionStatus
fields = '__all__' 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"]
}
widgets = {
'title': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextInputWidget),
} }
# Apply JSON editor widgets
self.fields['title'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(title_schema),
'title': 'Titles'
})
class TransmitterOpinionAdminForm(forms.ModelForm): class TransmitterOpinionAdminForm(forms.ModelForm):
"""Custom form for TransmitterOpinion with JSON editor widgets""" """Custom form for TransmitterOpinion with JSON editor widgets"""
@ -278,71 +81,11 @@ class TransmitterOpinionAdminForm(forms.ModelForm):
class Meta: class Meta:
model = TransmitterOpinion model = TransmitterOpinion
fields = '__all__' fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Schema for scholar_name JSON field
scholar_name_schema = {
"type": "array",
"title": "Scholar Names",
"items": {
"type": "object",
"title": "Scholar Name",
"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": "Scholar Name Text"
}
},
"required": ["language_code", "text"]
}
}
# Schema for opinion_text JSON field
opinion_text_schema = {
"type": "array",
"title": "Opinion Texts",
"items": {
"type": "object",
"title": "Opinion Text",
"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": "Opinion Text"
}
},
"required": ["language_code", "text"]
}
widgets = {
'scholar_name': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextInputWidget),
'opinion_text': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextareaWidget),
} }
# Apply JSON editor widgets
self.fields['scholar_name'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(scholar_name_schema),
'title': 'Scholar Names'
})
self.fields['opinion_text'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(opinion_text_schema),
'title': 'Opinion Texts'
})
class TransmitterOriginalTextAdminForm(forms.ModelForm): class TransmitterOriginalTextAdminForm(forms.ModelForm):
"""Custom form for TransmitterOriginalText with JSON editor widgets""" """Custom form for TransmitterOriginalText with JSON editor widgets"""
@ -350,101 +93,12 @@ class TransmitterOriginalTextAdminForm(forms.ModelForm):
class Meta: class Meta:
model = TransmitterOriginalText model = TransmitterOriginalText
fields = '__all__' 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"]
}
widgets = {
'title': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextInputWidget),
'text': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextareaWidget),
'translation': MultiLanguageJSONWidget(input_widget_class=UnfoldAdminTextareaWidget),
} }
# Schema for text JSON field
text_schema = {
"type": "array",
"title": "Texts",
"items": {
"type": "object",
"title": "Text",
"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": "Text Content"
}
},
"required": ["language_code", "text"]
}
}
# Schema for translation JSON field
translation_schema = {
"type": "array",
"title": "Translations",
"items": {
"type": "object",
"title": "Translation",
"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": "Translation Text"
}
},
"required": ["language_code", "text"]
}
}
# Apply JSON editor widgets
self.fields['title'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(title_schema),
'title': 'Titles'
})
self.fields['text'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(text_schema),
'title': 'Texts'
})
self.fields['translation'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(translation_schema),
'title': 'Translations'
})
class TransmitterOpinionInline(TabularInline): class TransmitterOpinionInline(TabularInline):
"""Inline for TransmitterOpinion in Transmitters admin""" """Inline for TransmitterOpinion in Transmitters admin"""
@ -490,13 +144,13 @@ class TransmittersAdmin(ModelAdmin):
@display(description=_('Full Name'), ordering='full_name') @display(description=_('Full Name'), ordering='full_name')
def get_full_name_display(self, obj): def get_full_name_display(self, obj):
""" """
Parses the JSON full_name and returns the first item's text.
Parses the JSON full_name and returns the first item's title.
""" """
if obj.full_name and isinstance(obj.full_name, list) and len(obj.full_name) > 0: if obj.full_name and isinstance(obj.full_name, list) and len(obj.full_name) > 0:
# Safely get the first item # Safely get the first item
first_item = obj.full_name[0] first_item = obj.full_name[0]
if isinstance(first_item, dict): if isinstance(first_item, dict):
return first_item.get('text', '-')
return first_item.get('title', '-')
return '-' return '-'
@display(description=_("Transmitter"), header=True) @display(description=_("Transmitter"), header=True)
@ -552,11 +206,11 @@ class NarratorLayerAdmin(ModelAdmin):
return self._extract_first_text(obj.name) return self._extract_first_text(obj.name)
def _extract_first_text(self, json_data): def _extract_first_text(self, json_data):
"""Helper to safely extract the first 'text' from a JSON list"""
"""Helper to safely extract the first 'title' from a JSON list"""
if json_data and isinstance(json_data, list) and len(json_data) > 0: if json_data and isinstance(json_data, list) and len(json_data) > 0:
first_item = json_data[0] first_item = json_data[0]
if isinstance(first_item, dict): if isinstance(first_item, dict):
return first_item.get('text', '-')
return first_item.get('title', '-')
return '-' return '-'
@ -579,11 +233,11 @@ class TransmitterReliabilityAdmin(ModelAdmin):
return self._extract_first_text(obj.title) return self._extract_first_text(obj.title)
def _extract_first_text(self, json_data): def _extract_first_text(self, json_data):
"""Helper to safely extract the first 'text' from a JSON list"""
"""Helper to safely extract the first 'title' from a JSON list"""
if json_data and isinstance(json_data, list) and len(json_data) > 0: if json_data and isinstance(json_data, list) and len(json_data) > 0:
first_item = json_data[0] first_item = json_data[0]
if isinstance(first_item, dict): if isinstance(first_item, dict):
return first_item.get('text', '-')
return first_item.get('title', '-')
return '-' return '-'
@ -606,11 +260,11 @@ class OpinionStatusAdmin(ModelAdmin):
return self._extract_first_text(obj.title) return self._extract_first_text(obj.title)
def _extract_first_text(self, json_data): def _extract_first_text(self, json_data):
"""Helper to safely extract the first 'text' from a JSON list"""
"""Helper to safely extract the first 'title' from a JSON list"""
if json_data and isinstance(json_data, list) and len(json_data) > 0: if json_data and isinstance(json_data, list) and len(json_data) > 0:
first_item = json_data[0] first_item = json_data[0]
if isinstance(first_item, dict): if isinstance(first_item, dict):
return first_item.get('text', '-')
return first_item.get('title', '-')
return '-' return '-'
@ -642,11 +296,11 @@ class TransmitterOpinionAdmin(ModelAdmin):
return self._extract_first_text(obj.scholar_name) return self._extract_first_text(obj.scholar_name)
def _extract_first_text(self, json_data): def _extract_first_text(self, json_data):
"""Helper to safely extract the first 'text' from a JSON list"""
"""Helper to safely extract the first 'title' from a JSON list"""
if json_data and isinstance(json_data, list) and len(json_data) > 0: if json_data and isinstance(json_data, list) and len(json_data) > 0:
first_item = json_data[0] first_item = json_data[0]
if isinstance(first_item, dict): if isinstance(first_item, dict):
return first_item.get('text', '-')
return first_item.get('title', '-')
return '-' return '-'
@ -679,11 +333,11 @@ class TransmitterOriginalTextAdmin(ModelAdmin):
return self._extract_first_text(obj.title) return self._extract_first_text(obj.title)
def _extract_first_text(self, json_data): def _extract_first_text(self, json_data):
"""Helper to safely extract the first 'text' from a JSON list"""
"""Helper to safely extract the first 'title' from a JSON list"""
if json_data and isinstance(json_data, list) and len(json_data) > 0: if json_data and isinstance(json_data, list) and len(json_data) > 0:
first_item = json_data[0] first_item = json_data[0]
if isinstance(first_item, dict): if isinstance(first_item, dict):
return first_item.get('text', '-')
return first_item.get('title', '-')
return '-' return '-'

69
apps/hadis/management/commands/migrate_json_keys.py

@ -0,0 +1,69 @@
from django.core.management.base import BaseCommand
from django.db import transaction
from django.apps import apps
from django.db import models as django_models
class Command(BaseCommand):
help = "Rename 'text' key to 'title' in all multi-language JSON fields across Hadis app models."
@transaction.atomic
def handle(self, *args, **options):
hadis_app = apps.get_app_config('hadis')
models = hadis_app.get_models()
self.stdout.write("Starting key migration in JSON fields...")
for model in models:
json_fields = [
f.name for f in model._meta.get_fields()
if isinstance(f, django_models.JSONField)
]
if not json_fields:
continue
self.stdout.write(f"Checking model {model.__name__} for fields: {json_fields}")
queryset = model.objects.all()
for obj in queryset:
changed = False
for field in json_fields:
value = getattr(obj, field)
if not isinstance(value, list):
continue
new_list = []
field_changed = False
for item in value:
if isinstance(item, dict):
# Check if this item follows the multi-language pattern
# lang_key can be 'language_code', 'lang', or 'code'
lang_val = item.get("language_code") or item.get("lang") or item.get("code")
# text_val can be 'text' or 'title'
text_val = item.get("text")
if lang_val and text_val is not None:
# We found a multi-language item with a 'text' key
new_item = {
"language_code": str(lang_val),
"title": text_val
}
# Copy any other keys
for k, v in item.items():
if k not in ["language_code", "lang", "code", "text", "title"]:
new_item[k] = v
new_list.append(new_item)
field_changed = True
else:
new_list.append(item)
else:
new_list.append(item)
if field_changed:
setattr(obj, field, new_list)
changed = True
if changed:
obj.save()
self.stdout.write(f" Updated keys in {model.__name__} ID: {obj.id}")
self.stdout.write(self.style.SUCCESS("JSON key migration completed!"))

22
apps/hadis/models/category.py

@ -18,7 +18,7 @@ class HadisSect(models.Model):
order = models.IntegerField(default=0, verbose_name=_('order')) order = models.IntegerField(default=0, verbose_name=_('order'))
def __str__(self): def __str__(self):
title = self.title[0]['text'] if self.title else "Untitled Sect"
title = self.title[0].get('title') or self.title[0].get('text', '') if self.title else "Untitled Sect"
return f"{self.sect_type}: {title}" return f"{self.sect_type}: {title}"
def get_title(self,lang): def get_title(self,lang):
@ -31,11 +31,11 @@ class HadisSect(models.Model):
for tr in self.title: for tr in self.title:
if isinstance(tr, dict) and tr.get('language_code') == lang: if isinstance(tr, dict) and tr.get('language_code') == lang:
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
for tr in self.title: for tr in self.title:
if isinstance(tr, dict) and tr.get('language_code') == 'en': if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
return None return None
def get_description(self,lang): def get_description(self,lang):
@ -48,11 +48,11 @@ class HadisSect(models.Model):
for tr in self.description: for tr in self.description:
if isinstance(tr, dict) and tr.get('language_code') == lang: if isinstance(tr, dict) and tr.get('language_code') == lang:
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
for tr in self.description: for tr in self.description:
if isinstance(tr, dict) and tr.get('language_code') == 'en': if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
return None return None
@ -100,7 +100,7 @@ class HadisCategory(MPTTModel):
if self.title and isinstance(self.title, list) and len(self.title) > 0: if self.title and isinstance(self.title, list) and len(self.title) > 0:
first_item = self.title[0] first_item = self.title[0]
if isinstance(first_item, dict): if isinstance(first_item, dict):
title_text = first_item.get('text', '').strip()
title_text = (first_item.get('title') or first_item.get('text') or '').strip()
if title_text: if title_text:
base_slug = slugify(title_text, allow_unicode=True) base_slug = slugify(title_text, allow_unicode=True)
slug = base_slug slug = base_slug
@ -175,7 +175,7 @@ class HadisCategory(MPTTModel):
ordering = ('order',) ordering = ('order',)
def __str__(self): def __str__(self):
title = self.title[0]['text'] if self.title else "Untitled Category"
title = self.title[0].get('title') or self.title[0].get('text', '') if self.title else "Untitled Category"
return f"{self.sect.sect_type}: {self.source_type} - {title}" return f"{self.sect.sect_type}: {self.source_type} - {title}"
def get_title(self,lang): def get_title(self,lang):
@ -188,11 +188,11 @@ class HadisCategory(MPTTModel):
for tr in self.title: for tr in self.title:
if isinstance(tr, dict) and tr.get('language_code') == lang: if isinstance(tr, dict) and tr.get('language_code') == lang:
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
for tr in self.title: for tr in self.title:
if isinstance(tr, dict) and tr.get('language_code') == 'en': if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
return None return None
def get_description(self,lang): def get_description(self,lang):
@ -205,11 +205,11 @@ class HadisCategory(MPTTModel):
for tr in self.description: for tr in self.description:
if isinstance(tr, dict) and tr.get('language_code') == lang: if isinstance(tr, dict) and tr.get('language_code') == lang:
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
for tr in self.description: for tr in self.description:
if isinstance(tr, dict) and tr.get('language_code') == 'en': if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
return None return None

46
apps/hadis/models/hadis.py

@ -79,7 +79,7 @@ class HadisCollection(models.Model):
super().save(*args, **kwargs) super().save(*args, **kwargs)
def __str__(self): def __str__(self):
return self.title[0]['text'] if self.title else "Untitled Collection"
return self.title[0]['title'] if self.title else "Untitled Collection"
def get_title(self,lang): def get_title(self,lang):
""" """
@ -91,11 +91,11 @@ class HadisCollection(models.Model):
for tr in self.title: for tr in self.title:
if isinstance(tr, dict) and tr.get('language_code') == lang: if isinstance(tr, dict) and tr.get('language_code') == lang:
return tr.get('text', '')
return tr.get('title', '')
for tr in self.title: for tr in self.title:
if isinstance(tr, dict) and tr.get('language_code') == 'en': if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return tr.get('title', '')
return None return None
def get_summary(self,lang): def get_summary(self,lang):
@ -108,11 +108,11 @@ class HadisCollection(models.Model):
for tr in self.summary: for tr in self.summary:
if isinstance(tr, dict) and tr.get('language_code') == lang: if isinstance(tr, dict) and tr.get('language_code') == lang:
return tr.get('text', '')
return tr.get('title', '')
for tr in self.summary: for tr in self.summary:
if isinstance(tr, dict) and tr.get('language_code') == 'en': if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return tr.get('title', '')
return None return None
class Meta: class Meta:
@ -144,7 +144,7 @@ class HadisTag(models.Model):
updated_at = models.DateTimeField(auto_now=True, verbose_name=_('updated at')) updated_at = models.DateTimeField(auto_now=True, verbose_name=_('updated at'))
def __str__(self): def __str__(self):
return self.title[0]['text'] if self.title else "Untagged"
return self.title[0]['title'] if self.title else "Untagged"
def get_title(self,lang): def get_title(self,lang):
""" """
@ -156,11 +156,11 @@ class HadisTag(models.Model):
for tr in self.title: for tr in self.title:
if isinstance(tr, dict) and tr.get('language_code') == lang: if isinstance(tr, dict) and tr.get('language_code') == lang:
return tr.get('text', '')
return tr.get('title', '')
for tr in self.title: for tr in self.title:
if isinstance(tr, dict) and tr.get('language_code') == 'en': if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return tr.get('title', '')
return None return None
@ -232,7 +232,7 @@ class HadisStatus(ColorPaletteMixin,models.Model):
super().save(*args, **kwargs) super().save(*args, **kwargs)
def __str__(self): def __str__(self):
return self.title[0]['text'] if self.title else str(self.id)
return self.title[0]['title'] if self.title else str(self.id)
def get_title(self,lang): def get_title(self,lang):
""" """
@ -248,7 +248,7 @@ class HadisStatus(ColorPaletteMixin,models.Model):
for tr in self.title: for tr in self.title:
if isinstance(tr, dict) and tr.get('language_code') == 'en': if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
return None return None
class Meta: class Meta:
@ -288,7 +288,7 @@ class Hadis(models.Model):
embedded_in = models.JSONField(default=list, blank=True) embedded_in = models.JSONField(default=list, blank=True)
def __str__(self): def __str__(self):
title = self.title[0]['text'] if self.title else f"Hadis {self.number}"
title = self.title[0].get('title') or self.title[0].get('text', '') if self.title else f"Hadis {self.number}"
return f"{self.number} - {title}" return f"{self.number} - {title}"
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
@ -300,7 +300,7 @@ class Hadis(models.Model):
if self.title and isinstance(self.title, list) and len(self.title) > 0: if self.title and isinstance(self.title, list) and len(self.title) > 0:
first_item = self.title[0] first_item = self.title[0]
if isinstance(first_item, dict): if isinstance(first_item, dict):
title_text = first_item.get("text")
title_text = first_item.get("title") or first_item.get("text")
# 2. If we found text, use smart slug # 2. If we found text, use smart slug
if title_text: if title_text:
@ -355,18 +355,18 @@ class Hadis(models.Model):
# 1) exact language # 1) exact language
for item in value: for item in value:
if isinstance(item, dict) and item.get("language_code") == lang: if isinstance(item, dict) and item.get("language_code") == lang:
return item.get("text", "")
return item.get("title") or item.get("text") or ""
# 2) fallback language # 2) fallback language
if fallback and fallback != lang: if fallback and fallback != lang:
for item in value: for item in value:
if isinstance(item, dict) and item.get("language_code") == fallback: if isinstance(item, dict) and item.get("language_code") == fallback:
return item.get("text", "")
return item.get("title") or item.get("text") or ""
# 3) first available # 3) first available
item = value[0] item = value[0]
print(item) print(item)
return item.get("text", "") if isinstance(item, dict) else None
return item.get("title") or item.get("text") if isinstance(item, dict) else None
def get_translation(self, lang): def get_translation(self, lang):
return self._get_json_field("translation" , lang) return self._get_json_field("translation" , lang)
@ -448,11 +448,11 @@ class HadisReference(models.Model):
for tr in self.description: for tr in self.description:
if isinstance(tr, dict) and tr.get('language_code') == lang: if isinstance(tr, dict) and tr.get('language_code') == lang:
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
for tr in self.description: for tr in self.description:
if isinstance(tr, dict) and tr.get('language_code') == 'en': if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
return None return None
def __str__(self): def __str__(self):
@ -507,7 +507,7 @@ class HadisCorrection(models.Model):
ordering = ("-created_at",) ordering = ("-created_at",)
def __str__(self): def __str__(self):
title = self.title[0]['text'] if self.title else "No Title"
title = self.title[0].get('title') or self.title[0].get('text', '') if self.title else "No Title"
return f"{self.hadis.number} - {title}" return f"{self.hadis.number} - {title}"
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
@ -517,7 +517,7 @@ class HadisCorrection(models.Model):
if isinstance(self.title, list) and len(self.title) > 0: if isinstance(self.title, list) and len(self.title) > 0:
first_item = self.title[0] first_item = self.title[0]
if isinstance(first_item, dict): if isinstance(first_item, dict):
title_text = first_item.get("text")
title_text = first_item.get("title") or first_item.get("text")
if title_text: if title_text:
self.slug = generate_smart_slug( self.slug = generate_smart_slug(
@ -549,17 +549,17 @@ class HadisCorrection(models.Model):
def get_title(self, lang): def get_title(self, lang):
if not self.title or not isinstance(self.title, list): return None if not self.title or not isinstance(self.title, list): return None
for tr in self.title: for tr in self.title:
if isinstance(tr, dict) and tr.get('language_code') == lang: return tr.get('text', '')
if isinstance(tr, dict) and tr.get('language_code') == lang: return tr.get('title') or tr.get('text') or ''
for tr in self.title: for tr in self.title:
if isinstance(tr, dict) and tr.get('language_code') == 'en': return tr.get('text', '')
if isinstance(tr, dict) and tr.get('language_code') == 'en': return tr.get('title') or tr.get('text') or ''
return None return None
def get_translation(self, lang): def get_translation(self, lang):
if not self.translation or not isinstance(self.translation, list): return None if not self.translation or not isinstance(self.translation, list): return None
for tr in self.translation: for tr in self.translation:
if isinstance(tr, dict) and tr.get('language_code') == lang: return tr.get('text', '')
if isinstance(tr, dict) and tr.get('language_code') == lang: return tr.get('title') or tr.get('text') or ''
for tr in self.translation: for tr in self.translation:
if isinstance(tr, dict) and tr.get('language_code') == 'en': return tr.get('text', '')
if isinstance(tr, dict) and tr.get('language_code') == 'en': return tr.get('title') or tr.get('text') or ''
return None return None

44
apps/hadis/models/reference.py

@ -10,7 +10,7 @@ class BookSubjectArea(models.Model):
updated_at = models.DateTimeField(auto_now=True, verbose_name=_('updated at')) updated_at = models.DateTimeField(auto_now=True, verbose_name=_('updated at'))
def __str__(self): def __str__(self):
return self.title[0]['text'] if self.title else str(self.id)
return self.title[0].get('title') or self.title[0].get('text', '') if self.title else str(self.id)
def get_title(self,lang): def get_title(self,lang):
""" """
@ -22,11 +22,11 @@ class BookSubjectArea(models.Model):
for tr in self.title: for tr in self.title:
if isinstance(tr, dict) and tr.get('language_code') == lang: if isinstance(tr, dict) and tr.get('language_code') == lang:
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
for tr in self.title: for tr in self.title:
if isinstance(tr, dict) and tr.get('language_code') == 'en': if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
return None return None
@ -36,7 +36,7 @@ class BookType(models.Model):
updated_at = models.DateTimeField(auto_now=True, verbose_name=_('updated at')) updated_at = models.DateTimeField(auto_now=True, verbose_name=_('updated at'))
def __str__(self): def __str__(self):
return self.title[0]['text'] if self.title else str(self.id)
return self.title[0].get('title') or self.title[0].get('text', '') if self.title else str(self.id)
def get_title(self,lang): def get_title(self,lang):
""" """
@ -48,11 +48,11 @@ class BookType(models.Model):
for tr in self.title: for tr in self.title:
if isinstance(tr, dict) and tr.get('language_code') == lang: if isinstance(tr, dict) and tr.get('language_code') == lang:
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
for tr in self.title: for tr in self.title:
if isinstance(tr, dict) and tr.get('language_code') == 'en': if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
return None return None
class BookReference(models.Model): class BookReference(models.Model):
@ -100,7 +100,7 @@ class BookReference(models.Model):
ordering = ('-created_at',) ordering = ('-created_at',)
def __str__(self): def __str__(self):
return self.title[0]['text'] if self.title else "Untitled Reference"
return self.title[0].get('title') or self.title[0].get('text', '') if self.title else "Untitled Reference"
def _get_json_field(self, field_name: str, lang: Optional[str]=None , fallback: str = "en"): def _get_json_field(self, field_name: str, lang: Optional[str]=None , fallback: str = "en"):
""" """
@ -117,18 +117,18 @@ class BookReference(models.Model):
# 1) exact language # 1) exact language
for item in value: for item in value:
if isinstance(item, dict) and item.get("language_code") == lang: if isinstance(item, dict) and item.get("language_code") == lang:
return item.get("text", "")
return item.get("title") or item.get("text") or ""
# 2) fallback language # 2) fallback language
if fallback and fallback != lang: if fallback and fallback != lang:
for item in value: for item in value:
if isinstance(item, dict) and item.get("language_code") == fallback: if isinstance(item, dict) and item.get("language_code") == fallback:
return item.get("text", "")
return item.get("title") or item.get("text") or ""
# 3) first available # 3) first available
item = value[0] item = value[0]
print(item) print(item)
return item.get("text", "") if isinstance(item, dict) else None
return item.get("title") or item.get("text") if isinstance(item, dict) else None
@property @property
def share_link(self): def share_link(self):
@ -157,7 +157,7 @@ class BookReference(models.Model):
if self.title and isinstance(self.title, list) and len(self.title) > 0: if self.title and isinstance(self.title, list) and len(self.title) > 0:
first_item = self.title[0] first_item = self.title[0]
if isinstance(first_item, dict): if isinstance(first_item, dict):
title_text = first_item.get('text', '').strip()
title_text = (first_item.get('title') or first_item.get('text') or '').strip()
if title_text: if title_text:
base_slug = slugify(title_text, allow_unicode=True) base_slug = slugify(title_text, allow_unicode=True)
slug = base_slug slug = base_slug
@ -240,11 +240,11 @@ class BookReferenceImage(models.Model):
for tr in self.description: for tr in self.description:
if isinstance(tr, dict) and tr.get('language_code') == lang: if isinstance(tr, dict) and tr.get('language_code') == lang:
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
for tr in self.description: for tr in self.description:
if isinstance(tr, dict) and tr.get('language_code') == 'en': if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
return None return None
def __str__(self): def __str__(self):
@ -300,7 +300,7 @@ class BookAuthor(models.Model):
ordering = ['name'] ordering = ['name']
def __str__(self): def __str__(self):
return self.name[0]['text'] if self.name else "Unknown Author"
return self.name[0].get('title') or self.name[0].get('text', '') if self.name else "Unknown Author"
def get_name(self,lang): def get_name(self,lang):
""" """
@ -312,11 +312,11 @@ class BookAuthor(models.Model):
for tr in self.name: for tr in self.name:
if isinstance(tr, dict) and tr.get('language_code') == lang: if isinstance(tr, dict) and tr.get('language_code') == lang:
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
for tr in self.name: for tr in self.name:
if isinstance(tr, dict) and tr.get('language_code') == 'en': if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
return None return None
@ -343,8 +343,8 @@ class BookAttribute(models.Model):
ordering = ['title'] ordering = ['title']
def __str__(self): def __str__(self):
title = self.title[0]['text'] if self.title else "No Title"
value = self.value[0]['text'] if self.value else "No Value"
title = self.title[0].get('title') or self.title[0].get('text', '') if self.title else "No Title"
value = self.value[0].get('title') or self.value[0].get('text', '') if self.value else "No Value"
return f"{title}: {value}" return f"{title}: {value}"
def get_title(self,lang): def get_title(self,lang):
@ -357,11 +357,11 @@ class BookAttribute(models.Model):
for tr in self.title: for tr in self.title:
if isinstance(tr, dict) and tr.get('language_code') == lang: if isinstance(tr, dict) and tr.get('language_code') == lang:
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
for tr in self.title: for tr in self.title:
if isinstance(tr, dict) and tr.get('language_code') == 'en': if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
return None return None
def get_value(self,lang): def get_value(self,lang):
@ -374,9 +374,9 @@ class BookAttribute(models.Model):
for tr in self.value: for tr in self.value:
if isinstance(tr, dict) and tr.get('language_code') == lang: if isinstance(tr, dict) and tr.get('language_code') == lang:
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
for tr in self.value: for tr in self.value:
if isinstance(tr, dict) and tr.get('language_code') == 'en': if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
return None return None

60
apps/hadis/models/transmitter.py

@ -30,7 +30,7 @@ class NarratorLayer(models.Model):
ordering = ['number'] ordering = ['number']
def __str__(self): def __str__(self):
name = self.name[0]['text'] if self.name else f"Layer {self.number}"
name = self.name[0].get('title') or self.name[0].get('text', '') if self.name else f"Layer {self.number}"
return f"{_('Layer')} {self.number} - {name}" return f"{_('Layer')} {self.number} - {name}"
def get_description(self,lang): def get_description(self,lang):
@ -43,11 +43,11 @@ class NarratorLayer(models.Model):
for tr in self.description: for tr in self.description:
if isinstance(tr, dict) and tr.get('language_code') == lang: if isinstance(tr, dict) and tr.get('language_code') == lang:
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
for tr in self.description: for tr in self.description:
if isinstance(tr, dict) and tr.get('language_code') == 'en': if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
return None return None
def get_name(self,lang): def get_name(self,lang):
@ -60,11 +60,11 @@ class NarratorLayer(models.Model):
for tr in self.name: for tr in self.name:
if isinstance(tr, dict) and tr.get('language_code') == lang: if isinstance(tr, dict) and tr.get('language_code') == lang:
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
for tr in self.name: for tr in self.name:
if isinstance(tr, dict) and tr.get('language_code') == 'en': if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
return None return None
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
@ -74,7 +74,7 @@ class NarratorLayer(models.Model):
if self.name and isinstance(self.name, list) and len(self.name) > 0: if self.name and isinstance(self.name, list) and len(self.name) > 0:
first_item = self.name[0] first_item = self.name[0]
if isinstance(first_item, dict): if isinstance(first_item, dict):
text = first_item.get('text', '').strip()
text = (first_item.get('title') or first_item.get('text') or '').strip()
if text: if text:
slug = slugify(text) slug = slugify(text)
# Ensure uniqueness # Ensure uniqueness
@ -135,7 +135,7 @@ class TransmitterReliability(ColorPaletteMixin, models.Model):
if self.title and isinstance(self.title, list) and len(self.title) > 0: if self.title and isinstance(self.title, list) and len(self.title) > 0:
first_item = self.title[0] first_item = self.title[0]
if isinstance(first_item, dict): if isinstance(first_item, dict):
text = first_item.get('text', '').strip()
text = (first_item.get('title') or first_item.get('text') or '').strip()
if text: if text:
slug = slugify(text) slug = slugify(text)
# Ensure uniqueness # Ensure uniqueness
@ -185,7 +185,7 @@ class TransmitterReliability(ColorPaletteMixin, models.Model):
super().save(*args, **kwargs) super().save(*args, **kwargs)
def __str__(self): def __str__(self):
return self.title[0]['text'] if self.title else str(self.id)
return self.title[0].get('title') or self.title[0].get('text', '') if self.title else str(self.id)
def get_title(self,lang): def get_title(self,lang):
""" """
@ -197,11 +197,11 @@ class TransmitterReliability(ColorPaletteMixin, models.Model):
for tr in self.title: for tr in self.title:
if isinstance(tr, dict) and tr.get('language_code') == lang: if isinstance(tr, dict) and tr.get('language_code') == lang:
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
for tr in self.title: for tr in self.title:
if isinstance(tr, dict) and tr.get('language_code') == 'en': if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
return None return None
class Meta: class Meta:
@ -314,7 +314,7 @@ class Transmitters(models.Model):
if self.full_name and isinstance(self.full_name, list) and len(self.full_name) > 0: if self.full_name and isinstance(self.full_name, list) and len(self.full_name) > 0:
first_item = self.full_name[0] first_item = self.full_name[0]
if isinstance(first_item, dict): if isinstance(first_item, dict):
name_text = first_item.get('text', '').strip()
name_text = (first_item.get('title') or first_item.get('text') or '').strip()
if name_text: if name_text:
base_slug = slugify(name_text, allow_unicode=True) base_slug = slugify(name_text, allow_unicode=True)
slug = base_slug slug = base_slug
@ -380,18 +380,18 @@ class Transmitters(models.Model):
# 1) exact language # 1) exact language
for item in value: for item in value:
if isinstance(item, dict) and item.get("language_code") == lang: if isinstance(item, dict) and item.get("language_code") == lang:
return item.get("text", "")
return item.get("title") or item.get("text") or ""
# 2) fallback language # 2) fallback language
if fallback and fallback != lang: if fallback and fallback != lang:
for item in value: for item in value:
if isinstance(item, dict) and item.get("language_code") == fallback: if isinstance(item, dict) and item.get("language_code") == fallback:
return item.get("text", "")
return item.get("title") or item.get("text") or ""
# 3) first available # 3) first available
item = value[0] item = value[0]
print(item) print(item)
return item.get("text", "") if isinstance(item, dict) else None
return item.get("title") or item.get("text") if isinstance(item, dict) else None
def get_full_name(self, lang): def get_full_name(self, lang):
return self._get_json_field("full_name" , lang) return self._get_json_field("full_name" , lang)
@ -417,7 +417,7 @@ class Transmitters(models.Model):
def __str__(self): def __str__(self):
if self.full_name and len(self.full_name) > 0: if self.full_name and len(self.full_name) > 0:
return self.full_name[0].get('text', str(self.id))
return self.full_name[0].get('title') or self.full_name[0].get('text', str(self.id))
return str(self.id) return str(self.id)
@ -498,7 +498,7 @@ class OpinionStatus(ColorPaletteMixin, models.Model):
if self.title and isinstance(self.title, list) and len(self.title) > 0: if self.title and isinstance(self.title, list) and len(self.title) > 0:
first_item = self.title[0] first_item = self.title[0]
if isinstance(first_item, dict): if isinstance(first_item, dict):
text = first_item.get('text', '').strip()
text = (first_item.get('title') or first_item.get('text') or '').strip()
if text: if text:
slug = slugify(text) slug = slugify(text)
# Ensure uniqueness # Ensure uniqueness
@ -548,7 +548,7 @@ class OpinionStatus(ColorPaletteMixin, models.Model):
super().save(*args, **kwargs) super().save(*args, **kwargs)
def __str__(self): def __str__(self):
return self.title[0]['text'] if self.title else str(self.id)
return self.title[0].get('title') or self.title[0].get('text', '') if self.title else str(self.id)
def get_title(self,lang): def get_title(self,lang):
""" """
@ -560,11 +560,11 @@ class OpinionStatus(ColorPaletteMixin, models.Model):
for tr in self.title: for tr in self.title:
if isinstance(tr, dict) and tr.get('language_code') == lang: if isinstance(tr, dict) and tr.get('language_code') == lang:
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
for tr in self.title: for tr in self.title:
if isinstance(tr, dict) and tr.get('language_code') == 'en': if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
return None return None
class Meta: class Meta:
@ -611,7 +611,7 @@ class TransmitterOpinion(models.Model):
ordering = ('-created_at',) ordering = ('-created_at',)
def __str__(self): def __str__(self):
scholar = self.scholar_name[0]['text'] if self.scholar_name else "Unknown Scholar"
scholar = self.scholar_name[0].get('title') or self.scholar_name[0].get('text', '') if self.scholar_name else "Unknown Scholar"
return f"{scholar}'s opinion on {self.transmitter} ({self.status})" return f"{scholar}'s opinion on {self.transmitter} ({self.status})"
def get_scholar_name(self,lang): def get_scholar_name(self,lang):
@ -624,11 +624,11 @@ class TransmitterOpinion(models.Model):
for tr in self.scholar_name: for tr in self.scholar_name:
if isinstance(tr, dict) and tr.get('language_code') == lang: if isinstance(tr, dict) and tr.get('language_code') == lang:
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
for tr in self.scholar_name: for tr in self.scholar_name:
if isinstance(tr, dict) and tr.get('language_code') == 'en': if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
return None return None
def get_opinion_tex(self,lang): def get_opinion_tex(self,lang):
@ -641,11 +641,11 @@ class TransmitterOpinion(models.Model):
for tr in self.opinion_text: for tr in self.opinion_text:
if isinstance(tr, dict) and tr.get('language_code') == lang: if isinstance(tr, dict) and tr.get('language_code') == lang:
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
for tr in self.opinion_text: for tr in self.opinion_text:
if isinstance(tr, dict) and tr.get('language_code') == 'en': if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
return None return None
class TransmitterOriginalText(models.Model): class TransmitterOriginalText(models.Model):
@ -671,7 +671,7 @@ class TransmitterOriginalText(models.Model):
verbose_name_plural = _('Transmitter Original Text') verbose_name_plural = _('Transmitter Original Text')
def __str__(self): def __str__(self):
title = self.title[0]['text'] if self.title else "Untitled"
title = self.title[0].get('title') or self.title[0].get('text', '') if self.title else "Untitled"
return f"{title} by {self.transmitter}" return f"{title} by {self.transmitter}"
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
@ -686,7 +686,7 @@ class TransmitterOriginalText(models.Model):
if isinstance(self.title, list) and self.title: if isinstance(self.title, list) and self.title:
first_item = self.title[0] first_item = self.title[0]
if isinstance(first_item, dict): if isinstance(first_item, dict):
title_text = first_item.get("text")
title_text = first_item.get("title") or first_item.get("text")
# Generate smart slug # Generate smart slug
if title_text: if title_text:
@ -734,11 +734,11 @@ class TransmitterOriginalText(models.Model):
for tr in self.title: for tr in self.title:
if isinstance(tr, dict) and tr.get('language_code') == lang: if isinstance(tr, dict) and tr.get('language_code') == lang:
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
for tr in self.title: for tr in self.title:
if isinstance(tr, dict) and tr.get('language_code') == 'en': if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
return None return None
def get_opinion_tex(self,lang): def get_opinion_tex(self,lang):
@ -752,11 +752,11 @@ class TransmitterOriginalText(models.Model):
for tr in self.opinion_text: for tr in self.opinion_text:
if isinstance(tr, dict) and tr.get('language_code') == lang: if isinstance(tr, dict) and tr.get('language_code') == lang:
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
for tr in self.opinion_text: for tr in self.opinion_text:
if isinstance(tr, dict) and tr.get('language_code') == 'en': if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return tr.get('title') or tr.get('text') or ''
return None return None
from .reference import BookReference from .reference import BookReference
class OriginalTextReference(models.Model): class OriginalTextReference(models.Model):

16
apps/hadis/serializers/category.py

@ -10,7 +10,7 @@ def get_localized_text(json_list, request=None, fallback_lang="en", language_cod
""" """
Extract localized text from a JSON list based on language. Extract localized text from a JSON list based on language.
Expects: [{"language_code": "en", "text": "..."}, ...]
Expects: [{"language_code": "en", "title": "..."}, ...]
Returns: Single text string or None Returns: Single text string or None
""" """
if not json_list or not isinstance(json_list, list): if not json_list or not isinstance(json_list, list):
@ -25,16 +25,16 @@ def get_localized_text(json_list, request=None, fallback_lang="en", language_cod
# 1) Exact match # 1) Exact match
for item in json_list: for item in json_list:
if isinstance(item, dict) and item.get("language_code") == language_code: if isinstance(item, dict) and item.get("language_code") == language_code:
return item.get("text")
return item.get("title") or item.get("text")
# 2) Fallback to English # 2) Fallback to English
for item in json_list: for item in json_list:
if isinstance(item, dict) and item.get("language_code") == "en": if isinstance(item, dict) and item.get("language_code") == "en":
return item.get("text")
return item.get("title") or item.get("text")
# 3) First available # 3) First available
if json_list and isinstance(json_list[0], dict): if json_list and isinstance(json_list[0], dict):
return json_list[0].get("text")
return json_list[0].get("title") or json_list[0].get("text")
return None return None
@ -47,7 +47,7 @@ class LocalizedField(serializers.Field):
""" """
def to_representation(self, value): def to_representation(self, value):
# Expecting value to be a list of {"language_code": "...", "text": "..."}
# Expecting value to be a list of {"language_code": "...", "title": "..."}
if not value or not isinstance(value, list): if not value or not isinstance(value, list):
return None return None
@ -60,16 +60,16 @@ class LocalizedField(serializers.Field):
# 1) Exact match with request language # 1) Exact match with request language
for item in value: for item in value:
if item.get("language_code") == language_code: if item.get("language_code") == language_code:
return item.get("text")
return item.get("title") or item.get("text")
# 2) Fallback to English # 2) Fallback to English
for item in value: for item in value:
if item.get("language_code") == "en": if item.get("language_code") == "en":
return item.get("text")
return item.get("title") or item.get("text")
# 3) Fallback to first item # 3) Fallback to first item
first = value[0] first = value[0]
return first.get("text") if isinstance(first, dict) else None
return first.get("title") or first.get("text") if isinstance(first, dict) else None
class SimpleCategory(serializers.ModelSerializer): class SimpleCategory(serializers.ModelSerializer):
title = LocalizedField() title = LocalizedField()

6
apps/hadis/views/category.py

@ -429,16 +429,16 @@ class HadisCategoryXMindView(APIView):
# 1. Try specific language # 1. Try specific language
for item in json_field: for item in json_field:
if item.get('language_code') == lang: if item.get('language_code') == lang:
return item.get('text', '')
return item.get('title') or item.get('text', '')
# 2. Fallback to English # 2. Fallback to English
for item in json_field: for item in json_field:
if item.get('language_code') == 'en': if item.get('language_code') == 'en':
return item.get('text', '')
return item.get('title') or item.get('text', '')
# 3. Fallback to first available # 3. Fallback to first available
if len(json_field) > 0: if len(json_field) > 0:
return json_field[0].get('text', '')
return json_field[0].get('title') or json_field[0].get('text', '')
return "Unknown" return "Unknown"

Loading…
Cancel
Save