Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django - alter bases in migrations

Tags:

django

For some reasons I want to apply a major change on my models. I want to rework my whole design somehow, but the Django migration implementations keep in mind the previous design by not updating my models bases.

Let me show quickly what I had before then what I have now.

app1.TopLevel
  |_ app1.IntermediateLevel
    |_ app2.LowLevel

I had 3 models like those, then now I would like to cut off this design to something more suited to my current project, such as

app2.TopLevel
  |_ app2.LowLevel

My major changes are, first I don't want an intermediate model anymore, second I don't need top keep the app1.TopLevel that way.

I don't have issues with the data (I run multiple migration, some with Python to put the data into temporary fields, then put back the data in the right field later and remove those temporary fields).

My issue is, when we create an inherited model we define its bases ;

migrations.CreateModel(
        name='IntermediateLevel',
        fields=[
            ('toplevel_ptr', models.OneToOneField(
                 auto_created=True,
                 on_delete=django.db.models.deletion.CASCADE,
                 parent_link=True,
                 primary_key=True, 
                 serialize=False, 
                 to='app1.TopLevel'),
            )
        ],
        bases=('app1.TopLevel',),
    )

In that cases I would got something like

Local field 'toplevel_ptr' in class 'LowLevel' clashes with field of similar name from base class 'IntermediateLevel'`

I read the official documentation and the source code (for migrations) but so far I didn't see anything about it. Is it possible to tell to the migration system that we changed a model bases (its parents) ?

Otherwise, the only one solution I got would be to create new models, run python migrations to copy the data from the old models to the new ones. Then remove the old models and rename the new ones to get the names I want.

like image 816
mille_a Avatar asked May 02 '16 13:05

mille_a


1 Answers

Had the same issue with the bases and I ended up writing my own migration operation like below. However, worth mention that the whole process looks like follows:

  1. Add a new IntegerField to the model with null=True
  2. Copy data from xxx_ptr to the new field
  3. Remove the xxx_ptrfield
  4. Run the RemoveModelBasesOptions operation
  5. Rename temporary Integer field into id and change it to AutoField
  6. Remove the old model which I inherited from

One thing to note is that if you have models with ForeignKey to your model, it will preserve the link anyway, so it is safe.

class RemoveModelBasesOptions(ModelOptionOperation):
    def __init__(self, name):
        super().__init__(name)

    def deconstruct(self):
        kwargs = {
            'name': self.name,
        }
        return (
            self.__class__.__qualname__,
            [],
            kwargs
        )

    def state_forwards(self, app_label, state):
        model_state = state.models[app_label, self.name_lower]
        model_state.bases = (models.Model,)
        state.reload_model(app_label, self.name_lower, delay=True)

    def database_forwards(self, app_label, schema_editor, from_state,
                          to_state):
        pass

    def database_backwards(self, app_label, schema_editor, from_state,
                           to_state):
        pass

    def describe(self):
        return "Remove bases from the model %s" % self.name

    @property
    def migration_name_fragment(self):
        return 'remove_%s_bases' % self.name_lower

Then, simply call it as an operation in your migration:

class Migration(migrations.Migration):
    dependencies = [
        ('xxxx', '0025_xxxx'),
    ]

    operations = [
        RemoveModelBasesOptions('Foo')
    ]
like image 89
Andrii Zarubin Avatar answered Oct 29 '22 17:10

Andrii Zarubin