Commit d28b5f13 authored by Markus Holtermann's avatar Markus Holtermann
Browse files

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

parent f9419a6d
Loading
Loading
Loading
Loading
+20 −1
Original line number Diff line number Diff line
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],
            )
+3 −0
Original line number Diff line number Diff line
@@ -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`).
+10 −2
Original line number Diff line number Diff line
@@ -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):
        """