diff --git a/api/tests.py b/api/tests.py index d71be462..1228b26c 100644 --- a/api/tests.py +++ b/api/tests.py @@ -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 = { diff --git a/babybuddy/fixtures/tests.json b/babybuddy/fixtures/tests.json index e92aa1f0..1eed6953 100644 --- a/babybuddy/fixtures/tests.json +++ b/babybuddy/fixtures/tests.json @@ -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, diff --git a/core/templatetags/duration.py b/core/templatetags/duration.py index db8da7f5..75bd2cf8 100644 --- a/core/templatetags/duration.py +++ b/core/templatetags/duration.py @@ -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" diff --git a/core/tests/tests_templatetags.py b/core/tests/tests_templatetags.py index 2e3caff8..d0cd57d6 100644 --- a/core/tests/tests_templatetags.py +++ b/core/tests/tests_templatetags.py @@ -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() diff --git a/dashboard/templates/cards/feeding_day.html b/dashboard/templates/cards/feeding_day.html index a0e0be1c..6569dcf0 100644 --- a/dashboard/templates/cards/feeding_day.html +++ b/dashboard/templates/cards/feeding_day.html @@ -8,15 +8,42 @@ {% endblock %} {% block title %} - {% if total %} - {{ total }} - {% else %} - {% trans "None" %} +{% if feedings|length > 0 %} + +{% else %} +{% trans "None" %} +{% endif %} -{% block content %} - {% if count > 0 %} - {% blocktrans %}{{ count }} feeding entries{% endblocktrans %} - {% endif %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/dashboard/templatetags/cards.py b/dashboard/templatetags/cards.py index b88a0164..55924856 100644 --- a/dashboard/templatetags/cards.py +++ b/dashboard/templatetags/cards.py @@ -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), } diff --git a/dashboard/tests/tests_templatetags.py b/dashboard/tests/tests_templatetags.py index d2e8ecc5..cc5180de 100644 --- a/dashboard/tests/tests_templatetags.py +++ b/dashboard/tests/tests_templatetags.py @@ -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", }, {