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

This commit is contained in:
Mariusz Felisiak 2021-01-13 21:28:09 +01:00
parent 06eec31970
commit d992f4e3c2
34 changed files with 49 additions and 183 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 objects and arrays (represented in Python using :py:class:`dict` and
:py:class:`list`) are supported. :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`` ``PositiveBigIntegerField``
--------------------------- ---------------------------

View File

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

View File

@ -305,3 +305,6 @@ to remove usage of these features.
* The ``list`` message for ``ModelMultipleChoiceField`` is removed. * The ``list`` message for ``ModelMultipleChoiceField`` is removed.
* Support for passing raw column aliases to ``QuerySet.order_by()`` 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.

View File

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

View File

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

View File

@ -144,10 +144,6 @@ class BookAdmin(ModelAdmin):
ordering = ('-id',) ordering = ('-id',)
class BookAdmin2(ModelAdmin):
list_filter = ('year', 'author', 'contributors', 'is_best_seller2', 'date_registered', 'no')
class BookAdminWithTupleBooleanFilter(BookAdmin): class BookAdminWithTupleBooleanFilter(BookAdmin):
list_filter = ( list_filter = (
'year', 'year',
@ -289,22 +285,22 @@ class ListFiltersTests(TestCase):
cls.djangonaut_book = Book.objects.create( cls.djangonaut_book = Book.objects.create(
title='Djangonaut: an art of living', year=2009, title='Djangonaut: an art of living', year=2009,
author=cls.alfred, is_best_seller=True, date_registered=cls.today, 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( cls.bio_book = Book.objects.create(
title='Django: a biography', year=1999, author=cls.alfred, title='Django: a biography', year=1999, author=cls.alfred,
is_best_seller=False, no=207, is_best_seller=False, no=207,
is_best_seller2=False, availability=False, availability=False,
) )
cls.django_book = Book.objects.create( cls.django_book = Book.objects.create(
title='The Django Book', year=None, author=cls.bob, title='The Django Book', year=None, author=cls.bob,
is_best_seller=None, date_registered=cls.today, no=103, is_best_seller=None, date_registered=cls.today, no=103,
is_best_seller2=None, availability=True, availability=True,
) )
cls.guitar_book = Book.objects.create( cls.guitar_book = Book.objects.create(
title='Guitar for dummies', year=2002, is_best_seller=True, title='Guitar for dummies', year=2002, is_best_seller=True,
date_registered=cls.one_week_ago, date_registered=cls.one_week_ago,
is_best_seller2=True, availability=None, availability=None,
) )
cls.guitar_book.contributors.set([cls.bob, cls.lisa]) cls.guitar_book.contributors.set([cls.bob, cls.lisa])
@ -1014,58 +1010,6 @@ class ListFiltersTests(TestCase):
self.assertIs(choice['selected'], True) self.assertIs(choice['selected'], True)
self.assertEqual(choice['query_string'], '?') 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): def test_fieldlistfilter_underscorelookup_tuple(self):
""" """
Ensure ('fieldpath', ClassName ) lookups pass lookup_allowed checks Ensure ('fieldpath', ClassName ) lookups pass lookup_allowed checks

View File

@ -163,12 +163,6 @@ class UtilsTests(SimpleTestCase):
display_value = display_for_field(None, models.TimeField(), self.empty_value) display_value = display_for_field(None, models.TimeField(), self.empty_value)
self.assertEqual(display_value, 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) 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 expected = '<img src="%sadmin/img/icon-unknown.svg" alt="None" />' % settings.STATIC_URL
self.assertHTMLEqual(display_value, expected) self.assertHTMLEqual(display_value, expected)

View File

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

View File

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

View File

@ -83,7 +83,6 @@ class NullableFields(models.Model):
float_field = models.FloatField(null=True, default=3.2) float_field = models.FloatField(null=True, default=3.2)
integer_field = models.IntegerField(null=True, default=2) integer_field = models.IntegerField(null=True, default=2)
null_boolean_field = models.BooleanField(null=True, default=False) 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_big_integer_field = models.PositiveBigIntegerField(null=True, default=2 ** 63 - 1)
positive_integer_field = models.PositiveIntegerField(null=True, default=3) positive_integer_field = models.PositiveIntegerField(null=True, default=3)
positive_small_integer_field = models.PositiveSmallIntegerField(null=True, default=4) positive_small_integer_field = models.PositiveSmallIntegerField(null=True, default=4)

View File

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

View File

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

View File

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

View File

@ -805,19 +805,6 @@ class CaseExpressionTests(TestCase):
transform=attrgetter('integer', 'null_boolean') 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): def test_update_positive_big_integer(self):
CaseTestModel.objects.update( CaseTestModel.objects.update(
positive_big_integer=Case( positive_big_integer=Case(

View File

@ -432,13 +432,6 @@ class FieldDeconstructionTests(SimpleTestCase):
self.assertEqual(kwargs, {"to": "auth.Permission"}) self.assertEqual(kwargs, {"to": "auth.Permission"})
self.assertEqual(kwargs['to'].setting_name, "AUTH_USER_MODEL") 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): def test_positive_integer_field(self):
field = models.PositiveIntegerField() field = models.PositiveIntegerField()
name, path, args, kwargs = field.deconstruct() name, path, args, kwargs = field.deconstruct()

View File

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

View File

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

View File

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

View File

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

View File

@ -56,7 +56,7 @@ class BasicFieldTests(SimpleTestCase):
def test_field_verbose_name(self): def test_field_verbose_name(self):
m = VerboseNameField 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('field%d' % i).verbose_name, 'verbose field%d' % i)
self.assertEqual(m._meta.get_field('id').verbose_name, 'verbose pk') self.assertEqual(m._meta.get_field('id').verbose_name, 'verbose pk')

View File

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

View File

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

View File

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

View File

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