From d992f4e3c29a81c956d3d616f0bc19701431b26e Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 13 Jan 2021 21:28:09 +0100 Subject: [PATCH] Refs #31369 -- Removed models.NullBooleanField per deprecation timeline. --- django/db/backends/mysql/base.py | 1 - django/db/backends/mysql/operations.py | 2 +- django/db/backends/oracle/base.py | 2 - django/db/backends/oracle/operations.py | 2 +- django/db/backends/oracle/utils.py | 1 - django/db/backends/postgresql/base.py | 1 - django/db/backends/sqlite3/base.py | 1 - django/db/backends/sqlite3/operations.py | 2 +- django/db/models/fields/__init__.py | 8 +-- docs/ref/checks.txt | 5 +- docs/ref/models/fields.txt | 11 ---- docs/releases/2.1.txt | 4 +- docs/releases/4.0.txt | 3 + docs/topics/forms/modelforms.txt | 2 - tests/admin_filters/models.py | 1 - tests/admin_filters/tests.py | 64 ++----------------- tests/admin_utils/tests.py | 6 -- tests/annotations/tests.py | 6 +- tests/backends/oracle/tests.py | 4 +- tests/bulk_create/models.py | 1 - tests/datatypes/models.py | 1 - tests/datatypes/tests.py | 4 -- tests/expressions_case/models.py | 1 - tests/expressions_case/tests.py | 13 ---- tests/field_deconstruction/tests.py | 7 -- .../test_deprecated_fields.py | 8 +-- tests/model_fields/models.py | 21 +++--- tests/model_fields/test_booleanfield.py | 23 ++----- tests/model_fields/test_promises.py | 9 +-- tests/model_fields/tests.py | 2 +- tests/runtests.py | 1 - tests/serializers/models/data.py | 4 -- tests/serializers/test_data.py | 7 +- tests/validation/test_error_messages.py | 4 +- 34 files changed, 49 insertions(+), 183 deletions(-) diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index f57ec283fc..470271c376 100644 --- a/django/db/backends/mysql/base.py +++ b/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', diff --git a/django/db/backends/mysql/operations.py b/django/db/backends/mysql/operations.py index 5d2a981226..e43c121b4d 100644 --- a/django/db/backends/mysql/operations.py +++ b/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: diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index d1650e7927..966eb4b6f4 100644 --- a/django/db/backends/oracle/base.py +++ b/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', diff --git a/django/db/backends/oracle/operations.py b/django/db/backends/oracle/operations.py index b829d2bd9b..964edc4549 100644 --- a/django/db/backends/oracle/operations.py +++ b/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: diff --git a/django/db/backends/oracle/utils.py b/django/db/backends/oracle/utils.py index 5665079aa2..bbfd7f6a39 100644 --- a/django/db/backends/oracle/utils.py +++ b/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, diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py index c752b5dba4..9eac005dd1 100644 --- a/django/db/backends/postgresql/base.py +++ b/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', diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index 9ce3208960..f8e1def982 100644 --- a/django/db/backends/sqlite3/base.py +++ b/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', diff --git a/django/db/backends/sqlite3/operations.py b/django/db/backends/sqlite3/operations.py index 71ef000c93..faf96a1b97 100644 --- a/django/db/backends/sqlite3/operations.py +++ b/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 diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 5b8b3cab23..0f53d9c30b 100644 --- a/django/db/models/fields/__init__.py +++ b/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): diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt index 48ffb8c816..e79a50b831 100644 --- a/docs/ref/checks.txt +++ b/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. diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index 3409d2d023..aedf115e08 100644 --- a/docs/ref/models/fields.txt +++ b/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`` --------------------------- diff --git a/docs/releases/2.1.txt b/docs/releases/2.1.txt index 21e9388680..bc03a67dce 100644 --- a/docs/releases/2.1.txt +++ b/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. diff --git a/docs/releases/4.0.txt b/docs/releases/4.0.txt index d3bb700aac..4e89cf2230 100644 --- a/docs/releases/4.0.txt +++ b/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. diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt index 586b668da9..179762474a 100644 --- a/docs/topics/forms/modelforms.txt +++ b/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` diff --git a/tests/admin_filters/models.py b/tests/admin_filters/models.py index f286e641de..90b9cab2ac 100644 --- a/tests/admin_filters/models.py +++ b/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'), diff --git a/tests/admin_filters/tests.py b/tests/admin_filters/tests.py index 2ad44b6c8c..17aa54a6f6 100644 --- a/tests/admin_filters/tests.py +++ b/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 diff --git a/tests/admin_utils/tests.py b/tests/admin_utils/tests.py index a74449bdc0..5960759a4d 100644 --- a/tests/admin_utils/tests.py +++ b/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 = 'None' % settings.STATIC_URL - self.assertHTMLEqual(display_value, expected) - display_value = display_for_field(None, models.BooleanField(null=True), self.empty_value) expected = 'None' % settings.STATIC_URL self.assertHTMLEqual(display_value, expected) diff --git a/tests/annotations/tests.py b/tests/annotations/tests.py index 8337f344ed..2aa1765125 100644 --- a/tests/annotations/tests.py +++ b/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 = ( diff --git a/tests/backends/oracle/tests.py b/tests/backends/oracle/tests.py index 258f98f5c9..85d45805e0 100644 --- a/tests/backends/oracle/tests.py +++ b/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)) diff --git a/tests/bulk_create/models.py b/tests/bulk_create/models.py index 9bde8a3976..586457b192 100644 --- a/tests/bulk_create/models.py +++ b/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) diff --git a/tests/datatypes/models.py b/tests/datatypes/models.py index ce78470f61..b1304a7cc7 100644 --- a/tests/datatypes/models.py +++ b/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) diff --git a/tests/datatypes/tests.py b/tests/datatypes/tests.py index 924d796121..52f24fe051 100644 --- a/tests/datatypes/tests.py +++ b/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') diff --git a/tests/expressions_case/models.py b/tests/expressions_case/models.py index 8e8e33a678..243e645005 100644 --- a/tests/expressions_case/models.py +++ b/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) diff --git a/tests/expressions_case/tests.py b/tests/expressions_case/tests.py index ec811ca511..24443ab3a1 100644 --- a/tests/expressions_case/tests.py +++ b/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( diff --git a/tests/field_deconstruction/tests.py b/tests/field_deconstruction/tests.py index bf00aa44e2..b746e46458 100644 --- a/tests/field_deconstruction/tests.py +++ b/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() diff --git a/tests/invalid_models_tests/test_deprecated_fields.py b/tests/invalid_models_tests/test_deprecated_fields.py index fdd5af1937..e240f20ba5 100644 --- a/tests/invalid_models_tests/test_deprecated_fields.py +++ b/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', ), ]) diff --git a/tests/model_fields/models.py b/tests/model_fields/models.py index c8867834da..9e8baeb565 100644 --- a/tests/model_fields/models.py +++ b/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() diff --git a/tests/model_fields/test_booleanfield.py b/tests/model_fields/test_booleanfield.py index 89e0ecfa2a..907385534b 100644 --- a/tests/model_fields/test_booleanfield.py +++ b/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() diff --git a/tests/model_fields/test_promises.py b/tests/model_fields/test_promises.py index 8e7f54b194..f48a4cc34a 100644 --- a/tests/model_fields/test_promises.py +++ b/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) diff --git a/tests/model_fields/tests.py b/tests/model_fields/tests.py index af2634dd63..5208b40dc9 100644 --- a/tests/model_fields/tests.py +++ b/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') diff --git a/tests/runtests.py b/tests/runtests.py index ba7c163bf6..fe5ca44ba6 100755 --- a/tests/runtests.py +++ b/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. diff --git a/tests/serializers/models/data.py b/tests/serializers/models/data.py index eaa2aa60e6..94c4a215fc 100644 --- a/tests/serializers/models/data.py +++ b/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) diff --git a/tests/serializers/test_data.py b/tests/serializers/test_data.py index 9fbdd256fe..91cad48e0f 100644 --- a/tests/serializers/test_data.py +++ b/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), diff --git a/tests/validation/test_error_messages.py b/tests/validation/test_error_messages.py index b8e4617886..5f1e0a75d0 100644 --- a/tests/validation/test_error_messages.py +++ b/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()