
    2j"r                         d Z ddl ddl ddlZddlZddl ddlZddlZ	ddZ
d Z G d d      Z edd	d
      	 	 	 	 	 dd       Zy)u  
Модуль экспорта данных в различные форматы файлов (CSV, XML, XLSX).

Основная задача - выгрузка данных из моделей CMF в файлы с поддержкой различных форматов.
    )*Nc                    ddl m}m} t        j                  j	                         }	|	j                  d      }
t        j                  j                  d| d|||ddd	       d
t        j                  j                   d|
 d| }t        j                  t        j                  |      }|j                         5  |j                          ddd        |t        | |j                  |||||d|       |j                   S # 1 sw Y   7xY w)u  
    Публичная функция для запуска отложенной задачи экспорта данных.

    Создает attachment для результата и запускает фоновую задачу export2file_task.

    Args:
        class_name: Имя класса модели CMF для экспорта
        field_names: Список полей для экспорта (если None - все видимые поля)
        bql: BQL фильтр для выборки данных
        format_file: Формат файла ('csv', 'xml', 'xlsx')
        include_archived: Включать ли архивные записи
        order_by: Поле для сортировки
        **kwargs: Дополнительные параметры (roadmap_ordered_ids, roadmap_level_of и т.д.)

    Returns:
        URL созданного attachment с результатом
    r   )schedule_deferred_jobcmfutilz%Y%m%d%H%M%SexportN)bqlfield_namesformat_fileokF)operatecmf_model_nameparent
audit_dataresult_statusignore_transactionzexport..)r   name)
class_nameres_attachment_idr	   r   r
   include_archivedorder_by)kwargs)cmf.includer   r   datetimenowstrftimemodelsCmfAuditaudit_eventgcurrent_useridCmfAttachmentdisable_aclsaveexport2file_taskurl)r   r	   r   r
   r   r   r   r   r   r   formatted_timeexport_filename
attachments                ./cmf/util/cmf_export.pyexport2filer,      s   $ ; 




!C \\.1N OO#'CP[lw4x*.5   J   1 12!N3C1[MRO%%Q^^/%RJ				 **ISCN;>CNHX@H4> 7=4> ? >> s   9D  D	c                     | syt         j                  j                  |       }|j                         j	                         S )u  
    Удаляет HTML теги из строки и возвращает чистый текст.
    
    Использует lxml для быстрого парсинга HTML. Если парсинг не удался,
    возвращает исходную строку как есть.
    
    Args:
        html_content: Строка с HTML разметкой
        
    Returns:
        Строка без HTML тегов
     )lxmlhtml
fromstringtext_contentstrip)html_contentdocs     r+   _strip_htmlr6   B   s6     
))

|
,C##%%    c                   b    e Zd ZdZddZd Zd Zd Zd Zde	fd	Z
d
 ZddZd Zd ZdefdZy)ExportDataManageru_  
    Менеджер для управления процессом экспорта данных.

    Инкапсулирует логику работы с данными: пагинация, проверка прав доступа,
    конвертация значений полей, получение метаданных полей.
    Nc                 l    || _         || _        || _        || _        ||ni | _        ddlm} || _        y)u  
        Инициализация менеджера экспорта.

        Args:
            cls: Класс модели CMF для экспорта
            bql: BQL фильтр для выборки данных
            include_archived: Включать ли архивные записи
            order_by: Поле для сортировки
            forced_captions: Словарь принудительных заголовков полей
        Nr   r   )clsr   r   r   forced_captionsr   r   )selfr<   r   r   r   r=   r   r   s           r+   __init__zExportDataManager.__init__]   s;      0 2A2MSU'r7   c                      y N r>   s    r+   preparezExportDataManager.preparep   s    r7   c                     t        |      S rA   )list)r>   r	   s     r+   get_extra_field_namesz'ExportDataManager.get_extra_field_namest   s    K  r7   c                     i S rA   rB   rC   s    r+   get_forced_captionsz%ExportDataManager.get_forced_captionsx   s    	r7   c              #   F   K   | j                  |      D ]	  }|di f  y w)Nr   )paginate_data)r>   r	   rows      r+   iter_rows_for_xlsxz$ExportDataManager.iter_rows_for_xlsx|   s,     %%k2 	Cq"*	s   !returnc                 n   	 | j                   j                  |j                        }|j                  }t	        |t
        j                  j                        xr |j                  }t	        |t
        j                  j                        xr |j                  }t	        |t
        j                  j                        xr |j                  }t	        |t
        j                  j                        xr |j                  }t        j                  j                  ||j                  ||j                  ||d|d	      S )uC  
        Проверяет права доступа к объекту для чтения.
        
        Args:
            obj: Объект модели CMF для проверки
            
        Returns:
            True если доступ разрешен, False в противном случае
        readF)	initial_acl_keyobject_modelobject_owner_id	object_idobject_parent_idobject_dictaccess_levelperm_security_level_allowed_idsraise_error)r   get_model_by_namer   __dict__
