mirror of https://github.com/snachodog/mybuddy.git
Add carousel of last 7 days of feedings to the todays feeding card (#450)
Add list of last 7 days of feedings to the todays feeding card Co-authored-by: Daniel Beard <daniel@medcrypt.co>
This commit is contained in:
parent
7178bf9fa4
commit
21b8a737c0
|
@ -311,8 +311,8 @@ class FeedingAPITestCase(TestBase.BabyBuddyAPITestCaseBase):
|
|||
{
|
||||
"id": 3,
|
||||
"child": 1,
|
||||
"start": "2017-11-18T14:00:00-05:00",
|
||||
"end": "2017-11-18T14:15:00-05:00",
|
||||
"start": "2017-11-18T19:00:00-05:00",
|
||||
"end": "2017-11-18T19:15:00-05:00",
|
||||
"duration": "00:15:00",
|
||||
"type": "formula",
|
||||
"method": "bottle",
|
||||
|
@ -333,7 +333,7 @@ class FeedingAPITestCase(TestBase.BabyBuddyAPITestCaseBase):
|
|||
self.endpoint, {"start_min": "2017-11-18T11:30:00-05:00"}
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(response.data["count"], 2)
|
||||
self.assertEqual(response.data["count"], 3)
|
||||
|
||||
def test_post(self):
|
||||
data = {
|
||||
|
|
|
@ -234,8 +234,8 @@
|
|||
"fields":
|
||||
{
|
||||
"child": 1,
|
||||
"start": "2017-11-18T14:00:00Z",
|
||||
"end": "2017-11-18T14:30:00Z",
|
||||
"start": "2017-11-18T14:00:00-05:00",
|
||||
"end": "2017-11-18T14:30:00-05:00",
|
||||
"duration": "00:30:00",
|
||||
"type": "breast milk",
|
||||
"method": "left breast",
|
||||
|
@ -248,8 +248,8 @@
|
|||
"fields":
|
||||
{
|
||||
"child": 1,
|
||||
"start": "2017-11-18T16:30:00Z",
|
||||
"end": "2017-11-18T17:00:00Z",
|
||||
"start": "2017-11-18T16:30:00-05:00",
|
||||
"end": "2017-11-18T17:00:00-05:00",
|
||||
"duration": "00:30:00",
|
||||
"type": "breast milk",
|
||||
"method": "right breast",
|
||||
|
@ -262,8 +262,8 @@
|
|||
"fields":
|
||||
{
|
||||
"child": 1,
|
||||
"start": "2017-11-18T19:00:00Z",
|
||||
"end": "2017-11-18T19:15:00Z",
|
||||
"start": "2017-11-18T19:00:00-05:00",
|
||||
"end": "2017-11-18T19:15:00-05:00",
|
||||
"duration": "00:15:00",
|
||||
"type": "formula",
|
||||
"method": "bottle",
|
||||
|
@ -271,6 +271,51 @@
|
|||
"notes": "forgot vitamins :("
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "core.feeding",
|
||||
"pk": 4,
|
||||
"fields":
|
||||
{
|
||||
"child": 1,
|
||||
"start": "2017-11-17T19:00:00-05:00",
|
||||
"end": "2017-11-17T19:15:00-05:00",
|
||||
"duration": "00:15:00",
|
||||
"type": "formula",
|
||||
"method": "bottle",
|
||||
"amount": 0.25,
|
||||
"notes": "forgot vitamins :("
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "core.feeding",
|
||||
"pk": 5,
|
||||
"fields":
|
||||
{
|
||||
"child": 1,
|
||||
"start": "2017-11-11T19:00:00-05:00",
|
||||
"end": "2017-11-11T19:15:00-05:00",
|
||||
"duration": "00:15:00",
|
||||
"type": "formula",
|
||||
"method": "bottle",
|
||||
"amount": 10.0,
|
||||
"notes": "last day feedings"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "core.feeding",
|
||||
"pk": 6,
|
||||
"fields":
|
||||
{
|
||||
"child": 1,
|
||||
"start": "2017-11-11T00:00:00-05:00",
|
||||
"end": "2017-11-11T00:15:00-05:00",
|
||||
"duration": "00:15:00",
|
||||
"type": "formula",
|
||||
"method": "bottle",
|
||||
"amount": 10.0,
|
||||
"notes": "oldest feeding at midnight"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "core.note",
|
||||
"pk": 1,
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
|
||||
from django import template
|
||||
from django.utils import timesince, timezone
|
||||
from django.utils.translation import gettext as _
|
||||
|
@ -90,3 +92,25 @@ def seconds(duration):
|
|||
return s
|
||||
except (ValueError, TypeError):
|
||||
return 0
|
||||
|
||||
|
||||
@register.filter()
|
||||
def dayssince(value, today=None):
|
||||
"""
|
||||
Returns the days since passed datetime in a user friendly way. (e.g. today, yesterday, 2 days ago, ...)
|
||||
:param value: a date instance
|
||||
:param today: date to compare to (defaults to today)
|
||||
:returns: the formatted string
|
||||
"""
|
||||
if today is None:
|
||||
today = timezone.datetime.now().date()
|
||||
|
||||
delta = today - value
|
||||
|
||||
if delta < datetime.timedelta(days=1):
|
||||
return "today"
|
||||
if delta < datetime.timedelta(days=2):
|
||||
return "yesterday"
|
||||
|
||||
# use standard timesince for anything beyond yesterday
|
||||
return str(delta.days) + " days ago"
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import unittest
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import TestCase, override_settings
|
||||
from django.utils import timezone, formats
|
||||
|
@ -62,6 +64,37 @@ class TemplateTagsTestCase(TestCase):
|
|||
self.assertEqual(duration.seconds(""), 0)
|
||||
self.assertRaises(TypeError, duration.seconds("not a delta"))
|
||||
|
||||
def test_duration_dayssince(self):
|
||||
# test with a few different dates that could be pathological
|
||||
dates = [
|
||||
timezone.datetime(2022, 1, 1, 0, 0, 1).date(), # new year
|
||||
timezone.datetime(2021, 12, 31, 23, 59, 59).date(), # almost new year
|
||||
timezone.datetime(
|
||||
1969, 2, 1, 23, 59, 59
|
||||
).date(), # old but middle of the year
|
||||
]
|
||||
for d in dates:
|
||||
self.assertEqual(duration.dayssince(d, today=d), "today")
|
||||
self.assertEqual(
|
||||
duration.dayssince((d - timezone.timedelta(hours=5)), today=d), "today"
|
||||
)
|
||||
self.assertEqual(
|
||||
duration.dayssince((d - timezone.timedelta(hours=24)), today=d),
|
||||
"yesterday",
|
||||
)
|
||||
self.assertEqual(
|
||||
duration.dayssince((d - timezone.timedelta(hours=24 * 2)), today=d),
|
||||
"2 days ago",
|
||||
)
|
||||
self.assertEqual(
|
||||
duration.dayssince((d - timezone.timedelta(hours=24 * 10)), today=d),
|
||||
"10 days ago",
|
||||
)
|
||||
self.assertEqual(
|
||||
duration.dayssince((d - timezone.timedelta(hours=24 * 60)), today=d),
|
||||
"60 days ago",
|
||||
)
|
||||
|
||||
def test_instance_add_url(self):
|
||||
child = Child.objects.create(
|
||||
first_name="Test", last_name="Child", birth_date=timezone.localdate()
|
||||
|
|
|
@ -8,15 +8,42 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block title %}
|
||||
{% if total %}
|
||||
{{ total }}
|
||||
{% else %}
|
||||
{% trans "None" %}
|
||||
{% if feedings|length > 0 %}
|
||||
<div id="feeding-days-carousel" class="carousel slide" data-interval="false">
|
||||
<div class="carousel-inner">
|
||||
{% for feeding in feedings %}
|
||||
<div class="carousel-item{% if forloop.counter == 1 %} active{% endif %}">
|
||||
<div class="last-feeding-method text-center">
|
||||
{% if feeding.total %}
|
||||
{{ feeding.total }}
|
||||
{% else %}
|
||||
{% trans "None" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="text-center small">
|
||||
{% if feeding.count > 0 %} {{ feeding.count }} feedings {% endif %}
|
||||
</div>
|
||||
{% blocktrans trimmed with since=feeding.date.date|dayssince %}
|
||||
<div class="text-center small text-muted">
|
||||
{{ since }}
|
||||
</div>
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% if feedings|length > 1 %}
|
||||
<a class="carousel-control-prev" href="#feeding-days-carousel" role="button" data-slide="prev">
|
||||
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
|
||||
<span class="sr-only">{% trans "Previous" %}</span>
|
||||
</a>
|
||||
<a class="carousel-control-next" href="#feeding-days-carousel" role="button" data-slide="next">
|
||||
<span class="carousel-control-next-icon" aria-hidden="true"></span>
|
||||
<span class="sr-only">{% trans "Next" %}</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% else %}
|
||||
{% trans "None" %}
|
||||
{% endif %}
|
||||
|
||||
{% block content %}
|
||||
{% if count > 0 %}
|
||||
{% blocktrans %}{{ count }} feeding entries{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -107,31 +107,46 @@ def card_diaperchange_types(context, child, date=None):
|
|||
|
||||
|
||||
@register.inclusion_tag("cards/feeding_day.html", takes_context=True)
|
||||
def card_feeding_day(context, child, date=None):
|
||||
def card_feeding_day(context, child, end_date=None):
|
||||
"""
|
||||
Filters Feeding instances to get total amount for a specific date.
|
||||
Filters Feeding instances to get total amount for a specific date and for 7 days before
|
||||
:param child: an instance of the Child model.
|
||||
:param date: a Date object for the day to filter.
|
||||
:param end_date: a Date object for the day to filter.
|
||||
:returns: a dict with count and total amount for the Feeding instances.
|
||||
"""
|
||||
if not date:
|
||||
date = timezone.localtime().date()
|
||||
if not end_date:
|
||||
end_date = timezone.localtime()
|
||||
|
||||
# push end_date to very end of that day
|
||||
end_date = end_date.replace(hour=23, minute=59, second=59, microsecond=9999)
|
||||
# we need a datetime to use the range helper in the model
|
||||
start_date = end_date - timezone.timedelta(
|
||||
days=8
|
||||
) # end of the -8th day so we get the FULL 7th day
|
||||
|
||||
instances = models.Feeding.objects.filter(child=child).filter(
|
||||
start__year=date.year, start__month=date.month, start__day=date.day
|
||||
) | models.Feeding.objects.filter(child=child).filter(
|
||||
end__year=date.year, end__month=date.month, end__day=date.day
|
||||
start__range=[start_date, end_date]
|
||||
)
|
||||
|
||||
total = sum([instance.amount for instance in instances if instance.amount])
|
||||
count = len(instances)
|
||||
empty = len(instances) == 0 or total == 0
|
||||
# prepare the result list for the last 7 days
|
||||
dates = [end_date - timezone.timedelta(days=i) for i in range(8)]
|
||||
results = [{"date": d, "total": 0, "count": 0} for d in dates]
|
||||
|
||||
# do one pass over the data and add it to the appropriate day
|
||||
for instance in instances:
|
||||
# convert to local tz and push feed_date to end so we're comparing apples to apples for the date
|
||||
feed_date = timezone.localtime(instance.start).replace(
|
||||
hour=23, minute=59, second=59, microsecond=9999
|
||||
)
|
||||
idx = (end_date - feed_date).days
|
||||
result = results[idx]
|
||||
result["total"] += instance.amount if instance.amount is not None else 0
|
||||
result["count"] += 1
|
||||
|
||||
return {
|
||||
"feedings": results,
|
||||
"type": "feeding",
|
||||
"total": total,
|
||||
"count": count,
|
||||
"empty": empty,
|
||||
"empty": len(instances) == 0,
|
||||
"hide_empty": _hide_empty(context),
|
||||
}
|
||||
|
||||
|
|
|
@ -156,8 +156,17 @@ class TemplateTagsTestCase(TestCase):
|
|||
self.assertEqual(data["type"], "feeding")
|
||||
self.assertFalse(data["empty"])
|
||||
self.assertFalse(data["hide_empty"])
|
||||
self.assertEqual(data["total"], 2.5)
|
||||
self.assertEqual(data["count"], 3)
|
||||
# most recent day
|
||||
self.assertEqual(data["feedings"][0]["total"], 2.5)
|
||||
self.assertEqual(data["feedings"][0]["count"], 3)
|
||||
|
||||
# yesterday
|
||||
self.assertEqual(data["feedings"][1]["total"], 0.25)
|
||||
self.assertEqual(data["feedings"][1]["count"], 1)
|
||||
|
||||
# last day
|
||||
self.assertEqual(data["feedings"][-1]["total"], 20.0)
|
||||
self.assertEqual(data["feedings"][-1]["count"], 2)
|
||||
|
||||
def test_card_feeding_last(self):
|
||||
data = cards.card_feeding_last(self.context, self.child)
|
||||
|
@ -246,7 +255,7 @@ class TemplateTagsTestCase(TestCase):
|
|||
},
|
||||
{
|
||||
"type": "duration",
|
||||
"stat": timezone.timedelta(0, 7200),
|
||||
"stat": timezone.timedelta(days=1, seconds=46980),
|
||||
"title": "Feeding frequency",
|
||||
},
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue