0074_alter_scrobble_item.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. # Generated by Django 4.2.26 on 2025-12-03 19:37
  2. from django.db import migrations, models
  3. from django.apps import apps
  4. from django.contrib.contenttypes.models import ContentType
  5. def backfill_media_sqlite(apps, schema_editor):
  6. """
  7. Backfill media tables into ScrobblableItem for SQLite.
  8. Safely updates M2M and FK relationships.
  9. """
  10. db = schema_editor.connection
  11. if db.vendor != "sqlite":
  12. raise RuntimeError("This migration is only for SQLite.")
  13. cursor = db.cursor()
  14. models_to_migrate = [
  15. ('books', 'Book', 'book_id'),
  16. ('books', 'Paper', 'paper_id'),
  17. ('music', 'Track', 'track_id'),
  18. ('videos', 'Video', 'video_id'),
  19. # Add other media tables here...
  20. ]
  21. ScrobblableItem = apps.get_model("scrobbles", "ScrobblableItem")
  22. for app_label, model_name, old_id_field in models_to_migrate:
  23. Model = apps.get_model(app_label, model_name)
  24. table = Model._meta.db_table
  25. print(f"\nMigrating {app_label}.{model_name}...")
  26. # --- Step 0: Temporarily rename all M2M tables that reference this table ---
  27. for m2m in Model._meta.many_to_many:
  28. m2m_table = m2m.remote_field.through._meta.db_table
  29. backup_table = f"_backup_{m2m_table}"
  30. print(f" Backing up M2M table {m2m_table} -> {backup_table}")
  31. cursor.execute(f"ALTER TABLE {m2m_table} RENAME TO {backup_table}")
  32. # --- Step 1: Select all rows from original table ---
  33. columns = [f.name for f in Model._meta.local_fields]
  34. select_cols = ", ".join(columns)
  35. cursor.execute(f"SELECT {select_cols} FROM {table}")
  36. rows = cursor.fetchall()
  37. col_index = {name: i for i, name in enumerate(columns)}
  38. # --- Step 2: Create ScrobblableItem and update FK ---
  39. for row in rows:
  40. old_id = row[col_index['id']]
  41. base = ScrobblableItem.objects.create(
  42. uuid=row[col_index.get('uuid')],
  43. title=row[col_index.get('title')] or "",
  44. description=row[col_index.get('description')] or "",
  45. base_run_time_seconds=row[col_index.get('base_run_time_seconds')],
  46. )
  47. # Preserve created/modified if present
  48. for fld in ('created', 'modified'):
  49. if fld in col_index:
  50. setattr(base, fld, row[col_index[fld]])
  51. base.save(update_fields=['created', 'modified'] if 'created' in col_index else None)
  52. # Update the media table to point to the new PK
  53. cursor.execute(
  54. f"UPDATE {table} SET scrobblableitem_ptr_id = ? WHERE id = ?",
  55. [base.id, old_id]
  56. )
  57. # Update scrobbles table FK
  58. scrobble_table = "scrobbles_scrobble"
  59. cursor.execute(
  60. f"UPDATE {scrobble_table} SET item_id = ? WHERE {old_id_field} = ?",
  61. [base.id, old_id]
  62. )
  63. # --- Step 3: Restore M2M tables pointing to new IDs ---
  64. for m2m in Model._meta.many_to_many:
  65. old_m2m_table = f"_backup_{m2m.remote_field.through._meta.db_table}"
  66. new_m2m_table = m2m.remote_field.through._meta.db_table
  67. print(f" Restoring M2M table {new_m2m_table} from {old_m2m_table}")
  68. # Recreate the M2M table
  69. columns = [f.name for f in m2m.remote_field.through._meta.local_fields]
  70. cols_str = ", ".join(columns)
  71. cursor.execute(f"INSERT INTO {new_m2m_table} ({cols_str}) "
  72. f"SELECT {cols_str} FROM {old_m2m_table} backup "
  73. f"JOIN {table} media ON backup.{old_id_field} = media.id")
  74. # Drop backup
  75. cursor.execute(f"DROP TABLE {old_m2m_table}")
  76. print(f"Finished migrating {app_label}.{model_name}")
  77. def backfill_media(apps, schema_editor):
  78. if schema_editor.connection.vendor == "sqlite":
  79. backfill_media_sqlite(apps, schema_editor)
  80. else:
  81. # Postgres-safe logic can go here
  82. pass
  83. class Migration(migrations.Migration):
  84. dependencies = [
  85. ('scrobbles', '0073_alter_scrobble_options_scrobble_item_and_more'),
  86. ]
  87. operations = [
  88. migrations.RunPython(backfill_media, reverse_code=migrations.RunPython.noop),
  89. migrations.AlterField(
  90. model_name='scrobble',
  91. name='item',
  92. field=models.ForeignKey(null=True, on_delete=models.deletion.CASCADE, related_name='scrobble_set', to='scrobbles.scrobblableitem'),
  93. ),
  94. ]