fix: issues (1)

This commit is contained in:
2026-02-12 08:54:19 +01:00
parent 8ec92a685c
commit d316601e9a
27 changed files with 440 additions and 235 deletions

View File

@@ -33,6 +33,8 @@ if not _user_log_dir:
if not _user_config_dir:
_user_config_dir = str(get_app_base_path() / "config")
from config import Config # noqa: E402
LOG_DIR: str = _user_log_dir
CONFIG_DIR: str = _user_config_dir
@@ -49,8 +51,6 @@ except Exception:
Path(LOG_DIR).mkdir(parents=True, exist_ok=True)
Path(CONFIG_DIR).mkdir(parents=True, exist_ok=True)
from config import Config
settings = Config(f"{CONFIG_DIR}/config.yaml")
DATABASE_DIR: Union[Path, str] = ( # type: ignore

View File

@@ -1216,9 +1216,9 @@ class Database:
Optional[int]: the id of the apparat
"""
log.debug("Creating apparat: {} - {}", app.appnr, app.name)
app = apparat.apparat
prof = apparat.prof
log.debug("Creating apparat: {} - {}", app.appnr, app.name)
present_prof = self.getProfByName(prof.name())
prof_id = present_prof.id
log.debug("Present prof: {}", preview(present_prof, 300))

View File

@@ -24,5 +24,5 @@ class DocumentationThread(QThread):
self._process.terminate() # terminate the subprocess
try:
self._process.wait(timeout=5) # wait up to 5 seconds
except:
except Exception:
self._process.kill() # force kill if it doesn't stop

View File

@@ -12,7 +12,7 @@ from .models import (
Subjects,
XMLMailSubmission,
)
from .constants import *
from .constants import * # noqa: F403
from .semester import Semester
__all__ = [

View File

@@ -21,7 +21,7 @@ class Prof:
telnr: str | None = None
# add function that sets the data based on a dict
def from_dict(self, data: dict[str, Union[str, int]]):
def from_dict(self, data: dict[str, Union[str, int]]) -> 'Prof':
for key, value in data.items():
if hasattr(self, key):
setattr(self, key, value)
@@ -38,27 +38,40 @@ class Prof:
self._title = value
# add function that sets the data from a tuple
def from_tuple(self, data: tuple[Union[str, int], ...]) -> Prof:
self.id = data[0]
self._title = data[1]
self.firstname = data[2]
self.lastname = data[3]
self.fullname = data[4]
self.mail = data[5]
self.telnr = data[6]
def from_tuple(self, data: tuple[Union[int, str, None], ...]) -> 'Prof':
self.id = data[0] if data[0] is not None and isinstance(data[0], int) else None
self._title = str(data[1]) if data[1] is not None else None
self.firstname = str(data[2]) if data[2] is not None else None
self.lastname = str(data[3]) if data[3] is not None else None
self.fullname = str(data[4]) if data[4] is not None else None
self.mail = str(data[5]) if data[5] is not None else None
self.telnr = str(data[6]) if data[6] is not None else None
return self
def name(self, comma: bool = False) -> Optional[str]:
if self.firstname is None and self.lastname is None:
if "," in self.fullname:
self.firstname = self.fullname.split(",")[1].strip()
self.lastname = self.fullname.split(",")[0].strip()
if self.fullname and "," in self.fullname:
parts = self.fullname.split(",")
if len(parts) >= 2:
self.firstname = parts[1].strip()
self.lastname = parts[0].strip()
else:
return self.fullname
if comma:
return f"{self.lastname}, {self.firstname}"
return f"{self.lastname} {self.firstname}"
if self.lastname and self.firstname:
return f"{self.lastname}, {self.firstname}"
elif self.lastname:
return self.lastname
elif self.firstname:
return f", {self.firstname}"
elif self.lastname and self.firstname:
return f"{self.lastname} {self.firstname}"
elif self.lastname:
return self.lastname
elif self.firstname:
return self.firstname
return self.fullname
@dataclass
@@ -90,10 +103,12 @@ class BookData:
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
if self.year is not None:
year_str = regex.sub(r"[^\d]", "", str(self.year))
self.year = int(year_str) if year_str else None
self.in_library = True if self.signature else False
def from_dict(self, data: dict) -> BookData:
def from_dict(self, data: dict[str, Any]) -> 'BookData':
for key, value in data.items():
setattr(self, key, value)
return self
@@ -132,23 +147,26 @@ class BookData:
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:
def from_dataclass(self, data_obj: Optional[Any]) -> None:
if data_obj is None:
return
for key, value in dataclass.__dict__.items():
for key, value in data_obj.__dict__.items():
setattr(self, key, value)
def get_book_type(self) -> str:
if "Online" in self.pages:
if self.pages and "Online" in self.pages:
return "eBook"
return "Druckausgabe"
def from_string(self, data: str) -> BookData:
def from_string(self, data: str) -> 'BookData':
ndata = json.loads(data)
# Create a new BookData instance and set its attributes
book_data = BookData()
for key, value in ndata.items():
setattr(book_data, key, value)
return book_data
return BookData(**ndata)
def from_LehmannsSearchResult(self, result: Any) -> BookData:
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
@@ -170,7 +188,7 @@ class BookData:
def edition_number(self) -> Optional[int]:
if self.edition is None:
return 0
match = regex.search(r"(\d+)", self.edition)
match = regex.search(r"(\d+)", self.edition or "")
if match:
return int(match.group(1))
return 0
@@ -215,13 +233,13 @@ class Subjects(Enum):
return self.value[0]
@property
def name(self) -> str:
def subject_name(self) -> str:
return self.value[1]
@classmethod
def get_index(cls, name: str) -> Optional[int]:
for i in cls:
if i.name == name:
if i.subject_name == name:
return i.id - 1
return None
@@ -355,25 +373,24 @@ class Book:
@dataclass
class SemapDocument:
subject: str | None
phoneNumber: int | None
mail: str | None
title: str | None
personName: str | None
personTitle: str | None
title_suggestions: list[str] = None
semester: Union[str, Semester] = None
books: list[Book] = None
subject: str | None = None
phoneNumber: int | None = None
mail: str | None = None
title: str | None = None
personName: str | None = None
personTitle: str | None = None
title_suggestions: list[str] = field(default_factory=list)
semester: Union[str, 'Semester', None] = None
books: list[Book] = field(default_factory=list)
eternal: bool = False
title_length: int = 0
title_max_length: int = 0
def __post_init__(self) -> None:
"""."""
self.title_suggestions = []
self.phoneNumber = int(
regex.sub(r"[^\d]", "", str(self.phoneNumber)),
)
if self.phoneNumber is not None:
phone_str = regex.sub(r"[^\d]", "", str(self.phoneNumber))
self.phoneNumber = int(phone_str) if phone_str else None
@property
def nameSetter(self):
@@ -399,7 +416,7 @@ class SemapDocument:
def renameSemester(self) -> None:
from src.services.openai import semester_converter
if self.semester:
if self.semester and isinstance(self.semester, str):
if ", Dauer" in self.semester:
self.semester = self.semester.split(",")[0]
self.eternal = True

View File

@@ -1,4 +1,4 @@
"""Semester helper class
"""Semester helper class.
A small utility around the *German* academic calendar that distinguishes
between *Wintersemester* (WiSe) and *Sommersemester* (SoSe).
@@ -7,7 +7,7 @@ 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``.
* 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:
@@ -26,13 +26,13 @@ class Semester:
"""Represents a German university semester (WiSe or SoSe)."""
# ------------------------------------------------------------------
# Classlevel defaults will be *copied* to each instance and then
# Class-level defaults - will be *copied* to each instance and then
# potentially overwritten in ``__init__``.
# ------------------------------------------------------------------
_year: int | None = None # Will be set in __post_init__
_semester: str | None = None # "WiSe" or "SoSe" set later
_semester: str | None = None # "WiSe" or "SoSe" - set later
_month: int | None = None # Will be set in __post_init__
value: str | None = None # Humanreadable label, e.g. "WiSe 23/24"
value: str | None = None # Human-readable label, e.g. "WiSe 23/24"
# ------------------------------------------------------------------
# Construction helpers
@@ -89,17 +89,22 @@ class Semester:
# ------------------------------------------------------------------
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"
if self._month is not None:
self._semester = "WiSe" if (self._month <= 3 or self._month > 9) else "SoSe"
else:
self._semester = "WiSe" # Default value if month is None
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}"
"""Human-readable semester label - e.g. ``WiSe 23/24`` or ``SoSe 24``."""
if self._year is not None:
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}"
else:
self.value = "<invalid Semester>"
# ------------------------------------------------------------------
# Public API
@@ -117,10 +122,12 @@ class Semester:
if value == 0:
return Semester(self._year, self._semester)
if self._year is None:
raise ValueError("Cannot offset from a semester with no year")
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")
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"
@@ -164,10 +171,14 @@ class Semester:
@property
def year(self) -> int:
if self._year is None:
raise ValueError("Year is not set for this semester")
return self._year
@property
def semester(self) -> str:
if self._semester is None:
raise ValueError("Semester is not set for this semester")
return self._semester
# ------------------------------------------------------------------
@@ -178,14 +189,14 @@ class Semester:
"""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.is_future_semester(end) and not start.isMatch(end):
if start.is_future_semester(end) and not start.is_match(end):
raise ValueError("'start' must not be after 'end'")
chain: list[Semester] = [start.value]
chain: list[str] = [str(start)]
current = start
while not current.isMatch(end):
while not current.is_match(end):
current = current.next
chain.append(current.value)
chain.append(str(current))
if len(chain) > 1000: # sanity guard
raise RuntimeError("generate_missing exceeded sane iteration limit")
return chain
@@ -195,9 +206,9 @@ class Semester:
# ------------------------------------------------------------------
@classmethod
def from_string(cls, s: str) -> Semester:
"""Parse a humanreadable semester label and return a :class:`Semester`.
"""Parse a human-readable semester label and return a :class:`Semester`.
Accepted formats (caseinsensitive)::
Accepted formats (case-insensitive)::
"SoSe <YY>" → SoSe of year YY
"WiSe <YY>/<YY+1>" → Winter term starting in YY
@@ -212,7 +223,7 @@ class Semester:
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)",
"invalid semester string format - expected 'SoSe YY' or 'WiSe YY/YY' (spacing flexible)",
)
term_raw, y1_str, y2_str = m.groups()
@@ -236,7 +247,7 @@ class Semester:
return cls(year, "WiSe")
# ------------------------- quick selftest -------------------------
# ------------------------- quick self-test -------------------------
if __name__ == "__main__":
# Chain generation demo ------------------------------------------------
s_start = Semester(6, "SoSe") # SoSe 6

