Commit 49c57f85 authored by Iacopo Spalletti's avatar Iacopo Spalletti Committed by Tim Graham
Browse files

Fixed #25005 -- Made date and time fields with auto_now/auto_now_add use effective default.

Thanks to Andriy Sokolovskiy for initial patch.
parent f5ff5010
Loading
Loading
Loading
Loading
+11 −1
Original line number Diff line number Diff line
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
+9 −3
Original line number Diff line number Diff line
@@ -802,9 +802,15 @@ 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()
            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(
+50 −3
Original line number Diff line number Diff line
@@ -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)
+19 −0
Original line number Diff line number Diff line
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

    initial = True

    operations = [
        migrations.CreateModel(
            name='Entry',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('title', models.CharField(max_length=255)),
            ],
        ),
    ]
+0 −0

Empty file added.

Loading