Add a diaper change types over time report.

This commit is contained in:
Christopher Charbonneau Wells 2017-08-24 14:54:01 -04:00
parent a114ce7421
commit d11733442d
5 changed files with 96 additions and 30 deletions

View File

@ -29,8 +29,8 @@ class Child(models.Model):
def name(self, reverse=False): def name(self, reverse=False):
if reverse: if reverse:
return '{}, {}'.format(self.last_name, self.first_name) return '{}, {}'.format(self.last_name, self.first_name)
else:
return '{} {}'.format(self.first_name, self.last_name) return '{} {}'.format(self.first_name, self.last_name)
class DiaperChange(models.Model): class DiaperChange(models.Model):

View File

@ -3,11 +3,11 @@
{% block title %}Diaper Change Report - {{ object }}{% endblock %} {% block title %}Diaper Change Report - {{ object }}{% endblock %}
{% block content %} {% block content %}
<div> <div class="jumbotron jumbotron-fluid text-center">
Diaper change report for {{ object }}. <h1 class="display-3">Diaper Change Reports</h1>
<h2 class="text-muted">{{ object }}</h2>
</div> </div>
<div class="container-fluid">
<div style="min-height: 600px;">
{{ html|safe }} {{ html|safe }}
</div> </div>

View File

@ -6,8 +6,8 @@ from django.conf.urls import url
from . import views from . import views
urlpatterns = [ urlpatterns = [
url(r'^reports/(?P<slug>[^/.]+)/changes/$', url(r'^reports/changes/(?P<slug>[^/.]+)/$',
views.DiaperChangeReport.as_view(), name='report-diaperchange'), views.DiaperChangesChildReport.as_view(), name='report-diaperchange'),
url(r'^reports/(?P<slug>[^/.]+)/sleep/$', url(r'^reports/(?P<slug>[^/.]+)/sleep/$',
views.SleepReport.as_view(), name='report-sleep'), views.SleepReport.as_view(), name='report-sleep'),
] ]

37
reports/utils.py Normal file
View File

@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
def default_graph_layout_options():
"""Default layout options for all graphs.
"""
return {
'font': {
'color': 'rgba(0, 0, 0, 1)',
# Bootstrap 4 font family.
'family': '-apple-system, BlinkMacSystemFont, "Segoe UI", '
'Roboto, "Helvetica Neue", Arial, sans-serif, '
'"Apple Color Emoji", "Segoe UI Emoji", '
'"Segoe UI Symbol"',
'size': 14,
},
'margin': {'b': 40, 't': 40},
'xaxis': {
'titlefont': {
'color': 'rgba(0, 0, 0, 0.54)'
}
},
'yaxis': {
'titlefont': {
'color': 'rgba(0, 0, 0, 0.54)'
}
}
}
def split_graph_output(output):
"""Split out of a Plotly graph in to html and javascript.
"""
html, javascript = output.split('<script')
javascript = '<script' + javascript
return html, javascript

View File

@ -2,7 +2,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.auth.mixins import PermissionRequiredMixin
from django.db.models import Count from django.db.models import Count, Case, When
from django.db.models.functions import TruncDate from django.db.models.functions import TruncDate
from django.views.generic.detail import DetailView from django.views.generic.detail import DetailView
@ -11,40 +11,69 @@ import plotly.graph_objs as go
from core.models import Child, DiaperChange from core.models import Child, DiaperChange
from .utils import default_graph_layout_options, split_graph_output
class DiaperChangeReport(PermissionRequiredMixin, DetailView):
class DiaperChangesChildReport(PermissionRequiredMixin, DetailView):
"""All sleep data for a child.""" """All sleep data for a child."""
model = Child model = Child
permission_required = ('core.view_child',) permission_required = ('core.view_child',)
template_name = 'reports/diaperchange.html' template_name = 'reports/diaperchange.html'
def get_context_data(self, **kwargs): def __init__(self):
context = super(DiaperChangeReport, self).get_context_data(**kwargs) super(DiaperChangesChildReport, self).__init__()
child = context['object'] self.html = ''
self.javascript = ''
# TODO: Move this logic to the model? Where should data processing like def get_context_data(self, **kwargs):
# this happen? context = super(DiaperChangesChildReport, self).get_context_data(**kwargs)
changes = DiaperChange.objects.filter(child=child)\ child = context['object']
.annotate(date=TruncDate('time'))\ self._change_types_over_time(child)
.values('date')\ context['html'] = self.html
.annotate(count=Count('id'))\ context['javascript'] = self.javascript
return context
def _change_types_over_time(self, child):
changes = DiaperChange.objects.filter(child=child) \
.annotate(date=TruncDate('time')) \
.values('date') \
.annotate(wet_count=Count(Case(When(wet=True, then=1)))) \
.annotate(solid_count=Count(Case(When(solid=True, then=1)))) \
.annotate(total=Count('id')) \
.order_by('-date') .order_by('-date')
trace1 = go.Scatter( solid_trace = go.Scatter(
mode='markers',
name='Solid changes',
x=list(changes.values_list('date', flat=True)), x=list(changes.values_list('date', flat=True)),
y=list(changes.values_list('count', flat=True)) y=list(changes.values_list('solid_count', flat=True)),
) )
wet_trace = go.Scatter(
mode='markers',
name='Wet changes',
x=list(changes.values_list('date', flat=True)),
y=list(changes.values_list('wet_count', flat=True))
)
total_trace = go.Scatter(
name='Total changes',
x=list(changes.values_list('date', flat=True)),
y=list(changes.values_list('total', flat=True))
)
layout_args = default_graph_layout_options()
layout_args['barmode'] = 'stack'
layout_args['title'] = 'Diaper change types over time'
layout_args['xaxis']['title'] = 'Date'
layout_args['yaxis']['title'] = 'Number of changes'
fig = go.Figure({ fig = go.Figure({
"data": [trace1], 'data': [solid_trace, wet_trace, total_trace],
"layout": go.Layout(title='Diaper Changes') 'layout': go.Layout(**layout_args)
}) })
div = plotly.plot(fig, output_type='div', include_plotlyjs=False) output = plotly.plot(fig, output_type='div', include_plotlyjs=False)
html, javascript = div.split('<script') html, javascript = split_graph_output(output)
self.html += '<a name="change_types_over_time">' + html
context['html'] = html self.javascript += javascript
context['javascript'] = '<script' + javascript
return context
class SleepReport(PermissionRequiredMixin, DetailView): class SleepReport(PermissionRequiredMixin, DetailView):