Scenario:
I have a model, Customer
class Customer(models.Model):
name = models.CharField(max_length=100)
age = models.IntegerField()
company = models.CharField(max_length=100)
and now I updated the company
attribute witha ForeignKey
relationship as below,
class Company(models.Model):
name = models.CharField(max_length=100)
location = models.CharField(max_length=100)
class Customer(models.Model):
name = models.CharField(max_length=100)
age = models.IntegerField()
company = models.ForeignKey(Company)
What I need is, when the new migrations applied to the DB,corresponding Company
instance must automatically generate and map to the company
attribute of Customer
instance.
Is that possible? How can I achieve this ?
You should think of migrations as a version control system for your database schema. makemigrations is responsible for packaging up your model changes into individual migration files - analogous to commits - and migrate is responsible for applying those to your database.
Run python manage.py makemigrations to create a migration file. This is an auto-generated migration file using the above command. Now we will add function for transferring data from users table to bank_accounts table.
Django Migration Operation that can be used to transfer a field's default value to the database scheme. You can then use AddDefaultValue in your migration file to transfer the default values to your database. Afterwards, it's just the usual ./manage.py migrate.
When saving a new model instance using Django, it is the framework, meaning the Python code, that sets the default value in the generated INSERT SQL statement. But when adding a new field to the model, it is the database that has knowledge of the default value and fills existing rows.
One inherent aspect of Django’s migration system is that it expects synchronicity between the code and the database. The system expects these Python model classes and the database tables and columns to be consistent with each other in order to function correctly.
When dealing with a Django migration, deploying a new version to a set of machines can quickly become tedious. In order to avoid downtime, you don’t know whether you should first deploy the new code or update the database, sort of a “chicken-and-egg” problem.
Let's start from your original model and do it step by step.
class Customer(models.Model):
name = models.CharField(max_length=100)
age = models.IntegerField()
company = models.CharField(max_length=100)
First you would have to keep the original field and create a new one, to be able to restore the old data afterwards.
class Customer(models.Model):
name = models.CharField(max_length=100)
age = models.IntegerField()
company = models.CharField(max_length=100)
_company = models.ForeignKey(Company)
Now you can create a first migration with manage.py makemigrations
. Then you will have to create a data migration. Create the migration using manage.py makemigrations yourapp --empty
and update the generated file:
from django.db import migrations
def export_customer_company(apps, schema_editor):
Customer = apps.get_model('yourapp', 'Customer')
Company = apps.get_model('yourapp', 'Company')
for customer in Customer.objects.all():
customer._company = Company.objects.get_or_create(name=customer.company)[0]
customer.save()
def revert_export_customer_company(apps, schema_editor):
Customer = apps.get_model('yourapp', 'Customer')
Company = apps.get_model('yourapp', 'Company')
for customer in Customer.objects.filter(_company__isnull=False):
customer.company = customer._company.name
customer.save()
class Migration(migrations.Migration):
dependencies = [
('yourapp', 'xxxx_previous_migration'), # Note this is auto-generated by django
]
operations = [
migrations.RunPython(export_customer_company, revert_export_customer_company),
]
The above migration will populate your Company
model and Customer._company
field according to Customer.company
.
Now you can drop the old Customer.company
and rename Customer._company
.
class Customer(models.Model):
name = models.CharField(max_length=100)
age = models.IntegerField()
company = models.ForeignKey(Company)
Final manage.py makemigrations
and manage.py migrate
.
Sure, but you have to do three migrations and the fields cant be named the same thing as both need to exist at the same time. If you already have removed the company field in your real database you are SOL and will have to fix them manually.
First, add the Company
model in a normal db migration, then do a data migration and have it run after the first db migration, then do another db migration removing the company field from the Customer
model.
The db migrations you can do with manage.py makemigrations
as usual, just add something like below in a migration file between them, here i named the new company ForeignKey
field to company_obj
def fix_companies(apps, schema_editor):
Company = apps.get_model("myapp", "Company")
Customer = apps.get_model("myapp", "Customer")
for c in Customer.objects.all():
company, _ = Company.objects.get_or_create(name=c.name)
c.company_obj = company
c.save()
def rev(apps, schema_editor):
# the reverse goes here if you want to copy company names into customer again if you migrate backwards.
pass
class Migration(migrations.Migration):
dependencies = [
('myapp', 'XXXX_migration_that_added_company_model'),
]
operations = [
migrations.RunPython(fix_companies, rev),
]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With