# 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),
|
]
|