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
Conversation
django/db/models/fields/related.py
Outdated
@@ -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) |
There was a problem hiding this comment.
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. 🤔
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 '+'
?
There was a problem hiding this comment.
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()
:
django/django/db/models/fields/related.py
Lines 324 to 325 in 74fd233
if self.remote_field.related_name is not None: | |
kwargs['related_name'] = self.remote_field.related_name |
Or handle it specially in ManyToManyField.deconstruct()
.
There was a problem hiding this comment.
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.
…sions between apps.
0b148fc
to
a9a7421
Compare
@manav014 Thanks 👍 I added a test. |
ticket-32294
I have added
app_label
in ManyToManyField related_name for related_name values be "+
" or "foo+
"