dev #21

Merged
WorldTeacher merged 46 commits from dev into main 2025-11-24 12:59:41 +00:00
43 changed files with 3593 additions and 3886 deletions
Showing only changes of commit 0061708785 - Show all commits

View File

@@ -57,6 +57,3 @@ jobs:
body: ${{ env.RELEASE_NOTES }}
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}

View File

@@ -1 +1 @@
0.2.1
1.0.0

View File

@@ -0,0 +1,21 @@
Subject: Vorschläge für Neuauflagen - {Appname}
MIME-Version: 1.0
Content-Type: text/html; charset="UTF-8"
Content-Transfer-Encoding: 8bit
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
</head>
<body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;">
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">{greeting}</p>
<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>
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">für Ihren Semesterapparat {AppNr} - {Appname} wurden folgende Neuauflagen gefunden:</p>
<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>
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">{newEditions}</p>
<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>
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sollen wir die alte(n) Auflage(n) aus dem Apparat durch diese austauschen?</p>
<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>
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><pre class="moz-signature" cols="72">-- </p>
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">{signature}</p>
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></pre></p></body></html>

View File

@@ -1,17 +1,18 @@
[project]
name = "semesterapparatsmanager"
version = "0.2.1"
version = "1.0.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
requires-python = ">=3.13"
dependencies = [
"appdirs>=1.4.4",
"beautifulsoup4>=4.12.3",
"beautifulsoup4>=4.13.5",
"bump-my-version>=0.29.0",
"chardet>=5.2.0",
"charset-normalizer>=3.4.3",
"comtypes>=1.4.9",
"darkdetect>=0.8.0",
"docx2pdf>=0.1.8",
"httpx>=0.28.1",
"loguru>=0.7.3",
"mkdocs>=1.6.1",
"mkdocs-material>=9.5.49",
@@ -35,9 +36,12 @@ dev = [
"icecream>=2.1.4",
"nuitka>=2.5.9",
]
swbtest = [
"alive-progress>=3.3.0",
]
[tool.bumpversion]
current_version = "0.2.1"
current_version = "1.0.0"
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
serialize = ["{major}.{minor}.{patch}"]
search = "{current_version}"
@@ -61,3 +65,7 @@ post_commit_hooks = []
filename = "src/__init__.py"
[[tool.bumpversion.files]]
filename = ".version"
[[tool.uv.index]]
url = "https://git.theprivateserver.de/api/packages/WorldTeacher/pypi/simple/"
default = false

View File

@@ -1,4 +1,4 @@
__version__ = "0.2.1"
__version__ = "1.0.0"
__author__ = "Alexander Kirchner"
__all__ = ["__version__", "__author__", "Icon", "settings"]
@@ -8,25 +8,24 @@ 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)
LOG_DIR: str = app.user_log_dir # type: ignore
CONFIG_DIR: str = app.user_config_dir # type: ignore
if not os.path.exists(LOG_DIR): # type: ignore
os.makedirs(LOG_DIR) # type: ignore
if not os.path.exists(CONFIG_DIR): # type: ignore
os.makedirs(CONFIG_DIR) # type: ignore
settings = Config(f"{CONFIG_DIR}/config.yaml")
DATABASE_DIR = (
app.user_config_dir if settings.database.path is None else settings.database.path
DATABASE_DIR = ( # type: ignore
app.user_config_dir if settings.database.path is None else settings.database.path # type: ignore
)
if not os.path.exists(DATABASE_DIR):
os.makedirs(DATABASE_DIR)
if not os.path.exists(DATABASE_DIR): # type: ignore
os.makedirs(DATABASE_DIR) # type: ignore
first_launch = settings.exists
if not os.path.exists(settings.database.temp.expanduser()):
settings.database.temp.expanduser().mkdir(parents=True, exist_ok=True)
if not os.path.exists(settings.database.temp.expanduser()): # type: ignore
settings.database.temp.expanduser().mkdir(parents=True, exist_ok=True) # type: ignore
from .utils.icon import Icon
if not os.path.exists("logs"):

View File

@@ -1,8 +1,24 @@
from .semester import Semester
from .database import Database
__all__ = [
"AdminCommands",
"Semester",
"AutoAdder",
"AvailChecker",
"BookGrabber",
"Database",
"DocumentationThread",
"NewEditionCheckerThread",
"recreateElsaFile",
"recreateFile",
"Catalogue"
]
from .admin_console import AdminCommands
from .thread_bookgrabber import BookGrabber
from .threads_availchecker import AvailChecker
from .threads_autoadder import AutoAdder
from .create_file import recreateElsaFile, recreateFile
from .database import Database
from .documentation_thread import DocumentationThread
from .create_file import recreateFile, recreateElsaFile
from .semester import Semester
from .thread_bookgrabber import BookGrabber
from .thread_neweditions import NewEditionCheckerThread
from .threads_autoadder import AutoAdder
from .threads_availchecker import AvailChecker
from .catalogue import Catalogue

101
src/backend/catalogue.py Normal file
View File

@@ -0,0 +1,101 @@
import requests
from bs4 import BeautifulSoup
from src.logic import BookData as Book
from datetime import datetime
import sys
import loguru
from src import LOG_DIR
URL = "https://rds.ibs-bw.de/phfreiburg/opac/RDSIndex/Search?type0%5B%5D=allfields&lookfor0%5B%5D={}&join=AND&bool0%5B%5D=AND&type0%5B%5D=au&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=ti&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=ct&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=isn&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=ta&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=co&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=py&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=pp&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=pu&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=si&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=zr&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=cc&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND"
BASE = "https://rds.ibs-bw.de"
log = loguru.logger
log.remove()
log.add(sys.stdout, level="INFO")
log.add(f"{LOG_DIR}/application.log", rotation="1 MB", retention="10 days")
log.add(
f"{LOG_DIR}/{datetime.now().strftime('%Y-%m-%d')}.log",
rotation="1 day",
retention="1 month",
)
class Catalogue:
def __init__(self, timeout=5):
self.timeout = timeout
reachable = self.check_connection()
if not reachable:
log.error("No internet connection available.")
raise ConnectionError("No internet connection available.")
def check_connection(self):
try:
response = requests.get("https://www.google.com", timeout=self.timeout)
if response.status_code == 200:
return True
except requests.exceptions.RequestException as e:
log.error(f"Could not connect to google.com: {e}")
def search_book(self, searchterm: str):
response = requests.get(URL.format(searchterm), timeout=self.timeout)
return response.text
def search(self, link: str):
response = requests.get(link, timeout=self.timeout)
return response.text
def get_book_links(self, searchterm: str):
response = self.search_book(searchterm)
soup = BeautifulSoup(response, "html.parser")
links = soup.find_all("a", class_="title getFull")
res = []
for link in links:
res.append(BASE + link["href"])
return res
def get_book(self, searchterm: str):
log.info(f"Searching for term: {searchterm}")
links = self.get_book_links(searchterm)
for link in links:
result = self.search(link)
# in result search for class col-xs-12 rds-dl RDS_LOCATION
# if found, return text of href
soup = BeautifulSoup(result, "html.parser")
location = soup.find_all("div", class_="col-xs-12 rds-dl RDS_LOCATION")
for loc in location:
if f"1. OG Semesterapparat" in loc.text:
title = (
soup.find("div", class_="headline text")
.text.replace("\n", "")
.strip()
)
ppn = soup.find(
"div", class_="col-xs-12 col-md-5 col-lg-4 rds-dl-head RDS_PPN"
)
signature = soup.find(
"div", class_="col-xs-12 rds-dl RDS_SIGNATURE"
)
if signature:
signature = (
signature.find_next("div")
.find_next("div")
.text.replace("\n", "")
.strip()
)
# use ppn to find the next div and extract the text
if ppn:
ppn = ppn.find_next("div").text.replace("\n", "").strip()
else:
ppn = None
isbn = soup.find(
"div", class_="col-xs-12 col-md-5 col-lg-4 rds-dl-head RDS_ISBN"
)
if isbn:
isbn = isbn.find_next("div").find_next("div").text
else:
isbn = None
return Book(
title=title, ppn=ppn, signature=signature, isbn=isbn, link=link
)
return False

View File

@@ -12,7 +12,7 @@ from typing import Any, List, Optional, Tuple, Union
import loguru
from src import LOG_DIR, settings, DATABASE_DIR
from src import DATABASE_DIR, LOG_DIR, settings
from src.backend.db import (
CREATE_ELSA_FILES_TABLE,
CREATE_ELSA_MEDIA_TABLE,
@@ -38,7 +38,6 @@ log.add(sys.stdout, level="INFO")
log.add(f"{LOG_DIR}/application.log", rotation="1 MB", retention="10 days")
ascii_lowercase = lower + digits + punctuation
@@ -68,6 +67,57 @@ class Database:
self.db_path = db_path
log.debug(f"Database path: {self.db_path}")
self.db_initialized = False
self.startup_check()
def startup_check(self):
# check existence of all tables. if any is missing, recreate the table
if not self.db_initialized:
self.initializeDatabase()
tables = self.get_db_contents()
tables = [t[1] for t in tables] if tables is not None else []
required_tables = [
"semesterapparat",
"messages",
"media",
"files",
"prof",
"user",
"subjects",
"elsa",
"elsa_files",
"elsa_media",
]
for table in required_tables:
if table not in tables:
log.critical(f"Table {table} is missing, recreating...")
self.create_table(table)
def create_table(self, table_name: str):
match table_name:
case "semesterapparat":
query = CREATE_TABLE_APPARAT
case "messages":
query = CREATE_TABLE_MESSAGES
case "media":
query = CREATE_TABLE_MEDIA
case "files":
query = CREATE_TABLE_FILES
case "prof":
query = CREATE_TABLE_PROF
case "user":
query = CREATE_TABLE_USER
case "subjects":
query = CREATE_TABLE_SUBJECTS
case "elsa":
query = CREATE_ELSA_TABLE
case "elsa_files":
query = CREATE_ELSA_FILES_TABLE
case "elsa_media":
query = CREATE_ELSA_MEDIA_TABLE
case _:
log.error(f"Table {table_name} is not a valid table name")
self.query_db(query)
def initializeDatabase(self):
if not self.db_initialized:
@@ -201,12 +251,12 @@ class Database:
logs_query = query
logs_args = args
if "fileblob" in query:
# set fileblob arg in logger to "too long"
logs_query = query
fileblob_location = query.find("fileblob")
# remove fileblob from query
logs_query = query[:fileblob_location] + "fileblob = too long"
# if "fileblob" in query:
# # set fileblob arg in logger to "too long"
# logs_query = query
# fileblob_location = query.find("fileblob")
# # remove fileblob from query
# logs_query = query[:fileblob_location] + "fileblob = too long"
log_message = f"Querying database with query {logs_query}, args: {logs_args}"
# if "INSERT" in query:
@@ -435,6 +485,7 @@ class Database:
deleted (int, optional): The state of the book. Set to 1 to include deleted ones. Defaults to 0.
Returns:
list[dict[int, BookData, int]]: A list of dictionaries containing the id, the metadata of the book and the availability of the book
"""
qdata = self.query_db(
@@ -451,6 +502,46 @@ class Database:
ret_result.append(data)
return ret_result
def getAllBooks(self):
# return all books in the database
qdata = self.query_db("SELECT id,bookdata FROM media WHERE deleted=0")
ret_result: list[dict[str, Any]] = []
if qdata is None:
return []
for result_a in qdata:
data: dict[str, Any] = {"id": int, "bookdata": BookData}
data["id"] = result_a[0]
data["bookdata"] = BookData().from_string(result_a[1])
ret_result.append(data)
return ret_result
def getBooksByProfId(self, prof_id: int, deleted: int = 0):
"""
Get the Books based on the professor id
Args:
prof_id (str): The ID of the professor
deleted (int, optional): The state of the book. Set to 1 to include deleted ones. Defaults to 0.
Returns:
list[dict[int, BookData, int]]: A list of dictionaries containing the id, the metadata of the book and the availability of the book
"""
qdata = self.query_db(
f"SELECT id,bookdata,available FROM media WHERE prof_id={prof_id} AND (deleted={deleted if deleted == 0 else '1 OR deleted=0'})"
)
ret_result = []
if qdata is None:
return []
for result_a in qdata:
data: dict[str, Any] = {"id": int, "bookdata": BookData, "available": int}
data["id"] = result_a[0]
data["bookdata"] = BookData().from_string(result_a[1])
data["available"] = result_a[2]
ret_result.append(data)
return ret_result
def updateBookdata(self, book_id: int, bookdata: BookData):
"""
Update the bookdata in the database
@@ -525,11 +616,12 @@ class Database:
str: The filename of the recreated file
"""
blob = self.getBlob(filename, app_id)
log.debug(blob)
tempdir = settings.database.temp.expanduser()
if not tempdir.exists():
tempdir.mkdir(parents=True, exist_ok=True)
file = tempfile.NamedTemporaryFile(
delete=False, dir=tempdir_path, mode="wb", suffix=f".{filetype}"
delete=False, dir=tempdir, mode="wb", suffix=f".{filetype}"
)
file.write(blob)
# log.debug("file created")
@@ -701,6 +793,20 @@ class Database:
else:
return prof[0]
def getProfMailById(self, prof_id: Union[str, int]) -> str:
"""get the mail of a professor based on the id
Args:
prof_id (Union[str,int]): the id of the professor
Returns:
str: the mail of the professor
"""
mail = self.query_db("SELECT mail FROM prof WHERE id=?", (prof_id,), one=True)[
0
]
return mail if mail is not None else ""
def getTitleById(self, prof_id: Union[str, int]) -> str:
"""get the title of a professor based on the id
@@ -877,6 +983,23 @@ class Database:
(newDate, today, app_id),
)
def getId(self, apparat_name) -> Optional[int]:
"""get the id of an apparat based on the name
Args:
apparat_name (str): the name of the apparat e.g. "Semesterapparat 1"
Returns:
Optional[int]: the id of the apparat, if the apparat is not found, None is returned
"""
data = self.query_db(
"SELECT id FROM semesterapparat WHERE name=?", (apparat_name,), one=True
)
if data is None:
return None
else:
return data[0]
def getApparatId(self, apparat_name) -> Optional[int]:
"""get the id of an apparat based on the name
@@ -1014,22 +1137,22 @@ class Database:
self.close_connection(conn)
return ret
def deleteApparat(self, app_id: Union[str, int], semester):
def deleteApparat(self, apparat: Apparat, semester: str):
"""Delete an apparat from the database
Args:
app_id (Union[str, int]): the id of the apparat
apparat: (Apparat): the apparat to be deleted
semester (str): the semester the apparat should be deleted from
"""
log.info(f"Deleting apparat with id {app_id} in semester {semester}")
apparat_nr = apparat.appnr
app_id = self.getId(apparat.name)
self.query_db(
"UPDATE semesterapparat SET deletion_status=1, deleted_date=? WHERE appnr=?",
(semester, app_id),
)
self.query_db(
"UPDATE media SET deleted=1 WHERE app_id=?",
(app_id,),
"UPDATE semesterapparat SET deletion_status=1, deleted_date=? WHERE appnr=? AND name=?",
(semester, apparat_nr, apparat.name),
)
# delete all books associated with the app_id
print(apparat_nr, app_id)
self.query_db("UPDATE media SET deleted=1 WHERE app_id=?", (app_id,))
def isEternal(self, id):
"""check if the apparat is eternal (dauerapparat)

View File

@@ -0,0 +1,263 @@
import re
import sys
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime
from math import ceil
from queue import Empty, Queue
from typing import List, Optional, Set, Union
import loguru
from PySide6.QtCore import QThread, Signal
from src import LOG_DIR
from src.logic import BookData
from src.logic.lehmannsapi import LehmannsClient
from src.logic.swb import SWB
log = loguru.logger
log.remove()
log.add(sys.stdout, level="INFO")
log.add(f"{LOG_DIR}/application.log", rotation="1 MB", retention="10 days")
log.add(
f"{LOG_DIR}/{datetime.now().strftime('%Y-%m-%d')}.log",
rotation="1 day",
retention="1 month",
)
def _norm_text(s: Optional[str]) -> str:
if not s:
return ""
# lowercase, collapse whitespace, drop some punctuation
s = s.lower()
s = re.sub(r"[\s\-\u2013\u2014]+", " ", s) # spaces/dashes
s = re.sub(r"[\"'`:.,;!?()\[\]{}]", "", s)
return s.strip()
def _same_book(a: BookData, b: BookData) -> bool:
"""Heuristic: same if ISBNs intersect; fallback to (title, author, year) normalized."""
isbns_a = _norm_isbns(a.isbn)
isbns_b = _norm_isbns(b.isbn)
if isbns_a and isbns_b and (isbns_a & isbns_b):
return True
ta, tb = _norm_text(a.title), _norm_text(b.title)
aa, ab = _norm_text(a.author), _norm_text(b.author)
ya, yb = (a.year or "").strip(), (b.year or "").strip()
# strong title match required; then author if available; then year if available
if ta and tb and ta == tb:
# if both have authors, require match
if aa and ab and aa == ab:
# if both have year, require match
if ya and yb:
return ya == yb
return True
# if one/both authors missing, allow title (+year if both present)
if ya and yb:
return ya == yb
return True
return False
def _norm_isbns(value: Union[str, List[str], None]) -> Set[str]:
"""Return a set of 10/13-digit ISBNs (digits only, keep X for ISBN-10 if present)."""
if value is None:
return set()
vals = value if isinstance(value, list) else [value]
out: Set[str] = set()
for v in vals:
s = str(v)
digits = re.sub(r"[^0-9Xx]", "", s)
# keep 13-digit or 10-digit tokens
m13 = re.findall(r"97[89]\d{10}", digits)
if m13:
out.update(m13)
else:
m10 = re.findall(r"\d{9}[0-9Xx]", digits)
out.update(x.upper() for x in m10)
return out
def filter_prefer_swb(records: List[BookData]) -> List[BookData]:
"""
If an SWB entry with a non-empty signature exists for a book, drop the HTTP(S) duplicate(s).
Returns a NEW list (does not mutate the input).
"""
swb_with_sig = [
r
for r in records
if (r.link == "SWB") and (r.signature and r.signature.strip())
]
if not swb_with_sig:
return list(records)
to_remove: Set[int] = set()
# For each URL entry, see if it matches any SWB-with-signature entry
for idx, rec in enumerate(records):
if not rec.link or not rec.link.lower().startswith("http"):
continue
for swb in swb_with_sig:
if _same_book(swb, rec):
to_remove.add(idx)
break
# Build filtered list
return [rec for i, rec in enumerate(records) if i not in to_remove]
class NewEditionCheckerThread(QThread):
updateSignal = Signal(int, int) # (processed, total)
updateProgress = Signal(int, int) # (processed, total)
total_entries_signal = Signal(int)
resultsSignal = Signal(list) # list[tuple[BookData, list[BookData]]]
def __init__(self, entries: Optional[list["BookData"]] = None, parent=None):
super().__init__(parent)
self.entries: list["BookData"] = entries if entries is not None else []
self.results: list[tuple["BookData", list["BookData"]]] = []
def reset(self):
self.entries = []
self.results = []
# ---------- internal helpers ----------
@staticmethod
def _split_evenly(items: list, parts: int) -> list[list]:
"""Split items as evenly as possible into `parts` chunks (no empty tails)."""
if parts <= 1 or len(items) <= 1:
return [items]
n = len(items)
base = n // parts
extra = n % parts
chunks = []
i = 0
for k in range(parts):
size = base + (1 if k < extra else 0)
if size == 0:
continue
chunks.append(items[i : i + size])
i += size
return chunks
@staticmethod
def _clean_title(raw: str) -> str:
title = raw.rstrip(" .:,;!?")
title = re.sub(r"\s*\(.*\)", "", title)
return title.strip()
@classmethod
def _process_book(
cls, book: "BookData"
) -> tuple["BookData", list["BookData"]] | None:
author = (
book.author.split(";")[0].replace(" ", "")
if (book.author and ";" in book.author)
else (book.author or "").replace(" ", "")
)
title = cls._clean_title(book.title or "")
# Query SWB
response: list[BookData] = SWB().getBooks(
[
"pica.bib=20735",
f"pica.tit={title.split(':')[0].strip()}",
# f"pica.per={author}",
]
)
# Remove same PPN
response = [entry for entry in response if entry.ppn != book.ppn]
for respo in response:
respo.link = "SWB"
# Query Lehmanns
with LehmannsClient() as client:
results = client.search_by_title(title, strict=True)
if results:
for res in results:
response.append(BookData().from_LehmannsSearchResult(res))
if not response:
return None
response = filter_prefer_swb(response)
# Remove entries matching the same ISBN as the current book
response = [
entry
for entry in response
if not (_norm_isbns(entry.isbn) & _norm_isbns(book.isbn))
]
if not response:
return None
return (book, response)
@classmethod
def _worker(cls, items: list["BookData"], q: Queue) -> None:
"""Worker for one chunk; pushes ('result', ...), ('progress', 1), and ('done', None)."""
try:
for book in items:
try:
result = cls._process_book(book)
except Exception:
result = None
if result is not None:
q.put(("result", result))
q.put(("progress", 1))
finally:
q.put(("done", None))
# ---------- thread entry point ----------
def run(self):
total = len(self.entries)
self.total_entries_signal.emit(total)
if total == 0:
log.debug("No entries to process.")
self.resultsSignal.emit([])
return
# Up to 4 workers; ~20 items per worker
num_workers = min(4, max(1, ceil(total / 20)))
chunks = self._split_evenly(self.entries, num_workers)
sizes = [len(ch) for ch in chunks]
q: Queue = Queue()
processed = 0
finished_workers = 0
with ThreadPoolExecutor(max_workers=len(chunks)) as ex:
futures = [ex.submit(self._worker, ch, q) for ch in chunks]
log.info(
f"Launched {len(futures)} worker thread(s) for {total} entries: {sizes} entries per thread."
)
for idx, sz in enumerate(sizes, 1):
log.debug(f"Thread {idx}: {sz} entries")
# Aggregate progress/results
while finished_workers < len(chunks):
try:
kind, payload = q.get(timeout=0.1)
except Empty:
continue
if kind == "progress":
processed += int(payload)
self.updateSignal.emit(processed, total)
self.updateProgress.emit(processed, total)
elif kind == "result":
self.results.append(payload)
elif kind == "done":
finished_workers += 1
self.resultsSignal.emit(self.results)

View File

@@ -56,6 +56,9 @@ class AvailChecker(QThread):
rds = transformer.get_data(data).return_data("rds_availability")
book_id = None
if not rds or not rds.items:
log.warning(f"No RDS data found for link {link}")
continue
for item in rds.items:
sign = item.superlocation
loc = item.location

View File

@@ -1,13 +1,12 @@
import csv
import chardet
from charset_normalizer import detect
def csv_to_list(path: str) -> list[str]:
"""
Extracts the data from a csv file and returns it as a pandas dataframe
"""
encoding = chardet.detect(open(path, "rb").read())["encoding"]
encoding = detect(open(path, "rb").read())["encoding"]
with open(path, newline="", encoding=encoding) as csvfile:
# if decoder fails to map, assign ""
reader = csv.reader(csvfile, delimiter=";", quotechar="|")

View File

@@ -1,8 +1,8 @@
from dataclasses import dataclass, field
from enum import Enum
import json
from typing import Union, Any, Optional
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Optional, Union
@dataclass
class Prof:
@@ -93,6 +93,24 @@ class BookData:
ndata = json.loads(data)
return BookData(**ndata)
def from_LehmannsSearchResult(self, result: Any) -> "BookData":
self.title = result.title
self.author = "; ".join(result.authors) if result.authors else None
self.edition = str(result.edition) if result.edition else None
self.link = result.url
self.isbn = (
result.isbn13
if isinstance(result.isbn13, list)
else [result.isbn13]
if result.isbn13
else []
)
self.pages = str(result.pages) if result.pages else None
self.publisher = result.publisher
self.year = str(result.year) if result.year else None
# self.pages = str(result.pages) if result.pages else None
return self
@dataclass
class MailData:

280
src/logic/lehmannsapi.py Normal file
View File

@@ -0,0 +1,280 @@
from __future__ import annotations
import re
from dataclasses import dataclass, asdict, field
from typing import Optional, List, Iterable
from urllib.parse import urljoin, quote_plus
import httpx
from bs4 import BeautifulSoup
BASE = "https://www.lehmanns.de"
SEARCH_URL = "https://www.lehmanns.de/search/quick?mediatype_id=&q="
@dataclass
class LehmannsSearchResult:
title: str
url: str
# Core fields from the listing card
year: Optional[int] = None
edition: Optional[int] = None
publisher: Optional[str] = None
isbn13: Optional[str] = None
# Extras from the listing card
description: Optional[str] = None
authors: list[str] = field(default_factory=list)
media_type: Optional[str] = None
book_format: Optional[str] = None
price_eur: Optional[float] = None
currency: str = "EUR"
image: Optional[str] = None
# From detail page:
pages: Optional[str] = None # "<N> Seiten"
buyable: bool = True # set in enrich_pages (detail page)
unavailable_hint: Optional[str] = None # e.g. "Titel ist leider vergriffen; keine Neuauflage"
def to_dict(self) -> dict:
return asdict(self)
class LehmannsClient:
"""Scrapes quick-search results, then enriches (and filters) via product pages."""
def __init__(self, timeout: float = 20.0):
self.client = httpx.Client(
headers={
"User-Agent": (
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/124.0 Safari/537.36"
),
"Accept-Language": "de-DE,de;q=0.9,en;q=0.8",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
},
timeout=timeout,
follow_redirects=True,
)
def close(self):
self.client.close()
def __enter__(self):
return self
def __exit__(self, *exc):
self.close()
# ------------------- Search (listing) -------------------
def build_search_url(self, title: str) -> str:
# spaces -> '+'
return SEARCH_URL + quote_plus(title)
def search_by_title(self, title: str, limit: Optional[int] = None, strict: bool = False) -> List[LehmannsSearchResult]:
"""
Parse the listing page only (no availability check here).
Use enrich_pages(...) afterwards to fetch detail pages, add 'pages',
and drop unbuyable items.
"""
url = self.build_search_url(title)
html = self._get(url)
if not html:
return []
results = self._parse_results(html)
self.enrich_pages(results)
if strict:
# filter results to only those with exact title match (case-insensitive)
title_lower = title.lower()
results = [r for r in results if r.title and r.title.lower() == title_lower]
results = [r for r in results if r.buyable]
return results
if limit is not None:
results = results[:max(0, limit)]
return results
# ------------------- Detail enrichment & filtering -------------------
def enrich_pages(self, results: Iterable[LehmannsSearchResult], drop_unbuyable: bool = True) -> List[LehmannsSearchResult]:
"""
Fetch each result.url, extract:
- pages: from <span class="book-meta meta-seiten" itemprop="numberOfPages">...</span>
- availability: from <li class="availability-3">...</li>
* if it contains "Titel ist leider vergriffen", mark buyable=False
* if it also contains "keine Neuauflage", set unavailable_hint accordingly
If drop_unbuyable=True, exclude non-buyable results from the returned list.
"""
enriched: List[LehmannsSearchResult] = []
for r in results:
try:
html = self._get(r.url)
if not html:
# Can't verify; keep as-is when not dropping, else skip
if not drop_unbuyable:
enriched.append(r)
continue
soup = BeautifulSoup(html, "html.parser")
# Pages
pages_node = soup.select_one(
"span.book-meta.meta-seiten[itemprop='numberOfPages'], "
"span.book-meta.meta-seiten[itemprop='numberofpages'], "
".meta-seiten [itemprop='numberOfPages'], "
".meta-seiten[itemprop='numberOfPages'], "
".book-meta.meta-seiten"
)
if pages_node:
text = pages_node.get_text(" ", strip=True)
m = re.search(r"\d+", text)
if m:
r.pages = f"{m.group(0)} Seiten"
# Availability via li.availability-3
avail_li = soup.select_one("li.availability-3")
if avail_li:
avail_text = " ".join(avail_li.get_text(" ", strip=True).split()).lower()
if "titel ist leider vergriffen" in avail_text:
r.buyable = False
if "keine neuauflage" in avail_text:
r.unavailable_hint = "Titel ist leider vergriffen; keine Neuauflage"
else:
r.unavailable_hint = "Titel ist leider vergriffen"
# Append or drop
if (not drop_unbuyable) or r.buyable:
enriched.append(r)
except Exception:
# On any per-item error, keep the record if not dropping; else skip
if not drop_unbuyable:
enriched.append(r)
continue
return enriched
# ------------------- Internals -------------------
def _get(self, url: str) -> Optional[str]:
try:
r = self.client.get(url)
r.encoding = "utf-8"
if r.status_code == 200 and "text/html" in (r.headers.get("content-type") or ""):
return r.text
except httpx.HTTPError:
pass
return None
def _parse_results(self, html: str) -> List[LehmannsSearchResult]:
soup = BeautifulSoup(html, "html.parser")
results: list[LehmannsSearchResult] = []
for block in soup.select("div.info-block"):
a = block.select_one(".title a[href]")
if not a:
continue
url = urljoin(BASE, a["href"].strip())
base_title = (block.select_one(".title [itemprop='name']") or a).get_text(strip=True)
# Alternative headline => extend title
alt_tag = block.select_one(".description[itemprop='alternativeHeadline']")
alternative_headline = alt_tag.get_text(strip=True) if alt_tag else None
title = f"{base_title} : {alternative_headline}" if alternative_headline else base_title
description = alternative_headline
# Authors from .author
authors: list[str] = []
author_div = block.select_one("div.author")
if author_div:
t = author_div.get_text(" ", strip=True)
t = re.sub(r"^\s*von\s+", "", t, flags=re.I)
for part in re.split(r"\s*;\s*|\s*&\s*|\s+und\s+", t):
name = " ".join(part.split())
if name:
authors.append(name)
# Media + format
media_type = None
book_format = None
type_text = block.select_one(".type")
if type_text:
t = type_text.get_text(" ", strip=True)
m = re.search(r"\b(Buch|eBook|Hörbuch)\b", t)
if m:
media_type = m.group(1)
fm = re.search(r"\(([^)]+)\)", t)
if fm:
book_format = fm.group(1).strip().upper()
# Year
year = None
y = block.select_one("[itemprop='copyrightYear']")
if y:
try:
year = int(y.get_text(strip=True))
except ValueError:
pass
# Edition
edition = None
ed = block.select_one("[itemprop='bookEdition']")
if ed:
m = re.search(r"\d+", ed.get_text(strip=True))
if m:
edition = int(m.group())
# Publisher
publisher = None
pub = block.select_one(".publisherprop [itemprop='name']") or block.select_one(".publisher [itemprop='name']")
if pub:
publisher = pub.get_text(strip=True)
# ISBN-13
isbn13 = None
isbn_tag = block.select_one(".isbn [itemprop='isbn'], [itemprop='isbn']")
if isbn_tag:
digits = re.sub(r"[^0-9Xx]", "", isbn_tag.get_text(strip=True))
m = re.search(r"(97[89]\d{10})", digits)
if m:
isbn13 = m.group(1)
# Price (best effort)
price_eur = None
txt = block.get_text(" ", strip=True)
mprice = re.search(r"(\d{1,3}(?:\.\d{3})*,\d{2})\s*€", txt)
if not mprice and block.parent:
sib = block.parent.get_text(" ", strip=True)
mprice = re.search(r"(\d{1,3}(?:\.\d{3})*,\d{2})\s*€", sib)
if mprice:
num = mprice.group(1).replace(".", "").replace(",", ".")
try:
price_eur = float(num)
except ValueError:
pass
# Image (best-effort)
image = None
left_img = block.find_previous("img")
if left_img and left_img.get("src"):
image = urljoin(BASE, left_img["src"])
results.append(
LehmannsSearchResult(
title=title,
url=url,
description=description,
authors=authors,
media_type=media_type,
book_format=book_format,
year=year,
edition=edition,
publisher=publisher,
isbn13=isbn13,
price_eur=price_eur,
image=image,
)
)
return results

448
src/logic/swb.py Normal file
View File

@@ -0,0 +1,448 @@
import xml.etree.ElementTree as ET
from dataclasses import dataclass, field
from typing import Dict, Iterable, List, Optional, Tuple
import requests
from src.logic.dataclass import BookData
# -----------------------
# Dataclasses
# -----------------------
# --- MARC XML structures ---
@dataclass
class ControlField:
tag: str
value: str
@dataclass
class SubField:
code: str
value: str
@dataclass
class DataField:
tag: str
ind1: str = " "
ind2: str = " "
subfields: List[SubField] = field(default_factory=list)
@dataclass
class MarcRecord:
leader: str
controlfields: List[ControlField] = field(default_factory=list)
datafields: List[DataField] = field(default_factory=list)
# --- SRU record wrapper ---
@dataclass
class Record:
recordSchema: str
recordPacking: str
recordData: MarcRecord
recordPosition: int
@dataclass
class EchoedSearchRequest:
version: str
query: str
maximumRecords: int
recordPacking: str
recordSchema: str
@dataclass
class SearchRetrieveResponse:
version: str
numberOfRecords: int
records: List[Record] = field(default_factory=list)
echoedSearchRetrieveRequest: Optional[EchoedSearchRequest] = None
# -----------------------
# Parser
# -----------------------
ZS = "http://www.loc.gov/zing/srw/"
MARC = "http://www.loc.gov/MARC21/slim"
NS = {"zs": ZS, "marc": MARC}
def _text(elem: Optional[ET.Element]) -> str:
return (elem.text or "") if elem is not None else ""
def _req_text(parent: ET.Element, path: str) -> str:
el = parent.find(path, NS)
if el is None or el.text is None:
raise ValueError(f"Required element not found or empty: {path}")
return el.text
def parse_marc_record(record_el: ET.Element) -> MarcRecord:
"""
record_el is the <marc:record> element (default ns MARC in your sample)
"""
# leader
leader_text = _req_text(record_el, "marc:leader")
# controlfields
controlfields: List[ControlField] = []
for cf in record_el.findall("marc:controlfield", NS):
tag = cf.get("tag", "").strip()
controlfields.append(ControlField(tag=tag, value=_text(cf)))
# datafields
datafields: List[DataField] = []
for df in record_el.findall("marc:datafield", NS):
tag = df.get("tag", "").strip()
ind1 = df.get("ind1") or " "
ind2 = df.get("ind2") or " "
subfields: List[SubField] = []
for sf in df.findall("marc:subfield", NS):
code = sf.get("code", "")
subfields.append(SubField(code=code, value=_text(sf)))
datafields.append(DataField(tag=tag, ind1=ind1, ind2=ind2, subfields=subfields))
return MarcRecord(
leader=leader_text, controlfields=controlfields, datafields=datafields
)
def parse_record(zs_record_el: ET.Element) -> Record:
recordSchema = _req_text(zs_record_el, "zs:recordSchema")
recordPacking = _req_text(zs_record_el, "zs:recordPacking")
# recordData contains a MARC <record> with default MARC namespace in your sample
recordData_el = zs_record_el.find("zs:recordData", NS)
if recordData_el is None:
raise ValueError("Missing zs:recordData")
marc_record_el = recordData_el.find("marc:record", NS)
if marc_record_el is None:
# If the MARC record uses default ns (xmlns="...") ElementTree still needs the ns-qualified name
# We already searched with prefix; this covers both default and prefixed cases.
raise ValueError("Missing MARC21 record inside zs:recordData")
marc_record = parse_marc_record(marc_record_el)
recordPosition = int(_req_text(zs_record_el, "zs:recordPosition"))
return Record(
recordSchema=recordSchema,
recordPacking=recordPacking,
recordData=marc_record,
recordPosition=recordPosition,
)
def parse_echoed_request(root: ET.Element) -> Optional[EchoedSearchRequest]:
el = root.find("zs:echoedSearchRetrieveRequest", NS)
if el is None:
return None
# Be permissive with missing fields
version = _text(el.find("zs:version", NS))
query = _text(el.find("zs:query", NS))
maximumRecords_text = _text(el.find("zs:maximumRecords", NS)) or "0"
recordPacking = _text(el.find("zs:recordPacking", NS))
recordSchema = _text(el.find("zs:recordSchema", NS))
try:
maximumRecords = int(maximumRecords_text)
except ValueError:
maximumRecords = 0
return EchoedSearchRequest(
version=version,
query=query,
maximumRecords=maximumRecords,
recordPacking=recordPacking,
recordSchema=recordSchema,
)
def parse_search_retrieve_response(xml_str: str) -> SearchRetrieveResponse:
root = ET.fromstring(xml_str)
# Root is zs:searchRetrieveResponse
version = _req_text(root, "zs:version")
numberOfRecords = int(_req_text(root, "zs:numberOfRecords"))
records_parent = root.find("zs:records", NS)
records: List[Record] = []
if records_parent is not None:
for r in records_parent.findall("zs:record", NS):
records.append(parse_record(r))
echoed = parse_echoed_request(root)
return SearchRetrieveResponse(
version=version,
numberOfRecords=numberOfRecords,
records=records,
echoedSearchRetrieveRequest=echoed,
)
# --- Query helpers over MarcRecord ---
def iter_datafields(
rec: MarcRecord,
tag: Optional[str] = None,
ind1: Optional[str] = None,
ind2: Optional[str] = None,
) -> Iterable[DataField]:
"""Yield datafields, optionally filtered by tag/indicators."""
for df in rec.datafields:
if tag is not None and df.tag != tag:
continue
if ind1 is not None and df.ind1 != ind1:
continue
if ind2 is not None and df.ind2 != ind2:
continue
yield df
def subfield_values(
rec: MarcRecord,
tag: str,
code: str,
*,
ind1: Optional[str] = None,
ind2: Optional[str] = None,
) -> List[str]:
"""All values for subfield `code` in every `tag` field (respecting indicators)."""
out: List[str] = []
for df in iter_datafields(rec, tag, ind1, ind2):
out.extend(sf.value for sf in df.subfields if sf.code == code)
return out
def first_subfield_value(
rec: MarcRecord,
tag: str,
code: str,
*,
ind1: Optional[str] = None,
ind2: Optional[str] = None,
default: Optional[str] = None,
) -> Optional[str]:
"""First value for subfield `code` in `tag` (respecting indicators)."""
for df in iter_datafields(rec, tag, ind1, ind2):
for sf in df.subfields:
if sf.code == code:
return sf.value
return default
def find_datafields_with_subfields(
rec: MarcRecord,
tag: str,
*,
where_all: Optional[Dict[str, str]] = None,
where_any: Optional[Dict[str, str]] = None,
casefold: bool = False,
ind1: Optional[str] = None,
ind2: Optional[str] = None,
) -> List[DataField]:
"""
Return datafields of `tag` whose subfields match constraints:
- where_all: every (code -> exact value) must be present
- where_any: at least one (code -> exact value) present
Set `casefold=True` for case-insensitive comparison.
"""
where_all = where_all or {}
where_any = where_any or {}
matched: List[DataField] = []
for df in iter_datafields(rec, tag, ind1, ind2):
# Map code -> list of values (with optional casefold applied)
vals: Dict[str, List[str]] = {}
for sf in df.subfields:
v = sf.value.casefold() if casefold else sf.value
vals.setdefault(sf.code, []).append(v)
ok = True
for c, v in where_all.items():
vv = v.casefold() if casefold else v
if c not in vals or vv not in vals[c]:
ok = False
break
if ok and where_any:
any_ok = any(
(c in vals) and ((v.casefold() if casefold else v) in vals[c])
for c, v in where_any.items()
)
if not any_ok:
ok = False
if ok:
matched.append(df)
return matched
def controlfield_value(
rec: MarcRecord, tag: str, default: Optional[str] = None
) -> Optional[str]:
"""Get the first controlfield value by tag (e.g., '001', '005')."""
for cf in rec.controlfields:
if cf.tag == tag:
return cf.value
return default
def datafields_value(
data: List[DataField], code: str, default: Optional[str] = None
) -> Optional[str]:
"""Get the first value for a specific subfield code in a list of datafields."""
for df in data:
for sf in df.subfields:
if sf.code == code:
return sf.value
return default
def datafield_value(
df: DataField, code: str, default: Optional[str] = None
) -> Optional[str]:
"""Get the first value for a specific subfield code in a datafield."""
for sf in df.subfields:
if sf.code == code:
return sf.value
return default
def _smart_join_title(a: str, b: Optional[str]) -> str:
"""
Join 245 $a and $b with MARC-style punctuation.
If $b is present, join with ' : ' unless either side already supplies punctuation.
"""
a = a.strip()
if not b:
return a
b = b.strip()
if a.endswith((":", ";", "/")) or b.startswith((":", ";", "/")):
return f"{a} {b}"
return f"{a} : {b}"
def subfield_values_from_fields(
fields: Iterable[DataField],
code: str,
) -> List[str]:
"""All subfield values with given `code` across a list of DataField."""
return [sf.value for df in fields for sf in df.subfields if sf.code == code]
def first_subfield_value_from_fields(
fields: Iterable[DataField],
code: str,
default: Optional[str] = None,
) -> Optional[str]:
"""First subfield value with given `code` across a list of DataField."""
for df in fields:
for sf in df.subfields:
if sf.code == code:
return sf.value
return default
def subfield_value_pairs_from_fields(
fields: Iterable[DataField],
code: str,
) -> List[Tuple[DataField, str]]:
"""
Return (DataField, value) pairs for all subfields with `code`.
Useful if you need to know which field a value came from.
"""
out: List[Tuple[DataField, str]] = []
for df in fields:
for sf in df.subfields:
if sf.code == code:
out.append((df, sf.value))
return out
def book_from_marc(rec: MarcRecord) -> BookData:
# PPN from controlfield 001
ppn = controlfield_value(rec, "001")
# Title = 245 $a + 245 $b (if present)
t_a = first_subfield_value(rec, "245", "a")
t_b = first_subfield_value(rec, "245", "b")
title = _smart_join_title(t_a, t_b) if t_a else None
# Signature = 924 where $9 == "Frei 129" → take that field's $g
frei_fields = find_datafields_with_subfields(
rec, "924", where_all={"9": "Frei 129"}
)
signature = first_subfield_value_from_fields(frei_fields, "g")
# Year = 264 $c (prefer ind2="1" publication; fallback to any 264)
year = first_subfield_value(rec, "264", "c", ind2="1") or first_subfield_value(
rec, "264", "c"
)
isbn = subfield_values(rec, "020", "a")
return BookData(
ppn=ppn,
title=title,
signature=signature,
edition=first_subfield_value(rec, "250", "a"),
year=year,
pages=first_subfield_value(rec, "300", "a"),
publisher=first_subfield_value(rec, "264", "b"),
isbn=isbn,
)
class SWB:
def __init__(self):
self.url = "https://sru.k10plus.de/opac-de-627!rec=1?version=1.1&operation=searchRetrieve&query={}&maximumRecords=10&recordSchema=marcxml"
self.bib_id = 20735
def get(self, query_args: Iterable[str]) -> List[Record]:
# if any query_arg ends with =, remove it
query_args = [arg for arg in query_args if not arg.endswith("=")]
query = "+and+".join(query_args)
query = query.replace(" ", "%20").replace("&", "%26")
url = self.url.format(query)
print("Fetching from SWB:", url)
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Accept": "application/xml",
"Accept-Charset": "latin1,utf-8;q=0.7,*;q=0.3",
}
response = requests.get(url, headers=headers)
if response.status_code != 200:
raise Exception(f"Error fetching data from SWB: {response.status_code}")
# print(response.text)
data = response.content
# extract top-level response
response = parse_search_retrieve_response(data)
return response.records
def getBooks(self, query_args: Iterable[str]) -> List[BookData]:
records: List[Record] = self.get(query_args)
books: List[BookData] = []
title = query_args[1].split("=")[1]
# print(len(records), "records found")
for rec in records:
book = book_from_marc(rec.recordData)
books.append(book)
books = [
b for b in books if b.title and b.title.lower().startswith(title.lower())
]
return books

