You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

manager.py 6.6KB

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