diff --git a/Gui/Employees/EmployeesTable.qml b/Gui/Employees/EmployeesTable.qml index dc1236d..dbf3f3d 100644 --- a/Gui/Employees/EmployeesTable.qml +++ b/Gui/Employees/EmployeesTable.qml @@ -12,6 +12,9 @@ ColumnLayout { spacing: Dimensions.l SearchBar { + onSubmitted: (query) => { + applicantModel.searchQuery = query + } } QuickFilter { model: ListModel { diff --git a/TeroStyle/SearchBar.qml b/TeroStyle/SearchBar.qml index 72918b5..aab43a9 100644 --- a/TeroStyle/SearchBar.qml +++ b/TeroStyle/SearchBar.qml @@ -4,11 +4,23 @@ import QtQuick.Layouts TextField { + id: field + + signal submitted(query: string) + Layout.preferredWidth: 300 placeholderText: qsTr("Suche") + Keys.onReturnPressed: { + field.submitted(field.text); + } + Button { icon.source: "qrc:/images/MagnifyingGlass.svg" isFieldButton: true + + onClicked: { + field.submitted(field.text) + } } } diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..37edcc6 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,14 @@ +services: + mariadb: + image: mariadb:latest + volumes: + - mariadb:/var/lib/mysql + ports: + - 127.0.0.1:8000:3306 + environment: + MARIADB_ROOT_PASSWORD: pyqcrm + MARIADB_USER: pyqcrm + MARIADB_PASSWORD: pyqcrm + MARIADB_DATABASE: pyqcrm +volumes: + mariadb: diff --git a/lib/Config.py b/lib/Config.py new file mode 100644 index 0000000..8c08232 --- /dev/null +++ b/lib/Config.py @@ -0,0 +1,19 @@ +from typing import TypedDict + + +class EncryptionConfig(TypedDict): + ENCRYPTION_KEY_VALID: str + ENCRYPTION_KEY: str + + +class DatabaseConfig(TypedDict): + DB_HOST: str + DB_USER: str + DB_PASS: str + DB_NAME: str + DB_PORT: str + + +class Config(TypedDict): + database: DatabaseConfig + pyqcrm: EncryptionConfig diff --git a/lib/ConfigLoader.py b/lib/ConfigLoader.py index 8809bc1..b963484 100644 --- a/lib/ConfigLoader.py +++ b/lib/ConfigLoader.py @@ -2,21 +2,24 @@ import os from base64 import b64encode from pathlib import Path +from typing import Optional from urllib.parse import urlparse import toml from Crypto.Random import get_random_bytes from PySide6.QtCore import QObject, Slot, Signal +from peewee import OperationalError from platformdirs import user_config_dir +from .Config import Config, DatabaseConfig from .DB.UserManager import UserManager from .PyqcrmFlags import PyqcrmFlags from .Vermasseln import Vermasseln -from .domain.BaseModel import database +from .domain.BaseModel import init_database_from_config class ConfigLoader(QObject): - __config = None + __config: Optional[Config] = None __version = "0.1-alpha" __check_enc_key = True @@ -39,34 +42,34 @@ class ConfigLoader(QObject): config_dir.mkdir(0o750, True, True) @Slot(dict, result=bool) - def setConfig(self, app_config): + def setConfig(self, app_config: Config): if not self.__config: base_conf = self.__initializeConfig() - conf = self.__checkDbConnection(app_config) - app_config = toml.dumps(app_config) + conf = self._is_db_connectable(app_config['database']) + app_config = toml.dumps(app_config) + if conf: + app_config = base_conf + app_config + self.__config = toml.loads(app_config) + self.__saveConfig() + conf = self.__checkAdminUser() if conf: - app_config = base_conf + app_config - self.__config = toml.loads(app_config) - self.__saveConfig() - conf = self.__checkAdminUser() - if conf: - self.configurationReady.emit() + self.configurationReady.emit() def __initializeConfig(self): - # print(f"In {__file__} file, __initializeConfig()") self.__encrypt_key = b64encode(get_random_bytes(32)).decode("utf-8") conf = f"[pyqcrm]\nVERSION = \"{self.__version}\"\n" conf = conf + f"ENCRYPTION_KEY_VALID = \"No\"\n" conf = conf + f"ENCRYPTION_KEY = \"{self.__encrypt_key}\"\n\n" return conf - def __checkDbConnection(self): - if database.is_closed(): + def _is_db_connectable(self, config: DatabaseConfig): + try: + init_database_from_config(config) self.dbConnectionError.emit("Connection OK", True) return True - else: + except OperationalError as e: self.dbConnectionError.emit("Connection fehlgeschlagen", False) - return False + return False def __saveConfig(self): # print(f"In {__file__} file, saveConfig()") @@ -190,7 +193,6 @@ class ConfigLoader(QObject): print(str(e)) def __configLoad(self): - # print(f"In {__file__} file, __configLoad()") try: with open(self.config_dir + '/pyqcrm.toml', 'r') as f: config = f.read() @@ -200,16 +202,11 @@ class ConfigLoader(QObject): print("Konnte die Konfiguration nicht laden.") except TypeError: print(f"Invalid Configuration: {__file__}") - except Exception as e: - print(str(e)) - def getConfig(self): - # print(f"In {__file__} file, getConfig()") - # print(self.__config) + def get_config(self) -> Optional[Config]: return self.__config def __setRecoveryPassword(self, key, salt=None): - # print(f"In {__file__} file, __setRecoveryPassword()") key = Vermasseln.userPasswordHash(key, salt) return key.split("$") @@ -222,7 +219,7 @@ class ConfigLoader(QObject): @Slot(str, str) def backupConfig(self, filename, password): - conf_file = toml.dumps(self.getConfig()) + conf_file = toml.dumps(self.get_config()) self.__saveData(filename, password, conf_file) @Slot(dict) @@ -233,7 +230,7 @@ class ConfigLoader(QObject): @Slot(result=dict) def getDbConf(self): try: - return self.__config['database'] + return self.__config['database'] if self.__config is not None else None except KeyError as ex: print(f"Missing database configuration: {ex}") return None diff --git a/lib/DB/AddressModel.py b/lib/DB/AddressModel.py index 357e8bd..f5392b2 100644 --- a/lib/DB/AddressModel.py +++ b/lib/DB/AddressModel.py @@ -28,9 +28,6 @@ class AddressModel(QAbstractListModel): PyqcrmDataRoles.CITY_ROLE: b"city", } - def setData(self): - pass - @Slot(bool, str) def getAddresses(self, all, zipcode): data = AddressDAO().getAddressData(all, zipcode) diff --git a/lib/DB/ApplicantModel.py b/lib/DB/ApplicantModel.py index 48745a5..22ab4c6 100644 --- a/lib/DB/ApplicantModel.py +++ b/lib/DB/ApplicantModel.py @@ -1,7 +1,7 @@ import uuid from typing import List, Callable, Any -from PySide6.QtCore import QModelIndex, Qt, QAbstractTableModel, Slot +from PySide6.QtCore import QModelIndex, Qt, QAbstractTableModel, Slot, Property, Signal from PySide6.QtQml import QJSValue from peewee import Select @@ -19,10 +19,12 @@ COLUMN_NAMES = ["Vorname", "Nachname", "PLZ", "Ort"] class ApplicantModel(QAbstractTableModel): _applicants: Select + _search_query: str = "" + search_query_changed = Signal(str) def __init__(self) -> None: super().__init__() - self._applicants = Applicant.select_table_data() + self._query_applicants() def rowCount(self, /, parent=...): return len(self._applicants) @@ -36,6 +38,15 @@ class ApplicantModel(QAbstractTableModel): return COLUMNS[index.column()](applicant) return None + @Property(str, notify=search_query_changed) + def searchQuery(self): + return self._search_query + + @searchQuery.setter + def searchQuery(self, value: str): + self._search_query = value + self._query_applicants() + @Slot(int, result=dict) def applicant(self, row) -> dict: applicant = Applicant.get_by_id(self._applicants[row].id) @@ -68,9 +79,13 @@ class ApplicantModel(QAbstractTableModel): applicant.email_address = values.property("emailAddress").toString() or None applicant.salutation = values.property("salutation").toString() or None applicant.save(force_insert=True) - self._applicants = Applicant.select_table_data() + self._query_applicants() def headerData(self, section: int, orientation: Qt.Orientation, role: int = Qt.ItemDataRole.DisplayRole): if role == Qt.ItemDataRole.DisplayRole: return COLUMN_NAMES[section] return None + + def _query_applicants(self): + self._applicants = Applicant.select_table_data(self._search_query) + self.layoutChanged.emit() diff --git a/lib/DB/BusinessModel.py b/lib/DB/BusinessModel.py index 1207417..682a26d 100644 --- a/lib/DB/BusinessModel.py +++ b/lib/DB/BusinessModel.py @@ -73,7 +73,7 @@ class BusinessModel(QAbstractTableModel): super().__init__() self.__business_dao = BusinessDAO() self.__business_dao.newBusinessAdded.connect(self.__refreshView) - self.__conf = ConfigLoader().getConfig() + self.__conf = ConfigLoader().get_config() self.__key = self.__conf['pyqcrm']['ENCRYPTION_KEY'] self.__getData() diff --git a/lib/DB/ContactModel.py b/lib/DB/ContactModel.py index c4b8155..2f29476 100644 --- a/lib/DB/ContactModel.py +++ b/lib/DB/ContactModel.py @@ -14,7 +14,7 @@ class ContactModel(QObject): super().__init__() # print(f"*** File: {__file__}, __init__()") #self.logger = logging.getLogger() - self.__conf = ConfigLoader().getConfig() + self.__conf = ConfigLoader().get_config() self.__key = self.__conf['pyqcrm']['ENCRYPTION_KEY'] self.__contact_dao = ContactDAO() self.__contact_dao.newObjectContactAdded.connect(self.objectContactAdded) diff --git a/lib/DB/EmployeeModel.py b/lib/DB/EmployeeModel.py index ae60437..08118e8 100644 --- a/lib/DB/EmployeeModel.py +++ b/lib/DB/EmployeeModel.py @@ -19,7 +19,7 @@ class EmployeeModel(QAbstractTableModel): super().__init__() self.__employee_dao = EmployeeDAO() self.__employee_dao.newEmployeeAdded.connect(self.__refreshView) - self.__conf = ConfigLoader().getConfig() + self.__conf = ConfigLoader().get_config() self.__key = self.__conf['pyqcrm']['ENCRYPTION_KEY'] self.__getData() diff --git a/lib/DB/ObjectModel.py b/lib/DB/ObjectModel.py index 34b2d08..86b560c 100644 --- a/lib/DB/ObjectModel.py +++ b/lib/DB/ObjectModel.py @@ -22,7 +22,7 @@ class ObjectModel(QAbstractTableModel): super().__init__() self.__object_dao = ObjectDAO() self.__object_dao.newObjectAdded.connect(self.__refreshView) - self.__conf = ConfigLoader().getConfig() + self.__conf = ConfigLoader().get_config() self.__key = self.__conf['pyqcrm']['ENCRYPTION_KEY'] self.__object_dao.newObjectAdded.connect(self.objectAdded) self.__getData() diff --git a/lib/domain/Applicant.py b/lib/domain/Applicant.py index 0fa4425..dd50c61 100644 --- a/lib/domain/Applicant.py +++ b/lib/domain/Applicant.py @@ -22,10 +22,23 @@ class Applicant(BaseModel): salutation = TextField(null=False) @classmethod - def select_table_data(cls): + def select_table_data(cls, search_query: str): return ( Applicant - .select(Applicant.id, Applicant.first_name, Applicant.last_name, ZipCode.id, ZipCode.zip_code, Town.town) + .select( + Applicant.id, + Applicant.first_name, + Applicant.last_name, + ZipCode.id, + ZipCode.zip_code, + Town.town + ) .join(ZipCode, join_type="LEFT JOIN") .join(Town, join_type="LEFT JOIN") + .where( + (Applicant.first_name.contains(search_query)) | + (Applicant.last_name.contains(search_query)) | + (ZipCode.zip_code.contains(search_query)) | + (Town.town.contains(search_query)) + ) ) diff --git a/lib/domain/BaseModel.py b/lib/domain/BaseModel.py index 56bac14..39326ed 100644 --- a/lib/domain/BaseModel.py +++ b/lib/domain/BaseModel.py @@ -1,6 +1,20 @@ from peewee import Model, MySQLDatabase -database = MySQLDatabase(None) +from lib.Config import DatabaseConfig + +database: MySQLDatabase = MySQLDatabase(None) + + +def init_database_from_config(config: DatabaseConfig): + database.init( + host=config['DB_HOST'], + user=config['DB_USER'], + password=config['DB_PASS'], + database=config['DB_NAME'], + port=int(config['DB_PORT']), + connect_timeout=5, + ) + database.connect() class BaseModel(Model): diff --git a/main.py b/main.py index c16c89f..d0b5335 100644 --- a/main.py +++ b/main.py @@ -22,7 +22,7 @@ from lib.DB.EmployeeModel import EmployeeModel from lib.DB.ObjectModel import ObjectModel from lib.DB.UserManager import UserManager from lib.Printers import Printers -from lib.domain.BaseModel import database +from lib.domain.BaseModel import database, init_database_from_config os.environ['QML_XHR_ALLOW_FILE_READ'] = '1' @@ -42,16 +42,7 @@ user = None def initializeProgram(): global address_model, applicant_model, bad_config, business_model, user, business_type, contact_model, employee_model, object_model, db_con, printers if not bad_config: - dbconf = config.getConfig()['database'] - database.init( - host=dbconf['DB_HOST'], - user=dbconf['DB_USER'], - password=dbconf['DB_PASS'], - database=dbconf['DB_NAME'], - port=int(dbconf['DB_PORT']), - connect_timeout=5, - ) - database.connect() + init_database_from_config(config.get_config()['database']) printers = Printers() if not database.is_closed(): @@ -114,7 +105,7 @@ if __name__ == "__main__": config = ConfigLoader() - if not config.getConfig(): + if not config.get_config(): bad_config = True config.configurationReady.connect(configReady) else: