From e90e31cfd32072c0a40f7c84d26f869ef0056c39 Mon Sep 17 00:00:00 2001 From: Christopher Charbonneau Wells Date: Mon, 30 Oct 2017 14:26:49 -0400 Subject: [PATCH] Add "Averages" dashboard card. --- core/templatetags/duration.py | 4 +- core/utils.py | 13 ++++-- dashboard/static_src/scss/cards.scss | 15 ++++++ dashboard/templates/cards/averages.html | 46 +++++++++++++++++++ dashboard/templates/dashboard/child.html | 1 + dashboard/templatetags/cards.py | 58 ++++++++++++++++++++++++ 6 files changed, 130 insertions(+), 7 deletions(-) create mode 100644 dashboard/templates/cards/averages.html diff --git a/core/templatetags/duration.py b/core/templatetags/duration.py index 02a17748..0571b4ae 100644 --- a/core/templatetags/duration.py +++ b/core/templatetags/duration.py @@ -10,12 +10,12 @@ register = template.Library() @register.filter -def duration_string(duration): +def duration_string(duration, precision='s'): """Format a duration (e.g. "2 hours, 3 minutes, 35 seconds").""" if not duration: return '' try: - return d_string(duration) + return d_string(duration, precision) except (ValueError, TypeError): return '' diff --git a/core/utils.py b/core/utils.py index 8a55a8a4..2efd7abf 100644 --- a/core/utils.py +++ b/core/utils.py @@ -15,18 +15,20 @@ def filter_by_params(request, model, available_params): return queryset -def duration_string(duration): +def duration_string(duration, precision='s'): """Format hours, minutes and seconds as a human-friendly string (e.g. "2 - hours, 25 minutes, 31 seconds")""" + hours, 25 minutes, 31 seconds") with precision to h = hours, m = minutes or + s = seconds. + """ h, m, s = duration_parts(duration) duration = '' if h > 0: duration = '{} hour{}'.format(h, 's' if h > 1 else '') - if m > 0: + if m > 0 and precision != 'h': duration += '{}{} minute{}'.format( '' if duration is '' else ', ', m, 's' if m > 1 else '') - if s > 0: + if s > 0 and precision != 'h' and precision != 'm': duration += '{}{} second{}'.format( '' if duration is '' else ', ', s, 's' if s > 1 else '') @@ -34,7 +36,8 @@ def duration_string(duration): def duration_parts(duration): - """Get hours, minutes and seconds from a timedelta.""" + """Get hours, minutes and seconds from a timedelta. + """ if not isinstance(duration, timezone.timedelta): raise TypeError('Duration provided must be a timedetla') h, remainder = divmod(duration.seconds, 3600) diff --git a/dashboard/static_src/scss/cards.scss b/dashboard/static_src/scss/cards.scss index 91271d06..0d203434 100644 --- a/dashboard/static_src/scss/cards.scss +++ b/dashboard/static_src/scss/cards.scss @@ -17,6 +17,21 @@ } } +.card-averages { + @extend .border-light; + + .card-header { + @extend .text-dark, .bg-light; + } + + .card-body { + @extend .text-light; + + .container { + padding: 0; + } + } +} .card-diaperchange { @extend .border-danger; diff --git a/dashboard/templates/cards/averages.html b/dashboard/templates/cards/averages.html new file mode 100644 index 00000000..8322ceb1 --- /dev/null +++ b/dashboard/templates/cards/averages.html @@ -0,0 +1,46 @@ +{% load duration %} + +
+
+ + Averages +
+
+
+
+
+
Time asleep
+ {% if sleep.average %} + {{ sleep.average|duration_string:'m' }} + {% else %} + Not enough data + {% endif %} +
+
+
Time awake
+ {% if sleep.btwn_average %} + {{ sleep.btwn_average|duration_string:'m' }} + {% else %} + Not enough data + {% endif %} +
+
+
Change freq.
+ {% if changes.btwn_average %} + {{ changes.btwn_average|duration_string:'m' }} + {% else %} + Not enough data + {% endif %} +
+
+
Feeding freq.
+ {% if feedings.btwn_average %} + {{ feedings.btwn_average|duration_string:'m' }} + {% else %} + Not enough data + {% endif %} +
+
+
+
+
\ No newline at end of file diff --git a/dashboard/templates/dashboard/child.html b/dashboard/templates/dashboard/child.html index 3ea854c3..14efa016 100644 --- a/dashboard/templates/dashboard/child.html +++ b/dashboard/templates/dashboard/child.html @@ -12,6 +12,7 @@ {% block content %}
+ {% card_averages object %} {% card_feeding_last object %} {% card_feeding_last_method object %} {% card_timer_list %} diff --git a/dashboard/templatetags/cards.py b/dashboard/templatetags/cards.py index 5e57677e..4a21d4aa 100644 --- a/dashboard/templatetags/cards.py +++ b/dashboard/templatetags/cards.py @@ -11,6 +11,64 @@ from core.models import DiaperChange, Feeding, Sleep, Timer, TummyTime register = template.Library() +@register.inclusion_tag('cards/averages.html') +def card_averages(child): + """Averages data for all models. + """ + instances = Sleep.objects.filter(child=child).order_by('start') + sleep = { + 'total': instances.aggregate(Sum('duration'))['duration__sum'], + 'count': instances.count(), + 'average': 0, + 'btwn_total': timezone.timedelta(0), + 'btwn_count': instances.count() - 1, + 'btwn_average': 0} + + last_instance = None + for instance in instances: + if last_instance: + sleep['btwn_total'] += instance.start - last_instance.end + last_instance = instance + + if sleep['count'] > 0: + sleep['average'] = sleep['total']/sleep['count'] + if sleep['btwn_count'] > 0: + sleep['btwn_average'] = sleep['btwn_total']/sleep['btwn_count'] + + instances = DiaperChange.objects.filter(child=child).order_by('time') + changes = { + 'btwn_total': timezone.timedelta(0), + 'btwn_count': instances.count() - 1, + 'btwn_average': 0} + last_instance = None + + for instance in instances: + if last_instance: + changes['btwn_total'] += instance.time - last_instance.time + last_instance = instance + + if changes['btwn_count'] > 0: + changes['btwn_average'] = changes['btwn_total']/changes['btwn_count'] + + instances = Feeding.objects.filter(child=child).order_by('start') + feedings = { + 'btwn_total': timezone.timedelta(0), + 'btwn_count': instances.count() - 1, + 'btwn_average': 0} + last_instance = None + + for instance in instances: + if last_instance: + feedings['btwn_total'] += instance.start - last_instance.end + last_instance = instance + + if feedings['btwn_count'] > 0: + feedings['btwn_average'] = \ + feedings['btwn_total']/feedings['btwn_count'] + + return {'changes': changes, 'feedings': feedings, 'sleep': sleep} + + @register.inclusion_tag('cards/diaperchange_last.html') def card_diaperchange_last(child): """Information about the most recent diaper change.