loko/streetup/assets/management/commands/initialize_asset_history.py
2026-07-22 14:48:40 +02:00

183 lines
7.3 KiB
Python

"""
Management command to initialize AssetHistory for existing assets in the database.
Creates a 'create' history entry for each asset using:
- installation_date if available
- 01/01/2000 as fallback
Usage: python manage.py initialize_asset_history [--dry-run] [--batch-size=1000]
"""
from django.core.management.base import BaseCommand
from django.contrib.contenttypes.models import ContentType
from django.utils import timezone
from django.db import transaction
from datetime import datetime
from assets.models import AssetHistory
class Command(BaseCommand):
help = 'Initialize AssetHistory for existing assets (creates __created__ entries)'
def add_arguments(self, parser):
parser.add_argument(
'--dry-run',
action='store_true',
help='Show what would be done without actually creating history entries'
)
parser.add_argument(
'--batch-size',
type=int,
default=1000,
help='Number of history entries to create in each batch (default: 1000)'
)
parser.add_argument(
'--app-label',
type=str,
default='assets',
help='App label to process (default: assets)'
)
def handle(self, *args, **options):
dry_run = options['dry_run']
batch_size = options['batch_size']
app_label = options['app_label']
self.stdout.write(self.style.SUCCESS('=' * 70))
self.stdout.write(self.style.SUCCESS('Asset History Initialization'))
self.stdout.write(self.style.SUCCESS('=' * 70))
if dry_run:
self.stdout.write(self.style.WARNING('\n[!] DRY RUN MODE - No changes will be made\n'))
# Default creation date for assets without installation_date
default_date = timezone.make_aware(datetime(2000, 1, 1, 0, 0, 0))
# Get all asset models from the specified app
asset_content_types = ContentType.objects.filter(app_label=app_label)
total_assets = 0
total_created = 0
total_skipped = 0
for content_type in asset_content_types:
model_class = content_type.model_class()
# Skip if model doesn't exist or isn't an asset-like model
if not model_class or not hasattr(model_class, 'code'):
continue
model_name = content_type.model
self.stdout.write(f'\nProcessing {content_type.app_label}.{model_name}...')
# Get all assets of this type
assets = model_class.objects.all()
asset_count = assets.count()
if asset_count == 0:
self.stdout.write(self.style.WARNING(f' No assets found'))
continue
self.stdout.write(f' Found {asset_count} assets')
# Get existing history entries for this model
existing_history = set(
AssetHistory.objects.filter(
content_type=content_type,
action_type='create'
).values_list('object_id', flat=True)
)
# Prepare batch of history entries
history_entries = []
processed = 0
created = 0
skipped = 0
for asset in assets.iterator(chunk_size=batch_size):
processed += 1
# Skip if already has creation history
if asset.id in existing_history:
skipped += 1
continue
# Determine creation date
if hasattr(asset, 'installation_date') and asset.installation_date:
# Combine date with time
dt = datetime.combine(asset.installation_date, datetime.min.time())
# Make timezone-aware if not already
if timezone.is_aware(dt):
creation_date = dt
else:
creation_date = timezone.make_aware(dt)
else:
creation_date = default_date
# Create creation history entry
history_entry = AssetHistory(
content_type=content_type,
object_id=asset.id,
timestamp=creation_date,
user=None, # System-generated
field_name='__created__',
old_value=None,
new_value=asset.code,
action_type='create'
)
history_entries.append(history_entry)
# Add initial status entry if asset has a status
if hasattr(asset, 'status') and asset.status:
status_entry = AssetHistory(
content_type=content_type,
object_id=asset.id,
timestamp=creation_date,
user=None, # System-generated
field_name='status',
old_value=None,
new_value=asset.status,
action_type='status_change'
)
history_entries.append(status_entry)
created += 1
# Batch insert when we reach batch_size
if len(history_entries) >= batch_size:
if not dry_run:
with transaction.atomic():
AssetHistory.objects.bulk_create(history_entries)
history_entries = []
self.stdout.write(f' ✓ Processed {processed}/{asset_count} assets...', ending='\r')
# Insert remaining entries
if history_entries and not dry_run:
with transaction.atomic():
AssetHistory.objects.bulk_create(history_entries)
# Summary for this model
self.stdout.write(f' ✓ Processed {processed}/{asset_count} assets')
if created > 0:
self.stdout.write(self.style.SUCCESS(f' [+] Created {created} history entries'))
if skipped > 0:
self.stdout.write(self.style.WARNING(f' [-] Skipped {skipped} (already have history)'))
total_assets += asset_count
total_created += created
total_skipped += skipped
# Final summary
self.stdout.write('\n' + '=' * 70)
self.stdout.write(self.style.SUCCESS('Summary:'))
self.stdout.write('=' * 70)
self.stdout.write(f'Total assets processed: {total_assets}')
self.stdout.write(self.style.SUCCESS(f'History entries created: {total_created}'))
self.stdout.write(self.style.WARNING(f'Assets skipped (existing history): {total_skipped}'))
if dry_run:
self.stdout.write('\n' + self.style.WARNING('[!] DRY RUN - No changes were made'))
self.stdout.write(self.style.WARNING(' Run without --dry-run to apply changes'))
else:
self.stdout.write('\n' + self.style.SUCCESS('[+] Asset history initialization complete!'))
self.stdout.write('')