'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 thisapp_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 |