Merge pull request #2346 from Markush2010/ticket21905
Fixed #21905 -- Add info message if DateField or TimeField use a fixed value
This commit is contained in:
commit
11932e978f
|
@ -1079,6 +1079,7 @@ class DateTimeCheckMixin(object):
|
||||||
def check(self, **kwargs):
|
def check(self, **kwargs):
|
||||||
errors = super(DateTimeCheckMixin, self).check(**kwargs)
|
errors = super(DateTimeCheckMixin, self).check(**kwargs)
|
||||||
errors.extend(self._check_mutually_exclusive_options())
|
errors.extend(self._check_mutually_exclusive_options())
|
||||||
|
errors.extend(self._check_fix_default_value())
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
def _check_mutually_exclusive_options(self):
|
def _check_mutually_exclusive_options(self):
|
||||||
|
@ -1103,6 +1104,9 @@ class DateTimeCheckMixin(object):
|
||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
def _check_fix_default_value(self):
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
class DateField(DateTimeCheckMixin, Field):
|
class DateField(DateTimeCheckMixin, Field):
|
||||||
empty_strings_allowed = False
|
empty_strings_allowed = False
|
||||||
|
@ -1122,6 +1126,49 @@ class DateField(DateTimeCheckMixin, Field):
|
||||||
kwargs['blank'] = True
|
kwargs['blank'] = True
|
||||||
super(DateField, self).__init__(verbose_name, name, **kwargs)
|
super(DateField, self).__init__(verbose_name, name, **kwargs)
|
||||||
|
|
||||||
|
def _check_fix_default_value(self):
|
||||||
|
"""
|
||||||
|
Adds a warning to the checks framework stating, that using an actual
|
||||||
|
date or datetime value is probably wrong; it's only being evaluated on
|
||||||
|
server start-up.
|
||||||
|
|
||||||
|
For details see ticket #21905
|
||||||
|
"""
|
||||||
|
if not self.has_default():
|
||||||
|
return []
|
||||||
|
|
||||||
|
now = timezone.now()
|
||||||
|
if not timezone.is_naive(now):
|
||||||
|
now = timezone.make_naive(now, timezone.utc)
|
||||||
|
value = self.default
|
||||||
|
if isinstance(value, datetime.datetime):
|
||||||
|
if not timezone.is_naive(value):
|
||||||
|
value = timezone.make_naive(value, timezone.utc)
|
||||||
|
value = value.date()
|
||||||
|
elif isinstance(value, datetime.date):
|
||||||
|
# Nothing to do, as dates don't have tz information
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# No explicit date / datetime value -- no checks necessary
|
||||||
|
return []
|
||||||
|
offset = datetime.timedelta(days=1)
|
||||||
|
lower = (now - offset).date()
|
||||||
|
upper = (now + offset).date()
|
||||||
|
if lower <= value <= upper:
|
||||||
|
return [
|
||||||
|
checks.Warning(
|
||||||
|
'Fixed default value provided.',
|
||||||
|
hint='It seems you set a fixed date / time / datetime '
|
||||||
|
'value as default for this field. This may not be '
|
||||||
|
'what you want. If you want to have the current date '
|
||||||
|
'as default, use `django.utils.timezone.now`',
|
||||||
|
obj=self,
|
||||||
|
id='fields.W161',
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
def deconstruct(self):
|
def deconstruct(self):
|
||||||
name, path, args, kwargs = super(DateField, self).deconstruct()
|
name, path, args, kwargs = super(DateField, self).deconstruct()
|
||||||
if self.auto_now:
|
if self.auto_now:
|
||||||
|
@ -1226,6 +1273,52 @@ class DateTimeField(DateField):
|
||||||
|
|
||||||
# __init__ is inherited from DateField
|
# __init__ is inherited from DateField
|
||||||
|
|
||||||
|
def _check_fix_default_value(self):
|
||||||
|
"""
|
||||||
|
Adds a warning to the checks framework stating, that using an actual
|
||||||
|
date or datetime value is probably wrong; it's only being evaluated on
|
||||||
|
server start-up.
|
||||||
|
|
||||||
|
For details see ticket #21905
|
||||||
|
"""
|
||||||
|
if not self.has_default():
|
||||||
|
return []
|
||||||
|
|
||||||
|
now = timezone.now()
|
||||||
|
if not timezone.is_naive(now):
|
||||||
|
now = timezone.make_naive(now, timezone.utc)
|
||||||
|
value = self.default
|
||||||
|
if isinstance(value, datetime.datetime):
|
||||||
|
second_offset = datetime.timedelta(seconds=10)
|
||||||
|
lower = now - second_offset
|
||||||
|
upper = now + second_offset
|
||||||
|
if timezone.is_aware(value):
|
||||||
|
value = timezone.make_naive(value, timezone.utc)
|
||||||
|
elif isinstance(value, datetime.date):
|
||||||
|
second_offset = datetime.timedelta(seconds=10)
|
||||||
|
lower = now - second_offset
|
||||||
|
lower = datetime.datetime(lower.year, lower.month, lower.day)
|
||||||
|
upper = now + second_offset
|
||||||
|
upper = datetime.datetime(upper.year, upper.month, upper.day)
|
||||||
|
value = datetime.datetime(value.year, value.month, value.day)
|
||||||
|
else:
|
||||||
|
# No explicit date / datetime value -- no checks necessary
|
||||||
|
return []
|
||||||
|
if lower <= value <= upper:
|
||||||
|
return [
|
||||||
|
checks.Warning(
|
||||||
|
'Fixed default value provided.',
|
||||||
|
hint='It seems you set a fixed date / time / datetime '
|
||||||
|
'value as default for this field. This may not be '
|
||||||
|
'what you want. If you want to have the current date '
|
||||||
|
'as default, use `django.utils.timezone.now`',
|
||||||
|
obj=self,
|
||||||
|
id='fields.W161',
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
def get_internal_type(self):
|
def get_internal_type(self):
|
||||||
return "DateTimeField"
|
return "DateTimeField"
|
||||||
|
|
||||||
|
@ -1935,6 +2028,52 @@ class TimeField(DateTimeCheckMixin, Field):
|
||||||
kwargs['blank'] = True
|
kwargs['blank'] = True
|
||||||
super(TimeField, self).__init__(verbose_name, name, **kwargs)
|
super(TimeField, self).__init__(verbose_name, name, **kwargs)
|
||||||
|
|
||||||
|
def _check_fix_default_value(self):
|
||||||
|
"""
|
||||||
|
Adds a warning to the checks framework stating, that using an actual
|
||||||
|
time or datetime value is probably wrong; it's only being evaluated on
|
||||||
|
server start-up.
|
||||||
|
|
||||||
|
For details see ticket #21905
|
||||||
|
"""
|
||||||
|
if not self.has_default():
|
||||||
|
return []
|
||||||
|
|
||||||
|
now = timezone.now()
|
||||||
|
if not timezone.is_naive(now):
|
||||||
|
now = timezone.make_naive(now, timezone.utc)
|
||||||
|
value = self.default
|
||||||
|
if isinstance(value, datetime.datetime):
|
||||||
|
second_offset = datetime.timedelta(seconds=10)
|
||||||
|
lower = now - second_offset
|
||||||
|
upper = now + second_offset
|
||||||
|
if timezone.is_aware(value):
|
||||||
|
value = timezone.make_naive(value, timezone.utc)
|
||||||
|
elif isinstance(value, datetime.time):
|
||||||
|
second_offset = datetime.timedelta(seconds=10)
|
||||||
|
lower = now - second_offset
|
||||||
|
upper = now + second_offset
|
||||||
|
value = datetime.datetime.combine(now.date(), value)
|
||||||
|
if timezone.is_aware(value):
|
||||||
|
value = timezone.make_naive(value, timezone.utc).time()
|
||||||
|
else:
|
||||||
|
# No explicit time / datetime value -- no checks necessary
|
||||||
|
return []
|
||||||
|
if lower <= value <= upper:
|
||||||
|
return [
|
||||||
|
checks.Warning(
|
||||||
|
'Fixed default value provided.',
|
||||||
|
hint='It seems you set a fixed date / time / datetime '
|
||||||
|
'value as default for this field. This may not be '
|
||||||
|
'what you want. If you want to have the current date '
|
||||||
|
'as default, use `django.utils.timezone.now`',
|
||||||
|
obj=self,
|
||||||
|
id='fields.W161',
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
def deconstruct(self):
|
def deconstruct(self):
|
||||||
name, path, args, kwargs = super(TimeField, self).deconstruct()
|
name, path, args, kwargs = super(TimeField, self).deconstruct()
|
||||||
if self.auto_now is not False:
|
if self.auto_now is not False:
|
||||||
|
|
|
@ -68,6 +68,8 @@ Fields
|
||||||
* **fields.E140**: FilePathFields must have either ``allow_files`` or ``allow_folders`` set to True.
|
* **fields.E140**: FilePathFields must have either ``allow_files`` or ``allow_folders`` set to True.
|
||||||
* **fields.E150**: GenericIPAddressFields cannot accept blank values if null values are not allowed, as blank values are stored as nulls.
|
* **fields.E150**: GenericIPAddressFields cannot accept blank values if null values are not allowed, as blank values are stored as nulls.
|
||||||
* **fields.E160**: The options ``auto_now``, ``auto_now_add``, and ``default`` are mutually exclusive. Only one of these options may be present.
|
* **fields.E160**: The options ``auto_now``, ``auto_now_add``, and ``default`` are mutually exclusive. Only one of these options may be present.
|
||||||
|
* **fields.W161**: Fixed default value provided.
|
||||||
|
|
||||||
|
|
||||||
File Fields
|
File Fields
|
||||||
~~~~~~~~~~~
|
~~~~~~~~~~~
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
# -*- encoding: utf-8 -*-
|
# -*- encoding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from django.core.checks import Error
|
from django.core.checks import Error, Warning as DjangoWarning
|
||||||
from django.db import connection, models
|
from django.db import connection, models
|
||||||
|
from django.test.utils import override_settings
|
||||||
|
from django.utils.timezone import make_aware, now
|
||||||
|
|
||||||
from .base import IsolatedModelsTestCase
|
from .base import IsolatedModelsTestCase
|
||||||
|
|
||||||
|
@ -198,6 +199,116 @@ class CharFieldTests(IsolatedModelsTestCase):
|
||||||
self.assertEqual(errors, expected)
|
self.assertEqual(errors, expected)
|
||||||
|
|
||||||
|
|
||||||
|
class DateFieldTests(IsolatedModelsTestCase):
|
||||||
|
|
||||||
|
def test_auto_now_and_auto_now_add_raise_error(self):
|
||||||
|
class Model(models.Model):
|
||||||
|
field0 = models.DateTimeField(auto_now=True, auto_now_add=True, default=now)
|
||||||
|
field1 = models.DateTimeField(auto_now=True, auto_now_add=False, default=now)
|
||||||
|
field2 = models.DateTimeField(auto_now=False, auto_now_add=True, default=now)
|
||||||
|
field3 = models.DateTimeField(auto_now=True, auto_now_add=True, default=None)
|
||||||
|
|
||||||
|
expected = []
|
||||||
|
checks = []
|
||||||
|
for i in range(4):
|
||||||
|
field = Model._meta.get_field('field%d' % i)
|
||||||
|
expected.append(Error(
|
||||||
|
"The options auto_now, auto_now_add, and default "
|
||||||
|
"are mutually exclusive. Only one of these options "
|
||||||
|
"may be present.",
|
||||||
|
hint=None,
|
||||||
|
obj=field,
|
||||||
|
id='fields.E160',
|
||||||
|
))
|
||||||
|
checks.extend(field.check())
|
||||||
|
self.assertEqual(checks, expected)
|
||||||
|
|
||||||
|
def test_fix_default_value(self):
|
||||||
|
class Model(models.Model):
|
||||||
|
field_dt = models.DateField(default=now())
|
||||||
|
field_d = models.DateField(default=now().date())
|
||||||
|
field_now = models.DateField(default=now)
|
||||||
|
|
||||||
|
field_dt = Model._meta.get_field('field_dt')
|
||||||
|
field_d = Model._meta.get_field('field_d')
|
||||||
|
field_now = Model._meta.get_field('field_now')
|
||||||
|
errors = field_dt.check()
|
||||||
|
errors.extend(field_d.check())
|
||||||
|
errors.extend(field_now.check()) # doesn't raise a warning
|
||||||
|
expected = [
|
||||||
|
DjangoWarning(
|
||||||
|
'Fixed default value provided.',
|
||||||
|
hint='It seems you set a fixed date / time / datetime '
|
||||||
|
'value as default for this field. This may not be '
|
||||||
|
'what you want. If you want to have the current date '
|
||||||
|
'as default, use `django.utils.timezone.now`',
|
||||||
|
obj=field_dt,
|
||||||
|
id='fields.W161',
|
||||||
|
),
|
||||||
|
DjangoWarning(
|
||||||
|
'Fixed default value provided.',
|
||||||
|
hint='It seems you set a fixed date / time / datetime '
|
||||||
|
'value as default for this field. This may not be '
|
||||||
|
'what you want. If you want to have the current date '
|
||||||
|
'as default, use `django.utils.timezone.now`',
|
||||||
|
obj=field_d,
|
||||||
|
id='fields.W161',
|
||||||
|
)
|
||||||
|
]
|
||||||
|
maxDiff = self.maxDiff
|
||||||
|
self.maxDiff = None
|
||||||
|
self.assertEqual(errors, expected)
|
||||||
|
self.maxDiff = maxDiff
|
||||||
|
|
||||||
|
@override_settings(USE_TZ=True)
|
||||||
|
def test_fix_default_value_tz(self):
|
||||||
|
self.test_fix_default_value()
|
||||||
|
|
||||||
|
|
||||||
|
class DateTimeFieldTests(IsolatedModelsTestCase):
|
||||||
|
|
||||||
|
def test_fix_default_value(self):
|
||||||
|
class Model(models.Model):
|
||||||
|
field_dt = models.DateTimeField(default=now())
|
||||||
|
field_d = models.DateTimeField(default=now().date())
|
||||||
|
field_now = models.DateTimeField(default=now)
|
||||||
|
|
||||||
|
field_dt = Model._meta.get_field('field_dt')
|
||||||
|
field_d = Model._meta.get_field('field_d')
|
||||||
|
field_now = Model._meta.get_field('field_now')
|
||||||
|
errors = field_dt.check()
|
||||||
|
errors.extend(field_d.check())
|
||||||
|
errors.extend(field_now.check()) # doesn't raise a warning
|
||||||
|
expected = [
|
||||||
|
DjangoWarning(
|
||||||
|
'Fixed default value provided.',
|
||||||
|
hint='It seems you set a fixed date / time / datetime '
|
||||||
|
'value as default for this field. This may not be '
|
||||||
|
'what you want. If you want to have the current date '
|
||||||
|
'as default, use `django.utils.timezone.now`',
|
||||||
|
obj=field_dt,
|
||||||
|
id='fields.W161',
|
||||||
|
),
|
||||||
|
DjangoWarning(
|
||||||
|
'Fixed default value provided.',
|
||||||
|
hint='It seems you set a fixed date / time / datetime '
|
||||||
|
'value as default for this field. This may not be '
|
||||||
|
'what you want. If you want to have the current date '
|
||||||
|
'as default, use `django.utils.timezone.now`',
|
||||||
|
obj=field_d,
|
||||||
|
id='fields.W161',
|
||||||
|
)
|
||||||
|
]
|
||||||
|
maxDiff = self.maxDiff
|
||||||
|
self.maxDiff = None
|
||||||
|
self.assertEqual(errors, expected)
|
||||||
|
self.maxDiff = maxDiff
|
||||||
|
|
||||||
|
@override_settings(USE_TZ=True)
|
||||||
|
def test_fix_default_value_tz(self):
|
||||||
|
self.test_fix_default_value()
|
||||||
|
|
||||||
|
|
||||||
class DecimalFieldTests(IsolatedModelsTestCase):
|
class DecimalFieldTests(IsolatedModelsTestCase):
|
||||||
|
|
||||||
def test_required_attributes(self):
|
def test_required_attributes(self):
|
||||||
|
@ -402,28 +513,45 @@ class ImageFieldTests(IsolatedModelsTestCase):
|
||||||
self.assertEqual(errors, expected)
|
self.assertEqual(errors, expected)
|
||||||
|
|
||||||
|
|
||||||
class DateFieldTests(IsolatedModelsTestCase):
|
class TimeFieldTests(IsolatedModelsTestCase):
|
||||||
|
|
||||||
def test_auto_now_and_auto_now_add_raise_error(self):
|
def test_fix_default_value(self):
|
||||||
dn = datetime.now
|
class Model(models.Model):
|
||||||
mutually_exclusive_combinations = (
|
field_dt = models.TimeField(default=now())
|
||||||
(True, True, dn),
|
field_t = models.TimeField(default=now().time())
|
||||||
(True, False, dn),
|
field_now = models.DateField(default=now)
|
||||||
(False, True, dn),
|
|
||||||
(True, True, None)
|
field_dt = Model._meta.get_field('field_dt')
|
||||||
|
field_t = Model._meta.get_field('field_t')
|
||||||
|
field_now = Model._meta.get_field('field_now')
|
||||||
|
errors = field_dt.check()
|
||||||
|
errors.extend(field_t.check())
|
||||||
|
errors.extend(field_now.check()) # doesn't raise a warning
|
||||||
|
expected = [
|
||||||
|
DjangoWarning(
|
||||||
|
'Fixed default value provided.',
|
||||||
|
hint='It seems you set a fixed date / time / datetime '
|
||||||
|
'value as default for this field. This may not be '
|
||||||
|
'what you want. If you want to have the current date '
|
||||||
|
'as default, use `django.utils.timezone.now`',
|
||||||
|
obj=field_dt,
|
||||||
|
id='fields.W161',
|
||||||
|
),
|
||||||
|
DjangoWarning(
|
||||||
|
'Fixed default value provided.',
|
||||||
|
hint='It seems you set a fixed date / time / datetime '
|
||||||
|
'value as default for this field. This may not be '
|
||||||
|
'what you want. If you want to have the current date '
|
||||||
|
'as default, use `django.utils.timezone.now`',
|
||||||
|
obj=field_t,
|
||||||
|
id='fields.W161',
|
||||||
)
|
)
|
||||||
|
]
|
||||||
|
maxDiff = self.maxDiff
|
||||||
|
self.maxDiff = None
|
||||||
|
self.assertEqual(errors, expected)
|
||||||
|
self.maxDiff = maxDiff
|
||||||
|
|
||||||
for auto_now, auto_now_add, default in mutually_exclusive_combinations:
|
@override_settings(USE_TZ=True)
|
||||||
field = models.DateTimeField(name="field", auto_now=auto_now,
|
def test_fix_default_value_tz(self):
|
||||||
auto_now_add=auto_now_add,
|
self.test_fix_default_value()
|
||||||
default=default)
|
|
||||||
expected = [Error(
|
|
||||||
"The options auto_now, auto_now_add, and default "
|
|
||||||
"are mutually exclusive. Only one of these options "
|
|
||||||
"may be present.",
|
|
||||||
hint=None,
|
|
||||||
obj=field,
|
|
||||||
id='fields.E160',
|
|
||||||
)]
|
|
||||||
checks = field.check()
|
|
||||||
self.assertEqual(checks, expected)
|
|
||||||
|
|
Loading…
Reference in New Issue