Django:复杂注释,如何避免for循环?

时间:2018-08-01 07:54:41

标签: python django django-orm

对于使用Google Realtime Analytics API的分析应用程序,我的models.py定义如下:

class Report(BaseModel):
    ios_report = JSONField()
    android_report = JSONField()

class Article(BaseModel):

    internal_id = models.IntegerField(unique=True)
    title = models.CharField(max_length=500)
    short_title = models.CharField(max_length=500)
    picture_url = models.URLField()
    published_date = models.DateField()
    clip_link = models.URLField()
    reports = models.ManyToManyField(
        "Report", through="ArticleInReport", related_name="articles"
    )

class ArticleInReport(BaseModel):

    article = models.ForeignKey("core.Article", on_delete=models.CASCADE, related_name='articleinreports')
    report = models.ForeignKey("core.Report", on_delete=models.CASCADE, related_name='articleinreports')
    ios_views = models.IntegerField()
    android_views = models.IntegerField()

    @property
    def total_views(self):
        return self.ios_views + self.android_views

一切始于以设置的时间间隔创建的Report对象。该报告包含有关文章及其各自视图的数据。 Report将与ArticleArticleInReport有关联,而Article在导入报告时保留了Article中的用户总数。

在我看来,我需要显示以下信息:

  • 最近24小时内获得观看次数的所有文章。
  • 每篇文章都带有以下信息:
  • 如果存在,则Report对象在最后一个views.py中拥有的视图数。如果不存在,则为0。

我正在reports_in_time_range = Report.objects.filter(created_date__range=[starting_range, right_now]) last_report = Report.objects.last() unique_articles = Article.objects.filter(articleinreports__report__in=reports_in_time_range).distinct('id') articles = Article.objects.filter(id__in=unique_articles).distinct('id').annotate( total_views=Case( When(articleinreports__report=last_report, then=(F("articleinreports__ios_views") + F("articleinreports__android_views"))), default=0, output_field=IntegerField(), )) sorted_articles = sorted(articles, key=operator.attrgetter('total_views'), reverse=True) 中实现以下目标:

total_views

但是我还需要为每个显示的文章提供一个“趋势图”,并提供以下信息:

  1. X轴:最近6个小时内导入的所有报告(或更确切地说,报告日期),无论文章ID是否出现在其中。
  2. Y轴:每个报告中的total_views值:如果存在该文章,则显示0,否则显示Article

我无法找到一种有效的方法,而无需借助多个for循环。我目前拥有的方法是在class Article(BaseModel): def get_article_data_for_reports(self, report_objs): graph_dict = {} graph_dict['x_vals'] = [x.created_date for x in report_objs] graph_dict['y_vals'] = [] for passed_report in report_objs: try: graph_dict['y_vals'].append(ArticleInReport.objects.get(article=self, report=passed_report).total_views) except ArticleInReport.DoesNotExist: graph_dict['y_vals'].append(0) print(graph_dict) return graph_dict 模型中添加以下方法:

views.py

context["articles"] = sorted_articles context["article_graphs"] = {} for article in sorted_articles: context["article_graphs"][article.internal_id]= article.get_article_data_for_reports(xhours_ago_reports) 文件中,我这样做:

<?xml version="1.0" encoding="utf-8"?>

然后我可以在视图的上下文中以某种方式使用它。但是在继续之前,我想知道是否有更好的方法可以做到这一点。每次刷新时,页面加载时间从毫秒增加到5-9秒。

1 个答案:

答案 0 :(得分:2)

from django.db.models import F


reports = Report.objects.all()  # Filter reports here

# This creates LEFT OUTER JOIN with all ArticleInReport, so each
# Article will appear in result once per each report which includes it
articles_with_reports = Article.objects.annotate(
    report_id=F('articleinreports__report_id')
)
# We are only interested in some reports
articles_in_reports = articles_with_reports.filter(
    report_id__in=reports.values('id')
)
# As each result row is actually ArticleInReport, this effectively gives
# amount of views per article per report
articles_with_views = articles_in_reports.annotate(
    views=(
            F('articleinreports__ios_views')
            + F('articleinreports__android_views')
    )
)
# Now do some processing in python - it's cheap
# We need dictionary to create final chart data
articles_map = {}  # {Article: {report_id: article_with_view}}
for article in articles_with_views:
    articles_map.setdefault(article, {})
    articles_map[article][article.report_id] = article.views

article_graphs = {}
# Force-evaluate to cache Reports
# Actually this would happen automatically, but to be certain...
reports = list(reports)
# As we want all Articles, we have to fetch them
for article in Article.objects.all():
    x_vals = []
    y_vals = []
    # Now for each report we will set article.views or 0
    # this will execute only once
    for report in reports:
        x_vals.append(report.created_date)
        if (
            article in articles_map
            and report.id in articles_map[article]
        ):
            # We have views for this article in this record
            y_vals.append(articles_map[article][report.id])
        else:
            # Defaults
            y_vals.append(0)
    article_graphs[article] = {
        'x_vals': x_vals,
        'y_vals': y_vals
    }

# Finally, we have article_graphs
# {
#    Article: {
#        'x_vals': [Date, Date, Date],
#        'y_vals': [100, 0, 50]
#    },
#    ....
# }

更新

要仅为Articles构建图形,并且在最近的报告中至少出现1次,我们只想直接使用articles_map

article_graphs = {}
# Force-evaluate to cache Reports
# Actually this would happen automatically, but to be certain...
reports = list(reports)
for article, views_by_report in articles_map.items():
    x_vals = []
    y_vals = []
    # Now for each report we will set article.views or 0
    for report in reports:
        x_vals.append(report.created_date)
        y_vals.append(views_by_report.get(report.id, 0))
    article_graphs[article] = {
        'x_vals': x_vals,
        'y_vals': y_vals
    }