""" 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('')