Implement search

This commit is contained in:
Yuri Becker
2025-04-24 01:37:09 +02:00
parent 45f19d80d0
commit 76fdc880c7
14 changed files with 125 additions and 50 deletions

View File

@@ -12,6 +12,9 @@ ColumnLayout {
spacing: Dimensions.l spacing: Dimensions.l
SearchBar { SearchBar {
onSubmitted: (query) => {
applicantModel.searchQuery = query
}
} }
QuickFilter { QuickFilter {
model: ListModel { model: ListModel {

View File

@@ -4,11 +4,23 @@ import QtQuick.Layouts
TextField { TextField {
id: field
signal submitted(query: string)
Layout.preferredWidth: 300 Layout.preferredWidth: 300
placeholderText: qsTr("Suche") placeholderText: qsTr("Suche")
Keys.onReturnPressed: {
field.submitted(field.text);
}
Button { Button {
icon.source: "qrc:/images/MagnifyingGlass.svg" icon.source: "qrc:/images/MagnifyingGlass.svg"
isFieldButton: true isFieldButton: true
onClicked: {
field.submitted(field.text)
}
} }
} }

14
docker-compose.yml Normal file
View File

@@ -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:

19
lib/Config.py Normal file
View File

@@ -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

View File

@@ -2,21 +2,24 @@
import os import os
from base64 import b64encode from base64 import b64encode
from pathlib import Path from pathlib import Path
from typing import Optional
from urllib.parse import urlparse from urllib.parse import urlparse
import toml import toml
from Crypto.Random import get_random_bytes from Crypto.Random import get_random_bytes
from PySide6.QtCore import QObject, Slot, Signal from PySide6.QtCore import QObject, Slot, Signal
from peewee import OperationalError
from platformdirs import user_config_dir from platformdirs import user_config_dir
from .Config import Config, DatabaseConfig
from .DB.UserManager import UserManager from .DB.UserManager import UserManager
from .PyqcrmFlags import PyqcrmFlags from .PyqcrmFlags import PyqcrmFlags
from .Vermasseln import Vermasseln from .Vermasseln import Vermasseln
from .domain.BaseModel import database from .domain.BaseModel import init_database_from_config
class ConfigLoader(QObject): class ConfigLoader(QObject):
__config = None __config: Optional[Config] = None
__version = "0.1-alpha" __version = "0.1-alpha"
__check_enc_key = True __check_enc_key = True
@@ -39,34 +42,34 @@ class ConfigLoader(QObject):
config_dir.mkdir(0o750, True, True) config_dir.mkdir(0o750, True, True)
@Slot(dict, result=bool) @Slot(dict, result=bool)
def setConfig(self, app_config): def setConfig(self, app_config: Config):
if not self.__config: if not self.__config:
base_conf = self.__initializeConfig() base_conf = self.__initializeConfig()
conf = self.__checkDbConnection(app_config) conf = self._is_db_connectable(app_config['database'])
app_config = toml.dumps(app_config) 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: if conf:
app_config = base_conf + app_config self.configurationReady.emit()
self.__config = toml.loads(app_config)
self.__saveConfig()
conf = self.__checkAdminUser()
if conf:
self.configurationReady.emit()
def __initializeConfig(self): def __initializeConfig(self):
# print(f"In {__file__} file, __initializeConfig()")
self.__encrypt_key = b64encode(get_random_bytes(32)).decode("utf-8") self.__encrypt_key = b64encode(get_random_bytes(32)).decode("utf-8")
conf = f"[pyqcrm]\nVERSION = \"{self.__version}\"\n" conf = f"[pyqcrm]\nVERSION = \"{self.__version}\"\n"
conf = conf + f"ENCRYPTION_KEY_VALID = \"No\"\n" conf = conf + f"ENCRYPTION_KEY_VALID = \"No\"\n"
conf = conf + f"ENCRYPTION_KEY = \"{self.__encrypt_key}\"\n\n" conf = conf + f"ENCRYPTION_KEY = \"{self.__encrypt_key}\"\n\n"
return conf return conf
def __checkDbConnection(self): def _is_db_connectable(self, config: DatabaseConfig):
if database.is_closed(): try:
init_database_from_config(config)
self.dbConnectionError.emit("Connection OK", True) self.dbConnectionError.emit("Connection OK", True)
return True return True
else: except OperationalError as e:
self.dbConnectionError.emit("Connection fehlgeschlagen", False) self.dbConnectionError.emit("Connection fehlgeschlagen", False)
return False return False
def __saveConfig(self): def __saveConfig(self):
# print(f"In {__file__} file, saveConfig()") # print(f"In {__file__} file, saveConfig()")
@@ -190,7 +193,6 @@ class ConfigLoader(QObject):
print(str(e)) print(str(e))
def __configLoad(self): def __configLoad(self):
# print(f"In {__file__} file, __configLoad()")
try: try:
with open(self.config_dir + '/pyqcrm.toml', 'r') as f: with open(self.config_dir + '/pyqcrm.toml', 'r') as f:
config = f.read() config = f.read()
@@ -200,16 +202,11 @@ class ConfigLoader(QObject):
print("Konnte die Konfiguration nicht laden.") print("Konnte die Konfiguration nicht laden.")
except TypeError: except TypeError:
print(f"Invalid Configuration: {__file__}") print(f"Invalid Configuration: {__file__}")
except Exception as e:
print(str(e))
def getConfig(self): def get_config(self) -> Optional[Config]:
# print(f"In {__file__} file, getConfig()")
# print(self.__config)
return self.__config return self.__config
def __setRecoveryPassword(self, key, salt=None): def __setRecoveryPassword(self, key, salt=None):
# print(f"In {__file__} file, __setRecoveryPassword()")
key = Vermasseln.userPasswordHash(key, salt) key = Vermasseln.userPasswordHash(key, salt)
return key.split("$") return key.split("$")
@@ -222,7 +219,7 @@ class ConfigLoader(QObject):
@Slot(str, str) @Slot(str, str)
def backupConfig(self, filename, password): def backupConfig(self, filename, password):
conf_file = toml.dumps(self.getConfig()) conf_file = toml.dumps(self.get_config())
self.__saveData(filename, password, conf_file) self.__saveData(filename, password, conf_file)
@Slot(dict) @Slot(dict)
@@ -233,7 +230,7 @@ class ConfigLoader(QObject):
@Slot(result=dict) @Slot(result=dict)
def getDbConf(self): def getDbConf(self):
try: try:
return self.__config['database'] return self.__config['database'] if self.__config is not None else None
except KeyError as ex: except KeyError as ex:
print(f"Missing database configuration: {ex}") print(f"Missing database configuration: {ex}")
return None return None

View File

@@ -28,9 +28,6 @@ class AddressModel(QAbstractListModel):
PyqcrmDataRoles.CITY_ROLE: b"city", PyqcrmDataRoles.CITY_ROLE: b"city",
} }
def setData(self):
pass
@Slot(bool, str) @Slot(bool, str)
def getAddresses(self, all, zipcode): def getAddresses(self, all, zipcode):
data = AddressDAO().getAddressData(all, zipcode) data = AddressDAO().getAddressData(all, zipcode)

View File

@@ -1,7 +1,7 @@
import uuid import uuid
from typing import List, Callable, Any 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 PySide6.QtQml import QJSValue
from peewee import Select from peewee import Select
@@ -19,10 +19,12 @@ COLUMN_NAMES = ["Vorname", "Nachname", "PLZ", "Ort"]
class ApplicantModel(QAbstractTableModel): class ApplicantModel(QAbstractTableModel):
_applicants: Select _applicants: Select
_search_query: str = ""
search_query_changed = Signal(str)
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()
self._applicants = Applicant.select_table_data() self._query_applicants()
def rowCount(self, /, parent=...): def rowCount(self, /, parent=...):
return len(self._applicants) return len(self._applicants)
@@ -36,6 +38,15 @@ class ApplicantModel(QAbstractTableModel):
return COLUMNS[index.column()](applicant) return COLUMNS[index.column()](applicant)
return None 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) @Slot(int, result=dict)
def applicant(self, row) -> dict: def applicant(self, row) -> dict:
applicant = Applicant.get_by_id(self._applicants[row].id) 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.email_address = values.property("emailAddress").toString() or None
applicant.salutation = values.property("salutation").toString() or None applicant.salutation = values.property("salutation").toString() or None
applicant.save(force_insert=True) 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): def headerData(self, section: int, orientation: Qt.Orientation, role: int = Qt.ItemDataRole.DisplayRole):
if role == Qt.ItemDataRole.DisplayRole: if role == Qt.ItemDataRole.DisplayRole:
return COLUMN_NAMES[section] return COLUMN_NAMES[section]
return None return None
def _query_applicants(self):
self._applicants = Applicant.select_table_data(self._search_query)
self.layoutChanged.emit()

View File

@@ -73,7 +73,7 @@ class BusinessModel(QAbstractTableModel):
super().__init__() super().__init__()
self.__business_dao = BusinessDAO() self.__business_dao = BusinessDAO()
self.__business_dao.newBusinessAdded.connect(self.__refreshView) self.__business_dao.newBusinessAdded.connect(self.__refreshView)
self.__conf = ConfigLoader().getConfig() self.__conf = ConfigLoader().get_config()
self.__key = self.__conf['pyqcrm']['ENCRYPTION_KEY'] self.__key = self.__conf['pyqcrm']['ENCRYPTION_KEY']
self.__getData() self.__getData()

View File

@@ -14,7 +14,7 @@ class ContactModel(QObject):
super().__init__() super().__init__()
# print(f"*** File: {__file__}, __init__()") # print(f"*** File: {__file__}, __init__()")
#self.logger = logging.getLogger() #self.logger = logging.getLogger()
self.__conf = ConfigLoader().getConfig() self.__conf = ConfigLoader().get_config()
self.__key = self.__conf['pyqcrm']['ENCRYPTION_KEY'] self.__key = self.__conf['pyqcrm']['ENCRYPTION_KEY']
self.__contact_dao = ContactDAO() self.__contact_dao = ContactDAO()
self.__contact_dao.newObjectContactAdded.connect(self.objectContactAdded) self.__contact_dao.newObjectContactAdded.connect(self.objectContactAdded)

View File

@@ -19,7 +19,7 @@ class EmployeeModel(QAbstractTableModel):
super().__init__() super().__init__()
self.__employee_dao = EmployeeDAO() self.__employee_dao = EmployeeDAO()
self.__employee_dao.newEmployeeAdded.connect(self.__refreshView) self.__employee_dao.newEmployeeAdded.connect(self.__refreshView)
self.__conf = ConfigLoader().getConfig() self.__conf = ConfigLoader().get_config()
self.__key = self.__conf['pyqcrm']['ENCRYPTION_KEY'] self.__key = self.__conf['pyqcrm']['ENCRYPTION_KEY']
self.__getData() self.__getData()

View File

@@ -22,7 +22,7 @@ class ObjectModel(QAbstractTableModel):
super().__init__() super().__init__()
self.__object_dao = ObjectDAO() self.__object_dao = ObjectDAO()
self.__object_dao.newObjectAdded.connect(self.__refreshView) self.__object_dao.newObjectAdded.connect(self.__refreshView)
self.__conf = ConfigLoader().getConfig() self.__conf = ConfigLoader().get_config()
self.__key = self.__conf['pyqcrm']['ENCRYPTION_KEY'] self.__key = self.__conf['pyqcrm']['ENCRYPTION_KEY']
self.__object_dao.newObjectAdded.connect(self.objectAdded) self.__object_dao.newObjectAdded.connect(self.objectAdded)
self.__getData() self.__getData()

View File

@@ -22,10 +22,23 @@ class Applicant(BaseModel):
salutation = TextField(null=False) salutation = TextField(null=False)
@classmethod @classmethod
def select_table_data(cls): def select_table_data(cls, search_query: str):
return ( return (
Applicant 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(ZipCode, join_type="LEFT JOIN")
.join(Town, 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))
)
) )

View File

@@ -1,6 +1,20 @@
from peewee import Model, MySQLDatabase 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): class BaseModel(Model):

15
main.py
View File

@@ -22,7 +22,7 @@ from lib.DB.EmployeeModel import EmployeeModel
from lib.DB.ObjectModel import ObjectModel from lib.DB.ObjectModel import ObjectModel
from lib.DB.UserManager import UserManager from lib.DB.UserManager import UserManager
from lib.Printers import Printers 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' os.environ['QML_XHR_ALLOW_FILE_READ'] = '1'
@@ -42,16 +42,7 @@ user = None
def initializeProgram(): def initializeProgram():
global address_model, applicant_model, bad_config, business_model, user, business_type, contact_model, employee_model, object_model, db_con, printers 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: if not bad_config:
dbconf = config.getConfig()['database'] init_database_from_config(config.get_config()['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()
printers = Printers() printers = Printers()
if not database.is_closed(): if not database.is_closed():
@@ -114,7 +105,7 @@ if __name__ == "__main__":
config = ConfigLoader() config = ConfigLoader()
if not config.getConfig(): if not config.get_config():
bad_config = True bad_config = True
config.configurationReady.connect(configReady) config.configurationReady.connect(configReady)
else: else: