@ -1,12 +1,12 @@
import os
import os
import json
import json
import csv
import csv
import re
from django.core.management.base import BaseCommand
from django.core.management.base import BaseCommand
from django.core.files import File
from django.core.files import File
from django.db import transaction
from django.db import transaction
from django.conf import settings
from django.conf import settings
# Import all necessary models
from apps.hadis.models import (
from apps.hadis.models import (
HadisCategory , HadisSect , HadisStatus , HadisTag , Hadis ,
HadisCategory , HadisSect , HadisStatus , HadisTag , Hadis ,
HadisCorrection , HadisReference , ReferenceImage , HadisTransmitter ,
HadisCorrection , HadisReference , ReferenceImage , HadisTransmitter ,
@ -23,16 +23,12 @@ class Command(BaseCommand):
parser . add_argument ( ' base_dir ' , type = str , help = ' Absolute path to the " тестовая база данных " directory ' )
parser . add_argument ( ' base_dir ' , type = str , help = ' Absolute path to the " тестовая база данных " directory ' )
def wrap_lang ( self , text , lang = " ru " ) :
def wrap_lang ( self , text , lang = " ru " ) :
""" Helper to format strings into the [ { ' language_code ' : lang, ' text ' : text} ] schema.
Always returns a valid dictionary to bypass Django ' s blank=False validators. " " "
if text is None :
text = " "
if text is None : text = " "
return [ { " language_code " : lang , " text " : str ( text ) . strip ( ) } ]
return [ { " language_code " : lang , " text " : str ( text ) . strip ( ) } ]
@transaction.atomic
@transaction.atomic
def handle ( self , * args , * * kwargs ) :
def handle ( self , * args , * * kwargs ) :
base_dir = kwargs [ ' base_dir ' ]
base_dir = kwargs [ ' base_dir ' ]
if not os . path . exists ( base_dir ) :
if not os . path . exists ( base_dir ) :
self . stderr . write ( self . style . ERROR ( f ' Directory not found: {base_dir} ' ) )
self . stderr . write ( self . style . ERROR ( f ' Directory not found: {base_dir} ' ) )
return
return
@ -45,6 +41,28 @@ class Command(BaseCommand):
narrators_path = os . path . join ( base_dir , ' narrators.json ' )
narrators_path = os . path . join ( base_dir , ' narrators.json ' )
tathir_path = os . path . join ( base_dir , ' tathir.json ' )
tathir_path = os . path . join ( base_dir , ' tathir.json ' )
# --- PRE-SCAN TATHIR.JSON FOR CITED VOLUMES ---
cited_book_volumes = { }
if os . path . exists ( tathir_path ) :
with open ( tathir_path , ' r ' , encoding = ' utf-8 ' ) as f :
t_data = json . load ( f ) . get ( ' materials ' , [ ] )
for item in t_data :
for ed in item . get ( ' editions ' , [ ] ) :
b_id = ed . get ( ' book_id ' )
b_vol = str ( ed . get ( ' volume ' ) ) . strip ( ) if ed . get ( ' volume ' ) is not None else ' '
if b_vol . lower ( ) == ' none ' : b_vol = ' '
if b_id :
if b_id not in cited_book_volumes :
cited_book_volumes [ b_id ] = set ( )
if b_vol :
try : cited_book_volumes [ b_id ] . add ( str ( int ( b_vol ) ) )
except ValueError : cited_book_volumes [ b_id ] . add ( b_vol )
# --- PRE-FLIGHT CLEANUP ---
self . stdout . write ( self . style . WARNING ( ' \n --- PRE-FLIGHT: Cleaning up old legacy books --- ' ) )
BookReference . objects . exclude ( legacy_id__isnull = True ) . exclude ( legacy_id__exact = ' ' ) . delete ( )
# --- PHASE 1: SCHOLARS & BOOKS ---
# --- PHASE 1: SCHOLARS & BOOKS ---
self . stdout . write ( self . style . WARNING ( ' \n --- PHASE 1: Loading Scholars & Books --- ' ) )
self . stdout . write ( self . style . WARNING ( ' \n --- PHASE 1: Loading Scholars & Books --- ' ) )
@ -54,11 +72,7 @@ class Command(BaseCommand):
reader = csv . reader ( f )
reader = csv . reader ( f )
for row in reader :
for row in reader :
if len ( row ) > = 3 :
if len ( row ) > = 3 :
scholars_map [ row [ 0 ] . strip ( ) ] = {
" ar " : row [ 1 ] . strip ( ) ,
" ru " : row [ 2 ] . strip ( )
}
self . stdout . write ( f ' Loaded {len(scholars_map)} scholars into memory. ' )
scholars_map [ row [ 0 ] . strip ( ) ] = { " ar " : row [ 1 ] . strip ( ) , " ru " : row [ 2 ] . strip ( ) }
if os . path . exists ( bib_path ) :
if os . path . exists ( bib_path ) :
with open ( bib_path , ' r ' , encoding = ' utf-8 ' ) as f :
with open ( bib_path , ' r ' , encoding = ' utf-8 ' ) as f :
@ -70,67 +84,83 @@ class Command(BaseCommand):
author_name = row [ 2 ] . strip ( )
author_name = row [ 2 ] . strip ( )
base_title = row [ 3 ] . strip ( )
base_title = row [ 3 ] . strip ( )
# Extract total volumes (Column 11 / Index 10)
vol_str = row [ 10 ] . strip ( ) if len ( row ) > 10 else ' '
vol_str = row [ 10 ] . strip ( ) if len ( row ) > 10 else ' '
try :
total_vols = int ( vol_str ) if vol_str . isdigit ( ) else 1
except ValueError :
total_vols = 1
# Create a BookReference for EVERY volume
for v in range ( 1 , total_vols + 1 ) :
# Generate unique ID and Title for multi-volume books
is_multi_vol = total_vols > 1
legacy_id = f " {base_legacy_id}-v{v} " if is_multi_vol else base_legacy_id
title_text = f " {base_title} (Vol {v}) " if is_multi_vol else base_title
try : total_vols_int = int ( vol_str ) if vol_str . isdigit ( ) else 1
except ValueError : total_vols_int = 1
existing_vols = set ( )
book_folder = os . path . join ( base_dir , ' books ' , base_legacy_id )
if os . path . exists ( book_folder ) :
for item in os . listdir ( book_folder ) :
if os . path . isdir ( os . path . join ( book_folder , item ) ) :
try : existing_vols . add ( str ( int ( item ) ) )
except ValueError : existing_vols . add ( item )
volumes_to_create = existing_vols . union ( cited_book_volumes . get ( base_legacy_id , set ( ) ) )
if not volumes_to_create : volumes_to_create = { ' ' }
for v in volumes_to_create :
legacy_id = f " {base_legacy_id}-v{v} " if v else base_legacy_id
title_text = f " {base_title} (Vol {v}) " if v else base_title
book , _ = BookReference . objects . update_or_create (
book , _ = BookReference . objects . update_or_create (
legacy_id = legacy_id ,
legacy_id = legacy_id ,
defaults = {
defaults = {
' title ' : self . wrap_lang ( title_text ) ,
' title ' : self . wrap_lang ( title_text ) ,
' number_of_volumes ' : total_vols ,
' volume ' : str ( v ) ,
' order ' : int ( row [ 1 ] ) if len ( row ) > 1 and row [ 1 ] . isdigit ( ) else 0 ,
' researcher ' : self . wrap_lang ( row [ 4 ] . strip ( ) if len ( row ) > 4 else ' ' ) ,
' publisher ' : self . wrap_lang ( row [ 5 ] . strip ( ) if len ( row ) > 5 else ' ' ) ,
' city_of_publication ' : self . wrap_lang ( row [ 6 ] . strip ( ) if len ( row ) > 6 else ' ' ) ,
' country_of_publication ' : self . wrap_lang ( row [ 7 ] . strip ( ) if len ( row ) > 7 else ' ' ) ,
' edition_number ' : row [ 8 ] . strip ( ) if len ( row ) > 8 else ' ' ,
' year_of_publication ' : row [ 9 ] . strip ( ) if len ( row ) > 9 else ' ' ,
' year_of_publication ' : row [ 9 ] . strip ( ) if len ( row ) > 9 else ' ' ,
' number_of_volumes ' : total_vols_int ,
' volume ' : v ,
' source_url ' : row [ 11 ] . strip ( ) if len ( row ) > 11 else ' ' ,
' source_url ' : row [ 11 ] . strip ( ) if len ( row ) > 11 else ' ' ,
' description ' : self . wrap_lang ( row [ 12 ] . strip ( ) if len ( row ) > 12 else ' ' ) ,
' description ' : self . wrap_lang ( row [ 12 ] . strip ( ) if len ( row ) > 12 else ' ' ) ,
' publisher ' : self . wrap_lang ( row [ 5 ] . strip ( ) if len ( row ) > 5 else ' ' ) ,
' language ' : self . wrap_lang ( ' ' )
' language ' : self . wrap_lang ( ' ' )
}
}
)
)
# Author
if author_name :
if author_name :
author , _ = BookAuthor . objects . get_or_create ( name = self . wrap_lang ( author_name ) )
author , _ = BookAuthor . objects . get_or_create ( name = self . wrap_lang ( author_name ) )
book . authors . add ( author )
book . authors . add ( author )
# Scan Book Folder for Specific Volume Images and PDFs
book_folder = os . path . join ( base_dir , ' books ' , base_legacy_id )
if os . path . exists ( book_folder ) :
vol_num_str = str ( v )
vol_padded_str = str ( v ) . zfill ( 2 ) # "1" -> "01"
# Map Book Tags
if len ( row ) > 13 and row [ 13 ] . strip ( ) :
for t in row [ 13 ] . split ( ' , ' ) :
if t . strip ( ) :
btag , _ = BookSubjectArea . objects . get_or_create ( title = self . wrap_lang ( t . strip ( ) ) )
book . subject_area . add ( btag )
# Attach Media
if os . path . exists ( book_folder ) :
for root , _ , files in os . walk ( book_folder ) :
for root , _ , files in os . walk ( book_folder ) :
folder_name = os . path . basename ( root )
folder_name = os . path . basename ( root )
is_root = ( root == book_folder )
for file in files :
for file in files :
file_path = os . path . join ( root , file )
file_lower = file . lower ( )
file_lower = file . lower ( )
# Attach PDF if it matches "1.pdf" or "01.pdf"
file_path = os . path . join ( root , file )
if file_lower . endswith ( ' .pdf ' ) :
if file_lower . endswith ( ' .pdf ' ) :
if file_lower in [ f " {vol_num_str }.pdf " , f " {vol_padded_str }.pdf " ] or not is_multi_vol :
if v and file_lower in [ f " {v}.pdf " , f " {v.zfill(2) }.pdf " ] :
with open ( file_path , ' rb ' ) as doc_f :
with open ( file_path , ' rb ' ) as doc_f :
doc = BookReferenceDocument ( book_reference = book , volume = vol_num_str , title = file )
doc = BookReferenceDocument ( book_reference = book , volume = v , title = file )
doc . file . save ( file , File ( doc_f ) , save = True )
elif not v and is_root and not file_lower [ 0 ] . isdigit ( ) :
with open ( file_path , ' rb ' ) as doc_f :
doc = BookReferenceDocument ( book_reference = book , volume = v , title = file )
doc . file . save ( file , File ( doc_f ) , save = True )
doc . file . save ( file , File ( doc_f ) , save = True )
# Attach Images if they are in folder "1" or "01"
elif file_lower . endswith ( ( ' .png ' , ' .jpg ' , ' .jpeg ' , ' .gif ' ) ) :
elif file_lower . endswith ( ( ' .png ' , ' .jpg ' , ' .jpeg ' , ' .gif ' ) ) :
if folder_name in [ vol_num_str , vol_padded_str ] or not is_multi_vol :
if v and not is_root and folder_name . lstrip ( ' 0 ' ) == v . lstrip ( ' 0 ' ) :
with open ( file_path , ' rb ' ) as img_f :
with open ( file_path , ' rb ' ) as img_f :
img = BookReferenceImage ( book_reference = book , volume = vol_num_str )
img = BookReferenceImage ( book_reference = book , volume = v )
img . image . save ( file , File ( img_f ) , save = True )
elif not v and is_root :
with open ( file_path , ' rb ' ) as img_f :
img = BookReferenceImage ( book_reference = book , volume = v )
img . image . save ( file , File ( img_f ) , save = True )
img . image . save ( file , File ( img_f ) , save = True )
self . stdout . write ( self . style . SUCCESS ( ' Books (split by volumes) loaded successfully. ' ) )
self . stdout . write ( self . style . SUCCESS ( ' Books loaded successfully. ' ) )
# --- PHASE 2: NARRATORS ---
# --- PHASE 2: NARRATORS ---
@ -142,25 +172,29 @@ class Command(BaseCommand):
for n_data in n_data_list :
for n_data in n_data_list :
legacy_id = n_data . get ( ' id ' )
legacy_id = n_data . get ( ' id ' )
legacy_number = int ( n_data . get ( ' narrator_number ' ) ) if str ( n_data . get ( ' narrator_number ' ) ) . isdigit ( ) else None
legacy_number = int ( n_data . get ( ' narrator_number ' ) ) if str ( n_data . get ( ' narrator_number ' ) ) . isdigit ( ) else None
info = n_data . get ( ' info ' , { } )
info = n_data . get ( ' info ' , { } )
ar_info = info . get ( ' arabic ' , { } )
ar_info = info . get ( ' arabic ' , { } )
reliability , _ = TransmitterReliability . objects . get_or_create (
title = self . wrap_lang ( n_data . get ( ' reliability ' , ' Unknown ' ) )
)
reliability , _ = TransmitterReliability . objects . get_or_create ( title = self . wrap_lang ( n_data . get ( ' reliability ' , ' Unknown ' ) ) )
generation = int ( n_data . get ( ' generation ' ) ) if str ( n_data . get ( ' generation ' ) ) . isdigit ( ) else None
generation = int ( n_data . get ( ' generation ' ) ) if str ( n_data . get ( ' generation ' ) ) . isdigit ( ) else None
if generation :
if generation :
NarratorLayer . objects . get_or_create (
number = generation ,
defaults = {
' name ' : self . wrap_lang ( f ' Layer {generation} ' ) ,
' description ' : self . wrap_lang ( ' ' )
}
)
NarratorLayer . objects . get_or_create ( number = generation , defaults = { ' name ' : self . wrap_lang ( f ' Layer {generation} ' ) , ' description ' : self . wrap_lang ( ' ' ) } )
# Safe Age Extraction
age_str = info . get ( ' age ' , ' ' )
age_nums = re . findall ( r ' \ d+ ' , str ( age_str ) )
age_val = int ( age_nums [ 0 ] ) if age_nums else None
# Madhhab Translation
madhhab_list = n_data . get ( ' madhab ' , [ ] )
madhhab_val = Transmitters . MadhhabChoices . UNKNOWN
if madhhab_list :
m_str = str ( madhhab_list [ 0 ] ) . lower ( )
if ' шиит ' in m_str : madhhab_val = Transmitters . MadhhabChoices . SHIA
elif ' суннит ' in m_str : madhhab_val = Transmitters . MadhhabChoices . SUNNI
else : madhhab_val = Transmitters . MadhhabChoices . OTHER
# Create Transmitter
transmitter , _ = Transmitters . objects . update_or_create (
transmitter , _ = Transmitters . objects . update_or_create (
legacy_id = legacy_id ,
legacy_id = legacy_id ,
defaults = {
defaults = {
@ -177,11 +211,20 @@ class Command(BaseCommand):
' reliability ' : reliability ,
' reliability ' : reliability ,
' in_sahih_bukhari ' : n_data . get ( ' transmitted_to_bukhari ' , False ) ,
' in_sahih_bukhari ' : n_data . get ( ' transmitted_to_bukhari ' , False ) ,
' in_sahih_muslim ' : n_data . get ( ' transmitted_to_muslim ' , False ) ,
' in_sahih_muslim ' : n_data . get ( ' transmitted_to_muslim ' , False ) ,
' relatives_raw ' : info . get ( ' relatives ' , { } )
' relatives_raw ' : info . get ( ' relatives ' , { } ) ,
# NEW FIELDS MAPPED
' freed_slave_of ' : self . wrap_lang ( info . get ( ' freed_slave_of ' , ' ' ) , ' ru ' ) + self . wrap_lang ( ar_info . get ( ' freed_slave_of ' , ' ' ) , ' ar ' ) ,
' occupation ' : self . wrap_lang ( info . get ( ' occupation ' , ' ' ) , ' ru ' ) + self . wrap_lang ( ar_info . get ( ' occupation ' , ' ' ) , ' ar ' ) ,
' features ' : self . wrap_lang ( info . get ( ' features ' , ' ' ) , ' ru ' ) + self . wrap_lang ( ar_info . get ( ' features ' , ' ' ) , ' ar ' ) ,
' birth_year_hijri ' : str ( info . get ( ' birth_year ' , ' ' ) ) ,
' death_year_hijri ' : str ( info . get ( ' death_year ' , ' ' ) ) ,
' age_at_death ' : age_val ,
' tags ' : n_data . get ( ' tags ' , [ ] ) ,
' madhhab ' : madhhab_val ,
}
}
)
)
# Opinions
for op in n_data . get ( ' strengthened_weakened ' , { } ) . get ( ' review ' , [ ] ) :
for op in n_data . get ( ' strengthened_weakened ' , { } ) . get ( ' review ' , [ ] ) :
author_ui = op . get ( ' author_ui ' )
author_ui = op . get ( ' author_ui ' )
scholar_data = scholars_map . get ( author_ui , { " ar " : author_ui , " ru " : author_ui } )
scholar_data = scholars_map . get ( author_ui , { " ar " : author_ui , " ru " : author_ui } )
@ -191,7 +234,6 @@ class Command(BaseCommand):
scholar_name = self . wrap_lang ( scholar_data [ ' ar ' ] , ' ar ' ) + self . wrap_lang ( scholar_data [ ' ru ' ] , ' ru ' )
scholar_name = self . wrap_lang ( scholar_data [ ' ar ' ] , ' ar ' ) + self . wrap_lang ( scholar_data [ ' ru ' ] , ' ru ' )
)
)
# Original Texts
for text_data in n_data . get ( ' excerpts ' , [ ] ) :
for text_data in n_data . get ( ' excerpts ' , [ ] ) :
orig_text , _ = TransmitterOriginalText . objects . get_or_create (
orig_text , _ = TransmitterOriginalText . objects . get_or_create (
transmitter = transmitter ,
transmitter = transmitter ,
@ -199,14 +241,16 @@ class Command(BaseCommand):
text = self . wrap_lang ( text_data . get ( ' text ' ) , ' ar ' ) ,
text = self . wrap_lang ( text_data . get ( ' text ' ) , ' ar ' ) ,
translation = self . wrap_lang ( text_data . get ( ' translation ' ) , ' ru ' )
translation = self . wrap_lang ( text_data . get ( ' translation ' ) , ' ru ' )
)
)
for ed in text_data . get ( ' editions ' , [ ] ) :
for ed in text_data . get ( ' editions ' , [ ] ) :
book_ref = self . _get_book_volume ( ed . get ( ' book_id ' ) , ed . get ( ' volume ' ) )
book_ref = self . _get_book_volume ( ed . get ( ' book_id ' ) , ed . get ( ' volume ' ) )
ref_obj , _ = OriginalTextReference . objects . get _or_create(
ref_obj , _ = OriginalTextReference . objects . update _or_create(
original_text = orig_text , book_reference = book_ref ,
original_text = orig_text , book_reference = book_ref ,
volume = ed . get ( ' volume ' ) , page = ed . get ( ' page ' ) , url = ed . get ( ' url ' )
defaults = {
' volume ' : str ( ed . get ( ' volume ' , ' ' ) ) ,
' page ' : str ( ed . get ( ' pages ' , ' ' ) ) , # Fixed from 'page'
' url ' : ed . get ( ' url ' , ' ' )
}
)
)
folder = ed . get ( ' screenshots_folder ' )
folder = ed . get ( ' screenshots_folder ' )
if folder :
if folder :
self . _attach_images ( os . path . join ( base_dir , ' screens_trx ' , legacy_id , folder ) , OriginalTextReferenceImage , ref_obj )
self . _attach_images ( os . path . join ( base_dir , ' screens_trx ' , legacy_id , folder ) , OriginalTextReferenceImage , ref_obj )
@ -214,21 +258,17 @@ class Command(BaseCommand):
self . stdout . write ( self . style . SUCCESS ( ' Narrators loaded successfully. ' ) )
self . stdout . write ( self . style . SUCCESS ( ' Narrators loaded successfully. ' ) )
# --- PHASE 3: HADITHS (Arguments, Corrections, Interpretations) ---
# --- PHASE 3: HADITHS ---
self . stdout . write ( self . style . WARNING ( ' \n --- PHASE 3: Loading Hadiths --- ' ) )
self . stdout . write ( self . style . WARNING ( ' \n --- PHASE 3: Loading Hadiths --- ' ) )
default_sect , _ = HadisSect . objects . get_or_create (
default_sect , _ = HadisSect . objects . get_or_create (
sect_type = ' sunni ' ,
sect_type = ' sunni ' ,
defaults = {
' title ' : self . wrap_lang ( ' Sunni ' ) ,
' description ' : self . wrap_lang ( ' ' )
}
defaults = { ' title ' : self . wrap_lang ( ' Sunni ' ) , ' description ' : self . wrap_lang ( ' ' ) }
)
)
if os . path . exists ( tathir_path ) :
if os . path . exists ( tathir_path ) :
with open ( tathir_path , ' r ' , encoding = ' utf-8 ' ) as f :
with open ( tathir_path , ' r ' , encoding = ' utf-8 ' ) as f :
materials = json . load ( f ) . get ( ' materials ' , [ ] )
materials = json . load ( f ) . get ( ' materials ' , [ ] )
# Map corrections to their parent hadiths
correction_to_hadith_map = { }
correction_to_hadith_map = { }
for item in materials :
for item in materials :
if item . get ( ' type ' ) == ' arguments ' :
if item . get ( ' type ' ) == ' arguments ' :
@ -243,11 +283,7 @@ class Command(BaseCommand):
cat_str = item . get ( ' category ' , [ ' ' ] ) [ 0 ]
cat_str = item . get ( ' category ' , [ ' ' ] ) [ 0 ]
category , _ = HadisCategory . objects . get_or_create (
category , _ = HadisCategory . objects . get_or_create (
title = self . wrap_lang ( cat_str ) ,
title = self . wrap_lang ( cat_str ) ,
defaults = {
' sect ' : default_sect ,
' source_type ' : item . get ( ' subtype ' , ' hadith ' ) or ' hadith ' ,
' description ' : self . wrap_lang ( ' ' )
}
defaults = { ' sect ' : default_sect , ' source_type ' : item . get ( ' subtype ' , ' hadith ' ) or ' hadith ' , ' description ' : self . wrap_lang ( ' ' ) }
)
)
status , _ = HadisStatus . objects . get_or_create (
status , _ = HadisStatus . objects . get_or_create (
title = self . wrap_lang ( item . get ( ' authenticity ' , ' ' ) ) ,
title = self . wrap_lang ( item . get ( ' authenticity ' , ' ' ) ) ,
@ -257,8 +293,7 @@ class Command(BaseCommand):
hadis , _ = Hadis . objects . update_or_create (
hadis , _ = Hadis . objects . update_or_create (
legacy_id = item . get ( ' id ' ) ,
legacy_id = item . get ( ' id ' ) ,
defaults = {
defaults = {
' category ' : category ,
' hadis_status ' : status ,
' category ' : category , ' hadis_status ' : status ,
' title ' : self . wrap_lang ( item . get ( ' aliases ' , [ ' ' ] ) [ 0 ] if item . get ( ' aliases ' ) else ' ' ) ,
' title ' : self . wrap_lang ( item . get ( ' aliases ' , [ ' ' ] ) [ 0 ] if item . get ( ' aliases ' ) else ' ' ) ,
' title_narrator ' : self . wrap_lang ( item . get ( ' aliases ' , [ ' ' ] ) [ 0 ] if item . get ( ' aliases ' ) else ' ' ) ,
' title_narrator ' : self . wrap_lang ( item . get ( ' aliases ' , [ ' ' ] ) [ 0 ] if item . get ( ' aliases ' ) else ' ' ) ,
' description ' : self . wrap_lang ( ' ' ) ,
' description ' : self . wrap_lang ( ' ' ) ,
@ -270,15 +305,17 @@ class Command(BaseCommand):
}
}
)
)
# Map Hadith Tags
hadis . tags . clear ( )
for tag_str in item . get ( ' tags ' , [ ] ) :
htag , _ = HadisTag . objects . get_or_create ( title = self . wrap_lang ( tag_str ) )
hadis . tags . add ( htag )
raw_chain = item . get ( ' chain ' , [ ] )
raw_chain = item . get ( ' chain ' , [ ] )
chain_arrays = [ ]
chain_arrays = [ ]
if raw_chain :
if raw_chain :
# Normalize: If it's a flat list of ints, wrap it in a list so it's a 2D array
if isinstance ( raw_chain [ 0 ] , int ) :
chain_arrays = [ raw_chain ]
else :
chain_arrays = raw_chain
if isinstance ( raw_chain [ 0 ] , int ) : chain_arrays = [ raw_chain ]
else : chain_arrays = raw_chain
for chain_idx , narrator_ids in enumerate ( chain_arrays ) :
for chain_idx , narrator_ids in enumerate ( chain_arrays ) :
for order_idx , n_id in enumerate ( narrator_ids ) :
for order_idx , n_id in enumerate ( narrator_ids ) :
@ -289,12 +326,18 @@ class Command(BaseCommand):
hadis = hadis , transmitter = transmitter , chain_index = chain_idx , order = order_idx ,
hadis = hadis , transmitter = transmitter , chain_index = chain_idx , order = order_idx ,
defaults = { ' narrator_layer ' : layer , ' status ' : transmitter . reliability }
defaults = { ' narrator_layer ' : layer , ' status ' : transmitter . reliability }
)
)
# Editions & Images
for ed in item . get ( ' editions ' , [ ] ) :
for ed in item . get ( ' editions ' , [ ] ) :
book = self . _get_book_volume ( ed . get ( ' book_id ' ) , ed . get ( ' volume ' ) )
book = self . _get_book_volume ( ed . get ( ' book_id ' ) , ed . get ( ' volume ' ) )
href , _ = HadisReference . objects . get _or_create(
href , _ = HadisReference . objects . update _or_create(
hadis = hadis , book_reference = book ,
hadis = hadis , book_reference = book ,
defaults = { ' hadith_number ' : str ( ed . get ( ' hadith_number ' , ' ' ) ) , ' description ' : self . wrap_lang ( ' ' ) }
defaults = {
' hadith_number ' : str ( ed . get ( ' hadith_number ' , ' ' ) ) ,
' description ' : self . wrap_lang ( ' ' ) ,
' volume ' : str ( ed . get ( ' volume ' , ' ' ) ) ,
' pages ' : str ( ed . get ( ' pages ' , ' ' ) ) ,
' url ' : ed . get ( ' url ' , ' ' )
}
)
)
if ed . get ( ' screenshots_folder ' ) :
if ed . get ( ' screenshots_folder ' ) :
self . _attach_images ( os . path . join ( base_dir , ' screens ' , item . get ( ' id ' ) , ed . get ( ' screenshots_folder ' ) ) , ReferenceImage , href , field_name = ' thumbnail ' )
self . _attach_images ( os . path . join ( base_dir , ' screens ' , item . get ( ' id ' ) , ed . get ( ' screenshots_folder ' ) ) , ReferenceImage , href , field_name = ' thumbnail ' )
@ -303,19 +346,27 @@ class Command(BaseCommand):
elif i_type == ' authenticity_analysis ' :
elif i_type == ' authenticity_analysis ' :
parent_id = correction_to_hadith_map . get ( item . get ( ' id ' ) )
parent_id = correction_to_hadith_map . get ( item . get ( ' id ' ) )
parent_hadith = Hadis . objects . filter ( legacy_id = parent_id ) . first ( )
parent_hadith = Hadis . objects . filter ( legacy_id = parent_id ) . first ( )
if parent_hadith :
if parent_hadith :
corr , _ = HadisCorrection . objects . get_or_create (
# CHANGE TO update_or_create HERE:
corr , _ = HadisCorrection . objects . update_or_create (
hadis = parent_hadith , legacy_id = item . get ( ' id ' ) ,
hadis = parent_hadith , legacy_id = item . get ( ' id ' ) ,
defaults = {
defaults = {
' title ' : self . wrap_lang ( ' ' ) ,
' title ' : self . wrap_lang ( ' ' ) ,
' text ' : item . get ( ' original_text ' , ' ' ) , # Directly mapped to TextField
' text ' : item . get ( ' original_text ' , ' ' ) ,
' translation ' : self . wrap_lang ( item . get ( ' translation ' , ' ' ) , ' ru ' )
' translation ' : self . wrap_lang ( item . get ( ' translation ' , ' ' ) , ' ru ' )
}
}
)
)
for ed in item . get ( ' editions ' , [ ] ) :
for ed in item . get ( ' editions ' , [ ] ) :
book = self . _get_book_volume ( ed . get ( ' book_id ' ) , ed . get ( ' volume ' ) )
book = self . _get_book_volume ( ed . get ( ' book_id ' ) , ed . get ( ' volume ' ) )
cref , _ = CorrectionReference . objects . get_or_create ( correction = corr , book_reference = book , defaults = { ' hadith_number ' : str ( ed . get ( ' hadith_number ' , ' ' ) ) } )
cref , _ = CorrectionReference . objects . update_or_create (
correction = corr , book_reference = book ,
defaults = {
' hadith_number ' : str ( ed . get ( ' hadith_number ' , ' ' ) ) ,
' volume ' : str ( ed . get ( ' volume ' , ' ' ) ) ,
' pages ' : str ( ed . get ( ' pages ' , ' ' ) ) ,
' url ' : ed . get ( ' url ' , ' ' )
}
)
if ed . get ( ' screenshots_folder ' ) :
if ed . get ( ' screenshots_folder ' ) :
self . _attach_images ( os . path . join ( base_dir , ' screens ' , item . get ( ' id ' ) , ed . get ( ' screenshots_folder ' ) ) , CorrectionReferenceImage , cref )
self . _attach_images ( os . path . join ( base_dir , ' screens ' , item . get ( ' id ' ) , ed . get ( ' screenshots_folder ' ) ) , CorrectionReferenceImage , cref )
@ -323,9 +374,9 @@ class Command(BaseCommand):
elif i_type == ' interpretation ' :
elif i_type == ' interpretation ' :
cat_str = item . get ( ' category ' , [ ' ' ] ) [ 0 ] if item . get ( ' category ' ) else ' '
cat_str = item . get ( ' category ' , [ ' ' ] ) [ 0 ] if item . get ( ' category ' ) else ' '
category = HadisCategory . objects . filter ( title__contains = [ { ' text ' : cat_str } ] ) . first ( )
category = HadisCategory . objects . filter ( title__contains = [ { ' text ' : cat_str } ] ) . first ( )
if category :
if category :
interp , _ = HadisInterpretation . objects . get_or_create (
# CHANGE TO update_or_create HERE:
interp , _ = HadisInterpretation . objects . update_or_create (
category = category , legacy_id = item . get ( ' id ' ) ,
category = category , legacy_id = item . get ( ' id ' ) ,
defaults = {
defaults = {
' title ' : self . wrap_lang ( ' ' ) ,
' title ' : self . wrap_lang ( ' ' ) ,
@ -335,7 +386,15 @@ class Command(BaseCommand):
)
)
for ed in item . get ( ' editions ' , [ ] ) :
for ed in item . get ( ' editions ' , [ ] ) :
book = self . _get_book_volume ( ed . get ( ' book_id ' ) , ed . get ( ' volume ' ) )
book = self . _get_book_volume ( ed . get ( ' book_id ' ) , ed . get ( ' volume ' ) )
iref , _ = InterpretationReference . objects . get_or_create ( interpretation = interp , book_reference = book , defaults = { ' hadith_number ' : str ( ed . get ( ' hadith_number ' , ' ' ) ) } )
iref , _ = InterpretationReference . objects . update_or_create (
interpretation = interp , book_reference = book ,
defaults = {
' hadith_number ' : str ( ed . get ( ' hadith_number ' , ' ' ) ) ,
' volume ' : str ( ed . get ( ' volume ' , ' ' ) ) ,
' pages ' : str ( ed . get ( ' pages ' , ' ' ) ) ,
' url ' : ed . get ( ' url ' , ' ' )
}
)
if ed . get ( ' screenshots_folder ' ) :
if ed . get ( ' screenshots_folder ' ) :
self . _attach_images ( os . path . join ( base_dir , ' screens ' , item . get ( ' id ' ) , ed . get ( ' screenshots_folder ' ) ) , InterpretationReferenceImage , iref )
self . _attach_images ( os . path . join ( base_dir , ' screens ' , item . get ( ' id ' ) , ed . get ( ' screenshots_folder ' ) ) , InterpretationReferenceImage , iref )
@ -344,15 +403,11 @@ class Command(BaseCommand):
def _get_book_volume ( self , book_id , volume_str ) :
def _get_book_volume ( self , book_id , volume_str ) :
""" Finds the specific volume of a book, with fallbacks. """
""" Finds the specific volume of a book, with fallbacks. """
if not book_id : return None
if not book_id : return None
# 1. Try to find specific volume (e.g., uuid-v2)
if volume_str :
if volume_str :
vol_clean = ' ' . join ( filter ( str . isdigit , str ( volume_str ) ) ) # extracts "2" from "Vol 2"
vol_clean = ' ' . join ( filter ( str . isdigit , str ( volume_str ) ) )
if vol_clean :
if vol_clean :
book = BookReference . objects . filter ( legacy_id = f " {book_id}-v{vol_clean} " ) . first ( )
book = BookReference . objects . filter ( legacy_id = f " {book_id}-v{vol_clean} " ) . first ( )
if book : return book
if book : return book
# 2. Fallback: Find the base book (single volume) or the first volume available
return BookReference . objects . filter ( legacy_id__startswith = book_id ) . first ( )
return BookReference . objects . filter ( legacy_id__startswith = book_id ) . first ( )
def _attach_images ( self , folder_path , ImageModelClass , reference_instance , field_name = ' image ' ) :
def _attach_images ( self , folder_path , ImageModelClass , reference_instance , field_name = ' image ' ) :
@ -363,7 +418,5 @@ class Command(BaseCommand):
file_path = os . path . join ( folder_path , filename )
file_path = os . path . join ( folder_path , filename )
with open ( file_path , ' rb ' ) as f :
with open ( file_path , ' rb ' ) as f :
img_obj = ImageModelClass ( reference = reference_instance , priority = i )
img_obj = ImageModelClass ( reference = reference_instance , priority = i )
# Dynamically grab the correct field ('image' or 'thumbnail')
image_field = getattr ( img_obj , field_name )
image_field = getattr ( img_obj , field_name )
image_field . save ( filename , File ( f ) , save = True )
image_field . save ( filename , File ( f ) , save = True )