githubEdit

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_date and history_user

  • Monitoring database size growth

Query Optimization

  • Use select_related() when accessing history users

  • Filter history queries by date ranges when possible

  • Consider pagination for large history sets

Troubleshooting

Missing User Information

If history_user is None:

  • Ensure HistoryRequestMiddleware is in MIDDLEWARE settings

  • Use save_with_user() or create_with_user() for programmatic changes

  • Check 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 of bulk_create()

  • Use update_with_user() instead of update()

History Not Created

If no history records are created:

  • Ensure the model inherits from AppBase

  • Check that simple_history is in INSTALLED_APPS

  • Verify the model has history = HistoricalRecords()

Last updated