separate cli and gui application in own python packages. provide database schema as python package.
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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}")
|
||||
@@ -0,0 +1,169 @@
|
||||
from PySide6.QtGui import QAction, QIcon
|
||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QMenu, QMessageBox, QTabWidget, QTableView, QProgressBar
|
||||
from PySide6.QtWidgets import QLabel, QMainWindow
|
||||
from sqlalchemy import Engine
|
||||
from kontor_schema import KontorDB
|
||||
|
||||
from .progress import ProgressUpdate
|
||||
from .dialogs import ExportKontorDialog, ImportKontorDialog
|
||||
from .model_config import KontorModelConfig
|
||||
from .table_model import KontorTableModel
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
|
||||
def __init__(self, engine: Engine, log):
|
||||
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.status_progress = QProgressBar()
|
||||
self.progress_update = ProgressUpdate(self.status_progress)
|
||||
self._create_statusbar()
|
||||
|
||||
self.data = []
|
||||
self.filter = {}
|
||||
self.kontor_db = KontorDB(engine, log)
|
||||
self.log = log
|
||||
self.central_widget = QWidget()
|
||||
parent_layout = QVBoxLayout()
|
||||
self.central_widget.setLayout(parent_layout)
|
||||
self.tabs = QTabWidget()
|
||||
self.tabs.addTab(self.generate_data_tab("comic"), "Comics")
|
||||
self.tabs.addTab(self.generate_data_tab("media_file"), "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.updateTitleAction.triggered.connect(self.update_title)
|
||||
self.downloadAction = QAction("&Download Videos", self)
|
||||
self.downloadAction.triggered.connect(self.download_file)
|
||||
self.checkFileAction = QAction("&Check files", self)
|
||||
self.checkFileAction.triggered.connect(self.check_files)
|
||||
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)
|
||||
media_file_menu.addAction(self.checkFileAction)
|
||||
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.status_progress.setEnabled(False)
|
||||
self.statusBar.addPermanentWidget(self.status_progress)
|
||||
|
||||
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():
|
||||
self.log.info(export_dlg.get_tables_to_export())
|
||||
self.log.info(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)
|
||||
else:
|
||||
self.statusBar.showMessage("Export cancelled", 3000)
|
||||
|
||||
def update_title(self):
|
||||
self.log.info("update title for table MediaFile")
|
||||
self.statusBar.showMessage("update title for table MediaFile", 3000)
|
||||
self.status_progress.setEnabled(True)
|
||||
self.kontor_db.update_title()
|
||||
self.status_progress.setEnabled(False)
|
||||
self.refresh()
|
||||
|
||||
def download_file(self):
|
||||
self.log.info("download videos for table MediaFile")
|
||||
self.statusBar.showMessage("download videos for table MediaFile", 3000)
|
||||
self.status_progress.setEnabled(True)
|
||||
self.kontor_db.download_file(False, self.progress_update)
|
||||
self.status_progress.setEnabled(False)
|
||||
self.refresh()
|
||||
|
||||
def check_files(self):
|
||||
self.log.info("check files")
|
||||
self.statusBar.showMessage("check files for table MediaFile", 3000)
|
||||
self.kontor_db.check_files()
|
||||
|
||||
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):
|
||||
data_tab = QWidget()
|
||||
|
||||
table_config = KontorModelConfig(self.kontor_db, self, table_name)
|
||||
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
|
||||
@@ -0,0 +1,47 @@
|
||||
from PySide6.QtWidgets import QHBoxLayout, QCheckBox
|
||||
from kontor_schema import KontorDB
|
||||
|
||||
|
||||
class KontorModelConfig:
|
||||
|
||||
def __init__(self, kontor_db: KontorDB, main_window, table_name: str):
|
||||
self.header = {}
|
||||
self.filter = {}
|
||||
self.main_window = main_window
|
||||
self._table_name = table_name
|
||||
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 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_name, 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
|
||||
@@ -0,0 +1,18 @@
|
||||
from PySide6.QtWidgets import QProgressBar
|
||||
|
||||
|
||||
class ProgressUpdate:
|
||||
def __init__(self, progress: QProgressBar):
|
||||
self.start = 0
|
||||
self.end = 0
|
||||
self.current = 0
|
||||
self.progress = progress
|
||||
|
||||
def start(self, start_value, end_value):
|
||||
self.start = start_value
|
||||
self.end = end_value
|
||||
self.current = start_value
|
||||
self.progress.update()
|
||||
|
||||
def update(self, current):
|
||||
self.progress.update()
|
||||
@@ -0,0 +1,108 @@
|
||||
from datetime import datetime
|
||||
|
||||
from PySide6.QtCore import QAbstractTableModel, QModelIndex
|
||||
from PySide6.QtGui import Qt
|
||||
|
||||
from .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
|
||||
Reference in New Issue
Block a user