"""Normalize fresh installations to the physical storage root. Older databases with existing /root content MUST be converted explicitly using `manage.py flatten_drive_root --apply`, after a backup. This migration does not move, delete or rewrite any user file. """ from pathlib import Path from django.conf import settings from django.db import migrations, transaction from django.db.models import Q def normalize_unused_root(apps, schema_editor): Space = apps.get_model('drive', 'DriveSpace') Item = apps.get_model('drive', 'DriveItem') Upload = apps.get_model('drive', 'UploadSession') root = Space.objects.filter(parent__isnull=True, private_owner__isnull=True).order_by('pk').first() if root is None or root.relative_path != 'root': return # The previous migrations create a logical "root" even for an empty DB. # Existing installations must not be migrated behind the user's back: # physical bytes must be moved consistently with DB paths. old_dir = Path(settings.DRIVE_STORAGE_ROOT).resolve() / 'root' if (old_dir.exists() and (not old_dir.is_dir() or any(old_dir.iterdir()))): print('GoodDrive: root/ has data; run flatten_drive_root --apply after backup.') return if (Item.objects.filter(storage_location__startswith='root/').exists() or Upload.objects.exists()): print('GoodDrive: existing files/uploads; run flatten_drive_root --apply after backup.') return old = list(Space.objects.filter(Q(pk=root.pk) | Q(relative_path__startswith='root/'))) new = {s.pk: ('' if s.pk == root.pk else s.relative_path[5:]) for s in old} if any(not path or path.startswith('/') for pk, path in new.items() if pk != root.pk): print('GoodDrive: invalid old space paths; use flatten_drive_root after inspection.') return other_paths = set(Space.objects.exclude(pk__in=new).values_list('relative_path', flat=True)) if any(path in other_paths for path in new.values()): print('GoodDrive: space paths conflict; use flatten_drive_root after inspection.') return with transaction.atomic(): for space in old: space.relative_path = new[space.pk] Space.objects.bulk_update(old, ['relative_path']) print('GoodDrive: new database root uses DRIVE_STORAGE_ROOT directly (no /root/).') class Migration(migrations.Migration): dependencies = [('drive', '0009_managed_spaces')] operations = [migrations.RunPython(normalize_unused_root, migrations.RunPython.noop)]