# Generated by Django 4.2.16 on 2024-11-07 10:31 from django.db import migrations import logging from core.models import AsyncMigrationStatus from core.redis import start_job_async_or_sync from django.db.models import Count, Min from projects.models import ProjectMember logger = logging.getLogger(__name__) migration_name = '0028_auto_20241107_1031' def forward_migration(migration_name): logger.info(f'Starting async migration {migration_name}') migration = AsyncMigrationStatus.objects.create( name=migration_name, status=AsyncMigrationStatus.STATUS_STARTED, ) try: # Get projects with duplicates projects_with_duplicates = ( ProjectMember.objects .values('project_id', 'user_id') .annotate(entry_count=Count('id')) .filter(entry_count__gt=1) .values_list('project_id', flat=True) .distinct() ) for project_id in projects_with_duplicates: # Remove duplicates for each project duplicates = ( ProjectMember.objects .filter(project_id=project_id) .values('user_id') .annotate(count=Count('id'), min_id=Min('id')) .filter(count__gt=1) ) total_deleted = 0 for dup in duplicates: user_id = dup['user_id'] min_id = dup['min_id'] entries_to_delete = ( ProjectMember.objects .filter(user_id=user_id, project_id=project_id) .exclude(id=min_id) ) deleted_count, _ = entries_to_delete.delete() total_deleted += deleted_count logger.info(f'Deleted {total_deleted} duplicate ProjectMember entries for project ID {project_id}.') except Exception as e: migration.status = AsyncMigrationStatus.STATUS_ERROR migration.save() logger.error(f'Async migration {migration_name} failed: {e}') raise migration.status = AsyncMigrationStatus.STATUS_FINISHED migration.save() logger.info(f'Async migration {migration_name} complete') def forwards(apps, schema_editor): # Dispatch migration to workers without passing unpicklable objects start_job_async_or_sync(forward_migration, migration_name=migration_name) def backwards(apps, schema_editor): pass class Migration(migrations.Migration): atomic = False dependencies = [ ("projects", "0027_project_custom_task_lock_ttl"), ] operations = [ migrations.RunPython(forwards, backwards), ]