Как работают вызовы models.Manager и пользовательских менеджеров?
Я расширил класс models.Manager и создал собственный менеджер.
class PublishedManager(models.Manager):
def get_queryset(self):
return super().get_queryset()\
.filter(status=Post.Status.PUBLISHED)
Это более чем понятно, но как работают вызовы менеджеров?
objects = models.Manager() # The default manager.
published = PublishedManager() # Our custom manager.
Я не обращаюсь к методу get_queryset, я просто вызываю конструкторы. Тогда как это работает?
It does not need to, the .all()
, .get(..)
, etc. all behind the curtains call get_queryset()
and then perform that method on the result [GitHub]:
class BaseManager: # ... @classmethod def _get_queryset_methods(cls, queryset_class): def create_method(name, method): @wraps(method) def manager_method(self, *args, **kwargs): return getattr(self.get_queryset(), name)(*args, **kwargs) return manager_method new_methods = {} for name, method in inspect.getmembers( queryset_class, predicate=inspect.isfunction ): # Only copy missing methods. if hasattr(cls, name): continue # Only copy public methods or methods with the attribute # queryset_only=False. queryset_only = getattr(method, "queryset_only", None) if queryset_only or (queryset_only is None and name.startswith("_")): continue # Copy the method onto the manager. new_methods[name] = create_method(name, method) return new_methods
Он проверяет методы класса queryset
, который он будет оборачивать. Теперь, если QuerySet
имеет метод .all()
, он создаст небольшую функцию .all()
для менеджера, которая сначала вызовет get_queryset()
менеджера, а затем .all()
на кверисете. Он делает это для всех функций, определенных в классе queryset, и таким образом создает для каждой из них функцию для менеджера.
Затем они вставляются как члены в Manager
с [GitHub]:
class BaseManager: # ... @classmethod def from_queryset(cls, queryset_class, class_name=None): if class_name is None: class_name = "%sFrom%s" % (cls.__name__, queryset_class.__name__) return type( class_name, (cls,), { "_queryset_class": queryset_class, **cls._get_queryset_methods(queryset_class), }, )
But there is something else that plays here: how does the manager knows what the model is? Django's models call .contribute_to_class(…)
on every attribute in the class, if it is available, for a manager, that looks like [GitHub]:
class BaseManager: # ... def contribute_to_class(self, cls, name): self.name = self.name or name self.model = cls setattr(cls, name, ManagerDescriptor(self)) cls._meta.add_manager(self)
Таким образом, модель внедряется в менеджер, который даже не был известен на момент создания элемента. При этом не устанавливает сам менеджер на класс модели, а дескриптор его.
Тогда дескриптор будет [делегировать получение атрибута менеджеру:
class ManagerDescriptor: # ... def __get__(self, instance, cls=None): if instance is not None: raise AttributeError( "Manager isn't accessible via %s instances" % cls.__name__ ) if cls._meta.abstract: raise AttributeError( "Manager isn't available; %s is abstract" % (cls._meta.object_name,) ) if cls._meta.swapped: raise AttributeError( "Manager isn't available; '%s' has been swapped for '%s'" % ( cls._meta.label, cls._meta.swapped, ) ) return cls._meta.managers_map[self.manager.name]
Это предотвратит использование my_book.objects
, и, таким образом, вы сможете использовать только Book.objects
, а также предотвратит вызов менеджера на абстрактных моделях.