U
    -d                     @   sH   d dl Z d dlm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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.j/d3d4d5d6 Z0e1d7d8 Z2d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           	      C   sz   dg}ddd|gdd|gg}|r0ddd|g|g}|rP|dddd	d
dddddg
7 }| j |||d}|rh|S t dd |D S )u  
        full=True - возвращаем список экземпляров RelationCacheModel со всеми полями
        иначе только список child_id
        fixme: фильтр по child_field - нужен ли? Возможно
        r   AND	parent_id==parent_fieldchild_modelINparent_modelparent_nameparent_codechild_field
child_name
child_codedepthfieldsfilter
for_updatec                 S   s   h | ]}|j jqS  )child_idvalue.0rowr   r   ./fields/cmf_relation_cache.py	<setcomp>A   s     z-RelationCache.get_children.<locals>.<setcomp>list)	clsr
   parent_field_namechild_modelsfullr   r   query_filterresultr   r   r    get_children.   s*            zRelationCache.get_childrenc                 C   sP   t t}| j||d|dD ]}||jj |jj q|sLdd | D S |S )u   
        get_children с разбитием по классам, потом удобней фильтровать, подгружать.
        result = {"child_model1": ["child_id1", ...], ...}
        T)r'   r&   c                 S   s   i | ]\}}|t |qS r   r"   )r   Z
model_nameZids_setr   r   r    
<dictcomp>N   s      z3RelationCache.get_children_dict.<locals>.<dictcomp>)r   setr*   r   r   addr   items)r$   r
   r%   r&   Z
return_setr)   r   r   r   r    get_children_dictC   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
   r   r   r   r   r    r!   _   s     z,RelationCache.get_parents.<locals>.<setcomp>r"   )	r$   r   child_field_nameZparents_modelsr'   r   r   r(   r)   r   r   r    get_parentsQ   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}||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|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)r'   r   :)r
   r   r   r   r   r   r   r   r   r   r      )ZCmfOrmErrorr#   r*   r1   	partitionr   r
   r   r   r   r   r   r   r   r   r   Zsave)r$   r
   r%   r   r0   r   r   r   r   direct_linkschildrenparentsdirect_linkparentchildr   r   r   r    add_relationa   sd    .          
     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   r3   Nr5   ztoo many relation (z) z::z => )r'   zrelation absent z depth )r#   lenLookupErrorr1   r*   r   r
   r   r   r   getdeleteFileNotFoundErrorr   )r$   r
   r%   r   r0   r7   r:   r9   r8   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_factoryr8   	right_forFrecursion_doneN)__name__
__module____qualname____doc__ZCmfTypeMeta__annotations__dataclassesfieldr#   r8   rG   rH   boolr   r   r   r    
FieldCache   s
   
rQ   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    
<listcomp>   s     z8RelationCache.get_field_models_names.<locals>.<listcomp>)getattr
issubclassZCmfSubclassedGenericRelationZrelated_modelsr#   r,   )r$   rO   Zfield_models_namesr   r   r    get_field_models_names   s    
z$RelationCache.get_field_models_namesc                 C   s   | j | j| |rLd|kr6|dd  d| }| j | j||f | j | j}t|trt|dds| j | j	sd| j | _	| 
|D ]}| || d qdS )uA   Используется при создании cls.fields_cache.r   nested_fieldsNTz.id)_fields_cacher8   appendr6   rG   rE   rW   CmfRelationBaserV   rH   rX   
_add_child)r$   r;   r<   backrefr   field_model_namer   r   r    r^      s    


zRelationCache._add_childc                 C   s  t }ttj|d| _| jD ]8}|j D ](}|j d|j }| j|d| j	|< q*qt
| j	}|D ]}| j	| }|j}t|trd|jrd| |}t|ttfr|D ]^}	d}
|jD ](}||	d r| j|||jd d}
q|
s| j||	 d|jpd |jd qqdtd|qdd	S )
ud   
        Вызывается после загрузки моделей при создании app)model_filterrY   )rE   F)r_   Tidz#Unsupported nested_fields for fieldN)rP   r#   ZcmfutilZiter_models_models_listr   valuesrT   rQ   r[   sortedrE   rW   
CmfRelBaserZ   rX   ZCmfBackrefBase
CmfM2MBase
startswithr^   r_   	TypeError)r$   ra   rS   rO   field_full_nameZsorted_fields
field_nameZfield_cacheZfield_modelsZfield_modelfoundZnested_field_namer   r   r    build_fields_cache	  s4    




  
z RelationCache.build_fields_cachec                 C   s2   | j | }|jrdS |jr.t|jttfr.dS d S )NT)r[   rG   r8   rW   rE   r]   rg   )r$   rj   fcr   r   r    _is_tree_field8  s
    
zRelationCache._is_tree_field)
inst_field	target_idc                 C   s   |j j}|j}| d| }| j| }|j}|dd }|jD ]`\}	}
|
d\}}}|	d\}}}t|ttfr||kr| 	|||j j
j| q@td|q@|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 мы должны понять:
          - есть ли такая связь в кеше
          - поля, между которыми связь
          - кто родитель и кто потомок
        rY   r4   r   Unsupported right_for for fieldUnsupported children for fieldN)instancerT   r[   rE   r6   rG   rW   r]   rg   rD   rb   r   ri   r8   )r$   rp   rq   r`   rk   rj   rn   rO   target_model_nameparent_field_full_namechild_field_full_namechild_model_name_r0   parent_model_namer%   child_full_field_namer   r   r    remove_field_referenceB  s&    	

