引言

之前只是看书的时候看过Python多继承的MRO顺序, 从来没在代码里面具体实践过, 今天终于遇到了一次.

在写NWU.ICU的过程中, 有课程评价一项, 建立Model的时候, 为了实现标记删除, 继承了自定义的SoftDeleteModel模型,

class Review(SoftDeleteModel):
	....
    pinyin = models.TextField(verbose_name='拼音', blank=True)
    search_vector = SearchVectorField(null=True)
    objects = SearchManager()

SoftDeleteModel如下

class SoftDeleteManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(is_deleted=False)


class SoftDeleteModel(models.Model):
    is_deleted = models.BooleanField(default=False)
    deleted_at = models.DateTimeField(null=True, blank=True)

    objects = SoftDeleteManager()
    all_objects = models.Manager()

    def soft_delete(self):
        self.is_deleted = True
        self.deleted_at = timezone.now()
        self.save()
        soft_delete_signal.send(sender=self.__class__, instance=self)

    def restore(self):
        self.is_deleted = False
        self.deleted_at = None
        self.save()

    class Meta:
        abstract = True

概括就是删除的时候标记is_deletedtrue, 并且重写了get_queryset方法, 使得model.objects.get()时, 让已被标记删除的数据不被查询到.

这本来工作好好的, 但为了引入拼音模糊搜索功能, 我在Review模型, 加了几行, 也就是上文里面的

    pinyin = models.TextField(verbose_name='拼音', blank=True)
    search_vector = SearchVectorField(null=True)
    objects = SearchManager()

使得我的model.objects.get()一直可以获取到已经被标记删除的数据.

解决方案

其中SearchManager如下

class SearchQuerySet(models.QuerySet):
    def search(self, query, query_table_name):
        ...


class SearchManager(models.Manager):
    def get_queryset(self):
        return SearchQuerySet(self.model, using=self._db)

    def search(self, query, page_size=10, current_page=1):
        ...
        search_results = self.get_queryset().search(query, query_table_name)
        return ...

原因很明显, objects = SearchManager()get_queryset的覆盖了我在SoftDeleteModel定义的objects的原有方法.

即这个get_queryset没有过滤器了.

我们要的, 不丢失SearchMangerSoftDeleteManger的所有功能.

思路整理来说就是, 在进行SearchManagerget_queryset后, 对其进行SoftDeleteMangerget_queryset的过滤, 可以考虑使用多重继承来实现, 毕竟除了get_queryset, 这两个class没有冲突.

class SoftDeleteSearchManager(SoftDeleteManager, SearchManager):
    def get_queryset(self):
        queryset = super().get_queryset()
        return queryset

MRO顺序

使用上述多重继承方案, 根据print(SoftDeleteSearchManager.mro())我们可得,

[<class 'course_assessment.managers.SoftDeleteSearchManager'>,
<class 'common.models.SoftDeleteManager'>,
<class 'course_assessment.managers.SearchManager'>,
<class 'django.db.models.manager.Manager'>,
<class 'django.db.models.manager.BaseManagerFromQuerySet'>,
<class 'django.db.models.manager.BaseManager'>,
<class 'object'>]
  1. SoftDeleteSearchManager:
    • 这是直接实例化的类, 因此是MRO的第一个元素.
  2. SoftDeleteManager:
    • 这是SoftDeleteSearchManager第一个继承的父类, 因此是MRO的第二个元素.
  3. SearchManager:
    • 由于SoftDeleteManager和SearchManager没有共同的父类, 类顺序直接依据左到右深度优先顺序, 因此SearchManager是第三个元素.
  4. Manager:
    • SoftDeleteManagerSearchManager都继承自Django内置的models.Manager, 因此Manager是MRO中第四个元素.
  5. object:
    • 这是所有新式类的基类, 也是MRO中的终点.

方法调用顺序

SoftDeleteSearchManager中调用get_queryset方法时, 搜索路径以下列顺序进行:

  1. 先在SoftDeleteSearchManager类中寻找get_queryset方法.
  2. 如果未找到, 向上查找SoftDeleteManager类.
  3. 如果还是未找到, 再查找SearchManager类.
  4. 再向上查找到models.Manager类.
  5. 最后到达顶层object类.

结合代码解释

在我们的代码中,SoftDeleteSearchManager类的具体实现覆盖了SoftDeleteManager中的get_queryset方法(MRO中的第二级):

class SoftDeleteSearchManager(SoftDeleteManager, SearchManager):
    def get_queryset(self):
        queryset = super().get_queryset() 
        # 这里的 super() 调用顺序依赖 MRO 顺序,首先使用 SoftDeleteManager 的 get_queryset 方法
        return queryset

也就是调用了

class SoftDeleteManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(is_deleted=False)

SoftDeleteManager这里再次使用的super().get_queryset()调用了MRO的第三级SearchManagerget_queryset, 也就是

class SearchManager(models.Manager):
    def get_queryset(self):
        return SearchQuerySet(self.model, using=self._db)

从而实现了我们上面说的

不丢失SearchMangerSoftDeleteManger的所有功能

总结

虽然平时记得MRO的继承顺序, 但真遇到的时候, 还真反应不过来实际被调用的顺序, 以及想不起来要这么写, 还是写的太少了, 天天写crud写的~