9

I have a small django model with two fields. There is already data in the database for this model.

class MetaDataValue(Model):
    metadata_id = models.ForeignKey(MetaData, on_delete=models.CASCADE,)
    value = models.CharField('value', max_length=200,)

I need to add another field,

short_value = models.CharField('short_value', max_length=200,)

I know when I perform a migration, it will complain because I don't have a default value for the existing rows in the database.

Is there a way to set the default value for short_value to the string in the value field that already exists in the database/model?

I want to do this because I only need to create a different short_value for about 20 rows in the database, and there is no universal default value for this field. I would rather not have something like 'fred' or 'default' in the short_value field because some fields have numbers, some have text, some have a combination of numbers and text. I also thought of creating a property instead of another model field, but there isn't a simple way to convert the value field into the short_value field.

Thanks!

user1045680
  • 815
  • 2
  • 9
  • 19

2 Answers2

18

You can simply add the field to the model and call:

python3 manage.py makemigrations

Django will prompt you to fill in a "one-off" default value for this. For example, if I run this on your model, I see:

You are trying to add a non-nullable field 'short_value' to metadatavalue without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
 1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
 2) Quit, and let me add a default in models.py
Select an option: 1
Please enter the default value now, as valid Python
The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now
Type 'exit' to exit this prompt
>>> 'default'
Migrations for 'app':
  app/migrations/0002_metadatavalue_short_value.py
    - Add field short_value to metadatavalue

(the boldface part is the things I wrote myself).

Here the rows will thus take the text default as value.

The "one-off" value is not a default value that is specified for your models. It is just a value that is stored in the migration file, and added to rows that might already exist.

We can post-edit the migration file, and provide a default value for the column and then run a function that basically copies the value from another column to the new column, for example:

# Generated by Django 2.0.2 on 2019-02-24 19:45

from django.db import migrations, models

def copy_value_to_short_value(apps, schema_editor):
    MetaDataValue = apps.get_model('app', 'metadatavalue')
    db_alias = schema_editor.connection.alias
    from django.db.models import F
    MetaDataValue.objects.using(db_alias).all().update(
        short_value=F('value')
    )


class Migration(migrations.Migration):

    dependencies = [
        ('app', '0001_initial_commit'),
    ]

    operations = [
        migrations.AddField(
            model_name='metadatavalue',
            name='short_value',
            field=models.CharField(default='value', max_length=200, verbose_name='short_value'),
            preserve_default=False,
        ),
        migrations.RunPython(copy_value_to_short_value),
    ]

We thus define a function copy_value_to_short_value that looks similar to a Django ORM query, and then we add the migrations.RunPython(copy_value_to_short_value) to the tasks that should be done in the migration.

You should of course edit the migration file before running the migrations, since otherwise the migration appears in the django_migrations table, and Django considers the migration as "done".

Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
  • I am trying to avoid the solution you suggest. I don't want the value 'default' stored in the existing rows. I want the current string in the existing field 'value' to be set as the value in the new field 'short_value' for the existing rows. – user1045680 Feb 24 '19 at 19:44
  • @user1045680: you can add an extra function to the migration file to make a migration that then copies the values from one column to the other one. – Willem Van Onsem Feb 24 '19 at 19:59
  • Thanks for the update. It looks like exactly what I needed, although more complicated than I wanted! ;) I had hoped django had a simpler solution. It seems a simple one liner for mysql (UPDATE `table` SET short_value=value) after running the migrations with the 'default' value will accomplish the same goal. – user1045680 Feb 25 '19 at 02:14
1

Similar to Willem's answer: After provided one-off default with a string like "default" , simply add SQL to your migration operations:

migrations.RunSQL("UPDATE metadatavalue SET short_value = value;")

EthanJ
  • 11
  • 1