引言
之前只是看书的时候看过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_deleted
为true
, 并且重写了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
没有过滤器了.
我们要的, 不丢失SearchManger
和SoftDeleteManger
的所有功能.
思路整理来说就是, 在进行SearchManager
的get_queryset
后, 对其进行SoftDeleteManger
中get_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'>]
- SoftDeleteSearchManager:
- 这是直接实例化的类, 因此是MRO的第一个元素.
- SoftDeleteManager:
- 这是SoftDeleteSearchManager第一个继承的父类, 因此是MRO的第二个元素.
- SearchManager:
- 由于SoftDeleteManager和SearchManager没有共同的父类, 类顺序直接依据左到右深度优先顺序, 因此SearchManager是第三个元素.
- Manager:
SoftDeleteManager
和SearchManager
都继承自Django内置的models.Manager
, 因此Manager是MRO中第四个元素.
- object:
- 这是所有新式类的基类, 也是MRO中的终点.
方法调用顺序
在SoftDeleteSearchManager
中调用get_queryset
方法时, 搜索路径以下列顺序进行:
- 先在
SoftDeleteSearchManager
类中寻找get_queryset
方法. - 如果未找到, 向上查找
SoftDeleteManager
类. - 如果还是未找到, 再查找
SearchManager
类. - 再向上查找到
models.Manager
类. - 最后到达顶层
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的第三级SearchManager
的get_queryset
, 也就是
class SearchManager(models.Manager):
def get_queryset(self):
return SearchQuerySet(self.model, using=self._db)
从而实现了我们上面说的
不丢失
SearchManger
和SoftDeleteManger
的所有功能
总结
虽然平时记得MRO的继承顺序, 但真遇到的时候, 还真反应不过来实际被调用的顺序, 以及想不起来要这么写, 还是写的太少了, 天天写crud写的~