-2

I have some models with a ForeignKey field that points to another model:

class Specimen(models.Model):
   ...

class ResultA(models.Model):
   specimen = models.ForeignKey(Specimen, on_delete=models.CASCADE, ...)
   ...

class ResultB(models.Model):
   specimen = models.ForeignKey(Specimen, on_delete=models.CASCADE, ...)
   ...

At some point, I realized that each specimen will only ever have zero or one of each type of result, so it probably makes sense to turn that into a OneToOneField.

The plan is to:

  1. Create an additional specimen_new field that's a OneToOneField on each result
  2. Create a data migration that will move the data from the ForeignKey field to the OneToOneField field
  3. Delete the ForeignKey field
  4. Rename the new specimen_new OneToOneField to just specimen
  5. Update all the code references to use the new OneToOneField properly

Step 1 was pretty simple:

class Specimen(models.Model):
   ...

class ResultA(models.Model):
   specimen = models.ForeignKey(Specimen, on_delete=models.CASCADE, ...)
   specimen_new = models.OneToOneField(Specimen, on_delete=models.CASCADE, ...)
   ...

class ResultB(models.Model):
   specimen = models.ForeignKey(Specimen, on_delete=models.CASCADE, ...)
   specimen_new = models.OneToOneField(Specimen, on_delete=models.CASCADE, ...)
   ...

I'm stuck on step 2 - creating the data migration.

Right now, it looks like this:

from django.db import migrations

def migrate_results(apps, schema_editor):
    # Get the model
    Specimen = apps.get_model('ahs', 'Specimen')

    # Update every specimen
    for specimen in Specimen.objects.filter():
        for field in Specimen._meta.get_fields():
            # Look for fields that end with "_new"
            if field.name.endswith("_new"):
                old_field_name = field.name[:-4] # Without the suffix
                new_field_name = field.name

                # Try to get the result
                result = getattr(specimen, old_field_name).first()

                # If the field had a result, copy it to the new OneToOneField
                if result:
                    setattr(specimen, new_field_name, result)
                    specimen.save()

class Migration(migrations.Migration):

    dependencies = [
        ('ahs', '0009_auto_20220525_0827'),
    ]

    operations = [
        migrations.RunPython(migrate_results),
    ]

As far as I can tell, this should work, and it doesn't crash. Print statements in the migration (not shown above) appear to show that the data IS being copied. But once I run the migration, none of the data has been copied to the OneToOneField.

Is there anything I'm obviously doing wrong?

Edit: Turns out that the ForeignKey field, and the OneToOneField are similar enough that if you just change the fields in your code directly, and run ./manage.py migrate, Django will make the appropriate changes in the DB. That still doesn't explain why my data migration wasn't being saved.

John
  • 1,917
  • 3
  • 20
  • 40

0 Answers0