Fixed #26475 -- Added functools.partial() support to migrations autodetector.

This commit is contained in:
Matthew Schinckel 2016-04-18 11:25:15 +09:30 committed by Tim Graham
parent 2a9bcb503f
commit 5402f3ab09
3 changed files with 60 additions and 0 deletions

View File

@ -1,6 +1,7 @@
from __future__ import unicode_literals
import datetime
import functools
import re
from itertools import chain
@ -63,6 +64,8 @@ class MigrationAutodetector(object):
key: self.deep_deconstruct(value)
for key, value in obj.items()
}
elif isinstance(obj, functools.partial):
return (obj.func, self.deep_deconstruct(obj.args), self.deep_deconstruct(obj.keywords))
elif isinstance(obj, COMPILED_REGEX_TYPE):
return RegexObject(obj)
elif isinstance(obj, type):

View File

@ -15,3 +15,6 @@ Bugfixes
* Fixed ``TimeField`` microseconds round-tripping on MySQL and SQLite
(:ticket:`26498`).
* Prevented ``makemigrations`` from generating infinite migrations for a model
field that references a ``functools.partial`` (:ticket:`26475`).

View File

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
import functools
import re
from django.apps import apps
@ -657,6 +658,59 @@ class AutodetectorTests(TestCase):
self.assertOperationTypes(changes, 'testapp', 0, ["AlterField"])
self.assertOperationAttributes(changes, "testapp", 0, 0, name="name", preserve_default=True)
def test_supports_functools_partial(self):
def _content_file_name(instance, filename, key, **kwargs):
return '{}/{}'.format(instance, filename)
def content_file_name(key, **kwargs):
return functools.partial(_content_file_name, key, **kwargs)
# An unchanged partial reference.
before = self.make_project_state([ModelState("testapp", "Author", [
("id", models.AutoField(primary_key=True)),
("file", models.FileField(max_length=200, upload_to=content_file_name('file'))),
])])
after = self.make_project_state([ModelState("testapp", "Author", [
("id", models.AutoField(primary_key=True)),
("file", models.FileField(max_length=200, upload_to=content_file_name('file'))),
])])
autodetector = MigrationAutodetector(before, after)
changes = autodetector._detect_changes()
self.assertNumberMigrations(changes, 'testapp', 0)
# A changed partial reference.
args_changed = self.make_project_state([ModelState("testapp", "Author", [
("id", models.AutoField(primary_key=True)),
("file", models.FileField(max_length=200, upload_to=content_file_name('other-file'))),
])])
autodetector = MigrationAutodetector(before, args_changed)
changes = autodetector._detect_changes()
self.assertNumberMigrations(changes, 'testapp', 1)
self.assertOperationTypes(changes, 'testapp', 0, ['AlterField'])
# Can't use assertOperationFieldAttributes because we need the
# deconstructed version, i.e., the exploded func/args/keywords rather
# than the partial: we don't care if it's not the same instance of the
# partial, only if it's the same source function, args, and keywords.
value = changes['testapp'][0].operations[0].field.upload_to
self.assertEqual(
(_content_file_name, ('other-file',), {}),
(value.func, value.args, value.keywords)
)
kwargs_changed = self.make_project_state([ModelState("testapp", "Author", [
("id", models.AutoField(primary_key=True)),
("file", models.FileField(max_length=200, upload_to=content_file_name('file', spam='eggs'))),
])])
autodetector = MigrationAutodetector(before, kwargs_changed)
changes = autodetector._detect_changes()
self.assertNumberMigrations(changes, 'testapp', 1)
self.assertOperationTypes(changes, 'testapp', 0, ['AlterField'])
value = changes['testapp'][0].operations[0].field.upload_to
self.assertEqual(
(_content_file_name, ('file',), {'spam': 'eggs'}),
(value.func, value.args, value.keywords)
)
@mock.patch('django.db.migrations.questioner.MigrationQuestioner.ask_not_null_alteration',
side_effect=AssertionError("Should not have prompted for not null addition"))
def test_alter_field_to_not_null_with_default(self, mocked_ask_method):