#31735: Migration problems when using intermediate (through) table on
many-to-many
relationship
-------------------------------------+-------------------------------------
Reporter: Rodrigo | Owner: nobody
Estevao |
Type: Bug | Status: new
Component: | Version: 3.0
Migrations | Keywords: many-to-
Severity: Normal |
many;relationship;intermediate;table;through
Triage Stage: | Has patch: 0
Unreviewed |
Needs documentation: 0 | Needs tests: 0
Patch needs improvement: 0 | Easy pickings: 0
UI/UX: 0 |
-------------------------------------+-------------------------------------
I've been facing a migration problem when using a through table on many-
to-many relationship.
When I create the migration using modles configured as follow ...
{{{
# src/apps/core/valuation/models.py
from django.utils import timezone
from django.db import models
from django.utils.translation import gettext_lazy as _
class Valuation(models.Model):
ref_year = models.PositiveIntegerField(
_('reference year'),
default=timezone.now().year
)
ref_month = models.PositiveSmallIntegerField(
_('reference month'),
default=timezone.now().month
)
remark = models.TextField(
_('notes'),
null=True,
blank=True,
)
class Meta:
db_table = 'core\".\"valuation'
verbose_name = _('Valuation')
verbose_name_plural = _('Valuations')
# src/apps/loader/datafile/models.py
from django.db import models
from django.utils.translation import gettext_lazy as _
class DataFile(models.Model):
path = models.FileField(_('path'), max_length=4096)
hash = models.CharField(_('file hash'), max_length=128)
remark = models.CharField(
_('remark'),
max_length=128,
null=True,
blank=True
)
uploaded_at = models.DateTimeField(
_('uploaded at'),
auto_now_add=True,
editable=False
)
uploaded_by = models.ForeignKey(
'account.Account',
db_column='uploaded_by',
verbose_name=_('uploaded by'),
related_name='+',
on_delete=models.PROTECT,
)
class Meta:
db_table = 'loader\".\"data_file'
verbose_name = 'data file'
verbose_name_plural = 'data files'
# src/apps/loader/dataload/models.py
from django.db import models
from django.utils.translation import gettext_lazy as _
from apps.loader.datafile.models import DataFile
from apps.core.valuation.models import Valuation
class DataLoad(models.Model):
valuation = models.ForeignKey(
to=Valuation,
verbose_name=_('valuation'),
related_name='dataloads',
on_delete=models.PROTECT
)
datafile = models.ManyToManyField(
to=DataFile,
related_name='dataloads',
through='dataload.DataLoadFile',
through_fields=('dataload', 'datafile',)
)
class Meta:
db_table = 'loader\".\"data_load'
verbose_name = 'data_load'
verbose_name_plural = 'data_load'
class DataLoadFile(models.Model):
dataload = models.ForeignKey(
to=DataLoad,
verbose_name=_('data load'),
related_name='datafiles',
on_delete=models.PROTECT
)
datafile = models.ForeignKey(
to=DataFile,
verbose_name=_('data file'),
related_name='+',
on_delete=models.PROTECT
)
class Meta:
db_table = 'loader\".\"data_load_files'
verbose_name = 'data load file'
verbose_name_plural = 'data load files'
}}}
... it creates the following migration file
{{{
# Generated by Django 3.0.7 on 2020-06-18 15:58
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('valuation', '0001_initial'),
('datafile', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='DataLoad',
fields=[
('id', models.AutoField(auto_created=True,
primary_key=True, serialize=False, verbose_name='ID')),
],
options={
'verbose_name': 'data_load',
'verbose_name_plural': 'data_load',
'db_table': 'loader"."data_load',
},
),
migrations.CreateModel(
name='DataLoadFile',
fields=[
('id', models.AutoField(auto_created=True,
primary_key=True, serialize=False, verbose_name='ID')),
('datafile',
models.ForeignKey(on_delete=django.db.models.deletion.PROTECT,
related_name='+', to='datafile.DataFile', verbose_name='data file')),
('dataload',
models.ForeignKey(on_delete=django.db.models.deletion.PROTECT,
related_name='datafiles', to='dataload.DataLoad', verbose_name='data
load')),
],
options={
'verbose_name': 'data load file',
'verbose_name_plural': 'data load files',
'db_table': 'loader"."data_load_files',
},
),
migrations.AddField(
model_name='dataload',
name='datafile',
field=models.ManyToManyField(related_name='dataloads',
through='dataload.DataLoadFile', to='datafile.DataFile'),
),
migrations.AddField(
model_name='dataload',
name='valuation',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT,
related_name='dataloads', to='valuation.Valuation',
verbose_name='valuation'),
),
]
}}}
when I run the migrate command I receive the following error:
{{{
Applying dataload.0001_initial...Traceback (most recent call last):
File "/home/rodrigo/Workspace/fgv/sacvbackend/src/venv/lib/python3.8
/site-packages/django/db/backends/utils.py", line 86, in _execute
return self.cursor.execute(sql, params)
psycopg2.errors.UndefinedObject: constraint
"data_load_valuation_id_9af1ae91_fk_valuation_id" does not exist
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/rodrigo/Workspace/fgv/sacvbackend/src/manage.py", line 21,
in <module>
main()
File "/home/rodrigo/Workspace/fgv/sacvbackend/src/manage.py", line 17,
in main
execute_from_command_line(sys.argv)
File "/home/rodrigo/Workspace/fgv/sacvbackend/src/venv/lib/python3.8
/site-packages/django/core/management/__init__.py", line 401, in
execute_from_command_line
utility.execute()
File "/home/rodrigo/Workspace/fgv/sacvbackend/src/venv/lib/python3.8
/site-packages/django/core/management/__init__.py", line 395, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File "/home/rodrigo/Workspace/fgv/sacvbackend/src/venv/lib/python3.8
/site-packages/django/core/management/base.py", line 328, in run_from_argv
self.execute(*args, **cmd_options)
File "/home/rodrigo/Workspace/fgv/sacvbackend/src/venv/lib/python3.8
/site-packages/django/core/management/base.py", line 369, in execute
output = self.handle(*args, **options)
File "/home/rodrigo/Workspace/fgv/sacvbackend/src/venv/lib/python3.8
/site-packages/django/core/management/base.py", line 83, in wrapped
res = handle_func(*args, **kwargs)
File "/home/rodrigo/Workspace/fgv/sacvbackend/src/venv/lib/python3.8
/site-packages/django/core/management/commands/migrate.py", line 231, in
handle
post_migrate_state = executor.migrate(
File "/home/rodrigo/Workspace/fgv/sacvbackend/src/venv/lib/python3.8
/site-packages/django/db/migrations/executor.py", line 117, in migrate
state = self._migrate_all_forwards(state, plan, full_plan, fake=fake,
fake_initial=fake_initial)
File "/home/rodrigo/Workspace/fgv/sacvbackend/src/venv/lib/python3.8
/site-packages/django/db/migrations/executor.py", line 147, in
_migrate_all_forwards
state = self.apply_migration(state, migration, fake=fake,
fake_initial=fake_initial)
File "/home/rodrigo/Workspace/fgv/sacvbackend/src/venv/lib/python3.8
/site-packages/django/db/migrations/executor.py", line 245, in
apply_migration
state = migration.apply(state, schema_editor)
File "/home/rodrigo/Workspace/fgv/sacvbackend/src/venv/lib/python3.8
/site-packages/django/db/migrations/migration.py", line 124, in apply
operation.database_forwards(self.app_label, schema_editor, old_state,
project_state)
File "/home/rodrigo/Workspace/fgv/sacvbackend/src/venv/lib/python3.8
/site-packages/django/db/migrations/operations/fields.py", line 110, in
database_forwards
schema_editor.add_field(
File "/home/rodrigo/Workspace/fgv/sacvbackend/src/venv/lib/python3.8
/site-packages/django/db/backends/base/schema.py", line 480, in add_field
self.execute(sql, params)
File "/home/rodrigo/Workspace/fgv/sacvbackend/src/venv/lib/python3.8
/site-packages/django/db/backends/base/schema.py", line 142, in execute
cursor.execute(sql, params)
File "/home/rodrigo/Workspace/fgv/sacvbackend/src/venv/lib/python3.8
/site-packages/django/db/backends/utils.py", line 100, in execute
return super().execute(sql, params)
File "/home/rodrigo/Workspace/fgv/sacvbackend/src/venv/lib/python3.8
/site-packages/django/db/backends/utils.py", line 68, in execute
return self._execute_with_wrappers(sql, params, many=False,
executor=self._execute)
File "/home/rodrigo/Workspace/fgv/sacvbackend/src/venv/lib/python3.8
/site-packages/django/db/backends/utils.py", line 77, in
_execute_with_wrappers
return executor(sql, params, many, context)
File "/home/rodrigo/Workspace/fgv/sacvbackend/src/venv/lib/python3.8
/site-packages/django/db/backends/utils.py", line 86, in _execute
return self.cursor.execute(sql, params)
File "/home/rodrigo/Workspace/fgv/sacvbackend/src/venv/lib/python3.8
/site-packages/django/db/utils.py", line 90, in __exit__
raise dj_exc_value.with_traceback(traceback) from exc_value
File "/home/rodrigo/Workspace/fgv/sacvbackend/src/venv/lib/python3.8
/site-packages/django/db/backends/utils.py", line 86, in _execute
return self.cursor.execute(sql, params)
django.db.utils.ProgrammingError: constraint
"data_load_valuation_id_9af1ae91_fk_valuation_id" does not exist
Error when trying create and run Django migrations!
Failed! The script execution has finished with errors!
}}}
If I just remove the intermediate table from DataLoad model like following
and run the makemigration command ...
{{{
class DataLoad(models.Model):
valuation = models.ForeignKey(
to=Valuation,
verbose_name=_('valuation'),
related_name='dataloads',
on_delete=models.PROTECT
)
datafile = models.ManyToManyField(
to=DataFile,
related_name='dataloads',
# through='dataload.DataLoadFile',
# through_fields=('dataload', 'datafile',)
)
}}}
... the resulting migration file is the following ...
{{{
# Generated by Django 3.0.7 on 2020-06-18 16:26
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('datafile', '0001_initial'),
('valuation', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='DataLoad',
fields=[
('id', models.AutoField(auto_created=True,
primary_key=True, serialize=False, verbose_name='ID')),
('datafile',
models.ManyToManyField(related_name='dataloads', to='datafile.DataFile')),
('valuation',
models.ForeignKey(on_delete=django.db.models.deletion.PROTECT,
related_name='dataloads', to='valuation.Valuation',
verbose_name='valuation')),
],
options={
'verbose_name': 'data_load',
'verbose_name_plural': 'data_load',
'db_table': 'loader"."data_load',
},
),
migrations.CreateModel(
name='DataLoadFile',
fields=[
('id', models.AutoField(auto_created=True,
primary_key=True, serialize=False, verbose_name='ID')),
('datafile',
models.ForeignKey(on_delete=django.db.models.deletion.PROTECT,
related_name='+', to='datafile.DataFile', verbose_name='data file')),
('dataload',
models.ForeignKey(on_delete=django.db.models.deletion.PROTECT,
related_name='datafiles', to='dataload.DataLoad', verbose_name='data
load')),
],
options={
'verbose_name': 'data load file',
'verbose_name_plural': 'data load files',
'db_table': 'loader"."data_load_files',
},
),
]
}}}
And the migration runs pretty fine, without any error.
Went through Google and I tried on Django Users group but I couldn't have
any answer about it. Thus, I do believe it could be a bug.
--
Ticket URL: <https://code.djangoproject.com/ticket/31735>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
--
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/django-updates/057.166987c8a7853f1b5cf33b38e99cc5ee%40djangoproject.com.