159 Commits

Author SHA1 Message Date
0406fe4f6f Refactor and enhance type hints across multiple modules
- Updated the `from_tuple` method in `Prof` class to specify return type.
- Added type hints for various methods in `LehmannsClient`, `OpenAI`, `WebRequest`, and `ZoteroController` classes to improve code clarity and type safety.
- Modified `pdf_to_csv` function to return a string instead of a DataFrame.
- Enhanced error handling and type hints in `wordparser` and `xmlparser` modules.
- Removed unused UI file `Ui_medianadder.ts`.
- Improved the layout and structure of the `semesterapparat_ui` to enhance user experience.
- Updated file picker to support `.doc` files in addition to `.docx`.
- Added unique item handling in `Ui` class to prevent duplicates in apparat list.
- General code cleanup and consistency improvements across various files.
2025-10-21 09:09:54 +02:00
560d8285b5 Feat: add delete edition dialog with fuzzy search 2025-10-10 09:10:06 +02:00
3cc6e793d2 more AI optimizations, reworked logger 2025-10-09 12:35:15 +02:00
7e07bdea0c commit AI suggested performance enhancements 2025-10-07 14:42:40 +02:00
06965db26a minor and major reworks: rename swb to SRU, add a test for pdf parsing
major: rework mail to send mail as plaintext instead of html, preventing the bleed-in of html text
2025-10-07 14:15:10 +02:00
0df7fd9fe6 add template for empty mail for individualized emails 2025-10-07 14:12:25 +02:00
713dbc1a1d UI: changes to new edition UIs 2025-10-07 14:11:57 +02:00
e061c1f5a9 rework threads and also use app_ids where applicable 2025-10-07 14:11:14 +02:00
8e9eff4f3a move semester dataclass into logic dir 2025-10-07 14:10:13 +02:00
6a11b3482e add icons 2025-10-07 14:09:11 +02:00
d35b2e816e UI: refactor mail template dialog for plaintext handling, improve logging, and update UI elements 2025-09-22 09:47:18 +02:00
11d5d67538 DB: add newedtion schema 2025-09-22 09:46:13 +02:00
ebf8363b2a UI: update main UI window to allow drops on document list, add support for newedition check & order 2025-09-22 09:45:51 +02:00
a2631570ec UI: allow drops for semap document list 2025-09-22 09:45:02 +02:00
9831aa3a62 UI: update templates, update code 2025-09-22 09:44:28 +02:00
c4be1d8bfa files: reorganize imports, remove print lines 2025-09-22 09:42:15 +02:00
7079b4d47f add insert, request, ordered functions for new edition books 2025-09-22 09:37:25 +02:00
65c86a65cd mail: add new mail to request new editions of books 2025-09-22 09:36:39 +02:00
f4e75831d5 rework email templates 2025-09-18 07:15:34 +02:00
4f28cfe55c add sender name to mail config dataclass 2025-09-18 07:15:15 +02:00
8b8c1c9393 chore: add logging, minor changes 2025-09-18 07:14:40 +02:00
247db562b1 add sound: error 2025-09-17 14:47:24 +02:00
1263faa23f delete files 2025-09-17 14:27:12 +02:00
fd6684cc47 add label setter and clearer 2025-09-08 10:36:57 +02:00
1ee7901d49 add eta, items/s signals, calculate values 2025-09-08 10:36:18 +02:00
e934a2b3f1 add ppn search 2025-09-08 10:35:47 +02:00
7be9dba9ca refresh displayed table entities after signatures were checked 2025-09-08 10:35:29 +02:00
6f21c22d22 UI: add eta label 2025-09-08 10:34:59 +02:00
1f34442397 rework catalogue wrapper to split entries based on space div 2025-09-08 10:34:34 +02:00
373257864f update searchpage, add updater thread and emit signal for progress bar
add context menu and related actions
2025-09-03 12:33:52 +02:00
b577a69dad add a progressbar to the statusbar which displays the search progress on searchstat page for signature update 2025-09-03 12:32:42 +02:00
a64fa9770f refactor searchBook to allow for regex matches 2025-09-03 12:32:05 +02:00
0061708785 Merge pull request 'merge main into dev' (#13) from main into dev
Reviewed-on: http://git.theprivateserver.de/PHB/SemesterapparatsManager/pulls/13
2025-09-03 09:44:13 +01:00
a3b68c2b77 Refactor initialization: streamline log and config directory creation; enhance module exports in backend and UI widgets 2025-09-03 10:42:12 +02:00
0ac5051aef Add Catalogue class for book searching and connection handling 2025-09-03 10:41:40 +02:00
bf419ec3bf Implement startup check and table creation in Database class; add methods to retrieve books and manage apparat deletion 2025-09-03 10:41:26 +02:00
c6f356fda4 Refactor user interface: enhance sound playback functionality and integrate signature update process,
fix broken database calls by using the app_id instead of the previously used appnr
2025-09-03 10:40:57 +02:00
087b8753fb Add progress dialog and threading for signature updates 2025-09-03 10:39:36 +02:00
09ec304637 Refactor WebRequest class: improve location handling and logging for Semesterapparat 2025-09-03 10:38:59 +02:00
f6ab64a8ee UI-Signatures: implement signature update functionality with progress tracking 2025-09-03 10:37:44 +02:00
4254567bfb delete faulty files 2025-09-03 10:37:18 +02:00
9ce46abdce UI-Signatures: add selector for signaturecheck 2025-09-03 10:36:56 +02:00
8cce13f6e5 UI-Statistics: add button for mass extend, add extend functionality 2025-09-03 10:36:07 +02:00
f22cbcd26a UI: add new admin option 2025-09-03 10:34:25 +02:00
6f22186b67 add ding sound 2025-09-03 10:33:54 +02:00
a231213276 switch from chardet to charset-normalizer 2025-09-03 10:33:39 +02:00
b344d806e2 refactor: reorganize imports and enhance logging setup; improve book processing logic in NewEditionCheckerThread 2025-09-03 10:33:15 +02:00
0e3199e289 add warning log to fix crashing bug 2025-09-03 10:32:39 +02:00
c00eb102ff remove qtqdm dependency, switch from chardet to charset-normalizer 2025-09-03 10:32:17 +02:00
63b2a1b7a3 Implement code changes to enhance functionality and improve performance 2025-09-01 14:33:02 +02:00
5a4156ba04 feat: enhance user interface with new edition checking functionality and sound notifications 2025-09-01 14:32:33 +02:00
af53b0310f refactor: update import statement and enhance word_to_semap function with AI parameter 2025-09-01 14:32:05 +02:00
ce7d22b26b feat: add from_LehmannsSearchResult method to BookData for processing Lehmanns search results 2025-09-01 14:31:46 +02:00
5f15352401 feat: implement NewEditionCheckerThread and related utility functions for book data processing 2025-09-01 14:31:23 +02:00
7da2b3f65d feat: add getProfMailById method to retrieve professor's email by ID
refactor: reorganize import statements and clean up commented code
2025-09-01 14:31:02 +02:00
5bf5eeae00 add APIs to parse data from SWB and Lehmanns 2025-09-01 14:30:37 +02:00
c6cbb1d825 refactor: reorganize imports and enhance Mail_Dialog to handle accepted_books 2025-09-01 14:30:10 +02:00
3bfb788f42 refactor: update __all__ exports and reorganize imports in __init__.py 2025-09-01 14:29:50 +02:00
1c5dfc8f3e sort imports 2025-09-01 14:29:32 +02:00
4c26aa8d21 fix: correct parameter name in QFileDialog method 2025-09-01 14:29:17 +02:00
b67a160e7a add sound to be played once check complete 2025-09-01 14:28:59 +02:00
d8fabdbe11 add progressbar for checking editions 2025-09-01 14:28:39 +02:00
ee8ea9dfda add files for edition checker 2025-09-01 14:28:05 +02:00
ec0f72337d add ui files for edition checker 2025-09-01 14:27:48 +02:00
6ae52b6626 add new mail template 2025-09-01 14:27:20 +02:00
f4d548d91a Bump version: 0.2.1 → 1.0.0 2025-07-03 12:34:56 +02:00
18b787dcad Merge branch 'dev' 2025-07-03 12:33:51 +02:00
8d94abfb1a manually bump version back to current 2025-07-03 12:14:36 +02:00
83e6446b16 allow dirty version bumps 2025-07-03 12:13:18 +02:00
88b6e8fc6a update gitignore, add test.py to be ignored 2025-07-03 12:10:50 +02:00
d4eae2b71e remove unneeded mail file 2025-07-03 11:22:38 +02:00
7c756c2f21 add mail folder to build command 2025-07-03 11:22:16 +02:00
5951081efa update build script 2025-07-03 10:37:49 +02:00
2e3845b568 update search page to show no resutl found message and tell how to search 2025-07-03 09:58:11 +02:00
290395d38d add error checking for non-existent users trying to log in 2025-07-03 09:22:43 +02:00
6d8051e4e6 fix: fix display of apparatsdata in table on doubleclick, was broken due to old dict usage instead of apparat class 2025-07-03 09:22:21 +02:00
42dc945ab6 change critical to debug for database path log 2025-07-03 07:22:26 +02:00
9b0bf3663b rework quiet handler, use logging to log to file 2025-07-03 07:22:06 +02:00
981fee5d7f add warning widget 2025-07-03 07:21:37 +02:00
3d15609536 update build: remove folders from previous build 2025-07-03 07:21:16 +02:00
a6d9498b39 rework documentation launch, use generated files instead of pure mkdocs, add quiet handler for use with logging in terminal 2025-07-03 07:20:57 +02:00
30228fd267 update build command 2025-07-02 11:00:56 +02:00
cd255696f0 rework documentation launch 2025-07-02 08:30:56 +02:00
5eccbebef7 add pyramid dependency for documentation hosting 2025-07-02 08:30:19 +02:00
bc061dcbc6 add .version file 2025-07-02 08:29:58 +02:00
7e0e26619f revert version 2025-07-02 08:29:44 +02:00
edd57011e0 feat: add build script 2025-06-25 14:37:41 +02:00
5f6af18ca9 chore: update dependencies 2025-06-25 14:37:30 +02:00
612020e495 chore: change logo 2025-06-25 14:37:16 +02:00
08b23f01f8 update main file to create application and check for wizard need 2025-06-24 15:48:03 +02:00
4bc7901c93 Merge pull request 'dev_database_issue' (#11) from dev_database_issue into dev
Reviewed-on: #11
2025-06-24 12:47:11 +01:00
c06ff40fd6 fix issue with database path falling back to faulty path
update logic in welcome wizard, add checks for qapplication instance
2025-06-24 13:46:08 +02:00
3d164898bf rework graph to include semester generation, fix bug where y-values were extracted wrong 2025-06-20 09:09:28 +02:00
86849b67f5 rework semester class using chatgpt - fixes offset bug, implements generate_missing function 2025-06-18 13:32:59 +02:00
7eb55c21d0 migrate from PyQt6 to PySide6, remove unneeded dependencies 2025-06-17 16:21:56 +02:00
c3d9daa1b0 replace datagraph with dataqtgraph implementation to fix a scrolling bug 2025-06-17 16:13:20 +02:00
fdab4e5caa start work on welcome wizard 2025-06-10 16:23:29 +02:00
dbad7165bc refactor: update configuration handling and OpenAI client initialization 2025-06-06 11:14:56 +02:00
2eceb07c0b delete unneeded files 2025-06-03 15:28:47 +02:00
3fbb8bbd52 add openai model to config, rework logging to use appdirs logging dir 2025-06-03 15:28:14 +02:00
9684229fc2 add appdirs dependency 2025-06-03 15:27:31 +02:00
a7b82ee3be update lockfile 2025-06-03 13:17:55 +02:00
771062ab7f add openai implementation to take over some parsing functionality 2025-06-03 13:17:36 +02:00
b874656eba set log level to INFO for improved logging clarity 2025-06-03 13:17:13 +02:00
abe17d8c57 fix path decoding 2025-06-03 13:16:03 +02:00
d02a8a271f rework wordparser
- add dropdown selector
- fix bug where Telefon: key got overwritten
2025-06-03 13:15:06 +02:00
e29b630405 include chatgpt section to config 2025-06-03 13:14:13 +02:00
bb4c4c4003 add openai dependency 2025-06-03 13:13:50 +02:00
b1d523f574 update wordparser 2025-05-26 13:22:47 +02:00
c77bdbc3de Update .gitea/workflows/release.yml 2025-05-20 07:28:54 +01:00
8036a7cb3c Update .gitea/workflows/release.yml 2025-05-20 07:28:38 +01:00
72fb775257 Update .gitea/workflows/release.yml 2025-05-20 07:23:39 +01:00
85b696f089 Update .gitea/workflows/release.yml 2025-05-20 07:23:06 +01:00
ac7c7ad60c fix typo #2 2025-05-14 15:06:24 +02:00
da5fb285c8 Merge pull request 'dev' (#8) from dev into main
Reviewed-on: #8
2025-05-14 14:05:34 +01:00
3bfc7a2672 fix typo 2025-05-14 15:04:56 +02:00
55d172b9a5 update wf 2025-05-14 15:02:44 +02:00
2785314e7c add lockfile 2025-05-14 15:02:22 +02:00
139396081b Merge pull request 'Add new changes to main' (#7) from dev into main
Reviewed-on: #7
2025-05-14 13:58:47 +01:00
f1c58699b0 add manual release workflow 2025-05-14 14:58:04 +02:00
6f670aa1c4 Update PyQt6 UI code generator version in multiple UI files 2025-05-14 14:41:12 +02:00
ef78d9ff0d add local files to exclusion list 2025-05-14 14:38:17 +02:00
0c53778f99 Refactor database message handling to support multiple messages and enhance type hints across various classes 2025-05-14 14:34:40 +02:00
0fdc904d2d Merge pull request 'Merge dev to main, nearing completion' (#6) from dev into main
Reviewed-on: #6
2025-05-13 15:32:46 +01:00
f7c499ea6e Refactor logging setup across multiple modules to use loguru with consistent configuration
- Updated logging initialization in MessageCalendar, admin_edit_prof, elsa_main, graph, iconLine, searchPage, and richtext modules to use loguru.
- Changed log rotation and retention settings for log files to improve log management.
- Replaced logger.debug/info calls with log.debug/info for consistency.
- Fixed a typo in the searchPage UI and updated related references in the UI files.
- Removed unused imports and cleaned up code for better readability.
2025-05-13 15:49:52 +02:00
4a3a95623a delete old, dupe files 2025-05-13 15:43:07 +02:00
0c8ecb2054 Remove unused imports and clean up code structure across multiple files 2025-05-13 11:39:47 +02:00
99b9f50784 rework database to use json string instead of dataclass dump. Prevents failed decodings, makes contents searchable, if needed 2025-05-13 11:37:25 +02:00
8f90247e98 Refactor UI and remove unused tests
- Deleted the generated UI file `untitled_ui.py` as it is no longer needed.
- Updated `search_statistic_page.ui` to enhance table properties, including grid style and sort indicators.
- Modified `search_statistic_page_ui.py` to reflect changes in the UI file and improve table header settings.
- Improved cleanup logic in `richtext.py` to ensure files are only deleted if they exist.
- Adjusted spacing in document generation for better formatting.
- Removed obsolete test files: `database_test.py`, `many_webrequest_test.py`, `test_database.py`, and `webrequest_test.py` to clean up the test suite.
2025-05-12 15:26:58 +02:00
d71de1bd1a add files from folder merger 2025-05-12 09:05:09 +02:00
5ac3509548 delete old files 2025-05-12 08:59:13 +02:00
c364a38649 Merge pull request 'Merge changes from agent_branch to dev' (#5) from dev_fix_media_not_adding_agent into dev
Reviewed-on: #5
2025-05-09 10:58:42 +01:00
468e8674ab update logging, update docuprint add new ui for generating documents 2025-05-09 11:57:18 +02:00
f7ea6f5d34 remove logs from tracker 2025-05-08 09:00:08 +02:00
20d07f5775 add. closes #3 closes #4 2025-04-28 15:45:20 +02:00
8c68655f9f add type checking, error handling, fix search issue. Closes #3 Closes #4 Fixes #3 Fixes #4 2025-04-28 15:36:29 +02:00
424411b077 add type checking, update deletion function in searchpage, add function to import apparat data from document 2025-04-28 15:31:35 +02:00
e6bbc469b1 update logging to be on a per file basis 2025-04-28 10:43:45 +02:00
98ac7377ac add word_to_semap_as import 2025-04-28 10:18:39 +02:00
5923bfd7ff add logger 2025-04-28 10:18:28 +02:00
7abe3d8cc0 add logger 2025-04-28 10:18:07 +02:00
b4c6169649 add logger, media list refresh, type check ignore 2025-04-28 10:16:56 +02:00
8b83b8c305 add logger 2025-04-28 10:16:24 +02:00
3d2be0fd47 fix graph bug 2025-04-28 10:16:10 +02:00
bbeb9cf701 remove logger variable, declare in files from now on 2025-04-28 10:15:48 +02:00
eb0b7a1fec add testclass, rework logging 2025-04-28 10:15:22 +02:00
80b96865e7 add SemapDocument and Book dataclasses, improve word document parsing 2025-04-25 12:16:14 +02:00
0867b1fce8 Merge pull request 'merge pyside dev into dev' (#2) from dev_pyside6 into dev
Reviewed-on: #2
2025-04-14 10:14:59 +01:00
da0e9e0725 fix bug in document creation 2025-04-14 11:08:15 +02:00
f0148d8855 add image, rework SemesterDocument 2025-04-14 11:07:48 +02:00
9cc08e2d91 start work on readme file 2025-04-14 11:07:06 +02:00
e91898137c Merge pull request 'dev_pyside6' (#1) from dev_pyside6 into dev
Reviewed-on: #1
2025-04-07 06:56:09 +01:00
921a84304f add printer mail to config 2025-04-07 07:54:34 +02:00
6ff7a70d11 rework logging, fix calculation error 2025-04-07 07:53:57 +02:00
ac32b86b17 fix bug in extend_loan function 2025-04-07 07:53:36 +02:00
dbefd2049f add mail to deliver deletion information 2025-04-07 07:52:01 +02:00
50dd03aee7 extract printer mail to settings file 2025-04-07 07:49:38 +02:00
f1724058b0 Bump version: 0.2.0 → 0.2.1 2025-03-26 10:28:25 +01:00
d225c1d4e8 change versioning 2025-03-26 10:28:18 +01:00
189 changed files with 15874 additions and 5956 deletions

View File

@@ -1,37 +0,0 @@
[tool.bumpversion]
current_version = "0.2.0-dev0"
parse = """(?x)
(?P<major>0|[1-9]\\d*)\\.
(?P<minor>0|[1-9]\\d*)\\.
(?P<patch>0|[1-9]\\d*)
(?:
- # dash separator for pre-release section
(?P<pre_l>[a-zA-Z-]+) # pre-release label
(?P<pre_n>0|[1-9]\\d*) # pre-release version number
)? # pre-release section is optional
"""
serialize = [
"{major}.{minor}.{patch}-{pre_l}{pre_n}",
"{major}.{minor}.{patch}",
]
search = "{current_version}"
replace = "{new_version}"
regex = false
ignore_missing_version = false
ignore_missing_files = false
tag = true
sign_tags = false
tag_name = "v{new_version}"
tag_message = "Bump version: {current_version} → {new_version}"
allow_dirty = false
commit = true
message = "Bump version: {current_version} → {new_version}"
commit_args = ""
setup_hooks = []
pre_commit_hooks = []
post_commit_hooks = []
[tool.bumpversion.parts.pre_l]
values = ["dev", "rc", "final"]
optional_value = "final"
[[tool.bumpversion.files]]
filename = "src/__init__.py"

View File

@@ -0,0 +1,59 @@
on:
workflow_dispatch:
inputs:
release_notes:
description: Release notes (use \n for newlines)
type: string
required: false
github_release:
description: 'Create Gitea Release'
default: true
type: boolean
bump:
description: 'Bump type'
required: false
default: 'patch'
type: choice
options:
- 'major'
- 'minor'
- 'patch'
jobs:
bump:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install UV
uses: astral-sh/setup-uv@v5
- name: Set up Python
run: uv python install
with:
python-version-file: "pyproject.toml"
- name: Install dependencies
run: uv sync --locked --all-extras --dev
- name: Install Bump tool
run: uv tool install bump-my-version
- name: Bump version
id: bump_version
run: |
uv tool run bump-my-version bump ${{ github.event.inputs.bump }} --tag --allow-dirty
- name: Add release notes
id: add_release_notes
run: |
echo "RELEASE_NOTES<<EOF" >> $GITHUB_ENV
echo "${{ github.event.inputs.release_notes }}" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
- name: Create Gitea Release
if: ${{ github.event.inputs.github_release == 'true' }}
uses: softprops/action-gh-release@v1
with:
tag_name:
release_name: Release ${{ github.sha }}
body: ${{ env.RELEASE_NOTES }}
draft: false
prerelease: false

7
.gitignore vendored
View File

@@ -226,5 +226,8 @@ output
config.yaml
**/tempCodeRunnerFile.py
uv.lock
uv.lock
logs/
*.pdf
*.docx
test.py

1
.version Normal file
View File

@@ -0,0 +1 @@
1.0.0

View File

@@ -1,3 +1,27 @@
# Semesterapparate
# SemesterapparatsManager
this repo will be used to create a GUI application to manage the semesterapparate of the PH Freiburg.
SemesterapparatsManager is a graphical tool for managing semester apparatuses in the University of Education Freiburg. It allows the users to manage the semester apparatuses in a user-friendly way. It's functions include management of physical and digital semester apparatuses, as well as creating the citations for the digital files of the digital semester apparatuses. For that it uses Zotero, an open source reference management software. The semester apparatuses are stored in a SQLite database, which is created and managed by the SemesterapparatsManager. The SemesterapparatsManager is written in Python and uses the PyQt6 library for the graphical user interface
## Features
- Manage physical semester apparatuses
- Add semester apparatuses
- Edit semester apparatuses
- Delete semester apparatuses
- Extend semester apparatuses
- Notify professors about semester apparatuses creation or deletion
- Add messages to all semester apparatuses, or an individual semester apparatus
- Manage digital semester apparatuses
- Use text parsing to extract information from the submitted form and create the scans
- if a book is used multiple parts of a book are used, it can be split into the parts
- Create the matching citations for the files
- Statistics and Search
- Search semester apparatuses by various criteria
- Show statistics about the semester apparatuses creation and deletion
- Edit user data
## Images
![Main Window](docs/images/mainUI.png)
![Statistics](docs/images/statistics.png)

View File

@@ -1,4 +0,0 @@
from src.ui.userInterface import launch_gui as UI
if __name__ == "__main__":
UI() #:des

View File

@@ -1,69 +0,0 @@
{
"version": "auto-py-to-exe-configuration_v1",
"pyinstallerOptions": [
{
"optionDest": "noconfirm",
"value": true
},
{
"optionDest": "filenames",
"value": "C:/Users/aky547/GitHub/SemesterapparatsManager/__main__.py"
},
{
"optionDest": "onefile",
"value": false
},
{
"optionDest": "console",
"value": false
},
{
"optionDest": "icon_file",
"value": "C:/Users/aky547/Downloads/VZjRNn1k.ico"
},
{
"optionDest": "name",
"value": "SemesterAppMan"
},
{
"optionDest": "clean_build",
"value": true
},
{
"optionDest": "strip",
"value": false
},
{
"optionDest": "noupx",
"value": false
},
{
"optionDest": "disable_windowed_traceback",
"value": false
},
{
"optionDest": "uac_admin",
"value": false
},
{
"optionDest": "uac_uiaccess",
"value": false
},
{
"optionDest": "argv_emulation",
"value": false
},
{
"optionDest": "bootloader_ignore_signals",
"value": false
},
{
"optionDest": "datas",
"value": "C:/Users/aky547/GitHub/SemesterapparatsManager/config.yaml;."
}
],
"nonPyinstallerOptions": {
"increaseRecursionLimit": true,
"manualArguments": ""
}
}

31
build.py Normal file
View File

@@ -0,0 +1,31 @@
import os
import shutil
with open(".version", "r") as version_file:
version = version_file.read().strip()
print("Building the project...")
print("Cleaning build dir...")
# clear dist directory
if os.path.exists("dist"):
shutil.rmtree("dist")
os.makedirs("dist")
print("Build directory cleaned.")
build = input("Include console in build? (y/n): ").strip().lower()
command = f"uv run python -m nuitka --standalone --output-dir=dist --include-data-dir=./config=config --include-data-dir=./site=site --include-data-dir=./icons=icons --include-data-dir=./mail_vorlagen=mail_vorlagen --enable-plugin=pyside6 --product-name=SemesterApparatsManager --product-version={version} --output-filename=SAM.exe --windows-icon-from-ico=icons/logo.ico"
executable = "main.py"
if build == 'y':
os.system(f"{command} {executable}")
else:
command += " --windows-console-mode=disable"
os.system(f"{command} {executable}")
# rename main.dist in dist dir to SemesterApparatsManager
os.rename("dist/main.dist", "dist/SemesterApparatsManager")
print("Build complete.")

57
config/base_config.yaml Normal file
View File

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

View File

@@ -1,8 +1,19 @@
from typing import Optional
from typing import Optional, Any, Union
from dataclasses import dataclass
from omegaconf import OmegaConf, DictConfig
import os
from pathlib import Path
@dataclass
class OpenAI:
api_key: str
model: str
def getattr(self, name: str):
return getattr(self, name)
def _setattr(self, name: str, value: Any):
setattr(self, name, value)
@dataclass
class Zotero:
@@ -10,33 +21,39 @@ class Zotero:
library_id: str
library_type: str
def getattr(self, name):
def getattr(self, name: str):
return getattr(self, name)
def _setattr(self, name, value):
def _setattr(self, name: str, value: Any):
setattr(self, name, value)
@dataclass
class Database:
name: str
path: str
temp: str
def getattr(self, name):
path: Union[str, Path, None]
temp: Union[str, Path, None]
def getattr(self, name: str):
return getattr(self, name)
def _setattr(self, name, value):
def _setattr(self, name: str, value: Any):
setattr(self, name, value)
def __post_init__(self):
if isinstance(self.path, str):
self.path = Path(self.path).expanduser()
if isinstance(self.temp, str):
self.temp = Path(self.temp).expanduser()
@dataclass
class Mail:
smtp_server: str
port: int
sender: str
sender_name: str
password: str
use_user_name: bool
printer_mail: str
user_name: str
signature: str | None = None
empty_signature = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
@@ -58,13 +75,13 @@ class Mail:
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px;
margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html>"""
def getattr(self, name):
def getattr(self, name: str):
return getattr(self, name)
def _setattr(self, name, value):
def _setattr(self, name: str, value: Any):
setattr(self, name, value)
def setValue(self, **kwargs):
def setValue(self, **kwargs: Any):
for key, value in kwargs.items():
if hasattr(self, key):
setattr(self, key, value)
@@ -78,7 +95,7 @@ class Icons:
self._colors = None
self._icons = None
def assign(self, key, value):
def assign(self, key: str, value: Any):
setattr(self, key, value)
@property
@@ -86,7 +103,7 @@ class Icons:
return self._path
@path.setter
def path(self, value):
def path(self, value: Any):
self._path = value
@property
@@ -94,7 +111,7 @@ class Icons:
return self._colors
@colors.setter
def colors(self, value):
def colors(self, value: Any):
self._colors = value
@property
@@ -102,10 +119,10 @@ class Icons:
return self._icons
@icons.setter
def icons(self, value):
def icons(self, value: Any):
self._icons = value
def get(self, name):
def get(self, name: str):
return self.icons.get(name)
@@ -120,7 +137,7 @@ class Config:
"""
_config: Optional[DictConfig] = None
config_exists: bool = True
def __init__(self, config_path: str):
"""
Loads the configuration file and stores it for future access.
@@ -132,10 +149,22 @@ class Config:
FileNotFoundError: Configuration file not found
"""
if not os.path.exists(config_path):
raise FileNotFoundError(f"Configuration file not found: {config_path}")
# copy base config file to the given path
base = "config/base_config.yaml"
if not os.path.exists(base):
raise FileNotFoundError(f"Base configuration file not found: {base}")
with open(base, "r") as base_file:
base_config = base_file.read()
with open(config_path, "w") as config_file:
config_file.write(base_config)
self.config_exists = False
self._config = OmegaConf.load(config_path)
self.config_path = config_path
@property
def exists(self) -> bool:
return self.config_exists
def save(self):
"""
Saves the current configuration to the file.
@@ -145,16 +174,22 @@ class Config:
"""
OmegaConf.save(self._config, self.config_path)
def reload(self):
"""
Reloads the configuration from the file.
"""
self._config = OmegaConf.load(self.config_path)
@property
def zotero(self):
return Zotero(**self._config.zotero)
@property
def zotero_attr(self, name):
def zotero_attr(self, name: str):
return getattr(self.zotero, name)
@zotero_attr.setter
def zotero_attr(self, name, value):
def zotero_attr(self, name: str, value: Any):
self.zotero._setattr(name, value)
@property
@@ -162,30 +197,37 @@ class Config:
return Database(**self._config.database)
@property
def database_attr(self, name):
def database_attr(self, name: str):
return getattr(self.database, name)
@database_attr.setter
def database_attr(self, name, value):
def database_attr(self, name: str, value: Any):
self.database._setattr(name, value)
@property
def openAI(self):
return OpenAI(**self._config.openAI)
@property
def mail(self):
return Mail(**self._config.mail)
def mail_attr(self, name):
def mail_attr(self, name: str):
return getattr(self.mail, name)
def set_mail_attr(self, name, value):
def set_mail_attr(self, name: str, value: Any):
OmegaConf.update(self._config, f"mail.{name}", value)
def set_database_attr(self, name, value):
def set_database_attr(self, name: str, value: Any):
OmegaConf.update(self._config, f"database.{name}", value)
def set_zotero_attr(self, name, value):
def set_zotero_attr(self, name: str, value: Any):
OmegaConf.update(self._config, f"zotero.{name}", value)
def set_icon_attr(self, name, value):
def set_openai_attr(self, name: str, value: Any):
OmegaConf.update(self._config, f"openAI.{name}", value)
def set_icon_attr(self, name: str, value: Any):
OmegaConf.update(self._config, f"icons.{name}", value)
@property

BIN
docs/images/statistics.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

1
icons/db_search.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M472-120q-73-1-137.5-13.5t-112-34Q175-189 147.5-218T120-280q0 33 27.5 62t75 50.5q47.5 21.5 112 34T472-120Zm-71-204q-30-3-58-8t-53.5-12q-25.5-7-48-15.5T200-379q19 11 41.5 19.5t48 15.5q25.5 7 53.5 12t58 8Zm79-275q86 0 177.5-26T760-679q-11-29-100.5-55T480-760q-91 0-178.5 25.5T200-679q15 29 104.5 54.5T480-599Zm-61 396q10 23 23 44t30 39q-73-1-137.5-13.5t-112-34Q175-189 147.5-218T120-280v-400q0-33 28.5-62t77.5-51q49-22 114.5-34.5T480-840q74 0 139.5 12.5T734-793q49 22 77.5 51t28.5 62q0 33-28.5 62T734-567q-49 22-114.5 34.5T480-520q-85 0-157-15t-123-44v101q40 37 100 54t121 22q-8 15-13 34.5t-7 43.5q-60-7-111.5-20T200-379v99q14 25 77 47t142 30ZM864-40 756-148q-22 13-46 20.5t-50 7.5q-75 0-127.5-52.5T480-300q0-75 52.5-127.5T660-480q75 0 127.5 52.5T840-300q0 26-7.5 50T812-204L920-96l-56 56ZM660-200q42 0 71-29t29-71q0-42-29-71t-71-29q-42 0-71 29t-29 71q0 42 29 71t71 29Z"/></svg>

After

Width:  |  Height:  |  Size: 992 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 264 KiB

After

Width:  |  Height:  |  Size: 264 KiB

1
icons/manage_search.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M80-200v-80h400v80H80Zm0-200v-80h200v80H80Zm0-200v-80h200v80H80Zm744 400L670-354q-24 17-52.5 25.5T560-320q-83 0-141.5-58.5T360-520q0-83 58.5-141.5T560-720q83 0 141.5 58.5T760-520q0 29-8.5 57.5T726-410l154 154-56 56ZM560-400q50 0 85-35t35-85q0-50-35-85t-85-35q-50 0-85 35t-35 85q0 50 35 85t85 35Z"/></svg>

After

Width:  |  Height:  |  Size: 420 B

1
icons/print.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M640-640v-120H320v120h-80v-200h480v200h-80Zm-480 80h640-640Zm560 100q17 0 28.5-11.5T760-500q0-17-11.5-28.5T720-540q-17 0-28.5 11.5T680-500q0 17 11.5 28.5T720-460Zm-80 260v-160H320v160h320Zm80 80H240v-160H80v-240q0-51 35-85.5t85-34.5h560q51 0 85.5 34.5T880-520v240H720v160Zm80-240v-160q0-17-11.5-28.5T760-560H200q-17 0-28.5 11.5T160-520v160h80v-80h480v80h80Z"/></svg>

After

Width:  |  Height:  |  Size: 482 B

1
icons/search_results.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M400-320q100 0 170-70t70-170q0-100-70-170t-170-70q-100 0-170 70t-70 170q0 100 70 170t170 70Zm-40-120v-280h80v280h-80Zm-140 0v-200h80v200h-80Zm280 0v-160h80v160h-80ZM824-80 597-307q-41 32-91 49.5T400-240q-134 0-227-93T80-560q0-134 93-227t227-93q134 0 227 93t93 227q0 56-17.5 106T653-363l227 227-56 56Z"/></svg>

After

Width:  |  Height:  |  Size: 425 B

1
icons/trash.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M280-120q-33 0-56.5-23.5T200-200v-520h-40v-80h200v-40h240v40h200v80h-40v520q0 33-23.5 56.5T680-120H280Zm400-600H280v520h400v-520ZM360-280h80v-360h-80v360Zm160 0h80v-360h-80v360ZM280-720v520-520Z"/></svg>

After

Width:  |  Height:  |  Size: 319 B

33
mail.py
View File

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

View File

@@ -0,0 +1,13 @@
Subject: Bitte um Bestellung von Neuerwerbungen für Semesterapparat {AppNr} - {AppName}
Hallo zusammen,
für den Semesterapparat {AppNr} - {Appname} wurden folgende Neuauflagen gefunden:
{newEditionsOrdered}
Wäre es möglich, diese, oder neuere Auflagen (wenn vorhanden), zu bestellen?
{signature}

View File

@@ -1,37 +1,17 @@
Message-ID: <b44248a9-025e-e86c-85d7-5949534f0ac4@ph-freiburg.de>
Date: Mon, 17 Jul 2023 12:59:04 +0200
MIME-Version: 1.0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101
Thunderbird/102.13.0
Content-Language: de-DE
From: {user_name} <{user_mail}>
Subject: =?UTF-8?Q?Information_bez=c3=bcglich_der_Aufl=c3=b6sung_des_Semeste?=
=?UTF-8?Q?rapparates_=7bAppNr=7d?=
X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; DSN=0; uuencode=0;
attachmentreminder=0; deliveryformat=0
X-Identity-Key: id1
Fcc: imap://aky547@imap.ph-freiburg.de/INBOX/Sent
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: 8bit
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
</head>
<body>{greeting}
<br>
<p>auf die E-Mail bezüglich der Auflösung oder Verlängerung der Semesterapparate haben wir von Ihnen keine Rückmeldung erhalten. Deshalb gehen wir davon aus, dass der Apparat aufgelöst werden kann.</p>
<p> Die Medien, die in den Apparaten aufgestellt waren, werden nun wieder regulär ausleihbar und sind dann an ihren Standorten bei den Fächern zu finden.</p>
<p></p>
<p>Falls Sie den Apparat erneut, oder einen neuen Apparat anlegen wollen, können Sie mir das ausgefüllte Formular zur Einrichtung des Apparates (<a class="moz-txt-link-freetext" href="https://www.ph-freiburg.de/bibliothek/lernen/semesterapparate/info-lehrende-sem.html">https://www.ph-freiburg.de/bibliothek/lernen/semesterapparate/info-lehrende-sem.html</a>) zukommen lassen.</p>
<p>Im Falle einer Verlängerung des Apparates reicht eine Antwort auf diese Mail.<br>
</p>
<p>Bei Fragen können Sie sich jederzeit an mich wenden.<br>
</p>
<p><br>
</p>
<pre class="moz-signature" cols="72">--
{signature}
</pre>
</body>
</html>
Subject: Information bezüglich der Auflösung des Semesterapparates {AppNr}
{greeting}
auf die E-Mail bezüglich der Auflösung oder Verlängerung der Semesterapparate haben wir von Ihnen keine Rückmeldung erhalten. Deshalb gehen wir davon aus, dass der Apparat aufgelöst werden kann.
Die Medien, die in den Apparaten aufgestellt waren, werden nun wieder regulär ausleihbar und sind dann an ihren Standorten bei den Fächern zu finden.
Falls Sie den Apparat erneut, oder einen neuen Apparat anlegen wollen,
können Sie mir das ausgefüllte Formular zur Einrichtung des Apparates
https://www.ph-freiburg.de/bibliothek/lernen/semesterapparate/info-lehrende-sem.html
zukommen lassen. Im Falle einer Verlängerung des Apparates reicht eine Antwort auf diese Mail.
Bei Fragen können Sie sich jederzeit an mich wenden.
{signature}

View File

@@ -1,36 +1,16 @@
Message-ID: <db617c48-29d6-d3d8-a67c-e9a6cf9b5bdb@ph-freiburg.de>
Date: Tue, 12 Sep 2023 13:01:35 +0200
MIME-Version: 1.0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101
Thunderbird/102.15.0
From: Alexander Kirchner <alexander.kirchner@ph-freiburg.de>
Subject: Information zum Semesterapparat {AppNr} - {Appname}
Content-Language: de-DE
X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; DSN=0; uuencode=0;
attachmentreminder=0; deliveryformat=0
X-Identity-Key: id1
Fcc: imap://aky547@imap.ph-freiburg.de/INBOX/Sent
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: 8bit
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
</head>
<body>{greeting}
<br>
<p>Ihr Semesterapparat {Appname} wurde angelegt.</p>
<p>Unter folgendem Link können Sie die Apparate einsehen:</p>
<p><a class="moz-txt-link-freetext" href="https://bsz.ibs-bw.de/aDISWeb/app?service=direct/0/Home/$DirectLink&amp;sp=SOPAC42&amp;sp=SWI00000002&amp;noRedir">https://bsz.ibs-bw.de/aDISWeb/app?service=direct/0/Home/$DirectLink&amp;sp=SOPAC42&amp;sp=SWI00000002&amp;noRedir</a></p>
<p>Ihr Apparat ist unter {AppSubject} > {Profname} > {AppNr} {Appname}.<br>
</p>
<p><br>
</p>
<p>Noch nicht vorhandene Medien wurden vorgemerkt und werden nach Rückkehr in die Bibliothek eingearbeitet.</p>
<p>Bei Fragen können Sie sich per Mail bei mir melden.<br>
</p>
<pre class="moz-signature" cols="72">--
{signature}
</pre>
</body>
</html>
Subject: Information zum Semesterapparat {AppNr} - {AppName}
{greeting}
Ihr Semesterapparat {Appname} wurde angelegt.
Unter folgendem Link können Sie die Apparate einsehen:
https://bsz.ibs-bw.de/aDISWeb/app?service=direct/0/Home/$DirectLink&amp;sp=SOPAC42&amp;sp=SWI00000002&amp;noRedir
Ihr Apparat ist unter {AppSubject} > {Profname} > {AppNr} {Appname}
Noch nicht vorhandene Medien wurden vorgemerkt und werden nach Rückkehr in die Bibliothek eingearbeitet.
Bei Fragen können Sie sich per Mail bei mir melden.
{signature}

View File

@@ -0,0 +1,10 @@
Subject: Information zur Auflösung des Semesterapparates {AppNr} - {Appname}
{greeting}
Ihr Semesterapparat "{Appname} ({AppNr})" wurde wie besprochen aufgelöst.
Die Medien sind von nun an wieder in den Regalen zu finden.
{signature}

View File

@@ -0,0 +1,15 @@
Subject: Neuauflagen für Semesterapparat {AppNr} - {AppName}
{greeting}
Für Ihren Semesterapparat {AppNr} - {Appname} wurden folgende Neuauflagen gefunden:
{newEditions}
Sollen wir die alte(n) Auflage(n) aus dem Apparat durch diese austauschen?
Nicht vorhandene Exemplare werden an die Erwerbungsabteilung weitergegeben
und nach Erhalt der Medien in den Apparat eingearbeitet.
{signature}

View File

@@ -0,0 +1,9 @@
Subject: CHANGEME
{greeting}
{signature}

20
main.py
View File

@@ -1,4 +1,22 @@
import sys
from PySide6 import QtWidgets
from src import first_launch, settings
from src.shared.logging import configure
from src.ui.userInterface import launch_gui as UI
from src.ui.widgets.welcome_wizard import launch_wizard as startup
if __name__ == "__main__":
UI()
configure("INFO")
app = QtWidgets.QApplication(sys.argv)
if not first_launch:
setup = startup()
if setup == 1:
settings.reload()
# kill qApplication singleton
UI()
else:
sys.exit()
else:
UI()

View File

@@ -1,26 +1,29 @@
[project]
name = "semesterapparatsmanager"
version = "0.1.0"
version = "1.0.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
requires-python = ">=3.13"
dependencies = [
"beautifulsoup4>=4.12.3",
"appdirs>=1.4.4",
"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",
"mkdocs-material-extensions>=1.3.1",
"natsort>=8.4.0",
"omegaconf>=2.3.0",
"openai>=1.79.0",
"pandas>=2.2.3",
"playwright>=1.49.1",
"pyqt6>=6.8.0",
"pyqtgraph>=0.13.7",
"pyramid>=2.0.2",
"pyside6>=6.9.1",
"python-docx>=1.1.2",
"pyzotero>=1.6.4",
"ratelimit>=2.2.1",
@@ -33,3 +36,36 @@ dev = [
"icecream>=2.1.4",
"nuitka>=2.5.9",
]
swbtest = [
"alive-progress>=3.3.0",
]
[tool.bumpversion]
current_version = "1.0.0"
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
serialize = ["{major}.{minor}.{patch}"]
search = "{current_version}"
replace = "{new_version}"
regex = false
ignore_missing_version = false
ignore_missing_files = false
tag = true
sign_tags = false
tag_name = "v{new_version}"
tag_message = "Bump version: {current_version} → {new_version}"
allow_dirty = true
commit = true
message = "Bump version: {current_version} → {new_version}"
moveable_tags = []
commit_args = ""
setup_hooks = []
pre_commit_hooks = []
post_commit_hooks = []
[[tool.bumpversion.files]]
filename = "src/__init__.py"
[[tool.bumpversion.files]]
filename = ".version"
[[tool.uv.index]]
url = "https://git.theprivateserver.de/api/packages/WorldTeacher/pypi/simple/"
default = false

View File

@@ -1,27 +1,35 @@
import sys
from config import Config
import os
from loguru import logger as log
from datetime import datetime
settings = Config("config/config.yaml")
from .utils.icon import Icon
__version__ = "0.2.0-dev0"
__version__ = "1.0.0"
__author__ = "Alexander Kirchner"
__all__ = ["__version__", "__author__", "Icon", "settings"]
import os
from pathlib import Path
from typing import Union
from appdirs import AppDirs
from config import Config
app = AppDirs("SemesterApparatsManager", "SAM")
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: Union[Path, str] = ( # type: ignore
app.user_config_dir if settings.database.path is None else settings.database.path # type: ignore
)
if not os.path.exists(DATABASE_DIR): # type: ignore
os.makedirs(DATABASE_DIR) # type: ignore
first_launch = settings.exists
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"):
os.mkdir("logs")
# open and close the file to create it
logger = log
logger.remove()
logger.add("logs/application.log", rotation="1 week", enqueue=True)
log.add(
f"logs/{datetime.now().strftime('%Y-%m-%d')}.log",
rotation="1 day",
compression="zip",
)
# logger.add(sys.stderr, format="{time} {level} {message}", level="INFO")
logger.add(sys.stdout)

View File

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

View File

@@ -2,6 +2,14 @@ import hashlib
import random
from .database import Database
import loguru
import sys
from src import LOG_DIR
log = loguru.logger
log.remove()
log.add(sys.stdout, level="INFO")
log.add(f"{LOG_DIR}/application.log", rotation="1 MB", retention="10 days")
# change passwords for apparats, change passwords for users, list users, create and delete users etc
@@ -9,9 +17,14 @@ from .database import Database
class AdminCommands:
"""Basic Admin commands for the admin console. This class is used to create, delete, and list users. It also has the ability to change passwords for users."""
def __init__(self):
"""Defaulf Constructor for the AdminCommands class."""
self.db = Database()
def __init__(self, db_path=None):
"""Default Constructor for the AdminCommands class."""
if db_path is None:
self.db = Database()
else:
self.db = Database(db_path=db_path)
log.info("AdminCommands initialized with database connection.")
log.debug("location: {}", self.db.db_path)
def create_password(self, password: str) -> tuple[str, str]:
"""Create a hashed password and a salt for the password.
@@ -44,6 +57,20 @@ class AdminCommands:
hashed_password = self.hash_password("admin")
self.db.createUser("admin", salt + hashed_password, "admin", salt)
def create_user(self, username: str, password: str, role: str = "user") -> bool:
"""Create a new user in the database.
Args:
username (str): the username of the user to be created.
password (str): the password of the user to be created.
role (str, optional): the role of the user to be created. Defaults to "user".
"""
hashed_password, salt = self.create_password(password)
status = self.db.createUser(
user=username, password=salt + hashed_password, role=role, salt=salt
)
return status
def hash_password(self, password: str) -> str:
"""Hash a password using SHA256.

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

@@ -0,0 +1,292 @@
from typing import List
import regex
import requests
from bs4 import BeautifulSoup
from src.logic import BookData as Book
from src.shared.logging import log
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"
class Catalogue:
def __init__(self, timeout=15):
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) -> List[str]:
response = self.search_book(searchterm)
soup = BeautifulSoup(response, "html.parser")
links = soup.find_all("a", class_="title getFull")
res: List[str] = []
for link in links:
res.append(BASE + link["href"]) # type: ignore
return res
def get_book(self, searchterm: str):
log.info(f"Searching for term: {searchterm}")
links = self.get_book_links(searchterm)
print(links)
for elink in links:
result = self.search(elink)
# in result search for class col-xs-12 rds-dl RDS_LOCATION
# if found, return text of href
soup = BeautifulSoup(result, "html.parser")
# Optional (unchanged): title and ppn if you need them
title_el = soup.find("div", class_="headline text")
title = title_el.get_text(strip=True) if title_el else None
ppn_el = soup.find(
"div", class_="col-xs-12 col-md-5 col-lg-4 rds-dl-head RDS_PPN"
)
# in ppn_el, get text of div col-xs-12 col-md-7 col-lg-8 rds-dl-panel
ppn = (
ppn_el.find_next_sibling(
"div", class_="col-xs-12 col-md-7 col-lg-8 rds-dl-panel"
).get_text(strip=True)
if ppn_el
else None
)
# get edition text at div class col-xs-12 col-md-5 col-lg-4 rds-dl-head RDS_EDITION
edition_el = soup.find(
"div", class_="col-xs-12 col-md-5 col-lg-4 rds-dl-head RDS_EDITION"
)
edition = (
edition_el.find_next_sibling(
"div", class_="col-xs-12 col-md-7 col-lg-8 rds-dl-panel"
).get_text(strip=True)
if edition_el
else None
)
authors = soup.find_all(
"div", class_="col-xs-12 col-md-5 col-lg-4 rds-dl-head RDS_PERSON"
)
author = None
if authors:
# get the names of the a href links in the div col-xs-12 col-md-7 col-lg-8 rds-dl-panel
author_names = []
for author in authors:
panel = author.find_next_sibling(
"div", class_="col-xs-12 col-md-7 col-lg-8 rds-dl-panel"
)
if panel:
links = panel.find_all("a")
for link in links:
author_names.append(link.text.strip())
author = (
";".join(author_names) if len(author_names) > 1 else author_names[0]
)
signature = None
panel = soup.select_one("div.panel-body")
if panel:
# Collect the RDS_* blocks in order, using the 'space' divs as separators
groups = []
cur = {}
for node in panel.select(
"div.rds-dl.RDS_SIGNATURE, div.rds-dl.RDS_STATUS, div.rds-dl.RDS_LOCATION, div.col-xs-12.space"
):
classes = node.get("class", [])
# Separator between entries
if "space" in classes:
if cur:
groups.append(cur)
cur = {}
continue
# Read the value from the corresponding panel cell
val_el = node.select_one(".rds-dl-panel")
val = (
val_el.get_text(" ", strip=True)
if val_el
else node.get_text(" ", strip=True)
)
if "RDS_SIGNATURE" in classes:
cur["signature"] = val
elif "RDS_STATUS" in classes:
cur["status"] = val
elif "RDS_LOCATION" in classes:
cur["location"] = val
if cur: # append the last group if not followed by a space
groups.append(cur)
# Find the signature for the entry whose location mentions "Semesterapparat"
for g in groups:
loc = g.get("location", "").lower()
if "semesterapparat" in loc:
signature = g.get("signature")
return Book(
title=title,
ppn=ppn,
signature=signature,
library_location=loc.split("-")[-1],
link=elink,
author=author,
edition=edition,
)
else:
return Book(
title=title,
ppn=ppn,
signature=signature,
library_location=loc.split("\n\n")[-1],
link=elink,
author=author,
edition=edition,
)
def get(self, ppn: str) -> Book | None:
# based on PPN, get title, people, edition, year, language, pages, isbn,
link = f"https://rds.ibs-bw.de/phfreiburg/opac/RDSIndexrecord/{ppn}"
result = self.search(link)
soup = BeautifulSoup(result, "html.parser")
def get_ppn(self, searchterm: str) -> str | None:
links = self.get_book_links(searchterm)
ppn = None
for link in links:
result = self.search(link)
soup = BeautifulSoup(result, "html.parser")
print(link)
ppn = link.split("/")[-1]
if ppn and regex.match(r"^\d{8,10}[X\d]?$", ppn):
return ppn
return ppn
def get_semesterapparat_number(self, searchterm: str) -> int:
links = self.get_book_links(searchterm)
for link in links:
result = self.search(link)
# in result search for class col-xs-12 rds-dl RDS_LOCATION
# if found, return text of href
soup = BeautifulSoup(result, "html.parser")
locations = soup.find_all("div", class_="col-xs-12 rds-dl RDS_LOCATION")
for location_el in locations:
if "Semesterapparat-" in location_el.text:
match = regex.search(r"Semesterapparat-(\d+)", location_el.text)
if match:
return int(match.group(1))
if "Handbibliothek-" in location_el.text:
return location_el.text.strip().split("\n\n")[-1].strip()
return location_el.text.strip().split("\n\n")[-1].strip()
return 0
def get_author(self, link: str) -> str:
links = self.get_book_links(f"kid:{link}")
author = None
for link in links:
# print(link)
result = self.search(link)
soup = BeautifulSoup(result, "html.parser")
# get all authors, return them as a string seperated by ;
authors = soup.find_all(
"div", class_="col-xs-12 col-md-5 col-lg-4 rds-dl-head RDS_PERSON"
)
if authors:
# get the names of the a href links in the div col-xs-12 col-md-7 col-lg-8 rds-dl-panel
author_names = []
for author in authors:
panel = author.find_next_sibling(
"div", class_="col-xs-12 col-md-7 col-lg-8 rds-dl-panel"
)
if panel:
links = panel.find_all("a")
for link in links:
author_names.append(link.text.strip())
author = "; ".join(author_names)
return author
def get_signature(self, isbn: str):
links = self.get_book_links(f"{isbn}")
signature = None
for link in links:
result = self.search(link)
soup = BeautifulSoup(result, "html.parser")
panel = soup.select_one("div.panel-body")
if panel:
# Collect the RDS_* blocks in order, using the 'space' divs as separators
groups = []
cur = {}
for node in panel.select(
"div.rds-dl.RDS_SIGNATURE, div.rds-dl.RDS_STATUS, div.rds-dl.RDS_LOCATION, div.col-xs-12.space"
):
classes = node.get("class", [])
# Separator between entries
if "space" in classes:
if cur:
groups.append(cur)
cur = {}
continue
# Read the value from the corresponding panel cell
val_el = node.select_one(".rds-dl-panel")
val = (
val_el.get_text(" ", strip=True)
if val_el
else node.get_text(" ", strip=True)
)
if "RDS_SIGNATURE" in classes:
cur["signature"] = val
elif "RDS_STATUS" in classes:
cur["status"] = val
elif "RDS_LOCATION" in classes:
cur["location"] = val
if cur: # append the last group if not followed by a space
groups.append(cur)
# Find the signature for the entry whose location mentions "Semesterapparat"
for g in groups:
print(g)
loc = g.get("location", "").lower()
if "semesterapparat" in loc:
signature = g.get("signature")
return signature
else:
signature = g.get("signature")
return signature
print("No signature found")
return signature
def in_library(self, ppn: str) -> bool:
if ppn is None:
return False
links = self.get_book_links(f"kid:{ppn}")
return len(links) > 0
def get_location(self, ppn: str) -> str | None:
if ppn is None:
return None
link = self.get_book(f"{ppn}")
if link is None:
return None
return link.library_location

View File

@@ -4,10 +4,18 @@ from pathlib import Path
from src.backend.database import Database
import loguru
import sys
from src import LOG_DIR
log = loguru.logger
log.remove()
log.add(sys.stdout, level="INFO")
log.add(f"{LOG_DIR}/application.log", rotation="1 MB", retention="10 days")
db = Database()
def recreateFile(name, app_id, filetype, open=True) -> Path:
def recreateFile(name: str, app_id: int, filetype: str, open: bool = True) -> Path:
"""
recreateFile creates a file from the database and opens it in the respective program, if the open parameter is set to True.
@@ -24,6 +32,7 @@ def recreateFile(name, app_id, filetype, open=True) -> Path:
"""
path = db.recreateFile(name, app_id, filetype=filetype)
path = Path(path)
log.info(f"File created: {path}")
if open:
if os.getenv("OS") == "Windows_NT":
path = path.resolve()

File diff suppressed because it is too large Load Diff

View File

@@ -12,12 +12,12 @@ CREATE_TABLE_APPARAT = """CREATE TABLE semesterapparat (
deleted_date TEXT,
apparat_id_adis INTEGER,
prof_id_adis INTEGER,
konto INTEGER REFERENCES app_kontos (id),
konto INTEGER,
FOREIGN KEY (prof_id) REFERENCES prof (id)
)"""
CREATE_TABLE_MEDIA = """CREATE TABLE media (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
bookdata BLOB,
bookdata TEXT,
app_id INTEGER,
prof_id INTEGER,
deleted INTEGER DEFAULT (0),
@@ -26,13 +26,7 @@ CREATE_TABLE_MEDIA = """CREATE TABLE media (
FOREIGN KEY (prof_id) REFERENCES prof (id),
FOREIGN KEY (app_id) REFERENCES semesterapparat (id)
)"""
CREATE_TABLE_APPKONTOS = """CREATE TABLE app_kontos (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
app_id INTEGER,
konto INTEGER,
passwort TEXT,
FOREIGN KEY (app_id) REFERENCES semesterapparat (id)
)"""
CREATE_TABLE_FILES = """CREATE TABLE files (
id INTEGER PRIMARY KEY,
filename TEXT,
@@ -107,3 +101,12 @@ CREATE_ELSA_MEDIA_TABLE = """CREATE TABLE elsa_media (
elsa_id INTEGER NOT NULL,
FOREIGN KEY (elsa_id) REFERENCES elsa (id)
)"""
CREATE_TABLE_NEWEDITIONS = """CREATE TABLE neweditions (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
new_bookdata TEXT,
old_edition_id INTEGER,
for_apparat INTEGER,
ordered BOOLEAN DEFAULT (0),
FOREIGN KEY (old_edition_id) REFERENCES media (id),
FOREIGN KEY (for_apparat) REFERENCES semesterapparat (id)
)"""

View File

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

View File

@@ -1,11 +1,23 @@
from PyQt6.QtCore import QThread
from src.utils.documentation import run_mkdocs
from PySide6.QtCore import QThread, Slot
from src.utils.documentation import website, QuietHandler
from wsgiref.simple_server import make_server
class DocumentationThread(QThread):
def __init__(self):
super().__init__()
self._server = None # store server so we can shut it down
def run(self):
# launch_documentation()
run_mkdocs()
self._server = make_server(
"localhost", 8000, website(), handler_class=QuietHandler
)
while not self.isInterruptionRequested():
self._server.handle_request()
@Slot() # slot you can connect to aboutToQuit
def stop(self):
self.requestInterruption() # ask the loop above to exit
if self._server:
self._server.shutdown() # unblock handle_request()

View File

@@ -1,128 +0,0 @@
import datetime
from src import logger
from dataclasses import dataclass
@dataclass
class Semester:
logger.debug("Semester class loaded")
_year: int | None = str(datetime.datetime.now().year)[2:]
_semester: str | None = None
_month: int | None = datetime.datetime.now().month
value: str = None
def __post_init__(self):
if isinstance(self._year, str):
self._year = int(self._year)
if self._year is None:
self._year = datetime.datetime.now().year[2:]
if self._month is None:
self._month = datetime.datetime.now().month
if self._semester is None:
self.generateSemester()
self.computeValue()
def __str__(self):
return self.value
def generateSemester(self):
if self._month < 4 or self._month < 9:
self._semester = "WiSe"
else:
self._semester = "SoSe"
@logger.catch
def computeValue(self):
# year is only last two digits
year = self._year
if self._semester == "WiSe":
if self._month < 4:
valueyear = str(year - 1) + "/" + str(year)
else:
valueyear = str(year)
self.value = f"{self._semester} {valueyear}"
@logger.catch
def offset(self, value: int) -> str:
"""Generate a new Semester object by offsetting the current semester by a given value
Args:
value (int): The value by which the semester should be offset
Returns:
str: the new semester value
"""
assert isinstance(value, int), "Value must be an integer"
if value == 0:
return self
if value > 0:
if value % 2 == 0:
return Semester(
self._year - value // 2, self._semester - value // 2 + 1
)
else:
semester = self._semester
semester = "SoSe" if semester == "WiSe" else "WiSe"
return Semester(self._year + value // 2, semester)
else:
if value % 2 == 0:
return Semester(self.year + value // 2, self._semester)
else:
semester = self._semester
semester = "SoSe" if semester == "WiSe" else "WiSe"
return Semester(self._year + value // 2, semester)
def isPastSemester(self, semester) -> bool:
"""Checks if the current Semester is a past Semester compared to the given Semester
Args:
semester (str): The semester to compare to
Returns:
bool: True if the current semester is in the past, False otherwise
"""
if self.year < semester.year:
return True
if self.year == semester.year:
if self.semester == "WiSe" and semester.semester == "SoSe":
return True
return False
def isFutureSemester(self, semester: "Semester") -> bool:
"""Checks if the current Semester is a future Semester compared to the given Semester
Args:
semester (str): The semester to compare to
Returns:
bool: True if the current semester is in the future, False otherwise
"""
if self.year > semester.year:
return True
if self.year == semester.year:
if self.semester == "SoSe" and semester.semester == "WiSe":
return True
return False
def from_string(self, val):
self.value = val
self._year = int(val[-2:])
self._semester = val[:4]
return self
@property
def next(self):
return self.offset(1)
@property
def previous(self):
return self.offset(-1)
@property
def year(self):
return self._year
@property
def semester(self):
return self._semester

View File

@@ -1,55 +1,60 @@
import sqlite3
from PySide6.QtCore import QThread, Signal
from PyQt6.QtCore import QThread
from PyQt6.QtCore import pyqtSignal as Signal
from src.backend import Database
from src.logic.webrequest import BibTextTransformer, WebRequest
from src.shared.logging import log
# Logger configured centrally in main; this module just uses `log`
class BookGrabber(QThread):
updateSignal = Signal(int, int)
done = Signal()
def __init__(self, appnr):
def __init__(self):
super(BookGrabber, self).__init__(parent=None)
self.is_Running = True
logger.info("Starting worker thread")
self.data = None
log.info("Starting worker thread")
self.data = []
self.app_id = None
self.prof_id = None
self.mode = None
self.book_id = None
self.use_any = False
self.use_exact = False
self.appnr = appnr
self.app_nr = None
self.tstate = (self.app_id, self.prof_id, self.mode, self.data)
self.request = WebRequest()
self.db = Database()
def add_values(self, app_id, prof_id, mode, data, any_book=False, exact=False):
def add_values(
self, app_id: int, prof_id: int, mode: str, data, any_book=False, exact=False
):
self.app_id = app_id
self.prof_id = prof_id
self.mode = mode
self.data = data
self.data: list[str] = data
self.use_any = any_book
self.use_exact = exact
logger.info(f"Working on {len(self.data)} entries")
self.tstate = (self.app_id, self.prof_id, self.mode, self.data)
logger.debug("State: " + str(self.tstate))
# print(self.tstate)
log.info(f"Working on {len(self.data)} entries")
self.tstate = (self.app_nr, self.prof_id, self.mode, self.data)
log.debug("State: " + str(self.tstate))
app_nr = self.db.query_db(
"SELECT appnr FROM semesterapparat WHERE id = ?", (self.app_id,)
)[0][0]
self.request.set_apparat(app_nr)
# log.debug(self.tstate)
def run(self):
self.db = Database()
item = 0
iterdata = self.data
# print(iterdata)
if self.prof_id is None:
self.prof_id = self.db.getProfNameByApparat(self.app_id)
for entry in iterdata:
# print(entry)
signature = str(entry)
logger.info("Processing entry: " + signature)
# log.debug(iterdata)
webdata = WebRequest().set_apparat(self.appnr).get_ppn(entry)
for entry in iterdata:
# log.debug(entry)
log.info("Processing entry: {}", entry)
webdata = self.request.get_ppn(entry)
if self.use_any:
webdata = webdata.use_any_book
webdata = webdata.get_data()
@@ -58,12 +63,12 @@ class BookGrabber(QThread):
continue
bd = BibTextTransformer(self.mode)
print(webdata)
log.debug(webdata)
if self.mode == "ARRAY":
if self.use_exact:
bd = bd.use_signature(entry)
bd = bd.get_data(webdata).return_data()
print(bd)
log.debug(bd)
if bd is None:
# bd = BookData
continue
@@ -76,25 +81,29 @@ class BookGrabber(QThread):
self.db.addBookToDatabase(bd, self.app_id, self.prof_id)
# get latest book id
self.book_id = self.db.getLastBookId()
logger.info("Added book to database")
log.info("Added book to database")
state = 0
for result in transformer.RDS_DATA:
# print(result.RDS_LOCATION)
if str(self.app_id) in result.RDS_LOCATION:
# log.debug(result.RDS_LOCATION)
if str(self.app_nr) in result.RDS_LOCATION:
state = 1
break
logger.info(f"State of {signature}: {state}")
# print("updating availability of " + str(self.book_id) + " to " + str(state))
log.info(f"State of {entry}: {state}")
log.debug(
"updating availability of " + str(self.book_id) + " to " + str(state)
)
try:
self.db.setAvailability(self.book_id, state)
except sqlite3.OperationalError as e:
logger.error(f"Failed to update availability: {e}")
log.debug("Added book to database")
except Exception as e:
log.error(f"Failed to update availability: {e}")
log.debug("Failed to update availability: " + str(e))
# time.sleep(5)
item += 1
self.updateSignal.emit(item, len(self.data))
logger.info("Worker thread finished")
log.info("Worker thread finished")
# self.done.emit()
self.quit()
@@ -102,87 +111,89 @@ class BookGrabber(QThread):
self.is_Running = False
# class BookGrabber(object):
# updateSignal = Signal(int, int)
# done = Signal()
class BookGrabberTest(QThread):
updateSignal = Signal(int, int)
done = Signal()
# def __init__(self, app_id, prof_id, mode, data, parent=None):
# super(BookGrabber, self).__init__(parent=None)
# self.is_Running = True
# logger = MyLogger("Worker")
# logger.info("Starting worker thread")
# self.data = data
# logger.info(f"Working on {len(self.data)} entries")
# self.app_id = app_id
# self.prof_id = prof_id
# self.mode = mode
# self.book_id = None
# self.state = (self.app_id, self.prof_id, self.mode, self.data)
# # print(self.state)
# logger.info("state: " + str(self.state))
# # time.sleep(2)
def __init__(self, appnr: int):
super(BookGrabberTest, self).__init__(parent=None)
self.is_Running = True
log.info("Starting worker thread")
self.data = None
self.app_nr = None
self.prof_id = None
self.mode = None
self.book_id = None
self.use_any = False
self.use_exact = False
self.app_nr = appnr
self.tstate = (self.app_nr, self.prof_id, self.mode, self.data)
self.results = []
# def resetValues(self):
# self.app_id = None
# self.prof_id = None
# self.mode = None
# self.data = None
# self.book_id = None
def add_values(
self, app_nr: int, prof_id: int, mode: str, data, any_book=False, exact=False
):
self.app_nr = app_nr
self.prof_id = prof_id
self.mode = mode
self.data = data
self.use_any = any_book
self.use_exact = exact
log.info(f"Working on {len(self.data)} entries")
self.tstate = (self.app_nr, self.prof_id, self.mode, self.data)
log.debug("State: " + str(self.tstate))
# log.debug(self.tstate)
# def run(self):
# while self.is_Running:
# self.db = Database()
# item = 0
# iterdata = self.data
# # print(iterdata)
# for entry in iterdata:
# # print(entry)
# signature = str(entry)
# logger.info("Processing entry: " + signature)
def run(self):
item = 0
iterdata = self.data
# log.debug(iterdata)
for entry in iterdata:
# log.debug(entry)
signature = str(entry)
log.info("Processing entry: " + signature)
# webdata = WebRequest().get_ppn(entry).get_data()
# if webdata == "error":
# continue
# bd = BibTextTransformer(self.mode).get_data(webdata).return_data()
# transformer = BibTextTransformer("RDS")
# rds = transformer.get_data(webdata).return_data("rds_availability")
# bd.signature = entry
# # confirm lock is acquired
# self.db.addBookToDatabase(bd, self.app_id, self.prof_id)
# # get latest book id
# self.book_id = self.db.getLastBookId()
# logger.info("Added book to database")
# state = 0
# # print(len(rds.items))
# for rds_item in rds.items:
# sign = rds_item.superlocation
# loc = rds_item.location
# # logger.debug(sign, loc)
# # logger.debug(rds_item)
# if self.app_id in sign or self.app_id in loc:
# state = 1
# break
webdata = WebRequest().set_apparat(self.app_nr).get_ppn(entry)
if self.use_any:
webdata = webdata.use_any_book
webdata = webdata.get_data()
# logger.info(f"State of {signature}: {state}")
# # print(
# "updating availability of "
# + str(self.book_id)
# + " to "
# + str(state)
# )
# try:
# self.db.setAvailability(self.book_id, state)
# except sqlite3.OperationalError as e:
# logger.error(f"Failed to update availability: {e}")
if webdata == "error":
continue
# # time.sleep(5)
# item += 1
# self.updateSignal.emit(item, len(self.data))
# logger.info("Worker thread finished")
# # self.done.emit()
# self.stop()
# if not self.is_Running:
# break
bd = BibTextTransformer(self.mode)
if self.mode == "ARRAY":
if self.use_exact:
bd = bd.use_signature(entry)
bd = bd.get_data(webdata).return_data()
if bd is None:
# bd = BookData
continue
bd.signature = entry
transformer = (
BibTextTransformer("RDS").get_data(webdata).return_data("rds_data")
)
# def stop(self):
# self.is_Running = False
# confirm lock is acquired
# get latest book id
log.info("Added book to database")
state = 0
for result in transformer.RDS_DATA:
# log.debug(result.RDS_LOCATION)
if str(self.app_nr) in result.RDS_LOCATION:
state = 1
break
log.info(f"State of {signature}: {state}")
# log.debug("updating availability of " + str(self.book_id) + " to " + str(state))
self.results.append(bd)
# time.sleep(5)
item += 1
self.updateSignal.emit(item, len(self.data))
log.info("Worker thread finished")
# self.done.emit()
self.quit()
def stop(self):
self.is_Running = False

View File

@@ -0,0 +1,345 @@
import os
import re
from concurrent.futures import ThreadPoolExecutor
from math import ceil
from queue import Empty, Queue
from time import monotonic # <-- NEW
from typing import List, Optional
from PySide6.QtCore import QThread, Signal
# from src.logic.webrequest import BibTextTransformer, WebRequest
from src.backend.catalogue import Catalogue
from src.logic import BookData
from src.logic.SRU import SWB
from src.shared.logging import log
# use all available cores - 2, but at least 1
THREAD_COUNT = max(os.cpu_count() - 2, 1)
THREAD_MIN_ITEMS = 5
# Logger configured centrally in main; use shared `log`
swb = SWB()
dnb = SWB()
cat = Catalogue()
RVK_ALLOWED = r"[A-Z0-9.\-\/]" # conservative RVK character set
def find_newer_edition(
swb_result: BookData, dnb_result: List[BookData]
) -> Optional[List[BookData]]:
"""
New edition if:
- year > swb.year OR
- edition_number > swb.edition_number
BUT: discard any candidate with year < swb.year (if both years are known).
Same-work check:
- Compare RVK roots of signatures (after stripping trailing '+N' and '(N)').
- If both have signatures and RVKs differ -> skip.
Preferences (in order):
1) RVK matches SWB
2) Print over Online-Ressource
3) Has signature
4) Newer: (year desc, edition_number desc)
"""
def strip_copy_and_edition(s: str) -> str:
s = re.sub(r"\(\s*\d+\s*\)", "", s) # remove '(N)'
s = re.sub(r"\s*\+\s*\d+\s*$", "", s) # remove trailing '+N'
return s
def extract_rvk_root(sig: Optional[str]) -> str:
if not sig:
return ""
t = strip_copy_and_edition(sig.upper())
t = re.sub(r"\s+", " ", t).strip()
m = re.match(rf"^([A-Z]{{1,3}}\s*{RVK_ALLOWED}*)", t)
if not m:
cleaned = re.sub(rf"[^{RVK_ALLOWED} ]+", "", t).strip()
return cleaned.split(" ")[0] if cleaned else ""
return re.sub(r"\s+", " ", m.group(1)).strip()
def has_sig(b: BookData) -> bool:
return bool(getattr(b, "signature", None))
def is_online(b: BookData) -> bool:
return (getattr(b, "media_type", None) or "").strip() == "Online-Ressource"
def is_print(b: BookData) -> bool:
return not is_online(b)
def rvk_matches_swb(b: BookData) -> bool:
if not has_sig(b) or not has_sig(swb_result):
return False
return extract_rvk_root(b.signature) == extract_rvk_root(swb_result.signature)
def strictly_newer(b: BookData) -> bool:
# Hard guard: if both years are known and candidate is older, discard
if (
b.year is not None
and swb_result.year is not None
and b.year < swb_result.year
):
return False
newer_by_year = (
b.year is not None
and swb_result.year is not None
and b.year > swb_result.year
)
newer_by_edition = (
b.edition_number is not None
and swb_result.edition_number is not None
and b.edition_number > swb_result.edition_number
)
# Thanks to the guard above, newer_by_edition can't pick something with a smaller year.
return newer_by_year or newer_by_edition
swb_has_sig = has_sig(swb_result)
swb_rvk = extract_rvk_root(getattr(swb_result, "signature", None))
# 1) Filter: same work (by RVK if both have sigs) AND strictly newer
candidates: List[BookData] = []
for b in dnb_result:
if has_sig(b) and swb_has_sig:
if extract_rvk_root(b.signature) != swb_rvk:
continue # different work
if strictly_newer(b):
candidates.append(b)
if not candidates:
return None
# 2) Dedupe by PPN → prefer (rvk-match, is-print, has-signature)
def pref_score(x: BookData) -> tuple[int, int, int]:
return (
1 if rvk_matches_swb(x) else 0,
1 if is_print(x) else 0,
1 if has_sig(x) else 0,
)
by_ppn: dict[Optional[str], BookData] = {}
for b in candidates:
key = getattr(b, "ppn", None)
prev = by_ppn.get(key)
if prev is None or pref_score(b) > pref_score(prev):
by_ppn[key] = b
deduped = list(by_ppn.values())
if not deduped:
return None
# 3) Preserve all qualifying newer editions, but order by preference
def sort_key(b: BookData):
year = b.year if b.year is not None else -1
ed = b.edition_number if b.edition_number is not None else -1
return (
1 if rvk_matches_swb(b) else 0,
1 if is_print(b) else 0,
1 if has_sig(b) else 0,
year,
ed,
)
deduped.sort(key=sort_key, reverse=True)
return deduped
class NewEditionCheckerThread(QThread):
updateSignal = Signal(int, int) # (processed, total)
updateProgress = Signal(int, int) # (processed, total)
total_entries_signal = Signal(int)
resultsSignal = Signal(list) # list[tuple[BookData, list[BookData]]]
# NEW: metrics signals
rateSignal = Signal(float) # items per second ("it/s")
etaSignal = Signal(int) # seconds remaining (-1 when unknown)
def __init__(self, entries: Optional[list["BookData"]] = None, parent=None):
super().__init__(parent)
self.entries: list["BookData"] = entries if entries is not None else []
self.results: list[tuple["BookData", list["BookData"]]] = []
def reset(self):
self.entries = []
self.results = []
# ---------- internal helpers ----------
@staticmethod
def _split_evenly(items: list, parts: int) -> list[list]:
"""Split items as evenly as possible into `parts` chunks (no empty tails)."""
if parts <= 1 or len(items) <= 1:
return [items]
n = len(items)
base = n // parts
extra = n % parts
chunks = []
i = 0
for k in range(parts):
size = base + (1 if k < extra else 0)
if size == 0:
continue
chunks.append(items[i : i + size])
i += size
return chunks
@staticmethod
def _clean_title(raw: str) -> str:
title = raw.rstrip(" .:,;!?")
title = re.sub(r"\s*\(.*\)", "", title)
return title.strip()
@classmethod
def _process_book(
cls, book: "BookData"
) -> tuple["BookData", list["BookData"]] | None:
"""Process one book; returns (original, [found editions]) or None on failure."""
if not book.title:
return None
response: list["BookData"] = []
query = [
f"pica.tit={book.title}",
f"pica.vlg={book.publisher}",
]
swb_result = swb.getBooks(["pica.bib=20735", f"pica.ppn={book.ppn}"])[0]
dnb_results = swb.getBooks(query)
new_editions = find_newer_edition(swb_result, dnb_results)
if new_editions is not None:
for new_edition in new_editions:
new_edition.library_location = cat.get_location(new_edition.ppn)
try:
isbn = (
str(new_edition.isbn[0])
if isinstance(new_edition.isbn, list)
else str(new_edition.isbn)
)
new_edition.link = (
f"https://www.lehmanns.de/search/quick?mediatype_id=2&q={isbn}"
)
except (IndexError, TypeError):
isbn = None
new_edition.in_library = cat.in_library(new_edition.ppn)
response = new_editions
# client = SWB()
# response: list["BookData"] = []
# # First, search by title only
# results = client.getBooks([f"pica.title={title}", f"pica.vlg={book.publisher}"])
# lehmanns = LehmannsClient()
# results = lehmanns.search_by_title(title)
# for result in results:
# if "(eBook)" in result.title:
# result.title = result.title.replace("(eBook)", "").strip()
# swb_results = client.getBooks(
# [
# f"pica.tit={result.title}",
# f"pica.vlg={result.publisher.split(',')[0]}",
# ]
# )
# for swb in swb_results:
# if swb.isbn == result.isbn:
# result.ppn = swb.ppn
# result.signature = swb.signature
# response.append(result)
# if (result.edition_number < swb.edition_number) and (
# swb.year > result.year
# ):
# response.append(result)
if response == []:
return None
# Remove duplicates based on ppn
return (book, response)
@classmethod
def _worker(cls, items: list["BookData"], q: Queue) -> None:
"""Worker for one chunk; pushes ('result', ...), ('progress', 1), and ('done', None)."""
try:
for book in items:
try:
result = cls._process_book(book)
except Exception:
result = None
if result is not None:
q.put(("result", result))
q.put(("progress", 1))
finally:
q.put(("done", None))
# ---------- thread entry point ----------
def run(self):
total = len(self.entries)
self.total_entries_signal.emit(total)
# start timer for metrics
t0 = monotonic()
if total == 0:
log.debug("No entries to process.")
# emit metrics (zero work)
self.rateSignal.emit(0.0)
self.etaSignal.emit(0)
self.resultsSignal.emit([])
return
# Up to 4 workers; ~20 items per worker
num_workers = min(THREAD_COUNT, max(1, ceil(total / THREAD_MIN_ITEMS)))
chunks = self._split_evenly(self.entries, num_workers)
sizes = [len(ch) for ch in chunks]
q: Queue = Queue()
processed = 0
finished_workers = 0
with ThreadPoolExecutor(max_workers=len(chunks)) as ex:
futures = [ex.submit(self._worker, ch, q) for ch in chunks]
log.info(
f"Launched {len(futures)} worker thread(s) for {total} entries: {sizes} entries per thread."
)
for idx, sz in enumerate(sizes, 1):
log.debug(f"Thread {idx}: {sz} entries")
# Aggregate progress/results
while finished_workers < len(chunks):
try:
kind, payload = q.get(timeout=0.1)
except Empty:
continue
if kind == "progress":
processed += int(payload)
self.updateSignal.emit(processed, total)
self.updateProgress.emit(processed, total)
# ---- NEW: compute & emit metrics ----
elapsed = max(1e-9, monotonic() - t0)
rate = processed / elapsed # items per second
remaining = max(0, total - processed)
eta_sec = int(round(remaining / rate)) if rate > 0 else -1
self.rateSignal.emit(rate)
# clamp negative just in case
self.etaSignal.emit(max(0, eta_sec) if eta_sec >= 0 else -1)
# -------------------------------------
elif kind == "result":
self.results.append(payload)
elif kind == "done":
finished_workers += 1
# Final metrics on completion
elapsed_total = max(1e-9, monotonic() - t0)
final_rate = total / elapsed_total
self.rateSignal.emit(final_rate)
self.etaSignal.emit(0)
self.resultsSignal.emit(self.results)

View File

@@ -1,11 +1,20 @@
import sys
import time
# from icecream import ic
from PyQt6.QtCore import QThread
from PyQt6.QtCore import pyqtSignal as Signal
import loguru
# from icecream import ic
from PySide6.QtCore import QThread
from PySide6.QtCore import Signal as Signal
from src import LOG_DIR
from src.backend import Database
log = loguru.logger
log.remove()
log.add(sys.stdout, level="INFO")
log.add(f"{LOG_DIR}/application.log", rotation="1 MB", retention="10 days")
# from src.transformers import RDS_AVAIL_DATA
@@ -22,13 +31,13 @@ class AutoAdder(QThread):
self.app_id = app_id
self.prof_id = prof_id
# print("Launched AutoAdder")
# print(self.data, self.app_id, self.prof_id)
# #print("Launched AutoAdder")
# #print(self.data, self.app_id, self.prof_id)
def run(self):
self.db = Database()
# show the dialog, start the thread to gather data and dynamically update progressbar and listwidget
logger.info("Starting worker thread")
log.info("Starting worker thread")
item = 0
for entry in self.data:
try:
@@ -39,12 +48,12 @@ class AutoAdder(QThread):
time.sleep(1)
except Exception as e:
# print(e)
logger.exception(
# #print(e)
log.exception(
f"The query failed with message {e} for signature {entry}"
)
continue
if item == len(self.data):
logger.info("Worker thread finished")
log.info("Worker thread finished")
# teminate thread
self.finished.emit()

View File

@@ -1,14 +1,10 @@
import time
# from icecream import ic
from PyQt6.QtCore import QThread
from PyQt6.QtCore import pyqtSignal as Signal
from PySide6.QtCore import QThread
from PySide6.QtCore import Signal as Signal
from src.backend.database import Database
from src.logic.webrequest import BibTextTransformer, WebRequest
# from src.transformers import RDS_AVAIL_DATA
from src.shared.logging import log
class AvailChecker(QThread):
@@ -16,13 +12,17 @@ class AvailChecker(QThread):
updateProgress = Signal(int, int)
def __init__(
self, links: list = None, appnumber: int = None, parent=None, books=list[dict]
self,
links: list[str] | None = None,
appnumber: int | None = None,
parent=None,
books: list[dict] | None = None,
):
if links is None:
links = []
super().__init__(parent)
logger.info("Starting worker thread")
logger.info(
log.info("Starting worker thread")
log.info(
"Checking availability for "
+ str(links)
+ " with appnumber "
@@ -31,42 +31,46 @@ class AvailChecker(QThread):
)
self.links = links
self.appnumber = appnumber
self.books = books
logger.info(
self.books = books or []
log.info(
f"Started worker with appnumber: {self.appnumber} and links: {self.links} and {len(self.books)} books..."
)
time.sleep(2)
# Pre-create reusable request and transformer to avoid per-item overhead
self._request = WebRequest().set_apparat(self.appnumber)
self._rds_transformer = BibTextTransformer("RDS")
def run(self):
self.db = Database()
state = 0
count = 0
for link in self.links:
logger.info("Processing entry: " + str(link))
data = WebRequest().set_apparat(self.appnumber).get_ppn(link).get_data()
transformer = BibTextTransformer("RDS")
rds = transformer.get_data(data).return_data("rds_availability")
log.info("Processing entry: " + str(link))
data = self._request.get_ppn(link).get_data()
rds = self._rds_transformer.get_data(data).return_data("rds_availability")
book_id = None
if not rds or not rds.items:
log.warning(f"No RDS data found for link {link}")
continue
for item in rds.items:
sign = item.superlocation
loc = item.location
# # print(item.location)
if self.appnumber in sign or self.appnumber in loc:
# # #print(item.location)
if str(self.appnumber) in sign or str(self.appnumber) in loc:
state = 1
break
for book in self.books:
if book["bookdata"].signature == link:
book_id = book["id"]
break
logger.info(f"State of {link}: " + str(state))
# print("Updating availability of " + str(book_id) + " to " + str(state))
log.info(f"State of {link}: " + str(state))
# #print("Updating availability of " + str(book_id) + " to " + str(state))
self.db.setAvailability(book_id, state)
count += 1
self.updateProgress.emit(count, len(self.links))
self.updateSignal.emit(item.callnumber, state)
logger.info("Worker thread finished")
log.info("Worker thread finished")
# teminate thread
self.quit()

631
src/logic/SRU.py Normal file
View File

@@ -0,0 +1,631 @@
import re
import xml.etree.ElementTree as ET
from dataclasses import dataclass, field
from enum import Enum
from typing import Dict, Iterable, List, Optional, Tuple, Union
import requests
from requests.adapters import HTTPAdapter
# centralized logging used via src.shared.logging
from src.logic.dataclass import BookData
from src.shared.logging import log
log # ensure imported logger is referenced
# -----------------------
# 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) -> Optional[str]:
el = parent.find(path, NS)
if el is None or el.text is None:
return None
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") or ""
# 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") or ""
recordPacking = _req_text(zs_record_el, "zs:recordPacking") or ""
# 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") or "0")
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: Union[str, bytes],
) -> SearchRetrieveResponse:
root = ET.fromstring(xml_str)
# Root is zs:searchRetrieveResponse
version = _req_text(root, "zs:version")
numberOfRecords = int(_req_text(root, "zs:numberOfRecords") or "0")
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")
mediatype = first_subfield_value(rec, "338", "a")
lang = subfield_values(rec, "041", "a")
authors = subfield_values(rec, "700", "a")
author = None
if authors:
author = "; ".join(authors)
return BookData(
ppn=ppn,
title=title,
signature=signature,
edition=first_subfield_value(rec, "250", "a") or "",
year=year,
pages=first_subfield_value(rec, "300", "a") or "",
publisher=first_subfield_value(rec, "264", "b") or "",
isbn=isbn,
language=lang,
link="",
author=author,
media_type=mediatype,
)
class SWBData(Enum):
URL = "https://sru.k10plus.de/opac-de-627!rec=1?version=1.1&operation=searchRetrieve&query={}&maximumRecords=100&recordSchema=marcxml"
ARGSCHEMA = "pica."
NAME = "SWB"
class DNBData(Enum):
URL = "https://services.dnb.de/sru/dnb?version=1.1&operation=searchRetrieve&query={}&maximumRecords=100&recordSchema=MARC21-xml"
ARGSCHEMA = ""
NAME = "DNB"
class SRUSite(Enum):
SWB = SWBData
DNB = DNBData
RVK_ALLOWED = r"[A-Z0-9.\-\/]" # conservative char set typically seen in RVK notations
def find_newer_edition(
swb_result: BookData, dnb_result: List[BookData]
) -> Optional[List[BookData]]:
"""
New edition if:
- year > swb.year OR
- edition_number > swb.edition_number
Additional guards & preferences:
- If both have signatures and they differ, skip (not the same work).
- For duplicates (same ppn): keep the one that has a signature, and
prefer a signature that matches swb_result.signature.
- If multiple remain: keep the single 'latest' by (year desc,
edition_number desc, best-signature-match desc, has-signature desc).
"""
def norm_sig(s: Optional[str]) -> str:
if not s:
return ""
# normalize: lowercase, collapse whitespace, keep alnum + a few separators
s = s.lower()
s = re.sub(r"\s+", " ", s).strip()
# remove obvious noise; adjust if your signature format differs
s = re.sub(r"[^a-z0-9\-_/\. ]+", "", s)
return s
def has_sig(b: BookData) -> bool:
return bool(getattr(b, "signature", None))
def sig_matches_swb(b: BookData) -> bool:
if not has_sig(b) or not has_sig(swb_result):
return False
return norm_sig(b.signature) == norm_sig(swb_result.signature)
def strictly_newer(b: BookData) -> bool:
by_year = (
b.year is not None
and swb_result.year is not None
and b.year > swb_result.year
)
by_edition = (
b.edition_number is not None
and swb_result.edition_number is not None
and b.edition_number > swb_result.edition_number
)
return by_year or by_edition
swb_sig_norm = norm_sig(getattr(swb_result, "signature", None))
# 1) Filter to same-work AND newer
candidates: List[BookData] = []
for b in dnb_result:
# Skip if both signatures exist and don't match (different work)
b_sig = getattr(b, "signature", None)
if b_sig and swb_result.signature:
if norm_sig(b_sig) != swb_sig_norm:
continue # not the same work
# Keep only if newer by rules
if strictly_newer(b):
candidates.append(b)
if not candidates:
return None
# 2) Dedupe by PPN, preferring signature (and matching signature if possible)
by_ppn: dict[Optional[str], BookData] = {}
for b in candidates:
key = getattr(b, "ppn", None)
prev = by_ppn.get(key)
if prev is None:
by_ppn[key] = b
continue
# Compute preference score for both
def ppn_pref_score(x: BookData) -> tuple[int, int]:
# (signature matches swb, has signature)
return (1 if sig_matches_swb(x) else 0, 1 if has_sig(x) else 0)
if ppn_pref_score(b) > ppn_pref_score(prev):
by_ppn[key] = b
deduped = list(by_ppn.values())
if not deduped:
return None
# 3) If multiple remain, keep only the latest one.
# Order: year desc, edition_number desc, signature-match desc, has-signature desc
def sort_key(b: BookData):
year = b.year if b.year is not None else -1
ed = b.edition_number if b.edition_number is not None else -1
sig_match = 1 if sig_matches_swb(b) else 0
sig_present = 1 if has_sig(b) else 0
return (year, ed, sig_match, sig_present)
best = max(deduped, key=sort_key)
return [best] if best else None
class Api:
def __init__(self, site: str, url: str, prefix: str):
self.site = site
self.url = url
self.prefix = prefix
# Reuse TCP connections across requests for better performance
self._session = requests.Session()
# Slightly larger connection pool for concurrent calls
adapter = HTTPAdapter(pool_connections=10, pool_maxsize=20)
self._session.mount("http://", adapter)
self._session.mount("https://", adapter)
def close(self):
try:
self._session.close()
except Exception:
pass
def __del__(self):
# Best-effort cleanup
self.close()
def get(self, query_args: Iterable[str]) -> List[Record]:
# if any query_arg ends with =, remove it
if self.site == "DNB":
args = [arg for arg in query_args if not arg.startswith("pica.")]
if args == []:
raise ValueError("DNB queries must include at least one search term")
query_args = args
# query_args = [f"{self.prefix}{arg}" for arg in query_args]
query = "+and+".join(query_args)
query = query.replace(" ", "%20").replace("&", "%26")
# query_args = [arg for arg in query_args if not arg.endswith("=")]
# query = "+and+".join(query_args)
# query = query.replace(" ", "%20").replace("&", "%26")
# insert the query into the url url is
url = self.url.format(query)
log.debug(url)
headers = {
"User-Agent": f"{self.site} SRU Client, <alexander.kirchner@ph-freiburg.de>",
"Accept": "application/xml",
"Accept-Charset": "latin1,utf-8;q=0.7,*;q=0.3",
}
# Use persistent session and set timeouts to avoid hanging
resp = self._session.get(url, headers=headers, timeout=(3.05, 60))
if resp.status_code != 200:
raise Exception(f"Error fetching data from SWB: {resp.status_code}")
# Parse using raw bytes (original behavior) to preserve encoding edge cases
sr = parse_search_retrieve_response(resp.content)
return sr.records
def getBooks(self, query_args: Iterable[str]) -> List[BookData]:
records: List[Record] = self.get(query_args)
# Avoid printing on hot paths; rely on logger if needed
log.debug(f"{self.site} found {len(records)} records for args={query_args}")
books: List[BookData] = []
# extract title from query_args if present
title = None
for arg in query_args:
if arg.startswith("pica.tit="):
title = arg.split("=")[1]
break
for rec in records:
book = book_from_marc(rec.recordData)
books.append(book)
if title:
books = [
b
for b in books
if b.title and b.title.lower().startswith(title.lower())
]
return books
def getLinkForBook(self, book: BookData) -> str:
# Not implemented: depends on catalog front-end; return empty string for now
return ""
class SWB(Api):
def __init__(self):
self.site = SWBData.NAME.value
self.url = SWBData.URL.value
self.prefix = SWBData.ARGSCHEMA.value
super().__init__(self.site, self.url, self.prefix)

View File

@@ -1,6 +1,35 @@
from .dataclass import ApparatData, BookData, Prof, Apparat, ELSA
__all__ = [
"custom_sort",
"sort_semesters_list",
"APP_NRS",
"PROF_TITLES",
"SEMAP_MEDIA_ACCOUNTS",
"csv_to_list",
"ELSA",
"Apparat",
"ApparatData",
"BookData",
"Prof",
"Semester",
"SemapDocument",
"elsa_word_to_csv",
"pdf_to_semap",
"word_docx_to_csv",
"word_to_semap",
"ZoteroController",
"eml_to_semap",
]
from .c_sort import custom_sort, sort_semesters_list
from .constants import APP_NRS, PROF_TITLES, SEMAP_MEDIA_ACCOUNTS
from .csvparser import csv_to_list
from .wordparser import elsa_word_to_csv, word_docx_to_csv
from .dataclass import ELSA, Apparat, ApparatData, BookData, Prof
from .semester import Semester
from .wordparser import (
SemapDocument,
elsa_word_to_csv,
pdf_to_semap,
word_docx_to_csv,
word_to_semap,
)
from .xmlparser import eml_to_semap
from .zotero import ZoteroController

View File

@@ -1,36 +1,4 @@
def parse_semester(semester: str):
"""
Parses the semester string into a sortable format.
Returns a tuple of (year, type), where type is 0 for SoSe and 1 for WiSe.
"""
if semester.startswith("SoSe"):
return int(semester.split()[1]), 0
elif semester.startswith("WiSe"):
year_part = semester.split()[1]
start_year, _ = map(int, year_part.split("/"))
return start_year, 1
else:
raise ValueError(f"Invalid semester format: {semester}")
def custom_sort(entries):
"""
Sorts the list of tuples based on the custom schema.
:param entries: List of tuples in the format (str, int, int).
:return: Sorted list of tuples.
"""
return sorted(
entries,
key=lambda entry: (
parse_semester(entry[0]), # Sort by semester parsed as (year, type)
entry[1], # Then by the second element of the tuple
entry[2], # Finally by the third element of the tuple
),
)
def parse_semester(semester: str):
def parse_semester(semester: str) -> tuple[int, int]:
"""
Parses the semester string into a sortable format.
Returns a tuple of (year, type), where type is 0 for SoSe and 1 for WiSe.
@@ -48,6 +16,23 @@ def parse_semester(semester: str):
raise ValueError(f"Invalid semester format: {semester}")
def custom_sort(entries) -> list:
"""
Sorts the list of tuples based on the custom schema.
:param entries: List of tuples in the format (str, int, int).
:return: Sorted list of tuples.
"""
return sorted(
entries,
key=lambda entry: (
parse_semester(entry[0]), # Sort by semester parsed as (year, type)
entry[1], # Then by the second element of the tuple
entry[2], # Finally by the third element of the tuple
),
)
def sort_semesters_list(semesters: list) -> list:
"""
Sorts a list of semester strings based on year and type.
@@ -83,4 +68,4 @@ if __name__ == "__main__":
"SoSe 25",
]
print(sort_semesters_list(unsorted))
# print(sort_semesters_list(unsorted))

View File

@@ -30,184 +30,184 @@ PROF_TITLES = [
]
SEMAP_MEDIA_ACCOUNTS = {
"1": "1008000055",
"2": "1008000188",
"3": "1008000211",
"4": "1008000344",
"5": "1008000477",
"6": "1008000500",
"7": "1008000633",
"8": "1008000766",
"9": "1008000899",
"10": "1008000922",
"11": "1008001044",
"12": "1008001177",
"13": "1008001200",
"14": "1008001333",
"15": "1008001466",
"16": "1008001599",
"17": "1008001622",
"18": "1008001755",
"19": "1008001888",
"20": "1008001911",
"21": "1008002033",
"22": "1008002166",
"23": "1008002299",
"24": "1008002322",
"25": "1008002455",
"26": "1008002588",
"27": "1008002611",
"28": "1008002744",
"29": "1008002877",
"30": "1008002900",
"31": "1008003022",
"32": "1008003155",
"33": "1008003288",
"34": "1008003311",
"35": "1008003444",
"36": "1008003577",
"37": "1008003600",
"38": "1008003733",
"39": "1008003866",
"40": "1008003999",
"41": "1008004011",
"42": "1008004144",
"43": "1008004277",
"44": "1008004300",
"45": "1008004433",
"46": "1008004566",
"47": "1008004699",
"48": "1008004722",
"49": "1008004855",
"50": "1008004988",
"51": "1008005000",
"52": "1008005133",
"53": "1008005266",
"54": "1008005399",
"55": "1008005422",
"56": "1008005555",
"57": "1008005688",
"58": "1008005711",
"59": "1008005844",
"60": "1008005977",
"61": "1008006099",
"62": "1008006122",
"63": "1008006255",
"64": "1008006388",
"65": "1008006411",
"66": "1008006544",
"67": "1008006677",
"68": "1008006700",
"69": "1008006833",
"70": "1008006966",
"71": "1008007088",
"72": "1008007111",
"73": "1008007244",
"74": "1008007377",
"75": "1008007400",
"76": "1008007533",
"77": "1008007666",
"78": "1008007799",
"79": "1008007822",
"80": "1008007955",
"81": "1008008077",
"82": "1008008100",
"83": "1008008233",
"84": "1008008366",
"85": "1008008499",
"86": "1008008522",
"87": "1008008655",
"88": "1008008788",
"89": "1008008811",
"90": "1008008944",
"91": "1008009066",
"92": "1008009199",
"93": "1008009222",
"94": "1008009355",
"95": "1008009488",
"96": "1008009511",
"97": "1008009644",
"98": "1008009777",
"99": "1008009800",
"100": "1008009933",
"101": "1008010022",
"102": "1008010155",
"103": "1008010288",
"104": "1008010311",
"105": "1008010444",
"106": "1008010577",
"107": "1008010600",
"108": "1008010733",
"109": "1008010866",
"110": "1008010999",
"111": "1008011011",
"112": "1008011144",
"113": "1008011277",
"114": "1008011300",
"115": "1008011433",
"116": "1008011566",
"117": "1008011699",
"118": "1008011722",
"119": "1008011855",
"120": "1008011988",
"121": "1008012000",
"122": "1008012133",
"123": "1008012266",
"124": "1008012399",
"125": "1008012422",
"126": "1008012555",
"127": "1008012688",
"128": "1008012711",
"129": "1008012844",
"130": "1008012977",
"131": "1008013099",
"132": "1008013122",
"133": "1008013255",
"134": "1008013388",
"135": "1008013411",
"136": "1008013544",
"137": "1008013677",
"138": "1008013700",
"139": "1008013833",
"140": "1008013966",
"141": "1008014088",
"142": "1008014111",
"143": "1008014244",
"144": "1008014377",
"145": "1008014400",
"146": "1008014533",
"147": "1008014666",
"148": "1008014799",
"149": "1008014822",
"150": "1008014955",
"151": "1008015077",
"152": "1008015100",
"153": "1008015233",
"154": "1008015366",
"155": "1008015499",
"156": "1008015522",
"157": "1008015655",
"158": "1008015788",
"159": "1008015811",
"160": "1008015944",
"161": "1008016066",
"162": "1008016199",
"163": "1008016222",
"164": "1008016355",
"165": "1008016488",
"166": "1008016511",
"167": "1008016644",
"168": "1008016777",
"169": "1008016800",
"170": "1008016933",
"171": "1008017055",
"172": "1008017188",
"173": "1008017211",
"174": "1008017344",
"175": "1008017477",
"176": "1008017500",
"177": "1008017633",
"178": "1008017766",
"179": "1008017899",
"180": "1008017922",
1: "1008000055",
2: "1008000188",
3: "1008000211",
4: "1008000344",
5: "1008000477",
6: "1008000500",
7: "1008000633",
8: "1008000766",
9: "1008000899",
10: "1008000922",
11: "1008001044",
12: "1008001177",
13: "1008001200",
14: "1008001333",
15: "1008001466",
16: "1008001599",
17: "1008001622",
18: "1008001755",
19: "1008001888",
20: "1008001911",
21: "1008002033",
22: "1008002166",
23: "1008002299",
24: "1008002322",
25: "1008002455",
26: "1008002588",
27: "1008002611",
28: "1008002744",
29: "1008002877",
30: "1008002900",
31: "1008003022",
32: "1008003155",
33: "1008003288",
34: "1008003311",
35: "1008003444",
36: "1008003577",
37: "1008003600",
38: "1008003733",
39: "1008003866",
40: "1008003999",
41: "1008004011",
42: "1008004144",
43: "1008004277",
44: "1008004300",
45: "1008004433",
46: "1008004566",
47: "1008004699",
48: "1008004722",
49: "1008004855",
50: "1008004988",
51: "1008005000",
52: "1008005133",
53: "1008005266",
54: "1008005399",
55: "1008005422",
56: "1008005555",
57: "1008005688",
58: "1008005711",
59: "1008005844",
60: "1008005977",
61: "1008006099",
62: "1008006122",
63: "1008006255",
64: "1008006388",
65: "1008006411",
66: "1008006544",
67: "1008006677",
68: "1008006700",
69: "1008006833",
70: "1008006966",
71: "1008007088",
72: "1008007111",
73: "1008007244",
74: "1008007377",
75: "1008007400",
76: "1008007533",
77: "1008007666",
78: "1008007799",
79: "1008007822",
80: "1008007955",
81: "1008008077",
82: "1008008100",
83: "1008008233",
84: "1008008366",
85: "1008008499",
86: "1008008522",
87: "1008008655",
88: "1008008788",
89: "1008008811",
90: "1008008944",
91: "1008009066",
92: "1008009199",
93: "1008009222",
94: "1008009355",
95: "1008009488",
96: "1008009511",
97: "1008009644",
98: "1008009777",
99: "1008009800",
100: "1008009933",
101: "1008010022",
102: "1008010155",
103: "1008010288",
104: "1008010311",
105: "1008010444",
106: "1008010577",
107: "1008010600",
108: "1008010733",
109: "1008010866",
110: "1008010999",
111: "1008011011",
112: "1008011144",
113: "1008011277",
114: "1008011300",
115: "1008011433",
116: "1008011566",
117: "1008011699",
118: "1008011722",
119: "1008011855",
120: "1008011988",
121: "1008012000",
122: "1008012133",
123: "1008012266",
124: "1008012399",
125: "1008012422",
126: "1008012555",
127: "1008012688",
128: "1008012711",
129: "1008012844",
130: "1008012977",
131: "1008013099",
132: "1008013122",
133: "1008013255",
134: "1008013388",
135: "1008013411",
136: "1008013544",
137: "1008013677",
138: "1008013700",
139: "1008013833",
140: "1008013966",
141: "1008014088",
142: "1008014111",
143: "1008014244",
144: "1008014377",
145: "1008014400",
146: "1008014533",
147: "1008014666",
148: "1008014799",
149: "1008014822",
150: "1008014955",
151: "1008015077",
152: "1008015100",
153: "1008015233",
154: "1008015366",
155: "1008015499",
156: "1008015522",
157: "1008015655",
158: "1008015788",
159: "1008015811",
160: "1008015944",
161: "1008016066",
162: "1008016199",
163: "1008016222",
164: "1008016355",
165: "1008016488",
166: "1008016511",
167: "1008016644",
168: "1008016777",
169: "1008016800",
170: "1008016933",
171: "1008017055",
172: "1008017188",
173: "1008017211",
174: "1008017344",
175: "1008017477",
176: "1008017500",
177: "1008017633",
178: "1008017766",
179: "1008017899",
180: "1008017922",
}

View File

@@ -1,13 +1,13 @@
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="|")
@@ -20,4 +20,4 @@ def csv_to_list(path: str) -> list[str]:
if __name__ == "__main__":
text = csv_to_list("C:/Users/aky547/Desktop/semap/71.csv")
# remove linebreaks
# print(text)
# #print(text)

View File

@@ -1,38 +1,43 @@
import re
import json
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Optional, Union
import regex
from src.logic.openai import name_tester, run_shortener, semester_converter
from src.logic.semester import Semester
@dataclass
class Prof:
id: int = None
_title: str = None
firstname: str = None
lastname: str = None
fullname: str = None
mail: str = None
telnr: str = None
id: Optional[int] = None
_title: Optional[str] = None
firstname: Optional[str] = None
lastname: Optional[str] = None
fullname: Optional[str] = None
mail: Optional[str] = None
telnr: Optional[str] = None
# add function that sets the data based on a dict
def from_dict(self, data: dict):
def from_dict(self, data: dict[str, Union[str, int]]):
for key, value in data.items():
if hasattr(self, key):
setattr(self, key, value)
return self
@property
def title(self):
def title(self) -> str:
if self._title is None or self._title == "None":
return ""
return self._title
@title.setter
def title(self, value):
def title(self, value: str):
self._title = value
# add function that sets the data from a tuple
def from_tuple(self, data: tuple):
def from_tuple(self, data: tuple[Union[str, int], ...]) -> "Prof":
setattr(self, "id", data[0])
setattr(self, "_title", data[1])
setattr(self, "firstname", data[2])
@@ -42,7 +47,7 @@ class Prof:
setattr(self, "telnr", data[6])
return self
def name(self, comma=False):
def name(self, comma: bool = False) -> Optional[str]:
if self.firstname is None and self.lastname is None:
if "," in self.fullname:
self.firstname = self.fullname.split(",")[1].strip()
@@ -62,46 +67,120 @@ class BookData:
signature: str | None = None
edition: str | None = None
link: str | None = None
isbn: str | list | None = field(default_factory=list)
isbn: Union[str, list[str], None] = field(default_factory=list)
author: str | None = None
language: str | list | None = field(default_factory=list)
language: Union[str, list[str], None] = field(default_factory=list)
publisher: str | None = None
place: str | None = None
year: str | None = None
year: int | None = None
pages: str | None = None
library_location: int | None = None
library_location: str | None = None
in_apparat: bool | None = False
adis_idn: str | None = None
old_book: Any | None = None
media_type: str | None = None #
in_library: bool | None = None # whether the book is in the library or not
def from_dict(self, data: dict):
def __post_init__(self):
self.library_location = (
str(self.library_location) if self.library_location else None
)
if isinstance(self.language, list) and self.language:
self.language = [lang.strip() for lang in self.language if lang.strip()]
self.language = ",".join(self.language)
self.year = regex.sub(r"[^\d]", "", str(self.year)) if self.year else None
self.in_library = True if self.signature else False
def from_dict(self, data: dict) -> "BookData":
for key, value in data.items():
setattr(self, key, value)
return self
def to_dict(self):
return self.__dict__
def merge(self, other: "BookData") -> "BookData":
for key, value in other.__dict__.items():
# merge lists, if the attribute is a list, extend it
if isinstance(value, list):
current_value = getattr(self, key)
if current_value is None:
current_value = []
elif not isinstance(current_value, list):
current_value = [current_value]
# extend the list with the new values, but only if they are not already in the list
for v in value:
if v not in current_value:
current_value.append(v)
setattr(self, key, current_value)
if value is not None and (
getattr(self, key) is None or getattr(self, key) == ""
):
setattr(self, key, value)
# in language, drop all entries that are longer than 3 characters
if isinstance(self.language, list):
self.language = [lang for lang in self.language if len(lang) <= 4]
return self
def from_dataclass(self, dataclass):
@property
def to_dict(self) -> str:
"""Convert the dataclass to a dictionary."""
data_dict = {
key: value for key, value in self.__dict__.items() if value is not None
}
# remove old_book from data_dict
if "old_book" in data_dict:
del data_dict["old_book"]
return json.dumps(data_dict, ensure_ascii=False)
def from_dataclass(self, dataclass: Optional[Any]) -> None:
if dataclass is None:
return
for key, value in dataclass.__dict__.items():
setattr(self, key, value)
def from_string(self, data: str):
if not data.startswith("BookData"):
raise ValueError("No valid BookData string")
def get_book_type(self) -> str:
if "Online" in self.pages:
return "eBook"
else:
pattern = r"(\w+)='([^']*)'"
data_dict = dict(re.findall(pattern, data))
# print(data_dict)
for key, value in data_dict.items():
setattr(self, key, value)
return "Druckausgabe"
def from_string(self, data: str) -> "BookData":
ndata = json.loads(data)
return BookData(**ndata)
def from_LehmannsSearchResult(self, result: Any) -> "BookData":
self.title = result.title
self.author = "; ".join(result.authors) if result.authors else None
self.edition = str(result.edition) if result.edition else None
self.link = result.url
self.isbn = (
result.isbn13
if isinstance(result.isbn13, list)
else [result.isbn13]
if result.isbn13
else []
)
self.pages = str(result.pages) if result.pages else None
self.publisher = result.publisher
self.year = str(result.year) if result.year else None
# self.pages = str(result.pages) if result.pages else None
return self
@property
def edition_number(self) -> Optional[int]:
if self.edition is None:
return 0
match = regex.search(r"(\d+)", self.edition)
if match:
return int(match.group(1))
return 0
@dataclass
class MailData:
subject: str | None = None
body: str | None = None
mailto: str | None = None
prof: str | None = None
subject: Optional[str] = None
body: Optional[str] = None
mailto: Optional[str] = None
prof: Optional[str] = None
class Subjects(Enum):
@@ -131,18 +210,19 @@ class Subjects(Enum):
ECONOMICS = (24, "Wirtschaftslehre")
@property
def id(self):
def id(self) -> int:
return self.value[0]
@property
def name(self):
def name(self) -> str:
return self.value[1]
@classmethod
def get_index(cls, name):
def get_index(cls, name: str) -> Optional[int]:
for i in cls:
if i.name == name:
return i.id - 1
return None
@dataclass
@@ -162,7 +242,7 @@ class Apparat:
prof_id_adis: str | None = None
konto: int | None = None
def from_tuple(self, data: tuple):
def from_tuple(self, data: tuple[Any, ...]) -> "Apparat":
self.id = data[0]
self.name = data[1]
self.prof_id = data[2]
@@ -180,7 +260,7 @@ class Apparat:
return self
@property
def get_semester(self):
def get_semester(self) -> Optional[str]:
if self.extend_until is not None:
return self.extend_until
else:
@@ -194,7 +274,7 @@ class ELSA:
semester: str | None = None
prof_id: int | None = None
def from_tuple(self, data):
def from_tuple(self, data: tuple[Any, ...]) -> "ELSA":
self.id = data[0]
self.date = data[1]
self.semester = data[2]
@@ -206,3 +286,124 @@ class ELSA:
class ApparatData:
prof: Prof = field(default_factory=Prof)
apparat: Apparat = field(default_factory=Apparat)
@dataclass
class XMLMailSubmission:
name: Optional[str] = None
lastname: Optional[str] = None
title: Optional[str] = None
telno: Optional[int] = None
email: Optional[str] = None
app_name: Optional[str] = None
subject: Optional[str] = None
semester: Optional[Semester] = None
books: Optional[list[BookData]] = None
@dataclass
class Book:
author: str = None
year: str = None
edition: str = None
title: str = None
location: str = None
publisher: str = None
signature: str = None
internal_notes: str = None
@property
def has_signature(self) -> bool:
return self.signature is not None and self.signature != ""
@property
def is_empty(self) -> bool:
return all(
[
self.author == "",
self.year == "",
self.edition == "",
self.title == "",
self.location == "",
self.publisher == "",
self.signature == "",
self.internal_notes == "",
]
)
def from_dict(self, data: dict[str, Any]):
for key, value in data.items():
value = value.strip()
if value == "\u2002\u2002\u2002\u2002\u2002":
value = ""
if key == "Autorenname(n):Nachname, Vorname":
self.author = value
elif key == "Jahr/Auflage":
self.year = value.split("/")[0] if "/" in value else value
self.edition = value.split("/")[1] if "/" in value else ""
elif key == "Titel":
self.title = value
elif key == "Ort und Verlag":
self.location = value.split(",")[0] if "," in value else value
self.publisher = value.split(",")[1] if "," in value else ""
elif key == "Standnummer":
self.signature = value.strip()
elif key == "Interne Vermerke":
self.internal_notes = value
@dataclass
class SemapDocument:
subject: str = None
phoneNumber: int = None
mail: str = None
title: str = None
title_suggestions: list[str] = None
semester: Union[str, Semester] = None
books: list[Book] = None
eternal: bool = False
personName: str = None
personTitle: str = None
title_length = 0
title_max_length = 0
def __post_init__(self):
self.title_suggestions = []
@property
def nameSetter(self):
data = name_tester(self.personTitle)
name = f"{data['last_name']}, {data['first_name']}"
if data["title"] is not None:
title = data["title"]
self.personTitle = title
self.personName = name
self.title_length = len(self.title) + 3 + len(self.personName.split(",")[0])
if self.title_length > 40:
name_len = len(self.personName.split(",")[0])
self.title_max_length = 38 - name_len
suggestions = run_shortener(self.title, self.title_max_length)
for suggestion in suggestions:
self.title_suggestions.append(suggestion["shortened_string"])
else:
self.title_suggestions = []
pass
@property
def renameSemester(self) -> None:
if self.semester:
if ", Dauer" in self.semester:
self.semester = self.semester.split(",")[0]
self.eternal = True
self.semester = Semester().from_string(self.semester)
else:
self.semester = Semester().from_string(
semester_converter(self.semester)
)
@property
def signatures(self) -> list[str]:
if self.books is not None:
return [book.signature for book in self.books if book.has_signature]
return []

View File

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

View File

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

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

@@ -0,0 +1,312 @@
from __future__ import annotations
import re
from dataclasses import asdict, dataclass, field
from typing import Iterable, List, Optional
from urllib.parse import quote_plus, urljoin
import httpx
from bs4 import BeautifulSoup
from src.logic.dataclass import BookData
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,
only_latest: bool = True,
) -> List[BookData]:
"""
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=title)
html = self._get(url)
if not html:
return []
results = self._parse_results(html)
self.enrich_pages(results)
results = [BookData().from_LehmannsSearchResult(r) for r in 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)]
if only_latest and len(results) > 1:
# keep only the latest edition (highest edition number)
results.sort(key=lambda r: (r.edition_number or 0), reverse=True)
results = [results[0]]
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") # type: ignore
# Pages
pages_node = soup.select_one( # type: ignore
"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") # type: ignore
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( # type: ignore
strip=True
)
# Alternative headline => extend title
alt_tag = block.select_one(".description[itemprop='alternativeHeadline']") # type: ignore
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") # type: ignore
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") # type: ignore
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']") # type: ignore
if y:
try:
year = int(y.get_text(strip=True))
except ValueError:
pass
# Edition
edition = None
ed = block.select_one("[itemprop='bookEdition']") # type: ignore
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( # type: ignore
".publisherprop [itemprop='name']"
) or block.select_one(".publisher [itemprop='name']") # type: ignore
if pub:
publisher = pub.get_text(strip=True)
# ISBN-13
isbn13 = None
isbn_tag = block.select_one(".isbn [itemprop='isbn'], [itemprop='isbn']") # type: ignore
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") # type: ignore
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

View File

@@ -1,92 +0,0 @@
import logging
import logging.handlers
import os
from loguru import logger as log
import sys
if not os.path.exists("logs"):
os.mkdir("logs")
# open and close the file to create it
logger = log
logger.remove()
logger.add("logs/application.log", rotation="50MB")
# logger.add(sys.stderr, format="{time} {level} {message}", level="INFO")
logger.add(
sys.stdout,
colorize=True,
format="<green>{time}</green> <level>{message}</level>",
level="WARNING",
)
log_filesize = 10 * 1024**2 # 10MB
backups = 5
# Create a common file handler for all loggers
common_file_handler = logging.handlers.RotatingFileHandler(
"logs/application.log",
mode="a",
encoding="utf-8",
maxBytes=log_filesize,
backupCount=backups,
)
common_file_handler.setLevel(logging.DEBUG)
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
common_file_handler.setFormatter(formatter)
# set max file size to 10MB, if exceeded, create a new file
class MyLogger:
def __init__(self, logger_name):
self.logger = logging.getLogger(logger_name)
self.logger.setLevel(logging.DEBUG)
self.logger.addHandler(common_file_handler)
self.encoding = "utf-8"
def log_info(self, message: str):
# ensure that the message is encoded in utf-8
self.logger.info(message.encode(self.encoding))
def log_debug(self, message: str):
self.logger.debug(message.encode(self.encoding))
def log_warning(self, message: str):
self.logger.warning(message.encode(self.encoding))
def log_error(self, message: str):
self.logger.error(message.encode(self.encoding))
def log_critical(self, message: str):
self.logger.critical(message.encode(self.encoding))
def log_exception(self, message: str):
self.logger.exception(message)
# Usage example:
if __name__ == "__main__":
logger1 = MyLogger("Logger1")
logger2 = MyLogger("Logger2")
logger1.log_info("This is an info message from Logger1")
logger1.log_debug("This is a debug message from Logger1")
logger1.log_warning("This is a warning message from Logger1")
logger1.log_error("This is an error message from Logger1")
logger1.log_critical("This is a critical message from Logger1")
logger2.log_info("This is an info message from Logger2")
logger2.log_debug("This is a debug message from Logger2")
logger2.log_warning("This is a warning message from Logger2")
logger2.log_error("This is an error message from Logger2")
logger2.log_critical("This is a critical message from Logger2")
try:
# Simulate an exception
raise Exception("An exception occurred")
except Exception:
logger1.log_exception("An exception occurred in Logger1")
logger2.log_exception("An exception occurred in Logger2")

View File

@@ -1 +0,0 @@

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

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

View File

@@ -1,10 +1,9 @@
# add depend path to system path
import pandas as pd
from pdfquery import PDFQuery
def pdf_to_csv(path: str) -> pd.DataFrame:
def pdf_to_csv(path: str) -> str:
"""
Extracts the data from a pdf file and returns it as a pandas dataframe
"""
@@ -21,4 +20,4 @@ if __name__ == "__main__":
text = pdf_to_csv("54_pdf.pdf")
# remove linebreaks
text = text.replace("\n", "")
print(text)
# print(text)

248
src/logic/semester.py Normal file
View File

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

View File

@@ -13,7 +13,7 @@ class Settings:
default_apps: bool = True
custom_applications: list[dict] = field(default_factory=list)
def save_settings(self):
def save_settings(self) -> None:
"""Save the settings to the config file."""
with open("config.yaml", "w") as f:
yaml.dump(self.__dict__, f)

View File

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

View File

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

View File

@@ -1,13 +1,17 @@
from typing import Any, Optional, Union
import requests
from bs4 import BeautifulSoup
from src import logger
# import sleep_and_retry decorator to retry requests
from ratelimit import limits, sleep_and_retry
from src.logic.dataclass import BookData
from src.shared.logging import log
from src.transformers import ARRAYData, BibTeXData, COinSData, RDSData, RISData
from src.transformers.transformers import RDS_AVAIL_DATA, RDS_GENERIC_DATA
# logger.add(sys.stderr, format="{time} {level} {message}", level="INFO")
API_URL = "https://rds.ibs-bw.de/phfreiburg/opac/RDSIndexrecord/{}/"
@@ -38,23 +42,23 @@ class WebRequest:
self.ppn = None
self.data = None
self.timeout = 5
logger.info("Initialized WebRequest")
log.info("Initialized WebRequest")
@property
def use_any_book(self):
"""use any book that matches the search term"""
self.use_any = True
logger.info("Using any book")
log.info("Using any book")
return self
def set_apparat(self, apparat):
def set_apparat(self, apparat: int) -> "WebRequest":
self.apparat = apparat
if int(self.apparat) < 10:
self.apparat = f"0{self.apparat}"
logger.info(f"Set apparat to {self.apparat}")
log.info(f"Set apparat to {self.apparat}")
return self
def get_ppn(self, signature):
def get_ppn(self, signature: str) -> "WebRequest":
self.signature = signature
if "+" in signature:
signature = signature.replace("+", "%2B")
@@ -65,43 +69,49 @@ class WebRequest:
@sleep_and_retry
@limits(calls=RATE_LIMIT, period=RATE_PERIOD)
def search_book(self, searchterm: str):
def search_book(self, searchterm: str) -> str:
response = requests.get(PPN_URL.format(searchterm), timeout=self.timeout)
return response.text
def get_book_links(self, searchterm: str):
response = self.search_book(searchterm)
@sleep_and_retry
@limits(calls=RATE_LIMIT, period=RATE_PERIOD)
def search_ppn(self, ppn: str) -> str:
response = requests.get(API_URL.format(ppn), timeout=self.timeout)
return response.text
def get_book_links(self, searchterm: str) -> list[str]:
response: str = self.search_book(searchterm) # type:ignore
soup = BeautifulSoup(response, "html.parser")
links = soup.find_all("a", class_="title getFull")
res = []
res: list[str] = []
for link in links:
res.append(BASE + link["href"])
return res
@sleep_and_retry
@limits(calls=RATE_LIMIT, period=RATE_PERIOD)
def search(self, link: str):
def search(self, link: str) -> Optional[str]:
try:
response = requests.get(link, timeout=self.timeout)
return response.text
except requests.exceptions.RequestException as e:
logger.error(f"Request failed: {e}")
log.error(f"Request failed: {e}")
return None
def get_data(self):
def get_data(self) -> Optional[list[str]]:
links = self.get_book_links(self.ppn)
log.debug(f"Links: {links}")
return_data: list[str] = []
for link in links:
result = self.search(link)
result: str = self.search(link) # type:ignore
# in result search for class col-xs-12 rds-dl RDS_LOCATION
# if found, return text of href
soup = BeautifulSoup(result, "html.parser")
locations = soup.find_all("div", class_="col-xs-12 rds-dl RDS_LOCATION")
if locations:
for location in locations:
item_location = location.find(
"div", class_="col-xs-12 col-md-7 col-lg-8 rds-dl-panel"
).text.strip()
if self.use_any:
if "1. OG Semesterapparat" in location.text:
log.success("Found Semesterapparat, adding entry")
pre_tag = soup.find_all("pre")
return_data = []
if pre_tag:
@@ -110,21 +120,43 @@ class WebRequest:
return_data.append(data)
return return_data
else:
logger.error("No <pre> tag found")
raise ValueError("No <pre> tag found")
if 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)
log.error("No <pre> tag found")
return return_data
else:
item_location = location.find(
"div", class_="col-xs-12 col-md-7 col-lg-8 rds-dl-panel"
).text.strip()
log.debug(f"Item location: {item_location}")
if self.use_any:
pre_tag = soup.find_all("pre")
if pre_tag:
for tag in pre_tag:
data = tag.text.strip()
return_data.append(data)
return return_data
else:
log.error("No <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:
logger.error("No <pre> tag found")
return return_data
log.error(
f"Signature {self.signature} not found in {item_location}"
)
# return_data = []
def get_data_elsa(self):
return return_data
def get_data_elsa(self) -> Optional[list[str]]:
links = self.get_book_links(self.ppn)
for link in links:
result = self.search(link)
@@ -142,7 +174,7 @@ class WebRequest:
return_data.append(data)
return return_data
else:
logger.error("No <pre> tag found")
log.error("No <pre> tag found")
return return_data
@@ -160,17 +192,17 @@ class BibTextTransformer:
self.field = None
self.signature = None
if mode not in self.valid_modes:
logger.error(f"Mode {mode} not valid")
log.error(f"Mode {mode} not valid")
raise ValueError(f"Mode {mode} not valid")
self.data = None
# self.bookdata = BookData(**self.data)
def use_signature(self, signature: str):
def use_signature(self, signature: str) -> "BibTextTransformer":
"""use the exact signature to search for the book"""
self.signature = signature
return self
def get_data(self, data: list):
def get_data(self, data: Optional[list[str]] = None) -> "BibTextTransformer":
RIS_IDENT = "TY -"
ARRAY_IDENT = "[kid]"
COinS_IDENT = "ctx_ver"
@@ -203,7 +235,15 @@ class BibTextTransformer:
self.data = line
return self
def return_data(self, option=None) -> BookData:
def return_data(
self, option: Any = None
) -> Union[
Optional[BookData],
Optional[RDS_GENERIC_DATA],
Optional[RDS_AVAIL_DATA],
None,
dict[str, Union[RDS_AVAIL_DATA, RDS_GENERIC_DATA]],
]:
"""Return Data to caller.
Args:
@@ -225,7 +265,7 @@ class BibTextTransformer:
return RISData().transform(self.data)
case "RDS":
return RDSData().transform(self.data).return_data(option)
case None:
case _:
return None
# if self.mode == "ARRAY":
@@ -242,7 +282,7 @@ class BibTextTransformer:
def cover(isbn):
test_url = f"https://www.buchhandel.de/cover/{isbn}/{isbn}-cover-m.jpg"
# print(test_url)
# log.debug(test_url)
data = requests.get(test_url, stream=True)
return data.content
@@ -252,8 +292,8 @@ def get_content(soup, css_class):
if __name__ == "__main__":
# print("main")
# log.debug("main")
link = "CU 8500 K64"
data = WebRequest(71).get_ppn(link).get_data()
bib = BibTextTransformer("ARRAY").get_data().return_data()
print(bib)
log.debug(bib)

View File

@@ -1,22 +1,30 @@
import zipfile
from typing import Any, Optional
import fitz # PyMuPDF
import pandas as pd
from bs4 import BeautifulSoup
from docx import Document
letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
from src.logic.dataclass import Book, SemapDocument
from src.shared.logging import log
def word_docx_to_csv(path) -> pd.DataFrame:
def word_docx_to_csv(path: str) -> list[pd.DataFrame]:
doc = Document(path)
tables = doc.tables
m_data = []
for table in tables:
data = []
for row in table.rows:
row_data = []
row_data: list[Any] = []
for cell in row.cells:
text = cell.text
text = text.replace("\n", "")
row_data.append(text)
# if text == "Ihr Fach:":
# row_data.append(get_fach(path))
data.append(row_data)
df = pd.DataFrame(data)
df.columns = df.iloc[0]
@@ -24,11 +32,29 @@ def word_docx_to_csv(path) -> pd.DataFrame:
m_data.append(df)
df = m_data[2]
return df
return m_data
def makeDict():
def get_fach(path: str) -> Optional[str]:
document = zipfile.ZipFile(path)
xml_data = document.read("word/document.xml")
document.close()
soup = BeautifulSoup(xml_data, "xml")
# text we need is in <w:p w14:paraId="12456A32" ... > -> w:r -> w:t
paragraphs = soup.find_all("w:p")
for para in paragraphs:
para_id = para.get("w14:paraId")
if para_id == "12456A32":
# get the data in the w:t
for run in para.find_all("w:r"):
data = run.find("w:t")
if data and data.contents:
return data.contents[0]
return None
def makeDict() -> dict[str, Optional[str]]:
return {
"work_author": None,
"section_author": None,
@@ -46,8 +72,8 @@ def makeDict():
}
def tuple_to_dict(tlist: tuple, type: str) -> dict:
ret = []
def tuple_to_dict(tlist: tuple, type: str) -> list[dict[str, Optional[str]]]:
ret: list[dict[str, Optional[str]]] = []
for line in tlist:
data = makeDict()
if type == "Monografien":
@@ -87,7 +113,7 @@ def tuple_to_dict(tlist: tuple, type: str) -> dict:
return ret
def elsa_word_to_csv(path):
def elsa_word_to_csv(path: str) -> tuple[list[dict[str, Optional[str]]], str]:
doc = Document(path)
# # print all lines in doc
doctype = [para.text for para in doc.paragraphs if para.text != ""][-1]
@@ -98,18 +124,18 @@ def elsa_word_to_csv(path):
}
tables = doc.tables
m_data = []
m_data: list[pd.DataFrame] = []
for table in tables:
data = []
data: list[list[str]] = []
for row in table.rows:
row_data = []
row_data: list[str] = []
for cell in row.cells:
text = cell.text
text = text.replace("\n", "")
text = text.replace("\u2002", "")
row_data.append(text)
data.append(row_data)
df = pd.DataFrame(data)
df = pd.DataFrame(data)
df.columns = df.iloc[0]
df = df.iloc[1:]
m_data.append(df)
@@ -118,10 +144,230 @@ def elsa_word_to_csv(path):
data = [
row for row in df.itertuples(index=False, name=None) if row != tuples[doctype]
]
# print(data)
# log.debug(data)
return tuple_to_dict(data, doctype), doctype
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)
apparatdata = df[0]
apparatdata = apparatdata.to_dict()
keys = list(apparatdata.keys())
# print(apparatdata, keys)
appdata = {keys[i]: keys[i + 1] for i in range(0, len(keys) - 1, 2)}
semap.phoneNumber = appdata["Telefon:"]
semap.subject = appdata["Ihr Fach:"]
semap.mail = appdata["Mailadresse:"]
semap.personName = ",".join(appdata["Ihr Name und Titel:"].split(",")[:-1])
semap.personTitle = ",".join(appdata["Ihr Name und Titel:"].split(",")[-1:]).strip()
apparatdata = df[1]
apparatdata = apparatdata.to_dict()
keys = list(apparatdata.keys())
appdata = {keys[i]: keys[i + 1] for i in range(0, len(keys), 2)}
semap.title = appdata["Veranstaltung:"]
semap.semester = appdata["Semester:"]
if ai:
semap.renameSemester
semap.nameSetter
books = df[2]
booklist = []
for i in range(len(books)):
if books.iloc[i].isnull().all():
continue
data = books.iloc[i].to_dict()
book = Book()
book.from_dict(data)
if book.is_empty:
continue
elif not book.has_signature:
continue
else:
booklist.append(book)
log.info("Found {} books", len(booklist))
semap.books = booklist
return semap
def pdf_to_semap(pdf_path: str, ai: bool = True) -> SemapDocument:
"""
Parse a Semesterapparat PDF like the sample you provided and return a SemapDocument.
- No external programs, only PyMuPDF.
- Robust to multi-line field values (e.g., hyphenated emails) and multi-line table cells.
- Works across multiple pages; headers only need to exist on the first page.
"""
doc = fitz.open(pdf_path)
semap = SemapDocument()
# ---------- helpers ----------
def _join_tokens(tokens: list[str]) -> str:
"""Join tokens, preserving hyphen/URL joins across line wraps."""
parts = []
for tok in tokens:
if parts and (
parts[-1].endswith("-")
or parts[-1].endswith("/")
or parts[-1].endswith(":")
):
parts[-1] = parts[-1] + tok # no space after '-', '/' or ':'
else:
parts.append(tok)
return " ".join(parts).strip()
def _extract_row_values_multiline(
page, labels: list[str], y_window: float = 24
) -> dict[str, str]:
"""For a row of inline labels (e.g., Name/Fach/Telefon/Mail), grab text to the right of each label."""
rects = []
for lab in labels:
hits = page.search_for(lab)
if hits:
rects.append((lab, hits[0]))
if not rects:
return {}
rects.sort(key=lambda t: t[1].x0)
words = page.get_text("words")
out = {}
for i, (lab, r) in enumerate(rects):
x0 = r.x1 + 1
x1 = rects[i + 1][1].x0 - 1 if i + 1 < len(rects) else page.rect.width - 5
y0 = r.y0 - 3
y1 = r.y0 + y_window
toks = [w for w in words if x0 <= w[0] <= x1 and y0 <= w[1] <= y1]
toks.sort(key=lambda w: (w[1], w[0])) # line, then x
out[lab] = _join_tokens([w[4] for w in toks])
return out
def _compute_columns_from_headers(page0):
"""Find column headers (once) and derive column centers + header baseline."""
headers = [
("Autorenname(n):", "Autorenname(n):Nachname, Vorname"),
("Jahr/Auflage", "Jahr/Auflage"),
("Titel", "Titel"),
("Ort und Verlag", "Ort und Verlag"),
("Standnummer", "Standnummer"),
("Interne Vermerke", "Interne Vermerke"),
]
found = []
for label, canon in headers:
rects = [
r for r in page0.search_for(label) if r.y0 > 200
] # skip top-of-form duplicates
if rects:
found.append((canon, rects[0]))
found.sort(key=lambda t: t[1].x0)
cols = [(canon, r.x0, r.x1, (r.x0 + r.x1) / 2.0) for canon, r in found]
header_y = min(r.y0 for _, r in found) if found else 0
return cols, header_y
def _extract_table_rows_from_page(
page, cols, header_y, y_top_margin=5, y_bottom_margin=40, y_tol=26.0
):
"""
Group words into logical rows (tolerant to wrapped lines), then map each word
to the nearest column by x-center and join tokens per column.
"""
words = [
w
for w in page.get_text("words")
if w[1] > header_y + y_top_margin
and w[3] < page.rect.height - y_bottom_margin
]
# group into row bands by y (tolerance big enough to capture wrapped lines, but below next row gap)
rows = []
for w in sorted(words, key=lambda w: w[1]):
y = w[1]
for row in rows:
if abs(row["y_mean"] - y) <= y_tol:
row["ys"].append(y)
row["y_mean"] = sum(row["ys"]) / len(row["ys"])
row["words"].append(w)
break
else:
rows.append({"y_mean": y, "ys": [y], "words": [w]})
# map to columns + join
joined_rows = []
for row in rows:
rowdict = {canon: "" for canon, *_ in cols}
words_by_col = {canon: [] for canon, *_ in cols}
for w in sorted(row["words"], key=lambda w: (w[1], w[0])):
xmid = (w[0] + w[2]) / 2.0
canon = min(cols, key=lambda c: abs(xmid - c[3]))[0]
words_by_col[canon].append(w[4])
for canon, toks in words_by_col.items():
rowdict[canon] = _join_tokens(toks)
if any(v for v in rowdict.values()):
joined_rows.append(rowdict)
return joined_rows
# ---------- top-of-form fields ----------
p0 = doc[0]
row1 = _extract_row_values_multiline(
p0,
["Ihr Name und Titel:", "Ihr Fach:", "Telefon:", "Mailadresse:"],
y_window=22,
)
row2 = _extract_row_values_multiline(
p0, ["Veranstaltung:", "Semester:"], y_window=20
)
name_title = row1.get("Ihr Name und Titel:", "") or ""
semap.subject = row1.get("Ihr Fach:", None)
semap.phoneNumber = row1.get("Telefon:", None) # keep as-is (string like "682-308")
semap.mail = row1.get("Mailadresse:", None)
semap.personName = ",".join(name_title.split(",")[:-1]) if name_title else None
semap.personTitle = (
",".join(name_title.split(",")[-1:]).strip() if name_title else None
)
semap.title = row2.get("Veranstaltung:", None)
semap.semester = row2.get("Semester:", None)
# ---------- table extraction (all pages) ----------
cols, header_y = _compute_columns_from_headers(p0)
all_rows: list[dict[str, Any]] = []
for pn in range(len(doc)):
all_rows.extend(_extract_table_rows_from_page(doc[pn], cols, header_y))
# drop the sub-header line "Nachname, Vorname" etc.
filtered = []
for r in all_rows:
if r.get("Autorenname(n):Nachname, Vorname", "").strip() in (
"",
"Nachname, Vorname",
):
# skip if it's just the sub-header line
if all(not r[c] for c in r if c != "Autorenname(n):Nachname, Vorname"):
continue
filtered.append(r)
# build Book objects (same filters as your word parser)
booklist: list[Book] = []
for row in filtered:
b = Book()
b.from_dict(row)
if b.is_empty:
continue
if not b.has_signature:
continue
booklist.append(b)
semap.books = booklist
# keep parity with your post-processing
if ai:
_ = semap.renameSemester
_ = semap.nameSetter
return semap
if __name__ == "__main__":
else_df = elsa_word_to_csv("C:/Users/aky547/Desktop/Antrag ELSA Schweitzer.docx")
else_df = pdf_to_semap("C:/Users/aky547/Dokumente/testsemap.pdf")
# print(else_df)

67
src/logic/xmlparser.py Normal file
View File

@@ -0,0 +1,67 @@
import xml.etree.ElementTree as ET
from src.logic.dataclass import Apparat, BookData, SemapDocument, XMLMailSubmission
from src.logic.semester import Semester
def parse_xml_submission(xml_string: str) -> XMLMailSubmission:
"""
Parse an XML string representing a mail submission and return an XMLMailSubmission object.
"""
submission = XMLMailSubmission()
root = ET.fromstring(xml_string)
static_data = root.find("static")
static_info = {child.tag: child.text for child in static_data}
books = root.find("books")
books_info = []
for book in books:
book_details = {detail.tag: detail.text for detail in book}
book = BookData(
author=book_details.get("authorname"),
year=book_details.get("year").split("/")[0]
if "/" in book_details.get("year")
else book_details.get("year"),
edition=book_details.get("year").split("/")[1]
if "/" in book_details.get("year")
else None,
title=book_details.get("title"),
signature=book_details.get("signature"),
)
books_info.append(book)
# Extract static data
submission.name = static_info.get("name")
submission.lastname = static_info.get("lastname")
submission.title = static_info.get("title")
submission.telno = int(static_info.get("telno"))
submission.email = static_info.get("mail")
submission.app_name = static_info.get("apparatsname")
submission.subject = static_info.get("subject")
sem_year = static_info.get("semester").split()[1]
sem_term = static_info.get("semester").split()[0]
submission.semester = Semester(semester=sem_term, year=int(sem_year))
submission.books = books_info
# Extract book information
# book_info = []
# for book in books:
# book_details = {detail.tag: detail.text for detail in book}
# book_info.append(book_details)
return submission
def eml_parser(path: str) -> XMLMailSubmission:
with open(path, "r", encoding="utf-8") as file:
xml_content = file.read().split("\n\n", 1)[1] # Skip headers
print("EML content loaded, parsing XML...")
print(xml_content)
return parse_xml_submission(xml_content)
def eml_to_semap(xml_mail: XMLMailSubmission) -> SemapDocument:
submission = eml_parser(xml_mail)
semap_doc = SemapDocument(
# prof=Prof(name=submission.name, lastname=submission.lastname, email=submission.email),
apparat=Apparat(name=submission.app_name, subject=submission.subject),
semester=submission.semester,
books=submission.books,
)
return semap_doc

View File

@@ -1,7 +1,10 @@
from pyzotero import zotero
from dataclasses import dataclass
from src.logic.webrequest import WebRequest, BibTextTransformer
from typing import Optional
from pyzotero import zotero
from src import settings
from src.logic.webrequest import BibTextTransformer, WebRequest
@dataclass
@@ -10,11 +13,11 @@ class Creator:
lastName: str = None
creatorType: str = "author"
def from_dict(self, data: dict):
def from_dict(self, data: dict) -> None:
for key, value in data.items():
setattr(self, key, value)
def from_string(self, data: str):
def from_string(self, data: str) -> "Creator":
if "," in data:
self.firstName = data.split(",")[1]
self.lastName = data.split(",")[0]
@@ -54,7 +57,7 @@ class Book:
rights: str = None
extra: str = None
def to_dict(self):
def to_dict(self) -> dict:
ret = {}
for key, value in self.__dict__.items():
if value:
@@ -93,14 +96,14 @@ class BookSection:
collections = list
relations = dict
def to_dict(self):
def to_dict(self) -> dict:
ret = {}
for key, value in self.__dict__.items():
if value:
ret[key] = value
return ret
def assign(self, book):
def assign(self, book) -> None:
for key, value in book.__dict__.items():
if key in self.__dict__.keys():
try:
@@ -140,14 +143,14 @@ class JournalArticle:
collections = list
relations = dict
def to_dict(self):
def to_dict(self) -> dict:
ret = {}
for key, value in self.__dict__.items():
if value:
ret[key] = value
return ret
def assign(self, book: dict):
def assign(self, book: dict) -> None:
for key, value in book.__dict__.items():
if key in self.__dict__.keys():
try:
@@ -160,15 +163,17 @@ class ZoteroController:
zoterocfg = settings.zotero
def __init__(self):
self.zot = zotero.Zotero(
if self.zoterocfg.library_id is None:
return
self.zot = zotero.Zotero( # type: ignore
self.zoterocfg.library_id,
self.zoterocfg.library_type,
self.zoterocfg.api_key,
)
def get_books(self):
def get_books(self) -> list:
ret = []
items = self.zot.top()
items = self.zot.top() # type: ignore
for item in items:
if item["data"]["itemType"] == "book":
ret.append(item)
@@ -176,7 +181,7 @@ class ZoteroController:
# create item in zotero
# item is a part of a book
def __get_data(self, isbn):
def __get_data(self, isbn) -> dict:
web = WebRequest()
web.get_ppn(isbn)
data = web.get_data_elsa()
@@ -185,8 +190,8 @@ class ZoteroController:
book = bib.return_data()
return book
# # print(zot.item_template("bookSection"))
def createBook(self, isbn):
# # #print(zot.item_template("bookSection"))
def createBook(self, isbn) -> Book:
book = self.__get_data(isbn)
bookdata = Book()
@@ -205,23 +210,23 @@ class ZoteroController:
bookdata.creators = authors
return bookdata
def createItem(self, item):
resp = self.zot.create_items([item])
def createItem(self, item) -> Optional[str]:
resp = self.zot.create_items([item]) # type: ignore
if "successful" in resp.keys():
# print(resp["successful"]["0"]["key"])
# #print(resp["successful"]["0"]["key"])
return resp["successful"]["0"]["key"]
else:
return None
def deleteItem(self, key):
def deleteItem(self, key) -> None:
items = self.zot.items()
for item in items:
if item["key"] == key:
self.zot.delete_item(item)
# print(item)
self.zot.delete_item(item) # type: ignore
# #print(item)
break
def createHGSection(self, book: Book, data: dict):
def createHGSection(self, book: Book, data: dict) -> Optional[str]:
chapter = BookSection()
chapter.assign(book)
chapter.pages = data["pages"]
@@ -239,11 +244,11 @@ class ZoteroController:
]
chapter.creators += authors
# print(chapter.to_dict())
# #print(chapter.to_dict())
return self.createItem(chapter.to_dict())
pass
def createBookSection(self, book: Book, data: dict):
def createBookSection(self, book: Book, data: dict) -> Optional[str]:
chapter = BookSection()
chapter.assign(book)
chapter.pages = data["pages"]
@@ -254,8 +259,8 @@ class ZoteroController:
return self.createItem(chapter.to_dict())
# chapter.creators
def createJournalArticle(self, journal, article):
# print(type(article))
def createJournalArticle(self, journal, article) -> Optional[str]:
# #print(type(article))
journalarticle = JournalArticle()
journalarticle.assign(journal)
journalarticle.itemType = "journalArticle"
@@ -271,12 +276,12 @@ class ZoteroController:
journalarticle.issue = article["issue"]
journalarticle.url = article["isbn"]
# print(journalarticle.to_dict())
# #print(journalarticle.to_dict())
return self.createItem(journalarticle.to_dict())
def get_citation(self, item):
title = self.zot.item(
def get_citation(self, item) -> str:
title = self.zot.item( # type: ignore
item,
content="bib",
style="deutsche-gesellschaft-fur-psychologie",
@@ -317,16 +322,16 @@ if __name__ == "__main__":
# if isinstance(publishers, str):
# publishers = [publishers]
# for publisher in publishers:
# # print(publisher)
# # #print(publisher)
# creator = Creator().from_string(publisher)
# creator.creatorType = "editor"
# authors.append(creator.__dict__)
# chapter.creators = authors
# chapter.publisher = book.publisher
# # print(chapter.to_dict())
# # #print(chapter.to_dict())
# createBookSection(chapter.to_dict())
# get_citation("9ZXH8DDE")
# # # print()
# # print(get_books())
# # print(zot.item_creator_types("bookSection"))
# # # #print()
# # #print(get_books())
# # #print(zot.item_creator_types("bookSection"))

25
src/shared/logging.py Normal file
View File

@@ -0,0 +1,25 @@
import sys
import loguru
from src import LOG_DIR
log = loguru.logger
_configured = False
def configure(level: str = "INFO", to_stdout: bool = True, rotate_bytes: str = "1 MB"):
global _configured
if _configured:
return log
log.remove()
if to_stdout:
log.add(sys.stdout, level=level)
# application rolling log
log.add(
f"{LOG_DIR}/application.log",
rotation=rotate_bytes,
retention="10 days",
)
_configured = True
return log

BIN
src/sounds/ding.mp3 Normal file

Binary file not shown.

BIN
src/sounds/error.mp3 Normal file

Binary file not shown.

View File

@@ -6,9 +6,15 @@ from dataclasses import dataclass
from dataclasses import field as dataclass_field
from typing import Any, List
from src import LOG_DIR
from src.logic.dataclass import BookData
import loguru
import sys
log = loguru.logger
log.remove()
log.add(sys.stdout, level="INFO")
log.add(f"{LOG_DIR}/application.log", rotation="1 MB", retention="10 days")
###Pydatnic models
@@ -131,8 +137,8 @@ class ARRAYData:
return data
except Exception:
# # print(f"ARRAYData.transform failed, {source}, {search}")
logger.exception(f"ARRAYData.transform failed, no string {search}")
# # log.debug(f"ARRAYData.transform failed, {source}, {search}")
log.exception(f"ARRAYData.transform failed, no string {search}")
return ""
def _get_list_entry(source: str, search: str, entry: str) -> str:
@@ -509,4 +515,4 @@ if __name__ == "__main__":
ret = RDSData().transform(data)
data = ret.return_data("rds_availability")
# print(data)
# log.debug(data)

View File

@@ -1,961 +0,0 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\semesterapparat_ui.ui'
#
# Created by: PyQt6 UI code generator 6.7.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt6 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.setWindowModality(QtCore.Qt.WindowModality.WindowModal)
MainWindow.setEnabled(True)
MainWindow.resize(1590, 800)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(MainWindow.sizePolicy().hasHeightForWidth())
MainWindow.setSizePolicy(sizePolicy)
MainWindow.setMinimumSize(QtCore.QSize(1278, 800))
MainWindow.setMaximumSize(QtCore.QSize(1590, 800))
MainWindow.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.NoContextMenu)
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap("c:\\Users\\aky547\\GitHub\\SemesterapparatsManager\\src\\ui\\../../../../../../icons/logo.ico"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
MainWindow.setWindowIcon(icon)
MainWindow.setStatusTip("")
self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.centralwidget.sizePolicy().hasHeightForWidth())
self.centralwidget.setSizePolicy(sizePolicy)
self.centralwidget.setObjectName("centralwidget")
self.verticalLayoutWidget = QtWidgets.QWidget(parent=self.centralwidget)
self.verticalLayoutWidget.setGeometry(QtCore.QRect(0, 0, 1271, 751))
self.verticalLayoutWidget.setObjectName("verticalLayoutWidget")
self.mainLayout = QtWidgets.QVBoxLayout(self.verticalLayoutWidget)
self.mainLayout.setContentsMargins(0, 0, 0, 0)
self.mainLayout.setObjectName("mainLayout")
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.gridLayout = QtWidgets.QGridLayout()
self.gridLayout.setObjectName("gridLayout")
self.tabWidget = QtWidgets.QTabWidget(parent=self.verticalLayoutWidget)
self.tabWidget.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
self.tabWidget.setObjectName("tabWidget")
self.createApparat = QtWidgets.QWidget()
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.createApparat.sizePolicy().hasHeightForWidth())
self.createApparat.setSizePolicy(sizePolicy)
self.createApparat.setObjectName("createApparat")
self.horizontalLayoutWidget_2 = QtWidgets.QWidget(parent=self.createApparat)
self.horizontalLayoutWidget_2.setGeometry(QtCore.QRect(0, 0, 1261, 163))
self.horizontalLayoutWidget_2.setObjectName("horizontalLayoutWidget_2")
self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget_2)
self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.formLayout = QtWidgets.QFormLayout()
self.formLayout.setObjectName("formLayout")
self.verticalLayout_2 = QtWidgets.QVBoxLayout()
self.verticalLayout_2.setObjectName("verticalLayout_2")
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
self.verticalLayout_2.addItem(spacerItem)
self.create_document = QtWidgets.QPushButton(parent=self.horizontalLayoutWidget_2)
self.create_document.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
self.create_document.setObjectName("create_document")
self.verticalLayout_2.addWidget(self.create_document)
self.create_new_app = QtWidgets.QPushButton(parent=self.horizontalLayoutWidget_2)
self.create_new_app.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
self.create_new_app.setObjectName("create_new_app")
self.verticalLayout_2.addWidget(self.create_new_app)
self.cancel_active_selection = QtWidgets.QPushButton(parent=self.horizontalLayoutWidget_2)
self.cancel_active_selection.setEnabled(False)
self.cancel_active_selection.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
self.cancel_active_selection.setObjectName("cancel_active_selection")
self.verticalLayout_2.addWidget(self.cancel_active_selection)
spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
self.verticalLayout_2.addItem(spacerItem1)
self.formLayout.setLayout(1, QtWidgets.QFormLayout.ItemRole.LabelRole, self.verticalLayout_2)
self.tableWidget_apparate = QtWidgets.QTableWidget(parent=self.horizontalLayoutWidget_2)
self.tableWidget_apparate.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
self.tableWidget_apparate.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.SizeAdjustPolicy.AdjustToContents)
self.tableWidget_apparate.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)
self.tableWidget_apparate.setAlternatingRowColors(True)
self.tableWidget_apparate.setTextElideMode(QtCore.Qt.TextElideMode.ElideMiddle)
self.tableWidget_apparate.setObjectName("tableWidget_apparate")
self.tableWidget_apparate.setColumnCount(6)
self.tableWidget_apparate.setRowCount(0)
item = QtWidgets.QTableWidgetItem()
self.tableWidget_apparate.setHorizontalHeaderItem(0, item)
item = QtWidgets.QTableWidgetItem()
self.tableWidget_apparate.setHorizontalHeaderItem(1, item)
item = QtWidgets.QTableWidgetItem()
self.tableWidget_apparate.setHorizontalHeaderItem(2, item)
item = QtWidgets.QTableWidgetItem()
self.tableWidget_apparate.setHorizontalHeaderItem(3, item)
item = QtWidgets.QTableWidgetItem()
self.tableWidget_apparate.setHorizontalHeaderItem(4, item)
item = QtWidgets.QTableWidgetItem()
self.tableWidget_apparate.setHorizontalHeaderItem(5, item)
self.tableWidget_apparate.horizontalHeader().setCascadingSectionResizes(True)
self.formLayout.setWidget(1, QtWidgets.QFormLayout.ItemRole.FieldRole, self.tableWidget_apparate)
self.horizontalLayout_2.addLayout(self.formLayout)
self.line = QtWidgets.QFrame(parent=self.createApparat)
self.line.setGeometry(QtCore.QRect(0, 160, 1261, 21))
self.line.setFrameShape(QtWidgets.QFrame.Shape.HLine)
self.line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
self.line.setObjectName("line")
self.gridLayoutWidget_2 = QtWidgets.QWidget(parent=self.createApparat)
self.gridLayoutWidget_2.setEnabled(True)
self.gridLayoutWidget_2.setGeometry(QtCore.QRect(0, 180, 1261, 511))
self.gridLayoutWidget_2.setObjectName("gridLayoutWidget_2")
self.gridLayout_2 = QtWidgets.QGridLayout(self.gridLayoutWidget_2)
self.gridLayout_2.setContentsMargins(0, 0, 0, 0)
self.gridLayout_2.setObjectName("gridLayout_2")
self.horizontalLayout_5 = QtWidgets.QHBoxLayout()
self.horizontalLayout_5.setObjectName("horizontalLayout_5")
spacerItem2 = QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Minimum)
self.horizontalLayout_5.addItem(spacerItem2)
self.chkbx_show_del_media = QtWidgets.QCheckBox(parent=self.gridLayoutWidget_2)
self.chkbx_show_del_media.setObjectName("chkbx_show_del_media")
self.horizontalLayout_5.addWidget(self.chkbx_show_del_media)
spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Minimum)
self.horizontalLayout_5.addItem(spacerItem3)
self.btn_reserve = QtWidgets.QPushButton(parent=self.gridLayoutWidget_2)
self.btn_reserve.setObjectName("btn_reserve")
self.horizontalLayout_5.addWidget(self.btn_reserve)
self.add_layout = QtWidgets.QHBoxLayout()
self.add_layout.setObjectName("add_layout")
self.label_info = QtWidgets.QLabel(parent=self.gridLayoutWidget_2)
self.label_info.setObjectName("label_info")
self.add_layout.addWidget(self.label_info)
self.line_2 = QtWidgets.QFrame(parent=self.gridLayoutWidget_2)
self.line_2.setFrameShape(QtWidgets.QFrame.Shape.VLine)
self.line_2.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
self.line_2.setObjectName("line_2")
self.add_layout.addWidget(self.line_2)
self.progress_label = QtWidgets.QLabel(parent=self.gridLayoutWidget_2)
self.progress_label.setObjectName("progress_label")
self.add_layout.addWidget(self.progress_label)
self.horizontalLayout_5.addLayout(self.add_layout)
spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Minimum)
self.horizontalLayout_5.addItem(spacerItem4)
self.avail_layout = QtWidgets.QHBoxLayout()
self.avail_layout.setObjectName("avail_layout")
self.horizontalLayout_5.addLayout(self.avail_layout)
self.label_20 = QtWidgets.QLabel(parent=self.gridLayoutWidget_2)
self.label_20.setObjectName("label_20")
self.horizontalLayout_5.addWidget(self.label_20)
self.line_3 = QtWidgets.QFrame(parent=self.gridLayoutWidget_2)
self.line_3.setFrameShape(QtWidgets.QFrame.Shape.VLine)
self.line_3.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
self.line_3.setObjectName("line_3")
self.horizontalLayout_5.addWidget(self.line_3)
self.avail_status = QtWidgets.QLabel(parent=self.gridLayoutWidget_2)
self.avail_status.setObjectName("avail_status")
self.horizontalLayout_5.addWidget(self.avail_status)
self.automation_add_selected_books = QtWidgets.QPushButton(parent=self.gridLayoutWidget_2)
self.automation_add_selected_books.setObjectName("automation_add_selected_books")
self.horizontalLayout_5.addWidget(self.automation_add_selected_books)
spacerItem5 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
self.horizontalLayout_5.addItem(spacerItem5)
self.gridLayout_2.addLayout(self.horizontalLayout_5, 4, 0, 1, 1)
self.tableWidget_apparat_media = QtWidgets.QTableWidget(parent=self.gridLayoutWidget_2)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.tableWidget_apparat_media.sizePolicy().hasHeightForWidth())
self.tableWidget_apparat_media.setSizePolicy(sizePolicy)
self.tableWidget_apparat_media.setMinimumSize(QtCore.QSize(1259, 0))
self.tableWidget_apparat_media.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
self.tableWidget_apparat_media.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.CustomContextMenu)
self.tableWidget_apparat_media.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.SizeAdjustPolicy.AdjustToContents)
self.tableWidget_apparat_media.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)
self.tableWidget_apparat_media.setAlternatingRowColors(True)
self.tableWidget_apparat_media.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows)
self.tableWidget_apparat_media.setObjectName("tableWidget_apparat_media")
self.tableWidget_apparat_media.setColumnCount(7)
self.tableWidget_apparat_media.setRowCount(0)
item = QtWidgets.QTableWidgetItem()
self.tableWidget_apparat_media.setHorizontalHeaderItem(0, item)
item = QtWidgets.QTableWidgetItem()
self.tableWidget_apparat_media.setHorizontalHeaderItem(1, item)
item = QtWidgets.QTableWidgetItem()
self.tableWidget_apparat_media.setHorizontalHeaderItem(2, item)
item = QtWidgets.QTableWidgetItem()
self.tableWidget_apparat_media.setHorizontalHeaderItem(3, item)
item = QtWidgets.QTableWidgetItem()
self.tableWidget_apparat_media.setHorizontalHeaderItem(4, item)
item = QtWidgets.QTableWidgetItem()
self.tableWidget_apparat_media.setHorizontalHeaderItem(5, item)
item = QtWidgets.QTableWidgetItem()
self.tableWidget_apparat_media.setHorizontalHeaderItem(6, item)
self.tableWidget_apparat_media.horizontalHeader().setCascadingSectionResizes(True)
self.gridLayout_2.addWidget(self.tableWidget_apparat_media, 9, 0, 1, 1)
self.label = QtWidgets.QLabel(parent=self.gridLayoutWidget_2)
font = QtGui.QFont()
font.setPointSize(11)
font.setBold(True)
self.label.setFont(font)
self.label.setObjectName("label")
self.gridLayout_2.addWidget(self.label, 2, 0, 1, 1)
self.app_group_box = QtWidgets.QGroupBox(parent=self.gridLayoutWidget_2)
self.app_group_box.setEnabled(True)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.app_group_box.sizePolicy().hasHeightForWidth())
self.app_group_box.setSizePolicy(sizePolicy)
self.app_group_box.setMinimumSize(QtCore.QSize(0, 210))
font = QtGui.QFont()
font.setPointSize(12)
font.setBold(True)
self.app_group_box.setFont(font)
self.app_group_box.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
self.app_group_box.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeading|QtCore.Qt.AlignmentFlag.AlignLeft|QtCore.Qt.AlignmentFlag.AlignVCenter)
self.app_group_box.setCheckable(False)
self.app_group_box.setObjectName("app_group_box")
self.document_list = QtWidgets.QTableWidget(parent=self.app_group_box)
self.document_list.setGeometry(QtCore.QRect(780, 20, 321, 181))
font = QtGui.QFont()
font.setPointSize(10)
font.setBold(False)
font.setKerning(False)
self.document_list.setFont(font)
self.document_list.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
self.document_list.setAcceptDrops(True)
self.document_list.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.document_list.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.SizeAdjustPolicy.AdjustToContents)
self.document_list.setDragEnabled(True)
self.document_list.setDragDropMode(QtWidgets.QAbstractItemView.DragDropMode.DragOnly)
self.document_list.setDefaultDropAction(QtCore.Qt.DropAction.LinkAction)
self.document_list.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.SingleSelection)
self.document_list.setObjectName("document_list")
self.document_list.setColumnCount(4)
self.document_list.setRowCount(0)
item = QtWidgets.QTableWidgetItem()
font = QtGui.QFont()
font.setFamily("Arial")
font.setPointSize(8)
item.setFont(font)
self.document_list.setHorizontalHeaderItem(0, item)
item = QtWidgets.QTableWidgetItem()
font = QtGui.QFont()
font.setFamily("Arial")
font.setPointSize(8)
item.setFont(font)
self.document_list.setHorizontalHeaderItem(1, item)
item = QtWidgets.QTableWidgetItem()
font = QtGui.QFont()
font.setFamily("Arial")
font.setPointSize(8)
item.setFont(font)
self.document_list.setHorizontalHeaderItem(2, item)
item = QtWidgets.QTableWidgetItem()
self.document_list.setHorizontalHeaderItem(3, item)
self.document_list.horizontalHeader().setDefaultSectionSize(107)
self.check_file = QtWidgets.QPushButton(parent=self.app_group_box)
self.check_file.setGeometry(QtCore.QRect(1110, 120, 131, 51))
font = QtGui.QFont()
font.setPointSize(9)
font.setBold(False)
self.check_file.setFont(font)
self.check_file.setObjectName("check_file")
self.btn_open_document = QtWidgets.QPushButton(parent=self.app_group_box)
self.btn_open_document.setGeometry(QtCore.QRect(1110, 80, 131, 25))
font = QtGui.QFont()
font.setPointSize(9)
font.setBold(False)
self.btn_open_document.setFont(font)
self.btn_open_document.setObjectName("btn_open_document")
self.btn_add_document = QtWidgets.QPushButton(parent=self.app_group_box)
self.btn_add_document.setGeometry(QtCore.QRect(1110, 40, 131, 25))
font = QtGui.QFont()
font.setPointSize(9)
font.setBold(False)
self.btn_add_document.setFont(font)
self.btn_add_document.setObjectName("btn_add_document")
self.appname_mand = QtWidgets.QLabel(parent=self.app_group_box)
self.appname_mand.setGeometry(QtCore.QRect(330, 50, 16, 21))
font = QtGui.QFont()
font.setPointSize(9)
font.setBold(False)
self.appname_mand.setFont(font)
self.appname_mand.setObjectName("appname_mand")
self.profname_mand = QtWidgets.QLabel(parent=self.app_group_box)
self.profname_mand.setGeometry(QtCore.QRect(110, 110, 16, 21))
font = QtGui.QFont()
font.setPointSize(9)
font.setBold(False)
self.profname_mand.setFont(font)
self.profname_mand.setObjectName("profname_mand")
self.prof_title = QtWidgets.QLineEdit(parent=self.app_group_box)
self.prof_title.setGeometry(QtCore.QRect(120, 80, 71, 20))
font = QtGui.QFont()
font.setPointSize(9)
font.setBold(False)
self.prof_title.setFont(font)
self.prof_title.setFocusPolicy(QtCore.Qt.FocusPolicy.ClickFocus)
self.prof_title.setObjectName("prof_title")
self.fach_mand = QtWidgets.QLabel(parent=self.app_group_box)
self.fach_mand.setGeometry(QtCore.QRect(510, 50, 47, 21))
font = QtGui.QFont()
font.setPointSize(9)
font.setBold(False)
self.fach_mand.setFont(font)
self.fach_mand.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
self.fach_mand.setObjectName("fach_mand")
self.btn_apparat_apply = QtWidgets.QPushButton(parent=self.app_group_box)
self.btn_apparat_apply.setGeometry(QtCore.QRect(360, 150, 75, 23))
font = QtGui.QFont()
font.setPointSize(9)
font.setBold(False)
self.btn_apparat_apply.setFont(font)
self.btn_apparat_apply.setObjectName("btn_apparat_apply")
self.label_9 = QtWidgets.QLabel(parent=self.app_group_box)
self.label_9.setGeometry(QtCore.QRect(20, 160, 71, 21))
font = QtGui.QFont()
font.setPointSize(9)
font.setBold(False)
self.label_9.setFont(font)
self.label_9.setObjectName("label_9")
self.gridLayoutWidget_5 = QtWidgets.QWidget(parent=self.app_group_box)
self.gridLayoutWidget_5.setGeometry(QtCore.QRect(520, 30, 241, 61))
self.gridLayoutWidget_5.setObjectName("gridLayoutWidget_5")
self.gridLayout_6 = QtWidgets.QGridLayout(self.gridLayoutWidget_5)
self.gridLayout_6.setContentsMargins(0, 0, 0, 0)
self.gridLayout_6.setObjectName("gridLayout_6")
self.app_fach = QtWidgets.QComboBox(parent=self.gridLayoutWidget_5)
self.app_fach.setMaximumSize(QtCore.QSize(16777215, 25))
font = QtGui.QFont()
font.setPointSize(9)
font.setBold(False)
self.app_fach.setFont(font)
self.app_fach.setEditable(True)
self.app_fach.setObjectName("app_fach")
self.gridLayout_6.addWidget(self.app_fach, 0, 1, 1, 1)
spacerItem6 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
self.gridLayout_6.addItem(spacerItem6, 0, 3, 1, 1)
self.valid_check_app_fach = QtWidgets.QToolButton(parent=self.gridLayoutWidget_5)
self.valid_check_app_fach.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
self.valid_check_app_fach.setText("")
self.valid_check_app_fach.setAutoRaise(True)
self.valid_check_app_fach.setArrowType(QtCore.Qt.ArrowType.NoArrow)
self.valid_check_app_fach.setObjectName("valid_check_app_fach")
self.gridLayout_6.addWidget(self.valid_check_app_fach, 0, 2, 1, 1)
self._mand = QtWidgets.QLabel(parent=self.app_group_box)
self._mand.setGeometry(QtCore.QRect(330, 90, 16, 21))
font = QtGui.QFont()
font.setPointSize(9)
font.setBold(False)
self._mand.setFont(font)
self._mand.setObjectName("_mand")
self.prof_tel_nr = QtWidgets.QLineEdit(parent=self.app_group_box)
self.prof_tel_nr.setGeometry(QtCore.QRect(120, 160, 121, 20))
font = QtGui.QFont()
font.setPointSize(9)
font.setBold(False)
self.prof_tel_nr.setFont(font)
self.prof_tel_nr.setInputMethodHints(QtCore.Qt.InputMethodHint.ImhNone)
self.prof_tel_nr.setPlaceholderText("")
self.prof_tel_nr.setObjectName("prof_tel_nr")
self.check_eternal_app = QtWidgets.QCheckBox(parent=self.app_group_box)
self.check_eternal_app.setEnabled(False)
self.check_eternal_app.setGeometry(QtCore.QRect(340, 120, 101, 17))
font = QtGui.QFont()
font.setPointSize(9)
font.setBold(False)
self.check_eternal_app.setFont(font)
self.check_eternal_app.setObjectName("check_eternal_app")
self.sem_sommer = QtWidgets.QCheckBox(parent=self.app_group_box)
self.sem_sommer.setGeometry(QtCore.QRect(340, 100, 82, 17))
font = QtGui.QFont()
font.setPointSize(9)
font.setBold(False)
self.sem_sommer.setFont(font)
self.sem_sommer.setFocusPolicy(QtCore.Qt.FocusPolicy.StrongFocus)
self.sem_sommer.setObjectName("sem_sommer")
self.drpdwn_prof_name = QtWidgets.QComboBox(parent=self.app_group_box)
self.drpdwn_prof_name.setGeometry(QtCore.QRect(120, 110, 121, 22))
font = QtGui.QFont()
font.setPointSize(9)
font.setBold(False)
self.drpdwn_prof_name.setFont(font)
self.drpdwn_prof_name.setFocusPolicy(QtCore.Qt.FocusPolicy.StrongFocus)
self.drpdwn_prof_name.setInputMethodHints(QtCore.Qt.InputMethodHint.ImhNone)
self.drpdwn_prof_name.setEditable(True)
self.drpdwn_prof_name.setInsertPolicy(QtWidgets.QComboBox.InsertPolicy.InsertAlphabetically)
self.drpdwn_prof_name.setPlaceholderText("")
self.drpdwn_prof_name.setFrame(True)
self.drpdwn_prof_name.setObjectName("drpdwn_prof_name")
self.mail_mand = QtWidgets.QLabel(parent=self.app_group_box)
self.mail_mand.setGeometry(QtCore.QRect(110, 140, 47, 21))
font = QtGui.QFont()
font.setPointSize(9)
font.setBold(False)
self.mail_mand.setFont(font)
self.mail_mand.setObjectName("mail_mand")
self.label_3 = QtWidgets.QLabel(parent=self.app_group_box)
self.label_3.setGeometry(QtCore.QRect(20, 80, 61, 20))
font = QtGui.QFont()
font.setPointSize(9)
font.setBold(False)
self.label_3.setFont(font)
self.label_3.setObjectName("label_3")
self.label_2 = QtWidgets.QLabel(parent=self.app_group_box)
self.label_2.setGeometry(QtCore.QRect(20, 50, 101, 21))
font = QtGui.QFont()
font.setPointSize(9)
font.setBold(False)
self.label_2.setFont(font)
self.label_2.setObjectName("label_2")
self.label_8 = QtWidgets.QLabel(parent=self.app_group_box)
self.label_8.setGeometry(QtCore.QRect(20, 140, 71, 21))
font = QtGui.QFont()
font.setPointSize(9)
font.setBold(False)
self.label_8.setFont(font)
self.label_8.setObjectName("label_8")
self.label_10 = QtWidgets.QLabel(parent=self.app_group_box)
self.label_10.setGeometry(QtCore.QRect(480, 50, 51, 21))
font = QtGui.QFont()
font.setPointSize(9)
font.setBold(False)
self.label_10.setFont(font)
self.label_10.setObjectName("label_10")
self.prof_mail = QtWidgets.QLineEdit(parent=self.app_group_box)
self.prof_mail.setGeometry(QtCore.QRect(120, 140, 121, 20))
font = QtGui.QFont()
font.setPointSize(9)
font.setBold(False)
self.prof_mail.setFont(font)
self.prof_mail.setInputMethodHints(QtCore.Qt.InputMethodHint.ImhEmailCharactersOnly)
self.prof_mail.setMaxLength(200)
self.prof_mail.setPlaceholderText("")
self.prof_mail.setObjectName("prof_mail")
self.formLayoutWidget_2 = QtWidgets.QWidget(parent=self.app_group_box)
self.formLayoutWidget_2.setGeometry(QtCore.QRect(560, 100, 211, 99))
self.formLayoutWidget_2.setObjectName("formLayoutWidget_2")
self.formLayout_3 = QtWidgets.QFormLayout(self.formLayoutWidget_2)
self.formLayout_3.setContentsMargins(0, 0, 0, 0)
self.formLayout_3.setObjectName("formLayout_3")
self.label_12 = QtWidgets.QLabel(parent=self.formLayoutWidget_2)
font = QtGui.QFont()
font.setPointSize(9)
font.setBold(False)
self.label_12.setFont(font)
self.label_12.setObjectName("label_12")
self.formLayout_3.setWidget(0, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_12)
self.prof_id_adis = QtWidgets.QLineEdit(parent=self.formLayoutWidget_2)
font = QtGui.QFont()
font.setPointSize(9)
font.setBold(False)
self.prof_id_adis.setFont(font)
self.prof_id_adis.setInputMethodHints(QtCore.Qt.InputMethodHint.ImhPreferNumbers)
self.prof_id_adis.setText("")
self.prof_id_adis.setObjectName("prof_id_adis")
self.formLayout_3.setWidget(0, QtWidgets.QFormLayout.ItemRole.FieldRole, self.prof_id_adis)
self.label_13 = QtWidgets.QLabel(parent=self.formLayoutWidget_2)
font = QtGui.QFont()
font.setPointSize(9)
font.setBold(False)
self.label_13.setFont(font)
self.label_13.setObjectName("label_13")
self.formLayout_3.setWidget(1, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_13)
self.apparat_id_adis = QtWidgets.QLineEdit(parent=self.formLayoutWidget_2)
font = QtGui.QFont()
font.setPointSize(9)
font.setBold(False)
self.apparat_id_adis.setFont(font)
self.apparat_id_adis.setInputMethodHints(QtCore.Qt.InputMethodHint.ImhPreferNumbers)
self.apparat_id_adis.setObjectName("apparat_id_adis")
self.formLayout_3.setWidget(1, QtWidgets.QFormLayout.ItemRole.FieldRole, self.apparat_id_adis)
self.sem_year = QtWidgets.QLineEdit(parent=self.app_group_box)
self.sem_year.setGeometry(QtCore.QRect(410, 90, 113, 20))
font = QtGui.QFont()
font.setPointSize(9)
font.setBold(False)
self.sem_year.setFont(font)
self.sem_year.setFocusPolicy(QtCore.Qt.FocusPolicy.StrongFocus)
self.sem_year.setMaxLength(5)
self.sem_year.setObjectName("sem_year")
self.check_send_mail = QtWidgets.QCheckBox(parent=self.app_group_box)
self.check_send_mail.setGeometry(QtCore.QRect(450, 150, 91, 24))
font = QtGui.QFont()
font.setPointSize(9)
font.setBold(False)
self.check_send_mail.setFont(font)
self.check_send_mail.setObjectName("check_send_mail")
self.sem_winter = QtWidgets.QCheckBox(parent=self.app_group_box)
self.sem_winter.setGeometry(QtCore.QRect(340, 80, 82, 17))
font = QtGui.QFont()
font.setPointSize(9)
font.setBold(False)
self.sem_winter.setFont(font)
self.sem_winter.setFocusPolicy(QtCore.Qt.FocusPolicy.StrongFocus)
self.sem_winter.setObjectName("sem_winter")
self.label_4 = QtWidgets.QLabel(parent=self.app_group_box)
self.label_4.setGeometry(QtCore.QRect(20, 110, 71, 21))
font = QtGui.QFont()
font.setPointSize(9)
font.setBold(False)
self.label_4.setFont(font)
self.label_4.setObjectName("label_4")
self.telnr_mand = QtWidgets.QLabel(parent=self.app_group_box)
self.telnr_mand.setGeometry(QtCore.QRect(110, 160, 47, 21))
font = QtGui.QFont()
font.setPointSize(9)
font.setBold(False)
self.telnr_mand.setFont(font)
self.telnr_mand.setObjectName("telnr_mand")
self.btn_apparat_save = QtWidgets.QPushButton(parent=self.app_group_box)
self.btn_apparat_save.setGeometry(QtCore.QRect(270, 150, 75, 23))
font = QtGui.QFont()
font.setPointSize(9)
font.setBold(False)
self.btn_apparat_save.setFont(font)
self.btn_apparat_save.setStatusTip("")
self.btn_apparat_save.setObjectName("btn_apparat_save")
self.label_5 = QtWidgets.QLabel(parent=self.app_group_box)
self.label_5.setGeometry(QtCore.QRect(250, 50, 91, 21))
font = QtGui.QFont()
font.setPointSize(9)
font.setBold(False)
self.label_5.setFont(font)
self.label_5.setObjectName("label_5")
self.app_name = QtWidgets.QLineEdit(parent=self.app_group_box)
self.app_name.setGeometry(QtCore.QRect(340, 50, 113, 20))
font = QtGui.QFont()
font.setPointSize(9)
font.setBold(False)
self.app_name.setFont(font)
self.app_name.setFocusPolicy(QtCore.Qt.FocusPolicy.StrongFocus)
self.app_name.setObjectName("app_name")
self.drpdwn_app_nr = QtWidgets.QComboBox(parent=self.app_group_box)
self.drpdwn_app_nr.setGeometry(QtCore.QRect(120, 50, 69, 22))
font = QtGui.QFont()
font.setPointSize(9)
font.setBold(False)
self.drpdwn_app_nr.setFont(font)
self.drpdwn_app_nr.setInputMethodHints(QtCore.Qt.InputMethodHint.ImhNone)
self.drpdwn_app_nr.setEditable(True)
self.drpdwn_app_nr.setObjectName("drpdwn_app_nr")
self.label_6 = QtWidgets.QLabel(parent=self.app_group_box)
self.label_6.setGeometry(QtCore.QRect(270, 90, 61, 21))
font = QtGui.QFont()
font.setPointSize(9)
font.setBold(False)
self.label_6.setFont(font)
self.label_6.setObjectName("label_6")
self.valid_check_profname = QtWidgets.QToolButton(parent=self.app_group_box)
self.valid_check_profname.setGeometry(QtCore.QRect(240, 110, 23, 22))
self.valid_check_profname.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
self.valid_check_profname.setText("")
self.valid_check_profname.setAutoRaise(True)
self.valid_check_profname.setArrowType(QtCore.Qt.ArrowType.NoArrow)
self.valid_check_profname.setObjectName("valid_check_profname")
self.valid_check_appname = QtWidgets.QToolButton(parent=self.app_group_box)
self.valid_check_appname.setGeometry(QtCore.QRect(450, 50, 22, 22))
self.valid_check_appname.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
self.valid_check_appname.setText("")
self.valid_check_appname.setAutoRaise(True)
self.valid_check_appname.setObjectName("valid_check_appname")
self.valid_check_semester = QtWidgets.QToolButton(parent=self.app_group_box)
self.valid_check_semester.setGeometry(QtCore.QRect(520, 90, 22, 22))
self.valid_check_semester.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
self.valid_check_semester.setText("")
self.valid_check_semester.setAutoRaise(True)
self.valid_check_semester.setObjectName("valid_check_semester")
self.valid_check_mail = QtWidgets.QToolButton(parent=self.app_group_box)
self.valid_check_mail.setGeometry(QtCore.QRect(240, 140, 22, 22))
self.valid_check_mail.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
self.valid_check_mail.setText("")
self.valid_check_mail.setAutoRaise(True)
self.valid_check_mail.setObjectName("valid_check_mail")
self.valid_check_telnr = QtWidgets.QToolButton(parent=self.app_group_box)
self.valid_check_telnr.setGeometry(QtCore.QRect(240, 160, 22, 22))
self.valid_check_telnr.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
self.valid_check_telnr.setText("")
self.valid_check_telnr.setAutoRaise(True)
self.valid_check_telnr.setObjectName("valid_check_telnr")
self.saveandcreate = QtWidgets.QPushButton(parent=self.app_group_box)
self.saveandcreate.setEnabled(False)
self.saveandcreate.setGeometry(QtCore.QRect(270, 180, 161, 24))
font = QtGui.QFont()
font.setPointSize(9)
font.setBold(False)
self.saveandcreate.setFont(font)
self.saveandcreate.setObjectName("saveandcreate")
self.gridLayout_2.addWidget(self.app_group_box, 1, 0, 1, 1)
self.add_medium = QtWidgets.QPushButton(parent=self.createApparat)
self.add_medium.setGeometry(QtCore.QRect(3, 695, 121, 20))
self.add_medium.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
self.add_medium.setObjectName("add_medium")
self.tabWidget.addTab(self.createApparat, "")
self.search_statistics = QtWidgets.QWidget()
self.search_statistics.setObjectName("search_statistics")
self.tabWidget.addTab(self.search_statistics, "")
self.elsatab = QtWidgets.QWidget()
self.elsatab.setObjectName("elsatab")
self.tabWidget.addTab(self.elsatab, "")
self.admin = QtWidgets.QWidget()
self.admin.setObjectName("admin")
self.label_21 = QtWidgets.QLabel(parent=self.admin)
self.label_21.setGeometry(QtCore.QRect(10, 30, 47, 22))
self.label_21.setObjectName("label_21")
self.select_action_box = QtWidgets.QComboBox(parent=self.admin)
self.select_action_box.setGeometry(QtCore.QRect(60, 30, 181, 22))
self.select_action_box.setObjectName("select_action_box")
self.select_action_box.addItem("")
self.select_action_box.addItem("")
self.select_action_box.addItem("")
self.admin_action = QtWidgets.QGroupBox(parent=self.admin)
self.admin_action.setGeometry(QtCore.QRect(10, 70, 570, 291))
font = QtGui.QFont()
font.setBold(False)
self.admin_action.setFont(font)
self.admin_action.setFlat(True)
self.admin_action.setCheckable(False)
self.admin_action.setObjectName("admin_action")
self.tabWidget.addTab(self.admin, "")
self.gridLayout.addWidget(self.tabWidget, 0, 0, 1, 1)
self.horizontalLayout.addLayout(self.gridLayout)
self.mainLayout.addLayout(self.horizontalLayout)
self.verticalLayoutWidget_2 = QtWidgets.QWidget(parent=self.centralwidget)
self.verticalLayoutWidget_2.setGeometry(QtCore.QRect(1280, 0, 306, 751))
self.verticalLayoutWidget_2.setObjectName("verticalLayoutWidget_2")
self.verticalLayout = QtWidgets.QVBoxLayout(self.verticalLayoutWidget_2)
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
self.verticalLayout.setObjectName("verticalLayout")
self.calendar_frame = QtWidgets.QFrame(parent=self.verticalLayoutWidget_2)
self.calendar_frame.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel)
self.calendar_frame.setFrameShadow(QtWidgets.QFrame.Shadow.Raised)
self.calendar_frame.setObjectName("calendar_frame")
self.verticalLayout_7 = QtWidgets.QVBoxLayout(self.calendar_frame)
self.verticalLayout_7.setObjectName("verticalLayout_7")
self.calendarlayout = QtWidgets.QVBoxLayout()
self.calendarlayout.setObjectName("calendarlayout")
self.verticalLayout_7.addLayout(self.calendarlayout)
self.verticalLayout.addWidget(self.calendar_frame)
self.frame_creation_progress = QtWidgets.QFrame(parent=self.verticalLayoutWidget_2)
self.frame_creation_progress.setObjectName("frame_creation_progress")
self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.frame_creation_progress)
self.verticalLayout_4.setSpacing(6)
self.verticalLayout_4.setObjectName("verticalLayout_4")
self.steps = QtWidgets.QFrame(parent=self.frame_creation_progress)
self.steps.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel)
self.steps.setFrameShadow(QtWidgets.QFrame.Shadow.Raised)
self.steps.setObjectName("steps")
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.steps)
self.verticalLayout_3.setSpacing(0)
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.groupBox_2 = QtWidgets.QGroupBox(parent=self.steps)
self.groupBox_2.setEnabled(True)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.groupBox_2.sizePolicy().hasHeightForWidth())
self.groupBox_2.setSizePolicy(sizePolicy)
font = QtGui.QFont()
font.setPointSize(11)
font.setBold(True)
self.groupBox_2.setFont(font)
self.groupBox_2.setObjectName("groupBox_2")
self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.groupBox_2)
self.verticalLayout_6.setObjectName("verticalLayout_6")
self.appdata_check = QtWidgets.QCheckBox(parent=self.groupBox_2)
font = QtGui.QFont()
font.setPointSize(8)
font.setBold(False)
self.appdata_check.setFont(font)
self.appdata_check.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
self.appdata_check.setObjectName("appdata_check")
self.verticalLayout_6.addWidget(self.appdata_check)
self.media_check = QtWidgets.QCheckBox(parent=self.groupBox_2)
font = QtGui.QFont()
font.setPointSize(8)
font.setBold(False)
self.media_check.setFont(font)
self.media_check.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
self.media_check.setObjectName("media_check")
self.verticalLayout_6.addWidget(self.media_check)
self.ids_check = QtWidgets.QCheckBox(parent=self.groupBox_2)
font = QtGui.QFont()
font.setPointSize(8)
font.setBold(False)
self.ids_check.setFont(font)
self.ids_check.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
self.ids_check.setObjectName("ids_check")
self.verticalLayout_6.addWidget(self.ids_check)
self.verticalLayout_3.addWidget(self.groupBox_2)
self.groupBox = QtWidgets.QGroupBox(parent=self.steps)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.groupBox.sizePolicy().hasHeightForWidth())
self.groupBox.setSizePolicy(sizePolicy)
font = QtGui.QFont()
font.setPointSize(11)
font.setBold(True)
self.groupBox.setFont(font)
self.groupBox.setObjectName("groupBox")
self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.groupBox)
self.verticalLayout_5.setObjectName("verticalLayout_5")
self.media_checked = QtWidgets.QCheckBox(parent=self.groupBox)
font = QtGui.QFont()
font.setPointSize(8)
font.setBold(False)
font.setItalic(False)
font.setUnderline(False)
font.setKerning(True)
font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferDefault)
self.media_checked.setFont(font)
self.media_checked.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
self.media_checked.setObjectName("media_checked")
self.verticalLayout_5.addWidget(self.media_checked)
self.media_edited_check = QtWidgets.QCheckBox(parent=self.groupBox)
font = QtGui.QFont()
font.setPointSize(8)
font.setBold(False)
font.setItalic(False)
font.setUnderline(False)
font.setKerning(True)
font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferDefault)
self.media_edited_check.setFont(font)
self.media_edited_check.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
self.media_edited_check.setObjectName("media_edited_check")
self.verticalLayout_5.addWidget(self.media_edited_check)
self.app_created = QtWidgets.QCheckBox(parent=self.groupBox)
font = QtGui.QFont()
font.setPointSize(8)
font.setBold(False)
font.setItalic(False)
font.setUnderline(False)
font.setKerning(True)
font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferDefault)
self.app_created.setFont(font)
self.app_created.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
self.app_created.setObjectName("app_created")
self.verticalLayout_5.addWidget(self.app_created)
self.btn_copy_adis_command = QtWidgets.QPushButton(parent=self.groupBox)
font = QtGui.QFont()
font.setPointSize(8)
font.setBold(False)
font.setItalic(False)
font.setUnderline(False)
font.setKerning(True)
font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferDefault)
self.btn_copy_adis_command.setFont(font)
self.btn_copy_adis_command.setStatusTip("")
self.btn_copy_adis_command.setWhatsThis("")
self.btn_copy_adis_command.setAccessibleDescription("")
self.btn_copy_adis_command.setAutoFillBackground(False)
icon1 = QtGui.QIcon()
icon1.addPixmap(QtGui.QPixmap("c:\\Users\\aky547\\GitHub\\SemesterapparatsManager\\src\\ui\\../../../../../../.designer/backup/icons/information.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
self.btn_copy_adis_command.setIcon(icon1)
self.btn_copy_adis_command.setCheckable(False)
self.btn_copy_adis_command.setChecked(False)
self.btn_copy_adis_command.setAutoDefault(False)
self.btn_copy_adis_command.setObjectName("btn_copy_adis_command")
self.verticalLayout_5.addWidget(self.btn_copy_adis_command)
self.verticalLayout_3.addWidget(self.groupBox)
self.verticalLayout_4.addWidget(self.steps)
self.verticalLayout.addWidget(self.frame_creation_progress)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 1590, 22))
self.menubar.setObjectName("menubar")
self.menuDatei = QtWidgets.QMenu(parent=self.menubar)
self.menuDatei.setObjectName("menuDatei")
self.menuEinstellungen = QtWidgets.QMenu(parent=self.menubar)
self.menuEinstellungen.setObjectName("menuEinstellungen")
self.menuHelp = QtWidgets.QMenu(parent=self.menubar)
self.menuHelp.setObjectName("menuHelp")
MainWindow.setMenuBar(self.menubar)
self.statusBar = QtWidgets.QStatusBar(parent=MainWindow)
self.statusBar.setObjectName("statusBar")
MainWindow.setStatusBar(self.statusBar)
self.actionBeenden = QtGui.QAction(parent=MainWindow)
self.actionBeenden.setMenuRole(QtGui.QAction.MenuRole.QuitRole)
self.actionBeenden.setShortcutVisibleInContextMenu(True)
self.actionBeenden.setObjectName("actionBeenden")
self.actionEinstellungen = QtGui.QAction(parent=MainWindow)
self.actionEinstellungen.setShortcutVisibleInContextMenu(True)
self.actionEinstellungen.setObjectName("actionEinstellungen")
self.actionDokumentation = QtGui.QAction(parent=MainWindow)
self.actionDokumentation.setShortcutContext(QtCore.Qt.ShortcutContext.ApplicationShortcut)
self.actionDokumentation.setObjectName("actionDokumentation")
self.actionAbout = QtGui.QAction(parent=MainWindow)
self.actionAbout.setMenuRole(QtGui.QAction.MenuRole.AboutRole)
self.actionAbout.setObjectName("actionAbout")
self.actionDokumentation_lokal = QtGui.QAction(parent=MainWindow)
self.actionDokumentation_lokal.setObjectName("actionDokumentation_lokal")
self.menuDatei.addAction(self.actionBeenden)
self.menuEinstellungen.addAction(self.actionEinstellungen)
self.menuHelp.addAction(self.actionDokumentation_lokal)
self.menuHelp.addAction(self.actionAbout)
self.menubar.addAction(self.menuDatei.menuAction())
self.menubar.addAction(self.menuEinstellungen.menuAction())
self.menubar.addAction(self.menuHelp.menuAction())
self.label_9.setBuddy(self.prof_tel_nr)
self.label_3.setBuddy(self.prof_title)
self.label_2.setBuddy(self.drpdwn_app_nr)
self.label_8.setBuddy(self.prof_mail)
self.label_10.setBuddy(self.app_fach)
self.label_12.setBuddy(self.prof_id_adis)
self.label_13.setBuddy(self.apparat_id_adis)
self.label_4.setBuddy(self.drpdwn_prof_name)
self.label_5.setBuddy(self.app_name)
self.label_6.setBuddy(self.sem_year)
self.retranslateUi(MainWindow)
self.tabWidget.setCurrentIndex(0)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
MainWindow.setTabOrder(self.drpdwn_app_nr, self.drpdwn_prof_name)
MainWindow.setTabOrder(self.drpdwn_prof_name, self.prof_mail)
MainWindow.setTabOrder(self.prof_mail, self.prof_tel_nr)
MainWindow.setTabOrder(self.prof_tel_nr, self.app_name)
MainWindow.setTabOrder(self.app_name, self.app_fach)
MainWindow.setTabOrder(self.app_fach, self.sem_sommer)
MainWindow.setTabOrder(self.sem_sommer, self.sem_winter)
MainWindow.setTabOrder(self.sem_winter, self.sem_year)
MainWindow.setTabOrder(self.sem_year, self.check_eternal_app)
MainWindow.setTabOrder(self.check_eternal_app, self.btn_add_document)
MainWindow.setTabOrder(self.btn_add_document, self.btn_open_document)
MainWindow.setTabOrder(self.btn_open_document, self.check_file)
MainWindow.setTabOrder(self.check_file, self.check_send_mail)
MainWindow.setTabOrder(self.check_send_mail, self.btn_apparat_save)
MainWindow.setTabOrder(self.btn_apparat_save, self.btn_apparat_apply)
MainWindow.setTabOrder(self.btn_apparat_apply, self.chkbx_show_del_media)
MainWindow.setTabOrder(self.chkbx_show_del_media, self.btn_reserve)
MainWindow.setTabOrder(self.btn_reserve, self.select_action_box)
MainWindow.setTabOrder(self.select_action_box, self.prof_id_adis)
MainWindow.setTabOrder(self.prof_id_adis, self.apparat_id_adis)
MainWindow.setTabOrder(self.apparat_id_adis, self.automation_add_selected_books)
MainWindow.setTabOrder(self.automation_add_selected_books, self.saveandcreate)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "Semesterapparatsmanagement"))
self.create_document.setToolTip(_translate("MainWindow", "Erstellt die Übersicht, welche am Regal ausgehängt werden kann"))
self.create_document.setText(_translate("MainWindow", "Übersicht erstellen"))
self.create_new_app.setText(_translate("MainWindow", "neu. App anlegen"))
self.cancel_active_selection.setText(_translate("MainWindow", "Auswahl abbrechen"))
self.tableWidget_apparate.setSortingEnabled(False)
item = self.tableWidget_apparate.horizontalHeaderItem(0)
item.setText(_translate("MainWindow", "AppNr"))
item = self.tableWidget_apparate.horizontalHeaderItem(1)
item.setText(_translate("MainWindow", "App Name"))
item = self.tableWidget_apparate.horizontalHeaderItem(2)
item.setText(_translate("MainWindow", "Professor"))
item = self.tableWidget_apparate.horizontalHeaderItem(3)
item.setText(_translate("MainWindow", "gültig bis"))
item = self.tableWidget_apparate.horizontalHeaderItem(4)
item.setText(_translate("MainWindow", "Dauerapparat"))
item = self.tableWidget_apparate.horizontalHeaderItem(5)
item.setText(_translate("MainWindow", "KontoNr"))
self.chkbx_show_del_media.setText(_translate("MainWindow", "gel. Medien anzeigen"))
self.btn_reserve.setText(_translate("MainWindow", "im Apparat?"))
self.label_info.setText(_translate("MainWindow", "Medien werden hinzugefügt"))
self.progress_label.setText(_translate("MainWindow", "Medium x/y"))
self.label_20.setText(_translate("MainWindow", "Medien werden geprüft"))
self.avail_status.setText(_translate("MainWindow", "TextLabel"))
self.automation_add_selected_books.setText(_translate("MainWindow", "Ausgewählte als verfügbar markieren"))
self.tableWidget_apparat_media.setSortingEnabled(True)
item = self.tableWidget_apparat_media.horizontalHeaderItem(0)
item.setText(_translate("MainWindow", "Buchtitel"))
item.setToolTip(_translate("MainWindow", "Es kann sein, dass der Buchtitel leer ist, dies kommt vor, wenn der Titel nicht passend formatiert ist"))
item = self.tableWidget_apparat_media.horizontalHeaderItem(1)
item.setText(_translate("MainWindow", "Signatur"))
item = self.tableWidget_apparat_media.horizontalHeaderItem(2)
item.setText(_translate("MainWindow", "Auflage"))
item = self.tableWidget_apparat_media.horizontalHeaderItem(3)
item.setText(_translate("MainWindow", "Autor"))
item = self.tableWidget_apparat_media.horizontalHeaderItem(4)
item.setText(_translate("MainWindow", "im Apparat?"))
item.setToolTip(_translate("MainWindow", "Diese Angabe ist nicht zuverlässig. Ist das ❌ vorhanden, kann das Medium im Apparat sein, aber aufgrund eines Bugs nicht gefunden worden"))
item = self.tableWidget_apparat_media.horizontalHeaderItem(5)
item.setText(_translate("MainWindow", "Vorgemerkt"))
item = self.tableWidget_apparat_media.horizontalHeaderItem(6)
item.setText(_translate("MainWindow", "Link"))
self.label.setText(_translate("MainWindow", " Medienliste"))
self.app_group_box.setTitle(_translate("MainWindow", "Apparatsdetails"))
item = self.document_list.horizontalHeaderItem(0)
item.setText(_translate("MainWindow", "Dokumentname"))
item = self.document_list.horizontalHeaderItem(1)
item.setText(_translate("MainWindow", "Dateityp"))
item = self.document_list.horizontalHeaderItem(2)
item.setText(_translate("MainWindow", "Neu?"))
item = self.document_list.horizontalHeaderItem(3)
item.setText(_translate("MainWindow", "path"))
self.check_file.setToolTip(_translate("MainWindow", "Abhängig von der Anzahl der Medien kann die Suche sehr lange dauern"))
self.check_file.setText(_translate("MainWindow", "Medien aus Dokument\n"
" hinzufügen"))
self.btn_open_document.setText(_translate("MainWindow", "Dokument öffnen"))
self.btn_add_document.setText(_translate("MainWindow", "Dokument hinzufügen"))
self.appname_mand.setText(_translate("MainWindow", "*"))
self.profname_mand.setText(_translate("MainWindow", "*"))
self.fach_mand.setText(_translate("MainWindow", "*"))
self.btn_apparat_apply.setText(_translate("MainWindow", "Aktualisieren"))
self.label_9.setText(_translate("MainWindow", "Tel"))
self._mand.setText(_translate("MainWindow", "*"))
self.check_eternal_app.setText(_translate("MainWindow", "Dauerapparat"))
self.sem_sommer.setText(_translate("MainWindow", "Sommer"))
self.drpdwn_prof_name.setToolTip(_translate("MainWindow", "Nachname, Vorname"))
self.mail_mand.setText(_translate("MainWindow", "*"))
self.label_3.setStatusTip(_translate("MainWindow", "sdvosdvsdv"))
self.label_3.setText(_translate("MainWindow", "Prof. Titel"))
self.label_2.setText(_translate("MainWindow", "Apparatsnummer"))
self.label_8.setText(_translate("MainWindow", "Mail"))
self.label_10.setText(_translate("MainWindow", "Fach"))
self.label_12.setText(_translate("MainWindow", "Prof-ID-aDIS"))
self.label_13.setText(_translate("MainWindow", "Apparat-ID-aDIS"))
self.sem_year.setPlaceholderText(_translate("MainWindow", "2023"))
self.check_send_mail.setText(_translate("MainWindow", "Mail senden"))
self.sem_winter.setText(_translate("MainWindow", "Winter"))
self.label_4.setText(_translate("MainWindow", "Prof. Name"))
self.telnr_mand.setText(_translate("MainWindow", "*"))
self.btn_apparat_save.setText(_translate("MainWindow", "Speichern"))
self.label_5.setText(_translate("MainWindow", "Apparatsname"))
self.label_6.setText(_translate("MainWindow", "Semester"))
self.valid_check_profname.setStatusTip(_translate("MainWindow", "Format: Nachname, Vorname"))
self.valid_check_mail.setStatusTip(_translate("MainWindow", "mail@irgendwas.wasanderes"))
self.saveandcreate.setText(_translate("MainWindow", "Speichern und anlegen"))
self.add_medium.setText(_translate("MainWindow", "Medien hinzufügen"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.createApparat), _translate("MainWindow", "Anlegen"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.search_statistics), _translate("MainWindow", "Suchen / Statistik"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.elsatab), _translate("MainWindow", "ELSA"))
self.label_21.setText(_translate("MainWindow", "Aktion:"))
self.select_action_box.setItemText(0, _translate("MainWindow", "Nutzer anlegen"))
self.select_action_box.setItemText(1, _translate("MainWindow", "Nutzer bearbeiten"))
self.select_action_box.setItemText(2, _translate("MainWindow", "Lehrperson bearbeiten"))
self.admin_action.setTitle(_translate("MainWindow", "GroupBox"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.admin), _translate("MainWindow", "Admin"))
self.groupBox_2.setTitle(_translate("MainWindow", "Software"))
self.appdata_check.setText(_translate("MainWindow", "Apparatsdaten eingegeben"))
self.media_check.setText(_translate("MainWindow", "Medien hinzugefügt / importiert"))
self.ids_check.setText(_translate("MainWindow", "Prof-ID und Apparat-ID eingetragen"))
self.groupBox.setTitle(_translate("MainWindow", "aDIS"))
self.media_checked.setText(_translate("MainWindow", "Medien geprüft"))
self.media_edited_check.setText(_translate("MainWindow", "Medien bearbeitet"))
self.app_created.setText(_translate("MainWindow", "Apparat angelegt"))
self.btn_copy_adis_command.setToolTip(_translate("MainWindow", "Hier klicken, um die aDIS Abfrage in die Zwischenablage zu kopieren"))
self.btn_copy_adis_command.setText(_translate("MainWindow", " aDIS Abfrage in Zwischenablage kopieren"))
self.menuDatei.setTitle(_translate("MainWindow", "Datei"))
self.menuEinstellungen.setTitle(_translate("MainWindow", "Bearbeiten"))
self.menuHelp.setTitle(_translate("MainWindow", "Help"))
self.actionBeenden.setText(_translate("MainWindow", "Beenden"))
self.actionBeenden.setShortcut(_translate("MainWindow", "Ctrl+Q"))
self.actionEinstellungen.setText(_translate("MainWindow", "Einstellungen"))
self.actionEinstellungen.setShortcut(_translate("MainWindow", "Alt+S"))
self.actionDokumentation.setText(_translate("MainWindow", "Dokumentation (online)"))
self.actionDokumentation.setShortcut(_translate("MainWindow", "F1"))
self.actionAbout.setText(_translate("MainWindow", "About"))
self.actionDokumentation_lokal.setText(_translate("MainWindow", "Dokumentation (lokal)"))
self.actionDokumentation_lokal.setShortcut(_translate("MainWindow", "F1"))

View File

@@ -1,52 +0,0 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\switchtest.ui'
#
# Created by: PyQt6 UI code generator 6.6.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt6 import QtCore, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.setWindowModality(QtCore.Qt.WindowModality.WindowModal)
MainWindow.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
self.verticalLayout.setObjectName("verticalLayout")
self.select_action_box = QtWidgets.QComboBox(parent=self.centralwidget)
self.select_action_box.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
self.select_action_box.setObjectName("select_action_box")
self.select_action_box.addItem("")
self.select_action_box.addItem("")
self.select_action_box.addItem("")
self.select_action_box.addItem("")
self.verticalLayout.addWidget(self.select_action_box)
self.localwidget = QtWidgets.QWidget(parent=self.centralwidget)
self.localwidget.setObjectName("localwidget")
self.verticalLayout.addWidget(self.localwidget)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 22))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(parent=MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.select_action_box.setItemText(
0, _translate("MainWindow", "Aktion auswählen")
)
self.select_action_box.setItemText(1, _translate("MainWindow", "edit_prof"))
self.select_action_box.setItemText(2, _translate("MainWindow", "add_user"))
self.select_action_box.setItemText(3, _translate("MainWindow", "edit_user"))

View File

@@ -1,6 +1,6 @@
import pathlib
from .Ui_semesterapparat_ui import Ui_MainWindow as Ui_Semesterapparat
from .semesterapparat_ui_ui import Ui_MainWindow as Ui_Semesterapparat
# from .dialogs import (
# ApparatExtendDialog,

View File

@@ -0,0 +1,157 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\Semesterapparate\ui\dialogs\edit_bookdata.ui'
#
# Created by: PySide6 UI code generator 6.3.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PySide6 import QtCore, QtGui, QtWidgets
from src.logic.dataclass import BookData
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Metadaten")
Dialog.resize(448, 572)
self.buttonBox = QtWidgets.QDialogButtonBox(Dialog)
self.buttonBox.setGeometry(QtCore.QRect(260, 530, 161, 32))
self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal)
self.buttonBox.setStandardButtons(
QtWidgets.QDialogButtonBox.StandardButton.Cancel
| QtWidgets.QDialogButtonBox.StandardButton.Ok
)
self.buttonBox.setObjectName("buttonBox")
self.gridLayoutWidget = QtWidgets.QWidget(Dialog)
self.gridLayoutWidget.setGeometry(QtCore.QRect(0, 0, 441, 531))
self.gridLayoutWidget.setObjectName("gridLayoutWidget")
self.gridLayout = QtWidgets.QGridLayout(self.gridLayoutWidget)
self.gridLayout.setSizeConstraint(
QtWidgets.QLayout.SizeConstraint.SetDefaultConstraint
)
self.gridLayout.setContentsMargins(0, 0, 0, 0)
self.gridLayout.setObjectName("gridLayout")
self.label_10 = QtWidgets.QLabel(self.gridLayoutWidget)
self.label_10.setObjectName("label_10")
self.gridLayout.addWidget(self.label_10, 10, 1, 1, 1)
self.label = QtWidgets.QLabel(self.gridLayoutWidget)
self.label.setObjectName("label")
self.gridLayout.addWidget(self.label, 0, 1, 1, 1)
self.label_9 = QtWidgets.QLabel(self.gridLayoutWidget)
self.label_9.setObjectName("label_9")
self.gridLayout.addWidget(self.label_9, 9, 1, 1, 1)
self.label_8 = QtWidgets.QLabel(self.gridLayoutWidget)
self.label_8.setObjectName("label_8")
self.gridLayout.addWidget(self.label_8, 8, 1, 1, 1)
self.label_12 = QtWidgets.QLabel(self.gridLayoutWidget)
self.label_12.setObjectName("label_12")
self.gridLayout.addWidget(self.label_12, 6, 1, 1, 1)
self.line_edition = QtWidgets.QLineEdit(self.gridLayoutWidget)
self.line_edition.setObjectName("line_edition")
self.gridLayout.addWidget(self.line_edition, 2, 2, 1, 1)
self.label_3 = QtWidgets.QLabel(self.gridLayoutWidget)
self.label_3.setObjectName("label_3")
self.gridLayout.addWidget(self.label_3, 2, 1, 1, 1)
self.label_4 = QtWidgets.QLabel(self.gridLayoutWidget)
self.label_4.setObjectName("label_4")
self.gridLayout.addWidget(self.label_4, 3, 1, 1, 1)
self.line_link = QtWidgets.QLineEdit(self.gridLayoutWidget)
self.line_link.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.ArrowCursor))
self.line_link.setReadOnly(True)
self.line_link.setObjectName("line_link")
self.gridLayout.addWidget(self.line_link, 6, 2, 1, 1)
self.label_5 = QtWidgets.QLabel(self.gridLayoutWidget)
self.label_5.setObjectName("label_5")
self.gridLayout.addWidget(self.label_5, 4, 1, 1, 1)
self.label_7 = QtWidgets.QLabel(self.gridLayoutWidget)
self.label_7.setObjectName("label_7")
self.gridLayout.addWidget(self.label_7, 7, 1, 1, 1)
self.label_6 = QtWidgets.QLabel(self.gridLayoutWidget)
self.label_6.setObjectName("label_6")
self.gridLayout.addWidget(self.label_6, 5, 1, 1, 1)
self.label_2 = QtWidgets.QLabel(self.gridLayoutWidget)
self.label_2.setObjectName("label_2")
self.gridLayout.addWidget(self.label_2, 1, 1, 1, 1)
spacerItem = QtWidgets.QSpacerItem(
5,
20,
QtWidgets.QSizePolicy.Policy.Fixed,
QtWidgets.QSizePolicy.Policy.Minimum,
)
self.gridLayout.addItem(spacerItem, 8, 0, 1, 1)
self.line_title = QtWidgets.QLineEdit(self.gridLayoutWidget)
self.line_title.setObjectName("line_title")
self.gridLayout.addWidget(self.line_title, 0, 2, 1, 1)
self.line_signature = QtWidgets.QLineEdit(self.gridLayoutWidget)
self.line_signature.setObjectName("line_signature")
self.gridLayout.addWidget(self.line_signature, 1, 2, 1, 1)
self.line_author = QtWidgets.QLineEdit(self.gridLayoutWidget)
self.line_author.setObjectName("line_author")
self.gridLayout.addWidget(self.line_author, 3, 2, 1, 1)
self.line_lang = QtWidgets.QLineEdit(self.gridLayoutWidget)
self.line_lang.setObjectName("line_lang")
self.gridLayout.addWidget(self.line_lang, 8, 2, 1, 1)
self.line_ppn = QtWidgets.QLineEdit(self.gridLayoutWidget)
self.line_ppn.setObjectName("line_ppn")
self.gridLayout.addWidget(self.line_ppn, 5, 2, 1, 1)
self.line_isbn = QtWidgets.QLineEdit(self.gridLayoutWidget)
self.line_isbn.setObjectName("line_isbn")
self.gridLayout.addWidget(self.line_isbn, 7, 2, 1, 1)
self.line_year = QtWidgets.QLineEdit(self.gridLayoutWidget)
self.line_year.setObjectName("line_year")
self.gridLayout.addWidget(self.line_year, 9, 2, 1, 1)
self.line_pages = QtWidgets.QLineEdit(self.gridLayoutWidget)
self.line_pages.setObjectName("line_pages")
self.gridLayout.addWidget(self.line_pages, 10, 2, 1, 1)
self.line_publisher = QtWidgets.QLineEdit(self.gridLayoutWidget)
self.line_publisher.setObjectName("line_publisher")
self.gridLayout.addWidget(self.line_publisher, 4, 2, 1, 1)
self.retranslateUi(Dialog)
self.buttonBox.accepted.connect(Dialog.accept) # type: ignore
self.buttonBox.rejected.connect(Dialog.reject) # type: ignore
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
self.label_10.setText(_translate("Dialog", "Seiten"))
self.label.setText(_translate("Dialog", "Titel"))
self.label_9.setText(_translate("Dialog", "Jahr"))
self.label_8.setText(_translate("Dialog", "Sprache"))
self.label_12.setText(_translate("Dialog", "Link"))
self.label_3.setText(_translate("Dialog", "Auflage"))
self.label_4.setText(_translate("Dialog", "Autor"))
self.label_5.setText(_translate("Dialog", "Herausgeber"))
self.label_7.setText(_translate("Dialog", "ISBN(s)"))
self.label_6.setText(_translate("Dialog", "PPN"))
self.label_2.setText(_translate("Dialog", "Signatur"))
def populate_fields(self, data: BookData):
self.line_author.setText(data.author)
self.line_edition.setText(data.edition)
self.line_isbn.setText(", ".join(data.isbn))
self.line_lang.setText(data.language)
self.line_link.setText(data.link)
self.line_pages.setText(data.pages)
self.line_ppn.setText(data.ppn)
self.line_publisher.setText(data.publisher)
self.line_signature.setText(data.signature)
self.line_title.setText(data.title)
self.line_year.setText(data.year)
def get_data(self) -> BookData:
return BookData(
ppn=self.line_ppn.text().strip(),
title=self.line_title.text().strip(),
signature=self.line_signature.text().strip(),
edition=self.line_edition.text().strip(),
link=self.line_link.text().strip(),
isbn=self.line_isbn.text().split(","),
author=self.line_author.text().strip(),
language=self.line_lang.text().strip(),
publisher=self.line_publisher.text().strip(),
year=self.line_year.text().strip(),
pages=self.line_pages.text().strip(),
)

View File

@@ -0,0 +1,109 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\Semesterapparate\ui\dialogs\fileparser.ui'
#
# Created by: PySide6 UI code generator 6.3.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PySide6 import QtCore, QtGui, QtWidgets
from src.logic.webrequest import BibTextTransformer, WebRequest
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(402, 301)
self.progressBar = QtWidgets.QProgressBar(Dialog)
self.progressBar.setGeometry(QtCore.QRect(10, 60, 381, 23))
self.progressBar.setProperty("value", 24)
self.progressBar.setObjectName("progressBar")
self.frame = QtWidgets.QFrame(Dialog)
self.frame.setGeometry(QtCore.QRect(10, 10, 381, 41))
self.frame.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel)
self.frame.setFrameShadow(QtWidgets.QFrame.Shadow.Raised)
self.frame.setObjectName("frame")
self.horizontalLayoutWidget = QtWidgets.QWidget(self.frame)
self.horizontalLayoutWidget.setGeometry(QtCore.QRect(0, 0, 381, 41))
self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget)
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout.setObjectName("horizontalLayout")
self.label = QtWidgets.QLabel(self.horizontalLayoutWidget)
self.label.setObjectName("label")
self.horizontalLayout.addWidget(self.label)
self.count = QtWidgets.QLabel(self.horizontalLayoutWidget)
font = QtGui.QFont()
font.setBold(True)
font.setWeight(75)
self.count.setFont(font)
self.count.setTextFormat(QtCore.Qt.TextFormat.PlainText)
self.count.setObjectName("count")
self.horizontalLayout.addWidget(self.count)
self.label_2 = QtWidgets.QLabel(self.horizontalLayoutWidget)
self.label_2.setObjectName("label_2")
self.horizontalLayout.addWidget(self.label_2)
spacerItem = QtWidgets.QSpacerItem(
40,
20,
QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Minimum,
)
self.horizontalLayout.addItem(spacerItem)
self.frame_2 = QtWidgets.QFrame(Dialog)
self.frame_2.setGeometry(QtCore.QRect(10, 100, 381, 201))
self.frame_2.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel)
self.frame_2.setFrameShadow(QtWidgets.QFrame.Shadow.Raised)
self.frame_2.setObjectName("frame_2")
self.listWidget = QtWidgets.QListWidget(self.frame_2)
self.listWidget.setGeometry(QtCore.QRect(0, 0, 381, 191))
self.listWidget.setObjectName("listWidget")
self.signatures = []
self.returned = []
# self.data_gathering_complete = QtCore.Signal()
self.retranslateUi(Dialog)
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
self.label.setText(_translate("Dialog", "Es wurden"))
self.count.setText(_translate("Dialog", "0"))
self.label_2.setText(_translate("Dialog", "Signaturen gefunden."))
def moveToThread(self, thread):
self.progressBar.moveToThread(thread)
self.frame.moveToThread(thread)
self.horizontalLayoutWidget.moveToThread(thread)
self.horizontalLayout.moveToThread(thread)
self.label.moveToThread(thread)
self.count.moveToThread(thread)
self.label_2.moveToThread(thread)
self.frame_2.moveToThread(thread)
self.listWidget.moveToThread(thread)
def run(self):
for signature in self.signatures:
self.count.setText(str(self.signatures.index(signature) + 1))
self.listWidget.addItem(signature)
webdata = WebRequest().get_ppn(signature).get_data()
bookdata = BibTextTransformer("ARRAY").get_data(webdata).return_data()
self.returned.append(bookdata)
self.progressBar.setValue(self.signatures.index(signature) + 1)
# self.data_gathering_complete.emit()
def deleteLater(self):
self.progressBar.deleteLater()
self.frame.deleteLater()
self.horizontalLayoutWidget.deleteLater()
self.horizontalLayout.deleteLater()
self.label.deleteLater()
self.count.deleteLater()
self.label_2.deleteLater()
self.frame_2.deleteLater()
self.listWidget.deleteLater()
self.signatures = []
self.returned = []
self.retranslateUi.deleteLater()
super().deleteLater()

111
src/ui/dialogs/Ui_login.py Normal file
View File

@@ -0,0 +1,111 @@
# Form implementation generated from reading ui file '/home/alexander/GitHub/Semesterapparate/ui/dialogs/login.ui'
#
# Created by: PySide6 UI code generator 6.5.3
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
import hashlib
from PySide6 import QtCore, QtWidgets
from src.backend.admin_console import AdminCommands
from src.backend.database import Database
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(218, 190)
self.dialog = Dialog
self.login_button = QtWidgets.QPushButton(parent=Dialog)
self.login_button.setGeometry(QtCore.QRect(30, 140, 76, 32))
self.login_button.setObjectName("login_button")
self.login_button.setText("Login")
self.login_button.setFocusPolicy(QtCore.Qt.FocusPolicy.ClickFocus)
self.cancel_button = QtWidgets.QPushButton(parent=Dialog)
self.cancel_button.setGeometry(QtCore.QRect(120, 140, 76, 32))
self.cancel_button.setObjectName("cancel_button")
self.cancel_button.setText("Cancel")
self.cancel_button.setFocusPolicy(QtCore.Qt.FocusPolicy.ClickFocus)
self.cancel_button.clicked.connect(self.cancel_buttonfn)
self.label = QtWidgets.QLabel(parent=Dialog)
self.label.setGeometry(QtCore.QRect(20, 40, 71, 21))
self.label.setObjectName("label")
self.lineEdit = QtWidgets.QLineEdit(parent=Dialog)
self.lineEdit.setGeometry(QtCore.QRect(80, 40, 113, 21))
self.lineEdit.setObjectName("lineEdit")
# set strong focus to lineEdit
self.lineEdit.setFocusPolicy(QtCore.Qt.FocusPolicy.StrongFocus)
self.label_2 = QtWidgets.QLabel(parent=Dialog)
self.label_2.setGeometry(QtCore.QRect(20, 80, 71, 21))
self.label_2.setObjectName("label_2")
self.lineEdit_2 = QtWidgets.QLineEdit(parent=Dialog)
self.lineEdit_2.setGeometry(QtCore.QRect(80, 80, 113, 21))
self.lineEdit_2.setInputMethodHints(QtCore.Qt.InputMethodHint.ImhSensitiveData)
# set echo mode to password
self.lineEdit_2.setEchoMode(QtWidgets.QLineEdit.EchoMode.Password)
self.lineEdit_2.setClearButtonEnabled(True)
self.lineEdit_2.setObjectName("lineEdit_2")
self.retranslateUi(Dialog)
# if buttonbox accepted is clicked, launch login test
self.login_button.clicked.connect(self.login)
self.lresult = -1
self.lusername = ""
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
self.label.setText(_translate("Dialog", "Username"))
self.label_2.setText(_translate("Dialog", "Password"))
def login(self):
username = self.lineEdit.text()
password = self.lineEdit_2.text()
# print(type(username), password)
# Assuming 'Database' is a class to interact with your database
db = Database()
hashed_password = hashlib.sha256(password.encode()).hexdigest()
if len(db.getUsers()) == 0:
AdminCommands().create_admin()
self.lresult = 1 # Indicate successful login
self.lusername = username
self.dialog.accept()
if db.login(username, hashed_password):
self.lresult = 1 # Indicate successful login
self.lusername = username
self.dialog.accept()
else:
# Credentials are invalid, display a warning
if username == "" or password == "":
warning_dialog = QtWidgets.QMessageBox()
warning_dialog.setIcon(QtWidgets.QMessageBox.Icon.Warning)
warning_dialog.setText("Please enter a username and password.")
warning_dialog.setWindowTitle("Login Failed")
warning_dialog.exec()
else:
warning_dialog = QtWidgets.QMessageBox()
warning_dialog.setIcon(QtWidgets.QMessageBox.Icon.Warning)
warning_dialog.setText(
"Invalid username or password. Please try again."
)
warning_dialog.setWindowTitle("Login Failed")
warning_dialog.exec()
def cancel_buttonfn(self):
self.dialog.reject()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
Dialog = QtWidgets.QDialog()
ui = Ui_Dialog()
ui.setupUi(Dialog)
Dialog.show()
sys.exit(app.exec())

View File

@@ -0,0 +1,201 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\mail_preview.ui'
#
# Created by: PySide6 UI code generator 6.3.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
import os
import re
import subprocess
import tempfile
from omegaconf import OmegaConf
from PySide6 import QtCore, QtWidgets
config = OmegaConf.load("config.yaml")
class Ui_eMailPreview(object):
def setupUi(
self,
eMailPreview,
app_id="",
app_name="",
app_subject="",
prof_name="",
data=None,
):
eMailPreview.setObjectName("eMailPreview")
eMailPreview.resize(676, 676)
self.buttonBox = QtWidgets.QDialogButtonBox(eMailPreview)
self.buttonBox.setGeometry(QtCore.QRect(310, 630, 341, 32))
self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal)
self.buttonBox.setStandardButtons(
QtWidgets.QDialogButtonBox.StandardButton.Cancel
| QtWidgets.QDialogButtonBox.StandardButton.Ok
)
self.buttonBox.setObjectName("buttonBox")
self.gridLayoutWidget = QtWidgets.QWidget(eMailPreview)
self.gridLayoutWidget.setGeometry(QtCore.QRect(10, 10, 661, 621))
self.gridLayoutWidget.setObjectName("gridLayoutWidget")
self.gridLayout = QtWidgets.QGridLayout(self.gridLayoutWidget)
self.gridLayout.setContentsMargins(0, 0, 0, 0)
self.gridLayout.setObjectName("gridLayout")
self.label_5 = QtWidgets.QLabel(self.gridLayoutWidget)
self.label_5.setObjectName("label_5")
self.gridLayout.addWidget(self.label_5, 0, 0, 1, 1)
self.prof_name = QtWidgets.QLineEdit(self.gridLayoutWidget)
self.prof_name.setObjectName("prof_name")
self.gridLayout.addWidget(self.prof_name, 2, 2, 1, 1)
self.label_3 = QtWidgets.QLabel(self.gridLayoutWidget)
self.label_3.setAlignment(
QtCore.Qt.AlignmentFlag.AlignLeading
| QtCore.Qt.AlignmentFlag.AlignLeft
| QtCore.Qt.AlignmentFlag.AlignTop
)
self.label_3.setObjectName("label_3")
self.gridLayout.addWidget(self.label_3, 5, 0, 1, 1)
self.mail_name = QtWidgets.QLineEdit(self.gridLayoutWidget)
self.mail_name.setObjectName("mail_name")
self.gridLayout.addWidget(self.mail_name, 1, 2, 1, 1)
self.label_2 = QtWidgets.QLabel(self.gridLayoutWidget)
self.label_2.setObjectName("label_2")
self.gridLayout.addWidget(self.label_2, 2, 0, 1, 1)
self.label_4 = QtWidgets.QLabel(self.gridLayoutWidget)
self.label_4.setObjectName("label_4")
self.gridLayout.addWidget(self.label_4, 3, 0, 1, 1)
self.mail_header = QtWidgets.QLineEdit(self.gridLayoutWidget)
self.mail_header.setObjectName("mail_header")
self.gridLayout.addWidget(self.mail_header, 3, 2, 1, 1)
self.label = QtWidgets.QLabel(self.gridLayoutWidget)
self.label.setObjectName("label")
self.gridLayout.addWidget(self.label, 1, 0, 1, 1)
self.comboBox = QtWidgets.QComboBox(self.gridLayoutWidget)
self.comboBox.setObjectName("comboBox")
self.gridLayout.addWidget(self.comboBox, 0, 2, 1, 1)
self.mail_body = QtWidgets.QTextEdit(self.gridLayoutWidget)
self.mail_body.setObjectName("mail_body")
self.gridLayout.addWidget(self.mail_body, 5, 2, 1, 1)
self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
self.gender_male = QtWidgets.QRadioButton(self.gridLayoutWidget)
self.gender_male.setObjectName("gender_male")
self.horizontalLayout_3.addWidget(self.gender_male)
self.gender_female = QtWidgets.QRadioButton(self.gridLayoutWidget)
self.gender_female.setObjectName("gender_female")
self.horizontalLayout_3.addWidget(self.gender_female)
self.gender_non = QtWidgets.QRadioButton(self.gridLayoutWidget)
self.gender_non.setObjectName("gender_non")
self.horizontalLayout_3.addWidget(self.gender_non)
spacerItem = QtWidgets.QSpacerItem(
40,
20,
QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Minimum,
)
self.horizontalLayout_3.addItem(spacerItem)
self.gridLayout.addLayout(self.horizontalLayout_3, 4, 2, 1, 1)
self.label_6 = QtWidgets.QLabel(self.gridLayoutWidget)
self.label_6.setObjectName("label_6")
self.gridLayout.addWidget(self.label_6, 4, 0, 1, 1)
self.retranslateUi(eMailPreview)
self.buttonBox.accepted.connect(eMailPreview.accept) # type: ignore
self.buttonBox.rejected.connect(eMailPreview.reject) # type: ignore
QtCore.QMetaObject.connectSlotsByName(eMailPreview)
self._appid = app_id
self._appname = app_name
self._subject = app_subject
self.prof_name.setText(prof_name)
self._mail_data = ""
self._data = data
self.load_mail_templates()
self.comboBox.addItem("")
self.comboBox.setCurrentText("")
self.buttonBox.accepted.connect(self.save_mail)
self.comboBox.currentIndexChanged.connect(self.set_mail)
self.gender_female.clicked.connect(self.set_mail)
self.gender_male.clicked.connect(self.set_mail)
self.gender_non.clicked.connect(self.set_mail)
def retranslateUi(self, eMailPreview):
_translate = QtCore.QCoreApplication.translate
eMailPreview.setWindowTitle(_translate("eMailPreview", "Dialog"))
self.label_5.setText(_translate("eMailPreview", "Art"))
self.label_3.setText(_translate("eMailPreview", "Mail"))
self.label_2.setText(_translate("eMailPreview", "Prof"))
self.label_4.setText(_translate("eMailPreview", "Betreff"))
self.label.setText(_translate("eMailPreview", "eMail"))
self.gender_male.setText(_translate("eMailPreview", "M"))
self.gender_female.setText(_translate("eMailPreview", "W"))
self.gender_non.setText(_translate("eMailPreview", "Divers"))
self.label_6.setText(_translate("eMailPreview", "Geschlecht"))
def get_greeting(self):
if self.gender_male.isChecked():
return "Sehr geehrter Herr"
elif self.gender_female.isChecked():
return "Sehr geehrte Frau"
elif self.gender_non.isChecked():
return "Guten Tag"
def set_mail(self):
email_template = self.comboBox.currentText()
if email_template == "":
return
with open(f"mail_vorlagen/{email_template}", "r", encoding="utf-8") as f:
mail_template = f.read()
header = re.findall(r"Subject: (.*)", mail_template)
if header:
email_header = header[0]
else:
email_header = email_template.split(".eml")[0]
self.mail_header.setText(email_header)
self.mail_data = mail_template.split("<html>")[0]
mail_html = mail_template.split("<html>")[1]
mail_html = "<html>" + mail_html
mail_html = mail_html.format(
Profname=self.prof_name.text().split(" ")[1],
Appname=self._appname,
AppNr=self._appid,
AppSubject=self._subject,
greeting=self.get_greeting(),
)
self.mail_body.setHtml(mail_html)
def load_mail_templates(self):
mail_templates = os.listdir("mail_vorlagen")
mail_templates = [f for f in mail_templates if f.endswith(".eml")]
# print(mail_templates)
self.comboBox.addItems(mail_templates)
def save_mail(self):
# create a temporary file
mail_header = self.mail_header.text()
mail_body = self.mail_body.toHtml()
mail = self.mail_data + mail_body
mail = mail.replace("Subject:", f"Subject: {mail_header}")
directory = config["database"]["tempdir"]
directory = directory.replace("~", str(os.path.expanduser("~")))
with tempfile.NamedTemporaryFile(
mode="w", delete=False, suffix=".eml", encoding="utf-8", dir=directory
) as f:
f.write(mail)
self.mail_path = f.name
# print(self.mail_path)
# open the file using thunderbird
subprocess.Popen([f"{self.mail_path}"])
# delete the file
# os.remove(self.mail_path)
def launch():
app = QtWidgets.QApplication([])
eMailPreview = QtWidgets.QDialog()
ui = Ui_eMailPreview()
ui.setupUi(eMailPreview, "1", "Test", "Biologie", "Kirchner, Alexander")
eMailPreview.show()
app.exec()

View File

@@ -0,0 +1,240 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\Semesterapparate\ui\dialogs\medianadder.ui'
#
# Created by: PySide6 UI code generator 6.3.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(637, 491)
self.label = QtWidgets.QLabel(Dialog)
self.label.setGeometry(QtCore.QRect(20, 10, 47, 21))
self.label.setObjectName("label")
self.label_2 = QtWidgets.QLabel(Dialog)
self.label_2.setGeometry(QtCore.QRect(20, 40, 47, 21))
self.label_2.setObjectName("label_2")
self.comboBox = QtWidgets.QComboBox(Dialog)
self.comboBox.setGeometry(QtCore.QRect(70, 40, 69, 22))
self.comboBox.setObjectName("comboBox")
self.comboBox.addItem("")
self.comboBox.addItem("")
self.comboBox.addItem("")
self.comboBox.addItem("")
self.lineEdit = QtWidgets.QLineEdit(Dialog)
self.lineEdit.setGeometry(QtCore.QRect(70, 10, 113, 20))
self.lineEdit.setObjectName("lineEdit")
self.label_3 = QtWidgets.QLabel(Dialog)
self.label_3.setGeometry(QtCore.QRect(20, 70, 47, 21))
self.label_3.setObjectName("label_3")
self.widget = QtWidgets.QWidget(Dialog)
self.widget.setGeometry(QtCore.QRect(330, 90, 301, 341))
self.widget.setObjectName("widget")
self.treeWidget = QtWidgets.QTreeWidget(self.widget)
self.treeWidget.setEnabled(True)
self.treeWidget.setGeometry(QtCore.QRect(0, 0, 301, 341))
self.treeWidget.setAutoFillBackground(False)
self.treeWidget.setLineWidth(0)
self.treeWidget.setMidLineWidth(0)
self.treeWidget.setVerticalScrollBarPolicy(
QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff
)
self.treeWidget.setHorizontalScrollBarPolicy(
QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff
)
self.treeWidget.setSizeAdjustPolicy(
QtWidgets.QAbstractScrollArea.SizeAdjustPolicy.AdjustToContents
)
self.treeWidget.setEditTriggers(
QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers
)
self.treeWidget.setAlternatingRowColors(True)
self.treeWidget.setSelectionMode(
QtWidgets.QAbstractItemView.SelectionMode.NoSelection
)
self.treeWidget.setTextElideMode(QtCore.Qt.TextElideMode.ElideMiddle)
self.treeWidget.setUniformRowHeights(True)
self.treeWidget.setItemsExpandable(False)
self.treeWidget.setExpandsOnDoubleClick(False)
self.treeWidget.setObjectName("treeWidget")
font = QtGui.QFont()
font.setPointSize(7)
self.treeWidget.headerItem().setFont(0, font)
item_0 = QtWidgets.QTreeWidgetItem(self.treeWidget)
brush = QtGui.QBrush(QtGui.QColor(255, 0, 0))
brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
item_0.setBackground(2, brush)
item_0 = QtWidgets.QTreeWidgetItem(self.treeWidget)
brush = QtGui.QBrush(QtGui.QColor(0, 255, 0))
brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
item_0.setBackground(2, brush)
item_0 = QtWidgets.QTreeWidgetItem(self.treeWidget)
item_0 = QtWidgets.QTreeWidgetItem(self.treeWidget)
item_0 = QtWidgets.QTreeWidgetItem(self.treeWidget)
item_0 = QtWidgets.QTreeWidgetItem(self.treeWidget)
item_0 = QtWidgets.QTreeWidgetItem(self.treeWidget)
item_0 = QtWidgets.QTreeWidgetItem(self.treeWidget)
item_0 = QtWidgets.QTreeWidgetItem(self.treeWidget)
item_0 = QtWidgets.QTreeWidgetItem(self.treeWidget)
item_0 = QtWidgets.QTreeWidgetItem(self.treeWidget)
self.treeWidget.header().setCascadingSectionResizes(False)
self.treeWidget.header().setDefaultSectionSize(60)
self.treeWidget.header().setHighlightSections(False)
self.treeWidget.header().setMinimumSectionSize(20)
self.listWidget = QtWidgets.QListWidget(Dialog)
self.listWidget.setGeometry(QtCore.QRect(10, 90, 281, 341))
self.listWidget.setContextMenuPolicy(
QtCore.Qt.ContextMenuPolicy.CustomContextMenu
)
self.listWidget.setObjectName("listWidget")
self.label_4 = QtWidgets.QLabel(Dialog)
self.label_4.setGeometry(QtCore.QRect(330, 50, 181, 21))
self.label_4.setObjectName("label_4")
self.label_5 = QtWidgets.QLabel(Dialog)
self.label_5.setGeometry(QtCore.QRect(200, 70, 41, 21))
self.label_5.setObjectName("label_5")
self.list_amount = QtWidgets.QLabel(Dialog)
self.list_amount.setGeometry(QtCore.QRect(240, 70, 47, 21))
self.list_amount.setObjectName("list_amount")
self.buttonBox = QtWidgets.QDialogButtonBox(Dialog)
self.buttonBox.setGeometry(QtCore.QRect(10, 450, 156, 23))
self.buttonBox.setStandardButtons(
QtWidgets.QDialogButtonBox.StandardButton.Cancel
| QtWidgets.QDialogButtonBox.StandardButton.Ok
)
self.buttonBox.setCenterButtons(False)
self.buttonBox.setObjectName("buttonBox")
self.buttonBox.setFocusPolicy(QtCore.Qt.FocusPolicy.ClickFocus)
# self.buttonBox.accepted.disconnect()
# set the activation action for the buttonBox to be shift enter
self.buttonBox.setFocusPolicy(QtCore.Qt.FocusPolicy.StrongFocus)
self.buttonBox.accepted.connect(Dialog.accept) # type: ignore
self.buttonBox.rejected.connect(Dialog.reject) # type: ignore
self.lineEdit.returnPressed.connect(self.add_to_list)
self.retranslateUi(Dialog)
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
self.label.setText(_translate("Dialog", "Signatur"))
self.label_2.setText(_translate("Dialog", "Modus"))
self.comboBox.setItemText(0, _translate("Dialog", "ARRAY"))
self.comboBox.setItemText(1, _translate("Dialog", "BibTeX"))
self.comboBox.setItemText(2, _translate("Dialog", "COinS"))
self.comboBox.setItemText(3, _translate("Dialog", "RIS"))
self.lineEdit.setPlaceholderText(_translate("Dialog", "Signatur / ISBN"))
self.label_3.setText(_translate("Dialog", "Queue"))
self.treeWidget.headerItem().setText(
0, _translate("Dialog", "Datensatz\\Metadata")
)
self.treeWidget.headerItem().setText(1, _translate("Dialog", "Array"))
self.treeWidget.headerItem().setText(2, _translate("Dialog", "BibTeX"))
self.treeWidget.headerItem().setText(3, _translate("Dialog", "COinS"))
self.treeWidget.headerItem().setText(4, _translate("Dialog", "RIS"))
__sortingEnabled = self.treeWidget.isSortingEnabled()
self.treeWidget.setSortingEnabled(False)
self.treeWidget.topLevelItem(0).setText(0, _translate("Dialog", "PPN"))
self.treeWidget.topLevelItem(0).setText(1, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(0).setText(2, _translate("Dialog", "0"))
self.treeWidget.topLevelItem(0).setText(3, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(0).setText(4, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(1).setText(0, _translate("Dialog", "Signatur"))
self.treeWidget.topLevelItem(1).setText(1, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(1).setText(2, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(1).setText(3, _translate("Dialog", "0"))
self.treeWidget.topLevelItem(1).setText(4, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(2).setText(0, _translate("Dialog", "Autor"))
self.treeWidget.topLevelItem(2).setText(1, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(2).setText(2, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(2).setText(3, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(2).setText(4, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(3).setText(0, _translate("Dialog", "ISBN"))
self.treeWidget.topLevelItem(3).setText(1, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(3).setText(2, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(3).setText(3, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(3).setText(4, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(4).setText(0, _translate("Dialog", "Jahr"))
self.treeWidget.topLevelItem(4).setText(1, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(4).setText(2, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(4).setText(3, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(4).setText(4, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(5).setText(0, _translate("Dialog", "Auflage"))
self.treeWidget.topLevelItem(5).setText(1, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(5).setText(2, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(5).setText(3, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(5).setText(4, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(6).setText(0, _translate("Dialog", "Sprache"))
self.treeWidget.topLevelItem(6).setText(1, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(6).setText(2, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(6).setText(3, _translate("Dialog", "0"))
self.treeWidget.topLevelItem(6).setText(4, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(7).setText(0, _translate("Dialog", "Herausgeber"))
self.treeWidget.topLevelItem(7).setText(1, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(7).setText(2, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(7).setText(3, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(7).setText(4, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(8).setText(0, _translate("Dialog", "Seiten"))
self.treeWidget.topLevelItem(8).setText(1, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(8).setText(2, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(8).setText(3, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(8).setText(4, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(9).setText(0, _translate("Dialog", "Titel"))
self.treeWidget.topLevelItem(9).setText(1, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(9).setText(2, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(9).setText(3, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(9).setText(4, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(10).setText(0, _translate("Dialog", "Link"))
self.treeWidget.topLevelItem(10).setText(1, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(10).setText(2, _translate("Dialog", "0"))
self.treeWidget.topLevelItem(10).setText(3, _translate("Dialog", "1"))
self.treeWidget.topLevelItem(10).setText(4, _translate("Dialog", "1"))
self.treeWidget.setSortingEnabled(__sortingEnabled)
self.label_4.setText(_translate("Dialog", "Belegbare Felder per Anbieter"))
self.label_5.setText(_translate("Dialog", "Anzahl:"))
self.list_amount.setText(_translate("Dialog", "0"))
self.recolorize()
self.listWidget.customContextMenuRequested.connect(self.custom_context_menu)
def add_to_list(self):
text = self.lineEdit.text().strip()
if text == "":
return
else:
self.listWidget.addItem(text)
self.list_amount.setText(str(self.listWidget.count()))
self.lineEdit.clear()
def recolorize(self):
# set the color of the cells of the treeWidget to red if the field is not supported by the provider
# else set it to green
for i in range(self.treeWidget.topLevelItemCount()):
for j in range(1, self.treeWidget.columnCount()):
if self.treeWidget.topLevelItem(i).text(j) == "0":
self.treeWidget.topLevelItem(i).setBackground(
j, QtGui.QColor(255, 0, 0)
)
else:
self.treeWidget.topLevelItem(i).setBackground(
j, QtGui.QColor(0, 255, 0)
)
# remove the text from the cells
self.treeWidget.topLevelItem(i).setText(j, "")
def custom_context_menu(self):
menu = QtWidgets.QMenu()
menu.addAction("Remove")
action = menu.exec(QtGui.QCursor.pos())
if action.text() == "Remove":
self.remove_from_list()
def remove_from_list(self):
self.listWidget.takeItem(self.listWidget.currentRow())
self.list_amount.setText(str(self.listWidget.count()))

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file '/home/alexander/GitHub/Semesterapparate/ui/dialogs/new_subject.ui'
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\Semesterapparate\ui\dialogs\new_subject.ui'
#
# Created by: PyQt6 UI code generator 6.5.3
# Created by: PySide6 UI code generator 6.3.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt6 import QtCore, QtWidgets
from PySide6 import QtCore, QtWidgets
class Ui_Dialog(object):
@@ -17,56 +17,47 @@ class Ui_Dialog(object):
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.verticalLayout = QtWidgets.QVBoxLayout()
self.verticalLayout.setObjectName("verticalLayout")
self.label = QtWidgets.QLabel(parent=Dialog)
self.label = QtWidgets.QLabel(Dialog)
self.label.setObjectName("label")
self.verticalLayout.addWidget(self.label)
self.gridLayout = QtWidgets.QGridLayout()
self.gridLayout.setObjectName("gridLayout")
spacerItem = QtWidgets.QSpacerItem(
40,
20,
QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Minimum,
)
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
self.gridLayout.addItem(spacerItem, 0, 1, 1, 1)
self.checkBox = QtWidgets.QCheckBox(parent=Dialog)
self.checkBox = QtWidgets.QCheckBox(Dialog)
self.checkBox.setObjectName("checkBox")
self.gridLayout.addWidget(self.checkBox, 0, 0, 1, 1)
self.verticalLayout.addLayout(self.gridLayout)
self.verticalLayout_2.addLayout(self.verticalLayout)
self.frame = QtWidgets.QFrame(parent=Dialog)
self.frame = QtWidgets.QFrame(Dialog)
self.frame.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel)
self.frame.setFrameShadow(QtWidgets.QFrame.Shadow.Raised)
self.frame.setObjectName("frame")
self.label_2 = QtWidgets.QLabel(parent=self.frame)
self.label_2 = QtWidgets.QLabel(self.frame)
self.label_2.setGeometry(QtCore.QRect(0, 10, 141, 16))
self.label_2.setObjectName("label_2")
self.lineEdit = QtWidgets.QLineEdit(parent=self.frame)
self.lineEdit = QtWidgets.QLineEdit(self.frame)
self.lineEdit.setGeometry(QtCore.QRect(0, 30, 141, 20))
self.lineEdit.setObjectName("lineEdit")
self.verticalLayout_2.addWidget(self.frame)
self.buttonBox = QtWidgets.QDialogButtonBox(parent=Dialog)
self.buttonBox = QtWidgets.QDialogButtonBox(Dialog)
self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal)
self.buttonBox.setStandardButtons(
QtWidgets.QDialogButtonBox.StandardButton.Cancel
| QtWidgets.QDialogButtonBox.StandardButton.Ok
)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.StandardButton.Cancel|QtWidgets.QDialogButtonBox.StandardButton.Ok)
self.buttonBox.setObjectName("buttonBox")
self.verticalLayout_2.addWidget(self.buttonBox)
self.frame.setVisible(False)
self.checkBox.stateChanged.connect(lambda: self.frame.setVisible(self.checkBox.isChecked()))
self.retranslateUi(Dialog)
self.buttonBox.accepted.connect(Dialog.accept) # type: ignore
self.buttonBox.rejected.connect(Dialog.reject) # type: ignore
self.buttonBox.accepted.connect(Dialog.accept) # type: ignore
self.buttonBox.rejected.connect(Dialog.reject) # type: ignore
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
self.label.setText(
_translate(
"Dialog",
"Das eingegebene Fach wurde nicht in der Datenbank gefunden. Soll es angelegt werden?",
)
)
self.label.setText(_translate("Dialog", "Das eingegebene Fach wurde nicht in der Datenbank gefunden. Soll es angelegt werden?"))
self.checkBox.setText(_translate("Dialog", "Ja"))
self.label_2.setText(_translate("Dialog", "Name des Neuen Faches:"))
def return_state(self):
return self.lineEdit.text()

View File

@@ -0,0 +1,158 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\Semesterapparate\ui\dialogs\parsed_titles.ui'
#
# Created by: PySide6 UI code generator 6.3.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PySide6 import QtCore, QtGui, QtWidgets
from src.logic.log import MyLogger
from src.logic.threads import AutoAdder
logger = MyLogger("AutoTitleAdder")
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(402, 316)
self.frame = QtWidgets.QFrame(Form)
self.frame.setGeometry(QtCore.QRect(10, 10, 381, 41))
self.frame.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel)
self.frame.setFrameShadow(QtWidgets.QFrame.Shadow.Raised)
self.frame.setObjectName("frame")
self.horizontalLayoutWidget = QtWidgets.QWidget(self.frame)
self.horizontalLayoutWidget.setGeometry(QtCore.QRect(0, 0, 381, 41))
self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget)
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout.setObjectName("horizontalLayout")
self.label = QtWidgets.QLabel(self.horizontalLayoutWidget)
self.label.setObjectName("label")
self.horizontalLayout.addWidget(self.label)
self.count = QtWidgets.QLabel(self.horizontalLayoutWidget)
font = QtGui.QFont()
font.setBold(True)
font.setWeight(75)
self.count.setFont(font)
self.count.setTextFormat(QtCore.Qt.TextFormat.PlainText)
self.count.setObjectName("count")
self.horizontalLayout.addWidget(self.count)
self.label_2 = QtWidgets.QLabel(self.horizontalLayoutWidget)
self.label_2.setObjectName("label_2")
self.horizontalLayout.addWidget(self.label_2)
spacerItem = QtWidgets.QSpacerItem(
40,
20,
QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Minimum,
)
self.horizontalLayout.addItem(spacerItem)
self.frame_2 = QtWidgets.QFrame(Form)
self.frame_2.setGeometry(QtCore.QRect(10, 80, 381, 201))
self.frame_2.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel)
self.frame_2.setFrameShadow(QtWidgets.QFrame.Shadow.Raised)
self.frame_2.setObjectName("frame_2")
self.horizontalLayoutWidget_2 = QtWidgets.QWidget(self.frame_2)
self.horizontalLayoutWidget_2.setGeometry(QtCore.QRect(0, 10, 381, 191))
self.horizontalLayoutWidget_2.setObjectName("horizontalLayoutWidget_2")
self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget_2)
self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.listWidget = QtWidgets.QListWidget(self.horizontalLayoutWidget_2)
self.listWidget.setObjectName("listWidget")
self.horizontalLayout_2.addWidget(self.listWidget)
self.listWidget_done = QtWidgets.QListWidget(self.horizontalLayoutWidget_2)
self.listWidget_done.setObjectName("listWidget_done")
self.horizontalLayout_2.addWidget(self.listWidget_done)
self.progressBar = QtWidgets.QProgressBar(Form)
self.progressBar.setGeometry(QtCore.QRect(10, 60, 381, 23))
self.progressBar.setProperty("value", 24)
self.progressBar.setObjectName("progressBar")
self.buttonBox = QtWidgets.QDialogButtonBox(Form)
self.buttonBox.setGeometry(QtCore.QRect(230, 290, 156, 23))
self.buttonBox.setStandardButtons(
QtWidgets.QDialogButtonBox.StandardButton.Cancel
| QtWidgets.QDialogButtonBox.StandardButton.Ok
)
self.buttonBox.setObjectName("buttonBox")
self.toolButton = QtWidgets.QToolButton(Form)
self.toolButton.setGeometry(QtCore.QRect(20, 290, 25, 19))
self.toolButton.setObjectName("toolButton")
self.signatures = []
self.prof_id = None
self.app_id = None
self.thread = QtCore.QThread()
self.toolButton.hide()
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
self.toolButton.clicked.connect(self.start)
# if cancel is clicked, terminate the thread
self.buttonBox.rejected.connect(self.thread_quit)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form"))
self.label.setText(_translate("Form", "Es wurden"))
self.count.setText(_translate("Form", "0"))
self.label_2.setText(_translate("Form", "Signaturen gefunden."))
self.toolButton.setText(_translate("Form", "..."))
def populate_table(self):
for i in range(len(self.signatures)):
self.listWidget.addItem(QtWidgets.QListWidgetItem())
self.listWidget.item(i).setText(self.signatures[i])
self.listWidget.item(i).setToolTip("Daten werden gesammelt")
def update_progress_bar(self, value: int):
self.progressBar.setValue(value)
def thread_quit(self):
# print("Terminating thread")
self.thread.terminate()
self.thread.quit()
self.thread.deleteLater()
self.thread = None
def start(self):
logger.log_info("Starting AutoAdder")
self.thread = AutoAdder(
data=self.signatures,
app_id=self.app_id,
prof_id=self.prof_id,
)
self.thread.finished.connect(self.on_completion)
self.thread.updateSignal.connect(self.update_progress_bar)
self.thread.setTextSignal.connect(self.update_lists)
self.thread.progress.connect(self.determine_progress)
self.thread.finished.connect(self.thread.quit)
self.thread.finished.connect(self.thread.deleteLater)
# self.thread.updateSignal.connect(self.update_progress_label)
# worker.finished.connect(worker.deleteLater)
self.thread.start()
def on_completion(self):
logger.log_info("AutoAdder finished")
logger.log_info("Returning data")
# create a function that closes the dialog
def determine_progress(self, signal):
# check length of listWidget
length = self.listWidget.count()
# print(f"Length of listWidget: {length}")
if length == 0:
logger.log_info("AutoAdder finished")
self.buttonBox.accepted.emit()
def update_lists(self, signal):
# get text of first entry in listWidget
text = self.listWidget.item(0).text()
# remove first entry
self.listWidget.takeItem(0)
# add first entry to listWidget_done
self.listWidget_done.addItem(text)

View File

@@ -0,0 +1,52 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\reminder.ui'
#
# Created by: PySide6 UI code generator 6.3.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):
def setupUi(self, Erinnerung):
Erinnerung.setObjectName("Erinnerung")
Erinnerung.resize(369, 308)
self.buttonBox = QtWidgets.QDialogButtonBox(Erinnerung)
self.buttonBox.setGeometry(QtCore.QRect(190, 270, 161, 32))
self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal)
self.buttonBox.setStandardButtons(
QtWidgets.QDialogButtonBox.StandardButton.Cancel
| QtWidgets.QDialogButtonBox.StandardButton.Ok
)
self.buttonBox.setObjectName("buttonBox")
self.message_box = QtWidgets.QTextEdit(Erinnerung)
self.message_box.setGeometry(QtCore.QRect(10, 60, 341, 201))
self.message_box.setObjectName("message_box")
self.label = QtWidgets.QLabel(Erinnerung)
self.label.setGeometry(QtCore.QRect(10, 30, 61, 21))
self.label.setObjectName("label")
self.label_2 = QtWidgets.QLabel(Erinnerung)
self.label_2.setGeometry(QtCore.QRect(150, 30, 81, 21))
self.label_2.setObjectName("label_2")
self.dateEdit = QtWidgets.QDateEdit(Erinnerung)
self.dateEdit.setGeometry(QtCore.QRect(240, 30, 110, 22))
self.dateEdit.setObjectName("dateEdit")
self.dateEdit.setDate(QtCore.QDate.currentDate())
self.retranslateUi(Erinnerung)
self.buttonBox.accepted.connect(Erinnerung.accept) # type: ignore
self.buttonBox.rejected.connect(Erinnerung.reject) # type: ignore
QtCore.QMetaObject.connectSlotsByName(Erinnerung)
def retranslateUi(self, Erinnerung):
_translate = QtCore.QCoreApplication.translate
Erinnerung.setWindowTitle(_translate("Erinnerung", "Dialog"))
self.label.setText(_translate("Erinnerung", "Nachricht:"))
self.label_2.setText(_translate("Erinnerung", "Erinnerung am:"))
def return_message(self) -> dict:
return {
"message": self.message_box.toPlainText(),
"remind_at": self.dateEdit.date().toString("yyyy-MM-dd"),
}

View File

@@ -0,0 +1,202 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\Semesterapparate\ui\dialogs\settings.ui'
#
# Created by: PySide6 UI code generator 6.3.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from omegaconf import OmegaConf
from PySide6 import QtCore, QtWidgets
config = OmegaConf.load("config.yaml")
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(743, 576)
self.buttonBox = QtWidgets.QDialogButtonBox(Dialog)
self.buttonBox.setGeometry(QtCore.QRect(120, 540, 621, 32))
self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal)
self.buttonBox.setStandardButtons(
QtWidgets.QDialogButtonBox.StandardButton.Cancel
| QtWidgets.QDialogButtonBox.StandardButton.Ok
)
self.buttonBox.setObjectName("buttonBox")
self.frame = QtWidgets.QFrame(Dialog)
self.frame.setGeometry(QtCore.QRect(0, 0, 741, 541))
self.frame.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel)
self.frame.setFrameShadow(QtWidgets.QFrame.Shadow.Raised)
self.frame.setObjectName("frame")
self.label = QtWidgets.QLabel(self.frame)
self.label.setGeometry(QtCore.QRect(400, 30, 161, 21))
self.label.setObjectName("label")
self.line = QtWidgets.QFrame(self.frame)
self.line.setGeometry(QtCore.QRect(370, 0, 20, 541))
self.line.setFrameShadow(QtWidgets.QFrame.Shadow.Plain)
self.line.setLineWidth(1)
self.line.setMidLineWidth(0)
self.line.setFrameShape(QtWidgets.QFrame.Shape.VLine)
self.line.setObjectName("line")
self.textBrowser = QtWidgets.QTextBrowser(self.frame)
self.textBrowser.setGeometry(QtCore.QRect(400, 50, 311, 51))
self.textBrowser.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
self.textBrowser.setInputMethodHints(QtCore.Qt.InputMethodHint.ImhNone)
self.textBrowser.setObjectName("textBrowser")
self.label_2 = QtWidgets.QLabel(self.frame)
self.label_2.setGeometry(QtCore.QRect(10, 20, 161, 21))
self.label_2.setObjectName("label_2")
self.os_apps = QtWidgets.QCheckBox(self.frame)
self.os_apps.setGeometry(QtCore.QRect(410, 110, 161, 17))
self.os_apps.setObjectName("os_apps")
self.formLayoutWidget = QtWidgets.QWidget(self.frame)
self.formLayoutWidget.setGeometry(QtCore.QRect(10, 50, 361, 491))
self.formLayoutWidget.setObjectName("formLayoutWidget")
self.gridLayout = QtWidgets.QGridLayout(self.formLayoutWidget)
self.gridLayout.setContentsMargins(0, 0, 0, 0)
self.gridLayout.setObjectName("gridLayout")
self.label_3 = QtWidgets.QLabel(self.formLayoutWidget)
self.label_3.setObjectName("label_3")
self.gridLayout.addWidget(self.label_3, 0, 0, 1, 1)
self.label_5 = QtWidgets.QLabel(self.formLayoutWidget)
self.label_5.setObjectName("label_5")
self.gridLayout.addWidget(self.label_5, 2, 0, 1, 1)
self.db_path = QtWidgets.QLineEdit(self.formLayoutWidget)
self.db_path.setEnabled(False)
self.db_path.setObjectName("db_path")
self.gridLayout.addWidget(self.db_path, 1, 1, 1, 1)
self.save_path = QtWidgets.QLineEdit(self.formLayoutWidget)
self.save_path.setObjectName("save_path")
self.gridLayout.addWidget(self.save_path, 2, 1, 1, 1)
self.label_4 = QtWidgets.QLabel(self.formLayoutWidget)
self.label_4.setObjectName("label_4")
self.gridLayout.addWidget(self.label_4, 1, 0, 1, 1)
self.db_name = QtWidgets.QLineEdit(self.formLayoutWidget)
self.db_name.setObjectName("db_name")
self.gridLayout.addWidget(self.db_name, 0, 1, 1, 1)
self.tb_set_save_path = QtWidgets.QToolButton(self.formLayoutWidget)
self.tb_set_save_path.setObjectName("tb_set_save_path")
self.gridLayout.addWidget(self.tb_set_save_path, 2, 2, 1, 1)
spacerItem = QtWidgets.QSpacerItem(
20,
40,
QtWidgets.QSizePolicy.Policy.Minimum,
QtWidgets.QSizePolicy.Policy.Expanding,
)
self.gridLayout.addItem(spacerItem, 3, 1, 1, 1)
self.tb_select_db = QtWidgets.QToolButton(self.formLayoutWidget)
self.tb_select_db.setObjectName("tb_select_db")
self.gridLayout.addWidget(self.tb_select_db, 0, 2, 1, 1)
self.scrollArea = QtWidgets.QScrollArea(self.frame)
self.scrollArea.setGeometry(QtCore.QRect(400, 130, 331, 411))
self.scrollArea.setWidgetResizable(True)
self.scrollArea.setObjectName("scrollArea")
self.scrollAreaWidgetContents = QtWidgets.QWidget()
self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 329, 409))
self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents")
self.treeWidget = QtWidgets.QTreeWidget(self.scrollAreaWidgetContents)
self.treeWidget.setGeometry(QtCore.QRect(0, 0, 331, 411))
self.treeWidget.setObjectName("treeWidget")
self.treeWidget.setContextMenuPolicy(
QtCore.Qt.ContextMenuPolicy.ActionsContextMenu
)
self.scrollArea.setWidget(self.scrollAreaWidgetContents)
self.label_3.setBuddy(self.db_name)
self.label_5.setBuddy(self.save_path)
self.label_4.setBuddy(self.db_path)
self.tb_select_db.clicked.connect(self.select_db)
self.tb_set_save_path.clicked.connect(self.set_save_path)
self.retranslateUi(Dialog)
self.buttonBox.accepted.connect(Dialog.accept) # type: ignore
self.buttonBox.rejected.connect(Dialog.reject) # type: ignore
QtCore.QMetaObject.connectSlotsByName(Dialog)
Dialog.setTabOrder(self.db_name, self.db_path)
Dialog.setTabOrder(self.db_path, self.save_path)
Dialog.setTabOrder(self.save_path, self.os_apps)
Dialog.setTabOrder(self.os_apps, self.textBrowser)
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
self.label.setText(_translate("Dialog", "Dateispezifische Einstellungen"))
self.textBrowser.setHtml(
_translate(
"Dialog",
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">\n'
'<html><head><meta name="qrichtext" content="1" /><style type="text/css">\n'
"p, li { white-space: pre-wrap; }\n"
"</style></head><body style=\" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;\">\n"
'<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hier können Einstellungen f<>r bestehende Dateiformate geändert, oder neue Dateiformate eingefügt werden</p>\n'
'<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html>',
)
)
self.label_2.setText(_translate("Dialog", "Allgemeine Einstellungen"))
self.os_apps.setToolTip(
_translate(
"Dialog",
"Verwendet im Betriebssystem festgelegte Anwendungen um Dateien zu öffnen",
)
)
self.os_apps.setText(_translate("Dialog", "Standard-Apps verwenden"))
self.label_3.setToolTip(
_translate(
"Dialog",
'<html><head/><body><p>Name der Datenbank, welche verwendet werden soll. <span style=" font-weight:600;">Muss</span> auf .db enden</p></body></html>',
)
)
self.label_3.setText(_translate("Dialog", "Datenbankname"))
self.label_5.setToolTip(
_translate(
"Dialog",
"Pfad, an dem heruntergeladene Dateien gespeichert werden sollen",
)
)
self.label_5.setText(_translate("Dialog", "Speicherpfad"))
self.label_4.setText(_translate("Dialog", "Datenbankpfad"))
self.db_name.setText(_translate("Dialog", "sap.db"))
self.tb_set_save_path.setText(_translate("Dialog", "..."))
self.tb_select_db.setText(_translate("Dialog", "..."))
self.load_config()
def load_config(self):
self.db_name.setText(config.database.name)
self.db_path.setText(config.database.path)
self.save_path.setText(config.save_path)
self.os_apps.setChecked(config.default_apps)
applications = config.custom_applications
for application in applications:
name = application.application
file_type = application.extensions
display_name = application.name
# print(name, file_type, display_name) #
# create new item
item = QtWidgets.QTreeWidgetItem(self.treeWidget)
item.setText(0, display_name)
def select_db(self):
# open file dialog, limit to .db files
file_dialog = QtWidgets.QFileDialog()
file_dialog.setFileMode(QtWidgets.QFileDialog.FileMode.AnyFile)
file_dialog.setNameFilter("Datenbank (*.db)")
file_dialog.setViewMode(QtWidgets.QFileDialog.ViewMode.Detail)
if file_dialog.exec():
self.db_name.setText(file_dialog.selectedFiles()[0].split("/")[-1])
self.db_path.setText(
file_dialog.selectedFiles()[0].split(self.db_name.text())[0]
)
def set_save_path(self):
# open file dialog, limit to .db files
file_dialog = QtWidgets.QFileDialog()
file_dialog.setFileMode(QtWidgets.QFileDialog.FileMode.Directory)
file_dialog.setViewMode(QtWidgets.QFileDialog.ViewMode.Detail)
if file_dialog.exec():
self.save_path.setText(file_dialog.selectedFiles()[0])
def return_data(self):
config.database.name = self.db_name.text()
config.database.path = self.db_path.text()
config.save_path = self.save_path.text()
config.default_apps = self.os_apps.isChecked()
return config

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1">
</TS>

View File

@@ -1,30 +1,34 @@
__all__ = [
"add_bookdata_ui",
"edit_bookdata_ui",
"login_ui",
"BookDataUI",
"LoginDialog",
"Mail_Dialog",
"MailTemplateDialog",
"medienadder_ui",
"parsed_titles_ui",
"MedienAdder",
"ParsedTitles",
"popus_confirm",
"reminder_ui",
"Settings",
"ReminderDialog",
"About",
"ElsaGenConfirm",
"ElsaAddEntry",
"ApparatExtendDialog",
"DocumentPrintDialog",
"NewEditionDialog",
"Settings",
"DeleteDialog",
]
from .bookdata import BookDataUI as edit_bookdata_ui
from .login import LoginDialog as login_ui
from .about import About
from .app_ext import ApparatExtendDialog
from .bookdata import BookDataUI
from .deletedialog import DeleteDialog
from .docuprint import DocumentPrintDialog
from .elsa_add_entry import ElsaAddEntry
from .elsa_gen_confirm import ElsaGenConfirm
from .login import LoginDialog
from .mail import Mail_Dialog
from .mailTemplate import MailTemplateDialog
from .medienadder import MedienAdder as medienadder_ui
from .parsed_titles import ParsedTitles as parsed_titles_ui
from .medienadder import MedienAdder
from .newEdition import NewEditionDialog
from .parsed_titles import ParsedTitles
from .popup_confirm import ConfirmDialog as popus_confirm
from .reminder import ReminderDialog as reminder_ui
from .about import About
from .elsa_gen_confirm import ElsaGenConfirm
from .elsa_add_entry import ElsaAddEntry
from .app_ext import ApparatExtendDialog
from .reminder import ReminderDialog
from .settings import Settings

View File

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

View File

@@ -1,7 +1,9 @@
from PyQt6 import QtWidgets
from .dialog_sources.Ui_apparat_extend import Ui_Dialog
from PySide6 import QtWidgets
from src import Icon
from .dialog_sources.apparat_extend_ui import Ui_Dialog
class ApparatExtendDialog(QtWidgets.QDialog, Ui_Dialog):
def __init__(

View File

@@ -1,8 +1,8 @@
from PyQt6 import QtWidgets
from PySide6 import QtWidgets
from src.logic.dataclass import BookData
from .dialog_sources.Ui_edit_bookdata import Ui_Dialog
from .dialog_sources.edit_bookdata_ui import Ui_Dialog
class BookDataUI(QtWidgets.QDialog, Ui_Dialog):

View File

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

View File

@@ -0,0 +1,32 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\confirm_extend.ui'
#
# Created by: PySide6 UI code generator 6.8.0
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_extend_confirm(object):
def setupUi(self, extend_confirm):
extend_confirm.setObjectName("extend_confirm")
extend_confirm.resize(380, 97)
self.buttonBox = QtWidgets.QDialogButtonBox(parent=extend_confirm)
self.buttonBox.setGeometry(QtCore.QRect(290, 20, 81, 241))
self.buttonBox.setOrientation(QtCore.Qt.Orientation.Vertical)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.StandardButton.Cancel|QtWidgets.QDialogButtonBox.StandardButton.Ok)
self.buttonBox.setObjectName("buttonBox")
self.textEdit = QtWidgets.QTextEdit(parent=extend_confirm)
self.textEdit.setGeometry(QtCore.QRect(10, 10, 271, 81))
self.textEdit.setObjectName("textEdit")
self.retranslateUi(extend_confirm)
self.buttonBox.accepted.connect(extend_confirm.accept) # type: ignore
self.buttonBox.rejected.connect(extend_confirm.reject) # type: ignore
QtCore.QMetaObject.connectSlotsByName(extend_confirm)
def retranslateUi(self, extend_confirm):
_translate = QtCore.QCoreApplication.translate
extend_confirm.setWindowTitle(_translate("extend_confirm", "Dialog"))

View File

@@ -0,0 +1,129 @@
from typing import Any
from PySide6 import QtCore, QtWidgets
from src import Icon
from src.backend.database import Database
from .dialog_sources.deletedialog_ui import Ui_Dialog
class DeleteDialog(QtWidgets.QDialog, Ui_Dialog):
def __init__(self):
super().__init__()
self.setupUi(self)
self.setWindowTitle("Medien löschen")
self.setWindowIcon(Icon("trash").icon)
self.reset_btn.clicked.connect(self.reset_selection)
self.cancel_btn.clicked.connect(self.close)
self.delete_btn.clicked.connect(self.delete_selected)
self.db = Database()
self.books = self.setBooks()
self.lineEdit.textChanged.connect(self.populate_books)
self.populate_books()
def delete_selected(self):
to_delete = []
for row in range(self.tableWidget.rowCount()):
checkbox = self.tableWidget.cellWidget(row, 0)
if checkbox is not None and checkbox.isChecked():
book_id_item = self.tableWidget.item(row, 6)
if book_id_item is not None:
book_id = int(book_id_item.text())
to_delete.append(book_id)
if to_delete:
self.db.deleteBooks(to_delete)
self.accept()
def reset_selection(self):
for row in range(self.tableWidget.rowCount()):
checkbox = self.tableWidget.cellWidget(row, 0)
if checkbox is not None:
checkbox.setChecked(False)
def setBooks(self) -> list[dict[str, Any]]:
result: list[dict[str, Any]] = []
books = self.db.getAllBooks()
for book in books:
title = book["bookdata"].title
signature = book["bookdata"].signature
edition = book["bookdata"].edition
appnr = self.db.getApparatNrByBookId(book["id"])
result.append(
{
"id": book["id"],
"appnr": appnr,
"title": title,
"signature": signature,
"edition": edition,
}
)
return result
def populate_books(self):
searchterm = self.lineEdit.text().lower()
self.tableWidget.setRowCount(0)
for book in self.books:
checkbox = QtWidgets.QCheckBox()
app_nr = book["appnr"]
title = book["title"]
signature = book["signature"]
edition = book["edition"] if book["edition"] else ""
if searchterm in title.lower() or searchterm in signature.lower():
self.tableWidget.insertRow(self.tableWidget.rowCount())
self.tableWidget.setCellWidget(
self.tableWidget.rowCount() - 1, 0, checkbox
)
self.tableWidget.setItem(
self.tableWidget.rowCount() - 1,
1,
QtWidgets.QTableWidgetItem(str(app_nr)),
)
self.tableWidget.setItem(
self.tableWidget.rowCount() - 1,
2,
QtWidgets.QTableWidgetItem(signature),
)
self.tableWidget.setItem(
self.tableWidget.rowCount() - 1,
3,
QtWidgets.QTableWidgetItem(title),
)
self.tableWidget.setItem(
self.tableWidget.rowCount() - 1,
4,
QtWidgets.QTableWidgetItem(edition),
)
self.tableWidget.setItem(
self.tableWidget.rowCount() - 1,
5,
QtWidgets.QTableWidgetItem(""),
)
self.tableWidget.setItem(
self.tableWidget.rowCount() - 1,
6,
QtWidgets.QTableWidgetItem(str(book["id"])),
)
else:
continue
# set column signature to be 10px wider than the longest entry
self.tableWidget.setColumnWidth(1, 100)
self.tableWidget.setColumnWidth(2, 150)
self.tableWidget.setColumnWidth(3, 150)
self.tableWidget.setColumnWidth(4, 100)
self.tableWidget.setColumnWidth(0, 50)
# horizontal header 0 should be centered
self.tableWidget.horizontalHeader().setDefaultAlignment(
QtCore.Qt.AlignmentFlag.AlignCenter
)
def launch():
app = QtWidgets.QApplication.instance()
if app is None:
app = QtWidgets.QApplication([])
dialog = DeleteDialog()
dialog.exec()

View File

@@ -1,137 +0,0 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\mail_preview.ui'
#
# Created by: PyQt6 UI code generator 6.7.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt6 import QtCore, QtGui, QtWidgets
class Ui_eMailPreview(object):
def setupUi(self, eMailPreview):
eMailPreview.setObjectName("eMailPreview")
eMailPreview.resize(700, 668)
icon = QtGui.QIcon()
icon.addPixmap(
QtGui.QPixmap(
"c:\\Users\\aky547\\GitHub\\SemesterapparatsManager\\src\\ui\\dialogs\\dialog_sources\\../../../../../../icons/email.svg"
),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.Off,
)
eMailPreview.setWindowIcon(icon)
self.gridLayout_2 = QtWidgets.QGridLayout(eMailPreview)
self.gridLayout_2.setObjectName("gridLayout_2")
self.gridLayout = QtWidgets.QGridLayout()
self.gridLayout.setObjectName("gridLayout")
self.prof_name = QtWidgets.QLineEdit(parent=eMailPreview)
self.prof_name.setObjectName("prof_name")
self.gridLayout.addWidget(self.prof_name, 2, 2, 1, 1)
self.newTemplate = QtWidgets.QPushButton(parent=eMailPreview)
self.newTemplate.setAutoFillBackground(False)
self.newTemplate.setText("")
self.newTemplate.setIconSize(QtCore.QSize(24, 24))
self.newTemplate.setAutoDefault(True)
self.newTemplate.setDefault(False)
self.newTemplate.setFlat(False)
self.newTemplate.setObjectName("newTemplate")
self.gridLayout.addWidget(self.newTemplate, 0, 3, 1, 1)
self.comboBox = QtWidgets.QComboBox(parent=eMailPreview)
self.comboBox.setObjectName("comboBox")
self.gridLayout.addWidget(self.comboBox, 0, 2, 1, 1)
self.mail_header = QtWidgets.QLineEdit(parent=eMailPreview)
self.mail_header.setObjectName("mail_header")
self.gridLayout.addWidget(self.mail_header, 3, 2, 1, 1)
self.label_6 = QtWidgets.QLabel(parent=eMailPreview)
self.label_6.setObjectName("label_6")
self.gridLayout.addWidget(self.label_6, 4, 0, 1, 1)
self.mail_body = QtWidgets.QTextEdit(parent=eMailPreview)
self.mail_body.setObjectName("mail_body")
self.gridLayout.addWidget(self.mail_body, 5, 2, 1, 1)
self.label_2 = QtWidgets.QLabel(parent=eMailPreview)
self.label_2.setObjectName("label_2")
self.gridLayout.addWidget(self.label_2, 2, 0, 1, 1)
self.mail_name = QtWidgets.QLineEdit(parent=eMailPreview)
self.mail_name.setObjectName("mail_name")
self.gridLayout.addWidget(self.mail_name, 1, 2, 1, 1)
self.label_5 = QtWidgets.QLabel(parent=eMailPreview)
self.label_5.setObjectName("label_5")
self.gridLayout.addWidget(self.label_5, 0, 0, 1, 1)
self.label_4 = QtWidgets.QLabel(parent=eMailPreview)
self.label_4.setObjectName("label_4")
self.gridLayout.addWidget(self.label_4, 3, 0, 1, 1)
self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
self.gender_male = QtWidgets.QRadioButton(parent=eMailPreview)
self.gender_male.setObjectName("gender_male")
self.horizontalLayout_3.addWidget(self.gender_male)
self.gender_female = QtWidgets.QRadioButton(parent=eMailPreview)
self.gender_female.setObjectName("gender_female")
self.horizontalLayout_3.addWidget(self.gender_female)
self.gender_non = QtWidgets.QRadioButton(parent=eMailPreview)
self.gender_non.setObjectName("gender_non")
self.horizontalLayout_3.addWidget(self.gender_non)
spacerItem = QtWidgets.QSpacerItem(
40,
20,
QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Minimum,
)
self.horizontalLayout_3.addItem(spacerItem)
self.gridLayout.addLayout(self.horizontalLayout_3, 4, 2, 1, 1)
self.label_3 = QtWidgets.QLabel(parent=eMailPreview)
self.label_3.setAlignment(
QtCore.Qt.AlignmentFlag.AlignLeading
| QtCore.Qt.AlignmentFlag.AlignLeft
| QtCore.Qt.AlignmentFlag.AlignTop
)
self.label_3.setObjectName("label_3")
self.gridLayout.addWidget(self.label_3, 5, 0, 1, 1)
self.label = QtWidgets.QLabel(parent=eMailPreview)
self.label.setObjectName("label")
self.gridLayout.addWidget(self.label, 1, 0, 1, 1)
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
spacerItem1 = QtWidgets.QSpacerItem(
40,
20,
QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Minimum,
)
self.horizontalLayout_2.addItem(spacerItem1)
self.btn_okay = QtWidgets.QPushButton(parent=eMailPreview)
self.btn_okay.setStatusTip("")
self.btn_okay.setObjectName("btn_okay")
self.horizontalLayout_2.addWidget(self.btn_okay)
self.buttonBox = QtWidgets.QDialogButtonBox(parent=eMailPreview)
self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal)
self.buttonBox.setStandardButtons(
QtWidgets.QDialogButtonBox.StandardButton.Cancel
)
self.buttonBox.setCenterButtons(True)
self.buttonBox.setObjectName("buttonBox")
self.horizontalLayout_2.addWidget(self.buttonBox)
self.gridLayout.addLayout(self.horizontalLayout_2, 6, 2, 1, 1)
self.gridLayout_2.addLayout(self.gridLayout, 0, 0, 1, 1)
self.retranslateUi(eMailPreview)
self.buttonBox.accepted.connect(eMailPreview.accept) # type: ignore
self.buttonBox.rejected.connect(eMailPreview.reject) # type: ignore
QtCore.QMetaObject.connectSlotsByName(eMailPreview)
def retranslateUi(self, eMailPreview):
_translate = QtCore.QCoreApplication.translate
eMailPreview.setWindowTitle(_translate("eMailPreview", "eMail Voransicht"))
self.label_6.setText(_translate("eMailPreview", "Anrede"))
self.label_2.setText(_translate("eMailPreview", "Prof"))
self.label_5.setText(_translate("eMailPreview", "Art"))
self.label_4.setText(_translate("eMailPreview", "Betreff"))
self.gender_male.setText(_translate("eMailPreview", "M"))
self.gender_female.setText(_translate("eMailPreview", "W"))
self.gender_non.setText(_translate("eMailPreview", "Divers"))
self.label_3.setText(_translate("eMailPreview", "Mail"))
self.label.setText(_translate("eMailPreview", "eMail"))
self.btn_okay.setWhatsThis(_translate("eMailPreview", "test"))
self.btn_okay.setText(_translate("eMailPreview", "Senden"))

View File

@@ -1,181 +0,0 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\newMailTemplateDesigner.ui'
#
# Created by: PyQt6 UI code generator 6.6.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt6 import QtCore, QtWidgets
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(689, 572)
self.verticalLayout_2 = QtWidgets.QVBoxLayout(Dialog)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.verticalLayout = QtWidgets.QVBoxLayout()
self.verticalLayout.setObjectName("verticalLayout")
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.bold = QtWidgets.QPushButton(parent=Dialog)
self.bold.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
self.bold.setCheckable(True)
self.bold.setObjectName("bold")
self.horizontalLayout_2.addWidget(self.bold)
self.italic = QtWidgets.QPushButton(parent=Dialog)
self.italic.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
self.italic.setCheckable(True)
self.italic.setObjectName("italic")
self.horizontalLayout_2.addWidget(self.italic)
self.underlined = QtWidgets.QPushButton(parent=Dialog)
self.underlined.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
self.underlined.setCheckable(True)
self.underlined.setObjectName("underlined")
self.horizontalLayout_2.addWidget(self.underlined)
self.fontBox = QtWidgets.QFontComboBox(parent=Dialog)
self.fontBox.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
self.fontBox.setObjectName("fontBox")
self.horizontalLayout_2.addWidget(self.fontBox)
self.fontSize = QtWidgets.QComboBox(parent=Dialog)
self.fontSize.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
self.fontSize.setObjectName("fontSize")
self.fontSize.addItem("")
self.fontSize.addItem("")
self.fontSize.addItem("")
self.fontSize.addItem("")
self.fontSize.addItem("")
self.fontSize.addItem("")
self.fontSize.addItem("")
self.fontSize.addItem("")
self.fontSize.addItem("")
self.fontSize.addItem("")
self.fontSize.addItem("")
self.fontSize.addItem("")
self.fontSize.addItem("")
self.fontSize.addItem("")
self.fontSize.addItem("")
self.horizontalLayout_2.addWidget(self.fontSize)
spacerItem = QtWidgets.QSpacerItem(
40,
20,
QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Minimum,
)
self.horizontalLayout_2.addItem(spacerItem)
self.verticalLayout.addLayout(self.horizontalLayout_2)
self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
self.horizontalLayout_4.setObjectName("horizontalLayout_4")
self.verticalLayout.addLayout(self.horizontalLayout_4)
self.gridLayout = QtWidgets.QGridLayout()
self.gridLayout.setObjectName("gridLayout")
self.label = QtWidgets.QLabel(parent=Dialog)
self.label.setObjectName("label")
self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
self.placeholder_list = QtWidgets.QComboBox(parent=Dialog)
self.placeholder_list.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
self.placeholder_list.setSizeAdjustPolicy(
QtWidgets.QComboBox.SizeAdjustPolicy.AdjustToContents
)
self.placeholder_list.setObjectName("placeholder_list")
self.placeholder_list.addItem("")
self.placeholder_list.addItem("")
self.placeholder_list.addItem("")
self.placeholder_list.addItem("")
self.placeholder_list.addItem("")
self.placeholder_list.addItem("")
self.gridLayout.addWidget(self.placeholder_list, 1, 0, 1, 1)
self.label_2 = QtWidgets.QLabel(parent=Dialog)
self.label_2.setObjectName("label_2")
self.gridLayout.addWidget(self.label_2, 0, 1, 1, 1)
self.lineEdit = QtWidgets.QLineEdit(parent=Dialog)
self.lineEdit.setEnabled(True)
self.lineEdit.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
self.lineEdit.setFrame(False)
self.lineEdit.setReadOnly(True)
self.lineEdit.setObjectName("lineEdit")
self.gridLayout.addWidget(self.lineEdit, 1, 1, 1, 1)
self.insertPlaceholder = QtWidgets.QPushButton(parent=Dialog)
self.insertPlaceholder.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
self.insertPlaceholder.setObjectName("insertPlaceholder")
self.gridLayout.addWidget(self.insertPlaceholder, 1, 2, 1, 1)
self.verticalLayout.addLayout(self.gridLayout)
self.label_3 = QtWidgets.QLabel(parent=Dialog)
self.label_3.setObjectName("label_3")
self.verticalLayout.addWidget(self.label_3)
self.subject = QtWidgets.QLineEdit(parent=Dialog)
self.subject.setObjectName("subject")
self.verticalLayout.addWidget(self.subject)
self.templateEdit = QtWidgets.QTextEdit(parent=Dialog)
self.templateEdit.setObjectName("templateEdit")
self.verticalLayout.addWidget(self.templateEdit)
self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
self.testTemplate = QtWidgets.QPushButton(parent=Dialog)
self.testTemplate.setObjectName("testTemplate")
self.horizontalLayout_3.addWidget(self.testTemplate)
spacerItem1 = QtWidgets.QSpacerItem(
40,
20,
QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Minimum,
)
self.horizontalLayout_3.addItem(spacerItem1)
self.verticalLayout.addLayout(self.horizontalLayout_3)
self.verticalLayout_2.addLayout(self.verticalLayout)
self.buttonBox = QtWidgets.QDialogButtonBox(parent=Dialog)
self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal)
self.buttonBox.setStandardButtons(
QtWidgets.QDialogButtonBox.StandardButton.Cancel
| QtWidgets.QDialogButtonBox.StandardButton.Discard
| QtWidgets.QDialogButtonBox.StandardButton.Save
)
self.buttonBox.setObjectName("buttonBox")
self.verticalLayout_2.addWidget(self.buttonBox)
self.retranslateUi(Dialog)
self.fontSize.setCurrentIndex(1)
QtCore.QMetaObject.connectSlotsByName(Dialog)
Dialog.setTabOrder(self.subject, self.templateEdit)
Dialog.setTabOrder(self.templateEdit, self.testTemplate)
Dialog.setTabOrder(self.testTemplate, self.insertPlaceholder)
Dialog.setTabOrder(self.insertPlaceholder, self.lineEdit)
Dialog.setTabOrder(self.lineEdit, self.fontSize)
Dialog.setTabOrder(self.fontSize, self.placeholder_list)
Dialog.setTabOrder(self.placeholder_list, self.fontBox)
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
self.bold.setText(_translate("Dialog", "Fett"))
self.italic.setText(_translate("Dialog", "Kursiv"))
self.underlined.setText(_translate("Dialog", "Unterstrichen"))
self.fontSize.setItemText(0, _translate("Dialog", "8"))
self.fontSize.setItemText(1, _translate("Dialog", "9"))
self.fontSize.setItemText(2, _translate("Dialog", "11"))
self.fontSize.setItemText(3, _translate("Dialog", "12"))
self.fontSize.setItemText(4, _translate("Dialog", "14"))
self.fontSize.setItemText(5, _translate("Dialog", "16"))
self.fontSize.setItemText(6, _translate("Dialog", "18"))
self.fontSize.setItemText(7, _translate("Dialog", "20"))
self.fontSize.setItemText(8, _translate("Dialog", "22"))
self.fontSize.setItemText(9, _translate("Dialog", "24"))
self.fontSize.setItemText(10, _translate("Dialog", "26"))
self.fontSize.setItemText(11, _translate("Dialog", "28"))
self.fontSize.setItemText(12, _translate("Dialog", "36"))
self.fontSize.setItemText(13, _translate("Dialog", "48"))
self.fontSize.setItemText(14, _translate("Dialog", "76"))
self.label.setText(_translate("Dialog", "Platzhalter"))
self.placeholder_list.setItemText(0, _translate("Dialog", "«Anrede»"))
self.placeholder_list.setItemText(1, _translate("Dialog", "«ApparatsName»"))
self.placeholder_list.setItemText(2, _translate("Dialog", "«ApparatsFach»"))
self.placeholder_list.setItemText(3, _translate("Dialog", "«ApparatsNummer»"))
self.placeholder_list.setItemText(4, _translate("Dialog", "«DozentName»"))
self.placeholder_list.setItemText(5, _translate("Dialog", "«Signatur»"))
self.label_2.setText(_translate("Dialog", "Beschreibung"))
self.insertPlaceholder.setText(
_translate("Dialog", "An aktiver Position einfügen")
)
self.label_3.setText(_translate("Dialog", "Betreff"))
self.testTemplate.setText(_translate("Dialog", "Template testen"))

View File

@@ -1,416 +0,0 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\settings.ui'
#
# Created by: PyQt6 UI code generator 6.7.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt6 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.setWindowModality(QtCore.Qt.WindowModality.NonModal)
Dialog.resize(651, 679)
sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Policy.MinimumExpanding,
QtWidgets.QSizePolicy.Policy.MinimumExpanding,
)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(Dialog.sizePolicy().hasHeightForWidth())
Dialog.setSizePolicy(sizePolicy)
self.verticalLayout = QtWidgets.QVBoxLayout(Dialog)
self.verticalLayout.setObjectName("verticalLayout")
self.toolBox = QtWidgets.QToolBox(parent=Dialog)
sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Policy.MinimumExpanding,
QtWidgets.QSizePolicy.Policy.MinimumExpanding,
)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.toolBox.sizePolicy().hasHeightForWidth())
self.toolBox.setSizePolicy(sizePolicy)
self.toolBox.setInputMethodHints(QtCore.Qt.InputMethodHint.ImhNone)
self.toolBox.setObjectName("toolBox")
self.page_1 = QtWidgets.QWidget()
self.page_1.setGeometry(QtCore.QRect(0, 0, 633, 511))
sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Policy.MinimumExpanding,
QtWidgets.QSizePolicy.Policy.MinimumExpanding,
)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.page_1.sizePolicy().hasHeightForWidth())
self.page_1.setSizePolicy(sizePolicy)
self.page_1.setObjectName("page_1")
self.gridLayout_3 = QtWidgets.QGridLayout(self.page_1)
self.gridLayout_3.setObjectName("gridLayout_3")
self.db_name = QtWidgets.QLineEdit(parent=self.page_1)
self.db_name.setObjectName("db_name")
self.gridLayout_3.addWidget(self.db_name, 0, 1, 1, 1)
self.label_5 = QtWidgets.QLabel(parent=self.page_1)
self.label_5.setObjectName("label_5")
self.gridLayout_3.addWidget(self.label_5, 0, 0, 1, 1)
self.db_path = QtWidgets.QLineEdit(parent=self.page_1)
self.db_path.setEnabled(False)
self.db_path.setObjectName("db_path")
self.gridLayout_3.addWidget(self.db_path, 1, 1, 1, 1)
self.label_12 = QtWidgets.QLabel(parent=self.page_1)
self.label_12.setObjectName("label_12")
self.gridLayout_3.addWidget(self.label_12, 2, 0, 1, 1)
self.label_11 = QtWidgets.QLabel(parent=self.page_1)
self.label_11.setObjectName("label_11")
self.gridLayout_3.addWidget(self.label_11, 1, 0, 1, 1)
self.tb_set_save_path = QtWidgets.QToolButton(parent=self.page_1)
self.tb_set_save_path.setObjectName("tb_set_save_path")
self.gridLayout_3.addWidget(self.tb_set_save_path, 2, 2, 1, 1)
self.tb_select_db = QtWidgets.QToolButton(parent=self.page_1)
self.tb_select_db.setObjectName("tb_select_db")
self.gridLayout_3.addWidget(self.tb_select_db, 0, 2, 1, 1)
self.save_path = QtWidgets.QLineEdit(parent=self.page_1)
self.save_path.setObjectName("save_path")
self.gridLayout_3.addWidget(self.save_path, 2, 1, 1, 1)
spacerItem = QtWidgets.QSpacerItem(
20,
40,
QtWidgets.QSizePolicy.Policy.Minimum,
QtWidgets.QSizePolicy.Policy.Expanding,
)
self.gridLayout_3.addItem(spacerItem, 3, 1, 1, 1)
self.toolBox.addItem(self.page_1, "")
self.page_2 = QtWidgets.QWidget()
self.page_2.setGeometry(QtCore.QRect(0, 0, 633, 511))
self.page_2.setObjectName("page_2")
self.gridLayout = QtWidgets.QGridLayout(self.page_2)
self.gridLayout.setObjectName("gridLayout")
self.zotero_library_type = QtWidgets.QLineEdit(parent=self.page_2)
self.zotero_library_type.setObjectName("zotero_library_type")
self.gridLayout.addWidget(self.zotero_library_type, 2, 2, 1, 1)
self.zotero_library_id = QtWidgets.QLineEdit(parent=self.page_2)
self.zotero_library_id.setObjectName("zotero_library_id")
self.gridLayout.addWidget(self.zotero_library_id, 1, 2, 1, 1)
self.label_4 = QtWidgets.QLabel(parent=self.page_2)
self.label_4.setObjectName("label_4")
self.gridLayout.addWidget(self.label_4, 2, 0, 1, 1)
self.label_3 = QtWidgets.QLabel(parent=self.page_2)
self.label_3.setObjectName("label_3")
self.gridLayout.addWidget(self.label_3, 1, 0, 1, 1)
self.zotero_api_key = QtWidgets.QLineEdit(parent=self.page_2)
self.zotero_api_key.setInputMethodHints(
QtCore.Qt.InputMethodHint.ImhHiddenText
| QtCore.Qt.InputMethodHint.ImhSensitiveData
)
self.zotero_api_key.setObjectName("zotero_api_key")
self.gridLayout.addWidget(self.zotero_api_key, 0, 2, 1, 1)
self.label_2 = QtWidgets.QLabel(parent=self.page_2)
self.label_2.setObjectName("label_2")
self.gridLayout.addWidget(self.label_2, 0, 0, 1, 1)
self.toggle_api_visibility = QtWidgets.QToolButton(parent=self.page_2)
self.toggle_api_visibility.setText("")
self.toggle_api_visibility.setObjectName("toggle_api_visibility")
self.gridLayout.addWidget(self.toggle_api_visibility, 0, 3, 1, 1)
spacerItem1 = QtWidgets.QSpacerItem(
20,
40,
QtWidgets.QSizePolicy.Policy.Minimum,
QtWidgets.QSizePolicy.Policy.Expanding,
)
self.gridLayout.addItem(spacerItem1, 3, 2, 1, 1)
self.toolBox.addItem(self.page_2, "")
self.page_3 = QtWidgets.QWidget()
self.page_3.setGeometry(QtCore.QRect(0, 0, 633, 511))
self.page_3.setObjectName("page_3")
self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.page_3)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.email_settings = QtWidgets.QTabWidget(parent=self.page_3)
self.email_settings.setObjectName("email_settings")
self.email_settingsPage1_2 = QtWidgets.QWidget()
self.email_settingsPage1_2.setObjectName("email_settingsPage1_2")
self.horizontalLayout_4 = QtWidgets.QHBoxLayout(self.email_settingsPage1_2)
self.horizontalLayout_4.setObjectName("horizontalLayout_4")
self.gridLayout_2 = QtWidgets.QGridLayout()
self.gridLayout_2.setObjectName("gridLayout_2")
self.smtp_address = QtWidgets.QLineEdit(parent=self.email_settingsPage1_2)
self.smtp_address.setClearButtonEnabled(True)
self.smtp_address.setObjectName("smtp_address")
self.gridLayout_2.addWidget(self.smtp_address, 0, 1, 1, 1)
self.label_8 = QtWidgets.QLabel(parent=self.email_settingsPage1_2)
self.label_8.setObjectName("label_8")
self.gridLayout_2.addWidget(self.label_8, 3, 0, 1, 1)
self.use_username_smtp_login = QtWidgets.QCheckBox(
parent=self.email_settingsPage1_2
)
self.use_username_smtp_login.setTristate(False)
self.use_username_smtp_login.setObjectName("use_username_smtp_login")
self.gridLayout_2.addWidget(self.use_username_smtp_login, 4, 1, 1, 1)
self.mail_username = QtWidgets.QLineEdit(parent=self.email_settingsPage1_2)
self.mail_username.setClearButtonEnabled(True)
self.mail_username.setObjectName("mail_username")
self.gridLayout_2.addWidget(self.mail_username, 3, 1, 1, 1)
self.smtp_port = QtWidgets.QLineEdit(parent=self.email_settingsPage1_2)
self.smtp_port.setInputMethodHints(
QtCore.Qt.InputMethodHint.ImhDigitsOnly
| QtCore.Qt.InputMethodHint.ImhPreferNumbers
)
self.smtp_port.setClearButtonEnabled(True)
self.smtp_port.setObjectName("smtp_port")
self.gridLayout_2.addWidget(self.smtp_port, 1, 1, 1, 1)
self.label_10 = QtWidgets.QLabel(parent=self.email_settingsPage1_2)
self.label_10.setObjectName("label_10")
self.gridLayout_2.addWidget(self.label_10, 5, 0, 1, 1)
self.label_7 = QtWidgets.QLabel(parent=self.email_settingsPage1_2)
self.label_7.setObjectName("label_7")
self.gridLayout_2.addWidget(self.label_7, 2, 0, 1, 1)
self.label_9 = QtWidgets.QLabel(parent=self.email_settingsPage1_2)
self.label_9.setText("")
self.label_9.setObjectName("label_9")
self.gridLayout_2.addWidget(self.label_9, 6, 0, 1, 1)
self.sender_email = QtWidgets.QLineEdit(parent=self.email_settingsPage1_2)
self.sender_email.setInputMethodHints(
QtCore.Qt.InputMethodHint.ImhEmailCharactersOnly
)
self.sender_email.setClearButtonEnabled(True)
self.sender_email.setObjectName("sender_email")
self.gridLayout_2.addWidget(self.sender_email, 2, 1, 1, 1)
self.label = QtWidgets.QLabel(parent=self.email_settingsPage1_2)
self.label.setObjectName("label")
self.gridLayout_2.addWidget(self.label, 0, 0, 1, 1)
self.password = QtWidgets.QLineEdit(parent=self.email_settingsPage1_2)
self.password.setInputMethodHints(
QtCore.Qt.InputMethodHint.ImhHiddenText
| QtCore.Qt.InputMethodHint.ImhSensitiveData
)
self.password.setClearButtonEnabled(True)
self.password.setObjectName("password")
self.gridLayout_2.addWidget(self.password, 5, 1, 1, 1)
self.label_6 = QtWidgets.QLabel(parent=self.email_settingsPage1_2)
self.label_6.setObjectName("label_6")
self.gridLayout_2.addWidget(self.label_6, 1, 0, 1, 1)
self.togglePassword = QtWidgets.QPushButton(parent=self.email_settingsPage1_2)
self.togglePassword.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
self.togglePassword.setText("")
self.togglePassword.setObjectName("togglePassword")
self.gridLayout_2.addWidget(self.togglePassword, 5, 2, 1, 1)
self.horizontalLayout_4.addLayout(self.gridLayout_2)
self.email_settings.addTab(self.email_settingsPage1_2, "")
self.email_settingsPage2_2 = QtWidgets.QWidget()
self.email_settingsPage2_2.setObjectName("email_settingsPage2_2")
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.email_settingsPage2_2)
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.verticalLayout_2 = QtWidgets.QVBoxLayout()
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
spacerItem2 = QtWidgets.QSpacerItem(
40,
20,
QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Minimum,
)
self.horizontalLayout_3.addItem(spacerItem2)
self.bold = QtWidgets.QPushButton(parent=self.email_settingsPage2_2)
self.bold.setCheckable(True)
self.bold.setObjectName("bold")
self.horizontalLayout_3.addWidget(self.bold)
self.italic = QtWidgets.QPushButton(parent=self.email_settingsPage2_2)
self.italic.setCheckable(True)
self.italic.setObjectName("italic")
self.horizontalLayout_3.addWidget(self.italic)
self.underscore = QtWidgets.QPushButton(parent=self.email_settingsPage2_2)
self.underscore.setCheckable(True)
self.underscore.setObjectName("underscore")
self.horizontalLayout_3.addWidget(self.underscore)
spacerItem3 = QtWidgets.QSpacerItem(
40,
20,
QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Minimum,
)
self.horizontalLayout_3.addItem(spacerItem3)
self.verticalLayout_2.addLayout(self.horizontalLayout_3)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.fontComboBox = QtWidgets.QFontComboBox(parent=self.email_settingsPage2_2)
self.fontComboBox.setObjectName("fontComboBox")
self.horizontalLayout.addWidget(self.fontComboBox)
self.font_size = QtWidgets.QComboBox(parent=self.email_settingsPage2_2)
self.font_size.setObjectName("font_size")
self.font_size.addItem("")
self.font_size.addItem("")
self.font_size.addItem("")
self.font_size.addItem("")
self.font_size.addItem("")
self.font_size.addItem("")
self.font_size.addItem("")
self.font_size.addItem("")
self.font_size.addItem("")
self.font_size.addItem("")
self.font_size.addItem("")
self.font_size.addItem("")
self.font_size.addItem("")
self.font_size.addItem("")
self.font_size.addItem("")
self.horizontalLayout.addWidget(self.font_size)
spacerItem4 = QtWidgets.QSpacerItem(
40,
20,
QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Minimum,
)
self.horizontalLayout.addItem(spacerItem4)
self.verticalLayout_2.addLayout(self.horizontalLayout)
self.verticalLayout_3.addLayout(self.verticalLayout_2)
self.editSignature = QtWidgets.QTextEdit(parent=self.email_settingsPage2_2)
self.editSignature.setObjectName("editSignature")
self.verticalLayout_3.addWidget(self.editSignature)
self.debug = QtWidgets.QPushButton(parent=self.email_settingsPage2_2)
self.debug.setObjectName("debug")
self.verticalLayout_3.addWidget(self.debug)
self.email_settings.addTab(self.email_settingsPage2_2, "")
self.horizontalLayout_2.addWidget(self.email_settings)
self.toolBox.addItem(self.page_3, "")
self.page_4 = QtWidgets.QWidget()
self.page_4.setGeometry(QtCore.QRect(0, 0, 633, 511))
self.page_4.setObjectName("page_4")
self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.page_4)
self.verticalLayout_4.setObjectName("verticalLayout_4")
self.groupBox = QtWidgets.QGroupBox(parent=self.page_4)
font = QtGui.QFont()
font.setPointSize(12)
font.setBold(True)
self.groupBox.setFont(font)
self.groupBox.setObjectName("groupBox")
self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.groupBox)
self.verticalLayout_5.setObjectName("verticalLayout_5")
self.scrollArea_3 = QtWidgets.QScrollArea(parent=self.groupBox)
self.scrollArea_3.setWidgetResizable(True)
self.scrollArea_3.setObjectName("scrollArea_3")
self.scrollAreaWidgetContents_3 = QtWidgets.QWidget()
self.scrollAreaWidgetContents_3.setGeometry(QtCore.QRect(0, 0, 593, 201))
self.scrollAreaWidgetContents_3.setObjectName("scrollAreaWidgetContents_3")
self.verticalLayout_7 = QtWidgets.QVBoxLayout(self.scrollAreaWidgetContents_3)
self.verticalLayout_7.setObjectName("verticalLayout_7")
self.gridLayout_4 = QtWidgets.QGridLayout()
self.gridLayout_4.setObjectName("gridLayout_4")
self.verticalLayout_7.addLayout(self.gridLayout_4)
self.scrollArea_3.setWidget(self.scrollAreaWidgetContents_3)
self.verticalLayout_5.addWidget(self.scrollArea_3)
self.verticalLayout_4.addWidget(self.groupBox)
self.scrollArea_2 = QtWidgets.QScrollArea(parent=self.page_4)
self.scrollArea_2.setWidgetResizable(True)
self.scrollArea_2.setObjectName("scrollArea_2")
self.scrollAreaWidgetContents_2 = QtWidgets.QWidget()
self.scrollAreaWidgetContents_2.setGeometry(QtCore.QRect(0, 0, 613, 241))
self.scrollAreaWidgetContents_2.setObjectName("scrollAreaWidgetContents_2")
self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.scrollAreaWidgetContents_2)
self.verticalLayout_6.setObjectName("verticalLayout_6")
self.vertical_icons = QtWidgets.QVBoxLayout()
self.vertical_icons.setObjectName("vertical_icons")
self.verticalLayout_6.addLayout(self.vertical_icons)
self.scrollArea_2.setWidget(self.scrollAreaWidgetContents_2)
self.verticalLayout_4.addWidget(self.scrollArea_2)
self.toolBox.addItem(self.page_4, "")
self.verticalLayout.addWidget(self.toolBox)
self.buttonBox = QtWidgets.QDialogButtonBox(parent=Dialog)
self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal)
self.buttonBox.setStandardButtons(
QtWidgets.QDialogButtonBox.StandardButton.Cancel
| QtWidgets.QDialogButtonBox.StandardButton.Ok
)
self.buttonBox.setObjectName("buttonBox")
self.verticalLayout.addWidget(self.buttonBox)
self.label_5.setBuddy(self.db_name)
self.label_12.setBuddy(self.save_path)
self.label_11.setBuddy(self.db_path)
self.retranslateUi(Dialog)
self.toolBox.setCurrentIndex(3)
self.email_settings.setCurrentIndex(0)
self.buttonBox.accepted.connect(Dialog.accept) # type: ignore
self.buttonBox.rejected.connect(Dialog.reject) # type: ignore
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
self.db_name.setText(_translate("Dialog", "sap.db"))
self.label_5.setToolTip(
_translate(
"Dialog",
'<html><head/><body><p>Name der Datenbank, welche verwendet werden soll. <span style=" font-weight:600;">Muss</span> auf .db enden</p></body></html>',
)
)
self.label_5.setText(_translate("Dialog", "Datenbankname"))
self.label_12.setToolTip(
_translate(
"Dialog",
"Pfad, an dem heruntergeladene Dateien gespeichert werden sollen",
)
)
self.label_12.setText(_translate("Dialog", "Temporäre Dateien"))
self.label_11.setText(_translate("Dialog", "Datenbankpfad"))
self.tb_set_save_path.setText(_translate("Dialog", "..."))
self.tb_select_db.setText(_translate("Dialog", "..."))
self.toolBox.setItemText(
self.toolBox.indexOf(self.page_1), _translate("Dialog", "Datenbank")
)
self.label_4.setText(_translate("Dialog", "Bibliothekstyp"))
self.label_3.setText(_translate("Dialog", "Bibliotheks-ID"))
self.label_2.setText(_translate("Dialog", "API Key"))
self.toolBox.setItemText(
self.toolBox.indexOf(self.page_2), _translate("Dialog", "Zotero")
)
self.label_8.setText(_translate("Dialog", "Nutzername"))
self.use_username_smtp_login.setStatusTip(
_translate(
"Dialog",
"Anklicken, wenn Nutzername benötigt wird, um sich beim Server anzumelden",
)
)
self.use_username_smtp_login.setText(
_translate("Dialog", "Nutzername zum\n Anmelden verwenden")
)
self.mail_username.setStatusTip(
_translate("Dialog", "Kürzel, von der Hochschule vergeben, bsp: Aky547")
)
self.label_10.setText(_translate("Dialog", "Passwort"))
self.label_7.setText(_translate("Dialog", "Sender-eMail"))
self.label.setText(_translate("Dialog", "SMTP-Server"))
self.label_6.setText(_translate("Dialog", "Port"))
self.email_settings.setTabText(
self.email_settings.indexOf(self.email_settingsPage1_2),
_translate("Dialog", "Allgemeines"),
)
self.bold.setText(_translate("Dialog", "Fett"))
self.italic.setText(_translate("Dialog", "Kursiv"))
self.underscore.setText(_translate("Dialog", "Unterstrichen"))
self.font_size.setItemText(0, _translate("Dialog", "8"))
self.font_size.setItemText(1, _translate("Dialog", "9"))
self.font_size.setItemText(2, _translate("Dialog", "11"))
self.font_size.setItemText(3, _translate("Dialog", "12"))
self.font_size.setItemText(4, _translate("Dialog", "14"))
self.font_size.setItemText(5, _translate("Dialog", "16"))
self.font_size.setItemText(6, _translate("Dialog", "18"))
self.font_size.setItemText(7, _translate("Dialog", "20"))
self.font_size.setItemText(8, _translate("Dialog", "22"))
self.font_size.setItemText(9, _translate("Dialog", "24"))
self.font_size.setItemText(10, _translate("Dialog", "26"))
self.font_size.setItemText(11, _translate("Dialog", "28"))
self.font_size.setItemText(12, _translate("Dialog", "36"))
self.font_size.setItemText(13, _translate("Dialog", "48"))
self.font_size.setItemText(14, _translate("Dialog", "72"))
self.debug.setText(_translate("Dialog", "Debug"))
self.email_settings.setTabText(
self.email_settings.indexOf(self.email_settingsPage2_2),
_translate("Dialog", "Signatur"),
)
self.toolBox.setItemText(
self.toolBox.indexOf(self.page_3), _translate("Dialog", "e-Mail")
)
self.groupBox.setTitle(_translate("Dialog", "Farben"))
self.toolBox.setItemText(
self.toolBox.indexOf(self.page_4), _translate("Dialog", "Icons")
)

View File

@@ -1,2 +1 @@
from .Ui_mail_preview import Ui_eMailPreview as MailPreviewDialog
from .Ui_newMailTemplateDesigner import Ui_Dialog as NewMailTemplateDesignerDialog
from .newMailTemplateDesigner_ui import Ui_Dialog as NewMailTemplateDesignerDialog

View File

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

View File

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

View File

@@ -1,21 +1,19 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\apparat_extend.ui'
#
# Created by: PyQt6 UI code generator 6.7.1
# Created by: PySide6 UI code generator 6.8.0
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(388, 103)
sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed
)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(Dialog.sizePolicy().hasHeightForWidth())
@@ -25,16 +23,11 @@ class Ui_Dialog(object):
self.buttonBox = QtWidgets.QDialogButtonBox(parent=Dialog)
self.buttonBox.setGeometry(QtCore.QRect(290, 30, 81, 241))
self.buttonBox.setOrientation(QtCore.Qt.Orientation.Vertical)
self.buttonBox.setStandardButtons(
QtWidgets.QDialogButtonBox.StandardButton.Abort
| QtWidgets.QDialogButtonBox.StandardButton.Save
)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.StandardButton.Abort|QtWidgets.QDialogButtonBox.StandardButton.Save)
self.buttonBox.setObjectName("buttonBox")
self.label = QtWidgets.QLabel(parent=Dialog)
self.label.setGeometry(QtCore.QRect(10, 0, 281, 31))
sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed
)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth())
@@ -67,16 +60,14 @@ class Ui_Dialog(object):
self.dauerapp.setObjectName("dauerapp")
self.retranslateUi(Dialog)
self.buttonBox.accepted.connect(Dialog.accept) # type: ignore
self.buttonBox.rejected.connect(Dialog.reject) # type: ignore
self.buttonBox.accepted.connect(Dialog.accept) # type: ignore
self.buttonBox.rejected.connect(Dialog.reject) # type: ignore
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
self.label.setText(
_translate("Dialog", "Bis wann soll der Apparat verlängert werden?")
)
self.label.setText(_translate("Dialog", "Bis wann soll der Apparat verlängert werden?"))
self.rad_sommer.setText(_translate("Dialog", "Sommer"))
self.rad_winter.setText(_translate("Dialog", "Winter"))
self.sem_year.setPlaceholderText(_translate("Dialog", "2023"))

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\confirm_extend.ui'
#
# Created by: PyQt6 UI code generator 6.7.1
# Created by: PySide6 UI code generator 6.8.0
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt6 import QtCore, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_extend_confirm(object):
@@ -20,16 +20,13 @@ class Ui_extend_confirm(object):
self.horizontalLayout.addWidget(self.textEdit)
self.buttonBox = QtWidgets.QDialogButtonBox(parent=extend_confirm)
self.buttonBox.setOrientation(QtCore.Qt.Orientation.Vertical)
self.buttonBox.setStandardButtons(
QtWidgets.QDialogButtonBox.StandardButton.Cancel
| QtWidgets.QDialogButtonBox.StandardButton.Ok
)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.StandardButton.Cancel|QtWidgets.QDialogButtonBox.StandardButton.Ok)
self.buttonBox.setObjectName("buttonBox")
self.horizontalLayout.addWidget(self.buttonBox)
self.retranslateUi(extend_confirm)
self.buttonBox.accepted.connect(extend_confirm.accept) # type: ignore
self.buttonBox.rejected.connect(extend_confirm.reject) # type: ignore
self.buttonBox.accepted.connect(extend_confirm.accept) # type: ignore
self.buttonBox.rejected.connect(extend_confirm.reject) # type: ignore
QtCore.QMetaObject.connectSlotsByName(extend_confirm)
def retranslateUi(self, extend_confirm):

View File

@@ -0,0 +1,138 @@
<?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>1001</width>
<height>649</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Medium suchen</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit">
<property name="placeholderText">
<string>Titel/Signatursuche</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTableWidget" name="tableWidget">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<column>
<property name="text">
<string/>
</property>
</column>
<column>
<property name="text">
<string>Apparat</string>
</property>
</column>
<column>
<property name="text">
<string>Signatur</string>
</property>
</column>
<column>
<property name="text">
<string>Titel</string>
</property>
</column>
<column>
<property name="text">
<string>Auflage</string>
</property>
</column>
<column>
<property name="text">
<string>ISBN</string>
</property>
</column>
<column>
<property name="text">
<string>ID</string>
</property>
</column>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="reset_btn">
<property name="text">
<string>Zurücksetzen</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="delete_btn">
<property name="text">
<string>Löschen</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cancel_btn">
<property name="text">
<string>Abbrechen</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,122 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'deletedialog.ui'
##
## Created by: Qt User Interface Compiler version 6.9.2
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
from PySide6.QtCore import (QCoreApplication, 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, QHeaderView,
QLabel, QLineEdit, QPushButton, QSizePolicy,
QSpacerItem, QTableWidget, QTableWidgetItem, QVBoxLayout,
QWidget)
class Ui_Dialog(object):
def setupUi(self, Dialog):
if not Dialog.objectName():
Dialog.setObjectName(u"Dialog")
Dialog.resize(1001, 649)
self.verticalLayout = QVBoxLayout(Dialog)
self.verticalLayout.setObjectName(u"verticalLayout")
self.horizontalLayout = QHBoxLayout()
self.horizontalLayout.setObjectName(u"horizontalLayout")
self.label = QLabel(Dialog)
self.label.setObjectName(u"label")
self.horizontalLayout.addWidget(self.label)
self.lineEdit = QLineEdit(Dialog)
self.lineEdit.setObjectName(u"lineEdit")
self.horizontalLayout.addWidget(self.lineEdit)
self.verticalLayout.addLayout(self.horizontalLayout)
self.tableWidget = QTableWidget(Dialog)
if (self.tableWidget.columnCount() < 7):
self.tableWidget.setColumnCount(7)
__qtablewidgetitem = QTableWidgetItem()
self.tableWidget.setHorizontalHeaderItem(0, __qtablewidgetitem)
__qtablewidgetitem1 = QTableWidgetItem()
self.tableWidget.setHorizontalHeaderItem(1, __qtablewidgetitem1)
__qtablewidgetitem2 = QTableWidgetItem()
self.tableWidget.setHorizontalHeaderItem(2, __qtablewidgetitem2)
__qtablewidgetitem3 = QTableWidgetItem()
self.tableWidget.setHorizontalHeaderItem(3, __qtablewidgetitem3)
__qtablewidgetitem4 = QTableWidgetItem()
self.tableWidget.setHorizontalHeaderItem(4, __qtablewidgetitem4)
__qtablewidgetitem5 = QTableWidgetItem()
self.tableWidget.setHorizontalHeaderItem(5, __qtablewidgetitem5)
__qtablewidgetitem6 = QTableWidgetItem()
self.tableWidget.setHorizontalHeaderItem(6, __qtablewidgetitem6)
self.tableWidget.setObjectName(u"tableWidget")
self.tableWidget.setAlternatingRowColors(True)
self.tableWidget.horizontalHeader().setStretchLastSection(True)
self.verticalLayout.addWidget(self.tableWidget)
self.horizontalLayout_2 = QHBoxLayout()
self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
self.horizontalSpacer_2 = QSpacerItem(20, 20, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum)
self.horizontalLayout_2.addItem(self.horizontalSpacer_2)
self.reset_btn = QPushButton(Dialog)
self.reset_btn.setObjectName(u"reset_btn")
self.horizontalLayout_2.addWidget(self.reset_btn)
self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
self.horizontalLayout_2.addItem(self.horizontalSpacer)
self.delete_btn = QPushButton(Dialog)
self.delete_btn.setObjectName(u"delete_btn")
self.horizontalLayout_2.addWidget(self.delete_btn)
self.cancel_btn = QPushButton(Dialog)
self.cancel_btn.setObjectName(u"cancel_btn")
self.horizontalLayout_2.addWidget(self.cancel_btn)
self.verticalLayout.addLayout(self.horizontalLayout_2)
self.retranslateUi(Dialog)
QMetaObject.connectSlotsByName(Dialog)
# setupUi
def retranslateUi(self, Dialog):
Dialog.setWindowTitle(QCoreApplication.translate("Dialog", u"Dialog", None))
self.label.setText(QCoreApplication.translate("Dialog", u"Medium suchen", None))
self.lineEdit.setPlaceholderText(QCoreApplication.translate("Dialog", u"Titel/Signatursuche", None))
___qtablewidgetitem = self.tableWidget.horizontalHeaderItem(1)
___qtablewidgetitem.setText(QCoreApplication.translate("Dialog", u"Apparat", None));
___qtablewidgetitem1 = self.tableWidget.horizontalHeaderItem(2)
___qtablewidgetitem1.setText(QCoreApplication.translate("Dialog", u"Signatur", None));
___qtablewidgetitem2 = self.tableWidget.horizontalHeaderItem(3)
___qtablewidgetitem2.setText(QCoreApplication.translate("Dialog", u"Titel", None));
___qtablewidgetitem3 = self.tableWidget.horizontalHeaderItem(4)
___qtablewidgetitem3.setText(QCoreApplication.translate("Dialog", u"Auflage", None));
___qtablewidgetitem4 = self.tableWidget.horizontalHeaderItem(5)
___qtablewidgetitem4.setText(QCoreApplication.translate("Dialog", u"ISBN", None));
___qtablewidgetitem5 = self.tableWidget.horizontalHeaderItem(6)
___qtablewidgetitem5.setText(QCoreApplication.translate("Dialog", u"ID", None));
self.reset_btn.setText(QCoreApplication.translate("Dialog", u"Zur\u00fccksetzen", None))
self.delete_btn.setText(QCoreApplication.translate("Dialog", u"L\u00f6schen", None))
self.cancel_btn.setText(QCoreApplication.translate("Dialog", u"Abbrechen", None))
# retranslateUi

View File

@@ -0,0 +1,232 @@
<?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>725</width>
<height>623</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QToolBox" name="toolBox">
<property name="font">
<font>
<bold>true</bold>
</font>
</property>
<property name="currentIndex">
<number>1</number>
</property>
<widget class="QWidget" name="page">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>707</width>
<height>545</height>
</rect>
</property>
<attribute name="label">
<string>Semesterapparatsübersicht</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>Mit dem Klick auf Okay wird eine Übersicht aller aktiven Semesterapparate erstellt und an den FollowME Drucker gesendet. Es kann bis zu 5 Minuten dauern, bis das Dokument im Drucker angezeigt wird.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="margin">
<number>5</number>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Dokument erstellen und drucken</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_2">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>707</width>
<height>545</height>
</rect>
</property>
<attribute name="label">
<string>Semesterapparatsschilder</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_2">
<property name="font">
<font>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>Hier kann das Dokument für die Semesterapparatsschilder erstellt werden. Hierfür müssen die entsprechenden Apparate ausgewählt werden. Mithilfe dieser wird das Dokument erstellt.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QPushButton" name="pushButton_2">
<property name="text">
<string>Dokument erstellen und drucken</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_load_current_apparats">
<property name="text">
<string>Aktuelle Apparate laden</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="expertMode">
<property name="text">
<string>Expertenmodus</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QTextBrowser" name="textBrowser">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="readOnly">
<bool>false</bool>
</property>
<property name="html">
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
hr { height: 1px; border-width: 0; }
li.unchecked::marker { content: &quot;\2610&quot;; }
li.checked::marker { content: &quot;\2612&quot;; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Segoe UI'; font-size:9pt; font-weight:700; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;SELECT&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt; prof.lname || ' (' || semesterapparat.name || ')' AS formatted_result&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;from&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt; semesterapparat&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt; INNER JOIN prof ON semesterapparat.prof_id = prof.id&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;WHERE&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt; (erstellsemester = 'SoSe 25'&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt; OR erstellsemester = 'WiSe 24/25')&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt; and semesterapparat.deletion_status = 0&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="manualCheck">
<property name="text">
<string>Anfragen und anzeigen</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QTableWidget" name="tableWidget">
<property name="font">
<font>
<bold>false</bold>
</font>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<attribute name="horizontalHeaderShowSortIndicator" stdset="0">
<bool>true</bool>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<column>
<property name="text">
<string/>
</property>
<property name="textAlignment">
<set>AlignLeading|AlignVCenter</set>
</property>
</column>
<column>
<property name="text">
<string>Name</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,142 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\documentprint.ui'
#
# Created by: PySide6 UI code generator 6.9.0
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(725, 623)
self.verticalLayout = QtWidgets.QVBoxLayout(Dialog)
self.verticalLayout.setObjectName("verticalLayout")
self.toolBox = QtWidgets.QToolBox(parent=Dialog)
font = QtGui.QFont()
font.setBold(True)
self.toolBox.setFont(font)
self.toolBox.setObjectName("toolBox")
self.page = QtWidgets.QWidget()
self.page.setGeometry(QtCore.QRect(0, 0, 707, 545))
self.page.setObjectName("page")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.page)
self.horizontalLayout.setObjectName("horizontalLayout")
self.label = QtWidgets.QLabel(parent=self.page)
font = QtGui.QFont()
font.setBold(False)
self.label.setFont(font)
self.label.setWordWrap(True)
self.label.setObjectName("label")
self.horizontalLayout.addWidget(self.label)
self.pushButton = QtWidgets.QPushButton(parent=self.page)
self.pushButton.setObjectName("pushButton")
self.horizontalLayout.addWidget(self.pushButton)
self.toolBox.addItem(self.page, "")
self.page_2 = QtWidgets.QWidget()
self.page_2.setGeometry(QtCore.QRect(0, 0, 707, 545))
self.page_2.setObjectName("page_2")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.page_2)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.label_2 = QtWidgets.QLabel(parent=self.page_2)
font = QtGui.QFont()
font.setBold(False)
self.label_2.setFont(font)
self.label_2.setWordWrap(True)
self.label_2.setObjectName("label_2")
self.horizontalLayout_2.addWidget(self.label_2)
self.verticalLayout_3 = QtWidgets.QVBoxLayout()
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.pushButton_2 = QtWidgets.QPushButton(parent=self.page_2)
self.pushButton_2.setObjectName("pushButton_2")
self.verticalLayout_3.addWidget(self.pushButton_2)
self.btn_load_current_apparats = QtWidgets.QPushButton(parent=self.page_2)
self.btn_load_current_apparats.setObjectName("btn_load_current_apparats")
self.verticalLayout_3.addWidget(self.btn_load_current_apparats)
self.expertMode = QtWidgets.QCheckBox(parent=self.page_2)
self.expertMode.setObjectName("expertMode")
self.verticalLayout_3.addWidget(self.expertMode)
self.horizontalLayout_2.addLayout(self.verticalLayout_3)
self.verticalLayout_2.addLayout(self.horizontalLayout_2)
self.frame = QtWidgets.QFrame(parent=self.page_2)
self.frame.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel)
self.frame.setFrameShadow(QtWidgets.QFrame.Shadow.Raised)
self.frame.setObjectName("frame")
self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.frame)
self.horizontalLayout_3.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout_3.setSpacing(0)
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
self.verticalLayout_4 = QtWidgets.QVBoxLayout()
self.verticalLayout_4.setObjectName("verticalLayout_4")
self.textBrowser = QtWidgets.QTextBrowser(parent=self.frame)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.textBrowser.sizePolicy().hasHeightForWidth())
self.textBrowser.setSizePolicy(sizePolicy)
self.textBrowser.setReadOnly(False)
self.textBrowser.setObjectName("textBrowser")
self.verticalLayout_4.addWidget(self.textBrowser)
self.manualCheck = QtWidgets.QPushButton(parent=self.frame)
self.manualCheck.setObjectName("manualCheck")
self.verticalLayout_4.addWidget(self.manualCheck)
self.horizontalLayout_3.addLayout(self.verticalLayout_4)
self.verticalLayout_2.addWidget(self.frame)
self.tableWidget = QtWidgets.QTableWidget(parent=self.page_2)
font = QtGui.QFont()
font.setBold(False)
self.tableWidget.setFont(font)
self.tableWidget.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)
self.tableWidget.setObjectName("tableWidget")
self.tableWidget.setColumnCount(2)
self.tableWidget.setRowCount(0)
item = QtWidgets.QTableWidgetItem()
item.setTextAlignment(QtCore.Qt.AlignmentFlag.AlignLeading|QtCore.Qt.AlignmentFlag.AlignVCenter)
self.tableWidget.setHorizontalHeaderItem(0, item)
item = QtWidgets.QTableWidgetItem()
self.tableWidget.setHorizontalHeaderItem(1, item)
self.tableWidget.horizontalHeader().setSortIndicatorShown(True)
self.tableWidget.horizontalHeader().setStretchLastSection(True)
self.verticalLayout_2.addWidget(self.tableWidget)
self.toolBox.addItem(self.page_2, "")
self.verticalLayout.addWidget(self.toolBox)
self.retranslateUi(Dialog)
self.toolBox.setCurrentIndex(1)
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
self.label.setText(_translate("Dialog", "Mit dem Klick auf Okay wird eine Übersicht aller aktiven Semesterapparate erstellt und an den FollowME Drucker gesendet. Es kann bis zu 5 Minuten dauern, bis das Dokument im Drucker angezeigt wird."))
self.pushButton.setText(_translate("Dialog", "Dokument erstellen und drucken"))
self.toolBox.setItemText(self.toolBox.indexOf(self.page), _translate("Dialog", "Semesterapparatsübersicht"))
self.label_2.setText(_translate("Dialog", "Hier kann das Dokument für die Semesterapparatsschilder erstellt werden. Hierfür müssen die entsprechenden Apparate ausgewählt werden. Mithilfe dieser wird das Dokument erstellt."))
self.pushButton_2.setText(_translate("Dialog", "Dokument erstellen und drucken"))
self.btn_load_current_apparats.setText(_translate("Dialog", "Aktuelle Apparate laden"))
self.expertMode.setText(_translate("Dialog", "Expertenmodus"))
self.textBrowser.setHtml(_translate("Dialog", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><meta charset=\"utf-8\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"hr { height: 1px; border-width: 0; }\n"
"li.unchecked::marker { content: \"\\2610\"; }\n"
"li.checked::marker { content: \"\\2612\"; }\n"
"</style></head><body style=\" font-family:\'Segoe UI\'; font-size:9pt; font-weight:700; font-style:normal;\">\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">SELECT</p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"> prof.lname || \' (\' || semesterapparat.name || \')\' AS formatted_result</p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">from</p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"> semesterapparat</p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"> INNER JOIN prof ON semesterapparat.prof_id = prof.id</p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">WHERE</p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"> (erstellsemester = \'SoSe 25\'</p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"> OR erstellsemester = \'WiSe 24/25\')</p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"> and semesterapparat.deletion_status = 0</p></body></html>"))
self.manualCheck.setText(_translate("Dialog", "Anfragen und anzeigen"))
item = self.tableWidget.horizontalHeaderItem(1)
item.setText(_translate("Dialog", "Name"))
self.toolBox.setItemText(self.toolBox.indexOf(self.page_2), _translate("Dialog", "Semesterapparatsschilder"))

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\edit_bookdata.ui'
#
# Created by: PyQt6 UI code generator 6.6.1
# Created by: PySide6 UI code generator 6.8.0
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt6 import QtCore, QtGui, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):
@@ -16,18 +16,13 @@ class Ui_Dialog(object):
self.buttonBox = QtWidgets.QDialogButtonBox(parent=Dialog)
self.buttonBox.setGeometry(QtCore.QRect(260, 530, 161, 32))
self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal)
self.buttonBox.setStandardButtons(
QtWidgets.QDialogButtonBox.StandardButton.Cancel
| QtWidgets.QDialogButtonBox.StandardButton.Ok
)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.StandardButton.Cancel|QtWidgets.QDialogButtonBox.StandardButton.Ok)
self.buttonBox.setObjectName("buttonBox")
self.gridLayoutWidget = QtWidgets.QWidget(parent=Dialog)
self.gridLayoutWidget.setGeometry(QtCore.QRect(0, 0, 441, 531))
self.gridLayoutWidget.setObjectName("gridLayoutWidget")
self.gridLayout = QtWidgets.QGridLayout(self.gridLayoutWidget)
self.gridLayout.setSizeConstraint(
QtWidgets.QLayout.SizeConstraint.SetDefaultConstraint
)
self.gridLayout.setSizeConstraint(QtWidgets.QLayout.SizeConstraint.SetDefaultConstraint)
self.gridLayout.setContentsMargins(0, 0, 0, 0)
self.gridLayout.setObjectName("gridLayout")
self.label_10 = QtWidgets.QLabel(parent=self.gridLayoutWidget)
@@ -71,12 +66,7 @@ class Ui_Dialog(object):
self.label_2 = QtWidgets.QLabel(parent=self.gridLayoutWidget)
self.label_2.setObjectName("label_2")
self.gridLayout.addWidget(self.label_2, 1, 1, 1, 1)
spacerItem = QtWidgets.QSpacerItem(
5,
20,
QtWidgets.QSizePolicy.Policy.Fixed,
QtWidgets.QSizePolicy.Policy.Minimum,
)
spacerItem = QtWidgets.QSpacerItem(5, 20, QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Minimum)
self.gridLayout.addItem(spacerItem, 8, 0, 1, 1)
self.line_title = QtWidgets.QLineEdit(parent=self.gridLayoutWidget)
self.line_title.setObjectName("line_title")
@@ -107,8 +97,8 @@ class Ui_Dialog(object):
self.gridLayout.addWidget(self.line_publisher, 4, 2, 1, 1)
self.retranslateUi(Dialog)
self.buttonBox.accepted.connect(Dialog.accept) # type: ignore
self.buttonBox.rejected.connect(Dialog.reject) # type: ignore
self.buttonBox.accepted.connect(Dialog.accept) # type: ignore
self.buttonBox.rejected.connect(Dialog.reject) # type: ignore
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):

View File

@@ -1,12 +1,12 @@
# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\SemesterapparatsManager\src\ui\dialogs\dialog_sources\elsa_add_table_entry.ui'
#
# Created by: PyQt6 UI code generator 6.7.1
# Created by: PySide6 UI code generator 6.8.0
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt6 import QtCore, QtWidgets
from PySide6 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):
@@ -21,12 +21,7 @@ class Ui_Dialog(object):
self.groupBox.setObjectName("groupBox")
self.gridLayout_4 = QtWidgets.QGridLayout(self.groupBox)
self.gridLayout_4.setObjectName("gridLayout_4")
spacerItem = QtWidgets.QSpacerItem(
40,
20,
QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Minimum,
)
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
self.gridLayout_4.addItem(spacerItem, 0, 3, 1, 1)
self.btn_mono = QtWidgets.QRadioButton(parent=self.groupBox)
self.btn_mono.setChecked(False)
@@ -50,12 +45,7 @@ class Ui_Dialog(object):
self.btn_search = QtWidgets.QPushButton(parent=Dialog)
self.btn_search.setObjectName("btn_search")
self.horizontalLayout_2.addWidget(self.btn_search)
spacerItem1 = QtWidgets.QSpacerItem(
40,
20,
QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Minimum,
)
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
self.horizontalLayout_2.addItem(spacerItem1)
self.make_quote = QtWidgets.QPushButton(parent=Dialog)
self.make_quote.setObjectName("make_quote")
@@ -281,12 +271,7 @@ class Ui_Dialog(object):
self.label_32 = QtWidgets.QLabel(parent=self.page)
self.label_32.setObjectName("label_32")
self.gridLayout_5.addWidget(self.label_32, 0, 0, 1, 1)
spacerItem2 = QtWidgets.QSpacerItem(
20,
40,
QtWidgets.QSizePolicy.Policy.Minimum,
QtWidgets.QSizePolicy.Policy.Expanding,
)
spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
self.gridLayout_5.addItem(spacerItem2, 7, 0, 1, 1)
self.file_desc_edit = QtWidgets.QTextEdit(parent=self.page)
self.file_desc_edit.setReadOnly(True)
@@ -308,12 +293,7 @@ class Ui_Dialog(object):
self.gridLayout_5.addWidget(self.ilias_filename, 4, 0, 1, 1)
self.verticalLayout_2 = QtWidgets.QVBoxLayout()
self.verticalLayout_2.setObjectName("verticalLayout_2")
spacerItem3 = QtWidgets.QSpacerItem(
20,
40,
QtWidgets.QSizePolicy.Policy.Minimum,
QtWidgets.QSizePolicy.Policy.Expanding,
)
spacerItem3 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
self.verticalLayout_2.addItem(spacerItem3)
self.copy_filename = QtWidgets.QToolButton(parent=self.page)
self.copy_filename.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight)
@@ -324,22 +304,12 @@ class Ui_Dialog(object):
self.filename_edit_label.setText("")
self.filename_edit_label.setObjectName("filename_edit_label")
self.verticalLayout_2.addWidget(self.filename_edit_label)
spacerItem4 = QtWidgets.QSpacerItem(
20,
40,
QtWidgets.QSizePolicy.Policy.Minimum,
QtWidgets.QSizePolicy.Policy.Expanding,
)
spacerItem4 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
self.verticalLayout_2.addItem(spacerItem4)
self.gridLayout_5.addLayout(self.verticalLayout_2, 1, 1, 1, 1)
self.verticalLayout_3 = QtWidgets.QVBoxLayout()
self.verticalLayout_3.setObjectName("verticalLayout_3")
spacerItem5 = QtWidgets.QSpacerItem(
20,
40,
QtWidgets.QSizePolicy.Policy.Minimum,
QtWidgets.QSizePolicy.Policy.Expanding,
)
spacerItem5 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
self.verticalLayout_3.addItem(spacerItem5)
self.copy_ilias_filename = QtWidgets.QToolButton(parent=self.page)
self.copy_ilias_filename.setObjectName("copy_ilias_filename")
@@ -348,22 +318,12 @@ class Ui_Dialog(object):
self.ilias_filename_label.setText("")
self.ilias_filename_label.setObjectName("ilias_filename_label")
self.verticalLayout_3.addWidget(self.ilias_filename_label)
spacerItem6 = QtWidgets.QSpacerItem(
20,
40,
QtWidgets.QSizePolicy.Policy.Minimum,
QtWidgets.QSizePolicy.Policy.Expanding,
)
spacerItem6 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
self.verticalLayout_3.addItem(spacerItem6)
self.gridLayout_5.addLayout(self.verticalLayout_3, 4, 1, 1, 1)
self.verticalLayout_4 = QtWidgets.QVBoxLayout()
self.verticalLayout_4.setObjectName("verticalLayout_4")
spacerItem7 = QtWidgets.QSpacerItem(
20,
40,
QtWidgets.QSizePolicy.Policy.Minimum,
QtWidgets.QSizePolicy.Policy.Expanding,
)
spacerItem7 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
self.verticalLayout_4.addItem(spacerItem7)
self.copy_qoute = QtWidgets.QToolButton(parent=self.page)
self.copy_qoute.setObjectName("copy_qoute")
@@ -372,12 +332,7 @@ class Ui_Dialog(object):
self.file_desc_edit_label.setText("")
self.file_desc_edit_label.setObjectName("file_desc_edit_label")
self.verticalLayout_4.addWidget(self.file_desc_edit_label)
spacerItem8 = QtWidgets.QSpacerItem(
20,
40,
QtWidgets.QSizePolicy.Policy.Minimum,
QtWidgets.QSizePolicy.Policy.Expanding,
)
spacerItem8 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
self.verticalLayout_4.addItem(spacerItem8)
self.gridLayout_5.addLayout(self.verticalLayout_4, 6, 1, 1, 1)
self.stackedWidget.addWidget(self.page)
@@ -385,11 +340,7 @@ class Ui_Dialog(object):
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.buttonBox = QtWidgets.QDialogButtonBox(parent=Dialog)
self.buttonBox.setStandardButtons(
QtWidgets.QDialogButtonBox.StandardButton.Cancel
| QtWidgets.QDialogButtonBox.StandardButton.Discard
| QtWidgets.QDialogButtonBox.StandardButton.Ok
)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.StandardButton.Cancel|QtWidgets.QDialogButtonBox.StandardButton.Discard|QtWidgets.QDialogButtonBox.StandardButton.Ok)
self.buttonBox.setObjectName("buttonBox")
self.horizontalLayout.addWidget(self.buttonBox)
self.retryButton = QtWidgets.QPushButton(parent=Dialog)
@@ -410,14 +361,11 @@ class Ui_Dialog(object):
self.btn_hg.setText(_translate("Dialog", "Herausgeberwerk"))
self.label_2.setText(_translate("Dialog", "Identifikator"))
self.btn_search.setText(_translate("Dialog", "Suchen"))
self.make_quote.setToolTip(
_translate("Dialog", "Zuerst die Seitenzahl anpassen")
)
self.make_quote.setToolTip(_translate("Dialog", "Zuerst die Seitenzahl anpassen"))
self.make_quote.setText(_translate("Dialog", "Zitat erstellen"))
self.label.setText(_translate("Dialog", "Autor(en)\n Nachname, Vorname"))
self.book_author.setToolTip(
_translate("Dialog", "Bei mehreren Autoren mit ; trennen")
)
self.label.setText(_translate("Dialog", "Autor(en)\n"
" Nachname, Vorname"))
self.book_author.setToolTip(_translate("Dialog", "Bei mehreren Autoren mit ; trennen"))
self.label_3.setText(_translate("Dialog", "Jahr"))
self.label_4.setText(_translate("Dialog", "Auflage"))
self.label_5.setText(_translate("Dialog", "Titel"))
@@ -425,13 +373,9 @@ class Ui_Dialog(object):
self.label_7.setText(_translate("Dialog", "Verlag"))
self.label_8.setText(_translate("Dialog", "Signatur"))
self.label_9.setText(_translate("Dialog", "Seiten"))
self.book_pages.setPlaceholderText(
_translate("Dialog", "Seitenanzahl des Mediums, zum zitieren ändern!")
)
self.book_pages.setPlaceholderText(_translate("Dialog", "Seitenanzahl des Mediums, zum zitieren ändern!"))
self.label_29.setText(_translate("Dialog", "ISBN"))
self.hg_editor.setToolTip(
_translate("Dialog", "Bei mehreren Autoren mit ; trennen")
)
self.hg_editor.setToolTip(_translate("Dialog", "Bei mehreren Autoren mit ; trennen"))
self.label_26.setText(_translate("Dialog", "Verlag"))
self.label_20.setText(_translate("Dialog", "Jahr"))
self.label_24.setText(_translate("Dialog", "Beitragstitel"))
@@ -439,16 +383,15 @@ class Ui_Dialog(object):
self.label_28.setText(_translate("Dialog", "Signatur"))
self.label_23.setText(_translate("Dialog", "Titel des Werkes"))
self.label_21.setText(_translate("Dialog", "Auflage"))
self.label_19.setText(_translate("Dialog", "Autor(en)\nNachname, Vorname"))
self.label_19.setText(_translate("Dialog", "Autor(en)\n"
"Nachname, Vorname"))
self.label_30.setText(_translate("Dialog", "ISBN"))
self.label_25.setText(_translate("Dialog", "Ort"))
self.label_22.setText(
_translate("Dialog", "Herausgebername(n)\nNachname, Vorname")
)
self.hg_author.setToolTip(
_translate("Dialog", "Bei mehreren Autoren mit ; trennen")
)
self.label_10.setText(_translate("Dialog", "Autor(en)\nNachname, Vorname"))
self.label_22.setText(_translate("Dialog", "Herausgebername(n)\n"
"Nachname, Vorname"))
self.hg_author.setToolTip(_translate("Dialog", "Bei mehreren Autoren mit ; trennen"))
self.label_10.setText(_translate("Dialog", "Autor(en)\n"
"Nachname, Vorname"))
self.label_14.setText(_translate("Dialog", "Name der Zeitschrift"))
self.label_11.setText(_translate("Dialog", "Jahr"))
self.label_17.setText(_translate("Dialog", "Seiten"))
@@ -458,9 +401,7 @@ class Ui_Dialog(object):
self.label_15.setText(_translate("Dialog", "Ort"))
self.label_13.setText(_translate("Dialog", "Artikeltitel"))
self.label_18.setText(_translate("Dialog", "Signatur"))
self.zs_author.setToolTip(
_translate("Dialog", "Bei mehreren Autoren mit ; trennen")
)
self.zs_author.setToolTip(_translate("Dialog", "Bei mehreren Autoren mit ; trennen"))
self.label_32.setText(_translate("Dialog", "Dateiname"))
self.label_34.setText(_translate("Dialog", "ILIAS Name"))
self.label_33.setText(_translate("Dialog", "ILIAS Dateibeschreibung"))

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