208 lines
10 KiB
Python
208 lines
10 KiB
Python
import csv
|
|
from datetime import datetime
|
|
import collections
|
|
import os.path
|
|
|
|
# Für Service Account Authentifizierung
|
|
from google.oauth2 import service_account # Wichtig!
|
|
from googleapiclient.discovery import build
|
|
from googleapiclient.errors import HttpError
|
|
|
|
# --- Konfiguration ---
|
|
CSV_FILENAME = 'Namensliste.csv'
|
|
GOOGLE_DOC_TITLE = f"Gruppenlisten Kinderhaus St. Martin Neuching (Service Acc) - {datetime.now().strftime('%Y-%m-%d')}"
|
|
|
|
EINRICHTUNG = "Kinderhaus St. Martin Neuching"
|
|
FOTODATUM = "02. - 05.06.2025"
|
|
GRUPPENNAME_SUFFIX = "gruppe"
|
|
|
|
FOTOGRAF_NAME = "Kinderfotos Erding"
|
|
FOTOGRAF_ADRESSE = "Gartenstr. 10 85445 Oberding"
|
|
FOTOGRAF_WEB = "www.kinderfotos-erding.de"
|
|
FOTOGRAF_TEL = "08122-8470867"
|
|
|
|
# Scopes für die Google Docs API
|
|
SCOPES = ['https://www.googleapis.com/auth/documents']
|
|
SERVICE_ACCOUNT_FILE = 'service_account.json' # Ihre Service Account Schlüsseldatei
|
|
# --- Ende Konfiguration ---
|
|
|
|
def get_docs_service_with_service_account():
|
|
"""Erstellt den API-Dienst mit einem Service Account."""
|
|
creds = None
|
|
try:
|
|
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
|
|
|
|
try:
|
|
service = build('docs', 'v1', credentials=creds)
|
|
return service
|
|
except HttpError as err:
|
|
print(f"Ein Fehler beim Erstellen des Docs Service mit Service Account ist aufgetreten: {err}")
|
|
return None
|
|
except Exception as e:
|
|
print(f"Ein unerwarteter Fehler beim Erstellen des Docs Service: {e}")
|
|
return None
|
|
|
|
def create_google_doc_from_csv(service):
|
|
# ... (CSV-Verarbeitung bleibt gleich) ...
|
|
|
|
# 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")
|
|
except HttpError as err:
|
|
print(f"Fehler beim Erstellen des Google Dokuments: {err}")
|
|
return None
|
|
|
|
requests = []
|
|
current_index = 1
|
|
|
|
for i, gruppe_original in enumerate(sorted_gruppen_namen):
|
|
kinder_liste = kinder_nach_gruppen[gruppe_original]
|
|
anzahl_kinder = len(kinder_liste)
|
|
gruppe_display_name = gruppe_original + GRUPPENNAME_SUFFIX
|
|
|
|
# --- 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_for_page}})
|
|
current_index += len(header_text_for_page)
|
|
|
|
# --- Tabelle ---
|
|
# Tabelle erstellen
|
|
num_rows_for_table = len(kinder_liste) + 1
|
|
num_cols_for_table = 3
|
|
|
|
# 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_fill_start_index = current_index + 1 # Index, an dem der Text für die Zellen beginnt
|
|
|
|
# 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_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 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}")
|
|
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.")
|
|
|
|
docs_service = get_docs_service_with_service_account()
|
|
if docs_service:
|
|
document_id = create_google_doc_from_csv(docs_service)
|
|
if document_id:
|
|
print("\n--- WICHTIG ---")
|
|
print(f"Das Dokument wurde vom Servicekonto erstellt: {SERVICE_ACCOUNT_FILE}")
|
|
print(f"Sie finden es unter: https://docs.google.com/document/d/{document_id}/edit")
|
|
print("Wenn Sie das Dokument in Ihrem Haupt-Google-Drive-Konto sehen und bearbeiten möchten,")
|
|
print("müssen Sie entweder:")
|
|
print("1. Das Servicekonto (die E-Mail-Adresse des Servicekontos, steht in der JSON-Datei)")
|
|
print(" als Bearbeiter zu dem Google Drive Ordner hinzufügen, in dem das Dokument erstellt werden soll (bevor das Skript läuft, oder das Dokument verschieben).")
|
|
print("2. Oder das Skript erweitern, um das Dokument nach der Erstellung explizit mit Ihrem")
|
|
print(" Benutzerkonto zu teilen (siehe auskommentierten Code-Teil mit `drive_service.permissions().create`).")
|
|
print(" Dafür benötigt das Servicekonto auch den Scope 'https://www.googleapis.com/auth/drive'.") |