Add Height, Head Circumference, and BMI, corresponding reports, and move to separate menu with Weight and Temperature.

This commit is contained in:
Yulian Kuncheff 2021-12-28 23:53:36 -08:00 committed by Christopher Charbonneau Wells
parent 89ed408acc
commit 9b604627a1
48 changed files with 2897 additions and 69 deletions

View File

@ -173,3 +173,18 @@ class WeightSerializer(CoreModelSerializer):
class Meta:
model = models.Weight
fields = ('id', 'child', 'weight', 'date', 'notes')
class HeightSerializer(CoreModelSerializer):
class Meta:
model = models.Height
fields = ('id', 'child', 'height', 'date', 'notes')
class HeadCircumferenceSerializer(CoreModelSerializer):
class Meta:
model = models.HeadCircumference
fields = ('id', 'child', 'head_circumference', 'date', 'notes')
class BMISerializer(CoreModelSerializer):
class Meta:
model = models.BMI
fields = ('id', 'child', 'bmi', 'date', 'notes')

View File

@ -15,6 +15,9 @@ router.register(r'temperature', views.TemperatureViewSet)
router.register(r'timers', views.TimerViewSet)
router.register(r'tummy-times', views.TummyTimeViewSet)
router.register(r'weight', views.WeightViewSet)
router.register(r'height', views.HeightViewSet)
router.register(r'head-circumference', views.HeadCircumferenceViewSet)
router.register(r'bmi', views.BMIViewSet)
app_name = 'api'

View File

@ -60,3 +60,18 @@ class WeightViewSet(viewsets.ModelViewSet):
queryset = models.Weight.objects.all()
serializer_class = serializers.WeightSerializer
filterset_fields = ('child', 'date')
class HeightViewSet(viewsets.ModelViewSet):
queryset = models.Height.objects.all()
serializer_class = serializers.HeightSerializer
filterset_fields = ('child', 'date')
class HeadCircumferenceViewSet(viewsets.ModelViewSet):
queryset = models.HeadCircumference.objects.all()
serializer_class = serializers.HeadCircumferenceSerializer
filterset_fields = ('child', 'date')
class BMIViewSet(viewsets.ModelViewSet):
queryset = models.BMI.objects.all()
serializer_class = serializers.BMISerializer
filterset_fields = ('child', 'date')

View File

@ -399,5 +399,68 @@
"date": "2017-11-18",
"notes": "before feed"
}
},
{
"model": "core.height",
"pk": 1,
"fields":
{
"child": 1,
"height": 9.5,
"date": "2017-11-11"
}
},
{
"model": "core.height",
"pk": 2,
"fields":
{
"child": 1,
"height": 10.5,
"date": "2017-11-18",
"notes": "before feed"
}
},
{
"model": "core.headcircumference",
"pk": 1,
"fields":
{
"child": 1,
"head_circumference": 5.5,
"date": "2017-11-11"
}
},
{
"model": "core.headcircumference",
"pk": 2,
"fields":
{
"child": 1,
"head_circumference": 6.5,
"date": "2017-11-18",
"notes": "before feed"
}
},
{
"model": "core.bmi",
"pk": 1,
"fields":
{
"child": 1,
"bmi": 25.5,
"date": "2017-11-11"
}
},
{
"model": "core.bmi",
"pk": 2,
"fields":
{
"child": 1,
"bmi": 26.5,
"date": "2017-11-18",
"notes": "before feed"
}
}
]

View File

@ -249,3 +249,60 @@ class Command(BaseCommand):
date=self.time.date(),
notes=notes
).save()
@transaction.atomic
def _add_height_entry(self):
"""
Add a height entry. This assumes a weekly interval.
:returns:
"""
self.height += uniform(0.1, 0.3)
notes = ''
if choice([True, False, False, False]):
notes = ' '.join(self.faker.sentences(randint(1, 5)))
models.Height.objects.create(
child=self.child,
height=round(self.height, 2),
date=self.time.date(),
notes=notes
).save()
@transaction.atomic
def _add_head_circumference_entry(self):
"""
Add a head circumference entry. This assumes a weekly interval.
:returns:
"""
self.head_circumference += uniform(0.1, 0.3)
notes = ''
if choice([True, False, False, False]):
notes = ' '.join(self.faker.sentences(randint(1, 5)))
models.HeadCircumference.objects.create(
child=self.child,
head_circumference=round(self.head_circumference, 2),
date=self.time.date(),
notes=notes
).save()
@transaction.atomic
def _add_bmi_entry(self):
"""
Add a BMI entry. This assumes a weekly interval.
:returns:
"""
self.bmi += uniform(0.1, 0.3)
notes = ''
if choice([True, False, False, False]):
notes = ' '.join(self.faker.sentences(randint(1, 5)))
models.bmi.objects.create(
child=self.child,
bmi=round(self.bmi, 2),
date=self.time.date(),
notes=notes
).save()

View File

@ -221,6 +221,30 @@
"css": "cancel",
"code": 59411,
"src": "fontawesome"
},
{
"uid": "6a96c51e961a2e48dfcbe2c47fd1204e",
"css": "height",
"code": 61508,
"src": "mfglabs"
},
{
"uid": "bb46b15cb78cc4cc05d3d715d522ac4d",
"css": "head-circumference",
"code": 59412,
"src": "entypo"
},
{
"uid": "2c452255d4fed51ef0a6ef86436a7d08",
"css": "bmi",
"code": 62101,
"src": "fontawesome"
},
{
"uid": "b95cfc96d48a72dd665e0ab109880b5e",
"css": "measurements",
"code": 59413,
"src": "entypo"
}
]
}

View File

@ -19,6 +19,9 @@
.icon-arrow-up:before { content: '\e811'; } /* '' */
.icon-arrow-down:before { content: '\e812'; } /* '' */
.icon-cancel:before { content: '\e813'; } /* '' */
.icon-head-circumference:before { content: '\e814'; } /* '' */
.icon-measurements:before { content: '\e815'; } /* '' */
.icon-height:before { content: '\f044'; } /* '' */
.icon-dashboard:before { content: '\f0e4'; } /* '' */
.icon-tummytime:before { content: '\f118'; } /* '' */
.icon-sad:before { content: '\f119'; } /* '' */
@ -34,4 +37,5 @@
.icon-note:before { content: '\f249'; } /* '' */
.icon-weight:before { content: '\f24e'; } /* '' */
.icon-today:before { content: '\f274'; } /* '' */
.icon-bmi:before { content: '\f295'; } /* '' */
.icon-temperature:before { content: '\f2c8'; } /* '' */

File diff suppressed because one or more lines are too long

View File

@ -19,6 +19,9 @@
.icon-arrow-up { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
.icon-arrow-down { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
.icon-cancel { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
.icon-head-circumference { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
.icon-measurements { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
.icon-height { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
.icon-dashboard { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
.icon-tummytime { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
.icon-sad { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
@ -34,4 +37,5 @@
.icon-note { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
.icon-weight { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
.icon-today { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
.icon-bmi { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
.icon-temperature { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }

View File

@ -30,6 +30,9 @@
.icon-arrow-up { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
.icon-arrow-down { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
.icon-cancel { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
.icon-head-circumference { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
.icon-measurements { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
.icon-height { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
.icon-dashboard { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
.icon-tummytime { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
.icon-sad { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
@ -45,4 +48,5 @@
.icon-note { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
.icon-weight { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
.icon-today { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
.icon-bmi { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
.icon-temperature { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }

View File

@ -1,11 +1,11 @@
@font-face {
font-family: 'babybuddy';
src: url('../font/babybuddy.eot?72731680');
src: url('../font/babybuddy.eot?72731680#iefix') format('embedded-opentype'),
url('../font/babybuddy.woff2?72731680') format('woff2'),
url('../font/babybuddy.woff?72731680') format('woff'),
url('../font/babybuddy.ttf?72731680') format('truetype'),
url('../font/babybuddy.svg?72731680#babybuddy') format('svg');
src: url('../font/babybuddy.eot?3615871');
src: url('../font/babybuddy.eot?3615871#iefix') format('embedded-opentype'),
url('../font/babybuddy.woff2?3615871') format('woff2'),
url('../font/babybuddy.woff?3615871') format('woff'),
url('../font/babybuddy.ttf?3615871') format('truetype'),
url('../font/babybuddy.svg?3615871#babybuddy') format('svg');
font-weight: normal;
font-style: normal;
}
@ -15,7 +15,7 @@
@media screen and (-webkit-min-device-pixel-ratio:0) {
@font-face {
font-family: 'babybuddy';
src: url('../font/babybuddy.svg?72731680#babybuddy') format('svg');
src: url('../font/babybuddy.svg?3615871#babybuddy') format('svg');
}
}
*/
@ -74,6 +74,9 @@
.icon-arrow-up:before { content: '\e811'; } /* '' */
.icon-arrow-down:before { content: '\e812'; } /* '' */
.icon-cancel:before { content: '\e813'; } /* '' */
.icon-head-circumference:before { content: '\e814'; } /* '' */
.icon-measurements:before { content: '\e815'; } /* '' */
.icon-height:before { content: '\f044'; } /* '' */
.icon-dashboard:before { content: '\f0e4'; } /* '' */
.icon-tummytime:before { content: '\f118'; } /* '' */
.icon-sad:before { content: '\f119'; } /* '' */
@ -89,4 +92,5 @@
.icon-note:before { content: '\f249'; } /* '' */
.icon-weight:before { content: '\f24e'; } /* '' */
.icon-today:before { content: '\f274'; } /* '' */
.icon-bmi:before { content: '\f295'; } /* '' */
.icon-temperature:before { content: '\f2c8'; } /* '' */

View File

@ -146,11 +146,11 @@
}
@font-face {
font-family: 'babybuddy';
src: url('./font/babybuddy.eot?3175326');
src: url('./font/babybuddy.eot?3175326#iefix') format('embedded-opentype'),
url('./font/babybuddy.woff?3175326') format('woff'),
url('./font/babybuddy.ttf?3175326') format('truetype'),
url('./font/babybuddy.svg?3175326#babybuddy') format('svg');
src: url('./font/babybuddy.eot?20083249');
src: url('./font/babybuddy.eot?20083249#iefix') format('embedded-opentype'),
url('./font/babybuddy.woff?20083249') format('woff'),
url('./font/babybuddy.ttf?20083249') format('truetype'),
url('./font/babybuddy.svg?20083249#babybuddy') format('svg');
font-weight: normal;
font-style: normal;
}
@ -281,9 +281,20 @@
</div>
</div>
<div class="row">
<div class="span3" title="Code: 0xe814">
<i class="demo-icon icon-head-circumference">&#xe814;</i> <span class="i-name">icon-head-circumference</span><span class="i-code">0xe814</span>
</div>
<div class="span3" title="Code: 0xe815">
<i class="demo-icon icon-measurements">&#xe815;</i> <span class="i-name">icon-measurements</span><span class="i-code">0xe815</span>
</div>
<div class="span3" title="Code: 0xf044">
<i class="demo-icon icon-height">&#xf044;</i> <span class="i-name">icon-height</span><span class="i-code">0xf044</span>
</div>
<div class="span3" title="Code: 0xf0e4">
<i class="demo-icon icon-dashboard">&#xf0e4;</i> <span class="i-name">icon-dashboard</span><span class="i-code">0xf0e4</span>
</div>
</div>
<div class="row">
<div class="span3" title="Code: 0xf118">
<i class="demo-icon icon-tummytime">&#xf118;</i> <span class="i-name">icon-tummytime</span><span class="i-code">0xf118</span>
</div>
@ -293,11 +304,11 @@
<div class="span3" title="Code: 0xf126">
<i class="demo-icon icon-source">&#xf126;</i> <span class="i-name">icon-source</span><span class="i-code">0xf126</span>
</div>
</div>
<div class="row">
<div class="span3" title="Code: 0xf137">
<i class="demo-icon icon-angle-circled-left">&#xf137;</i> <span class="i-name">icon-angle-circled-left</span><span class="i-code">0xf137</span>
</div>
</div>
<div class="row">
<div class="span3" title="Code: 0xf138">
<i class="demo-icon icon-angle-circled-right">&#xf138;</i> <span class="i-name">icon-angle-circled-right</span><span class="i-code">0xf138</span>
</div>
@ -307,11 +318,11 @@
<div class="span3" title="Code: 0xf1b1">
<i class="demo-icon icon-feeding">&#xf1b1;</i> <span class="i-name">icon-feeding</span><span class="i-code">0xf1b1</span>
</div>
</div>
<div class="row">
<div class="span3" title="Code: 0xf1b3">
<i class="demo-icon icon-activities">&#xf1b3;</i> <span class="i-name">icon-activities</span><span class="i-code">0xf1b3</span>
</div>
</div>
<div class="row">
<div class="span3" title="Code: 0xf1da">
<i class="demo-icon icon-timeline">&#xf1da;</i> <span class="i-name">icon-timeline</span><span class="i-code">0xf1da</span>
</div>
@ -321,17 +332,20 @@
<div class="span3" title="Code: 0xf236">
<i class="demo-icon icon-sleep">&#xf236;</i> <span class="i-name">icon-sleep</span><span class="i-code">0xf236</span>
</div>
</div>
<div class="row">
<div class="span3" title="Code: 0xf249">
<i class="demo-icon icon-note">&#xf249;</i> <span class="i-name">icon-note</span><span class="i-code">0xf249</span>
</div>
</div>
<div class="row">
<div class="span3" title="Code: 0xf24e">
<i class="demo-icon icon-weight">&#xf24e;</i> <span class="i-name">icon-weight</span><span class="i-code">0xf24e</span>
</div>
<div class="span3" title="Code: 0xf274">
<i class="demo-icon icon-today">&#xf274;</i> <span class="i-name">icon-today</span><span class="i-code">0xf274</span>
</div>
<div class="span3" title="Code: 0xf295">
<i class="demo-icon icon-bmi">&#xf295;</i> <span class="i-name">icon-bmi</span><span class="i-code">0xf295</span>
</div>
<div class="span3" title="Code: 0xf2c8">
<i class="demo-icon icon-temperature">&#xf2c8;</i> <span class="i-name">icon-temperature</span><span class="i-code">0xf2c8</span>
</div>

View File

@ -46,6 +46,12 @@
<glyph glyph-name="cancel" unicode="&#xe813;" d="M724 112q0-22-15-38l-76-76q-16-15-38-15t-38 15l-164 165-164-165q-16-15-38-15t-38 15l-76 76q-16 16-16 38t16 38l164 164-164 164q-16 16-16 38t16 38l76 76q16 16 38 16t38-16l164-164 164 164q16 16 38 16t38-16l76-76q15-15 15-38t-15-38l-164-164 164-164q15-15 15-38z" horiz-adv-x="785.7" />
<glyph glyph-name="head-circumference" unicode="&#xe814;" d="M408 760q168 0 287-116t123-282l122 0-184-206-184 206 144 0q-4 124-94 210t-214 86q-126 0-216-90t-90-218q0-126 90-216t216-90q104 0 182 60l70-76q-110-88-252-88-168 0-288 120t-120 290 120 290 288 120z" horiz-adv-x="940" />
<glyph glyph-name="measurements" unicode="&#xe815;" d="M630 750q28 0 49-21t21-49l0-760q0-30-21-50t-49-20l-560 0q-28 0-49 20t-21 50l0 760q0 28 21 49t49 21l60-150 440 0z m-100-100l-360 0-44 100 108 0 36 100 160 0 36-100 110 0z" horiz-adv-x="700" />
<glyph glyph-name="height" unicode="&#xf044;" d="M0 144q0-31 22-54l180-180q20-20 46-22t50 12l-96 96 41 41 97-97 42 41-98 97 41 41 97-96 42 40-97 98 40 40 98-96 39 39-98 96 42 42 97-97 41 41-97 97 41 41 97-97 41 41-97 97 41 41 97-97 41 41-97 98 41 40 98-97 41 42-98 97 41 41 96-95q13 22 10 48t-21 45l-180 180q-23 22-54 22t-55-23l-589-590q-23-22-23-54z" horiz-adv-x="922.9" />
<glyph glyph-name="dashboard" unicode="&#xf0e4;" d="M214 207q0 30-21 51t-50 21-51-21-21-51 21-50 51-21 50 21 21 50z m107 250q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m239-268l57 213q3 14-5 27t-21 16-27-3-17-22l-56-213q-33-3-60-25t-35-55q-11-43 11-81t66-50 81 11 50 66q9 33-4 65t-40 51z m369 18q0 30-21 51t-51 21-50-21-21-51 21-50 50-21 51 21 21 50z m-358 357q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m250-107q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m179-250q0-145-79-269-10-17-30-17h-782q-20 0-30 17-79 123-79 269 0 102 40 194t106 160 160 107 194 39 194-39 160-107 106-160 40-194z" horiz-adv-x="1000" />
<glyph glyph-name="tummytime" unicode="&#xf118;" d="M633 250q-21-67-77-109t-127-41-128 41-77 109q-4 14 3 27t21 18q14 4 27-2t17-22q14-44 52-72t85-28 84 28 52 72q4 15 18 22t27 2 21-18 2-27z m-276 243q0-30-21-51t-50-21-51 21-21 51 21 50 51 21 50-21 21-50z m286 0q0-30-21-51t-51-21-50 21-21 51 21 50 50 21 51-21 21-50z m143-143q0 73-29 139t-76 114-114 76-138 28-139-28-114-76-76-114-29-139 29-139 76-113 114-77 139-28 138 28 114 77 76 113 29 139z m71 0q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
@ -76,6 +82,8 @@
<glyph glyph-name="today" unicode="&#xf274;" d="M727 312l-286-286q-5-5-12-5t-13 5l-161 161q-5 6-5 13t5 12l26 26q5 5 12 5t13-5l123-123 247 248q6 5 13 5t13-5l25-26q5-5 5-12t-5-13z m-656-391h786v572h-786v-572z m215 679v161q0 8-5 13t-13 5h-36q-8 0-13-5t-5-13v-161q0-8 5-13t13-5h36q8 0 13 5t5 13z m428 0v161q0 8-5 13t-13 5h-35q-8 0-13-5t-5-13v-161q0-8 5-13t13-5h35q8 0 13 5t5 13z m215 36v-715q0-29-22-50t-50-21h-786q-29 0-50 21t-21 50v715q0 29 21 50t50 21h72v54q0 37 26 63t63 26h36q37 0 63-26t26-63v-54h214v54q0 37 27 63t63 26h35q37 0 64-26t26-63v-54h71q29 0 50-21t22-50z" horiz-adv-x="1000" />
<glyph glyph-name="bmi" unicode="&#xf295;" d="M714 136q0 29-21 50t-50 21-50-21-22-50 22-50 50-22 50 22 21 50z m-428 428q0 29-21 51t-51 21-50-21-21-51 21-50 50-21 51 21 21 50z m571-428q0-89-63-152t-151-63-152 63-62 152 62 151 152 63 151-63 63-151z m-53 607q0-11-8-21l-589-786q-11-15-28-15h-90q-14 0-25 11t-10 25q0 11 7 21l589 786q11 15 29 15h89q14 0 25-11t11-25z m-375-179q0-88-63-151t-152-63-151 63-63 151 63 152 151 63 152-63 63-152z" horiz-adv-x="857.1" />
<glyph glyph-name="temperature" unicode="&#xf2c8;" d="M357 100q0-45-31-76t-76-31-76 31-31 76q0 34 19 61t52 40v363h72v-363q32-12 52-40t19-61z m72 0q0 43-19 80t-53 63v428q0 45-31 76t-76 32-76-32-31-76v-428q-34-25-53-63t-19-80q0-74 53-126t126-53 126 53 53 126z m71 0q0-103-73-177t-177-73-177 73-73 177q0 102 71 175v396q0 75 53 127t126 52 126-52 53-127v-396q71-73 71-175z m71 321v-71h-107v71h107z m0 143v-71h-107v71h107z m0 143v-71h-107v71h107z" horiz-adv-x="571.4" />
</font>
</defs>

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -150,6 +150,20 @@
{% trans "Note" %}
</a>
{% endif %}
</div>
</li>
<li class="nav-item dropdown">
<a id="nav-measurements-menu-link"
class="nav-link dropdown-toggle"
href="#"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"><i class="icon-measurements" aria-hidden="true"></i>
{% trans "Measurements" %}
</a>
<div class="dropdown-menu" aria-labelledby="nav-measurements-menu-link">
{% if perms.core.view_temperature %}
<a class="dropdown-item{% if request.path == '/temperature/' %} active{% endif %}"
@ -179,6 +193,48 @@
</a>
{% endif %}
{% if perms.core.view_height %}
<a class="dropdown-item{% if request.path == '/height/' %} active{% endif %}"
href="{% url 'core:height-list' %}">
<i class="icon-height" aria-hidden="true"></i>
{% trans "Height" %}
</a>
{% endif %}
{% if perms.core.add_height %}
<a class="dropdown-item pl-5{% if request.path == '/height/add/' %} active{% endif %}"
href="{% url 'core:height-add' %}"><i class="icon-add" aria-hidden="true"></i>
{% trans "Height entry" %}
</a>
{% endif %}
{% if perms.core.view_head_circumference %}
<a class="dropdown-item{% if request.path == '/head-circumference/' %} active{% endif %}"
href="{% url 'core:head-circumference-list' %}">
<i class="icon-head-circumference aria-hidden="true"></i>
{% trans "Head Circumference" %}
</a>
{% endif %}
{% if perms.core.add_head_circumference %}
<a class="dropdown-item pl-5{% if request.path == '/head-circumference/add/' %} active{% endif %}"
href="{% url 'core:head-circumference-add' %}"><i class="icon-add" aria-hidden="true"></i>
{% trans "Head Circumference entry" %}
</a>
{% endif %}
{% if perms.core.view_bmi %}
<a class="dropdown-item{% if request.path == '/bmi/' %} active{% endif %}"
href="{% url 'core:bmi-list' %}">
<i class="icon-bmi" aria-hidden="true"></i>
{% trans "BMI" %}
</a>
{% endif %}
{% if perms.core.add_bmi %}
<a class="dropdown-item pl-5{% if request.path == '/bmi/add/' %} active{% endif %}"
href="{% url 'core:bmi-add' %}"><i class="icon-add" aria-hidden="true"></i>
{% trans "BMI entry" %}
</a>
{% endif %}
</div>
</li>

View File

@ -241,3 +241,39 @@ class WeightForm(CoreModelForm):
}),
'notes': forms.Textarea(attrs={'rows': 5}),
}
class HeightForm(CoreModelForm):
class Meta:
model = models.Height
fields = ['child', 'height', 'date', 'notes']
widgets = {
'date': forms.DateInput(attrs={
'autocomplete': 'off',
'data-target': '#datetimepicker_date',
}),
'notes': forms.Textarea(attrs={'rows': 5}),
}
class HeadCircumferenceForm(CoreModelForm):
class Meta:
model = models.HeadCircumference
fields = ['child', 'head_circumference', 'date', 'notes']
widgets = {
'date': forms.DateInput(attrs={
'autocomplete': 'off',
'data-target': '#datetimepicker_date',
}),
'notes': forms.Textarea(attrs={'rows': 5}),
}
class BMIForm(CoreModelForm):
class Meta:
model = models.BMI
fields = ['child', 'bmi', 'date', 'notes']
widgets = {
'date': forms.DateInput(attrs={
'autocomplete': 'off',
'data-target': '#datetimepicker_date',
}),
'notes': forms.Textarea(attrs={'rows': 5}),
}

View File

@ -0,0 +1,62 @@
# Generated by Django 3.2.10 on 2021-12-29 05:23
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('core', '0016_alter_sleep_napping'),
]
operations = [
migrations.CreateModel(
name='Height',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('height', models.FloatField(verbose_name='Height')),
('date', models.DateField(verbose_name='Date')),
('notes', models.TextField(blank=True, null=True, verbose_name='Notes')),
('child', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='height', to='core.child', verbose_name='Child')),
],
options={
'verbose_name': 'Height',
'verbose_name_plural': 'Height',
'ordering': ['-date'],
'default_permissions': ('view', 'add', 'change', 'delete'),
},
),
migrations.CreateModel(
name='HeadCircumference',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('head_circumference', models.FloatField(verbose_name='Head Circumference')),
('date', models.DateField(verbose_name='Date')),
('notes', models.TextField(blank=True, null=True, verbose_name='Notes')),
('child', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='head_circumference', to='core.child', verbose_name='Child')),
],
options={
'verbose_name': 'Head Circumference',
'verbose_name_plural': 'Head Circumference',
'ordering': ['-date'],
'default_permissions': ('view', 'add', 'change', 'delete'),
},
),
migrations.CreateModel(
name='BMI',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('bmi', models.FloatField(verbose_name='BMI')),
('date', models.DateField(verbose_name='Date')),
('notes', models.TextField(blank=True, null=True, verbose_name='Notes')),
('child', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='bmi', to='core.child', verbose_name='Child')),
],
options={
'verbose_name': 'BMI',
'verbose_name_plural': 'BMI',
'ordering': ['-date'],
'default_permissions': ('view', 'add', 'change', 'delete'),
},
),
]

View File

@ -591,3 +591,105 @@ class Weight(models.Model):
def clean(self):
validate_date(self.date, 'date')
class Height(models.Model):
model_name = 'height'
child = models.ForeignKey(
'Child',
on_delete=models.CASCADE,
related_name='height',
verbose_name=_('Child')
)
height = models.FloatField(
blank=False,
null=False,
verbose_name=_('Height')
)
date = models.DateField(
blank=False,
null=False,
verbose_name=_('Date')
)
notes = models.TextField(blank=True, null=True, verbose_name=_('Notes'))
objects = models.Manager()
class Meta:
default_permissions = ('view', 'add', 'change', 'delete')
ordering = ['-date']
verbose_name = _('Height')
verbose_name_plural = _('Height')
def __str__(self):
return str(_('Height'))
def clean(self):
validate_date(self.date, 'date')
class HeadCircumference(models.Model):
model_name = 'head_circumference'
child = models.ForeignKey(
'Child',
on_delete=models.CASCADE,
related_name='head_circumference',
verbose_name=_('Child')
)
head_circumference = models.FloatField(
blank=False,
null=False,
verbose_name=_('Head Circumference')
)
date = models.DateField(
blank=False,
null=False,
verbose_name=_('Date')
)
notes = models.TextField(blank=True, null=True, verbose_name=_('Notes'))
objects = models.Manager()
class Meta:
default_permissions = ('view', 'add', 'change', 'delete')
ordering = ['-date']
verbose_name = _('Head Circumference')
verbose_name_plural = _('Head Circumference')
def __str__(self):
return str(_('Head Circumference'))
def clean(self):
validate_date(self.date, 'date')
class BMI(models.Model):
model_name = 'bmi'
child = models.ForeignKey(
'Child',
on_delete=models.CASCADE,
related_name='bmi',
verbose_name=_('Child')
)
bmi = models.FloatField(
blank=False,
null=False,
verbose_name=_('BMI')
)
date = models.DateField(
blank=False,
null=False,
verbose_name=_('Date')
)
notes = models.TextField(blank=True, null=True, verbose_name=_('Notes'))
objects = models.Manager()
class Meta:
default_permissions = ('view', 'add', 'change', 'delete')
ordering = ['-date']
verbose_name = _('BMI')
verbose_name_plural = _('BMI')
def __str__(self):
return str(_('BMI'))
def clean(self):
validate_date(self.date, 'date')

View File

@ -0,0 +1,20 @@
{% extends 'babybuddy/page.html' %}
{% load i18n widget_tweaks %}
{% block title %}{% trans "Delete a BMI Entry" %}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'core:bmi-list' %}">{% trans "BMI" %}</a></li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Delete" %}</li>
{% endblock %}
{% block content %}
<form role="form" method="post">
{% csrf_token %}
{% blocktrans trimmed %}
<h1>Are you sure you want to delete <span class="text-info">{{ object }}</span>?</h1>
{% endblocktrans %}
<input type="submit" value="{% trans "Delete" %}" class="btn btn-danger" />
<a href="{% url 'core:bmi-list' %}" class="btn btn-default">{% trans "Cancel" %}</a>
</form>
{% endblock %}

View File

@ -0,0 +1,39 @@
{% extends 'babybuddy/page.html' %}
{% load i18n %}
{% block title %}
{% if object %}
{{ object }}
{% else %}
{% trans "Add a BMI Entry" %}
{% endif %}
{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'core:bmi-list' %}">{% trans "BMI" %}</a></li>
{% if object %}
<li class="breadcrumb-item active" aria-current="page">{% trans "Update" %}</li>
{% else %}
<li class="breadcrumb-item active" aria-current="page">{% trans "Add a BMI Entry" %}</li>
{% endif %}
{% endblock %}
{% block content %}
{% if object %}
{% blocktrans trimmed %}
<h1>Update <span class="text-info">{{ object }}</span></h1>
{% endblocktrans %}
{% else %}
<h1>{% trans "Add a BMI Entry" %}</h1>
{% endif %}
{% include 'babybuddy/form.html' %}
{% endblock %}
{% block javascript %}
<script type="text/javascript">
BabyBuddy.DatetimePicker.init($('#datetimepicker_date'), {
format: 'L',
extraFormats: ['YYYY-MM-DD']
});
</script>
{% endblock %}

View File

@ -0,0 +1,73 @@
{% extends 'babybuddy/page.html' %}
{% load i18n widget_tweaks %}
{% block title %}{% trans "BMI" %}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item active" aria-current="page">{% trans "BMI" %}</li>
{% endblock %}
{% block content %}
<h1>
{% trans "BMI" %}
{% if perms.core.add_bmi %}
<a href="{% url 'core:bmi-add' %}" class="btn btn-sm btn-success">
<i class="icon-bmi" aria-hidden="true"></i> {% trans "Add BMI" %}
</a>
{% endif %}
</h1>
{% include 'babybuddy/filter.html' %}
<div class="table-responsive">
<table class="table table-instances">
<thead class="thead-inverse">
<tr>
<th>{% trans "Actions" %}</th>
<th>{% trans "Date" %}</th>
{% if not unique_child %}
<th>{% trans "Child" %}</th>
{% endif %}
<th>{% trans "BMI" %}</th>
</tr>
</thead>
<tbody>
{% for bmi in object_list %}
{% cycle "odd" "even" as row_class silent %}
<tr class="{{ row_class }}">
<td>
<div class="btn-group btn-group-sm" role="group" aria-label="{% trans "Actions" %}">
{% if perms.core.change_bmi %}
<a href="{% url 'core:bmi-update' bmi.id %}" class="btn btn-primary">
<i class="icon-update" aria-hidden="true"></i>
</a>
{% endif %}
{% if perms.core.delete_bmi %}
<a href="{% url 'core:bmi-delete' bmi.id %}" class="btn btn-danger">
<i class="icon-delete" aria-hidden="true"></i>
</a>
{% endif %}
</div>
</td>
<th scope="row">{{ bmi.date }}</th>
{% if not unique_child %}
<td><a href="{% url 'core:child' bmi.child.slug %}">{{ bmi.child }}</a></td>
{% endif %}
<td>{{ bmi.bmi }}</td>
</tr>
{% if bmi.notes %}
<tr class="{{ row_class }} row-details">
<td colspan="4"><i class="icon-note mr-2" aria-hidden="true"></i>{{ bmi.notes }}</td>
</tr>
{% endif %}
{% empty %}
<tr>
<th colspan="4">{% trans "No bmi entries found." %}</th>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% include 'babybuddy/paginator.html' %}
{% endblock %}

View File

@ -0,0 +1,20 @@
{% extends 'babybuddy/page.html' %}
{% load i18n widget_tweaks %}
{% block title %}{% trans "Delete a Head Circumference Entry" %}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'core:head-circumference-list' %}">{% trans "Head Circumference" %}</a></li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Delete" %}</li>
{% endblock %}
{% block content %}
<form role="form" method="post">
{% csrf_token %}
{% blocktrans trimmed %}
<h1>Are you sure you want to delete <span class="text-info">{{ object }}</span>?</h1>
{% endblocktrans %}
<input type="submit" value="{% trans "Delete" %}" class="btn btn-danger" />
<a href="{% url 'core:head-circumference-list' %}" class="btn btn-default">{% trans "Cancel" %}</a>
</form>
{% endblock %}

View File

@ -0,0 +1,39 @@
{% extends 'babybuddy/page.html' %}
{% load i18n %}
{% block title %}
{% if object %}
{{ object }}
{% else %}
{% trans "Add a Head Circumference Entry" %}
{% endif %}
{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'core:head-circumference-list' %}">{% trans "Head Circumference" %}</a></li>
{% if object %}
<li class="breadcrumb-item active" aria-current="page">{% trans "Update" %}</li>
{% else %}
<li class="breadcrumb-item active" aria-current="page">{% trans "Add a Head Circumference Entry" %}</li>
{% endif %}
{% endblock %}
{% block content %}
{% if object %}
{% blocktrans trimmed %}
<h1>Update <span class="text-info">{{ object }}</span></h1>
{% endblocktrans %}
{% else %}
<h1>{% trans "Add a Head Circumference Entry" %}</h1>
{% endif %}
{% include 'babybuddy/form.html' %}
{% endblock %}
{% block javascript %}
<script type="text/javascript">
BabyBuddy.DatetimePicker.init($('#datetimepicker_date'), {
format: 'L',
extraFormats: ['YYYY-MM-DD']
});
</script>
{% endblock %}

View File

@ -0,0 +1,73 @@
{% extends 'babybuddy/page.html' %}
{% load i18n widget_tweaks %}
{% block title %}{% trans "Head Circumference" %}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item active" aria-current="page">{% trans "Head Circumference" %}</li>
{% endblock %}
{% block content %}
<h1>
{% trans "Head Circumference" %}
{% if perms.core.add_head_circumference %}
<a href="{% url 'core:head-circumference-add' %}" class="btn btn-sm btn-success">
<i class="icon-head-circumference" aria-hidden="true"></i> {% trans "Add Head Circumference" %}
</a>
{% endif %}
</h1>
{% include 'babybuddy/filter.html' %}
<div class="table-responsive">
<table class="table table-instances">
<thead class="thead-inverse">
<tr>
<th>{% trans "Actions" %}</th>
<th>{% trans "Date" %}</th>
{% if not unique_child %}
<th>{% trans "Child" %}</th>
{% endif %}
<th>{% trans "Head Circumference" %}</th>
</tr>
</thead>
<tbody>
{% for head_circumference in object_list %}
{% cycle "odd" "even" as row_class silent %}
<tr class="{{ row_class }}">
<td>
<div class="btn-group btn-group-sm" role="group" aria-label="{% trans "Actions" %}">
{% if perms.core.change_head_circumference %}
<a href="{% url 'core:head-circumference-update' head_circumference.id %}" class="btn btn-primary">
<i class="icon-update" aria-hidden="true"></i>
</a>
{% endif %}
{% if perms.core.delete_head_circumference %}
<a href="{% url 'core:head-circumference-delete' head_circumference.id %}" class="btn btn-danger">
<i class="icon-delete" aria-hidden="true"></i>
</a>
{% endif %}
</div>
</td>
<th scope="row">{{ head_circumference.date }}</th>
{% if not unique_child %}
<td><a href="{% url 'core:child' head_circumference.child.slug %}">{{ head_circumference.child }}</a></td>
{% endif %}
<td>{{ head_circumference.head_circumference }}</td>
</tr>
{% if head_circumference.notes %}
<tr class="{{ row_class }} row-details">
<td colspan="4"><i class="icon-note mr-2" aria-hidden="true"></i>{{ head_circumference.notes }}</td>
</tr>
{% endif %}
{% empty %}
<tr>
<th colspan="4">{% trans "No head circumference entries found." %}</th>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% include 'babybuddy/paginator.html' %}
{% endblock %}

View File

@ -0,0 +1,20 @@
{% extends 'babybuddy/page.html' %}
{% load i18n widget_tweaks %}
{% block title %}{% trans "Delete a Height Entry" %}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'core:height-list' %}">{% trans "Height" %}</a></li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Delete" %}</li>
{% endblock %}
{% block content %}
<form role="form" method="post">
{% csrf_token %}
{% blocktrans trimmed %}
<h1>Are you sure you want to delete <span class="text-info">{{ object }}</span>?</h1>
{% endblocktrans %}
<input type="submit" value="{% trans "Delete" %}" class="btn btn-danger" />
<a href="{% url 'core:height-list' %}" class="btn btn-default">{% trans "Cancel" %}</a>
</form>
{% endblock %}

View File

@ -0,0 +1,39 @@
{% extends 'babybuddy/page.html' %}
{% load i18n %}
{% block title %}
{% if object %}
{{ object }}
{% else %}
{% trans "Add a Height Entry" %}
{% endif %}
{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'core:height-list' %}">{% trans "Height" %}</a></li>
{% if object %}
<li class="breadcrumb-item active" aria-current="page">{% trans "Update" %}</li>
{% else %}
<li class="breadcrumb-item active" aria-current="page">{% trans "Add a Height Entry" %}</li>
{% endif %}
{% endblock %}
{% block content %}
{% if object %}
{% blocktrans trimmed %}
<h1>Update <span class="text-info">{{ object }}</span></h1>
{% endblocktrans %}
{% else %}
<h1>{% trans "Add a Height Entry" %}</h1>
{% endif %}
{% include 'babybuddy/form.html' %}
{% endblock %}
{% block javascript %}
<script type="text/javascript">
BabyBuddy.DatetimePicker.init($('#datetimepicker_date'), {
format: 'L',
extraFormats: ['YYYY-MM-DD']
});
</script>
{% endblock %}

View File

@ -0,0 +1,73 @@
{% extends 'babybuddy/page.html' %}
{% load i18n widget_tweaks %}
{% block title %}{% trans "Height" %}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item active" aria-current="page">{% trans "Height" %}</li>
{% endblock %}
{% block content %}
<h1>
{% trans "Height" %}
{% if perms.core.add_height %}
<a href="{% url 'core:height-add' %}" class="btn btn-sm btn-success">
<i class="icon-height" aria-hidden="true"></i> {% trans "Add Height" %}
</a>
{% endif %}
</h1>
{% include 'babybuddy/filter.html' %}
<div class="table-responsive">
<table class="table table-instances">
<thead class="thead-inverse">
<tr>
<th>{% trans "Actions" %}</th>
<th>{% trans "Date" %}</th>
{% if not unique_child %}
<th>{% trans "Child" %}</th>
{% endif %}
<th>{% trans "Height" %}</th>
</tr>
</thead>
<tbody>
{% for height in object_list %}
{% cycle "odd" "even" as row_class silent %}
<tr class="{{ row_class }}">
<td>
<div class="btn-group btn-group-sm" role="group" aria-label="{% trans "Actions" %}">
{% if perms.core.change_height %}
<a href="{% url 'core:height-update' height.id %}" class="btn btn-primary">
<i class="icon-update" aria-hidden="true"></i>
</a>
{% endif %}
{% if perms.core.delete_height %}
<a href="{% url 'core:height-delete' height.id %}" class="btn btn-danger">
<i class="icon-delete" aria-hidden="true"></i>
</a>
{% endif %}
</div>
</td>
<th scope="row">{{ height.date }}</th>
{% if not unique_child %}
<td><a href="{% url 'core:child' height.child.slug %}">{{ height.child }}</a></td>
{% endif %}
<td>{{ height.height }}</td>
</tr>
{% if height.notes %}
<tr class="{{ row_class }} row-details">
<td colspan="4"><i class="icon-note mr-2" aria-hidden="true"></i>{{ height.notes }}</td>
</tr>
{% endif %}
{% empty %}
<tr>
<th colspan="4">{% trans "No height entries found." %}</th>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% include 'babybuddy/paginator.html' %}
{% endblock %}

View File

@ -158,4 +158,43 @@ urlpatterns = [
views.WeightDelete.as_view(),
name='weight-delete'
),
path('height/', views.HeightList.as_view(), name='height-list'),
path('height/add/', views.HeightAdd.as_view(), name='height-add'),
path(
'height/<int:pk>/',
views.HeightUpdate.as_view(),
name='height-update'
),
path(
'height/<int:pk>/delete/',
views.HeightDelete.as_view(),
name='height-delete'
),
path('head-circumference/', views.HeadCircumferenceList.as_view(), name='head-circumference-list'),
path('head-circumference/add/', views.HeadCircumferenceAdd.as_view(), name='head-circumference-add'),
path(
'head-circumference/<int:pk>/',
views.HeadCircumferenceUpdate.as_view(),
name='head-circumference-update'
),
path(
'head-circumference/<int:pk>/delete/',
views.HeadCircumferenceDelete.as_view(),
name='head-circumference-delete'
),
path('bmi/', views.BMIList.as_view(), name='bmi-list'),
path('bmi/add/', views.BMIAdd.as_view(), name='bmi-add'),
path(
'bmi/<int:pk>/',
views.BMIUpdate.as_view(),
name='bmi-update'
),
path(
'bmi/<int:pk>/delete/',
views.BMIDelete.as_view(),
name='bmi-delete'
),
]

View File

@ -469,3 +469,87 @@ class WeightDelete(CoreDeleteView):
model = models.Weight
permission_required = ('core.delete_weight',)
success_url = reverse_lazy('core:weight-list')
class HeightList(PermissionRequiredMixin, BabyBuddyFilterView):
model = models.Height
template_name = 'core/height_list.html'
permission_required = ('core.view_height',)
paginate_by = 10
filterset_fields = ('child',)
class HeightAdd(CoreAddView):
model = models.Height
permission_required = ('core.add_height',)
form_class = forms.HeightForm
success_url = reverse_lazy('core:height-list')
class HeightUpdate(CoreUpdateView):
model = models.Height
permission_required = ('core.change_height',)
form_class = forms.HeightForm
success_url = reverse_lazy('core:height-list')
class HeightDelete(CoreDeleteView):
model = models.Height
permission_required = ('core.delete_height',)
success_url = reverse_lazy('core:height-list')
class HeadCircumferenceList(PermissionRequiredMixin, BabyBuddyFilterView):
model = models.HeadCircumference
template_name = 'core/head_circumference_list.html'
permission_required = ('core.view_head_circumference',)
paginate_by = 10
filterset_fields = ('child',)
class HeadCircumferenceAdd(CoreAddView):
model = models.HeadCircumference
template_name = 'core/head_circumference_form.html'
permission_required = ('core.add_head_circumference',)
form_class = forms.HeadCircumferenceForm
success_url = reverse_lazy('core:head-circumference-list')
class HeadCircumferenceUpdate(CoreUpdateView):
model = models.HeadCircumference
template_name = 'core/head_circumference_form.html'
permission_required = ('core.change_head_circumference',)
form_class = forms.HeadCircumferenceForm
success_url = reverse_lazy('core:head-circumference-list')
class HeadCircumferenceDelete(CoreDeleteView):
model = models.HeadCircumference
template_name = 'core/head_circumference_delete.html'
permission_required = ('core.delete_head_circumference',)
success_url = reverse_lazy('core:head-circumference-list')
class BMIList(PermissionRequiredMixin, BabyBuddyFilterView):
model = models.BMI
template_name = 'core/bmi_list.html'
permission_required = ('core.view_bmi',)
paginate_by = 10
filterset_fields = ('child',)
class BMIAdd(CoreAddView):
model = models.BMI
permission_required = ('core.add_bmi',)
form_class = forms.BMIForm
success_url = reverse_lazy('core:bmi-list')
class BMIUpdate(CoreUpdateView):
model = models.BMI
permission_required = ('core.change_bmi',)
form_class = forms.BMIForm
success_url = reverse_lazy('core:bmi-list')
class BMIDelete(CoreDeleteView):
model = models.BMI
permission_required = ('core.delete_bmi',)
success_url = reverse_lazy('core:bmi-list')

View File

@ -29,6 +29,9 @@
<a class="dropdown-item" href="{% url 'reports:report-sleep-totals-child' object.slug %}">{% trans "Sleep Totals" %}</a>
<a class="dropdown-item" href="{% url 'reports:report-tummytime-duration-child' object.slug %}">{% trans "Tummy Time Durations (Sum)" %}</a>
<a class="dropdown-item" href="{% url 'reports:report-weight-weight-child' object.slug %}">{% trans "Weight" %}</a>
<a class="dropdown-item" href="{% url 'reports:report-height-height-child' object.slug %}">{% trans "Height" %}</a>
<a class="dropdown-item" href="{% url 'reports:report-head-circumference-head-circumference-child' object.slug %}">{% trans "Head Circumference" %}</a>
<a class="dropdown-item" href="{% url 'reports:report-bmi-bmi-child' object.slug %}">{% trans "BMI" %}</a>
</div>
</div>

View File

@ -312,6 +312,27 @@ def card_statistics(context, child):
'type': 'float',
'stat': weight['change_weekly'],
'title': _('Weight change per week')})
height = _height_statistics(child)
if height:
stats.append({
'type': 'float',
'stat': height['change_weekly'],
'title': _('Height change per week')})
head_circumference = _head_circumference_statistics(child)
if head_circumference:
stats.append({
'type': 'float',
'stat': head_circumference['change_weekly'],
'title': _('Head circumference change per week')})
bmi = _bmi_statistics(child)
if bmi:
stats.append({
'type': 'float',
'stat': bmi['change_weekly'],
'title': _('BMI change per week')})
empty = len(stats) == 0
@ -476,6 +497,71 @@ def _weight_statistics(child):
return weight
def _height_statistics(child):
"""
Statistical height data.
:param child: an instance of the Child model.
:returns: a dictionary of statistics.
"""
height = {'change_weekly': 0.0}
instances = models.Height.objects.filter(child=child).order_by('-date')
if len(instances) == 0:
return False
newest = instances.first()
oldest = instances.last()
if newest != oldest:
height_change = newest.height - oldest.height
weeks = (newest.date - oldest.date).days/7
height['change_weekly'] = height_change/weeks
return height
def _head_circumference_statistics(child):
"""
Statistical head circumference data.
:param child: an instance of the Child model.
:returns: a dictionary of statistics.
"""
head_circumference = {'change_weekly': 0.0}
instances = models.HeadCircumference.objects.filter(child=child).order_by('-date')
if len(instances) == 0:
return False
newest = instances.first()
oldest = instances.last()
if newest != oldest:
head_circumference_change = newest.head_circumference - oldest.head_circumference
weeks = (newest.date - oldest.date).days/7
head_circumference['change_weekly'] = head_circumference_change/weeks
return head_circumference
def _bmi_statistics(child):
"""
Statistical BMI data.
:param child: an instance of the Child model.
:returns: a dictionary of statistics.
"""
bmi = {'change_weekly': 0.0}
instances = models.BMI.objects.filter(child=child).order_by('-date')
if len(instances) == 0:
return False
newest = instances.first()
oldest = instances.last()
if newest != oldest:
bmi_change = newest.bmi - oldest.bmi
weeks = (newest.date - oldest.date).days/7
bmi['change_weekly'] = bmi_change/weeks
return bmi
@register.inclusion_tag('cards/timer_list.html', takes_context=True)
def card_timer_list(context, child=None):

View File

@ -210,6 +210,21 @@ class TemplateTagsTestCase(TestCase):
'title': 'Weight change per week',
'stat': 1.0, 'type':
'float'
},
{
'title': 'Height change per week',
'stat': 1.0, 'type':
'float'
},
{
'title': 'Head circumference change per week',
'stat': 1.0, 'type':
'float'
},
{
'title': 'BMI change per week',
'stat': 1.0, 'type':
'float'
}
]

Binary file not shown.

View File

@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Baby Buddy\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-12-18 18:43+0000\n"
"POT-Creation-Date: 2021-12-28 23:25-0800\n"
"Language: en-GB\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -10,7 +10,7 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: babybuddy/admin.py:12 babybuddy/admin.py:13
#: babybuddy/templates/babybuddy/nav-dropdown.html:270
#: babybuddy/templates/babybuddy/nav-dropdown.html:326
#: babybuddy/templates/babybuddy/user_settings_form.html:8
msgid "Settings"
msgstr ""
@ -180,11 +180,11 @@ msgstr ""
#: babybuddy/templates/admin/base_site.html:4
#: babybuddy/templates/admin/base_site.html:7
#: babybuddy/templates/babybuddy/nav-dropdown.html:277
#: babybuddy/templates/babybuddy/nav-dropdown.html:338
msgid "Database Admin"
msgstr ""
#: babybuddy/templates/babybuddy/base.html:23
#: babybuddy/templates/babybuddy/base.html:36
msgid "Home"
msgstr ""
@ -227,7 +227,7 @@ msgid "Diaper Change"
msgstr "Nappy Change"
#: babybuddy/templates/babybuddy/nav-dropdown.html:57
#: babybuddy/templates/babybuddy/nav-dropdown.html:218 core/models.py:243
#: babybuddy/templates/babybuddy/nav-dropdown.html:274 core/models.py:243
#: core/models.py:247 core/templates/core/timer_detail.html:43
msgid "Feeding"
msgstr ""
@ -239,7 +239,7 @@ msgid "Note"
msgstr ""
#: babybuddy/templates/babybuddy/nav-dropdown.html:69
#: babybuddy/templates/babybuddy/nav-dropdown.html:225
#: babybuddy/templates/babybuddy/nav-dropdown.html:281
#: babybuddy/templates/babybuddy/welcome.html:42 core/models.py:330
#: core/models.py:331 core/models.py:334
#: core/templates/core/sleep_confirm_delete.html:7
@ -250,7 +250,7 @@ msgid "Sleep"
msgstr ""
#: babybuddy/templates/babybuddy/nav-dropdown.html:75
#: babybuddy/templates/babybuddy/nav-dropdown.html:158 core/models.py:369
#: babybuddy/templates/babybuddy/nav-dropdown.html:172 core/models.py:369
#: core/models.py:383 core/models.py:384 core/models.py:387
#: core/templates/core/temperature_confirm_delete.html:7
#: core/templates/core/temperature_form.html:13
@ -262,7 +262,7 @@ msgid "Temperature"
msgstr ""
#: babybuddy/templates/babybuddy/nav-dropdown.html:81
#: babybuddy/templates/babybuddy/nav-dropdown.html:238
#: babybuddy/templates/babybuddy/nav-dropdown.html:294
#: babybuddy/templates/babybuddy/welcome.html:50 core/models.py:537
#: core/models.py:538 core/models.py:541
#: core/templates/core/timer_detail.html:59
@ -275,7 +275,7 @@ msgid "Tummy Time"
msgstr ""
#: babybuddy/templates/babybuddy/nav-dropdown.html:87
#: babybuddy/templates/babybuddy/nav-dropdown.html:172 core/models.py:567
#: babybuddy/templates/babybuddy/nav-dropdown.html:186 core/models.py:567
#: core/models.py:581 core/models.py:582 core/models.py:585
#: core/templates/core/weight_confirm_delete.html:7
#: core/templates/core/weight_form.html:13
@ -311,8 +311,12 @@ msgstr ""
#: babybuddy/templates/babybuddy/nav-dropdown.html:136 core/models.py:104
#: core/models.py:137 core/models.py:196 core/models.py:267 core/models.py:300
#: core/models.py:364 core/models.py:401 core/models.py:509 core/models.py:562
#: core/models.py:596 core/models.py:630 core/models.py:664
#: core/templates/core/bmi_list.html:27
#: core/templates/core/diaperchange_list.html:27
#: core/templates/core/feeding_list.html:27
#: core/templates/core/head_circumference_list.html:27
#: core/templates/core/height_list.html:27
#: core/templates/core/note_list.html:27 core/templates/core/sleep_list.html:28
#: core/templates/core/temperature_list.html:27
#: core/templates/core/timer_list.html:27
@ -323,34 +327,95 @@ msgstr ""
#: babybuddy/templates/babybuddy/nav-dropdown.html:144 core/models.py:158
#: core/models.py:236 core/models.py:282 core/models.py:322 core/models.py:376
#: core/models.py:574 core/templates/core/note_confirm_delete.html:7
#: core/models.py:574 core/models.py:608 core/models.py:642 core/models.py:676
#: core/templates/core/note_confirm_delete.html:7
#: core/templates/core/note_form.html:13 core/templates/core/note_list.html:4
#: core/templates/core/note_list.html:7 core/templates/core/note_list.html:12
msgid "Notes"
msgstr ""
#: babybuddy/templates/babybuddy/nav-dropdown.html:164
msgid "Temperature reading"
msgid "Measurements"
msgstr ""
#: babybuddy/templates/babybuddy/nav-dropdown.html:178
msgid "Weight entry"
msgid "Temperature reading"
msgstr ""
#: babybuddy/templates/babybuddy/nav-dropdown.html:192
msgid "Weight entry"
msgstr ""
#: babybuddy/templates/babybuddy/nav-dropdown.html:200 core/models.py:601
#: core/models.py:615 core/models.py:616 core/models.py:619
#: core/templates/core/height_confirm_delete.html:7
#: core/templates/core/height_form.html:13
#: core/templates/core/height_list.html:4
#: core/templates/core/height_list.html:7
#: core/templates/core/height_list.html:12
#: core/templates/core/height_list.html:29
#: dashboard/templates/dashboard/child_button_group.html:32
#: reports/graphs/height_height.py:19 reports/graphs/height_height.py:30
#: reports/templates/reports/height_change.html:4
#: reports/templates/reports/height_change.html:8
msgid "Height"
msgstr ""
#: babybuddy/templates/babybuddy/nav-dropdown.html:206
msgid "Height entry"
msgstr ""
#: babybuddy/templates/babybuddy/nav-dropdown.html:214 core/models.py:635
#: core/models.py:649 core/models.py:650 core/models.py:653
#: core/templates/core/head_circumference_confirm_delete.html:7
#: core/templates/core/head_circumference_form.html:13
#: core/templates/core/head_circumference_list.html:4
#: core/templates/core/head_circumference_list.html:7
#: core/templates/core/head_circumference_list.html:12
#: core/templates/core/head_circumference_list.html:29
#: dashboard/templates/dashboard/child_button_group.html:33
#: reports/graphs/head_circumference_head_circumference.py:19
#: reports/graphs/head_circumference_head_circumference.py:30
#: reports/templates/reports/head_circumference_change.html:4
#: reports/templates/reports/head_circumference_change.html:8
msgid "Head Circumference"
msgstr ""
#: babybuddy/templates/babybuddy/nav-dropdown.html:220
msgid "Head Circumference entry"
msgstr ""
#: babybuddy/templates/babybuddy/nav-dropdown.html:228 core/models.py:669
#: core/models.py:683 core/models.py:684 core/models.py:687
#: core/templates/core/bmi_confirm_delete.html:7
#: core/templates/core/bmi_form.html:13 core/templates/core/bmi_list.html:4
#: core/templates/core/bmi_list.html:7 core/templates/core/bmi_list.html:12
#: core/templates/core/bmi_list.html:29
#: dashboard/templates/dashboard/child_button_group.html:34
#: reports/graphs/bmi_bmi.py:19 reports/graphs/bmi_bmi.py:30
#: reports/templates/reports/bmi_change.html:4
#: reports/templates/reports/bmi_change.html:8
msgid "BMI"
msgstr ""
#: babybuddy/templates/babybuddy/nav-dropdown.html:234
msgid "BMI entry"
msgstr ""
#: babybuddy/templates/babybuddy/nav-dropdown.html:248
msgid "Activities"
msgstr ""
#: babybuddy/templates/babybuddy/nav-dropdown.html:199
#: babybuddy/templates/babybuddy/nav-dropdown.html:255
#: reports/graphs/diaperchange_lifetimes.py:27
msgid "Changes"
msgstr ""
#: babybuddy/templates/babybuddy/nav-dropdown.html:205
#: babybuddy/templates/babybuddy/nav-dropdown.html:261
msgid "Change"
msgstr ""
#: babybuddy/templates/babybuddy/nav-dropdown.html:212
#: babybuddy/templates/babybuddy/nav-dropdown.html:268
#: babybuddy/templates/babybuddy/welcome.html:34 core/models.py:244
#: core/templates/core/feeding_confirm_delete.html:7
#: core/templates/core/feeding_form.html:13
@ -360,15 +425,15 @@ msgstr ""
msgid "Feedings"
msgstr ""
#: babybuddy/templates/babybuddy/nav-dropdown.html:231
#: babybuddy/templates/babybuddy/nav-dropdown.html:287
msgid "Sleep entry"
msgstr ""
#: babybuddy/templates/babybuddy/nav-dropdown.html:244
#: babybuddy/templates/babybuddy/nav-dropdown.html:300
msgid "Tummy Time entry"
msgstr ""
#: babybuddy/templates/babybuddy/nav-dropdown.html:269
#: babybuddy/templates/babybuddy/nav-dropdown.html:325
#: babybuddy/templates/babybuddy/user_list.html:17
#: babybuddy/templates/babybuddy/user_password_form.html:7
#: babybuddy/templates/babybuddy/user_settings_form.html:7 core/models.py:434
@ -376,23 +441,23 @@ msgstr ""
msgid "User"
msgstr ""
#: babybuddy/templates/babybuddy/nav-dropdown.html:271
#: babybuddy/templates/babybuddy/nav-dropdown.html:327
msgid "Password"
msgstr ""
#: babybuddy/templates/babybuddy/nav-dropdown.html:272
#: babybuddy/templates/babybuddy/nav-dropdown.html:331
msgid "Logout"
msgstr ""
#: babybuddy/templates/babybuddy/nav-dropdown.html:273
#: babybuddy/templates/babybuddy/nav-dropdown.html:334
msgid "Site"
msgstr ""
#: babybuddy/templates/babybuddy/nav-dropdown.html:274
#: babybuddy/templates/babybuddy/nav-dropdown.html:335
msgid "API Browser"
msgstr ""
#: babybuddy/templates/babybuddy/nav-dropdown.html:276
#: babybuddy/templates/babybuddy/nav-dropdown.html:337
#: babybuddy/templates/babybuddy/user_confirm_delete.html:7
#: babybuddy/templates/babybuddy/user_form.html:13
#: babybuddy/templates/babybuddy/user_list.html:4
@ -400,15 +465,15 @@ msgstr ""
msgid "Users"
msgstr ""
#: babybuddy/templates/babybuddy/nav-dropdown.html:279
#: babybuddy/templates/babybuddy/nav-dropdown.html:340
msgid "Support"
msgstr ""
#: babybuddy/templates/babybuddy/nav-dropdown.html:281
#: babybuddy/templates/babybuddy/nav-dropdown.html:342
msgid "Source Code"
msgstr ""
#: babybuddy/templates/babybuddy/nav-dropdown.html:283
#: babybuddy/templates/babybuddy/nav-dropdown.html:344
msgid "Chat / Support"
msgstr ""
@ -437,12 +502,18 @@ msgid "Delete User"
msgstr ""
#: babybuddy/templates/babybuddy/user_confirm_delete.html:9
#: core/templates/core/bmi_confirm_delete.html:8
#: core/templates/core/bmi_confirm_delete.html:17
#: core/templates/core/child_confirm_delete.html:9
#: core/templates/core/child_confirm_delete.html:31
#: core/templates/core/diaperchange_confirm_delete.html:8
#: core/templates/core/diaperchange_confirm_delete.html:17
#: core/templates/core/feeding_confirm_delete.html:8
#: core/templates/core/feeding_confirm_delete.html:17
#: core/templates/core/head_circumference_confirm_delete.html:8
#: core/templates/core/head_circumference_confirm_delete.html:17
#: core/templates/core/height_confirm_delete.html:8
#: core/templates/core/height_confirm_delete.html:17
#: core/templates/core/note_confirm_delete.html:8
#: core/templates/core/note_confirm_delete.html:17
#: core/templates/core/sleep_confirm_delete.html:8
@ -456,14 +527,17 @@ msgstr ""
#: core/templates/core/tummytime_confirm_delete.html:17
#: core/templates/core/weight_confirm_delete.html:8
#: core/templates/core/weight_confirm_delete.html:17
#: dashboard/templates/dashboard/child_button_group.html:45
#: dashboard/templates/dashboard/child_button_group.html:48
msgid "Delete"
msgstr ""
#: babybuddy/templates/babybuddy/user_confirm_delete.html:15
#: core/templates/core/bmi_confirm_delete.html:14
#: core/templates/core/child_confirm_delete.html:15
#: core/templates/core/diaperchange_confirm_delete.html:14
#: core/templates/core/feeding_confirm_delete.html:14
#: core/templates/core/head_circumference_confirm_delete.html:14
#: core/templates/core/height_confirm_delete.html:14
#: core/templates/core/note_confirm_delete.html:14
#: core/templates/core/sleep_confirm_delete.html:14
#: core/templates/core/temperature_confirm_delete.html:14
@ -477,9 +551,12 @@ msgid ""
msgstr ""
#: babybuddy/templates/babybuddy/user_confirm_delete.html:19
#: core/templates/core/bmi_confirm_delete.html:18
#: core/templates/core/child_confirm_delete.html:32
#: core/templates/core/diaperchange_confirm_delete.html:18
#: core/templates/core/feeding_confirm_delete.html:18
#: core/templates/core/head_circumference_confirm_delete.html:18
#: core/templates/core/height_confirm_delete.html:18
#: core/templates/core/note_confirm_delete.html:18
#: core/templates/core/sleep_confirm_delete.html:18
#: core/templates/core/temperature_confirm_delete.html:18
@ -498,9 +575,11 @@ msgid "Create User"
msgstr ""
#: babybuddy/templates/babybuddy/user_form.html:16
#: core/templates/core/child_form.html:16
#: core/templates/core/bmi_form.html:15 core/templates/core/child_form.html:16
#: core/templates/core/diaperchange_form.html:15
#: core/templates/core/feeding_form.html:15
#: core/templates/core/head_circumference_form.html:15
#: core/templates/core/height_form.html:15
#: core/templates/core/note_form.html:15 core/templates/core/sleep_form.html:15
#: core/templates/core/temperature_form.html:15
#: core/templates/core/timer_form.html:10
@ -510,9 +589,11 @@ msgid "Update"
msgstr ""
#: babybuddy/templates/babybuddy/user_form.html:24
#: core/templates/core/child_form.html:24
#: core/templates/core/bmi_form.html:23 core/templates/core/child_form.html:24
#: core/templates/core/diaperchange_form.html:23
#: core/templates/core/feeding_form.html:23
#: core/templates/core/head_circumference_form.html:23
#: core/templates/core/height_form.html:23
#: core/templates/core/note_form.html:23 core/templates/core/sleep_form.html:23
#: core/templates/core/temperature_form.html:23
#: core/templates/core/timer_form.html:18
@ -546,12 +627,17 @@ msgid "Active"
msgstr ""
#: babybuddy/templates/babybuddy/user_list.html:23
#: core/templates/core/bmi_list.html:24 core/templates/core/bmi_list.html:37
#: core/templates/core/child_list.html:28
#: core/templates/core/child_list.html:48
#: core/templates/core/diaperchange_list.html:24
#: core/templates/core/diaperchange_list.html:39
#: core/templates/core/feeding_list.html:24
#: core/templates/core/feeding_list.html:42
#: core/templates/core/head_circumference_list.html:24
#: core/templates/core/head_circumference_list.html:37
#: core/templates/core/height_list.html:24
#: core/templates/core/height_list.html:37
#: core/templates/core/note_list.html:24 core/templates/core/note_list.html:36
#: core/templates/core/sleep_list.html:24
#: core/templates/core/sleep_list.html:39
@ -704,30 +790,30 @@ msgid ""
"you will receive instructions for resetting your password."
msgstr ""
#: babybuddy/views.py:66
#: babybuddy/views.py:78
#, python-format
msgid "User %(username)s added!"
msgstr ""
#: babybuddy/views.py:76
#: babybuddy/views.py:88
#, python-format
msgid "User %(username)s updated."
msgstr ""
#: babybuddy/views.py:88
#: babybuddy/views.py:100
#, python-brace-format
msgid "User {user} deleted."
msgstr ""
#: babybuddy/views.py:111
#: babybuddy/views.py:123
msgid "Password updated."
msgstr ""
#: babybuddy/views.py:134
#: babybuddy/views.py:146
msgid "User API key regenerated."
msgstr ""
#: babybuddy/views.py:149
#: babybuddy/views.py:161
msgid "Settings saved!"
msgstr ""
@ -915,16 +1001,41 @@ msgstr ""
msgid "Milestone"
msgstr ""
#: core/models.py:572 core/templates/core/feeding_list.html:25
#: core/templates/core/weight_list.html:25
#: core/models.py:572 core/models.py:606 core/models.py:640 core/models.py:674
#: core/templates/core/bmi_list.html:25
#: core/templates/core/feeding_list.html:25
#: core/templates/core/head_circumference_list.html:25
#: core/templates/core/height_list.html:25
#: core/templates/core/weight_list.html:25 reports/graphs/bmi_bmi.py:28
#: reports/graphs/diaperchange_amounts.py:37
#: reports/graphs/diaperchange_types.py:47 reports/graphs/feeding_amounts.py:37
#: reports/graphs/feeding_duration.py:54 reports/graphs/sleep_pattern.py:146
#: reports/graphs/feeding_duration.py:54
#: reports/graphs/head_circumference_head_circumference.py:28
#: reports/graphs/height_height.py:28 reports/graphs/sleep_pattern.py:146
#: reports/graphs/sleep_totals.py:51 reports/graphs/tummytime_duration.py:49
#: reports/graphs/weight_weight.py:28
msgid "Date"
msgstr ""
#: core/templates/core/bmi_confirm_delete.html:4
msgid "Delete a BMI Entry"
msgstr ""
#: core/templates/core/bmi_form.html:8 core/templates/core/bmi_form.html:17
#: core/templates/core/bmi_form.html:27
msgid "Add a BMI Entry"
msgstr ""
#: core/templates/core/bmi_list.html:15
msgid "Add BMI"
msgstr ""
#: core/templates/core/bmi_list.html:66
#, fuzzy
#| msgid "No diaper changes found."
msgid "No bmi entries found."
msgstr "No nappy changes found."
#: core/templates/core/child_confirm_delete.html:4
msgid "Delete a Child"
msgstr ""
@ -1012,6 +1123,46 @@ msgstr ""
msgid "No feedings found."
msgstr ""
#: core/templates/core/head_circumference_confirm_delete.html:4
msgid "Delete a Head Circumference Entry"
msgstr ""
#: core/templates/core/head_circumference_form.html:8
#: core/templates/core/head_circumference_form.html:17
#: core/templates/core/head_circumference_form.html:27
msgid "Add a Head Circumference Entry"
msgstr ""
#: core/templates/core/head_circumference_list.html:15
msgid "Add Head Circumference"
msgstr ""
#: core/templates/core/head_circumference_list.html:66
#, fuzzy
#| msgid "No diaper changes found."
msgid "No head circumference entries found."
msgstr "No nappy changes found."
#: core/templates/core/height_confirm_delete.html:4
msgid "Delete a Height Entry"
msgstr ""
#: core/templates/core/height_form.html:8
#: core/templates/core/height_form.html:17
#: core/templates/core/height_form.html:27
msgid "Add a Height Entry"
msgstr ""
#: core/templates/core/height_list.html:15
msgid "Add Height"
msgstr ""
#: core/templates/core/height_list.html:66
#, fuzzy
#| msgid "No diaper changes found."
msgid "No height entries found."
msgstr "No nappy changes found."
#: core/templates/core/note_confirm_delete.html:4
msgid "Delete a Note"
msgstr ""
@ -1223,7 +1374,7 @@ msgid "%(since)s since previous"
msgstr ""
#: core/templates/timeline/_timeline.html:56
#: dashboard/templates/dashboard/child_button_group.html:38
#: dashboard/templates/dashboard/child_button_group.html:41
msgid "Edit"
msgstr ""
@ -1557,18 +1708,34 @@ msgstr ""
msgid "Weight change per week"
msgstr ""
#: dashboard/templatetags/cards.py:361
#: dashboard/templatetags/cards.py:321
msgid "Height change per week"
msgstr ""
#: dashboard/templatetags/cards.py:328
msgid "Head circumference change per week"
msgstr ""
#: dashboard/templatetags/cards.py:335
msgid "BMI change per week"
msgstr ""
#: dashboard/templatetags/cards.py:382
msgid "Feeding frequency (past 3 days)"
msgstr ""
#: dashboard/templatetags/cards.py:365
#: dashboard/templatetags/cards.py:386
msgid "Feeding frequency (past 2 weeks)"
msgstr ""
#: dashboard/templatetags/cards.py:371
#: dashboard/templatetags/cards.py:392
msgid "Feeding frequency"
msgstr ""
#: reports/graphs/bmi_bmi.py:27
msgid "<b>BMI</b>"
msgstr ""
#: reports/graphs/diaperchange_amounts.py:27
msgid "Diaper change amount"
msgstr "Nappy change amount"
@ -1633,6 +1800,14 @@ msgstr ""
msgid "Number of feedings"
msgstr ""
#: reports/graphs/head_circumference_head_circumference.py:27
msgid "<b>Head Circumference</b>"
msgstr ""
#: reports/graphs/height_height.py:27
msgid "<b>Height</b>"
msgstr ""
#: reports/graphs/sleep_pattern.py:143
msgid "<b>Sleep Pattern</b>"
msgstr ""

File diff suppressed because it is too large Load Diff

View File

@ -7,3 +7,6 @@ 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
from .height_height import height_height #NOQA
from .head_circumference_head_circumference import head_circumference_head_circumference #NOQA
from .bmi_bmi import bmi_bmi #NOQA

37
reports/graphs/bmi_bmi.py Normal file
View File

@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
from django.utils.translation import gettext as _
import plotly.offline as plotly
import plotly.graph_objs as go
from reports import utils
def bmi_bmi(objects):
"""
Create a graph showing bmi over time.
:param objects: a QuerySet of BMI instances.
:returns: a tuple of the the graph's html and javascript.
"""
objects = objects.order_by('-date')
trace = go.Scatter(
name=_('BMI'),
x=list(objects.values_list('date', flat=True)),
y=list(objects.values_list('bmi', flat=True)),
fill='tozeroy',
)
layout_args = utils.default_graph_layout_options()
layout_args['barmode'] = 'stack'
layout_args['title'] = _('<b>BMI</b>')
layout_args['xaxis']['title'] = _('Date')
layout_args['xaxis']['rangeselector'] = utils.rangeselector_date()
layout_args['yaxis']['title'] = _('BMI')
fig = go.Figure({
'data': [trace],
'layout': go.Layout(**layout_args)
})
output = plotly.plot(fig, output_type='div', include_plotlyjs=False)
return utils.split_graph_output(output)

View File

@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
from django.utils.translation import gettext as _
import plotly.offline as plotly
import plotly.graph_objs as go
from reports import utils
def head_circumference_head_circumference(objects):
"""
Create a graph showing head_circumference over time.
:param objects: a QuerySet of Head Circumference instances.
:returns: a tuple of the the graph's html and javascript.
"""
objects = objects.order_by('-date')
trace = go.Scatter(
name=_('Head Circumference'),
x=list(objects.values_list('date', flat=True)),
y=list(objects.values_list('head_circumference', flat=True)),
fill='tozeroy',
)
layout_args = utils.default_graph_layout_options()
layout_args['barmode'] = 'stack'
layout_args['title'] = _('<b>Head Circumference</b>')
layout_args['xaxis']['title'] = _('Date')
layout_args['xaxis']['rangeselector'] = utils.rangeselector_date()
layout_args['yaxis']['title'] = _('Head Circumference')
fig = go.Figure({
'data': [trace],
'layout': go.Layout(**layout_args)
})
output = plotly.plot(fig, output_type='div', include_plotlyjs=False)
return utils.split_graph_output(output)

View File

@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
from django.utils.translation import gettext as _
import plotly.offline as plotly
import plotly.graph_objs as go
from reports import utils
def height_height(objects):
"""
Create a graph showing height over time.
:param objects: a QuerySet of Height instances.
:returns: a tuple of the the graph's html and javascript.
"""
objects = objects.order_by('-date')
trace = go.Scatter(
name=_('Height'),
x=list(objects.values_list('date', flat=True)),
y=list(objects.values_list('height', flat=True)),
fill='tozeroy',
)
layout_args = utils.default_graph_layout_options()
layout_args['barmode'] = 'stack'
layout_args['title'] = _('<b>Height</b>')
layout_args['xaxis']['title'] = _('Date')
layout_args['xaxis']['rangeselector'] = utils.rangeselector_date()
layout_args['yaxis']['title'] = _('Height')
fig = go.Figure({
'data': [trace],
'layout': go.Layout(**layout_args)
})
output = plotly.plot(fig, output_type='div', include_plotlyjs=False)
return utils.split_graph_output(output)

View File

@ -0,0 +1,9 @@
{% extends 'reports/report_base.html' %}
{% load i18n %}
{% block title %}{% trans "BMI" %} - {{ object }}{% endblock %}
{% block breadcrumbs %}
{{ block.super }}
<li class="breadcrumb-item active" aria-current="page">{% trans "BMI" %}</li>
{% endblock %}

View File

@ -0,0 +1,9 @@
{% extends 'reports/report_base.html' %}
{% load i18n %}
{% block title %}{% trans "Head Circumference" %} - {{ object }}{% endblock %}
{% block breadcrumbs %}
{{ block.super }}
<li class="breadcrumb-item active" aria-current="page">{% trans "Head Circumference" %}</li>
{% endblock %}

View File

@ -0,0 +1,9 @@
{% extends 'reports/report_base.html' %}
{% load i18n %}
{% block title %}{% trans "Height" %} - {{ object }}{% endblock %}
{% block breadcrumbs %}
{{ block.super }}
<li class="breadcrumb-item active" aria-current="page">{% trans "Height" %}</li>
{% endblock %}

View File

@ -52,3 +52,12 @@ class ViewsTestCase(TestCase):
page = self.c.get('{}/weight/weight/'.format(base_url))
self.assertEqual(page.status_code, 200)
page = self.c.get('{}/height/height/'.format(base_url))
self.assertEqual(page.status_code, 200)
page = self.c.get('{}/head-circumference/head-circumference/'.format(base_url))
self.assertEqual(page.status_code, 200)
page = self.c.get('{}/bmi/bmi/'.format(base_url))
self.assertEqual(page.status_code, 200)

View File

@ -51,4 +51,19 @@ urlpatterns = [
views.WeightWeightChildReport.as_view(),
name='report-weight-weight-child'
),
path(
'children/<str:slug>/reports/height/height/',
views.HeightHeightChildReport.as_view(),
name='report-height-height-child'
),
path(
'children/<str:slug>/reports/head-circumference/head-circumference/',
views.HeadCircumferenceHeadCircumferenceChildReport.as_view(),
name='report-head-circumference-head-circumference-child'
),
path(
'children/<str:slug>/reports/bmi/bmi/',
views.BMIBMIChildReport.as_view(),
name='report-bmi-bmi-child'
),
]

