"""Migration 2026-05-25: Software-Karten initial befuellen. Etappe 3 — schlankes Karten-Konzept (siehe docs/decisions.md, neue Entscheidung zur Software-Sektion). Wir halten uns raus: SuS haben die Software auf den Schul-PCs / im Browser und machen eigene Erfahrungen. Wir liefern nur die Auswahl und ein paar Tutorials. Inhalte pro Karte: 1-2 Satz Beschreibung, Hauptlink, optional Tutorial-Link. Apps zusaetzlich App-Store + Play-Store-URLs (QR-Codes folgen in Folge-Schritt). Idempotent: INSERT OR REPLACE auf slug. Achtung: ueberschreibt Inline-Edits des Lehrers. Nur fuer Initial-Seed gedacht. """ import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) from database import get_db, init_db SOFTWARE = [ # ---------- Slicer (Pflicht) ---------- { "slug": "bambu-studio", "titel": "Bambu Studio", "kurzbeschreibung": "Slicer fuer unsere Drucker. Modell oeffnen, Drucker waehlen, drucken.", "url": "https://bambulab.com/de/download/studio", "tutorial_url": "https://wiki.bambulab.com/en/software/bambu-studio/studio-quick-start", "plattform": "Windows / macOS / Linux", "kategorie": "software", "sortierung": 10, }, # ---------- App ---------- { "slug": "bambu-handy", "titel": "Bambu Handy", "kurzbeschreibung": "Drucker live am Handy beobachten, pausieren, Kamera ansehen.", "url": "https://bambulab.com/de/download", "tutorial_url": None, "plattform": "iOS + Android", "app_store_url": "https://apps.apple.com/de/app/bambu-handy/id1620054970", "play_store_url": "https://play.google.com/store/apps/details?id=com.bambulab.bambu_link", "kategorie": "app", "sortierung": 20, }, # ---------- Modell-Plattformen ---------- { "slug": "printables", "titel": "Printables", "kurzbeschreibung": "Modell-Plattform von Prusa. Werbefrei, deutsche UI — erste Adresse fuer fertige Modelle.", "url": "https://www.printables.com", "tutorial_url": "https://www.printables.com/education", "plattform": "Browser", "kategorie": "software", "sortierung": 30, }, { "slug": "makerworld", "titel": "MakerWorld", "kurzbeschreibung": "Bambu-eigene Modell-Plattform mit Customizer — Modelle mit eigenem Namen oder Wunschmassen.", "url": "https://makerworld.com", "tutorial_url": "https://makerworld.com/en/models/1320280", # Parametric Name Keychain als Erst-Customizer "plattform": "Browser (Konto noetig)", "kategorie": "software", "sortierung": 40, }, # ---------- CAD ---------- { "slug": "tinkercad", "titel": "TinkerCAD", "kurzbeschreibung": "Visuelles 3D-CAD im Browser. Grundkoerper ziehen, kombinieren, exportieren. Einstieg ins eigene Modellieren.", "url": "https://www.tinkercad.com", "tutorial_url": "https://www.tinkercad.com/learn", "plattform": "Browser", "kategorie": "software", "sortierung": 50, }, # OpenSCAD bewusst nicht aufgenommen — solange wir keine Lektion dafuer # haben, wuerden wir's nur als Karte "in den Raum stellen". Wenn spaeter # eine OpenSCAD-Lektion entsteht (Bruecke zur Informatik), kann die Karte # via Admin-GUI oder Folge-Migration nachgezogen werden. { "slug": "onshape", "titel": "Onshape", "kurzbeschreibung": "Professionelles CAD im Browser. Was Profis nutzen — fuer Schulen kostenlos, ab 13 Jahren.", "url": "https://www.onshape.com/de/education/", "tutorial_url": "https://www.youtube.com/watch?v=VyVqG8ZPvaM", # stolz3D Anfaenger Grundkurs "plattform": "Browser + iOS + Android", "kategorie": "software", "sortierung": 70, }, # ---------- Online-Generatoren ---------- { "slug": "gridfinity", "titel": "Gridfinity Builder", "kurzbeschreibung": "Online-Generator fuer Gridfinity-Boxen — modulare Werkstatt-Ordnungssysteme nach Mass.", "url": "https://gridfinity.perplexinglabs.com/pr/gridfinity-rebuilt/0/1", "tutorial_url": None, "plattform": "Browser", "kategorie": "generator", "sortierung": 80, }, ] # Felder, die als Spalten gepflegt werden (alle anderen Keys werden ignoriert) SPALTEN = ( "slug", "titel", "kurzbeschreibung", "url", "tutorial_url", "plattform", "app_store_url", "play_store_url", "bild_pfad", "kategorie", "sortierung", ) def main(): init_db() conn = get_db() angelegt = 0 aktualisiert = 0 for s in SOFTWARE: # Default-Werte fuer optionale Felder, damit das UPDATE/INSERT einheitlich ist werte = {sp: s.get(sp) for sp in SPALTEN} existiert = conn.execute( "SELECT 1 FROM software_seite WHERE slug = ?", (werte["slug"],) ).fetchone() if existiert: conn.execute( """UPDATE software_seite SET titel = :titel, kurzbeschreibung = :kurzbeschreibung, url = :url, tutorial_url = :tutorial_url, plattform = :plattform, app_store_url = :app_store_url, play_store_url = :play_store_url, bild_pfad = :bild_pfad, kategorie = :kategorie, sortierung = :sortierung, aktiv = 1 WHERE slug = :slug""", werte ) aktualisiert += 1 else: conn.execute( """INSERT INTO software_seite (slug, titel, kurzbeschreibung, url, tutorial_url, plattform, app_store_url, play_store_url, bild_pfad, kategorie, sortierung, aktiv) VALUES (:slug, :titel, :kurzbeschreibung, :url, :tutorial_url, :plattform, :app_store_url, :play_store_url, :bild_pfad, :kategorie, :sortierung, 1)""", werte ) angelegt += 1 conn.commit() conn.close() print(f"[migration] software_seite: {angelegt} angelegt, {aktualisiert} aktualisiert " f"({len(SOFTWARE)} Karten total).") if __name__ == "__main__": main()