Browse Source

Add custom forms for JSON fields in Hadis admin

- Introduced custom forms for Hadis, HadisCollection, HadisTag, HadisStatus, HadisReference, and Transmitters models to utilize JSON editor widgets for better data management.
- Implemented schemas for various JSON fields including titles, descriptions, and addresses to enhance the admin interface.
- Updated list displays in admin classes to show the first text from JSON fields for improved readability.
- Added a management command to seed HadisStatus descriptions with multilingual definitions based on keywords.
master
Mohsen Taba 4 months ago
parent
commit
538d0316bc
  1. 517
      apps/hadis/admin/hadis.py
  2. 243
      apps/hadis/admin/reference.py
  3. 164
      apps/hadis/admin/transmitter.py
  4. 110
      apps/hadis/management/commands/seed_hadisstatus.py

517
apps/hadis/admin/hadis.py

@ -75,6 +75,176 @@ class HadisAdminForm(forms.ModelForm):
}
}
# 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)
explanation_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"]
}
},
"text": {
"type": "array",
"title": "Explanation Items",
"items": {
"type": "object",
"title": "Explanation Item",
"properties": {
"title": {
"type": "string",
"title": "Title"
},
"detail": {
"type": "string",
"title": "Detail",
"format": "textarea"
}
},
"required": ["title", "detail"]
}
}
},
"required": ["language_code", "text"]
}
}
# Schema for explanations JSON field (array of objects with title and description)
# explanations_schema = {
# "type": "array",
@ -137,6 +307,36 @@ class HadisAdminForm(forms.ModelForm):
'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),
# 'title': 'Explanations'
@ -148,6 +348,235 @@ class HadisAdminForm(forms.ModelForm):
# })
# Custom Forms for JSON Fields
class HadisCollectionAdminForm(forms.ModelForm):
"""Custom form for HadisCollection with JSON editor widgets"""
class Meta:
model = HadisCollection
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"]
}
}
# 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):
"""Custom form for HadisTag with JSON editor widgets"""
class Meta:
model = HadisTag
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"]
}
}
# Apply JSON editor widgets
self.fields['title'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(title_schema),
'title': 'Titles'
})
class HadisStatusAdminForm(forms.ModelForm):
"""Custom form for HadisStatus with JSON editor widgets"""
class Meta:
model = HadisStatus
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"]
}
}
# 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):
"""Custom form for HadisReference with JSON editor widgets"""
class Meta:
model = HadisReference
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"]
}
}
# Apply JSON editor widgets
self.fields['description'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(description_schema),
'title': 'Descriptions'
})
# Inline Admin Classes
class ReferenceImageInline(TabularInline):
"""Inline for ReferenceImage in HadisReference admin"""
@ -168,7 +597,8 @@ class HadisReferenceInline(TabularInline):
# Main Admin Classes
class HadisTagAdmin(ModelAdmin):
"""Admin for HadisTag model"""
list_display = ('title', 'status', 'created_at')
form = HadisTagAdminForm
list_display = ('get_title_display', 'status', 'created_at')
list_filter = ('status', 'created_at')
search_fields = ('title',)
readonly_fields = ('created_at', 'updated_at')
@ -183,10 +613,23 @@ class HadisTagAdmin(ModelAdmin):
}),
)
@display(description=_('Title'), ordering='title')
def get_title_display(self, obj):
return self._extract_first_text(obj.title)
def _extract_first_text(self, json_data):
"""Helper to safely extract the first 'text' from a JSON list"""
if json_data and isinstance(json_data, list) and len(json_data) > 0:
first_item = json_data[0]
if isinstance(first_item, dict):
return first_item.get('text', '-')
return '-'
class HadisStatusAdmin(ModelAdmin):
"""Admin for HadisStatus model"""
list_display = ('title', 'color', 'order', 'description')
form = HadisStatusAdminForm
list_display = ('get_title_display', 'color', 'order', 'get_description_display')
list_filter = ('color',)
search_fields = ('title', 'description')
ordering = ('order',)
@ -198,11 +641,27 @@ class HadisStatusAdmin(ModelAdmin):
}),
)
@display(description=_('Title'), ordering='title')
def get_title_display(self, obj):
return self._extract_first_text(obj.title)
@display(description=_('Description'))
def get_description_display(self, obj):
return self._extract_first_text(obj.description)
def _extract_first_text(self, json_data):
"""Helper to safely extract the first 'text' from a JSON list"""
if json_data and isinstance(json_data, list) and len(json_data) > 0:
first_item = json_data[0]
if isinstance(first_item, dict):
return first_item.get('text', '-')
return '-'
class HadisAdmin(ModelAdmin):
"""Admin for Hadis model"""
form = HadisAdminForm
list_display = ('number', 'title', 'category', 'status', 'hadis_status', 'created_at','slug')
list_display = ('number', 'get_title_display', 'category', 'status', 'hadis_status', 'created_at','slug')
list_filter = ('status', 'hadis_status', 'category', 'created_at')
search_fields = ('title', 'text', 'category__title', 'slug')
readonly_fields = ('created_at', 'updated_at', 'share_link', 'slug')
@ -212,13 +671,13 @@ class HadisAdmin(ModelAdmin):
fieldsets = (
(None, {
'fields': ('category', 'number', 'title', 'status', 'slug')
'fields': ('category', 'number', 'title', 'title_narrator', 'status', 'slug')
}),
(_('Content'), {
'fields': ('text', 'translation', 'explanation')
'fields': ('text', 'translation', 'explanation','description')
}),
(_('Status & Classification'), {
'fields': ('hadis_status', 'hadis_status_text', 'tags')
'fields': ('hadis_status','tags')
}),
(_('Additional Information'), {
'fields': ('address', 'links', 'share_link'),
@ -230,9 +689,22 @@ class HadisAdmin(ModelAdmin):
}),
)
@display(description=_('Title'), ordering='title')
def get_title_display(self, obj):
return self._extract_first_text(obj.title)
def _extract_first_text(self, json_data):
"""Helper to safely extract the first 'text' from a JSON list"""
if json_data and isinstance(json_data, list) and len(json_data) > 0:
first_item = json_data[0]
if isinstance(first_item, dict):
return first_item.get('text', '-')
return '-'
class HadisReferenceAdmin(ModelAdmin):
"""Admin for HadisReference model"""
form = HadisReferenceAdminForm
list_display = ('hadis', 'book_reference', 'created_at')
list_filter = ('created_at', 'book_reference')
search_fields = ('hadis__title', 'book_reference__title')
@ -241,7 +713,7 @@ class HadisReferenceAdmin(ModelAdmin):
fieldsets = (
(None, {
'fields': ('hadis', 'book_reference')
'fields': ('hadis', 'book_reference','description')
}),
(_('Timestamps'), {
'fields': ('created_at',),
@ -274,7 +746,8 @@ class HadisInCollectionInline(TabularInline):
class HadisCollectionAdmin(ModelAdmin):
"""Admin for HadisCollection model"""
list_display = ('title', 'slug', 'status', 'order', 'created_at')
form = HadisCollectionAdminForm
list_display = ('get_title_display', 'slug', 'status', 'order', 'created_at')
list_filter = ('status', 'created_at')
search_fields = ('title', 'slug', 'summary')
readonly_fields = ('slug', 'created_at', 'updated_at')
@ -283,7 +756,7 @@ class HadisCollectionAdmin(ModelAdmin):
fieldsets = (
(None, {
'fields': ('title', 'slug', 'summary', 'status', 'order', 'thumbnail')
'fields': ('title', 'slug', 'summary', 'status', 'order')
}),
(_('Timestamps'), {
'fields': ('created_at', 'updated_at'),
@ -291,6 +764,18 @@ class HadisCollectionAdmin(ModelAdmin):
}),
)
@display(description=_('Title'), ordering='title')
def get_title_display(self, obj):
return self._extract_first_text(obj.title)
def _extract_first_text(self, json_data):
"""Helper to safely extract the first 'text' from a JSON list"""
if json_data and isinstance(json_data, list) and len(json_data) > 0:
first_item = json_data[0]
if isinstance(first_item, dict):
return first_item.get('text', '-')
return '-'
class HadisInCollectionAdmin(ModelAdmin):
"""Admin for HadisInCollection model"""
@ -416,7 +901,7 @@ class HadisCorrectionAdminForm(forms.ModelForm):
class HadisCorrectionAdmin(ModelAdmin):
"""Admin for HadisCorrection model"""
form = HadisCorrectionAdminForm
list_display = ('hadis', 'title', 'slug', 'created_at')
list_display = ('hadis', 'get_title_display', 'slug', 'created_at')
list_filter = ('created_at', 'hadis__category')
search_fields = ('hadis__title', 'title', 'slug')
readonly_fields = ('slug', 'created_at', 'updated_at', 'share_link')
@ -439,6 +924,18 @@ class HadisCorrectionAdmin(ModelAdmin):
}),
)
@display(description=_('Title'), ordering='title')
def get_title_display(self, obj):
return self._extract_first_text(obj.title)
def _extract_first_text(self, json_data):
"""Helper to safely extract the first 'text' from a JSON list"""
if json_data and isinstance(json_data, list) and len(json_data) > 0:
first_item = json_data[0]
if isinstance(first_item, dict):
return first_item.get('text', '-')
return '-'
# Register models with dovoodi admin site
dovoodi_admin_site.register(HadisTag, HadisTagAdmin)

