merged command line and gui app in one command
@@ -95,6 +95,9 @@ venv.bak/
|
|||||||
.spyderproject
|
.spyderproject
|
||||||
.spyproject
|
.spyproject
|
||||||
|
|
||||||
|
# PyCharm
|
||||||
|
.idea/
|
||||||
|
|
||||||
# Rope project settings
|
# Rope project settings
|
||||||
.ropeproject
|
.ropeproject
|
||||||
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
|
|
||||||
from cement import Controller, ex
|
|
||||||
from cement.utils.version import get_version_banner
|
|
||||||
from ..core.version import get_version
|
|
||||||
|
|
||||||
VERSION_BANNER = """
|
|
||||||
Kontor CLI Tool %s
|
|
||||||
%s
|
|
||||||
""" % (get_version(), get_version_banner())
|
|
||||||
|
|
||||||
|
|
||||||
class CliBase(Controller):
|
|
||||||
class Meta:
|
|
||||||
label = 'base'
|
|
||||||
|
|
||||||
# text displayed at the top of --help output
|
|
||||||
description = 'Kontor CLI Tool'
|
|
||||||
|
|
||||||
# text displayed at the bottom of --help output
|
|
||||||
epilog = 'Usage: kontor command1 --foo bar'
|
|
||||||
|
|
||||||
# controller level arguments. ex: 'kontor --version'
|
|
||||||
arguments = [
|
|
||||||
### add a version banner
|
|
||||||
( [ '-v', '--version' ],
|
|
||||||
{ 'action' : 'version',
|
|
||||||
'version' : VERSION_BANNER } ),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def _default(self):
|
|
||||||
"""Default action if no sub-command is passed."""
|
|
||||||
|
|
||||||
self.app.args.print_help()
|
|
||||||
|
|
||||||
|
|
||||||
@ex(
|
|
||||||
help='example sub command1',
|
|
||||||
|
|
||||||
# sub-command level arguments. ex: 'kontor command1 --foo bar'
|
|
||||||
arguments=[
|
|
||||||
### add a sample foo option under subcommand namespace
|
|
||||||
( [ '-f', '--foo' ],
|
|
||||||
{ 'help' : 'notorious foo option',
|
|
||||||
'action' : 'store',
|
|
||||||
'dest' : 'foo' } ),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def command1(self):
|
|
||||||
"""Example sub-command."""
|
|
||||||
|
|
||||||
data = {
|
|
||||||
'foo' : 'bar',
|
|
||||||
}
|
|
||||||
|
|
||||||
### do something with arguments
|
|
||||||
if self.app.pargs.foo is not None:
|
|
||||||
data['foo'] = self.app.pargs.foo
|
|
||||||
|
|
||||||
self.app.render(data, 'command1.jinja2')
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
from sqlalchemy import Column, String, DateTime, Integer
|
|
||||||
from sqlalchemy.orm import DeclarativeBase, relationship, sessionmaker, declarative_base
|
|
||||||
|
|
||||||
|
|
||||||
# class Base(DeclarativeBase):
|
|
||||||
# pass
|
|
||||||
|
|
||||||
class BaseModel:
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def model_lookup_by_table_name(cls, table_name):
|
|
||||||
registry_instance = getattr(cls, "registry")
|
|
||||||
for mapper_ in registry_instance.mappers:
|
|
||||||
model = mapper_.class_
|
|
||||||
model_class_name = model.__tablename__
|
|
||||||
if model_class_name == table_name:
|
|
||||||
return model
|
|
||||||
|
|
||||||
|
|
||||||
Base = declarative_base(cls=BaseModel)
|
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
from PySide6.QtWidgets import QApplication
|
||||||
|
from cement import Controller, ex
|
||||||
|
from cement.utils.version import get_version_banner
|
||||||
|
from ..core.version import get_version
|
||||||
|
from ..gui.main_window import MainWindow
|
||||||
|
|
||||||
|
VERSION_BANNER = """
|
||||||
|
Kontor CLI Tool %s
|
||||||
|
%s
|
||||||
|
""" % (get_version(), get_version_banner())
|
||||||
|
|
||||||
|
|
||||||
|
class CliBase(Controller):
|
||||||
|
class Meta:
|
||||||
|
label = 'base'
|
||||||
|
|
||||||
|
# text displayed at the top of --help output
|
||||||
|
description = 'Kontor CLI Tool'
|
||||||
|
|
||||||
|
# text displayed at the bottom of --help output
|
||||||
|
epilog = 'Usage: kontor gui|database'
|
||||||
|
|
||||||
|
# controller level arguments. ex: 'kontor --version'
|
||||||
|
arguments = [
|
||||||
|
### add a version banner
|
||||||
|
(['-v', '--version'],
|
||||||
|
{'action': 'version',
|
||||||
|
'version': VERSION_BANNER}),
|
||||||
|
]
|
||||||
|
|
||||||
|
def _default(self):
|
||||||
|
"""Default action if no sub-command is passed."""
|
||||||
|
self.gui()
|
||||||
|
|
||||||
|
@ex(
|
||||||
|
help='start GUI'
|
||||||
|
)
|
||||||
|
def gui(self):
|
||||||
|
application = QApplication([])
|
||||||
|
window = MainWindow(self.app.session, self.app.log)
|
||||||
|
window.show()
|
||||||
|
application.exec()
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
from cement import Controller, ex
|
||||||
|
|
||||||
|
from ..database import KontorDB
|
||||||
|
|
||||||
|
|
||||||
|
class Database(Controller):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
label = 'database'
|
||||||
|
stacked_type = 'nested'
|
||||||
|
stacked_on = 'base'
|
||||||
|
|
||||||
|
@ex(
|
||||||
|
help='export database to given file',
|
||||||
|
arguments=[
|
||||||
|
(['-f', '--file'],
|
||||||
|
{'help': 'file to store database content',
|
||||||
|
'action': 'store',
|
||||||
|
'dest': 'db_file'})
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def export(self):
|
||||||
|
data = {
|
||||||
|
'db_file': 'data.json',
|
||||||
|
'export_type': 'JSON',
|
||||||
|
}
|
||||||
|
if self.app.pargs.db_file is not None:
|
||||||
|
data['db_file'] = self.app.pargs.db_file
|
||||||
|
kontor_db = KontorDB(self.app.session, self.app.log)
|
||||||
|
table_list = kontor_db.get_table_names()
|
||||||
|
kontor_db.export_db(data['export_type'], data['db_file'], table_list)
|
||||||
|
self.app.render(data, 'command1.jinja2')
|
||||||
@@ -2,37 +2,40 @@ import json
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import mariadb
|
|
||||||
from sqlalchemy import create_engine, select, text, MetaData, join
|
|
||||||
from sqlalchemy.orm import DeclarativeBase, relationship, sessionmaker
|
|
||||||
|
|
||||||
from .base import Base
|
from .base import Base
|
||||||
from .comic import Comic
|
from .comic import Comic, Artist, Publisher, Issue, StoryArc, TradePaperback, Volume, ComicWork, WorkType
|
||||||
from .metadata import MetaDataTable, MetaDataColumn
|
from .metadata import MetaDataTable, MetaDataColumn
|
||||||
|
from .tysc import Card, CardSet, Sport, Team, FieldPosition, Rooster, Player, Vendor
|
||||||
|
from .media import MediaFile
|
||||||
|
|
||||||
|
|
||||||
class KontorDB:
|
class KontorDB:
|
||||||
|
|
||||||
def __init__(self, db_config):
|
def __init__(self, db_session, log):
|
||||||
self.db_conn = mariadb.connect(
|
self.session = db_session
|
||||||
host=db_config['mariadb']['host'],
|
self.log = log
|
||||||
port=db_config['mariadb']['port'],
|
self.registry = {}
|
||||||
user=db_config['mariadb']['user'],
|
self.init_registry()
|
||||||
password=db_config['mariadb']['password'],
|
|
||||||
database=db_config['mariadb']['database']
|
def init_registry(self):
|
||||||
)
|
self.registry['card'] = Card
|
||||||
connect_string = ('mariadb+mariadbconnector://{}:{}@{}:{}/{}'
|
self.registry['card_set'] = CardSet
|
||||||
.format(
|
self.registry['sport'] = Sport
|
||||||
db_config['mariadb']['user'],
|
self.registry['team'] = Team
|
||||||
db_config['mariadb']['password'],
|
self.registry['field_position'] = FieldPosition
|
||||||
db_config['mariadb']['host'],
|
self.registry['rooster'] = Rooster
|
||||||
db_config['mariadb']['port'],
|
self.registry['player'] = Player
|
||||||
db_config['mariadb']['database']))
|
self.registry['vendor'] = Vendor
|
||||||
# engine = create_engine(connect_string, echo=True)
|
self.registry['artist'] = Artist
|
||||||
engine = create_engine(connect_string)
|
self.registry['publisher'] = Publisher
|
||||||
Base.metadata.create_all(bind=engine)
|
self.registry['comic'] = Comic
|
||||||
__session__ = sessionmaker(bind=engine)
|
self.registry['issue'] = Issue
|
||||||
self.session = __session__()
|
self.registry['story_arc'] = StoryArc
|
||||||
|
self.registry['trade_paperback'] = TradePaperback
|
||||||
|
self.registry['volume'] = Volume
|
||||||
|
self.registry['comic_work'] = ComicWork
|
||||||
|
self.registry['worktype'] = WorkType
|
||||||
|
self.registry['media_file'] = MediaFile
|
||||||
|
|
||||||
def get_table_names(self) -> list:
|
def get_table_names(self) -> list:
|
||||||
tables = self.session.query(MetaDataTable).all()
|
tables = self.session.query(MetaDataTable).all()
|
||||||
@@ -69,7 +72,7 @@ class KontorDB:
|
|||||||
filter(MetaDataTable.table_name == table_name).
|
filter(MetaDataTable.table_name == table_name).
|
||||||
filter(MetaDataColumn.show_filter == 1).all()):
|
filter(MetaDataColumn.show_filter == 1).all()):
|
||||||
_filter_map[column.column_name] = {'label': column.filter_label, 'widget': None}
|
_filter_map[column.column_name] = {'label': column.filter_label, 'widget': None}
|
||||||
print(f"retrieved {len(_filter_map)} filters: {_filter_map}")
|
self.log.info(f"retrieved {len(_filter_map)} filters: {_filter_map}")
|
||||||
return _filter_map
|
return _filter_map
|
||||||
|
|
||||||
def data(self, table, columns: dict, filters) -> list:
|
def data(self, table, columns: dict, filters) -> list:
|
||||||
@@ -96,67 +99,27 @@ class KontorDB:
|
|||||||
data.append(row)
|
data.append(row)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def get_data(self, table_name: str, columns: dict, where_clause: str) -> list:
|
|
||||||
data = []
|
|
||||||
cursor = self.db_conn.cursor()
|
|
||||||
cursor.execute(self.get_statement(table_name, columns, where_clause))
|
|
||||||
rows = cursor.fetchall()
|
|
||||||
print(len(rows))
|
|
||||||
for row in rows:
|
|
||||||
# print(f"KontorDB.get_data: {row}")
|
|
||||||
data.append(list(row))
|
|
||||||
cursor.close()
|
|
||||||
# print(f"KontorDB.getData: return {len(data)}")
|
|
||||||
if table_name == 'comic' and len(where_clause) == 0:
|
|
||||||
data.clear()
|
|
||||||
comics = self.session.query(Comic).all()
|
|
||||||
for item in comics:
|
|
||||||
# print(item)
|
|
||||||
row = []
|
|
||||||
for order in columns.keys():
|
|
||||||
column_name = columns[order]['column']
|
|
||||||
if str(column_name).endswith("_id"):
|
|
||||||
ref_table = column_name[:-3]
|
|
||||||
# print(f"{ref_table=}")
|
|
||||||
ref = getattr(item, ref_table)
|
|
||||||
value = getattr(ref, "name")
|
|
||||||
# print(f"{value=}")
|
|
||||||
row.append(value)
|
|
||||||
else:
|
|
||||||
row.append(getattr(item, column_name))
|
|
||||||
# print(repr(row))
|
|
||||||
data.append(row)
|
|
||||||
return data
|
|
||||||
|
|
||||||
def get_statement(self, table: str, header: dict, where_clause):
|
|
||||||
columns = ""
|
|
||||||
for index, column in header.items():
|
|
||||||
if index > 0:
|
|
||||||
columns += ", "
|
|
||||||
columns += column['column']
|
|
||||||
if len(columns) == 0:
|
|
||||||
columns = "*"
|
|
||||||
statement = f"SELECT {columns} FROM {table} {where_clause}"
|
|
||||||
print(f"{statement=}")
|
|
||||||
return statement
|
|
||||||
|
|
||||||
def export_db(self, export_type: str, export_file_name: str, export_table_list: list):
|
def export_db(self, export_type: str, export_file_name: str, export_table_list: list):
|
||||||
print(f"export DB to {export_file_name} as {export_type}")
|
self.log.info(f"export DB to {export_file_name} as {export_type}")
|
||||||
db = {}
|
db = {}
|
||||||
for table in export_table_list:
|
for table in export_table_list:
|
||||||
columns = self.get_column_meta_data(table, view_only=False)
|
columns = self.get_column_meta_data(table, view_only=False)
|
||||||
model = Base.model_lookup_by_table_name(table)
|
if table in self.registry:
|
||||||
|
model = self.registry[table]
|
||||||
|
else:
|
||||||
|
print(f"table {table} is not registered")
|
||||||
|
continue
|
||||||
rows = self.session.query(model).all()
|
rows = self.session.query(model).all()
|
||||||
entries = []
|
entries = []
|
||||||
print(f"found {len(rows)} entries")
|
self.log.debug(f"found {len(rows)} entries")
|
||||||
print(f"found {len(columns)} columns")
|
self.log.debug(f"found {len(columns)} columns")
|
||||||
for row in rows:
|
for row in rows:
|
||||||
print(row)
|
# print(row)
|
||||||
entry = {}
|
entry = {}
|
||||||
for order in columns:
|
for order in columns:
|
||||||
print(columns[order])
|
# print(columns[order])
|
||||||
column_name = columns[order]['column']
|
column_name = columns[order]['column']
|
||||||
print(f"get value {column_name} from {row} of table {table}")
|
# print(f"get value {column_name} from {row} of table {table}")
|
||||||
try:
|
try:
|
||||||
value = getattr(row, column_name)
|
value = getattr(row, column_name)
|
||||||
if isinstance(value, datetime):
|
if isinstance(value, datetime):
|
||||||
@@ -164,7 +127,7 @@ class KontorDB:
|
|||||||
else:
|
else:
|
||||||
entry[column_name] = value
|
entry[column_name] = value
|
||||||
except AttributeError as error:
|
except AttributeError as error:
|
||||||
print("could not get value")
|
self.log.debug("could not get value")
|
||||||
entries.append(entry)
|
entries.append(entry)
|
||||||
db[table] = entries
|
db[table] = entries
|
||||||
export_file = Path(export_file_name)
|
export_file = Path(export_file_name)
|
||||||
@@ -178,6 +141,6 @@ class KontorDB:
|
|||||||
case "SQLite":
|
case "SQLite":
|
||||||
export_file = Path(export_file_name)
|
export_file = Path(export_file_name)
|
||||||
case _:
|
case _:
|
||||||
print("unknown export type")
|
self.log.debug("unknown export type")
|
||||||
if export_file.exists():
|
if export_file.exists():
|
||||||
print(f"{export_file} exists")
|
self.log.debug(f"{export_file} exists")
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
from sqlalchemy.orm import DeclarativeBase
|
||||||
|
|
||||||
|
|
||||||
|
class Base(DeclarativeBase):
|
||||||
|
pass
|
||||||
@@ -20,6 +20,7 @@ class MetaDataTable(Base):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'{self.table_name}({self.id})'
|
return f'{self.table_name}({self.id})'
|
||||||
|
|
||||||
|
|
||||||
class MetaDataColumn(Base):
|
class MetaDataColumn(Base):
|
||||||
__tablename__ = 'meta_data_column'
|
__tablename__ = 'meta_data_column'
|
||||||
id = Column(String, primary_key=True)
|
id = Column(String, primary_key=True)
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
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 .dialogs import ExportKontorDialog, ImportKontorDialog
|
||||||
|
from .model_config import KontorModelConfig
|
||||||
|
from .table_model import KontorTableModel
|
||||||
|
|
||||||
|
|
||||||
|
class MainWindow(QMainWindow):
|
||||||
|
|
||||||
|
def __init__(self, session, log):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.tick = QIcon('kontor/res/tick.png')
|
||||||
|
self.cross = QIcon('kontor/res/cross.png')
|
||||||
|
self.import_icon = QIcon("kontor/res/application-import.png")
|
||||||
|
self.export_icon = QIcon("kontor/res/application-export.png")
|
||||||
|
self.circle_icon = QIcon("kontor/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(session, 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", 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():
|
||||||
|
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, 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
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
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 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
|
||||||
@@ -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
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
from cement import App, TestApp, init_defaults
|
from cement import App, TestApp, init_defaults
|
||||||
from cement.core.exc import CaughtSignal
|
from cement.core.exc import CaughtSignal
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
@@ -7,6 +6,7 @@ from sqlalchemy.orm import sessionmaker
|
|||||||
from .core.exc import KontorError
|
from .core.exc import KontorError
|
||||||
from .database.base import Base
|
from .database.base import Base
|
||||||
from .controllers.clibase import CliBase
|
from .controllers.clibase import CliBase
|
||||||
|
from .controllers.database import Database
|
||||||
|
|
||||||
# configuration defaults
|
# configuration defaults
|
||||||
CONFIG = init_defaults('kontor', 'mariadb')
|
CONFIG = init_defaults('kontor', 'mariadb')
|
||||||
@@ -17,6 +17,7 @@ CONFIG['mariadb']['host'] = '127.0.0.1'
|
|||||||
CONFIG['mariadb']['port'] = '3306'
|
CONFIG['mariadb']['port'] = '3306'
|
||||||
CONFIG['mariadb']['database'] = 'kontor'
|
CONFIG['mariadb']['database'] = 'kontor'
|
||||||
|
|
||||||
|
|
||||||
def extend_sqlalchemy(app):
|
def extend_sqlalchemy(app):
|
||||||
app.log.info('extending kontor application with sqlalchemy')
|
app.log.info('extending kontor application with sqlalchemy')
|
||||||
connect_string = ('mariadb+mariadbconnector://{}:{}@{}:{}/{}'.format(
|
connect_string = ('mariadb+mariadbconnector://{}:{}@{}:{}/{}'.format(
|
||||||
@@ -33,6 +34,11 @@ def extend_sqlalchemy(app):
|
|||||||
app.extend('session', __session__())
|
app.extend('session', __session__())
|
||||||
|
|
||||||
|
|
||||||
|
def close_session(app):
|
||||||
|
app.log.info('close session')
|
||||||
|
app.session.close()
|
||||||
|
|
||||||
|
|
||||||
class Kontor(App):
|
class Kontor(App):
|
||||||
"""Kontor primary application."""
|
"""Kontor primary application."""
|
||||||
|
|
||||||
@@ -66,14 +72,16 @@ class Kontor(App):
|
|||||||
|
|
||||||
hooks = [
|
hooks = [
|
||||||
('post_setup', extend_sqlalchemy),
|
('post_setup', extend_sqlalchemy),
|
||||||
|
('pre_close', close_session),
|
||||||
]
|
]
|
||||||
# register handlers
|
# register handlers
|
||||||
handlers = [
|
handlers = [
|
||||||
CliBase
|
CliBase,
|
||||||
|
Database,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class KontorTest(TestApp,Kontor):
|
class KontorTest(TestApp, Kontor):
|
||||||
"""A sub-class of Kontor that is better suited for testing."""
|
"""A sub-class of Kontor that is better suited for testing."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
Before Width: | Height: | Size: 513 B After Width: | Height: | Size: 513 B |
|
Before Width: | Height: | Size: 524 B After Width: | Height: | Size: 524 B |
|
Before Width: | Height: | Size: 836 B After Width: | Height: | Size: 836 B |
|
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 544 B |
|
Before Width: | Height: | Size: 634 B After Width: | Height: | Size: 634 B |
@@ -1,16 +0,0 @@
|
|||||||
# This is a sample Python script.
|
|
||||||
|
|
||||||
# Press Umschalt+F10 to execute it or replace it with your code.
|
|
||||||
# Press Double Shift to search everywhere for classes, files, tool windows, actions, and settings.
|
|
||||||
|
|
||||||
|
|
||||||
def print_hi(name):
|
|
||||||
# Use a breakpoint in the code line below to debug your script.
|
|
||||||
print(f'Hi, {name}') # Press Strg+F8 to toggle the breakpoint.
|
|
||||||
|
|
||||||
|
|
||||||
# Press the green button in the gutter to run the script.
|
|
||||||
if __name__ == '__main__':
|
|
||||||
print_hi('PyCharm')
|
|
||||||
|
|
||||||
# See PyCharm help at https://www.jetbrains.com/help/pycharm/
|
|
||||||
@@ -4,3 +4,4 @@ cement[yaml]
|
|||||||
cement[colorlog]
|
cement[colorlog]
|
||||||
mariadb
|
mariadb
|
||||||
sqlalchemy
|
sqlalchemy
|
||||||
|
PySide6
|
||||||
@@ -28,7 +28,7 @@ class Team(Base):
|
|||||||
name = Column(String(255), nullable=False, index=True, unique=True)
|
name = Column(String(255), nullable=False, index=True, unique=True)
|
||||||
short_name = Column(String(255), nullable=False, )
|
short_name = Column(String(255), nullable=False, )
|
||||||
sport_id = Column(String, ForeignKey("sport.id"), nullable=False)
|
sport_id = Column(String, ForeignKey("sport.id"), nullable=False)
|
||||||
sport = relationship("Sport", back_populates="positions")
|
sport = relationship("Sport", back_populates="teams")
|
||||||
roosters = relationship("Rooster")
|
roosters = relationship("Rooster")
|
||||||
|
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ class Rooster(Base):
|
|||||||
player_id = Column(String, ForeignKey("player.id"), nullable=False, index=True)
|
player_id = Column(String, ForeignKey("player.id"), nullable=False, index=True)
|
||||||
player = relationship("Player", back_populates="roosters")
|
player = relationship("Player", back_populates="roosters")
|
||||||
position_id = Column(String, ForeignKey("field_position.id"), nullable=False, index=True)
|
position_id = Column(String, ForeignKey("field_position.id"), nullable=False, index=True)
|
||||||
position = relationship("roosters")
|
position = relationship("FieldPosition", back_populates="roosters")
|
||||||
cards = relationship("Card")
|
cards = relationship("Card")
|
||||||
|
|
||||||
class Vendor(Base):
|
class Vendor(Base):
|
||||||
@@ -124,8 +124,8 @@ class Card(Base):
|
|||||||
card_number = Column(Integer, index=True)
|
card_number = Column(Integer, index=True)
|
||||||
year = Column(Integer, index=True)
|
year = Column(Integer, index=True)
|
||||||
card_set_id = Column(String, ForeignKey("card_set.id"), nullable=False)
|
card_set_id = Column(String, ForeignKey("card_set.id"), nullable=False)
|
||||||
card_set = relationship("cards")
|
card_set = relationship("CardSet", back_populates="cards")
|
||||||
rooster_id = Column(String, ForeignKey("rooster.id"), nullable=False)
|
rooster_id = Column(String, ForeignKey("rooster.id"), nullable=False)
|
||||||
rooster = relationship("cards")
|
rooster = relationship("Rooster", back_populates="cards")
|
||||||
vendor_id = Column(String, ForeignKey("vendor.id"), nullable=False)
|
vendor_id = Column(String, ForeignKey("vendor.id"), nullable=False)
|
||||||
vendor = relationship("Vendor", back_populates="cards")
|
vendor = relationship("Vendor", back_populates="cards")
|
||||||
@@ -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}")
|
||||||
|
After Width: | Height: | Size: 513 B |
|
After Width: | Height: | Size: 524 B |
|
After Width: | Height: | Size: 836 B |
|
After Width: | Height: | Size: 544 B |
|
After Width: | Height: | Size: 634 B |