Opened 4 years ago

Last modified 3 years ago

#31788 closed Bug

Models migration with change field foreign to many and deleting unique together. — at Version 4

Reported by: budzichd Owned by: nobody
Component: Migrations Version: 3.0
Severity: Normal Keywords:
Cc: Markus Holtermann, Simon Charette, David Wobrock Triage Stage: Ready for checkin
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description (last modified by Simon Charette)

I have models like

class Authors(models.Model):
    project_data_set = models.ForeignKey(
        ProjectDataSet,
        on_delete=models.PROTECT
    )
    state = models.IntegerField()
    start_date = models.DateField()

    class Meta:
         unique_together = (('project_data_set', 'state', 'start_date'),)

and

class DataSet(models.Model):
    name = models.TextField(max_length=50)


class Project(models.Model):
    data_sets = models.ManyToManyField(
        DataSet,
        through='ProjectDataSet',
    )
    name = models.TextField(max_length=50)


class ProjectDataSet(models.Model):
    """
    Cross table of data set and project
    """
    data_set = models.ForeignKey(DataSet, on_delete=models.PROTECT)
    project = models.ForeignKey(Project, on_delete=models.PROTECT)

    class Meta:
        unique_together = (('data_set', 'project'),)

when i want to change field project_data_set in Authors model from foreign key field to many to many field I must delete a unique_together, cause it can't be on many to many field.
Then my model should be like:

class Authors(models.Model):

    project_data_set = models.ManyToManyField(
        ProjectDataSet,
    )

    state = models.IntegerField()
    start_date = models.DateField()

But when I want to do a migrations.

  1. python3 manage.py makemigrations
  2. python3 manage.py migrate

I have error:
ValueError: Found wrong number (0) of constraints for app_authors(project_data_set, state, start_date)
The database is on production, so I can't delete previous initial migrations, and this error isn't depending on database, cause I delete it and error is still the same.
My solve is to first delete unique_together, then do a makemigrations and then migrate. After that change the field from foreign key to many to many field, then do a makemigrations and then migrate.
But in this way I have 2 migrations instead of one.

I added attachment with this project, download it and then do makemigrations and then migrate to see this error.

Change History (5)

by budzichd, 4 years ago

Attachment: ticket-django.zip added

Download this file and then do makemigrations and migrate to see this error.

comment:1 by budzichd, 4 years ago

Description: modified (diff)

comment:2 by budzichd, 4 years ago

Description: modified (diff)

comment:3 by Mariusz Felisiak, 4 years ago

Cc: Markus Holtermann Simon Charette added
Triage Stage: UnreviewedAccepted

Thanks for the report. Tentatively accepting, however I'm not sure if we can sort these operations properly, we should probably alter unique_together first

        migrations.AlterUniqueTogether(
            name='authors',
            unique_together=set(),
        ),
        migrations.RemoveField(
            model_name='authors',
            name='project_data_set',
        ),
        migrations.AddField(
            model_name='authors',
            name='project_data_set',
            field=models.ManyToManyField(to='dict.ProjectDataSet'),
        ),

You should take into account that you'll lose all data because ForeignKey cannot be altered to ManyToManyField.

comment:4 by Simon Charette, 4 years ago

Description: modified (diff)

I agree that you'll loose data but Alter(Index|Unique)Together should always be sorted before RemoveField

https://github.com/django/django/blob/b502061027b90499f2e20210f944292cecd74d24/django/db/migrations/autodetector.py#L910
https://github.com/django/django/blob/b502061027b90499f2e20210f944292cecd74d24/django/db/migrations/autodetector.py#L424-L430

So something's broken here in a few different ways and I suspect it's due to the fact the same field name project_data_set is reused for the many-to-many field.

If you start from

class Authors(models.Model):
    project_data_set = models.ForeignKey(
        ProjectDataSet,
        on_delete=models.PROTECT
    )
    state = models.IntegerField()
    start_date = models.DateField()

    class Meta:
         unique_together = (('project_data_set', 'state', 'start_date'),)

And generate makemigrations for

class Authors(models.Model):
    project_data_set = models.ManyToManyField(ProjectDataSet)
    state = models.IntegerField()
    start_date = models.DateField()

You'll get two migrations with the following operations

# 0002
operations = [ 
    migrations.AddField(
        model_name='authors',
        name='project_data_set',
        field=models.ManyToManyField(to='ticket_31788.ProjectDataSet'),
    ),
    migrations.AlterUniqueTogether(
        name='authors',
        unique_together=set(),
    ),
    migrations.RemoveField(
        model_name='authors',
        name='project_data_set',
    ),
]

# 0003
operations = [ 
    migrations.AddField(
        model_name='authors',
        name='project_data_set',
        field=models.ManyToManyField(to='ticket_31788.ProjectDataSet'),
    ),
]

If you change the name of the field to something else like project_data_sets every work as expected

operations = [ 
    migrations.AddField(
        model_name='authors',
        name='project_data_sets',
        field=models.ManyToManyField(to='ticket_31788.ProjectDataSet'),
    ),
    migrations.AlterUniqueTogether(
         name='authors',
         unique_together=set(),
    ),
    migrations.RemoveField(
        model_name='authors',
        name='project_data_set',
    ),
]

It seems like there's some bad interactions between generate_removed_fields and generate_added_fields when a field with the same name is added.

Last edited 4 years ago by Simon Charette (previous) (diff)
Note: See TracTickets for help on using tickets.
Back to Top