[1.7.x] Fixed #23418 -- Fail when migration deconstruct produces invalid import

Backport of d28b5f13b3 from master
This commit is contained in:
Markus Holtermann 2014-09-08 03:01:42 +02:00 committed by Tim Graham
parent 9c4fb019cb
commit b0def3bcac
3 changed files with 33 additions and 3 deletions

View File

@ -1,3 +1,5 @@
from importlib import import_module
def deconstructible(*args, **kwargs):
"""
Class decorator that allow the decorated class to be serialized
@ -19,8 +21,25 @@ def deconstructible(*args, **kwargs):
Returns a 3-tuple of class import path, positional arguments,
and keyword arguments.
"""
# Python 2/fallback version
if path:
module_name, _, name = path.rpartition('.')
else:
module_name = obj.__module__
name = obj.__class__.__name__
# Make sure it's actually there and not an inner class
module = import_module(module_name)
if not hasattr(module, name):
raise ValueError(
"Could not find object %s in %s.\n"
"Please note that you cannot serialize things like inner "
"classes. Please move the object into the main module "
"body to use migrations.\n"
"For more information, see "
"https://docs.djangoproject.com/en/dev/topics/migrations/#serializing-values"
% (name, module_name))
return (
path or '%s.%s' % (obj.__class__.__module__, obj.__class__.__name__),
path or '%s.%s' % (obj.__class__.__module__, name),
obj._constructor_args[0],
obj._constructor_args[1],
)

View File

@ -20,3 +20,6 @@ Bugfixes
* Fixed serialization of ``type`` objects in migrations (:ticket:`22951`).
* Allowed inline and hidden references to admin fields (:ticket:`23431`).
* The ``@deconstructible`` decorator now fails with a ``ValueError`` if the
decorated object cannot automatically be imported (:ticket:`23418`).

View File

@ -167,9 +167,17 @@ class WriterTests(TestCase):
self.assertEqual(string, "django.core.validators.EmailValidator(message='hello')")
self.serialize_round_trip(validator)
validator = deconstructible(path="custom.EmailValidator")(EmailValidator)(message="hello")
validator = deconstructible(path="migrations.test_writer.EmailValidator")(EmailValidator)(message="hello")
string = MigrationWriter.serialize(validator)[0]
self.assertEqual(string, "custom.EmailValidator(message='hello')")
self.assertEqual(string, "migrations.test_writer.EmailValidator(message='hello')")
validator = deconstructible(path="custom.EmailValidator")(EmailValidator)(message="hello")
with self.assertRaisesMessage(ImportError, "No module named 'custom'"):
MigrationWriter.serialize(validator)
validator = deconstructible(path="django.core.validators.EmailValidator2")(EmailValidator)(message="hello")
with self.assertRaisesMessage(ValueError, "Could not find object EmailValidator2 in django.core.validators."):
MigrationWriter.serialize(validator)
def test_serialize_empty_nonempty_tuple(self):
"""