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:
Florian Apolloner 2014-05-17 12:09:04 +02:00
commit 11932e978f
3 changed files with 293 additions and 24 deletions

View File

@ -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:

View File

@ -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
~~~~~~~~~~~ ~~~~~~~~~~~

View File

@ -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)