Premature field validation in Django migrations - django

I have the following issue:
I work on a Django app that has a primary DB and now we are adding a secondary DB for syncing only some of the data in it. When I tried to run the migrations, I got an error: null value in column 'field_2' violates not-null constraint. And here is the issue:
The model:
class A(models.Model):
field_1 = models.BooleanField(default=False)
field_2 = models.BooleanField(default=True) # added later
field_3 = models.BooleanField(default=True) # added later
and 4 migrations (well, more, but those don't count):
0001 - a related model is created
0002 - model A is created and the relation is created (ForeignKey)
0003 - field_2 is added to model A
0004 - field_3 is added to model A
However when I run migrate on the new DB, the migration stops at migration 0002 with the error mentioned above:
django.db.utils.IntegrityError: null value in column "field_2" violates not-null constraint
even though the field is created in a later migration.
I don't know hot to even begin a workaround this issue.
Edit:
Migration 0001:
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name='B',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
('year', models.IntegerField()),
('active', models.BooleanField(default=True)),
],
)
]
Migration 0002:
class Migration(migrations.Migration):
dependencies = ['app', '0001']
operations = [
migrations.CreateModel(
name='A',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('field_1', models.BooleanField(default=False))
],
),
migrations.AddField(
model_name='b',
name='a',
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, to='app.B'),
),
]
Migration 0003:
class Migration(migrations.Migration):
dependencies = ['app', '0002']
operations = [
migrations.AddField(
model_name='a',
name='field_2',
field=models.BooleanField(default=True),
),
]
Migration 0004:
class Migration(migrations.Migration):
dependencies = ['app', '0003']
operations = [
migrations.AddField(
model_name='a',
name='field_3',
field=models.BooleanField(default=True),
),
]
Migrated: 0001

Related

SQL request for a model

models.py
class Country(models.Model):
name = models.CharField(max_length=50, validators=[validate_name, ])
class Meta:
managed = False
db_table = 'countries'
def __str__(self):
return self.name
0001_initial.py
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Country',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50, validators=[countries.validators.validate_name])),
],
options={
'db_table': 'countries',
'managed': False,
},
),
]
sql
(venv) michael#michael:~/Documents/PyCharmProjects/db/db$ python manage.py sqlmigrate countries 0001_initial
BEGIN;
--
-- Create model Country
--
-- (no-op)
COMMIT;
Could you tell me whether this sql reflects the model or not? If not, how can it happen? And will it produce in the database?
There is no sql to apply to the db because of managed = False.
That is, running migrate does not change the db.

Django Postgres makemigrations only Autofield at 0001_initial.py

Python 3.10.4
Django 4.0.5
PostgreSQL 14
When I start "python manage.py makemigrations" i got the file "0001_initial.py" but all Fields, except autofields, are missing.
models.py
from django.db import models
# Create your models here.
class Username(models.Model):
#id = models.AutoField(primary_key=True)
username: models.CharField(max_length=100)
class Carrier(models.Model):
#id = models.AutoField(primary_key=True)
carriername: models.CharField(max_length=100)
desc: models.TextField()
0001_initial.py
# Generated by Django 4.0.5 on 2022-06-29 13:18
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Carrier',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
],
),
migrations.CreateModel(
name='Username',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
],
),
]
First, you must know that Django By default adds the id field to the models ...
Try to Delete the migration file and
you must use the = not the :
so it will be like this
class Username(models.Model):
username=models.CharField(max_length=100)
class Carrier(models.Model):
carriername = models.CharField(max_length=100)
desc = models.TextField()
rerun manage.py makemigrations and it Should work

How to run migrations on specific database using call_command() in Django?

