Merge branch 'dev'

This commit is contained in:
2025-07-03 12:33:51 +02:00
128 changed files with 4043 additions and 2510 deletions

View File

@@ -1,22 +0,0 @@
[tool.bumpversion]
current_version = "0.2.1"
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
serialize = ["{major}.{minor}.{patch}"]
search = "{current_version}"
replace = "{new_version}"
regex = false
ignore_missing_version = false
ignore_missing_files = false
tag = true
sign_tags = false
tag_name = "v{new_version}"
tag_message = "Bump version: {current_version} → {new_version}"
allow_dirty = false
commit = true
message = "Bump version: {current_version} → {new_version}"
commit_args = ""
setup_hooks = []
pre_commit_hooks = []
post_commit_hooks = []
[[tool.bumpversion.files]]
filename = "src/__init__.py"

View File

@@ -42,16 +42,6 @@ jobs:
id: bump_version
run: |
uv tool run bump-my-version bump ${{ github.event.inputs.bump }} --tag --allow-dirty
- name: Add release notes
id: add_release_notes
run: |
echo "RELEASE_NOTES<<EOF" >> $GITHUB_ENV
echo "${{ github.event.inputs.release_notes }}" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
- name: Create Gitea Release
if: ${{ github.event.inputs.github_release == 'true' }}
uses: softprops/action-gh-release@v1
- name: Add release notes
id: add_release_notes
run: |
@@ -62,14 +52,7 @@ jobs:
if: ${{ github.event.inputs.github_release == 'true' }}
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ github.sha }}
release_name: Release ${{ github.sha }}
body: ${{ env.RELEASE_NOTES }}
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}
tag_name:
release_name: Release ${{ github.sha }}
body: ${{ env.RELEASE_NOTES }}
draft: false

3
.gitignore vendored
View File

@@ -229,4 +229,5 @@ config.yaml
logs/
*.pdf
*.docx
*.docx
test.py

1
.version Normal file
View File

@@ -0,0 +1 @@
0.2.1

31
build.py Normal file
View File

@@ -0,0 +1,31 @@
import os
import shutil
with open(".version", "r") as version_file:
version = version_file.read().strip()
print("Building the project...")
print("Cleaning build dir...")
# clear dist directory
if os.path.exists("dist"):
shutil.rmtree("dist")
os.makedirs("dist")
print("Build directory cleaned.")
build = input("Include console in build? (y/n): ").strip().lower()
command = f"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.exe --windows-icon-from-ico=icons/logo.ico"
executable = "main.py"
if build == 'y':
os.system(f"{command} {executable}")
else:
command += " --windows-console-mode=disable"
os.system(f"{command} {executable}")
# rename main.dist in dist dir to SemesterApparatsManager
os.rename("dist/main.dist", "dist/SemesterApparatsManager")
print("Build complete.")

57
config/base_config.yaml Normal file
View File

@@ -0,0 +1,57 @@
default_apps: true
save_path: .
icon_path: icons/
openAI:
api_key:
model:
zotero:
api_key:
library_id:
library_type: user
database:
name: semesterapparate.db
path: .
temp: ~/AppData/Local/SAM/SemesterApparatsManager/Cache
mail:
smtp_server:
port:
sender:
printer_mail:
user_name:
use_user_name: true
password:
signature:
colors:
dark: '#6b6160'
light: '#000000'
warning: '#ff0000'
success: '#00ff00'
icons:
locked: locked.svg
logo: logo.ico
show_password: visibility_off.svg
hide_password: visibility_on.svg
settings: settings.svg
today: calendar_today.svg
save: save.svg
edit_note: edit_note.svg
warning: warning.svg
error: error.svg
mail: mail.svg
semester: semester.svg
template_fail: test_fail.svg
offAction: shutdown.svg
info: info.svg
help: help.svg
close: close.svg
notification: notification.svg
valid_true: check_success.svg
valid_false: check_fail.svg
edit: edit.svg
important_warn: red_warn.svg
person: person_add.svg
database: database.svg
icons: icons.svg
api: api.svg
print: print.svg
db_search: db_search.svg

View File

@@ -1,8 +1,19 @@
from typing import Optional
from typing import Optional, Any, Union
from dataclasses import dataclass
from omegaconf import OmegaConf, DictConfig
import os
from pathlib import Path
@dataclass
class OpenAI:
api_key: str
model: str
def getattr(self, name: str):
return getattr(self, name)
def _setattr(self, name: str, value: Any):
setattr(self, name, value)
@dataclass
class Zotero:
@@ -10,25 +21,29 @@ class Zotero:
library_id: str
library_type: str
def getattr(self, name):
def getattr(self, name: str):
return getattr(self, name)
def _setattr(self, name, value):
def _setattr(self, name: str, value: Any):
setattr(self, name, value)
@dataclass
class Database:
name: str
path: str
temp: str
def getattr(self, name):
path: Union[str, Path, None]
temp: Union[str, Path, None]
def getattr(self, name: str):
return getattr(self, name)
def _setattr(self, name, value):
def _setattr(self, name: str, value: Any):
setattr(self, name, value)
def __post_init__(self):
if isinstance(self.path, str):
self.path = Path(self.path).expanduser()
if isinstance(self.temp, str):
self.temp = Path(self.temp).expanduser()
@dataclass
class Mail:
@@ -59,13 +74,13 @@ class Mail:
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px;
margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html>"""
def getattr(self, name):
def getattr(self, name: str):
return getattr(self, name)
def _setattr(self, name, value):
def _setattr(self, name: str, value: Any):
setattr(self, name, value)
def setValue(self, **kwargs):
def setValue(self, **kwargs: Any):
for key, value in kwargs.items():
if hasattr(self, key):
setattr(self, key, value)
@@ -79,7 +94,7 @@ class Icons:
self._colors = None
self._icons = None
def assign(self, key, value):
def assign(self, key: str, value: Any):
setattr(self, key, value)
@property
@@ -87,7 +102,7 @@ class Icons:
return self._path
@path.setter
def path(self, value):
def path(self, value: Any):
self._path = value
@property
@@ -95,7 +110,7 @@ class Icons:
return self._colors
@colors.setter
def colors(self, value):
def colors(self, value: Any):
self._colors = value
@property
@@ -103,10 +118,10 @@ class Icons:
return self._icons
@icons.setter
def icons(self, value):
def icons(self, value: Any):
self._icons = value
def get(self, name):
def get(self, name: str):
return self.icons.get(name)
@@ -121,7 +136,7 @@ class Config:
"""
_config: Optional[DictConfig] = None
config_exists: bool = True
def __init__(self, config_path: str):
"""
Loads the configuration file and stores it for future access.
@@ -133,10 +148,22 @@ class Config:
FileNotFoundError: Configuration file not found
"""
if not os.path.exists(config_path):
raise FileNotFoundError(f"Configuration file not found: {config_path}")
# copy base config file to the given path
base = "config/base_config.yaml"
if not os.path.exists(base):
raise FileNotFoundError(f"Base configuration file not found: {base}")
with open(base, "r") as base_file:
base_config = base_file.read()
with open(config_path, "w") as config_file:
config_file.write(base_config)
self.config_exists = False
self._config = OmegaConf.load(config_path)
self.config_path = config_path
@property
def exists(self) -> bool:
return self.config_exists
def save(self):
"""
Saves the current configuration to the file.
@@ -146,16 +173,22 @@ class Config:
"""
OmegaConf.save(self._config, self.config_path)
def reload(self):
"""
Reloads the configuration from the file.
"""
self._config = OmegaConf.load(self.config_path)
@property
def zotero(self):
return Zotero(**self._config.zotero)
@property
def zotero_attr(self, name):
def zotero_attr(self, name: str):
return getattr(self.zotero, name)
@zotero_attr.setter
def zotero_attr(self, name, value):
def zotero_attr(self, name: str, value: Any):
self.zotero._setattr(name, value)
@property
@@ -163,30 +196,37 @@ class Config:
return Database(**self._config.database)
@property
def database_attr(self, name):
def database_attr(self, name: str):
return getattr(self.database, name)
@database_attr.setter
def database_attr(self, name, value):
def database_attr(self, name: str, value: Any):
self.database._setattr(name, value)
@property
def openAI(self):
return OpenAI(**self._config.openAI)
@property
def mail(self):
return Mail(**self._config.mail)
def mail_attr(self, name):
def mail_attr(self, name: str):
return getattr(self.mail, name)
def set_mail_attr(self, name, value):
def set_mail_attr(self, name: str, value: Any):
OmegaConf.update(self._config, f"mail.{name}", value)
def set_database_attr(self, name, value):
def set_database_attr(self, name: str, value: Any):
OmegaConf.update(self._config, f"database.{name}", value)
def set_zotero_attr(self, name, value):
def set_zotero_attr(self, name: str, value: Any):
OmegaConf.update(self._config, f"zotero.{name}", value)
def set_icon_attr(self, name, value):
def set_openai_attr(self, name: str, value: Any):
OmegaConf.update(self._config, f"openAI.{name}", value)
def set_icon_attr(self, name: str, value: Any):
OmegaConf.update(self._config, f"icons.{name}", value)
@property

Binary file not shown.

Before

Width:  |  Height:  |  Size: 264 KiB

After

Width:  |  Height:  |  Size: 264 KiB

33
mail.py
View File

@@ -1,33 +0,0 @@
from PyQt6 import QtWidgets
from src.ui.dialogs.mail import Mail_Dialog
def launch_gui(app_id="", app_name="", app_subject="", prof_name="", prof_mail=""):
QtWidgets.QApplication([""])
dialog = Mail_Dialog(
app_id=app_id,
app_name=app_name,
app_subject=app_subject,
prof_name=prof_name,
prof_mail=prof_mail,
# default_mail="Information bezüglich der Auflösung des Semesterapparates",
)
dialog.exec()
if __name__ == "__main__":
app_id = "123"
app_name = "Test"
app_subject = "TestFach"
prof_name = "Test"
prof_mail = "kirchneralexander020@gmail.com"
launch_gui(
app_id=app_id,
app_name=app_name,
app_subject=app_subject,
prof_name=prof_name,
prof_mail=prof_mail,
)

