Django migrations/South: New column take default value another value from the same record
Asked Answered
O

3

8

I want to add a new column to an already existing table, but I want to give it a default value dependent on already existing data:

e.g. Each record has a start_date. Now I want to add an open_until column, and I want to fill it with the value for start_date of each existing record. (the upcoming records will be able to pick different value)

Is there a friendly way to do this?

Opalina answered 1/9, 2014 at 15:33 Comment(5)
Not a database level, do it in the forms.py.Apollonian
@Apollonian but how would this target all the previous entries?Opalina
Once you have setted the default value in your database, is already done.Apollonian
You can do it, writing a method in your model that set the value, and calling it in the view before save the record.Apollonian
I give you an answer, and added a solution for you previous records.Apollonian
K
14

You can also do it within South. The only caveat is that you need two steps for that:

  1. A schema migration that adds the open_until column

    from django.db import models
    import datetime
    
    class MyModel(models.Model):
        start_date = models.DateField(),
        open_until = models.DateField(default=datetime.date.today),
    

    $ python manage.py schemamigration --auto appname

  2. A data migration that fills existing rows with the value of that other column

    $ python manage.py datamigration appname populate_open_until

    import datetime
    
    class Migration(DataMigration):
    
        def forwards(self, orm):
            "Set open_until value to that of start_date for existing rows"
            for t in orm.MyModel.objects.all():
                t.open_until = t.start_date
                t.save()
    
        def backwards(self, orm):
            "Revert back to default"
            for t in orm.MyModel.objects.all():
                t.open_until = datetime.date.today
                t.save()
    

(optional) In step 1 you can either provide a temporary default value or make it optional and add a 3rd step

  1. A schema migration that makes the open_until column mandatory.
Katzir answered 23/10, 2014 at 15:32 Comment(1)
I get Unknown command: 'datamigration'. Did you mean makemigrations?Brunella
C
2

In Python 3.8 I first add fields to MyApp models file and it looks like:

from django.db import models
import datetime

class MyModel(models.Model):
    start_date = models.DateField(),
    open_until = models.DateField(default=datetime.date.today),

Then, after running manage.py makemigrations add this lines to new migrations file is created:

def forward(apps, schema_editor):
    my_model_objects = apps.get_model('MyApp', 'MyModel').objects.all()
    for t in my_model_objects:
        t.open_until = t.start_date
        t.save()

def reverse(apps, schema_editor):
    pass

class Migration(migrations.Migration):
    operations = [
        " the base operations is here " ,
        migrations.RunPython(forward, reverse),
    ]
Canella answered 30/5, 2020 at 17:21 Comment(0)
A
-3

As I replied to you, setting a dynamic default value in a database level is not necessary if you are working with a framework :)

The best way, I think, is setting the value of your column in the view before saving the record.

models.py

from django.db import models

class MyModel(models.Model):
    start_date = models.DateField(),
    open_until = models.DateField(),

forms.py from django.forms import ModelForm

class MyForm(forms.ModelForm):
    model = MyModel

    fields = ('start_date')

class view

from django.http import HttpResponse
from django.views.generic import CreateView
from .models import MyModel


MyView(CreateView):
    form_class = MyForm

    def post(self, request, *args, **kwargs):
        form = self.form_class(request.POST)
        if form.is_valid():
            submitted_form = form.save(commit=False)
            submitted_form.open_until = form.cleaned_data["start_date"]
            submitted_form.save()
            # return an HttpResponse here

For the previous entries, make a view to call just one time, and then loop through all the records and save the values of the new column according the value of the order column.

Something like this:

from django.http import HttpResponse

def set_open_until_values(request)
    records = MyModel.objects.all()
    for record in records:
        record.open_until = record.start_date
        record.save()
    return HttpResponse("Done!!")
Apollonian answered 1/9, 2014 at 17:11 Comment(1)
This is a pile of hack. Op asked for migration od old data, not about how to handle newly created records. A data migration is the proper Djangoish way of doing this. Creating a one-time view? Why? If you really want to avoid data migrations, say, you're still prototyping, just run that function from CLI, no need for building views... Hacky hacky hackyKetty

© 2022 - 2024 — McMap. All rights reserved.