Fixed #26179 -- Removed null assignment check for non-nullable foreign key fields.

This commit is contained in:
ZachLiuGIS 2016-02-10 20:46:57 -05:00 committed by Tim Graham
parent 353aecbf8c
commit 04e13c8913
5 changed files with 32 additions and 42 deletions

View File

@ -193,14 +193,8 @@ class ForwardManyToOneDescriptor(object):
- ``instance`` is the ``child`` instance - ``instance`` is the ``child`` instance
- ``value`` in the ``parent`` instance on the right of the equal sign - ``value`` in the ``parent`` instance on the right of the equal sign
""" """
# If null=True, we can assign null here, but otherwise the value needs # An object must be an instance of the related class.
# to be an instance of the related class. if value is not None and not isinstance(value, self.field.remote_field.model._meta.concrete_model):
if value is None and self.field.null is False:
raise ValueError(
'Cannot assign None: "%s.%s" does not allow null values.' %
(instance._meta.object_name, self.field.name)
)
elif value is not None and not isinstance(value, self.field.remote_field.model._meta.concrete_model):
raise ValueError( raise ValueError(
'Cannot assign "%r": "%s.%s" must be a "%s" instance.' % ( 'Cannot assign "%r": "%s.%s" must be a "%s" instance.' % (
value, value,
@ -379,26 +373,17 @@ class ReverseOneToOneDescriptor(object):
# ForwardManyToOneDescriptor is annoying, but there's a bunch # ForwardManyToOneDescriptor is annoying, but there's a bunch
# of small differences that would make a common base class convoluted. # of small differences that would make a common base class convoluted.
# If null=True, we can assign null here, but otherwise the value needs
# to be an instance of the related class.
if value is None: if value is None:
if self.related.field.null: # Update the cached related instance (if any) & clear the cache.
# Update the cached related instance (if any) & clear the cache. try:
try: rel_obj = getattr(instance, self.cache_name)
rel_obj = getattr(instance, self.cache_name) except AttributeError:
except AttributeError: pass
pass
else:
delattr(instance, self.cache_name)
setattr(rel_obj, self.related.field.name, None)
else: else:
raise ValueError( delattr(instance, self.cache_name)
'Cannot assign None: "%s.%s" does not allow null values.' % ( setattr(rel_obj, self.related.field.name, None)
instance._meta.object_name,
self.related.get_accessor_name(),
)
)
elif not isinstance(value, self.related.related_model): elif not isinstance(value, self.related.related_model):
# An object must be an instance of the related class.
raise ValueError( raise ValueError(
'Cannot assign "%r": "%s.%s" must be a "%s" instance.' % ( 'Cannot assign "%r": "%s.%s" must be a "%s" instance.' % (
value, value,

View File

@ -479,6 +479,14 @@ creates the possibility of a deadlock. To adapt your code in the case of RQ,
you can `provide your own worker script <http://python-rq.org/docs/workers/>`_ you can `provide your own worker script <http://python-rq.org/docs/workers/>`_
that calls ``django.setup()``. that calls ``django.setup()``.
Removed null assignment check for non-null foreign key fields
-------------------------------------------------------------
In older versions, assigning ``None`` to a non-nullable ``ForeignKey`` or
``OneToOneField`` raised ``ValueError('Cannot assign None: "model.field" does
not allow null values.')``. For consistency with other model fields which don't
have a similar check, this check is removed.
Miscellaneous Miscellaneous
------------- -------------

View File

@ -717,14 +717,11 @@ class ProxyRelatedModelTest(TestCase):
class TestInitWithNoneArgument(SimpleTestCase): class TestInitWithNoneArgument(SimpleTestCase):
def test_none_not_allowed(self):
# TaggedItem requires a content_type, initializing with None should
# raise a ValueError.
msg = 'Cannot assign None: "TaggedItem.content_type" does not allow null values'
with self.assertRaisesMessage(ValueError, msg):
TaggedItem(content_object=None)
def test_none_allowed(self): def test_none_allowed(self):
# AllowsNullGFK doesn't require a content_type, so None argument should # AllowsNullGFK doesn't require a content_type, so None argument should
# also be allowed. # also be allowed.
AllowsNullGFK(content_object=None) AllowsNullGFK(content_object=None)
# TaggedItem requires a content_type but initializing with None should
# be allowed.
TaggedItem(content_object=None)

View File

@ -3,6 +3,7 @@ from copy import deepcopy
from django.core.exceptions import FieldError, MultipleObjectsReturned from django.core.exceptions import FieldError, MultipleObjectsReturned
from django.db import models, transaction from django.db import models, transaction
from django.db.utils import IntegrityError
from django.test import TestCase from django.test import TestCase
from django.utils import six from django.utils import six
from django.utils.deprecation import RemovedInDjango20Warning from django.utils.deprecation import RemovedInDjango20Warning
@ -486,19 +487,19 @@ class ManyToOneTests(TestCase):
p = Parent.objects.get(name="Parent") p = Parent.objects.get(name="Parent")
self.assertIsNone(p.bestchild) self.assertIsNone(p.bestchild)
# Assigning None fails: Child.parent is null=False. # Assigning None will not fail: Child.parent is null=False.
with self.assertRaises(ValueError): setattr(c, "parent", None)
setattr(c, "parent", None)
# You also can't assign an object of the wrong type here # You also can't assign an object of the wrong type here
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
setattr(c, "parent", First(id=1, second=1)) setattr(c, "parent", First(id=1, second=1))
# Nor can you explicitly assign None to Child.parent during object # You can assign None to Child.parent during object creation.
# creation (regression for #9649). Child(name='xyzzy', parent=None)
with self.assertRaises(ValueError):
Child(name='xyzzy', parent=None) # But when trying to save a Child with parent=None, the database will
with self.assertRaises(ValueError): # raise IntegrityError.
with self.assertRaises(IntegrityError), transaction.atomic():
Child.objects.create(name='xyzzy', parent=None) Child.objects.create(name='xyzzy', parent=None)
# Creation using keyword argument should cache the related object. # Creation using keyword argument should cache the related object.

View File

@ -228,9 +228,8 @@ class OneToOneTests(TestCase):
ug_bar.place = None ug_bar.place = None
self.assertIsNone(ug_bar.place) self.assertIsNone(ug_bar.place)
# Assigning None fails: Place.restaurant is null=False # Assigning None will not fail: Place.restaurant is null=False
with self.assertRaises(ValueError): setattr(p, 'restaurant', None)
setattr(p, 'restaurant', None)
# You also can't assign an object of the wrong type here # You also can't assign an object of the wrong type here
with self.assertRaises(ValueError): with self.assertRaises(ValueError):