17
main.py
View File

@@ -1,4 +1,19 @@
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
if __name__ == "__main__":
UI()
app = QtWidgets.QApplication(sys.argv)
if not first_launch:
setup = startup()
if setup == 1:
settings.reload()
# kill qApplication singleton
UI()
else:
sys.exit()
else:
UI()

View File

@@ -1,10 +1,11 @@
[project]
name = "semesterapparatsmanager"
version = "0.1.0"
version = "0.2.1"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"appdirs>=1.4.4",
"beautifulsoup4>=4.12.3",
"bump-my-version>=0.29.0",
"chardet>=5.2.0",
@@ -17,10 +18,11 @@ dependencies = [
"mkdocs-material-extensions>=1.3.1",
"natsort>=8.4.0",
"omegaconf>=2.3.0",
"openai>=1.79.0",
"pandas>=2.2.3",
"playwright>=1.49.1",
"pyqt6>=6.8.0",
"pyqtgraph>=0.13.7",
"pyramid>=2.0.2",
"pyside6>=6.9.1",
"python-docx>=1.1.2",
"pyzotero>=1.6.4",
"ratelimit>=2.2.1",
@@ -33,3 +35,29 @@ dev = [
"icecream>=2.1.4",
"nuitka>=2.5.9",
]
[tool.bumpversion]
current_version = "0.2.1"
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
serialize = ["{major}.{minor}.{patch}"]
search = "{current_version}"
replace = "{new_version}"
regex = false
ignore_missing_version = false
ignore_missing_files = false
tag = true
sign_tags = false
tag_name = "v{new_version}"
tag_message = "Bump version: {current_version} → {new_version}"
allow_dirty = true
commit = true
message = "Bump version: {current_version} → {new_version}"
moveable_tags = []
commit_args = ""
setup_hooks = []
pre_commit_hooks = []
post_commit_hooks = []
[[tool.bumpversion.files]]
filename = "src/__init__.py"
[[tool.bumpversion.files]]
filename = ".version"

View File

@@ -1,16 +1,33 @@
__all__ = ["__version__", "__author__", "Icon", "settings"]
from config import Config
import os
settings = Config("config/config.yaml")
if not os.path.exists(settings.database.temp):
os.mkdir(settings.database.temp)
from .utils.icon import Icon
__version__ = "0.2.1"
__author__ = "Alexander Kirchner"
__all__ = ["__version__", "__author__", "Icon", "settings"]
import os
from appdirs import AppDirs
from config import Config
app = AppDirs("SemesterApparatsManager", "SAM")
LOG_DIR = app.user_log_dir
CONFIG_DIR = app.user_config_dir
if not os.path.exists(LOG_DIR):
os.makedirs(LOG_DIR)
if not os.path.exists(CONFIG_DIR):
os.makedirs(CONFIG_DIR)
settings = Config(f"{CONFIG_DIR}/config.yaml")
DATABASE_DIR = (
app.user_config_dir if settings.database.path is None else settings.database.path
)
if not os.path.exists(DATABASE_DIR):
os.makedirs(DATABASE_DIR)
first_launch = settings.exists
if not os.path.exists(settings.database.temp.expanduser()):
settings.database.temp.expanduser().mkdir(parents=True, exist_ok=True)
from .utils.icon import Icon
if not os.path.exists("logs"):
os.mkdir("logs")

View File

@@ -2,6 +2,14 @@ import hashlib
import random
from .database import Database
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")
# change passwords for apparats, change passwords for users, list users, create and delete users etc
@@ -9,9 +17,14 @@ from .database import Database
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):
"""Defaulf Constructor for the AdminCommands class."""
self.db = Database()
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.
@@ -44,6 +57,20 @@ class AdminCommands:
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.

View File

@@ -6,11 +6,11 @@ from src.backend.database import Database
import loguru
import sys
from src import LOG_DIR
log = loguru.logger
log.remove()
log.add(sys.stdout)
log.add("logs/application.log", rotation="1 MB", retention="10 days")
log.add(sys.stdout, level="INFO")
log.add(f"{LOG_DIR}/application.log", rotation="1 MB", retention="10 days")
db = Database()

View File

