advanced-fk-seeding

star 0

Advanced seeding patterns for linking specific foreign keys

stratusadv By stratusadv schedule Updated 5/20/2026

name: advanced-fk-seeding description: Advanced seeding patterns for linking specific foreign keys

Advanced Foreign Key Seeding

Overview

This skill covers the pattern for seeding models that require specific, deterministic foreign key relationships — where you need to control exactly which records link to which, rather than relying on random or cached FK assignment.

This pattern resolves cache conflicts that occur when using in_order with cache_seed=True, enabling clean re-seeding without turning off the cache.

The Problem

When seeding related models together (e.g., bins + locations, fields + lots), the old approach used in_order via field overrides:

# OLD PATTERN - causes cache conflicts
def seed_with_location(cls, count: int = 5):
    # Step 1: Seed locations
    locations = LocationSeeder.seed_database(
        count=count,
        fields={'type': LocationTypeChoices.BIN, ...}
    )

    # Step 2: Seed bins with in_order FK (problem here)
    location_ids = [loc.id for loc in locations]

    return cls.seed_database(
        count=count,
        fields={
            'location_id': ('custom', 'in_order', {'values': location_ids})
        }
    )

Why It Fails

  1. seed_database() with cache_seed=True caches the generated objects including IDs
  2. Field overrides on subsequent runs collide with cached values
  3. in_order gets cached with the original values, not the new ones
  4. Result: Foreign keys link to old records, causing unique constraint violations or broken relationships

Symptoms

  • UniqueViolation errors on re-runs
  • Foreign keys pointing to wrong/missing records
  • Having to set cache_seed=False to work around the issue

The Solution

Use seed() to generate objects without database insertion, then manually manage IDs through bulk_create():

from __future__ import annotations

from app.location import models as location_models
from app.location.bin import models
from app.location.choices import LocationTypeChoices
from app.location.seeding.seeder import LocationSeeder


class BinSeeder(DjangoModelSeeder):
    model_class = models.Bin
    cache_seed = True

    @classmethod
    def with_rows(cls, count: int = 5, rows_per_bin: int = 4):
        # 1. Create bin location objects with field overrides
        bin_locations = LocationSeeder.seed(
            count=count,
            fields={
                'type': LocationTypeChoices.BIN,
                'name': ('llm', 'Generate a name for a storage bin...'),
            },
        )
        # 2. Bulk create locations and capture new IDs
        location_models.Location.objects.bulk_create(bin_locations)
        bin_location_ids = [loc.id for loc in bin_locations]

        # 3. Create bin objects, link to locations, bulk create
        bins = cls.seed(count=count)
        for bin, loc_id in zip(bins, bin_location_ids):
            bin.location_id = loc_id
        models.Bin.objects.bulk_create(bins)

        # 4. Create row child locations linked to bins
        total_rows = count * rows_per_bin
        row_names = [f'Row {i}' for i in range(1, total_rows + 1)]
        bin_ids_expanded = [bin_id for bin_id in bin_location_ids for _ in range(rows_per_bin)]

        rows = LocationSeeder.seed(
            count=total_rows,
            fields={
                'type': LocationTypeChoices.ROW,
            },
        )
        for row, parent_id, name in zip(rows, bin_ids_expanded, row_names):
            row.parent_id = parent_id
            row.name = name
        location_models.Location.objects.bulk_create(rows)

        return bins

Core Pattern

Step-by-Step

  1. Call Seeder.seed() with field overrides — generates objects with pk=None
  2. Call Model.objects.bulk_create() — inserts records and assigns real DB IDs
  3. Capture IDs from bulk create result[obj.id for obj in objects]
  4. Call child seeder's seed() — generates child objects
  5. Zip parent IDs with childrenfor child, parent_id in zip(children, parent_ids)
  6. Set FK fields directlychild.parent_id = parent_id
  7. Bulk create children

