Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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
Alasdair Nicol authored August 11, 2013 timgraham committed August 15, 2013
1  AUTHORS
@@ -442,6 +442,7 @@ answer newbie questions, and generally made Django that much better:
442 442
     Gopal Narayanan <gopastro@gmail.com>
443 443
     Fraser Nevett <mail@nevett.org>
444 444
     Sam Newman <http://www.magpiebrain.com/>
  445
+    Alasdair Nicol <http://al.sdair.co.uk/>
445 446
     Ryan Niemeyer <https://profiles.google.com/ryan.niemeyer/about>
446 447
     Filip Noetzel <http://filip.noetzel.co.uk/>
447 448
     Afonso Fernández Nogueira <fonzzo.django@gmail.com>
2  django/contrib/gis/tests/geoapp/models.py
@@ -40,7 +40,7 @@ class Track(models.Model):
40 40
     def __str__(self): return self.name
41 41
 
42 42
 class Truth(models.Model):
43  
-    val = models.BooleanField()
  43
+    val = models.BooleanField(default=False)
44 44
     objects = models.GeoManager()
45 45
 
46 46
 if not spatialite:
29  django/core/checks/compatibility/django_1_6_0.py
... ...
@@ -1,5 +1,6 @@
1 1
 from __future__ import unicode_literals
2 2
 
  3
+from django.db import models
3 4
 
4 5
 def check_test_runner():
5 6
     """
@@ -24,6 +25,31 @@ def check_test_runner():
24 25
         ]
25 26
         return ' '.join(message)
26 27
 
  28
+def check_boolean_field_default_value():
  29
+    """
  30
+    Checks if there are any BooleanFields without a default value, &
  31
+    warns the user that the default has changed from False to Null.
  32
+    """
  33
+    fields = []
  34
+    for cls in models.get_models():
  35
+        opts = cls._meta
  36
+        for f in opts.local_fields:
  37
+            if isinstance(f, models.BooleanField) and not f.has_default():
  38
+                fields.append(
  39
+                    '%s.%s: "%s"' % (opts.app_label, opts.object_name, f.name)
  40
+                )
  41
+    if fields:
  42
+        fieldnames = ", ".join(fields)
  43
+        message = [
  44
+            "You have not set a default value for one or more BooleanFields:",
  45
+            "%s." % fieldnames,
  46
+            "In Django 1.6 the default value of BooleanField was changed from",
  47
+            "False to Null when Field.default isn't defined. See",
  48
+            "https://docs.djangoproject.com/en/1.6/ref/models/fields/#booleanfield"
  49
+            "for more information."
  50
+        ]
  51
+        return ' '.join(message)
  52
+
27 53
 
28 54
 def run_checks():
29 55
     """
@@ -31,7 +57,8 @@ def run_checks():
31 57
     messages from all the relevant check functions for this version of Django.
32 58
     """
33 59
     checks = [
34  
-        check_test_runner()
  60
+        check_test_runner(),
  61
+        check_boolean_field_default_value(),
35 62
     ]
36 63
     # Filter out the ``None`` or empty strings.
37 64
     return [output for output in checks if output]
8  tests/admin_views/models.py
@@ -115,7 +115,7 @@ def get_absolute_url(self):
115 115
 @python_2_unicode_compatible
116 116
 class Color(models.Model):
117 117
     value = models.CharField(max_length=10)
118  
-    warm = models.BooleanField()
  118
+    warm = models.BooleanField(default=False)
119 119
     def __str__(self):
120 120
         return self.value
121 121
 
@@ -144,7 +144,7 @@ def __str__(self):
144 144
 
145 145
 @python_2_unicode_compatible
146 146
 class Inquisition(models.Model):
147  
-    expected = models.BooleanField()
  147
+    expected = models.BooleanField(default=False)
148 148
     leader = models.ForeignKey(Actor)
149 149
     country = models.CharField(max_length=20)
150 150
 
@@ -376,7 +376,7 @@ class Link(models.Model):
376 376
 
377 377
 class PrePopulatedPost(models.Model):
378 378
     title = models.CharField(max_length=100)
379  
-    published = models.BooleanField()
  379
+    published = models.BooleanField(default=False)
380 380
     slug = models.SlugField()
381 381
 
382 382
 
@@ -607,7 +607,7 @@ class PrePopulatedPostLargeSlug(models.Model):
607 607
     the javascript (ie, using THOUSAND_SEPARATOR ends up with maxLength=1,000)
608 608
     """
