Closes: #391 (Phase 1) - Snapshot mixin base columns into schema_document#480
Conversation
…t COT creation Add CustomObjectType._collect_base_columns() and _store_base_column_snapshot() to record the concrete columns contributed by the CustomObject mixin hierarchy (id, created, last_updated, and any future additions) at table-creation time. The snapshot is stored in schema_document["base_columns"] alongside any existing keys, preserving schema-exporter output from #386. Add migration 0009_backfill_base_columns to populate the snapshot for all existing COT tables using DB introspection, subtracting user-defined field names to isolate base columns. This is the foundation for Phase 2 (post_migrate auto-heal handler that detects and applies mixin schema drift when NetBox is upgraded). Note: migration 0009_alter_customobjecttype_version on branch 392-validate-semver-fields will need renumbering to 0010 before that PR merges.
…se_columns after creation
|
@claude /review |
|
Claude finished @bctiemann's task in 1m 55s —— View job PR Review: Closes #391 (Phase 1) - Snapshot base columns into schema_document
OverviewThis PR adds Phase 1 of the auto-heal schema upgrade mechanism: at COT birth, snapshot the mixin-contributed DB columns into Code Quality
Solid implementation. Using One minor note: the output order is determined by Django's internal field ordering (typically declaration order). The backfill migration, however, sorts by column name ( # Suggestion: sort in _collect_base_columns to match backfill ordering
return sorted([
{"name": f.name, "field_class": f.__class__.__name__, "null": f.null}
for f in model._meta.concrete_fields
if f.name not in user_field_names
], key=lambda e: e["name"])
The preservation of pre-existing Minor concern: when called during Migration Overall well-designed. A few observations:
TestsThe test suite (
One note on
SummaryThe implementation is solid and the design rationale is well-documented. The three main concerns to address before merge:
|
- Sort _collect_base_columns return value by name to match the alphabetical ordering used in the backfill migration, so both paths produce consistent snapshots - field_class is now always present in backfill entries (was conditionally absent for columns not in base_field_info); falls back to 'UnknownField' for any unrecognised column - Derive id field class from CustomObject._meta.pk.__class__.__name__ instead of hardcoding 'AutoField', handling BigAutoField installs - Use USER_TABLE_DATABASE_NAME_PREFIX constant in migration instead of duplicating the 'custom_objects_' literal - Update test to assert field_class is always present in backfilled entries Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Addressed all review issues. |
arthanson
left a comment
There was a problem hiding this comment.
merge feature and request merge to feature not 3886-schema-export
arthanson
left a comment
There was a problem hiding this comment.
need to merge feature and fix merge conflicts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
test_base_columns.py: update hardcoded migration reference from
0009_backfill_base_columns to 0010_backfill_base_columns. Migration 0009
is now alter_customobjecttype_version; the backfill was renumbered to 0010.
executor.py: Phase 1 always sets schema_document={"base_columns":[...]}
on COT creation, making it truthy. The finalisation condition
`not cot.schema_document` was meant to catch "never set", but now
incorrectly skips _update_schema_document for no-change applies on newly
created COTs. Switch to checking for the presence of schema_version so
that a COT whose schema_document was populated only by the base-column
snapshot still gets a proper executor-managed document on first apply.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CustomObject is abstract, so CustomObject._meta.pk is always None. The fallback "AutoField" fired unconditionally, causing backfilled COTs to record a different field_class for id than COTs created after this PR (which correctly record "BigAutoField" via _collect_base_columns on the concrete model). This would produce false drift alerts in Phase 2. Derive pk_class from DEFAULT_AUTO_FIELD instead, which matches what _collect_base_columns produces on concrete models. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resolves conflicts in models.py and 0010_backfill_base_columns.py by keeping the f.column fix from 391-base-column-snapshot (the correct version) over the pre-fix f.name version that landed in feature via the original PR #480 merge. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
CustomObjectType._collect_base_columns()-- static method that returns the concrete DB columns contributed by theCustomObjectmixin hierarchy (e.g.id,created,last_updated), excluding user-defined field names.CustomObjectType._store_base_column_snapshot()-- writes those columns toschema_document["base_columns"]immediately after a COT table is created, preserving any other existing keys inschema_document(e.g. the schema-exporter output from Define a schema format for portable Custom Object Type definitions #386)._store_base_column_snapshot()intocreate_model()so every new COT records its base column state at birth.0009_backfill_base_columnsthat uses DB introspection to populateschema_document["base_columns"]for all existing COTs, subtracting user-defined field names to isolate the base columns.Context
This is Phase 1 of the fix for #391 ("Automatically handle Custom Object Type schema upgrades created through changes to core mixins"). The snapshot recorded here will be consumed by the Phase 2
post_migrateauto-heal handler, which will diff the stored snapshot against the currentCustomObjectmodel definition and apply any new columns to existing COT tables when NetBox is upgraded.See the strategy discussion on #391 for the full design.
Migration note
392-validate-semver-fields(PR #455) has a0009_alter_customobjecttype_versionmigration that will conflict with our0009_backfill_base_columns. That PR will need its migration renumbered to0010before it merges into the updated386-schema-export.Test plan
CollectBaseColumnsTestCase--_collect_base_columns()returnsid/created/last_updated, excludes user-defined field names, and produces entries withname/field_class/nullkeysStoreBaseColumnSnapshotTestCase-- snapshot is written toschema_document["base_columns"], existing document keys are preserved, in-memory attribute is updatedCreateModelSnapshotTestCase-- a freshly created COT hasbase_columnsinschema_documentwith the expected mandatory columnsBackfillBaseColumnsTestCase-- migration function populates existing COTs, excludes user fields, is idempotent, skips already-backfilled rows, handles multiple COTs, and produces entries with required keysGenerated with Claude Code