summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorIacopo Spalletti <i.spalletti@nephila.it>2016-04-02 15:49:32 +0200
committerTim Graham <timograham@gmail.com>2016-05-09 07:48:40 -0400
commit49c57f8565d4b655de5161d299a053eddfc00bf2 (patch)
treed1c18f6f10492bfbbc7e4c62275f19bf0d572835 /django
parentf5ff5010cd74500785a707ac70b51d292403cddc (diff)
Fixed #25005 -- Made date and time fields with auto_now/auto_now_add use effective default.
Thanks to Andriy Sokolovskiy for initial patch.
Diffstat (limited to 'django')
-rw-r--r--django/db/backends/base/schema.py12
-rw-r--r--django/db/migrations/autodetector.py12
-rw-r--r--django/db/migrations/questioner.py53
3 files changed, 70 insertions, 7 deletions
diff --git a/django/db/backends/base/schema.py b/django/db/backends/base/schema.py
index f91ab72e3d..2fd0d2738c 100644
--- a/django/db/backends/base/schema.py
+++ b/django/db/backends/base/schema.py
@@ -1,9 +1,10 @@
import hashlib
import logging
+from datetime import datetime
from django.db.backends.utils import truncate_name
from django.db.transaction import atomic
-from django.utils import six
+from django.utils import six, timezone
from django.utils.encoding import force_bytes
logger = logging.getLogger('django.db.backends.schema')
@@ -201,6 +202,15 @@ class BaseDatabaseSchemaEditor(object):
default = six.binary_type()
else:
default = six.text_type()
+ elif getattr(field, 'auto_now', False) or getattr(field, 'auto_now_add', False):
+ default = datetime.now()
+ internal_type = field.get_internal_type()
+ if internal_type == 'DateField':
+ default = default.date
+ elif internal_type == 'TimeField':
+ default = default.time
+ elif internal_type == 'DateTimeField':
+ default = timezone.now
else:
default = None
# If it's a callable, call it
diff --git a/django/db/migrations/autodetector.py b/django/db/migrations/autodetector.py
index 96aa230b24..85e50746db 100644
--- a/django/db/migrations/autodetector.py
+++ b/django/db/migrations/autodetector.py
@@ -802,10 +802,16 @@ class MigrationAutodetector(object):
# You can't just add NOT NULL fields with no default or fields
# which don't allow empty strings as default.
preserve_default = True
- if (not field.null and not field.has_default() and not field.many_to_many and
- not (field.blank and field.empty_strings_allowed)):
+ time_fields = (models.DateField, models.DateTimeField, models.TimeField)
+ if (not field.null and not field.has_default() and
+ not field.many_to_many and
+ not (field.blank and field.empty_strings_allowed) and
+ not (isinstance(field, time_fields) and field.auto_now)):
field = field.clone()
- field.default = self.questioner.ask_not_null_addition(field_name, model_name)
+ if isinstance(field, time_fields) and field.auto_now_add:
+ field.default = self.questioner.ask_auto_now_add_addition(field_name, model_name)
+ else:
+ field.default = self.questioner.ask_not_null_addition(field_name, model_name)
preserve_default = False
self.add_operation(
app_label,
diff --git a/django/db/migrations/questioner.py b/django/db/migrations/questioner.py
index 0315df0630..36144f1a55 100644
--- a/django/db/migrations/questioner.py
+++ b/django/db/migrations/questioner.py
@@ -76,6 +76,11 @@ class MigrationQuestioner(object):
"Do you really want to merge these migrations?"
return self.defaults.get("ask_merge", False)
+ def ask_auto_now_add_addition(self, field_name, model_name):
+ "Adding an auto_now_add field to a model"
+ # None means quit
+ return None
+
class InteractiveMigrationQuestioner(MigrationQuestioner):
@@ -101,17 +106,36 @@ class InteractiveMigrationQuestioner(MigrationQuestioner):
pass
result = input("Please select a valid option: ")
- def _ask_default(self):
+ def _ask_default(self, default=''):
+ """
+ Prompt for a default value.
+
+ The ``default`` argument allows providing a custom default value (as a
+ string) which will be shown to the user and used as the return value
+ if the user doesn't provide any other input.
+ """
print("Please enter the default value now, as valid Python")
+ if default:
+ print(
+ "You can accept the default '{}' by pressing 'Enter' or you "
+ "can provide another value.".format(default)
+ )
print("The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now")
+ print("Type 'exit' to exit this prompt")
while True:
+ if default:
+ prompt = "[default: {}] >>> ".format(default)
+ else:
+ prompt = ">>> "
if six.PY3:
# Six does not correctly abstract over the fact that
# py3 input returns a unicode string, while py2 raw_input
# returns a bytestring.
- code = input(">>> ")
+ code = input(prompt)
else:
- code = input(">>> ").decode(sys.stdin.encoding)
+ code = input(prompt).decode(sys.stdin.encoding)
+ if not code and default:
+ code = default
if not code:
print("Please enter some code, or 'exit' (with no quotes) to exit.")
elif code == "exit":
@@ -186,6 +210,25 @@ class InteractiveMigrationQuestioner(MigrationQuestioner):
False,
)
+ def ask_auto_now_add_addition(self, field_name, model_name):
+ "Adding an auto_now_add field to a model"
+ if not self.dry_run:
+ choice = self._choice_input(
+ "You are trying to add the field '{}' with 'auto_now_add=True' "
+ "to {} without a default; the database needs something to "
+ "populate existing rows.\n".format(field_name, model_name),
+ [
+ "Provide a one-off default now (will be set on all "
+ "existing rows)",
+ "Quit, and let me add a default in models.py",
+ ]
+ )
+ if choice == 2:
+ sys.exit(3)
+ else:
+ return self._ask_default(default='timezone.now')
+ return None
+
class NonInteractiveMigrationQuestioner(MigrationQuestioner):
@@ -196,3 +239,7 @@ class NonInteractiveMigrationQuestioner(MigrationQuestioner):
def ask_not_null_alteration(self, field_name, model_name):
# We can't ask the user, so set as not provided.
return NOT_PROVIDED
+
+ def ask_auto_now_add_addition(self, field_name, model_name):
+ # We can't ask the user, so act like the user aborted.
+ sys.exit(3)