View File

@@ -1,17 +1,18 @@
import sys
from typing import Any, Optional, Union
import loguru
import requests
from bs4 import BeautifulSoup
# import sleep_and_retry decorator to retry requests
from ratelimit import limits, sleep_and_retry
from typing import Union, Any, Optional
from src.logic.dataclass import BookData
from src import LOG_DIR
from src.logic.dataclass import BookData
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, level="INFO")
@@ -20,7 +21,6 @@ log.add(f"{LOG_DIR}/application.log", rotation="1 MB", retention="10 days")
# logger.add(sys.stderr, format="{time} {level} {message}", level="INFO")
API_URL = "https://rds.ibs-bw.de/phfreiburg/opac/RDSIndexrecord/{}/"
PPN_URL = "https://rds.ibs-bw.de/phfreiburg/opac/RDSIndex/Search?type0%5B%5D=allfields&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=au&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=ti&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=ct&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=isn&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=ta&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=co&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=py&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=pp&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=pu&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=si&lookfor0%5B%5D={}&join=AND&bool0%5B%5D=AND&type0%5B%5D=zr&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=cc&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND"
BASE = "https://rds.ibs-bw.de"
@@ -111,21 +111,8 @@ class WebRequest:
locations = soup.find_all("div", class_="col-xs-12 rds-dl RDS_LOCATION")
if locations:
for location in locations:
item_location = location.find(
"div", class_="col-xs-12 col-md-7 col-lg-8 rds-dl-panel"
).text.strip()
log.debug(f"Item location: {item_location}")
if self.use_any:
pre_tag = soup.find_all("pre")
if pre_tag:
for tag in pre_tag:
data = tag.text.strip()
return_data.append(data)
return return_data
else:
log.error("No <pre> tag found")
raise ValueError("No <pre> tag found")
elif f"Semesterapparat-{self.apparat}" in item_location:
if "1. OG Semesterapparat" in location.text:
log.success("Found Semesterapparat, adding entry")
pre_tag = soup.find_all("pre")
return_data = []
if pre_tag:
@@ -137,10 +124,36 @@ class WebRequest:
log.error("No <pre> tag found")
return return_data
else:
log.error(
f"Signature {self.signature} not found in {item_location}"
)
# return_data = []
item_location = location.find(
"div", class_="col-xs-12 col-md-7 col-lg-8 rds-dl-panel"
).text.strip()
log.debug(f"Item location: {item_location}")
if self.use_any:
pre_tag = soup.find_all("pre")
if pre_tag:
for tag in pre_tag:
data = tag.text.strip()
return_data.append(data)
return return_data
else:
log.error("No <pre> tag found")
raise ValueError("No <pre> tag found")
elif f"Semesterapparat-{self.apparat}" in item_location:
pre_tag = soup.find_all("pre")
return_data = []
if pre_tag:
for tag in pre_tag:
data = tag.text.strip()
return_data.append(data)
return return_data
else:
log.error("No <pre> tag found")
return return_data
else:
log.error(
f"Signature {self.signature} not found in {item_location}"
)
# return_data = []
return return_data

