1
0
mirror of https://github.com/django/django.git synced 2024-12-22 17:16:24 +00:00

Refs #28147 -- Fixed setting of OneToOne and Foreign Key fields to None when using attnames.

Regression in 519016e5f2.
This commit is contained in:
Jon Dufresne 2019-07-23 05:04:06 -07:00 committed by Mariusz Felisiak
parent 619c9a4f49
commit 4122d9d3f1
5 changed files with 37 additions and 4 deletions

View File

@ -19,9 +19,9 @@ from django.utils.translation import gettext_lazy as _
from . import Field from . import Field
from .mixins import FieldCacheMixin from .mixins import FieldCacheMixin
from .related_descriptors import ( from .related_descriptors import (
ForwardManyToOneDescriptor, ForwardOneToOneDescriptor, ForeignKeyDeferredAttribute, ForwardManyToOneDescriptor,
ManyToManyDescriptor, ReverseManyToOneDescriptor, ForwardOneToOneDescriptor, ManyToManyDescriptor,
ReverseOneToOneDescriptor, ReverseManyToOneDescriptor, ReverseOneToOneDescriptor,
) )
from .related_lookups import ( from .related_lookups import (
RelatedExact, RelatedGreaterThan, RelatedGreaterThanOrEqual, RelatedIn, RelatedExact, RelatedGreaterThan, RelatedGreaterThanOrEqual, RelatedIn,
@ -764,7 +764,7 @@ class ForeignKey(ForeignObject):
By default ForeignKey will target the pk of the remote model but this By default ForeignKey will target the pk of the remote model but this
behavior can be changed by using the ``to_field`` argument. behavior can be changed by using the ``to_field`` argument.
""" """
descriptor_class = ForeignKeyDeferredAttribute
# Field flags # Field flags
many_to_many = False many_to_many = False
many_to_one = True many_to_one = True

View File

@ -67,9 +67,17 @@ from django.core.exceptions import FieldError
from django.db import connections, router, transaction from django.db import connections, router, transaction
from django.db.models import Q, signals from django.db.models import Q, signals
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from django.db.models.query_utils import DeferredAttribute
from django.utils.functional import cached_property 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: class ForwardManyToOneDescriptor:
""" """
Accessor to the related object on the forward side of a many-to-one or Accessor to the related object on the forward side of a many-to-one or

View File

@ -490,6 +490,10 @@ Miscellaneous
* ``intword`` template filter now translates ``1.0`` as a singular phrase and * ``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. 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: .. _deprecated-features-3.0:
Features deprecated in 3.0 Features deprecated in 3.0

View File

@ -168,6 +168,18 @@ class ManyToOneTests(TestCase):
parent.bestchild_id = child2.pk parent.bestchild_id = child2.pk
self.assertTrue(Parent.bestchild.is_cached(parent)) 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): def test_selects(self):
self.r.article_set.create(headline="John's second story", pub_date=datetime.date(2005, 7, 29)) 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)) self.r2.article_set.create(headline="Paul's story", pub_date=datetime.date(2006, 1, 17))

View File

@ -217,6 +217,15 @@ class OneToOneTests(TestCase):
b.place_id = self.p2.pk b.place_id = self.p2.pk
self.assertTrue(UndergroundBar.place.is_cached(b)) 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): def test_related_object_cache(self):
""" Regression test for #6886 (the related-object cache) """ """ Regression test for #6886 (the related-object cache) """