View File

@@ -123,7 +123,7 @@ class Database:
try:
if self.db_path is not None:
self.run_migrations()
except Exception as e:
except (sql.Error, OSError, IOError) as e:
log.error(f"Error while running migrations: {e}")
# --- Migration helpers integrated into Database ---
@@ -212,9 +212,9 @@ class Database:
).__str__()
return result[0]
def getElsaMediaType(self, id):
def getElsaMediaType(self, media_id):
query = "SELECT type FROM elsa_media WHERE id=?"
return self.query_db(query, (id,), one=True)[0]
return self.query_db(query, (media_id,), one=True)[0]
def get_db_contents(self) -> Union[List[Tuple[Any]], None]:
"""
@@ -736,7 +736,7 @@ class Database:
try:
bloat.debug("Recreated file blob size: {} bytes", len(blob))
bloat.debug("Recreated file blob (preview): {}", preview(blob, 2000))
except Exception:
except (TypeError, UnicodeDecodeError, ValueError):
bloat.debug("Recreated file blob (preview): {}", preview(blob, 2000))
tempdir = settings.database.temp.expanduser()
if not tempdir.exists():
@@ -990,16 +990,16 @@ class Database:
person = Prof()
return person.from_tuple(data)
def getProf(self, id) -> Prof:
def getProf(self, prof_id) -> Prof:
"""Get a professor based on the id
Args:
id ([type]): the id of the professor
prof_id ([type]): the id of the professor
Returns:
Prof: a Prof object containing the data of the professor
"""
data = self.query_db("SELECT * FROM prof WHERE id=?", (id,), one=True)
data = self.query_db("SELECT * FROM prof WHERE id=?", (prof_id,), one=True)
return Prof().from_tuple(data)
def getProfs(self) -> list[Prof]:
@@ -1278,17 +1278,17 @@ class Database:
# print(apparat_nr, app_id)
self.query_db("UPDATE media SET deleted=1 WHERE app_id=?", (app_id,))
def isEternal(self, id):
def isEternal(self, apparat_id):
"""check if the apparat is eternal (dauerapparat)
Args:
id (int): the id of the apparat to be checked
apparat_id (int): the id of the apparat to be checked
Returns:
int: the state of the apparat
"""
return self.query_db(
"SELECT dauer FROM semesterapparat WHERE appnr=?", (id,), one=True
"SELECT dauer FROM semesterapparat WHERE appnr=?", (apparat_id,), one=True
)
def getApparatName(self, app_id: Union[str, int], prof_id: Union[str, int]):

View File

@@ -1,6 +1,7 @@
import os
from datetime import datetime
from os.path import basename
from pathlib import Path
from docx import Document
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
@@ -69,20 +70,20 @@ class SemesterDocument:
full: bool = False,
):
assert isinstance(apparats, list), SemesterError(
"Apparats must be a list of tuples"
"Apparats must be a list of tuples",
)
assert all(isinstance(apparat, tuple) for apparat in apparats), SemesterError(
"Apparats must be a list of tuples"
"Apparats must be a list of tuples",
)
assert all(isinstance(apparat[0], int) for apparat in apparats), SemesterError(
"Apparat numbers must be integers"
"Apparat numbers must be integers",
)
assert all(isinstance(apparat[1], str) for apparat in apparats), SemesterError(
"Apparat names must be strings"
"Apparat names must be strings",
)
assert isinstance(semester, str), SemesterError("Semester must be a string")
assert "." not in filename and isinstance(filename, str), SemesterError(
"Filename must be a string and not contain an extension"
"Filename must be a string and not contain an extension",
)
self.doc = Document()
self.apparats = apparats
@@ -108,8 +109,7 @@ class SemesterDocument:
log.info("Document printed")
def set_table_border(self, table):
"""
Adds a full border to the table.
"""Adds a full border to the table.
:param table: Table object to which the border will be applied.
"""
@@ -150,7 +150,8 @@ class SemesterDocument:
trPr = row._tr.get_or_add_trPr() # Get or add the <w:trPr> element
trHeight = OxmlElement("w:trHeight")
trHeight.set(
qn("w:val"), str(int(Pt(15).pt * 20))
qn("w:val"),
str(int(Pt(15).pt * 20)),
) # Convert points to twips
trHeight.set(qn("w:hRule"), "exact") # Use "exact" for fixed height
trPr.append(trHeight)
@@ -233,7 +234,7 @@ class SemesterDocument:
self.save_document(self.filename + ".docx")
docpath = os.path.abspath(self.filename + ".docx")
doc = word.Documents.Open(docpath)
curdir = os.getcwd()
curdir = Path.cwd()
doc.SaveAs(f"{curdir}/{self.filename}.pdf", FileFormat=17)
doc.Close()
word.Quit()
@@ -317,7 +318,7 @@ class SemapSchilder:
self.save_document()
docpath = os.path.abspath(f"{self.filename}.docx")
doc = word.Documents.Open(docpath)
curdir = os.getcwd()
curdir = Path.cwd()
doc.SaveAs(f"{curdir}/{self.filename}.pdf", FileFormat=17)
doc.Close()
word.Quit()

