summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJon Dufresne <jon.dufresne@gmail.com>2019-07-23 05:04:06 -0700
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2019-07-27 12:04:56 +0200
commit4122d9d3f1983eea612f236e941d937bd8589a0d (patch)
tree8b13542116159590bc5fab124574e6b8038de814
parent619c9a4f49a1f0d9b53a732ae8c5132e32a6ce1c (diff)
Refs #28147 -- Fixed setting of OneToOne and Foreign Key fields to None when using attnames.
Regression in 519016e5f25d7c0a040015724f9920581551cab0.
-rw-r--r--django/db/models/fields/related.py8
-rw-r--r--django/db/models/fields/related_descriptors.py8
-rw-r--r--docs/releases/3.0.txt4
-rw-r--r--tests/many_to_one/tests.py12
-rw-r--r--tests/one_to_one/tests.py9
5 files changed, 37 insertions, 4 deletions
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index 56cee83a5e..f6c5ae2585 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -19,9 +19,9 @@ from django.utils.translation import gettext_lazy as _
from . import Field
from .mixins import FieldCacheMixin
from .related_descriptors import (
- ForwardManyToOneDescriptor, ForwardOneToOneDescriptor,
- ManyToManyDescriptor, ReverseManyToOneDescriptor,
- ReverseOneToOneDescriptor,
+ ForeignKeyDeferredAttribute, ForwardManyToOneDescriptor,
+ ForwardOneToOneDescriptor, ManyToManyDescriptor,
+ ReverseManyToOneDescriptor, ReverseOneToOneDescriptor,
)
from .related_lookups import (
RelatedExact, RelatedGreaterThan, RelatedGreaterThanOrEqual, RelatedIn,
@@ -764,7 +764,7 @@ class ForeignKey(ForeignObject):
By default ForeignKey will target the pk of the remote model but this
behavior can be changed by using the ``to_field`` argument.
"""
-
+ descriptor_class = ForeignKeyDeferredAttribute
# Field flags
many_to_many = False
many_to_one = True
diff --git a/django/db/models/fields/related_descriptors.py b/django/db/models/fields/related_descriptors.py
index 886e99e190..0b51b68ebe 100644
--- a/django/db/models/fields/related_descriptors.py
+++ b/django/db/models/fields/related_descriptors.py
@@ -67,9 +67,17 @@ from django.core.exceptions import FieldError
from django.db import connections, router, transaction
from django.db.models import Q, signals
from django.db.models.query import QuerySet
+from django.db.models.query_utils import DeferredAttribute
from django.utils.functional import cached_property
+class ForeignKeyDeferredAttribute(DeferredAttribute):
+ def __set__(self, instance, value):
+ if instance.__dict__.get(self.field.attname) != value and self.field.is_cached(instance):
+ self.field.delete_cached_value(instance)
+ instance.__dict__[self.field.attname] = value
+
+
class ForwardManyToOneDescriptor:
"""
Accessor to the related object on the forward side of a many-to-one or
diff --git a/docs/releases/3.0.txt b/docs/releases/3.0.txt
index 07a2396c80..30f70e0bcf 100644
--- a/docs/releases/3.0.txt
+++ b/docs/releases/3.0.txt
@@ -490,6 +490,10 @@ Miscellaneous
* ``intword`` template filter now translates ``1.0`` as a singular phrase and
all other numeric values as plural. This may be incorrect for some languages.
+* Assigning a value to a model's :class:`~django.db.models.ForeignKey` or
+ :class:`~django.db.models.OneToOneField` ``'_id'`` attribute now unsets the
+ corresponding field. Accessing the field afterwards will result in a query.
+
.. _deprecated-features-3.0:
Features deprecated in 3.0
diff --git a/tests/many_to_one/tests.py b/tests/many_to_one/tests.py
index a5215e58d7..e3121a7701 100644
--- a/tests/many_to_one/tests.py
+++ b/tests/many_to_one/tests.py
@@ -168,6 +168,18 @@ class ManyToOneTests(TestCase):
parent.bestchild_id = child2.pk
self.assertTrue(Parent.bestchild.is_cached(parent))
+ def test_assign_fk_id_none(self):
+ parent = Parent.objects.create(name='jeff')
+ child = Child.objects.create(name='frank', parent=parent)
+ parent.bestchild = child
+ parent.save()
+ parent.bestchild_id = None
+ parent.save()
+ self.assertIsNone(parent.bestchild_id)
+ self.assertFalse(Parent.bestchild.is_cached(parent))
+ self.assertIsNone(parent.bestchild)
+ self.assertTrue(Parent.bestchild.is_cached(parent))
+
def test_selects(self):
self.r.article_set.create(headline="John's second story", pub_date=datetime.date(2005, 7, 29))
self.r2.article_set.create(headline="Paul's story", pub_date=datetime.date(2006, 1, 17))
diff --git a/tests/one_to_one/tests.py b/tests/one_to_one/tests.py
index 78b3ff1135..1215cf8001 100644
--- a/tests/one_to_one/tests.py
+++ b/tests/one_to_one/tests.py
@@ -217,6 +217,15 @@ class OneToOneTests(TestCase):
b.place_id = self.p2.pk
self.assertTrue(UndergroundBar.place.is_cached(b))
+ def test_assign_o2o_id_none(self):
+ b = UndergroundBar.objects.create(place=self.p1)
+ b.place_id = None
+ b.save()
+ self.assertIsNone(b.place_id)
+ self.assertFalse(UndergroundBar.place.is_cached(b))
+ self.assertIsNone(b.place)
+ self.assertTrue(UndergroundBar.place.is_cached(b))
+
def test_related_object_cache(self):
""" Regression test for #6886 (the related-object cache) """