Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion django/db/backends/base/schema.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import datetime
import hashlib

from django.db.backends.utils import truncate_name
from django.db.transaction import atomic
from django.utils import six
from django.utils import six, timezone
from django.utils.encoding import force_bytes
from django.utils.log import getLogger

Expand Down Expand Up @@ -201,6 +202,13 @@ def effective_default(self, field):
default = six.binary_type()
else:
default = six.text_type()
elif getattr(field, 'auto_now', False) or getattr(field, 'auto_now_add', False):
default = timezone.now() # default for DateTimeField
internal_type = field.get_internal_type()
if internal_type == 'DateField':
default = default.date()
elif internal_type == 'TimeField':
default = default.time()
else:
default = None
# If it's a callable, call it
Expand Down
6 changes: 5 additions & 1 deletion django/db/migrations/autodetector.py
Original file line number Diff line number Diff line change
Expand Up @@ -788,7 +788,11 @@ def _generate_added_field(self, app_label, model_name, field_name):
preserve_default = True
if (not field.null and not field.has_default() and
not isinstance(field, models.ManyToManyField) and
not (field.blank and field.empty_strings_allowed)):
not (field.blank and field.empty_strings_allowed) and
not (
isinstance(field, (models.DateField, models.DateTimeField, models.TimeField)) and
(field.auto_now or field.auto_now_add)
)):
field = field.clone()
field.default = self.questioner.ask_not_null_addition(field_name, model_name)
preserve_default = False
Expand Down
42 changes: 42 additions & 0 deletions tests/migrations/test_autodetector.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,18 @@ class AutodetectorTests(TestCase):
("id", models.AutoField(primary_key=True)),
("name", models.CharField(max_length=200, default='Ada Lovelace')),
])
author_dates_of_birth_auto_now = ModelState("testapp", "Author", [
("id", models.AutoField(primary_key=True)),
("date_of_birth", models.DateField(auto_now=True)),
("date_time_of_birth", models.DateTimeField(auto_now=True)),
("time_of_birth", models.TimeField(auto_now=True)),
])
author_dates_of_birth_auto_now_add = ModelState("testapp", "Author", [
("id", models.AutoField(primary_key=True)),
("date_of_birth", models.DateField(auto_now_add=True)),
("date_time_of_birth", models.DateTimeField(auto_now_add=True)),
("time_of_birth", models.TimeField(auto_now_add=True)),
])
author_name_deconstructable_1 = ModelState("testapp", "Author", [
("id", models.AutoField(primary_key=True)),
("name", models.CharField(max_length=200, default=DeconstructableObject())),
Expand Down Expand Up @@ -522,6 +534,36 @@ def test_add_field(self):
self.assertOperationTypes(changes, 'testapp', 0, ["AddField"])
self.assertOperationAttributes(changes, "testapp", 0, 0, name="name")

@mock.patch('django.db.migrations.questioner.MigrationQuestioner.ask_not_null_addition',
side_effect=Exception("Should not have prompted for not null addition"))
def test_add_date_fields_with_auto_now_not_asking_for_default(self, mocked_ask_method):
# Make state
before = self.make_project_state([self.author_empty])
after = self.make_project_state([self.author_dates_of_birth_auto_now])
autodetector = MigrationAutodetector(before, after)
changes = autodetector._detect_changes()
# Right number/type of migrations?
self.assertNumberMigrations(changes, 'testapp', 1)
self.assertOperationTypes(changes, 'testapp', 0, ["AddField", "AddField", "AddField"])
self.assertOperationFieldAttributes(changes, "testapp", 0, 0, auto_now=True)
self.assertOperationFieldAttributes(changes, "testapp", 0, 1, auto_now=True)
self.assertOperationFieldAttributes(changes, "testapp", 0, 2, auto_now=True)

@mock.patch('django.db.migrations.questioner.MigrationQuestioner.ask_not_null_addition',
side_effect=Exception("Should not have prompted for not null addition"))
def test_add_date_fields_with_auto_now_add_not_asking_for_default(self, mocked_ask_method):
# Make state
before = self.make_project_state([self.author_empty])
after = self.make_project_state([self.author_dates_of_birth_auto_now_add])
autodetector = MigrationAutodetector(before, after)
changes = autodetector._detect_changes()
# Right number/type of migrations?
self.assertNumberMigrations(changes, 'testapp', 1)
self.assertOperationTypes(changes, 'testapp', 0, ["AddField", "AddField", "AddField"])
self.assertOperationFieldAttributes(changes, "testapp", 0, 0, auto_now_add=True)
self.assertOperationFieldAttributes(changes, "testapp", 0, 1, auto_now_add=True)
self.assertOperationFieldAttributes(changes, "testapp", 0, 2, auto_now_add=True)

def test_remove_field(self):
"""Tests autodetection of removed fields."""
# Make state
Expand Down
83 changes: 82 additions & 1 deletion tests/schema/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
import unittest
from copy import copy

import mock

from django.db import (
DatabaseError, IntegrityError, OperationalError, connection,
)
from django.db.models import Model
from django.db.models.fields import (
AutoField, BigIntegerField, BinaryField, BooleanField, CharField,
DateTimeField, IntegerField, PositiveIntegerField, SlugField, TextField,
DateField, DateTimeField, IntegerField, PositiveIntegerField, SlugField,
TextField, TimeField,
)
from django.db.models.fields.related import (
ForeignKey, ManyToManyField, OneToOneField,
Expand Down Expand Up @@ -1535,3 +1538,81 @@ def test_add_field_use_effective_default(self):
cursor.execute("SELECT surname FROM schema_author;")
item = cursor.fetchall()[0]
self.assertEqual(item[0], None if connection.features.interprets_empty_strings_as_nulls else '')

@mock.patch('django.db.backends.base.schema.timezone')
def test_add_datefield_and_datetimefield_use_effective_default(self, mocked_tz):
"""
#25005 - effective_default() should be used for DateField,
DateTimeField and TimeField if auto_now or auto_add_now is set.
"""
now = datetime.datetime(month=1, day=1, year=2000, hour=1, minute=1)
mocked_tz.now = mock.MagicMock(return_value=now)
# Create the table
with connection.schema_editor() as editor:
editor.create_model(Author)
# Ensure there's no surname field
columns = self.column_classes(Author)
self.assertNotIn("date_of_birth_auto_now", columns)
self.assertNotIn("date_of_birth_auto_now_add", columns)
self.assertNotIn("date_time_of_birth_auto_now", columns)
self.assertNotIn("date_time_of_birth_auto_now_add", columns)
self.assertNotIn("time_of_birth_auto_now", columns)
self.assertNotIn("time_of_birth_auto_now_add", columns)
# Create a row
Author.objects.create(name='Anonymous1')
# Add new date and datetime fields to ensure default will be
# used from effective_default
date_of_birth_auto_now = DateField(auto_now=True)
date_of_birth_auto_now.set_attributes_from_name('date_of_birth_auto_now')

date_of_birth_auto_now_add = DateField(auto_now_add=True)
date_of_birth_auto_now_add.set_attributes_from_name('date_of_birth_auto_now_add')

date_time_of_birth_auto_now = DateTimeField(auto_now=True)
date_time_of_birth_auto_now.set_attributes_from_name('date_time_of_birth_auto_now')

date_time_of_birth_auto_now_add = DateTimeField(auto_now_add=True)
date_time_of_birth_auto_now_add.set_attributes_from_name('date_time_of_birth_auto_now_add')

time_of_birth_auto_now = TimeField(auto_now=True)
time_of_birth_auto_now.set_attributes_from_name('time_of_birth_auto_now')

time_of_birth_auto_now_add = TimeField(auto_now_add=True)
time_of_birth_auto_now_add.set_attributes_from_name('time_of_birth_auto_now_add')

# Ensure fields was added with the right defaults
with connection.schema_editor() as editor:
editor.add_field(Author, date_of_birth_auto_now)
with connection.cursor() as cursor:
cursor.execute("SELECT date_of_birth_auto_now FROM schema_author;")
self.assertEqual(cursor.fetchall()[0][0], now.date())

with connection.schema_editor() as editor:
editor.add_field(Author, date_of_birth_auto_now_add)
with connection.cursor() as cursor:
cursor.execute("SELECT date_of_birth_auto_now_add FROM schema_author;")
self.assertEqual(cursor.fetchall()[0][0], now.date())

with connection.schema_editor() as editor:
editor.add_field(Author, date_time_of_birth_auto_now)
with connection.cursor() as cursor:
cursor.execute("SELECT date_time_of_birth_auto_now FROM schema_author;")
self.assertEqual(cursor.fetchall()[0][0], now)

with connection.schema_editor() as editor:
editor.add_field(Author, date_time_of_birth_auto_now_add)
with connection.cursor() as cursor:
cursor.execute("SELECT date_time_of_birth_auto_now_add FROM schema_author;")
self.assertEqual(cursor.fetchall()[0][0], now)

with connection.schema_editor() as editor:
editor.add_field(Author, time_of_birth_auto_now)
with connection.cursor() as cursor:
cursor.execute("SELECT time_of_birth_auto_now FROM schema_author;")
self.assertEqual(cursor.fetchall()[0][0], now.time())

with connection.schema_editor() as editor:
editor.add_field(Author, time_of_birth_auto_now_add)
with connection.cursor() as cursor:
cursor.execute("SELECT time_of_birth_auto_now_add FROM schema_author;")
self.assertEqual(cursor.fetchall()[0][0], now.time())