summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarkus Holtermann <info@markusholtermann.eu>2014-09-08 03:01:42 +0200
committerMarkus Holtermann <info@markusholtermann.eu>2014-09-08 21:27:34 +0200
commitd28b5f13b332bda4317949b98e33f528d30ec006 (patch)
tree587b3353049ad8971a148a7af5af9750850c8005
parentf9419a6dc0bd0c1ec3c6b4176a1fa8cadc755cf4 (diff)
downloaddjango-d28b5f13b332bda4317949b98e33f528d30ec006.tar.gz
Fixed #23418 -- Fail when migration deconstruct produces invalid import
-rw-r--r--django/utils/deconstruct.py21
-rw-r--r--docs/releases/1.7.1.txt3
-rw-r--r--tests/migrations/test_writer.py12
3 files changed, 33 insertions, 3 deletions
diff --git a/django/utils/deconstruct.py b/django/utils/deconstruct.py
index 7774e69997..627c2a9c68 100644
--- a/django/utils/deconstruct.py
+++ b/django/utils/deconstruct.py
@@ -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],
)
diff --git a/docs/releases/1.7.1.txt b/docs/releases/1.7.1.txt
index 2ed63c41b2..85421f2fa4 100644
--- a/docs/releases/1.7.1.txt
+++ b/docs/releases/1.7.1.txt
@@ -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`).
diff --git a/tests/migrations/test_writer.py b/tests/migrations/test_writer.py
index 3839f78cbd..f6c866d725 100644
--- a/tests/migrations/test_writer.py
+++ b/tests/migrations/test_writer.py
@@ -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):
"""