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 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 def create_and_fill_doc(docs_service, drive_service, folder_id, doc_title):
"""
Erstellt ein Dokument (idealerweise im Ordner) und befüllt es mit CSV-Daten,
wobei Tabellen als tabulatorgetrennter Text formatiert werden.
"""
document_id = None 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: if drive_service and folder_id:
file_metadata = { file_metadata = { 'name': doc_title, 'mimeType': 'application/vnd.google-apps.document', 'parents': [folder_id] }
'name': doc_title,
'mimeType': 'application/vnd.google-apps.document',
'parents': [folder_id]
}
try: try:
created_file = drive_service.files().create(body=file_metadata, fields='id').execute() created_file = drive_service.files().create(body=file_metadata, fields='id').execute()
document_id = created_file.get('id') document_id = created_file.get('id')
print(f"Google Doc via Drive API in Ordner '{folder_id}' erstellt, ID: {document_id}") print(f"Google Doc via Drive API in Ordner '{folder_id}' erstellt, ID: {document_id}")
except HttpError as err_drive_create: except HttpError as err_drive_create:
print(f"Fehler beim Erstellen des Dokuments mit Drive API im Ordner: {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: if err_drive_create.resp.status == 403: print(" -> Fehlende Scopes oder Drive API nicht aktiv?")
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 Fallback...")
print(" Versuche, Dokument im Root des Servicekontos mit Docs API zu erstellen...")
except Exception as e_drive_create_general: except Exception as e_drive_create_general:
print(f"Allg. Fehler beim Erstellen des Dokuments mit Drive API: {e_drive_create_general}") print(f"Allg. Fehler bei Drive API Erstellung: {e_drive_create_general}\n Versuche Fallback...")
print(" Versuche, Dokument im Root des Servicekontos mit Docs API zu erstellen...")
if not document_id: if not document_id:
if not docs_service: if not docs_service: print("FEHLER: Docs API Service nicht verfügbar."); return None
print("FEHLER: Docs API Service nicht verfügbar, kann kein Dokument erstellen.")
return None
try: try:
doc_body_for_create = {'title': doc_title} doc = docs_service.documents().create(body={'title': doc_title}).execute()
doc = docs_service.documents().create(body=doc_body_for_create).execute()
document_id = doc.get('documentId') document_id = doc.get('documentId')
print(f"Google Doc via Docs API (im Root des Servicekontos) erstellt, ID: {document_id}") print(f"Google Doc via Docs API (Root des SA) erstellt, ID: {document_id}")
if folder_id: if folder_id: print(f" BITTE manuell in Ordner '{folder_id}' verschieben.")
print(f" BITTE manuell in den 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
except Exception as e_docs_create:
print(f"Konnte Dokument auch nicht mit Docs API erstellen: {e_docs_create}")
return None
if not document_id: if not document_id: print("Konnte keine Doc-ID erhalten."); return None
print("Konnte keine Dokumenten-ID für die Befüllung erhalten.")
return None
# 2. Daten aus CSV lesen (Logik bleibt gleich) # 2. Daten aus CSV lesen (bleibt gleich)
kinder_nach_gruppen = collections.defaultdict(list) kinder_nach_gruppen = collections.defaultdict(list)
try: try:
with open(CSV_FILENAME, mode='r', encoding='utf-8-sig', newline='') as csvfile: with open(CSV_FILENAME, mode='r', encoding='utf-8-sig', newline='') as csvfile:
reader = csv.DictReader(csvfile, delimiter=';') reader = csv.DictReader(csvfile, delimiter=';')
for row in reader: for row in reader:
vorname = row.get('Vorname', '').strip() # ... (CSV Leselogik wie gehabt) ...
nachname = row.get('Nachname', '').strip() vorname = row.get('Vorname', '').strip(); nachname = row.get('Nachname', '').strip(); gruppe_original = row.get('Gruppe', '').strip()
gruppe_original = row.get('Gruppe', '').strip()
if not vorname or not nachname or not gruppe_original: continue if not vorname or not nachname or not gruppe_original: continue
kinder_nach_gruppen[gruppe_original].append({'Nachname': nachname, 'Vorname': vorname}) kinder_nach_gruppen[gruppe_original].append({'Nachname': nachname, 'Vorname': vorname})
except Exception as e: except Exception as e: print(f"FEHLER beim CSV-Lesen: {e}"); return document_id
print(f"FEHLER beim Lesen der CSV-Datei für Befüllung: {e}") if not kinder_nach_gruppen: print("Keine CSV-Daten."); return document_id
return document_id for gk in kinder_nach_gruppen: kinder_nach_gruppen[gk].sort(key=lambda x: (x['Nachname'].lower(), x['Vorname'].lower()))
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()))
sorted_gruppen_namen = sorted(kinder_nach_gruppen.keys()) sorted_gruppen_namen = sorted(kinder_nach_gruppen.keys())
stand_zeit = datetime.now().strftime("%d.%m.%Y %H:%M Uhr") 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 = [] requests = []
current_doc_index = 1 # Start des Dokuments
for i, gruppe_original in enumerate(sorted_gruppen_namen): 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] 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
# Baue den gesamten Textblock für diese Seite/Gruppe # --- A. Seiten-Header ---
page_text_lines = [] 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 # --- B. Tabelle erstellen (leer) ---
page_text_lines.append(f"{EINRICHTUNG}\t\t\t{FOTOGRAF_NAME}") # Tabs für rudimentäre Ausrichtung num_rows = len(kinder_liste) + 1 # +1 für Kopfzeile
page_text_lines.append(FOTODATUM) num_cols = 3
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)
full_page_text = "\n".join(page_text_lines) + "\n" # Alle Zeilen mit Newline verbinden # Die Tabelle wird an current_doc_index eingefügt.
table_start_index = current_doc_index
# Einfügen des gesamten Textblocks für die Seite requests.append({
if i == 0: 'insertTable': {
requests.append({'insertText': {'location': {'index': 1}, 'text': full_page_text}}) 'location': {'index': table_start_index},
else: 'rows': num_rows,
# Nach einem PageBreak sollte der nächste Textblock angehängt werden 'columns': num_cols
requests.append({'insertText': {'endOfSegmentLocation': {}, 'text': full_page_text}}) }
})
# Seitenumbruch nach jeder Gruppe, außer der letzten # --- C. Tabelleninhalt einfügen ---
if i < len(sorted_gruppen_namen) - 1: # Der Inhalt der ersten Zelle beginnt direkt nach dem Einfügen der Tabelle,
requests.append({'insertPageBreak': {'endOfSegmentLocation': {}}}) # 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 requests:
if not docs_service: if not docs_service: print("FEHLER: Docs Service nicht da."); return document_id
print("FEHLER: Docs API Service nicht verfügbar, kann Dokument nicht befüllen.")
return document_id
try: try:
print(f"Sende Batch Update für Dokument ID '{document_id}' (vereinfachte Textformatierung)...") print(f"Sende Batch Update (Test mit 1. Tabelle) für Doc ID '{document_id}'...")
docs_service.documents().batchUpdate( docs_service.documents().batchUpdate(documentId=document_id, body={'requests': requests}).execute()
documentId=document_id, body={'requests': requests} print("Dokument (Test mit 1. Tabelle) erfolgreich befüllt.")
).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.")
except HttpError as err: 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." error_details = "Keine Fehlerdetails im Content."
if err.content: if err.content:
try: error_details = err.content.decode('utf-8') 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}") 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 return document_id
# --- Main execution block --- # --- Main execution block ---
@@ -207,25 +219,22 @@ if __name__ == "__main__":
print(f"Info: Gruppennamen werden mit Suffix '{GRUPPENNAME_SUFFIX}' versehen.") print(f"Info: Gruppennamen werden mit Suffix '{GRUPPENNAME_SUFFIX}' versehen.")
print(f"Info: Zieldokumente sollen in Ordner-ID '{TARGET_FOLDER_ID}' landen.") 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: if docs_api_service:
final_doc_id = create_and_fill_doc( # Definition oben im Skript final_doc_id = create_and_fill_doc(
docs_api_service, docs_api_service,
drive_api_service, drive_api_service,
TARGET_FOLDER_ID, TARGET_FOLDER_ID,
GOOGLE_DOC_TITLE GOOGLE_DOC_TITLE
) )
if final_doc_id: 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"Dokument-ID: {final_doc_id}")
print(f"Link: https://docs.google.com/document/d/{final_doc_id}/edit") print(f"Link: https://docs.google.com/document/d/{final_doc_id}/edit")
if not drive_api_service or not TARGET_FOLDER_ID: if not drive_api_service or not TARGET_FOLDER_ID:
print("Das Dokument wurde im Root-Verzeichnis des Servicekontos erstellt.") 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: else:
print("\n--- FEHLGESCHLAGEN ---") print("\n--- FEHLGESCHLAGEN (Testlauf erste Tabelle) ---")
print("Konnte Dokument nicht erstellen oder befüllen.")
else: else:
print("Konnte Google Docs API Service nicht initialisieren. Skript wird beendet.") print("Konnte Google Docs API Service nicht initialisieren. Skript wird beendet.")