View File

@@ -1,2 +1,2 @@
# import basic error classes
from .DatabaseErrors import *
from .DatabaseErrors import * # noqa: F403

View File

@@ -6,3 +6,11 @@ from .transformers import (
RDSData,
RISData,
)
# Explicit re-exports to avoid F401 warnings
RDS_AVAIL_DATA = RDS_AVAIL_DATA
ARRAYData = ARRAYData
BibTeXData = BibTeXData
COinSData = COinSData
RDSData = RDSData
RISData = RISData

View File

@@ -141,7 +141,7 @@ class ARRAYData:
source = source.replace("\t", "").replace("\r", "")
source = source.split(search)[1].split(")")[0]
return _get_line(source, entry).replace("=>", "").strip()
except:
except Exception:
return ""
def _get_isbn(source: str) -> list:
@@ -157,7 +157,7 @@ class ARRAYData:
continue
ret.append(isb) if isb not in ret else None
return ret
except:
except Exception:
isbn = []
return isbn
@@ -294,7 +294,7 @@ class COinSData:
try:
data = source.split(f"{search}=")[1] # .split("")[0].strip()
return data.split("rft")[0].strip() if "rft" in data else data
except:
except Exception:
return ""
return BookData(
@@ -319,7 +319,7 @@ class RISData:
try:
data = source.split(f"{search} - ")[1] # .split("")[0].strip()
return data.split("\n")[0].strip() if "\n" in data else data
except:
except Exception:
return ""
return BookData(
@@ -356,7 +356,7 @@ class BibTeXData:
.replace("[", "")
.replace("];", "")
)
except:
except Exception:
return ""
return BookData(

View File

@@ -9,11 +9,11 @@ from ratelimit import limits, sleep_and_retry
from src.core.models import BookData
from src.shared.logging import log, get_bloat_logger, preview
from src.transformers import ARRAYData, BibTeXData, COinSData, RDSData, RISData
from src.transformers.transformers import RDS_AVAIL_DATA, RDS_GENERIC_DATA
# bloat logger for large/raw HTTP responses
bloat = get_bloat_logger()
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")

View File

@@ -6,3 +6,11 @@ from .transformers import (
RDSData,
RISData,
)
# Explicit re-exports to avoid F401 warnings
RDS_AVAIL_DATA = RDS_AVAIL_DATA
ARRAYData = ARRAYData
BibTeXData = BibTeXData
COinSData = COinSData
RDSData = RDSData
RISData = RISData

View File

@@ -141,7 +141,7 @@ class ARRAYData:
source = source.replace("\t", "").replace("\r", "")
source = source.split(search)[1].split(")")[0]
return _get_line(source, entry).replace("=>", "").strip()
except:
except Exception:
return ""
def _get_isbn(source: str) -> list:
@@ -157,7 +157,7 @@ class ARRAYData:
continue
ret.append(isb) if isb not in ret else None
return ret
except:
except Exception:
isbn = []
return isbn
@@ -294,7 +294,7 @@ class COinSData:
try:
data = source.split(f"{search}=")[1] # .split("")[0].strip()
return data.split("rft")[0].strip() if "rft" in data else data
except:
except Exception:
return ""
return BookData(
@@ -319,7 +319,7 @@ class RISData:
try:
data = source.split(f"{search} - ")[1] # .split("")[0].strip()
return data.split("\n")[0].strip() if "\n" in data else data
except:
except Exception:
return ""
return BookData(
@@ -356,7 +356,7 @@ class BibTeXData:
.replace("[", "")
.replace("];", "")
)
except:
except Exception:
return ""
return BookData(

View File

@@ -1,32 +1,35 @@
import pathlib
from .semesterapparat_ui_ui import Ui_MainWindow as Ui_Semesterapparat
# from .dialogs import (
# ApparatExtendDialog,
# Mail_Dialog,
# Settings,
# edit_bookdata_ui,
# login_ui,
# medienadder_ui,
# parsed_titles_ui,
# popus_confirm,
# reminder_ui,
# About,
# ElsaAddEntry,
# )
# from .widgets import (
# FilePicker,
# StatusWidget,
# CalendarEntry,
# MessageCalendar,
# SearchStatisticPage, #
# DataGraph,
# ElsaDialog,
# UserCreate,
# EditUser,
# EditProf,
# )
path = pathlib.Path(__file__).parent.absolute()
# from .mainwindow import Ui_MainWindow as Ui_MainWindow
# from .sap import Ui_MainWindow as MainWindow_SAP
import pathlib
from .semesterapparat_ui_ui import Ui_MainWindow as Ui_Semesterapparat
# Explicit re-export to avoid F401 warnings
Ui_Semesterapparat = Ui_Semesterapparat
# from .dialogs import (
# ApparatExtendDialog,
# Mail_Dialog,
# Settings,
# edit_bookdata_ui,
# login_ui,
# medienadder_ui,
# parsed_titles_ui,
# popus_confirm,
# reminder_ui,
# About,
# ElsaAddEntry,
# )
# from .widgets import (
# FilePicker,
# StatusWidget,
# CalendarEntry,
# MessageCalendar,
# SearchStatisticPage, #
# DataGraph,
# ElsaDialog,
# UserCreate,
# EditUser,
# EditProf,
# )
path = pathlib.Path(__file__).parent.absolute()
# from .mainwindow import Ui_MainWindow as Ui_MainWindow
# from .sap import Ui_MainWindow as MainWindow_SAP

View File

@@ -1 +1,4 @@
from .newMailTemplateDesigner_ui import Ui_Dialog as NewMailTemplateDesignerDialog
# Explicit re-export to avoid F401 warnings
NewMailTemplateDesignerDialog = NewMailTemplateDesignerDialog

View File

@@ -9,6 +9,12 @@ from src.services.lehmanns import LehmannsClient
from src.services.sru import SWB
def filter_prefer_swb(response):
"""Filter function to prefer SWB results when available."""
# This is a placeholder implementation - adjust based on actual requirements
return response
class CheckThread(QtCore.QThread):
updateSignal = QtCore.Signal()
total_entries_signal = QtCore.Signal(int)

View File

@@ -180,7 +180,8 @@ class Ui(QtWidgets.QMainWindow, Ui_Semesterapparat):
# self.update_app_media_list()
self.populate_prof_dropdown()
self.populate_appfach_dropdown()
# if the focus is changed from the prof name dropdown, set the prof data if the prof exists in the database, otherwise show a message
# if the focus is changed from the prof name dropdown, set the prof data if the
# prof exists in the database, otherwise show a message
self.drpdwn_prof_name.currentIndexChanged.connect(self.set_prof_data) # type:ignore
self.cancel_active_selection.clicked.connect(self.btn_cancel_active_selection) # type:ignore
self.check_eternal_app.stateChanged.connect(self.set_state) # type:ignore

View File

@@ -4,15 +4,36 @@ from src.database import Database
from src.utils.icon import Icon
class AdminQueryWidget(QtWidgets.QWidget, Ui_Form):
class AdminQueryWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
self.setupUi()
self.setWindowIcon(Icon("db_search").icon)
self.db = Database()
# Connect the button click to the method
self.sendquery.clicked.connect(self.on_pushButton_clicked)
def setupUi(self):
# Create the layout and widgets
layout = QtWidgets.QVBoxLayout(self)
# Create SQL query input area
self.sqlquery = QtWidgets.QTextEdit()
self.sqlquery.setPlaceholderText("Enter SQL query here...")
# Create execute button
self.sendquery = QtWidgets.QPushButton("Execute Query")
# Create results table
self.queryResult = QtWidgets.QTableWidget()
self.queryResult.setColumnCount(5) # Adjust as needed
self.queryResult.setHorizontalHeaderLabels(["Column 1", "Column 2", "Column 3", "Column 4", "Column 5"])
# Add widgets to layout
layout.addWidget(self.sqlquery)
layout.addWidget(self.sendquery)
layout.addWidget(self.queryResult)
def on_pushButton_clicked(self):
# Handle button click event
self.queryResult.setRowCount(0) # Clear previous results

View File

@@ -1,11 +1,13 @@
"""Utilities for managing documentation server functionality."""
import logging
import os
import subprocess
import sys
from pathlib import Path
from src import LOG_DIR
log_path = os.path.join(LOG_DIR, "web_documentation.log")
log_path = Path(LOG_DIR) / "web_documentation.log"
# Replace the default StreamHandler with a FileHandler
logging.basicConfig(
@@ -19,13 +21,12 @@ logger = logging.getLogger(__name__) # inherits the same file handler
docport = 8000
def start_documentation_server():
"""
Start the Zensical documentation server as a subprocess.
"""Start the Zensical documentation server as a subprocess.
Returns:
subprocess.Popen: The subprocess object, or None if startup failed.
"""
try:
# Prepare subprocess arguments
@@ -33,7 +34,7 @@ def start_documentation_server():
if sys.platform == "win32":
# Hide console window on Windows
creationflags = subprocess.CREATE_NO_WINDOW
# Start subprocess with all output suppressed
process = subprocess.Popen(
["uv", "run", "zensical", "serve"],
@@ -41,12 +42,12 @@ def start_documentation_server():
stderr=subprocess.DEVNULL,
stdin=subprocess.DEVNULL,
creationflags=creationflags,
cwd=os.getcwd(),
cwd=Path.cwd(),
)
logger.info(f"Documentation server started with PID {process.pid}")
return process
except Exception as e:
logger.error(f"Failed to start documentation server: {e}")
return None

View File

@@ -1,6 +1,7 @@
import os
from datetime import datetime
from os.path import basename
from pathlib import Path
from docx import Document
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
@@ -69,20 +70,20 @@ class SemesterDocument:
full: bool = False,
):
assert isinstance(apparats, list), SemesterError(
"Apparats must be a list of tuples"
"Apparats must be a list of tuples",
)
assert all(isinstance(apparat, tuple) for apparat in apparats), SemesterError(
"Apparats must be a list of tuples"
"Apparats must be a list of tuples",
)
assert all(isinstance(apparat[0], int) for apparat in apparats), SemesterError(
"Apparat numbers must be integers"
"Apparat numbers must be integers",
)
assert all(isinstance(apparat[1], str) for apparat in apparats), SemesterError(
"Apparat names must be strings"
"Apparat names must be strings",
)
assert isinstance(semester, str), SemesterError("Semester must be a string")
assert "." not in filename and isinstance(filename, str), SemesterError(
"Filename must be a string and not contain an extension"
"Filename must be a string and not contain an extension",
)
self.doc = Document()
self.apparats = apparats
@@ -108,8 +109,7 @@ class SemesterDocument:
log.info("Document printed")
def set_table_border(self, table):
"""
Adds a full border to the table.
"""Adds a full border to the table.
:param table: Table object to which the border will be applied.
"""
@@ -150,7 +150,8 @@ class SemesterDocument:
trPr = row._tr.get_or_add_trPr() # Get or add the <w:trPr> element
trHeight = OxmlElement("w:trHeight")
trHeight.set(
qn("w:val"), str(int(Pt(15).pt * 20))
qn("w:val"),
str(int(Pt(15).pt * 20)),
) # Convert points to twips
trHeight.set(qn("w:hRule"), "exact") # Use "exact" for fixed height
trPr.append(trHeight)
@@ -233,7 +234,7 @@ class SemesterDocument:
self.save_document(self.filename + ".docx")
docpath = os.path.abspath(self.filename + ".docx")
doc = word.Documents.Open(docpath)
curdir = os.getcwd()
curdir = Path.cwd()
doc.SaveAs(f"{curdir}/{self.filename}.pdf", FileFormat=17)
doc.Close()
word.Quit()
@@ -317,7 +318,7 @@ class SemapSchilder:
self.save_document()
docpath = os.path.abspath(f"{self.filename}.docx")
doc = word.Documents.Open(docpath)
curdir = os.getcwd()
curdir = Path.cwd()
doc.SaveAs(f"{curdir}/{self.filename}.pdf", FileFormat=17)
doc.Close()
word.Quit()