我有一个 Django 模型,以前看起来像这样:
class Car(models.Model):
manufacturer_id = models.IntegerField()
还有另一个模型,称为
Manufacturer
,
id
字段引用了该模型。但是,我意识到使用 Django 的内置外键功能会很有用,因此我将模型更改为:
class Car(models.Model):
manufacturer = models.ForeignKey(Manufacturer)
此更改似乎立即可以正常工作,查询可以正常工作,没有错误,但是当我尝试运行迁移时,Django 输出以下内容:
- Remove field manufacturer_id from car
- Add field manufacturer to car
执行此迁移将清除数据库中的所有现有关系,因此我不想这样做。我真的根本不需要任何迁移,因为像
Car.objects.get(manufacturer__name="Toyota")
这样的查询工作正常。我想要一个适当的数据库外键约束,但它不是一个高优先级。
所以我的问题是:有没有办法进行迁移或其他方法,允许我将现有字段转换为外键?我无法使用
--fake
,因为我需要在开发、产品和同事的计算机上可靠地工作。
虽然Django的迁移系统非常强大,但它并不擅长将现有列转换为外键而不丢失数据。你已经观察到它会尝试删除旧列并添加一个新列,这会导致数据丢失。
以下是一些在不丢失数据的情况下实现目标的方法:
1. 使用SQL语句手动迁移
这是最直接和最可靠的方法,特别是当你对SQL比较熟悉时。
-
创建迁移文件但不执行任何操作:
bash python manage.py makemigrations --empty yourapp
- 编辑迁移文件:
- 删除自动生成的操作 (删除和添加列)。
-
添加一个
RunSQL
操作,该操作将添加外键约束,但不会修改列本身: ```python from django.db import migrations, modelsclass Migration(migrations.Migration): dependencies = [ ('yourapp', 'previous_migration'), ]
operations = [ migrations.RunSQL( "ALTER TABLE yourapp_car ADD CONSTRAINT fk_manufacturer FOREIGN KEY (manufacturer_id) REFERENCES yourapp_manufacturer (id) DEFERRABLE INITIALLY DEFERRED;", reverse_sql='ALTER TABLE yourapp_car DROP CONSTRAINT fk_manufacturer;', ), ]
3. **应用迁移:**
bash python manage.py migrate ```
注意:
-
将
yourapp
替换为你的应用程序名称。 -
将
yourapp_car
和yourapp_manufacturer
替换为你的表名。 -
DEFERRABLE INITIALLY DEFERRED
部分是可选的,它允许推迟外键约束检查,直到事务结束。这在处理大量数据或可能存在现有数据不一致的情况时非常有用。
2. 使用第三方库
一些第三方库可以帮助简化此过程。一个流行的选择是
django-south
,它提供更强大的迁移功能。但是,
django-south
已经不再积极维护,并且与较新版本的Django不兼容。
3. 分阶段迁移
如果你无法使用手动SQL或第三方库,则可以使用以下步骤分阶段进行迁移:
-
添加新的外键字段:
python class Car(models.Model): manufacturer_id = models.IntegerField() manufacturer = models.ForeignKey(Manufacturer, null=True, blank=True, on_delete=models.SET_NULL)
-
使用
null=True
和blank=True
允许该字段为空。 -
选择合适的
on_delete
行为。 -
运行迁移:
bash python manage.py makemigrations python manage.py migrate
-
编写脚本以填充新字段: ```python from yourapp.models import Car, Manufacturer
for car in Car.objects.all(): car.manufacturer = Manufacturer.objects.get(id=car.manufacturer_id) car.save() ```
- 删除旧字段并更新数据库约束:
-
与步骤1类似,使用
RunSQL
操作删除旧列并更新外键约束以使其不再可为空。
选择哪种方法取决于你的具体情况和对不同工具的熟悉程度。
标签:python,python-3.x,django,django-migrations From: 66803323