This commit is contained in:
2025-05-26 18:23:25 +00:00
parent 757df6afe9
commit 7304d08149

View File

@@ -47,9 +47,44 @@ def get_docs_service_with_service_account():
return None return None
def create_google_doc_from_csv(service): def create_google_doc_from_csv(service):
# ... (CSV-Verarbeitung bleibt gleich) ... """Liest CSV, verarbeitet Daten und erstellt/befüllt das Google Doc."""
kinder_nach_gruppen = collections.defaultdict(list)
# 1. Neues Google Doc erstellen (bleibt gleich) 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
# Kinder innerhalb jeder Gruppe sortieren
for gruppe_key in kinder_nach_gruppen: # Geändert von 'gruppe' zu 'gruppe_key' um Verwechslung zu vermeiden
kinder_nach_gruppen[gruppe_key].sort(key=lambda x: (x['Nachname'].lower(), x['Vorname'].lower()))
# ***** KORREKTUR HIER: sorted_gruppen_namen definieren *****
sorted_gruppen_namen = sorted(kinder_nach_gruppen.keys())
stand_zeit = datetime.now().strftime("%d.%m.%Y %H:%M Uhr")
# 1. Neues Google Doc erstellen
try: try:
doc_body = {'title': GOOGLE_DOC_TITLE} doc_body = {'title': GOOGLE_DOC_TITLE}
doc = service.documents().create(body=doc_body).execute() doc = service.documents().create(body=doc_body).execute()
@@ -63,7 +98,7 @@ def create_google_doc_from_csv(service):
requests = [] requests = []
current_index = 1 current_index = 1
for i, gruppe_original in enumerate(sorted_gruppen_namen): for i, gruppe_original in enumerate(sorted_gruppen_namen): # Jetzt ist sorted_gruppen_namen definiert
kinder_liste = kinder_nach_gruppen[gruppe_original] kinder_liste = kinder_nach_gruppen[gruppe_original]
anzahl_kinder = len(kinder_liste) anzahl_kinder = len(kinder_liste)
gruppe_display_name = gruppe_original + GRUPPENNAME_SUFFIX gruppe_display_name = gruppe_original + GRUPPENNAME_SUFFIX
@@ -77,11 +112,9 @@ def create_google_doc_from_csv(service):
current_index += len(header_text_for_page) current_index += len(header_text_for_page)
# --- Tabelle --- # --- Tabelle ---
# Tabelle erstellen
num_rows_for_table = len(kinder_liste) + 1 num_rows_for_table = len(kinder_liste) + 1
num_cols_for_table = 3 num_cols_for_table = 3
# Füge die Tabellenstruktur ein
requests.append({ requests.append({
'insertTable': { 'insertTable': {
'location': {'index': current_index}, 'location': {'index': current_index},
@@ -89,60 +122,26 @@ def create_google_doc_from_csv(service):
'columns': num_cols_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_fill_start_index = current_index + 1 # Index, an dem der Text für die Zellen beginnt table_fill_start_index = current_index + 1
# Baue den gesamten Textinhalt für die Tabelle
table_text_content = [] table_text_content = []
# Kopfzeile table_text_content.append("Nachname\tVorname\tGruppe")
table_text_content.append("Nachname\tVorname\tGruppe") # Tabs für Spalten
# Datenzeilen
for kind in kinder_liste: for kind in kinder_liste:
table_text_content.append(f"{kind['Nachname']}\t{kind['Vorname']}\t{gruppe_display_name}") 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 full_table_text = "\n".join(table_text_content) + "\n"
# 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({ requests.append({
'insertText': { 'insertText': {
'location': {'index': table_fill_start_index}, # Am Anfang der ersten Zelle 'location': {'index': table_fill_start_index},
'text': full_table_text '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 ---
# Korrektur: 'text' muss auf derselben Ebene wie 'endOfSegmentLocation' sein
footer_text_for_page = ( 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" f"\n{anzahl_kinder} angemeldete Kinder\n\n"
"Dies ist die Liste der bereits angemeldeten Kinder. Bitte die Eltern der noch fehlenden\n" "Dies ist die Liste der bereits angemeldeten Kinder. Bitte die Eltern der noch fehlenden\n"
"Kinder an die Anmeldung erinnern.\n\n" "Kinder an die Anmeldung erinnern.\n\n"
@@ -151,40 +150,53 @@ def create_google_doc_from_csv(service):
) )
requests.append({ requests.append({
'insertText': { 'insertText': {
# 'location': {'index': current_index_for_footer}, # VERSUCH 1 'endOfSegmentLocation': {},
'endOfSegmentLocation': {} # VERSUCH 2: Sicherer, fügt am Ende des aktuellen Haupttextkörpers ein 'text': footer_text_for_page
# 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 --- # --- Seitenumbruch ---
if i < len(sorted_gruppen_namen) - 1: if i < len(sorted_gruppen_namen) - 1:
requests.append({'insertPageBreak': {'endOfSegmentLocation': {}}}) requests.append({'insertPageBreak': {'endOfSegmentLocation': {}}})
# current_index wird für die nächste Seite wieder auf 1 gesetzt, # Reset current_index for the new page if not using endOfSegmentLocation for header
# da ein PageBreak einen neuen Abschnitt startet, in dem die Indizes neu beginnen. # Da wir endOfSegmentLocation für den nächsten Header nicht explizit verwenden (er basiert auf current_index),
# Dies ist eine Vereinfachung. Technisch gesehen addiert sich der Index weiter, # müssen wir sicherstellen, dass current_index für die nächste Seite korrekt ist.
# aber für `endOfSegmentLocation` ist es nicht so kritisch. # Mit endOfSegmentLocation für Footer und PageBreak ist die manuelle Index-Verfolgung
# Für den nächsten Header setzen wir current_index NICHT zurück, da endOfSegmentLocation verwendet wird. # für den *nächsten* Header schwierig.
# Wir nehmen an, dass `endOfSegmentLocation` den Cursor richtig für den nächsten Block positioniert. # Eine sicherere Methode wäre, den Header der nächsten Seite auch mit endOfSegmentLocation zu beginnen.
# Wenn `location` verwendet wird, müssten wir den `current_index` genau weiterführen. # Für jetzt lassen wir current_index einfach weiterlaufen und hoffen, dass
# endOfSegmentLocation für Footer/PageBreak den Cursor korrekt für den *nächsten* Block
# (der wieder 'location' mit 'current_index' verwendet) positioniert.
# Dies könnte ein potenzielles Problem für die nächste Iteration sein, wenn der Footer/PageBreak
# den `current_index` nicht so verschiebt, wie es für den nächsten Header-Insert benötigt wird.
# EINFACHERE LÖSUNG: current_index nach endOfSegmentLocation nicht mehr verwenden.
# Wenn endOfSegmentLocation verwendet wird, sollte der nächste Insert auch endOfSegmentLocation verwenden oder
# man muss den Index explizit auf 1 für eine neue Seite setzen (was aber nicht korrekt ist, wenn das Dokument
# als ein langer Stream behandelt wird).
# Für jetzt: Da der Header der nächsten Seite `current_index` verwendet, müssen wir ihn aktualisieren.
# Ein Page Break fügt 1 zum Index hinzu. Der Footer hat `len(footer_text_for_page)`.
current_index += len(footer_text_for_page) + 1 # +1 für den PageBreak
# Batch-Update ausführen (bleibt gleich)
# Batch-Update ausführen
if requests: if requests:
try: try:
print("Sende Batch Update an Google Docs API...") print("Sende Batch Update an Google Docs API...")
print("Anzahl der Requests:", len(requests)) print("Anzahl der Requests:", len(requests))
# print("Requests (Auszug):", requests[:5]) # Für Debugging ggf. komplett ausgeben
service.documents().batchUpdate( service.documents().batchUpdate(
documentId=document_id, body={'requests': requests} documentId=document_id, body={'requests': requests}
).execute() ).execute()
print("Dokument erfolgreich befüllt.") print("Dokument erfolgreich befüllt.")
except HttpError as err: except HttpError as err:
print(f"Fehler beim Befüllen des Google Dokuments: {err}") print(f"Fehler beim Befüllen des Google Dokuments: {err}")
error_details = err.content.decode('utf-8') if err.content else "Keine Details verfügbar." error_details = "Keine Fehlerdetails im Content."
if err.content:
try:
error_details = err.content.decode('utf-8')
except Exception as e_decode:
error_details = f"Fehler beim Dekodieren der Fehlerdetails: {e_decode}"
print(f"Details zum Fehler ({err.resp.status} {err._get_reason()}): {error_details}") 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 return document_id