Warum Seeding in Django nervt
Jedes Django-Projekt läuft gegen dieselbe Wand, sobald das Schema über eine Handvoll Models hinauswächst. Alle üblichen Wege für Testdaten haben einen Haken:
- JSON/YAML-Fixtures +
loaddata. Sie veralten. Ein neues Non-Null-Feld und jede Fixture bricht. Außerdem sind sie naturgemäß winzig, niemand tippt 5.000 Zeilen von Hand. factory_boy-Factories. Stark für den Objekt-Aufbau innerhalb eines Tests, aber jede Beziehung verdrahtest du selbst, und sie sind nicht dafür gedacht, eine ganze Dev- oder Staging-Datenbank zu füllen.- Ein eigener
manage.py seed-Befehl. Am Ende läufst du den Abhängigkeitsgraph selbst ab, und er bricht, sobald sich ein Model ändert.
Was du eigentlich willst: etwas auf die bereits vorhandenen Models zeigen und eine befüllte Datenbank bekommen, in der jeder Fremdschlüssel aufgeht. Hier der ganze Ablauf.
Schritt 1, auf deine models.py zeigen
SeedBase liest deine Django-Models direkt. Füge deine models.py ein oder pushe das ganze Projekt mit einem Klick aus dem PyCharm- oder VS-Code-Plugin. Nimm einen typischen Shop:
class Customer(models.Model):
name = models.CharField(max_length=120)
email = models.EmailField(unique=True)
city = models.CharField(max_length=80)
class Product(models.Model):
sku = models.CharField(max_length=32, unique=True)
name = models.CharField(max_length=200)
price = models.DecimalField(max_digits=10, decimal_places=2)
class Order(models.Model):
STATUS = [("pending", "Pending"), ("paid", "Paid"), ("shipped", "Shipped")]
customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
status = models.CharField(max_length=16, choices=STATUS)
created_at = models.DateTimeField(auto_now_add=True)
class OrderItem(models.Model):
order = models.ForeignKey(Order, related_name="items", on_delete=models.CASCADE)
product = models.ForeignKey(Product, on_delete=models.PROTECT)
quantity = models.PositiveIntegerField()
Der Parser versteht Django, nicht nur generisches SQL:
- Das implizite
id. Kein expliziter Primärschlüssel? Das automatischeAutoFieldidwird ergänzt, genau wie Django es täte. - Abstrakte Basisklassen und Mixins. Felder einer abstrakten Basis fließen in jedes konkrete Kind, ein
TimeStampedModel-Mixin wirkt also überall richtig. - Jeder Beziehungstyp.
ForeignKey,OneToOneFieldundManyToManyField(inklusive der impliziten Through-Tabelle) werden zu echten, auflösbaren Referenzen.ForeignKey("self")wird ein Baum, keine Vorwärtsreferenz. choices. Ein Feld mit choices bekommt immer nur einen seiner echten Werte (pending / paid / shipped), nie einen Zufallsstring.
Schritt 2, generieren
Auf Generieren klicken. Der Teil, der Django-Daten überhaupt ladbar macht, passiert hier:
- Topologische Insert-Reihenfolge.
CustomerundProductvorOrder,OrdervorOrderItem. KeinIntegrityErrorbeim Laden. - Fremdschlüssel gehen auf. Jedes
order.customer_idzeigt auf einen wirklich erzeugten Kunden, jedesOrderItem.product_idauf ein echtes Produkt. - Through-Tabellen werden befüllt. n:m-Verknüpfungen referenzieren auf beiden Seiten existierende Zeilen, auch die Join-Tabelle ist konsistent.
Schritt 3, realistisch, nicht nur gültig
Gültige Daten, bei denen jede Zeile gleich aussieht, verstecken trotzdem Bugs. Ein paar Dinge, die sie sich wie Produktion verhalten lassen:
- Schiefe Verteilungen. Nicht jeder Kunde hat genau fünf Bestellungen. Ein langer Schwanz (einer hat zwei, ein anderer neunzehn) ist genau dort, wo Pagination und N+1-Queries wirklich brechen.
- Kohärente Werte. Die E-Mail eines Kunden passt zum Namen (
lena.mueller@…für Lena Müller), undorder.totalentspricht der Summe seinerOrderItem-Zeilen. Abgeleitete Werte werden nach der Generierung rekonziliert. - Lokalisiert und kohärent. Sprache wählen, und Namen, Städte, PLZ und Land passen zusammen (Zürich gehört zu CH, nicht US). Praktisch, wenn deine Daten in einem Demo landen.
- Zeitbewusste Timestamps.
auto_now_addbleibt vorauto_now, und Timestamps entstehen relativ zu heute, damit "letzte 30 Tage"-Admin-Filter befüllt bleiben, während der Datensatz altert.
Schritt 4, in deine Datenbank laden
Als SQL exportieren und direkt in die Projekt-Datenbank pipen, mit aktivierten Constraints, weil die Inserts schon in FK-Reihenfolge stehen:
# direkt in die in Django konfigurierte Datenbank
python manage.py dbshell < seed.sql
# oder klassisch psql
psql "$DATABASE_URL" -f seed.sql
Oder die Datei überspringen und per CLI ziehen, praktisch in einem Makefile oder beim frischen Dev-Setup:
pip install seedbase
seedbase pull --project <id> --format sql --out seed.sql
Du kannst auch in der UI eine Datenbank verbinden und die Zeilen direkt pushen, ganz ohne SQL-Datei.
Schritt 5, in CI reproduzierbar machen
Die Generierung ist pro Seed deterministisch, ein CI-Lauf bekommt also jedes Mal exakt dieselbe Datenbank. Das Python-SDK und das pytest-Plugin ziehen geseedete Daten direkt in deine Test-Datenbank, und du kannst die Generierungs-Config als JSON neben deinen Migrations committen, damit das ganze Team denselben Datensatz aus denselben Models neu erzeugt.
factory_boy weiterhin das richtige Werkzeug. Sie lösen verschiedene Probleme und ergänzen sich gut.
Das ist der ganze Loop
models.py rein, generieren, laden. Die harten Teile (Insert-Reihenfolge, jeden Fremdschlüssel auflösen, abstrakte Basen, Through-Tabellen, realistische Schiefe) sind erledigt, du schreibst keinen Seed-Befehl mehr von Hand. Getestet an einem echten Django-Projekt mit 20 Apps und 226 Models, daher kommen die meisten Edge-Cases. Derselbe Ansatz funktioniert für jedes Schema, SQL und Prisma inklusive.
Deine Django-Datenbank befüllen, kostenlos
Füge deine models.py ein oder pushe sie aus deiner IDE, generiere eine befüllte, FK-konsistente Datenbank und lade sie mit manage.py dbshell. Free-Tier, keine Kreditkarte.
- Jeder FK geht auf
- Realistische Verteilungen
- SQL / CSV / JSON
- EU-gehostet
Mehr: Testdaten generieren · vs Faker · DSGVO-Anonymisierung