mirror of https://github.com/snachodog/mybuddy.git
Add "Averages" dashboard card.
This commit is contained in:
parent
22664ecc0f
commit
e90e31cfd3
|
@ -10,12 +10,12 @@ register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def duration_string(duration):
|
def duration_string(duration, precision='s'):
|
||||||
"""Format a duration (e.g. "2 hours, 3 minutes, 35 seconds")."""
|
"""Format a duration (e.g. "2 hours, 3 minutes, 35 seconds")."""
|
||||||
if not duration:
|
if not duration:
|
||||||
return ''
|
return ''
|
||||||
try:
|
try:
|
||||||
return d_string(duration)
|
return d_string(duration, precision)
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
|
|
@ -15,18 +15,20 @@ def filter_by_params(request, model, available_params):
|
||||||
return queryset
|
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
|
"""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)
|
h, m, s = duration_parts(duration)
|
||||||
|
|
||||||
duration = ''
|
duration = ''
|
||||||
if h > 0:
|
if h > 0:
|
||||||
duration = '{} hour{}'.format(h, 's' if h > 1 else '')
|
duration = '{} hour{}'.format(h, 's' if h > 1 else '')
|
||||||
if m > 0:
|
if m > 0 and precision != 'h':
|
||||||
duration += '{}{} minute{}'.format(
|
duration += '{}{} minute{}'.format(
|
||||||
'' if duration is '' else ', ', m, 's' if m > 1 else '')
|
'' 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(
|
duration += '{}{} second{}'.format(
|
||||||
'' if duration is '' else ', ', s, 's' if s > 1 else '')
|
'' if duration is '' else ', ', s, 's' if s > 1 else '')
|
||||||
|
|
||||||
|
@ -34,7 +36,8 @@ def duration_string(duration):
|
||||||
|
|
||||||
|
|
||||||
def duration_parts(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):
|
if not isinstance(duration, timezone.timedelta):
|
||||||
raise TypeError('Duration provided must be a timedetla')
|
raise TypeError('Duration provided must be a timedetla')
|
||||||
h, remainder = divmod(duration.seconds, 3600)
|
h, remainder = divmod(duration.seconds, 3600)
|
||||||
|
|
|
@ -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 {
|
.card-diaperchange {
|
||||||
@extend .border-danger;
|
@extend .border-danger;
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
{% load duration %}
|
||||||
|
|
||||||
|
<div class="card card-dashboard card-averages">
|
||||||
|
<div class="card-header">
|
||||||
|
<i class="icon icon-graph pull-left" aria-hidden="true"></i>
|
||||||
|
Averages
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row no-gutters text-center">
|
||||||
|
<div class="col-6 col-xl-3">
|
||||||
|
<div class="text-muted">Time asleep</div>
|
||||||
|
{% if sleep.average %}
|
||||||
|
{{ sleep.average|duration_string:'m' }}
|
||||||
|
{% else %}
|
||||||
|
<em>Not enough data</em>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="col-6 col-xl-3">
|
||||||
|
<div class="text-muted">Time awake</div>
|
||||||
|
{% if sleep.btwn_average %}
|
||||||
|
{{ sleep.btwn_average|duration_string:'m' }}
|
||||||
|
{% else %}
|
||||||
|
<em>Not enough data</em>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="col-6 col-xl-3">
|
||||||
|
<div class="text-muted">Change freq.</div>
|
||||||
|
{% if changes.btwn_average %}
|
||||||
|
{{ changes.btwn_average|duration_string:'m' }}
|
||||||
|
{% else %}
|
||||||
|
<em>Not enough data</em>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="col-6 col-xl-3">
|
||||||
|
<div class="text-muted">Feeding freq.</div>
|
||||||
|
{% if feedings.btwn_average %}
|
||||||
|
{{ feedings.btwn_average|duration_string:'m' }}
|
||||||
|
{% else %}
|
||||||
|
<em>Not enough data</em>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -12,6 +12,7 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div id="dashboard-child" class="row align-items-start">
|
<div id="dashboard-child" class="row align-items-start">
|
||||||
<div class="col-lg-4 col-md-6 col-sm-12">
|
<div class="col-lg-4 col-md-6 col-sm-12">
|
||||||
|
{% card_averages object %}
|
||||||
{% card_feeding_last object %}
|
{% card_feeding_last object %}
|
||||||
{% card_feeding_last_method object %}
|
{% card_feeding_last_method object %}
|
||||||
{% card_timer_list %}
|
{% card_timer_list %}
|
||||||
|
|
|
@ -11,6 +11,64 @@ from core.models import DiaperChange, Feeding, Sleep, Timer, TummyTime
|
||||||
register = template.Library()
|
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')
|
@register.inclusion_tag('cards/diaperchange_last.html')
|
||||||
def card_diaperchange_last(child):
|
def card_diaperchange_last(child):
|
||||||
"""Information about the most recent diaper change.
|
"""Information about the most recent diaper change.
|
||||||
|
|
Loading…
Reference in New Issue