U
    i                     @   sP   d dl Z d dlmZ d dlZd dlT d dlT d dlmZ G dd dej	j
ZdS )    N)defaultdict)*)timeitc                	   @   s  e Zd ZdZdZdZeedddddddZeedddZ	ee
d	d
ZeedddZee
dd
Zeedd
Zee
dd
Zee
dd
Zee
dd
Zee
dd
Zee
dd
Zee
dd
Zed9ddZed:ddZed;ddZed<ddZedd ZejG dd  d Zi ZdZ ed!d" Z!ed=d#d$Z"ed%d& Z#ed'd( Z$ee%e&d)d*d+Z'ed>e%e&e(j)j*d,d-d.Z+ed/d0 Z,ed1d2 Z-ee.de/j0d3d4d5d6 Z1e2d7d8 Z3dS )?RelationCacheu  
    Мастер таблица всех деревянных связей объектов. В т.ч. непрямых. Т.е. дерего развёрнутое.
    Не заменяет m2m таблицы.
    Работа с данными этой таблицы только через методы этой модели. Вся магия деревьев здесь.

    TODO: Мультиколоночные индексы id+field

    TODO: какой-нить лок на изменение дерева, т.к. одновременные изменения его поломают.
        Сейчас защита на select for update, но нет уверенности что этого достаточно.
    Fu)   Идентификатор объектаu3   Автоматически генерируетсяT)captionZcommentZnullableZprimary_keyreadonlyZvisibleu   ID родителя)r   indexu$   Поле связи родителя)r   u   ID потомкаu"   Поле связи потомкаu   Глубина связиu   Модель родителяu   Имя родителяu   Код родителяu   Модель потомкаu.   Опциональное имя потомкаu   Код потомкаNc                    s  dg}ddl m} ddlm} ||}	t|	|}
g }|jr8t|
ddr8|rXtdt|
t	j
jsntd|
jrd	d
 |
 j|dgdD }ndd
 |
 j|dgdD }|sg S g }dd
 |
jD D ]"\}}|dd|gdd|gg qt|dkrd%|}ddd|g|g} r2 fdd
|D }n|}nddd|gdd|gg} rdddd g|g}|r|ddddddddd d!g
7 }| j|||d"}|r|S td#d$ |D }|D ]}|| q|S )&u  
        full=True - возвращаем список экземпляров RelationCacheModel со всеми полями
        иначе только список child_id
        fixme: фильтр по child_field - нужен ли? Возможно
        r   r   cmfutil)confignested_fields_is_edge_parentFu]   get_children(full=True) не поддерживается для nested_fields_is_edge_parentuZ   TODO Тип поля не поддерживается для nested_fields_is_edge_parentc                 S   s   g | ]
}|j qS  )right_id.0ir   r   "./cmf/fields/cmf_relation_cache.py
<listcomp>P   s     z.RelationCache.get_children.<locals>.<listcomp>r   )left_idfieldsc                 S   s   g | ]
}|j qS r   )r   r   r   r   r   r   R   s     r   )r   r   c                 S   s   g | ]}| d qS ).splitr   r   r   r   r   Y   s     parent_model=parent_field   ORAND	parent_idINc                    s"   g | ]}| d d  kr|qS ):r   r   r   child_modelsr   r   r   a   s      ==child_modelparent_nameparent_codechild_field
child_name
child_codedepthr   filter
for_updatec                 S   s   h | ]}|j jqS r   )child_idvaluer   rowr   r   r   	<setcomp>r   s     z-RelationCache.get_children.<locals>.<setcomp>)r   )cmf.utilr
   cmf.includer   Zget_model_by_idgetattrRELATION_CACHE_EDGE_OPTIMIZE	Exception
issubclasscmfr   
CmfM2MBaseleftZm2m_model_clsZslistnested_fieldsappendlenlist)clsr   parent_field_namer#   fullr.   r   r
   r   r   r   Zadditional_children_idsZnested_ids_depth0Zparent_meta_filterZnested_model_namenested_field_namequery_filterresultresZadditional_idr   r"   r   get_children7   s`    

         zRelationCache.get_childrenc                 C   sP   t t}| j|||dD ]}||dd  | q|sLdd | D S |S )u   
        get_children с разбитием по классам, потом удобней фильтровать, подгружать.
        result = {"child_model1": ["child_id1", ...], ...}
        r"   r!   r   c                 S   s   i | ]\}}|t |qS r   r@   )r   Z
