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