issubclasscmfr   	CmfEntityperm_effective_acl_idCmfModelcmf_owner_id	parent_id%perm_security_level_allowed_ids_cacheCmfAccessListcheck_accessr"   )r>   objmodelobj_dictrQ   obj_owner_idobj_parent_idrc   s           r+   check_access_objz"ExportDataManager.check_access_obj   s    	 ..s~~><<$UCJJ,@,@A_cF_F_!%)<)<=R#BRBR"5#***=*=>P3==0:5#**BVBV0W  1F\_  ]F  ]F-##00+#..Zfff}(,Q_d	 1 
 	
r7   c              #     K   ddl m} d}d}d|vrdg|z   }g d}g }t        | j                  |      r|ddgz  }	 | j                  j	                  | j
                  |||z   g||z   |z   | j                  | j                        }|sy
||z  }|D ]T  }| j                  |      st        | j                  d	      r| j                  j                  |      }	n|g}	|	D ]  }
|
  V w)uP  
        Генератор для постраничной загрузки данных с проверкой прав доступа.
        
        Загружает данные пакетами по 50000 записей, фильтрует по правам доступа
        и возвращает только разрешённые записи.
        
        Args:
            fields: Список полей для экспорта. Если '--' отсутствует, добавляется в начало.
            
        Yields:
            Записи, к которым пользователь имеет доступ на чтение.
            
        Note:
            Для проверки доступа автоматически подключаются служебные поля:
            - cmf_owner_id, cmf_author_id — владелец и автор
            - access_author, access_responsible — права автора и ответственного
            - perm_security_level_id — уровень безопасности
            - responsible_id, spectators, executors — исполнители и наблюдатели
            - project_id, parent_id — иерархия
            - perm_effective_acl_id, perm_security_level_allowed_ids_cache — кэш ACL
        r   CmfActiveEntityiP  z--)ra   access_authorcmf_author_idperm_security_level_idaccess_responsibleresponsible_id
spectators	executors
project_idrb   r_   rc   
logic_typeactivity)filterslicefieldsr   r   export_hookN)common.models.cmf_active_entityrn   r\   r<   slistr   r   r   rk   hasattrr|   )r>   r{   rn   startstepsecurity_fieldspreload_fieldsdatarL   rowssub_rows              r+   rK   zExportDataManager.paginate_data   s    , 	DvVf_F

 dhh0 |Z88N88>>$)54<#8$*_$<~$M.2.C.C&*mm	 " 5D
 TME ",,S1488]388//4D5D# "G!M"" s   C$C&c                    |yt        |d      rt        |j                        S t        |d      rt        |j                        S t	        |t
              r.dj                  |D cg c]  }| j                  |       c}      S |r^t        |t        j                  j                        r:||n|j                  }|t        |      n|}|j                  |t        |            S |rXt        |t        j                  j                        r4||n|j                  }|j                  |t        |      |      S ||      S |rt        |      S dS c c}w )u  
        Конвертирует значение поля в строку для экспорта.
        
        Обрабатывает различные типы данных: None, объекты с атрибутами name/id,
        списки, поля CmfChoiceInt и CmfChoice.
        
        Args:
            val: Значение поля
            field_obj: Объект поля (опционально)
            choices_override: Альтернативный словарь choices из ui_form_custom,
                              со строковыми ключами (CmfJson)
            
        Returns:
            Строковое представление значения
        r.   r   r"   ,)r   strr   r"   
isinstancerF   joinconvert_valr\   r]   r{   CmfChoiceIntchoicesget	CmfChoice)r>   val	field_objchoices_overriderL   r   
lookup_keys          r+   r   zExportDataManager.convert_val   s      ;S&!sxx= S$svv;T"88cBsT--c2BCC:i1H1HI*:*F&IL]L]G%5%ASsJ;;z3s844:i1E1EF*:*F&IL]L]G;;+;+Gs3xRUVVSRUVV"3s8** Cs   %Ec                     t        ||      rt        ||      S |}|j                  d      D ]  }| yt        ||d      } |S )u  
        Получает поле объекта. Работает с вложенными полями через точку.
        
        Например: get_included_attr(doc, 'person.second_name')
        
        Args:
            obj: Объект модели
            field_str: Имя поля (может содержать точку для вложенных полей)
            
        Returns:
            Значение поля или None
        r   N)r   getattrsplit)r>   rf   	field_strcurfields        r+   get_included_attrz#ExportDataManager.get_included_attr   sU     3	"3	**__S) 	,E{#ud+C	, 
