代码之家  ›  专栏  ›  技术社区  ›  zerohedge

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

  •  1
  • zerohedge  · 技术社区  · 6 年前

    对于使用谷歌实时分析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 以设置的间隔创建的对象。此报告包含有关文章及其各自视图的数据。一 报告 将与 Article 通过 ArticleInReport ,其中包含 文章 在导入报表时 .

    在我的视图中,我需要显示以下信息:

    • 过去24小时内收到的所有文章。
    • 每一篇文章都附有以下信息:
    • 如果存在,视图的数目 文章 对象在最后 报告 . 如果不存在,则为0。

    我的目标是 views.py :

    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)
    

    但我还需要为显示的每一篇文章提供一个“趋势图”,其中包含以下信息:

    1. X轴:过去6小时内导入的所有报告(或更确切地说,报告日期),无论文章ID是否显示在其中。
    2. Y轴:的值 total_views 在每个报告中:如果文章存在,则显示 总图 ,如果没有,返回 0 .

    我找不到有效的方法 在不使用多个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
    

    而在 VIEW 文件我这样做:

        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)
    

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

    1 回复  |  直到 6 年前
        1
  •  2
  •   Alexandr Tatarinov    6 年前
    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 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
        }