@@ -1,12 +1,18 @@
import os
import sqlite3 as sql
import tempfile
from pathlib import Path
from src import settings
from typing import Any, List, Optional, Tuple, Union
import datetime
import json
import os
import sqlite3 as sql
import sys
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
import loguru
from src import LOG_DIR, settings, DATABASE_DIR
from src.backend.db import (
CREATE_ELSA_FILES_TABLE,
CREATE_ELSA_MEDIA_TABLE,
@@ -20,17 +26,16 @@ from src.backend.db import (
CREATE_TABLE_USER,
)
from src.errors import AppPresentError, NoResultError
from src.logic import ApparatData, BookData, Prof, Apparat, ELSA
from src.logic import ELSA, Apparat, ApparatData, BookData, Prof
from src.logic.constants import SEMAP_MEDIA_ACCOUNTS
from src.utils.blob import create_blob
from .semester import Semester
from string import ascii_lowercase as lower, digits, punctuation
import loguru
import sys
log = loguru.logger
log.remove()
log.add(sys.stdout)
log.add("logs/application.log", rotation="1 MB", retention="10 days")
log.add(sys.stdout, level="INFO")
log.add(f"{LOG_DIR}/application.log", rotation="1 MB", retention="10 days")
@@ -39,12 +44,11 @@ ascii_lowercase = lower + digits + punctuation
# get the line that called the function
class Database:
database = settings.database
"""
Initialize the database and create the tables if they do not exist.
"""
def __init__(self, db_path: str = None):
def __init__(self, db_path: Union[Path, None] = None):
"""
Default constructor for the database class
@@ -52,23 +56,41 @@ class Database:
db_path (str, optional): Optional Path for testing / specific purposes. Defaults to None.
"""
if db_path is None:
self.db_path = self.database.path + self.database.name
self.db_path = self.db_path.replace("~", str(Path.home()))
log.debug(self.db_path)
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
self.checkDatabaseStatus()
log.debug(f"Database path: {self.db_path}")
self.db_initialized = False
def initializeDatabase(self):
if not self.db_initialized:
self.checkDatabaseStatus()
self.db_initialized = True
def overwritePath(self, new_db_path: str):
log.debug("got new path, overwriting")
self.db_path = Path(new_db_path)
def checkDatabaseStatus(self):
path = self.database.path
path = path.replace("~", str(Path.home()))
path = os.path.abspath(path)
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()
@@ -503,11 +525,9 @@ class Database:
str: The filename of the recreated file
"""
blob = self.getBlob(filename, app_id)
tempdir = self.database.temp
tempdir = tempdir.replace("~", str(Path.home()))
tempdir_path = Path(tempdir)
if not os.path.exists(tempdir_path):
os.mkdir(tempdir_path)
tempdir = settings.database.temp.expanduser()
if not tempdir.exists():
tempdir.mkdir(parents=True, exist_ok=True)
file = tempfile.NamedTemporaryFile(
delete=False, dir=tempdir_path, mode="wb", suffix=f".{filetype}"
)
@@ -1197,10 +1217,13 @@ class Database:
Returns:
bool: True if the login was successful, False if not
"""
salt = self.query_db(
"SELECT salt FROM user WHERE username=?", (user,), one=True
)[0]
if salt is None:
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(
@@ -1276,6 +1299,13 @@ class Database:
"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
@@ -1463,11 +1493,10 @@ class Database:
"SELECT fileblob FROM elsa_files WHERE filename=?", (filename,), one=True
)[0]
# log.debug(blob)
tempdir = self.database.temp
tempdir = tempdir.replace("~", str(Path.home()))
tempdir_path = Path(tempdir)
if not os.path.exists(tempdir_path):
os.mkdir(tempdir_path)
tempdir = settings.database.temp.expanduser()
if not tempdir.exists():
tempdir.mkdir(parents=True, exist_ok=True)
file = tempfile.NamedTemporaryFile(
delete=False, dir=tempdir_path, mode="wb", suffix=f".{filetype}"
)

View File

@@ -9,10 +9,7 @@ def delete_temp_contents():
"""
delete_temp_contents deletes the contents of the temp directory.
"""
path = database.temp
path = path.replace("~", str(Path.home()))
path = Path(path)
path = path.resolve()
path = database.temp.expanduser()
for root, dirs, files in os.walk(path):
for file in files:
os.remove(os.path.join(root, file))

View File

@@ -1,11 +1,23 @@
from PyQt6.QtCore import QThread
from src.utils.documentation import run_mkdocs
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()
run_mkdocs()
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()

View File

@@ -1,146 +1,242 @@
import datetime
"""Semester helper class
A small utility around the *German* academic calendar that distinguishes
between *Wintersemester* (WiSe) and *Sommersemester* (SoSe).
Key points
----------
* A **`Semester`** is identified by a *term* ("SoSe" or "WiSe") and the last two
digits of the calendar year in which the term *starts*.
* Formatting **never** pads the year with a leading zero so ``6`` stays ``6``.
* ``offset(n)`` and the static ``generate_missing`` reliably walk the timeline
one semester at a time with correct year transitions:
SoSe 6 → **WiSe 6/7** → SoSe 7 → WiSe 7/8 → …
"""
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)
log.add("logs/application.log", rotation="1 MB", retention="10 days")
log.add(sys.stdout, level="INFO")
log.add(f"{LOG_DIR}/application.log", rotation="1 MB", retention="10 days")
@dataclass
# @dataclass
class Semester:
log.debug("Semester class loaded")
"""Represents a German university semester (WiSe or SoSe)."""
_year: int | None = str(datetime.datetime.now().year)[2:]
_semester: str | None = None
# ------------------------------------------------------------------
# Classlevel defaults will be *copied* to each instance and then
# potentially overwritten in ``__init__``.
# ------------------------------------------------------------------
_year: int | None = int(str(datetime.datetime.now().year)[2:]) # 24 → 24
_semester: str | None = None # "WiSe" or "SoSe" set later
_month: int | None = datetime.datetime.now().month
value: str = None
log.debug(
f"Initialized Semester class with values: month: {_month}, semester: {_semester}, year {_year}"
)
value: str | None = None # Humanreadable label, e.g. "WiSe 23/24"
def __post_init__(self):
if isinstance(self._year, str):
self._year = int(self._year)
# ------------------------------------------------------------------
# Construction helpers
# ------------------------------------------------------------------
def __init__(
self,
year: int | None = None,
semester: str | None = None,
month: int | None = None,
) -> None:
if year is not None:
self._year = int(year)
if semester is not None:
if semester not in ("WiSe", "SoSe"):
raise ValueError("semester must be 'WiSe' or 'SoSe'")
self._semester = semester
if month is not None:
self._month = month
self.__post_init__()
def __post_init__(self) -> None: # noqa: D401 keep original name
if self._year is None:
self._year = datetime.datetime.now().year[2:]
self._year = int(str(datetime.datetime.now().year)[2:])
if self._month is None:
self._month = datetime.datetime.now().month
if self._semester is None:
self.generateSemester()
self.computeValue()
self._generate_semester_from_month()
self._compute_value()
def __str__(self):
return self.value
# ------------------------------------------------------------------
# Dunder helpers
# ------------------------------------------------------------------
def __str__(self) -> str: # noqa: D401 keep original name
return self.value or "<invalid Semester>"
def generateSemester(self):
if self._month <= 3 or self._month > 9:
self._semester = "WiSe"
else:
self._semester = "SoSe"
def __repr__(self) -> str: # Helpful for debugging lists
return f"Semester({self._year!r}, {self._semester!r})"
@log.catch
def computeValue(self):
# year is only last two digits
# ------------------------------------------------------------------
# Internal helpers
# ------------------------------------------------------------------
def _generate_semester_from_month(self) -> None:
"""Infer *WiSe* / *SoSe* from the month attribute."""
self._semester = "WiSe" if (self._month <= 3 or self._month > 9) else "SoSe"
def _compute_value(self) -> None:
"""Humanreadable semester label e.g. ``WiSe 23/24`` or ``SoSe 24``."""
year = self._year
valueyear = str(year)
if self._semester == "WiSe":
if self._month < 4:
valueyear = str(year - 1) + "/" + str(year)
else:
valueyear = str(year) + "/" + str(year + 1)
self.value = f"{self._semester} {valueyear}"
next_year = (year + 1) % 100 # wrap 99 → 0
self.value = f"WiSe {year}/{next_year}"
else: # SoSe
self.value = f"SoSe {year}"
@log.catch
def offset(self, value: int) -> str:
"""Generate a new Semester object by offsetting the current semester by a given value
# ------------------------------------------------------------------
# Public API
# ------------------------------------------------------------------
def offset(self, value: int) -> "Semester":
"""Return a new :class:`Semester` *value* steps away.
Args:
value (int): The value by which the semester should be offset
The algorithm maps every semester to a monotonically increasing
*linear index* so that simple addition suffices:
Returns:
str: the new semester value
``index = year * 2 + (0 if SoSe else 1)``.
"""
assert isinstance(value, int), "Value must be an integer"
if not isinstance(value, int):
raise TypeError("value must be an int (number of semesters to jump)")
if value == 0:
return self
if value > 0:
if value % 2 == 0:
return Semester(
self._year - value // 2, self._semester - value // 2 + 1
)
else:
semester = self._semester
semester = "SoSe" if semester == "WiSe" else "WiSe"
return Semester(self._year + value // 2, semester)
else:
if value % 2 == 0:
return Semester(self.year + value // 2, self._semester)
else:
semester = self._semester
semester = "SoSe" if semester == "WiSe" else "WiSe"
return Semester(self._year + value // 2, semester)
return Semester(self._year, self._semester)
def isPastSemester(self, semester) -> bool:
"""Checks if the current Semester is a past Semester compared to the given Semester
current_idx = self._year * 2 + (0 if self._semester == "SoSe" else 1)
target_idx = current_idx + value
if target_idx < 0:
raise ValueError("offset would result in a negative year not supported")
Args:
semester (str): The semester to compare to
new_year, semester_bit = divmod(target_idx, 2)
new_semester = "SoSe" if semester_bit == 0 else "WiSe"
return Semester(new_year, new_semester)
Returns:
bool: True if the current semester is in the past, False otherwise
"""
if self.year < semester.year:
# ------------------------------------------------------------------
# Comparison helpers
# ------------------------------------------------------------------
def isPastSemester(self, other: "Semester") -> bool:
if self.year < other.year:
return True
if self.year == semester.year:
if self.semester == "WiSe" and semester.semester == "SoSe":
return True
if self.year == other.year:
return (
self.semester == "WiSe" and other.semester == "SoSe"
) # WiSe before next SoSe
return False
def isFutureSemester(self, semester: "Semester") -> bool:
"""Checks if the current Semester is a future Semester compared to the given Semester
Args:
semester (str): The semester to compare to
Returns:
bool: True if the current semester is in the future, False otherwise
"""
if self.year > semester.year:
def isFutureSemester(self, other: "Semester") -> bool:
if self.year > other.year:
return True
if self.year == semester.year:
if self.semester == "SoSe" and semester.semester == "WiSe":
return True
if self.year == other.year:
return (
self.semester == "SoSe" and other.semester == "WiSe"
) # SoSe after WiSe of same year
return False
def from_string(self, val: str):
if " " in val:
values = val.split(" ")
if len(values) != 2:
raise ValueError("Invalid semester format")
self._semester = values[0]
if len(values[1]) == 4:
self._year = int(values[1][2:])
# self._year = int(values[1])
self.computeValue()
return self
def isMatch(self, other: "Semester") -> bool:
return self.year == other.year and self.semester == other.semester
# ------------------------------------------------------------------
# Convenience properties
# ------------------------------------------------------------------
@property
def next(self):
def next(self) -> "Semester":
return self.offset(1)
@property
def previous(self):
def previous(self) -> "Semester":
return self.offset(-1)
@property
def year(self):
def year(self) -> int:
return self._year
@property
def semester(self):
def semester(self) -> str:
return self._semester
# ------------------------------------------------------------------
# Static helpers
# ------------------------------------------------------------------
@staticmethod
def generate_missing(start: "Semester", end: "Semester") -> list[str]:
"""Return all consecutive semesters from *start* to *end* (inclusive)."""
if not isinstance(start, Semester) or not isinstance(end, Semester):
raise TypeError("start and end must be Semester instances")
if start.isFutureSemester(end) and not start.isMatch(end):
raise ValueError("'start' must not be after 'end'")
chain: list[Semester] = [start.value]
current = start
while not current.isMatch(end):
current = current.next
chain.append(current.value)
if len(chain) > 1000: # sanity guard
raise RuntimeError("generate_missing exceeded sane iteration limit")
return chain
# ------------------------------------------------------------------
# Parsing helper
# ------------------------------------------------------------------
@classmethod
def from_string(cls, s: str) -> "Semester":
"""Parse a humanreadable semester label and return a :class:`Semester`.
Accepted formats (caseinsensitive)::
"SoSe <YY>" → SoSe of year YY
"WiSe <YY>/<YY+1>" → Winter term starting in YY
"WiSe <YY>" → Shorthand for the above (next year implied)
``YY`` may contain a leading zero ("06" → 6).
"""
if not isinstance(s, str):
raise TypeError("s must be a string")
pattern = r"\s*(WiSe|SoSe)\s+(\d{1,2})(?:\s*/\s*(\d{1,2}))?\s*"
m = re.fullmatch(pattern, s, flags=re.IGNORECASE)
if not m:
raise ValueError(
"invalid semester string format expected 'SoSe YY' or 'WiSe YY/YY' (spacing flexible)"
)
term_raw, y1_str, y2_str = m.groups()
term = term_raw.capitalize() # normalize case → "WiSe" or "SoSe"
year = int(y1_str.lstrip("0") or "0") # "06" → 6, "0" stays 0
if term == "SoSe":
if y2_str is not None:
raise ValueError(
"SoSe string should not contain '/' followed by a second year"
)
return cls(year, "SoSe")
# term == "WiSe"
if y2_str is not None:
next_year = int(y2_str.lstrip("0") or "0")
expected_next = (year + 1) % 100
if next_year != expected_next:
raise ValueError("WiSe second year must equal first year + 1 (mod 100)")
# Accept both explicit "WiSe 6/7" and shorthand "WiSe 6"
return cls(year, "WiSe")
# ------------------------- quick selftest -------------------------
if __name__ == "__main__":
# Chain generation demo ------------------------------------------------
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])
# Parsing demo ---------------------------------------------------------
for label in ["SoSe 6", "WiSe 6/7", "wise 23/24", "WiSe 9"]:
print("from_string:", label, "", Semester.from_string(label))

View File

@@ -1,19 +1,19 @@
from PyQt6.QtCore import QThread
from PyQt6.QtCore import pyqtSignal as Signal
from PySide6.QtCore import QThread
from PySide6.QtCore import 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)
log.add("logs/application.log", rotation="1 MB", retention="10 days")
log.add(sys.stdout, level="INFO")
log.add(f"{LOG_DIR}/application.log", rotation="1 MB", retention="10 days")
# logger.add(sys.stderr, format="{time} {level} {message}", level="INFO")
log.add(sys.stdout)
log.add(sys.stdout, level="INFO")
class BookGrabber(QThread):

View File

@@ -1,17 +1,17 @@
import time
# from icecream import ic
from PyQt6.QtCore import QThread
from PyQt6.QtCore import pyqtSignal as Signal
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
log = loguru.logger
log.remove()
log.add(sys.stdout)
log.add("logs/application.log", rotation="1 MB", retention="10 days")
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

View File

@@ -1,11 +1,11 @@
import time
# from icecream import ic
from PyQt6.QtCore import QThread
from PyQt6.QtCore import pyqtSignal as Signal
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
@@ -14,8 +14,8 @@ import sys
log = loguru.logger
log.remove()
log.add(sys.stdout)
log.add("logs/application.log", rotation="1 MB", retention="10 days")
log.add(sys.stdout, level="INFO")
log.add(f"{LOG_DIR}/application.log", rotation="1 MB", retention="10 days")

View File

@@ -1,27 +0,0 @@
from docx import Document
data = {}
wordDoc = Document("files/Semesterapparat - Anmeldung.docx")
paragraphs = wordDoc.tables
for table in paragraphs:
for column in table.columns:
cellcount = 0
for _cell in column.cells:
if cellcount < 12:
cellcount += 1
# print(f"cell:{cell.text}")
# # print(f'paragraphs[{i}]: {paragraphs[i]}')
# data[i] = paragraphs[i]
# for i in range(0, len(paragraphs)):
# for i in range(2, len(paragraphs)):
# data[i] = paragraphs[i]
# print(data)
# for table in wordDoc.tables:
# for row in table.rows:
# # print('---')
# for cell in row.cells:
# # print(f'cell:{cell.text}')

View File

@@ -1,10 +0,0 @@
import tabula
file = "files/Semesterapparat - Anmeldung.pdf"
def extract_book_data(file):
tabula.read_pdf(file, pages="all", encoding="utf-8", multiple_tables=True)
tabula.convert_into(file, file.replace(".pdf"), output_format="csv", pages="all")
with open("files/Semesterapparat - Anmeldung.csv", "r") as f:
f.read()

View File

@@ -1 +0,0 @@

53
src/logic/openai.py Normal file
View File

@@ -0,0 +1,53 @@
from openai import OpenAI
from src import settings
import json
def init_client():
"""Initialize the OpenAI client with the API key and model from settings."""
global client, model, api_key
if not settings.openAI.api_key:
raise ValueError("OpenAI API key is not set in the configuration.")
if not settings.openAI.model:
raise ValueError("OpenAI model is not set in the configuration.")
model = settings.openAI.model
api_key = settings.openAI.api_key
client = OpenAI(api_key=api_key)
return client
def run_shortener(title:str, length:int):
client = init_client()
response = client.responses.create(
model=model,
instructions="""you are a sentence shortener. The next message will contain the string to shorten and the length limit.
You need to shorten the string to be under the length limit, while keeping as much detail as possible. The result may NOT be longer than the length limit.
based on that, please reply only the shortened string. Give me 5 choices. if the length is too long, discard the string and try another one.Return the data as a python list containing the result as {"shortened_string": shortened_string, "length": lengthasInt}. Do not return the answer in a codeblock, use a pure string. Before answering, check the results and if ANY is longer than the needed_length, discard all and try again""",
input=f'{{"string":"{title}", "needed_length":{length}}}',
)
answers = response.output_text
return eval(answers) # type: ignore
#answers are strings in json format, so we need to convert them to a list of dicts
def name_tester(name: str):
client = init_client()
response = client.responses.create(
model = model,
instructions="""you are a name tester, You are given a name and will have to split the name into first name, last name, and if present the title. Return the name in a json format with the keys "title", "first_name", "last_name". If no title is present, set title to none. Do NOt return the answer in a codeblock, use a pure json string. Assume the names are in the usual german naming scheme""",
input = f'{{"name":"{name}"}}'
)
answers = response.output_text
return json.loads(answers)
def semester_converter(semester:str):
client = init_client()
response = client.responses.create(
model = model,
instructions="""you are a semester converter. You will be given a string. Convert this into a string like this: SoSe YY or WiSe YY/YY+1. Do not return the answer in a codeblock, use a pure string.""",
input = semester
)
answers = response.output_text
return answers

View File

@@ -1,194 +0,0 @@
import os
# from icecream import ic
from omegaconf import OmegaConf
from PyQt6 import QtWidgets
from PyQt6.QtCore import QThread
from PyQt6.QtCore import pyqtSignal as Signal
from src.backend.database import Database
from src.logic.log import MyLogger
from src.logic.webrequest import BibTextTransformer, WebRequest
# from src.transformers import RDS_AVAIL_DATA
from src.ui.dialogs.Ui_mail_preview import Ui_eMailPreview
config = OmegaConf.load("config.yaml")
class BackgroundChecker(QThread):
"""Check all apparats for available Books"""
pass
class MockAvailCheck:
def __init__(
self, links: list = None, appnumber: int = None, parent=None, books=list[dict]
):
if links is None:
links = []
super().__init__(parent)
self.logger = MyLogger("MockAvailChecker")
self.logger.log_info("Starting worker thread")
self.logger.log_info(
"Checking availability for "
+ str(links)
+ " with appnumber "
+ str(appnumber)
+ "..."
)
self.links = links
self.appnumber = appnumber
self.books = books
def run(self):
self.db = Database()
state = 0
count = 0
result = []
for link in self.links:
self.logger.log_info("Processing entry: " + str(link))
data = WebRequest().get_ppn(link).get_data()
transformer = BibTextTransformer("RDS")
rds = transformer.get_data(data).return_data("rds_availability")
for item in rds.items:
sign = item.superlocation
loc = item.location
# ic(item.location, item.superlocation)
if self.appnumber in sign or self.appnumber in loc:
state = 1
book_id = None
for book in self.books:
if book["bookdata"].signature == link:
book_id = book["id"]
break
self.logger.log_info(f"State of {link}: " + str(state))
print(
"lock acquired, updating availability of "
+ str(book_id)
+ " to "
+ str(state)
)
result.append((item.callnumber, state))
count += 1
return result
self.logger.log_info("Worker thread finished")
# teminate thread
class Mailer(Ui_eMailPreview):
updateSignal = Signal(int)
def __init__(self, data=None, parent=None):
super(QThread).__init__()
super(Ui_eMailPreview).__init__()
self.logger = MyLogger("Mailer")
self.data = data
self.appid = data["app_id"]
self.appname = data["app_name"]
self.subject = data["app_subject"]
self.profname = data["prof_name"]
self.mail_data = ""
self.prof_mail = data["prof_mail"]
self.dialog = QtWidgets.QDialog()
self.comboBox.currentIndexChanged.connect(self.set_mail)
self.prof_name.setText(self.prof_name)
self.mail_name.setText(self.prof_mail)
self.load_mail_templates()
self.gender_female.clicked.connect(self.set_mail)
self.gender_male.clicked.connect(self.set_mail)
self.gender_non.clicked.connect(self.set_mail)
self.buttonBox.accepted.connect(self.createAndSendMail)
def load_mail_templates(self):
# print("loading mail templates")
mail_templates = os.listdir("mail_vorlagen")
for template in mail_templates:
self.comboBox.addItem(template)
def get_greeting(self):
if self.gender_male.isChecked():
return "Sehr geehrter Herr"
elif self.gender_female.isChecked():
return "Sehr geehrte Frau"
elif self.gender_non.isChecked():
return "Guten Tag"
def set_mail(self):
email_template = self.comboBox.currentText()
if email_template == "":
return
with open(f"mail_vorlagen/{email_template}", "r", encoding="utf-8") as f:
mail_template = f.read()
email_header = email_template.split(".eml")[0]
if "{AppNr}" in email_template:
email_header = email_template.split(".eml")[0]
email_header = email_header.format(AppNr=self.appid, AppName=self.appname)
self.mail_header.setText(email_header)
self.mail_data = mail_template.split("<html>")[0]
mail_html = mail_template.split("<html>")[1]
mail_html = "<html>" + mail_html
Appname = self.appname
mail_html = mail_html.format(
Profname=self.prof_name.text().split(" ")[-1],
Appname=Appname,
AppNr=self.appid,
AppSubject=self.subject,
greeting=self.get_greeting(),
)
self.mail_body.setHtml(mail_html)
def createAndSendMail(self):
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
smtp_server = config["mail"]["smtp_server"]
port: int = config["mail"]["port"]
sender_email = config["mail"]["sender"]
password = config["mail"]["password"]
message = MIMEMultipart()
message["From"] = sender_email
message["To"] = self.prof_mail
message["Subject"] = self.mail_header.text()
mail_body = self.mail_body.toHtml()
message.attach(MIMEText(mail_body, "html"))
mail = message.as_string()
server = smtplib.SMTP_SSL(smtp_server, port)
# server.starttls()
# server.auth(mechanism="PLAIN")
if config["mail"]["use_user_name"] == 1:
# print(config["mail"]["user_name"])
server.login(config["mail"]["user_name"], password)
else:
server.login(sender_email, password)
server.sendmail(sender_email, self.prof_mail, mail)
# print("Mail sent")
# end active process
server.quit()
class MailThread(QThread):
updateSignal = Signal(int)
def __init__(self, data=None, parent=None):
super(QThread).__init__()
super(MailThread).__init__()
self.logger = MyLogger("MailThread")
self.data = data
def show_ui(self):
self.mailer = Mailer()
self.mailer.__init__()
self.mailer.dialog.exec_()
self.mailer.dialog.show()
def run(self):
self.show_ui()
self.updateSignal.emit(1)

View File

@@ -1,266 +0,0 @@
import sqlite3
import time
from PyQt6.QtCore import QThread, pyqtSignal
from src.backend.database import Database
from src.logic.log import MyLogger
from src.logic.webrequest import BibTextTransformer, WebRequest
# from icecream import ic
class BookGrabber(QThread):
updateSignal = pyqtSignal(int, int)
def __init__(self, filename):
super(BookGrabber, self).__init__(parent=None)
self.is_Running = True
self.logger = MyLogger("Worker")
self.logger.log_info("Starting worker thread")
self.data, self.app_id, self.prof_id, self.mode = self.readFile(filename)
self.book_id = None
time.sleep(2)
def readFile(self, filename):
with open(filename, "r") as file:
data = file.readlines()
app_id = data[0].strip()
prof_id = data[1].strip()
mode = data[2].strip()
data = data[3:]
return data, app_id, prof_id, mode
# def resetValues(self):
# self.app_id = None
# self.prof_id = None
# self.mode = None
# self.data = None
# self.book_id = None
def run(self):
while self.is_Running:
self.db = Database()
item = 0
iterdata = self.data
print(iterdata)
for entry in iterdata:
signature = str(entry)
self.logger.log_info("Processing entry: " + signature)
webdata = WebRequest().get_ppn(entry).get_data()
if webdata == "error":
continue
bd = BibTextTransformer(self.mode).get_data(webdata).return_data()
transformer = BibTextTransformer("RDS")
rds = transformer.get_data(webdata).return_data("rds_availability")
bd.signature = entry
# confirm lock is acquired
print("lock acquired, adding book to database")
self.db.addBookToDatabase(bd, self.app_id, self.prof_id)
# get latest book id
self.book_id = self.db.getLastBookId()
self.logger.log_info("Added book to database")
state = 0
print(len(rds.items))
for rds_item in rds.items:
sign = rds_item.superlocation
loc = rds_item.location
# ic(sign, loc)
# ic(rds_item)
if self.app_id in sign or self.app_id in loc:
state = 1
break
# for book in self.books:
# if book["bookdata"].signature == entry:
# book_id = book["id"]
# break
self.logger.log_info(f"State of {signature}: {state}")
print(
"updating availability of "
+ str(self.book_id)
+ " to "
+ str(state)
)
try:
self.db.setAvailability(self.book_id, state)
except sqlite3.OperationalError as e:
self.logger.log_error(f"Failed to update availability: {e}")
# time.sleep(5)
item += 1
self.updateSignal.emit(item, len(self.data))
self.logger.log_info("Worker thread finished")
self.stop()
if not self.is_Running:
break
def stop(self):
self.is_Running = False
class AvailChecker(QThread):
updateSignal = pyqtSignal(str, int)
updateProgress = pyqtSignal(int, int)
def __init__(
self, links: list = None, appnumber: int = None, parent=None, books=list[dict]
):
if links is None:
links = []
super().__init__(parent)
self.logger = MyLogger("AvailChecker")
self.logger.log_info("Starting worker thread")
self.logger.log_info(
"Checking availability for "
+ str(links)
+ " with appnumber "
+ str(appnumber)
+ "..."
)
self.links = links
self.appnumber = appnumber
self.books = books
self.logger.log_info(
f"Started worker with appnumber: {self.appnumber} and links: {self.links} and {len(self.books)} books..."
)
time.sleep(2)
def run(self):
self.db = Database()
state = 0
count = 0
for link in self.links:
self.logger.log_info("Processing entry: " + str(link))
data = WebRequest().get_ppn(link).get_data()
transformer = BibTextTransformer("RDS")
rds = transformer.get_data(data).return_data("rds_availability")
book_id = None
for item in rds.items:
sign = item.superlocation
loc = item.location
# print(item.location)
if self.appnumber in sign or self.appnumber in loc:
state = 1
break
for book in self.books:
if book["bookdata"].signature == link:
book_id = book["id"]
break
self.logger.log_info(f"State of {link}: " + str(state))
print("Updating availability of " + str(book_id) + " to " + str(state))
self.db.setAvailability(book_id, state)
count += 1
self.updateProgress.emit(count, len(self.links))
self.updateSignal.emit(item.callnumber, state)
self.logger.log_info("Worker thread finished")
# teminate thread
self.quit()
class AutoAdder(QThread):
updateSignal = pyqtSignal(int)
setTextSignal = pyqtSignal(int)
progress = pyqtSignal(int)
def __init__(self, data=None, app_id=None, prof_id=None, parent=None):
super().__init__(parent)
self.logger = MyLogger("AutoAdder")
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
self.logger.log_info("Starting worker thread")
item = 0
for entry in self.data:
try:
# webdata = WebRequest().get_ppn(entry).get_data()
# bd = BibTextTransformer("ARRAY").get_data(webdata).return_data()
# bd.signature = entry
self.updateSignal.emit(item)
self.setTextSignal.emit(entry)
# qsleep
item += 1
self.progress.emit(item)
print(item, len(self.data))
time.sleep(1)
except Exception as e:
print(e)
self.logger.log_exception(
f"The query failed with message {e} for signature {entry}"
)
continue
if item == len(self.data):
self.logger.log_info("Worker thread finished")
# teminate thread
self.finished.emit()
class MockAvailCheck:
def __init__(
self, links: list = None, appnumber: int = None, parent=None, books=list[dict]
):
if links is None:
links = []
super().__init__(parent)
self.logger = MyLogger("MockAvailChecker")
self.logger.log_info("Starting worker thread")
self.logger.log_info(
"Checking availability for "
+ str(links)
+ " with appnumber "
+ str(appnumber)
+ "..."
)
self.links = links
self.appnumber = appnumber
self.books = books
def run(self):
self.db = Database()
state = 0
count = 0
result = []
for link in self.links:
self.logger.log_info("Processing entry: " + str(link))
data = WebRequest().get_ppn(link).get_data()
transformer = BibTextTransformer("RDS")
rds = transformer.get_data(data).return_data("rds_availability")
for item in rds.items:
sign = item.superlocation
loc = item.location
# ic(item.location, item.superlocation)
if self.appnumber in sign or self.appnumber in loc:
state = 1
book_id = None
for book in self.books:
if book["bookdata"].signature == link:
book_id = book["id"]
break
self.logger.log_info(f"State of {link}: " + str(state))
print(
"lock acquired, updating availability of "
+ str(book_id)
+ " to "
+ str(state)
)
result.append((item.callnumber, state))
count += 1
return result
self.logger.log_info("Worker thread finished")
# teminate thread

View File

@@ -11,10 +11,11 @@ from src.transformers import ARRAYData, BibTeXData, COinSData, RDSData, RISData
from src.transformers.transformers import RDS_AVAIL_DATA, RDS_GENERIC_DATA
import loguru
import sys
from src import LOG_DIR
log = loguru.logger
log.remove()
log.add(sys.stdout)
log.add("logs/application.log", rotation="1 MB", retention="10 days")
log.add(sys.stdout, level="INFO")
log.add(f"{LOG_DIR}/application.log", rotation="1 MB", retention="10 days")
# logger.add(sys.stderr, format="{time} {level} {message}", level="INFO")

View File

@@ -1,16 +1,21 @@
import pandas as pd
from docx import Document
import sys
import zipfile
from dataclasses import dataclass
from src.backend import Semester
from typing import Union, Any
from typing import Any, Union
import loguru
import sys
import pandas as pd
from bs4 import BeautifulSoup
from docx import Document
from src import LOG_DIR
from src.backend import Semester
from src.logic.openai import name_tester, run_shortener, semester_converter
log = loguru.logger
log.remove()
log.add(sys.stdout)
log.add("logs/application.log", rotation="1 MB", retention="10 days")
log.add(sys.stdout, level="INFO")
log.add(f"{LOG_DIR}/application.log", rotation="1 MB", retention="10 days")
@@ -75,12 +80,37 @@ class SemapDocument:
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:
log.warning("Title is too long")
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 ", Dauer" in self.semester:
@@ -88,8 +118,8 @@ class SemapDocument:
self.eternal = True
self.semester = Semester().from_string(self.semester)
else:
logger.warning("Semester {} is not valid", self.semester)
self.semester = None
log.warning("Semester {} is not valid", self.semester)
self.semester = Semester().from_string(semester_converter(self.semester))
@property
def signatures(self) -> list[str]:
@@ -105,11 +135,14 @@ def word_docx_to_csv(path: str) -> list[pd.DataFrame]:
for table in tables:
data = []
for row in table.rows:
row_data = []
row_data: list[Any] = []
for cell in row.cells:
text = cell.text
text = text.replace("\n", "")
row_data.append(text)
if text == "Ihr Fach:":
row_data.append(get_fach(path))
data.append(row_data)
df = pd.DataFrame(data)
df.columns = df.iloc[0]
@@ -117,11 +150,27 @@ def word_docx_to_csv(path: str) -> list[pd.DataFrame]:
m_data.append(df)
# for df[0, 1]: merge i and i+1 as key, value
return m_data
def get_fach(path: str) -> str:
document = zipfile.ZipFile(path)
xml_data = document.read("word/document.xml")
document.close()
soup = BeautifulSoup(xml_data, "xml")
# text we need is in <w:p w14:paraId="12456A32" ... > -> w:r -> w:t
paragraphs = soup.find_all("w:p")
names = []
for para in paragraphs:
para_id = para.get("w14:paraId")
if para_id == "12456A32":
# get the data in the w:t
for run in para.find_all("w:r"):
data = run.find("w:t")
return data.contents[0]
def makeDict():
return {
"work_author": None,
@@ -222,10 +271,10 @@ def word_to_semap(word_path: str) -> SemapDocument:
df = word_docx_to_csv(word_path)
apparatdata = df[0]
apparatdata = apparatdata.to_dict()
keys = list(apparatdata.keys())
print(apparatdata, keys)
appdata = {keys[i]: keys[i + 1] for i in range(0, len(keys), 2)}
appdata = {keys[i]: keys[i + 1] for i in range(0, len(keys) - 1, 2)}
semap.phoneNumber = appdata["Telefon:"]
semap.subject = appdata["Ihr Fach:"]
semap.mail = appdata["Mailadresse:"]
@@ -238,6 +287,8 @@ def word_to_semap(word_path: str) -> SemapDocument:
semap.title = appdata["Veranstaltung:"]
semap.semester = appdata["Semester:"]
semap.renameSemester
semap.nameSetter
books = df[2]
booklist = []
for i in range(len(books)):
@@ -254,7 +305,6 @@ def word_to_semap(word_path: str) -> SemapDocument:
booklist.append(book)
log.info("Found {} books", len(booklist))
semap.books = booklist
return semap

View File

@@ -160,6 +160,8 @@ class ZoteroController:
zoterocfg = settings.zotero
def __init__(self):
if self.zoterocfg.library_id is None:
return
self.zot = zotero.Zotero(
self.zoterocfg.library_id,
self.zoterocfg.library_type,

View File

@@ -6,15 +6,15 @@ from dataclasses import dataclass
from dataclasses import field as dataclass_field
from typing import Any, List
from src import LOG_DIR
from src.logic.dataclass import BookData
import loguru
import sys
log = loguru.logger
log.remove()
log.add(sys.stdout)
log.add("logs/application.log", rotation="1 MB", retention="10 days")
log.add(sys.stdout, level="INFO")
log.add(f"{LOG_DIR}/application.log", rotation="1 MB", retention="10 days")
###Pydatnic models

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\Semesterapparate\ui\dialogs\edit_bookdata.ui'
#
# Created by: PyQt6 UI code generator 6.3.1
# Created by: PySide6 UI code generator 6.3.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
from src.logic.dataclass import BookData

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\Semesterapparate\ui\dialogs\fileparser.ui'
#
# Created by: PyQt6 UI code generator 6.3.1
# Created by: PySide6 UI code generator 6.3.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
from src.logic.webrequest import BibTextTransformer, WebRequest
@@ -61,7 +61,7 @@ class Ui_Dialog(object):
self.listWidget.setObjectName("listWidget")
self.signatures = []
self.returned = []
# self.data_gathering_complete = QtCore.pyqtSignal()
# self.data_gathering_complete = QtCore.Signal()
self.retranslateUi(Dialog)
QtCore.QMetaObject.connectSlotsByName(Dialog)

View File

@@ -1,6 +1,6 @@
# Form implementation generated from reading ui file '/home/alexander/GitHub/Semesterapparate/ui/dialogs/login.ui'
#
# Created by: PyQt6 UI code generator 6.5.3
# Created by: PySide6 UI code generator 6.5.3
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
@@ -8,7 +8,7 @@
import hashlib
from PyQt6 import QtCore, QtWidgets
from PySide6 import QtCore, QtWidgets
from src.backend.database import Database
from src.backend.admin_console import AdminCommands

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\mail_preview.ui'
#
# Created by: PyQt6 UI code generator 6.3.1
# Created by: PySide6 UI code generator 6.3.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
import subprocess
import tempfile
import os

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\Semesterapparate\ui\dialogs\medianadder.ui'
#
# Created by: PyQt6 UI code generator 6.3.1
# Created by: PySide6 UI code generator 6.3.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\Semesterapparate\ui\dialogs\new_subject.ui'
#
# Created by: PyQt6 UI code generator 6.3.1
# Created by: PySide6 UI code generator 6.3.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt6 import QtCore, QtWidgets
from PySide6 import QtCore, QtWidgets
class Ui_Dialog(object):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\Semesterapparate\ui\dialogs\parsed_titles.ui'
#
# Created by: PyQt6 UI code generator 6.3.1
# Created by: PySide6 UI code generator 6.3.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
from src.logic.log import MyLogger
from src.logic.threads import AutoAdder

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\reminder.ui'
#
# Created by: PyQt6 UI code generator 6.3.1
# Created by: PySide6 UI code generator 6.3.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):

View File

@@ -1,13 +1,13 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\Semesterapparate\ui\dialogs\settings.ui'
#
# Created by: PyQt6 UI code generator 6.3.1
# Created by: PySide6 UI code generator 6.3.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from omegaconf import OmegaConf
from PyQt6 import QtCore, QtWidgets
from PySide6 import QtCore, QtWidgets
config = OmegaConf.load("config.yaml")

View File

@@ -1,6 +1,6 @@
from .dialog_sources.Ui_about import Ui_about
from PyQt6 import QtWidgets
from PyQt6.QtCore import PYQT_VERSION_STR
from PySide6 import QtWidgets
import PySide6
from src import Icon, __version__, __author__
@@ -20,7 +20,7 @@ class About(QtWidgets.QDialog, Ui_about):
data = {
"Version": __version__,
"Author": __author__,
"PyQt6 Version": PYQT_VERSION_STR,
"PySide6 Version": PySide6.__version__,
"License": "MIT License",
"Icons": """Google Material Design Icons (https://fonts.google.com/icons)
StableDiffusion (logo)

View File

@@ -1,4 +1,4 @@
from PyQt6 import QtWidgets
from PySide6 import QtWidgets
from .dialog_sources.Ui_apparat_extend import Ui_Dialog
from src import Icon

View File

@@ -1,4 +1,4 @@
from PyQt6 import QtWidgets
from PySide6 import QtWidgets
from src.logic.dataclass import BookData

View File

@@ -1,5 +1,5 @@
from .dialog_sources.Ui_confirm_extend import Ui_extend_confirm
from PyQt6 import QtWidgets
from PySide6 import QtWidgets
class ConfirmExtend(QtWidgets.QDialog, Ui_extend_confirm):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\confirm_extend.ui'
#
# Created by: PyQt6 UI code generator 6.8.0
# 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.
from PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_extend_confirm(object):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\about.ui'
#
# Created by: PyQt6 UI code generator 6.6.1
# 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 PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_about(object):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\app_status.ui'
#
# Created by: PyQt6 UI code generator 6.6.1
# 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 PyQt6 import QtCore
from PySide6 import QtCore
class Ui_Form(object):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\apparat_extend.ui'
#
# Created by: PyQt6 UI code generator 6.7.1
# 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 PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\confirm_extend.ui'
#
# Created by: PyQt6 UI code generator 6.7.1
# 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 PyQt6 import QtCore, QtWidgets
from PySide6 import QtCore, QtWidgets
class Ui_extend_confirm(object):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\edit_bookdata.ui'
#
# Created by: PyQt6 UI code generator 6.6.1
# 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 PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\elsa_add_table_entry.ui'
#
# Created by: PyQt6 UI code generator 6.7.1
# 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 PyQt6 import QtCore, QtWidgets
from PySide6 import QtCore, QtWidgets
class Ui_Dialog(object):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\elsa_generate_citation.ui'
#
# Created by: PyQt6 UI code generator 6.6.1
# 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 PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\elsa_generator_confirm.ui'
#
# Created by: PyQt6 UI code generator 6.6.1
# 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 PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\login.ui'
#
# Created by: PyQt6 UI code generator 6.6.1
# 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 PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\mail_preview.ui'
#
# Created by: PyQt6 UI code generator 6.7.1
# 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 PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_eMailPreview(object):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\medianadder.ui'
#
# Created by: PyQt6 UI code generator 6.7.1
# 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 PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\newMailTemplateDesigner.ui'
#
# Created by: PyQt6 UI code generator 6.6.1
# 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 PyQt6 import QtCore, QtWidgets
from PySide6 import QtCore, QtWidgets
class Ui_Dialog(object):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\parsed_titles.ui'
#
# Created by: PyQt6 UI code generator 6.6.1
# 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 PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_Form(object):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\reminder.ui'
#
# Created by: PyQt6 UI code generator 6.6.1
# 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 PyQt6 import QtCore, QtWidgets
from PySide6 import QtCore, QtWidgets
class Ui_Erinnerung(object):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\settings.ui'
#
# Created by: PyQt6 UI code generator 6.7.1
# 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 PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\about.ui'
#
# Created by: PyQt6 UI code generator 6.8.0
# 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.
from PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_about(object):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\app_status.ui'
#
# Created by: PyQt6 UI code generator 6.8.0
# 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.
from PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_Form(object):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\apparat_extend.ui'
#
# Created by: PyQt6 UI code generator 6.8.0
# 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.
from PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\confirm_extend.ui'
#
# Created by: PyQt6 UI code generator 6.8.0
# 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.
from PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_extend_confirm(object):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\documentprint.ui'
#
# Created by: PyQt6 UI code generator 6.9.0
# Created by: PySide6 UI code generator 6.9.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.
from PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\edit_bookdata.ui'
#
# Created by: PyQt6 UI code generator 6.8.0
# 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.
from PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\elsa_add_table_entry.ui'
#
# Created by: PyQt6 UI code generator 6.8.0
# 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.
from PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\elsa_generate_citation.ui'
#
# Created by: PyQt6 UI code generator 6.8.0
# 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.
from PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\elsa_generator_confirm.ui'
#
# Created by: PyQt6 UI code generator 6.8.0
# 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.
from PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\login.ui'
#
# Created by: PyQt6 UI code generator 6.8.0
# 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.
from PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\mail_preview.ui'
#
# Created by: PyQt6 UI code generator 6.8.0
# 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.
from PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_eMailPreview(object):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\medianadder.ui'
#
# Created by: PyQt6 UI code generator 6.8.0
# 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.
from PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\newMailTemplateDesigner.ui'
#
# Created by: PyQt6 UI code generator 6.8.0
# 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.
from PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\parsed_titles.ui'
#
# Created by: PyQt6 UI code generator 6.8.0
# 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.
from PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_Form(object):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\reminder.ui'
#
# Created by: PyQt6 UI code generator 6.8.0
# 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.
from PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_Erinnerung(object):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\settings.ui'
#
# Created by: PyQt6 UI code generator 6.9.0
# Created by: PySide6 UI code generator 6.9.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.
from PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):