r7   c                    |j                  d      }|}t        |      D ]  \  }}t        ||d      }|s't        |d      r|j                  j                  |      }|t        |      dz
  k(  s|s|c S t        |d      r)t        t              j                  |j                        }t        |d      r8|j                  r,t        t              j                  |j                  d         } y y)un  
        Рекурсивно получает объект поля, учитывая вложенность через точку.
        
        Args:
            class_obj: Класс модели
            field_name: Имя поля с учетом вложенности
            
        Returns:
            Объект поля или None
        r   Nr{      rg   r   r   )
r   	enumerater   r   r{   r   lenvarsr   rg   )r>   	class_obj
field_namepartscurrent_objipartr   s           r+   get_field_objzExportDataManager.get_field_obj  s       % ' 	GAtT48Ih!?'..2248	CJN")  y'*"6l..y?H-)2B2B"6l..y/?/?/BC	 r7   fields_namec                 T      fdi }|D ]  }  j                   |      ||<    |S )u  
        Получает заголовки (caption) для списка полей.
        
        Учитывает forced_captions, вложенные поля и связанные сущности.
        
        Args:
            fields_name: Список имен полей
            
        Returns:
            Словарь {имя_поля: заголовок}
        c           
         |
j                   v r
j                   |   S |j                  d      }| j                  j                  |d         }ddi}|sY| j                  |j                         v r=t        t              || j                        }|j                  j                  |d         }|}t        t        t                    }t        j                  j                  |d<   |r|j                  }t        |      dkD  rft        |d      r|j                  r|j                  d   }nt        |d      r|j                  }n|S | d 	||   dj                  |dd               }|S )	Nr   r   CmfTestplanTestcaseCmfTestcaser^   r   r   rg   )r=   r   r{   r   r   keysr   r   copyr]   r^   captionr   r   rg   r   )r   r   pural_namesr   linked_entitylinked_class_objres
models_cls
model_nameget_captionr>   s            r+   r   z3ExportDataManager.get_captions.<locals>.get_caption@  sQ   T111++J77$**3/K$$((Q8E%}M Y11]5G5G5II#'<i>R>R0S#T (//33KNCCd6l+J&)jj&:&:J{#mm{#a'uh/ELL%*\\!_
 0%*[[
"
 E;z*/EsxxP[\]\^P_G`#a"bcCJr7   )r<   )r>   r   r   r   r   s   `   @r+   get_captionszExportDataManager.get_captions4  s9    	6 % 	@J)$((J?C
O	@
r7   rA   )NN)__name__
__module____qualname____doc__r?   rD   rG   rI   rM   boolrk   rK   r   r   r   rF   r   rB   r7   r+   r9   r9   U   sO    &!

t 
B9"v!+F,<* *r7   r9   u'   Экспорт данных в файл   T)descriptionpriorityshow_bg_progressbarc                    ddl m t        t              |    t        j                  j                  |dg      }j                         }	 |	|||fi |j                          j                  }
s?|
j                  j                         D cg c]  }|j                  s|j                   c}j                        t        j                               }t!               }D ]}  }|dk(  rd}d|d<   n]|
j                  j                  |      rBt#        |
j                  j                  |      t$        j                  j&                        r|dz  }|j)                  |        ||_        j-                        D ci c]  }|j/                  |
|       c}i fd	fd
fd}fd}fd}ddlm} t5        j6                         5 }	 | d| }|dk(  r	 ||       n*|dk(  r	 ||       n|dk(  r	 ||       nt9        d|       t;        |d      5 }|j=                         5  |j?                  |dd       ddd       ddd       ddd       yc c}w c c}w # 1 sw Y   $xY w# 1 sw Y   (xY w# t@        $ r#}tB        jE                  d| d| d        d}~ww xY w# 1 sw Y   yxY w)zN
    https://bcrm.carbonsoft.ru/project/Document/DOC-007693#spec-007668-b
    r   rm   	file_type)r"   r{   timetracker_historyzop_gantt_task.actual_workuG   Журнал работ.Фактические трудозатратыz.namec           	      f   t              syt        | dd      }|syt        |      t        t        | dd            t        t        | dd            |f}|v r|   S j                  |       }d}|r8|j	                  di       j	                  |      r|d   |   j	                  d      }||<   |S )uh   Возвращает choices для поля строки с учётом ui_form_custom проекта.Nrv   logic_type_idactivity_id	ui_fieldsr   )r\   r   r   _build_ui_formr   )	rL   r   rv   	cache_keyui_form_customresultrn   _row_choices_cacher<   s	         r+   get_row_field_choicesz/export2file_task.<locals>.get_row_field_choices  s    #/S,5