609 609
     title = models.CharField(max_length=100)
610  
-    published = models.BooleanField()
  610
+    published = models.BooleanField(default=False)
611 611
     slug = models.SlugField(max_length=1000)
612 612
 
613 613
 class AdminOrderedField(models.Model):
2  tests/aggregation_regress/models.py
@@ -64,7 +64,7 @@ def __str__(self):
64 64
 class Entries(models.Model):
65 65
     EntryID = models.AutoField(primary_key=True, db_column='Entry ID')
66 66
     Entry = models.CharField(unique=True, max_length=50)
67  
-    Exclude = models.BooleanField()
  67
+    Exclude = models.BooleanField(default=False)
68 68
 
69 69
 
70 70
 class Clues(models.Model):
10  tests/check/models.py
... ...
@@ -1 +1,9 @@
1  
-# Stubby.
  1
+from django.db import models
  2
+
  3
+class Book(models.Model):
  4
+    title = models.CharField(max_length=250)
  5
+    is_published = models.BooleanField(default=False)
  6
+
  7
+class BlogPost(models.Model):
  8
+    title = models.CharField(max_length=250)
  9
+    is_published = models.BooleanField(default=False)
20  tests/check/tests.py
@@ -2,8 +2,10 @@
2 2
 from django.core.checks.compatibility import django_1_6_0
3 3
 from django.core.management.commands import check
4 4
 from django.core.management import call_command
  5
+from django.db.models.fields import NOT_PROVIDED
5 6
 from django.test import TestCase
6 7
 
  8
+from .models import Book
7 9
 
8 10
 class StubCheckModule(object):
9 11
     # Has no ``run_checks`` attribute & will trigger a warning.
@@ -53,6 +55,24 @@ def test_run_checks_overridden(self):
53 55
         with self.settings(TEST_RUNNER='myapp.test.CustomRunnner'):
54 56
             self.assertEqual(len(django_1_6_0.run_checks()), 0)
55 57
 
  58
+    def test_boolean_field_default_value(self):
  59
+        with self.settings(TEST_RUNNER='myapp.test.CustomRunnner'):
  60
+            # We patch the field's default value to trigger the warning
  61
+            boolean_field = Book._meta.get_field('is_published')
  62
+            old_default = boolean_field.default
  63
+            try:
  64
+                boolean_field.default = NOT_PROVIDED
  65
+                result = django_1_6_0.run_checks()
  66
+                self.assertEqual(len(result), 1)
  67
+                self.assertTrue("You have not set a default value for one or more BooleanFields" in result[0])
  68
+                self.assertTrue('check.Book: "is_published"' in result[0])
  69
+                # We did not patch the BlogPost.is_published field so
  70
+                # there should not be a warning about it
  71
+                self.assertFalse('check.BlogPost' in result[0])
  72
+            finally:
  73
+                # Restore the ``default``
  74
+                boolean_field.default = old_default
  75
+
56 76
     def test_check_compatibility(self):
57 77
         with self.settings(TEST_RUNNER='django.test.runner.DiscoverRunner'):
58 78
             result = base.check_compatibility()
2  tests/comment_tests/models.py
@@ -30,7 +30,7 @@ class Entry(models.Model):
30 30
     title = models.CharField(max_length=250)
31 31
     body = models.TextField()
32 32
     pub_date = models.DateField()
33  
-    enable_comments = models.BooleanField()
  33
+    enable_comments = models.BooleanField(default=False)
34 34
 
35 35
     def __str__(self):
36 36
         return self.title
4  tests/custom_managers/models.py
@@ -67,7 +67,7 @@ def manager_only(self):
67 67
 class Person(models.Model):