I'm just wondering what the correct syntax is for calling $ python manage.py migrate app_name --database db_name with the management.call_command() function at runtime.
So far, I have the following:
from django.core import management
from django.core.management.commands import migrate
# Migrate the core.contrib.dynamics if needed to the pre-specified database:
management.call_command(migrate.Command(), 'dynamics', '--database {}'.format(DB_NAME))
However, I get the following error at runtime when calling the above:
Cannot find a migration matching '--database default_node' from app 'dynamics'.
I'm 99% sure I'm probably calling the -- args incorrectly? Can anyone point me in the right direction with this?
The migrations for the dynamics app are as follows:
# Generated by Django 3.0.8 on 2020-07-02 14:28
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='ModelSchema',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=32, unique=True)),
('_modified', models.DateTimeField(auto_now=True)),
],
),
migrations.CreateModel(
name='FieldSchema',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=63)),
('data_type', models.CharField(choices=[('character', 'character'), ('text', 'text'), ('integer', 'integer'), ('float', 'float'), ('boolean', 'boolean'), ('date', 'date')], editable=False, max_length=16)),
('null', models.BooleanField(default=False)),
('unique', models.BooleanField(default=False)),
('max_length', models.PositiveIntegerField(null=True)),
('model_schema', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='fields', to='dynamics.ModelSchema')),
],
options={
'unique_together': {('name', 'model_schema')},
},
),
]
Considering you already ran the migrations, you can simply use
management.call_command('migrate', app_label='dynamics', database='dbname')

Problems accessing Django model fields from model before migration

I'm moving the field some_field from Model_A to a new Model_B, with a OneToOne relationship. Before deleting this field in Model_A, I want to copy the value from (the historical) Model_A to the newly created Model_B. The problem is that I cannot retrieve the field at migration time, since Model_A doesn't any longer contain some_field.
This is the error message I get when I try running my custom migration:
AttributeError: 'Model_A' object has no attribute 'some_field'
Models before changes:
class Model_A:
some_field = models.BooleanField(default=False)
some_other_field = models.BooleanField(default=False)
Models after changes:
class Model_A:
some_other_field = models.BooleanField(default=False)
class Model_B:
model_a = models.OneToOneField(Model_A, related_name='extension')
some_field = models.BooleanField(default=False)
Migration:
class Migration(migrations.Migration):
dependencies = [
('my_app', '0001_initial'),
]
def forwards_func(apps, schema_editor):
# This is where I try to get the "historical" Model_A
Model_A = apps2.get_model("my_app", "Model_A")
# And this is where I intend to copy the some_field values
for model_A_instance in Model_A.objects.all():
b = Model_B(model_a=model_A_instance)
# b gets created correctly, but the following step fails
b.some_field = modelA_instance.some_field
b.save()
operations = [
migrations.CreateModel(
name='Model_B',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('some_field', models.BooleanField(default=False)),
('model_a', models.OneToOneField(related_name='extension', to='my_app.Model_A')),
],
options={
},
bases=(models.Model,),
),
migrations.RunPython(forwards_func),
migrations.RemoveField(
model_name='model_a',
name='some_field',
),
]
I'm pretty much aware that I somehow have to get hold of a "historical" representation of Model_A (= the one currently in the database), but I thought that was what the apps2.get_model("my_app", "Model_A") part was for.
Any input on how to accomplish this? Or should I maybe split the migration into two, where the first one creates Model_B + copies the some_field values and the second one deletes the some_field field from Model_A?
Yes, you need to have historical representation of Model_A and it's pretty easy to retrieve it. Thats what apps passed into your function called by RunPython migration is for, so why are you using some apps2 insdead of apps here? Also, you should get your Model_B from same apps instance as Model_A is from. Your migration should look like that:
class Migration(migrations.Migration):
dependencies = [
('my_app', '0001_initial'),
]
def forwards_func(apps, schema_editor):
# This is where I try to get the "historical" Model_A
# here is change - using apps passed into forwards_func by RunPython instead of apps2
Model_A = apps.get_model("my_app", "Model_A")
Model_B = apps.get_model("my_app", "Model_B")
# And this is where I intend to copy the some_field values
for model_A_instance in Model_A.objects.all():
b = Model_B(model_a=model_A_instance)
b.some_field = modelA_instance.some_field
b.save()
operations = [
migrations.CreateModel(
name='Model_B',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('some_field', models.BooleanField(default=False)),
('model_a', models.OneToOneField(related_name='extension', to='my_app.Model_A')),
],
options={
},
bases=(models.Model,),
),
migrations.RunPython(forwards_func),
migrations.RemoveField(
model_name='model_a',
name='some_field',
),
]
Why are you using apps2 anyway? And what it is?

Django 1.8 migration unable to cast column id to integer