z$RelationCache.remove_field_reference)rp   rq   targetc                 C   s  d}d}|r:|j j}t|tjjr2|jj}|jj}|j}n|	dd \}}}|j
j}|j}	| d|	 }
| j|
 }|j}|j
}d}d}t|tjjr|jj}|jj}|jD ]l\}}|	d\}}}|	d\}}}t|ttfr||kr| j|||j j|||||d qtd|
q|jD ]Z}|	d\}}}t|ttfrn||krx| j|j j|	||||||d n
td|
q dS )u`  
        Создаём связь между field.instance и target_id, если нужно
        По field и fields_cache мы должны понять:
          - есть ли такая связь в кеше
          - поля, между которыми связь
          - кто родитель и кто потомок
        Nr4   r   rY   r   r   r   r   rr   rs   )rb   r   
isinstancecmfrR   	CmfEntitynamecoderT   r6   rt   r[   rE   rG   rW   r]   rg   r=   ri   r8   )r$   rp   rq   r}   Ztarget_nameZtarget_coderu   ry   r`   rk   rj   rn   rO   rt   Zinstance_nameZinstance_coderv   rw   rx   r0   rz   r%   r{   r   r   r    add_field_referenceh  sf    	

     

     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 )NrY   r~   rr   rs   )r   r   rR   r   r   r   r   r6   r[   rE   rV   rT   r]   rG   rW   r=   rb   rg   ri   r8   )r$   rj   instZ	inst_nameZ	inst_coder`   ry   rk   rn   frp   Zinst_field_nameZinst_field_coderv   rw   rx   r0   rz   r%   r{   Zrel_instZrel_inst_nameZrel_inst_coder   r   r    _make_field_relations  s    



    
    
    z#RelationCache._make_field_relationsc                 C   sH  d}d}d}| j D ]}t|tjjs(qtd|j  |d7 }| |j	j
d  }|| |j	jd  7 }|j	}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 r5   F)Zsynchronize_session z
 deleted, z duplicate refs/z m2m tables cleared)rc   rW   r   rR   ZBaseM2MModelprintrT   query_deprecatedr   dp_modelr
   ZisnotrA   Zroot_idr,   Zdprb   left_idright_idr\   r-   Zin_)r$   Zmodels_countZ	dup_countZclearedrS   Zdeletedr   Zuniq_relZdup_idsZrel_idr   r   r   r   r    clear_old_m2m_tree  s0      "z RelationCache.clear_old_m2m_tree<   Z	log_startZalarm_levelZalarm_thresholdc                    s   t jd}dt jd< td    }td| d    tdtj	dd fd	d
} j
D ]|j qf   |dkrt jd= n
|t jd< dS )u  
        Актуализация данных в таблице. Требуется монопольный режим для обеспечения целостности.
            TODO: Лок на таблицу
        Пока лучше выключать срм на время ребилда:
            systemctl stop crm
            cd /opt/crm
            python3 ./manage.py shell <<<"models.RelationCache.recalculate()"
        Есть смысл делать при обновлении, после наката миграций.

        TODO: clear=True - предварительно очистить таблицу.
        TODO: выборочный пересчёт
        ZNO_CACHE1zClear relation_cachez  z ClearedFr5   r   c                    s   d}g }g }j  D ]6}j d|j } |r|| ||j q|sVd S jd|dD ]"}|d7 }|D ]} || qvqftdj d	| d
 d S )Nr   rY   rb   r   r   r   r5   zBuild relations from z done, z objects)rb   r   r   )r   rd   rT   ro   r\   r#   r   r   )Z_model_nameZcount_Ztree_fields_full_namesZtree_fields_namesrO   rj   rt   rk   r$   rS   r   r    create_model_relations8  s    

z9RelationCache.recalculate.<locals>.create_model_relationsN)osenvironr@   r   r   rA   r   r   loggingWARNINGrc   rT   fix_relation_locations)r$   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)rR   zCheck Model r   r   r5   zInvalid z: z - r4   r   z rows invalid, z fixedN)Z
cmf.modelsr   r   cmf.includerR   varsrd   r   typerW   r   rT   r#   r   r   rb   r   rA   r6   rh   rV   )r   r   rR   rS   Z	all_countZinvalid_countZfixed_countrt   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)3rI   rJ   rK   rL   ZabstractZFieldZCmfTUUIDrb   r
   ZCmfTextr   r   r   ZCmfIntr   r   r   r   r   r   r   classmethodr*   r/   r1   r=   rD   rN   Z	dataclassrQ   r[   rc   rX   r^   rm   ro   rf   strr|   r   rR   ZCmfModelr   r   r   r   r   r   r   staticmethodr   r   r   r   r    r   	   sz   
            <
<


.
	%:
L
)@r   )rN   collectionsr   Zcmf.fields.base_fieldsr   Zcmf.cmf_profiler   r   rR   Z	BaseModelr   r   r   r   r    <module>   s
   