mirror of https://github.com/snachodog/mybuddy.git
Add a diaper change types over time report.
This commit is contained in:
parent
a114ce7421
commit
d11733442d
|
@ -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):
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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
|
|
@ -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):
|
||||||
|
|
Loading…
Reference in New Issue