From 04ad1981e1426da7b89f8ac41f5eca7786ad6b5a5021380244f00d73c26d237c Mon Sep 17 00:00:00 2001 From: linuxero Date: Fri, 21 Mar 2025 16:27:51 +0100 Subject: [PATCH] Adding documentation comments for doxygen (#1) --- Gui/PyqcrmConf.qml | 2 +- lib/ConfigLoader.py | 144 +++++++++++++++++++++++++++++++++++++++- lib/DB/AddressDAO.py | 45 +++++++++++++ lib/DB/AddressModel.py | 56 +++++++++++++++- lib/DB/BTypeModel.py | 47 +++++++++++++ lib/DB/BusinessModel.py | 78 +++++++++++++++++++++- lib/Vermasseln.py | 62 ++++++++++++++++- main.py | 88 +++++++++++++++++++++++- 8 files changed, 513 insertions(+), 9 deletions(-) diff --git a/Gui/PyqcrmConf.qml b/Gui/PyqcrmConf.qml index e95c337..d1f6ed9 100644 --- a/Gui/PyqcrmConf.qml +++ b/Gui/PyqcrmConf.qml @@ -143,7 +143,7 @@ Item if (db['database']['DB_HOST'] === '' || db['database']['DB_PORT'] === '' || db['database']['DB_NAME'] === '' || db['database']['DB_USER'] === '' || db['database']['DB_PASS'] === ''); - else config.saveDbConf(company) + else config.saveDbConf(db) } function updateCompanyInfo() diff --git a/lib/ConfigLoader.py b/lib/ConfigLoader.py index 5867a34..5be28fc 100644 --- a/lib/ConfigLoader.py +++ b/lib/ConfigLoader.py @@ -1,4 +1,56 @@ -# This Python file uses the following encoding: utf-8 +"""! @brief Defines the configuration class.""" +## +# @file ConfigLoader.py +# +# @brief Defines the ConfigLoader class. +# +# @section description_configloader Description +# Defines the base class for the program configuration. +# - ConfigLoader (base class) +# +# @section libraries_configloader Libraries/Modules +# - os standard library +# - Access to system specific information. +# - urllib Python package +# - Collects several modules for working with URLs. +# - toml Python library +# - A Python library for parsing and creating TOML. +# - platformdirs Python package +# - Determining appropriate platform-specific dirs. +# - pathlib Python module +# - Offers classes representing filesystem paths with semantics appropriate for different operating systems. +# - shutil Python module +# - Offers a number of high-level operations on files and collections of files. +# - base64 Python standard library +# - Provides functions for encoding binary data to printable ASCII characters and decoding such encodings back to binary data. +# - QObject PySide6 class +# - The base class of all Qt objects. +# - Slot PySide6 function +# - A function that is called in response to a particular signal. +# - Signal PySide6 class +# - Provides a way to declare and connect Qt signals in a pythonic way. +# - Crypto Python package +# - Provides cryptographic functionalities. +# - DbManager local class +# - Provides a singleton database connection for the entire program. +# - UserManager local class +# - Provides a model to handle users. +# - PyqcrmFlags local ENUM +# - Provides ENUMS to facilitate working in the program. +# - Vermasseln local class +# - Provides encryption functionality for the program. +# +# @section notes_configloader Notes +# - Needs a database connection. +# +# @section todo_configloader TODO +# - None. +# +# @section author_configloader Author(s) +# - Created by Linuxero on 03/14/2025. +# - Modified by Linuxero on 03/14/2025. +# +# Copyright (c) 2025 Schnaxero. All rights reserved. import toml from platformdirs import user_config_dir from pathlib import Path @@ -17,6 +69,10 @@ from .PyqcrmFlags import PyqcrmFlags class ConfigLoader(QObject): + """! The ConfigLoader class. + Defines the class utilized by all different parts of the program. + Handles the local configuration of the whole program. + """ __config = None __version = "0.1-alpha" __check_enc_key = True @@ -30,6 +86,8 @@ class ConfigLoader(QObject): invalidEncryptionKey = Signal() def __init__(self): + """! The ConfigLoader class initializer. + """ super().__init__() # print(f"In {__file__} file, __init__()") self.config_dir = user_config_dir() + '/pyqcrm' @@ -44,6 +102,10 @@ class ConfigLoader(QObject): @Slot(dict, result = bool) def setConfig(self, app_config): + """! Prepares the configuration of the program. + @param app_config The configuration as a dictionary. + @return True on success, False on failure. + """ # print(f"In {__file__} file, setConfig()") if not self.__config: base_conf = self.__initializeConfig() @@ -58,6 +120,8 @@ class ConfigLoader(QObject): self.configurationReady.emit() def __initializeConfig(self): + """! Creates the initial configuration of the program. + """ # print(f"In {__file__} file, __initializeConfig()") self.__encrypt_key = b64encode(get_random_bytes(32)).decode("utf-8") conf = f"[pyqcrm]\nVERSION = \"{self.__version}\"\n" @@ -66,6 +130,10 @@ class ConfigLoader(QObject): return conf def __checkDbConnection(self, db_config): + """! Tests for a valid database connection. + @param db_config The configuration of the database connection as a dictionary. + @return True on success, False on failure. + """ # print(f"In {__file__} file, __checkDbConnection()") con = DbManager(db_config['database']).getConnection() if con: @@ -77,6 +145,8 @@ class ConfigLoader(QObject): def __saveConfig(self): + """! Saves the configuration of the program. + """ # print(f"In {__file__} file, saveConfig()") try: with open (self.config_dir + '/pyqcrm.toml', 'w') as f: @@ -88,6 +158,9 @@ class ConfigLoader(QObject): def __checkAdminUser(self): + """! Checks for a valid admin account of the program. + @return True on success, False on failure. + """ # print(f"In {__file__} file, __checkAdminUser()") result = UserManager().checkAdmin() if not result: @@ -100,6 +173,10 @@ class ConfigLoader(QObject): @Slot(dict, result= bool) def addAdminUser(self, user_config): + """! Adds an admin account. + @param user_config The credentials of the admin account as a dictionary. + @return True on success, False on failure. + """ # print(f"In {__file__} file, addAdminUser()") admin = UserManager(user_config["user"], PyqcrmFlags.ADMIN).createUser() if not admin: @@ -114,6 +191,12 @@ class ConfigLoader(QObject): @Slot(str, str) def __saveData(self, recovery_file, recovery_password, data): + """! Generic function to save backups to a file. + This function emits a configurationReady signal. + @param recovery_file The full path of the backup file. + @param recovery_password password to secure the backup file. + @param data The content of the backup file. + """ # print(f"In {__file__} file, __saveData()") local = False rp = self.__setRecoveryPassword(recovery_password) @@ -133,6 +216,11 @@ class ConfigLoader(QObject): @Slot(str, str) def getRecoveryKey(self, recovery_file, recovery_password): + """! Loads the encryption key from a backup. + This function emits a configurationReady signal. + @param recovery_file The full path of the backup file. + @param recovery_password password to secure the backup file. + """ rec_file = urlparse(recovery_file) rec_file = rec_file.path if os.name == "nt": @@ -150,6 +238,11 @@ class ConfigLoader(QObject): print(str(e)) def __parseImport(self, rec_file, recovery_password): + """! Loads the content from a backup. + @param rec_file The full path of the backup file. + @param recovery_password password used to secure the backup file. + @return The content on success, None on failure. + """ local = False with open(rec_file, "r") as f: @@ -167,23 +260,40 @@ class ConfigLoader(QObject): def __invalidateEncryptionKey(self): + """! Flag the encryption key as invalid. + """ # print(f"In {__file__} file, __invalidateEncryptionKey()") self.__config['pyqcrm']['ENCRYPTION_KEY_VALID'] = 'No' self.__saveConfig() @Slot() def checkEncryptionKey(self): + """! Checks the validity of the encryption key. + This function emits an invalidEncryptionKey signal. + """ # print(f"In {__file__} file, __checkEncryptionKey()") if self.__config['pyqcrm']['ENCRYPTION_KEY_VALID'] == 'No': self.invalidEncryptionKey.emit() def __checkRecoveryPassword(self, recovery_password, password, salt): + """! Generic function to save backups to a file. + This function emits a configurationReady signal. + @param recovery_password The password from the backup file. + @param password The password used when creating the backup file. + @param salt A salt to hash the password. + @return A password. + """ # print(f"In {__file__} file, __checkRecoveryPassword()") rp = self.__setRecoveryPassword(recovery_password, salt) return rp[1] == password @Slot(str, str) # todo: non local encryption def importConfig(self, confile, password): + """! Generic function to import configuration from a backup. + This function emits a invalidEncryptionKey signal. + @param conffile The path of the backup file. + @param password The password used when creating the backup file. + """ confile = urlparse(confile) confile = confile.path if os.name == "nt": @@ -206,6 +316,9 @@ class ConfigLoader(QObject): def __configLoad(self): + """! Loads the program configuration. + This function emits a configurationReady signal. + """ # print(f"In {__file__} file, __configLoad()") try: with open (self.config_dir + '/pyqcrm.toml', 'r') as f: @@ -221,6 +334,9 @@ class ConfigLoader(QObject): def getConfig(self): + """! Returns the program configuration. + @return configuration as a toml file. + """ # print(f"In {__file__} file, getConfig()") # print(self.__config) return self.__config @@ -239,17 +355,27 @@ class ConfigLoader(QObject): @Slot(str, str) def backupConfig(self, filename, password): + """! Saves the program configuration. + @param filename the path of the backup file. + @param password the password to secure the backup. + """ conf_file = toml.dumps(self.getConfig()) self.__saveData(filename, password, conf_file) @Slot(dict) def saveDbConf(self, db = None): + """! Saves/Upates the database configuration. + @param db Database configuration as a dictionary. + """ self.__config.update(db) self.__saveConfig() @Slot(result = dict) def getDbConf(self): + """! Loads the database configuration. + @return Database configuration as a dictionary on success, None on failure. + """ try: return self.__config['database'] except KeyError as ex: @@ -258,11 +384,17 @@ class ConfigLoader(QObject): @Slot(dict) def saveCompanyInfo(self, company = None): + """! Saves/Upates the company information. + @param company Company configuration as a dictionary. + """ self.__config.update(company) self.__saveConfig() @Slot(result = dict) def getCompanyInfo(self): + """! Loads the company information. + @return Company information as a dictionary on success, None on failure. + """ try: return self.__config['company'] except KeyError as ex: @@ -271,11 +403,17 @@ class ConfigLoader(QObject): @Slot(dict) def saveMiscConf(self, misc_conf = None): + """! Saves/Upates the miscellaneous configuration. + @param misc_conf Extra configuration as a dictionary. + """ self.__config.update(misc_conf) self.__saveConfig() @Slot(result = bool) def systray(self): + """! Loads the system tray configuration. + @return boolean if system tray is set to be used, False can also be returned on failure. + """ try: return self.__config['misc']['SYSTRAY'] except KeyError as ex: @@ -285,6 +423,10 @@ class ConfigLoader(QObject): @Slot(str, str) def backupEncryptkey(self, filename, password): + """! Saves/Upates the encryption key. + @param filename Path to save the key. + @param password Password to secure the backup. + """ encrypt_key = self.__config['pyqcrm']['ENCRYPTION_KEY'] self.__saveData(filename, password, encrypt_key) diff --git a/lib/DB/AddressDAO.py b/lib/DB/AddressDAO.py index b2150bd..c2f7cf5 100644 --- a/lib/DB/AddressDAO.py +++ b/lib/DB/AddressDAO.py @@ -1,11 +1,47 @@ +"""! @brief Defines the low-level DAO class to handle addresses.""" +## +# @file AddressDAO.py +# +# @brief Defines the AddressDAO class. +# +# @section description_addressdao Description +# Defines the low-lever DAO class to handle CRUD operations on addresses. +# - AddressDAO (DAO class) +# +# @section libraries_addressdao Libraries/Modules +# - mariadb Python module +# - MariaDB Connector/Python enables python programs to access MariaDB and MySQL databases, using an API which is compliant with the Python DB API 2.0 (PEP-249). +# - json Python standard library +# - JSON encoder and decoder. +# - DbManager Local class +# - Singleton class to provide for the database connection. +# +# @section notes_addressdao Notes +# - None. +# +# @section todo_addressdao TODO +# - None. +# +# @section author_addressdao Author(s) +# - Created by Linuxero on 03/14/2025. +# - Modified by Linuxero on 03/14/2025. +# +# Copyright (c) 2025 Schnaxero. All rights reserved. + from .DbManager import DbManager import mariadb import json class AddressDAO: + """! The AddressDAO class. + Defines a low-level class utilized for DAO. + Handles the address CRUD operations. + """ __cur = None def __init__(self): + """! The AddressDAO class initializer. + """ #print(f"*** File: {__file__}, init()") self.__con = DbManager().getConnection() @@ -16,6 +52,8 @@ class AddressDAO: def __importPlz(self): + """! Utility function to import zipcodes from an external databases. + """ with open("pfad zur datei", "r") as plz: postcodes = json.load(plz) irgendwas = "" @@ -37,6 +75,8 @@ class AddressDAO: print("FINISHED")# def __importCountry(self): + """! Utility function to import countries information from an external databases. + """ with open("pfad zur datei", "r") as country: countries = json.load(country) old = "" @@ -67,6 +107,11 @@ class AddressDAO: def getAddressData(self, all = True, zipcode = None): + """! Loads available addresses in the program. + @param all Filter to get specific addresses as a boolean. + @param zipcode Specific zipcode pattern. + @return Found addresses as a dictionary on success, None on failure. + """ try: if self.__cur: self.__cur.callproc("getAddress", (all, zipcode,)) diff --git a/lib/DB/AddressModel.py b/lib/DB/AddressModel.py index aecd12f..bfdf65d 100644 --- a/lib/DB/AddressModel.py +++ b/lib/DB/AddressModel.py @@ -1,17 +1,63 @@ +"""! @brief Defines the model class to handle addresses.""" +## +# @file AddressModel.py +# +# @brief Defines the AddressModel class. +# +# @section description_addressmodel Description +# Defines the model class to handle CRUD operations on addresses. +# - AddressModel (Model class) +# +# @section libraries_addressmodel Libraries/Modules +# - QAbstractListModel PySid6 core Class +# - Provides an abstract model that can be subclassed to create one-dimensional list models. +# - QModelIndex PySid6 core Class +# - Used to locate data in a data model. +# - Slot PySide6 function +# - A function that is called in response to a particular signal. +# - Qt PySid6 core Class +# - A namespace contains miscellaneous identifiers used throughout the Qt library. +# - AddressDAO Local class +# - Defines the low-lever DAO class to handle CRUD operations on addresses. +# +# @section notes_addressmodel Notes +# - None. +# +# @section todo_addressmodel TODO +# - None. +# +# @section author_addressmodel Author(s) +# - Created by Linuxero on 03/14/2025. +# - Modified by Linuxero on 03/14/2025. +# +# Copyright (c) 2025 Schnaxero. All rights reserved. + from PySide6.QtCore import QAbstractListModel, Qt, Slot, QModelIndex from .AddressDAO import AddressDAO from ..PyqcrmDataRoles import PyqcrmDataRoles class AddressModel(QAbstractListModel): - + """! The AddressModel class. + Defines a model class utilized to handle data. + Inherits from QAbstractListModel + Handles the address data operations. + """ def __init__(self): + """! The AddressModel class initializer. + """ super().__init__() self.__address_data = AddressDAO().getAddressData() def rowCount(self, parent = QModelIndex()): + """! Returns the number of rows under the given parent. + Ref. rowCount() + """ return len(self.__address_data) def data(self, index, role = Qt.DisplayRole): + """! Returns the data stored under the given role for the item referred to by the index. + Ref. data() + """ row = index.row() if role == Qt.DisplayRole: data = self.__address_data[row][2] @@ -22,6 +68,9 @@ class AddressModel(QAbstractListModel): return None def roleNames(self): + """! Returns the model’s role names. + Ref. roleNames() + """ return { Qt.DisplayRole: b"display", PyqcrmDataRoles.CITY_ROLE: b"city", @@ -32,6 +81,11 @@ class AddressModel(QAbstractListModel): @Slot(bool, str) def getAddresses(self, all, zipcode): + """! Loads the addresses from the storage backend. + @param all Boolean to specify whether all addresses to be returned or not. + @param zipcode String to look up addresses following a specific zipcode. + @return Returns a dictionary containing the addresses. + """ data = AddressDAO().getAddressData(all, zipcode) return data diff --git a/lib/DB/BTypeModel.py b/lib/DB/BTypeModel.py index 246dd66..859a5da 100644 --- a/lib/DB/BTypeModel.py +++ b/lib/DB/BTypeModel.py @@ -1,16 +1,63 @@ +"""! @brief Defines the model class to handle type of client.""" +## +# @file BTypeModel.py +# +# @brief Defines the BTypeModel class. +# +# @section description_businesstypemodel Description +# Defines the model class to handle CRUD operations on customers types. +# - BTypeModel (Model class) +# +# @section libraries_businesstypemodel Libraries/Modules +# - QAbstractListModel PySid6 core Class +# - Provides an abstract model that can be subclassed to create one-dimensional list models. +# - QModelIndex PySid6 core Class +# - Used to locate data in a data model. +# - Slot PySide6 function +# - A function that is called in response to a particular signal. +# - Qt PySid6 core Class +# - A namespace contains miscellaneous identifiers used throughout the Qt library. +# - BTypeDAO Local class +# - Defines the low-lever DAO class to handle CRUD operations on customers types. +# +# @section notes_businesstypemodel Notes +# - None. +# +# @section todo_businesstypemodel TODO +# - None. +# +# @section author_businesstypemodel Author(s) +# - Created by Linuxero on 03/14/2025. +# - Modified by Linuxero on 03/14/2025. +# +# Copyright (c) 2025 Schnaxero. All rights reserved. + from PySide6.QtCore import QAbstractListModel, Qt, QModelIndex from .BTypeDAO import BTypeDAO class BTypeModel(QAbstractListModel): + """! The BTypeModel class. + Defines a model class utilized to handle data. + Inherits from QAbstractListModel + Handles the customers types data operations. + """ def __init__(self): + """! The AddressModel class initializer. + """ super().__init__() self.__btype_data = BTypeDAO().getBType() def rowCount(self, parent = QModelIndex()): + """! Returns the number of rows under the given parent. + Ref. rowCount() + """ return len(self.__btype_data) def data(self, index, role = Qt.DisplayRole): + """! Returns the data stored under the given role for the item referred to by the index. + Ref. data() + """ row = index.row() if role == Qt.DisplayRole: data= self.__btype_data[row][1] diff --git a/lib/DB/BusinessModel.py b/lib/DB/BusinessModel.py index 36b188d..947bbb0 100644 --- a/lib/DB/BusinessModel.py +++ b/lib/DB/BusinessModel.py @@ -1,4 +1,41 @@ -# This Python file uses the following encoding: utf-8 +"""! @brief Defines the model class to handle customers.""" +## +# @file BusinessModel.py +# +# @brief Defines the BusinessModel class. +# +# @section description_businessmodel Description +# Defines the model class to handle CRUD operations on customers. +# - BusinessModel (Model class) +# +# @section libraries_businessmodel Libraries/Modules +# - QAbstractListModel PySid6 core Class +# - Provides an abstract model that can be subclassed to create one-dimensional list models. +# - QModelIndex PySid6 core Class +# - Used to locate data in a data model. +# - Slot PySide6 function +# - A function that is called in response to a particular signal. +# - Signal PySide6 class +# - Provides a way to declare and connect Qt signals in a pythonic way. +# - Qt PySid6 core Class +# - A namespace contains miscellaneous identifiers used throughout the Qt library. +# - BusinessDAO Local class +# - Defines the low-lever DAO class to handle CRUD operations on customers. +# - ConfigLoader Local class +# - Defines the base class for the program configuration. +# +# @section notes_businessmodel Notes +# - None. +# +# @section todo_businessmodel TODO +# - None. +# +# @section author_businessmodel Author(s) +# - Created by Linuxero on 03/14/2025. +# - Modified by Linuxero on 03/14/2025. +# +# Copyright (c) 2025 Schnaxero. All rights reserved. + from PySide6.QtCore import QAbstractTableModel, QModelIndex, Qt, Slot, Signal from .BusinessDAO import BusinessDAO from ..PyqcrmFlags import PyqcrmFlags @@ -62,6 +99,11 @@ from ..ConfigLoader import ConfigLoader class BusinessModel(QAbstractTableModel): + """! The BusinessModel class. + Defines a model class utilized to handle data. + Inherits from QAbstractListModel + Handles the customers data operations. + """ __visible_index = {} __visible_columns = None __col_name = "" @@ -70,6 +112,8 @@ class BusinessModel(QAbstractTableModel): __business_dict = {'business':{}} #,'contact':{}} def __init__(self): + """! The AddressModel class initializer. + """ super().__init__() self.__business_dao = BusinessDAO() self.__business_dao.newBusinessAdded.connect(self.__refreshView) @@ -78,12 +122,17 @@ class BusinessModel(QAbstractTableModel): self.__getData() def __getData(self, criterion = "Alle"): + """! Returns the customers data according to the model. + @param criterion String to specify which customers are to be fetched. + """ self.beginResetModel() rows, self.__visible_columns = self.__business_dao.getBusiness(self.__key, criterion) self.__data = rows self.endResetModel() def __getBusinessInfo(self): + """! Fetches detailed information about a customer. + """ self.__business_dict['business']['id'] = self.__business[0][0] self.__business_dict['business']['contactid'] = self.__business[0][1] self.__business_dict['business']['company'] = self.__business[0][2] @@ -100,18 +149,30 @@ class BusinessModel(QAbstractTableModel): self.__business_dict['business']['city'] = self.__business[0][13] def rowCount(self, parent= QModelIndex()): + """! Returns the number of rows under the given parent. + Ref. rowCount() + """ return len (self.__data) def columnCount(self, parent= QModelIndex()): + """! Returns the number of columns for the children of the given parent. + Ref. columnCount() + """ return len(self.__visible_columns) - 1 def data(self, index, role = Qt.DisplayRole): + """! Returns the data stored under the given role for the item referred to by the index. + Ref. data() + """ if role == Qt.DisplayRole: row = self.__data[index.row()] return row[index.column() + 1] return None def headerData(self, section, orientation, role= Qt.DisplayRole): + """! Returns the data for the given role and section in the header with the specified orientation. + Ref. headerData() + """ if orientation == Qt.Horizontal and role ==Qt.DisplayRole: self.__col_name = self.__visible_columns[section + 1] return self.__col_name @@ -132,6 +193,9 @@ class BusinessModel(QAbstractTableModel): @Slot(int) def onRowClicked(self, row): + """! Handles a selected customer from the GUI. + @param row The number of the customer in the data model. + """ #print(self.__data) #print(f"Selected table row: {row}, corresponding DB ID: {self.__data[row][0]}") if not self.__business_dict['business'] or self.__data[row][0] != self.__business_dict['business']['id']: @@ -143,19 +207,31 @@ class BusinessModel(QAbstractTableModel): @Slot(result = dict) def getClientDetails(self): + """! Fetches a selected customer from the GUI. + @return A dictionary containing all information of a customer. + """ return self.__business_dict @Slot(str) def viewCriterion(self, criterion): + """! Updates the customers view. + @param criterion The criterion used to look for customers. + """ self.__getData(criterion) @Slot(dict, int) def addBusiness(self, business, contact_id): + """! Saves a customer view. + @param business The customer to be saved. + @param contact_id The contact person id if available. + """ self.__business_dao.addBusiness(business, contact_id) @Slot() def __refreshView(self): + """! Updates the customers view. + """ self.__getData() @Slot(dict) diff --git a/lib/Vermasseln.py b/lib/Vermasseln.py index 67570cb..008e382 100644 --- a/lib/Vermasseln.py +++ b/lib/Vermasseln.py @@ -1,16 +1,58 @@ -# This Python file uses the following encoding: utf-8 +"""! @brief Defines the encryption class.""" +## +# @file Vermasseln.py +# +# @brief Defines the Vermasseln class. +# +# @section description_vermasseln Description +# Defines the base class for the program encryption mechansim. +# - Vermasseln (base class) +# +# @section libraries_configloader Libraries/Modules +# - platform Python standard library +# - Access to underlying platform’s identifying data. +# - base64 Python standard library +# - Provides functions for encoding binary data to printable ASCII characters and decoding such encodings back to binary data. +# - random Python module +# - Implements pseudo-random number generators for various distributions. +# - string Python standard library +# - Common string operations. +# - Crypto Python package +# - Provides cryptographic functionalities. +# +# @section notes_vermasseln Notes +# - None. +# +# @section todo_vermasseln TODO +# - None. +# +# @section author_vermasseln Author(s) +# - Created by Linuxero on 03/14/2025. +# - Modified by Linuxero on 03/14/2025. +# +# Copyright (c) 2025 Schnaxero. All rights reserved. + from Crypto.Cipher import AES from base64 import b64encode, b64decode import platform from Crypto.Hash import SHA256, SHA3_512 -from Crypto.Protocol.KDF import PBKDF2 -from Crypto.Random import get_random_bytes +# from Crypto.Protocol.KDF import PBKDF2 +# from Crypto.Random import get_random_bytes import random import string class Vermasseln: + """! The Vermasseln class. + Defines the class utilized by different parts of the program. + Handles the encryption/decryption of the whole program. + """ def oscarVermasseln(self, data, local= True): + """! Encrypts data. + @param data The data to encrypt. + @param local Is the encryption local to the host or not? + @return encrypted data. + """ b_data = data.encode("utf-8") cipher = self.__vermasslungsKobold(local) @@ -21,6 +63,11 @@ class Vermasseln: return storable_data def entschluesseln(self, data, local = True): + """! Decrypts data. + @param data The data to decrypt. + @param local Is the encryption local to the host or not? + @return decrypted data on success, None on failure. + """ try: data_list = data.split(".") encoded_data = [b64decode(x) for x in data_list] @@ -38,6 +85,10 @@ class Vermasseln: return decrypted_data def __vermasslungsKobold(self, local = True): + """! Prepares the encryption key. + @param local Is the encryption local to the host or not? + @return encryption key. + """ key = platform.processor().encode("utf-8") if local else b"(==daniishtverhaftetwegensexy#)" key = key[0:31] hash_key = SHA256.new(key) @@ -48,6 +99,11 @@ class Vermasseln: @classmethod def userPasswordHash(self, password, salt = None): + """! Hashes data. + @param password The data to hash. + @param salt The salt to use if available. + @return hashed data. + """ if not salt: salt = "".join(random.choice(string.ascii_letters + string.digits) for i in range (32)) hash_pw = (salt + password).encode("utf-8") diff --git a/main.py b/main.py index e95c0cb..0b59450 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,63 @@ # # !/home/linuxero/proj/tero/pyqcrm/.qtcreator/venv-3.13.1/bin/python3 +"""! @brief CRM for cleaning services written in Python, PySide6 and QtQuick.""" +## +# @mainpage PYQCRM - Qt for Python CRM +# +# @section description_main Description +# A CRM program for digitally manageing the business of cleaning +# services company. +# +# @section notes_main Notes +# - The project is built using QtCreator 6.8.x +# - Minimum Python version 3.13.x +# - PySide6 +# +# Copyright (c) 2025 Schnaxero. All rights reserved. +## +# @file main.py +# +# @brief Main entry point of the program +# +# @section description_pyqcrm Description +# Initialization of the program goes here. Various global functions called from the entry point. +# The GUI application is set up and started here. +# +# @section libraries_main Libraries/Modules +# - os standard library +# - Access to environ, a mapping object of keys and values to set an environment variable. +# - sys standard library +# - Access to argv and system functions. +# - logging standard library +# - Access to logging functions. +# - QLocalServer PySide6 class +# - Provides a local socket based server. +# - QLocalSocket PySide6 class +# - Provides a local socket. +# - QSystemTrayIcon PySide6 class +# - Provides an icon for the application in the system tray. +# - QIcon PySide6 class +# - Provides scalable icons in different modes and states. +# - QGuiApplication PySide6 class +# - Manages the GUI application’s control flow and main settings. +# - QQmlApplicationEngine PySide6 class +# - Provides a convenient way to load an application from a single QML file. +# - QIODevice PySide6 class +# - Base interface class of all I/O devices in Qt. +# - lib module (local) +# - Backbone classes to run the program. +# +# @section notes_pyqcrm Notes +# - Install dependencies (See requirements.txt). +# +# @section todo_pyqcrm TODO +# - A lot of nice-to-haves. +# +# @section author_pyqcrm Author(s) +# - Created by Linuxero on 03/14/2025. +# - Modified by Linuxero on 03/14/2025. +# +# Copyright (c) 2025 Schnaxero. All rights reserved. +# Imports import os import sys import logging @@ -20,7 +79,8 @@ from lib.DB.EmployeeModel import EmployeeModel from lib.DB.ObjectModel import ObjectModel from lib.Printers import Printers - +# Environment settings +## Allow local file read. os.environ['QML_XHR_ALLOW_FILE_READ'] = '1' # [pyqcrm] @@ -35,19 +95,40 @@ os.environ['QML_XHR_ALLOW_FILE_READ'] = '1' # name="" # type="" - +# Global Constants +## Configuration availability. bad_config = False + +## Database connection available. db_con = False + +## The model class for address manipulation. address_model = None + +## The model class for customer manipulation. business_model = None + +## The model class for customer type manipulation. business_type = None + +## The model class for contact manipulation. contact_model = None + +## The model class for employee manipulation. employee_model = None + +## The model class for object manipulation. object_model = None + +## The class of available printers on the system. printers = None + +## The logged-in user. user = None +# Functions def initializeProgram(): + """! Initializes the program.""" print(f"In {__file__} file, initializeProgram()") global address_model, bad_config, business_model, user, business_type, contact_model, employee_model, object_model, db_con, printers if not bad_config: @@ -66,12 +147,14 @@ def initializeProgram(): publishContext() def configReady(): + """! Slot to respond to the validity of the configuration.""" global bad_config bad_config = False initializeProgram() def publishContext(): + """! Connect necessary modules to the QML GUI.""" # print(f"In {__file__} file, publishContext()") global engine, address_model, bad_config, business_model, user, business_type, contact_model, object_model, employee_model, printers engine.rootContext().setContextProperty("loggedin_user", user) @@ -82,6 +165,7 @@ def publishContext(): engine.rootContext().setContextProperty("employee_model", employee_model) engine.rootContext().setContextProperty("object_model", object_model) +# Entry point of the program if __name__ == "__main__": #QResource.registerResource("rc_qml.py") app = QGuiApplication(sys.argv)