django 深入分析 Manager 和 QuerySet 的区别

Manager

前言:基础篇可以看之前发布的文章:https://blog.csdn.net/qq_39253370/article/details/108502177?spm=1001.2014.3001.5501

默认 django 的模型都有一个管理器,叫 Manager,可以看到 Manager 继承 BaseManager并且调用了 from_queryset 将 QuerySet 类传进去。

class Manager(BaseManager.from_queryset(QuerySet)):
    pass

为什么将 QuerySet 传进去,可以看下面源码:
注意:版本有差异的话可能会不一样,下面为 3.2 源码

@classmethod
    def _get_queryset_methods(cls, queryset_class):
    	# 创建方法
        def create_method(name, method):
            def manager_method(self, *args, **kwargs):
                return getattr(self.get_queryset(), name)(*args, **kwargs)
            manager_method.__name__ = method.__name__
            manager_method.__doc__ = method.__doc__
            return manager_method
		
		"""
		循环获取 QuerySet 中的方法
		inspect.getmembers: 获取对象的属性
		判断 QuerySet 中的方法是否以 _ 开头,如果开头则跳过或是获取到 queryset_only 方法也跳过
		"""
        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_class: 就是上面默认管理器中传入的 QuerySet
	"""
    @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),
        })

那么 QuerySet 里面就是我们所有操作数据库的一些方法,上述源码得出结论,不管是默认的管理还是自定义的管理器都拥有QuerySet 中的所有方法。

问题一:什么时候自定义管理器?

这个要熟悉管理器的查询逻辑,上述源码看到管理器是具有 QuerySet 的方法的,那么如果想修改初始的 QuerySet 方法,可以在管理器中重写该方法,比如:

class NewsManager(models.Manager):
    def filter(self):
        return super(NewsManager, self).filter(title__icontains="ceshi").values("title")

views.py:
def test(request):
    qs = News.objects.filter()
    print(qs)
    return JsonResponse({"count": list(qs)})

或者想添加额外的 Manager 方法和一些具有辨识度的方法,比如 AbstractUser 的默认管理器中就有 create_user 这样的方法。

class UserManager(BaseUserManager):
    def create_user(self, username, email=None, password=None, **extra_fields):
        extra_fields.setdefault('is_staff', False)
        extra_fields.setdefault('is_superuser', False)
        return self._create_user(username, email, password, **extra_fields)

    def create_superuser(self, username, email=None, password=None, **extra_fields):
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)

注意:Manager 中自定义的方法不支持链式调用,比如下面的:

class NewsManager(models.Manager):

    def with_contain_title(self):
        return self.filter(title__icontains="ceshi").values("title")

    def with_is_published(self):
        return self.filter(is_published=True).values("title", "is_published")

# views.py
def test(request):
    qs = News.objects.with_contain_title().with_is_published()
    print(qs)

这将引发:AttributeError: 'QuerySet' object has no attribute 'with_is_published'

但是可以支持调用原有 queryset 中的方法:
qs = News.objects.with_contain_title().filter(is_published=True).values("title", "is_published")
print(qs) # 

问题二:如何组织自定义Manager/或自定义QuerySet类?

上面 Manager 定义的方法我们也可以自定义在 QuerySet 中,使用QuerySet.as_manager() 访问这些方法, 比如:

class NewsQuerySet(models.QuerySet):

    def with_contain_title(self):
        return self.filter(title__icontains="ceshi").values("title")

    def with_is_published(self):
        return self.filter(is_published=True).values("title", "is_published")

class News(models.Model):
    objects = NewsQuerySet.as_manager()

# views.py
def test(request):
    qs = News.objects.with_contain_title()
    print(qs) # 

似乎在如何组织自定义Manager或自定义QuerySet类之间的逻辑方面有很大的灵活性。应该根据什么原则来决定使用那种呢?
上述管理器的缺点也看到了,主要是没办法链式查询,管理器更像是贴合模型的内容,queryset 更像是公共的查询 api
QuerySet 主要是便于组合查询:

def test(request):
    qs = News.objects.with_contain_title().with_is_published()
    print(qs) # 

从逻辑上讲,这些定义主要对查询集的扩展或是避免写一些重复的查询。如果你不在查询集上做很多的操作或是你根本不想返回查询集,这可能用不到模型管理器,一般自定义 QuerySet 比较常用。

QuerySet

自定义 QuerySet 的好处和管理器差不多,可以重写原有里面的方法或是想定制属于自己的 QuerySet 方法。
但是有一个问题就是:我们自己定义的方法怎么从管理器中访问,因为QuerySet 中所有的方法都是通过管理器去访问的,那么如何将我们自定义的方法添加到管理器中?有两种方法:
第一种: 通过管理器去承载
这种方法像是从 QuerySet 中拷贝到 Manager 中,但是多了一些重复的代码,比如我再 QuerySetz 中定义的方法还要再 NewsManager 中再写一遍,显得有些冗余,那么有没有更为方便的写法呢,这就用到了第二种方法。

class NewsQuerySet(models.QuerySet):

    def with_contain_title(self):
        return self.filter(title__icontains="ceshi").values("title")

    def with_is_published(self):
        return self.filter(is_published=True).values("title", "is_published")


class NewsManager(models.Manager):

    def get_queryset(self):
        return NewsQuerySet(model=self.model, using=self._db)

    def with_contain_title(self):
        return self.get_queryset().with_contain_title()

    def with_is_published(self):
        return self.get_queryset().with_is_published()

第二种: as_manager
先看源码:

"""
from_queryset:有没有很熟悉,在上述管理器继承哪里也有这个,实际是一个东西,就是去复制 QuerySet 中的方法。
"""
def as_manager(cls):
    # Address the circular dependency between `Queryset` and `Manager`.
    from django.db.models.manager import Manager
    manager = Manager.from_queryset(cls)()
    manager._built_with_as_manager = True
    return manager
as_manager.queryset_only = True
as_manager = classmethod(as_manager)

QuerySet 中的 as_manager 实际作用就是再去执行一遍 copy 的动作,将我们自定义的方法给copy到管理器中,等价于方法一。

class NewsQuerySet(models.QuerySet):

    def with_contain_title(self):
        return self.filter(title__icontains="ceshi").values("title")

    def with_is_published(self):
        return self.filter(is_published=True).values("title", "is_published")


# class NewsManager(models.Manager):
#
#     def get_queryset(self):
#         return NewsQuerySet(model=self.model, using=self._db)
#
#     def with_contain_title(self):
#         return self.get_queryset().with_contain_title()
#
#     def with_is_published(self):
#         return self.get_queryset().with_is_published()


class News(models.Model):
    """
    研究进展
    """
    # objects = NewsManager()
    objects = NewsQuerySet.as_manager()

# views.py
def test(request):
    # qs = News.objects.with_contain_title()
    qs = News.objects.with_is_published()
    print(qs)

from_queryset

有时我们可能要同时定义Manager 和 QuerySet,这个时候可以调用 from_queryset,他将返回一个基础的 Manager 子类和带有一份 QuerySet 方法的拷贝:

class NewsQuerySet(models.QuerySet):

    def with_contain_title(self):
        return self.filter(title__icontains="ceshi").values("title")

    def with_is_published(self):
        return self.filter(is_published=True).values("title", "is_published")


class NewsManager(models.Manager):

    # def get_queryset(self):
    #     return NewsQuerySet(model=self.model, using=self._db)

    # def with_contain_title(self):
    #     return self.get_queryset().with_contain_title()
    #
    # def with_is_published(self):
    #     return self.get_queryset().with_is_published()

    def with_counts(self):
        self.count()


class News(models.Model):
    """
    研究进展
    """
    # objects = NewsManager()
    # objects = NewsQueryset.as_manager()
    objects = NewsManager.from_queryset(NewsQuerySet)()

# views.py
def test(request):
    # qs = News.objects.with_contain_title()
    qs = News.objects.with_is_published()
    print(qs) # 
    return JsonResponse({"count": list(qs)})

总结:

  1. 如果你不想在查询集上做更多的操作,可以把方法定义在 Manager 中,像基础模块 auth 中用户管理器中一样,只定义一些管理器的终止语句。 这个时候可以自定义 Manager
  2. 如果想在查询集上做很多的操作,这个时候可以自定义 QuerySet 更适合你

参考文献

Django之管理器Manager(之前我写的一篇文章):https://blog.csdn.net/qq_39253370/article/details/108502177?spm=1001.2014.3001.5501
官网:https://docs.djangoproject.com/zh-hans/4.1/topics/db/managers/

你可能感兴趣的:(python,django,orm,django,python,后端)