View File

@@ -9,7 +9,7 @@ from bs4 import BeautifulSoup
from docx import Document
from src import LOG_DIR
from src.backend import Semester
from src.backend.semester import Semester
from src.logic.openai import name_tester, run_shortener, semester_converter
log = loguru.logger
@@ -18,7 +18,6 @@ log.add(sys.stdout, level="INFO")
log.add(f"{LOG_DIR}/application.log", rotation="1 MB", retention="10 days")
letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
@@ -111,6 +110,7 @@ class SemapDocument:
else:
self.title_suggestions = []
pass
@property
def renameSemester(self) -> None:
if ", Dauer" in self.semester:
@@ -141,8 +141,8 @@ def word_docx_to_csv(path: str) -> list[pd.DataFrame]:
text = text.replace("\n", "")
row_data.append(text)
if text == "Ihr Fach:":
row_data.append(get_fach(path))
# if text == "Ihr Fach:":
# row_data.append(get_fach(path))
data.append(row_data)
df = pd.DataFrame(data)
df.columns = df.iloc[0]
@@ -265,7 +265,7 @@ def elsa_word_to_csv(path: str):
return tuple_to_dict(data, doctype), doctype
def word_to_semap(word_path: str) -> SemapDocument:
def word_to_semap(word_path: str, ai: bool = True) -> SemapDocument:
log.info("Parsing Word Document {}", word_path)
semap = SemapDocument()
df = word_docx_to_csv(word_path)
@@ -286,8 +286,9 @@ def word_to_semap(word_path: str) -> SemapDocument:
appdata = {keys[i]: keys[i + 1] for i in range(0, len(keys), 2)}
semap.title = appdata["Veranstaltung:"]
semap.semester = appdata["Semester:"]
semap.renameSemester
semap.nameSetter
if ai:
semap.renameSemester
semap.nameSetter
books = df[2]
booklist = []
@@ -309,7 +310,5 @@ def word_to_semap(word_path: str) -> SemapDocument:
if __name__ == "__main__":
else_df = elsa_word_to_csv(
"C:/Users/aky547/Desktop/ELSA_Bestellung Scann Der Westen und der Rest.docx"
)
else_df = word_to_semap("C:/Users/aky547/Desktop/semap/db/temp/tmpzsz_hgdr.docx")
print(else_df)

BIN
src/sounds/ding.mp3 Normal file

Binary file not shown.

View File

