From a5ec2d85c83693c3bc5b871bf04b82ae1654d690 Mon Sep 17 00:00:00 2001 From: Ohad Lutzky Date: Mon, 4 Oct 2021 20:37:11 +0100 Subject: [PATCH] Add tummy time duration report (#316) --- .../dashboard/child_button_group.html | 1 + reports/graphs/__init__.py | 1 + reports/graphs/tummytime_duration.py | 72 +++++++++++++++++++ .../templates/reports/tummytime_duration.html | 9 +++ reports/urls.py | 5 ++ reports/views.py | 24 +++++++ 6 files changed, 112 insertions(+) create mode 100644 reports/graphs/tummytime_duration.py create mode 100644 reports/templates/reports/tummytime_duration.html diff --git a/dashboard/templates/dashboard/child_button_group.html b/dashboard/templates/dashboard/child_button_group.html index 88490f08..1719eec2 100644 --- a/dashboard/templates/dashboard/child_button_group.html +++ b/dashboard/templates/dashboard/child_button_group.html @@ -27,6 +27,7 @@ {% trans "Feeding Durations (Average)" %} {% trans "Sleep Pattern" %} {% trans "Sleep Totals" %} + {% trans "Tummy Time Durations (Sum)" %} {% trans "Weight" %} diff --git a/reports/graphs/__init__.py b/reports/graphs/__init__.py index b6de25e1..66453167 100644 --- a/reports/graphs/__init__.py +++ b/reports/graphs/__init__.py @@ -5,4 +5,5 @@ from .feeding_amounts import feeding_amounts # NOQA from .feeding_duration import feeding_duration # NOQA from .sleep_pattern import sleep_pattern # NOQA from .sleep_totals import sleep_totals # NOQA +from .tummytime_duration import tummytime_duration # NOQA from .weight_weight import weight_weight # NOQA diff --git a/reports/graphs/tummytime_duration.py b/reports/graphs/tummytime_duration.py new file mode 100644 index 00000000..e3e8b15f --- /dev/null +++ b/reports/graphs/tummytime_duration.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +from django.db.models import Count, Sum +from django.db.models.functions import TruncDate +from django.utils.translation import gettext as _ + +import plotly.offline as plotly +import plotly.graph_objs as go + +from core.utils import duration_parts + +from reports import utils + + +def tummytime_duration(instances): + """ + Create a graph showing total duration of tummy time instances per day. + + :param instances: a QuerySet of TummyTime instances. + :returns: a tuple of the the graph's html and javascript. + """ + totals = instances.annotate(date=TruncDate('start')) \ + .values('date') \ + .annotate(count=Count('id')) \ + .annotate(sum=Sum('duration')) \ + .order_by('-date') + + sums = [] + for total in totals: + sums.append(total['sum']) + + trace_avg = go.Bar( + name=_('Total duration'), + x=list(totals.values_list('date', flat=True)), + y=[td.seconds/60 for td in sums], + hoverinfo='text', + text=[_duration_string_ms(td) for td in sums] + ) + trace_count = go.Scatter( + name=_('Number of sessions'), + mode='markers', + x=list(totals.values_list('date', flat=True)), + y=list(totals.values_list('count', flat=True)), + yaxis='y2', + hoverinfo='y' + ) + + layout_args = utils.default_graph_layout_options() + layout_args['title'] = _('Total Tummy Time Durations') + layout_args['xaxis']['title'] = _('Date') + layout_args['xaxis']['rangeselector'] = utils.rangeselector_date() + layout_args['yaxis']['title'] = _('Total duration (minutes)') + layout_args['yaxis2'] = dict(layout_args['yaxis']) + layout_args['yaxis2']['title'] = _('Number of sessions') + layout_args['yaxis2']['overlaying'] = 'y' + layout_args['yaxis2']['side'] = 'right' + + fig = go.Figure({ + 'data': [trace_avg, trace_count], + 'layout': go.Layout(**layout_args) + }) + output = plotly.plot(fig, output_type='div', include_plotlyjs=False) + return utils.split_graph_output(output) + + +def _duration_string_ms(duration): + """ + Format a "short" duration string with only minutes and seconds. This is + intended to fit better in smaller spaces on a graph. + :returns: a string of the form Xm. + """ + h, m, s = duration_parts(duration) + return '{}m{}s'.format(m, s) diff --git a/reports/templates/reports/tummytime_duration.html b/reports/templates/reports/tummytime_duration.html new file mode 100644 index 00000000..9e49c872 --- /dev/null +++ b/reports/templates/reports/tummytime_duration.html @@ -0,0 +1,9 @@ +{% extends 'reports/report_base.html' %} +{% load i18n %} + +{% block title %}{% trans "Total Tummy Time Durations" %} - {{ object }}{% endblock %} + +{% block breadcrumbs %} + {{ block.super }} + +{% endblock %} diff --git a/reports/urls.py b/reports/urls.py index 30cb6947..8b6cf5a2 100644 --- a/reports/urls.py +++ b/reports/urls.py @@ -41,6 +41,11 @@ urlpatterns = [ views.SleepTotalsChildReport.as_view(), name='report-sleep-totals-child' ), + path( + 'children//reports/tummytime/duration/', + views.TummyTimeDurationChildReport.as_view(), + name='report-tummytime-duration-child' + ), path( 'children//reports/weight/weight/', views.WeightWeightChildReport.as_view(), diff --git a/reports/views.py b/reports/views.py index 6db19797..85497061 100644 --- a/reports/views.py +++ b/reports/views.py @@ -109,6 +109,30 @@ class FeedingDurationChildReport(PermissionRequiredMixin, DetailView): return context +class TummyTimeDurationChildReport(PermissionRequiredMixin, DetailView): + """ + Graph of tummy time durations over time. + """ + model = models.Child + permission_required = ('core.view_child',) + template_name = 'reports/tummytime_duration.html' + + def __init__(self): + super(TummyTimeDurationChildReport, self).__init__() + self.html = '' + self.js = '' + + def get_context_data(self, **kwargs): + context = super(TummyTimeDurationChildReport, self).get_context_data( + **kwargs) + child = context['object'] + instances = models.TummyTime.objects.filter(child=child) + if instances: + context['html'], context['js'] = \ + graphs.tummytime_duration(instances) + return context + + class SleepPatternChildReport(PermissionRequiredMixin, DetailView): """ Graph of sleep pattern comparing sleep to wake times by day.