View File

@ -195,3 +195,54 @@ class WeightWeightChildReport(PermissionRequiredMixin, DetailView):
if objects:
context['html'], context['js'] = graphs.weight_weight(objects)
return context
class HeightHeightChildReport(PermissionRequiredMixin, DetailView):
"""
Graph of height change over time.
"""
model = models.Child
permission_required = ('core.view_child',)
template_name = 'reports/height_change.html'
def get_context_data(self, **kwargs):
context = super(HeightHeightChildReport, self).get_context_data(
**kwargs)
child = context['object']
objects = models.Height.objects.filter(child=child)
if objects:
context['html'], context['js'] = graphs.height_height(objects)
return context
class HeadCircumferenceHeadCircumferenceChildReport(PermissionRequiredMixin, DetailView):
"""
Graph of head circumference change over time.
"""
model = models.Child
permission_required = ('core.view_child',)
template_name = 'reports/head_circumference_change.html'
def get_context_data(self, **kwargs):
context = super(HeadCircumferenceHeadCircumferenceChildReport, self).get_context_data(
**kwargs)
child = context['object']
objects = models.HeadCircumference.objects.filter(child=child)
if objects:
context['html'], context['js'] = graphs.head_circumference_head_circumference(objects)
return context
class BMIBMIChildReport(PermissionRequiredMixin, DetailView):
"""
Graph of BMI change over time.
"""
model = models.Child
permission_required = ('core.view_child',)
template_name = 'reports/bmi_change.html'
def get_context_data(self, **kwargs):
context = super(BMIBMIChildReport, self).get_context_data(
**kwargs)
child = context['object']
objects = models.BMI.objects.filter(child=child)
if objects:
context['html'], context['js'] = graphs.bmi_bmi(objects)
return context