Ticket #28715: auto_now_add_skip_sql_calls.2.patch
File auto_now_add_skip_sql_calls.2.patch, 11.3 KB (added by , 7 years 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: 196 196 'requires_literal_defaults must provide a prepare_default() method' 197 197 ) 198 198 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""" 201 202 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): 209 207 default = datetime.now() 210 208 internal_type = field.get_internal_type() 211 209 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) 219 221 # If it's a callable, call it 220 222 if callable(default): 221 223 default = default() 222 224 # Run it through the field's get_db_prep_save method so we can send it 223 225 # 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) 226 227 227 228 def quote_value(self, value): 228 229 """ … … class BaseDatabaseSchemaEditor: 616 617 not new_field.null and 617 618 old_default != new_default and 618 619 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) 620 623 ) 621 624 if needs_database_default: 622 625 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): 1230 1230 kwargs['auto_now'] = True 1231 1231 if self.auto_now_add: 1232 1232 kwargs['auto_now_add'] = True 1233 kwargs['default'] = datetime.date.today 1233 1234 if self.auto_now or self.auto_now_add: 1234 1235 del kwargs['editable'] 1235 1236 del kwargs['blank'] … … class DateTimeField(DateField): 1367 1368 1368 1369 return [] 1369 1370 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 1370 1377 def get_internal_type(self): 1371 1378 return "DateTimeField" 1372 1379 -
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 1 import datetime 2 1 3 from django.apps import apps 2 4 from django.db import models 3 5 from django.test import SimpleTestCase, override_settings 4 6 from django.test.utils import isolate_lru_cache 7 from django.utils import timezone 5 8 6 9 7 10 class FieldDeconstructionTests(SimpleTestCase): … … class FieldDeconstructionTests(SimpleTestCase): 113 116 self.assertEqual(path, "django.db.models.DateField") 114 117 self.assertEqual(args, []) 115 118 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}) 116 129 117 130 def test_datetime_field(self): 118 131 field = models.DateTimeField() … … class FieldDeconstructionTests(SimpleTestCase): 124 137 name, path, args, kwargs = field.deconstruct() 125 138 self.assertEqual(path, "django.db.models.DateTimeField") 126 139 self.assertEqual(args, []) 127 self.assertEqual(kwargs, {"auto_now_add": True })140 self.assertEqual(kwargs, {"auto_now_add": True, 'default': timezone.now}) 128 141 # Bug #21785 129 142 field = models.DateTimeField(auto_now=True, auto_now_add=True) 130 143 name, path, args, kwargs = field.deconstruct() 131 144 self.assertEqual(path, "django.db.models.DateTimeField") 132 145 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}) 134 147 135 148 def test_decimal_field(self): 136 149 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 1 import datetime 1 2 import functools 2 3 import re 3 4 from unittest import mock … … from django.db.migrations.questioner import MigrationQuestioner 14 15 from django.db.migrations.state import ModelState, ProjectState 15 16 from django.test import TestCase, override_settings 16 17 from django.test.utils import isolate_lru_cache 18 from django.utils import timezone 17 19 18 20 from .models import FoodManager, FoodQuerySet 19 21 … … class AutodetectorTests(TestCase): 73 75 ("date_time_of_birth", models.DateTimeField(auto_now_add=True)), 74 76 ("time_of_birth", models.TimeField(auto_now_add=True)), 75 77 ]) 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 ]) 76 84 author_name_deconstructible_1 = ModelState("testapp", "Author", [ 77 85 ("id", models.AutoField(primary_key=True)), 78 86 ("name", models.CharField(max_length=200, default=DeconstructibleObject())), … … class AutodetectorTests(TestCase): 682 690 self.assertOperationFieldAttributes(changes, "testapp", 0, 1, auto_now=True) 683 691 self.assertOperationFieldAttributes(changes, "testapp", 0, 2, auto_now=True) 684 692 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 685 711 @mock.patch('django.db.migrations.questioner.MigrationQuestioner.ask_not_null_addition', 686 712 side_effect=AssertionError("Should not have prompted for not null addition")) 687 713 def test_add_date_fields_with_auto_now_add_not_asking_for_null_addition(self, mocked_ask_method): … … class AutodetectorTests(TestCase): 702 728 self.assertOperationFieldAttributes(changes, "testapp", 0, 0, auto_now_add=True) 703 729 self.assertOperationFieldAttributes(changes, "testapp", 0, 1, auto_now_add=True) 704 730 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) 706 732 707 733 def test_remove_field(self): 708 734 """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): 1291 1291 app_label = 'migrations' 1292 1292 1293 1293 # 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): 1295 1295 out = io.StringIO() 1296 1296 with self.temporary_migration_module(module='migrations.test_auto_now_add'): 1297 1297 call_command('makemigrations', 'migrations', interactive=True, stdout=out) 1298 1298 output = out.getvalue() 1299 prompt_output = prompt_stdout.getvalue()1300 self.assertIn("You can accept the default 'timezone.now' by pressing 'Enter'", prompt_output)1301 1299 self.assertIn("Add field creation_date to entry", output) 1302 1300 1303 1301