diff --git a/django/db/models/fields/related_descriptors.py b/django/db/models/fields/related_descriptors.py index 2633de33a7b..eae3a9628d6 100644 --- a/django/db/models/fields/related_descriptors.py +++ b/django/db/models/fields/related_descriptors.py @@ -193,14 +193,8 @@ class ForwardManyToOneDescriptor(object): - ``instance`` is the ``child`` instance - ``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 - # to be an instance of the related class. - 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): + # An object must be an instance of the related class. + if value is not None and not isinstance(value, self.field.remote_field.model._meta.concrete_model): raise ValueError( 'Cannot assign "%r": "%s.%s" must be a "%s" instance.' % ( value, @@ -379,26 +373,17 @@ class ReverseOneToOneDescriptor(object): # ForwardManyToOneDescriptor is annoying, but there's a bunch # 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 self.related.field.null: - # Update the cached related instance (if any) & clear the cache. - try: - rel_obj = getattr(instance, self.cache_name) - except AttributeError: - pass - else: - delattr(instance, self.cache_name) - setattr(rel_obj, self.related.field.name, None) + # Update the cached related instance (if any) & clear the cache. + try: + rel_obj = getattr(instance, self.cache_name) + except AttributeError: + pass else: - raise ValueError( - 'Cannot assign None: "%s.%s" does not allow null values.' % ( - instance._meta.object_name, - self.related.get_accessor_name(), - ) - ) + delattr(instance, self.cache_name) + setattr(rel_obj, self.related.field.name, None) elif not isinstance(value, self.related.related_model): + # An object must be an instance of the related class. raise ValueError( 'Cannot assign "%r": "%s.%s" must be a "%s" instance.' % ( value, diff --git a/docs/releases/1.10.txt b/docs/releases/1.10.txt index 009020444f4..c152c1dc780 100644 --- a/docs/releases/1.10.txt +++ b/docs/releases/1.10.txt @@ -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 `_ 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 ------------- diff --git a/tests/generic_relations/tests.py b/tests/generic_relations/tests.py index 436bb922224..806f8ca91a0 100644 --- a/tests/generic_relations/tests.py +++ b/tests/generic_relations/tests.py @@ -717,14 +717,11 @@ class ProxyRelatedModelTest(TestCase): 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): # AllowsNullGFK doesn't require a content_type, so None argument should # also be allowed. AllowsNullGFK(content_object=None) + # TaggedItem requires a content_type but initializing with None should + # be allowed. + TaggedItem(content_object=None) diff --git a/tests/many_to_one/tests.py b/tests/many_to_one/tests.py index e8210955ceb..c5a0e3f8f3e 100644 --- a/tests/many_to_one/tests.py +++ b/tests/many_to_one/tests.py @@ -3,6 +3,7 @@ from copy import deepcopy from django.core.exceptions import FieldError, MultipleObjectsReturned from django.db import models, transaction +from django.db.utils import IntegrityError from django.test import TestCase from django.utils import six from django.utils.deprecation import RemovedInDjango20Warning @@ -486,19 +487,19 @@ class ManyToOneTests(TestCase): p = Parent.objects.get(name="Parent") self.assertIsNone(p.bestchild) - # Assigning None fails: Child.parent is null=False. - with self.assertRaises(ValueError): - setattr(c, "parent", None) + # Assigning None will not fail: Child.parent is null=False. + setattr(c, "parent", None) # You also can't assign an object of the wrong type here with self.assertRaises(ValueError): setattr(c, "parent", First(id=1, second=1)) - # Nor can you explicitly assign None to Child.parent during object - # creation (regression for #9649). - with self.assertRaises(ValueError): - Child(name='xyzzy', parent=None) - with self.assertRaises(ValueError): + # You can assign None to Child.parent during object creation. + Child(name='xyzzy', parent=None) + + # But when trying to save a Child with parent=None, the database will + # raise IntegrityError. + with self.assertRaises(IntegrityError), transaction.atomic(): Child.objects.create(name='xyzzy', parent=None) # Creation using keyword argument should cache the related object. diff --git a/tests/one_to_one/tests.py b/tests/one_to_one/tests.py index 4e3ce072a4d..074a95f1b5f 100644 --- a/tests/one_to_one/tests.py +++ b/tests/one_to_one/tests.py @@ -228,9 +228,8 @@ class OneToOneTests(TestCase): ug_bar.place = None self.assertIsNone(ug_bar.place) - # Assigning None fails: Place.restaurant is null=False - with self.assertRaises(ValueError): - setattr(p, 'restaurant', None) + # Assigning None will not fail: Place.restaurant is null=False + setattr(p, 'restaurant', None) # You also can't assign an object of the wrong type here with self.assertRaises(ValueError):