解决Django迁移时InvalidBasesError无法解析基类的问题
问题背景
我有coreapp和product两个Django应用,最初为复用公共字段,在coreapp中创建了BaseModel,product的所有模型都继承该类。执行python manage.py makemigrations和migrate完成初始迁移后,做了两处修改:
- 在
BaseModel中添加abstract = True,将其改为抽象基类 - 把
product模型中的attribute_object重命名为attribute、category_object重命名为category
再次执行makemigrations生成新迁移文件后,运行migrate时出现以下错误:
django.db.migrations.exceptions.InvalidBasesError: Cannot resolve bases for [<ModelState: 'product.Attribute'>, <ModelState: 'product.Category'>, <ModelState: 'product.Product'>, <ModelState: 'product.AttributeValue'>]
This can happen if you are inheriting models from an app with migrations (e.g. contrib.auth) in an app with no migrations; see https://docs.djangoproject.com/en/4.1/topics/migrations/#dependencies for more
相关代码
coreapp/models.py(修改前)
from django.db import models class BaseModel(models.Model): created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True)
coreapp/models.py(修改后)
from django.db import models class BaseModel(models.Model): created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: abstract = True
product/models.py(修改前)
from django.db import models from project_root.coreapp.models import BaseModel class Attribute(BaseModel): title = models.CharField(max_length=100) def __str__(self): return self.title class AttributeValue(BaseModel): title = models.CharField(max_length=100) attribute_object = models.ForeignKey(Attribute,on_delete=models.CASCADE, related_name='attribute_values') def __str__(self): return self.title class Category(BaseModel): title = models.CharField(max_length=200) parent= models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True) def __str__(self): return self.title class Product(BaseModel): title = models.CharField(max_length=500) price = models.FloatField() qty = models.BigIntegerField() sku = models.CharField(max_length=100) detail= models.TextField(max_length=5000) category_object = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='products') def __str__(self): return self.title
product/models.py(修改后)
from django.db import models from project_root.coreapp.models import BaseModel class Attribute(BaseModel): title = models.CharField(max_length=100) def __str__(self): return self.title class AttributeValue(BaseModel): title = models.CharField(max_length=100) attribute = models.ForeignKey(Attribute,on_delete=models.CASCADE, related_name='attribute_values') def __str__(self): return self.title class Category(BaseModel): title = models.CharField(max_length=200) parent= models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True) def __str__(self): return self.title class Product(BaseModel): title = models.CharField(max_length=500) price = models.FloatField() qty = models.BigIntegerField() sku = models.CharField(max_length=100) detail= models.TextField(max_length=5000) category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='products') def __str__(self): return self.title
生成的迁移文件
coreapp/migrations/0001_initial.py
from django.db import migrations, models class Migration(migrations.Migration): initial = True dependencies = [] operations = [ migrations.CreateModel( name='BaseModel', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('created_at', models.DateTimeField(auto_now_add=True)), ('updated_at', models.DateTimeField(auto_now=True)), ], ), ]
product/migrations/0001_initial.py
# Generated by Django 4.1 on 2023-10-15 05:59 from django.db import migrations, models import django.db.models.deletion class Migration(migrations.Migration): initial = True dependencies = [('coreapp', '0001_initial')] operations = [ migrations.CreateModel( name='Attribute', fields=[ ('basemodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='coreapp.basemodel')), ('title', models.CharField(max_length=100)), ], bases=('coreapp.basemodel',), ), migrations.CreateModel( name='Category', fields=[ ('basemodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='coreapp.basemodel')), ('title', models.CharField(max_length=200)), ('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='product.category')), ], bases=('coreapp.basemodel',), ), migrations.CreateModel( name='Product', fields=[ ('basemodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='coreapp.basemodel')), ('title', models.CharField(max_length=500)), ('price', models.FloatField()), ('qty', models.BigIntegerField()), ('sku', models.CharField(max_length=100)), ('detail', models.TextField(max_length=5000)), ('category_object', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='products', to='product.category')), ], bases=('coreapp.basemodel',), ), migrations.CreateModel( name='AttributeValue', fields=[ ('basemodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='coreapp.basemodel')), ('title', models.CharField(max_length=100)), ('attribute_object', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attribute_values', to='product.attribute')), ], bases=('coreapp.basemodel',), ), ] print('product migration 1')
修改后自动生成的错误迁移文件
coreapp/migrations/0002_delete_basemodel.py
# Generated by Django 4.1 on 2023-10-15 06:03 from django.db import migrations class Migration(migrations.Migration): dependencies = [ ('product', '0002_rename_attribute_object_attributevalue_attribute_and_more'), ('coreapp', '0001_initial'), ] operations = [migrations.DeleteModel(name='BaseModel')]
product/migrations/0002_rename_attribute_object_attributevalue_attribute_and_more.py
# Generated by Django 4.1 on 2023-10-15 06:03 from django.db import migrations, models import django.utils.timezone class Migration(migrations.Migration): dependencies = [('product', '0001_initial')] operations = [ migrations.RenameField( model_name='attributevalue', old_name='attribute_object', new_name='attribute', ), migrations.RenameField( model_name='product', old_name='category_object', new_name='category', ), migrations.RemoveField(model_name='attribute', name='basemodel_ptr'), migrations.RemoveField(model_name='attributevalue', name='basemodel_ptr'), migrations.RemoveField(model_name='category', name='basemodel_ptr'), migrations.RemoveField(model_name='product', name='basemodel_ptr'), migrations.AddField( model_name='attribute', name='created_at', field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), preserve_default=False, ), migrations.AddField( model_name='attribute', name='id', field=models.BigAutoField(auto_created=True, default=1, primary_key=True, serialize=False, verbose_name='ID'), preserve_default=False, ), migrations.AddField(model_name='attribute', name='updated_at', field=models.DateTimeField(auto_now=True)), migrations.AddField( model_name='attributevalue', name='created_at', field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), preserve_default=False, ), migrations.AddField( model_name='attributevalue', name='id', field=models.BigAutoField(auto_created=True, default=1, primary_key=True, serialize=False, verbose_name='ID'), preserve_default=False, ), migrations.AddField(model_name='attributevalue', name='updated_at', field=models.DateTimeField(auto_now=True)), migrations.AddField( model_name='category', name='created_at', field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), preserve_default=False, ), migrations.AddField( model_name='category', name='id', field=models.BigAutoField(auto_created=True, default=1, primary_key=True, serialize=False, verbose_name='ID'), preserve_default=False, ), migrations.AddField(model_name='category', name='updated_at', field=models.DateTimeField(auto_now=True)), migrations.AddField( model_name='product', name='created_at', field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), preserve_default=False, ), migrations.AddField( model_name='product', name='id', field=models.BigAutoField(auto_created=True, default=1, primary_key=True, serialize=False, verbose_name='ID'), preserve_default=False, ), migrations.AddField(model_name='product', name='updated_at', field=models.DateTimeField(auto_now=True)), ] print('product migration 2')
解决方案
问题核心是:将非抽象模型改为抽象模型时,Django自动生成的迁移文件存在依赖顺序冲突,继承关系变更的迁移逻辑不顺畅。可按以下步骤解决:
步骤1:回滚到初始迁移状态
保留修改后的模型代码,将数据库回滚到第一次迁移完成的状态:
python manage.py migrate coreapp 0001 python manage.py migrate product 0001
步骤2:删除自动生成的错误迁移文件
删除coreapp/migrations/0002_delete_basemodel.py和product/migrations/0002_rename_attribute_object_attributevalue_attribute_and_more.py这两个文件。
步骤3:分阶段处理迁移
第一阶段:仅处理字段重命名
暂时保留BaseModel为非抽象类,只提交字段名修改的迁移:
python manage.py makemigrations product python manage.py migrate product
此时会生成仅包含字段重命名的迁移文件(比如product/migrations/0002_rename_fields.py)。
第二阶段:处理BaseModel改为抽象类
- 确认
coreapp/models.py中已添加abstract=True。 - 在
coreapp/migrations下创建0002_make_basemodel_abstract.py:
from django.db import migrations class Migration(migrations.Migration): dependencies = [ ('coreapp', '0001_initial'), ('product', '0002_rename_fields'), # 替换为实际生成的字段重命名迁移文件名 ] operations = [migrations.DeleteModel(name='BaseModel')]
- 在
product/migrations下创建0003_convert_to_abstract_base.py:
from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('product', '0002_rename_fields'), # 替换为实际生成的字段重命名迁移文件名 ('coreapp', '0002_make_basemodel_abstract'), ] operations = [ # 移除继承关联字段 migrations.RemoveField(model_name='attribute', name='basemodel_ptr'), migrations.RemoveField(model_name='attributevalue', name='basemodel_ptr'), migrations.RemoveField(model_name='category', name='basemodel_ptr'), migrations.RemoveField(model_name='product', name='basemodel_ptr'), # 添加抽象基类的公共字段到每个模型 migrations.AddField(model_name='attribute', name='created_at', field=models.DateTimeField(auto_now_add=True)), migrations.AddField(model_name='attribute', name='updated_at', field=models.DateTimeField(auto_now=True)), migrations.AddField(model_name='attribute', name='id', field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), preserve_default=False), migrations.AddField(model_name='attributevalue', name='created_at', field=models.DateTimeField(auto_now_add=True)), migrations.AddField(model_name='attributevalue', name='updated_at', field=models.DateTimeField(auto_now=True)), migrations.AddField(model_name='attributevalue', name='id', field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), preserve_default=False), migrations.AddField(model_name='category', name='created_at', field=models.DateTimeField(auto_now_add=True)), migrations.AddField(model_name='category', name='updated_at', field=models.DateTimeField(auto_now=True)), migrations.AddField(model_name='category', name='id', field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), preserve_default=False), migrations.AddField(model_name='product', name='created_at', field=models.DateTimeField(auto_now_add=True)), migrations.AddField(model_name='product', name='updated_at', field=models.DateTimeField(auto_now=True)), migrations.AddField(model_name='product', name='id', field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), preserve_default=False), ]
步骤4:执行最终迁移
按顺序执行迁移:
python manage.py migrate coreapp python manage.py migrate product
替代方案(开发环境适用)
如果是开发环境且数据可丢弃,可直接重置:
- 删除
coreapp/migrations和product/migrations下除__init__.py外的所有文件 - 删除数据库文件(如sqlite的
db.sqlite3) - 重新初始化迁移:
python manage.py makemigrations python manage.py migrate
内容的提问来源于stack exchange,提问作者Tasif

