Преглед изворни кода

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

ZachLiuGIS пре 9 година
родитељ
комит
04e13c8913

+ 10 - 25
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,

+ 8 - 0
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 <http://python-rq.org/docs/workers/>`_
 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
 -------------
 

+ 3 - 6
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)

+ 9 - 8
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.

+ 2 - 3
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):