#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 django-updates+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/django-updates/057.166987c8a7853f1b5cf33b38e99cc5ee%40djangoproject.com.