Reputation: 8001
I have a Django model that used to look like this:
class Car(models.Model):
manufacturer_id = models.IntegerField()
There is another model called Manufacturer
that the id
field refers to. However, I realized that it would be useful to use Django's built-in foreign key functionality, so I changed the model to this:
class Car(models.Model):
manufacturer = models.ForeignKey(Manufacturer)
This change appears to work fine immediately, queries work without errors, but when I try to run migrations, Django outputs the following:
- Remove field manufacturer_id from car
- Add field manufacturer to car
Doing this migration would clear all the existing relationships in the database, so I don't want to do that. I don't really want any migrations at all, since queries like Car.objects.get(manufacturer__name="Toyota")
work fine. I would like a proper database foreign key constraint, but it's not a high priority.
So my question is this: Is there a way to make a migration or something else that allows me to convert an existing field to a foreign key? I cannot use --fake
since I need to reliably work across dev, prod, and my coworkers' computers.
Upvotes: 5
Views: 7766
Reputation: 6419
Since the field won't receive any real changes (that is, all SQL would be emitted is effectively a noop when combined), you could simply tell Django not to run any SQL at all, and only change the in-python model state. This is what your model migration would look like
class Migration(migrations.Migration):
dependencies = [
('device', '0017_auto_20240723_0936'),
]
operations = [
migrations.SeparateDatabaseAndState(
state_operations=[
migrations.RemoveField(
model_name='car',
name='manufacturer_id',
),
migrations.AddField(
model_name='car',
name='manufacturer',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='device.SinglePhaseDevice'),
),
],
database_operations=[]
)
]
Be aware of the meaning of this action though. You are effectively changing the model without running any actual DDL. In certain (broken) database configurations that list must be populated with something. Extra attention, testing and reviewing is REQUIRED to ensure database_operations
is indeed empty.
A simple way to verify
The two DDLs should be functionally equivalent barring field orders (due to django generated one being likely to include REMOVE field statements, messing up field orders). If they aren't, try add some migrations.AlterField()
ops to database_operations until they match up with each other.
Upvotes: 1
Reputation: 36
I've faced the same issue today. Can be done without renaming an existing field or dropping existing column.
...
migrations.CreateModel(
name="Car",
fields=[
(
"manufacturer",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
db_constraint=False,
db_index=False,
to="Manufacturer",
),
),
db_constraint
and db_index
are True
on Car.manufacturer
field of Car
model. True is a default value if the fields not set../manage.py makemigrations
that generates AlterField
migration with required constraint and index on Car.manufacturer_id
.The first step won't effect db consistency because the generated DDL will be the same for IntegerField
and ForeignKey
with db_constaint=False
and db_index=False
and the second migration adds missing constraint and index.
You can check that with ./manage.py sqlmigrate app migration
command
Upvotes: -1
Reputation: 351
You can do data migration
I am not sure, there might be another solution where you can rename the field to name you want to, then alter the filed to new type (do a migration)
operations = [
migrations.RenameField(
model_name='car',
old_name='manufacturer_id',
new_name='manufacturer',
),
migrations.AlterField(
model_name='car',
name='manufacturer',
field=ForeignKey(blank=True, null=True,
on_delete=django.db.models.deletion.CASCADE
),
]
Upvotes: 9