View File

@@ -1,5 +1,5 @@
from .dialog_sources.documentprint_ui import Ui_Dialog
from PyQt6 import QtWidgets, QtCore
from PySide6 import QtWidgets, QtCore
from src import Icon
from src.utils.richtext import SemapSchilder, SemesterDocument

View File

@@ -1,7 +1,7 @@
from .dialog_sources.Ui_elsa_add_table_entry import Ui_Dialog
from src.logic.webrequest import WebRequest, BibTextTransformer
from src import Icon
from PyQt6 import QtWidgets
from PySide6 import QtWidgets
from src.transformers.transformers import DictToTable
from src.logic.zotero import ZoteroController

View File

@@ -1,5 +1,5 @@
from .dialog_sources.Ui_elsa_generate_citation import Ui_Dialog
from PyQt6 import QtWidgets
from PySide6 import QtWidgets
class ElsaCitation(QtWidgets.QDialog, Ui_Dialog):

View File

@@ -1,5 +1,5 @@
from .dialog_sources.Ui_elsa_generator_confirm import Ui_Dialog
from PyQt6 import QtWidgets
from PySide6 import QtWidgets
class ElsaGenConfirm(QtWidgets.QDialog, Ui_Dialog):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'ui/dialogs/extend_apparat.ui'
#
# Created by: PyQt6 UI code generator 6.3.1
# Created by: PySide6 UI code generator 6.3.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_Frame(object):

