GooDrive/backend/drive/migrations/0011_normalize_empty_root.py
Louis Mazin f3a7cf6337 nice
2026-10-14 01:29:42 +02:00

54 lines
2.5 KiB
Python

"""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)]