'Writing a Django migration that adds a unique field based off another field

I have a model SessionCategory with a unique field name. Certain pivotal instances of this model are referenced by name; the only problem is that name is also editable in our internal dashboard so that a user could inadvertently 'break' these references by changing the name.

To solve this, I'd like to create a new field name_slug which is a slugified version of name, also with a unique constraint. I've tried to do the following:

from django.db import models
from django.utils.text import slugify
from django.db.models.signals import pre_save
from django.dispatch import receiver



class SessionCategory(models.Model):
    name = models.CharField(max_length=255, unique=True)
    name_slug = models.CharField(max_length=255, unique=True)


@receiver(pre_save, sender=SessionCategory)
def create_name_slug(sender, instance, **kwargs):
    if not instance.name_slug:
        instance.name_slug = slugify(instance.name)

where I've added the name_slug unique field. The problem is that if I try to python manage.py makemigrations, I get the following prompt:

(venv) Kurts-MacBook-Pro-2:lucy-web kurtpeek$ python manage.py makemigrations
You are trying to add a non-nullable field 'name_slug' to sessioncategory 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: 

I've come across a workflow at https://docs.djangoproject.com/en/dev/howto/writing-migrations/#migrations-that-add-unique-fields for writing migrations that create unique fields. In that example, the unique field is generated using the 'general' function uuid.uuid4(). In my case, however, I need access to the name of the specific instance, like I was hoping to obtain by connecting to the pre_save signal.

How can I create this migration and maintain the unique constraint?



Solution 1:[1]

I think the RunPython feature will help you.

Step1. You have already a migration file before adding name_slug field. Then create a new migration file with, name_slug = models.CharField(max_length=255, unique=True,null=True).The file will be like this

app_name/migrations/0003_some_name.py

from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ('sample', '0006_sessioncategory'),
    ]

    operations = [
        migrations.AddField(
            model_name='sessioncategory',
            name='name_slug',
            field=models.CharField(max_length=255, null=True, unique=True),
        ),
    ]

**Step2.** Add a custom migration scrip by using `RunPython` as below,
from __future__ import unicode_literals

from django.db import migrations, models from django.utils.text import slugify

class Migration(migrations.Migration): def forwards_func(apps, schema_editor): SessionCategory = apps.get_model("sample", "SessionCategory") for instance in SessionCategory.objects.all(): if not instance.name_slug: instance.name_slug = slugify(instance.name) instance.save()

def reverse_func(apps, schema_editor): pass</b> dependencies = [ ('sample', '0006_sessioncategory'), ] operations = [ migrations.AddField( model_name='sessioncategory', name='name_slug', field=models.CharField(max_length=255, null=True, unique=True), ), <b>migrations.RunPython(forwards_func, reverse_func )</b> ]</code></pre>



Step 3. do the migration by python manage.py migrate
That's it !



Warning!
do not run python manage.py migrate command on step 1 (only makemigrations command)

Note
The migrations files are generated in my local system to reproduce the behaviour.It maybe different for you

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 run_the_race