B
    if`                 @   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)caption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_idz==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   "./cmf/fields/cmf_relation_cache.py	<setcomp>D   s    z-RelationCache.get_children.<locals>.<setcomp>)list)	clsr   parent_field_namechild_modelsfullr   r   query_filterresultr   r   r   get_children0   s    
zRelationCache.get_childrenc             C   sT   t t}x0| j||d|dD ]}||jj |jj qW |sPd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>Q   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_dictF   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   z==r   r   r   )r   r   r   c             S   s   h | ]}|j jqS r   )r   r   )r   r   r   r   r   r   c   s    z,RelationCache.get_parents.<locals>.<setcomp>)r    )	r!   r   child_field_nameZparents_modelsr$   r   r   r%   r&   r   r   r   get_parentsT   s    zRelationCache.get_parentsc	             C   sf  |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}x||g D ]}x|
|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  qW qW d	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'   r.   	partitionr   r   r   r   r   r   r   r   r   r   r   Zsave)r!   r   r"   r   r-   r   r   r   r   direct_linkschildrenparentsdirect_linkparentchildr   r   r   r   add_relatione   s>    2zRelationCache.add_relationc             C   sj  | 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}x|| D ]}	x|| 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| 
|	|
|qW qW dS )u3   
        TODO: пакетный режим
        r   Tr   )r   r   r   r   r   r   r   Nr1   ztoo many relation (z) z::z => )r$   zrelation absent z depth )r    lenLookupErrorr.   r'   r   r   r   r   r   getdeleteFileNotFoundErrorr   )r!   r   r"   r   r-   r3   r6   r5   r4   r7   r8   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_factoryr4   	right_forFrecursion_doneN)__name__
__module____qualname____doc__ZCmfTypeMeta__annotations__dataclassesfieldr    r4   rC   rD   boolr   r   r   r   
FieldCache   s
   
rM   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!   rK   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 | _	x$| 
|D ]}| || d qW dS )uA   Используется при создании cls.fields_cache.r   nested_fieldsNTz.id)_fields_cacher4   appendr2   rC   rA   rS   CmfRelationBaserR   rD   rT   
_add_child)r!   r7   r8   backrefr   field_model_namer   r   r   rZ      s    
zRelationCache._add_childc             C   sJ  t }ttj|d| _xb| jD ]X}xR|j D ]D}t|tj	j
jsN|jdksNq.|j d|j }| j|d| j|< q.W qW t| j}x|D ]}| j| }|j}t|tr|jr| |}t|ttfr8xx|D ]d}	d}
x2|jD ](}||	d r| j|||jd d}
qW |
s| j||	 d|jp$d |jd qW qtd|qW d	S )
ud   
        Вызывается после загрузки моделей при создании app)model_filteridrU   )rA   F)r[   Tz#Unsupported nested_fields for fieldN)rL   r    ZcmfutilZiter_models_models_listr   valuesrS   cmfrN   Z
base_model
CmfRelBaserP   rM   rW   sortedrA   rV   rT   ZCmfBackrefBase
CmfM2MBase
startswithrZ   r[   	TypeError)r!   r]   rO   rK   field_full_nameZsorted_fields
field_nameZfield_cacheZfield_modelsZfield_modelfoundZnested_field_namer   r   r   build_fields_cache  s2    




&z RelationCache.build_fields_cachec             C   s8   | j |}|r4|jrdS |jr4t|jttfr4dS d S )NT)rW   r<   rC   r4   rS   rA   rY   rd   )r!   rg   fcr   r   r   _is_tree_fieldA  s    zRelationCache._is_tree_field)
inst_field	target_idc             C   s  |j j}|j}| d| }| j| }|j}|dd }xj|jD ]`\}	}
|
d\}}}|	d\}}}t|ttfr||kr| 	|||j j
j| qBtd|qBW xV|jD ]L}|d\}}}t|ttfr||kr| 	|j j
j||| qtd|qW dS )uk  
        Рвём связь между field.instance и target_id, если она существует
        По field и fields_cache мы должны понять:
          - есть ли такая связь в кеше
          - поля, между которыми связь
          - кто родитель и кто потомок
        rU   r0   r   zUnsupported right_for for fieldzUnsupported children for fieldN)instancerP   rW   rA   r2   rC   rS   rY   rd   r@   r^   r   rf   r4   )r!   rm   rn   r\   rh   rg   rk   rK   target_model_nameparent_field_full_namechild_field_full_namechild_model_name_r-   parent_model_namer"   child_full_field_namer   r   r   remove_field_referenceL  s&    	