O_d34]D12	
	 **%i00++C0n00bAEEjQ#K0<@@KF(.9%r7   c                     |rGt        |t        j                  j                  t        j                  j                  f      r	 | |      S y)u_   Возвращает choices_override для choice-полей; None для остальных.N)r\   r]   r{   r   r   )rL   r   r   r   s      r+   get_choices_overridez.export2file_task.<locals>.get_choices_override  s8    I

0G0GI]I]/^_(j99r7   c                    ddl }t        | dd      5 }|j                  |dd|j                        }g }t	        j                              D ]  \  }}g }D ]  }|dk(  rT|j                  j                  |             |j                  d	      r#|j                  j                  |       d
       j                  ||      }	j                  |      }
 |||
      }j                  |	|
|      }|j                  |       |j                  d	      s|j                  t        |              |dk(  r|j                  |       |j                  |        	 ddd       y# 1 sw Y   yxY w)u   
        Записывает данные в файл формата CSV.
        
        Args:
            file_path: Путь к файлу для записи
        r   Nzw+r.   )newline;")	delimiter	quotecharquotingtext    без html)csvopenwriterQUOTE_MINIMALr   rK   appendr   endswithr   r   r6   writerow)	file_pathr   csvfiler   csv_headr   rL   csv_rowr   field_valuer   r   val_strcaptionsdata_managerr	   field_objects_cacher   s                r+   
_write_csvz$export2file_task.<locals>._write_csv  sZ    	)T2. 	)'ZZ3#sO`O`ZaFH#L$>$>{$KL )3( =EAv U(;< >>&1$OOx||E/B.C<,PQ"."@"@e"LK 3 7 7 >I';C	'R$*66{IO_`GNN7+ ~~f-{7';<=" 6OOH-(+)	) 	) 	)s   DE*AE**E3c                 
   ddl m} ddlm} |j	                  d      }|j                  dd       |j	                  d      }|j                  |       |j	                  d      }d	|_        |j                  |       |j	                  d
      }dt               v rt        j                  nd|_        |j                  |       |j	                  d      }d|_        |j                  |       |j	                  d      }d|_        |j                  |       |j	                  d      }	|	j                  dd       |j                  |	       |j	                  d      }
|j                  |
       d}t        j                              D ]  \  }}|j	                  d      }t               }|j                  |       D ]G  }|j                  d      }j                  j!                  |d         }|d   |vr1|j	                  |d         ||d   <   |j                  ||d             |j!                  |d         }t#        ||d   d      }|rt%        |t&        j                  j(                        r|st+        t,              |j.                     j                  D ]K  }|dk(  r|j0                  |_        t3        ||      s'|j                  |t5        t#        ||                   M |j                  d|j6                         J|rt%        |t&        j                  j8                  t&        j                  j:                  f      r|s|j                  d|j6                         |D ]Z  }|j	                  d      }|j                  dt5        |j<                               |j0                  |_        |j                  |       \ |rt%        |t&        j                  j>                  t&        j                  j@                  f      r|O |||      }t%        |t&        j                  j>                        r7||n|jB                  }|t5        |      n|}|j!                  |d      |_        n4||n|jB                  }|j!                  |t5        |      n|d      |_        |j                  d|j6                         t5        jE                  ||            |_        |j                  dj!                  ||             J  |	j                  dt5        |             |	j                  dt5        |              ||       jG                  |jI                  |             y)u   
        Записывает данные в файл формата XML (RSS 0.92).
        
        Args:
            file_path: Путь к файлу для записи
        r   )ElementTree)Pathrssversionz0.92channeltitleEvaTeamlinkrequestr.   r   u+   XML представление данныхlanguagezru-rutaskr   0
build_infoitemr   Nr   r   valuer"   endtotal)%	xml.etreer   pathlibr   Elementsetr   r   globalsr   r'   r   rK   dictr   r{   r   r   r\   r]   CmfRelationBaser   r   r   r   r   r   r   
CmfM2MBaseCmfBackrefBaser"   r   r   r   r   write_bytestostring)r   ETr   r   r   r   r   r   r   r   r   r   rL   r   elementsr   r   r   
field_infor   keyvr   r   r   r   r   r<   r   r	   r   s                             r+   
_write_xmlz$export2file_task.<locals>._write_xml  sT    	0 jj	6"**Y'

7

