فهرست منبع

Refs #31369 -- Removed models.NullBooleanField per deprecation timeline.

Mariusz Felisiak 4 سال پیش
والد
کامیت
d992f4e3c2

+ 0 - 1
django/db/backends/mysql/base.py

@@ -119,7 +119,6 @@ class DatabaseWrapper(BaseDatabaseWrapper):
         'IPAddressField': 'char(15)',
         'GenericIPAddressField': 'char(39)',
         'JSONField': 'json',
-        'NullBooleanField': 'bool',
         'OneToOneField': 'integer',
         'PositiveBigIntegerField': 'bigint UNSIGNED',
         'PositiveIntegerField': 'integer UNSIGNED',

+ 1 - 1
django/db/backends/mysql/operations.py

@@ -291,7 +291,7 @@ class DatabaseOperations(BaseDatabaseOperations):
     def get_db_converters(self, expression):
         converters = super().get_db_converters(expression)
         internal_type = expression.output_field.get_internal_type()
-        if internal_type in ['BooleanField', 'NullBooleanField']:
+        if internal_type == 'BooleanField':
             converters.append(self.convert_booleanfield_value)
         elif internal_type == 'DateTimeField':
             if settings.USE_TZ:

+ 0 - 2
django/db/backends/oracle/base.py

@@ -127,7 +127,6 @@ class DatabaseWrapper(BaseDatabaseWrapper):
         'BigIntegerField': 'NUMBER(19)',
         'IPAddressField': 'VARCHAR2(15)',
         'GenericIPAddressField': 'VARCHAR2(39)',
-        'NullBooleanField': 'NUMBER(1)',
         'OneToOneField': 'NUMBER(11)',
         'PositiveBigIntegerField': 'NUMBER(19)',
         'PositiveIntegerField': 'NUMBER(11)',
@@ -143,7 +142,6 @@ class DatabaseWrapper(BaseDatabaseWrapper):
     data_type_check_constraints = {
         'BooleanField': '%(qn_column)s IN (0,1)',
         'JSONField': '%(qn_column)s IS JSON',
-        'NullBooleanField': '%(qn_column)s IN (0,1)',
         'PositiveBigIntegerField': '%(qn_column)s >= 0',
         'PositiveIntegerField': '%(qn_column)s >= 0',
         'PositiveSmallIntegerField': '%(qn_column)s >= 0',

+ 1 - 1
django/db/backends/oracle/operations.py

@@ -182,7 +182,7 @@ END;
             converters.append(self.convert_textfield_value)
         elif internal_type == 'BinaryField':
             converters.append(self.convert_binaryfield_value)
-        elif internal_type in ['BooleanField', 'NullBooleanField']:
+        elif internal_type == 'BooleanField':
             converters.append(self.convert_booleanfield_value)
         elif internal_type == 'DateTimeField':
             if settings.USE_TZ:

+ 0 - 1
django/db/backends/oracle/utils.py

@@ -73,7 +73,6 @@ class BulkInsertMapper:
         'DurationField': INTERVAL,
         'FloatField': NUMBER,
         'IntegerField': NUMBER,
-        'NullBooleanField': NUMBER,
         'PositiveBigIntegerField': NUMBER,
         'PositiveIntegerField': NUMBER,
         'PositiveSmallIntegerField': NUMBER,

+ 0 - 1
django/db/backends/postgresql/base.py

@@ -87,7 +87,6 @@ class DatabaseWrapper(BaseDatabaseWrapper):
         'IPAddressField': 'inet',
         'GenericIPAddressField': 'inet',
         'JSONField': 'jsonb',
-        'NullBooleanField': 'boolean',
         'OneToOneField': 'integer',
         'PositiveBigIntegerField': 'bigint',
         'PositiveIntegerField': 'integer',

+ 0 - 1
django/db/backends/sqlite3/base.py

@@ -104,7 +104,6 @@ class DatabaseWrapper(BaseDatabaseWrapper):
         'IPAddressField': 'char(15)',
         'GenericIPAddressField': 'char(39)',
         'JSONField': 'text',
-        'NullBooleanField': 'bool',
         'OneToOneField': 'integer',
         'PositiveBigIntegerField': 'bigint unsigned',
         'PositiveIntegerField': 'integer unsigned',

+ 1 - 1
django/db/backends/sqlite3/operations.py

@@ -277,7 +277,7 @@ class DatabaseOperations(BaseDatabaseOperations):
             converters.append(self.get_decimalfield_converter(expression))
         elif internal_type == 'UUIDField':
             converters.append(self.convert_uuidfield_value)
-        elif internal_type in ('NullBooleanField', 'BooleanField'):
+        elif internal_type == 'BooleanField':
             converters.append(self.convert_booleanfield_value)
         return converters
 

+ 4 - 4
django/db/models/fields/__init__.py

@@ -1987,13 +1987,13 @@ class NullBooleanField(BooleanField):
         'invalid_nullable': _('“%(value)s” value must be either None, True or False.'),
     }
     description = _("Boolean (Either True, False or None)")
