From 4e654ad3151457bf4e59d863ae1b63eefec0b524 Mon Sep 17 00:00:00 2001 From: Floke Date: Mon, 26 May 2025 19:37:41 +0000 Subject: [PATCH] revert to tabs --- list_generator.py | 212 ++++++++++++++-------------------------------- 1 file changed, 62 insertions(+), 150 deletions(-) diff --git a/list_generator.py b/list_generator.py index 88927aff..8e3483d4 100644 --- a/list_generator.py +++ b/list_generator.py @@ -23,14 +23,12 @@ FOTOGRAF_TEL = "08122-8470867" SCOPES = [ 'https://www.googleapis.com/auth/documents', - 'https://www.googleapis.com/auth/drive.file' # Notwendig für Erstellung im Ordner - # Alternativ: 'https://www.googleapis.com/auth/drive' für vollen Drive-Zugriff + 'https://www.googleapis.com/auth/drive.file' ] SERVICE_ACCOUNT_FILE = 'service_account.json' # --- Ende Konfiguration --- -def get_services_with_service_account(): # Umbenannt für Klarheit - """Erstellt Docs und Drive API-Dienste mit einem Service Account.""" +def get_services_with_service_account(): creds = None docs_service = None drive_service = None @@ -38,198 +36,112 @@ def get_services_with_service_account(): # Umbenannt für Klarheit creds = service_account.Credentials.from_service_account_file( SERVICE_ACCOUNT_FILE, scopes=SCOPES) except Exception as e: - print(f"Fehler beim Laden der Service Account Credentials aus '{SERVICE_ACCOUNT_FILE}': {e}") - return None, None - + print(f"Fehler Credentials: {e}"); return None, None try: docs_service = build('docs', 'v1', credentials=creds) - print("Google Docs API Service erfolgreich erstellt.") - except Exception as e_docs: - print(f"Konnte Google Docs API Service nicht erstellen: {e_docs}") - + print("Google Docs API Service erstellt.") + except Exception as e_docs: print(f"Fehler Docs Service: {e_docs}") try: - # Prüfen, ob Drive Scope vorhanden ist, bevor Drive Service gebaut wird - has_drive_scope = any(s in SCOPES for s in ['https://www.googleapis.com/auth/drive.file', 'https://www.googleapis.com/auth/drive']) - if has_drive_scope: + if any(s in SCOPES for s in ['https://www.googleapis.com/auth/drive.file', 'https://www.googleapis.com/auth/drive']): drive_service = build('drive', 'v3', credentials=creds) - print("Google Drive API Service erfolgreich erstellt.") - else: - print("WARNUNG: Kein Drive Scope in SCOPES definiert. Drive Service wird nicht erstellt.") - print(" Dokumente können nicht in spezifischen Ordnern erstellt werden.") - except Exception as e_drive: - print(f"Konnte Google Drive API Service nicht erstellen: {e_drive}") - - return docs_service, drive_service # Gibt beide Services zurück + print("Google Drive API Service erstellt.") + else: print("WARNUNG: Kein Drive Scope für Drive Service.") + except Exception as e_drive: print(f"Fehler Drive Service: {e_drive}") + return docs_service, drive_service -def create_and_fill_doc(docs_service, drive_service, folder_id, doc_title): +def create_and_fill_doc_plain_text(docs_service, drive_service, folder_id, doc_title): document_id = None - - # 1. Dokument erstellen (Logik bleibt wie in der Version, die Drive API bevorzugt) if drive_service and folder_id: - file_metadata = { 'name': doc_title, 'mimeType': 'application/vnd.google-apps.document', 'parents': [folder_id] } + file_metadata = {'name': doc_title, 'mimeType': 'application/vnd.google-apps.document', 'parents': [folder_id]} try: created_file = drive_service.files().create(body=file_metadata, fields='id').execute() document_id = created_file.get('id') - print(f"Google Doc via Drive API in Ordner '{folder_id}' erstellt, ID: {document_id}") - except HttpError as err_drive_create: - print(f"Fehler beim Erstellen des Dokuments mit Drive API im Ordner: {err_drive_create}") - if err_drive_create.resp.status == 403: print(" -> Fehlende Scopes oder Drive API nicht aktiv?") - print(" Versuche Fallback...") - except Exception as e_drive_create_general: - print(f"Allg. Fehler bei Drive API Erstellung: {e_drive_create_general}\n Versuche Fallback...") - - if not document_id: - if not docs_service: print("FEHLER: Docs API Service nicht verfügbar."); return None + print(f"Doc via Drive API in Ordner '{folder_id}' erstellt, ID: {document_id}") + except HttpError as err: print(f"Fehler Drive API Erstellung: {err}\n Versuche Fallback...") + except Exception as e: print(f"Allg. Fehler Drive API Erstellung: {e}\n Versuche Fallback...") + if not document_id: + if not docs_service: print("FEHLER: Docs Service nicht da."); return None try: doc = docs_service.documents().create(body={'title': doc_title}).execute() document_id = doc.get('documentId') - print(f"Google Doc via Docs API (Root des SA) erstellt, ID: {document_id}") + print(f"Doc via Docs API (Root SA) erstellt, ID: {document_id}") if folder_id: print(f" BITTE manuell in Ordner '{folder_id}' verschieben.") - except Exception as e_docs_create: print(f"Konnte Doc auch nicht mit Docs API erstellen: {e_docs_create}"); return None - + except Exception as e: print(f"Konnte Doc auch nicht mit Docs API erstellen: {e}"); return None if not document_id: print("Konnte keine Doc-ID erhalten."); return None - # 2. Daten aus CSV lesen (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: - # ... (CSV Leselogik wie gehabt) ... - vorname = row.get('Vorname', '').strip(); nachname = row.get('Nachname', '').strip(); gruppe_original = row.get('Gruppe', '').strip() + 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: continue kinder_nach_gruppen[gruppe_original].append({'Nachname': nachname, 'Vorname': vorname}) - except Exception as e: print(f"FEHLER beim CSV-Lesen: {e}"); return document_id - if not kinder_nach_gruppen: print("Keine CSV-Daten."); return document_id + except Exception as e: print(f"FEHLER CSV-Lesen: {e}"); return document_id + if not kinder_nach_gruppen: print("Keine CSV-Daten."); return document_id for gk in kinder_nach_gruppen: kinder_nach_gruppen[gk].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") - # 3. Dokument befüllen - MIT ECHTEN TABELLEN (Fokus auf Seite 1) requests = [] - current_doc_index = 1 # Start des Dokuments + col_width_nachname = 25 # Ggf. anpassen für besseres Padding + col_width_vorname = 25 # Ggf. anpassen for i, gruppe_original in enumerate(sorted_gruppen_namen): - # ---- NUR FÜR TESTZWECKE: Bearbeite nur die erste Gruppe ---- - if i > 0: - print(f"Überspringe Gruppe '{gruppe_original}' für diesen Testlauf (nur erste Gruppe wird mit Tabelle erstellt).") - # Damit der Footer der ersten Seite korrekt kommt und ein evtl. PageBreak, - # wenn es mehr als eine Gruppe gibt, müssen wir den Rest der Logik hier anpassen. - # Für den reinen Tabellentest ist dieser `break` ausreichend. - break - # ---- ENDE TEST ---- - kinder_liste = kinder_nach_gruppen[gruppe_original] anzahl_kinder = len(kinder_liste) gruppe_display_name = gruppe_original + GRUPPENNAME_SUFFIX - - # --- A. Seiten-Header --- - header_text = (f"{EINRICHTUNG}\t\t\t{FOTOGRAF_NAME}\n{FOTODATUM}\n\n") - requests.append({'insertText': {'location': {'index': current_doc_index}, 'text': header_text}}) - current_doc_index += len(header_text) + page_text_lines = [] + page_text_lines.append(f"{EINRICHTUNG}\t\t\t{FOTOGRAF_NAME}") + page_text_lines.append(FOTODATUM); page_text_lines.append("") - # --- B. Tabelle erstellen (leer) --- - num_rows_for_test = 1 # NUR EINE ZEILE FÜR DEN TEST (Kopfzeile) - num_cols = 3 + kopf_nachname = "Nachname".ljust(col_width_nachname) + kopf_vorname = "Vorname".ljust(col_width_vorname) + page_text_lines.append(f"{kopf_nachname}\t{kopf_vorname}\tGruppe") + for kind in kinder_liste: + nachname_gepadded = kind['Nachname'].ljust(col_width_nachname) + vorname_gepadded = kind['Vorname'].ljust(col_width_vorname) + page_text_lines.append(f"{nachname_gepadded}\t{vorname_gepadded}\t{gruppe_display_name}") + page_text_lines.append("") - table_start_index = current_doc_index - requests.append({ - 'insertTable': { - 'location': {'index': table_start_index}, - 'rows': num_rows_for_test, # Nur eine Zeile - 'columns': num_cols - } - }) + page_text_lines.append(f"{anzahl_kinder} angemeldete Kinder"); page_text_lines.append("") + page_text_lines.append("Dies ist die Liste der bereits angemeldeten Kinder. Bitte die Eltern der noch fehlenden") + page_text_lines.append("Kinder an die Anmeldung erinnern."); page_text_lines.append("") + page_text_lines.append(f"Stand {stand_zeit}"); page_text_lines.append("") + page_text_lines.append(FOTOGRAF_NAME); page_text_lines.append(FOTOGRAF_ADRESSE) + page_text_lines.append(FOTOGRAF_WEB); page_text_lines.append(FOTOGRAF_TEL) - # --- C. NUR KOPFZEILE einfügen --- - # Wir versuchen wieder verschiedene Offsets, aber nur für EINE Zeile Text. - # OFFSET-TEST: - # 1. table_start_index + 1 - # 2. table_start_index + 2 - # 3. table_start_index + 3 (unwahrscheinlich, aber zum Testen) - # 4. table_start_index + 4 (noch unwahrscheinlicher) + full_page_text = "\n".join(page_text_lines) + "\n" + if i == 0: + requests.append({'insertText': {'location': {'index': 1}, 'text': full_page_text}}) + else: + requests.append({'insertText': {'endOfSegmentLocation': {}, 'text': full_page_text}}) + if i < len(sorted_gruppen_namen) - 1: + requests.append({'insertPageBreak': {'endOfSegmentLocation': {}}}) - # BEGINNEN WIR MIT OFFSET +4, da die Struktur komplexer sein könnte als gedacht - # (TableStart, RowStart, CellStart, ParagraphStart?) - content_insertion_index_for_test = table_start_index + 4 # TESTWERT! - - kopfzeilen_text = "Nachname\tVorname\tGruppe\n" # Nur die Kopfzeile, mit Tabs und Newline am Ende - - requests.append({ - 'insertText': { - 'location': {'index': content_insertion_index_for_test}, - 'text': kopfzeilen_text - } - }) - - - - - # --- D. Index aktualisieren und Footer --- - # Den Index nach einer Tabelle korrekt weiterzuführen ist der schwierigste Teil. - # Die Tabelle selbst (Struktur) plus ihr Inhalt haben eine bestimmte Länge. - # Für diesen Testlauf mit nur einer Tabelle ist es weniger kritisch, was danach kommt, - # aber für Folgeseiten wäre es das. - # Wir verwenden hier endOfSegmentLocation für den Footer, um diese Komplexität zu umgehen. - - footer_text = ( - f"\n{anzahl_kinder} angemeldete Kinder\n\n" - # ... (Rest des Footers wie gehabt) ... - f"Dies ist die Liste der bereits angemeldeten Kinder. Bitte die Eltern der noch fehlenden\nKinder an die Anmeldung erinnern.\n\nStand {stand_zeit}\n\n{FOTOGRAF_NAME}\n{FOTOGRAF_ADRESSE}\n{FOTOGRAF_WEB}\n{FOTOGRAF_TEL}\n" - ) - requests.append({'insertText': {'endOfSegmentLocation': {}, 'text': footer_text}}) - - # --- E. Seitenumbruch (für diesen Test nicht relevant, da wir nach einer Gruppe abbrechen) --- - # if i < len(sorted_gruppen_namen) - 1: - # requests.append({'insertPageBreak': {'endOfSegmentLocation': {}}}) - # current_doc_index = 1 # Für die nächste Seite (vereinfachte Annahme, wenn EOS verwendet wird) - # oder muss komplexer berechnet werden. - - # Batch-Update ausführen (bleibt gleich) if requests: - if not docs_service: print("FEHLER: Docs Service nicht da."); return document_id + if not docs_service: print("FEHLER: Docs Service nicht da."); return document_id try: - print(f"Sende Batch Update (Test mit 1. Tabelle) für Doc ID '{document_id}'...") + print(f"Sende Batch Update (reiner Text) für Doc ID '{document_id}'...") docs_service.documents().batchUpdate(documentId=document_id, body={'requests': requests}).execute() - print("Dokument (Test mit 1. Tabelle) erfolgreich befüllt.") + print("Dokument erfolgreich mit reinem Text befüllt.") except HttpError as err: - print(f"Fehler beim Befüllen (Test mit 1. Tabelle) Doc ID '{document_id}': {err}") - # ... (Fehlerdetails wie gehabt) ... - error_details = "Keine Fehlerdetails im Content." - if err.content: - try: error_details = err.content.decode('utf-8') - except: pass - print(f"Details zum Fehler ({err.resp.status} {err._get_reason()}): {error_details}") - # Wichtig: Gib die Requests aus, um zu sehen, was gesendet wurde - print("\n--- Gesendete Requests für diesen Fehler: ---") - for req_idx, req_content in enumerate(requests): - print(f"Request [{req_idx}]: {req_content}") - print("--- Ende Requests ---") - + print(f"Fehler beim Befüllen (reiner Text) Doc ID '{document_id}': {err}") + # ... (Fehlerdetails) ... + return document_id return document_id -# --- Main execution block --- if __name__ == "__main__": - print(f"Info: Verwendetes Fotodatum: {FOTODATUM}") - print(f"Info: Gruppennamen werden mit Suffix '{GRUPPENNAME_SUFFIX}' versehen.") - print(f"Info: Zieldokumente sollen in Ordner-ID '{TARGET_FOLDER_ID}' landen.") - + print(f"Info: Fotodatum: {FOTODATUM}, Suffix: '{GRUPPENNAME_SUFFIX}', Ordner: '{TARGET_FOLDER_ID}'") docs_api_service, drive_api_service = get_services_with_service_account() - if docs_api_service: - final_doc_id = create_and_fill_doc( - docs_api_service, - drive_api_service, - TARGET_FOLDER_ID, - GOOGLE_DOC_TITLE - ) + final_doc_id = create_and_fill_doc_plain_text( # Funktionsname hier geändert + docs_api_service, drive_api_service, TARGET_FOLDER_ID, GOOGLE_DOC_TITLE ) if final_doc_id: - print(f"\n--- SKRIPT BEENDET (Testlauf erste Tabelle) ---") + print(f"\n--- SKRIPT BEENDET ---") print(f"Dokument-ID: {final_doc_id}") print(f"Link: https://docs.google.com/document/d/{final_doc_id}/edit") - if not drive_api_service or not TARGET_FOLDER_ID: - print("Das Dokument wurde im Root-Verzeichnis des Servicekontos erstellt.") - else: - print("\n--- FEHLGESCHLAGEN (Testlauf erste Tabelle) ---") - else: - print("Konnte Google Docs API Service nicht initialisieren. Skript wird beendet.") \ No newline at end of file + print("HINWEIS: Die 'Tabellen' wurden als tabulatorgetrennter Text eingefügt.") + print(" Markieren Sie den Text in Google Docs und verwenden Sie 'Einfügen > Tabelle > Tabelle aus Text erstellen...'") + else: print("\n--- FEHLGESCHLAGEN ---") + else: print("Konnte Docs Service nicht initialisieren.") \ No newline at end of file