model_nameZids_setr   r   r   
<dictcomp>   s      z3RelationCache.get_children_dict.<locals>.<dictcomp>)r   setrH   r   additems)rA   r   rB   r#   Z
return_setrF   r2   r   r   r   get_children_dictw   s    zRelationCache.get_children_dictc           	      C   sZ   dg}ddd|gdd|gg}|r0ddd|g|g}| j |||d}|rH|S t d	d
 |D S )u   
        full=True - возвращаем список экземпляров RelationCacheModel со всеми полями
        иначе только список parent_id
        r   r   r/   r$   r(   r   r    r,   c                 S   s   h | ]}|j jqS r   )r   r0   r1   r   r   r   r3      s     z,RelationCache.get_parents.<locals>.<setcomp>rI   )	rA   r/   child_field_nameZparents_modelsrC   r.   r   rE   rF   r   r   r   get_parents   s    zRelationCache.get_parentsc	                 C   s  |r|s<t d| d| d| d| d||h|h|h 
| j||||dddgd}	|	r^d	S | j||ddd
}
| j||ddd
}| |||dd |||||dd ||dd}d}||g D ]}|
|g D ]}|j|j }|j|jf|j|jfkr|d7 }|j|j	f|j|j	fkr&|d7 }|j|jkrd}|d|j
 d|j d|j d|j d	7 }|d7 }|sddlm} || d}td|  | |j|j|j|j
|j|j|j	|j|j|j|d  qqd	S )u   Добавим прямую связь между полями двух объектов, а также связи между родителями и детьми.z%add_relation: try add None relation, (z, z) -> r   Tr   r   r   r/   r(   r+   r.   r   N)rC   r.   r!   )r   r   r   r&   r'   r/   r(   r%   r)   r*   r+   Fr   ud   Объект вложен сам в себя через промежуточные сущности: z) -> ... -> z).u    Зацикливание может привести к поблемам с производительностью, устраните зацикливание.r	   z"ERROR RelationCache cycle detect: )ZCmfOrmErrorr@   rH   rP   	partitionr+   r   r   r/   r(   r&   r'   r)   r*   r4   r
   Zadmin_alertgdebugr   r%   Zsave)rA   r   rB   r/   rO   r&   r'   r)   r*   direct_linkschildrenparentsdirect_linkZlinked_objects_cycle_alarmparentchildr+   Zerror_messager
   r   r   r   add_relation   sx    .          
*
     zRelationCache.add_relationc                 C   sb  | j ||||dddgd}|s"dS t|dkr`tdt| d| d	| d
| d	| 
|||||d }| j||dd}| j||dd}|| D ]}	|| D ]}
|	j|
j }|	j|	jf|j|jfkr|d7 }|
j|
j	f|j|j	fkr|d7 }| j
|	j|	j|
j|
j	|ddgd}|r|  qtd|	jj d	|	jj d
|
jj d	|
j	j d| 
|	|
|qqdS )u3   
        TODO: пакетный режим
        r   Tr   rR   Nr   ztoo many relation (z) z::z => )rC   zrelation absent z depth )r@   r?   LookupErrorrP   rH   r+   r   r   r/   r(   getdeleteFileNotFoundErrorr0   )rA   r   rB   r/   rO   rW   rZ   rY   rX   r[   r\   r+   linkr   r   r   remove_relation   s\    	    $       
0  zRelationCache.remove_relationc                   @   sN   e Zd ZU dZeed< ejedZ	eed< ejedZ
eed< dZeed< dS )	zRelationCache.FieldCacheu~   Данные по полю модели Cmf, отражающие его связи и положение в иерархии.field_class)default_factoryrX   	right_forFrecursion_doneN)__name__
__module____qualname____doc__ZCmfTypeMeta__annotations__dataclassesfieldr@   rX   rf   rg   boolr   r   r   r   
FieldCache&  s
   