z$RelationCache.remove_field_reference)rm   rn   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}xv|jD ]l\}}|	d\}}}|	d\}}}t|ttfr||kr| j|||j j|||||d qtd|
qW xd|jD ]Z}|	d\}}}t|ttfrt||kr~| j|j j|	||||||d n
td|
q&W dS )u`  
        Создаём связь между field.instance и target_id, если нужно
        По field и fields_cache мы должны понять:
          - есть ли такая связь в кеше
          - поля, между которыми связь
          - кто родитель и кто потомок
        Nr0   r   rU   )r   r   r   r   zUnsupported right_for for fieldzUnsupported children for field)r^   r   
isinstancera   rN   	CmfEntitynamecoderP   r2   ro   rW   rA   rC   rS   rY   rd   r9   rf   r4   )r!   rm   rn   rx   Ztarget_nameZtarget_coderp   rt   r\   rh   rg   rk   rK   ro   Zinstance_nameZinstance_coderq   rr   rs   r-   ru   r"   rv   r   r   r   add_field_referencer  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}x|jD ]|\}}|d\}}}|d\}}}t|	tr|
jj|kr| j|
jjj||jj|||||d qt|	tr
qtd|qW x|jD ]}|d\}}}t|	trt|
jj|kr| j|jj||
jjj|||||d nt|	trxr|
jD ]\}|j|krd }d }t |tjjr|jj}|jj}| j|jj||jj|||||d qW n
td|q W d S )NrU   )r   r   r   r   zUnsupported right_for for fieldzUnsupported children for field)ry   ra   rN   rz   r{   r   r|   r2   rW   rA   rR   rP   rY   rC   rS   r9   r^   rd   rf   r4   )r!   rg   ZinstZ	inst_nameZ	inst_coder\   rt   rh   rk   frm   Zinst_field_nameZinst_field_coderq   rr   rs   r-   ru   r"   rv   Zrel_instZrel_inst_nameZrel_inst_coder   r   r   _make_field_relations  sh    





z#RelationCache._make_field_relationsc             C   sR  d}d}d}x*| j D ]}t|tjjs,qtd|j  |d7 }| |j	j
d  }|| |j	jd  7 }|j	}t }g }xZ|j|j|j|jD ]@\}	}
}|
|f|kr|d7 }|d7 }||	 q||
|f qW |j||j|jdd |r|d7 }t|j d| d| d qW t| d	| d
 d S )Nr   zClear r1   F)Zsynchronize_session z
 deleted, z duplicate refs/z m2m tables cleared)r_   rS   ra   rN   ZBaseM2MModelprintrP   query_deprecatedr   dp_modelr   Zisnotr=   Zroot_idr)   Zdpr^   left_idright_idrX   r*   Zin_)r!   Zmodels_countZ	dup_countZclearedrO   Zdeletedr   Zuniq_relZdup_idsZrel_idr   r   r   r   r   clear_old_m2m_tree  s0     "" z RelationCache.clear_old_m2m_tree<   )	log_startalarm_levelalarm_thresholdc                s   t jd}dt jd< td    }td| d    tdtj	dd fd	d
}x j
D ]|j qhW    |dkrt jd= n
|t jd< 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 ClearedFr1   )r   r   r   c                s   d}g }g }xDj  D ]6}j d|j } |r|| ||j qW |sZd S x:jd|dD ]&}|d7 }x|D ]} || q~W qlW tdj d	| d
 d S )Nr   rU   r^   r|   r{   )r   r1   zBuild relations from z done, z objects)r^   r|   r{   )r   r`   rP   rl   rX   r    r   r   )Z_model_nameZcount_Ztree_fields_full_namesZtree_fields_namesrK   rg   ro   rh   )r!   rO   r   r   create_model_relationsB  s    


z9RelationCache.recalculate.<locals>.create_model_relationsN)osenvironr<   r   r   r=   r   r   loggingWARNINGr_   rP   fix_relation_locations)r!   Z	cache_oldcountr   r   )r!   rO   r   recalculate$  s    

zRelationCache.recalculatec           	   C   sP  ddl m} m} ddlm} x,t| D ]}t|ts>q,t	|| rDt
d|j  d}d}d}x|jdgdD ]}|d7 }|jr|js|d7 }t
d|j d	|jj d
|jj  |  |d7 }qv|jjdd }|jjdd }	|j| |	 krv|d7 }t
d|j d	| d
|	  qvW t
| d| d| d t	||r,t
d|j  d}d}d}x|jdgdD ]}|d7 }|jjdd }
|jjdd }|jjdd }	|j|sz|d7 }t
d|j d	| d
|	  t||
drt||drt||dsz|  |d7 }qzW t
| d| d| d q,W dS )u6  
        TODO: временно, после патчинга баз - убрать.
        Из-за бага, м2м связи могут быть в 'чужой' таблице. Найдём их и исправим.
        python3 ./manage.py shell "models.RelationCache.fix_relation_locations()"
        r   )CmfM2MModelCmfGM2MModel)rN   zCheck Model r   )r   r1   zInvalid z: z - r0   r   z rows invalid, z fixedN)Z
cmf.modelsr   r   cmf.includerN   varsr`   ry   typerS   r   rP   r    r   r   r^   r   r=   r2   re   rR   )r   r   rN   rO   Z	all_countZinvalid_countZfixed_countro   Zleft_model_nameZright_model_nameZid_model_namer   r   r   r   f  sV    
$ 
z$RelationCache.fix_relation_locations)NNN)NF)NNN)NNNN)N)NN)4rE   rF   rG   rH   ZabstractZ	api_allowZFieldZCmfTUUIDr^   r   ZCmfTextr   r   r   ZCmfIntr   r   r   r   r   r   r   classmethodr'   r,   r.   r9   r@   rJ   Z	dataclassrM   rW   r_   rT   rZ   rj   rl   rb   strrw   ra   rN   ZCmfModelr}   r   r   r   r   r   r   staticmethodr   r   r   r   r   r   
   sV   
>=0%:M*Ar   )rJ   collectionsr   Zcmf.models.base_modelra   Zcmf.fields.base_fieldsr   Zcmf.cmf_profiler   rN   Z	BaseModelr   r   r   r   r   <module>   s   