Karlis Rode
Karlis Rode

Reputation: 3862

Problems accessing Django model fields from model before migration

I'm moving the field some_field from Model_A to a new Model_B, with a OneToOne relationship. Before deleting this field in Model_A, I want to copy the value from (the historical) Model_A to the newly created Model_B. The problem is that I cannot retrieve the field at migration time, since Model_A doesn't any longer contain some_field.

This is the error message I get when I try running my custom migration:

AttributeError: 'Model_A' object has no attribute 'some_field'

Models before changes:

class Model_A:
    some_field = models.BooleanField(default=False)
    some_other_field = models.BooleanField(default=False)

Models after changes:

class Model_A:
    some_other_field = models.BooleanField(default=False)

class Model_B:
    model_a = models.OneToOneField(Model_A, related_name='extension')
    some_field = models.BooleanField(default=False)

Migration:

class Migration(migrations.Migration):

    dependencies = [
        ('my_app', '0001_initial'),
    ]

    def forwards_func(apps, schema_editor):
        # This is where I try to get the "historical" Model_A
        Model_A = apps2.get_model("my_app", "Model_A")

        # And this is where I intend to copy the some_field values
        for model_A_instance in Model_A.objects.all():
            b = Model_B(model_a=model_A_instance)
            # b gets created correctly, but the following step fails
            b.some_field = modelA_instance.some_field
            b.save()

    operations = [
        migrations.CreateModel(
            name='Model_B',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                ('some_field', models.BooleanField(default=False)),
                ('model_a', models.OneToOneField(related_name='extension', to='my_app.Model_A')),
            ],
            options={
            },
            bases=(models.Model,),
        ),

        migrations.RunPython(forwards_func),

        migrations.RemoveField(
            model_name='model_a',
            name='some_field',
        ),
    ]

I'm pretty much aware that I somehow have to get hold of a "historical" representation of Model_A (= the one currently in the database), but I thought that was what the apps2.get_model("my_app", "Model_A") part was for.

Any input on how to accomplish this? Or should I maybe split the migration into two, where the first one creates Model_B + copies the some_field values and the second one deletes the some_field field from Model_A?

Upvotes: 0

Views: 411

Answers (1)

GwynBleidD
GwynBleidD

Reputation: 20539

Yes, you need to have historical representation of Model_A and it's pretty easy to retrieve it. Thats what apps passed into your function called by RunPython migration is for, so why are you using some apps2 insdead of apps here? Also, you should get your Model_B from same apps instance as Model_A is from. Your migration should look like that:

class Migration(migrations.Migration):

    dependencies = [
        ('my_app', '0001_initial'),
    ]

    def forwards_func(apps, schema_editor):
        # This is where I try to get the "historical" Model_A
        # here is change - using apps passed into forwards_func by RunPython instead of apps2
        Model_A = apps.get_model("my_app", "Model_A")
        Model_B = apps.get_model("my_app", "Model_B")

        # And this is where I intend to copy the some_field values
        for model_A_instance in Model_A.objects.all():
            b = Model_B(model_a=model_A_instance)
            b.some_field = modelA_instance.some_field
            b.save()

    operations = [
        migrations.CreateModel(
            name='Model_B',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                ('some_field', models.BooleanField(default=False)),
                ('model_a', models.OneToOneField(related_name='extension', to='my_app.Model_A')),
            ],
            options={
            },
            bases=(models.Model,),
        ),

        migrations.RunPython(forwards_func),

        migrations.RemoveField(
            model_name='model_a',
            name='some_field',
        ),
    ]

Why are you using apps2 anyway? And what it is?

Upvotes: 2

Related Questions