【问题标题】:django-admin: Add extra row with totalsdjango-admin:添加额外的总计行
【发布时间】:2012-01-22 10:33:03
【问题描述】:

我正在使用标准的 django 管理模块来显示行列表。其中一列是数字字段。我想显示一个额外的“总计”行,其中大部分列为空白,除了数字列,它应该是所有对象的总数。

在管理模块中是否有一种简单的方法来执行此操作,或者我最好为它制作一个自定义视图?

我正在使用 Django 1.2。

【问题讨论】:

  • 只是为了确保:您是指当前页面上的所有对象还是数据库中的所有对象的总和?
  • @lazerscience:我指的是当前显示的对象(因此,如果您应用过滤器,它只会显示过滤后的项目)

标签: python django django-admin django-views django-1.2


【解决方案1】:

通过应用过滤器进行计算:

def changelist_view(self, request, extra_context=None):
    extra_context = extra_context or {}
    extra_context['total'] = sum([item.cash for item in self.get_queryset(request)])
    return super().changelist_view(request, extra_context=extra_context)

def get_queryset(self, request):
    qs = super().get_queryset(request)
    return qs.filter(**request.GET.dict())

【讨论】:

    【解决方案2】:

    在最初的问题中,在评论中澄清说他们对当前页面上的所有对象感兴趣。如果您对数据库中的所有对象感兴趣,但应用了过滤器,则可以采用类似于c4urself's answer 的路线,但具有以下核心位:

    from django.contrib import admin
    from django.db.models import Sum
    
    from tomatoes.tomato.models import CherryTomato
    
    class TomatoAdmin(admin.ModelAdmin):
    
        def get_changelist_instance(self, request):
            cl = super().get_changelist_instance(request)
            q = cl.get_queryset(request).aggregate(tomato_sum=Sum('num_in_box'))
            cl.tomato_count = q['tomato_sum']
            return cl
    
        class Meta:
            model = CherryTomato
    
        list_display = ('name', 'num_in_box')
    

    【讨论】:

      【解决方案3】:

      是的,您可以通过多种方式做到这一点,但大多数 django-ist 的做法是:

      首先覆盖默认的django列表视图...并给出一个新的模板文件目录

      ModelAdmin.changelist_view(self, request, extra_context=None)
      

      喜欢:

      class MyModelAdmin(admin.ModelAdmin):
      
          # A template for a very customized change view:
          change_list_template = 'admin/myapp/extras/sometemplate_change_form.html'
      
          def get_total(self):
              #functions to calculate whatever you want...
              total = YourModel.objects.all().aggregate(tot=Sum('total'))['tot']
              return total
      
          def changelist_view(self, request, extra_context=None):
              my_context = {
                  'total': self.get_total(),
              }
              return super(MyModelAdmin, self).changelist_view(request,
                  extra_context=my_context)
      

      因此,您将列表视图上下文添加为“总计”,以保留您的总价值并将其传递给模板。

      如果 change_list_template 将设置,django 使用该模板,否则,它使用标准 django 模板。

      如果调用 def changelist_view(self, request, extra_context=None),django 使用该函数创建内容,否则使用默认 django 视图。

      然后创建一个admin/myapp/extras/sometemplate_change_form.html 文件并将您的{{total}} 放在您想要的任何位置。

      如何override admin templates 的指南 这是override admin views

      的方法

      更新:我添加了一个简单的aggregate 来计算总数。您可以对其进行编辑以根据需要进行设置。

      更新 2:ModelAdmin 模板覆盖选项从 ModelAdmin.change_form_template 固定为 ModelAdmin.change_list_template。 (谢谢你 c4urself)。是的,但是更改默认的 django 管理模板是一个非常糟糕的选择,因为它被许多其他 ModelAdmin 使用,并且如果更新相关模板可能会导致问题。



      注意
      使用过滤器时,总数不会改变,请参阅下面的评论。

      【讨论】:

      • 我认为这是一个很好的方法,我认为change_form_template 应该是change_list_template。尽管如果您覆盖管理模板,则根本不需要给它一个模板名称。
      • 甜,这真是我一直在寻找的复制粘贴食谱。我只想说,我没有在 Admin 类上配置 change_list_template,而是创建了一个类似:templates/admin/my_app/my_model/change_list.html 的文件,然后在该文件中只使用 {% extends 'admin/change_list.html' %},并且只覆盖一个块,例如:@987654335 @。约定优于配置 ;)
      • 即使使用super(MyModelAdmin, self).get_queryset(request),使用过滤器时查询集也不会更新,Total 始终用于未过滤列表,c4urself 的答案并非如此,我认为这更好清洁工。
      【解决方案4】:

      这个话题已经持续了一段时间,但我可能不是最后一个正在寻找这种功能的人。因此,我创建了一个可以轻松添加总计的包。

      结帐https://github.com/douwevandermeij/admin-totals

      用法:

      from admin_totals.admin import ModelAdminTotals
      from django.contrib import admin
      from django.db.models import Sum, Avg
      
      @admin.register(MyModel)
      class MyModelAdmin(ModelAdminTotals):
          list_display = ['col_a', 'col_b', 'col_c']
          list_totals = [('col_b', Sum), ('col_c', Avg)]
      

      随意分叉和改进它。

      【讨论】:

      • 漂亮!这应该至少包含在框架中的 django 模块列表中,甚至更好。谢谢!
      • 这太棒了!谢谢!
      • 这让我们的生活更简单
      【解决方案5】:

      还有一个包“django-admin-changelist-stats”可以给出汇总统计。我已经在一个 Django 1.6 项目中成功使用了它。

      IMO 使用 c4urself 的解决方案进行细粒度的模板定制,但如果它只是聚合(总和/平均/最小/最大)的表,那么这个解决方案非常好。

      来自其网站

      " 这个简单的应用程序为 Django 管理更改列表视图提供了统计和聚合功能。 它允许以一种简单的方式在更改列表页面的末尾显示统计信息,只需向模型管理对象添加一个选项。 "

      更多信息,包括示例,请访问其 bitbucket 存储库网页 https://bitbucket.org/frankban/django-admin-changelist-stats/src

      【讨论】:

      • 不幸的是,这个库不支持相关字段的聚合——例如Sum('menu__meal__cost') 失败
      • 此存储库在提供的链接上不再可用。
      【解决方案6】:

      我认为 Django 的方法是覆盖 django 的管理应用程序使用的 ChangeList 类。您可以在 django 1.2 中通过在管理类中调用 get_changelist 方法来执行此操作。在我的示例中:TomatoAdmin 调用此方法返回自定义 ChangeList 类。这个 ChangeList 类:MyChangeList 只是在change_list.html 的上下文中添加了一个额外的属性。

      确保 change_list.html 位于以下目录中:

      app_label/change_list.html
      

      在示例中为:templates/admin/tomato/change_list.html

      models.py(带有整数字段的简单模型)

      class CherryTomato(models.Model):
          name = models.CharField(max_length=100)
          num_in_box = models.IntegerField()
      

      admin.py(您的 Admin 类和自定义 ChangeList 类)

      from django.contrib import admin
      from django.contrib.admin.views.main import ChangeList
      from django.db.models import Count, Sum
      
      from tomatoes.tomato.models import CherryTomato
      
      class MyChangeList(ChangeList):
      
          def get_results(self, *args, **kwargs):
              super(MyChangeList, self).get_results(*args, **kwargs)
              q = self.result_list.aggregate(tomato_sum=Sum('num_in_box'))
              self.tomato_count = q['tomato_sum']
      
      class TomatoAdmin(admin.ModelAdmin):
      
          def get_changelist(self, request):
              return MyChangeList
      
          class Meta:
              model = CherryTomato
      
          list_display = ('name', 'num_in_box')
      
      admin.site.register(CherryTomato, TomatoAdmin)
      

      change_list.html(仅相关位,复制/粘贴现有​​的并扩展它)

        {% block result_list %}
            {% if action_form and actions_on_top and cl.full_result_count %}{% admin_actions %}{% endif %}
            {% result_list cl %}
            {% if action_form and actions_on_bottom and cl.full_result_count %}{% admin_actions %}{% endif %}
            Tomatoes on this page: {{ cl.tomato_count }}
        {% endblock %}
      

      我会留给你如何按照你想要的方式设置样式。

      【讨论】:

      • 实际上这是更好的解决方案,因为如上所述自定义 ChangeList 可以针对选定的过滤器聚合“total”。
      • 更好,可以和过滤器相处:)
      • 这对我来说几乎是完美的。总数也反映了分页,因为它确实进入了查询集。
      • 我收到一个模板错误:'第 4 行的块标记无效:'admin_actions'、预期的 'elif'、'else' 或 'endif'。您是否忘记注册或加载此标签?我不明白 endif 在那里。
      • 刚刚检查过,这仍然是它的正确名称(参见:github.com/django/django/blob/master/django/contrib/admin/…)。由于模板名称不正确,您可能无法正确覆盖它?
      【解决方案7】:

      好的,所以有一种方法可以做到这一点,包括添加几个新的模板标签和扩展管理模板。

      首先,在应用的templatetags 文件夹中,创建一个包含模板标签的admin_totals.py 文件以创建总计行:

      from django.template import Library
      
      register = Library()
      
      def totals_row(cl):
          total_functions = getattr(cl.model_admin, 'total_functions', {})
          totals = []
          for field_name in cl.list_display:
              if field_name in total_functions:
                  values = [getattr(i, field_name) for i in cl.result_list]
                  totals.append(total_functions[field_name](values))
              else:
                  totals.append('')
          return {'cl': cl, 'totals_row': totals}
      totals_row = register.inclusion_tag("myapp/totals_row.html")(totals_row)
      

      那么您需要myapp/totals_row.html 中所述行的模板(无论您的模板在哪里):

      <table id="result_totals">
          <tfoot>
              <tr>
                  {% for total in totals_row %}<td>{{ total }}</td>{% endfor %}
              </tr>
          </tfoot>
      </table>
      

      然后你需要将它连接到一个继承自 Django 默认的自定义管理模板中,例如myapp/mymodel_admin.html

      {% extends "admin/change_list.html" %}
      
      {% load admin_totals %}
      
      {% block result_list %}
      {{ block.super }}
      {% totals_row cl %}
      {% endblock %}
      

      最后,将其连接到应用程序中 admin.py 文件的配置中:

      class MyModelAdmin(ModelAdmin):
      
          list_display = ('name', 'date', 'numerical_awesomeness')
          total_functions = {'numerical_awesomeness': sum}
      
          change_list_template = 'myapp/mymodel_admin.html'
      

      这应该为您的新模型连接到自定义管理模板,显示总计行。如果您愿意,还可以使用 sum 以外的其他摘要函数对其进行扩展。

      还剩下一点:总计行实际上不在结果表中,因为这需要对 Django 管理模板进行一些相当粗糙的复制和粘贴。对于奖励积分,您可以在 totals_row.html 文件的底部添加以下 JavaScript:

      <script type="text/javascript">
          django.jQuery('#result_list').append(django.jQuery('#result_totals tfoot')[0])
          django.jQuery('#result_totals').remove()
      </script>
      

      一个警告:所有这些将只反映当前显示的项目的总数,而不是现有的所有项目。解决此问题的一种方法是将list_per_page 设置为ModelAdmin 类中的某个大得无法使用的数字,如果您不介意潜在的性能影响的话。

      【讨论】:

        【解决方案8】:

        如果您创建一个覆盖管理视图的自定义视图,您应该能够获取查询集以及当前页面信息并对数据进行切片以创建适合当前页面的总数。如果您想要一个真实的总数,我仍然会看到自定义覆盖的管理视图作为您正在寻找的选项。

        【讨论】:

          【解决方案9】:

          AFAIK 如果不创建自定义视图,就无法做到这一点。无论如何,这个概念有点缺陷,因为用户希望这样的行只显示可见对象的总数,而不是查询集中的所有对象。因此,任何分页都会导致混乱。

          顺便说一句,您可以通过执行以下操作将额外的添加到管理列表视图:

          class ItemAdmin(admin.ModelAdmin):
              model = Item
          
              list_display = ('field1', 'field2', 'extra_field')
          
              def extra_field(self, obj):
                  return u'%s' % do_something_with(obj)
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2019-01-28
            • 2023-01-13
            • 1970-01-01
            • 2013-02-21
            • 1970-01-01
            • 2020-06-09
            • 1970-01-01
            • 2012-05-12
            相关资源
            最近更新 更多