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:
Benjamin Häublein 2021-05-14 05:28:39 +02:00 committed by GitHub
parent fe568876c7
commit 1dca1cc050
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 336 additions and 126 deletions

View File

@ -14,7 +14,7 @@ class SettingsInline(admin.StackedInline):
can_delete = False can_delete = False
fieldsets = ( fieldsets = (
(_('Dashboard'), { (_('Dashboard'), {
'fields': ('dashboard_refresh_rate',) 'fields': ('dashboard_refresh_rate', 'dashboard_hide_empty',)
}), }),
) )

View File

@ -41,4 +41,9 @@ class UserPasswordForm(PasswordChangeForm):
class UserSettingsForm(forms.ModelForm): class UserSettingsForm(forms.ModelForm):
class Meta: class Meta:
model = Settings model = Settings
fields = ['dashboard_refresh_rate', 'language', 'timezone'] fields = [
'dashboard_refresh_rate',
'dashboard_hide_empty',
'language',
'timezone'
]

View File

@ -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'),
),
]

View File

@ -34,6 +34,11 @@ class Settings(models.Model):
(timezone.timedelta(minutes=15), _('15 min.')), (timezone.timedelta(minutes=15), _('15 min.')),
(timezone.timedelta(minutes=30), _('30 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( language = models.CharField(
choices=settings.LANGUAGES, choices=settings.LANGUAGES,
default=settings.LANGUAGE_CODE, default=settings.LANGUAGE_CODE,

View File

@ -64,6 +64,11 @@
{% include 'babybuddy/form_field.html' %} {% include 'babybuddy/form_field.html' %}
{% endwith %} {% endwith %}
</div> </div>
<div class="form-group row">
{% with form_settings.dashboard_hide_empty as field %}
{% include 'babybuddy/form_field.html' %}
{% endwith %}
</div>
</fieldset> </fieldset>
<fieldset> <fieldset>
<legend>{% trans "API" %}</legend> <legend>{% trans "API" %}</legend>

View File

@ -1,4 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import datetime
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.management import call_command from django.core.management import call_command
from django.test import Client as HttpClient, override_settings, TestCase 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(page.status_code, 200)
self.assertEqual(timezone.get_current_timezone_name(), self.assertEqual(timezone.get_current_timezone_name(),
params['timezone']) 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))

View File

@ -1,11 +1,13 @@
<div class="card card-dashboard card-{{ type }}"> {% if not empty or not hide_empty %}
<div class="card-header"> <div class="card card-dashboard card-{{ type }}">
<i class="icon icon-{{ type }} pull-left" aria-hidden="true"></i> <div class="card-header">
{% block header %}{% endblock %} <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>
<div class="card-body"> {% endif %}
<span class="card-title"><strong>{% block title %}{% endblock %}</strong></span>
<div class="card-text">{% block content %}{% endblock %}</div>
</div>
{% block listgroup %}{% endblock %}
</div>

View File

@ -1,40 +1,46 @@
{% load duration i18n %} {% load duration i18n %}
<div class="card card-dashboard card-statistics"> {% if not empty or not hide_empty %}
<div class="card-header"> <div class="card card-dashboard card-statistics">
<i class="icon icon-graph pull-left" aria-hidden="true"></i> <div class="card-header">
{% trans "Statistics" %} <i class="icon icon-graph pull-left" aria-hidden="true"></i>
</div> {% trans "Statistics" %}
<div class="card-body text-center"> </div>
<div id="statistics-carousel" class="carousel slide" data-interval="false"> <div class="card-body text-center">
<div class="carousel-inner"> {% if stats|length > 0 %}
{% for stat in stats %} <div id="statistics-carousel" class="carousel slide" data-interval="false">
<div class="carousel-item{% if forloop.counter == 1 %} active{% endif %}"> <div class="carousel-inner">
<span class="card-title"> {% for stat in stats %}
{% if stat.stat %} <div class="carousel-item{% if forloop.counter == 1 %} active{% endif %}">
{% if stat.type == 'duration' %} <span class="card-title">
{{ stat.stat|duration_string:'m' }} {% if stat.stat %}
{% elif stat.type == 'float' %} {% if stat.type == 'duration' %}
{{ stat.stat|floatformat }} {{ stat.stat|duration_string:'m' }}
{% elif stat.type == 'float' %}
{{ stat.stat|floatformat }}
{% else %}
{{ stat.stat }}
{% endif %}
{% else %} {% else %}
{{ stat.stat }} <em>{% trans "Not enough data" %}</em>
{% endif %} {% endif %}
{% else %} </span>
<em>{% trans "Not enough data" %}</em> <div class="card-text">{{ stat.title }}</div>
{% endif %} </div>
</span> {% endfor %}
<div class="card-text">{{ stat.title }}</div> </div>
</div> <a class="carousel-control-prev" href="#statistics-carousel" role="button" data-slide="prev">
{% endfor %} <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> </div>
<a class="carousel-control-prev" href="#statistics-carousel" role="button" data-slide="prev"> {% else %}
<span class="carousel-control-prev-icon" aria-hidden="true"></span> <span class="card-title"><strong>{% trans "No data yet" %}</strong></span>
<span class="sr-only">{% trans "Previous" %}</span> {% endif %}
</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> </div>
</div> </div>
</div> {% endif %}

View File

@ -9,12 +9,15 @@ from datetime import date, datetime, time
from core import models from core import models
register = template.Library() register = template.Library()
@register.inclusion_tag('cards/diaperchange_last.html') def _hide_empty(context):
def card_diaperchange_last(child): 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. Information about the most recent diaper change.
:param child: an instance of the Child model. :param child: an instance of the Child model.
@ -22,11 +25,18 @@ def card_diaperchange_last(child):
""" """
instance = models.DiaperChange.objects.filter( instance = models.DiaperChange.objects.filter(
child=child).order_by('-time').first() 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') @register.inclusion_tag('cards/diaperchange_types.html', takes_context=True)
def card_diaperchange_types(child, date=None): def card_diaperchange_types(context, child, date=None):
""" """
Creates a break down of wet and solid Diaper Change instances for the past Creates a break down of wet and solid Diaper Change instances for the past
seven days. seven days.
@ -51,6 +61,8 @@ def card_diaperchange_types(child, date=None):
instances = models.DiaperChange.objects.filter(child=child) \ instances = models.DiaperChange.objects.filter(child=child) \
.filter(time__gt=min_date).filter(time__lt=max_date).order_by('-time') .filter(time__gt=min_date).filter(time__lt=max_date).order_by('-time')
empty = len(instances) == 0
for instance in instances: for instance in instances:
key = (max_date - instance.time).days key = (max_date - instance.time).days
if instance.wet: if instance.wet:
@ -65,11 +77,17 @@ def card_diaperchange_types(child, date=None):
stats[key]['wet_pct'] = info['wet'] / total * 100 stats[key]['wet_pct'] = info['wet'] / total * 100
stats[key]['solid_pct'] = info['solid'] / 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') @register.inclusion_tag('cards/feeding_day.html', takes_context=True)
def card_feeding_day(child, date=None): def card_feeding_day(context, child, date=None):
""" """
Filters Feeding instances to get total amount for a specific date. Filters Feeding instances to get total amount for a specific date.
:param child: an instance of the Child model. :param child: an instance of the Child model.
@ -78,6 +96,7 @@ def card_feeding_day(child, date=None):
""" """
if not date: if not date:
date = timezone.localtime().date() date = timezone.localtime().date()
instances = models.Feeding.objects.filter(child=child).filter( instances = models.Feeding.objects.filter(child=child).filter(
start__year=date.year, start__year=date.year,
start__month=date.month, 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]) total = sum([instance.amount for instance in instances if instance.amount])
count = len(instances) 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') @register.inclusion_tag('cards/feeding_last.html', takes_context=True)
def card_feeding_last(child): def card_feeding_last(context, child):
""" """
Information about the most recent feeding. Information about the most recent feeding.
:param child: an instance of the Child model. :param child: an instance of the Child model.
@ -102,11 +128,18 @@ def card_feeding_last(child):
""" """
instance = models.Feeding.objects.filter(child=child) \ instance = models.Feeding.objects.filter(child=child) \
.order_by('-end').first() .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') @register.inclusion_tag('cards/feeding_last_method.html', takes_context=True)
def card_feeding_last_method(child): def card_feeding_last_method(context, child):
""" """
Information about the three most recent feeding methods. Information about the three most recent feeding methods.
:param child: an instance of the Child model. :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) \ instances = models.Feeding.objects.filter(child=child) \
.order_by('-end')[:3] .order_by('-end')[:3]
empty = len(instances) == 0
# Results are reversed for carousel forward/back behavior. # 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') @register.inclusion_tag('cards/sleep_last.html', takes_context=True)
def card_sleep_last(child): def card_sleep_last(context, child):
""" """
Information about the most recent sleep entry. Information about the most recent sleep entry.
:param child: an instance of the Child model. :param child: an instance of the Child model.
@ -127,11 +167,18 @@ def card_sleep_last(child):
""" """
instance = models.Sleep.objects.filter(child=child) \ instance = models.Sleep.objects.filter(child=child) \
.order_by('-end').first() .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') @register.inclusion_tag('cards/sleep_day.html', takes_context=True)
def card_sleep_day(child, date=None): def card_sleep_day(context, child, date=None):
""" """
Filters Sleep instances to get count and total values for a specific date. Filters Sleep instances to get count and total values for a specific date.
:param child: an instance of the Child model. :param child: an instance of the Child model.
@ -147,6 +194,7 @@ def card_sleep_day(child, date=None):
end__year=date.year, end__year=date.year,
end__month=date.month, end__month=date.month,
end__day=date.day) end__day=date.day)
empty = len(instances) == 0
total = timezone.timedelta(seconds=0) total = timezone.timedelta(seconds=0)
for instance in instances: for instance in instances:
@ -161,11 +209,17 @@ def card_sleep_day(child, date=None):
count = len(instances) 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') @register.inclusion_tag('cards/sleep_naps_day.html', takes_context=True)
def card_sleep_naps_day(child, date=None): def card_sleep_naps_day(context, child, date=None):
""" """
Filters Sleep instances categorized as naps and generates statistics for a Filters Sleep instances categorized as naps and generates statistics for a
specific date. specific date.
@ -182,14 +236,18 @@ def card_sleep_naps_day(child, date=None):
end__year=date.year, end__year=date.year,
end__month=date.month, end__month=date.month,
end__day=date.day) end__day=date.day)
empty = len(instances) == 0
return { return {
'type': 'sleep', 'type': 'sleep',
'total': instances.aggregate(Sum('duration'))['duration__sum'], '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') @register.inclusion_tag('cards/statistics.html', takes_context=True)
def card_statistics(child): def card_statistics(context, child):
""" """
Statistics data for all models. Statistics data for all models.
:param child: an instance of the Child model. :param child: an instance of the Child model.
@ -198,45 +256,56 @@ def card_statistics(child):
stats = [] stats = []
changes = _diaperchange_statistics(child) changes = _diaperchange_statistics(child)
stats.append({ if changes:
'type': 'duration',
'stat': changes['btwn_average'],
'title': _('Diaper change frequency')})
feedings = _feeding_statistics(child)
for item in feedings:
stats.append({ stats.append({
'type': 'duration', 'type': 'duration',
'stat': item['btwn_average'], 'stat': changes['btwn_average'],
'title': item['title']}) '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) naps = _nap_statistics(child)
stats.append({ if naps:
'type': 'duration', stats.append({
'stat': naps['average'], 'type': 'duration',
'title': _('Average nap duration')}) 'stat': naps['average'],
stats.append({ 'title': _('Average nap duration')})
'type': 'float', stats.append({
'stat': naps['avg_per_day'], 'type': 'float',
'title': _('Average naps per day')}) 'stat': naps['avg_per_day'],
'title': _('Average naps per day')})
sleep = _sleep_statistics(child) sleep = _sleep_statistics(child)
stats.append({ if sleep:
'type': 'duration', stats.append({
'stat': sleep['average'], 'type': 'duration',
'title': _('Average sleep duration')}) 'stat': sleep['average'],
stats.append({ 'title': _('Average sleep duration')})
'type': 'duration', stats.append({
'stat': sleep['btwn_average'], 'type': 'duration',
'title': _('Average awake duration')}) 'stat': sleep['btwn_average'],
'title': _('Average awake duration')})
weight = _weight_statistics(child) weight = _weight_statistics(child)
stats.append({ if weight:
'type': 'float', stats.append({
'stat': weight['change_weekly'], 'type': 'float',
'title': _('Weight change per week')}) '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): def _diaperchange_statistics(child):
@ -247,6 +316,8 @@ def _diaperchange_statistics(child):
""" """
instances = models.DiaperChange.objects.filter(child=child) \ instances = models.DiaperChange.objects.filter(child=child) \
.order_by('time') .order_by('time')
if len(instances) == 0:
return False
changes = { changes = {
'btwn_total': timezone.timedelta(0), 'btwn_total': timezone.timedelta(0),
'btwn_count': instances.count() - 1, 'btwn_count': instances.count() - 1,
@ -292,6 +363,8 @@ def _feeding_statistics(child):
timespan['btwn_average'] = 0.0 timespan['btwn_average'] = 0.0
instances = models.Feeding.objects.filter(child=child).order_by('start') instances = models.Feeding.objects.filter(child=child).order_by('start')
if len(instances) == 0:
return False
last_instance = None last_instance = None
for instance in instances: for instance in instances:
@ -317,6 +390,8 @@ def _nap_statistics(child):
:returns: a dictionary of statistics. :returns: a dictionary of statistics.
""" """
instances = models.Sleep.naps.filter(child=child).order_by('start') instances = models.Sleep.naps.filter(child=child).order_by('start')
if len(instances) == 0:
return False
naps = { naps = {
'total': instances.aggregate(Sum('duration'))['duration__sum'], 'total': instances.aggregate(Sum('duration'))['duration__sum'],
'count': instances.count(), 'count': instances.count(),
@ -340,6 +415,9 @@ def _sleep_statistics(child):
:returns: a dictionary of statistics. :returns: a dictionary of statistics.
""" """
instances = models.Sleep.objects.filter(child=child).order_by('start') instances = models.Sleep.objects.filter(child=child).order_by('start')
if len(instances) == 0:
return False
sleep = { sleep = {
'total': instances.aggregate(Sum('duration'))['duration__sum'], 'total': instances.aggregate(Sum('duration'))['duration__sum'],
'count': instances.count(), 'count': instances.count(),
@ -371,6 +449,9 @@ def _weight_statistics(child):
weight = {'change_weekly': 0.0} weight = {'change_weekly': 0.0}
instances = models.Weight.objects.filter(child=child).order_by('-date') instances = models.Weight.objects.filter(child=child).order_by('-date')
if len(instances) == 0:
return False
newest = instances.first() newest = instances.first()
oldest = instances.last() oldest = instances.last()
@ -382,8 +463,8 @@ def _weight_statistics(child):
return weight return weight
@register.inclusion_tag('cards/timer_list.html') @register.inclusion_tag('cards/timer_list.html', takes_context=True)
def card_timer_list(child=None): def card_timer_list(context, child=None):
""" """
Filters for currently active Timer instances, optionally by child. Filters for currently active Timer instances, optionally by child.
:param child: an instance of the Child model. :param child: an instance of the Child model.
@ -397,11 +478,18 @@ def card_timer_list(child=None):
).order_by('-start') ).order_by('-start')
else: else:
instances = models.Timer.objects.filter(active=True).order_by('-start') 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') @register.inclusion_tag('cards/tummytime_last.html', takes_context=True)
def card_tummytime_last(child): def card_tummytime_last(context, child):
""" """
Filters the most recent tummy time. Filters the most recent tummy time.
:param child: an instance of the Child model. :param child: an instance of the Child model.
@ -409,11 +497,18 @@ def card_tummytime_last(child):
""" """
instance = models.TummyTime.objects.filter(child=child) \ instance = models.TummyTime.objects.filter(child=child) \
.order_by('-end').first() .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') @register.inclusion_tag('cards/tummytime_day.html', takes_context=True)
def card_tummytime_day(child, date=None): def card_tummytime_day(context, child, date=None):
""" """
Filters Tummy Time instances and generates statistics for a specific date. Filters Tummy Time instances and generates statistics for a specific date.
:param child: an instance of the Child model. :param child: an instance of the Child model.
@ -425,14 +520,20 @@ def card_tummytime_day(child, date=None):
instances = models.TummyTime.objects.filter( instances = models.TummyTime.objects.filter(
child=child, end__year=date.year, end__month=date.month, child=child, end__year=date.year, end__month=date.month,
end__day=date.day).order_by('-end') end__day=date.day).order_by('-end')
empty = len(instances) == 0
stats = { stats = {
'total': timezone.timedelta(seconds=0), 'total': timezone.timedelta(seconds=0),
'count': instances.count() 'count': instances.count()
} }
for instance in instances: for instance in instances:
stats['total'] += timezone.timedelta(seconds=instance.duration.seconds) stats['total'] += timezone.timedelta(seconds=instance.duration.seconds)
return { return {
'type': 'tummytime', 'type': 'tummytime',
'stats': stats, 'stats': stats,
'instances': instances, 'instances': instances,
'last': instances.first()} 'last': instances.first(),
'empty': empty,
'hide_empty': _hide_empty(context)
}

View File

@ -10,6 +10,11 @@ from core import models
from dashboard.templatetags import cards from dashboard.templatetags import cards
class MockUserRequest:
def __init__(self, user):
self.user = user
class TemplateTagsTestCase(TestCase): class TemplateTagsTestCase(TestCase):
fixtures = ['tests.json'] fixtures = ['tests.json']
@ -17,6 +22,7 @@ class TemplateTagsTestCase(TestCase):
def setUpClass(cls): def setUpClass(cls):
super(TemplateTagsTestCase, cls).setUpClass() super(TemplateTagsTestCase, cls).setUpClass()
cls.child = models.Child.objects.first() cls.child = models.Child.objects.first()
cls.context = {'request': MockUserRequest(User.objects.first())}
# Ensure timezone matches the one defined by fixtures. # Ensure timezone matches the one defined by fixtures.
user_timezone = Settings.objects.first().timezone user_timezone = Settings.objects.first().timezone
@ -26,14 +32,26 @@ class TemplateTagsTestCase(TestCase):
date = timezone.localtime().strptime('2017-11-18', '%Y-%m-%d') date = timezone.localtime().strptime('2017-11-18', '%Y-%m-%d')
cls.date = timezone.make_aware(date) 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): 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.assertEqual(data['type'], 'diaperchange')
self.assertFalse(data['empty'])
self.assertFalse(data['hide_empty'])
self.assertIsInstance(data['change'], models.DiaperChange) self.assertIsInstance(data['change'], models.DiaperChange)
self.assertEqual(data['change'], models.DiaperChange.objects.first()) self.assertEqual(data['change'], models.DiaperChange.objects.first())
def test_card_diaperchange_types(self): 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') self.assertEqual(data['type'], 'diaperchange')
stats = { stats = {
0: {'wet_pct': 50.0, 'solid_pct': 50.0, 'solid': 1, 'wet': 1}, 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) self.assertEqual(data['stats'], stats)
def test_card_feeding_day(self): 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.assertEqual(data['type'], 'feeding')
self.assertFalse(data['empty'])
self.assertFalse(data['hide_empty'])
self.assertEqual(data['total'], 2.5) self.assertEqual(data['total'], 2.5)
self.assertEqual(data['count'], 3) self.assertEqual(data['count'], 3)
def test_card_feeding_last(self): 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.assertEqual(data['type'], 'feeding')
self.assertFalse(data['empty'])
self.assertFalse(data['hide_empty'])
self.assertIsInstance(data['feeding'], models.Feeding) self.assertIsInstance(data['feeding'], models.Feeding)
self.assertEqual(data['feeding'], models.Feeding.objects.first()) self.assertEqual(data['feeding'], models.Feeding.objects.first())
def test_card_feeding_last_method(self): 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.assertEqual(data['type'], 'feeding')
self.assertFalse(data['empty'])
self.assertFalse(data['hide_empty'])
self.assertEqual(len(data['feedings']), 3) self.assertEqual(len(data['feedings']), 3)
for feeding in data['feedings']: for feeding in data['feedings']:
self.assertIsInstance(feeding, models.Feeding) self.assertIsInstance(feeding, models.Feeding)
@ -69,25 +93,38 @@ class TemplateTagsTestCase(TestCase):
models.Feeding.objects.first().method) models.Feeding.objects.first().method)
def test_card_sleep_last(self): 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.assertEqual(data['type'], 'sleep')
self.assertFalse(data['empty'])
self.assertFalse(data['hide_empty'])
self.assertIsInstance(data['sleep'], models.Sleep) self.assertIsInstance(data['sleep'], models.Sleep)
self.assertEqual(data['sleep'], models.Sleep.objects.first()) self.assertEqual(data['sleep'], models.Sleep.objects.first())
def test_card_sleep_day(self): def test_card_sleep_last_empty(self):
data = cards.card_sleep_day(self.child, self.date) models.Sleep.objects.all().delete()
data = cards.card_sleep_last(self.context, self.child)
self.assertEqual(data['type'], 'sleep') 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['total'], timezone.timedelta(2, 7200))
self.assertEqual(data['count'], 4) self.assertEqual(data['count'], 4)
def test_card_sleep_naps_day(self): 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.assertEqual(data['type'], 'sleep')
self.assertFalse(data['empty'])
self.assertFalse(data['hide_empty'])
self.assertEqual(data['total'], timezone.timedelta(0, 9000)) self.assertEqual(data['total'], timezone.timedelta(0, 9000))
self.assertEqual(data['count'], 2) self.assertEqual(data['count'], 2)
def test_card_statistics(self): def test_card_statistics(self):
data = cards.card_statistics(self.child) data = cards.card_statistics(self.context, self.child)
stats = [ stats = [
{ {
'title': 'Diaper change frequency', 'title': 'Diaper change frequency',
@ -139,6 +176,8 @@ class TemplateTagsTestCase(TestCase):
] ]
self.assertEqual(data['stats'], stats) self.assertEqual(data['stats'], stats)
self.assertFalse(data['empty'])
self.assertFalse(data['hide_empty'])
def test_card_timer_list(self): def test_card_timer_list(self):
user = User.objects.first() 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.assertIsInstance(data['instances'][0], models.Timer)
self.assertEqual(len(data['instances']), 3) 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.assertIsInstance(data['instances'][0], models.Timer)
self.assertTrue(timers['no_child'] in data['instances']) self.assertTrue(timers['no_child'] in data['instances'])
self.assertTrue(timers['child'] in data['instances']) self.assertTrue(timers['child'] in data['instances'])
self.assertFalse(timers['child_two'] 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.assertIsInstance(data['instances'][0], models.Timer)
self.assertTrue(timers['no_child'] in data['instances']) self.assertTrue(timers['no_child'] in data['instances'])
self.assertTrue(timers['child_two'] in data['instances']) self.assertTrue(timers['child_two'] in data['instances'])
self.assertFalse(timers['child'] in data['instances']) self.assertFalse(timers['child'] in data['instances'])
def test_card_tummytime_last(self): 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.assertEqual(data['type'], 'tummytime')
self.assertFalse(data['empty'])
self.assertFalse(data['hide_empty'])
self.assertIsInstance(data['tummytime'], models.TummyTime) self.assertIsInstance(data['tummytime'], models.TummyTime)
self.assertEqual(data['tummytime'], models.TummyTime.objects.first()) self.assertEqual(data['tummytime'], models.TummyTime.objects.first())
def test_card_tummytime_day(self): 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.assertEqual(data['type'], 'tummytime')
self.assertFalse(data['empty'])
self.assertFalse(data['hide_empty'])
self.assertIsInstance(data['instances'].first(), models.TummyTime) self.assertIsInstance(data['instances'].first(), models.TummyTime)
self.assertIsInstance(data['last'], models.TummyTime) self.assertIsInstance(data['last'], models.TummyTime)
stats = {'count': 3, 'total': timezone.timedelta(0, 300)} stats = {'count': 3, 'total': timezone.timedelta(0, 300)}