Skip to content

Fix dynamic field tracking #1596

Open
ace2016 wants to merge 2 commits intodjango-commons:masterfrom
ace2016:fix-dynamic-field-tracking-1517
Open

Fix dynamic field tracking #1596
ace2016 wants to merge 2 commits intodjango-commons:masterfrom
ace2016:fix-dynamic-field-tracking-1517

Conversation

@ace2016
Copy link
Copy Markdown

@ace2016 ace2016 commented Mar 25, 2026

[Improvement] – Fix historical tracking for dynamically added model fields

Description

Context:
Prior to this change, django-simple-history suffered from a timing limitation where dynamically created fields were not being tracked. Historical models are generated by copying the concrete model's fields during the Django class_prepared signal. However, custom metaclasses (such as django-organizations' OrgMeta) often add fields (like ForeignKey relationships) to models after the class_prepared hook has fired. Consequently, these late-added fields were entirely omitted from the generated historical tracking models, breaking history functionality for those fields.

Approach:
This fix introduces a hybrid re-finalization pattern to ensure all fields are captured without disrupting the framework's startup timing dependency on settings:

  • Immediate Finalization with Tracking: Modified the existing HistoricalRecords.finalize() method to continue building historical models immediately upon class_prepared, preserving reliance on module-level settings (like SIMPLE_HISTORY_HISTORY_ID_USE_UUID). However, it now records a field count snapshot in _finalized_models.
  • Deferred Re-evaluation in AppConfig: Created SimpleHistoryAppConfig with a ready() hook that calls a new function, _process_pending_finalizations().
  • Dynamic Field Detection & Re-creation: During ready(), the framework iterates over _finalized_models. If a model's current field count differs from its snapshot (indicating dynamically added fields), its historical model is safely re-created to incorporate the new fields.
  • Signal Cleanup: Implemented logic within _process_pending_finalizations() to explicitly disconnect outdated post_save, post_delete, and pre_delete signals before re-finalization, explicitly preventing duplicate tracking records.

Impact:

  • Functionality: Dynamically added fields (specifically those added via metaclasses post-class_prepared) are now correctly included and tracked in historical models.
  • Robustness: Mitigates the core issue (Issue Dynamically created fields aren't tracked #1517) comprehensively while avoiding regressions related to runtime feature flags and settings.
  • Coverage: Provides complete end-to-end verification through a custom metaclass test suite, bringing total tests passing to 365 (up from 360).

Visual Proof / Evidence

image

Tests

  • New tests added
  • Existing tests updated or refactored

Unit Tests Summary:

Created the DynamicFieldTrackingTest class containing 5 new end-to-end tests:

  • test_historical_model_has_dynamic_field: Verifies the dynamic group ForeignKey exists on the reconstructed historical model.
  • test_historical_model_for_group_exists: Ensures the base DynamicGroup model tracks standard fields as expected.
  • test_no_duplicate_history_on_save: Confirms exactly 1 history record generated upon model creation (verifying valid signal cleanup).
  • test_no_duplicate_history_on_update: Validates that subsequent updates generate exactly 1 additional tracking delta.
  • test_dynamic_field_value_tracked_in_history: Manually tests value mutation over time to ensure dynamic ForeignKey values persist accurately across the history tables.

@ace2016 ace2016 changed the title Fix dynamic field tracking #1517 Fix dynamic field tracking Mar 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant