deconstruct.py 2.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
  1. from importlib import import_module
  2. from django.utils.version import get_docs_version
  3. def deconstructible(*args, path=None):
  4. """
  5. Class decorator that allows the decorated class to be serialized
  6. by the migrations subsystem.
  7. The `path` kwarg specifies the import path.
  8. """
  9. def decorator(klass):
  10. def __new__(cls, *args, **kwargs):
  11. # We capture the arguments to make returning them trivial
  12. obj = super(klass, cls).__new__(cls)
  13. obj._constructor_args = (args, kwargs)
  14. return obj
  15. def deconstruct(obj):
  16. """
  17. Return a 3-tuple of class import path, positional arguments,
  18. and keyword arguments.
  19. """
  20. # Fallback version
  21. if path and type(obj) is klass:
  22. module_name, _, name = path.rpartition(".")
  23. else:
  24. module_name = obj.__module__
  25. name = obj.__class__.__name__
  26. # Make sure it's actually there and not an inner class
  27. module = import_module(module_name)
  28. if not hasattr(module, name):
  29. raise ValueError(
  30. "Could not find object %s in %s.\n"
  31. "Please note that you cannot serialize things like inner "
  32. "classes. Please move the object into the main module "
  33. "body to use migrations.\n"
  34. "For more information, see "
  35. "https://docs.djangoproject.com/en/%s/topics/migrations/"
  36. "#serializing-values" % (name, module_name, get_docs_version())
  37. )
  38. return (
  39. (
  40. path
  41. if path and type(obj) is klass
  42. else f"{obj.__class__.__module__}.{name}"
  43. ),
  44. obj._constructor_args[0],
  45. obj._constructor_args[1],
  46. )
  47. klass.__new__ = staticmethod(__new__)
  48. klass.deconstruct = deconstruct
  49. return klass
  50. if not args:
  51. return decorator
  52. return decorator(*args)