#28715: Prevent a migration changing DateTimeField(auto_now_add=True) to
default=timezone.now from generating SQL
--------------------------------------+------------------------------------
     Reporter:  Дилян Палаузов        |                    Owner:  nobody
         Type:  Cleanup/optimization  |                   Status:  new
    Component:  Migrations            |                  Version:  1.11
     Severity:  Normal                |               Resolution:
     Keywords:                        |             Triage Stage:  Accepted
    Has patch:  0                     |      Needs documentation:  0
  Needs tests:  0                     |  Patch needs improvement:  0
Easy pickings:  0                     |                    UI/UX:  0
--------------------------------------+------------------------------------
Description changed by Дилян Палаузов:

Old description:

> A switch from DateTimeField(auto_now_add=True) to
> DateTimeField(default=django.utils.timezone.new) creates the statements
>   ALTER TABLE SET DEFAULT '2017-10-16T09:35:52.710695'::timestamp;
>   ALTER TABLE DROP DEFAULT
> which have no effects, apart from locking the whole table twice.
>
> A proposal to recognize, when the effective default-callable doesn't
> change and skip changing the DEFAULT twice in this case, as well as not
> generating a migration when this is the only change on a field:
> {{{
> diff --git a/django/db/backends/base/schema.py
> b/django/db/backends/base/schema.py
> --- a/django/db/backends/base/schema.py
> +++ b/django/db/backends/base/schema.py
> @@ -199,28 +199,33 @@ class BaseDatabaseSchemaEditor(object):
>              'requires_literal_defaults must provide a prepare_default()
> method'
>          )
>
> -    def effective_default(self, field):
> +    @staticmethod
> +    def effective_default_before_callable(field):
>          """
> -        Returns a field's effective database default value
> +        Returns a field's effective database default callable or value
>          """
>          if field.has_default():
> -            default = field.get_default()
> +            return field._get_default
>          elif not field.null and field.blank and
> field.empty_strings_allowed:
>              if field.get_internal_type() == "BinaryField":
> -                default = six.binary_type()
> +                return six.binary_type()
>              else:
> -                default = six.text_type()
> +                return 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
> +                return default.date
>              elif internal_type == 'TimeField':
> -                default = default.time
> +                return default.time
>              elif internal_type == 'DateTimeField':
> -                default = timezone.now
> -        else:
> -            default = None
> +                return timezone.now
> +
> +    def effective_default(self, field):
> +        """
> +        Returns a field's effective database default value
> +        """
> +        default =
> BaseDatabaseSchemaEditor.effective_default_before_callable(field)
>          # If it's a callable, call it
>          if callable(default):
>              default = default()
> @@ -615,6 +620,7 @@ class BaseDatabaseSchemaEditor(object):
>              old_default != new_default and
>              new_default is not None and
>              not self.skip_default(new_field)
> +            and
> BaseDatabaseSchemaEditor.effective_default_before_callable(old_field) !=
> BaseDatabaseSchemaEdit
>          )
>          if needs_database_default:
>              if self.connection.features.requires_literal_defaults:
> diff --git a/django/db/models/fields/__init__.py
> b/django/db/models/fields/__init__.py
> --- a/django/db/models/fields/__init__.py
> +++ b/django/db/models/fields/__init__.py
> @@ -1232,7 +1232,7 @@ class DateField(DateTimeCheckMixin, Field):
>          if self.auto_now:
>              kwargs['auto_now'] = True
>          if self.auto_now_add:
> -            kwargs['auto_now_add'] = True
> +            kwargs['default'] = timezone.now
>          if self.auto_now or self.auto_now_add:
>              del kwargs['editable']
>              del kwargs['blank']
> }}}

New description:

 A switch from DateTimeField(auto_now_add=True) to
 DateTimeField(default=django.utils.timezone.new) creates the statements
   ALTER TABLE SET DEFAULT '2017-10-16T09:35:52.710695'::timestamp;
   ALTER TABLE DROP DEFAULT
 which have no effects, apart from locking the whole table twice.

 A proposal to recognize, when the effective default-callable doesn't
 change and skip changing the DEFAULT twice in this case, as well as not
 generating a migration when this is the only change on a field:
 {{{
 diff --git a/django/db/backends/base/schema.py
 b/django/db/backends/base/schema.py
 --- a/django/db/backends/base/schema.py
 +++ b/django/db/backends/base/schema.py
 @@ -199,28 +199,33 @@ class BaseDatabaseSchemaEditor(object):
              'requires_literal_defaults must provide a prepare_default()
 method'
          )

 -    def effective_default(self, field):
 +    @staticmethod
 +    def effective_default_before_callable(field):
          """
 -        Returns a field's effective database default value
 +        Returns a field's effective database default callable or value
          """
          if field.has_default():
 -            default = field.get_default()
 +            return field._get_default
          elif not field.null and field.blank and
 field.empty_strings_allowed:
              if field.get_internal_type() == "BinaryField":
 -                default = six.binary_type()
 +                return six.binary_type()
              else:
 -                default = six.text_type()
 +                return 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
 +                return default.date
              elif internal_type == 'TimeField':
 -                default = default.time
 +                return default.time
              elif internal_type == 'DateTimeField':
 -                default = timezone.now
 -        else:
 -            default = None
 +                return timezone.now
 +
 +    def effective_default(self, field):
 +        """
 +        Returns a field's effective database default value
 +        """
 +        default =
 BaseDatabaseSchemaEditor.effective_default_before_callable(field)
          # If it's a callable, call it
          if callable(default):
              default = default()
 @@ -615,6 +620,7 @@ class BaseDatabaseSchemaEditor(object):
              old_default != new_default and
              new_default is not None and
              not self.skip_default(new_field)
 +            and
 BaseDatabaseSchemaEditor.effective_default_before_callable(old_field) !=
 BaseDatabaseSchemaEdit
          )
          if needs_database_default:
              if self.connection.features.requires_literal_defaults:
 diff --git a/django/db/models/fields/__init__.py
 b/django/db/models/fields/__init__.py
 index 8d40c77..2f5d5c2 100644
 --- a/django/db/models/fields/__init__.py
 +++ b/django/db/models/fields/__init__.py
 @@ -1232,7 +1232,7 @@ class DateField(DateTimeCheckMixin, Field):
          if self.auto_now:
              kwargs['auto_now'] = True
          if self.auto_now_add:
 -            kwargs['auto_now_add'] = True
 +            kwargs['default'] = datetime.date.today
          if self.auto_now or self.auto_now_add:
              del kwargs['editable']
              del kwargs['blank']
 @@ -1372,6 +1372,12 @@ class DateTimeField(DateField):

          return []

 +    def deconstruct(self):
 +        name, path, args, kwargs = super(DateTimeField,
 self).deconstruct()
 +        if self.auto_now_add:
 +            kwargs['default'] = timezone.now
 +        return name, path, args, kwargs
 +
      def get_internal_type(self):
          return "DateTimeField"

 }}}

--

-- 
Ticket URL: <https://code.djangoproject.com/ticket/28715#comment:3>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/072.7b0f500b99e0cedb5fb997ad2cda54f5%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to