View File

@@ -1,4 +1,4 @@
from PyQt6 import QtWidgets
from PySide6 import QtWidgets
from src.logic.webrequest import BibTextTransformer, WebRequest

View File

@@ -1,20 +1,18 @@
import hashlib
import sys
from PyQt6 import QtCore, QtWidgets
import loguru
from PySide6 import QtCore, QtWidgets
from src.backend.admin_console import AdminCommands
from src import LOG_DIR, Icon
from src.backend.database import Database
from .dialog_sources.login_ui import Ui_Dialog
import sys
import loguru
from src import Icon
log = loguru.logger
log.remove()
log.add(sys.stdout)
log.add("logs/application.log", rotation="1 MB", retention="10 days")
log.add(sys.stdout, level="INFO")
log.add(f"{LOG_DIR}/application.log", rotation="1 MB", retention="10 days")
class LoginDialog(Ui_Dialog):
@@ -52,6 +50,7 @@ class LoginDialog(Ui_Dialog):
self.lineEdit_2.setEchoMode(QtWidgets.QLineEdit.EchoMode.Password)
self.lineEdit_2.setClearButtonEnabled(True)
self.lineEdit_2.setObjectName("lineEdit_2")
log.info("Calling database")
self.db = Database()
self.retranslateUi(Dialog)
@@ -76,6 +75,8 @@ class LoginDialog(Ui_Dialog):
hashed_password = hashlib.sha256(password.encode()).hexdigest()
if len(self.db.getUsers()) == 0:
from src.backend.admin_console import AdminCommands
AdminCommands().create_admin()
self.lresult = 1 # Indicate successful login
self.lusername = username
@@ -89,14 +90,14 @@ class LoginDialog(Ui_Dialog):
else:
# Credentials are invalid, display a warning
if username == "" or password == "":
logger.warning("Invalid username or password. Login failed.")
log.warning("Invalid username or password. Login failed.")
warning_dialog = QtWidgets.QMessageBox()
warning_dialog.setIcon(QtWidgets.QMessageBox.Icon.Warning)
warning_dialog.setText("Please enter a username and password.")
warning_dialog.setWindowTitle("Login Failed")
warning_dialog.exec()
else:
logger.warning("Invalid username or password. Login failed.")
log.warning("Invalid username or password. Login failed.")
warning_dialog = QtWidgets.QMessageBox()
warning_dialog.setIcon(QtWidgets.QMessageBox.Icon.Warning)
warning_dialog.setText(

View File

@@ -1,7 +1,7 @@
import os
import sys
from PyQt6 import QtWidgets
from PySide6 import QtWidgets
from src import Icon, settings as config
@@ -10,11 +10,11 @@ from .dialog_sources.Ui_mail_preview import Ui_eMailPreview as MailPreviewDialog
from .mailTemplate import MailTemplateDialog
import loguru
import sys
from src import LOG_DIR
log = loguru.logger
log.remove()
log.add(sys.stdout)
log.add("logs/application.log", rotation="1 MB", retention="10 days")
log.add(sys.stdout, level="INFO")
log.add(f"{LOG_DIR}/application.log", rotation="1 MB", retention="10 days")

View File

@@ -1,6 +1,6 @@
import os
from PyQt6 import QtGui, QtWidgets, QtCore
from PySide6 import QtGui, QtWidgets, QtCore
from src import Icon
@@ -22,7 +22,7 @@ logger.add(sys.stdout)
class MailTemplateDialog(QtWidgets.QDialog, NewMailTemplateDesignerDialog):
updateSignal = QtCore.pyqtSignal()
updateSignal = QtCore.Signal()
def __init__(self, parent=None) -> None:
super().__init__(parent)

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'ui\dialogs\mail_preview.ui'
#
# Created by: PyQt6 UI code generator 6.3.1
# Created by: PySide6 UI code generator 6.3.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt6 import QtCore, QtWidgets
from PySide6 import QtCore, QtWidgets
class Ui_Dialog(object):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\mail_preview.ui'
#
# Created by: PyQt6 UI code generator 6.8.0
# 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.
from PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_eMailPreview(object):

View File

@@ -1,4 +1,4 @@
from PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
from .dialog_sources.medianadder_ui import Ui_Dialog
from src import Icon

View File

@@ -1,4 +1,4 @@
from PyQt6 import QtWidgets
from PySide6 import QtWidgets
from src.backend import AutoAdder
@@ -6,11 +6,11 @@ from src.backend 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)
log.add("logs/application.log", rotation="1 MB", retention="10 days")
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):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\parsed_titles.ui'
#
# Created by: PyQt6 UI code generator 6.9.0
# Created by: PySide6 UI code generator 6.9.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.
from PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_Form(object):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'ui\dialogs\confirm_extend.ui'
#
# Created by: PyQt6 UI code generator 6.3.1
# Created by: PySide6 UI code generator 6.3.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt6 import QtWidgets
from PySide6 import QtWidgets
from src import Icon

