""" Management command to fix timezone-aware datetime fields. This command converts naive datetime fields to timezone-aware datetimes using the Asia/Riyadh timezone. Usage: python manage.py fix_timezone_data """ from django.core.management.base import BaseCommand from django.utils import timezone from django.apps import apps import pytz class Command(BaseCommand): help = 'Convert naive datetime fields to timezone-aware datetimes' def add_arguments(self, parser): parser.add_argument( '--dry-run', action='store_true', help='Show what would be updated without making changes', ) def handle(self, *args, **options): dry_run = options['dry_run'] riyadh_tz = pytz.timezone('Asia/Riyadh') self.stdout.write(self.style.SUCCESS('Starting timezone data migration...')) if dry_run: self.stdout.write(self.style.WARNING('DRY RUN MODE - No changes will be made')) # List of models and their datetime fields to fix models_to_fix = [ ('hr', 'Attendance', ['check_in', 'check_out']), ('hr', 'LeaveRequest', ['reviewed_at']), ('appointments', 'Appointment', ['confirmation_sent_at', 'arrival_at', 'start_at', 'end_at', 'no_show_at', 'cancel_at', 'reschedule_at']), ('appointments', 'AppointmentReminder', ['scheduled_for', 'sent_at']), ('appointments', 'AppointmentConfirmation', ['confirmed_at', 'sent_at', 'expires_at', 'last_reminder_at']), ('finance', 'Payment', ['payment_date', 'processed_at']), ('finance', 'Invoice', ['zatca_submission_date']), ('finance', 'CSID', ['issue_date', 'expiry_date', 'revocation_date', 'last_used']), ('core', 'Consent', ['signed_at']), ('documents', 'ClinicalNote', ['finalized_at']), ('integrations', 'Message', ['sent_at', 'delivered_at']), ('notifications', 'Notification', ['sent_at', 'read_at']), ('referrals', 'Referral', ['responded_at', 'completed_at']), ] total_updated = 0 for app_label, model_name, fields in models_to_fix: try: model = apps.get_model(app_label, model_name) for field_name in fields: # Check if field exists if not hasattr(model, field_name): continue # Get all records with naive datetime in this field records = model.objects.all() updated_count = 0 for record in records: field_value = getattr(record, field_name) # Skip if field is None or already timezone-aware if field_value is None or timezone.is_aware(field_value): continue # Convert naive datetime to timezone-aware # Assume the naive datetime was in Riyadh timezone aware_datetime = riyadh_tz.localize(field_value) if not dry_run: setattr(record, field_name, aware_datetime) record.save(update_fields=[field_name]) updated_count += 1 if updated_count > 0: total_updated += updated_count self.stdout.write( self.style.SUCCESS( f' ✓ {app_label}.{model_name}.{field_name}: {updated_count} records {"would be " if dry_run else ""}updated' ) ) except LookupError: self.stdout.write( self.style.WARNING(f' ⚠ Model {app_label}.{model_name} not found, skipping...') ) except Exception as e: self.stdout.write( self.style.ERROR(f' ✗ Error processing {app_label}.{model_name}: {str(e)}') ) if total_updated > 0: self.stdout.write( self.style.SUCCESS( f'\n{"Would update" if dry_run else "Updated"} {total_updated} datetime fields to timezone-aware' ) ) else: self.stdout.write( self.style.SUCCESS('\nNo naive datetime fields found - all data is already timezone-aware!') ) if dry_run: self.stdout.write( self.style.WARNING('\nThis was a dry run. Run without --dry-run to apply changes.') )