rp   c                 C   sT   t |dg }t |ddr(|t |dg7 }t|trH|dd | D 7 }tt|S )u$   Список имён related_modelsmodelsmodelNc                 S   s   g | ]
}|j qS r   )
class_name)r   Z	sub_modelr   r   r   r   >  s     z8RelationCache.get_field_models_names.<locals>.<listcomp>)r6   r9   ZCmfSubclassedGenericRelationZrelated_modelsr@   rK   )rA   rn   Zfield_models_namesr   r   r   get_field_models_names6  s    
z$RelationCache.get_field_models_namesc                 C   s   | j | j| |rLd|kr6|dd  d| }| j | j||f | j | j}t|ddrxtd| d| t|t	rt|dds| j | j
sd| j | _
| |D ]}| || d	 qdS )
uA   Используется при создании cls.fields_cacher   r   r   Nu   Нельзя ссылаться с помощью nested_fields на поле, помеченное как nested_fields_is_edge_parent: z -> r=   Tz.id)_fields_cacherX   r>   rT   rf   rd   r6   r8   r9   CmfRelationBaserg   rt   
_add_child)rA   r[   r\   backrefr(   field_model_namer   r   r   rw   A  s     


zRelationCache._add_childc                 C   s6  t }ttj|d| _| jD ]T}|j D ]D}t|tj	j
jsJ|jdksJq*|j d|j }| j|d| j|< q*qt| j}|D ]}| j| }|j}t|tr|jr| |}t|ttfr&|D ]`}	d}
|jD ](}||	d r| j|||jd d}
q|
s| j||	 d|jpd |jd qqtd|qd	S )
ud   
        Вызывается после загрузки моделей при создании app)model_filteridr   )rd   F)rx   Tz#Unsupported nested_fields for fieldN)ro   r@   r
   Ziter_models_models_listr   valuesr9   r:   rq   Z
base_model
CmfRelBasers   rp   ru   sortedrd   r=   rt   ZCmfBackrefBaser;   
startswithrw   rx   	TypeError)rA   rz   rr   rn   field_full_nameZsorted_fields
field_nameZfield_cacheZfield_modelsZfield_modelfoundrD   r   r   r   build_fields_cacheW  s8    




  
z RelationCache.build_fields_cachec                 C   s8   | j |}|r4|jrdS |jr4t|jttfr4dS d S )NT)ru   r_   rf   rX   r9   rd   rv   r;   )rA   r   fcr   r   r   _is_tree_field  s    zRelationCache._is_tree_field)
inst_field	target_idc                 C   s  |j j}|j}| d| }| j| }|j}|dd }tjrF|jrF|jD ]`\}	}
|
d\}}}|	d\}}}t	|t
tfr||kr| |||j jj| qLtd|qL|jD ]L}|d\}}}t	|t
tfr||kr| |j jj||| qtd|qdS )uk  
        Рвём связь между field.instance и target_id, если она существует
        По field и fields_cache мы должны понять:
          - есть ли такая связь в кеше
          - поля, между которыми связь
          - кто родитель и кто потомок
        r   r!   r   Unsupported right_for for fieldUnsupported children for fieldN)instancers   ru   rd   rT   r   r7   r   rf   r9   rv   r;   rc   r{   r0   r   rX   )rA   r   r   ry   r   r   r   rn   target_model_nameparent_field_full_namechild_field_full_namechild_model_name_rO   parent_model_namerB   child_full_field_namer   r   r   remove_field_reference  s(    	
	
z$RelationCache.remove_field_reference)r   r   targetc                 C   s  |j j}|j}| d| }| j| }|js6|js6dS d}d}	|rp|jj}t|tj	j
rh|jj}|jj}	|j}
n|dd \}
}}|j}|j }d}d}t|tj	j
r|jj}|jj}tjr|jrdS |jD ]l\}}|d\}}}|d\}}}t|ttfr.|
|kr8| j|||jj|||	||d qtd|q|jD ]Z}|d\}}}t|ttfr|
|kr| j|jj|||||||	d n
td|q@dS )u`  
        Создаём связь между field.instance и target_id, если нужно
        По field и fields_cache мы должны понять:
          - есть ли такая связь в кеше
          - поля, между которыми связь
          - кто родитель и кто потомок
        r   Nr!   r   r&   r'   r)   r*   r   r   )r   rs   ru   rX   rf   r{   r0   
isinstancer:   rq   	CmfEntitynamecoderT   rd   r   r7   r   r9   rv   r;   r]   r   )rA   r   r   r   ry   r   r   r   Ztarget_nameZtarget_coder   r   rn   r   Zinstance_nameZinstance_coder   r   r   rO   r   rB   r   r   r   r   add_field_reference  sn    	

     

     z!RelationCache.add_field_referencec                 C   s  d }d }t |tjjr&|jj}|jj}|d\}}}| j| }|j	}	t
||	j}
|
sZd S d }d }t |
trt |
jtjjr|
jjj}|
jjj}|jD ]|\}}|d\}}}|d\}}}t|	tr|
jj|kr| j|
jjj||jj|||||d qt|	trqtd|q|jD ]}|d\}}}t|	trn|
jj|kr| j|jj||
jjj|||||d n|t|	tr|
jD ]\}|j|krd }d }t |tjjr|jj}|jj}| j|jj||jj|||||d qn
td|qd S )Nr   r   r   r   )r   r:   rq   r   r   r0   r   rT   ru   rd   r6   rs   rv   rf   r9   r]   r{   r;   r   rX   )rA   r   instZ	inst_nameZ	inst_codery   r   r   r   fr   Zinst_field_nameZinst_field_coder   r   r   rO   r   rB   r   Zrel_instZrel_inst_nameZrel_inst_coder   r   r   _make_field_relations  s    



    
    
    z#RelationCache._make_field_relationsc                 C   sP  d}d}d}| j D ]"}t|tjjs(qtd|j  |d7 }|j}|j	|
|jjd  }||j	|
|jjd  7 }t }g }|j	|j|j|jD ]@\}	}
}|
|f|kr|d7 }|d7 }||	 q||
|f q|j	|
|j|jdd |r|d7 }t|j d| d| d qt| d	| d
 d S )Nr   zClear r   F)Zsynchronize_sessionrS   z
 deleted, z duplicate refs/z m2m tables cleared)r|   r9   r:   rq   ZBaseM2MModelprintrs   dp_modeldpquery_deprecatedr-   r   Zisnotr`   Zroot_idrK   r{   r   r   r>   rL   Zin_)rA   Zmodels_countZ	dup_countZclearedrr   r   ZdeletedZuniq_relZdup_idsZrel_idr   r   r   r   r   clear_old_m2m_treeR  s0     $	 "z RelationCache.clear_old_m2m_tree<   Z	log_startZalarm_levelZalarm_thresholdc                    s   t   tjd}dtjd< td  j j	 }td| d  
  tdtjdd fd	d
} jD ]|j qt   |dkrtjd= n
|tjd< t   dS )u  
        Актуализация данных в таблице. Требуется монопольный режим для обеспечения целостности.
            TODO: Лок на таблицу
        Пока лучше выключать срм на время ребилда:
            systemctl stop eva-app
            cd /opt/eva-app
            python3 ./manage.py shell <<<"models.RelationCache.recalculate()"
        Есть смысл делать при обновлении, после наката миграций.

        TODO: clear=True - предварительно очистить таблицу.
        TODO: выборочный пересчёт
        ZNO_CACHE1zClear relation_cachez  z ClearedFr   r   c                    s   d}g }g }j  D ]J}tjr.t|ddr.qj d|j } |r|| ||j q|sjd S jd|dD ]"}|d	7 }|D ]} 	|| qqzt