-    system_check_deprecated_details = {
+    system_check_removed_details = {
         'msg': (
-            'NullBooleanField is deprecated. Support for it (except in '
-            'historical migrations) will be removed in Django 4.0.'
+            'NullBooleanField is removed except for support in historical '
+            'migrations.'
         ),
         'hint': 'Use BooleanField(null=True) instead.',
-        'id': 'fields.W903',
+        'id': 'fields.E903',
     }
 
     def __init__(self, *args, **kwargs):

+ 4 - 1
docs/ref/checks.txt

@@ -206,7 +206,10 @@ Model fields
 * **fields.W902**: ``FloatRangeField`` is deprecated and will be removed in
   Django 3.1. *This check appeared in Django 2.2 and 3.0*.
 * **fields.W903**: ``NullBooleanField`` is deprecated. Support for it (except
-  in historical migrations) will be removed in Django 4.0.
+  in historical migrations) will be removed in Django 4.0. *This check appeared
+  in Django 3.1 and 3.2*.
+* **fields.E903**: ``NullBooleanField`` is removed except for support in
+  historical migrations.
 * **fields.W904**: ``django.contrib.postgres.fields.JSONField`` is deprecated.
   Support for it (except in historical migrations) will be removed in Django
   4.0.

+ 0 - 11
docs/ref/models/fields.txt

@@ -1254,17 +1254,6 @@ To query ``JSONField`` in the database, see :ref:`querying-jsonfield`.
     objects and arrays (represented in Python using :py:class:`dict` and
     :py:class:`list`) are supported.
 
-``NullBooleanField``
---------------------
-
-.. class:: NullBooleanField(**options)
-
-Like :class:`BooleanField` with ``null=True``.
-
-.. deprecated:: 3.1
-
-    ``NullBooleanField`` is deprecated in favor of ``BooleanField(null=True)``.
-
 ``PositiveBigIntegerField``
 ---------------------------
 

+ 2 - 2
docs/releases/2.1.txt

@@ -189,8 +189,8 @@ Models
   now support using field transforms.
 
 * :class:`~django.db.models.BooleanField` can now be ``null=True``. This is
-  encouraged instead of :class:`~django.db.models.NullBooleanField`, which will
-  likely be deprecated in the future.
+  encouraged instead of ``NullBooleanField``, which will likely be deprecated
+  in the future.
 
 * The new :meth:`.QuerySet.explain` method displays the database's execution
   plan of a queryset's query.

+ 3 - 0
docs/releases/4.0.txt

@@ -305,3 +305,6 @@ to remove usage of these features.
 * The ``list`` message for ``ModelMultipleChoiceField`` is removed.
 
 * Support for passing raw column aliases to ``QuerySet.order_by()`` is removed.
+
+* The ``NullBooleanField`` model field is removed, except for support in
+  historical migrations.

+ 0 - 2
docs/topics/forms/modelforms.txt

@@ -106,8 +106,6 @@ Model field                         Form field
 :class:`ManyToManyField`            :class:`~django.forms.ModelMultipleChoiceField`
                                     (see below)
 
