183 lines
7.3 KiB
Python
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('')
|