Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

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.
  • Loading branch information...
commit 22c6497f990fd12359b759a71abfcbf3f52b2d52 1 parent 55339a7
@alasdairnicol alasdairnicol authored timgraham committed
View
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>
View
2  django/contrib/gis/tests/geoapp/models.py
@@ -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:
View
29 django/core/checks/compatibility/django_1_6_0.py
@@ -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]
View
8 tests/admin_views/models.py
@@ -115,7 +115,7 @@ def get_absolute_url(self):
@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 @@ def __str__(self):
@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):
View
2  tests/aggregation_regress/models.py
@@ -64,7 +64,7 @@ def __str__(self):
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):
View
10 tests/check/models.py
@@ -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)
View
20 tests/check/tests.py
@@ -2,8 +2,10 @@
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 @@ def test_run_checks_overridden(self):
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()
View
2  tests/comment_tests/models.py
@@ -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
View
4 tests/custom_managers/models.py
@@ -67,7 +67,7 @@ def manager_only(self):
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 @@ def __str__(self):
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')
View
2  tests/generic_relations/models.py
@@ -92,7 +92,7 @@ def get_queryset(self):
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
View
2  tests/inspectdb/models.py
@@ -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)
View
4 tests/model_fields/models.py
@@ -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")
View
23 tests/model_fields/tests.py
@@ -12,7 +12,7 @@
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 @@ def test_null_default(self):
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
View
6 tests/model_formsets/models.py
@@ -117,7 +117,7 @@ def __str__(self):
@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 Meta:
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.
View
8 tests/model_inheritance/models.py
@@ -63,7 +63,7 @@ def __str__(self):
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 Meta:
@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 @@ def __str__(self):
@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
View
8 tests/model_inheritance_regress/models.py
@@ -18,15 +18,15 @@ def __str__(self):
@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 Meta:
class BusStation(Station):
bus_routes = models.CommaSeparatedIntegerField(max_length=128)
- inbound = models.BooleanField()
+ inbound = models.BooleanField(default=False)
class TrainStation(Station):
zone = models.IntegerField()
View
4 tests/model_inheritance_select_related/models.py
@@ -20,8 +20,8 @@ def __str__(self):
@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
View
2  tests/modeladmin/models.py
@@ -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.
View
4 tests/one_to_one/models.py
@@ -22,8 +22,8 @@ def __str__(self):
@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
View
4 tests/one_to_one_regress/models.py
@@ -15,8 +15,8 @@ def __str__(self):
@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
View
2  tests/raw_query/models.py
@@ -18,7 +18,7 @@ def __init__(self, *args, **kwargs):
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):
View
2  tests/reverse_single_related/models.py
@@ -6,7 +6,7 @@ def get_queryset(self):
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):
View
4 tests/serializers_regress/models.py
@@ -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)
Please sign in to comment.
Something went wrong with that request. Please try again.