I have a Model
class Mystery(models.Model):
first = models.CharField(max_length=256)
second = models.CharField(max_length=256)
third = models.CharField(max_length=256)
player = models.ForeignKey(Player)
I added the player ForeignKey but when I try to migrate it using South it seems that I can't create this whith a null=False. I have this message :
The field 'Mystery.player' does not have a default specified, yet is
NOT NULL. Since you are adding this field, you MUST specify a default
value to use for existing rows. Would you like to:
1. Quit now, and add a default to the field in models.py
2. Specify a one-off value to use for existing columns now
I use this command :
manage.py schemamigration myapp --auto
Thanks a lot !
This is best accomplished in 3 migrations.
Step 1. Create the new model and allow player to be null: player = models.ForeignKey(Player, null=True)
Step 2. Run ./manage.py schemamigration <app> --auto
Step 3. Run ./manage.py datamigration <app> set_default_players
Step 4. Update forwards and backwards in <app>/migrations/<number>_set_default_players.py to use whatever logic you like for setting the default player. Ensure that every Mystery object has a value for player.
Step 5. Update the Mystery model so that player has null=False.
Step 6. Run ./manage.py schemamigration <app> --auto
Step 7. Run ./manage.py migrate <app>
Another option is to create a data migration before adding the ForeignKey, in which you create a new Player instance with a specific id. Be sure that that id does not exist previously in your database.
1.Create the data migration file
$ ./manage.py datamigration myapp add_player
Created 00XX_add_player.py
2.Edit the forwards and backwards methods of the file:
def forwards(self, orm):
orm['myapp.Player'].objects.create(name=u'Very misterious player', id=34)
def backwards(self, orm):
# Haven't tested this one yet
orm['myapp.Player'].objects.filter(id=34).delete()
3.Add the ForeignKey to your Mistery class and migrate the schema again. It will ask for the default value to your data migration id, in this example, 34.
$ ./manage.py schemamigration --auto myapp
? The field 'Mistery.player' does not have a default specified, yet is NOT NULL.
? Since you are adding this field, you MUST specify a default
? value to use for existing rows. Would you like to:
? 1. Quit now, and add a default to the field in models.py
? 2. Specify a one-off value to use for existing columns now
? Please select a choice: 2
? Please enter Python code for your one-off default value.
? The datetime module is available, so you can do e.g. datetime.date.today()
>>> 34
+ Added field player on myapp.Mistery
Created 0010_auto__add_field_mistery_player.py. You can now apply this migration with: ./manage.py migrate myapp
4.Finally run the migrate command and it will execute the migrations in sequential order, inserting the new Player and updating all the Mistery rows with the reference to your new Player.
If your database already contains Mystery objects, then south must know, what value put into player field, because it cannot be blank.
One possible solution:
choose
2. Specify a one-off value to use for existing columns now
and then enter 1. So all of your existing Mystery objects will now point to Player with pk = 1. Then you can change (if needed) this in admin page.
Related
I have the following model:
class Teacher(models.Model)
tenured = models.BooleanField(default=False)
I want to migrate this BooleanField to a CharField with additional options ['No', 'Pending', 'Yes']. Like so:
class Teacher(models.Model)
TENURE_CHOICES = [(choice,choice) for choice in ['No', 'Pending', 'Yes']]
tenured = models.CharField(max_length=7, default='No', choices=TENURE_CHOICES)
Now, as I have 1000+ records already present for this model I would like to migrate them from their Boolean value to the 'Yes'/'No' value when migrating.
How would I do this without having to store a backup of this table and reapply after migrations have taken place? Can I do this as part of the migration process?
You can create migration using makemigrations command and boolean values will be casted to chars, '1' for True and '0' for False. After that you can write simple command to swap all '0' to 'No' and all '1' to 'Yes' or even from DB level.
from authorization.models import TestModel
TestModel.objects.create(example_bool=True)
<TestModel: TestModel object (1)>
TestModel.objects.create(example_bool=True)
<TestModel: TestModel object (2)>
TestModel.objects.create(example_bool=True)
<TestModel: TestModel object (3)>
TestModel.objects.create(example_bool=False)
<TestModel: TestModel object (4)>
TestModel.objects.create(example_bool=False)
<TestModel: TestModel object (5)>
for x in TestModel.objects.all():
print(x.example_bool)
1
1
1
0
0
We run a zero downtime application on django so we can never remove a field in the same deployment as the change the model to reflect the deletion.
To migrate we rename the old field and add the new field
You could use this approach to first rename and add the new field with custom migration according to docs https://docs.djangoproject.com/en/4.0/howto/writing-migrations/.
So you keep the data until you are sure everything is fine.
Once you are satisfied remove the renamed field from model -> deploy -> make migrations-> deploy again.
This variant is zero downtime complient and has the benefit of not requiring a backup or a copy of the table.
My model:
class Item(models.Model):
name = models.CharField(...)
description = models.CharField(...)
I run manage.py makemigrations and manage.py migrate
Then I switched to another git branch where description field doesn't exist yet but when I try to create new Item object I see:
null value in column "description" violates not-null constrain
What is the best way to fix that?
Your database has a column which is not in your new branch.
So, either drop that column from your database, or create a new DB.
One more option is to go back to the previous branch, make the description nullable by updating its definition to:
description = models.CharField(null=True...)
and then run the makemigrations and migrate commands.
Consider this structure:
some_table(id: small int)
and I want change it to this:
some_table(id: string)
Now I do this with three migrations:
Create a new column _id with type string
(datamigration) Copy data from id to _id with string conversion
Remove id and rename _id to id
Is there a way to do this with only one migration?
You can directly change the type of a column from int to string. Note that, unless strict sql mode is enabled, integers will be truncated to the maximum string length and data is possibly lost, so always make a backup and choose a max_length that's high enough. Also, the migration can't easily be reversed (sql doesn't directly support changing a string column to an int column), so a backup is really important in this one.
Django pre 1.7 / South
You can use db.alter_column. First, create a migration, but don't apply it yet, or you'll lose the data:
>>> python manage.py schemamigration my_app --auto
Then, change the forwards method into this:
class Migration(SchemaMigration):
def forwards(self, orm):
db.alter_column('some_table', 'id', models.CharField(max_length=255))
def backwards(self, orm):
raise RuntimeError('Cannot reverse this migration.')
This will alter the column to match the new CharField field. Now apply the migration and you're done.
Django 1.7
You can use the AlterField operation to change the column. First, create an empty migration:
>>> python manage.py makemigrations --empty my_app
Then, add the following operation:
class Migration(migrations.Migration):
operations = [
migrations.AlterField('some_model', 'id', models.CharField(max_length=255))
]
Now run the migration, and Django will alter the field to match the new CharField.
Edit: I understand the reason why this happened. It was because of the existence of `initial_data.json` file. Apparently, south wants to add those fixtures after migration but failing because of the unique property of a field.
I changed my model from this:
class Setting(models.Model):
anahtar = models.CharField(max_length=20,unique=True)
deger = models.CharField(max_length=40)
def __unicode__(self):
return self.anahtar
To this,
class Setting(models.Model):
anahtar = models.CharField(max_length=20,unique=True)
deger = models.CharField(max_length=100)
def __unicode__(self):
return self.anahtar
Schema migration command completed successfully, but, trying to migrate gives me this error:
IntegrityError: duplicate key value violates unique constraint
"blog_setting_anahtar_key" DETAIL: Key (anahtar)=(blog_baslik) already
exists.
I want to keep that field unique, but still migrate the field. By the way, data loss on that table is acceptable, so long as other tables in DB stay intact.
It's actually the default behavior of syncdb to run initial_data.json each time. From the Django docs:
If you create a fixture named initial_data.[xml/yaml/json], that fixture will be loaded every time you run syncdb. This is extremely convenient, but be careful: remember that the data will be refreshed every time you run syncdb. So don't use initial_data for data you'll want to edit.
See: docs
Personally, I think the use-case for initial data that needs to be reloaded each and every time a change occurs is retarded, so I never use initial_data.json.
The better method, since you're using South, is to manually call loaddata on a specific fixture necessary for your migration. In the case of initial data, that would go in your 0001_initial.py migration.
def forwards(self, orm):
from django.core.management import call_command
call_command("loaddata", "my_fixture.json")
See: http://south.aeracode.org/docs/fixtures.html
Also, remember that the path to your fixture is relative to the project root. So, if your fixture is at "myproject/myapp/fixtures/my_fixture.json" call_command would actually look like:
call_command('loaddata', 'myapp/fixtures/my_fixture.json')
And, of course, your fixture can't be named 'initial_data.json', otherwise, the default behavior will take over.
I am using South to change a ForeignKey TO ManyToManyField in one of the models in Django but it is not working out as expected.
# Original Schema
class Item(models.Model):
category = models.ForeignKey(Category, default=default_category)
To be changed to
# Original Schema
class Item(models.Model):
category = models.ManyToManyField(Category, default=default_category)
So after commenting out the ForeignKey line in the model I do,
python manage.py schemamigration affected_model --auto
? The field 'Item.category' does not have a default specified, yet is NOT NULL.
? Since you are removing this field, you MUST specify a default
? value to use for existing rows. Would you like to:
? 1. Quit now, and add a default to the field in models.py
? 2. Specify a one-off value to use for existing columns now
? 3. Disable the backwards migration by raising an exception.
? Please select a choice:
I am confused by this because 1. I have specified a default value which is "default_category" and 2. I am not removing any field I am just changing it to ManyToManyField. My question is how to go ahead in this case? Is there any other trick to make this conversion using South?
BTW I am using South 0.7 and Django 1.1.1
Thanks for the help.
In fact you are removing the field. Foreignkeys are represented by a column in your database that in that case would be named category_id. ManyToMany relationships are represented by a "through" table. With django you can either specif the through table or have one generated for you automatically.
This is a very nontrivial migration and you will need to hand code it. It will require a bit of understanding what the underlying database representation of your model is.
You will require 3 migrations to cleanly do this. First create a schemamigration with a dummy manytomany relationship to hold your data.
Then create a datamigration to copy the foreignkey relationships to your dummy manytomany
Finally create schemamigration to delete the foreignkey and rename the dummy manytomany table.
Steps 2 and 3 will both require you to manually write the migrations. I should emphasize this is a very nontrivial style of migration. However, it's entirely doable you just have to really understand what these relationships mean to the database more than your average migration. If you have little or no data it would be much simpler to just drop the tables and start over fresh with your migrations.