如何在 Django 中预取相关对象?
How can I prefetch related objects in Django?
假设我有以下模型和相关方法:
class Turbine(models.Model):
...
pass
def relContracts(self):
contracts = self.contracted_turbines.all()
return contracts
class Contract(models.Model):
turbines = models.ManyToManyField(Turbine,related_name='contracted_turbines')
def _contracted_windfarm_name(self):
windfarms = self.turbines.order_by().values_list("wind_farm__name", flat=True).distinct().select_related
if len(windfarms) == 1:
return windfarms[0]
else:
return ", ".join([str(x) for x in windfarms])
contracted_windfarm_name = property(_contracted_windfarm_name)
def _turbine_age(self):
first_commisioning = self.turbines.all().aggregate(first=Min('commisioning'))['first']
start = self.start_operation.year
age = start - first_commisioning.year
return age
turbine_age = property(_turbine_age)
Django-debug-toolbar 告诉我,函数“_contracted_windfarm_name”和“_turbine_age”会导致每个合约的数据库重复。
我的合同查询集由以下 get_queryset 方法接收,我已经为其他方法成功预取 'turbines':
def get_queryset(self, **kwargs):
qs = super(ContractTableView, self).get_queryset().filter(active=True).prefetch_related('turbines', 'turbines__wind_farm')
self.filter = self.filter_class(self.request.GET, queryset=qs)
return self.filter.qs
我试过预取 'turbines__contracted_turbines' 但无法减少重复项的数量。
_contracted_windfarm_name
方法用于填充一个django-tables2方法的列如下:
contracted_windfarm = dt2.Column(accessor='contracted_windfarm_name', verbose_name='Wind Farm', orderable=False)
我哪里弄错了?如何预取涡轮机的关联合约?
解决方案:第一个问题
我在 get_queryset() 方法中向查询集添加了一个简单的注释:
def get_queryset(self, **kwargs):
qs = super(ContractTableView, self).get_queryset()\
.filter(active=True).prefetch_related('turbines', 'turbines__wind_farm')\
.annotate(first_com_date=Case(When(turbines__commisioning__isnull=False, then=Min('turbines__commisioning'))))
self.filter = self.filter_class(self.request.GET, queryset=qs)
return self.filter.qs
这导致 _turbine_age() 方法略有变化:
def _turbine_age(self):
first_commisioning = self.first_commisioning
start = self.start_operation.year
age = start - first_commisioning.year
return age
turbine_age = property(_turbine_age)
解决方案:第二个问题
在get_queryset()
方法中预取了turbines__wind_farm
,就不需要调用distinct()
方法了:
def _contracted_windfarm_name(self):
windfarms = list(set([str(x.wind_farm.name) for x in self.turbines.all()]))
if len(windfarms) == 1:
return windfarms[0]
else:
return ", ".join([str(x) for x in windfarms])
contracted_windfarm_name = property(_contracted_windfarm_name)
可以删除所有重复的查询!
感谢@dirkgroten 的宝贵贡献!
from django.db.models import Min
class ContractManager(models.Manager):
def with_first_commissioning(self):
return self.annotate(first_commissioning=Min('turbines__commissioning'))
class Contract(models.Model):
objects = ContractManager()
...
然后 Contract.objects.with_first_commissioning()
returns 你的查询集每个 Contract
都有额外的 first_commissioning
值。所以在 Contract._turbine_age()
中你可以只删除第一行。
现在风电场名称的情况有点复杂。如果您使用的是 Postgresql(支持 StringAgg
),您可以类似地在 ContractManager
中添加此查询集:
from django.db.models import Subquery, OuterRef
from django.contrib.postgres.aggregates import StringAgg
def with_windfarms(self):
wind_farms = WindFarm.objects.filter('turbines__contract'=OuterRef('pk')).order_by().distinct().values('turbines__contract')
wind_farm_names = wind_farms.annotate(names=StringAgg('name', delimiter=', ')).values('names')
return self.annotate(wind_farm_names=Subquery(wind_farm_names))
然后在你的 _contracted_windfarm_name()
方法中,你可以访问 self.wind_farm_names
假设你正在循环查询集的结果(你应该检查 hasattr
以防你的方法得到以不同的方式使用)。
如果您不在 Postgresql 上,则只需更改查询集以执行 prefetch_related
,然后确保在此之后不添加任何与查询相关的逻辑:
from django.db.models import Prefetch
def with_windfarms(self):
return self.prefetch_related(Prefetch('turbines', queryset=Turbine.objects.order_by().select_related('wind_farm').distinct('wind_farm__name')))
这样在您的 _contracted_wind_farms
方法中,您可以 [str(x.wind_farm.name) for x in self.turbines]
在这两种情况下,我假设您在视图中的某处循环遍历查询集中的 contracts
:
for contract in Contract.objects.with_first_commissioning():
contract._turbine_age()...
for contract in Contract.objects.with_windfarms():
contract._contracted_windfarm_name()...
假设我有以下模型和相关方法:
class Turbine(models.Model):
...
pass
def relContracts(self):
contracts = self.contracted_turbines.all()
return contracts
class Contract(models.Model):
turbines = models.ManyToManyField(Turbine,related_name='contracted_turbines')
def _contracted_windfarm_name(self):
windfarms = self.turbines.order_by().values_list("wind_farm__name", flat=True).distinct().select_related
if len(windfarms) == 1:
return windfarms[0]
else:
return ", ".join([str(x) for x in windfarms])
contracted_windfarm_name = property(_contracted_windfarm_name)
def _turbine_age(self):
first_commisioning = self.turbines.all().aggregate(first=Min('commisioning'))['first']
start = self.start_operation.year
age = start - first_commisioning.year
return age
turbine_age = property(_turbine_age)
Django-debug-toolbar 告诉我,函数“_contracted_windfarm_name”和“_turbine_age”会导致每个合约的数据库重复。
我的合同查询集由以下 get_queryset 方法接收,我已经为其他方法成功预取 'turbines':
def get_queryset(self, **kwargs):
qs = super(ContractTableView, self).get_queryset().filter(active=True).prefetch_related('turbines', 'turbines__wind_farm')
self.filter = self.filter_class(self.request.GET, queryset=qs)
return self.filter.qs
我试过预取 'turbines__contracted_turbines' 但无法减少重复项的数量。
_contracted_windfarm_name
方法用于填充一个django-tables2方法的列如下:
contracted_windfarm = dt2.Column(accessor='contracted_windfarm_name', verbose_name='Wind Farm', orderable=False)
我哪里弄错了?如何预取涡轮机的关联合约?
解决方案:第一个问题
我在 get_queryset() 方法中向查询集添加了一个简单的注释:
def get_queryset(self, **kwargs):
qs = super(ContractTableView, self).get_queryset()\
.filter(active=True).prefetch_related('turbines', 'turbines__wind_farm')\
.annotate(first_com_date=Case(When(turbines__commisioning__isnull=False, then=Min('turbines__commisioning'))))
self.filter = self.filter_class(self.request.GET, queryset=qs)
return self.filter.qs
这导致 _turbine_age() 方法略有变化:
def _turbine_age(self):
first_commisioning = self.first_commisioning
start = self.start_operation.year
age = start - first_commisioning.year
return age
turbine_age = property(_turbine_age)
解决方案:第二个问题
在get_queryset()
方法中预取了turbines__wind_farm
,就不需要调用distinct()
方法了:
def _contracted_windfarm_name(self):
windfarms = list(set([str(x.wind_farm.name) for x in self.turbines.all()]))
if len(windfarms) == 1:
return windfarms[0]
else:
return ", ".join([str(x) for x in windfarms])
contracted_windfarm_name = property(_contracted_windfarm_name)
可以删除所有重复的查询!
感谢@dirkgroten 的宝贵贡献!
from django.db.models import Min
class ContractManager(models.Manager):
def with_first_commissioning(self):
return self.annotate(first_commissioning=Min('turbines__commissioning'))
class Contract(models.Model):
objects = ContractManager()
...
然后 Contract.objects.with_first_commissioning()
returns 你的查询集每个 Contract
都有额外的 first_commissioning
值。所以在 Contract._turbine_age()
中你可以只删除第一行。
现在风电场名称的情况有点复杂。如果您使用的是 Postgresql(支持 StringAgg
),您可以类似地在 ContractManager
中添加此查询集:
from django.db.models import Subquery, OuterRef
from django.contrib.postgres.aggregates import StringAgg
def with_windfarms(self):
wind_farms = WindFarm.objects.filter('turbines__contract'=OuterRef('pk')).order_by().distinct().values('turbines__contract')
wind_farm_names = wind_farms.annotate(names=StringAgg('name', delimiter=', ')).values('names')
return self.annotate(wind_farm_names=Subquery(wind_farm_names))
然后在你的 _contracted_windfarm_name()
方法中,你可以访问 self.wind_farm_names
假设你正在循环查询集的结果(你应该检查 hasattr
以防你的方法得到以不同的方式使用)。
如果您不在 Postgresql 上,则只需更改查询集以执行 prefetch_related
,然后确保在此之后不添加任何与查询相关的逻辑:
from django.db.models import Prefetch
def with_windfarms(self):
return self.prefetch_related(Prefetch('turbines', queryset=Turbine.objects.order_by().select_related('wind_farm').distinct('wind_farm__name')))
这样在您的 _contracted_wind_farms
方法中,您可以 [str(x.wind_farm.name) for x in self.turbines]
在这两种情况下,我假设您在视图中的某处循环遍历查询集中的 contracts
:
for contract in Contract.objects.with_first_commissioning():
contract._turbine_age()...
for contract in Contract.objects.with_windfarms():
contract._contracted_windfarm_name()...