This commit is contained in:
2025-05-26 19:26:38 +00:00
parent 3225898d60
commit a8e2e79f8f

View File

@@ -61,144 +61,156 @@ def get_services_with_service_account(): # Umbenannt für Klarheit
return docs_service, drive_service # Gibt beide Services zurück
def create_and_fill_doc(docs_service, drive_service, folder_id, doc_title): # Name beibehalten
"""
Erstellt ein Dokument (idealerweise im Ordner) und befüllt es mit CSV-Daten,
wobei Tabellen als tabulatorgetrennter Text formatiert werden.
"""
def create_and_fill_doc(docs_service, drive_service, folder_id, doc_title):
document_id = None
# 1. Dokument erstellen (Logik bleibt gleich)
# 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(" -> Dies deutet oft auf fehlende Scopes ('drive.file' oder 'drive') hin oder darauf, dass die Drive API nicht im Projekt aktiviert ist.")
print(" Versuche, Dokument im Root des Servicekontos mit Docs API zu erstellen...")
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 beim Erstellen des Dokuments mit Drive API: {e_drive_create_general}")
print(" Versuche, Dokument im Root des Servicekontos mit Docs API zu erstellen...")
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, kann kein Dokument erstellen.")
return None
if not docs_service: print("FEHLER: Docs API Service nicht verfügbar."); return None
try:
doc_body_for_create = {'title': doc_title}
doc = docs_service.documents().create(body=doc_body_for_create).execute()
doc = docs_service.documents().create(body={'title': doc_title}).execute()
document_id = doc.get('documentId')
print(f"Google Doc via Docs API (im Root des Servicekontos) erstellt, ID: {document_id}")
if folder_id:
print(f" BITTE manuell in den Ordner '{folder_id}' verschieben.")
except Exception as e_docs_create:
print(f"Konnte Dokument auch nicht mit Docs API erstellen: {e_docs_create}")
return None
print(f"Google Doc via Docs API (Root des 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
if not document_id:
print("Konnte keine Dokumenten-ID für die Befüllung erhalten.")
return None
if not document_id: print("Konnte keine Doc-ID erhalten."); return None
# 2. Daten aus CSV lesen (Logik bleibt gleich)
# 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:
vorname = row.get('Vorname', '').strip()
nachname = row.get('Nachname', '').strip()
gruppe_original = row.get('Gruppe', '').strip()
# ... (CSV Leselogik wie gehabt) ...
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 Lesen der CSV-Datei für Befüllung: {e}")
return document_id
if not kinder_nach_gruppen:
print("Keine Daten aus der CSV-Datei für Befüllung geladen.")
return document_id
for gruppe_key in kinder_nach_gruppen:
kinder_nach_gruppen[gruppe_key].sort(key=lambda x: (x['Nachname'].lower(), x['Vorname'].lower()))
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
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 - JETZT MIT VEREINFACHTER TEXTFORMATIERUNG
# 3. Dokument befüllen - MIT ECHTEN TABELLEN (Fokus auf Seite 1)
requests = []
current_doc_index = 1 # Start des Dokuments
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
# Baue den gesamten Textblock für diese Seite/Gruppe
page_text_lines = []
# --- 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)
# Seiten-Header
page_text_lines.append(f"{EINRICHTUNG}\t\t\t{FOTOGRAF_NAME}") # Tabs für rudimentäre Ausrichtung
page_text_lines.append(FOTODATUM)
page_text_lines.append("") # Leerzeile
# "Tabellen"-Daten als Text
page_text_lines.append("Nachname\tVorname\tGruppe") # Kopfzeile mit Tabs
for kind in kinder_liste:
page_text_lines.append(f"{kind['Nachname']}\t{kind['Vorname']}\t{gruppe_display_name}")
page_text_lines.append("") # Leerzeile nach der "Tabelle"
# Footer-Text
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)
# --- B. Tabelle erstellen (leer) ---
num_rows = len(kinder_liste) + 1 # +1 für Kopfzeile
num_cols = 3
full_page_text = "\n".join(page_text_lines) + "\n" # Alle Zeilen mit Newline verbinden
# Einfügen des gesamten Textblocks für die Seite
if i == 0:
requests.append({'insertText': {'location': {'index': 1}, 'text': full_page_text}})
else:
# Nach einem PageBreak sollte der nächste Textblock angehängt werden
requests.append({'insertText': {'endOfSegmentLocation': {}, 'text': full_page_text}})
# Die Tabelle wird an current_doc_index eingefügt.
table_start_index = current_doc_index
requests.append({
'insertTable': {
'location': {'index': table_start_index},
'rows': num_rows,
'columns': num_cols
}
})
# Seitenumbruch nach jeder Gruppe, außer der letzten
if i < len(sorted_gruppen_namen) - 1:
requests.append({'insertPageBreak': {'endOfSegmentLocation': {}}})
# --- C. Tabelleninhalt einfügen ---
# Der Inhalt der ersten Zelle beginnt direkt nach dem Einfügen der Tabelle,
# plus einem Offset für die Tabellenstruktur-Tags.
# Ein häufig funktionierender Offset ist +2 (für TableStart, RowStart).
# Wir nehmen an, dass der `current_doc_index` jetzt auf den Beginn der Tabelle zeigt.
# Der Inhalt der ersten Zelle beginnt bei `table_start_index + 2` (relativ zum Dokumentenanfang)
# oder, anders gesagt, Index 1 *innerhalb* der Tabellenstruktur, die bei table_start_index beginnt.
#
# Die Google Docs API ist so gedacht, dass wenn man Text mit Tabs/Newlines an den
# Startindex einer Zelle sendet, sie den Text in die Tabelle verteilt.
# Batch-Update ausführen
table_content_start_index = table_start_index + 2 # Annahme: Index der ersten Zelle (Beginn des Inhaltsbereichs)
# Erstelle den gesamten Text für die Tabelle
table_lines = []
table_lines.append("Nachname\tVorname\tGruppe") # Kopfzeile
for kind in kinder_liste:
table_lines.append(f"{kind['Nachname']}\t{kind['Vorname']}\t{gruppe_display_name}")
full_table_text_for_cells = "\n".join(table_lines) + "\n" # Wichtig: endet mit \n
requests.append({
'insertText': {
'location': {'index': table_content_start_index},
'text': full_table_text_for_cells
}
})
# --- 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 API Service nicht verfügbar, kann Dokument nicht befüllen.")
return document_id
if not docs_service: print("FEHLER: Docs Service nicht da."); return document_id
try:
print(f"Sende Batch Update für Dokument ID '{document_id}' (vereinfachte Textformatierung)...")
docs_service.documents().batchUpdate(
documentId=document_id, body={'requests': requests}
).execute()
print("Dokument erfolgreich befüllt (mit tabulatorgetrenntem Text).")
print("Sie können den Text in Google Docs markieren und über 'Einfügen' > 'Tabelle' > 'Tabelle aus Text erstellen' umwandeln.")
print(f"Sende Batch Update (Test mit 1. Tabelle) 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.")
except HttpError as err:
print(f"Fehler beim Befüllen des Dokuments ID '{document_id}': {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 # Ignoriere Dekodierungsfehler
except: pass
print(f"Details zum Fehler ({err.resp.status} {err._get_reason()}): {error_details}")
return document_id
# 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 ---")
return document_id
# --- Main execution block ---
@@ -207,25 +219,22 @@ if __name__ == "__main__":
print(f"Info: Gruppennamen werden mit Suffix '{GRUPPENNAME_SUFFIX}' versehen.")
print(f"Info: Zieldokumente sollen in Ordner-ID '{TARGET_FOLDER_ID}' landen.")
docs_api_service, drive_api_service = get_services_with_service_account() # Definition oben im Skript
docs_api_service, drive_api_service = get_services_with_service_account()
if docs_api_service:
final_doc_id = create_and_fill_doc( # Definition oben im Skript
if docs_api_service:
final_doc_id = create_and_fill_doc(
docs_api_service,
drive_api_service,
TARGET_FOLDER_ID,
GOOGLE_DOC_TITLE
)
if final_doc_id:
print(f"\n--- SKRIPT BEENDET ---")
print(f"\n--- SKRIPT BEENDET (Testlauf erste Tabelle) ---")
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.")
print("HINWEIS: Die 'Tabellen' wurden als tabulatorgetrennter Text eingefügt.")
print(" Öffnen Sie das Dokument und formatieren Sie den Text bei Bedarf als Tabelle.")
else:
print("\n--- FEHLGESCHLAGEN ---")
print("Konnte Dokument nicht erstellen oder befüllen.")
print("\n--- FEHLGESCHLAGEN (Testlauf erste Tabelle) ---")
else:
print("Konnte Google Docs API Service nicht initialisieren. Skript wird beendet.")