View File

@@ -1,4 +1,4 @@
from PyQt6 import QtWidgets
from PySide6 import QtWidgets
from .dialog_sources.reminder_ui import Ui_Erinnerung as Ui_Dialog
from src import Icon

View File

@@ -1,14 +1,15 @@
from PyQt6 import QtCore, QtGui, QtWidgets
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
log = loguru.logger
log.remove()
log.add(sys.stdout)
log.add("logs/application.log", rotation="1 MB", retention="10 days")
log.add(sys.stdout, level="INFO")
log.add(f"{LOG_DIR}/application.log", rotation="1 MB", retention="10 days")
@@ -80,8 +81,8 @@ class Settings(QtWidgets.QDialog, _settings):
def load_config(self):
self.db_name.setText(settings.database.name)
self.db_path.setText(settings.database.path)
self.save_path.setText(settings.database.temp)
self.db_path.setText(str(settings.database.path.expanduser()))
self.save_path.setText(str(settings.database.temp.expanduser()))
self.smtp_address.setText(settings.mail.smtp_server)
self.smtp_port.setText(str(settings.mail.port))
self.sender_email.setText(settings.mail.sender)

View File

@@ -1,4 +1,4 @@
from PyQt6 import QtWidgets
from PySide6 import QtWidgets
class ValidatorButton(QtWidgets.QToolButton):