Key Points

  • seed() returns objects with pk=None by default
  • bulk_create() assigns real IDs after insert
  • Setting FK fields directly bypasses the cache conflict
  • Works with cache_seed=True on all seeders

Examples

Simple Link (No Children)

For models like Warehouse that link directly to locations:

@classmethod
def with_locations(cls, count: int = 5):
    warehouse_locations = LocationSeeder.seed(
        count=count,
        fields={
            'type': LocationTypeChoices.WAREHOUSE,
            'name': ('llm', 'Generate a name for a warehouse...'),
        },
    )
    location_models.Location.objects.bulk_create(warehouse_locations)
    warehouse_location_ids = [loc.id for loc in warehouse_locations]

    warehouses = cls.seed(count=count)
    for warehouse, loc_id in zip(warehouses, warehouse_location_ids):
        warehouse.location_id = loc_id
    models.Warehouse.objects.bulk_create(warehouses)

    return warehouses

With Children (Parent + Multiple Children Per Parent)

For models like Bin that have multiple row children per bin:

@classmethod
def with_rows(cls, count: int = 5, rows_per_bin: int = 4):
    # ... create and save parent locations ...

    # Create rows with expanded parent IDs
    total_rows = count * rows_per_bin
    row_names = [f'Row {i}' for i in range(1, total_rows + 1)]
    bin_ids_expanded = [bin_id for bin_id in bin_location_ids for _ in range(rows_per_bin)]

    rows = LocationSeeder.seed(count=total_rows)
    for row, parent_id, name in zip(rows, bin_ids_expanded, row_names):
        row.parent_id = parent_id
        row.name = name
    location_models.Location.objects.bulk_create(rows)

Setting Type in Loop (When Field Override Doesn't Apply)

Some fields like type may not apply via field override due to caching. Set them explicitly:

lots = LocationSeeder.seed(
    count=total_lots,
    fields={
        'type': LocationTypeChoices.LOT,
    },
)
for lot, parent_id, name in zip(lots, field_ids_expanded, lot_names):
    lot.type = LocationTypeChoices.LOT  # Explicitly set, not from field override
    lot.parent_id = parent_id
    lot.name = name
location_models.Location.objects.bulk_create(lots)

Usage in seed.py

from __future__ import annotations

from app.location.bin.seeding.seeder import BinSeeder
from app.location.field.seeding.seeder import FieldSeeder
from app.location.warehouse.seeding.seeder import WarehouseSeeder

# Seed bins with child rows
bins = BinSeeder.with_rows(count=5)

# Seed fields with child lots
fields = FieldSeeder.with_lots(count=5)

# Seed warehouses
warehouses = WarehouseSeeder.with_locations(count=5)

Code Review Checklist

When reviewing advanced FK seeding code, verify the following:

  1. Correct Method: Uses seed() not seed_database() for initial object generation
  2. Bulk Create Order: Parent objects are bulk created before capturing IDs
  3. ID Capture: IDs are captured from bulk_create result, not from original objects
  4. Zip Pattern: Uses zip() to pair children with parent IDs
  5. Expanded IDs: For multiple children per parent, IDs are expanded with list comprehension
  6. Direct FK Assignment: FK fields are set directly on objects before bulk_create
  7. Return Value: Method returns parent objects when appropriate
  8. Cache Config: cache_seed=True is set on the seeder class
  9. No Duplicate IDs: Ensures no duplicate constraint violations on re-runs
  10. Child Type Override: Explicitly sets type in loop when field override doesn't apply
  11. Consistent Naming: Method names follow pattern (with_locations, with_rows, with_lots)
  12. Comments: Step comments explain each phase of the seeding process

Related Skills

  • seeding: Basic seeding patterns and field configurations
  • models: Django model conventions
Install via CLI
npx skills add https://github.com/stratusadv/opencode-files-public --skill advanced-fk-seeding
Repository Details
star Stars 0
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator