Разработка программного решения для сокращения времени заполнения электронных журналов

Дипломная работа по предмету «Программирование»
Информация о работе
  • Тема: Разработка программного решения для сокращения времени заполнения электронных журналов
  • Количество скачиваний: 4
  • Тип: Дипломная работа
  • Предмет: Программирование
  • Количество страниц: 57
  • Язык работы: Русский язык
  • Дата загрузки: 2021-10-08 08:21:48
  • Размер файла: 1746.51 кб
Помогла работа? Поделись ссылкой
Информация о документе

Документ предоставляется как есть, мы не несем ответственности, за правильность представленной в нём информации. Используя информацию для подготовки своей работы необходимо помнить, что текст работы может быть устаревшим, работа может не пройти проверку на заимствования.

Если Вы являетесь автором текста представленного на данной странице и не хотите чтобы он был размешён на нашем сайте напишите об этом перейдя по ссылке: «Правообладателям»

Можно ли скачать документ с работой

Да, скачать документ можно бесплатно, без регистрации перейдя по ссылке:

ОГЛАВЛЕНИЕ

ВВЕДЕНИЕ        2

1.        ПРЕДМЕТНОЕ ИССЛЕДОВАНИЕ ПРОЕКТНОЙ ДЕЯТЕЛЬНОСТИ        4

1.1        Опрос в электронном сервисе Google Формы        4

1.2        Проведение опроса среди преподавателей        5

1.3        Структура утвержденных рабочих программ        7

1.4        Структура CSV-файлов для разных АСУ        9

1.4.1        АСУ СГК        10

1.4.2        АСУ РСО        11

1.5        Выбор языка программирования        12

1.6        Выбор IDE для разработки        14

1.7        Выбор библиотек для программного обеспечения        16

Вывод по главе        18

2.        РАЗРАБОТКА ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ        19

2.1        Разработка логики        19

2.2        Разработка визуальной части        29

2.3        Тестирование        35

2.4        Компиляция в исполняемый файл        39

Вывод по главе        40

ЗАКЛЮЧЕНИЕ        41

СПИСОК ИСПОЛЬЗОВАННОЙ ЛИТЕРАТУРЫ        42

ПРИЛОЖЕНИЕ        44


ВВЕДЕНИЕ

В последнее время в средние общеобразовательные учреждения внедряются информационные технологии, как в сферу обучения, так и в сферу управления учреждением.

Сегодня каждое образовательное учреждение имеет несколько электронных журналов, внутренние журналы и регионального назначения, известны как автоматизированные системы управления ресурсами системы образования (далее - АСУ РСО).

С внедрением таких систем преподаватели вынуждены уходить от «бумажных» журналов и осваивать новые технологии, которые приводят к тому, что педагоги тратят огромное количество времени на заполнение тематического планирования (далее – рабочая программа) уроков и дублирование сразу в нескольких журналов.

В каждой электронной системе содержится функция «импорта» тематического планирования. Это бы позволило сотрудникам колледжа ускорить процесс заполнения тематического плана дисциплины, если бы не существовало несколько недостатков, такие как:

утвержденные рабочие программы дисциплин имеют собственный утверждённый шаблон и хранятся в Word-документе;

актуализация учебных планов групп приводит к тому, что количество часов по дисциплине либо сокращается, либо увеличивается;

разные системы имеют разные формат столбцов CSV файлов для импорта;

тематический план дисциплины может изменится, в виду устаревания информации;

Все это приводит к том, что преподаватели колледжа должны каждый раз разрабатывать тематическое план, утверждать и вновь заполнять в электронные журналы и АСУ РСО, на что уходит приличное количество времени.

Целью данного дипломного проекта является разработка программного решения, которое на основе одной таблички утвержденных рабочих программы анализировало структуру плана и обрабатывала данные и на выходе выдавало CSV-файлы, специально для разных систем, в которых предусмотрена функция «импорта».

Для осуществления данного проекта необходимо:

  • Провести опрос среди преподавателей, для оценки затраты времени на заполнение электронных журналов.
  • Выяснить утверждённый структуру таблиц утвержденных рабочих программ
  • Определить структуру готового шаблона CSV-файлов
  • Выбор языка программирования, IDE и библиотек
  • Разработать программу, осуществляющую парсинг таблиц
  • Разработать интерфейс программы для визуального удобства

Внедрение данного программного продукта позволит облегчить работу

преподавателей в колледже и сократит время заполнения электронных журналов.

  1. ПРЕДМЕТНОЕ ИССЛЕДОВАНИЕ ПРОЕКТНОЙ ДЕЯТЕЛЬНОСТИ
    1.   Опрос в электронном сервисе Google Формы


Чтобы понять, сколько педагоги затрачивают время на заполнение тематического плана и оценить возможность сократить время на внесение рабочей программы в электронный журнал, мной был проведен опрос на платформе «Google Формы».

Google формы — один из облачных сервисов Google, связанный с облаком и таблицами. Как и для использования любых других продуктов Google, для авторизации достаточно просто иметь почтовый ящик на Gmail.

Гугл-формы — простой, удобный и надёжный инструмент. Он интуитивно понятный, лёгкий в освоении, быстро внедряется и, что немаловажно, бесплатный.

Продукт позволяет:

  • Проводить опросы;
  • Регистрировать участников мероприятий;
  • Сегментировать клиентов;
  • Брифовать клиентов;
  • Получать обратную связь;
  • Собирать отзывы;
  • Проводить тестирования и викторины;
  • Собирать контакты для рассылок.

Для получения доступа к данному инструменту, необходимо иметь аккаунт Google. Для создание учетной записи необходимо перейти на сайт google.com, нажать «Регистрация» и придумать уникальное имя пользователя для почтового ящика с защищенным паролем и контрольными вопросами, для восстановления доступа к аккаунту, в случае утери.        

       С помощью старшего методиста по отделению и прошу выполнить массовую рассылку для проведения и участие моего опроса преподавателей колледжа.

       Получать ответы от преподавателей я смогу в режиме реального времени, все результаты опросов сохраняются в облаке сервиса Google Диск, что позволяет получать доступ в любой момент и где угодно.

       Полученные данные я могу просматривать отдельно по каждому респонденту и просматривать автоматически созданные диаграммы на основе результатов опроса, также сервис позволяет экспортировать данные в виде таблицы.


    1. Проведение опроса среди преподавателей


На протяжении месяца наши преподаватели могли ответить на вопросы: «Сколько занимает по времени заполнение тематического плана 1 предмета в АСУ РСО и АСУ СГК?» и «Хотели бы вы ускорить время на внесении рабочих программ?»

       Всего в опросе приняло участие 24 преподавателя. На первых вопрос (рис. 1), касающийся журнала АСУ СГК, восемь респондентов отметили, что в АСУ СГК заполняют только одну дисциплину 1.5 часа, другие восемь тратят на это 2 часа, пятеро педагогов заносят рабочую программу за 1 час, трое преподавателей оказались самыми быстрыми – им требуется всего 30 минут.

Рис. 1.

На второй вопрос: «Сколько занимает время на заполнение в АСУ РСО?» (рис.2)

