Cascade Deletion and Restoration
Overview
Configured parent/child relationships drive bulk soft-delete and restore so delete_item / restore_item stay thin: you declare edges once in config/cascade_config.py instead of repeating queries in every CRUD helper.
Motivation
In a relational database with complex parent-child relationships, soft deleting or restoring a parent entity often requires the same operation on its children. For example:
- Deleting a TestRun should also soft delete all associated TestResults
- Restoring a TestRun should also restore all associated TestResults
The Problem: Manual Cascade Logic
Before the cascade system, each CRUD function needed manual cascade logic:
The Solution: Configuration-Driven Cascades
With the cascade system, the same operation is now automatic:
Architecture
The cascade system consists of three main components:
1. Cascade Configuration (config/cascade_config.py)
Centralized registry that defines all parent-child relationships:
2. Cascade Service (services/cascade.py)
Generic service that reads the configuration and performs bulk cascade operations:
3. Automatic Integration (utils/crud_utils.py)
The generic delete_item() and restore_item() functions automatically call the cascade service:
How to Add New Cascade Relationships
Adding a new cascade relationship is a 5-line change in config/cascade_config.py:
Step 1: Identify the Relationship
Determine:
- Parent model: The entity being deleted/restored
- Child model: The dependent entity
- Foreign key: The column in the child table that references the parent
Step 2: Add to Configuration
Add a new entry or append to an existing entry in CASCADE_RELATIONSHIPS:
Step 3: Test the Cascade
That’s it! No code changes needed. The cascade will work automatically. Test it:
Advanced Configuration
Disable Delete or Restore for Specific Relationships
You can selectively disable cascade operations:
Complex Multi-Level Cascades
The system automatically handles multi-level cascades:
When you delete a Project:
- All
TestSetrecords are soft deleted - All
Testrecords in those test sets are soft deleted - All
TestResultrecords for those tests are soft deleted
Note: Each level is handled independently, not recursively. The parent cascade handles its immediate children only.
Performance Considerations
Bulk UPDATE Operations
The cascade service uses efficient bulk UPDATE queries instead of loading objects:
Benefits:
- Fast: Single UPDATE query regardless of how many children
- Memory-efficient: No objects loaded into memory
- Transaction-safe: All updates happen in one transaction
Organization Filtering
The cascade service automatically applies organization filters when available:
This ensures:
- Security: Only cascade to entities in the same organization
- Performance: Smaller result sets to process
- Data integrity: Cross-org cascades are prevented
Transaction Management
All cascade operations are fully transactional:
All child updates and the parent soft-delete happen in one commit (rollback on error).
Testing Cascade Relationships
Unit Tests
Test cascade relationships in isolation:
Best Practices
1. Document Relationships
Always add a description to cascade relationships:
2. Consider Data Preservation
Some relationships should NOT cascade:
3. Test Cascade Behavior
Always add tests when adding new cascade relationships:
4. Monitor Performance
For very large child sets, log counts and ensure indexed FKs; avoid chains deeper than you can explain in a code review.
Common Patterns
Pattern 1: Simple One-to-Many
The most common pattern:
Pattern 2: Multiple Children
A parent with multiple child types:
Pattern 3: Conditional Cascade
Different behavior for delete vs. restore:
Troubleshooting
Issue: Cascade Not Working
Symptom: Children are not being soft deleted when parent is deleted.
Solutions:
- Check that the relationship is configured in
CASCADE_RELATIONSHIPS - Verify the
foreign_keyname matches the actual column name - Ensure
cascade_delete=True(default) - Check logs for any errors during cascade
Issue: Performance Degradation
Symptom: Cascade operations are slow.
Solutions:
- Add an index on the foreign key column
- Verify bulk UPDATE is being used (not loading objects)
- Check for unnecessary filters or joins
- Consider batching for very large child sets (>10,000)
Issue: Orphaned Records
Symptom: Children remain after parent is permanently deleted.
Solutions:
- Soft delete cascade should happen before hard delete
- Check that hard delete operations go through proper channels
- Use database foreign key constraints with
ON DELETEclauses as backup
Migrating away from manual cascades
Add the relationship to CASCADE_RELATIONSHIPS, delete bespoke child-update blocks in favor of delete_item/restore_item, then fix tests to assert via without_soft_delete_filter() as needed.