#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.
>
> A proposal to recognize, when the effective default-callable doesn't
> change:
> {{{
> 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,32 @@ class BaseDatabaseSchemaEditor(object):
>              'requires_literal_defaults must provide a prepare_default()
> method'
>          )
>
> -    def effective_default(self, field):
> +    def effective_default_before_callable(self, 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 = self.effective_default_before_callable(field)
>          # If it's a callable, call it
>          if callable(default):
>              default = default()
> @@ -615,6 +619,7 @@ class BaseDatabaseSchemaEditor(object):
>              old_default != new_default and
>              new_default is not None and
>              not self.skip_default(new_field)
> +            and self.effective_default_before_callable(old_field) !=
> self.effective_default_before_callable(new
>          )
>          if needs_database_default:
>              if self.connection.features.requires_literal_defaults:
>
> }}}

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
 --- 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']
 }}}

--

-- 
Ticket URL: <https://code.djangoproject.com/ticket/28715#comment:2>
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 django-updates+unsubscr...@googlegroups.com.
To post to this group, send email to django-updates@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/072.52d8c1364b1739da51df002ef486de38%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to