#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.