Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I set/provide a default value while django migration?

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 ?

like image 629
JPG Avatar asked Mar 11 '18 15:03

JPG


People also ask

What is difference between migrate and migration in Django?

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.

How do I create a custom migration in Django?

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.

How to transfer default values from one field to another in Django?

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.

What is default value in Django model?

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.

How does Django migrate work?

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.

Should you deploy the new Django code or update the database?

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.


2 Answers

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.

like image 167
Antoine Pinsard Avatar answered Oct 18 '22 05:10

Antoine Pinsard


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),
    ]
like image 3
krs Avatar answered Oct 18 '22 04:10

krs