feat: переделка под ОКИИ (187-ФЗ) — объекты КИИ вместо ИС, категории 1-3, ОКВЭД, убраны ПДн/бумажные носители
- вкладка «Информационные системы» → «Объекты КИИ», убрана «Структура объекта» - «Тип угроз»/«УЗ» → категория значимости 1/2/3/без категории - удалены Категории ПДн, кол-во записей, перечень/субъекты/действия с ПДн + словари - «Пользователи ИС» → «Пользователи ОКИИ», средства защиты оставлены - вкладка «Бумажные документы» удалена полностью (GUI + логика + модели) - ОКВЭД: поля во вкладке «Организация», словарь okved.json, выбор галочками, импорт - нормализация регистра адреса DaData (КАПС → нормальный) - importer: объекты КИИ (наименование объекта, ПО, СЗИ, пользователи)
This commit is contained in:
+1
-1
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""DokoGen — Пакет генератора документов 187-ФЗ"""
|
||||
|
||||
from .models import Company, CommissionMember, InformationSystem, PaperDocument
|
||||
from .models import Company, CommissionMember, InformationSystem
|
||||
from .declension import inflect_name, decline, company_name_decline, get_short_fio, get_initials
|
||||
from .variables import build_replacements, VARIABLE_DEFS
|
||||
from .generator import process_template
|
||||
|
||||
@@ -0,0 +1,268 @@
|
||||
[
|
||||
"01.1 Выращивание однолетних культур",
|
||||
"01.2 Выращивание многолетних культур",
|
||||
"01.3 Выращивание рассады",
|
||||
"01.4 Животноводство",
|
||||
"01.5 Смешанное сельское хозяйство",
|
||||
"01.6 Деятельность вспомогательная в области производства сельскохозяйственных культур и послеуборочной обработки сельхозпродукции",
|
||||
"01.7 Охота, отлов и отстрел диких животных",
|
||||
"01.8 Лесоводство и прочая деятельность в области лесозаготовок",
|
||||
"02.1 Лесоводство и прочая деятельность в области лесозаготовок",
|
||||
"02.2 Предоставление услуг в области лесоводства и лесозаготовок",
|
||||
"02.3 Сбор и заготовка пищевых лесных ресурсов, недревесных лесных ресурсов и лекарственных растений",
|
||||
"03.1 Рыболовство",
|
||||
"03.2 Рыбоводство",
|
||||
"05.1 Добыча угля",
|
||||
"05.2 Добыча и обогащение угля",
|
||||
"06.1 Добыча сырой нефти и природного газа",
|
||||
"06.2 Добыча природного газа и газового конденсата",
|
||||
"07.1 Добыча железных руд",
|
||||
"07.2 Добыча руд цветных металлов",
|
||||
"08.1 Добыча камня, песка и глины",
|
||||
"08.9 Добыча полезных ископаемых, не включенных в другие группировки",
|
||||
"09.1 Деятельность вспомогательная в сфере добычи нефти и природного газа",
|
||||
"10.1 Переработка и консервирование мяса и мясной пищевой продукции",
|
||||
"10.2 Переработка и консервирование рыбы, ракообразных и моллюсков",
|
||||
"10.3 Переработка и консервирование фруктов и овощей",
|
||||
"10.4 Производство растительных и животных масел и жиров",
|
||||
"10.5 Производство молочной продукции",
|
||||
"10.6 Производство мукомольной и крупяной промышленности, крахмала и крахмалосодержащих продуктов",
|
||||
"10.7 Производство хлебобулочных и мучных кондитерских изделий",
|
||||
"10.8 Производство прочих пищевых продуктов",
|
||||
"10.9 Производство готовых кормов для животных",
|
||||
"11.0 Производство напитков",
|
||||
"12.0 Производство табачных изделий",
|
||||
"13.1 Прядение текстильных волокон",
|
||||
"13.2 Ткацкое производство",
|
||||
"13.3 Отделка тканей и текстильных изделий",
|
||||
"13.9 Производство прочих текстильных изделий",
|
||||
"14.1 Производство одежды, кроме одежды из меха",
|
||||
"14.2 Производство меховых изделий",
|
||||
"14.3 Производство вязаных и трикотажных изделий одежды",
|
||||
"15.1 Дубление и отделка кожи, производство чемоданов, сумок и т.п.",
|
||||
"15.2 Производство обуви",
|
||||
"16.1 Распиловка и строгание древесины",
|
||||
"16.2 Производство изделий из дерева, пробки, соломки",
|
||||
"17.1 Производство целлюлозы, древесной массы, бумаги и картона",
|
||||
"17.2 Производство изделий из бумаги и картона",
|
||||
"18.1 Деятельность полиграфическая и предоставление услуг в этой области",
|
||||
"18.2 Копирование записанных носителей информации",
|
||||
"19.1 Производство кокса",
|
||||
"19.2 Производство нефтепродуктов",
|
||||
"20.1 Производство основных химических веществ, удобрений, пластмасс и синтетического каучука",
|
||||
"20.2 Производство пестицидов и прочей агрохимической продукции",
|
||||
"20.3 Производство красок, лаков и аналогичных покрытий",
|
||||
"20.4 Производство мыла и моющих, чистящих и полирующих средств",
|
||||
"20.5 Производство прочих химических продуктов",
|
||||
"21.1 Производство фармацевтических субстанций",
|
||||
"21.2 Производство лекарственных препаратов и материалов",
|
||||
"22.1 Производство резиновых изделий",
|
||||
"22.2 Производство пластмассовых изделий",
|
||||
"23.1 Производство стекла и изделий из стекла",
|
||||
"23.2 Производство огнеупорных изделий",
|
||||
"23.3 Производство строительных керамических материалов",
|
||||
"23.4 Производство прочих фарфоровых и керамических изделий",
|
||||
"23.5 Производство цемента, извести и гипса",
|
||||
"23.6 Производство изделий из бетона, цемента и гипса",
|
||||
"23.7 Резка, обработка и отделка камня",
|
||||
"23.9 Производство абразивных и неметаллических минеральных изделий",
|
||||
"24.1 Производство чугуна, стали и ферросплавов",
|
||||
"24.2 Производство стальных труб, полых профилей и фитингов",
|
||||
"24.3 Производство прочих стальных изделий первичной обработки",
|
||||
"24.4 Производство основных драгоценных металлов и цветных металлов",
|
||||
"24.5 Литье металлов",
|
||||
"25.1 Производство строительных металлических конструкций и изделий",
|
||||
"25.2 Производство металлических цистерн, резервуаров и емкостей",
|
||||
"25.3 Производство паровых котлов",
|
||||
"25.4 Производство оружия и боеприпасов",
|
||||
"25.5 Ковка, прессование, штамповка и профилирование; порошковая металлургия",
|
||||
"25.6 Обработка металлических изделий механическая",
|
||||
"25.7 Производство ножевых изделий и столовых приборов",
|
||||
"25.9 Производство прочих готовых металлических изделий",
|
||||
"26.1 Производство элементов электронной аппаратуры и печатных схем",
|
||||
"26.2 Производство компьютеров и периферийного оборудования",
|
||||
"26.3 Производство коммуникационного оборудования",
|
||||
"26.4 Производство бытовой электроники",
|
||||
"26.5 Производство контрольно-измерительных приборов и навигационных приборов",
|
||||
"26.6 Производство облучающего и электротерапевтического оборудования",
|
||||
"26.7 Производство оптических приборов, фото- и кинооборудования",
|
||||
"26.8 Производство незаписанных магнитных и оптических носителей",
|
||||
"27.1 Производство электродвигателей, генераторов, трансформаторов",
|
||||
"27.2 Производство электрических аккумуляторов и аккумуляторных батарей",
|
||||
"27.3 Производство кабелей и кабельной арматуры",
|
||||
"27.4 Производство электрического осветительного оборудования",
|
||||
"27.5 Производство бытовых приборов",
|
||||
"27.9 Производство прочего электрического оборудования",
|
||||
"28.1 Производство машин и оборудования общего назначения",
|
||||
"28.2 Производство прочих машин и оборудования общего назначения",
|
||||
"28.3 Производство машин и оборудования для сельского и лесного хозяйства",
|
||||
"28.4 Производство станков, машин и оборудования для обработки металлов",
|
||||
"28.9 Производство прочих машин и оборудования специального назначения",
|
||||
"29.1 Производство автотранспортных средств",
|
||||
"29.2 Производство кузовов, прицепов и полуприцепов",
|
||||
"29.3 Производство комплектующих и принадлежностей для автотранспортных средств",
|
||||
"30.1 Строительство кораблей, судов и лодок",
|
||||
"30.2 Производство железнодорожных локомотивов и подвижного состава",
|
||||
"30.3 Производство летательных аппаратов и космических аппаратов",
|
||||
"30.4 Производство военных боевых машин",
|
||||
"30.9 Производство прочих транспортных средств и оборудования",
|
||||
"31.0 Производство мебели",
|
||||
"32.1 Производство ювелирных изделий, бижутерии",
|
||||
"32.2 Производство музыкальных инструментов",
|
||||
"32.3 Производство спортивных товаров",
|
||||
"32.4 Производство игр и игрушек",
|
||||
"32.5 Производство медицинских инструментов и оборудования",
|
||||
"32.9 Производство изделий, не включенных в другие группировки",
|
||||
"33.1 Ремонт и монтаж металлических изделий и машин",
|
||||
"33.2 Монтаж промышленных машин и оборудования",
|
||||
"35.1 Производство, передача и распределение электроэнергии",
|
||||
"35.2 Производство и распределение газообразного топлива",
|
||||
"35.3 Производство, передача и распределение пара и горячей воды",
|
||||
"36.0 Забор, очистка и распределение воды",
|
||||
"37.0 Сбор и обработка сточных вод",
|
||||
"38.1 Сбор отходов",
|
||||
"38.2 Обработка и утилизация отходов",
|
||||
"38.3 Деятельность по обработке вторичного сырья",
|
||||
"39.0 Предоставление услуг в области ликвидации последствий загрязнений",
|
||||
"41.1 Разработка строительных проектов",
|
||||
"41.2 Строительство жилых и нежилых зданий",
|
||||
"42.1 Строительство автомобильных и железных дорог",
|
||||
"42.2 Строительство инженерных коммуникаций",
|
||||
"42.9 Строительство прочих инженерных сооружений",
|
||||
"43.1 Разборка и снос зданий, подготовка строительного участка",
|
||||
"43.2 Производство электромонтажных, санитарно-технических и прочих строительно-монтажных работ",
|
||||
"43.3 Работы строительные отделочные",
|
||||
"43.9 Работы строительные специализированные прочие",
|
||||
"45.1 Торговля автотранспортными средствами",
|
||||
"45.2 Техническое обслуживание и ремонт автотранспортных средств",
|
||||
"45.3 Торговля автомобильными деталями и принадлежностями",
|
||||
"45.4 Торговля мотоциклами и их обслуживание",
|
||||
"46.1 Торговля оптовая за вознаграждение или на договорной основе",
|
||||
"46.2 Торговля оптовая сельскохозяйственным сырьем и живыми животными",
|
||||
"46.3 Торговля оптовая пищевыми продуктами, напитками и табачными изделиями",
|
||||
"46.4 Торговля оптовая непродовольственными потребительскими товарами",
|
||||
"46.5 Торговля оптовая информационным и коммуникационным оборудованием",
|
||||
"46.6 Торговля оптовая прочими машинами и оборудованием",
|
||||
"46.7 Торговля оптовая специализированная прочая",
|
||||
"46.9 Торговля оптовая неспециализированная",
|
||||
"47.1 Торговля розничная в неспециализированных магазинах",
|
||||
"47.2 Торговля розничная пищевыми продуктами в специализированных магазинах",
|
||||
"47.3 Торговля розничная моторным топливом",
|
||||
"47.4 Торговля розничная информационным и коммуникационным оборудованием",
|
||||
"47.5 Торговля розничная прочими бытовыми изделиями",
|
||||
"47.6 Торговля розничная товарами культурно-развлекательного назначения",
|
||||
"47.7 Торговля розничная прочими товарами в специализированных магазинах",
|
||||
"47.8 Торговля розничная в палатках и на рынках",
|
||||
"47.9 Торговля розничная вне магазинов, палаток, рынков",
|
||||
"49.1 Деятельность железнодорожного пассажирского транспорта",
|
||||
"49.2 Деятельность железнодорожного грузового транспорта",
|
||||
"49.3 Деятельность прочего сухопутного пассажирского транспорта",
|
||||
"49.4 Деятельность автомобильного грузового транспорта",
|
||||
"49.5 Деятельность трубопроводного транспорта",
|
||||
"50.1 Деятельность морского пассажирского транспорта",
|
||||
"50.2 Деятельность морского грузового транспорта",
|
||||
"50.3 Деятельность внутреннего водного пассажирского транспорта",
|
||||
"50.4 Деятельность внутреннего водного грузового транспорта",
|
||||
"51.1 Деятельность пассажирского воздушного транспорта",
|
||||
"51.2 Деятельность грузового воздушного транспорта",
|
||||
"52.1 Деятельность по складированию и хранению",
|
||||
"52.2 Деятельность транспортная вспомогательная",
|
||||
"53.1 Деятельность почтовой связи общего пользования",
|
||||
"53.2 Деятельность почтовой связи прочая и курьерская",
|
||||
"55.1 Деятельность гостиниц и прочих мест временного проживания",
|
||||
"55.2 Деятельность по предоставлению мест для краткосрочного проживания",
|
||||
"55.3 Деятельность по предоставлению мест для кемпингов и стоянок",
|
||||
"56.1 Деятельность ресторанов и услуги по доставке продуктов питания",
|
||||
"56.2 Деятельность предприятий общественного питания",
|
||||
"56.3 Подача напитков",
|
||||
"58.1 Издание книг, периодических изданий и другие виды издательской деятельности",
|
||||
"58.2 Издание программного обеспечения",
|
||||
"59.1 Производство кинофильмов, видеофильмов и телевизионных программ",
|
||||
"59.2 Деятельность в области звукозаписи и издания музыкальных произведений",
|
||||
"60.1 Деятельность в области радиовещания",
|
||||
"60.2 Деятельность в области телевизионного вещания",
|
||||
"61.1 Деятельность в области проводной связи",
|
||||
"61.2 Деятельность в области беспроводной связи",
|
||||
"61.3 Деятельность в области спутниковой связи",
|
||||
"61.9 Деятельность в области телекоммуникаций прочая",
|
||||
"62.0 Разработка компьютерного программного обеспечения, консультационные услуги в данной области",
|
||||
"63.1 Деятельность по обработке данных, предоставление услуг по размещению информации",
|
||||
"63.9 Деятельность в области информационных услуг прочая",
|
||||
"64.1 Денежное посредничество",
|
||||
"64.2 Деятельность холдинговых компаний",
|
||||
"64.3 Деятельность инвестиционных фондов",
|
||||
"64.9 Деятельность по финансовому посредничеству прочая",
|
||||
"65.1 Страхование жизни",
|
||||
"65.2 Перестрахование",
|
||||
"65.3 Страхование и перестрахование, негосударственное пенсионное обеспечение",
|
||||
"66.1 Деятельность вспомогательная в сфере финансовых услуг",
|
||||
"66.2 Деятельность вспомогательная в сфере страхования",
|
||||
"66.3 Деятельность по управлению фондами",
|
||||
"68.1 Покупка и продажа собственного недвижимого имущества",
|
||||
"68.2 Аренда и управление собственным или арендованным недвижимым имуществом",
|
||||
"68.3 Операции с недвижимым имуществом за вознаграждение или на договорной основе",
|
||||
"69.1 Деятельность в области права",
|
||||
"69.2 Деятельность по оказанию услуг в области бухгалтерского учета и аудита",
|
||||
"70.1 Деятельность головных офисов",
|
||||
"70.2 Консультирование по вопросам управления",
|
||||
"70.3 Деятельность по связям с общественностью",
|
||||
"71.1 Деятельность в области архитектуры, инженерных изысканий",
|
||||
"71.2 Технические испытания, исследования, анализ и сертификация",
|
||||
"72.1 Научные исследования и разработки в области естественных и технических наук",
|
||||
"72.2 Научные исследования и разработки в области общественных и гуманитарных наук",
|
||||
"73.1 Деятельность рекламная",
|
||||
"73.2 Исследование конъюнктуры рынка и опросы общественного мнения",
|
||||
"74.1 Деятельность специализированная в области дизайна",
|
||||
"74.2 Деятельность в области фотографии",
|
||||
"74.3 Деятельность по письменному и устному переводу",
|
||||
"74.9 Деятельность профессиональная, научная и техническая прочая",
|
||||
"75.0 Деятельность ветеринарная",
|
||||
"77.1 Аренда и лизинг автотранспортных средств",
|
||||
"77.2 Прокат и аренда предметов личного пользования и хозяйственно-бытового назначения",
|
||||
"77.3 Аренда и лизинг машин и оборудования",
|
||||
"77.4 Аренда и лизинг интеллектуальной собственности",
|
||||
"78.1 Деятельность агентств по подбору персонала",
|
||||
"78.2 Деятельность агентств по временному трудоустройству",
|
||||
"78.3 Деятельность по обеспечению безопасности в чрезвычайных ситуациях; деятельность по обеспечению безопасности в области информатизации",
|
||||
"79.1 Деятельность туристических агентств и туроператоров",
|
||||
"79.9 Услуги по бронированию прочие",
|
||||
"80.1 Деятельность частных охранных служб",
|
||||
"80.2 Деятельность систем обеспечения безопасности",
|
||||
"80.3 Деятельность по расследованию",
|
||||
"81.1 Деятельность по комплексному обслуживанию помещений",
|
||||
"81.2 Деятельность по чистке и уборке",
|
||||
"81.3 Деятельность по благоустройству ландшафта",
|
||||
"82.1 Деятельность административно-хозяйственная комплексная",
|
||||
"82.2 Деятельность центров обработки телефонных вызовов",
|
||||
"82.3 Деятельность по организации конференций и выставок",
|
||||
"82.9 Деятельность по предоставлению вспомогательных услуг для бизнеса",
|
||||
"84.1 Деятельность органов государственного управления",
|
||||
"84.2 Предоставление государственных услуг обществу",
|
||||
"84.3 Деятельность в области обязательного социального обеспечения",
|
||||
"85.1 Образование общее",
|
||||
"85.2 Образование профессиональное",
|
||||
"85.3 Обучение профессиональное",
|
||||
"85.4 Образование дополнительное",
|
||||
"86.1 Деятельность больничных организаций",
|
||||
"86.2 Медицинская и стоматологическая практика",
|
||||
"86.9 Деятельность в области медицины прочая",
|
||||
"87.1 Деятельность по уходу с обеспечением проживания",
|
||||
"87.2 Деятельность по уходу с обеспечением проживания для умственно отсталых и психически больных",
|
||||
"87.9 Деятельность по уходу с обеспечением проживания прочая",
|
||||
"88.1 Предоставление социальных услуг без обеспечения проживания престарелым и инвалидам",
|
||||
"88.9 Предоставление прочих социальных услуг без обеспечения проживания",
|
||||
"90.0 Деятельность творческая, деятельность в области искусства и организации развлечений",
|
||||
"91.0 Деятельность библиотек, архивов, музеев и прочих объектов культуры",
|
||||
"92.0 Деятельность по организации и проведению азартных игр и пари",
|
||||
"93.1 Деятельность в области спорта",
|
||||
"93.2 Деятельность в области отдыха и развлечений",
|
||||
"94.1 Деятельность профессиональных союзов",
|
||||
"94.2 Деятельность религиозных организаций",
|
||||
"94.9 Деятельность общественных организаций прочая",
|
||||
"95.1 Ремонт компьютеров и коммуникационного оборудования",
|
||||
"95.2 Ремонт предметов личного потребления и хозяйственно-бытового назначения",
|
||||
"96.0 Деятельность по предоставлению прочих персональных услуг",
|
||||
"97.0 Деятельность домашних хозяйств с наемными работниками",
|
||||
"98.0 Деятельность домашних хозяйств по производству товаров для собственного использования",
|
||||
"99.0 Деятельность экстерриториальных организаций и органов"
|
||||
]
|
||||
@@ -1,17 +0,0 @@
|
||||
[
|
||||
"сбор",
|
||||
"систематизация",
|
||||
"хранение",
|
||||
"извлечение",
|
||||
"передача (предоставление доступа)",
|
||||
"блокирование",
|
||||
"уничтожение",
|
||||
"запись",
|
||||
"накопление",
|
||||
"уточнение (обновление, изменение)",
|
||||
"использование",
|
||||
"обезличивание",
|
||||
"удаление",
|
||||
"распространение",
|
||||
"иные действия"
|
||||
]
|
||||
@@ -1,16 +0,0 @@
|
||||
[
|
||||
"Сотрудникам организации",
|
||||
"Соискателям",
|
||||
"Родственникам сотрудников",
|
||||
"Уволенным сотрудникам",
|
||||
"Контрагентам",
|
||||
"Представителям контрагентов",
|
||||
"Клиентам",
|
||||
"Посетителям сайта",
|
||||
"Выгодоприобретателям по договорам",
|
||||
"Субъект персональных данных, предоставивший согласие на трансграничную передачу персональных данных",
|
||||
"Учащимя",
|
||||
"Студентам",
|
||||
"Законным представителям",
|
||||
"Иным категориям субъектов персональных данных, персональные данные которых обрабатываются"
|
||||
]
|
||||
@@ -1,44 +0,0 @@
|
||||
[
|
||||
"Фамилия, Имя, Отчество",
|
||||
"Год рождения",
|
||||
"Месяц рождения",
|
||||
"Дата рождения",
|
||||
"Место рождения",
|
||||
"Семейное положение",
|
||||
"Социальное положение",
|
||||
"Имущественное положение",
|
||||
"Сведения о доходах",
|
||||
"Пол",
|
||||
"Адрес электронной почты",
|
||||
"Адрес места жительства",
|
||||
"Адрес регистрации",
|
||||
"Номер телефона",
|
||||
"Страховой номер индивидуального лицевого счета (СНИЛС)",
|
||||
"Идентификационный номер налогоплательщика (ИНН);",
|
||||
"Сведения о гражданстве",
|
||||
"Данные документа, удостоверяющего личность",
|
||||
"Данные водительского удостоверения",
|
||||
"Данные документа, удостоверяющего личность за пределами Российской Федерации",
|
||||
"Данные документа, содержащиеся в свидетельстве о рождении",
|
||||
"Реквизиты банковских карт",
|
||||
"Номер расчетного счета",
|
||||
"Номер лицевого счета",
|
||||
"Профессия",
|
||||
"Должность",
|
||||
"Сведения о трудовой деятельности (в том числе стаж работы, данные о трудовой занятости на текущее время с указанием наименования и расчетного счета организации)",
|
||||
"Сведения об отношении к воинской обязанности, о воинском учете",
|
||||
"Иные персональные данные",
|
||||
"Сведения, собираемые посредством метрических программ (например, Яндекс.Метрика)",
|
||||
"Сведения об образовании",
|
||||
"Фото-видео изображение лица",
|
||||
"Данные голоса человека",
|
||||
"Сведения о состоянии здоровья",
|
||||
"Национальная принадлежность",
|
||||
"Политические взгляды",
|
||||
"Религиозные или философские убеждения",
|
||||
"Сведения о судимости",
|
||||
"Данные изображения лица, полученные с помощью фото- видео устройств, на основании которых можно установить его личность и которые используются оператором для установления личности субъекта персональных данных (биометрия)",
|
||||
"Данные голоса человека, на основании которых можно установить его личность и которые используются оператором для установления личности субъекта персональных данных (биометрия)",
|
||||
"Изображение папиллярных узоров пальцев рук",
|
||||
"Иные биометрические персональные данные"
|
||||
]
|
||||
+10
-10
@@ -97,10 +97,10 @@ def _generate_variables_reference(path):
|
||||
_bullet(doc, f'{var} — {desc}')
|
||||
|
||||
_h(doc, 'Циклы', 12)
|
||||
_bullet(doc, 'information_systems — ИС ({{item.name}}, {{item.description}}, {{item.structure}}, {{item.pd_actions}}, {{item.defense_tools_list}}, {{item.users}}, {{item.category}}, {{item.pd_count}} и др.).')
|
||||
_bullet(doc, 'information_systems — объекты КИИ ({{item.name}}, {{item.description}}, {{item.category}}, {{item.defense_tools_list}}, {{item.users}}, {{item.software}} и др.).')
|
||||
_bullet(doc, 'commission — комиссия ({{item.role}}, {{item.position}}, {{item.fio}}, {{item.fio_initials}}, {{item.fio_short}}, {{item.signature}}).')
|
||||
_bullet(doc, 'employees_access — сотрудники с доступом ({{item.position}}, {{item.fio}}).')
|
||||
_bullet(doc, 'paper_documents — бумажные документы ({{item.document}}, {{item.storage}}).')
|
||||
|
||||
|
||||
doc.save(path)
|
||||
|
||||
@@ -120,10 +120,10 @@ def _generate_user_manual(path):
|
||||
|
||||
_h(doc, '2. Основные вкладки', 12)
|
||||
_bullet(doc, 'Организация — реквизиты, адреса, руководитель, склонения названий, проверка данных (Морфер / DaData / ИИ).')
|
||||
_bullet(doc, 'Информационные системы — ИС, категории ПДн, действия с ПДн, средства защиты, пользователи.')
|
||||
_bullet(doc, 'Комиссия — состав комиссии по классификации ИСПДн.')
|
||||
_bullet(doc, 'Пользователи — сотрудники с доступом к ПДн.')
|
||||
_bullet(doc, 'Бумажные документы — бумажные носители ПДн и места хранения.')
|
||||
_bullet(doc, 'Объекты КИИ — наименование, описание, категория значимости, средства защиты, пользователи ОКИИ.')
|
||||
_bullet(doc, 'Комиссия — состав комиссии по категорированию объектов КИИ.')
|
||||
_bullet(doc, 'Пользователи — сотрудники с доступом к объектам КИИ.')
|
||||
|
||||
_bullet(doc, 'Генерация — выбор шаблонов и запуск генерации.')
|
||||
_bullet(doc, 'Лог — журнал действий, экспорт в файл.')
|
||||
|
||||
@@ -137,13 +137,13 @@ def _generate_user_manual(path):
|
||||
|
||||
_h(doc, '4. Циклы (повторяющиеся блоки)', 12)
|
||||
_p(doc, 'Если в документе нужно повторить блок для каждой ИС, комиссии, '
|
||||
'сотрудника или бумажного документа — используются маркеры цикла:')
|
||||
'сотрудника или объекта КИИ — используются маркеры цикла:')
|
||||
_p(doc, '<!-- loop:information_systems --> ... {{item.name}} ... <!-- loop_end -->', bold=True)
|
||||
_p(doc, 'Доступные циклы:')
|
||||
_bullet(doc, 'information_systems — ИС ({{item.name}}, {{item.description}}, {{item.structure}}, {{item.pd_actions}}, {{item.defense_tools_list}}, {{item.users}}, {{item.category}}, {{item.pd_count}} и др.).')
|
||||
_bullet(doc, 'information_systems — объекты КИИ ({{item.name}}, {{item.description}}, {{item.category}}, {{item.defense_tools_list}}, {{item.users}}, {{item.software}} и др.).')
|
||||
_bullet(doc, 'commission — комиссия ({{item.role}}, {{item.position}}, {{item.fio}}, {{item.fio_initials}}, {{item.fio_short}}, {{item.signature}}).')
|
||||
_bullet(doc, 'employees_access — сотрудники с доступом ({{item.position}}, {{item.fio}}).')
|
||||
_bullet(doc, 'paper_documents — бумажные документы ({{item.document}}, {{item.storage}}).')
|
||||
|
||||
_p(doc, 'Внутри цикла переменные пишутся через точку: {{item.поле}}. '
|
||||
'Допускается и синтаксис без точки: {{item_поле}}.')
|
||||
|
||||
@@ -179,7 +179,7 @@ def _generate_admin_manual(path):
|
||||
_h(doc, '1. Файлы настроек рядом с программой', 12)
|
||||
_bullet(doc, 'api_settings.json — API-ключи (DaData, ИИ).')
|
||||
_bullet(doc, 'import_settings.json — словарь импорта: что и откуда брать из опросника.')
|
||||
_bullet(doc, 'dictionaries/ — справочники: defense_tools.json, pd_actions.json, pd_items.json, pd_categories.json.')
|
||||
_bullet(doc, 'dictionaries/ — справочники: defense_tools.json, okved.json.')
|
||||
_bullet(doc, 'dokogen.log — журнал работы (пишется автоматически).')
|
||||
_bullet(doc, 'docs/ — инструкции и пустой опросник (создаются автоматически).')
|
||||
|
||||
|
||||
+53
-150
@@ -141,8 +141,8 @@ def import_187fz(filepath: str, log_fn: Optional[Callable] = None, settings_path
|
||||
data = {
|
||||
'fullName': '', 'shortName': '', 'addressLegal': '', 'addressActual': '',
|
||||
'email': '', 'phone': '', 'inn': '', 'ogrn': '', 'kpp': '',
|
||||
'okved': '', 'chiefPosition': '', 'chiefFio': '',
|
||||
'ispdnFio': '', 'ispdnPosition': '', 'paperDocuments': '', 'paperStorage': '',
|
||||
'okved': '', 'okvedExtra': '', 'chiefPosition': '', 'chiefFio': '',
|
||||
'ispdnFio': '', 'ispdnPosition': '',
|
||||
'ispdnDepartment': '', 'ispdnEmail': '', 'ispdnPhone': '',
|
||||
'informationSystems': [], 'commission': [], 'employeesAccess': [],
|
||||
}
|
||||
@@ -248,7 +248,7 @@ def _import_187_common(wb, data, settings, log_fn):
|
||||
|
||||
|
||||
def _import_187_is_list(wb, data, settings, log_fn):
|
||||
"""Импорт информационных систем из остальных листов."""
|
||||
"""Импорт объектов КИИ из остальных листов (ОБЪЕКТ №1, №2, ...)."""
|
||||
exclude_kws = settings.get('is_sheet_exclude_keywords', ['общие сведения', 'сотрудники'])
|
||||
is_sheets = []
|
||||
for sn in wb.sheetnames:
|
||||
@@ -257,7 +257,7 @@ def _import_187_is_list(wb, data, settings, log_fn):
|
||||
continue
|
||||
is_sheets.append(sn)
|
||||
if not is_sheets:
|
||||
log_fn("⚠ Листы ИС не найдены")
|
||||
log_fn("⚠ Листы объектов КИИ не найдены")
|
||||
return
|
||||
|
||||
is_list = []
|
||||
@@ -275,16 +275,20 @@ def _import_187_is_list(wb, data, settings, log_fn):
|
||||
continue
|
||||
|
||||
is_obj = {
|
||||
'name': '', 'description': '', 'room': '', 'employee': '', 'position': '',
|
||||
'personalDataList': [], 'pd_subjects_list': [], 'pd_actions': [],
|
||||
'usersList': [], 'isInternet': False, 'personalDataCount': 'менее 100 000',
|
||||
'defense_tools': [], 'processing_mode': '',
|
||||
'name': '', 'description': '', 'usersList': [],
|
||||
'isInternet': False, 'defense_tools': [], 'software': '',
|
||||
}
|
||||
|
||||
current_section = 'header'
|
||||
pd_items_raw = []
|
||||
subjects_raw = []
|
||||
actions_raw = []
|
||||
defense_keywords = {
|
||||
'антивирус': 'Антивирус',
|
||||
'крипто': 'СКЗИ',
|
||||
'межсетев': 'Межсетевой экран',
|
||||
'сзи.*от несанкционирован': 'СЗИ от НСД',
|
||||
'обнаружен.*вторжен': 'СОВ',
|
||||
'программные.*средств.*модул': 'Доверенная загрузка',
|
||||
'доверен.*загрузк': 'Доверенная загрузка',
|
||||
'гарантирован': 'Доверенная загрузка',
|
||||
}
|
||||
|
||||
for vals in rows:
|
||||
field = (vals[0] or '').strip()
|
||||
@@ -292,159 +296,58 @@ def _import_187_is_list(wb, data, settings, log_fn):
|
||||
val_b = clean_value(vals[1]) if len(vals) > 1 else ''
|
||||
is_checked = val_b.upper() in ('ДА', 'YES', '✓', '✔', '1', 'TRUE', 'ЕСТЬ')
|
||||
|
||||
if 'цель обработки пд' in field_lower or ('цель обработки' in field_lower and ':' in field):
|
||||
current_section = 'purpose'
|
||||
if 'наименование объекта' in field_lower or 'название объекта' in field_lower:
|
||||
is_obj['name'] = val_b
|
||||
log_fn(f" Объект КИИ: {val_b}")
|
||||
continue
|
||||
if 'перечисленные пдн принадлежат' in field_lower or 'принадлеж' in field_lower:
|
||||
current_section = 'subjects'
|
||||
continue
|
||||
if 'структура ис' in field_lower or 'структура информационной' in field_lower:
|
||||
if 'описание объекта' in field_lower or 'описание окки' in field_lower:
|
||||
if val_b:
|
||||
is_obj['structure'] = val_b
|
||||
elif ':' in field:
|
||||
# Значение может быть записано в той же ячейке после двоеточия
|
||||
tail = field.split(':', 1)[1].strip()
|
||||
if tail and 'выбрать' not in tail.lower():
|
||||
is_obj['structure'] = tail
|
||||
is_obj['description'] = val_b
|
||||
continue
|
||||
if 'названия программ' in field_lower or ('программ' in field_lower and 'ос взаимодейств' in field_lower):
|
||||
if 'операционная' in field_lower or 'прикладные программы' in field_lower or 'наименования программно' in field_lower:
|
||||
if val_b:
|
||||
is_obj['software'] = val_b
|
||||
continue
|
||||
if 'локальную сеть' in field_lower:
|
||||
is_obj['is_has_lan'] = is_checked
|
||||
continue
|
||||
if 'количество записей' in field_lower or 'количество субъект' in field_lower:
|
||||
if val_b:
|
||||
is_obj['personalDataCount'] = val_b
|
||||
continue
|
||||
if 'перечень действий с пд' in field_lower or ('действи' in field_lower and 'пд' in field_lower):
|
||||
current_section = 'actions'
|
||||
continue
|
||||
if 'способ обработки пд' in field_lower:
|
||||
if val_b:
|
||||
is_obj['processing_mode'] = val_b
|
||||
is_obj['software'] = (is_obj['software'] + '; ' + val_b) if is_obj['software'] else val_b
|
||||
continue
|
||||
if 'интернет' in field_lower:
|
||||
is_obj['isInternet'] = is_checked
|
||||
continue
|
||||
|
||||
if current_section == 'header':
|
||||
if 'перечень пд' in field_lower:
|
||||
current_section = 'pd_items'
|
||||
elif 'название ис' in field_lower or 'наименование ис' in field_lower or 'наименование информационной' in field_lower or 'наименование объекта' in field_lower or 'название объекта' in field_lower:
|
||||
is_obj['name'] = val_b
|
||||
log_fn(f" ИС: {val_b}")
|
||||
elif 'описание ис' in field_lower or 'описание информационной' in field_lower:
|
||||
if val_b:
|
||||
is_obj['description'] = val_b
|
||||
log_fn(f" Описание: {val_b[:60]}")
|
||||
continue
|
||||
elif 'сотрудник' in field_lower and 'должност' not in field_lower:
|
||||
is_obj['employee'] = val_b
|
||||
if val_b:
|
||||
parts = [p.strip() for p in val_b.replace('\n', ';').split(';') if p.strip()]
|
||||
for part in parts:
|
||||
cleaned = part.strip()
|
||||
if ':' in cleaned and len(cleaned.split(':')[0]) < 20:
|
||||
cleaned = cleaned.split(':', 1)[1].strip()
|
||||
if cleaned and cleaned not in is_obj['usersList']:
|
||||
is_obj['usersList'].append(cleaned)
|
||||
elif 'должность сотрудника' in field_lower:
|
||||
is_obj['position'] = val_b
|
||||
elif 'наименование отдела' in field_lower:
|
||||
pass
|
||||
elif 'номер кабинета' in field_lower:
|
||||
is_obj['room'] = val_b
|
||||
elif 'значение' in field_lower or 'примечание' in field_lower:
|
||||
continue
|
||||
elif val_b and 'выбрать' not in field_lower:
|
||||
if not is_obj['description']:
|
||||
is_obj['description'] = f"{field}: {val_b}"
|
||||
else:
|
||||
is_obj['description'] += f"\n{field}: {val_b}"
|
||||
elif current_section == 'pd_items':
|
||||
if is_checked and 'выбрать' not in field_lower and field_lower != 'значение':
|
||||
pd_items_raw.append(field)
|
||||
if 'цель обработки' in field_lower:
|
||||
current_section = 'purpose'
|
||||
elif current_section == 'purpose':
|
||||
purpose_val = val_b or field or ''
|
||||
if purpose_val and 'выбрать' not in purpose_val.lower() and ':' not in purpose_val[:40]:
|
||||
is_obj['purpose'] = purpose_val
|
||||
log_fn(f" Цель: {purpose_val[:80]}")
|
||||
current_section = 'header'
|
||||
elif current_section == 'subjects':
|
||||
if is_checked and 'выбрать' not in field_lower:
|
||||
subjects_raw.append(field)
|
||||
if 'количество записей' in field_lower or 'действи' in field_lower:
|
||||
continue
|
||||
elif current_section == 'actions':
|
||||
if is_checked and 'выбрать' not in field_lower and 'защита' not in field_lower[:20]:
|
||||
actions_raw.append(field)
|
||||
if 'защита' in field_lower and 'действи' not in field_lower:
|
||||
current_section = 'defense'
|
||||
elif current_section == 'defense':
|
||||
defense_keywords = {
|
||||
'антивирус': 'Антивирус',
|
||||
'крипто': 'СКЗИ',
|
||||
'межсетев': 'Межсетевой экран',
|
||||
'сзи.*от несанкционирован': 'СЗИ от НСД',
|
||||
'обнаружен.*вторжен': 'СОВ',
|
||||
'программные.*средств.*модул': 'Доверенная загрузка',
|
||||
'доверен.*загрузк': 'Доверенная загрузка',
|
||||
'гарантирован': 'Доверенная загрузка',
|
||||
}
|
||||
matched_label = None
|
||||
for kw, label in defense_keywords.items():
|
||||
if re.search(kw, field_lower):
|
||||
matched_label = label
|
||||
break
|
||||
if matched_label and val_b and 'выбрать' not in field_lower:
|
||||
# Без префикса категории: просто название средства (Kaspersky, КриптоПро)
|
||||
is_obj['defense_tools'].append(val_b)
|
||||
elif matched_label:
|
||||
pass
|
||||
elif 'куда' in field_lower or 'передаются' in field_lower:
|
||||
current_section = 'header'
|
||||
elif 'способ обработки' in field_lower or 'локальную сеть' in field_lower or 'интернет' in field_lower:
|
||||
current_section = 'header'
|
||||
|
||||
if pd_items_raw:
|
||||
is_obj['personalDataList'] = pd_items_raw
|
||||
log_fn(f" ПДн: {len(pd_items_raw)}")
|
||||
if subjects_raw:
|
||||
is_obj['pd_subjects_list'] = subjects_raw
|
||||
log_fn(f" Субъектов: {len(subjects_raw)}")
|
||||
if actions_raw:
|
||||
is_obj['pd_actions'] = actions_raw
|
||||
log_fn(f" Действий: {len(actions_raw)}")
|
||||
if 'сотрудник' in field_lower and 'должност' not in field_lower:
|
||||
if val_b:
|
||||
parts = [p.strip() for p in val_b.replace('\n', ';').split(';') if p.strip()]
|
||||
for part in parts:
|
||||
cleaned = part.strip()
|
||||
if ':' in cleaned and len(cleaned.split(':')[0]) < 20:
|
||||
cleaned = cleaned.split(':', 1)[1].strip()
|
||||
if cleaned and cleaned not in is_obj['usersList']:
|
||||
is_obj['usersList'].append(cleaned)
|
||||
continue
|
||||
# Средства защиты
|
||||
matched = False
|
||||
for kw, label in defense_keywords.items():
|
||||
if re.search(kw, field_lower):
|
||||
if val_b and 'выбрать' not in field_lower:
|
||||
is_obj['defense_tools'].append(val_b)
|
||||
matched = True
|
||||
break
|
||||
if matched:
|
||||
continue
|
||||
if val_b and 'выбрать' not in field_lower and field_lower not in ('значение', 'примечание'):
|
||||
# Остальные заполненные поля — в описание (адрес, сфера, тип и т.д.)
|
||||
if not is_obj['description']:
|
||||
is_obj['description'] = f"{field}: {val_b}"
|
||||
else:
|
||||
is_obj['description'] += f"\n{field}: {val_b}"
|
||||
|
||||
if not is_obj['name']:
|
||||
is_obj['name'] = sheet_name.strip()
|
||||
log_fn(f" ⚠ ИС без названия — использовано имя листа: {is_obj['name']}")
|
||||
|
||||
# Автоопределение категорий ПДн
|
||||
pd_cats = []
|
||||
all_pd_text = ' '.join(p.lower() for p in pd_items_raw)
|
||||
special_kw = ['состоян.*здоров', 'национальн', 'политическ', 'религиозн', 'философ', 'судимост', 'интимн']
|
||||
if any(re.search(kw, all_pd_text) for kw in special_kw):
|
||||
pd_cats.append('специальные')
|
||||
bio_kw = ['биометрическ', 'изображен.*лиц', 'голос.*человек', 'папилляр', 'дактилоскоп', 'фото.*изображен']
|
||||
if any(re.search(kw, all_pd_text) for kw in bio_kw):
|
||||
pd_cats.append('биометрические')
|
||||
if pd_items_raw:
|
||||
pd_cats.append('иные')
|
||||
if pd_cats:
|
||||
is_obj['personalDataCategory'] = pd_cats
|
||||
log_fn(f" Категории ПДн: {', '.join(pd_cats)}")
|
||||
log_fn(f" ⚠ Объект без названия — использовано имя листа: {is_obj['name']}")
|
||||
|
||||
is_list.append(is_obj)
|
||||
log_fn(f" ✅ {is_obj['name']} — импортирована")
|
||||
log_fn(f" ✅ {is_obj['name']} — импортирован")
|
||||
|
||||
if is_list:
|
||||
data['informationSystems'] = is_list
|
||||
log_fn(f"\n✅ Всего импортировано ИС: {len(is_list)}")
|
||||
log_fn(f"\n✅ Всего импортировано объектов КИИ: {len(is_list)}")
|
||||
|
||||
|
||||
def _import_187_employees(wb, data, settings, log_fn):
|
||||
@@ -497,7 +400,7 @@ def _import_187_employees(wb, data, settings, log_fn):
|
||||
if 'перечень сотрудников' in a_lower:
|
||||
in_commission = False
|
||||
in_employees = True
|
||||
log_fn(" Секция: сотрудники с доступом к ПДн")
|
||||
log_fn(" Секция: сотрудники с доступом к объектам КИИ")
|
||||
continue
|
||||
if a_lower in ('№ п/п', '№', 'номер', ''):
|
||||
continue
|
||||
|
||||
+10
-31
@@ -7,7 +7,7 @@ from typing import List, Dict, Any
|
||||
|
||||
@dataclass
|
||||
class CommissionMember:
|
||||
"""Член комиссии по ПДн"""
|
||||
"""Член комиссии по категорированию объектов КИИ"""
|
||||
role: str = "член комиссии"
|
||||
position: str = ""
|
||||
fio: str = ""
|
||||
@@ -15,38 +15,19 @@ class CommissionMember:
|
||||
|
||||
@dataclass
|
||||
class InformationSystem:
|
||||
"""Информационная система (обработка ПДн)"""
|
||||
"""Объект КИИ (вместо информационной системы 152-ФЗ)"""
|
||||
name: str = ""
|
||||
description: str = ""
|
||||
is_local_network: bool = False
|
||||
is_internet: bool = False
|
||||
defence_level: str = ""
|
||||
threat_type: str = "3"
|
||||
category: str = ""
|
||||
pd_list: str = ""
|
||||
pd_count: str = "менее 100 000"
|
||||
users: str = ""
|
||||
defense_tools_list: List[str] = field(default_factory=list)
|
||||
processing_modes: str = ""
|
||||
subject_type: str = "работники организации"
|
||||
pd_actions_list: List[str] = field(default_factory=list)
|
||||
pd_subjects_list: List[str] = field(default_factory=list)
|
||||
personal_data_list: List[str] = field(default_factory=list)
|
||||
users_list: List[str] = field(default_factory=list)
|
||||
personal_data_category: List[str] = field(default_factory=list)
|
||||
category: str = "" # Категория значимости объекта КИИ: 1 / 2 / 3 / без категории
|
||||
users_list: List[str] = field(default_factory=list) # Пользователи ОКИИ
|
||||
defense_tools_list: List[str] = field(default_factory=list) # Средства защиты
|
||||
purpose: str = ""
|
||||
room: str = ""
|
||||
structure: str = "" # Структура информационной системы
|
||||
structure: str = "" # Структура объекта КИИ
|
||||
software: str = "" # Программное обеспечение
|
||||
|
||||
|
||||
@dataclass
|
||||
class PaperDocument:
|
||||
"""Бумажный документ с ПДн"""
|
||||
name: str = ""
|
||||
storage: str = ""
|
||||
|
||||
|
||||
@dataclass
|
||||
class Company:
|
||||
"""Организация — основной объект (187-ФЗ)"""
|
||||
@@ -75,6 +56,8 @@ class Company:
|
||||
ogrn: str = ""
|
||||
ogrn_date: str = ""
|
||||
kpp: str = ""
|
||||
okved: str = "" # Основной ОКВЭД
|
||||
okved_extra: List[str] = field(default_factory=list) # Дополнительные ОКВЭД
|
||||
|
||||
# Должностные лица
|
||||
chief_position: str = ""
|
||||
@@ -91,7 +74,6 @@ class Company:
|
||||
# Списки
|
||||
commission: List[CommissionMember] = field(default_factory=list)
|
||||
information_systems: List[InformationSystem] = field(default_factory=list)
|
||||
paper_documents_list: List[PaperDocument] = field(default_factory=list)
|
||||
employees_access: List[Dict[str, str]] = field(default_factory=list)
|
||||
|
||||
# Наименование структурного подразделения по безопасности
|
||||
@@ -135,13 +117,10 @@ class Company:
|
||||
InformationSystem(**isys) if isinstance(isys, dict) else isys
|
||||
for isys in d["information_systems"]
|
||||
]
|
||||
if "paper_documents_list" in d and isinstance(d["paper_documents_list"], list):
|
||||
d["paper_documents_list"] = [
|
||||
PaperDocument(**p) if isinstance(p, dict) else p
|
||||
for p in d["paper_documents_list"]
|
||||
]
|
||||
if "employees_access" in d and not isinstance(d["employees_access"], list):
|
||||
d["employees_access"] = []
|
||||
if "okved_extra" in d and not isinstance(d.get("okved_extra"), list):
|
||||
d["okved_extra"] = []
|
||||
valid = {f.name for f in cls.__dataclass_fields__.values()}
|
||||
return cls(**{k: v for k, v in d.items() if k in valid})
|
||||
|
||||
|
||||
+158
-362
@@ -11,7 +11,7 @@ from datetime import datetime
|
||||
from typing import List, Optional
|
||||
|
||||
from . import (
|
||||
Company, CommissionMember, InformationSystem, PaperDocument,
|
||||
Company, CommissionMember, InformationSystem,
|
||||
inflect_name,
|
||||
build_replacements, VARIABLE_DEFS,
|
||||
process_template,
|
||||
@@ -157,7 +157,6 @@ class DokoGenApp:
|
||||
self._build_is_tab()
|
||||
self._build_commission_tab()
|
||||
self._build_users_tab()
|
||||
self._build_paper_tab()
|
||||
self._build_generation_tab()
|
||||
self._build_log_tab()
|
||||
|
||||
@@ -270,6 +269,33 @@ class DokoGenApp:
|
||||
|
||||
f2.columnconfigure(1, weight=1)
|
||||
|
||||
# --- ОКВЭД ---
|
||||
f_okved = ttk.LabelFrame(left, text="ОКВЭД", padding=10)
|
||||
f_okved.pack(fill=tk.X, pady=5)
|
||||
|
||||
# Основной ОКВЭД — один, из словаря или вручную
|
||||
ttk.Label(f_okved, text="Основной ОКВЭД:").grid(row=0, column=0, sticky=tk.W, pady=2)
|
||||
okved_entry = ttk.Entry(f_okved, width=50)
|
||||
okved_entry.grid(row=0, column=1, padx=10, sticky=tk.EW, pady=1)
|
||||
self._company_entries['okved'] = okved_entry
|
||||
ttk.Button(f_okved, text="Выбрать из словаря",
|
||||
command=self._select_main_okved).grid(row=0, column=2, padx=5)
|
||||
|
||||
# Дополнительные ОКВЭД — сколько угодно
|
||||
ttk.Label(f_okved, text="Дополнительные ОКВЭД:").grid(row=1, column=0, sticky=tk.NW, pady=2)
|
||||
self.okved_extra_listbox = tk.Listbox(f_okved, height=4, width=50)
|
||||
self.okved_extra_listbox.grid(row=1, column=1, padx=10, sticky=tk.EW, pady=1)
|
||||
okved_btn = ttk.Frame(f_okved)
|
||||
okved_btn.grid(row=1, column=2, padx=5, sticky=tk.N)
|
||||
ttk.Button(okved_btn, text="Выбрать из словаря",
|
||||
command=self._select_extra_okved).pack(fill=tk.X, pady=1)
|
||||
ttk.Button(okved_btn, text="+ Добавить",
|
||||
command=self._add_okved_manual).pack(fill=tk.X, pady=1)
|
||||
ttk.Button(okved_btn, text="- Удалить",
|
||||
command=self._delete_okved_extra).pack(fill=tk.X, pady=1)
|
||||
|
||||
f_okved.columnconfigure(1, weight=1)
|
||||
|
||||
# --- Должностные лица ---
|
||||
f3 = ttk.LabelFrame(left, text="Должностные лица", padding=10)
|
||||
f3.pack(fill=tk.X, pady=5)
|
||||
@@ -686,12 +712,12 @@ class DokoGenApp:
|
||||
# ---------- ВКЛАДКА "ИНФОРМАЦИОННЫЕ СИСТЕМЫ" ----------
|
||||
def _build_is_tab(self):
|
||||
tab = ttk.Frame(self.notebook)
|
||||
self.notebook.add(tab, text="Информационные системы")
|
||||
self.notebook.add(tab, text="Объекты КИИ")
|
||||
|
||||
# Левая панель — список ИС
|
||||
# Левая панель — список объектов КИИ
|
||||
left = ttk.Frame(tab)
|
||||
left.pack(side=tk.LEFT, fill=tk.Y, padx=5, pady=5)
|
||||
ttk.Label(left, text="Информационные системы:").pack()
|
||||
ttk.Label(left, text="Объекты КИИ:").pack()
|
||||
self.is_listbox = tk.Listbox(left, width=35, height=18, exportselection=False)
|
||||
self.is_listbox.pack(fill=tk.BOTH, expand=True, pady=5)
|
||||
self.is_listbox.bind('<<ListboxSelect>>', self._on_is_select)
|
||||
@@ -703,7 +729,7 @@ class DokoGenApp:
|
||||
ttk.Button(btn_frame, text="- Удалить", command=self._delete_is).pack(side=tk.LEFT, padx=2)
|
||||
|
||||
# Правая панель — редактирование
|
||||
self._is_edit_panel = ttk.LabelFrame(tab, text="Редактирование ИС")
|
||||
self._is_edit_panel = ttk.LabelFrame(tab, text="Редактирование объекта КИИ")
|
||||
self._is_edit_panel.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5)
|
||||
|
||||
fields_frame = ttk.Frame(self._is_edit_panel, padding=10)
|
||||
@@ -726,51 +752,19 @@ class DokoGenApp:
|
||||
self.is_internet_var = tk.BooleanVar()
|
||||
ttk.Checkbutton(chk_frame, text="Интернет", variable=self.is_internet_var).pack(side=tk.LEFT, padx=5)
|
||||
|
||||
# Тип угроз
|
||||
self.is_threat_var = tk.StringVar(value="3")
|
||||
self.is_threat_var.trace_add("write", lambda *_: self._on_categories_or_count_changed())
|
||||
ttk.Label(fields_frame, text="Тип угроз:").grid(row=3, column=0, sticky=tk.W, pady=2)
|
||||
threat_frame = ttk.Frame(fields_frame)
|
||||
threat_frame.grid(row=3, column=1, sticky=tk.W, padx=10)
|
||||
for val, text in [("1", "1 (НДВ в СПО)"), ("2", "2 (НДВ в ППО)"), ("3", "3 (НДВ нет)")]:
|
||||
ttk.Radiobutton(threat_frame, text=text, variable=self.is_threat_var,
|
||||
# Категория значимости объекта КИИ
|
||||
self.is_category_var = tk.StringVar(value="без категории")
|
||||
ttk.Label(fields_frame, text="Категория значимости:").grid(row=3, column=0, sticky=tk.W, pady=2)
|
||||
cat_frame = ttk.Frame(fields_frame)
|
||||
cat_frame.grid(row=3, column=1, sticky=tk.W, padx=10)
|
||||
for val in ["1", "2", "3", "без категории"]:
|
||||
ttk.Radiobutton(cat_frame, text=val, variable=self.is_category_var,
|
||||
value=val).pack(side=tk.LEFT, padx=3)
|
||||
|
||||
# Уровень защищённости
|
||||
self.is_defence_var = tk.StringVar(value="4")
|
||||
ttk.Label(fields_frame, text="Ур. защищённости:").grid(row=4, column=0, sticky=tk.W, pady=2)
|
||||
def_frame = ttk.Frame(fields_frame)
|
||||
def_frame.grid(row=4, column=1, sticky=tk.W, padx=10)
|
||||
for lvl in ["1", "2", "3", "4"]:
|
||||
ttk.Radiobutton(def_frame, text=f"{lvl} ур.",
|
||||
variable=self.is_defence_var, value=lvl).pack(side=tk.LEFT, padx=3)
|
||||
|
||||
|
||||
# Категории ПДн
|
||||
ttk.Label(fields_frame, text="Категории ПДн:").grid(row=5, column=0, sticky=tk.NW, pady=2)
|
||||
cat_frame = ttk.Frame(fields_frame)
|
||||
cat_frame.grid(row=5, column=1, sticky=tk.W, padx=10)
|
||||
self.category_vars = []
|
||||
for cat in ["специальные", "биометрические", "общедоступные", "иные"]:
|
||||
var = tk.BooleanVar()
|
||||
var.trace_add('write', lambda *_: self._on_categories_or_count_changed())
|
||||
cb = ttk.Checkbutton(cat_frame, text=cat, variable=var)
|
||||
cb.pack(anchor=tk.W)
|
||||
self.category_vars.append((cat, var))
|
||||
|
||||
# Количество записей ПДн (выпадающий список)
|
||||
ttk.Label(fields_frame, text="Кол-во записей:").grid(row=6, column=0, sticky=tk.W, pady=2)
|
||||
self.is_pd_count_var = tk.StringVar(value="менее 100 000")
|
||||
self.is_pd_count_combo = ttk.Combobox(fields_frame, textvariable=self.is_pd_count_var,
|
||||
values=["менее 100 000", "более 100 000"],
|
||||
state='readonly', width=28)
|
||||
self.is_pd_count_combo.grid(row=6, column=1, padx=10, sticky=tk.W)
|
||||
self.is_pd_count_combo.bind('<<ComboboxSelected>>', lambda e: self._on_categories_or_count_changed())
|
||||
|
||||
# Структура ИС
|
||||
ttk.Label(fields_frame, text="Структура ИС:").grid(row=7, column=0, sticky=tk.W, pady=2)
|
||||
self.is_structure_entry = ttk.Entry(fields_frame, width=55)
|
||||
self.is_structure_entry.grid(row=7, column=1, padx=10, sticky=tk.EW)
|
||||
# Программное обеспечение
|
||||
ttk.Label(fields_frame, text="Программное обеспечение:").grid(row=4, column=0, sticky=tk.W, pady=2)
|
||||
self.is_software_entry = ttk.Entry(fields_frame, width=55)
|
||||
self.is_software_entry.grid(row=4, column=1, padx=10, sticky=tk.EW)
|
||||
|
||||
fields_frame.columnconfigure(1, weight=1)
|
||||
|
||||
@@ -778,39 +772,6 @@ class DokoGenApp:
|
||||
nb2 = ttk.Notebook(self._is_edit_panel)
|
||||
nb2.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
||||
|
||||
# Список ПДн
|
||||
pd_tab = ttk.Frame(nb2)
|
||||
nb2.add(pd_tab, text="Перечень ПДн")
|
||||
pd_btn_frame = ttk.Frame(pd_tab)
|
||||
pd_btn_frame.pack(fill=tk.X)
|
||||
ttk.Button(pd_btn_frame, text="+ Добавить", command=self._add_pd_item).pack(side=tk.LEFT, padx=2)
|
||||
ttk.Button(pd_btn_frame, text="- Удалить", command=lambda: self._del_from_listbox(self.pd_listbox)).pack(side=tk.LEFT, padx=2)
|
||||
ttk.Button(pd_btn_frame, text="Выбрать из списка", command=self._select_from_pd_list).pack(side=tk.LEFT, padx=2)
|
||||
self.pd_listbox = tk.Listbox(pd_tab, height=6)
|
||||
self.pd_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||
|
||||
# Субъекты ПДн
|
||||
subj_tab = ttk.Frame(nb2)
|
||||
nb2.add(subj_tab, text="Субъекты ПДн")
|
||||
subj_btn_frame = ttk.Frame(subj_tab)
|
||||
subj_btn_frame.pack(fill=tk.X)
|
||||
ttk.Button(subj_btn_frame, text="+ Добавить", command=self._add_pd_subjects_item).pack(side=tk.LEFT, padx=2)
|
||||
ttk.Button(subj_btn_frame, text="- Удалить", command=lambda: self._del_from_listbox(self.pd_subjects_listbox)).pack(side=tk.LEFT, padx=2)
|
||||
ttk.Button(subj_btn_frame, text="Выбрать из списка", command=self._select_from_subjects_list).pack(side=tk.LEFT, padx=2)
|
||||
self.pd_subjects_listbox = tk.Listbox(subj_tab, height=6)
|
||||
self.pd_subjects_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||
|
||||
# Действия с ПДн
|
||||
act_tab = ttk.Frame(nb2)
|
||||
nb2.add(act_tab, text="Действия с ПДн")
|
||||
act_btn_frame = ttk.Frame(act_tab)
|
||||
act_btn_frame.pack(fill=tk.X)
|
||||
ttk.Button(act_btn_frame, text="+ Добавить", command=self._add_pd_action_item).pack(side=tk.LEFT, padx=2)
|
||||
ttk.Button(act_btn_frame, text="- Удалить", command=lambda: self._del_from_listbox(self.pd_actions_listbox)).pack(side=tk.LEFT, padx=2)
|
||||
ttk.Button(act_btn_frame, text="Выбрать из списка", command=self._select_from_actions_list).pack(side=tk.LEFT, padx=2)
|
||||
self.pd_actions_listbox = tk.Listbox(act_tab, height=6)
|
||||
self.pd_actions_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||
|
||||
# СЗИ
|
||||
def_tab = ttk.Frame(nb2)
|
||||
nb2.add(def_tab, text="Средства защиты")
|
||||
@@ -822,9 +783,9 @@ class DokoGenApp:
|
||||
self.defense_tools_listbox = tk.Listbox(def_tab, height=6)
|
||||
self.defense_tools_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||
|
||||
# Пользователи
|
||||
# Пользователи ОКИИ
|
||||
user_tab = ttk.Frame(nb2)
|
||||
nb2.add(user_tab, text="Пользователи ИС")
|
||||
nb2.add(user_tab, text="Пользователи ОКИИ")
|
||||
self.users_listbox = tk.Listbox(user_tab, height=6)
|
||||
self.users_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||
btnf = ttk.Frame(user_tab)
|
||||
@@ -886,7 +847,7 @@ class DokoGenApp:
|
||||
frame = ttk.Frame(scrollable, padding=10)
|
||||
frame.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
ttk.Label(frame, text="Сотрудники, имеющие доступ к ПДн",
|
||||
ttk.Label(frame, text="Сотрудники, имеющие доступ к объектам КИИ",
|
||||
font=('Arial', 11, 'bold')).pack(anchor=tk.W, pady=(0, 10))
|
||||
|
||||
# Заголовки
|
||||
@@ -971,102 +932,6 @@ class DokoGenApp:
|
||||
if emp['position'] or emp['fio']:
|
||||
self.model.employees_access.append(emp)
|
||||
|
||||
# ---------- ВКЛАДКА "БУМАЖНЫЕ ДОКУМЕНТЫ" ----------
|
||||
def _build_paper_tab(self):
|
||||
tab = ttk.Frame(self.notebook)
|
||||
self.notebook.add(tab, text="Бумажные документы")
|
||||
|
||||
canvas = tk.Canvas(tab, highlightthickness=0)
|
||||
scrollbar = ttk.Scrollbar(tab, orient=tk.VERTICAL, command=canvas.yview)
|
||||
scrollable = ttk.Frame(canvas)
|
||||
scrollable.bind("<Configure>",
|
||||
lambda e: canvas.configure(scrollregion=canvas.bbox("all")))
|
||||
canvas.create_window((0, 0), window=scrollable, anchor="nw")
|
||||
canvas.configure(yscrollcommand=scrollbar.set)
|
||||
canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
||||
|
||||
frame = ttk.Frame(scrollable, padding=10)
|
||||
frame.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
ttk.Label(frame, text="Бумажные носители ПДн (документы)",
|
||||
font=('Arial', 11, 'bold')).pack(anchor=tk.W, pady=(0, 10))
|
||||
|
||||
# Заголовки
|
||||
header = ttk.Frame(frame)
|
||||
header.pack(fill=tk.X)
|
||||
for w, col in [(45, "Название документа"), (45, "Место хранения")]:
|
||||
ttk.Label(header, text=col, font=('', 9, 'bold'),
|
||||
width=w, anchor=tk.W).pack(side=tk.LEFT, padx=2)
|
||||
|
||||
self._paper_frame = ttk.Frame(frame)
|
||||
self._paper_frame.pack(fill=tk.X, pady=5)
|
||||
|
||||
btn_frame = ttk.Frame(frame)
|
||||
btn_frame.pack(fill=tk.X, pady=5)
|
||||
ttk.Button(btn_frame, text="+ Добавить документ",
|
||||
command=self._add_paper_row_ui).pack(side=tk.LEFT, padx=2)
|
||||
ttk.Button(btn_frame, text="- Удалить выбранный",
|
||||
command=self._delete_paper_selected).pack(side=tk.LEFT, padx=2)
|
||||
|
||||
self._paper_count_label = ttk.Label(frame, text="Добавлено: 0")
|
||||
self._paper_count_label.pack(anchor=tk.W, pady=5)
|
||||
|
||||
self._paper_entries = []
|
||||
# Загрузка существующих
|
||||
for p in self.model.paper_documents_list:
|
||||
self._add_paper_row(p.name, p.storage)
|
||||
self._update_paper_count()
|
||||
|
||||
def _add_paper_row(self, name="", storage=""):
|
||||
"""Добавляет одну строку документа."""
|
||||
row = ttk.Frame(self._paper_frame)
|
||||
row.pack(fill=tk.X, pady=2)
|
||||
|
||||
name_entry = ttk.Entry(row, width=45)
|
||||
name_entry.insert(0, name)
|
||||
name_entry.pack(side=tk.LEFT, padx=2)
|
||||
|
||||
storage_entry = ttk.Entry(row, width=45)
|
||||
storage_entry.insert(0, storage)
|
||||
storage_entry.pack(side=tk.LEFT, padx=2)
|
||||
|
||||
del_btn = ttk.Button(row, text="✖", width=3,
|
||||
command=lambda: self._delete_paper_row(row))
|
||||
del_btn.pack(side=tk.LEFT, padx=5)
|
||||
|
||||
self._paper_entries.append((row, name_entry, storage_entry))
|
||||
self._update_paper_count()
|
||||
|
||||
def _add_paper_row_ui(self):
|
||||
self._add_paper_row()
|
||||
|
||||
def _delete_paper_row(self, row_frame):
|
||||
self._paper_entries = [e for e in self._paper_entries if e[0] != row_frame]
|
||||
row_frame.destroy()
|
||||
self._update_paper_count()
|
||||
|
||||
def _delete_paper_selected(self):
|
||||
if self._paper_entries:
|
||||
row_frame = self._paper_entries[-1][0]
|
||||
self._delete_paper_row(row_frame)
|
||||
|
||||
def _update_paper_count(self):
|
||||
count = len(self._paper_entries)
|
||||
if hasattr(self, '_paper_count_label'):
|
||||
self._paper_count_label.config(text=f"Добавлено: {count}")
|
||||
|
||||
def _collect_paper_documents(self):
|
||||
"""Собирает данные бумажных документов из GUI в модель."""
|
||||
self.model.paper_documents_list.clear()
|
||||
for row, name_e, storage_e in self._paper_entries:
|
||||
name = name_e.get().strip()
|
||||
storage = storage_e.get().strip()
|
||||
if name or storage:
|
||||
self.model.paper_documents_list.append(
|
||||
PaperDocument(name=name, storage=storage)
|
||||
)
|
||||
|
||||
# ---------- ВКЛАДКА "ГЕНЕРАЦИЯ" ----------
|
||||
def _build_generation_tab(self):
|
||||
tab = ttk.Frame(self.notebook)
|
||||
@@ -1142,29 +1007,21 @@ class DokoGenApp:
|
||||
setattr(c, key, entry.get().strip())
|
||||
c.contract_number = self.doc_number_var.get()
|
||||
c.contract_date = self.doc_date_var.get()
|
||||
if hasattr(self, 'okved_extra_listbox'):
|
||||
c.okved_extra = list(self.okved_extra_listbox.get(0, tk.END))
|
||||
|
||||
def _collect_is(self):
|
||||
"""Собирает данные ИС из GUI в модель."""
|
||||
"""Собирает данные объекта КИИ из GUI в модель."""
|
||||
if self._current_is_idx is not None and self._current_is_idx < len(self.model.information_systems):
|
||||
isys = self.model.information_systems[self._current_is_idx]
|
||||
isys.name = self.is_name_entry.get().strip()
|
||||
isys.description = self.is_description_entry.get().strip()
|
||||
if hasattr(self, 'is_structure_entry'):
|
||||
isys.structure = self.is_structure_entry.get().strip()
|
||||
if hasattr(self, 'is_software_entry'):
|
||||
isys.software = self.is_software_entry.get().strip()
|
||||
isys.is_local_network = self.is_lan_var.get()
|
||||
isys.is_internet = self.is_internet_var.get()
|
||||
isys.threat_type = self.is_threat_var.get()
|
||||
isys.defence_level = self.is_defence_var.get()
|
||||
cats = [c for c, v in self.category_vars if v.get()]
|
||||
isys.personal_data_category = cats
|
||||
if hasattr(self, 'is_pd_count_entry'):
|
||||
isys.pd_count = self.is_pd_count_entry.get().strip() or 'менее 100 000'
|
||||
isys.category = self.is_category_var.get()
|
||||
# Списки
|
||||
isys.personal_data_list = list(self.pd_listbox.get(0, tk.END))
|
||||
isys.pd_subjects_list = list(self.pd_subjects_listbox.get(0, tk.END))
|
||||
isys.pd_actions_list = list(self.pd_actions_listbox.get(0, tk.END))
|
||||
isys.defense_tools_list = list(self.defense_tools_listbox.get(0, tk.END))
|
||||
isys.users_list = list(self.users_listbox.get(0, tk.END))
|
||||
|
||||
@@ -1199,6 +1056,12 @@ class DokoGenApp:
|
||||
self.doc_number_var.set(c.contract_number or "")
|
||||
self.doc_date_var.set(c.contract_date or "")
|
||||
|
||||
# Дополнительные ОКВЭД
|
||||
if hasattr(self, 'okved_extra_listbox'):
|
||||
self.okved_extra_listbox.delete(0, tk.END)
|
||||
for item in (c.okved_extra or []):
|
||||
self.okved_extra_listbox.insert(tk.END, item)
|
||||
|
||||
# Склонения
|
||||
idx_map = {'nomn': 1, 'gent': 2, 'datv': 3, 'accs': 4, 'ablt': 5, 'loct': 6}
|
||||
for code, entry in self.decl_entries.items():
|
||||
@@ -1236,15 +1099,6 @@ class DokoGenApp:
|
||||
)
|
||||
self._update_emp_count()
|
||||
|
||||
# Бумажные документы
|
||||
if hasattr(self, '_paper_entries'):
|
||||
for row_frame, _, _ in self._paper_entries:
|
||||
row_frame.destroy()
|
||||
self._paper_entries.clear()
|
||||
for p in c.paper_documents_list:
|
||||
self._add_paper_row(p.name, p.storage)
|
||||
self._update_paper_count()
|
||||
|
||||
# Шаблоны
|
||||
self._on_templates_changed()
|
||||
|
||||
@@ -1296,64 +1150,6 @@ class DokoGenApp:
|
||||
self.log(f"🏙 Город определён: {city}")
|
||||
|
||||
# ==================== АВТОРАСЧЁТ УРОВНЯ ЗАЩИЩЁННОСТИ ====================
|
||||
def _auto_calc_defence_level(self):
|
||||
"""Автоматический расчёт УЗ по ПП №1119: категории ПДн, тип субъектов,
|
||||
кол-во записей, тип угроз."""
|
||||
if self._current_is_idx is None:
|
||||
return
|
||||
|
||||
categories = [cat for cat, var in self.category_vars if var.get()]
|
||||
pd_count = self.is_pd_count_var.get() if hasattr(self, 'is_pd_count_var') else 'менее 100 000'
|
||||
threat_type = self.is_threat_var.get() if hasattr(self, 'is_threat_var') else '3'
|
||||
is_large = 'более' in pd_count.lower()
|
||||
# Автоопределение типа субъектов: если в Субъектах ПДн выбраны только "Сотрудники" → работники, иначе → иные
|
||||
subj_items = self.pd_subjects_listbox.get(0, tk.END) if hasattr(self, 'pd_subjects_listbox') else []
|
||||
has_only_employees = len(subj_items) > 0 and all('сотрудник' in s.lower() for s in subj_items)
|
||||
is_employees = has_only_employees or len(subj_items) == 0
|
||||
|
||||
# Таблица ПП №1119: (категория, тип_субъектов, объём, тип_угроз) → УЗ
|
||||
def lookup(cat_group, employees, large, threat):
|
||||
# Общедоступные → всегда 4
|
||||
if cat_group == 'public':
|
||||
return 4
|
||||
# Специальные/биометрические
|
||||
if cat_group == 'special_bio':
|
||||
tbl = {
|
||||
('emp', False, '1'): 2, ('emp', True, '1'): 1,
|
||||
('other', False, '1'): 1, ('other', True, '1'): 1,
|
||||
('emp', False, '2'): 2, ('emp', True, '2'): 1,
|
||||
('other', False, '2'): 2, ('other', True, '2'): 1,
|
||||
('emp', False, '3'): 3, ('emp', True, '3'): 2,
|
||||
('other', False, '3'): 3, ('other', True, '3'): 2,
|
||||
}
|
||||
else: # иные
|
||||
tbl = {
|
||||
('emp', False, '1'): 3, ('emp', True, '1'): 2,
|
||||
('other', False, '1'): 2, ('other', True, '1'): 1,
|
||||
('emp', False, '2'): 4, ('emp', True, '2'): 3,
|
||||
('other', False, '2'): 3, ('other', True, '2'): 2,
|
||||
('emp', False, '3'): 4, ('emp', True, '3'): 4,
|
||||
('other', False, '3'): 4, ('other', True, '3'): 3,
|
||||
}
|
||||
key = ('emp' if employees else 'other', large, threat)
|
||||
return tbl.get(key, 4)
|
||||
|
||||
# Определяем группу категорий
|
||||
if 'общедоступные' in categories and not any(c in categories for c in ['специальные', 'биометрические', 'иные']):
|
||||
cat_group = 'public'
|
||||
elif any(c in categories for c in ['специальные', 'биометрические']):
|
||||
cat_group = 'special_bio'
|
||||
else:
|
||||
cat_group = 'other'
|
||||
|
||||
level = lookup(cat_group, is_employees, is_large, threat_type)
|
||||
self.is_defence_var.set(str(level))
|
||||
|
||||
def _on_categories_or_count_changed(self):
|
||||
"""Обработчик изменения категорий ПДн или количества записей."""
|
||||
if self._current_is_idx is not None:
|
||||
self._auto_calc_defence_level()
|
||||
|
||||
# ==================== КОМИССИЯ ====================
|
||||
def _add_commission_row(self, role="Член комиссии", position="", fio=""):
|
||||
row = ttk.Frame(self._comm_members_frame)
|
||||
@@ -1421,28 +1217,15 @@ class DokoGenApp:
|
||||
self.is_name_entry.insert(0, isys.name or "")
|
||||
self.is_description_entry.delete(0, tk.END)
|
||||
self.is_description_entry.insert(0, isys.description or "")
|
||||
if hasattr(self, 'is_structure_entry'):
|
||||
self.is_structure_entry.delete(0, tk.END)
|
||||
self.is_structure_entry.insert(0, isys.structure or "")
|
||||
if hasattr(self, 'is_software_entry'):
|
||||
self.is_software_entry.delete(0, tk.END)
|
||||
self.is_software_entry.insert(0, isys.software or "")
|
||||
self.is_lan_var.set(isys.is_local_network)
|
||||
self.is_internet_var.set(isys.is_internet)
|
||||
self.is_threat_var.set(isys.threat_type or "3")
|
||||
self.is_defence_var.set(isys.defence_level or "4")
|
||||
for cat, var in self.category_vars:
|
||||
var.set(cat in isys.personal_data_category)
|
||||
# Заполняем кол-во записей ПДн
|
||||
if hasattr(self, 'is_pd_count_entry'):
|
||||
self.is_pd_count_entry.delete(0, tk.END)
|
||||
self.is_pd_count_entry.insert(0, isys.pd_count or 'менее 100 000')
|
||||
self.is_category_var.set(isys.category or "без категории")
|
||||
|
||||
# Списки
|
||||
for lb, items in [
|
||||
(self.pd_listbox, isys.personal_data_list or []),
|
||||
(self.pd_subjects_listbox, isys.pd_subjects_list or []),
|
||||
(self.pd_actions_listbox, isys.pd_actions_list or []),
|
||||
(self.defense_tools_listbox, isys.defense_tools_list or []),
|
||||
(self.users_listbox, isys.users_list or []),
|
||||
]:
|
||||
@@ -1471,13 +1254,7 @@ class DokoGenApp:
|
||||
description=src.description,
|
||||
is_local_network=src.is_local_network,
|
||||
is_internet=src.is_internet,
|
||||
defence_level=src.defence_level,
|
||||
subject_type=src.subject_type,
|
||||
threat_type=src.threat_type,
|
||||
personal_data_category=list(src.personal_data_category),
|
||||
personal_data_list=list(src.personal_data_list),
|
||||
pd_actions_list=list(src.pd_actions_list),
|
||||
pd_subjects_list=list(src.pd_subjects_list),
|
||||
category=src.category,
|
||||
defense_tools_list=list(src.defense_tools_list),
|
||||
users_list=list(src.users_list),
|
||||
)
|
||||
@@ -1497,16 +1274,6 @@ class DokoGenApp:
|
||||
self.is_listbox.selection_set(0)
|
||||
self._on_is_select()
|
||||
|
||||
def _add_pd_item(self):
|
||||
item = simpledialog.askstring("Добавить ПДн", "Введите персональные данные:")
|
||||
if item:
|
||||
self.pd_listbox.insert(tk.END, item)
|
||||
|
||||
def _add_pd_subjects_item(self):
|
||||
item = simpledialog.askstring("Добавить категорию", "Введите категорию субъектов:")
|
||||
if item:
|
||||
self.pd_subjects_listbox.insert(tk.END, item)
|
||||
|
||||
def _add_defense_item(self):
|
||||
item = simpledialog.askstring("Добавить СЗИ", "Введите средство защиты:")
|
||||
if item:
|
||||
@@ -1533,7 +1300,7 @@ class DokoGenApp:
|
||||
if not names:
|
||||
messagebox.showinfo(
|
||||
"Выбор пользователей",
|
||||
"Список пуст.\nЗаполните вкладку «Пользователи» (сотрудники с доступом к ПДн) "
|
||||
"Список пуст.\nЗаполните вкладку «Пользователи» (сотрудники с доступом к объектам КИИ) "
|
||||
"или добавьте пользователей в другой ИС."
|
||||
)
|
||||
return
|
||||
@@ -1607,44 +1374,6 @@ class DokoGenApp:
|
||||
|
||||
dialog.wait_window()
|
||||
|
||||
def _add_pd_action_item(self):
|
||||
item = simpledialog.askstring("Добавить действие", "Введите действие с ПДн:")
|
||||
if item:
|
||||
self.pd_actions_listbox.insert(tk.END, item)
|
||||
|
||||
def _select_from_actions_list(self):
|
||||
"""Выбор действий с ПДн из JSON словаря."""
|
||||
import json
|
||||
dict_path = os.path.join(os.path.dirname(__file__), 'dictionaries', 'pd_actions.json')
|
||||
try:
|
||||
with open(dict_path, 'r', encoding='utf-8') as f:
|
||||
items = json.load(f)
|
||||
self._select_from_dialog("Выбор действий с персональными данными", items, self.pd_actions_listbox)
|
||||
except Exception as e:
|
||||
messagebox.showerror("Ошибка", f"Не удалось загрузить словарь: {e}")
|
||||
|
||||
def _select_from_pd_list(self):
|
||||
"""Выбор ПДн из JSON словаря."""
|
||||
import json
|
||||
dict_path = os.path.join(os.path.dirname(__file__), 'dictionaries', 'pd_items.json')
|
||||
try:
|
||||
with open(dict_path, 'r', encoding='utf-8') as f:
|
||||
items = json.load(f)
|
||||
self._select_from_dialog("Выбор персональных данных", items, self.pd_listbox)
|
||||
except Exception as e:
|
||||
messagebox.showerror("Ошибка", f"Не удалось загрузить словарь: {e}")
|
||||
|
||||
def _select_from_subjects_list(self):
|
||||
"""Выбор категорий субъектов из JSON словаря."""
|
||||
import json
|
||||
dict_path = os.path.join(os.path.dirname(__file__), 'dictionaries', 'pd_categories.json')
|
||||
try:
|
||||
with open(dict_path, 'r', encoding='utf-8') as f:
|
||||
items = json.load(f)
|
||||
self._select_from_dialog("Выбор категорий субъектов ПДн", items, self.pd_subjects_listbox)
|
||||
except Exception as e:
|
||||
messagebox.showerror("Ошибка", f"Не удалось загрузить словарь: {e}")
|
||||
|
||||
def _select_from_defense_list(self):
|
||||
"""Выбор СЗИ из JSON словаря."""
|
||||
import json
|
||||
@@ -1656,6 +1385,84 @@ class DokoGenApp:
|
||||
except Exception as e:
|
||||
messagebox.showerror("Ошибка", f"Не удалось загрузить словарь: {e}")
|
||||
|
||||
# ==================== ОКВЭД ====================
|
||||
def _load_okved_dict(self):
|
||||
"""Загружает словарь ОКВЭД (dictionaries/okved.json)."""
|
||||
import json
|
||||
dict_path = os.path.join(os.path.dirname(__file__), 'dictionaries', 'okved.json')
|
||||
try:
|
||||
with open(dict_path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
messagebox.showerror("Ошибка", f"Не удалось загрузить словарь ОКВЭД:\n{e}")
|
||||
return []
|
||||
|
||||
def _select_main_okved(self):
|
||||
"""Выбор основного ОКВЭД из словаря (всегда один)."""
|
||||
items = self._load_okved_dict()
|
||||
if not items:
|
||||
return
|
||||
current = self._company_entries.get('okved').get().strip() if 'okved' in self._company_entries else ''
|
||||
dialog = tk.Toplevel(self.root)
|
||||
dialog.title("Основной ОКВЭД")
|
||||
dialog.geometry("500x500")
|
||||
dialog.transient(self.root)
|
||||
dialog.grab_set()
|
||||
|
||||
frame = ttk.Frame(dialog, padding=10)
|
||||
frame.pack(fill=tk.BOTH, expand=True)
|
||||
ttk.Label(frame, text="Выберите основной ОКВЭД (один):",
|
||||
font=('Arial', 10, 'bold')).pack(anchor=tk.W, pady=(0, 5))
|
||||
|
||||
canvas = tk.Canvas(frame, highlightthickness=0)
|
||||
scrollbar = ttk.Scrollbar(frame, orient=tk.VERTICAL, command=canvas.yview)
|
||||
check_frame = ttk.Frame(canvas)
|
||||
check_frame.bind("<Configure>", lambda e: canvas.configure(scrollregion=canvas.bbox("all")))
|
||||
canvas.create_window((0, 0), window=check_frame, anchor="nw")
|
||||
canvas.configure(yscrollcommand=scrollbar.set)
|
||||
canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
||||
|
||||
# Радио-кнопки: основной всегда один
|
||||
var = tk.StringVar(value=current)
|
||||
for item in items:
|
||||
ttk.Radiobutton(check_frame, text=item, variable=var,
|
||||
value=item).pack(anchor=tk.W, pady=1)
|
||||
|
||||
btn_frame = ttk.Frame(dialog, padding=10)
|
||||
btn_frame.pack(fill=tk.X)
|
||||
|
||||
def _apply():
|
||||
val = var.get()
|
||||
if val:
|
||||
entry = self._company_entries.get('okved')
|
||||
if entry:
|
||||
entry.delete(0, tk.END)
|
||||
entry.insert(0, val)
|
||||
dialog.destroy()
|
||||
|
||||
ttk.Button(btn_frame, text="✅ Применить", command=_apply).pack(side=tk.RIGHT, padx=2)
|
||||
ttk.Button(btn_frame, text="Отмена", command=dialog.destroy).pack(side=tk.RIGHT, padx=2)
|
||||
dialog.wait_window()
|
||||
|
||||
def _select_extra_okved(self):
|
||||
"""Выбор дополнительных ОКВЭД из словаря (можно несколько)."""
|
||||
items = self._load_okved_dict()
|
||||
if not items:
|
||||
return
|
||||
self._select_from_dialog("Выбор дополнительных ОКВЭД", items, self.okved_extra_listbox)
|
||||
|
||||
def _add_okved_manual(self):
|
||||
"""Ручное добавление дополнительного ОКВЭД."""
|
||||
item = simpledialog.askstring("Добавить ОКВЭД", "Введите код ОКВЭД:")
|
||||
if item:
|
||||
self.okved_extra_listbox.insert(tk.END, item.strip())
|
||||
|
||||
def _delete_okved_extra(self):
|
||||
sel = self.okved_extra_listbox.curselection()
|
||||
if sel:
|
||||
self.okved_extra_listbox.delete(sel[0])
|
||||
|
||||
# ==================== ШАБЛОНЫ ====================
|
||||
def select_templates(self):
|
||||
folder = filedialog.askdirectory(title="Папка шаблонов")
|
||||
@@ -1720,7 +1527,6 @@ class DokoGenApp:
|
||||
self._collect_commission()
|
||||
self._collect_is()
|
||||
self._collect_employees()
|
||||
self._collect_paper_documents()
|
||||
|
||||
# Проверка
|
||||
if not self._validate_before_generate():
|
||||
@@ -1939,7 +1745,24 @@ class DokoGenApp:
|
||||
if data.get('ispdnDepartment'):
|
||||
self.model.security_department = data['ispdnDepartment']
|
||||
|
||||
# Информационные системы
|
||||
# ОКВЭД (основной и дополнительные)
|
||||
if data.get('okved'):
|
||||
entry = self._company_entries.get('okved')
|
||||
if entry:
|
||||
entry.delete(0, tk.END)
|
||||
entry.insert(0, data['okved'])
|
||||
if data.get('okvedExtra'):
|
||||
if hasattr(self, 'okved_extra_listbox'):
|
||||
self.okved_extra_listbox.delete(0, tk.END)
|
||||
extra = data['okvedExtra']
|
||||
if isinstance(extra, str):
|
||||
parts = [p.strip() for p in extra.replace(';', '\n').split('\n') if p.strip()]
|
||||
else:
|
||||
parts = [str(p).strip() for p in extra if str(p).strip()]
|
||||
for p in parts:
|
||||
self.okved_extra_listbox.insert(tk.END, p)
|
||||
|
||||
# Объекты КИИ
|
||||
if data.get('informationSystems'):
|
||||
self.model.information_systems.clear()
|
||||
for is_data in data['informationSystems']:
|
||||
@@ -1948,17 +1771,12 @@ class DokoGenApp:
|
||||
description=is_data.get('description', ''),
|
||||
is_local_network=is_data.get('is_has_lan', is_data.get('is_local_network', False)),
|
||||
is_internet=is_data.get('isInternet', False),
|
||||
pd_count=is_data.get('personalDataCount', 'менее 100 000'),
|
||||
personal_data_list=is_data.get('personalDataList', []),
|
||||
pd_subjects_list=is_data.get('pd_subjects_list', []),
|
||||
category=is_data.get('category', ''),
|
||||
defense_tools_list=is_data.get('defense_tools', []),
|
||||
users_list=is_data.get('usersList', []),
|
||||
personal_data_category=is_data.get('personalDataCategory', []),
|
||||
processing_modes=is_data.get('processing_mode', ''),
|
||||
purpose=is_data.get('description', ''),
|
||||
room=is_data.get('room', ''),
|
||||
purpose=is_data.get('purpose', is_data.get('description', '')),
|
||||
structure=is_data.get('structure', ''),
|
||||
pd_actions_list=is_data.get('pd_actions', []),
|
||||
software=is_data.get('software', ''),
|
||||
)
|
||||
self.model.information_systems.append(isys)
|
||||
self._refresh_is_listbox()
|
||||
@@ -2005,26 +1823,6 @@ class DokoGenApp:
|
||||
)
|
||||
self._update_emp_count()
|
||||
|
||||
# Бумажные документы (из «Названия документов» / «Место хранения»)
|
||||
if data.get('paperDocuments'):
|
||||
self.model.paper_documents_list.clear()
|
||||
names = [n.strip() for n in data['paperDocuments'].replace('\n', ';').split(';') if n.strip()]
|
||||
storages = [s.strip() for s in (data.get('paperStorage') or '').replace('\n', ';').split(';') if s.strip()]
|
||||
for i, name in enumerate(names):
|
||||
storage = storages[i] if i < len(storages) else (storages[0] if storages else '')
|
||||
self.model.paper_documents_list.append(
|
||||
PaperDocument(name=name, storage=storage)
|
||||
)
|
||||
# Обновляем GUI
|
||||
if hasattr(self, '_paper_entries'):
|
||||
for row_frame, _, _ in self._paper_entries:
|
||||
row_frame.destroy()
|
||||
self._paper_entries.clear()
|
||||
for p in self.model.paper_documents_list:
|
||||
self._add_paper_row(p.name, p.storage)
|
||||
self._update_paper_count()
|
||||
self.log(f" Бумажные документы: {len(self.model.paper_documents_list)} шт.")
|
||||
|
||||
self.log("✅ Импорт применён")
|
||||
self.root.after(0, _apply)
|
||||
|
||||
@@ -2069,7 +1867,6 @@ class DokoGenApp:
|
||||
self._collect_commission()
|
||||
self._collect_is()
|
||||
self._collect_employees()
|
||||
self._collect_paper_documents()
|
||||
try:
|
||||
with open(filename, 'w', encoding='utf-8') as f:
|
||||
json.dump(self.model.to_dict(), f, ensure_ascii=False, indent=2)
|
||||
@@ -2095,7 +1892,6 @@ class DokoGenApp:
|
||||
self._collect_commission()
|
||||
self._collect_is()
|
||||
self._collect_employees()
|
||||
self._collect_paper_documents()
|
||||
try:
|
||||
os.makedirs(os.path.dirname(self._autosave_path), exist_ok=True)
|
||||
with open(self._autosave_path, 'w', encoding='utf-8') as f:
|
||||
|
||||
+30
-91
@@ -57,7 +57,8 @@ VARIABLE_DEFS = [
|
||||
('{{company_ogrn_date}}', 'A', 'Дата ОГРН', 'company:ogrn_date'),
|
||||
('{{company_kpp}}', 'A', 'КПП', 'company:kpp'),
|
||||
('{{security_department}}', 'A', 'Наименование структурного подразделения по безопасности', 'company:security_department'),
|
||||
('{{item.pd_actions}}', 'B', 'Действия с ПДн (в цикле ИС)', 'item:pd_actions'),
|
||||
('{{company_okved}}', 'A', 'Основной ОКВЭД', 'company:okved'),
|
||||
('{{company_okved_extra}}', 'A', 'Дополнительные ОКВЭД', 'company:okved_extra'),
|
||||
|
||||
# === Комиссия ===
|
||||
('{{commission_list}}', 'A', 'Список комиссии', 'commission_list'),
|
||||
@@ -69,22 +70,15 @@ VARIABLE_DEFS = [
|
||||
('{{document_number}}', 'A', 'Номер документа', 'doc_number'),
|
||||
|
||||
# === 187-ФЗ ===
|
||||
('{{is_security_requirements}}', 'B', 'Требования уровня защищённости', 'security_reqs'),
|
||||
('{{is_list}}', 'B', 'Список всех ИС (нумерованный)', 'is_list'),
|
||||
('{{is_name}}', 'B', 'Наименование первой ИС', 'is:name'),
|
||||
('{{name}}', 'B', 'Название ИС (алиас)', 'is:name'),
|
||||
('{{description}}', 'B', 'Описание ИС (алиас)', 'is:description'),
|
||||
('{{category}}', 'B', 'Категория ИС (алиас)', 'is:category'),
|
||||
('{{is_security_requirements}}', 'B', 'Требования к защите объекта КИИ', 'security_reqs'),
|
||||
('{{is_list}}', 'B', 'Список всех объектов КИИ (нумерованный)', 'is_list'),
|
||||
('{{is_name}}', 'B', 'Наименование первого объекта КИИ', 'is:name'),
|
||||
('{{name}}', 'B', 'Название объекта КИИ (алиас)', 'is:name'),
|
||||
('{{description}}', 'B', 'Описание объекта КИИ (алиас)', 'is:description'),
|
||||
('{{category}}', 'B', 'Категория значимости объекта КИИ (алиас)', 'is:category'),
|
||||
('{{number}}', 'B', 'Номер (алиас)', 'doc_number'),
|
||||
('{{pd_count}}', 'B', 'Количество записей ПДн (алиас)', 'is:pd_count'),
|
||||
('{{pd_list}}', 'B', 'Список ПДн (алиас)', 'is:pd_list'),
|
||||
('{{pd_subjects}}', 'B', 'Субъекты ПДн (алиас)', 'is:pd_subjects'),
|
||||
('{{threat_type}}', 'B', 'Тип угроз (алиас)', 'is:threat_type'),
|
||||
('{{users}}', 'B', 'Пользователи ИС (алиас)', 'is:users'),
|
||||
('{{defence_level}}', 'B', 'Уровень защищённости (алиас)', 'is:defence_level'),
|
||||
('{{users}}', 'B', 'Пользователи ОКИИ (алиас)', 'is:users'),
|
||||
('{{full_name_genitive}}', 'A', 'Полное наименование (род.п., алиас)', 'decl:full_name:gent'),
|
||||
('{{document}}', 'A', 'Название бумажного документа (алиас)', 'company:paper_documents'),
|
||||
('{{storage}}', 'A', 'Место хранения (алиас)', 'company:paper_storage'),
|
||||
('{{responsible_fio_accs}}', 'A', 'ФИО ответственного (вин.п.)', 'fio_acl:responsible'),
|
||||
('{{commission1}}', 'A', 'Должность члена комиссии 1', 'commission_member:0:position'),
|
||||
('{{commission1_fio}}', 'A', 'ФИО члена комиссии 1', 'commission_member:0:fio'),
|
||||
@@ -221,13 +215,11 @@ def _add_is_indexed_vars(replacements: Dict[str, str], isys, idx: int):
|
||||
'is_address': getattr(isys, 'address', '') or '—',
|
||||
'is_description': isys.description or '—',
|
||||
'is_has_internet': 'да' if isys.is_internet else 'нет',
|
||||
'is_defence_level': str(isys.defence_level or '—'),
|
||||
'is_category': ', '.join(isys.personal_data_category) or isys.category or '—',
|
||||
'is_pd_list': ', '.join(isys.personal_data_list or []) or '—',
|
||||
'is_category': isys.category or '—',
|
||||
'is_users': ', '.join(isys.users_list or []) or '—',
|
||||
'is_pd_count': str(isys.pd_count or '—'),
|
||||
'is_purpose': isys.purpose or '—',
|
||||
'is_structure': getattr(isys, 'structure', '') or '—',
|
||||
'is_software': getattr(isys, 'software', '') or '—',
|
||||
}
|
||||
for f, v in fields.items():
|
||||
replacements[f'{{{{{f}_{idx}}}}}'] = str(v)
|
||||
@@ -295,68 +287,16 @@ def _build_loop_data(company: Company, is_list: List) -> Dict[str, List[Dict]]:
|
||||
'address': getattr(isys, 'address', '') or '—',
|
||||
'is_has_lan': 'Да' if isys.is_local_network else 'Нет',
|
||||
'has_internet': 'да' if isys.is_internet else 'нет',
|
||||
'defence_level': str(isys.defence_level or '—'),
|
||||
'threat_type': str(isys.threat_type or '3'),
|
||||
'category': ', '.join(isys.personal_data_category) or isys.category or '—',
|
||||
'pd_list': ', '.join(isys.personal_data_list or []) or '—',
|
||||
'pd_count': str(isys.pd_count or '—'),
|
||||
'category': isys.category or '—',
|
||||
'users': ', '.join(isys.users_list or []) or '—',
|
||||
'users_loop': users_loop,
|
||||
'defense_tools_list': ', '.join(isys.defense_tools_list or []) or '—',
|
||||
'processing_modes': isys.processing_modes or '—',
|
||||
'pd_subjects': '; '.join(isys.pd_subjects_list or []) or '—',
|
||||
'is_purpose': isys.purpose or '—',
|
||||
'structure': getattr(isys, 'structure', '') or '—',
|
||||
'software': getattr(isys, 'software', '') or '—',
|
||||
'pd_actions': '; '.join(isys.pd_actions_list or []) or '—',
|
||||
})
|
||||
loops['information_systems'] = items
|
||||
|
||||
# Бумажные документы
|
||||
def _storage_with_prep(storage: str) -> str:
|
||||
"""«сейф» → «в сейфе» (предлог + предложный падеж).
|
||||
Если уже начинается с предлога — оставить как есть."""
|
||||
if not storage:
|
||||
return ''
|
||||
s = storage.strip()
|
||||
low = s.lower()
|
||||
if low.startswith(('в ', 'на ', 'под ', 'за ', 'у ', 'при ', 'из ', 'со ', 'во ', 'над ', 'перед ')):
|
||||
return s
|
||||
words = s.split()
|
||||
out = []
|
||||
# Исключения предложного падежа: «в шкафу», «в углу» (не «в шкафе»)
|
||||
loct_exceptions = {'шкаф': 'шкафу', 'угол': 'углу', 'край': 'краю', 'рот': 'рту', 'мост': 'мосту'}
|
||||
for w in words:
|
||||
core = w.strip('.,;')
|
||||
if not core or core.isdigit() or core.startswith('№'):
|
||||
out.append(w)
|
||||
continue
|
||||
if core.lower() in loct_exceptions:
|
||||
out.append(loct_exceptions[core.lower()])
|
||||
continue
|
||||
try:
|
||||
p = dc.morph.parse(core)[0]
|
||||
inf = p.inflect({'loct'})
|
||||
if inf:
|
||||
out.append(inf.word)
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
out.append(w)
|
||||
return 'в ' + ' '.join(out)
|
||||
|
||||
paper_list = getattr(company, 'paper_documents_list', None) or []
|
||||
if paper_list:
|
||||
items = []
|
||||
for p in paper_list:
|
||||
storage = p.storage or ''
|
||||
items.append({
|
||||
'document': p.name or '',
|
||||
'storage': _storage_with_prep(storage), # «в сейфе»
|
||||
'storage_raw': storage, # «сейф» (как введено)
|
||||
})
|
||||
loops['paper_documents'] = items
|
||||
|
||||
# Сотрудники с доступом
|
||||
employees = getattr(company, 'employees_access', None) or []
|
||||
if employees:
|
||||
@@ -448,11 +388,6 @@ def _resolve(source: str, ctx: Dict) -> str:
|
||||
'name': 'name',
|
||||
'description': 'description',
|
||||
'category': 'category',
|
||||
'defence_level': 'defence_level',
|
||||
'threat_type': 'threat_type',
|
||||
'pd_list': lambda: ', '.join(first.personal_data_list or []),
|
||||
'pd_count': 'pd_count',
|
||||
'pd_subjects': lambda: '; '.join(first.pd_subjects_list or []),
|
||||
'users': lambda: ', '.join(first.users_list or []),
|
||||
}
|
||||
if field in field_map:
|
||||
@@ -501,21 +436,25 @@ def _get_fio(company: Company, who: str) -> str:
|
||||
|
||||
|
||||
def _get_security_text(ctx: Dict) -> str:
|
||||
"""Возвращает текст требований для уровня защищённости."""
|
||||
defence_level = ctx.get('extended_fields', {}).get('defence_level', '4')
|
||||
"""Возвращает текст требований к защите объекта КИИ по категории значимости."""
|
||||
is_list = ctx.get('is_list', [])
|
||||
if not defence_level and is_list:
|
||||
defence_level = str(getattr(is_list[0], 'defence_level', '4') or '4')
|
||||
dl = str(defence_level).strip()[0]
|
||||
category = ''
|
||||
if is_list:
|
||||
category = str(getattr(is_list[0], 'category', '') or '').strip()
|
||||
|
||||
TEXTS = {
|
||||
'1': """Для обеспечения 1-го уровня защищённости...""",
|
||||
'2': """Для обеспечения 2-го уровня защищённости...""",
|
||||
'3': """Для обеспечения 3-го уровня защищённости...""",
|
||||
'4': """Для обеспечения 4-го уровня защищённости персональных данных необходимо:
|
||||
— режим безопасности помещений;
|
||||
— сохранность носителей ПДн;
|
||||
— утверждение перечня лиц с доступом к ПДн;
|
||||
— использование сертифицированных СЗИ.""",
|
||||
'1': """Для значимого объекта КИИ 1-й категории значимости необходимо:
|
||||
— создание системы безопасности значимого объекта КИИ;
|
||||
— обеспечение защиты информации от компьютерных атак;
|
||||
— категорирование объектов КИИ и контроль его результатов.""",
|
||||
'2': """Для значимого объекта КИИ 2-й категории значимости необходимо:
|
||||
— создание системы безопасности значимого объекта КИИ;
|
||||
— обеспечение защиты информации от компьютерных атак.""",
|
||||
'3': """Для значимого объекта КИИ 3-й категории значимости необходимо:
|
||||
— обеспечение защиты информации от компьютерных атак.""",
|
||||
}
|
||||
return TEXTS.get(dl, TEXTS['4'])
|
||||
if category in TEXTS:
|
||||
return TEXTS[category]
|
||||
return """Для объекта КИИ, не являющегося значимым, необходимо:
|
||||
— принятие мер по защите информации, предусмотренных законодательством РФ о безопасности КИИ."""
|
||||
|
||||
|
||||
+32
-2
@@ -65,6 +65,17 @@ _ADDRESS_ABBR = {
|
||||
'СТ': 'ст.', 'Ш': 'ш.', 'Р-Н': 'р-н.', 'Б-Р': 'б-р.', 'МКР': 'мкр.',
|
||||
'НАБ': 'наб.', 'ТУП': 'туп.', 'ПР-Д': 'пр-д.', 'КРП': 'крп.',
|
||||
'Г.': 'г.', 'УЛ.': 'ул.', 'Д.': 'д.', 'Р-Н.': 'р-н.',
|
||||
'Г.О': 'г.о.', 'Г.О.': 'г.о.', 'ГО': 'г.о.', 'М.О': 'м.о.', 'М.О.': 'м.о.',
|
||||
}
|
||||
|
||||
# Слова, которые в адресах пишутся со строчной буквы (кроме случаев,
|
||||
# когда стоят первым словом после точки — «г. Электроугли» и т.п.)
|
||||
_ADDRESS_LOWERCASE = {
|
||||
'область', 'край', 'республика', 'округ', 'район', 'город', 'поселок',
|
||||
'посёлок', 'деревня', 'село', 'станица', 'хутор', 'аул', 'улица',
|
||||
'проспект', 'переулок', 'бульвар', 'шоссе', 'набережная', 'площадь',
|
||||
'проезд', 'тупик', 'аллея', 'линия', 'дом', 'строение', 'корпус',
|
||||
'квартира', 'офис', 'помещение', 'этаж', 'комната', 'участок',
|
||||
}
|
||||
|
||||
|
||||
@@ -105,7 +116,12 @@ def _normalize_text(text):
|
||||
|
||||
|
||||
def normalize_address(text):
|
||||
"""«Г Крымск» → «г. Крымск», «Р-Н» → «р-н.» и т.п."""
|
||||
"""«Г Крымск» → «г. Крымск», «Р-Н» → «р-н.», «МОСКОВСКАЯ ОБЛАСТЬ» → «Московская область».
|
||||
|
||||
Приводит адрес к нормальному регистру: каждое слово с заглавной буквы,
|
||||
адресные сокращения (г., ул., д., обл., г.о.) и административные термины
|
||||
(область, район, город, дом и т.п.) — со строчной.
|
||||
"""
|
||||
if not text:
|
||||
return ''
|
||||
parts = re.split(r'(\s+|[,\;])', text)
|
||||
@@ -115,7 +131,21 @@ def normalize_address(text):
|
||||
out.append(_ADDRESS_ABBR[p.strip().upper()])
|
||||
else:
|
||||
out.append(p)
|
||||
return ''.join(out)
|
||||
res = ''.join(out)
|
||||
# КАПС → нормальный регистр по словам
|
||||
res = _normalize_text(res)
|
||||
# Адресные термины — со строчной буквы (сохраняя пунктуацию)
|
||||
words = res.split(' ')
|
||||
fixed = []
|
||||
for w in words:
|
||||
m = re.match(r'^([^\w]*)([\w\-]+)([^\w]*)$', w, re.UNICODE)
|
||||
if m:
|
||||
pre, core, post = m.group(1), m.group(2), m.group(3)
|
||||
if core.lower() in _ADDRESS_LOWERCASE:
|
||||
fixed.append(pre + core.lower() + post)
|
||||
continue
|
||||
fixed.append(w)
|
||||
return ' '.join(fixed)
|
||||
|
||||
|
||||
def _fmt_date(value):
|
||||
|
||||
Reference in New Issue
Block a user