View File

@@ -1923,8 +1923,8 @@ Einige Angaben müssen ggf angepasst werden</string>
<property name="title">
<string>Help</string>
</property>
<addaction name="actionDokumentation_lokal"/>
<addaction name="actionAbout"/>
<addaction name="actionDokumentation"/>
</widget>
<addaction name="menuDatei"/>
<addaction name="menuEinstellungen"/>
@@ -1956,17 +1956,6 @@ Einige Angaben müssen ggf angepasst werden</string>
<bool>true</bool>
</property>
</action>
<action name="actionDokumentation">
<property name="text">
<string>Dokumentation (online)</string>
</property>
<property name="shortcut">
<string>F1</string>
</property>
<property name="shortcutContext">
<enum>Qt::ApplicationShortcut</enum>
</property>
</action>
<action name="actionAbout">
<property name="text">
<string>About</string>
@@ -1975,9 +1964,9 @@ Einige Angaben müssen ggf angepasst werden</string>
<enum>QAction::AboutRole</enum>
</property>
</action>
<action name="actionDokumentation_lokal">
<action name="actionDokumentation">
<property name="text">
<string>Dokumentation (lokal)</string>
<string>Dokumentation</string>
</property>
<property name="shortcut">
<string>F1</string>
@@ -2009,8 +1998,6 @@ Einige Angaben müssen ggf angepasst werden</string>
<tabstop>automation_add_selected_books</tabstop>
<tabstop>saveandcreate</tabstop>
</tabstops>
<resources>
<include location="../../resources.qrc"/>
</resources>
<resources/>
<connections/>
</ui>

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\sounds\semesterapparat_ui.ui'
#
# Created by: PyQt6 UI code generator 6.9.0
# Created by: PySide6 UI code generator 6.9.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.
from PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):

View File

