This commit is contained in:
2025-05-26 18:20:27 +00:00
parent e13108ea4f
commit 0a2840aadd

View File

@@ -47,72 +47,15 @@ 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):
"""Liest CSV, verarbeitet Daten und erstellt/befüllt das Google Doc.""" # ... (CSV-Verarbeitung bleibt gleich) ...
# (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.)
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
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
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()
document_id = doc.get('documentId') document_id = doc.get('documentId')
print(f"Google Doc erstellt mit ID: {document_id}") print(f"Google Doc erstellt mit ID: {document_id}")
print(f"Link: https://docs.google.com/document/d/{document_id}/edit") 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: except HttpError as err:
print(f"Fehler beim Erstellen des Google Dokuments: {err}") print(f"Fehler beim Erstellen des Google Dokuments: {err}")
return None return None
@@ -125,61 +68,126 @@ def create_google_doc_from_csv(service):
anzahl_kinder = len(kinder_liste) anzahl_kinder = len(kinder_liste)
gruppe_display_name = gruppe_original + GRUPPENNAME_SUFFIX gruppe_display_name = gruppe_original + GRUPPENNAME_SUFFIX
header_text = ( # --- Seiten-Header ---
header_text_for_page = (
f"{EINRICHTUNG}\t\t\t{FOTOGRAF_NAME}\n" f"{EINRICHTUNG}\t\t\t{FOTOGRAF_NAME}\n"
f"{FOTODATUM}\n\n" f"{FOTODATUM}\n\n"
) )
requests.append({'insertText': {'location': {'index': current_index}, 'text': header_text}}) requests.append({'insertText': {'location': {'index': current_index}, 'text': header_text_for_page}})
current_index += len(header_text) current_index += len(header_text_for_page)
num_rows = len(kinder_liste) + 1 # --- Tabelle ---
num_cols = 3 # 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 table_fill_start_index = current_index + 1 # Index, an dem der Text für die Zellen beginnt
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
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" 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"
f"Stand {stand_zeit}\n\n" f"Stand {stand_zeit}\n\n"
f"{FOTOGRAF_NAME}\n{FOTOGRAF_ADRESSE}\n{FOTOGRAF_WEB}\n{FOTOGRAF_TEL}\n" f"{FOTOGRAF_NAME}\n{FOTOGRAF_ADRESSE}\n{FOTOGRAF_WEB}\n{FOTOGRAF_TEL}\n"
) )
requests.append({'insertText': {'location': {'index': current_index}, 'text': footer_text}}) requests.append({
current_index += len(footer_text) '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: if i < len(sorted_gruppen_namen) - 1:
requests.append({'insertPageBreak': {'endOfSegmentLocation': {}}}) 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: if requests:
try: 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( 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}")
print("Details zum Fehler:", err.resp.status, err._get_reason()) error_details = err.content.decode('utf-8') if err.content else "Keine Details verfügbar."
print("Gesendete Requests (Auszug):", requests[:5]) 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
if __name__ == '__main__': if __name__ == '__main__':
print(f"Info: Verwendetes Fotodatum: {FOTODATUM}") print(f"Info: Verwendetes Fotodatum: {FOTODATUM}")
print(f"Info: Gruppennamen werden mit Suffix '{GRUPPENNAME_SUFFIX}' versehen.") print(f"Info: Gruppennamen werden mit Suffix '{GRUPPENNAME_SUFFIX}' versehen.")