NetBox change-logging limits for schema migration rollback
0 reputation · 15 Feb 2025, 05:51 UTC
0 reputation · 15 Feb 2025, 05:51 UTC
NetBox records model changes in its built-in change log, and administrators may want to use that history to reconstruct a pre-migration state after a schema change. The uncertainty is whether this log remains a reliable rollback source when a Django migration is reversed, especially on instances where plugins have extended core models or added custom fields.
Django migrations in NetBox follow the standard framework, so reversing a migration is technically possible, but the interaction between rolled-back schema state and existing change-log entries is not always clear. Plugin data may be left orphaned, and the log may not reflect low-level column changes unless custom signals are used. Assuming NetBox 3.4 or later, where a change-notes field exists on many core models, whether that free-form rationale survives a migration reversal consistently across plugin-extended objects is unclear.
Where does NetBox draw the line between supported change-log-based recovery and unsupported schema rollback scenarios? Specifically:
34775 reputation · 15 Feb 2025, 08:35 UTC
The built-in change log (ObjectChange table) remains queryable after a Django migration reversal, but it does not provide a reliable rollback mechanism for plugin-extended models or custom fields. Change-log entries persist as serialized JSON, yet they become orphaned references when the underlying schema (columns, content types, or plugin models) is removed. NetBox does not document a supported boundary for using change-notes or history as a schema rollback aid; this is explicitly an unsupported operation.
ObjectChange rows are stored in a separate table with a changed_data JSON field. Reversing a migration drops columns but does not delete these historical rows.ObjectChange rows that reference the deleted field or content type remain in the database. They become orphaned—queryable but no longer mappable to a live model.change_notes field on core models survives migration reversal because it lives on the base model table, not the dropped column. However, notes attached to plugin-extended objects may reference fields that no longer exist.post_save, pre_delete) do not fire during migrate --reverse. Audit entries for the reversal itself are not created automatically.Django's migration framework operates at the schema layer. NetBox's change-logging is implemented via model signals and a middleware that serializes changed_data at the application layer. When a migration reverses, the database schema reverts but the application-layer log table is untouched. The ORM can still read ObjectChange rows, but any code that tries to deserialize changed_data into a model instance will fail if the target field or content type is missing. This is why the log stays internally consistent (it's just JSON) but externally inconsistent with the live schema.
./manage.py migrate <app> <migration> --plan then ./manage.py migrate <app> <previous_migration>.SELECT id, changed_data, changed_object_type_id FROM extras_objectchange WHERE changed_object_type_id = (SELECT id FROM django_content_type WHERE model = '<your_model>');changed_data for keys that correspond to the dropped column(s).ObjectChange.objects.get(pk=...).get_changed_object() — expect ContentType.DoesNotExist or attribute errors for plugin fields.Which specific plugin or custom-field definition is being removed? The answer changes if the plugin uses a separate model table (foreign key to core model) versus CustomField (EAV stored in extras_customfieldvalue). The former leaves orphaned ObjectChange rows pointing to a deleted ContentType; the latter leaves CustomFieldValue rows that the change log may still reference by custom_field_data JSON. Confirm the plugin's data model before attempting any reconstruction.
Use comments to ask for clarification. Post a solution as an answer.
34,775 reputation · 15 Feb 2025, 13:22 UTC
One concrete boundary to check after reversing a Django migration is whether change-log rows still resolve to the correct content type. Because migration reversal restores schema state without deleting historical log entries, rows written before the rollback can retain a content_type_id that no longer matches the restored model definition.
For plugin-extended models, this is easy to miss when the plugin's model registration or app label changes during reversal. A practical verification step is to join the change-log table against django_content_type and flag rows where the stored reference no longer corresponds to the expected app label and model for the reversed state. This identifies structured references that have become semantically orphaned without requiring the log itself to be mutated.
Free-form change-notes fields generally survive database-level reversal unchanged, but their relevance to the restored schema should be treated as unverified until manually reviewed. Distinguishing between preserved free text and stale structured references is often the practical limit of relying on the built-in log as a rollback aid.