@ -516,18 +516,8 @@ class HadisCategoryXMindView(APIView):
Leaves - > Hadiths
"""
def get_localized ( self , json_field , lang ) :
if not json_field or not isinstance ( json_field , list ) :
return " "
for item in json_field :
if isinstance ( item , dict ) and item . get ( ' language_code ' ) == lang :
return item . get ( ' text ' , ' ' )
for item in json_field :
if isinstance ( item , dict ) and item . get ( ' language_code ' ) == ' en ' :
return item . get ( ' text ' , ' ' )
if len ( json_field ) > 0 and isinstance ( json_field [ 0 ] , dict ) :
return json_field [ 0 ] . get ( ' text ' , ' ' )
return " "
def get_localized ( self , json_field , lang = ' en ' ) :
return get_localized_text ( json_field , language_code = lang ) or " "
def get_arabic ( self , json_field ) :
return get_arabic_localized_text ( json_field ) or " "
@ -544,7 +534,7 @@ class HadisCategoryXMindView(APIView):
@hadis_category_xmind_swagger
def get ( self , request , category_slug ) :
lang = request . query_params . get ( ' lang ' , ' en ' )
lang = request . query_params . get ( ' lang ' ) or request . query_params . get ( ' language_code ' ) or ' en '
category = get_object_or_404 ( HadisCategory , slug = category_slug )
root_title = self . get_localized ( category . title , lang ) or category . slug
@ -555,14 +545,18 @@ class HadisCategoryXMindView(APIView):
# Fetch active hadiths with their transmitters and corrections
transmitter_qs = HadisTransmitter . objects . select_related (
' transmitter__reliability ' ,
' status '
) . order_by ( ' chain_index ' , ' order ' )
' status ' ,
' narrator_layer '
) . prefetch_related (
' uncertain_transmitters__reliability '
) . order_by ( ' order ' )
hadiths = Hadis . objects . filter (
category = category ,
status = True
) . order_by ( ' number ' ) . prefetch_related (
Prefetch ( ' transmitters ' , queryset = transmitter_qs ) ,
' narrator_layers ' ,
' hadiscorrection_set '
)
@ -619,13 +613,15 @@ class HadisCategoryXMindView(APIView):
" corrections " : corrections_data ,
}
# Group transmitters by chain_index
chain_groups = defaultdict ( list )
# Group transmitters by narrative layer (NarratorLayer)
# Each narrative layer forms its own transmission branch
layer_groups = defaultdict ( list )
for ht in hadis . transmitters . all ( ) :
if ht . transmitter :
chain_groups [ ht . chain_index ] . append ( ht )
layer_key = ht . narrator_layer_id if ht . narrator_layer_id is not None else f " chain-{ht.chain_index} "
layer_groups [ layer_key ] . append ( ht )
if not chain _groups:
if not layer _groups:
# No transmitters: directly attach hadith to category root
hadis_key = ( " hadith " , hadis . id )
if hadis_key not in root_tree [ " children_map " ] :
@ -637,18 +633,34 @@ class HadisCategoryXMindView(APIView):
root_tree [ " children_map " ] [ hadis_key ] [ " leaf_hadiths " ] . add ( hadis . id )
root_tree [ " leaf_hadiths " ] . add ( hadis . id )
else :
for chain_idx , ht_list in chain_groups . items ( ) :
ht_list . sort ( key = lambda x : x . order )
# Sort layers by layer number if available
sorted_layer_items = sorted (
layer_groups . items ( ) ,
key = lambda item : ( item [ 1 ] [ 0 ] . narrator_layer . number if item [ 1 ] and item [ 1 ] [ 0 ] . narrator_layer else 0 )
)
for layer_key , ht_list in sorted_layer_items :
# Sort transmitters in reverse order so the chain goes from first narrator to last narrator reaching the hadith
ht_list . sort ( key = lambda x : x . order , reverse = True )
curr = root_tree
curr [ " leaf_hadiths " ] . add ( hadis . id )
for ht in ht_list :
tr = ht . transmitter
tr_key = ( " narrator " , tr . id )
is_uncertain = getattr ( ht , ' is_uncertain ' , False )
cand_objs = list ( ht . uncertain_transmitters . all ( ) ) if is_uncertain else [ ]
is_node_uncertain = bool ( is_uncertain and len ( cand_objs ) > 0 )
if is_node_uncertain :
cand_ids = tuple ( sorted ( [ ut . id for ut in cand_objs ] ) )
tr_key = ( " uncertain " , cand_ids )
else :
cand_ids = ( )
tr_key = ( " narrator " , tr . id if tr else ht . id )
if tr_key not in curr [ " children_map " ] :
rel_obj = tr . reliability or ht . status
rel_obj = ( tr . reliability if tr else None ) or ht . status
rel_data = None
if rel_obj :
rel_data = {
@ -659,33 +671,94 @@ class HadisCategoryXMindView(APIView):
" slug " : getattr ( rel_obj , ' slug ' , None ) ,
}
layer_obj = ht . narrator_layer
layer_data = None
if layer_obj :
layer_data = {
" id " : layer_obj . id ,
" number " : layer_obj . number ,
" slug " : layer_obj . slug ,
" name " : self . get_localized ( layer_obj . name , lang ) ,
" description " : self . get_localized ( layer_obj . description , lang ) ,
}
uncertain_candidates = [ ]
if is_node_uncertain :
for ut in cand_objs :
ut_rel = ut . reliability
ut_rel_data = None
if ut_rel :
ut_rel_data = {
" id " : ut_rel . id ,
" title " : self . get_localized ( ut_rel . title , lang ) ,
" color " : getattr ( ut_rel , ' color ' , None ) ,
" main_color_code " : getattr ( ut_rel , ' main_color_code ' , None ) ,
" slug " : getattr ( ut_rel , ' slug ' , None ) ,
}
uncertain_candidates . append ( {
" id " : ut . id ,
" transmitter_id " : ut . id ,
" slug " : ut . slug ,
" name " : self . get_localized ( ut . full_name , lang ) or ut . slug ,
" title " : self . get_localized ( ut . full_name , lang ) or ut . slug ,
" full_name " : self . get_localized ( ut . full_name , lang ) ,
" arabic_full_name " : self . get_arabic ( ut . full_name ) ,
" kunya " : self . get_localized ( ut . kunya , lang ) ,
" arabic_kunya " : self . get_arabic ( ut . kunya ) ,
" known_as " : self . get_localized ( ut . known_as , lang ) ,
" arabic_known_as " : self . get_arabic ( ut . known_as ) ,
" nickname " : self . get_localized ( ut . nickname , lang ) ,
" arabic_nickname " : self . get_arabic ( ut . nickname ) ,
" birth_year_hijri " : ut . birth_year_hijri ,
" death_year_hijri " : ut . death_year_hijri ,
" reliability " : ut_rel_data ,
" thumbnail " : self . _get_thumbnail_url ( ut . thumbnail , request ) ,
" share_link " : ut . share_link ,
} )
node_type_str = " uncertain " if is_node_uncertain else " narrator "
node_id_str = f " uncertain-{ ' - ' .join(map(str, cand_ids))} " if is_node_uncertain else f " narrator-{tr.id if tr else ht.id} "
tr_node_data = {
" id " : f " narrator-{tr.id} " ,
" transmitter_id " : tr . id ,
" node_type " : " narrator " ,
" slug " : tr . slug ,
" title " : self . get_localized ( tr . full_name , lang ) or tr . slug ,
" full_name " : self . get_localized ( tr . full_name , lang ) ,
" arabic_full_name " : self . get_arabic ( tr . full_name ) ,
" kunya " : self . get_localized ( tr . kunya , lang ) ,
" arabic_kunya " : self . get_arabic ( tr . kunya ) ,
" known_as " : self . get_localized ( tr . known_as , lang ) ,
" arabic_known_as " : self . get_arabic ( tr . known_as ) ,
" nickname " : self . get_localized ( tr . nickname , lang ) ,
" arabic_nickname " : self . get_arabic ( tr . nickname ) ,
" birth_year_hijri " : tr . birth_year_hijri ,
" death_year_hijri " : tr . death_year_hijri ,
" age_at_death " : tr . age_at_death ,
" generation " : tr . generation ,
" tadlis " : tr . tadlis ,
" ikhtilat " : tr . ikhtilat ,
" companion_type " : tr . companion_type ,
" madhhab " : tr . madhhab ,
" in_sahih_muslim " : tr . in_sahih_muslim ,
" in_sahih_bukhari " : tr . in_sahih_bukhari ,
" id " : node_id_str ,
" transmitter_id " : tr . id if tr else ( uncertain_candidates [ 0 ] [ " id " ] if uncertain_candidates else None ) ,
" node_type " : node_type_str ,
" tag " : node_type_str ,
" slug " : tr . slug if tr else ( uncertain_candidates [ 0 ] [ " slug " ] if uncertain_candidates else None ) ,
" title " : " Uncertain narrator " if is_node_uncertain else ( self . get_localized ( tr . full_name , lang ) or tr . slug if tr else " Narrator " ) ,
" full_name " : " Uncertain narrator " if is_node_uncertain else ( self . get_localized ( tr . full_name , lang ) if tr else " Narrator " ) ,
" arabic_full_name " : self . get_arabic ( tr . full_name ) if tr else None ,
" kunya " : self . get_localized ( tr . kunya , lang ) if tr else None ,
" arabic_kunya " : self . get_arabic ( tr . kunya ) if tr else None ,
" known_as " : self . get_localized ( tr . known_as , lang ) if tr else None ,
" arabic_known_as " : self . get_arabic ( tr . known_as ) if tr else None ,
" nickname " : self . get_localized ( tr . nickname , lang ) if tr else None ,
" arabic_nickname " : self . get_arabic ( tr . nickname ) if tr else None ,
" birth_year_hijri " : tr . birth_year_hijri if tr else None ,
" death_year_hijri " : tr . death_year_hijri if tr else None ,
" age_at_death " : tr . age_at_death if tr else None ,
" generation " : tr . generation if tr else None ,
" tadlis " : tr . tadlis if tr else None ,
" ikhtilat " : tr . ikhtilat if tr else None ,
" companion_type " : tr . companion_type if tr else None ,
" madhhab " : tr . madhhab if tr else None ,
" in_sahih_muslim " : tr . in_sahih_muslim if tr else None ,
" in_sahih_bukhari " : tr . in_sahih_bukhari if tr else None ,
" reliability " : rel_data ,
" thumbnail " : self . _get_thumbnail_url ( tr . thumbnail , request ) ,
" share_link " : tr . share_link ,
" is_uncertain " : is_node_uncertain ,
" uncertain_transmitters " : uncertain_candidates ,
" transmitter " : {
" id " : tr . id ,
" name " : self . get_localized ( tr . full_name , lang ) or tr . slug ,
" full_name " : self . get_localized ( tr . full_name , lang ) ,
" slug " : tr . slug ,
" reliability " : rel_data ,
} if tr else None ,
" layer " : layer_data ,
" layer_number " : layer_obj . number if layer_obj else None ,
" layer_name " : self . get_localized ( layer_obj . name , lang ) if layer_obj else None ,
" thumbnail " : self . _get_thumbnail_url ( tr . thumbnail , request ) if tr else None ,
" share_link " : tr . share_link if tr else None ,
}
curr [ " children_map " ] [ tr_key ] = {
" data " : tr_node_data ,
@ -705,18 +778,16 @@ class HadisCategoryXMindView(APIView):
}
curr [ " children_map " ] [ hadis_key ] [ " leaf_hadiths " ] . add ( hadis . id )
# Convert internal trie to the standard XMind JSON tree structure
# Format prefix tree into recursive XMind JSON structure
def format_tree_node ( node_dict , path_prefix = " " ) :
node_data = dict ( node_dict [ " data " ] ) if " data " in node_dict else { }
children_map = node_dict . get ( " children_map " , { } )
# Form unique instance ID for tree canvas
base_id = node_data . get ( " id " , " node " )
unique_node_id = f " {path_prefix}_{base_id} " if path_prefix else base_id
node_data = dict ( node_dict [ " data " ] )
node_type = node_data . get ( " node_type " , " unknown " )
raw_id = node_data . get ( " id " , " 0 " )
unique_node_id = f " {path_prefix}_{node_type}_{raw_id} "
node_data [ " node_id " ] = unique_node_id
attached = [ ]
for ( child_type , child_id ) , child_subtree in children_map . items ( ) :
for ( child_type , child_id ) , child_subtree in node_dict [ " children_map" ] . items ( ) :
formatted_child = format_tree_node ( child_subtree , path_prefix = unique_node_id )
attached . append ( formatted_child )
@ -746,4 +817,8 @@ class HadisCategoryXMindView(APIView):
}
}
return Response ( data )
response = Response ( data )
response [ ' Cache-Control ' ] = ' no-cache, no-store, must-revalidate '
response [ ' Pragma ' ] = ' no-cache '
response [ ' Expires ' ] = ' 0 '
return response