68 68
     first_name = models.CharField(max_length=30)
69 69
     last_name = models.CharField(max_length=30)
70  
-    fun = models.BooleanField()
  70
+    fun = models.BooleanField(default=False)
71 71
     objects = PersonManager()
72 72
 
73 73
     custom_queryset_default_manager = CustomQuerySet.as_manager()
@@ -80,7 +80,7 @@ def __str__(self):
80 80
 class Book(models.Model):
81 81
     title = models.CharField(max_length=50)
82 82
     author = models.CharField(max_length=30)
83  
-    is_published = models.BooleanField()
  83
+    is_published = models.BooleanField(default=False)
84 84
     published_objects = PublishedBookManager()
85 85
     authors = models.ManyToManyField(Person, related_name='books')
86 86
 
2  tests/generic_relations/models.py
@@ -92,7 +92,7 @@ def get_queryset(self):
92 92
         return super(GeckoManager, self).get_queryset().filter(has_tail=True)
93 93
 
94 94
 class Gecko(models.Model):
95  
-    has_tail = models.BooleanField()
  95
+    has_tail = models.BooleanField(default=False)
96 96
     objects = GeckoManager()
97 97
 
98 98
 # To test fix for #11263
2  tests/inspectdb/models.py
@@ -37,7 +37,7 @@ class SpecialColumnName(models.Model):
37 37
 class ColumnTypes(models.Model):
38 38
     id = models.AutoField(primary_key=True)
39 39
     big_int_field = models.BigIntegerField()
40  
-    bool_field = models.BooleanField()
  40
+    bool_field = models.BooleanField(default=False)
41 41
     null_bool_field = models.NullBooleanField()
42 42
     char_field = models.CharField(max_length=10)
43 43
     comma_separated_int_field = models.CommaSeparatedIntegerField(max_length=99)
4  tests/model_fields/models.py
@@ -58,7 +58,7 @@ class NullBooleanModel(models.Model):
58 58
     nbfield = models.NullBooleanField()
59 59
 
60 60
 class BooleanModel(models.Model):
61  
-    bfield = models.BooleanField()
  61
+    bfield = models.BooleanField(default=None)
62 62
     string = models.CharField(max_length=10, default='abc')
63 63
 
64 64
 class FksToBooleans(models.Model):
@@ -72,7 +72,7 @@ class RenamedField(models.Model):
72 72
 class VerboseNameField(models.Model):
73 73
     id = models.AutoField("verbose pk", primary_key=True)
74 74
     field1 = models.BigIntegerField("verbose field1")
75  
-    field2 = models.BooleanField("verbose field2")
  75
+    field2 = models.BooleanField("verbose field2", default=False)
76 76
     field3 = models.CharField("verbose field3", max_length=10)
77 77
     field4 = models.CommaSeparatedIntegerField("verbose field4", max_length=99)
78 78
     field5 = models.DateField("verbose field5")
23  tests/model_fields/tests.py
@@ -12,7 +12,7 @@
12 12
     AutoField, BigIntegerField, BinaryField, BooleanField, CharField,
13 13
     CommaSeparatedIntegerField, DateField, DateTimeField, DecimalField,
14 14
     EmailField, FilePathField, FloatField, IntegerField, IPAddressField,
15  
-    GenericIPAddressField, NullBooleanField, PositiveIntegerField,
  15
+    GenericIPAddressField, NOT_PROVIDED, NullBooleanField, PositiveIntegerField,
16 16
     PositiveSmallIntegerField, SlugField, SmallIntegerField, TextField,
17 17
     TimeField, URLField)
18 18
 from django.db.models.fields.files import FileField, ImageField
@@ -275,10 +275,23 @@ def test_null_default(self):
275 275
         Check that a BooleanField defaults to None -- which isn't
