diff --git a/list_generator.py b/list_generator.py index 8c053bb9..a20db49a 100644 --- a/list_generator.py +++ b/list_generator.py @@ -1,43 +1,61 @@ import csv from datetime import datetime import collections -import os # For path handling, though not strictly needed if files are in same dir +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' -OUTPUT_FILENAME = 'Gruppenlisten_Output.txt' +GOOGLE_DOC_TITLE = f"Gruppenlisten Kinderhaus St. Martin Neuching (Service Acc) - {datetime.now().strftime('%Y-%m-%d')}" EINRICHTUNG = "Kinderhaus St. Martin Neuching" -# Bitte das Datum anpassen, falls die Annahme nicht korrekt war: -# Mögliche Optionen: -# FOTODATUM = "02.06. & 05.06.2025" # Für zwei spezifische Tage -# FOTODATUM = "02.06. + 05.06.2025" # Alternative für zwei Tage -FOTODATUM = "02. - 05.06.2025" # Für einen Zeitraum - -GRUPPENNAME_SUFFIX = "gruppe" # Wird an den Gruppennamen aus der CSV angehängt +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 generate_group_lists(): - """ - Liest die CSV-Datei, verarbeitet die Daten und generiert die gruppierten Listen. - """ +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): + """Liest CSV, verarbeitet Daten und erstellt/befüllt das Google Doc.""" + # (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) try: - # 'utf-8-sig' handhabt das BOM (Byte Order Mark), falls vorhanden with open(CSV_FILENAME, mode='r', encoding='utf-8-sig', newline='') as csvfile: - # Korrektur für das BOM-Zeichen, falls es manuell am Anfang der CSV-Daten steht - # und nicht durch utf-8-sig entfernt wird (bei String-Input nötig, bei Datei seltener) - # content = csvfile.read() - # if content.startswith('\ufeff'): - # content = content[1:] - # reader = csv.DictReader(content.splitlines(), delimiter=';') # Für String-Input - reader = csv.DictReader(csvfile, delimiter=';') for row in reader: vorname = row.get('Vorname', '').strip() @@ -47,92 +65,136 @@ def generate_group_lists(): 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 + return None except Exception as e: print(f"FEHLER beim Lesen der CSV-Datei: {e}") - return + return None if not kinder_nach_gruppen: print("Keine Daten aus der CSV-Datei geladen.") - return + return None - # Kinder innerhalb jeder Gruppe sortieren for gruppe in kinder_nach_gruppen: kinder_nach_gruppen[gruppe].sort(key=lambda x: (x['Nachname'].lower(), x['Vorname'].lower())) - - # Alphabetisch sortierte Gruppenliste für die Ausgabe + sorted_gruppen_namen = sorted(kinder_nach_gruppen.keys()) - - gesamtdokument_teile = [] stand_zeit = datetime.now().strftime("%d.%m.%Y %H:%M Uhr") - for gruppe_original in sorted_gruppen_namen: + # 1. Neues Google Doc erstellen + 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") + + # 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: + 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) - - # Gruppennamen für die Anzeige anpassen gruppe_display_name = gruppe_original + GRUPPENNAME_SUFFIX - seiteninhalt = [] - - # Header - # Für die Spaltenausrichtung in Google Docs ist es besser, Tabulatoren zu verwenden - # und dann in Docs "Text in Tabelle umwandeln" zu nutzen oder manuell zu formatieren. - seiteninhalt.append(f"{EINRICHTUNG}\t\t\t{FOTOGRAF_NAME}") - seiteninhalt.append(FOTODATUM) - seiteninhalt.append("") # Leerzeile - - # Tabellenkopf - # Die Spaltenbreiten hier sind nur für die Textausgabe gedacht. - # Feste Breiten sind besser als nur Tabs für die reine Textansicht. - seiteninhalt.append(f"{'Nachname':<25}{'Vorname':<25}{'Gruppe'}") - seiteninhalt.append("-" * (25 + 25 + len(gruppe_display_name) + 2)) # Linie unter Kopf - - # Tabellenzeilen + header_text = ( + f"{EINRICHTUNG}\t\t\t{FOTOGRAF_NAME}\n" + f"{FOTODATUM}\n\n" + ) + requests.append({'insertText': {'location': {'index': current_index}, 'text': header_text}}) + current_index += len(header_text) + + num_rows = len(kinder_liste) + 1 + num_cols = 3 + + requests.append({'insertTable': {'location': {'index': current_index}, 'rows': num_rows, 'columns': num_cols}}) + + table_content_start_index = current_index + 2 + + 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: - seiteninhalt.append(f"{kind['Nachname']:<25}{kind['Vorname']:<25}{gruppe_display_name}") + 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) - seiteninhalt.append("") # Leerzeile + current_index = table_content_start_index - # Footer - seiteninhalt.append(f"{anzahl_kinder} angemeldete Kinder") - seiteninhalt.append("") - seiteninhalt.append("Dies ist die Liste der bereits angemeldeten Kinder. Bitte die Eltern der noch fehlenden") - seiteninhalt.append("Kinder an die Anmeldung erinnern.") - seiteninhalt.append("") - seiteninhalt.append(f"Stand {stand_zeit}") - seiteninhalt.append("\n") # Eine zusätzliche Leerzeile vor Fotografen-Block - seiteninhalt.append(FOTOGRAF_NAME) - seiteninhalt.append(FOTOGRAF_ADRESSE) - seiteninhalt.append(FOTOGRAF_WEB) - seiteninhalt.append(FOTOGRAF_TEL) - - gesamtdokument_teile.append("\n".join(seiteninhalt)) + footer_text = ( + 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}, 'text': footer_text}}) + current_index += len(footer_text) - # Seiten mit Trenner zusammenfügen - final_output_text = "\n\n--- SEITENWECHSEL / PAGE BREAK ---\n\n".join(gesamtdokument_teile) + if i < len(sorted_gruppen_namen) - 1: + requests.append({'insertPageBreak': {'endOfSegmentLocation': {}}}) + # current_index += 1 # Für Page Break, aber mit endOfSegmentLocation nicht mehr so relevant für Index - try: - with open(OUTPUT_FILENAME, mode='w', encoding='utf-8') as outfile: - outfile.write(final_output_text) - print(f"Erfolg! Die Listen wurden in '{OUTPUT_FILENAME}' gespeichert.") - except Exception as e: - print(f"FEHLER beim Schreiben der Ausgabedatei: {e}") + if requests: + try: + 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}") + print("Details zum Fehler:", err.resp.status, err._get_reason()) + print("Gesendete Requests (Auszug):", requests[:5]) + return document_id -if __name__ == "__main__": - # Hier können Sie das Datum ggf. noch dynamischer setzen oder abfragen - # Beispiel: aktuelles_jahr = datetime.now().year - # FOTODATUM = f"02. - 05.06.{aktuelles_jahr}" - - # Überprüfen Sie Ihre Datumseinstellung: +if __name__ == '__main__': print(f"Info: Verwendetes Fotodatum: {FOTODATUM}") print(f"Info: Gruppennamen werden mit Suffix '{GRUPPENNAME_SUFFIX}' versehen.") - generate_group_lists() \ No newline at end of file + 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'.") \ No newline at end of file