From d7853ab67d55afaf2bf21ea8ee0d0cf89b08dd1a Mon Sep 17 00:00:00 2001 From: WorldTeacher <41587052+WorldTeacher@users.noreply.github.com> Date: Fri, 17 May 2024 08:35:37 +0200 Subject: [PATCH] rest of files, not sorted --- .trunk/trunk.yaml | 18 +- .vscode/settings.json | 4 +- build.app.json | 69 + config.yaml | 12 +- curr.txt | 0 database_testing.py | 5 +- db_testing.py | 3 +- dbfunctions | Bin 0 -> 2140 bytes docs - Kopie/database.html | 4266 +++++++++++++++++ docs - Kopie/database.md | 327 ++ docs - Kopie/db.html | 132 + docs - Kopie/db.md | 0 docs - Kopie/index.md | 18 + documentation/database.html | 3071 ++++++++++++ mail.py | 34 + ...hner (alexander.kirchner@ph-freiburg.de).eml | 54 - ...Auflösung des Semesterapparates {AppNr}.eml | 4 +- ...um Semesterapparat {AppNr} - {AppName}.eml | 2 +- open_file_test.py | 57 + resources.qrc | 16 + src/backend/delete_temp_contents.py | 11 +- src/backend/import pyautogui.py | 16 + src/backend/olddatabase.py | 753 +++ src/backend/pickles.py | 7 +- src/backend/semester.py | 19 +- src/errors/DatabaseErrors.py | 9 +- src/errors/__init__.py | 4 +- src/logic/constants.py | 2 +- src/logic/csvparser.py | 13 +- src/logic/dataclass.py | 9 +- src/logic/get_msword_content.py | 22 +- src/logic/get_pdf_content.py | 7 +- src/logic/log.py | 23 +- src/logic/pdfparser.py | 18 +- src/logic/settings.py | 14 +- src/logic/thread_bookgrabber.py | 7 +- src/logic/threads.py | 24 +- src/logic/threads_autoadder.py | 5 +- src/logic/threads_availchecker.py | 5 +- src/logic/threads_copy.py | 22 +- src/logic/userInterface.py | 149 +- src/logic/webrequest.py | 6 +- src/transformers/__init__.py | 14 +- src/transformers/transformers.py | 2 +- src/ui/Ui_semesterapparat_ui.py | 109 +- src/ui/Ui_setupwizard.py | 1 - src/ui/__init__.py | 9 +- src/ui/icons/email.svg | 1 + src/ui/icons/locked.svg | 1 + src/ui/icons/logo.ico | Bin 0 -> 270398 bytes src/ui/icons/plus.svg | 1 + src/ui/mainwindow.py | 88 +- src/ui/resources_rc.py | 21 +- src/ui/sap.py | 111 +- src/ui/semesterapparat_ui.ui | 41 +- src/ui/semesterapparat_ui_ui.py | 1344 ++++-- src/ui/setupwizard_ui.py | 2 - src/ui/sounds/semesterapparat_ui_ui.py | 334 +- src/ui/untitled.ui | 692 --- src/ui/untitled_ui.py | 477 -- src/ui/widgets/Ui_progress_overview_widget.py | 2 +- src/ui/widgets/collapse.py | 3 +- src/ui/widgets/default_apps.py | 6 +- src/ui/widgets/graph.py | 1 - src/ui/widgets/webview_ui.py | 9 +- src/utils/Ui_docs.py | 30 + src/utils/__init__.py | 4 +- src/utils/blob.py | 2 - src/utils/docs.ui | 28 + src/utils/docs_ui.py | 77 + src/utils/documentationview.py | 6 +- src/utils/pickles.py | 3 + test.py | 149 +- test.ttxt | Bin 0 -> 2582 bytes test/bookgrabbertest.py | 66 + test/database_test.py | 22 +- test/many_webrequest_test.py | 2 +- test/rds_test.py | 103 + test/semestergen_test.py | 7 + test/test_database.py | 3 +- test/webrequest_test.py | 25 +- testing.txt | Bin 0 -> 2582 bytes 82 files changed, 10724 insertions(+), 2309 deletions(-) create mode 100644 build.app.json create mode 100644 curr.txt create mode 100644 dbfunctions create mode 100644 docs - Kopie/database.html create mode 100644 docs - Kopie/database.md create mode 100644 docs - Kopie/db.html create mode 100644 docs - Kopie/db.md create mode 100644 docs - Kopie/index.md create mode 100644 documentation/database.html create mode 100644 mail.py delete mode 100644 mail_vorlagen/Information bezüglich der Auflösung des Semesterapparates {AppNr} - Alexander Kirchner (alexander.kirchner@ph-freiburg.de).eml create mode 100644 open_file_test.py create mode 100644 resources.qrc create mode 100644 src/backend/import pyautogui.py create mode 100644 src/backend/olddatabase.py create mode 100644 src/ui/icons/email.svg create mode 100644 src/ui/icons/locked.svg create mode 100644 src/ui/icons/logo.ico create mode 100644 src/ui/icons/plus.svg delete mode 100644 src/ui/untitled.ui delete mode 100644 src/ui/untitled_ui.py create mode 100644 src/utils/Ui_docs.py create mode 100644 src/utils/docs.ui create mode 100644 src/utils/docs_ui.py create mode 100644 test.ttxt create mode 100644 test/bookgrabbertest.py create mode 100644 test/rds_test.py create mode 100644 test/semestergen_test.py create mode 100644 testing.txt diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 5d2080f..17961ef 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -2,11 +2,11 @@ # To learn more about the format of this file, see https://docs.trunk.io/reference/trunk-yaml version: 0.1 cli: - version: 1.20.0 + version: 1.22.1 plugins: sources: - id: trunk - ref: v1.4.3 + ref: v1.5.0 uri: https://github.com/trunk-io/plugins runtimes: enabled: @@ -25,15 +25,15 @@ lint: - trivy - trufflehog enabled: - - osv-scanner@1.6.2 - - sql-formatter@15.2.0 - - sqlfluff@2.3.5 + - osv-scanner@1.7.2 + - sql-formatter@15.3.1 + - sqlfluff@3.0.6 - isort@5.13.2 - - ruff@0.2.2 - - bandit@1.7.7 - - markdownlint@0.39.0 + - ruff@0.4.3 + - bandit@1.7.8 + - markdownlint@0.40.0 - yamllint@1.35.1 - - black@24.2.0 + - black@24.4.2 actions: disabled: - trunk-fmt-pre-commit diff --git a/.vscode/settings.json b/.vscode/settings.json index df88aea..2ed40b2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -15,7 +15,9 @@ "**/.pytest_cache": true, ".pytest_cache": true, "**/__pycache__": true, - "__pycache__": true + "__pycache__": true, + "**/.trunk": true, + "**/.vscode": true }, "explorerExclude.backup": {}, "python.testing.unittestEnabled": false, diff --git a/build.app.json b/build.app.json new file mode 100644 index 0000000..dc3c383 --- /dev/null +++ b/build.app.json @@ -0,0 +1,69 @@ +{ + "version": "auto-py-to-exe-configuration_v1", + "pyinstallerOptions": [ + { + "optionDest": "noconfirm", + "value": true + }, + { + "optionDest": "filenames", + "value": "C:/Users/aky547/GitHub/SemesterapparatsManager/__main__.py" + }, + { + "optionDest": "onefile", + "value": false + }, + { + "optionDest": "console", + "value": false + }, + { + "optionDest": "icon_file", + "value": "C:/Users/aky547/Downloads/VZjRNn1k.ico" + }, + { + "optionDest": "name", + "value": "SemesterAppMan" + }, + { + "optionDest": "clean_build", + "value": true + }, + { + "optionDest": "strip", + "value": false + }, + { + "optionDest": "noupx", + "value": false + }, + { + "optionDest": "disable_windowed_traceback", + "value": false + }, + { + "optionDest": "uac_admin", + "value": false + }, + { + "optionDest": "uac_uiaccess", + "value": false + }, + { + "optionDest": "argv_emulation", + "value": false + }, + { + "optionDest": "bootloader_ignore_signals", + "value": false + }, + { + "optionDest": "datas", + "value": "C:/Users/aky547/GitHub/SemesterapparatsManager/config.yaml;." + } + ], + "nonPyinstallerOptions": { + "increaseRecursionLimit": true, + "manualArguments": "" + } +} \ No newline at end of file diff --git a/config.yaml b/config.yaml index 2a4d6df..4efe763 100644 --- a/config.yaml +++ b/config.yaml @@ -30,9 +30,9 @@ database: path: ~\Desktop\semap\db\ tempdir: ~\Desktop\semap\db\temp\ mail: - smtp_server: - port: - sender: - user_name: - use_user_name: - password: \ No newline at end of file + smtp_server: smtp.ph-freiburg.de + port: 465 + sender: alexander.kirchner@ph-freiburg.de + user_name: aky547 + use_user_name: 1 + password: CMcDna3qPh2*n diff --git a/curr.txt b/curr.txt new file mode 100644 index 0000000..e69de29 diff --git a/database_testing.py b/database_testing.py index b02ec3a..7e6cdcb 100644 --- a/database_testing.py +++ b/database_testing.py @@ -10,10 +10,9 @@ apparat.subject = "Physik" apparat.semester = "SoSe 2021" -files = {"name": "test.png", "type": "png", - "path": r"C:\Users\aky547\Desktop\test.png"} +files = {"name": "test.png", "type": "png", "path": r"C:\Users\aky547\Desktop\test.png"} db = Database() # print(db.recreate_file("testfile.pdf",files,3)) # db.insert_file(files,3) # recreate_file("test.pdf",files,1))#insert_file(files,1)) -db.get_apparats_name(70) \ No newline at end of file +db.get_apparats_name(70) diff --git a/db_testing.py b/db_testing.py index 627e487..4d963d3 100644 --- a/db_testing.py +++ b/db_testing.py @@ -1,7 +1,6 @@ -from omegaconf import OmegaConf - from codebase import Database from codebase.pickles import load_pickle, make_pickle +from omegaconf import OmegaConf from webrequest import BibTextTransformer, WebRequest config = OmegaConf.load("config.yaml") diff --git a/dbfunctions b/dbfunctions new file mode 100644 index 0000000000000000000000000000000000000000..1ad65f3f3eba9a3550e91fda748b4f43f278a4aa GIT binary patch literal 2140 zcmah~%Wi`}4D`8D|Dm^B`bSlT2#}P9M+%{7e!lI@uAF^B6a~b3Y|q-W-oM{B*-0ri zeg)p0w0O=~dzBZ@(n|wGD~t45+YFyljzGnpGko^)2}>mx&w7?KGB)yqs52~nPTESD z@aK8~{~Mk&(BwgM1FL4L-*<57=rVdc#a6+WT#G2q!nusn^2QTJvD-*uR zqk&(E=#E()vqud_FwR?R6RU)sRbI<;2J=x4D>0P4W~dRT!+(ooh{<&`MWj6qF)@z= z_Ee#^9hk6Rx>w~%bzDppRb}jD&e{dF_O;<+Sp8I}(}F%S?zT!|&vkY-a_ea)^i^dR zP_EP08fHK*bG!7F^w_SuiYl6z_(J7wabk_&O zppsL|2^}7hW56?3OlQuNoeQkf79Ul9nMARZMW#4`u+|&-vm}#7hEhbKjx2s{CBb?>AO#ZB=3OSdog^+|3qSIn%*j?= zC?DP!ygTeYzlAGgkJlei6M^ruZb4ZvR!hu=Nkb{e^^@{Z%JWA8@Wm5`;@VnO%Rd+mk_F?vGc2^gZb QPHsN8c5x@~@+?pC4<2=SfB*mh literal 0 HcmV?d00001 diff --git a/docs - Kopie/database.html b/docs - Kopie/database.html new file mode 100644 index 0000000..b3ff3a4 --- /dev/null +++ b/docs - Kopie/database.html @@ -0,0 +1,4266 @@ + + + + + + +database API documentation + + + + + + + + + + + +
+
+
+

Module database

+
+
+
+ +Expand source code + +
import datetime
+import os
+import re
+import sqlite3 as sql
+import tempfile
+from src.logic.log import MyLogger
+#from icecream import ic
+from typing import List, Tuple, Dict, Any, Optional, Union
+from pathlib import Path
+from omegaconf import OmegaConf
+from src.backend.db import CREATE_TABLE_APPARAT, CREATE_TABLE_MESSAGES, CREATE_TABLE_MEDIA, CREATE_TABLE_APPKONTOS, CREATE_TABLE_FILES, CREATE_TABLE_PROF, CREATE_TABLE_USER, CREATE_TABLE_SUBJECTS
+from src.logic.constants import SEMAP_MEDIA_ACCOUNTS
+from src.logic.dataclass import ApparatData, BookData
+from src.errors import NoResultError, AppPresentError
+from src.utils import load_pickle, dump_pickle,create_blob
+config = OmegaConf.load("config.yaml")
+logger = MyLogger(__name__)
+
+
+class Database:
+    """
+    Initialize the database and create the tables if they do not exist.
+    """
+    def __init__(self, db_path: str = None):
+        """
+        Default constructor for the database class
+
+        Args:
+            db_path (str, optional): Optional Path for testing / specific purposes. Defaults to None.
+        """
+        if db_path is None:
+            self.db_path = config.database.path + config.database.name
+            self.db_path = self.db_path.replace("~", str(Path.home()))
+        else:
+            self.db_path = db_path
+        if self.get_db_contents() is None:
+            logger.log_critical("Database does not exist, creating tables")
+            self.create_tables()
+    def get_db_contents(self)->Union[List[Tuple], None]:
+        """
+        Get the contents of the 
+
+        Returns:
+            Union[List[Tuple], None]: _description_
+        """
+        try:
+            with sql.connect(self.db_path) as conn:
+                cursor = conn.cursor()
+                cursor.execute("SELECT * FROM sqlite_master WHERE type='table'")
+                return cursor.fetchall()
+        except sql.OperationalError:
+            return None
+    def connect(self)->sql.Connection:
+        """
+        Connect to the database
+
+        Returns:
+            sql.Connection: The active connection to the database
+        """
+        return sql.connect(self.db_path)
+    def close_connection(self, conn: sql.Connection):
+        """
+        closes the connection to the database
+        
+        Args:
+        ----
+            - conn (sql.Connection): the connection to be closed
+        """
+        conn.close()
+    def create_tables(self):
+        """
+        Create the tables in the database
+        """
+        conn = self.connect()
+        cursor = conn.cursor()
+        cursor.execute(CREATE_TABLE_APPARAT)
+        cursor.execute(CREATE_TABLE_MESSAGES)
+        cursor.execute(CREATE_TABLE_MEDIA)
+        cursor.execute(CREATE_TABLE_APPKONTOS)
+        cursor.execute(CREATE_TABLE_FILES)
+        cursor.execute(CREATE_TABLE_PROF)
+        cursor.execute(CREATE_TABLE_USER)
+        cursor.execute(CREATE_TABLE_SUBJECTS)
+        conn.commit()
+        self.close_connection(conn)
+    def insertInto(self, query:str, params:Tuple) -> None:
+        """
+        Insert sent data into the database
+
+        Args:
+            query (str): The query to be executed
+            params (Tuple): the parameters to be inserted into the database
+        """
+        conn = self.connect()
+        cursor = conn.cursor()
+        logger.log_info(f"Inserting {params} into database with query {query}")
+        cursor.execute(query, params)
+        conn.commit()
+        self.close_connection(conn)
+    def query_db(self, query: str, args: Tuple = (), one: bool = False)->Union[Tuple, List[Tuple]]:
+        """
+        Query the Database for the sent query.
+
+        Args:
+            query (str): The query to be executed
+            args (Tuple, optional): The arguments for the query. Defaults to ().
+            one (bool, optional): Return the first result only. Defaults to False.
+
+        Returns:
+            Union[Typle|List[Tuple]]: Returns the result of the query
+        """
+        conn = self.connect()
+        cursor = conn.cursor()
+        logger.log_info(f"Querying database with query {query}, args: {args}")
+        cursor.execute(query, args)
+        rv = cursor.fetchall()
+        conn.commit()
+        self.close_connection(conn)
+        return (rv[0] if rv else None) if one else rv
+
+    # Books
+    def addBookToDatabase(self, bookdata:BookData,app_id:Union[str,int], prof_id:Union[str,int]):
+        """
+        Add books to the database. Both app_id and prof_id are required to add the book to the database, as the app_id and prof_id are used to select the books later on.
+
+        Args:
+            bookdata (BookData): The metadata of the book to be added
+            app_id (str): The apparat id where the book should be added to
+            prof_id (str): The id of the professor where the book should be added to.
+        """
+        conn = self.connect()
+        cursor = conn.cursor()
+        t_query = (
+            f"SELECT bookdata FROM media WHERE app_id={app_id} AND prof_id={prof_id}"
+        )
+        # print(t_query)
+        result = cursor.execute(t_query).fetchall()
+        result = [load_pickle(i[0]) for i in result]
+        if bookdata in result:
+            print("Bookdata already in database")
+            # check if the book was deleted in the apparat
+            query = (
+                "SELECT deleted FROM media WHERE app_id=? AND prof_id=? AND bookdata=?"
+            )
+            params = (app_id, prof_id, dump_pickle(bookdata))
+            result = cursor.execute(query, params).fetchone()
+            if result[0] == 1:
+                print("Book was deleted, updating bookdata")
+                query = "UPDATE media SET deleted=0 WHERE app_id=? AND prof_id=? AND bookdata=?"
+                params = (app_id, prof_id, dump_pickle(bookdata))
+                cursor.execute(query, params)
+                conn.commit()
+            return
+
+        query = (
+            "INSERT INTO media (bookdata, app_id, prof_id,deleted) VALUES (?, ?, ?,?)"
+        )
+        converted = dump_pickle(bookdata)
+        params = (converted, app_id, prof_id, 0)
+        cursor.execute(query, params)
+        conn.commit()
+        self.close_connection(conn)
+    def getBookIdBasedOnSignature(self, app_id:Union[str,int], prof_id:Union[str,int],signature:str)->int:
+        """
+        Get a book id based on the signature of the book.
+
+        Args:
+            app_id (str): The apparat id the book should be associated with
+            prof_id (str): The professor id the book should be associated with
+            signature (str): The signature of the book
+
+        Returns:
+            int: The id of the book
+        """
+        result = self.query_db("SELECT bookdata, id FROM media WHERE app_id=? AND prof_id=?", (app_id,prof_id))
+        books = [(load_pickle(i[0]),i[1]) for i in result]
+        book = [i for i in books if i[0].signature == signature][0][1]
+        return book
+    def getBookBasedOnSignature(self, app_id:Union[str,int], prof_id:Union[str,int],signature:str)->BookData:
+        """
+        Get the book based on the signature of the book.
+
+        Args:
+            app_id (str): The apparat id the book should be associated with
+            prof_id (str): The professor id the book should be associated with
+            signature (str): The signature of the book
+
+        Returns:
+            BookData: The total metadata of the book wrapped in a BookData object
+        """
+        result = self.query_db("SELECT bookdata FROM media WHERE app_id=? AND prof_id=?", (app_id,prof_id))
+        books = [load_pickle(i[0]) for i in result]
+        book = [i for i in books if i.signature == signature][0]
+        return book
+    def getLastBookId(self)->int:
+        """
+        Get the last book id in the database
+
+        Returns:
+            int: ID of the last book in the database
+        """
+        return self.query_db("SELECT id FROM media ORDER BY id DESC", one=True)[0]
+    def searchBook(self, data:dict[str, str])->list[tuple[BookData, int]]:
+        """
+        Search a book in the database based on the sent data.
+
+        Args:
+            data (dict[str, str]): A dictionary containing the data to be searched for. The dictionary can contain the following: 
+            - signature: The signature of the book
+            - title: The title of the book
+
+        Returns:
+            list[tuple[BookData, int]]: A list of tuples containing the wrapped Metadata and the id of the book
+        """
+        rdata = self.query_db("SELECT * FROM media WHERE deleted=0")
+        #ic(rdata, len(rdata))
+        mode = 0
+        if len(data)== 1:
+            if "signature" in data.keys():
+                mode = 1
+            elif "title" in data.keys():
+                mode = 2
+        elif len(data) == 2:
+            mode = 3
+        else:
+            return None
+        ret = []
+        for book in rdata:
+            bookdata = load_pickle(book[1])
+            app_id = book[2]
+            prof_id = book[3]
+            if mode == 1:
+                if data["signature"] in bookdata.signature:
+                    ret.append((bookdata,app_id,prof_id))
+            elif mode == 2:
+                if data["title"] in bookdata.title:
+                    ret.append((bookdata,app_id,prof_id))
+            elif mode == 3:
+                if data["signature"] in bookdata.signature and data["title"] in bookdata.title:
+                    ret.append((bookdata,app_id,prof_id))
+        #ic(ret)
+        return ret
+    def setAvailability(self, book_id:str, available:str):
+        """
+        Set the availability of a book in the database
+
+        Args:
+            book_id (str): The id of the book
+            available (str): The availability of the book
+        """
+        self.query_db("UPDATE media SET available=? WHERE id=?", (available,book_id))
+    def getBookId(self, bookdata:BookData, app_id:Union[str,int], prof_id:Union[str,int])->int:
+        """
+        Get the id of a book based on the metadata of the book
+
+        Args:
+            bookdata (BookData): The wrapped metadata of the book
+            app_id (str): The apparat id the book should be associated with
+            prof_id (str): The professor id the book should be associated with
+
+        Returns:
+            int: ID of the book
+        """
+        result = self.query_db("SELECT id FROM media WHERE bookdata=? AND app_id=? AND prof_id=?", (dump_pickle(bookdata),app_id,prof_id), one=True)
+        return result[0]
+    def getBook(self,book_id:int)->BookData:
+        """
+        Get the book based on the id in the database
+
+        Args:
+            book_id (int): The id of the book
+
+        Returns:
+            BookData: The metadata of the book wrapped in a BookData object
+        """
+        return load_pickle(self.query_db("SELECT bookdata FROM media WHERE id=?", (book_id,), one=True)[0])
+    def getBooks(self, app_id:Union[str,int], prof_id:Union[str,int], deleted=0)->list[dict[int, BookData, int]]:
+        """
+        Get the Books based on the apparat id and the professor id
+
+        Args:
+            app_id (str): The ID of the apparat
+            prof_id (str): The ID of the professor
+            deleted (int, optional): The state of the book. Set to 1 to include deleted ones. Defaults to 0.
+
+        Returns:
+            list[dict[int, BookData, int]]: A list of dictionaries containing the id, the metadata of the book and the availability of the book
+        """
+        qdata = self.query_db(f"SELECT id,bookdata,available FROM media WHERE (app_id={app_id} AND prof_id={prof_id}) AND (deleted={deleted if deleted == 0 else '1 OR deleted=0'})")
+        ret_result = []
+        for result_a in qdata:
+            data = {"id": int, "bookdata": BookData, "available": int}
+            data["id"] = result_a[0]
+            data["bookdata"] = load_pickle(result_a[1])
+            data["available"] = result_a[2]
+            ret_result.append(data)
+        return ret_result
+    def updateBookdata(self, book_id, bookdata:BookData):
+        """
+        Update the bookdata in the database
+
+        Args:
+            book_id (str): The id of the book
+            bookdata (BookData): The new metadata of the book
+        """
+        self.query_db("UPDATE media SET bookdata=? WHERE id=?", (dump_pickle(bookdata),book_id))
+    def deleteBook(self, book_id):
+        """
+        Delete a book from the database
+
+        Args:
+            book_id (str): ID of the book
+        """
+        self.query_db("UPDATE media SET deleted=1 WHERE id=?", (book_id,))
+
+    # File Interactions
+    def getBlob(self, filename, app_id:Union[str,int]):
+        """
+        Get a blob from the database
+
+        Args:
+            filename (str): The name of the file
+            app_id (str): ID of the apparat
+
+        Returns:
+            bytes: The file stored in 
+        """
+        return self.query_db("SELECT fileblob FROM files WHERE filename=? AND app_id=?", (filename,app_id), one=True)[0]
+    def insertFile(self, file: list[dict], app_id:Union[str,int], prof_id:Union[str,int]):
+        """Instert a list of files into the database
+
+        Args:
+            file (list[dict]): a list containing all the files to be inserted
+            Structured: [{"name": "filename", "path": "path", "type": "filetype"}]
+            app_id (int): the id of the apparat
+            prof_id (str): the id of the professor
+        """
+        for f in file:
+            filename = f["name"]
+            path = f["path"]
+            filetyp = f["type"]
+            if path == "Database":
+                continue
+            blob = create_blob(path)
+            query = "INSERT OR IGNORE INTO files (filename, fileblob, app_id, filetyp,prof_id) VALUES (?, ?, ?, ?,?)"
+            self.query_db(query, (filename, blob, app_id, filetyp,prof_id))
+    def recreateFile(self, filename:str, app_id:Union[str,int],filetype:str)->str:
+        """Recreate a file from the database
+
+        Args:
+            filename (str): the name of the file
+            app_id (Union[str,int]): the id of the apparat
+            filetype (str): the extension of the file to be created
+
+        Returns:
+            str: The filename of the recreated file 
+        """
+        blob = self.getBlob(filename, app_id)
+        tempdir = config.database.tempdir
+        tempdir = tempdir.replace("~", str(Path.home()))
+        tempdir_path = Path(tempdir)        
+        if not os.path.exists(tempdir_path):
+            os.mkdir(tempdir_path)
+        file = tempfile.NamedTemporaryFile(
+            delete=False, dir=tempdir_path, mode="wb", suffix=f".{filetype}"
+        )
+        file.write(blob)
+        print("file created")
+        return file.name
+    def getFiles(self, app_id:Union[str,int], prof_id:int)->list[tuple]:
+        """Get all the files associated with the apparat and the professor
+
+        Args:
+            app_id (Union[str,int]): The id of the apparat
+            prof_id (Union[str,int]): the id of the professor
+
+        Returns:
+            list[tuple]: a list of tuples containing the filename and the filetype for the corresponding apparat and professor
+        """
+        return self.query_db("SELECT filename, filetyp FROM files WHERE app_id=? AND prof_id=?", (app_id,prof_id))
+
+    def getSemersters(self)->list[str]:
+        """Return all the unique semesters in the database
+
+        Returns:
+            list: a list of strings containing the semesters
+        """
+        data = self.query_db("SELECT DISTINCT erstellsemester FROM semesterapparat")
+        return [i[0] for i in data]
+
+    def getSubjects(self):
+        """Get all the subjects in the database
+
+        Returns:
+            list[tuple]: a list of tuples containing the subjects
+        """
+        return self.query_db("SELECT * FROM subjects")
+
+    # Messages
+    def addMessage(self, message:dict,user:str, app_id:Union[str,int]):
+        """add a Message to the database
+
+        Args:
+            message (dict): the message to be added
+            user (str): the user who added the message
+            app_id (Union[str,int]): the id of the apparat
+        """
+        def __getUserId(user):
+            return self.query_db("SELECT id FROM user WHERE username=?", (user,), one=True)[0]
+        user_id = __getUserId(user)
+        self.query_db("INSERT INTO messages (message, user_id, remind_at,appnr) VALUES (?,?,?,?)", (message["message"],user_id,message["remind_at"],app_id))
+    def getMessages(self, date:str)->list[dict[str, str, str, str]]:
+        """Get all the messages for a specific date
+
+        Args:
+            date (str): a date.datetime object formatted as a string in the format "YYYY-MM-DD"
+        
+        Returns:
+            list[dict[str, str, str, str]]: a list of dictionaries containing the message, the user who added the message, the apparat id and the id of the message
+        """
+        def __get_user_name(user_id):
+            return self.query_db("SELECT username FROM user WHERE id=?", (user_id,), one=True)[0]
+        messages = self.query_db("SELECT * FROM messages WHERE remind_at=?", (date,))
+        ret = [
+            {
+                "message": i[2],
+                "user": __get_user_name(i[4]),
+                "appnr": i[5],
+                "id": i[0]
+            }
+            for i in messages
+        ]
+        return ret
+    def deleteMessage(self, message_id):
+        """Delete a message from the database
+
+        Args:
+            message_id (str): the id of the message
+        """
+        self.query_db("DELETE FROM messages WHERE id=?", (message_id,))
+
+    # Prof data
+    def getProfNameById(self, prof_id:Union[str,int],add_title:bool=False)->str:
+        """Get a professor name based on the id
+
+        Args:
+            prof_id (Union[str,int]): The id of the professor
+            add_title (bool, optional): wether to add the title or no. Defaults to False.
+
+        Returns:
+            str: The name of the professor
+        """
+        prof = self.query_db("SELECT fullname FROM prof WHERE id=?", (prof_id,), one=True)
+        if add_title:
+            return f"{self.getTitleById(prof_id)}{prof[0]}"
+        else:
+            return prof[0]
+    def getTitleById(self, prof_id:Union[str,int])->str:
+        """get the title of a professor based on the id
+
+        Args:
+            prof_id (Union[str,int]): the id of the professor
+
+        Returns:
+            str: the title of the professor, with an added whitespace at the end, if no title is present, an empty string is returned
+        """
+        title = self.query_db("SELECT titel FROM prof WHERE id=?", (prof_id,), one=True)[0]
+        return f"{title} " if title is not None else ""
+    def getProfByName(self, prof_name:str)->tuple:
+        """get all the data of a professor based on the name
+
+        Args:
+            prof_name (str): the name of the professor
+
+        Returns:
+            tuple: the data of the professor
+        """
+        return self.query_db("SELECT * FROM prof WHERE fullname=?", (prof_name,), one=True)
+    def getProfId(self, prof_name:str)->Optional[int]:
+        """Get the id of a professor based on the name
+
+        Args:
+            prof_name (str): the name of the professor
+
+        Returns:
+            Optional[int]: the id of the professor, if the professor is not found, None is returned
+        """
+        
+        data = self.getProfByName(prof_name.replace(",", ""))
+        if data is None:
+            return None
+        else:
+            return data[0]
+    def getSpecificProfData(self, prof_id:Union[str,int], fields:List[str])->tuple:
+        """A customisable function to get specific data of a professor based on the id
+
+        Args:
+            prof_id (Union[str,int]): the id of the professor
+            fields (List[str]): a list of fields to be returned
+
+        Returns:
+            tuple: a tuple containing the requested data
+        """
+        query = "SELECT "
+        for field in fields:
+            query += f"{field},"
+        query = query[:-1]
+        query += " FROM prof WHERE id=?"
+        return self.query_db(query, (prof_id,), one=True)[0]
+    def getProfData(self, profname:str):
+        """Get mail, telephone number and title of a professor based on the name
+
+        Args:
+            profname (str): name of the professor
+
+        Returns:
+            tuple: the mail, telephone number and title of the professor
+        """
+        data = self.query_db("SELECT mail, telnr, titel FROM prof WHERE fullname=?", (profname.replace(",",""),), one=True)
+        return data
+    def createProf(self, prof_details:dict):
+        """Create a professor in the database
+
+        Args:
+            prof_details (dict): a dictionary containing the details of the professor
+        """
+        prof_title = prof_details["prof_title"]
+        prof_fname = prof_details["profname"].split(",")[1]
+        prof_fname = prof_fname.strip()
+        prof_lname = prof_details["profname"].split(",")[0]
+        prof_lname = prof_lname.strip()
+        prof_fullname = prof_details["profname"].replace(",", "")
+        prof_mail = prof_details["prof_mail"]
+        prof_tel = prof_details["prof_tel"]
+        params = (prof_title, prof_fname, prof_lname, prof_mail, prof_tel, prof_fullname)
+        query = "INSERT OR IGNORE INTO prof (titel, fname, lname, mail, telnr, fullname) VALUES (?, ?, ?, ?, ?, ?)"
+        self.insertInto(query=query, params=params)
+    def getProfs(self)->list[tuple]:
+        """Return all the professors in the database
+
+        Returns:
+            list[tuple]: a list containing all the professors in individual tuples
+        """
+        return self.query_db("SELECT * FROM prof")
+
+    # Apparat
+    def getAllAparats(self,deleted=0)->list[tuple]:
+        """Get all the apparats in the database
+
+        Args:
+            deleted (int, optional): Switch the result to use . Defaults to 0.
+
+        Returns:
+            list[tuple]: a list of tuples containing the apparats
+        """
+        return self.query_db("SELECT * FROM semesterapparat WHERE deletion_status=?", (deleted,))
+    def getApparatData(self, appnr, appname)->ApparatData:
+        """Get the Apparat data based on the apparat number and the name
+
+        Args:
+            appnr (str): the apparat number
+            appname (str): the name of the apparat
+
+        Raises:
+            NoResultError: an error is raised if no result is found
+
+        Returns:
+            ApparatData: the appended data of the apparat wrapped in an ApparatData object
+        """
+        result = self.query_db("SELECT * FROM semesterapparat WHERE appnr=? AND name=?", (appnr,appname), one=True)
+        if result is None:
+            raise NoResultError("No result found")
+        apparat = ApparatData()
+        apparat.appname = result[1]
+        apparat.appnr = result[4]
+        apparat.dauerapp = True if result[7] == 1 else False
+        prof_data = self.getProfData(self.getProfNameById(result[2]))
+        apparat.profname = self.getProfNameById(result[2])
+        apparat.prof_mail = prof_data[0]
+        apparat.prof_tel = prof_data[1]
+        apparat.prof_title = prof_data[2]
+        apparat.app_fach = result[3]
+        apparat.erstellsemester = result[5]
+        apparat.semester = result[8]
+        apparat.deleted = result[9]
+        apparat.apparat_adis_id = result[11]
+        apparat.prof_adis_id = result[12]
+        return apparat
+    def getUnavailableApparatNumbers(self)->List[int]:
+        """Get a list of all the apparat numbers in the database that are currently in use
+
+        Returns:
+            List[int]: the list of used apparat numbers
+        """
+        numbers = self.query_db("SELECT appnr FROM semesterapparat WHERE deletion_status=0")    
+        numbers = [i[0] for i in numbers]
+        logger.log_info(f"Currently used apparat numbers: {numbers}")
+        return numbers
+    def setNewSemesterDate(self, app_id:Union[str,int], newDate, dauerapp=False):
+        """Set the new semester date for an apparat
+
+        Args:
+            app_id (Union[str,int]): the id of the apparat
+            newDate (str): the new date
+            dauerapp (bool, optional): if the apparat was changed to dauerapparat. Defaults to False.
+        """
+        date = datetime.datetime.strptime(newDate, "%d.%m.%Y").strftime("%Y-%m-%d")
+        if dauerapp:
+            self.query_db("UPDATE semesterapparat SET verlängerung_bis=?, dauerapp=? WHERE appnr=?", (date,dauerapp,app_id))
+        else:
+            self.query_db("UPDATE semesterapparat SET endsemester=? WHERE appnr=?", (date,app_id))
+    def getApparatId(self, apparat_name)->Optional[int]:
+        """get the id of an apparat based on the name
+
+        Args:
+            apparat_name (str): the name of the apparat e.g. "Semesterapparat 1"
+
+        Returns:
+            Optional[int]: the id of the apparat, if the apparat is not found, None is returned
+        """
+        data = self.query_db("SELECT appnr FROM semesterapparat WHERE name=?", (apparat_name,), one=True)
+        if data is None:
+            return None
+        else:
+            return data[0]
+    def createApparat(self, apparat:ApparatData)->int:
+        """create the apparat in the database
+
+        Args:
+            apparat (ApparatData): the wrapped metadata of the apparat
+
+        Raises:
+            AppPresentError: an error describing that the apparats chosen id is already present in the database
+
+        Returns:
+            Optional[int]: the id of the apparat
+        """
+                
+        prof_id = self.getProfId(apparat.profname)
+        app_id = self.getApparatId(apparat.appname)
+        if app_id:
+            raise AppPresentError(app_id)
+
+        self.createProf(apparat.get_prof_details())
+        prof_id = self.getProfId(apparat.profname)
+        #ic(prof_id)
+        query = f"INSERT OR IGNORE INTO semesterapparat (appnr, name, erstellsemester, dauer, prof_id, fach,deletion_status,konto) VALUES ('{apparat.appnr}', '{apparat.appname}', '{apparat.semester}', '{apparat.dauerapp}', {prof_id}, '{apparat.app_fach}', '{0}', '{SEMAP_MEDIA_ACCOUNTS[apparat.appnr]}')"
+        logger.log_info(query)
+        self.query_db(query)
+        return self.getApparatId(apparat.appname)
+    def getApparatsByProf(self, prof_id:Union[str,int])->list[tuple]:
+        """Get all apparats based on the professor id
+
+        Args:
+            prof_id (Union[str,int]): the id of the professor
+
+        Returns:
+            list[tuple]: a list of tuples containing the apparats
+        """
+        return self.query_db("SELECT * FROM semesterapparat WHERE prof_id=?", (prof_id,))
+    def getApparatsBySemester(self, semester:str)->dict[list]:
+        """get all apparats based on the semester
+
+        Args:
+            semester (str): the selected semester
+
+        Returns:
+            dict[list]: a list off all created and deleted apparats for the selected semester
+        """
+        data = self.query_db("SELECT name, prof_id FROM semesterapparat WHERE erstellsemester=?", (semester,))
+        conn = self.connect()
+        cursor = conn.cursor()
+        c_tmp = []
+        for i in data:
+            c_tmp.append((i[0], self.getProfNameById(i[1])))
+        query = (
+            f"SELECT name,prof_id FROM semesterapparat WHERE deleted_date='{semester}'"
+        )
+        result = cursor.execute(query).fetchall()
+        d_tmp = []
+        for i in result:
+            d_tmp.append((i[0], self.getProfNameById(i[1])))
+        # group the apparats by prof
+        c_ret = {}
+        for i in c_tmp:
+            if i[1] not in c_ret.keys():
+                c_ret[i[1]] = [i[0]]
+            else:
+                c_ret[i[1]].append(i[0])
+        d_ret = {}
+        for i in d_tmp:
+            if i[1] not in d_ret.keys():
+                d_ret[i[1]] = [i[0]]
+            else:
+                d_ret[i[1]].append(i[0])
+        self.close_connection(conn)
+        return {"created": c_ret, "deleted": d_ret}
+    def getApparatCountBySemester(self)->tuple[list[str],list[int]]:
+        """get a list of all apparats created and deleted by semester
+
+        Returns:
+            tuple[list[str],list[int]]: a tuple containing two lists, the first list contains the semesters, the second list contains the amount of apparats created and deleted for the corresponding semester
+        """
+        conn = self.connect()
+        cursor = conn.cursor()
+        semesters = self.getSemersters()
+        created = []
+        deleted = []
+        for semester in semesters:
+            query = f"SELECT COUNT(*) FROM semesterapparat WHERE erstellsemester='{semester}'"
+            result = cursor.execute(query).fetchone()
+            created.append(result[0])
+            query = f"SELECT COUNT(*) FROM semesterapparat WHERE deletion_status=1 AND deleted_date='{semester}'"
+            result = cursor.execute(query).fetchone()
+            deleted.append(result[0])
+        # store data in a tuple
+        ret = []
+        e_tuple = ()
+        for sem in semesters:
+            e_tuple = (
+                sem,
+                created[semesters.index(sem)],
+                deleted[semesters.index(sem)],
+            )
+            ret.append(e_tuple)
+        self.close_connection(conn)
+        return ret 
+    def deleteApparat(self, app_id:Union[str,int], semester:str):
+        """Delete an apparat from the database
+
+        Args:
+            app_id (Union[str, int]): the id of the apparat
+            semester (str): the semester the apparat should be deleted from
+        """
+        self.query_db("UPDATE semesterapparat SET deletion_status=1, deleted_date=? WHERE appnr=?", (semester,app_id)) 
+    def isEternal(self, id):
+        """check if the apparat is eternal (dauerapparat)
+
+        Args:
+            id (int): the id of the apparat to be checked
+
+        Returns:
+            int: the state of the apparat
+        """
+        return self.query_db("SELECT dauer FROM semesterapparat WHERE appnr=?", (id,), one=True)
+    def getApparatName(self, app_id:Union[str,int], prof_id:Union[str,int]):
+        """get the name of the apparat based on the id
+
+        Args:
+            app_id (Union[str,int]): the id of the apparat
+            prof_id (Union[str,int]): the id of the professor
+
+        Returns:
+            str: the name of the apparat
+        """
+        return self.query_db("SELECT name FROM semesterapparat WHERE appnr=? AND prof_id=?", (app_id,prof_id), one=True)[0]
+    def updateApparat(self, apparat_data:ApparatData):
+        """Update an apparat in the database
+
+        Args:
+            apparat_data (ApparatData): the new metadata of the apparat
+        """
+        query = f"UPDATE semesterapparat SET name = ?, fach = ?, dauer = ?, prof_id = ? WHERE appnr = ?"
+        params = (
+            apparat_data.appname,
+            apparat_data.app_fach,
+            apparat_data.dauerapp,
+            self.getProfId(apparat_data.profname),
+            apparat_data.appnr,        
+        )
+        self.query_db(query, params)
+    def checkApparatExists(self, apparat_name:str):
+        """check if the apparat is already present in the database based on the name
+
+        Args:
+            apparat_name (str): the name of the apparat
+
+        Returns:
+            bool: True if the apparat is present, False if not
+        """
+        return True if self.query_db("SELECT appnr FROM semesterapparat WHERE name=?", (apparat_name,), one=True) else False
+    def checkApparatExistsById(self, app_id:Union[str,int])->bool:
+        """a check to see if the apparat is already present in the database, based on the id
+
+        Args:
+            app_id (Union[str, int]): the id of the apparat
+
+        Returns:
+            bool: True if the apparat is present, False if not
+        """
+        return True if self.query_db("SELECT appnr FROM semesterapparat WHERE appnr=?", (app_id,), one=True) else False
+    # Statistics
+    def statistic_request(self, **kwargs: Any):
+        """Take n amount of kwargs and return the result of the query
+        """
+        def __query(query):
+            """execute the query and return the result
+
+            Args:
+                query (str): the constructed query
+
+            Returns:
+                list: the result of the query
+            """
+            conn = self.connect()
+            cursor = conn.cursor()
+            result = cursor.execute(query).fetchall()
+            for result_a in result:
+                orig_value = result_a
+                prof_name = self.getProfNameById(result_a[2])
+                # replace the prof_id with the prof_name
+                result_a = list(result_a)
+                result_a[2] = prof_name
+                result_a = tuple(result_a)
+                result[result.index(orig_value)] = result_a
+            self.close_connection(conn)
+            return result
+        if "deletable" in kwargs.keys():
+            query = f"SELECT * FROM semesterapparat WHERE deletion_status=0 AND dauer=0 AND (erstellsemester!='{kwargs['deletesemester']}' OR verlängerung_bis!='{kwargs['deletesemester']}')"
+            return __query(query)
+        if "dauer" in kwargs.keys():
+            kwargs["dauer"] = kwargs["dauer"].replace("Ja", "1").replace("Nein", "0")
+        query = "SELECT * FROM semesterapparat WHERE "
+        for key, value in kwargs.items() if kwargs.items() is not None else {}:
+            print(key, value)
+            query += f"{key}='{value}' AND "
+            print(query)
+        # remove deletesemester part from normal query, as this will be added to the database upon deleting the apparat
+        if "deletesemester" in kwargs.keys():
+            query = query.replace(
+                f"deletesemester='{kwargs['deletesemester']}' AND ", ""
+            )
+        if "endsemester" in kwargs.keys():
+            if "erstellsemester" in kwargs.keys():
+                query = query.replace(f"endsemester='{kwargs['endsemester']}' AND ", "")
+                query = query.replace(
+                    f"erstellsemester='{kwargs['erstellsemester']} AND ", "xyz"
+                )
+            else:
+                query = query.replace(
+                    f"endsemester='{kwargs['endsemester']}' AND ", "xyz"
+                )
+                print("replaced")
+            query = query.replace(
+                "xyz",
+                f"(erstellsemester='{kwargs['endsemester']}' OR verlängerung_bis='{kwargs['endsemester']}') AND ",
+            )
+        # remove all x="" parts from the query where x is a key in kwargs
+        query = query[:-5]
+        print(query)
+        return __query(query)
+
+    # Admin data
+    def getUser(self):
+        """Get a single user from the database"""
+        return self.query_db("SELECT * FROM user", one=True)
+    def getUsers(self)->list[tuple]:
+        """Return a list of tuples of all the users in the database"""
+        return self.query_db("SELECT * FROM user")
+
+    def login(self, user, hashed_password):
+        """try to login the user.
+        The salt for the user will be requested from the database and then added to the hashed password. The password will then be compared to the password in the database 
+
+        Args:
+            user (str): username that tries to login
+            hashed_password (str): the password the user tries to login with
+
+        Returns:
+            bool: True if the login was successful, False if not
+        """
+        salt = self.query_db("SELECT salt FROM user WHERE username=?", (user,), one=True)[0]
+        if salt is None:
+            return False
+        hashed_password = salt + hashed_password
+        password = self.query_db("SELECT password FROM user WHERE username=?", (user,), one=True)[0]
+        if password == hashed_password:
+            return True
+        else:
+            return False
+    def changePassword(self, user, new_password):
+        """change the password of a user.
+        The password will be added with the salt and then committed to the database
+
+        Args:
+            user (str): username
+            new_password (str): the hashed password
+        """
+        salt = self.query_db("SELECT salt FROM user WHERE username=?", (user,), one=True)[0]
+        new_password = salt + new_password
+        self.query_db("UPDATE user SET password=? WHERE username=?", (new_password,user))
+    def getRole(self, user):
+        """get the role of the user
+
+        Args:
+            user (str): username
+
+        Returns:
+            str: the name of the role
+        """
+        return self.query_db("SELECT role FROM user WHERE username=?", (user,), one=True)[0]
+    def getRoles(self)->list[tuple]:
+        """get all the roles in the database
+
+        Returns:
+            list[str]: a list of all the roles
+        """
+        return self.query_db("SELECT role FROM user")
+    def checkUsername(self, user)->bool:
+        """a check to see if the username is already present in the database
+
+        Args:
+            user (str): the username
+
+        Returns:
+            bool: True if the username is present, False if not
+        """
+        data = self.query_db("SELECT username FROM user WHERE username=?", (user,), one=True)
+        return True if data is not None else False
+    def createUser(self, user, password, role, salt):
+        """create an user from the AdminCommands class.
+
+        Args:
+            user (str): the username of the user
+            password (str): a hashed password
+            role (str): the role of the user
+            salt (str): a salt for the password
+        """
+        self.query_db("INSERT OR IGNORE INTO user (username, password, role, salt) VALUES (?,?,?,?)", (user,password,role,salt))
+    def deleteUser(self, user):
+        """delete an unser
+
+        Args:
+            user (str): username of the user
+        """
+        self.query_db("DELETE FROM user WHERE username=?", (user,))
+    def updateUser(self, username, data:dict[str, str]):
+        """changge the data of a user
+
+        Args:
+            username (str): the username of the user
+            data (dict[str, str]): the data to be changed
+        """
+        conn = self.connect()
+        cursor = conn.cursor()
+        query = "UPDATE user SET "
+        for key,value in data.items():
+            if key == "username":
+                continue
+            query += f"{key}='{value}',"
+        query = query[:-1]
+        query += " WHERE username=?"
+        params = (username,)
+        cursor.execute(query, params)
+        conn.commit()
+        self.close_connection(conn)
+    def getFacultyMember(self, name:str)->tuple:
+        """get a faculty member based on the name
+
+        Args:
+            name (str): the name to be searched for
+
+        Returns:
+            tuple: a tuple containing the data of the faculty member
+        """
+        return self.query_db("SELECT titel, fname,lname,mail,telnr,fullname FROM prof WHERE fullname=?", (name,), one=True)
+    def updateFacultyMember(self, data:dict, oldlname:str, oldfname:str):
+        """update the data of a faculty member
+
+        Args:
+            data (dict): a dictionary containing the data to be updated
+            oldlname (str): the old last name of the faculty member
+            oldfname (str): the old first name of the faculty member
+        """
+        placeholders = ", ".join([f"{i}=:{i} " for i in data.keys()])
+        query = f"UPDATE prof SET {placeholders} WHERE lname = :oldlname AND fname = :oldfname"
+        data["oldlname"] = oldlname
+        data["oldfname"] = oldfname
+        self.query_db(query, data)
+    def getFacultyMembers(self):
+        """get a list of all faculty members
+
+        Returns:
+            list[tuple]: a list of tuples containing the faculty members
+        """
+        return self.query_db("SELECT titel, fname,lname,mail,telnr,fullname FROM prof")
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class Database +(db_path: str = None) +
+
+

Initialize the database and create the tables if they do not exist.

+

Default constructor for the database class

+

Args

+
+
db_path : str, optional
+
Optional Path for testing / specific purposes. Defaults to None.
+
+
+ +Expand source code + +
class Database:
+    """
+    Initialize the database and create the tables if they do not exist.
+    """
+    def __init__(self, db_path: str = None):
+        """
+        Default constructor for the database class
+
+        Args:
+            db_path (str, optional): Optional Path for testing / specific purposes. Defaults to None.
+        """
+        if db_path is None:
+            self.db_path = config.database.path + config.database.name
+            self.db_path = self.db_path.replace("~", str(Path.home()))
+        else:
+            self.db_path = db_path
+        if self.get_db_contents() is None:
+            logger.log_critical("Database does not exist, creating tables")
+            self.create_tables()
+    def get_db_contents(self)->Union[List[Tuple], None]:
+        """
+        Get the contents of the 
+
+        Returns:
+            Union[List[Tuple], None]: _description_
+        """
+        try:
+            with sql.connect(self.db_path) as conn:
+                cursor = conn.cursor()
+                cursor.execute("SELECT * FROM sqlite_master WHERE type='table'")
+                return cursor.fetchall()
+        except sql.OperationalError:
+            return None
+    def connect(self)->sql.Connection:
+        """
+        Connect to the database
+
+        Returns:
+            sql.Connection: The active connection to the database
+        """
+        return sql.connect(self.db_path)
+    def close_connection(self, conn: sql.Connection):
+        """
+        closes the connection to the database
+        
+        Args:
+        ----
+            - conn (sql.Connection): the connection to be closed
+        """
+        conn.close()
+    def create_tables(self):
+        """
+        Create the tables in the database
+        """
+        conn = self.connect()
+        cursor = conn.cursor()
+        cursor.execute(CREATE_TABLE_APPARAT)
+        cursor.execute(CREATE_TABLE_MESSAGES)
+        cursor.execute(CREATE_TABLE_MEDIA)
+        cursor.execute(CREATE_TABLE_APPKONTOS)
+        cursor.execute(CREATE_TABLE_FILES)
+        cursor.execute(CREATE_TABLE_PROF)
+        cursor.execute(CREATE_TABLE_USER)
+        cursor.execute(CREATE_TABLE_SUBJECTS)
+        conn.commit()
+        self.close_connection(conn)
+    def insertInto(self, query:str, params:Tuple) -> None:
+        """
+        Insert sent data into the database
+
+        Args:
+            query (str): The query to be executed
+            params (Tuple): the parameters to be inserted into the database
+        """
+        conn = self.connect()
+        cursor = conn.cursor()
+        logger.log_info(f"Inserting {params} into database with query {query}")
+        cursor.execute(query, params)
+        conn.commit()
+        self.close_connection(conn)
+    def query_db(self, query: str, args: Tuple = (), one: bool = False)->Union[Tuple, List[Tuple]]:
+        """
+        Query the Database for the sent query.
+
+        Args:
+            query (str): The query to be executed
+            args (Tuple, optional): The arguments for the query. Defaults to ().
+            one (bool, optional): Return the first result only. Defaults to False.
+
+        Returns:
+            Union[Typle|List[Tuple]]: Returns the result of the query
+        """
+        conn = self.connect()
+        cursor = conn.cursor()
+        logger.log_info(f"Querying database with query {query}, args: {args}")
+        cursor.execute(query, args)
+        rv = cursor.fetchall()
+        conn.commit()
+        self.close_connection(conn)
+        return (rv[0] if rv else None) if one else rv
+
+    # Books
+    def addBookToDatabase(self, bookdata:BookData,app_id:Union[str,int], prof_id:Union[str,int]):
+        """
+        Add books to the database. Both app_id and prof_id are required to add the book to the database, as the app_id and prof_id are used to select the books later on.
+
+        Args:
+            bookdata (BookData): The metadata of the book to be added
+            app_id (str): The apparat id where the book should be added to
+            prof_id (str): The id of the professor where the book should be added to.
+        """
+        conn = self.connect()
+        cursor = conn.cursor()
+        t_query = (
+            f"SELECT bookdata FROM media WHERE app_id={app_id} AND prof_id={prof_id}"
+        )
+        # print(t_query)
+        result = cursor.execute(t_query).fetchall()
+        result = [load_pickle(i[0]) for i in result]
+        if bookdata in result:
+            print("Bookdata already in database")
+            # check if the book was deleted in the apparat
+            query = (
+                "SELECT deleted FROM media WHERE app_id=? AND prof_id=? AND bookdata=?"
+            )
+            params = (app_id, prof_id, dump_pickle(bookdata))
+            result = cursor.execute(query, params).fetchone()
+            if result[0] == 1:
+                print("Book was deleted, updating bookdata")
+                query = "UPDATE media SET deleted=0 WHERE app_id=? AND prof_id=? AND bookdata=?"
+                params = (app_id, prof_id, dump_pickle(bookdata))
+                cursor.execute(query, params)
+                conn.commit()
+            return
+
+        query = (
+            "INSERT INTO media (bookdata, app_id, prof_id,deleted) VALUES (?, ?, ?,?)"
+        )
+        converted = dump_pickle(bookdata)
+        params = (converted, app_id, prof_id, 0)
+        cursor.execute(query, params)
+        conn.commit()
+        self.close_connection(conn)
+    def getBookIdBasedOnSignature(self, app_id:Union[str,int], prof_id:Union[str,int],signature:str)->int:
+        """
+        Get a book id based on the signature of the book.
+
+        Args:
+            app_id (str): The apparat id the book should be associated with
+            prof_id (str): The professor id the book should be associated with
+            signature (str): The signature of the book
+
+        Returns:
+            int: The id of the book
+        """
+        result = self.query_db("SELECT bookdata, id FROM media WHERE app_id=? AND prof_id=?", (app_id,prof_id))
+        books = [(load_pickle(i[0]),i[1]) for i in result]
+        book = [i for i in books if i[0].signature == signature][0][1]
+        return book
+    def getBookBasedOnSignature(self, app_id:Union[str,int], prof_id:Union[str,int],signature:str)->BookData:
+        """
+        Get the book based on the signature of the book.
+
+        Args:
+            app_id (str): The apparat id the book should be associated with
+            prof_id (str): The professor id the book should be associated with
+            signature (str): The signature of the book
+
+        Returns:
+            BookData: The total metadata of the book wrapped in a BookData object
+        """
+        result = self.query_db("SELECT bookdata FROM media WHERE app_id=? AND prof_id=?", (app_id,prof_id))
+        books = [load_pickle(i[0]) for i in result]
+        book = [i for i in books if i.signature == signature][0]
+        return book
+    def getLastBookId(self)->int:
+        """
+        Get the last book id in the database
+
+        Returns:
+            int: ID of the last book in the database
+        """
+        return self.query_db("SELECT id FROM media ORDER BY id DESC", one=True)[0]
+    def searchBook(self, data:dict[str, str])->list[tuple[BookData, int]]:
+        """
+        Search a book in the database based on the sent data.
+
+        Args:
+            data (dict[str, str]): A dictionary containing the data to be searched for. The dictionary can contain the following: 
+            - signature: The signature of the book
+            - title: The title of the book
+
+        Returns:
+            list[tuple[BookData, int]]: A list of tuples containing the wrapped Metadata and the id of the book
+        """
+        rdata = self.query_db("SELECT * FROM media WHERE deleted=0")
+        #ic(rdata, len(rdata))
+        mode = 0
+        if len(data)== 1:
+            if "signature" in data.keys():
+                mode = 1
+            elif "title" in data.keys():
+                mode = 2
+        elif len(data) == 2:
+            mode = 3
+        else:
+            return None
+        ret = []
+        for book in rdata:
+            bookdata = load_pickle(book[1])
+            app_id = book[2]
+            prof_id = book[3]
+            if mode == 1:
+                if data["signature"] in bookdata.signature:
+                    ret.append((bookdata,app_id,prof_id))
+            elif mode == 2:
+                if data["title"] in bookdata.title:
+                    ret.append((bookdata,app_id,prof_id))
+            elif mode == 3:
+                if data["signature"] in bookdata.signature and data["title"] in bookdata.title:
+                    ret.append((bookdata,app_id,prof_id))
+        #ic(ret)
+        return ret
+    def setAvailability(self, book_id:str, available:str):
+        """
+        Set the availability of a book in the database
+
+        Args:
+            book_id (str): The id of the book
+            available (str): The availability of the book
+        """
+        self.query_db("UPDATE media SET available=? WHERE id=?", (available,book_id))
+    def getBookId(self, bookdata:BookData, app_id:Union[str,int], prof_id:Union[str,int])->int:
+        """
+        Get the id of a book based on the metadata of the book
+
+        Args:
+            bookdata (BookData): The wrapped metadata of the book
+            app_id (str): The apparat id the book should be associated with
+            prof_id (str): The professor id the book should be associated with
+
+        Returns:
+            int: ID of the book
+        """
+        result = self.query_db("SELECT id FROM media WHERE bookdata=? AND app_id=? AND prof_id=?", (dump_pickle(bookdata),app_id,prof_id), one=True)
+        return result[0]
+    def getBook(self,book_id:int)->BookData:
+        """
+        Get the book based on the id in the database
+
+        Args:
+            book_id (int): The id of the book
+
+        Returns:
+            BookData: The metadata of the book wrapped in a BookData object
+        """
+        return load_pickle(self.query_db("SELECT bookdata FROM media WHERE id=?", (book_id,), one=True)[0])
+    def getBooks(self, app_id:Union[str,int], prof_id:Union[str,int], deleted=0)->list[dict[int, BookData, int]]:
+        """
+        Get the Books based on the apparat id and the professor id
+
+        Args:
+            app_id (str): The ID of the apparat
+            prof_id (str): The ID of the professor
+            deleted (int, optional): The state of the book. Set to 1 to include deleted ones. Defaults to 0.
+
+        Returns:
+            list[dict[int, BookData, int]]: A list of dictionaries containing the id, the metadata of the book and the availability of the book
+        """
+        qdata = self.query_db(f"SELECT id,bookdata,available FROM media WHERE (app_id={app_id} AND prof_id={prof_id}) AND (deleted={deleted if deleted == 0 else '1 OR deleted=0'})")
+        ret_result = []
+        for result_a in qdata:
+            data = {"id": int, "bookdata": BookData, "available": int}
+            data["id"] = result_a[0]
+            data["bookdata"] = load_pickle(result_a[1])
+            data["available"] = result_a[2]
+            ret_result.append(data)
+        return ret_result
+    def updateBookdata(self, book_id, bookdata:BookData):
+        """
+        Update the bookdata in the database
+
+        Args:
+            book_id (str): The id of the book
+            bookdata (BookData): The new metadata of the book
+        """
+        self.query_db("UPDATE media SET bookdata=? WHERE id=?", (dump_pickle(bookdata),book_id))
+    def deleteBook(self, book_id):
+        """
+        Delete a book from the database
+
+        Args:
+            book_id (str): ID of the book
+        """
+        self.query_db("UPDATE media SET deleted=1 WHERE id=?", (book_id,))
+
+    # File Interactions
+    def getBlob(self, filename, app_id:Union[str,int]):
+        """
+        Get a blob from the database
+
+        Args:
+            filename (str): The name of the file
+            app_id (str): ID of the apparat
+
+        Returns:
+            bytes: The file stored in 
+        """
+        return self.query_db("SELECT fileblob FROM files WHERE filename=? AND app_id=?", (filename,app_id), one=True)[0]
+    def insertFile(self, file: list[dict], app_id:Union[str,int], prof_id:Union[str,int]):
+        """Instert a list of files into the database
+
+        Args:
+            file (list[dict]): a list containing all the files to be inserted
+            Structured: [{"name": "filename", "path": "path", "type": "filetype"}]
+            app_id (int): the id of the apparat
+            prof_id (str): the id of the professor
+        """
+        for f in file:
+            filename = f["name"]
+            path = f["path"]
+            filetyp = f["type"]
+            if path == "Database":
+                continue
+            blob = create_blob(path)
+            query = "INSERT OR IGNORE INTO files (filename, fileblob, app_id, filetyp,prof_id) VALUES (?, ?, ?, ?,?)"
+            self.query_db(query, (filename, blob, app_id, filetyp,prof_id))
+    def recreateFile(self, filename:str, app_id:Union[str,int],filetype:str)->str:
+        """Recreate a file from the database
+
+        Args:
+            filename (str): the name of the file
+            app_id (Union[str,int]): the id of the apparat
+            filetype (str): the extension of the file to be created
+
+        Returns:
+            str: The filename of the recreated file 
+        """
+        blob = self.getBlob(filename, app_id)
+        tempdir = config.database.tempdir
+        tempdir = tempdir.replace("~", str(Path.home()))
+        tempdir_path = Path(tempdir)        
+        if not os.path.exists(tempdir_path):
+            os.mkdir(tempdir_path)
+        file = tempfile.NamedTemporaryFile(
+            delete=False, dir=tempdir_path, mode="wb", suffix=f".{filetype}"
+        )
+        file.write(blob)
+        print("file created")
+        return file.name
+    def getFiles(self, app_id:Union[str,int], prof_id:int)->list[tuple]:
+        """Get all the files associated with the apparat and the professor
+
+        Args:
+            app_id (Union[str,int]): The id of the apparat
+            prof_id (Union[str,int]): the id of the professor
+
+        Returns:
+            list[tuple]: a list of tuples containing the filename and the filetype for the corresponding apparat and professor
+        """
+        return self.query_db("SELECT filename, filetyp FROM files WHERE app_id=? AND prof_id=?", (app_id,prof_id))
+
+    def getSemersters(self)->list[str]:
+        """Return all the unique semesters in the database
+
+        Returns:
+            list: a list of strings containing the semesters
+        """
+        data = self.query_db("SELECT DISTINCT erstellsemester FROM semesterapparat")
+        return [i[0] for i in data]
+
+    def getSubjects(self):
+        """Get all the subjects in the database
+
+        Returns:
+            list[tuple]: a list of tuples containing the subjects
+        """
+        return self.query_db("SELECT * FROM subjects")
+
+    # Messages
+    def addMessage(self, message:dict,user:str, app_id:Union[str,int]):
+        """add a Message to the database
+
+        Args:
+            message (dict): the message to be added
+            user (str): the user who added the message
+            app_id (Union[str,int]): the id of the apparat
+        """
+        def __getUserId(user):
+            return self.query_db("SELECT id FROM user WHERE username=?", (user,), one=True)[0]
+        user_id = __getUserId(user)
+        self.query_db("INSERT INTO messages (message, user_id, remind_at,appnr) VALUES (?,?,?,?)", (message["message"],user_id,message["remind_at"],app_id))
+    def getMessages(self, date:str)->list[dict[str, str, str, str]]:
+        """Get all the messages for a specific date
+
+        Args:
+            date (str): a date.datetime object formatted as a string in the format "YYYY-MM-DD"
+        
+        Returns:
+            list[dict[str, str, str, str]]: a list of dictionaries containing the message, the user who added the message, the apparat id and the id of the message
+        """
+        def __get_user_name(user_id):
+            return self.query_db("SELECT username FROM user WHERE id=?", (user_id,), one=True)[0]
+        messages = self.query_db("SELECT * FROM messages WHERE remind_at=?", (date,))
+        ret = [
+            {
+                "message": i[2],
+                "user": __get_user_name(i[4]),
+                "appnr": i[5],
+                "id": i[0]
+            }
+            for i in messages
+        ]
+        return ret
+    def deleteMessage(self, message_id):
+        """Delete a message from the database
+
+        Args:
+            message_id (str): the id of the message
+        """
+        self.query_db("DELETE FROM messages WHERE id=?", (message_id,))
+
+    # Prof data
+    def getProfNameById(self, prof_id:Union[str,int],add_title:bool=False)->str:
+        """Get a professor name based on the id
+
+        Args:
+            prof_id (Union[str,int]): The id of the professor
+            add_title (bool, optional): wether to add the title or no. Defaults to False.
+
+        Returns:
+            str: The name of the professor
+        """
+        prof = self.query_db("SELECT fullname FROM prof WHERE id=?", (prof_id,), one=True)
+        if add_title:
+            return f"{self.getTitleById(prof_id)}{prof[0]}"
+        else:
+            return prof[0]
+    def getTitleById(self, prof_id:Union[str,int])->str:
+        """get the title of a professor based on the id
+
+        Args:
+            prof_id (Union[str,int]): the id of the professor
+
+        Returns:
+            str: the title of the professor, with an added whitespace at the end, if no title is present, an empty string is returned
+        """
+        title = self.query_db("SELECT titel FROM prof WHERE id=?", (prof_id,), one=True)[0]
+        return f"{title} " if title is not None else ""
+    def getProfByName(self, prof_name:str)->tuple:
+        """get all the data of a professor based on the name
+
+        Args:
+            prof_name (str): the name of the professor
+
+        Returns:
+            tuple: the data of the professor
+        """
+        return self.query_db("SELECT * FROM prof WHERE fullname=?", (prof_name,), one=True)
+    def getProfId(self, prof_name:str)->Optional[int]:
+        """Get the id of a professor based on the name
+
+        Args:
+            prof_name (str): the name of the professor
+
+        Returns:
+            Optional[int]: the id of the professor, if the professor is not found, None is returned
+        """
+        
+        data = self.getProfByName(prof_name.replace(",", ""))
+        if data is None:
+            return None
+        else:
+            return data[0]
+    def getSpecificProfData(self, prof_id:Union[str,int], fields:List[str])->tuple:
+        """A customisable function to get specific data of a professor based on the id
+
+        Args:
+            prof_id (Union[str,int]): the id of the professor
+            fields (List[str]): a list of fields to be returned
+
+        Returns:
+            tuple: a tuple containing the requested data
+        """
+        query = "SELECT "
+        for field in fields:
+            query += f"{field},"
+        query = query[:-1]
+        query += " FROM prof WHERE id=?"
+        return self.query_db(query, (prof_id,), one=True)[0]
+    def getProfData(self, profname:str):
+        """Get mail, telephone number and title of a professor based on the name
+
+        Args:
+            profname (str): name of the professor
+
+        Returns:
+            tuple: the mail, telephone number and title of the professor
+        """
+        data = self.query_db("SELECT mail, telnr, titel FROM prof WHERE fullname=?", (profname.replace(",",""),), one=True)
+        return data
+    def createProf(self, prof_details:dict):
+        """Create a professor in the database
+
+        Args:
+            prof_details (dict): a dictionary containing the details of the professor
+        """
+        prof_title = prof_details["prof_title"]
+        prof_fname = prof_details["profname"].split(",")[1]
+        prof_fname = prof_fname.strip()
+        prof_lname = prof_details["profname"].split(",")[0]
+        prof_lname = prof_lname.strip()
+        prof_fullname = prof_details["profname"].replace(",", "")
+        prof_mail = prof_details["prof_mail"]
+        prof_tel = prof_details["prof_tel"]
+        params = (prof_title, prof_fname, prof_lname, prof_mail, prof_tel, prof_fullname)
+        query = "INSERT OR IGNORE INTO prof (titel, fname, lname, mail, telnr, fullname) VALUES (?, ?, ?, ?, ?, ?)"
+        self.insertInto(query=query, params=params)
+    def getProfs(self)->list[tuple]:
+        """Return all the professors in the database
+
+        Returns:
+            list[tuple]: a list containing all the professors in individual tuples
+        """
+        return self.query_db("SELECT * FROM prof")
+
+    # Apparat
+    def getAllAparats(self,deleted=0)->list[tuple]:
+        """Get all the apparats in the database
+
+        Args:
+            deleted (int, optional): Switch the result to use . Defaults to 0.
+
+        Returns:
+            list[tuple]: a list of tuples containing the apparats
+        """
+        return self.query_db("SELECT * FROM semesterapparat WHERE deletion_status=?", (deleted,))
+    def getApparatData(self, appnr, appname)->ApparatData:
+        """Get the Apparat data based on the apparat number and the name
+
+        Args:
+            appnr (str): the apparat number
+            appname (str): the name of the apparat
+
+        Raises:
+            NoResultError: an error is raised if no result is found
+
+        Returns:
+            ApparatData: the appended data of the apparat wrapped in an ApparatData object
+        """
+        result = self.query_db("SELECT * FROM semesterapparat WHERE appnr=? AND name=?", (appnr,appname), one=True)
+        if result is None:
+            raise NoResultError("No result found")
+        apparat = ApparatData()
+        apparat.appname = result[1]
+        apparat.appnr = result[4]
+        apparat.dauerapp = True if result[7] == 1 else False
+        prof_data = self.getProfData(self.getProfNameById(result[2]))
+        apparat.profname = self.getProfNameById(result[2])
+        apparat.prof_mail = prof_data[0]
+        apparat.prof_tel = prof_data[1]
+        apparat.prof_title = prof_data[2]
+        apparat.app_fach = result[3]
+        apparat.erstellsemester = result[5]
+        apparat.semester = result[8]
+        apparat.deleted = result[9]
+        apparat.apparat_adis_id = result[11]
+        apparat.prof_adis_id = result[12]
+        return apparat
+    def getUnavailableApparatNumbers(self)->List[int]:
+        """Get a list of all the apparat numbers in the database that are currently in use
+
+        Returns:
+            List[int]: the list of used apparat numbers
+        """
+        numbers = self.query_db("SELECT appnr FROM semesterapparat WHERE deletion_status=0")    
+        numbers = [i[0] for i in numbers]
+        logger.log_info(f"Currently used apparat numbers: {numbers}")
+        return numbers
+    def setNewSemesterDate(self, app_id:Union[str,int], newDate, dauerapp=False):
+        """Set the new semester date for an apparat
+
+        Args:
+            app_id (Union[str,int]): the id of the apparat
+            newDate (str): the new date
+            dauerapp (bool, optional): if the apparat was changed to dauerapparat. Defaults to False.
+        """
+        date = datetime.datetime.strptime(newDate, "%d.%m.%Y").strftime("%Y-%m-%d")
+        if dauerapp:
+            self.query_db("UPDATE semesterapparat SET verlängerung_bis=?, dauerapp=? WHERE appnr=?", (date,dauerapp,app_id))
+        else:
+            self.query_db("UPDATE semesterapparat SET endsemester=? WHERE appnr=?", (date,app_id))
+    def getApparatId(self, apparat_name)->Optional[int]:
+        """get the id of an apparat based on the name
+
+        Args:
+            apparat_name (str): the name of the apparat e.g. "Semesterapparat 1"
+
+        Returns:
+            Optional[int]: the id of the apparat, if the apparat is not found, None is returned
+        """
+        data = self.query_db("SELECT appnr FROM semesterapparat WHERE name=?", (apparat_name,), one=True)
+        if data is None:
+            return None
+        else:
+            return data[0]
+    def createApparat(self, apparat:ApparatData)->int:
+        """create the apparat in the database
+
+        Args:
+            apparat (ApparatData): the wrapped metadata of the apparat
+
+        Raises:
+            AppPresentError: an error describing that the apparats chosen id is already present in the database
+
+        Returns:
+            Optional[int]: the id of the apparat
+        """
+                
+        prof_id = self.getProfId(apparat.profname)
+        app_id = self.getApparatId(apparat.appname)
+        if app_id:
+            raise AppPresentError(app_id)
+
+        self.createProf(apparat.get_prof_details())
+        prof_id = self.getProfId(apparat.profname)
+        #ic(prof_id)
+        query = f"INSERT OR IGNORE INTO semesterapparat (appnr, name, erstellsemester, dauer, prof_id, fach,deletion_status,konto) VALUES ('{apparat.appnr}', '{apparat.appname}', '{apparat.semester}', '{apparat.dauerapp}', {prof_id}, '{apparat.app_fach}', '{0}', '{SEMAP_MEDIA_ACCOUNTS[apparat.appnr]}')"
+        logger.log_info(query)
+        self.query_db(query)
+        return self.getApparatId(apparat.appname)
+    def getApparatsByProf(self, prof_id:Union[str,int])->list[tuple]:
+        """Get all apparats based on the professor id
+
+        Args:
+            prof_id (Union[str,int]): the id of the professor
+
+        Returns:
+            list[tuple]: a list of tuples containing the apparats
+        """
+        return self.query_db("SELECT * FROM semesterapparat WHERE prof_id=?", (prof_id,))
+    def getApparatsBySemester(self, semester:str)->dict[list]:
+        """get all apparats based on the semester
+
+        Args:
+            semester (str): the selected semester
+
+        Returns:
+            dict[list]: a list off all created and deleted apparats for the selected semester
+        """
+        data = self.query_db("SELECT name, prof_id FROM semesterapparat WHERE erstellsemester=?", (semester,))
+        conn = self.connect()
+        cursor = conn.cursor()
+        c_tmp = []
+        for i in data:
+            c_tmp.append((i[0], self.getProfNameById(i[1])))
+        query = (
+            f"SELECT name,prof_id FROM semesterapparat WHERE deleted_date='{semester}'"
+        )
+        result = cursor.execute(query).fetchall()
+        d_tmp = []
+        for i in result:
+            d_tmp.append((i[0], self.getProfNameById(i[1])))
+        # group the apparats by prof
+        c_ret = {}
+        for i in c_tmp:
+            if i[1] not in c_ret.keys():
+                c_ret[i[1]] = [i[0]]
+            else:
+                c_ret[i[1]].append(i[0])
+        d_ret = {}
+        for i in d_tmp:
+            if i[1] not in d_ret.keys():
+                d_ret[i[1]] = [i[0]]
+            else:
+                d_ret[i[1]].append(i[0])
+        self.close_connection(conn)
+        return {"created": c_ret, "deleted": d_ret}
+    def getApparatCountBySemester(self)->tuple[list[str],list[int]]:
+        """get a list of all apparats created and deleted by semester
+
+        Returns:
+            tuple[list[str],list[int]]: a tuple containing two lists, the first list contains the semesters, the second list contains the amount of apparats created and deleted for the corresponding semester
+        """
+        conn = self.connect()
+        cursor = conn.cursor()
+        semesters = self.getSemersters()
+        created = []
+        deleted = []
+        for semester in semesters:
+            query = f"SELECT COUNT(*) FROM semesterapparat WHERE erstellsemester='{semester}'"
+            result = cursor.execute(query).fetchone()
+            created.append(result[0])
+            query = f"SELECT COUNT(*) FROM semesterapparat WHERE deletion_status=1 AND deleted_date='{semester}'"
+            result = cursor.execute(query).fetchone()
+            deleted.append(result[0])
+        # store data in a tuple
+        ret = []
+        e_tuple = ()
+        for sem in semesters:
+            e_tuple = (
+                sem,
+                created[semesters.index(sem)],
+                deleted[semesters.index(sem)],
+            )
+            ret.append(e_tuple)
+        self.close_connection(conn)
+        return ret 
+    def deleteApparat(self, app_id:Union[str,int], semester:str):
+        """Delete an apparat from the database
+
+        Args:
+            app_id (Union[str, int]): the id of the apparat
+            semester (str): the semester the apparat should be deleted from
+        """
+        self.query_db("UPDATE semesterapparat SET deletion_status=1, deleted_date=? WHERE appnr=?", (semester,app_id)) 
+    def isEternal(self, id):
+        """check if the apparat is eternal (dauerapparat)
+
+        Args:
+            id (int): the id of the apparat to be checked
+
+        Returns:
+            int: the state of the apparat
+        """
+        return self.query_db("SELECT dauer FROM semesterapparat WHERE appnr=?", (id,), one=True)
+    def getApparatName(self, app_id:Union[str,int], prof_id:Union[str,int]):
+        """get the name of the apparat based on the id
+
+        Args:
+            app_id (Union[str,int]): the id of the apparat
+            prof_id (Union[str,int]): the id of the professor
+
+        Returns:
+            str: the name of the apparat
+        """
+        return self.query_db("SELECT name FROM semesterapparat WHERE appnr=? AND prof_id=?", (app_id,prof_id), one=True)[0]
+    def updateApparat(self, apparat_data:ApparatData):
+        """Update an apparat in the database
+
+        Args:
+            apparat_data (ApparatData): the new metadata of the apparat
+        """
+        query = f"UPDATE semesterapparat SET name = ?, fach = ?, dauer = ?, prof_id = ? WHERE appnr = ?"
+        params = (
+            apparat_data.appname,
+            apparat_data.app_fach,
+            apparat_data.dauerapp,
+            self.getProfId(apparat_data.profname),
+            apparat_data.appnr,        
+        )
+        self.query_db(query, params)
+    def checkApparatExists(self, apparat_name:str):
+        """check if the apparat is already present in the database based on the name
+
+        Args:
+            apparat_name (str): the name of the apparat
+
+        Returns:
+            bool: True if the apparat is present, False if not
+        """
+        return True if self.query_db("SELECT appnr FROM semesterapparat WHERE name=?", (apparat_name,), one=True) else False
+    def checkApparatExistsById(self, app_id:Union[str,int])->bool:
+        """a check to see if the apparat is already present in the database, based on the id
+
+        Args:
+            app_id (Union[str, int]): the id of the apparat
+
+        Returns:
+            bool: True if the apparat is present, False if not
+        """
+        return True if self.query_db("SELECT appnr FROM semesterapparat WHERE appnr=?", (app_id,), one=True) else False
+    # Statistics
+    def statistic_request(self, **kwargs: Any):
+        """Take n amount of kwargs and return the result of the query
+        """
+        def __query(query):
+            """execute the query and return the result
+
+            Args:
+                query (str): the constructed query
+
+            Returns:
+                list: the result of the query
+            """
+            conn = self.connect()
+            cursor = conn.cursor()
+            result = cursor.execute(query).fetchall()
+            for result_a in result:
+                orig_value = result_a
+                prof_name = self.getProfNameById(result_a[2])
+                # replace the prof_id with the prof_name
+                result_a = list(result_a)
+                result_a[2] = prof_name
+                result_a = tuple(result_a)
+                result[result.index(orig_value)] = result_a
+            self.close_connection(conn)
+            return result
+        if "deletable" in kwargs.keys():
+            query = f"SELECT * FROM semesterapparat WHERE deletion_status=0 AND dauer=0 AND (erstellsemester!='{kwargs['deletesemester']}' OR verlängerung_bis!='{kwargs['deletesemester']}')"
+            return __query(query)
+        if "dauer" in kwargs.keys():
+            kwargs["dauer"] = kwargs["dauer"].replace("Ja", "1").replace("Nein", "0")
+        query = "SELECT * FROM semesterapparat WHERE "
+        for key, value in kwargs.items() if kwargs.items() is not None else {}:
+            print(key, value)
+            query += f"{key}='{value}' AND "
+            print(query)
+        # remove deletesemester part from normal query, as this will be added to the database upon deleting the apparat
+        if "deletesemester" in kwargs.keys():
+            query = query.replace(
+                f"deletesemester='{kwargs['deletesemester']}' AND ", ""
+            )
+        if "endsemester" in kwargs.keys():
+            if "erstellsemester" in kwargs.keys():
+                query = query.replace(f"endsemester='{kwargs['endsemester']}' AND ", "")
+                query = query.replace(
+                    f"erstellsemester='{kwargs['erstellsemester']} AND ", "xyz"
+                )
+            else:
+                query = query.replace(
+                    f"endsemester='{kwargs['endsemester']}' AND ", "xyz"
+                )
+                print("replaced")
+            query = query.replace(
+                "xyz",
+                f"(erstellsemester='{kwargs['endsemester']}' OR verlängerung_bis='{kwargs['endsemester']}') AND ",
+            )
+        # remove all x="" parts from the query where x is a key in kwargs
+        query = query[:-5]
+        print(query)
+        return __query(query)
+
+    # Admin data
+    def getUser(self):
+        """Get a single user from the database"""
+        return self.query_db("SELECT * FROM user", one=True)
+    def getUsers(self)->list[tuple]:
+        """Return a list of tuples of all the users in the database"""
+        return self.query_db("SELECT * FROM user")
+
+    def login(self, user, hashed_password):
+        """try to login the user.
+        The salt for the user will be requested from the database and then added to the hashed password. The password will then be compared to the password in the database 
+
+        Args:
+            user (str): username that tries to login
+            hashed_password (str): the password the user tries to login with
+
+        Returns:
+            bool: True if the login was successful, False if not
+        """
+        salt = self.query_db("SELECT salt FROM user WHERE username=?", (user,), one=True)[0]
+        if salt is None:
+            return False
+        hashed_password = salt + hashed_password
+        password = self.query_db("SELECT password FROM user WHERE username=?", (user,), one=True)[0]
+        if password == hashed_password:
+            return True
+        else:
+            return False
+    def changePassword(self, user, new_password):
+        """change the password of a user.
+        The password will be added with the salt and then committed to the database
+
+        Args:
+            user (str): username
+            new_password (str): the hashed password
+        """
+        salt = self.query_db("SELECT salt FROM user WHERE username=?", (user,), one=True)[0]
+        new_password = salt + new_password
+        self.query_db("UPDATE user SET password=? WHERE username=?", (new_password,user))
+    def getRole(self, user):
+        """get the role of the user
+
+        Args:
+            user (str): username
+
+        Returns:
+            str: the name of the role
+        """
+        return self.query_db("SELECT role FROM user WHERE username=?", (user,), one=True)[0]
+    def getRoles(self)->list[tuple]:
+        """get all the roles in the database
+
+        Returns:
+            list[str]: a list of all the roles
+        """
+        return self.query_db("SELECT role FROM user")
+    def checkUsername(self, user)->bool:
+        """a check to see if the username is already present in the database
+
+        Args:
+            user (str): the username
+
+        Returns:
+            bool: True if the username is present, False if not
+        """
+        data = self.query_db("SELECT username FROM user WHERE username=?", (user,), one=True)
+        return True if data is not None else False
+    def createUser(self, user, password, role, salt):
+        """create an user from the AdminCommands class.
+
+        Args:
+            user (str): the username of the user
+            password (str): a hashed password
+            role (str): the role of the user
+            salt (str): a salt for the password
+        """
+        self.query_db("INSERT OR IGNORE INTO user (username, password, role, salt) VALUES (?,?,?,?)", (user,password,role,salt))
+    def deleteUser(self, user):
+        """delete an unser
+
+        Args:
+            user (str): username of the user
+        """
+        self.query_db("DELETE FROM user WHERE username=?", (user,))
+    def updateUser(self, username, data:dict[str, str]):
+        """changge the data of a user
+
+        Args:
+            username (str): the username of the user
+            data (dict[str, str]): the data to be changed
+        """
+        conn = self.connect()
+        cursor = conn.cursor()
+        query = "UPDATE user SET "
+        for key,value in data.items():
+            if key == "username":
+                continue
+            query += f"{key}='{value}',"
+        query = query[:-1]
+        query += " WHERE username=?"
+        params = (username,)
+        cursor.execute(query, params)
+        conn.commit()
+        self.close_connection(conn)
+    def getFacultyMember(self, name:str)->tuple:
+        """get a faculty member based on the name
+
+        Args:
+            name (str): the name to be searched for
+
+        Returns:
+            tuple: a tuple containing the data of the faculty member
+        """
+        return self.query_db("SELECT titel, fname,lname,mail,telnr,fullname FROM prof WHERE fullname=?", (name,), one=True)
+    def updateFacultyMember(self, data:dict, oldlname:str, oldfname:str):
+        """update the data of a faculty member
+
+        Args:
+            data (dict): a dictionary containing the data to be updated
+            oldlname (str): the old last name of the faculty member
+            oldfname (str): the old first name of the faculty member
+        """
+        placeholders = ", ".join([f"{i}=:{i} " for i in data.keys()])
+        query = f"UPDATE prof SET {placeholders} WHERE lname = :oldlname AND fname = :oldfname"
+        data["oldlname"] = oldlname
+        data["oldfname"] = oldfname
+        self.query_db(query, data)
+    def getFacultyMembers(self):
+        """get a list of all faculty members
+
+        Returns:
+            list[tuple]: a list of tuples containing the faculty members
+        """
+        return self.query_db("SELECT titel, fname,lname,mail,telnr,fullname FROM prof")
+
+

Methods

+
+
+def addBookToDatabase(self, bookdata: src.logic.dataclass.BookData, app_id: Union[str, int], prof_id: Union[str, int]) +
+
+

Add books to the database. Both app_id and prof_id are required to add the book to the database, as the app_id and prof_id are used to select the books later on.

+

Args

+
+
bookdata : BookData
+
The metadata of the book to be added
+
app_id : str
+
The apparat id where the book should be added to
+
prof_id : str
+
The id of the professor where the book should be added to.
+
+
+ +Expand source code + +
def addBookToDatabase(self, bookdata:BookData,app_id:Union[str,int], prof_id:Union[str,int]):
+    """
+    Add books to the database. Both app_id and prof_id are required to add the book to the database, as the app_id and prof_id are used to select the books later on.
+
+    Args:
+        bookdata (BookData): The metadata of the book to be added
+        app_id (str): The apparat id where the book should be added to
+        prof_id (str): The id of the professor where the book should be added to.
+    """
+    conn = self.connect()
+    cursor = conn.cursor()
+    t_query = (
+        f"SELECT bookdata FROM media WHERE app_id={app_id} AND prof_id={prof_id}"
+    )
+    # print(t_query)
+    result = cursor.execute(t_query).fetchall()
+    result = [load_pickle(i[0]) for i in result]
+    if bookdata in result:
+        print("Bookdata already in database")
+        # check if the book was deleted in the apparat
+        query = (
+            "SELECT deleted FROM media WHERE app_id=? AND prof_id=? AND bookdata=?"
+        )
+        params = (app_id, prof_id, dump_pickle(bookdata))
+        result = cursor.execute(query, params).fetchone()
+        if result[0] == 1:
+            print("Book was deleted, updating bookdata")
+            query = "UPDATE media SET deleted=0 WHERE app_id=? AND prof_id=? AND bookdata=?"
+            params = (app_id, prof_id, dump_pickle(bookdata))
+            cursor.execute(query, params)
+            conn.commit()
+        return
+
+    query = (
+        "INSERT INTO media (bookdata, app_id, prof_id,deleted) VALUES (?, ?, ?,?)"
+    )
+    converted = dump_pickle(bookdata)
+    params = (converted, app_id, prof_id, 0)
+    cursor.execute(query, params)
+    conn.commit()
+    self.close_connection(conn)
+
+
+
+def addMessage(self, message: dict, user: str, app_id: Union[str, int]) +
+
+

add a Message to the database

+

Args

+
+
message : dict
+
the message to be added
+
user : str
+
the user who added the message
+
app_id : Union[str,int]
+
the id of the apparat
+
+
+ +Expand source code + +
def addMessage(self, message:dict,user:str, app_id:Union[str,int]):
+    """add a Message to the database
+
+    Args:
+        message (dict): the message to be added
+        user (str): the user who added the message
+        app_id (Union[str,int]): the id of the apparat
+    """
+    def __getUserId(user):
+        return self.query_db("SELECT id FROM user WHERE username=?", (user,), one=True)[0]
+    user_id = __getUserId(user)
+    self.query_db("INSERT INTO messages (message, user_id, remind_at,appnr) VALUES (?,?,?,?)", (message["message"],user_id,message["remind_at"],app_id))
+
+
+
+def changePassword(self, user, new_password) +
+
+

change the password of a user. +The password will be added with the salt and then committed to the database

+

Args

+
+
user : str
+
username
+
new_password : str
+
the hashed password
+
+
+ +Expand source code + +
def changePassword(self, user, new_password):
+    """change the password of a user.
+    The password will be added with the salt and then committed to the database
+
+    Args:
+        user (str): username
+        new_password (str): the hashed password
+    """
+    salt = self.query_db("SELECT salt FROM user WHERE username=?", (user,), one=True)[0]
+    new_password = salt + new_password
+    self.query_db("UPDATE user SET password=? WHERE username=?", (new_password,user))
+
+
+
+def checkApparatExists(self, apparat_name: str) +
+
+

check if the apparat is already present in the database based on the name

+

Args

+
+
apparat_name : str
+
the name of the apparat
+
+

Returns

+
+
bool
+
True if the apparat is present, False if not
+
+
+ +Expand source code + +
def checkApparatExists(self, apparat_name:str):
+    """check if the apparat is already present in the database based on the name
+
+    Args:
+        apparat_name (str): the name of the apparat
+
+    Returns:
+        bool: True if the apparat is present, False if not
+    """
+    return True if self.query_db("SELECT appnr FROM semesterapparat WHERE name=?", (apparat_name,), one=True) else False
+
+
+
+def checkApparatExistsById(self, app_id: Union[str, int]) ‑> bool +
+
+

a check to see if the apparat is already present in the database, based on the id

+

Args

+
+
app_id : Union[str, int]
+
the id of the apparat
+
+

Returns

+
+
bool
+
True if the apparat is present, False if not
+
+
+ +Expand source code + +
def checkApparatExistsById(self, app_id:Union[str,int])->bool:
+    """a check to see if the apparat is already present in the database, based on the id
+
+    Args:
+        app_id (Union[str, int]): the id of the apparat
+
+    Returns:
+        bool: True if the apparat is present, False if not
+    """
+    return True if self.query_db("SELECT appnr FROM semesterapparat WHERE appnr=?", (app_id,), one=True) else False
+
+
+
+def checkUsername(self, user) ‑> bool +
+
+

a check to see if the username is already present in the database

+

Args

+
+
user : str
+
the username
+
+

Returns

+
+
bool
+
True if the username is present, False if not
+
+
+ +Expand source code + +
def checkUsername(self, user)->bool:
+    """a check to see if the username is already present in the database
+
+    Args:
+        user (str): the username
+
+    Returns:
+        bool: True if the username is present, False if not
+    """
+    data = self.query_db("SELECT username FROM user WHERE username=?", (user,), one=True)
+    return True if data is not None else False
+
+
+
+def close_connection(self, conn: sqlite3.Connection) +
+
+

closes the connection to the database

+

Args:

+
- conn (sql.Connection): the connection to be closed
+
+
+ +Expand source code + +
def close_connection(self, conn: sql.Connection):
+    """
+    closes the connection to the database
+    
+    Args:
+    ----
+        - conn (sql.Connection): the connection to be closed
+    """
+    conn.close()
+
+
+
+def connect(self) ‑> sqlite3.Connection +
+
+

Connect to the database

+

Returns

+
+
sql.Connection
+
The active connection to the database
+
+
+ +Expand source code + +
def connect(self)->sql.Connection:
+    """
+    Connect to the database
+
+    Returns:
+        sql.Connection: The active connection to the database
+    """
+    return sql.connect(self.db_path)
+
+
+
+def createApparat(self, apparat: src.logic.dataclass.ApparatData) ‑> int +
+
+

create the apparat in the database

+

Args

+
+
apparat : ApparatData
+
the wrapped metadata of the apparat
+
+

Raises

+
+
AppPresentError
+
an error describing that the apparats chosen id is already present in the database
+
+

Returns

+
+
Optional[int]
+
the id of the apparat
+
+
+ +Expand source code + +
def createApparat(self, apparat:ApparatData)->int:
+    """create the apparat in the database
+
+    Args:
+        apparat (ApparatData): the wrapped metadata of the apparat
+
+    Raises:
+        AppPresentError: an error describing that the apparats chosen id is already present in the database
+
+    Returns:
+        Optional[int]: the id of the apparat
+    """
+            
+    prof_id = self.getProfId(apparat.profname)
+    app_id = self.getApparatId(apparat.appname)
+    if app_id:
+        raise AppPresentError(app_id)
+
+    self.createProf(apparat.get_prof_details())
+    prof_id = self.getProfId(apparat.profname)
+    #ic(prof_id)
+    query = f"INSERT OR IGNORE INTO semesterapparat (appnr, name, erstellsemester, dauer, prof_id, fach,deletion_status,konto) VALUES ('{apparat.appnr}', '{apparat.appname}', '{apparat.semester}', '{apparat.dauerapp}', {prof_id}, '{apparat.app_fach}', '{0}', '{SEMAP_MEDIA_ACCOUNTS[apparat.appnr]}')"
+    logger.log_info(query)
+    self.query_db(query)
+    return self.getApparatId(apparat.appname)
+
+
+
+def createProf(self, prof_details: dict) +
+
+

Create a professor in the database

+

Args

+
+
prof_details : dict
+
a dictionary containing the details of the professor
+
+
+ +Expand source code + +
def createProf(self, prof_details:dict):
+    """Create a professor in the database
+
+    Args:
+        prof_details (dict): a dictionary containing the details of the professor
+    """
+    prof_title = prof_details["prof_title"]
+    prof_fname = prof_details["profname"].split(",")[1]
+    prof_fname = prof_fname.strip()
+    prof_lname = prof_details["profname"].split(",")[0]
+    prof_lname = prof_lname.strip()
+    prof_fullname = prof_details["profname"].replace(",", "")
+    prof_mail = prof_details["prof_mail"]
+    prof_tel = prof_details["prof_tel"]
+    params = (prof_title, prof_fname, prof_lname, prof_mail, prof_tel, prof_fullname)
+    query = "INSERT OR IGNORE INTO prof (titel, fname, lname, mail, telnr, fullname) VALUES (?, ?, ?, ?, ?, ?)"
+    self.insertInto(query=query, params=params)
+
+
+
+def createUser(self, user, password, role, salt) +
+
+

create an user from the AdminCommands class.

+

Args

+
+
user : str
+
the username of the user
+
password : str
+
a hashed password
+
role : str
+
the role of the user
+
salt : str
+
a salt for the password
+
+
+ +Expand source code + +
def createUser(self, user, password, role, salt):
+    """create an user from the AdminCommands class.
+
+    Args:
+        user (str): the username of the user
+        password (str): a hashed password
+        role (str): the role of the user
+        salt (str): a salt for the password
+    """
+    self.query_db("INSERT OR IGNORE INTO user (username, password, role, salt) VALUES (?,?,?,?)", (user,password,role,salt))
+
+
+
+def create_tables(self) +
+
+

Create the tables in the database

+
+ +Expand source code + +
def create_tables(self):
+    """
+    Create the tables in the database
+    """
+    conn = self.connect()
+    cursor = conn.cursor()
+    cursor.execute(CREATE_TABLE_APPARAT)
+    cursor.execute(CREATE_TABLE_MESSAGES)
+    cursor.execute(CREATE_TABLE_MEDIA)
+    cursor.execute(CREATE_TABLE_APPKONTOS)
+    cursor.execute(CREATE_TABLE_FILES)
+    cursor.execute(CREATE_TABLE_PROF)
+    cursor.execute(CREATE_TABLE_USER)
+    cursor.execute(CREATE_TABLE_SUBJECTS)
+    conn.commit()
+    self.close_connection(conn)
+
+
+
+def deleteApparat(self, app_id: Union[str, int], semester: str) +
+
+

Delete an apparat from the database

+

Args

+
+
app_id : Union[str, int]
+
the id of the apparat
+
semester : str
+
the semester the apparat should be deleted from
+
+
+ +Expand source code + +
def deleteApparat(self, app_id:Union[str,int], semester:str):
+    """Delete an apparat from the database
+
+    Args:
+        app_id (Union[str, int]): the id of the apparat
+        semester (str): the semester the apparat should be deleted from
+    """
+    self.query_db("UPDATE semesterapparat SET deletion_status=1, deleted_date=? WHERE appnr=?", (semester,app_id)) 
+
+
+
+def deleteBook(self, book_id) +
+
+

Delete a book from the database

+

Args

+
+
book_id : str
+
ID of the book
+
+
+ +Expand source code + +
def deleteBook(self, book_id):
+    """
+    Delete a book from the database
+
+    Args:
+        book_id (str): ID of the book
+    """
+    self.query_db("UPDATE media SET deleted=1 WHERE id=?", (book_id,))
+
+
+
+def deleteMessage(self, message_id) +
+
+

Delete a message from the database

+

Args

+
+
message_id : str
+
the id of the message
+
+
+ +Expand source code + +
def deleteMessage(self, message_id):
+    """Delete a message from the database
+
+    Args:
+        message_id (str): the id of the message
+    """
+    self.query_db("DELETE FROM messages WHERE id=?", (message_id,))
+
+
+
+def deleteUser(self, user) +
+
+

delete an unser

+

Args

+
+
user : str
+
username of the user
+
+
+ +Expand source code + +
def deleteUser(self, user):
+    """delete an unser
+
+    Args:
+        user (str): username of the user
+    """
+    self.query_db("DELETE FROM user WHERE username=?", (user,))
+
+
+
+def getAllAparats(self, deleted=0) ‑> list[tuple] +
+
+

Get all the apparats in the database

+

Args

+
+
deleted : int, optional
+
Switch the result to use . Defaults to 0.
+
+

Returns

+
+
list[tuple]
+
a list of tuples containing the apparats
+
+
+ +Expand source code + +
def getAllAparats(self,deleted=0)->list[tuple]:
+    """Get all the apparats in the database
+
+    Args:
+        deleted (int, optional): Switch the result to use . Defaults to 0.
+
+    Returns:
+        list[tuple]: a list of tuples containing the apparats
+    """
+    return self.query_db("SELECT * FROM semesterapparat WHERE deletion_status=?", (deleted,))
+
+
+
+def getApparatCountBySemester(self) ‑> tuple[list[str], list[int]] +
+
+

get a list of all apparats created and deleted by semester

+

Returns

+
+
tuple[list[str],list[int]]
+
a tuple containing two lists, the first list contains the semesters, the second list contains the amount of apparats created and deleted for the corresponding semester
+
+
+ +Expand source code + +
def getApparatCountBySemester(self)->tuple[list[str],list[int]]:
+    """get a list of all apparats created and deleted by semester
+
+    Returns:
+        tuple[list[str],list[int]]: a tuple containing two lists, the first list contains the semesters, the second list contains the amount of apparats created and deleted for the corresponding semester
+    """
+    conn = self.connect()
+    cursor = conn.cursor()
+    semesters = self.getSemersters()
+    created = []
+    deleted = []
+    for semester in semesters:
+        query = f"SELECT COUNT(*) FROM semesterapparat WHERE erstellsemester='{semester}'"
+        result = cursor.execute(query).fetchone()
+        created.append(result[0])
+        query = f"SELECT COUNT(*) FROM semesterapparat WHERE deletion_status=1 AND deleted_date='{semester}'"
+        result = cursor.execute(query).fetchone()
+        deleted.append(result[0])
+    # store data in a tuple
+    ret = []
+    e_tuple = ()
+    for sem in semesters:
+        e_tuple = (
+            sem,
+            created[semesters.index(sem)],
+            deleted[semesters.index(sem)],
+        )
+        ret.append(e_tuple)
+    self.close_connection(conn)
+    return ret 
+
+
+
+def getApparatData(self, appnr, appname) ‑> src.logic.dataclass.ApparatData +
+
+

Get the Apparat data based on the apparat number and the name

+

Args

+
+
appnr : str
+
the apparat number
+
appname : str
+
the name of the apparat
+
+

Raises

+
+
NoResultError
+
an error is raised if no result is found
+
+

Returns

+
+
ApparatData
+
the appended data of the apparat wrapped in an ApparatData object
+
+
+ +Expand source code + +
def getApparatData(self, appnr, appname)->ApparatData:
+    """Get the Apparat data based on the apparat number and the name
+
+    Args:
+        appnr (str): the apparat number
+        appname (str): the name of the apparat
+
+    Raises:
+        NoResultError: an error is raised if no result is found
+
+    Returns:
+        ApparatData: the appended data of the apparat wrapped in an ApparatData object
+    """
+    result = self.query_db("SELECT * FROM semesterapparat WHERE appnr=? AND name=?", (appnr,appname), one=True)
+    if result is None:
+        raise NoResultError("No result found")
+    apparat = ApparatData()
+    apparat.appname = result[1]
+    apparat.appnr = result[4]
+    apparat.dauerapp = True if result[7] == 1 else False
+    prof_data = self.getProfData(self.getProfNameById(result[2]))
+    apparat.profname = self.getProfNameById(result[2])
+    apparat.prof_mail = prof_data[0]
+    apparat.prof_tel = prof_data[1]
+    apparat.prof_title = prof_data[2]
+    apparat.app_fach = result[3]
+    apparat.erstellsemester = result[5]
+    apparat.semester = result[8]
+    apparat.deleted = result[9]
+    apparat.apparat_adis_id = result[11]
+    apparat.prof_adis_id = result[12]
+    return apparat
+
+
+
+def getApparatId(self, apparat_name) ‑> Optional[int] +
+
+

get the id of an apparat based on the name

+

Args

+
+
apparat_name : str
+
the name of the apparat e.g. "Semesterapparat 1"
+
+

Returns

+
+
Optional[int]
+
the id of the apparat, if the apparat is not found, None is returned
+
+
+ +Expand source code + +
def getApparatId(self, apparat_name)->Optional[int]:
+    """get the id of an apparat based on the name
+
+    Args:
+        apparat_name (str): the name of the apparat e.g. "Semesterapparat 1"
+
+    Returns:
+        Optional[int]: the id of the apparat, if the apparat is not found, None is returned
+    """
+    data = self.query_db("SELECT appnr FROM semesterapparat WHERE name=?", (apparat_name,), one=True)
+    if data is None:
+        return None
+    else:
+        return data[0]
+
+
+
+def getApparatName(self, app_id: Union[str, int], prof_id: Union[str, int]) +
+
+

get the name of the apparat based on the id

+

Args

+
+
app_id : Union[str,int]
+
the id of the apparat
+
prof_id : Union[str,int]
+
the id of the professor
+
+

Returns

+
+
str
+
the name of the apparat
+
+
+ +Expand source code + +
def getApparatName(self, app_id:Union[str,int], prof_id:Union[str,int]):
+    """get the name of the apparat based on the id
+
+    Args:
+        app_id (Union[str,int]): the id of the apparat
+        prof_id (Union[str,int]): the id of the professor
+
+    Returns:
+        str: the name of the apparat
+    """
+    return self.query_db("SELECT name FROM semesterapparat WHERE appnr=? AND prof_id=?", (app_id,prof_id), one=True)[0]
+
+
+
+def getApparatsByProf(self, prof_id: Union[str, int]) ‑> list[tuple] +
+
+

Get all apparats based on the professor id

+

Args

+
+
prof_id : Union[str,int]
+
the id of the professor
+
+

Returns

+
+
list[tuple]
+
a list of tuples containing the apparats
+
+
+ +Expand source code + +
def getApparatsByProf(self, prof_id:Union[str,int])->list[tuple]:
+    """Get all apparats based on the professor id
+
+    Args:
+        prof_id (Union[str,int]): the id of the professor
+
+    Returns:
+        list[tuple]: a list of tuples containing the apparats
+    """
+    return self.query_db("SELECT * FROM semesterapparat WHERE prof_id=?", (prof_id,))
+
+
+
+def getApparatsBySemester(self, semester: str) ‑> dict[list] +
+
+

get all apparats based on the semester

+

Args

+
+
semester : str
+
the selected semester
+
+

Returns

+
+
dict[list]
+
a list off all created and deleted apparats for the selected semester
+
+
+ +Expand source code + +
def getApparatsBySemester(self, semester:str)->dict[list]:
+    """get all apparats based on the semester
+
+    Args:
+        semester (str): the selected semester
+
+    Returns:
+        dict[list]: a list off all created and deleted apparats for the selected semester
+    """
+    data = self.query_db("SELECT name, prof_id FROM semesterapparat WHERE erstellsemester=?", (semester,))
+    conn = self.connect()
+    cursor = conn.cursor()
+    c_tmp = []
+    for i in data:
+        c_tmp.append((i[0], self.getProfNameById(i[1])))
+    query = (
+        f"SELECT name,prof_id FROM semesterapparat WHERE deleted_date='{semester}'"
+    )
+    result = cursor.execute(query).fetchall()
+    d_tmp = []
+    for i in result:
+        d_tmp.append((i[0], self.getProfNameById(i[1])))
+    # group the apparats by prof
+    c_ret = {}
+    for i in c_tmp:
+        if i[1] not in c_ret.keys():
+            c_ret[i[1]] = [i[0]]
+        else:
+            c_ret[i[1]].append(i[0])
+    d_ret = {}
+    for i in d_tmp:
+        if i[1] not in d_ret.keys():
+            d_ret[i[1]] = [i[0]]
+        else:
+            d_ret[i[1]].append(i[0])
+    self.close_connection(conn)
+    return {"created": c_ret, "deleted": d_ret}
+
+
+
+def getBlob(self, filename, app_id: Union[str, int]) +
+
+

Get a blob from the database

+

Args

+
+
filename : str
+
The name of the file
+
app_id : str
+
ID of the apparat
+
+

Returns

+
+
bytes
+
The file stored in
+
+
+ +Expand source code + +
def getBlob(self, filename, app_id:Union[str,int]):
+    """
+    Get a blob from the database
+
+    Args:
+        filename (str): The name of the file
+        app_id (str): ID of the apparat
+
+    Returns:
+        bytes: The file stored in 
+    """
+    return self.query_db("SELECT fileblob FROM files WHERE filename=? AND app_id=?", (filename,app_id), one=True)[0]
+
+
+
+def getBook(self, book_id: int) ‑> src.logic.dataclass.BookData +
+
+

Get the book based on the id in the database

+

Args

+
+
book_id : int
+
The id of the book
+
+

Returns

+
+
BookData
+
The metadata of the book wrapped in a BookData object
+
+
+ +Expand source code + +
def getBook(self,book_id:int)->BookData:
+    """
+    Get the book based on the id in the database
+
+    Args:
+        book_id (int): The id of the book
+
+    Returns:
+        BookData: The metadata of the book wrapped in a BookData object
+    """
+    return load_pickle(self.query_db("SELECT bookdata FROM media WHERE id=?", (book_id,), one=True)[0])
+
+
+
+def getBookBasedOnSignature(self, app_id: Union[str, int], prof_id: Union[str, int], signature: str) ‑> src.logic.dataclass.BookData +
+
+

Get the book based on the signature of the book.

+

Args

+
+
app_id : str
+
The apparat id the book should be associated with
+
prof_id : str
+
The professor id the book should be associated with
+
signature : str
+
The signature of the book
+
+

Returns

+
+
BookData
+
The total metadata of the book wrapped in a BookData object
+
+
+ +Expand source code + +
def getBookBasedOnSignature(self, app_id:Union[str,int], prof_id:Union[str,int],signature:str)->BookData:
+    """
+    Get the book based on the signature of the book.
+
+    Args:
+        app_id (str): The apparat id the book should be associated with
+        prof_id (str): The professor id the book should be associated with
+        signature (str): The signature of the book
+
+    Returns:
+        BookData: The total metadata of the book wrapped in a BookData object
+    """
+    result = self.query_db("SELECT bookdata FROM media WHERE app_id=? AND prof_id=?", (app_id,prof_id))
+    books = [load_pickle(i[0]) for i in result]
+    book = [i for i in books if i.signature == signature][0]
+    return book
+
+
+
+def getBookId(self, bookdata: src.logic.dataclass.BookData, app_id: Union[str, int], prof_id: Union[str, int]) ‑> int +
+
+

Get the id of a book based on the metadata of the book

+

Args

+
+
bookdata : BookData
+
The wrapped metadata of the book
+
app_id : str
+
The apparat id the book should be associated with
+
prof_id : str
+
The professor id the book should be associated with
+
+

Returns

+
+
int
+
ID of the book
+
+
+ +Expand source code + +
def getBookId(self, bookdata:BookData, app_id:Union[str,int], prof_id:Union[str,int])->int:
+    """
+    Get the id of a book based on the metadata of the book
+
+    Args:
+        bookdata (BookData): The wrapped metadata of the book
+        app_id (str): The apparat id the book should be associated with
+        prof_id (str): The professor id the book should be associated with
+
+    Returns:
+        int: ID of the book
+    """
+    result = self.query_db("SELECT id FROM media WHERE bookdata=? AND app_id=? AND prof_id=?", (dump_pickle(bookdata),app_id,prof_id), one=True)
+    return result[0]
+
+
+
+def getBookIdBasedOnSignature(self, app_id: Union[str, int], prof_id: Union[str, int], signature: str) ‑> int +
+
+

Get a book id based on the signature of the book.

+

Args

+
+
app_id : str
+
The apparat id the book should be associated with
+
prof_id : str
+
The professor id the book should be associated with
+
signature : str
+
The signature of the book
+
+

Returns

+
+
int
+
The id of the book
+
+
+ +Expand source code + +
def getBookIdBasedOnSignature(self, app_id:Union[str,int], prof_id:Union[str,int],signature:str)->int:
+    """
+    Get a book id based on the signature of the book.
+
+    Args:
+        app_id (str): The apparat id the book should be associated with
+        prof_id (str): The professor id the book should be associated with
+        signature (str): The signature of the book
+
+    Returns:
+        int: The id of the book
+    """
+    result = self.query_db("SELECT bookdata, id FROM media WHERE app_id=? AND prof_id=?", (app_id,prof_id))
+    books = [(load_pickle(i[0]),i[1]) for i in result]
+    book = [i for i in books if i[0].signature == signature][0][1]
+    return book
+
+
+
+def getBooks(self, app_id: Union[str, int], prof_id: Union[str, int], deleted=0) ‑> list[dict[int, src.logic.dataclass.BookData, int]] +
+
+

Get the Books based on the apparat id and the professor id

+

Args

+
+
app_id : str
+
The ID of the apparat
+
prof_id : str
+
The ID of the professor
+
deleted : int, optional
+
The state of the book. Set to 1 to include deleted ones. Defaults to 0.
+
+

Returns

+
+
list[dict[int, BookData, int]]
+
A list of dictionaries containing the id, the metadata of the book and the availability of the book
+
+
+ +Expand source code + +
def getBooks(self, app_id:Union[str,int], prof_id:Union[str,int], deleted=0)->list[dict[int, BookData, int]]:
+    """
+    Get the Books based on the apparat id and the professor id
+
+    Args:
+        app_id (str): The ID of the apparat
+        prof_id (str): The ID of the professor
+        deleted (int, optional): The state of the book. Set to 1 to include deleted ones. Defaults to 0.
+
+    Returns:
+        list[dict[int, BookData, int]]: A list of dictionaries containing the id, the metadata of the book and the availability of the book
+    """
+    qdata = self.query_db(f"SELECT id,bookdata,available FROM media WHERE (app_id={app_id} AND prof_id={prof_id}) AND (deleted={deleted if deleted == 0 else '1 OR deleted=0'})")
+    ret_result = []
+    for result_a in qdata:
+        data = {"id": int, "bookdata": BookData, "available": int}
+        data["id"] = result_a[0]
+        data["bookdata"] = load_pickle(result_a[1])
+        data["available"] = result_a[2]
+        ret_result.append(data)
+    return ret_result
+
+
+
+def getFacultyMember(self, name: str) ‑> tuple +
+
+

get a faculty member based on the name

+

Args

+
+
name : str
+
the name to be searched for
+
+

Returns

+
+
tuple
+
a tuple containing the data of the faculty member
+
+
+ +Expand source code + +
def getFacultyMember(self, name:str)->tuple:
+    """get a faculty member based on the name
+
+    Args:
+        name (str): the name to be searched for
+
+    Returns:
+        tuple: a tuple containing the data of the faculty member
+    """
+    return self.query_db("SELECT titel, fname,lname,mail,telnr,fullname FROM prof WHERE fullname=?", (name,), one=True)
+
+
+
+def getFacultyMembers(self) +
+
+

get a list of all faculty members

+

Returns

+
+
list[tuple]
+
a list of tuples containing the faculty members
+
+
+ +Expand source code + +
def getFacultyMembers(self):
+    """get a list of all faculty members
+
+    Returns:
+        list[tuple]: a list of tuples containing the faculty members
+    """
+    return self.query_db("SELECT titel, fname,lname,mail,telnr,fullname FROM prof")
+
+
+
+def getFiles(self, app_id: Union[str, int], prof_id: int) ‑> list[tuple] +
+
+

Get all the files associated with the apparat and the professor

+

Args

+
+
app_id : Union[str,int]
+
The id of the apparat
+
prof_id : Union[str,int]
+
the id of the professor
+
+

Returns

+
+
list[tuple]
+
a list of tuples containing the filename and the filetype for the corresponding apparat and professor
+
+
+ +Expand source code + +
def getFiles(self, app_id:Union[str,int], prof_id:int)->list[tuple]:
+    """Get all the files associated with the apparat and the professor
+
+    Args:
+        app_id (Union[str,int]): The id of the apparat
+        prof_id (Union[str,int]): the id of the professor
+
+    Returns:
+        list[tuple]: a list of tuples containing the filename and the filetype for the corresponding apparat and professor
+    """
+    return self.query_db("SELECT filename, filetyp FROM files WHERE app_id=? AND prof_id=?", (app_id,prof_id))
+
+
+
+def getLastBookId(self) ‑> int +
+
+

Get the last book id in the database

+

Returns

+
+
int
+
ID of the last book in the database
+
+
+ +Expand source code + +
def getLastBookId(self)->int:
+    """
+    Get the last book id in the database
+
+    Returns:
+        int: ID of the last book in the database
+    """
+    return self.query_db("SELECT id FROM media ORDER BY id DESC", one=True)[0]
+
+
+
+def getMessages(self, date: str) ‑> list[dict[str, str, str, str]] +
+
+

Get all the messages for a specific date

+

Args

+
+
date : str
+
a date.datetime object formatted as a string in the format "YYYY-MM-DD"
+
+

Returns

+
+
list[dict[str, str, str, str]]
+
a list of dictionaries containing the message, the user who added the message, the apparat id and the id of the message
+
+
+ +Expand source code + +
def getMessages(self, date:str)->list[dict[str, str, str, str]]:
+    """Get all the messages for a specific date
+
+    Args:
+        date (str): a date.datetime object formatted as a string in the format "YYYY-MM-DD"
+    
+    Returns:
+        list[dict[str, str, str, str]]: a list of dictionaries containing the message, the user who added the message, the apparat id and the id of the message
+    """
+    def __get_user_name(user_id):
+        return self.query_db("SELECT username FROM user WHERE id=?", (user_id,), one=True)[0]
+    messages = self.query_db("SELECT * FROM messages WHERE remind_at=?", (date,))
+    ret = [
+        {
+            "message": i[2],
+            "user": __get_user_name(i[4]),
+            "appnr": i[5],
+            "id": i[0]
+        }
+        for i in messages
+    ]
+    return ret
+
+
+
+def getProfByName(self, prof_name: str) ‑> tuple +
+
+

get all the data of a professor based on the name

+

Args

+
+
prof_name : str
+
the name of the professor
+
+

Returns

+
+
tuple
+
the data of the professor
+
+
+ +Expand source code + +
def getProfByName(self, prof_name:str)->tuple:
+    """get all the data of a professor based on the name
+
+    Args:
+        prof_name (str): the name of the professor
+
+    Returns:
+        tuple: the data of the professor
+    """
+    return self.query_db("SELECT * FROM prof WHERE fullname=?", (prof_name,), one=True)
+
+
+
+def getProfData(self, profname: str) +
+
+

Get mail, telephone number and title of a professor based on the name

+

Args

+
+
profname : str
+
name of the professor
+
+

Returns

+
+
tuple
+
the mail, telephone number and title of the professor
+
+
+ +Expand source code + +
def getProfData(self, profname:str):
+    """Get mail, telephone number and title of a professor based on the name
+
+    Args:
+        profname (str): name of the professor
+
+    Returns:
+        tuple: the mail, telephone number and title of the professor
+    """
+    data = self.query_db("SELECT mail, telnr, titel FROM prof WHERE fullname=?", (profname.replace(",",""),), one=True)
+    return data
+
+
+
+def getProfId(self, prof_name: str) ‑> Optional[int] +
+
+

Get the id of a professor based on the name

+

Args

+
+
prof_name : str
+
the name of the professor
+
+

Returns

+
+
Optional[int]
+
the id of the professor, if the professor is not found, None is returned
+
+
+ +Expand source code + +
def getProfId(self, prof_name:str)->Optional[int]:
+    """Get the id of a professor based on the name
+
+    Args:
+        prof_name (str): the name of the professor
+
+    Returns:
+        Optional[int]: the id of the professor, if the professor is not found, None is returned
+    """
+    
+    data = self.getProfByName(prof_name.replace(",", ""))
+    if data is None:
+        return None
+    else:
+        return data[0]
+
+
+
+def getProfNameById(self, prof_id: Union[str, int], add_title: bool = False) ‑> str +
+
+

Get a professor name based on the id

+

Args

+
+
prof_id : Union[str,int]
+
The id of the professor
+
add_title : bool, optional
+
wether to add the title or no. Defaults to False.
+
+

Returns

+
+
str
+
The name of the professor
+
+
+ +Expand source code + +
def getProfNameById(self, prof_id:Union[str,int],add_title:bool=False)->str:
+    """Get a professor name based on the id
+
+    Args:
+        prof_id (Union[str,int]): The id of the professor
+        add_title (bool, optional): wether to add the title or no. Defaults to False.
+
+    Returns:
+        str: The name of the professor
+    """
+    prof = self.query_db("SELECT fullname FROM prof WHERE id=?", (prof_id,), one=True)
+    if add_title:
+        return f"{self.getTitleById(prof_id)}{prof[0]}"
+    else:
+        return prof[0]
+
+
+
+def getProfs(self) ‑> list[tuple] +
+
+

Return all the professors in the database

+

Returns

+
+
list[tuple]
+
a list containing all the professors in individual tuples
+
+
+ +Expand source code + +
def getProfs(self)->list[tuple]:
+    """Return all the professors in the database
+
+    Returns:
+        list[tuple]: a list containing all the professors in individual tuples
+    """
+    return self.query_db("SELECT * FROM prof")
+
+
+
+def getRole(self, user) +
+
+

get the role of the user

+

Args

+
+
user : str
+
username
+
+

Returns

+
+
str
+
the name of the role
+
+
+ +Expand source code + +
def getRole(self, user):
+    """get the role of the user
+
+    Args:
+        user (str): username
+
+    Returns:
+        str: the name of the role
+    """
+    return self.query_db("SELECT role FROM user WHERE username=?", (user,), one=True)[0]
+
+
+
+def getRoles(self) ‑> list[tuple] +
+
+

get all the roles in the database

+

Returns

+
+
list[str]
+
a list of all the roles
+
+
+ +Expand source code + +
def getRoles(self)->list[tuple]:
+    """get all the roles in the database
+
+    Returns:
+        list[str]: a list of all the roles
+    """
+    return self.query_db("SELECT role FROM user")
+
+
+
+def getSemersters(self) ‑> list[str] +
+
+

Return all the unique semesters in the database

+

Returns

+
+
list
+
a list of strings containing the semesters
+
+
+ +Expand source code + +
def getSemersters(self)->list[str]:
+    """Return all the unique semesters in the database
+
+    Returns:
+        list: a list of strings containing the semesters
+    """
+    data = self.query_db("SELECT DISTINCT erstellsemester FROM semesterapparat")
+    return [i[0] for i in data]
+
+
+
+def getSpecificProfData(self, prof_id: Union[str, int], fields: List[str]) ‑> tuple +
+
+

A customisable function to get specific data of a professor based on the id

+

Args

+
+
prof_id : Union[str,int]
+
the id of the professor
+
fields : List[str]
+
a list of fields to be returned
+
+

Returns

+
+
tuple
+
a tuple containing the requested data
+
+
+ +Expand source code + +
def getSpecificProfData(self, prof_id:Union[str,int], fields:List[str])->tuple:
+    """A customisable function to get specific data of a professor based on the id
+
+    Args:
+        prof_id (Union[str,int]): the id of the professor
+        fields (List[str]): a list of fields to be returned
+
+    Returns:
+        tuple: a tuple containing the requested data
+    """
+    query = "SELECT "
+    for field in fields:
+        query += f"{field},"
+    query = query[:-1]
+    query += " FROM prof WHERE id=?"
+    return self.query_db(query, (prof_id,), one=True)[0]
+
+
+
+def getSubjects(self) +
+
+

Get all the subjects in the database

+

Returns

+
+
list[tuple]
+
a list of tuples containing the subjects
+
+
+ +Expand source code + +
def getSubjects(self):
+    """Get all the subjects in the database
+
+    Returns:
+        list[tuple]: a list of tuples containing the subjects
+    """
+    return self.query_db("SELECT * FROM subjects")
+
+
+
+def getTitleById(self, prof_id: Union[str, int]) ‑> str +
+
+

get the title of a professor based on the id

+

Args

+
+
prof_id : Union[str,int]
+
the id of the professor
+
+

Returns

+
+
str
+
the title of the professor, with an added whitespace at the end, if no title is present, an empty string is returned
+
+
+ +Expand source code + +
def getTitleById(self, prof_id:Union[str,int])->str:
+    """get the title of a professor based on the id
+
+    Args:
+        prof_id (Union[str,int]): the id of the professor
+
+    Returns:
+        str: the title of the professor, with an added whitespace at the end, if no title is present, an empty string is returned
+    """
+    title = self.query_db("SELECT titel FROM prof WHERE id=?", (prof_id,), one=True)[0]
+    return f"{title} " if title is not None else ""
+
+
+
+def getUnavailableApparatNumbers(self) ‑> List[int] +
+
+

Get a list of all the apparat numbers in the database that are currently in use

+

Returns

+
+
List[int]
+
the list of used apparat numbers
+
+
+ +Expand source code + +
def getUnavailableApparatNumbers(self)->List[int]:
+    """Get a list of all the apparat numbers in the database that are currently in use
+
+    Returns:
+        List[int]: the list of used apparat numbers
+    """
+    numbers = self.query_db("SELECT appnr FROM semesterapparat WHERE deletion_status=0")    
+    numbers = [i[0] for i in numbers]
+    logger.log_info(f"Currently used apparat numbers: {numbers}")
+    return numbers
+
+
+
+def getUser(self) +
+
+

Get a single user from the database

+
+ +Expand source code + +
def getUser(self):
+    """Get a single user from the database"""
+    return self.query_db("SELECT * FROM user", one=True)
+
+
+
+def getUsers(self) ‑> list[tuple] +
+
+

Return a list of tuples of all the users in the database

+
+ +Expand source code + +
def getUsers(self)->list[tuple]:
+    """Return a list of tuples of all the users in the database"""
+    return self.query_db("SELECT * FROM user")
+
+
+
+def get_db_contents(self) ‑> Optional[List[Tuple]] +
+
+

Get the contents of the

+

Returns

+
+
Union[List[Tuple], None]
+
description
+
+
+ +Expand source code + +
def get_db_contents(self)->Union[List[Tuple], None]:
+    """
+    Get the contents of the 
+
+    Returns:
+        Union[List[Tuple], None]: _description_
+    """
+    try:
+        with sql.connect(self.db_path) as conn:
+            cursor = conn.cursor()
+            cursor.execute("SELECT * FROM sqlite_master WHERE type='table'")
+            return cursor.fetchall()
+    except sql.OperationalError:
+        return None
+
+
+
+def insertFile(self, file: list[dict], app_id: Union[str, int], prof_id: Union[str, int]) +
+
+

Instert a list of files into the database

+

Args

+
+
file : list[dict]
+
a list containing all the files to be inserted
+
Structured
+
[{"name": "filename", "path": "path", "type": "filetype"}]
+
app_id : int
+
the id of the apparat
+
prof_id : str
+
the id of the professor
+
+
+ +Expand source code + +
def insertFile(self, file: list[dict], app_id:Union[str,int], prof_id:Union[str,int]):
+    """Instert a list of files into the database
+
+    Args:
+        file (list[dict]): a list containing all the files to be inserted
+        Structured: [{"name": "filename", "path": "path", "type": "filetype"}]
+        app_id (int): the id of the apparat
+        prof_id (str): the id of the professor
+    """
+    for f in file:
+        filename = f["name"]
+        path = f["path"]
+        filetyp = f["type"]
+        if path == "Database":
+            continue
+        blob = create_blob(path)
+        query = "INSERT OR IGNORE INTO files (filename, fileblob, app_id, filetyp,prof_id) VALUES (?, ?, ?, ?,?)"
+        self.query_db(query, (filename, blob, app_id, filetyp,prof_id))
+
+
+
+def insertInto(self, query: str, params: Tuple) ‑> None +
+
+

Insert sent data into the database

+

Args

+
+
query : str
+
The query to be executed
+
params : Tuple
+
the parameters to be inserted into the database
+
+
+ +Expand source code + +
def insertInto(self, query:str, params:Tuple) -> None:
+    """
+    Insert sent data into the database
+
+    Args:
+        query (str): The query to be executed
+        params (Tuple): the parameters to be inserted into the database
+    """
+    conn = self.connect()
+    cursor = conn.cursor()
+    logger.log_info(f"Inserting {params} into database with query {query}")
+    cursor.execute(query, params)
+    conn.commit()
+    self.close_connection(conn)
+
+
+
+def isEternal(self, id) +
+
+

check if the apparat is eternal (dauerapparat)

+

Args

+
+
id : int
+
the id of the apparat to be checked
+
+

Returns

+
+
int
+
the state of the apparat
+
+
+ +Expand source code + +
def isEternal(self, id):
+    """check if the apparat is eternal (dauerapparat)
+
+    Args:
+        id (int): the id of the apparat to be checked
+
+    Returns:
+        int: the state of the apparat
+    """
+    return self.query_db("SELECT dauer FROM semesterapparat WHERE appnr=?", (id,), one=True)
+
+
+
+def login(self, user, hashed_password) +
+
+

try to login the user. +The salt for the user will be requested from the database and then added to the hashed password. The password will then be compared to the password in the database

+

Args

+
+
user : str
+
username that tries to login
+
hashed_password : str
+
the password the user tries to login with
+
+

Returns

+
+
bool
+
True if the login was successful, False if not
+
+
+ +Expand source code + +
def login(self, user, hashed_password):
+    """try to login the user.
+    The salt for the user will be requested from the database and then added to the hashed password. The password will then be compared to the password in the database 
+
+    Args:
+        user (str): username that tries to login
+        hashed_password (str): the password the user tries to login with
+
+    Returns:
+        bool: True if the login was successful, False if not
+    """
+    salt = self.query_db("SELECT salt FROM user WHERE username=?", (user,), one=True)[0]
+    if salt is None:
+        return False
+    hashed_password = salt + hashed_password
+    password = self.query_db("SELECT password FROM user WHERE username=?", (user,), one=True)[0]
+    if password == hashed_password:
+        return True
+    else:
+        return False
+
+
+
+def query_db(self, query: str, args: Tuple = (), one: bool = False) ‑> Union[Tuple, List[Tuple]] +
+
+

Query the Database for the sent query.

+

Args

+
+
query : str
+
The query to be executed
+
args : Tuple, optional
+
The arguments for the query. Defaults to ().
+
one : bool, optional
+
Return the first result only. Defaults to False.
+
+

Returns

+

Union[Typle|List[Tuple]]: Returns the result of the query

+
+ +Expand source code + +
def query_db(self, query: str, args: Tuple = (), one: bool = False)->Union[Tuple, List[Tuple]]:
+    """
+    Query the Database for the sent query.
+
+    Args:
+        query (str): The query to be executed
+        args (Tuple, optional): The arguments for the query. Defaults to ().
+        one (bool, optional): Return the first result only. Defaults to False.
+
+    Returns:
+        Union[Typle|List[Tuple]]: Returns the result of the query
+    """
+    conn = self.connect()
+    cursor = conn.cursor()
+    logger.log_info(f"Querying database with query {query}, args: {args}")
+    cursor.execute(query, args)
+    rv = cursor.fetchall()
+    conn.commit()
+    self.close_connection(conn)
+    return (rv[0] if rv else None) if one else rv
+
+
+
+def recreateFile(self, filename: str, app_id: Union[str, int], filetype: str) ‑> str +
+
+

Recreate a file from the database

+

Args

+
+
filename : str
+
the name of the file
+
app_id : Union[str,int]
+
the id of the apparat
+
filetype : str
+
the extension of the file to be created
+
+

Returns

+
+
str
+
The filename of the recreated file
+
+
+ +Expand source code + +
def recreateFile(self, filename:str, app_id:Union[str,int],filetype:str)->str:
+    """Recreate a file from the database
+
+    Args:
+        filename (str): the name of the file
+        app_id (Union[str,int]): the id of the apparat
+        filetype (str): the extension of the file to be created
+
+    Returns:
+        str: The filename of the recreated file 
+    """
+    blob = self.getBlob(filename, app_id)
+    tempdir = config.database.tempdir
+    tempdir = tempdir.replace("~", str(Path.home()))
+    tempdir_path = Path(tempdir)        
+    if not os.path.exists(tempdir_path):
+        os.mkdir(tempdir_path)
+    file = tempfile.NamedTemporaryFile(
+        delete=False, dir=tempdir_path, mode="wb", suffix=f".{filetype}"
+    )
+    file.write(blob)
+    print("file created")
+    return file.name
+
+
+
+def searchBook(self, data: dict[str, str]) ‑> list[tuple[src.logic.dataclass.BookData, int]] +
+
+

Search a book in the database based on the sent data.

+

Args

+
+
data : dict[str, str]
+
A dictionary containing the data to be searched for. The dictionary can contain the following:
+
+
    +
  • signature: The signature of the book
  • +
  • title: The title of the book
  • +
+

Returns

+
+
list[tuple[BookData, int]]
+
A list of tuples containing the wrapped Metadata and the id of the book
+
+
+ +Expand source code + +
def searchBook(self, data:dict[str, str])->list[tuple[BookData, int]]:
+    """
+    Search a book in the database based on the sent data.
+
+    Args:
+        data (dict[str, str]): A dictionary containing the data to be searched for. The dictionary can contain the following: 
+        - signature: The signature of the book
+        - title: The title of the book
+
+    Returns:
+        list[tuple[BookData, int]]: A list of tuples containing the wrapped Metadata and the id of the book
+    """
+    rdata = self.query_db("SELECT * FROM media WHERE deleted=0")
+    #ic(rdata, len(rdata))
+    mode = 0
+    if len(data)== 1:
+        if "signature" in data.keys():
+            mode = 1
+        elif "title" in data.keys():
+            mode = 2
+    elif len(data) == 2:
+        mode = 3
+    else:
+        return None
+    ret = []
+    for book in rdata:
+        bookdata = load_pickle(book[1])
+        app_id = book[2]
+        prof_id = book[3]
+        if mode == 1:
+            if data["signature"] in bookdata.signature:
+                ret.append((bookdata,app_id,prof_id))
+        elif mode == 2:
+            if data["title"] in bookdata.title:
+                ret.append((bookdata,app_id,prof_id))
+        elif mode == 3:
+            if data["signature"] in bookdata.signature and data["title"] in bookdata.title:
+                ret.append((bookdata,app_id,prof_id))
+    #ic(ret)
+    return ret
+
+
+
+def setAvailability(self, book_id: str, available: str) +
+
+

Set the availability of a book in the database

+

Args

+
+
book_id : str
+
The id of the book
+
available : str
+
The availability of the book
+
+
+ +Expand source code + +
def setAvailability(self, book_id:str, available:str):
+    """
+    Set the availability of a book in the database
+
+    Args:
+        book_id (str): The id of the book
+        available (str): The availability of the book
+    """
+    self.query_db("UPDATE media SET available=? WHERE id=?", (available,book_id))
+
+
+
+def setNewSemesterDate(self, app_id: Union[str, int], newDate, dauerapp=False) +
+
+

Set the new semester date for an apparat

+

Args

+
+
app_id : Union[str,int]
+
the id of the apparat
+
newDate : str
+
the new date
+
dauerapp : bool, optional
+
if the apparat was changed to dauerapparat. Defaults to False.
+
+
+ +Expand source code + +
def setNewSemesterDate(self, app_id:Union[str,int], newDate, dauerapp=False):
+    """Set the new semester date for an apparat
+
+    Args:
+        app_id (Union[str,int]): the id of the apparat
+        newDate (str): the new date
+        dauerapp (bool, optional): if the apparat was changed to dauerapparat. Defaults to False.
+    """
+    date = datetime.datetime.strptime(newDate, "%d.%m.%Y").strftime("%Y-%m-%d")
+    if dauerapp:
+        self.query_db("UPDATE semesterapparat SET verlängerung_bis=?, dauerapp=? WHERE appnr=?", (date,dauerapp,app_id))
+    else:
+        self.query_db("UPDATE semesterapparat SET endsemester=? WHERE appnr=?", (date,app_id))
+
+
+
+def statistic_request(self, **kwargs: Any) +
+
+

Take n amount of kwargs and return the result of the query

+
+ +Expand source code + +
def statistic_request(self, **kwargs: Any):
+    """Take n amount of kwargs and return the result of the query
+    """
+    def __query(query):
+        """execute the query and return the result
+
+        Args:
+            query (str): the constructed query
+
+        Returns:
+            list: the result of the query
+        """
+        conn = self.connect()
+        cursor = conn.cursor()
+        result = cursor.execute(query).fetchall()
+        for result_a in result:
+            orig_value = result_a
+            prof_name = self.getProfNameById(result_a[2])
+            # replace the prof_id with the prof_name
+            result_a = list(result_a)
+            result_a[2] = prof_name
+            result_a = tuple(result_a)
+            result[result.index(orig_value)] = result_a
+        self.close_connection(conn)
+        return result
+    if "deletable" in kwargs.keys():
+        query = f"SELECT * FROM semesterapparat WHERE deletion_status=0 AND dauer=0 AND (erstellsemester!='{kwargs['deletesemester']}' OR verlängerung_bis!='{kwargs['deletesemester']}')"
+        return __query(query)
+    if "dauer" in kwargs.keys():
+        kwargs["dauer"] = kwargs["dauer"].replace("Ja", "1").replace("Nein", "0")
+    query = "SELECT * FROM semesterapparat WHERE "
+    for key, value in kwargs.items() if kwargs.items() is not None else {}:
+        print(key, value)
+        query += f"{key}='{value}' AND "
+        print(query)
+    # remove deletesemester part from normal query, as this will be added to the database upon deleting the apparat
+    if "deletesemester" in kwargs.keys():
+        query = query.replace(
+            f"deletesemester='{kwargs['deletesemester']}' AND ", ""
+        )
+    if "endsemester" in kwargs.keys():
+        if "erstellsemester" in kwargs.keys():
+            query = query.replace(f"endsemester='{kwargs['endsemester']}' AND ", "")
+            query = query.replace(
+                f"erstellsemester='{kwargs['erstellsemester']} AND ", "xyz"
+            )
+        else:
+            query = query.replace(
+                f"endsemester='{kwargs['endsemester']}' AND ", "xyz"
+            )
+            print("replaced")
+        query = query.replace(
+            "xyz",
+            f"(erstellsemester='{kwargs['endsemester']}' OR verlängerung_bis='{kwargs['endsemester']}') AND ",
+        )
+    # remove all x="" parts from the query where x is a key in kwargs
+    query = query[:-5]
+    print(query)
+    return __query(query)
+
+
+
+def updateApparat(self, apparat_data: src.logic.dataclass.ApparatData) +
+
+

Update an apparat in the database

+

Args

+
+
apparat_data : ApparatData
+
the new metadata of the apparat
+
+
+ +Expand source code + +
def updateApparat(self, apparat_data:ApparatData):
+    """Update an apparat in the database
+
+    Args:
+        apparat_data (ApparatData): the new metadata of the apparat
+    """
+    query = f"UPDATE semesterapparat SET name = ?, fach = ?, dauer = ?, prof_id = ? WHERE appnr = ?"
+    params = (
+        apparat_data.appname,
+        apparat_data.app_fach,
+        apparat_data.dauerapp,
+        self.getProfId(apparat_data.profname),
+        apparat_data.appnr,        
+    )
+    self.query_db(query, params)
+
+
+
+def updateBookdata(self, book_id, bookdata: src.logic.dataclass.BookData) +
+
+

Update the bookdata in the database

+

Args

+
+
book_id : str
+
The id of the book
+
bookdata : BookData
+
The new metadata of the book
+
+
+ +Expand source code + +
def updateBookdata(self, book_id, bookdata:BookData):
+    """
+    Update the bookdata in the database
+
+    Args:
+        book_id (str): The id of the book
+        bookdata (BookData): The new metadata of the book
+    """
+    self.query_db("UPDATE media SET bookdata=? WHERE id=?", (dump_pickle(bookdata),book_id))
+
+
+
+def updateFacultyMember(self, data: dict, oldlname: str, oldfname: str) +
+
+

update the data of a faculty member

+

Args

+
+
data : dict
+
a dictionary containing the data to be updated
+
oldlname : str
+
the old last name of the faculty member
+
oldfname : str
+
the old first name of the faculty member
+
+
+ +Expand source code + +
def updateFacultyMember(self, data:dict, oldlname:str, oldfname:str):
+    """update the data of a faculty member
+
+    Args:
+        data (dict): a dictionary containing the data to be updated
+        oldlname (str): the old last name of the faculty member
+        oldfname (str): the old first name of the faculty member
+    """
+    placeholders = ", ".join([f"{i}=:{i} " for i in data.keys()])
+    query = f"UPDATE prof SET {placeholders} WHERE lname = :oldlname AND fname = :oldfname"
+    data["oldlname"] = oldlname
+    data["oldfname"] = oldfname
+    self.query_db(query, data)
+
+
+
+def updateUser(self, username, data: dict[str, str]) +
+
+

changge the data of a user

+

Args

+
+
username : str
+
the username of the user
+
data : dict[str, str]
+
the data to be changed
+
+
+ +Expand source code + +
def updateUser(self, username, data:dict[str, str]):
+    """changge the data of a user
+
+    Args:
+        username (str): the username of the user
+        data (dict[str, str]): the data to be changed
+    """
+    conn = self.connect()
+    cursor = conn.cursor()
+    query = "UPDATE user SET "
+    for key,value in data.items():
+        if key == "username":
+            continue
+        query += f"{key}='{value}',"
+    query = query[:-1]
+    query += " WHERE username=?"
+    params = (username,)
+    cursor.execute(query, params)
+    conn.commit()
+    self.close_connection(conn)
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs - Kopie/database.md b/docs - Kopie/database.md new file mode 100644 index 0000000..c518b09 --- /dev/null +++ b/docs - Kopie/database.md @@ -0,0 +1,327 @@ +Module database +=============== + +Classes +------- + +`Database(db_path: str = None)` +: Initialize the database and create the tables if they do not exist. + + Default constructor for the database class + + Args: + db_path (str, optional): Optional Path for testing / specific purposes. Defaults to None. + + ### Methods + + `addBookToDatabase(self, bookdata: src.logic.dataclass.BookData, app_id: str, prof_id: str)` + : Add books to the database. Both app_id and prof_id are required to add the book to the database, as the app_id and prof_id are used to select the books later on. + + Args: + bookdata (BookData): The metadata of the book to be added + app_id (str): The apparat id where the book should be added to + prof_id (str): The id of the professor where the book should be added to. + + `addMessage(self, message: dict, user, appnr)` + : + + `changePassword(self, user, new_password)` + : + + `checkApparatExists(self, apparat_name)` + : + + `checkApparatExistsById(self, apparat_id)` + : + + `checkUsername(self, user)` + : + + `close_connection(self, conn: sqlite3.Connection)` + : closes the connection to the database + + Args: + ---- + - conn (sql.Connection): the connection to be closed + + `connect(self) ‑> sqlite3.Connection` + : Connect to the database + + Returns: + sql.Connection: The active connection to the database + + `createApparat(self, apparat: src.logic.dataclass.ApparatData) ‑> Union[src.errors.DatabaseErrors.AppPresentError, ForwardRef(None), int]` + : + + `createProf(self, prof_details: dict)` + : + + `createUser(self, user, password, role, salt)` + : Create a user based on passed data. + + Args: + ---- + - username (str): Username to be used + - password (str): the salted password + - role (str): Role of the user + - salt (str): a random salt for the user + + `create_tables(self)` + : Create the tables in the database + + `deleteApparat(self, appnr, semester)` + : + + `deleteBook(self, book_id)` + : Delete a book from the database + + Args: + book_id (str): ID of the book + + `deleteMessage(self, message_id)` + : + + `deleteUser(self, user)` + : + + `getAllAparats(self, deleted=0)` + : + + `getApparatCountBySemester(self) ‑> tuple[list[str], list[int]]` + : + + `getApparatData(self, appnr, appname) ‑> src.logic.dataclass.ApparatData` + : + + `getApparatId(self, apparat_name)` + : + + `getApparatName(self, app_id, prof_id)` + : + + `getApparatsByProf(self, prof_id: int) ‑> list[tuple]` + : + + `getApparatsBySemester(self, semester: str) ‑> dict` + : + + `getBlob(self, filename, app_id)` + : Get a blob from the database + + Args: + filename (str): The name of the file + app_id (str): ID of the apparat + + Returns: + bytes: The file stored in + + `getBook(self, book_id: int) ‑> src.logic.dataclass.BookData` + : Get the book based on the id in the database + + Args: + book_id (int): The id of the book + + Returns: + BookData: The metadata of the book wrapped in a BookData object + + `getBookBasedOnSignature(self, app_id: str, prof_id: str, signature: str) ‑> src.logic.dataclass.BookData` + : Get the book based on the signature of the book. + + Args: + app_id (str): The apparat id the book should be associated with + prof_id (str): The professor id the book should be associated with + signature (str): The signature of the book + + Returns: + BookData: The total metadata of the book wrapped in a BookData object + + `getBookId(self, bookdata: src.logic.dataclass.BookData, app_id, prof_id) ‑> int` + : Get the id of a book based on the metadata of the book + + Args: + bookdata (BookData): The wrapped metadata of the book + app_id (str): The apparat id the book should be associated with + prof_id (str): The professor id the book should be associated with + + Returns: + int: ID of the book + + `getBookIdBasedOnSignature(self, app_id: str, prof_id: str, signature: str) ‑> int` + : Get a book id based on the signature of the book. + + Args: + app_id (str): The apparat id the book should be associated with + prof_id (str): The professor id the book should be associated with + signature (str): The signature of the book + + Returns: + int: The id of the book + + `getBooks(self, app_id, prof_id, deleted=0) ‑> list[dict[int, src.logic.dataclass.BookData, int]]` + : Get the Books based on the apparat id and the professor id + + Args: + app_id (str): The ID of the apparat + prof_id (str): The ID of the professor + deleted (int, optional): The state of the book. Set to 1 to include deleted ones. Defaults to 0. + + Returns: + list[dict[int, BookData, int]]: A list of dictionaries containing the id, the metadata of the book and the availability of the book + + `getFacultyMember(self, name: str)` + : + + `getFacultyMembers(self)` + : + + `getFiles(self, app_id: int, prof_id: int) ‑> list[tuple]` + : + + `getLastBookId(self) ‑> int` + : Get the last book id in the database + + Returns: + int: ID of the last book in the database + + `getMessages(self, date: str)` + : + + `getProfByName(self, prof_name: str)` + : + + `getProfData(self, profname: str)` + : + + `getProfId(self, prof_name: str)` + : getProfId _summary_ + + :param prof_name: _description_ + :type prof_name: str + :return: _description_ + :rtype: _type_ + + `getProfNameById(self, prof_id: int, add_title: bool = False)` + : + + `getProfs(self)` + : + + `getRole(self, user)` + : + + `getRoles(self)` + : + + `getSemersters(self)` + : + + `getSpecificProfData(self, prof_id: int, fields: List[str])` + : getSpecificProfData _summary_ + + + + Args: + ---- + - prof_id (int): _description_ + - fields (List[str]): _description_ + + Returns: + ------- + - _type_: _description_ + + `getSubjects(self)` + : + + `getTitleById(self, prof_id: int)` + : + + `getUnavailableApparatNumbers(self) ‑> List[int]` + : getUnavailableApparatNumbers returns a list of all currently used ApparatNumbers + + + + Returns: + ------- + - number(List[int]): a list of all currently used apparat numbers + + `getUser(self)` + : + + `getUsers(self)` + : + + `get_db_contents(self) ‑> Optional[List[Tuple]]` + : Get the contents of the + + Returns: + Union[List[Tuple], None]: _description_ + + `insertFile(self, file: list[dict], app_id: int, prof_id)` + : + + `insertInto(self, query: str, params: Tuple) ‑> None` + : Insert sent data into the database + + Args: + query (str): The query to be executed + params (Tuple): the parameters to be inserted into the database + + `isEternal(self, id)` + : + + `login(self, user, hashed_password)` + : + + `query_db(self, query: str, args: Tuple = (), one: bool = False) ‑> Union[Tuple, List[Tuple]]` + : Query the Database for the sent query. + + Args: + query (str): The query to be executed + args (Tuple, optional): The arguments for the query. Defaults to (). + one (bool, optional): Return the first result only. Defaults to False. + + Returns: + Union[Typle|List[Tuple]]: Returns the result of the query + + `recreateFile(self, filename, app_id, filetype)` + : + + `searchBook(self, data: dict[str, str]) ‑> list[tuple[src.logic.dataclass.BookData, int]]` + : Search a book in the database based on the sent data. + + Args: + data (dict[str, str]): A dictionary containing the data to be searched for. The dictionary can contain the following: + - signature: The signature of the book + - title: The title of the book + + Returns: + list[tuple[BookData, int]]: A list of tuples containing the wrapped Metadata and the id of the book + + `setAvailability(self, book_id: str, available: str)` + : Set the availability of a book in the database + + Args: + book_id (str): The id of the book + available (str): The availability of the book + + `setNewSemesterDate(self, appnr, newDate, dauerapp=False)` + : + + `statistic_request(self, **kwargs: Any)` + : + + `updateApparat(self, apparat_data: src.logic.dataclass.ApparatData)` + : + + `updateBookdata(self, book_id, bookdata: src.logic.dataclass.BookData)` + : Update the bookdata in the database + + Args: + book_id (str): The id of the book + bookdata (BookData): The new metadata of the book + + `updateFacultyMember(self, data, oldlname, oldfname)` + : + + `updateUser(self, username, data: dict[str, str])` + : \ No newline at end of file diff --git a/docs - Kopie/db.html b/docs - Kopie/db.html new file mode 100644 index 0000000..f2b125f --- /dev/null +++ b/docs - Kopie/db.html @@ -0,0 +1,132 @@ + + + + + + +db API documentation + + + + + + + + + + + +
+
+
+

Module db

+
+
+

Module db provides the database schema for the semesterapparat application.

+
+
+
+ +Expand source code + +
CREATE_TABLE_APPARAT = """CREATE TABLE semesterapparat (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    name TEXT,
+    prof_id INTEGER,
+    fach TEXT,
+    appnr INTEGER,
+    erstellsemester TEXT,
+    verlängert_am TEXT,
+    dauer BOOLEAN,
+    verlängerung_bis TEXT,
+    deletion_status INTEGER,
+    deleted_date TEXT,
+    apparat_id_adis INTEGER,
+    prof_id_adis INTEGER,
+    konto INTEGER REFERENCES app_kontos (id),
+    FOREIGN KEY (prof_id) REFERENCES prof (id)
+  )"""
+CREATE_TABLE_MEDIA = """CREATE TABLE media (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    bookdata BLOB,
+    app_id INTEGER,
+    prof_id INTEGER,
+    deleted INTEGER DEFAULT (0),
+    available BOOLEAN,
+    reservation BOOLEAN,
+    FOREIGN KEY (prof_id) REFERENCES prof (id),
+    FOREIGN KEY (app_id) REFERENCES semesterapparat (id)
+  )"""
+CREATE_TABLE_APPKONTOS = """CREATE TABLE app_kontos (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    app_id INTEGER,
+    konto INTEGER,
+    passwort TEXT,
+    FOREIGN KEY (app_id) REFERENCES semesterapparat (id)
+    )"""
+CREATE_TABLE_FILES = """CREATE TABLE files (
+    id INTEGER PRIMARY KEY,
+    filename TEXT,
+    fileblob BLOB,
+    app_id INTEGER,
+    filetyp TEXT,
+    prof_id INTEGER REFERENCES prof (id),
+    FOREIGN KEY (app_id) REFERENCES semesterapparat (id)
+    )"""
+CREATE_TABLE_MESSAGES = """CREATE TABLE messages (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    created_at date NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    message TEXT NOT NULL,
+    remind_at date NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    user_id INTEGER NOT NULL,
+    appnr INTEGER,
+    FOREIGN KEY (user_id) REFERENCES user (id)
+  )"""
+CREATE_TABLE_PROF = """CREATE TABLE prof (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    titel TEXT,
+    fname TEXT,
+    lname TEXT,
+    fullname TEXT NOT NULL UNIQUE,
+    mail TEXT,
+    telnr TEXT
+  )"""
+CREATE_TABLE_USER = """CREATE TABLE user (
+    id integer NOT NULL PRIMARY KEY AUTOINCREMENT,
+    created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    username TEXT NOT NULL UNIQUE,
+    password TEXT NOT NULL,
+    salt TEXT NOT NULL,
+    role TEXT NOT NULL,
+    email TEXT UNIQUE,
+    name TEXT
+  )"""
+CREATE_TABLE_SUBJECTS = """CREATE TABLE subjects (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    name TEXT NOT NULL UNIQUE
+)"""
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs - Kopie/db.md b/docs - Kopie/db.md new file mode 100644 index 0000000..e69de29 diff --git a/docs - Kopie/index.md b/docs - Kopie/index.md new file mode 100644 index 0000000..a77c9f7 --- /dev/null +++ b/docs - Kopie/index.md @@ -0,0 +1,18 @@ +# Welcome to MkDocs + +For full documentation visit [mkdocs.org](https://www.mkdocs.org). + +## Commands + +* `mkdocs new [dir-name]` - Create a new project. +* `mkdocs serve` - Start the live-reloading docs server. +* `mkdocs build` - Build the documentation site. +* `mkdocs -h` - Print help message and exit. + +## Project layout + + mkdocs.yml # The configuration file. + docs/ + index.md # The documentation homepage. + database.md # Documentation of the Database and it's tables + ... # Other markdown pages, images and other files. diff --git a/documentation/database.html b/documentation/database.html new file mode 100644 index 0000000..65ce4cf --- /dev/null +++ b/documentation/database.html @@ -0,0 +1,3071 @@ + + + + + + +database API documentation + + + + + + + + + + + +
+
+
+

Module database

+
+
+
+ +Expand source code + +
import datetime
+import os
+import re
+import sqlite3 as sql
+import tempfile
+from src.logic.log import MyLogger
+#from icecream import ic
+from typing import List, Tuple, Dict, Any, Optional, Union
+from pathlib import Path
+from omegaconf import OmegaConf
+from src.backend.db import CREATE_TABLE_APPARAT, CREATE_TABLE_MESSAGES, CREATE_TABLE_MEDIA, CREATE_TABLE_APPKONTOS, CREATE_TABLE_FILES, CREATE_TABLE_PROF, CREATE_TABLE_USER, CREATE_TABLE_SUBJECTS
+from src.logic.constants import SEMAP_MEDIA_ACCOUNTS
+from src.logic.dataclass import ApparatData, BookData
+from src.errors import NoResultError, AppPresentError
+from src.utils import load_pickle, dump_pickle,create_blob
+config = OmegaConf.load("config.yaml")
+logger = MyLogger(__name__)
+
+
+class Database:
+    """
+    Initialize the database and create the tables if they do not exist.
+    """
+    def __init__(self, db_path: str = None):
+        """
+        Default constructor for the database class
+
+        Args:
+            db_path (str, optional): Optional Path for testing / specific purposes. Defaults to None.
+        """
+        if db_path is None:
+            self.db_path = config.database.path + config.database.name
+            self.db_path = self.db_path.replace("~", str(Path.home()))
+        else:
+            self.db_path = db_path
+        if self.get_db_contents() is None:
+            logger.log_critical("Database does not exist, creating tables")
+            self.create_tables()
+
+    def get_db_contents(self)->Union[List[Tuple], None]:
+        """
+        Get the contents of the 
+
+        Returns:
+            Union[List[Tuple], None]: _description_
+        """
+        try:
+            with sql.connect(self.db_path) as conn:
+                cursor = conn.cursor()
+                cursor.execute("SELECT * FROM sqlite_master WHERE type='table'")
+                return cursor.fetchall()
+        except sql.OperationalError:
+            return None
+
+    def connect(self)->sql.Connection:
+        """
+        Connect to the database
+
+        Returns:
+            sql.Connection: The active connection to the database
+        """
+        return sql.connect(self.db_path)
+
+    def close_connection(self, conn: sql.Connection):
+        """
+        closes the connection to the database
+        
+        Args:
+        ----
+            - conn (sql.Connection): the connection to be closed
+        """
+        conn.close()
+
+    def create_tables(self):
+        """
+        Create the tables in the database
+        """
+        conn = self.connect()
+        cursor = conn.cursor()
+        cursor.execute(CREATE_TABLE_APPARAT)
+        cursor.execute(CREATE_TABLE_MESSAGES)
+        cursor.execute(CREATE_TABLE_MEDIA)
+        cursor.execute(CREATE_TABLE_APPKONTOS)
+        cursor.execute(CREATE_TABLE_FILES)
+        cursor.execute(CREATE_TABLE_PROF)
+        cursor.execute(CREATE_TABLE_USER)
+        cursor.execute(CREATE_TABLE_SUBJECTS)
+        conn.commit()
+        self.close_connection(conn)
+
+    def insertInto(self, query:str, params:Tuple) -> None:
+        """
+        Insert sent data into the database
+
+        Args:
+            query (str): The query to be executed
+            params (Tuple): the parameters to be inserted into the database
+        """
+        conn = self.connect()
+        cursor = conn.cursor()
+        logger.log_info(f"Inserting {params} into database with query {query}")
+        cursor.execute(query, params)
+        conn.commit()
+        self.close_connection(conn)
+
+    def query_db(self, query: str, args: Tuple = (), one: bool = False)->Union[Tuple, List[Tuple]]:
+        """
+        Query the Database for the sent query.
+
+        Args:
+            query (str): The query to be executed
+            args (Tuple, optional): The arguments for the query. Defaults to ().
+            one (bool, optional): Return the first result only. Defaults to False.
+
+        Returns:
+            Union[Typle|List[Tuple]]: Returns the result of the query
+        """
+        conn = self.connect()
+        cursor = conn.cursor()
+        logger.log_info(f"Querying database with query {query}, args: {args}")
+        cursor.execute(query, args)
+        rv = cursor.fetchall()
+        conn.commit()
+        self.close_connection(conn)
+        return (rv[0] if rv else None) if one else rv
+
+    # Books
+    def addBookToDatabase(self, bookdata:BookData,app_id:str, prof_id:str):
+        """
+        Add books to the database. Both app_id and prof_id are required to add the book to the database, as the app_id and prof_id are used to select the books later on.
+
+        Args:
+            bookdata (BookData): The metadata of the book to be added
+            app_id (str): The apparat id where the book should be added to
+            prof_id (str): The id of the professor where the book should be added to.
+        """
+        conn = self.connect()
+        cursor = conn.cursor()
+        t_query = (
+            f"SELECT bookdata FROM media WHERE app_id={app_id} AND prof_id={prof_id}"
+        )
+        # print(t_query)
+        result = cursor.execute(t_query).fetchall()
+        result = [load_pickle(i[0]) for i in result]
+        if bookdata in result:
+            print("Bookdata already in database")
+            # check if the book was deleted in the apparat
+            query = (
+                "SELECT deleted FROM media WHERE app_id=? AND prof_id=? AND bookdata=?"
+            )
+            params = (app_id, prof_id, dump_pickle(bookdata))
+            result = cursor.execute(query, params).fetchone()
+            if result[0] == 1:
+                print("Book was deleted, updating bookdata")
+                query = "UPDATE media SET deleted=0 WHERE app_id=? AND prof_id=? AND bookdata=?"
+                params = (app_id, prof_id, dump_pickle(bookdata))
+                cursor.execute(query, params)
+                conn.commit()
+            return
+
+        query = (
+            "INSERT INTO media (bookdata, app_id, prof_id,deleted) VALUES (?, ?, ?,?)"
+        )
+        converted = dump_pickle(bookdata)
+        params = (converted, app_id, prof_id, 0)
+        cursor.execute(query, params)
+        conn.commit()
+        self.close_connection(conn)
+    def getBookIdBasedOnSignature(self, app_id:str, prof_id:str,signature:str)->int:
+        """
+        Get a book id based on the signature of the book.
+
+        Args:
+            app_id (str): The apparat id the book should be associated with
+            prof_id (str): The professor id the book should be associated with
+            signature (str): The signature of the book
+
+        Returns:
+            int: The id of the book
+        """
+        result = self.query_db("SELECT bookdata, id FROM media WHERE app_id=? AND prof_id=?", (app_id,prof_id))
+        books = [(load_pickle(i[0]),i[1]) for i in result]
+        book = [i for i in books if i[0].signature == signature][0][1]
+        return book
+    def getBookBasedOnSignature(self, app_id:str, prof_id:str,signature:str)->BookData:
+        """
+        Get the book based on the signature of the book.
+
+        Args:
+            app_id (str): The apparat id the book should be associated with
+            prof_id (str): The professor id the book should be associated with
+            signature (str): The signature of the book
+
+        Returns:
+            BookData: The total metadata of the book wrapped in a BookData object
+        """
+        result = self.query_db("SELECT bookdata FROM media WHERE app_id=? AND prof_id=?", (app_id,prof_id))
+        books = [load_pickle(i[0]) for i in result]
+        book = [i for i in books if i.signature == signature][0]
+        return book
+    def getLastBookId(self)->int:
+        """
+        Get the last book id in the database
+
+        Returns:
+            int: ID of the last book in the database
+        """
+        return self.query_db("SELECT id FROM media ORDER BY id DESC", one=True)[0]
+    def searchBook(self, data:dict[str, str])->list[tuple[BookData, int]]:
+        """
+        Search a book in the database based on the sent data.
+
+        Args:
+            data (dict[str, str]): A dictionary containing the data to be searched for. The dictionary can contain the following: 
+            - signature: The signature of the book
+            - title: The title of the book
+
+        Returns:
+            list[tuple[BookData, int]]: A list of tuples containing the wrapped Metadata and the id of the book
+        """
+        rdata = self.query_db("SELECT * FROM media WHERE deleted=0")
+        #ic(rdata, len(rdata))
+        mode = 0
+        if len(data)== 1:
+            if "signature" in data.keys():
+                mode = 1
+            elif "title" in data.keys():
+                mode = 2
+        elif len(data) == 2:
+            mode = 3
+        else:
+            return None
+        ret = []
+        for book in rdata:
+            bookdata = load_pickle(book[1])
+            app_id = book[2]
+            prof_id = book[3]
+            if mode == 1:
+                if data["signature"] in bookdata.signature:
+                    ret.append((bookdata,app_id,prof_id))
+            elif mode == 2:
+                if data["title"] in bookdata.title:
+                    ret.append((bookdata,app_id,prof_id))
+            elif mode == 3:
+                if data["signature"] in bookdata.signature and data["title"] in bookdata.title:
+                    ret.append((bookdata,app_id,prof_id))
+        #ic(ret)
+        return ret
+    def setAvailability(self, book_id:str, available:str):
+        """
+        Set the availability of a book in the database
+
+        Args:
+            book_id (str): The id of the book
+            available (str): The availability of the book
+        """
+        self.query_db("UPDATE media SET available=? WHERE id=?", (available,book_id))
+    def getBookId(self, bookdata:BookData, app_id, prof_id)->int:
+        """
+        Get the id of a book based on the metadata of the book
+
+        Args:
+            bookdata (BookData): The wrapped metadata of the book
+            app_id (str): The apparat id the book should be associated with
+            prof_id (str): The professor id the book should be associated with
+
+        Returns:
+            int: ID of the book
+        """
+        result = self.query_db("SELECT id FROM media WHERE bookdata=? AND app_id=? AND prof_id=?", (dump_pickle(bookdata),app_id,prof_id), one=True)
+        return result[0]
+    def getBook(self,book_id:int)->BookData:
+        """
+        Get the book based on the id in the database
+
+        Args:
+            book_id (int): The id of the book
+
+        Returns:
+            BookData: The metadata of the book wrapped in a BookData object
+        """
+        return load_pickle(self.query_db("SELECT bookdata FROM media WHERE id=?", (book_id,), one=True)[0])
+    def getBooks(self, app_id, prof_id, deleted=0)->list[dict[int, BookData, int]]:
+        """
+        Get the Books based on the apparat id and the professor id
+
+        Args:
+            app_id (str): The ID of the apparat
+            prof_id (str): The ID of the professor
+            deleted (int, optional): The state of the book. Set to 1 to include deleted ones. Defaults to 0.
+
+        Returns:
+            list[dict[int, BookData, int]]: A list of dictionaries containing the id, the metadata of the book and the availability of the book
+        """
+        qdata = self.query_db(f"SELECT id,bookdata,available FROM media WHERE (app_id={app_id} AND prof_id={prof_id}) AND (deleted={deleted if deleted == 0 else '1 OR deleted=0'})")
+        ret_result = []
+        for result_a in qdata:
+            data = {"id": int, "bookdata": BookData, "available": int}
+            data["id"] = result_a[0]
+            data["bookdata"] = load_pickle(result_a[1])
+            data["available"] = result_a[2]
+            ret_result.append(data)
+        return ret_result
+
+    def updateBookdata(self, book_id, bookdata:BookData):
+        """
+        Update the bookdata in the database
+
+        Args:
+            book_id (str): The id of the book
+            bookdata (BookData): The new metadata of the book
+        """
+        self.query_db("UPDATE media SET bookdata=? WHERE id=?", (dump_pickle(bookdata),book_id))
+    def deleteBook(self, book_id):
+        """
+        Delete a book from the database
+
+        Args:
+            book_id (str): ID of the book
+        """
+        self.query_db("UPDATE media SET deleted=1 WHERE id=?", (book_id,))
+
+    # File Interactions
+    def getBlob(self, filename, app_id):
+        """
+        Get a blob from the database
+
+        Args:
+            filename (str): The name of the file
+            app_id (str): ID of the apparat
+
+        Returns:
+            bytes: The file stored in 
+        """
+        return self.query_db("SELECT fileblob FROM files WHERE filename=? AND app_id=?", (filename,app_id), one=True)[0]
+    def insertFile(self, file: list[dict], app_id: int, prof_id):
+        for f in file:
+            filename = f["name"]
+            path = f["path"]
+            filetyp = f["type"]
+            if path == "Database":
+                continue
+            blob = create_blob(path)
+            query = "INSERT OR IGNORE INTO files (filename, fileblob, app_id, filetyp,prof_id) VALUES (?, ?, ?, ?,?)"
+            self.query_db(query, (filename, blob, app_id, filetyp,prof_id))
+    def recreateFile(self, filename, app_id,filetype):
+        blob = self.getBlob(filename, app_id)
+        tempdir = config.database.tempdir
+        tempdir = tempdir.replace("~", str(Path.home()))
+        tempdir_path = Path(tempdir)        
+        if not os.path.exists(tempdir_path):
+            os.mkdir(tempdir_path)
+        file = tempfile.NamedTemporaryFile(
+            delete=False, dir=tempdir_path, mode="wb", suffix=f".{filetype}"
+        )
+        file.write(blob)
+        print("file created")
+        return file.name
+    def getFiles(self, app_id:int, prof_id:int)->list[tuple]:
+        return self.query_db("SELECT filename, filetyp FROM files WHERE app_id=? AND prof_id=?", (app_id,prof_id))
+
+    def getSemersters(self):
+        data = self.query_db("SELECT DISTINCT erstellsemester FROM semesterapparat")
+        return [i[0] for i in data]
+
+    def getSubjects(self):
+        return self.query_db("SELECT * FROM subjects")
+
+    # Messages
+    def addMessage(self, message:dict,user, appnr):
+        def __getUserId(user):
+            return self.query_db("SELECT id FROM user WHERE username=?", (user,), one=True)[0]
+        user_id = __getUserId(user)
+        self.query_db("INSERT INTO messages (message, user_id, remind_at,appnr) VALUES (?,?,?,?)", (message["message"],user_id,message["remind_at"],appnr))
+    def getMessages(self, date:str):
+        def __get_user_name(user_id):
+            return self.query_db("SELECT username FROM user WHERE id=?", (user_id,), one=True)[0]
+        messages = self.query_db("SELECT * FROM messages WHERE remind_at=?", (date,))
+        ret = [
+            {
+                "message": i[2],
+                "user": __get_user_name(i[4]),
+                "appnr": i[5],
+                "id": i[0]
+            }
+            for i in messages
+        ]
+        return ret
+    def deleteMessage(self, message_id):
+        self.query_db("DELETE FROM messages WHERE id=?", (message_id,))
+
+    # Prof data
+    def getProfNameById(self, prof_id:int,add_title:bool=False):
+        prof = self.query_db("SELECT fullname FROM prof WHERE id=?", (prof_id,), one=True)
+        if add_title:
+            return f"{self.getTitleById(prof_id)}{prof[0]}"
+        else:
+            return prof[0]
+    def getTitleById(self, prof_id:int):
+        title = self.query_db("SELECT titel FROM prof WHERE id=?", (prof_id,), one=True)[0]
+        return f"{title} " if title is not None else ""
+    def getProfByName(self, prof_name:str):
+        return self.query_db("SELECT * FROM prof WHERE fullname=?", (prof_name,), one=True)
+    def getProfId(self, prof_name:str):
+        """
+        getProfId _summary_
+
+        :param prof_name: _description_
+        :type prof_name: str
+        :return: _description_
+        :rtype: _type_
+        """
+        data = self.getProfByName(prof_name.replace(",", ""))
+        if data is None:
+            return None
+        else:
+            return data[0]
+    def getSpecificProfData(self, prof_id:int, fields:List[str]):
+        """
+        getSpecificProfData _summary_
+        
+        
+        
+        Args:
+        ----
+            - prof_id (int): _description_
+            - fields (List[str]): _description_
+        
+        Returns:
+        -------
+            - _type_: _description_
+        """
+        query = "SELECT "
+        for field in fields:
+            query += f"{field},"
+        query = query[:-1]
+        query += " FROM prof WHERE id=?"
+        return self.query_db(query, (prof_id,), one=True)[0]
+    def getProfData(self, profname:str):
+        
+        data = self.query_db("SELECT mail, telnr, titel FROM prof WHERE fullname=?", (profname.replace(",",""),), one=True)
+        return data
+    def createProf(self, prof_details:dict):
+        prof_title = prof_details["prof_title"]
+        prof_fname = prof_details["profname"].split(",")[1]
+        prof_fname = prof_fname.strip()
+        prof_lname = prof_details["profname"].split(",")[0]
+        prof_lname = prof_lname.strip()
+        prof_fullname = prof_details["profname"].replace(",", "")
+        prof_mail = prof_details["prof_mail"]
+        prof_tel = prof_details["prof_tel"]
+        params = (prof_title, prof_fname, prof_lname, prof_mail, prof_tel, prof_fullname)
+        query = "INSERT OR IGNORE INTO prof (titel, fname, lname, mail, telnr, fullname) VALUES (?, ?, ?, ?, ?, ?)"
+        self.insertInto(query=query, params=params)
+    def getProfs(self):
+        return self.query_db("SELECT * FROM prof")
+
+    # Apparat
+    def getAllAparats(self,deleted=0):
+        return self.query_db("SELECT * FROM semesterapparat WHERE deletion_status=?", (deleted,))
+    def getApparatData(self, appnr, appname)->ApparatData:
+
+        result = self.query_db("SELECT * FROM semesterapparat WHERE appnr=? AND name=?", (appnr,appname), one=True)
+        if result is None:
+            raise NoResultError("No result found")
+        apparat = ApparatData()
+        apparat.appname = result[1]
+        apparat.appnr = result[4]
+        apparat.dauerapp = True if result[7] == 1 else False
+        prof_data = self.getProfData(self.getProfNameById(result[2]))
+        apparat.profname = self.getProfNameById(result[2])
+        apparat.prof_mail = prof_data[0]
+        apparat.prof_tel = prof_data[1]
+        apparat.prof_title = prof_data[2]
+        apparat.app_fach = result[3]
+        apparat.erstellsemester = result[5]
+        apparat.semester = result[8]
+        apparat.deleted = result[9]
+        apparat.apparat_adis_id = result[11]
+        apparat.prof_adis_id = result[12]
+        return apparat
+    def getUnavailableApparatNumbers(self)->List[int]:
+        """
+        getUnavailableApparatNumbers returns a list of all currently used ApparatNumbers
+        
+        
+        
+        Returns:
+        -------
+            - number(List[int]): a list of all currently used apparat numbers
+        """
+        numbers = self.query_db("SELECT appnr FROM semesterapparat WHERE deletion_status=0")    
+        numbers = [i[0] for i in numbers]
+        logger.log_info(f"Currently used apparat numbers: {numbers}")
+        return numbers
+    def setNewSemesterDate(self, appnr, newDate, dauerapp=False):
+        date = datetime.datetime.strptime(newDate, "%d.%m.%Y").strftime("%Y-%m-%d")
+        if dauerapp:
+            self.query_db("UPDATE semesterapparat SET verlängerung_bis=?, dauerapp=? WHERE appnr=?", (date,dauerapp,appnr))
+        else:
+            self.query_db("UPDATE semesterapparat SET endsemester=? WHERE appnr=?", (date,appnr))
+    def getApparatId(self, apparat_name):
+        data = self.query_db("SELECT appnr FROM semesterapparat WHERE name=?", (apparat_name,), one=True)
+        if data is None:
+            return None
+        else:
+            return data[0]
+    def createApparat(self, apparat:ApparatData)->Optional[AppPresentError]|int:
+        prof_id = self.getProfId(apparat.profname)
+        app_id = self.getApparatId(apparat.appname)
+        if app_id:
+            return AppPresentError(app_id)
+
+        self.createProf(apparat.get_prof_details())
+        prof_id = self.getProfId(apparat.profname)
+        #ic(prof_id)
+        query = f"INSERT OR IGNORE INTO semesterapparat (appnr, name, erstellsemester, dauer, prof_id, fach,deletion_status,konto) VALUES ('{apparat.appnr}', '{apparat.appname}', '{apparat.semester}', '{apparat.dauerapp}', {prof_id}, '{apparat.app_fach}', '{0}', '{SEMAP_MEDIA_ACCOUNTS[apparat.appnr]}')"
+        logger.log_info(query)
+        self.query_db(query)
+        return self.getApparatId(apparat.appname)
+    def getApparatsByProf(self, prof_id:int)->list[tuple]:
+        return self.query_db("SELECT * FROM semesterapparat WHERE prof_id=?", (prof_id,))
+    def getApparatsBySemester(self, semester:str)->dict:
+        data = self.query_db("SELECT name, prof_id FROM semesterapparat WHERE erstellsemester=?", (semester,))
+        conn = self.connect()
+        cursor = conn.cursor()
+        c_tmp = []
+        for i in data:
+            c_tmp.append((i[0], self.getProfNameById(i[1])))
+        query = (
+            f"SELECT name,prof_id FROM semesterapparat WHERE deleted_date='{semester}'"
+        )
+        result = cursor.execute(query).fetchall()
+        d_tmp = []
+        for i in result:
+            d_tmp.append((i[0], self.getProfNameById(i[1])))
+        # group the apparats by prof
+        c_ret = {}
+        for i in c_tmp:
+            if i[1] not in c_ret.keys():
+                c_ret[i[1]] = [i[0]]
+            else:
+                c_ret[i[1]].append(i[0])
+        d_ret = {}
+        for i in d_tmp:
+            if i[1] not in d_ret.keys():
+                d_ret[i[1]] = [i[0]]
+            else:
+                d_ret[i[1]].append(i[0])
+        self.close_connection(conn)
+        return {"created": c_ret, "deleted": d_ret}
+    def getApparatCountBySemester(self)->tuple[list[str],list[int]]:
+        conn = self.connect()
+        cursor = conn.cursor()
+        semesters = self.getSemersters()
+        created = []
+        deleted = []
+        for semester in semesters:
+            query = f"SELECT COUNT(*) FROM semesterapparat WHERE erstellsemester='{semester}'"
+            result = cursor.execute(query).fetchone()
+            created.append(result[0])
+            query = f"SELECT COUNT(*) FROM semesterapparat WHERE deletion_status=1 AND deleted_date='{semester}'"
+            result = cursor.execute(query).fetchone()
+            deleted.append(result[0])
+        # store data in a tuple
+        ret = []
+        e_tuple = ()
+        for sem in semesters:
+            e_tuple = (
+                sem,
+                created[semesters.index(sem)],
+                deleted[semesters.index(sem)],
+            )
+            ret.append(e_tuple)
+        self.close_connection(conn)
+        return ret 
+    def deleteApparat(self, appnr, semester):
+        self.query_db("UPDATE semesterapparat SET deletion_status=1, deleted_date=? WHERE appnr=?", (semester,appnr)) 
+    def isEternal(self, id):
+        return self.query_db("SELECT dauer FROM semesterapparat WHERE appnr=?", (id,), one=True)
+    def getApparatName(self, app_id, prof_id):
+        return self.query_db("SELECT name FROM semesterapparat WHERE appnr=? AND prof_id=?", (app_id,prof_id), one=True)[0]
+    def updateApparat(self, apparat_data:ApparatData):
+        query = f"UPDATE semesterapparat SET name = ?, fach = ?, dauer = ?, prof_id = ? WHERE appnr = ?"
+        params = (
+            apparat_data.appname,
+            apparat_data.app_fach,
+            apparat_data.dauerapp,
+            self.getProfId(apparat_data.profname),
+            apparat_data.appnr,        
+        )
+        self.query_db(query, params)
+    def checkApparatExists(self, apparat_name):
+        return True if self.query_db("SELECT appnr FROM semesterapparat WHERE name=?", (apparat_name,), one=True) else False
+    def checkApparatExistsById(self, apparat_id):
+        return True if self.query_db("SELECT appnr FROM semesterapparat WHERE appnr=?", (apparat_id,), one=True) else False
+    # Statistics
+    def statistic_request(self, **kwargs: Any):
+        def __query(query):
+            conn = self.connect()
+            cursor = conn.cursor()
+            result = cursor.execute(query).fetchall()
+            for result_a in result:
+                orig_value = result_a
+                prof_name = self.getProfNameById(result_a[2])
+                # replace the prof_id with the prof_name
+                result_a = list(result_a)
+                result_a[2] = prof_name
+                result_a = tuple(result_a)
+                result[result.index(orig_value)] = result_a
+            self.close_connection(conn)
+            return result
+        if "deletable" in kwargs.keys():
+            query = f"SELECT * FROM semesterapparat WHERE deletion_status=0 AND dauer=0 AND (erstellsemester!='{kwargs['deletesemester']}' OR verlängerung_bis!='{kwargs['deletesemester']}')"
+            return __query(query)
+        if "dauer" in kwargs.keys():
+            kwargs["dauer"] = kwargs["dauer"].replace("Ja", "1").replace("Nein", "0")
+        query = "SELECT * FROM semesterapparat WHERE "
+        for key, value in kwargs.items() if kwargs.items() is not None else {}:
+            print(key, value)
+            query += f"{key}='{value}' AND "
+            print(query)
+        # remove deletesemester part from normal query, as this will be added to the database upon deleting the apparat
+        if "deletesemester" in kwargs.keys():
+            query = query.replace(
+                f"deletesemester='{kwargs['deletesemester']}' AND ", ""
+            )
+        if "endsemester" in kwargs.keys():
+            if "erstellsemester" in kwargs.keys():
+                query = query.replace(f"endsemester='{kwargs['endsemester']}' AND ", "")
+                query = query.replace(
+                    f"erstellsemester='{kwargs['erstellsemester']} AND ", "xyz"
+                )
+            else:
+                query = query.replace(
+                    f"endsemester='{kwargs['endsemester']}' AND ", "xyz"
+                )
+                print("replaced")
+            query = query.replace(
+                "xyz",
+                f"(erstellsemester='{kwargs['endsemester']}' OR verlängerung_bis='{kwargs['endsemester']}') AND ",
+            )
+        # remove all x="" parts from the query where x is a key in kwargs
+        query = query[:-5]
+        print(query)
+        return __query(query)
+
+    # Admin data
+    def getUser(self):
+        return self.query_db("SELECT * FROM user", one=True)
+    def getUsers(self):
+        return self.query_db("SELECT * FROM user")
+
+    def login(self, user, hashed_password):
+        salt = self.query_db("SELECT salt FROM user WHERE username=?", (user,), one=True)[0]
+        if salt is None:
+            return False
+        hashed_password = salt + hashed_password
+        password = self.query_db("SELECT password FROM user WHERE username=?", (user,), one=True)[0]
+        if password == hashed_password:
+            return True
+        else:
+            return False
+    def changePassword(self, user, new_password):
+        salt = self.query_db("SELECT salt FROM user WHERE username=?", (user,), one=True)[0]
+        new_password = salt + new_password
+        self.query_db("UPDATE user SET password=? WHERE username=?", (new_password,user))
+    def getRole(self, user):
+        return self.query_db("SELECT role FROM user WHERE username=?", (user,), one=True)[0]
+    def getRoles(self):
+        return self.query_db("SELECT role FROM user")
+    def checkUsername(self, user):
+        data = self.query_db("SELECT username FROM user WHERE username=?", (user,), one=True)
+        return True if data is not None else False
+    def createUser(self, user, password, role, salt):
+        """Create a user based on passed data.
+
+        Args:
+        ----
+            - username (str): Username to be used
+            - password (str): the salted password
+            - role (str): Role of the user
+            - salt (str): a random salt for the user
+        """
+        self.query_db("INSERT OR IGNORE INTO user (username, password, role, salt) VALUES (?,?,?,?)", (user,password,role,salt))
+    def deleteUser(self, user):
+        self.query_db("DELETE FROM user WHERE username=?", (user,))
+    def updateUser(self, username, data:dict[str, str]):
+        conn = self.connect()
+        cursor = conn.cursor()
+        query = "UPDATE user SET "
+        for key,value in data.items():
+            if key == "username":
+                continue
+            query += f"{key}='{value}',"
+        query = query[:-1]
+        query += " WHERE username=?"
+        params = (username,)
+        cursor.execute(query, params)
+        conn.commit()
+        self.close_connection(conn)
+    def getFacultyMember(self, name:str):
+        return self.query_db("SELECT titel, fname,lname,mail,telnr,fullname FROM prof WHERE fullname=?", (name,), one=True)
+    def updateFacultyMember(self, data, oldlname, oldfname):
+        placeholders = ", ".join([f"{i}=:{i} " for i in data.keys()])
+        query = f"UPDATE prof SET {placeholders} WHERE lname = :oldlname AND fname = :oldfname"
+        data["oldlname"] = oldlname
+        data["oldfname"] = oldfname
+        self.query_db(query, data)
+    def getFacultyMembers(self):
+        return self.query_db("SELECT titel, fname,lname,mail,telnr,fullname FROM prof")
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class Database +(db_path: str = None) +
+
+

Initialize the database and create the tables if they do not exist.

+

Default constructor for the database class

+

Args

+
+
db_path : str, optional
+
Optional Path for testing / specific purposes. Defaults to None.
+
+
+ +Expand source code + +
class Database:
+    """
+    Initialize the database and create the tables if they do not exist.
+    """
+    def __init__(self, db_path: str = None):
+        """
+        Default constructor for the database class
+
+        Args:
+            db_path (str, optional): Optional Path for testing / specific purposes. Defaults to None.
+        """
+        if db_path is None:
+            self.db_path = config.database.path + config.database.name
+            self.db_path = self.db_path.replace("~", str(Path.home()))
+        else:
+            self.db_path = db_path
+        if self.get_db_contents() is None:
+            logger.log_critical("Database does not exist, creating tables")
+            self.create_tables()
+
+    def get_db_contents(self)->Union[List[Tuple], None]:
+        """
+        Get the contents of the 
+
+        Returns:
+            Union[List[Tuple], None]: _description_
+        """
+        try:
+            with sql.connect(self.db_path) as conn:
+                cursor = conn.cursor()
+                cursor.execute("SELECT * FROM sqlite_master WHERE type='table'")
+                return cursor.fetchall()
+        except sql.OperationalError:
+            return None
+
+    def connect(self)->sql.Connection:
+        """
+        Connect to the database
+
+        Returns:
+            sql.Connection: The active connection to the database
+        """
+        return sql.connect(self.db_path)
+
+    def close_connection(self, conn: sql.Connection):
+        """
+        closes the connection to the database
+        
+        Args:
+        ----
+            - conn (sql.Connection): the connection to be closed
+        """
+        conn.close()
+
+    def create_tables(self):
+        """
+        Create the tables in the database
+        """
+        conn = self.connect()
+        cursor = conn.cursor()
+        cursor.execute(CREATE_TABLE_APPARAT)
+        cursor.execute(CREATE_TABLE_MESSAGES)
+        cursor.execute(CREATE_TABLE_MEDIA)
+        cursor.execute(CREATE_TABLE_APPKONTOS)
+        cursor.execute(CREATE_TABLE_FILES)
+        cursor.execute(CREATE_TABLE_PROF)
+        cursor.execute(CREATE_TABLE_USER)
+        cursor.execute(CREATE_TABLE_SUBJECTS)
+        conn.commit()
+        self.close_connection(conn)
+
+    def insertInto(self, query:str, params:Tuple) -> None:
+        """
+        Insert sent data into the database
+
+        Args:
+            query (str): The query to be executed
+            params (Tuple): the parameters to be inserted into the database
+        """
+        conn = self.connect()
+        cursor = conn.cursor()
+        logger.log_info(f"Inserting {params} into database with query {query}")
+        cursor.execute(query, params)
+        conn.commit()
+        self.close_connection(conn)
+
+    def query_db(self, query: str, args: Tuple = (), one: bool = False)->Union[Tuple, List[Tuple]]:
+        """
+        Query the Database for the sent query.
+
+        Args:
+            query (str): The query to be executed
+            args (Tuple, optional): The arguments for the query. Defaults to ().
+            one (bool, optional): Return the first result only. Defaults to False.
+
+        Returns:
+            Union[Typle|List[Tuple]]: Returns the result of the query
+        """
+        conn = self.connect()
+        cursor = conn.cursor()
+        logger.log_info(f"Querying database with query {query}, args: {args}")
+        cursor.execute(query, args)
+        rv = cursor.fetchall()
+        conn.commit()
+        self.close_connection(conn)
+        return (rv[0] if rv else None) if one else rv
+
+    # Books
+    def addBookToDatabase(self, bookdata:BookData,app_id:str, prof_id:str):
+        """
+        Add books to the database. Both app_id and prof_id are required to add the book to the database, as the app_id and prof_id are used to select the books later on.
+
+        Args:
+            bookdata (BookData): The metadata of the book to be added
+            app_id (str): The apparat id where the book should be added to
+            prof_id (str): The id of the professor where the book should be added to.
+        """
+        conn = self.connect()
+        cursor = conn.cursor()
+        t_query = (
+            f"SELECT bookdata FROM media WHERE app_id={app_id} AND prof_id={prof_id}"
+        )
+        # print(t_query)
+        result = cursor.execute(t_query).fetchall()
+        result = [load_pickle(i[0]) for i in result]
+        if bookdata in result:
+            print("Bookdata already in database")
+            # check if the book was deleted in the apparat
+            query = (
+                "SELECT deleted FROM media WHERE app_id=? AND prof_id=? AND bookdata=?"
+            )
+            params = (app_id, prof_id, dump_pickle(bookdata))
+            result = cursor.execute(query, params).fetchone()
+            if result[0] == 1:
+                print("Book was deleted, updating bookdata")
+                query = "UPDATE media SET deleted=0 WHERE app_id=? AND prof_id=? AND bookdata=?"
+                params = (app_id, prof_id, dump_pickle(bookdata))
+                cursor.execute(query, params)
+                conn.commit()
+            return
+
+        query = (
+            "INSERT INTO media (bookdata, app_id, prof_id,deleted) VALUES (?, ?, ?,?)"
+        )
+        converted = dump_pickle(bookdata)
+        params = (converted, app_id, prof_id, 0)
+        cursor.execute(query, params)
+        conn.commit()
+        self.close_connection(conn)
+    def getBookIdBasedOnSignature(self, app_id:str, prof_id:str,signature:str)->int:
+        """
+        Get a book id based on the signature of the book.
+
+        Args:
+            app_id (str): The apparat id the book should be associated with
+            prof_id (str): The professor id the book should be associated with
+            signature (str): The signature of the book
+
+        Returns:
+            int: The id of the book
+        """
+        result = self.query_db("SELECT bookdata, id FROM media WHERE app_id=? AND prof_id=?", (app_id,prof_id))
+        books = [(load_pickle(i[0]),i[1]) for i in result]
+        book = [i for i in books if i[0].signature == signature][0][1]
+        return book
+    def getBookBasedOnSignature(self, app_id:str, prof_id:str,signature:str)->BookData:
+        """
+        Get the book based on the signature of the book.
+
+        Args:
+            app_id (str): The apparat id the book should be associated with
+            prof_id (str): The professor id the book should be associated with
+            signature (str): The signature of the book
+
+        Returns:
+            BookData: The total metadata of the book wrapped in a BookData object
+        """
+        result = self.query_db("SELECT bookdata FROM media WHERE app_id=? AND prof_id=?", (app_id,prof_id))
+        books = [load_pickle(i[0]) for i in result]
+        book = [i for i in books if i.signature == signature][0]
+        return book
+    def getLastBookId(self)->int:
+        """
+        Get the last book id in the database
+
+        Returns:
+            int: ID of the last book in the database
+        """
+        return self.query_db("SELECT id FROM media ORDER BY id DESC", one=True)[0]
+    def searchBook(self, data:dict[str, str])->list[tuple[BookData, int]]:
+        """
+        Search a book in the database based on the sent data.
+
+        Args:
+            data (dict[str, str]): A dictionary containing the data to be searched for. The dictionary can contain the following: 
+            - signature: The signature of the book
+            - title: The title of the book
+
+        Returns:
+            list[tuple[BookData, int]]: A list of tuples containing the wrapped Metadata and the id of the book
+        """
+        rdata = self.query_db("SELECT * FROM media WHERE deleted=0")
+        #ic(rdata, len(rdata))
+        mode = 0
+        if len(data)== 1:
+            if "signature" in data.keys():
+                mode = 1
+            elif "title" in data.keys():
+                mode = 2
+        elif len(data) == 2:
+            mode = 3
+        else:
+            return None
+        ret = []
+        for book in rdata:
+            bookdata = load_pickle(book[1])
+            app_id = book[2]
+            prof_id = book[3]
+            if mode == 1:
+                if data["signature"] in bookdata.signature:
+                    ret.append((bookdata,app_id,prof_id))
+            elif mode == 2:
+                if data["title"] in bookdata.title:
+                    ret.append((bookdata,app_id,prof_id))
+            elif mode == 3:
+                if data["signature"] in bookdata.signature and data["title"] in bookdata.title:
+                    ret.append((bookdata,app_id,prof_id))
+        #ic(ret)
+        return ret
+    def setAvailability(self, book_id:str, available:str):
+        """
+        Set the availability of a book in the database
+
+        Args:
+            book_id (str): The id of the book
+            available (str): The availability of the book
+        """
+        self.query_db("UPDATE media SET available=? WHERE id=?", (available,book_id))
+    def getBookId(self, bookdata:BookData, app_id, prof_id)->int:
+        """
+        Get the id of a book based on the metadata of the book
+
+        Args:
+            bookdata (BookData): The wrapped metadata of the book
+            app_id (str): The apparat id the book should be associated with
+            prof_id (str): The professor id the book should be associated with
+
+        Returns:
+            int: ID of the book
+        """
+        result = self.query_db("SELECT id FROM media WHERE bookdata=? AND app_id=? AND prof_id=?", (dump_pickle(bookdata),app_id,prof_id), one=True)
+        return result[0]
+    def getBook(self,book_id:int)->BookData:
+        """
+        Get the book based on the id in the database
+
+        Args:
+            book_id (int): The id of the book
+
+        Returns:
+            BookData: The metadata of the book wrapped in a BookData object
+        """
+        return load_pickle(self.query_db("SELECT bookdata FROM media WHERE id=?", (book_id,), one=True)[0])
+    def getBooks(self, app_id, prof_id, deleted=0)->list[dict[int, BookData, int]]:
+        """
+        Get the Books based on the apparat id and the professor id
+
+        Args:
+            app_id (str): The ID of the apparat
+            prof_id (str): The ID of the professor
+            deleted (int, optional): The state of the book. Set to 1 to include deleted ones. Defaults to 0.
+
+        Returns:
+            list[dict[int, BookData, int]]: A list of dictionaries containing the id, the metadata of the book and the availability of the book
+        """
+        qdata = self.query_db(f"SELECT id,bookdata,available FROM media WHERE (app_id={app_id} AND prof_id={prof_id}) AND (deleted={deleted if deleted == 0 else '1 OR deleted=0'})")
+        ret_result = []
+        for result_a in qdata:
+            data = {"id": int, "bookdata": BookData, "available": int}
+            data["id"] = result_a[0]
+            data["bookdata"] = load_pickle(result_a[1])
+            data["available"] = result_a[2]
+            ret_result.append(data)
+        return ret_result
+
+    def updateBookdata(self, book_id, bookdata:BookData):
+        """
+        Update the bookdata in the database
+
+        Args:
+            book_id (str): The id of the book
+            bookdata (BookData): The new metadata of the book
+        """
+        self.query_db("UPDATE media SET bookdata=? WHERE id=?", (dump_pickle(bookdata),book_id))
+    def deleteBook(self, book_id):
+        """
+        Delete a book from the database
+
+        Args:
+            book_id (str): ID of the book
+        """
+        self.query_db("UPDATE media SET deleted=1 WHERE id=?", (book_id,))
+
+    # File Interactions
+    def getBlob(self, filename, app_id):
+        """
+        Get a blob from the database
+
+        Args:
+            filename (str): The name of the file
+            app_id (str): ID of the apparat
+
+        Returns:
+            bytes: The file stored in 
+        """
+        return self.query_db("SELECT fileblob FROM files WHERE filename=? AND app_id=?", (filename,app_id), one=True)[0]
+    def insertFile(self, file: list[dict], app_id: int, prof_id):
+        for f in file:
+            filename = f["name"]
+            path = f["path"]
+            filetyp = f["type"]
+            if path == "Database":
+                continue
+            blob = create_blob(path)
+            query = "INSERT OR IGNORE INTO files (filename, fileblob, app_id, filetyp,prof_id) VALUES (?, ?, ?, ?,?)"
+            self.query_db(query, (filename, blob, app_id, filetyp,prof_id))
+    def recreateFile(self, filename, app_id,filetype):
+        blob = self.getBlob(filename, app_id)
+        tempdir = config.database.tempdir
+        tempdir = tempdir.replace("~", str(Path.home()))
+        tempdir_path = Path(tempdir)        
+        if not os.path.exists(tempdir_path):
+            os.mkdir(tempdir_path)
+        file = tempfile.NamedTemporaryFile(
+            delete=False, dir=tempdir_path, mode="wb", suffix=f".{filetype}"
+        )
+        file.write(blob)
+        print("file created")
+        return file.name
+    def getFiles(self, app_id:int, prof_id:int)->list[tuple]:
+        return self.query_db("SELECT filename, filetyp FROM files WHERE app_id=? AND prof_id=?", (app_id,prof_id))
+
+    def getSemersters(self):
+        data = self.query_db("SELECT DISTINCT erstellsemester FROM semesterapparat")
+        return [i[0] for i in data]
+
+    def getSubjects(self):
+        return self.query_db("SELECT * FROM subjects")
+
+    # Messages
+    def addMessage(self, message:dict,user, appnr):
+        def __getUserId(user):
+            return self.query_db("SELECT id FROM user WHERE username=?", (user,), one=True)[0]
+        user_id = __getUserId(user)
+        self.query_db("INSERT INTO messages (message, user_id, remind_at,appnr) VALUES (?,?,?,?)", (message["message"],user_id,message["remind_at"],appnr))
+    def getMessages(self, date:str):
+        def __get_user_name(user_id):
+            return self.query_db("SELECT username FROM user WHERE id=?", (user_id,), one=True)[0]
+        messages = self.query_db("SELECT * FROM messages WHERE remind_at=?", (date,))
+        ret = [
+            {
+                "message": i[2],
+                "user": __get_user_name(i[4]),
+                "appnr": i[5],
+                "id": i[0]
+            }
+            for i in messages
+        ]
+        return ret
+    def deleteMessage(self, message_id):
+        self.query_db("DELETE FROM messages WHERE id=?", (message_id,))
+
+    # Prof data
+    def getProfNameById(self, prof_id:int,add_title:bool=False):
+        prof = self.query_db("SELECT fullname FROM prof WHERE id=?", (prof_id,), one=True)
+        if add_title:
+            return f"{self.getTitleById(prof_id)}{prof[0]}"
+        else:
+            return prof[0]
+    def getTitleById(self, prof_id:int):
+        title = self.query_db("SELECT titel FROM prof WHERE id=?", (prof_id,), one=True)[0]
+        return f"{title} " if title is not None else ""
+    def getProfByName(self, prof_name:str):
+        return self.query_db("SELECT * FROM prof WHERE fullname=?", (prof_name,), one=True)
+    def getProfId(self, prof_name:str):
+        """
+        getProfId _summary_
+
+        :param prof_name: _description_
+        :type prof_name: str
+        :return: _description_
+        :rtype: _type_
+        """
+        data = self.getProfByName(prof_name.replace(",", ""))
+        if data is None:
+            return None
+        else:
+            return data[0]
+    def getSpecificProfData(self, prof_id:int, fields:List[str]):
+        """
+        getSpecificProfData _summary_
+        
+        
+        
+        Args:
+        ----
+            - prof_id (int): _description_
+            - fields (List[str]): _description_
+        
+        Returns:
+        -------
+            - _type_: _description_
+        """
+        query = "SELECT "
+        for field in fields:
+            query += f"{field},"
+        query = query[:-1]
+        query += " FROM prof WHERE id=?"
+        return self.query_db(query, (prof_id,), one=True)[0]
+    def getProfData(self, profname:str):
+        
+        data = self.query_db("SELECT mail, telnr, titel FROM prof WHERE fullname=?", (profname.replace(",",""),), one=True)
+        return data
+    def createProf(self, prof_details:dict):
+        prof_title = prof_details["prof_title"]
+        prof_fname = prof_details["profname"].split(",")[1]
+        prof_fname = prof_fname.strip()
+        prof_lname = prof_details["profname"].split(",")[0]
+        prof_lname = prof_lname.strip()
+        prof_fullname = prof_details["profname"].replace(",", "")
+        prof_mail = prof_details["prof_mail"]
+        prof_tel = prof_details["prof_tel"]
+        params = (prof_title, prof_fname, prof_lname, prof_mail, prof_tel, prof_fullname)
+        query = "INSERT OR IGNORE INTO prof (titel, fname, lname, mail, telnr, fullname) VALUES (?, ?, ?, ?, ?, ?)"
+        self.insertInto(query=query, params=params)
+    def getProfs(self):
+        return self.query_db("SELECT * FROM prof")
+
+    # Apparat
+    def getAllAparats(self,deleted=0):
+        return self.query_db("SELECT * FROM semesterapparat WHERE deletion_status=?", (deleted,))
+    def getApparatData(self, appnr, appname)->ApparatData:
+
+        result = self.query_db("SELECT * FROM semesterapparat WHERE appnr=? AND name=?", (appnr,appname), one=True)
+        if result is None:
+            raise NoResultError("No result found")
+        apparat = ApparatData()
+        apparat.appname = result[1]
+        apparat.appnr = result[4]
+        apparat.dauerapp = True if result[7] == 1 else False
+        prof_data = self.getProfData(self.getProfNameById(result[2]))
+        apparat.profname = self.getProfNameById(result[2])
+        apparat.prof_mail = prof_data[0]
+        apparat.prof_tel = prof_data[1]
+        apparat.prof_title = prof_data[2]
+        apparat.app_fach = result[3]
+        apparat.erstellsemester = result[5]
+        apparat.semester = result[8]
+        apparat.deleted = result[9]
+        apparat.apparat_adis_id = result[11]
+        apparat.prof_adis_id = result[12]
+        return apparat
+    def getUnavailableApparatNumbers(self)->List[int]:
+        """
+        getUnavailableApparatNumbers returns a list of all currently used ApparatNumbers
+        
+        
+        
+        Returns:
+        -------
+            - number(List[int]): a list of all currently used apparat numbers
+        """
+        numbers = self.query_db("SELECT appnr FROM semesterapparat WHERE deletion_status=0")    
+        numbers = [i[0] for i in numbers]
+        logger.log_info(f"Currently used apparat numbers: {numbers}")
+        return numbers
+    def setNewSemesterDate(self, appnr, newDate, dauerapp=False):
+        date = datetime.datetime.strptime(newDate, "%d.%m.%Y").strftime("%Y-%m-%d")
+        if dauerapp:
+            self.query_db("UPDATE semesterapparat SET verlängerung_bis=?, dauerapp=? WHERE appnr=?", (date,dauerapp,appnr))
+        else:
+            self.query_db("UPDATE semesterapparat SET endsemester=? WHERE appnr=?", (date,appnr))
+    def getApparatId(self, apparat_name):
+        data = self.query_db("SELECT appnr FROM semesterapparat WHERE name=?", (apparat_name,), one=True)
+        if data is None:
+            return None
+        else:
+            return data[0]
+    def createApparat(self, apparat:ApparatData)->Optional[AppPresentError]|int:
+        prof_id = self.getProfId(apparat.profname)
+        app_id = self.getApparatId(apparat.appname)
+        if app_id:
+            return AppPresentError(app_id)
+
+        self.createProf(apparat.get_prof_details())
+        prof_id = self.getProfId(apparat.profname)
+        #ic(prof_id)
+        query = f"INSERT OR IGNORE INTO semesterapparat (appnr, name, erstellsemester, dauer, prof_id, fach,deletion_status,konto) VALUES ('{apparat.appnr}', '{apparat.appname}', '{apparat.semester}', '{apparat.dauerapp}', {prof_id}, '{apparat.app_fach}', '{0}', '{SEMAP_MEDIA_ACCOUNTS[apparat.appnr]}')"
+        logger.log_info(query)
+        self.query_db(query)
+        return self.getApparatId(apparat.appname)
+    def getApparatsByProf(self, prof_id:int)->list[tuple]:
+        return self.query_db("SELECT * FROM semesterapparat WHERE prof_id=?", (prof_id,))
+    def getApparatsBySemester(self, semester:str)->dict:
+        data = self.query_db("SELECT name, prof_id FROM semesterapparat WHERE erstellsemester=?", (semester,))
+        conn = self.connect()
+        cursor = conn.cursor()
+        c_tmp = []
+        for i in data:
+            c_tmp.append((i[0], self.getProfNameById(i[1])))
+        query = (
+            f"SELECT name,prof_id FROM semesterapparat WHERE deleted_date='{semester}'"
+        )
+        result = cursor.execute(query).fetchall()
+        d_tmp = []
+        for i in result:
+            d_tmp.append((i[0], self.getProfNameById(i[1])))
+        # group the apparats by prof
+        c_ret = {}
+        for i in c_tmp:
+            if i[1] not in c_ret.keys():
+                c_ret[i[1]] = [i[0]]
+            else:
+                c_ret[i[1]].append(i[0])
+        d_ret = {}
+        for i in d_tmp:
+            if i[1] not in d_ret.keys():
+                d_ret[i[1]] = [i[0]]
+            else:
+                d_ret[i[1]].append(i[0])
+        self.close_connection(conn)
+        return {"created": c_ret, "deleted": d_ret}
+    def getApparatCountBySemester(self)->tuple[list[str],list[int]]:
+        conn = self.connect()
+        cursor = conn.cursor()
+        semesters = self.getSemersters()
+        created = []
+        deleted = []
+        for semester in semesters:
+            query = f"SELECT COUNT(*) FROM semesterapparat WHERE erstellsemester='{semester}'"
+            result = cursor.execute(query).fetchone()
+            created.append(result[0])
+            query = f"SELECT COUNT(*) FROM semesterapparat WHERE deletion_status=1 AND deleted_date='{semester}'"
+            result = cursor.execute(query).fetchone()
+            deleted.append(result[0])
+        # store data in a tuple
+        ret = []
+        e_tuple = ()
+        for sem in semesters:
+            e_tuple = (
+                sem,
+                created[semesters.index(sem)],
+                deleted[semesters.index(sem)],
+            )
+            ret.append(e_tuple)
+        self.close_connection(conn)
+        return ret 
+    def deleteApparat(self, appnr, semester):
+        self.query_db("UPDATE semesterapparat SET deletion_status=1, deleted_date=? WHERE appnr=?", (semester,appnr)) 
+    def isEternal(self, id):
+        return self.query_db("SELECT dauer FROM semesterapparat WHERE appnr=?", (id,), one=True)
+    def getApparatName(self, app_id, prof_id):
+        return self.query_db("SELECT name FROM semesterapparat WHERE appnr=? AND prof_id=?", (app_id,prof_id), one=True)[0]
+    def updateApparat(self, apparat_data:ApparatData):
+        query = f"UPDATE semesterapparat SET name = ?, fach = ?, dauer = ?, prof_id = ? WHERE appnr = ?"
+        params = (
+            apparat_data.appname,
+            apparat_data.app_fach,
+            apparat_data.dauerapp,
+            self.getProfId(apparat_data.profname),
+            apparat_data.appnr,        
+        )
+        self.query_db(query, params)
+    def checkApparatExists(self, apparat_name):
+        return True if self.query_db("SELECT appnr FROM semesterapparat WHERE name=?", (apparat_name,), one=True) else False
+    def checkApparatExistsById(self, apparat_id):
+        return True if self.query_db("SELECT appnr FROM semesterapparat WHERE appnr=?", (apparat_id,), one=True) else False
+    # Statistics
+    def statistic_request(self, **kwargs: Any):
+        def __query(query):
+            conn = self.connect()
+            cursor = conn.cursor()
+            result = cursor.execute(query).fetchall()
+            for result_a in result:
+                orig_value = result_a
+                prof_name = self.getProfNameById(result_a[2])
+                # replace the prof_id with the prof_name
+                result_a = list(result_a)
+                result_a[2] = prof_name
+                result_a = tuple(result_a)
+                result[result.index(orig_value)] = result_a
+            self.close_connection(conn)
+            return result
+        if "deletable" in kwargs.keys():
+            query = f"SELECT * FROM semesterapparat WHERE deletion_status=0 AND dauer=0 AND (erstellsemester!='{kwargs['deletesemester']}' OR verlängerung_bis!='{kwargs['deletesemester']}')"
+            return __query(query)
+        if "dauer" in kwargs.keys():
+            kwargs["dauer"] = kwargs["dauer"].replace("Ja", "1").replace("Nein", "0")
+        query = "SELECT * FROM semesterapparat WHERE "
+        for key, value in kwargs.items() if kwargs.items() is not None else {}:
+            print(key, value)
+            query += f"{key}='{value}' AND "
+            print(query)
+        # remove deletesemester part from normal query, as this will be added to the database upon deleting the apparat
+        if "deletesemester" in kwargs.keys():
+            query = query.replace(
+                f"deletesemester='{kwargs['deletesemester']}' AND ", ""
+            )
+        if "endsemester" in kwargs.keys():
+            if "erstellsemester" in kwargs.keys():
+                query = query.replace(f"endsemester='{kwargs['endsemester']}' AND ", "")
+                query = query.replace(
+                    f"erstellsemester='{kwargs['erstellsemester']} AND ", "xyz"
+                )
+            else:
+                query = query.replace(
+                    f"endsemester='{kwargs['endsemester']}' AND ", "xyz"
+                )
+                print("replaced")
+            query = query.replace(
+                "xyz",
+                f"(erstellsemester='{kwargs['endsemester']}' OR verlängerung_bis='{kwargs['endsemester']}') AND ",
+            )
+        # remove all x="" parts from the query where x is a key in kwargs
+        query = query[:-5]
+        print(query)
+        return __query(query)
+
+    # Admin data
+    def getUser(self):
+        return self.query_db("SELECT * FROM user", one=True)
+    def getUsers(self):
+        return self.query_db("SELECT * FROM user")
+
+    def login(self, user, hashed_password):
+        salt = self.query_db("SELECT salt FROM user WHERE username=?", (user,), one=True)[0]
+        if salt is None:
+            return False
+        hashed_password = salt + hashed_password
+        password = self.query_db("SELECT password FROM user WHERE username=?", (user,), one=True)[0]
+        if password == hashed_password:
+            return True
+        else:
+            return False
+    def changePassword(self, user, new_password):
+        salt = self.query_db("SELECT salt FROM user WHERE username=?", (user,), one=True)[0]
+        new_password = salt + new_password
+        self.query_db("UPDATE user SET password=? WHERE username=?", (new_password,user))
+    def getRole(self, user):
+        return self.query_db("SELECT role FROM user WHERE username=?", (user,), one=True)[0]
+    def getRoles(self):
+        return self.query_db("SELECT role FROM user")
+    def checkUsername(self, user):
+        data = self.query_db("SELECT username FROM user WHERE username=?", (user,), one=True)
+        return True if data is not None else False
+    def createUser(self, user, password, role, salt):
+        """Create a user based on passed data.
+
+        Args:
+        ----
+            - username (str): Username to be used
+            - password (str): the salted password
+            - role (str): Role of the user
+            - salt (str): a random salt for the user
+        """
+        self.query_db("INSERT OR IGNORE INTO user (username, password, role, salt) VALUES (?,?,?,?)", (user,password,role,salt))
+    def deleteUser(self, user):
+        self.query_db("DELETE FROM user WHERE username=?", (user,))
+    def updateUser(self, username, data:dict[str, str]):
+        conn = self.connect()
+        cursor = conn.cursor()
+        query = "UPDATE user SET "
+        for key,value in data.items():
+            if key == "username":
+                continue
+            query += f"{key}='{value}',"
+        query = query[:-1]
+        query += " WHERE username=?"
+        params = (username,)
+        cursor.execute(query, params)
+        conn.commit()
+        self.close_connection(conn)
+    def getFacultyMember(self, name:str):
+        return self.query_db("SELECT titel, fname,lname,mail,telnr,fullname FROM prof WHERE fullname=?", (name,), one=True)
+    def updateFacultyMember(self, data, oldlname, oldfname):
+        placeholders = ", ".join([f"{i}=:{i} " for i in data.keys()])
+        query = f"UPDATE prof SET {placeholders} WHERE lname = :oldlname AND fname = :oldfname"
+        data["oldlname"] = oldlname
+        data["oldfname"] = oldfname
+        self.query_db(query, data)
+    def getFacultyMembers(self):
+        return self.query_db("SELECT titel, fname,lname,mail,telnr,fullname FROM prof")
+
+

Methods

+
+
+def addBookToDatabase(self, bookdata: src.logic.dataclass.BookData, app_id: str, prof_id: str) +
+
+

Add books to the database. Both app_id and prof_id are required to add the book to the database, as the app_id and prof_id are used to select the books later on.

+

Args

+
+
bookdata : BookData
+
The metadata of the book to be added
+
app_id : str
+
The apparat id where the book should be added to
+
prof_id : str
+
The id of the professor where the book should be added to.
+
+
+ +Expand source code + +
def addBookToDatabase(self, bookdata:BookData,app_id:str, prof_id:str):
+    """
+    Add books to the database. Both app_id and prof_id are required to add the book to the database, as the app_id and prof_id are used to select the books later on.
+
+    Args:
+        bookdata (BookData): The metadata of the book to be added
+        app_id (str): The apparat id where the book should be added to
+        prof_id (str): The id of the professor where the book should be added to.
+    """
+    conn = self.connect()
+    cursor = conn.cursor()
+    t_query = (
+        f"SELECT bookdata FROM media WHERE app_id={app_id} AND prof_id={prof_id}"
+    )
+    # print(t_query)
+    result = cursor.execute(t_query).fetchall()
+    result = [load_pickle(i[0]) for i in result]
+    if bookdata in result:
+        print("Bookdata already in database")
+        # check if the book was deleted in the apparat
+        query = (
+            "SELECT deleted FROM media WHERE app_id=? AND prof_id=? AND bookdata=?"
+        )
+        params = (app_id, prof_id, dump_pickle(bookdata))
+        result = cursor.execute(query, params).fetchone()
+        if result[0] == 1:
+            print("Book was deleted, updating bookdata")
+            query = "UPDATE media SET deleted=0 WHERE app_id=? AND prof_id=? AND bookdata=?"
+            params = (app_id, prof_id, dump_pickle(bookdata))
+            cursor.execute(query, params)
+            conn.commit()
+        return
+
+    query = (
+        "INSERT INTO media (bookdata, app_id, prof_id,deleted) VALUES (?, ?, ?,?)"
+    )
+    converted = dump_pickle(bookdata)
+    params = (converted, app_id, prof_id, 0)
+    cursor.execute(query, params)
+    conn.commit()
+    self.close_connection(conn)
+
+
+
+def addMessage(self, message: dict, user, appnr) +
+
+
+
+ +Expand source code + +
def addMessage(self, message:dict,user, appnr):
+    def __getUserId(user):
+        return self.query_db("SELECT id FROM user WHERE username=?", (user,), one=True)[0]
+    user_id = __getUserId(user)
+    self.query_db("INSERT INTO messages (message, user_id, remind_at,appnr) VALUES (?,?,?,?)", (message["message"],user_id,message["remind_at"],appnr))
+
+
+
+def changePassword(self, user, new_password) +
+
+
+
+ +Expand source code + +
def changePassword(self, user, new_password):
+    salt = self.query_db("SELECT salt FROM user WHERE username=?", (user,), one=True)[0]
+    new_password = salt + new_password
+    self.query_db("UPDATE user SET password=? WHERE username=?", (new_password,user))
+
+
+
+def checkApparatExists(self, apparat_name) +
+
+
+
+ +Expand source code + +
def checkApparatExists(self, apparat_name):
+    return True if self.query_db("SELECT appnr FROM semesterapparat WHERE name=?", (apparat_name,), one=True) else False
+
+
+
+def checkApparatExistsById(self, apparat_id) +
+
+
+
+ +Expand source code + +
def checkApparatExistsById(self, apparat_id):
+    return True if self.query_db("SELECT appnr FROM semesterapparat WHERE appnr=?", (apparat_id,), one=True) else False
+
+
+
+def checkUsername(self, user) +
+
+
+
+ +Expand source code + +
def checkUsername(self, user):
+    data = self.query_db("SELECT username FROM user WHERE username=?", (user,), one=True)
+    return True if data is not None else False
+
+
+
+def close_connection(self, conn: sqlite3.Connection) +
+
+

closes the connection to the database

+

Args:

+
- conn (sql.Connection): the connection to be closed
+
+
+ +Expand source code + +
def close_connection(self, conn: sql.Connection):
+    """
+    closes the connection to the database
+    
+    Args:
+    ----
+        - conn (sql.Connection): the connection to be closed
+    """
+    conn.close()
+
+
+
+def connect(self) ‑> sqlite3.Connection +
+
+

Connect to the database

+

Returns

+
+
sql.Connection
+
The active connection to the database
+
+
+ +Expand source code + +
def connect(self)->sql.Connection:
+    """
+    Connect to the database
+
+    Returns:
+        sql.Connection: The active connection to the database
+    """
+    return sql.connect(self.db_path)
+
+
+
+def createApparat(self, apparat: src.logic.dataclass.ApparatData) ‑> Union[src.errors.DatabaseErrors.AppPresentError, ForwardRef(None), int] +
+
+
+
+ +Expand source code + +
def createApparat(self, apparat:ApparatData)->Optional[AppPresentError]|int:
+    prof_id = self.getProfId(apparat.profname)
+    app_id = self.getApparatId(apparat.appname)
+    if app_id:
+        return AppPresentError(app_id)
+
+    self.createProf(apparat.get_prof_details())
+    prof_id = self.getProfId(apparat.profname)
+    #ic(prof_id)
+    query = f"INSERT OR IGNORE INTO semesterapparat (appnr, name, erstellsemester, dauer, prof_id, fach,deletion_status,konto) VALUES ('{apparat.appnr}', '{apparat.appname}', '{apparat.semester}', '{apparat.dauerapp}', {prof_id}, '{apparat.app_fach}', '{0}', '{SEMAP_MEDIA_ACCOUNTS[apparat.appnr]}')"
+    logger.log_info(query)
+    self.query_db(query)
+    return self.getApparatId(apparat.appname)
+
+
+
+def createProf(self, prof_details: dict) +
+
+
+
+ +Expand source code + +
def createProf(self, prof_details:dict):
+    prof_title = prof_details["prof_title"]
+    prof_fname = prof_details["profname"].split(",")[1]
+    prof_fname = prof_fname.strip()
+    prof_lname = prof_details["profname"].split(",")[0]
+    prof_lname = prof_lname.strip()
+    prof_fullname = prof_details["profname"].replace(",", "")
+    prof_mail = prof_details["prof_mail"]
+    prof_tel = prof_details["prof_tel"]
+    params = (prof_title, prof_fname, prof_lname, prof_mail, prof_tel, prof_fullname)
+    query = "INSERT OR IGNORE INTO prof (titel, fname, lname, mail, telnr, fullname) VALUES (?, ?, ?, ?, ?, ?)"
+    self.insertInto(query=query, params=params)
+
+
+
+def createUser(self, user, password, role, salt) +
+
+

Create a user based on passed data.

+

Args:

+
- username (str): Username to be used
+- password (str): the salted password
+- role (str): Role of the user
+- salt (str): a random salt for the user
+
+
+ +Expand source code + +
def createUser(self, user, password, role, salt):
+    """Create a user based on passed data.
+
+    Args:
+    ----
+        - username (str): Username to be used
+        - password (str): the salted password
+        - role (str): Role of the user
+        - salt (str): a random salt for the user
+    """
+    self.query_db("INSERT OR IGNORE INTO user (username, password, role, salt) VALUES (?,?,?,?)", (user,password,role,salt))
+
+
+
+def create_tables(self) +
+
+

Create the tables in the database

+
+ +Expand source code + +
def create_tables(self):
+    """
+    Create the tables in the database
+    """
+    conn = self.connect()
+    cursor = conn.cursor()
+    cursor.execute(CREATE_TABLE_APPARAT)
+    cursor.execute(CREATE_TABLE_MESSAGES)
+    cursor.execute(CREATE_TABLE_MEDIA)
+    cursor.execute(CREATE_TABLE_APPKONTOS)
+    cursor.execute(CREATE_TABLE_FILES)
+    cursor.execute(CREATE_TABLE_PROF)
+    cursor.execute(CREATE_TABLE_USER)
+    cursor.execute(CREATE_TABLE_SUBJECTS)
+    conn.commit()
+    self.close_connection(conn)
+
+
+
+def deleteApparat(self, appnr, semester) +
+
+
+
+ +Expand source code + +
def deleteApparat(self, appnr, semester):
+    self.query_db("UPDATE semesterapparat SET deletion_status=1, deleted_date=? WHERE appnr=?", (semester,appnr)) 
+
+
+
+def deleteBook(self, book_id) +
+
+

Delete a book from the database

+

Args

+
+
book_id : str
+
ID of the book
+
+
+ +Expand source code + +
def deleteBook(self, book_id):
+    """
+    Delete a book from the database
+
+    Args:
+        book_id (str): ID of the book
+    """
+    self.query_db("UPDATE media SET deleted=1 WHERE id=?", (book_id,))
+
+
+
+def deleteMessage(self, message_id) +
+
+
+
+ +Expand source code + +
def deleteMessage(self, message_id):
+    self.query_db("DELETE FROM messages WHERE id=?", (message_id,))
+
+
+
+def deleteUser(self, user) +
+
+
+
+ +Expand source code + +
def deleteUser(self, user):
+    self.query_db("DELETE FROM user WHERE username=?", (user,))
+
+
+
+def getAllAparats(self, deleted=0) +
+
+
+
+ +Expand source code + +
def getAllAparats(self,deleted=0):
+    return self.query_db("SELECT * FROM semesterapparat WHERE deletion_status=?", (deleted,))
+
+
+
+def getApparatCountBySemester(self) ‑> tuple[list[str], list[int]] +
+
+
+
+ +Expand source code + +
def getApparatCountBySemester(self)->tuple[list[str],list[int]]:
+    conn = self.connect()
+    cursor = conn.cursor()
+    semesters = self.getSemersters()
+    created = []
+    deleted = []
+    for semester in semesters:
+        query = f"SELECT COUNT(*) FROM semesterapparat WHERE erstellsemester='{semester}'"
+        result = cursor.execute(query).fetchone()
+        created.append(result[0])
+        query = f"SELECT COUNT(*) FROM semesterapparat WHERE deletion_status=1 AND deleted_date='{semester}'"
+        result = cursor.execute(query).fetchone()
+        deleted.append(result[0])
+    # store data in a tuple
+    ret = []
+    e_tuple = ()
+    for sem in semesters:
+        e_tuple = (
+            sem,
+            created[semesters.index(sem)],
+            deleted[semesters.index(sem)],
+        )
+        ret.append(e_tuple)
+    self.close_connection(conn)
+    return ret 
+
+
+
+def getApparatData(self, appnr, appname) ‑> src.logic.dataclass.ApparatData +
+
+
+
+ +Expand source code + +
def getApparatData(self, appnr, appname)->ApparatData:
+
+    result = self.query_db("SELECT * FROM semesterapparat WHERE appnr=? AND name=?", (appnr,appname), one=True)
+    if result is None:
+        raise NoResultError("No result found")
+    apparat = ApparatData()
+    apparat.appname = result[1]
+    apparat.appnr = result[4]
+    apparat.dauerapp = True if result[7] == 1 else False
+    prof_data = self.getProfData(self.getProfNameById(result[2]))
+    apparat.profname = self.getProfNameById(result[2])
+    apparat.prof_mail = prof_data[0]
+    apparat.prof_tel = prof_data[1]
+    apparat.prof_title = prof_data[2]
+    apparat.app_fach = result[3]
+    apparat.erstellsemester = result[5]
+    apparat.semester = result[8]
+    apparat.deleted = result[9]
+    apparat.apparat_adis_id = result[11]
+    apparat.prof_adis_id = result[12]
+    return apparat
+
+
+
+def getApparatId(self, apparat_name) +
+
+
+
+ +Expand source code + +
def getApparatId(self, apparat_name):
+    data = self.query_db("SELECT appnr FROM semesterapparat WHERE name=?", (apparat_name,), one=True)
+    if data is None:
+        return None
+    else:
+        return data[0]
+
+
+
+def getApparatName(self, app_id, prof_id) +
+
+
+
+ +Expand source code + +
def getApparatName(self, app_id, prof_id):
+    return self.query_db("SELECT name FROM semesterapparat WHERE appnr=? AND prof_id=?", (app_id,prof_id), one=True)[0]
+
+
+
+def getApparatsByProf(self, prof_id: int) ‑> list[tuple] +
+
+
+
+ +Expand source code + +
def getApparatsByProf(self, prof_id:int)->list[tuple]:
+    return self.query_db("SELECT * FROM semesterapparat WHERE prof_id=?", (prof_id,))
+
+
+
+def getApparatsBySemester(self, semester: str) ‑> dict +
+
+
+
+ +Expand source code + +
def getApparatsBySemester(self, semester:str)->dict:
+    data = self.query_db("SELECT name, prof_id FROM semesterapparat WHERE erstellsemester=?", (semester,))
+    conn = self.connect()
+    cursor = conn.cursor()
+    c_tmp = []
+    for i in data:
+        c_tmp.append((i[0], self.getProfNameById(i[1])))
+    query = (
+        f"SELECT name,prof_id FROM semesterapparat WHERE deleted_date='{semester}'"
+    )
+    result = cursor.execute(query).fetchall()
+    d_tmp = []
+    for i in result:
+        d_tmp.append((i[0], self.getProfNameById(i[1])))
+    # group the apparats by prof
+    c_ret = {}
+    for i in c_tmp:
+        if i[1] not in c_ret.keys():
+            c_ret[i[1]] = [i[0]]
+        else:
+            c_ret[i[1]].append(i[0])
+    d_ret = {}
+    for i in d_tmp:
+        if i[1] not in d_ret.keys():
+            d_ret[i[1]] = [i[0]]
+        else:
+            d_ret[i[1]].append(i[0])
+    self.close_connection(conn)
+    return {"created": c_ret, "deleted": d_ret}
+
+
+
+def getBlob(self, filename, app_id) +
+
+

Get a blob from the database

+

Args

+
+
filename : str
+
The name of the file
+
app_id : str
+
ID of the apparat
+
+

Returns

+
+
bytes
+
The file stored in
+
+
+ +Expand source code + +
def getBlob(self, filename, app_id):
+    """
+    Get a blob from the database
+
+    Args:
+        filename (str): The name of the file
+        app_id (str): ID of the apparat
+
+    Returns:
+        bytes: The file stored in 
+    """
+    return self.query_db("SELECT fileblob FROM files WHERE filename=? AND app_id=?", (filename,app_id), one=True)[0]
+
+
+
+def getBook(self, book_id: int) ‑> src.logic.dataclass.BookData +
+
+

Get the book based on the id in the database

+

Args

+
+
book_id : int
+
The id of the book
+
+

Returns

+
+
BookData
+
The metadata of the book wrapped in a BookData object
+
+
+ +Expand source code + +
def getBook(self,book_id:int)->BookData:
+    """
+    Get the book based on the id in the database
+
+    Args:
+        book_id (int): The id of the book
+
+    Returns:
+        BookData: The metadata of the book wrapped in a BookData object
+    """
+    return load_pickle(self.query_db("SELECT bookdata FROM media WHERE id=?", (book_id,), one=True)[0])
+
+
+
+def getBookBasedOnSignature(self, app_id: str, prof_id: str, signature: str) ‑> src.logic.dataclass.BookData +
+
+

Get the book based on the signature of the book.

+

Args

+
+
app_id : str
+
The apparat id the book should be associated with
+
prof_id : str
+
The professor id the book should be associated with
+
signature : str
+
The signature of the book
+
+

Returns

+
+
BookData
+
The total metadata of the book wrapped in a BookData object
+
+
+ +Expand source code + +
def getBookBasedOnSignature(self, app_id:str, prof_id:str,signature:str)->BookData:
+    """
+    Get the book based on the signature of the book.
+
+    Args:
+        app_id (str): The apparat id the book should be associated with
+        prof_id (str): The professor id the book should be associated with
+        signature (str): The signature of the book
+
+    Returns:
+        BookData: The total metadata of the book wrapped in a BookData object
+    """
+    result = self.query_db("SELECT bookdata FROM media WHERE app_id=? AND prof_id=?", (app_id,prof_id))
+    books = [load_pickle(i[0]) for i in result]
+    book = [i for i in books if i.signature == signature][0]
+    return book
+
+
+
+def getBookId(self, bookdata: src.logic.dataclass.BookData, app_id, prof_id) ‑> int +
+
+

Get the id of a book based on the metadata of the book

+

Args

+
+
bookdata : BookData
+
The wrapped metadata of the book
+
app_id : str
+
The apparat id the book should be associated with
+
prof_id : str
+
The professor id the book should be associated with
+
+

Returns

+
+
int
+
ID of the book
+
+
+ +Expand source code + +
def getBookId(self, bookdata:BookData, app_id, prof_id)->int:
+    """
+    Get the id of a book based on the metadata of the book
+
+    Args:
+        bookdata (BookData): The wrapped metadata of the book
+        app_id (str): The apparat id the book should be associated with
+        prof_id (str): The professor id the book should be associated with
+
+    Returns:
+        int: ID of the book
+    """
+    result = self.query_db("SELECT id FROM media WHERE bookdata=? AND app_id=? AND prof_id=?", (dump_pickle(bookdata),app_id,prof_id), one=True)
+    return result[0]
+
+
+
+def getBookIdBasedOnSignature(self, app_id: str, prof_id: str, signature: str) ‑> int +
+
+

Get a book id based on the signature of the book.

+

Args

+
+
app_id : str
+
The apparat id the book should be associated with
+
prof_id : str
+
The professor id the book should be associated with
+
signature : str
+
The signature of the book
+
+

Returns

+
+
int
+
The id of the book
+
+
+ +Expand source code + +
def getBookIdBasedOnSignature(self, app_id:str, prof_id:str,signature:str)->int:
+    """
+    Get a book id based on the signature of the book.
+
+    Args:
+        app_id (str): The apparat id the book should be associated with
+        prof_id (str): The professor id the book should be associated with
+        signature (str): The signature of the book
+
+    Returns:
+        int: The id of the book
+    """
+    result = self.query_db("SELECT bookdata, id FROM media WHERE app_id=? AND prof_id=?", (app_id,prof_id))
+    books = [(load_pickle(i[0]),i[1]) for i in result]
+    book = [i for i in books if i[0].signature == signature][0][1]
+    return book
+
+
+
+def getBooks(self, app_id, prof_id, deleted=0) ‑> list[dict[int, src.logic.dataclass.BookData, int]] +
+
+

Get the Books based on the apparat id and the professor id

+

Args

+
+
app_id : str
+
The ID of the apparat
+
prof_id : str
+
The ID of the professor
+
deleted : int, optional
+
The state of the book. Set to 1 to include deleted ones. Defaults to 0.
+
+

Returns

+
+
list[dict[int, BookData, int]]
+
A list of dictionaries containing the id, the metadata of the book and the availability of the book
+
+
+ +Expand source code + +
def getBooks(self, app_id, prof_id, deleted=0)->list[dict[int, BookData, int]]:
+    """
+    Get the Books based on the apparat id and the professor id
+
+    Args:
+        app_id (str): The ID of the apparat
+        prof_id (str): The ID of the professor
+        deleted (int, optional): The state of the book. Set to 1 to include deleted ones. Defaults to 0.
+
+    Returns:
+        list[dict[int, BookData, int]]: A list of dictionaries containing the id, the metadata of the book and the availability of the book
+    """
+    qdata = self.query_db(f"SELECT id,bookdata,available FROM media WHERE (app_id={app_id} AND prof_id={prof_id}) AND (deleted={deleted if deleted == 0 else '1 OR deleted=0'})")
+    ret_result = []
+    for result_a in qdata:
+        data = {"id": int, "bookdata": BookData, "available": int}
+        data["id"] = result_a[0]
+        data["bookdata"] = load_pickle(result_a[1])
+        data["available"] = result_a[2]
+        ret_result.append(data)
+    return ret_result
+
+
+
+def getFacultyMember(self, name: str) +
+
+
+
+ +Expand source code + +
def getFacultyMember(self, name:str):
+    return self.query_db("SELECT titel, fname,lname,mail,telnr,fullname FROM prof WHERE fullname=?", (name,), one=True)
+
+
+
+def getFacultyMembers(self) +
+
+
+
+ +Expand source code + +
def getFacultyMembers(self):
+    return self.query_db("SELECT titel, fname,lname,mail,telnr,fullname FROM prof")
+
+
+
+def getFiles(self, app_id: int, prof_id: int) ‑> list[tuple] +
+
+
+
+ +Expand source code + +
def getFiles(self, app_id:int, prof_id:int)->list[tuple]:
+    return self.query_db("SELECT filename, filetyp FROM files WHERE app_id=? AND prof_id=?", (app_id,prof_id))
+
+
+
+def getLastBookId(self) ‑> int +
+
+

Get the last book id in the database

+

Returns

+
+
int
+
ID of the last book in the database
+
+
+ +Expand source code + +
def getLastBookId(self)->int:
+    """
+    Get the last book id in the database
+
+    Returns:
+        int: ID of the last book in the database
+    """
+    return self.query_db("SELECT id FROM media ORDER BY id DESC", one=True)[0]
+
+
+
+def getMessages(self, date: str) +
+
+
+
+ +Expand source code + +
def getMessages(self, date:str):
+    def __get_user_name(user_id):
+        return self.query_db("SELECT username FROM user WHERE id=?", (user_id,), one=True)[0]
+    messages = self.query_db("SELECT * FROM messages WHERE remind_at=?", (date,))
+    ret = [
+        {
+            "message": i[2],
+            "user": __get_user_name(i[4]),
+            "appnr": i[5],
+            "id": i[0]
+        }
+        for i in messages
+    ]
+    return ret
+
+
+
+def getProfByName(self, prof_name: str) +
+
+
+
+ +Expand source code + +
def getProfByName(self, prof_name:str):
+    return self.query_db("SELECT * FROM prof WHERE fullname=?", (prof_name,), one=True)
+
+
+
+def getProfData(self, profname: str) +
+
+
+
+ +Expand source code + +
def getProfData(self, profname:str):
+    
+    data = self.query_db("SELECT mail, telnr, titel FROM prof WHERE fullname=?", (profname.replace(",",""),), one=True)
+    return data
+
+
+
+def getProfId(self, prof_name: str) +
+
+

getProfId summary

+

:param prof_name: description +:type prof_name: str +:return: description +:rtype: type

+
+ +Expand source code + +
def getProfId(self, prof_name:str):
+    """
+    getProfId _summary_
+
+    :param prof_name: _description_
+    :type prof_name: str
+    :return: _description_
+    :rtype: _type_
+    """
+    data = self.getProfByName(prof_name.replace(",", ""))
+    if data is None:
+        return None
+    else:
+        return data[0]
+
+
+
+def getProfNameById(self, prof_id: int, add_title: bool = False) +
+
+
+
+ +Expand source code + +
def getProfNameById(self, prof_id:int,add_title:bool=False):
+    prof = self.query_db("SELECT fullname FROM prof WHERE id=?", (prof_id,), one=True)
+    if add_title:
+        return f"{self.getTitleById(prof_id)}{prof[0]}"
+    else:
+        return prof[0]
+
+
+
+def getProfs(self) +
+
+
+
+ +Expand source code + +
def getProfs(self):
+    return self.query_db("SELECT * FROM prof")
+
+
+
+def getRole(self, user) +
+
+
+
+ +Expand source code + +
def getRole(self, user):
+    return self.query_db("SELECT role FROM user WHERE username=?", (user,), one=True)[0]
+
+
+
+def getRoles(self) +
+
+
+
+ +Expand source code + +
def getRoles(self):
+    return self.query_db("SELECT role FROM user")
+
+
+
+def getSemersters(self) +
+
+
+
+ +Expand source code + +
def getSemersters(self):
+    data = self.query_db("SELECT DISTINCT erstellsemester FROM semesterapparat")
+    return [i[0] for i in data]
+
+
+
+def getSpecificProfData(self, prof_id: int, fields: List[str]) +
+
+

getSpecificProfData summary

+

Args:

+
- prof_id (int): _description_
+- fields (List[str]): _description_
+
+

Returns:

+
- _type_: _description_
+
+
+ +Expand source code + +
def getSpecificProfData(self, prof_id:int, fields:List[str]):
+    """
+    getSpecificProfData _summary_
+    
+    
+    
+    Args:
+    ----
+        - prof_id (int): _description_
+        - fields (List[str]): _description_
+    
+    Returns:
+    -------
+        - _type_: _description_
+    """
+    query = "SELECT "
+    for field in fields:
+        query += f"{field},"
+    query = query[:-1]
+    query += " FROM prof WHERE id=?"
+    return self.query_db(query, (prof_id,), one=True)[0]
+
+
+
+def getSubjects(self) +
+
+
+
+ +Expand source code + +
def getSubjects(self):
+    return self.query_db("SELECT * FROM subjects")
+
+
+
+def getTitleById(self, prof_id: int) +
+
+
+
+ +Expand source code + +
def getTitleById(self, prof_id:int):
+    title = self.query_db("SELECT titel FROM prof WHERE id=?", (prof_id,), one=True)[0]
+    return f"{title} " if title is not None else ""
+
+
+
+def getUnavailableApparatNumbers(self) ‑> List[int] +
+
+

getUnavailableApparatNumbers returns a list of all currently used ApparatNumbers

+

Returns:

+
- number(List[int]): a list of all currently used apparat numbers
+
+
+ +Expand source code + +
def getUnavailableApparatNumbers(self)->List[int]:
+    """
+    getUnavailableApparatNumbers returns a list of all currently used ApparatNumbers
+    
+    
+    
+    Returns:
+    -------
+        - number(List[int]): a list of all currently used apparat numbers
+    """
+    numbers = self.query_db("SELECT appnr FROM semesterapparat WHERE deletion_status=0")    
+    numbers = [i[0] for i in numbers]
+    logger.log_info(f"Currently used apparat numbers: {numbers}")
+    return numbers
+
+
+
+def getUser(self) +
+
+
+
+ +Expand source code + +
def getUser(self):
+    return self.query_db("SELECT * FROM user", one=True)
+
+
+
+def getUsers(self) +
+
+
+
+ +Expand source code + +
def getUsers(self):
+    return self.query_db("SELECT * FROM user")
+
+
+
+def get_db_contents(self) ‑> Optional[List[Tuple]] +
+
+

Get the contents of the

+

Returns

+
+
Union[List[Tuple], None]
+
description
+
+
+ +Expand source code + +
def get_db_contents(self)->Union[List[Tuple], None]:
+    """
+    Get the contents of the 
+
+    Returns:
+        Union[List[Tuple], None]: _description_
+    """
+    try:
+        with sql.connect(self.db_path) as conn:
+            cursor = conn.cursor()
+            cursor.execute("SELECT * FROM sqlite_master WHERE type='table'")
+            return cursor.fetchall()
+    except sql.OperationalError:
+        return None
+
+
+
+def insertFile(self, file: list[dict], app_id: int, prof_id) +
+
+
+
+ +Expand source code + +
def insertFile(self, file: list[dict], app_id: int, prof_id):
+    for f in file:
+        filename = f["name"]
+        path = f["path"]
+        filetyp = f["type"]
+        if path == "Database":
+            continue
+        blob = create_blob(path)
+        query = "INSERT OR IGNORE INTO files (filename, fileblob, app_id, filetyp,prof_id) VALUES (?, ?, ?, ?,?)"
+        self.query_db(query, (filename, blob, app_id, filetyp,prof_id))
+
+
+
+def insertInto(self, query: str, params: Tuple) ‑> None +
+
+

Insert sent data into the database

+

Args

+
+
query : str
+
The query to be executed
+
params : Tuple
+
the parameters to be inserted into the database
+
+
+ +Expand source code + +
def insertInto(self, query:str, params:Tuple) -> None:
+    """
+    Insert sent data into the database
+
+    Args:
+        query (str): The query to be executed
+        params (Tuple): the parameters to be inserted into the database
+    """
+    conn = self.connect()
+    cursor = conn.cursor()
+    logger.log_info(f"Inserting {params} into database with query {query}")
+    cursor.execute(query, params)
+    conn.commit()
+    self.close_connection(conn)
+
+
+
+def isEternal(self, id) +
+
+
+
+ +Expand source code + +
def isEternal(self, id):
+    return self.query_db("SELECT dauer FROM semesterapparat WHERE appnr=?", (id,), one=True)
+
+
+
+def login(self, user, hashed_password) +
+
+
+
+ +Expand source code + +
def login(self, user, hashed_password):
+    salt = self.query_db("SELECT salt FROM user WHERE username=?", (user,), one=True)[0]
+    if salt is None:
+        return False
+    hashed_password = salt + hashed_password
+    password = self.query_db("SELECT password FROM user WHERE username=?", (user,), one=True)[0]
+    if password == hashed_password:
+        return True
+    else:
+        return False
+
+
+
+def query_db(self, query: str, args: Tuple = (), one: bool = False) ‑> Union[Tuple, List[Tuple]] +
+
+

Query the Database for the sent query.

+

Args

+
+
query : str
+
The query to be executed
+
args : Tuple, optional
+
The arguments for the query. Defaults to ().
+
one : bool, optional
+
Return the first result only. Defaults to False.
+
+

Returns

+

Union[Typle|List[Tuple]]: Returns the result of the query

+
+ +Expand source code + +
def query_db(self, query: str, args: Tuple = (), one: bool = False)->Union[Tuple, List[Tuple]]:
+    """
+    Query the Database for the sent query.
+
+    Args:
+        query (str): The query to be executed
+        args (Tuple, optional): The arguments for the query. Defaults to ().
+        one (bool, optional): Return the first result only. Defaults to False.
+
+    Returns:
+        Union[Typle|List[Tuple]]: Returns the result of the query
+    """
+    conn = self.connect()
+    cursor = conn.cursor()
+    logger.log_info(f"Querying database with query {query}, args: {args}")
+    cursor.execute(query, args)
+    rv = cursor.fetchall()
+    conn.commit()
+    self.close_connection(conn)
+    return (rv[0] if rv else None) if one else rv
+
+
+
+def recreateFile(self, filename, app_id, filetype) +
+
+
+
+ +Expand source code + +
def recreateFile(self, filename, app_id,filetype):
+    blob = self.getBlob(filename, app_id)
+    tempdir = config.database.tempdir
+    tempdir = tempdir.replace("~", str(Path.home()))
+    tempdir_path = Path(tempdir)        
+    if not os.path.exists(tempdir_path):
+        os.mkdir(tempdir_path)
+    file = tempfile.NamedTemporaryFile(
+        delete=False, dir=tempdir_path, mode="wb", suffix=f".{filetype}"
+    )
+    file.write(blob)
+    print("file created")
+    return file.name
+
+
+
+def searchBook(self, data: dict[str, str]) ‑> list[tuple[src.logic.dataclass.BookData, int]] +
+
+

Search a book in the database based on the sent data.

+

Args

+
+
data : dict[str, str]
+
A dictionary containing the data to be searched for. The dictionary can contain the following:
+
+
    +
  • signature: The signature of the book
  • +
  • title: The title of the book
  • +
+

Returns

+
+
list[tuple[BookData, int]]
+
A list of tuples containing the wrapped Metadata and the id of the book
+
+
+ +Expand source code + +
def searchBook(self, data:dict[str, str])->list[tuple[BookData, int]]:
+    """
+    Search a book in the database based on the sent data.
+
+    Args:
+        data (dict[str, str]): A dictionary containing the data to be searched for. The dictionary can contain the following: 
+        - signature: The signature of the book
+        - title: The title of the book
+
+    Returns:
+        list[tuple[BookData, int]]: A list of tuples containing the wrapped Metadata and the id of the book
+    """
+    rdata = self.query_db("SELECT * FROM media WHERE deleted=0")
+    #ic(rdata, len(rdata))
+    mode = 0
+    if len(data)== 1:
+        if "signature" in data.keys():
+            mode = 1
+        elif "title" in data.keys():
+            mode = 2
+    elif len(data) == 2:
+        mode = 3
+    else:
+        return None
+    ret = []
+    for book in rdata:
+        bookdata = load_pickle(book[1])
+        app_id = book[2]
+        prof_id = book[3]
+        if mode == 1:
+            if data["signature"] in bookdata.signature:
+                ret.append((bookdata,app_id,prof_id))
+        elif mode == 2:
+            if data["title"] in bookdata.title:
+                ret.append((bookdata,app_id,prof_id))
+        elif mode == 3:
+            if data["signature"] in bookdata.signature and data["title"] in bookdata.title:
+                ret.append((bookdata,app_id,prof_id))
+    #ic(ret)
+    return ret
+
+
+
+def setAvailability(self, book_id: str, available: str) +
+
+

Set the availability of a book in the database

+

Args

+
+
book_id : str
+
The id of the book
+
available : str
+
The availability of the book
+
+
+ +Expand source code + +
def setAvailability(self, book_id:str, available:str):
+    """
+    Set the availability of a book in the database
+
+    Args:
+        book_id (str): The id of the book
+        available (str): The availability of the book
+    """
+    self.query_db("UPDATE media SET available=? WHERE id=?", (available,book_id))
+
+
+
+def setNewSemesterDate(self, appnr, newDate, dauerapp=False) +
+
+
+
+ +Expand source code + +
def setNewSemesterDate(self, appnr, newDate, dauerapp=False):
+    date = datetime.datetime.strptime(newDate, "%d.%m.%Y").strftime("%Y-%m-%d")
+    if dauerapp:
+        self.query_db("UPDATE semesterapparat SET verlängerung_bis=?, dauerapp=? WHERE appnr=?", (date,dauerapp,appnr))
+    else:
+        self.query_db("UPDATE semesterapparat SET endsemester=? WHERE appnr=?", (date,appnr))
+
+
+
+def statistic_request(self, **kwargs: Any) +
+
+
+
+ +Expand source code + +
def statistic_request(self, **kwargs: Any):
+    def __query(query):
+        conn = self.connect()
+        cursor = conn.cursor()
+        result = cursor.execute(query).fetchall()
+        for result_a in result:
+            orig_value = result_a
+            prof_name = self.getProfNameById(result_a[2])
+            # replace the prof_id with the prof_name
+            result_a = list(result_a)
+            result_a[2] = prof_name
+            result_a = tuple(result_a)
+            result[result.index(orig_value)] = result_a
+        self.close_connection(conn)
+        return result
+    if "deletable" in kwargs.keys():
+        query = f"SELECT * FROM semesterapparat WHERE deletion_status=0 AND dauer=0 AND (erstellsemester!='{kwargs['deletesemester']}' OR verlängerung_bis!='{kwargs['deletesemester']}')"
+        return __query(query)
+    if "dauer" in kwargs.keys():
+        kwargs["dauer"] = kwargs["dauer"].replace("Ja", "1").replace("Nein", "0")
+    query = "SELECT * FROM semesterapparat WHERE "
+    for key, value in kwargs.items() if kwargs.items() is not None else {}:
+        print(key, value)
+        query += f"{key}='{value}' AND "
+        print(query)
+    # remove deletesemester part from normal query, as this will be added to the database upon deleting the apparat
+    if "deletesemester" in kwargs.keys():
+        query = query.replace(
+            f"deletesemester='{kwargs['deletesemester']}' AND ", ""
+        )
+    if "endsemester" in kwargs.keys():
+        if "erstellsemester" in kwargs.keys():
+            query = query.replace(f"endsemester='{kwargs['endsemester']}' AND ", "")
+            query = query.replace(
+                f"erstellsemester='{kwargs['erstellsemester']} AND ", "xyz"
+            )
+        else:
+            query = query.replace(
+                f"endsemester='{kwargs['endsemester']}' AND ", "xyz"
+            )
+            print("replaced")
+        query = query.replace(
+            "xyz",
+            f"(erstellsemester='{kwargs['endsemester']}' OR verlängerung_bis='{kwargs['endsemester']}') AND ",
+        )
+    # remove all x="" parts from the query where x is a key in kwargs
+    query = query[:-5]
+    print(query)
+    return __query(query)
+
+
+
+def updateApparat(self, apparat_data: src.logic.dataclass.ApparatData) +
+
+
+
+ +Expand source code + +
def updateApparat(self, apparat_data:ApparatData):
+    query = f"UPDATE semesterapparat SET name = ?, fach = ?, dauer = ?, prof_id = ? WHERE appnr = ?"
+    params = (
+        apparat_data.appname,
+        apparat_data.app_fach,
+        apparat_data.dauerapp,
+        self.getProfId(apparat_data.profname),
+        apparat_data.appnr,        
+    )
+    self.query_db(query, params)
+
+
+
+def updateBookdata(self, book_id, bookdata: src.logic.dataclass.BookData) +
+
+

Update the bookdata in the database

+

Args

+
+
book_id : str
+
The id of the book
+
bookdata : BookData
+
The new metadata of the book
+
+
+ +Expand source code + +
def updateBookdata(self, book_id, bookdata:BookData):
+    """
+    Update the bookdata in the database
+
+    Args:
+        book_id (str): The id of the book
+        bookdata (BookData): The new metadata of the book
+    """
+    self.query_db("UPDATE media SET bookdata=? WHERE id=?", (dump_pickle(bookdata),book_id))
+
+
+
+def updateFacultyMember(self, data, oldlname, oldfname) +
+
+
+
+ +Expand source code + +
def updateFacultyMember(self, data, oldlname, oldfname):
+    placeholders = ", ".join([f"{i}=:{i} " for i in data.keys()])
+    query = f"UPDATE prof SET {placeholders} WHERE lname = :oldlname AND fname = :oldfname"
+    data["oldlname"] = oldlname
+    data["oldfname"] = oldfname
+    self.query_db(query, data)
+
+
+
+def updateUser(self, username, data: dict[str, str]) +
+
+
+
+ +Expand source code + +
def updateUser(self, username, data:dict[str, str]):
+    conn = self.connect()
+    cursor = conn.cursor()
+    query = "UPDATE user SET "
+    for key,value in data.items():
+        if key == "username":
+            continue
+        query += f"{key}='{value}',"
+    query = query[:-1]
+    query += " WHERE username=?"
+    params = (username,)
+    cursor.execute(query, params)
+    conn.commit()
+    self.close_connection(conn)
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/mail.py b/mail.py new file mode 100644 index 0000000..def3d24 --- /dev/null +++ b/mail.py @@ -0,0 +1,34 @@ +import sys + +from PyQt6 import QtWidgets + +from src.ui.dialogs.mail import Mail_Dialog + + +def launch_gui(app_id="", app_name="", app_subject="", prof_name="", prof_mail=""): + QtWidgets.QApplication([""]) + + dialog = Mail_Dialog( + app_id=app_id, + app_name=app_name, + app_subject=app_subject, + prof_name=prof_name, + prof_mail=prof_mail, + # default_mail="Information bezüglich der Auflösung des Semesterapparates", + ) + dialog.exec() + + +if __name__ == "__main__": + app_id = "123" + app_name = "Test" + app_subject = "TestFach" + prof_name = "Test" + prof_mail = "kirchneralexander020@gmail.com" + launch_gui( + app_id=app_id, + app_name=app_name, + app_subject=app_subject, + prof_name=prof_name, + prof_mail=prof_mail, + ) diff --git a/mail_vorlagen/Information bezüglich der Auflösung des Semesterapparates {AppNr} - Alexander Kirchner (alexander.kirchner@ph-freiburg.de).eml b/mail_vorlagen/Information bezüglich der Auflösung des Semesterapparates {AppNr} - Alexander Kirchner (alexander.kirchner@ph-freiburg.de).eml deleted file mode 100644 index 9bc91f4..0000000 --- a/mail_vorlagen/Information bezüglich der Auflösung des Semesterapparates {AppNr} - Alexander Kirchner (alexander.kirchner@ph-freiburg.de).eml +++ /dev/null @@ -1,54 +0,0 @@ -Message-ID: <987b46cf-2d8b-4a27-acb3-c50f61d3d85d@ph-freiburg.de> -Date: Tue, 31 Oct 2023 11:38:34 +0100 -MIME-Version: 1.0 -User-Agent: Mozilla Thunderbird -From: Alexander Kirchner -Subject: =?UTF-8?Q?Information_bez=C3=BCglich_der_Aufl=C3=B6sung_des_Semeste?= - =?UTF-8?Q?rapparates_=7BAppNr=7D?= -Content-Language: de-DE -X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; DSN=0; uuencode=0; - attachmentreminder=0; deliveryformat=0 -X-Identity-Key: id1 -Fcc: imap://aky547@imap.ph-freiburg.de/INBOX/Sent -Content-Type: text/html; charset=UTF-8 -Content-Transfer-Encoding: 8bit - - - - - - - -

Sehr geehrte/r {Profname},
-

-


-

- auf die E-Mail bezüglich der Auflösung oder Verlängerung der - Semesterapparate haben wir von Ihnen keine Rückmeldung erhalten. - Deshalb gehen wir davon aus, dass der Apparat aufgelöst werden kann. - Die Medien, die im Apparat aufgestellt waren, werden nun wieder - regulär ausleihbar und sind dann an ihren Standorten bei den Fächern - zu finden.
-
- Falls Sie den Apparat erneut, oder einen neuen Apparat anlegen - wollen, können Sie mir das ausgefüllte Formular zur Einrichtung des - Apparates (https://www.ph-freiburg.de/bibliothek/lernen/semesterapparate/info-lehrende-sem.html) - zukommen lassen. Im Falle einer Verlängerung des Apparates reicht - eine Antwort auf diese Mail. -


-

-

Bei Fragen können Sie sich jederzeit an mich wenden.
-

-


-

-
-- 
-Freundliche Grüße
-
-Alexander Kirchner
-
-
-Bibliothek der Pädagogischen Hochschule Freiburg
-Tel. 0761/682-778
- - diff --git a/mail_vorlagen/Information bezüglich der Auflösung des Semesterapparates {AppNr}.eml b/mail_vorlagen/Information bezüglich der Auflösung des Semesterapparates {AppNr}.eml index f415583..b4c6455 100644 --- a/mail_vorlagen/Information bezüglich der Auflösung des Semesterapparates {AppNr}.eml +++ b/mail_vorlagen/Information bezüglich der Auflösung des Semesterapparates {AppNr}.eml @@ -20,7 +20,7 @@ Content-Transfer-Encoding: 8bit -

Sehr geehrte/r Herr/Frau {Profname},
+

{greeting}


@@ -51,4 +51,4 @@ Alexander Kirchner Bibliothek der Pädagogischen Hochschule Freiburg Tel. 0761/682-778 - + \ No newline at end of file diff --git a/mail_vorlagen/Information zum Semesterapparat {AppNr} - {AppName}.eml b/mail_vorlagen/Information zum Semesterapparat {AppNr} - {AppName}.eml index d4d2232..b534e9b 100644 --- a/mail_vorlagen/Information zum Semesterapparat {AppNr} - {AppName}.eml +++ b/mail_vorlagen/Information zum Semesterapparat {AppNr} - {AppName}.eml @@ -18,7 +18,7 @@ Content-Transfer-Encoding: 8bit -

{greeting} {Profname},
+

{greeting}


diff --git a/open_file_test.py b/open_file_test.py new file mode 100644 index 0000000..3792acf --- /dev/null +++ b/open_file_test.py @@ -0,0 +1,57 @@ +import os +from pathlib import Path + +from omegaconf import OmegaConf + +from src.backend.database import Database + +db = Database() +config = OmegaConf.load("config.yaml") + + +def main(): + _selected_doc_name = "sap71.csv" + _selected_doc_location = "Database" + _selected_doc_filetype = "csv" + print(_selected_doc_name, _selected_doc_filetype) + if not _selected_doc_location == "Database": + path = Path(_selected_doc_location) + path: Path = path + "/" + _selected_doc_name + if os.getenv("OS") == "Windows_NT": + path = path.resolve() + os.startfile(path) + else: + path = path.resolve() + os.system(f"open {path}") + else: + try: + _selected_doc_name = db.recreateFile( + _selected_doc_name, "71", filetype=_selected_doc_filetype + ) + except Exception as e: + print(e) + path = config.database.tempdir + _selected_doc_name + # if ~ in path, replace it with the home directory + if "~" in path: + path = path.replace("~", str(Path.home())) + path = Path(path) + if os.getenv("OS") == "Windows_NT": + path = path.resolve() + os.startfile(path) + else: + path = path.resolve() + os.system(f"open {path}") + # filebytes = self.db.get_blob( + # _selected_doc_name, + # self.active_apparat(), + # ) + # # use io.BytesIO to create a file-like object from the bytes and open it in the respective program + # file = io.BytesIO(filebytes) + # file.name = _selected_doc_name + + # QtGui.QDesktopServices.openUrl(QtCore.QUrl(file)) + # print(type(filebytes)) + + +if __name__ == "__main__": + main() diff --git a/resources.qrc b/resources.qrc new file mode 100644 index 0000000..7be4647 --- /dev/null +++ b/resources.qrc @@ -0,0 +1,16 @@ + + + resources/logo_SAP.ico + resources/logo_SAP.png + resources/1f4e7.svg + resources/1f510.svg + resources/2795.svg + resources/VZjRNn1k.ico + + + resources/critical.png + resources/information.png + resources/question.png + resources/warning.png + + diff --git a/src/backend/delete_temp_contents.py b/src/backend/delete_temp_contents.py index 583de9c..6aacd95 100644 --- a/src/backend/delete_temp_contents.py +++ b/src/backend/delete_temp_contents.py @@ -1,8 +1,11 @@ import os -from omegaconf import OmegaConf from pathlib import Path + +from omegaconf import OmegaConf + config = OmegaConf.load("config.yaml") + def delete_temp_contents(): """ delete_temp_contents deletes the contents of the temp directory. @@ -16,7 +19,7 @@ def delete_temp_contents(): os.remove(os.path.join(root, file)) for dir in dirs: os.rmdir(os.path.join(root, dir)) - - + + if __name__ == "__main__": - delete_temp_contents() \ No newline at end of file + delete_temp_contents() diff --git a/src/backend/import pyautogui.py b/src/backend/import pyautogui.py new file mode 100644 index 0000000..837caf9 --- /dev/null +++ b/src/backend/import pyautogui.py @@ -0,0 +1,16 @@ +import time + +import pyautogui + + +def main(): + key_count = input("How many keys do you have? ") + key = pyautogui.RIGHT + for _i in range(int(key_count)): + pyautogui.keyDown(key) + pyautogui.keyUp(key) + time.sleep(0.1) + + +if __name__ == "__main__": + main() diff --git a/src/backend/olddatabase.py b/src/backend/olddatabase.py new file mode 100644 index 0000000..fb3a941 --- /dev/null +++ b/src/backend/olddatabase.py @@ -0,0 +1,753 @@ +import datetime +import os + +# from src.data import pickles +import pickle +import re +import shutil +import sqlite3 as sql3 +import tempfile +from typing import Any + +from omegaconf import OmegaConf + +from src.logic.constants import SEMAP_MEDIA_ACCOUNTS +from src.logic.dataclass import ApparatData, BookData +from src.logic.log import MyLogger + +# from icecream import ic +config = OmegaConf.load("config.yaml") + +logger = MyLogger("Database") + + +class Database: + logger.log_info("Database imported") + + def __init__(self) -> None: + # TODO: change path later on to a variable based on the settings + self.database_path = f"{config.database.path}{config.database.name}" + # ic(self.database_path) + self.database_path = "sap.db" + logger.log_info("Connecting to database") + self.database = sql3.connect(self.database_path) + self.cur = self.database.cursor() + + pass + + def create_database(self): + # create database from template + subjects = config.subjects + for subject in subjects: + self.cur.execute(f"INSERT INTO subjects (name) VALUES ('{subject}')") + self.database.commit() + + def create_blob(self, file): + with open(file, "rb") as f: + blob = f.read() + return blob + + def recreate_file(self, filename, app_id: int): + blob = self.get_blob(filename, app_id) + # write the blob to the file and save it to a preset destination + # with open(filename, "wb") as f: + # f.write(blob) + # use tempfile to create a temporary file + if not os.path.exists(config.database.tempdir): + os.mkdir(config.database.tempdir) + tempfile.NamedTemporaryFile( + filename=filename, delete=False, dir=config.database.tempdir, mode="wb" + ).write(blob) + + # user = os.getlogin() + # home = os.path.expanduser("~") + + # # check if the folder exists, if not, create it + # if not os.path.exists(f"{home}/Desktop/SemApp/{user}"): + # os.mkdir(f"{home}/Desktop/SemApp/{user}") + # shutil.move(filename, f"{home}/Desktop/SemApp/{user}") + + def get_blob(self, filename: str, app_id: int): + query = f"SELECT fileblob FROM files WHERE filename='{filename}' AND app_id={app_id}" + logger.log_info(f"Retrieving blob for {filename}, Appid {app_id} from database") + result = self.cur.execute(query).fetchone() + return result[0] + + def get_kto_no(self, app_id: int): + query = f"SELECT konto FROM semesterapparat WHERE id={app_id}" + result = self.cur.execute(query).fetchone() + return result[0] + + def insert_file(self, file: list[dict], app_id: int, prof_id): + for f in file: + filename = f["name"] + path = f["path"] + filetyp = f["type"] + print(f"filename: {filename}, path: {path}, filetyp: {filetyp}") + if path == "Database": + continue + blob = self.create_blob(path) + + query = "INSERT OR IGNORE INTO files (filename, fileblob, app_id, filetyp,prof_id) VALUES (?, ?, ?, ?,?)" + params = (filename, blob, app_id, filetyp, prof_id) + self.cur.execute(query, params) + logger.log_info( + f"Inserted {len(file)} file(s) of Apparat {app_id} into database" + ) + self.database.commit() + + def get_files(self, app_id: int, prof_id: int): + query = f"SELECT filename, filetyp FROM files WHERE app_id={app_id} AND prof_id={prof_id}" + result: list[tuple] = self.cur.execute(query).fetchall() + return result + + def get_prof_name_by_id(self, id, add_title: bool = False): + if add_title is True: + query = f"SELECT titel, fname, lname FROM prof WHERE id={id}" + result = self.cur.execute(query).fetchone() + name = " ".join(result[0:3]) + return name + else: + query = f"SELECT fullname FROM prof WHERE id={id}" + print(query) + result = self.cur.execute(query).fetchone() + name = result[0] + return name + + def get_prof_id(self, profname: str): + query = f"SELECT id FROM prof WHERE fullname='{profname.replace(',', '')}'" + result = self.cur.execute(query).fetchone() + if result is None: + return None + return self.cur.execute(query).fetchone()[0] + + def get_app_id(self, appname: str): + query = f"SELECT id FROM semesterapparat WHERE name='{appname}'" + result = self.cur.execute(query).fetchone() + if result is None: + return None + return self.cur.execute(query).fetchone()[0] + + def get_profs(self): + query = "select * from prof" + return self.cur.execute(query).fetchall() + + def app_exists(self, appnr: str) -> bool: + query = f"SELECT appnr FROM semesterapparat WHERE appnr='{appnr}'" + result = self.cur.execute(query).fetchone() + if result is None: + return False + return True + + def get_prof_data(self, profname: str = None, id: int = None) -> dict[str, str]: + if profname is not None: + profname = profname.replace(",", "") + profname = re.sub(r"\s+", " ", profname).strip() + if id: + query = "Select prof_id FROM semesterapparat WHERE appnr=?" + params = (id,) + result = self.cur.execute(query, params).fetchone() + id = result[0] + query = ( + f"SELECT * FROM prof WHERE fullname='{profname}'" + if id is None + else f"SELECT * FROM prof WHERE id={id}" + ) + result = self.cur.execute(query).fetchone() + return_data = { + "prof_title": result[1], + "profname": f"{result[3], result[2]}", + "prof_mail": result[5], + "prof_tel": result[6], + "id": result[0], + } + print(return_data) + # select the entry that contains the first name + return return_data + + def set_new_sem_date(self, appnr, new_sem_date, dauerapp=False): + # Todo: use extend_semester in new release + date = datetime.datetime.now().strftime("%Y-%m-%d") + + query = f"UPDATE semesterapparat SET verlängert_am='{date}', verlängerung_bis='{new_sem_date}' WHERE appnr='{appnr}'" + if dauerapp is not False: + query = f"UPDATE semesterapparat SET verlängert_am='{date}', verlängerung_bis='{new_sem_date}', dauerapp='{dauerapp} WHERE appnr='{appnr}'" + self.cur.execute(query) + + self.database.commit() + + def create_apparat(self, ApparatData: ApparatData): + prof_id = self.get_prof_id(ApparatData.profname) + app_id = self.get_app_id(ApparatData.appname) + if app_id is None: + if prof_id is None: + self.create_prof(ApparatData.get_prof_details()) + prof_id = self.get_prof_id(ApparatData.profname) + + query = f"INSERT OR IGNORE INTO semesterapparat (appnr, name, erstellsemester, dauer, prof_id, fach,deletion_status,konto) VALUES ('{ApparatData.appnr}', '{ApparatData.appname}', '{ApparatData.semester}', '{ApparatData.dauerapp}', {prof_id}, '{ApparatData.app_fach}', '{ApparatData.deleted}', '{SEMAP_MEDIA_ACCOUNTS[ApparatData.appnr]}')" + print(query) + self.cur.execute(query) + self.database.commit() + logger.log_info(f"Created new apparat {ApparatData.appname}") + app_id = self.get_app_id(ApparatData.appname) + # if ApparatData.media_list is not None: #! Deprecated + # for media in ApparatData.media_list: + # self.insert_file(media, app_id) + # self.database.commit() + return app_id + + def create_prof(self, prof_details: dict): + prof_title = prof_details["prof_title"] + prof_fname = prof_details["profname"].split(",")[1] + prof_fname = prof_fname.strip() + prof_lname = prof_details["profname"].split(",")[0] + prof_lname = prof_lname.strip() + prof_fullname = prof_details["profname"].replace(",", "") + prof_mail = prof_details["prof_mail"] + prof_tel = prof_details["prof_tel"] + + query = f'INSERT OR IGNORE INTO prof (titel, fname, lname, fullname, mail, telnr) VALUES ("{prof_title}", "{prof_fname}", "{prof_lname}", "{prof_fullname}", "{prof_mail}", "{prof_tel}")' + self.cur.execute(query) + self.database.commit() + pass + + def get_apparat_nrs(self) -> list: + try: + self.cur.execute( + "SELECT appnr FROM semesterapparat Where deletion_status=0" + ) + except sql3.OperationalError: + return [] + return [i[0] for i in self.cur.fetchall()] + + def get_all_apparts(self, deleted=0): + + self.cur.execute( + f"SELECT * FROM semesterapparat WHERE deletion_status={deleted}" + ) + return self.cur.fetchall() + + # + def get_app_data(self, appnr, appname) -> ApparatData: + result = self.cur.execute( + f"SELECT * FROM semesterapparat WHERE appnr='{appnr}' AND name='{appname}'" + ).fetchone() + print(f"result: {result}") + # app_id=result[0] + data = ApparatData() + data.appnr = appnr + data.app_fach = result[3] + data.appname = result[1] + profname = self.get_prof_name_by_id(result[2]) + # set profname to lastname, firstname + profname = f"{profname.split(' ')[0]}, {profname.split(' ')[1]}" + data.profname = profname + prof_data = self.get_prof_data(data.profname) + data.prof_mail = prof_data["prof_mail"] + data.prof_tel = prof_data["prof_tel"] + data.prof_title = prof_data["prof_title"] + data.erstellsemester = result[5] + data.semester = result[8] + data.deleted = result[9] + data.apparat_adis_id = result[12] + data.prof_adis_id = None + + print(data) + # data.media_list=self.get_media(app_id) + + return data + + def add_medium(self, bookdata: BookData, app_id: str, prof_id: str, *args): + # insert the bookdata into the media table + # try to retrieve the bookdata from the media table, check if the to be inserted bookdata is already in the table for the corresponding apparat + # if yes, do not insert the bookdata + # if no, insert the bookdata + t_query = ( + f"SELECT bookdata FROM media WHERE app_id={app_id} AND prof_id={prof_id}" + ) + # print(t_query) + result = self.cur.execute(t_query).fetchall() + result = [pickle.loads(i[0]) for i in result] + if bookdata in result: + print("Bookdata already in database") + # check if the book was deleted in the apparat + query = ( + "SELECT deleted FROM media WHERE app_id=? AND prof_id=? AND bookdata=?" + ) + params = (app_id, prof_id, pickle.dumps(bookdata)) + result = self.cur.execute(query, params).fetchone() + if result[0] == 1: + print("Book was deleted, updating bookdata") + query = "UPDATE media SET deleted=0 WHERE app_id=? AND prof_id=? AND bookdata=?" + params = (app_id, prof_id, pickle.dumps(bookdata)) + self.cur.execute(query, params) + self.database.commit() + return + + query = ( + "INSERT INTO media (bookdata, app_id, prof_id,deleted) VALUES (?, ?, ?,?)" + ) + converted = pickle.dumps(bookdata) + params = (converted, app_id, prof_id, 0) + self.cur.execute(query, params) + self.database.commit() + + def request_medium(self, app_id, prof_id, signature) -> int: + query = "SELECT bookdata, id FROM media WHERE app_id=? AND prof_id=?" + params = (app_id, prof_id) + result = self.cur.execute(query, params).fetchall() + books = [(i[1], pickle.loads(i[0])) for i in result] + print(books) + book = [i[0] for i in books if i[1].signature == signature] + return book[0] + + def add_message(self, message: dict, user, appnr): + def __get_user_id(user): + query = "SELECT id FROM user WHERE username=?" + params = (user,) + result = self.cur.execute(query, params).fetchone() + return result[0] + + user_id = __get_user_id(user) + query = "INSERT INTO messages (message, user_id, remind_at) VALUES (?, ?, ?)" + params = (message["message"], user_id, message["remind_at"]) + self.cur.execute(query, params) + self.database.commit() + + def get_messages(self, date: str): + def __get_user_name(id): + query = "SELECT username FROM user WHERE id=?" + params = (id,) + result = self.cur.execute(query, params).fetchone() + return result[0] + + query = f"SELECT * FROM messages WHERE remind_at='{date}'" + result = self.cur.execute(query).fetchall() + ret = [ + { + "message": i[2], + "user": __get_user_name(i[4]), + "apparatnr": i[5], + "id": i[0], + } + for i in result + ] + return ret + + def get_apparat_id(self, appname): + query = f"SELECT appnr FROM semesterapparat WHERE name='{appname}'" + result = self.cur.execute(query).fetchone() + return result[0] + + def get_apparats_by_semester(self, semester: str): + query = f"SELECT * FROM semesterapparat WHERE erstellsemester='{semester}'" + result = self.cur.execute(query).fetchall() + return result + + def get_semester(self) -> list[str]: + query = "SELECT DISTINCT erstellsemester FROM semesterapparat" + result = self.cur.execute(query).fetchall() + return [i for i in result] + + def is_eternal(self, id): + query = f"SELECT dauer FROM semesterapparat WHERE id={id}" + result = self.cur.execute(query).fetchone() + return result[0] + + def statistic_request(self, **kwargs: Any): + def __query(query): + result = self.cur.execute(query).fetchall() + for result_a in result: + orig_value = result_a + prof_name = self.get_prof_name_by_id(result_a[2]) + # replace the prof_id with the prof_name + result_a = list(result_a) + result_a[2] = prof_name + result_a = tuple(result_a) + result[result.index(orig_value)] = result_a + + return result + + if "deletable" in kwargs.keys(): + query = f"SELECT * FROM semesterapparat WHERE deletion_status=0 AND dauer=0 AND (erstellsemester!='{kwargs['deletesemester']}' OR verlängerung_bis!='{kwargs['deletesemester']}')" + return __query(query) + + if "dauer" in kwargs.keys(): + kwargs["dauer"] = kwargs["dauer"].replace("Ja", "1").replace("Nein", "0") + query = "SELECT * FROM semesterapparat WHERE " + for key, value in kwargs.items() if kwargs.items() is not None else {}: + print(key, value) + query += f"{key}='{value}' AND " + print(query) + # remove deletesemester part from normal query, as this will be added to the database upon deleting the apparat + if "deletesemester" in kwargs.keys(): + query = query.replace( + f"deletesemester='{kwargs['deletesemester']}' AND ", "" + ) + if "endsemester" in kwargs.keys(): + if "erstellsemester" in kwargs.keys(): + query = query.replace(f"endsemester='{kwargs['endsemester']}' AND ", "") + query = query.replace( + f"erstellsemester='{kwargs['erstellsemester']} AND ", "xyz" + ) + else: + query = query.replace( + f"endsemester='{kwargs['endsemester']}' AND ", "xyz" + ) + print("replaced") + query = query.replace( + "xyz", + f"(erstellsemester='{kwargs['endsemester']}' OR verlängerung_bis='{kwargs['endsemester']}') AND ", + ) + # remove all x="" parts from the query where x is a key in kwargs + + query = query[:-5] + print(query) + return __query(query) + + def get_app_count_by_semester(self) -> tuple[list[str], list[int]]: + """get the apparats created and deleted in the distinct semesters""" + + # get unique semesters + query = "SELECT DISTINCT erstellsemester FROM semesterapparat" + result = self.cur.execute(query).fetchall() + semesters = [i[0] for i in result] + created = [] + deleted = [] + for semester in semesters: + query = f"SELECT COUNT(*) FROM semesterapparat WHERE erstellsemester='{semester}'" + result = self.cur.execute(query).fetchone() + created.append(result[0]) + query = f"SELECT COUNT(*) FROM semesterapparat WHERE deletion_status=1 AND deleted_date='{semester}'" + result = self.cur.execute(query).fetchone() + deleted.append(result[0]) + # store data in a tuple + ret = [] + e_tuple = () + for sem in semesters: + e_tuple = ( + sem, + created[semesters.index(sem)], + deleted[semesters.index(sem)], + ) + ret.append(e_tuple) + return ret + # get the count of apparats created in the semesters + + def apparats_by_semester(self, semester: str): + """Get a list of all created and deleted apparats in the given semester""" + # get a list of apparats created and in the given semester + query = f"SELECT name,prof_id FROM semesterapparat WHERE erstellsemester='{semester}'" + result = self.cur.execute(query).fetchall() + c_tmp = [] + for i in result: + c_tmp.append((i[0], self.get_prof_name_by_id(i[1]))) + query = ( + f"SELECT name,prof_id FROM semesterapparat WHERE deleted_date='{semester}'" + ) + result = self.cur.execute(query).fetchall() + d_tmp = [] + for i in result: + d_tmp.append((i[0], self.get_prof_name_by_id(i[1]))) + # group the apparats by prof + c_ret = {} + for i in c_tmp: + if i[1] not in c_ret.keys(): + c_ret[i[1]] = [i[0]] + else: + c_ret[i[1]].append(i[0]) + d_ret = {} + for i in d_tmp: + if i[1] not in d_ret.keys(): + d_ret[i[1]] = [i[0]] + else: + d_ret[i[1]].append(i[0]) + return {"created": c_ret, "deleted": d_ret} + + def delete_message(self, message_id): + query = "DELETE FROM messages WHERE id=?" + params = (message_id,) + self.cur.execute(query, params) + self.database.commit() + + def delete_medium(self, title_id): + # delete the bookdata from the media table + query = "UPDATE media SET deleted=1 WHERE id=?" + params = (title_id,) + self.cur.execute(query, params) + self.database.commit() + pass + + def update_bookdata(self, bookdata: BookData, title_id): + query = "UPDATE media SET bookdata=? WHERE id=?" + converted = pickle.dumps(bookdata) + params = (converted, title_id) + self.cur.execute(query, params) + self.database.commit() + + def get_specific_book(self, book_id): + query = "SELECT bookdata FROM media WHERE id=?" + params = (book_id,) + result = self.cur.execute(query, params).fetchone() + return pickle.loads(result[0]) + + def get_media(self, app_id, prof_id, del_state=0) -> list[dict[int, BookData, int]]: + """request media from database and return result as list. + + Args: + ---- + - app_id (int): ID of the apparat + - prof_id (int): ID of the prof + - del_state (int, optional): If deleted books should be requested as well. 1 = yes 0 = no. Defaults to 0. + + Returns: + ------- + - list[dict[int,BookData,int]]: Returns a list of dictionaries containing the bookdata, the id and the availability of the book in the following format: + ------- + {"id": int, + "bookdata": BookData, + "available": int} + """ + query = f"SELECT id,bookdata,available FROM media WHERE (app_id={app_id} AND prof_id={prof_id}) AND (deleted={del_state if del_state == 0 else '1 OR deleted=0'})" + logger.log_info(f"Requesting media from database with query: {query}") + result = self.cur.execute(query).fetchall() + ret_result = [] + for result_a in result: + # ic(result_a) + data = {"id": int, "bookdata": BookData, "available": int} + data["id"] = result_a[0] + data["bookdata"] = pickle.loads(result_a[1]) + data["available"] = result_a[2] + ret_result.append(data) + return ret_result + + def get_subjects_and_aliases(self): + query = "SELECT subjects.name, aliases.name FROM subjects LEFT JOIN aliases ON subjects.id = aliases.subject_id" + return self.cur.execute(query).fetchall() + + def get_subjects(self): + query = "SELECT id,name FROM subjects" + return self.cur.execute(query).fetchall() + + def get_aliases(self, subject_id): + query = f"SELECT name FROM aliases WHERE subject_id={subject_id}" + return self.cur.execute(query).fetchall() + + def add_subject(self, subject_name): + query = f"INSERT INTO subjects (name) VALUES ('{subject_name}')" + self.cur.execute(query) + self.database.commit() + + def get_apparats_by_prof(self, prof_id): + query = f"SELECT * FROM semesterapparat WHERE prof_id={prof_id}" + return self.cur.execute(query).fetchall() + + def add_alias(self, alias_name, subject): + query = f"SELECT id FROM subjects WHERE name='{subject}'" + subject_id = self.cur.execute(query).fetchone()[0] + query = f"INSERT INTO aliases (name,subject_id) VALUES ('{alias_name}',{subject_id})" + self.cur.execute(query) + self.database.commit() + + def update_apparat(self, apparat_data: ApparatData): + data = apparat_data + query = "UPDATE semesterapparat SET name = ?, fach = ?, dauer = ?, prof_id = ? WHERE appnr = ?" + params = ( + data.appname, + data.app_fach, + data.dauerapp, + self.get_prof_id(data.profname), + data.appnr, + ) + print(query) + self.cur.execute(query, params) + self.database.commit() + + def delete_apparat(self, appnr: str, semester: str): + # update the deletion status to 1 and the deleted_state to semester for the given apparat + query = f"UPDATE semesterapparat SET deletion_status=1, deleted_date='{semester}' WHERE appnr='{appnr}'" + self.cur.execute(query) + self.database.commit() + + def get_book_id(self, bookdata: BookData, app_id: int, prof_id: int): + query = "SELECT id FROM media WHERE bookdata=? AND app_id=? AND prof_id=?" + params = (pickle.loads(bookdata), app_id, prof_id) + result = self.cur.execute(query, params).fetchone() + return result + + def set_availability(self, book_id, available): + query = "UPDATE media SET available=? WHERE id=?" + params = (available, book_id) + self.cur.execute(query, params) + self.database.commit() + + def get_latest_book_id(self): + query = "SELECT id FROM media ORDER BY id DESC LIMIT 1" + result = self.cur.execute(query).fetchone() + return result[0] + + def close(self): + self.database.close() + + def login(self, username, hashed_password) -> bool: + # check if the user and password exist in the database + # if yes, return True + # if no, return False + query = "SELECT salt FROM user WHERE username=?" + params = (username,) + result = self.cur.execute(query, params).fetchone() + + if result is None: + return False + salt = result[0] + print(salt) + query = "SELECT password FROM user WHERE username=?" + params = (str(username),) + result = self.cur.execute(query, params).fetchone() + password = result[0] + if password == f"{salt}{hashed_password}": + return True + else: + return False + + # admin stuff below here + def get_users(self): + query = "SELECT * FROM user" + return self.cur.execute(query).fetchall() + + def get_apparats(self) -> list[tuple]: + query = "SELECT * FROM semesterapparat" + return self.cur.execute(query).fetchall() + + def change_password(self, user, password): + saltq = "SELECT salt FROM user WHERE username=?" + params = (user,) + salt = self.cur.execute(saltq, params).fetchone()[0] + password = f"{salt}{password}" + query = "UPDATE user SET password=? WHERE username=?" + params = (password, user) + self.cur.execute(query, params) + self.database.commit() + + def get_role(self, username): + query = "SELECT role FROM user WHERE username=?" + params = (username,) + result = self.cur.execute(query, params).fetchone() + return result[0] + + def get_roles(self): + query = "SELECT role FROM user" + return self.cur.execute(query).fetchall() + + def create_user(self, username, password, role, salt): + """Create a user based on passed data. + + Args: + ---- + - username (str): Username to be used + - password (str): the salted password + - role (str): Role of the user + - salt (str): a random salt for the user + """ + query = "INSERT OR IGNORE INTO user (username, password, role, salt) VALUES (?, ?, ?, ?)" + params = (username, password, role, salt) + self.cur.execute(query, params) + self.database.commit() + + def delete_user(self, user): + query = "DELETE FROM user WHERE username=?" + params = (user,) + self.cur.execute(query, params) + self.database.commit() + + def get_faculty_members(self, name: str = None): + query = "SELECT titel, fname,lname,mail,telnr,fullname FROM prof" + if name: + query = query.replace(",fullname", "") + query += f" WHERE fullname='{name}'" + return self.cur.execute(query).fetchall() + + def update_faculty_member(self, data, oldlname, oldfname): + placeholders = ", ".join(f"{k} = :{k}" for k in data.keys()) + sql = f"UPDATE prof SET {placeholders} WHERE lname = :oldlname AND fname = :oldfname" + data["oldlname"] = oldlname + data["oldfname"] = oldfname + print(sql, data) + self.cur.execute(sql, data) + self.database.commit() + + def faculty_data(self, name): + query = f"SELECT * FROM prof WHERE fullname='{name}'" + result = self.cur.execute(query).fetchone() + return result + + def update_user(self, username, data: dict[str, str]): + + query = "UPDATE user SET " + for key, value in data.items(): + if key == "username": + continue + query += f"{key}='{value}'," + query = query[:-1] + query += " WHERE username=?" + params = (username,) + + self.cur.execute(query, params) + self.database.commit() + + def check_username(self, username): + query = "SELECT username FROM user WHERE username=?" + params = (username,) + result = self.cur.execute(query, params).fetchone() + if result is None: + return False + return True + + def get_apparats_name(self, app_id, prof_id): + query = f"SELECT name FROM semesterapparat WHERE appnr={app_id} AND prof_id={prof_id}" + # ic(query) + result = self.cur.execute(query).fetchone() + return result[0] + + def search_book(self, data: dict[str, str]) -> list[tuple[BookData, int]]: + query = "SELECT * FROM media " + result = self.database.execute(query).fetchall() + ret = [] + # get length of data dict + length = len(data) + mode = 0 + if length == 1: + if "signature" in data.keys(): + mode = 1 + elif "title" in data.keys(): + mode = 2 + elif length == 2: + mode = 3 + else: + return None + print(len(result)) + for res in result: + bookdata = pickle.loads(res[1]) + app_id = res[2] + prof_id = res[3] + # if signature and title present in dict: + # if signature present in dict: + if mode == 1: + if data["signature"] in bookdata.signature: + ret.append((bookdata, app_id, prof_id)) + # if title present in dict: + elif mode == 2: + if data["title"] in bookdata.title: + ret.append((bookdata, app_id, prof_id)) + elif mode == 3: + if ( + data["signature"] in bookdata.signature + and data["title"] in bookdata.title + ): + ret.append((bookdata, app_id, prof_id)) + return ret + + +if __name__ == "__main__": + db = Database() + print(db.login("kirchner", "loginpass")) diff --git a/src/backend/pickles.py b/src/backend/pickles.py index 5b4e104..22f60c9 100644 --- a/src/backend/pickles.py +++ b/src/backend/pickles.py @@ -1,9 +1,10 @@ import pickle -from typing import ByteString, Any +from typing import Any, ByteString -def make_pickle(data:Any): + +def make_pickle(data: Any): return pickle.dumps(data) -def load_pickle(data:ByteString): +def load_pickle(data: ByteString): return pickle.loads(data) diff --git a/src/backend/semester.py b/src/backend/semester.py index c4ab2cc..a373bbe 100644 --- a/src/backend/semester.py +++ b/src/backend/semester.py @@ -1,13 +1,14 @@ import datetime + def generateSemesterByDate(): - currentYear = datetime.datetime.now().year - currentYear = int(str(currentYear)[-2:]) - month = datetime.datetime.now().month - if month >= 4 and month <= 9: - return "SoSe " + str(currentYear) + currentYear = datetime.datetime.now().year + currentYear = int(str(currentYear)[-2:]) + month = datetime.datetime.now().month + if month >= 4 and month <= 9: + return "SoSe " + str(currentYear) + else: + if month == 10 or month == 11: + return f"WiSe {currentYear}/{currentYear+1}" else: - if month == 10 or month == 11: - return f"WiSe {currentYear}/{currentYear+1}" - else: - return f"WiSe {currentYear-1}/{currentYear}" + return f"WiSe {currentYear-1}/{currentYear}" diff --git a/src/errors/DatabaseErrors.py b/src/errors/DatabaseErrors.py index a557702..60674fe 100644 --- a/src/errors/DatabaseErrors.py +++ b/src/errors/DatabaseErrors.py @@ -1,9 +1,10 @@ class NoResultError(Exception): - def __init__(self,message): - self.message = f"The query: {message} returned no results" + def __init__(self, message): + self.message = f"The query: {message} returned no results" super().__init__(self.message) - + + class AppPresentError(Exception): def __init__(self, message): self.message = f"The app {message} is already present in the database" - super().__init__(self.message) \ No newline at end of file + super().__init__(self.message) diff --git a/src/errors/__init__.py b/src/errors/__init__.py index 684b4a1..0a24916 100644 --- a/src/errors/__init__.py +++ b/src/errors/__init__.py @@ -1,2 +1,2 @@ -#import basic error classes -from .DatabaseErrors import * \ No newline at end of file +# import basic error classes +from .DatabaseErrors import * diff --git a/src/logic/constants.py b/src/logic/constants.py index 42c2625..f2b4223 100644 --- a/src/logic/constants.py +++ b/src/logic/constants.py @@ -212,4 +212,4 @@ SEMAP_MEDIA_ACCOUNTS = { "178": "1008017766", "179": "1008017899", "180": "1008017922", -} \ No newline at end of file +} diff --git a/src/logic/csvparser.py b/src/logic/csvparser.py index 95129e5..7653ce7 100644 --- a/src/logic/csvparser.py +++ b/src/logic/csvparser.py @@ -7,15 +7,16 @@ def csv_to_list(path: str) -> list[str]: """ Extracts the data from a csv file and returns it as a pandas dataframe """ - with open(path, newline='') as csvfile: - reader = csv.reader(csvfile, delimiter=';', quotechar='|') + with open(path, newline="") as csvfile: + reader = csv.reader(csvfile, delimiter=";", quotechar="|") ret = [] for row in reader: print(row) ret.append(row[0].replace('"', "")) - return ret - + return ret + + if __name__ == "__main__": text = csv_to_list("C:/Users/aky547/Desktop/semap/71.csv") - #remove linebreaks - print(text) \ No newline at end of file + # remove linebreaks + print(text) diff --git a/src/logic/dataclass.py b/src/logic/dataclass.py index f617d9a..a41e29e 100644 --- a/src/logic/dataclass.py +++ b/src/logic/dataclass.py @@ -2,6 +2,7 @@ import re from dataclasses import dataclass, field from enum import Enum + @dataclass class ApparatData: prof_title: str | None = None @@ -101,17 +102,17 @@ class Subjects(Enum): TECHNIC = (22, "Technik") THEOLOGY = (23, "Theologie") ECONOMICS = (24, "Wirtschaftslehre") - + @property def id(self): return self.value[0] - + @property def name(self): return self.value[1] - + @classmethod def get_index(cls, name): for i in cls: if i.name == name: - return i.id -1 \ No newline at end of file + return i.id - 1 diff --git a/src/logic/get_msword_content.py b/src/logic/get_msword_content.py index 1f44218..d8e6e79 100644 --- a/src/logic/get_msword_content.py +++ b/src/logic/get_msword_content.py @@ -1,24 +1,23 @@ from docx import Document -data={} -wordDoc = Document('files/Semesterapparat - Anmeldung.docx') +data = {} +wordDoc = Document("files/Semesterapparat - Anmeldung.docx") paragraphs = wordDoc.tables for table in paragraphs: for column in table.columns: - cellcount=0 + cellcount = 0 for cell in column.cells: - if cellcount<12: - cellcount+=1 - print(f'cell:{cell.text}') - - + if cellcount < 12: + cellcount += 1 + print(f"cell:{cell.text}") + # print(f'paragraphs[{i}]: {paragraphs[i]}') # data[i] = paragraphs[i] - + # for i in range(0, len(paragraphs)): # for i in range(2, len(paragraphs)): # data[i] = paragraphs[i] - + print(data) # for table in wordDoc.tables: @@ -26,6 +25,3 @@ print(data) # print('---') # for cell in row.cells: # print(f'cell:{cell.text}') - - - diff --git a/src/logic/get_pdf_content.py b/src/logic/get_pdf_content.py index 2a3d6ed..1b0213d 100644 --- a/src/logic/get_pdf_content.py +++ b/src/logic/get_pdf_content.py @@ -1,11 +1,10 @@ import tabula +file = "files/Semesterapparat - Anmeldung.pdf" -file="files/Semesterapparat - Anmeldung.pdf" def extract_book_data(file): - tables=tabula.read_pdf(file,pages="all",encoding="utf-8",multiple_tables=True) + tabula.read_pdf(file, pages="all", encoding="utf-8", multiple_tables=True) tabula.convert_into(file, file.replace(".pdf"), output_format="csv", pages="all") with open("files/Semesterapparat - Anmeldung.csv", "r") as f: - content=f.read() - + f.read() diff --git a/src/logic/log.py b/src/logic/log.py index a351fcb..d7747d8 100644 --- a/src/logic/log.py +++ b/src/logic/log.py @@ -1,25 +1,38 @@ import logging +import logging.handlers import os if not os.path.exists("logs"): os.mkdir("logs") -#open and close the file to create it + # open and close the file to create it with open("logs/application.log", "w") as f: pass +log_filesize = 10 * 1024**2 # 10MB +backups = 5 # Create a common file handler for all loggers -common_file_handler = logging.FileHandler("logs/application.log") +common_file_handler = logging.handlers.RotatingFileHandler( + "logs/application.log", + mode="a", + encoding="utf-8", + maxBytes=log_filesize, + backupCount=backups, +) + common_file_handler.setLevel(logging.DEBUG) formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") common_file_handler.setFormatter(formatter) -#set max file size to 10MB, if exceeded, create a new file + + +# set max file size to 10MB, if exceeded, create a new file + + class MyLogger: def __init__(self, logger_name): self.logger = logging.getLogger(logger_name) self.logger.setLevel(logging.DEBUG) self.logger.addHandler(common_file_handler) self.encoding = "utf-8" - def log_info(self, message: str): # ensure that the message is encoded in utf-8 @@ -61,6 +74,6 @@ if __name__ == "__main__": try: # Simulate an exception raise Exception("An exception occurred") - except Exception as e: + except Exception: logger1.log_exception("An exception occurred in Logger1") logger2.log_exception("An exception occurred in Logger2") diff --git a/src/logic/pdfparser.py b/src/logic/pdfparser.py index 5c0482e..f4c394e 100644 --- a/src/logic/pdfparser.py +++ b/src/logic/pdfparser.py @@ -12,19 +12,15 @@ def pdf_to_csv(path: str) -> pd.DataFrame: """ file = PDFQuery(path) file.load() - #get the text from the pdf file - text_elems = file.extract([ - ('with_formatter', 'text'), - ('all_text', '*') - ]) - extracted_text = text_elems['all_text'] - + # get the text from the pdf file + text_elems = file.extract([("with_formatter", "text"), ("all_text", "*")]) + extracted_text = text_elems["all_text"] + return extracted_text - - + + if __name__ == "__main__": text = pdf_to_csv("54_pdf.pdf") - #remove linebreaks + # remove linebreaks text = text.replace("\n", "") print(text) - diff --git a/src/logic/settings.py b/src/logic/settings.py index 3e7cda3..3b4754b 100644 --- a/src/logic/settings.py +++ b/src/logic/settings.py @@ -1,20 +1,24 @@ -import yaml from dataclasses import dataclass, field +import yaml + + @dataclass class Settings: """Settings for the app.""" + save_path: str database_name: str database_path: str - default_apps:bool = True + default_apps: bool = True custom_applications: list[dict] = field(default_factory=list) + def save_settings(self): """Save the settings to the config file.""" with open("config.yaml", "w") as f: yaml.dump(self.__dict__, f) - -#open the config file and load the settings + + +# open the config file and load the settings with open("config.yaml", "r") as f: data = yaml.safe_load(f) - diff --git a/src/logic/thread_bookgrabber.py b/src/logic/thread_bookgrabber.py index 50dfcbb..351127f 100644 --- a/src/logic/thread_bookgrabber.py +++ b/src/logic/thread_bookgrabber.py @@ -1,7 +1,8 @@ import sqlite3 import time -from PyQt6.QtCore import QThread, pyqtSignal as Signal +from PyQt6.QtCore import QThread +from PyQt6.QtCore import pyqtSignal as Signal from src.backend.database import Database from src.logic.log import MyLogger @@ -62,8 +63,8 @@ class BookGrabber(QThread): for rds_item in rds.items: sign = rds_item.superlocation loc = rds_item.location - #ic(sign, loc) - #ic(rds_item) + # ic(sign, loc) + # ic(rds_item) if self.app_id in sign or self.app_id in loc: state = 1 break diff --git a/src/logic/threads.py b/src/logic/threads.py index 913a9dd..5176d4c 100644 --- a/src/logic/threads.py +++ b/src/logic/threads.py @@ -2,10 +2,11 @@ import os import sqlite3 import time -#from icecream import ic +# from icecream import ic from omegaconf import OmegaConf from PyQt6 import QtWidgets -from PyQt6.QtCore import QThread, pyqtSignal as Signal +from PyQt6.QtCore import QThread +from PyQt6.QtCore import pyqtSignal as Signal from src.backend.database import Database from src.logic.log import MyLogger @@ -26,7 +27,7 @@ class BackgroundChecker(QThread): class MockAvailCheck: def __init__( - self, links: list = [], appnumber: int = None, parent=None, books=list[dict] + self, links: list = None, appnumber: int = None, parent=None, books=list[dict] ): if links is None: links = [] @@ -58,7 +59,7 @@ class MockAvailCheck: for item in rds.items: sign = item.superlocation loc = item.location - #ic(item.location, item.superlocation) + # ic(item.location, item.superlocation) if self.appnumber in sign or self.appnumber in loc: state = 1 book_id = None @@ -197,18 +198,3 @@ class MailThread(QThread): def run(self): self.show_ui() self.updateSignal.emit(1) - - - - - - - - - - - - - - - diff --git a/src/logic/threads_autoadder.py b/src/logic/threads_autoadder.py index 8c8c869..3354013 100644 --- a/src/logic/threads_autoadder.py +++ b/src/logic/threads_autoadder.py @@ -1,7 +1,8 @@ import time -#from icecream import ic -from PyQt6.QtCore import QThread, pyqtSignal as Signal +# from icecream import ic +from PyQt6.QtCore import QThread +from PyQt6.QtCore import pyqtSignal as Signal from src.backend.database import Database from src.logic.log import MyLogger diff --git a/src/logic/threads_availchecker.py b/src/logic/threads_availchecker.py index e05f2dc..2dea557 100644 --- a/src/logic/threads_availchecker.py +++ b/src/logic/threads_availchecker.py @@ -1,7 +1,8 @@ import time -#from icecream import ic -from PyQt6.QtCore import QThread, pyqtSignal as Signal +# from icecream import ic +from PyQt6.QtCore import QThread +from PyQt6.QtCore import pyqtSignal as Signal from src.backend.database import Database from src.logic.log import MyLogger diff --git a/src/logic/threads_copy.py b/src/logic/threads_copy.py index 5a42f9d..904c430 100644 --- a/src/logic/threads_copy.py +++ b/src/logic/threads_copy.py @@ -1,14 +1,16 @@ +import os +import sqlite3 import threading import time -import os + from PyQt6.QtCore import QThread, pyqtSignal from src.backend.database import Database from src.logic.log import MyLogger -from src.transformers import RDS_AVAIL_DATA from src.logic.webrequest import BibTextTransformer, WebRequest -import sqlite3 -#from icecream import ic +from src.transformers import RDS_AVAIL_DATA + +# from icecream import ic class BookGrabber(QThread): @@ -19,8 +21,8 @@ class BookGrabber(QThread): self.is_Running = True self.logger = MyLogger("Worker") self.logger.log_info("Starting worker thread") - self.data,self.app_id,self.prof_id,self.mode = self.readFile(filename) - + self.data, self.app_id, self.prof_id, self.mode = self.readFile(filename) + self.book_id = None time.sleep(2) @@ -68,8 +70,8 @@ class BookGrabber(QThread): for rds_item in rds.items: sign = rds_item.superlocation loc = rds_item.location - #ic(sign, loc) - #ic(rds_item) + # ic(sign, loc) + # ic(rds_item) if self.app_id in sign or self.app_id in loc: state = 1 break @@ -213,7 +215,7 @@ class AutoAdder(QThread): class MockAvailCheck: def __init__( - self, links: list = [], appnumber: int = None, parent=None, books=list[dict] + self, links: list = None, appnumber: int = None, parent=None, books=list[dict] ): if links is None: links = [] @@ -245,7 +247,7 @@ class MockAvailCheck: for item in rds.items: sign = item.superlocation loc = item.location - #ic(item.location, item.superlocation) + # ic(item.location, item.superlocation) if self.appnumber in sign or self.appnumber in loc: state = 1 book_id = None diff --git a/src/logic/userInterface.py b/src/logic/userInterface.py index 03c5593..1d2ead8 100644 --- a/src/logic/userInterface.py +++ b/src/logic/userInterface.py @@ -16,8 +16,6 @@ from PyQt6 import QtCore, QtGui, QtWidgets from PyQt6.QtCore import QDate, QThread from PyQt6.QtGui import QColor, QRegularExpressionValidator -# from src.logic.webrequest import BibTextTransformer, WebRequest -# from src.utils import documentationview from src.backend.admin_console import AdminCommands from src.backend.create_file import recreateFile from src.backend.database import Database @@ -29,7 +27,7 @@ from src.logic.csvparser import csv_to_list from src.logic.dataclass import ApparatData, BookData from src.logic.log import MyLogger from src.logic.wordparser import word_docx_to_csv -from src.ui import ( # Mail_Dialog, +from src.ui import ( App_Ext_Dialog, FilePicker, GraphWidget, @@ -63,7 +61,6 @@ class Medien(medienadder_ui): return self.comboBox.currentText() - class MyComboBox(QtWidgets.QComboBox): def __init__(self, parent=None): @@ -184,18 +181,7 @@ class Ui(Ui_Semesterapparat): QtWidgets.QScrollBar(), QtCore.Qt.AlignmentFlag.AlignRight ) # connect contextmenuevent to tablewidget - self.tableWidget_apparate.setContextMenuPolicy( - QtCore.Qt.ContextMenuPolicy.CustomContextMenu - ) - self.tableWidget_apparate.customContextMenuRequested.connect( - self.open_context_menu - ) - self.tableWidget_apparat_media.setContextMenuPolicy( - QtCore.Qt.ContextMenuPolicy.CustomContextMenu - ) - self.tableWidget_apparat_media.customContextMenuRequested.connect( - self.media_context_menu - ) + # enable automatic resizing of columns for book_search_result self.book_search_result.horizontalHeader().setSectionResizeMode( QtWidgets.QHeaderView.ResizeMode.Stretch @@ -235,6 +221,9 @@ class Ui(Ui_Semesterapparat): self.label_20.hide() self.line_3.hide() self.avail_status.hide() + self.chkbx_show_del_media.hide() + self.groupBox_2.hide() + self.groupBox.hide() self.check_deletable.stateChanged.connect(self.gridchange) self.tableWidget.horizontalHeader().setSectionResizeMode( QtWidgets.QHeaderView.ResizeMode.Stretch @@ -247,6 +236,9 @@ class Ui(Ui_Semesterapparat): self.tableWidget.resizeColumnsToContents() self.tableWidget.resizeRowsToContents() # set self.app_fach viable inputs to be + # self.app_fach.addItems([subject[1] for subject in self.db.getSubjects()]) + # self.app_fach.addItem("") + # self.app_fach.setCurrentText("") # create a thread, that continually checks the validity of the inputs @@ -269,10 +261,6 @@ class Ui(Ui_Semesterapparat): # self.thread_check() ### Admin interface ### - #!admin - create user - #!admin - delete user - #!admin - change user - # TODO:admin - change faculty self.select_action_box.addItem("") self.select_action_box.setCurrentText("") self.hide_all() @@ -282,7 +270,22 @@ class Ui(Ui_Semesterapparat): ) self.book_search.clicked.connect(self.search_book) - # enable click functionality for the combobox to allow selection of roles + # Context Menus + self.tableWidget_apparate.setContextMenuPolicy( + QtCore.Qt.ContextMenuPolicy.CustomContextMenu + ) + self.tableWidget_apparat_media.setContextMenuPolicy( + QtCore.Qt.ContextMenuPolicy.CustomContextMenu + ) + self.tableWidget_apparate.customContextMenuRequested.connect( + self.open_context_menu + ) + self.tableWidget_apparat_media.customContextMenuRequested.connect( + self.media_context_menu + ) + self.tableWidget.customContextMenuRequested.connect( + self.statistics_table_context_menu + ) # admin buttons self.user_frame_addUser.clicked.connect(self.add_user) @@ -290,6 +293,7 @@ class Ui(Ui_Semesterapparat): self.update_user.clicked.connect(self.update_user_data) self.update_faculty_member.clicked.connect(self.edit_faculty_member_action) + # Create instances to be used by the threads in the application self.bookGrabber = None self.availChecker = None self.mail_thread = None @@ -322,7 +326,7 @@ class Ui(Ui_Semesterapparat): "title": title if title != "" else None, } params = {key: value for key, value in params.items() if value is not None} - #ic(params) + # ic(params) retdata = self.db.searchBook(params) if retdata is None: return @@ -378,7 +382,7 @@ class Ui(Ui_Semesterapparat): password = self.user_create_frame_password.text() role = self.user_frame_userrole.currentText() if self.db.checkUsername(username): - #ic("username already exists") + # ic("username already exists") # self.user_create_frame_error.setText("Der Nutzername ist bereits vergeben")#TODO: implement error message return userdata = AdminCommands().create_password(password) @@ -402,8 +406,10 @@ class Ui(Ui_Semesterapparat): ) self.user_delete_confirm.setChecked(False) else: - self.user_delete_err_message.setText("Bitte bestätigen Sie die Löschung des Nutzers") # TODO: implement error message - #ic("please confirm the deletion of the user") + self.user_delete_err_message.setText( + "Bitte bestätigen Sie die Löschung des Nutzers" + ) # TODO: implement error message + # ic("please confirm the deletion of the user") def update_user_data(self): username = self.user_edit_frame_user_select.currentText() @@ -752,15 +758,21 @@ class Ui(Ui_Semesterapparat): def delete_selected_apparats(self): # get all selected apparats selected_apparats = [] + selected_apparat_rows = [] for i in range(self.tableWidget.rowCount()): if self.tableWidget.cellWidget(i, 0).isChecked(): selected_apparats.append(self.tableWidget.item(i, 2).text()) + selected_apparat_rows.append(i) # delete all selected apparats print(selected_apparats) for apparat in selected_apparats: self.db.deleteApparat(apparat, self.generateSemester(today=True)) + for row in selected_apparat_rows: + # set the background of the row to red + for j in range(5): + self.tableWidget.item(row, j).setBackground(QtGui.QColor(235, 74, 71)) # refresh the table - self.tab_changed() + self.populate_tab() self.btn_del_select_apparats.setEnabled(False) def statistics(self): @@ -842,7 +854,7 @@ class Ui(Ui_Semesterapparat): def populate_frame(self, appdata: ApparatData): # populate the frame with the data from the database - #ic(appdata) + # ic(appdata) self.drpdwn_app_nr.setCurrentText(str(appdata.appnr)) self.prof_title.setText(appdata.prof_title) prof_name = appdata.profname.split(" ") @@ -912,6 +924,8 @@ class Ui(Ui_Semesterapparat): self.update_app_media_list() self.cancel_active_selection.click() + self.check_send_mail.show() + self.chkbx_show_del_media.show() def confirm_popup(self, message: str): dial = QtWidgets.QDialog() @@ -1052,7 +1066,7 @@ class Ui(Ui_Semesterapparat): return selected_prof = self.drpdwn_prof_name.currentText() data = self.db.getProfData(selected_prof) - #ic(data) + # ic(data) prof_title = data[2] if prof_title == "None": prof_title = "Kein Titel" @@ -1071,12 +1085,13 @@ class Ui(Ui_Semesterapparat): return None def load_app_data(self, app_id=None): - print(type(app_id)) + if isinstance(app_id, str): # double click the tableWidget_apparate row with the given app_id row, column = self.get_index_of_value(self.tableWidget_apparate, app_id) # set the current index to the row self.tableWidget_apparate.setCurrentCell(row, 0) + self.check_send_mail.hide() app_pos = self.tableWidget_apparate.currentIndex() appnr = self.tableWidget_apparate.item(app_pos.row(), 0).text() appname = self.tableWidget_apparate.item(app_pos.row(), 1).text() @@ -1089,6 +1104,7 @@ class Ui(Ui_Semesterapparat): self.populate_frame(appdata) self.btn_apparat_save.hide() self.btn_reserve.show() + self.chkbx_show_del_media.show() self.drpdwn_app_nr.setDisabled(True) self.update_app_media_list() self.update_documemt_list() @@ -1128,9 +1144,7 @@ class Ui(Ui_Semesterapparat): self.chkbx_show_del_media.setEnabled(True) self.drpdwn_app_nr.setEnabled(True) self.app_fach.setEnabled(True) - self.app_fach.addItems([subject[1] for subject in self.db.getSubjects()]) - self.app_fach.addItem("") - self.app_fach.setCurrentText("") + if self.tableWidget_apparat_media.rowCount() > 0: self.tableWidget_apparat_media.setRowCount(0) # clear all fields @@ -1281,6 +1295,9 @@ class Ui(Ui_Semesterapparat): self.dokument_list.setRowCount(0) self.frame.setEnabled(False) self.app_fach.setCurrentText("") + self.chkbx_show_del_media.hide() + self.check_send_mail.hide() + self.btn_reserve.hide() for child in self.frame.findChildren(QtWidgets.QLineEdit): child.clear() @@ -1297,7 +1314,7 @@ class Ui(Ui_Semesterapparat): # booklist:list[BookData]=[book[0] for book in books] self.tableWidget_apparat_media.setRowCount(0) for book in books: - book_id = book["id"] + book["id"] book_data = book["bookdata"] availability = book["available"] # bd = BookData().from_string(book) @@ -1587,7 +1604,7 @@ class Ui(Ui_Semesterapparat): ] signatures = [i for i in signatures if i != ""] - #ic(signatures) + # ic(signatures) print("starting thread") self.autoGrabber = BookGrabber( mode="ARRAY", app_id=app_id, prof_id=prof_id, data=signatures @@ -1638,6 +1655,7 @@ class Ui(Ui_Semesterapparat): self.drpdwn_prof_name.clear() self.tableWidget_apparat_media.setRowCount(0) self.frame.setEnabled(False) + self.check_send_mail.setChecked(False) if not self.validate_fields(): self.confirm_popup("Bitte alle Pflichtfelder ausfüllen!") @@ -1718,7 +1736,7 @@ class Ui(Ui_Semesterapparat): self.old_apparats = self.apparats def insert_apparat_into_table(self, apparat): - #ic(apparat) + # ic(apparat) def __dauer_check(apparat): return "Ja" if apparat[7] == 1 else "Nein" @@ -1741,7 +1759,7 @@ class Ui(Ui_Semesterapparat): 0, 3, QtWidgets.QTableWidgetItem( - str(apparat[8]) if apparat[8] != None else apparat[5] + str(apparat[8]) if apparat[8] is not None else apparat[5] ), ) self.tableWidget_apparate.setItem( @@ -1766,6 +1784,23 @@ class Ui(Ui_Semesterapparat): remind_action.triggered.connect(self.reminder) menu.exec(self.tableWidget_apparate.mapToGlobal(position)) + def statistics_table_context_menu(self, position): + menu = QtWidgets.QMenu() + restore_action = menu.addAction("Wiederherstellen") + menu.addAction(restore_action) + restore_action.triggered.connect(self.restore_apparat) + menu.exec(self.tableWidget.mapToGlobal(position)) + + def restore_apparat(self): + row = self.tableWidget.currentRow() + apparat = self.tableWidget.item(row, 1).text() + ic(apparat) + apparat_id = self.db.getApparatId(apparat) + # restore the apparat + self.db.restoreApparat(apparat_id) + # update the table + self.reload() + def reminder(self): self.logger.log_info("Opening reminder dialog") dialog = QtWidgets.QDialog() @@ -1850,7 +1885,7 @@ class Ui(Ui_Semesterapparat): print(data) OmegaConf.save(data, "config.yaml") # re-load the config - config = OmegaConf.load("config.yaml") + OmegaConf.load("config.yaml") self.logger.log_info("Saved settings to config.yaml") self.reload() @@ -2080,43 +2115,3 @@ if __name__ == "__main__": # app.exec() # open login screen launch_gui() - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/logic/webrequest.py b/src/logic/webrequest.py index 8ac8c07..8146adf 100644 --- a/src/logic/webrequest.py +++ b/src/logic/webrequest.py @@ -2,11 +2,12 @@ import requests from bs4 import BeautifulSoup from omegaconf import OmegaConf +# import sleep_and_retry decorator to retry requests +from ratelimit import limits, sleep_and_retry + from src.logic.dataclass import BookData from src.logic.log import MyLogger from src.transformers import ARRAYData, BibTeXData, COinSData, RDSData, RISData -#import sleep_and_retry decorator to retry requests -from ratelimit import limits, sleep_and_retry logger = MyLogger(__name__) config = OmegaConf.load("config.yaml") @@ -121,6 +122,7 @@ class BibTextTransformer: if RDS_IDENT in line: self.data = line return self + def return_data(self, option=None) -> BookData: """Return Data to caller. diff --git a/src/transformers/__init__.py b/src/transformers/__init__.py index c247740..96f8070 100644 --- a/src/transformers/__init__.py +++ b/src/transformers/__init__.py @@ -1,6 +1,8 @@ -from .transformers import (RDS_AVAIL_DATA, - ARRAYData, - COinSData, - BibTeXData, - RISData, - RDSData) \ No newline at end of file +from .transformers import ( + RDS_AVAIL_DATA, + ARRAYData, + BibTeXData, + COinSData, + RDSData, + RISData, +) diff --git a/src/transformers/transformers.py b/src/transformers/transformers.py index d5f0667..c79f748 100644 --- a/src/transformers/transformers.py +++ b/src/transformers/transformers.py @@ -131,7 +131,7 @@ class ARRAYData: .strip() ) - except Exception as e: + except Exception: logger.log_exception("ARRAYData.transform failed") return "" diff --git a/src/ui/Ui_semesterapparat_ui.py b/src/ui/Ui_semesterapparat_ui.py index 1943e09..c1d7cfb 100644 --- a/src/ui/Ui_semesterapparat_ui.py +++ b/src/ui/Ui_semesterapparat_ui.py @@ -14,15 +14,18 @@ class Ui_MainWindow(object): MainWindow.setObjectName("MainWindow") MainWindow.setWindowModality(QtCore.Qt.WindowModality.WindowModal) MainWindow.setEnabled(True) - MainWindow.resize(1601, 800) + MainWindow.resize(1589, 800) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(MainWindow.sizePolicy().hasHeightForWidth()) MainWindow.setSizePolicy(sizePolicy) MainWindow.setMinimumSize(QtCore.QSize(1278, 800)) - MainWindow.setMaximumSize(QtCore.QSize(1920, 800)) + MainWindow.setMaximumSize(QtCore.QSize(1590, 800)) MainWindow.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) + icon = QtGui.QIcon() + icon.addPixmap(QtGui.QPixmap("icons/logo.ico"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) + MainWindow.setWindowIcon(icon) MainWindow.setStatusTip("") self.centralwidget = QtWidgets.QWidget(parent=MainWindow) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed) @@ -81,7 +84,7 @@ class Ui_MainWindow(object): self.tableWidget_apparate = QtWidgets.QTableWidget(parent=self.horizontalLayoutWidget_2) self.tableWidget_apparate.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) self.tableWidget_apparate.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.SizeAdjustPolicy.AdjustToContents) - self.tableWidget_apparate.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.SelectedClicked) + self.tableWidget_apparate.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers) self.tableWidget_apparate.setAlternatingRowColors(True) self.tableWidget_apparate.setTextElideMode(QtCore.Qt.TextElideMode.ElideMiddle) self.tableWidget_apparate.setObjectName("tableWidget_apparate") @@ -130,7 +133,6 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(12) font.setBold(True) - font.setWeight(75) self.app_group_box.setFont(font) self.app_group_box.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeading|QtCore.Qt.AlignmentFlag.AlignLeft|QtCore.Qt.AlignmentFlag.AlignVCenter) self.app_group_box.setCheckable(False) @@ -140,7 +142,6 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(10) font.setBold(False) - font.setWeight(50) font.setKerning(False) self.dokument_list.setFont(font) self.dokument_list.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) @@ -189,7 +190,6 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(9) font.setBold(False) - font.setWeight(50) self.label_5.setFont(font) self.label_5.setObjectName("label_5") self.sem_winter = QtWidgets.QRadioButton(parent=self.frame) @@ -197,7 +197,6 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(9) font.setBold(False) - font.setWeight(50) self.sem_winter.setFont(font) self.sem_winter.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) self.sem_winter.setObjectName("sem_winter") @@ -206,7 +205,6 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(9) font.setBold(False) - font.setWeight(50) self.label_4.setFont(font) self.label_4.setObjectName("label_4") self.drpdwn_app_nr = QtWidgets.QComboBox(parent=self.frame) @@ -214,7 +212,6 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(9) font.setBold(False) - font.setWeight(50) self.drpdwn_app_nr.setFont(font) self.drpdwn_app_nr.setInputMethodHints(QtCore.Qt.InputMethodHint.ImhDigitsOnly) self.drpdwn_app_nr.setEditable(True) @@ -224,7 +221,6 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(9) font.setBold(False) - font.setWeight(50) self.app_name.setFont(font) self.app_name.setFocusPolicy(QtCore.Qt.FocusPolicy.StrongFocus) self.app_name.setObjectName("app_name") @@ -233,7 +229,6 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(9) font.setBold(False) - font.setWeight(50) self.sem_sommer.setFont(font) self.sem_sommer.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) self.sem_sommer.setObjectName("sem_sommer") @@ -242,7 +237,6 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(9) font.setBold(False) - font.setWeight(50) self.label_3.setFont(font) self.label_3.setObjectName("label_3") self.label_6 = QtWidgets.QLabel(parent=self.frame) @@ -250,7 +244,6 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(9) font.setBold(False) - font.setWeight(50) self.label_6.setFont(font) self.label_6.setObjectName("label_6") self.sem_year = QtWidgets.QLineEdit(parent=self.frame) @@ -258,7 +251,6 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(9) font.setBold(False) - font.setWeight(50) self.sem_year.setFont(font) self.sem_year.setFocusPolicy(QtCore.Qt.FocusPolicy.StrongFocus) self.sem_year.setMaxLength(4) @@ -268,7 +260,6 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(9) font.setBold(False) - font.setWeight(50) self.label_2.setFont(font) self.label_2.setObjectName("label_2") self.btn_apparat_save = QtWidgets.QPushButton(parent=self.frame) @@ -276,7 +267,6 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(9) font.setBold(False) - font.setWeight(50) self.btn_apparat_save.setFont(font) self.btn_apparat_save.setObjectName("btn_apparat_save") self.btn_apparat_apply = QtWidgets.QPushButton(parent=self.frame) @@ -284,7 +274,6 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(9) font.setBold(False) - font.setWeight(50) self.btn_apparat_apply.setFont(font) self.btn_apparat_apply.setObjectName("btn_apparat_apply") self.check_eternal_app = QtWidgets.QCheckBox(parent=self.frame) @@ -292,7 +281,6 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(9) font.setBold(False) - font.setWeight(50) self.check_eternal_app.setFont(font) self.check_eternal_app.setObjectName("check_eternal_app") self.label_8 = QtWidgets.QLabel(parent=self.frame) @@ -300,7 +288,6 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(9) font.setBold(False) - font.setWeight(50) self.label_8.setFont(font) self.label_8.setObjectName("label_8") self.prof_mail = QtWidgets.QLineEdit(parent=self.frame) @@ -308,7 +295,6 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(9) font.setBold(False) - font.setWeight(50) self.prof_mail.setFont(font) self.prof_mail.setInputMethodHints(QtCore.Qt.InputMethodHint.ImhEmailCharactersOnly) self.prof_mail.setMaxLength(200) @@ -319,7 +305,6 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(9) font.setBold(False) - font.setWeight(50) self.label_9.setFont(font) self.label_9.setObjectName("label_9") self.prof_tel_nr = QtWidgets.QLineEdit(parent=self.frame) @@ -327,7 +312,6 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(9) font.setBold(False) - font.setWeight(50) self.prof_tel_nr.setFont(font) self.prof_tel_nr.setInputMethodHints(QtCore.Qt.InputMethodHint.ImhDigitsOnly) self.prof_tel_nr.setPlaceholderText("") @@ -337,7 +321,6 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(9) font.setBold(False) - font.setWeight(50) self.label_10.setFont(font) self.label_10.setObjectName("label_10") self.drpdwn_prof_name = QtWidgets.QComboBox(parent=self.frame) @@ -345,7 +328,6 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(8) font.setBold(False) - font.setWeight(50) self.drpdwn_prof_name.setFont(font) self.drpdwn_prof_name.setFocusPolicy(QtCore.Qt.FocusPolicy.StrongFocus) self.drpdwn_prof_name.setEditable(True) @@ -358,7 +340,6 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(9) font.setBold(False) - font.setWeight(50) self.mail_mand.setFont(font) self.mail_mand.setObjectName("mail_mand") self.telnr_mand = QtWidgets.QLabel(parent=self.frame) @@ -366,7 +347,6 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(9) font.setBold(False) - font.setWeight(50) self.telnr_mand.setFont(font) self.telnr_mand.setObjectName("telnr_mand") self.profname_mand = QtWidgets.QLabel(parent=self.frame) @@ -374,7 +354,6 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(9) font.setBold(False) - font.setWeight(50) self.profname_mand.setFont(font) self.profname_mand.setObjectName("profname_mand") self.appname_mand = QtWidgets.QLabel(parent=self.frame) @@ -382,7 +361,6 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(9) font.setBold(False) - font.setWeight(50) self.appname_mand.setFont(font) self.appname_mand.setObjectName("appname_mand") self.fach_mand = QtWidgets.QLabel(parent=self.frame) @@ -390,7 +368,6 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(9) font.setBold(False) - font.setWeight(50) self.fach_mand.setFont(font) self.fach_mand.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) self.fach_mand.setObjectName("fach_mand") @@ -399,7 +376,6 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(9) font.setBold(False) - font.setWeight(50) self._mand.setFont(font) self._mand.setObjectName("_mand") self.btn_add_document = QtWidgets.QPushButton(parent=self.frame) @@ -407,7 +383,6 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(9) font.setBold(False) - font.setWeight(50) self.btn_add_document.setFont(font) self.btn_add_document.setObjectName("btn_add_document") self.btn_open_document = QtWidgets.QPushButton(parent=self.frame) @@ -415,7 +390,6 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(9) font.setBold(False) - font.setWeight(50) self.btn_open_document.setFont(font) self.btn_open_document.setObjectName("btn_open_document") self.check_file = QtWidgets.QPushButton(parent=self.frame) @@ -423,7 +397,6 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(9) font.setBold(False) - font.setWeight(50) self.check_file.setFont(font) self.check_file.setObjectName("check_file") self.formLayoutWidget_2 = QtWidgets.QWidget(parent=self.frame) @@ -436,7 +409,6 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(9) font.setBold(False) - font.setWeight(50) self.label_12.setFont(font) self.label_12.setObjectName("label_12") self.formLayout_3.setWidget(0, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_12) @@ -444,19 +416,24 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(9) font.setBold(False) - font.setWeight(50) self.prof_id_adis.setFont(font) + self.prof_id_adis.setInputMethodHints(QtCore.Qt.InputMethodHint.ImhPreferNumbers) + self.prof_id_adis.setText("") self.prof_id_adis.setObjectName("prof_id_adis") self.formLayout_3.setWidget(0, QtWidgets.QFormLayout.ItemRole.FieldRole, self.prof_id_adis) self.label_13 = QtWidgets.QLabel(parent=self.formLayoutWidget_2) font = QtGui.QFont() font.setPointSize(9) font.setBold(False) - font.setWeight(50) self.label_13.setFont(font) self.label_13.setObjectName("label_13") self.formLayout_3.setWidget(1, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_13) self.apparat_id_adis = QtWidgets.QLineEdit(parent=self.formLayoutWidget_2) + font = QtGui.QFont() + font.setPointSize(9) + font.setBold(False) + self.apparat_id_adis.setFont(font) + self.apparat_id_adis.setInputMethodHints(QtCore.Qt.InputMethodHint.ImhPreferNumbers) self.apparat_id_adis.setObjectName("apparat_id_adis") self.formLayout_3.setWidget(1, QtWidgets.QFormLayout.ItemRole.FieldRole, self.apparat_id_adis) self.check_send_mail = QtWidgets.QCheckBox(parent=self.frame) @@ -464,7 +441,6 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(9) font.setBold(False) - font.setWeight(50) self.check_send_mail.setFont(font) self.check_send_mail.setObjectName("check_send_mail") self.frame_3 = QtWidgets.QFrame(parent=self.frame) @@ -483,7 +459,6 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(9) font.setBold(False) - font.setWeight(50) self.app_fach.setFont(font) self.app_fach.setObjectName("app_fach") self.gridLayout_6.addWidget(self.app_fach, 0, 0, 1, 1) @@ -494,7 +469,6 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(9) font.setBold(False) - font.setWeight(50) self.prof_title.setFont(font) self.prof_title.setFocusPolicy(QtCore.Qt.FocusPolicy.StrongFocus) self.prof_title.setObjectName("prof_title") @@ -569,7 +543,6 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(11) font.setBold(True) - font.setWeight(75) self.label.setFont(font) self.label.setObjectName("label") self.gridLayout_2.addWidget(self.label, 2, 0, 1, 1) @@ -797,6 +770,7 @@ class Ui_MainWindow(object): self.tableWidget = QtWidgets.QTableWidget(parent=self.table) self.tableWidget.setGeometry(QtCore.QRect(0, 0, 761, 391)) self.tableWidget.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) + self.tableWidget.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.CustomContextMenu) self.tableWidget.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers) self.tableWidget.setObjectName("tableWidget") self.tableWidget.setColumnCount(5) @@ -1071,7 +1045,6 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(8) font.setBold(False) - font.setWeight(50) self.appdata_check.setFont(font) self.appdata_check.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) self.appdata_check.setObjectName("appdata_check") @@ -1080,16 +1053,14 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(8) font.setBold(False) - font.setWeight(50) self.media_check.setFont(font) self.media_check.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) self.media_check.setObjectName("media_check") self.ids_check = QtWidgets.QCheckBox(parent=self.groupBox_2) - self.ids_check.setGeometry(QtCore.QRect(20, 140, 241, 41)) + self.ids_check.setGeometry(QtCore.QRect(20, 110, 241, 41)) font = QtGui.QFont() font.setPointSize(8) font.setBold(False) - font.setWeight(50) self.ids_check.setFont(font) self.ids_check.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) self.ids_check.setObjectName("ids_check") @@ -1108,7 +1079,6 @@ class Ui_MainWindow(object): font.setBold(False) font.setItalic(False) font.setUnderline(False) - font.setWeight(50) font.setKerning(True) font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferDefault) self.media_checked.setFont(font) @@ -1121,7 +1091,6 @@ class Ui_MainWindow(object): font.setBold(False) font.setItalic(False) font.setUnderline(False) - font.setWeight(50) font.setKerning(True) font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferDefault) self.media_edited_check.setFont(font) @@ -1134,7 +1103,6 @@ class Ui_MainWindow(object): font.setBold(False) font.setItalic(False) font.setUnderline(False) - font.setWeight(50) font.setKerning(True) font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferDefault) self.app_created.setFont(font) @@ -1147,7 +1115,6 @@ class Ui_MainWindow(object): font.setBold(False) font.setItalic(False) font.setUnderline(False) - font.setWeight(50) font.setKerning(True) font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferDefault) self.btn_copy_adis_command.setFont(font) @@ -1155,9 +1122,9 @@ class Ui_MainWindow(object): self.btn_copy_adis_command.setWhatsThis("") self.btn_copy_adis_command.setAccessibleDescription("") self.btn_copy_adis_command.setAutoFillBackground(False) - icon = QtGui.QIcon() - icon.addPixmap(QtGui.QPixmap("c:\\Users\\aky547\\GitHub\\SemesterapparatsManager\\src\\ui\\../../../.designer/backup/icons/information.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) - self.btn_copy_adis_command.setIcon(icon) + icon1 = QtGui.QIcon() + icon1.addPixmap(QtGui.QPixmap("c:\\Users\\aky547\\GitHub\\SemesterapparatsManager\\src\\ui\\../../../../../../.designer/backup/icons/information.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) + self.btn_copy_adis_command.setIcon(icon1) self.btn_copy_adis_command.setCheckable(False) self.btn_copy_adis_command.setChecked(False) self.btn_copy_adis_command.setAutoDefault(False) @@ -1205,7 +1172,7 @@ class Ui_MainWindow(object): self.label_total_day_messages.setObjectName("label_total_day_messages") MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(parent=MainWindow) - self.menubar.setGeometry(QtCore.QRect(0, 0, 1601, 30)) + self.menubar.setGeometry(QtCore.QRect(0, 0, 1589, 22)) self.menubar.setObjectName("menubar") self.menuDatei = QtWidgets.QMenu(parent=self.menubar) self.menuDatei.setObjectName("menuDatei") @@ -1235,30 +1202,30 @@ class Ui_MainWindow(object): self.retranslateUi(MainWindow) self.tabWidget.setCurrentIndex(0) - self.tabWidget_2.setCurrentIndex(1) - self.stackedWidget_4.setCurrentIndex(1) - self.tabWidget_3.setCurrentIndex(1) + self.tabWidget_2.setCurrentIndex(0) + self.stackedWidget_4.setCurrentIndex(0) + self.tabWidget_3.setCurrentIndex(0) QtCore.QMetaObject.connectSlotsByName(MainWindow) - MainWindow.setTabOrder(self.drpdwn_app_nr, self.drpdwn_prof_name) + MainWindow.setTabOrder(self.drpdwn_app_nr, self.prof_title) + MainWindow.setTabOrder(self.prof_title, self.drpdwn_prof_name) MainWindow.setTabOrder(self.drpdwn_prof_name, self.prof_mail) MainWindow.setTabOrder(self.prof_mail, self.prof_tel_nr) MainWindow.setTabOrder(self.prof_tel_nr, self.app_name) - MainWindow.setTabOrder(self.app_name, self.sem_year) + MainWindow.setTabOrder(self.app_name, self.app_fach) + MainWindow.setTabOrder(self.app_fach, self.sem_year) MainWindow.setTabOrder(self.sem_year, self.check_eternal_app) MainWindow.setTabOrder(self.check_eternal_app, self.btn_add_document) MainWindow.setTabOrder(self.btn_add_document, self.btn_open_document) MainWindow.setTabOrder(self.btn_open_document, self.check_file) - MainWindow.setTabOrder(self.check_file, self.btn_apparat_save) + MainWindow.setTabOrder(self.check_file, self.check_send_mail) + MainWindow.setTabOrder(self.check_send_mail, self.btn_apparat_save) MainWindow.setTabOrder(self.btn_apparat_save, self.btn_apparat_apply) - MainWindow.setTabOrder(self.btn_apparat_apply, self.check_send_mail) - MainWindow.setTabOrder(self.check_send_mail, self.chkbx_show_del_media) - MainWindow.setTabOrder(self.chkbx_show_del_media, self.btn_reserve) - MainWindow.setTabOrder(self.btn_reserve, self.prof_id_adis) + MainWindow.setTabOrder(self.btn_apparat_apply, self.prof_id_adis) MainWindow.setTabOrder(self.prof_id_adis, self.apparat_id_adis) MainWindow.setTabOrder(self.apparat_id_adis, self.tabWidget_2) - MainWindow.setTabOrder(self.tabWidget_2, self.btn_del_select_apparats) - MainWindow.setTabOrder(self.btn_del_select_apparats, self.tabWidget_3) - MainWindow.setTabOrder(self.tabWidget_3, self.select_action_box) + MainWindow.setTabOrder(self.tabWidget_2, self.seach_by_signature) + MainWindow.setTabOrder(self.seach_by_signature, self.search_by_title) + MainWindow.setTabOrder(self.search_by_title, self.select_action_box) MainWindow.setTabOrder(self.select_action_box, self.user_create_frame_username) MainWindow.setTabOrder(self.user_create_frame_username, self.user_frame_userrole) MainWindow.setTabOrder(self.user_frame_userrole, self.user_create_frame_password) @@ -1272,9 +1239,7 @@ class Ui_MainWindow(object): MainWindow.setTabOrder(self.user_edit_frame_new_password, self.update_user) MainWindow.setTabOrder(self.update_user, self.edit_faculty_member_title) MainWindow.setTabOrder(self.edit_faculty_member_title, self.edit_faculty_member_select_member) - MainWindow.setTabOrder(self.edit_faculty_member_select_member, self.faculty_member_old_telnr) - MainWindow.setTabOrder(self.faculty_member_old_telnr, self.faculty_member_oldmail) - MainWindow.setTabOrder(self.faculty_member_oldmail, self.edit_faculty_member_new_title) + MainWindow.setTabOrder(self.edit_faculty_member_select_member, self.edit_faculty_member_new_title) MainWindow.setTabOrder(self.edit_faculty_member_new_title, self.edit_faculty_member_new_surname) MainWindow.setTabOrder(self.edit_faculty_member_new_surname, self.user_faculty_member_new_name) MainWindow.setTabOrder(self.user_faculty_member_new_name, self.user_faculty_member_new_mail) @@ -1290,6 +1255,12 @@ class Ui_MainWindow(object): MainWindow.setTabOrder(self.box_dauerapp, self.box_appnrs) MainWindow.setTabOrder(self.box_appnrs, self.btn_copy_adis_command) MainWindow.setTabOrder(self.btn_copy_adis_command, self.spin_select_message) + MainWindow.setTabOrder(self.spin_select_message, self.chkbx_show_del_media) + MainWindow.setTabOrder(self.chkbx_show_del_media, self.btn_reserve) + MainWindow.setTabOrder(self.btn_reserve, self.book_search) + MainWindow.setTabOrder(self.book_search, self.btn_del_select_apparats) + MainWindow.setTabOrder(self.btn_del_select_apparats, self.tabWidget_3) + MainWindow.setTabOrder(self.tabWidget_3, self.book_search_result) def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate @@ -1298,7 +1269,7 @@ class Ui_MainWindow(object): self.load_app.setText(_translate("MainWindow", "App. aufrufen")) self.create_new_app.setText(_translate("MainWindow", "neu. App anlegen")) self.cancel_active_selection.setText(_translate("MainWindow", "Auswahl abbrechen")) - self.tableWidget_apparate.setSortingEnabled(True) + self.tableWidget_apparate.setSortingEnabled(False) item = self.tableWidget_apparate.horizontalHeaderItem(0) item.setText(_translate("MainWindow", "AppNr")) item = self.tableWidget_apparate.horizontalHeaderItem(1) diff --git a/src/ui/Ui_setupwizard.py b/src/ui/Ui_setupwizard.py index 5da1dbf..d2e4689 100644 --- a/src/ui/Ui_setupwizard.py +++ b/src/ui/Ui_setupwizard.py @@ -84,4 +84,3 @@ class Ui_Wizard(object): self.label_3.setText(_translate("Wizard", "Save Path")) self.save_path.setPlaceholderText(_translate("Wizard", "~/Desktop/SemapFiles")) self.btn_save_path_select.setText(_translate("Wizard", "...")) - diff --git a/src/ui/__init__.py b/src/ui/__init__.py index 93f0297..53fff92 100644 --- a/src/ui/__init__.py +++ b/src/ui/__init__.py @@ -8,20 +8,15 @@ from .dialogs import ( fileparser_ui, login_ui, medienadder_ui, + new_subject_ui, parsed_titles_ui, popus_confirm, reminder_ui, settings_ui, - new_subject_ui, ) from .Ui_semesterapparat_ui import Ui_MainWindow as Ui_Semesterapparat from .Ui_setupwizard import Ui_Wizard as SetupWizard -from .widgets import ( - FilePicker, - GraphWidget, - Message_Widget, - StatusWidget, -) +from .widgets import FilePicker, GraphWidget, Message_Widget, StatusWidget path = pathlib.Path(__file__).parent.absolute() # from .mainwindow import Ui_MainWindow as Ui_MainWindow diff --git a/src/ui/icons/email.svg b/src/ui/icons/email.svg new file mode 100644 index 0000000..d8ce780 --- /dev/null +++ b/src/ui/icons/email.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/icons/locked.svg b/src/ui/icons/locked.svg new file mode 100644 index 0000000..014079e --- /dev/null +++ b/src/ui/icons/locked.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/icons/logo.ico b/src/ui/icons/logo.ico new file mode 100644 index 0000000000000000000000000000000000000000..4dfec92884a2c07de86f6bb8c48860378222215f GIT binary patch literal 270398 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 \ No newline at end of file diff --git a/src/ui/mainwindow.py b/src/ui/mainwindow.py index e6152b0..89cf610 100644 --- a/src/ui/mainwindow.py +++ b/src/ui/mainwindow.py @@ -1,28 +1,35 @@ # Form implementation generated from reading ui file 'untitled.ui' # -# Created by: PySide6 UI code generator 6.3.1 +# Created by: PyQt6 UI code generator 6.3.1 # # WARNING: Any manual changes made to this file will be lost when pyuic6 is # run again. Do not edit this file unless you know what you are doing. -from PySide6 import QtCore, QtGui, QtWidgets +from PyQt6 import QtCore, QtGui, QtWidgets class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") MainWindow.resize(1280, 720) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) + sizePolicy = QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.Policy.Expanding, + QtWidgets.QSizePolicy.Policy.Expanding, + ) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(MainWindow.sizePolicy().hasHeightForWidth()) MainWindow.setSizePolicy(sizePolicy) self.centralwidget = QtWidgets.QWidget(MainWindow) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed) + sizePolicy = QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed + ) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.centralwidget.sizePolicy().hasHeightForWidth()) + sizePolicy.setHeightForWidth( + self.centralwidget.sizePolicy().hasHeightForWidth() + ) self.centralwidget.setSizePolicy(sizePolicy) self.centralwidget.setObjectName("centralwidget") self.verticalLayoutWidget = QtWidgets.QWidget(self.centralwidget) @@ -38,7 +45,10 @@ class Ui_MainWindow(object): self.tabWidget = QtWidgets.QTabWidget(self.verticalLayoutWidget) self.tabWidget.setObjectName("tabWidget") self.tab = QtWidgets.QWidget() - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred) + sizePolicy = QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.Policy.Preferred, + QtWidgets.QSizePolicy.Policy.Preferred, + ) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.tab.sizePolicy().hasHeightForWidth()) @@ -54,7 +64,12 @@ class Ui_MainWindow(object): self.formLayout.setObjectName("formLayout") self.verticalLayout_2 = QtWidgets.QVBoxLayout() self.verticalLayout_2.setObjectName("verticalLayout_2") - spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) + spacerItem = QtWidgets.QSpacerItem( + 20, + 40, + QtWidgets.QSizePolicy.Policy.Minimum, + QtWidgets.QSizePolicy.Policy.Expanding, + ) self.verticalLayout_2.addItem(spacerItem) self.load_app = QtWidgets.QPushButton(self.horizontalLayoutWidget_2) self.load_app.setObjectName("load_app") @@ -62,10 +77,19 @@ class Ui_MainWindow(object): self.create_new_app = QtWidgets.QPushButton(self.horizontalLayoutWidget_2) self.create_new_app.setObjectName("create_new_app") self.verticalLayout_2.addWidget(self.create_new_app) - spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) + spacerItem1 = QtWidgets.QSpacerItem( + 20, + 40, + QtWidgets.QSizePolicy.Policy.Minimum, + QtWidgets.QSizePolicy.Policy.Expanding, + ) self.verticalLayout_2.addItem(spacerItem1) - self.formLayout.setLayout(0, QtWidgets.QFormLayout.ItemRole.LabelRole, self.verticalLayout_2) - self.tableWidget_apparate = QtWidgets.QTableWidget(self.horizontalLayoutWidget_2) + self.formLayout.setLayout( + 0, QtWidgets.QFormLayout.ItemRole.LabelRole, self.verticalLayout_2 + ) + self.tableWidget_apparate = QtWidgets.QTableWidget( + self.horizontalLayoutWidget_2 + ) self.tableWidget_apparate.setObjectName("tableWidget_apparate") self.tableWidget_apparate.setColumnCount(4) self.tableWidget_apparate.setRowCount(0) @@ -77,13 +101,19 @@ class Ui_MainWindow(object): self.tableWidget_apparate.setHorizontalHeaderItem(2, item) item = QtWidgets.QTableWidgetItem() self.tableWidget_apparate.setHorizontalHeaderItem(3, item) - self.formLayout.setWidget(0, QtWidgets.QFormLayout.ItemRole.FieldRole, self.tableWidget_apparate) + self.formLayout.setWidget( + 0, QtWidgets.QFormLayout.ItemRole.FieldRole, self.tableWidget_apparate + ) self.horizontalLayout_3 = QtWidgets.QHBoxLayout() self.horizontalLayout_3.setObjectName("horizontalLayout_3") - self.formLayout.setLayout(2, QtWidgets.QFormLayout.ItemRole.LabelRole, self.horizontalLayout_3) + self.formLayout.setLayout( + 2, QtWidgets.QFormLayout.ItemRole.LabelRole, self.horizontalLayout_3 + ) self.horizontalLayout_4 = QtWidgets.QHBoxLayout() self.horizontalLayout_4.setObjectName("horizontalLayout_4") - self.formLayout.setLayout(1, QtWidgets.QFormLayout.ItemRole.FieldRole, self.horizontalLayout_4) + self.formLayout.setLayout( + 1, QtWidgets.QFormLayout.ItemRole.FieldRole, self.horizontalLayout_4 + ) self.horizontalLayout_2.addLayout(self.formLayout) self.line = QtWidgets.QFrame(self.tab) self.line.setGeometry(QtCore.QRect(0, 160, 1261, 21)) @@ -118,17 +148,26 @@ class Ui_MainWindow(object): self.label.setObjectName("label") self.gridLayout_2.addWidget(self.label, 1, 0, 1, 1) self.app_group_box = QtWidgets.QGroupBox(self.gridLayoutWidget_2) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Expanding) + sizePolicy = QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.Policy.Preferred, + QtWidgets.QSizePolicy.Policy.Expanding, + ) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.app_group_box.sizePolicy().hasHeightForWidth()) + sizePolicy.setHeightForWidth( + self.app_group_box.sizePolicy().hasHeightForWidth() + ) self.app_group_box.setSizePolicy(sizePolicy) font = QtGui.QFont() font.setPointSize(12) font.setBold(True) font.setWeight(75) self.app_group_box.setFont(font) - self.app_group_box.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeading|QtCore.Qt.AlignmentFlag.AlignLeft|QtCore.Qt.AlignmentFlag.AlignVCenter) + self.app_group_box.setAlignment( + QtCore.Qt.AlignmentFlag.AlignLeading + | QtCore.Qt.AlignmentFlag.AlignLeft + | QtCore.Qt.AlignmentFlag.AlignVCenter + ) self.app_group_box.setCheckable(False) self.app_group_box.setObjectName("app_group_box") self.tableWidget = QtWidgets.QTableWidget(self.app_group_box) @@ -142,7 +181,9 @@ class Ui_MainWindow(object): self.tableWidget.setHorizontalHeaderItem(1, item) self.frame = QtWidgets.QFrame(self.app_group_box) self.frame.setGeometry(QtCore.QRect(10, 30, 731, 151)) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed) + sizePolicy = QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed + ) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.frame.sizePolicy().hasHeightForWidth()) @@ -304,7 +345,9 @@ class Ui_MainWindow(object): def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) - self.load_app.setToolTip(_translate("MainWindow", "Load the Semesterapparate from the database")) + self.load_app.setToolTip( + _translate("MainWindow", "Load the Semesterapparate from the database") + ) self.load_app.setText(_translate("MainWindow", "App. Laden")) self.create_new_app.setText(_translate("MainWindow", "neu. App anlegen")) item = self.tableWidget_apparate.horizontalHeaderItem(0) @@ -344,14 +387,19 @@ class Ui_MainWindow(object): self.btn_add_document.setText(_translate("MainWindow", "Dokument hinzufügen")) self.btn_open_document.setText(_translate("MainWindow", "Dokument öffnen")) self.toolButton.setText(_translate("MainWindow", "...")) - self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("MainWindow", "Tab 1")) - self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("MainWindow", "Tab 2")) + self.tabWidget.setTabText( + self.tabWidget.indexOf(self.tab), _translate("MainWindow", "Tab 1") + ) + self.tabWidget.setTabText( + self.tabWidget.indexOf(self.tab_2), _translate("MainWindow", "Tab 2") + ) self.menuDatei.setTitle(_translate("MainWindow", "Datei")) self.menuEinstellungen.setTitle(_translate("MainWindow", "Einstellungen")) if __name__ == "__main__": import sys + app = QtWidgets.QApplication(sys.argv) MainWindow = QtWidgets.QMainWindow() ui = Ui_MainWindow() diff --git a/src/ui/resources_rc.py b/src/ui/resources_rc.py index 91cd916..47c9635 100644 --- a/src/ui/resources_rc.py +++ b/src/ui/resources_rc.py @@ -3,7 +3,7 @@ # Created by: The Resource Compiler for Qt version 6.6.2 # WARNING! All changes made in this file will be lost! -from PySide6 import QtCore +from PyQt6 import QtCore qt_resource_data = b"\ \x00\x00\x00\xde\ @@ -131,25 +131,32 @@ qt_resource_struct = b"\ \x00\x00\x00b\x00\x02\x00\x00\x00\x01\x00\x00\x00\x06\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\xd0\x00\x00\x00\x00\x00\x01\x00\x00\x02\xdb\ -\x00\x00\x01\x8d\xabem#\ +\x00\x00\x01\x8e,\x5c5%\ \x00\x00\x00b\x00\x02\x00\x00\x00\x01\x00\x00\x00\x08\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x8e\x00\x00\x00\x00\x00\x01\x00\x00\x00\xe2\ -\x00\x00\x01\x8d\xabem!\ +\x00\x00\x01\x8e,\x5c5\x22\ \x00\x00\x00b\x00\x02\x00\x00\x00\x01\x00\x00\x00\x0a\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\xb2\x00\x00\x00\x00\x00\x01\x00\x00\x01\xdb\ -\x00\x00\x01\x8d\xabem!\ +\x00\x00\x01\x8e,\x5c5!\ \x00\x00\x00b\x00\x02\x00\x00\x00\x01\x00\x00\x00\x0c\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00r\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ -\x00\x00\x01\x8d\xabem#\ +\x00\x00\x01\x8e,\x5c5'\ " + def qInitResources(): - QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + QtCore.qRegisterResourceData( + 0x03, qt_resource_struct, qt_resource_name, qt_resource_data + ) + def qCleanupResources(): - QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + QtCore.qUnregisterResourceData( + 0x03, qt_resource_struct, qt_resource_name, qt_resource_data + ) + qInitResources() diff --git a/src/ui/sap.py b/src/ui/sap.py index 6732450..5bfbc06 100644 --- a/src/ui/sap.py +++ b/src/ui/sap.py @@ -1,28 +1,35 @@ # Form implementation generated from reading ui file 'ui/semesterapparat_ui.ui' # -# Created by: PySide6 UI code generator 6.3.1 +# Created by: PyQt6 UI code generator 6.3.1 # # WARNING: Any manual changes made to this file will be lost when pyuic6 is # run again. Do not edit this file unless you know what you are doing. -from PySide6 import QtCore, QtGui, QtWidgets +from PyQt6 import QtCore, QtGui, QtWidgets class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") MainWindow.resize(1280, 747) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) + sizePolicy = QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.Policy.Expanding, + QtWidgets.QSizePolicy.Policy.Expanding, + ) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(MainWindow.sizePolicy().hasHeightForWidth()) MainWindow.setSizePolicy(sizePolicy) self.centralwidget = QtWidgets.QWidget(MainWindow) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed) + sizePolicy = QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed + ) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.centralwidget.sizePolicy().hasHeightForWidth()) + sizePolicy.setHeightForWidth( + self.centralwidget.sizePolicy().hasHeightForWidth() + ) self.centralwidget.setSizePolicy(sizePolicy) self.centralwidget.setObjectName("centralwidget") self.verticalLayoutWidget = QtWidgets.QWidget(self.centralwidget) @@ -39,7 +46,10 @@ class Ui_MainWindow(object): self.tabWidget.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) self.tabWidget.setObjectName("tabWidget") self.tab = QtWidgets.QWidget() - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred) + sizePolicy = QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.Policy.Preferred, + QtWidgets.QSizePolicy.Policy.Preferred, + ) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.tab.sizePolicy().hasHeightForWidth()) @@ -55,7 +65,12 @@ class Ui_MainWindow(object): self.formLayout.setObjectName("formLayout") self.verticalLayout_2 = QtWidgets.QVBoxLayout() self.verticalLayout_2.setObjectName("verticalLayout_2") - spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) + spacerItem = QtWidgets.QSpacerItem( + 20, + 40, + QtWidgets.QSizePolicy.Policy.Minimum, + QtWidgets.QSizePolicy.Policy.Expanding, + ) self.verticalLayout_2.addItem(spacerItem) self.load_app = QtWidgets.QPushButton(self.horizontalLayoutWidget_2) self.load_app.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) @@ -65,12 +80,23 @@ class Ui_MainWindow(object): self.create_new_app.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) self.create_new_app.setObjectName("create_new_app") self.verticalLayout_2.addWidget(self.create_new_app) - spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) + spacerItem1 = QtWidgets.QSpacerItem( + 20, + 40, + QtWidgets.QSizePolicy.Policy.Minimum, + QtWidgets.QSizePolicy.Policy.Expanding, + ) self.verticalLayout_2.addItem(spacerItem1) - self.formLayout.setLayout(0, QtWidgets.QFormLayout.ItemRole.LabelRole, self.verticalLayout_2) - self.tableWidget_apparate = QtWidgets.QTableWidget(self.horizontalLayoutWidget_2) + self.formLayout.setLayout( + 0, QtWidgets.QFormLayout.ItemRole.LabelRole, self.verticalLayout_2 + ) + self.tableWidget_apparate = QtWidgets.QTableWidget( + self.horizontalLayoutWidget_2 + ) self.tableWidget_apparate.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) - self.tableWidget_apparate.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers) + self.tableWidget_apparate.setEditTriggers( + QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers + ) self.tableWidget_apparate.setObjectName("tableWidget_apparate") self.tableWidget_apparate.setColumnCount(4) self.tableWidget_apparate.setRowCount(0) @@ -82,13 +108,19 @@ class Ui_MainWindow(object): self.tableWidget_apparate.setHorizontalHeaderItem(2, item) item = QtWidgets.QTableWidgetItem() self.tableWidget_apparate.setHorizontalHeaderItem(3, item) - self.formLayout.setWidget(0, QtWidgets.QFormLayout.ItemRole.FieldRole, self.tableWidget_apparate) + self.formLayout.setWidget( + 0, QtWidgets.QFormLayout.ItemRole.FieldRole, self.tableWidget_apparate + ) self.horizontalLayout_3 = QtWidgets.QHBoxLayout() self.horizontalLayout_3.setObjectName("horizontalLayout_3") - self.formLayout.setLayout(2, QtWidgets.QFormLayout.ItemRole.LabelRole, self.horizontalLayout_3) + self.formLayout.setLayout( + 2, QtWidgets.QFormLayout.ItemRole.LabelRole, self.horizontalLayout_3 + ) self.horizontalLayout_4 = QtWidgets.QHBoxLayout() self.horizontalLayout_4.setObjectName("horizontalLayout_4") - self.formLayout.setLayout(1, QtWidgets.QFormLayout.ItemRole.FieldRole, self.horizontalLayout_4) + self.formLayout.setLayout( + 1, QtWidgets.QFormLayout.ItemRole.FieldRole, self.horizontalLayout_4 + ) self.horizontalLayout_2.addLayout(self.formLayout) self.line = QtWidgets.QFrame(self.tab) self.line.setGeometry(QtCore.QRect(0, 160, 1261, 21)) @@ -102,17 +134,26 @@ class Ui_MainWindow(object): self.gridLayout_2.setContentsMargins(0, 0, 0, 0) self.gridLayout_2.setObjectName("gridLayout_2") self.app_group_box = QtWidgets.QGroupBox(self.gridLayoutWidget_2) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Expanding) + sizePolicy = QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.Policy.Preferred, + QtWidgets.QSizePolicy.Policy.Expanding, + ) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.app_group_box.sizePolicy().hasHeightForWidth()) + sizePolicy.setHeightForWidth( + self.app_group_box.sizePolicy().hasHeightForWidth() + ) self.app_group_box.setSizePolicy(sizePolicy) font = QtGui.QFont() font.setPointSize(12) font.setBold(True) font.setWeight(75) self.app_group_box.setFont(font) - self.app_group_box.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeading|QtCore.Qt.AlignmentFlag.AlignLeft|QtCore.Qt.AlignmentFlag.AlignVCenter) + self.app_group_box.setAlignment( + QtCore.Qt.AlignmentFlag.AlignLeading + | QtCore.Qt.AlignmentFlag.AlignLeft + | QtCore.Qt.AlignmentFlag.AlignVCenter + ) self.app_group_box.setCheckable(False) self.app_group_box.setObjectName("app_group_box") self.dokument_list = QtWidgets.QTableWidget(self.app_group_box) @@ -121,7 +162,9 @@ class Ui_MainWindow(object): font.setKerning(False) self.dokument_list.setFont(font) self.dokument_list.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) - self.dokument_list.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.SingleSelection) + self.dokument_list.setSelectionMode( + QtWidgets.QAbstractItemView.SelectionMode.SingleSelection + ) self.dokument_list.setObjectName("dokument_list") self.dokument_list.setColumnCount(4) self.dokument_list.setRowCount(0) @@ -148,7 +191,9 @@ class Ui_MainWindow(object): self.frame = QtWidgets.QFrame(self.app_group_box) self.frame.setEnabled(True) self.frame.setGeometry(QtCore.QRect(10, 30, 731, 151)) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed) + sizePolicy = QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed + ) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.frame.sizePolicy().hasHeightForWidth()) @@ -331,7 +376,9 @@ class Ui_MainWindow(object): self.drpdwn_prof_name.setFocusPolicy(QtCore.Qt.FocusPolicy.StrongFocus) self.drpdwn_prof_name.setEditable(True) self.drpdwn_prof_name.setCurrentText("") - self.drpdwn_prof_name.setInsertPolicy(QtWidgets.QComboBox.InsertPolicy.InsertAlphabetically) + self.drpdwn_prof_name.setInsertPolicy( + QtWidgets.QComboBox.InsertPolicy.InsertAlphabetically + ) self.drpdwn_prof_name.setFrame(True) self.drpdwn_prof_name.setObjectName("drpdwn_prof_name") self.btn_add_document = QtWidgets.QPushButton(self.app_group_box) @@ -445,7 +492,9 @@ class Ui_MainWindow(object): def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) - self.load_app.setToolTip(_translate("MainWindow", "Load the Semesterapparate from the database")) + self.load_app.setToolTip( + _translate("MainWindow", "Load the Semesterapparate from the database") + ) self.load_app.setText(_translate("MainWindow", "App. aufrufen")) self.create_new_app.setText(_translate("MainWindow", "neu. App anlegen")) item = self.tableWidget_apparate.horizontalHeaderItem(0) @@ -483,7 +532,9 @@ class Ui_MainWindow(object): self.btn_open_document.setText(_translate("MainWindow", "Dokument öffnen")) self.toolButton.setText(_translate("MainWindow", "...")) self.label_7.setText(_translate("MainWindow", "Suche")) - self.search_media.setPlaceholderText(_translate("MainWindow", "Buch oder Signatur")) + self.search_media.setPlaceholderText( + _translate("MainWindow", "Buch oder Signatur") + ) self.label.setText(_translate("MainWindow", "Medienliste")) item = self.tableWidget_apparat_media.horizontalHeaderItem(0) item.setText(_translate("MainWindow", "Buchtitel")) @@ -493,17 +544,27 @@ class Ui_MainWindow(object): item.setText(_translate("MainWindow", "Auflage")) item = self.tableWidget_apparat_media.horizontalHeaderItem(3) item.setText(_translate("MainWindow", "Signatur")) - self.search.setToolTip(_translate("MainWindow", "Sucht im Katalog nach allen Medien, die die Apparatsnummer enthalten")) + self.search.setToolTip( + _translate( + "MainWindow", + "Sucht im Katalog nach allen Medien, die die Apparatsnummer enthalten", + ) + ) self.search.setText(_translate("MainWindow", "Suche")) self.add_medium.setText(_translate("MainWindow", "Medium hinzufügen")) - self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("MainWindow", "Tab 1")) - self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("MainWindow", "Tab 2")) + self.tabWidget.setTabText( + self.tabWidget.indexOf(self.tab), _translate("MainWindow", "Tab 1") + ) + self.tabWidget.setTabText( + self.tabWidget.indexOf(self.tab_2), _translate("MainWindow", "Tab 2") + ) self.menuDatei.setTitle(_translate("MainWindow", "Datei")) self.menuEinstellungen.setTitle(_translate("MainWindow", "Einstellungen")) if __name__ == "__main__": import sys + app = QtWidgets.QApplication(sys.argv) MainWindow = QtWidgets.QMainWindow() ui = Ui_MainWindow() diff --git a/src/ui/semesterapparat_ui.ui b/src/ui/semesterapparat_ui.ui index 686133c..28b44a1 100644 --- a/src/ui/semesterapparat_ui.ui +++ b/src/ui/semesterapparat_ui.ui @@ -40,6 +40,10 @@ Semesterapparatsmanagement + + + ../../icons/logo.ico../../icons/logo.ico + @@ -70,7 +74,7 @@ Qt::NoFocus - 1 + 0 @@ -166,7 +170,7 @@ QAbstractScrollArea::AdjustToContents - QAbstractItemView::SelectedClicked + QAbstractItemView::NoEditTriggers true @@ -175,7 +179,7 @@ Qt::ElideMiddle - true + false true @@ -986,6 +990,12 @@ false + + Qt::ImhPreferNumbers + + + + @@ -1002,7 +1012,17 @@ - + + + + 9 + false + + + + Qt::ImhPreferNumbers + + @@ -1402,7 +1422,7 @@ - 1 + 0 @@ -1646,7 +1666,7 @@ - 1 + 0 @@ -1791,6 +1811,9 @@ Qt::NoFocus + + Qt::CustomContextMenu + QAbstractItemView::NoEditTriggers @@ -2613,7 +2636,7 @@ - ../../../../../.designer/backup/icons/information.png../../../../../.designer/backup/icons/information.png + ../../../../../../.designer/backup/icons/information.png../../../../../../.designer/backup/icons/information.png false @@ -2892,6 +2915,8 @@ tabWidget_3 book_search_result - + + + diff --git a/src/ui/semesterapparat_ui_ui.py b/src/ui/semesterapparat_ui_ui.py index fe8c3d5..b5fc69e 100644 --- a/src/ui/semesterapparat_ui_ui.py +++ b/src/ui/semesterapparat_ui_ui.py @@ -8,27 +8,77 @@ ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ -from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, - QMetaObject, QObject, QPoint, QRect, - QSize, QTime, QUrl, Qt) -from PySide6.QtGui import (QWidgetAction, QBrush, QColor, QConicalGradient, - QCursor, QFont, QFontDatabase, QGradient, - QIcon, QImage, QKeySequence, QLinearGradient, - QPainter, QPalette, QPixmap, QRadialGradient, - QTransform) -from PySide6.QtWidgets import (QAbstractItemView, QAbstractScrollArea, QApplication, QCalendarWidget, - QCheckBox, QComboBox, QFormLayout, QFrame, - QGridLayout, QGroupBox, QHBoxLayout, QHeaderView, - QLabel, QLineEdit, QMainWindow, QMenu, - QMenuBar, QPushButton, QRadioButton, QSizePolicy, - QSpacerItem, QSpinBox, QStackedWidget, QStatusBar, - QTabWidget, QTableWidget, QTableWidgetItem, QTextEdit, - QVBoxLayout, QWidget) +from PyQt6.QtCore import ( + QCoreApplication, + QDate, + QDateTime, + QLocale, + QMetaObject, + QObject, + QPoint, + QRect, + QSize, + Qt, + QTime, + QUrl, +) +from PyQt6.QtGui import ( + QBrush, + QColor, + QConicalGradient, + QCursor, + QFont, + QFontDatabase, + QGradient, + QIcon, + QImage, + QKeySequence, + QLinearGradient, + QPainter, + QPalette, + QPixmap, + QRadialGradient, + QTransform, + QWidgetAction, +) +from PyQt6.QtWidgets import ( + QAbstractItemView, + QAbstractScrollArea, + QApplication, + QCalendarWidget, + QCheckBox, + QComboBox, + QFormLayout, + QFrame, + QGridLayout, + QGroupBox, + QHBoxLayout, + QHeaderView, + QLabel, + QLineEdit, + QMainWindow, + QMenu, + QMenuBar, + QPushButton, + QRadioButton, + QSizePolicy, + QSpacerItem, + QSpinBox, + QStackedWidget, + QStatusBar, + QTableWidget, + QTableWidgetItem, + QTabWidget, + QTextEdit, + QVBoxLayout, + QWidget, +) + class Ui_MainWindow(object): def setupUi(self, MainWindow): if not MainWindow.objectName(): - MainWindow.setObjectName(u"MainWindow") + MainWindow.setObjectName("MainWindow") MainWindow.setWindowModality(Qt.WindowModal) MainWindow.setEnabled(True) MainWindow.resize(1601, 800) @@ -41,79 +91,86 @@ class Ui_MainWindow(object): MainWindow.setMaximumSize(QSize(1920, 800)) MainWindow.setContextMenuPolicy(Qt.NoContextMenu) self.actionBeenden = QWidgetAction(MainWindow) - self.actionBeenden.setObjectName(u"actionBeenden") + self.actionBeenden.setObjectName("actionBeenden") self.actionBeenden.setShortcutVisibleInContextMenu(True) self.actionEinstellungen = QWidgetAction(MainWindow) - self.actionEinstellungen.setObjectName(u"actionEinstellungen") + self.actionEinstellungen.setObjectName("actionEinstellungen") self.actionEinstellungen.setShortcutVisibleInContextMenu(True) self.actionDokumentation = QWidgetAction(MainWindow) - self.actionDokumentation.setObjectName(u"actionDokumentation") + self.actionDokumentation.setObjectName("actionDokumentation") self.actionDokumentation.setShortcutContext(Qt.ApplicationShortcut) self.centralwidget = QWidget(MainWindow) - self.centralwidget.setObjectName(u"centralwidget") - sizePolicy.setHeightForWidth(self.centralwidget.sizePolicy().hasHeightForWidth()) + self.centralwidget.setObjectName("centralwidget") + sizePolicy.setHeightForWidth( + self.centralwidget.sizePolicy().hasHeightForWidth() + ) self.centralwidget.setSizePolicy(sizePolicy) self.verticalLayoutWidget = QWidget(self.centralwidget) - self.verticalLayoutWidget.setObjectName(u"verticalLayoutWidget") + self.verticalLayoutWidget.setObjectName("verticalLayoutWidget") self.verticalLayoutWidget.setGeometry(QRect(0, 0, 1271, 761)) self.verticalLayout = QVBoxLayout(self.verticalLayoutWidget) - self.verticalLayout.setObjectName(u"verticalLayout") + self.verticalLayout.setObjectName("verticalLayout") self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.horizontalLayout = QHBoxLayout() - self.horizontalLayout.setObjectName(u"horizontalLayout") + self.horizontalLayout.setObjectName("horizontalLayout") self.gridLayout = QGridLayout() - self.gridLayout.setObjectName(u"gridLayout") + self.gridLayout.setObjectName("gridLayout") self.tabWidget = QTabWidget(self.verticalLayoutWidget) - self.tabWidget.setObjectName(u"tabWidget") + self.tabWidget.setObjectName("tabWidget") self.tabWidget.setFocusPolicy(Qt.NoFocus) self.tab = QWidget() - self.tab.setObjectName(u"tab") - sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred) + self.tab.setObjectName("tab") + sizePolicy1 = QSizePolicy( + QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred + ) sizePolicy1.setHorizontalStretch(0) sizePolicy1.setVerticalStretch(0) sizePolicy1.setHeightForWidth(self.tab.sizePolicy().hasHeightForWidth()) self.tab.setSizePolicy(sizePolicy1) self.horizontalLayoutWidget_2 = QWidget(self.tab) - self.horizontalLayoutWidget_2.setObjectName(u"horizontalLayoutWidget_2") + self.horizontalLayoutWidget_2.setObjectName("horizontalLayoutWidget_2") self.horizontalLayoutWidget_2.setGeometry(QRect(0, 0, 1261, 163)) self.horizontalLayout_2 = QHBoxLayout(self.horizontalLayoutWidget_2) - self.horizontalLayout_2.setObjectName(u"horizontalLayout_2") + self.horizontalLayout_2.setObjectName("horizontalLayout_2") self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0) self.formLayout = QFormLayout() - self.formLayout.setObjectName(u"formLayout") + self.formLayout.setObjectName("formLayout") self.verticalLayout_2 = QVBoxLayout() - self.verticalLayout_2.setObjectName(u"verticalLayout_2") - self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.verticalSpacer = QSpacerItem( + 20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding + ) self.verticalLayout_2.addItem(self.verticalSpacer) self.load_app = QPushButton(self.horizontalLayoutWidget_2) - self.load_app.setObjectName(u"load_app") + self.load_app.setObjectName("load_app") self.load_app.setFocusPolicy(Qt.NoFocus) self.verticalLayout_2.addWidget(self.load_app) self.create_new_app = QPushButton(self.horizontalLayoutWidget_2) - self.create_new_app.setObjectName(u"create_new_app") + self.create_new_app.setObjectName("create_new_app") self.create_new_app.setFocusPolicy(Qt.NoFocus) self.verticalLayout_2.addWidget(self.create_new_app) self.cancel_active_selection = QPushButton(self.horizontalLayoutWidget_2) - self.cancel_active_selection.setObjectName(u"cancel_active_selection") + self.cancel_active_selection.setObjectName("cancel_active_selection") self.cancel_active_selection.setFocusPolicy(Qt.NoFocus) self.verticalLayout_2.addWidget(self.cancel_active_selection) - self.verticalSpacer_2 = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding) + self.verticalSpacer_2 = QSpacerItem( + 20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding + ) self.verticalLayout_2.addItem(self.verticalSpacer_2) - self.formLayout.setLayout(1, QFormLayout.LabelRole, self.verticalLayout_2) self.tableWidget_apparate = QTableWidget(self.horizontalLayoutWidget_2) - if (self.tableWidget_apparate.columnCount() < 6): + if self.tableWidget_apparate.columnCount() < 6: self.tableWidget_apparate.setColumnCount(6) __qtablewidgetitem = QTableWidgetItem() self.tableWidget_apparate.setHorizontalHeaderItem(0, __qtablewidgetitem) @@ -127,9 +184,11 @@ class Ui_MainWindow(object): self.tableWidget_apparate.setHorizontalHeaderItem(4, __qtablewidgetitem4) __qtablewidgetitem5 = QTableWidgetItem() self.tableWidget_apparate.setHorizontalHeaderItem(5, __qtablewidgetitem5) - self.tableWidget_apparate.setObjectName(u"tableWidget_apparate") + self.tableWidget_apparate.setObjectName("tableWidget_apparate") self.tableWidget_apparate.setFocusPolicy(Qt.NoFocus) - self.tableWidget_apparate.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents) + self.tableWidget_apparate.setSizeAdjustPolicy( + QAbstractScrollArea.AdjustToContents + ) self.tableWidget_apparate.setEditTriggers(QAbstractItemView.SelectedClicked) self.tableWidget_apparate.setAlternatingRowColors(True) self.tableWidget_apparate.setTextElideMode(Qt.ElideMiddle) @@ -139,62 +198,67 @@ class Ui_MainWindow(object): self.formLayout.setWidget(1, QFormLayout.FieldRole, self.tableWidget_apparate) self.horizontalLayout_4 = QHBoxLayout() - self.horizontalLayout_4.setObjectName(u"horizontalLayout_4") + self.horizontalLayout_4.setObjectName("horizontalLayout_4") self.formLayout.setLayout(2, QFormLayout.FieldRole, self.horizontalLayout_4) self.horizontalLayout_3 = QHBoxLayout() - self.horizontalLayout_3.setObjectName(u"horizontalLayout_3") + self.horizontalLayout_3.setObjectName("horizontalLayout_3") self.formLayout.setLayout(3, QFormLayout.LabelRole, self.horizontalLayout_3) - self.horizontalLayout_2.addLayout(self.formLayout) self.line = QFrame(self.tab) - self.line.setObjectName(u"line") + self.line.setObjectName("line") self.line.setGeometry(QRect(0, 160, 1261, 21)) self.line.setFrameShape(QFrame.HLine) self.line.setFrameShadow(QFrame.Sunken) self.gridLayoutWidget_2 = QWidget(self.tab) - self.gridLayoutWidget_2.setObjectName(u"gridLayoutWidget_2") + self.gridLayoutWidget_2.setObjectName("gridLayoutWidget_2") self.gridLayoutWidget_2.setEnabled(True) self.gridLayoutWidget_2.setGeometry(QRect(0, 180, 1261, 511)) self.gridLayout_2 = QGridLayout(self.gridLayoutWidget_2) - self.gridLayout_2.setObjectName(u"gridLayout_2") + self.gridLayout_2.setObjectName("gridLayout_2") self.gridLayout_2.setContentsMargins(0, 0, 0, 0) self.app_group_box = QGroupBox(self.gridLayoutWidget_2) - self.app_group_box.setObjectName(u"app_group_box") - sizePolicy2 = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed) + self.app_group_box.setObjectName("app_group_box") + sizePolicy2 = QSizePolicy( + QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed + ) sizePolicy2.setHorizontalStretch(0) sizePolicy2.setVerticalStretch(0) - sizePolicy2.setHeightForWidth(self.app_group_box.sizePolicy().hasHeightForWidth()) + sizePolicy2.setHeightForWidth( + self.app_group_box.sizePolicy().hasHeightForWidth() + ) self.app_group_box.setSizePolicy(sizePolicy2) self.app_group_box.setMinimumSize(QSize(0, 210)) font = QFont() font.setPointSize(12) font.setBold(True) self.app_group_box.setFont(font) - self.app_group_box.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignVCenter) + self.app_group_box.setAlignment( + Qt.AlignLeading | Qt.AlignLeft | Qt.AlignVCenter + ) self.app_group_box.setCheckable(False) self.dokument_list = QTableWidget(self.app_group_box) - if (self.dokument_list.columnCount() < 4): + if self.dokument_list.columnCount() < 4: self.dokument_list.setColumnCount(4) font1 = QFont() - font1.setFamilies([u"Arial"]) + font1.setFamilies(["Arial"]) font1.setPointSize(8) __qtablewidgetitem6 = QTableWidgetItem() - __qtablewidgetitem6.setFont(font1); + __qtablewidgetitem6.setFont(font1) self.dokument_list.setHorizontalHeaderItem(0, __qtablewidgetitem6) __qtablewidgetitem7 = QTableWidgetItem() - __qtablewidgetitem7.setFont(font1); + __qtablewidgetitem7.setFont(font1) self.dokument_list.setHorizontalHeaderItem(1, __qtablewidgetitem7) __qtablewidgetitem8 = QTableWidgetItem() - __qtablewidgetitem8.setFont(font1); + __qtablewidgetitem8.setFont(font1) self.dokument_list.setHorizontalHeaderItem(2, __qtablewidgetitem8) __qtablewidgetitem9 = QTableWidgetItem() self.dokument_list.setHorizontalHeaderItem(3, __qtablewidgetitem9) - self.dokument_list.setObjectName(u"dokument_list") + self.dokument_list.setObjectName("dokument_list") self.dokument_list.setGeometry(QRect(765, 20, 321, 181)) font2 = QFont() font2.setPointSize(10) @@ -209,7 +273,7 @@ class Ui_MainWindow(object): self.dokument_list.setDefaultDropAction(Qt.LinkAction) self.dokument_list.setSelectionMode(QAbstractItemView.SingleSelection) self.frame = QFrame(self.app_group_box) - self.frame.setObjectName(u"frame") + self.frame.setObjectName("frame") self.frame.setEnabled(True) self.frame.setGeometry(QRect(10, 30, 1241, 151)) sizePolicy.setHeightForWidth(self.frame.sizePolicy().hasHeightForWidth()) @@ -217,92 +281,92 @@ class Ui_MainWindow(object): self.frame.setFrameShape(QFrame.StyledPanel) self.frame.setFrameShadow(QFrame.Raised) self.label_5 = QLabel(self.frame) - self.label_5.setObjectName(u"label_5") + self.label_5.setObjectName("label_5") self.label_5.setGeometry(QRect(250, 20, 91, 21)) font3 = QFont() font3.setPointSize(9) font3.setBold(False) self.label_5.setFont(font3) self.sem_winter = QRadioButton(self.frame) - self.sem_winter.setObjectName(u"sem_winter") + self.sem_winter.setObjectName("sem_winter") self.sem_winter.setGeometry(QRect(340, 50, 82, 17)) self.sem_winter.setFont(font3) self.sem_winter.setFocusPolicy(Qt.NoFocus) self.label_4 = QLabel(self.frame) - self.label_4.setObjectName(u"label_4") + self.label_4.setObjectName("label_4") self.label_4.setGeometry(QRect(10, 80, 71, 21)) self.label_4.setFont(font3) self.drpdwn_app_nr = QComboBox(self.frame) - self.drpdwn_app_nr.setObjectName(u"drpdwn_app_nr") + self.drpdwn_app_nr.setObjectName("drpdwn_app_nr") self.drpdwn_app_nr.setGeometry(QRect(110, 20, 69, 22)) self.drpdwn_app_nr.setFont(font3) self.drpdwn_app_nr.setInputMethodHints(Qt.ImhDigitsOnly) self.drpdwn_app_nr.setEditable(True) self.app_name = QLineEdit(self.frame) - self.app_name.setObjectName(u"app_name") + self.app_name.setObjectName("app_name") self.app_name.setGeometry(QRect(340, 20, 113, 20)) self.app_name.setFont(font3) self.app_name.setFocusPolicy(Qt.StrongFocus) self.sem_sommer = QRadioButton(self.frame) - self.sem_sommer.setObjectName(u"sem_sommer") + self.sem_sommer.setObjectName("sem_sommer") self.sem_sommer.setGeometry(QRect(340, 70, 82, 17)) self.sem_sommer.setFont(font3) self.sem_sommer.setFocusPolicy(Qt.NoFocus) self.label_3 = QLabel(self.frame) - self.label_3.setObjectName(u"label_3") + self.label_3.setObjectName("label_3") self.label_3.setGeometry(QRect(10, 50, 61, 20)) self.label_3.setFont(font3) self.label_6 = QLabel(self.frame) - self.label_6.setObjectName(u"label_6") + self.label_6.setObjectName("label_6") self.label_6.setGeometry(QRect(270, 60, 61, 21)) self.label_6.setFont(font3) self.sem_year = QLineEdit(self.frame) - self.sem_year.setObjectName(u"sem_year") + self.sem_year.setObjectName("sem_year") self.sem_year.setGeometry(QRect(410, 60, 113, 20)) self.sem_year.setFont(font3) self.sem_year.setFocusPolicy(Qt.StrongFocus) self.sem_year.setMaxLength(4) self.label_2 = QLabel(self.frame) - self.label_2.setObjectName(u"label_2") + self.label_2.setObjectName("label_2") self.label_2.setGeometry(QRect(10, 20, 101, 21)) self.label_2.setFont(font3) self.btn_apparat_save = QPushButton(self.frame) - self.btn_apparat_save.setObjectName(u"btn_apparat_save") + self.btn_apparat_save.setObjectName("btn_apparat_save") self.btn_apparat_save.setGeometry(QRect(260, 120, 75, 23)) self.btn_apparat_save.setFont(font3) self.btn_apparat_apply = QPushButton(self.frame) - self.btn_apparat_apply.setObjectName(u"btn_apparat_apply") + self.btn_apparat_apply.setObjectName("btn_apparat_apply") self.btn_apparat_apply.setGeometry(QRect(350, 120, 75, 23)) self.btn_apparat_apply.setFont(font3) self.check_eternal_app = QCheckBox(self.frame) - self.check_eternal_app.setObjectName(u"check_eternal_app") + self.check_eternal_app.setObjectName("check_eternal_app") self.check_eternal_app.setGeometry(QRect(340, 90, 101, 17)) self.check_eternal_app.setFont(font3) self.label_8 = QLabel(self.frame) - self.label_8.setObjectName(u"label_8") + self.label_8.setObjectName("label_8") self.label_8.setGeometry(QRect(10, 110, 71, 21)) self.label_8.setFont(font3) self.prof_mail = QLineEdit(self.frame) - self.prof_mail.setObjectName(u"prof_mail") + self.prof_mail.setObjectName("prof_mail") self.prof_mail.setGeometry(QRect(110, 110, 121, 20)) self.prof_mail.setFont(font3) self.prof_mail.setInputMethodHints(Qt.ImhEmailCharactersOnly) self.prof_mail.setMaxLength(200) self.label_9 = QLabel(self.frame) - self.label_9.setObjectName(u"label_9") + self.label_9.setObjectName("label_9") self.label_9.setGeometry(QRect(10, 130, 71, 21)) self.label_9.setFont(font3) self.prof_tel_nr = QLineEdit(self.frame) - self.prof_tel_nr.setObjectName(u"prof_tel_nr") + self.prof_tel_nr.setObjectName("prof_tel_nr") self.prof_tel_nr.setGeometry(QRect(110, 130, 121, 20)) self.prof_tel_nr.setFont(font3) self.prof_tel_nr.setInputMethodHints(Qt.ImhDigitsOnly) self.label_10 = QLabel(self.frame) - self.label_10.setObjectName(u"label_10") + self.label_10.setObjectName("label_10") self.label_10.setGeometry(QRect(470, 20, 51, 21)) self.label_10.setFont(font3) self.drpdwn_prof_name = QComboBox(self.frame) - self.drpdwn_prof_name.setObjectName(u"drpdwn_prof_name") + self.drpdwn_prof_name.setObjectName("drpdwn_prof_name") self.drpdwn_prof_name.setGeometry(QRect(110, 80, 121, 22)) font4 = QFont() font4.setPointSize(8) @@ -313,99 +377,101 @@ class Ui_MainWindow(object): self.drpdwn_prof_name.setInsertPolicy(QComboBox.InsertAlphabetically) self.drpdwn_prof_name.setFrame(True) self.mail_mand = QLabel(self.frame) - self.mail_mand.setObjectName(u"mail_mand") + self.mail_mand.setObjectName("mail_mand") self.mail_mand.setGeometry(QRect(100, 110, 47, 21)) self.mail_mand.setFont(font3) self.telnr_mand = QLabel(self.frame) - self.telnr_mand.setObjectName(u"telnr_mand") + self.telnr_mand.setObjectName("telnr_mand") self.telnr_mand.setGeometry(QRect(100, 130, 47, 21)) self.telnr_mand.setFont(font3) self.profname_mand = QLabel(self.frame) - self.profname_mand.setObjectName(u"profname_mand") + self.profname_mand.setObjectName("profname_mand") self.profname_mand.setGeometry(QRect(100, 80, 47, 21)) self.profname_mand.setFont(font3) self.appname_mand = QLabel(self.frame) - self.appname_mand.setObjectName(u"appname_mand") + self.appname_mand.setObjectName("appname_mand") self.appname_mand.setGeometry(QRect(330, 20, 16, 21)) self.appname_mand.setFont(font3) self.fach_mand = QLabel(self.frame) - self.fach_mand.setObjectName(u"fach_mand") + self.fach_mand.setObjectName("fach_mand") self.fach_mand.setGeometry(QRect(500, 20, 47, 21)) self.fach_mand.setFont(font3) self.fach_mand.setFocusPolicy(Qt.NoFocus) self._mand = QLabel(self.frame) - self._mand.setObjectName(u"_mand") + self._mand.setObjectName("_mand") self._mand.setGeometry(QRect(330, 60, 16, 21)) self._mand.setFont(font3) self.btn_add_document = QPushButton(self.frame) - self.btn_add_document.setObjectName(u"btn_add_document") + self.btn_add_document.setObjectName("btn_add_document") self.btn_add_document.setGeometry(QRect(1090, 20, 131, 25)) self.btn_add_document.setFont(font3) self.btn_open_document = QPushButton(self.frame) - self.btn_open_document.setObjectName(u"btn_open_document") + self.btn_open_document.setObjectName("btn_open_document") self.btn_open_document.setGeometry(QRect(1090, 60, 131, 25)) self.btn_open_document.setFont(font3) self.check_file = QPushButton(self.frame) - self.check_file.setObjectName(u"check_file") + self.check_file.setObjectName("check_file") self.check_file.setGeometry(QRect(1090, 100, 131, 51)) self.check_file.setFont(font3) self.formLayoutWidget_2 = QWidget(self.frame) - self.formLayoutWidget_2.setObjectName(u"formLayoutWidget_2") + self.formLayoutWidget_2.setObjectName("formLayoutWidget_2") self.formLayoutWidget_2.setGeometry(QRect(550, 70, 202, 80)) self.formLayout_3 = QFormLayout(self.formLayoutWidget_2) - self.formLayout_3.setObjectName(u"formLayout_3") + self.formLayout_3.setObjectName("formLayout_3") self.formLayout_3.setContentsMargins(0, 0, 0, 0) self.label_12 = QLabel(self.formLayoutWidget_2) - self.label_12.setObjectName(u"label_12") + self.label_12.setObjectName("label_12") self.label_12.setFont(font3) self.formLayout_3.setWidget(0, QFormLayout.LabelRole, self.label_12) self.prof_id_adis = QLineEdit(self.formLayoutWidget_2) - self.prof_id_adis.setObjectName(u"prof_id_adis") + self.prof_id_adis.setObjectName("prof_id_adis") self.prof_id_adis.setFont(font3) self.formLayout_3.setWidget(0, QFormLayout.FieldRole, self.prof_id_adis) self.label_13 = QLabel(self.formLayoutWidget_2) - self.label_13.setObjectName(u"label_13") + self.label_13.setObjectName("label_13") self.label_13.setFont(font3) self.formLayout_3.setWidget(1, QFormLayout.LabelRole, self.label_13) self.apparat_id_adis = QLineEdit(self.formLayoutWidget_2) - self.apparat_id_adis.setObjectName(u"apparat_id_adis") + self.apparat_id_adis.setObjectName("apparat_id_adis") self.formLayout_3.setWidget(1, QFormLayout.FieldRole, self.apparat_id_adis) self.check_send_mail = QCheckBox(self.frame) - self.check_send_mail.setObjectName(u"check_send_mail") + self.check_send_mail.setObjectName("check_send_mail") self.check_send_mail.setGeometry(QRect(450, 120, 91, 17)) self.check_send_mail.setFont(font3) self.frame_3 = QFrame(self.frame) - self.frame_3.setObjectName(u"frame_3") + self.frame_3.setObjectName("frame_3") self.frame_3.setGeometry(QRect(510, 0, 241, 61)) self.frame_3.setFrameShape(QFrame.StyledPanel) self.frame_3.setFrameShadow(QFrame.Raised) self.gridLayoutWidget_5 = QWidget(self.frame_3) - self.gridLayoutWidget_5.setObjectName(u"gridLayoutWidget_5") + self.gridLayoutWidget_5.setObjectName("gridLayoutWidget_5") self.gridLayoutWidget_5.setGeometry(QRect(0, 0, 241, 61)) self.gridLayout_6 = QGridLayout(self.gridLayoutWidget_5) - self.gridLayout_6.setObjectName(u"gridLayout_6") + self.gridLayout_6.setObjectName("gridLayout_6") self.gridLayout_6.setContentsMargins(0, 0, 0, 0) self.app_fach = QComboBox(self.gridLayoutWidget_5) - self.app_fach.setObjectName(u"app_fach") + self.app_fach.setObjectName("app_fach") self.app_fach.setMaximumSize(QSize(16777215, 20)) self.app_fach.setFont(font3) self.gridLayout_6.addWidget(self.app_fach, 0, 0, 1, 1) - self.horizontalSpacer_7 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) + self.horizontalSpacer_7 = QSpacerItem( + 40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum + ) self.gridLayout_6.addItem(self.horizontalSpacer_7, 0, 1, 1, 1) self.prof_title = QLineEdit(self.frame) - self.prof_title.setObjectName(u"prof_title") + self.prof_title.setObjectName("prof_title") self.prof_title.setGeometry(QRect(110, 50, 71, 20)) self.prof_title.setFont(font3) self.prof_title.setFocusPolicy(Qt.StrongFocus) @@ -447,7 +513,7 @@ class Ui_MainWindow(object): self.gridLayout_2.addWidget(self.app_group_box, 1, 0, 1, 1) self.tableWidget_apparat_media = QTableWidget(self.gridLayoutWidget_2) - if (self.tableWidget_apparat_media.columnCount() < 7): + if self.tableWidget_apparat_media.columnCount() < 7: self.tableWidget_apparat_media.setColumnCount(7) __qtablewidgetitem10 = QTableWidgetItem() self.tableWidget_apparat_media.setHorizontalHeaderItem(0, __qtablewidgetitem10) @@ -463,26 +529,36 @@ class Ui_MainWindow(object): self.tableWidget_apparat_media.setHorizontalHeaderItem(5, __qtablewidgetitem15) __qtablewidgetitem16 = QTableWidgetItem() self.tableWidget_apparat_media.setHorizontalHeaderItem(6, __qtablewidgetitem16) - self.tableWidget_apparat_media.setObjectName(u"tableWidget_apparat_media") - sizePolicy3 = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Expanding) + self.tableWidget_apparat_media.setObjectName("tableWidget_apparat_media") + sizePolicy3 = QSizePolicy( + QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Expanding + ) sizePolicy3.setHorizontalStretch(0) sizePolicy3.setVerticalStretch(0) - sizePolicy3.setHeightForWidth(self.tableWidget_apparat_media.sizePolicy().hasHeightForWidth()) + sizePolicy3.setHeightForWidth( + self.tableWidget_apparat_media.sizePolicy().hasHeightForWidth() + ) self.tableWidget_apparat_media.setSizePolicy(sizePolicy3) self.tableWidget_apparat_media.setMinimumSize(QSize(1259, 0)) self.tableWidget_apparat_media.setFocusPolicy(Qt.NoFocus) self.tableWidget_apparat_media.setContextMenuPolicy(Qt.CustomContextMenu) - self.tableWidget_apparat_media.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents) + self.tableWidget_apparat_media.setSizeAdjustPolicy( + QAbstractScrollArea.AdjustToContents + ) self.tableWidget_apparat_media.setEditTriggers(QAbstractItemView.NoEditTriggers) self.tableWidget_apparat_media.setAlternatingRowColors(True) - self.tableWidget_apparat_media.setSelectionBehavior(QAbstractItemView.SelectRows) + self.tableWidget_apparat_media.setSelectionBehavior( + QAbstractItemView.SelectRows + ) self.tableWidget_apparat_media.setSortingEnabled(True) - self.tableWidget_apparat_media.horizontalHeader().setCascadingSectionResizes(True) + self.tableWidget_apparat_media.horizontalHeader().setCascadingSectionResizes( + True + ) self.gridLayout_2.addWidget(self.tableWidget_apparat_media, 9, 0, 1, 1) self.label = QLabel(self.gridLayoutWidget_2) - self.label.setObjectName(u"label") + self.label.setObjectName("label") font5 = QFont() font5.setPointSize(11) font5.setBold(True) @@ -491,227 +567,237 @@ class Ui_MainWindow(object): self.gridLayout_2.addWidget(self.label, 2, 0, 1, 1) self.horizontalLayout_5 = QHBoxLayout() - self.horizontalLayout_5.setObjectName(u"horizontalLayout_5") - self.horizontalSpacer = QSpacerItem(20, 20, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum) + self.horizontalLayout_5.setObjectName("horizontalLayout_5") + self.horizontalSpacer = QSpacerItem( + 20, 20, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum + ) self.horizontalLayout_5.addItem(self.horizontalSpacer) self.chkbx_show_del_media = QCheckBox(self.gridLayoutWidget_2) - self.chkbx_show_del_media.setObjectName(u"chkbx_show_del_media") + self.chkbx_show_del_media.setObjectName("chkbx_show_del_media") self.horizontalLayout_5.addWidget(self.chkbx_show_del_media) - self.horizontalSpacer_3 = QSpacerItem(40, 20, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum) + self.horizontalSpacer_3 = QSpacerItem( + 40, 20, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum + ) self.horizontalLayout_5.addItem(self.horizontalSpacer_3) self.btn_reserve = QPushButton(self.gridLayoutWidget_2) - self.btn_reserve.setObjectName(u"btn_reserve") + self.btn_reserve.setObjectName("btn_reserve") self.horizontalLayout_5.addWidget(self.btn_reserve) self.add_layout = QHBoxLayout() - self.add_layout.setObjectName(u"add_layout") + self.add_layout.setObjectName("add_layout") self.label_info = QLabel(self.gridLayoutWidget_2) - self.label_info.setObjectName(u"label_info") + self.label_info.setObjectName("label_info") self.add_layout.addWidget(self.label_info) self.line_2 = QFrame(self.gridLayoutWidget_2) - self.line_2.setObjectName(u"line_2") + self.line_2.setObjectName("line_2") self.line_2.setFrameShape(QFrame.VLine) self.line_2.setFrameShadow(QFrame.Sunken) self.add_layout.addWidget(self.line_2) self.progress_label = QLabel(self.gridLayoutWidget_2) - self.progress_label.setObjectName(u"progress_label") + self.progress_label.setObjectName("progress_label") self.add_layout.addWidget(self.progress_label) - self.horizontalLayout_5.addLayout(self.add_layout) - self.horizontalSpacer_4 = QSpacerItem(40, 20, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum) + self.horizontalSpacer_4 = QSpacerItem( + 40, 20, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum + ) self.horizontalLayout_5.addItem(self.horizontalSpacer_4) self.avail_layout = QHBoxLayout() - self.avail_layout.setObjectName(u"avail_layout") + self.avail_layout.setObjectName("avail_layout") self.horizontalLayout_5.addLayout(self.avail_layout) self.label_20 = QLabel(self.gridLayoutWidget_2) - self.label_20.setObjectName(u"label_20") + self.label_20.setObjectName("label_20") self.horizontalLayout_5.addWidget(self.label_20) self.line_3 = QFrame(self.gridLayoutWidget_2) - self.line_3.setObjectName(u"line_3") + self.line_3.setObjectName("line_3") self.line_3.setFrameShape(QFrame.VLine) self.line_3.setFrameShadow(QFrame.Sunken) self.horizontalLayout_5.addWidget(self.line_3) self.avail_status = QLabel(self.gridLayoutWidget_2) - self.avail_status.setObjectName(u"avail_status") + self.avail_status.setObjectName("avail_status") self.horizontalLayout_5.addWidget(self.avail_status) - self.horizontalSpacer_2 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) + self.horizontalSpacer_2 = QSpacerItem( + 40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum + ) self.horizontalLayout_5.addItem(self.horizontalSpacer_2) - self.gridLayout_2.addLayout(self.horizontalLayout_5, 4, 0, 1, 1) self.add_medium = QPushButton(self.tab) - self.add_medium.setObjectName(u"add_medium") + self.add_medium.setObjectName("add_medium") self.add_medium.setGeometry(QRect(0, 700, 121, 20)) self.add_medium.setFocusPolicy(Qt.NoFocus) self.tabWidget.addTab(self.tab, "") self.tab_2 = QWidget() - self.tab_2.setObjectName(u"tab_2") + self.tab_2.setObjectName("tab_2") self.verticalLayoutWidget_2 = QWidget(self.tab_2) - self.verticalLayoutWidget_2.setObjectName(u"verticalLayoutWidget_2") + self.verticalLayoutWidget_2.setObjectName("verticalLayoutWidget_2") self.verticalLayoutWidget_2.setGeometry(QRect(0, 0, 1251, 721)) self.verticalLayout_3 = QVBoxLayout(self.verticalLayoutWidget_2) - self.verticalLayout_3.setObjectName(u"verticalLayout_3") + self.verticalLayout_3.setObjectName("verticalLayout_3") self.verticalLayout_3.setContentsMargins(0, 0, 0, 0) self.tabWidget_2 = QTabWidget(self.verticalLayoutWidget_2) - self.tabWidget_2.setObjectName(u"tabWidget_2") + self.tabWidget_2.setObjectName("tabWidget_2") self.tabWidget_2.setMaximumSize(QSize(16777215, 250)) self.tab_3 = QWidget() - self.tab_3.setObjectName(u"tab_3") + self.tab_3.setObjectName("tab_3") self.btn_search = QPushButton(self.tab_3) - self.btn_search.setObjectName(u"btn_search") + self.btn_search.setObjectName("btn_search") self.btn_search.setGeometry(QRect(10, 180, 75, 23)) self.gridLayoutWidget = QWidget(self.tab_3) - self.gridLayoutWidget.setObjectName(u"gridLayoutWidget") + self.gridLayoutWidget.setObjectName("gridLayoutWidget") self.gridLayoutWidget.setGeometry(QRect(10, 10, 491, 161)) self.gridLayout_3 = QGridLayout(self.gridLayoutWidget) - self.gridLayout_3.setObjectName(u"gridLayout_3") + self.gridLayout_3.setObjectName("gridLayout_3") self.gridLayout_3.setContentsMargins(0, 0, 0, 0) self.label_7 = QLabel(self.gridLayoutWidget) - self.label_7.setObjectName(u"label_7") + self.label_7.setObjectName("label_7") self.gridLayout_3.addWidget(self.label_7, 0, 0, 1, 1) self.box_erstellsemester = QComboBox(self.gridLayoutWidget) - self.box_erstellsemester.setObjectName(u"box_erstellsemester") + self.box_erstellsemester.setObjectName("box_erstellsemester") self.box_erstellsemester.setEditable(True) self.gridLayout_3.addWidget(self.box_erstellsemester, 1, 3, 1, 1) self.label_18 = QLabel(self.gridLayoutWidget) - self.label_18.setObjectName(u"label_18") + self.label_18.setObjectName("label_18") self.gridLayout_3.addWidget(self.label_18, 2, 2, 1, 1) self.label_17 = QLabel(self.gridLayoutWidget) - self.label_17.setObjectName(u"label_17") + self.label_17.setObjectName("label_17") self.gridLayout_3.addWidget(self.label_17, 0, 2, 1, 1) self.label_19 = QLabel(self.gridLayoutWidget) - self.label_19.setObjectName(u"label_19") + self.label_19.setObjectName("label_19") self.gridLayout_3.addWidget(self.label_19, 1, 2, 1, 1) self.box_dauerapp = QComboBox(self.gridLayoutWidget) - self.box_dauerapp.setObjectName(u"box_dauerapp") + self.box_dauerapp.setObjectName("box_dauerapp") self.gridLayout_3.addWidget(self.box_dauerapp, 2, 3, 1, 1) self.label_11 = QLabel(self.gridLayoutWidget) - self.label_11.setObjectName(u"label_11") + self.label_11.setObjectName("label_11") self.gridLayout_3.addWidget(self.label_11, 1, 0, 1, 1) self.label_16 = QLabel(self.gridLayoutWidget) - self.label_16.setObjectName(u"label_16") + self.label_16.setObjectName("label_16") self.gridLayout_3.addWidget(self.label_16, 2, 0, 1, 1) self.box_semester = QComboBox(self.gridLayoutWidget) - self.box_semester.setObjectName(u"box_semester") + self.box_semester.setObjectName("box_semester") self.box_semester.setEditable(True) self.gridLayout_3.addWidget(self.box_semester, 0, 3, 1, 1) self.box_appnrs = QComboBox(self.gridLayoutWidget) - self.box_appnrs.setObjectName(u"box_appnrs") + self.box_appnrs.setObjectName("box_appnrs") self.box_appnrs.setEditable(True) self.gridLayout_3.addWidget(self.box_appnrs, 0, 1, 1, 1) self.box_fach = QComboBox(self.gridLayoutWidget) - self.box_fach.setObjectName(u"box_fach") + self.box_fach.setObjectName("box_fach") self.box_fach.setEditable(True) self.gridLayout_3.addWidget(self.box_fach, 2, 1, 1, 1) self.box_person = QComboBox(self.gridLayoutWidget) - self.box_person.setObjectName(u"box_person") + self.box_person.setObjectName("box_person") self.box_person.setEditable(True) self.gridLayout_3.addWidget(self.box_person, 1, 1, 1, 1) - self.verticalSpacer_3 = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding) + self.verticalSpacer_3 = QSpacerItem( + 20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding + ) self.gridLayout_3.addItem(self.verticalSpacer_3, 4, 0, 1, 1) self.label_15 = QLabel(self.gridLayoutWidget) - self.label_15.setObjectName(u"label_15") + self.label_15.setObjectName("label_15") self.gridLayout_3.addWidget(self.label_15, 3, 0, 1, 1) self.check_deletable = QCheckBox(self.gridLayoutWidget) - self.check_deletable.setObjectName(u"check_deletable") + self.check_deletable.setObjectName("check_deletable") self.check_deletable.setFocusPolicy(Qt.StrongFocus) self.gridLayout_3.addWidget(self.check_deletable, 3, 1, 1, 1) self.db_err_message = QLabel(self.tab_3) - self.db_err_message.setObjectName(u"db_err_message") + self.db_err_message.setObjectName("db_err_message") self.db_err_message.setGeometry(QRect(100, 180, 401, 23)) self.tabWidget_2.addTab(self.tab_3, "") self.tab_4 = QWidget() - self.tab_4.setObjectName(u"tab_4") + self.tab_4.setObjectName("tab_4") self.formLayoutWidget = QWidget(self.tab_4) - self.formLayoutWidget.setObjectName(u"formLayoutWidget") + self.formLayoutWidget.setObjectName("formLayoutWidget") self.formLayoutWidget.setGeometry(QRect(10, 10, 451, 151)) self.formLayout_6 = QFormLayout(self.formLayoutWidget) - self.formLayout_6.setObjectName(u"formLayout_6") + self.formLayout_6.setObjectName("formLayout_6") self.formLayout_6.setContentsMargins(0, 0, 0, 0) self.label_25 = QLabel(self.formLayoutWidget) - self.label_25.setObjectName(u"label_25") + self.label_25.setObjectName("label_25") self.formLayout_6.setWidget(0, QFormLayout.LabelRole, self.label_25) self.book_search = QPushButton(self.formLayoutWidget) - self.book_search.setObjectName(u"book_search") + self.book_search.setObjectName("book_search") self.formLayout_6.setWidget(3, QFormLayout.LabelRole, self.book_search) self.seach_by_signature = QLineEdit(self.formLayoutWidget) - self.seach_by_signature.setObjectName(u"seach_by_signature") + self.seach_by_signature.setObjectName("seach_by_signature") self.seach_by_signature.setClearButtonEnabled(True) self.formLayout_6.setWidget(0, QFormLayout.FieldRole, self.seach_by_signature) self.label_26 = QLabel(self.formLayoutWidget) - self.label_26.setObjectName(u"label_26") + self.label_26.setObjectName("label_26") self.formLayout_6.setWidget(1, QFormLayout.LabelRole, self.label_26) self.search_by_title = QLineEdit(self.formLayoutWidget) - self.search_by_title.setObjectName(u"search_by_title") + self.search_by_title.setObjectName("search_by_title") self.search_by_title.setClearButtonEnabled(True) self.formLayout_6.setWidget(1, QFormLayout.FieldRole, self.search_by_title) - self.verticalSpacer_4 = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding) + self.verticalSpacer_4 = QSpacerItem( + 20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding + ) self.formLayout_6.setItem(2, QFormLayout.LabelRole, self.verticalSpacer_4) @@ -720,24 +806,24 @@ class Ui_MainWindow(object): self.verticalLayout_3.addWidget(self.tabWidget_2) self.stackedWidget_4 = QStackedWidget(self.verticalLayoutWidget_2) - self.stackedWidget_4.setObjectName(u"stackedWidget_4") + self.stackedWidget_4.setObjectName("stackedWidget_4") self.stackedWidget_4.setFrameShape(QFrame.StyledPanel) self.stackedWidget_4.setFrameShadow(QFrame.Raised) self.stackedWidget_4Page1 = QWidget() - self.stackedWidget_4Page1.setObjectName(u"stackedWidget_4Page1") + self.stackedWidget_4Page1.setObjectName("stackedWidget_4Page1") self.tabWidget_3 = QTabWidget(self.stackedWidget_4Page1) - self.tabWidget_3.setObjectName(u"tabWidget_3") + self.tabWidget_3.setObjectName("tabWidget_3") self.tabWidget_3.setGeometry(QRect(780, 10, 441, 441)) self.tab_6 = QWidget() - self.tab_6.setObjectName(u"tab_6") + self.tab_6.setObjectName("tab_6") self.gridLayoutWidget_3 = QWidget(self.tab_6) - self.gridLayoutWidget_3.setObjectName(u"gridLayoutWidget_3") + self.gridLayoutWidget_3.setObjectName("gridLayoutWidget_3") self.gridLayoutWidget_3.setGeometry(QRect(0, 0, 431, 411)) self.gridLayout_4 = QGridLayout(self.gridLayoutWidget_3) - self.gridLayout_4.setObjectName(u"gridLayout_4") + self.gridLayout_4.setObjectName("gridLayout_4") self.gridLayout_4.setContentsMargins(0, 0, 0, 0) self.statistics_table = QTableWidget(self.gridLayoutWidget_3) - if (self.statistics_table.columnCount() < 3): + if self.statistics_table.columnCount() < 3: self.statistics_table.setColumnCount(3) __qtablewidgetitem17 = QTableWidgetItem() self.statistics_table.setHorizontalHeaderItem(0, __qtablewidgetitem17) @@ -745,11 +831,15 @@ class Ui_MainWindow(object): self.statistics_table.setHorizontalHeaderItem(1, __qtablewidgetitem18) __qtablewidgetitem19 = QTableWidgetItem() self.statistics_table.setHorizontalHeaderItem(2, __qtablewidgetitem19) - self.statistics_table.setObjectName(u"statistics_table") - sizePolicy4 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) + self.statistics_table.setObjectName("statistics_table") + sizePolicy4 = QSizePolicy( + QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding + ) sizePolicy4.setHorizontalStretch(0) sizePolicy4.setVerticalStretch(0) - sizePolicy4.setHeightForWidth(self.statistics_table.sizePolicy().hasHeightForWidth()) + sizePolicy4.setHeightForWidth( + self.statistics_table.sizePolicy().hasHeightForWidth() + ) self.statistics_table.setSizePolicy(sizePolicy4) self.statistics_table.setMaximumSize(QSize(16777215, 16777215)) self.statistics_table.setFocusPolicy(Qt.NoFocus) @@ -765,32 +855,34 @@ class Ui_MainWindow(object): self.tabWidget_3.addTab(self.tab_6, "") self.tab_7 = QWidget() - self.tab_7.setObjectName(u"tab_7") + self.tab_7.setObjectName("tab_7") self.tabWidget_3.addTab(self.tab_7, "") self.widget = QWidget(self.stackedWidget_4Page1) - self.widget.setObjectName(u"widget") + self.widget.setObjectName("widget") self.widget.setGeometry(QRect(10, 10, 761, 441)) self.horizontalLayoutWidget_3 = QWidget(self.widget) - self.horizontalLayoutWidget_3.setObjectName(u"horizontalLayoutWidget_3") + self.horizontalLayoutWidget_3.setObjectName("horizontalLayoutWidget_3") self.horizontalLayoutWidget_3.setGeometry(QRect(0, 0, 761, 51)) self.horizontalLayout_7 = QHBoxLayout(self.horizontalLayoutWidget_3) - self.horizontalLayout_7.setObjectName(u"horizontalLayout_7") + self.horizontalLayout_7.setObjectName("horizontalLayout_7") self.horizontalLayout_7.setContentsMargins(0, 0, 0, 0) self.btn_del_select_apparats = QPushButton(self.horizontalLayoutWidget_3) - self.btn_del_select_apparats.setObjectName(u"btn_del_select_apparats") + self.btn_del_select_apparats.setObjectName("btn_del_select_apparats") self.btn_del_select_apparats.setFocusPolicy(Qt.StrongFocus) self.horizontalLayout_7.addWidget(self.btn_del_select_apparats) - self.horizontalSpacer_5 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) + self.horizontalSpacer_5 = QSpacerItem( + 40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum + ) self.horizontalLayout_7.addItem(self.horizontalSpacer_5) self.table = QWidget(self.widget) - self.table.setObjectName(u"table") + self.table.setObjectName("table") self.table.setGeometry(QRect(0, 50, 761, 391)) self.tableWidget = QTableWidget(self.table) - if (self.tableWidget.columnCount() < 5): + if self.tableWidget.columnCount() < 5: self.tableWidget.setColumnCount(5) __qtablewidgetitem20 = QTableWidgetItem() self.tableWidget.setHorizontalHeaderItem(0, __qtablewidgetitem20) @@ -802,15 +894,15 @@ class Ui_MainWindow(object): self.tableWidget.setHorizontalHeaderItem(3, __qtablewidgetitem23) __qtablewidgetitem24 = QTableWidgetItem() self.tableWidget.setHorizontalHeaderItem(4, __qtablewidgetitem24) - self.tableWidget.setObjectName(u"tableWidget") + self.tableWidget.setObjectName("tableWidget") self.tableWidget.setGeometry(QRect(0, 0, 761, 391)) self.tableWidget.setFocusPolicy(Qt.NoFocus) self.tableWidget.setEditTriggers(QAbstractItemView.NoEditTriggers) self.stackedWidget_4.addWidget(self.stackedWidget_4Page1) self.page = QWidget() - self.page.setObjectName(u"page") + self.page.setObjectName("page") self.book_search_result = QTableWidget(self.page) - if (self.book_search_result.columnCount() < 3): + if self.book_search_result.columnCount() < 3: self.book_search_result.setColumnCount(3) __qtablewidgetitem25 = QTableWidgetItem() self.book_search_result.setHorizontalHeaderItem(0, __qtablewidgetitem25) @@ -818,7 +910,7 @@ class Ui_MainWindow(object): self.book_search_result.setHorizontalHeaderItem(1, __qtablewidgetitem26) __qtablewidgetitem27 = QTableWidgetItem() self.book_search_result.setHorizontalHeaderItem(2, __qtablewidgetitem27) - self.book_search_result.setObjectName(u"book_search_result") + self.book_search_result.setObjectName("book_search_result") self.book_search_result.setGeometry(QRect(10, 20, 1081, 421)) self.book_search_result.setFrameShadow(QFrame.Plain) self.book_search_result.setEditTriggers(QAbstractItemView.NoEditTriggers) @@ -831,306 +923,340 @@ class Ui_MainWindow(object): self.tabWidget.addTab(self.tab_2, "") self.tab_5 = QWidget() - self.tab_5.setObjectName(u"tab_5") + self.tab_5.setObjectName("tab_5") self.label_21 = QLabel(self.tab_5) - self.label_21.setObjectName(u"label_21") + self.label_21.setObjectName("label_21") self.label_21.setGeometry(QRect(10, 30, 47, 22)) self.select_action_box = QComboBox(self.tab_5) self.select_action_box.addItem("") self.select_action_box.addItem("") self.select_action_box.addItem("") self.select_action_box.addItem("") - self.select_action_box.setObjectName(u"select_action_box") + self.select_action_box.setObjectName("select_action_box") self.select_action_box.setGeometry(QRect(70, 30, 181, 22)) self.user_create_frame = QFrame(self.tab_5) - self.user_create_frame.setObjectName(u"user_create_frame") + self.user_create_frame.setObjectName("user_create_frame") self.user_create_frame.setGeometry(QRect(10, 60, 591, 141)) self.user_create_frame.setFrameShape(QFrame.StyledPanel) self.user_create_frame.setFrameShadow(QFrame.Raised) self.gridLayoutWidget_4 = QWidget(self.user_create_frame) - self.gridLayoutWidget_4.setObjectName(u"gridLayoutWidget_4") + self.gridLayoutWidget_4.setObjectName("gridLayoutWidget_4") self.gridLayoutWidget_4.setGeometry(QRect(0, 0, 581, 141)) self.gridLayout_5 = QGridLayout(self.gridLayoutWidget_4) - self.gridLayout_5.setObjectName(u"gridLayout_5") + self.gridLayout_5.setObjectName("gridLayout_5") self.gridLayout_5.setContentsMargins(0, 0, 0, 0) self.label_22 = QLabel(self.gridLayoutWidget_4) - self.label_22.setObjectName(u"label_22") + self.label_22.setObjectName("label_22") self.gridLayout_5.addWidget(self.label_22, 0, 0, 1, 1) self.user_create_frame_username = QLineEdit(self.gridLayoutWidget_4) - self.user_create_frame_username.setObjectName(u"user_create_frame_username") + self.user_create_frame_username.setObjectName("user_create_frame_username") self.user_create_frame_username.setMaximumSize(QSize(150, 16777215)) self.gridLayout_5.addWidget(self.user_create_frame_username, 0, 1, 1, 1) self.label_24 = QLabel(self.gridLayoutWidget_4) - self.label_24.setObjectName(u"label_24") + self.label_24.setObjectName("label_24") self.gridLayout_5.addWidget(self.label_24, 0, 2, 1, 1) self.label_23 = QLabel(self.gridLayoutWidget_4) - self.label_23.setObjectName(u"label_23") + self.label_23.setObjectName("label_23") self.gridLayout_5.addWidget(self.label_23, 1, 0, 1, 1) - self.horizontalSpacer_6 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) + self.horizontalSpacer_6 = QSpacerItem( + 40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum + ) self.gridLayout_5.addItem(self.horizontalSpacer_6, 0, 4, 1, 1) self.user_frame_userrole = QComboBox(self.gridLayoutWidget_4) - self.user_frame_userrole.setObjectName(u"user_frame_userrole") + self.user_frame_userrole.setObjectName("user_frame_userrole") self.gridLayout_5.addWidget(self.user_frame_userrole, 0, 3, 1, 1) self.user_create_frame_password = QLineEdit(self.gridLayoutWidget_4) - self.user_create_frame_password.setObjectName(u"user_create_frame_password") + self.user_create_frame_password.setObjectName("user_create_frame_password") self.user_create_frame_password.setMaximumSize(QSize(150, 16777215)) self.gridLayout_5.addWidget(self.user_create_frame_password, 1, 1, 1, 1) self.user_frame_addUser = QPushButton(self.gridLayoutWidget_4) - self.user_frame_addUser.setObjectName(u"user_frame_addUser") + self.user_frame_addUser.setObjectName("user_frame_addUser") self.gridLayout_5.addWidget(self.user_frame_addUser, 1, 3, 1, 1) self.user_frame_err_message = QLabel(self.gridLayoutWidget_4) - self.user_frame_err_message.setObjectName(u"user_frame_err_message") + self.user_frame_err_message.setObjectName("user_frame_err_message") self.gridLayout_5.addWidget(self.user_frame_err_message, 1, 4, 1, 1) self.user_delete_frame = QFrame(self.tab_5) - self.user_delete_frame.setObjectName(u"user_delete_frame") + self.user_delete_frame.setObjectName("user_delete_frame") self.user_delete_frame.setGeometry(QRect(10, 60, 591, 141)) self.user_delete_frame.setFrameShape(QFrame.StyledPanel) self.user_delete_frame.setFrameShadow(QFrame.Raised) self.gridLayoutWidget_7 = QWidget(self.user_delete_frame) - self.gridLayoutWidget_7.setObjectName(u"gridLayoutWidget_7") + self.gridLayoutWidget_7.setObjectName("gridLayoutWidget_7") self.gridLayoutWidget_7.setGeometry(QRect(0, 0, 581, 141)) self.gridLayout_8 = QGridLayout(self.gridLayoutWidget_7) - self.gridLayout_8.setObjectName(u"gridLayout_8") + self.gridLayout_8.setObjectName("gridLayout_8") self.gridLayout_8.setContentsMargins(0, 0, 0, 0) self.horizontalLayout_8 = QHBoxLayout() - self.horizontalLayout_8.setObjectName(u"horizontalLayout_8") - self.horizontalSpacer_10 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) + self.horizontalLayout_8.setObjectName("horizontalLayout_8") + self.horizontalSpacer_10 = QSpacerItem( + 40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum + ) self.horizontalLayout_8.addItem(self.horizontalSpacer_10) self.pushButton = QPushButton(self.gridLayoutWidget_7) - self.pushButton.setObjectName(u"pushButton") + self.pushButton.setObjectName("pushButton") self.horizontalLayout_8.addWidget(self.pushButton) - self.horizontalSpacer_11 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) + self.horizontalSpacer_11 = QSpacerItem( + 40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum + ) self.horizontalLayout_8.addItem(self.horizontalSpacer_11) - self.gridLayout_8.addLayout(self.horizontalLayout_8, 1, 1, 1, 1) self.label_34 = QLabel(self.gridLayoutWidget_7) - self.label_34.setObjectName(u"label_34") + self.label_34.setObjectName("label_34") self.gridLayout_8.addWidget(self.label_34, 0, 0, 1, 1) self.user_delete_frame_user_select = QComboBox(self.gridLayoutWidget_7) - self.user_delete_frame_user_select.setObjectName(u"user_delete_frame_user_select") + self.user_delete_frame_user_select.setObjectName( + "user_delete_frame_user_select" + ) self.gridLayout_8.addWidget(self.user_delete_frame_user_select, 0, 1, 1, 1) self.user_delete_confirm = QRadioButton(self.gridLayoutWidget_7) - self.user_delete_confirm.setObjectName(u"user_delete_confirm") + self.user_delete_confirm.setObjectName("user_delete_confirm") self.user_delete_confirm.setLayoutDirection(Qt.RightToLeft) self.gridLayout_8.addWidget(self.user_delete_confirm, 1, 0, 1, 1) - self.horizontalSpacer_12 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) + self.horizontalSpacer_12 = QSpacerItem( + 40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum + ) self.gridLayout_8.addItem(self.horizontalSpacer_12, 0, 2, 1, 1) self.user_delete_err_message = QLabel(self.gridLayoutWidget_7) - self.user_delete_err_message.setObjectName(u"user_delete_err_message") + self.user_delete_err_message.setObjectName("user_delete_err_message") self.gridLayout_8.addWidget(self.user_delete_err_message, 1, 2, 1, 1) self.user_edit_frame = QFrame(self.tab_5) - self.user_edit_frame.setObjectName(u"user_edit_frame") + self.user_edit_frame.setObjectName("user_edit_frame") self.user_edit_frame.setGeometry(QRect(10, 60, 591, 141)) self.user_edit_frame.setFrameShape(QFrame.StyledPanel) self.user_edit_frame.setFrameShadow(QFrame.Raised) self.gridLayoutWidget_10 = QWidget(self.user_edit_frame) - self.gridLayoutWidget_10.setObjectName(u"gridLayoutWidget_10") + self.gridLayoutWidget_10.setObjectName("gridLayoutWidget_10") self.gridLayoutWidget_10.setGeometry(QRect(0, 0, 581, 141)) self.gridLayout_11 = QGridLayout(self.gridLayoutWidget_10) - self.gridLayout_11.setObjectName(u"gridLayout_11") + self.gridLayout_11.setObjectName("gridLayout_11") self.gridLayout_11.setContentsMargins(0, 0, 0, 0) self.user_edit_frame_role_select = QComboBox(self.gridLayoutWidget_10) - self.user_edit_frame_role_select.setObjectName(u"user_edit_frame_role_select") + self.user_edit_frame_role_select.setObjectName("user_edit_frame_role_select") self.gridLayout_11.addWidget(self.user_edit_frame_role_select, 0, 3, 1, 1) self.label_38 = QLabel(self.gridLayoutWidget_10) - self.label_38.setObjectName(u"label_38") + self.label_38.setObjectName("label_38") self.gridLayout_11.addWidget(self.label_38, 0, 0, 1, 1) self.user_edit_frame_user_select = QComboBox(self.gridLayoutWidget_10) - self.user_edit_frame_user_select.setObjectName(u"user_edit_frame_user_select") + self.user_edit_frame_user_select.setObjectName("user_edit_frame_user_select") self.user_edit_frame_user_select.setMaximumSize(QSize(150, 16777215)) self.gridLayout_11.addWidget(self.user_edit_frame_user_select, 0, 1, 1, 1) self.update_user = QPushButton(self.gridLayoutWidget_10) - self.update_user.setObjectName(u"update_user") + self.update_user.setObjectName("update_user") self.gridLayout_11.addWidget(self.update_user, 1, 3, 1, 1) self.label_40 = QLabel(self.gridLayoutWidget_10) - self.label_40.setObjectName(u"label_40") + self.label_40.setObjectName("label_40") self.gridLayout_11.addWidget(self.label_40, 0, 2, 1, 1) self.label_39 = QLabel(self.gridLayoutWidget_10) - self.label_39.setObjectName(u"label_39") + self.label_39.setObjectName("label_39") self.gridLayout_11.addWidget(self.label_39, 1, 0, 1, 1) self.user_edit_frame_new_password = QLineEdit(self.gridLayoutWidget_10) - self.user_edit_frame_new_password.setObjectName(u"user_edit_frame_new_password") + self.user_edit_frame_new_password.setObjectName("user_edit_frame_new_password") self.user_edit_frame_new_password.setMaximumSize(QSize(150, 16777215)) self.gridLayout_11.addWidget(self.user_edit_frame_new_password, 1, 1, 1, 1) - self.horizontalSpacer_14 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) + self.horizontalSpacer_14 = QSpacerItem( + 40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum + ) self.gridLayout_11.addItem(self.horizontalSpacer_14, 0, 4, 1, 1) self.edit_faculty_member = QFrame(self.tab_5) - self.edit_faculty_member.setObjectName(u"edit_faculty_member") + self.edit_faculty_member.setObjectName("edit_faculty_member") self.edit_faculty_member.setGeometry(QRect(10, 60, 1051, 241)) self.edit_faculty_member.setFrameShape(QFrame.StyledPanel) self.edit_faculty_member.setFrameShadow(QFrame.Raised) self.gridLayoutWidget_11 = QWidget(self.edit_faculty_member) - self.gridLayoutWidget_11.setObjectName(u"gridLayoutWidget_11") + self.gridLayoutWidget_11.setObjectName("gridLayoutWidget_11") self.gridLayoutWidget_11.setGeometry(QRect(0, 0, 751, 223)) self.gridLayout_12 = QGridLayout(self.gridLayoutWidget_11) - self.gridLayout_12.setObjectName(u"gridLayout_12") + self.gridLayout_12.setObjectName("gridLayout_12") self.gridLayout_12.setContentsMargins(0, 0, 0, 0) self.formLayout_2 = QFormLayout() - self.formLayout_2.setObjectName(u"formLayout_2") + self.formLayout_2.setObjectName("formLayout_2") self.label_43 = QLabel(self.gridLayoutWidget_11) - self.label_43.setObjectName(u"label_43") + self.label_43.setObjectName("label_43") self.formLayout_2.setWidget(0, QFormLayout.LabelRole, self.label_43) self.edit_faculty_member_new_title = QComboBox(self.gridLayoutWidget_11) - self.edit_faculty_member_new_title.setObjectName(u"edit_faculty_member_new_title") + self.edit_faculty_member_new_title.setObjectName( + "edit_faculty_member_new_title" + ) self.edit_faculty_member_new_title.setEditable(True) - self.formLayout_2.setWidget(0, QFormLayout.FieldRole, self.edit_faculty_member_new_title) + self.formLayout_2.setWidget( + 0, QFormLayout.FieldRole, self.edit_faculty_member_new_title + ) self.label_44 = QLabel(self.gridLayoutWidget_11) - self.label_44.setObjectName(u"label_44") + self.label_44.setObjectName("label_44") self.formLayout_2.setWidget(1, QFormLayout.LabelRole, self.label_44) self.edit_faculty_member_new_surname = QLineEdit(self.gridLayoutWidget_11) - self.edit_faculty_member_new_surname.setObjectName(u"edit_faculty_member_new_surname") + self.edit_faculty_member_new_surname.setObjectName( + "edit_faculty_member_new_surname" + ) - self.formLayout_2.setWidget(1, QFormLayout.FieldRole, self.edit_faculty_member_new_surname) + self.formLayout_2.setWidget( + 1, QFormLayout.FieldRole, self.edit_faculty_member_new_surname + ) self.label_45 = QLabel(self.gridLayoutWidget_11) - self.label_45.setObjectName(u"label_45") + self.label_45.setObjectName("label_45") self.formLayout_2.setWidget(2, QFormLayout.LabelRole, self.label_45) self.user_faculty_member_new_name = QLineEdit(self.gridLayoutWidget_11) - self.user_faculty_member_new_name.setObjectName(u"user_faculty_member_new_name") - - self.formLayout_2.setWidget(2, QFormLayout.FieldRole, self.user_faculty_member_new_name) + self.user_faculty_member_new_name.setObjectName("user_faculty_member_new_name") + self.formLayout_2.setWidget( + 2, QFormLayout.FieldRole, self.user_faculty_member_new_name + ) self.gridLayout_12.addLayout(self.formLayout_2, 2, 2, 1, 1) self.formLayout_4 = QFormLayout() - self.formLayout_4.setObjectName(u"formLayout_4") + self.formLayout_4.setObjectName("formLayout_4") self.edit_faculty_member_title = QLineEdit(self.gridLayoutWidget_11) - self.edit_faculty_member_title.setObjectName(u"edit_faculty_member_title") + self.edit_faculty_member_title.setObjectName("edit_faculty_member_title") self.edit_faculty_member_title.setFocusPolicy(Qt.TabFocus) self.edit_faculty_member_title.setReadOnly(True) - self.formLayout_4.setWidget(0, QFormLayout.LabelRole, self.edit_faculty_member_title) + self.formLayout_4.setWidget( + 0, QFormLayout.LabelRole, self.edit_faculty_member_title + ) self.edit_faculty_member_select_member = QComboBox(self.gridLayoutWidget_11) - self.edit_faculty_member_select_member.setObjectName(u"edit_faculty_member_select_member") + self.edit_faculty_member_select_member.setObjectName( + "edit_faculty_member_select_member" + ) - self.formLayout_4.setWidget(0, QFormLayout.FieldRole, self.edit_faculty_member_select_member) + self.formLayout_4.setWidget( + 0, QFormLayout.FieldRole, self.edit_faculty_member_select_member + ) self.label_46 = QLabel(self.gridLayoutWidget_11) - self.label_46.setObjectName(u"label_46") + self.label_46.setObjectName("label_46") self.formLayout_4.setWidget(1, QFormLayout.LabelRole, self.label_46) self.faculty_member_old_telnr = QLineEdit(self.gridLayoutWidget_11) - self.faculty_member_old_telnr.setObjectName(u"faculty_member_old_telnr") + self.faculty_member_old_telnr.setObjectName("faculty_member_old_telnr") self.faculty_member_old_telnr.setFocusPolicy(Qt.ClickFocus) self.faculty_member_old_telnr.setReadOnly(True) - self.formLayout_4.setWidget(1, QFormLayout.FieldRole, self.faculty_member_old_telnr) + self.formLayout_4.setWidget( + 1, QFormLayout.FieldRole, self.faculty_member_old_telnr + ) self.label_49 = QLabel(self.gridLayoutWidget_11) - self.label_49.setObjectName(u"label_49") + self.label_49.setObjectName("label_49") self.formLayout_4.setWidget(2, QFormLayout.LabelRole, self.label_49) self.faculty_member_oldmail = QLineEdit(self.gridLayoutWidget_11) - self.faculty_member_oldmail.setObjectName(u"faculty_member_oldmail") + self.faculty_member_oldmail.setObjectName("faculty_member_oldmail") self.faculty_member_oldmail.setFocusPolicy(Qt.NoFocus) self.faculty_member_oldmail.setInputMethodHints(Qt.ImhNone) self.faculty_member_oldmail.setReadOnly(True) - self.formLayout_4.setWidget(2, QFormLayout.FieldRole, self.faculty_member_oldmail) - + self.formLayout_4.setWidget( + 2, QFormLayout.FieldRole, self.faculty_member_oldmail + ) self.gridLayout_12.addLayout(self.formLayout_4, 0, 2, 1, 1) self.formLayout_5 = QFormLayout() - self.formLayout_5.setObjectName(u"formLayout_5") + self.formLayout_5.setObjectName("formLayout_5") self.label_47 = QLabel(self.gridLayoutWidget_11) - self.label_47.setObjectName(u"label_47") + self.label_47.setObjectName("label_47") self.formLayout_5.setWidget(0, QFormLayout.LabelRole, self.label_47) self.label_48 = QLabel(self.gridLayoutWidget_11) - self.label_48.setObjectName(u"label_48") + self.label_48.setObjectName("label_48") self.formLayout_5.setWidget(1, QFormLayout.LabelRole, self.label_48) self.user_faculty_member_new_mail = QLineEdit(self.gridLayoutWidget_11) - self.user_faculty_member_new_mail.setObjectName(u"user_faculty_member_new_mail") + self.user_faculty_member_new_mail.setObjectName("user_faculty_member_new_mail") - self.formLayout_5.setWidget(0, QFormLayout.FieldRole, self.user_faculty_member_new_mail) + self.formLayout_5.setWidget( + 0, QFormLayout.FieldRole, self.user_faculty_member_new_mail + ) self.user_faculty_member_new_telnr = QLineEdit(self.gridLayoutWidget_11) - self.user_faculty_member_new_telnr.setObjectName(u"user_faculty_member_new_telnr") - - self.formLayout_5.setWidget(1, QFormLayout.FieldRole, self.user_faculty_member_new_telnr) + self.user_faculty_member_new_telnr.setObjectName( + "user_faculty_member_new_telnr" + ) + self.formLayout_5.setWidget( + 1, QFormLayout.FieldRole, self.user_faculty_member_new_telnr + ) self.gridLayout_12.addLayout(self.formLayout_5, 2, 4, 1, 1) self.label_41 = QLabel(self.gridLayoutWidget_11) - self.label_41.setObjectName(u"label_41") + self.label_41.setObjectName("label_41") self.gridLayout_12.addWidget(self.label_41, 0, 0, 1, 1) self.update_faculty_member = QPushButton(self.gridLayoutWidget_11) - self.update_faculty_member.setObjectName(u"update_faculty_member") + self.update_faculty_member.setObjectName("update_faculty_member") self.gridLayout_12.addWidget(self.update_faculty_member, 3, 4, 1, 1) self.label_42 = QLabel(self.gridLayoutWidget_11) - self.label_42.setObjectName(u"label_42") + self.label_42.setObjectName("label_42") self.gridLayout_12.addWidget(self.label_42, 2, 0, 1, 1) @@ -1138,40 +1264,38 @@ class Ui_MainWindow(object): self.gridLayout.addWidget(self.tabWidget, 0, 0, 1, 1) - self.horizontalLayout.addLayout(self.gridLayout) - self.verticalLayout.addLayout(self.horizontalLayout) self.horizontalLayoutWidget = QWidget(self.centralwidget) - self.horizontalLayoutWidget.setObjectName(u"horizontalLayoutWidget") + self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget") self.horizontalLayoutWidget.setGeometry(QRect(1280, 360, 311, 391)) self.horizontalLayout_6 = QHBoxLayout(self.horizontalLayoutWidget) - self.horizontalLayout_6.setObjectName(u"horizontalLayout_6") + self.horizontalLayout_6.setObjectName("horizontalLayout_6") self.horizontalLayout_6.setContentsMargins(0, 0, 0, 0) self.frame_creation_progress = QFrame(self.horizontalLayoutWidget) - self.frame_creation_progress.setObjectName(u"frame_creation_progress") + self.frame_creation_progress.setObjectName("frame_creation_progress") self.verticalLayout_4 = QVBoxLayout(self.frame_creation_progress) self.verticalLayout_4.setSpacing(6) - self.verticalLayout_4.setObjectName(u"verticalLayout_4") + self.verticalLayout_4.setObjectName("verticalLayout_4") self.groupBox_2 = QGroupBox(self.frame_creation_progress) - self.groupBox_2.setObjectName(u"groupBox_2") + self.groupBox_2.setObjectName("groupBox_2") self.groupBox_2.setEnabled(True) sizePolicy4.setHeightForWidth(self.groupBox_2.sizePolicy().hasHeightForWidth()) self.groupBox_2.setSizePolicy(sizePolicy4) self.appdata_check = QCheckBox(self.groupBox_2) - self.appdata_check.setObjectName(u"appdata_check") + self.appdata_check.setObjectName("appdata_check") self.appdata_check.setGeometry(QRect(20, 30, 241, 41)) self.appdata_check.setFont(font4) self.appdata_check.setFocusPolicy(Qt.NoFocus) self.media_check = QCheckBox(self.groupBox_2) - self.media_check.setObjectName(u"media_check") + self.media_check.setObjectName("media_check") self.media_check.setGeometry(QRect(20, 70, 241, 41)) self.media_check.setFont(font4) self.media_check.setFocusPolicy(Qt.NoFocus) self.ids_check = QCheckBox(self.groupBox_2) - self.ids_check.setObjectName(u"ids_check") + self.ids_check.setObjectName("ids_check") self.ids_check.setGeometry(QRect(20, 140, 241, 41)) self.ids_check.setFont(font4) self.ids_check.setFocusPolicy(Qt.NoFocus) @@ -1179,38 +1303,43 @@ class Ui_MainWindow(object): self.verticalLayout_4.addWidget(self.groupBox_2) self.groupBox = QGroupBox(self.frame_creation_progress) - self.groupBox.setObjectName(u"groupBox") + self.groupBox.setObjectName("groupBox") sizePolicy4.setHeightForWidth(self.groupBox.sizePolicy().hasHeightForWidth()) self.groupBox.setSizePolicy(sizePolicy4) self.media_checked = QCheckBox(self.groupBox) - self.media_checked.setObjectName(u"media_checked") + self.media_checked.setObjectName("media_checked") self.media_checked.setGeometry(QRect(20, 30, 241, 41)) font6 = QFont() font6.setPointSize(8) font6.setBold(False) - font6.setItalic(False) + font6.setItal # ic(False) font6.setUnderline(False) font6.setKerning(True) font6.setStyleStrategy(QFont.PreferDefault) self.media_checked.setFont(font6) self.media_checked.setFocusPolicy(Qt.NoFocus) self.media_edited_check = QCheckBox(self.groupBox) - self.media_edited_check.setObjectName(u"media_edited_check") + self.media_edited_check.setObjectName("media_edited_check") self.media_edited_check.setGeometry(QRect(20, 70, 241, 41)) self.media_edited_check.setFont(font6) self.media_edited_check.setFocusPolicy(Qt.NoFocus) self.app_created = QCheckBox(self.groupBox) - self.app_created.setObjectName(u"app_created") + self.app_created.setObjectName("app_created") self.app_created.setGeometry(QRect(20, 110, 161, 41)) self.app_created.setFont(font6) self.app_created.setFocusPolicy(Qt.NoFocus) self.btn_copy_adis_command = QPushButton(self.groupBox) - self.btn_copy_adis_command.setObjectName(u"btn_copy_adis_command") + self.btn_copy_adis_command.setObjectName("btn_copy_adis_command") self.btn_copy_adis_command.setGeometry(QRect(170, 120, 101, 23)) self.btn_copy_adis_command.setFont(font6) self.btn_copy_adis_command.setAutoFillBackground(False) icon = QIcon() - icon.addFile(u"../../../.designer/backup/icons/information.png", QSize(), QIcon.Normal, QIcon.Off) + icon.addFile( + "../../../.designer/backup/icons/information.png", + QSize(), + QIcon.Normal, + QIcon.Off, + ) self.btn_copy_adis_command.setIcon(icon) self.btn_copy_adis_command.setCheckable(False) self.btn_copy_adis_command.setChecked(False) @@ -1218,61 +1347,60 @@ class Ui_MainWindow(object): self.verticalLayout_4.addWidget(self.groupBox) - self.horizontalLayout_6.addWidget(self.frame_creation_progress) self.frame_2 = QFrame(self.centralwidget) - self.frame_2.setObjectName(u"frame_2") + self.frame_2.setObjectName("frame_2") self.frame_2.setGeometry(QRect(1280, 10, 301, 341)) self.frame_2.setFrameShape(QFrame.StyledPanel) self.frame_2.setFrameShadow(QFrame.Raised) self.calendarWidget = QCalendarWidget(self.frame_2) - self.calendarWidget.setObjectName(u"calendarWidget") + self.calendarWidget.setObjectName("calendarWidget") self.calendarWidget.setGeometry(QRect(0, 0, 291, 191)) self.calendarWidget.setFocusPolicy(Qt.NoFocus) self.calendarWidget.setGridVisible(True) self.calendarWidget.setVerticalHeaderFormat(QCalendarWidget.NoVerticalHeader) self.message_frame = QFrame(self.frame_2) - self.message_frame.setObjectName(u"message_frame") + self.message_frame.setObjectName("message_frame") self.message_frame.setGeometry(QRect(0, 210, 301, 121)) self.message_frame.setFrameShape(QFrame.StyledPanel) self.message_frame.setFrameShadow(QFrame.Raised) self.label_14 = QLabel(self.message_frame) - self.label_14.setObjectName(u"label_14") + self.label_14.setObjectName("label_14") self.label_14.setGeometry(QRect(10, 10, 47, 20)) self.line_app_info = QLineEdit(self.message_frame) - self.line_app_info.setObjectName(u"line_app_info") + self.line_app_info.setObjectName("line_app_info") self.line_app_info.setEnabled(True) self.line_app_info.setGeometry(QRect(60, 10, 31, 20)) self.line_app_info.setFocusPolicy(Qt.NoFocus) self.message_box = QTextEdit(self.message_frame) - self.message_box.setObjectName(u"message_box") + self.message_box.setObjectName("message_box") self.message_box.setGeometry(QRect(10, 40, 281, 71)) self.message_box.setFocusPolicy(Qt.NoFocus) self.btn_delete_message = QPushButton(self.message_frame) - self.btn_delete_message.setObjectName(u"btn_delete_message") + self.btn_delete_message.setObjectName("btn_delete_message") self.btn_delete_message.setGeometry(QRect(130, 10, 75, 23)) self.btn_delete_message.setFocusPolicy(Qt.NoFocus) self.spin_select_message = QSpinBox(self.message_frame) - self.spin_select_message.setObjectName(u"spin_select_message") + self.spin_select_message.setObjectName("spin_select_message") self.spin_select_message.setGeometry(QRect(210, 10, 74, 22)) self.spin_select_message.setMinimum(1) self.label_total_day_messages = QLabel(self.message_frame) - self.label_total_day_messages.setObjectName(u"label_total_day_messages") + self.label_total_day_messages.setObjectName("label_total_day_messages") self.label_total_day_messages.setGeometry(QRect(240, 10, 21, 22)) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QMenuBar(MainWindow) - self.menubar.setObjectName(u"menubar") + self.menubar.setObjectName("menubar") self.menubar.setGeometry(QRect(0, 0, 1601, 30)) self.menuDatei = QMenu(self.menubar) - self.menuDatei.setObjectName(u"menuDatei") + self.menuDatei.setObjectName("menuDatei") self.menuEinstellungen = QMenu(self.menubar) - self.menuEinstellungen.setObjectName(u"menuEinstellungen") + self.menuEinstellungen.setObjectName("menuEinstellungen") self.menuHelp = QMenu(self.menubar) - self.menuHelp.setObjectName(u"menuHelp") + self.menuHelp.setObjectName("menuHelp") MainWindow.setMenuBar(self.menubar) self.statusbar = QStatusBar(MainWindow) - self.statusbar.setObjectName(u"statusbar") + self.statusbar.setObjectName("statusbar") MainWindow.setStatusBar(self.statusbar) QWidget.setTabOrder(self.drpdwn_app_nr, self.drpdwn_prof_name) QWidget.setTabOrder(self.drpdwn_prof_name, self.prof_mail) @@ -1299,22 +1427,44 @@ class Ui_MainWindow(object): QWidget.setTabOrder(self.user_frame_userrole, self.user_create_frame_password) QWidget.setTabOrder(self.user_create_frame_password, self.user_frame_addUser) QWidget.setTabOrder(self.user_frame_addUser, self.user_delete_frame_user_select) - QWidget.setTabOrder(self.user_delete_frame_user_select, self.user_delete_confirm) + QWidget.setTabOrder( + self.user_delete_frame_user_select, self.user_delete_confirm + ) QWidget.setTabOrder(self.user_delete_confirm, self.pushButton) QWidget.setTabOrder(self.pushButton, self.user_edit_frame_user_select) - QWidget.setTabOrder(self.user_edit_frame_user_select, self.user_edit_frame_role_select) - QWidget.setTabOrder(self.user_edit_frame_role_select, self.user_edit_frame_new_password) + QWidget.setTabOrder( + self.user_edit_frame_user_select, self.user_edit_frame_role_select + ) + QWidget.setTabOrder( + self.user_edit_frame_role_select, self.user_edit_frame_new_password + ) QWidget.setTabOrder(self.user_edit_frame_new_password, self.update_user) QWidget.setTabOrder(self.update_user, self.edit_faculty_member_title) - QWidget.setTabOrder(self.edit_faculty_member_title, self.edit_faculty_member_select_member) - QWidget.setTabOrder(self.edit_faculty_member_select_member, self.faculty_member_old_telnr) + QWidget.setTabOrder( + self.edit_faculty_member_title, self.edit_faculty_member_select_member + ) + QWidget.setTabOrder( + self.edit_faculty_member_select_member, self.faculty_member_old_telnr + ) QWidget.setTabOrder(self.faculty_member_old_telnr, self.faculty_member_oldmail) - QWidget.setTabOrder(self.faculty_member_oldmail, self.edit_faculty_member_new_title) - QWidget.setTabOrder(self.edit_faculty_member_new_title, self.edit_faculty_member_new_surname) - QWidget.setTabOrder(self.edit_faculty_member_new_surname, self.user_faculty_member_new_name) - QWidget.setTabOrder(self.user_faculty_member_new_name, self.user_faculty_member_new_mail) - QWidget.setTabOrder(self.user_faculty_member_new_mail, self.user_faculty_member_new_telnr) - QWidget.setTabOrder(self.user_faculty_member_new_telnr, self.update_faculty_member) + QWidget.setTabOrder( + self.faculty_member_oldmail, self.edit_faculty_member_new_title + ) + QWidget.setTabOrder( + self.edit_faculty_member_new_title, self.edit_faculty_member_new_surname + ) + QWidget.setTabOrder( + self.edit_faculty_member_new_surname, self.user_faculty_member_new_name + ) + QWidget.setTabOrder( + self.user_faculty_member_new_name, self.user_faculty_member_new_mail + ) + QWidget.setTabOrder( + self.user_faculty_member_new_mail, self.user_faculty_member_new_telnr + ) + QWidget.setTabOrder( + self.user_faculty_member_new_telnr, self.update_faculty_member + ) QWidget.setTabOrder(self.update_faculty_member, self.box_fach) QWidget.setTabOrder(self.box_fach, self.box_person) QWidget.setTabOrder(self.box_person, self.btn_search) @@ -1340,214 +1490,474 @@ class Ui_MainWindow(object): self.stackedWidget_4.setCurrentIndex(1) self.tabWidget_3.setCurrentIndex(1) - QMetaObject.connectSlotsByName(MainWindow) + # setupUi def retranslateUi(self, MainWindow): - MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"Semesterapparatsmanagement", None)) -#if QT_CONFIG(statustip) + MainWindow.setWindowTitle( + QCoreApplication.translate("MainWindow", "Semesterapparatsmanagement", None) + ) + # if QT_CONFIG(statustip) MainWindow.setStatusTip("") -#endif // QT_CONFIG(statustip) - self.actionBeenden.setText(QCoreApplication.translate("MainWindow", u"Beenden", None)) -#if QT_CONFIG(shortcut) - self.actionBeenden.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+Q", None)) -#endif // QT_CONFIG(shortcut) - self.actionEinstellungen.setText(QCoreApplication.translate("MainWindow", u"Einstellungen", None)) -#if QT_CONFIG(shortcut) - self.actionEinstellungen.setShortcut(QCoreApplication.translate("MainWindow", u"Alt+S", None)) -#endif // QT_CONFIG(shortcut) - self.actionDokumentation.setText(QCoreApplication.translate("MainWindow", u"Dokumentation", None)) -#if QT_CONFIG(shortcut) - self.actionDokumentation.setShortcut(QCoreApplication.translate("MainWindow", u"F1", None)) -#endif // QT_CONFIG(shortcut) -#if QT_CONFIG(tooltip) - self.load_app.setToolTip(QCoreApplication.translate("MainWindow", u"Load the Semesterapparate from the database", None)) -#endif // QT_CONFIG(tooltip) - self.load_app.setText(QCoreApplication.translate("MainWindow", u"App. aufrufen", None)) - self.create_new_app.setText(QCoreApplication.translate("MainWindow", u"neu. App anlegen", None)) - self.cancel_active_selection.setText(QCoreApplication.translate("MainWindow", u"Auswahl abbrechen", None)) + # endif // QT_CONFIG(statustip) + self.actionBeenden.setText( + QCoreApplication.translate("MainWindow", "Beenden", None) + ) + # if QT_CONFIG(shortcut) + self.actionBeenden.setShortcut( + QCoreApplication.translate("MainWindow", "Ctrl+Q", None) + ) + # endif // QT_CONFIG(shortcut) + self.actionEinstellungen.setText( + QCoreApplication.translate("MainWindow", "Einstellungen", None) + ) + # if QT_CONFIG(shortcut) + self.actionEinstellungen.setShortcut( + QCoreApplication.translate("MainWindow", "Alt+S", None) + ) + # endif // QT_CONFIG(shortcut) + self.actionDokumentation.setText( + QCoreApplication.translate("MainWindow", "Dokumentation", None) + ) + # if QT_CONFIG(shortcut) + self.actionDokumentation.setShortcut( + QCoreApplication.translate("MainWindow", "F1", None) + ) + # endif // QT_CONFIG(shortcut) + # if QT_CONFIG(tooltip) + self.load_app.setToolTip( + QCoreApplication.translate( + "MainWindow", "Load the Semesterapparate from the database", None + ) + ) + # endif // QT_CONFIG(tooltip) + self.load_app.setText( + QCoreApplication.translate("MainWindow", "App. aufrufen", None) + ) + self.create_new_app.setText( + QCoreApplication.translate("MainWindow", "neu. App anlegen", None) + ) + self.cancel_active_selection.setText( + QCoreApplication.translate("MainWindow", "Auswahl abbrechen", None) + ) ___qtablewidgetitem = self.tableWidget_apparate.horizontalHeaderItem(0) - ___qtablewidgetitem.setText(QCoreApplication.translate("MainWindow", u"AppNr", None)); + ___qtablewidgetitem.setText( + QCoreApplication.translate("MainWindow", "AppNr", None) + ) ___qtablewidgetitem1 = self.tableWidget_apparate.horizontalHeaderItem(1) - ___qtablewidgetitem1.setText(QCoreApplication.translate("MainWindow", u"App Name", None)); + ___qtablewidgetitem1.setText( + QCoreApplication.translate("MainWindow", "App Name", None) + ) ___qtablewidgetitem2 = self.tableWidget_apparate.horizontalHeaderItem(2) - ___qtablewidgetitem2.setText(QCoreApplication.translate("MainWindow", u"Professor", None)); + ___qtablewidgetitem2.setText( + QCoreApplication.translate("MainWindow", "Professor", None) + ) ___qtablewidgetitem3 = self.tableWidget_apparate.horizontalHeaderItem(3) - ___qtablewidgetitem3.setText(QCoreApplication.translate("MainWindow", u"g\u00fcltig bis", None)); + ___qtablewidgetitem3.setText( + QCoreApplication.translate("MainWindow", "g\u00fcltig bis", None) + ) ___qtablewidgetitem4 = self.tableWidget_apparate.horizontalHeaderItem(4) - ___qtablewidgetitem4.setText(QCoreApplication.translate("MainWindow", u"Dauerapparat", None)); + ___qtablewidgetitem4.setText( + QCoreApplication.translate("MainWindow", "Dauerapparat", None) + ) ___qtablewidgetitem5 = self.tableWidget_apparate.horizontalHeaderItem(5) - ___qtablewidgetitem5.setText(QCoreApplication.translate("MainWindow", u"KontoNr", None)); - self.app_group_box.setTitle(QCoreApplication.translate("MainWindow", u"Apparatsdetails", None)) + ___qtablewidgetitem5.setText( + QCoreApplication.translate("MainWindow", "KontoNr", None) + ) + self.app_group_box.setTitle( + QCoreApplication.translate("MainWindow", "Apparatsdetails", None) + ) ___qtablewidgetitem6 = self.dokument_list.horizontalHeaderItem(0) - ___qtablewidgetitem6.setText(QCoreApplication.translate("MainWindow", u"Dokumentname", None)); + ___qtablewidgetitem6.setText( + QCoreApplication.translate("MainWindow", "Dokumentname", None) + ) ___qtablewidgetitem7 = self.dokument_list.horizontalHeaderItem(1) - ___qtablewidgetitem7.setText(QCoreApplication.translate("MainWindow", u"Dateityp", None)); + ___qtablewidgetitem7.setText( + QCoreApplication.translate("MainWindow", "Dateityp", None) + ) ___qtablewidgetitem8 = self.dokument_list.horizontalHeaderItem(2) - ___qtablewidgetitem8.setText(QCoreApplication.translate("MainWindow", u"Neu?", None)); + ___qtablewidgetitem8.setText( + QCoreApplication.translate("MainWindow", "Neu?", None) + ) ___qtablewidgetitem9 = self.dokument_list.horizontalHeaderItem(3) - ___qtablewidgetitem9.setText(QCoreApplication.translate("MainWindow", u"path", None)); - self.label_5.setText(QCoreApplication.translate("MainWindow", u"Apparatsname", None)) - self.sem_winter.setText(QCoreApplication.translate("MainWindow", u"Winter", None)) - self.label_4.setText(QCoreApplication.translate("MainWindow", u"Prof. Name", None)) - self.sem_sommer.setText(QCoreApplication.translate("MainWindow", u"Sommer", None)) - self.label_3.setText(QCoreApplication.translate("MainWindow", u"Prof. Titel", None)) - self.label_6.setText(QCoreApplication.translate("MainWindow", u"Semester", None)) - self.sem_year.setPlaceholderText(QCoreApplication.translate("MainWindow", u"2023", None)) - self.label_2.setText(QCoreApplication.translate("MainWindow", u"Apparatsnummer", None)) -#if QT_CONFIG(statustip) - self.btn_apparat_save.setStatusTip(QCoreApplication.translate("MainWindow", u"searching", None)) -#endif // QT_CONFIG(statustip) - self.btn_apparat_save.setText(QCoreApplication.translate("MainWindow", u"Speichern", None)) - self.btn_apparat_apply.setText(QCoreApplication.translate("MainWindow", u"Aktualisieren", None)) - self.check_eternal_app.setText(QCoreApplication.translate("MainWindow", u"Dauerapparat", None)) - self.label_8.setText(QCoreApplication.translate("MainWindow", u"Mail", None)) + ___qtablewidgetitem9.setText( + QCoreApplication.translate("MainWindow", "path", None) + ) + self.label_5.setText( + QCoreApplication.translate("MainWindow", "Apparatsname", None) + ) + self.sem_winter.setText( + QCoreApplication.translate("MainWindow", "Winter", None) + ) + self.label_4.setText( + QCoreApplication.translate("MainWindow", "Prof. Name", None) + ) + self.sem_sommer.setText( + QCoreApplication.translate("MainWindow", "Sommer", None) + ) + self.label_3.setText( + QCoreApplication.translate("MainWindow", "Prof. Titel", None) + ) + self.label_6.setText(QCoreApplication.translate("MainWindow", "Semester", None)) + self.sem_year.setPlaceholderText( + QCoreApplication.translate("MainWindow", "2023", None) + ) + self.label_2.setText( + QCoreApplication.translate("MainWindow", "Apparatsnummer", None) + ) + # if QT_CONFIG(statustip) + self.btn_apparat_save.setStatusTip( + QCoreApplication.translate("MainWindow", "searching", None) + ) + # endif // QT_CONFIG(statustip) + self.btn_apparat_save.setText( + QCoreApplication.translate("MainWindow", "Speichern", None) + ) + self.btn_apparat_apply.setText( + QCoreApplication.translate("MainWindow", "Aktualisieren", None) + ) + self.check_eternal_app.setText( + QCoreApplication.translate("MainWindow", "Dauerapparat", None) + ) + self.label_8.setText(QCoreApplication.translate("MainWindow", "Mail", None)) self.prof_mail.setPlaceholderText("") - self.label_9.setText(QCoreApplication.translate("MainWindow", u"Tel", None)) + self.label_9.setText(QCoreApplication.translate("MainWindow", "Tel", None)) self.prof_tel_nr.setPlaceholderText("") - self.label_10.setText(QCoreApplication.translate("MainWindow", u"Fach", None)) + self.label_10.setText(QCoreApplication.translate("MainWindow", "Fach", None)) self.drpdwn_prof_name.setCurrentText("") - self.mail_mand.setText(QCoreApplication.translate("MainWindow", u"*", None)) - self.telnr_mand.setText(QCoreApplication.translate("MainWindow", u"*", None)) - self.profname_mand.setText(QCoreApplication.translate("MainWindow", u"*", None)) - self.appname_mand.setText(QCoreApplication.translate("MainWindow", u"*", None)) - self.fach_mand.setText(QCoreApplication.translate("MainWindow", u"*", None)) - self._mand.setText(QCoreApplication.translate("MainWindow", u"*", None)) - self.btn_add_document.setText(QCoreApplication.translate("MainWindow", u"Dokument hinzuf\u00fcgen", None)) - self.btn_open_document.setText(QCoreApplication.translate("MainWindow", u"Dokument \u00f6ffnen", None)) -#if QT_CONFIG(tooltip) - self.check_file.setToolTip(QCoreApplication.translate("MainWindow", u"Abh\u00e4ngig von der Anzahl der Medien kann die Suche sehr lange dauern", None)) -#endif // QT_CONFIG(tooltip) - self.check_file.setText(QCoreApplication.translate("MainWindow", u"Medien aus Dokument\n" -" hinzuf\u00fcgen", None)) - self.label_12.setText(QCoreApplication.translate("MainWindow", u"Prof-ID-aDIS", None)) - self.label_13.setText(QCoreApplication.translate("MainWindow", u"Apparat-ID-aDIS", None)) - self.check_send_mail.setText(QCoreApplication.translate("MainWindow", u"Mail senden", None)) + self.mail_mand.setText(QCoreApplication.translate("MainWindow", "*", None)) + self.telnr_mand.setText(QCoreApplication.translate("MainWindow", "*", None)) + self.profname_mand.setText(QCoreApplication.translate("MainWindow", "*", None)) + self.appname_mand.setText(QCoreApplication.translate("MainWindow", "*", None)) + self.fach_mand.setText(QCoreApplication.translate("MainWindow", "*", None)) + self._mand.setText(QCoreApplication.translate("MainWindow", "*", None)) + self.btn_add_document.setText( + QCoreApplication.translate("MainWindow", "Dokument hinzuf\u00fcgen", None) + ) + self.btn_open_document.setText( + QCoreApplication.translate("MainWindow", "Dokument \u00f6ffnen", None) + ) + # if QT_CONFIG(tooltip) + self.check_file.setToolTip( + QCoreApplication.translate( + "MainWindow", + "Abh\u00e4ngig von der Anzahl der Medien kann die Suche sehr lange dauern", + None, + ) + ) + # endif // QT_CONFIG(tooltip) + self.check_file.setText( + QCoreApplication.translate( + "MainWindow", "Medien aus Dokument\n" " hinzuf\u00fcgen", None + ) + ) + self.label_12.setText( + QCoreApplication.translate("MainWindow", "Prof-ID-aDIS", None) + ) + self.label_13.setText( + QCoreApplication.translate("MainWindow", "Apparat-ID-aDIS", None) + ) + self.check_send_mail.setText( + QCoreApplication.translate("MainWindow", "Mail senden", None) + ) ___qtablewidgetitem10 = self.tableWidget_apparat_media.horizontalHeaderItem(0) - ___qtablewidgetitem10.setText(QCoreApplication.translate("MainWindow", u"Buchtitel", None)); -#if QT_CONFIG(tooltip) - ___qtablewidgetitem10.setToolTip(QCoreApplication.translate("MainWindow", u"Es kann sein, dass der Buchtitel leer ist, dies kommt vor, wenn der Titel nicht passend formatiert ist", None)); -#endif // QT_CONFIG(tooltip) + ___qtablewidgetitem10.setText( + QCoreApplication.translate("MainWindow", "Buchtitel", None) + ) + # if QT_CONFIG(tooltip) + ___qtablewidgetitem10.setToolTip( + QCoreApplication.translate( + "MainWindow", + "Es kann sein, dass der Buchtitel leer ist, dies kommt vor, wenn der Titel nicht passend formatiert ist", + None, + ) + ) + # endif // QT_CONFIG(tooltip) ___qtablewidgetitem11 = self.tableWidget_apparat_media.horizontalHeaderItem(1) - ___qtablewidgetitem11.setText(QCoreApplication.translate("MainWindow", u"Signatur", None)); + ___qtablewidgetitem11.setText( + QCoreApplication.translate("MainWindow", "Signatur", None) + ) ___qtablewidgetitem12 = self.tableWidget_apparat_media.horizontalHeaderItem(2) - ___qtablewidgetitem12.setText(QCoreApplication.translate("MainWindow", u"Auflage", None)); + ___qtablewidgetitem12.setText( + QCoreApplication.translate("MainWindow", "Auflage", None) + ) ___qtablewidgetitem13 = self.tableWidget_apparat_media.horizontalHeaderItem(3) - ___qtablewidgetitem13.setText(QCoreApplication.translate("MainWindow", u"Autor", None)); + ___qtablewidgetitem13.setText( + QCoreApplication.translate("MainWindow", "Autor", None) + ) ___qtablewidgetitem14 = self.tableWidget_apparat_media.horizontalHeaderItem(4) - ___qtablewidgetitem14.setText(QCoreApplication.translate("MainWindow", u"im Apparat?", None)); -#if QT_CONFIG(tooltip) - ___qtablewidgetitem14.setToolTip(QCoreApplication.translate("MainWindow", u"Diese Angabe ist nicht zuverl\u00e4ssig. Ist das \u274c vorhanden, kann das Medium im Apparat sein, aber aufgrund eines Bugs nicht gefunden worden", None)); -#endif // QT_CONFIG(tooltip) + ___qtablewidgetitem14.setText( + QCoreApplication.translate("MainWindow", "im Apparat?", None) + ) + # if QT_CONFIG(tooltip) + ___qtablewidgetitem14.setToolTip( + QCoreApplication.translate( + "MainWindow", + "Diese Angabe ist nicht zuverl\u00e4ssig. Ist das \u274c vorhanden, kann das Medium im Apparat sein, aber aufgrund eines Bugs nicht gefunden worden", + None, + ) + ) + # endif // QT_CONFIG(tooltip) ___qtablewidgetitem15 = self.tableWidget_apparat_media.horizontalHeaderItem(5) - ___qtablewidgetitem15.setText(QCoreApplication.translate("MainWindow", u"Vorgemerkt", None)); + ___qtablewidgetitem15.setText( + QCoreApplication.translate("MainWindow", "Vorgemerkt", None) + ) ___qtablewidgetitem16 = self.tableWidget_apparat_media.horizontalHeaderItem(6) - ___qtablewidgetitem16.setText(QCoreApplication.translate("MainWindow", u"Link", None)); - self.label.setText(QCoreApplication.translate("MainWindow", u" Medienliste", None)) - self.chkbx_show_del_media.setText(QCoreApplication.translate("MainWindow", u"gel. Medien anzeigen", None)) - self.btn_reserve.setText(QCoreApplication.translate("MainWindow", u"im Apparat?", None)) - self.label_info.setText(QCoreApplication.translate("MainWindow", u"Medien werden hinzugef\u00fcgt", None)) - self.progress_label.setText(QCoreApplication.translate("MainWindow", u"Medium x/y", None)) - self.label_20.setText(QCoreApplication.translate("MainWindow", u"Medien werden gepr\u00fcft", None)) - self.avail_status.setText(QCoreApplication.translate("MainWindow", u"TextLabel", None)) - self.add_medium.setText(QCoreApplication.translate("MainWindow", u"Medien hinzuf\u00fcgen", None)) - self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), QCoreApplication.translate("MainWindow", u"Anlegen", None)) - self.btn_search.setText(QCoreApplication.translate("MainWindow", u"Suchen", None)) - self.label_7.setText(QCoreApplication.translate("MainWindow", u"Appnr.:", None)) - self.label_18.setText(QCoreApplication.translate("MainWindow", u"Dauerapp:", None)) - self.label_17.setText(QCoreApplication.translate("MainWindow", u"Endsemester:", None)) - self.label_19.setText(QCoreApplication.translate("MainWindow", u"Erstellsemester:", None)) - self.label_11.setText(QCoreApplication.translate("MainWindow", u"Person:", None)) - self.label_16.setText(QCoreApplication.translate("MainWindow", u"Fach:", None)) - self.label_15.setText(QCoreApplication.translate("MainWindow", u"L\u00f6schbar", None)) + ___qtablewidgetitem16.setText( + QCoreApplication.translate("MainWindow", "Link", None) + ) + self.label.setText( + QCoreApplication.translate("MainWindow", " Medienliste", None) + ) + self.chkbx_show_del_media.setText( + QCoreApplication.translate("MainWindow", "gel. Medien anzeigen", None) + ) + self.btn_reserve.setText( + QCoreApplication.translate("MainWindow", "im Apparat?", None) + ) + self.label_info.setText( + QCoreApplication.translate( + "MainWindow", "Medien werden hinzugef\u00fcgt", None + ) + ) + self.progress_label.setText( + QCoreApplication.translate("MainWindow", "Medium x/y", None) + ) + self.label_20.setText( + QCoreApplication.translate("MainWindow", "Medien werden gepr\u00fcft", None) + ) + self.avail_status.setText( + QCoreApplication.translate("MainWindow", "TextLabel", None) + ) + self.add_medium.setText( + QCoreApplication.translate("MainWindow", "Medien hinzuf\u00fcgen", None) + ) + self.tabWidget.setTabText( + self.tabWidget.indexOf(self.tab), + QCoreApplication.translate("MainWindow", "Anlegen", None), + ) + self.btn_search.setText( + QCoreApplication.translate("MainWindow", "Suchen", None) + ) + self.label_7.setText(QCoreApplication.translate("MainWindow", "Appnr.:", None)) + self.label_18.setText( + QCoreApplication.translate("MainWindow", "Dauerapp:", None) + ) + self.label_17.setText( + QCoreApplication.translate("MainWindow", "Endsemester:", None) + ) + self.label_19.setText( + QCoreApplication.translate("MainWindow", "Erstellsemester:", None) + ) + self.label_11.setText(QCoreApplication.translate("MainWindow", "Person:", None)) + self.label_16.setText(QCoreApplication.translate("MainWindow", "Fach:", None)) + self.label_15.setText( + QCoreApplication.translate("MainWindow", "L\u00f6schbar", None) + ) self.check_deletable.setText("") self.db_err_message.setText("") - self.tabWidget_2.setTabText(self.tabWidget_2.indexOf(self.tab_3), QCoreApplication.translate("MainWindow", u"Statistik", None)) - self.label_25.setText(QCoreApplication.translate("MainWindow", u"Signatur", None)) - self.book_search.setText(QCoreApplication.translate("MainWindow", u"Suche", None)) - self.label_26.setText(QCoreApplication.translate("MainWindow", u"Titel", None)) - self.tabWidget_2.setTabText(self.tabWidget_2.indexOf(self.tab_4), QCoreApplication.translate("MainWindow", u"Suchen", None)) + self.tabWidget_2.setTabText( + self.tabWidget_2.indexOf(self.tab_3), + QCoreApplication.translate("MainWindow", "Statistik", None), + ) + self.label_25.setText( + QCoreApplication.translate("MainWindow", "Signatur", None) + ) + self.book_search.setText( + QCoreApplication.translate("MainWindow", "Suche", None) + ) + self.label_26.setText(QCoreApplication.translate("MainWindow", "Titel", None)) + self.tabWidget_2.setTabText( + self.tabWidget_2.indexOf(self.tab_4), + QCoreApplication.translate("MainWindow", "Suchen", None), + ) ___qtablewidgetitem17 = self.statistics_table.horizontalHeaderItem(0) - ___qtablewidgetitem17.setText(QCoreApplication.translate("MainWindow", u"Semester", None)); + ___qtablewidgetitem17.setText( + QCoreApplication.translate("MainWindow", "Semester", None) + ) ___qtablewidgetitem18 = self.statistics_table.horizontalHeaderItem(1) - ___qtablewidgetitem18.setText(QCoreApplication.translate("MainWindow", u"Zugang", None)); + ___qtablewidgetitem18.setText( + QCoreApplication.translate("MainWindow", "Zugang", None) + ) ___qtablewidgetitem19 = self.statistics_table.horizontalHeaderItem(2) - ___qtablewidgetitem19.setText(QCoreApplication.translate("MainWindow", u"Abgang", None)); - self.tabWidget_3.setTabText(self.tabWidget_3.indexOf(self.tab_6), QCoreApplication.translate("MainWindow", u"Tabelle", None)) - self.tabWidget_3.setTabText(self.tabWidget_3.indexOf(self.tab_7), QCoreApplication.translate("MainWindow", u"Erstellte und gel\u00f6schte Semesterapparate", None)) - self.btn_del_select_apparats.setText(QCoreApplication.translate("MainWindow", u"Ausgew\u00e4hlte L\u00f6schen", None)) + ___qtablewidgetitem19.setText( + QCoreApplication.translate("MainWindow", "Abgang", None) + ) + self.tabWidget_3.setTabText( + self.tabWidget_3.indexOf(self.tab_6), + QCoreApplication.translate("MainWindow", "Tabelle", None), + ) + self.tabWidget_3.setTabText( + self.tabWidget_3.indexOf(self.tab_7), + QCoreApplication.translate( + "MainWindow", "Erstellte und gel\u00f6schte Semesterapparate", None + ), + ) + self.btn_del_select_apparats.setText( + QCoreApplication.translate( + "MainWindow", "Ausgew\u00e4hlte L\u00f6schen", None + ) + ) ___qtablewidgetitem20 = self.tableWidget.horizontalHeaderItem(1) - ___qtablewidgetitem20.setText(QCoreApplication.translate("MainWindow", u"Apparatsname", None)); + ___qtablewidgetitem20.setText( + QCoreApplication.translate("MainWindow", "Apparatsname", None) + ) ___qtablewidgetitem21 = self.tableWidget.horizontalHeaderItem(2) - ___qtablewidgetitem21.setText(QCoreApplication.translate("MainWindow", u"Apparatsnummer", None)); + ___qtablewidgetitem21.setText( + QCoreApplication.translate("MainWindow", "Apparatsnummer", None) + ) ___qtablewidgetitem22 = self.tableWidget.horizontalHeaderItem(3) - ___qtablewidgetitem22.setText(QCoreApplication.translate("MainWindow", u"Person", None)); + ___qtablewidgetitem22.setText( + QCoreApplication.translate("MainWindow", "Person", None) + ) ___qtablewidgetitem23 = self.tableWidget.horizontalHeaderItem(4) - ___qtablewidgetitem23.setText(QCoreApplication.translate("MainWindow", u"Fach", None)); + ___qtablewidgetitem23.setText( + QCoreApplication.translate("MainWindow", "Fach", None) + ) ___qtablewidgetitem24 = self.book_search_result.horizontalHeaderItem(0) - ___qtablewidgetitem24.setText(QCoreApplication.translate("MainWindow", u"Titel", None)); + ___qtablewidgetitem24.setText( + QCoreApplication.translate("MainWindow", "Titel", None) + ) ___qtablewidgetitem25 = self.book_search_result.horizontalHeaderItem(1) - ___qtablewidgetitem25.setText(QCoreApplication.translate("MainWindow", u"Signatur", None)); + ___qtablewidgetitem25.setText( + QCoreApplication.translate("MainWindow", "Signatur", None) + ) ___qtablewidgetitem26 = self.book_search_result.horizontalHeaderItem(2) - ___qtablewidgetitem26.setText(QCoreApplication.translate("MainWindow", u"Apparat", None)); - self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), QCoreApplication.translate("MainWindow", u"Suchen / Statistik", None)) - self.label_21.setText(QCoreApplication.translate("MainWindow", u"Aktion:", None)) - self.select_action_box.setItemText(0, QCoreApplication.translate("MainWindow", u"Nutzer anlegen", None)) - self.select_action_box.setItemText(1, QCoreApplication.translate("MainWindow", u"Nutzer l\u00f6schen", None)) - self.select_action_box.setItemText(2, QCoreApplication.translate("MainWindow", u"Nutzer aktualisieren", None)) - self.select_action_box.setItemText(3, QCoreApplication.translate("MainWindow", u"Lehrperson bearbeiten", None)) + ___qtablewidgetitem26.setText( + QCoreApplication.translate("MainWindow", "Apparat", None) + ) + self.tabWidget.setTabText( + self.tabWidget.indexOf(self.tab_2), + QCoreApplication.translate("MainWindow", "Suchen / Statistik", None), + ) + self.label_21.setText(QCoreApplication.translate("MainWindow", "Aktion:", None)) + self.select_action_box.setItemText( + 0, QCoreApplication.translate("MainWindow", "Nutzer anlegen", None) + ) + self.select_action_box.setItemText( + 1, QCoreApplication.translate("MainWindow", "Nutzer l\u00f6schen", None) + ) + self.select_action_box.setItemText( + 2, QCoreApplication.translate("MainWindow", "Nutzer aktualisieren", None) + ) + self.select_action_box.setItemText( + 3, QCoreApplication.translate("MainWindow", "Lehrperson bearbeiten", None) + ) - self.label_22.setText(QCoreApplication.translate("MainWindow", u"Nutzername", None)) - self.label_24.setText(QCoreApplication.translate("MainWindow", u"Rolle", None)) - self.label_23.setText(QCoreApplication.translate("MainWindow", u"Passwort", None)) - self.user_frame_addUser.setText(QCoreApplication.translate("MainWindow", u"Anlegen", None)) + self.label_22.setText( + QCoreApplication.translate("MainWindow", "Nutzername", None) + ) + self.label_24.setText(QCoreApplication.translate("MainWindow", "Rolle", None)) + self.label_23.setText( + QCoreApplication.translate("MainWindow", "Passwort", None) + ) + self.user_frame_addUser.setText( + QCoreApplication.translate("MainWindow", "Anlegen", None) + ) self.user_frame_err_message.setText("") - self.pushButton.setText(QCoreApplication.translate("MainWindow", u"Nutzer l\u00f6schen", None)) - self.label_34.setText(QCoreApplication.translate("MainWindow", u"Nutzername", None)) - self.user_delete_confirm.setText(QCoreApplication.translate("MainWindow", u"Wirklich l\u00f6schen?", None)) + self.pushButton.setText( + QCoreApplication.translate("MainWindow", "Nutzer l\u00f6schen", None) + ) + self.label_34.setText( + QCoreApplication.translate("MainWindow", "Nutzername", None) + ) + self.user_delete_confirm.setText( + QCoreApplication.translate("MainWindow", "Wirklich l\u00f6schen?", None) + ) self.user_delete_err_message.setText("") - self.label_38.setText(QCoreApplication.translate("MainWindow", u"Nutzername", None)) - self.update_user.setText(QCoreApplication.translate("MainWindow", u"Aktualisieren", None)) - self.label_40.setText(QCoreApplication.translate("MainWindow", u"Rolle", None)) - self.label_39.setText(QCoreApplication.translate("MainWindow", u"Neues Passwort", None)) - self.label_43.setText(QCoreApplication.translate("MainWindow", u"Titel", None)) - self.label_44.setText(QCoreApplication.translate("MainWindow", u"Vorname", None)) - self.label_45.setText(QCoreApplication.translate("MainWindow", u"Nachname", None)) - self.label_46.setText(QCoreApplication.translate("MainWindow", u"Telefonnummer", None)) - self.label_49.setText(QCoreApplication.translate("MainWindow", u"Mail", None)) - self.label_47.setText(QCoreApplication.translate("MainWindow", u"Mail", None)) - self.label_48.setText(QCoreApplication.translate("MainWindow", u"Telefon", None)) - self.label_41.setText(QCoreApplication.translate("MainWindow", u"Alte Angaben", None)) - self.update_faculty_member.setText(QCoreApplication.translate("MainWindow", u"Aktualisieren", None)) - self.label_42.setText(QCoreApplication.translate("MainWindow", u"Neue Angaben", None)) - self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_5), QCoreApplication.translate("MainWindow", u"Admin", None)) - self.groupBox_2.setTitle(QCoreApplication.translate("MainWindow", u"Software", None)) - self.appdata_check.setText(QCoreApplication.translate("MainWindow", u"Apparatsdaten eingegeben", None)) - self.media_check.setText(QCoreApplication.translate("MainWindow", u"Medien hinzugef\u00fcgt / importiert", None)) - self.ids_check.setText(QCoreApplication.translate("MainWindow", u"Prof-ID und Apparat-ID eingetragen", None)) - self.groupBox.setTitle(QCoreApplication.translate("MainWindow", u"aDIS", None)) - self.media_checked.setText(QCoreApplication.translate("MainWindow", u"Medien gepr\u00fcft", None)) - self.media_edited_check.setText(QCoreApplication.translate("MainWindow", u"Medien bearbeitet", None)) - self.app_created.setText(QCoreApplication.translate("MainWindow", u"Apparat angelegt", None)) -#if QT_CONFIG(tooltip) - self.btn_copy_adis_command.setToolTip(QCoreApplication.translate("MainWindow", u"Hier klicken, um die aDIS Abfrage in die Zwischenablage zu kopieren", None)) -#endif // QT_CONFIG(tooltip) -#if QT_CONFIG(statustip) + self.label_38.setText( + QCoreApplication.translate("MainWindow", "Nutzername", None) + ) + self.update_user.setText( + QCoreApplication.translate("MainWindow", "Aktualisieren", None) + ) + self.label_40.setText(QCoreApplication.translate("MainWindow", "Rolle", None)) + self.label_39.setText( + QCoreApplication.translate("MainWindow", "Neues Passwort", None) + ) + self.label_43.setText(QCoreApplication.translate("MainWindow", "Titel", None)) + self.label_44.setText(QCoreApplication.translate("MainWindow", "Vorname", None)) + self.label_45.setText( + QCoreApplication.translate("MainWindow", "Nachname", None) + ) + self.label_46.setText( + QCoreApplication.translate("MainWindow", "Telefonnummer", None) + ) + self.label_49.setText(QCoreApplication.translate("MainWindow", "Mail", None)) + self.label_47.setText(QCoreApplication.translate("MainWindow", "Mail", None)) + self.label_48.setText(QCoreApplication.translate("MainWindow", "Telefon", None)) + self.label_41.setText( + QCoreApplication.translate("MainWindow", "Alte Angaben", None) + ) + self.update_faculty_member.setText( + QCoreApplication.translate("MainWindow", "Aktualisieren", None) + ) + self.label_42.setText( + QCoreApplication.translate("MainWindow", "Neue Angaben", None) + ) + self.tabWidget.setTabText( + self.tabWidget.indexOf(self.tab_5), + QCoreApplication.translate("MainWindow", "Admin", None), + ) + self.groupBox_2.setTitle( + QCoreApplication.translate("MainWindow", "Software", None) + ) + self.appdata_check.setText( + QCoreApplication.translate("MainWindow", "Apparatsdaten eingegeben", None) + ) + self.media_check.setText( + QCoreApplication.translate( + "MainWindow", "Medien hinzugef\u00fcgt / importiert", None + ) + ) + self.ids_check.setText( + QCoreApplication.translate( + "MainWindow", "Prof-ID und Apparat-ID eingetragen", None + ) + ) + self.groupBox.setTitle(QCoreApplication.translate("MainWindow", "aDIS", None)) + self.media_checked.setText( + QCoreApplication.translate("MainWindow", "Medien gepr\u00fcft", None) + ) + self.media_edited_check.setText( + QCoreApplication.translate("MainWindow", "Medien bearbeitet", None) + ) + self.app_created.setText( + QCoreApplication.translate("MainWindow", "Apparat angelegt", None) + ) + # if QT_CONFIG(tooltip) + self.btn_copy_adis_command.setToolTip( + QCoreApplication.translate( + "MainWindow", + "Hier klicken, um die aDIS Abfrage in die Zwischenablage zu kopieren", + None, + ) + ) + # endif // QT_CONFIG(tooltip) + # if QT_CONFIG(statustip) self.btn_copy_adis_command.setStatusTip("") -#endif // QT_CONFIG(statustip) -#if QT_CONFIG(whatsthis) + # endif // QT_CONFIG(statustip) + # if QT_CONFIG(whatsthis) self.btn_copy_adis_command.setWhatsThis("") -#endif // QT_CONFIG(whatsthis) -#if QT_CONFIG(accessibility) + # endif // QT_CONFIG(whatsthis) + # if QT_CONFIG(accessibility) self.btn_copy_adis_command.setAccessibleDescription("") -#endif // QT_CONFIG(accessibility) - self.btn_copy_adis_command.setText(QCoreApplication.translate("MainWindow", u" aDIS Abfrage", None)) - self.label_14.setText(QCoreApplication.translate("MainWindow", u"Apparat", None)) - self.btn_delete_message.setText(QCoreApplication.translate("MainWindow", u"L\u00f6schen", None)) - self.label_total_day_messages.setText(QCoreApplication.translate("MainWindow", u"TextLabel", None)) - self.menuDatei.setTitle(QCoreApplication.translate("MainWindow", u"Datei", None)) - self.menuEinstellungen.setTitle(QCoreApplication.translate("MainWindow", u"Bearbeiten", None)) - self.menuHelp.setTitle(QCoreApplication.translate("MainWindow", u"Help", None)) - # retranslateUi + # endif // QT_CONFIG(accessibility) + self.btn_copy_adis_command.setText( + QCoreApplication.translate("MainWindow", " aDIS Abfrage", None) + ) + self.label_14.setText(QCoreApplication.translate("MainWindow", "Apparat", None)) + self.btn_delete_message.setText( + QCoreApplication.translate("MainWindow", "L\u00f6schen", None) + ) + self.label_total_day_messages.setText( + QCoreApplication.translate("MainWindow", "TextLabel", None) + ) + self.menuDatei.setTitle(QCoreApplication.translate("MainWindow", "Datei", None)) + self.menuEinstellungen.setTitle( + QCoreApplication.translate("MainWindow", "Bearbeiten", None) + ) + self.menuHelp.setTitle(QCoreApplication.translate("MainWindow", "Help", None)) + # retranslateUi diff --git a/src/ui/setupwizard_ui.py b/src/ui/setupwizard_ui.py index 73792b0..e1d1ac8 100644 --- a/src/ui/setupwizard_ui.py +++ b/src/ui/setupwizard_ui.py @@ -149,5 +149,3 @@ class Ui_Wizard(object): ) # retranslateUi - - diff --git a/src/ui/sounds/semesterapparat_ui_ui.py b/src/ui/sounds/semesterapparat_ui_ui.py index 3e94d23..6d00bd9 100644 --- a/src/ui/sounds/semesterapparat_ui_ui.py +++ b/src/ui/sounds/semesterapparat_ui_ui.py @@ -1,12 +1,12 @@ # Form implementation generated from reading ui file '/home/alexander/GitHub/Semesterapparate/ui/sounds/semesterapparat_ui.ui' # -# Created by: PySide6 UI code generator 6.5.3 +# Created by: PyQt6 UI code generator 6.5.3 # # WARNING: Any manual changes made to this file will be lost when pyuic6 is # run again. Do not edit this file unless you know what you are doing. -from PySide6 import QtCore, QtGui, QtWidgets +from PyQt6 import QtCore, QtGui, QtWidgets class Ui_MainWindow(object): @@ -15,7 +15,9 @@ class Ui_MainWindow(object): MainWindow.setWindowModality(QtCore.Qt.WindowModality.WindowModal) MainWindow.setEnabled(True) MainWindow.resize(1593, 800) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed) + sizePolicy = QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed + ) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(MainWindow.sizePolicy().hasHeightForWidth()) @@ -24,10 +26,14 @@ class Ui_MainWindow(object): MainWindow.setMaximumSize(QtCore.QSize(1920, 800)) MainWindow.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu) self.centralwidget = QtWidgets.QWidget(parent=MainWindow) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed) + sizePolicy = QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed + ) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.centralwidget.sizePolicy().hasHeightForWidth()) + sizePolicy.setHeightForWidth( + self.centralwidget.sizePolicy().hasHeightForWidth() + ) self.centralwidget.setSizePolicy(sizePolicy) self.centralwidget.setObjectName("centralwidget") self.verticalLayoutWidget = QtWidgets.QWidget(parent=self.centralwidget) @@ -44,7 +50,10 @@ class Ui_MainWindow(object): self.tabWidget.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) self.tabWidget.setObjectName("tabWidget") self.tab = QtWidgets.QWidget() - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred) + sizePolicy = QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.Policy.Preferred, + QtWidgets.QSizePolicy.Policy.Preferred, + ) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.tab.sizePolicy().hasHeightForWidth()) @@ -60,26 +69,48 @@ class Ui_MainWindow(object): self.formLayout.setObjectName("formLayout") self.verticalLayout_2 = QtWidgets.QVBoxLayout() self.verticalLayout_2.setObjectName("verticalLayout_2") - spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) + spacerItem = QtWidgets.QSpacerItem( + 20, + 40, + QtWidgets.QSizePolicy.Policy.Minimum, + QtWidgets.QSizePolicy.Policy.Expanding, + ) self.verticalLayout_2.addItem(spacerItem) self.load_app = QtWidgets.QPushButton(parent=self.horizontalLayoutWidget_2) self.load_app.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) self.load_app.setObjectName("load_app") self.verticalLayout_2.addWidget(self.load_app) - self.create_new_app = QtWidgets.QPushButton(parent=self.horizontalLayoutWidget_2) + self.create_new_app = QtWidgets.QPushButton( + parent=self.horizontalLayoutWidget_2 + ) self.create_new_app.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) self.create_new_app.setObjectName("create_new_app") self.verticalLayout_2.addWidget(self.create_new_app) - self.cancel_active_selection = QtWidgets.QPushButton(parent=self.horizontalLayoutWidget_2) + self.cancel_active_selection = QtWidgets.QPushButton( + parent=self.horizontalLayoutWidget_2 + ) self.cancel_active_selection.setObjectName("cancel_active_selection") self.verticalLayout_2.addWidget(self.cancel_active_selection) - spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) + spacerItem1 = QtWidgets.QSpacerItem( + 20, + 40, + QtWidgets.QSizePolicy.Policy.Minimum, + QtWidgets.QSizePolicy.Policy.Expanding, + ) self.verticalLayout_2.addItem(spacerItem1) - self.formLayout.setLayout(1, QtWidgets.QFormLayout.ItemRole.LabelRole, self.verticalLayout_2) - self.tableWidget_apparate = QtWidgets.QTableWidget(parent=self.horizontalLayoutWidget_2) + self.formLayout.setLayout( + 1, QtWidgets.QFormLayout.ItemRole.LabelRole, self.verticalLayout_2 + ) + self.tableWidget_apparate = QtWidgets.QTableWidget( + parent=self.horizontalLayoutWidget_2 + ) self.tableWidget_apparate.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) - self.tableWidget_apparate.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.SizeAdjustPolicy.AdjustToContents) - self.tableWidget_apparate.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.SelectedClicked) + self.tableWidget_apparate.setSizeAdjustPolicy( + QtWidgets.QAbstractScrollArea.SizeAdjustPolicy.AdjustToContents + ) + self.tableWidget_apparate.setEditTriggers( + QtWidgets.QAbstractItemView.EditTrigger.SelectedClicked + ) self.tableWidget_apparate.setAlternatingRowColors(True) self.tableWidget_apparate.setTextElideMode(QtCore.Qt.TextElideMode.ElideMiddle) self.tableWidget_apparate.setObjectName("tableWidget_apparate") @@ -98,13 +129,19 @@ class Ui_MainWindow(object): item = QtWidgets.QTableWidgetItem() self.tableWidget_apparate.setHorizontalHeaderItem(5, item) self.tableWidget_apparate.horizontalHeader().setCascadingSectionResizes(True) - self.formLayout.setWidget(1, QtWidgets.QFormLayout.ItemRole.FieldRole, self.tableWidget_apparate) + self.formLayout.setWidget( + 1, QtWidgets.QFormLayout.ItemRole.FieldRole, self.tableWidget_apparate + ) self.horizontalLayout_4 = QtWidgets.QHBoxLayout() self.horizontalLayout_4.setObjectName("horizontalLayout_4") - self.formLayout.setLayout(2, QtWidgets.QFormLayout.ItemRole.FieldRole, self.horizontalLayout_4) + self.formLayout.setLayout( + 2, QtWidgets.QFormLayout.ItemRole.FieldRole, self.horizontalLayout_4 + ) self.horizontalLayout_3 = QtWidgets.QHBoxLayout() self.horizontalLayout_3.setObjectName("horizontalLayout_3") - self.formLayout.setLayout(3, QtWidgets.QFormLayout.ItemRole.LabelRole, self.horizontalLayout_3) + self.formLayout.setLayout( + 3, QtWidgets.QFormLayout.ItemRole.LabelRole, self.horizontalLayout_3 + ) self.horizontalLayout_2.addLayout(self.formLayout) self.line = QtWidgets.QFrame(parent=self.tab) self.line.setGeometry(QtCore.QRect(0, 160, 1261, 21)) @@ -119,10 +156,14 @@ class Ui_MainWindow(object): self.gridLayout_2.setContentsMargins(0, 0, 0, 0) self.gridLayout_2.setObjectName("gridLayout_2") self.app_group_box = QtWidgets.QGroupBox(parent=self.gridLayoutWidget_2) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Fixed) + sizePolicy = QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Fixed + ) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.app_group_box.sizePolicy().hasHeightForWidth()) + sizePolicy.setHeightForWidth( + self.app_group_box.sizePolicy().hasHeightForWidth() + ) self.app_group_box.setSizePolicy(sizePolicy) self.app_group_box.setMinimumSize(QtCore.QSize(0, 210)) font = QtGui.QFont() @@ -130,7 +171,11 @@ class Ui_MainWindow(object): font.setBold(True) font.setWeight(75) self.app_group_box.setFont(font) - self.app_group_box.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeading|QtCore.Qt.AlignmentFlag.AlignLeft|QtCore.Qt.AlignmentFlag.AlignVCenter) + self.app_group_box.setAlignment( + QtCore.Qt.AlignmentFlag.AlignLeading + | QtCore.Qt.AlignmentFlag.AlignLeft + | QtCore.Qt.AlignmentFlag.AlignVCenter + ) self.app_group_box.setCheckable(False) self.app_group_box.setObjectName("app_group_box") self.dokument_list = QtWidgets.QTableWidget(parent=self.app_group_box) @@ -143,11 +188,17 @@ class Ui_MainWindow(object): self.dokument_list.setFont(font) self.dokument_list.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) self.dokument_list.setAcceptDrops(True) - self.dokument_list.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.SizeAdjustPolicy.AdjustToContents) + self.dokument_list.setSizeAdjustPolicy( + QtWidgets.QAbstractScrollArea.SizeAdjustPolicy.AdjustToContents + ) self.dokument_list.setDragEnabled(True) - self.dokument_list.setDragDropMode(QtWidgets.QAbstractItemView.DragDropMode.DropOnly) + self.dokument_list.setDragDropMode( + QtWidgets.QAbstractItemView.DragDropMode.DropOnly + ) self.dokument_list.setDefaultDropAction(QtCore.Qt.DropAction.LinkAction) - self.dokument_list.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.SingleSelection) + self.dokument_list.setSelectionMode( + QtWidgets.QAbstractItemView.SelectionMode.SingleSelection + ) self.dokument_list.setObjectName("dokument_list") self.dokument_list.setColumnCount(4) self.dokument_list.setRowCount(0) @@ -174,7 +225,9 @@ class Ui_MainWindow(object): self.frame = QtWidgets.QFrame(parent=self.app_group_box) self.frame.setEnabled(True) self.frame.setGeometry(QtCore.QRect(10, 30, 1241, 151)) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed) + sizePolicy = QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed + ) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.frame.sizePolicy().hasHeightForWidth()) @@ -317,7 +370,9 @@ class Ui_MainWindow(object): font.setBold(False) font.setWeight(50) self.prof_mail.setFont(font) - self.prof_mail.setInputMethodHints(QtCore.Qt.InputMethodHint.ImhEmailCharactersOnly) + self.prof_mail.setInputMethodHints( + QtCore.Qt.InputMethodHint.ImhEmailCharactersOnly + ) self.prof_mail.setMaxLength(200) self.prof_mail.setPlaceholderText("") self.prof_mail.setObjectName("prof_mail") @@ -366,7 +421,9 @@ class Ui_MainWindow(object): self.drpdwn_prof_name.setFocusPolicy(QtCore.Qt.FocusPolicy.StrongFocus) self.drpdwn_prof_name.setEditable(True) self.drpdwn_prof_name.setCurrentText("") - self.drpdwn_prof_name.setInsertPolicy(QtWidgets.QComboBox.InsertPolicy.InsertAlphabetically) + self.drpdwn_prof_name.setInsertPolicy( + QtWidgets.QComboBox.InsertPolicy.InsertAlphabetically + ) self.drpdwn_prof_name.setFrame(True) self.drpdwn_prof_name.setObjectName("drpdwn_prof_name") self.mail_mand = QtWidgets.QLabel(parent=self.frame) @@ -455,7 +512,9 @@ class Ui_MainWindow(object): font.setWeight(50) self.label_12.setFont(font) self.label_12.setObjectName("label_12") - self.formLayout_3.setWidget(0, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_12) + self.formLayout_3.setWidget( + 0, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_12 + ) self.prof_id_adis = QtWidgets.QLineEdit(parent=self.formLayoutWidget_2) font = QtGui.QFont() font.setPointSize(9) @@ -463,7 +522,9 @@ class Ui_MainWindow(object): font.setWeight(50) self.prof_id_adis.setFont(font) self.prof_id_adis.setObjectName("prof_id_adis") - self.formLayout_3.setWidget(0, QtWidgets.QFormLayout.ItemRole.FieldRole, self.prof_id_adis) + self.formLayout_3.setWidget( + 0, QtWidgets.QFormLayout.ItemRole.FieldRole, self.prof_id_adis + ) self.label_13 = QtWidgets.QLabel(parent=self.formLayoutWidget_2) font = QtGui.QFont() font.setPointSize(9) @@ -471,10 +532,14 @@ class Ui_MainWindow(object): font.setWeight(50) self.label_13.setFont(font) self.label_13.setObjectName("label_13") - self.formLayout_3.setWidget(1, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_13) + self.formLayout_3.setWidget( + 1, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_13 + ) self.apparat_id_adis = QtWidgets.QLineEdit(parent=self.formLayoutWidget_2) self.apparat_id_adis.setObjectName("apparat_id_adis") - self.formLayout_3.setWidget(1, QtWidgets.QFormLayout.ItemRole.FieldRole, self.apparat_id_adis) + self.formLayout_3.setWidget( + 1, QtWidgets.QFormLayout.ItemRole.FieldRole, self.apparat_id_adis + ) self.check_send_mail = QtWidgets.QCheckBox(parent=self.frame) self.check_send_mail.setGeometry(QtCore.QRect(450, 120, 91, 17)) font = QtGui.QFont() @@ -518,19 +583,33 @@ class Ui_MainWindow(object): self.frame.raise_() self.dokument_list.raise_() self.gridLayout_2.addWidget(self.app_group_box, 1, 0, 1, 1) - self.tableWidget_apparat_media = QtWidgets.QTableWidget(parent=self.gridLayoutWidget_2) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Expanding) + self.tableWidget_apparat_media = QtWidgets.QTableWidget( + parent=self.gridLayoutWidget_2 + ) + sizePolicy = QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Expanding + ) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.tableWidget_apparat_media.sizePolicy().hasHeightForWidth()) + sizePolicy.setHeightForWidth( + self.tableWidget_apparat_media.sizePolicy().hasHeightForWidth() + ) self.tableWidget_apparat_media.setSizePolicy(sizePolicy) self.tableWidget_apparat_media.setMinimumSize(QtCore.QSize(1259, 0)) self.tableWidget_apparat_media.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) - self.tableWidget_apparat_media.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.CustomContextMenu) - self.tableWidget_apparat_media.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.SizeAdjustPolicy.AdjustToContents) - self.tableWidget_apparat_media.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers) + self.tableWidget_apparat_media.setContextMenuPolicy( + QtCore.Qt.ContextMenuPolicy.CustomContextMenu + ) + self.tableWidget_apparat_media.setSizeAdjustPolicy( + QtWidgets.QAbstractScrollArea.SizeAdjustPolicy.AdjustToContents + ) + self.tableWidget_apparat_media.setEditTriggers( + QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers + ) self.tableWidget_apparat_media.setAlternatingRowColors(True) - self.tableWidget_apparat_media.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows) + self.tableWidget_apparat_media.setSelectionBehavior( + QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows + ) self.tableWidget_apparat_media.setObjectName("tableWidget_apparat_media") self.tableWidget_apparat_media.setColumnCount(7) self.tableWidget_apparat_media.setRowCount(0) @@ -548,7 +627,9 @@ class Ui_MainWindow(object): self.tableWidget_apparat_media.setHorizontalHeaderItem(5, item) item = QtWidgets.QTableWidgetItem() self.tableWidget_apparat_media.setHorizontalHeaderItem(6, item) - self.tableWidget_apparat_media.horizontalHeader().setCascadingSectionResizes(True) + self.tableWidget_apparat_media.horizontalHeader().setCascadingSectionResizes( + True + ) self.gridLayout_2.addWidget(self.tableWidget_apparat_media, 9, 0, 1, 1) self.label = QtWidgets.QLabel(parent=self.gridLayoutWidget_2) font = QtGui.QFont() @@ -560,12 +641,22 @@ class Ui_MainWindow(object): self.gridLayout_2.addWidget(self.label, 2, 0, 1, 1) self.horizontalLayout_5 = QtWidgets.QHBoxLayout() self.horizontalLayout_5.setObjectName("horizontalLayout_5") - spacerItem2 = QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Minimum) + spacerItem2 = QtWidgets.QSpacerItem( + 20, + 20, + QtWidgets.QSizePolicy.Policy.Fixed, + QtWidgets.QSizePolicy.Policy.Minimum, + ) self.horizontalLayout_5.addItem(spacerItem2) self.chkbx_show_del_media = QtWidgets.QCheckBox(parent=self.gridLayoutWidget_2) self.chkbx_show_del_media.setObjectName("chkbx_show_del_media") self.horizontalLayout_5.addWidget(self.chkbx_show_del_media) - spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Minimum) + spacerItem3 = QtWidgets.QSpacerItem( + 40, + 20, + QtWidgets.QSizePolicy.Policy.Fixed, + QtWidgets.QSizePolicy.Policy.Minimum, + ) self.horizontalLayout_5.addItem(spacerItem3) self.add_layout = QtWidgets.QHBoxLayout() self.add_layout.setObjectName("add_layout") @@ -581,12 +672,22 @@ class Ui_MainWindow(object): self.progress_label.setObjectName("progress_label") self.add_layout.addWidget(self.progress_label) self.horizontalLayout_5.addLayout(self.add_layout) - spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Minimum) + spacerItem4 = QtWidgets.QSpacerItem( + 40, + 20, + QtWidgets.QSizePolicy.Policy.Fixed, + QtWidgets.QSizePolicy.Policy.Minimum, + ) self.horizontalLayout_5.addItem(spacerItem4) self.btn_reserve = QtWidgets.QPushButton(parent=self.gridLayoutWidget_2) self.btn_reserve.setObjectName("btn_reserve") self.horizontalLayout_5.addWidget(self.btn_reserve) - spacerItem5 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) + spacerItem5 = QtWidgets.QSpacerItem( + 40, + 20, + QtWidgets.QSizePolicy.Policy.Expanding, + QtWidgets.QSizePolicy.Policy.Minimum, + ) self.horizontalLayout_5.addItem(spacerItem5) self.gridLayout_2.addLayout(self.horizontalLayout_5, 4, 0, 1, 1) self.add_medium = QtWidgets.QPushButton(parent=self.tab) @@ -657,7 +758,12 @@ class Ui_MainWindow(object): self.box_person.setEditable(True) self.box_person.setObjectName("box_person") self.gridLayout_3.addWidget(self.box_person, 1, 1, 1, 1) - spacerItem6 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) + spacerItem6 = QtWidgets.QSpacerItem( + 20, + 40, + QtWidgets.QSizePolicy.Policy.Minimum, + QtWidgets.QSizePolicy.Policy.Expanding, + ) self.gridLayout_3.addItem(spacerItem6, 4, 0, 1, 1) self.label_15 = QtWidgets.QLabel(parent=self.gridLayoutWidget) self.label_15.setObjectName("label_15") @@ -675,7 +781,9 @@ class Ui_MainWindow(object): self.tab_4.setObjectName("tab_4") self.tabWidget_2.addTab(self.tab_4, "") self.verticalLayout_3.addWidget(self.tabWidget_2) - self.stackedWidget_4 = QtWidgets.QStackedWidget(parent=self.verticalLayoutWidget_2) + self.stackedWidget_4 = QtWidgets.QStackedWidget( + parent=self.verticalLayoutWidget_2 + ) self.stackedWidget_4.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel) self.stackedWidget_4.setFrameShadow(QtWidgets.QFrame.Shadow.Raised) self.stackedWidget_4.setObjectName("stackedWidget_4") @@ -688,13 +796,19 @@ class Ui_MainWindow(object): self.tab_6.setObjectName("tab_6") self.statistics_table = QtWidgets.QTableWidget(parent=self.tab_6) self.statistics_table.setGeometry(QtCore.QRect(0, 0, 435, 191)) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed) + sizePolicy = QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed + ) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.statistics_table.sizePolicy().hasHeightForWidth()) + sizePolicy.setHeightForWidth( + self.statistics_table.sizePolicy().hasHeightForWidth() + ) self.statistics_table.setSizePolicy(sizePolicy) self.statistics_table.setMaximumSize(QtCore.QSize(16777215, 191)) - self.statistics_table.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers) + self.statistics_table.setEditTriggers( + QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers + ) self.statistics_table.setAlternatingRowColors(True) self.statistics_table.setObjectName("statistics_table") self.statistics_table.setColumnCount(3) @@ -722,17 +836,26 @@ class Ui_MainWindow(object): self.horizontalLayout_7 = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget_3) self.horizontalLayout_7.setContentsMargins(0, 0, 0, 0) self.horizontalLayout_7.setObjectName("horizontalLayout_7") - self.btn_del_select_apparats = QtWidgets.QPushButton(parent=self.horizontalLayoutWidget_3) + self.btn_del_select_apparats = QtWidgets.QPushButton( + parent=self.horizontalLayoutWidget_3 + ) self.btn_del_select_apparats.setObjectName("btn_del_select_apparats") self.horizontalLayout_7.addWidget(self.btn_del_select_apparats) - spacerItem7 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) + spacerItem7 = QtWidgets.QSpacerItem( + 40, + 20, + QtWidgets.QSizePolicy.Policy.Expanding, + QtWidgets.QSizePolicy.Policy.Minimum, + ) self.horizontalLayout_7.addItem(spacerItem7) self.table = QtWidgets.QWidget(parent=self.widget) self.table.setGeometry(QtCore.QRect(0, 50, 761, 391)) self.table.setObjectName("table") self.tableWidget = QtWidgets.QTableWidget(parent=self.table) self.tableWidget.setGeometry(QtCore.QRect(0, 0, 761, 391)) - self.tableWidget.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers) + self.tableWidget.setEditTriggers( + QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers + ) self.tableWidget.setObjectName("tableWidget") self.tableWidget.setColumnCount(5) self.tableWidget.setRowCount(0) @@ -761,14 +884,19 @@ class Ui_MainWindow(object): self.horizontalLayout_6 = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget) self.horizontalLayout_6.setContentsMargins(0, 0, 0, 0) self.horizontalLayout_6.setObjectName("horizontalLayout_6") - self.frame_creation_progress = QtWidgets.QFrame(parent=self.horizontalLayoutWidget) + self.frame_creation_progress = QtWidgets.QFrame( + parent=self.horizontalLayoutWidget + ) self.frame_creation_progress.setObjectName("frame_creation_progress") self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.frame_creation_progress) self.verticalLayout_4.setSpacing(6) self.verticalLayout_4.setObjectName("verticalLayout_4") self.groupBox_2 = QtWidgets.QGroupBox(parent=self.frame_creation_progress) self.groupBox_2.setEnabled(True) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) + sizePolicy = QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.Policy.Expanding, + QtWidgets.QSizePolicy.Policy.Expanding, + ) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.groupBox_2.sizePolicy().hasHeightForWidth()) @@ -800,7 +928,10 @@ class Ui_MainWindow(object): self.ids_check.setObjectName("ids_check") self.verticalLayout_4.addWidget(self.groupBox_2) self.groupBox = QtWidgets.QGroupBox(parent=self.frame_creation_progress) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) + sizePolicy = QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.Policy.Expanding, + QtWidgets.QSizePolicy.Policy.Expanding, + ) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.groupBox.sizePolicy().hasHeightForWidth()) @@ -811,7 +942,7 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(8) font.setBold(False) - font.setItalic(False) + font.setItal # ic(False) font.setUnderline(False) font.setWeight(50) font.setKerning(True) @@ -823,7 +954,7 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(8) font.setBold(False) - font.setItalic(False) + font.setItal # ic(False) font.setUnderline(False) font.setWeight(50) font.setKerning(True) @@ -835,7 +966,7 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(8) font.setBold(False) - font.setItalic(False) + font.setItal # ic(False) font.setUnderline(False) font.setWeight(50) font.setKerning(True) @@ -847,7 +978,7 @@ class Ui_MainWindow(object): font = QtGui.QFont() font.setPointSize(8) font.setBold(False) - font.setItalic(False) + font.setItal # ic(False) font.setUnderline(False) font.setWeight(50) font.setKerning(True) @@ -858,7 +989,13 @@ class Ui_MainWindow(object): self.btn_copy_adis_command.setAccessibleDescription("") self.btn_copy_adis_command.setAutoFillBackground(False) icon = QtGui.QIcon() - icon.addPixmap(QtGui.QPixmap("/home/alexander/GitHub/Semesterapparate/ui/sounds/../../../.designer/backup/icons/information.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) + icon.addPixmap( + QtGui.QPixmap( + "/home/alexander/GitHub/Semesterapparate/ui/sounds/../../../.designer/backup/icons/information.png" + ), + QtGui.QIcon.Mode.Normal, + QtGui.QIcon.State.Off, + ) self.btn_copy_adis_command.setIcon(icon) self.btn_copy_adis_command.setCheckable(False) self.btn_copy_adis_command.setChecked(False) @@ -874,7 +1011,9 @@ class Ui_MainWindow(object): self.calendarWidget = QtWidgets.QCalendarWidget(parent=self.frame_2) self.calendarWidget.setGeometry(QtCore.QRect(0, 0, 291, 191)) self.calendarWidget.setGridVisible(True) - self.calendarWidget.setVerticalHeaderFormat(QtWidgets.QCalendarWidget.VerticalHeaderFormat.NoVerticalHeader) + self.calendarWidget.setVerticalHeaderFormat( + QtWidgets.QCalendarWidget.VerticalHeaderFormat.NoVerticalHeader + ) self.calendarWidget.setObjectName("calendarWidget") self.message_frame = QtWidgets.QFrame(parent=self.frame_2) self.message_frame.setGeometry(QtCore.QRect(0, 210, 291, 121)) @@ -946,11 +1085,17 @@ class Ui_MainWindow(object): def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate - MainWindow.setWindowTitle(_translate("MainWindow", "Semesterapparatsmanagement")) - self.load_app.setToolTip(_translate("MainWindow", "Load the Semesterapparate from the database")) + MainWindow.setWindowTitle( + _translate("MainWindow", "Semesterapparatsmanagement") + ) + self.load_app.setToolTip( + _translate("MainWindow", "Load the Semesterapparate from the database") + ) self.load_app.setText(_translate("MainWindow", "App. aufrufen")) self.create_new_app.setText(_translate("MainWindow", "neu. App anlegen")) - self.cancel_active_selection.setText(_translate("MainWindow", "Auswahl abbrechen")) + self.cancel_active_selection.setText( + _translate("MainWindow", "Auswahl abbrechen") + ) self.tableWidget_apparate.setSortingEnabled(True) item = self.tableWidget_apparate.horizontalHeaderItem(0) item.setText(_translate("MainWindow", "AppNr")) @@ -995,9 +1140,15 @@ class Ui_MainWindow(object): self._mand.setText(_translate("MainWindow", "*")) self.btn_add_document.setText(_translate("MainWindow", "Dokument hinzufügen")) self.btn_open_document.setText(_translate("MainWindow", "Dokument öffnen")) - self.check_file.setToolTip(_translate("MainWindow", "Abhängig von der Anzahl der Medien kann die Suche sehr lange dauern")) - self.check_file.setText(_translate("MainWindow", "Medien aus Dokument\n" -" hinzufügen")) + self.check_file.setToolTip( + _translate( + "MainWindow", + "Abhängig von der Anzahl der Medien kann die Suche sehr lange dauern", + ) + ) + self.check_file.setText( + _translate("MainWindow", "Medien aus Dokument\n" " hinzufügen") + ) self.label_12.setText(_translate("MainWindow", "Prof-ID-aDIS")) self.label_13.setText(_translate("MainWindow", "Apparat-ID-aDIS")) self.check_send_mail.setText(_translate("MainWindow", "Mail senden")) @@ -1017,12 +1168,16 @@ class Ui_MainWindow(object): item = self.tableWidget_apparat_media.horizontalHeaderItem(6) item.setText(_translate("MainWindow", "Link")) self.label.setText(_translate("MainWindow", " Medienliste")) - self.chkbx_show_del_media.setText(_translate("MainWindow", "gel. Medien anzeigen")) + self.chkbx_show_del_media.setText( + _translate("MainWindow", "gel. Medien anzeigen") + ) self.label_info.setText(_translate("MainWindow", "Medien werden hinzugefügt")) self.progress_label.setText(_translate("MainWindow", "Medium x/y")) self.btn_reserve.setText(_translate("MainWindow", "Vorgemertk?")) self.add_medium.setText(_translate("MainWindow", "Medien hinzufügen")) - self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("MainWindow", "Anlegen")) + self.tabWidget.setTabText( + self.tabWidget.indexOf(self.tab), _translate("MainWindow", "Anlegen") + ) self.btn_search.setText(_translate("MainWindow", "Suchen")) self.label_7.setText(_translate("MainWindow", "Appnr.:")) self.label_18.setText(_translate("MainWindow", "Dauerapp:")) @@ -1031,17 +1186,28 @@ class Ui_MainWindow(object): self.label_11.setText(_translate("MainWindow", "Person:")) self.label_16.setText(_translate("MainWindow", "Fach:")) self.label_15.setText(_translate("MainWindow", "Löschbar")) - self.tabWidget_2.setTabText(self.tabWidget_2.indexOf(self.tab_3), _translate("MainWindow", "Statistik")) - self.tabWidget_2.setTabText(self.tabWidget_2.indexOf(self.tab_4), _translate("MainWindow", "Suchen")) + self.tabWidget_2.setTabText( + self.tabWidget_2.indexOf(self.tab_3), _translate("MainWindow", "Statistik") + ) + self.tabWidget_2.setTabText( + self.tabWidget_2.indexOf(self.tab_4), _translate("MainWindow", "Suchen") + ) item = self.statistics_table.horizontalHeaderItem(0) item.setText(_translate("MainWindow", "Semester")) item = self.statistics_table.horizontalHeaderItem(1) item.setText(_translate("MainWindow", "Zugang")) item = self.statistics_table.horizontalHeaderItem(2) item.setText(_translate("MainWindow", "Abgang")) - self.tabWidget_3.setTabText(self.tabWidget_3.indexOf(self.tab_6), _translate("MainWindow", "Tabelle")) - self.tabWidget_3.setTabText(self.tabWidget_3.indexOf(self.tab_7), _translate("MainWindow", "Erstellte und gelöschte Semesterapparate")) - self.btn_del_select_apparats.setText(_translate("MainWindow", "Ausgewählte Löschen")) + self.tabWidget_3.setTabText( + self.tabWidget_3.indexOf(self.tab_6), _translate("MainWindow", "Tabelle") + ) + self.tabWidget_3.setTabText( + self.tabWidget_3.indexOf(self.tab_7), + _translate("MainWindow", "Erstellte und gelöschte Semesterapparate"), + ) + self.btn_del_select_apparats.setText( + _translate("MainWindow", "Ausgewählte Löschen") + ) item = self.tableWidget.horizontalHeaderItem(0) item.setText(_translate("MainWindow", " ")) item = self.tableWidget.horizontalHeaderItem(1) @@ -1052,17 +1218,31 @@ class Ui_MainWindow(object): item.setText(_translate("MainWindow", "Person")) item = self.tableWidget.horizontalHeaderItem(4) item.setText(_translate("MainWindow", "Fach")) - self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("MainWindow", "Suchen / Statistik")) - self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_5), _translate("MainWindow", "Admin")) + self.tabWidget.setTabText( + self.tabWidget.indexOf(self.tab_2), + _translate("MainWindow", "Suchen / Statistik"), + ) + self.tabWidget.setTabText( + self.tabWidget.indexOf(self.tab_5), _translate("MainWindow", "Admin") + ) self.groupBox_2.setTitle(_translate("MainWindow", "Software")) self.appdata_check.setText(_translate("MainWindow", "Apparatsdaten eingegeben")) - self.media_check.setText(_translate("MainWindow", "Medien hinzugefügt / importiert")) - self.ids_check.setText(_translate("MainWindow", "Prof-ID und Apparat-ID eingetragen")) + self.media_check.setText( + _translate("MainWindow", "Medien hinzugefügt / importiert") + ) + self.ids_check.setText( + _translate("MainWindow", "Prof-ID und Apparat-ID eingetragen") + ) self.groupBox.setTitle(_translate("MainWindow", "aDIS")) self.media_checked.setText(_translate("MainWindow", "Medien geprüft")) self.media_edited_check.setText(_translate("MainWindow", "Medien bearbeitet")) self.app_created.setText(_translate("MainWindow", "Apparat angelegt")) - self.btn_copy_adis_command.setToolTip(_translate("MainWindow", "Hier klicken, um die aDIS Abfrage in die Zwischenablage zu kopieren")) + self.btn_copy_adis_command.setToolTip( + _translate( + "MainWindow", + "Hier klicken, um die aDIS Abfrage in die Zwischenablage zu kopieren", + ) + ) self.btn_copy_adis_command.setText(_translate("MainWindow", " aDIS Abfrage")) self.label_14.setText(_translate("MainWindow", "Apparat")) self.btn_delete_message.setText(_translate("MainWindow", "Löschen")) diff --git a/src/ui/untitled.ui b/src/ui/untitled.ui deleted file mode 100644 index ee79033..0000000 --- a/src/ui/untitled.ui +++ /dev/null @@ -1,692 +0,0 @@ - - - MainWindow - - - - 0 - 0 - 1280 - 720 - - - - - 0 - 0 - - - - MainWindow - - - - - 0 - 0 - - - - - - 0 - 0 - 1271 - 671 - - - - - - - - - - - 0 - - - - - 0 - 0 - - - - Tab 1 - - - - - 0 - 0 - 1261 - 161 - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Load the Semesterapparate from the database - - - App. aufrufen - - - - - - - neu. App anlegen - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - AppNr - - - - - App Name - - - - - Professor - - - - - Dauerapparat - - - - - - - - - - - - - - - - - - 0 - 160 - 1261 - 21 - - - - Qt::Horizontal - - - - - - 0 - 180 - 1261 - 461 - - - - - - - - Buchtitel - - - - - Autor - - - - - Auflage - - - - - Signatur - - - - - - - - - 0 - 0 - - - - - 12 - 75 - true - - - - Apparatsdetails - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - false - - - - - 820 - 20 - 256 - 192 - - - - - Dokumentname - - - - - Typ - - - - - - - 10 - 30 - 731 - 151 - - - - - 0 - 0 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - 110 - 50 - 69 - 22 - - - - - - - 250 - 20 - 91 - 21 - - - - - 9 - 50 - false - - - - Apparatsname - - - - - - 110 - 80 - 121 - 20 - - - - - 9 - 50 - false - - - - Nachname, Vorname - - - - - - 340 - 50 - 82 - 17 - - - - - 9 - 50 - false - - - - Winter - - - - - - 10 - 80 - 71 - 21 - - - - - 9 - 50 - false - - - - Prof. Name - - - - - - 110 - 20 - 69 - 22 - - - - - - - 340 - 20 - 113 - 20 - - - - - - - 340 - 70 - 82 - 17 - - - - - 9 - 50 - false - - - - Sommer - - - - - - 10 - 50 - 61 - 20 - - - - - 9 - 50 - false - - - - Prof. Titel - - - - - - 270 - 60 - 51 - 21 - - - - - 9 - 50 - false - - - - Semester - - - - - - 410 - 60 - 113 - 20 - - - - - 9 - 50 - false - - - - 2023 - - - - - - 10 - 20 - 101 - 21 - - - - - 9 - 50 - false - - - - Apparatsnummer - - - - - - 260 - 120 - 75 - 23 - - - - - 9 - 50 - false - - - - Speichern - - - - - - 350 - 120 - 75 - 23 - - - - - 9 - 50 - false - - - - Aktualisieren - - - - - - 340 - 90 - 101 - 17 - - - - - 9 - 50 - false - - - - Dauerapparat - - - - - - - 1100 - 40 - 131 - 25 - - - - - 9 - 50 - false - - - - Dokument hinzufügen - - - - - - 1100 - 80 - 131 - 25 - - - - - 9 - 50 - false - - - - Dokument öffnen - - - - - - 1110 - 110 - 25 - 19 - - - - ... - - - - - - 20 - 200 - 47 - 21 - - - - - 9 - 50 - false - - - - Suche - - - - - - 80 - 200 - 211 - 20 - - - - - 9 - 50 - false - - - - Buch oder Signatur - - - - - - 0 - 180 - 1259 - 18 - - - - - 11 - 75 - true - - - - Medienliste - - - - - - - - - - Tab 2 - - - - - - - - - - - - - - - 0 - 0 - 1280 - 21 - - - - - Datei - - - - - Einstellungen - - - - - - - - - - diff --git a/src/ui/untitled_ui.py b/src/ui/untitled_ui.py deleted file mode 100644 index 51fa1de..0000000 --- a/src/ui/untitled_ui.py +++ /dev/null @@ -1,477 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################ -# Form generated from reading UI file 'untitled.ui' -## -# Created by: Qt User Interface Compiler version 6.5.2 -## -# WARNING! All changes made in this file will be lost when recompiling UI file! -################################################################################ - -from PyQt6.QtCore import ( - QCoreApplication, - QDate, - QDateTime, - QLocale, - QMetaObject, - QObject, - QPoint, - QRect, - QSize, - Qt, - QTime, - QUrl, -) -from PyQt6.QtGui import ( - QBrush, - QColor, - QConicalGradient, - QCursor, - QFont, - QFontDatabase, - QGradient, - QIcon, - QImage, - QKeySequence, - QLinearGradient, - QPainter, - QPalette, - QPixmap, - QRadialGradient, - QTransform, - QWidgetAction, -) -from PyQt6.QtWidgets import ( - QApplication, - QCheckBox, - QComboBox, - QFormLayout, - QFrame, - QGridLayout, - QGroupBox, - QHBoxLayout, - QHeaderView, - QLabel, - QLineEdit, - QMainWindow, - QMenu, - QMenuBar, - QPushButton, - QRadioButton, - QSizePolicy, - QSpacerItem, - QStatusBar, - QTableWidget, - QTableWidgetItem, - QTabWidget, - QToolButton, - QVBoxLayout, - QWidget, -) - - -class Ui_MainWindow(object): - def setupUi(self, MainWindow): - if not MainWindow.objectName(): - MainWindow.setObjectName("MainWindow") - MainWindow.resize(1280, 720) - sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(MainWindow.sizePolicy().hasHeightForWidth()) - MainWindow.setSizePolicy(sizePolicy) - self.centralwidget = QWidget(MainWindow) - self.centralwidget.setObjectName("centralwidget") - sizePolicy1 = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) - sizePolicy1.setHorizontalStretch(0) - sizePolicy1.setVerticalStretch(0) - sizePolicy1.setHeightForWidth( - self.centralwidget.sizePolicy().hasHeightForWidth() - ) - self.centralwidget.setSizePolicy(sizePolicy1) - self.verticalLayoutWidget = QWidget(self.centralwidget) - self.verticalLayoutWidget.setObjectName("verticalLayoutWidget") - self.verticalLayoutWidget.setGeometry(QRect(0, 0, 1271, 671)) - self.verticalLayout = QVBoxLayout(self.verticalLayoutWidget) - self.verticalLayout.setObjectName("verticalLayout") - self.verticalLayout.setContentsMargins(0, 0, 0, 0) - self.horizontalLayout = QHBoxLayout() - self.horizontalLayout.setObjectName("horizontalLayout") - self.gridLayout = QGridLayout() - self.gridLayout.setObjectName("gridLayout") - self.tabWidget = QTabWidget(self.verticalLayoutWidget) - self.tabWidget.setObjectName("tabWidget") - self.tab = QWidget() - self.tab.setObjectName("tab") - sizePolicy2 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) - sizePolicy2.setHorizontalStretch(0) - sizePolicy2.setVerticalStretch(0) - sizePolicy2.setHeightForWidth(self.tab.sizePolicy().hasHeightForWidth()) - self.tab.setSizePolicy(sizePolicy2) - self.horizontalLayoutWidget_2 = QWidget(self.tab) - self.horizontalLayoutWidget_2.setObjectName("horizontalLayoutWidget_2") - self.horizontalLayoutWidget_2.setGeometry(QRect(0, 0, 1261, 161)) - self.horizontalLayout_2 = QHBoxLayout(self.horizontalLayoutWidget_2) - self.horizontalLayout_2.setObjectName("horizontalLayout_2") - self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0) - self.formLayout = QFormLayout() - self.formLayout.setObjectName("formLayout") - self.verticalLayout_2 = QVBoxLayout() - self.verticalLayout_2.setObjectName("verticalLayout_2") - self.verticalSpacer = QSpacerItem( - 20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding - ) - - self.verticalLayout_2.addItem(self.verticalSpacer) - - self.load_app = QPushButton(self.horizontalLayoutWidget_2) - self.load_app.setObjectName("load_app") - - self.verticalLayout_2.addWidget(self.load_app) - - self.create_new_app = QPushButton(self.horizontalLayoutWidget_2) - self.create_new_app.setObjectName("create_new_app") - - self.verticalLayout_2.addWidget(self.create_new_app) - - self.verticalSpacer_2 = QSpacerItem( - 20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding - ) - - self.verticalLayout_2.addItem(self.verticalSpacer_2) - - self.formLayout.setLayout(0, QFormLayout.LabelRole, self.verticalLayout_2) - - self.tableWidget_apparate = QTableWidget(self.horizontalLayoutWidget_2) - if self.tableWidget_apparate.columnCount() < 4: - self.tableWidget_apparate.setColumnCount(4) - __qtablewidgetitem = QTableWidgetItem() - self.tableWidget_apparate.setHorizontalHeaderItem(0, __qtablewidgetitem) - __qtablewidgetitem1 = QTableWidgetItem() - self.tableWidget_apparate.setHorizontalHeaderItem(1, __qtablewidgetitem1) - __qtablewidgetitem2 = QTableWidgetItem() - self.tableWidget_apparate.setHorizontalHeaderItem(2, __qtablewidgetitem2) - __qtablewidgetitem3 = QTableWidgetItem() - self.tableWidget_apparate.setHorizontalHeaderItem(3, __qtablewidgetitem3) - self.tableWidget_apparate.setObjectName("tableWidget_apparate") - - self.formLayout.setWidget(0, QFormLayout.FieldRole, self.tableWidget_apparate) - - self.horizontalLayout_3 = QHBoxLayout() - self.horizontalLayout_3.setObjectName("horizontalLayout_3") - - self.formLayout.setLayout(2, QFormLayout.LabelRole, self.horizontalLayout_3) - - self.horizontalLayout_4 = QHBoxLayout() - self.horizontalLayout_4.setObjectName("horizontalLayout_4") - - self.formLayout.setLayout(1, QFormLayout.FieldRole, self.horizontalLayout_4) - - self.horizontalLayout_2.addLayout(self.formLayout) - - self.line = QFrame(self.tab) - self.line.setObjectName("line") - self.line.setGeometry(QRect(0, 160, 1261, 21)) - self.line.setFrameShape(QFrame.HLine) - self.line.setFrameShadow(QFrame.Sunken) - self.gridLayoutWidget_2 = QWidget(self.tab) - self.gridLayoutWidget_2.setObjectName("gridLayoutWidget_2") - self.gridLayoutWidget_2.setGeometry(QRect(0, 180, 1261, 461)) - self.gridLayout_2 = QGridLayout(self.gridLayoutWidget_2) - self.gridLayout_2.setObjectName("gridLayout_2") - self.gridLayout_2.setContentsMargins(0, 0, 0, 0) - self.tableWidget_apparat_media = QTableWidget(self.gridLayoutWidget_2) - if self.tableWidget_apparat_media.columnCount() < 4: - self.tableWidget_apparat_media.setColumnCount(4) - __qtablewidgetitem4 = QTableWidgetItem() - self.tableWidget_apparat_media.setHorizontalHeaderItem(0, __qtablewidgetitem4) - __qtablewidgetitem5 = QTableWidgetItem() - self.tableWidget_apparat_media.setHorizontalHeaderItem(1, __qtablewidgetitem5) - __qtablewidgetitem6 = QTableWidgetItem() - self.tableWidget_apparat_media.setHorizontalHeaderItem(2, __qtablewidgetitem6) - __qtablewidgetitem7 = QTableWidgetItem() - self.tableWidget_apparat_media.setHorizontalHeaderItem(3, __qtablewidgetitem7) - self.tableWidget_apparat_media.setObjectName("tableWidget_apparat_media") - - self.gridLayout_2.addWidget(self.tableWidget_apparat_media, 2, 0, 1, 1) - - self.app_group_box = QGroupBox(self.gridLayoutWidget_2) - self.app_group_box.setObjectName("app_group_box") - sizePolicy3 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) - sizePolicy3.setHorizontalStretch(0) - sizePolicy3.setVerticalStretch(0) - sizePolicy3.setHeightForWidth( - self.app_group_box.sizePolicy().hasHeightForWidth() - ) - self.app_group_box.setSizePolicy(sizePolicy3) - font = QFont() - font.setPointSize(12) - font.setBold(True) - self.app_group_box.setFont(font) - self.app_group_box.setAlignment( - Qt.AlignLeading | Qt.AlignLeft | Qt.AlignVCenter - ) - self.app_group_box.setCheckable(False) - self.dokument_list = QTableWidget(self.app_group_box) - if self.dokument_list.columnCount() < 2: - self.dokument_list.setColumnCount(2) - __qtablewidgetitem8 = QTableWidgetItem() - self.dokument_list.setHorizontalHeaderItem(0, __qtablewidgetitem8) - __qtablewidgetitem9 = QTableWidgetItem() - self.dokument_list.setHorizontalHeaderItem(1, __qtablewidgetitem9) - self.dokument_list.setObjectName("dokument_list") - self.dokument_list.setGeometry(QRect(820, 20, 256, 192)) - self.frame = QFrame(self.app_group_box) - self.frame.setObjectName("frame") - self.frame.setGeometry(QRect(10, 30, 731, 151)) - sizePolicy1.setHeightForWidth(self.frame.sizePolicy().hasHeightForWidth()) - self.frame.setSizePolicy(sizePolicy1) - self.frame.setFrameShape(QFrame.StyledPanel) - self.frame.setFrameShadow(QFrame.Raised) - self.drpdwn_prof_title = QComboBox(self.frame) - self.drpdwn_prof_title.setObjectName("drpdwn_prof_title") - self.drpdwn_prof_title.setGeometry(QRect(110, 50, 69, 22)) - self.label_5 = QLabel(self.frame) - self.label_5.setObjectName("label_5") - self.label_5.setGeometry(QRect(250, 20, 91, 21)) - font1 = QFont() - font1.setPointSize(9) - font1.setBold(False) - self.label_5.setFont(font1) - self.lineEdit = QLineEdit(self.frame) - self.lineEdit.setObjectName("lineEdit") - self.lineEdit.setGeometry(QRect(110, 80, 121, 20)) - self.lineEdit.setFont(font1) - self.sem_winter = QRadioButton(self.frame) - self.sem_winter.setObjectName("sem_winter") - self.sem_winter.setGeometry(QRect(340, 50, 82, 17)) - self.sem_winter.setFont(font1) - self.label_4 = QLabel(self.frame) - self.label_4.setObjectName("label_4") - self.label_4.setGeometry(QRect(10, 80, 71, 21)) - self.label_4.setFont(font1) - self.drpdwn_app_nr = QComboBox(self.frame) - self.drpdwn_app_nr.setObjectName("drpdwn_app_nr") - self.drpdwn_app_nr.setGeometry(QRect(110, 20, 69, 22)) - self.app_name = QLineEdit(self.frame) - self.app_name.setObjectName("app_name") - self.app_name.setGeometry(QRect(340, 20, 113, 20)) - self.sem_sommer = QRadioButton(self.frame) - self.sem_sommer.setObjectName("sem_sommer") - self.sem_sommer.setGeometry(QRect(340, 70, 82, 17)) - self.sem_sommer.setFont(font1) - self.label_3 = QLabel(self.frame) - self.label_3.setObjectName("label_3") - self.label_3.setGeometry(QRect(10, 50, 61, 20)) - self.label_3.setFont(font1) - self.label_6 = QLabel(self.frame) - self.label_6.setObjectName("label_6") - self.label_6.setGeometry(QRect(270, 60, 51, 21)) - self.label_6.setFont(font1) - self.sem_year = QLineEdit(self.frame) - self.sem_year.setObjectName("sem_year") - self.sem_year.setGeometry(QRect(410, 60, 113, 20)) - self.sem_year.setFont(font1) - self.label_2 = QLabel(self.frame) - self.label_2.setObjectName("label_2") - self.label_2.setGeometry(QRect(10, 20, 101, 21)) - self.label_2.setFont(font1) - self.btn_apparat_save = QPushButton(self.frame) - self.btn_apparat_save.setObjectName("btn_apparat_save") - self.btn_apparat_save.setGeometry(QRect(260, 120, 75, 23)) - self.btn_apparat_save.setFont(font1) - self.btn_apparat_apply = QPushButton(self.frame) - self.btn_apparat_apply.setObjectName("btn_apparat_apply") - self.btn_apparat_apply.setGeometry(QRect(350, 120, 75, 23)) - self.btn_apparat_apply.setFont(font1) - self.checkBox = QCheckBox(self.frame) - self.checkBox.setObjectName("checkBox") - self.checkBox.setGeometry(QRect(340, 90, 101, 17)) - self.checkBox.setFont(font1) - self.btn_add_document = QPushButton(self.app_group_box) - self.btn_add_document.setObjectName("btn_add_document") - self.btn_add_document.setGeometry(QRect(1100, 40, 131, 25)) - self.btn_add_document.setFont(font1) - self.btn_open_document = QPushButton(self.app_group_box) - self.btn_open_document.setObjectName("btn_open_document") - self.btn_open_document.setGeometry(QRect(1100, 80, 131, 25)) - self.btn_open_document.setFont(font1) - self.toolButton = QToolButton(self.app_group_box) - self.toolButton.setObjectName("toolButton") - self.toolButton.setGeometry(QRect(1110, 110, 25, 19)) - self.label_7 = QLabel(self.app_group_box) - self.label_7.setObjectName("label_7") - self.label_7.setGeometry(QRect(20, 200, 47, 21)) - self.label_7.setFont(font1) - self.lineEdit_2 = QLineEdit(self.app_group_box) - self.lineEdit_2.setObjectName("lineEdit_2") - self.lineEdit_2.setGeometry(QRect(80, 200, 211, 20)) - self.lineEdit_2.setFont(font1) - self.label = QLabel(self.app_group_box) - self.label.setObjectName("label") - self.label.setGeometry(QRect(0, 180, 1259, 18)) - font2 = QFont() - font2.setPointSize(11) - font2.setBold(True) - self.label.setFont(font2) - - self.gridLayout_2.addWidget(self.app_group_box, 1, 0, 1, 1) - - self.tabWidget.addTab(self.tab, "") - self.tab_2 = QWidget() - self.tab_2.setObjectName("tab_2") - self.tabWidget.addTab(self.tab_2, "") - - self.gridLayout.addWidget(self.tabWidget, 0, 0, 1, 1) - - self.horizontalLayout.addLayout(self.gridLayout) - - self.verticalLayout.addLayout(self.horizontalLayout) - - MainWindow.setCentralWidget(self.centralwidget) - self.menubar = QMenuBar(MainWindow) - self.menubar.setObjectName("menubar") - self.menubar.setGeometry(QRect(0, 0, 1280, 21)) - self.menuDatei = QMenu(self.menubar) - self.menuDatei.setObjectName("menuDatei") - self.menuEinstellungen = QMenu(self.menubar) - self.menuEinstellungen.setObjectName("menuEinstellungen") - MainWindow.setMenuBar(self.menubar) - self.statusbar = QStatusBar(MainWindow) - self.statusbar.setObjectName("statusbar") - MainWindow.setStatusBar(self.statusbar) - - self.menubar.addAction(self.menuDatei.menuAction()) - self.menubar.addAction(self.menuEinstellungen.menuAction()) - - self.retranslateUi(MainWindow) - - self.tabWidget.setCurrentIndex(0) - - QMetaObject.connectSlotsByName(MainWindow) - - # setupUi - - def retranslateUi(self, MainWindow): - MainWindow.setWindowTitle( - QCoreApplication.translate("MainWindow", "MainWindow", None) - ) - # if QT_CONFIG(tooltip) - self.load_app.setToolTip( - QCoreApplication.translate( - "MainWindow", "Load the Semesterapparate from the database", None - ) - ) - # endif // QT_CONFIG(tooltip) - self.load_app.setText( - QCoreApplication.translate("MainWindow", "App. aufrufen", None) - ) - self.create_new_app.setText( - QCoreApplication.translate("MainWindow", "neu. App anlegen", None) - ) - ___qtablewidgetitem = self.tableWidget_apparate.horizontalHeaderItem(0) - ___qtablewidgetitem.setText( - QCoreApplication.translate("MainWindow", "AppNr", None) - ) - ___qtablewidgetitem1 = self.tableWidget_apparate.horizontalHeaderItem(1) - ___qtablewidgetitem1.setText( - QCoreApplication.translate("MainWindow", "App Name", None) - ) - ___qtablewidgetitem2 = self.tableWidget_apparate.horizontalHeaderItem(2) - ___qtablewidgetitem2.setText( - QCoreApplication.translate("MainWindow", "Professor", None) - ) - ___qtablewidgetitem3 = self.tableWidget_apparate.horizontalHeaderItem(3) - ___qtablewidgetitem3.setText( - QCoreApplication.translate("MainWindow", "Dauerapparat", None) - ) - ___qtablewidgetitem4 = self.tableWidget_apparat_media.horizontalHeaderItem(0) - ___qtablewidgetitem4.setText( - QCoreApplication.translate("MainWindow", "Buchtitel", None) - ) - ___qtablewidgetitem5 = self.tableWidget_apparat_media.horizontalHeaderItem(1) - ___qtablewidgetitem5.setText( - QCoreApplication.translate("MainWindow", "Autor", None) - ) - ___qtablewidgetitem6 = self.tableWidget_apparat_media.horizontalHeaderItem(2) - ___qtablewidgetitem6.setText( - QCoreApplication.translate("MainWindow", "Auflage", None) - ) - ___qtablewidgetitem7 = self.tableWidget_apparat_media.horizontalHeaderItem(3) - ___qtablewidgetitem7.setText( - QCoreApplication.translate("MainWindow", "Signatur", None) - ) - self.app_group_box.setTitle( - QCoreApplication.translate("MainWindow", "Apparatsdetails", None) - ) - ___qtablewidgetitem8 = self.dokument_list.horizontalHeaderItem(0) - ___qtablewidgetitem8.setText( - QCoreApplication.translate("MainWindow", "Dokumentname", None) - ) - ___qtablewidgetitem9 = self.dokument_list.horizontalHeaderItem(1) - ___qtablewidgetitem9.setText( - QCoreApplication.translate("MainWindow", "Typ", None) - ) - self.label_5.setText( - QCoreApplication.translate("MainWindow", "Apparatsname", None) - ) - self.lineEdit.setPlaceholderText( - QCoreApplication.translate("MainWindow", "Nachname, Vorname", None) - ) - self.sem_winter.setText( - QCoreApplication.translate("MainWindow", "Winter", None) - ) - self.label_4.setText( - QCoreApplication.translate("MainWindow", "Prof. Name", None) - ) - self.sem_sommer.setText( - QCoreApplication.translate("MainWindow", "Sommer", None) - ) - self.label_3.setText( - QCoreApplication.translate("MainWindow", "Prof. Titel", None) - ) - self.label_6.setText(QCoreApplication.translate("MainWindow", "Semester", None)) - self.sem_year.setPlaceholderText( - QCoreApplication.translate("MainWindow", "2023", None) - ) - self.label_2.setText( - QCoreApplication.translate("MainWindow", "Apparatsnummer", None) - ) - self.btn_apparat_save.setText( - QCoreApplication.translate("MainWindow", "Speichern", None) - ) - self.btn_apparat_apply.setText( - QCoreApplication.translate("MainWindow", "Aktualisieren", None) - ) - self.checkBox.setText( - QCoreApplication.translate("MainWindow", "Dauerapparat", None) - ) - self.btn_add_document.setText( - QCoreApplication.translate("MainWindow", "Dokument hinzuf\u00fcgen", None) - ) - self.btn_open_document.setText( - QCoreApplication.translate("MainWindow", "Dokument \u00f6ffnen", None) - ) - self.toolButton.setText(QCoreApplication.translate("MainWindow", "...", None)) - self.label_7.setText(QCoreApplication.translate("MainWindow", "Suche", None)) - self.lineEdit_2.setPlaceholderText( - QCoreApplication.translate("MainWindow", "Buch oder Signatur", None) - ) - self.label.setText( - QCoreApplication.translate("MainWindow", "Medienliste", None) - ) - self.tabWidget.setTabText( - self.tabWidget.indexOf(self.tab), - QCoreApplication.translate("MainWindow", "Tab 1", None), - ) - self.tabWidget.setTabText( - self.tabWidget.indexOf(self.tab_2), - QCoreApplication.translate("MainWindow", "Tab 2", None), - ) - self.menuDatei.setTitle(QCoreApplication.translate("MainWindow", "Datei", None)) - self.menuEinstellungen.setTitle( - QCoreApplication.translate("MainWindow", "Einstellungen", None) - ) - - # retranslateUi - diff --git a/src/ui/widgets/Ui_progress_overview_widget.py b/src/ui/widgets/Ui_progress_overview_widget.py index 4d8f937..9ecffbe 100644 --- a/src/ui/widgets/Ui_progress_overview_widget.py +++ b/src/ui/widgets/Ui_progress_overview_widget.py @@ -63,7 +63,7 @@ class Ui_Form(object): font = QtGui.QFont() font.setPointSize(10) font.setBold(True) - font.setItal#ic(False) + font.setItal # ic(False) font.setUnderline(False) font.setWeight(75) font.setKerning(True) diff --git a/src/ui/widgets/collapse.py b/src/ui/widgets/collapse.py index 1dae55b..75a1d65 100644 --- a/src/ui/widgets/collapse.py +++ b/src/ui/widgets/collapse.py @@ -55,7 +55,7 @@ class StatusWidget(QWidget): action_item, [f"{person} ({str(len(entries))})"] ) for entry in entries: - entry_item = QTreeWidgetItem(person_item, [entry]) + QTreeWidgetItem(person_item, [entry]) # Make the person entry collapsible person_item.setExpanded(False) @@ -75,4 +75,3 @@ if __name__ == "__main__": widget.person_double_clicked.connect(lambda x: print(x)) sys.exit(app.exec()) - diff --git a/src/ui/widgets/default_apps.py b/src/ui/widgets/default_apps.py index e390968..b3f3a40 100644 --- a/src/ui/widgets/default_apps.py +++ b/src/ui/widgets/default_apps.py @@ -1,15 +1,15 @@ -from PySide6 import QtCore, QtGui, QtWidgets +from PyQt6 import QtCore, QtGui, QtWidgets class CollapsibleWidget(object): pass -from PySide6 import QtCore, QtGui, QtWidgets +from PyQt6 import QtCore, QtGui, QtWidgets class CollapsibleWidget(object): -from PySide6 import QtCore, QtGui, QtWidgets +from PyQt6 import QtCore, QtGui, QtWidgets class CollapsibleWidget(object): diff --git a/src/ui/widgets/graph.py b/src/ui/widgets/graph.py index f3f37a4..5e51699 100644 --- a/src/ui/widgets/graph.py +++ b/src/ui/widgets/graph.py @@ -49,4 +49,3 @@ if __name__ == "__main__": widget = GraphWidget(data=data, legend_labels=["+", "-"]) widget.show() sys.exit(app.exec()) - diff --git a/src/ui/widgets/webview_ui.py b/src/ui/widgets/webview_ui.py index f56758e..2bfbeae 100644 --- a/src/ui/widgets/webview_ui.py +++ b/src/ui/widgets/webview_ui.py @@ -1,13 +1,12 @@ # Form implementation generated from reading ui file '/home/alexander/GitHub/SemesterapparatsManager/src/ui/widgets/webview.ui' # -# Created by: PySide6 UI code generator 6.6.1 +# Created by: PyQt6 UI code generator 6.6.1 # # WARNING: Any manual changes made to this file will be lost when pyuic6 is # run again. Do not edit this file unless you know what you are doing. -from PySide6 import QtCore, QtGui, QtWidgets -from PySide6 import QtWebEngineWidgets +from PyQt6 import QtCore, QtGui, QtWebEngineWidgets, QtWidgets class Ui_MainWindow(object): @@ -16,7 +15,9 @@ class Ui_MainWindow(object): MainWindow.resize(800, 600) self.centralwidget = QtWidgets.QWidget(parent=MainWindow) self.centralwidget.setObjectName("centralwidget") - self.webEngineView = QtWebEngineWidgets.QWebEngineView(parent=self.centralwidget) + self.webEngineView = QtWebEngineWidgets.QWebEngineView( + parent=self.centralwidget + ) self.webEngineView.setGeometry(QtCore.QRect(160, 190, 300, 200)) self.webEngineView.setUrl(QtCore.QUrl("about:blank")) self.webEngineView.setObjectName("webEngineView") diff --git a/src/utils/Ui_docs.py b/src/utils/Ui_docs.py new file mode 100644 index 0000000..1bf770c --- /dev/null +++ b/src/utils/Ui_docs.py @@ -0,0 +1,30 @@ +# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\utils\docs.ui' +# +# Created by: PyQt6 UI code generator 6.6.1 +# +# WARNING: Any manual changes made to this file will be lost when pyuic6 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt6 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(800, 600) + self.gridLayout_2 = QtWidgets.QGridLayout(Dialog) + self.gridLayout_2.setObjectName("gridLayout_2") + self.gridLayout = QtWidgets.QGridLayout() + self.gridLayout.setObjectName("gridLayout") + self.tabs = QtWidgets.QTabWidget(parent=Dialog) + self.tabs.setObjectName("tabs") + self.gridLayout.addWidget(self.tabs, 0, 0, 1, 1) + self.gridLayout_2.addLayout(self.gridLayout, 0, 0, 1, 1) + + self.retranslateUi(Dialog) + QtCore.QMetaObject.connectSlotsByName(Dialog) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "Dialog")) diff --git a/src/utils/__init__.py b/src/utils/__init__.py index bcfb57c..e38f19a 100644 --- a/src/utils/__init__.py +++ b/src/utils/__init__.py @@ -1,2 +1,2 @@ -from .pickles import load_pickle, dump_pickle -from .blob import create_blob \ No newline at end of file +from .blob import create_blob +from .pickles import dump_pickle, load_pickle diff --git a/src/utils/blob.py b/src/utils/blob.py index a484123..c28636a 100644 --- a/src/utils/blob.py +++ b/src/utils/blob.py @@ -5,5 +5,3 @@ def create_blob(file): with open(file, "rb") as f: blob = f.read() return blob - - diff --git a/src/utils/docs.ui b/src/utils/docs.ui new file mode 100644 index 0000000..a0e46c9 --- /dev/null +++ b/src/utils/docs.ui @@ -0,0 +1,28 @@ + + + Dialog + + + + 0 + 0 + 800 + 600 + + + + Dialog + + + + + + + + + + + + + + diff --git a/src/utils/docs_ui.py b/src/utils/docs_ui.py new file mode 100644 index 0000000..f03de63 --- /dev/null +++ b/src/utils/docs_ui.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- + +################################################################################ +## Form generated from reading UI file 'docs.ui' +## +## Created by: Qt User Interface Compiler version 6.6.2 +## +## WARNING! All changes made in this file will be lost when recompiling UI file! +################################################################################ + +from PyQt6.QtCore import ( + QCoreApplication, + QDate, + QDateTime, + QLocale, + QMetaObject, + QObject, + QPoint, + QRect, + QSize, + Qt, + QTime, + QUrl, +) +from PyQt6.QtGui import ( + QBrush, + QColor, + QConicalGradient, + QCursor, + QFont, + QFontDatabase, + QGradient, + QIcon, + QImage, + QKeySequence, + QLinearGradient, + QPainter, + QPalette, + QPixmap, + QRadialGradient, + QTransform, +) +from PyQt6.QtWidgets import ( + QApplication, + QDialog, + QGridLayout, + QSizePolicy, + QTabWidget, + QWidget, +) + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dokumentation") + Dialog.resize(800, 600) + self.gridLayout_2 = QGridLayout(Dialog) + self.gridLayout_2.setObjectName("gridLayout_2") + self.gridLayout = QGridLayout() + self.gridLayout.setObjectName("gridLayout") + self.tabs = QTabWidget(Dialog) + self.tabs.setObjectName("tabs") + + self.gridLayout.addWidget(self.tabs, 0, 0, 1, 1) + + self.gridLayout_2.addLayout(self.gridLayout, 0, 0, 1, 1) + + self.retranslateUi(Dialog) + + QMetaObject.connectSlotsByName(Dialog) + + # setupUi + + def retranslateUi(self, Dialog): + Dialog.setWindowTitle(QCoreApplication.translate("Dialog", "Dialog", None)) + + # retranslateUi diff --git a/src/utils/documentationview.py b/src/utils/documentationview.py index f5f1d7b..8f6a87c 100644 --- a/src/utils/documentationview.py +++ b/src/utils/documentationview.py @@ -1,9 +1,9 @@ import os import sys -# from PySide6 import Webview -from PySide6.QtWebEngineWidgets import QWebEngineView -from PySide6.QtWidgets import QApplication, QMainWindow, QTabWidget +# from PyQt6 import Webview +from PyQt6.QtWebEngineWidgets import QWebEngineView +from PyQt6.QtWidgets import QApplication, QMainWindow, QTabWidget documentation_path = "docs" diff --git a/src/utils/pickles.py b/src/utils/pickles.py index ffb57d3..cb5a84f 100644 --- a/src/utils/pickles.py +++ b/src/utils/pickles.py @@ -1,6 +1,9 @@ import pickle + def load_pickle(data): return pickle.loads(data) + + def dump_pickle(data): return pickle.dumps(data) diff --git a/test.py b/test.py index d689d8d..45ccd54 100644 --- a/test.py +++ b/test.py @@ -1,142 +1,11 @@ -# # # # tupl = (0, 0, 0, 0, 0, 0) +olddata = ( + None, + "Christian", + "Berger", + "alexander.kirchner@ph-freiburg.de", + "764", + "Berger Christian", +) -# # # # def change_value(index: int, state: int): -# # # # global tupl -# # # # tupl = list(tupl) -# # # # print(len(tupl)) -# # # # tupl[index] = state -# # # # tupl = tuple(tupl) - - -# # # # def check_validity() -> bool: -# # # # global tupl -# # # # if all(tupl): -# # # # return True -# # # # else: -# # # # return False - - -# # # # print(tupl) -# # # # print(check_validity()) -# # # # change_value(0, 1) -# # # # for i in range(1, 6): -# # # # change_value(i, 1) -# # # # print(tupl) -# # # # print(check_validity()) -# # # import sqlite3 -# # # from codebase import Database -# # # # print(messages) - -# # # def day_to_message(messages:list[dict[str]]): -# # # print(messages) -# # # ret = [] -# # # #extract the remind_at from each message and add them to ret. If the key already exists, append the message to the list -# # # for message in messages: -# # # print(message) -# # # remind_at = message["remind_at"] -# # # if remind_at in ret: -# # # ret[remind_at].append(message) -# # # else: -# # # ret[remind_at] = [message] -# # # print(ret) -# # # if __name__ =="__man__": -# # # db = Database() - -# # # messages = db.get_messages() -# # # print(messages) -# # # print(day_to_message(messages)) - - -# # from natsort import natsorted - -# # unsorted = ["WiSe 23/24", "SoSe 23", "WiSe 21/22", "SoSe 21", "WiSe 22/23", "SoSe 22"] - -# # def custom_sort(unsorted:list[str])->list[str]: -# # """Sort a list of semesters in the format "SoSe n" and "WiSe n/n+1" in the correct order. -# # Where n == year in 2 digit format - -# # Args: -# # unsorted (list[str]): List of semesters in the format "SoSe n" and "WiSe n/n+1" - -# # Returns: -# # ret (list[str]): Sorted list in correct order -# # """ -# # #split the list into two lists, one with the summer semesters and one with the winter semesters -# # summer = natsorted([ i for i in unsorted if "SoSe" in i]) -# # winter = natsorted([i for i in unsorted if "WiSe" in i]) -# # #merge the lists entries alternately -# # ret = [] -# # for i in range(len(summer)): -# # ret.append(summer[i]) -# # ret.append(winter[i]) -# # return ret -# from typing import Any - -# def statistic_request(**kwargs:Any): - - -# if "deletable" in kwargs.keys(): -# query = f"SELECT * FROM semesterapparat WHERE deletion_status=0 AND dauer=0 AND (erstellsemester!='{kwargs['deletesemester']}' OR verlängerung_bis!='{kwargs['deletesemester']}')" -# return query - -# if "dauer" in kwargs.keys(): -# kwargs["dauer"] = kwargs["dauer"].replace("Ja", "1").replace("Nein", "0") -# query = "SELECT * FROM semesterapparat WHERE " -# for key, value in kwargs.items() if kwargs.items() is not None else {}: -# print(key, value) -# query += f"{key}='{value}' AND " -# print(query) -# #remove deletesemester part from normal query, as this will be added to the database upon deleting the apparat -# if "deletesemester" in kwargs.keys(): -# query = query.replace(f"deletesemester='{kwargs['deletesemester']}' AND ", "") -# if "endsemester" in kwargs.keys(): -# if "erstellsemester" in kwargs.keys(): -# query = query.replace(f"endsemester='{kwargs['endsemester']}' AND ", "") -# query = query.replace(f"erstellsemester='{kwargs['erstellsemester']} AND ", "xyz") -# else: -# query = query.replace(f"endsemester='{kwargs['endsemester']}' AND ", "xyz") -# print("replaced") -# query = query.replace("xyz", f"(erstellsemester='{kwargs['endsemester']}' OR verlängerung_bis='{kwargs['endsemester']}') AND ") - -# query = query[:-5].strip() -# return query -# from threads import AutoAdder -# from PyQt6 import QtWidgets -# from ui import parsed_titles_ui -# import sys -# def main(data): -# app = QtWidgets.QApplication(sys.argv) -# dialog = QtWidgets.QDialog() -# ui = parsed_titles_ui() -# ui.setupUi(dialog) -# ui.signatures = data -# ui.app_id = 3 -# ui.prof_id = 1 -# ui.toolButton.click() -# ui.populate_table() -# ui.progressBar.setMaximum(len(data)) -# ui.progressBar.setValue(0) - -# dialog.show() -# sys.exit(app.exec()) - -# if __name__ == "__main__": -# data = ['YH 6876 S344', 'YM 3500 L925', 'CU 3200 W862', 'CW 6940 W842', 'CZ 1360 M379', 'CU 3800 V445', 'CU 3100 L948', 'CU 3200 H379 (3)', 'YC 7093 K95', 'CU 8590 E34 (2)', 'MS 6410 L744 (2)+1', 'CUS778', 'Psy K 120: 125 b', 'Psy L 170: 66', 'MR 2600 M474 (12)+16', 'Psy K 760: 19', 'Psy K 110: 92', 'Psy K 400: 45 a', 'CD 20/10,6'] -# main(data) - -# from src.backend.database import Database -# import pickle -# db = Database() - -# query="SELECT * from media where id=1" -# _data = db.database.execute(query).fetchall() -# var = _data[0][1] -# print(pickle.loads(var)) -from src.backend.database_rewrite import Database -from omegaconf import OmegaConf - -config = OmegaConf.load("config.yaml") - -subjects = [subject["name"] for subject in config.subjects] -print(subjects) \ No newline at end of file +print(olddata[1], olddata[2], olddata[3], olddata[4], olddata[5]) diff --git a/test.ttxt b/test.ttxt new file mode 100644 index 0000000000000000000000000000000000000000..be571af292ae95e722a08cfb9f64d82fe3da84ae GIT binary patch literal 2582 zcmaKuOK%fF5QOK9#7}V)J1=tJz=0b`aO4ziHnwBMudFvD&W{J`>zeUw9zxODu-#K# zUHusU{nMmn>eD=RsY%;(n?CFBI31;Ny3pro`j|e*vew^5evO`Kx=AB_@8v&DyTgfd z`5zzrd*OCEKa$s8-nTlxPAB?*mmc)5J}uHp=ZSZ!lb3o<^68zdz5MTFU*`yuU;{5R zdI%4r!)7DoX?n_t_U6-!uJyiAT%ykOy%g$3-&@5l48{q6bGVij4$F*ZMX*oOSuhxS zjMAh0t^>0T*8M>h&RpgbyBrNPo=LoKb+*gQ7IAJO>qJb*b{wA<*>fiL_VN3wc`Jn* z;p~LgOAG4NNnz@{NY4?2OhM%-v9ZD#+oyu!YMY4oqNq~d*W zbicvSH6YKq82r`)jw5iMIA%Ds|F6>Hz$|AvA%aN<85h-w>JCHosUG~$x8w%5;&G$% zO;|W<%@=ydS@NbAUaI15ztj6iJ@oi8a;2t&bRVFLrF;o5fm+uU>Tp)pd3o*`{LO9C9fNSSNUF!+NLBxj57gz{4Ey zSZg0Ox$;!4_8$8Qjmi|)xx&X?z70E>K1L3%;t*r0Jm&h_XS?UeIkMZ@;OWV;&Sv3G zzyp5Xcx_-{M{z6h+T$t|p)ZOe~i2M(lBPF{rhICHl)PGK@?%wOZVPXa+yulBnvAt#{oh>W;7P9d9{m zRcD9_l)DlCjr4xXJgr}4`O**EwPqU*p4_6fF`vUyJDv=-!iRUQ8(U(n%qCIkw|BZb znaoi9SNf8^r*DC51xw7LAip7~AsdSAuu#NugBLwLy+zM*@_p?~7g>9LqmVcFD?Mdj zyEB{>b8|1nU7=1 and "admin" in Database().getUser() - - \ No newline at end of file + assert len(Database().getUser()) >= 1 and "admin" in Database().getUser() diff --git a/test/many_webrequest_test.py b/test/many_webrequest_test.py index d9445d4..2cdd479 100644 --- a/test/many_webrequest_test.py +++ b/test/many_webrequest_test.py @@ -7,4 +7,4 @@ def many_test_webdata(): test_webdata_bibtexttransform("COinS") test_webdata_bibtexttransform("ARRAY") test_webdata_bibtexttransform("RDS") - assert True is True \ No newline at end of file + assert True is True diff --git a/test/rds_test.py b/test/rds_test.py new file mode 100644 index 0000000..a7e6b6c --- /dev/null +++ b/test/rds_test.py @@ -0,0 +1,103 @@ +def contact_prof(self): + dialog = QtWidgets.QDialog() + mail_prev = Mail_Dialog() + mail_prev.setupUi(dialog) + mail_prevs = os.listdir("mail_vorlagen") + if self.app_name.text() == "": + mail_prevs.remove("Information zum Semesterapparat.eml") + mail_prev.comboBox.addItems(mail_prevs) + active_apparat_id = self.tableWidget_apparate.item( + self.tableWidget_apparate.currentRow(), 0 + ).text() + general_data = { + "Appname": self.app_name.text(), + "AppSubject": self.app_fach.currentText(), + "appnr": self.active_apparat, + } + print(active_apparat_id) + mail_prev.appid = active_apparat_id + base_data = self.db.getProfData(id=active_apparat_id) + profname = self.db.getProfNameById(base_data["id"]) + profname = profname.split(" ") + profname = f"{profname[1]} {profname[0]}" + pass_data = { + "prof_name": profname, + "mail_name": base_data["prof_mail"], + "general": general_data, + } + + mail_prev.set_data(pass_data) + mail_prev.set_mail() + dialog.exec() + + +import subprocess +import tempfile + +from PyQt6 import QtCore, QtGui, QtWidgets + +from src.ui.dialogs.mail_preview import Ui_Dialog + + +class Mail_Dialog(QtWidgets.QDialog, Ui_Dialog): + def __init__(self, parent=None): + super().__init__(parent) + self.setupUi(self) + self.appid = "" + self.mail_data = "" + self.data = None + self.buttonBox.accepted.connect(self.save_mail) + + def set_data(self, data: dict): + print(data) + self.prof_name.setText(data["prof_name"]) + self.mail_name.setText(data["mail_name"]) + # assign data["general"] to self.data + self.data = data["general"] + + def set_mail(self): + email_template = self.comboBox.currentText() + with open(f"mail_vorlagen/{email_template}", "r", encoding="utf-8") as f: + mail_template = f.read() + email_header = email_template.split(".eml")[0] + if "{AppNr}" in email_template: + email_header = email_template.split(".eml")[0].format(AppNr=self.appid) + self.mail_header.setText(email_header) + self.mail_data = mail_template.split("")[0] + mail_html = mail_template.split("")[1] + mail_html = "" + mail_html + print(self.data) + Appname = self.data["Appname"] + mail_html = mail_html.format( + Profname=self.prof_name.text().split(" ")[-1], Appname=Appname + ) + + self.mail_body.setHtml(mail_html) + + def save_mail(self): + # create a temporary file + mail_header = self.mail_header.text() + mail_body = self.mail_body.toHtml() + mail = self.mail_data + mail_body + mail = mail.replace("Subject:", f"Subject: {mail_header}") + with tempfile.NamedTemporaryFile( + mode="w", delete=False, suffix=".eml", encoding="utf-8", dir="mails" + ) as f: + f.write(mail) + self.mail_path = f.name + print(self.mail_path) + # open the file using thunderbird + subprocess.Popen(["thunderbird", f"{self.mail_path}"]) + # delete the file + # os.remove(self.mail_path) + + +if __name__ == "__main__": + import sys + + app = QtWidgets.QApplication(sys.argv) + Dialog = QtWidgets.QDialog() + ui = Mail_Dialog() + ui.setupUi(Dialog) + Dialog.show() + sys.exit(app.exec()) diff --git a/test/semestergen_test.py b/test/semestergen_test.py new file mode 100644 index 0000000..8c398e0 --- /dev/null +++ b/test/semestergen_test.py @@ -0,0 +1,7 @@ +import pytest + +from src.backend.semester import generateSemesterByDate + + +def test_generateSemesterByDate(): + assert generateSemesterByDate() == "WiSe 23/24" diff --git a/test/test_database.py b/test/test_database.py index 96a637f..6008df2 100644 --- a/test/test_database.py +++ b/test/test_database.py @@ -1,5 +1,4 @@ from src.backend.database import Database - db = Database("semap.db") -# print(db.query_db("SELECT * FROM subjects WHERE id=1")) \ No newline at end of file +# print(db.query_db("SELECT * FROM subjects WHERE id=1")) diff --git a/test/webrequest_test.py b/test/webrequest_test.py index 1924a00..9de4d7c 100644 --- a/test/webrequest_test.py +++ b/test/webrequest_test.py @@ -1,23 +1,18 @@ import pytest -from src.logic.webrequest import WebRequest -from src.logic.webrequest import BibTextTransformer -from src.logic.dataclass import BookData -def test_webdata_bibtexttransform(source_data:str="RIS"): +from src.logic.dataclass import BookData +from src.logic.webrequest import BibTextTransformer, WebRequest + + +def test_webdata_bibtexttransform(source_data: str = "RIS"): request = WebRequest().get_ppn("ST 250 U42 (15) ").get_data() assert isinstance(request, list) is True - assert len(request)>0 - model:BookData = BibTextTransformer(source_data).get_data(request).return_data() + assert len(request) > 0 + model: BookData = BibTextTransformer(source_data).get_data(request).return_data() assert model is not None - assert model.signature =="ST 250 U42 (15)" + assert model.signature == "ST 250 U42 (15)" assert model.ppn == "1693321114" assert model.author == "Ullenboom, Christian" assert model.link == "https://rds.ibs-bw.de/phfreiburg/link?kid=1693321114" - assert model.pages=="1246" - assert model.publisher=="Rheinwerk Computing" - - - - - - \ No newline at end of file + assert model.pages == "1246" + assert model.publisher == "Rheinwerk Computing" diff --git a/testing.txt b/testing.txt new file mode 100644 index 0000000000000000000000000000000000000000..be571af292ae95e722a08cfb9f64d82fe3da84ae GIT binary patch literal 2582 zcmaKuOK%fF5QOK9#7}V)J1=tJz=0b`aO4ziHnwBMudFvD&W{J`>zeUw9zxODu-#K# zUHusU{nMmn>eD=RsY%;(n?CFBI31;Ny3pro`j|e*vew^5evO`Kx=AB_@8v&DyTgfd z`5zzrd*OCEKa$s8-nTlxPAB?*mmc)5J}uHp=ZSZ!lb3o<^68zdz5MTFU*`yuU;{5R zdI%4r!)7DoX?n_t_U6-!uJyiAT%ykOy%g$3-&@5l48{q6bGVij4$F*ZMX*oOSuhxS zjMAh0t^>0T*8M>h&RpgbyBrNPo=LoKb+*gQ7IAJO>qJb*b{wA<*>fiL_VN3wc`Jn* z;p~LgOAG4NNnz@{NY4?2OhM%-v9ZD#+oyu!YMY4oqNq~d*W zbicvSH6YKq82r`)jw5iMIA%Ds|F6>Hz$|AvA%aN<85h-w>JCHosUG~$x8w%5;&G$% zO;|W<%@=ydS@NbAUaI15ztj6iJ@oi8a;2t&bRVFLrF;o5fm+uU>Tp)pd3o*`{LO9C9fNSSNUF!+NLBxj57gz{4Ey zSZg0Ox$;!4_8$8Qjmi|)xx&X?z70E>K1L3%;t*r0Jm&h_XS?UeIkMZ@;OWV;&Sv3G zzyp5Xcx_-{M{z6h+T$t|p)ZOe~i2M(lBPF{rhICHl)PGK@?%wOZVPXa+yulBnvAt#{oh>W;7P9d9{m zRcD9_l)DlCjr4xXJgr}4`O**EwPqU*p4_6fF`vUyJDv=-!iRUQ8(U(n%qCIkw|BZb znaoi9SNf8^r*DC51xw7LAip7~AsdSAuu#NugBLwLy+zM*@_p?~7g>9LqmVcFD?Mdj zyEB{>b8|1nU7