276 276
         a valid value (#15124).
277 277
         """
278  
-        b = BooleanModel()
279  
-        self.assertIsNone(b.bfield)
280  
-        with self.assertRaises(IntegrityError):
281  
-            b.save()
  278
+        # Patch the boolean field's default value. We give it a default
  279
+        # value when defining the model to satisfy the check tests
  280
+        # #20895.
  281
+        boolean_field = BooleanModel._meta.get_field('bfield')
  282
+        self.assertTrue(boolean_field.has_default())
  283
+        old_default = boolean_field.default
  284
+        try:
  285
+            boolean_field.default = NOT_PROVIDED
  286
+            # check patch was succcessful
  287
+            self.assertFalse(boolean_field.has_default())
  288
+            b = BooleanModel()
  289
+            self.assertIsNone(b.bfield)
  290
+            with self.assertRaises(IntegrityError):
  291
+                b.save()
  292
+        finally:
  293
+            boolean_field.default = old_default
  294
+
282 295
         nb = NullBooleanModel()
283 296
         self.assertIsNone(nb.nbfield)
284 297
         nb.save()           # no error
6  tests/model_formsets/models.py
@@ -117,7 +117,7 @@ def __str__(self):
117 117
 
118 118
 @python_2_unicode_compatible
119 119
 class Restaurant(Place):
120  
-    serves_pizza = models.BooleanField()
  120
+    serves_pizza = models.BooleanField(default=False)
121 121
 
122 122
     def __str__(self):
123 123
         return self.name
@@ -141,11 +141,11 @@ class Meta:
141 141
         unique_together = (('price', 'quantity'),)
142 142
 
143 143
 class MexicanRestaurant(Restaurant):
144  
-    serves_tacos = models.BooleanField()
  144
+    serves_tacos = models.BooleanField(default=False)
145 145
 
146 146
 class ClassyMexicanRestaurant(MexicanRestaurant):
147 147
     restaurant = models.OneToOneField(MexicanRestaurant, parent_link=True, primary_key=True)
148  
-    tacos_are_yummy = models.BooleanField()
  148
+    tacos_are_yummy = models.BooleanField(default=False)
149 149
 
150 150
 # models for testing unique_together validation when a fk is involved and
151 151
 # using inlineformset_factory.
8  tests/model_inheritance/models.py
@@ -63,7 +63,7 @@ def __str__(self):
63 63
         return self.content
64 64
 
65 65
 class Comment(Attachment):
66  
-    is_spam = models.BooleanField()
  66
+    is_spam = models.BooleanField(default=False)
67 67
 
68 68
 class Link(Attachment):
69 69
     url = models.URLField()
@@ -96,8 +96,8 @@ class Meta:
96 96
 
97 97
 @python_2_unicode_compatible
98 98
 class Restaurant(Place, Rating):
99  
-    serves_hot_dogs = models.BooleanField()
100  
-    serves_pizza = models.BooleanField()
  99
+    serves_hot_dogs = models.BooleanField(default=False)
  100
+    serves_pizza = models.BooleanField(default=False)
101 101
     chef = models.ForeignKey(Chef, null=True, blank=True)
102 102
 
103 103
     class Meta(Rating.Meta):
@@ -108,7 +108,7 @@ def __str__(self):
108 108
 
109 109
 @python_2_unicode_compatible
110 110
 class ItalianRestaurant(Restaurant):
111  
-    serves_gnocchi = models.BooleanField()
  111
+    serves_gnocchi = models.BooleanField(default=False)
112 112
 
113 113
     def __str__(self):
114 114
         return "%s the italian restaurant" % self.name
8  tests/model_inheritance_regress/models.py
@@ -18,15 +18,15 @@ def __str__(self):
18 18
 
19 19
 @python_2_unicode_compatible
20 20
 class Restaurant(Place):
21  
-    serves_hot_dogs = models.BooleanField()
22  
-    serves_pizza = models.BooleanField()
  21
+    serves_hot_dogs = models.BooleanField(default=False)
  22
+    serves_pizza = models.BooleanField(default=False)
23 23
 
24 24
     def __str__(self):
25 25
         return "%s the restaurant" % self.name
26 26
 
27 27
 @python_2_unicode_compatible
28 28
 class ItalianRestaurant(Restaurant):
29  
-    serves_gnocchi = models.BooleanField()
  29
+    serves_gnocchi = models.BooleanField(default=False)
30 30
 
31 31
     def __str__(self):
32 32
         return "%s the italian restaurant" % self.name
@@ -184,7 +184,7 @@ class Meta:
184 184
 
185 185
 class BusStation(Station):
186 186
     bus_routes = models.CommaSeparatedIntegerField(max_length=128)
187  
-    inbound = models.BooleanField()
  187
+    inbound = models.BooleanField(default=False)
188 188
 
189 189
 class TrainStation(Station):
190 190
     zone = models.IntegerField()
4  tests/model_inheritance_select_related/models.py
@@ -20,8 +20,8 @@ def __str__(self):
2  tests/modeladmin/models.py
@@ -32,7 +32,7 @@ class ValidationTestModel(models.Model):
32 32
     slug = models.SlugField()
33 33
     users = models.ManyToManyField(User)
34 34
     state = models.CharField(max_length=2, choices=(("CO", "Colorado"), ("WA", "Washington")))
35  
-    is_active = models.BooleanField()
  35
+    is_active = models.BooleanField(default=False)
36 36
     pub_date = models.DateTimeField()
37 37
     band = models.ForeignKey(Band)
38 38
     no = models.IntegerField(verbose_name="Number", blank=True, null=True) # This field is intentionally 2 characters long. See #16080.
4  tests/one_to_one/models.py
@@ -22,8 +22,8 @@ def __str__(self):
22 22
 @python_2_unicode_compatible
23 23
 class Restaurant(models.Model):
24 24
     place = models.OneToOneField(Place, primary_key=True)
25  
-    serves_hot_dogs = models.BooleanField()
26  
-    serves_pizza = models.BooleanField()
  25
+    serves_hot_dogs = models.BooleanField(default=False)
  26
+    serves_pizza = models.BooleanField(default=False)
27 27
 
28 28
     def __str__(self):
29 29
         return "%s the restaurant" % self.place.name
4  tests/one_to_one_regress/models.py
@@ -15,8 +15,8 @@ def __str__(self):
15 15
 @python_2_unicode_compatible
16 16
 class Restaurant(models.Model):
17 17
     place = models.OneToOneField(Place)
18  
-    serves_hot_dogs = models.BooleanField()
19  
-    serves_pizza = models.BooleanField()
  18
+    serves_hot_dogs = models.BooleanField(default=False)
  19
+    serves_pizza = models.BooleanField(default=False)
20 20
 
21 21
     def __str__(self):
22 22
         return "%s the restaurant" % self.place.name
2  tests/raw_query/models.py
@@ -18,7 +18,7 @@ def __init__(self, *args, **kwargs):
18 18
 class Book(models.Model):
19 19
     title = models.CharField(max_length=255)
20 20
     author = models.ForeignKey(Author)
21  
-    paperback = models.BooleanField()
  21
+    paperback = models.BooleanField(default=False)
22 22
     opening_line = models.TextField()
23 23
 
24 24
 class Coffee(models.Model):
2  tests/reverse_single_related/models.py
@@ -6,7 +6,7 @@ def get_queryset(self):
4  tests/serializers_regress/models.py
@@ -16,7 +16,7 @@ class BinaryData(models.Model):
16 16
     data = models.BinaryField(null=True)
17 17
 
18 18
 class BooleanData(models.Model):
19  
-    data = models.BooleanField()
  19
+    data = models.BooleanField(default=False)
20 20
 
21 21
 class CharData(models.Model):
22 22
     data = models.CharField(max_length=30, null=True)
@@ -166,7 +166,7 @@ class Intermediate(models.Model):
166 166
 # or all database backends.
167 167
 
168 168
 class BooleanPKData(models.Model):
169  
-    data = models.BooleanField(primary_key=True)
  169
+    data = models.BooleanField(primary_key=True, default=False)
170 170
 
171 171
 class CharPKData(models.Model):
172 172
     data = models.CharField(max_length=30, primary_key=True)

0 notes on commit 22c6497

Please sign in to comment.
Something went wrong with that request. Please try again.