From 0b591c38571c560786d5be5d0c4d871a90ae1f61 Mon Sep 17 00:00:00 2001 From: Floskinner <58706771+Floskinner@users.noreply.github.com> Date: Tue, 25 May 2021 23:52:06 +0200 Subject: [PATCH 01/25] init commit --- tools/kontaktdaten.ui | 348 ++++++++++++++++++++++++++++++++++++++ tools/uhrzeiten.ui | 385 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 733 insertions(+) create mode 100644 tools/kontaktdaten.ui create mode 100644 tools/uhrzeiten.ui diff --git a/tools/kontaktdaten.ui b/tools/kontaktdaten.ui new file mode 100644 index 00000000..62bdaf07 --- /dev/null +++ b/tools/kontaktdaten.ui @@ -0,0 +1,348 @@ + + + MainWindow + + + + 0 + 0 + 541 + 383 + + + + MainWindow + + + + + + + + + Automatische Terminbuchung für den Corona Impfterminservice + + + Qt::AlignCenter + + + + + + + vaccipy + + + Qt::AlignHCenter|Qt::AlignTop + + + + + + + + + + + PLZ's der Impfzentren: + + + + + + + + + + + + + Beispiel: 68163, 69124, 69469 + + + true + + + + + + + Code: + + + + + + + NNNN-NNNN-NNNN + + + -- + + + A1C3-D2FG-4IJK + + + false + + + + + + + Anrede: + + + + + + + false + + + Bitte Wählen + + + QComboBox::NoInsert + + + + Bitte Wählen + + + + + Frau + + + + + Herr + + + + + ... + + + + + + + + Vorname: + + + + + + + Max + + + true + + + + + + + Nachname: + + + + + + + Mustermann + + + true + + + + + + + Straße: + + + + + + + + + + 100 + 0 + + + + Hausnummer: + + + + + + + + 0 + 0 + + + + true + + + + + + + + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + PLZ Wohnort: + + + + + + + + + true + + + + + + + + 100 + 0 + + + + Wohnort: + + + + + + + 99999 + + + 88045 + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Telefonnummer: + + + + + + + +4\9 + + + + + + false + + + + + + + Mail: + + + + + + + true + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Reset + + + false + + + + + + + + + diff --git a/tools/uhrzeiten.ui b/tools/uhrzeiten.ui new file mode 100644 index 00000000..7000c25f --- /dev/null +++ b/tools/uhrzeiten.ui @@ -0,0 +1,385 @@ + + + MainWindow + + + + 0 + 0 + 513 + 398 + + + + MainWindow + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Qt::Horizontal + + + + + + + Hier kannst du Uhrzeiten und Tage eingrenzen, an dem ein Termin gebucht werden soll + + + Qt::AlignCenter + + + + + + + + + QFrame::Box + + + QFrame::Plain + + + + + + 0 + + + + + 1. Termin + + + true + + + + + + + 2. Termin + + + true + + + + + + + + + Qt::Horizontal + + + + + + + Nur Uhrzeiten zwischen + + + Qt::AlignCenter + + + + + + + + + Start: + + + + + + + Ende: + + + + + + + + + + + + + + + + Uhr + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + + QDateTimeEdit::HourSection + + + false + + + + + + + + + + Uhr + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + + Bei welchen Terminen sollen die Bedingungen gelten? + + + Qt::AlignCenter + + + + + + + + + Termin fühestens ab: + + + Qt::AlignCenter + + + + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + QAbstractSpinBox::UpDownArrows + + + + + + + + + QDateTimeEdit::DaySection + + + true + + + + 2021 + 5 + 25 + + + + + + + + + + Qt::Horizontal + + + + + + + + + + + + vaccipy + + + Qt::AlignCenter + + + + + + + + + + 0 + 0 + + + + QFrame::Box + + + QFrame::Plain + + + + + + Dienstags + + + true + + + + + + + Montags + + + true + + + + + + + Qt::Horizontal + + + + + + + Donnerstags + + + true + + + + + + + Samstags + + + true + + + + + + + Sonntags + + + true + + + + + + + Mittwochs + + + true + + + + + + + Nur an folgenden Tagen: + + + + + + + Freitags + + + true + + + + + + + + + + + + Qt::Horizontal + + + + + + + + + QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Reset + + + + + + + + + From d29328eb9dd1d081746976994fa02b5a742dde06 Mon Sep 17 00:00:00 2001 From: Floskinner <58706771+Floskinner@users.noreply.github.com> Date: Tue, 25 May 2021 23:54:17 +0200 Subject: [PATCH 02/25] init commit --- requirements.txt | 3 + tools/{ => gui}/kontaktdaten.ui | 0 tools/gui/qttimer.py | 186 ++++++++++++++++++++++++++++++++ tools/{ => gui}/uhrzeiten.ui | 0 tools/its.py | 77 +++++++++---- 5 files changed, 247 insertions(+), 19 deletions(-) rename tools/{ => gui}/kontaktdaten.ui (100%) create mode 100644 tools/gui/qttimer.py rename tools/{ => gui}/uhrzeiten.ui (100%) diff --git a/requirements.txt b/requirements.txt index 2bb55f52..8d89be49 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,6 @@ urllib3>=1.26.4 pyinstaller>=4.3 plyer>=2.0.0 cloudscraper>=1.2.52 +PyQt5==5.15.4 +PyQt5-Qt5==5.15.2 +PyQt5-sip==12.9.0 \ No newline at end of file diff --git a/tools/kontaktdaten.ui b/tools/gui/kontaktdaten.ui similarity index 100% rename from tools/kontaktdaten.ui rename to tools/gui/kontaktdaten.ui diff --git a/tools/gui/qttimer.py b/tools/gui/qttimer.py new file mode 100644 index 00000000..5dd2397d --- /dev/null +++ b/tools/gui/qttimer.py @@ -0,0 +1,186 @@ +import os +import json +from PyQt5 import QtWidgets, uic +from PyQt5.QtCore import QTime + +# Folgende Widgets stehen zur Verfügung: + +### Checkboxes ### +# mo_check_box +# di_check_box +# mi_check_box +# do_check_box +# fr_check_box +# so_check_box +# sa_check_box +# erster_termin_check_box +# zweiter_termin_check_box + +### QTimeEdit ### +# start_time +# end_time + +### Buttons ### +# pushButton + +### Labels ### +# header_label +# wochentage_label +# termine_anwenden_label +# uhr_header_label +# uhr_start_label +# uhr_end_label +# und_label + +PATH = os.path.dirname(os.path.realpath(__file__)) + +# TODO: [ ] Wenn das Fenster geschlossen wir soll was passieren...? nicht speichern, aber Programm fortfahren +# TODO: [ ] .icon adden +# TODO: [X] Fenstergröße sollte man nicht verändern können +# TODO: [X] Fenster in den Vordergund bringen +# TODO: [X] Docstrings +# TODO: [X] Rückmeldung für erfolgreiches speichern + + +class QtTimer(QtWidgets.QMainWindow): + """ + Klasse für das erstellen einer zeitspanne.json mithilfe einer GUI / PyQt5 + Diese erbt von QtWidgets.QMainWindow + """ + + def __init__(self, pfad_zeitspanne: str, pfad_fenster_layout: str): + """ + Ladet das angegebene Layout (wurde mit QT Designer erstellt https://www.qt.io/download) + Das Fenster wird automtaisch nach dem erstellen der Klasse geöffnet + + Args: + pfad_zeitspanne (str): Speicherpfad für zeitspanne.json + pfad_fester_layout (str): Speicherort der .ui - Datei + """ + + super(QtTimer, self).__init__() + + # Laden der .ui Datei und Anpassungen + uic.loadUi(pfad_fenster_layout, self) + self.setFixedSize(self.size()) + + # self.bestaetigen() soll beim Klicken auf Bestätigen aufgerufen werden + self.pushButton.clicked.connect(self.bestaetigt) + + # Setzte leere Werte + self.aktive_wochentage = list() + self.start_uhrzeit: QTime = None + self.end_uhrzeit: QTime = None + self.aktive_termine = list() + self.speicherpfad = pfad_zeitspanne + + # GUI anzeigen + self.show() + # Workaround, damit das Fenster hoffentlich im Vordergrund ist + self.activateWindow() + + @staticmethod + def start(pfad_zeitspanne: str, pfad_fenster_layout=os.path.join(PATH, "qttimer.ui")): + """ + Öffnet eine GUI in dem der User seine Parameter angeben kann + Diese werden bei Bestätigung direkt in den mit übergebenen Pfad gespeichert + Anschließend schließt sich das Fenster + + Args: + pfad_zeitspanne (str): Speicherpfad für zeitspanne.json + pfad_fester_layout (str): Speicherort der .ui - Datei. Default: .\\qttimer.ui + """ + + app = QtWidgets.QApplication(list()) + window = QtTimer(pfad_zeitspanne, pfad_fenster_layout) + app.exec_() + + def bestaetigt(self): + """ + Aktuallisiert alle Werte und Speichert gleichzeig die Aktuellen Werte + """ + + # Alle Werte von aus der GUI aktuallisieren + self.__aktuallisiere_aktive_wochentage() + self.__aktuallisiere_aktive_termine() + + if not self.__aktuallisiere_uhrzeiten(): + QtWidgets.QMessageBox.critical(self, "Ungültige Eingabe!", "Start-Uhrzeit ist später als End-Uhrzeit!") + return + + # Speichert alle Werte ab + self.speicher_einstellungen() + + self.close() + + def speicher_einstellungen(self): + """ + Speichert alle Werte in der entsprechenden JSON-Formatierung + Speicherpfad wurde beim erstellen der Klasse mit übergeben + """ + + data = { + "wochentage": self.aktive_wochentage, + "startzeit": { + "h": self.start_uhrzeit.hour(), + "m": self.start_uhrzeit.minute() + }, + "endzeit": { + "h": self.end_uhrzeit.hour(), + "m": self.end_uhrzeit.minute() + }, + "einhalten_bei": self.aktive_termine + } + + with open(self.speicherpfad, 'w', encoding='utf-8') as f: + try: + json.dump(data, f, ensure_ascii=False, indent=4) + QtWidgets.QMessageBox.information(self, "Gepseichert", "Daten erfolgreich gespeichert") + + except (TypeError, IOError, FileNotFoundError) as error: + QtWidgets.QMessageBox.critical(self, "Fehler!", "Daten konnten nicht gespeichert werden.") + raise error + + def __aktuallisiere_aktive_wochentage(self): + """ + Alle "checked" Wochentage in der GUI werden gesichert + """ + # Zur sicherheit alte Werte löschen + self.aktive_wochentage.clear() + + # Alle Checkboxen der GUI selektieren und durchgehen + # BUG: Wenn die reihenfolge im Layout geändert wird, stimmen die Wochentage nicht mehr 0 = Mo ... 6 = So + checkboxes = self.mo_check_box.parent().findChildren(QtWidgets.QCheckBox) + for num, checkboxe in enumerate(checkboxes, 0): + if checkboxe.isChecked(): + self.aktive_wochentage.append(num) + + def __aktuallisiere_uhrzeiten(self) -> bool: + """ + Aktuallisert die eingegebenen Uhrzeiten der GUI + """ + + if self.start_time.time() < self.end_time.time(): + self.start_uhrzeit = self.start_time.time() + self.end_uhrzeit = self.end_time.time() + + return True + else: + return False + + def __aktuallisiere_aktive_termine(self): + """ + Aktuallisert die eingegebenen Uhrzeiten der GUI + """ + + # Zur sicherheit alte Werte löschen + self.aktive_termine.clear() + + if self.erster_termin_check_box.isChecked(): + self.aktive_termine.append(1) + if self.zweiter_termin_check_box.isChecked(): + self.aktive_termine.append(2) + + +if __name__ == "__main__": + QtTimer.start(f"{PATH}\\..\\zeitspanne.json") diff --git a/tools/uhrzeiten.ui b/tools/gui/uhrzeiten.ui similarity index 100% rename from tools/uhrzeiten.ui rename to tools/gui/uhrzeiten.ui diff --git a/tools/its.py b/tools/its.py index d864a39f..e1dcdff9 100644 --- a/tools/its.py +++ b/tools/its.py @@ -4,12 +4,14 @@ import time from base64 import b64encode from datetime import datetime +from datetime import time as dtime from random import choice from typing import Dict, List import cloudscraper + from selenium.webdriver import ActionChains from selenium.webdriver import Chrome from selenium.webdriver.chrome.options import Options @@ -481,7 +483,7 @@ def login(self): @retry_on_failure() - def termin_suchen(self, plz): + def termin_suchen(self, plz: int, zeitspanne: dict): """Es wird nach einen verfügbaren Termin in der gewünschten PLZ gesucht. Ausgewählt wird der erstbeste Termin (!). Zurückgegeben wird das Ergebnis der Abfrage und der Status-Code. @@ -515,22 +517,59 @@ def termin_suchen(self, plz): res_json = res.json() terminpaare = res_json.get("termine") if terminpaare: - # Auswahl des erstbesten Terminpaares - self.terminpaar = choice(terminpaare) - self.plz_termin = plz - self.log.success(f"Terminpaar gefunden!") - self.impfzentrum = self.verfuegbare_impfzentren.get(plz) - self.log.success("'{}' in {} {}".format( - self.impfzentrum.get("Zentrumsname").strip(), - self.impfzentrum.get("PLZ"), - self.impfzentrum.get("Ort"))) - for num, termin in enumerate(self.terminpaar, 1): - ts = datetime.fromtimestamp(termin["begin"] / 1000).strftime( - '%d.%m.%Y um %H:%M Uhr') - self.log.success(f"{num}. Termin: {ts}") - if ENABLE_BEEPY: - beepy.beep('coin') - return True, 200 + # Checken ob verfügbare terminpaare in angegebener Zeitspanne liegt + if zeitspanne["einhalten_bei"]: + terminpaare_in_zeitspanne = list() + + # Alle Terminpaare durchgehen + for terminpaar in terminpaare: + termine_in_zeitspanne = True + + # Einzelne Termine druchgehen + for num, termin in enumerate(terminpaar, 1): + + # Soll einer der Beiden Termine überprüft werden + if num in zeitspanne["einhalten_bei"]: + startzeit = dtime(zeitspanne["startzeit"]["h"], zeitspanne["startzeit"]["m"]) + endzeit = dtime(zeitspanne["endzeit"]["h"], zeitspanne["endzeit"]["m"]) + wochentage = zeitspanne["wochentage"] + + termin_zeit = datetime.fromtimestamp(int(termin["begin"])/1000) + + # Termin inherhalb der Zeitspanne und im Wochentag + if not ((startzeit <= termin_zeit.time() <= endzeit) and (termin_zeit.weekday() in wochentage)): + termine_in_zeitspanne = False + + # Beide Termine sind in der Zeitspanne + if termine_in_zeitspanne: + terminpaare_in_zeitspanne.append(terminpaar) + else: + self.log.info("Termin gefunden - jedoch nicht im entsprechenden Zeitraum") + for num, terminpaar in enumerate(terminpaar, 1): + ts = datetime.fromtimestamp(terminpaar["begin"] / 1000).strftime( + '%d.%m.%Y um %H:%M Uhr') + self.log.info(f"{num}. Termin: {ts}") + else: + # Keine Bedingungen, alle Terminpaare zugelassen + terminpaare_in_zeitspanne = terminpaare + + if terminpaare_in_zeitspanne: + # Auswahl des erstbesten Terminpaares + self.terminpaar = choice(terminpaare_in_zeitspanne) + self.plz_termin = plz + self.log.success(f"Terminpaar gefunden!") + self.impfzentrum = self.verfuegbare_impfzentren.get(plz) + self.log.success("'{}' in {} {}".format( + self.impfzentrum.get("Zentrumsname").strip(), + self.impfzentrum.get("PLZ"), + self.impfzentrum.get("Ort"))) + for num, termin in enumerate(self.terminpaar, 1): + ts = datetime.fromtimestamp(termin["begin"] / 1000).strftime( + '%d.%m.%Y um %H:%M Uhr') + self.log.success(f"{num}. Termin: {ts}") + if ENABLE_BEEPY: + beepy.beep('coin') + return True, 200 else: self.log.info(f"Keine Termine verfügbar in {plz}") else: @@ -650,7 +689,7 @@ def code_bestaetigen(self, token, sms_pin): return False @staticmethod - def terminsuche(code: str, plz_impfzentren: list, kontakt: dict,PATH:str, check_delay: int = 30): + def terminsuche(code: str, plz_impfzentren: list, kontakt: dict, PATH:str, zeitspanne: dict, check_delay: int = 30): """ Workflow für die Terminbuchung. @@ -673,7 +712,7 @@ def terminsuche(code: str, plz_impfzentren: list, kontakt: dict,PATH:str, check_ # durchlaufe jede eingegebene PLZ und suche nach Termin for plz in its.plz_impfzentren: - termin_gefunden, status_code = its.termin_suchen(plz) + termin_gefunden, status_code = its.termin_suchen(plz, zeitspanne) # Durchlauf aller PLZ unterbrechen, wenn Termin gefunden wurde if termin_gefunden: From 83fd8c2e678812c151e7866300c30fb561ee6f29 Mon Sep 17 00:00:00 2001 From: Floskinner <58706771+Floskinner@users.noreply.github.com> Date: Wed, 26 May 2021 19:55:31 +0200 Subject: [PATCH 03/25] Init commit --- gui.py | 137 +++++ tools/gui/main.py | 116 ++++ tools/gui/main.ui | 192 +++++++ tools/gui/qtkontakt.py | 31 ++ tools/gui/{qttimer.py => qtzeiten.py} | 101 ++-- tools/gui/uhrzeiten.ui | 757 +++++++++++++------------- 6 files changed, 902 insertions(+), 432 deletions(-) create mode 100644 gui.py create mode 100644 tools/gui/main.py create mode 100644 tools/gui/main.ui create mode 100644 tools/gui/qtkontakt.py rename tools/gui/{qttimer.py => qtzeiten.py} (63%) diff --git a/gui.py b/gui.py new file mode 100644 index 00000000..1ce5dd1e --- /dev/null +++ b/gui.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 + +import sys +import os +import json + +from PyQt5 import QtWidgets, uic +from tools.gui.qtzeiten import QtZeiten +from tools.its import ImpfterminService + +PATH = os.path.dirname(os.path.realpath(__file__)) + + +class HauptGUI(QtWidgets.QMainWindow): + + # Folgende Widgets stehen zur Verfügung: + + ### QLineEdit ### + # i_kontaktdaten_pfad + # i_zeitspanne_pfad + + ### Buttons ### + # b_termin_suchen + # b_code_generieren + # b_dateien_kontaktdaten + # b_dateien_zeitspanne + # b_neue_kontaktdaten + # b_neue_zeitspanne + + def __init__(self, pfad_fenster_layout=os.path.join(PATH, "tools/gui/main.ui")): + super().__init__() + + # Laden der .ui Datei und Anpassungen + uic.loadUi(pfad_fenster_layout, self) + + # Funktionen den Buttons zuweisen + self.b_termin_suchen.clicked.connect(self.__termin_suchen) + self.b_code_generieren.clicked.connect(self.__code_generieren) + self.b_dateien_kontaktdaten.clicked.connect(lambda: self.__oeffne_file_dialog("kontaktdaten")) + self.b_dateien_zeitspanne.clicked.connect(lambda: self.__oeffne_file_dialog("zeitspanne")) + self.b_neue_kontaktdaten.clicked.connect(self.kontaktdaten_erstellen) + self.b_neue_zeitspanne.clicked.connect(self.zeitspanne_erstellen) + + # Standard Pfade + self.pfad_kontaktdaten: str = os.path.join(PATH, "data", "kontaktdaten.json") + self.pfad_zeitspanne: str = os.path.join(PATH, "data", "zeitspanne.json") + + # Pfade in der GUI anzeigen + self.i_kontaktdaten_pfad.setText(self.pfad_kontaktdaten) + self.i_zeitspanne_pfad.setText(self.pfad_zeitspanne) + + # Events für Eingabefelder + self.i_kontaktdaten_pfad.textChanged.connect(self.__update_pfade) + self.i_zeitspanne_pfad.textChanged.connect(self.__update_pfade) + + # GUI anzeigen + self.show() + + # Workaround, damit das Fenster hoffentlich im Vordergrund ist + self.activateWindow() + + + def __termin_suchen(self): + kontaktdaten = self.__get_kontaktdaten() + zeitspanne = self.__get_zeitspanne() + + kontakt = kontaktdaten["kontakt"] + code = kontaktdaten["code"] + plz_impfzentren = kontaktdaten["plz_impfzentren"] + + ImpfterminService.terminsuche(code=code, plz_impfzentren=plz_impfzentren, kontakt=kontakt, zeitspanne=zeitspanne, PATH=PATH) + + + def __code_generieren(self): + pass + + + def __get_kontaktdaten(self) -> dict: + + if not os.path.isfile(self.pfad_kontaktdaten): + self.kontaktdaten_erstellen() + pass + + with open(self.pfad_kontaktdaten, "r", encoding='utf-8') as f: + kontaktdaten = json.load(f) + + return kontaktdaten + + + def __get_zeitspanne(self) -> dict: + if not os.path.isfile(self.pfad_zeitspanne): + self.zeitspanne_erstellen() + + with open(self.pfad_zeitspanne, "r", encoding='utf-8') as f: + zeitspanne = json.load(f) + + return zeitspanne + + + def __oeffne_file_dialog(self, datei: str): + datei_data = QtWidgets.QFileDialog.getOpenFileName(self, datei, os.path.join(PATH, "data"), "JSON Files (*.json)") + dateipfad = datei_data[0] + + if datei == "kontaktdaten": + self.i_kontaktdaten_pfad.setText(dateipfad) + else: + self.i_zeitspanne_pfad.setText(dateipfad) + + + def __update_pfade(self): + self.pfad_kontaktdaten = self.i_kontaktdaten_pfad.text() + self.pfad_zeitspanne = self.i_zeitspanne_pfad.text() + + @staticmethod + def start_gui(): + app = QtWidgets.QApplication(list()) + window = HauptGUI() + app.exec_() + + + def kontaktdaten_erstellen(self): + pass + + + def zeitspanne_erstellen(self): + dialog = QtZeiten() + dialog.show() + dialog.exec_() + + + +def main(): + HauptGUI.start_gui() + + +if __name__ == "__main__": + main() diff --git a/tools/gui/main.py b/tools/gui/main.py new file mode 100644 index 00000000..ce8a7b08 --- /dev/null +++ b/tools/gui/main.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file '.\tools\gui\main.ui' +# +# Created by: PyQt5 UI code generator 5.15.4 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_MainWindow(object): + def setupUi(self, MainWindow): + MainWindow.setObjectName("MainWindow") + MainWindow.resize(470, 270) + self.centralwidget = QtWidgets.QWidget(MainWindow) + self.centralwidget.setObjectName("centralwidget") + self.gridLayout_2 = QtWidgets.QGridLayout(self.centralwidget) + self.gridLayout_2.setObjectName("gridLayout_2") + self.gridLayout = QtWidgets.QGridLayout() + self.gridLayout.setObjectName("gridLayout") + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout.addItem(spacerItem, 5, 0, 1, 1) + self.b_termin_suchen = QtWidgets.QPushButton(self.centralwidget) + self.b_termin_suchen.setAutoDefault(False) + self.b_termin_suchen.setDefault(True) + self.b_termin_suchen.setFlat(False) + self.b_termin_suchen.setObjectName("b_termin_suchen") + self.gridLayout.addWidget(self.b_termin_suchen, 6, 1, 1, 1) + self.header_label = QtWidgets.QLabel(self.centralwidget) + font = QtGui.QFont() + font.setPointSize(14) + self.header_label.setFont(font) + self.header_label.setAlignment(QtCore.Qt.AlignCenter) + self.header_label.setObjectName("header_label") + self.gridLayout.addWidget(self.header_label, 0, 0, 1, 2) + self.b_code_generieren = QtWidgets.QPushButton(self.centralwidget) + self.b_code_generieren.setObjectName("b_code_generieren") + self.gridLayout.addWidget(self.b_code_generieren, 6, 0, 1, 1) + self.line = QtWidgets.QFrame(self.centralwidget) + self.line.setFrameShape(QtWidgets.QFrame.HLine) + self.line.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line.setObjectName("line") + self.gridLayout.addWidget(self.line, 1, 0, 1, 1) + self.line_2 = QtWidgets.QFrame(self.centralwidget) + self.line_2.setFrameShape(QtWidgets.QFrame.HLine) + self.line_2.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line_2.setObjectName("line_2") + self.gridLayout.addWidget(self.line_2, 1, 1, 1, 1) + self.pfad_layout = QtWidgets.QFormLayout() + self.pfad_layout.setObjectName("pfad_layout") + self.kontaktdaten_label = QtWidgets.QLabel(self.centralwidget) + self.kontaktdaten_label.setObjectName("kontaktdaten_label") + self.pfad_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.kontaktdaten_label) + self.gridLayout_3 = QtWidgets.QGridLayout() + self.gridLayout_3.setObjectName("gridLayout_3") + self.i_kontaktdaten_pfad = QtWidgets.QLineEdit(self.centralwidget) + self.i_kontaktdaten_pfad.setObjectName("i_kontaktdaten_pfad") + self.gridLayout_3.addWidget(self.i_kontaktdaten_pfad, 0, 0, 1, 1) + self.b_dateien_kontaktdaten = QtWidgets.QPushButton(self.centralwidget) + self.b_dateien_kontaktdaten.setObjectName("b_dateien_kontaktdaten") + self.gridLayout_3.addWidget(self.b_dateien_kontaktdaten, 0, 1, 1, 1) + self.pfad_layout.setLayout(0, QtWidgets.QFormLayout.FieldRole, self.gridLayout_3) + self.zeitspanne_label = QtWidgets.QLabel(self.centralwidget) + self.zeitspanne_label.setObjectName("zeitspanne_label") + self.pfad_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.zeitspanne_label) + self.gridLayout_4 = QtWidgets.QGridLayout() + self.gridLayout_4.setObjectName("gridLayout_4") + self.lineEdit = QtWidgets.QLineEdit(self.centralwidget) + self.lineEdit.setObjectName("lineEdit") + self.gridLayout_4.addWidget(self.lineEdit, 0, 0, 1, 1) + self.b_dateien_zeitspanne = QtWidgets.QPushButton(self.centralwidget) + self.b_dateien_zeitspanne.setObjectName("b_dateien_zeitspanne") + self.gridLayout_4.addWidget(self.b_dateien_zeitspanne, 0, 1, 1, 1) + self.pfad_layout.setLayout(1, QtWidgets.QFormLayout.FieldRole, self.gridLayout_4) + self.gridLayout.addLayout(self.pfad_layout, 4, 0, 1, 2) + self.gridLayout_2.addLayout(self.gridLayout, 0, 0, 1, 1) + MainWindow.setCentralWidget(self.centralwidget) + self.menubar = QtWidgets.QMenuBar(MainWindow) + self.menubar.setGeometry(QtCore.QRect(0, 0, 470, 21)) + self.menubar.setObjectName("menubar") + MainWindow.setMenuBar(self.menubar) + self.statusbar = QtWidgets.QStatusBar(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", "vaccipy")) + self.b_termin_suchen.setText(_translate("MainWindow", "Termin suchen")) + self.header_label.setText(_translate("MainWindow", "Vaccipy\n" +"\n" +"Willkommen bei der automatische Terminbuchung\n" +"für den Corona Impfterminservice ")) + self.b_code_generieren.setText(_translate("MainWindow", "Impf-Code generieren")) + self.kontaktdaten_label.setText(_translate("MainWindow", "Pfad Kontakdaten:")) + self.i_kontaktdaten_pfad.setText(_translate("MainWindow", "./data/kontaktdaten.json")) + self.b_dateien_kontaktdaten.setText(_translate("MainWindow", "Dateien")) + self.zeitspanne_label.setText(_translate("MainWindow", "Pfad Zeitspanne:")) + self.lineEdit.setText(_translate("MainWindow", "./data/zeitspanne.json")) + self.b_dateien_zeitspanne.setText(_translate("MainWindow", "Dateien")) + + +if __name__ == "__main__": + import sys + app = QtWidgets.QApplication(sys.argv) + MainWindow = QtWidgets.QMainWindow() + ui = Ui_MainWindow() + ui.setupUi(MainWindow) + MainWindow.show() + sys.exit(app.exec_()) diff --git a/tools/gui/main.ui b/tools/gui/main.ui new file mode 100644 index 00000000..d0e55428 --- /dev/null +++ b/tools/gui/main.ui @@ -0,0 +1,192 @@ + + + MainWindow + + + + 0 + 0 + 549 + 382 + + + + vaccipy + + + + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Termin suchen + + + false + + + true + + + false + + + + + + + + 14 + + + + Vaccipy + +Willkommen bei der automatische Terminbuchung +für den Corona Impfterminservice + + + Qt::AlignCenter + + + + + + + Impf-Code generieren + + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + + + + + + + Pfad Kontakdaten: + + + + + + + + + + + + true + + + + + + + Dateien + + + + + + + + + Pfad Zeitspanne: + + + + + + + + + + + + true + + + + + + + Dateien + + + + + + + + + + + Kontakdaten erstellen + + + + + + + Zeitspanne erstellen + + + + + + + + + + + 0 + 0 + 549 + 21 + + + + + + + + diff --git a/tools/gui/qtkontakt.py b/tools/gui/qtkontakt.py new file mode 100644 index 00000000..14159993 --- /dev/null +++ b/tools/gui/qtkontakt.py @@ -0,0 +1,31 @@ + +from PyQt5 import QtWidgets, uic +from PyQt5.QtCore import QTime + + +# Folgende Widgets stehen zur Verfügung: + +### QLineEdit #### +# i_plz_impfzentren +# i_code_impfzentren +# i_vorname +# i_nachname +# i_strasse +# i_hausnummer +# i_wohnort +# i_plz_wohnort +# i_telefon +# i_mail + +### QComboBox ### +# i_anrede_combo_box + +### QDialogButtonBox ### +# buttonBox +# Apply +# Cancel +# Reset + +class QtKontakt(QtWidgets.QMainWindow): + def __init__(self): + super().__init__() \ No newline at end of file diff --git a/tools/gui/qttimer.py b/tools/gui/qtzeiten.py similarity index 63% rename from tools/gui/qttimer.py rename to tools/gui/qtzeiten.py index 5dd2397d..19db9440 100644 --- a/tools/gui/qttimer.py +++ b/tools/gui/qtzeiten.py @@ -1,99 +1,67 @@ import os import json from PyQt5 import QtWidgets, uic -from PyQt5.QtCore import QTime +from PyQt5.QtCore import QTime, QDateTime # Folgende Widgets stehen zur Verfügung: ### Checkboxes ### -# mo_check_box -# di_check_box -# mi_check_box -# do_check_box -# fr_check_box -# so_check_box -# sa_check_box -# erster_termin_check_box -# zweiter_termin_check_box +# i_mo_check_box +# i_di_check_box +# i_mi_check_box +# i_do_check_box +# i_fr_check_box +# i_so_check_box +# i_sa_check_box +# i_erster_termin_check_box +# i_zweiter_termin_check_box ### QTimeEdit ### -# start_time -# end_time +# i_start_time_qtime +# i_end_time_qtime -### Buttons ### -# pushButton +### QDateEdit ### +# i_start_datum_qdate -### Labels ### -# header_label -# wochentage_label -# termine_anwenden_label -# uhr_header_label -# uhr_start_label -# uhr_end_label -# und_label +### QDialogButtonBox ### +# buttonBox +# Apply +# Cancel +# Reset PATH = os.path.dirname(os.path.realpath(__file__)) -# TODO: [ ] Wenn das Fenster geschlossen wir soll was passieren...? nicht speichern, aber Programm fortfahren -# TODO: [ ] .icon adden -# TODO: [X] Fenstergröße sollte man nicht verändern können -# TODO: [X] Fenster in den Vordergund bringen -# TODO: [X] Docstrings -# TODO: [X] Rückmeldung für erfolgreiches speichern - -class QtTimer(QtWidgets.QMainWindow): +class QtZeiten(QtWidgets.QDialog): """ Klasse für das erstellen einer zeitspanne.json mithilfe einer GUI / PyQt5 - Diese erbt von QtWidgets.QMainWindow + Diese erbt von QtWidgets.QDialog """ - def __init__(self, pfad_zeitspanne: str, pfad_fenster_layout: str): + def __init__(self, pfad_fenster_layout = os.path.join(PATH, "uhrzeiten.ui")): """ Ladet das angegebene Layout (wurde mit QT Designer erstellt https://www.qt.io/download) Das Fenster wird automtaisch nach dem erstellen der Klasse geöffnet Args: - pfad_zeitspanne (str): Speicherpfad für zeitspanne.json pfad_fester_layout (str): Speicherort der .ui - Datei """ - super(QtTimer, self).__init__() + super(QtZeiten, self).__init__() # Laden der .ui Datei und Anpassungen uic.loadUi(pfad_fenster_layout, self) - self.setFixedSize(self.size()) + self.i_start_datum_qdate.setMinimumDateTime(QDateTime.currentDateTime()) - # self.bestaetigen() soll beim Klicken auf Bestätigen aufgerufen werden - self.pushButton.clicked.connect(self.bestaetigt) + # Funktionen den Buttons zuweisen + self.buttonBox.accepted # Setzte leere Werte self.aktive_wochentage = list() self.start_uhrzeit: QTime = None self.end_uhrzeit: QTime = None self.aktive_termine = list() - self.speicherpfad = pfad_zeitspanne - - # GUI anzeigen - self.show() - # Workaround, damit das Fenster hoffentlich im Vordergrund ist - self.activateWindow() - - @staticmethod - def start(pfad_zeitspanne: str, pfad_fenster_layout=os.path.join(PATH, "qttimer.ui")): - """ - Öffnet eine GUI in dem der User seine Parameter angeben kann - Diese werden bei Bestätigung direkt in den mit übergebenen Pfad gespeichert - Anschließend schließt sich das Fenster - - Args: - pfad_zeitspanne (str): Speicherpfad für zeitspanne.json - pfad_fester_layout (str): Speicherort der .ui - Datei. Default: .\\qttimer.ui - """ - app = QtWidgets.QApplication(list()) - window = QtTimer(pfad_zeitspanne, pfad_fenster_layout) - app.exec_() def bestaetigt(self): """ @@ -119,6 +87,8 @@ def speicher_einstellungen(self): Speicherpfad wurde beim erstellen der Klasse mit übergeben """ + speicherpfad = self.__oeffne_file_dialog() + data = { "wochentage": self.aktive_wochentage, "startzeit": { @@ -132,7 +102,7 @@ def speicher_einstellungen(self): "einhalten_bei": self.aktive_termine } - with open(self.speicherpfad, 'w', encoding='utf-8') as f: + with open(speicherpfad, 'w', encoding='utf-8') as f: try: json.dump(data, f, ensure_ascii=False, indent=4) QtWidgets.QMessageBox.information(self, "Gepseichert", "Daten erfolgreich gespeichert") @@ -141,6 +111,7 @@ def speicher_einstellungen(self): QtWidgets.QMessageBox.critical(self, "Fehler!", "Daten konnten nicht gespeichert werden.") raise error + def __aktuallisiere_aktive_wochentage(self): """ Alle "checked" Wochentage in der GUI werden gesichert @@ -150,7 +121,7 @@ def __aktuallisiere_aktive_wochentage(self): # Alle Checkboxen der GUI selektieren und durchgehen # BUG: Wenn die reihenfolge im Layout geändert wird, stimmen die Wochentage nicht mehr 0 = Mo ... 6 = So - checkboxes = self.mo_check_box.parent().findChildren(QtWidgets.QCheckBox) + checkboxes = self.i_mo_check_box.parent().findChildren(QtWidgets.QCheckBox) for num, checkboxe in enumerate(checkboxes, 0): if checkboxe.isChecked(): self.aktive_wochentage.append(num) @@ -181,6 +152,14 @@ def __aktuallisiere_aktive_termine(self): if self.zweiter_termin_check_box.isChecked(): self.aktive_termine.append(2) + def __oeffne_file_dialog(self) -> str: + datei_data = QtWidgets.QFileDialog.saveFileContent(self, "Zeitspanne", os.path.join(PATH, "data"), "JSON Files (*.json)") + dateipfad = datei_data[0] + return dateipfad + if __name__ == "__main__": - QtTimer.start(f"{PATH}\\..\\zeitspanne.json") + app = QtWidgets.QApplication(list()) + window = QtZeiten() + window.show() + app.exec_() diff --git a/tools/gui/uhrzeiten.ui b/tools/gui/uhrzeiten.ui index 7000c25f..f9790753 100644 --- a/tools/gui/uhrzeiten.ui +++ b/tools/gui/uhrzeiten.ui @@ -1,384 +1,399 @@ - MainWindow - + Zeitspanne_Dialog + 0 0 - 513 - 398 + 764 + 489 - MainWindow + vaccipy - Zeitspanne - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - Qt::Horizontal - - - - - - - Hier kannst du Uhrzeiten und Tage eingrenzen, an dem ein Termin gebucht werden soll - - - Qt::AlignCenter - - - - - - - - - QFrame::Box - - - QFrame::Plain - - - - - - 0 - - - - - 1. Termin - - - true - - - - - - - 2. Termin - - - true - - - - - - - - - Qt::Horizontal - - - - - - - Nur Uhrzeiten zwischen - - - Qt::AlignCenter - - - - - - - - - Start: - - - - - - - Ende: - - - - - - - - - - - - - - - - Uhr - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - - - - QDateTimeEdit::HourSection - - - false - - - - - - - - - - Uhr - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - - - - Bei welchen Terminen sollen die Bedingungen gelten? - - - Qt::AlignCenter - - - - - - - - - Termin fühestens ab: - - - Qt::AlignCenter - - - - - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - QAbstractSpinBox::UpDownArrows - - - - - - - - - QDateTimeEdit::DaySection - - - true - - - - 2021 - 5 - 25 - - - - - - - - - - Qt::Horizontal - - - - - - - - - - - - vaccipy - - - Qt::AlignCenter - - - - - - - - - - 0 - 0 - - - - QFrame::Box - - - QFrame::Plain - - - - - - Dienstags - - - true - - - - - - - Montags - - - true - - - - - - - Qt::Horizontal - - - - - - - Donnerstags - - - true - - - - - - - Samstags - - - true - - - - - - - Sonntags - - - true - - - - - - - Mittwochs - - - true - - - - - - - Nur an folgenden Tagen: - - - - - - - Freitags - - - true - - - - - - - - - - - - Qt::Horizontal - - - - - - - - - QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Reset - - - - - + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Reset + + + + + + + + + Qt::Horizontal + + + + + + + + 12 + + + + Hier kannst du Uhrzeiten und Tage eingrenzen, an dem ein Termin gebucht werden soll + + + Qt::AlignCenter + + + + + + + + + QFrame::Box + + + QFrame::Plain + + + + + + 0 + + + + + 1. Termin + + + true + + + + + + + 2. Termin + + + true + + + + + + + + + Qt::Horizontal + + + + + + + Nur Uhrzeiten zwischen + + + Qt::AlignCenter + + + + + + + + + Start: + + + + + + + Ende: + + + + + + + + + + + + + + + + Uhr + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + + QDateTimeEdit::HourSection + + + false + + + + + + + + + + Uhr + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + + Bei welchen Terminen sollen die Bedingungen gelten? + + + Qt::AlignCenter + + + + + + + + + Termin fühestens ab: + + + Qt::AlignCenter + + + + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + QAbstractSpinBox::UpDownArrows + + + + + + + + + QDateTimeEdit::DaySection + + + true + + + + 2021 + 5 + 25 + + + + + + + + + + Qt::Horizontal + + + + + + + + + + + + + 14 + + + + vaccipy + + + Qt::AlignCenter + + + + + + + + + + 0 + 0 + + + + QFrame::Box + + + QFrame::Plain + + + + + + Dienstags + + + true + + + + + + + Montags + + + true + + + + + + + Qt::Horizontal + + + + + + + Donnerstags + + + true + + + + + + + Samstags + + + true + + + + + + + Sonntags + + + true + + + + + + + Mittwochs + + + true + + + + + + + Nur an folgenden Tagen: + + + + + + + Freitags + + + true + + + + + + + + + + + + Qt::Horizontal + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + From c65270787975d0160dd67cefa1e15d8f318ec586 Mon Sep 17 00:00:00 2001 From: Floskinner <58706771+Floskinner@users.noreply.github.com> Date: Wed, 26 May 2021 21:02:58 +0200 Subject: [PATCH 04/25] umstrukturierung --- tools/gui/qtzeiten.py | 164 +++++++++++++++++++++++++++--------------- 1 file changed, 105 insertions(+), 59 deletions(-) diff --git a/tools/gui/qtzeiten.py b/tools/gui/qtzeiten.py index 19db9440..1a4252e7 100644 --- a/tools/gui/qtzeiten.py +++ b/tools/gui/qtzeiten.py @@ -38,7 +38,7 @@ class QtZeiten(QtWidgets.QDialog): Diese erbt von QtWidgets.QDialog """ - def __init__(self, pfad_fenster_layout = os.path.join(PATH, "uhrzeiten.ui")): + def __init__(self, standard_speicherpfad:str, pfad_fenster_layout=os.path.join(PATH, "uhrzeiten.ui")): """ Ladet das angegebene Layout (wurde mit QT Designer erstellt https://www.qt.io/download) Das Fenster wird automtaisch nach dem erstellen der Klasse geöffnet @@ -49,58 +49,40 @@ def __init__(self, pfad_fenster_layout = os.path.join(PATH, "uhrzeiten.ui")): super(QtZeiten, self).__init__() + self.standard_speicherpfad = standard_speicherpfad + self.pfad_fenster_layout = pfad_fenster_layout + # Laden der .ui Datei und Anpassungen - uic.loadUi(pfad_fenster_layout, self) + uic.loadUi(self.pfad_fenster_layout, self) self.i_start_datum_qdate.setMinimumDateTime(QDateTime.currentDateTime()) - # Funktionen den Buttons zuweisen - self.buttonBox.accepted - - # Setzte leere Werte - self.aktive_wochentage = list() - self.start_uhrzeit: QTime = None - self.end_uhrzeit: QTime = None - self.aktive_termine = list() + # Funktionen für Buttonbox zuweisen + self.buttonBox.clicked.connect(self.__button_clicked) def bestaetigt(self): """ Aktuallisiert alle Werte und Speichert gleichzeig die Aktuellen Werte """ - # Alle Werte von aus der GUI aktuallisieren - self.__aktuallisiere_aktive_wochentage() - self.__aktuallisiere_aktive_termine() - - if not self.__aktuallisiere_uhrzeiten(): + try: + self.speicher_einstellungen() + self.close() + except ValueError as error: QtWidgets.QMessageBox.critical(self, "Ungültige Eingabe!", "Start-Uhrzeit ist später als End-Uhrzeit!") - return - - # Speichert alle Werte ab - self.speicher_einstellungen() - - self.close() + except (TypeError, IOError, FileNotFoundError) as error: + QtWidgets.QMessageBox.critical(self, "Fehler beim Speichern!", "Bitte erneut versuchen!") + def speicher_einstellungen(self): """ Speichert alle Werte in der entsprechenden JSON-Formatierung - Speicherpfad wurde beim erstellen der Klasse mit übergeben + Speicherpfad wird vom User abgefragt """ speicherpfad = self.__oeffne_file_dialog() - data = { - "wochentage": self.aktive_wochentage, - "startzeit": { - "h": self.start_uhrzeit.hour(), - "m": self.start_uhrzeit.minute() - }, - "endzeit": { - "h": self.end_uhrzeit.hour(), - "m": self.end_uhrzeit.minute() - }, - "einhalten_bei": self.aktive_termine - } + data = self.__get_alle_werte() with open(speicherpfad, 'w', encoding='utf-8') as f: try: @@ -111,55 +93,119 @@ def speicher_einstellungen(self): QtWidgets.QMessageBox.critical(self, "Fehler!", "Daten konnten nicht gespeichert werden.") raise error + def __button_clicked(self, button): + clicked_button = self.buttonBox.standardButton(button) + if clicked_button == QtWidgets.QDialogButtonBox.Apply: + self.bestaetigt() + if clicked_button == QtWidgets.QDialogButtonBox.Reset: + self.__reset() + elif clicked_button == QtWidgets.QDialogButtonBox.Cancel: + self.close() + + def __get_alle_werte(self) -> dict: + """ + Gibt alle nötigen Daten richtig formatiert zum abspeichern + + Returns: + dict: alle Daten + """ + + aktive_wochentage = self.__get_aktive_wochentage() + uhrzeiten = self.__get_uhrzeiten() + termine = self.__get_aktive_termine() + + zeitspanne = { + "wochentage": aktive_wochentage, + "startzeit": uhrzeiten["startzeit"], + "endzeiten": uhrzeiten["endzeit"], + "einhalten_bei": termine + } + return zeitspanne - def __aktuallisiere_aktive_wochentage(self): + def __get_aktive_wochentage(self) -> list: """ - Alle "checked" Wochentage in der GUI werden gesichert + Alle "checked" Wochentage in der GUI + + Returns: + list: Alle aktiven Wochentage """ - # Zur sicherheit alte Werte löschen - self.aktive_wochentage.clear() + + # Leere liste + aktive_wochentage = list() # Alle Checkboxen der GUI selektieren und durchgehen # BUG: Wenn die reihenfolge im Layout geändert wird, stimmen die Wochentage nicht mehr 0 = Mo ... 6 = So checkboxes = self.i_mo_check_box.parent().findChildren(QtWidgets.QCheckBox) for num, checkboxe in enumerate(checkboxes, 0): if checkboxe.isChecked(): - self.aktive_wochentage.append(num) + aktive_wochentage.append(num) - def __aktuallisiere_uhrzeiten(self) -> bool: - """ - Aktuallisert die eingegebenen Uhrzeiten der GUI + return aktive_wochentage + + def __get_uhrzeiten(self) -> dict: + """ + Erstellt ein Dict mit ensprechenden start und endzeiten + + Raises: + ValueError: start uhrzeit < end uhrzeit + + Returns: + dict: fertiges dict zum speichern mit startzeit und endzeit """ - if self.start_time.time() < self.end_time.time(): - self.start_uhrzeit = self.start_time.time() - self.end_uhrzeit = self.end_time.time() + start_uhrzeit: QTime = self.i_start_time_qtime.time() + end_uhrzeit: QTime = self.i_end_time_qtime.time() + + if start_uhrzeit >= end_uhrzeit: + raise ValueError - return True - else: - return False + uhrzeiten = { + "startzeit": { + "h": start_uhrzeit.hour(), + "m": start_uhrzeit.minute() + }, + "endzeit": { + "h": end_uhrzeit.hour(), + "m": end_uhrzeit.minute() + } + } + return uhrzeiten - def __aktuallisiere_aktive_termine(self): + def __get_aktive_termine(self) -> list: """ - Aktuallisert die eingegebenen Uhrzeiten der GUI + Liste mit den aktiven Terminen 1 = 1. Termin 2 = 2. Termin + + Returns: + list: Termine """ - # Zur sicherheit alte Werte löschen - self.aktive_termine.clear() + aktive_termine = list() - if self.erster_termin_check_box.isChecked(): - self.aktive_termine.append(1) - if self.zweiter_termin_check_box.isChecked(): - self.aktive_termine.append(2) + if self.i_erster_termin_check_box.isChecked(): + aktive_termine.append(1) + if self.i_zweiter_termin_check_box.isChecked(): + aktive_termine.append(2) + return aktive_termine def __oeffne_file_dialog(self) -> str: - datei_data = QtWidgets.QFileDialog.saveFileContent(self, "Zeitspanne", os.path.join(PATH, "data"), "JSON Files (*.json)") - dateipfad = datei_data[0] + """ + Öffnet einen File Dialog, der den Speicherort festlegt + + Returns: + str: Speicherpfad + """ + + datei_data = QtWidgets.QFileDialog.getSaveFileName(self, "Zeitspanne", self.standard_speicherpfad, "JSON Files (*.json)") + dateipfad = datei_data[0] # (Pfad, Dateityp) return dateipfad + def __reset(self): + #TODO: Reset + pass + if __name__ == "__main__": app = QtWidgets.QApplication(list()) - window = QtZeiten() + window = QtZeiten(".\\zeitspanne.json") window.show() app.exec_() From f0c1f54745914d59c24709d7b7373c6a5f4670ce Mon Sep 17 00:00:00 2001 From: Floskinner <58706771+Floskinner@users.noreply.github.com> Date: Wed, 26 May 2021 22:10:46 +0200 Subject: [PATCH 05/25] kontaktdaten implementriert --- gui.py | 9 +- tools/gui/kontaktdaten.ui | 664 ++++++++++++++++++++------------------ tools/gui/qtkontakt.py | 112 ++++++- tools/gui/qtzeiten.py | 4 +- tools/gui/uhrzeiten.ui | 2 +- 5 files changed, 462 insertions(+), 329 deletions(-) diff --git a/gui.py b/gui.py index 1ce5dd1e..2b531a6b 100644 --- a/gui.py +++ b/gui.py @@ -6,6 +6,7 @@ from PyQt5 import QtWidgets, uic from tools.gui.qtzeiten import QtZeiten +from tools.gui.qtkontakt import QtKontakt from tools.its import ImpfterminService PATH = os.path.dirname(os.path.realpath(__file__)) @@ -68,6 +69,7 @@ def __termin_suchen(self): code = kontaktdaten["code"] plz_impfzentren = kontaktdaten["plz_impfzentren"] + #TODO: starte es in einem extra thread, sonst hängt sich die GUI auf ImpfterminService.terminsuche(code=code, plz_impfzentren=plz_impfzentren, kontakt=kontakt, zeitspanne=zeitspanne, PATH=PATH) @@ -79,7 +81,6 @@ def __get_kontaktdaten(self) -> dict: if not os.path.isfile(self.pfad_kontaktdaten): self.kontaktdaten_erstellen() - pass with open(self.pfad_kontaktdaten, "r", encoding='utf-8') as f: kontaktdaten = json.load(f) @@ -119,11 +120,13 @@ def start_gui(): def kontaktdaten_erstellen(self): - pass + dialog = QtKontakt(self.pfad_kontaktdaten) + dialog.show() + dialog.exec_() def zeitspanne_erstellen(self): - dialog = QtZeiten() + dialog = QtZeiten(self.pfad_zeitspanne) dialog.show() dialog.exec_() diff --git a/tools/gui/kontaktdaten.ui b/tools/gui/kontaktdaten.ui index 62bdaf07..2141bd1b 100644 --- a/tools/gui/kontaktdaten.ui +++ b/tools/gui/kontaktdaten.ui @@ -1,347 +1,371 @@ - MainWindow - + Dialog + 0 0 - 541 - 383 + 715 + 428 - MainWindow + vaccipy - Kontaktdaten - - - - - - - - Automatische Terminbuchung für den Corona Impfterminservice - - - Qt::AlignCenter - - - - - - - vaccipy - - - Qt::AlignHCenter|Qt::AlignTop - - - - - - - - - - - PLZ's der Impfzentren: - - - - - - - - - - - - - Beispiel: 68163, 69124, 69469 - - - true - - - - - - - Code: - - - - - - - NNNN-NNNN-NNNN - - - -- - - - A1C3-D2FG-4IJK - - - false - - - - - - - Anrede: - - - - - - - false - - - Bitte Wählen - - - QComboBox::NoInsert - - + + + + + + 14 + + + + vaccipy + + + Qt::AlignCenter + + + + + + + + + 6 + + + 6 + + + 6 + + + 6 + + + - Bitte Wählen + PLZ's der Impfzentren: + + + + + + + - - - Frau + + + + Beispiel: 68163, 69124, 69469 + + + true - - + + + + - Herr + Code: + + + + + + + NNNN-NNNN-NNNN - - - ... + -- - - - - - - - Vorname: - - - - - - - Max - - - true - - - - - - - Nachname: - - - - - - - Mustermann - - - true - - - - - - - Straße: - - - - - - - - - - 100 - 0 - - + + A1C3-D2FG-4IJK + + + true + + + + + + + Anrede: + + + + + + + false + + + Bitte Wählen + + + QComboBox::NoInsert + + - Hausnummer: - - - - - - - - 0 - 0 - - - - true - - - - - - - - - - true - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - PLZ Wohnort: - - - - - - - - - true - - - - - - - - 100 - 0 - + Bitte Wählen + + - Wohnort: - - - - - - - 99999 + Frau - - 88045 - - - true - - - - - - - Qt::Horizontal + + + + Herr - - - 40 - 20 - + + + + ... - - - - - - - - Telefonnummer: - - - - - - - +4\9 - - - - - - false - - - - - - - Mail: - - - - - - - true - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Reset - - - false - - - - - + + + + + + + Vorname: + + + + + + + Max + + + false + + + + + + + Nachname: + + + + + + + Mustermann + + + false + + + + + + + Straße: + + + + + + + + + + 100 + 0 + + + + Hausnummer: + + + + + + + + 0 + 0 + + + + false + + + + + + + + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + PLZ Wohnort: + + + + + + + + + false + + + + + + + + 100 + 0 + + + + Wohnort: + + + + + + + 99999 + + + 88045 + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Telefonnummer: + + + + + + + + + + +49 + + + +4917299334455 + + + false + + + + + + + Mail: + + + + + + + false + + + + + + + + + + + + 12 + + + + Gebe Bitte deine Persöhnliche Daten ein + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Reset|QDialogButtonBox::Save + + + + diff --git a/tools/gui/qtkontakt.py b/tools/gui/qtkontakt.py index 14159993..4fda0c39 100644 --- a/tools/gui/qtkontakt.py +++ b/tools/gui/qtkontakt.py @@ -1,3 +1,5 @@ +import os +import json from PyQt5 import QtWidgets, uic from PyQt5.QtCore import QTime @@ -26,6 +28,110 @@ # Cancel # Reset -class QtKontakt(QtWidgets.QMainWindow): - def __init__(self): - super().__init__() \ No newline at end of file +PATH = os.path.dirname(os.path.realpath(__file__)) + + +class QtKontakt(QtWidgets.QDialog): + def __init__(self, standard_speicherpfad:str, pfad_fenster_layout=os.path.join(PATH, "kontaktdaten.ui")): + super().__init__() + + self.standard_speicherpfad = standard_speicherpfad + + # Laden der .ui Datei + uic.loadUi(pfad_fenster_layout, self) + + # Funktionen für Buttonbox zuweisen + self.buttonBox.clicked.connect(self.__button_clicked) + + def bestaetigt(self): + try: + self.speicher_einstellungen() + self.close() + except (TypeError, IOError, FileNotFoundError) as error: + QtWidgets.QMessageBox.critical(self, "Fehler beim Speichern!", "Bitte erneut versuchen!") + print(error) + + def speicher_einstellungen(self): + """ + Speichert alle Werte in der entsprechenden JSON-Formatierung + Speicherpfad wird vom User abgefragt + """ + + speicherpfad = self.__oeffne_file_dialog() + + data = self.__get_alle_werte() + + with open(speicherpfad, 'w', encoding='utf-8') as f: + try: + json.dump(data, f, ensure_ascii=False, indent=4) + QtWidgets.QMessageBox.information(self, "Gepseichert", "Daten erfolgreich gespeichert") + + except (TypeError, IOError, FileNotFoundError) as error: + QtWidgets.QMessageBox.critical(self, "Fehler!", "Daten konnten nicht gespeichert werden.") + raise error + + def __button_clicked(self, button): + clicked_button = self.buttonBox.standardButton(button) + if clicked_button == QtWidgets.QDialogButtonBox.Save: + self.bestaetigt() + if clicked_button == QtWidgets.QDialogButtonBox.Reset: + self.__reset() + elif clicked_button == QtWidgets.QDialogButtonBox.Cancel: + self.close() + + def __get_alle_werte(self) -> dict: + plz_zentrum_raw = self.i_plz_impfzentren.text() + code = self.i_code_impfzentren.text().strip() + anrede = self.i_anrede_combo_box.currentText().strip() + vorname = self.i_vorname.text().strip() + nachname = self.i_nachname.text().strip() + strasse = self.i_strasse.text().strip() + hausnummer = self.i_hausnummer.text().strip() + wohnort = self.i_wohnort.text().strip() + plz_wohnort = self.i_plz_wohnort.text().strip() + telefon = self.i_telefon.text().strip() + mail = self.i_mail.text().strip() + + # PLZ der Zentren in liste und "strippen" + plz_zentren = plz_zentrum_raw.split(",") + plz_zentren = [plz.strip() for plz in plz_zentren] + + kontaktdaten = { + "plz_impfzentren": plz_zentren, + "code": code, + "kontakt": { + "anrede": anrede, + "vorname": vorname, + "nachname": nachname, + "strasse": strasse, + "hausnummer": hausnummer, + "plz": plz_wohnort, + "ort": wohnort, + "phone": telefon, + "notificationChannel": "email", + "notificationReceiver": mail + } + } + return kontaktdaten + + def __oeffne_file_dialog(self) -> str: + """ + Öffnet einen File Dialog, der den Speicherort festlegt + + Returns: + str: Speicherpfad + """ + + datei_data = QtWidgets.QFileDialog.getSaveFileName(self, "Kontaktdaten", self.standard_speicherpfad, "JSON Files (*.json)") + dateipfad = datei_data[0] # (Pfad, Dateityp) + return dateipfad + + def __reset(self): + pass + + +if __name__ == "__main__": + app = QtWidgets.QApplication(list()) + window = QtKontakt("./kontaktdaten.json") + window.show() + app.exec_() diff --git a/tools/gui/qtzeiten.py b/tools/gui/qtzeiten.py index 1a4252e7..770210db 100644 --- a/tools/gui/qtzeiten.py +++ b/tools/gui/qtzeiten.py @@ -62,7 +62,7 @@ def __init__(self, standard_speicherpfad:str, pfad_fenster_layout=os.path.join(P def bestaetigt(self): """ - Aktuallisiert alle Werte und Speichert gleichzeig die Aktuellen Werte + Speichert die aktuellen Werte """ try: @@ -95,7 +95,7 @@ def speicher_einstellungen(self): def __button_clicked(self, button): clicked_button = self.buttonBox.standardButton(button) - if clicked_button == QtWidgets.QDialogButtonBox.Apply: + if clicked_button == QtWidgets.QDialogButtonBox.Save: self.bestaetigt() if clicked_button == QtWidgets.QDialogButtonBox.Reset: self.__reset() diff --git a/tools/gui/uhrzeiten.ui b/tools/gui/uhrzeiten.ui index f9790753..9c6887e5 100644 --- a/tools/gui/uhrzeiten.ui +++ b/tools/gui/uhrzeiten.ui @@ -22,7 +22,7 @@ Qt::Horizontal - QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Reset + QDialogButtonBox::Cancel|QDialogButtonBox::Reset|QDialogButtonBox::Save From 6069a24fd6fbabd6e0df1b9036db41ff581ff267 Mon Sep 17 00:00:00 2001 From: Floskinner <58706771+Floskinner@users.noreply.github.com> Date: Wed, 26 May 2021 22:24:47 +0200 Subject: [PATCH 06/25] added docstrings --- gui.py | 112 +++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 81 insertions(+), 31 deletions(-) diff --git a/gui.py b/gui.py index 2b531a6b..6d7d9972 100644 --- a/gui.py +++ b/gui.py @@ -28,7 +28,15 @@ class HauptGUI(QtWidgets.QMainWindow): # b_neue_kontaktdaten # b_neue_zeitspanne - def __init__(self, pfad_fenster_layout=os.path.join(PATH, "tools/gui/main.ui")): + # TODO: Ausgabe der cmd in der GUI wiederspiegelen - wenn sowas überhaupt geht + def __init__(self, pfad_fenster_layout: str = os.path.join(PATH, "tools/gui/main.ui")): + """ + Main der GUI Anwendung + + Args: + pfad_fenster_layout (str, optional): Ort der main.ui. Defaults to os.path.join(PATH, "tools/gui/main.ui"). + """ + super().__init__() # Laden der .ui Datei und Anpassungen @@ -60,8 +68,39 @@ def __init__(self, pfad_fenster_layout=os.path.join(PATH, "tools/gui/main.ui")): # Workaround, damit das Fenster hoffentlich im Vordergrund ist self.activateWindow() + @staticmethod + def start_gui(): + """ + Startet die GUI Anwendung + """ + + app = QtWidgets.QApplication(list()) + window = HauptGUI() + app.exec_() + + def kontaktdaten_erstellen(self): + """ + Ruft den Dialog für die Kontaktdaten auf + """ + + dialog = QtKontakt(self.pfad_kontaktdaten) + dialog.show() + dialog.exec_() + + def zeitspanne_erstellen(self): + """ + Ruft den Dialog für die Zeitspanne auf + """ + + dialog = QtZeiten(self.pfad_zeitspanne) + dialog.show() + dialog.exec_() def __termin_suchen(self): + """ + Startet den Prozess der terminsuche mit Impfterminservice.terminsuche + """ + kontaktdaten = self.__get_kontaktdaten() zeitspanne = self.__get_zeitspanne() @@ -69,15 +108,24 @@ def __termin_suchen(self): code = kontaktdaten["code"] plz_impfzentren = kontaktdaten["plz_impfzentren"] - #TODO: starte es in einem extra thread, sonst hängt sich die GUI auf + # TODO: starte es in einem extra thread, sonst hängt sich die GUI auf ImpfterminService.terminsuche(code=code, plz_impfzentren=plz_impfzentren, kontakt=kontakt, zeitspanne=zeitspanne, PATH=PATH) - def __code_generieren(self): - pass + """ + Startet den Prozess der Codegenerierung + """ + # TODO: code generierung implementieren + pass def __get_kontaktdaten(self) -> dict: + """ + Ladet die Kontakdaten aus dem in der GUI hinterlegten Pfad + + Returns: + dict: Kontakdaten + """ if not os.path.isfile(self.pfad_kontaktdaten): self.kontaktdaten_erstellen() @@ -87,8 +135,14 @@ def __get_kontaktdaten(self) -> dict: return kontaktdaten - def __get_zeitspanne(self) -> dict: + """ + Ladet die Zeitspanne aus dem in der GUI hinterlegtem Pfad + + Returns: + dict: Zeitspanne + """ + if not os.path.isfile(self.pfad_zeitspanne): self.zeitspanne_erstellen() @@ -97,42 +151,38 @@ def __get_zeitspanne(self) -> dict: return zeitspanne - def __oeffne_file_dialog(self, datei: str): - datei_data = QtWidgets.QFileDialog.getOpenFileName(self, datei, os.path.join(PATH, "data"), "JSON Files (*.json)") - dateipfad = datei_data[0] + """ + Öffnet einen "File-Picker", der entsprechende Werte für die kontaktdaten / zeitspanne einliest - if datei == "kontaktdaten": - self.i_kontaktdaten_pfad.setText(dateipfad) - else: - self.i_zeitspanne_pfad.setText(dateipfad) + Args: + datei (str): Startpfad vom File-Picker + """ + # Öffnet den "File-Picker" vom System um ein bereits existierende Datei auszuwählen + datei_data = QtWidgets.QFileDialog.getOpenFileName(self, datei, os.path.join(PATH, "data"), "JSON Files (*.json)") + dateipfad = datei_data[0] # (pfad, typ) + + if dateipfad: + if datei == "kontaktdaten": + self.i_kontaktdaten_pfad.setText(dateipfad) + else: + self.i_zeitspanne_pfad.setText(dateipfad) def __update_pfade(self): + """ + Wird ein Pfad in der GUI verändert, so werden die entsprechende Attribute angepasst + """ + self.pfad_kontaktdaten = self.i_kontaktdaten_pfad.text() self.pfad_zeitspanne = self.i_zeitspanne_pfad.text() - @staticmethod - def start_gui(): - app = QtWidgets.QApplication(list()) - window = HauptGUI() - app.exec_() - - - def kontaktdaten_erstellen(self): - dialog = QtKontakt(self.pfad_kontaktdaten) - dialog.show() - dialog.exec_() - - - def zeitspanne_erstellen(self): - dialog = QtZeiten(self.pfad_zeitspanne) - dialog.show() - dialog.exec_() - - def main(): + """ + Startet die GUI-Anwendung + """ + HauptGUI.start_gui() From 4688a0fb53d234dbb109085408c87f3a62dcc707 Mon Sep 17 00:00:00 2001 From: Floskinner <58706771+Floskinner@users.noreply.github.com> Date: Wed, 26 May 2021 22:35:02 +0200 Subject: [PATCH 07/25] Added docstring / comments --- gui.py | 3 ++- tools/gui/qtkontakt.py | 32 +++++++++++++++++++++++++++++++- tools/gui/qtzeiten.py | 29 +++++++++++++++++++---------- 3 files changed, 52 insertions(+), 12 deletions(-) diff --git a/gui.py b/gui.py index 6d7d9972..1871e043 100644 --- a/gui.py +++ b/gui.py @@ -34,7 +34,8 @@ def __init__(self, pfad_fenster_layout: str = os.path.join(PATH, "tools/gui/main Main der GUI Anwendung Args: - pfad_fenster_layout (str, optional): Ort der main.ui. Defaults to os.path.join(PATH, "tools/gui/main.ui"). + pfad_fenster_layout (str, optional): Ladet das angegebene Layout (wurde mit QT Designer erstellt https://www.qt.io/download). + Defaults to os.path.join(PATH, "tools/gui/main.ui"). """ super().__init__() diff --git a/tools/gui/qtkontakt.py b/tools/gui/qtkontakt.py index 4fda0c39..a77f02fd 100644 --- a/tools/gui/qtkontakt.py +++ b/tools/gui/qtkontakt.py @@ -44,6 +44,10 @@ def __init__(self, standard_speicherpfad:str, pfad_fenster_layout=os.path.join(P self.buttonBox.clicked.connect(self.__button_clicked) def bestaetigt(self): + """ + Versucht die Daten zu speichern und schließt sich anschließend selbst + """ + try: self.speicher_einstellungen() self.close() @@ -71,6 +75,13 @@ def speicher_einstellungen(self): raise error def __button_clicked(self, button): + """ + Zuweisung der einzelnen Funktionen der Buttons in der ButtonBox + + Args: + button (PyQt5.QtWidgets.QPushButton): Button welcher gedrückt wurde + """ + clicked_button = self.buttonBox.standardButton(button) if clicked_button == QtWidgets.QDialogButtonBox.Save: self.bestaetigt() @@ -80,6 +91,13 @@ def __button_clicked(self, button): self.close() def __get_alle_werte(self) -> dict: + """ + Holt sich alle Werte aus der GUI und gibt diese fertig zum speichern zurück + + Returns: + dict: User eingaben + """ + plz_zentrum_raw = self.i_plz_impfzentren.text() code = self.i_code_impfzentren.text().strip() anrede = self.i_anrede_combo_box.currentText().strip() @@ -118,18 +136,30 @@ def __oeffne_file_dialog(self) -> str: """ Öffnet einen File Dialog, der den Speicherort festlegt + Raises: + FileNotFoundError: Wird geworfen, wenn kein Pfad angegeben wurde + Returns: - str: Speicherpfad + str: speicherpfad """ datei_data = QtWidgets.QFileDialog.getSaveFileName(self, "Kontaktdaten", self.standard_speicherpfad, "JSON Files (*.json)") dateipfad = datei_data[0] # (Pfad, Dateityp) + + if not dateipfad: + raise FileNotFoundError + return dateipfad def __reset(self): + """ + Setzt alle Werte in der GUI zurück + """ + pass +# Zum schnellen einzeltesten if __name__ == "__main__": app = QtWidgets.QApplication(list()) window = QtKontakt("./kontaktdaten.json") diff --git a/tools/gui/qtzeiten.py b/tools/gui/qtzeiten.py index 770210db..7fb21017 100644 --- a/tools/gui/qtzeiten.py +++ b/tools/gui/qtzeiten.py @@ -38,17 +38,18 @@ class QtZeiten(QtWidgets.QDialog): Diese erbt von QtWidgets.QDialog """ - def __init__(self, standard_speicherpfad:str, pfad_fenster_layout=os.path.join(PATH, "uhrzeiten.ui")): + def __init__(self, standard_speicherpfad: str, pfad_fenster_layout=os.path.join(PATH, "uhrzeiten.ui")): """ - Ladet das angegebene Layout (wurde mit QT Designer erstellt https://www.qt.io/download) - Das Fenster wird automtaisch nach dem erstellen der Klasse geöffnet + Eingabe der Zeitkonfigurationen Args: - pfad_fester_layout (str): Speicherort der .ui - Datei + standard_speicherpfad (str): standard speicherpfad der JSON-Datei + pfad_fenster_layout (str, optional): Layout des Dialogs. Defaults to os.path.join(PATH, "uhrzeiten.ui"). """ super(QtZeiten, self).__init__() + # Startwerte setzten self.standard_speicherpfad = standard_speicherpfad self.pfad_fenster_layout = pfad_fenster_layout @@ -56,13 +57,12 @@ def __init__(self, standard_speicherpfad:str, pfad_fenster_layout=os.path.join(P uic.loadUi(self.pfad_fenster_layout, self) self.i_start_datum_qdate.setMinimumDateTime(QDateTime.currentDateTime()) - # Funktionen für Buttonbox zuweisen self.buttonBox.clicked.connect(self.__button_clicked) def bestaetigt(self): """ - Speichert die aktuellen Werte + Speichert die aktuellen Werte und schließt anschließend den Dialog """ try: @@ -72,7 +72,6 @@ def bestaetigt(self): QtWidgets.QMessageBox.critical(self, "Ungültige Eingabe!", "Start-Uhrzeit ist später als End-Uhrzeit!") except (TypeError, IOError, FileNotFoundError) as error: QtWidgets.QMessageBox.critical(self, "Fehler beim Speichern!", "Bitte erneut versuchen!") - def speicher_einstellungen(self): """ @@ -81,7 +80,6 @@ def speicher_einstellungen(self): """ speicherpfad = self.__oeffne_file_dialog() - data = self.__get_alle_werte() with open(speicherpfad, 'w', encoding='utf-8') as f: @@ -94,9 +92,16 @@ def speicher_einstellungen(self): raise error def __button_clicked(self, button): + """ + Zuweisung der einzelnen Funktionen der Buttons in der ButtonBox + + Args: + button (PyQt5.QtWidgets.QPushButton): Button welcher gedrückt wurde + """ + clicked_button = self.buttonBox.standardButton(button) if clicked_button == QtWidgets.QDialogButtonBox.Save: - self.bestaetigt() + self.bestaetigt() if clicked_button == QtWidgets.QDialogButtonBox.Reset: self.__reset() elif clicked_button == QtWidgets.QDialogButtonBox.Cancel: @@ -109,7 +114,7 @@ def __get_alle_werte(self) -> dict: Returns: dict: alle Daten """ - + aktive_wochentage = self.__get_aktive_wochentage() uhrzeiten = self.__get_uhrzeiten() termine = self.__get_aktive_termine() @@ -197,6 +202,10 @@ def __oeffne_file_dialog(self) -> str: datei_data = QtWidgets.QFileDialog.getSaveFileName(self, "Zeitspanne", self.standard_speicherpfad, "JSON Files (*.json)") dateipfad = datei_data[0] # (Pfad, Dateityp) + + if not dateipfad: + raise FileNotFoundError + return dateipfad def __reset(self): From d16aca056d777607b6ed70864a287cf84bee7e78 Mon Sep 17 00:00:00 2001 From: Floskinner <58706771+Floskinner@users.noreply.github.com> Date: Wed, 26 May 2021 22:51:00 +0200 Subject: [PATCH 08/25] =?UTF-8?q?Fr=C3=BCheste=20Terminbuchung=20implement?= =?UTF-8?q?iert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/gui/qtzeiten.py | 18 +++++++++++++++--- tools/its.py | 12 +++++++----- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/tools/gui/qtzeiten.py b/tools/gui/qtzeiten.py index 7fb21017..c0be3595 100644 --- a/tools/gui/qtzeiten.py +++ b/tools/gui/qtzeiten.py @@ -1,7 +1,7 @@ import os import json from PyQt5 import QtWidgets, uic -from PyQt5.QtCore import QTime, QDateTime +from PyQt5.QtCore import QTime, QDate ,QDateTime # Folgende Widgets stehen zur Verfügung: @@ -118,12 +118,18 @@ def __get_alle_werte(self) -> dict: aktive_wochentage = self.__get_aktive_wochentage() uhrzeiten = self.__get_uhrzeiten() termine = self.__get_aktive_termine() + start_datum = self.__get_start_datum() zeitspanne = { "wochentage": aktive_wochentage, "startzeit": uhrzeiten["startzeit"], - "endzeiten": uhrzeiten["endzeit"], - "einhalten_bei": termine + "endzeit": uhrzeiten["endzeit"], + "einhalten_bei": termine, + "startdatum": { + "jahr": start_datum.year(), + "monat": start_datum.month(), + "tag": start_datum.day() + }, } return zeitspanne @@ -176,6 +182,12 @@ def __get_uhrzeiten(self) -> dict: } return uhrzeiten + def __get_start_datum(self) -> QDate: + """ + Aktuallisiert das Startdatum + """ + return self.i_start_datum_qdate.date() + def __get_aktive_termine(self) -> list: """ Liste mit den aktiven Terminen 1 = 1. Termin 2 = 2. Termin diff --git a/tools/its.py b/tools/its.py index e1dcdff9..e67edef9 100644 --- a/tools/its.py +++ b/tools/its.py @@ -3,7 +3,7 @@ import sys import time from base64 import b64encode -from datetime import datetime +from datetime import datetime, date from datetime import time as dtime from random import choice @@ -485,7 +485,7 @@ def login(self): @retry_on_failure() def termin_suchen(self, plz: int, zeitspanne: dict): """Es wird nach einen verfügbaren Termin in der gewünschten PLZ gesucht. - Ausgewählt wird der erstbeste Termin (!). + Ausgewählt wird der erstbeste Termin, welcher im entsprechenden Zeitraum liegt (!). Zurückgegeben wird das Ergebnis der Abfrage und der Status-Code. Bei Status-Code > 400 müssen die Cookies erneuert werden. @@ -518,7 +518,8 @@ def termin_suchen(self, plz: int, zeitspanne: dict): terminpaare = res_json.get("termine") if terminpaare: # Checken ob verfügbare terminpaare in angegebener Zeitspanne liegt - if zeitspanne["einhalten_bei"]: + # Falls daten nicht vorhanden - einfach alle aktzeptieren + if zeitspanne.get("einhalten_bei"): terminpaare_in_zeitspanne = list() # Alle Terminpaare durchgehen @@ -530,6 +531,7 @@ def termin_suchen(self, plz: int, zeitspanne: dict): # Soll einer der Beiden Termine überprüft werden if num in zeitspanne["einhalten_bei"]: + startdatum = date(zeitspanne["startdatum"]["jahr"], zeitspanne["startdatum"]["monat"], zeitspanne["startdatum"]["tag"]) startzeit = dtime(zeitspanne["startzeit"]["h"], zeitspanne["startzeit"]["m"]) endzeit = dtime(zeitspanne["endzeit"]["h"], zeitspanne["endzeit"]["m"]) wochentage = zeitspanne["wochentage"] @@ -537,7 +539,7 @@ def termin_suchen(self, plz: int, zeitspanne: dict): termin_zeit = datetime.fromtimestamp(int(termin["begin"])/1000) # Termin inherhalb der Zeitspanne und im Wochentag - if not ((startzeit <= termin_zeit.time() <= endzeit) and (termin_zeit.weekday() in wochentage)): + if not ((startzeit <= termin_zeit.time() <= endzeit) and termin_zeit.date() >= startdatum and (termin_zeit.weekday() in wochentage)): termine_in_zeitspanne = False # Beide Termine sind in der Zeitspanne @@ -689,7 +691,7 @@ def code_bestaetigen(self, token, sms_pin): return False @staticmethod - def terminsuche(code: str, plz_impfzentren: list, kontakt: dict, PATH:str, zeitspanne: dict, check_delay: int = 30): + def terminsuche(code: str, plz_impfzentren: list, kontakt: dict, PATH:str, zeitspanne: dict = dict(), check_delay: int = 30): """ Workflow für die Terminbuchung. From 19177d5afc701e2ee0e3ec72f8e8e22f565e9441 Mon Sep 17 00:00:00 2001 From: Floskinner <58706771+Floskinner@users.noreply.github.com> Date: Wed, 26 May 2021 22:56:35 +0200 Subject: [PATCH 09/25] updated --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 71965ce5..4ff42511 100644 --- a/.gitignore +++ b/.gitignore @@ -9,8 +9,9 @@ log/ # data kontaktdaten.json +zeitspanne.json # pyinstaller # temporary building files: -build/ \ No newline at end of file +build/ From a2acec4373fdc9bc3024e02a73908194df4840ad Mon Sep 17 00:00:00 2001 From: Floskinner <58706771+Floskinner@users.noreply.github.com> Date: Thu, 27 May 2021 18:19:58 +0200 Subject: [PATCH 10/25] terminsuche startet in eigenem Thread MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit somit hängt sich die GUI nicht mehr auf nach --- gui.py | 36 ++++++++++++++++++++++++++++++++++-- tools/gui/uhrzeiten.ui | 2 +- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/gui.py b/gui.py index 1871e043..921b3a9c 100644 --- a/gui.py +++ b/gui.py @@ -3,6 +3,7 @@ import sys import os import json +import threading from PyQt5 import QtWidgets, uic from tools.gui.qtzeiten import QtZeiten @@ -63,6 +64,9 @@ def __init__(self, pfad_fenster_layout: str = os.path.join(PATH, "tools/gui/main self.i_kontaktdaten_pfad.textChanged.connect(self.__update_pfade) self.i_zeitspanne_pfad.textChanged.connect(self.__update_pfade) + # Speichert alle termin_suchen Threads + self.such_threads = list() + # GUI anzeigen self.show() @@ -99,17 +103,45 @@ def zeitspanne_erstellen(self): def __termin_suchen(self): """ - Startet den Prozess der terminsuche mit Impfterminservice.terminsuche + Startet den Prozess der terminsuche mit Impfterminservice.terminsuche in einem neuen Thread + Dieser wird in self.such_threads hinzugefügt. + Alle Threads sind deamon Thread (Sofort töten sobald der Bot beendet wird) """ kontaktdaten = self.__get_kontaktdaten() zeitspanne = self.__get_zeitspanne() + terminsuche_thread = threading.Thread(target=self.__start_terminsuche, args=(kontaktdaten, zeitspanne), daemon=True) + terminsuche_thread.setName(kontaktdaten["code"]) + + try: + + terminsuche_thread.start() + if not terminsuche_thread.is_alive(): + raise RuntimeError( + f"Terminsuche wurde gestartet, lebt aber nicht mehr!\n\nTermin mit Code: {terminsuche_thread.getName()}\nBitte Daten Prüfen!" + ) + + except Exception as error: + QtWidgets.QMessageBox.critical(self, "Fehler - Suche nicht gestartet!", str(error)) + + else: + self.such_threads.append(terminsuche_thread) + + def __start_terminsuche(self, kontaktdaten: dict, zeitspanne: dict): + """ + Startet die Terminsuche. Dies nur mit einem Thread starten, da die GUI sonst hängt + + Args: + kontaktdaten (dict): kontakdaten aus kontaktdaten.json + zeitspanne (dict): zeitspanne aus zeitspanne.json + """ + kontakt = kontaktdaten["kontakt"] code = kontaktdaten["code"] plz_impfzentren = kontaktdaten["plz_impfzentren"] - # TODO: starte es in einem extra thread, sonst hängt sich die GUI auf + # Startet das eigentliche suchen ImpfterminService.terminsuche(code=code, plz_impfzentren=plz_impfzentren, kontakt=kontakt, zeitspanne=zeitspanne, PATH=PATH) def __code_generieren(self): diff --git a/tools/gui/uhrzeiten.ui b/tools/gui/uhrzeiten.ui index 9c6887e5..02004d5f 100644 --- a/tools/gui/uhrzeiten.ui +++ b/tools/gui/uhrzeiten.ui @@ -194,7 +194,7 @@ - Termin fühestens ab: + Termin frühestens ab: Qt::AlignCenter From f525b038f05ff091edaa005990e270eeed607961 Mon Sep 17 00:00:00 2001 From: Floskinner <58706771+Floskinner@users.noreply.github.com> Date: Thu, 27 May 2021 18:30:16 +0200 Subject: [PATCH 11/25] "Tab-Order" angepasst --- tools/gui/kontaktdaten.ui | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/tools/gui/kontaktdaten.ui b/tools/gui/kontaktdaten.ui index 2141bd1b..f2627efa 100644 --- a/tools/gui/kontaktdaten.ui +++ b/tools/gui/kontaktdaten.ui @@ -101,7 +101,7 @@ - false + true Bitte Wählen @@ -124,11 +124,6 @@ Herr - - - ... - - @@ -367,6 +362,19 @@ + + i_plz_impfzentren + i_code_impfzentren + i_anrede_combo_box + i_vorname + i_nachname + i_strasse + i_hausnummer + i_plz_wohnort + i_wohnort + i_telefon + i_mail + From d0b055b3f7ea3e5c7b3aee1409dde6fa242413a4 Mon Sep 17 00:00:00 2001 From: Floskinner <58706771+Floskinner@users.noreply.github.com> Date: Thu, 27 May 2021 19:43:52 +0200 Subject: [PATCH 12/25] =?UTF-8?q?Reset=20Button=20nun=20funktionsf=C3=A4hi?= =?UTF-8?q?g?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/gui/qtkontakt.py | 11 +++++++++-- tools/gui/qtzeiten.py | 32 +++++++++++++++++++++++++++----- tools/gui/uhrzeiten.ui | 14 ++++++++++++++ 3 files changed, 50 insertions(+), 7 deletions(-) diff --git a/tools/gui/qtkontakt.py b/tools/gui/qtkontakt.py index a77f02fd..59cbdb67 100644 --- a/tools/gui/qtkontakt.py +++ b/tools/gui/qtkontakt.py @@ -28,11 +28,14 @@ # Cancel # Reset +### Layouts ### +# kontakdaten_layout + PATH = os.path.dirname(os.path.realpath(__file__)) class QtKontakt(QtWidgets.QDialog): - def __init__(self, standard_speicherpfad:str, pfad_fenster_layout=os.path.join(PATH, "kontaktdaten.ui")): + def __init__(self, standard_speicherpfad: str, pfad_fenster_layout=os.path.join(PATH, "kontaktdaten.ui")): super().__init__() self.standard_speicherpfad = standard_speicherpfad @@ -156,7 +159,11 @@ def __reset(self): Setzt alle Werte in der GUI zurück """ - pass + for widget in self.children(): + if isinstance(widget, QtWidgets.QLineEdit): + widget.setText("") + elif isinstance(widget, QtWidgets.QComboBox): + widget.setCurrentText("Bitte Wählen") # Zum schnellen einzeltesten diff --git a/tools/gui/qtzeiten.py b/tools/gui/qtzeiten.py index c0be3595..696bdaeb 100644 --- a/tools/gui/qtzeiten.py +++ b/tools/gui/qtzeiten.py @@ -1,7 +1,7 @@ import os import json from PyQt5 import QtWidgets, uic -from PyQt5.QtCore import QTime, QDate ,QDateTime +from PyQt5.QtCore import QTime, QDate, QDateTime # Folgende Widgets stehen zur Verfügung: @@ -29,6 +29,9 @@ # Cancel # Reset +### QFrame ### +# tage_frame + PATH = os.path.dirname(os.path.realpath(__file__)) @@ -146,7 +149,7 @@ def __get_aktive_wochentage(self) -> list: # Alle Checkboxen der GUI selektieren und durchgehen # BUG: Wenn die reihenfolge im Layout geändert wird, stimmen die Wochentage nicht mehr 0 = Mo ... 6 = So - checkboxes = self.i_mo_check_box.parent().findChildren(QtWidgets.QCheckBox) + checkboxes = self.tage_frame.findChildren(QtWidgets.QCheckBox) for num, checkboxe in enumerate(checkboxes, 0): if checkboxe.isChecked(): aktive_wochentage.append(num) @@ -220,9 +223,28 @@ def __oeffne_file_dialog(self) -> str: return dateipfad - def __reset(self): - #TODO: Reset - pass + def __reset(self, widgets: list = None): + """ + Setzt alle Werte in der GUI zurück + """ + + if widgets == None: + self.__reset(self.children()) + return + + for widget in widgets: + if isinstance(widget, QtWidgets.QCheckBox): + widget.setChecked(True) + elif isinstance(widget, QtWidgets.QDateEdit): + widget.setDate(QDateTime.currentDateTime().date()) + elif isinstance(widget, QtWidgets.QTimeEdit): + if widget == self.i_start_time_qtime: + widget.setTime(QTime(0, 1)) + else: + widget.setTime(QTime(23, 59)) + elif isinstance(widget, QtWidgets.QFrame): + self.__reset(widget.children()) + if __name__ == "__main__": diff --git a/tools/gui/uhrzeiten.ui b/tools/gui/uhrzeiten.ui index 02004d5f..b4d3bd53 100644 --- a/tools/gui/uhrzeiten.ui +++ b/tools/gui/uhrzeiten.ui @@ -395,6 +395,20 @@ + + i_mo_check_box + i_di_check_box + i_mi_check_box + i_do_check_box + i_fr_check_box + i_sa_check_box + i_so_check_box + i_start_datum_qdate + i_start_time_qtime + i_end_time_qtime + i_erster_termin_check_box + i_zweiter_termin_check_box + From 300d2cd1bc88235fa4a2140c1a5d7c100a0ec548 Mon Sep 17 00:00:00 2001 From: Floskinner <58706771+Floskinner@users.noreply.github.com> Date: Thu, 27 May 2021 20:29:39 +0200 Subject: [PATCH 13/25] Code ausgelagert --- gui.py | 46 +++++++++++----------------- tools/gui/__init__.py | 68 ++++++++++++++++++++++++++++++++++++++++++ tools/gui/qtkontakt.py | 33 +++----------------- tools/gui/qtzeiten.py | 35 +++++----------------- 4 files changed, 96 insertions(+), 86 deletions(-) create mode 100644 tools/gui/__init__.py diff --git a/gui.py b/gui.py index 921b3a9c..1227a155 100644 --- a/gui.py +++ b/gui.py @@ -6,6 +6,7 @@ import threading from PyQt5 import QtWidgets, uic +from tools.gui import * from tools.gui.qtzeiten import QtZeiten from tools.gui.qtkontakt import QtKontakt from tools.its import ImpfterminService @@ -47,8 +48,8 @@ def __init__(self, pfad_fenster_layout: str = os.path.join(PATH, "tools/gui/main # Funktionen den Buttons zuweisen self.b_termin_suchen.clicked.connect(self.__termin_suchen) self.b_code_generieren.clicked.connect(self.__code_generieren) - self.b_dateien_kontaktdaten.clicked.connect(lambda: self.__oeffne_file_dialog("kontaktdaten")) - self.b_dateien_zeitspanne.clicked.connect(lambda: self.__oeffne_file_dialog("zeitspanne")) + self.b_dateien_kontaktdaten.clicked.connect(self.__update_kontaktdaten_pfad) + self.b_dateien_zeitspanne.clicked.connect(self.__update_zeitspanne_pfad) self.b_neue_kontaktdaten.clicked.connect(self.kontaktdaten_erstellen) self.b_neue_zeitspanne.clicked.connect(self.zeitspanne_erstellen) @@ -61,8 +62,8 @@ def __init__(self, pfad_fenster_layout: str = os.path.join(PATH, "tools/gui/main self.i_zeitspanne_pfad.setText(self.pfad_zeitspanne) # Events für Eingabefelder - self.i_kontaktdaten_pfad.textChanged.connect(self.__update_pfade) - self.i_zeitspanne_pfad.textChanged.connect(self.__update_pfade) + self.i_kontaktdaten_pfad.textChanged.connect(self.__update_kontaktdaten_pfad) + self.i_zeitspanne_pfad.textChanged.connect(self.__update_zeitspanne_pfad) # Speichert alle termin_suchen Threads self.such_threads = list() @@ -184,32 +185,19 @@ def __get_zeitspanne(self) -> dict: return zeitspanne - def __oeffne_file_dialog(self, datei: str): - """ - Öffnet einen "File-Picker", der entsprechende Werte für die kontaktdaten / zeitspanne einliest - - Args: - datei (str): Startpfad vom File-Picker - """ - - # Öffnet den "File-Picker" vom System um ein bereits existierende Datei auszuwählen - datei_data = QtWidgets.QFileDialog.getOpenFileName(self, datei, os.path.join(PATH, "data"), "JSON Files (*.json)") - dateipfad = datei_data[0] # (pfad, typ) - - if dateipfad: - if datei == "kontaktdaten": - self.i_kontaktdaten_pfad.setText(dateipfad) - else: - self.i_zeitspanne_pfad.setText(dateipfad) - - def __update_pfade(self): - """ - Wird ein Pfad in der GUI verändert, so werden die entsprechende Attribute angepasst - """ - - self.pfad_kontaktdaten = self.i_kontaktdaten_pfad.text() - self.pfad_zeitspanne = self.i_zeitspanne_pfad.text() + def __update_kontaktdaten_pfad(self): + try: + pfad = oeffne_file_dialog_select(self, "Kontakdaten", self.pfad_kontaktdaten) + self.pfad_kontaktdaten = pfad + except FileNotFoundError: + pass + def __update_zeitspanne_pfad(self): + try: + pfad = oeffne_file_dialog_select(self, "Zeitspanne", self.pfad_zeitspanne) + self.pfad_zeitspanne = pfad + except FileNotFoundError: + pass def main(): """ diff --git a/tools/gui/__init__.py b/tools/gui/__init__.py new file mode 100644 index 00000000..a3da201f --- /dev/null +++ b/tools/gui/__init__.py @@ -0,0 +1,68 @@ +import json +from PyQt5 import QtWidgets + + +def oeffne_file_dialog_save(parent_widged: QtWidgets.QWidget, titel: str, standard_speicherpfad: str, dateityp="JSON Files (*.json)") -> str: + """ + Öffnet ein File Dialog, der entsprechend einen Pfad zurück gibt, wohin gespeichert werden soll + + Args: + parent_widged (PyQt5.QtWidgets.QWidget): + titel (str): Titel des Dialogs + standard_speicherpfad (str): Pfad welcher direkt geöffnet wird als Vorschlag + dateityp (str, optional): selectedFilter example: "Images (*.png *.xpm *.jpg)". Defaults to "JSON Files (*.json)". + + Raises: + FileNotFoundError: Wird geworfen, wenn der Dateipfad leer ist + + Returns: + str: Vollständiger Pfad + """ + + datei_data = QtWidgets.QFileDialog.getSaveFileName(parent_widged, titel, standard_speicherpfad, dateityp) + dateipfad = datei_data[0] # (Pfad, Dateityp) + + if not dateipfad: + raise FileNotFoundError + + return dateipfad + + +def oeffne_file_dialog_select(parent_widged: QtWidgets.QWidget, titel: str, standard_oeffnungspfad: str, dateityp="JSON Files (*.json)") -> str: + """ + Öffnet einen File Dialog um eine existierende Datei auszuwählen + + Args: + parent_widged (QtWidgets.QWidget): Parent QWidget an das der Dialog gehängt werden soll + titel (str): Titel des Dialogs + standard_oeffnungspfad (str): Pfad welcher direkt geöffnet wird als Vorschlag + dateityp (str, optional): selectedFilter example: "Images (*.png *.xpm *.jpg)". Defaults to "JSON Files (*.json)". + + Raises: + FileNotFoundError: Wird geworfen, wenn der Dateipfad leer ist + + Returns: + str: Vollständiger Pfad zur Datei + """ + + # Öffnet den "File-Picker" vom System um ein bereits existierende Datei auszuwählen + datei_data = QtWidgets.QFileDialog.getOpenFileName(parent_widged, titel, standard_oeffnungspfad, "JSON Files (*.json)") + dateipfad = datei_data[0] # (pfad, typ) + + if not dateipfad: + raise FileNotFoundError + + return dateipfad + + +def speichern(speicherpfad: str, data: dict): + """ + Speichert die Daten mittels json.dump an den entsprechenden Ort + + Args: + speicherpfad (str): speicherort + data (dict): Speicherdaten + """ + + with open(speicherpfad, 'w', encoding='utf-8') as f: + json.dump(data, f, ensure_ascii=False, indent=4) diff --git a/tools/gui/qtkontakt.py b/tools/gui/qtkontakt.py index 59cbdb67..10016bb5 100644 --- a/tools/gui/qtkontakt.py +++ b/tools/gui/qtkontakt.py @@ -4,6 +4,7 @@ from PyQt5 import QtWidgets, uic from PyQt5.QtCore import QTime +from tools.gui import * # Folgende Widgets stehen zur Verfügung: @@ -53,6 +54,7 @@ def bestaetigt(self): try: self.speicher_einstellungen() + QtWidgets.QMessageBox.information(self, "Gepseichert", "Daten erfolgreich gespeichert") self.close() except (TypeError, IOError, FileNotFoundError) as error: QtWidgets.QMessageBox.critical(self, "Fehler beim Speichern!", "Bitte erneut versuchen!") @@ -64,18 +66,10 @@ def speicher_einstellungen(self): Speicherpfad wird vom User abgefragt """ - speicherpfad = self.__oeffne_file_dialog() - + speicherpfad = oeffne_file_dialog_save(self, "Kontaktdaten", self.standard_speicherpfad) data = self.__get_alle_werte() - with open(speicherpfad, 'w', encoding='utf-8') as f: - try: - json.dump(data, f, ensure_ascii=False, indent=4) - QtWidgets.QMessageBox.information(self, "Gepseichert", "Daten erfolgreich gespeichert") - - except (TypeError, IOError, FileNotFoundError) as error: - QtWidgets.QMessageBox.critical(self, "Fehler!", "Daten konnten nicht gespeichert werden.") - raise error + speichern(speicherpfad, data) def __button_clicked(self, button): """ @@ -135,25 +129,6 @@ def __get_alle_werte(self) -> dict: } return kontaktdaten - def __oeffne_file_dialog(self) -> str: - """ - Öffnet einen File Dialog, der den Speicherort festlegt - - Raises: - FileNotFoundError: Wird geworfen, wenn kein Pfad angegeben wurde - - Returns: - str: speicherpfad - """ - - datei_data = QtWidgets.QFileDialog.getSaveFileName(self, "Kontaktdaten", self.standard_speicherpfad, "JSON Files (*.json)") - dateipfad = datei_data[0] # (Pfad, Dateityp) - - if not dateipfad: - raise FileNotFoundError - - return dateipfad - def __reset(self): """ Setzt alle Werte in der GUI zurück diff --git a/tools/gui/qtzeiten.py b/tools/gui/qtzeiten.py index 696bdaeb..beff8e13 100644 --- a/tools/gui/qtzeiten.py +++ b/tools/gui/qtzeiten.py @@ -3,6 +3,8 @@ from PyQt5 import QtWidgets, uic from PyQt5.QtCore import QTime, QDate, QDateTime +from tools.gui import * + # Folgende Widgets stehen zur Verfügung: ### Checkboxes ### @@ -70,9 +72,10 @@ def bestaetigt(self): try: self.speicher_einstellungen() + QtWidgets.QMessageBox.information(self, "Gepseichert", "Daten erfolgreich gespeichert") self.close() except ValueError as error: - QtWidgets.QMessageBox.critical(self, "Ungültige Eingabe!", "Start-Uhrzeit ist später als End-Uhrzeit!") + QtWidgets.QMessageBox.critical(self, "Ungültige Eingabe!", str(error)) except (TypeError, IOError, FileNotFoundError) as error: QtWidgets.QMessageBox.critical(self, "Fehler beim Speichern!", "Bitte erneut versuchen!") @@ -82,17 +85,10 @@ def speicher_einstellungen(self): Speicherpfad wird vom User abgefragt """ - speicherpfad = self.__oeffne_file_dialog() + speicherpfad = oeffne_file_dialog_save(self, "Zeitspanne", self.standard_speicherpfad) data = self.__get_alle_werte() - with open(speicherpfad, 'w', encoding='utf-8') as f: - try: - json.dump(data, f, ensure_ascii=False, indent=4) - QtWidgets.QMessageBox.information(self, "Gepseichert", "Daten erfolgreich gespeichert") - - except (TypeError, IOError, FileNotFoundError) as error: - QtWidgets.QMessageBox.critical(self, "Fehler!", "Daten konnten nicht gespeichert werden.") - raise error + speichern(speicherpfad, data) def __button_clicked(self, button): """ @@ -171,7 +167,7 @@ def __get_uhrzeiten(self) -> dict: end_uhrzeit: QTime = self.i_end_time_qtime.time() if start_uhrzeit >= end_uhrzeit: - raise ValueError + raise ValueError("Start Uhrzeit is später als Enduhrzeit") uhrzeiten = { "startzeit": { @@ -207,22 +203,6 @@ def __get_aktive_termine(self) -> list: aktive_termine.append(2) return aktive_termine - def __oeffne_file_dialog(self) -> str: - """ - Öffnet einen File Dialog, der den Speicherort festlegt - - Returns: - str: Speicherpfad - """ - - datei_data = QtWidgets.QFileDialog.getSaveFileName(self, "Zeitspanne", self.standard_speicherpfad, "JSON Files (*.json)") - dateipfad = datei_data[0] # (Pfad, Dateityp) - - if not dateipfad: - raise FileNotFoundError - - return dateipfad - def __reset(self, widgets: list = None): """ Setzt alle Werte in der GUI zurück @@ -244,7 +224,6 @@ def __reset(self, widgets: list = None): widget.setTime(QTime(23, 59)) elif isinstance(widget, QtWidgets.QFrame): self.__reset(widget.children()) - if __name__ == "__main__": From f7f515b1efa0b9f0ed8876c08ef1beb3e3f6fc6c Mon Sep 17 00:00:00 2001 From: Floskinner <58706771+Floskinner@users.noreply.github.com> Date: Thu, 27 May 2021 22:03:51 +0200 Subject: [PATCH 14/25] verbesserte Datenabfrage --- gui.py | 28 ++++++++++++----- tools/gui/__init__.py | 69 ++++++++++++++++++++++++++++++++++++++++++ tools/gui/qtkontakt.py | 31 +++++++++++++++++-- 3 files changed, 119 insertions(+), 9 deletions(-) diff --git a/gui.py b/gui.py index 1227a155..46f297cc 100644 --- a/gui.py +++ b/gui.py @@ -50,7 +50,7 @@ def __init__(self, pfad_fenster_layout: str = os.path.join(PATH, "tools/gui/main self.b_code_generieren.clicked.connect(self.__code_generieren) self.b_dateien_kontaktdaten.clicked.connect(self.__update_kontaktdaten_pfad) self.b_dateien_zeitspanne.clicked.connect(self.__update_zeitspanne_pfad) - self.b_neue_kontaktdaten.clicked.connect(self.kontaktdaten_erstellen) + self.b_neue_kontaktdaten.clicked.connect(lambda: self.kontaktdaten_erstellen(Modus.TERMIN_SUCHEN)) self.b_neue_zeitspanne.clicked.connect(self.zeitspanne_erstellen) # Standard Pfade @@ -84,12 +84,15 @@ def start_gui(): window = HauptGUI() app.exec_() - def kontaktdaten_erstellen(self): + def kontaktdaten_erstellen(self, modus: Modus = Modus.TERMIN_SUCHEN): """ Ruft den Dialog für die Kontaktdaten auf + + Args: + modus (Modus): Abhängig vom Modus werden nicht alle Daten benötigt. Defalut TERMIN_SUCHEN """ - dialog = QtKontakt(self.pfad_kontaktdaten) + dialog = QtKontakt(modus, self.pfad_kontaktdaten) dialog.show() dialog.exec_() @@ -109,9 +112,15 @@ def __termin_suchen(self): Alle Threads sind deamon Thread (Sofort töten sobald der Bot beendet wird) """ - kontaktdaten = self.__get_kontaktdaten() + kontaktdaten = self.__get_kontaktdaten(Modus.TERMIN_SUCHEN) zeitspanne = self.__get_zeitspanne() + try: + check_alle_kontakt_daten_da(Modus.TERMIN_SUCHEN, kontaktdaten) + except FehlendeDatenException as error: + QtWidgets.QMessageBox.critical(self, "Daten unvollständig!", f"Es fehlen Daten in der JSON Datei\n\n{error}") + return + terminsuche_thread = threading.Thread(target=self.__start_terminsuche, args=(kontaktdaten, zeitspanne), daemon=True) terminsuche_thread.setName(kontaktdaten["code"]) @@ -151,18 +160,21 @@ def __code_generieren(self): """ # TODO: code generierung implementieren - pass + QtWidgets.QMessageBox.information(self, "Noch nicht verfügbar", "Funktion nur über Konsole verfügbar") - def __get_kontaktdaten(self) -> dict: + def __get_kontaktdaten(self, modus: Modus) -> dict: """ Ladet die Kontakdaten aus dem in der GUI hinterlegten Pfad + Args: + modus (Modus): Abhängig vom Modus werden nicht alle Daten benötigt. + Returns: dict: Kontakdaten """ if not os.path.isfile(self.pfad_kontaktdaten): - self.kontaktdaten_erstellen() + self.kontaktdaten_erstellen(modus) with open(self.pfad_kontaktdaten, "r", encoding='utf-8') as f: kontaktdaten = json.load(f) @@ -183,6 +195,8 @@ def __get_zeitspanne(self) -> dict: with open(self.pfad_zeitspanne, "r", encoding='utf-8') as f: zeitspanne = json.load(f) + #TODO: Prüfen ob Daten vollständig + return zeitspanne def __update_kontaktdaten_pfad(self): diff --git a/tools/gui/__init__.py b/tools/gui/__init__.py index a3da201f..3cebd0ea 100644 --- a/tools/gui/__init__.py +++ b/tools/gui/__init__.py @@ -1,7 +1,76 @@ import json +from enum import Enum, auto from PyQt5 import QtWidgets +class Modus(Enum): + CODE_GENERIEREN = auto() + TERMIN_SUCHEN = auto() + + +class FehlendeDatenException(Exception): + pass + + +def check_alle_kontakt_daten_da(modus: Modus, data: dict): + """ + Nur für Kontaktdaten! + Überprüft ob alle Key vorhanden sind und ob die Values kein leeren String enthalten + + Args: + modus (Modus): Entsprechend werden Daten überprüft + data (dict): Inhalt der JSON + + Raises: + FehlendeDatenException: Es wird ein Key oder Value vermisst + """ + + if modus == Modus.TERMIN_SUCHEN: + try: + # Daten vorhanden + data["plz_impfzentren"] + data["code"] + data["kontakt"]["anrede"] + data["kontakt"]["vorname"] + data["kontakt"]["nachname"] + data["kontakt"]["strasse"] + data["kontakt"]["hausnummer"] + data["kontakt"]["plz"] + data["kontakt"]["ort"] + data["kontakt"]["phone"] + data["kontakt"]["notificationChannel"] + data["kontakt"]["notificationReceiver"] + + # Daten enthalten kein leerer String + # PLZ + for plz in data["plz_impfzentren"]: + if not plz.strip(): + raise FehlendeDatenException("Wert fuer \"plz_impfzentren\" fehlerhaft!") + if not data["code"].strip(): + raise FehlendeDatenException("Wert fuer \"code\" fehlt!") + # 2. Ebene + for key, value in data["kontakt"].items(): + if not value.strip(): + raise FehlendeDatenException(f"Wert fuer \"{key}\" fehlt!") + except KeyError as error: + raise FehlendeDatenException("Schluesselwort Fehlt!") from error + + elif modus == Modus.CODE_GENERIEREN: + try: + # Daten vorhanden + data["plz_impfzentren"] + data["kontakt"]["phone"] + data["kontakt"]["notificationChannel"] + data["kontakt"]["notificationReceiver"] + + # Daten enthalten kein leerer String + for key, values in data.items(): + if values.strip() == "": + raise FehlendeDatenException(f"Wert fuer \"{key}\" fehlt!") + except KeyError as error: + raise FehlendeDatenException("Schluesselwort Fehlt!") from error + + def oeffne_file_dialog_save(parent_widged: QtWidgets.QWidget, titel: str, standard_speicherpfad: str, dateityp="JSON Files (*.json)") -> str: """ Öffnet ein File Dialog, der entsprechend einen Pfad zurück gibt, wohin gespeichert werden soll diff --git a/tools/gui/qtkontakt.py b/tools/gui/qtkontakt.py index 10016bb5..c2c7be50 100644 --- a/tools/gui/qtkontakt.py +++ b/tools/gui/qtkontakt.py @@ -36,17 +36,29 @@ class QtKontakt(QtWidgets.QDialog): - def __init__(self, standard_speicherpfad: str, pfad_fenster_layout=os.path.join(PATH, "kontaktdaten.ui")): + def __init__(self, modus: Modus, standard_speicherpfad: str, pfad_fenster_layout=os.path.join(PATH, "kontaktdaten.ui")): super().__init__() self.standard_speicherpfad = standard_speicherpfad # Laden der .ui Datei uic.loadUi(pfad_fenster_layout, self) + self.setup(modus) # Funktionen für Buttonbox zuweisen self.buttonBox.clicked.connect(self.__button_clicked) + def setup(self, modus: Modus): + if modus == Modus.TERMIN_SUCHEN: + # Default - Alle Felder aktiv + pass + elif modus == Modus.CODE_GENERIEREN: + # Benötig wird: PLZ's der Impfzentren, Telefonnummer, Mail + # Alles andere wird daher deaktiviert + self.readonly_alle_line_edits(("i_plz_impfzentren", "i_telefon", "i_mail")) + else: + raise RuntimeError("Modus ungueltig!") + def bestaetigt(self): """ Versucht die Daten zu speichern und schließt sich anschließend selbst @@ -58,7 +70,6 @@ def bestaetigt(self): self.close() except (TypeError, IOError, FileNotFoundError) as error: QtWidgets.QMessageBox.critical(self, "Fehler beim Speichern!", "Bitte erneut versuchen!") - print(error) def speicher_einstellungen(self): """ @@ -129,6 +140,22 @@ def __get_alle_werte(self) -> dict: } return kontaktdaten + def readonly_alle_line_edits(self, ausgeschlossen: list): + """ + Setzt alle QLineEdit auf "read only", ausgeschlossen der Widgets in ausgeschlossen. + Setzt zudem den PlacholderText auf "Daten werden nicht benötigt" + + Args: + ausgeschlossen (list): Liste mit den ObjectNamen der Widgets die ausgeschlossen werden sollen + """ + + line_edits = self.findChildren(QtWidgets.QLineEdit) + + for line_edit in line_edits: + if line_edit.objectName() not in ausgeschlossen: + line_edit.setReadOnly(True) + line_edit.setPlaceholderText("Daten werden nicht benötigt") + def __reset(self): """ Setzt alle Werte in der GUI zurück From 6e63aa49911d0e0b0970352d68c3c178be1bfc8d Mon Sep 17 00:00:00 2001 From: Floskinner <58706771+Floskinner@users.noreply.github.com> Date: Thu, 27 May 2021 23:30:57 +0200 Subject: [PATCH 15/25] Terminsuche in Threads und Managebar --- gui.py | 100 ++++++++++++++++++++++++++++++++++++---------- tools/gui/main.ui | 59 +++++++++++++++++---------- 2 files changed, 117 insertions(+), 42 deletions(-) diff --git a/gui.py b/gui.py index 46f297cc..fd855f4e 100644 --- a/gui.py +++ b/gui.py @@ -3,7 +3,9 @@ import sys import os import json +import time import threading +import multiprocessing from PyQt5 import QtWidgets, uic from tools.gui import * @@ -30,6 +32,9 @@ class HauptGUI(QtWidgets.QMainWindow): # b_neue_kontaktdaten # b_neue_zeitspanne + ### Layouts ### + # prozesse_layout + # TODO: Ausgabe der cmd in der GUI wiederspiegelen - wenn sowas überhaupt geht def __init__(self, pfad_fenster_layout: str = os.path.join(PATH, "tools/gui/main.ui")): """ @@ -65,8 +70,12 @@ def __init__(self, pfad_fenster_layout: str = os.path.join(PATH, "tools/gui/main self.i_kontaktdaten_pfad.textChanged.connect(self.__update_kontaktdaten_pfad) self.i_zeitspanne_pfad.textChanged.connect(self.__update_zeitspanne_pfad) - # Speichert alle termin_suchen Threads - self.such_threads = list() + # Speichert alle termin_suchen Prozesse + self.such_prozesse = list() + + # Überwachnung der Prozesse + self.prozess_bewacher = threading.Thread(target=self.__check_status_der_prozesse, daemon=True) + self.prozess_bewacher.start() # GUI anzeigen self.show() @@ -121,22 +130,7 @@ def __termin_suchen(self): QtWidgets.QMessageBox.critical(self, "Daten unvollständig!", f"Es fehlen Daten in der JSON Datei\n\n{error}") return - terminsuche_thread = threading.Thread(target=self.__start_terminsuche, args=(kontaktdaten, zeitspanne), daemon=True) - terminsuche_thread.setName(kontaktdaten["code"]) - - try: - - terminsuche_thread.start() - if not terminsuche_thread.is_alive(): - raise RuntimeError( - f"Terminsuche wurde gestartet, lebt aber nicht mehr!\n\nTermin mit Code: {terminsuche_thread.getName()}\nBitte Daten Prüfen!" - ) - - except Exception as error: - QtWidgets.QMessageBox.critical(self, "Fehler - Suche nicht gestartet!", str(error)) - - else: - self.such_threads.append(terminsuche_thread) + self.__start_terminsuche(kontaktdaten, zeitspanne) def __start_terminsuche(self, kontaktdaten: dict, zeitspanne: dict): """ @@ -151,8 +145,26 @@ def __start_terminsuche(self, kontaktdaten: dict, zeitspanne: dict): code = kontaktdaten["code"] plz_impfzentren = kontaktdaten["plz_impfzentren"] - # Startet das eigentliche suchen - ImpfterminService.terminsuche(code=code, plz_impfzentren=plz_impfzentren, kontakt=kontakt, zeitspanne=zeitspanne, PATH=PATH) + terminsuche_prozess = multiprocessing.Process(target=ImpfterminService.terminsuche, name=f"{code}-{len(self.such_prozesse)}", daemon=True, kwargs={ + "code": code, + "plz_impfzentren": plz_impfzentren, + "kontakt": kontakt, + "zeitspanne": zeitspanne, + "PATH": PATH}) + try: + terminsuche_prozess.start() + if not terminsuche_prozess.is_alive(): + raise RuntimeError( + f"Terminsuche wurde gestartet, lebt aber nicht mehr!\n\nTermin mit Code: {terminsuche_prozess.getName()}\nBitte Daten Prüfen!" + ) + + except Exception as error: + QtWidgets.QMessageBox.critical(self, "Fehler - Suche nicht gestartet!", str(error)) + + else: + QtWidgets.QMessageBox.information(self, "Suche gestartet", "Terminsuche wurde gestartet!\nWeitere Infos in der Konsole") + self.such_prozesse.append(terminsuche_prozess) + self.__add_prozess_in_gui(terminsuche_prozess) def __code_generieren(self): """ @@ -195,7 +207,7 @@ def __get_zeitspanne(self) -> dict: with open(self.pfad_zeitspanne, "r", encoding='utf-8') as f: zeitspanne = json.load(f) - #TODO: Prüfen ob Daten vollständig + # TODO: Prüfen ob Daten vollständig return zeitspanne @@ -213,6 +225,52 @@ def __update_zeitspanne_pfad(self): except FileNotFoundError: pass + def __add_prozess_in_gui(self, prozess: multiprocessing.Process): + """ + Die Prozesse werden in der GUI in dem prozesse_layout angezeigt + """ + # addRow(label, field) + label = QtWidgets.QLabel(f"Prozess: {prozess.name}") + button = QtWidgets.QPushButton("Stoppen") + button.setObjectName(prozess.name) + button.clicked.connect(lambda: self.__stop_prozess(prozess)) + + self.prozesse_layout.addRow(label, button) + + def __stop_prozess(self, prozess: multiprocessing.Process): + """ + Stopped den übergebenen Prozess und löscht diesen aus der GUI + + Args: + prozess (multiprocessing.Process): Prozess welcher getötet werden soll + """ + prozess.kill() + self.such_prozesse.remove(prozess) + + def __remove_prozess_von_gui(self, prozess: multiprocessing.Process): + """ + Entfernt die Anzeige des Prozesses aus der GUI + + Args: + prozess (multiprocessing.Process): Prozess welcher entfernt werden soll + warnung (bool, optional): Warnung an den User ausgeben, dass der Prozess weg ist. Defaults to False. + """ + + button = self.findChild(QtWidgets.QPushButton, prozess.name) + self.prozesse_layout.removeRow(button) + + def __check_status_der_prozesse(self): + """ + Wird von einem Thread dauerhaft durchlaufen um zu prüfen ob ein Prozess sich beendet hat + """ + + while True: + for prozess in self.such_prozesse: + if not prozess.is_alive(): + self.__remove_prozess_von_gui(prozess) + time.sleep(5) + + def main(): """ Startet die GUI-Anwendung diff --git a/tools/gui/main.ui b/tools/gui/main.ui index d0e55428..6986bd25 100644 --- a/tools/gui/main.ui +++ b/tools/gui/main.ui @@ -29,20 +29,7 @@ 6 - - - - Qt::Vertical - - - - 20 - 40 - - - - - + Termin suchen @@ -58,6 +45,19 @@ + + + + Qt::Vertical + + + + 20 + 40 + + + + @@ -76,13 +76,6 @@ für den Corona Impfterminservice - - - - Impf-Code generieren - - - @@ -90,6 +83,13 @@ für den Corona Impfterminservice + + + + Impf-Code generieren + + + @@ -171,6 +171,23 @@ für den Corona Impfterminservice + + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + From 711b0b674e3fae8264b7534bf75ce386bf9bacf6 Mon Sep 17 00:00:00 2001 From: Floskinner <58706771+Floskinner@users.noreply.github.com> Date: Fri, 28 May 2021 21:59:42 +0200 Subject: [PATCH 16/25] Terminsuche implementiert --- gui.py | 26 ++--- tools/gui/__init__.py | 3 +- tools/gui/main.ui | 44 ++++---- tools/gui/qtterminsuche.py | 199 +++++++++++++++++++++++++++++++++++++ tools/gui/terminsuche.ui | 147 +++++++++++++++++++++++++++ 5 files changed, 383 insertions(+), 36 deletions(-) create mode 100644 tools/gui/qtterminsuche.py create mode 100644 tools/gui/terminsuche.ui diff --git a/gui.py b/gui.py index fd855f4e..296314bf 100644 --- a/gui.py +++ b/gui.py @@ -11,6 +11,7 @@ from tools.gui import * from tools.gui.qtzeiten import QtZeiten from tools.gui.qtkontakt import QtKontakt +from tools.gui.qtterminsuche import QtTerminsuche from tools.its import ImpfterminService PATH = os.path.dirname(os.path.realpath(__file__)) @@ -71,7 +72,8 @@ def __init__(self, pfad_fenster_layout: str = os.path.join(PATH, "tools/gui/main self.i_zeitspanne_pfad.textChanged.connect(self.__update_zeitspanne_pfad) # Speichert alle termin_suchen Prozesse - self.such_prozesse = list() + self.such_prozesse = list(list()) + self.prozesse_counter = 0 # Überwachnung der Prozesse self.prozess_bewacher = threading.Thread(target=self.__check_status_der_prozesse, daemon=True) @@ -141,16 +143,11 @@ def __start_terminsuche(self, kontaktdaten: dict, zeitspanne: dict): zeitspanne (dict): zeitspanne aus zeitspanne.json """ - kontakt = kontaktdaten["kontakt"] code = kontaktdaten["code"] - plz_impfzentren = kontaktdaten["plz_impfzentren"] - - terminsuche_prozess = multiprocessing.Process(target=ImpfterminService.terminsuche, name=f"{code}-{len(self.such_prozesse)}", daemon=True, kwargs={ - "code": code, - "plz_impfzentren": plz_impfzentren, - "kontakt": kontakt, + terminsuche_prozess = multiprocessing.Process(target=QtTerminsuche.start_suche, name=f"{code}-{self.prozesse_counter}", daemon=True, kwargs={ + "kontaktdaten": kontaktdaten, "zeitspanne": zeitspanne, - "PATH": PATH}) + "ROOT_PATH": PATH}) try: terminsuche_prozess.start() if not terminsuche_prozess.is_alive(): @@ -162,9 +159,10 @@ def __start_terminsuche(self, kontaktdaten: dict, zeitspanne: dict): QtWidgets.QMessageBox.critical(self, "Fehler - Suche nicht gestartet!", str(error)) else: - QtWidgets.QMessageBox.information(self, "Suche gestartet", "Terminsuche wurde gestartet!\nWeitere Infos in der Konsole") + # QtWidgets.QMessageBox.information(self, "Suche gestartet", "Terminsuche wurde gestartet!\nWeitere Infos in der Konsole") self.such_prozesse.append(terminsuche_prozess) self.__add_prozess_in_gui(terminsuche_prozess) + self.prozesse_counter += 1 def __code_generieren(self): """ @@ -172,7 +170,7 @@ def __code_generieren(self): """ # TODO: code generierung implementieren - QtWidgets.QMessageBox.information(self, "Noch nicht verfügbar", "Funktion nur über Konsole verfügbar") + QtWidgets.QMessageBox.information(self, "Noch nicht verfügbar", "Funktion nur über Konsolenanwendung verfügbar") def __get_kontaktdaten(self, modus: Modus) -> dict: """ @@ -246,6 +244,7 @@ def __stop_prozess(self, prozess: multiprocessing.Process): """ prozess.kill() self.such_prozesse.remove(prozess) + self.__remove_prozess_von_gui(prozess) def __remove_prozess_von_gui(self, prozess: multiprocessing.Process): """ @@ -268,14 +267,15 @@ def __check_status_der_prozesse(self): for prozess in self.such_prozesse: if not prozess.is_alive(): self.__remove_prozess_von_gui(prozess) - time.sleep(5) + self.such_prozesse.remove(prozess) + time.sleep(2) def main(): """ Startet die GUI-Anwendung """ - + multiprocessing.freeze_support() HauptGUI.start_gui() diff --git a/tools/gui/__init__.py b/tools/gui/__init__.py index 3cebd0ea..573a5a1b 100644 --- a/tools/gui/__init__.py +++ b/tools/gui/__init__.py @@ -1,6 +1,7 @@ import json from enum import Enum, auto -from PyQt5 import QtWidgets +from PyQt5 import QtWidgets, uic +from PyQt5.QtWidgets import QMessageBox class Modus(Enum): diff --git a/tools/gui/main.ui b/tools/gui/main.ui index 6986bd25..206894c8 100644 --- a/tools/gui/main.ui +++ b/tools/gui/main.ui @@ -29,7 +29,21 @@ 6 - + + + + Qt::Horizontal + + + + + + + Laufende Prozesse: + + + + Termin suchen @@ -45,7 +59,7 @@ - + Qt::Vertical @@ -76,27 +90,13 @@ für den Corona Impfterminservice - - - - Qt::Horizontal - - - - + Impf-Code generieren - - - - Qt::Horizontal - - - @@ -171,18 +171,18 @@ für den Corona Impfterminservice - + - - + + Qt::Horizontal - - + + Qt::Horizontal diff --git a/tools/gui/qtterminsuche.py b/tools/gui/qtterminsuche.py new file mode 100644 index 00000000..c7d7d475 --- /dev/null +++ b/tools/gui/qtterminsuche.py @@ -0,0 +1,199 @@ +#!/usr/bin/env python3 + +import sys +import os +import json +import time +import ctypes +import threading +import multiprocessing + +from io import StringIO + +from PyQt5 import QtWidgets, uic, QtCore, QtGui +from PyQt5.QtCore import QObject, QThread, pyqtSignal +from tools.gui import * +from tools.its import ImpfterminService + +PATH = os.path.dirname(os.path.realpath(__file__)) + + +class EigenerStream(QtCore.QObject): + """ + Klasse wenn auf write() zugegriffen wird, dann wird das Siganl textWritten ausgelöst + """ + + # Signal welches ausgelöst werden kann + text_schreiben = pyqtSignal(str) + + def write(self, stream): + """ + Löst das Signal text_schreiben aus und übergibt das Argumet text weiter + """ + self.text_schreiben.emit(str(stream)) + + +class Worker(QObject): + """ + Worker, der nichts anderes macht, als den Termin mithilfe its.py zu suchen + sobald die Suche beendet wurde, wird ein "fertig" Signal geworfen, welches den Rückgabewert von its übergibt + """ + + # Signal wenn suche abgeschlossen + fertig = pyqtSignal(bool) + + def __init__(self, kontaktdaten: dict, zeitspanne: dict, ROOT_PATH: str): + """ + Args: + kontaktdaten (dict): kontakdaten aus kontaktdaten.json + zeitspanne (dict): zeitspanne aus zeitspanne.json + ROOT_PATH (str): Pfad zur main.py / gui.py + """ + super().__init__() + + self.kontaktdaten = kontaktdaten + self.zeitspanne = zeitspanne + self.ROOT_PATH = ROOT_PATH + + def suchen(self): + """ + Startet die Terminsuche. Dies nur mit einem Thread starten, da die GUI sonst hängt + """ + + kontakt = self.kontaktdaten["kontakt"] + code = self.kontaktdaten["code"] + plz_impfzentren = self.kontaktdaten["plz_impfzentren"] + + erfolgreich = ImpfterminService.terminsuche(code=code, plz_impfzentren=plz_impfzentren, kontakt=kontakt, + PATH=self.ROOT_PATH, zeitspanne=self.zeitspanne) + + self.fertig.emit(erfolgreich) + + +class QtTerminsuche(QtWidgets.QMainWindow): + + # Folgende Widgets stehen zur Verfügung: + + ### QLabel ### + # code_label + # vorname_label + # nachname_label + + ### ButtonBox ### + # buttonBox + # Close + + ### QTextEdit (readonly) ### + # console_text_edit + + def __init__(self, kontaktdaten: dict, zeitspanne: dict, ROOT_PATH: str, pfad_fenster_layout=os.path.join(PATH, "terminsuche.ui")): + + super().__init__() + + # Laden der .ui Datei und Anpassungen + uic.loadUi(pfad_fenster_layout, self) + + # Attribute erstellen + self.erfolgreich: bool = None + self.kontaktdaten = kontaktdaten + self.zeitspanne = zeitspanne + self.ROOT_PATH = ROOT_PATH + + # std.out & error umleiten auf das Textfeld + sys.stdout = EigenerStream(text_schreiben=self.update_ausgabe) + sys.stderr = EigenerStream(text_schreiben=self.update_ausgabe) + + # Entsprechend Konfigurieren + self.setup_thread() + + # Infos setzten + self.setup_infos() + + # GUI anzeigen + self.show() + + # Terminsuche starten + self.thread.start() + + + @staticmethod + def start_suche(kontaktdaten: dict, zeitspanne: dict, ROOT_PATH: str): + """ + Startet die Suche in einem eigenen Fenster mit umlenkung der Konsolenausgabe in das Fenster + + Args: + kontaktdaten (dict): kontaktdaten aus JSON + zeitspanne (dict): zeitspanne aus JSON + ROOT_PATH (str): Pfad zum Root Ordner, damit dieser an its übergeben werden kann + """ + app = QtWidgets.QApplication(list()) + window = QtTerminsuche(kontaktdaten, zeitspanne, ROOT_PATH) + app.exec_() + + def setup_infos(self): + self.code_label.setText(self.kontaktdaten["code"]) + self.vorname_label.setText(self.kontaktdaten["kontakt"]["vorname"]) + self.nachname_label.setText(self.kontaktdaten["kontakt"]["nachname"]) + + def setup_thread(self): + """ + Thread + Worker erstellen und Konfigurieren + """ + + self.thread = QThread(parent=self) + self.worker = Worker(self.kontaktdaten, self.zeitspanne, self.ROOT_PATH) + + # Worker und Thread verbinden + self.worker.moveToThread(self.thread) + + # Signale setzten + self.worker.fertig.connect(self.suche_beendet) + self.worker.fertig.connect(self.thread.quit) + self.worker.fertig.connect(self.worker.deleteLater) + + self.thread.started.connect(self.worker.suchen) + self.thread.finished.connect(self.thread.deleteLater) + + def update_ausgabe(self, text): + """ + Fügt den übergeben Text dem console_text_edit hinzu + + Args: + text (str): Text welcher hinzukommen soll + """ + + cursor = self.console_text_edit.textCursor() + cursor.movePosition(QtGui.QTextCursor.End) + cursor.insertText(str(text)) + self.console_text_edit.setTextCursor(cursor) + self.console_text_edit.ensureCursorVisible() + + def suche_beendet(self, erfolgreich: bool): + """ + Wird aufgerufen, sobald die Suche vom Worker beendet wurde + + Args: + erfolgreich (bool): Bei erfolgreichen Beenden Hinweis ausgeben + """ + + if erfolgreich: + QtWidgets.QMessageBox.information(self, "Termin gefunden!", "Die Suche wird beendet!\nVorher Ausgabe prüfen!") + + def closeEvent(self, event): + """ + Wird aufgerufen, wenn die Anwendung geschlossen wird + + Args: + event: Schließen Event von QT + """ + + if self.thread.isRunning(): + self.thread.quit() + + if self.erfolgreich == None: + QtWidgets.QMessageBox.warning(self, "Suche beenden", "Die Suche wird beendet!\nVorher Ausgabe Prüfen!") + + # Streams wieder korrigieren, damit kein Fehler kommt + sys.stdout = sys.__stdout__ + sys.stderr = sys.__stderr__ + super().closeEvent(event) diff --git a/tools/gui/terminsuche.ui b/tools/gui/terminsuche.ui new file mode 100644 index 00000000..ecadccf6 --- /dev/null +++ b/tools/gui/terminsuche.ui @@ -0,0 +1,147 @@ + + + terminsuche_window + + + + 0 + 0 + 616 + 489 + + + + vaccipy - Terminuche + + + + + + + + + Qt::Horizontal + + + + + + + true + + + + + + + QDialogButtonBox::Close + + + + + + + + 14 + + + + Terminsuche + + + Qt::AlignCenter + + + + + + + + + + 12 + + + + Code: + + + + + + + + 12 + + + + XXXX-XXXX-XXXX + + + + + + + + 12 + + + + Vorname: + + + + + + + + 12 + + + + Max + + + + + + + + 12 + + + + Nachname: + + + + + + + + 12 + + + + Mustermann + + + + + + + + + Qt::Horizontal + + + + + + + + + + + + From 17108e631c27941c2afdd3abca45803d6af752a9 Mon Sep 17 00:00:00 2001 From: Floskinner <58706771+Floskinner@users.noreply.github.com> Date: Fri, 28 May 2021 22:25:40 +0200 Subject: [PATCH 17/25] Fehlerbeseitigung --- gui.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/gui.py b/gui.py index 296314bf..dd7fd360 100644 --- a/gui.py +++ b/gui.py @@ -123,11 +123,15 @@ def __termin_suchen(self): Alle Threads sind deamon Thread (Sofort töten sobald der Bot beendet wird) """ - kontaktdaten = self.__get_kontaktdaten(Modus.TERMIN_SUCHEN) - zeitspanne = self.__get_zeitspanne() - try: + kontaktdaten = self.__get_kontaktdaten(Modus.TERMIN_SUCHEN) + zeitspanne = self.__get_zeitspanne() + check_alle_kontakt_daten_da(Modus.TERMIN_SUCHEN, kontaktdaten) + + except FileNotFoundError as error: + QtWidgets.QMessageBox.critical(self, "Datei nicht gefunden!", f"Datei zum Laden konnte nicht gefunden werden\n\nBitte erstellen") + return except FehlendeDatenException as error: QtWidgets.QMessageBox.critical(self, "Daten unvollständig!", f"Es fehlen Daten in der JSON Datei\n\n{error}") return From 9458bc76cb4d17354e8d5726da7c72de157c8680 Mon Sep 17 00:00:00 2001 From: Floskinner <58706771+Floskinner@users.noreply.github.com> Date: Fri, 28 May 2021 22:28:57 +0200 Subject: [PATCH 18/25] init commit --- specs/windows-terminservice-gui.spec | 38 ++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 specs/windows-terminservice-gui.spec diff --git a/specs/windows-terminservice-gui.spec b/specs/windows-terminservice-gui.spec new file mode 100644 index 00000000..f7c4d69d --- /dev/null +++ b/specs/windows-terminservice-gui.spec @@ -0,0 +1,38 @@ +# -*- mode: python ; coding: utf-8 -*- + + +block_cipher = None + + +a = Analysis(['..\\gui.py'], + pathex=['specs//'], + binaries=[('..\\tools\\chromedriver\\chromedriver-windows.exe', 'tools\\chromedriver\\'), ('..\\tools\\gui\\kontaktdaten.ui', 'tools\\gui\\'), ('..\\tools\\gui\\main.ui', 'tools\\gui\\'), ('..\\tools\\gui\\terminsuche.ui', 'tools\\gui\\'), ('..\\tools\\gui\\uhrzeiten.ui', 'tools\\gui\\'), ('..\\images\\spritze.ico', 'images\\')], + datas=[('../tools/cloudscraper', './cloudscraper/')], + hiddenimports=['plyer.platforms.win.notification', 'cloudscraper'], + hookspath=[], + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False) +pyz = PYZ(a.pure, a.zipped_data, + cipher=block_cipher) +exe = EXE(pyz, + a.scripts, + [], + exclude_binaries=True, + name='windows-terminservice-gui', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + console=True , icon='..\\images\\spritze.ico') +coll = COLLECT(exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=True, + upx_exclude=[], + name='windows-terminservice-gui') From eb41ea2e0d235846b0d8efc201a24cb1729173f4 Mon Sep 17 00:00:00 2001 From: Floskinner <58706771+Floskinner@users.noreply.github.com> Date: Fri, 28 May 2021 22:49:33 +0200 Subject: [PATCH 19/25] added icon --- gui.py | 6 ++++-- specs/windows-terminservice-gui.spec | 2 +- tools/gui/qtkontakt.py | 4 +++- tools/gui/qtterminsuche.py | 5 ++--- tools/gui/qtzeiten.py | 4 +++- 5 files changed, 13 insertions(+), 8 deletions(-) diff --git a/gui.py b/gui.py index dd7fd360..353a3f97 100644 --- a/gui.py +++ b/gui.py @@ -8,6 +8,7 @@ import multiprocessing from PyQt5 import QtWidgets, uic +from PyQt5.QtGui import QIcon from tools.gui import * from tools.gui.qtzeiten import QtZeiten from tools.gui.qtkontakt import QtKontakt @@ -50,6 +51,7 @@ def __init__(self, pfad_fenster_layout: str = os.path.join(PATH, "tools/gui/main # Laden der .ui Datei und Anpassungen uic.loadUi(pfad_fenster_layout, self) + self.setWindowIcon(QIcon(os.path.join(PATH, "images/spritze.ico"))) # Funktionen den Buttons zuweisen self.b_termin_suchen.clicked.connect(self.__termin_suchen) @@ -103,7 +105,7 @@ def kontaktdaten_erstellen(self, modus: Modus = Modus.TERMIN_SUCHEN): modus (Modus): Abhängig vom Modus werden nicht alle Daten benötigt. Defalut TERMIN_SUCHEN """ - dialog = QtKontakt(modus, self.pfad_kontaktdaten) + dialog = QtKontakt(modus, self.pfad_kontaktdaten, PATH) dialog.show() dialog.exec_() @@ -112,7 +114,7 @@ def zeitspanne_erstellen(self): Ruft den Dialog für die Zeitspanne auf """ - dialog = QtZeiten(self.pfad_zeitspanne) + dialog = QtZeiten(self.pfad_zeitspanne, PATH) dialog.show() dialog.exec_() diff --git a/specs/windows-terminservice-gui.spec b/specs/windows-terminservice-gui.spec index f7c4d69d..219c990b 100644 --- a/specs/windows-terminservice-gui.spec +++ b/specs/windows-terminservice-gui.spec @@ -27,7 +27,7 @@ exe = EXE(pyz, bootloader_ignore_signals=False, strip=False, upx=True, - console=True , icon='..\\images\\spritze.ico') + console=False , icon='..\\images\\spritze.ico') coll = COLLECT(exe, a.binaries, a.zipfiles, diff --git a/tools/gui/qtkontakt.py b/tools/gui/qtkontakt.py index c2c7be50..8f163b82 100644 --- a/tools/gui/qtkontakt.py +++ b/tools/gui/qtkontakt.py @@ -3,6 +3,7 @@ from PyQt5 import QtWidgets, uic from PyQt5.QtCore import QTime +from PyQt5.QtGui import QIcon from tools.gui import * @@ -36,13 +37,14 @@ class QtKontakt(QtWidgets.QDialog): - def __init__(self, modus: Modus, standard_speicherpfad: str, pfad_fenster_layout=os.path.join(PATH, "kontaktdaten.ui")): + def __init__(self, modus: Modus, standard_speicherpfad: str, ROOT_PATH: str, pfad_fenster_layout=os.path.join(PATH, "kontaktdaten.ui")): super().__init__() self.standard_speicherpfad = standard_speicherpfad # Laden der .ui Datei uic.loadUi(pfad_fenster_layout, self) + self.setWindowIcon(QIcon(os.path.join(ROOT_PATH, "images/spritze.ico"))) self.setup(modus) # Funktionen für Buttonbox zuweisen diff --git a/tools/gui/qtterminsuche.py b/tools/gui/qtterminsuche.py index c7d7d475..4088a5b1 100644 --- a/tools/gui/qtterminsuche.py +++ b/tools/gui/qtterminsuche.py @@ -4,14 +4,12 @@ import os import json import time -import ctypes -import threading -import multiprocessing from io import StringIO from PyQt5 import QtWidgets, uic, QtCore, QtGui from PyQt5.QtCore import QObject, QThread, pyqtSignal +from PyQt5.QtGui import QIcon from tools.gui import * from tools.its import ImpfterminService @@ -92,6 +90,7 @@ def __init__(self, kontaktdaten: dict, zeitspanne: dict, ROOT_PATH: str, pfad_fe # Laden der .ui Datei und Anpassungen uic.loadUi(pfad_fenster_layout, self) + self.setWindowIcon(QIcon(os.path.join(ROOT_PATH, "images/spritze.ico"))) # Attribute erstellen self.erfolgreich: bool = None diff --git a/tools/gui/qtzeiten.py b/tools/gui/qtzeiten.py index beff8e13..1c9b6e90 100644 --- a/tools/gui/qtzeiten.py +++ b/tools/gui/qtzeiten.py @@ -2,6 +2,7 @@ import json from PyQt5 import QtWidgets, uic from PyQt5.QtCore import QTime, QDate, QDateTime +from PyQt5.QtGui import QIcon from tools.gui import * @@ -43,7 +44,7 @@ class QtZeiten(QtWidgets.QDialog): Diese erbt von QtWidgets.QDialog """ - def __init__(self, standard_speicherpfad: str, pfad_fenster_layout=os.path.join(PATH, "uhrzeiten.ui")): + def __init__(self, standard_speicherpfad: str, ROOT_PATH: str, pfad_fenster_layout=os.path.join(PATH, "uhrzeiten.ui")): """ Eingabe der Zeitkonfigurationen @@ -60,6 +61,7 @@ def __init__(self, standard_speicherpfad: str, pfad_fenster_layout=os.path.join( # Laden der .ui Datei und Anpassungen uic.loadUi(self.pfad_fenster_layout, self) + self.setWindowIcon(QIcon(os.path.join(ROOT_PATH, "images/spritze.ico"))) self.i_start_datum_qdate.setMinimumDateTime(QDateTime.currentDateTime()) # Funktionen für Buttonbox zuweisen From 0eac40c5df325a1e5f41dc9db638680a253fc9de Mon Sep 17 00:00:00 2001 From: Floskinner <58706771+Floskinner@users.noreply.github.com> Date: Fri, 28 May 2021 22:56:36 +0200 Subject: [PATCH 20/25] Button war ohne Funktion --- tools/gui/qtterminsuche.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/gui/qtterminsuche.py b/tools/gui/qtterminsuche.py index 4088a5b1..78d2792e 100644 --- a/tools/gui/qtterminsuche.py +++ b/tools/gui/qtterminsuche.py @@ -92,6 +92,8 @@ def __init__(self, kontaktdaten: dict, zeitspanne: dict, ROOT_PATH: str, pfad_fe uic.loadUi(pfad_fenster_layout, self) self.setWindowIcon(QIcon(os.path.join(ROOT_PATH, "images/spritze.ico"))) + self.buttonBox.rejected.connect(self.close) + # Attribute erstellen self.erfolgreich: bool = None self.kontaktdaten = kontaktdaten From 6c66093f732c002d4babda88277aee94d6ea03ae Mon Sep 17 00:00:00 2001 From: Floskinner <58706771+Floskinner@users.noreply.github.com> Date: Fri, 28 May 2021 23:52:50 +0200 Subject: [PATCH 21/25] added gui build --- docs/distribution.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/distribution.md b/docs/distribution.md index 76450399..15ca3047 100644 --- a/docs/distribution.md +++ b/docs/distribution.md @@ -52,7 +52,7 @@ Schritte zum Erstellen einer Distribution: Nachdem mit pyinstaller die Distribution erstellt wurde, ist diese in im `dist/` folder zu finden. -#### Windows +#### Windows Konsolenanwendung .spec Datei erstellen und anschließend Distribution erstellen: ```shell @@ -60,6 +60,13 @@ pyi-makespec main.py --specpath "specs//" --add-binary "..\tools\chromedriver\ch pyinstaller --clean specs/windows-terminservice.spec ``` +#### Windows GUI + +```shell +pyi-makespec gui.py --specpath "specs//" --add-binary "..\tools\chromedriver\chromedriver-windows.exe;tools\chromedriver\" --add-binary "..\tools\gui\kontaktdaten.ui;tools\gui\" --add-binary "..\tools\gui\main.ui;tools\gui\" --add-binary "..\tools\gui\terminsuche.ui;tools\gui\" --add-binary "..\tools\gui\uhrzeiten.ui;tools\gui\" --add-binary "..\images\spritze.ico;images\" --name windows-terminservice-gui --hidden-import plyer.platforms.win.notification --hidden-import cloudscraper --add-data "../tools/cloudscraper;./cloudscraper/" --icon "..\images\spritze.ico" --windowed + +pyinstaller --clean specs/windows-terminservice-gui.spec +``` #### Linux ```shell From c2df7a52472659d99a6cf68b243d70ab6f3e43b7 Mon Sep 17 00:00:00 2001 From: Floskinner <58706771+Floskinner@users.noreply.github.com> Date: Fri, 28 May 2021 23:53:04 +0200 Subject: [PATCH 22/25] fixes FileDialog Bug --- gui.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/gui.py b/gui.py index 353a3f97..8e6f9edb 100644 --- a/gui.py +++ b/gui.py @@ -69,10 +69,6 @@ def __init__(self, pfad_fenster_layout: str = os.path.join(PATH, "tools/gui/main self.i_kontaktdaten_pfad.setText(self.pfad_kontaktdaten) self.i_zeitspanne_pfad.setText(self.pfad_zeitspanne) - # Events für Eingabefelder - self.i_kontaktdaten_pfad.textChanged.connect(self.__update_kontaktdaten_pfad) - self.i_zeitspanne_pfad.textChanged.connect(self.__update_zeitspanne_pfad) - # Speichert alle termin_suchen Prozesse self.such_prozesse = list(list()) self.prozesse_counter = 0 @@ -219,6 +215,7 @@ def __update_kontaktdaten_pfad(self): try: pfad = oeffne_file_dialog_select(self, "Kontakdaten", self.pfad_kontaktdaten) self.pfad_kontaktdaten = pfad + self.i_kontaktdaten_pfad.setText(self.pfad_kontaktdaten) except FileNotFoundError: pass @@ -226,6 +223,7 @@ def __update_zeitspanne_pfad(self): try: pfad = oeffne_file_dialog_select(self, "Zeitspanne", self.pfad_zeitspanne) self.pfad_zeitspanne = pfad + self.i_zeitspanne_pfad.setText(self.pfad_zeitspanne) except FileNotFoundError: pass From 5fb3b6dbbc5a0864cb70e25ee2a8807b395bdec1 Mon Sep 17 00:00:00 2001 From: Florian Glaser Date: Sat, 29 May 2021 00:35:28 +0200 Subject: [PATCH 23/25] some unix fixes --- gui.py | 5 +++-- tools/gui/__init__.py | 8 ++++++-- tools/gui/qtkontakt.py | 4 ++-- tools/gui/qtterminsuche.py | 1 + 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/gui.py b/gui.py index 8e6f9edb..ca7575c9 100644 --- a/gui.py +++ b/gui.py @@ -7,7 +7,7 @@ import threading import multiprocessing -from PyQt5 import QtWidgets, uic +from PyQt5 import QtCore, QtWidgets, uic from PyQt5.QtGui import QIcon from tools.gui import * from tools.gui.qtzeiten import QtZeiten @@ -90,6 +90,7 @@ def start_gui(): """ app = QtWidgets.QApplication(list()) + app.setAttribute(QtCore.Qt.AA_X11InitThreads) window = HauptGUI() app.exec_() @@ -101,7 +102,7 @@ def kontaktdaten_erstellen(self, modus: Modus = Modus.TERMIN_SUCHEN): modus (Modus): Abhängig vom Modus werden nicht alle Daten benötigt. Defalut TERMIN_SUCHEN """ - dialog = QtKontakt(modus, self.pfad_kontaktdaten, PATH) + dialog = QtKontakt(self, modus, self.pfad_kontaktdaten, PATH) dialog.show() dialog.exec_() diff --git a/tools/gui/__init__.py b/tools/gui/__init__.py index 573a5a1b..633f1bd9 100644 --- a/tools/gui/__init__.py +++ b/tools/gui/__init__.py @@ -89,7 +89,9 @@ def oeffne_file_dialog_save(parent_widged: QtWidgets.QWidget, titel: str, standa str: Vollständiger Pfad """ - datei_data = QtWidgets.QFileDialog.getSaveFileName(parent_widged, titel, standard_speicherpfad, dateityp) + options = QtWidgets.QFileDialog.Options() + options |= QtWidgets.QFileDialog.DontUseNativeDialog + datei_data = QtWidgets.QFileDialog.getSaveFileName(parent=parent_widged, caption=titel, directory=standard_speicherpfad, filter="JSON Files (*.json)", options=options) dateipfad = datei_data[0] # (Pfad, Dateityp) if not dateipfad: @@ -116,7 +118,9 @@ def oeffne_file_dialog_select(parent_widged: QtWidgets.QWidget, titel: str, stan """ # Öffnet den "File-Picker" vom System um ein bereits existierende Datei auszuwählen - datei_data = QtWidgets.QFileDialog.getOpenFileName(parent_widged, titel, standard_oeffnungspfad, "JSON Files (*.json)") + options = QtWidgets.QFileDialog.Options() + options |= QtWidgets.QFileDialog.DontUseNativeDialog + datei_data = QtWidgets.QFileDialog.getOpenFileName(parent=parent_widged, caption=titel, directory=standard_oeffnungspfad, filter="JSON Files (*.json)", options=options) dateipfad = datei_data[0] # (pfad, typ) if not dateipfad: diff --git a/tools/gui/qtkontakt.py b/tools/gui/qtkontakt.py index 8f163b82..629c9971 100644 --- a/tools/gui/qtkontakt.py +++ b/tools/gui/qtkontakt.py @@ -37,8 +37,8 @@ class QtKontakt(QtWidgets.QDialog): - def __init__(self, modus: Modus, standard_speicherpfad: str, ROOT_PATH: str, pfad_fenster_layout=os.path.join(PATH, "kontaktdaten.ui")): - super().__init__() + def __init__(self, parent: QtWidgets.QWidget, modus: Modus, standard_speicherpfad: str, ROOT_PATH: str, pfad_fenster_layout=os.path.join(PATH, "kontaktdaten.ui")): + super().__init__(parent=parent) self.standard_speicherpfad = standard_speicherpfad diff --git a/tools/gui/qtterminsuche.py b/tools/gui/qtterminsuche.py index 78d2792e..2548f9e0 100644 --- a/tools/gui/qtterminsuche.py +++ b/tools/gui/qtterminsuche.py @@ -128,6 +128,7 @@ def start_suche(kontaktdaten: dict, zeitspanne: dict, ROOT_PATH: str): ROOT_PATH (str): Pfad zum Root Ordner, damit dieser an its übergeben werden kann """ app = QtWidgets.QApplication(list()) + app.setAttribute(QtCore.Qt.AA_X11InitThreads) window = QtTerminsuche(kontaktdaten, zeitspanne, ROOT_PATH) app.exec_() From 25360dd68c5e2ab19692f7abf615f41cbdd8cd10 Mon Sep 17 00:00:00 2001 From: JuliusJacobitz Date: Sat, 29 May 2021 20:00:12 +0200 Subject: [PATCH 24/25] create missing dirs in init --- gui.py | 5 ++++- tools/__init__.py | 0 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 tools/__init__.py diff --git a/gui.py b/gui.py index ca7575c9..fec25268 100644 --- a/gui.py +++ b/gui.py @@ -14,6 +14,7 @@ from tools.gui.qtkontakt import QtKontakt from tools.gui.qtterminsuche import QtTerminsuche from tools.its import ImpfterminService +from tools.utils import create_missing_dirs PATH = os.path.dirname(os.path.realpath(__file__)) @@ -48,7 +49,9 @@ def __init__(self, pfad_fenster_layout: str = os.path.join(PATH, "tools/gui/main """ super().__init__() - + + create_missing_dirs() + # Laden der .ui Datei und Anpassungen uic.loadUi(pfad_fenster_layout, self) self.setWindowIcon(QIcon(os.path.join(PATH, "images/spritze.ico"))) diff --git a/tools/__init__.py b/tools/__init__.py new file mode 100644 index 00000000..e69de29b From 996a6ca3b13508a48fcdc9ef66bbee887e758333 Mon Sep 17 00:00:00 2001 From: JuliusJacobitz Date: Sat, 29 May 2021 20:45:19 +0200 Subject: [PATCH 25/25] inno setup for windows gui --- .github/workflows/build.yaml | 2 ++ .github/workflows/deploy.yaml | 41 +++++++++++++++++---- specs/windows-terminservice-gui.iss | 55 +++++++++++++++++++++++++++++ specs/windows-terminservice.iss | 1 + 4 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 specs/windows-terminservice-gui.iss diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 482d252a..ddb122b6 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -22,6 +22,8 @@ jobs: pip install -r requirements.txt - name: Build Windows run: pyinstaller --clean --noconfirm specs/windows-terminservice.spec + - name: Build Windows GUI + run: pyinstaller --clean --noconfirm specs/windows-terminservice-gui.spec build-linux: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index bf4e4191..8efa7197 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -16,6 +16,9 @@ jobs: steps: - name: Checkout uses: actions/checkout@v2.3.4 + - name: tagname + uses: olegtarasov/get-tag@v2.1 + - name: Setup Python uses: actions/setup-python@v2.2.2 with: @@ -24,33 +27,57 @@ jobs: run: | python -m pip install --upgrade pip pip install -r requirements.txt + - name: Build Windows run: pyinstaller --clean --noconfirm specs/windows-terminservice.spec + - name: Build Windows GUI + run: pyinstaller --clean --noconfirm specs/windows-terminservice-gui.spec + - name: Chocolatey install innosetup uses: crazy-max/ghaction-chocolatey@v1.5.0 with: # Arguments to pass to Chocolatey args: install innosetup --install-arguments="'/DIR=../innosetup'" --force - - name: tagname - uses: olegtarasov/get-tag@v2.1 - - name: Run innosetup + + - name: Innosetup windows-terminservice # Run innosetup and set application version to git tag run: ../innosetup/ISCC.exe specs/windows-terminservice.iss /DApplicationVersion=${{ env.GIT_TAG_NAME }} - - name: Zip + - name: Innosetup windows-terminservice-gui + # Run innosetup and set application version to git tag + run: ../innosetup/ISCC.exe specs/windows-terminservice-gui.iss /DApplicationVersion=${{ env.GIT_TAG_NAME }} + + - name: Zip Windows uses: papeloto/action-zip@v1 with: - files: dist/ + files: dist/windows-terminservice dest: vaccipy-windows.zip + - name: Zip Windows gui + uses: papeloto/action-zip@v1 + with: + files: dist/windows-terminservice-gui + dest: vaccipy-windows-gui.zip + - name: Store windows zip as build artifact uses: actions/upload-artifact@v2 with: name: vaccipy-windows path: vaccipy-windows.zip + - name: Store windows gui zip as build artifact + uses: actions/upload-artifact@v2 + with: + name: vaccipy-windows-gui + path: vaccipy-windows-gui.zip + - name: Store windows installer as build artifact uses: actions/upload-artifact@v2 with: name: vaccipy-windows-installer path: installers/vaccipy_installer.exe + - name: Store windows gui installer as build artifact + uses: actions/upload-artifact@v2 + with: + name: vaccipy-windows-gui-installer + path: installers/vaccipy_gui_installer.exe build-linux: runs-on: ubuntu-latest @@ -72,7 +99,7 @@ jobs: with: files: dist/ dest: vaccipy-ubuntu.zip - - name: Store windows build artifact + - name: Store ubuntu build artifact uses: actions/upload-artifact@v2 with: name: vaccipy-ubuntu @@ -90,5 +117,7 @@ jobs: prerelease: false files: | vaccipy-windows-installer/vaccipy_installer.exe + vaccipy-windows-gui-installer/vaccipy_gui_installer.exe vaccipy-windows/vaccipy-windows.zip + vaccipy-windows-gui/vaccipy-windows-gui.zip vaccipy-ubuntu/vaccipy-ubuntu.zip diff --git a/specs/windows-terminservice-gui.iss b/specs/windows-terminservice-gui.iss new file mode 100644 index 00000000..8ba3be8b --- /dev/null +++ b/specs/windows-terminservice-gui.iss @@ -0,0 +1,55 @@ +; Script generated by the Inno Script Studio Wizard. +; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! + +#define MyAppName "Vaccipy GUI" + +#ifndef ApplicationVersion +#define ApplicationVersion "0.1" +#endif + +#define MyAppPublisher "vaccipy" +#define MyAppURL "https://github.com/iamnotturner/vaccipy" +#define MyAppExeName "windows-terminservice-gui.exe" + +[Setup] +; NOTE: The value of AppId uniquely identifies this application. +; Do not use the same AppId value in installers for other applications. +; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) +AppId={{E8DD1F42-66E8-47F6-8B1F-CBC47CDC78BE} +AppName={#MyAppName} +AppVersion={#ApplicationVersion} +;AppVerName={#MyAppName} {#ApplicationVersion} +AppPublisher={#MyAppPublisher} +AppPublisherURL={#MyAppURL} +AppSupportURL={#MyAppURL} +AppUpdatesURL={#MyAppURL} +DefaultDirName={pf}\vaccipy_gui +DisableDirPage=yes +DefaultGroupName={#MyAppName} +OutputDir=../installers +OutputBaseFilename=vaccipy_gui_installer +SetupIconFile=../images/spritze.ico +Compression=lzma +SolidCompression=yes + +[Languages] +Name: "german"; MessagesFile: "compiler:Languages\German.isl" + +[Tasks] +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked + +[Files] +Source: "..\dist\windows-terminservice-gui\windows-terminservice-gui.exe"; DestDir: "{app}"; Flags: ignoreversion +Source: "..\dist\windows-terminservice-gui\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs +; NOTE: Don't use "Flags: ignoreversion" on any shared system files + +[Icons] +Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" +Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon + +[Run] +Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent + +[Dirs] +Name: "{app}\data"; Flags: uninsalwaysuninstall; Permissions: users-modify +Name: "{app}\tools\log"; Flags: uninsalwaysuninstall; Permissions: users-modify \ No newline at end of file diff --git a/specs/windows-terminservice.iss b/specs/windows-terminservice.iss index 32895d06..2caf1416 100644 --- a/specs/windows-terminservice.iss +++ b/specs/windows-terminservice.iss @@ -27,6 +27,7 @@ DefaultDirName={pf}\{#MyAppName} DefaultGroupName={#MyAppName} OutputDir=../installers OutputBaseFilename=vaccipy_installer +SetupIconFile=../images/spritze.ico Compression=lzma SolidCompression=yes