@@ -1,74 +1,71 @@
# encoding: utf-8
import atexit
import os
import time
import sys
import tempfile
import time
import webbrowser
from datetime import datetime
from pathlib import Path
from typing import Any, Union
import loguru
from natsort import natsorted
from PyQt6 import QtCore, QtGui, QtWidgets
from PyQt6.QtCore import QThread
from PyQt6.QtGui import QRegularExpressionValidator
from PySide6 import QtCore, QtGui, QtWidgets
from PySide6.QtCore import QThread, Qt
from PySide6.QtGui import QRegularExpressionValidator
from src import Icon
from src.backend import Database, BookGrabber, AvailChecker, DocumentationThread
from src.backend.semester import Semester
from src import LOG_DIR, Icon
from src.backend import AvailChecker, BookGrabber, Database, DocumentationThread
from src.backend.create_file import recreateFile
from src.backend.delete_temp_contents import delete_temp_contents as tempdelete
from src.ui import Ui_Semesterapparat
from src.backend.semester import Semester
from src.logic import (
APP_NRS,
# PROF_TITLES,
Apparat,
ApparatData,
BookData,
Prof,
SemapDocument,
csv_to_list,
word_to_semap,
SemapDocument,
Prof,
Apparat,
)
from src.ui import Ui_Semesterapparat
from src.ui.dialogs import (
popus_confirm,
MedienAdder,
About,
ApparatExtendDialog,
Mail_Dialog,
Settings,
BookDataUI,
DocumentPrintDialog,
LoginDialog,
Mail_Dialog,
MedienAdder,
ParsedTitles,
ReminderDialog,
DocumentPrintDialog,
Settings,
popus_confirm,
)
from src.ui.widgets import (
ElsaDialog,
MessageCalendar,
FilePicker,
CalendarEntry,
UserCreate,
SearchStatisticPage,
EditUser,
EditProf,
EditUser,
ElsaDialog,
FilePicker,
MessageCalendar,
SearchStatisticPage,
UserCreate,
)
from datetime import datetime
import loguru
log = loguru.logger
log.remove()
log.add(sys.stdout)
log.add("logs/application.log", rotation="1 MB", retention="10 days")
log.add(sys.stdout, level="INFO")
log.add(f"{LOG_DIR}/application.log", rotation="1 MB", retention="10 days")
log.add(
f"logs/{datetime.now().strftime('%Y-%m-%d')}.log",
f"{LOG_DIR}/{datetime.now().strftime('%Y-%m-%d')}.log",
rotation="1 day",
retention="1 month",
)
log.critical("UI started")
valid_input = (0, 0, 0, 0, 0, 0)
@@ -120,7 +117,7 @@ class Ui(Ui_Semesterapparat):
# Actions
self.actionEinstellungen.triggered.connect(self.open_settings) # type:ignore
Icon("settings", self.actionEinstellungen)
self.actionDokumentation_lokal.triggered.connect(self.open_documentation) # type:ignore
self.documentation_open = False
Icon("offAction", self.actionBeenden)
self.actionBeenden.triggered.connect(self.quit) # type:ignore
self.actionAbout.triggered.connect(self.open_about) # type:ignore
@@ -206,7 +203,7 @@ class Ui(Ui_Semesterapparat):
self.add_medium.setEnabled(False)
self.docu = DocumentationThread()
self.actionDokumentation_lokal.triggered.connect(self.open_documentation) # type:ignore
self.actionDokumentation.triggered.connect(self.open_documentation) # type:ignore
# get all current apparats and cache them in a list
self.apparats = self.get_apparats()
@@ -331,10 +328,16 @@ class Ui(Ui_Semesterapparat):
def open_documentation(self):
log.info("Opening Documentation")
if not self.docu.isRunning():
self.statusBar.showMessage("Dokumentation wird geöffnet", 5000)
if not self.documentation_open:
# write "opening documentation in 5s into status bar"
self.documentation_open = True
self.docu.start()
time.sleep(5)
time.sleep(5)
webbrowser.open("http://localhost:8000")
self.statusBar.showMessage("")
def update_calendar(self, data: list[dict[str, Any]]):
self.calendarWidget.setMessages([data])
@@ -556,6 +559,7 @@ class Ui(Ui_Semesterapparat):
else:
self.__setValidState(self.valid_check_semester, 0, self._mand, 5)
self.check_eternal_app.setEnabled(False)
return valid
def display_valid_semester(self):
print(f"""
@@ -563,6 +567,7 @@ class Ui(Ui_Semesterapparat):
Sommer: {self.sem_sommer.isChecked()}
Winter: {self.sem_winter.isChecked()}
Eternal: {self.check_eternal_app.isChecked()}
Valid: {self.validate_semester()}
""")
def change_state(self, index, state):
@@ -985,7 +990,7 @@ class Ui(Ui_Semesterapparat):
self.confirm_popup("Bitte erst ein document auswählen!", title="Fehler")
return
if not _selected_doc_location == "Database":
path = Path(_selected_doc_location + "/" + _selected_doc_name)
path = Path(_selected_doc_location)
# path: Path = path.resolve()
# path.
# path = path + "/" + _selected_doc_name
@@ -1119,12 +1124,53 @@ class Ui(Ui_Semesterapparat):
return
self.prof_mail.setText(data.mail)
self.prof_tel_nr.setText(str(data.phoneNumber))
self.app_name.setText(data.title)
self.app_fach.setCurrentText(data.subject)
self.prof_tel_nr.setText(str(data.phoneNumber).replace("-", ""))
if len(data.title_suggestions) > 0:
# create a dialog that has a dropdown with the suggestions, and oc and cancel button. on ok return the selected text and set it as title
dialog = QtWidgets.QDialog()
dialog.setWindowTitle("Titelvorschläge")
dialog.setModal(True)
layout = QtWidgets.QVBoxLayout()
label = QtWidgets.QLabel(
f"Bitte wählen Sie einen Titel aus:/nDer Titel darf max. {data.title_max_length} Zeichen lang sein."
)
layout.addWidget(label)
dropdown = QtWidgets.QComboBox()
titles = [f"{title} [{len(title)}]" for title in data.title_suggestions]
dropdown.addItems(titles)
layout.addWidget(dropdown)
button_box = QtWidgets.QDialogButtonBox()
button_box.setStandardButtons(
QtWidgets.QDialogButtonBox.StandardButton.Cancel
| QtWidgets.QDialogButtonBox.StandardButton.Ok
)
button_box.accepted.connect(dialog.accept)
button_box.rejected.connect(dialog.reject)
layout.addWidget(button_box)
dialog.setLayout(layout)
dialog.exec()
if dialog.result() == QtWidgets.QDialog.DialogCode.Accepted:
print("Selected title:", dropdown.currentText())
self.app_name.setText(dropdown.currentText().split(" [")[0].strip())
else:
self.app_name.setText("CHANGEME")
# self.app_name.setText(data.title)
subjects = self.db.getSubjects()
subjects = [subject[1] for subject in subjects]
self.app_fach.setCurrentText(data.subject if data.subject in subjects else "")
self.prof_title.setText(data.personTitle)
self.drpdwn_prof_name.setCurrentText(data.personName)
self.sem_year.setText("20" + data.semester.year)
self.sem_year.setText("20" + str(data.semester.year))
if data.semester.semester == "SoSe":
self.sem_sommer.setChecked(True)
self.sem_winter.setChecked(False)
else:
self.sem_winter.setChecked(True)
self.sem_sommer.setChecked(False)
if data.eternal:
self.check_eternal_app.setChecked(True)
self.validate_semester()
def btn_check_file_threaded(self):
for runner in self.bookGrabber:
@@ -1771,7 +1817,11 @@ def launch_gui():
# #log.debug("checking if database available")
log.info("Starting login dialog")
app = QtWidgets.QApplication(sys.argv)
app = QtWidgets.QApplication.instance()
if app is None:
app = QtWidgets.QApplication(sys.argv)
else:
log.info("Using existing QApplication instance")
login_dialog = QtWidgets.QDialog()
ui = LoginDialog()
ui.setupUi(login_dialog)
@@ -1787,8 +1837,14 @@ def launch_gui():
# #log.debug(aui.active_user)
MainWindow.show()
# atexit.register()
app.aboutToQuit.connect(
aui.validate_thread.quit
) # if that thread uses an event loop
app.aboutToQuit.connect(aui.docu.terminate) # our new slot
app.aboutToQuit.connect(aui.docu.wait)
atexit.register(tempdelete)
atexit.register(aui.validate_thread.quit)
# atexit.register(aui.validate_thread.quit)
# atexit.register(aui.docu.quit)
sys.exit(app.exec())
elif ui.lresult == 0:

View File

@@ -1,16 +1,19 @@
from PyQt6 import QtWidgets, QtCore
from PyQt6.QtCore import QDate
from PyQt6.QtGui import QColor, QPen
from src.backend import Database
import sys
from typing import Any
import darkdetect
import loguru
import sys
from PySide6 import QtCore, QtWidgets
from PySide6.QtCore import QDate
from PySide6.QtGui import QColor, QPen
from src import LOG_DIR
from src.backend import Database
log = loguru.logger
log.remove()
log.add(sys.stdout)
log.add("logs/application.log", rotation="1 MB", retention="10 days")
log.add(sys.stdout, level="INFO")
log.add(f"{LOG_DIR}/application.log", rotation="1 MB", retention="10 days")
color = "#ddfb00" if darkdetect.isDark() else "#2204ff"

View File

@@ -16,7 +16,7 @@ __all__ = [
from .collapse import StatusWidget
from .filepicker import FilePicker
from .graph import DataGraph
from .graph import DataQtGraph
from .calendar_entry import CalendarEntry
from .MessageCalendar import MessageCalendar
from .searchPage import SearchStatisticPage

Some files were not shown because too many files have changed in this diff Show More