Implement search
This commit is contained in:
@@ -12,6 +12,9 @@ ColumnLayout {
|
|||||||
spacing: Dimensions.l
|
spacing: Dimensions.l
|
||||||
|
|
||||||
SearchBar {
|
SearchBar {
|
||||||
|
onSubmitted: (query) => {
|
||||||
|
applicantModel.searchQuery = query
|
||||||
|
}
|
||||||
}
|
}
|
||||||
QuickFilter {
|
QuickFilter {
|
||||||
model: ListModel {
|
model: ListModel {
|
||||||
|
|||||||
@@ -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
14
docker-compose.yml
Normal 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
19
lib/Config.py
Normal 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
|
||||||
@@ -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,10 +42,10 @@ 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:
|
if conf:
|
||||||
app_config = base_conf + app_config
|
app_config = base_conf + app_config
|
||||||
@@ -53,18 +56,18 @@ class ConfigLoader(QObject):
|
|||||||
self.configurationReady.emit()
|
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
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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))
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
15
main.py
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user