Вы находитесь на странице: 1из 38

МИНИСТЕРСТВО НАУКИ И ВЫСШЕГО ОБРАЗОВАНИЯ РОССИЙСКОЙ ФЕДЕРАЦИИ

федеральное государственное автономное образовательное учреждение


высшего образования
«Северный (Арктический) федеральный университет имени М.В. Ломоносова»

_____________________________________________
(наименование высшей школы / филиала / института / колледжа)

КУРСОВАЯ РАБОТА

По дисциплине Шаблонное программирование

На тему Использование паттернов при решении задач профессиональной деятельности


Паттерны «MVC», «Observer», «Action» и «Memento»

Выполнил обучающийся:
Квашнин Александр Юрьевич
(Ф.И.О.)

Направление подготовки / специальность:


09.03.02 Информационные системы и технологии
(код и наименование)

Курс: 4
Группа: 353817

Руководитель:
Деменкова Е.А, доцент, к.т.н
(Ф.И.О. руководителя, должность / уч. степень / звание)

Признать, что работа выполнена и


защищена с отметкой
(отметка прописью) (дата)

Руководитель Е.А Деменкова


(подпись руководителя) (инициалы, фамилия)

Архангельск 2022
МИНИСТЕРСТВО НАУКИ И ВЫСШЕГО ОБРАЗОВАНИЯ РОССИЙСКОЙ ФЕДЕРАЦИИ
федеральное государственное автономное образовательное учреждение
высшего образования
«Северный (Арктический) федеральный университет имени М.В. Ломоносова»

Кафедра информационных систем и технологий

ЗАДАНИЕ НА КУРСОВУЮ РАБОТУ

по Шаблонное программирование
(наименование дисциплины)
студенту ВШИТАС 4 курса 353817 группы
Квашнину Александру Юрьевичу
(фамилия, имя, отчество студента)
09.03.02 Информационные системы и технологии
(код и наименование направления подготовки/специальности)
ТЕМА: Использование паттернов при решении
задач профессиональной деятельности

ИСХОДНЫЕ ДАННЫЕ: Необходимо разработать систему


по учету средств вычислительной техники на предприятии,
используя паттерны «MVC», «Observer», «Action» и «Memento»

При этом выполнить:


1. Рассмотреть предметную область с целью подобрать проблему и
применить для ее решения данные паттерны.
2. Описать решение с использованием нотации UML, и составлением
классовой диаграммы и диаграммы взаимодействия.
3. Разработать приложение, соответствующую созданным диаграммам,
иллюстрирующую применение паттернов и решение поставленной задачи.
В пояснительной записке к курсовой работе должны быть следующие
разделы:
- описание предметной области, для которой разрабатывается приложение,
которое позволит решить задачу;
- краткое описание функциональных и качественных требований
к разрабатываемому приложению, которое позволит решить задачу;
- диаграммы схемы данных и блок-схемы основных алгоритмов;
- аргументированное обоснование применения выбранных шаблонов;
- навигационная модель пользовательского интерфейса;
- экранные копии разработанных форм и окон;
- описание процесса тестирования разработанного продукта;
- приложение с исходным кодом разработанного продукта.

Срок проектирования с « 31 » января 2022 г. по « 12 » июня 2022г.


Руководитель проекта доцент Деменкова Е.А
(должность) (подпись)
Архангельск 2022
ЛИСТ ДЛЯ ЗАМЕЧАНИЙ
ОГЛАВЛЕНИЕ

1 Обзор предметной области........................................................................................................5


2 Проектирование и реализация шаблона проектирования......................................................7
2.1 Диаграммы основных алгоритмов.................................................................................7
2.2 Обоснование выбора средств реализации.....................................................................8
3 Реализация программного продукта.........................................................................................9
3.1 Реализация шаблонов MVC и Observer.........................................................................9
3.2 Реализация паттернов «Memento» и «Action»............................................................16
Заключение...................................................................................................................................21
Список использованных источников.........................................................................................22
Приложение А (обязательное) UML - диаграммы...................................................................23
Приложение Б (обязательное) Исходный код разработанных программных модулей........24

4
1 ОБЗОР ПРЕДМЕТНОЙ ОБЛАСТИ

Необходимо разработать приложение по учету перемещений вычислительной


техники на предприятии. Предприятие имеет в своей структуре ряд подразделений.
необходимо вести учёт средств ВТ и оргтехники на предприятии. Каждое средство имеет
инвентарный номер, название, модель, дату приобретения, стоимость. На предприятии
средства могут передаваться из подразделения в подразделение, при этом необходимо
знать дату передачи и новое материально ответственное лицо (ФИО, должность).
Материально ответственный должен работать в том подразделении куда передаётся
техника. Также необходимо знать номер комнаты, где находится техника на текущий
момент. О каждом подразделении фиксируется номер, полное и краткое название. Также
необходимо фиксировать кто по должности в подразделении является руководителем. А
кто материально ответственным лицом.
Приложение должно иметь две функциональные составляющие:
- функционал оператора;
- функционал администратора;
Оператор не должен видеть и использовать функции администратора, такие как
добавление новых пользователей, либо, наоборот – удаление пользователей из системы,
поэтому необходимо разделение приложения на права использования.
Основной функционал оператора завязан на четырех основных функциях (CRUD):
- create (добавить);
- read (прочитать);
- update (обновить);
- delete (удалить).
Требуется разработать функционал для работы с объектами информационной
системы оператором.
Исходя из требований к функциям системы, рассмотрим функции опертора.
Оператор должен иметь возможность работы со следующими объектами системы:
- учет подразделений;
- учет техники;
- учет перемещений техники;
Данный функционал реализуем при помощи шаблонов:
- «MVC» - стандарт для приложений такого класса;
- «Listener» - будет передавать изменения в модели представлению;
- «Snapshot» и «Command» - необходимы для реализации отмены изменений.

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

6
2 ПРОЕКТИРОВАНИЕ И РЕАЛИЗАЦИЯ ШАБЛОНА
ПРОЕКТИРОВАНИЯ

2.1 Диаграммы основных алгоритмов

Рассмотрим UML-диаграммы основных функциональных действий в соответствии


с рисунком А.1.
Оператор будет отвечать за такие варианты использования, как Управление
техникой (manage equipment), Управление перемещением техники (manage move
equipment), Управление подразделением (manage subdivision).
Вариант использования управление техникой отвечает за варианты использования:
- добавить информацию о технике (add info equipment);
- изменить информацию о технике (edit info equipment);
- получить информацию о технике (get info equipment);
- удалить информацию о технике (del info equipment).
Вариант использования управление перемещением техники отвечает за варианты
использования:
- добавить информацию о перемещении (add info about move);
- изменить информацию о перемещении (edit info about move);
- получить информацию о перемещении (get info about move);
- удалить информацию о перемещении (del info about move).
Вариант использования управление подразделением отвечает за варианты
использования:
- добавить информацию о подразделении (add info subdivision);
- изменить информацию о подразделении (edit info subdivision);
- получить информацию о подразделении (get info subdivision);
- удалить информацию о подразделении (del info subdivision).
Администратор будет отвечать за вариант использования Управление
сотрудниками (stuff management).
Вариант использования управление сотрудниками отвечает за варианты
использования:
- добавить информацию о сотруднике (add stuff);
- изменить информацию о сотруднике (edit stuff);
- получить информацию о сотруднике (get stuff);
- удалить информацию о сотруднике (del stuff).

7
Рассмотрим Диаграмму последовательности для добавления нового подразделения
в систему в соответствии с рисунком А.2.
В начале оператор открывает окно со всеми подразделениями в системе, после чего
вводит информацию о подразделении в соответствующие поля и нажимает кнопку
«Добавить». Система также учитывает, чтобы необходимые поля были заполнены верно,
например, в поле номер комнаты не может быть строка с текстом. После проверки на
валидность данных – данный кортеж загружается в модель данных. После чего в списке
подразделений появляется новое с введенными ранее данными.
Диаграмма классов представлена на рисунке А.3.
Диаграмма схемы данных представлена на рисунке А.4.

2.2 Обоснование выбора средств реализации

В качестве языка программирования был выбран Python, разработанный в 1989-


1991 годах и имеющий поддержку в настоящее время.
В качестве библиотеки для создания пользовательского интерфейса будет выбрана
PyQt - набор расширений графического фреймворка Qt для языка программирования
Python, выполненный в виде расширения Python. PyQt разработан британской компанией
Riverbank Computing. PyQt работает на всех платформах, поддерживаемых Qt: Linux и
другие UNIX-подобные ОС, Mac OS X и Windows.
В качестве среды разработки выберем Microsoft Visual Studio Code – для написания
программного кода. VS Code - Позиционируется как «лёгкий» редактор кода
для кроссплатформенной разработки веб- и облачных приложений. Включает в
себя отладчик, инструменты для работы с Git, подсветку синтаксиса, IntelliSense и
средства для рефакторинга. Имеет широкие возможности для кастомизации:
пользовательские темы, сочетания клавиш и файлы конфигурации. Также используем Qt
Designer – для разработки пользовательского интерфейса.
Для проектирования уровней архитектуры возьмем за основу паттерн «MVC». Он
удобен для проектирования систем такого типа и используется во многих фреймворках, в
том числе и в выбранном в разработке данной системы – PyQt. За «Model» системы будет
приниматься бизнес-логика приложения. «View» и «Controller» - в PyQt перемешаны и
отвечают за внешний вид приложения и доступ к модели данных.
Воспользуемся паттерном «Observer» для подписки на события изменения модели
данных и уведомлении представления об этих изменениях. Так как в качестве базы
данных используется кастомная модель данных с хранением объектов классов-сущностей
при помощи сериализации - данная реализация не представлена в фреймворке PyQt, в

8
отличие от класса модели данных для SQL баз данных (например QSqlTableModel).
Поэтому для уведомления представлений об изменениях используется данный паттерн.
Воспользуемся паттернами «Memento» и «Action» - которые в совместном
использовании позволяют реализовать хранение снимков состояний модели данных и
отмену последних действий пользователем путем возвращения к предыдущему снимку
состояния.
3 РЕАЛИЗАЦИЯ ПРОГРАММНОГО ПРОДУКТА

Итоговый исходный код (программные модули) программного продукта


представлен на листингах Б.1 – Б.2.
Для наглядности реализации паттернов – рассмотрим её на примере реализации
учета подразделений.

3.1 Реализация шаблонов MVC и Listener

Начнем создание проекта с реализации паттернов «MVC» и «Observer»


Концепция паттерна «MVC» предполагает разделение логики приложения на три
взаимосвязанных компонента:
- «Model» (модель): данный компонент непосредственно описывает используемые
в проекте данные, определяет структуру приложения. Данные объекты моделей хранятся в
хранилище данных, которое может быть представлено как базой данных, так и, например,
в JSON файле. Также модель не должна содержать логику взаимодействия с
пользователем, не содержать логику отображения данных в представлении, модель
должна именно содержать данные и хранить логику управления этими данными;
- «View» (представление): представляет собой визуальную составляющую, с
помощью которой пользователь взаимодействует с данными, представление же, в свою
очередь, не должно содержать логику обработки запроса или управления данными;
- «Controller» (контроллер): связующее звено между представлением и моделью,
именно он содержит логику обработки запросов от пользователей, он получает данные,
принятые от представления, и обрабатывает их.
Паттерн «Observer» - поведенческий паттерн проектирования, который создаёт
механизм подписки, позволяющий одним объектам следить и реагировать на события,
происходящие в других объектах.
При создании данного проекта появилась необходимость разделить логику
приложения на два модуля:

9
Модели для хранения данных, которая также инициирует события изменения
самой себя;
Совмещенном контроллере и представлении, позволяющем пользователю
осуществлять взаимодействие с системой, а также реагировать на изменения в модели.
Приступим к реализации данных шаблонов.
Для этого, для начала - в VS Code создадим соответствующие модули – model.py и
view.py.
Приступим к созданию «Model» в шаблоне «MVC». Для этого в программном
модуле «model.py» создадим необходимые нам классы.
Создадим класс Subdivision. Класс Subdivision представляет собой описание
подразделения. Данный класс унаследован от object и содержит в себе:
- приватные атрибуты, такие как: «number_of_subdivision»,
«fullname_of_subdivision», «shortname_of_subdivision», «number_of_chamber», « supervisor
» , «financially_responsible»- номер, полное наименование, краткое наименование, номер
комнаты, руководитель и материально ответственное лицо соответственно;
- методы, возвращающие приватные атрибуты (геттеры) с декоратором
«@property».
Исходный код данного класса представлен на листинге 1.
Листинг 1 – Класс Subdivision
class Subdivision(object):
def __init__(self, number_of_subdivision,
fullname_of_subdivision,
shortname_of_subdivision,
number_of_chamber, supervisor,
financially_responsible):
self.__number_of_subdivision = number_of_subdivision
self.__fullname_of_subdivision = fullname_of_subdivision
self.__shortname_of_subdivision =
shortname_of_subdivision
self.__number_of_chamber = number_of_chamber
self.__supervisor = supervisor
self.__financially_responsible = financially_responsible

@property
def number_of_subdivision(self):
return self.__number_of_subdivision

@property
def fullname_of_subdivision(self):
return self.__fullname_of_subdivision

@property
def shortname_of_subdivision(self):

10
return self.__shortname_of_subdivision

@property
def number_of_chamber(self):
return self.__number_of_chamber

@property
def supervisor(self):
return self.__supervisor

@property
def financially_responsible(self):
return self.__financially_responsible
После чего создадим класс AbstractModel – реализующий паттерн «Observer».
Класс AbstractModel предназначен для извещения представлений об изменениях в
модели данных. Данный класс унаследован от object и содержит в себе:
- атрибут listeners – список вызываемых объектов;
- методы «addListener» и «removeListener» - добавление и удаление вызываемого
объекта соответственно;
- метод «update» - отвечает за извещение об изменениях.
Исходный код данного класса представлен на листинге 2.
Листинг 2 – Класс AbstractModel
class AbstractModel(object):
def __init__(self):
self.listeners = []

def addListener(self, listenerFunc):


self.listeners.append(listenerFunc)

def removeListener(self, listenerFunc):


self.listeners.remove(listenerFunc)

def update(self):
for eachFunc in self.listeners:
eachFunc(self)
После чего создадим класс Model.
Класс Model представляет собой описание модели данных. Данный класс отвечает
за хранение экземпляров классов и их обработку. Класс унаследован от класса
AbstractModel, описание и предназначение которого указано выше. Model в рамках
рассмотрения функции учета подразделений содержит в себе:
- приватные атрибуты, такие как: «listSubdivision» - список для хранения
экземпляров класса Subdivision;
- методы для добавления, изменения, удаления экземпляров классов из
соответствующих списков;
11
- методы для получения списков экземпляров классов;
- методы open_database и save_database, использующиеся для десериализации и
сериализации списков экземпляров классов с помощью модуля pickle.
Исходный код данного класса представлен в листинге 3.
Листинг 3 – Класс Model
class Model(AbstractModel):
"""Класс Model представляет собой описание модели данных"""

def __init__(self):
super(Model,self).__init__()
self.__listSubdivision=[]
self.queue = []
try:
self.open_database()
except:
self.save_database()

def open_database(self):
cort=ModelSerialize.deserialize()
self.__listEquipment = cort[0]
self.__listMove = cort[1]
self.__listStuff = cort[2]
self.__listSubdivision=cort[3]
self.__listMove = cort[4]

def newSubdivision(self,number_of_subdivision,
fullname_of_subdivision,
shortname_of_subdivision,
number_of_chamber, supervisor,
financially_responsible):
subdivision = Subdivision(number_of_subdivision,
fullname_of_subdivision,
shortname_of_subdivision,
number_of_chamber, supervisor,
financially_responsible)
self.__listSubdivision.append(subdivision)
self.update()
self.save_database()

def updateSubdivision(self, old, fullname_of_subdivision,


shortname_of_subdivision,
number_of_chamber, supervisor,
financially_responsible):
for index,item in enumerate(self.__listSubdivision):
if item.number_of_subdivision == old:
new = Subdivision(old, fullname_of_subdivision,
shortname_of_subdivision,
number_of_chamber, supervisor,
financially_responsible)

12
self.__listSubdivision[index] = new
self.update()
self.save_database()

def deleteSubdivison(self,numb):
for item in self.__listEquipment:
if item.get_subdivision().number_of_subdivision ==
numb:
return False

for item in enumerate(self.__listSubdivision):


if item.number_of_subdivision == numb:
self.__listSubdivision.remove(item)
self.update()
self.save_database()

def getAllSubdivision(self):
return self.__listSubdivision
После разработки программного модуля модели данных приступаем к
программному модулю представления. Для этого необходимо, в соответствии с
диаграммой классов, разработать пользовательский интерфейс для класса учета
подразделений. Разработанный интерфейс для учета подразделений представлен на
рисунке 1.

13
Рисунок 1 – Интерфейс представления учета подразделений

После создания интерфейсов приступим непосредственно к разработке


программного модуля представления. Полученный нами ранее интерфейс привяжем к
классу модуля представления. Также импортируем из разработанного ранее программного
модуля класс модели данных.
Класс View_Subdivision представляет собой описание представления по работе с
подразделениями. Данный класс унаследован от QtWidgets.QWidget и содержит в себе:
- конструктор, в котором выполняется: загрузка файла интерфейса
«view_subdivision.ui», вызов метода «addListener» модели данных с передачей в качестве
параметра – метода «self.update» для извещения об изменениях в модели, установка
заголовков для «twSubdivision», подключение слотов нажатия на кнопки
добавления/изменения/удаления подразделений к соответствующим сигналам,
подключения слота нажатия на «twSubdivision» к соответствующему сигналу;
- слот «btn_Add_clicked», в котором происходит вызов метода экземпляра класса
модели данных «newSubdivision», с передачей в качестве параметров текущего текста в
элементах интерфейса «leShDescr», «leFull», «leNumberRoom», «leSupervisor», «leFin», а
также – переменной «k» - номера подразделения, определяемого путем подсчета текущего
количества подразделений в модели данных;
- слот «btn_Edit_clicked», в котором происходит вызов метода экземпляра класса
модели данных «updateSubdivision», с передачей в качестве параметров номера выбранной
строки в таблице подразделений и текущего текста в элементах интерфейса «leShDescr»,
«leFull», «leNumberRoom», «leSupervisor», «leFin»;
- слот «btnDelclicked», в котором происходит вызов метода экземпляра класса
модели данных «deleteSubdivison», с передачей в качества параметра номера выбранной
строки в таблице подразделений;
- слот «on_tw_clicked», в котором происходит назначение элементам интерфейса,
доступным для редактирования – значений из выбранной строки из таблицы
подразделений;
- метод «update», в котором происходит обновление представления из модели
данных.
Исходный код данного класса представлен на листинге 4.
Листинг 4 – Класс View_Subdivision
class View_Subdivision(QtWidgets.QWidget):
def __init__(self):
super(View_Subdivision, self).__init__()

