add cli app and fix relationship typos

This commit is contained in:
Thomas Peetz
2025-01-13 22:54:25 +01:00
parent d0eae1980a
commit f74c07af9a
54 changed files with 1138 additions and 5 deletions
View File
+12
View File
@@ -0,0 +1,12 @@
from abc import ABC, abstractmethod
class DataViewMeta(ABC):
@abstractmethod
def get_header(self):
pass
class ComicView(DataViewMeta):
def get_header(self):
pass
+32
View File
@@ -0,0 +1,32 @@
from typing import List
from PySide6.QtCore import QModelIndex, QAbstractTableModel
from PySide6.QtGui import Qt
from gui.data_view import DataViewMeta
class DataViewModel(QAbstractTableModel):
def __init__(self):
super().__init__()
self.main_window = None
self._config = None
self._data = List[DataViewMeta]
def rowCount(self, parent=QModelIndex()):
return len(self._data)
def columnCount(self, parent=QModelIndex()):
return 0
def headerData(self, section, orientation, role=Qt.ItemDataRole.DisplayRole):
return None
def data(self, index, role=Qt.ItemDataRole.DisplayRole):
return None
def setData(self, index, value, role=Qt.ItemDataRole.EditRole):
return False
def flags(self, index):
return None
+106
View File
@@ -0,0 +1,106 @@
from pathlib import Path
from PySide6.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QLabel, QHBoxLayout, QPushButton, QFileDialog, \
QCheckBox, QComboBox
class ExportKontorDialog(QDialog):
def __init__(self, parent=None, kontor_db=None):
super().__init__(parent)
self.parent = parent
self.kontor_db = kontor_db
self.file_name = "data.json"
self.tables = []
self._table_options = {}
self.export_options = {"JSON": {"ext": ".json"}, "YAML": {"ext": ".yaml"}, "SQLite": {"ext": ".db"}}
self.current_export_type = "JSON"
buttons = (QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
self.buttonBox = QDialogButtonBox(buttons)
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
layout = QVBoxLayout()
self.label = QLabel()
self.label.setText("Export DB to data.json")
self.combo_box = QComboBox()
self.combo_box.addItems(["JSON", "YAML", "SQLite"])
self.combo_box.currentTextChanged.connect(self.change_export_type)
file_layout = QHBoxLayout()
file_layout.addWidget(self.label)
file_layout.addWidget(self.combo_box)
file_button = QPushButton("Select file")
file_button.clicked.connect(self.select_file)
file_layout.addWidget(file_button)
layout.addLayout(file_layout)
for table_name in self.kontor_db.get_table_names():
check_box = QCheckBox(table_name)
check_box.setChecked(True)
self.tables.append(table_name)
self._table_options[table_name] = check_box
check_box.stateChanged.connect(self.change_selection)
layout.addWidget(check_box)
layout.addWidget(self.buttonBox)
self.setLayout(layout)
def change_selection(self):
self.tables.clear()
for (name, box) in self._table_options.items():
if box.isChecked():
self.tables.append(name)
def change_export_type(self, text):
self.current_export_type = text
self.label.setText(f'Export DB to data.{self.export_options[text]["ext"]}')
def select_file(self):
file_dialog = QFileDialog()
file_dialog.setFileMode(QFileDialog.FileMode.AnyFile)
file_dialog.setDefaultSuffix(self.export_options[self.current_export_type]["ext"])
file_dialog.setNameFilter(f'*{self.export_options[self.current_export_type]["ext"]}')
if file_dialog.exec():
self.file_name = file_dialog.selectedFiles()[0]
export_file = Path(self.file_name)
self.file_name = export_file.with_suffix(self.export_options[self.current_export_type]["ext"])
self.label.setText(f"Export DB to {self.file_name}")
def get_tables_to_export(self) -> list:
return self.tables
class ImportKontorDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.file_name = None
QBtn = (QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
self.buttonBox = QDialogButtonBox(QBtn)
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
self.label = QLabel()
self.label.setText("Import DB from data.json")
layout = QVBoxLayout()
file_layout = QHBoxLayout()
file_layout.addWidget(self.label)
file_button = QPushButton("Select file")
file_button.clicked.connect(self.select_file)
file_layout.addWidget(file_button)
layout.addLayout(file_layout)
layout.addWidget(self.buttonBox)
self.setLayout(layout)
def select_file(self):
file_dialog = QFileDialog()
file_dialog.setFileMode(QFileDialog.FileMode.ExistingFile)
if file_dialog.exec():
self.file_name = file_dialog.selectedFiles()[0]
self.label.setText(f"Import DB from {self.file_name}")
+138
View File
@@ -0,0 +1,138 @@
from PySide6.QtGui import QAction, QIcon
from PySide6.QtWidgets import QWidget, QVBoxLayout, QMenu, QMessageBox, QTabWidget, QTableView
from PySide6.QtWidgets import QLabel, QMainWindow
from database import KontorDB
from database.media import MediaFile
from database.comic import Comic
from gui.dialogs import ExportKontorDialog, ImportKontorDialog
from gui.model_config import KontorModelConfig
from gui.table_model import KontorTableModel
class MainWindow(QMainWindow):
def __init__(self, config):
super().__init__()
self.tick = QIcon('res/tick.png')
self.cross = QIcon('res/cross.png')
self.import_icon = QIcon("res/application-import.png")
self.export_icon = QIcon("res/application-export.png")
self.circle_icon = QIcon("res/arrow-circle-double.png")
self.setWindowTitle("Kontor")
self.setMinimumSize(800, 500)
self._create_actions()
self._create_menubar()
self._create_toolbars()
self._create_statusbar()
self.data = []
self.filter = {}
self.kontor_db = KontorDB(config)
self.central_widget = QWidget()
parent_layout = QVBoxLayout()
self.central_widget.setLayout(parent_layout)
self.tabs = QTabWidget()
self.tabs.addTab(self.generate_data_tab("comic", Comic), "Comics")
self.tabs.addTab(self.generate_data_tab("media_file", MediaFile), "MediaFile")
self.tabs.currentChanged.connect(self._tab_changed)
#label.setAlignment(Qt.AlignmentFlag.AlignCenter)
parent_layout.addWidget(self.tabs)
self.setCentralWidget(self.central_widget)
def _create_actions(self):
self.newAction = QAction("&New", self)
self.aboutAction = QAction("&Über...", self)
self.aboutAction.triggered.connect(self.about)
self.importAction = QAction(self.import_icon, "&Import", self)
self.importAction.triggered.connect(self.import_from_file)
self.exportAction = QAction(self.export_icon, "&Export", self)
self.exportAction.triggered.connect(self.export_to_file)
self.refreshAction = QAction(self.circle_icon, "&Refresh", self)
self.refreshAction.triggered.connect(self.refresh)
self.updateTitleAction = QAction("&Update Titles", self)
self.downloadAction = QAction("&Download Videos", self)
self.exitAction = QAction("&Beenden", self)
self.exitAction.setShortcut("Alt+F4")
self.exitAction.triggered.connect(self.close)
def _create_menubar(self):
menu_bar = self.menuBar()
# File menu
file_menu = QMenu("&Datei")
menu_bar.addMenu(file_menu)
file_menu.addAction(self.exitAction)
# Kontor menu
kontor_menu = QMenu("&Kontor")
menu_bar.addMenu(kontor_menu)
kontor_menu.addAction(self.importAction)
kontor_menu.addAction(self.exportAction)
comic_menu = QMenu("&Comic")
tysc_menu = QMenu("&TradeYourSportCards")
media_file_menu = QMenu("&MediaFile")
media_file_menu.addAction(self.updateTitleAction)
media_file_menu.addAction(self.downloadAction)
kontor_menu.addMenu(comic_menu)
kontor_menu.addMenu(tysc_menu)
kontor_menu.addMenu(media_file_menu)
# Help menu
help_menu = QMenu("&Hilfe")
menu_bar.addMenu(help_menu)
help_menu.addAction(self.aboutAction)
def _create_toolbars(self):
# Kontor toolbar
kontor_tool_bar = self.addToolBar("Kontor")
kontor_tool_bar.addAction(self.importAction)
kontor_tool_bar.addAction(self.exportAction)
kontor_tool_bar.addAction(self.refreshAction)
def _create_statusbar(self):
self.statusBar = self.statusBar()
self.statusBar.showMessage("Kontor ready", 6000)
self.status_label = QLabel("")
self.statusBar.addPermanentWidget(self.status_label)
def about(self):
QMessageBox.about(self.central_widget, "Über Kontor", f"Python: 3.11\nKontor: 0.1.0")
def import_from_file(self):
import_dlg = ImportKontorDialog(self)
if import_dlg.exec():
print(f"import DB from file {import_dlg.file_name}")
else:
print("no nothing for import")
pass
def export_to_file(self):
export_dlg = ExportKontorDialog(self, self.kontor_db)
if export_dlg.exec():
print(export_dlg.get_tables_to_export())
print(f"export DB to {export_dlg.file_name}")
self.statusBar.showMessage(f"export DB to {export_dlg.file_name}", 3000)
self.kontor_db.export_db(export_dlg.current_export_type, export_dlg.file_name, export_dlg.get_tables_to_export())
else:
self.statusBar.showMessage("Export cancelled", 3000)
def refresh(self):
self.data[self.tabs.currentIndex()].refresh()
def _tab_changed(self, tab_index):
self.data[tab_index].refresh()
def generate_data_tab(self, table_name, table):
data_tab = QWidget()
table_config = KontorModelConfig(self.kontor_db, self, table_name, table)
model = KontorTableModel(table_config)
layout = QVBoxLayout()
self.data.append(model)
data_tab.setLayout(layout)
table_view = QTableView()
table_view.setModel(model)
layout.addLayout(table_config.get_filter_layout())
layout.addWidget(table_view)
model.refresh()
return data_tab
+65
View File
@@ -0,0 +1,65 @@
import mariadb
from PySide6.QtWidgets import QHBoxLayout, QCheckBox
from database import KontorDB
class KontorModelConfig:
def __init__(self, kontor_db: KontorDB, main_window, table_name: str, table):
self.header = {}
self.filter = {}
self.main_window = main_window
self._table_name = table_name
self._table = table
self.kontor_db = kontor_db
self.get_table_config()
def get_table_config(self):
self.header = self.kontor_db.get_column_meta_data(self._table_name)
self.filter = self.kontor_db.get_filters(self._table_name)
def get_filter(self) -> str:
filter_rule = ""
# print(self.filter["download"].isChecked())
for column, filter_info in self.filter.items():
# print(column, filter_info)
if filter_info['widget'].isChecked():
# print(column, filter_info, filter_rule, len(filter_rule))
if len(filter_rule) < 1:
filter_rule += "WHERE "
if len(filter_rule) > 8:
filter_rule += " AND "
filter_rule += f"{column} is true"
# print(f"{filter_rule=}")
return filter_rule
def filters(self) -> dict:
_filters = {}
# print(self.filter["download"].isChecked())
for column, filter_info in self.filter.items():
# print(column, filter_info)
if filter_info['widget'].isChecked():
_filters[column] = True
# print(f"{filter_rule=}")
return _filters
def get_data(self) -> list:
# data = self.kontor_db.get_data(self._table_name, self.header, self.get_filter())
# data.clear()
data = self.kontor_db.data(self._table, self.header, self.filters())
# print(f"KontorModelConfig.get_data: {len(data)}")
# comics = self.kontor_db.session.query(Comic).all()
# print(f'{len(comics)} Comics loaded')
return data
def get_filter_layout(self) -> QHBoxLayout:
filter_layout = QHBoxLayout()
for column, filter_info in self.filter.items():
filter_checkbox = QCheckBox()
filter_checkbox.setText(filter_info['label'])
filter_checkbox.checkStateChanged.connect(self.main_window.refresh)
self.filter[column]['widget'] = filter_checkbox
filter_layout.addWidget(filter_checkbox)
filter_layout.addStretch()
return filter_layout
+108
View File
@@ -0,0 +1,108 @@
from datetime import datetime
from PySide6.QtCore import QAbstractTableModel, QModelIndex
from PySide6.QtGui import Qt
from gui.model_config import KontorModelConfig
class KontorTableModel(QAbstractTableModel):
def __init__(self, model_config: KontorModelConfig):
super().__init__()
self._main_window = model_config.main_window
self._config = model_config
self._data = []
def refresh(self):
data = self._config.get_data()
count = 0
# print(data)
if data is not None:
self.beginResetModel()
self._data.clear()
self._data = data
self.endResetModel()
count = len(data)
# print(data)
# print(self._data)
self.layoutChanged.emit()
self._main_window.statusBar.showMessage(f"{count} Einträge geladen", 3000)
def rowCount(self, parent=QModelIndex()):
# The length of the outer list.
if self._data is None:
return 0
return len(self._data)
def headerData(self, col, orientation, role=Qt.ItemDataRole.DisplayRole):
if orientation == Qt.Orientation.Horizontal and role == Qt.ItemDataRole.DisplayRole:
return self._config.header[col]['label']
if orientation == Qt.Orientation.Vertical and role == Qt.ItemDataRole.DisplayRole:
return str(col+1)
def data(self, index, role=Qt.ItemDataRole.DisplayRole):
if self._data is None:
return None
value = self._data[index.row()][index.column()]
# print('{}:: {}:: {}: {}'.format(index, role, value, type(value)))
if role == Qt.ItemDataRole.DisplayRole or role == Qt.ItemDataRole.EditRole:
if isinstance(value, datetime):
return value.strftime("%Y-%m-%d %M:%M:%S")
if isinstance(value, str):
return value
if isinstance(value, bytes):
if value == b'\x01':
return self._main_window.tick
else:
return self._main_window.cross
if isinstance(value, int):
# print('{}:: {}: {}'.format(index, value, type(value)))
if value == 1:
return self._main_window.tick
else:
return self._main_window.cross
if isinstance(value, bool):
if value:
return self._main_window.tick
else:
return self._main_window.cross
return str(value)
if role == Qt.ItemDataRole.DecorationRole:
if isinstance(value, bytes):
if value == b'\x01':
return self._main_window.tick
else:
return self._main_window.cross
if isinstance(value, int):
if value == 1:
return self._main_window.tick
else:
return self._main_window.cross
if isinstance(value, bool):
if value:
return self._main_window.tick
else:
return self._main_window.cross
def columnCount(self, index=QModelIndex()):
# The following takes the first sub-list, and returns
# the length (only works if all rows are an equal length)
# print(f"Header count: {len(self._config.get_header())}")
return len(self._config.header)
def setData(self, index, value, role: int) -> bool:
print(index, role)
if role == Qt.ItemDataRole.EditRole:
self._data[index.row()][index.column()] = value
print(self._data[index.row()][index.column()])
self.dataChanged.emit(index, index)
return True
if role == Qt.ItemDataRole.CheckStateRole:
print("role == Qt.ItemDataRole.CheckStateRole")
checked = value == Qt.CheckState.Checked
self._data[index.row()][index.column()] = checked
return False
def flags(self, index):
return Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsEditable | Qt.ItemFlag.ItemIsUserTristate