Justin M. Ucar
Justin M. Ucar

Reputation: 883

Django migration default value callable generates identical entry

I am adding a new field to an existing db table. it is to be auto-generated with strings.
Here is my code:

from django.utils.crypto import get_random_string

...
Model:
    verification_token = models.CharField(max_length=60, null=False, blank=False, default=get_random_string)

I generate my migration file with ./manage.py makemigrations and a file is generated. I verify the new file has default set to field=models.CharField(default=django.utils.crypto.get_random_string, max_length=60)

so all seems fine.
Proceed with ./manage.py migrate it goes with no error from terminal. However when i check my table i see all the token fields are filled with identical values.
enter image description here

Is this something i am doing wrong? How can i fix this within migrations?

Upvotes: 7

Views: 4406

Answers (2)

knbk
knbk

Reputation: 53649

When a new column is added to a table, and the column is NOT NULL, each entry in the column must be filled with a valid value during the creation of the column. Django does this by adding a DEFAULT clause to the column definition. Since this is a single default value for the whole column, your function will only be called once.

You can populate the column with unique values using a data migration. The procedure for a slightly different use-case is described in the documentation, but the basics of the data migrations are as follows:

from django.db import migrations, models
from django.utils.crypto import get_random_string

def generate_verification_token(apps, schema_editor):
    MyModel = apps.get_model('myapp', 'MyModel')
    for row in MyModel.objects.all():
        row.verification_token = get_random_string()
        row.save()

class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '0004_add_verification_token_field'),
    ]

    operations = [
        # omit reverse_code=... if you don't want the migration to be reversible.
        migrations.RunPython(generate_verification_token, reverse_code=migrations.RunPython.noop),
    ]

Just add this in a new migration file, change the apps.get_model() call and change the dependencies to point to the previous migration in the app.

Upvotes: 11

binpy
binpy

Reputation: 4194

It maybe the token string to sort, so django will save some duplicates values. But, i'm not sure it is your main problem.

Anyway, I suggest you to handle duplicates values using while, then filter your model by generated token, makesure that token isn't used yet. I'll give you exampe such as below..

from django.utils.crypto import get_random_string

def generate_token():
    token = get_random_string()
    number = 2
    while YourModel.objects.filter(verification_token=token).exists():
        token = '%s-%d' % (token, number)
        number += 1
    return token

in your field of verification_token;

verification_token = models.CharField(max_length=60, unique=True, default=generate_token)

I also suggest you to using unique=True to handle duplicated values.

Upvotes: 1

Related Questions