History Tracking
Gyrinx uses django-simple-history to track changes to all models. This provides a comprehensive audit trail of who made what changes and when.
Overview
All models that inherit from AppBase automatically have history tracking enabled. This means:
Every create, update, and delete operation is recorded
The user who made the change is tracked (when possible)
Full historical state is preserved
Changes can be queried and compared
Automatic User Tracking
Web Requests
For changes made through web requests (forms, admin), the user is automatically tracked via the HistoryRequestMiddleware.
Programmatic Changes
For changes made in code (management commands, scripts), you need to explicitly provide the user:
# Using save_with_user (defaults to owner if no user provided)
campaign = Campaign(name="My Campaign", owner=user)
campaign.save_with_user(user=admin_user)
# Using create_with_user (defaults to owner if no user provided)
campaign = Campaign.objects.create_with_user(
user=admin_user,
name="My Campaign",
owner=user
)
# Using bulk operations with history
campaigns = [Campaign(name=f"Campaign {i}", owner=user) for i in range(3)]
Campaign.bulk_create_with_history(campaigns, user=admin_user)Default User Behavior
When no explicit user is provided, the system uses the object's owner as the history user:
History Models
Every model with history tracking gets a corresponding historical model:
Querying History
All History for a Model
Recent Changes
Changes by User
Comparing Versions
Bulk Operations
Standard Django bulk operations don't create history records:
Use the history-aware methods instead:
Best Practices
Management Commands
Always provide a user for history tracking in management commands:
Data Migrations
For data migrations that create or modify records, ensure history is tracked:
Testing
History records are created during tests, so account for them:
Performance Considerations
History Volume
History records accumulate over time. Consider:
Periodic cleanup of old history records
Indexing on
history_dateandhistory_userMonitoring database size growth
Query Optimization
Use
select_related()when accessing history usersFilter history queries by date ranges when possible
Consider pagination for large history sets
Troubleshooting
Missing User Information
If history_user is None:
Ensure
HistoryRequestMiddlewareis inMIDDLEWAREsettingsUse
save_with_user()orcreate_with_user()for programmatic changesCheck that the user is authenticated in the request
Bulk Operations Not Tracked
Standard bulk operations don't trigger signals that create history:
Use
bulk_create_with_history()instead ofbulk_create()Use
update_with_user()instead ofupdate()
History Not Created
If no history records are created:
Ensure the model inherits from
AppBaseCheck that
simple_historyis inINSTALLED_APPSVerify the model has
history = HistoricalRecords()
Last updated