diff --git a/qt/database/__init__.py b/qt/database/__init__.py index c0191cc..d74e927 100644 --- a/qt/database/__init__.py +++ b/qt/database/__init__.py @@ -1,3 +1,7 @@ +import json +from datetime import datetime +from pathlib import Path + import mariadb from sqlalchemy import create_engine, select, text, MetaData, join from sqlalchemy.orm import DeclarativeBase, relationship, sessionmaker @@ -30,35 +34,67 @@ class KontorDB: __session__ = sessionmaker(bind=engine) self.session = __session__() - def get_table_id(self, table_name): - result = self.session.execute(select(MetaDataTable.id).where(MetaDataTable.table_name == table_name)).scalar() - return result - def get_table_names(self) -> list: tables = self.session.query(MetaDataTable).all() result = [table.table_name for table in tables] return result - def get_column_meta_data(self, table_id: str, table_name: str) -> dict: + def get_column_meta_data(self, table_name: str, view_only=True) -> dict: meta_data = {} order = 0 - for (_, column) in self.session.query(MetaDataTable, MetaDataColumn).filter(MetaDataTable.id == MetaDataColumn.table_id).filter(MetaDataTable.table_name == table_name).filter(MetaDataColumn.is_shown == 1).all(): - meta_data[order] = {'column': column.column_name, 'label': column.column_label, 'order': column.column_order, 'ref_column': column.ref_column} - order += 1 + if view_only: + for (_, column) in (self.session.query(MetaDataTable, MetaDataColumn). + filter(MetaDataTable.id == MetaDataColumn.table_id). + filter(MetaDataTable.table_name == table_name). + filter(MetaDataColumn.is_shown == 1).all()): + meta_data[order] = {'column': column.column_name, 'label': column.column_label, + 'order': column.column_order, 'ref_column': column.ref_column} + order += 1 + else: + for (_, column) in (self.session.query(MetaDataTable, MetaDataColumn). + filter(MetaDataTable.id == MetaDataColumn.table_id). + filter(MetaDataTable.table_name == table_name).all()): + meta_data[order] = { + 'column': column.column_name, + 'order': column.column_order, + 'ref_column': column.ref_column + } + order += 1 return meta_data - def get_filters(self, table_id): - cursor = self.db_conn.cursor() - filters = {} - cursor.execute( - "SELECT column_name, filter_label from meta_data_column WHERE table_id=? AND show_filter is true", - (table_id,)) - rows = cursor.fetchall() - for row in rows: - filters[row[0]] = {'label': row[1], 'widget': None} - cursor.close() - # print(f"retrieved {len(rows)} filters: {filters}") - return filters + def get_filters(self, table_name): + _filter_map = {} + for (_, column) in (self.session.query(MetaDataTable, MetaDataColumn). + filter(MetaDataTable.id == MetaDataColumn.table_id). + filter(MetaDataTable.table_name == table_name). + filter(MetaDataColumn.show_filter == 1).all()): + _filter_map[column.column_name] = {'label': column.filter_label, 'widget': None} + print(f"retrieved {len(_filter_map)} filters: {_filter_map}") + return _filter_map + + def data(self, table, columns: dict, filters) -> list: + data = [] + entries = [] + if len(filters) == 0: + entries = self.session.query(table).all() + else: + entries = self.session.query(table).filter_by(**filters) + for entry in entries: + 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(entry, ref_table) + value = getattr(ref, "name") + # print(f"{value=}") + row.append(value) + else: + row.append(getattr(entry, column_name)) + # print(repr(row)) + data.append(row) + return data def get_data(self, table_name: str, columns: dict, where_clause: str) -> list: data = [] @@ -103,3 +139,45 @@ class KontorDB: 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): + print(f"export DB to {export_file_name} as {export_type}") + db = {} + for table in export_table_list: + columns = self.get_column_meta_data(table, view_only=False) + model = Base.model_lookup_by_table_name(table) + rows = self.session.query(model).all() + entries = [] + print(f"found {len(rows)} entries") + print(f"found {len(columns)} columns") + for row in rows: + print(row) + entry = {} + for order in columns: + print(columns[order]) + column_name = columns[order]['column'] + print(f"get value {column_name} from {row} of table {table}") + try: + value = getattr(row, column_name) + if isinstance(value, datetime): + entry[column_name] = str(value) + else: + entry[column_name] = value + except AttributeError as error: + print("could not get value") + entries.append(entry) + db[table] = entries + export_file = Path(export_file_name) + match export_type: + case "JSON": + json_dump = json.dumps(db, indent=4) + with open(export_file_name, "w") as dump_file: + dump_file.write(json_dump) + case "YAML": + export_file = Path(export_file_name) + case "SQLite": + export_file = Path(export_file_name) + case _: + print("unknown export type") + if export_file.exists(): + print(f"{export_file} exists") diff --git a/qt/database/base.py b/qt/database/base.py index 9339e51..f97d724 100644 --- a/qt/database/base.py +++ b/qt/database/base.py @@ -1,7 +1,20 @@ from sqlalchemy import Column, String, DateTime, Integer -from sqlalchemy.orm import DeclarativeBase, relationship, sessionmaker +from sqlalchemy.orm import DeclarativeBase, relationship, sessionmaker, declarative_base -class Base(DeclarativeBase): - pass +# 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) \ No newline at end of file diff --git a/qt/database/comic.py b/qt/database/comic.py index dc4204f..1dc31b4 100644 --- a/qt/database/comic.py +++ b/qt/database/comic.py @@ -1,4 +1,4 @@ -from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String +from sqlalchemy import Column, DateTime, ForeignKey, Integer, String from sqlalchemy.dialects.mysql import BIT from sqlalchemy.orm import relationship @@ -106,7 +106,7 @@ class Artist(Base): comic_works = relationship("ComicWork") -class Worktype(Base): +class WorkType(Base): __tablename__ = "worktype" id = Column(String, primary_key=True) created_date = Column(DateTime) @@ -126,5 +126,5 @@ class ComicWork(Base): comic = relationship("Comic", back_populates="comic_works") artist_id = Column(String, ForeignKey("artist.id"), nullable=False) artist = relationship("Artist", back_populates="comic_works") - worktype_id = Column(String, ForeignKey("worktype.id"), nullable=False) - worktype = relationship("Worktype", back_populates="comic_works") + work_type_id = Column(String, ForeignKey("worktype.id"), nullable=False) + work_type = relationship("WorkType", back_populates="comic_works") diff --git a/qt/gui/data_view_model.py b/qt/gui/data_view_model.py index e4e1bab..4ffdc8c 100644 --- a/qt/gui/data_view_model.py +++ b/qt/gui/data_view_model.py @@ -1,7 +1,6 @@ from typing import List -from PyQt5.QtCore import QAbstractTableModel -from PySide6.QtCore import QModelIndex +from PySide6.QtCore import QModelIndex, QAbstractTableModel from PySide6.QtGui import Qt from gui.data_view import DataViewMeta @@ -14,19 +13,19 @@ class DataViewModel(QAbstractTableModel): self._config = None self._data = List[DataViewMeta] - def rowCount(self, parent = QModelIndex()): + def rowCount(self, parent=QModelIndex()): return len(self._data) - def columnCount(self, parent = QModelIndex()): + def columnCount(self, parent=QModelIndex()): return 0 - def headerData(self, section, orientation, role = Qt.ItemDataRole.DisplayRole): + def headerData(self, section, orientation, role=Qt.ItemDataRole.DisplayRole): return None - def data(self, index, role = Qt.ItemDataRole.DisplayRole): + def data(self, index, role=Qt.ItemDataRole.DisplayRole): return None - def setData(self, index, value, role = Qt.ItemDataRole.EditRole): + def setData(self, index, value, role=Qt.ItemDataRole.EditRole): return False def flags(self, index): diff --git a/qt/gui/dialogs.py b/qt/gui/dialogs.py index 703baeb..21dbe92 100644 --- a/qt/gui/dialogs.py +++ b/qt/gui/dialogs.py @@ -1,7 +1,7 @@ from pathlib import Path from PySide6.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QLabel, QHBoxLayout, QPushButton, QFileDialog, \ - QGroupBox, QCheckBox, QComboBox + QCheckBox, QComboBox class ExportKontorDialog(QDialog): @@ -10,7 +10,7 @@ class ExportKontorDialog(QDialog): self.parent = parent self.kontor_db = kontor_db - self.file_name = None + self.file_name = "data.json" self.tables = [] self._table_options = {} diff --git a/qt/gui/main_window.py b/qt/gui/main_window.py index 42b4383..f47abd5 100644 --- a/qt/gui/main_window.py +++ b/qt/gui/main_window.py @@ -3,6 +3,8 @@ from PySide6.QtWidgets import QWidget, QVBoxLayout, QMenu, QMessageBox, QTabWidg 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 @@ -33,8 +35,8 @@ class MainWindow(QMainWindow): 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.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) @@ -111,6 +113,7 @@ class MainWindow(QMainWindow): 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) @@ -120,9 +123,9 @@ class MainWindow(QMainWindow): def _tab_changed(self, tab_index): self.data[tab_index].refresh() - def generate_data_tab(self, table_name): + def generate_data_tab(self, table_name, table): data_tab = QWidget() - table_config = KontorModelConfig(self.kontor_db, self, table_name) + table_config = KontorModelConfig(self.kontor_db, self, table_name, table) model = KontorTableModel(table_config) layout = QVBoxLayout() self.data.append(model) diff --git a/qt/gui/model_config.py b/qt/gui/model_config.py index c2644e9..a3f902d 100644 --- a/qt/gui/model_config.py +++ b/qt/gui/model_config.py @@ -6,25 +6,18 @@ from database import KontorDB class KontorModelConfig: - def __init__(self, kontor_db: KontorDB, main_window, table_name: str): + def __init__(self, kontor_db: KontorDB, main_window, table_name: str, table): self.header = {} self.filter = {} self.main_window = main_window - self._table = table_name - self._table_id = None + self._table_name = table_name + self._table = table self.kontor_db = kontor_db self.get_table_config() - def get_table_id(self): - if self._table_id is not None: - return - self._table_id = self.kontor_db.get_table_id(self._table) - def get_table_config(self): - if self._table_id is None: - self.get_table_id() - self.header = self.kontor_db.get_column_meta_data(self._table_id, self._table) - self.filter = self.kontor_db.get_filters(self._table_id) + 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 = "" @@ -41,8 +34,20 @@ class KontorModelConfig: # 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, self.header, self.get_filter()) + # 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') diff --git a/qt/gui/table_model.py b/qt/gui/table_model.py index e11dacc..e1ba6ae 100644 --- a/qt/gui/table_model.py +++ b/qt/gui/table_model.py @@ -57,7 +57,7 @@ class KontorTableModel(QAbstractTableModel): else: return self._main_window.cross if isinstance(value, int): - print('{}:: {}: {}'.format(index, value, type(value))) + # print('{}:: {}: {}'.format(index, value, type(value))) if value == 1: return self._main_window.tick else: