From 0a2840aadd986b0f516e098a6be235722dab3f18 Mon Sep 17 00:00:00 2001 From: Floke Date: Mon, 26 May 2025 18:20:27 +0000 Subject: [PATCH] bugfix --- list_generator.py | 182 ++++++++++++++++++++++++---------------------- 1 file changed, 95 insertions(+), 87 deletions(-) diff --git a/list_generator.py b/list_generator.py index 3e92a6b5..e4c9c888 100644 --- a/list_generator.py +++ b/list_generator.py @@ -47,72 +47,15 @@ def get_docs_service_with_service_account(): return None def create_google_doc_from_csv(service): - """Liest CSV, verarbeitet Daten und erstellt/befüllt das Google Doc.""" - # (Dieser Funktionsteil bleibt identisch mit dem aus der vorherigen Antwort, - # die die Google Docs API verwendet, um das Dokument zu befüllen. - # Sie beginnt mit dem Einlesen der CSV und endet mit dem batchUpdate.) + # ... (CSV-Verarbeitung bleibt gleich) ... - kinder_nach_gruppen = collections.defaultdict(list) - - try: - with open(CSV_FILENAME, mode='r', encoding='utf-8-sig', newline='') as csvfile: - reader = csv.DictReader(csvfile, delimiter=';') - for row in reader: - vorname = row.get('Vorname', '').strip() - nachname = row.get('Nachname', '').strip() - gruppe_original = row.get('Gruppe', '').strip() - - if not vorname or not nachname or not gruppe_original: - print(f"Warnung: Zeile übersprungen wegen fehlender Daten: {row}") - continue - kinder_nach_gruppen[gruppe_original].append({ - 'Nachname': nachname, - 'Vorname': vorname - }) - except FileNotFoundError: - print(f"FEHLER: Die Datei '{CSV_FILENAME}' wurde nicht gefunden.") - return None - except Exception as e: - print(f"FEHLER beim Lesen der CSV-Datei: {e}") - return None - - if not kinder_nach_gruppen: - print("Keine Daten aus der CSV-Datei geladen.") - return None - - for gruppe in kinder_nach_gruppen: - kinder_nach_gruppen[gruppe].sort(key=lambda x: (x['Nachname'].lower(), x['Vorname'].lower())) - - sorted_gruppen_namen = sorted(kinder_nach_gruppen.keys()) - stand_zeit = datetime.now().strftime("%d.%m.%Y %H:%M Uhr") - - # 1. Neues Google Doc erstellen + # 1. Neues Google Doc erstellen (bleibt gleich) try: doc_body = {'title': GOOGLE_DOC_TITLE} doc = service.documents().create(body=doc_body).execute() document_id = doc.get('documentId') print(f"Google Doc erstellt mit ID: {document_id}") print(f"Link: https://docs.google.com/document/d/{document_id}/edit") - - # WICHTIG: Teilen Sie das Dokument mit Ihrem Hauptkonto, wenn Sie es dort sehen möchten - # Holen Sie sich dazu den Drive Service (benötigt 'https://www.googleapis.com/auth/drive' Scope) - # drive_service = build('drive', 'v3', credentials=creds) # Benötigt ggf. Drive Scope im Service Account - # user_permission = { - # 'type': 'user', - # 'role': 'writer', # oder 'owner', 'reader' - # 'emailAddress': 'ihre-email@example.com' # IHRE E-MAIL-ADRESSE - # } - # try: - # drive_service.permissions().create( - # fileId=document_id, - # body=user_permission, - # fields='id' - # ).execute() - # print(f"Dokument wurde mit 'ihre-email@example.com' geteilt.") - # except HttpError as error: - # print(f"Fehler beim Teilen des Dokuments: {error}") - # Dieses Teilen ist optional und erfordert, dass der Service Account auch Drive-Berechtigungen hat. - except HttpError as err: print(f"Fehler beim Erstellen des Google Dokuments: {err}") return None @@ -125,61 +68,126 @@ def create_google_doc_from_csv(service): anzahl_kinder = len(kinder_liste) gruppe_display_name = gruppe_original + GRUPPENNAME_SUFFIX - header_text = ( + # --- Seiten-Header --- + header_text_for_page = ( f"{EINRICHTUNG}\t\t\t{FOTOGRAF_NAME}\n" f"{FOTODATUM}\n\n" ) - requests.append({'insertText': {'location': {'index': current_index}, 'text': header_text}}) - current_index += len(header_text) + requests.append({'insertText': {'location': {'index': current_index}, 'text': header_text_for_page}}) + current_index += len(header_text_for_page) - num_rows = len(kinder_liste) + 1 - num_cols = 3 + # --- Tabelle --- + # Tabelle erstellen + num_rows_for_table = len(kinder_liste) + 1 + num_cols_for_table = 3 - requests.append({'insertTable': {'location': {'index': current_index}, 'rows': num_rows, 'columns': num_cols}}) + # Füge die Tabellenstruktur ein + requests.append({ + 'insertTable': { + 'location': {'index': current_index}, + 'rows': num_rows_for_table, + 'columns': num_cols_for_table + } + }) + # WICHTIG: Nach insertTable befindet sich der Cursor (und somit der nächste Einfügepunkt + # für Text, den die API in die Zellen füllen soll) typischerweise direkt + # AM ANFANG der ersten Zelle. Der Index für diesen Punkt ist current_index + 1 + # (da insertTable selbst eine bestimmte Länge im JSON-Request hat, aber der + # Dokumentenindex sich auf den Punkt bezieht, an dem die *Tabelle* beginnt). - table_content_start_index = current_index + 2 - - kopf_zellen = ["Nachname", "Vorname", "Gruppe"] - for z, kopf_text in enumerate(kopf_zellen): - text_to_insert = kopf_text + ('\t' if z < len(kopf_zellen) -1 else '') - requests.append({'insertText': {'location': {'index': table_content_start_index }, 'text': text_to_insert}}) - table_content_start_index += len(text_to_insert) - - for kind in kinder_liste: - row_data = [kind['Nachname'], kind['Vorname'], gruppe_display_name] - for z, cell_text in enumerate(row_data): - text_to_insert = cell_text + ('\t' if z < len(row_data) -1 else '\n') - requests.append({'insertText': {'location': {'index': table_content_start_index}, 'text': text_to_insert}}) - table_content_start_index += len(text_to_insert) - - current_index = table_content_start_index + table_fill_start_index = current_index + 1 # Index, an dem der Text für die Zellen beginnt - footer_text = ( + # Baue den gesamten Textinhalt für die Tabelle + table_text_content = [] + # Kopfzeile + table_text_content.append("Nachname\tVorname\tGruppe") # Tabs für Spalten + # Datenzeilen + for kind in kinder_liste: + table_text_content.append(f"{kind['Nachname']}\t{kind['Vorname']}\t{gruppe_display_name}") + + full_table_text = "\n".join(table_text_content) + "\n" # Newlines für Zeilen, plus ein extra Newline am Ende + + # Füge den gesamten Tabelleninhalt in einem Rutsch ein. + # Die API sollte dies in die Zellen der zuvor eingefügten Tabelle füllen. + requests.append({ + 'insertText': { + 'location': {'index': table_fill_start_index}, # Am Anfang der ersten Zelle + 'text': full_table_text + } + }) + # Aktualisiere den current_index um die Länge des eingefügten Tabellentextes + # PLUS die Strukturzeichen der Tabelle selbst. Dies ist schwer genau zu bestimmen. + # Eine sicherere Methode ist, nach diesem Batch-Update den aktuellen Dokumenten-Endindex + # abzufragen, aber für eine einfache Sequenz versuchen wir es mit einer Annahme. + # Die Länge der Struktur ist ungefähr: rows * cols * (1 für cell end) + rows * (1 für row end) + 1 (für table end) + # Das ist zu komplex. Einfacher: Der current_index wird auf den Start der Tabelle gesetzt, + # plus die Länge des reinen Textes, der eingefügt wurde. Die API fügt den Text *in* die Tabelle ein. + # Der nächste Text wird *nach* der Tabelle eingefügt. + # current_index bleibt current_index (Start der Tabelle), der nächste Text wird DANACH eingefügt. + # Also: current_index für den Footer muss *nach* der Tabelle sein. + # Die Tabelle selbst belegt `1` (für `insertTable`) im `requests` Array. + # Der Text der Tabelle belegt `len(full_table_text)`. + # Der Cursor ist nach dem Einfügen des Textes am Ende des Textes *innerhalb* der letzten Zelle. + # Um *nach* der Tabelle zu sein, brauchen wir `endOfSegmentLocation` oder eine bessere Indexlogik. + + # NEUER ANSATZ für Index nach Tabelle: + # Der `current_index` zeigt auf den Beginn der Tabelle. + # Der nächste Text (Footer) muss nach der Tabelle eingefügt werden. + # Wir setzen den Index für den Footer auf current_index + 1 (symbolisch für "nach der Tabelle"). + # Die API wird versuchen, dies nach der gerade bearbeiteten Tabellenstruktur einzufügen. + + current_index_for_footer = current_index + 1 # Symbolisch "nach der Tabelle" + + # --- Footer --- + footer_text_for_page = ( + # Ein Newline am Anfang, um sicherzustellen, dass wir aus der Tabelle heraus sind + # Dies ist oft nicht nötig, wenn die API den Cursor korrekt nach der Tabelle platziert. + # Wir lassen es erstmal weg, da full_table_text mit \n endet. f"\n{anzahl_kinder} angemeldete Kinder\n\n" "Dies ist die Liste der bereits angemeldeten Kinder. Bitte die Eltern der noch fehlenden\n" "Kinder an die Anmeldung erinnern.\n\n" f"Stand {stand_zeit}\n\n" f"{FOTOGRAF_NAME}\n{FOTOGRAF_ADRESSE}\n{FOTOGRAF_WEB}\n{FOTOGRAF_TEL}\n" ) - requests.append({'insertText': {'location': {'index': current_index}, 'text': footer_text}}) - current_index += len(footer_text) + requests.append({ + 'insertText': { + # 'location': {'index': current_index_for_footer}, # VERSUCH 1 + 'endOfSegmentLocation': {} # VERSUCH 2: Sicherer, fügt am Ende des aktuellen Haupttextkörpers ein + # Nachdem die Tabelle und ihr Inhalt verarbeitet wurden. + }, + 'text': footer_text_for_page # Text muss auf derselben Ebene wie location sein + }) + # Wenn endOfSegmentLocation verwendet wird, ist die manuelle Indexverfolgung hier schwierig. + # --- Seitenumbruch --- if i < len(sorted_gruppen_namen) - 1: requests.append({'insertPageBreak': {'endOfSegmentLocation': {}}}) - # current_index += 1 # Für Page Break, aber mit endOfSegmentLocation nicht mehr so relevant für Index + # current_index wird für die nächste Seite wieder auf 1 gesetzt, + # da ein PageBreak einen neuen Abschnitt startet, in dem die Indizes neu beginnen. + # Dies ist eine Vereinfachung. Technisch gesehen addiert sich der Index weiter, + # aber für `endOfSegmentLocation` ist es nicht so kritisch. + # Für den nächsten Header setzen wir current_index NICHT zurück, da endOfSegmentLocation verwendet wird. + # Wir nehmen an, dass `endOfSegmentLocation` den Cursor richtig für den nächsten Block positioniert. + # Wenn `location` verwendet wird, müssten wir den `current_index` genau weiterführen. + # Batch-Update ausführen (bleibt gleich) if requests: try: + print("Sende Batch Update an Google Docs API...") + print("Anzahl der Requests:", len(requests)) + # print("Requests (Auszug):", requests[:5]) # Für Debugging ggf. komplett ausgeben service.documents().batchUpdate( documentId=document_id, body={'requests': requests} ).execute() print("Dokument erfolgreich befüllt.") except HttpError as err: print(f"Fehler beim Befüllen des Google Dokuments: {err}") - print("Details zum Fehler:", err.resp.status, err._get_reason()) - print("Gesendete Requests (Auszug):", requests[:5]) + error_details = err.content.decode('utf-8') if err.content else "Keine Details verfügbar." + print(f"Details zum Fehler ({err.resp.status} {err._get_reason()}): {error_details}") + # print("Gesendete Requests:", requests) # Alle Requests ausgeben für genaues Debugging return document_id + if __name__ == '__main__': print(f"Info: Verwendetes Fotodatum: {FOTODATUM}") print(f"Info: Gruppennamen werden mit Suffix '{GRUPPENNAME_SUFFIX}' versehen.")