Reputation: 89
I have a project where I need to change a normal field to an FK field and that in multiple models. The problem is that the project is productive and the database (Postgres) is full of entries.
I started locally by just mindlessly changing the field type to forgein_key() and trying to do a migration. Seems that Django is so smart that he tries to update this field, but he stumbles because of values in the model that don't exist in the related model. How can I skip those errors? Can I tell Django to set the FK == null if the related entry does not exist?
Heres some code:
from persons.models import Customer, Employee
class Insurance(models.Model):
id = models.BigAutoField(primary_key=True)
customer = models.ForeignKey(Customer, models.SET_NULL, blank=True, null=True)
employee_id = ForeignKey(Customer, models.SET_NULL, blank=True, null=True)
objects = models.Manager()
Before those fields where char fields and the relation was made in the backend code. The reason I'm trying to do this is that I need the functionalities from Django like select / prefech_related for the search functionality in the frontend insurance table. The user should have the option to search also for the name of the insured person rather than just the ID.
This is the error caused by not existing entries:
django.db.utils.IntegrityError: insert or update on table "insurance_insurance" violates foreign key constraint "insurance_insur_customer_id_98e84761_fk_person_"
DETAIL: Key (customer_id)=(100883) is not present in table "person_customer".
Can anyone tell me a solid safer way to make these relations?
Thanks in advance!
Upvotes: 1
Views: 745
Reputation: 476547
You probably should do that before turning the IntegerField
(?) into ForeignKey
. Frst we can make the IntegerField
nullable:
class Insurance(models.Model):
id = models.BigAutoField(primary_key=True)
customer = models.IntegerField(null=True, db_column='customer_id')
employee_id = ForeignKey(Customer, models.SET_NULL, blank=True, null=True)
You can then make a migration with:
python3 manage.py makemigrations
In the data migration file [Django-doc], we can then run a query to set the values for which there are no customers to NULL
:
from django.db.models import Q
def set_invalid_customers_to_null(apps, schema_editor):
Customer = apps.get_model('app', 'Customer')
Insurance = apps.get_model('app', 'Insurance')
Insurance.objects.filter(
~Q(customer__in=Customer.objects.values_list('pk', flat=True))
).update(customer=None)
You can make an individual migration, or just add this migration function to the migration file constructed through the model change.
Next we can change the type of the column to a ForeignKey
:
class Insurance(models.Model):
id = models.BigAutoField(primary_key=True)
customer = models.ForeignKey(Customer, null=True, db_column='customer_id')
employee_id = ForeignKey(Customer, models.SET_NULL, blank=True, null=True)
Upvotes: 0
Reputation: 2742
You can follow next steps:
1) Make duplicate of fileds with FK,
class Insurance(models.Model):
id = models.BigAutoField(primary_key=True)
customer = models.CharField()
employee_id = models.CharField()
customer_new = models.ForeignKey(Customer, models.SET_NULL, blank=True, null=True)
employee_id_new = ForeignKey(Customer, models.SET_NULL, blank=True, null=True)
objects = models.Manager()
2) Create script to migrate data from old fields to new fields as ForeignKey
3) Delete old fields
class Insurance(models.Model):
id = models.BigAutoField(primary_key=True)
customer_new = models.ForeignKey(Customer, models.SET_NULL, blank=True, null=True)
employee_id_new = ForeignKey(Customer, models.SET_NULL, blank=True, null=True)
objects = models.Manager()
4) Rename new fields
class Insurance(models.Model):
id = models.BigAutoField(primary_key=True)
customer = models.ForeignKey(Customer, models.SET_NULL, blank=True, null=True)
employee_id = ForeignKey(Customer, models.SET_NULL, blank=True, null=True)
objects = models.Manager()
Don't forget to make DB backup before each migration, to rollback in case of problems.
Upvotes: 2