Ticket #28715: auto_now_add_skip_sql_calls.2.patch

File auto_now_add_skip_sql_calls.2.patch, 11.3 KB (added by Дилян Палаузов, 12 months ago)
  • django/db/backends/base/schema.py

    commit 69cf48fda4da47ca25ea9ef4f76324697b2692b7
    Author: Дилян Палаузов <dpa@mail.lab>
    Date:   Mon Mar 5 11:44:09 2018 +0000
    
        Refs #28715 -  Prevent a migration changing DateTimeField(auto_now_add=True to default=timezone.now) from generating SQL
        
        ... likewise for DateField(auto_now_add=True) -> DateField(default=datetime.date.today)
    
    diff --git a/django/db/backends/base/schema.py b/django/db/backends/base/schema.py
    index f1e8e66..df5e0e5 100644
    a b class BaseDatabaseSchemaEditor: 
    196196            'requires_literal_defaults must provide a prepare_default() method'
    197197        )
    198198
    199     def effective_default(self, field):
    200         """Return a field's effective database default value."""
     199    @staticmethod
     200    def effective_default_before_callable(field):
     201        """Return a field's effective database default callable or value"""
    201202        if field.has_default():
    202             default = field.get_default()
    203         elif not field.null and field.blank and field.empty_strings_allowed:
    204             if field.get_internal_type() == "BinaryField":
    205                 default = bytes()
    206             else:
    207                 default = str()
    208         elif getattr(field, 'auto_now', False) or getattr(field, 'auto_now_add', False):
     203            return field._get_default
     204        if not field.null and field.blank and field.empty_strings_allowed:
     205            return bytes() if field.get_internal_type() == "BinaryField" else str()
     206        if getattr(field, 'auto_now', False) or getattr(field, 'auto_now_add', False):
    209207            default = datetime.now()
    210208            internal_type = field.get_internal_type()
    211209            if internal_type == 'DateField':
    212                 default = default.date
    213             elif internal_type == 'TimeField':
    214                 default = default.time
    215             elif internal_type == 'DateTimeField':
    216                 default = timezone.now
    217         else:
    218             default = None
     210                return default.date
     211            if internal_type == 'TimeField':
     212                return default.time
     213            if internal_type == 'DateTimeField':
     214                return timezone.now
     215
     216    def effective_default(self, field):
     217        """
     218        Return a field's effective database default value
     219        """
     220        default = BaseDatabaseSchemaEditor.effective_default_before_callable(field)
    219221        # If it's a callable, call it
    220222        if callable(default):
    221223            default = default()
    222224        # Run it through the field's get_db_prep_save method so we can send it
    223225        # to the database.
    224         default = field.get_db_prep_save(default, self.connection)
    225         return default
     226        return field.get_db_prep_save(default, self.connection)
    226227
    227228    def quote_value(self, value):
    228229        """
    class BaseDatabaseSchemaEditor: 
    616617            not new_field.null and
    617618            old_default != new_default and
    618619            new_default is not None and
    619             not self.skip_default(new_field)
     620            not self.skip_default(new_field) and
     621            BaseDatabaseSchemaEditor.effective_default_before_callable(old_field) !=
     622            BaseDatabaseSchemaEditor.effective_default_before_callable(new_field)
    620623        )
    621624        if needs_database_default:
    622625            actions.append(self._alter_column_default_sql(model, old_field, new_field))
  • django/db/models/fields/__init__.py

    diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
    index ea31096..1c32de0 100644
    a b class DateField(DateTimeCheckMixin, Field): 
    12301230            kwargs['auto_now'] = True
    12311231        if self.auto_now_add:
    12321232            kwargs['auto_now_add'] = True
     1233            kwargs['default'] = datetime.date.today
    12331234        if self.auto_now or self.auto_now_add:
    12341235            del kwargs['editable']
    12351236            del kwargs['blank']
    class DateTimeField(DateField): 
    13671368
    13681369        return []
    13691370
     1371    def deconstruct(self):
     1372        name, path, args, kwargs = super(DateTimeField, self).deconstruct()
     1373        if self.auto_now_add:
     1374            kwargs['default'] = timezone.now
     1375        return name, path, args, kwargs
     1376
    13701377    def get_internal_type(self):
    13711378        return "DateTimeField"
    13721379
  • tests/field_deconstruction/tests.py

    diff --git a/tests/field_deconstruction/tests.py b/tests/field_deconstruction/tests.py
    index 2cf1f93..172e37d 100644
    a b  
     1import datetime
     2
    13from django.apps import apps
    24from django.db import models
    35from django.test import SimpleTestCase, override_settings
    46from django.test.utils import isolate_lru_cache
     7from django.utils import timezone
    58
    69
    710class FieldDeconstructionTests(SimpleTestCase):
    class FieldDeconstructionTests(SimpleTestCase): 
    113116        self.assertEqual(path, "django.db.models.DateField")
    114117        self.assertEqual(args, [])
    115118        self.assertEqual(kwargs, {"auto_now": True})
     119        field = models.DateField(auto_now_add=True)
     120        name, path, args, kwargs = field.deconstruct()
     121        self.assertEqual(path, "django.db.models.DateField")
     122        self.assertEqual(args, [])
     123        self.assertEqual(kwargs, {"auto_now_add": True, 'default': datetime.date.today})
     124        field = models.DateField(auto_now=True, auto_now_add=True)
     125        name, path, args, kwargs = field.deconstruct()
     126        self.assertEqual(path, "django.db.models.DateField")
     127        self.assertEqual(args, [])
     128        self.assertEqual(kwargs, {"auto_now_add": True, "auto_now": True, 'default': datetime.date.today})
    116129
    117130    def test_datetime_field(self):
    118131        field = models.DateTimeField()
    class FieldDeconstructionTests(SimpleTestCase): 
    124137        name, path, args, kwargs = field.deconstruct()
    125138        self.assertEqual(path, "django.db.models.DateTimeField")
    126139        self.assertEqual(args, [])
    127         self.assertEqual(kwargs, {"auto_now_add": True})
     140        self.assertEqual(kwargs, {"auto_now_add": True, 'default': timezone.now})
    128141        # Bug #21785
    129142        field = models.DateTimeField(auto_now=True, auto_now_add=True)
    130143        name, path, args, kwargs = field.deconstruct()
    131144        self.assertEqual(path, "django.db.models.DateTimeField")
    132145        self.assertEqual(args, [])
    133         self.assertEqual(kwargs, {"auto_now_add": True, "auto_now": True})
     146        self.assertEqual(kwargs, {"auto_now_add": True, "auto_now": True,  'default': timezone.now})
    134147
    135148    def test_decimal_field(self):
    136149        field = models.DecimalField(max_digits=5, decimal_places=2)
  • tests/migrations/test_autodetector.py

    diff --git a/tests/migrations/test_autodetector.py b/tests/migrations/test_autodetector.py
    index 43c3a19..b256e7d 100644
    a b  
     1import datetime
    12import functools
    23import re
    34from unittest import mock
    from django.db.migrations.questioner import MigrationQuestioner 
    1415from django.db.migrations.state import ModelState, ProjectState
    1516from django.test import TestCase, override_settings
    1617from django.test.utils import isolate_lru_cache
     18from django.utils import timezone
    1719
    1820from .models import FoodManager, FoodQuerySet
    1921
    class AutodetectorTests(TestCase): 
    7375        ("date_time_of_birth", models.DateTimeField(auto_now_add=True)),
    7476        ("time_of_birth", models.TimeField(auto_now_add=True)),
    7577    ])
     78    author_dates_of_birth_default_timezone_now = ModelState("testapp", "Author", [
     79        ("id", models.AutoField(primary_key=True)),
     80        ("date_of_birth", models.DateField(default=datetime.date.today)),
     81        ("date_time_of_birth", models.DateTimeField(default=timezone.now)),
     82        ("time_of_birth", models.TimeField(auto_now_add=True)),
     83    ])
    7684    author_name_deconstructible_1 = ModelState("testapp", "Author", [
    7785        ("id", models.AutoField(primary_key=True)),
    7886        ("name", models.CharField(max_length=200, default=DeconstructibleObject())),
    class AutodetectorTests(TestCase): 
    682690        self.assertOperationFieldAttributes(changes, "testapp", 0, 1, auto_now=True)
    683691        self.assertOperationFieldAttributes(changes, "testapp", 0, 2, auto_now=True)
    684692
     693    def test_add_date_fields_with_auto_now_add_switching_to_default(self):
     694        changes = self.get_changes([self.author_dates_of_birth_default_timezone_now],
     695                                   [self.author_dates_of_birth_auto_now_add])
     696        # Right number/type of migrations?
     697        self.assertNumberMigrations(changes, 'testapp', 1)
     698        self.assertOperationTypes(changes, 'testapp', 0, ["AlterField", "AlterField"])
     699        self.assertOperationAttributes(changes, "testapp", 0, 0, name="date_of_birth", preserve_default=True)
     700        self.assertOperationAttributes(changes, "testapp", 0, 1, name="date_time_of_birth", preserve_default=True)
     701
     702    def test_add_date_fields_with_default_switching_to_auto_now_add(self):
     703        changes = self.get_changes([self.author_dates_of_birth_auto_now_add],
     704                                   [self.author_dates_of_birth_default_timezone_now])
     705        # Right number/type of migrations?
     706        self.assertNumberMigrations(changes, 'testapp', 1)
     707        self.assertOperationTypes(changes, 'testapp', 0, ["AlterField", "AlterField"])
     708        self.assertOperationAttributes(changes, "testapp", 0, 0, name="date_of_birth", preserve_default=True)
     709        self.assertOperationAttributes(changes, "testapp", 0, 1, name="date_time_of_birth", preserve_default=True)
     710
    685711    @mock.patch('django.db.migrations.questioner.MigrationQuestioner.ask_not_null_addition',
    686712                side_effect=AssertionError("Should not have prompted for not null addition"))
    687713    def test_add_date_fields_with_auto_now_add_not_asking_for_null_addition(self, mocked_ask_method):
    class AutodetectorTests(TestCase): 
    702728        self.assertOperationFieldAttributes(changes, "testapp", 0, 0, auto_now_add=True)
    703729        self.assertOperationFieldAttributes(changes, "testapp", 0, 1, auto_now_add=True)
    704730        self.assertOperationFieldAttributes(changes, "testapp", 0, 2, auto_now_add=True)
    705         self.assertEqual(mocked_ask_method.call_count, 3)
     731        self.assertEqual(mocked_ask_method.call_count, 1)
    706732
    707733    def test_remove_field(self):
    708734        """Tests autodetection of removed fields."""
  • tests/migrations/test_commands.py

    diff --git a/tests/migrations/test_commands.py b/tests/migrations/test_commands.py
    index 8a77624..326d889 100644
    a b class MakeMigrationsTests(MigrationTestBase): 
    12911291                app_label = 'migrations'
    12921292
    12931293        # Monkeypatch interactive questioner to auto accept
    1294         with mock.patch('django.db.migrations.questioner.sys.stdout', new_callable=io.StringIO) as prompt_stdout:
     1294        with mock.patch('django.db.migrations.questioner.sys.stdout', new_callable=io.StringIO):
    12951295            out = io.StringIO()
    12961296            with self.temporary_migration_module(module='migrations.test_auto_now_add'):
    12971297                call_command('makemigrations', 'migrations', interactive=True, stdout=out)
    12981298            output = out.getvalue()
    1299             prompt_output = prompt_stdout.getvalue()
    1300             self.assertIn("You can accept the default 'timezone.now' by pressing 'Enter'", prompt_output)
    13011299            self.assertIn("Add field creation_date to entry", output)
    13021300
    13031301
Back to Top