Как работают вызовы models.Manager и пользовательских менеджеров?

Следующий вопрос был задан другим пользователем, который впоследствии удалил вопрос. Но я считаю полезным немного "покопаться" в том, как работает логика менеджера Django.

Я расширил класс 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() [Django-doc], .get(…) [Django-doc], 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, а также предотвратит вызов менеджера на абстрактных моделях.

Вернуться на верх