From cb15231888df2545356ad307eaea07f36aa0e8e0 Mon Sep 17 00:00:00 2001 From: Daniel Pyrathon Date: Thu, 13 Mar 2014 11:32:20 +0000 Subject: [PATCH] Fixed #21798 -- Added check for DateTime mutually exclusive options Added DateTimeCheckMixin to avoid the use of default, auto_now, and auto_now_add options together. Added the fields.E151 Error that is raised if one or more of these options are used together. --- django/db/models/fields/__init__.py | 34 +++++++++++++++++-- docs/ref/checks.txt | 1 + docs/ref/models/fields.txt | 3 ++ .../test_ordinary_fields.py | 28 +++++++++++++++ 4 files changed, 64 insertions(+), 2 deletions(-) diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 77e233f7e82..f2c1471fef8 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -1074,7 +1074,37 @@ class CommaSeparatedIntegerField(CharField): return super(CommaSeparatedIntegerField, self).formfield(**defaults) -class DateField(Field): +class DateTimeCheckMixin(object): + + def check(self, **kwargs): + errors = super(DateTimeCheckMixin, self).check(**kwargs) + errors.extend(self._check_mutually_exclusive_options()) + return errors + + def _check_mutually_exclusive_options(self): + # auto_now, auto_now_add, and default are mutually exclusive + # options. The use of more than one of these options together + # will trigger an Error + mutually_exclusive_options = [self.auto_now_add, self.auto_now, + self.has_default()] + enabled_options = [option not in (None, False) + for option in mutually_exclusive_options].count(True) + if enabled_options > 1: + return [ + checks.Error( + "The options auto_now, auto_now_add, and default " + "are mutually exclusive. Only one of these options " + "may be present.", + hint=None, + obj=self, + id='fields.E151', + ) + ] + else: + return [] + + +class DateField(DateTimeCheckMixin, Field): empty_strings_allowed = False default_error_messages = { 'invalid': _("'%(value)s' value has an invalid date format. It must be " @@ -1887,7 +1917,7 @@ class TextField(Field): return super(TextField, self).formfield(**defaults) -class TimeField(Field): +class TimeField(DateTimeCheckMixin, Field): empty_strings_allowed = False default_error_messages = { 'invalid': _("'%(value)s' value has an invalid format. It must be in " diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt index c8e97df6609..f03437e276e 100644 --- a/docs/ref/checks.txt +++ b/docs/ref/checks.txt @@ -67,6 +67,7 @@ Fields * **fields.E134**: ``max_digits`` must be greater or equal to ``decimal_places``. * **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.E151**: The options ``auto_now``, ``auto_now_add``, and ``default`` are mutually exclusive. Only one of these options may be present. File Fields ~~~~~~~~~~~ diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index 8d4e0ca3ca0..41dca84bb49 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -477,6 +477,9 @@ The default form widget for this field is a and a shortcut for "Today". Includes an additional ``invalid_date`` error message key. +The options ``auto_now_add``, ``auto_now``, and ``default`` are mutually exclusive. +Any combination of these options will result in an error. + .. note:: As currently implemented, setting ``auto_now`` or ``auto_now_add`` to ``True`` will cause the field to have ``editable=False`` and ``blank=True`` diff --git a/tests/invalid_models_tests/test_ordinary_fields.py b/tests/invalid_models_tests/test_ordinary_fields.py index 3ad7e250b01..e42f9bcaaa2 100644 --- a/tests/invalid_models_tests/test_ordinary_fields.py +++ b/tests/invalid_models_tests/test_ordinary_fields.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- from __future__ import unicode_literals +from datetime import datetime import unittest from django.core.checks import Error @@ -399,3 +400,30 @@ class ImageFieldTests(IsolatedModelsTestCase): ), ] self.assertEqual(errors, expected) + + +class DateFieldTests(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) + ) + + 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.E151', + )] + checks = field.check() + self.assertEqual(checks, expected)