页面加载非常慢 - 如何正确优化?

时间:2017-01-06 16:08:59

标签: python django django-models django-database sql-optimization

我正在开发一个包含不同产品统计信息的大页面。我在那里使用多个图表,表格等。加载页面需要5秒钟。

这是一个非常简化的 models.py

修改

例如,OccurencesTable包含此行:

last_scan_time = tables.columns.TemplateColumn("""{{ record.get_last_scan.datetime }}""",accessor='get_last_scan.datetime', verbose_name='Last scan time')

因此,对于每一行,必须对调用上次扫描的数据库执行查询。更好的方法是预加载所有Scan个对象。

产品

class Product(models.Model):
    user = models.ForeignKey(User, null=False, blank=False, related_name='products')
    name = models.CharField(max_length=200)

    def get_occurences(self):
        return self.occurences.prefetch_related("scans__price__currency")

    def get_occurences_count(self):
        return self.occurences.all().count()

    def get_all_scans(self):
        return [item for sublist in [x.scans.all() for x in self.get_occurences()] for item in sublist]

    def get_all_valid_scans(self):
        return [item for sublist in [x.get_valid_scans() for x in self.get_occurences()] for item in sublist]

    def get_last_scans(self):
        scans = []
        for occ in self.get_occurences():
            scan = occ.get_last_scan()
            if scan:
                scans.append(scan)
        return scans

   # @property
    #def last_scan_time(self):
        #scans = sorted([x for x in self.get_last_scans() if x.datetime],key = lambda x: x.datetime, reverse=True)
        #Scan.objects.filter(occurence__product=self,price__amount__isnull=False)
        #return str(scans[0].datetime)

    def get_last_min_scan(self):

        sorted_last_scans = [x for x in self.get_last_scans() if x.valid]
        sorted_last_scans.sort(key=lambda x: x.price.eur_price)
        return sorted_last_scans[0] if sorted_last_scans else None

    def get_last_min_price(self):
        last_scan = self.get_last_min_scan()
        if last_scan:
            return last_scan.price.eur_price
        return None

    def get_active_occurences(self):
        return self.get_occurences().filter(active=True)

OCCURENCE

class Occurence(models.Model):
    product = models.ForeignKey(Product, related_name='occurences', on_delete=models.CASCADE)

    _last_scan = models.OneToOneField('Scan',null=True,blank=True,related_name='+')


    currency = models.ForeignKey('Currency',related_name='occurences')

    def get_last_scan(self):
        try:
            last = self.scans.select_related("price__amount").order_by('datetime').last()
        except:
            last = None
        return last

    def get_last_valid_scan(self):
        try:
            last = self.scans.exclude(price__isnull=True).order_by('-datetime').first()
        except:
            last = None
        return last

    def get_second_last_valid_scan(self):
        scans = self.scans.exclude(price__isnull=True).order_by('-datetime').select_related("price")
        if scans.count()>=2:
            return scans[1]
        return None

    def get_valid_scans(self):
        return self.scans.all().exclude(price__isnull=True)

    def get_min_scan(self):
        scan = self.get_valid_scans().order_by('price__amount').first()
        if scan:
            return scan
        return None

    """ STATS METHODS """

    def stats_get_difference_for_two_last_scans(self):
        second_last_valid_scan = self.get_second_last_valid_scan()
        if second_last_valid_scan:
            difference_in_percent = math_ops.round_eur(decimal.Decimal(-1 * (100 - self.get_last_valid_scan().price.eur_price / second_last_valid_scan.price.eur_price * 100), 2))
        else:
            difference_in_percent = decimal.Decimal(0)
        return {'percent':difference_in_percent,
                'xml_tag':'<two_last_scans_difference_in_percent>',
                'xml_close_tag':'</two_last_scans_difference_in_percent>',
                'label':'Last scans diff'}

    def stats_get_min_price(self):
        scan = self.get_min_scan()
        if scan:
            price = scan.price.eur_price
        else:
            price = None
        return {'price': price,
                'xml_tag': '<min_price>',
                'xml_close_tag': '</min_price>',
                'label': 'Min'}

    def stats_get_avg_price(self):
        prices = [x.price for x in self.scans.all() if x.price]
        if prices:
            price = math_ops.round_eur(decimal.Decimal(sum([x.eur_price for x in prices]) / len(prices), 2))
        else:
            price = None

        preferred_currency = self.product.user.userprofile.preferred_currency

        if preferred_currency:
            if preferred_currency.shortcut == 'czk':
                amount = Exchange.eur_to_czk(price)
                pref_currency_string = '{} CZK'.format(amount)
                pref_currency_amount = amount
            else:
                amount = price
                pref_currency_string = u'{} €'.format(amount)
                pref_currency_amount = amount
        else:
            if self.currency.shortcut == 'czk':
                amount = Exchange.eur_to_czk(price)
                pref_currency_string = '{} CZK'.format(amount)
                pref_currency_amount = amount
            else:
                amount = price
                pref_currency_string = u'{} €'.format(amount)
                pref_currency_amount = amount


        return {'price': price,
                'pref_currency_string':pref_currency_string,
                'pref_currency_amount':pref_currency_amount,
                'xml_tag': '<average_price>',
                'xml_close_tag': '</average_price>',
                'label': 'AVG'}