d
j d| d d S )Nr   r   Fr   r{   r   r   r   r   zBuild relations from z done, z objects)r{   r   r   )r   r}   r   r7   r6   rs   r   r>   r@   r   r   )Z_model_nameZcount_Ztree_fields_full_namesZtree_fields_namesrn   r   r   r   rA   rr   r   r   create_model_relations  s"    

z9RelationCache.recalculate.<locals>.create_model_relationsN)Z	CMF_CACHEZflushdbosenvironr_   r   r   r   r   r`   r   r   loggingWARNINGr|   rs   fix_relation_locations)rA   Z	cache_oldcountr   r   r   r   recalculate|  s     



zRelationCache.recalculatec               	   C   sB  ddl m} m} ddlm} t| D ]}t|ts:q(t	|| r<t
d|j  d}d}d}|jdgdD ]}|d7 }|jr|js|d7 }t
d|j d	|jj d
|jj  |  |d7 }qp|jjdd }|jjdd }	|j| |	 krp|d7 }t
d|j d	| d
|	  qpt
| d| d| d t	||r(t
d|j  d}d}d}|jdgdD ]}|d7 }|jjdd }
|jjdd }|jjdd }	|j|sp|d7 }t
d|j d	| d
|	  t||
drt||drt||dsp|  |d7 }qpt
| d| d| d q(dS )u6  
        TODO: временно, после патчинга баз - убрать.
        Из-за бага, м2м связи могут быть в 'чужой' таблице. Найдём их и исправим.
        python3 ./manage.py shell "models.RelationCache.fix_relation_locations()"
        r   )CmfM2MModelCmfGM2MModel)rq   zCheck Model r   r   r   zInvalid z: z - r!   r   z rows invalid, z fixedN)Z
cmf.modelsr   r   r5   rq   varsr}   r   typer9   r   rs   r@   r   r   r{   r0   r`   rT   r   r6   )r   r   rq   rr   Z	all_countZinvalid_countZfixed_countr   Zleft_model_nameZright_model_nameZid_model_namer   r   r   r     sZ    
$


z$RelationCache.fix_relation_locations)NNN)NF)NNN)NNNN)N)NN)4rh   ri   rj   rk   ZabstractZ	api_allowZFieldZCmfTUUIDr{   r   ZCmfTextr   r/   r(   ZCmfIntr+   r   r&   r'   r%   r)   r*   classmethodrH   rN   rP   r]   rc   rm   Z	dataclassrp   ru   r|   rt   rw   r   r   r~   strr   r:   rq   ZCmfModelr   r   r   r   r   r   r   staticmethodr   r   r   r   r   r      s|   
     ?       L
<


/

-D
L
)Hr   )rm   collectionsr   Zcmf.models.base_modelr:   Zcmf.fields.base_fieldsr5   Zcmf.cmf_profiler   rq   Z	BaseModelr   r   r   r   r   <module>   s   