I'm migrating my site from an SQLite backend to a Postgres backend. We've been running native-Django style migrations (i.e., not South) from the beginning of the project. Most of the migrations run fine, but there's a hiccup in our of our applications.
We got this far in the Postgres migration. (All other apps fully migrated.) All of the migrations ran without incident on SQLite3.
processes
[X] 0001_initial
[X] 0002_auto_20150508_2149
[ ] 0003_auto_20150511_1543
[ ] 0004_auto_20150528_1739
[ ] 0005_process_upstream
[ ] 0006_auto_20150605_1436
[ ] 0007_auto_20150605_1706
[ ] 0008_milestone_prevailing_process
These two migrations ran correctly:
0001_initial.py:
class Migration(migrations.Migration):
dependencies = [
]
operations = [
migrations.CreateModel(
name='DateReason',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(unique=True, max_length=50)),
('active', models.BooleanField(default=True)),
('final', models.BooleanField(default=False)),
],
),
migrations.CreateModel(
name='EventType',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(unique=True, max_length=50)),
('active', models.BooleanField(default=True)),
],
),
migrations.CreateModel(
name='Metric',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(unique=True, max_length=50)),
('active', models.BooleanField(default=True)),
],
),
migrations.CreateModel(
name='Process',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=50)),
('sequence', models.PositiveIntegerField()),
('sla', models.PositiveSmallIntegerField(null=True, blank=True)),
('milestone', models.BooleanField(default=False)),
],
options={
'ordering': ['workflow', 'sequence'],
},
),
migrations.CreateModel(
name='Workflow',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(unique=True, max_length=20)),
('active', models.BooleanField(default=True)),
],
),
migrations.AddField(
model_name='process',
name='workflow',
field=models.ForeignKey(to='processes.Workflow'),
),
migrations.AlterUniqueTogether(
name='process',
unique_together=set([('workflow', 'name'), ('workflow', 'sequence')]),
),
]
0002_auto_20150508_2149.py:
class Migration(migrations.Migration):
dependencies = [
('processes', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Milestone',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=50)),
('sequence', models.PositiveIntegerField()),
('workflow', models.ForeignKey(to='processes.Workflow')),
],
options={
'ordering': ['workflow', 'sequence'],
},
),
migrations.AlterModelOptions(
name='process',
options={'ordering': ['milestone', 'sequence']},
),
migrations.AlterUniqueTogether(
name='process',
unique_together=set([('milestone', 'name'), ('milestone', 'sequence')]),
),
migrations.RemoveField(
model_name='process',
name='workflow',
),
migrations.AlterUniqueTogether(
name='milestone',
unique_together=set([('workflow', 'name'), ('workflow', 'sequence')]),
),
]
This migration won't run:
0003_auto_20150511_1543.py
class Migration(migrations.Migration):
dependencies = [
('processes', '0002_auto_20150508_2149'),
]
operations = [
migrations.AlterModelOptions(
name='process',
options={'ordering': ['milestone', 'sequence'], 'verbose_name_plural': 'processes'},
),
migrations.AlterField(
model_name='process',
name='milestone',
field=models.ForeignKey(to='processes.Milestone'),
),
]
Attempting to run this migration results in:
django.db.utils.ProgrammingError: column "milestone_id" cannot be cast automatically to type integer
HINT: Specify a USING expression to perform the conversion.
The current, fully migrated state of the relevant model tables is:
class Milestone(models.Model):
"""A collection of steps in a workflow"""
workflow = models.ForeignKey(Workflow, blank=False, null=False)
name = models.CharField(max_length=50, blank=False, null=False)
sequence = models.PositiveIntegerField(blank=False, null=False)
prevailing_process = models.ForeignKey('Process', blank=False, null=True, related_name='controls_milestone')
class Meta:
ordering = ['workflow', 'sequence']
unique_together = (("workflow", "sequence"), ("workflow", "name"))
def __unicode__(self):
return u"{0}: {1}".format(self.workflow.name, self.name)
class Process(models.Model):
"""A step in a workflow"""
milestone = models.ForeignKey(Milestone, blank=False, null=False)
name = models.CharField(max_length=50, blank=False, null=False)
sequence = models.PositiveIntegerField(blank=False, null=False)
sla = models.PositiveSmallIntegerField(blank=True, null=True)
upstream = models.ForeignKey('self', blank=True, null=True, on_delete=models.SET_NULL, related_name='downstream_set')
class Meta:
ordering = ['milestone', 'sequence']
unique_together = (("milestone", "sequence"), ("milestone", "name"))
verbose_name_plural = "processes"
def __unicode__(self):
return u"{0} {1}: {2}".format(self.milestone.workflow.name, self.milestone.name, self.name)
Squashing the migrations didn't help. The Postgres database is clean except for table definitions. The relevant Postgres table definitions in their stuck form are:
scorecard=# \d processes_milestone
Table "public.processes_milestone"
Column | Type | Modifiers
-------------+-----------------------+------------------------------------------------------------------
id | integer | not null default nextval('processes_milestone_id_seq'::regclass)
name | character varying(50) | not null
sequence | integer | not null
workflow_id | integer | not null
Indexes:
"processes_milestone_pkey" PRIMARY KEY, btree (id)
"processes_milestone_workflow_id_21e7e70ae59594a8_uniq" UNIQUE CONSTRAINT, btree (workflow_id, sequence)
"processes_milestone_workflow_id_363216929a08f11e_uniq" UNIQUE CONSTRAINT, btree (workflow_id, name)
"processes_milestone_846c77cf" btree (workflow_id)
Check constraints:
"processes_milestone_sequence_check" CHECK (sequence >= 0)
Foreign-key constraints:
"processes_workflow_id_53b7557aa3f3378e_fk_processes_workflow_id" FOREIGN KEY (workflow_id) REFERENCES processes_workflow(id) DEFERRABLE INITIALLY DEFERRED
scorecard=# \d processes_process
Table "public.processes_process"
Column | Type | Modifiers
-----------+-----------------------+----------------------------------------------------------------
id | integer | not null default nextval('processes_process_id_seq'::regclass)
name | character varying(50) | not null
sequence | integer | not null
sla | smallint |
milestone | boolean | not null
Indexes:
"processes_process_pkey" PRIMARY KEY, btree (id)
"processes_process_milestone_20dc77c2825fcc38_uniq" UNIQUE CONSTRAINT, btree (milestone, name)
"processes_process_milestone_5bb869985140bf86_uniq" UNIQUE CONSTRAINT, btree (milestone, sequence)
Check constraints:
"processes_process_sequence_check" CHECK (sequence >= 0)
"processes_process_sla_check" CHECK (sla >= 0)
Referenced by:
TABLE "collection_implementation" CONSTRAINT "collection__process_id_6461d2ef37b3f126_fk_processes_process_id" FOREIGN KEY (process_id) REFERENCES processes_process(id) DEFERRABLE INITIALLY DEFERRED
I'm basically out of ideas. It looks like it's already an integer, and really, what else would it expect a Django-specified primary key to be?
The problem is the migration from Process.milestone as a boolean field to Process.milestone as a foreign key. Postgres doesn't wait for a migration to fail on uncastable data. It wants a rule to alter the table in advance.
If you don't intend any sort of data migration between two fields, the easiest option is to simply drop and add the field. In this specific case, it would mean changing the operations as follows:
operations = [
migrations.RemoveField(
model_name='process',
name='milestone'
),
migrations.AddField(
model_name='process',
name='milestone',
field=models.ForeignKey(to='processes.Milestone'),
),
migrations.AlterModelOptions(
name='process',
options={'ordering': ['milestone', 'sequence'], 'verbose_name_plural': 'processes'},
)
]
Postgres doesn't know how to convert a boolean field into an integer, even if the table is empty. You need to tell it with a using clause. You can use an SQL migration for the integer conversion before you convert to a foreign key. I don't think there's a way to do this without any SQL, django doesn't know how to do that.
ALTER_SQL = '''
ALTER TABLE processes_process ALTER COLUMN milestone TYPE integer USING (
CASE milestone
when TRUE then ...
when FALSE then ...
END
);
'''
class Migration(migrations.Migration):
dependencies = [
('processes', '0002_auto_20150508_2149'),
]
operations = [
migrations.AlterModelOptions(
name='process',
options={'ordering': ['milestone', 'sequence'], 'verbose_name_plural': 'processes'},
),
migrations.RunSQL(ALTER_SQL, None, [
migrations.AlterField(
model_name='process',
name='milestone',
field=models.IntegerField(),
preserve_default=True,
),
]),
migrations.AlterField(
model_name='process',
name='milestone',
field=models.ForeignKey(to='processes.Milestone'),
),
]
If the table is empty, you might be able to get away with an empty using clause or an empty case.