Fixed #23418 -- Fail when migration deconstruct produces invalid import

This commit is contained in:
Markus Holtermann 2014-09-08 03:01:42 +02:00
parent f9419a6dc0
commit d28b5f13b3
3 changed files with 33 additions and 3 deletions

View File

@ -1,3 +1,5 @@
from importlib import import_module
def deconstructible(*args, **kwargs): def deconstructible(*args, **kwargs):
""" """
Class decorator that allow the decorated class to be serialized 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, Returns a 3-tuple of class import path, positional arguments,
and keyword 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 ( 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[0],
obj._constructor_args[1], obj._constructor_args[1],
) )

View File

@ -20,3 +20,6 @@ Bugfixes
* Fixed serialization of ``type`` objects in migrations (:ticket:`22951`). * Fixed serialization of ``type`` objects in migrations (:ticket:`22951`).
* Allowed inline and hidden references to admin fields (:ticket:`23431`). * 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.assertEqual(string, "django.core.validators.EmailValidator(message='hello')")
self.serialize_round_trip(validator) 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] 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): def test_serialize_empty_nonempty_tuple(self):
""" """