From f5548f2fb8cdc8caa15acc3a7e6698ce5d6e42ec Mon Sep 17 00:00:00 2001 From: prog1764 Date: Tue, 4 Aug 2026 15:59:06 +0400 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=B2=D0=BA=D0=BB=D0=B0=D0=B4=D0=BA?= =?UTF-8?q?=D0=B0=20=C2=AB=D0=9F=D0=9E=C2=BB=20=E2=80=94=20=D0=BC=D0=BD?= =?UTF-8?q?=D0=BE=D0=B3=D0=BE=D1=81=D1=82=D1=80=D0=BE=D1=87=D0=BD=D1=8B?= =?UTF-8?q?=D0=B5=20=D0=BF=D0=BE=D0=BB=D1=8F=20(=D1=81=D0=BF=D0=B8=D1=81?= =?UTF-8?q?=D0=BA=D0=B8=20=D0=BF=D0=BE=D1=81=D1=82=D1=80=D0=BE=D1=87=D0=BD?= =?UTF-8?q?=D0=BE,=20=D0=BA=D0=B0=D0=BA=20=D0=B2=20=D0=BE=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D1=81=D0=BD=D0=B8=D0=BA=D0=B5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - hw_sw/os_sw/app_sw теперь tk.Text с прокруткой вместо однострочного Entry - импортёр сохраняет переносы строк (_clean_multiline), а не схлопывает в одну строку - _collect_is/_on_is_select поддержка Text-виджетов --- dokogen/importer.py | 30 +++++++++++++++++++++--------- dokogen/ui.py | 29 +++++++++++++++++++---------- 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/dokogen/importer.py b/dokogen/importer.py index 89482a9..12f051a 100644 --- a/dokogen/importer.py +++ b/dokogen/importer.py @@ -330,6 +330,14 @@ def _import_187_is_list(wb, data, settings, log_fn): return True return False + def _clean_multiline(text): + """Чистит многострочное значение, сохраняя переносы строк.""" + if text is None: + return '' + s = str(text).replace('\r\n', '\n').replace('\r', '\n') + lines = [ln.strip() for ln in s.split('\n')] + return '\n'.join(ln for ln in lines if ln) + def _split_entries(text): """Разбивает список средств защиты на ОТДЕЛЬНЫЕ программы. @@ -445,22 +453,26 @@ def _import_187_is_list(wb, data, settings, log_fn): if _set(field_lower, 'net_method', val_b): log_fn(f" Способ взаимодействия: {val_b[:60]}") continue - # --- ПО и аппаратные средства --- + # --- ПО и аппаратные средства (сохраняем переносы строк) --- if 'наименования программно-аппаратных средств' in field_lower: - if _set(field_lower, 'hw_sw', val_b): - log_fn(f" ПО/аппаратные средства: {val_b[:60]}") + if raw_b and 'выбрать' not in field_lower and not _is_hint(raw_b): + is_obj['hw_sw'] = _clean_multiline(raw_b) + log_fn(f" ПО/аппаратные средства: {str(raw_b)[:60]}") continue if 'операционная' in field_lower and 'система' in field_lower: - if _set(field_lower, 'os_sw', val_b): - log_fn(f" ОС: {val_b[:60]}") + if raw_b and 'выбрать' not in field_lower and not _is_hint(raw_b): + is_obj['os_sw'] = _clean_multiline(raw_b) + log_fn(f" ОС: {str(raw_b)[:60]}") continue if 'прикладные программы' in field_lower: - if _set(field_lower, 'app_sw', val_b): - log_fn(f" Прикладные программы: {val_b[:60]}") + if raw_b and 'выбрать' not in field_lower and not _is_hint(raw_b): + is_obj['app_sw'] = _clean_multiline(raw_b) + log_fn(f" Прикладные программы: {str(raw_b)[:60]}") continue if 'общесистемн' in field_lower: - if _set(field_lower, 'os_sw', val_b): - log_fn(f" Общесистемное ПО: {val_b[:60]}") + if raw_b and 'выбрать' not in field_lower and not _is_hint(raw_b): + is_obj['os_sw'] = _clean_multiline(raw_b) + log_fn(f" Общесистемное ПО: {str(raw_b)[:60]}") continue # --- Безопасность / 236 приказ --- if 'категория нарушителя' in field_lower: diff --git a/dokogen/ui.py b/dokogen/ui.py index 1b49ba2..8d31050 100644 --- a/dokogen/ui.py +++ b/dokogen/ui.py @@ -920,16 +920,19 @@ class DokoGenApp: sw_frame.pack(fill=tk.BOTH, expand=True) sw_fields = [ - ("ПО и аппаратные средства (модели, производители, количество):", "is_hw_sw_entry"), - ("Операционные системы:", "is_os_sw_entry"), - ("Прикладные программы (MS Office, OpenOffice и пр.):", "is_app_sw_entry"), + ("ПО и аппаратные средства (модели, производители, количество):", "is_hw_sw_entry", 5), + ("Операционные системы:", "is_os_sw_entry", 3), + ("Прикладные программы (MS Office, OpenOffice и пр.):", "is_app_sw_entry", 4), ] - for i, (label, attr) in enumerate(sw_fields): - ttk.Label(sw_frame, text=label).grid(row=i, column=0, sticky=tk.W, pady=2) - entry = ttk.Entry(sw_frame, width=70) - entry.grid(row=i, column=1, padx=8, sticky=tk.EW, pady=1) - setattr(self, attr, entry) - sw_frame.columnconfigure(1, weight=1) + for i, (label, attr, height) in enumerate(sw_fields): + ttk.Label(sw_frame, text=label).grid(row=i * 2, column=0, sticky=tk.W, pady=(4, 0)) + txt = tk.Text(sw_frame, width=70, height=height, wrap=tk.WORD) + txt.grid(row=i * 2 + 1, column=0, columnspan=2, padx=8, sticky=tk.EW, pady=1) + sb = ttk.Scrollbar(sw_frame, orient=tk.VERTICAL, command=txt.yview) + sb.grid(row=i * 2 + 1, column=2, sticky=tk.NS) + txt.configure(yscrollcommand=sb.set) + setattr(self, attr, txt) + sw_frame.columnconfigure(0, weight=1) # ===== Вкладка «Безопасность (236)» ===== sec_tab = ttk.Frame(nb2) @@ -1228,7 +1231,10 @@ class DokoGenApp: ]: w = getattr(self, attr, None) if w is not None: - setattr(isys, model_field, w.get().strip()) + if isinstance(w, tk.Text): + setattr(isys, model_field, w.get('1.0', 'end-1c').strip()) + else: + setattr(isys, model_field, w.get().strip()) # Списки isys.defense_tools_list = list(self.defense_tools_listbox.get(0, tk.END)) isys.users_list = list(self.users_listbox.get(0, tk.END)) @@ -1460,6 +1466,9 @@ class DokoGenApp: val = getattr(isys, model_field, '') or '' if isinstance(w, ttk.Combobox): w.set(val) + elif isinstance(w, tk.Text): + w.delete('1.0', tk.END) + w.insert('1.0', val) else: w.delete(0, tk.END) w.insert(0, val)