From cb15231888df2545356ad307eaea07f36aa0e8e0 Mon Sep 17 00:00:00 2001
From: Daniel Pyrathon <pirosb3@gmail.com>
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 77e233f7e8..f2c1471fef 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 c8e97df660..f03437e276 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 8d4e0ca3ca..41dca84bb4 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 3ad7e250b0..e42f9bcaaa 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)