14
uic.loadUi("UI/view_subdivision.ui", self)
m.addListener(self.update)
self.update(m)

self.twSubdivision.setHorizontalHeaderLabels(("Номер;Полное
описание;Краткое описание;Номер
комнаты;Руководитель;МОЛ").split(";"))

self.btnAdd.clicked.connect(self.btn_Add_clicked)

self.btnEdit.clicked.connect(self.btn_Edit_clicked)
self.btnDel.clicked.connect(self.btnDelclicked)
self.twSubdivision.clicked.connect(self.on_tw_clicked)

def on_tw_clicked(self):

self.leFull.setText(self.twSubdivision.item(self.twSubdivision.c
urrentItem().row(),1).text())

self.leShDescr.setText(self.twSubdivision.item(self.twSubdivisio
n.currentItem().row(),2).text())

self.leNumberRoom.setText(self.twSubdivision.item(self.twSubdivi
sion.currentItem().row(),3).text())

self.leSupervisor.setText(self.twSubdivision.item(self.twSubdivi
sion.currentItem().row(),4).text())

self.leFin.setText(self.twSubdivision.item(self.twSubdivision.cu
rrentItem().row(),5).text())

def btn_Edit_clicked(self):

sh_descr = self.leShDescr.text()
f_descr = self.leFull.text()
numb_room= self.leNumberRoom.text()
sup = self.leSupervisor.text()
fin = self.leFin.text()

caretaker.backup()

m.updateSubdivision(int(self.twSubdivision.item(self.twSubdivisi
on.currentItem().row(),0).text()),f_descr,sh_descr,numb_room,sup
,fin)

def btnDelclicked(self):
caretaker.backup()
if
m.deleteSubdivison(int(self.twSubdivision.item(self.twSubdivisio
n.currentItem().row(),0).text()))==False:

15
QtWidgets.QMessageBox.warning(self,"Ошибка
удаления","Для того что бы удалить данное подазделение - сначала
удалитевсю связанную с ним вычислителью технику")

def btn_Add_clicked(self):
caretaker.backup()
sh_descr = self.leShDescr.text()
f_descr = self.leFull.text()
numb_room= self.leNumberRoom.text()
sup = self.leSupervisor.text()
fin = self.leFin.text()
twnumcols = len(m.getAllSubdivision())
if twnumcols == 0:
k=1
else:
k = int(self.twSubdivision.item(twnumcols-
1,0).text())+1
m.newSubdivision(k,f_descr,sh_descr, numb_room,sup,fin)

def update(self, m:Model):


data = m.getAllSubdivision()
numrows = len(data)
numcols = 6
self.twSubdivision.setColumnCount(numcols)
self.twSubdivision.setRowCount(numrows)
for index, item in enumerate(data):

self.twSubdivision.setItem(index,0,QtWidgets.QTableWidgetItem(st
r(data[index].number_of_subdivision)))

self.twSubdivision.setItem(index,1,QtWidgets.QTableWidgetItem(st
r(data[index].fullname_of_subdivision)))

self.twSubdivision.setItem(index,2,QtWidgets.QTableWidgetItem(st
r(data[index].shortname_of_subdivision)))

self.twSubdivision.setItem(index,3,QtWidgets.QTableWidgetItem(st
r(data[index].number_of_chamber)))

self.twSubdivision.setItem(index,4,QtWidgets.QTableWidgetItem(st
r(data[index].supervisor)))

self.twSubdivision.setItem(index,5,QtWidgets.QTableWidgetItem(st
r(data[index].financially_responsible)))

3.2 Реализация паттернов «Memento» и «Action»

16
Совместная реализация данных шаблонов позволяет хранить резервные копии
сложного состояния информационной системы и восстанавливать его, если потребуется.
Объекты команд выступают в роли опекунов и запрашивают снимки у
информационной системы перед тем, как выполнить своё действие. Если потребуется
отменить операцию, команда сможет восстановить состояние информационной системы,
используя сохранённый снимок.
При этом снимок не имеет публичных полей, поэтому другие объекты не имеют
доступа к его внутренним данным.
Для реализации данного паттерна нам необходимо доработать ранее созданные
«model.py» и «view.py».
В программном модуле модели данных создадим и опишем классы:
- Snapshot и DoSnapshot – интерфейс для снимка и класс снимка соответсвенно;
- CareTaker – опекун и реализация паттерна команда.
Исходный код данных классов представлен на листинге 5.
Листинг 5 – Классы Snapshot и Caretaker
class Snapshot(ABC):

@abstractmethod
def get_name(self) -> str:
pass

@abstractmethod
def get_date(self) -> str:
pass

class DoSnapshot(Snapshot):
def __init__(self, stateSubdivision) -> None:

self._stateSubdivision = stateSubdivision
self._date = str(datetime.now())[:19]

def get_state(self):
return self._stateSubdivision

def get_name(self) -> str:


return f"{self._date}"

def get_date(self) -> str:


return self._date

class CareTaker():
def __init__(self, model: Model) -> None:
self._snapshots = []
self._model = model

17
def backup(self) -> None:
self._snapshots.append(self._model.save())

def undo(self) -> None:


if not len(self._snapshots):
return

snapshot = self._snapshots.pop()
try:
self._model.restore(snapshot)
except Exception:
self.undo()

def show_history(self) -> None:


for snapshot in self._snapshots:
print(snapshot.get_name())
Так же доработаем класс модели данных методами save и restore, позволяющими
сохранять внутри снимка состояние модели данных и восстанавливать его соответственно.
Исходный код данных методов представлен на листинге 6.
Листинг 6 – Методы save и restore
def save(self):
ls = self.__listStuff[:]
lsu = self.__listSubdivision[:]
leq = self.__listEquipment[:]
lm = self.__listMove[:]
self.update()
return DoSnapshot(ls,lsu,leq,lm)

def restore(self, snapshot: Snapshot):


cort = snapshot.get_state()
self.__listStuff = cort[0]
self.__listSubdivision = cort[1]
self.__listEquipment = cort[2]
self.__listMove = cort[3]
self.update()

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


подразделений – добавим туда вызов метода класса опекуна – «backup» - совершающий
сохранение снимка текущего состояния и помещающий его в лист снимков.
Далее - доработаем интерфейс системы для возможности совершать отмену
предыдущего действия, для этого в интерфейсе добавим кнопку «Отмена предыдущего
действия» по нажатию на которую будет происходить вызов метода класса опекуна
«undo», возвращающий последний элемент в списке снимков, и тем самым отменяющий
предыдущее действие пользователя.

18
Перейдем к тестированию разработанных модулей. Запустим наш проект,
авторизуемся под учетной записью оператора и перейдем во вкладку «Подразделения».
Добавим несколько записей подразделений в систему. Результат отображен на рисунке 2.

Рисунок 2 – Добавленные подразделения

После чего проверим отмену предыдущих действи, нажмем дважды на


соответствующую кнопку. Результат изображен на рисунке 3.

19
Рисунок 3 – Проверка отмены предыдущего действия

Отмена предыдущего действия успешно отработала.

20
ЗАКЛЮЧЕНИЕ

В результате выполнения курсовой работы были выполнены следующие цели:


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

21
СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ

1 BOUML [Electronic resource] / BOUML – Electronic text data – [Paris]: Pages Bruno,
2012. – Mode of access: https://www.bouml.fr/documentation.html, free access (02.10.2022). –
Title from screen

22
ПРИЛОЖЕНИЕ А
(обязательное)
UML - диаграммы

Рисунок А.1 Диаграмма вариантов использования

Рисунок А.3 – Диаграмма классов

23
ПРИЛОЖЕНИЕ Б
(обязательное)
Исходный код разработанных программных модулей

Листинг Б.1 – Программный код модуля модели данных

from abc import ABC, abstractmethod


from ast import Sub
from datetime import datetime,date
import pickle

class Stuff(object):
def __init__(self,id,login,password,position,name,surname) -> None:
self.__id = id
self.__login = login
self.__password = password
self.__position = position
self.__name = name
self.__surname = surname

@property
def get_id(self):
return self.__id

@property
def get_login(self):
return self.__login

@property
def get_password(self):
return self.__password

@property
def get_position(self):
return self.__position

@property
def get_name(self):
return self.__name

@property
def get_surname(self):
return self.__surname

class Subdivision(object):
def __init__(self, number_of_subdivision, fullname_of_subdivision,
shortname_of_subdivision,
number_of_chamber, supervisor,
financially_responsible):
self.__number_of_subdivision = number_of_subdivision
self.__fullname_of_subdivision = fullname_of_subdivision
self.__shortname_of_subdivision = shortname_of_subdivision
self.__number_of_chamber = number_of_chamber
self.__supervisor = supervisor
self.__financially_responsible = financially_responsible

@property

24
def number_of_subdivision(self):
return self.__number_of_subdivision

@property
def fullname_of_subdivision(self):
return self.__fullname_of_subdivision

@property
def shortname_of_subdivision(self):
return self.__shortname_of_subdivision

@property
def number_of_chamber(self):
return self.__number_of_chamber

@property
def supervisor(self):
return self.__supervisor

@property
def financially_responsible(self):
return self.__financially_responsible

class Equipment():
'''
Класс представляет собой описание вычислительной техники
'''
def __init__(self, subdivision:Subdivision, name_of_equipment='',
model_of_equipment='',
date_of_purchase=date.today(), inventory_number=0,
price_of_equipment=0):
self.__sub = subdivision
self.__name_of_equipment = name_of_equipment
self.__model_of_equipment = model_of_equipment
self.__date_of_purchase = date_of_purchase
self.__inventory_number = inventory_number
self.__price_of_equipment = price_of_equipment

def get_subdivision(self):
return self.__sub

def set_subdivion(self,val):
self.__sub = val

def get_inventory_number(self):
return self.__inventory_number

def get_name_of_equipment(self):
return self.__name_of_equipment

def get_model_of_equipment(self):
return self.__model_of_equipment

def get_date_of_purchase(self):
return self.__date_of_purchase

def get_price_of_equipment(self):
return self.__price_of_equipment

25
class move_equipment():
'''
Класс представления перемещения техники
'''
def __init__(self,equip, to_subdivision:Subdivision,
financially_responsible:Subdivision,to_chamber:Subdivision,from_subdivision:S
ubdivision,from_of_chamber:Subdivision, date_of_move=date.today()):
self.__eqip = equip
self.__to_subdivision = to_subdivision
self.__from_subdivision = from_subdivision
self.__from_of_chamber = from_of_chamber
self.__financially_responsible = financially_responsible
self.__to_chamber = to_chamber
self.__date_of_move = date_of_move

@property
def from_of_chamber(self):
return self.__from_of_chamber

@property
def to_chamber(self):
return self.__to_chamber

def get_equip(self):
return self.__eqip

def get_to_subdivision(self):
return self.__to_subdivision

def get_from_subdivision(self):
return self.__from_subdivision

def get_from_of_chamber(self):
return self.__from_of_chamber

def get_financially_responsible(self):
return self.__financially_responsible

def get_to_chamber(self):
return self.__to_chamber

def get_date_of_move(self):
return self.__date_of_move

class AbstractModel(object):
def __init__(self):
self.listeners = []

def addListener(self, listenerFunc):


self.listeners.append(listenerFunc)

def removeListener(self, listenerFunc):


self.listeners.remove(listenerFunc)

def update(self):
for eachFunc in self.listeners:
eachFunc(self)

class Snapshot(ABC):

26
@abstractmethod
def get_name(self) -> str:
pass

@abstractmethod
def get_date(self) -> str:
pass

class ModelSerialize(object):
"""Класс ModelSerialize предназначен для сериализации и десериалезации
модели данных"""
@staticmethod
def serialize(model):
with open('model.pkl', 'wb') as f:
pickle.dump(model,f)
f.closed

@staticmethod
def deserialize():
with open('model.pkl', 'rb') as f:
model = pickle.load(f)
f.closed
return model

class Model(AbstractModel):

def __init__(self):
super(Model,self).__init__()
self.__listStuff=[]
self.__listSubdivision=[]
self.__listEquipment=[]
self.__listMove=[]
self.queue = []

try:
self.open_database()
except:
self.save_database()

def open_database(self):
cort=ModelSerialize.deserialize()
self.__listEquipment = cort[0]
self.__listMove = cort[1]
self.__listStuff = cort[2]
self.__listSubdivision=cort[3]
self.__listMove = cort[4]

def save_database(self):
mod =
self.__listEquipment,self.__listMove,self.__listStuff,self.__listSubdivision,
self.__listMove;
ModelSerialize.serialize(mod)

def authorization(self, login, password):


k = 0

27
if login == "root" and password == "root":
return "Success:Admin"
for item in self.__listStuff:
if item.get_login == login and item.get_password == password:
k = k + 1
if k==1:
return "Success:Operator"

if k == 0:
return "Error!"

def newStuff(self, id, login,password,position,name,surname):


stuff = Stuff(id,login,password,position,name,surname)
self.__listStuff.append(stuff)
self.queue.append(ModelTransaction(stuff.__class__.__name__, id,
"add").write())
self.update()
self.save_database()

def getAllStuff(self):
return self.__listStuff

def updateUser(self, oldstuffid, login,password,position,name,surname):


for index, item in enumerate(self.__listStuff):
if item.get_id == oldstuffid:
newstaff=Stuff(oldstuffid,
login,password,position,name,surname)
self.__listStuff[index] = newstaff

self.queue.append(ModelTransaction(newstaff.__class__.__name__, id,
"upd").write())
break
self.update()
self.save_database()

def deleteUser(self, stuffid):


for index, item in enumerate(self.__listStuff):
if item.get_id == stuffid:
self.__listStuff.remove(item)
self.queue.append(ModelTransaction(Stuff.__name__, stuffid,
"rem").write())
self.update()
self.save_database()

def newSubdivision(self,number_of_subdivision, fullname_of_subdivision,


shortname_of_subdivision,
number_of_chamber, supervisor,
financially_responsible):
subdivision = Subdivision(number_of_subdivision,
fullname_of_subdivision,
shortname_of_subdivision,
number_of_chamber, supervisor,
financially_responsible)
self.__listSubdivision.append(subdivision)
self.update()
self.save_database()

def updateSubdivision(self, old, fullname_of_subdivision,


shortname_of_subdivision,
number_of_chamber, supervisor,
financially_responsible):
for index,item in enumerate(self.__listSubdivision):

28
if item.number_of_subdivision == old:
new = Subdivision(old, fullname_of_subdivision,
shortname_of_subdivision,
number_of_chamber, supervisor,
financially_responsible)
self.__listSubdivision[index] = new
self.update()
self.save_database()

def deleteSubdivison(self,numb):
for item in self.__listEquipment:
if item.get_subdivision().number_of_subdivision == numb:
return False

for item in self.__listSubdivision:


if item.number_of_subdivision == numb:
self.__listSubdivision.remove(item)
self.update()
self.save_database()

def getAllSubdivision(self):
return self.__listSubdivision

def newEquipment(self,sub,name_of_equipment,
model_of_equipment,date_of_purchase, inventory_number, price_of_equipment):
equipment = Equipment(sub,name_of_equipment,
model_of_equipment,date_of_purchase, inventory_number, price_of_equipment)
self.__listEquipment.append(equipment)
self.update()
self.save_database()

def getAllEquipment(self):
return self.__listEquipment

def newMoveEquipment(self, equip, to_subdivision:Subdivision,


financially_responsible:Subdivision,to_chamber:Subdivision,from_subdivision:S
ubdivision,from_of_chamber:Subdivision, date_of_move=date.today()):
move =
move_equipment(equip,to_subdivision,financially_responsible,to_chamber,from_s
ubdivision,from_of_chamber,date_of_move)
self.__listMove.append(move)
for item in self.__listEquipment:
if item.get_inventory_number() == equip.get_inventory_number():
item.set_subdivion(to_subdivision)
self.update()
self.save_database()

def getAllMove(self):
return self.__listMove

def save(self):
ls = self.__listStuff[:]
lsu = self.__listSubdivision[:]
leq = self.__listEquipment[:]
lm = self.__listMove[:]
self.update()
return DoSnapshot(ls,lsu,leq,lm)

def restore(self, snapshot: Snapshot):


cort = snapshot.get_state()

29
self.__listStuff = cort[0]
self.__listSubdivision = cort[1]
self.__listEquipment = cort[2]
self.__listMove = cort[3]
self.update()

class ModelTransaction(object):
"""Класс ModelTransaction предназначен для учета транзакций в модели
данных"""
def __init__(self, obj, objid, do):
self.obj=obj
self.objid = objid
self.do = do
self.when = datetime.now()

def write(self):
f = open('modeltransaction.txt', 'a')
if self.do=="rem":
f.write('Когда: {0} : Удален экземпляр класса {1} - ID:{2} из
модели данных\n'.format(self.when, self.obj, self.objid))
f.closed
if self.do=="add":
f.write('Когда: {0} : Добавлен экземпляр класса {1} - ID:{2} из
модели данных \n'.format(self.when, self.obj, self.objid))
f.closed
if self.do=="upd":
f.write('Когда: {0} : Изменен экземпляр класса {1} - ID:{2} из
модели данных \n'.format(self.when, self.obj, self.objid))
f.closed

class DoSnapshot(Snapshot):
def __init__(self, stateStuff,stateSubdivision,stateEquipment,stateMove)
-> None:
self._stateStuff = stateStuff
self._stateSubdivision = stateSubdivision
self._stateEquipment = stateEquipment
self._stateMove = stateMove

self._date = str(datetime.now())[:19]

def get_state(self):
return
self._stateStuff,self._stateSubdivision,self._stateEquipment,self._stateMove;

def get_name(self) -> str:


return f"{self._date}"

def get_date(self) -> str:


return self._date

class CareTaker():

def __init__(self, model: Model) -> None:


self._snapshots = []
self._model = model

30
def backup(self) -> None:
self._snapshots.append(self._model.save())

def undo(self) -> None:


if not len(self._snapshots):
return

snapshot = self._snapshots.pop()
print(f"Caretaker: Restoring state to: {snapshot.get_name()}")
try:
self._model.restore(snapshot)
except Exception:
self.undo()

def show_history(self) -> None:


for snapshot in self._snapshots:
print(snapshot.get_name())

Листинг Б.2 – Программный код модуля представления

from ast import Mod


from datetime import date, datetime
from pyexpat import model
import sys
from model import Model
from PyQt5 import QtWidgets,uic,QtCore,QtGui
from PyQt5.QtCore import QDate, QDateTime
from model import CareTaker

class loginView(QtWidgets.QDialog):
def __init__(self):
super(loginView, self).__init__()
uic.loadUi("UI/loginview.ui", self)
self.lePassword.setEchoMode(QtWidgets.QLineEdit.EchoMode.Password)
self.btnLogin.clicked.connect(self.btn_Login_clicked)

def btn_Login_clicked(self):

login = self.leLogin.text()
password = self.lePassword.text()
aut= m.authorization(login,password)
print(aut)
if aut == "Success:Admin":
self.adminpanel = adminView()
self.adminpanel.show()
if aut == "Success:Operator":
self.opreatorpanel = operatorView()
self.opreatorpanel.show()
if aut == "Error!":
msg = QtWidgets.QMessageBox(self)
msg.setText("В доступе отказано! Проверьте правильность вводимых
вами логина и пароля!")
msg.setWindowTitle("Ошибка вход")
msg.show()

class adminView(QtWidgets.QWidget):

31
def __init__(self):
super(adminView, self).__init__()
uic.loadUi("UI/adminview.ui", self)
m.addListener(self.update)
self.update(m)

self.twUsers.setHorizontalHeaderLabels(("Номер;Логин;Пароль;Должность;Имя;Фам
илия").split(";"))

self.btnAddUser.clicked.connect(self.btn_AddUser_clicked)
self.btnDeleteUser.clicked.connect(self.btn_DelUser_clicked)
self.btnUpdateUser.clicked.connect(self.btn_UpdateUser_clicked)
self.twUsers.clicked.connect(self.on_twUsers_clicked)

def btn_AddUser_clicked(self):
login = self.leLogin.text()
password = self.lePassword.text()
role = self.cbRole.currentText()
pozition = self.lePosition.text()
name = self.leName.text()
surname = self.leSurname.text()
twnumcols = len(m.getAllStuff())
if twnumcols == 0:
k=1
else:
k = int(self.twUsers.item(twnumcols-1,0).text())+1
m.newStuff(k,login,password,pozition,name,surname)

def btn_UpdateUser_clicked(self):
login = self.leLogin.text()
password = self.lePassword.text()
role = self.cbRole.currentText()

m.updateUser(int(self.twUsers.item(self.twUsers.currentItem().row(),0).text()
),login,password,role)

def btn_DelUser_clicked(self):

m.deleteUser(int(self.twUsers.item(self.twUsers.currentItem().row(),0).text()
))

def on_twUsers_clicked(self):

self.leLogin.setText(self.twUsers.item(self.twUsers.currentItem().row(),1).te
xt())

self.lePassword.setText(self.twUsers.item(self.twUsers.currentItem().row(),2)
.text())

self.lePosition.setText(self.twUsers.item(self.twUsers.currentItem().row(),3)
.text())

self.leName.setText(self.twUsers.item(self.twUsers.currentItem().row(),4).tex
t())

self.leSurname.setText(self.twUsers.item(self.twUsers.currentItem().row(),5).
text())

#self.cbRole.setCurrentText(self.twUsers.item(self.twUsers.currentItem().row(
),3).text())

32
def update(self, m):
data = m.getAllStuff()
numrows = len(data)
numcols = 6
self.twUsers.setColumnCount(numcols)
self.twUsers.setRowCount(numrows)
for index, item in enumerate(data):

self.twUsers.setItem(index,0,QtWidgets.QTableWidgetItem(str(data[index].get_i
d)))

self.twUsers.setItem(index,1,QtWidgets.QTableWidgetItem(str(data[index].get_l
ogin)))

self.twUsers.setItem(index,2,QtWidgets.QTableWidgetItem(str(data[index].get_p
assword)))

self.twUsers.setItem(index,3,QtWidgets.QTableWidgetItem(str(data[index].get_p
osition)))

self.twUsers.setItem(index,4,QtWidgets.QTableWidgetItem(str(data[index].get_n
ame)))

self.twUsers.setItem(index,5,QtWidgets.QTableWidgetItem(str(data[index].get_s
urname)))

class View_Subdivision(QtWidgets.QWidget):
def __init__(self):
super(View_Subdivision, self).__init__()
uic.loadUi("UI/view_subdivision.ui", self)
m.addListener(self.update)
self.update(m)

self.twSubdivision.setHorizontalHeaderLabels(("Номер;Полное
описание;Краткое описание;Номер комнаты;Руководитель;МОЛ").split(";"))

self.btnAdd.clicked.connect(self.btn_Add_clicked)

self.btnEdit.clicked.connect(self.btn_Edit_clicked)
self.btnDel.clicked.connect(self.btnDelclicked)
self.twSubdivision.clicked.connect(self.on_tw_clicked)

def on_tw_clicked(self):

self.leFull.setText(self.twSubdivision.item(self.twSubdivision.currentItem().
row(),1).text())

self.leShDescr.setText(self.twSubdivision.item(self.twSubdivision.currentItem
().row(),2).text())

self.leNumberRoom.setText(self.twSubdivision.item(self.twSubdivision.currentI
tem().row(),3).text())

self.leSupervisor.setText(self.twSubdivision.item(self.twSubdivision.currentI
tem().row(),4).text())

self.leFin.setText(self.twSubdivision.item(self.twSubdivision.currentItem().r
ow(),5).text())

33
def btn_Edit_clicked(self):

sh_descr = self.leShDescr.text()
f_descr = self.leFull.text()
numb_room= self.leNumberRoom.text()
sup = self.leSupervisor.text()
fin = self.leFin.text()

caretaker.backup()

m.updateSubdivision(int(self.twSubdivision.item(self.twSubdivision.currentIte
m().row(),0).text()),f_descr,sh_descr,numb_room,sup,fin)

def btnDelclicked(self):
caretaker.backup()
if
m.deleteSubdivison(int(self.twSubdivision.item(self.twSubdivision.currentItem
().row(),0).text()))==False:
QtWidgets.QMessageBox.warning(self,"Ошибка удаления","Для того
что бы удалить данное подазделение - сначала удалитевсю связанную с ним
вычислителью технику")

def btn_Add_clicked(self):
caretaker.backup()
sh_descr = self.leShDescr.text()
f_descr = self.leFull.text()
numb_room= self.leNumberRoom.text()
sup = self.leSupervisor.text()
fin = self.leFin.text()
twnumcols = len(m.getAllSubdivision())
if twnumcols == 0:
k=1
else:
k = int(self.twSubdivision.item(twnumcols-1,0).text())+1
m.newSubdivision(k,f_descr,sh_descr, numb_room,sup,fin)

def update(self, m:Model):


data = m.getAllSubdivision()
numrows = len(data)
numcols = 6
self.twSubdivision.setColumnCount(numcols)
self.twSubdivision.setRowCount(numrows)
for index, item in enumerate(data):

self.twSubdivision.setItem(index,0,QtWidgets.QTableWidgetItem(str(data[index]
.number_of_subdivision)))

self.twSubdivision.setItem(index,1,QtWidgets.QTableWidgetItem(str(data[index]
.fullname_of_subdivision)))

self.twSubdivision.setItem(index,2,QtWidgets.QTableWidgetItem(str(data[index]
.shortname_of_subdivision)))

self.twSubdivision.setItem(index,3,QtWidgets.QTableWidgetItem(str(data[index]
.number_of_chamber)))

self.twSubdivision.setItem(index,4,QtWidgets.QTableWidgetItem(str(data[index]
.supervisor)))

34
self.twSubdivision.setItem(index,5,QtWidgets.QTableWidgetItem(str(data[index]
.financially_responsible)))

class View_Equipment(QtWidgets.QWidget):
def __init__(self):
super(View_Equipment, self).__init__()
uic.loadUi("UI/view_equipment.ui", self)
m.addListener(self.update)
self.update(m)

self.twEquip.setHorizontalHeaderLabels(("Инв.номер;Название;Модель;Дата
покупки;Цена;Кабинет").split(";"))

self.bAdd.clicked.connect(self.btn_Add_clicked)

def btn_Add_clicked(self):

sub = self.cbSub.currentText()
inv = self.leInv.text()
model = self.leModel.text()
name = self.leName.text()
d = self.deDate.date().toPyDate()
price = self.lePrice.text()
caretaker.backup()
for item in m.getAllSubdivision():
if str(item.number_of_subdivision)==sub:
tr_sub=item

m.newEquipment(tr_sub,name,model,d,inv,price)

def update(self, m:Model):


self.cbSub.clear()
subs = m.getAllSubdivision()
for item in subs:
self.cbSub.addItem(str(item.number_of_subdivision))
data = m.getAllEquipment()
numrows = len(data)
numcols = 6
self.twEquip.setColumnCount(numcols)
self.twEquip.setRowCount(numrows)
for index, item in enumerate(data):

self.twEquip.setItem(index,0,QtWidgets.QTableWidgetItem(str(data[index].get_i
nventory_number())))

self.twEquip.setItem(index,1,QtWidgets.QTableWidgetItem(str(data[index].get_n
ame_of_equipment())))

self.twEquip.setItem(index,2,QtWidgets.QTableWidgetItem(str(data[index].get_m
odel_of_equipment())))

self.twEquip.setItem(index,3,QtWidgets.QTableWidgetItem(str(data[index].get_d
ate_of_purchase())))

35
self.twEquip.setItem(index,4,QtWidgets.QTableWidgetItem(str(data[index].get_p
rice_of_equipment())))

self.twEquip.setItem(index,5,QtWidgets.QTableWidgetItem(str(data[index].get_s
ubdivision().number_of_chamber)))

class View_Move(QtWidgets.QWidget):
def __init__(self):
super(View_Move, self).__init__()
uic.loadUi("UI/view_move.ui", self)
m.addListener(self.update)
self.update(m)

self.twMove.setHorizontalHeaderLabels(("Дата;Инв.номер;Из
подразделения;Из кабинета;В подразделение;В кабинет;Новый МОЛ").split(";"))

self.bAdd.clicked.connect(self.btn_Add_clicked)

self.cbEquip.currentIndexChanged[str].connect(self.cb_Equip_Index_changed)

def cb_Equip_Index_changed(self,item_selected):

subs = m.getAllSubdivision()
inv = self.cbEquip.currentText()
eq = ""
for item in m.getAllEquipment():

if str(item.get_inventory_number()) == item_selected:
eq = item.get_subdivision().number_of_subdivision
self.cbTo.clear()

for item in subs:


if item.number_of_subdivision != eq:
self.cbTo.addItem(str(item.number_of_subdivision))

def btn_Add_clicked(self):

eq = self.cbEquip.currentText()

for item in m.getAllEquipment():


if item.get_inventory_number() == eq:
equip = item
from_s = item.get_subdivision()
from_ch = item.get_subdivision()

to_s = self.cbTo.currentText()

for item in m.getAllSubdivision():


if str(item.number_of_subdivision) == to_s:
to_sub = item
to_ch = item
fin = item

caretaker.backup()
m.newMoveEquipment(equip,to_sub,fin,to_ch,from_s,from_ch)

def update(self, m:Model):


self.cbEquip.clear()

36
equip = m.getAllEquipment()

for item in equip:


self.cbEquip.addItem(str(item.get_inventory_number()))
data = m.getAllMove()
numrows = len(data)
numcols = 7
self.twMove.setColumnCount(numcols)
self.twMove.setRowCount(numrows)
for index, item in enumerate(data):

self.twMove.setItem(index,0,QtWidgets.QTableWidgetItem(str(data[index].get_da
te_of_move())))

self.twMove.setItem(index,1,QtWidgets.QTableWidgetItem(str(data[index].get_eq
uip().get_inventory_number())))

self.twMove.setItem(index,2,QtWidgets.QTableWidgetItem(str(data[index].get_fr
om_subdivision().number_of_subdivision)))

self.twMove.setItem(index,3,QtWidgets.QTableWidgetItem(str(data[index].get_fr
om_of_chamber().number_of_chamber)))

self.twMove.setItem(index,4,QtWidgets.QTableWidgetItem(str(data[index].get_to
_subdivision().number_of_subdivision)))

self.twMove.setItem(index,5,QtWidgets.QTableWidgetItem(str(data[index].get_to
_chamber().number_of_chamber)))

self.twMove.setItem(index,6,QtWidgets.QTableWidgetItem(str(data[index].get_fi
nancially_responsible().financially_responsible)))

class operatorView(QtWidgets.QWidget):
def __init__(self):
super(operatorView, self).__init__()
uic.loadUi("UI/operatorview.ui", self)
self.sub_v = View_Subdivision()
self.eq_v = View_Equipment()
self.m_v = View_Move()

self.tabWidget.insertTab(1, self.sub_v, "Подразделения")


self.tabWidget.insertTab(2, self.eq_v, "Техника")
self.tabWidget.insertTab(3, self.m_v, "Перемещение техники")

self.btnRestore.clicked.connect(self.btnRestore_clicked)

def btnRestore_clicked(self):
caretaker.undo()

if __name__ == '__main__':
m = Model()
caretaker = CareTaker(m)
app = QtWidgets.QApplication(sys.argv)
window = loginView()

37
window.show()
app.exec()

38

Вам также может понравиться