Fixed #20895 -- Made check management command warn if a BooleanField does not have a default value
Thanks to Collin Anderson for the suggestion and Tim Graham for reviewing the patch.
This commit is contained in:
parent
55339a7669
commit
22c6497f99
1
AUTHORS
1
AUTHORS
|
@ -442,6 +442,7 @@ answer newbie questions, and generally made Django that much better:
|
|||
Gopal Narayanan <gopastro@gmail.com>
|
||||
Fraser Nevett <mail@nevett.org>
|
||||
Sam Newman <http://www.magpiebrain.com/>
|
||||
Alasdair Nicol <http://al.sdair.co.uk/>
|
||||
Ryan Niemeyer <https://profiles.google.com/ryan.niemeyer/about>
|
||||
Filip Noetzel <http://filip.noetzel.co.uk/>
|
||||
Afonso Fernández Nogueira <fonzzo.django@gmail.com>
|
||||
|
|
|
@ -40,7 +40,7 @@ class Track(models.Model):
|
|||
def __str__(self): return self.name
|
||||
|
||||
class Truth(models.Model):
|
||||
val = models.BooleanField()
|
||||
val = models.BooleanField(default=False)
|
||||
objects = models.GeoManager()
|
||||
|
||||
if not spatialite:
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models
|
||||
|
||||
def check_test_runner():
|
||||
"""
|
||||
|
@ -24,6 +25,31 @@ def check_test_runner():
|
|||
]
|
||||
return ' '.join(message)
|
||||
|
||||
def check_boolean_field_default_value():
|
||||
"""
|
||||
Checks if there are any BooleanFields without a default value, &
|
||||
warns the user that the default has changed from False to Null.
|
||||
"""
|
||||
fields = []
|
||||
for cls in models.get_models():
|
||||
opts = cls._meta
|
||||
for f in opts.local_fields:
|
||||
if isinstance(f, models.BooleanField) and not f.has_default():
|
||||
fields.append(
|
||||
'%s.%s: "%s"' % (opts.app_label, opts.object_name, f.name)
|
||||
)
|
||||
if fields:
|
||||
fieldnames = ", ".join(fields)
|
||||
message = [
|
||||
"You have not set a default value for one or more BooleanFields:",
|
||||
"%s." % fieldnames,
|
||||
"In Django 1.6 the default value of BooleanField was changed from",
|
||||
"False to Null when Field.default isn't defined. See",
|
||||
"https://docs.djangoproject.com/en/1.6/ref/models/fields/#booleanfield"
|
||||
"for more information."
|
||||
]
|
||||
return ' '.join(message)
|
||||
|
||||
|
||||
def run_checks():
|
||||
"""
|
||||
|
@ -31,7 +57,8 @@ def run_checks():
|
|||
messages from all the relevant check functions for this version of Django.
|
||||
"""
|
||||
checks = [
|
||||
check_test_runner()
|
||||
check_test_runner(),
|
||||
check_boolean_field_default_value(),
|
||||
]
|
||||
# Filter out the ``None`` or empty strings.
|
||||
return [output for output in checks if output]
|
||||
|
|
|
@ -115,7 +115,7 @@ class ModelWithStringPrimaryKey(models.Model):
|
|||
@python_2_unicode_compatible
|
||||
class Color(models.Model):
|
||||
value = models.CharField(max_length=10)
|
||||
warm = models.BooleanField()
|
||||
warm = models.BooleanField(default=False)
|
||||
def __str__(self):
|
||||
return self.value
|
||||
|
||||
|
@ -144,7 +144,7 @@ class Actor(models.Model):
|
|||
|
||||
@python_2_unicode_compatible
|
||||
class Inquisition(models.Model):
|
||||
expected = models.BooleanField()
|
||||
expected = models.BooleanField(default=False)
|
||||
leader = models.ForeignKey(Actor)
|
||||
country = models.CharField(max_length=20)
|
||||
|
||||
|
@ -376,7 +376,7 @@ class Link(models.Model):
|
|||
|
||||
class PrePopulatedPost(models.Model):
|
||||
title = models.CharField(max_length=100)
|
||||
published = models.BooleanField()
|
||||
published = models.BooleanField(default=False)
|
||||
slug = models.SlugField()
|
||||
|
||||
|
||||
|
@ -607,7 +607,7 @@ class PrePopulatedPostLargeSlug(models.Model):
|
|||
the javascript (ie, using THOUSAND_SEPARATOR ends up with maxLength=1,000)
|
||||
"""
|
||||
title = models.CharField(max_length=100)
|
||||
published = models.BooleanField()
|
||||
published = models.BooleanField(default=False)
|
||||
slug = models.SlugField(max_length=1000)
|
||||
|
||||
class AdminOrderedField(models.Model):
|
||||
|
|
|
@ -64,7 +64,7 @@ class Store(models.Model):
|
|||
class Entries(models.Model):
|
||||
EntryID = models.AutoField(primary_key=True, db_column='Entry ID')
|
||||
Entry = models.CharField(unique=True, max_length=50)
|
||||
Exclude = models.BooleanField()
|
||||
Exclude = models.BooleanField(default=False)
|
||||
|
||||
|
||||
class Clues(models.Model):
|
||||
|
|
|
@ -1 +1,9 @@
|
|||
# Stubby.
|
||||
from django.db import models
|
||||
|
||||
class Book(models.Model):
|
||||
title = models.CharField(max_length=250)
|
||||
is_published = models.BooleanField(default=False)
|
||||
|
||||
class BlogPost(models.Model):
|
||||
title = models.CharField(max_length=250)
|
||||
is_published = models.BooleanField(default=False)
|
||||
|
|
|
@ -2,8 +2,10 @@ from django.core.checks.compatibility import base
|
|||
from django.core.checks.compatibility import django_1_6_0
|
||||
from django.core.management.commands import check
|
||||
from django.core.management import call_command
|
||||
from django.db.models.fields import NOT_PROVIDED
|
||||
from django.test import TestCase
|
||||
|
||||
from .models import Book
|
||||
|
||||
class StubCheckModule(object):
|
||||
# Has no ``run_checks`` attribute & will trigger a warning.
|
||||
|
@ -53,6 +55,24 @@ class CompatChecksTestCase(TestCase):
|
|||
with self.settings(TEST_RUNNER='myapp.test.CustomRunnner'):
|
||||
self.assertEqual(len(django_1_6_0.run_checks()), 0)
|
||||
|
||||
def test_boolean_field_default_value(self):
|
||||
with self.settings(TEST_RUNNER='myapp.test.CustomRunnner'):
|
||||
# We patch the field's default value to trigger the warning
|
||||
boolean_field = Book._meta.get_field('is_published')
|
||||
old_default = boolean_field.default
|
||||
try:
|
||||
boolean_field.default = NOT_PROVIDED
|
||||
result = django_1_6_0.run_checks()
|
||||
self.assertEqual(len(result), 1)
|
||||
self.assertTrue("You have not set a default value for one or more BooleanFields" in result[0])
|
||||
self.assertTrue('check.Book: "is_published"' in result[0])
|
||||
# We did not patch the BlogPost.is_published field so
|
||||
# there should not be a warning about it
|
||||
self.assertFalse('check.BlogPost' in result[0])
|
||||
finally:
|
||||
# Restore the ``default``
|
||||
boolean_field.default = old_default
|
||||
|
||||
def test_check_compatibility(self):
|
||||
with self.settings(TEST_RUNNER='django.test.runner.DiscoverRunner'):
|
||||
result = base.check_compatibility()
|
||||
|
|
|
@ -30,7 +30,7 @@ class Entry(models.Model):
|
|||
title = models.CharField(max_length=250)
|
||||
body = models.TextField()
|
||||
pub_date = models.DateField()
|
||||
enable_comments = models.BooleanField()
|
||||
enable_comments = models.BooleanField(default=False)
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
|
|
@ -67,7 +67,7 @@ CustomManager = BaseCustomManager.from_queryset(CustomQuerySet)
|
|||
class Person(models.Model):
|
||||
first_name = models.CharField(max_length=30)
|
||||
last_name = models.CharField(max_length=30)
|
||||
fun = models.BooleanField()
|
||||
fun = models.BooleanField(default=False)
|
||||
objects = PersonManager()
|
||||
|
||||
custom_queryset_default_manager = CustomQuerySet.as_manager()
|
||||
|
@ -80,7 +80,7 @@ class Person(models.Model):
|
|||
class Book(models.Model):
|
||||
title = models.CharField(max_length=50)
|
||||
author = models.CharField(max_length=30)
|
||||
is_published = models.BooleanField()
|
||||
is_published = models.BooleanField(default=False)
|
||||
published_objects = PublishedBookManager()
|
||||
authors = models.ManyToManyField(Person, related_name='books')
|
||||
|
||||
|
|
|
@ -92,7 +92,7 @@ class GeckoManager(models.Manager):
|
|||
return super(GeckoManager, self).get_queryset().filter(has_tail=True)
|
||||
|
||||
class Gecko(models.Model):
|
||||
has_tail = models.BooleanField()
|
||||
has_tail = models.BooleanField(default=False)
|
||||
objects = GeckoManager()
|
||||
|
||||
# To test fix for #11263
|
||||
|
|
|
@ -37,7 +37,7 @@ class SpecialColumnName(models.Model):
|
|||
class ColumnTypes(models.Model):
|
||||
id = models.AutoField(primary_key=True)
|
||||
big_int_field = models.BigIntegerField()
|
||||
bool_field = models.BooleanField()
|
||||
bool_field = models.BooleanField(default=False)
|
||||
null_bool_field = models.NullBooleanField()
|
||||
char_field = models.CharField(max_length=10)
|
||||
comma_separated_int_field = models.CommaSeparatedIntegerField(max_length=99)
|
||||
|
|
|
@ -58,7 +58,7 @@ class NullBooleanModel(models.Model):
|
|||
nbfield = models.NullBooleanField()
|
||||
|
||||
class BooleanModel(models.Model):
|
||||
bfield = models.BooleanField()
|
||||
bfield = models.BooleanField(default=None)
|
||||
string = models.CharField(max_length=10, default='abc')
|
||||
|
||||
class FksToBooleans(models.Model):
|
||||
|
@ -72,7 +72,7 @@ class RenamedField(models.Model):
|
|||
class VerboseNameField(models.Model):
|
||||
id = models.AutoField("verbose pk", primary_key=True)
|
||||
field1 = models.BigIntegerField("verbose field1")
|
||||
field2 = models.BooleanField("verbose field2")
|
||||
field2 = models.BooleanField("verbose field2", default=False)
|
||||
field3 = models.CharField("verbose field3", max_length=10)
|
||||
field4 = models.CommaSeparatedIntegerField("verbose field4", max_length=99)
|
||||
field5 = models.DateField("verbose field5")
|
||||
|
|
|
@ -12,7 +12,7 @@ from django.db.models.fields import (
|
|||
AutoField, BigIntegerField, BinaryField, BooleanField, CharField,
|
||||
CommaSeparatedIntegerField, DateField, DateTimeField, DecimalField,
|
||||
EmailField, FilePathField, FloatField, IntegerField, IPAddressField,
|
||||
GenericIPAddressField, NullBooleanField, PositiveIntegerField,
|
||||
GenericIPAddressField, NOT_PROVIDED, NullBooleanField, PositiveIntegerField,
|
||||
PositiveSmallIntegerField, SlugField, SmallIntegerField, TextField,
|
||||
TimeField, URLField)
|
||||
from django.db.models.fields.files import FileField, ImageField
|
||||
|
@ -275,10 +275,23 @@ class BooleanFieldTests(unittest.TestCase):
|
|||
Check that a BooleanField defaults to None -- which isn't
|
||||
a valid value (#15124).
|
||||
"""
|
||||
b = BooleanModel()
|
||||
self.assertIsNone(b.bfield)
|
||||
with self.assertRaises(IntegrityError):
|
||||
b.save()
|
||||
# Patch the boolean field's default value. We give it a default
|
||||
# value when defining the model to satisfy the check tests
|
||||
# #20895.
|
||||
boolean_field = BooleanModel._meta.get_field('bfield')
|
||||
self.assertTrue(boolean_field.has_default())
|
||||
old_default = boolean_field.default
|
||||
try:
|
||||
boolean_field.default = NOT_PROVIDED
|
||||
# check patch was succcessful
|
||||
self.assertFalse(boolean_field.has_default())
|
||||
b = BooleanModel()
|
||||
self.assertIsNone(b.bfield)
|
||||
with self.assertRaises(IntegrityError):
|
||||
b.save()
|
||||
finally:
|
||||
boolean_field.default = old_default
|
||||
|
||||
nb = NullBooleanModel()
|
||||
self.assertIsNone(nb.nbfield)
|
||||
nb.save() # no error
|
||||
|
|
|
@ -117,7 +117,7 @@ class OwnerProfile(models.Model):
|
|||
|
||||
@python_2_unicode_compatible
|
||||
class Restaurant(Place):
|
||||
serves_pizza = models.BooleanField()
|
||||
serves_pizza = models.BooleanField(default=False)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
@ -141,11 +141,11 @@ class Price(models.Model):
|
|||
unique_together = (('price', 'quantity'),)
|
||||
|
||||
class MexicanRestaurant(Restaurant):
|
||||
serves_tacos = models.BooleanField()
|
||||
serves_tacos = models.BooleanField(default=False)
|
||||
|
||||
class ClassyMexicanRestaurant(MexicanRestaurant):
|
||||
restaurant = models.OneToOneField(MexicanRestaurant, parent_link=True, primary_key=True)
|
||||
tacos_are_yummy = models.BooleanField()
|
||||
tacos_are_yummy = models.BooleanField(default=False)
|
||||
|
||||
# models for testing unique_together validation when a fk is involved and
|
||||
# using inlineformset_factory.
|
||||
|
|
|
@ -63,7 +63,7 @@ class Attachment(models.Model):
|
|||
return self.content
|
||||
|
||||
class Comment(Attachment):
|
||||
is_spam = models.BooleanField()
|
||||
is_spam = models.BooleanField(default=False)
|
||||
|
||||
class Link(Attachment):
|
||||
url = models.URLField()
|
||||
|
@ -96,8 +96,8 @@ class Rating(models.Model):
|
|||
|
||||
@python_2_unicode_compatible
|
||||
class Restaurant(Place, Rating):
|
||||
serves_hot_dogs = models.BooleanField()
|
||||
serves_pizza = models.BooleanField()
|
||||
serves_hot_dogs = models.BooleanField(default=False)
|
||||
serves_pizza = models.BooleanField(default=False)
|
||||
chef = models.ForeignKey(Chef, null=True, blank=True)
|
||||
|
||||
class Meta(Rating.Meta):
|
||||
|
@ -108,7 +108,7 @@ class Restaurant(Place, Rating):
|
|||
|
||||
@python_2_unicode_compatible
|
||||
class ItalianRestaurant(Restaurant):
|
||||
serves_gnocchi = models.BooleanField()
|
||||
serves_gnocchi = models.BooleanField(default=False)
|
||||
|
||||
def __str__(self):
|
||||
return "%s the italian restaurant" % self.name
|
||||
|
|
|
@ -18,15 +18,15 @@ class Place(models.Model):
|
|||
|
||||
@python_2_unicode_compatible
|
||||
class Restaurant(Place):
|
||||
serves_hot_dogs = models.BooleanField()
|
||||
serves_pizza = models.BooleanField()
|
||||
serves_hot_dogs = models.BooleanField(default=False)
|
||||
serves_pizza = models.BooleanField(default=False)
|
||||
|
||||
def __str__(self):
|
||||
return "%s the restaurant" % self.name
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class ItalianRestaurant(Restaurant):
|
||||
serves_gnocchi = models.BooleanField()
|
||||
serves_gnocchi = models.BooleanField(default=False)
|
||||
|
||||
def __str__(self):
|
||||
return "%s the italian restaurant" % self.name
|
||||
|
@ -184,7 +184,7 @@ class Station(SearchableLocation):
|
|||
|
||||
class BusStation(Station):
|
||||
bus_routes = models.CommaSeparatedIntegerField(max_length=128)
|
||||
inbound = models.BooleanField()
|
||||
inbound = models.BooleanField(default=False)
|
||||
|
||||
class TrainStation(Station):
|
||||
zone = models.IntegerField()
|
||||
|
|
|
@ -20,8 +20,8 @@ class Place(models.Model):
|
|||
|
||||
@python_2_unicode_compatible
|
||||
class Restaurant(Place):
|
||||
serves_sushi = models.BooleanField()
|
||||
serves_steak = models.BooleanField()
|
||||
serves_sushi = models.BooleanField(default=False)
|
||||
serves_steak = models.BooleanField(default=False)
|
||||
|
||||
def __str__(self):
|
||||
return "%s the restaurant" % self.name
|
||||
|
|
|
@ -32,7 +32,7 @@ class ValidationTestModel(models.Model):
|
|||
slug = models.SlugField()
|
||||
users = models.ManyToManyField(User)
|
||||
state = models.CharField(max_length=2, choices=(("CO", "Colorado"), ("WA", "Washington")))
|
||||
is_active = models.BooleanField()
|
||||
is_active = models.BooleanField(default=False)
|
||||
pub_date = models.DateTimeField()
|
||||
band = models.ForeignKey(Band)
|
||||
no = models.IntegerField(verbose_name="Number", blank=True, null=True) # This field is intentionally 2 characters long. See #16080.
|
||||
|
|
|
@ -22,8 +22,8 @@ class Place(models.Model):
|
|||
@python_2_unicode_compatible
|
||||
class Restaurant(models.Model):
|
||||
place = models.OneToOneField(Place, primary_key=True)
|
||||
serves_hot_dogs = models.BooleanField()
|
||||
serves_pizza = models.BooleanField()
|
||||
serves_hot_dogs = models.BooleanField(default=False)
|
||||
serves_pizza = models.BooleanField(default=False)
|
||||
|
||||
def __str__(self):
|
||||
return "%s the restaurant" % self.place.name
|
||||
|
|
|
@ -15,8 +15,8 @@ class Place(models.Model):
|
|||
@python_2_unicode_compatible
|
||||
class Restaurant(models.Model):
|
||||
place = models.OneToOneField(Place)
|
||||
serves_hot_dogs = models.BooleanField()
|
||||
serves_pizza = models.BooleanField()
|
||||
serves_hot_dogs = models.BooleanField(default=False)
|
||||
serves_pizza = models.BooleanField(default=False)
|
||||
|
||||
def __str__(self):
|
||||
return "%s the restaurant" % self.place.name
|
||||
|
|
|
@ -18,7 +18,7 @@ class Author(models.Model):
|
|||
class Book(models.Model):
|
||||
title = models.CharField(max_length=255)
|
||||
author = models.ForeignKey(Author)
|
||||
paperback = models.BooleanField()
|
||||
paperback = models.BooleanField(default=False)
|
||||
opening_line = models.TextField()
|
||||
|
||||
class Coffee(models.Model):
|
||||
|
|
|
@ -6,7 +6,7 @@ class SourceManager(models.Manager):
|
|||
return super(SourceManager, self).get_queryset().filter(is_public=True)
|
||||
|
||||
class Source(models.Model):
|
||||
is_public = models.BooleanField()
|
||||
is_public = models.BooleanField(default=False)
|
||||
objects = SourceManager()
|
||||
|
||||
class Item(models.Model):
|
||||
|
|
|
@ -16,7 +16,7 @@ class BinaryData(models.Model):
|
|||
data = models.BinaryField(null=True)
|
||||
|
||||
class BooleanData(models.Model):
|
||||
data = models.BooleanField()
|
||||
data = models.BooleanField(default=False)
|
||||
|
||||
class CharData(models.Model):
|
||||
data = models.CharField(max_length=30, null=True)
|
||||
|
@ -166,7 +166,7 @@ class Intermediate(models.Model):
|
|||
# or all database backends.
|
||||
|
||||
class BooleanPKData(models.Model):
|
||||
data = models.BooleanField(primary_key=True)
|
||||
data = models.BooleanField(primary_key=True, default=False)
|
||||
|
||||
class CharPKData(models.Model):
|
||||
data = models.CharField(max_length=30, primary_key=True)
|
||||
|
|
Loading…
Reference in New Issue