Files
Brancheneinstufung2/list_generator.py
Floke ea3ee863d0 feat: Integrate gdoctableapppy for table creation and header styling
Replaces plain text table generation with gdoctableapppy library
to create true Docs tables. Attempts to bold header row.
Retains manual copy for main doc header/footer.
2025-05-27 05:16:08 +00:00

236 lines
13 KiB
Python

import csv
from datetime import datetime
import collections
import os.path
from google.oauth2 import service_account
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
# Importiere die neue Bibliothek
from gdoctableapppy import DocsTableApp # Hauptklasse
from gdoctableapppy import Utility # Für endOfSegmentLocation, falls benötigt
# --- Konfiguration ---
CSV_FILENAME = 'Namensliste.csv'
GOOGLE_DOC_TITLE = f"Gruppenlisten_{datetime.now().strftime('%Y-%m-%d_%H-%M')}"
TARGET_FOLDER_ID = "18DNQaH9zbcBzwhckJI-4Uah-WXTXg6bg"
# Diese werden nur für den einmaligen Infoblock verwendet
EINRICHTUNG_INFO = "Kinderhaus St. Martin Neuching"
FOTODATUM_INFO = "02. - 05.06.2025"
GRUPPENNAME_SUFFIX = "gruppe"
SCOPES = [
'https://www.googleapis.com/auth/documents',
'https://www.googleapis.com/auth/drive.file'
]
SERVICE_ACCOUNT_FILE = 'service_account.json'
# --- Ende Konfiguration ---
def get_services_with_service_account():
creds = None; docs_service = None; drive_service = None
try: creds = service_account.Credentials.from_service_account_file(SERVICE_ACCOUNT_FILE, scopes=SCOPES)
except Exception as e: print(f"Fehler Credentials: {e}"); return None, None
try: docs_service = build('docs', 'v1', credentials=creds); print("Docs API Service erstellt.")
except Exception as e: print(f"Fehler Docs Service: {e}")
try:
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("Drive API Service erstellt.")
else: print("WARNUNG: Kein Drive Scope für Drive Service.")
except Exception as e: print(f"Fehler Drive Service: {e}")
return docs_service, drive_service
def generate_tables_with_gdoctableapp(docs_service, document_id_to_fill):
"""
Befüllt ein existierendes Dokument mit echten Tabellen pro Gruppe
unter Verwendung der gdoctableapppy Bibliothek.
"""
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=';')
# ... (CSV Leselogik wie gehabt) ...
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: continue
kinder_nach_gruppen[gruppe_original].append({'Nachname': nachname, 'Vorname': vorname})
except Exception as e: print(f"FEHLER CSV-Lesen: {e}"); return False
if not kinder_nach_gruppen: print("Keine CSV-Daten."); return False
for gk_key in kinder_nach_gruppen:
kinder_nach_gruppen[gk_key].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")
# Initialisiere DocsTableApp
# Die Bibliothek erwartet 'creds' oder 'service'. Da wir 'service' haben:
try:
dta = DocsTableApp(service=docs_service)
util = Utility() # Für endOfSegmentLocation, falls wir es für Text außerhalb brauchen
except Exception as e_dta_init:
print(f"Fehler bei der Initialisierung von DocsTableApp: {e_dta_init}")
return False
# Liste für Requests, die nicht von DocsTableApp direkt gehandhabt werden (z.B. reiner Text, PageBreaks)
additional_requests = []
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
# 1. Daten für die Tabelle vorbereiten (Liste von Listen)
table_values = []
table_values.append(["Nachname", "Vorname", "Gruppe"]) # Kopfzeile
for kind in kinder_liste:
table_values.append([kind['Nachname'], kind['Vorname'], gruppe_display_name])
# 2. Tabelle mit gdoctableapppy einfügen
# Wir müssen den Einfügeindex bestimmen.
# Für die erste Tabelle (i==0) nach der initialen Info (die in main eingefügt wurde),
# verwenden wir endOfSegmentLocation. Für weitere Tabellen auch.
current_insert_location = util.get_end_of_segment_location()
try:
print(f"Versuche Tabelle für Gruppe '{gruppe_original}' mit gdoctableapppy einzufügen...")
# Definiere Stil für die Kopfzeile (fett)
# Die Syntax hier ist eine Annahme basierend auf typischen Bibliotheksmustern.
# Ggf. muss dies an die genaue Implementierung von gdoctableapppy angepasst werden.
# Die Bibliothek könnte eine Methode wie `set_range_style` oder `format_cells` haben.
# Tanaikes Bibliotheken sind oft sehr flexibel.
# `add_table` sendet das BatchUpdate direkt oder gibt Requests zurück.
# Laut Doku: "This method uses Docs API. So when this method is used, the table is directly created to Google Document."
# Das bedeutet, wir müssen die Requests nicht sammeln und selbst senden für die Tabelle.
# Index für add_table: Wenn None, wird es ans Ende angehängt.
# Da wir die einmalige Info in main eingefügt haben, sollte None hier passen.
# Und für Folgetabellen wird es auch ans Ende (nach dem PageBreak) angehängt.
# Einfügen der Tabelle
res_table = dta.add_table(document_id_to_fill, values=table_values, index=None) # index=None hängt ans Ende an
# Nun versuchen, die Kopfzeile fett zu machen.
# Wir brauchen eine Referenz auf die Tabelle oder ihre Position, um die Kopfzeile zu formatieren.
# `add_table` gibt laut Doku die `startIndex` der Tabelle zurück.
# { "table_object": created_table_object, "startIndex": table_start_index, "endIndex": table_end_index }
# Die Formatierung ist komplexer, da sie Zellbereiche benötigt.
# Die Bibliothek hat `update_text_style_to_range`
# update_text_style_to_range(document_id, text_style, fields, text_range=None, table_range=None)
# table_range = {"table_start_index": tableStartIndex, "row_index": 0, "column_index": 0, "row_span": 1, "column_span": 3} (Beispiel)
if res_table and "startIndex" in res_table:
table_start_idx = res_table["startIndex"]
header_range = {
"table_start_index": table_start_idx,
"row_index": 0, # Erste Zeile (Kopfzeile)
"column_index": 0, # Erste Spalte
"row_span": 1, # Nur eine Zeile
"column_span": 3 # Über alle 3 Spalten der Kopfzeile
}
text_style_bold = {"bold": True}
fields_bold = "bold"
dta.update_text_style_to_range(document_id_to_fill, text_style_bold, fields_bold, table_range=header_range)
print(f" Kopfzeile für Tabelle '{gruppe_original}' versucht fett zu formatieren.")
else:
print(f" Konnte startIndex für Tabelle '{gruppe_original}' nicht erhalten, Kopfzeile nicht formatiert.")
# 3. Text unter der Tabelle einfügen (Anzahl Kinder, etc.)
# Dies muss als separater Textblock NACH der Tabelle eingefügt werden.
# Wir verwenden dafür `additional_requests` und `endOfSegmentLocation`.
footer_lines_for_group = []
footer_lines_for_group.append("") # Leerzeile nach der Tabelle
footer_lines_for_group.append(f"{anzahl_kinder} angemeldete Kinder")
footer_lines_for_group.append("")
footer_lines_for_group.append("Dies ist die Liste der bereits angemeldeten Kinder. Bitte die Eltern der noch fehlenden")
footer_lines_for_group.append("Kinder an die Anmeldung erinnern.")
footer_lines_for_group.append("")
footer_lines_for_group.append(f"Stand {stand_zeit}")
footer_lines_for_group.append("")
full_footer_text_for_group = "\n".join(footer_lines_for_group) + "\n"
additional_requests.append({'insertText': {'endOfSegmentLocation': {}, 'text': full_footer_text_for_group}})
except Exception as e_table:
print(f"Fehler beim Erstellen/Formatieren der Tabelle für Gruppe '{gruppe_original}' mit gdoctableapppy: {e_table}")
# Fallback: Füge die Daten als einfachen Text ein, wenn die Bibliothek fehlschlägt
fallback_text_lines = ["FEHLER BEI TABELLENERSTELLUNG FÜR DIESE GRUPPE"]
kopf_nachname = "Nachname".ljust(25); kopf_vorname = "Vorname".ljust(25)
fallback_text_lines.append(f"{kopf_nachname}\t{kopf_vorname}\tGruppe")
for r in table_values[1:]: fallback_text_lines.append(f"{r[0].ljust(25)}\t{r[1].ljust(25)}\t{r[2]}")
additional_requests.append({'insertText': {'endOfSegmentLocation': {}, 'text': "\n".join(fallback_text_lines) + "\n\n"}})
# 4. Seitenumbruch (wenn nicht die letzte Gruppe)
if i < len(sorted_gruppen_namen) - 1:
additional_requests.append({'insertPageBreak': {'endOfSegmentLocation': {}}})
# Führe die zusätzlichen Requests aus (für Text unter Tabellen und Seitenumbrüche)
if additional_requests:
try:
print(f"Sende zusätzliche Requests (Footer-Texte, PageBreaks) für Doc ID '{document_id_to_fill}'...")
docs_service.documents().batchUpdate(documentId=document_id_to_fill, body={'requests': additional_requests}).execute()
print("Zusätzliche Requests erfolgreich ausgeführt.")
except HttpError as err:
print(f"Fehler beim Ausführen der zusätzlichen Requests für Doc ID '{document_id_to_fill}': {err}")
# ... (Fehlerdetails) ...
return False # Befüllen war nicht vollständig erfolgreich
return True
# --- Main execution block ---
if __name__ == "__main__":
print(f"Info: Ordner-ID für Dokument: '{TARGET_FOLDER_ID}'")
docs_api_service, drive_api_service = get_services_with_service_account()
if not docs_api_service:
print("Konnte Google Docs API Service nicht initialisieren. Skript wird beendet.")
else:
document_id = None
# 1. Dokument erstellen
if drive_api_service and TARGET_FOLDER_ID:
file_metadata = {'name': GOOGLE_DOC_TITLE, 'mimeType': 'application/vnd.google-apps.document', 'parents': [TARGET_FOLDER_ID]}
try:
created_file = drive_api_service.files().create(body=file_metadata, fields='id').execute()
document_id = created_file.get('id')
print(f"Neues leeres Doc via Drive API in Ordner '{TARGET_FOLDER_ID}' erstellt, ID: {document_id}")
except Exception as e: print(f"Fehler Drive API Erstellung: {e}\n Versuche Fallback...")
if not document_id: # Fallback
try:
doc = docs_api_service.documents().create(body={'title': GOOGLE_DOC_TITLE}).execute()
document_id = doc.get('documentId')
print(f"Neues leeres Doc via Docs API (Root SA) erstellt, ID: {document_id}")
if TARGET_FOLDER_ID: print(f" BITTE manuell in Ordner '{TARGET_FOLDER_ID}' verschieben.")
except Exception as e: print(f"Konnte leeres Doc auch nicht mit Docs API erstellen: {e}")
if document_id:
# 2. Einmalige Info (Kita, Datum) GANZ AM ANFANG des Dokuments einfügen
initial_info_lines = [
"Info zum Kopieren für Ihre manuelle Kopfzeile:",
EINRICHTUNG_INFO, FOTODATUM_INFO,
"\n" + "="*70 + "\n"
]
initial_text = "\n".join(initial_info_lines) + "\n"
initial_requests = [{'insertText': {'location': {'index': 1}, 'text': initial_text}}]
try:
docs_api_service.documents().batchUpdate(documentId=document_id, body={'requests': initial_requests}).execute()
print("Einmalige Info am Dokumentanfang eingefügt.")
except HttpError as err: print(f"Fehler beim Einfügen der einmaligen Info: {err}")
# 3. Dokument mit gruppenspezifischen Daten (echte Tabellen) befüllen
success_filling = generate_tables_with_gdoctableapp(docs_api_service, document_id)
if success_filling:
print(f"\n--- SKRIPT BEENDET ---")
print(f"Dokument-ID: {document_id}")
print(f"Link: https://docs.google.com/document/d/{document_id}/edit")
print("HINWEIS: Es wurde versucht, echte Tabellen mit fetter Kopfzeile zu erstellen.")
else:
print("\n--- FEHLER BEIM BEFÜLLEN DES DOKUMENTS MIT TABELLEN ---")
else:
print("\n--- FEHLGESCHLAGEN: Konnte kein Dokument erstellen ---")