Рис. 2.

       Одиннадцать преподавателей ответили, что заполняют в АСУ РСО один предмет за 1,5 часа. Возросло количество педагогов, которые заполняют тематический план около 2 часов. Двое заполняют быстрее всех и справляются за 1 час, один преподаватель оказался медленным из всех.

Большинство респондентов отметили, что заполнение в АСУ РСО тяжелее, чем в АСУ РСО, из-за чего на заполнение только одного предмета может уйти до 3 часов.

       Большинство преподавателей хотели бы сократить время на внесение данных в электронный журнал, об этом говорят ответы (рис. 3).

Рис 3.

       Усредненное время на одну дисциплину – 1.5-2 часа только на 1 журнал. У преподавателя может быть дисциплина до 250 часов или до 15 предметов за 1 учебный период.

       Можно сделать вывод, что проблема действительно имеет место быть и можно существенно исправить данную ситуацию.


    1.   Структура утвержденных рабочих программ


В ГАПОУ «СГК» рабочие программы дисциплин оформляются по утвержденному шаблону и отправляются председателям предметно-цикловой комиссии на проверку, затем после проверки преподаватели вносят уже утвержденную программу в электронные журналы и ведут пары строго по тематическому планированию.

Рассмотрим одну из рабочих программ профессионального модуля на примере: ПМ.05 Разработка WEB-приложений, дисциплины МДК.05.01 Прикладное программирование (таблица №1)


Таблица №1

Образец утвержденного тематического плана рабочей программы

Наименование разделов профессионального модуля (ПМ), междисциплинарных курсов (МДК) и тем

Содержание учебного материала, лабораторные работы и практические занятия, самостоятельная работа студентов, курсовая работа

Объем часов

Уровень освоения

Раздел 1.

Тема 1.1. Основы HTML

  1. Основные понятия Web-программирования

1



  1. Классификация Web-сайтов

2



Самостоятельная работа для студента.

Написать конспект по теме «Уровни языка HTML»

1



  1. Основы разметки: Теги, атрибуты, элементы

1


Тема 1.3. Графика

  1. Практическая работа №1 Оформить текст. Изменить параметры шрифта

2



  1. Практическая работа №2 Создать нумерованный, маркированный и смешанный списки и список определений

1



Таблица состоит из 4 столбцов: в первом столбце находится название дисциплины и профессионального модуля, так же раздел или введение, темы. Если присмотреться, то у каждого раздела и темы есть свой номер формата – 1 или 1.1.

Во втором столбце располагаются содержание учебного материала, практических занятий или самостоятельных работ. Можно заметить, что уроки и практические занятия имеют нумерацию, но самостоятельная работа студента не нумеруется.

В третьем столбце проставляется количество академических часов необходимых для изучение определенной темы, урока или практического занятия.

В четвертом столбце находится «Уровень освоение» материала, но так как вносить эти данные в электронные журналы нет необходимости, данный столбец и его содержимое можно проигнорировать.


    1.   Структура CSV-файлов для разных АСУ


Для загрузки тематических планов в АСУ РСО используются функция «Импорт» и CSV-файлы. У каждой системы свои требования к CSV-файлу. При загрузке данного файла в автоматизированные системы все данные автоматически заносятся в нужные столбцы и упрощаются процесс миграции данных.

Импорт и экспорт данных — это автоматический или полуавтоматический ввод и вывод наборов данных между различными программными приложениями. Он включает «перевод» из формата, используемого в одном приложении, в формат, используемый другим, когда такой перевод выполняется автоматически через машинные процессы, такие как транс кодирование, преобразование данных и другие. Истинный экспорт данных часто содержит данные в необработанных форматах, которые в противном случае не могут быть прочитаны конечными пользователями без пользовательского интерфейса, который был разработан для их отображения.

CSV (Comma-Separated Values) представляет собой файл текстового формата, который предназначен для отображения табличных данных. При этом колонки разделяются запятой и точкой с запятой.

Формат CSV используют, чтобы хранить таблицы в текстовых файлах. Данные очень часто упаковывают именно в таблицы, поэтому CSV-файлы очень популярны.

У CSV много достоинств перед тем же форматом Excel: текстовые файлы очень просты, открываются быстро, читаются на любом устройстве и в любой среде без дополнительных инструментов.

       Чаще всего для импорта и экспорта используют файлы форматом CSV.

      1. АСУ СГК


АСУ СГК – автоматизированная система управление Самарского государственного колледжа, расположенная по адресу сети интернет asu.samgk.ru, является журналом для преподавателей и инструментом для реализации образовательных программ, используемой учебной частью для составления расписания и учебных планов для групп.

Зайдем в раздел «Журнал» - Выберем год и дисциплину с семестром – и перейдем в раздел «Тематический план»

