
    4jYs                         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l
mZmZmZmZ ddZd Z G d d      Z ed	d
d      	 	 	 	 	 dd       Zy)u  
Модуль экспорта данных в различные форматы файлов (CSV, XML, XLSX).

Основная задача - выгрузка данных из моделей CMF в файлы с поддержкой различных форматов.
    )*N)OptionalListUnionAnyc                    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export2filer0      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_htmlr:   D   s6     
))

|
,C##%%    c            
           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ddedeeeee   f      f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   rA   r   r
   s           r/   __init__zExportDataManager.__init___   s;      0 2A2MSU'r;   c                      y N rB   s    r/   preparezExportDataManager.preparer   s    r;   c                     t        |      S rE   )list)rB   r   s     r/   get_extra_field_namesz'ExportDataManager.get_extra_field_namesv   s    K  r;   c                     i S rE   rF   rG   s    r/   get_forced_captionsz%ExportDataManager.get_forced_captionsz   s    	r;   c              #   F   K   | j                  |      D ]	  }|di f  y w)Nr   )paginate_data)rB   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&   )rB   objmodelobj_dictrU   obj_owner_idobj_parent_idrg   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 
 	
r;   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--)re   access_authorcmf_author_idperm_security_level_idaccess_responsibleresponsible_id
spectators	executors
project_idrf   rc   rg   
logic_typeactivity)filterslicefieldsr   r   export_hookN)common.models.cmf_active_entityrr   r`   r@   slistr   r   r   ro   hasattrr   )rB   r   rr   startstepsecurity_fieldspreload_fieldsdatarP   rowssub_rows              r/   rO   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:
            Строковое представление значения
        r2   r   r&   ,)r   strr   r&   
isinstancerJ   joinconvert_valr`   ra   r   CmfChoiceIntchoicesget	CmfChoice)rB   val	field_objchoices_overriderP   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rj   zSimpleMapper.SimpleModel	field_strc           	          t        ||      rt        ||      S |}|j                  d      D ]>  }| yt        |t              r|D cg c]  }t        ||d       }}2t        ||d      }@ |S c c}w )u$  
        Получаем значение поля объекта по пути (путь может содержать точку для вложенных полей)
        Поддерживает вложенные поля и поля m2m-связей

        Например: 
        - get_included_attr(doc, 'person.second_name')
        - get_included_attr(doc, 'tags.name') - m2m связь

        Возвращает единственное значение поля, либо список значений, или None
        r   N)r   getattrsplitr   rJ   )rB   rj   r   curfielditems         r/   get_included_attrz#ExportDataManager.get_included_attr  s     3	"3	**__S) 	0E{#t$>ABdwtUD1BBc5$/	0 
 Cs   A/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      rk   r!   r   )
r   	enumerater   r   r   r   lenvarsr!   rk   )rB   	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	 r;   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CmfTestcaserb   r   r!   rk   )rA   r   r   r   r   keysr   r!   copyra   rb   captionr   r   rk   r   )r   r   pural_namesr   linked_entitylinked_class_objres
models_cls
model_nameget_captionrB   s            r/   r   z3ExportDataManager.get_captions.<locals>.get_captionE  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Jr;   )r@   )rB   r   r   r   r   s   `   @r/   get_captionszExportDataManager.get_captions9  s9    	6 % 	@J)$((J?C
O	@
r;   rE   )NN)__name__
__module____qualname____doc__rC   rH   rK   rM   rQ   boolro   rO   r   r   r   r   r   r   r   r   rJ   r   rF   r;   r/   r=   r=   W   s~    &!

t 
B9"v!+F%? C T\]bcfhlmphqcq]rTs 2<* *r;   r=   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   rq   	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 проекта.Nrz   logic_type_idactivity_id	ui_fieldsr   )r`   r   r   _build_ui_formr   )	rP   r   rz   	cache_keyui_form_customresultrr   _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%r;   c                     |rGt        |t        j                  j                  t        j                  j                  f      r	 | |      S y)u_   Возвращает choices_override для choice-полей; None для остальных.N)r`   ra   r   r   r   )rP   r   r   r   s      r/   get_choices_overridez.export2file_task.<locals>.get_choices_override  s8    I

0G0GI]I]/^_(j99r;   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+r2   )newline;")	delimiter	quotecharquotingtext    без html)csvopenwriterQUOTE_MINIMALr   rO   appendr   endswithr   r   r:   writerow)	file_pathr   csvfiler   csv_headr   rP   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requestr2   r   u+   XML представление данныхlanguagezru-rutaskr   0
build_infor   r   Nr   r   valuer&   endtotal)%	xml.etreer   pathlibr   Elementsetr   r   globalsr   r+   r   rO   dictr   r   r   r   r`   ra   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   rP   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5r;   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   )rP   columnN    )openpyxlr  Workbookactiver   rQ   r   r   r:   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!r;   r?   z/export.r   xmlxlsxu0   Формат не поддерживается: rbF)backupmake_previewNzError during export to z: ERROR)level)#r   rr   r   r!   r'   r   get_export_managerrH   r@   r   valuesvisibler   rK   r  rM   rJ   r`   ra   
CmfRelBaser   rA   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   rA   
new_fieldsr   r   r  r*  r
   tmpdirr   ferr   r   r   r@   r   r   r   r   s     `                    @@@@@@@@r/   r*   r*   e  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.fieldsra   r   r6  	lxml.htmlr3   typingr   r   r   r   r0   r:   r=   cmf_deferred_jobr*   rF   r;   r/   <module>rG     su           . --b&&L L\ GRSimn "&!&&+"S oSr;   