Django中如何实现员工任务总时长的计算与展示?
我想知道在Django Template中是否有方法展示变量的总和——也就是对同类型变量,有没有模板侧的函数可以实现相加?
我知道逻辑通常应该放在模板之外,所以已经写了一个计算展示内容的函数:
def mission_duration(self): duration = self.end - self.begin return duration.days
通过{{ mission.mission_duration }}在模板中调用这个函数,运行正常。
但现在模板里有多个这类变量(目前是通过循环查询数据库展示的),有没有简便方法对它们的值求和并展示结果?而且结果要显示在循环之外的位置。
另外,如何在Model/View层解决这个问题?任务和员工是OneToMany关联关系,通常展示的是员工数据,我现在不清楚怎么在渲染模板前遍历员工的所有任务记录来计算所需的总和。
解答
模板侧方案(不推荐)
Django模板本身没有内置的求和函数,若要在模板中处理,可自定义模板标签,但会增加模板复杂度,不符合“模板只做展示”的设计原则:
- 在app目录下创建
templatetags文件夹,新建__init__.py和sum_tags.py:
from django import template register = template.Library() @register.simple_tag def sum_values(values): return sum(values)
- 在模板中加载并使用:
{% load sum_tags %} {% with durations=missions|get_list_of:'mission_duration' %} 总时长:{% sum_values durations %} {% endwith %}
注:需额外自定义get_list_of过滤器提取每个任务的时长值,或在循环时手动收集值到列表中,这种方式仅适合简单场景。
推荐的Model/View层方案
业务逻辑应放在视图或模型层处理,针对OneToMany关联的场景,有两种高效实现方式:
方式一:视图层遍历求和
若已获取员工的所有任务,可直接在视图中计算总和:
def employee_detail(request, employee_id): employee = Employee.objects.get(id=employee_id) missions = employee.foreign_missions.all() total_duration = sum(mission.mission_duration() for mission in missions) context = { 'employee': employee, 'missions': missions, 'total_duration': total_duration } return render(request, 'show.html', context)
模板中直接使用{{ total_duration }}即可展示结果。
方式二:模型管理器+数据库聚合(性能最优)
直接在数据库层面计算总和,避免Python层遍历,性能更优,还能直接返回天数:
from django.db.models import Sum, F, ExpressionWrapper, IntegerField class Duration_Manager(models.Manager): def total_duration_days(self, employee): # 计算总秒数后转换为天数 total_seconds = self.filter(employee=employee).aggregate( total=Sum(ExpressionWrapper( F('end') - F('begin'), output_field=IntegerField() )) )['total'] or 0 return total_seconds // 86400
在Foreign_Missions模型中指定管理器:
class Foreign_Missions(models.Model): # 模型字段... objects = Duration_Manager()
视图中调用并传入模板:
context = { 'missions': Foreign_Missions.objects.filter(employee_id=request.POST['id']).order_by('-begin'), 'total_duration_days': Foreign_Missions.objects.total_duration_days(employee) }
提问者2023年4月20日更新
基于第一个答案(其对我的应用结构做出了非常准确的假设),我进行了一些修改,现在的代码如下:
views.py
... context = { ... 'missions':Foreign_Missions.objects.filter(employee_id=request.POST['id']).order_by('-begin'), 'total_duration_days':Foreign_Missions.objects.total_duration_days(employee) } return render(request, 'show.html', context)
models.py(Foreign_Missions类中)
... objects = Duration_Manager() ...
managers.py
class Duration_Manager(models.Manager): def with_duration_days(self): return self.annotate( duration_days= ExpressionWrapper( (F('end') - F('begin')), output_field=fields.DurationField() ) ) def total_duration_days(self, employee): return self.with_duration_days().filter(employee=employee).aggregate( total_duration=Sum('duration_days') )['total_duration']
现在功能已正常运行……仍需调整Duration字段的输出格式为“仅显示天数”,但计算结果正确。
内容的提问来源于stack exchange,提问作者Krallenkroete

