我正在尝试优化Django项目(第1.8.6节),其中每个页面一次显示100家公司及其数据。我注意到在下面的index.html代码段中执行了不必要的SQL查询(尤其是contact.get_order_count
):
的index.html:
{% for company in company_list %}
<tr>
<td>{{ company.name }}</td>
<td>{{ company.get_order_count }}</td>
<td>{{ company.get_order_sum|floatformat:2 }}</td>
<td><input type="checkbox" name="select{{company.pk}}" id=""></td>
</tr>
{% for contact in company.contacts.all %}
<tr>
<td> </td>
<td>{{ contact.first_name }} {{ contact.last_name }}</td>
<td>Orders: {{ contact.get_order_count }}</td>
<td></td>
</tr>
{% endfor %}
{% endfor %}
问题似乎在于使用外键对其他表的常量SQL查询。我查了解如何解决这个问题,并发现prefetch_related()
似乎是解决方案。但是,无论我使用什么参数,我都会收到一个关于无法解析预取的TemplateSyntaxError。什么是正确的预取语法,还是有任何其他方法来优化我错过了?
我已经在下面列出了相关的model.py相关摘要,以防它相关。我让prefetch_related
使用已定义的方法,但它并没有改变性能或查询量。
model.py:
class Company(models.Model):
name = models.CharField(max_length=150)
def get_order_count(self):
return self.orders.count()
def get_order_sum(self):
return self.orders.aggregate(Sum('total'))['total__sum']
class Contact(models.Model):
company = models.ForeignKey(
Company, related_name="contacts", on_delete=models.PROTECT)
first_name = models.CharField(max_length=150)
last_name = models.CharField(max_length=150, blank=True)
def get_order_count(self):
return self.orders.count()
class Order(models.Model):
company = models.ForeignKey(Company, related_name="orders")
contact = models.ForeignKey(Contact, related_name="orders")
total = models.DecimalField(max_digits=18, decimal_places=9)
def __str__(self):
return "%s" % self.order_number
编辑:
视图是ListView,并将company_list
定义为model = Company
。我根据给定的建议修改了视图:
class IndexView(ListView):
template_name = "mailer/index.html"
model = Company
contacts = Contact.objects.annotate(order_count=Count('orders'))
contact_list = Company.objects.all().prefetch_related(Prefetch('contacts', queryset=contacts))
paginate_by = 100
答案 0 :(得分:1)
每次调用方法时,调用get_order_count
和get_order_sum
方法都会导致一个查询。你可以通过annotating the queryset来避免这种情况。
from django.db.models import Count, Sum
contacts = Contact.objects.annotate(order_count=Count('orders'), order_sum=Sum('orders'))
然后,您需要使用Prefetch
对象告诉Django使用带注释的查询集。
contact_list = Company.objects.all().prefetch_related(Prefetch("contacts", queryset=contacts)
请注意,您需要在视图中将prefetch_related
添加到查询集中,无法在模板中调用它。
由于您使用的是ListView
,因此您应该覆盖get_queryset
方法,并在那里调用prefetch_related()
:
class IndexView(ListView):
template_name = "mailer/index.html"
model = Company
paginate_by = 100
def get_queryset(self):
# Annotate the contacts with the order counts and sums
contacts = Contact.objects.annotate(order_count=Count('orders')
queryset = super(IndexView, self).get_queryset()
# Annotate the companies with order_count and order_sum
queryset = queryset.annotate(order_count=Count('orders'), order_sum=Sum('orders'))
# Prefetch the related contacts. Use the annotated queryset from before
queryset = queryset.prefetch_related(Prefetch('contacts', queryset=contacts))
return queryset
然后在您的模板中,您应该使用{{ contact.order_count }}
代替{{ contact.get_order_count }}
,{{ company.order_count }}
代替{{ company.get_order_count }}
。
答案 1 :(得分:0)
在views.py
中试试company_list = Company.objects.all().prefetch_related("order", "contacts")