I'm trying to add internal_code to a Django Model in the existing project.
internal_code = models.CharField(max_length=128, default=uuid.uuid4, unique=True)
The problem is that when running migrate, Django raises IntegrityError:
DETAIL: Key (internal_code)=(b24f1ca6-bd90-4c91-87b0-5f246a4057e1) is duplicated.
I understand that this problem exists only during migrate as it is generated just once.
Is there a way to avoid this behavior without having to do this?:
set field to null=True
migrate
add RunPython that will populate all the existing objects internal_code fields
set field to null=False
EDIT: This is the final migration file. I want to know if I can avoid writing such migration to get the same result (automatic so not touching shell)
from django.db import migrations, models
import uuid
def gen_uuid(apps, schema_editor):
Product = apps.get_model('products', 'Product')
for product in Product.objects.all():
product.my_sku = uuid.uuid4()
product.save(update_fields=['my_sku'])
class Migration(migrations.Migration):
dependencies = [
('products', '0015_auto_20210827_1252'),
]
operations = [
migrations.AddField(
model_name='product',
name='my_sku',
field=models.CharField(max_length=128, null=True),
),
migrations.RunPython(gen_uuid),
migrations.AlterField(
model_name='product',
name='my_sku',
field=models.CharField(default=uuid.uuid4, max_length=128, unique=True),
),
]
Related
As a proof of concept, I made these two migrations in the simplest possible Django app, polls.
Here is migration 0001.
# Generated by Django 4.0.5 on 2022-06-15 18:17
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Question',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('question_text', models.CharField(max_length=200)),
],
),
]
Here is migration 0002
# Generated by Django 4.0.5 on 2022-06-15 18:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('polls', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='question',
name='question_type',
field=models.CharField(max_length=200, null=True),
),
]
And here is the model.
from django.db import models
class Question(models.Model):
question_text = models.CharField(max_length=200)
question_type = models.CharField(max_length=200, null=True)
Let's say I've migrated the database, and now want to revert 0002. I run the following command python manage.py sqlmigrate polls 0002 --backwards and get this SQL
BEGIN;
--
-- Add field question_type to question
--
CREATE TABLE "new__polls_question" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "question_text" varchar(200) NOT NULL);
INSERT INTO "new__polls_question" ("id", "question_text") SELECT "id", "question_text" FROM "polls_question";
DROP TABLE "polls_question";
ALTER TABLE "new__polls_question" RENAME TO "polls_question";
COMMIT;
Clearly, it's dropping the entire column (by dropping and recreating the table), but I'm curious, could Django be made to keep that column without deleting it?
from django.db import models
class Town(models.Model):
name: models.CharField(max_length=70,unique=True)
country: models.CharField(max_length=30,unique=True)
class Meta:
pass
This is my model Town whith two attributes: name and country. When I create a migration in the initial_0001.py file only id column is shown
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Town',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
],
),
]
What kind of problem could it be?
The ID-Field is always automatically generated by Django when making migrations. You can specifiy your own ID field aswell, but using an auto-incremented like this is fine for your use case.
You also might want to get rid of the unique=True, as it would prevent adding multiple towns from the same country.
Create your model like this and redo your migration process:
class Town(models.Model):
name = models.CharField(max_length=70)
country = models.CharField(max_length=30)
When you have 'no changes detected' when making migrations, I usually follow these steps:
First, Delete migrations folder in project.
Then delete django-migration entries in your Database with this query:
DELETE from django_migrations WHERE app='yourAppName'
Then, create new folder ‘migrations” in app folder + init.py
Lastly, redo the migration process:
py manage.py makemigrations
py manage.py migrate --run-syncdb
I had the same problem, and this solved it. Hope it helps.
Instead of : it should be =
You also need to provide a default value for all the previous rows that were created
Then run python manage.py makemigrations
Run python manage.py migrate
class Town(models.Model):
name = models.CharField(max_length=70,unique=True, default='')
country = models.CharField(max_length=30,unique=True, default='')
class Meta:
pass
I want to add a new HASH column to my existing django table. I referred How to generate HASH for Django model. Here is how I am doing it:
def _create_uuid():
return uuid.uuid4().hex[:20]
class users(models.Model):
user_id = models.CharField(max_length=100, primary_key=True)
uuid= models.CharField(max_length=20, null=False, default=_create_uuid)
While this works completely fine for all the new users created after the addition of the uuid column. But all the existing users get assigned with the same uuid in the uuid column when I migrate. I want the existing users to have unique uuid as well. How can I do this?
You could run a Python script during the migration to generate the hashes. Generate a migrations file first with an empty default value. Then add a RunPython function and alter the field to use your _create_uuid default function for new values.
The migration script could look like this:
from django.db import migrations, models
import YOURPROJECT.YOURAPP.models
import uuid
def generate_hash(apps, schema_editor):
User = apps.get_model("YOURAPP", "users")
users = User.objects.all()
for u in users:
u.uuid = uuid.uuid4().hex[:20]
u.save()
def reverse_generate_hash(apps, schema_editor):
pass
class Migration(migrations.Migration):
dependencies = [
('YOUTAPP', 'PREVIOUS_MIGRATIONS_FILE'),
]
operations = [
migrations.AddField(
model_name='users',
name='uuid',
field=models.CharField(default='', max_length=20),
),
migrations.RunPython(generate_hash, reverse_generate_hash),
migrations.AlterField(
model_name='users',
name='uuid',
field=models.CharField(default=jYOURPROJECT.YOURAPP.models._create_uuid, max_length=20),
),
]
I have a database in microsoft sql server. I created tables and views in it.
I ran py manage.py inspetdb view_Name > Models.py and populated my models.py file with managed=false. I also dont want my model to alter my database. I just want it for data retrieval.
Should I definitely run migrate/makemigrations?
After inspectdb should i apply makemigrations on my app or is just migrate enough? And also what are the points to remember while using inspectdb on an existing database.
Also I have something like the below in my models.py file for all columns
created = models.DateTimeField(db_column='Created', blank=True, null=True) # Field name made lowercase
Is having the fieldname in lowercase safe ? Or should I change it as it is in my column? And what are those db_column='Created', blank=True, null=True fields. Not all my views have such fields. Only a few have such values.
Models.py contents
# This is an auto-generated Django model module.
# You'll have to do the following manually to clean this up:
# * Rearrange models' order
# * Make sure each model has one field with primary_key=True
# * Make sure each ForeignKey has `on_delete` set to the desired behavior.
# * Remove `managed = False` lines if you wish to allow Django to create, modify, and delete the table
# Feel free to rename the models, but don't rename db_table values or field names.
from django.db import models
class test1table(models.Model):
created = models.DateTimeField(db_column='Created', blank=True, null=True) # Field name made lowercase.
class Meta:
managed = False
db_table = 'Test1'
Migrations file 0001_intial.py
# Generated by Django 2.1.14 on 2019-11-28 07:22
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='test1table',
fields=[
('created', models.DateTimeField(blank=True, db_column='Created', null=True
],
options={
'db_table': 'Test1',
'managed': False,
},
),
]
This solution solved my problem: I created a notepad file, pasted the code, changed the extension to .py and replaced the models.py file.
Success in migrate!
I have a model:
class Model(models.Model):
price = models.DecimalField(...)
There are already Model objects in production database.
Now I add price_total field to this model which can't be null.
class Model(models.Model):
price = models.DecimalField(...)
price_total = models.DecimalField(...)
I want this price_total to be equal to price right after migration.
Something like:
price_total = models.DecimalField(default=this_object.price,...)
Is it possible to do it somehow?
The only thing I know about is:
make price_total nullable
makemigrations + migrate
set price_total equal to price for example through django shell
make price_total not nullable
makemigration + migrate
But this way has multiple disadvantages, you can forgot to do that in production, it has many steps etc...
Is there a better way?
you can do it by manual edit migration,
do makemigrations with null
do makemigrations with not null
Edit first make migration by add datamigration with update and move operations from second migrate file
remove the second migrations file,
for example:
from django.db import migrations, models
from django.db.models import F
def set_price_total(apps, schema_editor):
# Change the myapp on your
Model = apps.get_model('myapp', 'Model')
Model.objects.update(price_total=F('price'))
class Migration(migrations.Migration):
dependencies = [
('myapp', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='model',
name='price_total',
field=models.DecimalField(
decimal_places=2, max_digits=10, null=True),
),
migrations.RunPython(set_price_total),
migrations.AlterField(
model_name='model',
name='price_total',
field=models.DecimalField(
decimal_places=2, default=1, max_digits=10),
preserve_default=False,
),
]
You are on track to do it properly.
Just make sure step 3 in done in a datamigration (certainly not through django shell).
This way you won't forget to run it on production.
I'm pretty sure you can't do both add the column and setting the value to be the same as another column.
To convince yourself, you can search for vanilla SQL implementation like https://stackoverflow.com/a/13250005/1435156