@@ -1,23 +1,21 @@
import os
import sys
import loguru
from PySide6 import QtWidgets
from src import Icon, settings as config
from src import LOG_DIR, Icon
from src import settings as config
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, level="INFO")
log.add(f"{LOG_DIR}/application.log", rotation="1 MB", retention="10 days")
empty_signature = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
<html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style
@@ -47,6 +45,7 @@ class Mail_Dialog(QtWidgets.QDialog, MailPreviewDialog):
app_subject,
prof_name,
prof_mail,
accepted_books=None,
parent=None,
default_mail="Information zum Semesterapparat",
):
@@ -58,6 +57,7 @@ class Mail_Dialog(QtWidgets.QDialog, MailPreviewDialog):
self.appname = app_name
self.subject = app_subject
self.profname = prof_name
self.books = accepted_books if accepted_books is not None else []
self.mail_data = ""
self.signature = self.determine_signature()
self.prof_mail = prof_mail
@@ -148,6 +148,14 @@ Tel.: 0761/682-778 | 07617682-545"""
AppSubject=self.subject,
greeting=self.get_greeting(),
signature=self.signature,
newEditions="<br>".join(
[
f"{book.title} von {book.author} (ISBN: {book.isbn}, Auflage: {book.edition}, In Bibliothek: {'ja' if getattr(book, 'library_location', 1) == 1 else 'nein'})"
for book in self.books
]
)
if self.books
else "keine neuen Auflagen gefunden",
)
self.mail_body.setHtml(mail_html)

View File

@@ -0,0 +1,91 @@
from typing import List, Optional, Set, Union
import re
from PySide6 import QtCore
from PySide6.QtWidgets import QDialog, QPushButton, QVBoxLayout
from qtqdm import Qtqdm, QtqdmProgressBar
from src.logic import BookData
from src.logic.lehmannsapi import LehmannsClient
from src.logic.swb import SWB
class CheckThread(QtCore.QThread):
updateSignal = QtCore.Signal()
total_entries_signal = QtCore.Signal(int)
resultSignal = QtCore.Signal(str)
etaSignal = QtCore.Signal(dict)
startSignal = QtCore.Signal()
progress = QtCore.Signal(dict)
def __init__(self, items: list[BookData]):
super().__init__()
self.items: List[BookData] = items
self.results: List[tuple[BookData, List[BookData]]] = []
self.total_entries_signal.emit(len(items))
def _update_callback(self, status):
self.progress.emit(status)
def run(self):
tqdm_object = Qtqdm(
range(len(self.items)),
unit_scale=True,
)
for i in tqdm_object:
book: BookData = self.items[i]
author = (
book.author.split(";")[0].replace(" ", "")
if ";" in book.author
else book.author.replace(" ", "")
)
# title = book.title.split(":")[0].strip()
# remove trailing punctuation from title
title = book.title.rstrip(" .:,;!?")
response: list[BookData] = []
response = SWB().getBooks(
[
"pica.bib=20735",
f"pica.tit={title.split(':')[0].strip()}",
# f"pica.per={author}",
]
)
# in the response, remove the entry with the same ppn
response = [entry for entry in response if entry.ppn != book.ppn]
for respo in response:
respo.link = "SWB"
with LehmannsClient() as client:
results = client.search_by_title(title, strict=True)
client.enrich_pages(results)
if not results:
continue
for res in results:
response.append(BookData().from_LehmannsSearchResult(res))
if response == []:
continue
# check results if lehmanns has a result with the same isbn from the results of swb. if so, if we have a signature, remove, else keep
response = filter_prefer_swb(response)
result = (book, response)
self.results.append(result)
class ProgressDialog(QDialog):
def __init__(self, items: list):
super().__init__()
self.setWindowTitle("Progress")
self.setModal(True)
layout = QVBoxLayout(self)
self.progress_bar = QtqdmProgressBar(self)
self.items: List[BookData] = items
layout.addWidget(self.progress_bar)
self.results: List[tuple[BookData, List[BookData]]] = []
self.start_button = QPushButton("Start Progress", self)
self.start_button.hide()
self.start_button.clicked.connect(self.start)
layout.addWidget(self.start_button)
def start(self):

View File

@@ -339,14 +339,22 @@
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="avail_layout"/>
</item>
<item>
<widget class="QLabel" name="label_20">
<property name="text">
<string>Medien werden geprüft</string>
</property>
</widget>
<layout class="QHBoxLayout" name="avail_layout">
<item>
<widget class="QLabel" name="label_20">
<property name="text">
<string>Medien werden geprüft</string>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="progressBar">
<property name="value">
<number>24</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_3">
@@ -1614,6 +1622,11 @@ Einige Angaben müssen ggf angepasst werden</string>
<string>Lehrperson bearbeiten</string>
</property>
</item>
<item>
<property name="text">
<string>Signaturen aktualisieren</string>
</property>
</item>
</widget>
<widget class="QGroupBox" name="admin_action">
<property name="geometry">

View File

@@ -3,7 +3,7 @@
################################################################################
## Form generated from reading UI file 'semesterapparat_ui.ui'
##
## Created by: Qt User Interface Compiler version 6.9.1
## Created by: Qt User Interface Compiler version 6.9.2
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
@@ -20,9 +20,9 @@ from PySide6.QtWidgets import (QAbstractItemView, QAbstractScrollArea, QApplicat
QComboBox, QFormLayout, QFrame, QGridLayout,
QGroupBox, QHBoxLayout, QHeaderView, QLabel,
QLineEdit, QMainWindow, QMenu, QMenuBar,
QPushButton, QSizePolicy, QSpacerItem, QStatusBar,
QTabWidget, QTableWidget, QTableWidgetItem, QToolButton,
QVBoxLayout, QWidget)
QProgressBar, QPushButton, QSizePolicy, QSpacerItem,
QStatusBar, QTabWidget, QTableWidget, QTableWidgetItem,
QToolButton, QVBoxLayout, QWidget)
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
@@ -207,13 +207,19 @@ class Ui_MainWindow(object):
self.avail_layout = QHBoxLayout()
self.avail_layout.setObjectName(u"avail_layout")
self.horizontalLayout_5.addLayout(self.avail_layout)
self.label_20 = QLabel(self.gridLayoutWidget_2)
self.label_20.setObjectName(u"label_20")
self.horizontalLayout_5.addWidget(self.label_20)
self.avail_layout.addWidget(self.label_20)
self.progressBar = QProgressBar(self.gridLayoutWidget_2)
self.progressBar.setObjectName(u"progressBar")
self.progressBar.setValue(24)
self.avail_layout.addWidget(self.progressBar)
self.horizontalLayout_5.addLayout(self.avail_layout)
self.line_3 = QFrame(self.gridLayoutWidget_2)
self.line_3.setObjectName(u"line_3")
@@ -618,6 +624,7 @@ class Ui_MainWindow(object):
self.select_action_box.addItem("")
self.select_action_box.addItem("")
self.select_action_box.addItem("")
self.select_action_box.addItem("")
self.select_action_box.setObjectName(u"select_action_box")
self.select_action_box.setGeometry(QRect(60, 30, 181, 22))
self.admin_action = QGroupBox(self.admin)
@@ -973,6 +980,7 @@ class Ui_MainWindow(object):
self.select_action_box.setItemText(0, QCoreApplication.translate("MainWindow", u"Nutzer anlegen", None))
self.select_action_box.setItemText(1, QCoreApplication.translate("MainWindow", u"Nutzer bearbeiten", None))
self.select_action_box.setItemText(2, QCoreApplication.translate("MainWindow", u"Lehrperson bearbeiten", None))
self.select_action_box.setItemText(3, QCoreApplication.translate("MainWindow", u"Signaturen aktualisieren", None))
self.admin_action.setTitle(QCoreApplication.translate("MainWindow", u"GroupBox", None))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.admin), QCoreApplication.translate("MainWindow", u"Admin", None))

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -7,16 +7,23 @@ import time
import webbrowser
from datetime import datetime
from pathlib import Path
from typing import Any, Union
from typing import Any, List, Optional, Tuple, Union
import loguru
from natsort import natsorted
from PySide6 import QtCore, QtGui, QtWidgets
from PySide6.QtCore import QThread, Qt
from PySide6.QtCore import QThread
from PySide6.QtGui import QRegularExpressionValidator
from PySide6.QtMultimedia import QAudioOutput, QMediaPlayer
from src import LOG_DIR, Icon
from src.backend import AvailChecker, BookGrabber, Database, DocumentationThread
from src.backend import (
AvailChecker,
BookGrabber,
Database,
DocumentationThread,
NewEditionCheckerThread,
)
from src.backend.create_file import recreateFile
from src.backend.delete_temp_contents import delete_temp_contents as tempdelete
from src.backend.semester import Semester
@@ -51,7 +58,11 @@ from src.ui.widgets import (
ElsaDialog,
FilePicker,
MessageCalendar,
NewEditionChecker,
NewEditionCheckSelector,
SearchStatisticPage,
UpdaterThread,
UpdateSignatures,
UserCreate,
)
@@ -65,7 +76,7 @@ log.add(
rotation="1 day",
retention="1 month",
)
log.critical("UI started")
log.success("UI started")
valid_input = (0, 0, 0, 0, 0, 0)
@@ -189,6 +200,9 @@ class Ui(Ui_Semesterapparat):
self.btn_reserve.hide()
self.label_20.hide()
self.line_3.hide()
self.progressBar.setValue(0)
self.progressBar.hide()
self.progressBar.setMinimum(0)
self.avail_status.hide()
self.chkbx_show_del_media.hide()
self.automation_add_selected_books.hide()
@@ -238,6 +252,7 @@ class Ui(Ui_Semesterapparat):
self.availChecker = None
self.mail_thread = None
self.autoGrabber = None
self.newEditionChecker = NewEditionCheckerThread()
self.elsatab.setLayout(QtWidgets.QVBoxLayout())
self.search_statistics.setLayout(QtWidgets.QVBoxLayout())
@@ -252,6 +267,9 @@ class Ui(Ui_Semesterapparat):
self.steps.hide()
self.player = QMediaPlayer()
self.audio_output = QAudioOutput()
self.valid_check_semester.clicked.connect(self.display_valid_semester) # type:ignore
def create_doc(self):
@@ -295,6 +313,9 @@ class Ui(Ui_Semesterapparat):
elif self.select_action_box.currentText() == "Lehrperson bearbeiten":
self.setWidget(EditProf())
self.admin_action.setTitle("Lehrperson bearbeiten")
elif self.select_action_box.currentText() == "Signaturen aktualisieren":
self.setWidget(UpdateSignatures())
self.admin_action.setTitle("Signaturen aktualisieren")
else:
self.hideWidget()
self.admin_action.setTitle("")
@@ -312,6 +333,12 @@ class Ui(Ui_Semesterapparat):
tempdelete()
sys.exit()
def play_sound(self, sound_file: str):
self.player.setAudioOutput(self.audio_output)
self.audio_output.setVolume(50)
self.player.setSource(QtCore.QUrl.fromLocalFile(f"src/sounds/{sound_file}"))
self.player.play()
def get_apparats(self):
alist = self.db.getAllAparats(deleted=0)
alist = natsorted(alist, key=lambda x: x.appnr, reverse=True)
@@ -399,7 +426,7 @@ class Ui(Ui_Semesterapparat):
else:
return f"WiSe {currentYear}/{currentYear + 1}"
def open_apparat(self, apparat):
def open_apparat(self, apparat: Union[int, str]):
if self.load_app_data(apparat):
# change tab focus to tab 0
self.tabWidget.setCurrentIndex(0)
@@ -608,17 +635,17 @@ class Ui(Ui_Semesterapparat):
self.prof_mail.setText(data.mail)
self.app_name.setFocus()
def get_index_of_value(self, table_widget, value):
def get_index_of_value(self, table_widget: QtWidgets.QTableWidget, value: str):
for i in range(table_widget.rowCount()):
for j in range(table_widget.columnCount()):
if (
table_widget.item(i, j) is not None
and table_widget.item(i, j).text() == value
and table_widget.item(i, j).text() == value # type: ignore
):
return i, j
return (None, None)
def load_app_data(self, app_id=None):
def load_app_data(self, app_id: Optional[Union[int, str]] = None):
self.cancel_active_selection.setEnabled(True)
self.add_medium.setEnabled(True)
for child in self.app_group_box.findChildren(QtWidgets.QToolButton):
@@ -848,8 +875,9 @@ class Ui(Ui_Semesterapparat):
def update_app_media_list(self):
deleted = 0 if not self.chkbx_show_del_media.isChecked() else 1
app_id = self.active_apparat
app_id = self.db.getId(self.app_name.text())
prof_id = self.db.getProfId(self.profdata)
print(app_id, prof_id)
books: list[dict[int, BookData, int]] = self.db.getBooks(
app_id, prof_id, deleted
)
@@ -889,10 +917,15 @@ class Ui(Ui_Semesterapparat):
3,
QtWidgets.QTableWidgetItem(book_data.author),
)
self.tableWidget_apparat_media.setItem(
self.tableWidget_apparat_media.rowCount() - 1,
6,
QtWidgets.QTableWidgetItem(book_data.link),
label = QtWidgets.QLabel(f'<a href="{book_data.link}">Katalog</a>')
label.setOpenExternalLinks(True)
label.setTextFormat(QtCore.Qt.TextFormat.RichText)
label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
label.setTextInteractionFlags(
QtCore.Qt.TextInteractionFlag.TextBrowserInteraction
)
self.tableWidget_apparat_media.setCellWidget(
self.tableWidget_apparat_media.rowCount() - 1, 6, label
)
if availability == 1:
# display green checkmark at column 4 in the row
@@ -1099,7 +1132,9 @@ class Ui(Ui_Semesterapparat):
log.info("File selected: {}, {}", file_name, file_location)
if file_location == "Database":
# create warning, then return
self.db.recreateFile(file_name, self.active_apparat, filetype=file_type)
file = self.db.recreateFile(
file_name, self.active_apparat, filetype=file_type
)
if file_type == "pdf":
# Todo: implement parser here
self.confirm_popup("PDF Dateien werden nicht unterstützt!", title="Fehler")
@@ -1176,6 +1211,7 @@ class Ui(Ui_Semesterapparat):
for runner in self.bookGrabber:
if not runner.isRunning():
runner.deleteLater()
self.bookGrabber.remove(runner)
# #log.debug("Checking file")
# get active app_id and prof_id
self.tableWidget_apparate.setEnabled(False)
@@ -1434,10 +1470,14 @@ class Ui(Ui_Semesterapparat):
contact_action = menu.addAction("Kontaktieren")
delete_action = menu.addAction("Löschen")
remind_action = menu.addAction("Erinnerung")
new_edition_check = menu.addAction("Auf Neuauflagen prüfen")
menu.addAction(extend_action)
menu.addActions([contact_action, delete_action, remind_action])
menu.addActions(
[contact_action, delete_action, remind_action, new_edition_check]
)
extend_action.triggered.connect(self.extend_apparat)
remind_action.triggered.connect(self.reminder)
new_edition_check.triggered.connect(self.check_new_editions)
# convert point to row and column
row = self.tableWidget_apparate.rowAt(position.y())
column = self.tableWidget_apparate.columnAt(position.x())
@@ -1454,6 +1494,79 @@ class Ui(Ui_Semesterapparat):
)
menu.exec(self.tableWidget_apparate.mapToGlobal(position))
def update_status(self, curr, total):
self.avail_status.show()
self.label_20.show()
self.progressBar.show()
self.avail_status.setText(f"{curr}/{total}")
self.progressBar.setValue(curr)
if curr == total:
self.avail_status.hide()
self.label_20.hide()
self.progressBar.hide()
self.progressBar.setValue(0)
self.avail_status.setText("0/0")
def check_new_editions(self):
# create a dialog that asks "Prof oder Apparat" with a button for each. based on that either search through the books of the apparat, or all books associated with the prof
selector = NewEditionCheckSelector()
selector.exec()
pick = selector.selection
app_id = self.tableWidget_apparate.item(
self.tableWidget_apparate.currentRow(), 0
).text()
prof_id: int = self.db.getProfIDByApparat(app_id)
app_name = self.tableWidget_apparate.item(
self.tableWidget_apparate.currentRow(), 1
).text()
subject = self.tableWidget_apparate.item(
self.tableWidget_apparate.currentRow(), 4
).text()
if pick == "professor":
books = self.db.getBooksByProfId(prof_id)
app_name = "Sammelmail"
app_id = ", ".join(
[str(app.appnr) for app in self.db.getApparatsByProf(prof_id)]
)
else:
books = self.db.getBooks(app_id, prof_id, deleted=0)
books = [book["bookdata"] for book in books]
log.info(f"Checking {len(books)} for new editions")
self.newEditionChecker.entries = books
self.newEditionChecker.finished.connect(self.newEditionChecker.reset)
self.progressBar.setMaximum(len(books))
self.newEditionChecker.updateSignal.connect(self.update_status)
self.newEditionChecker.start()
while self.newEditionChecker.isRunning():
QtWidgets.QApplication.processEvents()
self.play_sound("ding.mp3")
results = self.newEditionChecker.results
if results == []:
return
log.info(f"Found {len(results)} possible new editions - opening dialog")
newEditionChecker = NewEditionChecker(results=results)
newEditionChecker.exec()
accepted_books = newEditionChecker.accepted_books
# print(accepted_books)
if accepted_books == []:
return
self.mail_thread = Mail_Dialog(
prof_name=self.db.getSpecificProfData(prof_id, ["fullname"]),
prof_mail=self.db.getProfMailById(prof_id),
app_id=app_id,
app_name=app_name,
app_subject=subject,
accepted_books=accepted_books,
default_mail="Neuauflagen für Semesterapparat",
)
self.mail_thread.show()
def reminder(self):
log.info("Opening reminder dialog")
reminder = ReminderDialog()
@@ -1540,8 +1653,45 @@ class Ui(Ui_Semesterapparat):
menu.exec(self.tableWidget_apparat_media.mapToGlobal(position)) # type: ignore
def update_data(self):
# TODO: use link in table, parse data and if needed, update location / signature
pass
signatures = [
self.tableWidget_apparat_media.item(row, 1).text()
for row in range(self.tableWidget_apparat_media.rowCount())
] # type: ignore
prof_id = self.db.getProfId(self.profdata) # type: ignore
app_id = self.active_apparat
books: List[Tuple[int, BookData]] = []
for signature in signatures:
book = self.db.getBookBasedOnSignature(
app_id=app_id,
signature=signature,
prof_id=prof_id,
)
book_id = self.db.getBookIdBasedOnSignature(
self.active_apparat,
prof_id,
signature,
)
books.append((book_id, book))
# self.autoUpdater.entries = books
# self.autoUpdater.finished.connect(self.autoUpdater.reset)
self.updater = UpdaterThread()
u_books = []
for book_id, book in books:
u_books.append({"id": book_id, "bookdata": book})
self.updater.books = u_books
self.progressBar.setMaximum(len(books))
self.updater.finished.connect(self.updater.deleteLater)
self.updater.finished.connect(self.update_app_media_list)
self.updater.currtot.connect(self.update_status)
self.updater.start()
# ppn = book.link.split("kid=")[-1]
# result = cat.get_book(ppn)
# if result:
# book.signature = result.signature
# book.in_apparat = True
# print(book)
# self.db.updateBookdata(book_id, book)
# self.update_app_media_list()
def copy_to_apparat(self):
selected_rows = self.tableWidget_apparat_media.selectionModel().selectedRows() # type: ignore
@@ -1639,12 +1789,12 @@ class Ui(Ui_Semesterapparat):
).text()
prof_id = self.db.getProfId(self.profdata)
data = self.db.getBookBasedOnSignature(
app_id=self.active_apparat,
app_id=self.db.getId(self.app_name.text()),
signature=book,
prof_id=prof_id,
)
book_id = self.db.getBookIdBasedOnSignature(
self.active_apparat,
self.db.getId(self.app_name.text()),
prof_id,
book,
)
@@ -1732,10 +1882,18 @@ class Ui(Ui_Semesterapparat):
else:
return
def __contact_dialog(self, apparat, location: tuple | str, mail=None, pid=""):
def __contact_dialog(
self,
apparat,
location: tuple | str,
mail=None,
pid="",
accepted_books=None,
app_id="",
):
log.debug(
"Got these values apparat: {}, location: {}, mail: {}, pid: {}".format(
apparat, location, mail, pid
"Got these values apparat: {}, location: {}, mail: {}, pid: {}, accepted_books: {}, app_id: {}".format(
apparat, location, mail, pid, accepted_books, app_id
)
)
@@ -1798,10 +1956,16 @@ class Ui(Ui_Semesterapparat):
if state == 1:
log.debug("Deleting apparat {}", selected_apparat_id)
pid = self.db.getProfIDByApparat(selected_apparat_id)
self.db.deleteApparat(selected_apparat_id, Semester().value)
apparat = Apparat(
appnr=int(selected_apparat_id),
name=self.tableWidget_apparate.item(
self.tableWidget_apparate.currentRow(), 1
).text(),
)
self.db.deleteApparat(apparat=apparat, semester=Semester().value)
# delete the corresponding entry from self.apparats
for apparat in self.apparats:
if apparat[4] == int(selected_apparat_id):
if apparat.appnr == int(selected_apparat_id):
self.apparats.remove(apparat)
break
self.old_apparats = self.apparats
@@ -1842,6 +2006,7 @@ def launch_gui():
) # if that thread uses an event loop
app.aboutToQuit.connect(aui.docu.terminate) # our new slot
app.aboutToQuit.connect(aui.docu.wait)
app.aboutToQuit.connect(aui.newEditionChecker.terminate)
atexit.register(tempdelete)
# atexit.register(aui.validate_thread.quit)
# atexit.register(aui.docu.quit)

View File

@@ -1,9 +1,7 @@
__all__ = [
"LoginWidget",
"RegisterWidget",
"DataQtGraph",
"StatusWidget",
"FilePicker",
"DataGraph",
"CalendarEntry",
"MessageCalendar",
"SearchStatisticPage",
@@ -12,16 +10,23 @@ __all__ = [
"EditUser",
"EditProf",
"IconWidget",
"NewEditionChecker",
"NewEditionCheckSelector",
"UpdateSignatures",
"UpdaterThread"
]
from .admin_create_user import UserCreate
from .admin_edit_prof import EditProf
from .admin_edit_user import EditUser
from .calendar_entry import CalendarEntry
from .collapse import StatusWidget
from .elsa_main import ElsaDialog
from .filepicker import FilePicker
from .graph import DataQtGraph
from .calendar_entry import CalendarEntry
from .MessageCalendar import MessageCalendar
from .searchPage import SearchStatisticPage
from .elsa_main import ElsaDialog
from .admin_create_user import UserCreate
from .admin_edit_user import EditUser
from .admin_edit_prof import EditProf
from .iconLine import IconWidget
from .MessageCalendar import MessageCalendar
from .new_edition_check import NewEditionChecker, NewEditionCheckSelector
from .searchPage import SearchStatisticPage
from .signature_update import UpdateSignatures, UpdaterThread

View File

@@ -1,24 +1,26 @@
import os
from .widget_sources.elsa_maindialog_ui import Ui_Dialog
from PySide6 import QtCore, QtWidgets, QtGui
from PySide6.QtGui import QRegularExpressionValidator
from PySide6.QtCore import QDate
from src import Icon
from src.backend import Semester, Database
from src.logic import elsa_word_to_csv, Prof
from src.ui.dialogs import ElsaAddEntry, popus_confirm
from src.ui.widgets import FilePicker, DataQtGraph
from src.backend import recreateElsaFile
import loguru
import sys
from src import LOG_DIR
import loguru
from PySide6 import QtCore, QtGui, QtWidgets
from PySide6.QtCore import QDate
from PySide6.QtGui import QRegularExpressionValidator
from src import LOG_DIR, Icon
from src.backend import Database, Semester, recreateElsaFile
from src.logic import Prof, elsa_word_to_csv
from src.ui.dialogs import ElsaAddEntry, popus_confirm
from src.ui.widgets.filepicker import FilePicker
from src.ui.widgets.graph import DataQtGraph
from .widget_sources.elsa_maindialog_ui import Ui_Dialog
log = loguru.logger
log.remove()
log.add(sys.stdout, level="INFO")
log.add(f"{LOG_DIR}/application.log", rotation="1 MB", retention="10 days")
class ElsaDialog(QtWidgets.QDialog, Ui_Dialog):
def __init__(self):
super().__init__()
@@ -240,7 +242,7 @@ class ElsaDialog(QtWidgets.QDialog, Ui_Dialog):
if prof_id is None:
self.db.createProf(profdata)
prof_id = self.db.getProfId(prof)
prof_id = self.db.getProfId(profdata)
self.profs.append(
"f{}, {}".format(profdata.lastname, profdata.firstname), prof_id
)
@@ -413,6 +415,16 @@ class ElsaDialog(QtWidgets.QDialog, Ui_Dialog):
log.debug(
f"elsa_id: {elsa_id}, prof: {self.elsa_prof.currentText()}, semester: {self.elsa_semester.text()}, date: {self.elsa_date.text()}"
)
self.db.insertElsaFile(
[
{
"name": file.split("/")[-1],
"path": file,
"type": file.split(".")[-1],
}
],
elsa_id,
)
for row in data:
if self.seperateEntries.isChecked():
if ";" in row["pages"]:
@@ -426,6 +438,7 @@ class ElsaDialog(QtWidgets.QDialog, Ui_Dialog):
else:
self.setElsaRow(row)
self.db.addElsaMedia(row, elsa_id)
self.quote_entry.setEnabled(True)
def openDocumentElsa(self):

View File

@@ -20,7 +20,7 @@ class FilePicker:
files, _ = filepicker.getOpenFileNames(
caption="Open file",
directory=self.last_path,
dir=self.last_path,
filter="Unterstützte Dateien (*.docx *.csv *.eml);;Word (*.docx);;CSV Files (*.csv);;Mail (*.eml)",
)
if files:

View File

@@ -0,0 +1,185 @@
from typing import List
from PySide6 import QtWidgets
from PySide6.QtCore import Qt
from src.logic import BookData
from .widget_sources.new_edition_check_book_ui import (
Ui_Dialog as Ui_NewEditionCheckBook,
)
from .widget_sources.new_edition_check_found_result_ui import (
Ui_Dialog as Ui_NewEditionCheckFoundResult,
)
from .widget_sources.new_edition_check_ui import Ui_Dialog as Ui_NewEditionCheck
from .widget_sources.new_edition_check_selector_ui import Ui_Dialog as Ui_NewEditionCheckSelector
class NewEditionCheckSelector(QtWidgets.QDialog, Ui_NewEditionCheckSelector):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
self.setWindowTitle("Neuauflagen prüfen")
self.btn_apparat.clicked.connect(self.select_apparat)
self.btn_prof.clicked.connect(self.select_professor)
self.selection = None
def select_apparat(self):
self.selection = "apparat"
self.accept()
def select_professor(self):
self.selection = "professor"
self.accept()
class NewEditionCheckFoundResult(QtWidgets.QDialog, Ui_NewEditionCheckFoundResult):
def __init__(self, book: BookData, parent=None):
assert isinstance(book, BookData)
super().__init__(parent)
self.setupUi(self)
self.book = book
self.line_ppn.setText(self.book.ppn if self.book.ppn else "")
self.line_title.setText(self.book.title if self.book.title else "")
self.line_signature.setText(self.book.signature if self.book.signature else "")
self.line_edition.setText(self.book.edition if self.book.edition else "")
self.line_publisher.setText(self.book.publisher if self.book.publisher else "")
self.line_year.setText(self.book.year if self.book.year else "")
self.line_pages.setText(self.book.pages if self.book.pages else "")
link = self.book.link if self.book.link else ""
if self.book.link != "SWB":
link = f"<a href='{link}'>Lehmanns</a>"
self.line_source.setText(link)
self.line_source.setOpenExternalLinks(True)
self.line_source.setTextFormat(Qt.TextFormat.RichText)
self.line_source.setTextInteractionFlags(
Qt.TextInteractionFlag.TextBrowserInteraction
)
self.line_isbn.setText(
", ".join(self.book.isbn)
if isinstance(self.book.isbn, list)
else self.book.isbn
)
if (
self.book.link == "SWB"
and self.book.signature is not None
and self.book.signature != ""
):
self.in_library.setText(
"Diese Neuauflage ist bereits in der Bibliothek vorhanden."
)
self.book.library_location = 1
pass
class NewEditionCheckBook(QtWidgets.QDialog, Ui_NewEditionCheckBook):
def __init__(self, book: BookData, responses: List[BookData], parent=None):
super().__init__(parent)
self.setupUi(self)
self.book = book
self.accepted_books = []
self.responses = responses
self.line_author.setText(self.book.author)
self.line_title.setText(self.book.title)
self.line_ppn.setText(self.book.ppn if self.book.ppn else "")
self.line_signature.setText(self.book.signature if self.book.signature else "")
self.line_edition.setText(self.book.edition if self.book.edition else "")
self.line_publisher.setText(self.book.publisher if self.book.publisher else "")
self.line_year.setText(self.book.year if self.book.year else "")
self.line_pages.setText(self.book.pages if self.book.pages else "")
self.line_isbn.setText(
", ".join(self.book.isbn)
if isinstance(self.book.isbn, list)
else self.book.isbn
)
for _ in range(self.stackedWidget.count()):
widget = self.stackedWidget.widget(0)
self.stackedWidget.removeWidget(widget)
widget.deleteLater()
for response in self.responses:
self.stackedWidget.addWidget(
NewEditionCheckFoundResult(parent=self, book=response)
)
self.label_book_index.setText(f"1 / {self.stackedWidget.count()}")
self.btn_next.clicked.connect(self.next)
self.btn_prev.clicked.connect(self.previous)
def next(self):
index = self.stackedWidget.currentIndex()
if index < self.stackedWidget.count() - 1:
index += 1
self.stackedWidget.setCurrentIndex(index)
self.label_book_index.setText(f"{index + 1} / {self.stackedWidget.count()}")
if index == self.stackedWidget.count() - 1:
self.btn_next.hide()
def previous(self):
index = self.stackedWidget.currentIndex()
if index > 0:
index -= 1
self.stackedWidget.setCurrentIndex(index)
self.label_book_index.setText(f"{index + 1} / {self.stackedWidget.count()}")
if index < self.stackedWidget.count() - 1:
self.btn_next.show()
pass
class NewEditionChecker(QtWidgets.QDialog, Ui_NewEditionCheck):
def __init__(self, results, parent=None):
super().__init__(parent)
self.setupUi(self)
self.results = results
self.setWindowTitle("Prüfung auf Neuauflagen")
# remove pages from stacked widget
for _ in range(self.stackedWidget.count()):
widget = self.stackedWidget.widget(0)
self.stackedWidget.removeWidget(widget)
widget.deleteLater()
for resultset in self.results:
book, responses = resultset
self.stackedWidget.addWidget(
NewEditionCheckBook(parent=self, book=book, responses=responses)
)
self.accepted_books = []
self.stackedWidget.setCurrentIndex(0)
self.progressBar.setMaximum(len(self.results))
self.progressBar.setValue(1)
self.btn_next.clicked.connect(self.next)
self.btn_prev.clicked.connect(self.previous)
self.btn_finish.hide()
self.btn_finish.clicked.connect(self.accept)
self.btn_prev.hide()
def next(self):
index = self.stackedWidget.currentIndex()
if index < self.stackedWidget.count() - 1:
index += 1
self.stackedWidget.setCurrentIndex(index)
self.progressBar.setValue(index + 1)
self.btn_prev.show()
if index == self.stackedWidget.count() - 1:
self.btn_next.hide()
self.btn_finish.show()
def previous(self):
index = self.stackedWidget.currentIndex()
if index > 0:
index -= 1
self.stackedWidget.setCurrentIndex(index)
self.progressBar.setValue(index + 1)
def accept(self) -> None:
print("finished checking for new editions")
accepted_books = []
for i in range(self.stackedWidget.count()):
book_widget = self.stackedWidget.widget(i)
if isinstance(book_widget, NewEditionCheckBook):
for j in range(book_widget.stackedWidget.count()):
found_widget = book_widget.stackedWidget.widget(j)
if isinstance(found_widget, NewEditionCheckFoundResult):
if found_widget.checkBox.isChecked():
accepted_books.append(found_widget.book)
super().accept()
print("accepted", len(accepted_books), "new editions")
self.accepted_books = accepted_books

View File

@@ -53,6 +53,8 @@ class SearchStatisticPage(QtWidgets.QDialog, Ui_Dialog):
self.btn_notify_for_deletion.clicked.connect(self.notify_for_deletion)
self.btn_notify_for_deletion.setEnabled(False)
self.btn_del_select_apparats.setEnabled(False)
self.btn_extendSelection.clicked.connect(self.mass_extend_apparats)
self.btn_extendSelection.setEnabled(False)
self.tableWidget.resizeColumnsToContents()
self.tableWidget.resizeRowsToContents()
self.db = Database()
@@ -72,6 +74,23 @@ class SearchStatisticPage(QtWidgets.QDialog, Ui_Dialog):
self.search_by_title.returnPressed.connect(self.search_book)
self.populate_tab()
def mass_extend_apparats(self):
extend = ApparatExtendDialog()
extend.exec()
if extend.result() == QtWidgets.QDialog.DialogCode.Accepted:
data = extend.get_data()
log.debug(data)
for i in range(self.tableWidget.rowCount()):
if self.tableWidget.cellWidget(i, 0).isChecked():
app_name = self.tableWidget.item(i, 1).text()
app_id = self.db.getApparatId(app_name)
self.db.setNewSemesterDate(app_id, data["semester"], data["dauerapp"])
# remove the row
self.tableWidget.removeRow(i)
self.refreshSignal.emit()
def restore_apparat(self):
selected_rows = self.tableWidget.selectionModel().selectedRows()
apparats = []

View File

@@ -0,0 +1,59 @@
from PySide6 import QtCore, QtWidgets
from src.backend.catalogue import Catalogue
from src.backend.database import Database
from .widget_sources.admin_update_signatures_ui import Ui_Dialog
class UpdaterThread(QtCore.QThread):
progress = QtCore.Signal(int)
currtot = QtCore.Signal(int, int)
def __init__(self, books=None):
super().__init__()
self.books = books
self.db = Database()
self.catalogue = Catalogue()
def run(self):
total_books = len(self.books)
for index, book in enumerate(self.books):
id = book["id"]
bookdata = book["bookdata"]
ppn = bookdata.link.split("kid=")[-1]
result = self.catalogue.get_book(ppn)
if result:
bookdata.signature = result.signature
print(bookdata)
self.db.updateBookdata(id, bookdata)
else:
print(f"No result for {ppn}")
self.db.deleteBook(id)
self.progress.emit(index + 1)
self.currtot.emit(index + 1, total_books)
class UpdateSignatures(QtWidgets.QDialog, Ui_Dialog):
def __init__(self, parent=None):
super(UpdateSignatures, self).__init__(parent)
self.setupUi(self)
self.setWindowTitle("Updating signatures...")
self.progressBar.setValue(0)
self.pushButton.clicked.connect(self.start)
self.db = Database()
self.catalogue = Catalogue()
def start(self):
books = self.db.getAllBooks()
total_books = len(books)
self.progressBar.setMaximum(total_books)
self.updater = UpdaterThread(books)
self.updater.progress.connect(self.update_progress)
self.updater.start()
def update_progress(self, value):
self.progressBar.setValue(value)
if value >= self.progressBar.maximum():
self.pushButton.setText("Done")
self.pushButton.setEnabled(False)

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Signaturen aktualisieren</string>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="progressBar">
<property name="value">
<number>24</number>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'admin_update_signatures.ui'
##
## Created by: Qt User Interface Compiler version 6.9.2
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
QMetaObject, QObject, QPoint, QRect,
QSize, QTime, QUrl, Qt)
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QFont, QFontDatabase, QGradient, QIcon,
QImage, QKeySequence, QLinearGradient, QPainter,
QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QApplication, QDialog, QProgressBar, QPushButton,
QSizePolicy, QVBoxLayout, QWidget)
class Ui_Dialog(object):
def setupUi(self, Dialog):
if not Dialog.objectName():
Dialog.setObjectName(u"Dialog")
Dialog.resize(400, 300)
self.verticalLayout = QVBoxLayout(Dialog)
self.verticalLayout.setObjectName(u"verticalLayout")
self.pushButton = QPushButton(Dialog)
self.pushButton.setObjectName(u"pushButton")
self.verticalLayout.addWidget(self.pushButton)
self.progressBar = QProgressBar(Dialog)
self.progressBar.setObjectName(u"progressBar")
self.progressBar.setValue(24)
self.verticalLayout.addWidget(self.progressBar)
self.retranslateUi(Dialog)
QMetaObject.connectSlotsByName(Dialog)
# setupUi
def retranslateUi(self, Dialog):
Dialog.setWindowTitle(QCoreApplication.translate("Dialog", u"Dialog", None))
self.pushButton.setText(QCoreApplication.translate("Dialog", u"Signaturen aktualisieren", None))
# retranslateUi

View File

@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>632</width>
<height>726</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QStackedWidget" name="stackedWidget">
<widget class="QWidget" name="page"/>
<widget class="QWidget" name="page_2"/>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QProgressBar" name="progressBar">
<property name="value">
<number>24</number>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="btn_prev">
<property name="text">
<string>Previous</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_next">
<property name="text">
<string>Next</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_finish">
<property name="text">
<string>Finish</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,182 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>618</width>
<height>637</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<property name="sizeGripEnabled">
<bool>false</bool>
</property>
<property name="modal">
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Ausgewähltes Buch</string>
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_9">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Gefundenes Buch</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_book_index">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>PPN</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="line_ppn"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Titel</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="line_title"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Signatur</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="line_signature"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Auflage</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="line_edition"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Verlag</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="line_publisher"/>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Jahr</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLineEdit" name="line_year"/>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Seiten</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLineEdit" name="line_pages"/>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Autor</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QLineEdit" name="line_author"/>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>ISBN</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QLineEdit" name="line_isbn"/>
</item>
</layout>
</item>
<item row="1" column="1">
<widget class="QStackedWidget" name="stackedWidget">
<widget class="QWidget" name="page"/>
<widget class="QWidget" name="page_2"/>
</widget>
</item>
<item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="btn_prev">
<property name="text">
<string>Previous</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_next">
<property name="text">
<string>Next</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,202 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'new_edition_check_book.ui'
##
## Created by: Qt User Interface Compiler version 6.9.2
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
QMetaObject, QObject, QPoint, QRect,
QSize, QTime, QUrl, Qt)
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QFont, QFontDatabase, QGradient, QIcon,
QImage, QKeySequence, QLinearGradient, QPainter,
QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QApplication, QDialog, QFormLayout, QGridLayout,
QHBoxLayout, QLabel, QLineEdit, QPushButton,
QSizePolicy, QStackedWidget, QWidget)
class Ui_Dialog(object):
def setupUi(self, Dialog):
if not Dialog.objectName():
Dialog.setObjectName(u"Dialog")
Dialog.resize(618, 637)
Dialog.setSizeGripEnabled(False)
Dialog.setModal(False)
self.gridLayout = QGridLayout(Dialog)
self.gridLayout.setObjectName(u"gridLayout")
self.label_10 = QLabel(Dialog)
self.label_10.setObjectName(u"label_10")
self.gridLayout.addWidget(self.label_10, 0, 0, 1, 1)
self.horizontalLayout_2 = QHBoxLayout()
self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
self.label_9 = QLabel(Dialog)
self.label_9.setObjectName(u"label_9")
sizePolicy = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.label_9.sizePolicy().hasHeightForWidth())
self.label_9.setSizePolicy(sizePolicy)
self.horizontalLayout_2.addWidget(self.label_9)
self.label_book_index = QLabel(Dialog)
self.label_book_index.setObjectName(u"label_book_index")
sizePolicy.setHeightForWidth(self.label_book_index.sizePolicy().hasHeightForWidth())
self.label_book_index.setSizePolicy(sizePolicy)
self.horizontalLayout_2.addWidget(self.label_book_index)
self.gridLayout.addLayout(self.horizontalLayout_2, 0, 1, 1, 1)
self.formLayout_2 = QFormLayout()
self.formLayout_2.setObjectName(u"formLayout_2")
self.label = QLabel(Dialog)
self.label.setObjectName(u"label")
self.formLayout_2.setWidget(0, QFormLayout.ItemRole.LabelRole, self.label)
self.line_ppn = QLineEdit(Dialog)
self.line_ppn.setObjectName(u"line_ppn")
self.formLayout_2.setWidget(0, QFormLayout.ItemRole.FieldRole, self.line_ppn)
self.label_2 = QLabel(Dialog)
self.label_2.setObjectName(u"label_2")
self.formLayout_2.setWidget(1, QFormLayout.ItemRole.LabelRole, self.label_2)
self.line_title = QLineEdit(Dialog)
self.line_title.setObjectName(u"line_title")
self.formLayout_2.setWidget(1, QFormLayout.ItemRole.FieldRole, self.line_title)
self.label_3 = QLabel(Dialog)
self.label_3.setObjectName(u"label_3")
self.formLayout_2.setWidget(2, QFormLayout.ItemRole.LabelRole, self.label_3)
self.line_signature = QLineEdit(Dialog)
self.line_signature.setObjectName(u"line_signature")
self.formLayout_2.setWidget(2, QFormLayout.ItemRole.FieldRole, self.line_signature)
self.label_4 = QLabel(Dialog)
self.label_4.setObjectName(u"label_4")
self.formLayout_2.setWidget(3, QFormLayout.ItemRole.LabelRole, self.label_4)
self.line_edition = QLineEdit(Dialog)
self.line_edition.setObjectName(u"line_edition")
self.formLayout_2.setWidget(3, QFormLayout.ItemRole.FieldRole, self.line_edition)
self.label_5 = QLabel(Dialog)
self.label_5.setObjectName(u"label_5")
self.formLayout_2.setWidget(4, QFormLayout.ItemRole.LabelRole, self.label_5)
self.line_publisher = QLineEdit(Dialog)
self.line_publisher.setObjectName(u"line_publisher")
self.formLayout_2.setWidget(4, QFormLayout.ItemRole.FieldRole, self.line_publisher)
self.label_6 = QLabel(Dialog)
self.label_6.setObjectName(u"label_6")
self.formLayout_2.setWidget(5, QFormLayout.ItemRole.LabelRole, self.label_6)
self.line_year = QLineEdit(Dialog)
self.line_year.setObjectName(u"line_year")
self.formLayout_2.setWidget(5, QFormLayout.ItemRole.FieldRole, self.line_year)
self.label_7 = QLabel(Dialog)
self.label_7.setObjectName(u"label_7")
self.formLayout_2.setWidget(6, QFormLayout.ItemRole.LabelRole, self.label_7)
self.line_pages = QLineEdit(Dialog)
self.line_pages.setObjectName(u"line_pages")
self.formLayout_2.setWidget(6, QFormLayout.ItemRole.FieldRole, self.line_pages)
self.label_8 = QLabel(Dialog)
self.label_8.setObjectName(u"label_8")
self.formLayout_2.setWidget(7, QFormLayout.ItemRole.LabelRole, self.label_8)
self.line_author = QLineEdit(Dialog)
self.line_author.setObjectName(u"line_author")
self.formLayout_2.setWidget(7, QFormLayout.ItemRole.FieldRole, self.line_author)
self.label_11 = QLabel(Dialog)
self.label_11.setObjectName(u"label_11")
self.formLayout_2.setWidget(8, QFormLayout.ItemRole.LabelRole, self.label_11)
self.line_isbn = QLineEdit(Dialog)
self.line_isbn.setObjectName(u"line_isbn")
self.formLayout_2.setWidget(8, QFormLayout.ItemRole.FieldRole, self.line_isbn)
self.gridLayout.addLayout(self.formLayout_2, 1, 0, 1, 1)
self.stackedWidget = QStackedWidget(Dialog)
self.stackedWidget.setObjectName(u"stackedWidget")
self.page = QWidget()
self.page.setObjectName(u"page")
self.stackedWidget.addWidget(self.page)
self.page_2 = QWidget()
self.page_2.setObjectName(u"page_2")
self.stackedWidget.addWidget(self.page_2)
self.gridLayout.addWidget(self.stackedWidget, 1, 1, 1, 1)
self.horizontalLayout = QHBoxLayout()
self.horizontalLayout.setObjectName(u"horizontalLayout")
self.btn_prev = QPushButton(Dialog)
self.btn_prev.setObjectName(u"btn_prev")
self.horizontalLayout.addWidget(self.btn_prev)
self.btn_next = QPushButton(Dialog)
self.btn_next.setObjectName(u"btn_next")
self.horizontalLayout.addWidget(self.btn_next)
self.gridLayout.addLayout(self.horizontalLayout, 2, 1, 1, 1)
self.retranslateUi(Dialog)
QMetaObject.connectSlotsByName(Dialog)
# setupUi
def retranslateUi(self, Dialog):
Dialog.setWindowTitle(QCoreApplication.translate("Dialog", u"Dialog", None))
self.label_10.setText(QCoreApplication.translate("Dialog", u"Ausgew\u00e4hltes Buch", None))
self.label_9.setText(QCoreApplication.translate("Dialog", u"Gefundenes Buch", None))
self.label_book_index.setText(QCoreApplication.translate("Dialog", u"TextLabel", None))
self.label.setText(QCoreApplication.translate("Dialog", u"PPN", None))
self.label_2.setText(QCoreApplication.translate("Dialog", u"Titel", None))
self.label_3.setText(QCoreApplication.translate("Dialog", u"Signatur", None))
self.label_4.setText(QCoreApplication.translate("Dialog", u"Auflage", None))
self.label_5.setText(QCoreApplication.translate("Dialog", u"Verlag", None))
self.label_6.setText(QCoreApplication.translate("Dialog", u"Jahr", None))
self.label_7.setText(QCoreApplication.translate("Dialog", u"Seiten", None))
self.label_8.setText(QCoreApplication.translate("Dialog", u"Autor", None))
self.label_11.setText(QCoreApplication.translate("Dialog", u"ISBN", None))
self.btn_prev.setText(QCoreApplication.translate("Dialog", u"Previous", None))
self.btn_next.setText(QCoreApplication.translate("Dialog", u"Next", None))
# retranslateUi

View File

@@ -0,0 +1,145 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>PPN</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="line_ppn"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Titel</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="line_title"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Signatur</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="line_signature"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Auflage</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="line_edition"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Verlag</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="line_publisher"/>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Jahr</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLineEdit" name="line_year"/>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Seiten</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLineEdit" name="line_pages"/>
</item>
<item row="11" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="9" column="1">
<widget class="QCheckBox" name="checkBox">
<property name="text">
<string>Bestellen</string>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Quelle</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QLabel" name="line_source">
<property name="text">
<string/>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>ISBN</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QLineEdit" name="line_isbn"/>
</item>
<item row="10" column="1">
<widget class="QLabel" name="in_library">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,155 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'new_edition_check_found_result.ui'
##
## Created by: Qt User Interface Compiler version 6.9.2
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
QMetaObject, QObject, QPoint, QRect,
QSize, QTime, QUrl, Qt)
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QFont, QFontDatabase, QGradient, QIcon,
QImage, QKeySequence, QLinearGradient, QPainter,
QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QApplication, QCheckBox, QDialog, QFormLayout,
QLabel, QLineEdit, QSizePolicy, QSpacerItem,
QWidget)
class Ui_Dialog(object):
def setupUi(self, Dialog):
if not Dialog.objectName():
Dialog.setObjectName(u"Dialog")
Dialog.resize(400, 300)
self.formLayout = QFormLayout(Dialog)
self.formLayout.setObjectName(u"formLayout")
self.label = QLabel(Dialog)
self.label.setObjectName(u"label")
self.formLayout.setWidget(0, QFormLayout.ItemRole.LabelRole, self.label)
self.line_ppn = QLineEdit(Dialog)
self.line_ppn.setObjectName(u"line_ppn")
self.formLayout.setWidget(0, QFormLayout.ItemRole.FieldRole, self.line_ppn)
self.label_2 = QLabel(Dialog)
self.label_2.setObjectName(u"label_2")
self.formLayout.setWidget(1, QFormLayout.ItemRole.LabelRole, self.label_2)
self.line_title = QLineEdit(Dialog)
self.line_title.setObjectName(u"line_title")
self.formLayout.setWidget(1, QFormLayout.ItemRole.FieldRole, self.line_title)
self.label_3 = QLabel(Dialog)
self.label_3.setObjectName(u"label_3")
self.formLayout.setWidget(2, QFormLayout.ItemRole.LabelRole, self.label_3)
self.line_signature = QLineEdit(Dialog)
self.line_signature.setObjectName(u"line_signature")
self.formLayout.setWidget(2, QFormLayout.ItemRole.FieldRole, self.line_signature)
self.label_4 = QLabel(Dialog)
self.label_4.setObjectName(u"label_4")
self.formLayout.setWidget(3, QFormLayout.ItemRole.LabelRole, self.label_4)
self.line_edition = QLineEdit(Dialog)
self.line_edition.setObjectName(u"line_edition")
self.formLayout.setWidget(3, QFormLayout.ItemRole.FieldRole, self.line_edition)
self.label_5 = QLabel(Dialog)
self.label_5.setObjectName(u"label_5")
self.formLayout.setWidget(4, QFormLayout.ItemRole.LabelRole, self.label_5)
self.line_publisher = QLineEdit(Dialog)
self.line_publisher.setObjectName(u"line_publisher")
self.formLayout.setWidget(4, QFormLayout.ItemRole.FieldRole, self.line_publisher)
self.label_6 = QLabel(Dialog)
self.label_6.setObjectName(u"label_6")
self.formLayout.setWidget(5, QFormLayout.ItemRole.LabelRole, self.label_6)
self.line_year = QLineEdit(Dialog)
self.line_year.setObjectName(u"line_year")
self.formLayout.setWidget(5, QFormLayout.ItemRole.FieldRole, self.line_year)
self.label_7 = QLabel(Dialog)
self.label_7.setObjectName(u"label_7")
self.formLayout.setWidget(6, QFormLayout.ItemRole.LabelRole, self.label_7)
self.line_pages = QLineEdit(Dialog)
self.line_pages.setObjectName(u"line_pages")
self.formLayout.setWidget(6, QFormLayout.ItemRole.FieldRole, self.line_pages)
self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
self.formLayout.setItem(11, QFormLayout.ItemRole.FieldRole, self.verticalSpacer)
self.checkBox = QCheckBox(Dialog)
self.checkBox.setObjectName(u"checkBox")
self.formLayout.setWidget(9, QFormLayout.ItemRole.FieldRole, self.checkBox)
self.label_8 = QLabel(Dialog)
self.label_8.setObjectName(u"label_8")
self.formLayout.setWidget(8, QFormLayout.ItemRole.LabelRole, self.label_8)
self.line_source = QLabel(Dialog)
self.line_source.setObjectName(u"line_source")
self.line_source.setTextFormat(Qt.PlainText)
self.formLayout.setWidget(8, QFormLayout.ItemRole.FieldRole, self.line_source)
self.label_9 = QLabel(Dialog)
self.label_9.setObjectName(u"label_9")
self.formLayout.setWidget(7, QFormLayout.ItemRole.LabelRole, self.label_9)
self.line_isbn = QLineEdit(Dialog)
self.line_isbn.setObjectName(u"line_isbn")
self.formLayout.setWidget(7, QFormLayout.ItemRole.FieldRole, self.line_isbn)
self.in_library = QLabel(Dialog)
self.in_library.setObjectName(u"in_library")
self.formLayout.setWidget(10, QFormLayout.ItemRole.FieldRole, self.in_library)
self.retranslateUi(Dialog)
QMetaObject.connectSlotsByName(Dialog)
# setupUi
def retranslateUi(self, Dialog):
Dialog.setWindowTitle(QCoreApplication.translate("Dialog", u"Dialog", None))
self.label.setText(QCoreApplication.translate("Dialog", u"PPN", None))
self.label_2.setText(QCoreApplication.translate("Dialog", u"Titel", None))
self.label_3.setText(QCoreApplication.translate("Dialog", u"Signatur", None))
self.label_4.setText(QCoreApplication.translate("Dialog", u"Auflage", None))
self.label_5.setText(QCoreApplication.translate("Dialog", u"Verlag", None))
self.label_6.setText(QCoreApplication.translate("Dialog", u"Jahr", None))
self.label_7.setText(QCoreApplication.translate("Dialog", u"Seiten", None))
self.checkBox.setText(QCoreApplication.translate("Dialog", u"Bestellen", None))
self.label_8.setText(QCoreApplication.translate("Dialog", u"Quelle", None))
self.line_source.setText("")
self.label_9.setText(QCoreApplication.translate("Dialog", u"ISBN", None))
self.in_library.setText("")
# retranslateUi

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>475</width>
<height>66</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Soll nur der Apparat geprüft werden, oder sollen alle Medien des Profs geprüft werden?</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="btn_apparat">
<property name="text">
<string>Apparat</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_prof">
<property name="text">
<string>Prof</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'new_edition_check_selector.ui'
##
## Created by: Qt User Interface Compiler version 6.9.2
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
QMetaObject, QObject, QPoint, QRect,
QSize, QTime, QUrl, Qt)
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QFont, QFontDatabase, QGradient, QIcon,
QImage, QKeySequence, QLinearGradient, QPainter,
QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QApplication, QDialog, QHBoxLayout, QLabel,
QPushButton, QSizePolicy, QVBoxLayout, QWidget)
class Ui_Dialog(object):
def setupUi(self, Dialog):
if not Dialog.objectName():
Dialog.setObjectName(u"Dialog")
Dialog.resize(475, 66)
self.verticalLayout = QVBoxLayout(Dialog)
self.verticalLayout.setObjectName(u"verticalLayout")
self.label = QLabel(Dialog)
self.label.setObjectName(u"label")
self.verticalLayout.addWidget(self.label)
self.horizontalLayout = QHBoxLayout()
self.horizontalLayout.setObjectName(u"horizontalLayout")
self.btn_apparat = QPushButton(Dialog)
self.btn_apparat.setObjectName(u"btn_apparat")
self.horizontalLayout.addWidget(self.btn_apparat)
self.btn_prof = QPushButton(Dialog)
self.btn_prof.setObjectName(u"btn_prof")
self.horizontalLayout.addWidget(self.btn_prof)
self.verticalLayout.addLayout(self.horizontalLayout)
self.retranslateUi(Dialog)
QMetaObject.connectSlotsByName(Dialog)
# setupUi
def retranslateUi(self, Dialog):
Dialog.setWindowTitle(QCoreApplication.translate("Dialog", u"Dialog", None))
self.label.setText(QCoreApplication.translate("Dialog", u"Soll nur der Apparat gepr\u00fcft werden, oder sollen alle Medien des Profs gepr\u00fcft werden?", None))
self.btn_apparat.setText(QCoreApplication.translate("Dialog", u"Apparat", None))
self.btn_prof.setText(QCoreApplication.translate("Dialog", u"Prof", None))
# retranslateUi

View File

@@ -0,0 +1,92 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'new_edition_check.ui'
##
## Created by: Qt User Interface Compiler version 6.9.2
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
QMetaObject, QObject, QPoint, QRect,
QSize, QTime, QUrl, Qt)
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QFont, QFontDatabase, QGradient, QIcon,
QImage, QKeySequence, QLinearGradient, QPainter,
QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QApplication, QDialog, QFrame, QHBoxLayout,
QProgressBar, QPushButton, QSizePolicy, QStackedWidget,
QVBoxLayout, QWidget)
class Ui_Dialog(object):
def setupUi(self, Dialog):
if not Dialog.objectName():
Dialog.setObjectName(u"Dialog")
Dialog.resize(632, 726)
self.verticalLayout_4 = QVBoxLayout(Dialog)
self.verticalLayout_4.setObjectName(u"verticalLayout_4")
self.verticalLayout_3 = QVBoxLayout()
self.verticalLayout_3.setObjectName(u"verticalLayout_3")
self.frame = QFrame(Dialog)
self.frame.setObjectName(u"frame")
self.frame.setFrameShape(QFrame.StyledPanel)
self.frame.setFrameShadow(QFrame.Raised)
self.verticalLayout_5 = QVBoxLayout(self.frame)
self.verticalLayout_5.setObjectName(u"verticalLayout_5")
self.stackedWidget = QStackedWidget(self.frame)
self.stackedWidget.setObjectName(u"stackedWidget")
self.page = QWidget()
self.page.setObjectName(u"page")
self.stackedWidget.addWidget(self.page)
self.page_2 = QWidget()
self.page_2.setObjectName(u"page_2")
self.stackedWidget.addWidget(self.page_2)
self.verticalLayout_5.addWidget(self.stackedWidget)
self.verticalLayout_3.addWidget(self.frame)
self.progressBar = QProgressBar(Dialog)
self.progressBar.setObjectName(u"progressBar")
self.progressBar.setValue(24)
self.verticalLayout_3.addWidget(self.progressBar)
self.horizontalLayout = QHBoxLayout()
self.horizontalLayout.setObjectName(u"horizontalLayout")
self.btn_prev = QPushButton(Dialog)
self.btn_prev.setObjectName(u"btn_prev")
self.horizontalLayout.addWidget(self.btn_prev)
self.btn_next = QPushButton(Dialog)
self.btn_next.setObjectName(u"btn_next")
self.horizontalLayout.addWidget(self.btn_next)
self.btn_finish = QPushButton(Dialog)
self.btn_finish.setObjectName(u"btn_finish")
self.horizontalLayout.addWidget(self.btn_finish)
self.verticalLayout_3.addLayout(self.horizontalLayout)
self.verticalLayout_4.addLayout(self.verticalLayout_3)
self.retranslateUi(Dialog)
QMetaObject.connectSlotsByName(Dialog)
# setupUi
def retranslateUi(self, Dialog):
Dialog.setWindowTitle(QCoreApplication.translate("Dialog", u"Dialog", None))
self.btn_prev.setText(QCoreApplication.translate("Dialog", u"Previous", None))
self.btn_next.setText(QCoreApplication.translate("Dialog", u"Next", None))
self.btn_finish.setText(QCoreApplication.translate("Dialog", u"Finish", None))
# retranslateUi

View File

@@ -357,6 +357,13 @@
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_extendSelection">
<property name="text">
<string>Ausgewählte Verlängern</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_5">
<property name="orientation">

View File

@@ -3,131 +3,142 @@
################################################################################
## Form generated from reading UI file 'search_statistic_page.ui'
##
## Created by: Qt User Interface Compiler version 6.9.1
## Created by: Qt User Interface Compiler version 6.9.2
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
QMetaObject, QObject, QPoint, QRect,
QSize, QTime, QUrl, Qt)
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QFont, QFontDatabase, QGradient, QIcon,
QImage, QKeySequence, QLinearGradient, QPainter,
QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QAbstractItemView, QApplication, QCheckBox, QComboBox,
QDialog, QFrame, QGridLayout, QHBoxLayout,
QHeaderView, QLabel, QLayout, QLineEdit,
QPushButton, QSizePolicy, QSpacerItem, QStackedWidget,
QTabWidget, QTableWidget, QTableWidgetItem, QVBoxLayout,
QWidget)
from PySide6.QtCore import QCoreApplication, Qt
from PySide6.QtWidgets import (
QAbstractItemView,
QCheckBox,
QComboBox,
QFrame,
QGridLayout,
QHBoxLayout,
QLabel,
QLayout,
QLineEdit,
QPushButton,
QSizePolicy,
QSpacerItem,
QStackedWidget,
QTableWidget,
QTableWidgetItem,
QTabWidget,
QVBoxLayout,
QWidget,
)
class Ui_Dialog(object):
def setupUi(self, Dialog):
if not Dialog.objectName():
Dialog.setObjectName(u"Dialog")
Dialog.setObjectName("Dialog")
Dialog.resize(1244, 767)
self.verticalLayout = QVBoxLayout(Dialog)
self.verticalLayout.setObjectName(u"verticalLayout")
self.verticalLayout.setObjectName("verticalLayout")
self.tabWidget_2 = QTabWidget(Dialog)
self.tabWidget_2.setObjectName(u"tabWidget_2")
self.tabWidget_2.setObjectName("tabWidget_2")
self.tabWidget_2.setMaximumSize(QSize(16777215, 250))
self.tabWidget_2.setFocusPolicy(Qt.ClickFocus)
self.tabWidget_2.setTabPosition(QTabWidget.North)
self.tabWidget_2.setTabShape(QTabWidget.Rounded)
self.tab_3 = QWidget()
self.tab_3.setObjectName(u"tab_3")
self.tab_3.setObjectName("tab_3")
self.horizontalLayout_2 = QHBoxLayout(self.tab_3)
self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.gridLayout_3 = QGridLayout()
self.gridLayout_3.setObjectName(u"gridLayout_3")
self.gridLayout_3.setObjectName("gridLayout_3")
self.box_semester = QComboBox(self.tab_3)
self.box_semester.setObjectName(u"box_semester")
self.box_semester.setObjectName("box_semester")
self.box_semester.setEditable(True)
self.gridLayout_3.addWidget(self.box_semester, 0, 3, 1, 1)
self.label_18 = QLabel(self.tab_3)
self.label_18.setObjectName(u"label_18")
self.label_18.setObjectName("label_18")
self.gridLayout_3.addWidget(self.label_18, 2, 2, 1, 1)
self.box_fach = QComboBox(self.tab_3)
self.box_fach.setObjectName(u"box_fach")
self.box_fach.setObjectName("box_fach")
self.box_fach.setEditable(True)
self.gridLayout_3.addWidget(self.box_fach, 2, 1, 1, 1)
self.label_15 = QLabel(self.tab_3)
self.label_15.setObjectName(u"label_15")
self.label_15.setObjectName("label_15")
self.gridLayout_3.addWidget(self.label_15, 3, 0, 1, 1)
self.label_11 = QLabel(self.tab_3)
self.label_11.setObjectName(u"label_11")
self.label_11.setObjectName("label_11")
self.gridLayout_3.addWidget(self.label_11, 1, 0, 1, 1)
self.verticalSpacer_3 = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
self.verticalSpacer_3 = QSpacerItem(
20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding
)
self.gridLayout_3.addItem(self.verticalSpacer_3, 4, 0, 1, 1)
self.label_7 = QLabel(self.tab_3)
self.label_7.setObjectName(u"label_7")
self.label_7.setObjectName("label_7")
self.gridLayout_3.addWidget(self.label_7, 0, 0, 1, 1)
self.label_17 = QLabel(self.tab_3)
self.label_17.setObjectName(u"label_17")
self.label_17.setObjectName("label_17")
self.gridLayout_3.addWidget(self.label_17, 0, 2, 1, 1)
self.box_appnrs = QComboBox(self.tab_3)
self.box_appnrs.setObjectName(u"box_appnrs")
self.box_appnrs.setObjectName("box_appnrs")
self.box_appnrs.setEditable(True)
self.gridLayout_3.addWidget(self.box_appnrs, 0, 1, 1, 1)
self.box_dauerapp = QComboBox(self.tab_3)
self.box_dauerapp.setObjectName(u"box_dauerapp")
self.box_dauerapp.setObjectName("box_dauerapp")
self.gridLayout_3.addWidget(self.box_dauerapp, 2, 3, 1, 1)
self.box_person = QComboBox(self.tab_3)
self.box_person.setObjectName(u"box_person")
self.box_person.setObjectName("box_person")
self.box_person.setEditable(True)
self.gridLayout_3.addWidget(self.box_person, 1, 1, 1, 1)
self.box_erstellsemester = QComboBox(self.tab_3)
self.box_erstellsemester.setObjectName(u"box_erstellsemester")
self.box_erstellsemester.setObjectName("box_erstellsemester")
self.box_erstellsemester.setEditable(True)
self.gridLayout_3.addWidget(self.box_erstellsemester, 1, 3, 1, 1)
self.label_19 = QLabel(self.tab_3)
self.label_19.setObjectName(u"label_19")
self.label_19.setObjectName("label_19")
self.gridLayout_3.addWidget(self.label_19, 1, 2, 1, 1)
self.label_16 = QLabel(self.tab_3)
self.label_16.setObjectName(u"label_16")
self.label_16.setObjectName("label_16")
self.gridLayout_3.addWidget(self.label_16, 2, 0, 1, 1)
self.check_deletable = QCheckBox(self.tab_3)
self.check_deletable.setObjectName(u"check_deletable")
self.check_deletable.setObjectName("check_deletable")
self.check_deletable.setFocusPolicy(Qt.StrongFocus)
self.gridLayout_3.addWidget(self.check_deletable, 3, 1, 1, 1)
self.btn_search = QPushButton(self.tab_3)
self.btn_search.setObjectName(u"btn_search")
self.btn_search.setObjectName("btn_search")
self.gridLayout_3.addWidget(self.btn_search, 5, 0, 1, 1)
self.db_err_message = QLabel(self.tab_3)
self.db_err_message.setObjectName(u"db_err_message")
self.db_err_message.setObjectName("db_err_message")
self.gridLayout_3.addWidget(self.db_err_message, 5, 1, 1, 1)
@@ -139,7 +150,9 @@ class Ui_Dialog(object):
self.horizontalLayout_2.addLayout(self.gridLayout_3)
self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
self.horizontalSpacer = QSpacerItem(
40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum
)
self.horizontalLayout_2.addItem(self.horizontalSpacer)
@@ -147,58 +160,60 @@ class Ui_Dialog(object):
self.horizontalLayout_2.setStretch(1, 1)
self.tabWidget_2.addTab(self.tab_3, "")
self.tab_4 = QWidget()
self.tab_4.setObjectName(u"tab_4")
self.tab_4.setObjectName("tab_4")
self.horizontalLayout_3 = QHBoxLayout(self.tab_4)
self.horizontalLayout_3.setObjectName(u"horizontalLayout_3")
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
self.gridLayout = QGridLayout()
self.gridLayout.setObjectName(u"gridLayout")
self.gridLayout.setObjectName("gridLayout")
self.search_by_signature = QLineEdit(self.tab_4)
self.search_by_signature.setObjectName(u"search_by_signature")
self.search_by_signature.setObjectName("search_by_signature")
self.search_by_signature.setFocusPolicy(Qt.ClickFocus)
self.search_by_signature.setClearButtonEnabled(True)
self.gridLayout.addWidget(self.search_by_signature, 0, 1, 1, 1)
self.label_25 = QLabel(self.tab_4)
self.label_25.setObjectName(u"label_25")
self.label_25.setObjectName("label_25")
self.gridLayout.addWidget(self.label_25, 0, 0, 1, 1)
self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
self.verticalSpacer = QSpacerItem(
20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding
)
self.gridLayout.addItem(self.verticalSpacer, 5, 0, 1, 1)
self.search_by_title = QLineEdit(self.tab_4)
self.search_by_title.setObjectName(u"search_by_title")
self.search_by_title.setObjectName("search_by_title")
self.search_by_title.setFocusPolicy(Qt.ClickFocus)
self.search_by_title.setClearButtonEnabled(True)
self.gridLayout.addWidget(self.search_by_title, 1, 1, 1, 1)
self.label_26 = QLabel(self.tab_4)
self.label_26.setObjectName(u"label_26")
self.label_26.setObjectName("label_26")
self.gridLayout.addWidget(self.label_26, 1, 0, 1, 1)
self.horizontalLayout_4 = QHBoxLayout()
self.horizontalLayout_4.setObjectName(u"horizontalLayout_4")
self.horizontalLayout_4.setObjectName("horizontalLayout_4")
self.label = QLabel(self.tab_4)
self.label.setObjectName(u"label")
self.label.setObjectName("label")
self.horizontalLayout_4.addWidget(self.label)
self.no_result = QLabel(self.tab_4)
self.no_result.setObjectName(u"no_result")
self.no_result.setObjectName("no_result")
self.horizontalLayout_4.addWidget(self.no_result)
self.gridLayout.addLayout(self.horizontalLayout_4, 3, 1, 1, 1)
self.horizontalLayout_3.addLayout(self.gridLayout)
self.horizontalSpacer_2 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
self.horizontalSpacer_2 = QSpacerItem(
40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum
)
self.horizontalLayout_3.addItem(self.horizontalSpacer_2)
@@ -209,54 +224,64 @@ class Ui_Dialog(object):
self.verticalLayout.addWidget(self.tabWidget_2)
self.verticalLayout_3 = QVBoxLayout()
self.verticalLayout_3.setObjectName(u"verticalLayout_3")
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.verticalLayout_3.setSizeConstraint(QLayout.SetDefaultConstraint)
self.stackedWidget_4 = QStackedWidget(Dialog)
self.stackedWidget_4.setObjectName(u"stackedWidget_4")
sizePolicy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
self.stackedWidget_4.setObjectName("stackedWidget_4")
sizePolicy = QSizePolicy(
QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding
)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.stackedWidget_4.sizePolicy().hasHeightForWidth())
sizePolicy.setHeightForWidth(
self.stackedWidget_4.sizePolicy().hasHeightForWidth()
)
self.stackedWidget_4.setSizePolicy(sizePolicy)
self.stackedWidget_4.setFrameShape(QFrame.StyledPanel)
self.stackedWidget_4.setFrameShadow(QFrame.Raised)
self.apparatResult = QWidget()
self.apparatResult.setObjectName(u"apparatResult")
self.apparatResult.setObjectName("apparatResult")
self.horizontalLayout = QHBoxLayout(self.apparatResult)
self.horizontalLayout.setObjectName(u"horizontalLayout")
self.horizontalLayout.setObjectName("horizontalLayout")
self.app_results = QWidget(self.apparatResult)
self.app_results.setObjectName(u"app_results")
self.app_results.setObjectName("app_results")
self.verticalLayout_6 = QVBoxLayout(self.app_results)
self.verticalLayout_6.setObjectName(u"verticalLayout_6")
self.verticalLayout_6.setObjectName("verticalLayout_6")
self.verticalLayout_4 = QVBoxLayout()
self.verticalLayout_4.setObjectName(u"verticalLayout_4")
self.verticalLayout_4.setObjectName("verticalLayout_4")
self.horizontalLayout_7 = QHBoxLayout()
self.horizontalLayout_7.setObjectName(u"horizontalLayout_7")
self.horizontalLayout_7.setObjectName("horizontalLayout_7")
self.verticalLayout_5 = QVBoxLayout()
self.verticalLayout_5.setObjectName(u"verticalLayout_5")
self.verticalLayout_5.setObjectName("verticalLayout_5")
self.horizontalLayout_7.addLayout(self.verticalLayout_5)
self.btn_del_select_apparats = QPushButton(self.app_results)
self.btn_del_select_apparats.setObjectName(u"btn_del_select_apparats")
self.btn_del_select_apparats.setObjectName("btn_del_select_apparats")
self.btn_del_select_apparats.setFocusPolicy(Qt.StrongFocus)
self.horizontalLayout_7.addWidget(self.btn_del_select_apparats)
self.btn_notify_for_deletion = QPushButton(self.app_results)
self.btn_notify_for_deletion.setObjectName(u"btn_notify_for_deletion")
self.btn_notify_for_deletion.setObjectName("btn_notify_for_deletion")
self.horizontalLayout_7.addWidget(self.btn_notify_for_deletion)
self.horizontalSpacer_5 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
self.btn_extendSelection = QPushButton(self.app_results)
self.btn_extendSelection.setObjectName("btn_extendSelection")
self.horizontalLayout_7.addWidget(self.btn_extendSelection)
self.horizontalSpacer_5 = QSpacerItem(
40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum
)
self.horizontalLayout_7.addItem(self.horizontalSpacer_5)
self.verticalLayout_4.addLayout(self.horizontalLayout_7)
self.tableWidget = QTableWidget(self.app_results)
if (self.tableWidget.columnCount() < 5):
if self.tableWidget.columnCount() < 5:
self.tableWidget.setColumnCount(5)
__qtablewidgetitem = QTableWidgetItem()
self.tableWidget.setHorizontalHeaderItem(0, __qtablewidgetitem)
@@ -268,35 +293,33 @@ class Ui_Dialog(object):
self.tableWidget.setHorizontalHeaderItem(3, __qtablewidgetitem3)
__qtablewidgetitem4 = QTableWidgetItem()
self.tableWidget.setHorizontalHeaderItem(4, __qtablewidgetitem4)
self.tableWidget.setObjectName(u"tableWidget")
self.tableWidget.setObjectName("tableWidget")
self.tableWidget.setFocusPolicy(Qt.NoFocus)
self.tableWidget.setContextMenuPolicy(Qt.CustomContextMenu)
self.tableWidget.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.tableWidget.setGridStyle(Qt.NoPen)
self.tableWidget.setSortingEnabled(True)
self.tableWidget.horizontalHeader().setStretchLastSection(True)
self.tableWidget.verticalHeader().setProperty(u"showSortIndicator", True)
self.tableWidget.verticalHeader().setProperty("showSortIndicator", True)
self.verticalLayout_4.addWidget(self.tableWidget)
self.verticalLayout_6.addLayout(self.verticalLayout_4)
self.horizontalLayout.addWidget(self.app_results)
self.stats = QFrame(self.apparatResult)
self.stats.setObjectName(u"stats")
self.stats.setObjectName("stats")
self.verticalLayout_8 = QVBoxLayout(self.stats)
self.verticalLayout_8.setObjectName(u"verticalLayout_8")
self.verticalLayout_8.setObjectName("verticalLayout_8")
self.tabWidget_3 = QTabWidget(self.stats)
self.tabWidget_3.setObjectName(u"tabWidget_3")
self.tabWidget_3.setObjectName("tabWidget_3")
self.statistic_table = QWidget()
self.statistic_table.setObjectName(u"statistic_table")
self.statistic_table.setObjectName("statistic_table")
self.verticalLayout_7 = QVBoxLayout(self.statistic_table)
self.verticalLayout_7.setObjectName(u"verticalLayout_7")
self.verticalLayout_7.setObjectName("verticalLayout_7")
self.statistics_table = QTableWidget(self.statistic_table)
if (self.statistics_table.columnCount() < 3):
if self.statistics_table.columnCount() < 3:
self.statistics_table.setColumnCount(3)
__qtablewidgetitem5 = QTableWidgetItem()
self.statistics_table.setHorizontalHeaderItem(0, __qtablewidgetitem5)
@@ -304,8 +327,10 @@ class Ui_Dialog(object):
self.statistics_table.setHorizontalHeaderItem(1, __qtablewidgetitem6)
__qtablewidgetitem7 = QTableWidgetItem()
self.statistics_table.setHorizontalHeaderItem(2, __qtablewidgetitem7)
self.statistics_table.setObjectName(u"statistics_table")
sizePolicy.setHeightForWidth(self.statistics_table.sizePolicy().hasHeightForWidth())
self.statistics_table.setObjectName("statistics_table")
sizePolicy.setHeightForWidth(
self.statistics_table.sizePolicy().hasHeightForWidth()
)
self.statistics_table.setSizePolicy(sizePolicy)
self.statistics_table.setMaximumSize(QSize(16777215, 16777215))
self.statistics_table.setFocusPolicy(Qt.NoFocus)
@@ -315,36 +340,35 @@ class Ui_Dialog(object):
self.statistics_table.horizontalHeader().setCascadingSectionResizes(True)
self.statistics_table.horizontalHeader().setMinimumSectionSize(40)
self.statistics_table.horizontalHeader().setDefaultSectionSize(80)
self.statistics_table.horizontalHeader().setProperty(u"showSortIndicator", True)
self.statistics_table.horizontalHeader().setProperty("showSortIndicator", True)
self.statistics_table.horizontalHeader().setStretchLastSection(False)
self.statistics_table.verticalHeader().setStretchLastSection(True)
self.verticalLayout_7.addWidget(self.statistics_table)
self.dataLayout = QHBoxLayout()
self.dataLayout.setObjectName(u"dataLayout")
self.dataLayout.setObjectName("dataLayout")
self.verticalLayout_7.addLayout(self.dataLayout)
self.tabWidget_3.addTab(self.statistic_table, "")
self.graph_table = QWidget()
self.graph_table.setObjectName(u"graph_table")
self.graph_table.setObjectName("graph_table")
self.tabWidget_3.addTab(self.graph_table, "")
self.verticalLayout_8.addWidget(self.tabWidget_3)
self.horizontalLayout.addWidget(self.stats)
self.stackedWidget_4.addWidget(self.apparatResult)
self.bookresult = QWidget()
self.bookresult.setObjectName(u"bookresult")
self.bookresult.setObjectName("bookresult")
sizePolicy.setHeightForWidth(self.bookresult.sizePolicy().hasHeightForWidth())
self.bookresult.setSizePolicy(sizePolicy)
self.verticalLayout_2 = QVBoxLayout(self.bookresult)
self.verticalLayout_2.setObjectName(u"verticalLayout_2")
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.book_search_result = QTableWidget(self.bookresult)
if (self.book_search_result.columnCount() < 3):
if self.book_search_result.columnCount() < 3:
self.book_search_result.setColumnCount(3)
__qtablewidgetitem8 = QTableWidgetItem()
self.book_search_result.setHorizontalHeaderItem(0, __qtablewidgetitem8)
@@ -352,7 +376,7 @@ class Ui_Dialog(object):
self.book_search_result.setHorizontalHeaderItem(1, __qtablewidgetitem9)
__qtablewidgetitem10 = QTableWidgetItem()
self.book_search_result.setHorizontalHeaderItem(2, __qtablewidgetitem10)
self.book_search_result.setObjectName(u"book_search_result")
self.book_search_result.setObjectName("book_search_result")
self.book_search_result.setFrameShadow(QFrame.Plain)
self.book_search_result.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.book_search_result.setAlternatingRowColors(True)
@@ -360,9 +384,11 @@ class Ui_Dialog(object):
self.book_search_result.horizontalHeader().setCascadingSectionResizes(True)
self.book_search_result.horizontalHeader().setMinimumSectionSize(100)
self.book_search_result.horizontalHeader().setDefaultSectionSize(200)
self.book_search_result.horizontalHeader().setProperty(u"showSortIndicator", True)
self.book_search_result.horizontalHeader().setProperty(
"showSortIndicator", True
)
self.book_search_result.horizontalHeader().setStretchLastSection(True)
self.book_search_result.verticalHeader().setProperty(u"showSortIndicator", False)
self.book_search_result.verticalHeader().setProperty("showSortIndicator", False)
self.verticalLayout_2.addWidget(self.book_search_result)
@@ -370,10 +396,9 @@ class Ui_Dialog(object):
self.verticalLayout_3.addWidget(self.stackedWidget_4)
self.verticalLayout.addLayout(self.verticalLayout_3)
#if QT_CONFIG(shortcut)
# if QT_CONFIG(shortcut)
self.label_18.setBuddy(self.box_dauerapp)
self.label_15.setBuddy(self.check_deletable)
self.label_11.setBuddy(self.box_person)
@@ -383,7 +408,7 @@ class Ui_Dialog(object):
self.label_16.setBuddy(self.box_fach)
self.label_25.setBuddy(self.search_by_signature)
self.label_26.setBuddy(self.search_by_title)
#endif // QT_CONFIG(shortcut)
# endif // QT_CONFIG(shortcut)
QWidget.setTabOrder(self.box_appnrs, self.box_person)
QWidget.setTabOrder(self.box_person, self.box_fach)
QWidget.setTabOrder(self.box_fach, self.check_deletable)
@@ -401,57 +426,118 @@ class Ui_Dialog(object):
self.stackedWidget_4.setCurrentIndex(0)
self.tabWidget_3.setCurrentIndex(0)
QMetaObject.connectSlotsByName(Dialog)
# setupUi
def retranslateUi(self, Dialog):
Dialog.setWindowTitle(QCoreApplication.translate("Dialog", u"Dialog", None))
self.label_18.setText(QCoreApplication.translate("Dialog", u"Dauerapp:", None))
self.label_15.setText(QCoreApplication.translate("Dialog", u"L\u00f6schbar", None))
self.label_11.setText(QCoreApplication.translate("Dialog", u"Person:", None))
self.label_7.setText(QCoreApplication.translate("Dialog", u"Appnr.:", None))
self.label_17.setText(QCoreApplication.translate("Dialog", u"Endsemester:", None))
self.label_19.setText(QCoreApplication.translate("Dialog", u"Erstellsemester:", None))
self.label_16.setText(QCoreApplication.translate("Dialog", u"Fach:", None))
Dialog.setWindowTitle(QCoreApplication.translate("Dialog", "Dialog", None))
self.label_18.setText(QCoreApplication.translate("Dialog", "Dauerapp:", None))
self.label_15.setText(
QCoreApplication.translate("Dialog", "L\u00f6schbar", None)
)
self.label_11.setText(QCoreApplication.translate("Dialog", "Person:", None))
self.label_7.setText(QCoreApplication.translate("Dialog", "Appnr.:", None))
self.label_17.setText(
QCoreApplication.translate("Dialog", "Endsemester:", None)
)
self.label_19.setText(
QCoreApplication.translate("Dialog", "Erstellsemester:", None)
)
self.label_16.setText(QCoreApplication.translate("Dialog", "Fach:", None))
self.check_deletable.setText("")
self.btn_search.setText(QCoreApplication.translate("Dialog", u"Suchen", None))
self.btn_search.setText(QCoreApplication.translate("Dialog", "Suchen", None))
self.db_err_message.setText("")
self.tabWidget_2.setTabText(self.tabWidget_2.indexOf(self.tab_3), QCoreApplication.translate("Dialog", u"Statistik", None))
#if QT_CONFIG(statustip)
self.search_by_signature.setStatusTip(QCoreApplication.translate("Dialog", u"Trunkierung mit * am Ende unterst\u00fctzt", None))
#endif // QT_CONFIG(statustip)
self.label_25.setText(QCoreApplication.translate("Dialog", u"Signatur", None))
self.label_26.setText(QCoreApplication.translate("Dialog", u"Titel", None))
self.label.setText(QCoreApplication.translate("Dialog", u"Suche mit Enter starten", None))
self.tabWidget_2.setTabText(
self.tabWidget_2.indexOf(self.tab_3),
QCoreApplication.translate("Dialog", "Statistik", None),
)
# if QT_CONFIG(statustip)
self.search_by_signature.setStatusTip(
QCoreApplication.translate(
"Dialog", "Trunkierung mit * am Ende unterst\u00fctzt", None
)
)
# endif // QT_CONFIG(statustip)
self.label_25.setText(QCoreApplication.translate("Dialog", "Signatur", None))
self.label_26.setText(QCoreApplication.translate("Dialog", "Titel", None))
self.label.setText(
QCoreApplication.translate("Dialog", "Suche mit Enter starten", None)
)
self.no_result.setText("")
self.tabWidget_2.setTabText(self.tabWidget_2.indexOf(self.tab_4), QCoreApplication.translate("Dialog", u"Suchen", None))
self.btn_del_select_apparats.setText(QCoreApplication.translate("Dialog", u"Ausgew\u00e4hlte L\u00f6schen", None))
#if QT_CONFIG(statustip)
self.btn_notify_for_deletion.setStatusTip(QCoreApplication.translate("Dialog", u"Zeigt f\u00fcr jeden ausgew\u00e4hlten Apparat eine eMail-Vorlage an", None))
#endif // QT_CONFIG(statustip)
self.btn_notify_for_deletion.setText(QCoreApplication.translate("Dialog", u"Ausgew\u00e4hlte Benachrichtigen", None))
self.tabWidget_2.setTabText(
self.tabWidget_2.indexOf(self.tab_4),
QCoreApplication.translate("Dialog", "Suchen", None),
)
self.btn_del_select_apparats.setText(
QCoreApplication.translate("Dialog", "Ausgew\u00e4hlte L\u00f6schen", None)
)
# if QT_CONFIG(statustip)
self.btn_notify_for_deletion.setStatusTip(
QCoreApplication.translate(
"Dialog",
"Zeigt f\u00fcr jeden ausgew\u00e4hlten Apparat eine eMail-Vorlage an",
None,
)
)
# endif // QT_CONFIG(statustip)
self.btn_notify_for_deletion.setText(
QCoreApplication.translate(
"Dialog", "Ausgew\u00e4hlte Benachrichtigen", None
)
)
self.btn_extendSelection.setText(
QCoreApplication.translate(
"Dialog", "Ausgew\u00e4hlte Verl\u00e4ngern", None
)
)
___qtablewidgetitem = self.tableWidget.horizontalHeaderItem(1)
___qtablewidgetitem.setText(QCoreApplication.translate("Dialog", u"Apparatsname", None));
___qtablewidgetitem.setText(
QCoreApplication.translate("Dialog", "Apparatsname", None)
)
___qtablewidgetitem1 = self.tableWidget.horizontalHeaderItem(2)
___qtablewidgetitem1.setText(QCoreApplication.translate("Dialog", u"Apparatsnummer", None));
___qtablewidgetitem1.setText(
QCoreApplication.translate("Dialog", "Apparatsnummer", None)
)
___qtablewidgetitem2 = self.tableWidget.horizontalHeaderItem(3)
___qtablewidgetitem2.setText(QCoreApplication.translate("Dialog", u"Person", None));
___qtablewidgetitem2.setText(
QCoreApplication.translate("Dialog", "Person", None)
)
___qtablewidgetitem3 = self.tableWidget.horizontalHeaderItem(4)
___qtablewidgetitem3.setText(QCoreApplication.translate("Dialog", u"Fach", None));
___qtablewidgetitem3.setText(QCoreApplication.translate("Dialog", "Fach", None))
___qtablewidgetitem4 = self.statistics_table.horizontalHeaderItem(0)
___qtablewidgetitem4.setText(QCoreApplication.translate("Dialog", u"Semester", None));
___qtablewidgetitem4.setText(
QCoreApplication.translate("Dialog", "Semester", None)
)
___qtablewidgetitem5 = self.statistics_table.horizontalHeaderItem(1)
___qtablewidgetitem5.setText(QCoreApplication.translate("Dialog", u"Zugang", None));
___qtablewidgetitem5.setText(
QCoreApplication.translate("Dialog", "Zugang", None)
)
___qtablewidgetitem6 = self.statistics_table.horizontalHeaderItem(2)
___qtablewidgetitem6.setText(QCoreApplication.translate("Dialog", u"Abgang", None));
self.tabWidget_3.setTabText(self.tabWidget_3.indexOf(self.statistic_table), QCoreApplication.translate("Dialog", u"Tabelle", None))
self.tabWidget_3.setTabText(self.tabWidget_3.indexOf(self.graph_table), QCoreApplication.translate("Dialog", u"Erstellte und gel\u00f6schte Semesterapparate", None))
___qtablewidgetitem6.setText(
QCoreApplication.translate("Dialog", "Abgang", None)
)
self.tabWidget_3.setTabText(
self.tabWidget_3.indexOf(self.statistic_table),
QCoreApplication.translate("Dialog", "Tabelle", None),
)
self.tabWidget_3.setTabText(
self.tabWidget_3.indexOf(self.graph_table),
QCoreApplication.translate(
"Dialog", "Erstellte und gel\u00f6schte Semesterapparate", None
),
)
___qtablewidgetitem7 = self.book_search_result.horizontalHeaderItem(0)
___qtablewidgetitem7.setText(QCoreApplication.translate("Dialog", u"Titel", None));
___qtablewidgetitem7.setText(
QCoreApplication.translate("Dialog", "Titel", None)
)
___qtablewidgetitem8 = self.book_search_result.horizontalHeaderItem(1)
___qtablewidgetitem8.setText(QCoreApplication.translate("Dialog", u"Signatur", None));
___qtablewidgetitem8.setText(
QCoreApplication.translate("Dialog", "Signatur", None)
)
___qtablewidgetitem9 = self.book_search_result.horizontalHeaderItem(2)
___qtablewidgetitem9.setText(QCoreApplication.translate("Dialog", u"Apparat", None));
# retranslateUi
___qtablewidgetitem9.setText(
QCoreApplication.translate("Dialog", "Apparat", None)
)
# retranslateUi

688
uv.lock generated

File diff suppressed because it is too large Load Diff