diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml
index 892b841..7b84059 100644
--- a/.gitea/workflows/release.yml
+++ b/.gitea/workflows/release.yml
@@ -1,59 +1,223 @@
+name: Build and Release
+
on:
workflow_dispatch:
inputs:
- release_notes:
- description: Release notes (use \n for newlines)
- type: string
- required: false
github_release:
- description: 'Create Gitea Release'
+ description: "Create Gitea Release"
default: true
type: boolean
+ prerelease:
+ description: "Is this a prerelease?"
+ default: false
+ type: boolean
bump:
- description: 'Bump type'
+ description: "Bump type"
required: false
- default: 'patch'
+ default: "patch"
type: choice
options:
- - 'major'
- - 'minor'
- - 'patch'
+ - "major"
+ - "minor"
+ - "patch"
+env:
+ BASE_URL: "http://192.168.178.110:3000"
jobs:
- bump:
+ prepare:
runs-on: ubuntu-latest
+ outputs:
+ version: ${{ steps.bump.outputs.version }}
+ tag: ${{ steps.bump.outputs.tag }}
+ changelog: ${{ steps.build_changelog.outputs.changelog }}
+
steps:
- - name: Checkout
- uses: actions/checkout@v4
+ - name: Checkout code
+ uses: actions/checkout@v5
with:
fetch-depth: 0
- - name: Install UV
+ fetch-tags: true
+
+ - name: Install uv
uses: astral-sh/setup-uv@v5
+
- name: Set up Python
- run: uv python install
+ uses: actions/setup-python@v5
+ with:
+ # Uses the version specified in pyproject.toml
+ python-version-file: "pyproject.toml"
+
+ - name: Set Git identity
+ run: |
+ git config user.name "Gitea CI"
+ git config user.email "ci@git.theprivateserver.de"
+
+ - name: Bump version
+ id: bump
+ run: |
+ uv tool install bump-my-version
+
+ uv tool run bump-my-version bump "${{ github.event.inputs.bump }}"
+ version="$(uv tool run bump-my-version show current_version)"
+
+ echo "VERSION=$version" >> "$GITHUB_ENV"
+ echo "version=$version" >> "$GITHUB_OUTPUT"
+ echo "tag=v$version" >> "$GITHUB_OUTPUT"
+ # no env needed here, uv handles the Python it installs
+
+ - name: Push changes
+ uses: ad-m/github-push-action@master
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ branch: ${{ github.ref }}
+
+ - name: Build Changelog
+ id: build_changelog
+ uses: https://github.com/mikepenz/release-changelog-builder-action@v6.0.1
+ with:
+ platform: "gitea"
+ baseURL: "${{ env.BASE_URL }}"
+ configuration: ".gitea/changelog_config.json"
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITEA_TOKEN }}
+
+ build-linux:
+ needs: prepare
+ runs-on: ubuntu-latest
+ env:
+ VERSION: ${{ needs.prepare.outputs.version }}
+ TAG_NAME: ${{ needs.prepare.outputs.tag }}
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v5
+ with:
+ fetch-depth: 0
+ fetch-tags: true
+
+ - name: Install uv
+ uses: astral-sh/setup-uv@v5
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
with:
python-version-file: "pyproject.toml"
- - name: Install dependencies
- run: uv sync --locked --all-extras --dev
- - name: Install Bump tool
- run: uv tool install bump-my-version
- - name: Bump version
- id: bump_version
+
+ - name: Install all dependencies
+ run: uv sync --all-groups
+
+ - name: Build Linux release with Nuitka
run: |
- uv tool run bump-my-version bump ${{ github.event.inputs.bump }} --tag --allow-dirty
- - name: Add release notes
- id: add_release_notes
+ uv run python -m nuitka \
+ --standalone \
+ --output-dir=dist \
+ --include-data-dir=./config=config \
+ --include-data-dir=./site=site \
+ --include-data-dir=./icons=icons \
+ --include-data-dir=./mail_vorlagen=mail_vorlagen \
+ --enable-plugin=pyside6 \
+ --product-name=SemesterApparatsManager \
+ --product-version=${VERSION} \
+ --output-filename=SAM \
+ main.py
+
+ - name: Prepare Linux Release Artifact
run: |
- echo "RELEASE_NOTES< Sehr geehrte/r {Profname}, Bei Fragen können Sie sich jederzeit an mich wenden. 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 in den Apparaten 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. Ihr Semesterapparat {Appname} wurde angelegt. Unter folgendem Link können Sie die Apparate einsehen: Ihr Apparat ist unter {AppSubject} > {Profname} > {AppNr} {Appname}. Noch nicht vorhandene Medien wurden vorgemerkt und werden nach Rückkehr in die Bibliothek eingearbeitet. Bei Fragen können Sie sich per Mail bei mir melden. {greeting} Ihr Semesterapparat "{Appname} ({AppNr})" wurde wie besprochen aufgelöst. Die Medien sind von nun an wieder in den Regalen zu finden. Click to expand traditional pip installation steps
+
+1. **Create virtual environment**:
+ ```bash
+ python -m venv .venv
+ ```
+
+2. **Activate virtual environment**:
+ - Windows (PowerShell):
+ ```powershell
+ .venv\Scripts\Activate.ps1
+ ```
+ - Linux/Mac:
+ ```bash
+ source .venv/bin/activate
+ ```
+
+3. **Install dependencies**:
+ ```bash
+ pip install -r requirements.txt
+ ```
+
+4. **Run the application**:
+ ```bash
+ python main.py
+ ```
+
+
-
-
-
- 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.
-
-
-
- --
-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 96e504c..7a343b9 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
@@ -1,37 +1,17 @@
-Message-ID:
-
-
-
- --
-{signature}
-
-
-
\ No newline at end of file
+Subject: Information bezüglich der Auflösung des Semesterapparates {AppNr}
+
+
+{greeting}
+
+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 in den Apparaten 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.
+
+{signature}
\ 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 0ffbc1a..756a6b8 100644
--- a/mail_vorlagen/Information zum Semesterapparat {AppNr} - {AppName}.eml
+++ b/mail_vorlagen/Information zum Semesterapparat {AppNr} - {AppName}.eml
@@ -1,36 +1,16 @@
-Message-ID:
-
-
-
- --
-{signature}
-
-
-
+Subject: Information zum Semesterapparat {AppNr} - {AppName}
+
+
+{greeting}
+
+Ihr Semesterapparat {Appname} wurde angelegt.
+Unter folgendem Link können Sie die Apparate einsehen:
+https://bsz.ibs-bw.de/aDISWeb/app?service=direct/0/Home/$DirectLink&sp=SOPAC42&sp=SWI00000002&noRedir
+
+Ihr Apparat ist unter {AppSubject} > {Profname} > {AppNr} {Appname}
+
+Noch nicht vorhandene Medien wurden vorgemerkt und werden nach Rückkehr in die Bibliothek eingearbeitet.
+Bei Fragen können Sie sich per Mail bei mir melden.
+
+{signature}
\ No newline at end of file
diff --git a/mail_vorlagen/Information zur Auflösung des Semesterapparates {AppNr} - {Appname}.eml b/mail_vorlagen/Information zur Auflösung des Semesterapparates {AppNr} - {Appname}.eml
new file mode 100644
index 0000000..836fd9b
--- /dev/null
+++ b/mail_vorlagen/Information zur Auflösung des Semesterapparates {AppNr} - {Appname}.eml
@@ -0,0 +1,10 @@
+
+Subject: Information zur Auflösung des Semesterapparates {AppNr} - {Appname}
+
+
+{greeting}
+
+Ihr Semesterapparat "{Appname} ({AppNr})" wurde wie besprochen aufgelöst.
+Die Medien sind von nun an wieder in den Regalen zu finden.
+
+{signature}
\ No newline at end of file
diff --git a/mail_vorlagen/Information zur Auflösung des Semesterapparates.eml b/mail_vorlagen/Information zur Auflösung des Semesterapparates.eml
deleted file mode 100644
index 45ca60f..0000000
--- a/mail_vorlagen/Information zur Auflösung des Semesterapparates.eml
+++ /dev/null
@@ -1,18 +0,0 @@
-Subject: Information zur Auflösung des Semesterapparates {AppNr} - {Appname}
-MIME-Version: 1.0
-Content-Type: text/html; charset="UTF-8"
-Content-Transfer-Encoding: 8bit
-
-
-
-
-
-
---
{signature}
- \ No newline at end of file diff --git a/mail_vorlagen/Neuauflagen für Semesterapparat {AppNr} - {AppName}.eml b/mail_vorlagen/Neuauflagen für Semesterapparat {AppNr} - {AppName}.eml index 5f0b3be..0598de7 100644 --- a/mail_vorlagen/Neuauflagen für Semesterapparat {AppNr} - {AppName}.eml +++ b/mail_vorlagen/Neuauflagen für Semesterapparat {AppNr} - {AppName}.eml @@ -1,21 +1,15 @@ -Subject: Vorschläge für Neuauflagen - {Appname} -MIME-Version: 1.0 -Content-Type: text/html; charset="UTF-8" -Content-Transfer-Encoding: 8bit - - - - - - -{greeting}
-für Ihren Semesterapparat {AppNr} - {Appname} wurden folgende Neuauflagen gefunden:
-{newEditions}
-Sollen wir die alte(n) Auflage(n) aus dem Apparat durch diese austauschen?
--- -\ No newline at end of file + +Subject: Neuauflagen für Semesterapparat {AppNr} - {AppName} + + +{greeting} + +Für Ihren Semesterapparat {AppNr} - {Appname} wurden folgende Neuauflagen gefunden: + +{newEditions} + +Sollen wir die alte(n) Auflage(n) aus dem Apparat durch diese austauschen? +Nicht vorhandene Exemplare werden an die Erwerbungsabteilung weitergegeben +und nach Erhalt der Medien in den Apparat eingearbeitet. + +{signature} \ No newline at end of file diff --git a/mail_vorlagen/blankomail.eml b/mail_vorlagen/blankomail.eml new file mode 100644 index 0000000..3fd5ea1 --- /dev/null +++ b/mail_vorlagen/blankomail.eml @@ -0,0 +1,9 @@ + +Subject: CHANGEME + + +{greeting} + + + +{signature} \ No newline at end of file diff --git a/main.py b/main.py index 4066d3d..8a2e07d 100644 --- a/main.py +++ b/main.py @@ -1,11 +1,14 @@ -from src import first_launch, settings -from src.ui.widgets.welcome_wizard import launch_wizard as startup -from PySide6 import QtWidgets import sys -from src.ui.userInterface import launch_gui as UI +from PySide6 import QtWidgets + +from src import first_launch, settings +from src.shared.logging import configure +from src.ui.userInterface import launch_gui as UI +from src.ui.widgets.welcome_wizard import launch_wizard as startup if __name__ == "__main__": + configure("INFO") app = QtWidgets.QApplication(sys.argv) if not first_launch: setup = startup() @@ -16,4 +19,4 @@ if __name__ == "__main__": else: sys.exit() else: - UI() \ No newline at end of file + UI() diff --git a/pyproject.toml b/pyproject.toml index 470172a..536773b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ dependencies = [ "omegaconf>=2.3.0", "openai>=1.79.0", "pandas>=2.2.3", + "pdfquery>=0.4.3", "playwright>=1.49.1", "pyramid>=2.0.2", "pyside6>=6.9.1", diff --git a/src/__init__.py b/src/__init__.py index 1062a81..93fbfbc 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -3,6 +3,8 @@ __author__ = "Alexander Kirchner" __all__ = ["__version__", "__author__", "Icon", "settings"] import os +from pathlib import Path +from typing import Union from appdirs import AppDirs @@ -18,7 +20,7 @@ if not os.path.exists(CONFIG_DIR): # type: ignore settings = Config(f"{CONFIG_DIR}/config.yaml") -DATABASE_DIR = ( # type: ignore +DATABASE_DIR: Union[Path, str] = ( # type: ignore app.user_config_dir if settings.database.path is None else settings.database.path # type: ignore ) if not os.path.exists(DATABASE_DIR): # type: ignore diff --git a/src/admin/__init__.py b/src/admin/__init__.py new file mode 100644 index 0000000..5eee246 --- /dev/null +++ b/src/admin/__init__.py @@ -0,0 +1,5 @@ +"""Administrative functions and commands.""" + +from .commands import AdminCommands + +__all__ = ["AdminCommands"] diff --git a/src/admin/commands.py b/src/admin/commands.py new file mode 100644 index 0000000..aa27ee0 --- /dev/null +++ b/src/admin/commands.py @@ -0,0 +1,103 @@ +import hashlib +import random + +from src.database import Database +from src.shared.logging import log + + +# change passwords for apparats, change passwords for users, list users, create and delete users etc +# create a class that has all commands. for each command, create a function that does the thing +class AdminCommands: + """Basic Admin commands for the admin console. This class is used to create, delete, and list users. It also has the ability to change passwords for users.""" + + def __init__(self, db_path=None): + """Default Constructor for the AdminCommands class.""" + if db_path is None: + self.db = Database() + else: + self.db = Database(db_path=db_path) + log.info("AdminCommands initialized with database connection.") + log.debug("location: {}", self.db.db_path) + + def create_password(self, password: str) -> tuple[str, str]: + """Create a hashed password and a salt for the password. + + Args: + password (str): the base password to be hashed. + + Returns: + tuple[str,str]: a tuple containing the hashed password and the salt used to hash the password. + """ + salt = self.create_salt() + hashed_password = self.hash_password(password) + return (hashed_password, salt) + + def create_salt(self) -> str: + """Generate a random 16 digit long salt for the password. + + Returns: + str: the randomized salt + """ + return "".join( + random.choices( + "abcdefghijklmnopqrstuvwxyzQWERTZUIOPLKJHGFDSAYXCVBNM0123456789", k=16 + ) + ) + + def create_admin(self): + """Create the admin in the database. This is only used once, when the database is created.""" + salt = self.create_salt() + hashed_password = self.hash_password("admin") + self.db.createUser("admin", salt + hashed_password, "admin", salt) + + def create_user(self, username: str, password: str, role: str = "user") -> bool: + """Create a new user in the database. + + Args: + username (str): the username of the user to be created. + password (str): the password of the user to be created. + role (str, optional): the role of the user to be created. Defaults to "user". + """ + hashed_password, salt = self.create_password(password) + status = self.db.createUser( + user=username, password=salt + hashed_password, role=role, salt=salt + ) + return status + + def hash_password(self, password: str) -> str: + """Hash a password using SHA256. + + Args: + password (str): the password to be hashed. + + Returns: + str: the hashed password. + """ + hashed = hashlib.sha256((password).encode("utf-8")).hexdigest() + return hashed + + def list_users(self) -> list[tuple]: + """List all available users in the database. + + Returns: + list[tuple]: a list of all users, containing all stored data for each user in a tuple. + """ + return self.db.getUsers() + + def delete_user(self, username: str): + """Delete a selected user from the database. + + Args: + username (str): the username of the user to be deleted. + """ + self.db.deleteUser(username) + + def change_password(self, username, password): + """change the password for a user. + + Args: + username (str): username of the user to change the password for. + password (str): the new, non-hashed password to change to. + """ + hashed_password = self.hash_password(password) + self.db.changePassword(username, hashed_password) diff --git a/src/backend/__init__.py b/src/backend/__init__.py index 9b79959..66a6838 100644 --- a/src/backend/__init__.py +++ b/src/backend/__init__.py @@ -1,6 +1,5 @@ __all__ = [ "AdminCommands", - "Semester", "AutoAdder", "AvailChecker", "BookGrabber", @@ -9,16 +8,15 @@ __all__ = [ "NewEditionCheckerThread", "recreateElsaFile", "recreateFile", - "Catalogue" + "Catalogue", ] from .admin_console import AdminCommands +from .catalogue import Catalogue from .create_file import recreateElsaFile, recreateFile from .database import Database from .documentation_thread import DocumentationThread -from .semester import Semester from .thread_bookgrabber import BookGrabber from .thread_neweditions import NewEditionCheckerThread from .threads_autoadder import AutoAdder from .threads_availchecker import AvailChecker -from .catalogue import Catalogue diff --git a/src/backend/catalogue.py b/src/backend/catalogue.py index 4f72ec1..c9f1a82 100644 --- a/src/backend/catalogue.py +++ b/src/backend/catalogue.py @@ -1,27 +1,18 @@ +from typing import List + +import regex import requests from bs4 import BeautifulSoup from src.logic import BookData as Book +from src.shared.logging import log -from datetime import datetime -import sys -import loguru -from src import LOG_DIR URL = "https://rds.ibs-bw.de/phfreiburg/opac/RDSIndex/Search?type0%5B%5D=allfields&lookfor0%5B%5D={}&join=AND&bool0%5B%5D=AND&type0%5B%5D=au&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=ti&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=ct&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=isn&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=ta&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=co&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=py&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=pp&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=pu&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=si&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=zr&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=cc&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND" BASE = "https://rds.ibs-bw.de" -log = loguru.logger -log.remove() -log.add(sys.stdout, level="INFO") -log.add(f"{LOG_DIR}/application.log", rotation="1 MB", retention="10 days") -log.add( - f"{LOG_DIR}/{datetime.now().strftime('%Y-%m-%d')}.log", - rotation="1 day", - retention="1 month", -) class Catalogue: - def __init__(self, timeout=5): + def __init__(self, timeout=15): self.timeout = timeout reachable = self.check_connection() if not reachable: @@ -44,58 +35,258 @@ class Catalogue: response = requests.get(link, timeout=self.timeout) return response.text - def get_book_links(self, searchterm: str): + def get_book_links(self, searchterm: str) -> List[str]: response = self.search_book(searchterm) soup = BeautifulSoup(response, "html.parser") links = soup.find_all("a", class_="title getFull") - res = [] + res: List[str] = [] for link in links: - res.append(BASE + link["href"]) + res.append(BASE + link["href"]) # type: ignore return res def get_book(self, searchterm: str): log.info(f"Searching for term: {searchterm}") + links = self.get_book_links(searchterm) + print(links) + for elink in links: + result = self.search(elink) + # in result search for class col-xs-12 rds-dl RDS_LOCATION + # if found, return text of href + soup = BeautifulSoup(result, "html.parser") + + # Optional (unchanged): title and ppn if you need them + title_el = soup.find("div", class_="headline text") + title = title_el.get_text(strip=True) if title_el else None + + ppn_el = soup.find( + "div", class_="col-xs-12 col-md-5 col-lg-4 rds-dl-head RDS_PPN" + ) + # in ppn_el, get text of div col-xs-12 col-md-7 col-lg-8 rds-dl-panel + ppn = ( + ppn_el.find_next_sibling( + "div", class_="col-xs-12 col-md-7 col-lg-8 rds-dl-panel" + ).get_text(strip=True) + if ppn_el + else None + ) + + # get edition text at div class col-xs-12 col-md-5 col-lg-4 rds-dl-head RDS_EDITION + edition_el = soup.find( + "div", class_="col-xs-12 col-md-5 col-lg-4 rds-dl-head RDS_EDITION" + ) + edition = ( + edition_el.find_next_sibling( + "div", class_="col-xs-12 col-md-7 col-lg-8 rds-dl-panel" + ).get_text(strip=True) + if edition_el + else None + ) + + authors = soup.find_all( + "div", class_="col-xs-12 col-md-5 col-lg-4 rds-dl-head RDS_PERSON" + ) + author = None + if authors: + # get the names of the a href links in the div col-xs-12 col-md-7 col-lg-8 rds-dl-panel + author_names = [] + for author in authors: + panel = author.find_next_sibling( + "div", class_="col-xs-12 col-md-7 col-lg-8 rds-dl-panel" + ) + if panel: + links = panel.find_all("a") + for link in links: + author_names.append(link.text.strip()) + author = ( + ";".join(author_names) if len(author_names) > 1 else author_names[0] + ) + signature = None + + panel = soup.select_one("div.panel-body") + if panel: + # Collect the RDS_* blocks in order, using the 'space' divs as separators + groups = [] + cur = {} + for node in panel.select( + "div.rds-dl.RDS_SIGNATURE, div.rds-dl.RDS_STATUS, div.rds-dl.RDS_LOCATION, div.col-xs-12.space" + ): + classes = node.get("class", []) + # Separator between entries + if "space" in classes: + if cur: + groups.append(cur) + cur = {} + continue + + # Read the value from the corresponding panel cell + val_el = node.select_one(".rds-dl-panel") + val = ( + val_el.get_text(" ", strip=True) + if val_el + else node.get_text(" ", strip=True) + ) + + if "RDS_SIGNATURE" in classes: + cur["signature"] = val + elif "RDS_STATUS" in classes: + cur["status"] = val + elif "RDS_LOCATION" in classes: + cur["location"] = val + + if cur: # append the last group if not followed by a space + groups.append(cur) + + # Find the signature for the entry whose location mentions "Semesterapparat" + for g in groups: + loc = g.get("location", "").lower() + if "semesterapparat" in loc: + signature = g.get("signature") + return Book( + title=title, + ppn=ppn, + signature=signature, + library_location=loc.split("-")[-1], + link=elink, + author=author, + edition=edition, + ) + else: + return Book( + title=title, + ppn=ppn, + signature=signature, + library_location=loc.split("\n\n")[-1], + link=elink, + author=author, + edition=edition, + ) + + def get(self, ppn: str) -> Book | None: + # based on PPN, get title, people, edition, year, language, pages, isbn, + link = f"https://rds.ibs-bw.de/phfreiburg/opac/RDSIndexrecord/{ppn}" + result = self.search(link) + soup = BeautifulSoup(result, "html.parser") + + def get_ppn(self, searchterm: str) -> str | None: + links = self.get_book_links(searchterm) + ppn = None + for link in links: + result = self.search(link) + soup = BeautifulSoup(result, "html.parser") + print(link) + ppn = link.split("/")[-1] + if ppn and regex.match(r"^\d{8,10}[X\d]?$", ppn): + return ppn + return ppn + + def get_semesterapparat_number(self, searchterm: str) -> int: links = self.get_book_links(searchterm) for link in links: result = self.search(link) # in result search for class col-xs-12 rds-dl RDS_LOCATION # if found, return text of href soup = BeautifulSoup(result, "html.parser") - location = soup.find_all("div", class_="col-xs-12 rds-dl RDS_LOCATION") - for loc in location: - if f"1. OG Semesterapparat" in loc.text: - title = ( - soup.find("div", class_="headline text") - .text.replace("\n", "") - .strip() + + locations = soup.find_all("div", class_="col-xs-12 rds-dl RDS_LOCATION") + for location_el in locations: + if "Semesterapparat-" in location_el.text: + match = regex.search(r"Semesterapparat-(\d+)", location_el.text) + if match: + return int(match.group(1)) + if "Handbibliothek-" in location_el.text: + return location_el.text.strip().split("\n\n")[-1].strip() + return location_el.text.strip().split("\n\n")[-1].strip() + return 0 + + def get_author(self, link: str) -> str: + links = self.get_book_links(f"kid:{link}") + author = None + for link in links: + # print(link) + result = self.search(link) + soup = BeautifulSoup(result, "html.parser") + # get all authors, return them as a string seperated by ; + authors = soup.find_all( + "div", class_="col-xs-12 col-md-5 col-lg-4 rds-dl-head RDS_PERSON" + ) + if authors: + # get the names of the a href links in the div col-xs-12 col-md-7 col-lg-8 rds-dl-panel + author_names = [] + for author in authors: + panel = author.find_next_sibling( + "div", class_="col-xs-12 col-md-7 col-lg-8 rds-dl-panel" ) - ppn = soup.find( - "div", class_="col-xs-12 col-md-5 col-lg-4 rds-dl-head RDS_PPN" + if panel: + links = panel.find_all("a") + for link in links: + author_names.append(link.text.strip()) + author = "; ".join(author_names) + return author + + def get_signature(self, isbn: str): + links = self.get_book_links(f"{isbn}") + signature = None + for link in links: + result = self.search(link) + soup = BeautifulSoup(result, "html.parser") + panel = soup.select_one("div.panel-body") + if panel: + # Collect the RDS_* blocks in order, using the 'space' divs as separators + groups = [] + cur = {} + for node in panel.select( + "div.rds-dl.RDS_SIGNATURE, div.rds-dl.RDS_STATUS, div.rds-dl.RDS_LOCATION, div.col-xs-12.space" + ): + classes = node.get("class", []) + # Separator between entries + if "space" in classes: + if cur: + groups.append(cur) + cur = {} + continue + + # Read the value from the corresponding panel cell + val_el = node.select_one(".rds-dl-panel") + val = ( + val_el.get_text(" ", strip=True) + if val_el + else node.get_text(" ", strip=True) ) - signature = soup.find( - "div", class_="col-xs-12 rds-dl RDS_SIGNATURE" - ) - if signature: - signature = ( - signature.find_next("div") - .find_next("div") - .text.replace("\n", "") - .strip() - ) - # use ppn to find the next div and extract the text - if ppn: - ppn = ppn.find_next("div").text.replace("\n", "").strip() + + if "RDS_SIGNATURE" in classes: + cur["signature"] = val + elif "RDS_STATUS" in classes: + cur["status"] = val + elif "RDS_LOCATION" in classes: + cur["location"] = val + + if cur: # append the last group if not followed by a space + groups.append(cur) + + # Find the signature for the entry whose location mentions "Semesterapparat" + for g in groups: + print(g) + loc = g.get("location", "").lower() + if "semesterapparat" in loc: + signature = g.get("signature") + return signature else: - ppn = None - isbn = soup.find( - "div", class_="col-xs-12 col-md-5 col-lg-4 rds-dl-head RDS_ISBN" - ) - if isbn: - isbn = isbn.find_next("div").find_next("div").text - else: - isbn = None - return Book( - title=title, ppn=ppn, signature=signature, isbn=isbn, link=link - ) - return False + signature = g.get("signature") + return signature + print("No signature found") + return signature + + def in_library(self, ppn: str) -> bool: + if ppn is None: + return False + links = self.get_book_links(f"kid:{ppn}") + return len(links) > 0 + + def get_location(self, ppn: str) -> str | None: + if ppn is None: + return None + link = self.get_book(f"{ppn}") + if link is None: + return None + return link.library_location diff --git a/src/backend/database.py b/src/backend/database.py index e2e5d03..1ef0477 100644 --- a/src/backend/database.py +++ b/src/backend/database.py @@ -1,8 +1,8 @@ import datetime import json import os +import re import sqlite3 as sql -import sys import tempfile from dataclasses import asdict from pathlib import Path @@ -10,9 +10,7 @@ from string import ascii_lowercase as lower from string import digits, punctuation from typing import Any, List, Optional, Tuple, Union -import loguru - -from src import DATABASE_DIR, LOG_DIR, settings +from src import DATABASE_DIR, settings from src.backend.db import ( CREATE_ELSA_FILES_TABLE, CREATE_ELSA_MEDIA_TABLE, @@ -21,6 +19,7 @@ from src.backend.db import ( CREATE_TABLE_FILES, CREATE_TABLE_MEDIA, CREATE_TABLE_MESSAGES, + CREATE_TABLE_NEWEDITIONS, CREATE_TABLE_PROF, CREATE_TABLE_SUBJECTS, CREATE_TABLE_USER, @@ -28,16 +27,10 @@ from src.backend.db import ( from src.errors import AppPresentError, NoResultError from src.logic import ELSA, Apparat, ApparatData, BookData, Prof from src.logic.constants import SEMAP_MEDIA_ACCOUNTS +from src.logic.semester import Semester +from src.shared.logging import log from src.utils.blob import create_blob -from .semester import Semester - -log = loguru.logger -log.remove() -log.add(sys.stdout, level="INFO") -log.add(f"{LOG_DIR}/application.log", rotation="1 MB", retention="10 days") - - ascii_lowercase = lower + digits + punctuation @@ -86,6 +79,7 @@ class Database: "elsa", "elsa_files", "elsa_media", + "neweditions", ] for table in required_tables: @@ -115,6 +109,8 @@ class Database: query = CREATE_ELSA_FILES_TABLE case "elsa_media": query = CREATE_ELSA_MEDIA_TABLE + case "neweditions": + query = CREATE_TABLE_NEWEDITIONS case _: log.error(f"Table {table_name} is not a valid table name") self.query_db(query) @@ -123,6 +119,66 @@ class Database: if not self.db_initialized: self.checkDatabaseStatus() self.db_initialized = True + # run migrations after initial creation to bring schema up-to-date + try: + if self.db_path is not None: + self.run_migrations() + except Exception as e: + log.error(f"Error while running migrations: {e}") + + # --- Migration helpers integrated into Database --- + def _ensure_migrations_table(self, conn: sql.Connection) -> None: + cursor = conn.cursor() + cursor.execute( + """ + CREATE TABLE IF NOT EXISTS schema_migrations ( + id TEXT PRIMARY KEY, + applied_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP + ) + """ + ) + conn.commit() + + def _applied_migrations(self, conn: sql.Connection) -> List[str]: + cursor = conn.cursor() + cursor.execute("SELECT id FROM schema_migrations ORDER BY id") + rows = cursor.fetchall() + return [r[0] for r in rows] + + def _apply_sql_file(self, conn: sql.Connection, path: Path) -> None: + log.info(f"Applying migration {path.name}") + sql_text = path.read_text(encoding="utf-8") + cursor = conn.cursor() + cursor.executescript(sql_text) + cursor.execute( + "INSERT OR REPLACE INTO schema_migrations (id) VALUES (?)", (path.name,) + ) + conn.commit() + + def run_migrations(self) -> None: + """Apply unapplied .sql migrations from src/backend/migrations using this Database's connection.""" + migrations_dir = Path(__file__).parent / "migrations" + if not migrations_dir.exists(): + log.debug("Migrations directory does not exist, skipping migrations") + return + + conn = self.connect() + try: + self._ensure_migrations_table(conn) + applied = set(self._applied_migrations(conn)) + + migration_files = sorted( + [p for p in migrations_dir.iterdir() if p.suffix == ".sql"] + ) + for m in migration_files: + if m.name in applied: + log.debug(f"Skipping already applied migration {m.name}") + continue + self._apply_sql_file(conn, m) + finally: + conn.close() + + # --- end migration helpers --- def overwritePath(self, new_db_path: str): log.debug("got new path, overwriting") @@ -144,7 +200,7 @@ class Database: self.create_tables() self.insertSubjects() - def getElsaMediaID(self, work_author, signature, pages): + def getElsaMediaID(self, work_author: str, signature: str, pages: str): query = ( "SELECT id FROM elsa_media WHERE work_author=? AND signature=? AND pages=?" ) @@ -160,7 +216,7 @@ class Database: query = "SELECT type FROM elsa_media WHERE id=?" return self.query_db(query, (id,), one=True)[0] - def get_db_contents(self) -> Union[List[Tuple], None]: + def get_db_contents(self) -> Union[List[Tuple[Any]], None]: """ Get the contents of the @@ -182,7 +238,13 @@ class Database: Returns: sql.Connection: The active connection to the database """ - return sql.connect(self.db_path) + conn = sql.connect(self.db_path) + # Fast pragmas suitable for a desktop app DB + conn.execute("PRAGMA journal_mode=WAL;") + conn.execute("PRAGMA synchronous=NORMAL;") + conn.execute("PRAGMA temp_store=MEMORY;") + conn.execute("PRAGMA mmap_size=134217728;") # 128MB + return conn def close_connection(self, conn: sql.Connection): """ @@ -198,20 +260,10 @@ class Database: """ 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_FILES) - cursor.execute(CREATE_TABLE_PROF) - cursor.execute(CREATE_TABLE_USER) - cursor.execute(CREATE_TABLE_SUBJECTS) - cursor.execute(CREATE_ELSA_TABLE) - cursor.execute(CREATE_ELSA_FILES_TABLE) - cursor.execute(CREATE_ELSA_MEDIA_TABLE) - conn.commit() - self.close_connection(conn) + # Bootstrapping of tables is handled via migrations. Run migrations instead + # of executing the hard-coded DDL here. Migrations are idempotent and + # contain the CREATE TABLE IF NOT EXISTS statements. + self.run_migrations() def insertInto(self, query: str, params: Tuple) -> None: """ @@ -223,16 +275,31 @@ class Database: """ conn = self.connect() cursor = conn.cursor() - log.debug(f"Inserting {params} into database with query {query}") + log.debug(f"Inserting into DB: {query}") cursor.execute(query, params) conn.commit() self.close_connection(conn) + def getWebADISAuth(self) -> Tuple[str, str]: + """ + Get the WebADIS authentication data from the database + + Returns: + Tuple[str, str]: The username and password for WebADIS + """ + result = self.query_db( + "SELECT username, password FROM webadis_login WHERE effective_range='SAP'", + one=True, + ) + if result is None: + return ("", "") + return (result[0], result[1]) + @log.catch def query_db( self, query: str, - args: Tuple[Any, Any] = (), # type:ignore + args: Tuple[Any] = (), # type:ignore one: bool = False, # type:ignore ) -> Union[Tuple[Any, Any], List[Tuple[Any, Any]]]: """ @@ -382,49 +449,66 @@ class 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]]: + def searchBook( + self, data: dict[str, str] + ) -> Optional[list[tuple["BookData", int, int]]]: """ - Search a book in the database based on the sent data. + Search a book in the database using regex against signature/title. 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 + data: may contain: + - "signature": regex to match against BookData.signature + - "title": regex to match against BookData.title Returns: - list[tuple[BookData, int]]: A list of tuples containing the wrapped Metadata and the id of the book + list of (BookData, app_id, prof_id) tuples, or None if invalid args """ - rdata = self.query_db("SELECT * FROM media WHERE deleted=0") - # log.debug(rdata, len(rdata)) + + # Determine mode (kept compatible with your original logic) mode = 0 - if len(data) == 1: - if "signature" in data.keys(): - mode = 1 - elif "title" in data.keys(): - mode = 2 - elif len(data) == 2: + if len(data) == 1 and "signature" in data: + mode = 1 + elif len(data) == 1 and "title" in data: + mode = 2 + elif len(data) == 2 and "signature" in data and "title" in data: mode = 3 else: return None - ret = [] - for book in rdata: - bookdata = BookData().from_string(book[1]) - app_id = book[2] - prof_id = book[3] + + def _compile(expr: str) -> re.Pattern: + try: + return re.compile(expr, re.IGNORECASE | re.UNICODE) + except re.error: + # If user provided a broken regex, treat it as a literal + return re.compile(re.escape(expr), re.IGNORECASE | re.UNICODE) + + sig_re = _compile(data["signature"]) if mode in (1, 3) else None + title_re = _compile(data["title"]) if mode in (2, 3) else None + + # Fetch candidates once + rows = self.query_db("SELECT * FROM media WHERE deleted=0") + + results: list[tuple["BookData", int, int]] = [] + for row in rows: + bookdata = BookData().from_string( + row[1] + ) # assumes row[1] is the serialized bookdata + app_id = row[2] + prof_id = row[3] + + sig_val = bookdata.signature + title_val = bookdata.title if mode == 1: - if data["signature"] in bookdata.signature: - ret.append((bookdata, app_id, prof_id)) + if sig_re.search(sig_val): + results.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)) - # log.debug(ret) - return ret + if title_re.search(title_val): + results.append((bookdata, app_id, prof_id)) + else: # mode == 3 + if sig_re.search(sig_val) and title_re.search(title_val): + results.append((bookdata, app_id, prof_id)) + + return results def setAvailability(self, book_id: str, available: str): """ @@ -452,7 +536,7 @@ class Database: """ result = self.query_db( "SELECT id FROM media WHERE bookdata=? AND app_id=? AND prof_id=?", - (dump_pickle(bookdata), app_id, prof_id), + (bookdata.to_dict, app_id, prof_id), one=True, ) return result[0] @@ -502,7 +586,15 @@ class Database: ret_result.append(data) return ret_result - def getAllBooks(self): + def getAllBooks(self) -> list[dict[str, Union[int, BookData]]]: + """ + Get all books in the database that are not set as deleted + + Returns + ------- + list[dict[str, Union[int, BookData]]] + A list of dictionaries containing the id and the metadata of the book + """ # return all books in the database qdata = self.query_db("SELECT id,bookdata FROM media WHERE deleted=0") ret_result: list[dict[str, Any]] = [] @@ -516,17 +608,31 @@ class Database: ret_result.append(data) return ret_result - def getBooksByProfId(self, prof_id: int, deleted: int = 0): + def getApparatNrByBookId(self, book_id): + appNr = self.query_db( + "SELECT appnr FROM semesterapparat WHERE id IN (SELECT app_id FROM media WHERE id=?)", + (book_id,), + one=True, + ) + return appNr[0] if appNr else None + + def getBooksByProfId( + self, prof_id: int, deleted: int = 0 + ) -> list[dict[str, Union[int, BookData]]]: """ Get the Books based on the professor id - Args: - 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. + Parameters + ---------- + prof_id : int + The ID of the professor + deleted : int, optional + If set to 1, it will include deleted books, by default 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 + Returns + ------- + list[dict[str, Union[int, BookData]]] + 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 prof_id={prof_id} AND (deleted={deleted if deleted == 0 else '1 OR deleted=0'})" @@ -563,6 +669,16 @@ class Database: """ self.query_db("UPDATE media SET deleted=1 WHERE id=?", (book_id,)) + def deleteBooks(self, ids: list[int]): + """ + Delete multiple books from the database + + Args: + ids (list[int]): A list of book ids to be deleted + """ + query = f"UPDATE media SET deleted=1 WHERE id IN ({','.join(['?'] * len(ids))})" + self.query_db(query, tuple(ids)) + # File Interactions def getBlob(self, filename: str, app_id: Union[str, int]) -> bytes: """ @@ -1151,7 +1267,7 @@ class Database: (semester, apparat_nr, apparat.name), ) # delete all books associated with the app_id - print(apparat_nr, app_id) + # print(apparat_nr, app_id) self.query_db("UPDATE media SET deleted=1 WHERE app_id=?", (app_id,)) def isEternal(self, id): @@ -1224,11 +1340,11 @@ class Database: 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 + def checkApparatExistsByNr(self, app_nr: Union[str, int]) -> bool: + """a check to see if the apparat is already present in the database, based on the nr. This query will exclude deleted apparats Args: - app_id (Union[str, int]): the id of the apparat + app_nr (Union[str, int]): the id of the apparat Returns: bool: True if the apparat is present, False if not @@ -1236,7 +1352,9 @@ class Database: return ( True if self.query_db( - "SELECT appnr FROM semesterapparat WHERE appnr=?", (app_id,), one=True + "SELECT id FROM semesterapparat WHERE appnr=? and deletion_status=0", + (app_nr,), + one=True, ) else False ) @@ -1621,7 +1739,7 @@ class Database: tempdir.mkdir(parents=True, exist_ok=True) file = tempfile.NamedTemporaryFile( - delete=False, dir=tempdir_path, mode="wb", suffix=f".{filetype}" + delete=False, dir=tempdir, mode="wb", suffix=f".{filetype}" ) file.write(blob) # log.debug("file created") @@ -1684,9 +1802,9 @@ class Database: telnr = profdata.telnr title = profdata.title - query = f"INSERT INTO prof (fname, lname, fullname, mail, telnr,titel) VALUES ('{fname}','{lname}','{fullname}','{mail}','{telnr}','{title}')" + query = "INSERT INTO prof (fname, lname, fullname, mail, telnr, titel) VALUES (?,?,?,?,?,?)" log.debug(query) - cursor.execute(query) + cursor.execute(query, (fname, lname, fullname, mail, telnr, title)) conn.commit() conn.close() @@ -1729,10 +1847,10 @@ class Database: fullname = profdata["profname"] else: fullname = profdata.name() - query = f"SELECT id FROM prof WHERE fullname = '{fullname}'" + query = "SELECT id FROM prof WHERE fullname = ?" log.debug(query) - cursor.execute(query) + cursor.execute(query, (fullname,)) result = cursor.fetchone() if result: return result[0] @@ -1747,10 +1865,10 @@ class Database: """ conn = self.connect() cursor = conn.cursor() - query = f"SELECT * FROM prof WHERE fullname = '{fullname}'" + query = "SELECT * FROM prof WHERE fullname = ?" log.debug(query) - result = cursor.execute(query).fetchone() + result = cursor.execute(query, (fullname,)).fetchone() if result: return Prof().from_tuple(result) else: @@ -1766,8 +1884,8 @@ class Database: int | None: The id of the prof or None if not found """ - query = f"SELECT prof_id from semesterapparat WHERE appnr = '{apprarat_id}' and deletion_status = 0" - data = self.query_db(query) + query = "SELECT prof_id from semesterapparat WHERE appnr = ? and deletion_status = 0" + data = self.query_db(query, (apprarat_id,)) if data: log.info("Prof ID: " + str(data[0][0])) return data[0][0] @@ -1778,20 +1896,13 @@ class Database: # get book data new_apparat_id = apparat new_prof_id = self.getProfIDByApparat(new_apparat_id) - query = f""" - INSERT INTO media (bookdata, app_id, prof_id, deleted, available, reservation) - SELECT - bookdata, - '{new_apparat_id}', - '{new_prof_id}', - 0, - available, - reservation - FROM media - where id = '{book_id}'""" + query = ( + "INSERT INTO media (bookdata, app_id, prof_id, deleted, available, reservation) " + "SELECT bookdata, ?, ?, 0, available, reservation FROM media WHERE id = ?" + ) connection = self.connect() cursor = connection.cursor() - cursor.execute(query) + cursor.execute(query, (new_apparat_id, new_prof_id, book_id)) connection.commit() connection.close() @@ -1803,16 +1914,18 @@ class Database: appratat (int): the ID of the new apparat """ # get book data - query = f"UPDATE media SET app_id = '{appratat}' WHERE id = '{book_id}'" + query = "UPDATE media SET app_id = ? WHERE id = ?" connection = self.connect() cursor = connection.cursor() - cursor.execute(query) + cursor.execute(query, (appratat, book_id)) connection.commit() connection.close() def getApparatNameByAppNr(self, appnr: int): - query = f"SELECT name FROM semesterapparat WHERE appnr = '{appnr}' and deletion_status = 0" - data = self.query_db(query) + query = ( + "SELECT name FROM semesterapparat WHERE appnr = ? and deletion_status = 0" + ) + data = self.query_db(query, (appnr,)) if data: return data[0][0] else: @@ -1825,3 +1938,71 @@ class Database: result = cursor.fetchone() connection.close() return result + + def getBookIdByPPN(self, ppn: str) -> int: + query = "SELECT id FROM media WHERE bookdata LIKE ?" + data = self.query_db(query, (f"%{ppn}%",)) + if data: + return data[0][0] + else: + return None + + def getNewEditionsByApparat(self, apparat_id: int) -> list[BookData]: + """Get all new editions for a specific apparat + + Args: + apparat_id (int): the id of the apparat + + Returns: + list[tuple]: A list of tuples containing the new editions data + """ + query = "SELECT * FROM neweditions WHERE for_apparat=? AND ordered=0" + results = self.query_db(query, (apparat_id,)) + res = [] + for result in results: + # keep only new edition payload; old edition can be reconstructed if needed + res.append(BookData().from_string(result[1])) + return res + + def setOrdered(self, newBook_id: int): + query = "UPDATE neweditions SET ordered=1 WHERE id=?" + self.query_db(query, (newBook_id,)) + + def getBooksWithNewEditions(self, app_id) -> List[BookData]: + # select all bookdata from media, based on the old_edition_id in neweditions where for_apparat = app_id; also get the new_edition bookdata + + query = "SELECT m.bookdata, new_bookdata FROM media m JOIN neweditions n ON m.id = n.old_edition_id WHERE n.for_apparat = ?" + results = self.query_db(query, (app_id,)) + # store results in tuple old,new + res = [] + for result in results: + oldedition = BookData().from_string(result[0]) + newedition = BookData().from_string(result[1]) + res.append((oldedition, newedition)) + return res + + def getNewEditionId(self, newBook: BookData): + query = "SELECT id FROM neweditions WHERE new_bookdata LIKE ?" + args = ( + newBook.isbn[0] if newBook.isbn and len(newBook.isbn) > 0 else newBook.ppn + ) + params = (f"%{args}%",) + data = self.query_db(query, params, one=True) + if data: + return data[0] + else: + return None + + def insertNewEdition(self, newBook: BookData, oldBookId: int, for_apparat: int): + # check if new edition already in table, check based on newBook.ppn + check_query = "SELECT id FROM neweditions WHERE new_bookdata LIKE ?" + check_params = (f"%{newBook.ppn}%",) + data = self.query_db(check_query, check_params, one=True) + if data: + log.info("New edition already in table, skipping insert") + return + + query = "INSERT INTO neweditions (new_bookdata, old_edition_id, for_apparat) VALUES (?,?,?)" + params = (newBook.to_dict, oldBookId, for_apparat) + + self.query_db(query, params) diff --git a/src/backend/db.py b/src/backend/db.py index 0ee03fb..991cb59 100644 --- a/src/backend/db.py +++ b/src/backend/db.py @@ -101,3 +101,12 @@ CREATE_ELSA_MEDIA_TABLE = """CREATE TABLE elsa_media ( elsa_id INTEGER NOT NULL, FOREIGN KEY (elsa_id) REFERENCES elsa (id) )""" +CREATE_TABLE_NEWEDITIONS = """CREATE TABLE neweditions ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + new_bookdata TEXT, + old_edition_id INTEGER, + for_apparat INTEGER, + ordered BOOLEAN DEFAULT (0), + FOREIGN KEY (old_edition_id) REFERENCES media (id), + FOREIGN KEY (for_apparat) REFERENCES semesterapparat (id) +)""" diff --git a/src/backend/delete_temp_contents.py b/src/backend/delete_temp_contents.py index 21f393c..a61062b 100644 --- a/src/backend/delete_temp_contents.py +++ b/src/backend/delete_temp_contents.py @@ -1,5 +1,4 @@ import os -from pathlib import Path from src import settings database = settings.database diff --git a/src/backend/migration_runner.py b/src/backend/migration_runner.py new file mode 100644 index 0000000..a8393b8 --- /dev/null +++ b/src/backend/migration_runner.py @@ -0,0 +1,68 @@ +import os +import sqlite3 as sql +from pathlib import Path +from typing import List + +from src import DATABASE_DIR, settings +from src.shared.logging import log + +MIGRATIONS_DIR = Path(__file__).parent / "migrations" + + +def _ensure_migrations_table(conn: sql.Connection) -> None: + cursor = conn.cursor() + cursor.execute( + """ + CREATE TABLE IF NOT EXISTS schema_migrations ( + id TEXT PRIMARY KEY, + applied_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP + ) + """ + ) + conn.commit() + + +def _applied_migrations(conn: sql.Connection) -> List[str]: + cursor = conn.cursor() + cursor.execute("SELECT id FROM schema_migrations ORDER BY id") + rows = cursor.fetchall() + return [r[0] for r in rows] + + +def _apply_sql_file(conn: sql.Connection, path: Path) -> None: + log.info(f"Applying migration {path.name}") + sql_text = path.read_text(encoding="utf-8") + cursor = conn.cursor() + cursor.executescript(sql_text) + cursor.execute( + "INSERT OR REPLACE INTO schema_migrations (id) VALUES (?)", (path.name,) + ) + conn.commit() + + +def run_migrations(db_path: Path) -> None: + """Run all unapplied migrations from the migrations directory against the database at db_path.""" + if not MIGRATIONS_DIR.exists(): + log.debug("Migrations directory does not exist, skipping migrations") + return + + # Ensure database directory exists + db_dir = settings.database.path or Path(DATABASE_DIR) + if not db_dir.exists(): + os.makedirs(db_dir, exist_ok=True) + + conn = sql.connect(db_path) + try: + _ensure_migrations_table(conn) + applied = set(_applied_migrations(conn)) + + migration_files = sorted( + [p for p in MIGRATIONS_DIR.iterdir() if p.suffix in (".sql",)] + ) + for m in migration_files: + if m.name in applied: + log.debug(f"Skipping already applied migration {m.name}") + continue + _apply_sql_file(conn, m) + finally: + conn.close() diff --git a/src/backend/migrations/V001__create_base_tables.sql b/src/backend/migrations/V001__create_base_tables.sql new file mode 100644 index 0000000..4848add --- /dev/null +++ b/src/backend/migrations/V001__create_base_tables.sql @@ -0,0 +1,132 @@ +BEGIN TRANSACTION; + +CREATE TABLE IF NOT EXISTS 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, + FOREIGN KEY (prof_id) REFERENCES prof (id) + ); + +CREATE TABLE IF NOT EXISTS media ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + bookdata TEXT, + 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 IF NOT EXISTS 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 IF NOT EXISTS 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 IF NOT EXISTS 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 IF NOT EXISTS 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 IF NOT EXISTS subjects ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name TEXT NOT NULL UNIQUE +); + +CREATE TABLE IF NOT EXISTS elsa ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + date TEXT NOT NULL, + semester TEXT NOT NULL, + prof_id INTEGER NOT NULL + ); + +CREATE TABLE IF NOT EXISTS elsa_files ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + filename TEXT NOT NULL, + fileblob BLOB NOT NULL, + elsa_id INTEGER NOT NULL, + filetyp TEXT NOT NULL, + FOREIGN KEY (elsa_id) REFERENCES elsa (id) + ); + +CREATE TABLE IF NOT EXISTS elsa_media ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + work_author TEXT, + section_author TEXT, + year TEXT, + edition TEXT, + work_title TEXT, + chapter_title TEXT, + location TEXT, + publisher TEXT, + signature TEXT, + issue TEXT, + pages TEXT, + isbn TEXT, + type TEXT, + elsa_id INTEGER NOT NULL, + FOREIGN KEY (elsa_id) REFERENCES elsa (id) + ); + +CREATE TABLE IF NOT EXISTS neweditions ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + new_bookdata TEXT, + old_edition_id INTEGER, + for_apparat INTEGER, + ordered BOOLEAN DEFAULT (0), + FOREIGN KEY (old_edition_id) REFERENCES media (id), + FOREIGN KEY (for_apparat) REFERENCES semesterapparat (id) +); + +-- Helpful indices to speed up frequent lookups and joins +CREATE INDEX IF NOT EXISTS idx_media_app_prof ON media(app_id, prof_id); +CREATE INDEX IF NOT EXISTS idx_media_deleted ON media(deleted); +CREATE INDEX IF NOT EXISTS idx_media_available ON media(available); +CREATE INDEX IF NOT EXISTS idx_messages_remind_at ON messages(remind_at); +CREATE INDEX IF NOT EXISTS idx_semesterapparat_prof ON semesterapparat(prof_id); +CREATE INDEX IF NOT EXISTS idx_semesterapparat_appnr ON semesterapparat(appnr); + +COMMIT; diff --git a/src/backend/migrations/V002__create_table_webadis_login.sql b/src/backend/migrations/V002__create_table_webadis_login.sql new file mode 100644 index 0000000..5e1b3a8 --- /dev/null +++ b/src/backend/migrations/V002__create_table_webadis_login.sql @@ -0,0 +1,10 @@ +BEGIN TRANSACTION; + +CREATE TABLE IF NOT EXISTS webadis_login ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + username TEXT NOT NULL, + password TEXT NOT NULL + ); + +COMMIT; + diff --git a/src/backend/migrations/V003_update_webadis_add_user_area.sql b/src/backend/migrations/V003_update_webadis_add_user_area.sql new file mode 100644 index 0000000..1b5567b --- /dev/null +++ b/src/backend/migrations/V003_update_webadis_add_user_area.sql @@ -0,0 +1,6 @@ +BEGIN TRANSACTION; + +ALTER TABLE webadis_login +ADD COLUMN effective_range TEXT; + +COMMIT; \ No newline at end of file diff --git a/src/backend/thread_bookgrabber.py b/src/backend/thread_bookgrabber.py index 02fef02..7736ab4 100644 --- a/src/backend/thread_bookgrabber.py +++ b/src/backend/thread_bookgrabber.py @@ -1,19 +1,10 @@ -from PySide6.QtCore import QThread -from PySide6.QtCore import Signal +from PySide6.QtCore import QThread, Signal + from src.backend import Database - from src.logic.webrequest import BibTextTransformer, WebRequest -import loguru -import sys -from src import LOG_DIR -log = loguru.logger -log.remove() -log.add(sys.stdout, level="INFO") -log.add(f"{LOG_DIR}/application.log", rotation="1 MB", retention="10 days") +from src.shared.logging import log - -# logger.add(sys.stderr, format="{time} {level} {message}", level="INFO") -log.add(sys.stdout, level="INFO") +# Logger configured centrally in main; this module just uses `log` class BookGrabber(QThread): @@ -31,9 +22,10 @@ class BookGrabber(QThread): self.book_id = None self.use_any = False self.use_exact = False - self.app_id = None + self.app_nr = None self.tstate = (self.app_id, self.prof_id, self.mode, self.data) self.request = WebRequest() + self.db = Database() def add_values( self, app_id: int, prof_id: int, mode: str, data, any_book=False, exact=False @@ -45,13 +37,15 @@ class BookGrabber(QThread): self.use_any = any_book self.use_exact = exact log.info(f"Working on {len(self.data)} entries") - self.tstate = (self.app_id, self.prof_id, self.mode, self.data) + self.tstate = (self.app_nr, self.prof_id, self.mode, self.data) log.debug("State: " + str(self.tstate)) - self.request.set_apparat(self.app_id) + app_nr = self.db.query_db( + "SELECT appnr FROM semesterapparat WHERE id = ?", (self.app_id,) + )[0][0] + self.request.set_apparat(app_nr) # log.debug(self.tstate) def run(self): - self.db = Database() item = 0 iterdata = self.data # log.debug(iterdata) @@ -91,7 +85,7 @@ class BookGrabber(QThread): state = 0 for result in transformer.RDS_DATA: # log.debug(result.RDS_LOCATION) - if str(self.app_id) in result.RDS_LOCATION: + if str(self.app_nr) in result.RDS_LOCATION: state = 1 break @@ -126,27 +120,27 @@ class BookGrabberTest(QThread): self.is_Running = True log.info("Starting worker thread") self.data = None - self.app_id = None + self.app_nr = None self.prof_id = None self.mode = None self.book_id = None self.use_any = False self.use_exact = False - self.app_id = appnr - self.tstate = (self.app_id, self.prof_id, self.mode, self.data) + self.app_nr = appnr + self.tstate = (self.app_nr, self.prof_id, self.mode, self.data) self.results = [] def add_values( - self, app_id: int, prof_id: int, mode: str, data, any_book=False, exact=False + self, app_nr: int, prof_id: int, mode: str, data, any_book=False, exact=False ): - self.app_id = app_id + self.app_nr = app_nr self.prof_id = prof_id self.mode = mode self.data = data self.use_any = any_book self.use_exact = exact log.info(f"Working on {len(self.data)} entries") - self.tstate = (self.app_id, self.prof_id, self.mode, self.data) + self.tstate = (self.app_nr, self.prof_id, self.mode, self.data) log.debug("State: " + str(self.tstate)) # log.debug(self.tstate) @@ -159,7 +153,7 @@ class BookGrabberTest(QThread): signature = str(entry) log.info("Processing entry: " + signature) - webdata = WebRequest().set_apparat(self.app_id).get_ppn(entry) + webdata = WebRequest().set_apparat(self.app_nr).get_ppn(entry) if self.use_any: webdata = webdata.use_any_book webdata = webdata.get_data() @@ -186,7 +180,7 @@ class BookGrabberTest(QThread): state = 0 for result in transformer.RDS_DATA: # log.debug(result.RDS_LOCATION) - if str(self.app_id) in result.RDS_LOCATION: + if str(self.app_nr) in result.RDS_LOCATION: state = 1 break diff --git a/src/backend/thread_neweditions.py b/src/backend/thread_neweditions.py index 45e662f..7de4026 100644 --- a/src/backend/thread_neweditions.py +++ b/src/backend/thread_neweditions.py @@ -1,113 +1,152 @@ +import os import re -import sys from concurrent.futures import ThreadPoolExecutor -from datetime import datetime from math import ceil from queue import Empty, Queue -from typing import List, Optional, Set, Union +from time import monotonic # <-- NEW +from typing import List, Optional -import loguru from PySide6.QtCore import QThread, Signal -from src import LOG_DIR +# from src.logic.webrequest import BibTextTransformer, WebRequest +from src.backend.catalogue import Catalogue from src.logic import BookData -from src.logic.lehmannsapi import LehmannsClient -from src.logic.swb import SWB +from src.logic.SRU import SWB +from src.shared.logging import log -log = loguru.logger -log.remove() -log.add(sys.stdout, level="INFO") -log.add(f"{LOG_DIR}/application.log", rotation="1 MB", retention="10 days") +# use all available cores - 2, but at least 1 +THREAD_COUNT = max(os.cpu_count() - 2, 1) +THREAD_MIN_ITEMS = 5 -log.add( - f"{LOG_DIR}/{datetime.now().strftime('%Y-%m-%d')}.log", - rotation="1 day", - retention="1 month", -) +# Logger configured centrally in main; use shared `log` + +swb = SWB() +dnb = SWB() +cat = Catalogue() + +RVK_ALLOWED = r"[A-Z0-9.\-\/]" # conservative RVK character set -def _norm_text(s: Optional[str]) -> str: - if not s: - return "" - # lowercase, collapse whitespace, drop some punctuation - s = s.lower() - s = re.sub(r"[\s\-\u2013\u2014]+", " ", s) # spaces/dashes - s = re.sub(r"[\"'`:.,;!?()\[\]{}]", "", s) - return s.strip() - - -def _same_book(a: BookData, b: BookData) -> bool: - """Heuristic: same if ISBNs intersect; fallback to (title, author, year) normalized.""" - isbns_a = _norm_isbns(a.isbn) - isbns_b = _norm_isbns(b.isbn) - if isbns_a and isbns_b and (isbns_a & isbns_b): - return True - - ta, tb = _norm_text(a.title), _norm_text(b.title) - aa, ab = _norm_text(a.author), _norm_text(b.author) - ya, yb = (a.year or "").strip(), (b.year or "").strip() - - # strong title match required; then author if available; then year if available - if ta and tb and ta == tb: - # if both have authors, require match - if aa and ab and aa == ab: - # if both have year, require match - if ya and yb: - return ya == yb - return True - # if one/both authors missing, allow title (+year if both present) - if ya and yb: - return ya == yb - return True - - return False - - -def _norm_isbns(value: Union[str, List[str], None]) -> Set[str]: - """Return a set of 10/13-digit ISBNs (digits only, keep X for ISBN-10 if present).""" - if value is None: - return set() - vals = value if isinstance(value, list) else [value] - out: Set[str] = set() - for v in vals: - s = str(v) - digits = re.sub(r"[^0-9Xx]", "", s) - # keep 13-digit or 10-digit tokens - m13 = re.findall(r"97[89]\d{10}", digits) - if m13: - out.update(m13) - else: - m10 = re.findall(r"\d{9}[0-9Xx]", digits) - out.update(x.upper() for x in m10) - return out - - -def filter_prefer_swb(records: List[BookData]) -> List[BookData]: +def find_newer_edition( + swb_result: BookData, dnb_result: List[BookData] +) -> Optional[List[BookData]]: """ - If an SWB entry with a non-empty signature exists for a book, drop the HTTP(S) duplicate(s). - Returns a NEW list (does not mutate the input). + New edition if: + - year > swb.year OR + - edition_number > swb.edition_number + BUT: discard any candidate with year < swb.year (if both years are known). + + Same-work check: + - Compare RVK roots of signatures (after stripping trailing '+N' and '(N)'). + - If both have signatures and RVKs differ -> skip. + + Preferences (in order): + 1) RVK matches SWB + 2) Print over Online-Ressource + 3) Has signature + 4) Newer: (year desc, edition_number desc) """ - swb_with_sig = [ - r - for r in records - if (r.link == "SWB") and (r.signature and r.signature.strip()) - ] - if not swb_with_sig: - return list(records) - to_remove: Set[int] = set() + def strip_copy_and_edition(s: str) -> str: + s = re.sub(r"\(\s*\d+\s*\)", "", s) # remove '(N)' + s = re.sub(r"\s*\+\s*\d+\s*$", "", s) # remove trailing '+N' + return s - # For each URL entry, see if it matches any SWB-with-signature entry - for idx, rec in enumerate(records): - if not rec.link or not rec.link.lower().startswith("http"): - continue - for swb in swb_with_sig: - if _same_book(swb, rec): - to_remove.add(idx) - break + def extract_rvk_root(sig: Optional[str]) -> str: + if not sig: + return "" + t = strip_copy_and_edition(sig.upper()) + t = re.sub(r"\s+", " ", t).strip() + m = re.match(rf"^([A-Z]{{1,3}}\s*{RVK_ALLOWED}*)", t) + if not m: + cleaned = re.sub(rf"[^{RVK_ALLOWED} ]+", "", t).strip() + return cleaned.split(" ")[0] if cleaned else "" + return re.sub(r"\s+", " ", m.group(1)).strip() - # Build filtered list - return [rec for i, rec in enumerate(records) if i not in to_remove] + def has_sig(b: BookData) -> bool: + return bool(getattr(b, "signature", None)) + + def is_online(b: BookData) -> bool: + return (getattr(b, "media_type", None) or "").strip() == "Online-Ressource" + + def is_print(b: BookData) -> bool: + return not is_online(b) + + def rvk_matches_swb(b: BookData) -> bool: + if not has_sig(b) or not has_sig(swb_result): + return False + return extract_rvk_root(b.signature) == extract_rvk_root(swb_result.signature) + + def strictly_newer(b: BookData) -> bool: + # Hard guard: if both years are known and candidate is older, discard + if ( + b.year is not None + and swb_result.year is not None + and b.year < swb_result.year + ): + return False + + newer_by_year = ( + b.year is not None + and swb_result.year is not None + and b.year > swb_result.year + ) + newer_by_edition = ( + b.edition_number is not None + and swb_result.edition_number is not None + and b.edition_number > swb_result.edition_number + ) + # Thanks to the guard above, newer_by_edition can't pick something with a smaller year. + return newer_by_year or newer_by_edition + + swb_has_sig = has_sig(swb_result) + swb_rvk = extract_rvk_root(getattr(swb_result, "signature", None)) + + # 1) Filter: same work (by RVK if both have sigs) AND strictly newer + candidates: List[BookData] = [] + for b in dnb_result: + if has_sig(b) and swb_has_sig: + if extract_rvk_root(b.signature) != swb_rvk: + continue # different work + if strictly_newer(b): + candidates.append(b) + + if not candidates: + return None + + # 2) Dedupe by PPN → prefer (rvk-match, is-print, has-signature) + def pref_score(x: BookData) -> tuple[int, int, int]: + return ( + 1 if rvk_matches_swb(x) else 0, + 1 if is_print(x) else 0, + 1 if has_sig(x) else 0, + ) + + by_ppn: dict[Optional[str], BookData] = {} + for b in candidates: + key = getattr(b, "ppn", None) + prev = by_ppn.get(key) + if prev is None or pref_score(b) > pref_score(prev): + by_ppn[key] = b + + deduped = list(by_ppn.values()) + if not deduped: + return None + + # 3) Preserve all qualifying newer editions, but order by preference + def sort_key(b: BookData): + year = b.year if b.year is not None else -1 + ed = b.edition_number if b.edition_number is not None else -1 + return ( + 1 if rvk_matches_swb(b) else 0, + 1 if is_print(b) else 0, + 1 if has_sig(b) else 0, + year, + ed, + ) + + deduped.sort(key=sort_key, reverse=True) + return deduped class NewEditionCheckerThread(QThread): @@ -116,6 +155,10 @@ class NewEditionCheckerThread(QThread): total_entries_signal = Signal(int) resultsSignal = Signal(list) # list[tuple[BookData, list[BookData]]] + # NEW: metrics signals + rateSignal = Signal(float) # items per second ("it/s") + etaSignal = Signal(int) # seconds remaining (-1 when unknown) + def __init__(self, entries: Optional[list["BookData"]] = None, parent=None): super().__init__(parent) self.entries: list["BookData"] = entries if entries is not None else [] @@ -155,49 +198,64 @@ class NewEditionCheckerThread(QThread): def _process_book( cls, book: "BookData" ) -> tuple["BookData", list["BookData"]] | None: - author = ( - book.author.split(";")[0].replace(" ", "") - if (book.author and ";" in book.author) - else (book.author or "").replace(" ", "") - ) - title = cls._clean_title(book.title or "") - - # Query SWB - response: list[BookData] = SWB().getBooks( - [ - "pica.bib=20735", - f"pica.tit={title.split(':')[0].strip()}", - # f"pica.per={author}", - ] - ) - - # Remove same PPN - response = [entry for entry in response if entry.ppn != book.ppn] - for respo in response: - respo.link = "SWB" - - # Query Lehmanns - with LehmannsClient() as client: - results = client.search_by_title(title, strict=True) - if results: - for res in results: - response.append(BookData().from_LehmannsSearchResult(res)) - - if not response: + """Process one book; returns (original, [found editions]) or None on failure.""" + if not book.title: return None - - response = filter_prefer_swb(response) - - # Remove entries matching the same ISBN as the current book - response = [ - entry - for entry in response - if not (_norm_isbns(entry.isbn) & _norm_isbns(book.isbn)) + response: list["BookData"] = [] + query = [ + f"pica.tit={book.title}", + f"pica.vlg={book.publisher}", ] - if not response: - return None + swb_result = swb.getBooks(["pica.bib=20735", f"pica.ppn={book.ppn}"])[0] + dnb_results = swb.getBooks(query) + new_editions = find_newer_edition(swb_result, dnb_results) + if new_editions is not None: + for new_edition in new_editions: + new_edition.library_location = cat.get_location(new_edition.ppn) + try: + isbn = ( + str(new_edition.isbn[0]) + if isinstance(new_edition.isbn, list) + else str(new_edition.isbn) + ) + new_edition.link = ( + f"https://www.lehmanns.de/search/quick?mediatype_id=2&q={isbn}" + ) + except (IndexError, TypeError): + isbn = None + new_edition.in_library = cat.in_library(new_edition.ppn) + response = new_editions + + # client = SWB() + # response: list["BookData"] = [] + # # First, search by title only + # results = client.getBooks([f"pica.title={title}", f"pica.vlg={book.publisher}"]) + + # lehmanns = LehmannsClient() + # results = lehmanns.search_by_title(title) + # for result in results: + # if "(eBook)" in result.title: + # result.title = result.title.replace("(eBook)", "").strip() + # swb_results = client.getBooks( + # [ + # f"pica.tit={result.title}", + # f"pica.vlg={result.publisher.split(',')[0]}", + # ] + # ) + # for swb in swb_results: + # if swb.isbn == result.isbn: + # result.ppn = swb.ppn + # result.signature = swb.signature + # response.append(result) + # if (result.edition_number < swb.edition_number) and ( + # swb.year > result.year + # ): + # response.append(result) + if response == []: + return None + # Remove duplicates based on ppn return (book, response) @classmethod @@ -221,13 +279,19 @@ class NewEditionCheckerThread(QThread): total = len(self.entries) self.total_entries_signal.emit(total) + # start timer for metrics + t0 = monotonic() + if total == 0: log.debug("No entries to process.") + # emit metrics (zero work) + self.rateSignal.emit(0.0) + self.etaSignal.emit(0) self.resultsSignal.emit([]) return # Up to 4 workers; ~20 items per worker - num_workers = min(4, max(1, ceil(total / 20))) + num_workers = min(THREAD_COUNT, max(1, ceil(total / THREAD_MIN_ITEMS))) chunks = self._split_evenly(self.entries, num_workers) sizes = [len(ch) for ch in chunks] @@ -255,9 +319,27 @@ class NewEditionCheckerThread(QThread): processed += int(payload) self.updateSignal.emit(processed, total) self.updateProgress.emit(processed, total) + + # ---- NEW: compute & emit metrics ---- + elapsed = max(1e-9, monotonic() - t0) + rate = processed / elapsed # items per second + remaining = max(0, total - processed) + eta_sec = int(round(remaining / rate)) if rate > 0 else -1 + + self.rateSignal.emit(rate) + # clamp negative just in case + self.etaSignal.emit(max(0, eta_sec) if eta_sec >= 0 else -1) + # ------------------------------------- + elif kind == "result": self.results.append(payload) elif kind == "done": finished_workers += 1 + # Final metrics on completion + elapsed_total = max(1e-9, monotonic() - t0) + final_rate = total / elapsed_total + self.rateSignal.emit(final_rate) + self.etaSignal.emit(0) + self.resultsSignal.emit(self.results) diff --git a/src/backend/threads_autoadder.py b/src/backend/threads_autoadder.py index b793ae6..e956ef9 100644 --- a/src/backend/threads_autoadder.py +++ b/src/backend/threads_autoadder.py @@ -1,13 +1,15 @@ +import sys import time +import loguru + # from icecream import ic from PySide6.QtCore import QThread from PySide6.QtCore import Signal as Signal -from src.backend import Database -import loguru -import sys from src import LOG_DIR +from src.backend import Database + log = loguru.logger log.remove() log.add(sys.stdout, level="INFO") @@ -29,8 +31,8 @@ class AutoAdder(QThread): self.app_id = app_id self.prof_id = prof_id - # print("Launched AutoAdder") - # print(self.data, self.app_id, self.prof_id) + # #print("Launched AutoAdder") + # #print(self.data, self.app_id, self.prof_id) def run(self): self.db = Database() @@ -46,7 +48,7 @@ class AutoAdder(QThread): time.sleep(1) except Exception as e: - # print(e) + # #print(e) log.exception( f"The query failed with message {e} for signature {entry}" ) diff --git a/src/backend/threads_availchecker.py b/src/backend/threads_availchecker.py index c3ba3cc..451df6e 100644 --- a/src/backend/threads_availchecker.py +++ b/src/backend/threads_availchecker.py @@ -1,22 +1,11 @@ -import time - # from icecream import ic from PySide6.QtCore import QThread from PySide6.QtCore import Signal as Signal from src.backend.database import Database -from src import LOG_DIR -from src.logic.webrequest import BibTextTransformer, WebRequest - -# from src.transformers import RDS_AVAIL_DATA -import loguru -import sys - -log = loguru.logger -log.remove() -log.add(sys.stdout, level="INFO") -log.add(f"{LOG_DIR}/application.log", rotation="1 MB", retention="10 days") - +from src.backend.webadis import get_book_medianr +from src.logic.webrequest import BibTextTransformer, TransformerType, WebRequest +from src.shared.logging import log class AvailChecker(QThread): @@ -24,7 +13,11 @@ class AvailChecker(QThread): updateProgress = Signal(int, int) def __init__( - self, links: list = None, appnumber: int = None, parent=None, books=list[dict] + self, + links: list[str] | None = None, + appnumber: int | None = None, + parent=None, + books: list[dict] | None = None, ): if links is None: links = [] @@ -39,11 +32,13 @@ class AvailChecker(QThread): ) self.links = links self.appnumber = appnumber - self.books = books + self.books = books or [] log.info( f"Started worker with appnumber: {self.appnumber} and links: {self.links} and {len(self.books)} books..." ) - time.sleep(2) + # Pre-create reusable request and transformer to avoid per-item overhead + self._request = WebRequest().set_apparat(self.appnumber) + self._rds_transformer = BibTextTransformer(TransformerType.RDS) def run(self): self.db = Database() @@ -51,9 +46,8 @@ class AvailChecker(QThread): count = 0 for link in self.links: log.info("Processing entry: " + str(link)) - data = WebRequest().set_apparat(self.appnumber).get_ppn(link).get_data() - transformer = BibTextTransformer("RDS") - rds = transformer.get_data(data).return_data("rds_availability") + data = self._request.get_ppn(link).get_data() + rds = self._rds_transformer.get_data(data).return_data("rds_availability") book_id = None if not rds or not rds.items: @@ -62,8 +56,8 @@ class AvailChecker(QThread): for item in rds.items: sign = item.superlocation loc = item.location - # # print(item.location) - if self.appnumber in sign or self.appnumber in loc: + # # #print(item.location) + if str(self.appnumber) in sign or str(self.appnumber) in loc: state = 1 break for book in self.books: @@ -71,7 +65,13 @@ class AvailChecker(QThread): book_id = book["id"] break log.info(f"State of {link}: " + str(state)) - # print("Updating availability of " + str(book_id) + " to " + str(state)) + # #print("Updating availability of " + str(book_id) + " to " + str(state)) + # use get_book_medianr to update the medianr of the book in the database + auth = self.db.getWebADISAuth + medianr = get_book_medianr(rds.items[0].callnumber, self.appnumber, auth) + book_data = book["bookdata"] + book_data.medianr = medianr + self.db.updateBookdata(book["id"], book_data) self.db.setAvailability(book_id, state) count += 1 self.updateProgress.emit(count, len(self.links)) diff --git a/src/backend/webadis.py b/src/backend/webadis.py new file mode 100644 index 0000000..af79650 --- /dev/null +++ b/src/backend/webadis.py @@ -0,0 +1,35 @@ +from playwright.sync_api import sync_playwright + + +def get_book_medianr(signature: str, semesterapparat_nr: int, auth: tuple) -> str: + with sync_playwright() as playwright: + browser = playwright.chromium.launch(headless=True) + context = browser.new_context() + page = context.new_page() + page.goto( + "https://bsz.ibs-bw.de:22998/aDISWeb/app?service=direct/0/Home/$DirectLink&sp=SDAP42" + ) + page.get_by_role("textbox", name="Benutzer").fill(auth[0]) + page.get_by_role("textbox", name="Benutzer").press("Tab") + page.get_by_role("textbox", name="Kennwort").fill(auth[1]) + page.get_by_role("textbox", name="Kennwort").press("Enter") + page.get_by_role("button", name="Katalog").click() + page.get_by_role("textbox", name="Signatur").click() + page.get_by_role("textbox", name="Signatur").fill(signature) + page.get_by_role("textbox", name="Signatur").press("Enter") + book_list = page.locator("iframe").content_frame.get_by_role( + "cell", name="Bibliothek der Pädagogischen" + ) + # this will always find one result, we need to split the resulting text based on the entries that start with "* " + book_entries = book_list.inner_text().split("\n") + books = [] + for entry in book_entries: + if entry.startswith("* "): + books.append(entry) + for book in books: + if f"Semesterapparat: {semesterapparat_nr}" in book: + return book.split("* ")[1].split(":")[0] + + # --------------------- + context.close() + browser.close() diff --git a/src/background/__init__.py b/src/background/__init__.py new file mode 100644 index 0000000..08935ad --- /dev/null +++ b/src/background/__init__.py @@ -0,0 +1,16 @@ +"""Background tasks and threading operations.""" + +from .autoadder import AutoAdder +from .availability_checker import AvailChecker +from .book_grabber import BookGrabber, BookGrabberTest +from .new_editions import NewEditionCheckerThread +from .documentation_server import DocumentationThread + +__all__ = [ + "AutoAdder", + "AvailChecker", + "BookGrabber", + "BookGrabberTest", + "NewEditionCheckerThread", + "DocumentationThread", +] diff --git a/src/background/autoadder.py b/src/background/autoadder.py new file mode 100644 index 0000000..d5863b7 --- /dev/null +++ b/src/background/autoadder.py @@ -0,0 +1,59 @@ +import sys +import time + +import loguru + +# from icecream import ic +from PySide6.QtCore import QThread +from PySide6.QtCore import Signal as Signal + +from src import LOG_DIR +from src.database import Database + +log = loguru.logger +log.remove() +log.add(sys.stdout, level="INFO") +log.add(f"{LOG_DIR}/application.log", rotation="1 MB", retention="10 days") + + +# from src.transformers import RDS_AVAIL_DATA + + +class AutoAdder(QThread): + updateSignal = Signal(int) + + setTextSignal = Signal(int) + progress = Signal(int) + + def __init__(self, data=None, app_id=None, prof_id=None, parent=None): + super().__init__(parent) + self.data = data + self.app_id = app_id + self.prof_id = prof_id + + # #print("Launched AutoAdder") + # #print(self.data, self.app_id, self.prof_id) + + def run(self): + self.db = Database() + # show the dialog, start the thread to gather data and dynamically update progressbar and listwidget + log.info("Starting worker thread") + item = 0 + for entry in self.data: + try: + self.updateSignal.emit(item) + self.setTextSignal.emit(entry) + item += 1 + self.progress.emit(item) + time.sleep(1) + + except Exception as e: + # #print(e) + log.exception( + f"The query failed with message {e} for signature {entry}" + ) + continue + if item == len(self.data): + log.info("Worker thread finished") + # teminate thread + self.finished.emit() diff --git a/src/background/availability_checker.py b/src/background/availability_checker.py new file mode 100644 index 0000000..b749a64 --- /dev/null +++ b/src/background/availability_checker.py @@ -0,0 +1,83 @@ +# from icecream import ic +from PySide6.QtCore import QThread +from PySide6.QtCore import Signal as Signal + +from src.database import Database +from src.services.webadis import get_book_medianr +from src.services.webrequest import BibTextTransformer, TransformerType, WebRequest +from src.shared.logging import log + + +class AvailChecker(QThread): + updateSignal = Signal(str, int) + updateProgress = Signal(int, int) + + def __init__( + self, + links: list[str] | None = None, + appnumber: int | None = None, + parent=None, + books: list[dict] | None = None, + ): + if links is None: + links = [] + super().__init__(parent) + log.info("Starting worker thread") + log.info( + "Checking availability for " + + str(links) + + " with appnumber " + + str(appnumber) + + "..." + ) + self.links = links + self.appnumber = appnumber + self.books = books or [] + log.info( + f"Started worker with appnumber: {self.appnumber} and links: {self.links} and {len(self.books)} books..." + ) + # Pre-create reusable request and transformer to avoid per-item overhead + self._request = WebRequest().set_apparat(self.appnumber) + self._rds_transformer = BibTextTransformer(TransformerType.RDS) + + def run(self): + self.db = Database() + state = 0 + count = 0 + for link in self.links: + log.info("Processing entry: " + str(link)) + data = self._request.get_ppn(link).get_data() + rds = self._rds_transformer.get_data(data).return_data("rds_availability") + + book_id = None + if not rds or not rds.items: + log.warning(f"No RDS data found for link {link}") + continue + for item in rds.items: + sign = item.superlocation + loc = item.location + # # #print(item.location) + if str(self.appnumber) in sign or str(self.appnumber) in loc: + state = 1 + break + for book in self.books: + if book["bookdata"].signature == link: + book_id = book["id"] + break + log.info(f"State of {link}: " + str(state)) + # #print("Updating availability of " + str(book_id) + " to " + str(state)) + # use get_book_medianr to update the medianr of the book in the database + auth = self.db.getWebADISAuth + medianr = get_book_medianr(rds.items[0].callnumber, self.appnumber, auth) + book_data = book["bookdata"] + book_data.medianr = medianr + self.db.updateBookdata(book["id"], book_data) + self.db.setAvailability(book_id, state) + count += 1 + self.updateProgress.emit(count, len(self.links)) + self.updateSignal.emit(item.callnumber, state) + + log.info("Worker thread finished") + # teminate thread + + self.quit() diff --git a/src/background/book_grabber.py b/src/background/book_grabber.py new file mode 100644 index 0000000..a134d88 --- /dev/null +++ b/src/background/book_grabber.py @@ -0,0 +1,199 @@ +from PySide6.QtCore import QThread, Signal + +from src.database import Database +from src.services.webrequest import BibTextTransformer, WebRequest +from src.shared.logging import log + +# Logger configured centrally in main; this module just uses `log` + + +class BookGrabber(QThread): + updateSignal = Signal(int, int) + done = Signal() + + def __init__(self): + super(BookGrabber, self).__init__(parent=None) + self.is_Running = True + log.info("Starting worker thread") + self.data = [] + self.app_id = None + self.prof_id = None + self.mode = None + self.book_id = None + self.use_any = False + self.use_exact = False + self.app_nr = None + self.tstate = (self.app_id, self.prof_id, self.mode, self.data) + self.request = WebRequest() + self.db = Database() + + def add_values( + self, app_id: int, prof_id: int, mode: str, data, any_book=False, exact=False + ): + self.app_id = app_id + self.prof_id = prof_id + self.mode = mode + self.data: list[str] = data + self.use_any = any_book + self.use_exact = exact + log.info(f"Working on {len(self.data)} entries") + self.tstate = (self.app_nr, self.prof_id, self.mode, self.data) + log.debug("State: " + str(self.tstate)) + app_nr = self.db.query_db( + "SELECT appnr FROM semesterapparat WHERE id = ?", (self.app_id,) + )[0][0] + self.request.set_apparat(app_nr) + # log.debug(self.tstate) + + def run(self): + item = 0 + iterdata = self.data + # log.debug(iterdata) + + for entry in iterdata: + # log.debug(entry) + log.info("Processing entry: {}", entry) + + webdata = self.request.get_ppn(entry) + if self.use_any: + webdata = webdata.use_any_book + webdata = webdata.get_data() + + if webdata == "error": + continue + + bd = BibTextTransformer(self.mode) + log.debug(webdata) + if self.mode == "ARRAY": + if self.use_exact: + bd = bd.use_signature(entry) + bd = bd.get_data(webdata).return_data() + log.debug(bd) + if bd is None: + # bd = BookData + continue + bd.signature = entry + transformer = ( + BibTextTransformer("RDS").get_data(webdata).return_data("rds_data") + ) + + # confirm lock is acquired + self.db.addBookToDatabase(bd, self.app_id, self.prof_id) + # get latest book id + self.book_id = self.db.getLastBookId() + log.info("Added book to database") + state = 0 + for result in transformer.RDS_DATA: + # log.debug(result.RDS_LOCATION) + if str(self.app_nr) in result.RDS_LOCATION: + state = 1 + break + + log.info(f"State of {entry}: {state}") + log.debug( + "updating availability of " + str(self.book_id) + " to " + str(state) + ) + try: + self.db.setAvailability(self.book_id, state) + log.debug("Added book to database") + except Exception as e: + log.error(f"Failed to update availability: {e}") + log.debug("Failed to update availability: " + str(e)) + + # time.sleep(5) + item += 1 + self.updateSignal.emit(item, len(self.data)) + log.info("Worker thread finished") + # self.done.emit() + self.quit() + + def stop(self): + self.is_Running = False + + +class BookGrabberTest(QThread): + updateSignal = Signal(int, int) + done = Signal() + + def __init__(self, appnr: int): + super(BookGrabberTest, self).__init__(parent=None) + self.is_Running = True + log.info("Starting worker thread") + self.data = None + self.app_nr = None + self.prof_id = None + self.mode = None + self.book_id = None + self.use_any = False + self.use_exact = False + self.app_nr = appnr + self.tstate = (self.app_nr, self.prof_id, self.mode, self.data) + self.results = [] + + def add_values( + self, app_nr: int, prof_id: int, mode: str, data, any_book=False, exact=False + ): + self.app_nr = app_nr + self.prof_id = prof_id + self.mode = mode + self.data = data + self.use_any = any_book + self.use_exact = exact + log.info(f"Working on {len(self.data)} entries") + self.tstate = (self.app_nr, self.prof_id, self.mode, self.data) + log.debug("State: " + str(self.tstate)) + # log.debug(self.tstate) + + def run(self): + item = 0 + iterdata = self.data + # log.debug(iterdata) + for entry in iterdata: + # log.debug(entry) + signature = str(entry) + log.info("Processing entry: " + signature) + + webdata = WebRequest().set_apparat(self.app_nr).get_ppn(entry) + if self.use_any: + webdata = webdata.use_any_book + webdata = webdata.get_data() + + if webdata == "error": + continue + + bd = BibTextTransformer(self.mode) + if self.mode == "ARRAY": + if self.use_exact: + bd = bd.use_signature(entry) + bd = bd.get_data(webdata).return_data() + if bd is None: + # bd = BookData + continue + bd.signature = entry + transformer = ( + BibTextTransformer("RDS").get_data(webdata).return_data("rds_data") + ) + + # confirm lock is acquired + # get latest book id + log.info("Added book to database") + state = 0 + for result in transformer.RDS_DATA: + # log.debug(result.RDS_LOCATION) + if str(self.app_nr) in result.RDS_LOCATION: + state = 1 + break + + log.info(f"State of {signature}: {state}") + # log.debug("updating availability of " + str(self.book_id) + " to " + str(state)) + self.results.append(bd) + + # time.sleep(5) + item += 1 + self.updateSignal.emit(item, len(self.data)) + log.info("Worker thread finished") + # self.done.emit() + self.quit() + + def stop(self): + self.is_Running = False diff --git a/src/background/documentation_server.py b/src/background/documentation_server.py new file mode 100644 index 0000000..57cffcb --- /dev/null +++ b/src/background/documentation_server.py @@ -0,0 +1,23 @@ +from PySide6.QtCore import QThread, Slot +from src.utils.documentation import website, QuietHandler +from wsgiref.simple_server import make_server + + +class DocumentationThread(QThread): + def __init__(self): + super().__init__() + self._server = None # store server so we can shut it down + + def run(self): + # launch_documentation() + self._server = make_server( + "localhost", 8000, website(), handler_class=QuietHandler + ) + while not self.isInterruptionRequested(): + self._server.handle_request() + + @Slot() # slot you can connect to aboutToQuit + def stop(self): + self.requestInterruption() # ask the loop above to exit + if self._server: + self._server.shutdown() # unblock handle_request() \ No newline at end of file diff --git a/src/background/new_editions.py b/src/background/new_editions.py new file mode 100644 index 0000000..05245a4 --- /dev/null +++ b/src/background/new_editions.py @@ -0,0 +1,345 @@ +import os +import re +from concurrent.futures import ThreadPoolExecutor +from math import ceil +from queue import Empty, Queue +from time import monotonic # <-- NEW +from typing import List, Optional + +from PySide6.QtCore import QThread, Signal + +# from src.services.webrequest import BibTextTransformer, WebRequest +from src.services.catalogue import Catalogue +from src.core.models import BookData +from src.services.sru import SWB +from src.shared.logging import log + +# use all available cores - 2, but at least 1 +THREAD_COUNT = max(os.cpu_count() - 2, 1) +THREAD_MIN_ITEMS = 5 + +# Logger configured centrally in main; use shared `log` + +swb = SWB() +dnb = SWB() +cat = Catalogue() + +RVK_ALLOWED = r"[A-Z0-9.\-\/]" # conservative RVK character set + + +def find_newer_edition( + swb_result: BookData, dnb_result: List[BookData] +) -> Optional[List[BookData]]: + """ + New edition if: + - year > swb.year OR + - edition_number > swb.edition_number + BUT: discard any candidate with year < swb.year (if both years are known). + + Same-work check: + - Compare RVK roots of signatures (after stripping trailing '+N' and '(N)'). + - If both have signatures and RVKs differ -> skip. + + Preferences (in order): + 1) RVK matches SWB + 2) Print over Online-Ressource + 3) Has signature + 4) Newer: (year desc, edition_number desc) + """ + + def strip_copy_and_edition(s: str) -> str: + s = re.sub(r"\(\s*\d+\s*\)", "", s) # remove '(N)' + s = re.sub(r"\s*\+\s*\d+\s*$", "", s) # remove trailing '+N' + return s + + def extract_rvk_root(sig: Optional[str]) -> str: + if not sig: + return "" + t = strip_copy_and_edition(sig.upper()) + t = re.sub(r"\s+", " ", t).strip() + m = re.match(rf"^([A-Z]{{1,3}}\s*{RVK_ALLOWED}*)", t) + if not m: + cleaned = re.sub(rf"[^{RVK_ALLOWED} ]+", "", t).strip() + return cleaned.split(" ")[0] if cleaned else "" + return re.sub(r"\s+", " ", m.group(1)).strip() + + def has_sig(b: BookData) -> bool: + return bool(getattr(b, "signature", None)) + + def is_online(b: BookData) -> bool: + return (getattr(b, "media_type", None) or "").strip() == "Online-Ressource" + + def is_print(b: BookData) -> bool: + return not is_online(b) + + def rvk_matches_swb(b: BookData) -> bool: + if not has_sig(b) or not has_sig(swb_result): + return False + return extract_rvk_root(b.signature) == extract_rvk_root(swb_result.signature) + + def strictly_newer(b: BookData) -> bool: + # Hard guard: if both years are known and candidate is older, discard + if ( + b.year is not None + and swb_result.year is not None + and b.year < swb_result.year + ): + return False + + newer_by_year = ( + b.year is not None + and swb_result.year is not None + and b.year > swb_result.year + ) + newer_by_edition = ( + b.edition_number is not None + and swb_result.edition_number is not None + and b.edition_number > swb_result.edition_number + ) + # Thanks to the guard above, newer_by_edition can't pick something with a smaller year. + return newer_by_year or newer_by_edition + + swb_has_sig = has_sig(swb_result) + swb_rvk = extract_rvk_root(getattr(swb_result, "signature", None)) + + # 1) Filter: same work (by RVK if both have sigs) AND strictly newer + candidates: List[BookData] = [] + for b in dnb_result: + if has_sig(b) and swb_has_sig: + if extract_rvk_root(b.signature) != swb_rvk: + continue # different work + if strictly_newer(b): + candidates.append(b) + + if not candidates: + return None + + # 2) Dedupe by PPN → prefer (rvk-match, is-print, has-signature) + def pref_score(x: BookData) -> tuple[int, int, int]: + return ( + 1 if rvk_matches_swb(x) else 0, + 1 if is_print(x) else 0, + 1 if has_sig(x) else 0, + ) + + by_ppn: dict[Optional[str], BookData] = {} + for b in candidates: + key = getattr(b, "ppn", None) + prev = by_ppn.get(key) + if prev is None or pref_score(b) > pref_score(prev): + by_ppn[key] = b + + deduped = list(by_ppn.values()) + if not deduped: + return None + + # 3) Preserve all qualifying newer editions, but order by preference + def sort_key(b: BookData): + year = b.year if b.year is not None else -1 + ed = b.edition_number if b.edition_number is not None else -1 + return ( + 1 if rvk_matches_swb(b) else 0, + 1 if is_print(b) else 0, + 1 if has_sig(b) else 0, + year, + ed, + ) + + deduped.sort(key=sort_key, reverse=True) + return deduped + + +class NewEditionCheckerThread(QThread): + updateSignal = Signal(int, int) # (processed, total) + updateProgress = Signal(int, int) # (processed, total) + total_entries_signal = Signal(int) + resultsSignal = Signal(list) # list[tuple[BookData, list[BookData]]] + + # NEW: metrics signals + rateSignal = Signal(float) # items per second ("it/s") + etaSignal = Signal(int) # seconds remaining (-1 when unknown) + + def __init__(self, entries: Optional[list["BookData"]] = None, parent=None): + super().__init__(parent) + self.entries: list["BookData"] = entries if entries is not None else [] + self.results: list[tuple["BookData", list["BookData"]]] = [] + + def reset(self): + self.entries = [] + self.results = [] + + # ---------- internal helpers ---------- + + @staticmethod + def _split_evenly(items: list, parts: int) -> list[list]: + """Split items as evenly as possible into `parts` chunks (no empty tails).""" + if parts <= 1 or len(items) <= 1: + return [items] + n = len(items) + base = n // parts + extra = n % parts + chunks = [] + i = 0 + for k in range(parts): + size = base + (1 if k < extra else 0) + if size == 0: + continue + chunks.append(items[i : i + size]) + i += size + return chunks + + @staticmethod + def _clean_title(raw: str) -> str: + title = raw.rstrip(" .:,;!?") + title = re.sub(r"\s*\(.*\)", "", title) + return title.strip() + + @classmethod + def _process_book( + cls, book: "BookData" + ) -> tuple["BookData", list["BookData"]] | None: + """Process one book; returns (original, [found editions]) or None on failure.""" + if not book.title: + return None + response: list["BookData"] = [] + query = [ + f"pica.tit={book.title}", + f"pica.vlg={book.publisher}", + ] + + swb_result = swb.getBooks(["pica.bib=20735", f"pica.ppn={book.ppn}"])[0] + dnb_results = swb.getBooks(query) + new_editions = find_newer_edition(swb_result, dnb_results) + + if new_editions is not None: + for new_edition in new_editions: + new_edition.library_location = cat.get_location(new_edition.ppn) + try: + isbn = ( + str(new_edition.isbn[0]) + if isinstance(new_edition.isbn, list) + else str(new_edition.isbn) + ) + new_edition.link = ( + f"https://www.lehmanns.de/search/quick?mediatype_id=2&q={isbn}" + ) + except (IndexError, TypeError): + isbn = None + new_edition.in_library = cat.in_library(new_edition.ppn) + response = new_editions + + # client = SWB() + # response: list["BookData"] = [] + # # First, search by title only + # results = client.getBooks([f"pica.title={title}", f"pica.vlg={book.publisher}"]) + + # lehmanns = LehmannsClient() + # results = lehmanns.search_by_title(title) + # for result in results: + # if "(eBook)" in result.title: + # result.title = result.title.replace("(eBook)", "").strip() + # swb_results = client.getBooks( + # [ + # f"pica.tit={result.title}", + # f"pica.vlg={result.publisher.split(',')[0]}", + # ] + # ) + # for swb in swb_results: + # if swb.isbn == result.isbn: + # result.ppn = swb.ppn + # result.signature = swb.signature + # response.append(result) + # if (result.edition_number < swb.edition_number) and ( + # swb.year > result.year + # ): + # response.append(result) + if response == []: + return None + # Remove duplicates based on ppn + return (book, response) + + @classmethod + def _worker(cls, items: list["BookData"], q: Queue) -> None: + """Worker for one chunk; pushes ('result', ...), ('progress', 1), and ('done', None).""" + try: + for book in items: + try: + result = cls._process_book(book) + except Exception: + result = None + if result is not None: + q.put(("result", result)) + q.put(("progress", 1)) + finally: + q.put(("done", None)) + + # ---------- thread entry point ---------- + + def run(self): + total = len(self.entries) + self.total_entries_signal.emit(total) + + # start timer for metrics + t0 = monotonic() + + if total == 0: + log.debug("No entries to process.") + # emit metrics (zero work) + self.rateSignal.emit(0.0) + self.etaSignal.emit(0) + self.resultsSignal.emit([]) + return + + # Up to 4 workers; ~20 items per worker + num_workers = min(THREAD_COUNT, max(1, ceil(total / THREAD_MIN_ITEMS))) + chunks = self._split_evenly(self.entries, num_workers) + sizes = [len(ch) for ch in chunks] + + q: Queue = Queue() + processed = 0 + finished_workers = 0 + + with ThreadPoolExecutor(max_workers=len(chunks)) as ex: + futures = [ex.submit(self._worker, ch, q) for ch in chunks] + + log.info( + f"Launched {len(futures)} worker thread(s) for {total} entries: {sizes} entries per thread." + ) + for idx, sz in enumerate(sizes, 1): + log.debug(f"Thread {idx}: {sz} entries") + + # Aggregate progress/results + while finished_workers < len(chunks): + try: + kind, payload = q.get(timeout=0.1) + except Empty: + continue + + if kind == "progress": + processed += int(payload) + self.updateSignal.emit(processed, total) + self.updateProgress.emit(processed, total) + + # ---- NEW: compute & emit metrics ---- + elapsed = max(1e-9, monotonic() - t0) + rate = processed / elapsed # items per second + remaining = max(0, total - processed) + eta_sec = int(round(remaining / rate)) if rate > 0 else -1 + + self.rateSignal.emit(rate) + # clamp negative just in case + self.etaSignal.emit(max(0, eta_sec) if eta_sec >= 0 else -1) + # ------------------------------------- + + elif kind == "result": + self.results.append(payload) + elif kind == "done": + finished_workers += 1 + + # Final metrics on completion + elapsed_total = max(1e-9, monotonic() - t0) + final_rate = total / elapsed_total + self.rateSignal.emit(final_rate) + self.etaSignal.emit(0) + + self.resultsSignal.emit(self.results) diff --git a/src/core/__init__.py b/src/core/__init__.py new file mode 100644 index 0000000..15185c3 --- /dev/null +++ b/src/core/__init__.py @@ -0,0 +1,30 @@ +"""Core domain models and business constants.""" + +from .models import ( + Apparat, + ApparatData, + Book, + BookData, + ELSA, + MailData, + Prof, + SemapDocument, + Subjects, + XMLMailSubmission, +) +from .constants import * +from .semester import Semester + +__all__ = [ + "Apparat", + "ApparatData", + "Book", + "BookData", + "ELSA", + "MailData", + "Prof", + "SemapDocument", + "Subjects", + "XMLMailSubmission", + "Semester", +] diff --git a/src/core/constants.py b/src/core/constants.py new file mode 100644 index 0000000..94f0916 --- /dev/null +++ b/src/core/constants.py @@ -0,0 +1,213 @@ +APP_NRS = [i for i in range(1, 181)] + +PROF_TITLES = [ + "Dr. mult.", + "Dr. paed.", + "Dr. rer. pol.", + "Dr. sc. techn.", + "Drs.", + "Dr. agr.", + "Dr. habil.", + "Dr. oec.", + "Dr. med.", + "Dr. e. h.", + "Dr. oec. publ.", + "Dr. -Ing.", + "Dr. theol.", + "Dr. med. vet.", + "Dr. ing.", + "Dr. rer. nat.", + "Dr. des.", + "Dr. sc. mus.", + "Dr. h. c.", + "Dr. pharm.", + "Dr. med. dent.", + "Dr. phil. nat.", + "Dr. phil.", + "Dr. iur.", + "Dr.", + "Kein Titel", +] + +SEMAP_MEDIA_ACCOUNTS = { + 1: "1008000055", + 2: "1008000188", + 3: "1008000211", + 4: "1008000344", + 5: "1008000477", + 6: "1008000500", + 7: "1008000633", + 8: "1008000766", + 9: "1008000899", + 10: "1008000922", + 11: "1008001044", + 12: "1008001177", + 13: "1008001200", + 14: "1008001333", + 15: "1008001466", + 16: "1008001599", + 17: "1008001622", + 18: "1008001755", + 19: "1008001888", + 20: "1008001911", + 21: "1008002033", + 22: "1008002166", + 23: "1008002299", + 24: "1008002322", + 25: "1008002455", + 26: "1008002588", + 27: "1008002611", + 28: "1008002744", + 29: "1008002877", + 30: "1008002900", + 31: "1008003022", + 32: "1008003155", + 33: "1008003288", + 34: "1008003311", + 35: "1008003444", + 36: "1008003577", + 37: "1008003600", + 38: "1008003733", + 39: "1008003866", + 40: "1008003999", + 41: "1008004011", + 42: "1008004144", + 43: "1008004277", + 44: "1008004300", + 45: "1008004433", + 46: "1008004566", + 47: "1008004699", + 48: "1008004722", + 49: "1008004855", + 50: "1008004988", + 51: "1008005000", + 52: "1008005133", + 53: "1008005266", + 54: "1008005399", + 55: "1008005422", + 56: "1008005555", + 57: "1008005688", + 58: "1008005711", + 59: "1008005844", + 60: "1008005977", + 61: "1008006099", + 62: "1008006122", + 63: "1008006255", + 64: "1008006388", + 65: "1008006411", + 66: "1008006544", + 67: "1008006677", + 68: "1008006700", + 69: "1008006833", + 70: "1008006966", + 71: "1008007088", + 72: "1008007111", + 73: "1008007244", + 74: "1008007377", + 75: "1008007400", + 76: "1008007533", + 77: "1008007666", + 78: "1008007799", + 79: "1008007822", + 80: "1008007955", + 81: "1008008077", + 82: "1008008100", + 83: "1008008233", + 84: "1008008366", + 85: "1008008499", + 86: "1008008522", + 87: "1008008655", + 88: "1008008788", + 89: "1008008811", + 90: "1008008944", + 91: "1008009066", + 92: "1008009199", + 93: "1008009222", + 94: "1008009355", + 95: "1008009488", + 96: "1008009511", + 97: "1008009644", + 98: "1008009777", + 99: "1008009800", + 100: "1008009933", + 101: "1008010022", + 102: "1008010155", + 103: "1008010288", + 104: "1008010311", + 105: "1008010444", + 106: "1008010577", + 107: "1008010600", + 108: "1008010733", + 109: "1008010866", + 110: "1008010999", + 111: "1008011011", + 112: "1008011144", + 113: "1008011277", + 114: "1008011300", + 115: "1008011433", + 116: "1008011566", + 117: "1008011699", + 118: "1008011722", + 119: "1008011855", + 120: "1008011988", + 121: "1008012000", + 122: "1008012133", + 123: "1008012266", + 124: "1008012399", + 125: "1008012422", + 126: "1008012555", + 127: "1008012688", + 128: "1008012711", + 129: "1008012844", + 130: "1008012977", + 131: "1008013099", + 132: "1008013122", + 133: "1008013255", + 134: "1008013388", + 135: "1008013411", + 136: "1008013544", + 137: "1008013677", + 138: "1008013700", + 139: "1008013833", + 140: "1008013966", + 141: "1008014088", + 142: "1008014111", + 143: "1008014244", + 144: "1008014377", + 145: "1008014400", + 146: "1008014533", + 147: "1008014666", + 148: "1008014799", + 149: "1008014822", + 150: "1008014955", + 151: "1008015077", + 152: "1008015100", + 153: "1008015233", + 154: "1008015366", + 155: "1008015499", + 156: "1008015522", + 157: "1008015655", + 158: "1008015788", + 159: "1008015811", + 160: "1008015944", + 161: "1008016066", + 162: "1008016199", + 163: "1008016222", + 164: "1008016355", + 165: "1008016488", + 166: "1008016511", + 167: "1008016644", + 168: "1008016777", + 169: "1008016800", + 170: "1008016933", + 171: "1008017055", + 172: "1008017188", + 173: "1008017211", + 174: "1008017344", + 175: "1008017477", + 176: "1008017500", + 177: "1008017633", + 178: "1008017766", + 179: "1008017899", + 180: "1008017922", +} diff --git a/src/core/models.py b/src/core/models.py new file mode 100644 index 0000000..a7d4688 --- /dev/null +++ b/src/core/models.py @@ -0,0 +1,410 @@ +import json +from dataclasses import dataclass, field +from enum import Enum +from typing import Any, Optional, Union + +import regex + +from src.logic.openai import name_tester, run_shortener, semester_converter +from src.logic.semester import Semester + + +@dataclass +class Prof: + id: Optional[int] = None + _title: Optional[str] = None + firstname: Optional[str] = None + lastname: Optional[str] = None + fullname: Optional[str] = None + mail: Optional[str] = None + telnr: Optional[str] = None + + # add function that sets the data based on a dict + def from_dict(self, data: dict[str, Union[str, int]]): + for key, value in data.items(): + if hasattr(self, key): + setattr(self, key, value) + return self + + @property + def title(self) -> str: + if self._title is None or self._title == "None": + return "" + return self._title + + @title.setter + def title(self, value: str): + self._title = value + + # add function that sets the data from a tuple + def from_tuple(self, data: tuple[Union[str, int], ...]) -> "Prof": + setattr(self, "id", data[0]) + setattr(self, "_title", data[1]) + setattr(self, "firstname", data[2]) + setattr(self, "lastname", data[3]) + setattr(self, "fullname", data[4]) + setattr(self, "mail", data[5]) + setattr(self, "telnr", data[6]) + return self + + def name(self, comma: bool = False) -> Optional[str]: + if self.firstname is None and self.lastname is None: + if "," in self.fullname: + self.firstname = self.fullname.split(",")[1].strip() + self.lastname = self.fullname.split(",")[0].strip() + else: + return self.fullname + + if comma: + return f"{self.lastname}, {self.firstname}" + return f"{self.lastname} {self.firstname}" + + +@dataclass +class BookData: + ppn: str | None = None + title: str | None = None + signature: str | None = None + edition: str | None = None + link: str | None = None + isbn: Union[str, list[str], None] = field(default_factory=list) + author: str | None = None + language: Union[str, list[str], None] = field(default_factory=list) + publisher: str | None = None + place: str | None = None + year: int | None = None + pages: str | None = None + library_location: str | None = None + in_apparat: bool | None = False + adis_idn: str | None = None + old_book: Any | None = None + media_type: str | None = None # + in_library: bool | None = None # whether the book is in the library or not + medianr: int | None = None # Media number in the library system + + def __post_init__(self): + self.library_location = ( + str(self.library_location) if self.library_location else None + ) + if isinstance(self.language, list) and self.language: + self.language = [lang.strip() for lang in self.language if lang.strip()] + self.language = ",".join(self.language) + self.year = regex.sub(r"[^\d]", "", str(self.year)) if self.year else None + self.in_library = True if self.signature else False + + def from_dict(self, data: dict) -> "BookData": + for key, value in data.items(): + setattr(self, key, value) + return self + + def merge(self, other: "BookData") -> "BookData": + for key, value in other.__dict__.items(): + # merge lists, if the attribute is a list, extend it + if isinstance(value, list): + current_value = getattr(self, key) + if current_value is None: + current_value = [] + elif not isinstance(current_value, list): + current_value = [current_value] + # extend the list with the new values, but only if they are not already in the list + for v in value: + if v not in current_value: + current_value.append(v) + setattr(self, key, current_value) + if value is not None and ( + getattr(self, key) is None or getattr(self, key) == "" + ): + setattr(self, key, value) + # in language, drop all entries that are longer than 3 characters + if isinstance(self.language, list): + self.language = [lang for lang in self.language if len(lang) <= 4] + return self + + @property + def to_dict(self) -> str: + """Convert the dataclass to a dictionary.""" + data_dict = { + key: value for key, value in self.__dict__.items() if value is not None + } + # remove old_book from data_dict + if "old_book" in data_dict: + del data_dict["old_book"] + return json.dumps(data_dict, ensure_ascii=False) + + def from_dataclass(self, dataclass: Optional[Any]) -> None: + if dataclass is None: + return + for key, value in dataclass.__dict__.items(): + setattr(self, key, value) + + def get_book_type(self) -> str: + if "Online" in self.pages: + return "eBook" + else: + return "Druckausgabe" + + def from_string(self, data: str) -> "BookData": + ndata = json.loads(data) + + return BookData(**ndata) + + def from_LehmannsSearchResult(self, result: Any) -> "BookData": + self.title = result.title + self.author = "; ".join(result.authors) if result.authors else None + self.edition = str(result.edition) if result.edition else None + self.link = result.url + self.isbn = ( + result.isbn13 + if isinstance(result.isbn13, list) + else [result.isbn13] + if result.isbn13 + else [] + ) + self.pages = str(result.pages) if result.pages else None + self.publisher = result.publisher + self.year = str(result.year) if result.year else None + # self.pages = str(result.pages) if result.pages else None + return self + + @property + def edition_number(self) -> Optional[int]: + if self.edition is None: + return 0 + match = regex.search(r"(\d+)", self.edition) + if match: + return int(match.group(1)) + return 0 + + +@dataclass +class MailData: + subject: Optional[str] = None + body: Optional[str] = None + mailto: Optional[str] = None + prof: Optional[str] = None + + +class Subjects(Enum): + BIOLOGY = (1, "Biologie") + CHEMISTRY = (2, "Chemie") + GERMAN = (3, "Deutsch") + ENGLISH = (4, "Englisch") + PEDAGOGY = (5, "Erziehungswissenschaft") + FRENCH = (6, "Französisch") + GEOGRAPHY = (7, "Geographie") + HISTORY = (8, "Geschichte") + HEALTH_EDUCATION = (9, "Gesundheitspädagogik") + HTW = (10, "Haushalt / Textil") + ART = (11, "Kunst") + MATH_IT = (12, "Mathematik / Informatik") + MEDIAPEDAGOGY = (13, "Medien in der Bildung") + MUSIC = (14, "Musik") + PHILOSOPHY = (15, "Philosophie") + PHYSICS = (16, "Physik") + POLITICS = (17, "Politikwissenschaft") + PRORECTORATE = (18, "Prorektorat Lehre und Studium") + PSYCHOLOGY = (19, "Psychologie") + SOCIOLOGY = (20, "Soziologie") + SPORT = (21, "Sport") + TECHNIC = (22, "Technik") + THEOLOGY = (23, "Theologie") + ECONOMICS = (24, "Wirtschaftslehre") + + @property + def id(self) -> int: + return self.value[0] + + @property + def name(self) -> str: + return self.value[1] + + @classmethod + def get_index(cls, name: str) -> Optional[int]: + for i in cls: + if i.name == name: + return i.id - 1 + return None + + +@dataclass +class Apparat: + id: int | None = None + name: str | None = None + prof_id: int | None = None + subject: str | None = None + appnr: int | None = None + created_semester: str | None = None + extended_at: str | None = None + eternal: bool = False + extend_until: str | None = None + deleted: int | None = None + deleted_date: str | None = None + apparat_id_adis: str | None = None + prof_id_adis: str | None = None + konto: int | None = None + + def from_tuple(self, data: tuple[Any, ...]) -> "Apparat": + self.id = data[0] + self.name = data[1] + self.prof_id = data[2] + self.subject = data[3] + self.appnr = data[4] + self.created_semester = data[5] + self.extended_at = data[6] + self.eternal = data[7] + self.extend_until = data[8] + self.deleted = data[9] + self.deleted_date = data[10] + self.apparat_id_adis = data[11] + self.prof_id_adis = data[12] + self.konto = data[13] + return self + + @property + def get_semester(self) -> Optional[str]: + if self.extend_until is not None: + return self.extend_until + else: + return self.created_semester + + +@dataclass +class ELSA: + id: int | None = None + date: str | None = None + semester: str | None = None + prof_id: int | None = None + + def from_tuple(self, data: tuple[Any, ...]) -> "ELSA": + self.id = data[0] + self.date = data[1] + self.semester = data[2] + self.prof_id = data[3] + return self + + +@dataclass +class ApparatData: + prof: Prof = field(default_factory=Prof) + apparat: Apparat = field(default_factory=Apparat) + + +@dataclass +class XMLMailSubmission: + name: Optional[str] = None + lastname: Optional[str] = None + title: Optional[str] = None + telno: Optional[int] = None + email: Optional[str] = None + app_name: Optional[str] = None + subject: Optional[str] = None + semester: Optional[Semester] = None + books: Optional[list[BookData]] = None + + +@dataclass +class Book: + author: str = None + year: str = None + edition: str = None + title: str = None + location: str = None + publisher: str = None + signature: str = None + internal_notes: str = None + + @property + def has_signature(self) -> bool: + return self.signature is not None and self.signature != "" + + @property + def is_empty(self) -> bool: + return all( + [ + self.author == "", + self.year == "", + self.edition == "", + self.title == "", + self.location == "", + self.publisher == "", + self.signature == "", + self.internal_notes == "", + ] + ) + + def from_dict(self, data: dict[str, Any]): + for key, value in data.items(): + value = value.strip() + if value == "\u2002\u2002\u2002\u2002\u2002": + value = "" + + if key == "Autorenname(n):Nachname, Vorname": + self.author = value + elif key == "Jahr/Auflage": + self.year = value.split("/")[0] if "/" in value else value + self.edition = value.split("/")[1] if "/" in value else "" + elif key == "Titel": + self.title = value + elif key == "Ort und Verlag": + self.location = value.split(",")[0] if "," in value else value + self.publisher = value.split(",")[1] if "," in value else "" + elif key == "Standnummer": + self.signature = value.strip() + elif key == "Interne Vermerke": + self.internal_notes = value + + +@dataclass +class SemapDocument: + subject: str = None + phoneNumber: int = None + mail: str = None + title: str = None + title_suggestions: list[str] = None + semester: Union[str, Semester] = None + books: list[Book] = None + eternal: bool = False + personName: str = None + personTitle: str = None + title_length = 0 + title_max_length = 0 + + def __post_init__(self): + self.title_suggestions = [] + + @property + def nameSetter(self): + data = name_tester(self.personTitle) + name = f"{data['last_name']}, {data['first_name']}" + if data["title"] is not None: + title = data["title"] + self.personTitle = title + self.personName = name + self.title_length = len(self.title) + 3 + len(self.personName.split(",")[0]) + if self.title_length > 40: + name_len = len(self.personName.split(",")[0]) + self.title_max_length = 38 - name_len + suggestions = run_shortener(self.title, self.title_max_length) + for suggestion in suggestions: + self.title_suggestions.append(suggestion["shortened_string"]) + else: + self.title_suggestions = [] + pass + + @property + def renameSemester(self) -> None: + if self.semester: + if ", Dauer" in self.semester: + self.semester = self.semester.split(",")[0] + self.eternal = True + self.semester = Semester().from_string(self.semester) + else: + self.semester = Semester().from_string( + semester_converter(self.semester) + ) + + @property + def signatures(self) -> list[str]: + if self.books is not None: + return [book.signature for book in self.books if book.has_signature] + return [] diff --git a/src/backend/semester.py b/src/core/semester.py similarity index 90% rename from src/backend/semester.py rename to src/core/semester.py index 0f24a9e..08e2b03 100644 --- a/src/backend/semester.py +++ b/src/core/semester.py @@ -15,20 +15,13 @@ Key points """ from __future__ import annotations + import datetime import re -from dataclasses import dataclass -import loguru -import sys -from src import LOG_DIR -log = loguru.logger -log.remove() -log.add(sys.stdout, level="INFO") -log.add(f"{LOG_DIR}/application.log", rotation="1 MB", retention="10 days") + +from src.shared.logging import log - -# @dataclass class Semester: """Represents a German university semester (WiSe or SoSe).""" @@ -123,21 +116,22 @@ class Semester: # ------------------------------------------------------------------ # Comparison helpers # ------------------------------------------------------------------ - def isPastSemester(self, other: "Semester") -> bool: - if self.year < other.year: + def isPastSemester(self, current: "Semester") -> bool: + log.debug(f"Comparing {self} < {current}") + if self.year < current.year: return True - if self.year == other.year: + if self.year == current.year: return ( - self.semester == "WiSe" and other.semester == "SoSe" + self.semester == "WiSe" and current.semester == "SoSe" ) # WiSe before next SoSe return False - def isFutureSemester(self, other: "Semester") -> bool: - if self.year > other.year: + def isFutureSemester(self, current: "Semester") -> bool: + if self.year > current.year: return True - if self.year == other.year: + if self.year == current.year: return ( - self.semester == "SoSe" and other.semester == "WiSe" + self.semester == "SoSe" and current.semester == "WiSe" ) # SoSe after WiSe of same year return False @@ -235,8 +229,20 @@ if __name__ == "__main__": s_start = Semester(6, "SoSe") # SoSe 6 s_end = Semester(25, "WiSe") # WiSe 25/26 chain = Semester.generate_missing(s_start, s_end) - print("generate_missing:", [str(s) for s in chain]) + # print("generate_missing:", [str(s) for s in chain]) # Parsing demo --------------------------------------------------------- - for label in ["SoSe 6", "WiSe 6/7", "wise 23/24", "WiSe 9"]: - print("from_string:", label, "→", Semester.from_string(label)) + examples = [ + "SoSe 6", + "WiSe 6/7", + "WiSe 6", + "SoSe 23", + "WiSe 23/24", + "WiSe 24", + "WiSe 99/00", + "SoSe 00", + "WiSe 100/101", # test large year + ] + for ex in examples: + parsed = Semester.from_string(ex) + print(f"'{ex}' → {parsed} ({parsed.year=}, {parsed.semester=})") diff --git a/src/database/__init__.py b/src/database/__init__.py new file mode 100644 index 0000000..4e811a4 --- /dev/null +++ b/src/database/__init__.py @@ -0,0 +1,5 @@ +"""Database layer for data persistence.""" + +from .connection import Database + +__all__ = ["Database"] diff --git a/src/database/connection.py b/src/database/connection.py new file mode 100644 index 0000000..4111457 --- /dev/null +++ b/src/database/connection.py @@ -0,0 +1,2008 @@ +import datetime +import json +import os +import re +import sqlite3 as sql +import tempfile +from dataclasses import asdict +from pathlib import Path +from string import ascii_lowercase as lower +from string import digits, punctuation +from typing import Any, List, Optional, Tuple, Union + +from src import DATABASE_DIR, settings +from src.database.schemas import ( + CREATE_ELSA_FILES_TABLE, + CREATE_ELSA_MEDIA_TABLE, + CREATE_ELSA_TABLE, + CREATE_TABLE_APPARAT, + CREATE_TABLE_FILES, + CREATE_TABLE_MEDIA, + CREATE_TABLE_MESSAGES, + CREATE_TABLE_NEWEDITIONS, + CREATE_TABLE_PROF, + CREATE_TABLE_SUBJECTS, + CREATE_TABLE_USER, +) +from src.errors import AppPresentError, NoResultError +from src.core.models import ELSA, Apparat, ApparatData, BookData, Prof +from src.core.constants import SEMAP_MEDIA_ACCOUNTS +from src.core.semester import Semester +from src.shared.logging import log +from src.utils.blob import create_blob + +ascii_lowercase = lower + digits + punctuation + + +# get the line that called the function +class Database: + """ + Initialize the database and create the tables if they do not exist. + """ + + def __init__(self, db_path: Union[Path, None] = 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: + if settings.database.path is not None: + self.db_path = Path( + settings.database.path.expanduser(), settings.database.name + ) + else: + self.db_path = None + + # self.db_path = self.db_path.replace("~", str(Path.home())) + else: + self.db_path = db_path + log.debug(f"Database path: {self.db_path}") + self.db_initialized = False + self.startup_check() + + def startup_check(self): + # check existence of all tables. if any is missing, recreate the table + if not self.db_initialized: + self.initializeDatabase() + tables = self.get_db_contents() + tables = [t[1] for t in tables] if tables is not None else [] + required_tables = [ + "semesterapparat", + "messages", + "media", + "files", + "prof", + "user", + "subjects", + "elsa", + "elsa_files", + "elsa_media", + "neweditions", + ] + + for table in required_tables: + if table not in tables: + log.critical(f"Table {table} is missing, recreating...") + self.create_table(table) + + def create_table(self, table_name: str): + match table_name: + case "semesterapparat": + query = CREATE_TABLE_APPARAT + case "messages": + query = CREATE_TABLE_MESSAGES + case "media": + query = CREATE_TABLE_MEDIA + case "files": + query = CREATE_TABLE_FILES + case "prof": + query = CREATE_TABLE_PROF + case "user": + query = CREATE_TABLE_USER + case "subjects": + query = CREATE_TABLE_SUBJECTS + case "elsa": + query = CREATE_ELSA_TABLE + case "elsa_files": + query = CREATE_ELSA_FILES_TABLE + case "elsa_media": + query = CREATE_ELSA_MEDIA_TABLE + case "neweditions": + query = CREATE_TABLE_NEWEDITIONS + case _: + log.error(f"Table {table_name} is not a valid table name") + self.query_db(query) + + def initializeDatabase(self): + if not self.db_initialized: + self.checkDatabaseStatus() + self.db_initialized = True + # run migrations after initial creation to bring schema up-to-date + try: + if self.db_path is not None: + self.run_migrations() + except Exception as e: + log.error(f"Error while running migrations: {e}") + + # --- Migration helpers integrated into Database --- + def _ensure_migrations_table(self, conn: sql.Connection) -> None: + cursor = conn.cursor() + cursor.execute( + """ + CREATE TABLE IF NOT EXISTS schema_migrations ( + id TEXT PRIMARY KEY, + applied_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP + ) + """ + ) + conn.commit() + + def _applied_migrations(self, conn: sql.Connection) -> List[str]: + cursor = conn.cursor() + cursor.execute("SELECT id FROM schema_migrations ORDER BY id") + rows = cursor.fetchall() + return [r[0] for r in rows] + + def _apply_sql_file(self, conn: sql.Connection, path: Path) -> None: + log.info(f"Applying migration {path.name}") + sql_text = path.read_text(encoding="utf-8") + cursor = conn.cursor() + cursor.executescript(sql_text) + cursor.execute( + "INSERT OR REPLACE INTO schema_migrations (id) VALUES (?)", (path.name,) + ) + conn.commit() + + def run_migrations(self) -> None: + """Apply unapplied .sql migrations from src/backend/migrations using this Database's connection.""" + migrations_dir = Path(__file__).parent / "migrations" + if not migrations_dir.exists(): + log.debug("Migrations directory does not exist, skipping migrations") + return + + conn = self.connect() + try: + self._ensure_migrations_table(conn) + applied = set(self._applied_migrations(conn)) + + migration_files = sorted( + [p for p in migrations_dir.iterdir() if p.suffix == ".sql"] + ) + for m in migration_files: + if m.name in applied: + log.debug(f"Skipping already applied migration {m.name}") + continue + self._apply_sql_file(conn, m) + finally: + conn.close() + + # --- end migration helpers --- + + def overwritePath(self, new_db_path: str): + log.debug("got new path, overwriting") + self.db_path = Path(new_db_path) + + def checkDatabaseStatus(self): + path = settings.database.path + if path is None: + path = Path(DATABASE_DIR) + # path = path.replace("~", str(Path.home())) + # path = os.path.abspath(path) + if not os.path.exists(path): + # create path + # log.debug(path) + os.makedirs(path) + if self.get_db_contents() == []: + log.critical("Database does not exist, creating tables") + log.critical(f"Path: {path}") + self.create_tables() + self.insertSubjects() + + def getElsaMediaID(self, work_author: str, signature: str, pages: str): + query = ( + "SELECT id FROM elsa_media WHERE work_author=? AND signature=? AND pages=?" + ) + params = (work_author, signature, pages) + result = self.query_db(query, params, one=True) + if result is None: + return NoResultError( + f"work_author: {work_author}, signature: {signature}, pages: {pages}" + ).__str__() + return result[0] + + def getElsaMediaType(self, id): + query = "SELECT type FROM elsa_media WHERE id=?" + return self.query_db(query, (id,), one=True)[0] + + def get_db_contents(self) -> Union[List[Tuple[Any]], 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 + """ + conn = sql.connect(self.db_path) + # Fast pragmas suitable for a desktop app DB + conn.execute("PRAGMA journal_mode=WAL;") + conn.execute("PRAGMA synchronous=NORMAL;") + conn.execute("PRAGMA temp_store=MEMORY;") + conn.execute("PRAGMA mmap_size=134217728;") # 128MB + return conn + + 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 + """ + # Bootstrapping of tables is handled via migrations. Run migrations instead + # of executing the hard-coded DDL here. Migrations are idempotent and + # contain the CREATE TABLE IF NOT EXISTS statements. + self.run_migrations() + + 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() + log.debug(f"Inserting into DB: {query}") + cursor.execute(query, params) + conn.commit() + self.close_connection(conn) + + def getWebADISAuth(self) -> Tuple[str, str]: + """ + Get the WebADIS authentication data from the database + + Returns: + Tuple[str, str]: The username and password for WebADIS + """ + result = self.query_db( + "SELECT username, password FROM webadis_login WHERE effective_range='SAP'", + one=True, + ) + if result is None: + return ("", "") + return (result[0], result[1]) + + @log.catch + def query_db( + self, + query: str, + args: Tuple[Any] = (), # type:ignore + one: bool = False, # type:ignore + ) -> Union[Tuple[Any, Any], List[Tuple[Any, Any]]]: + """ + 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[Tuple | List[Tuple]]: Returns the result of the query + """ + conn = self.connect() + cursor = conn.cursor() + logs_query = query + + logs_args = args + # if "fileblob" in query: + # # set fileblob arg in logger to "too long" + # logs_query = query + # fileblob_location = query.find("fileblob") + # # remove fileblob from query + # logs_query = query[:fileblob_location] + "fileblob = too long" + + log_message = f"Querying database with query {logs_query}, args: {logs_args}" + # if "INSERT" in query: + # log_message = f"Querying database with query {query}" + if "INTO user" in query: + log_message = f"Querying database with query {query}" + # log.debug(f"DB Query: {log_message}") + log.debug(log_message) + try: + cursor.execute(query, args) + rv = cursor.fetchall() + conn.commit() + self.close_connection(conn) + except sql.OperationalError as e: + log.error(f"Error in query: {e}") + return None + 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. + """ + log.info(f"Adding book {bookdata.signature} to database") + if app_id is None or prof_id is None: + raise ValueError("Apparate ID or Prof ID is None") + conn = self.connect() + cursor = conn.cursor() + t_query = ( + f"SELECT bookdata FROM media WHERE app_id={app_id} AND prof_id={prof_id}" + ) + log.debug(t_query) + # # log.debug(t_query) + result = cursor.execute(t_query).fetchall() + result = [BookData().from_string(i[0]) for i in result] + if bookdata in result: + # log.debug("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, json.dumps(asdict(bookdata), ensure_ascii=False)) + result = cursor.execute(query, params).fetchone() + if result[0] == 1: + # log.debug("Book was deleted, updating bookdata") + query = "UPDATE media SET deleted=0 WHERE app_id=? AND prof_id=? AND bookdata=?" + params = ( + app_id, + prof_id, + json.dumps(asdict(bookdata), ensure_ascii=False), + ) + cursor.execute(query, params) + conn.commit() + return + + query = ( + "INSERT INTO media (bookdata, app_id, prof_id,deleted) VALUES (?, ?, ?,?)" + ) + converted = json.dumps(asdict(bookdata), ensure_ascii=False) + params = (converted, app_id, prof_id, 0) + cursor.execute(query, params) + logMessage = f"Added book with signature {bookdata.signature} to database, data: {converted}" + log.info(logMessage) + 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 = [(BookData().from_string(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: list[BookData] = [BookData().from_string(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] + ) -> Optional[list[tuple["BookData", int, int]]]: + """ + Search a book in the database using regex against signature/title. + + Args: + data: may contain: + - "signature": regex to match against BookData.signature + - "title": regex to match against BookData.title + + Returns: + list of (BookData, app_id, prof_id) tuples, or None if invalid args + """ + + # Determine mode (kept compatible with your original logic) + mode = 0 + if len(data) == 1 and "signature" in data: + mode = 1 + elif len(data) == 1 and "title" in data: + mode = 2 + elif len(data) == 2 and "signature" in data and "title" in data: + mode = 3 + else: + return None + + def _compile(expr: str) -> re.Pattern: + try: + return re.compile(expr, re.IGNORECASE | re.UNICODE) + except re.error: + # If user provided a broken regex, treat it as a literal + return re.compile(re.escape(expr), re.IGNORECASE | re.UNICODE) + + sig_re = _compile(data["signature"]) if mode in (1, 3) else None + title_re = _compile(data["title"]) if mode in (2, 3) else None + + # Fetch candidates once + rows = self.query_db("SELECT * FROM media WHERE deleted=0") + + results: list[tuple["BookData", int, int]] = [] + for row in rows: + bookdata = BookData().from_string( + row[1] + ) # assumes row[1] is the serialized bookdata + app_id = row[2] + prof_id = row[3] + + sig_val = bookdata.signature + title_val = bookdata.title + if mode == 1: + if sig_re.search(sig_val): + results.append((bookdata, app_id, prof_id)) + elif mode == 2: + if title_re.search(title_val): + results.append((bookdata, app_id, prof_id)) + else: # mode == 3 + if sig_re.search(sig_val) and title_re.search(title_val): + results.append((bookdata, app_id, prof_id)) + + return results + + 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=?", + (bookdata.to_dict, 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 BookData().from_string( + 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: int = 0 + ) -> list[dict[str, Union[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 = [] + if qdata is None: + return [] + for result_a in qdata: + data: dict[str, Any] = {"id": int, "bookdata": BookData, "available": int} + data["id"] = result_a[0] + data["bookdata"] = BookData().from_string(result_a[1]) + data["available"] = result_a[2] + ret_result.append(data) + return ret_result + + def getAllBooks(self) -> list[dict[str, Union[int, BookData]]]: + """ + Get all books in the database that are not set as deleted + + Returns + ------- + list[dict[str, Union[int, BookData]]] + A list of dictionaries containing the id and the metadata of the book + """ + # return all books in the database + qdata = self.query_db("SELECT id,bookdata FROM media WHERE deleted=0") + ret_result: list[dict[str, Any]] = [] + if qdata is None: + return [] + for result_a in qdata: + data: dict[str, Any] = {"id": int, "bookdata": BookData} + data["id"] = result_a[0] + data["bookdata"] = BookData().from_string(result_a[1]) + + ret_result.append(data) + return ret_result + + def getApparatNrByBookId(self, book_id): + appNr = self.query_db( + "SELECT appnr FROM semesterapparat WHERE id IN (SELECT app_id FROM media WHERE id=?)", + (book_id,), + one=True, + ) + return appNr[0] if appNr else None + + def getBooksByProfId( + self, prof_id: int, deleted: int = 0 + ) -> list[dict[str, Union[int, BookData]]]: + """ + Get the Books based on the professor id + + Parameters + ---------- + prof_id : int + The ID of the professor + deleted : int, optional + If set to 1, it will include deleted books, by default 0 + + Returns + ------- + list[dict[str, Union[int, BookData]]] + 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 prof_id={prof_id} AND (deleted={deleted if deleted == 0 else '1 OR deleted=0'})" + ) + ret_result = [] + if qdata is None: + return [] + for result_a in qdata: + data: dict[str, Any] = {"id": int, "bookdata": BookData, "available": int} + data["id"] = result_a[0] + data["bookdata"] = BookData().from_string(result_a[1]) + data["available"] = result_a[2] + ret_result.append(data) + return ret_result + + def updateBookdata(self, book_id: int, 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 + """ + query = "UPDATE media SET bookdata= ? WHERE id=?" + book = bookdata.to_dict + self.query_db(query, (book, book_id)) + + def deleteBook(self, book_id: int): + """ + 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 deleteBooks(self, ids: list[int]): + """ + Delete multiple books from the database + + Args: + ids (list[int]): A list of book ids to be deleted + """ + query = f"UPDATE media SET deleted=1 WHERE id IN ({','.join(['?'] * len(ids))})" + self.query_db(query, tuple(ids)) + + # File Interactions + def getBlob(self, filename: str, app_id: Union[str, int]) -> bytes: + """ + 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) + log.debug(blob) + tempdir = settings.database.temp.expanduser() + if not tempdir.exists(): + tempdir.mkdir(parents=True, exist_ok=True) + file = tempfile.NamedTemporaryFile( + delete=False, dir=tempdir, mode="wb", suffix=f".{filetype}" + ) + file.write(blob) + # log.debug("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 getSemesters(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 insertSubjects(self): + # log.debug("Inserting subjects") + subjects = [ + "Biologie", + "Chemie", + "Deutsch", + "Englisch", + "Erziehungswissenschaft", + "Französisch", + "Geographie", + "Geschichte", + "Gesundheitspädagogik", + "Haushalt / Textil", + "Kunst", + "Mathematik / Informatik", + "Medien in der Bildung", + "Musik", + "Philosophie", + "Physik", + "Politikwissenschaft", + "Prorektorat Lehre und Studium", + "Psychologie", + "Soziologie", + "Sport", + "Technik", + "Theologie", + "Wirtschaftslehre", + ] + conn = self.connect() + cursor = conn.cursor() + for subject in subjects: + cursor.execute("INSERT INTO subjects (name) VALUES (?)", (subject,)) + conn.commit() + self.close_connection(conn) + + 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, messages: list[dict[str, Any]], user: str, app_id: Union[str, int] + ): + """add a Message to the database + + Args: + messages (list[dict[str, Any]]): the messages to be added + user (str): the user who added the messages + app_id (Union[str,int]): the id of the apparat + """ + + def __getUserId(user: str): + return self.query_db( + "SELECT id FROM user WHERE username=?", (user,), one=True + )[0] + + user_id = __getUserId(user) + for message in messages: + self.query_db( + "INSERT INTO messages (message, user_id, remind_at,appnr) VALUES (?,?,?,?)", + (message["message"], user_id, message["remind_at"], app_id), + ) + + def getAllMessages(self) -> list[dict[str, str, str, str]]: + """Get all the messages in the database + + 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: int): + return self.query_db( + "SELECT username FROM user WHERE id=?", (user_id,), one=True + )[0] + + messages = self.query_db("SELECT * FROM messages") + ret = [ + { + "message": i[2], + "user": __get_user_name(i[4]), + "appnr": i[5], + "id": i[0], + "remind_at": i[3], + } + for i in messages + ] + return ret + + def getMessages(self, date: str) -> list[dict[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]]: 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: int): + 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: int): + """Delete a message from the database + + Args: + message_id (str): the id of the message + """ + log.debug(f"Deleting message with id {message_id}") + 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 getProfMailById(self, prof_id: Union[str, int]) -> str: + """get the mail of a professor based on the id + + Args: + prof_id (Union[str,int]): the id of the professor + + Returns: + str: the mail of the professor + """ + mail = self.query_db("SELECT mail FROM prof WHERE id=?", (prof_id,), one=True)[ + 0 + ] + return mail if mail is not None else "" + + 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 getSpecificProfData( + self, prof_id: Union[str, int], fields: List[str] + ) -> tuple[Any, ...]: + """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 getProfById(self, prof_id: Union[str, int]) -> Prof: + """Get a professor based on the id + + Args: + prof_id (Union[str,int]): the id of the professor + + Returns: + Prof: a Prof object containing the data of the professor + """ + data = self.query_db("SELECT * FROM prof WHERE id=?", (prof_id,), one=True) + return Prof().from_tuple(data) + + 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 * FROM prof WHERE fullname=?", + (profname.replace(",", ""),), + one=True, + ) + person = Prof() + return person.from_tuple(data) + + def getProf(self, id) -> Prof: + """Get a professor based on the id + + Args: + id ([type]): the id of the professor + + Returns: + Prof: a Prof object containing the data of the professor + """ + data = self.query_db("SELECT * FROM prof WHERE id=?", (id,), one=True) + return Prof().from_tuple(data) + + def getProfs(self) -> list[Prof]: + """Return all the professors in the database + + Returns: + list[tuple]: a list containing all the professors in individual tuples + tuple: (id, titel, fname, lname, fullname, mail, telnr) + """ + profs = self.query_db("SELECT * FROM prof") + return [Prof().from_tuple(prof) for prof in profs] + + # Apparat + def getAllAparats(self, deleted: int = 0) -> list[Apparat]: + """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 + """ + apparats = self.query_db( + "SELECT * FROM semesterapparat WHERE deletion_status=?", (deleted,) + ) + ret: list[Apparat] = [] + for apparat in apparats: + ret.append(Apparat().from_tuple(apparat)) + return ret + + 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.apparat.id = result[0] + apparat.apparat.name = result[1] + apparat.apparat.appnr = result[4] + apparat.apparat.eternal = True if result[7] == 1 else False + apparat.prof = self.getProfData(self.getProfNameById(result[2])) + apparat.prof.fullname = self.getProfNameById(result[2]) + apparat.apparat.prof_id = result[2] + + apparat.apparat.subject = result[3] + apparat.apparat.created_semester = result[5] + apparat.apparat.extend_until = result[8] + apparat.apparat.deleted = result[9] + apparat.apparat.apparat_id_adis = result[11] + apparat.apparat.prof_id_adis = result[12] + apparat.apparat.konto = result[13] + 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] + numbers.sort() + 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. + """ + # today as yyyy-mm-dd + today = datetime.datetime.now().strftime("%Y-%m-%d") + + if dauerapp: + self.query_db( + "UPDATE semesterapparat SET verlängerung_bis=?, dauer=?, verlängert_am=? WHERE appnr=?", + (newDate, dauerapp, today, app_id), + ) + else: + self.query_db( + "UPDATE semesterapparat SET verlängerung_bis=?, verlängert_am=? WHERE appnr=?", + (newDate, today, app_id), + ) + + def getId(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 id FROM semesterapparat WHERE name=?", (apparat_name,), one=True + ) + if data is None: + return None + else: + return data[0] + + 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 + """ + log.debug(apparat) + app = apparat.apparat + prof = apparat.prof + present_prof = self.getProfByName(prof.name()) + prof_id = present_prof.id + log.debug(present_prof) + + app_id = self.getApparatId(app.name) + if app_id: + return AppPresentError(app_id) + if not prof_id: + log.debug("prof id not present, creating prof with data", prof) + prof_id = self.createProf(prof) + log.debug(prof_id) + query = f"INSERT OR IGNORE INTO semesterapparat (appnr, name, erstellsemester, dauer, prof_id, fach,deletion_status,konto) VALUES ('{app.appnr}', '{app.name}', '{app.created_semester}', '{app.eternal}', {prof_id}, '{app.subject}', '{0}', '{SEMAP_MEDIA_ACCOUNTS[app.appnr]}')" + log.debug(query) + self.query_db(query) + return None + + def getApparatsByProf(self, prof_id: Union[str, int]) -> list[Apparat]: + """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 + """ + data = self.query_db( + "SELECT * FROM semesterapparat WHERE prof_id=?", (prof_id,) + ) + ret = [] + for i in data: + log.debug(i) + ret.append(Apparat().from_tuple(i)) + return ret + + 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.getSemesters() + 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 = [] + 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, apparat: Apparat, semester: str): + """Delete an apparat from the database + + Args: + apparat: (Apparat): the apparat to be deleted + semester (str): the semester the apparat should be deleted from + """ + apparat_nr = apparat.appnr + app_id = self.getId(apparat.name) + self.query_db( + "UPDATE semesterapparat SET deletion_status=1, deleted_date=? WHERE appnr=? AND name=?", + (semester, apparat_nr, apparat.name), + ) + # delete all books associated with the app_id + # print(apparat_nr, app_id) + self.query_db("UPDATE media SET deleted=1 WHERE app_id=?", (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 + """ + result = self.query_db( + "SELECT name FROM semesterapparat WHERE appnr=? AND prof_id=?", + (app_id, prof_id), + one=True, + ) + if result: + return result[0] + return None + + def updateApparat(self, apparat_data: ApparatData): + """Update an apparat in the database + + Args: + apparat_data (ApparatData): the new metadata of the apparat + """ + query = "UPDATE semesterapparat SET name = ?, fach = ?, dauer = ?, prof_id = ?, prof_id_adis = ?, apparat_id_adis = ? WHERE appnr = ?" + params = ( + apparat_data.apparat.name, + apparat_data.apparat.subject, + apparat_data.apparat.eternal, + self.getProfData(apparat_data.prof.fullname).id, + apparat_data.apparat.prof_id_adis, + apparat_data.apparat.apparat_id_adis, + apparat_data.apparat.appnr, + ) + log.debug(f"Updating apparat with query {query} and params {params}") + self.query_db(query, params) + + def checkApparatExists(self, app_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=?", + (app_name,), + one=True, + ) + else False + ) + + def checkApparatExistsByNr(self, app_nr: Union[str, int]) -> bool: + """a check to see if the apparat is already present in the database, based on the nr. This query will exclude deleted apparats + + Args: + app_nr (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 id FROM semesterapparat WHERE appnr=? and deletion_status=0", + (app_nr,), + 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 + """ + log.debug(f"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) + log.debug(f"Query result: {result}") + return result + + if "deletable" in kwargs.keys(): + query = f"""SELECT * FROM semesterapparat + WHERE deletion_status=0 AND dauer=0 AND + ( + (erstellsemester!='{kwargs["deletesemester"]}' AND verlängerung_bis IS NULL) OR + (erstellsemester!='{kwargs["deletesemester"]}' AND verlängerung_bis!='{kwargs["deletesemester"]}' AND verlängerung_bis!='{Semester().next}') + )""" + 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 {}: + # log.debug(key, value) + query += f"{key}='{value}' AND " + # log.debug(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" + ) + # log.debug("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 + log.info(f"Query before: {query}") + query = query.strip() + query = query[:-4] + log.info(f"Query after: {query}") + # check if query ends with lowercase letter or a '. if not, remove last symbol and try again + while query[-1] not in ascii_lowercase and query[-1] != "'": + query = query[:-1] + query = query.strip() + + # log.debug(query) + res = __query(query) + # log.debug(res) + return res + + # 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 + """ + try: + salt = self.query_db( + "SELECT salt FROM user WHERE username=?", (user,), one=True + )[0] + if salt is None: + return False + except TypeError: + 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: str) -> str: + """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 + """ + roles = self.query_db("SELECT role FROM user") + return [i[0] for i in roles] + + 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), + ) + # check if user was created + return ( + self.query_db( + "SELECT username FROM user WHERE username=?", (user,), one=True + ) + is not None + ) + + 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") + + def restoreApparat(self, app_id: Union[str, int], app_name: str): + """restore an apparat from the database + + Args: + app_id (Union[str, int]): the id of the apparat + """ + return self.query_db( + "UPDATE semesterapparat SET deletion_status=0, deleted_date=NULL WHERE appnr=? and name=?", + (app_id, app_name), + ) + + # ELSA + + def createElsaApparat(self, date, prof_id, semester) -> int: + """create a new apparat in the database for the ELSA system + + Args: + date (str): the name of the apparat + prof_id (int): the id of the professor + semester (str): the semester the apparat is created in + + Returns: + int: the id of the apparat + """ + self.query_db( + "INSERT OR IGNORE INTO elsa (date, prof_id, semester) VALUES (?,?,?)", + (date, prof_id, semester), + ) + # get the id of the apparat + apparat_id = self.query_db( + "SELECT id FROM elsa WHERE date=? AND prof_id=? AND semester=?", + (date, prof_id, semester), + one=True, + )[0] + return apparat_id + + def updateElsaApparat(self, date, prof_id, semester, elsa_id): + """update an ELSA apparat in the database + + Args: + date (str): the name of the apparat + prof_id (int): the id of the professor + semester (str): the semester the apparat is created in + elsa_id (int): the id of the ELSA apparat + """ + self.query_db( + "UPDATE elsa SET date=?, prof_id=?, semester=? WHERE id=?", + (date, prof_id, semester, elsa_id), + ) + + def addElsaMedia(self, data: dict, elsa_id: int): + """add a media to the ELSA system + + Args: + data (dict): a dictionary containing the data of the media, + elsa_id (int): the id of the ELSA apparat + """ + headers = [] + entries = [] + for key, value in data.items(): + headers.append(key) + entries.append(value) + headers.append("elsa_id") + entries.append(elsa_id) + query = f"INSERT INTO elsa_media ({', '.join(headers)}) VALUES ({', '.join(['?' for i in range(len(headers))])})" + self.query_db(query, entries) + + def getElsaMedia(self, elsa_id: int): + """get all the media of an ELSA apparat + + Args: + elsa_id (int): the id of the ELSA apparat + + Returns: + list[tuple]: a list of tuples containing the media + """ + media = self.query_db("SELECT * FROM elsa_media WHERE elsa_id=?", (elsa_id,)) + # convert the media to a list of dictionaries + ret = [] + table_fields = self.query_db("PRAGMA table_info(elsa_media)") + for m in media: + tmp = {} + for i in range(len(m)): + tmp[table_fields[i][1]] = m[i] + ret.append(tmp) + return ret + + def insertElsaFile(self, file: list[dict], elsa_id: int): + """Instert a list of files into the ELSA system + + Args: + file (list[dict]): a list containing all the files to be inserted + Structured: [{"name": "filename", "path": "path", "type": "filetype"}] + elsa_id (int): the id of the ELSA apparat + """ + 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 elsa_files (filename, fileblob, elsa_id, filetyp) VALUES (?, ?, ?, ?)" + self.query_db(query, (filename, blob, elsa_id, filetyp)) + + def recreateElsaFile(self, filename: str, filetype: str) -> str: + """Recreate a file from the ELSA system + + Args: + filename (str): the name of the file + elsa_id (int): the id of the ELSA apparat + filetype (str): the extension of the file to be created + + Returns: + str: The filename of the recreated file + """ + blob = self.query_db( + "SELECT fileblob FROM elsa_files WHERE filename=?", (filename,), one=True + )[0] + # log.debug(blob) + tempdir = settings.database.temp.expanduser() + if not tempdir.exists(): + tempdir.mkdir(parents=True, exist_ok=True) + + file = tempfile.NamedTemporaryFile( + delete=False, dir=tempdir, mode="wb", suffix=f".{filetype}" + ) + file.write(blob) + # log.debug("file created") + return file.name + + def getElsaApparats(self) -> ELSA: + """Get all the ELSA apparats in the database + + Returns: + list[tuple]: a list of tuples containing the ELSA apparats + """ + return self.query_db( + "SELECT * FROM elsa ORDER BY substr(date, 7, 4) || '-' || substr(date, 4, 2) || '-' || substr(date, 1, 2)" + ) + + def getElsaId(self, prof_id: int, semester: str, date: str) -> int: + """get the id of an ELSA apparat based on the professor, semester and date + + Args: + prof_id (int): the id of the professor + semester (str): the semester + date (str): the date of the apparat + + Returns: + int: the id of the ELSA apparat + """ + + data = self.query_db( + "SELECT id FROM elsa WHERE prof_id=? AND semester=? AND date=?", + (prof_id, semester, date), + one=True, + ) + if data is None: + return None + return data[0] + + def getElsaFiles(self, elsa_id: int): + """get all the files of an ELSA apparat + + Args: + elsa_id (int): the id of the ELSA apparat + + Returns: + list[tuple]: a list of tuples containing the files + """ + return self.query_db( + "SELECT filename, filetyp FROM elsa_files WHERE elsa_id=?", (elsa_id,) + ) + + ### + + def createProf(self, profdata: Prof): + log.debug(profdata) + conn = self.connect() + cursor = conn.cursor() + fname = profdata.firstname + lname = profdata.lastname + fullname = f"{lname} {fname}" + mail = profdata.mail + telnr = profdata.telnr + title = profdata.title + + query = "INSERT INTO prof (fname, lname, fullname, mail, telnr, titel) VALUES (?,?,?,?,?,?)" + log.debug(query) + cursor.execute(query, (fname, lname, fullname, mail, telnr, title)) + + conn.commit() + conn.close() + return self.getProfId(profdata) + + def getElsaProfId(self, profname): + query = f"SELECT id FROM elsa_prof WHERE fullname = '{profname}'" + data = self.query_db(query) + if data: + return data[0][0] + else: + return None + + def getElsaProfs(self) -> list[str]: + query = "SELECT fullname FROM elsa_prof" + data = self.query_db(query) + if data: + return [i[0] for i in data] + else: + return [] + + def getProfId(self, profdata: dict[str, Any] | Prof): + """Get the prof ID based on the profdata + + Args: + profdata (dict | Prof): either a dictionary containing the prof data or a Prof object + + Returns: + int | None: The id of the prof or None if not found + """ + conn = self.connect() + cursor = conn.cursor() + if isinstance(profdata, dict): + name = profdata["profname"] + if "," in name: + fname = name.split(", ")[1].strip() + lname = name.split(", ")[0].strip() + fullname = f"{lname} {fname}" + else: + fullname = profdata["profname"] + else: + fullname = profdata.name() + query = "SELECT id FROM prof WHERE fullname = ?" + log.debug(query) + + cursor.execute(query, (fullname,)) + result = cursor.fetchone() + if result: + return result[0] + else: + return None + + def getProfByName(self, fullname): + """Get all Data of the prof based on fullname + + Args: + fullname (str): The full name of the prof + """ + conn = self.connect() + cursor = conn.cursor() + query = "SELECT * FROM prof WHERE fullname = ?" + log.debug(query) + + result = cursor.execute(query, (fullname,)).fetchone() + if result: + return Prof().from_tuple(result) + else: + return Prof() + + def getProfIDByApparat(self, apprarat_id: int) -> Optional[int]: + """Get the prof id based on the semesterapparat id from the database + + Args: + apprarat_id (int): Number of the apparat + + Returns: + + int | None: The id of the prof or None if not found + """ + query = "SELECT prof_id from semesterapparat WHERE appnr = ? and deletion_status = 0" + data = self.query_db(query, (apprarat_id,)) + if data: + log.info("Prof ID: " + str(data[0][0])) + return data[0][0] + else: + return None + + def copyBookToApparat(self, book_id: int, apparat: int): + # get book data + new_apparat_id = apparat + new_prof_id = self.getProfIDByApparat(new_apparat_id) + query = ( + "INSERT INTO media (bookdata, app_id, prof_id, deleted, available, reservation) " + "SELECT bookdata, ?, ?, 0, available, reservation FROM media WHERE id = ?" + ) + connection = self.connect() + cursor = connection.cursor() + cursor.execute(query, (new_apparat_id, new_prof_id, book_id)) + connection.commit() + connection.close() + + def moveBookToApparat(self, book_id: int, appratat: int): + """Move the book to the new apparat + + Args: + book_id (int): the ID of the book + appratat (int): the ID of the new apparat + """ + # get book data + query = "UPDATE media SET app_id = ? WHERE id = ?" + connection = self.connect() + cursor = connection.cursor() + cursor.execute(query, (appratat, book_id)) + connection.commit() + connection.close() + + def getApparatNameByAppNr(self, appnr: int): + query = ( + "SELECT name FROM semesterapparat WHERE appnr = ? and deletion_status = 0" + ) + data = self.query_db(query, (appnr,)) + if data: + return data[0][0] + else: + return None + + def fetch_one(self, query: str, args: tuple[Any, ...] = ()) -> tuple[Any, ...]: + connection = self.connect() + cursor = connection.cursor() + cursor.execute(query, args) + result = cursor.fetchone() + connection.close() + return result + + def getBookIdByPPN(self, ppn: str) -> int: + query = "SELECT id FROM media WHERE bookdata LIKE ?" + data = self.query_db(query, (f"%{ppn}%",)) + if data: + return data[0][0] + else: + return None + + def getNewEditionsByApparat(self, apparat_id: int) -> list[BookData]: + """Get all new editions for a specific apparat + + Args: + apparat_id (int): the id of the apparat + + Returns: + list[tuple]: A list of tuples containing the new editions data + """ + query = "SELECT * FROM neweditions WHERE for_apparat=? AND ordered=0" + results = self.query_db(query, (apparat_id,)) + res = [] + for result in results: + # keep only new edition payload; old edition can be reconstructed if needed + res.append(BookData().from_string(result[1])) + return res + + def setOrdered(self, newBook_id: int): + query = "UPDATE neweditions SET ordered=1 WHERE id=?" + self.query_db(query, (newBook_id,)) + + def getBooksWithNewEditions(self, app_id) -> List[BookData]: + # select all bookdata from media, based on the old_edition_id in neweditions where for_apparat = app_id; also get the new_edition bookdata + + query = "SELECT m.bookdata, new_bookdata FROM media m JOIN neweditions n ON m.id = n.old_edition_id WHERE n.for_apparat = ?" + results = self.query_db(query, (app_id,)) + # store results in tuple old,new + res = [] + for result in results: + oldedition = BookData().from_string(result[0]) + newedition = BookData().from_string(result[1]) + res.append((oldedition, newedition)) + return res + + def getNewEditionId(self, newBook: BookData): + query = "SELECT id FROM neweditions WHERE new_bookdata LIKE ?" + args = ( + newBook.isbn[0] if newBook.isbn and len(newBook.isbn) > 0 else newBook.ppn + ) + params = (f"%{args}%",) + data = self.query_db(query, params, one=True) + if data: + return data[0] + else: + return None + + def insertNewEdition(self, newBook: BookData, oldBookId: int, for_apparat: int): + # check if new edition already in table, check based on newBook.ppn + check_query = "SELECT id FROM neweditions WHERE new_bookdata LIKE ?" + check_params = (f"%{newBook.ppn}%",) + data = self.query_db(check_query, check_params, one=True) + if data: + log.info("New edition already in table, skipping insert") + return + + query = "INSERT INTO neweditions (new_bookdata, old_edition_id, for_apparat) VALUES (?,?,?)" + params = (newBook.to_dict, oldBookId, for_apparat) + + self.query_db(query, params) diff --git a/src/database/migrations/V001__create_base_tables.sql b/src/database/migrations/V001__create_base_tables.sql new file mode 100644 index 0000000..4848add --- /dev/null +++ b/src/database/migrations/V001__create_base_tables.sql @@ -0,0 +1,132 @@ +BEGIN TRANSACTION; + +CREATE TABLE IF NOT EXISTS 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, + FOREIGN KEY (prof_id) REFERENCES prof (id) + ); + +CREATE TABLE IF NOT EXISTS media ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + bookdata TEXT, + 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 IF NOT EXISTS 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 IF NOT EXISTS 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 IF NOT EXISTS 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 IF NOT EXISTS 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 IF NOT EXISTS subjects ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name TEXT NOT NULL UNIQUE +); + +CREATE TABLE IF NOT EXISTS elsa ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + date TEXT NOT NULL, + semester TEXT NOT NULL, + prof_id INTEGER NOT NULL + ); + +CREATE TABLE IF NOT EXISTS elsa_files ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + filename TEXT NOT NULL, + fileblob BLOB NOT NULL, + elsa_id INTEGER NOT NULL, + filetyp TEXT NOT NULL, + FOREIGN KEY (elsa_id) REFERENCES elsa (id) + ); + +CREATE TABLE IF NOT EXISTS elsa_media ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + work_author TEXT, + section_author TEXT, + year TEXT, + edition TEXT, + work_title TEXT, + chapter_title TEXT, + location TEXT, + publisher TEXT, + signature TEXT, + issue TEXT, + pages TEXT, + isbn TEXT, + type TEXT, + elsa_id INTEGER NOT NULL, + FOREIGN KEY (elsa_id) REFERENCES elsa (id) + ); + +CREATE TABLE IF NOT EXISTS neweditions ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + new_bookdata TEXT, + old_edition_id INTEGER, + for_apparat INTEGER, + ordered BOOLEAN DEFAULT (0), + FOREIGN KEY (old_edition_id) REFERENCES media (id), + FOREIGN KEY (for_apparat) REFERENCES semesterapparat (id) +); + +-- Helpful indices to speed up frequent lookups and joins +CREATE INDEX IF NOT EXISTS idx_media_app_prof ON media(app_id, prof_id); +CREATE INDEX IF NOT EXISTS idx_media_deleted ON media(deleted); +CREATE INDEX IF NOT EXISTS idx_media_available ON media(available); +CREATE INDEX IF NOT EXISTS idx_messages_remind_at ON messages(remind_at); +CREATE INDEX IF NOT EXISTS idx_semesterapparat_prof ON semesterapparat(prof_id); +CREATE INDEX IF NOT EXISTS idx_semesterapparat_appnr ON semesterapparat(appnr); + +COMMIT; diff --git a/src/database/migrations/V002__create_table_webadis_login.sql b/src/database/migrations/V002__create_table_webadis_login.sql new file mode 100644 index 0000000..5e1b3a8 --- /dev/null +++ b/src/database/migrations/V002__create_table_webadis_login.sql @@ -0,0 +1,10 @@ +BEGIN TRANSACTION; + +CREATE TABLE IF NOT EXISTS webadis_login ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + username TEXT NOT NULL, + password TEXT NOT NULL + ); + +COMMIT; + diff --git a/src/database/migrations/V003_update_webadis_add_user_area.sql b/src/database/migrations/V003_update_webadis_add_user_area.sql new file mode 100644 index 0000000..1b5567b --- /dev/null +++ b/src/database/migrations/V003_update_webadis_add_user_area.sql @@ -0,0 +1,6 @@ +BEGIN TRANSACTION; + +ALTER TABLE webadis_login +ADD COLUMN effective_range TEXT; + +COMMIT; \ No newline at end of file diff --git a/src/database/schemas.py b/src/database/schemas.py new file mode 100644 index 0000000..991cb59 --- /dev/null +++ b/src/database/schemas.py @@ -0,0 +1,112 @@ +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, + FOREIGN KEY (prof_id) REFERENCES prof (id) + )""" +CREATE_TABLE_MEDIA = """CREATE TABLE media ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + bookdata TEXT, + 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_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 +)""" + +CREATE_ELSA_TABLE = """CREATE TABLE elsa ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + date TEXT NOT NULL, + semester TEXT NOT NULL, + prof_id INTEGER NOT NULL + )""" +CREATE_ELSA_FILES_TABLE = """CREATE TABLE elsa_files ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + filename TEXT NOT NULL, + fileblob BLOB NOT NULL, + elsa_id INTEGER NOT NULL, + filetyp TEXT NOT NULL, + FOREIGN KEY (elsa_id) REFERENCES elsa (id) + )""" +CREATE_ELSA_MEDIA_TABLE = """CREATE TABLE elsa_media ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + work_author TEXT, + section_author TEXT, + year TEXT, + edition TEXT, + work_title TEXT, + chapter_title TEXT, + location TEXT, + publisher TEXT, + signature TEXT, + issue TEXT, + pages TEXT, + isbn TEXT, + type TEXT, + elsa_id INTEGER NOT NULL, + FOREIGN KEY (elsa_id) REFERENCES elsa (id) + )""" +CREATE_TABLE_NEWEDITIONS = """CREATE TABLE neweditions ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + new_bookdata TEXT, + old_edition_id INTEGER, + for_apparat INTEGER, + ordered BOOLEAN DEFAULT (0), + FOREIGN KEY (old_edition_id) REFERENCES media (id), + FOREIGN KEY (for_apparat) REFERENCES semesterapparat (id) +)""" diff --git a/src/documents/__init__.py b/src/documents/__init__.py new file mode 100644 index 0000000..139597f --- /dev/null +++ b/src/documents/__init__.py @@ -0,0 +1,2 @@ + + diff --git a/src/documents/generators.py b/src/documents/generators.py new file mode 100644 index 0000000..bd163cd --- /dev/null +++ b/src/documents/generators.py @@ -0,0 +1,371 @@ +import os +from datetime import datetime +from os.path import basename + +from docx import Document +from docx.enum.text import WD_PARAGRAPH_ALIGNMENT +from docx.oxml import OxmlElement +from docx.oxml.ns import qn +from docx.shared import Cm, Pt, RGBColor + +from src import settings +from src.shared.logging import log + +logger = log + +font = "Cascadia Mono" + + +def print_document(file: str) -> None: + # send document to printer as attachment of email + import smtplib + from email.mime.application import MIMEApplication + from email.mime.multipart import MIMEMultipart + from email.mime.text import MIMEText + + smtp = settings.mail.smtp_server + port = settings.mail.port + sender_email = settings.mail.sender + password = settings.mail.password + receiver = settings.mail.printer_mail + message = MIMEMultipart() + message["From"] = sender_email + message["To"] = receiver + message["cc"] = settings.mail.sender + message["Subject"] = "." + mail_body = "." + message.attach(MIMEText(mail_body, "html")) + with open(file, "rb") as fil: + part = MIMEApplication(fil.read(), Name=basename(file)) + # After the file is closed + part["Content-Disposition"] = 'attachment; filename="%s"' % basename(file) + message.attach(part) + mail = message.as_string() + with smtplib.SMTP_SSL(smtp, port) as server: + server.connect(smtp, port) + server.login(settings.mail.user_name, password) + server.sendmail(sender_email, receiver, mail) + server.quit() + log.success("Mail sent") + + +class SemesterError(Exception): + """Custom exception for semester-related errors.""" + + def __init__(self, message: str): + super().__init__(message) + log.error(message) + + def __str__(self): + return f"SemesterError: {self.args[0]}" + + +class SemesterDocument: + def __init__( + self, + apparats: list[tuple[int, str]], + semester: str, + filename: str, + full: bool = False, + ): + assert isinstance(apparats, list), SemesterError( + "Apparats must be a list of tuples" + ) + assert all(isinstance(apparat, tuple) for apparat in apparats), SemesterError( + "Apparats must be a list of tuples" + ) + assert all(isinstance(apparat[0], int) for apparat in apparats), SemesterError( + "Apparat numbers must be integers" + ) + assert all(isinstance(apparat[1], str) for apparat in apparats), SemesterError( + "Apparat names must be strings" + ) + assert isinstance(semester, str), SemesterError("Semester must be a string") + assert "." not in filename and isinstance(filename, str), SemesterError( + "Filename must be a string and not contain an extension" + ) + self.doc = Document() + self.apparats = apparats + self.semester = semester + self.table_font_normal = font + self.table_font_bold = font + self.header_font = font + self.header_font_size = Pt(26) + self.sub_header_font_size = Pt(18) + self.table_font_size = Pt(10) + self.color_red = RGBColor(255, 0, 0) + self.color_blue = RGBColor(0, 0, 255) + self.filename = filename + if full: + log.info("Full document generation") + self.cleanup + log.info("Cleanup done") + self.make_document() + log.info("Document created") + self.create_pdf() + log.info("PDF created") + print_document(self.filename + ".pdf") + log.info("Document printed") + + def set_table_border(self, table): + """ + Adds a full border to the table. + + :param table: Table object to which the border will be applied. + """ + tbl = table._element + tbl_pr = tbl.xpath("w:tblPr")[0] + tbl_borders = OxmlElement("w:tblBorders") + + # Define border styles + for border_name in ["top", "left", "bottom", "right", "insideH", "insideV"]: + border = OxmlElement(f"w:{border_name}") + border.set(qn("w:val"), "single") + border.set(qn("w:sz"), "4") # Thickness of the border + border.set(qn("w:space"), "0") + border.set(qn("w:color"), "000000") # Black color + tbl_borders.append(border) + + tbl_pr.append(tbl_borders) + + def create_sorted_table(self) -> None: + # Sort the apparats list by the string in the tuple (index 1) + self.apparats.sort(key=lambda x: x[1]) + # Create a table with rows equal to the length of the apparats list + table = self.doc.add_table(rows=len(self.apparats), cols=2) + table.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER + + # Set column widths by directly modifying the cell properties + widths = [Cm(1.19), Cm(10)] + for col_idx, width in enumerate(widths): + for cell in table.columns[col_idx].cells: + cell_width_element = cell._element.xpath(".//w:tcPr")[0] + tcW = OxmlElement("w:tcW") + tcW.set(qn("w:w"), str(int(width.cm * 567))) # Convert cm to twips + tcW.set(qn("w:type"), "dxa") + cell_width_element.append(tcW) + + # Adjust row heights + for row in table.rows: + trPr = row._tr.get_or_add_trPr() # Get or add the{signature}
-
tag found")
+ return return_data
+ else:
+ item_location = location.find(
+ "div", class_="col-xs-12 col-md-7 col-lg-8 rds-dl-panel"
+ ).text.strip()
+ log.debug(f"Item location: {item_location}")
+ if self.use_any:
+ pre_tag = soup.find_all("pre")
+ if pre_tag:
+ for tag in pre_tag:
+ data = tag.text.strip()
+ return_data.append(data)
+ return return_data
+ else:
+ log.error("No tag found")
+ raise ValueError("No tag found")
+ elif f"Semesterapparat-{self.apparat}" in item_location:
+ pre_tag = soup.find_all("pre")
+ return_data = []
+ if pre_tag:
+ for tag in pre_tag:
+ data = tag.text.strip()
+ return_data.append(data)
+ return return_data
+ else:
+ log.error("No tag found")
+ return return_data
+ else:
+ log.error(
+ f"Signature {self.signature} not found in {item_location}"
+ )
+ # return_data = []
+
+ return return_data
+
+ def get_data_elsa(self) -> Optional[list[str]]:
+ links = self.get_book_links(self.ppn)
+ for link in links:
+ result = self.search(link)
+ # in result search for class col-xs-12 rds-dl RDS_LOCATION
+ # if found, return text of href
+ soup = BeautifulSoup(result, "html.parser")
+ locations = soup.find_all("div", class_="col-xs-12 rds-dl RDS_LOCATION")
+ if locations:
+ for _ in locations:
+ pre_tag = soup.find_all("pre")
+ return_data = []
+ if pre_tag:
+ for tag in pre_tag:
+ data = tag.text.strip()
+ return_data.append(data)
+ return return_data
+ else:
+ log.error("No tag found")
+ return return_data
+
+
+class BibTextTransformer:
+ """Transforms data from the web into a BibText format.
+ Valid Modes are ARRAY, COinS, BibTeX, RIS, RDS
+ Raises:
+ ValueError: Raised if mode is not in valid_modes
+ """
+
+ valid_modes = [
+ TransformerType.ARRAY,
+ TransformerType.COinS,
+ TransformerType.BibTeX,
+ TransformerType.RIS,
+ TransformerType.RDS,
+ ]
+
+ def __init__(self, mode: TransformerType = TransformerType.ARRAY) -> None:
+ self.mode = mode.value
+ self.field = None
+ self.signature = None
+ if mode not in self.valid_modes:
+ log.error(f"Mode {mode} not valid")
+ raise ValueError(f"Mode {mode} not valid")
+ self.data = None
+ # self.bookdata = BookData(**self.data)
+
+ def use_signature(self, signature: str) -> "BibTextTransformer":
+ """use the exact signature to search for the book"""
+ self.signature = signature
+ return self
+
+ def get_data(self, data: Optional[list[str]] = None) -> "BibTextTransformer":
+ RIS_IDENT = "TY -"
+ ARRAY_IDENT = "[kid]"
+ COinS_IDENT = "ctx_ver"
+ BIBTEX_IDENT = "@book"
+ RDS_IDENT = "RDS ---------------------------------- "
+
+ if data is None:
+ self.data = None
+ return self
+
+ if self.mode == "RIS":
+ for line in data:
+ if RIS_IDENT in line:
+ self.data = line
+ elif self.mode == "ARRAY":
+ for line in data:
+ if ARRAY_IDENT in line:
+ self.data = line
+ elif self.mode == "COinS":
+ for line in data:
+ if COinS_IDENT in line:
+ self.data = line
+ elif self.mode == "BibTeX":
+ for line in data:
+ if BIBTEX_IDENT in line:
+ self.data = line
+ elif self.mode == "RDS":
+ for line in data:
+ if RDS_IDENT in line:
+ self.data = line
+ return self
+
+ def return_data(
+ self, option: Any = None
+ ) -> Union[
+ Optional[BookData],
+ Optional[RDS_GENERIC_DATA],
+ Optional[RDS_AVAIL_DATA],
+ None,
+ dict[str, Union[RDS_AVAIL_DATA, RDS_GENERIC_DATA]],
+ ]:
+ """Return Data to caller.
+
+ Args:
+ option (string, optional): Option for RDS as there are two filetypes. Use rds_availability or rds_data. Anything else gives a dict of both responses. Defaults to None.
+
+ Returns:
+ BookData: a dataclass containing data about the book
+ """
+ if self.data is None:
+ return None
+ match self.mode:
+ case "ARRAY":
+ return ARRAYData(self.signature).transform(self.data)
+ case "COinS":
+ return COinSData().transform(self.data)
+ case "BibTeX":
+ return BibTeXData().transform(self.data)
+ case "RIS":
+ return RISData().transform(self.data)
+ case "RDS":
+ return RDSData().transform(self.data).return_data(option)
+ case _:
+ return None
+
+ # if self.mode == "ARRAY":
+ # return ARRAYData().transform(self.data)
+ # elif self.mode == "COinS":
+ # return COinSData().transform(self.data)
+ # elif self.mode == "BibTeX":
+ # return BibTeXData().transform(self.data)
+ # elif self.mode == "RIS":
+ # return RISData().transform(self.data)
+ # elif self.mode == "RDS":
+ # return RDSData().transform(self.data).return_data(option)
+
+
+def cover(isbn):
+ test_url = f"https://www.buchhandel.de/cover/{isbn}/{isbn}-cover-m.jpg"
+ # log.debug(test_url)
+ data = requests.get(test_url, stream=True)
+ return data.content
+
+
+def get_content(soup, css_class):
+ return soup.find("div", class_=css_class).text.strip()
+
+
+if __name__ == "__main__":
+ # log.debug("main")
+ link = "CU 8500 K64"
+ data = WebRequest(71).get_ppn(link).get_data()
+ bib = BibTextTransformer("ARRAY").get_data().return_data()
+ log.debug(bib)
diff --git a/src/services/zotero.py b/src/services/zotero.py
new file mode 100644
index 0000000..6ca2588
--- /dev/null
+++ b/src/services/zotero.py
@@ -0,0 +1,340 @@
+from dataclasses import dataclass
+from typing import Optional
+
+from pyzotero import zotero
+
+from src import settings
+from src.services.webrequest import BibTextTransformer, WebRequest
+from src.shared.logging import log
+
+
+@dataclass
+class Creator:
+ firstName: str = None
+ lastName: str = None
+ creatorType: str = "author"
+
+ def from_dict(self, data: dict) -> None:
+ for key, value in data.items():
+ setattr(self, key, value)
+
+ def from_string(self, data: str) -> "Creator":
+ if "," in data:
+ self.firstName = data.split(",")[1]
+ self.lastName = data.split(",")[0]
+
+ return self
+
+ # set __dict__ object to be used in json
+
+
+@dataclass
+class Book:
+ itemType: str = "book"
+ creators: list[Creator] = None
+ tags: list = None
+ collections: list = None
+ relations: dict = None
+ title: str = None
+ abstractNote: str = None
+ series: str = None
+ seriesNumber: str = None
+ volume: str = None
+ numberOfVolumes: str = None
+ edition: str = None
+ place: str = None
+ publisher: str = None
+ date: str = None
+ numPages: str = None
+ language: str = None
+ ISBN: str = None
+ shortTitle: str = None
+ url: str = None
+ accessDate: str = None
+ archive: str = None
+ archiveLocation: str = None
+ libraryCatalog: str = None
+ callNumber: str = None
+ rights: str = None
+ extra: str = None
+
+ def to_dict(self) -> dict:
+ ret = {}
+ for key, value in self.__dict__.items():
+ if value:
+ ret[key] = value
+ return ret
+
+
+@dataclass
+class BookSection:
+ itemType: str = "bookSection"
+ title: str = None
+ creators: list[Creator] = None
+ abstractNote: str = None
+ bookTitle: str = None
+ series: str = None
+ seriesNumber: str = None
+ volume: str = None
+ numberOfVolumes: str = None
+ edition: str = None
+ place: str = None
+ publisher: str = None
+ date: str = None
+ pages: str = None
+ language: str = None
+ ISBN: str = None
+ shortTitle: str = None
+ url: str = None
+ accessDate: str = None
+ archive: str = None
+ archiveLocation: str = None
+ libraryCatalog: str = None
+ callNumber: str = None
+ rights: str = None
+ extra: str = None
+ tags = list
+ collections = list
+ relations = dict
+
+ def to_dict(self) -> dict:
+ ret = {}
+ for key, value in self.__dict__.items():
+ if value:
+ ret[key] = value
+ return ret
+
+ def assign(self, book) -> None:
+ for key, value in book.__dict__.items():
+ if key in self.__dict__.keys():
+ try:
+ setattr(self, key, value)
+ except AttributeError:
+ pass
+
+
+@dataclass
+class JournalArticle:
+ itemType = "journalArticle"
+ title: str = None
+ creators: list[Creator] = None
+ abstractNote: str = None
+ publicationTitle: str = None
+ volume: str = None
+ issue: str = None
+ pages: str = None
+ date: str = None
+ series: str = None
+ seriesTitle: str = None
+ seriesText: str = None
+ journalAbbreviation: str = None
+ language: str = None
+ DOI: str = None
+ ISSN: str = None
+ shortTitle: str = None
+ url: str = None
+ accessDate: str = None
+ archive: str = None
+ archiveLocation: str = None
+ libraryCatalog: str = None
+ callNumber: str = None
+ rights: str = None
+ extra: str = None
+ tags = list
+ collections = list
+ relations = dict
+
+ def to_dict(self) -> dict:
+ ret = {}
+ for key, value in self.__dict__.items():
+ if value:
+ ret[key] = value
+ return ret
+
+ def assign(self, book: dict) -> None:
+ for key, value in book.__dict__.items():
+ if key in self.__dict__.keys():
+ try:
+ setattr(self, key, value)
+ except AttributeError:
+ pass
+
+
+class ZoteroController:
+ zoterocfg = settings.zotero
+
+ def __init__(self):
+ if self.zoterocfg.library_id is None:
+ return
+ self.zot = zotero.Zotero( # type: ignore
+ self.zoterocfg.library_id,
+ self.zoterocfg.library_type,
+ self.zoterocfg.api_key,
+ )
+
+ def get_books(self) -> list:
+ ret = []
+ items = self.zot.top() # type: ignore
+ for item in items:
+ if item["data"]["itemType"] == "book":
+ ret.append(item)
+ return ret
+
+ # create item in zotero
+ # item is a part of a book
+ def __get_data(self, isbn) -> dict:
+ web = WebRequest()
+ web.get_ppn(isbn)
+ data = web.get_data_elsa()
+ bib = BibTextTransformer()
+ bib.get_data(data)
+ book = bib.return_data()
+ return book
+
+ # # #print(zot.item_template("bookSection"))
+ def createBook(self, isbn) -> Book:
+ book = self.__get_data(isbn)
+
+ bookdata = Book()
+ bookdata.title = book.title.split(":")[0]
+ bookdata.ISBN = book.isbn
+ bookdata.language = book.language
+ bookdata.date = book.year
+ bookdata.publisher = book.publisher
+ bookdata.url = book.link
+ bookdata.edition = book.edition
+ bookdata.place = book.place
+ bookdata.numPages = book.pages
+ authors = [
+ Creator().from_string(author).__dict__ for author in book.author.split(";")
+ ]
+ authors = [author for author in authors if author["lastName"] is not None]
+ bookdata.creators = authors
+ return bookdata
+
+ def createItem(self, item) -> Optional[str]:
+ resp = self.zot.create_items([item]) # type: ignore
+ if "successful" in resp.keys():
+ log.debug(resp)
+ return resp["successful"]["0"]["key"]
+ else:
+ return None
+
+ def deleteItem(self, key) -> None:
+ items = self.zot.items()
+ for item in items:
+ if item["key"] == key:
+ self.zot.delete_item(item) # type: ignore
+ # #print(item)
+ break
+
+ def createHGSection(self, book: Book, data: dict) -> Optional[str]:
+ log.debug(book)
+ chapter = BookSection()
+ chapter.assign(book)
+ chapter.pages = data["pages"]
+ chapter.itemType = "bookSection"
+ chapter.ISBN = ""
+ chapter.url = ""
+ chapter.title = data["chapter_title"]
+ creators = chapter.creators
+ for creator in creators:
+ creator["creatorType"] = "editor"
+ chapter.creators = creators
+ authors = [
+ Creator().from_string(author).__dict__
+ for author in data["section_author"].split(";")
+ ]
+ chapter.creators += authors
+
+ log.debug(chapter.to_dict())
+ return self.createItem(chapter.to_dict())
+ pass
+
+ def createBookSection(self, book: Book, data: dict) -> Optional[str]:
+ chapter = BookSection()
+ chapter.assign(book)
+ chapter.pages = data["pages"]
+ chapter.itemType = "bookSection"
+ chapter.ISBN = ""
+ chapter.url = ""
+ chapter.title = ""
+ return self.createItem(chapter.to_dict())
+ # chapter.creators
+
+ def createJournalArticle(self, journal, article) -> Optional[str]:
+ # #print(type(article))
+ journalarticle = JournalArticle()
+ journalarticle.assign(journal)
+ journalarticle.itemType = "journalArticle"
+ journalarticle.creators = [
+ Creator().from_string(author).__dict__
+ for author in article["section_author"].split(";")
+ ]
+ journalarticle.date = article["year"]
+ journalarticle.title = article["chapter_title"]
+ journalarticle.publicationTitle = article["work_title"].split(":")[0].strip()
+ journalarticle.pages = article["pages"]
+ journalarticle.ISSN = article["isbn"]
+ journalarticle.issue = article["issue"]
+ journalarticle.url = article["isbn"]
+
+ # #print(journalarticle.to_dict())
+
+ return self.createItem(journalarticle.to_dict())
+
+ def get_citation(self, item) -> str:
+ title = self.zot.item( # type: ignore
+ item,
+ content="bib",
+ style="deutsche-gesellschaft-fur-psychologie",
+ )[0]
+ # title = title[0]
+ title = (
+ title.replace("", "")
+ .replace("", "")
+ .replace('', "")
+ .replace("", "")
+ .replace("&", "&")
+ )
+ return title
+
+
+if __name__ == "__main__":
+ zot = ZoteroController()
+ book = zot.createBook("DV 3000 D649 (4)")
+ row = "Döbert, Hans & Hörner, Wolfgang & Kopp, Bortho von & Reuter, Lutz R."
+ zot.createBookSection()
+
+ # book = Book()
+ # # # book.
+ # ISBN = "9783801718718"
+ # book = createBook(isbn=ISBN)
+ # chapter = BookSection()
+ # chapter.title = "Geistige Behinderung"
+ # chapter.bookTitle = book.title
+ # chapter.pages = "511 - 538"
+ # chapter.publisher = book.publisher
+ # authors = [
+ # Creator("Jennifer M.", "Phillips").__dict__,
+ # Creator("Hower", "Kwon").__dict__,
+ # Creator("Carl", "Feinstein").__dict__,
+ # Creator("Inco", "Spintczok von Brisinski").__dict__,
+ # ]
+ # publishers = book.author
+ # if isinstance(publishers, str):
+ # publishers = [publishers]
+ # for publisher in publishers:
+ # # #print(publisher)
+ # creator = Creator().from_string(publisher)
+ # creator.creatorType = "editor"
+ # authors.append(creator.__dict__)
+
+ # chapter.creators = authors
+ # chapter.publisher = book.publisher
+ # # #print(chapter.to_dict())
+ # createBookSection(chapter.to_dict())
+ # get_citation("9ZXH8DDE")
+ # # # #print()
+ # # #print(get_books())
+ # # #print(zot.item_creator_types("bookSection"))
diff --git a/src/shared/__init__.py b/src/shared/__init__.py
new file mode 100644
index 0000000..fe09d94
--- /dev/null
+++ b/src/shared/__init__.py
@@ -0,0 +1,6 @@
+"""Shared utilities and cross-cutting concerns."""
+
+from .logging import log
+from .config import Settings, load_config
+
+__all__ = ["log", "Settings", "load_config"]
diff --git a/src/shared/config.py b/src/shared/config.py
new file mode 100644
index 0000000..029dd15
--- /dev/null
+++ b/src/shared/config.py
@@ -0,0 +1,66 @@
+"""Application configuration and settings."""
+
+from dataclasses import dataclass, field
+from pathlib import Path
+from typing import Any
+
+import yaml
+
+from src.shared.logging import log
+
+
+@dataclass
+class Settings:
+ """Settings for the application."""
+
+ save_path: str
+ database_name: str
+ database_path: str
+ bib_id: str = ""
+ default_apps: bool = True
+ custom_applications: list[dict[str, Any]] = field(default_factory=list)
+
+ def save_settings(self, config_path: str | Path = "config.yaml") -> None:
+ """Save the settings to the config file.
+
+ Args:
+ config_path: Path to the configuration file
+ """
+ try:
+ with open(config_path, "w") as f:
+ yaml.dump(self.__dict__, f)
+ log.info(f"Settings saved to {config_path}")
+ except Exception as e:
+ log.error(f"Failed to save settings: {e}")
+ raise
+
+ @classmethod
+ def load_settings(cls, config_path: str | Path = "config.yaml") -> dict[str, Any]:
+ """Load the settings from the config file.
+
+ Args:
+ config_path: Path to the configuration file
+
+ Returns:
+ Dictionary containing the loaded settings
+ """
+ try:
+ with open(config_path, "r") as f:
+ data = yaml.safe_load(f)
+ log.info(f"Settings loaded from {config_path}")
+ return data
+ except Exception as e:
+ log.error(f"Failed to load settings: {e}")
+ raise
+
+
+def load_config(config_path: str | Path = "config.yaml") -> dict[str, Any]:
+ """Convenience function to load configuration.
+
+ Args:
+ config_path: Path to the configuration file
+
+ Returns:
+ Dictionary containing the loaded settings
+ """
+ return Settings.load_settings(config_path)
diff --git a/src/shared/logging.py b/src/shared/logging.py
new file mode 100644
index 0000000..995f2f2
--- /dev/null
+++ b/src/shared/logging.py
@@ -0,0 +1,25 @@
+import sys
+
+import loguru
+
+from src import LOG_DIR
+
+log = loguru.logger
+_configured = False
+
+
+def configure(level: str = "INFO", to_stdout: bool = True, rotate_bytes: str = "1 MB"):
+ global _configured
+ if _configured:
+ return log
+ log.remove()
+ if to_stdout:
+ log.add(sys.stdout, level=level)
+ # application rolling log
+ log.add(
+ f"{LOG_DIR}/application.log",
+ rotation=rotate_bytes,
+ retention="10 days",
+ )
+ _configured = True
+ return log
diff --git a/src/sounds/error.mp3 b/src/sounds/error.mp3
new file mode 100644
index 0000000..6b4487f
Binary files /dev/null and b/src/sounds/error.mp3 differ
diff --git a/src/transformers/transformers.py b/src/transformers/transformers.py
index a7ef88a..ade70b7 100644
--- a/src/transformers/transformers.py
+++ b/src/transformers/transformers.py
@@ -2,14 +2,15 @@ from __future__ import annotations
import json
import re
+import sys
from dataclasses import dataclass
from dataclasses import field as dataclass_field
from typing import Any, List
+import loguru
+
from src import LOG_DIR
from src.logic.dataclass import BookData
-import loguru
-import sys
log = loguru.logger
log.remove()
@@ -36,6 +37,7 @@ class Item:
department: str | None = dataclass_field(default_factory=str)
locationhref: str | None = dataclass_field(default_factory=str)
location: str | None = dataclass_field(default_factory=str)
+ ktrl_nr: str | None = dataclass_field(default_factory=str)
def from_dict(self, data: dict):
"""Import data from dict"""
@@ -382,6 +384,8 @@ class RDSData:
def transform(self, data: str):
# rds_availability = RDS_AVAIL_DATA()
# rds_data = RDS_GENERIC_DATA()
+ print(data)
+
def __get_raw_data(data: str) -> list:
# create base data to be turned into pydantic classes
data = data.split("RDS ----------------------------------")[1]
diff --git a/src/ui/dialogs/Ui_edit_bookdata.py b/src/ui/dialogs/Ui_edit_bookdata.py
index eba44e0..f9865d6 100644
--- a/src/ui/dialogs/Ui_edit_bookdata.py
+++ b/src/ui/dialogs/Ui_edit_bookdata.py
@@ -8,7 +8,7 @@
from PySide6 import QtCore, QtGui, QtWidgets
-from src.logic.dataclass import BookData
+from src.core.models import BookData
class Ui_Dialog(object):
diff --git a/src/ui/dialogs/Ui_fileparser.py b/src/ui/dialogs/Ui_fileparser.py
index da869db..72fbb60 100644
--- a/src/ui/dialogs/Ui_fileparser.py
+++ b/src/ui/dialogs/Ui_fileparser.py
@@ -8,7 +8,7 @@
from PySide6 import QtCore, QtGui, QtWidgets
-from src.logic.webrequest import BibTextTransformer, WebRequest
+from src.services.webrequest import BibTextTransformer, WebRequest
class Ui_Dialog(object):
diff --git a/src/ui/dialogs/Ui_login.py b/src/ui/dialogs/Ui_login.py
index 7e807ac..b9089dd 100644
--- a/src/ui/dialogs/Ui_login.py
+++ b/src/ui/dialogs/Ui_login.py
@@ -10,8 +10,9 @@ import hashlib
from PySide6 import QtCore, QtWidgets
-from src.backend.database import Database
-from src.backend.admin_console import AdminCommands
+from src.admin import AdminCommands
+from src.database import Database
+
class Ui_Dialog(object):
def setupUi(self, Dialog):
@@ -64,13 +65,11 @@ class Ui_Dialog(object):
def login(self):
username = self.lineEdit.text()
password = self.lineEdit_2.text()
- print(type(username), password)
+ # print(type(username), password)
# Assuming 'Database' is a class to interact with your database
- db = Database()
+ db = Database()
- hashed_password = hashlib.sha256(
- password.encode()
- ).hexdigest()
+ hashed_password = hashlib.sha256(password.encode()).hexdigest()
if len(db.getUsers()) == 0:
AdminCommands().create_admin()
self.lresult = 1 # Indicate successful login
diff --git a/src/ui/dialogs/Ui_mail_preview.py b/src/ui/dialogs/Ui_mail_preview.py
index 32ed57d..1980790 100644
--- a/src/ui/dialogs/Ui_mail_preview.py
+++ b/src/ui/dialogs/Ui_mail_preview.py
@@ -6,17 +6,18 @@
# run again. Do not edit this file unless you know what you are doing.
-from PySide6 import QtCore, QtGui, QtWidgets
-import subprocess
-import tempfile
import os
import re
+import subprocess
+import tempfile
+
from omegaconf import OmegaConf
+from PySide6 import QtCore, QtWidgets
config = OmegaConf.load("config.yaml")
-class Ui_eMailPreview(object):
+class Ui_eMailPreview(object):
def setupUi(
self,
eMailPreview,
@@ -31,7 +32,10 @@ class Ui_eMailPreview(object):
self.buttonBox = QtWidgets.QDialogButtonBox(eMailPreview)
self.buttonBox.setGeometry(QtCore.QRect(310, 630, 341, 32))
self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal)
- self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.StandardButton.Cancel|QtWidgets.QDialogButtonBox.StandardButton.Ok)
+ self.buttonBox.setStandardButtons(
+ QtWidgets.QDialogButtonBox.StandardButton.Cancel
+ | QtWidgets.QDialogButtonBox.StandardButton.Ok
+ )
self.buttonBox.setObjectName("buttonBox")
self.gridLayoutWidget = QtWidgets.QWidget(eMailPreview)
self.gridLayoutWidget.setGeometry(QtCore.QRect(10, 10, 661, 621))
@@ -46,7 +50,11 @@ class Ui_eMailPreview(object):
self.prof_name.setObjectName("prof_name")
self.gridLayout.addWidget(self.prof_name, 2, 2, 1, 1)
self.label_3 = QtWidgets.QLabel(self.gridLayoutWidget)
- self.label_3.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeading|QtCore.Qt.AlignmentFlag.AlignLeft|QtCore.Qt.AlignmentFlag.AlignTop)
+ self.label_3.setAlignment(
+ QtCore.Qt.AlignmentFlag.AlignLeading
+ | QtCore.Qt.AlignmentFlag.AlignLeft
+ | QtCore.Qt.AlignmentFlag.AlignTop
+ )
self.label_3.setObjectName("label_3")
self.gridLayout.addWidget(self.label_3, 5, 0, 1, 1)
self.mail_name = QtWidgets.QLineEdit(self.gridLayoutWidget)
@@ -81,7 +89,12 @@ class Ui_eMailPreview(object):
self.gender_non = QtWidgets.QRadioButton(self.gridLayoutWidget)
self.gender_non.setObjectName("gender_non")
self.horizontalLayout_3.addWidget(self.gender_non)
- spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
+ spacerItem = QtWidgets.QSpacerItem(
+ 40,
+ 20,
+ QtWidgets.QSizePolicy.Policy.Expanding,
+ QtWidgets.QSizePolicy.Policy.Minimum,
+ )
self.horizontalLayout_3.addItem(spacerItem)
self.gridLayout.addLayout(self.horizontalLayout_3, 4, 2, 1, 1)
self.label_6 = QtWidgets.QLabel(self.gridLayoutWidget)
@@ -89,8 +102,8 @@ class Ui_eMailPreview(object):
self.gridLayout.addWidget(self.label_6, 4, 0, 1, 1)
self.retranslateUi(eMailPreview)
- self.buttonBox.accepted.connect(eMailPreview.accept) # type: ignore
- self.buttonBox.rejected.connect(eMailPreview.reject) # type: ignore
+ self.buttonBox.accepted.connect(eMailPreview.accept) # type: ignore
+ self.buttonBox.rejected.connect(eMailPreview.reject) # type: ignore
QtCore.QMetaObject.connectSlotsByName(eMailPreview)
self._appid = app_id
self._appname = app_name
@@ -127,7 +140,6 @@ class Ui_eMailPreview(object):
return "Sehr geehrte Frau"
elif self.gender_non.isChecked():
return "Guten Tag"
-
def set_mail(self):
email_template = self.comboBox.currentText()
@@ -145,14 +157,19 @@ class Ui_eMailPreview(object):
mail_html = mail_template.split("")[1]
mail_html = "" + mail_html
mail_html = mail_html.format(
- Profname=self.prof_name.text().split(" ")[1], Appname=self._appname, AppNr=self._appid, AppSubject = self._subject,greeting = self.get_greeting()
+ Profname=self.prof_name.text().split(" ")[1],
+ Appname=self._appname,
+ AppNr=self._appid,
+ AppSubject=self._subject,
+ greeting=self.get_greeting(),
)
self.mail_body.setHtml(mail_html)
+
def load_mail_templates(self):
mail_templates = os.listdir("mail_vorlagen")
mail_templates = [f for f in mail_templates if f.endswith(".eml")]
- print(mail_templates)
+ # print(mail_templates)
self.comboBox.addItems(mail_templates)
def save_mail(self):
@@ -168,16 +185,17 @@ class Ui_eMailPreview(object):
) as f:
f.write(mail)
self.mail_path = f.name
- print(self.mail_path)
+ # print(self.mail_path)
# open the file using thunderbird
subprocess.Popen([f"{self.mail_path}"])
# delete the file
# os.remove(self.mail_path)
+
def launch():
app = QtWidgets.QApplication([])
eMailPreview = QtWidgets.QDialog()
ui = Ui_eMailPreview()
- ui.setupUi(eMailPreview, "1","Test","Biologie","Kirchner, Alexander")
+ ui.setupUi(eMailPreview, "1", "Test", "Biologie", "Kirchner, Alexander")
eMailPreview.show()
- app.exec()
\ No newline at end of file
+ app.exec()
diff --git a/src/ui/dialogs/Ui_medianadder.ts b/src/ui/dialogs/Ui_medianadder.ts
deleted file mode 100644
index 6401616..0000000
--- a/src/ui/dialogs/Ui_medianadder.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/src/ui/dialogs/Ui_parsed_titles.py b/src/ui/dialogs/Ui_parsed_titles.py
index b05ad32..b42eb2a 100644
--- a/src/ui/dialogs/Ui_parsed_titles.py
+++ b/src/ui/dialogs/Ui_parsed_titles.py
@@ -110,7 +110,7 @@ class Ui_Form(object):
self.progressBar.setValue(value)
def thread_quit(self):
- print("Terminating thread")
+ # print("Terminating thread")
self.thread.terminate()
self.thread.quit()
self.thread.deleteLater()
@@ -144,7 +144,7 @@ class Ui_Form(object):
def determine_progress(self, signal):
# check length of listWidget
length = self.listWidget.count()
- print(f"Length of listWidget: {length}")
+ # print(f"Length of listWidget: {length}")
if length == 0:
logger.log_info("AutoAdder finished")
self.buttonBox.accepted.emit()
diff --git a/src/ui/dialogs/Ui_reminder.py b/src/ui/dialogs/Ui_reminder.py
index 283e509..001303a 100644
--- a/src/ui/dialogs/Ui_reminder.py
+++ b/src/ui/dialogs/Ui_reminder.py
@@ -6,7 +6,7 @@
# run again. Do not edit this file unless you know what you are doing.
-from PySide6 import QtCore, QtGui, QtWidgets
+from PySide6 import QtCore, QtWidgets
class Ui_Dialog(object):
diff --git a/src/ui/dialogs/Ui_settings.py b/src/ui/dialogs/Ui_settings.py
index 36c39f2..4009cf6 100644
--- a/src/ui/dialogs/Ui_settings.py
+++ b/src/ui/dialogs/Ui_settings.py
@@ -169,7 +169,7 @@ class Ui_Dialog(object):
name = application.application
file_type = application.extensions
display_name = application.name
- print(name, file_type, display_name) #
+ # print(name, file_type, display_name) #
# create new item
item = QtWidgets.QTreeWidgetItem(self.treeWidget)
item.setText(0, display_name)
diff --git a/src/ui/dialogs/__init__.py b/src/ui/dialogs/__init__.py
index 1940b77..a4c3242 100644
--- a/src/ui/dialogs/__init__.py
+++ b/src/ui/dialogs/__init__.py
@@ -12,20 +12,23 @@ __all__ = [
"ElsaAddEntry",
"ApparatExtendDialog",
"DocumentPrintDialog",
+ "NewEditionDialog",
"Settings",
+ "DeleteDialog",
]
+from .about import About
+from .app_ext import ApparatExtendDialog
from .bookdata import BookDataUI
+from .deletedialog import DeleteDialog
+from .docuprint import DocumentPrintDialog
+from .elsa_add_entry import ElsaAddEntry
+from .elsa_gen_confirm import ElsaGenConfirm
from .login import LoginDialog
from .mail import Mail_Dialog
from .mailTemplate import MailTemplateDialog
from .medienadder import MedienAdder
+from .newEdition import NewEditionDialog
from .parsed_titles import ParsedTitles
from .popup_confirm import ConfirmDialog as popus_confirm
from .reminder import ReminderDialog
-from .about import About
-from .elsa_gen_confirm import ElsaGenConfirm
-from .elsa_add_entry import ElsaAddEntry
-from .app_ext import ApparatExtendDialog
-from .docuprint import DocumentPrintDialog
-
from .settings import Settings
diff --git a/src/ui/dialogs/about.py b/src/ui/dialogs/about.py
index 972495e..496aadf 100644
--- a/src/ui/dialogs/about.py
+++ b/src/ui/dialogs/about.py
@@ -1,7 +1,9 @@
-from .dialog_sources.Ui_about import Ui_about
-from PySide6 import QtWidgets
import PySide6
-from src import Icon, __version__, __author__
+from PySide6 import QtWidgets
+
+from src import Icon, __author__, __version__
+
+from .dialog_sources.about_ui import Ui_about
class About(QtWidgets.QDialog, Ui_about):
diff --git a/src/ui/dialogs/app_ext.py b/src/ui/dialogs/app_ext.py
index 8db924f..5bf77e7 100644
--- a/src/ui/dialogs/app_ext.py
+++ b/src/ui/dialogs/app_ext.py
@@ -1,7 +1,9 @@
from PySide6 import QtWidgets
-from .dialog_sources.Ui_apparat_extend import Ui_Dialog
+
from src import Icon
+from .dialog_sources.apparat_extend_ui import Ui_Dialog
+
class ApparatExtendDialog(QtWidgets.QDialog, Ui_Dialog):
def __init__(
diff --git a/src/ui/dialogs/bookdata.py b/src/ui/dialogs/bookdata.py
index 9561604..0a7e34d 100644
--- a/src/ui/dialogs/bookdata.py
+++ b/src/ui/dialogs/bookdata.py
@@ -1,8 +1,8 @@
from PySide6 import QtWidgets
-from src.logic.dataclass import BookData
+from src.core.models import BookData
-from .dialog_sources.Ui_edit_bookdata import Ui_Dialog
+from .dialog_sources.edit_bookdata_ui import Ui_Dialog
class BookDataUI(QtWidgets.QDialog, Ui_Dialog):
diff --git a/src/ui/dialogs/confirm_extend_ui.py b/src/ui/dialogs/confirm_extend_ui.py
index cc2674e..a82c515 100644
--- a/src/ui/dialogs/confirm_extend_ui.py
+++ b/src/ui/dialogs/confirm_extend_ui.py
@@ -6,7 +6,7 @@
# run again. Do not edit this file unless you know what you are doing.
-from PySide6 import QtCore, QtGui, QtWidgets
+from PySide6 import QtCore, QtWidgets
class Ui_extend_confirm(object):
diff --git a/src/ui/dialogs/deletedialog.py b/src/ui/dialogs/deletedialog.py
new file mode 100644
index 0000000..0c81bdf
--- /dev/null
+++ b/src/ui/dialogs/deletedialog.py
@@ -0,0 +1,129 @@
+from typing import Any
+
+from PySide6 import QtCore, QtWidgets
+
+from src import Icon
+from src.database import Database
+
+from .dialog_sources.deletedialog_ui import Ui_Dialog
+
+
+class DeleteDialog(QtWidgets.QDialog, Ui_Dialog):
+ def __init__(self):
+ super().__init__()
+ self.setupUi(self)
+ self.setWindowTitle("Medien löschen")
+ self.setWindowIcon(Icon("trash").icon)
+ self.reset_btn.clicked.connect(self.reset_selection)
+ self.cancel_btn.clicked.connect(self.close)
+ self.delete_btn.clicked.connect(self.delete_selected)
+
+ self.db = Database()
+ self.books = self.setBooks()
+ self.lineEdit.textChanged.connect(self.populate_books)
+ self.populate_books()
+
+ def delete_selected(self):
+ to_delete = []
+ for row in range(self.tableWidget.rowCount()):
+ checkbox = self.tableWidget.cellWidget(row, 0)
+ if checkbox is not None and checkbox.isChecked():
+ book_id_item = self.tableWidget.item(row, 6)
+ if book_id_item is not None:
+ book_id = int(book_id_item.text())
+ to_delete.append(book_id)
+ if to_delete:
+ self.db.deleteBooks(to_delete)
+ self.accept()
+
+ def reset_selection(self):
+ for row in range(self.tableWidget.rowCount()):
+ checkbox = self.tableWidget.cellWidget(row, 0)
+ if checkbox is not None:
+ checkbox.setChecked(False)
+
+ def setBooks(self) -> list[dict[str, Any]]:
+ result: list[dict[str, Any]] = []
+ books = self.db.getAllBooks()
+ for book in books:
+ title = book["bookdata"].title
+ signature = book["bookdata"].signature
+ edition = book["bookdata"].edition
+ appnr = self.db.getApparatNrByBookId(book["id"])
+ result.append(
+ {
+ "id": book["id"],
+ "appnr": appnr,
+ "title": title,
+ "signature": signature,
+ "edition": edition,
+ }
+ )
+ return result
+
+ def populate_books(self):
+ searchterm = self.lineEdit.text().lower()
+ self.tableWidget.setRowCount(0)
+ for book in self.books:
+ checkbox = QtWidgets.QCheckBox()
+ app_nr = book["appnr"]
+ title = book["title"]
+ signature = book["signature"]
+ edition = book["edition"] if book["edition"] else ""
+
+ if searchterm in title.lower() or searchterm in signature.lower():
+ self.tableWidget.insertRow(self.tableWidget.rowCount())
+ self.tableWidget.setCellWidget(
+ self.tableWidget.rowCount() - 1, 0, checkbox
+ )
+ self.tableWidget.setItem(
+ self.tableWidget.rowCount() - 1,
+ 1,
+ QtWidgets.QTableWidgetItem(str(app_nr)),
+ )
+ self.tableWidget.setItem(
+ self.tableWidget.rowCount() - 1,
+ 2,
+ QtWidgets.QTableWidgetItem(signature),
+ )
+ self.tableWidget.setItem(
+ self.tableWidget.rowCount() - 1,
+ 3,
+ QtWidgets.QTableWidgetItem(title),
+ )
+ self.tableWidget.setItem(
+ self.tableWidget.rowCount() - 1,
+ 4,
+ QtWidgets.QTableWidgetItem(edition),
+ )
+ self.tableWidget.setItem(
+ self.tableWidget.rowCount() - 1,
+ 5,
+ QtWidgets.QTableWidgetItem(""),
+ )
+ self.tableWidget.setItem(
+ self.tableWidget.rowCount() - 1,
+ 6,
+ QtWidgets.QTableWidgetItem(str(book["id"])),
+ )
+ else:
+ continue
+ # set column signature to be 10px wider than the longest entry
+ self.tableWidget.setColumnWidth(1, 100)
+ self.tableWidget.setColumnWidth(2, 150)
+ self.tableWidget.setColumnWidth(3, 150)
+ self.tableWidget.setColumnWidth(4, 100)
+
+ self.tableWidget.setColumnWidth(0, 50)
+ # horizontal header 0 should be centered
+ self.tableWidget.horizontalHeader().setDefaultAlignment(
+ QtCore.Qt.AlignmentFlag.AlignCenter
+ )
+
+
+def launch():
+ app = QtWidgets.QApplication.instance()
+ if app is None:
+ app = QtWidgets.QApplication([])
+ dialog = DeleteDialog()
+ dialog.exec()
diff --git a/src/ui/dialogs/dialog_sources/Ui_about.py b/src/ui/dialogs/dialog_sources/Ui_about.py
deleted file mode 100644
index 6434828..0000000
--- a/src/ui/dialogs/dialog_sources/Ui_about.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\about.ui'
-#
-# Created by: PySide6 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
-
-
-class Ui_about(object):
- def setupUi(self, about):
- about.setObjectName("about")
- about.resize(301, 313)
- self.verticalLayout = QtWidgets.QVBoxLayout(about)
- self.verticalLayout.setObjectName("verticalLayout")
- self.version = QtWidgets.QLabel(parent=about)
- font = QtGui.QFont()
- font.setPointSize(12)
- font.setBold(True)
- self.version.setFont(font)
- self.version.setScaledContents(False)
- self.version.setObjectName("version")
- self.verticalLayout.addWidget(self.version)
- self.description = QtWidgets.QTextEdit(parent=about)
- self.description.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
- self.description.setReadOnly(True)
- self.description.setObjectName("description")
- self.verticalLayout.addWidget(self.description)
-
- self.retranslateUi(about)
- QtCore.QMetaObject.connectSlotsByName(about)
-
- def retranslateUi(self, about):
- _translate = QtCore.QCoreApplication.translate
- about.setWindowTitle(_translate("about", "Dialog"))
- self.version.setText(_translate("about", "SemesterapparatsManagement"))
diff --git a/src/ui/dialogs/dialog_sources/Ui_app_status.py b/src/ui/dialogs/dialog_sources/Ui_app_status.py
deleted file mode 100644
index 7dcc51b..0000000
--- a/src/ui/dialogs/dialog_sources/Ui_app_status.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\app_status.ui'
-#
-# Created by: PySide6 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
-
-
-class Ui_Form(object):
- def setupUi(self, Form):
- Form.setObjectName("Form")
- Form.resize(300, 500)
-
- self.retranslateUi(Form)
- QtCore.QMetaObject.connectSlotsByName(Form)
-
- def retranslateUi(self, Form):
- _translate = QtCore.QCoreApplication.translate
- Form.setWindowTitle(_translate("Form", "Form"))
diff --git a/src/ui/dialogs/dialog_sources/Ui_apparat_extend.py b/src/ui/dialogs/dialog_sources/Ui_apparat_extend.py
deleted file mode 100644
index 828d1a8..0000000
--- a/src/ui/dialogs/dialog_sources/Ui_apparat_extend.py
+++ /dev/null
@@ -1,83 +0,0 @@
-# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\apparat_extend.ui'
-#
-# Created by: PySide6 UI code generator 6.7.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
-
-
-class Ui_Dialog(object):
- def setupUi(self, Dialog):
- Dialog.setObjectName("Dialog")
- Dialog.resize(388, 103)
- sizePolicy = QtWidgets.QSizePolicy(
- QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed
- )
- sizePolicy.setHorizontalStretch(0)
- sizePolicy.setVerticalStretch(0)
- sizePolicy.setHeightForWidth(Dialog.sizePolicy().hasHeightForWidth())
- Dialog.setSizePolicy(sizePolicy)
- Dialog.setMinimumSize(QtCore.QSize(388, 103))
- Dialog.setMaximumSize(QtCore.QSize(388, 103))
- self.buttonBox = QtWidgets.QDialogButtonBox(parent=Dialog)
- self.buttonBox.setGeometry(QtCore.QRect(290, 30, 81, 241))
- self.buttonBox.setOrientation(QtCore.Qt.Orientation.Vertical)
- self.buttonBox.setStandardButtons(
- QtWidgets.QDialogButtonBox.StandardButton.Abort
- | QtWidgets.QDialogButtonBox.StandardButton.Save
- )
- self.buttonBox.setObjectName("buttonBox")
- self.label = QtWidgets.QLabel(parent=Dialog)
- self.label.setGeometry(QtCore.QRect(10, 0, 281, 31))
- sizePolicy = QtWidgets.QSizePolicy(
- QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed
- )
- sizePolicy.setHorizontalStretch(0)
- sizePolicy.setVerticalStretch(0)
- sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth())
- self.label.setSizePolicy(sizePolicy)
- font = QtGui.QFont()
- font.setPointSize(10)
- self.label.setFont(font)
- self.label.setObjectName("label")
- self.frame = QtWidgets.QFrame(parent=Dialog)
- self.frame.setGeometry(QtCore.QRect(10, 30, 241, 41))
- self.frame.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel)
- self.frame.setFrameShadow(QtWidgets.QFrame.Shadow.Raised)
- self.frame.setObjectName("frame")
- self.line = QtWidgets.QFrame(parent=self.frame)
- self.line.setGeometry(QtCore.QRect(120, 0, 3, 61))
- self.line.setFrameShape(QtWidgets.QFrame.Shape.VLine)
- self.line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
- self.line.setObjectName("line")
- self.rad_sommer = QtWidgets.QRadioButton(parent=self.frame)
- self.rad_sommer.setGeometry(QtCore.QRect(10, 10, 82, 21))
- self.rad_sommer.setObjectName("rad_sommer")
- self.rad_winter = QtWidgets.QRadioButton(parent=self.frame)
- self.rad_winter.setGeometry(QtCore.QRect(140, 10, 82, 21))
- self.rad_winter.setObjectName("rad_winter")
- self.sem_year = QtWidgets.QLineEdit(parent=Dialog)
- self.sem_year.setGeometry(QtCore.QRect(10, 70, 121, 20))
- self.sem_year.setObjectName("sem_year")
- self.dauerapp = QtWidgets.QCheckBox(parent=Dialog)
- self.dauerapp.setGeometry(QtCore.QRect(150, 70, 111, 21))
- self.dauerapp.setObjectName("dauerapp")
-
- self.retranslateUi(Dialog)
- self.buttonBox.accepted.connect(Dialog.accept) # type: ignore
- self.buttonBox.rejected.connect(Dialog.reject) # type: ignore
- QtCore.QMetaObject.connectSlotsByName(Dialog)
-
- def retranslateUi(self, Dialog):
- _translate = QtCore.QCoreApplication.translate
- Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
- self.label.setText(
- _translate("Dialog", "Bis wann soll der Apparat verlängert werden?")
- )
- self.rad_sommer.setText(_translate("Dialog", "Sommer"))
- self.rad_winter.setText(_translate("Dialog", "Winter"))
- self.sem_year.setPlaceholderText(_translate("Dialog", "2023"))
- self.dauerapp.setText(_translate("Dialog", "Dauerapparat"))
diff --git a/src/ui/dialogs/dialog_sources/Ui_confirm_extend.py b/src/ui/dialogs/dialog_sources/Ui_confirm_extend.py
deleted file mode 100644
index 72ef74f..0000000
--- a/src/ui/dialogs/dialog_sources/Ui_confirm_extend.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\confirm_extend.ui'
-#
-# Created by: PySide6 UI code generator 6.7.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, QtWidgets
-
-
-class Ui_extend_confirm(object):
- def setupUi(self, extend_confirm):
- extend_confirm.setObjectName("extend_confirm")
- extend_confirm.resize(380, 97)
- self.horizontalLayout = QtWidgets.QHBoxLayout(extend_confirm)
- self.horizontalLayout.setObjectName("horizontalLayout")
- self.textEdit = QtWidgets.QTextEdit(parent=extend_confirm)
- self.textEdit.setObjectName("textEdit")
- self.horizontalLayout.addWidget(self.textEdit)
- self.buttonBox = QtWidgets.QDialogButtonBox(parent=extend_confirm)
- self.buttonBox.setOrientation(QtCore.Qt.Orientation.Vertical)
- self.buttonBox.setStandardButtons(
- QtWidgets.QDialogButtonBox.StandardButton.Cancel
- | QtWidgets.QDialogButtonBox.StandardButton.Ok
- )
- self.buttonBox.setObjectName("buttonBox")
- self.horizontalLayout.addWidget(self.buttonBox)
-
- self.retranslateUi(extend_confirm)
- self.buttonBox.accepted.connect(extend_confirm.accept) # type: ignore
- self.buttonBox.rejected.connect(extend_confirm.reject) # type: ignore
- QtCore.QMetaObject.connectSlotsByName(extend_confirm)
-
- def retranslateUi(self, extend_confirm):
- _translate = QtCore.QCoreApplication.translate
- extend_confirm.setWindowTitle(_translate("extend_confirm", "Dialog"))
diff --git a/src/ui/dialogs/dialog_sources/Ui_edit_bookdata.py b/src/ui/dialogs/dialog_sources/Ui_edit_bookdata.py
deleted file mode 100644
index 381cd43..0000000
--- a/src/ui/dialogs/dialog_sources/Ui_edit_bookdata.py
+++ /dev/null
@@ -1,127 +0,0 @@
-# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\edit_bookdata.ui'
-#
-# Created by: PySide6 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
-
-
-class Ui_Dialog(object):
- def setupUi(self, Dialog):
- Dialog.setObjectName("Dialog")
- Dialog.resize(448, 572)
- self.buttonBox = QtWidgets.QDialogButtonBox(parent=Dialog)
- self.buttonBox.setGeometry(QtCore.QRect(260, 530, 161, 32))
- self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal)
- self.buttonBox.setStandardButtons(
- QtWidgets.QDialogButtonBox.StandardButton.Cancel
- | QtWidgets.QDialogButtonBox.StandardButton.Ok
- )
- self.buttonBox.setObjectName("buttonBox")
- self.gridLayoutWidget = QtWidgets.QWidget(parent=Dialog)
- self.gridLayoutWidget.setGeometry(QtCore.QRect(0, 0, 441, 531))
- self.gridLayoutWidget.setObjectName("gridLayoutWidget")
- self.gridLayout = QtWidgets.QGridLayout(self.gridLayoutWidget)
- self.gridLayout.setSizeConstraint(
- QtWidgets.QLayout.SizeConstraint.SetDefaultConstraint
- )
- self.gridLayout.setContentsMargins(0, 0, 0, 0)
- self.gridLayout.setObjectName("gridLayout")
- self.label_10 = QtWidgets.QLabel(parent=self.gridLayoutWidget)
- self.label_10.setObjectName("label_10")
- self.gridLayout.addWidget(self.label_10, 10, 1, 1, 1)
- self.label = QtWidgets.QLabel(parent=self.gridLayoutWidget)
- self.label.setObjectName("label")
- self.gridLayout.addWidget(self.label, 0, 1, 1, 1)
- self.label_9 = QtWidgets.QLabel(parent=self.gridLayoutWidget)
- self.label_9.setObjectName("label_9")
- self.gridLayout.addWidget(self.label_9, 9, 1, 1, 1)
- self.label_8 = QtWidgets.QLabel(parent=self.gridLayoutWidget)
- self.label_8.setObjectName("label_8")
- self.gridLayout.addWidget(self.label_8, 8, 1, 1, 1)
- self.label_12 = QtWidgets.QLabel(parent=self.gridLayoutWidget)
- self.label_12.setObjectName("label_12")
- self.gridLayout.addWidget(self.label_12, 6, 1, 1, 1)
- self.line_edition = QtWidgets.QLineEdit(parent=self.gridLayoutWidget)
- self.line_edition.setObjectName("line_edition")
- self.gridLayout.addWidget(self.line_edition, 2, 2, 1, 1)
- self.label_3 = QtWidgets.QLabel(parent=self.gridLayoutWidget)
- self.label_3.setObjectName("label_3")
- self.gridLayout.addWidget(self.label_3, 2, 1, 1, 1)
- self.label_4 = QtWidgets.QLabel(parent=self.gridLayoutWidget)
- self.label_4.setObjectName("label_4")
- self.gridLayout.addWidget(self.label_4, 3, 1, 1, 1)
- self.line_link = QtWidgets.QLineEdit(parent=self.gridLayoutWidget)
- self.line_link.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.ArrowCursor))
- self.line_link.setReadOnly(True)
- self.line_link.setObjectName("line_link")
- self.gridLayout.addWidget(self.line_link, 6, 2, 1, 1)
- self.label_5 = QtWidgets.QLabel(parent=self.gridLayoutWidget)
- self.label_5.setObjectName("label_5")
- self.gridLayout.addWidget(self.label_5, 4, 1, 1, 1)
- self.label_7 = QtWidgets.QLabel(parent=self.gridLayoutWidget)
- self.label_7.setObjectName("label_7")
- self.gridLayout.addWidget(self.label_7, 7, 1, 1, 1)
- self.label_6 = QtWidgets.QLabel(parent=self.gridLayoutWidget)
- self.label_6.setObjectName("label_6")
- self.gridLayout.addWidget(self.label_6, 5, 1, 1, 1)
- self.label_2 = QtWidgets.QLabel(parent=self.gridLayoutWidget)
- self.label_2.setObjectName("label_2")
- self.gridLayout.addWidget(self.label_2, 1, 1, 1, 1)
- spacerItem = QtWidgets.QSpacerItem(
- 5,
- 20,
- QtWidgets.QSizePolicy.Policy.Fixed,
- QtWidgets.QSizePolicy.Policy.Minimum,
- )
- self.gridLayout.addItem(spacerItem, 8, 0, 1, 1)
- self.line_title = QtWidgets.QLineEdit(parent=self.gridLayoutWidget)
- self.line_title.setObjectName("line_title")
- self.gridLayout.addWidget(self.line_title, 0, 2, 1, 1)
- self.line_signature = QtWidgets.QLineEdit(parent=self.gridLayoutWidget)
- self.line_signature.setObjectName("line_signature")
- self.gridLayout.addWidget(self.line_signature, 1, 2, 1, 1)
- self.line_author = QtWidgets.QLineEdit(parent=self.gridLayoutWidget)
- self.line_author.setObjectName("line_author")
- self.gridLayout.addWidget(self.line_author, 3, 2, 1, 1)
- self.line_lang = QtWidgets.QLineEdit(parent=self.gridLayoutWidget)
- self.line_lang.setObjectName("line_lang")
- self.gridLayout.addWidget(self.line_lang, 8, 2, 1, 1)
- self.line_ppn = QtWidgets.QLineEdit(parent=self.gridLayoutWidget)
- self.line_ppn.setObjectName("line_ppn")
- self.gridLayout.addWidget(self.line_ppn, 5, 2, 1, 1)
- self.line_isbn = QtWidgets.QLineEdit(parent=self.gridLayoutWidget)
- self.line_isbn.setObjectName("line_isbn")
- self.gridLayout.addWidget(self.line_isbn, 7, 2, 1, 1)
- self.line_year = QtWidgets.QLineEdit(parent=self.gridLayoutWidget)
- self.line_year.setObjectName("line_year")
- self.gridLayout.addWidget(self.line_year, 9, 2, 1, 1)
- self.line_pages = QtWidgets.QLineEdit(parent=self.gridLayoutWidget)
- self.line_pages.setObjectName("line_pages")
- self.gridLayout.addWidget(self.line_pages, 10, 2, 1, 1)
- self.line_publisher = QtWidgets.QLineEdit(parent=self.gridLayoutWidget)
- self.line_publisher.setObjectName("line_publisher")
- self.gridLayout.addWidget(self.line_publisher, 4, 2, 1, 1)
-
- self.retranslateUi(Dialog)
- self.buttonBox.accepted.connect(Dialog.accept) # type: ignore
- self.buttonBox.rejected.connect(Dialog.reject) # type: ignore
- QtCore.QMetaObject.connectSlotsByName(Dialog)
-
- def retranslateUi(self, Dialog):
- _translate = QtCore.QCoreApplication.translate
- Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
- self.label_10.setText(_translate("Dialog", "Seiten"))
- self.label.setText(_translate("Dialog", "Titel"))
- self.label_9.setText(_translate("Dialog", "Jahr"))
- self.label_8.setText(_translate("Dialog", "Sprache"))
- self.label_12.setText(_translate("Dialog", "Link"))
- self.label_3.setText(_translate("Dialog", "Auflage"))
- self.label_4.setText(_translate("Dialog", "Autor"))
- self.label_5.setText(_translate("Dialog", "Herausgeber"))
- self.label_7.setText(_translate("Dialog", "ISBN(s)"))
- self.label_6.setText(_translate("Dialog", "PPN"))
- self.label_2.setText(_translate("Dialog", "Signatur"))
diff --git a/src/ui/dialogs/dialog_sources/Ui_elsa_add_table_entry.py b/src/ui/dialogs/dialog_sources/Ui_elsa_add_table_entry.py
deleted file mode 100644
index a393508..0000000
--- a/src/ui/dialogs/dialog_sources/Ui_elsa_add_table_entry.py
+++ /dev/null
@@ -1,470 +0,0 @@
-# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\elsa_add_table_entry.ui'
-#
-# Created by: PySide6 UI code generator 6.7.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, QtWidgets
-
-
-class Ui_Dialog(object):
- def setupUi(self, Dialog):
- Dialog.setObjectName("Dialog")
- Dialog.resize(529, 482)
- self.verticalLayout = QtWidgets.QVBoxLayout(Dialog)
- self.verticalLayout.setObjectName("verticalLayout")
- self.groupBox = QtWidgets.QGroupBox(parent=Dialog)
- self.groupBox.setFlat(True)
- self.groupBox.setCheckable(False)
- self.groupBox.setObjectName("groupBox")
- self.gridLayout_4 = QtWidgets.QGridLayout(self.groupBox)
- self.gridLayout_4.setObjectName("gridLayout_4")
- spacerItem = QtWidgets.QSpacerItem(
- 40,
- 20,
- QtWidgets.QSizePolicy.Policy.Expanding,
- QtWidgets.QSizePolicy.Policy.Minimum,
- )
- self.gridLayout_4.addItem(spacerItem, 0, 3, 1, 1)
- self.btn_mono = QtWidgets.QRadioButton(parent=self.groupBox)
- self.btn_mono.setChecked(False)
- self.btn_mono.setObjectName("btn_mono")
- self.gridLayout_4.addWidget(self.btn_mono, 0, 0, 1, 1)
- self.btn_zs = QtWidgets.QRadioButton(parent=self.groupBox)
- self.btn_zs.setObjectName("btn_zs")
- self.gridLayout_4.addWidget(self.btn_zs, 0, 2, 1, 1)
- self.btn_hg = QtWidgets.QRadioButton(parent=self.groupBox)
- self.btn_hg.setObjectName("btn_hg")
- self.gridLayout_4.addWidget(self.btn_hg, 0, 1, 1, 1)
- self.verticalLayout.addWidget(self.groupBox)
- self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
- self.horizontalLayout_2.setObjectName("horizontalLayout_2")
- self.label_2 = QtWidgets.QLabel(parent=Dialog)
- self.label_2.setObjectName("label_2")
- self.horizontalLayout_2.addWidget(self.label_2)
- self.searchIdent = QtWidgets.QLineEdit(parent=Dialog)
- self.searchIdent.setObjectName("searchIdent")
- self.horizontalLayout_2.addWidget(self.searchIdent)
- self.btn_search = QtWidgets.QPushButton(parent=Dialog)
- self.btn_search.setObjectName("btn_search")
- self.horizontalLayout_2.addWidget(self.btn_search)
- spacerItem1 = QtWidgets.QSpacerItem(
- 40,
- 20,
- QtWidgets.QSizePolicy.Policy.Expanding,
- QtWidgets.QSizePolicy.Policy.Minimum,
- )
- self.horizontalLayout_2.addItem(spacerItem1)
- self.make_quote = QtWidgets.QPushButton(parent=Dialog)
- self.make_quote.setObjectName("make_quote")
- self.horizontalLayout_2.addWidget(self.make_quote)
- self.verticalLayout.addLayout(self.horizontalLayout_2)
- self.stackedWidget = QtWidgets.QStackedWidget(parent=Dialog)
- self.stackedWidget.setObjectName("stackedWidget")
- self.mono = QtWidgets.QWidget()
- self.mono.setObjectName("mono")
- self.gridLayout_2 = QtWidgets.QGridLayout(self.mono)
- self.gridLayout_2.setObjectName("gridLayout_2")
- self.label = QtWidgets.QLabel(parent=self.mono)
- self.label.setObjectName("label")
- self.gridLayout_2.addWidget(self.label, 0, 0, 1, 1)
- self.book_author = QtWidgets.QLineEdit(parent=self.mono)
- self.book_author.setObjectName("book_author")
- self.gridLayout_2.addWidget(self.book_author, 0, 1, 1, 1)
- self.label_3 = QtWidgets.QLabel(parent=self.mono)
- self.label_3.setObjectName("label_3")
- self.gridLayout_2.addWidget(self.label_3, 1, 0, 1, 1)
- self.book_year = QtWidgets.QLineEdit(parent=self.mono)
- self.book_year.setObjectName("book_year")
- self.gridLayout_2.addWidget(self.book_year, 1, 1, 1, 1)
- self.label_4 = QtWidgets.QLabel(parent=self.mono)
- self.label_4.setObjectName("label_4")
- self.gridLayout_2.addWidget(self.label_4, 2, 0, 1, 1)
- self.book_edition = QtWidgets.QLineEdit(parent=self.mono)
- self.book_edition.setObjectName("book_edition")
- self.gridLayout_2.addWidget(self.book_edition, 2, 1, 1, 1)
- self.label_5 = QtWidgets.QLabel(parent=self.mono)
- self.label_5.setObjectName("label_5")
- self.gridLayout_2.addWidget(self.label_5, 3, 0, 1, 1)
- self.book_title = QtWidgets.QLineEdit(parent=self.mono)
- self.book_title.setObjectName("book_title")
- self.gridLayout_2.addWidget(self.book_title, 3, 1, 1, 1)
- self.label_6 = QtWidgets.QLabel(parent=self.mono)
- self.label_6.setObjectName("label_6")
- self.gridLayout_2.addWidget(self.label_6, 4, 0, 1, 1)
- self.book_place = QtWidgets.QLineEdit(parent=self.mono)
- self.book_place.setObjectName("book_place")
- self.gridLayout_2.addWidget(self.book_place, 4, 1, 1, 1)
- self.label_7 = QtWidgets.QLabel(parent=self.mono)
- self.label_7.setObjectName("label_7")
- self.gridLayout_2.addWidget(self.label_7, 5, 0, 1, 1)
- self.book_publisher = QtWidgets.QLineEdit(parent=self.mono)
- self.book_publisher.setObjectName("book_publisher")
- self.gridLayout_2.addWidget(self.book_publisher, 5, 1, 1, 1)
- self.label_8 = QtWidgets.QLabel(parent=self.mono)
- self.label_8.setObjectName("label_8")
- self.gridLayout_2.addWidget(self.label_8, 6, 0, 1, 1)
- self.book_signature = QtWidgets.QLineEdit(parent=self.mono)
- self.book_signature.setObjectName("book_signature")
- self.gridLayout_2.addWidget(self.book_signature, 6, 1, 1, 1)
- self.label_9 = QtWidgets.QLabel(parent=self.mono)
- self.label_9.setObjectName("label_9")
- self.gridLayout_2.addWidget(self.label_9, 7, 0, 1, 1)
- self.book_pages = QtWidgets.QLineEdit(parent=self.mono)
- self.book_pages.setObjectName("book_pages")
- self.gridLayout_2.addWidget(self.book_pages, 7, 1, 1, 1)
- self.page_warn_2 = QtWidgets.QToolButton(parent=self.mono)
- self.page_warn_2.setText("")
- self.page_warn_2.setAutoRaise(True)
- self.page_warn_2.setObjectName("page_warn_2")
- self.gridLayout_2.addWidget(self.page_warn_2, 7, 2, 1, 1)
- self.label_29 = QtWidgets.QLabel(parent=self.mono)
- self.label_29.setObjectName("label_29")
- self.gridLayout_2.addWidget(self.label_29, 8, 0, 1, 1)
- self.book_isbn = QtWidgets.QLineEdit(parent=self.mono)
- self.book_isbn.setObjectName("book_isbn")
- self.gridLayout_2.addWidget(self.book_isbn, 8, 1, 1, 1)
- self.stackedWidget.addWidget(self.mono)
- self.hg = QtWidgets.QWidget()
- self.hg.setObjectName("hg")
- self.gridLayout_3 = QtWidgets.QGridLayout(self.hg)
- self.gridLayout_3.setObjectName("gridLayout_3")
- self.hg_editor = QtWidgets.QLineEdit(parent=self.hg)
- self.hg_editor.setObjectName("hg_editor")
- self.gridLayout_3.addWidget(self.hg_editor, 4, 1, 1, 1)
- self.label_26 = QtWidgets.QLabel(parent=self.hg)
- self.label_26.setObjectName("label_26")
- self.gridLayout_3.addWidget(self.label_26, 7, 0, 1, 1)
- self.hg_edition = QtWidgets.QLineEdit(parent=self.hg)
- self.hg_edition.setObjectName("hg_edition")
- self.gridLayout_3.addWidget(self.hg_edition, 2, 1, 1, 1)
- self.label_20 = QtWidgets.QLabel(parent=self.hg)
- self.label_20.setObjectName("label_20")
- self.gridLayout_3.addWidget(self.label_20, 1, 0, 1, 1)
- self.label_24 = QtWidgets.QLabel(parent=self.hg)
- self.label_24.setObjectName("label_24")
- self.gridLayout_3.addWidget(self.label_24, 3, 0, 1, 1)
- self.label_27 = QtWidgets.QLabel(parent=self.hg)
- self.label_27.setObjectName("label_27")
- self.gridLayout_3.addWidget(self.label_27, 8, 0, 1, 1)
- self.label_28 = QtWidgets.QLabel(parent=self.hg)
- self.label_28.setObjectName("label_28")
- self.gridLayout_3.addWidget(self.label_28, 9, 0, 1, 1)
- self.label_23 = QtWidgets.QLabel(parent=self.hg)
- self.label_23.setObjectName("label_23")
- self.gridLayout_3.addWidget(self.label_23, 5, 0, 1, 1)
- self.label_21 = QtWidgets.QLabel(parent=self.hg)
- self.label_21.setObjectName("label_21")
- self.gridLayout_3.addWidget(self.label_21, 2, 0, 1, 1)
- self.hg_pages = QtWidgets.QLineEdit(parent=self.hg)
- self.hg_pages.setObjectName("hg_pages")
- self.gridLayout_3.addWidget(self.hg_pages, 8, 1, 1, 1)
- self.label_19 = QtWidgets.QLabel(parent=self.hg)
- self.label_19.setObjectName("label_19")
- self.gridLayout_3.addWidget(self.label_19, 0, 0, 1, 1)
- self.hg_signature = QtWidgets.QLineEdit(parent=self.hg)
- self.hg_signature.setObjectName("hg_signature")
- self.gridLayout_3.addWidget(self.hg_signature, 9, 1, 1, 1)
- self.label_30 = QtWidgets.QLabel(parent=self.hg)
- self.label_30.setObjectName("label_30")
- self.gridLayout_3.addWidget(self.label_30, 10, 0, 1, 1)
- self.label_25 = QtWidgets.QLabel(parent=self.hg)
- self.label_25.setObjectName("label_25")
- self.gridLayout_3.addWidget(self.label_25, 6, 0, 1, 1)
- self.hg_year = QtWidgets.QLineEdit(parent=self.hg)
- self.hg_year.setObjectName("hg_year")
- self.gridLayout_3.addWidget(self.hg_year, 1, 1, 1, 1)
- self.label_22 = QtWidgets.QLabel(parent=self.hg)
- self.label_22.setObjectName("label_22")
- self.gridLayout_3.addWidget(self.label_22, 4, 0, 1, 1)
- self.hg_title = QtWidgets.QLineEdit(parent=self.hg)
- self.hg_title.setObjectName("hg_title")
- self.gridLayout_3.addWidget(self.hg_title, 5, 1, 1, 1)
- self.hg_chaptertitle = QtWidgets.QLineEdit(parent=self.hg)
- self.hg_chaptertitle.setObjectName("hg_chaptertitle")
- self.gridLayout_3.addWidget(self.hg_chaptertitle, 3, 1, 1, 1)
- self.hg_author = QtWidgets.QLineEdit(parent=self.hg)
- self.hg_author.setObjectName("hg_author")
- self.gridLayout_3.addWidget(self.hg_author, 0, 1, 1, 1)
- self.hg_isbn = QtWidgets.QLineEdit(parent=self.hg)
- self.hg_isbn.setObjectName("hg_isbn")
- self.gridLayout_3.addWidget(self.hg_isbn, 10, 1, 1, 1)
- self.hg_publisher = QtWidgets.QLineEdit(parent=self.hg)
- self.hg_publisher.setObjectName("hg_publisher")
- self.gridLayout_3.addWidget(self.hg_publisher, 7, 1, 1, 1)
- self.hg_place = QtWidgets.QLineEdit(parent=self.hg)
- self.hg_place.setObjectName("hg_place")
- self.gridLayout_3.addWidget(self.hg_place, 6, 1, 1, 1)
- self.page_warn_3 = QtWidgets.QToolButton(parent=self.hg)
- self.page_warn_3.setText("")
- self.page_warn_3.setAutoRaise(True)
- self.page_warn_3.setObjectName("page_warn_3")
- self.gridLayout_3.addWidget(self.page_warn_3, 8, 2, 1, 1)
- self.stackedWidget.addWidget(self.hg)
- self.zs = QtWidgets.QWidget()
- self.zs.setObjectName("zs")
- self.gridLayout = QtWidgets.QGridLayout(self.zs)
- self.gridLayout.setObjectName("gridLayout")
- self.label_10 = QtWidgets.QLabel(parent=self.zs)
- self.label_10.setObjectName("label_10")
- self.gridLayout.addWidget(self.label_10, 0, 0, 1, 1)
- self.zs_publisher = QtWidgets.QLineEdit(parent=self.zs)
- self.zs_publisher.setObjectName("zs_publisher")
- self.gridLayout.addWidget(self.zs_publisher, 6, 1, 1, 1)
- self.zs_place = QtWidgets.QLineEdit(parent=self.zs)
- self.zs_place.setObjectName("zs_place")
- self.gridLayout.addWidget(self.zs_place, 5, 1, 1, 1)
- self.label_14 = QtWidgets.QLabel(parent=self.zs)
- self.label_14.setObjectName("label_14")
- self.gridLayout.addWidget(self.label_14, 4, 0, 1, 1)
- self.label_11 = QtWidgets.QLabel(parent=self.zs)
- self.label_11.setObjectName("label_11")
- self.gridLayout.addWidget(self.label_11, 1, 0, 1, 1)
- self.zs_year = QtWidgets.QLineEdit(parent=self.zs)
- self.zs_year.setObjectName("zs_year")
- self.gridLayout.addWidget(self.zs_year, 1, 1, 1, 1)
- self.label_17 = QtWidgets.QLabel(parent=self.zs)
- self.label_17.setObjectName("label_17")
- self.gridLayout.addWidget(self.label_17, 7, 0, 1, 1)
- self.label_16 = QtWidgets.QLabel(parent=self.zs)
- self.label_16.setObjectName("label_16")
- self.gridLayout.addWidget(self.label_16, 6, 0, 1, 1)
- self.zs_issue = QtWidgets.QLineEdit(parent=self.zs)
- self.zs_issue.setObjectName("zs_issue")
- self.gridLayout.addWidget(self.zs_issue, 2, 1, 1, 1)
- self.zs_chapter_title = QtWidgets.QLineEdit(parent=self.zs)
- self.zs_chapter_title.setObjectName("zs_chapter_title")
- self.gridLayout.addWidget(self.zs_chapter_title, 3, 1, 1, 1)
- self.zs_isbn = QtWidgets.QLineEdit(parent=self.zs)
- self.zs_isbn.setObjectName("zs_isbn")
- self.gridLayout.addWidget(self.zs_isbn, 9, 1, 1, 1)
- self.label_12 = QtWidgets.QLabel(parent=self.zs)
- self.label_12.setObjectName("label_12")
- self.gridLayout.addWidget(self.label_12, 2, 0, 1, 1)
- self.label_31 = QtWidgets.QLabel(parent=self.zs)
- self.label_31.setObjectName("label_31")
- self.gridLayout.addWidget(self.label_31, 9, 0, 1, 1)
- self.label_15 = QtWidgets.QLabel(parent=self.zs)
- self.label_15.setObjectName("label_15")
- self.gridLayout.addWidget(self.label_15, 5, 0, 1, 1)
- self.zs_signature = QtWidgets.QLineEdit(parent=self.zs)
- self.zs_signature.setObjectName("zs_signature")
- self.gridLayout.addWidget(self.zs_signature, 8, 1, 1, 1)
- self.zs_pages = QtWidgets.QLineEdit(parent=self.zs)
- self.zs_pages.setObjectName("zs_pages")
- self.gridLayout.addWidget(self.zs_pages, 7, 1, 1, 1)
- self.label_13 = QtWidgets.QLabel(parent=self.zs)
- self.label_13.setObjectName("label_13")
- self.gridLayout.addWidget(self.label_13, 3, 0, 1, 1)
- self.label_18 = QtWidgets.QLabel(parent=self.zs)
- self.label_18.setObjectName("label_18")
- self.gridLayout.addWidget(self.label_18, 8, 0, 1, 1)
- self.zs_author = QtWidgets.QLineEdit(parent=self.zs)
- self.zs_author.setObjectName("zs_author")
- self.gridLayout.addWidget(self.zs_author, 0, 1, 1, 1)
- self.zs_title = QtWidgets.QLineEdit(parent=self.zs)
- self.zs_title.setObjectName("zs_title")
- self.gridLayout.addWidget(self.zs_title, 4, 1, 1, 1)
- self.page_warn = QtWidgets.QToolButton(parent=self.zs)
- self.page_warn.setText("")
- self.page_warn.setAutoRaise(True)
- self.page_warn.setObjectName("page_warn")
- self.gridLayout.addWidget(self.page_warn, 7, 2, 1, 1)
- self.stackedWidget.addWidget(self.zs)
- self.page = QtWidgets.QWidget()
- self.page.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight)
- self.page.setObjectName("page")
- self.gridLayout_5 = QtWidgets.QGridLayout(self.page)
- self.gridLayout_5.setObjectName("gridLayout_5")
- self.label_32 = QtWidgets.QLabel(parent=self.page)
- self.label_32.setObjectName("label_32")
- self.gridLayout_5.addWidget(self.label_32, 0, 0, 1, 1)
- spacerItem2 = QtWidgets.QSpacerItem(
- 20,
- 40,
- QtWidgets.QSizePolicy.Policy.Minimum,
- QtWidgets.QSizePolicy.Policy.Expanding,
- )
- self.gridLayout_5.addItem(spacerItem2, 7, 0, 1, 1)
- self.file_desc_edit = QtWidgets.QTextEdit(parent=self.page)
- self.file_desc_edit.setReadOnly(True)
- self.file_desc_edit.setObjectName("file_desc_edit")
- self.gridLayout_5.addWidget(self.file_desc_edit, 6, 0, 1, 1)
- self.label_34 = QtWidgets.QLabel(parent=self.page)
- self.label_34.setObjectName("label_34")
- self.gridLayout_5.addWidget(self.label_34, 3, 0, 1, 1)
- self.filename_edit = QtWidgets.QTextEdit(parent=self.page)
- self.filename_edit.setReadOnly(True)
- self.filename_edit.setObjectName("filename_edit")
- self.gridLayout_5.addWidget(self.filename_edit, 1, 0, 1, 1)
- self.label_33 = QtWidgets.QLabel(parent=self.page)
- self.label_33.setObjectName("label_33")
- self.gridLayout_5.addWidget(self.label_33, 5, 0, 1, 1)
- self.ilias_filename = QtWidgets.QTextEdit(parent=self.page)
- self.ilias_filename.setReadOnly(True)
- self.ilias_filename.setObjectName("ilias_filename")
- self.gridLayout_5.addWidget(self.ilias_filename, 4, 0, 1, 1)
- self.verticalLayout_2 = QtWidgets.QVBoxLayout()
- self.verticalLayout_2.setObjectName("verticalLayout_2")
- spacerItem3 = QtWidgets.QSpacerItem(
- 20,
- 40,
- QtWidgets.QSizePolicy.Policy.Minimum,
- QtWidgets.QSizePolicy.Policy.Expanding,
- )
- self.verticalLayout_2.addItem(spacerItem3)
- self.copy_filename = QtWidgets.QToolButton(parent=self.page)
- self.copy_filename.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight)
- self.copy_filename.setAutoFillBackground(False)
- self.copy_filename.setObjectName("copy_filename")
- self.verticalLayout_2.addWidget(self.copy_filename)
- self.filename_edit_label = QtWidgets.QLabel(parent=self.page)
- self.filename_edit_label.setText("")
- self.filename_edit_label.setObjectName("filename_edit_label")
- self.verticalLayout_2.addWidget(self.filename_edit_label)
- spacerItem4 = QtWidgets.QSpacerItem(
- 20,
- 40,
- QtWidgets.QSizePolicy.Policy.Minimum,
- QtWidgets.QSizePolicy.Policy.Expanding,
- )
- self.verticalLayout_2.addItem(spacerItem4)
- self.gridLayout_5.addLayout(self.verticalLayout_2, 1, 1, 1, 1)
- self.verticalLayout_3 = QtWidgets.QVBoxLayout()
- self.verticalLayout_3.setObjectName("verticalLayout_3")
- spacerItem5 = QtWidgets.QSpacerItem(
- 20,
- 40,
- QtWidgets.QSizePolicy.Policy.Minimum,
- QtWidgets.QSizePolicy.Policy.Expanding,
- )
- self.verticalLayout_3.addItem(spacerItem5)
- self.copy_ilias_filename = QtWidgets.QToolButton(parent=self.page)
- self.copy_ilias_filename.setObjectName("copy_ilias_filename")
- self.verticalLayout_3.addWidget(self.copy_ilias_filename)
- self.ilias_filename_label = QtWidgets.QLabel(parent=self.page)
- self.ilias_filename_label.setText("")
- self.ilias_filename_label.setObjectName("ilias_filename_label")
- self.verticalLayout_3.addWidget(self.ilias_filename_label)
- spacerItem6 = QtWidgets.QSpacerItem(
- 20,
- 40,
- QtWidgets.QSizePolicy.Policy.Minimum,
- QtWidgets.QSizePolicy.Policy.Expanding,
- )
- self.verticalLayout_3.addItem(spacerItem6)
- self.gridLayout_5.addLayout(self.verticalLayout_3, 4, 1, 1, 1)
- self.verticalLayout_4 = QtWidgets.QVBoxLayout()
- self.verticalLayout_4.setObjectName("verticalLayout_4")
- spacerItem7 = QtWidgets.QSpacerItem(
- 20,
- 40,
- QtWidgets.QSizePolicy.Policy.Minimum,
- QtWidgets.QSizePolicy.Policy.Expanding,
- )
- self.verticalLayout_4.addItem(spacerItem7)
- self.copy_qoute = QtWidgets.QToolButton(parent=self.page)
- self.copy_qoute.setObjectName("copy_qoute")
- self.verticalLayout_4.addWidget(self.copy_qoute)
- self.file_desc_edit_label = QtWidgets.QLabel(parent=self.page)
- self.file_desc_edit_label.setText("")
- self.file_desc_edit_label.setObjectName("file_desc_edit_label")
- self.verticalLayout_4.addWidget(self.file_desc_edit_label)
- spacerItem8 = QtWidgets.QSpacerItem(
- 20,
- 40,
- QtWidgets.QSizePolicy.Policy.Minimum,
- QtWidgets.QSizePolicy.Policy.Expanding,
- )
- self.verticalLayout_4.addItem(spacerItem8)
- self.gridLayout_5.addLayout(self.verticalLayout_4, 6, 1, 1, 1)
- self.stackedWidget.addWidget(self.page)
- self.verticalLayout.addWidget(self.stackedWidget)
- self.horizontalLayout = QtWidgets.QHBoxLayout()
- self.horizontalLayout.setObjectName("horizontalLayout")
- self.buttonBox = QtWidgets.QDialogButtonBox(parent=Dialog)
- self.buttonBox.setStandardButtons(
- QtWidgets.QDialogButtonBox.StandardButton.Cancel
- | QtWidgets.QDialogButtonBox.StandardButton.Discard
- | QtWidgets.QDialogButtonBox.StandardButton.Ok
- )
- self.buttonBox.setObjectName("buttonBox")
- self.horizontalLayout.addWidget(self.buttonBox)
- self.retryButton = QtWidgets.QPushButton(parent=Dialog)
- self.retryButton.setObjectName("retryButton")
- self.horizontalLayout.addWidget(self.retryButton)
- self.verticalLayout.addLayout(self.horizontalLayout)
-
- self.retranslateUi(Dialog)
- self.stackedWidget.setCurrentIndex(3)
- QtCore.QMetaObject.connectSlotsByName(Dialog)
-
- def retranslateUi(self, Dialog):
- _translate = QtCore.QCoreApplication.translate
- Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
- self.groupBox.setTitle(_translate("Dialog", "Medientyp?"))
- self.btn_mono.setText(_translate("Dialog", "Monografie"))
- self.btn_zs.setText(_translate("Dialog", "Zeitschrift"))
- self.btn_hg.setText(_translate("Dialog", "Herausgeberwerk"))
- self.label_2.setText(_translate("Dialog", "Identifikator"))
- self.btn_search.setText(_translate("Dialog", "Suchen"))
- self.make_quote.setToolTip(
- _translate("Dialog", "Zuerst die Seitenzahl anpassen")
- )
- self.make_quote.setText(_translate("Dialog", "Zitat erstellen"))
- self.label.setText(_translate("Dialog", "Autor(en)\n Nachname, Vorname"))
- self.book_author.setToolTip(
- _translate("Dialog", "Bei mehreren Autoren mit ; trennen")
- )
- self.label_3.setText(_translate("Dialog", "Jahr"))
- self.label_4.setText(_translate("Dialog", "Auflage"))
- self.label_5.setText(_translate("Dialog", "Titel"))
- self.label_6.setText(_translate("Dialog", "Ort"))
- self.label_7.setText(_translate("Dialog", "Verlag"))
- self.label_8.setText(_translate("Dialog", "Signatur"))
- self.label_9.setText(_translate("Dialog", "Seiten"))
- self.book_pages.setPlaceholderText(
- _translate("Dialog", "Seitenanzahl des Mediums, zum zitieren ändern!")
- )
- self.label_29.setText(_translate("Dialog", "ISBN"))
- self.hg_editor.setToolTip(
- _translate("Dialog", "Bei mehreren Autoren mit ; trennen")
- )
- self.label_26.setText(_translate("Dialog", "Verlag"))
- self.label_20.setText(_translate("Dialog", "Jahr"))
- self.label_24.setText(_translate("Dialog", "Beitragstitel"))
- self.label_27.setText(_translate("Dialog", "Seiten"))
- self.label_28.setText(_translate("Dialog", "Signatur"))
- self.label_23.setText(_translate("Dialog", "Titel des Werkes"))
- self.label_21.setText(_translate("Dialog", "Auflage"))
- self.label_19.setText(_translate("Dialog", "Autor(en)\nNachname, Vorname"))
- self.label_30.setText(_translate("Dialog", "ISBN"))
- self.label_25.setText(_translate("Dialog", "Ort"))
- self.label_22.setText(
- _translate("Dialog", "Herausgebername(n)\nNachname, Vorname")
- )
- self.hg_author.setToolTip(
- _translate("Dialog", "Bei mehreren Autoren mit ; trennen")
- )
- self.label_10.setText(_translate("Dialog", "Autor(en)\nNachname, Vorname"))
- self.label_14.setText(_translate("Dialog", "Name der Zeitschrift"))
- self.label_11.setText(_translate("Dialog", "Jahr"))
- self.label_17.setText(_translate("Dialog", "Seiten"))
- self.label_16.setText(_translate("Dialog", "Verlag"))
- self.label_12.setText(_translate("Dialog", "Heft"))
- self.label_31.setText(_translate("Dialog", "ISSN"))
- self.label_15.setText(_translate("Dialog", "Ort"))
- self.label_13.setText(_translate("Dialog", "Artikeltitel"))
- self.label_18.setText(_translate("Dialog", "Signatur"))
- self.zs_author.setToolTip(
- _translate("Dialog", "Bei mehreren Autoren mit ; trennen")
- )
- self.label_32.setText(_translate("Dialog", "Dateiname"))
- self.label_34.setText(_translate("Dialog", "ILIAS Name"))
- self.label_33.setText(_translate("Dialog", "ILIAS Dateibeschreibung"))
- self.copy_filename.setText(_translate("Dialog", "Kopieren"))
- self.copy_ilias_filename.setText(_translate("Dialog", "Kopieren"))
- self.copy_qoute.setText(_translate("Dialog", "Kopieren"))
- self.retryButton.setText(_translate("Dialog", "Wiederholen"))
diff --git a/src/ui/dialogs/dialog_sources/Ui_elsa_generate_citation.py b/src/ui/dialogs/dialog_sources/Ui_elsa_generate_citation.py
deleted file mode 100644
index 43944f4..0000000
--- a/src/ui/dialogs/dialog_sources/Ui_elsa_generate_citation.py
+++ /dev/null
@@ -1,83 +0,0 @@
-# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\elsa_generate_citation.ui'
-#
-# Created by: PySide6 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
-
-
-class Ui_Dialog(object):
- def setupUi(self, Dialog):
- Dialog.setObjectName("Dialog")
- Dialog.resize(564, 517)
- self.verticalLayout_2 = QtWidgets.QVBoxLayout(Dialog)
- self.verticalLayout_2.setObjectName("verticalLayout_2")
- self.select_type = QtWidgets.QFrame(parent=Dialog)
- self.select_type.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel)
- self.select_type.setFrameShadow(QtWidgets.QFrame.Shadow.Raised)
- self.select_type.setObjectName("select_type")
- self.verticalLayout = QtWidgets.QVBoxLayout(self.select_type)
- self.verticalLayout.setObjectName("verticalLayout")
- self.radio_mono = QtWidgets.QRadioButton(parent=self.select_type)
- self.radio_mono.setObjectName("radio_mono")
- self.verticalLayout.addWidget(self.radio_mono)
- self.radio_zs = QtWidgets.QRadioButton(parent=self.select_type)
- self.radio_zs.setObjectName("radio_zs")
- self.verticalLayout.addWidget(self.radio_zs)
- self.radio_hg = QtWidgets.QRadioButton(parent=self.select_type)
- self.radio_hg.setObjectName("radio_hg")
- self.verticalLayout.addWidget(self.radio_hg)
- spacerItem = QtWidgets.QSpacerItem(
- 20,
- 40,
- QtWidgets.QSizePolicy.Policy.Minimum,
- QtWidgets.QSizePolicy.Policy.Expanding,
- )
- self.verticalLayout.addItem(spacerItem)
- self.verticalLayout_2.addWidget(self.select_type)
- self.check = QtWidgets.QGroupBox(parent=Dialog)
- font = QtGui.QFont()
- font.setBold(True)
- self.check.setFont(font)
- self.check.setObjectName("check")
- self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.check)
- self.verticalLayout_3.setObjectName("verticalLayout_3")
- self.citation_style_result = QtWidgets.QStackedWidget(parent=self.check)
- font = QtGui.QFont()
- font.setBold(False)
- self.citation_style_result.setFont(font)
- self.citation_style_result.setObjectName("citation_style_result")
- self.monografie = QtWidgets.QWidget()
- self.monografie.setObjectName("monografie")
- self.citation_style_result.addWidget(self.monografie)
- self.zsaufsatz = QtWidgets.QWidget()
- self.zsaufsatz.setObjectName("zsaufsatz")
- self.citation_style_result.addWidget(self.zsaufsatz)
- self.herausgeberwerk = QtWidgets.QWidget()
- self.herausgeberwerk.setObjectName("herausgeberwerk")
- self.citation_style_result.addWidget(self.herausgeberwerk)
- self.verticalLayout_3.addWidget(self.citation_style_result)
- self.pushButton = QtWidgets.QPushButton(parent=self.check)
- self.pushButton.setObjectName("pushButton")
- self.verticalLayout_3.addWidget(
- self.pushButton, 0, QtCore.Qt.AlignmentFlag.AlignRight
- )
- self.verticalLayout_2.addWidget(self.check)
- self.verticalLayout_2.setStretch(0, 20)
- self.verticalLayout_2.setStretch(1, 80)
-
- self.retranslateUi(Dialog)
- self.citation_style_result.setCurrentIndex(2)
- QtCore.QMetaObject.connectSlotsByName(Dialog)
-
- def retranslateUi(self, Dialog):
- _translate = QtCore.QCoreApplication.translate
- Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
- self.radio_mono.setText(_translate("Dialog", "Monografie"))
- self.radio_zs.setText(_translate("Dialog", "Zeitschriftenaufsatz"))
- self.radio_hg.setText(_translate("Dialog", "Herausgeberwerk"))
- self.check.setTitle(_translate("Dialog", "Daten"))
- self.pushButton.setText(_translate("Dialog", "Bestätigen"))
diff --git a/src/ui/dialogs/dialog_sources/Ui_elsa_generator_confirm.py b/src/ui/dialogs/dialog_sources/Ui_elsa_generator_confirm.py
deleted file mode 100644
index c56c6f3..0000000
--- a/src/ui/dialogs/dialog_sources/Ui_elsa_generator_confirm.py
+++ /dev/null
@@ -1,129 +0,0 @@
-# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\elsa_generator_confirm.ui'
-#
-# Created by: PySide6 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
-
-
-class Ui_Dialog(object):
- def setupUi(self, Dialog):
- Dialog.setObjectName("Dialog")
- Dialog.resize(530, 210)
- sizePolicy = QtWidgets.QSizePolicy(
- QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed
- )
- sizePolicy.setHorizontalStretch(0)
- sizePolicy.setVerticalStretch(0)
- sizePolicy.setHeightForWidth(Dialog.sizePolicy().hasHeightForWidth())
- Dialog.setSizePolicy(sizePolicy)
- Dialog.setMaximumSize(QtCore.QSize(530, 210))
- self.horizontalLayout = QtWidgets.QHBoxLayout(Dialog)
- self.horizontalLayout.setObjectName("horizontalLayout")
- self.groupBox = QtWidgets.QGroupBox(parent=Dialog)
- font = QtGui.QFont()
- font.setBold(True)
- self.groupBox.setFont(font)
- self.groupBox.setObjectName("groupBox")
- self.gridLayout = QtWidgets.QGridLayout(self.groupBox)
- self.gridLayout.setObjectName("gridLayout")
- self.label = QtWidgets.QLabel(parent=self.groupBox)
- font = QtGui.QFont()
- font.setBold(False)
- self.label.setFont(font)
- self.label.setObjectName("label")
- self.gridLayout.addWidget(self.label, 1, 0, 1, 1)
- self.bookauthor = QtWidgets.QLineEdit(parent=self.groupBox)
- font = QtGui.QFont()
- font.setBold(False)
- self.bookauthor.setFont(font)
- self.bookauthor.setObjectName("bookauthor")
- self.gridLayout.addWidget(self.bookauthor, 5, 1, 1, 1)
- self.book_title = QtWidgets.QLineEdit(parent=self.groupBox)
- font = QtGui.QFont()
- font.setBold(False)
- self.book_title.setFont(font)
- self.book_title.setObjectName("book_title")
- self.gridLayout.addWidget(self.book_title, 3, 1, 1, 1)
- self.label_5 = QtWidgets.QLabel(parent=self.groupBox)
- font = QtGui.QFont()
- font.setBold(False)
- self.label_5.setFont(font)
- self.label_5.setObjectName("label_5")
- self.gridLayout.addWidget(self.label_5, 5, 0, 1, 1)
- self.pages = QtWidgets.QLineEdit(parent=self.groupBox)
- font = QtGui.QFont()
- font.setBold(False)
- self.pages.setFont(font)
- self.pages.setObjectName("pages")
- self.gridLayout.addWidget(self.pages, 4, 1, 1, 1)
- self.label_2 = QtWidgets.QLabel(parent=self.groupBox)
- font = QtGui.QFont()
- font.setBold(False)
- self.label_2.setFont(font)
- self.label_2.setObjectName("label_2")
- self.gridLayout.addWidget(self.label_2, 2, 0, 1, 1)
- self.label_3 = QtWidgets.QLabel(parent=self.groupBox)
- font = QtGui.QFont()
- font.setBold(False)
- self.label_3.setFont(font)
- self.label_3.setObjectName("label_3")
- self.gridLayout.addWidget(self.label_3, 3, 0, 1, 1)
- self.label_4 = QtWidgets.QLabel(parent=self.groupBox)
- font = QtGui.QFont()
- font.setBold(False)
- self.label_4.setFont(font)
- self.label_4.setObjectName("label_4")
- self.gridLayout.addWidget(self.label_4, 4, 0, 1, 1)
- self.chapter_title = QtWidgets.QLineEdit(parent=self.groupBox)
- font = QtGui.QFont()
- font.setBold(False)
- self.chapter_title.setFont(font)
- self.chapter_title.setObjectName("chapter_title")
- self.gridLayout.addWidget(self.chapter_title, 1, 1, 1, 1)
- self.chapter_authors = QtWidgets.QLineEdit(parent=self.groupBox)
- font = QtGui.QFont()
- font.setBold(False)
- self.chapter_authors.setFont(font)
- self.chapter_authors.setObjectName("chapter_authors")
- self.gridLayout.addWidget(self.chapter_authors, 2, 1, 1, 1)
- self.label_6 = QtWidgets.QLabel(parent=self.groupBox)
- font = QtGui.QFont()
- font.setBold(False)
- self.label_6.setFont(font)
- self.label_6.setObjectName("label_6")
- self.gridLayout.addWidget(self.label_6, 0, 1, 1, 1)
- self.horizontalLayout.addWidget(self.groupBox)
- self.buttonBox = QtWidgets.QDialogButtonBox(parent=Dialog)
- self.buttonBox.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight)
- self.buttonBox.setOrientation(QtCore.Qt.Orientation.Vertical)
- self.buttonBox.setStandardButtons(
- QtWidgets.QDialogButtonBox.StandardButton.Cancel
- | QtWidgets.QDialogButtonBox.StandardButton.Ok
- )
- self.buttonBox.setCenterButtons(False)
- self.buttonBox.setObjectName("buttonBox")
- self.horizontalLayout.addWidget(self.buttonBox)
-
- self.retranslateUi(Dialog)
- self.buttonBox.accepted.connect(Dialog.accept) # type: ignore
- self.buttonBox.rejected.connect(Dialog.reject) # type: ignore
- QtCore.QMetaObject.connectSlotsByName(Dialog)
-
- def retranslateUi(self, Dialog):
- _translate = QtCore.QCoreApplication.translate
- Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
- self.groupBox.setTitle(_translate("Dialog", "Angaben korrekt?"))
- self.label.setText(_translate("Dialog", "Kapiteltitel"))
- self.label_5.setText(_translate("Dialog", "Herausgebername"))
- self.label_2.setText(_translate("Dialog", "Autor(en)"))
- self.label_3.setText(_translate("Dialog", "Buchtitel"))
- self.label_4.setText(_translate("Dialog", "Seite(n)"))
- self.label_6.setText(
- _translate(
- "Dialog", "Hier können fehlerhafte / fehlende Daten geändert werden"
- )
- )
diff --git a/src/ui/dialogs/dialog_sources/Ui_login.py b/src/ui/dialogs/dialog_sources/Ui_login.py
deleted file mode 100644
index deb1a62..0000000
--- a/src/ui/dialogs/dialog_sources/Ui_login.py
+++ /dev/null
@@ -1,53 +0,0 @@
-# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\login.ui'
-#
-# Created by: PySide6 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
-
-
-class Ui_Dialog(object):
- def setupUi(self, Dialog):
- Dialog.setObjectName("Dialog")
- Dialog.resize(218, 190)
- icon = QtGui.QIcon()
- icon.addPixmap(
- QtGui.QPixmap(":/icons/resources/1f510.svg"),
- QtGui.QIcon.Mode.Normal,
- QtGui.QIcon.State.Off,
- )
- Dialog.setWindowIcon(icon)
- self.label = QtWidgets.QLabel(parent=Dialog)
- self.label.setGeometry(QtCore.QRect(20, 40, 71, 21))
- self.label.setObjectName("label")
- self.lineEdit = QtWidgets.QLineEdit(parent=Dialog)
- self.lineEdit.setGeometry(QtCore.QRect(80, 40, 113, 21))
- self.lineEdit.setObjectName("lineEdit")
- self.label_2 = QtWidgets.QLabel(parent=Dialog)
- self.label_2.setGeometry(QtCore.QRect(20, 80, 71, 21))
- self.label_2.setObjectName("label_2")
- self.lineEdit_2 = QtWidgets.QLineEdit(parent=Dialog)
- self.lineEdit_2.setGeometry(QtCore.QRect(80, 80, 113, 21))
- self.lineEdit_2.setInputMethodHints(QtCore.Qt.InputMethodHint.ImhSensitiveData)
- self.lineEdit_2.setClearButtonEnabled(True)
- self.lineEdit_2.setObjectName("lineEdit_2")
- self.login_button = QtWidgets.QPushButton(parent=Dialog)
- self.login_button.setGeometry(QtCore.QRect(30, 140, 76, 32))
- self.login_button.setObjectName("login_button")
- self.cancel_button = QtWidgets.QPushButton(parent=Dialog)
- self.cancel_button.setGeometry(QtCore.QRect(120, 140, 76, 32))
- self.cancel_button.setObjectName("cancel_button")
-
- self.retranslateUi(Dialog)
- QtCore.QMetaObject.connectSlotsByName(Dialog)
-
- def retranslateUi(self, Dialog):
- _translate = QtCore.QCoreApplication.translate
- Dialog.setWindowTitle(_translate("Dialog", "Login"))
- self.label.setText(_translate("Dialog", "Username"))
- self.label_2.setText(_translate("Dialog", "Password"))
- self.login_button.setText(_translate("Dialog", "Login"))
- self.cancel_button.setText(_translate("Dialog", "Cancel"))
diff --git a/src/ui/dialogs/dialog_sources/Ui_mail_preview.py b/src/ui/dialogs/dialog_sources/Ui_mail_preview.py
deleted file mode 100644
index e852ebf..0000000
--- a/src/ui/dialogs/dialog_sources/Ui_mail_preview.py
+++ /dev/null
@@ -1,137 +0,0 @@
-# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\mail_preview.ui'
-#
-# Created by: PySide6 UI code generator 6.7.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
-
-
-class Ui_eMailPreview(object):
- def setupUi(self, eMailPreview):
- eMailPreview.setObjectName("eMailPreview")
- eMailPreview.resize(700, 668)
- icon = QtGui.QIcon()
- icon.addPixmap(
- QtGui.QPixmap(
- "c:\\Users\\aky547\\GitHub\\SemesterapparatsManager\\src\\ui\\dialogs\\dialog_sources\\../../../../../../icons/email.svg"
- ),
- QtGui.QIcon.Mode.Normal,
- QtGui.QIcon.State.Off,
- )
- eMailPreview.setWindowIcon(icon)
- self.gridLayout_2 = QtWidgets.QGridLayout(eMailPreview)
- self.gridLayout_2.setObjectName("gridLayout_2")
- self.gridLayout = QtWidgets.QGridLayout()
- self.gridLayout.setObjectName("gridLayout")
- self.prof_name = QtWidgets.QLineEdit(parent=eMailPreview)
- self.prof_name.setObjectName("prof_name")
- self.gridLayout.addWidget(self.prof_name, 2, 2, 1, 1)
- self.newTemplate = QtWidgets.QPushButton(parent=eMailPreview)
- self.newTemplate.setAutoFillBackground(False)
- self.newTemplate.setText("")
- self.newTemplate.setIconSize(QtCore.QSize(24, 24))
- self.newTemplate.setAutoDefault(True)
- self.newTemplate.setDefault(False)
- self.newTemplate.setFlat(False)
- self.newTemplate.setObjectName("newTemplate")
- self.gridLayout.addWidget(self.newTemplate, 0, 3, 1, 1)
- self.comboBox = QtWidgets.QComboBox(parent=eMailPreview)
- self.comboBox.setObjectName("comboBox")
- self.gridLayout.addWidget(self.comboBox, 0, 2, 1, 1)
- self.mail_header = QtWidgets.QLineEdit(parent=eMailPreview)
- self.mail_header.setObjectName("mail_header")
- self.gridLayout.addWidget(self.mail_header, 3, 2, 1, 1)
- self.label_6 = QtWidgets.QLabel(parent=eMailPreview)
- self.label_6.setObjectName("label_6")
- self.gridLayout.addWidget(self.label_6, 4, 0, 1, 1)
- self.mail_body = QtWidgets.QTextEdit(parent=eMailPreview)
- self.mail_body.setObjectName("mail_body")
- self.gridLayout.addWidget(self.mail_body, 5, 2, 1, 1)
- self.label_2 = QtWidgets.QLabel(parent=eMailPreview)
- self.label_2.setObjectName("label_2")
- self.gridLayout.addWidget(self.label_2, 2, 0, 1, 1)
- self.mail_name = QtWidgets.QLineEdit(parent=eMailPreview)
- self.mail_name.setObjectName("mail_name")
- self.gridLayout.addWidget(self.mail_name, 1, 2, 1, 1)
- self.label_5 = QtWidgets.QLabel(parent=eMailPreview)
- self.label_5.setObjectName("label_5")
- self.gridLayout.addWidget(self.label_5, 0, 0, 1, 1)
- self.label_4 = QtWidgets.QLabel(parent=eMailPreview)
- self.label_4.setObjectName("label_4")
- self.gridLayout.addWidget(self.label_4, 3, 0, 1, 1)
- self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
- self.horizontalLayout_3.setObjectName("horizontalLayout_3")
- self.gender_male = QtWidgets.QRadioButton(parent=eMailPreview)
- self.gender_male.setObjectName("gender_male")
- self.horizontalLayout_3.addWidget(self.gender_male)
- self.gender_female = QtWidgets.QRadioButton(parent=eMailPreview)
- self.gender_female.setObjectName("gender_female")
- self.horizontalLayout_3.addWidget(self.gender_female)
- self.gender_non = QtWidgets.QRadioButton(parent=eMailPreview)
- self.gender_non.setObjectName("gender_non")
- self.horizontalLayout_3.addWidget(self.gender_non)
- spacerItem = QtWidgets.QSpacerItem(
- 40,
- 20,
- QtWidgets.QSizePolicy.Policy.Expanding,
- QtWidgets.QSizePolicy.Policy.Minimum,
- )
- self.horizontalLayout_3.addItem(spacerItem)
- self.gridLayout.addLayout(self.horizontalLayout_3, 4, 2, 1, 1)
- self.label_3 = QtWidgets.QLabel(parent=eMailPreview)
- self.label_3.setAlignment(
- QtCore.Qt.AlignmentFlag.AlignLeading
- | QtCore.Qt.AlignmentFlag.AlignLeft
- | QtCore.Qt.AlignmentFlag.AlignTop
- )
- self.label_3.setObjectName("label_3")
- self.gridLayout.addWidget(self.label_3, 5, 0, 1, 1)
- self.label = QtWidgets.QLabel(parent=eMailPreview)
- self.label.setObjectName("label")
- self.gridLayout.addWidget(self.label, 1, 0, 1, 1)
- self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
- self.horizontalLayout_2.setObjectName("horizontalLayout_2")
- spacerItem1 = QtWidgets.QSpacerItem(
- 40,
- 20,
- QtWidgets.QSizePolicy.Policy.Expanding,
- QtWidgets.QSizePolicy.Policy.Minimum,
- )
- self.horizontalLayout_2.addItem(spacerItem1)
- self.btn_okay = QtWidgets.QPushButton(parent=eMailPreview)
- self.btn_okay.setStatusTip("")
- self.btn_okay.setObjectName("btn_okay")
- self.horizontalLayout_2.addWidget(self.btn_okay)
- self.buttonBox = QtWidgets.QDialogButtonBox(parent=eMailPreview)
- self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal)
- self.buttonBox.setStandardButtons(
- QtWidgets.QDialogButtonBox.StandardButton.Cancel
- )
- self.buttonBox.setCenterButtons(True)
- self.buttonBox.setObjectName("buttonBox")
- self.horizontalLayout_2.addWidget(self.buttonBox)
- self.gridLayout.addLayout(self.horizontalLayout_2, 6, 2, 1, 1)
- self.gridLayout_2.addLayout(self.gridLayout, 0, 0, 1, 1)
-
- self.retranslateUi(eMailPreview)
- self.buttonBox.accepted.connect(eMailPreview.accept) # type: ignore
- self.buttonBox.rejected.connect(eMailPreview.reject) # type: ignore
- QtCore.QMetaObject.connectSlotsByName(eMailPreview)
-
- def retranslateUi(self, eMailPreview):
- _translate = QtCore.QCoreApplication.translate
- eMailPreview.setWindowTitle(_translate("eMailPreview", "eMail Voransicht"))
- self.label_6.setText(_translate("eMailPreview", "Anrede"))
- self.label_2.setText(_translate("eMailPreview", "Prof"))
- self.label_5.setText(_translate("eMailPreview", "Art"))
- self.label_4.setText(_translate("eMailPreview", "Betreff"))
- self.gender_male.setText(_translate("eMailPreview", "M"))
- self.gender_female.setText(_translate("eMailPreview", "W"))
- self.gender_non.setText(_translate("eMailPreview", "Divers"))
- self.label_3.setText(_translate("eMailPreview", "Mail"))
- self.label.setText(_translate("eMailPreview", "eMail"))
- self.btn_okay.setWhatsThis(_translate("eMailPreview", "test"))
- self.btn_okay.setText(_translate("eMailPreview", "Senden"))
diff --git a/src/ui/dialogs/dialog_sources/Ui_medianadder.py b/src/ui/dialogs/dialog_sources/Ui_medianadder.py
deleted file mode 100644
index db20569..0000000
--- a/src/ui/dialogs/dialog_sources/Ui_medianadder.py
+++ /dev/null
@@ -1,385 +0,0 @@
-# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\medianadder.ui'
-#
-# Created by: PySide6 UI code generator 6.7.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
-
-
-class Ui_Dialog(object):
- def setupUi(self, Dialog):
- Dialog.setObjectName("Dialog")
- Dialog.resize(620, 481)
- icon = QtGui.QIcon()
- icon.addPixmap(
- QtGui.QPixmap(":/icons/resources/2795.svg"),
- QtGui.QIcon.Mode.Normal,
- QtGui.QIcon.State.Off,
- )
- Dialog.setWindowIcon(icon)
- self.label = QtWidgets.QLabel(parent=Dialog)
- self.label.setGeometry(QtCore.QRect(20, 10, 47, 21))
- self.label.setObjectName("label")
- self.label_2 = QtWidgets.QLabel(parent=Dialog)
- self.label_2.setGeometry(QtCore.QRect(20, 40, 47, 21))
- self.label_2.setObjectName("label_2")
- self.comboBox = QtWidgets.QComboBox(parent=Dialog)
- self.comboBox.setGeometry(QtCore.QRect(70, 40, 69, 22))
- self.comboBox.setObjectName("comboBox")
- self.comboBox.addItem("")
- self.comboBox.addItem("")
- self.comboBox.addItem("")
- self.comboBox.addItem("")
- self.lineEdit = QtWidgets.QLineEdit(parent=Dialog)
- self.lineEdit.setGeometry(QtCore.QRect(70, 10, 113, 20))
- self.lineEdit.setObjectName("lineEdit")
- self.label_3 = QtWidgets.QLabel(parent=Dialog)
- self.label_3.setGeometry(QtCore.QRect(20, 90, 47, 21))
- self.label_3.setObjectName("label_3")
- self.widget = QtWidgets.QWidget(parent=Dialog)
- self.widget.setGeometry(QtCore.QRect(330, 90, 281, 381))
- self.widget.setObjectName("widget")
- self.horizontalLayout = QtWidgets.QHBoxLayout(self.widget)
- self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
- self.horizontalLayout.setObjectName("horizontalLayout")
- self.tableWidget = QtWidgets.QTableWidget(parent=self.widget)
- self.tableWidget.setEnabled(True)
- self.tableWidget.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
- self.tableWidget.setAutoFillBackground(False)
- self.tableWidget.setLineWidth(0)
- self.tableWidget.setMidLineWidth(0)
- self.tableWidget.setVerticalScrollBarPolicy(
- QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff
- )
- self.tableWidget.setHorizontalScrollBarPolicy(
- QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff
- )
- self.tableWidget.setSizeAdjustPolicy(
- QtWidgets.QAbstractScrollArea.SizeAdjustPolicy.AdjustToContents
- )
- self.tableWidget.setEditTriggers(
- QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers
- )
- self.tableWidget.setAlternatingRowColors(True)
- self.tableWidget.setSelectionMode(
- QtWidgets.QAbstractItemView.SelectionMode.NoSelection
- )
- self.tableWidget.setTextElideMode(QtCore.Qt.TextElideMode.ElideMiddle)
- self.tableWidget.setObjectName("tableWidget")
- self.tableWidget.setColumnCount(4)
- self.tableWidget.setRowCount(11)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setVerticalHeaderItem(0, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setVerticalHeaderItem(1, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setVerticalHeaderItem(2, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setVerticalHeaderItem(3, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setVerticalHeaderItem(4, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setVerticalHeaderItem(5, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setVerticalHeaderItem(6, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setVerticalHeaderItem(7, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setVerticalHeaderItem(8, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setVerticalHeaderItem(9, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setVerticalHeaderItem(10, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setHorizontalHeaderItem(0, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setHorizontalHeaderItem(1, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setHorizontalHeaderItem(2, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setHorizontalHeaderItem(3, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(0, 0, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(0, 1, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(0, 2, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(0, 3, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(1, 0, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(1, 1, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(1, 2, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(1, 3, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(2, 0, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(2, 1, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(2, 2, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(2, 3, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(3, 0, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(3, 1, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(3, 2, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(3, 3, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(4, 0, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(4, 1, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(4, 2, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(4, 3, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(5, 0, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(5, 1, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(5, 2, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(5, 3, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(6, 0, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(6, 1, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(6, 2, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(6, 3, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(7, 0, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(7, 1, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(7, 2, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(7, 3, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(8, 0, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(8, 1, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(8, 2, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(8, 3, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(9, 0, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(9, 1, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(9, 2, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(9, 3, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(10, 0, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(10, 1, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(10, 2, item)
- item = QtWidgets.QTableWidgetItem()
- self.tableWidget.setItem(10, 3, item)
- self.tableWidget.horizontalHeader().setDefaultSectionSize(45)
- self.horizontalLayout.addWidget(self.tableWidget)
- self.listWidget = QtWidgets.QListWidget(parent=Dialog)
- self.listWidget.setGeometry(QtCore.QRect(10, 110, 281, 321))
- self.listWidget.setContextMenuPolicy(
- QtCore.Qt.ContextMenuPolicy.CustomContextMenu
- )
- self.listWidget.setObjectName("listWidget")
- self.label_4 = QtWidgets.QLabel(parent=Dialog)
- self.label_4.setGeometry(QtCore.QRect(330, 50, 181, 21))
- self.label_4.setObjectName("label_4")
- self.label_5 = QtWidgets.QLabel(parent=Dialog)
- self.label_5.setGeometry(QtCore.QRect(200, 90, 41, 21))
- self.label_5.setObjectName("label_5")
- self.list_amount = QtWidgets.QLabel(parent=Dialog)
- self.list_amount.setGeometry(QtCore.QRect(240, 90, 47, 21))
- self.list_amount.setObjectName("list_amount")
- self.horizontalLayoutWidget = QtWidgets.QWidget(parent=Dialog)
- self.horizontalLayoutWidget.setGeometry(QtCore.QRect(10, 440, 160, 31))
- self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget")
- self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget)
- self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
- self.horizontalLayout_2.setObjectName("horizontalLayout_2")
- self.btn_save = QtWidgets.QPushButton(parent=self.horizontalLayoutWidget)
- self.btn_save.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
- self.btn_save.setObjectName("btn_save")
- self.horizontalLayout_2.addWidget(self.btn_save)
- self.btn_cancel = QtWidgets.QPushButton(parent=self.horizontalLayoutWidget)
- self.btn_cancel.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
- self.btn_cancel.setObjectName("btn_cancel")
- self.horizontalLayout_2.addWidget(self.btn_cancel)
- self.check_use_any_book = QtWidgets.QCheckBox(parent=Dialog)
- self.check_use_any_book.setGeometry(QtCore.QRect(20, 70, 141, 20))
- self.check_use_any_book.setObjectName("check_use_any_book")
- self.check_use_exact_signature = QtWidgets.QCheckBox(parent=Dialog)
- self.check_use_exact_signature.setGeometry(QtCore.QRect(165, 70, 121, 20))
- self.check_use_exact_signature.setObjectName("check_use_exact_signature")
-
- self.retranslateUi(Dialog)
- QtCore.QMetaObject.connectSlotsByName(Dialog)
- Dialog.setTabOrder(self.lineEdit, self.comboBox)
- Dialog.setTabOrder(self.comboBox, self.listWidget)
- Dialog.setTabOrder(self.listWidget, self.tableWidget)
-
- def retranslateUi(self, Dialog):
- _translate = QtCore.QCoreApplication.translate
- Dialog.setWindowTitle(_translate("Dialog", "Medien"))
- self.label.setText(_translate("Dialog", "Signatur"))
- self.label_2.setText(_translate("Dialog", "Modus"))
- self.comboBox.setItemText(0, _translate("Dialog", "ARRAY"))
- self.comboBox.setItemText(1, _translate("Dialog", "BibTeX"))
- self.comboBox.setItemText(2, _translate("Dialog", "COinS"))
- self.comboBox.setItemText(3, _translate("Dialog", "RIS"))
- self.lineEdit.setPlaceholderText(_translate("Dialog", "Signatur / ISBN"))
- self.label_3.setText(_translate("Dialog", "Queue"))
- item = self.tableWidget.verticalHeaderItem(0)
- item.setText(_translate("Dialog", "PPN"))
- item = self.tableWidget.verticalHeaderItem(1)
- item.setText(_translate("Dialog", "Signatur"))
- item = self.tableWidget.verticalHeaderItem(2)
- item.setText(_translate("Dialog", "Autor"))
- item = self.tableWidget.verticalHeaderItem(3)
- item.setText(_translate("Dialog", "ISBN"))
- item = self.tableWidget.verticalHeaderItem(4)
- item.setText(_translate("Dialog", "Jahr"))
- item = self.tableWidget.verticalHeaderItem(5)
- item.setText(_translate("Dialog", "Auflage"))
- item = self.tableWidget.verticalHeaderItem(6)
- item.setText(_translate("Dialog", "Sprache"))
- item = self.tableWidget.verticalHeaderItem(7)
- item.setText(_translate("Dialog", "Herausgeber"))
- item = self.tableWidget.verticalHeaderItem(8)
- item.setText(_translate("Dialog", "Seiten"))
- item = self.tableWidget.verticalHeaderItem(9)
- item.setText(_translate("Dialog", "Titel"))
- item = self.tableWidget.verticalHeaderItem(10)
- item.setText(_translate("Dialog", "Link"))
- item = self.tableWidget.horizontalHeaderItem(0)
- item.setText(_translate("Dialog", "Array"))
- item = self.tableWidget.horizontalHeaderItem(1)
- item.setText(_translate("Dialog", "BibTeX"))
- item = self.tableWidget.horizontalHeaderItem(2)
- item.setText(_translate("Dialog", "COinS"))
- item = self.tableWidget.horizontalHeaderItem(3)
- item.setText(_translate("Dialog", "RIS"))
- __sortingEnabled = self.tableWidget.isSortingEnabled()
- self.tableWidget.setSortingEnabled(False)
- item = self.tableWidget.item(0, 0)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(0, 1)
- item.setText(_translate("Dialog", "0"))
- item = self.tableWidget.item(0, 2)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(0, 3)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(1, 0)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(1, 1)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(1, 2)
- item.setText(_translate("Dialog", "0"))
- item = self.tableWidget.item(1, 3)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(2, 0)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(2, 1)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(2, 2)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(2, 3)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(3, 0)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(3, 1)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(3, 2)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(3, 3)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(4, 0)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(4, 1)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(4, 2)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(4, 3)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(5, 0)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(5, 1)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(5, 2)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(5, 3)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(6, 0)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(6, 1)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(6, 2)
- item.setText(_translate("Dialog", "0"))
- item = self.tableWidget.item(6, 3)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(7, 0)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(7, 1)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(7, 2)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(7, 3)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(8, 0)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(8, 1)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(8, 2)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(8, 3)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(9, 0)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(9, 1)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(9, 2)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(9, 3)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(10, 0)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(10, 1)
- item.setText(_translate("Dialog", "0"))
- item = self.tableWidget.item(10, 2)
- item.setText(_translate("Dialog", "1"))
- item = self.tableWidget.item(10, 3)
- item.setText(_translate("Dialog", "1"))
- self.tableWidget.setSortingEnabled(__sortingEnabled)
- self.label_4.setText(_translate("Dialog", "Belegbare Felder per Anbieter"))
- self.label_5.setText(_translate("Dialog", "Anzahl:"))
- self.list_amount.setText(_translate("Dialog", "0"))
- self.btn_save.setText(_translate("Dialog", "Ok"))
- self.btn_cancel.setText(_translate("Dialog", "Abbrechen"))
- self.check_use_any_book.setToolTip(
- _translate(
- "Dialog",
- "Verwendet ein zufälliges Buch des Datensatzes, nützlich wenn das Buch noch nicht im Apparat ist",
- )
- )
- self.check_use_any_book.setText(_translate("Dialog", "Jedes Buch verwenden"))
- self.check_use_exact_signature.setToolTip(
- _translate(
- "Dialog", "Verwendet die eingegebene Signatur für die Suche von Daten"
- )
- )
- self.check_use_exact_signature.setText(_translate("Dialog", "Exakte Signatur"))
diff --git a/src/ui/dialogs/dialog_sources/Ui_newMailTemplateDesigner.py b/src/ui/dialogs/dialog_sources/Ui_newMailTemplateDesigner.py
deleted file mode 100644
index ee2e682..0000000
--- a/src/ui/dialogs/dialog_sources/Ui_newMailTemplateDesigner.py
+++ /dev/null
@@ -1,181 +0,0 @@
-# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\newMailTemplateDesigner.ui'
-#
-# Created by: PySide6 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, QtWidgets
-
-
-class Ui_Dialog(object):
- def setupUi(self, Dialog):
- Dialog.setObjectName("Dialog")
- Dialog.resize(689, 572)
- self.verticalLayout_2 = QtWidgets.QVBoxLayout(Dialog)
- self.verticalLayout_2.setObjectName("verticalLayout_2")
- self.verticalLayout = QtWidgets.QVBoxLayout()
- self.verticalLayout.setObjectName("verticalLayout")
- self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
- self.horizontalLayout_2.setObjectName("horizontalLayout_2")
- self.bold = QtWidgets.QPushButton(parent=Dialog)
- self.bold.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
- self.bold.setCheckable(True)
- self.bold.setObjectName("bold")
- self.horizontalLayout_2.addWidget(self.bold)
- self.italic = QtWidgets.QPushButton(parent=Dialog)
- self.italic.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
- self.italic.setCheckable(True)
- self.italic.setObjectName("italic")
- self.horizontalLayout_2.addWidget(self.italic)
- self.underlined = QtWidgets.QPushButton(parent=Dialog)
- self.underlined.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
- self.underlined.setCheckable(True)
- self.underlined.setObjectName("underlined")
- self.horizontalLayout_2.addWidget(self.underlined)
- self.fontBox = QtWidgets.QFontComboBox(parent=Dialog)
- self.fontBox.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
- self.fontBox.setObjectName("fontBox")
- self.horizontalLayout_2.addWidget(self.fontBox)
- self.fontSize = QtWidgets.QComboBox(parent=Dialog)
- self.fontSize.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
- self.fontSize.setObjectName("fontSize")
- self.fontSize.addItem("")
- self.fontSize.addItem("")
- self.fontSize.addItem("")
- self.fontSize.addItem("")
- self.fontSize.addItem("")
- self.fontSize.addItem("")
- self.fontSize.addItem("")
- self.fontSize.addItem("")
- self.fontSize.addItem("")
- self.fontSize.addItem("")
- self.fontSize.addItem("")
- self.fontSize.addItem("")
- self.fontSize.addItem("")
- self.fontSize.addItem("")
- self.fontSize.addItem("")
- self.horizontalLayout_2.addWidget(self.fontSize)
- spacerItem = QtWidgets.QSpacerItem(
- 40,
- 20,
- QtWidgets.QSizePolicy.Policy.Expanding,
- QtWidgets.QSizePolicy.Policy.Minimum,
- )
- self.horizontalLayout_2.addItem(spacerItem)
- self.verticalLayout.addLayout(self.horizontalLayout_2)
- self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
- self.horizontalLayout_4.setObjectName("horizontalLayout_4")
- self.verticalLayout.addLayout(self.horizontalLayout_4)
- self.gridLayout = QtWidgets.QGridLayout()
- self.gridLayout.setObjectName("gridLayout")
- self.label = QtWidgets.QLabel(parent=Dialog)
- self.label.setObjectName("label")
- self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
- self.placeholder_list = QtWidgets.QComboBox(parent=Dialog)
- self.placeholder_list.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
- self.placeholder_list.setSizeAdjustPolicy(
- QtWidgets.QComboBox.SizeAdjustPolicy.AdjustToContents
- )
- self.placeholder_list.setObjectName("placeholder_list")
- self.placeholder_list.addItem("")
- self.placeholder_list.addItem("")
- self.placeholder_list.addItem("")
- self.placeholder_list.addItem("")
- self.placeholder_list.addItem("")
- self.placeholder_list.addItem("")
- self.gridLayout.addWidget(self.placeholder_list, 1, 0, 1, 1)
- self.label_2 = QtWidgets.QLabel(parent=Dialog)
- self.label_2.setObjectName("label_2")
- self.gridLayout.addWidget(self.label_2, 0, 1, 1, 1)
- self.lineEdit = QtWidgets.QLineEdit(parent=Dialog)
- self.lineEdit.setEnabled(True)
- self.lineEdit.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
- self.lineEdit.setFrame(False)
- self.lineEdit.setReadOnly(True)
- self.lineEdit.setObjectName("lineEdit")
- self.gridLayout.addWidget(self.lineEdit, 1, 1, 1, 1)
- self.insertPlaceholder = QtWidgets.QPushButton(parent=Dialog)
- self.insertPlaceholder.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
- self.insertPlaceholder.setObjectName("insertPlaceholder")
- self.gridLayout.addWidget(self.insertPlaceholder, 1, 2, 1, 1)
- self.verticalLayout.addLayout(self.gridLayout)
- self.label_3 = QtWidgets.QLabel(parent=Dialog)
- self.label_3.setObjectName("label_3")
- self.verticalLayout.addWidget(self.label_3)
- self.subject = QtWidgets.QLineEdit(parent=Dialog)
- self.subject.setObjectName("subject")
- self.verticalLayout.addWidget(self.subject)
- self.templateEdit = QtWidgets.QTextEdit(parent=Dialog)
- self.templateEdit.setObjectName("templateEdit")
- self.verticalLayout.addWidget(self.templateEdit)
- self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
- self.horizontalLayout_3.setObjectName("horizontalLayout_3")
- self.testTemplate = QtWidgets.QPushButton(parent=Dialog)
- self.testTemplate.setObjectName("testTemplate")
- self.horizontalLayout_3.addWidget(self.testTemplate)
- spacerItem1 = QtWidgets.QSpacerItem(
- 40,
- 20,
- QtWidgets.QSizePolicy.Policy.Expanding,
- QtWidgets.QSizePolicy.Policy.Minimum,
- )
- self.horizontalLayout_3.addItem(spacerItem1)
- self.verticalLayout.addLayout(self.horizontalLayout_3)
- self.verticalLayout_2.addLayout(self.verticalLayout)
- self.buttonBox = QtWidgets.QDialogButtonBox(parent=Dialog)
- self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal)
- self.buttonBox.setStandardButtons(
- QtWidgets.QDialogButtonBox.StandardButton.Cancel
- | QtWidgets.QDialogButtonBox.StandardButton.Discard
- | QtWidgets.QDialogButtonBox.StandardButton.Save
- )
- self.buttonBox.setObjectName("buttonBox")
- self.verticalLayout_2.addWidget(self.buttonBox)
-
- self.retranslateUi(Dialog)
- self.fontSize.setCurrentIndex(1)
- QtCore.QMetaObject.connectSlotsByName(Dialog)
- Dialog.setTabOrder(self.subject, self.templateEdit)
- Dialog.setTabOrder(self.templateEdit, self.testTemplate)
- Dialog.setTabOrder(self.testTemplate, self.insertPlaceholder)
- Dialog.setTabOrder(self.insertPlaceholder, self.lineEdit)
- Dialog.setTabOrder(self.lineEdit, self.fontSize)
- Dialog.setTabOrder(self.fontSize, self.placeholder_list)
- Dialog.setTabOrder(self.placeholder_list, self.fontBox)
-
- def retranslateUi(self, Dialog):
- _translate = QtCore.QCoreApplication.translate
- Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
- self.bold.setText(_translate("Dialog", "Fett"))
- self.italic.setText(_translate("Dialog", "Kursiv"))
- self.underlined.setText(_translate("Dialog", "Unterstrichen"))
- self.fontSize.setItemText(0, _translate("Dialog", "8"))
- self.fontSize.setItemText(1, _translate("Dialog", "9"))
- self.fontSize.setItemText(2, _translate("Dialog", "11"))
- self.fontSize.setItemText(3, _translate("Dialog", "12"))
- self.fontSize.setItemText(4, _translate("Dialog", "14"))
- self.fontSize.setItemText(5, _translate("Dialog", "16"))
- self.fontSize.setItemText(6, _translate("Dialog", "18"))
- self.fontSize.setItemText(7, _translate("Dialog", "20"))
- self.fontSize.setItemText(8, _translate("Dialog", "22"))
- self.fontSize.setItemText(9, _translate("Dialog", "24"))
- self.fontSize.setItemText(10, _translate("Dialog", "26"))
- self.fontSize.setItemText(11, _translate("Dialog", "28"))
- self.fontSize.setItemText(12, _translate("Dialog", "36"))
- self.fontSize.setItemText(13, _translate("Dialog", "48"))
- self.fontSize.setItemText(14, _translate("Dialog", "76"))
- self.label.setText(_translate("Dialog", "Platzhalter"))
- self.placeholder_list.setItemText(0, _translate("Dialog", "«Anrede»"))
- self.placeholder_list.setItemText(1, _translate("Dialog", "«ApparatsName»"))
- self.placeholder_list.setItemText(2, _translate("Dialog", "«ApparatsFach»"))
- self.placeholder_list.setItemText(3, _translate("Dialog", "«ApparatsNummer»"))
- self.placeholder_list.setItemText(4, _translate("Dialog", "«DozentName»"))
- self.placeholder_list.setItemText(5, _translate("Dialog", "«Signatur»"))
- self.label_2.setText(_translate("Dialog", "Beschreibung"))
- self.insertPlaceholder.setText(
- _translate("Dialog", "An aktiver Position einfügen")
- )
- self.label_3.setText(_translate("Dialog", "Betreff"))
- self.testTemplate.setText(_translate("Dialog", "Template testen"))
diff --git a/src/ui/dialogs/dialog_sources/Ui_parsed_titles.py b/src/ui/dialogs/dialog_sources/Ui_parsed_titles.py
deleted file mode 100644
index bf60db7..0000000
--- a/src/ui/dialogs/dialog_sources/Ui_parsed_titles.py
+++ /dev/null
@@ -1,91 +0,0 @@
-# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\parsed_titles.ui'
-#
-# Created by: PySide6 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
-
-
-class Ui_Form(object):
- def setupUi(self, Form):
- Form.setObjectName("Form")
- Form.resize(402, 316)
- self.frame = QtWidgets.QFrame(parent=Form)
- self.frame.setGeometry(QtCore.QRect(10, 10, 381, 41))
- self.frame.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel)
- self.frame.setFrameShadow(QtWidgets.QFrame.Shadow.Raised)
- self.frame.setObjectName("frame")
- self.horizontalLayoutWidget = QtWidgets.QWidget(parent=self.frame)
- self.horizontalLayoutWidget.setGeometry(QtCore.QRect(0, 0, 381, 41))
- self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget")
- self.horizontalLayout = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget)
- self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
- self.horizontalLayout.setObjectName("horizontalLayout")
- self.label = QtWidgets.QLabel(parent=self.horizontalLayoutWidget)
- self.label.setObjectName("label")
- self.horizontalLayout.addWidget(self.label)
- self.count = QtWidgets.QLabel(parent=self.horizontalLayoutWidget)
- font = QtGui.QFont()
- font.setBold(True)
- font.setWeight(75)
- self.count.setFont(font)
- self.count.setTextFormat(QtCore.Qt.TextFormat.PlainText)
- self.count.setObjectName("count")
- self.horizontalLayout.addWidget(self.count)
- self.label_2 = QtWidgets.QLabel(parent=self.horizontalLayoutWidget)
- self.label_2.setObjectName("label_2")
- self.horizontalLayout.addWidget(self.label_2)
- spacerItem = QtWidgets.QSpacerItem(
- 40,
- 20,
- QtWidgets.QSizePolicy.Policy.Expanding,
- QtWidgets.QSizePolicy.Policy.Minimum,
- )
- self.horizontalLayout.addItem(spacerItem)
- self.frame_2 = QtWidgets.QFrame(parent=Form)
- self.frame_2.setGeometry(QtCore.QRect(10, 80, 381, 201))
- self.frame_2.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel)
- self.frame_2.setFrameShadow(QtWidgets.QFrame.Shadow.Raised)
- self.frame_2.setObjectName("frame_2")
- self.horizontalLayoutWidget_2 = QtWidgets.QWidget(parent=self.frame_2)
- self.horizontalLayoutWidget_2.setGeometry(QtCore.QRect(0, 10, 381, 191))
- self.horizontalLayoutWidget_2.setObjectName("horizontalLayoutWidget_2")
- self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget_2)
- self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
- self.horizontalLayout_2.setObjectName("horizontalLayout_2")
- self.listWidget = QtWidgets.QListWidget(parent=self.horizontalLayoutWidget_2)
- self.listWidget.setObjectName("listWidget")
- self.horizontalLayout_2.addWidget(self.listWidget)
- self.listWidget_done = QtWidgets.QListWidget(
- parent=self.horizontalLayoutWidget_2
- )
- self.listWidget_done.setObjectName("listWidget_done")
- self.horizontalLayout_2.addWidget(self.listWidget_done)
- self.progressBar = QtWidgets.QProgressBar(parent=Form)
- self.progressBar.setGeometry(QtCore.QRect(10, 60, 381, 23))
- self.progressBar.setProperty("value", 24)
- self.progressBar.setObjectName("progressBar")
- self.buttonBox = QtWidgets.QDialogButtonBox(parent=Form)
- self.buttonBox.setGeometry(QtCore.QRect(230, 290, 156, 23))
- self.buttonBox.setStandardButtons(
- QtWidgets.QDialogButtonBox.StandardButton.Cancel
- | QtWidgets.QDialogButtonBox.StandardButton.Ok
- )
- self.buttonBox.setObjectName("buttonBox")
- self.toolButton = QtWidgets.QToolButton(parent=Form)
- self.toolButton.setGeometry(QtCore.QRect(20, 290, 25, 19))
- self.toolButton.setObjectName("toolButton")
-
- self.retranslateUi(Form)
- QtCore.QMetaObject.connectSlotsByName(Form)
-
- def retranslateUi(self, Form):
- _translate = QtCore.QCoreApplication.translate
- Form.setWindowTitle(_translate("Form", "Form"))
- self.label.setText(_translate("Form", "Es wurden"))
- self.count.setText(_translate("Form", "0"))
- self.label_2.setText(_translate("Form", "Signaturen gefunden."))
- self.toolButton.setText(_translate("Form", "..."))
diff --git a/src/ui/dialogs/dialog_sources/Ui_reminder.py b/src/ui/dialogs/dialog_sources/Ui_reminder.py
deleted file mode 100644
index 5f62089..0000000
--- a/src/ui/dialogs/dialog_sources/Ui_reminder.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\reminder.ui'
-#
-# Created by: PySide6 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, QtWidgets
-
-
-class Ui_Erinnerung(object):
- def setupUi(self, Erinnerung):
- Erinnerung.setObjectName("Erinnerung")
- Erinnerung.resize(358, 308)
- self.buttonBox = QtWidgets.QDialogButtonBox(parent=Erinnerung)
- self.buttonBox.setGeometry(QtCore.QRect(190, 270, 161, 32))
- self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal)
- self.buttonBox.setStandardButtons(
- QtWidgets.QDialogButtonBox.StandardButton.Cancel
- | QtWidgets.QDialogButtonBox.StandardButton.Ok
- )
- self.buttonBox.setObjectName("buttonBox")
- self.message_box = QtWidgets.QTextEdit(parent=Erinnerung)
- self.message_box.setGeometry(QtCore.QRect(10, 60, 341, 201))
- self.message_box.setObjectName("message_box")
- self.label = QtWidgets.QLabel(parent=Erinnerung)
- self.label.setGeometry(QtCore.QRect(10, 30, 61, 21))
- self.label.setObjectName("label")
- self.label_2 = QtWidgets.QLabel(parent=Erinnerung)
- self.label_2.setGeometry(QtCore.QRect(120, 30, 81, 21))
- self.label_2.setObjectName("label_2")
- self.dateEdit = QtWidgets.QDateEdit(parent=Erinnerung)
- self.dateEdit.setGeometry(QtCore.QRect(210, 30, 141, 22))
- self.dateEdit.setObjectName("dateEdit")
-
- self.retranslateUi(Erinnerung)
- self.buttonBox.accepted.connect(Erinnerung.accept) # type: ignore
- self.buttonBox.rejected.connect(Erinnerung.reject) # type: ignore
- QtCore.QMetaObject.connectSlotsByName(Erinnerung)
-
- def retranslateUi(self, Erinnerung):
- _translate = QtCore.QCoreApplication.translate
- Erinnerung.setWindowTitle(_translate("Erinnerung", "Dialog"))
- self.label.setText(_translate("Erinnerung", "Nachricht:"))
- self.label_2.setText(_translate("Erinnerung", "Erinnerung am:"))
diff --git a/src/ui/dialogs/dialog_sources/Ui_settings.py b/src/ui/dialogs/dialog_sources/Ui_settings.py
deleted file mode 100644
index f6cdc11..0000000
--- a/src/ui/dialogs/dialog_sources/Ui_settings.py
+++ /dev/null
@@ -1,341 +0,0 @@
-# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\settings.ui'
-#
-# Created by: PySide6 UI code generator 6.7.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
-
-
-class Ui_Dialog(object):
- def setupUi(self, Dialog):
- Dialog.setObjectName("Dialog")
- Dialog.setWindowModality(QtCore.Qt.WindowModality.NonModal)
- Dialog.resize(651, 679)
- sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.MinimumExpanding)
- sizePolicy.setHorizontalStretch(0)
- sizePolicy.setVerticalStretch(0)
- sizePolicy.setHeightForWidth(Dialog.sizePolicy().hasHeightForWidth())
- Dialog.setSizePolicy(sizePolicy)
- self.verticalLayout = QtWidgets.QVBoxLayout(Dialog)
- self.verticalLayout.setObjectName("verticalLayout")
- self.toolBox = QtWidgets.QToolBox(parent=Dialog)
- sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.MinimumExpanding)
- sizePolicy.setHorizontalStretch(0)
- sizePolicy.setVerticalStretch(0)
- sizePolicy.setHeightForWidth(self.toolBox.sizePolicy().hasHeightForWidth())
- self.toolBox.setSizePolicy(sizePolicy)
- self.toolBox.setInputMethodHints(QtCore.Qt.InputMethodHint.ImhNone)
- self.toolBox.setObjectName("toolBox")
- self.page_1 = QtWidgets.QWidget()
- self.page_1.setGeometry(QtCore.QRect(0, 0, 633, 511))
- sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.MinimumExpanding)
- sizePolicy.setHorizontalStretch(0)
- sizePolicy.setVerticalStretch(0)
- sizePolicy.setHeightForWidth(self.page_1.sizePolicy().hasHeightForWidth())
- self.page_1.setSizePolicy(sizePolicy)
- self.page_1.setObjectName("page_1")
- self.gridLayout_3 = QtWidgets.QGridLayout(self.page_1)
- self.gridLayout_3.setObjectName("gridLayout_3")
- self.db_name = QtWidgets.QLineEdit(parent=self.page_1)
- self.db_name.setObjectName("db_name")
- self.gridLayout_3.addWidget(self.db_name, 0, 1, 1, 1)
- self.label_5 = QtWidgets.QLabel(parent=self.page_1)
- self.label_5.setObjectName("label_5")
- self.gridLayout_3.addWidget(self.label_5, 0, 0, 1, 1)
- self.db_path = QtWidgets.QLineEdit(parent=self.page_1)
- self.db_path.setEnabled(False)
- self.db_path.setObjectName("db_path")
- self.gridLayout_3.addWidget(self.db_path, 1, 1, 1, 1)
- self.label_12 = QtWidgets.QLabel(parent=self.page_1)
- self.label_12.setObjectName("label_12")
- self.gridLayout_3.addWidget(self.label_12, 2, 0, 1, 1)
- self.label_11 = QtWidgets.QLabel(parent=self.page_1)
- self.label_11.setObjectName("label_11")
- self.gridLayout_3.addWidget(self.label_11, 1, 0, 1, 1)
- self.tb_set_save_path = QtWidgets.QToolButton(parent=self.page_1)
- self.tb_set_save_path.setObjectName("tb_set_save_path")
- self.gridLayout_3.addWidget(self.tb_set_save_path, 2, 2, 1, 1)
- self.tb_select_db = QtWidgets.QToolButton(parent=self.page_1)
- self.tb_select_db.setObjectName("tb_select_db")
- self.gridLayout_3.addWidget(self.tb_select_db, 0, 2, 1, 1)
- self.save_path = QtWidgets.QLineEdit(parent=self.page_1)
- self.save_path.setObjectName("save_path")
- self.gridLayout_3.addWidget(self.save_path, 2, 1, 1, 1)
- spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
- self.gridLayout_3.addItem(spacerItem, 3, 1, 1, 1)
- self.toolBox.addItem(self.page_1, "")
- self.page_2 = QtWidgets.QWidget()
- self.page_2.setGeometry(QtCore.QRect(0, 0, 633, 511))
- self.page_2.setObjectName("page_2")
- self.gridLayout = QtWidgets.QGridLayout(self.page_2)
- self.gridLayout.setObjectName("gridLayout")
- self.zotero_library_type = QtWidgets.QLineEdit(parent=self.page_2)
- self.zotero_library_type.setObjectName("zotero_library_type")
- self.gridLayout.addWidget(self.zotero_library_type, 2, 2, 1, 1)
- self.zotero_library_id = QtWidgets.QLineEdit(parent=self.page_2)
- self.zotero_library_id.setObjectName("zotero_library_id")
- self.gridLayout.addWidget(self.zotero_library_id, 1, 2, 1, 1)
- self.label_4 = QtWidgets.QLabel(parent=self.page_2)
- self.label_4.setObjectName("label_4")
- self.gridLayout.addWidget(self.label_4, 2, 0, 1, 1)
- self.label_3 = QtWidgets.QLabel(parent=self.page_2)
- self.label_3.setObjectName("label_3")
- self.gridLayout.addWidget(self.label_3, 1, 0, 1, 1)
- self.zotero_api_key = QtWidgets.QLineEdit(parent=self.page_2)
- self.zotero_api_key.setInputMethodHints(QtCore.Qt.InputMethodHint.ImhHiddenText|QtCore.Qt.InputMethodHint.ImhSensitiveData)
- self.zotero_api_key.setObjectName("zotero_api_key")
- self.gridLayout.addWidget(self.zotero_api_key, 0, 2, 1, 1)
- self.label_2 = QtWidgets.QLabel(parent=self.page_2)
- self.label_2.setObjectName("label_2")
- self.gridLayout.addWidget(self.label_2, 0, 0, 1, 1)
- self.toggle_api_visibility = QtWidgets.QToolButton(parent=self.page_2)
- self.toggle_api_visibility.setText("")
- self.toggle_api_visibility.setObjectName("toggle_api_visibility")
- self.gridLayout.addWidget(self.toggle_api_visibility, 0, 3, 1, 1)
- spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
- self.gridLayout.addItem(spacerItem1, 3, 2, 1, 1)
- self.toolBox.addItem(self.page_2, "")
- self.page_3 = QtWidgets.QWidget()
- self.page_3.setGeometry(QtCore.QRect(0, 0, 633, 511))
- self.page_3.setObjectName("page_3")
- self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.page_3)
- self.horizontalLayout_2.setObjectName("horizontalLayout_2")
- self.email_settings = QtWidgets.QTabWidget(parent=self.page_3)
- self.email_settings.setObjectName("email_settings")
- self.email_settingsPage1_2 = QtWidgets.QWidget()
- self.email_settingsPage1_2.setObjectName("email_settingsPage1_2")
- self.horizontalLayout_4 = QtWidgets.QHBoxLayout(self.email_settingsPage1_2)
- self.horizontalLayout_4.setObjectName("horizontalLayout_4")
- self.gridLayout_2 = QtWidgets.QGridLayout()
- self.gridLayout_2.setObjectName("gridLayout_2")
- self.use_username_smtp_login = QtWidgets.QCheckBox(parent=self.email_settingsPage1_2)
- self.use_username_smtp_login.setTristate(False)
- self.use_username_smtp_login.setObjectName("use_username_smtp_login")
- self.gridLayout_2.addWidget(self.use_username_smtp_login, 4, 1, 1, 1)
- self.label_6 = QtWidgets.QLabel(parent=self.email_settingsPage1_2)
- self.label_6.setObjectName("label_6")
- self.gridLayout_2.addWidget(self.label_6, 1, 0, 1, 1)
- self.smtp_port = QtWidgets.QLineEdit(parent=self.email_settingsPage1_2)
- self.smtp_port.setInputMethodHints(QtCore.Qt.InputMethodHint.ImhDigitsOnly|QtCore.Qt.InputMethodHint.ImhPreferNumbers)
- self.smtp_port.setClearButtonEnabled(True)
- self.smtp_port.setObjectName("smtp_port")
- self.gridLayout_2.addWidget(self.smtp_port, 1, 1, 1, 1)
- self.label_7 = QtWidgets.QLabel(parent=self.email_settingsPage1_2)
- self.label_7.setObjectName("label_7")
- self.gridLayout_2.addWidget(self.label_7, 2, 0, 1, 1)
- self.sender_email = QtWidgets.QLineEdit(parent=self.email_settingsPage1_2)
- self.sender_email.setInputMethodHints(QtCore.Qt.InputMethodHint.ImhEmailCharactersOnly)
- self.sender_email.setClearButtonEnabled(True)
- self.sender_email.setObjectName("sender_email")
- self.gridLayout_2.addWidget(self.sender_email, 2, 1, 1, 1)
- self.mail_username = QtWidgets.QLineEdit(parent=self.email_settingsPage1_2)
- self.mail_username.setClearButtonEnabled(True)
- self.mail_username.setObjectName("mail_username")
- self.gridLayout_2.addWidget(self.mail_username, 3, 1, 1, 1)
- self.label_9 = QtWidgets.QLabel(parent=self.email_settingsPage1_2)
- self.label_9.setText("")
- self.label_9.setObjectName("label_9")
- self.gridLayout_2.addWidget(self.label_9, 7, 0, 1, 1)
- self.password = QtWidgets.QLineEdit(parent=self.email_settingsPage1_2)
- self.password.setInputMethodHints(QtCore.Qt.InputMethodHint.ImhHiddenText|QtCore.Qt.InputMethodHint.ImhSensitiveData)
- self.password.setClearButtonEnabled(True)
- self.password.setObjectName("password")
- self.gridLayout_2.addWidget(self.password, 5, 1, 1, 1)
- self.smtp_address = QtWidgets.QLineEdit(parent=self.email_settingsPage1_2)
- self.smtp_address.setClearButtonEnabled(True)
- self.smtp_address.setObjectName("smtp_address")
- self.gridLayout_2.addWidget(self.smtp_address, 0, 1, 1, 1)
- self.label = QtWidgets.QLabel(parent=self.email_settingsPage1_2)
- self.label.setObjectName("label")
- self.gridLayout_2.addWidget(self.label, 0, 0, 1, 1)
- self.label_10 = QtWidgets.QLabel(parent=self.email_settingsPage1_2)
- self.label_10.setObjectName("label_10")
- self.gridLayout_2.addWidget(self.label_10, 5, 0, 1, 1)
- self.togglePassword = QtWidgets.QPushButton(parent=self.email_settingsPage1_2)
- self.togglePassword.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
- self.togglePassword.setText("")
- self.togglePassword.setObjectName("togglePassword")
- self.gridLayout_2.addWidget(self.togglePassword, 5, 2, 1, 1)
- self.label_8 = QtWidgets.QLabel(parent=self.email_settingsPage1_2)
- self.label_8.setObjectName("label_8")
- self.gridLayout_2.addWidget(self.label_8, 3, 0, 1, 1)
- self.label_13 = QtWidgets.QLabel(parent=self.email_settingsPage1_2)
- self.label_13.setObjectName("label_13")
- self.gridLayout_2.addWidget(self.label_13, 6, 0, 1, 1)
- self.printermail = QtWidgets.QLineEdit(parent=self.email_settingsPage1_2)
- self.printermail.setObjectName("printermail")
- self.gridLayout_2.addWidget(self.printermail, 6, 1, 1, 1)
- self.horizontalLayout_4.addLayout(self.gridLayout_2)
- self.email_settings.addTab(self.email_settingsPage1_2, "")
- self.email_settingsPage2_2 = QtWidgets.QWidget()
- self.email_settingsPage2_2.setObjectName("email_settingsPage2_2")
- self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.email_settingsPage2_2)
- self.verticalLayout_3.setObjectName("verticalLayout_3")
- self.verticalLayout_2 = QtWidgets.QVBoxLayout()
- self.verticalLayout_2.setObjectName("verticalLayout_2")
- self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
- self.horizontalLayout_3.setObjectName("horizontalLayout_3")
- spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
- self.horizontalLayout_3.addItem(spacerItem2)
- self.bold = QtWidgets.QPushButton(parent=self.email_settingsPage2_2)
- self.bold.setCheckable(True)
- self.bold.setObjectName("bold")
- self.horizontalLayout_3.addWidget(self.bold)
- self.italic = QtWidgets.QPushButton(parent=self.email_settingsPage2_2)
- self.italic.setCheckable(True)
- self.italic.setObjectName("italic")
- self.horizontalLayout_3.addWidget(self.italic)
- self.underscore = QtWidgets.QPushButton(parent=self.email_settingsPage2_2)
- self.underscore.setCheckable(True)
- self.underscore.setObjectName("underscore")
- self.horizontalLayout_3.addWidget(self.underscore)
- spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
- self.horizontalLayout_3.addItem(spacerItem3)
- self.verticalLayout_2.addLayout(self.horizontalLayout_3)
- self.horizontalLayout = QtWidgets.QHBoxLayout()
- self.horizontalLayout.setObjectName("horizontalLayout")
- self.fontComboBox = QtWidgets.QFontComboBox(parent=self.email_settingsPage2_2)
- self.fontComboBox.setObjectName("fontComboBox")
- self.horizontalLayout.addWidget(self.fontComboBox)
- self.font_size = QtWidgets.QComboBox(parent=self.email_settingsPage2_2)
- self.font_size.setObjectName("font_size")
- self.font_size.addItem("")
- self.font_size.addItem("")
- self.font_size.addItem("")
- self.font_size.addItem("")
- self.font_size.addItem("")
- self.font_size.addItem("")
- self.font_size.addItem("")
- self.font_size.addItem("")
- self.font_size.addItem("")
- self.font_size.addItem("")
- self.font_size.addItem("")
- self.font_size.addItem("")
- self.font_size.addItem("")
- self.font_size.addItem("")
- self.font_size.addItem("")
- self.horizontalLayout.addWidget(self.font_size)
- spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
- self.horizontalLayout.addItem(spacerItem4)
- self.verticalLayout_2.addLayout(self.horizontalLayout)
- self.verticalLayout_3.addLayout(self.verticalLayout_2)
- self.editSignature = QtWidgets.QTextEdit(parent=self.email_settingsPage2_2)
- self.editSignature.setObjectName("editSignature")
- self.verticalLayout_3.addWidget(self.editSignature)
- self.debug = QtWidgets.QPushButton(parent=self.email_settingsPage2_2)
- self.debug.setObjectName("debug")
- self.verticalLayout_3.addWidget(self.debug)
- self.email_settings.addTab(self.email_settingsPage2_2, "")
- self.horizontalLayout_2.addWidget(self.email_settings)
- self.toolBox.addItem(self.page_3, "")
- self.page_4 = QtWidgets.QWidget()
- self.page_4.setGeometry(QtCore.QRect(0, 0, 633, 511))
- self.page_4.setObjectName("page_4")
- self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.page_4)
- self.verticalLayout_4.setObjectName("verticalLayout_4")
- self.groupBox = QtWidgets.QGroupBox(parent=self.page_4)
- font = QtGui.QFont()
- font.setPointSize(12)
- font.setBold(True)
- self.groupBox.setFont(font)
- self.groupBox.setObjectName("groupBox")
- self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.groupBox)
- self.verticalLayout_5.setObjectName("verticalLayout_5")
- self.scrollArea_3 = QtWidgets.QScrollArea(parent=self.groupBox)
- self.scrollArea_3.setWidgetResizable(True)
- self.scrollArea_3.setObjectName("scrollArea_3")
- self.scrollAreaWidgetContents_3 = QtWidgets.QWidget()
- self.scrollAreaWidgetContents_3.setGeometry(QtCore.QRect(0, 0, 593, 201))
- self.scrollAreaWidgetContents_3.setObjectName("scrollAreaWidgetContents_3")
- self.verticalLayout_7 = QtWidgets.QVBoxLayout(self.scrollAreaWidgetContents_3)
- self.verticalLayout_7.setObjectName("verticalLayout_7")
- self.gridLayout_4 = QtWidgets.QGridLayout()
- self.gridLayout_4.setObjectName("gridLayout_4")
- self.verticalLayout_7.addLayout(self.gridLayout_4)
- self.scrollArea_3.setWidget(self.scrollAreaWidgetContents_3)
- self.verticalLayout_5.addWidget(self.scrollArea_3)
- self.verticalLayout_4.addWidget(self.groupBox)
- self.scrollArea_2 = QtWidgets.QScrollArea(parent=self.page_4)
- self.scrollArea_2.setWidgetResizable(True)
- self.scrollArea_2.setObjectName("scrollArea_2")
- self.scrollAreaWidgetContents_2 = QtWidgets.QWidget()
- self.scrollAreaWidgetContents_2.setGeometry(QtCore.QRect(0, 0, 613, 241))
- self.scrollAreaWidgetContents_2.setObjectName("scrollAreaWidgetContents_2")
- self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.scrollAreaWidgetContents_2)
- self.verticalLayout_6.setObjectName("verticalLayout_6")
- self.vertical_icons = QtWidgets.QVBoxLayout()
- self.vertical_icons.setObjectName("vertical_icons")
- self.verticalLayout_6.addLayout(self.vertical_icons)
- self.scrollArea_2.setWidget(self.scrollAreaWidgetContents_2)
- self.verticalLayout_4.addWidget(self.scrollArea_2)
- self.toolBox.addItem(self.page_4, "")
- self.verticalLayout.addWidget(self.toolBox)
- self.buttonBox = QtWidgets.QDialogButtonBox(parent=Dialog)
- self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal)
- self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.StandardButton.Cancel|QtWidgets.QDialogButtonBox.StandardButton.Ok)
- self.buttonBox.setObjectName("buttonBox")
- self.verticalLayout.addWidget(self.buttonBox)
- self.label_5.setBuddy(self.db_name)
- self.label_12.setBuddy(self.save_path)
- self.label_11.setBuddy(self.db_path)
-
- self.retranslateUi(Dialog)
- self.toolBox.setCurrentIndex(2)
- self.email_settings.setCurrentIndex(0)
- self.buttonBox.accepted.connect(Dialog.accept) # type: ignore
- self.buttonBox.rejected.connect(Dialog.reject) # type: ignore
- QtCore.QMetaObject.connectSlotsByName(Dialog)
-
- def retranslateUi(self, Dialog):
- _translate = QtCore.QCoreApplication.translate
- Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
- self.db_name.setText(_translate("Dialog", "sap.db"))
- self.label_5.setToolTip(_translate("Dialog", "Name der Datenbank, welche verwendet werden soll. Muss auf .db enden
"))
- self.label_5.setText(_translate("Dialog", "Datenbankname"))
- self.label_12.setToolTip(_translate("Dialog", "Pfad, an dem heruntergeladene Dateien gespeichert werden sollen"))
- self.label_12.setText(_translate("Dialog", "Temporäre Dateien"))
- self.label_11.setText(_translate("Dialog", "Datenbankpfad"))
- self.tb_set_save_path.setText(_translate("Dialog", "..."))
- self.tb_select_db.setText(_translate("Dialog", "..."))
- self.toolBox.setItemText(self.toolBox.indexOf(self.page_1), _translate("Dialog", "Datenbank"))
- self.label_4.setText(_translate("Dialog", "Bibliothekstyp"))
- self.label_3.setText(_translate("Dialog", "Bibliotheks-ID"))
- self.label_2.setText(_translate("Dialog", "API Key"))
- self.toolBox.setItemText(self.toolBox.indexOf(self.page_2), _translate("Dialog", "Zotero"))
- self.use_username_smtp_login.setStatusTip(_translate("Dialog", "Anklicken, wenn Nutzername benötigt wird, um sich beim Server anzumelden"))
- self.use_username_smtp_login.setText(_translate("Dialog", "Nutzername zum\n"
-" Anmelden verwenden"))
- self.label_6.setText(_translate("Dialog", "Port"))
- self.label_7.setText(_translate("Dialog", "Sender-eMail"))
- self.mail_username.setStatusTip(_translate("Dialog", "Kürzel, von der Hochschule vergeben, bsp: Aky547"))
- self.label.setText(_translate("Dialog", "SMTP-Server"))
- self.label_10.setText(_translate("Dialog", "Passwort"))
- self.label_8.setText(_translate("Dialog", "Nutzername"))
- self.label_13.setText(_translate("Dialog", "Printmail"))
- self.email_settings.setTabText(self.email_settings.indexOf(self.email_settingsPage1_2), _translate("Dialog", "Allgemeines"))
- self.bold.setText(_translate("Dialog", "Fett"))
- self.italic.setText(_translate("Dialog", "Kursiv"))
- self.underscore.setText(_translate("Dialog", "Unterstrichen"))
- self.font_size.setItemText(0, _translate("Dialog", "8"))
- self.font_size.setItemText(1, _translate("Dialog", "9"))
- self.font_size.setItemText(2, _translate("Dialog", "11"))
- self.font_size.setItemText(3, _translate("Dialog", "12"))
- self.font_size.setItemText(4, _translate("Dialog", "14"))
- self.font_size.setItemText(5, _translate("Dialog", "16"))
- self.font_size.setItemText(6, _translate("Dialog", "18"))
- self.font_size.setItemText(7, _translate("Dialog", "20"))
- self.font_size.setItemText(8, _translate("Dialog", "22"))
- self.font_size.setItemText(9, _translate("Dialog", "24"))
- self.font_size.setItemText(10, _translate("Dialog", "26"))
- self.font_size.setItemText(11, _translate("Dialog", "28"))
- self.font_size.setItemText(12, _translate("Dialog", "36"))
- self.font_size.setItemText(13, _translate("Dialog", "48"))
- self.font_size.setItemText(14, _translate("Dialog", "72"))
- self.debug.setText(_translate("Dialog", "Debug"))
- self.email_settings.setTabText(self.email_settings.indexOf(self.email_settingsPage2_2), _translate("Dialog", "Signatur"))
- self.toolBox.setItemText(self.toolBox.indexOf(self.page_3), _translate("Dialog", "e-Mail"))
- self.groupBox.setTitle(_translate("Dialog", "Farben"))
- self.toolBox.setItemText(self.toolBox.indexOf(self.page_4), _translate("Dialog", "Icons"))
diff --git a/src/ui/dialogs/dialog_sources/__init__.py b/src/ui/dialogs/dialog_sources/__init__.py
index bb410ed..345e7a2 100644
--- a/src/ui/dialogs/dialog_sources/__init__.py
+++ b/src/ui/dialogs/dialog_sources/__init__.py
@@ -1,2 +1 @@
-from .Ui_mail_preview import Ui_eMailPreview as MailPreviewDialog
-from .Ui_newMailTemplateDesigner import Ui_Dialog as NewMailTemplateDesignerDialog
+from .newMailTemplateDesigner_ui import Ui_Dialog as NewMailTemplateDesignerDialog
diff --git a/src/ui/dialogs/dialog_sources/app_status_ui.py b/src/ui/dialogs/dialog_sources/app_status_ui.py
index d845f0e..9a913cd 100644
--- a/src/ui/dialogs/dialog_sources/app_status_ui.py
+++ b/src/ui/dialogs/dialog_sources/app_status_ui.py
@@ -6,7 +6,7 @@
# run again. Do not edit this file unless you know what you are doing.
-from PySide6 import QtCore, QtGui, QtWidgets
+from PySide6 import QtCore
class Ui_Form(object):
diff --git a/src/ui/dialogs/dialog_sources/confirm_extend_ui.py b/src/ui/dialogs/dialog_sources/confirm_extend_ui.py
index bef4a6b..2eed1d9 100644
--- a/src/ui/dialogs/dialog_sources/confirm_extend_ui.py
+++ b/src/ui/dialogs/dialog_sources/confirm_extend_ui.py
@@ -6,7 +6,7 @@
# run again. Do not edit this file unless you know what you are doing.
-from PySide6 import QtCore, QtGui, QtWidgets
+from PySide6 import QtCore, QtWidgets
class Ui_extend_confirm(object):
diff --git a/src/ui/dialogs/dialog_sources/deletedialog.ui b/src/ui/dialogs/dialog_sources/deletedialog.ui
new file mode 100644
index 0000000..8670475
--- /dev/null
+++ b/src/ui/dialogs/dialog_sources/deletedialog.ui
@@ -0,0 +1,138 @@
+
+
+ Dialog
+
+
+
+ 0
+ 0
+ 1001
+ 649
+
+
+
+ Dialog
+
+
+ -
+
+ -
+
+
+ Medium suchen
+
+
+
+ -
+
+
+ Titel/Signatursuche
+
+
+
+
+
+ -
+
+
+ true
+
+
+ true
+
+
+
+
+
+
+
+
+ Apparat
+
+
+
+
+ Signatur
+
+
+
+
+ Titel
+
+
+
+
+ Auflage
+
+
+
+
+ ISBN
+
+
+
+
+ ID
+
+
+
+
+ -
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 20
+ 20
+
+
+
+
+ -
+
+
+ Zurücksetzen
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Löschen
+
+
+
+ -
+
+
+ Abbrechen
+
+
+
+
+
+
+
+
+
+
diff --git a/src/ui/dialogs/dialog_sources/deletedialog_ui.py b/src/ui/dialogs/dialog_sources/deletedialog_ui.py
new file mode 100644
index 0000000..138b6d6
--- /dev/null
+++ b/src/ui/dialogs/dialog_sources/deletedialog_ui.py
@@ -0,0 +1,114 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'deletedialog.ui'
+##
+## Created by: Qt User Interface Compiler version 6.9.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide6.QtCore import (QCoreApplication, QMetaObject)
+from PySide6.QtWidgets import (QHBoxLayout, QLabel, QLineEdit, QPushButton, QSizePolicy,
+ QSpacerItem, QTableWidget, QTableWidgetItem, QVBoxLayout)
+
+class Ui_Dialog(object):
+ def setupUi(self, Dialog):
+ if not Dialog.objectName():
+ Dialog.setObjectName(u"Dialog")
+ Dialog.resize(1001, 649)
+ self.verticalLayout = QVBoxLayout(Dialog)
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.horizontalLayout = QHBoxLayout()
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.label = QLabel(Dialog)
+ self.label.setObjectName(u"label")
+
+ self.horizontalLayout.addWidget(self.label)
+
+ self.lineEdit = QLineEdit(Dialog)
+ self.lineEdit.setObjectName(u"lineEdit")
+
+ self.horizontalLayout.addWidget(self.lineEdit)
+
+
+ self.verticalLayout.addLayout(self.horizontalLayout)
+
+ self.tableWidget = QTableWidget(Dialog)
+ if (self.tableWidget.columnCount() < 7):
+ self.tableWidget.setColumnCount(7)
+ __qtablewidgetitem = QTableWidgetItem()
+ self.tableWidget.setHorizontalHeaderItem(0, __qtablewidgetitem)
+ __qtablewidgetitem1 = QTableWidgetItem()
+ self.tableWidget.setHorizontalHeaderItem(1, __qtablewidgetitem1)
+ __qtablewidgetitem2 = QTableWidgetItem()
+ self.tableWidget.setHorizontalHeaderItem(2, __qtablewidgetitem2)
+ __qtablewidgetitem3 = QTableWidgetItem()
+ self.tableWidget.setHorizontalHeaderItem(3, __qtablewidgetitem3)
+ __qtablewidgetitem4 = QTableWidgetItem()
+ self.tableWidget.setHorizontalHeaderItem(4, __qtablewidgetitem4)
+ __qtablewidgetitem5 = QTableWidgetItem()
+ self.tableWidget.setHorizontalHeaderItem(5, __qtablewidgetitem5)
+ __qtablewidgetitem6 = QTableWidgetItem()
+ self.tableWidget.setHorizontalHeaderItem(6, __qtablewidgetitem6)
+ self.tableWidget.setObjectName(u"tableWidget")
+ self.tableWidget.setAlternatingRowColors(True)
+ self.tableWidget.horizontalHeader().setStretchLastSection(True)
+
+ self.verticalLayout.addWidget(self.tableWidget)
+
+ self.horizontalLayout_2 = QHBoxLayout()
+ self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
+ self.horizontalSpacer_2 = QSpacerItem(20, 20, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum)
+
+ self.horizontalLayout_2.addItem(self.horizontalSpacer_2)
+
+ self.reset_btn = QPushButton(Dialog)
+ self.reset_btn.setObjectName(u"reset_btn")
+
+ self.horizontalLayout_2.addWidget(self.reset_btn)
+
+ self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
+
+ self.horizontalLayout_2.addItem(self.horizontalSpacer)
+
+ self.delete_btn = QPushButton(Dialog)
+ self.delete_btn.setObjectName(u"delete_btn")
+
+ self.horizontalLayout_2.addWidget(self.delete_btn)
+
+ self.cancel_btn = QPushButton(Dialog)
+ self.cancel_btn.setObjectName(u"cancel_btn")
+
+ self.horizontalLayout_2.addWidget(self.cancel_btn)
+
+
+ self.verticalLayout.addLayout(self.horizontalLayout_2)
+
+
+ self.retranslateUi(Dialog)
+
+ QMetaObject.connectSlotsByName(Dialog)
+ # setupUi
+
+ def retranslateUi(self, Dialog):
+ Dialog.setWindowTitle(QCoreApplication.translate("Dialog", u"Dialog", None))
+ self.label.setText(QCoreApplication.translate("Dialog", u"Medium suchen", None))
+ self.lineEdit.setPlaceholderText(QCoreApplication.translate("Dialog", u"Titel/Signatursuche", None))
+ ___qtablewidgetitem = self.tableWidget.horizontalHeaderItem(1)
+ ___qtablewidgetitem.setText(QCoreApplication.translate("Dialog", u"Apparat", None))
+ ___qtablewidgetitem1 = self.tableWidget.horizontalHeaderItem(2)
+ ___qtablewidgetitem1.setText(QCoreApplication.translate("Dialog", u"Signatur", None))
+ ___qtablewidgetitem2 = self.tableWidget.horizontalHeaderItem(3)
+ ___qtablewidgetitem2.setText(QCoreApplication.translate("Dialog", u"Titel", None))
+ ___qtablewidgetitem3 = self.tableWidget.horizontalHeaderItem(4)
+ ___qtablewidgetitem3.setText(QCoreApplication.translate("Dialog", u"Auflage", None))
+ ___qtablewidgetitem4 = self.tableWidget.horizontalHeaderItem(5)
+ ___qtablewidgetitem4.setText(QCoreApplication.translate("Dialog", u"ISBN", None))
+ ___qtablewidgetitem5 = self.tableWidget.horizontalHeaderItem(6)
+ ___qtablewidgetitem5.setText(QCoreApplication.translate("Dialog", u"ID", None))
+ self.reset_btn.setText(QCoreApplication.translate("Dialog", u"Zur\u00fccksetzen", None))
+ self.delete_btn.setText(QCoreApplication.translate("Dialog", u"L\u00f6schen", None))
+ self.cancel_btn.setText(QCoreApplication.translate("Dialog", u"Abbrechen", None))
+ # retranslateUi
+
diff --git a/src/ui/dialogs/dialog_sources/elsa_add_table_entry.ui b/src/ui/dialogs/dialog_sources/elsa_add_table_entry.ui
index 1f44d99..882c722 100644
--- a/src/ui/dialogs/dialog_sources/elsa_add_table_entry.ui
+++ b/src/ui/dialogs/dialog_sources/elsa_add_table_entry.ui
@@ -7,7 +7,7 @@
0
0
529
- 482
+ 484
@@ -208,6 +208,9 @@
-
+
+ Qt::NoFocus
+
@@ -352,6 +355,9 @@ Nachname, Vorname
-
+
+ Qt::NoFocus
+
@@ -471,6 +477,9 @@ Nachname, Vorname
-
+
+ Qt::NoFocus
+
@@ -508,6 +517,9 @@ Nachname, Vorname
-
+
+ Qt::NoFocus
+
true
@@ -522,6 +534,9 @@ Nachname, Vorname
-
+
+ Qt::NoFocus
+
true
@@ -536,6 +551,9 @@ Nachname, Vorname
-
+
+ Qt::NoFocus
+
true
@@ -703,6 +721,51 @@ Nachname, Vorname
+
+ btn_mono
+ btn_hg
+ btn_zs
+ searchIdent
+ btn_search
+ book_author
+ book_year
+ book_edition
+ book_title
+ book_place
+ book_publisher
+ book_signature
+ book_pages
+ book_isbn
+ hg_author
+ hg_year
+ hg_edition
+ hg_chaptertitle
+ hg_editor
+ hg_title
+ hg_place
+ hg_publisher
+ hg_pages
+ hg_signature
+ hg_isbn
+ zs_author
+ zs_year
+ zs_issue
+ zs_chapter_title
+ zs_title
+ zs_place
+ zs_publisher
+ zs_pages
+ zs_signature
+ zs_isbn
+ make_quote
+ file_desc_edit
+ filename_edit
+ ilias_filename
+ copy_filename
+ copy_ilias_filename
+ copy_qoute
+ retryButton
+
diff --git a/src/ui/dialogs/dialog_sources/elsa_add_table_entry_ui.py b/src/ui/dialogs/dialog_sources/elsa_add_table_entry_ui.py
index 3a77b77..04c176a 100644
--- a/src/ui/dialogs/dialog_sources/elsa_add_table_entry_ui.py
+++ b/src/ui/dialogs/dialog_sources/elsa_add_table_entry_ui.py
@@ -1,411 +1,681 @@
-# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\elsa_add_table_entry.ui'
-#
-# Created by: PySide6 UI code generator 6.8.0
-#
-# 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.
+# -*- coding: utf-8 -*-
+################################################################################
+## Form generated from reading UI file 'elsa_add_table_entry.ui'
+##
+## Created by: Qt User Interface Compiler version 6.9.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
-from PySide6 import QtCore, QtGui, QtWidgets
-
+from PySide6.QtCore import (QCoreApplication, QMetaObject, Qt)
+from PySide6.QtWidgets import (QDialogButtonBox,
+ QGridLayout, QGroupBox, QHBoxLayout, QLabel,
+ QLineEdit, QPushButton, QRadioButton, QSizePolicy,
+ QSpacerItem, QStackedWidget, QTextEdit, QToolButton,
+ QVBoxLayout, QWidget)
class Ui_Dialog(object):
def setupUi(self, Dialog):
- Dialog.setObjectName("Dialog")
- Dialog.resize(529, 482)
- self.verticalLayout = QtWidgets.QVBoxLayout(Dialog)
- self.verticalLayout.setObjectName("verticalLayout")
- self.groupBox = QtWidgets.QGroupBox(parent=Dialog)
+ if not Dialog.objectName():
+ Dialog.setObjectName(u"Dialog")
+ Dialog.resize(529, 484)
+ self.verticalLayout = QVBoxLayout(Dialog)
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.groupBox = QGroupBox(Dialog)
+ self.groupBox.setObjectName(u"groupBox")
self.groupBox.setFlat(True)
self.groupBox.setCheckable(False)
- self.groupBox.setObjectName("groupBox")
- self.gridLayout_4 = QtWidgets.QGridLayout(self.groupBox)
- self.gridLayout_4.setObjectName("gridLayout_4")
- spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
- self.gridLayout_4.addItem(spacerItem, 0, 3, 1, 1)
- self.btn_mono = QtWidgets.QRadioButton(parent=self.groupBox)
+ self.gridLayout_4 = QGridLayout(self.groupBox)
+ self.gridLayout_4.setObjectName(u"gridLayout_4")
+ self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
+
+ self.gridLayout_4.addItem(self.horizontalSpacer, 0, 3, 1, 1)
+
+ self.btn_mono = QRadioButton(self.groupBox)
+ self.btn_mono.setObjectName(u"btn_mono")
self.btn_mono.setChecked(False)
- self.btn_mono.setObjectName("btn_mono")
+
self.gridLayout_4.addWidget(self.btn_mono, 0, 0, 1, 1)
- self.btn_zs = QtWidgets.QRadioButton(parent=self.groupBox)
- self.btn_zs.setObjectName("btn_zs")
+
+ self.btn_zs = QRadioButton(self.groupBox)
+ self.btn_zs.setObjectName(u"btn_zs")
+
self.gridLayout_4.addWidget(self.btn_zs, 0, 2, 1, 1)
- self.btn_hg = QtWidgets.QRadioButton(parent=self.groupBox)
- self.btn_hg.setObjectName("btn_hg")
+
+ self.btn_hg = QRadioButton(self.groupBox)
+ self.btn_hg.setObjectName(u"btn_hg")
+
self.gridLayout_4.addWidget(self.btn_hg, 0, 1, 1, 1)
+
+
self.verticalLayout.addWidget(self.groupBox)
- self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
- self.horizontalLayout_2.setObjectName("horizontalLayout_2")
- self.label_2 = QtWidgets.QLabel(parent=Dialog)
- self.label_2.setObjectName("label_2")
+
+ self.horizontalLayout_2 = QHBoxLayout()
+ self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
+ self.label_2 = QLabel(Dialog)
+ self.label_2.setObjectName(u"label_2")
+
self.horizontalLayout_2.addWidget(self.label_2)
- self.searchIdent = QtWidgets.QLineEdit(parent=Dialog)
- self.searchIdent.setObjectName("searchIdent")
+
+ self.searchIdent = QLineEdit(Dialog)
+ self.searchIdent.setObjectName(u"searchIdent")
+
self.horizontalLayout_2.addWidget(self.searchIdent)
- self.btn_search = QtWidgets.QPushButton(parent=Dialog)
- self.btn_search.setObjectName("btn_search")
+
+ self.btn_search = QPushButton(Dialog)
+ self.btn_search.setObjectName(u"btn_search")
+
self.horizontalLayout_2.addWidget(self.btn_search)
- spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
- self.horizontalLayout_2.addItem(spacerItem1)
- self.make_quote = QtWidgets.QPushButton(parent=Dialog)
- self.make_quote.setObjectName("make_quote")
+
+ self.horizontalSpacer_2 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
+
+ self.horizontalLayout_2.addItem(self.horizontalSpacer_2)
+
+ self.make_quote = QPushButton(Dialog)
+ self.make_quote.setObjectName(u"make_quote")
+
self.horizontalLayout_2.addWidget(self.make_quote)
+
+
self.verticalLayout.addLayout(self.horizontalLayout_2)
- self.stackedWidget = QtWidgets.QStackedWidget(parent=Dialog)
- self.stackedWidget.setObjectName("stackedWidget")
- self.mono = QtWidgets.QWidget()
- self.mono.setObjectName("mono")
- self.gridLayout_2 = QtWidgets.QGridLayout(self.mono)
- self.gridLayout_2.setObjectName("gridLayout_2")
- self.label = QtWidgets.QLabel(parent=self.mono)
- self.label.setObjectName("label")
+
+ self.stackedWidget = QStackedWidget(Dialog)
+ self.stackedWidget.setObjectName(u"stackedWidget")
+ self.mono = QWidget()
+ self.mono.setObjectName(u"mono")
+ self.gridLayout_2 = QGridLayout(self.mono)
+ self.gridLayout_2.setObjectName(u"gridLayout_2")
+ self.label = QLabel(self.mono)
+ self.label.setObjectName(u"label")
+
self.gridLayout_2.addWidget(self.label, 0, 0, 1, 1)
- self.book_author = QtWidgets.QLineEdit(parent=self.mono)
- self.book_author.setObjectName("book_author")
+
+ self.book_author = QLineEdit(self.mono)
+ self.book_author.setObjectName(u"book_author")
+
self.gridLayout_2.addWidget(self.book_author, 0, 1, 1, 1)
- self.label_3 = QtWidgets.QLabel(parent=self.mono)
- self.label_3.setObjectName("label_3")
+
+ self.label_3 = QLabel(self.mono)
+ self.label_3.setObjectName(u"label_3")
+
self.gridLayout_2.addWidget(self.label_3, 1, 0, 1, 1)
- self.book_year = QtWidgets.QLineEdit(parent=self.mono)
- self.book_year.setObjectName("book_year")
+
+ self.book_year = QLineEdit(self.mono)
+ self.book_year.setObjectName(u"book_year")
+
self.gridLayout_2.addWidget(self.book_year, 1, 1, 1, 1)
- self.label_4 = QtWidgets.QLabel(parent=self.mono)
- self.label_4.setObjectName("label_4")
+
+ self.label_4 = QLabel(self.mono)
+ self.label_4.setObjectName(u"label_4")
+
self.gridLayout_2.addWidget(self.label_4, 2, 0, 1, 1)
- self.book_edition = QtWidgets.QLineEdit(parent=self.mono)
- self.book_edition.setObjectName("book_edition")
+
+ self.book_edition = QLineEdit(self.mono)
+ self.book_edition.setObjectName(u"book_edition")
+
self.gridLayout_2.addWidget(self.book_edition, 2, 1, 1, 1)
- self.label_5 = QtWidgets.QLabel(parent=self.mono)
- self.label_5.setObjectName("label_5")
+
+ self.label_5 = QLabel(self.mono)
+ self.label_5.setObjectName(u"label_5")
+
self.gridLayout_2.addWidget(self.label_5, 3, 0, 1, 1)
- self.book_title = QtWidgets.QLineEdit(parent=self.mono)
- self.book_title.setObjectName("book_title")
+
+ self.book_title = QLineEdit(self.mono)
+ self.book_title.setObjectName(u"book_title")
+
self.gridLayout_2.addWidget(self.book_title, 3, 1, 1, 1)
- self.label_6 = QtWidgets.QLabel(parent=self.mono)
- self.label_6.setObjectName("label_6")
+
+ self.label_6 = QLabel(self.mono)
+ self.label_6.setObjectName(u"label_6")
+
self.gridLayout_2.addWidget(self.label_6, 4, 0, 1, 1)
- self.book_place = QtWidgets.QLineEdit(parent=self.mono)
- self.book_place.setObjectName("book_place")
+
+ self.book_place = QLineEdit(self.mono)
+ self.book_place.setObjectName(u"book_place")
+
self.gridLayout_2.addWidget(self.book_place, 4, 1, 1, 1)
- self.label_7 = QtWidgets.QLabel(parent=self.mono)
- self.label_7.setObjectName("label_7")
+
+ self.label_7 = QLabel(self.mono)
+ self.label_7.setObjectName(u"label_7")
+
self.gridLayout_2.addWidget(self.label_7, 5, 0, 1, 1)
- self.book_publisher = QtWidgets.QLineEdit(parent=self.mono)
- self.book_publisher.setObjectName("book_publisher")
+
+ self.book_publisher = QLineEdit(self.mono)
+ self.book_publisher.setObjectName(u"book_publisher")
+
self.gridLayout_2.addWidget(self.book_publisher, 5, 1, 1, 1)
- self.label_8 = QtWidgets.QLabel(parent=self.mono)
- self.label_8.setObjectName("label_8")
+
+ self.label_8 = QLabel(self.mono)
+ self.label_8.setObjectName(u"label_8")
+
self.gridLayout_2.addWidget(self.label_8, 6, 0, 1, 1)
- self.book_signature = QtWidgets.QLineEdit(parent=self.mono)
- self.book_signature.setObjectName("book_signature")
+
+ self.book_signature = QLineEdit(self.mono)
+ self.book_signature.setObjectName(u"book_signature")
+
self.gridLayout_2.addWidget(self.book_signature, 6, 1, 1, 1)
- self.label_9 = QtWidgets.QLabel(parent=self.mono)
- self.label_9.setObjectName("label_9")
+
+ self.label_9 = QLabel(self.mono)
+ self.label_9.setObjectName(u"label_9")
+
self.gridLayout_2.addWidget(self.label_9, 7, 0, 1, 1)
- self.book_pages = QtWidgets.QLineEdit(parent=self.mono)
- self.book_pages.setObjectName("book_pages")
+
+ self.book_pages = QLineEdit(self.mono)
+ self.book_pages.setObjectName(u"book_pages")
+
self.gridLayout_2.addWidget(self.book_pages, 7, 1, 1, 1)
- self.page_warn_2 = QtWidgets.QToolButton(parent=self.mono)
- self.page_warn_2.setText("")
+
+ self.page_warn_2 = QToolButton(self.mono)
+ self.page_warn_2.setObjectName(u"page_warn_2")
+ self.page_warn_2.setFocusPolicy(Qt.NoFocus)
self.page_warn_2.setAutoRaise(True)
- self.page_warn_2.setObjectName("page_warn_2")
+
self.gridLayout_2.addWidget(self.page_warn_2, 7, 2, 1, 1)
- self.label_29 = QtWidgets.QLabel(parent=self.mono)
- self.label_29.setObjectName("label_29")
+
+ self.label_29 = QLabel(self.mono)
+ self.label_29.setObjectName(u"label_29")
+
self.gridLayout_2.addWidget(self.label_29, 8, 0, 1, 1)
- self.book_isbn = QtWidgets.QLineEdit(parent=self.mono)
- self.book_isbn.setObjectName("book_isbn")
+
+ self.book_isbn = QLineEdit(self.mono)
+ self.book_isbn.setObjectName(u"book_isbn")
+
self.gridLayout_2.addWidget(self.book_isbn, 8, 1, 1, 1)
+
self.stackedWidget.addWidget(self.mono)
- self.hg = QtWidgets.QWidget()
- self.hg.setObjectName("hg")
- self.gridLayout_3 = QtWidgets.QGridLayout(self.hg)
- self.gridLayout_3.setObjectName("gridLayout_3")
- self.hg_editor = QtWidgets.QLineEdit(parent=self.hg)
- self.hg_editor.setObjectName("hg_editor")
+ self.hg = QWidget()
+ self.hg.setObjectName(u"hg")
+ self.gridLayout_3 = QGridLayout(self.hg)
+ self.gridLayout_3.setObjectName(u"gridLayout_3")
+ self.hg_editor = QLineEdit(self.hg)
+ self.hg_editor.setObjectName(u"hg_editor")
+
self.gridLayout_3.addWidget(self.hg_editor, 4, 1, 1, 1)
- self.label_26 = QtWidgets.QLabel(parent=self.hg)
- self.label_26.setObjectName("label_26")
+
+ self.label_26 = QLabel(self.hg)
+ self.label_26.setObjectName(u"label_26")
+
self.gridLayout_3.addWidget(self.label_26, 7, 0, 1, 1)
- self.hg_edition = QtWidgets.QLineEdit(parent=self.hg)
- self.hg_edition.setObjectName("hg_edition")
+
+ self.hg_edition = QLineEdit(self.hg)
+ self.hg_edition.setObjectName(u"hg_edition")
+
self.gridLayout_3.addWidget(self.hg_edition, 2, 1, 1, 1)
- self.label_20 = QtWidgets.QLabel(parent=self.hg)
- self.label_20.setObjectName("label_20")
+
+ self.label_20 = QLabel(self.hg)
+ self.label_20.setObjectName(u"label_20")
+
self.gridLayout_3.addWidget(self.label_20, 1, 0, 1, 1)
- self.label_24 = QtWidgets.QLabel(parent=self.hg)
- self.label_24.setObjectName("label_24")
+
+ self.label_24 = QLabel(self.hg)
+ self.label_24.setObjectName(u"label_24")
+
self.gridLayout_3.addWidget(self.label_24, 3, 0, 1, 1)
- self.label_27 = QtWidgets.QLabel(parent=self.hg)
- self.label_27.setObjectName("label_27")
+
+ self.label_27 = QLabel(self.hg)
+ self.label_27.setObjectName(u"label_27")
+
self.gridLayout_3.addWidget(self.label_27, 8, 0, 1, 1)
- self.label_28 = QtWidgets.QLabel(parent=self.hg)
- self.label_28.setObjectName("label_28")
+
+ self.label_28 = QLabel(self.hg)
+ self.label_28.setObjectName(u"label_28")
+
self.gridLayout_3.addWidget(self.label_28, 9, 0, 1, 1)
- self.label_23 = QtWidgets.QLabel(parent=self.hg)
- self.label_23.setObjectName("label_23")
+
+ self.label_23 = QLabel(self.hg)
+ self.label_23.setObjectName(u"label_23")
+
self.gridLayout_3.addWidget(self.label_23, 5, 0, 1, 1)
- self.label_21 = QtWidgets.QLabel(parent=self.hg)
- self.label_21.setObjectName("label_21")
+
+ self.label_21 = QLabel(self.hg)
+ self.label_21.setObjectName(u"label_21")
+
self.gridLayout_3.addWidget(self.label_21, 2, 0, 1, 1)
- self.hg_pages = QtWidgets.QLineEdit(parent=self.hg)
- self.hg_pages.setObjectName("hg_pages")
+
+ self.hg_pages = QLineEdit(self.hg)
+ self.hg_pages.setObjectName(u"hg_pages")
+
self.gridLayout_3.addWidget(self.hg_pages, 8, 1, 1, 1)
- self.label_19 = QtWidgets.QLabel(parent=self.hg)
- self.label_19.setObjectName("label_19")
+
+ self.label_19 = QLabel(self.hg)
+ self.label_19.setObjectName(u"label_19")
+
self.gridLayout_3.addWidget(self.label_19, 0, 0, 1, 1)
- self.hg_signature = QtWidgets.QLineEdit(parent=self.hg)
- self.hg_signature.setObjectName("hg_signature")
+
+ self.hg_signature = QLineEdit(self.hg)
+ self.hg_signature.setObjectName(u"hg_signature")
+
self.gridLayout_3.addWidget(self.hg_signature, 9, 1, 1, 1)
- self.label_30 = QtWidgets.QLabel(parent=self.hg)
- self.label_30.setObjectName("label_30")
+
+ self.label_30 = QLabel(self.hg)
+ self.label_30.setObjectName(u"label_30")
+
self.gridLayout_3.addWidget(self.label_30, 10, 0, 1, 1)
- self.label_25 = QtWidgets.QLabel(parent=self.hg)
- self.label_25.setObjectName("label_25")
+
+ self.label_25 = QLabel(self.hg)
+ self.label_25.setObjectName(u"label_25")
+
self.gridLayout_3.addWidget(self.label_25, 6, 0, 1, 1)
- self.hg_year = QtWidgets.QLineEdit(parent=self.hg)
- self.hg_year.setObjectName("hg_year")
+
+ self.hg_year = QLineEdit(self.hg)
+ self.hg_year.setObjectName(u"hg_year")
+
self.gridLayout_3.addWidget(self.hg_year, 1, 1, 1, 1)
- self.label_22 = QtWidgets.QLabel(parent=self.hg)
- self.label_22.setObjectName("label_22")
+
+ self.label_22 = QLabel(self.hg)
+ self.label_22.setObjectName(u"label_22")
+
self.gridLayout_3.addWidget(self.label_22, 4, 0, 1, 1)
- self.hg_title = QtWidgets.QLineEdit(parent=self.hg)
- self.hg_title.setObjectName("hg_title")
+
+ self.hg_title = QLineEdit(self.hg)
+ self.hg_title.setObjectName(u"hg_title")
+
self.gridLayout_3.addWidget(self.hg_title, 5, 1, 1, 1)
- self.hg_chaptertitle = QtWidgets.QLineEdit(parent=self.hg)
- self.hg_chaptertitle.setObjectName("hg_chaptertitle")
+
+ self.hg_chaptertitle = QLineEdit(self.hg)
+ self.hg_chaptertitle.setObjectName(u"hg_chaptertitle")
+
self.gridLayout_3.addWidget(self.hg_chaptertitle, 3, 1, 1, 1)
- self.hg_author = QtWidgets.QLineEdit(parent=self.hg)
- self.hg_author.setObjectName("hg_author")
+
+ self.hg_author = QLineEdit(self.hg)
+ self.hg_author.setObjectName(u"hg_author")
+
self.gridLayout_3.addWidget(self.hg_author, 0, 1, 1, 1)
- self.hg_isbn = QtWidgets.QLineEdit(parent=self.hg)
- self.hg_isbn.setObjectName("hg_isbn")
+
+ self.hg_isbn = QLineEdit(self.hg)
+ self.hg_isbn.setObjectName(u"hg_isbn")
+
self.gridLayout_3.addWidget(self.hg_isbn, 10, 1, 1, 1)
- self.hg_publisher = QtWidgets.QLineEdit(parent=self.hg)
- self.hg_publisher.setObjectName("hg_publisher")
+
+ self.hg_publisher = QLineEdit(self.hg)
+ self.hg_publisher.setObjectName(u"hg_publisher")
+
self.gridLayout_3.addWidget(self.hg_publisher, 7, 1, 1, 1)
- self.hg_place = QtWidgets.QLineEdit(parent=self.hg)
- self.hg_place.setObjectName("hg_place")
+
+ self.hg_place = QLineEdit(self.hg)
+ self.hg_place.setObjectName(u"hg_place")
+
self.gridLayout_3.addWidget(self.hg_place, 6, 1, 1, 1)
- self.page_warn_3 = QtWidgets.QToolButton(parent=self.hg)
- self.page_warn_3.setText("")
+
+ self.page_warn_3 = QToolButton(self.hg)
+ self.page_warn_3.setObjectName(u"page_warn_3")
+ self.page_warn_3.setFocusPolicy(Qt.NoFocus)
self.page_warn_3.setAutoRaise(True)
- self.page_warn_3.setObjectName("page_warn_3")
+
self.gridLayout_3.addWidget(self.page_warn_3, 8, 2, 1, 1)
+
self.stackedWidget.addWidget(self.hg)
- self.zs = QtWidgets.QWidget()
- self.zs.setObjectName("zs")
- self.gridLayout = QtWidgets.QGridLayout(self.zs)
- self.gridLayout.setObjectName("gridLayout")
- self.label_10 = QtWidgets.QLabel(parent=self.zs)
- self.label_10.setObjectName("label_10")
+ self.zs = QWidget()
+ self.zs.setObjectName(u"zs")
+ self.gridLayout = QGridLayout(self.zs)
+ self.gridLayout.setObjectName(u"gridLayout")
+ self.label_10 = QLabel(self.zs)
+ self.label_10.setObjectName(u"label_10")
+
self.gridLayout.addWidget(self.label_10, 0, 0, 1, 1)
- self.zs_publisher = QtWidgets.QLineEdit(parent=self.zs)
- self.zs_publisher.setObjectName("zs_publisher")
+
+ self.zs_publisher = QLineEdit(self.zs)
+ self.zs_publisher.setObjectName(u"zs_publisher")
+
self.gridLayout.addWidget(self.zs_publisher, 6, 1, 1, 1)
- self.zs_place = QtWidgets.QLineEdit(parent=self.zs)
- self.zs_place.setObjectName("zs_place")
+
+ self.zs_place = QLineEdit(self.zs)
+ self.zs_place.setObjectName(u"zs_place")
+
self.gridLayout.addWidget(self.zs_place, 5, 1, 1, 1)
- self.label_14 = QtWidgets.QLabel(parent=self.zs)
- self.label_14.setObjectName("label_14")
+
+ self.label_14 = QLabel(self.zs)
+ self.label_14.setObjectName(u"label_14")
+
self.gridLayout.addWidget(self.label_14, 4, 0, 1, 1)
- self.label_11 = QtWidgets.QLabel(parent=self.zs)
- self.label_11.setObjectName("label_11")
+
+ self.label_11 = QLabel(self.zs)
+ self.label_11.setObjectName(u"label_11")
+
self.gridLayout.addWidget(self.label_11, 1, 0, 1, 1)
- self.zs_year = QtWidgets.QLineEdit(parent=self.zs)
- self.zs_year.setObjectName("zs_year")
+
+ self.zs_year = QLineEdit(self.zs)
+ self.zs_year.setObjectName(u"zs_year")
+
self.gridLayout.addWidget(self.zs_year, 1, 1, 1, 1)
- self.label_17 = QtWidgets.QLabel(parent=self.zs)
- self.label_17.setObjectName("label_17")
+
+ self.label_17 = QLabel(self.zs)
+ self.label_17.setObjectName(u"label_17")
+
self.gridLayout.addWidget(self.label_17, 7, 0, 1, 1)
- self.label_16 = QtWidgets.QLabel(parent=self.zs)
- self.label_16.setObjectName("label_16")
+
+ self.label_16 = QLabel(self.zs)
+ self.label_16.setObjectName(u"label_16")
+
self.gridLayout.addWidget(self.label_16, 6, 0, 1, 1)
- self.zs_issue = QtWidgets.QLineEdit(parent=self.zs)
- self.zs_issue.setObjectName("zs_issue")
+
+ self.zs_issue = QLineEdit(self.zs)
+ self.zs_issue.setObjectName(u"zs_issue")
+
self.gridLayout.addWidget(self.zs_issue, 2, 1, 1, 1)
- self.zs_chapter_title = QtWidgets.QLineEdit(parent=self.zs)
- self.zs_chapter_title.setObjectName("zs_chapter_title")
+
+ self.zs_chapter_title = QLineEdit(self.zs)
+ self.zs_chapter_title.setObjectName(u"zs_chapter_title")
+
self.gridLayout.addWidget(self.zs_chapter_title, 3, 1, 1, 1)
- self.zs_isbn = QtWidgets.QLineEdit(parent=self.zs)
- self.zs_isbn.setObjectName("zs_isbn")
+
+ self.zs_isbn = QLineEdit(self.zs)
+ self.zs_isbn.setObjectName(u"zs_isbn")
+
self.gridLayout.addWidget(self.zs_isbn, 9, 1, 1, 1)
- self.label_12 = QtWidgets.QLabel(parent=self.zs)
- self.label_12.setObjectName("label_12")
+
+ self.label_12 = QLabel(self.zs)
+ self.label_12.setObjectName(u"label_12")
+
self.gridLayout.addWidget(self.label_12, 2, 0, 1, 1)
- self.label_31 = QtWidgets.QLabel(parent=self.zs)
- self.label_31.setObjectName("label_31")
+
+ self.label_31 = QLabel(self.zs)
+ self.label_31.setObjectName(u"label_31")
+
self.gridLayout.addWidget(self.label_31, 9, 0, 1, 1)
- self.label_15 = QtWidgets.QLabel(parent=self.zs)
- self.label_15.setObjectName("label_15")
+
+ self.label_15 = QLabel(self.zs)
+ self.label_15.setObjectName(u"label_15")
+
self.gridLayout.addWidget(self.label_15, 5, 0, 1, 1)
- self.zs_signature = QtWidgets.QLineEdit(parent=self.zs)
- self.zs_signature.setObjectName("zs_signature")
+
+ self.zs_signature = QLineEdit(self.zs)
+ self.zs_signature.setObjectName(u"zs_signature")
+
self.gridLayout.addWidget(self.zs_signature, 8, 1, 1, 1)
- self.zs_pages = QtWidgets.QLineEdit(parent=self.zs)
- self.zs_pages.setObjectName("zs_pages")
+
+ self.zs_pages = QLineEdit(self.zs)
+ self.zs_pages.setObjectName(u"zs_pages")
+
self.gridLayout.addWidget(self.zs_pages, 7, 1, 1, 1)
- self.label_13 = QtWidgets.QLabel(parent=self.zs)
- self.label_13.setObjectName("label_13")
+
+ self.label_13 = QLabel(self.zs)
+ self.label_13.setObjectName(u"label_13")
+
self.gridLayout.addWidget(self.label_13, 3, 0, 1, 1)
- self.label_18 = QtWidgets.QLabel(parent=self.zs)
- self.label_18.setObjectName("label_18")
+
+ self.label_18 = QLabel(self.zs)
+ self.label_18.setObjectName(u"label_18")
+
self.gridLayout.addWidget(self.label_18, 8, 0, 1, 1)
- self.zs_author = QtWidgets.QLineEdit(parent=self.zs)
- self.zs_author.setObjectName("zs_author")
+
+ self.zs_author = QLineEdit(self.zs)
+ self.zs_author.setObjectName(u"zs_author")
+
self.gridLayout.addWidget(self.zs_author, 0, 1, 1, 1)
- self.zs_title = QtWidgets.QLineEdit(parent=self.zs)
- self.zs_title.setObjectName("zs_title")
+
+ self.zs_title = QLineEdit(self.zs)
+ self.zs_title.setObjectName(u"zs_title")
+
self.gridLayout.addWidget(self.zs_title, 4, 1, 1, 1)
- self.page_warn = QtWidgets.QToolButton(parent=self.zs)
- self.page_warn.setText("")
+
+ self.page_warn = QToolButton(self.zs)
+ self.page_warn.setObjectName(u"page_warn")
+ self.page_warn.setFocusPolicy(Qt.NoFocus)
self.page_warn.setAutoRaise(True)
- self.page_warn.setObjectName("page_warn")
+
self.gridLayout.addWidget(self.page_warn, 7, 2, 1, 1)
+
self.stackedWidget.addWidget(self.zs)
- self.page = QtWidgets.QWidget()
- self.page.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight)
- self.page.setObjectName("page")
- self.gridLayout_5 = QtWidgets.QGridLayout(self.page)
- self.gridLayout_5.setObjectName("gridLayout_5")
- self.label_32 = QtWidgets.QLabel(parent=self.page)
- self.label_32.setObjectName("label_32")
+ self.page = QWidget()
+ self.page.setObjectName(u"page")
+ self.page.setLayoutDirection(Qt.LeftToRight)
+ self.gridLayout_5 = QGridLayout(self.page)
+ self.gridLayout_5.setObjectName(u"gridLayout_5")
+ self.label_32 = QLabel(self.page)
+ self.label_32.setObjectName(u"label_32")
+
self.gridLayout_5.addWidget(self.label_32, 0, 0, 1, 1)
- spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
- self.gridLayout_5.addItem(spacerItem2, 7, 0, 1, 1)
- self.file_desc_edit = QtWidgets.QTextEdit(parent=self.page)
+
+ self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
+
+ self.gridLayout_5.addItem(self.verticalSpacer, 7, 0, 1, 1)
+
+ self.file_desc_edit = QTextEdit(self.page)
+ self.file_desc_edit.setObjectName(u"file_desc_edit")
+ self.file_desc_edit.setFocusPolicy(Qt.NoFocus)
self.file_desc_edit.setReadOnly(True)
- self.file_desc_edit.setObjectName("file_desc_edit")
+
self.gridLayout_5.addWidget(self.file_desc_edit, 6, 0, 1, 1)
- self.label_34 = QtWidgets.QLabel(parent=self.page)
- self.label_34.setObjectName("label_34")
+
+ self.label_34 = QLabel(self.page)
+ self.label_34.setObjectName(u"label_34")
+
self.gridLayout_5.addWidget(self.label_34, 3, 0, 1, 1)
- self.filename_edit = QtWidgets.QTextEdit(parent=self.page)
+
+ self.filename_edit = QTextEdit(self.page)
+ self.filename_edit.setObjectName(u"filename_edit")
+ self.filename_edit.setFocusPolicy(Qt.NoFocus)
self.filename_edit.setReadOnly(True)
- self.filename_edit.setObjectName("filename_edit")
+
self.gridLayout_5.addWidget(self.filename_edit, 1, 0, 1, 1)
- self.label_33 = QtWidgets.QLabel(parent=self.page)
- self.label_33.setObjectName("label_33")
+
+ self.label_33 = QLabel(self.page)
+ self.label_33.setObjectName(u"label_33")
+
self.gridLayout_5.addWidget(self.label_33, 5, 0, 1, 1)
- self.ilias_filename = QtWidgets.QTextEdit(parent=self.page)
+
+ self.ilias_filename = QTextEdit(self.page)
+ self.ilias_filename.setObjectName(u"ilias_filename")
+ self.ilias_filename.setFocusPolicy(Qt.NoFocus)
self.ilias_filename.setReadOnly(True)
- self.ilias_filename.setObjectName("ilias_filename")
+
self.gridLayout_5.addWidget(self.ilias_filename, 4, 0, 1, 1)
- self.verticalLayout_2 = QtWidgets.QVBoxLayout()
- self.verticalLayout_2.setObjectName("verticalLayout_2")
- spacerItem3 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
- self.verticalLayout_2.addItem(spacerItem3)
- self.copy_filename = QtWidgets.QToolButton(parent=self.page)
- self.copy_filename.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight)
+
+ self.verticalLayout_2 = QVBoxLayout()
+ self.verticalLayout_2.setObjectName(u"verticalLayout_2")
+ self.verticalSpacer_3 = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
+
+ self.verticalLayout_2.addItem(self.verticalSpacer_3)
+
+ self.copy_filename = QToolButton(self.page)
+ self.copy_filename.setObjectName(u"copy_filename")
+ self.copy_filename.setLayoutDirection(Qt.LeftToRight)
self.copy_filename.setAutoFillBackground(False)
- self.copy_filename.setObjectName("copy_filename")
+
self.verticalLayout_2.addWidget(self.copy_filename)
- self.filename_edit_label = QtWidgets.QLabel(parent=self.page)
- self.filename_edit_label.setText("")
- self.filename_edit_label.setObjectName("filename_edit_label")
+
+ self.filename_edit_label = QLabel(self.page)
+ self.filename_edit_label.setObjectName(u"filename_edit_label")
+
self.verticalLayout_2.addWidget(self.filename_edit_label)
- spacerItem4 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
- self.verticalLayout_2.addItem(spacerItem4)
+
+ self.verticalSpacer_2 = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
+
+ self.verticalLayout_2.addItem(self.verticalSpacer_2)
+
+
self.gridLayout_5.addLayout(self.verticalLayout_2, 1, 1, 1, 1)
- self.verticalLayout_3 = QtWidgets.QVBoxLayout()
- self.verticalLayout_3.setObjectName("verticalLayout_3")
- spacerItem5 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
- self.verticalLayout_3.addItem(spacerItem5)
- self.copy_ilias_filename = QtWidgets.QToolButton(parent=self.page)
- self.copy_ilias_filename.setObjectName("copy_ilias_filename")
+
+ self.verticalLayout_3 = QVBoxLayout()
+ self.verticalLayout_3.setObjectName(u"verticalLayout_3")
+ self.verticalSpacer_5 = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
+
+ self.verticalLayout_3.addItem(self.verticalSpacer_5)
+
+ self.copy_ilias_filename = QToolButton(self.page)
+ self.copy_ilias_filename.setObjectName(u"copy_ilias_filename")
+
self.verticalLayout_3.addWidget(self.copy_ilias_filename)
- self.ilias_filename_label = QtWidgets.QLabel(parent=self.page)
- self.ilias_filename_label.setText("")
- self.ilias_filename_label.setObjectName("ilias_filename_label")
+
+ self.ilias_filename_label = QLabel(self.page)
+ self.ilias_filename_label.setObjectName(u"ilias_filename_label")
+
self.verticalLayout_3.addWidget(self.ilias_filename_label)
- spacerItem6 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
- self.verticalLayout_3.addItem(spacerItem6)
+
+ self.verticalSpacer_4 = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
+
+ self.verticalLayout_3.addItem(self.verticalSpacer_4)
+
+
self.gridLayout_5.addLayout(self.verticalLayout_3, 4, 1, 1, 1)
- self.verticalLayout_4 = QtWidgets.QVBoxLayout()
- self.verticalLayout_4.setObjectName("verticalLayout_4")
- spacerItem7 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
- self.verticalLayout_4.addItem(spacerItem7)
- self.copy_qoute = QtWidgets.QToolButton(parent=self.page)
- self.copy_qoute.setObjectName("copy_qoute")
+
+ self.verticalLayout_4 = QVBoxLayout()
+ self.verticalLayout_4.setObjectName(u"verticalLayout_4")
+ self.verticalSpacer_7 = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
+
+ self.verticalLayout_4.addItem(self.verticalSpacer_7)
+
+ self.copy_qoute = QToolButton(self.page)
+ self.copy_qoute.setObjectName(u"copy_qoute")
+
self.verticalLayout_4.addWidget(self.copy_qoute)
- self.file_desc_edit_label = QtWidgets.QLabel(parent=self.page)
- self.file_desc_edit_label.setText("")
- self.file_desc_edit_label.setObjectName("file_desc_edit_label")
+
+ self.file_desc_edit_label = QLabel(self.page)
+ self.file_desc_edit_label.setObjectName(u"file_desc_edit_label")
+
self.verticalLayout_4.addWidget(self.file_desc_edit_label)
- spacerItem8 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
- self.verticalLayout_4.addItem(spacerItem8)
+
+ self.verticalSpacer_6 = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
+
+ self.verticalLayout_4.addItem(self.verticalSpacer_6)
+
+
self.gridLayout_5.addLayout(self.verticalLayout_4, 6, 1, 1, 1)
+
self.stackedWidget.addWidget(self.page)
+
self.verticalLayout.addWidget(self.stackedWidget)
- self.horizontalLayout = QtWidgets.QHBoxLayout()
- self.horizontalLayout.setObjectName("horizontalLayout")
- self.buttonBox = QtWidgets.QDialogButtonBox(parent=Dialog)
- self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.StandardButton.Cancel|QtWidgets.QDialogButtonBox.StandardButton.Discard|QtWidgets.QDialogButtonBox.StandardButton.Ok)
- self.buttonBox.setObjectName("buttonBox")
+
+ self.horizontalLayout = QHBoxLayout()
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.buttonBox = QDialogButtonBox(Dialog)
+ self.buttonBox.setObjectName(u"buttonBox")
+ self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Discard|QDialogButtonBox.Ok)
+
self.horizontalLayout.addWidget(self.buttonBox)
- self.retryButton = QtWidgets.QPushButton(parent=Dialog)
- self.retryButton.setObjectName("retryButton")
+
+ self.retryButton = QPushButton(Dialog)
+ self.retryButton.setObjectName(u"retryButton")
+
self.horizontalLayout.addWidget(self.retryButton)
+
+
self.verticalLayout.addLayout(self.horizontalLayout)
+ QWidget.setTabOrder(self.btn_mono, self.btn_hg)
+ QWidget.setTabOrder(self.btn_hg, self.btn_zs)
+ QWidget.setTabOrder(self.btn_zs, self.searchIdent)
+ QWidget.setTabOrder(self.searchIdent, self.btn_search)
+ QWidget.setTabOrder(self.btn_search, self.book_author)
+ QWidget.setTabOrder(self.book_author, self.book_year)
+ QWidget.setTabOrder(self.book_year, self.book_edition)
+ QWidget.setTabOrder(self.book_edition, self.book_title)
+ QWidget.setTabOrder(self.book_title, self.book_place)
+ QWidget.setTabOrder(self.book_place, self.book_publisher)
+ QWidget.setTabOrder(self.book_publisher, self.book_signature)
+ QWidget.setTabOrder(self.book_signature, self.book_pages)
+ QWidget.setTabOrder(self.book_pages, self.book_isbn)
+ QWidget.setTabOrder(self.book_isbn, self.hg_author)
+ QWidget.setTabOrder(self.hg_author, self.hg_year)
+ QWidget.setTabOrder(self.hg_year, self.hg_edition)
+ QWidget.setTabOrder(self.hg_edition, self.hg_chaptertitle)
+ QWidget.setTabOrder(self.hg_chaptertitle, self.hg_editor)
+ QWidget.setTabOrder(self.hg_editor, self.hg_title)
+ QWidget.setTabOrder(self.hg_title, self.hg_place)
+ QWidget.setTabOrder(self.hg_place, self.hg_publisher)
+ QWidget.setTabOrder(self.hg_publisher, self.hg_pages)
+ QWidget.setTabOrder(self.hg_pages, self.hg_signature)
+ QWidget.setTabOrder(self.hg_signature, self.hg_isbn)
+ QWidget.setTabOrder(self.hg_isbn, self.zs_author)
+ QWidget.setTabOrder(self.zs_author, self.zs_year)
+ QWidget.setTabOrder(self.zs_year, self.zs_issue)
+ QWidget.setTabOrder(self.zs_issue, self.zs_chapter_title)
+ QWidget.setTabOrder(self.zs_chapter_title, self.zs_title)
+ QWidget.setTabOrder(self.zs_title, self.zs_place)
+ QWidget.setTabOrder(self.zs_place, self.zs_publisher)
+ QWidget.setTabOrder(self.zs_publisher, self.zs_pages)
+ QWidget.setTabOrder(self.zs_pages, self.zs_signature)
+ QWidget.setTabOrder(self.zs_signature, self.zs_isbn)
+ QWidget.setTabOrder(self.zs_isbn, self.make_quote)
+ QWidget.setTabOrder(self.make_quote, self.file_desc_edit)
+ QWidget.setTabOrder(self.file_desc_edit, self.filename_edit)
+ QWidget.setTabOrder(self.filename_edit, self.ilias_filename)
+ QWidget.setTabOrder(self.ilias_filename, self.copy_filename)
+ QWidget.setTabOrder(self.copy_filename, self.copy_ilias_filename)
+ QWidget.setTabOrder(self.copy_ilias_filename, self.copy_qoute)
+ QWidget.setTabOrder(self.copy_qoute, self.retryButton)
+
self.retranslateUi(Dialog)
+
self.stackedWidget.setCurrentIndex(3)
- QtCore.QMetaObject.connectSlotsByName(Dialog)
+
+
+ QMetaObject.connectSlotsByName(Dialog)
+ # setupUi
def retranslateUi(self, Dialog):
- _translate = QtCore.QCoreApplication.translate
- Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
- self.groupBox.setTitle(_translate("Dialog", "Medientyp?"))
- self.btn_mono.setText(_translate("Dialog", "Monografie"))
- self.btn_zs.setText(_translate("Dialog", "Zeitschrift"))
- self.btn_hg.setText(_translate("Dialog", "Herausgeberwerk"))
- self.label_2.setText(_translate("Dialog", "Identifikator"))
- self.btn_search.setText(_translate("Dialog", "Suchen"))
- self.make_quote.setToolTip(_translate("Dialog", "Zuerst die Seitenzahl anpassen"))
- self.make_quote.setText(_translate("Dialog", "Zitat erstellen"))
- self.label.setText(_translate("Dialog", "Autor(en)\n"
-" Nachname, Vorname"))
- self.book_author.setToolTip(_translate("Dialog", "Bei mehreren Autoren mit ; trennen"))
- self.label_3.setText(_translate("Dialog", "Jahr"))
- self.label_4.setText(_translate("Dialog", "Auflage"))
- self.label_5.setText(_translate("Dialog", "Titel"))
- self.label_6.setText(_translate("Dialog", "Ort"))
- self.label_7.setText(_translate("Dialog", "Verlag"))
- self.label_8.setText(_translate("Dialog", "Signatur"))
- self.label_9.setText(_translate("Dialog", "Seiten"))
- self.book_pages.setPlaceholderText(_translate("Dialog", "Seitenanzahl des Mediums, zum zitieren ändern!"))
- self.label_29.setText(_translate("Dialog", "ISBN"))
- self.hg_editor.setToolTip(_translate("Dialog", "Bei mehreren Autoren mit ; trennen"))
- self.label_26.setText(_translate("Dialog", "Verlag"))
- self.label_20.setText(_translate("Dialog", "Jahr"))
- self.label_24.setText(_translate("Dialog", "Beitragstitel"))
- self.label_27.setText(_translate("Dialog", "Seiten"))
- self.label_28.setText(_translate("Dialog", "Signatur"))
- self.label_23.setText(_translate("Dialog", "Titel des Werkes"))
- self.label_21.setText(_translate("Dialog", "Auflage"))
- self.label_19.setText(_translate("Dialog", "Autor(en)\n"
-"Nachname, Vorname"))
- self.label_30.setText(_translate("Dialog", "ISBN"))
- self.label_25.setText(_translate("Dialog", "Ort"))
- self.label_22.setText(_translate("Dialog", "Herausgebername(n)\n"
-"Nachname, Vorname"))
- self.hg_author.setToolTip(_translate("Dialog", "Bei mehreren Autoren mit ; trennen"))
- self.label_10.setText(_translate("Dialog", "Autor(en)\n"
-"Nachname, Vorname"))
- self.label_14.setText(_translate("Dialog", "Name der Zeitschrift"))
- self.label_11.setText(_translate("Dialog", "Jahr"))
- self.label_17.setText(_translate("Dialog", "Seiten"))
- self.label_16.setText(_translate("Dialog", "Verlag"))
- self.label_12.setText(_translate("Dialog", "Heft"))
- self.label_31.setText(_translate("Dialog", "ISSN"))
- self.label_15.setText(_translate("Dialog", "Ort"))
- self.label_13.setText(_translate("Dialog", "Artikeltitel"))
- self.label_18.setText(_translate("Dialog", "Signatur"))
- self.zs_author.setToolTip(_translate("Dialog", "Bei mehreren Autoren mit ; trennen"))
- self.label_32.setText(_translate("Dialog", "Dateiname"))
- self.label_34.setText(_translate("Dialog", "ILIAS Name"))
- self.label_33.setText(_translate("Dialog", "ILIAS Dateibeschreibung"))
- self.copy_filename.setText(_translate("Dialog", "Kopieren"))
- self.copy_ilias_filename.setText(_translate("Dialog", "Kopieren"))
- self.copy_qoute.setText(_translate("Dialog", "Kopieren"))
- self.retryButton.setText(_translate("Dialog", "Wiederholen"))
+ Dialog.setWindowTitle(QCoreApplication.translate("Dialog", u"Dialog", None))
+ self.groupBox.setTitle(QCoreApplication.translate("Dialog", u"Medientyp?", None))
+ self.btn_mono.setText(QCoreApplication.translate("Dialog", u"Monografie", None))
+ self.btn_zs.setText(QCoreApplication.translate("Dialog", u"Zeitschrift", None))
+ self.btn_hg.setText(QCoreApplication.translate("Dialog", u"Herausgeberwerk", None))
+ self.label_2.setText(QCoreApplication.translate("Dialog", u"Identifikator", None))
+ self.btn_search.setText(QCoreApplication.translate("Dialog", u"Suchen", None))
+#if QT_CONFIG(tooltip)
+ self.make_quote.setToolTip(QCoreApplication.translate("Dialog", u"Zuerst die Seitenzahl anpassen", None))
+#endif // QT_CONFIG(tooltip)
+ self.make_quote.setText(QCoreApplication.translate("Dialog", u"Zitat erstellen", None))
+ self.label.setText(QCoreApplication.translate("Dialog", u"Autor(en)\n"
+" Nachname, Vorname", None))
+#if QT_CONFIG(tooltip)
+ self.book_author.setToolTip(QCoreApplication.translate("Dialog", u"Bei mehreren Autoren mit ; trennen", None))
+#endif // QT_CONFIG(tooltip)
+ self.label_3.setText(QCoreApplication.translate("Dialog", u"Jahr", None))
+ self.label_4.setText(QCoreApplication.translate("Dialog", u"Auflage", None))
+ self.label_5.setText(QCoreApplication.translate("Dialog", u"Titel", None))
+ self.label_6.setText(QCoreApplication.translate("Dialog", u"Ort", None))
+ self.label_7.setText(QCoreApplication.translate("Dialog", u"Verlag", None))
+ self.label_8.setText(QCoreApplication.translate("Dialog", u"Signatur", None))
+ self.label_9.setText(QCoreApplication.translate("Dialog", u"Seiten", None))
+ self.book_pages.setPlaceholderText(QCoreApplication.translate("Dialog", u"Seitenanzahl des Mediums, zum zitieren \u00e4ndern!", None))
+ self.page_warn_2.setText("")
+ self.label_29.setText(QCoreApplication.translate("Dialog", u"ISBN", None))
+#if QT_CONFIG(tooltip)
+ self.hg_editor.setToolTip(QCoreApplication.translate("Dialog", u"Bei mehreren Autoren mit ; trennen", None))
+#endif // QT_CONFIG(tooltip)
+ self.label_26.setText(QCoreApplication.translate("Dialog", u"Verlag", None))
+ self.label_20.setText(QCoreApplication.translate("Dialog", u"Jahr", None))
+ self.label_24.setText(QCoreApplication.translate("Dialog", u"Beitragstitel", None))
+ self.label_27.setText(QCoreApplication.translate("Dialog", u"Seiten", None))
+ self.label_28.setText(QCoreApplication.translate("Dialog", u"Signatur", None))
+ self.label_23.setText(QCoreApplication.translate("Dialog", u"Titel des Werkes", None))
+ self.label_21.setText(QCoreApplication.translate("Dialog", u"Auflage", None))
+ self.label_19.setText(QCoreApplication.translate("Dialog", u"Autor(en)\n"
+"Nachname, Vorname", None))
+ self.label_30.setText(QCoreApplication.translate("Dialog", u"ISBN", None))
+ self.label_25.setText(QCoreApplication.translate("Dialog", u"Ort", None))
+ self.label_22.setText(QCoreApplication.translate("Dialog", u"Herausgebername(n)\n"
+"Nachname, Vorname", None))
+#if QT_CONFIG(tooltip)
+ self.hg_author.setToolTip(QCoreApplication.translate("Dialog", u"Bei mehreren Autoren mit ; trennen", None))
+#endif // QT_CONFIG(tooltip)
+ self.page_warn_3.setText("")
+ self.label_10.setText(QCoreApplication.translate("Dialog", u"Autor(en)\n"
+"Nachname, Vorname", None))
+ self.label_14.setText(QCoreApplication.translate("Dialog", u"Name der Zeitschrift", None))
+ self.label_11.setText(QCoreApplication.translate("Dialog", u"Jahr", None))
+ self.label_17.setText(QCoreApplication.translate("Dialog", u"Seiten", None))
+ self.label_16.setText(QCoreApplication.translate("Dialog", u"Verlag", None))
+ self.label_12.setText(QCoreApplication.translate("Dialog", u"Heft", None))
+ self.label_31.setText(QCoreApplication.translate("Dialog", u"ISSN", None))
+ self.label_15.setText(QCoreApplication.translate("Dialog", u"Ort", None))
+ self.label_13.setText(QCoreApplication.translate("Dialog", u"Artikeltitel", None))
+ self.label_18.setText(QCoreApplication.translate("Dialog", u"Signatur", None))
+#if QT_CONFIG(tooltip)
+ self.zs_author.setToolTip(QCoreApplication.translate("Dialog", u"Bei mehreren Autoren mit ; trennen", None))
+#endif // QT_CONFIG(tooltip)
+ self.page_warn.setText("")
+ self.label_32.setText(QCoreApplication.translate("Dialog", u"Dateiname", None))
+ self.label_34.setText(QCoreApplication.translate("Dialog", u"ILIAS Name", None))
+ self.label_33.setText(QCoreApplication.translate("Dialog", u"ILIAS Dateibeschreibung", None))
+ self.copy_filename.setText(QCoreApplication.translate("Dialog", u"Kopieren", None))
+ self.filename_edit_label.setText("")
+ self.copy_ilias_filename.setText(QCoreApplication.translate("Dialog", u"Kopieren", None))
+ self.ilias_filename_label.setText("")
+ self.copy_qoute.setText(QCoreApplication.translate("Dialog", u"Kopieren", None))
+ self.file_desc_edit_label.setText("")
+ self.retryButton.setText(QCoreApplication.translate("Dialog", u"Wiederholen", None))
+ # retranslateUi
+
diff --git a/src/ui/dialogs/dialog_sources/mail_preview.ui b/src/ui/dialogs/dialog_sources/mail_preview.ui
index 4e74d8f..02503e2 100644
--- a/src/ui/dialogs/dialog_sources/mail_preview.ui
+++ b/src/ui/dialogs/dialog_sources/mail_preview.ui
@@ -13,10 +13,6 @@
eMail Voransicht
-
-
- ../../../../../../icons/email.svg ../../../../../../icons/email.svg
-
-
@@ -190,9 +186,7 @@
-
-
-
+
buttonBox
diff --git a/src/ui/dialogs/dialog_sources/mail_preview_ui.py b/src/ui/dialogs/dialog_sources/mail_preview_ui.py
index a7ce30d..8fd1ef7 100644
--- a/src/ui/dialogs/dialog_sources/mail_preview_ui.py
+++ b/src/ui/dialogs/dialog_sources/mail_preview_ui.py
@@ -1,115 +1,170 @@
-# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\mail_preview.ui'
-#
-# Created by: PySide6 UI code generator 6.8.0
-#
-# 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.
+# -*- coding: utf-8 -*-
+################################################################################
+## Form generated from reading UI file 'mail_preview.ui'
+##
+## Created by: Qt User Interface Compiler version 6.9.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
-from PySide6 import QtCore, QtGui, QtWidgets
-
+from PySide6.QtCore import (QCoreApplication, QMetaObject, QSize, Qt)
+from PySide6.QtWidgets import (QComboBox, QDialogButtonBox, QGridLayout, QHBoxLayout, QLabel,
+ QLineEdit, QPushButton, QRadioButton, QSizePolicy,
+ QSpacerItem, QTextEdit)
class Ui_eMailPreview(object):
def setupUi(self, eMailPreview):
- eMailPreview.setObjectName("eMailPreview")
+ if not eMailPreview.objectName():
+ eMailPreview.setObjectName(u"eMailPreview")
eMailPreview.resize(700, 668)
- icon = QtGui.QIcon()
- icon.addPixmap(QtGui.QPixmap("c:\\Users\\aky547\\GitHub\\SemesterapparatsManager\\src\\ui\\dialogs\\dialog_sources\\../../../../../../icons/email.svg"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
- eMailPreview.setWindowIcon(icon)
- self.gridLayout_2 = QtWidgets.QGridLayout(eMailPreview)
- self.gridLayout_2.setObjectName("gridLayout_2")
- self.gridLayout = QtWidgets.QGridLayout()
- self.gridLayout.setObjectName("gridLayout")
- self.prof_name = QtWidgets.QLineEdit(parent=eMailPreview)
- self.prof_name.setObjectName("prof_name")
+ self.gridLayout_2 = QGridLayout(eMailPreview)
+ self.gridLayout_2.setObjectName(u"gridLayout_2")
+ self.gridLayout = QGridLayout()
+ self.gridLayout.setObjectName(u"gridLayout")
+ self.prof_name = QLineEdit(eMailPreview)
+ self.prof_name.setObjectName(u"prof_name")
+
self.gridLayout.addWidget(self.prof_name, 2, 2, 1, 1)
- self.newTemplate = QtWidgets.QPushButton(parent=eMailPreview)
+
+ self.newTemplate = QPushButton(eMailPreview)
+ self.newTemplate.setObjectName(u"newTemplate")
self.newTemplate.setAutoFillBackground(False)
- self.newTemplate.setText("")
- self.newTemplate.setIconSize(QtCore.QSize(24, 24))
+ self.newTemplate.setIconSize(QSize(24, 24))
self.newTemplate.setAutoDefault(True)
- self.newTemplate.setDefault(False)
self.newTemplate.setFlat(False)
- self.newTemplate.setObjectName("newTemplate")
+
self.gridLayout.addWidget(self.newTemplate, 0, 3, 1, 1)
- self.comboBox = QtWidgets.QComboBox(parent=eMailPreview)
- self.comboBox.setObjectName("comboBox")
+
+ self.comboBox = QComboBox(eMailPreview)
+ self.comboBox.setObjectName(u"comboBox")
+
self.gridLayout.addWidget(self.comboBox, 0, 2, 1, 1)
- self.mail_header = QtWidgets.QLineEdit(parent=eMailPreview)
- self.mail_header.setObjectName("mail_header")
+
+ self.mail_header = QLineEdit(eMailPreview)
+ self.mail_header.setObjectName(u"mail_header")
+
self.gridLayout.addWidget(self.mail_header, 3, 2, 1, 1)
- self.label_6 = QtWidgets.QLabel(parent=eMailPreview)
- self.label_6.setObjectName("label_6")
+
+ self.label_6 = QLabel(eMailPreview)
+ self.label_6.setObjectName(u"label_6")
+
self.gridLayout.addWidget(self.label_6, 4, 0, 1, 1)
- self.mail_body = QtWidgets.QTextEdit(parent=eMailPreview)
- self.mail_body.setObjectName("mail_body")
+
+ self.mail_body = QTextEdit(eMailPreview)
+ self.mail_body.setObjectName(u"mail_body")
+
self.gridLayout.addWidget(self.mail_body, 5, 2, 1, 1)
- self.label_2 = QtWidgets.QLabel(parent=eMailPreview)
- self.label_2.setObjectName("label_2")
+
+ self.label_2 = QLabel(eMailPreview)
+ self.label_2.setObjectName(u"label_2")
+
self.gridLayout.addWidget(self.label_2, 2, 0, 1, 1)
- self.mail_name = QtWidgets.QLineEdit(parent=eMailPreview)
- self.mail_name.setObjectName("mail_name")
+
+ self.mail_name = QLineEdit(eMailPreview)
+ self.mail_name.setObjectName(u"mail_name")
+
self.gridLayout.addWidget(self.mail_name, 1, 2, 1, 1)
- self.label_5 = QtWidgets.QLabel(parent=eMailPreview)
- self.label_5.setObjectName("label_5")
+
+ self.label_5 = QLabel(eMailPreview)
+ self.label_5.setObjectName(u"label_5")
+
self.gridLayout.addWidget(self.label_5, 0, 0, 1, 1)
- self.label_4 = QtWidgets.QLabel(parent=eMailPreview)
- self.label_4.setObjectName("label_4")
+
+ self.label_4 = QLabel(eMailPreview)
+ self.label_4.setObjectName(u"label_4")
+
self.gridLayout.addWidget(self.label_4, 3, 0, 1, 1)
- self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
- self.horizontalLayout_3.setObjectName("horizontalLayout_3")
- self.gender_male = QtWidgets.QRadioButton(parent=eMailPreview)
- self.gender_male.setObjectName("gender_male")
+
+ self.horizontalLayout_3 = QHBoxLayout()
+ self.horizontalLayout_3.setObjectName(u"horizontalLayout_3")
+ self.gender_male = QRadioButton(eMailPreview)
+ self.gender_male.setObjectName(u"gender_male")
+
self.horizontalLayout_3.addWidget(self.gender_male)
- self.gender_female = QtWidgets.QRadioButton(parent=eMailPreview)
- self.gender_female.setObjectName("gender_female")
+
+ self.gender_female = QRadioButton(eMailPreview)
+ self.gender_female.setObjectName(u"gender_female")
+
self.horizontalLayout_3.addWidget(self.gender_female)
- self.gender_non = QtWidgets.QRadioButton(parent=eMailPreview)
- self.gender_non.setObjectName("gender_non")
+
+ self.gender_non = QRadioButton(eMailPreview)
+ self.gender_non.setObjectName(u"gender_non")
+
self.horizontalLayout_3.addWidget(self.gender_non)
- spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
- self.horizontalLayout_3.addItem(spacerItem)
+
+ self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
+
+ self.horizontalLayout_3.addItem(self.horizontalSpacer)
+
+
self.gridLayout.addLayout(self.horizontalLayout_3, 4, 2, 1, 1)
- self.label_3 = QtWidgets.QLabel(parent=eMailPreview)
- self.label_3.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeading|QtCore.Qt.AlignmentFlag.AlignLeft|QtCore.Qt.AlignmentFlag.AlignTop)
- self.label_3.setObjectName("label_3")
+
+ self.label_3 = QLabel(eMailPreview)
+ self.label_3.setObjectName(u"label_3")
+ self.label_3.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignTop)
+
self.gridLayout.addWidget(self.label_3, 5, 0, 1, 1)
- self.label = QtWidgets.QLabel(parent=eMailPreview)
- self.label.setObjectName("label")
+
+ self.label = QLabel(eMailPreview)
+ self.label.setObjectName(u"label")
+
self.gridLayout.addWidget(self.label, 1, 0, 1, 1)
- self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
- self.horizontalLayout_2.setObjectName("horizontalLayout_2")
- spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
- self.horizontalLayout_2.addItem(spacerItem1)
- self.btn_okay = QtWidgets.QPushButton(parent=eMailPreview)
- self.btn_okay.setStatusTip("")
- self.btn_okay.setObjectName("btn_okay")
+
+ self.horizontalLayout_2 = QHBoxLayout()
+ self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
+ self.horizontalSpacer_2 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
+
+ self.horizontalLayout_2.addItem(self.horizontalSpacer_2)
+
+ self.btn_okay = QPushButton(eMailPreview)
+ self.btn_okay.setObjectName(u"btn_okay")
+
self.horizontalLayout_2.addWidget(self.btn_okay)
- self.buttonBox = QtWidgets.QDialogButtonBox(parent=eMailPreview)
- self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal)
- self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.StandardButton.Cancel)
+
+ self.buttonBox = QDialogButtonBox(eMailPreview)
+ self.buttonBox.setObjectName(u"buttonBox")
+ self.buttonBox.setOrientation(Qt.Horizontal)
+ self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel)
self.buttonBox.setCenterButtons(True)
- self.buttonBox.setObjectName("buttonBox")
+
self.horizontalLayout_2.addWidget(self.buttonBox)
+
+
self.gridLayout.addLayout(self.horizontalLayout_2, 6, 2, 1, 1)
+
+
self.gridLayout_2.addLayout(self.gridLayout, 0, 0, 1, 1)
+
self.retranslateUi(eMailPreview)
- self.buttonBox.accepted.connect(eMailPreview.accept) # type: ignore
- self.buttonBox.rejected.connect(eMailPreview.reject) # type: ignore
- QtCore.QMetaObject.connectSlotsByName(eMailPreview)
+ self.buttonBox.accepted.connect(eMailPreview.accept)
+ self.buttonBox.rejected.connect(eMailPreview.reject)
+
+ self.newTemplate.setDefault(False)
+
+
+ QMetaObject.connectSlotsByName(eMailPreview)
+ # setupUi
def retranslateUi(self, eMailPreview):
- _translate = QtCore.QCoreApplication.translate
- eMailPreview.setWindowTitle(_translate("eMailPreview", "eMail Voransicht"))
- self.label_6.setText(_translate("eMailPreview", "Anrede"))
- self.label_2.setText(_translate("eMailPreview", "Prof"))
- self.label_5.setText(_translate("eMailPreview", "Art"))
- self.label_4.setText(_translate("eMailPreview", "Betreff"))
- self.gender_male.setText(_translate("eMailPreview", "M"))
- self.gender_female.setText(_translate("eMailPreview", "W"))
- self.gender_non.setText(_translate("eMailPreview", "Divers"))
- self.label_3.setText(_translate("eMailPreview", "Mail"))
- self.label.setText(_translate("eMailPreview", "eMail"))
- self.btn_okay.setWhatsThis(_translate("eMailPreview", "test"))
- self.btn_okay.setText(_translate("eMailPreview", "Senden"))
+ eMailPreview.setWindowTitle(QCoreApplication.translate("eMailPreview", u"eMail Voransicht", None))
+ self.newTemplate.setText("")
+ self.label_6.setText(QCoreApplication.translate("eMailPreview", u"Anrede", None))
+ self.label_2.setText(QCoreApplication.translate("eMailPreview", u"Prof", None))
+ self.label_5.setText(QCoreApplication.translate("eMailPreview", u"Art", None))
+ self.label_4.setText(QCoreApplication.translate("eMailPreview", u"Betreff", None))
+ self.gender_male.setText(QCoreApplication.translate("eMailPreview", u"M", None))
+ self.gender_female.setText(QCoreApplication.translate("eMailPreview", u"W", None))
+ self.gender_non.setText(QCoreApplication.translate("eMailPreview", u"Divers", None))
+ self.label_3.setText(QCoreApplication.translate("eMailPreview", u"Mail", None))
+ self.label.setText(QCoreApplication.translate("eMailPreview", u"eMail", None))
+#if QT_CONFIG(statustip)
+ self.btn_okay.setStatusTip("")
+#endif // QT_CONFIG(statustip)
+#if QT_CONFIG(whatsthis)
+ self.btn_okay.setWhatsThis(QCoreApplication.translate("eMailPreview", u"test", None))
+#endif // QT_CONFIG(whatsthis)
+ self.btn_okay.setText(QCoreApplication.translate("eMailPreview", u"Senden", None))
+ # retranslateUi
+
diff --git a/src/ui/dialogs/dialog_sources/newMailTemplateDesigner.ui b/src/ui/dialogs/dialog_sources/newMailTemplateDesigner.ui
index 559c795..0092de5 100644
--- a/src/ui/dialogs/dialog_sources/newMailTemplateDesigner.ui
+++ b/src/ui/dialogs/dialog_sources/newMailTemplateDesigner.ui
@@ -16,157 +16,6 @@
-
- -
-
- -
-
-
- Qt::NoFocus
-
-
- Fett
-
-
- true
-
-
-
- -
-
-
- Qt::NoFocus
-
-
- Kursiv
-
-
- true
-
-
-
- -
-
-
- Qt::NoFocus
-
-
- Unterstrichen
-
-
- true
-
-
-
- -
-
-
- Qt::NoFocus
-
-
-
- -
-
-
- Qt::NoFocus
-
-
- 1
-
- -
-
- 8
-
-
- -
-
- 9
-
-
- -
-
- 11
-
-
- -
-
- 12
-
-
- -
-
- 14
-
-
- -
-
- 16
-
-
- -
-
- 18
-
-
- -
-
- 20
-
-
- -
-
- 22
-
-
- -
-
- 24
-
-
- -
-
- 26
-
-
- -
-
- 28
-
-
- -
-
- 36
-
-
- -
-
- 48
-
-
- -
-
- 76
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
-
-
- -
-
-
-
-
@@ -308,9 +157,7 @@
testTemplate
insertPlaceholder
lineEdit
- fontSize
placeholder_list
- fontBox
diff --git a/src/ui/dialogs/dialog_sources/newMailTemplateDesigner_ui.py b/src/ui/dialogs/dialog_sources/newMailTemplateDesigner_ui.py
index 0892c34..f13b04c 100644
--- a/src/ui/dialogs/dialog_sources/newMailTemplateDesigner_ui.py
+++ b/src/ui/dialogs/dialog_sources/newMailTemplateDesigner_ui.py
@@ -1,163 +1,133 @@
-# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\newMailTemplateDesigner.ui'
-#
-# Created by: PySide6 UI code generator 6.8.0
-#
-# 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.
+# -*- coding: utf-8 -*-
+################################################################################
+## Form generated from reading UI file 'newMailTemplateDesigner.ui'
+##
+## Created by: Qt User Interface Compiler version 6.9.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
-from PySide6 import QtCore, QtGui, QtWidgets
-
+from PySide6.QtCore import (QCoreApplication, QMetaObject, Qt)
+from PySide6.QtWidgets import (QComboBox, QDialogButtonBox, QGridLayout, QHBoxLayout, QLabel,
+ QLineEdit, QPushButton, QSizePolicy, QSpacerItem,
+ QTextEdit, QVBoxLayout, QWidget)
class Ui_Dialog(object):
def setupUi(self, Dialog):
- Dialog.setObjectName("Dialog")
+ if not Dialog.objectName():
+ Dialog.setObjectName(u"Dialog")
Dialog.resize(689, 572)
- self.verticalLayout_2 = QtWidgets.QVBoxLayout(Dialog)
- self.verticalLayout_2.setObjectName("verticalLayout_2")
- self.verticalLayout = QtWidgets.QVBoxLayout()
- self.verticalLayout.setObjectName("verticalLayout")
- self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
- self.horizontalLayout_2.setObjectName("horizontalLayout_2")
- self.bold = QtWidgets.QPushButton(parent=Dialog)
- self.bold.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
- self.bold.setCheckable(True)
- self.bold.setObjectName("bold")
- self.horizontalLayout_2.addWidget(self.bold)
- self.italic = QtWidgets.QPushButton(parent=Dialog)
- self.italic.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
- self.italic.setCheckable(True)
- self.italic.setObjectName("italic")
- self.horizontalLayout_2.addWidget(self.italic)
- self.underlined = QtWidgets.QPushButton(parent=Dialog)
- self.underlined.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
- self.underlined.setCheckable(True)
- self.underlined.setObjectName("underlined")
- self.horizontalLayout_2.addWidget(self.underlined)
- self.fontBox = QtWidgets.QFontComboBox(parent=Dialog)
- self.fontBox.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
- self.fontBox.setObjectName("fontBox")
- self.horizontalLayout_2.addWidget(self.fontBox)
- self.fontSize = QtWidgets.QComboBox(parent=Dialog)
- self.fontSize.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
- self.fontSize.setObjectName("fontSize")
- self.fontSize.addItem("")
- self.fontSize.addItem("")
- self.fontSize.addItem("")
- self.fontSize.addItem("")
- self.fontSize.addItem("")
- self.fontSize.addItem("")
- self.fontSize.addItem("")
- self.fontSize.addItem("")
- self.fontSize.addItem("")
- self.fontSize.addItem("")
- self.fontSize.addItem("")
- self.fontSize.addItem("")
- self.fontSize.addItem("")
- self.fontSize.addItem("")
- self.fontSize.addItem("")
- self.horizontalLayout_2.addWidget(self.fontSize)
- spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
- self.horizontalLayout_2.addItem(spacerItem)
- self.verticalLayout.addLayout(self.horizontalLayout_2)
- self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
- self.horizontalLayout_4.setObjectName("horizontalLayout_4")
- self.verticalLayout.addLayout(self.horizontalLayout_4)
- self.gridLayout = QtWidgets.QGridLayout()
- self.gridLayout.setObjectName("gridLayout")
- self.label = QtWidgets.QLabel(parent=Dialog)
- self.label.setObjectName("label")
+ self.verticalLayout_2 = QVBoxLayout(Dialog)
+ self.verticalLayout_2.setObjectName(u"verticalLayout_2")
+ self.verticalLayout = QVBoxLayout()
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.gridLayout = QGridLayout()
+ self.gridLayout.setObjectName(u"gridLayout")
+ self.label = QLabel(Dialog)
+ self.label.setObjectName(u"label")
+
self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
- self.placeholder_list = QtWidgets.QComboBox(parent=Dialog)
- self.placeholder_list.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
- self.placeholder_list.setSizeAdjustPolicy(QtWidgets.QComboBox.SizeAdjustPolicy.AdjustToContents)
- self.placeholder_list.setObjectName("placeholder_list")
+
+ self.placeholder_list = QComboBox(Dialog)
self.placeholder_list.addItem("")
self.placeholder_list.addItem("")
self.placeholder_list.addItem("")
self.placeholder_list.addItem("")
self.placeholder_list.addItem("")
self.placeholder_list.addItem("")
+ self.placeholder_list.setObjectName(u"placeholder_list")
+ self.placeholder_list.setFocusPolicy(Qt.NoFocus)
+ self.placeholder_list.setSizeAdjustPolicy(QComboBox.AdjustToContents)
+
self.gridLayout.addWidget(self.placeholder_list, 1, 0, 1, 1)
- self.label_2 = QtWidgets.QLabel(parent=Dialog)
- self.label_2.setObjectName("label_2")
+
+ self.label_2 = QLabel(Dialog)
+ self.label_2.setObjectName(u"label_2")
+
self.gridLayout.addWidget(self.label_2, 0, 1, 1, 1)
- self.lineEdit = QtWidgets.QLineEdit(parent=Dialog)
+
+ self.lineEdit = QLineEdit(Dialog)
+ self.lineEdit.setObjectName(u"lineEdit")
self.lineEdit.setEnabled(True)
- self.lineEdit.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
+ self.lineEdit.setFocusPolicy(Qt.NoFocus)
self.lineEdit.setFrame(False)
self.lineEdit.setReadOnly(True)
- self.lineEdit.setObjectName("lineEdit")
+
self.gridLayout.addWidget(self.lineEdit, 1, 1, 1, 1)
- self.insertPlaceholder = QtWidgets.QPushButton(parent=Dialog)
- self.insertPlaceholder.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
- self.insertPlaceholder.setObjectName("insertPlaceholder")
+
+ self.insertPlaceholder = QPushButton(Dialog)
+ self.insertPlaceholder.setObjectName(u"insertPlaceholder")
+ self.insertPlaceholder.setFocusPolicy(Qt.NoFocus)
+
self.gridLayout.addWidget(self.insertPlaceholder, 1, 2, 1, 1)
+
+
self.verticalLayout.addLayout(self.gridLayout)
- self.label_3 = QtWidgets.QLabel(parent=Dialog)
- self.label_3.setObjectName("label_3")
+
+ self.label_3 = QLabel(Dialog)
+ self.label_3.setObjectName(u"label_3")
+
self.verticalLayout.addWidget(self.label_3)
- self.subject = QtWidgets.QLineEdit(parent=Dialog)
- self.subject.setObjectName("subject")
+
+ self.subject = QLineEdit(Dialog)
+ self.subject.setObjectName(u"subject")
+
self.verticalLayout.addWidget(self.subject)
- self.templateEdit = QtWidgets.QTextEdit(parent=Dialog)
- self.templateEdit.setObjectName("templateEdit")
+
+ self.templateEdit = QTextEdit(Dialog)
+ self.templateEdit.setObjectName(u"templateEdit")
+
self.verticalLayout.addWidget(self.templateEdit)
- self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
- self.horizontalLayout_3.setObjectName("horizontalLayout_3")
- self.testTemplate = QtWidgets.QPushButton(parent=Dialog)
- self.testTemplate.setObjectName("testTemplate")
+
+ self.horizontalLayout_3 = QHBoxLayout()
+ self.horizontalLayout_3.setObjectName(u"horizontalLayout_3")
+ self.testTemplate = QPushButton(Dialog)
+ self.testTemplate.setObjectName(u"testTemplate")
+
self.horizontalLayout_3.addWidget(self.testTemplate)
- spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
- self.horizontalLayout_3.addItem(spacerItem1)
+
+ self.horizontalSpacer_2 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
+
+ self.horizontalLayout_3.addItem(self.horizontalSpacer_2)
+
+
self.verticalLayout.addLayout(self.horizontalLayout_3)
+
+
self.verticalLayout_2.addLayout(self.verticalLayout)
- self.buttonBox = QtWidgets.QDialogButtonBox(parent=Dialog)
- self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal)
- self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.StandardButton.Cancel|QtWidgets.QDialogButtonBox.StandardButton.Discard|QtWidgets.QDialogButtonBox.StandardButton.Save)
- self.buttonBox.setObjectName("buttonBox")
+
+ self.buttonBox = QDialogButtonBox(Dialog)
+ self.buttonBox.setObjectName(u"buttonBox")
+ self.buttonBox.setOrientation(Qt.Horizontal)
+ self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Discard|QDialogButtonBox.Save)
+
self.verticalLayout_2.addWidget(self.buttonBox)
+ QWidget.setTabOrder(self.subject, self.templateEdit)
+ QWidget.setTabOrder(self.templateEdit, self.testTemplate)
+ QWidget.setTabOrder(self.testTemplate, self.insertPlaceholder)
+ QWidget.setTabOrder(self.insertPlaceholder, self.lineEdit)
+ QWidget.setTabOrder(self.lineEdit, self.placeholder_list)
+
self.retranslateUi(Dialog)
- self.fontSize.setCurrentIndex(1)
- QtCore.QMetaObject.connectSlotsByName(Dialog)
- Dialog.setTabOrder(self.subject, self.templateEdit)
- Dialog.setTabOrder(self.templateEdit, self.testTemplate)
- Dialog.setTabOrder(self.testTemplate, self.insertPlaceholder)
- Dialog.setTabOrder(self.insertPlaceholder, self.lineEdit)
- Dialog.setTabOrder(self.lineEdit, self.fontSize)
- Dialog.setTabOrder(self.fontSize, self.placeholder_list)
- Dialog.setTabOrder(self.placeholder_list, self.fontBox)
+
+ QMetaObject.connectSlotsByName(Dialog)
+ # setupUi
def retranslateUi(self, Dialog):
- _translate = QtCore.QCoreApplication.translate
- Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
- self.bold.setText(_translate("Dialog", "Fett"))
- self.italic.setText(_translate("Dialog", "Kursiv"))
- self.underlined.setText(_translate("Dialog", "Unterstrichen"))
- self.fontSize.setItemText(0, _translate("Dialog", "8"))
- self.fontSize.setItemText(1, _translate("Dialog", "9"))
- self.fontSize.setItemText(2, _translate("Dialog", "11"))
- self.fontSize.setItemText(3, _translate("Dialog", "12"))
- self.fontSize.setItemText(4, _translate("Dialog", "14"))
- self.fontSize.setItemText(5, _translate("Dialog", "16"))
- self.fontSize.setItemText(6, _translate("Dialog", "18"))
- self.fontSize.setItemText(7, _translate("Dialog", "20"))
- self.fontSize.setItemText(8, _translate("Dialog", "22"))
- self.fontSize.setItemText(9, _translate("Dialog", "24"))
- self.fontSize.setItemText(10, _translate("Dialog", "26"))
- self.fontSize.setItemText(11, _translate("Dialog", "28"))
- self.fontSize.setItemText(12, _translate("Dialog", "36"))
- self.fontSize.setItemText(13, _translate("Dialog", "48"))
- self.fontSize.setItemText(14, _translate("Dialog", "76"))
- self.label.setText(_translate("Dialog", "Platzhalter"))
- self.placeholder_list.setItemText(0, _translate("Dialog", "«Anrede»"))
- self.placeholder_list.setItemText(1, _translate("Dialog", "«ApparatsName»"))
- self.placeholder_list.setItemText(2, _translate("Dialog", "«ApparatsFach»"))
- self.placeholder_list.setItemText(3, _translate("Dialog", "«ApparatsNummer»"))
- self.placeholder_list.setItemText(4, _translate("Dialog", "«DozentName»"))
- self.placeholder_list.setItemText(5, _translate("Dialog", "«Signatur»"))
- self.label_2.setText(_translate("Dialog", "Beschreibung"))
- self.insertPlaceholder.setText(_translate("Dialog", "An aktiver Position einfügen"))
- self.label_3.setText(_translate("Dialog", "Betreff"))
- self.testTemplate.setText(_translate("Dialog", "Template testen"))
+ Dialog.setWindowTitle(QCoreApplication.translate("Dialog", u"Dialog", None))
+ self.label.setText(QCoreApplication.translate("Dialog", u"Platzhalter", None))
+ self.placeholder_list.setItemText(0, QCoreApplication.translate("Dialog", u"\u00abAnrede\u00bb", None))
+ self.placeholder_list.setItemText(1, QCoreApplication.translate("Dialog", u"\u00abApparatsName\u00bb", None))
+ self.placeholder_list.setItemText(2, QCoreApplication.translate("Dialog", u"\u00abApparatsFach\u00bb", None))
+ self.placeholder_list.setItemText(3, QCoreApplication.translate("Dialog", u"\u00abApparatsNummer\u00bb", None))
+ self.placeholder_list.setItemText(4, QCoreApplication.translate("Dialog", u"\u00abDozentName\u00bb", None))
+ self.placeholder_list.setItemText(5, QCoreApplication.translate("Dialog", u"\u00abSignatur\u00bb", None))
+
+ self.label_2.setText(QCoreApplication.translate("Dialog", u"Beschreibung", None))
+ self.insertPlaceholder.setText(QCoreApplication.translate("Dialog", u"An aktiver Position einf\u00fcgen", None))
+ self.label_3.setText(QCoreApplication.translate("Dialog", u"Betreff", None))
+ self.testTemplate.setText(QCoreApplication.translate("Dialog", u"Template testen", None))
+ # retranslateUi
+
diff --git a/src/ui/dialogs/dialog_sources/order_neweditions.ui b/src/ui/dialogs/dialog_sources/order_neweditions.ui
new file mode 100644
index 0000000..508befd
--- /dev/null
+++ b/src/ui/dialogs/dialog_sources/order_neweditions.ui
@@ -0,0 +1,89 @@
+
+
+ Dialog
+
+
+
+ 0
+ 0
+ 808
+ 629
+
+
+
+ Dialog
+
+
+ -
+
+
+
+ Bestellen
+
+
+
+
+ Signatur
+
+
+
+
+ Titel
+
+
+
+
+ ISBN
+
+
+
+
+ Autor
+
+
+
+
+ Auflage
+
+
+
+
+ Standort
+
+
+
+
+ Link
+
+
+
+
+ -
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Bestellen
+
+
+
+
+
+
+
+
+
+
diff --git a/src/ui/dialogs/dialog_sources/order_neweditions_ui.py b/src/ui/dialogs/dialog_sources/order_neweditions_ui.py
new file mode 100644
index 0000000..92f8cee
--- /dev/null
+++ b/src/ui/dialogs/dialog_sources/order_neweditions_ui.py
@@ -0,0 +1,85 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'order_neweditions.ui'
+##
+## Created by: Qt User Interface Compiler version 6.9.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide6.QtCore import (QCoreApplication, QMetaObject)
+from PySide6.QtWidgets import (QHBoxLayout, QPushButton, QSizePolicy, QSpacerItem, QTableWidget,
+ QTableWidgetItem, QVBoxLayout)
+
+class Ui_Dialog(object):
+ def setupUi(self, Dialog):
+ if not Dialog.objectName():
+ Dialog.setObjectName(u"Dialog")
+ Dialog.resize(808, 629)
+ self.verticalLayout = QVBoxLayout(Dialog)
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.tableWidget = QTableWidget(Dialog)
+ if (self.tableWidget.columnCount() < 8):
+ self.tableWidget.setColumnCount(8)
+ __qtablewidgetitem = QTableWidgetItem()
+ self.tableWidget.setHorizontalHeaderItem(0, __qtablewidgetitem)
+ __qtablewidgetitem1 = QTableWidgetItem()
+ self.tableWidget.setHorizontalHeaderItem(1, __qtablewidgetitem1)
+ __qtablewidgetitem2 = QTableWidgetItem()
+ self.tableWidget.setHorizontalHeaderItem(2, __qtablewidgetitem2)
+ __qtablewidgetitem3 = QTableWidgetItem()
+ self.tableWidget.setHorizontalHeaderItem(3, __qtablewidgetitem3)
+ __qtablewidgetitem4 = QTableWidgetItem()
+ self.tableWidget.setHorizontalHeaderItem(4, __qtablewidgetitem4)
+ __qtablewidgetitem5 = QTableWidgetItem()
+ self.tableWidget.setHorizontalHeaderItem(5, __qtablewidgetitem5)
+ __qtablewidgetitem6 = QTableWidgetItem()
+ self.tableWidget.setHorizontalHeaderItem(6, __qtablewidgetitem6)
+ __qtablewidgetitem7 = QTableWidgetItem()
+ self.tableWidget.setHorizontalHeaderItem(7, __qtablewidgetitem7)
+ self.tableWidget.setObjectName(u"tableWidget")
+
+ self.verticalLayout.addWidget(self.tableWidget)
+
+ self.horizontalLayout = QHBoxLayout()
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
+
+ self.horizontalLayout.addItem(self.horizontalSpacer)
+
+ self.pushButton = QPushButton(Dialog)
+ self.pushButton.setObjectName(u"pushButton")
+
+ self.horizontalLayout.addWidget(self.pushButton)
+
+
+ self.verticalLayout.addLayout(self.horizontalLayout)
+
+
+ self.retranslateUi(Dialog)
+
+ QMetaObject.connectSlotsByName(Dialog)
+ # setupUi
+
+ def retranslateUi(self, Dialog):
+ Dialog.setWindowTitle(QCoreApplication.translate("Dialog", u"Dialog", None))
+ ___qtablewidgetitem = self.tableWidget.horizontalHeaderItem(0)
+ ___qtablewidgetitem.setText(QCoreApplication.translate("Dialog", u"Bestellen", None))
+ ___qtablewidgetitem1 = self.tableWidget.horizontalHeaderItem(1)
+ ___qtablewidgetitem1.setText(QCoreApplication.translate("Dialog", u"Signatur", None))
+ ___qtablewidgetitem2 = self.tableWidget.horizontalHeaderItem(2)
+ ___qtablewidgetitem2.setText(QCoreApplication.translate("Dialog", u"Titel", None))
+ ___qtablewidgetitem3 = self.tableWidget.horizontalHeaderItem(3)
+ ___qtablewidgetitem3.setText(QCoreApplication.translate("Dialog", u"ISBN", None))
+ ___qtablewidgetitem4 = self.tableWidget.horizontalHeaderItem(4)
+ ___qtablewidgetitem4.setText(QCoreApplication.translate("Dialog", u"Autor", None))
+ ___qtablewidgetitem5 = self.tableWidget.horizontalHeaderItem(5)
+ ___qtablewidgetitem5.setText(QCoreApplication.translate("Dialog", u"Auflage", None))
+ ___qtablewidgetitem6 = self.tableWidget.horizontalHeaderItem(6)
+ ___qtablewidgetitem6.setText(QCoreApplication.translate("Dialog", u"Standort", None))
+ ___qtablewidgetitem7 = self.tableWidget.horizontalHeaderItem(7)
+ ___qtablewidgetitem7.setText(QCoreApplication.translate("Dialog", u"Link", None))
+ self.pushButton.setText(QCoreApplication.translate("Dialog", u"Bestellen", None))
+ # retranslateUi
+
diff --git a/src/ui/dialogs/dialog_sources/reminder_ui.py b/src/ui/dialogs/dialog_sources/reminder_ui.py
index 25b7bf8..feea85f 100644
--- a/src/ui/dialogs/dialog_sources/reminder_ui.py
+++ b/src/ui/dialogs/dialog_sources/reminder_ui.py
@@ -6,7 +6,7 @@
# run again. Do not edit this file unless you know what you are doing.
-from PySide6 import QtCore, QtGui, QtWidgets
+from PySide6 import QtCore, QtWidgets
class Ui_Erinnerung(object):
diff --git a/src/ui/dialogs/docuprint.py b/src/ui/dialogs/docuprint.py
index 3f87c1c..0339238 100644
--- a/src/ui/dialogs/docuprint.py
+++ b/src/ui/dialogs/docuprint.py
@@ -1,10 +1,12 @@
-from .dialog_sources.documentprint_ui import Ui_Dialog
-from PySide6 import QtWidgets, QtCore
-from src import Icon
-
-from src.utils.richtext import SemapSchilder, SemesterDocument
-from src.backend import Semester, Database
from natsort import natsorted
+from PySide6 import QtWidgets
+
+from src import Icon
+from src.database import Database
+from src.core.models import Semester
+from src.utils.richtext import SemapSchilder, SemesterDocument
+
+from .dialog_sources.documentprint_ui import Ui_Dialog
class DocumentPrintDialog(QtWidgets.QDialog, Ui_Dialog):
@@ -23,25 +25,25 @@ class DocumentPrintDialog(QtWidgets.QDialog, Ui_Dialog):
# Ensure the signal is connected only once
try:
self.pushButton_2.clicked.disconnect()
- except TypeError:
+ except (TypeError, RuntimeWarning):
pass # Signal was not connected before
self.pushButton_2.clicked.connect(self.on_pushButton_2_clicked)
try:
self.pushButton.clicked.disconnect()
- except TypeError:
+ except (TypeError, RuntimeWarning):
pass
self.pushButton.clicked.connect(self.on_pushButton_clicked)
try:
self.btn_load_current_apparats.clicked.disconnect()
- except TypeError:
+ except (TypeError, RuntimeWarning):
pass
self.btn_load_current_apparats.clicked.connect(self.load_current_clicked)
try:
self.manualCheck.clicked.disconnect()
- except TypeError:
+ except (TypeError, RuntimeWarning):
pass
self.manualCheck.clicked.connect(self.manual_request)
@@ -108,10 +110,10 @@ class DocumentPrintDialog(QtWidgets.QDialog, Ui_Dialog):
def on_pushButton_clicked(self):
apparats: list[tuple[int, str]] = []
apps = self.db.getAllAparats(0)
- apps = natsorted(apps, key=lambda x: x[4], reverse=True)
+ apps = natsorted(apps, key=lambda x: x.appnr, reverse=True)
for app in apps:
- prof = self.db.getProfById(app[2])
- data = (app[4], f"{prof.lastname} ({app[1]})")
+ prof = self.db.getProfById(app.prof_id)
+ data = (app.appnr, f"{prof.lastname} ({app.name})")
apparats.append(data)
SemesterDocument(
semester=self.semester.value,
diff --git a/src/ui/dialogs/elsa_add_entry.py b/src/ui/dialogs/elsa_add_entry.py
index c15a0ab..17fc5b7 100644
--- a/src/ui/dialogs/elsa_add_entry.py
+++ b/src/ui/dialogs/elsa_add_entry.py
@@ -1,9 +1,12 @@
-from .dialog_sources.Ui_elsa_add_table_entry import Ui_Dialog
-from src.logic.webrequest import WebRequest, BibTextTransformer
-from src import Icon
from PySide6 import QtWidgets
+
+from src import Icon
+from src.services.webrequest import BibTextTransformer, WebRequest
+from src.services.zotero import ZoteroController
+from src.shared.logging import log
from src.transformers.transformers import DictToTable
-from src.logic.zotero import ZoteroController
+
+from .dialog_sources.elsa_add_table_entry_ui import Ui_Dialog
zot = ZoteroController()
dtt = DictToTable()
@@ -174,14 +177,14 @@ class ElsaAddEntry(QtWidgets.QDialog, Ui_Dialog):
self.stackedWidget.setCurrentIndex(3)
def search(self, pages=None):
- print("searching")
+ # #print("searching")
param = self.searchIdent.text()
web = WebRequest()
web.get_ppn(param)
data = web.get_data_elsa()
# if isinstance(data, list):
# data = data[0]
- bib = BibTextTransformer("ARRAY")
+ bib = BibTextTransformer()
bib.get_data(data)
data = bib.return_data()
self.setdata(data, pages)
diff --git a/src/ui/dialogs/elsa_gen_confirm.py b/src/ui/dialogs/elsa_gen_confirm.py
index 8b15842..e63cc7a 100644
--- a/src/ui/dialogs/elsa_gen_confirm.py
+++ b/src/ui/dialogs/elsa_gen_confirm.py
@@ -1,6 +1,7 @@
-from .dialog_sources.Ui_elsa_generator_confirm import Ui_Dialog
from PySide6 import QtWidgets
+from .dialog_sources.elsa_generator_confirm_ui import Ui_Dialog
+
class ElsaGenConfirm(QtWidgets.QDialog, Ui_Dialog):
def __init__(self, parent=None, data=None):
diff --git a/src/ui/dialogs/ext_app.py b/src/ui/dialogs/ext_app.py
index 425067e..b6c80c7 100644
--- a/src/ui/dialogs/ext_app.py
+++ b/src/ui/dialogs/ext_app.py
@@ -6,7 +6,7 @@
# run again. Do not edit this file unless you know what you are doing.
-from PySide6 import QtCore, QtGui, QtWidgets
+from PySide6 import QtCore, QtWidgets
class Ui_Frame(object):
diff --git a/src/ui/dialogs/fileparser.py b/src/ui/dialogs/fileparser.py
index c0c29cd..edb736f 100644
--- a/src/ui/dialogs/fileparser.py
+++ b/src/ui/dialogs/fileparser.py
@@ -1,6 +1,6 @@
from PySide6 import QtWidgets
-from src.logic.webrequest import BibTextTransformer, WebRequest
+from src.services.webrequest import BibTextTransformer, WebRequest
from .dialog_sources.Ui_fileparser import Ui_Dialog
diff --git a/src/ui/dialogs/login.py b/src/ui/dialogs/login.py
index cc8fa34..09b9d1f 100644
--- a/src/ui/dialogs/login.py
+++ b/src/ui/dialogs/login.py
@@ -5,7 +5,7 @@ import loguru
from PySide6 import QtCore, QtWidgets
from src import LOG_DIR, Icon
-from src.backend.database import Database
+from src.database import Database
from .dialog_sources.login_ui import Ui_Dialog
@@ -70,12 +70,12 @@ class LoginDialog(Ui_Dialog):
def login(self):
username = self.lineEdit.text()
password = self.lineEdit_2.text()
- # print(type(username), password)
+ # #print(type(username), password)
# Assuming 'Database' is a class to interact with your database
hashed_password = hashlib.sha256(password.encode()).hexdigest()
if len(self.db.getUsers()) == 0:
- from src.backend.admin_console import AdminCommands
+ from src.admin import AdminCommands
AdminCommands().create_admin()
self.lresult = 1 # Indicate successful login
diff --git a/src/ui/dialogs/mail.py b/src/ui/dialogs/mail.py
index caaf826..703d08a 100644
--- a/src/ui/dialogs/mail.py
+++ b/src/ui/dialogs/mail.py
@@ -1,51 +1,72 @@
import os
+import re
+import smtplib
import sys
-import loguru
from PySide6 import QtWidgets
-from src import LOG_DIR, Icon
+from src import Icon
from src import settings as config
+from src.shared.logging import log
-from .dialog_sources.Ui_mail_preview import Ui_eMailPreview as MailPreviewDialog
+from .dialog_sources.mail_preview_ui import Ui_eMailPreview as MailPreviewDialog
from .mailTemplate import MailTemplateDialog
-log = loguru.logger
-log.remove()
-log.add(sys.stdout, level="INFO")
-log.add(f"{LOG_DIR}/application.log", rotation="1 MB", retention="10 days")
+CSS_RESET = ""
+
+empty_signature = """"""
-empty_signature = """
+def _escape_braces_in_style(html: str) -> str:
+ """
+ Double curly braces ONLY inside blocks so that
+ str.format(...) won't treat CSS as placeholders. The doubled braces
+ will automatically render back to single braces after formatting.
+ """
- )",
+ repl,
+ html,
+ flags=re.IGNORECASE | re.DOTALL,
+ )
- hr { height: 1px; border-width: 0; }
- li.unchecked::marker { content: "\2610"; }
+def _split_eml_headers_body(eml_text: str) -> tuple[str, str]:
+ """
+ Return (headers, body_html). Robustly split on first blank line.
+ Accepts lines that contain only spaces/tabs as the separator.
+ """
- li.checked::marker { content: "\2612"; }
-
-
-
-
-"""
+ parts = re.split(r"\r?\n[ \t]*\r?\n", eml_text, maxsplit=1)
+ if len(parts) == 2:
+ return parts[0], parts[1]
+ # Fallback: try to split right after the Content-Transfer-Encoding line
+ m = re.search(
+ r"(?:^|\r?\n)Content-Transfer-Encoding:.*?(?:\r?\n)",
+ eml_text,
+ flags=re.I | re.S,
+ )
+ if m:
+ return eml_text[: m.end()], eml_text[m.end() :]
+ return "", eml_text # last resort: treat entire content as body
class Mail_Dialog(QtWidgets.QDialog, MailPreviewDialog):
def __init__(
self,
- app_id,
- app_name,
- app_subject,
- prof_name,
- prof_mail,
+ app_id=None,
+ app_name=None,
+ app_subject=None,
+ prof_name=None,
+ prof_mail=None,
accepted_books=None,
+ ordered_books=None,
parent=None,
default_mail="Information zum Semesterapparat",
):
@@ -58,6 +79,7 @@ class Mail_Dialog(QtWidgets.QDialog, MailPreviewDialog):
self.subject = app_subject
self.profname = prof_name
self.books = accepted_books if accepted_books is not None else []
+ self.ordered_books = ordered_books if ordered_books is not None else []
self.mail_data = ""
self.signature = self.determine_signature()
self.prof_mail = prof_mail
@@ -65,52 +87,29 @@ class Mail_Dialog(QtWidgets.QDialog, MailPreviewDialog):
self.prof_name.setText(prof_name)
self.mail_name.setText(self.prof_mail)
self.load_mail_templates()
- # if none of the radio buttons is checked, disable the accept button of the dialog
self.setWindowIcon(Icon("mail").icon)
self.btn_okay.setEnabled(False)
Icon("edit_note", self.newTemplate)
self.newTemplate.clicked.connect(self.open_new_template)
if default_mail is not None:
- # get the nearest match to the default mail
for i in range(self.comboBox.count()):
if default_mail in self.comboBox.itemText(i):
default_mail = self.comboBox.itemText(i)
break
self.comboBox.setCurrentText(default_mail)
+ self.comboBox.currentIndexChanged.connect(self.set_mail)
+ # re-render when user changes greeting via radio buttons
self.gender_female.clicked.connect(self.set_mail)
self.gender_male.clicked.connect(self.set_mail)
self.gender_non.clicked.connect(self.set_mail)
+
+ # reflect initial state (OK disabled until a greeting is chosen)
+ self._update_ok_button()
self.btn_okay.clicked.connect(self.createAndSendMail)
- def open_new_template(self):
- log.info("Opening new template dialog")
- # TODO: implement new mail template dialog
- dialog = MailTemplateDialog()
- dialog.updateSignal.connect(self.load_mail_templates)
- dialog.exec()
-
- pass
-
- def determine_signature(self):
- if config.mail.signature is empty_signature or config.mail.signature == "":
- return """Mit freundlichen Grüßen
-Ihr Semesterapparatsteam
-Mail: semesterapparate@ph-freiburg.de
-Tel.: 0761/682-778 | 07617682-545"""
- else:
- return config.mail.signature
-
- def load_mail_templates(self):
- # print("loading mail templates")
- log.info("Loading mail templates")
- mail_templates = os.listdir("mail_vorlagen")
- log.info(f"Mail templates: {mail_templates}")
- self.comboBox.clear()
- for template in mail_templates:
- self.comboBox.addItem(template)
-
+ # add these helpers inside Mail_Dialog
def get_greeting(self):
prof = self.profname.split(" ")[0]
if self.gender_male.isChecked():
@@ -124,45 +123,104 @@ Tel.: 0761/682-778 | 07617682-545"""
name = f"{self.profname.split(' ')[1]} {self.profname.split(' ')[0]}"
return f"Guten Tag {name},"
+ def _update_ok_button(self):
+ checked = (
+ self.gender_male.isChecked()
+ or self.gender_female.isChecked()
+ or self.gender_non.isChecked()
+ )
+ self.btn_okay.setEnabled(checked)
+
+ def _on_gender_toggled(self, checked: bool):
+ # Only refresh when a button becomes checked
+ if checked:
+ self.set_mail()
+
+ def open_new_template(self):
+ log.info("Opening new template dialog")
+ dialog = MailTemplateDialog()
+ dialog.updateSignal.connect(self.load_mail_templates)
+ dialog.exec()
+
+ def determine_signature(self):
+ # use equality, not identity
+ if (
+ config.mail.signature == empty_signature
+ or config.mail.signature.strip() == ""
+ ):
+ return """Mit freundlichen Grüßen
+Ihr Semesterapparatsteam
+Mail: semesterapparate@ph-freiburg.de
+Tel.: 0761/682-778 | 0761/682-545"""
+ else:
+ return config.mail.signature
+
+ def load_mail_templates(self):
+ log.info("Loading mail templates")
+ mail_templates = [
+ f for f in os.listdir("mail_vorlagen") if f.lower().endswith(".eml")
+ ]
+ log.info(f"Mail templates: {mail_templates}")
+ self.comboBox.clear()
+ for template in mail_templates:
+ self.comboBox.addItem(template)
+
def set_mail(self):
log.info("Setting mail")
+ self._update_ok_button() # keep OK enabled state in sync
+
email_template = self.comboBox.currentText()
- if email_template == "":
+ if not email_template:
log.debug("No mail template selected")
return
+
with open(f"mail_vorlagen/{email_template}", "r", encoding="utf-8") as f:
- mail_template = f.read()
+ eml_text = f.read()
+
+ # header label for UI (unchanged)
email_header = email_template.split(".eml")[0]
if "{AppNr}" in email_template:
- email_header = email_template.split(".eml")[0]
- email_header = email_header.format(AppNr=self.appid, AppName=self.appname)
+ email_header = email_header.format(AppNr=self.appid, AppName=self.appname)
self.mail_header.setText(email_header)
- self.mail_data = mail_template.split("")[0]
- mail_html = mail_template.split("")[1]
- mail_html = "" + mail_html
- Appname = self.appname
- mail_html = mail_html.format(
- Profname=self.profname.split(" ")[0],
- Appname=Appname,
- AppNr=self.appid,
- AppSubject=self.subject,
- greeting=self.get_greeting(),
- signature=self.signature,
- newEditions="
".join(
- [
- f"{book.title} von {book.author} (ISBN: {book.isbn}, Auflage: {book.edition}, In Bibliothek: {'ja' if getattr(book, 'library_location', 1) == 1 else 'nein'})"
- for book in self.books
- ]
- )
- if self.books
- else "keine neuen Auflagen gefunden",
- )
- self.mail_body.setHtml(mail_html)
+ headers, body_html = _split_eml_headers_body(eml_text)
+ body_html = _escape_braces_in_style(body_html)
+
+ # compute greeting from the current toggle selection
+ greeting = self.get_greeting()
+
+ try:
+ body_html = body_html.format(
+ Profname=self.profname.split(" ")[
+ 0
+ ], # last name if your template uses {Profname}
+ Appname=self.appname,
+ AppNr=self.appid,
+ AppSubject=self.subject,
+ greeting=greeting,
+ signature=self.signature,
+ newEditions="\n".join(
+ [
+ f"- {book.title} (ISBN: {','.join(book.isbn)}, Auflage: {book.edition if book.edition else 'nicht bekannt'}, In Bibliothek: {'ja' if getattr(book, 'signature', None) is not None and 'Handbibliothek' not in str(book.library_location) else 'nein'}, Typ: {book.get_book_type()}) Aktuelle Auflage: {book.old_book.edition if book.old_book and book.old_book.edition else 'nicht bekannt'}"
+ for book in (self.books or [])
+ ]
+ )
+ if self.books
+ else "keine neuen Auflagen gefunden",
+ newEditionsOrdered="\n".join(
+ [
+ f" - {book.title}, ISBN: {','.join(book.isbn)}, Bibliotheksstandort : {book.library_location if book.library_location else 'N/A'}, Link: {book.link}"
+ for book in (self.ordered_books or [])
+ ]
+ ),
+ )
+ except Exception as e:
+ log.error(f"Template formatting failed: {e}")
+
+ self.mail_body.setPlainText(body_html)
def createAndSendMail(self):
log.info("Sending mail")
- import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
@@ -176,31 +234,29 @@ Tel.: 0761/682-778 | 07617682-545"""
message["From"] = sender_email
message["To"] = self.prof_mail
message["Subject"] = self.mail_header.text()
- # include a Fcc to the senders sent folder
- message["cc"] = "semesterapparate@ph-freiburg.de"
+ message["Cc"] = "semesterapparate@ph-freiburg.de"
+
+ mail_body = self.mail_body.toPlainText()
+ # strange_string = """p, li { white-space: pre-wrap; }
+ # hr { height: 1px; border-width: 0; }
+ # li.unchecked::marker { content: "\2610"; }
+ # li.checked::marker { content: "\2612"; }
+ # """
+ # mail_body.replace(strange_string, "")
+ message.attach(MIMEText(mail_body, "Plain", "utf-8"))
- mail_body = self.mail_body.toHtml()
- message.attach(MIMEText(mail_body, "html"))
mail = message.as_string()
with smtplib.SMTP_SSL(smtp_server, port) as server:
- server.connect(smtp_server, port)
- # server.connect(smtp_server, port)
- # server.auth(mechanism="PLAIN")
+ server.connect(smtp_server, port) # not needed for SMTP_SSL
if config.mail.use_user_name is True:
- # print(config["mail"]["user_name"])
-
server.login(config.mail.user_name, password)
else:
server.login(sender_email, password)
server.sendmail(sender_email, tolist, mail)
-
- # print("Mail sent")
- # end active process
server.quit()
+ pass
log.info("Mail sent, closing connection to server and dialog")
- # close the dialog
-
self.accept()
@@ -225,8 +281,6 @@ def launch_gui(
if __name__ == "__main__":
- import sys
-
app = QtWidgets.QApplication(sys.argv)
Dialog = QtWidgets.QDialog()
ui = Mail_Dialog()
diff --git a/src/ui/dialogs/mailTemplate.py b/src/ui/dialogs/mailTemplate.py
index c8aae04..32c0dd5 100644
--- a/src/ui/dialogs/mailTemplate.py
+++ b/src/ui/dialogs/mailTemplate.py
@@ -1,23 +1,18 @@
import os
+import re
+import sys
-from PySide6 import QtGui, QtWidgets, QtCore
+from loguru import logger as log
+from PySide6 import QtCore, QtWidgets
from src import Icon
from .dialog_sources import NewMailTemplateDesignerDialog
-import sys
-from loguru import logger as log
-
logger = log
logger.remove()
logger.add("logs/application.log", rotation="1 week", retention="1 month", enqueue=True)
-log.add(
- f"logs/mail.log",
- enqueue=True,
-)
-
-# logger.add(sys.stderr, format="{time} {level} {message}", level="INFO")
+log.add("logs/mail.log", enqueue=True)
logger.add(sys.stdout)
@@ -28,35 +23,34 @@ class MailTemplateDialog(QtWidgets.QDialog, NewMailTemplateDesignerDialog):
super().__init__(parent)
self.setupUi(self)
self.setWindowIcon(Icon("edit_note").icon)
- self.setWindowTitle("Mailvorlage erstellen")
+ self.setWindowTitle("Mailvorlage erstellen (Text)")
+
+ # placeholders UI
self.placeholder_list.addItem("")
self.placeholder_list.setCurrentText("")
self.insertPlaceholder.clicked.connect(self.insert_placeholder)
self.placeholder_list.currentTextChanged.connect(self.updateDescription)
- self.bold.clicked.connect(self.setFontBold)
- self.italic.clicked.connect(self.setTextFontItalic)
- self.underlined.clicked.connect(self.setTextFontUnderline)
- self.testTemplate.clicked.connect(self.test_template)
- self.fontBox.currentFontChanged.connect(self.setCurrentFont)
- self.fontSize.currentTextChanged.connect(self.setFontSize)
- # buttonbox
- # save button
+
+ # formatting buttons (kept enabled for UX, but saving uses plain text)
+
+ # buttons
self.buttonBox.button(
QtWidgets.QDialogButtonBox.StandardButton.Save
).clicked.connect(self.save_template)
- # discard button
self.buttonBox.button(
QtWidgets.QDialogButtonBox.StandardButton.Discard
).clicked.connect(self.discard_changes)
- # cancel button
self.buttonBox.button(
QtWidgets.QDialogButtonBox.StandardButton.Cancel
).clicked.connect(self.closeNow)
- log.info("Mail template dialog setup complete")
+ log.info("Mail template dialog (plaintext) setup complete")
+
+ def _normalize_newlines(self, s: str) -> str:
+ # Convert any CRLF/CR to LF then back to CRLF for .eml
+ s = s.replace("\\r\\n", "\\n").replace("\\r", "\\n")
+ return s
def save_template(self):
- # print("save triggered")
- # create a dialog to ask for the name of the template
dialog = QtWidgets.QInputDialog()
dialog.setInputMode(QtWidgets.QInputDialog.InputMode.TextInput)
dialog.setLabelText("Bitte geben Sie den Namen des Templates ein:")
@@ -66,12 +60,11 @@ class MailTemplateDialog(QtWidgets.QDialog, NewMailTemplateDesignerDialog):
dialog.setWindowIcon(Icon("save").icon)
save = dialog.exec()
template_name = dialog.textValue()
- log.info("Saving template")
+ log.info("Saving plaintext template")
if template_name != "" and save == 1:
template = template_name + ".eml"
if template in os.listdir("mail_vorlagen"):
log.error("Template already exists")
- # warning dialog
dialog = QtWidgets.QMessageBox()
dialog.setIcon(QtWidgets.QMessageBox.Icon.Warning)
Icon("warning", dialog)
@@ -87,36 +80,34 @@ class MailTemplateDialog(QtWidgets.QDialog, NewMailTemplateDesignerDialog):
ret = dialog.exec()
if ret == QtWidgets.QMessageBox.StandardButton.No:
return
- mail = f"""Subject: {self.subject.text()}
-MIME-Version: 1.0
-Content-Type: text/html; charset="UTF-8"
-Content-Transfer-Encoding: 8bit
-{self.templateEdit.toHtml()}"""
- html_head = """
-
-
-
- """
- mail_base = mail.split("")[0]
- mail_body = mail.split("")[1]
- mail = mail_base + html_head + mail_body
- mail = (
- mail.replace("<", "<")
- .replace(">", ">")
- .replace(""", '"')
- .replace("&", "&")
- )
- with open(f"mail_vorlagen/{template}", "w", encoding="utf-8") as f:
- f.write(mail)
+
+ # Build plaintext from editor
+ body_text = self.templateEdit.toPlainText()
+ body_text = self._normalize_newlines(body_text)
+
+ # Build EML headers and payload (headers, then one blank line, then body)
+ mail_headers = f"""
+ Subject: {self.subject.text()}
+ """
+ mail_body = body_text
+
+ eml = mail_headers + "\n\n" + mail_body
+ eml = re.sub(r" +", " ", eml) # remove multiple spaces
+ eml = re.sub(r"\n +", "\n", eml) #
+ print(eml)
+
+ with open(
+ f"mail_vorlagen/{template}", "w", encoding="utf-8", newline=""
+ ) as f:
+ f.write(eml)
+
self.updateSignal.emit()
self.close()
- log.success(f"Template {template} saved successfully")
+ # log.success(f"Template {template} saved successfully (plaintext)")
else:
- # warning dialog
dialog = QtWidgets.QMessageBox()
dialog.setIcon(QtWidgets.QMessageBox.Icon.Warning)
dialog.setWindowIcon(Icon("warning").icon)
-
dialog.setText("Bitte geben Sie einen Namen für das Template ein.")
dialog.setWindowTitle("Fehler beim Speichern")
dialog.exec()
@@ -145,52 +136,20 @@ Content-Transfer-Encoding: 8bit
self.close()
def updateDescription(self):
- # print("update triggered")
- # print(self.placeholder_list.currentText())
placeholders = {
- "anrede": "Die Anrede beinhaltet sowohl Person als auch Sehr geehrte/r; dargestellt als: {greeting}",
+ "anrede": "Die Anrede inkl. 'Sehr geehrte/r' oder neutral; dargestellt als: {greeting}",
"apparatsfach": "Das Fach, in welchem der Apparat angelegt wurde; dargestellt als: {AppSubject}",
"apparatsname": "Der Name des Apparats; dargestellt als: {Appname}",
"apparatsnummer": "Die Nummer des Apparats; dargestellt als: {AppNr}",
"dozentname": "Der Name des Dozenten / der Dozentin; dargestellt als: {Profname}",
- "signatur": "Die persönliche / allgemeine Signatur am ende der Mail; dargestellt als: {signature}",
+ "signatur": "Ihre Signatur; dargestellt als: {signature}",
"": " ",
}
- for (
- key,
- item,
- ) in placeholders.items():
+ for key, item in placeholders.items():
if key in self.placeholder_list.currentText().lower():
self.lineEdit.setText(item)
break
- def setCurrentFont(self):
- font = self.fontBox.currentFont()
- font.setPointSize(int(self.fontSize.currentText()))
- self.templateEdit.setFont(font)
-
- def setFontSize(self):
- size = self.fontSize.currentText()
- self.templateEdit.setFontPointSize(int(size))
-
- def setFontBold(self):
- if self.bold.isChecked():
- self.templateEdit.setFontWeight(QtGui.QFont.Weight.Bold)
- else:
- self.templateEdit.setFontWeight(QtGui.QFont.Weight.Normal)
-
- def setTextFontItalic(self):
- if self.italic.isChecked():
- self.templateEdit.setFontItalic(True)
- else:
- self.templateEdit.setFontItalic(False)
-
- def setTextFontUnderline(self):
- if self.underlined.isChecked():
- self.templateEdit.setFontUnderline(True)
- else:
- self.templateEdit.setFontUnderline(False)
-
def test_template(self):
placeholders = [
"{greeting}",
@@ -201,35 +160,29 @@ Content-Transfer-Encoding: 8bit
"{signature}",
]
mail_subject = self.subject.text()
- mail_body = self.templateEdit.toHtml()
+ mail_body = self.templateEdit.toPlainText()
missing_body = []
missing_subject = []
try:
- assert placeholders[2] in mail_subject
+ assert "{Appname}" in mail_subject or "{AppName}" in mail_subject
except AssertionError:
- missing_subject.append(placeholders[2])
- # check if all placeholders are in the mail body
+ missing_subject.append("{Appname}")
for placeholder in placeholders:
try:
assert placeholder in mail_body
except AssertionError:
missing_body.append(placeholder)
- if missing_body != []:
- # warning dialog
- Icon("template_fail", self.testTemplate)
+ if missing_body:
dialog = QtWidgets.QMessageBox()
dialog.setWindowIcon(Icon("warning").icon)
-
dialog.setText("Folgende Platzhalter fehlen im Template:")
-
missing = (
"Betreff:\n"
- + "\n".join(missing_subject)
- + "\n\n"
+ + "\\n".join(missing_subject)
+ + "\\n\\n"
+ "Mailtext:\n"
- + "\n".join(missing_body)
+ + "\\n".join(missing_body)
)
-
dialog.setInformativeText(f"{missing}")
dialog.setWindowTitle("Fehlende Platzhalter")
dialog.exec()
@@ -238,21 +191,16 @@ Content-Transfer-Encoding: 8bit
self.testTemplate.setText("✔")
def insert_placeholder(self):
- placeholder = {
+ placeholder_map = {
"anrede": "{greeting}",
"apparatsfach": "{AppSubject}",
"apparatsname": "{Appname}",
"apparatsnummer": "{AppNr}",
"dozentname": "{Profname}",
- "signatur": """--
-{signature}
-""",
+ "signatur": "{signature}",
}
cursor = self.templateEdit.textCursor()
- for (
- key,
- item,
- ) in placeholder.items():
+ for key, item in placeholder_map.items():
if key in self.placeholder_list.currentText().lower():
cursor.insertText(item)
break
diff --git a/src/ui/dialogs/mail_preview_ui.py b/src/ui/dialogs/mail_preview_ui.py
index da6b448..b633b07 100644
--- a/src/ui/dialogs/mail_preview_ui.py
+++ b/src/ui/dialogs/mail_preview_ui.py
@@ -6,7 +6,7 @@
# run again. Do not edit this file unless you know what you are doing.
-from PySide6 import QtCore, QtGui, QtWidgets
+from PySide6 import QtCore, QtWidgets
class Ui_eMailPreview(object):
diff --git a/src/ui/dialogs/medienadder.py b/src/ui/dialogs/medienadder.py
index ecdeac6..e8b5da5 100644
--- a/src/ui/dialogs/medienadder.py
+++ b/src/ui/dialogs/medienadder.py
@@ -1,8 +1,9 @@
from PySide6 import QtCore, QtGui, QtWidgets
-from .dialog_sources.medianadder_ui import Ui_Dialog
from src import Icon
+from .dialog_sources.medianadder_ui import Ui_Dialog
+
class MedienAdder(QtWidgets.QDialog, Ui_Dialog):
def __init__(self, parent=None):
@@ -78,4 +79,4 @@ def launch_gui():
dialog = MedienAdder()
dialog.show()
app.exec()
- # print(dialog.mode, dialog.data, dialog.result())
+ # #print(dialog.mode, dialog.data, dialog.result())
diff --git a/src/ui/dialogs/newEdition.py b/src/ui/dialogs/newEdition.py
new file mode 100644
index 0000000..77fe6e1
--- /dev/null
+++ b/src/ui/dialogs/newEdition.py
@@ -0,0 +1,109 @@
+from PySide6 import QtCore, QtWidgets
+
+from src.services.catalogue import Catalogue
+from src.database import Database
+from src.ui.dialogs.mail import Mail_Dialog
+
+from .dialog_sources.order_neweditions_ui import Ui_Dialog
+
+
+class NewEditionDialog(QtWidgets.QDialog, Ui_Dialog):
+ def __init__(self, app_id, mail_data):
+ super().__init__()
+ self.setupUi(self)
+ self.setWindowTitle("Neuauflagen bestellen")
+ self.db = Database()
+ self.catalogue = Catalogue()
+ self.app_id = app_id
+ self.mail_data = mail_data
+ self.books = self.db.getNewEditionsByApparat(app_id)
+ self.pushButton.clicked.connect(self.orderBooks)
+ self.populateTable()
+
+ def populateTable(self):
+ for book in self.books:
+ # signature not required here; using book.signature directly when needed
+ link_label = QtWidgets.QLabel()
+ link = (
+ book.link
+ if book.link != "SWB"
+ else f"https://www.lehmanns.de/search/quick?mediatype_id=&q={book.isbn[0]}"
+ )
+
+ link_label.setText(f'Lehmanns.de')
+ link_label.setOpenExternalLinks(True)
+ link_label.setTextFormat(QtCore.Qt.TextFormat.RichText)
+ link_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
+ link_label.setTextInteractionFlags(
+ QtCore.Qt.TextInteractionFlag.TextBrowserInteraction
+ )
+ self.tableWidget.insertRow(0)
+ # first column is checkbox for ordering
+ checkbox = QtWidgets.QCheckBox()
+ checked = True if not book.signature else False
+ if book.library_location is not None:
+ checked = True if "hb" in book.library_location else checked
+ checkbox.setChecked(checked)
+ self.tableWidget.setCellWidget(0, 0, checkbox)
+ self.tableWidget.setItem(
+ 0,
+ 1,
+ QtWidgets.QTableWidgetItem(
+ str(book.signature if book.signature else "")
+ ),
+ )
+ self.tableWidget.setItem(0, 2, QtWidgets.QTableWidgetItem(book.title))
+ isbn = (
+ book.isbn[0]
+ if isinstance(book.isbn, list) and len(book.isbn) > 0
+ else book.isbn
+ )
+ self.tableWidget.setItem(0, 3, QtWidgets.QTableWidgetItem(isbn))
+ self.tableWidget.setItem(0, 4, QtWidgets.QTableWidgetItem(book.author))
+ self.tableWidget.setItem(0, 5, QtWidgets.QTableWidgetItem(book.edition))
+ self.tableWidget.setItem(
+ 0, 6, QtWidgets.QTableWidgetItem(book.library_location)
+ )
+ self.tableWidget.setCellWidget(0, 7, link_label)
+
+ def orderBooks(self):
+ ordered_books = []
+ for row in range(self.tableWidget.rowCount()):
+ checkbox = self.tableWidget.cellWidget(row, 0)
+ if checkbox.isChecked():
+ book = self.books[row]
+ book.link = (
+ book.link
+ if book.link != "SWB"
+ else f"https://www.lehmanns.de/search/quick?mediatype_id=&q={book.isbn[0]}"
+ )
+ # print(f"Bestelle Neuauflage für {book.title} ({book.edition})")
+ book.isbn = [book.isbn] if isinstance(book.isbn, str) else book.isbn
+ ordered_books.append(book)
+ # Process ordered_books as needed
+ editionId = self.db.getNewEditionId(book)
+ self.db.setOrdered(editionId)
+
+ self.mail = Mail_Dialog(
+ app_id=self.mail_data.get("app_nr"),
+ prof_name=self.mail_data.get("prof_name"),
+ prof_mail=self.mail_data.get("prof_mail"),
+ app_name=self.mail_data.get("app_name"),
+ default_mail="Bitte um Bestellung",
+ ordered_books=ordered_books,
+ )
+ self.mail.exec()
+
+
+def launch():
+ app = QtWidgets.QApplication.instance()
+ if app is None:
+ app = QtWidgets.QApplication([])
+ mail_data = {
+ "prof_name": "Erwerbung",
+ "prof_mail": "carola.wiestler@ph-freiburg.de",
+ "app_nr": 131,
+ "app_name": "Beratung und Teamarbeit",
+ }
+ dialog = NewEditionDialog(app_id=18, mail_data=mail_data)
+ dialog.exec()
diff --git a/src/ui/dialogs/parsed_titles.py b/src/ui/dialogs/parsed_titles.py
index d5ba73b..416f38b 100644
--- a/src/ui/dialogs/parsed_titles.py
+++ b/src/ui/dialogs/parsed_titles.py
@@ -1,17 +1,19 @@
+import sys
+
+import loguru
from PySide6 import QtWidgets
-from src.backend import AutoAdder
-
+from src import LOG_DIR
+from src.background import AutoAdder
from .dialog_sources.parsed_titles_ui import Ui_Form
-import loguru
-import sys
-from src import LOG_DIR
+
log = loguru.logger
log.remove()
log.add(sys.stdout, level="INFO")
log.add(f"{LOG_DIR}/application.log", rotation="1 MB", retention="10 days")
+
class ParsedTitles(QtWidgets.QWidget, Ui_Form):
def __init__(self, parent=None):
super().__init__(parent)
@@ -32,7 +34,7 @@ class ParsedTitles(QtWidgets.QWidget, Ui_Form):
self.progressBar.setValue(value)
def worker_quit(self):
- # print("Terminating worker")
+ # #print("Terminating worker")
self.worker.terminate()
self.worker.quit()
self.worker.deleteLater()
@@ -66,7 +68,7 @@ class ParsedTitles(QtWidgets.QWidget, Ui_Form):
def determine_progress(self, signal):
# check length of listWidget
length = self.listWidget.count()
- # print(f"Length of listWidget: {length}")
+ # #print(f"Length of listWidget: {length}")
if length == 0:
log.info("AutoAdder finished")
self.buttonBox.accepted.emit()
diff --git a/src/ui/dialogs/popup_confirm.py b/src/ui/dialogs/popup_confirm.py
index 8b7f19c..2d8401a 100644
--- a/src/ui/dialogs/popup_confirm.py
+++ b/src/ui/dialogs/popup_confirm.py
@@ -10,7 +10,7 @@ from PySide6 import QtWidgets
from src import Icon
-from .dialog_sources.Ui_confirm_extend import Ui_extend_confirm
+from .dialog_sources.confirm_extend_ui import Ui_extend_confirm
class ConfirmDialog(QtWidgets.QDialog, Ui_extend_confirm):
diff --git a/src/ui/dialogs/progress.py b/src/ui/dialogs/progress.py
index 470a94b..9464ff3 100644
--- a/src/ui/dialogs/progress.py
+++ b/src/ui/dialogs/progress.py
@@ -1,13 +1,12 @@
-from typing import List, Optional, Set, Union
-import re
+from typing import List
from PySide6 import QtCore
from PySide6.QtWidgets import QDialog, QPushButton, QVBoxLayout
from qtqdm import Qtqdm, QtqdmProgressBar
-from src.logic import BookData
-from src.logic.lehmannsapi import LehmannsClient
-from src.logic.swb import SWB
+from src.core.models import BookData
+from src.services.lehmanns import LehmannsClient
+from src.services.sru import SWB
class CheckThread(QtCore.QThread):
@@ -32,6 +31,7 @@ class CheckThread(QtCore.QThread):
range(len(self.items)),
unit_scale=True,
)
+ swb_client = SWB()
for i in tqdm_object:
book: BookData = self.items[i]
author = (
@@ -43,7 +43,7 @@ class CheckThread(QtCore.QThread):
# remove trailing punctuation from title
title = book.title.rstrip(" .:,;!?")
response: list[BookData] = []
- response = SWB().getBooks(
+ response = swb_client.getBooks(
[
"pica.bib=20735",
f"pica.tit={title.split(':')[0].strip()}",
@@ -88,4 +88,6 @@ class ProgressDialog(QDialog):
layout.addWidget(self.start_button)
def start(self):
-
+ # Start logic is managed externally; keep method for UI wiring
+ pass
+
diff --git a/src/ui/dialogs/settings.py b/src/ui/dialogs/settings.py
index df918d0..c21b70d 100644
--- a/src/ui/dialogs/settings.py
+++ b/src/ui/dialogs/settings.py
@@ -1,10 +1,12 @@
-from PySide6 import QtCore, QtGui, QtWidgets
-from src import Icon, settings
-from .dialog_sources.settings_ui import Ui_Dialog as _settings
-from src.ui.widgets.iconLine import IconWidget
-import loguru
import sys
-from src import LOG_DIR
+
+import loguru
+from PySide6 import QtCore, QtGui, QtWidgets
+
+from src import LOG_DIR, Icon, settings
+from src.ui.widgets.iconLine import IconWidget
+
+from .dialog_sources.settings_ui import Ui_Dialog as _settings
log = loguru.logger
log.remove()
@@ -12,7 +14,6 @@ log.add(sys.stdout, level="INFO")
log.add(f"{LOG_DIR}/application.log", rotation="1 MB", retention="10 days")
-
base = """'