价格

class Price(models.Model):
    currency = models.ForeignKey('Currency',related_name='prices')
    amount = models.DecimalField(max_digits=10,decimal_places=2)


    def __unicode__(self):
        return u'{} {}'.format(self.amount,self.currency)

    def to_eur(self):
        if self.currency.shortcut=='eur':
            return self.amount
        elif self.currency.shortcut=='czk':
            return Exchange.objects.first().czk_to_eur(self.amount)

    def to_czk(self):
        if self.currency.shortcut == 'czk':
            return self.amount
        elif self.currency.shortcut == 'eur':
            return Exchange.objects.first().eur_to_czk(self.amount)

    @property
    def eur_price(self):
        if self.currency.shortcut=='eur':
            return self.amount
        elif self.currency.shortcut=='czk':
            return self.to_eur()

    @property
    def czk_price(self):
        cents = decimal.Decimal('01')
        if self.currency.shortcut == 'czk':
            return (self.amount).quantize(cents, decimal.ROUND_HALF_UP)
        elif self.currency.shortcut == 'eur':
            return self.to_czk()

    @property
    def pref_currency_amount(self):
        pref_currency = self.scan.occurence.product.user.userprofile.preferred_currency
        if pref_currency:
            if pref_currency.shortcut == 'czk':
                return self.czk_price
            else: return self.eur_price
        return self.amount

    @property
    def pref_currency_string(self):
        pref_currency = self.scan.occurence.product.user.userprofile.preferred_currency
        # return pref_currency.shortcut
        if pref_currency:
            if pref_currency.shortcut.lower() == 'czk':
                return u'{} {}'.format(self.czk_price, pref_currency.shortcut)
            else:
                return u'{} {}'.format(self.eur_price, pref_currency.special_sign)
        return u'{} {}'.format(self.amount,self.currency.special_sign)


    def get_price(self,currency):
        if currency=='eur':
            return self.eur_price
        elif currency=='czk':
            return self.czk_price

    def get_exchanged_price_string(self):
        if self.currency.shortcut=='czk':
            return u'{} {}'.format(Exchange.czk_to_eur(self.amount),u'€')
        else:
            return u'{} {}'.format(Exchange.eur_to_czk(self.amount),'CZK')

    def get_original_price_string(self):
        if self.currency.shortcut=='czk':
            return u'{} {}'.format(self.amount,u'€')
        else:
            return u'{} {}'.format(Exchange.eur_to_czk(self.amount),'CZK')

例如,根据occurences,渲染表django-debug-toolbar大约需要2秒。我正在尝试使用select_relatedprefetch_related来优化它,但它仍然很慢。

这是因为我调用的不同方法具有相同的查询,并且这些查询被多次调用。

class OccurencesTable(tables.Table):
    site = tables.columns.TemplateColumn("""<a href="{{ record.url }}">{{ record.site.name }}</a>""",accessor='site.name', verbose_name=u'Site')
    avg_price = tables.columns.TemplateColumn("""{{ record.stats_get_avg_price.pref_currency_string }}""",accessor='stats_get_avg_price.price', verbose_name='AVG price')
    last_scan_price = tables.columns.TemplateColumn("""{{ record.get_last_scan.price.pref_currency_string }} """,accessor='get_last_scan.price.amount', verbose_name='Last scan price')
    last_scan_time = tables.columns.TemplateColumn("""{{ record.get_last_scan.datetime }}""",accessor='get_last_scan.datetime', verbose_name='Last scan time')
    difference = tables.columns.TemplateColumn("""{% load static %}{% with diff=record.stats_get_difference_for_two_last_scans.percent %}
                {% if diff > 0 %}+{% endif %}{{ diff }} % <img style="height: 15px" src="{% if diff < 0 %}{% static "img/icons/arrow-trend-minus.png" %}{% elif diff == 0 %}{% static "img/icons/arrow-trend-normal.png" %}{% else %}{% static "img/icons/arrow-trend-plus.png" %}{% endif %}">
                {% endwith %}""",verbose_name='Difference')

    class Meta:
        model = Occurence
        fields = ('id', 'site', 'last_scan_time','last_scan_price', 'difference',  'avg_price')
        attrs = {'id': 'id_occurences_table',
                 'class': 'table', }

无法弄清楚如何优化模型 Occurence Product 的方法。你有什么想法吗?

1 个答案:

答案 0 :(得分:2)

使用这样的代码,您应该对获得的时间感到兴奋

@property
def last_scan_time(self):
    scans = sorted([x for x in self.get_last_scans() if x.datetime],key = lambda x: x.datetime, reverse=True)
    Scan.objects.filter(occurence__product=self,price__amount__isnull=False)
    return str(scans[0].datetime)

此代码通过调用get_last_scans()检索整个表,然后在python代码中对结果进行排序!数据库具有非常快速的内置排序功能。请使用它。

此代码中还有很多其他类似的功能。你将不得不修复它们。在数据库中进行过滤和排序。不在你的python代码中。