model.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. from collections.abc import Mapping
  2. from typing import Tuple, Type, cast
  3. from django.db.migrations.state import ModelState
  4. from django.db.models import Model
  5. from psqlextra.models import PostgresModel
  6. class PostgresModelState(ModelState):
  7. """Base for custom model states.
  8. We need this base class to create some hooks into rendering models,
  9. creating new states and cloning state. Most of the logic resides
  10. here in the base class. Our derived classes implement the `_pre_*`
  11. methods.
  12. """
  13. @classmethod
  14. def from_model( # type: ignore[override]
  15. cls, model: Type[PostgresModel], *args, **kwargs
  16. ) -> "PostgresModelState":
  17. """Creates a new :see:PostgresModelState object from the specified
  18. model.
  19. We override this so derived classes get the chance to attach
  20. additional information to the newly created model state.
  21. We also need to patch up the base class for the model.
  22. """
  23. model_state = super().from_model(
  24. cast(Type[Model], model), *args, **kwargs
  25. )
  26. model_state = cls._pre_new(
  27. model, cast("PostgresModelState", model_state)
  28. )
  29. # django does not add abstract bases as a base in migrations
  30. # because it assumes the base does not add anything important
  31. # in a migration.. but it does, so we replace the Model
  32. # base with the actual base
  33. bases: Tuple[Type[Model], ...] = tuple()
  34. for base in model_state.bases:
  35. if issubclass(base, Model):
  36. bases += (cls._get_base_model_class(),)
  37. else:
  38. bases += (base,)
  39. model_state.bases = cast(Tuple[Type[Model]], bases)
  40. return model_state
  41. def clone(self) -> "PostgresModelState":
  42. """Gets an exact copy of this :see:PostgresModelState."""
  43. model_state = super().clone()
  44. return self._pre_clone(cast(PostgresModelState, model_state))
  45. def render(self, apps):
  46. """Renders this state into an actual model."""
  47. # TODO: figure out a way to do this witout pretty much
  48. # copying the base class's implementation
  49. try:
  50. bases = tuple(
  51. (apps.get_model(base) if isinstance(base, str) else base)
  52. for base in self.bases
  53. )
  54. except LookupError:
  55. # TODO: this should be a InvalidBaseError
  56. raise ValueError(
  57. "Cannot resolve one or more bases from %r" % (self.bases,)
  58. )
  59. if isinstance(self.fields, Mapping):
  60. # In Django 3.1 `self.fields` became a `dict`
  61. fields = {
  62. name: field.clone() for name, field in self.fields.items()
  63. }
  64. else:
  65. # In Django < 3.1 `self.fields` is a list of (name, field) tuples
  66. fields = {name: field.clone() for name, field in self.fields}
  67. meta = type(
  68. "Meta",
  69. (),
  70. {"app_label": self.app_label, "apps": apps, **self.options},
  71. )
  72. attributes = {
  73. **fields,
  74. "Meta": meta,
  75. "__module__": "__fake__",
  76. **dict(self.construct_managers()),
  77. }
  78. return type(*self._pre_render(self.name, bases, attributes))
  79. @classmethod
  80. def _pre_new(
  81. cls,
  82. model: Type[PostgresModel],
  83. model_state: "PostgresModelState",
  84. ) -> "PostgresModelState":
  85. """Called when a new model state is created from the specified
  86. model."""
  87. return model_state
  88. def _pre_clone(
  89. self, model_state: "PostgresModelState"
  90. ) -> "PostgresModelState":
  91. """Called when this model state is cloned."""
  92. return model_state
  93. def _pre_render(self, name: str, bases, attributes):
  94. """Called when this model state is rendered into a model."""
  95. return name, bases, attributes
  96. @classmethod
  97. def _get_base_model_class(self) -> Type[PostgresModel]:
  98. """Gets the class to use as a base class for rendered models."""
  99. return PostgresModel