mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	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:
		| @@ -1079,6 +1079,7 @@ class DateTimeCheckMixin(object): | ||||
|     def check(self, **kwargs): | ||||
|         errors = super(DateTimeCheckMixin, self).check(**kwargs) | ||||
|         errors.extend(self._check_mutually_exclusive_options()) | ||||
|         errors.extend(self._check_fix_default_value()) | ||||
|         return errors | ||||
|  | ||||
|     def _check_mutually_exclusive_options(self): | ||||
| @@ -1103,6 +1104,9 @@ class DateTimeCheckMixin(object): | ||||
|         else: | ||||
|             return [] | ||||
|  | ||||
|     def _check_fix_default_value(self): | ||||
|         return [] | ||||
|  | ||||
|  | ||||
| class DateField(DateTimeCheckMixin, Field): | ||||
|     empty_strings_allowed = False | ||||
| @@ -1122,6 +1126,49 @@ class DateField(DateTimeCheckMixin, Field): | ||||
|             kwargs['blank'] = True | ||||
|         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): | ||||
|         name, path, args, kwargs = super(DateField, self).deconstruct() | ||||
|         if self.auto_now: | ||||
| @@ -1226,6 +1273,52 @@ class DateTimeField(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): | ||||
|         return "DateTimeField" | ||||
|  | ||||
| @@ -1935,6 +2028,52 @@ class TimeField(DateTimeCheckMixin, Field): | ||||
|             kwargs['blank'] = True | ||||
|         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): | ||||
|         name, path, args, kwargs = super(TimeField, self).deconstruct() | ||||
|         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.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.W161**: Fixed default value provided. | ||||
|  | ||||
|  | ||||
| File Fields | ||||
| ~~~~~~~~~~~ | ||||
|   | ||||
| @@ -1,11 +1,12 @@ | ||||
| # -*- encoding: utf-8 -*- | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| from datetime import datetime | ||||
| 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.test.utils import override_settings | ||||
| from django.utils.timezone import make_aware, now | ||||
|  | ||||
| from .base import IsolatedModelsTestCase | ||||
|  | ||||
| @@ -198,6 +199,116 @@ class CharFieldTests(IsolatedModelsTestCase): | ||||
|         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): | ||||
|  | ||||
|     def test_required_attributes(self): | ||||
| @@ -402,28 +513,45 @@ class ImageFieldTests(IsolatedModelsTestCase): | ||||
|         self.assertEqual(errors, expected) | ||||
|  | ||||
|  | ||||
| class DateFieldTests(IsolatedModelsTestCase): | ||||
| class TimeFieldTests(IsolatedModelsTestCase): | ||||
|  | ||||
|     def test_auto_now_and_auto_now_add_raise_error(self): | ||||
|             dn = datetime.now | ||||
|             mutually_exclusive_combinations = ( | ||||
|                 (True, True, dn), | ||||
|                 (True, False, dn), | ||||
|                 (False, True, dn), | ||||
|                 (True, True, None) | ||||
|     def test_fix_default_value(self): | ||||
|         class Model(models.Model): | ||||
|             field_dt = models.TimeField(default=now()) | ||||
|             field_t = models.TimeField(default=now().time()) | ||||
|             field_now = models.DateField(default=now) | ||||
|  | ||||
|         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: | ||||
|                 field = models.DateTimeField(name="field", auto_now=auto_now, | ||||
|                                              auto_now_add=auto_now_add, | ||||
|                                              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) | ||||
|     @override_settings(USE_TZ=True) | ||||
|     def test_fix_default_value_tz(self): | ||||
|         self.test_fix_default_value() | ||||
|   | ||||
		Reference in New Issue
	
	Block a user