Fixed #26475 -- Added functools.partial() support to migrations autodetector.
This commit is contained in:
parent
2a9bcb503f
commit
5402f3ab09
|
@ -1,6 +1,7 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
import functools
|
||||||
import re
|
import re
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
|
||||||
|
@ -63,6 +64,8 @@ class MigrationAutodetector(object):
|
||||||
key: self.deep_deconstruct(value)
|
key: self.deep_deconstruct(value)
|
||||||
for key, value in obj.items()
|
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):
|
elif isinstance(obj, COMPILED_REGEX_TYPE):
|
||||||
return RegexObject(obj)
|
return RegexObject(obj)
|
||||||
elif isinstance(obj, type):
|
elif isinstance(obj, type):
|
||||||
|
|
|
@ -15,3 +15,6 @@ Bugfixes
|
||||||
|
|
||||||
* Fixed ``TimeField`` microseconds round-tripping on MySQL and SQLite
|
* Fixed ``TimeField`` microseconds round-tripping on MySQL and SQLite
|
||||||
(:ticket:`26498`).
|
(:ticket:`26498`).
|
||||||
|
|
||||||
|
* Prevented ``makemigrations`` from generating infinite migrations for a model
|
||||||
|
field that references a ``functools.partial`` (:ticket:`26475`).
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
import functools
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
|
@ -657,6 +658,59 @@ class AutodetectorTests(TestCase):
|
||||||
self.assertOperationTypes(changes, 'testapp', 0, ["AlterField"])
|
self.assertOperationTypes(changes, 'testapp', 0, ["AlterField"])
|
||||||
self.assertOperationAttributes(changes, "testapp", 0, 0, name="name", preserve_default=True)
|
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',
|
@mock.patch('django.db.migrations.questioner.MigrationQuestioner.ask_not_null_alteration',
|
||||||
side_effect=AssertionError("Should not have prompted for not null addition"))
|
side_effect=AssertionError("Should not have prompted for not null addition"))
|
||||||
def test_alter_field_to_not_null_with_default(self, mocked_ask_method):
|
def test_alter_field_to_not_null_with_default(self, mocked_ask_method):
|
||||||
|
|
Loading…
Reference in New Issue