Рис. 4. Редактор тематического плана

       В открытом окне (рис. 4), есть возможность занесения тематического плана построчно в ручном режиме с помощью кнопки «Добавить (зеленый плюс», либо воспользоваться функцией «Импорт» возле надписи «CSV-файл с тем. Планом»

       Файл имеет следующую структуру (таблица №2):

Таблица №2

Структура CSV-файла для АСУ СГК

Тема

Самостоятельная работа

1.

Основные понятия Web-программирования


2.

Классификация Web-сайтов

Написать конспект по теме «Уровни языка HTML»

3

Практическая работа №1 Оформить текст. Изменить параметры шрифта


       

В первом столбце идет нумерация урока в прибавляющую сторону. Во втором столбце располагается тема урока или тема практического занятия, а самостоятельная работа размещается в третьем столбце. Допускается использование пустых ячеек. Кодировка файла UTF-8 без BOM.


      1. АСУ РСО


АСУ РСО - автоматизированные системы управления ресурсами системы образования, система регионального назначения. В колледжах используется модуль среднего профессионального образования, располагающегося по адресу spo.asurso.ru.

Данная система не является основной, но так как имеет статус регионального назначения, её заполнение является обязательной.

Проходим авторизацию и переходим в раздел «Обучение», далее в «Рабочие программы дисциплин», добавляем пустую рабочую программу

Рис. 5. Пустой календарно тематический план в АСУ РСО

Видим, что присутствует два возможных способа заполнения КТП: ручной (нажимая поочередно на «добавить занятие», вносим так каждый урок) или же воспользоваться функцией «импорта».

Нажимаем «Импорт», открывается таблица с требуемой структурой файла (Рис.6)

Рис.6.

Таблица №3

Структура CSV-файла для АСУ РСО

Раздел

Текст длиной до 256 символов

Тема

Текст длиной до 256 символов

Описание темы

Текст длиной до 5000 символов

Требования к студентам

Текст длиной до 5000 символов

Занятие

Текст длиной до 256 символов

Тип занятия

Лекция, Практ. Работа/занятие

Длительность

Целое число в диапазоне от 1 до 999

Материалы

Текст длиной до 256 символов

Дом. задание

Текст длиной до 5000 символов


Почти все поля обязательны для заполнения. Кодировка UTF-8 без BOM. Разделитель — точка с запятой.


    1.   Выбор языка программирования


Изучив структуру файлов, мне остаётся разработать программное решение, которое бы анализировало все данные из таблички рабочей программы тематического плана и на выходе должно выдавать файлы для импорта, соответствующие структуре каждой системы.

Для разработки программного обеспечения мной выбран язык программирования – Python. Выбор данного языка обосновывается тем, что:

- по данным портала habr, посвященному сайту программированию, есть указание на индекс TIOBE (рис. 7) (один из самых известных рейтингов языков программирования);

- Python является популярным языком на момент 2020 года и является быстрорастущим по сравнению с 2019 годом.

Рис. 7. Рейтинг TIOBE

Язык прост в изучении, благодаря простоте синтаксиса Python отлично подходит начиняющим программистам. Код легко читается, понятен другим разработчикам и человеку, обладающему базовыми знаниями английского языка. Кроссплатформенность языка позволяет запускать разработанное приложение абсолютно на разных платформах (Windows, Linux и другие). Наличие мощных библиотек для разработчиков позволяет расширить функционал своего программного решения. Открытый исходный код позволяет свободно и бесплатно использовать язык. Сообщество поддержки – огромное количество форумов для пользователей и разработчиков.

Язык программирования Python практически ничем не ограничен, поэтому также может использовать в крупных проектах. К примеру, данный язык интенсивно применяется популярными IT-гигантами, такими как Google и Yandex.

Таким образом, Python отлично подходит под наши задачи.


    1.   Выбор IDE для разработки


Для языка программирование необходимо выбрать IDE.

IDE (Integrated Development Environment) — интегрированная среда разработки программного обеспечения. Простыми словами, программа, которая делает работу программистов более удобное и продуктивной.

Требования к среде разработки программного обеспечения:

  1. Подсветка цветами синтаксиса языка, методов и классов, переменных;
  2. Функция подсказок параметров и завершение написания кода;
  3. Отладка программного решения;
  4. Интеграция с системой контроля версии (GIT);
  5. Диспетчер пакетов (или других репозиториев) с библиотеками.

IDE разделяется на платные, условно-бесплатные (чаще всего для студентов или частного использования) и полностью бесплатные категории.

По данным опроса программистов ведущей компанией по разработке интегрированных сред для разработки программного обеспечения, проведенном в 2019 году, был задан вопрос: «Каким IDE / редактором вы используете больше всего?» (рис. 7) следует, что самые популярные IDE-шки на данным момент Visual Studio (занявшее 1 место с результатом 32%) и Visual Studio Code (занявшее 2 место с результатом 14%).

Рис.7. Рейтинг IDE

       Так как Visual Studio достаточно мощный инструмент, а программировать будем на легком языке – Python, нам вполне будет достаточно Visual Studio Code.

       Рассмотрим достоинства Visual Studio Code перед классической Visual Studio:

  • Распространяется бесплатно;
  • Открытый исходный код редактора;
  • Магазин плагинов «из коробки», можно установить поддержкой синтаксиса абсолютного любого языка;
  • Встроенный мощный механизм автозаполнения – IntelliSense;
  • Легкий и быстрый.

Выбор, очевиден – установим, в качестве основной IDE – VS Code.


    1.   Выбор библиотек для программного обеспечения


Современные программы слишком сложные, чтобы создавать их с нуля, поэтому разработчики используют библиотеки, с ними код пишется гораздо быстрее.

Карл Саган сказал: «Если вы хотите испечь яблочный пирог c нуля, вам сначала надо создать Вселенную». У программистов для этого есть библиотеки.

Библиотека — это набор готовых функций, классов и объектов для решения каких-то задач.

Мы не пытаемся изобретать велосипед каждый раз, когда нужно поехать в булочную, — просто берём готовый и едем на нём. То же самое и в коде. Программист должен думать о реализации бизнес-логики, а не о том, как работает оборудование или по какому протоколу, передаются данные.

Каждая библиотека предоставляет возможности для решения каких-то конкретных задач:

  • выполнения математических операций;
  • работы с графикой;
  • работы с файлами;
  • работы с сетью;
  • шифрования и так далее.

Они могут быть встроены в язык или добавляться отдельно.

Без библиотек не обойтись, очень важно быстро и регулярно выпускать продукт, чтобы он был конкурентоспособен. Кроме того, библиотеки гораздо безопаснее написанного с нуля кода, потому что над ними трудится много людей, и ещё больше тестирует при использовании.

Pandas — это библиотека Python с открытым исходным кодом, предоставляющая высокопроизводительный инструмент для обработки и анализа данных с использованием его мощных структур данных. Название Pandas происходит от слова Panel Data — эконометрика из многомерных данных.

До Pandas Python в основном использовался для сбора и подготовки данных. Это имело очень небольшой вклад в анализ данных. Панды решили эту проблему. Используя Pandas, мы можем выполнить пять типичных шагов по обработке и анализу данных, независимо от происхождения данных — загрузить, подготовить, манипулировать, моделировать и анализировать.

Python с Pandas используется в широком спектре областей, включая академические и коммерческие области, включая финансы, экономику, статистику, аналитику и т.д.

       Ключевые особенности панд:

  • Быстрый и эффективный объект DataFrame с индивидуальной индексацией по умолчанию;
  • Инструменты для загрузки данных в объекты данных в памяти из разных форматов файлов;
  • Выравнивание данных и интегрированная обработка отсутствующих данных;
  • Изменение формы и поворот наборов дат;
  • Метка нарезки, индексация и подмножество больших наборов данных;
  • Столбцы из структуры данных могут быть удалены или вставлены;
  • Группировка по данным для агрегации и преобразований;
  • Высокая производительность слияния и объединения данных;
  • Функциональность временных рядов.

С помощью библиотеки Pandas мы можем превратить сырую таблицу Excel, содержащую категориальные признаки, в датафрейм с числовыми данными, понятными для модели машинного обучения.

PyQt5 является одним из наиболее часто используемых модулей для создания GUI приложений в Python, это связанно с его простотой, о которой вы узнаете далее.

Еще одна замечательная особенность, которая вдохновляет разработчиков пользоваться PyQt5 – это PyQt5 Designer, благодаря которому можно создавать сложные GUI приложения достаточно быстро, нужно только переносить свои виджеты для создания собственной формы


Вывод по главе


Таким образом, можно заключить, что по данным опроса преподаватели в ГАПОУ СО «СГК» имеют проблемы с продолжительностью внесения тематического плана рабочей программы дисциплин. Это может сказываться на качестве преподавания и грамотности распределения рабочего времени. Время заполнения электронных систем можно сократить, если автоматизировать процесс путем обработки данных из рабочих программ в преобразованные файлы для импорта в систему.

Для реализации проекта будут использованы Visual Studio Code как основная IDE, язык программирования выбран был Python и библиотека Pandas для обработки данных.

  1. РАЗРАБОТКА ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ
    1.   Разработка логики


Приступим к разработке внутренней части программы. Создадим папку и внутри создаем файл программ main.py формата py. (рис.8)

Рис. 8.

       Воспользуемся командой PIP в терминале, чтобы загрузить необходимые библиотеки для дальнейшей разработки программы. Загрузим и установим Pandas, EasyGUI, PyInquirer, xlrd, P2pyexe (рис. 9)

Рис. 9.


       Выполним импорт модулей и библиотеки в проект с помощью from и import (рис. 10)

Рис. 10.

       Функция Main будет стартовой точкой в приложении, где мы будем запрашивать у пользователя таблицу с тематическим планом, отдавать другой функции на обработку и спрашивать о том, куда сохранить итоговые файлы для импорта (рис.11)

Рис. 11.

       Создадим модель урока в папке Models, в нем будут храниться поля № урока, темы и количество часов (рис. 12)


Рис. 12.

       Создадим класс Parser и разработаем логику обработки данных. В функции parse будет поочередно выполнять другие функции, связанные с обработкой и возвращать массив готовых данных. (рис 13)

Рис. 13.

       При импорте из Excel библиотека Pandas переносит данные первую строчку в название столбцов. Функция rename columns будет сохранять столбцы, переименовывать и выдавать потерянные данные в первую строчку (рис. 14)

Рис. 14.

       Библиотека Pandas объединенные строчки Excel считает их данные NaN. Функция Clear NAN будет чистить объединённые строчки с NAN и заменять на пустоту. (рис. 15)

Рис. 15.

       При переносе данных из Word в Excel добавлялись символы, которые мешали обрабатывать данные и приводили к возникновениям сбоев в работе программы. Соответственно мы от них избавляемся. (рис.16)

Рис. 16.

       При анализе тематических планов было замечено, что преподаватели отталкиваются от установленного шаблона. Уроки были разделены на две ячейки - номер и тема урока. В данной функции мы объединяем строчки и превращаем в единое целое название урока (рис.17)

Рис. 17.

       Также было обнаружено разделение ячеек и с «Самостоятельными работами». Чистим строчку от лишние пробелов, проверяем, что строка с числом маленькая и собираем все в одну ячейку, удаляем второй столбец. (рис. 18)

Рис. 18.

       В некоторых рабочих программах отсутствует в начале «Раздел» и «Введение», это приводит к тому, что уроки до следующего раздела не обрабатываются. Подставляем фантомные в первый столбец «Введение». Чтобы парсер не пропускал уроки и засчитывал их все. (рис 19)

Рис. 19.

       Функции, отвечающие за распознавание урока, глав, тем, будем определять тип данных в массив путем простыми регулярными выражениями. Так, например, раздел будет определяться по наличию номера и слова «Раздел» или «Введение» в первом столбце. (рис. 20)


Рис. 20.

       Так же будет определятся «Тема». Если перед ней будет стоять ХХ Тема, где ХХ – это номер или цифра. (рис. 21)

Рис. 21.

       Сам урок будет определятся по наличию цифры и точки (или без неё) во втором столбце массива. (рис 22)

       Рис. 22.

       А самостоятельная работа в отличии урока будет распознаваться по слову «Самостоятельная». (рис 23)

Рис. 23.

       Вернемся к Main.py и реализуем выбор порядка расположения данных в ячейках для тестирования. (рис. 24)

Рис. 24.

       Проверку введенного числа будет осуществлять ColumnsValidator и NumberValidator, который не допустит аварийного завершения работы программы, если пользователь введен не число (обработка исключений) (рис. 25, 26).

Рис. 25.

Рис. 26.

       

Дадим выбор пользователю выбирать типы строчек для самостоятельных работы (одиночные или двойные) (рис.27)

Рис. 27.

       Будем спрашивать у пользователя в какой электронный журнал он хочет загрузить тематический план, и в зависимости от его выбора, будем выполнять разные функции и разную структуру CSV файла. (рис. 28)

Рис. 28.

       Функция выполняющий экспорт в АСУ РСО будет выполнять создание CSV под утвержденный шаблон файла. (рис. 27)

Рис. 27.

       Реализация последней функции для генерации CSV файла, но уже под систему АСУ СГК (рис.28)

Рис. 28.

       И функция, которая будет возвращаться к началу, по кругу после завершения процесса парсинга, чтобы открывать следующий файл.  (рис.29)

Рис. 29.



    1.   Разработка визуальной части


Я разработал «консольное» программное обеспечение и проделал промежуточную отладку. Большинство пользователей привыкли видеть программное обеспечение в «графическом» виде. Необходимо внедрить привычный вид для программного обеспечения.

Для интерфейса я буду использовать библиотеку QtPy5 и программное решение Qt Designer для создание оконных интерфейсов.

Переходим на официальный сайт и загружаем утилиту.  (рис. 30)

Рис. 30.

Устанавливаем дизайнерскую утилиту и переходим обратно в проект, загружаем непосредственно библиотеку QtPy5.

Выполняю команду pip для установки (рис. 31):

Рис. 31.

Открываем QT Designer и создаем новый макет (рис. 32):

Рис. 32.

Открывается редактор. Слева располагается панель «виджетов» в середине экрана «макет окна», которое будет выполняться в будущем в приложении, справа располагается панель свойств, где можно редактировать свойства объектов или менять их геометрию (рис.33).

 

Рис. 33.

Накидаем первые элементы на форму. Это будет «GroupBox» и внутри него разместим «RadioButtons» и один «Textbox». Чтобы пользователь программы мог выбирать, какой у него тип столбцов используется, и Textbox будет использоваться в случае, если используется нестандартный тип столбцов. (рис.34)

Рис. 34.

       Теперь добавить выбор варианта «тип занятий». В нем будет использоваться элемент «Combobox». Это выпадающий список из нескольких вариантов, добавим в качестве пояснения элемент Textbox (рис.35)

Рис. 35.

       Также добавить аналогично и для «типа самостоятельных работ» (рис.36).

Рис. 36.

       Вносим последний Combobox для выбора типа CSV файла для АСУ систем. (рис 37)

Рис. 37.

       Реализуем кнопку «парсинга», при нажатии которой будет выполнятся вся логика программы, этот объект называется Button (рис. 38).

Рис. 38.

       Добавим заголовок приложения, а для эстетического вида выравниваем элементы (рис 39).

Рис. 39.

       Добавляем справа небольшую мини-инструкцию для пользователя, где будет описано то, как работать, а также за что отвечает каждый элемент настроек. (рис. 40)

Рис. 40.

       Далее мы сохраняем макет интерфейса через Save и рядом c папкой проекта файл в формате ui. (рис. 41)

Рис. 41.

       И снова возвращаемся в проект, выполняем команды преобразования UI в PY (рис. 42)

Рис. 42.

       Интерфейс программного обеспечения разработан

    1.   Тестирование


Проведем тестирование нашего программного обеспечения, в том числе интерфейса. Запускаем нашу программу py main.py (рис. 43)

Рис. 43.

       Открывается окно только что разработанного интерфейса. (рис. 44)

Рис. 44.

       Копируем тематический план в таблицу Excel, сохраняем её как plan.xlsx. (рис. 45)

       

Рис. 45.

       Выставляем настройки и загружаем файл в нашу программу. Выбираем экспорт сразу в два журнала!

       Экспортируем и выбираем папку. (рис. 46)

Рис. 46.

       Сформировались таблицы для загрузки через функцию «Импорт» в АСУ системы (рис.47)

Рис. 47.

       Пробуем загрузить тематический план с помощью полученных CSV файлов.

       Переходим в АСУ РСО и проверяем. (рис. 48)

       

Рис. 48.

       Файл загружен без ошибок. Файл сформировался корректно и валидно. (рис. 49)

       

Рис. 49.

Выбираем файл и нажимаем «Загрузить» (рис. 50)

       

Рис. 50.

       Тематический план успешно загружен без ошибок!


    1.   Компиляция в исполняемый файл


По умолчанию программа запускается с помощью предустановленных библиотек Python и через командную строку «py main.py».

Так как основная будущая аудитория программы не умеет обращаться с командной строкой и устанавливать Python библиотеки, я интегрирую все библиотеки и сам Python в EXE-файл (исполняемый файл).

Установим PyInstaller с помощью PIP (рис.51)

Рис. 51

       Затем выполним конвертацию с помощью библиотеки PyInstaller. Прописываем pyinstaller main.py (рис.52)

Рис. 52.

       Происходит сборка библиотеки и py-кода. По окончании компиляции открываем папку dist и видим готовый проект. (рис.53)

Рис. 53.


Вывод по главе

Таким образом, была разработана логика программы, которая анализирует данные в таблицах и конвертирует данные в различные файлы.

Для пользователя был разработан графический интерфейс и мини инструкция в самом окне. Весь код был собран в исполняемый файл, чтобы пользователь программы не задумывался об установке Python и его библиотек.

ЗАКЛЮЧЕНИЕ

Как уже было сказано выше, в сферу образования начали внедряться электронные и информационные технологии, одни моменты упрощаются, а другие усложняются.

В ходе исследования было выяснено, что преподаватели тратят значительную часть времени на заполнении тематического плана рабочей программы дисциплин и в АСУ РСО и АСУ СГК. Затрата такого огромного количество времени негативно сказывается на организацию учебного процесса.

Практическая часть дипломной состоит в разработке программного обеспечения, которая обработала бы утвержденную таблицу и перевела данные тематического плана в определенную структуру CSV-файла для загрузки в разные системы.

Мной была разработана логика и графический интерфейс программы, было проведено тестирование функциональной части.

Уже на этапе тестирования заметна высокая эффективность в заполнении тематического плана в автоматизированные системы. Время заполнения тематического плана только на 1 предмет составила 3 минуты (в это время вошёл перенос пользователем программы таблицы плана из Word в Excel), а непосредственно сама обработка таблицы программой проходит моментально. Преподавателю останется только в разделе «Импорт» выбрать CSV-файл для загрузки.

В настоящее время данная программа используется в узком кругу преподавателей. Планируется внедрение данной программы среди сообщества преподавателей ГАПОУ СО «СГК».

Можно прийти к выводу, что поставленные задачи были решены в полном объеме.

СПИСОК ИСПОЛЬЗОВАННОЙ ЛИТЕРАТУРЫ

  1. Python 3. Самое необходимое / Н. А. Прохоренок, В. А. Дронов. — 2-е изд., перераб. и доп. — СПб.: БХВ-Петербург, 2019 — 608 с.
  2. Как устроен Python. Гид для разработчиков, программистов и интересующихся. — СПб.: Питер, 2019. — 272 с
  3. Изучаем Python, том 1, 5-е изд.: Пер. с англ. — СПб.: ООО “Диалектика”, 2019. — 832 с
  4. Начинаем программировать на Python. — 4-е изд.: Пер. с англ. — СПб.: БХВ-Петербург, 2019 — 768 с.
  5. Изучаем Python: программирование игр, визуализация данных, веб-приложения. 3-е изд. — СПб.: Питер, 2020. — 512 с.:
  6. Легкий способ выучить Python 3 / Зед Шоу; [пер. с англ. М. А. Райтмана]. — Москва: Эксмо, 2019. — 368 с.
  7. Python для чайников, 2- изд.: Пер. с англ. — СПб.: БХВ-Петербург, 2019 — 768 с.
  8. Devpractice Team. Pandas. Работа с данными. 2-е изд. - devpractice.ru. 2020. - 170 с.
  9. Excel 2019. Библия пользователя: Пер. с англ. — СПб: ООО "Диалектика", 2019. — 1136 с
  10. Python для сетевых инженеров [Электронный ресурс]. – Режим доступа: https://pyneng.readthedocs.io/ru/latest/book/17_serialization/csv.html. – Дата доступа: 04.05.2021.
  11. Программируем на Python [Электронный ресурс]. – Режим доступа: https://pythonim.ru/osnovy/rabota-s-csv-faylami-python. – Дата доступа: 15.04.2021.
  12. Google Справка - Редакторы документов (Как использовать Google Формы) [Электронный ресурс]. – Режим доступа: https://support.google.com/docs/answer/6281888?co=GENIE.Platform. – Дата доступа: 18.04.2021.
  13. Google Формы [Электронный ресурс]. – Режим доступа: https://www.google.ru/forms/about/. – Дата доступа: 01.03.2020.
  14. Python 3 для начинающих (PyQT5: первые программы) [Электронный ресурс]. – Режим доступа: https://pythonworld.ru/gui/pyqt5-firstprograms.html. – Дата доступа: 09.04.2021.
  15. Qt Designer [Электронный ресурс]. – Режим доступа: https://build-system.fman.io/qt-designer-download. – Дата доступа: 15.04.2021.

ПРИЛОЖЕНИЕ

Листинг Main.py


import sys

import traceback

import csv

import easygui

import itertools

from typing import List, Tuple

import pyautogui


from PyInquirer import prompt

from pandas import read_excel

from xlrd import XLRDError


from models.ColumnsValidator import ColumnsValidator

from models.NumberValidator import NumberValidator

from models.parser import parse

from models.TopicHour import TopicHour


from PyQt5 import QtCore, QtGui, QtWidgets

from gui import Ui_MainWindow, QtWidgets


dev_mode = False if len(sys.argv) == 1 else bool(sys.argv[1])


class Start(QtWidgets.QMainWindow, Ui_MainWindow):

    def __init__(self):

        super(Start, self).__init__()

        self.ui = Ui_MainWindow()

        self.ui.setupUi(self)


        self.ui.radioButton.toggled.connect(self.type_data)

        self.ui.radioButton_2.toggled.connect(self.type_data)

        self.ui.radioButton_3.toggled.connect(self.type_data)


        self.ui.pushButton.clicked.connect(self.ok)


        self.ui.comboBox.activated.connect(self.type_lesson)


        self.ui.radioButton.setChecked(True)


    def type_data(self):  # columns_settings

        radioBtn = self.sender()


        if radioBtn.isChecked():


            if radioBtn.objectName() == 'radioButton':

                answr = '0,1,2'

                answr = list(map(int, answr.split(',')))

                #print(answr)

                return answr


            elif radioBtn.objectName() == 'radioButton_2':

                answr = '0,1,2,3'

                answr = list(map(int, answr.split(',')))

                #print(answr)

                return answr


            elif radioBtn.objectName() == 'radioButton_3':

                answr = self.ui.lineEdit.text()

                answr = list(map(int, answr.split(',')))

                #print(answr)

                return answr


    def type_lesson(self):

        a = self.ui.comboBox.currentIndex()

        #print("a: ", a)

        return a


    def type_work(self):

        b = self.ui.comboBox_2.currentIndex()

        #print("b: ", b)

        return b


    def total(self):

        ps = self.type_lesson()

        inds = self.type_work()


        aboba = []

        aboba.append(ps)

        aboba.append(inds)

        return tuple(aboba)


    def format_export(self):

        c = self.ui.comboBox_3.currentIndex()

        print("c: ", c)

        return c


    def ok(self):

        self.main()


    def main(self):

        while True:

            file_path = easygui.fileopenbox(title="Выберете файл Еxcel", filetypes=[["*.xlsx""*.xls""Excel file"]])

            if file_path is not None:

                print(">> Путь до файла: " + file_path + '\n')

                try:

                    columns_setting = self.type_data()

                    book = read_excel(file_path, usecols=columns_setting)

                except XLRDError:

                    print(">> [!] Перепроверь файл, кажется это не таблица")

                    return

                practical_setting, independent_setting = self.total()

                try:

                    syllabus = parse(book, practical_setting, independent_setting)

                except:

                    print(traceback.format_exc())

                    input()

                    return

                print(">> [OK] Таблица обработана")

                save_path = easygui.diropenbox(title="Выберите место, куда сохранить CSV-файлы для импорта")

                if save_path is None:

                    print(">> [!] Не выбрана папка куда сохранять!")

                    return

                if '/' in file_path:

                    split = '/'

                else:

                    split = '\\'


                filename = file_path.split(split)[-1].split('.')[0]

                self.export_menu(save_path + split + filename, syllabus)

                break

            else:

                print(">> [!] Такого файла нет")

                break



    def export_menu(self, path: str, syllabus: List[TopicHour]):

       #choices = ['SPO', 'Asu SGK', 'SPO + Asu SGK']

       #question = [{'type': 'list', 'name': 'export_type', 'choices': choices, 'message': 'Выберите формат CSV:'}]

        index = self.format_export()

        if index == 0:

            self.export_sgo(path, syllabus)

        elif index == 1:

            self.export_asu(path, syllabus)

        elif index == 2:

            self.export_sgo(path, syllabus)

            self.export_asu(path, syllabus)


    def export_sgo(self, path: str, syllabus: List[TopicHour]):

        path += '_sgo.csv'

        with open(path, mode='w', encoding='utf-8-sig'as sgo_csv_file:

            writer = csv.writer(sgo_csv_file, delimiter=';', quotechar='"', lineterminator='\n', quoting=csv.QUOTE_MINIMAL)

            current_selection = ''

            current_topic = ''

            for item in syllabus:

                selection = ''

                place_dash = False

                if current_selection != item.selection:

                    current_selection = item.selection

                    selection = item.selection

                    place_dash = True


                topic = ''

                if current_topic != item.topic:

                    current_topic = item.topic

                    topic = item.topic

                    place_dash = True


                description, demand = ''''

                if place_dash:

                    description, demand = '-''-'


                homework = item.homework if item.homework != '' else '-'

                writer.writerow([selection, topic, description, demand, item.content, item.hourType.value,

                                 item.countHours, '-', homework])


    def export_asu(self, path: str, syllabus: List[TopicHour]):

        path += '_asu.csv'

        with open(path, mode='w', encoding='utf-8-sig'as asu_csv_file:

            writer = csv.writer(asu_csv_file, delimiter=';', quotechar='"', lineterminator='\n', quoting=csv.QUOTE_MINIMAL)

            row_index = 1

            for item in syllabus:

                for _ in itertools.repeat(None, item.countHours):

                    writer.writerow([row_index, item.content, item.homework])

                    row_index += 1


app = QtWidgets.QApplication([])

z = Start()

z.show()

sys.exit(app.exec())


Листинг Gui.py

# -*- coding: utf-8 -*-


# Form implementation generated from reading ui file 'gui.ui'

#

# Created by: PyQt5 UI code generator 5.15.4

#

# WARNING: Any manual changes made to this file will be lost when pyuic5 is

# run again.  Do not edit this file unless you know what you are doing.


from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):

    def setupUi(self, MainWindow):

        MainWindow.setObjectName("MainWindow")

        MainWindow.resize(923500)

        MainWindow.setMinimumSize(QtCore.QSize(923500))

        MainWindow.setMaximumSize(QtCore.QSize(923500))

        self.centralwidget = QtWidgets.QWidget(MainWindow)

        self.centralwidget.setObjectName("centralwidget")

        self.pushButton = QtWidgets.QPushButton(self.centralwidget)

        self.pushButton.setGeometry(QtCore.QRect(5037026131))

        self.pushButton.setObjectName("pushButton")

        self.groupBox = QtWidgets.QGroupBox(self.centralwidget)

        self.groupBox.setGeometry(QtCore.QRect(40100281111))

        self.groupBox.setObjectName("groupBox")

        self.radioButton = QtWidgets.QRadioButton(self.groupBox)

        self.radioButton.setEnabled(True)

        self.radioButton.setGeometry(QtCore.QRect(102018131))

        self.radioButton.setTabletTracking(False)

        self.radioButton.setAcceptDrops(False)

        self.radioButton.setChecked(True)

        self.radioButton.setObjectName("radioButton")

        self.radioButton_2 = QtWidgets.QRadioButton(self.groupBox)

        self.radioButton_2.setGeometry(QtCore.QRect(10508221))

        self.radioButton_2.setObjectName("radioButton_2")

        self.lineEdit = QtWidgets.QLineEdit(self.groupBox)

        self.lineEdit.setGeometry(QtCore.QRect(308014120))

        self.lineEdit.setObjectName("lineEdit")

        self.radioButton_3 = QtWidgets.QRadioButton(self.groupBox)

        self.radioButton_3.setGeometry(QtCore.QRect(10801617))

        self.radioButton_3.setText("")

        self.radioButton_3.setObjectName("radioButton_3")

        self.label_4 = QtWidgets.QLabel(self.centralwidget)

        self.label_4.setGeometry(QtCore.QRect(405030031))

        self.label_4.setMinimumSize(QtCore.QSize(3000))

        self.label_4.setMaximumSize(QtCore.QSize(30016777215))

        font = QtGui.QFont()

        font.setPointSize(19)

        font.setBold(False)

        font.setWeight(50)

        self.label_4.setFont(font)

        self.label_4.setLayoutDirection(QtCore.Qt.LeftToRight)

        self.label_4.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)

        self.label_4.setObjectName("label_4")

        self.groupBox_2 = QtWidgets.QGroupBox(self.centralwidget)

        self.groupBox_2.setGeometry(QtCore.QRect(4022028151))

        self.groupBox_2.setObjectName("groupBox_2")

        self.comboBox = QtWidgets.QComboBox(self.groupBox_2)

        self.comboBox.setGeometry(QtCore.QRect(102026122))

        self.comboBox.setObjectName("comboBox")

        self.comboBox.addItem("")

        self.comboBox.addItem("")

        self.groupBox_3 = QtWidgets.QGroupBox(self.centralwidget)

        self.groupBox_3.setGeometry(QtCore.QRect(4027028151))

        self.groupBox_3.setObjectName("groupBox_3")

        self.comboBox_2 = QtWidgets.QComboBox(self.groupBox_3)

        self.comboBox_2.setGeometry(QtCore.QRect(102026122))

        self.comboBox_2.setObjectName("comboBox_2")

        self.comboBox_2.addItem("")

        self.comboBox_2.addItem("")

        self.groupBox_4 = QtWidgets.QGroupBox(self.centralwidget)

        self.groupBox_4.setGeometry(QtCore.QRect(4032028151))

        self.groupBox_4.setObjectName("groupBox_4")

        self.comboBox_3 = QtWidgets.QComboBox(self.groupBox_4)

        self.comboBox_3.setGeometry(QtCore.QRect(102026122))

        self.comboBox_3.setObjectName("comboBox_3")

        self.comboBox_3.addItem("")

        self.comboBox_3.addItem("")

        self.comboBox_3.addItem("")

        self.label_5 = QtWidgets.QLabel(self.centralwidget)

        self.label_5.setGeometry(QtCore.QRect(330110571131))

        self.label_5.setMinimumSize(QtCore.QSize(3000))

        font = QtGui.QFont()

        font.setPointSize(19)

        font.setBold(False)

        font.setWeight(50)

        self.label_5.setFont(font)

        self.label_5.setLayoutDirection(QtCore.Qt.LeftToRight)

        self.label_5.setText("")

        self.label_5.setPixmap(QtGui.QPixmap("img/1.PNG"))

        self.label_5.setScaledContents(False)

        self.label_5.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)

        self.label_5.setObjectName("label_5")

        self.label_6 = QtWidgets.QLabel(self.centralwidget)

        self.label_6.setGeometry(QtCore.QRect(33025057191))

        self.label_6.setMinimumSize(QtCore.QSize(3000))

        font = QtGui.QFont()

        font.setPointSize(19)

        font.setBold(False)

        font.setWeight(50)

        self.label_6.setFont(font)

        self.label_6.setLayoutDirection(QtCore.Qt.LeftToRight)

        self.label_6.setText("")

        self.label_6.setPixmap(QtGui.QPixmap("img/2.PNG"))

        self.label_6.setScaledContents(False)

        self.label_6.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)

        self.label_6.setObjectName("label_6")

        self.label = QtWidgets.QLabel(self.centralwidget)

        self.label.setGeometry(QtCore.QRect(3309047116))

        self.label.setObjectName("label")

        self.label_2 = QtWidgets.QLabel(self.centralwidget)

        self.label_2.setGeometry(QtCore.QRect(33035047116))

        self.label_2.setObjectName("label_2")

        self.label_3 = QtWidgets.QLabel(self.centralwidget)

        self.label_3.setGeometry(QtCore.QRect(78044011120))

        self.label_3.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)

        self.label_3.setObjectName("label_3")

        MainWindow.setCentralWidget(self.centralwidget)

        self.menubar = QtWidgets.QMenuBar(MainWindow)

        self.menubar.setGeometry(QtCore.QRect(0092321))

        self.menubar.setObjectName("menubar")

        MainWindow.setMenuBar(self.menubar)

        self.statusbar = QtWidgets.QStatusBar(MainWindow)

        self.statusbar.setObjectName("statusbar")

        MainWindow.setStatusBar(self.statusbar)


        self.retranslateUi(MainWindow)

        QtCore.QMetaObject.connectSlotsByName(MainWindow)


    def retranslateUi(self, MainWindow):

        _translate = QtCore.QCoreApplication.translate

        MainWindow.setWindowTitle(_translate("MainWindow""SyllabusParcer"))

        self.pushButton.setText(_translate("MainWindow""Начать"))

        self.groupBox.setTitle(_translate("MainWindow""Укажите тип столбцов (Excel) "))

        self.radioButton.setText(_translate("MainWindow""0,1,2 (по умолчанию)"))

        self.radioButton_2.setText(_translate("MainWindow""0,1,2,3"))

        self.label_4.setText(_translate("MainWindow""Парсер учебных планов"))

        self.groupBox_2.setTitle(_translate("MainWindow""Укажите тип уроков"))

        self.comboBox.setItemText(0, _translate("MainWindow""Весь текст слитно"))

        self.comboBox.setItemText(1, _translate("MainWindow""Заголовок отдельно - текст отдельно"))

        self.groupBox_3.setTitle(_translate("MainWindow""Укажите тип самостоятельных работ"))

        self.comboBox_2.setItemText(0, _translate("MainWindow""Весь текст слитно"))

        self.comboBox_2.setItemText(1, _translate("MainWindow""Заголовок отдельно - текст отдельно"))

        self.groupBox_4.setTitle(_translate("MainWindow""Укажите тип CSV файла, для какой АСУ"))

        self.comboBox_3.setItemText(0, _translate("MainWindow""АСУ РСО"))

        self.comboBox_3.setItemText(1, _translate("MainWindow""АСУ СГК"))

        self.comboBox_3.setItemText(2, _translate("MainWindow""АСУ РСО + СГК"))

        self.label.setText(_translate("MainWindow""Перенесите таблицу учебного плана в Excel и сохраните"))

        self.label_2.setText(_translate("MainWindow""Если структура тематического плана не соотвествует утвержденному шаблону, работать не будет"))

        self.label_3.setText(_translate("MainWindow""Алексей Кулагин"))


Листинг Parser.py

import numpy

import pandas

import re

from typing import List

from .TopicHour import TopicHour

from .TopicHour import HourType

from pandas import DataFrame


def parse(df: DataFrame, practical_setting: int, independent_setting: int) -> List[TopicHour]:

    df = rename_columns(df)

    df = clear_nan(df)

    df = try_fix_symbols(df)

    df = try_fix_topics(df)

    df = try_fix_independent(df)

    df = try_fix_introduction(df)


    raw_syllabus = make_raw_syllabus(df, practical_setting, independent_setting)

    return make_final_syllabus(raw_syllabus)


def make_raw_syllabus(df: DataFrame, practical_setting: int, independent_setting: int) -> List:

    rows_size = df['topic'].size

    selection_indexes = range_indexes(get_selection_indexes(df), rows_size)

    topic_indexes = range_indexes(get_topic_indexes(df), rows_size)

    practical_indexes = get_practical_indexes(df)

    independent_indexes = get_independent_indexes(df)


    raw_syllabus = []

    for selection_index in selection_indexes:  # Разделы

        selection_value = df.loc[selection_index.start, 'topic']

        topics = []

        for topic_index in topic_indexes:  # Темы

            if selection_index.start <= topic_index.start <= selection_index.stop:  # Если тема попадает в раздел

                topic_value = df.loc[topic_index.start, 'topic']

                practicals = []

                for practical_index in practical_indexes:  # Уроки

                    if topic_index.start <= practical_index <= topic_index.stop:  # Если занятие попадает в тему

                        if practical_setting == 0:

                            practical_value: str = df.loc[practical_index, 'content']

                        else:

                            practical_next_line_index = practical_index

                            practical_next_line_index += 1

                            practical_value: str = df.loc[practical_next_line_index, 'content']

                        independent_index = practical_index

                        independent_index += 1

                        independent = ''

                        if independent_index in independent_indexes:  # Если есть СР после занятия

                            if independent_setting == 0:

                                independent = df.loc[independent_index, 'content']

                            else:

                                independent_index += 1

                                independent = df.loc[independent_index, 'content']

                        hours = int(df.loc[practical_index, 'hours'])

                        regexp = re.compile(r'[Пп]рактичес')

                        if regexp.search(practical_value):

                            hour_type = HourType.PracticalWork

                        else:

                            hour_type = HourType.Lecture

                        practicals.append({'practical': practical_value, 'independent': independent,

                                           'hours': hours, 'hourType': hour_type})

                topics.append({'topic': topic_value, 'practicals': practicals})

        raw_syllabus.append({'selection': selection_value, 'topics': topics})

    return raw_syllabus


def make_final_syllabus(raw_syllabus: List) -> List[TopicHour]:

    syllabus = []

    regex_hour = r'^\d+\.??'

    regex_dash_hour = r'^-\d+\.?'

    for selection in raw_syllabus:

        for topic in selection.get('topics'):

            for practical in topic.get('practicals'):

                practical_value = practical.get('practical')

                practical_value = re.sub(regex_hour, '', practical_value, 1)  # замена число-точка на ничего

                practical_value = re.sub(regex_dash_hour, '', practical_value, 1)  # замена куска '-123.'

                syllabus.append(TopicHour(selection.get('selection'), topic.get('topic'), practical_value,

                                          practical.get('independent'), practical.get('hours'),

                                          practical.get('hourType')))

    return syllabus


def rename_columns(df: DataFrame) -> DataFrame:

    curr_col = df.columns

    if curr_col.size == 4:

        df.columns = ["topic""content""content2""hours"]

        df.loc[-1] = [curr_col[0], curr_col[1], curr_col[2], curr_col[3]]

    else:

        df.columns = ["topic""content""hours"]

        df.loc[-1] = [curr_col[0], curr_col[1], curr_col[2]]


    df.index = df.index + 1

    return df.sort_index()


def clear_nan(df: DataFrame) -> DataFrame:

    df = df.dropna(axis=0, how='all')

    df = df.reset_index()

    del df['index']

    df.loc[:, 'topic'] = df['topic'].fillna("")

    df.loc[:, 'hours'] = df['hours'].fillna(1)

    if df.columns.size == 4:

        df.loc[:, 'content'] = df['content'].fillna("")

        df.loc[:, 'content2'] = df['content2'].fillna("")

        df = df.astype({"topic": str, "content": str, "content2": str, "hours": int})

        df = df.replace(r'^\s+$', numpy.nan, regex=True)

    else:

        df.loc[:, 'content'] = df['content'].fillna("")

        df = df.astype({"topic": str, "content": str, "hours": int})

        df = df.replace(r'^\s+$', numpy.nan, regex=True)

    return df


def try_fix_topics(df: DataFrame) -> DataFrame:

    topic_index = get_topic_indexes(df)

    for index in topic_index:

        cur_row_value = df.loc[index].get('topic')


        new_row_index = index + 1

        next_row_value = df.loc[new_row_index].get('topic')

        if next_row_value != "":

            cur_row_value += " " + next_row_value

            df.at[index, 'topic'] = cur_row_value

            df.at[new_row_index, 'topic'] = ""


    return df


def try_fix_independent(df: DataFrame) -> DataFrame:

    practical_index = get_practical_indexes(df)

    if df.columns.size != 4:

        return df


    for index in practical_index:

        df.loc[index, 'content'] = " ".join(df.loc[index, 'content'].split())


    for index in practical_index:

        row = df.loc[index]

        curr_col_value = row.get('content')

        next_col_value = row.get('content2')

        if len(curr_col_value) < 5:

            df.loc[index, 'content'] = curr_col_value + next_col_value


    del df['content2']

    return df


def try_fix_symbols(df: DataFrame) -> DataFrame:

    df.loc[:, 'topic'] = df['topic'].str.replace('^\d$''',)

    df.loc[:, 'content'] = df['content'].str.replace('\xa0''')

    df.loc[:, 'content'] = df['content'].str.replace('\xad''')

    df.loc[:, 'content'] = df['content'].str.replace('\d\)''')

    df.loc[:, 'content'] = df['content'].str.replace(';'':')


    df.loc[:, 'topic'] = df['topic'].fillna("")

    df.loc[:, 'content'] = df['content'].fillna("")

    return df


def try_fix_introduction(df: DataFrame) -> DataFrame:

    introduction_regex = re.compile(r'[Вв]ведение')

    rows_size = df['topic'].size

    selection_indexes = range_indexes(get_selection_indexes(df), rows_size)

    for selection_index in selection_indexes:

        selection_value = df.loc[selection_index.start, 'topic']

        if introduction_regex.search(selection_value):

            if df.columns.size == 4:

                df.loc[-1] = [selection_value, ''''0]

            else:

                df.loc[-1] = [selection_value, ''0]


            df.loc[selection_index.start, 'topic'] = 'Тема. Введение'

            df.index = df.index + 1

            return df.sort_index()

    return df


def range_indexes(old_indexes: List[int], df_size: int) -> List[pandas.RangeIndex]:

    indexes = []

    i = 0

    while i <= len(old_indexes) - 1:

        curr_value = old_indexes[i]

        next_index = i

        next_index += 1

        if i == len(old_indexes) - 1:

            indexes.append(pandas.RangeIndex(curr_value, df_size))

        else:

            next_value = old_indexes[next_index]

            next_value -= 1

            indexes.append(pandas.RangeIndex(curr_value, next_value))

        i += 1


    return indexes


def get_selection_indexes(df: DataFrame):

    selection_index = df[df['topic'].str.contains('(?:(?:Р|р)аздел)|(?:^(?:В|в)ведение)')].index.values

    return selection_index


def get_topic_indexes(df: DataFrame):

    topic_index = df[df['topic'].str.contains('(?:Т|т)ема')].index.values

    return topic_index


def get_practical_indexes(df: DataFrame):

    practical_index = df[df['content'].str.contains('^\ *\d+\.?')].index.values

    return practical_index


def get_independent_indexes(df: DataFrame):

    independent_index = df[df['content'].str.contains('(?:С|с)амостоятельная')].index.values

    return independent_index



ColumnsValidator.py

from prompt_toolkit.validation import Validator, ValidationError

import re


class ColumnsValidator(Validator):

    def validate(self, document):

        ok = re.match(r'^[0-9](,[0-9])*$', document.text)

        if not ok:

            raise ValidationError(message='Введите пожайлуста стобцы в формате: 0,1,2,3')



NumberValidator.py

from prompt_toolkit.validation import Validator, ValidationError


class NumberValidator(Validator):

    def validate(self, document):

        try:

            int(document.text)

        except ValueError:

            raise ValidationError(message='Введите пожалуйста число')


TopicHour.py

from dataclasses import dataclass

from enum import Enum


class HourType(Enum):

    Lecture = "Лекция"

    PracticalWork = "Практ. работа"


@dataclass()

class TopicHour:

    selection: str

    topic: str

    content: str

    homework: str

    countHours: int

    hourType: HourType


    def __int__(self):

        self.countHours = 1

        self.hourType = HourType.Lecture