243
apps/hadis/admin/reference.py

@ -227,6 +227,174 @@ class BookAttributeAdminForm(forms.ModelForm):
})
class BookAuthorAdminForm(forms.ModelForm):
"""Custom form for BookAuthor with JSON editor widgets"""
class Meta:
model = BookAuthor
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"]
}
}
# Apply JSON editor widgets
self.fields['name'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(name_schema),
'title': 'Names'
})
class BookReferenceImageAdminForm(forms.ModelForm):
"""Custom form for BookReferenceImage with JSON editor widgets"""
class Meta:
model = BookReferenceImage
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"]
}
}
# Apply JSON editor widgets
self.fields['description'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(description_schema),
'title': 'Descriptions'
})
class BookSubjectAreaAdminForm(forms.ModelForm):
"""Custom form for BookSubjectArea with JSON editor widgets"""
class Meta:
model = BookSubjectArea
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"]
}
}
# Apply JSON editor widgets
self.fields['title'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(title_schema),
'title': 'Titles'
})
class BookTypeAdminForm(forms.ModelForm):
"""Custom form for BookType with JSON editor widgets"""
class Meta:
model = BookType
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"]
}
}
# Apply JSON editor widgets
self.fields['title'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(title_schema),
'title': 'Titles'
})
# -----------------------------------------------------------------------------
# 1. Inlines
# -----------------------------------------------------------------------------
@ -236,6 +404,7 @@ class BookReferenceImageInline(TabularInline):
Inline for managing Book Images directly inside the BookReference page.
"""
model = BookReferenceImage
form = BookReferenceImageAdminForm
extra = 0
fields = ('image', 'order', 'description')
readonly_fields = ('created_at',)
@ -285,10 +454,10 @@ class BookReferenceAdmin(ModelAdmin):
fieldsets = (
(_('Basic Info'), {
'fields': ('title', 'description', 'slug', 'language', 'subject_area')
'fields': ('title', 'description', 'slug', 'language')
}),
(_('Publication Info'), {
'fields': ('publisher', 'isbn', 'year_of_publication', 'number_page', 'volume', 'type')
'fields': ('publisher', 'isbn', 'year_of_publication', 'number_page', 'volume')
}),
(_('Rating & Stats'), {
'fields': ('rate', 'created_at', 'updated_at')
@ -316,6 +485,7 @@ class BookReferenceAdmin(ModelAdmin):
class BookAuthorAdmin(ModelAdmin):
"""Admin for BookAuthor model"""
form = BookAuthorAdminForm
list_display = ('get_name_display', 'birth_year_hijri', 'death_year_hijri', 'birth_year_miladi', 'death_year_miladi', 'created_at', 'updated_at')
search_fields = ('name',) # Note: Search works best on exact text matches
@ -353,6 +523,9 @@ class BookAuthorAdmin(ModelAdmin):
return '-'
class BookReferenceImageAdmin(ModelAdmin):
"""Admin for BookReferenceImage model"""
form = BookReferenceImageAdminForm
# Display the custom string, plus the raw order and book link for convenience
list_display = ("display_name", "order", "book_reference")
@ -404,44 +577,46 @@ class BookAttributeAdmin(ModelAdmin):
return '-'
class BookSubjectAreaAdmin(ModelAdmin):
"""Admin for BookSubjectArea model"""
# class BookSubjectAreaAdmin(ModelAdmin):
# """Admin for BookSubjectArea model"""
# form = BookSubjectAreaAdminForm
list_display = ('get_title_display', 'created_at', 'updated_at')
search_fields = ('title',)
readonly_fields = ('created_at', 'updated_at')
# list_display = ('get_title_display', 'created_at', 'updated_at')
# search_fields = ('title',)
# readonly_fields = ('created_at', 'updated_at')
@display(description=_('Title'), ordering='title')
def get_title_display(self, obj):
return self._extract_first_text(obj.title)
# @display(description=_('Title'), ordering='title')
# def get_title_display(self, obj):
# return self._extract_first_text(obj.title)
def _extract_first_text(self, json_data):
"""Helper to safely extract the first 'text' from a JSON list"""
if json_data and isinstance(json_data, list) and len(json_data) > 0:
first_item = json_data[0]
if isinstance(first_item, dict):
return first_item.get('text', '-')
return '-'
# def _extract_first_text(self, json_data):
# """Helper to safely extract the first 'text' from a JSON list"""
# if json_data and isinstance(json_data, list) and len(json_data) > 0:
# first_item = json_data[0]
# if isinstance(first_item, dict):
# return first_item.get('text', '-')
# return '-'
class BookTypeAdmin(ModelAdmin):
"""Admin for BookType model"""
# class BookTypeAdmin(ModelAdmin):
# """Admin for BookType model"""
# form = BookTypeAdminForm
list_display = ('get_title_display', 'created_at', 'updated_at')
search_fields = ('title',)
readonly_fields = ('created_at', 'updated_at')
# list_display = ('get_title_display', 'created_at', 'updated_at')
# search_fields = ('title',)
# readonly_fields = ('created_at', 'updated_at')
@display(description=_('Title'), ordering='title')
def get_title_display(self, obj):
return self._extract_first_text(obj.title)
# @display(description=_('Title'), ordering='title')
# def get_title_display(self, obj):
# return self._extract_first_text(obj.title)
def _extract_first_text(self, json_data):
"""Helper to safely extract the first 'text' from a JSON list"""
if json_data and isinstance(json_data, list) and len(json_data) > 0:
first_item = json_data[0]
if isinstance(first_item, dict):
return first_item.get('text', '-')
return '-'
# def _extract_first_text(self, json_data):
# """Helper to safely extract the first 'text' from a JSON list"""
# if json_data and isinstance(json_data, list) and len(json_data) > 0:
# first_item = json_data[0]
# if isinstance(first_item, dict):
# return first_item.get('text', '-')
# return '-'
# -----------------------------------------------------------------------------
@ -452,5 +627,5 @@ dovoodi_admin_site.register(BookReference, BookReferenceAdmin)
dovoodi_admin_site.register(BookAuthor, BookAuthorAdmin)
dovoodi_admin_site.register(BookAttribute, BookAttributeAdmin)
dovoodi_admin_site.register(BookReferenceImage, BookReferenceImageAdmin)
dovoodi_admin_site.register(BookSubjectArea, BookSubjectAreaAdmin)
dovoodi_admin_site.register(BookType, BookTypeAdmin)
# dovoodi_admin_site.register(BookSubjectArea, BookSubjectAreaAdmin)
# dovoodi_admin_site.register(BookType, BookTypeAdmin)

164
apps/hadis/admin/transmitter.py

@ -34,8 +34,98 @@ class TransmitterOriginalTextInline(TabularInline):
fields = ('title', 'slug')
# Custom Forms for JSON Fields
class TransmittersAdminForm(forms.ModelForm):
"""Custom form for Transmitters with JSON editor widgets"""
class Meta:
model = Transmitters
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'
})
class TransmittersAdmin(ModelAdmin):
"""Admin for Transmitters model"""
form = TransmittersAdminForm
list_display = ('get_full_name_display', 'birth_year_hijri', 'death_year_hijri')
list_filter = ('birth_year_hijri', 'death_year_hijri')
search_fields = ('full_name', 'description')
@ -44,10 +134,10 @@ class TransmittersAdmin(ModelAdmin):
fieldsets = (
(None, {
'fields': ('full_name', 'birth_year_hijri', 'death_year_hijri')
'fields': ('full_name', 'birth_year_hijri', 'death_year_hijri','known_as','nickname')
}),
(_('Additional Information'), {
'fields': ('description',),
'fields': ('description','origin','lived_in','died_in','kunya'),
# 'classes': ('collapse',)
}),
(_('Timestamps'), {
@ -422,7 +512,7 @@ class TransmitterOriginalTextAdminForm(forms.ModelForm):
class NarratorLayerAdmin(ModelAdmin):
"""Admin for NarratorLayer model"""
form = NarratorLayerAdminForm
list_display = ('number', 'name', 'slug', 'created_at')
list_display = ('number', 'get_name_display', 'slug', 'created_at')
list_filter = ('created_at', 'updated_at')
search_fields = ('name', 'slug')
readonly_fields = ('slug', 'created_at', 'updated_at')
@ -441,11 +531,23 @@ class NarratorLayerAdmin(ModelAdmin):
}),
)
@display(description=_('Name'), ordering='name')
def get_name_display(self, obj):
return self._extract_first_text(obj.name)
def _extract_first_text(self, json_data):
"""Helper to safely extract the first 'text' from a JSON list"""
if json_data and isinstance(json_data, list) and len(json_data) > 0:
first_item = json_data[0]
if isinstance(first_item, dict):
return first_item.get('text', '-')
return '-'
class TransmitterReliabilityAdmin(ModelAdmin):
"""Admin for TransmitterReliability model"""
form = TransmitterReliabilityAdminForm
list_display = ('title', 'slug', 'color')
list_display = ('get_title_display', 'slug', 'color')
list_filter = ('color',)
search_fields = ('title', 'slug')
readonly_fields = ('slug',)
@ -456,11 +558,23 @@ class TransmitterReliabilityAdmin(ModelAdmin):
}),
)
@display(description=_('Title'), ordering='title')
def get_title_display(self, obj):
return self._extract_first_text(obj.title)
def _extract_first_text(self, json_data):
"""Helper to safely extract the first 'text' from a JSON list"""
if json_data and isinstance(json_data, list) and len(json_data) > 0:
first_item = json_data[0]
if isinstance(first_item, dict):
return first_item.get('text', '-')
return '-'
class OpinionStatusAdmin(ModelAdmin):
"""Admin for OpinionStatus model"""
form = OpinionStatusAdminForm
list_display = ('title', 'slug', 'color')
list_display = ('get_title_display', 'slug', 'color')
list_filter = ('color',)
search_fields = ('title', 'slug')
readonly_fields = ('slug',)
@ -471,13 +585,25 @@ class OpinionStatusAdmin(ModelAdmin):
}),
)
@display(description=_('Title'), ordering='title')
def get_title_display(self, obj):
return self._extract_first_text(obj.title)
def _extract_first_text(self, json_data):
"""Helper to safely extract the first 'text' from a JSON list"""
if json_data and isinstance(json_data, list) and len(json_data) > 0:
first_item = json_data[0]
if isinstance(first_item, dict):
return first_item.get('text', '-')
return '-'
class TransmitterOpinionAdmin(ModelAdmin):
"""Admin for TransmitterOpinion model"""
form = TransmitterOpinionAdminForm
list_display = ('transmitter', 'scholar_name', 'status', 'created_at')
list_display = ('transmitter', 'get_scholar_name_display', 'status', 'created_at')
list_filter = ('status', 'created_at', 'transmitter')
search_fields = ('transmitter__full_name', 'scholar_name')
readonly_fields = ('created_at', 'updated_at')
@ -495,6 +621,18 @@ class TransmitterOpinionAdmin(ModelAdmin):
}),
)
@display(description=_('Scholar Name'), ordering='scholar_name')
def get_scholar_name_display(self, obj):
return self._extract_first_text(obj.scholar_name)
def _extract_first_text(self, json_data):
"""Helper to safely extract the first 'text' from a JSON list"""
if json_data and isinstance(json_data, list) and len(json_data) > 0:
first_item = json_data[0]
if isinstance(first_item, dict):
return first_item.get('text', '-')
return '-'
@ -502,7 +640,7 @@ class TransmitterOpinionAdmin(ModelAdmin):
class TransmitterOriginalTextAdmin(ModelAdmin):
"""Admin for TransmitterOriginalText model"""
form = TransmitterOriginalTextAdminForm
list_display = ('transmitter', 'title', 'slug')
list_display = ('transmitter', 'get_title_display', 'slug')
list_filter = ('transmitter',)
search_fields = ('transmitter__full_name', 'title', 'slug')
readonly_fields = ('slug',)
@ -520,6 +658,18 @@ class TransmitterOriginalTextAdmin(ModelAdmin):
}),
)
@display(description=_('Title'), ordering='title')
def get_title_display(self, obj):
return self._extract_first_text(obj.title)
def _extract_first_text(self, json_data):
"""Helper to safely extract the first 'text' from a JSON list"""
if json_data and isinstance(json_data, list) and len(json_data) > 0:
first_item = json_data[0]
if isinstance(first_item, dict):
return first_item.get('text', '-')
return '-'
# Register models with the custom admin site
dovoodi_admin_site.register(Transmitters, TransmittersAdmin)

110
apps/hadis/management/commands/seed_hadisstatus.py

@ -0,0 +1,110 @@
import json
from django.core.management.base import BaseCommand
from django.db import transaction
# REPLACE 'apps.hadis.models' with your actual app path
from apps.hadis.models import HadisStatus
class Command(BaseCommand):
help = 'Updates HadisStatus descriptions mapping Russian/English titles to trilingual academic definitions'
def handle(self, *args, **options):
self.stdout.write("Starting update of HadisStatus descriptions...")
# 1. DEFINITIONS LIBRARY
# We define the content for each 'Type' of status found in your DB
# SAHIH (Authentic) -> Matches: "Достоверный", "Authentic / Accepted"
desc_sahih = {
'en': "A Sahih (Authentic) Hadith represents the highest level of reliability. It possesses a continuous chain of narrators (Isnad) made up of upright (Adl) and retentive (Dabit) transmitters, free from any hidden defects (Illah) or irregularities (Shudhudh). It is universally accepted for deriving Islamic laws.",
'fa': "حدیث صحیح بالاترین درجه اعتبار را دارد. این حدیث دارای سندی متصل است که راویان آن عادل و دارای ضبط (حافظه قوی) هستند و در آن هیچ‌گونه علت (عیب پنهان) یا شذوذ (مخالفت با راویان معتبرتر) وجود ندارد. این حدیث مبنای استنباط احکام شرعی است.",
'ru': "Хадис Сахих (Достоверный) представляет собой высший уровень надежности. Он имеет непрерывную цепочку передатчиков (иснад), состоящую из праведных ('адль) и обладающих хорошей памятью (дабит) людей, свободен от скрытых недостатков ('илля) или отклонений (шузуз). Он безоговорочно принимается для вынесения шариатских решений."
}
# HASAN (Good) -> Matches: "Хороший"
desc_hasan = {
'en': "A Hasan (Good) Hadith is valid and acceptable for legal rulings. It fulfills all conditions of a Sahih hadith (continuity, uprightness, lack of defects), except that the memory or precision of one or more narrators is slightly below the perfect standard required for Sahih.",
'fa': "حدیث حسن حدیثی معتبر و قابل استناد در احکام است. این حدیث تمام شرایط حدیث صحیح (اتصال سند، عدالت راوی، عدم عیب) را دارد، با این تفاوت که ضبط (دقت و حافظه) یک یا چند نفر از راویان آن کمی پایین‌تر از حد عالی لازم برای حدیث صحیح است.",
'ru': "Хадис Хасан (Хороший) является действительным и приемлемым. Он отвечает всем условиям хадиса Сахих (непрерывность, праведность, отсутствие недостатков), за исключением того, что память или точность одного из передатчиков немного ниже идеального стандарта, требуемого для Сахих."
}
# DA'IF (Weak) -> Matches: "Слабый", "Weak / Needs Review"
desc_weak = {
'en': "A Da'if (Weak) Hadith fails to reach the rank of Hasan because it lacks one or more essential conditions of authenticity. This could be due to a disconnection in the chain of narrators or a significant weakness in the character or memory of a narrator. It is generally not used for legal rulings.",
'fa': "حدیث ضعیف حدیثی است که به درجه حسن نمی‌رسد زیرا فاقد یک یا چند شرط اصلی صحت است. این امر می‌تواند ناشی از قطع در سند روایت یا ضعف جدی در شخصیت یا حافظه راوی باشد. این حدیث معمولاً مبنای احکام فقهی قرار نمی‌گیرد.",
'ru': "Хадис Даиф (Слабый) не достигает уровня Хасан, так как в нем отсутствует одно или несколько условий достоверности. Это может быть связано с разрывом в цепочке передатчиков или значительной слабостью в характере или памяти передатчика. Обычно он не используется для правовых решений."
}
# MAWDU (Fabricated) -> Matches: "Выдуманный"
desc_fabricated = {
'en': "A Mawdu' (Fabricated) Hadith is a false narration attributed to the Prophet (PBUH) which he did not say. It is considered rejected (Mardud) and is impermissible to convey except to expose its fabrication and warn the community against it.",
'fa': "حدیث موضوع (جعلی) روایتی دروغین است که به پیامبر (ص) نسبت داده شده در حالی که ایشان آن را نفرموده‌اند. این حدیث مردود است و نقل آن حرام می‌باشد، مگر برای افشای جعلی بودن و هشدار دادن به جامعه درباره آن.",
'ru': "Хадис Мавду (Выдуманный) — это ложное предание, приписанное Пророку (мир ему), которого он не говорил. Он считается отвергнутым (Мардуд), и его передача запрещена, кроме как для разоблачения его ложности и предостережения общины."
}
# BROKEN / INTERRUPTED -> Matches: "Разорванный", "Прерванный"
desc_broken = {
'en': "This category ('Munqati' or 'Maqtu') refers to a Hadith with a non-continuous chain of transmission. There is a missing link (or links) between the narrators, meaning the person reporting the Hadith did not hear it directly from the source they are quoting.",
'fa': "این دسته (منقطع یا مقطوع) به حدیثی اشاره دارد که سند آن پیوسته نیست. در زنجیره راویان حلقه‌ای مفقود وجود دارد، به این معنا که نقل‌کننده حدیث، آن را مستقیماً از کسی که از او نقل می‌کند، نشنیده است.",
'ru': "Эта категория ('Мункати' или 'Макту') относится к хадису с прерывистой цепочкой передачи. Между передатчиками отсутствует одно или несколько звеньев; это означает, что человек, передающий хадис, не слышал его непосредственно от источника, который он цитирует."
}
# UNKNOWN -> Matches: "Неизвестный"
desc_unknown = {
'en': "This status ('Majhul') indicates that a narrator in the chain is not identified or their reliability status is unknown to scholars. Without verification of the narrator's identity and trustworthiness, the narration cannot be authenticated.",
'fa': "این وضعیت (مجهول) نشان می‌دهد که یکی از راویان در سند شناخته شده نیست یا وضعیت اعتبار او برای علما نامشخص است. بدون احراز هویت و وثاقت راوی، روایت قابل تایید و استناد نمی‌باشد.",
'ru': "Этот статус ('Маджхуль') указывает на то, что передатчик в цепочке не идентифицирован или его статус надежности неизвестен ученым. Без подтверждения личности и благонадежности передатчика предание не может быть признано достоверным."
}
# 2. MAPPING LOGIC
# We define keywords to look for in the title (searching both English and Russian text)
mapping_rules = [
(['достоверный', 'authentic', 'accepted'], desc_sahih),
(['хороший', 'good'], desc_hasan),
(['слабый', 'weak', 'needs review'], desc_weak),
(['выдуманный', 'fabricated'], desc_fabricated),
(['разорванный', 'прерванный', 'broken', 'interrupted'], desc_broken),
(['неизвестный', 'unknown'], desc_unknown),
]
count = 0
qs = HadisStatus.objects.all()
if not qs.exists():
self.stdout.write(self.style.WARNING("No HadisStatus objects found."))
return
for status in qs:
# 1. Extract all text from the title list into a single lowercase search string
# Example title: [{"text": "Достоверный", "language_code": "ru"}] -> "достоверный"
search_text = ""
if isinstance(status.title, list):
search_text = " ".join([item.get('text', '') for item in status.title]).lower()
# 2. Find the correct definition
selected_desc = None
found_key = ""
for keywords, description in mapping_rules:
# Check if ANY keyword exists in the search text
if any(k in search_text for k in keywords):
selected_desc = description
found_key = search_text
break
# 3. Update or Skip
if selected_desc:
# Construct the new JSON format
new_description = [
{"language_code": "en", "text": selected_desc['en']},
{"language_code": "fa", "text": selected_desc['fa']},
{"language_code": "ru", "text": selected_desc['ru']}
]
status.description = new_description
status.save()
count += 1
self.stdout.write(f"Updated ID {status.id} ({found_key}) -> mapped to new description.")
else:
self.stdout.write(self.style.WARNING(f"Skipped ID {status.id}: Title '{search_text}' did not match any known categories."))
self.stdout.write(self.style.SUCCESS(f"Successfully updated {count} HadisStatus descriptions."))
Loading…
Cancel
Save