Fixed #20484 -- Added model validation for GenericIPAddressField
GenericIPAddressField must not allow blank for NOT NULL fields Thanks Erik Romijn.
This commit is contained in:
parent
a2967d5204
commit
ded95ccdce
|
@ -113,6 +113,8 @@ def get_validation_errors(outfile, app=None):
|
||||||
e.add(opts, '"%s": BooleanFields do not accept null values. Use a NullBooleanField instead.' % f.name)
|
e.add(opts, '"%s": BooleanFields do not accept null values. Use a NullBooleanField instead.' % f.name)
|
||||||
if isinstance(f, models.FilePathField) and not (f.allow_files or f.allow_folders):
|
if isinstance(f, models.FilePathField) and not (f.allow_files or f.allow_folders):
|
||||||
e.add(opts, '"%s": FilePathFields must have either allow_files or allow_folders set to True.' % f.name)
|
e.add(opts, '"%s": FilePathFields must have either allow_files or allow_folders set to True.' % f.name)
|
||||||
|
if isinstance(f, models.GenericIPAddressField) and not getattr(f, 'null', False) and getattr(f, 'blank', False):
|
||||||
|
e.add(opts, '"%s": GenericIPAddressField can not accept blank values if null values are not allowed, as blank values are stored as null.' % f.name)
|
||||||
if f.choices:
|
if f.choices:
|
||||||
if isinstance(f.choices, six.string_types) or not is_iterable(f.choices):
|
if isinstance(f.choices, six.string_types) or not is_iterable(f.choices):
|
||||||
e.add(opts, '"%s": "choices" should be iterable (e.g., a tuple or list).' % f.name)
|
e.add(opts, '"%s": "choices" should be iterable (e.g., a tuple or list).' % f.name)
|
||||||
|
|
|
@ -601,6 +601,13 @@ Miscellaneous
|
||||||
``django.contrib.admindocs.middleware`` because it is an implementation
|
``django.contrib.admindocs.middleware`` because it is an implementation
|
||||||
detail of admindocs, proven not to be reusable in general.
|
detail of admindocs, proven not to be reusable in general.
|
||||||
|
|
||||||
|
* :class:`~django.db.models.GenericIPAddressField` will now only allow
|
||||||
|
``blank`` values if ``null`` values are also allowed. Creating a
|
||||||
|
``GenericIPAddressField`` where ``blank`` is allowed but ``null`` is not
|
||||||
|
will trigger a model validation error because ``blank`` values are always
|
||||||
|
stored as ``null``. Previously, storing a ``blank`` value in a field which
|
||||||
|
did not allow ``null`` would cause a database exception at runtime.
|
||||||
|
|
||||||
Features deprecated in 1.6
|
Features deprecated in 1.6
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ class FieldErrors(models.Model):
|
||||||
index = models.CharField(max_length=10, db_index='bad')
|
index = models.CharField(max_length=10, db_index='bad')
|
||||||
field_ = models.CharField(max_length=10)
|
field_ = models.CharField(max_length=10)
|
||||||
nullbool = models.BooleanField(null=True)
|
nullbool = models.BooleanField(null=True)
|
||||||
|
generic_ip_notnull_blank = models.GenericIPAddressField(null=False, blank=True)
|
||||||
|
|
||||||
|
|
||||||
class Target(models.Model):
|
class Target(models.Model):
|
||||||
|
@ -380,6 +381,7 @@ invalid_models.fielderrors: "choices2": "choices" should be a sequence of two-it
|
||||||
invalid_models.fielderrors: "index": "db_index" should be either None, True or False.
|
invalid_models.fielderrors: "index": "db_index" should be either None, True or False.
|
||||||
invalid_models.fielderrors: "field_": Field names cannot end with underscores, because this would lead to ambiguous queryset filters.
|
invalid_models.fielderrors: "field_": Field names cannot end with underscores, because this would lead to ambiguous queryset filters.
|
||||||
invalid_models.fielderrors: "nullbool": BooleanFields do not accept null values. Use a NullBooleanField instead.
|
invalid_models.fielderrors: "nullbool": BooleanFields do not accept null values. Use a NullBooleanField instead.
|
||||||
|
invalid_models.fielderrors: "generic_ip_notnull_blank": GenericIPAddressField can not accept blank values if null values are not allowed, as blank values are stored as null.
|
||||||
invalid_models.clash1: Accessor for field 'foreign' clashes with field 'Target.clash1_set'. Add a related_name argument to the definition for 'foreign'.
|
invalid_models.clash1: Accessor for field 'foreign' clashes with field 'Target.clash1_set'. Add a related_name argument to the definition for 'foreign'.
|
||||||
invalid_models.clash1: Accessor for field 'foreign' clashes with related m2m field 'Target.clash1_set'. Add a related_name argument to the definition for 'foreign'.
|
invalid_models.clash1: Accessor for field 'foreign' clashes with related m2m field 'Target.clash1_set'. Add a related_name argument to the definition for 'foreign'.
|
||||||
invalid_models.clash1: Reverse query name for field 'foreign' clashes with field 'Target.clash1'. Add a related_name argument to the definition for 'foreign'.
|
invalid_models.clash1: Reverse query name for field 'foreign' clashes with field 'Target.clash1'. Add a related_name argument to the definition for 'foreign'.
|
||||||
|
|
|
@ -95,7 +95,7 @@ class GenericIPAddressTestModel(models.Model):
|
||||||
blank=True, null=True)
|
blank=True, null=True)
|
||||||
|
|
||||||
class GenericIPAddrUnpackUniqueTest(models.Model):
|
class GenericIPAddrUnpackUniqueTest(models.Model):
|
||||||
generic_v4unpack_ip = models.GenericIPAddressField(blank=True, unique=True, unpack_ipv4=True)
|
generic_v4unpack_ip = models.GenericIPAddressField(null=True, blank=True, unique=True, unpack_ipv4=True)
|
||||||
|
|
||||||
|
|
||||||
# A model can't have multiple AutoFields
|
# A model can't have multiple AutoFields
|
||||||
|
|
|
@ -109,9 +109,9 @@ class GenericIPAddressFieldTests(ValidationTestCase):
|
||||||
|
|
||||||
def test_correct_generic_ip_passes(self):
|
def test_correct_generic_ip_passes(self):
|
||||||
giptm = GenericIPAddressTestModel(generic_ip="1.2.3.4")
|
giptm = GenericIPAddressTestModel(generic_ip="1.2.3.4")
|
||||||
self.assertEqual(None, giptm.full_clean())
|
self.assertIsNone(giptm.full_clean())
|
||||||
giptm = GenericIPAddressTestModel(generic_ip="2001::2")
|
giptm = GenericIPAddressTestModel(generic_ip="2001::2")
|
||||||
self.assertEqual(None, giptm.full_clean())
|
self.assertIsNone(giptm.full_clean())
|
||||||
|
|
||||||
def test_invalid_generic_ip_raises_error(self):
|
def test_invalid_generic_ip_raises_error(self):
|
||||||
giptm = GenericIPAddressTestModel(generic_ip="294.4.2.1")
|
giptm = GenericIPAddressTestModel(generic_ip="294.4.2.1")
|
||||||
|
@ -121,7 +121,7 @@ class GenericIPAddressFieldTests(ValidationTestCase):
|
||||||
|
|
||||||
def test_correct_v4_ip_passes(self):
|
def test_correct_v4_ip_passes(self):
|
||||||
giptm = GenericIPAddressTestModel(v4_ip="1.2.3.4")
|
giptm = GenericIPAddressTestModel(v4_ip="1.2.3.4")
|
||||||
self.assertEqual(None, giptm.full_clean())
|
self.assertIsNone(giptm.full_clean())
|
||||||
|
|
||||||
def test_invalid_v4_ip_raises_error(self):
|
def test_invalid_v4_ip_raises_error(self):
|
||||||
giptm = GenericIPAddressTestModel(v4_ip="294.4.2.1")
|
giptm = GenericIPAddressTestModel(v4_ip="294.4.2.1")
|
||||||
|
@ -131,7 +131,7 @@ class GenericIPAddressFieldTests(ValidationTestCase):
|
||||||
|
|
||||||
def test_correct_v6_ip_passes(self):
|
def test_correct_v6_ip_passes(self):
|
||||||
giptm = GenericIPAddressTestModel(v6_ip="2001::2")
|
giptm = GenericIPAddressTestModel(v6_ip="2001::2")
|
||||||
self.assertEqual(None, giptm.full_clean())
|
self.assertIsNone(giptm.full_clean())
|
||||||
|
|
||||||
def test_invalid_v6_ip_raises_error(self):
|
def test_invalid_v6_ip_raises_error(self):
|
||||||
giptm = GenericIPAddressTestModel(v6_ip="1.2.3.4")
|
giptm = GenericIPAddressTestModel(v6_ip="1.2.3.4")
|
||||||
|
@ -151,10 +151,16 @@ class GenericIPAddressFieldTests(ValidationTestCase):
|
||||||
giptm = GenericIPAddressTestModel(generic_ip="::ffff:10.10.10.10")
|
giptm = GenericIPAddressTestModel(generic_ip="::ffff:10.10.10.10")
|
||||||
giptm.save()
|
giptm.save()
|
||||||
giptm = GenericIPAddressTestModel(generic_ip="10.10.10.10")
|
giptm = GenericIPAddressTestModel(generic_ip="10.10.10.10")
|
||||||
self.assertEqual(None, giptm.full_clean())
|
self.assertIsNone(giptm.full_clean())
|
||||||
|
|
||||||
# These two are the same, because we are doing IPv4 unpacking
|
# These two are the same, because we are doing IPv4 unpacking
|
||||||
giptm = GenericIPAddrUnpackUniqueTest(generic_v4unpack_ip="::ffff:18.52.18.52")
|
giptm = GenericIPAddrUnpackUniqueTest(generic_v4unpack_ip="::ffff:18.52.18.52")
|
||||||
giptm.save()
|
giptm.save()
|
||||||
giptm = GenericIPAddrUnpackUniqueTest(generic_v4unpack_ip="18.52.18.52")
|
giptm = GenericIPAddrUnpackUniqueTest(generic_v4unpack_ip="18.52.18.52")
|
||||||
self.assertFailsValidation(giptm.full_clean, ['generic_v4unpack_ip',])
|
self.assertFailsValidation(giptm.full_clean, ['generic_v4unpack_ip',])
|
||||||
|
|
||||||
|
def test_empty_generic_ip_passes(self):
|
||||||
|
giptm = GenericIPAddressTestModel(generic_ip="")
|
||||||
|
self.assertIsNone(giptm.full_clean())
|
||||||
|
giptm = GenericIPAddressTestModel(generic_ip=None)
|
||||||
|
self.assertIsNone(giptm.full_clean())
|
||||||
|
|
Loading…
Reference in New Issue