-:class:`NullBooleanField`           :class:`~django.forms.NullBooleanField`
-
 :class:`PositiveBigIntegerField`    :class:`~django.forms.IntegerField`
 
 :class:`PositiveIntegerField`       :class:`~django.forms.IntegerField`

+ 0 - 1
tests/admin_filters/models.py

@@ -29,7 +29,6 @@ class Book(models.Model):
         blank=True, null=True,
     )
     is_best_seller = models.BooleanField(default=0, null=True)
-    is_best_seller2 = models.NullBooleanField(default=0)
     date_registered = models.DateField(null=True)
     availability = models.BooleanField(choices=(
         (False, 'Paid'),

+ 4 - 60
tests/admin_filters/tests.py

@@ -144,10 +144,6 @@ class BookAdmin(ModelAdmin):
     ordering = ('-id',)
 
 
-class BookAdmin2(ModelAdmin):
-    list_filter = ('year', 'author', 'contributors', 'is_best_seller2', 'date_registered', 'no')
-
-
 class BookAdminWithTupleBooleanFilter(BookAdmin):
     list_filter = (
         'year',
@@ -289,22 +285,22 @@ class ListFiltersTests(TestCase):
         cls.djangonaut_book = Book.objects.create(
             title='Djangonaut: an art of living', year=2009,
             author=cls.alfred, is_best_seller=True, date_registered=cls.today,
-            is_best_seller2=True, availability=True,
+            availability=True,
         )
         cls.bio_book = Book.objects.create(
             title='Django: a biography', year=1999, author=cls.alfred,
             is_best_seller=False, no=207,
-            is_best_seller2=False, availability=False,
+            availability=False,
         )
         cls.django_book = Book.objects.create(
             title='The Django Book', year=None, author=cls.bob,
             is_best_seller=None, date_registered=cls.today, no=103,
-            is_best_seller2=None, availability=True,
+            availability=True,
         )
         cls.guitar_book = Book.objects.create(
             title='Guitar for dummies', year=2002, is_best_seller=True,
             date_registered=cls.one_week_ago,
-            is_best_seller2=True, availability=None,
+            availability=None,
         )
         cls.guitar_book.contributors.set([cls.bob, cls.lisa])
 
@@ -1014,58 +1010,6 @@ class ListFiltersTests(TestCase):
         self.assertIs(choice['selected'], True)
         self.assertEqual(choice['query_string'], '?')
 
-    def test_booleanfieldlistfilter_nullbooleanfield(self):
-        modeladmin = BookAdmin2(Book, site)
-
-        request = self.request_factory.get('/')
-        request.user = self.alfred
-        changelist = modeladmin.get_changelist_instance(request)
-
-        request = self.request_factory.get('/', {'is_best_seller2__exact': 0})
-        request.user = self.alfred
-        changelist = modeladmin.get_changelist_instance(request)
-
-        # Make sure the correct queryset is returned
-        queryset = changelist.get_queryset(request)
-        self.assertEqual(list(queryset), [self.bio_book])
-
-        # Make sure the correct choice is selected
-        filterspec = changelist.get_filters(request)[0][3]
-        self.assertEqual(filterspec.title, 'is best seller2')
-        choice = select_by(filterspec.choices(changelist), "display", "No")
-        self.assertIs(choice['selected'], True)
-        self.assertEqual(choice['query_string'], '?is_best_seller2__exact=0')
-
-        request = self.request_factory.get('/', {'is_best_seller2__exact': 1})
-        request.user = self.alfred
-        changelist = modeladmin.get_changelist_instance(request)
-
-        # Make sure the correct queryset is returned
-        queryset = changelist.get_queryset(request)
-        self.assertEqual(list(queryset), [self.guitar_book, self.djangonaut_book])
-
-        # Make sure the correct choice is selected
-        filterspec = changelist.get_filters(request)[0][3]
-        self.assertEqual(filterspec.title, 'is best seller2')
-        choice = select_by(filterspec.choices(changelist), "display", "Yes")
-        self.assertIs(choice['selected'], True)
-        self.assertEqual(choice['query_string'], '?is_best_seller2__exact=1')
-
-        request = self.request_factory.get('/', {'is_best_seller2__isnull': 'True'})
-        request.user = self.alfred
-        changelist = modeladmin.get_changelist_instance(request)
-
-        # Make sure the correct queryset is returned
-        queryset = changelist.get_queryset(request)
-        self.assertEqual(list(queryset), [self.django_book])
-
-        # Make sure the correct choice is selected
-        filterspec = changelist.get_filters(request)[0][3]
-        self.assertEqual(filterspec.title, 'is best seller2')
-        choice = select_by(filterspec.choices(changelist), "display", "Unknown")
-        self.assertIs(choice['selected'], True)
-        self.assertEqual(choice['query_string'], '?is_best_seller2__isnull=True')
-
     def test_fieldlistfilter_underscorelookup_tuple(self):
         """
         Ensure ('fieldpath', ClassName ) lookups pass lookup_allowed checks

+ 0 - 6
tests/admin_utils/tests.py

@@ -163,12 +163,6 @@ class UtilsTests(SimpleTestCase):
         display_value = display_for_field(None, models.TimeField(), self.empty_value)
         self.assertEqual(display_value, self.empty_value)
 
-        # Regression test for #13071: NullBooleanField has special
-        # handling.
-        display_value = display_for_field(None, models.NullBooleanField(), self.empty_value)
-        expected = '<img src="%sadmin/img/icon-unknown.svg" alt="None">' % settings.STATIC_URL
-        self.assertHTMLEqual(display_value, expected)
-
         display_value = display_for_field(None, models.BooleanField(null=True), self.empty_value)
         expected = '<img src="%sadmin/img/icon-unknown.svg" alt="None" />' % settings.STATIC_URL
         self.assertHTMLEqual(display_value, expected)

+ 2 - 4
tests/annotations/tests.py

@@ -4,8 +4,8 @@ from decimal import Decimal
 from django.core.exceptions import FieldDoesNotExist, FieldError
 from django.db.models import (
     BooleanField, Case, CharField, Count, DateTimeField, DecimalField, Exists,
-    ExpressionWrapper, F, FloatField, Func, IntegerField, Max,
-    NullBooleanField, OuterRef, Q, Subquery, Sum, Value, When,
+    ExpressionWrapper, F, FloatField, Func, IntegerField, Max, OuterRef, Q,
+    Subquery, Sum, Value, When,
 )
 from django.db.models.expressions import RawSQL
 from django.db.models.functions import (
@@ -641,14 +641,12 @@ class NonAggregateAnnotationTestCase(TestCase):
             is_book=Value(True, output_field=BooleanField()),
             is_pony=Value(False, output_field=BooleanField()),
             is_none=Value(None, output_field=BooleanField(null=True)),
-            is_none_old=Value(None, output_field=NullBooleanField()),
         )
         self.assertGreater(len(books), 0)
         for book in books:
             self.assertIs(book.is_book, True)
             self.assertIs(book.is_pony, False)
             self.assertIsNone(book.is_none)
-            self.assertIsNone(book.is_none_old)
 
     def test_annotation_in_f_grouped_by_annotation(self):
         qs = (

+ 2 - 2
tests/backends/oracle/tests.py

@@ -1,7 +1,7 @@
 import unittest
 
 from django.db import DatabaseError, connection
-from django.db.models import BooleanField, NullBooleanField
+from django.db.models import BooleanField
 from django.test import TransactionTestCase
 
 from ..models import Square
@@ -48,7 +48,7 @@ class Tests(unittest.TestCase):
 
     def test_boolean_constraints(self):
         """Boolean fields have check constraints on their values."""
-        for field in (BooleanField(), NullBooleanField(), BooleanField(null=True)):
+        for field in (BooleanField(), BooleanField(null=True)):
             with self.subTest(field=field):
                 field.set_attributes_from_name('is_nice')
                 self.assertIn('"IS_NICE" IN (0,1)', field.db_check(connection))

+ 0 - 1
tests/bulk_create/models.py

@@ -83,7 +83,6 @@ class NullableFields(models.Model):
     float_field = models.FloatField(null=True, default=3.2)
     integer_field = models.IntegerField(null=True, default=2)
     null_boolean_field = models.BooleanField(null=True, default=False)
-    null_boolean_field_old = models.NullBooleanField(null=True, default=False)
     positive_big_integer_field = models.PositiveBigIntegerField(null=True, default=2 ** 63 - 1)
     positive_integer_field = models.PositiveIntegerField(null=True, default=3)
     positive_small_integer_field = models.PositiveSmallIntegerField(null=True, default=4)

+ 0 - 1
tests/datatypes/models.py

@@ -10,7 +10,6 @@ class Donut(models.Model):
     name = models.CharField(max_length=100)
     is_frosted = models.BooleanField(default=False)
     has_sprinkles = models.BooleanField(null=True)
-    has_sprinkles_old = models.NullBooleanField()
     baked_date = models.DateField(null=True)
     baked_time = models.TimeField(null=True)
     consumed_at = models.DateTimeField(null=True)

+ 0 - 4
tests/datatypes/tests.py

@@ -12,18 +12,14 @@ class DataTypesTestCase(TestCase):
         d = Donut(name='Apple Fritter')
         self.assertFalse(d.is_frosted)
         self.assertIsNone(d.has_sprinkles)
-        self.assertIsNone(d.has_sprinkles_old)
         d.has_sprinkles = True
-        d.has_sprinkles_old = True
         self.assertTrue(d.has_sprinkles)
-        self.assertTrue(d.has_sprinkles_old)
 
         d.save()
 
         d2 = Donut.objects.get(name='Apple Fritter')
         self.assertFalse(d2.is_frosted)
         self.assertTrue(d2.has_sprinkles)
-        self.assertTrue(d2.has_sprinkles_old)
 
     def test_date_type(self):
         d = Donut(name='Apple Fritter')

+ 0 - 1
tests/expressions_case/models.py

@@ -26,7 +26,6 @@ class CaseTestModel(models.Model):
         image = models.ImageField(null=True)
     generic_ip_address = models.GenericIPAddressField(null=True)
     null_boolean = models.BooleanField(null=True)
-    null_boolean_old = models.NullBooleanField()
     positive_integer = models.PositiveIntegerField(null=True)
     positive_small_integer = models.PositiveSmallIntegerField(null=True)
     positive_big_integer = models.PositiveSmallIntegerField(null=True)

+ 0 - 13
tests/expressions_case/tests.py

@@ -805,19 +805,6 @@ class CaseExpressionTests(TestCase):
             transform=attrgetter('integer', 'null_boolean')
         )
 
-    def test_update_null_boolean_old(self):
-        CaseTestModel.objects.update(
-            null_boolean_old=Case(
-                When(integer=1, then=True),
-                When(integer=2, then=False),
-            ),
-        )
-        self.assertQuerysetEqual(
-            CaseTestModel.objects.all().order_by('pk'),
-            [(1, True), (2, False), (3, None), (2, False), (3, None), (3, None), (4, None)],
-            transform=attrgetter('integer', 'null_boolean_old')
-        )
-
     def test_update_positive_big_integer(self):
         CaseTestModel.objects.update(
             positive_big_integer=Case(

+ 0 - 7
tests/field_deconstruction/tests.py

@@ -432,13 +432,6 @@ class FieldDeconstructionTests(SimpleTestCase):
         self.assertEqual(kwargs, {"to": "auth.Permission"})
         self.assertEqual(kwargs['to'].setting_name, "AUTH_USER_MODEL")
 
-    def test_null_boolean_field(self):
-        field = models.NullBooleanField()
-        name, path, args, kwargs = field.deconstruct()
-        self.assertEqual(path, "django.db.models.NullBooleanField")
-        self.assertEqual(args, [])
-        self.assertEqual(kwargs, {})
-
     def test_positive_integer_field(self):
         field = models.PositiveIntegerField()
         name, path, args, kwargs = field.deconstruct()

+ 4 - 4
tests/invalid_models_tests/test_deprecated_fields.py

@@ -44,11 +44,11 @@ class DeprecatedFieldsTests(SimpleTestCase):
 
         model = NullBooleanFieldModel()
         self.assertEqual(model.check(), [
-            checks.Warning(
-                'NullBooleanField is deprecated. Support for it (except in '
-                'historical migrations) will be removed in Django 4.0.',
+            checks.Error(
+                'NullBooleanField is removed except for support in historical '
+                'migrations.',
                 hint='Use BooleanField(null=True) instead.',
                 obj=NullBooleanFieldModel._meta.get_field('nb'),
-                id='fields.W903',
+                id='fields.E903',
             ),
         ])

+ 9 - 12
tests/model_fields/models.py

@@ -135,7 +135,6 @@ class Post(models.Model):
 
 class NullBooleanModel(models.Model):
     nbfield = models.BooleanField(null=True, blank=True)
-    nbfield_old = models.NullBooleanField()
 
 
 class BooleanModel(models.Model):
@@ -192,16 +191,15 @@ class VerboseNameField(models.Model):
     # field_image = models.ImageField("verbose field")
     field11 = models.IntegerField("verbose field11")
     field12 = models.GenericIPAddressField("verbose field12", protocol="ipv4")
-    field13 = models.NullBooleanField("verbose field13")
-    field14 = models.PositiveIntegerField("verbose field14")
-    field15 = models.PositiveSmallIntegerField("verbose field15")
-    field16 = models.SlugField("verbose field16")
-    field17 = models.SmallIntegerField("verbose field17")
-    field18 = models.TextField("verbose field18")
-    field19 = models.TimeField("verbose field19")
-    field20 = models.URLField("verbose field20")
-    field21 = models.UUIDField("verbose field21")
-    field22 = models.DurationField("verbose field22")
+    field13 = models.PositiveIntegerField("verbose field13")
+    field14 = models.PositiveSmallIntegerField("verbose field14")
+    field15 = models.SlugField("verbose field15")
+    field16 = models.SmallIntegerField("verbose field16")
+    field17 = models.TextField("verbose field17")
+    field18 = models.TimeField("verbose field18")
+    field19 = models.URLField("verbose field19")
+    field20 = models.UUIDField("verbose field20")
+    field21 = models.DurationField("verbose field21")
 
 
 class GenericIPAddress(models.Model):
@@ -385,7 +383,6 @@ class AllFieldsModel(models.Model):
     floatf = models.FloatField()
     integer = models.IntegerField()
     generic_ip = models.GenericIPAddressField()
-    null_boolean = models.NullBooleanField()
     positive_integer = models.PositiveIntegerField()
     positive_small_integer = models.PositiveSmallIntegerField()
     slug = models.SlugField()

+ 5 - 18
tests/model_fields/test_booleanfield.py

@@ -26,18 +26,12 @@ class BooleanFieldTests(TestCase):
     def test_nullbooleanfield_get_prep_value(self):
         self._test_get_prep_value(models.BooleanField(null=True))
 
-    def test_nullbooleanfield_old_get_prep_value(self):
-        self._test_get_prep_value(models.NullBooleanField())
-
     def test_booleanfield_to_python(self):
         self._test_to_python(models.BooleanField())
 
     def test_nullbooleanfield_to_python(self):
         self._test_to_python(models.BooleanField(null=True))
 
-    def test_nullbooleanfield_old_to_python(self):
-        self._test_to_python(models.NullBooleanField())
-
     def test_booleanfield_choices_blank(self):
         """
         BooleanField with choices and defaults doesn't generate a formfield
@@ -59,8 +53,6 @@ class BooleanFieldTests(TestCase):
     def test_nullbooleanfield_formfield(self):
         f = models.BooleanField(null=True)
         self.assertIsInstance(f.formfield(), forms.NullBooleanField)
-        f = models.NullBooleanField()
-        self.assertIsInstance(f.formfield(), forms.NullBooleanField)
 
     def test_return_type(self):
         b = BooleanModel.objects.create(bfield=True)
@@ -71,15 +63,13 @@ class BooleanFieldTests(TestCase):
         b2.refresh_from_db()
         self.assertIs(b2.bfield, False)
 
-        b3 = NullBooleanModel.objects.create(nbfield=True, nbfield_old=True)
+        b3 = NullBooleanModel.objects.create(nbfield=True)
         b3.refresh_from_db()
         self.assertIs(b3.nbfield, True)
-        self.assertIs(b3.nbfield_old, True)
 
-        b4 = NullBooleanModel.objects.create(nbfield=False, nbfield_old=False)
+        b4 = NullBooleanModel.objects.create(nbfield=False)
         b4.refresh_from_db()
         self.assertIs(b4.nbfield, False)
-        self.assertIs(b4.nbfield_old, False)
 
         # When an extra clause exists, the boolean conversions are applied with
         # an offset (#13293).
@@ -92,8 +82,8 @@ class BooleanFieldTests(TestCase):
         """
         bmt = BooleanModel.objects.create(bfield=True)
         bmf = BooleanModel.objects.create(bfield=False)
-        nbmt = NullBooleanModel.objects.create(nbfield=True, nbfield_old=True)
-        nbmf = NullBooleanModel.objects.create(nbfield=False, nbfield_old=False)
+        nbmt = NullBooleanModel.objects.create(nbfield=True)
+        nbmf = NullBooleanModel.objects.create(nbfield=False)
         m1 = FksToBooleans.objects.create(bf=bmt, nbf=nbmt)
         m2 = FksToBooleans.objects.create(bf=bmf, nbf=nbmf)
 
@@ -107,10 +97,8 @@ class BooleanFieldTests(TestCase):
         mc = FksToBooleans.objects.select_related().get(pk=m2.id)
         self.assertIs(mb.bf.bfield, True)
         self.assertIs(mb.nbf.nbfield, True)
-        self.assertIs(mb.nbf.nbfield_old, True)
         self.assertIs(mc.bf.bfield, False)
         self.assertIs(mc.nbf.nbfield, False)
-        self.assertIs(mc.nbf.nbfield_old, False)
 
     def test_null_default(self):
         """
@@ -126,7 +114,6 @@ class BooleanFieldTests(TestCase):
 
         nb = NullBooleanModel()
         self.assertIsNone(nb.nbfield)
-        self.assertIsNone(nb.nbfield_old)
         nb.save()  # no error
 
 
@@ -142,5 +129,5 @@ class ValidationTest(SimpleTestCase):
         NullBooleanField shouldn't throw a validation error when given a value
         of None.
         """
-        nullboolean = NullBooleanModel(nbfield=None, nbfield_old=None)
+        nullboolean = NullBooleanModel(nbfield=None)
         nullboolean.full_clean()

+ 2 - 7
tests/model_fields/test_promises.py

@@ -5,9 +5,8 @@ from django.db.models import (
     AutoField, BinaryField, BooleanField, CharField, DateField, DateTimeField,
     DecimalField, EmailField, FileField, FilePathField, FloatField,
     GenericIPAddressField, ImageField, IntegerField, IPAddressField,
-    NullBooleanField, PositiveBigIntegerField, PositiveIntegerField,
-    PositiveSmallIntegerField, SlugField, SmallIntegerField, TextField,
-    TimeField, URLField,
+    PositiveBigIntegerField, PositiveIntegerField, PositiveSmallIntegerField,
+    SlugField, SmallIntegerField, TextField, TimeField, URLField,
 )
 from django.test import SimpleTestCase
 from django.utils.functional import lazy
@@ -85,10 +84,6 @@ class PromiseTest(SimpleTestCase):
         lazy_func = lazy(lambda: 0, int)
         self.assertIsInstance(GenericIPAddressField().get_prep_value(lazy_func()), str)
 
-    def test_NullBooleanField(self):
-        lazy_func = lazy(lambda: True, bool)
-        self.assertIsInstance(NullBooleanField().get_prep_value(lazy_func()), bool)
-
     def test_PositiveIntegerField(self):
         lazy_func = lazy(lambda: 1, int)
         self.assertIsInstance(PositiveIntegerField().get_prep_value(lazy_func()), int)

+ 1 - 1
tests/model_fields/tests.py

@@ -56,7 +56,7 @@ class BasicFieldTests(SimpleTestCase):
 
     def test_field_verbose_name(self):
         m = VerboseNameField
-        for i in range(1, 23):
+        for i in range(1, 22):
             self.assertEqual(m._meta.get_field('field%d' % i).verbose_name, 'verbose field%d' % i)
 
         self.assertEqual(m._meta.get_field('id').verbose_name, 'verbose pk')

+ 0 - 1
tests/runtests.py

@@ -191,7 +191,6 @@ def setup(verbosity, test_labels, parallel, start_at, start_after):
     settings.LOGGING = log_config
     settings.SILENCED_SYSTEM_CHECKS = [
         'fields.W342',  # ForeignKey(unique=True) -> OneToOneField
-        'fields.W903',  # NullBooleanField deprecated.
     ]
 
     # Load all the ALWAYS_INSTALLED_APPS.

+ 0 - 4
tests/serializers/models/data.py

@@ -70,10 +70,6 @@ class GenericIPAddressData(models.Model):
     data = models.GenericIPAddressField(null=True)
 
 
-class NullBooleanData(models.Model):
-    data = models.NullBooleanField(null=True)
-
-
 class PositiveBigIntegerData(models.Model):
     data = models.PositiveBigIntegerField(null=True)
 

+ 2 - 5
tests/serializers/test_data.py

@@ -23,8 +23,8 @@ from .models import (
     GenericData, GenericIPAddressData, GenericIPAddressPKData,
     InheritAbstractModel, InheritBaseModel, IntegerData, IntegerPKData,
     Intermediate, LengthModel, M2MData, M2MIntermediateData, M2MSelfData,
-    ModifyingSaveData, NullBooleanData, O2OData, PositiveBigIntegerData,
-    PositiveIntegerData, PositiveIntegerPKData, PositiveSmallIntegerData,
+    ModifyingSaveData, O2OData, PositiveBigIntegerData, PositiveIntegerData,
+    PositiveIntegerPKData, PositiveSmallIntegerData,
     PositiveSmallIntegerPKData, SlugData, SlugPKData, SmallData, SmallPKData,
     Tag, TextData, TimeData, UniqueAnchor, UUIDData, UUIDDefaultData,
 )
@@ -238,9 +238,6 @@ test_data = [
     # (XX, ImageData
     (data_obj, 95, GenericIPAddressData, "fe80:1424:2223:6cff:fe8a:2e8a:2151:abcd"),
     (data_obj, 96, GenericIPAddressData, None),
-    (data_obj, 100, NullBooleanData, True),
-    (data_obj, 101, NullBooleanData, False),
-    (data_obj, 102, NullBooleanData, None),
     (data_obj, 110, PositiveBigIntegerData, 9223372036854775807),
     (data_obj, 111, PositiveBigIntegerData, None),
     (data_obj, 120, PositiveIntegerData, 123456789),

+ 2 - 2
tests/validation/test_error_messages.py

@@ -36,8 +36,8 @@ class ValidationMessagesTest(TestCase):
         self._test_validation_messages(f, 'fõo', ['“fõo” value must be a decimal number.'])
 
     def test_null_boolean_field_raises_error_message(self):
-        f = models.NullBooleanField()
-        self._test_validation_messages(f, 'fõo', ['“fõo” value must be either None, True or False.'])
+        f = models.BooleanField(null=True)
+        self._test_validation_messages(f, 'fõo', ['“fõo” value must be either True, False, or None.'])
 
     def test_date_field_raises_error_message(self):
         f = models.DateField()