manager.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. import copy
  2. import inspect
  3. from importlib import import_module
  4. from django.db import router
  5. from django.db.models.query import QuerySet
  6. class BaseManager:
  7. # To retain order, track each time a Manager instance is created.
  8. creation_counter = 0
  9. # Set to True for the 'objects' managers that are automatically created.
  10. auto_created = False
  11. #: If set to True the manager will be serialized into migrations and will
  12. #: thus be available in e.g. RunPython operations.
  13. use_in_migrations = False
  14. def __new__(cls, *args, **kwargs):
  15. # Capture the arguments to make returning them trivial.
  16. obj = super().__new__(cls)
  17. obj._constructor_args = (args, kwargs)
  18. return obj
  19. def __init__(self):
  20. super().__init__()
  21. self._set_creation_counter()
  22. self.model = None
  23. self.name = None
  24. self._db = None
  25. self._hints = {}
  26. def __str__(self):
  27. """Return "app_label.model_label.manager_name"."""
  28. return '%s.%s' % (self.model._meta.label, self.name)
  29. def __class_getitem__(cls, *args, **kwargs):
  30. return cls
  31. def deconstruct(self):
  32. """
  33. Return a 5-tuple of the form (as_manager (True), manager_class,
  34. queryset_class, args, kwargs).
  35. Raise a ValueError if the manager is dynamically generated.
  36. """
  37. qs_class = self._queryset_class
  38. if getattr(self, '_built_with_as_manager', False):
  39. # using MyQuerySet.as_manager()
  40. return (
  41. True, # as_manager
  42. None, # manager_class
  43. '%s.%s' % (qs_class.__module__, qs_class.__name__), # qs_class
  44. None, # args
  45. None, # kwargs
  46. )
  47. else:
  48. module_name = self.__module__
  49. name = self.__class__.__name__
  50. # Make sure it's actually there and not an inner class
  51. module = import_module(module_name)
  52. if not hasattr(module, name):
  53. raise ValueError(
  54. "Could not find manager %s in %s.\n"
  55. "Please note that you need to inherit from managers you "
  56. "dynamically generated with 'from_queryset()'."
  57. % (name, module_name)
  58. )
  59. return (
  60. False, # as_manager
  61. '%s.%s' % (module_name, name), # manager_class
  62. None, # qs_class
  63. self._constructor_args[0], # args
  64. self._constructor_args[1], # kwargs
  65. )
  66. def check(self, **kwargs):
  67. return []
  68. @classmethod
  69. def _get_queryset_methods(cls, queryset_class):
  70. def create_method(name, method):
  71. def manager_method(self, *args, **kwargs):
  72. return getattr(self.get_queryset(), name)(*args, **kwargs)
  73. manager_method.__name__ = method.__name__
  74. manager_method.__doc__ = method.__doc__
  75. return manager_method
  76. new_methods = {}
  77. for name, method in inspect.getmembers(queryset_class, predicate=inspect.isfunction):
  78. # Only copy missing methods.
  79. if hasattr(cls, name):
  80. continue
  81. # Only copy public methods or methods with the attribute `queryset_only=False`.
  82. queryset_only = getattr(method, 'queryset_only', None)
  83. if queryset_only or (queryset_only is None and name.startswith('_')):
  84. continue
  85. # Copy the method onto the manager.
  86. new_methods[name] = create_method(name, method)
  87. return new_methods
  88. @classmethod
  89. def from_queryset(cls, queryset_class, class_name=None):
  90. if class_name is None:
  91. class_name = '%sFrom%s' % (cls.__name__, queryset_class.__name__)
  92. return type(class_name, (cls,), {
  93. '_queryset_class': queryset_class,
  94. **cls._get_queryset_methods(queryset_class),
  95. })
  96. def contribute_to_class(self, cls, name):
  97. self.name = self.name or name
  98. self.model = cls
  99. setattr(cls, name, ManagerDescriptor(self))
  100. cls._meta.add_manager(self)
  101. def _set_creation_counter(self):
  102. """
  103. Set the creation counter value for this instance and increment the
  104. class-level copy.
  105. """
  106. self.creation_counter = BaseManager.creation_counter
  107. BaseManager.creation_counter += 1
  108. def db_manager(self, using=None, hints=None):
  109. obj = copy.copy(self)
  110. obj._db = using or self._db
  111. obj._hints = hints or self._hints
  112. return obj
  113. @property
  114. def db(self):
  115. return self._db or router.db_for_read(self.model, **self._hints)
  116. #######################
  117. # PROXIES TO QUERYSET #
  118. #######################
  119. def get_queryset(self):
  120. """
  121. Return a new QuerySet object. Subclasses can override this method to
  122. customize the behavior of the Manager.
  123. """
  124. return self._queryset_class(model=self.model, using=self._db, hints=self._hints)
  125. def all(self):
  126. # We can't proxy this method through the `QuerySet` like we do for the
  127. # rest of the `QuerySet` methods. This is because `QuerySet.all()`
  128. # works by creating a "copy" of the current queryset and in making said
  129. # copy, all the cached `prefetch_related` lookups are lost. See the
  130. # implementation of `RelatedManager.get_queryset()` for a better
  131. # understanding of how this comes into play.
  132. return self.get_queryset()
  133. def __eq__(self, other):
  134. return (
  135. isinstance(other, self.__class__) and
  136. self._constructor_args == other._constructor_args
  137. )
  138. def __hash__(self):
  139. return id(self)
  140. class Manager(BaseManager.from_queryset(QuerySet)):
  141. pass
  142. class ManagerDescriptor:
  143. def __init__(self, manager):
  144. self.manager = manager
  145. def __get__(self, instance, cls=None):
  146. if instance is not None:
  147. raise AttributeError("Manager isn't accessible via %s instances" % cls.__name__)
  148. if cls._meta.abstract:
  149. raise AttributeError("Manager isn't available; %s is abstract" % (
  150. cls._meta.object_name,
  151. ))
  152. if cls._meta.swapped:
  153. raise AttributeError(
  154. "Manager isn't available; '%s' has been swapped for '%s'" % (
  155. cls._meta.label,
  156. cls._meta.swapped,
  157. )
  158. )
  159. return cls._meta.managers_map[self.manager.name]
  160. class EmptyManager(Manager):
  161. def __init__(self, model):
  162. super().__init__()
  163. self.model = model
  164. def get_queryset(self):
  165. return super().get_queryset().none()