summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Wobrock <david.wobrock@gmail.com>2021-04-28 21:15:26 +0200
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2021-05-28 20:25:59 +0200
commitb9df2b74b98b4d63933e8061d3cfc1f6f39eb747 (patch)
treeef8464e74158f7f103bec75463f9177b891388d2
parentb746596f5f0e1fcac791b0f7c8bfc3d69dfef2ff (diff)
Fixed #32676 -- Prevented migrations from rendering related field attributes when not passed during initialization.
Thanks Simon Charette for the implementation idea.
-rw-r--r--django/db/models/fields/related.py42
-rw-r--r--docs/releases/4.0.txt16
-rw-r--r--tests/field_deconstruction/tests.py28
-rw-r--r--tests/schema/fields.py7
4 files changed, 80 insertions, 13 deletions
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index 899ae8efe8..a0e8da10fd 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -89,6 +89,18 @@ class RelatedField(FieldCacheMixin, Field):
many_to_many = False
many_to_one = False
+ def __init__(
+ self,
+ related_name=None,
+ related_query_name=None,
+ limit_choices_to=None,
+ **kwargs,
+ ):
+ self._related_name = related_name
+ self._related_query_name = related_query_name
+ self._limit_choices_to = limit_choices_to
+ super().__init__(**kwargs)
+
@cached_property
def related_model(self):
# Can't cache this property until all the models are loaded.
@@ -319,12 +331,12 @@ class RelatedField(FieldCacheMixin, Field):
def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
- if self.remote_field.limit_choices_to:
- kwargs['limit_choices_to'] = self.remote_field.limit_choices_to
- if self.remote_field.related_name is not None:
- kwargs['related_name'] = self.remote_field.related_name
- if self.remote_field.related_query_name is not None:
- kwargs['related_query_name'] = self.remote_field.related_query_name
+ if self._limit_choices_to:
+ kwargs['limit_choices_to'] = self._limit_choices_to
+ if self._related_name is not None:
+ kwargs['related_name'] = self._related_name
+ if self._related_query_name is not None:
+ kwargs['related_query_name'] = self._related_query_name
return name, path, args, kwargs
def get_forward_related_filter(self, obj):
@@ -471,7 +483,13 @@ class ForeignObject(RelatedField):
on_delete=on_delete,
)
- super().__init__(rel=rel, **kwargs)
+ super().__init__(
+ rel=rel,
+ related_name=related_name,
+ related_query_name=related_query_name,
+ limit_choices_to=limit_choices_to,
+ **kwargs,
+ )
self.from_fields = from_fields
self.to_fields = to_fields
@@ -825,6 +843,9 @@ class ForeignKey(ForeignObject):
super().__init__(
to,
on_delete,
+ related_name=related_name,
+ related_query_name=related_query_name,
+ limit_choices_to=limit_choices_to,
from_fields=[RECURSIVE_RELATIONSHIP_CONSTANT],
to_fields=[to_field],
**kwargs,
@@ -1174,7 +1195,12 @@ class ManyToManyField(RelatedField):
)
self.has_null_arg = 'null' in kwargs
- super().__init__(**kwargs)
+ super().__init__(
+ related_name=related_name,
+ related_query_name=related_query_name,
+ limit_choices_to=limit_choices_to,
+ **kwargs,
+ )
self.db_table = db_table
self.swappable = swappable
diff --git a/docs/releases/4.0.txt b/docs/releases/4.0.txt
index 831bb9b854..eecbc9e4d8 100644
--- a/docs/releases/4.0.txt
+++ b/docs/releases/4.0.txt
@@ -387,6 +387,18 @@ custom middleware::
.. _Content-Security-Policy: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
+Migrations autodetector changes
+-------------------------------
+
+The migrations autodetector now uses model states instead of model classes.
+Also, migration operations for ``ForeignKey`` and ``ManyToManyField`` fields no
+longer specify attributes which were not passed to the fields during
+initialization.
+
+As a side-effect, running ``makemigrations`` might generate no-op
+``AlterField`` operations for ``ManyToManyField`` and ``ForeignKey`` fields in
+some cases.
+
Miscellaneous
-------------
@@ -422,10 +434,6 @@ Miscellaneous
* Tests that fail to load, for example due to syntax errors, now always match
when using :option:`test --tag`.
-* The migrations autodetector now uses model states instead of model classes.
- As a side-effect ``makemigrations`` might generate no-op ``AlterField``
- operations for ``ForeignKey`` fields in some cases.
-
* The undocumented ``django.contrib.admin.utils.lookup_needs_distinct()``
function is renamed to ``lookup_spawns_duplicates()``.
diff --git a/tests/field_deconstruction/tests.py b/tests/field_deconstruction/tests.py
index b746e46458..846f6d3ed7 100644
--- a/tests/field_deconstruction/tests.py
+++ b/tests/field_deconstruction/tests.py
@@ -432,6 +432,34 @@ class FieldDeconstructionTests(SimpleTestCase):
self.assertEqual(kwargs, {"to": "auth.Permission"})
self.assertEqual(kwargs['to'].setting_name, "AUTH_USER_MODEL")
+ def test_many_to_many_field_related_name(self):
+ class MyModel(models.Model):
+ flag = models.BooleanField(default=True)
+ m2m = models.ManyToManyField('self')
+ m2m_related_name = models.ManyToManyField(
+ 'self',
+ related_name='custom_name',
+ related_query_name='custom_query_name',
+ limit_choices_to={'flag': True},
+ )
+
+ name, path, args, kwargs = MyModel.m2m.field.deconstruct()
+ self.assertEqual(path, 'django.db.models.ManyToManyField')
+ self.assertEqual(args, [])
+ # deconstruct() should not include attributes which were not passed to
+ # the field during initialization.
+ self.assertEqual(kwargs, {'to': 'field_deconstruction.MyModel'})
+ # Passed attributes.
+ name, path, args, kwargs = MyModel.m2m_related_name.field.deconstruct()
+ self.assertEqual(path, 'django.db.models.ManyToManyField')
+ self.assertEqual(args, [])
+ self.assertEqual(kwargs, {
+ 'to': 'field_deconstruction.MyModel',
+ 'related_name': 'custom_name',
+ 'related_query_name': 'custom_query_name',
+ 'limit_choices_to': {'flag': True},
+ })
+
def test_positive_integer_field(self):
field = models.PositiveIntegerField()
name, path, args, kwargs = field.deconstruct()
diff --git a/tests/schema/fields.py b/tests/schema/fields.py
index e4b62eab39..aaba202364 100644
--- a/tests/schema/fields.py
+++ b/tests/schema/fields.py
@@ -34,7 +34,12 @@ class CustomManyToManyField(RelatedField):
self.db_table = db_table
if kwargs['rel'].through is not None:
assert self.db_table is None, "Cannot specify a db_table if an intermediary model is used."
- super().__init__(**kwargs)
+ super().__init__(
+ related_name=related_name,
+ related_query_name=related_query_name,
+ limit_choices_to=limit_choices_to,
+ **kwargs,
+ )
def contribute_to_class(self, cls, name, **kwargs):
if self.remote_field.symmetrical and (