mirror of https://github.com/snachodog/mybuddy.git
Add option for hiding empty dashboard cards (#213)
* add option for hiding empty dashboard cards * rework add option for hiding empty dashboard cards missed statistics.html * don't exit early in cards * add forms test for dashboard_hide_empty * add tests for cards * fix early exit in card_diaperchange_latest * change dependency of migration * rename migration * introduce hiding of cards in templates * linting * add context to test_card_diaperchange_last * setup MockUserRequest * add context to all cards test cases * add test for settings_dashboard_hide_empty_on * change dashboard_hide_test, but it doesn't work * add test for _user_wants_hide * fix test_user_wants_hide user object, simpliy check for data['empty'] * add test for user_wants_hide to every card * linting * fix trailing whitespace * rename user_wants_hide to hide_empty * fix hidden statistics * add user.refresh_from_db to test case, add test case for dashboard_refresh_rate * Follow redirect and correct assertion Co-authored-by: jcgoette <jcgoette@gmail.com> Co-authored-by: Benjamin Häublein <benjaminh@debian.vm.hp> Co-authored-by: Christopher C. Wells <git@chris-wells.net>
This commit is contained in:
parent
fe568876c7
commit
1dca1cc050
|
@ -14,7 +14,7 @@ class SettingsInline(admin.StackedInline):
|
|||
can_delete = False
|
||||
fieldsets = (
|
||||
(_('Dashboard'), {
|
||||
'fields': ('dashboard_refresh_rate',)
|
||||
'fields': ('dashboard_refresh_rate', 'dashboard_hide_empty',)
|
||||
}),
|
||||
)
|
||||
|
||||
|
|
|
@ -41,4 +41,9 @@ class UserPasswordForm(PasswordChangeForm):
|
|||
class UserSettingsForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Settings
|
||||
fields = ['dashboard_refresh_rate', 'language', 'timezone']
|
||||
fields = [
|
||||
'dashboard_refresh_rate',
|
||||
'dashboard_hide_empty',
|
||||
'language',
|
||||
'timezone'
|
||||
]
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 3.1.5 on 2021-01-19 23:17
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('babybuddy', '0013_auto_20210411_1241'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='settings',
|
||||
name='dashboard_hide_empty',
|
||||
field=models.BooleanField(default=False, verbose_name='Hide Empty Dashboard Cards'),
|
||||
),
|
||||
]
|
|
@ -34,6 +34,11 @@ class Settings(models.Model):
|
|||
(timezone.timedelta(minutes=15), _('15 min.')),
|
||||
(timezone.timedelta(minutes=30), _('30 min.')),
|
||||
])
|
||||
dashboard_hide_empty = models.BooleanField(
|
||||
verbose_name=_('Hide Empty Dashboard Cards'),
|
||||
default=False,
|
||||
editable=True
|
||||
)
|
||||
language = models.CharField(
|
||||
choices=settings.LANGUAGES,
|
||||
default=settings.LANGUAGE_CODE,
|
||||
|
|
|
@ -64,6 +64,11 @@
|
|||
{% include 'babybuddy/form_field.html' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
{% with form_settings.dashboard_hide_empty as field %}
|
||||
{% include 'babybuddy/form_field.html' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "API" %}</legend>
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.management import call_command
|
||||
from django.test import Client as HttpClient, override_settings, TestCase
|
||||
|
@ -132,3 +134,26 @@ class FormsTestCase(TestCase):
|
|||
self.assertEqual(page.status_code, 200)
|
||||
self.assertEqual(timezone.get_current_timezone_name(),
|
||||
params['timezone'])
|
||||
|
||||
def test_user_settings_dashboard_hide_empty_on(self):
|
||||
self.c.login(**self.credentials)
|
||||
|
||||
params = self.settings_template.copy()
|
||||
params['dashboard_hide_empty'] = 'on'
|
||||
|
||||
page = self.c.post('/user/settings/', data=params, follow=True)
|
||||
self.assertEqual(page.status_code, 200)
|
||||
self.user.refresh_from_db()
|
||||
self.assertTrue(self.user.settings.dashboard_hide_empty)
|
||||
|
||||
def test_user_settings_dashboard_refresh_rate(self):
|
||||
self.c.login(**self.credentials)
|
||||
|
||||
params = self.settings_template.copy()
|
||||
params['dashboard_refresh_rate'] = '0:05:00'
|
||||
|
||||
page = self.c.post('/user/settings/', data=params, follow=True)
|
||||
self.assertEqual(page.status_code, 200)
|
||||
self.user.refresh_from_db()
|
||||
self.assertEqual(self.user.settings.dashboard_refresh_rate,
|
||||
datetime.timedelta(seconds=300))
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
<div class="card card-dashboard card-{{ type }}">
|
||||
<div class="card-header">
|
||||
<i class="icon icon-{{ type }} pull-left" aria-hidden="true"></i>
|
||||
{% block header %}{% endblock %}
|
||||
{% if not empty or not hide_empty %}
|
||||
<div class="card card-dashboard card-{{ type }}">
|
||||
<div class="card-header">
|
||||
<i class="icon icon-{{ type }} pull-left" aria-hidden="true"></i>
|
||||
{% block header %}{% endblock %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<span class="card-title"><strong>{% block title %}{% endblock %}</strong></span>
|
||||
<div class="card-text"> {% block content %}{% endblock %} </div>
|
||||
</div>
|
||||
{% block listgroup %}{% endblock %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<span class="card-title"><strong>{% block title %}{% endblock %}</strong></span>
|
||||
<div class="card-text">{% block content %}{% endblock %}</div>
|
||||
</div>
|
||||
{% block listgroup %}{% endblock %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
|
@ -1,40 +1,46 @@
|
|||
{% load duration i18n %}
|
||||
|
||||
<div class="card card-dashboard card-statistics">
|
||||
<div class="card-header">
|
||||
<i class="icon icon-graph pull-left" aria-hidden="true"></i>
|
||||
{% trans "Statistics" %}
|
||||
</div>
|
||||
<div class="card-body text-center">
|
||||
<div id="statistics-carousel" class="carousel slide" data-interval="false">
|
||||
<div class="carousel-inner">
|
||||
{% for stat in stats %}
|
||||
<div class="carousel-item{% if forloop.counter == 1 %} active{% endif %}">
|
||||
<span class="card-title">
|
||||
{% if stat.stat %}
|
||||
{% if stat.type == 'duration' %}
|
||||
{{ stat.stat|duration_string:'m' }}
|
||||
{% elif stat.type == 'float' %}
|
||||
{{ stat.stat|floatformat }}
|
||||
{% if not empty or not hide_empty %}
|
||||
<div class="card card-dashboard card-statistics">
|
||||
<div class="card-header">
|
||||
<i class="icon icon-graph pull-left" aria-hidden="true"></i>
|
||||
{% trans "Statistics" %}
|
||||
</div>
|
||||
<div class="card-body text-center">
|
||||
{% if stats|length > 0 %}
|
||||
<div id="statistics-carousel" class="carousel slide" data-interval="false">
|
||||
<div class="carousel-inner">
|
||||
{% for stat in stats %}
|
||||
<div class="carousel-item{% if forloop.counter == 1 %} active{% endif %}">
|
||||
<span class="card-title">
|
||||
{% if stat.stat %}
|
||||
{% if stat.type == 'duration' %}
|
||||
{{ stat.stat|duration_string:'m' }}
|
||||
{% elif stat.type == 'float' %}
|
||||
{{ stat.stat|floatformat }}
|
||||
{% else %}
|
||||
{{ stat.stat }}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{{ stat.stat }}
|
||||
<em>{% trans "Not enough data" %}</em>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<em>{% trans "Not enough data" %}</em>
|
||||
{% endif %}
|
||||
</span>
|
||||
<div class="card-text">{{ stat.title }}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</span>
|
||||
<div class="card-text">{{ stat.title }}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<a class="carousel-control-prev" href="#statistics-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="#statistics-carousel" role="button" data-slide="next">
|
||||
<span class="carousel-control-next-icon" aria-hidden="true"></span>
|
||||
<span class="sr-only">{% trans "Next" %}</span>
|
||||
</a>
|
||||
</div>
|
||||
<a class="carousel-control-prev" href="#statistics-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="#statistics-carousel" role="button" data-slide="next">
|
||||
<span class="carousel-control-next-icon" aria-hidden="true"></span>
|
||||
<span class="sr-only">{% trans "Next" %}</span>
|
||||
</a>
|
||||
{% else %}
|
||||
<span class="card-title"><strong>{% trans "No data yet" %}</strong></span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
|
@ -9,12 +9,15 @@ from datetime import date, datetime, time
|
|||
|
||||
from core import models
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.inclusion_tag('cards/diaperchange_last.html')
|
||||
def card_diaperchange_last(child):
|
||||
def _hide_empty(context):
|
||||
return context['request'].user.settings.dashboard_hide_empty
|
||||
|
||||
|
||||
@register.inclusion_tag('cards/diaperchange_last.html', takes_context=True)
|
||||
def card_diaperchange_last(context, child):
|
||||
"""
|
||||
Information about the most recent diaper change.
|
||||
:param child: an instance of the Child model.
|
||||
|
@ -22,11 +25,18 @@ def card_diaperchange_last(child):
|
|||
"""
|
||||
instance = models.DiaperChange.objects.filter(
|
||||
child=child).order_by('-time').first()
|
||||
return {'type': 'diaperchange', 'change': instance}
|
||||
empty = not instance
|
||||
|
||||
return {
|
||||
'type': 'diaperchange',
|
||||
'change': instance,
|
||||
'empty': empty,
|
||||
'hide_empty': _hide_empty(context)
|
||||
}
|
||||
|
||||
|
||||
@register.inclusion_tag('cards/diaperchange_types.html')
|
||||
def card_diaperchange_types(child, date=None):
|
||||
@register.inclusion_tag('cards/diaperchange_types.html', takes_context=True)
|
||||
def card_diaperchange_types(context, child, date=None):
|
||||
"""
|
||||
Creates a break down of wet and solid Diaper Change instances for the past
|
||||
seven days.
|
||||
|
@ -51,6 +61,8 @@ def card_diaperchange_types(child, date=None):
|
|||
|
||||
instances = models.DiaperChange.objects.filter(child=child) \
|
||||
.filter(time__gt=min_date).filter(time__lt=max_date).order_by('-time')
|
||||
empty = len(instances) == 0
|
||||
|
||||
for instance in instances:
|
||||
key = (max_date - instance.time).days
|
||||
if instance.wet:
|
||||
|
@ -65,11 +77,17 @@ def card_diaperchange_types(child, date=None):
|
|||
stats[key]['wet_pct'] = info['wet'] / total * 100
|
||||
stats[key]['solid_pct'] = info['solid'] / total * 100
|
||||
|
||||
return {'type': 'diaperchange', 'stats': stats, 'total': week_total}
|
||||
return {
|
||||
'type': 'diaperchange',
|
||||
'stats': stats,
|
||||
'total': week_total,
|
||||
'empty': empty,
|
||||
'hide_empty': _hide_empty(context)
|
||||
}
|
||||
|
||||
|
||||
@register.inclusion_tag('cards/feeding_day.html')
|
||||
def card_feeding_day(child, date=None):
|
||||
@register.inclusion_tag('cards/feeding_day.html', takes_context=True)
|
||||
def card_feeding_day(context, child, date=None):
|
||||
"""
|
||||
Filters Feeding instances to get total amount for a specific date.
|
||||
:param child: an instance of the Child model.
|
||||
|
@ -78,6 +96,7 @@ def card_feeding_day(child, date=None):
|
|||
"""
|
||||
if not date:
|
||||
date = timezone.localtime().date()
|
||||
|
||||
instances = models.Feeding.objects.filter(child=child).filter(
|
||||
start__year=date.year,
|
||||
start__month=date.month,
|
||||
|
@ -89,12 +108,19 @@ def card_feeding_day(child, date=None):
|
|||
|
||||
total = sum([instance.amount for instance in instances if instance.amount])
|
||||
count = len(instances)
|
||||
empty = len(instances) == 0
|
||||
|
||||
return {'type': 'feeding', 'total': total, 'count': count}
|
||||
return {
|
||||
'type': 'feeding',
|
||||
'total': total,
|
||||
'count': count,
|
||||
'empty': empty,
|
||||
'hide_empty': _hide_empty(context)
|
||||
}
|
||||
|
||||
|
||||
@register.inclusion_tag('cards/feeding_last.html')
|
||||
def card_feeding_last(child):
|
||||
@register.inclusion_tag('cards/feeding_last.html', takes_context=True)
|
||||
def card_feeding_last(context, child):
|
||||
"""
|
||||
Information about the most recent feeding.
|
||||
:param child: an instance of the Child model.
|
||||
|
@ -102,11 +128,18 @@ def card_feeding_last(child):
|
|||
"""
|
||||
instance = models.Feeding.objects.filter(child=child) \
|
||||
.order_by('-end').first()
|
||||
return {'type': 'feeding', 'feeding': instance}
|
||||
empty = not instance
|
||||
|
||||
return {
|
||||
'type': 'feeding',
|
||||
'feeding': instance,
|
||||
'empty': empty,
|
||||
'hide_empty': _hide_empty(context)
|
||||
}
|
||||
|
||||
|
||||
@register.inclusion_tag('cards/feeding_last_method.html')
|
||||
def card_feeding_last_method(child):
|
||||
@register.inclusion_tag('cards/feeding_last_method.html', takes_context=True)
|
||||
def card_feeding_last_method(context, child):
|
||||
"""
|
||||
Information about the three most recent feeding methods.
|
||||
:param child: an instance of the Child model.
|
||||
|
@ -114,12 +147,19 @@ def card_feeding_last_method(child):
|
|||
"""
|
||||
instances = models.Feeding.objects.filter(child=child) \
|
||||
.order_by('-end')[:3]
|
||||
empty = len(instances) == 0
|
||||
|
||||
# Results are reversed for carousel forward/back behavior.
|
||||
return {'type': 'feeding', 'feedings': list(reversed(instances))}
|
||||
return {
|
||||
'type': 'feeding',
|
||||
'feedings': list(reversed(instances)),
|
||||
'empty': empty,
|
||||
'hide_empty': _hide_empty(context)
|
||||
}
|
||||
|
||||
|
||||
@register.inclusion_tag('cards/sleep_last.html')
|
||||
def card_sleep_last(child):
|
||||
@register.inclusion_tag('cards/sleep_last.html', takes_context=True)
|
||||
def card_sleep_last(context, child):
|
||||
"""
|
||||
Information about the most recent sleep entry.
|
||||
:param child: an instance of the Child model.
|
||||
|
@ -127,11 +167,18 @@ def card_sleep_last(child):
|
|||
"""
|
||||
instance = models.Sleep.objects.filter(child=child) \
|
||||
.order_by('-end').first()
|
||||
return {'type': 'sleep', 'sleep': instance}
|
||||
empty = not instance
|
||||
|
||||
return {
|
||||
'type': 'sleep',
|
||||
'sleep': instance,
|
||||
'empty': empty,
|
||||
'hide_empty': _hide_empty(context)
|
||||
}
|
||||
|
||||
|
||||
@register.inclusion_tag('cards/sleep_day.html')
|
||||
def card_sleep_day(child, date=None):
|
||||
@register.inclusion_tag('cards/sleep_day.html', takes_context=True)
|
||||
def card_sleep_day(context, child, date=None):
|
||||
"""
|
||||
Filters Sleep instances to get count and total values for a specific date.
|
||||
:param child: an instance of the Child model.
|
||||
|
@ -147,6 +194,7 @@ def card_sleep_day(child, date=None):
|
|||
end__year=date.year,
|
||||
end__month=date.month,
|
||||
end__day=date.day)
|
||||
empty = len(instances) == 0
|
||||
|
||||
total = timezone.timedelta(seconds=0)
|
||||
for instance in instances:
|
||||
|
@ -161,11 +209,17 @@ def card_sleep_day(child, date=None):
|
|||
|
||||
count = len(instances)
|
||||
|
||||
return {'type': 'sleep', 'total': total, 'count': count}
|
||||
return {
|
||||
'type': 'sleep',
|
||||
'total': total,
|
||||
'count': count,
|
||||
'empty': empty,
|
||||
'hide_empty': _hide_empty(context)
|
||||
}
|
||||
|
||||
|
||||
@register.inclusion_tag('cards/sleep_naps_day.html')
|
||||
def card_sleep_naps_day(child, date=None):
|
||||
@register.inclusion_tag('cards/sleep_naps_day.html', takes_context=True)
|
||||
def card_sleep_naps_day(context, child, date=None):
|
||||
"""
|
||||
Filters Sleep instances categorized as naps and generates statistics for a
|
||||
specific date.
|
||||
|
@ -182,14 +236,18 @@ def card_sleep_naps_day(child, date=None):
|
|||
end__year=date.year,
|
||||
end__month=date.month,
|
||||
end__day=date.day)
|
||||
empty = len(instances) == 0
|
||||
|
||||
return {
|
||||
'type': 'sleep',
|
||||
'total': instances.aggregate(Sum('duration'))['duration__sum'],
|
||||
'count': len(instances)}
|
||||
'count': len(instances),
|
||||
'empty': empty, 'hide_empty': _hide_empty(context)
|
||||
}
|
||||
|
||||
|
||||
@register.inclusion_tag('cards/statistics.html')
|
||||
def card_statistics(child):
|
||||
@register.inclusion_tag('cards/statistics.html', takes_context=True)
|
||||
def card_statistics(context, child):
|
||||
"""
|
||||
Statistics data for all models.
|
||||
:param child: an instance of the Child model.
|
||||
|
@ -198,45 +256,56 @@ def card_statistics(child):
|
|||
stats = []
|
||||
|
||||
changes = _diaperchange_statistics(child)
|
||||
stats.append({
|
||||
'type': 'duration',
|
||||
'stat': changes['btwn_average'],
|
||||
'title': _('Diaper change frequency')})
|
||||
|
||||
feedings = _feeding_statistics(child)
|
||||
for item in feedings:
|
||||
if changes:
|
||||
stats.append({
|
||||
'type': 'duration',
|
||||
'stat': item['btwn_average'],
|
||||
'title': item['title']})
|
||||
'stat': changes['btwn_average'],
|
||||
'title': _('Diaper change frequency')})
|
||||
|
||||
feedings = _feeding_statistics(child)
|
||||
if feedings:
|
||||
for item in feedings:
|
||||
stats.append({
|
||||
'type': 'duration',
|
||||
'stat': item['btwn_average'],
|
||||
'title': item['title']})
|
||||
|
||||
naps = _nap_statistics(child)
|
||||
stats.append({
|
||||
'type': 'duration',
|
||||
'stat': naps['average'],
|
||||
'title': _('Average nap duration')})
|
||||
stats.append({
|
||||
'type': 'float',
|
||||
'stat': naps['avg_per_day'],
|
||||
'title': _('Average naps per day')})
|
||||
if naps:
|
||||
stats.append({
|
||||
'type': 'duration',
|
||||
'stat': naps['average'],
|
||||
'title': _('Average nap duration')})
|
||||
stats.append({
|
||||
'type': 'float',
|
||||
'stat': naps['avg_per_day'],
|
||||
'title': _('Average naps per day')})
|
||||
|
||||
sleep = _sleep_statistics(child)
|
||||
stats.append({
|
||||
'type': 'duration',
|
||||
'stat': sleep['average'],
|
||||
'title': _('Average sleep duration')})
|
||||
stats.append({
|
||||
'type': 'duration',
|
||||
'stat': sleep['btwn_average'],
|
||||
'title': _('Average awake duration')})
|
||||
if sleep:
|
||||
stats.append({
|
||||
'type': 'duration',
|
||||
'stat': sleep['average'],
|
||||
'title': _('Average sleep duration')})
|
||||
stats.append({
|
||||
'type': 'duration',
|
||||
'stat': sleep['btwn_average'],
|
||||
'title': _('Average awake duration')})
|
||||
|
||||
weight = _weight_statistics(child)
|
||||
stats.append({
|
||||
'type': 'float',
|
||||
'stat': weight['change_weekly'],
|
||||
'title': _('Weight change per week')})
|
||||
if weight:
|
||||
stats.append({
|
||||
'type': 'float',
|
||||
'stat': weight['change_weekly'],
|
||||
'title': _('Weight change per week')})
|
||||
|
||||
return {'stats': stats}
|
||||
empty = len(stats) == 0
|
||||
|
||||
return {
|
||||
'stats': stats,
|
||||
'empty': empty,
|
||||
'hide_empty': _hide_empty(context)
|
||||
}
|
||||
|
||||
|
||||
def _diaperchange_statistics(child):
|
||||
|
@ -247,6 +316,8 @@ def _diaperchange_statistics(child):
|
|||
"""
|
||||
instances = models.DiaperChange.objects.filter(child=child) \
|
||||
.order_by('time')
|
||||
if len(instances) == 0:
|
||||
return False
|
||||
changes = {
|
||||
'btwn_total': timezone.timedelta(0),
|
||||
'btwn_count': instances.count() - 1,
|
||||
|
@ -292,6 +363,8 @@ def _feeding_statistics(child):
|
|||
timespan['btwn_average'] = 0.0
|
||||
|
||||
instances = models.Feeding.objects.filter(child=child).order_by('start')
|
||||
if len(instances) == 0:
|
||||
return False
|
||||
last_instance = None
|
||||
|
||||
for instance in instances:
|
||||
|
@ -317,6 +390,8 @@ def _nap_statistics(child):
|
|||
:returns: a dictionary of statistics.
|
||||
"""
|
||||
instances = models.Sleep.naps.filter(child=child).order_by('start')
|
||||
if len(instances) == 0:
|
||||
return False
|
||||
naps = {
|
||||
'total': instances.aggregate(Sum('duration'))['duration__sum'],
|
||||
'count': instances.count(),
|
||||
|
@ -340,6 +415,9 @@ def _sleep_statistics(child):
|
|||
:returns: a dictionary of statistics.
|
||||
"""
|
||||
instances = models.Sleep.objects.filter(child=child).order_by('start')
|
||||
if len(instances) == 0:
|
||||
return False
|
||||
|
||||
sleep = {
|
||||
'total': instances.aggregate(Sum('duration'))['duration__sum'],
|
||||
'count': instances.count(),
|
||||
|
@ -371,6 +449,9 @@ def _weight_statistics(child):
|
|||
weight = {'change_weekly': 0.0}
|
||||
|
||||
instances = models.Weight.objects.filter(child=child).order_by('-date')
|
||||
if len(instances) == 0:
|
||||
return False
|
||||
|
||||
newest = instances.first()
|
||||
oldest = instances.last()
|
||||
|
||||
|
@ -382,8 +463,8 @@ def _weight_statistics(child):
|
|||
return weight
|
||||
|
||||
|
||||
@register.inclusion_tag('cards/timer_list.html')
|
||||
def card_timer_list(child=None):
|
||||
@register.inclusion_tag('cards/timer_list.html', takes_context=True)
|
||||
def card_timer_list(context, child=None):
|
||||
"""
|
||||
Filters for currently active Timer instances, optionally by child.
|
||||
:param child: an instance of the Child model.
|
||||
|
@ -397,11 +478,18 @@ def card_timer_list(child=None):
|
|||
).order_by('-start')
|
||||
else:
|
||||
instances = models.Timer.objects.filter(active=True).order_by('-start')
|
||||
return {'type': 'timer', 'instances': list(instances)}
|
||||
empty = len(instances) == 0
|
||||
|
||||
return {
|
||||
'type': 'timer',
|
||||
'instances': list(instances),
|
||||
'empty': empty,
|
||||
'hide_empty': _hide_empty(context)
|
||||
}
|
||||
|
||||
|
||||
@register.inclusion_tag('cards/tummytime_last.html')
|
||||
def card_tummytime_last(child):
|
||||
@register.inclusion_tag('cards/tummytime_last.html', takes_context=True)
|
||||
def card_tummytime_last(context, child):
|
||||
"""
|
||||
Filters the most recent tummy time.
|
||||
:param child: an instance of the Child model.
|
||||
|
@ -409,11 +497,18 @@ def card_tummytime_last(child):
|
|||
"""
|
||||
instance = models.TummyTime.objects.filter(child=child) \
|
||||
.order_by('-end').first()
|
||||
return {'type': 'tummytime', 'tummytime': instance}
|
||||
empty = not instance
|
||||
|
||||
return {
|
||||
'type': 'tummytime',
|
||||
'tummytime': instance,
|
||||
'empty': empty,
|
||||
'hide_empty': _hide_empty(context)
|
||||
}
|
||||
|
||||
|
||||
@register.inclusion_tag('cards/tummytime_day.html')
|
||||
def card_tummytime_day(child, date=None):
|
||||
@register.inclusion_tag('cards/tummytime_day.html', takes_context=True)
|
||||
def card_tummytime_day(context, child, date=None):
|
||||
"""
|
||||
Filters Tummy Time instances and generates statistics for a specific date.
|
||||
:param child: an instance of the Child model.
|
||||
|
@ -425,14 +520,20 @@ def card_tummytime_day(child, date=None):
|
|||
instances = models.TummyTime.objects.filter(
|
||||
child=child, end__year=date.year, end__month=date.month,
|
||||
end__day=date.day).order_by('-end')
|
||||
empty = len(instances) == 0
|
||||
|
||||
stats = {
|
||||
'total': timezone.timedelta(seconds=0),
|
||||
'count': instances.count()
|
||||
}
|
||||
for instance in instances:
|
||||
stats['total'] += timezone.timedelta(seconds=instance.duration.seconds)
|
||||
|
||||
return {
|
||||
'type': 'tummytime',
|
||||
'stats': stats,
|
||||
'instances': instances,
|
||||
'last': instances.first()}
|
||||
'last': instances.first(),
|
||||
'empty': empty,
|
||||
'hide_empty': _hide_empty(context)
|
||||
}
|
||||
|
|
|
@ -10,6 +10,11 @@ from core import models
|
|||
from dashboard.templatetags import cards
|
||||
|
||||
|
||||
class MockUserRequest:
|
||||
def __init__(self, user):
|
||||
self.user = user
|
||||
|
||||
|
||||
class TemplateTagsTestCase(TestCase):
|
||||
fixtures = ['tests.json']
|
||||
|
||||
|
@ -17,6 +22,7 @@ class TemplateTagsTestCase(TestCase):
|
|||
def setUpClass(cls):
|
||||
super(TemplateTagsTestCase, cls).setUpClass()
|
||||
cls.child = models.Child.objects.first()
|
||||
cls.context = {'request': MockUserRequest(User.objects.first())}
|
||||
|
||||
# Ensure timezone matches the one defined by fixtures.
|
||||
user_timezone = Settings.objects.first().timezone
|
||||
|
@ -26,14 +32,26 @@ class TemplateTagsTestCase(TestCase):
|
|||
date = timezone.localtime().strptime('2017-11-18', '%Y-%m-%d')
|
||||
cls.date = timezone.make_aware(date)
|
||||
|
||||
def test_hide_empty(self):
|
||||
request = MockUserRequest(User.objects.first())
|
||||
request.user.settings.dashboard_hide_empty = True
|
||||
context = {'request': request}
|
||||
hide_empty = cards._hide_empty(context)
|
||||
self.assertTrue(hide_empty)
|
||||
|
||||
def test_card_diaperchange_last(self):
|
||||
data = cards.card_diaperchange_last(self.child)
|
||||
data = cards.card_diaperchange_last(self.context, self.child)
|
||||
self.assertEqual(data['type'], 'diaperchange')
|
||||
self.assertFalse(data['empty'])
|
||||
self.assertFalse(data['hide_empty'])
|
||||
self.assertIsInstance(data['change'], models.DiaperChange)
|
||||
self.assertEqual(data['change'], models.DiaperChange.objects.first())
|
||||
|
||||
def test_card_diaperchange_types(self):
|
||||
data = cards.card_diaperchange_types(self.child, self.date)
|
||||
data = cards.card_diaperchange_types(
|
||||
self.context,
|
||||
self.child,
|
||||
self.date)
|
||||
self.assertEqual(data['type'], 'diaperchange')
|
||||
stats = {
|
||||
0: {'wet_pct': 50.0, 'solid_pct': 50.0, 'solid': 1, 'wet': 1},
|
||||
|
@ -47,20 +65,26 @@ class TemplateTagsTestCase(TestCase):
|
|||
self.assertEqual(data['stats'], stats)
|
||||
|
||||
def test_card_feeding_day(self):
|
||||
data = cards.card_feeding_day(self.child, self.date)
|
||||
data = cards.card_feeding_day(self.context, self.child, self.date)
|
||||
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)
|
||||
|
||||
def test_card_feeding_last(self):
|
||||
data = cards.card_feeding_last(self.child)
|
||||
data = cards.card_feeding_last(self.context, self.child)
|
||||
self.assertEqual(data['type'], 'feeding')
|
||||
self.assertFalse(data['empty'])
|
||||
self.assertFalse(data['hide_empty'])
|
||||
self.assertIsInstance(data['feeding'], models.Feeding)
|
||||
self.assertEqual(data['feeding'], models.Feeding.objects.first())
|
||||
|
||||
def test_card_feeding_last_method(self):
|
||||
data = cards.card_feeding_last_method(self.child)
|
||||
data = cards.card_feeding_last_method(self.context, self.child)
|
||||
self.assertEqual(data['type'], 'feeding')
|
||||
self.assertFalse(data['empty'])
|
||||
self.assertFalse(data['hide_empty'])
|
||||
self.assertEqual(len(data['feedings']), 3)
|
||||
for feeding in data['feedings']:
|
||||
self.assertIsInstance(feeding, models.Feeding)
|
||||
|
@ -69,25 +93,38 @@ class TemplateTagsTestCase(TestCase):
|
|||
models.Feeding.objects.first().method)
|
||||
|
||||
def test_card_sleep_last(self):
|
||||
data = cards.card_sleep_last(self.child)
|
||||
data = cards.card_sleep_last(self.context, self.child)
|
||||
self.assertEqual(data['type'], 'sleep')
|
||||
self.assertFalse(data['empty'])
|
||||
self.assertFalse(data['hide_empty'])
|
||||
self.assertIsInstance(data['sleep'], models.Sleep)
|
||||
self.assertEqual(data['sleep'], models.Sleep.objects.first())
|
||||
|
||||
def test_card_sleep_day(self):
|
||||
data = cards.card_sleep_day(self.child, self.date)
|
||||
def test_card_sleep_last_empty(self):
|
||||
models.Sleep.objects.all().delete()
|
||||
data = cards.card_sleep_last(self.context, self.child)
|
||||
self.assertEqual(data['type'], 'sleep')
|
||||
self.assertTrue(data['empty'])
|
||||
self.assertFalse(data['hide_empty'])
|
||||
|
||||
def test_card_sleep_day(self):
|
||||
data = cards.card_sleep_day(self.context, self.child, self.date)
|
||||
self.assertEqual(data['type'], 'sleep')
|
||||
self.assertFalse(data['empty'])
|
||||
self.assertFalse(data['hide_empty'])
|
||||
self.assertEqual(data['total'], timezone.timedelta(2, 7200))
|
||||
self.assertEqual(data['count'], 4)
|
||||
|
||||
def test_card_sleep_naps_day(self):
|
||||
data = cards.card_sleep_naps_day(self.child, self.date)
|
||||
data = cards.card_sleep_naps_day(self.context, self.child, self.date)
|
||||
self.assertEqual(data['type'], 'sleep')
|
||||
self.assertFalse(data['empty'])
|
||||
self.assertFalse(data['hide_empty'])
|
||||
self.assertEqual(data['total'], timezone.timedelta(0, 9000))
|
||||
self.assertEqual(data['count'], 2)
|
||||
|
||||
def test_card_statistics(self):
|
||||
data = cards.card_statistics(self.child)
|
||||
data = cards.card_statistics(self.context, self.child)
|
||||
stats = [
|
||||
{
|
||||
'title': 'Diaper change frequency',
|
||||
|
@ -139,6 +176,8 @@ class TemplateTagsTestCase(TestCase):
|
|||
]
|
||||
|
||||
self.assertEqual(data['stats'], stats)
|
||||
self.assertFalse(data['empty'])
|
||||
self.assertFalse(data['hide_empty'])
|
||||
|
||||
def test_card_timer_list(self):
|
||||
user = User.objects.first()
|
||||
|
@ -165,31 +204,35 @@ class TemplateTagsTestCase(TestCase):
|
|||
),
|
||||
}
|
||||
|
||||
data = cards.card_timer_list()
|
||||
data = cards.card_timer_list(self.context)
|
||||
self.assertIsInstance(data['instances'][0], models.Timer)
|
||||
self.assertEqual(len(data['instances']), 3)
|
||||
|
||||
data = cards.card_timer_list(child)
|
||||
data = cards.card_timer_list(self.context, child)
|
||||
self.assertIsInstance(data['instances'][0], models.Timer)
|
||||
self.assertTrue(timers['no_child'] in data['instances'])
|
||||
self.assertTrue(timers['child'] in data['instances'])
|
||||
self.assertFalse(timers['child_two'] in data['instances'])
|
||||
|
||||
data = cards.card_timer_list(child_two)
|
||||
data = cards.card_timer_list(self.context, child_two)
|
||||
self.assertIsInstance(data['instances'][0], models.Timer)
|
||||
self.assertTrue(timers['no_child'] in data['instances'])
|
||||
self.assertTrue(timers['child_two'] in data['instances'])
|
||||
self.assertFalse(timers['child'] in data['instances'])
|
||||
|
||||
def test_card_tummytime_last(self):
|
||||
data = cards.card_tummytime_last(self.child)
|
||||
data = cards.card_tummytime_last(self.context, self.child)
|
||||
self.assertEqual(data['type'], 'tummytime')
|
||||
self.assertFalse(data['empty'])
|
||||
self.assertFalse(data['hide_empty'])
|
||||
self.assertIsInstance(data['tummytime'], models.TummyTime)
|
||||
self.assertEqual(data['tummytime'], models.TummyTime.objects.first())
|
||||
|
||||
def test_card_tummytime_day(self):
|
||||
data = cards.card_tummytime_day(self.child, self.date)
|
||||
data = cards.card_tummytime_day(self.context, self.child, self.date)
|
||||
self.assertEqual(data['type'], 'tummytime')
|
||||
self.assertFalse(data['empty'])
|
||||
self.assertFalse(data['hide_empty'])
|
||||
self.assertIsInstance(data['instances'].first(), models.TummyTime)
|
||||
self.assertIsInstance(data['last'], models.TummyTime)
|
||||
stats = {'count': 3, 'total': timezone.timedelta(0, 300)}
|
||||
|
|
Loading…
Reference in New Issue