Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed #32294 -- Prevented ManyToManyField's hidden related name collisions between apps. #13822

Merged
merged 1 commit into from Dec 30, 2020

Conversation

manav014
Copy link
Contributor

ticket-32294

I have added app_label in ManyToManyField related_name for related_name values be "+" or "foo+"

@@ -1614,8 +1614,7 @@ def contribute_to_class(self, cls, name, **kwargs):
# related_name with one generated from the m2m field name. Django
# still uses backwards relations internally and we need to avoid
# clashes between multiple m2m fields with related_name == '+'.
self.remote_field.related_name = "_%s_%s_+" % (cls.__name__.lower(), name)

self.remote_field.related_name = "_%s_%s_%s_+" % (cls._meta.app_label, cls.__name__.lower(), name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find the leading and trailing underscore a little surprising here. PR #4382 originally introduced this code and doesn't really explain why either. 🤔

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because these are names that are only used internally by Django.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've noticed that these "internal" names leak into migrations. Making this change will cause migrations to be generated when users upgrade.

I guess that is inevitable to fix this, but perhaps the migrations need to be fixed to revert to related_name='+' when they are generated?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've noticed that these "internal" names leak into migrations. Making this change will cause migrations to be generated when users upgrade.

Are you sure? I didn't notice this in a sample project.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example:

class OtherModel(models.Model):
    pass


class OtherModelM2M(models.Model):
    m2m = models.ManyToManyField(OtherModel, related_name='+')

makemigrations didn't detect any changes with this PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. I can confirm that it doesn't produce migrations. But it does mean that the value in existing migrations will be "wrong". I generated these operations and checked that the change didn't produce new migrations:

    operations = [
        migrations.CreateModel(
            name='Author',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
            ],
        ),
        migrations.CreateModel(
            name='Publisher',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
            ],
        ),
        migrations.CreateModel(
            name='Book',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('authors', models.ManyToManyField(related_name='_book_authors_+', to='book.Author')),
                ('publisher', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='book.publisher')),
            ],
        ),
    ]

If I remove and regenerate the migrations I get the following:

    operations = [
        migrations.CreateModel(
            name='Author',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
            ],
        ),
        migrations.CreateModel(
            name='Publisher',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
            ],
        ),
        migrations.CreateModel(
            name='Book',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('authors', models.ManyToManyField(related_name='_book_book_authors_+', to='book.Author')),
                ('publisher', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='book.publisher')),
            ],
        ),
    ]

So '_book_authors_+' changed to '_book_book_authors_+' when I regenerated from scratch. It doesn't seem so nice that this "internal" stuff "leaks" into migrations. Perhaps when creating migrations we should ensure the value of related_name is forced to '+' if it ends with '+'?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps when creating migrations we should ensure the value of related_name is forced to '+' if it ends with '+'?

We could edit the following in RelatedField.deconstruct() to undo the changes made in ManyToManyField.contribute_to_class():

if self.remote_field.related_name is not None:
kwargs['related_name'] = self.remote_field.related_name

Or handle it specially in ManyToManyField.deconstruct().

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pope1ni Sure, but that's a separate issue not related with this patch.

@felixxm felixxm self-assigned this Dec 30, 2020
@felixxm felixxm changed the title Fixed #32294 -- Added app_label in ManyToManyField related_name Fixed #32294 -- Prevented ManyToManyField's hidden related name collisions between apps. Dec 30, 2020
@felixxm
Copy link
Member

felixxm commented Dec 30, 2020

@manav014 Thanks 👍 I added a test.

@felixxm felixxm merged commit a9a7421 into django:master Dec 30, 2020
@manav014 manav014 deleted the ticket_32294 branch December 30, 2020 20:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants