# Convert Site.site_id from BigAutoField to name-based SlugField PK with numeric suffix. # atomic=False: PostgreSQL cannot ALTER TABLE in the same transaction after PK UPDATEs. from django.db import migrations, models import django.db.models.deletion from django.utils.text import slugify def _unique_slug(site_name: str, old_pk: str, used: set[str]) -> str: base = slugify(str(site_name or "").strip())[:55] or f"site-{old_pk}"[:55] try: seed = int(old_pk) % 10000 except ValueError: seed = sum(ord(c) for c in old_pk) % 10000 for i in range(10000): code = f"{base}-{(seed + i) % 10000:04d}" if code not in used: used.add(code) return code raise RuntimeError(f"Could not allocate site id for {old_pk}") def forwards_slug_pks(apps, schema_editor): Site = apps.get_model("farms", "Site") Kandang = apps.get_model("farms", "Kandang") PusatRegistration = apps.get_model("farms", "PusatRegistration") used: set[str] = set() mapping: dict[str, str] = {} for site in Site.objects.all().order_by("site_id"): old = str(site.site_id) mapping[old] = _unique_slug(site.site_name, old, used) for old_id, new_id in mapping.items(): if old_id == new_id: continue Kandang.objects.filter(site_id=old_id).update(site_id=new_id) PusatRegistration.objects.filter(site_id=old_id).update(site_id=new_id) Site.objects.filter(site_id=old_id).update(site_id=new_id) # Ensure site names are unique before adding the constraint. seen_names: dict[str, int] = {} for site in Site.objects.all().order_by("site_id"): name = site.site_name count = seen_names.get(name, 0) seen_names[name] = count + 1 if count == 0: continue suffix = f"-{count + 1}" site.site_name = f"{name[: 30 - len(suffix)]}{suffix}" site.save(update_fields=["site_name"]) def noop_reverse(apps, schema_editor): raise RuntimeError("Cannot reverse site_id slug migration.") class Migration(migrations.Migration): atomic = False dependencies = [ ("farms", "0016_pusat_registration"), ] operations = [ migrations.AlterField( model_name="kandang", name="site", field=models.ForeignKey( db_constraint=False, on_delete=django.db.models.deletion.CASCADE, related_name="kandangs", to="farms.site", ), ), migrations.AlterField( model_name="pusatregistration", name="site", field=models.OneToOneField( db_constraint=False, on_delete=django.db.models.deletion.CASCADE, related_name="pusat_registration", to="farms.site", ), ), migrations.AlterField( model_name="site", name="site_id", field=models.CharField(max_length=64, primary_key=True, serialize=False), ), migrations.RunPython(forwards_slug_pks, noop_reverse), migrations.AlterField( model_name="site", name="site_id", field=models.SlugField(max_length=64, primary_key=True, serialize=False), ), migrations.AlterField( model_name="site", name="site_name", field=models.CharField(max_length=30, unique=True), ), migrations.AlterField( model_name="kandang", name="site", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="kandangs", to="farms.site", ), ), migrations.AlterField( model_name="pusatregistration", name="site", field=models.OneToOneField( on_delete=django.db.models.deletion.CASCADE, related_name="pusat_registration", to="farms.site", ), ), ]