bugfix
This commit is contained in:
@@ -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()
|
||||
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'.")
|
||||
Reference in New Issue
Block a user