在大表中使用预取和过滤器优化 Django

问题描述

我有一个使用 MysqLPHP/html 数据库,正在将其转移到 Django 项目中。

我拥有所有功能,但由于与其他表的关系,加载我想要的数据表非常缓慢。

搜索几天后,我知道我可能必须使用 model.Manager 才能使用 prefetch_all。但是,我并没有被困在如何将其调用到我的模板中。

我有以下模型(简化版):

class OrganisationManager(models.Manager):
    def get_queryset_director(self):
        person_query = Position.objects.select_related('person').filter(position_current=True,position_type="director"
            )
        return super().get_queryset().prefetch_related(Prefetch('position_set',queryset=person_query,to_attr="position_list"))
    def get_queryset_president(self):
        person_query = Position.objects.select_related('person').filter(position_current=True,position_type="president"
            )
        return super().get_queryset().prefetch_related(Prefetch('position_set',to_attr="position_list"))
 
class Person(models.Model):
    full_name = models.CharField(max_length=255,blank=True,null=True)
    country = models.ForeignKey(Country,models.CASCADE,null=True)
    birth_date = models.DateField(blank=True,null=True)

class Organisation(models.Model):
    organisation_name = models.CharField(max_length=255,null=True)
    positions = models.ManyToManyField(Person,through='Position')

    # positions are dynamic,even though there should only be only one director and president at each given time,a onetoone model wouldn't work in this scenario
    objects =  OrganisationManager()

    # The following defs are currently used to show the names and start dates of the director and president in the detailview and listview
    def director(self):
        return self.position_set.filter(position_current=True,position_type="director").last()
    def president(self):
        return self.position_set.filter(position_current=True,position_type="P").last()

class Position(models.Model):
    POSITION_TYPES = (
        ('president','President'),('director','Director'),)
    person = models.ForeignKey(Person,on_delete=models.CASCADE)
    organisation = models.ForeignKey(Organisation,on_delete=models.CASCADE)
    position_type = models.CharField(max_length=255,choices=POSITION_TYPES,null=True)
    position_current = models.BooleanField(default=False)
    position_start = models.CharField(max_length=10,null=True)

我希望我的桌子看起来像这样:

组织名称 总裁 总裁上任日期 导演 导演开始日期
组织 1 组织 1 的主席 2013 组织 1 总监 2015
组织 2 组织 2 主席 2018 组织 2 总监 2017

使用我目前拥有的代码,一切都很好。但是因为每次都要调用数据库,这甚至会导致Heroku超时。

我不明白如何在表 (models.Manager) 模板中的 ListView 中使用预取查询。谢谢!

解决方法

实现您想要的结果的一种方法是使用 subqueries。所以像:

president_subquery = Position.objects.filter(
    organisation=OuterRef('pk'),position_type='president',position_current=True
).last()

director_subquery = Position.objects.filter(
    organisation=OuterRef('pk'),position_type='director',position_current=True
).last()

Organisation.objects.annotate(
    president=Subquery(president_subquery.values('person__fullname')),president_start_date=Subquery(president_subquery.values('position_start')),director=Subquery(director_subquery.values('person__fullname')),director_start_date=Subquery(director_subquery.values('position_start')),)