7#
uzz&!#,	#9GKKr	tjj/H{#::j)x zz&!#tZZ-
z" : :; GH ,	TFAs::f%DvHNN4 ) (T
(..s3

{1~6"1~1/1zz+a./IH[^,KKQ 89%\\+a.9
%c;q>4@Zszz/I/IJ"#'<0F0F#G#N#N TC"f}2=2B2B
 (&{C8 *sCS8Q4R ST #y%--@z%#**2G2GIbIb1cd""y%--@!, 5A$&JJw$7E!IIdCI6)*EJ&--e4	5
 z%#**2I2I3::K_K_1`a"* ';CU'S$!%)@)@A6F6R"2X]XeXe9I9US%5[f
*1++j"*E
6F6R"2X]XeXe*1++JZJfc+6Flwy{*|
NN9emm<&),*H*Hj*Y&ZJONN9hll:z.RSQ(T	,	T\ 	A#a&!Y##BKK$45r7   c                    ddl m} |j                         }|j                  }d|_        d}j                        D ]  \  }}}|sdg }D ]J  }	|j                  j                  |	             |	dv s(|j                  j                  |	       d       L |j                  |       d}g }
D ]  }	|	|v r6||	   }|
j                  |       |	dv s"|
j                  t        |             =j                  ||	      }j                  |	      } ||	|      }j                  |||      }|
j                  |       |	dv s|
j                  t        |              |j                  |
       |dkD  s9|j                  }|j                  |   }t        |d      |_        d|_        |j!                  |d	
      }|j"                  d|dz  z   |j"                   |_         |j%                  |        y)uy  
        Записывает данные в файл формата XLSX.

        Строки берёт у data_manager.iter_rows_for_xlsx: (row, depth, overrides).
        overrides - готовые значения колонок в обход convert_val; для text/result_text
        они также держат рядом companion-колонку "без html" для выравнивания XLSX.
        depth > 0 включает группировку строк (outlineLevel) и отступ первой колонки.

        Args:
            file_path: Путь к файлу для записи
        r   )workbooku   ВыгрузкаF)r   result_textr   T   r   )rL   columnN    )openpyxlr  Workbookactiver   rM   r   r   r6   r   r   max_rowrow_dimensionsminoutlineLevelhiddencellr   r%   )r   r  	book_xlsx
sheet_xlsxheader_writtenrow_objdepthoverride_valuesr   r   r   r   r   r   r   xl_rowrd
first_cellr   r   r	   r   r   s                     r+   _write_xlsxz%export2file_task.<locals>._write_xlsx&  s
    	&%%'	%%
-
/;/N/N{/[ $	P+GUO!( NEOOHLL$78 77 8<<+>*?|(LMN !!(+!%G$ =O+-e4GNN7+ 77  {7';<"."@"@%"PK 3 7 7 >I';GUI'V$*66{IO_`GNN7+ 77{7';<=  g&qy#++..v6"%eQ- 	'___B
##/*-*;)<Z=M=M<N'OJ$I$	PL 	y!r7   r;   z/export.r   xmlxlsxu0   Формат не поддерживается: rbF)backupmake_previewNzError during export to z: ERROR)level)#r}   rn   r   r   r#   r   get_export_managerrD   r<   r{   valuesvisibler   rG   r  rI   rF   r\   r]   
CmfRelBaser   r=   r   r   r   r   tempfileTemporaryDirectory
ValueErrorr   r$   upload_stream_file	Exceptionr    debug)r   r   r	   r   r
   r   r   r   r*   manager_clsdata_clsr   r=   
new_fieldsr   r   r  r&  r   tmpdirr   fern   r   r   r<   r   r   r   r   s     `                    @@@@@@@@r+   r&   r&   `  s    @
v,z
"C%%))->})UJ((*KsC)98NvNLH5=__5K5K5M_EQVQ^Q^u''_44[AK<;;=>OJ! &
..4J <EO78__  ,HOO<O<OPZ<[]`]g]g]r]r1s'!J*%& K#2L ((5H \ggRW5,"<"<Xu"MMg , )  )DW6 W6r8" 8"r $		$	$	& &	!((;-8Ie#9%%9%&I& #ST_S`!abbi& W!((* W11!EPU1VWW S `$ hLW WW W
  	GG-k]"QC@GP	% sm   I=,I=J1K3AJ>JJ$J,JJJJ	J	K(KKKK)NNNr   FN)NNr   FN)r   r   
cmf.configr   
cmf.fieldsr]   r   r2  	lxml.htmlr/   r,   r6   r9   cmf_deferred_jobr&   rB   r7   r+   <module>rB     sm          -b&&I IV GRSimn "&!&&+"S oSr7   