mirror of https://github.com/snachodog/mybuddy.git
parent
a2e203a3f8
commit
7b7f17fde6
|
@ -99,7 +99,7 @@ class TemperatureFilter(TimeFieldFilter, TagsFieldFilter):
|
||||||
class TimerFilter(StartEndFieldFilter):
|
class TimerFilter(StartEndFieldFilter):
|
||||||
class Meta(StartEndFieldFilter.Meta):
|
class Meta(StartEndFieldFilter.Meta):
|
||||||
model = models.Timer
|
model = models.Timer
|
||||||
fields = sorted(StartEndFieldFilter.Meta.fields + ["active", "user"])
|
fields = sorted(StartEndFieldFilter.Meta.fields + ["user"])
|
||||||
|
|
||||||
|
|
||||||
class TummyTimeFilter(StartEndFieldFilter, TagsFieldFilter):
|
class TummyTimeFilter(StartEndFieldFilter, TagsFieldFilter):
|
||||||
|
|
|
@ -77,16 +77,12 @@ class CoreModelWithDurationSerializer(CoreModelSerializer):
|
||||||
timer = attrs["timer"]
|
timer = attrs["timer"]
|
||||||
attrs.pop("timer")
|
attrs.pop("timer")
|
||||||
|
|
||||||
if timer.end:
|
|
||||||
end = timer.end
|
|
||||||
else:
|
|
||||||
end = timezone.now()
|
|
||||||
if timer.child:
|
if timer.child:
|
||||||
attrs["child"] = timer.child
|
attrs["child"] = timer.child
|
||||||
|
|
||||||
# Overwrites values provided directly!
|
# Overwrites values provided directly!
|
||||||
attrs["start"] = timer.start
|
attrs["start"] = timer.start
|
||||||
attrs["end"] = end
|
attrs["end"] = timezone.now()
|
||||||
|
|
||||||
# The "child", "start", and "end" field should all be set at this
|
# The "child", "start", and "end" field should all be set at this
|
||||||
# point. If one is not, model validation will fail because they are
|
# point. If one is not, model validation will fail because they are
|
||||||
|
@ -103,7 +99,7 @@ class CoreModelWithDurationSerializer(CoreModelSerializer):
|
||||||
|
|
||||||
# Only actually stop the timer if all validation passed.
|
# Only actually stop the timer if all validation passed.
|
||||||
if timer:
|
if timer:
|
||||||
timer.stop(attrs["end"])
|
timer.stop()
|
||||||
|
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
@ -232,10 +228,11 @@ class TimerSerializer(CoreModelSerializer):
|
||||||
queryset=get_user_model().objects.all(),
|
queryset=get_user_model().objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
|
duration = serializers.DurationField(read_only=True, required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Timer
|
model = models.Timer
|
||||||
fields = ("id", "child", "name", "start", "end", "duration", "active", "user")
|
fields = ("id", "child", "name", "start", "duration", "user")
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
attrs = super(TimerSerializer, self).validate(attrs)
|
attrs = super(TimerSerializer, self).validate(attrs)
|
||||||
|
|
41
api/tests.py
41
api/tests.py
|
@ -47,7 +47,6 @@ class TestBase:
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
timer.refresh_from_db()
|
timer.refresh_from_db()
|
||||||
self.assertTrue(timer.active)
|
|
||||||
child = models.Child.objects.first()
|
child = models.Child.objects.first()
|
||||||
|
|
||||||
self.timer_test_data["child"] = child.id
|
self.timer_test_data["child"] = child.id
|
||||||
|
@ -55,11 +54,9 @@ class TestBase:
|
||||||
self.endpoint, self.timer_test_data, format="json"
|
self.endpoint, self.timer_test_data, format="json"
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
timer.refresh_from_db()
|
|
||||||
self.assertFalse(timer.active)
|
|
||||||
obj = self.model.objects.get(pk=response.data["id"])
|
obj = self.model.objects.get(pk=response.data["id"])
|
||||||
self.assertEqual(obj.start, start)
|
self.assertEqual(obj.start, start)
|
||||||
self.assertEqual(obj.end, timer.end)
|
self.assertIsNotNone(obj.end)
|
||||||
|
|
||||||
def test_post_with_timer_with_child(self):
|
def test_post_with_timer_with_child(self):
|
||||||
if not self.timer_test_data:
|
if not self.timer_test_data:
|
||||||
|
@ -73,12 +70,10 @@ class TestBase:
|
||||||
self.endpoint, self.timer_test_data, format="json"
|
self.endpoint, self.timer_test_data, format="json"
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
timer.refresh_from_db()
|
|
||||||
self.assertFalse(timer.active)
|
|
||||||
obj = self.model.objects.get(pk=response.data["id"])
|
obj = self.model.objects.get(pk=response.data["id"])
|
||||||
self.assertEqual(obj.child, timer.child)
|
self.assertIsNotNone(obj.child)
|
||||||
self.assertEqual(obj.start, start)
|
self.assertEqual(obj.start, start)
|
||||||
self.assertEqual(obj.end, timer.end)
|
self.assertIsNotNone(obj.end)
|
||||||
|
|
||||||
|
|
||||||
class BMIAPITestCase(TestBase.BabyBuddyAPITestCaseBase):
|
class BMIAPITestCase(TestBase.BabyBuddyAPITestCaseBase):
|
||||||
|
@ -703,19 +698,7 @@ class TimerAPITestCase(TestBase.BabyBuddyAPITestCaseBase):
|
||||||
def test_get(self):
|
def test_get(self):
|
||||||
response = self.client.get(self.endpoint)
|
response = self.client.get(self.endpoint)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertEqual(
|
self.assertEqual(response.data["results"][0]["id"], 1)
|
||||||
response.data["results"][0],
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"child": None,
|
|
||||||
"name": "Fake timer",
|
|
||||||
"start": "2017-11-17T23:30:00-05:00",
|
|
||||||
"end": "2017-11-18T00:30:00-05:00",
|
|
||||||
"duration": "01:00:00",
|
|
||||||
"active": False,
|
|
||||||
"user": 1,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_post(self):
|
def test_post(self):
|
||||||
data = {"name": "New fake timer", "user": 1}
|
data = {"name": "New fake timer", "user": 1}
|
||||||
|
@ -743,31 +726,19 @@ class TimerAPITestCase(TestBase.BabyBuddyAPITestCaseBase):
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertEqual(response.data, entry)
|
self.assertEqual(response.data["name"], entry["name"])
|
||||||
|
|
||||||
def test_start_stop_timer(self):
|
def test_start_restart_timer(self):
|
||||||
endpoint = "{}{}/".format(self.endpoint, 1)
|
endpoint = "{}{}/".format(self.endpoint, 1)
|
||||||
response = self.client.get(endpoint)
|
response = self.client.get(endpoint)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertFalse(response.data["active"])
|
|
||||||
|
|
||||||
response = self.client.patch(f"{endpoint}restart/")
|
response = self.client.patch(f"{endpoint}restart/")
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertTrue(response.data["active"])
|
|
||||||
|
|
||||||
# Restart twice is allowed
|
# Restart twice is allowed
|
||||||
response = self.client.patch(f"{endpoint}restart/")
|
response = self.client.patch(f"{endpoint}restart/")
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertTrue(response.data["active"])
|
|
||||||
|
|
||||||
response = self.client.patch(f"{endpoint}stop/")
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
||||||
self.assertFalse(response.data["active"])
|
|
||||||
|
|
||||||
# Stopping twice is allowed, too
|
|
||||||
response = self.client.patch(f"{endpoint}stop/")
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
||||||
self.assertFalse(response.data["active"])
|
|
||||||
|
|
||||||
|
|
||||||
class TummyTimeAPITestCase(TestBase.BabyBuddyAPITestCaseBase):
|
class TummyTimeAPITestCase(TestBase.BabyBuddyAPITestCaseBase):
|
||||||
|
|
|
@ -118,12 +118,6 @@ class TimerViewSet(viewsets.ModelViewSet):
|
||||||
ordering_fields = ("duration", "end", "start")
|
ordering_fields = ("duration", "end", "start")
|
||||||
ordering = "-start"
|
ordering = "-start"
|
||||||
|
|
||||||
@action(detail=True, methods=["patch"])
|
|
||||||
def stop(self, request, pk=None):
|
|
||||||
timer = self.get_object()
|
|
||||||
timer.stop()
|
|
||||||
return Response(self.serializer_class(timer).data)
|
|
||||||
|
|
||||||
@action(detail=True, methods=["patch"])
|
@action(detail=True, methods=["patch"])
|
||||||
def restart(self, request, pk=None):
|
def restart(self, request, pk=None):
|
||||||
timer = self.get_object()
|
timer = self.get_object()
|
||||||
|
|
|
@ -393,9 +393,6 @@
|
||||||
{
|
{
|
||||||
"name": "Fake timer",
|
"name": "Fake timer",
|
||||||
"start": "2017-11-18T04:30:00Z",
|
"start": "2017-11-18T04:30:00Z",
|
||||||
"end": "2017-11-18T05:30:00Z",
|
|
||||||
"duration": "01:00:00",
|
|
||||||
"active": false,
|
|
||||||
"user": 1
|
"user": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -216,8 +216,8 @@ class TemperatureAdmin(ImportExportMixin, ExportActionMixin, admin.ModelAdmin):
|
||||||
|
|
||||||
@admin.register(models.Timer)
|
@admin.register(models.Timer)
|
||||||
class TimerAdmin(admin.ModelAdmin):
|
class TimerAdmin(admin.ModelAdmin):
|
||||||
list_display = ("name", "child", "start", "end", "duration", "active", "user")
|
list_display = ("name", "child", "start", "duration", "user")
|
||||||
list_filter = ("child", "active", "user")
|
list_filter = ("child", "user")
|
||||||
search_fields = ("child__first_name", "child__last_name", "name", "user")
|
search_fields = ("child__first_name", "child__last_name", "name", "user")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ def set_initial_values(kwargs, form_type):
|
||||||
if timer_id:
|
if timer_id:
|
||||||
timer = models.Timer.objects.get(id=timer_id)
|
timer = models.Timer.objects.get(id=timer_id)
|
||||||
kwargs["initial"].update(
|
kwargs["initial"].update(
|
||||||
{"timer": timer, "start": timer.start, "end": timer.end or timezone.now()}
|
{"timer": timer, "start": timer.start, "end": timezone.now()}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Set type and method values for Feeding instance based on last feed.
|
# Set type and method values for Feeding instance based on last feed.
|
||||||
|
@ -83,7 +83,7 @@ class CoreModelForm(forms.ModelForm):
|
||||||
instance = super(CoreModelForm, self).save(commit=False)
|
instance = super(CoreModelForm, self).save(commit=False)
|
||||||
if self.timer_id:
|
if self.timer_id:
|
||||||
timer = models.Timer.objects.get(id=self.timer_id)
|
timer = models.Timer.objects.get(id=self.timer_id)
|
||||||
timer.stop(instance.end)
|
timer.stop()
|
||||||
if commit:
|
if commit:
|
||||||
instance.save()
|
instance.save()
|
||||||
self.save_m2m()
|
self.save_m2m()
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def delete_inactive_timers(apps, schema_editor):
|
||||||
|
from core import models
|
||||||
|
|
||||||
|
for timer in models.Timer.objects.filter(active=False):
|
||||||
|
timer.delete()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("core", "0026_alter_feeding_end_alter_feeding_start_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name="timer",
|
||||||
|
options={
|
||||||
|
"default_permissions": ("view", "add", "change", "delete"),
|
||||||
|
"ordering": ["-start"],
|
||||||
|
"verbose_name": "Timer",
|
||||||
|
"verbose_name_plural": "Timers",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="timer",
|
||||||
|
name="duration",
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="timer",
|
||||||
|
name="end",
|
||||||
|
),
|
||||||
|
migrations.RunPython(
|
||||||
|
delete_inactive_timers, reverse_code=migrations.RunPython.noop
|
||||||
|
),
|
||||||
|
]
|
|
@ -559,12 +559,6 @@ class Timer(models.Model):
|
||||||
start = models.DateTimeField(
|
start = models.DateTimeField(
|
||||||
default=timezone.now, blank=False, verbose_name=_("Start time")
|
default=timezone.now, blank=False, verbose_name=_("Start time")
|
||||||
)
|
)
|
||||||
end = models.DateTimeField(
|
|
||||||
blank=True, editable=False, null=True, verbose_name=_("End time")
|
|
||||||
)
|
|
||||||
duration = models.DurationField(
|
|
||||||
editable=False, null=True, verbose_name=_("Duration")
|
|
||||||
)
|
|
||||||
active = models.BooleanField(default=True, editable=False, verbose_name=_("Active"))
|
active = models.BooleanField(default=True, editable=False, verbose_name=_("Active"))
|
||||||
user = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
"auth.User",
|
"auth.User",
|
||||||
|
@ -577,7 +571,7 @@ class Timer(models.Model):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
default_permissions = ("view", "add", "change", "delete")
|
default_permissions = ("view", "add", "change", "delete")
|
||||||
ordering = ["-active", "-start", "-end"]
|
ordering = ["-start"]
|
||||||
verbose_name = _("Timer")
|
verbose_name = _("Timer")
|
||||||
verbose_name_plural = _("Timers")
|
verbose_name_plural = _("Timers")
|
||||||
|
|
||||||
|
@ -600,42 +594,24 @@ class Timer(models.Model):
|
||||||
return self.user.get_full_name()
|
return self.user.get_full_name()
|
||||||
return self.user.get_username()
|
return self.user.get_username()
|
||||||
|
|
||||||
@classmethod
|
def duration(self):
|
||||||
def from_db(cls, db, field_names, values):
|
return timezone.now() - self.start
|
||||||
instance = super(Timer, cls).from_db(db, field_names, values)
|
|
||||||
if not instance.duration:
|
|
||||||
instance.duration = timezone.now() - instance.start
|
|
||||||
return instance
|
|
||||||
|
|
||||||
def restart(self):
|
def restart(self):
|
||||||
"""Restart the timer."""
|
"""Restart the timer."""
|
||||||
self.start = timezone.now()
|
self.start = timezone.now()
|
||||||
self.end = None
|
|
||||||
self.duration = None
|
|
||||||
self.active = True
|
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def stop(self, end=None):
|
def stop(self):
|
||||||
"""Stop the timer."""
|
"""Stop (delete) the timer."""
|
||||||
if not end:
|
self.delete()
|
||||||
end = timezone.now()
|
|
||||||
self.end = end
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
self.active = self.end is None
|
|
||||||
self.name = self.name or None
|
self.name = self.name or None
|
||||||
if self.start and self.end:
|
|
||||||
self.duration = self.end - self.start
|
|
||||||
else:
|
|
||||||
self.duration = None
|
|
||||||
super(Timer, self).save(*args, **kwargs)
|
super(Timer, self).save(*args, **kwargs)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
validate_time(self.start, "start")
|
validate_time(self.start, "start")
|
||||||
if self.end:
|
|
||||||
validate_time(self.end, "end")
|
|
||||||
validate_duration(self)
|
|
||||||
|
|
||||||
|
|
||||||
class TummyTime(models.Model):
|
class TummyTime(models.Model):
|
||||||
|
|
|
@ -93,13 +93,7 @@ BabyBuddy.Timer = function ($) {
|
||||||
timerElement.find('.timer-minutes').text(parseInt(duration[1]));
|
timerElement.find('.timer-minutes').text(parseInt(duration[1]));
|
||||||
timerElement.find('.timer-seconds').text(parseInt(duration[2]));
|
timerElement.find('.timer-seconds').text(parseInt(duration[2]));
|
||||||
lastUpdate = new Date()
|
lastUpdate = new Date()
|
||||||
|
runIntervalId = setInterval(Timer.tick, 1000);
|
||||||
if (data['active']) {
|
|
||||||
runIntervalId = setInterval(Timer.tick, 1000);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
timerElement.addClass('timer-stopped');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
{% extends 'babybuddy/page.html' %}
|
|
||||||
{% load humanize i18n widget_tweaks %}
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
{% blocktrans %}Delete All Inactive Timers{% endblocktrans %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block breadcrumbs %}
|
|
||||||
<li class="breadcrumb-item"><a href="{% url 'core:timer-list' %}">{% trans "Timers" %}</a></li>
|
|
||||||
<li class="breadcrumb-item active" aria-current="page">{% trans "Delete Inactive" %}</li>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<form role="form" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<h1>
|
|
||||||
{% blocktrans trimmed with number=timer_count|apnumber|intcomma count counter=timer_count %}
|
|
||||||
Are you sure you want to delete {{ number }} inactive timer?
|
|
||||||
{% plural %}
|
|
||||||
Are you sure you want to delete {{ number }} inactive timers?
|
|
||||||
{% endblocktrans %}
|
|
||||||
</h1>
|
|
||||||
<input type="submit" value="{% trans "Delete" %}" class="btn btn-danger" />
|
|
||||||
<a href={% url "babybuddy:root-router" %} class="btn btn-default">{% trans "Cancel" %}</a>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
|
@ -13,7 +13,7 @@
|
||||||
<div class="p-5 mb-4 bg-dark rounded-3 text-center">
|
<div class="p-5 mb-4 bg-dark rounded-3 text-center">
|
||||||
<div class="container-fluid py-1">
|
<div class="container-fluid py-1">
|
||||||
<h1 id="timer-status"
|
<h1 id="timer-status"
|
||||||
class="display-1 {% if not object.active %} timer-stopped{% endif %}">
|
class="display-1">
|
||||||
<span class="timer-hours">{{ object.duration|hours }}</span>h
|
<span class="timer-hours">{{ object.duration|hours }}</span>h
|
||||||
<span class="timer-minutes">{{ object.duration|minutes }}</span>m
|
<span class="timer-minutes">{{ object.duration|minutes }}</span>m
|
||||||
<span class="timer-seconds">{{ object.duration|seconds }}</span>s
|
<span class="timer-seconds">{{ object.duration|seconds }}</span>s
|
||||||
|
@ -27,9 +27,6 @@
|
||||||
|
|
||||||
<p class="lead text-secondary">
|
<p class="lead text-secondary">
|
||||||
{% trans "Started" %} {{ object.start }}
|
{% trans "Started" %} {{ object.start }}
|
||||||
{% if not object.active %}
|
|
||||||
/ {% trans "Stopped" %} {{ object.end }}
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
</p>
|
||||||
<p class="text-muted">
|
<p class="text-muted">
|
||||||
{% blocktrans trimmed with user=object.user_username %}
|
{% blocktrans trimmed with user=object.user_username %}
|
||||||
|
@ -80,14 +77,6 @@
|
||||||
<label class="visually-hidden">{% trans "Restart timer" %}</label>
|
<label class="visually-hidden">{% trans "Restart timer" %}</label>
|
||||||
<button type="submit" class="btn btn-lg btn-secondary"><i class="icon-refresh" aria-hidden="true"></i></button>
|
<button type="submit" class="btn btn-lg btn-secondary"><i class="icon-refresh" aria-hidden="true"></i></button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% if object.active %}
|
|
||||||
<form action="{% url 'core:timer-stop' timer.id %}" role="form" method="post" class="d-inline">
|
|
||||||
{% csrf_token %}
|
|
||||||
<label class="visually-hidden">{% trans "Delete timer" %}</label>
|
|
||||||
<button type="submit" class="btn btn-lg btn-warning"><i class="icon-stop" aria-hidden="true"></i></button>
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -95,9 +84,7 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block javascript %}
|
{% block javascript %}
|
||||||
{% if object.active %}
|
<script type="application/javascript">
|
||||||
<script type="application/javascript">
|
BabyBuddy.Timer.run({{ timer.id }}, 'timer-status');
|
||||||
BabyBuddy.Timer.run({{ timer.id }}, 'timer-status');
|
</script>
|
||||||
</script>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -62,11 +62,4 @@
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% include 'babybuddy/paginator.html' %}
|
{% include 'babybuddy/paginator.html' %}
|
||||||
|
|
||||||
{% if object_list and perms.core.delete_timer %}
|
|
||||||
<a href="{% url 'core:timer-delete-inactive' %}" class="btn btn-sm btn-danger">
|
|
||||||
<i class="icon-delete" aria-hidden="true"></i> {% trans "Delete Inactive Timers" %}
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -49,7 +49,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if timers %}
|
{% if timers %}
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
<h6 class="dropdown-header">{% trans "Active Timers" %}</h6>
|
<h6 class="dropdown-header">{% trans "Timers" %}</h6>
|
||||||
{% for timer in timers %}
|
{% for timer in timers %}
|
||||||
<a class="dropdown-item" href="{% url 'core:timer-detail' timer.id %}">
|
<a class="dropdown-item" href="{% url 'core:timer-detail' timer.id %}">
|
||||||
{{ timer.title_with_child }}
|
{{ timer.title_with_child }}
|
||||||
|
|
|
@ -8,15 +8,14 @@ register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
@register.inclusion_tag("core/timer_nav.html", takes_context=True)
|
@register.inclusion_tag("core/timer_nav.html", takes_context=True)
|
||||||
def timer_nav(context, active=True):
|
def timer_nav(context):
|
||||||
"""
|
"""
|
||||||
Get a list of active Timer instances to include in the nav menu.
|
Get a list of Timer instances to include in the nav menu.
|
||||||
:param context: Django's context data.
|
:param context: Django's context data.
|
||||||
:param active: the state of Timers to filter.
|
|
||||||
:returns: a dictionary with timers data.
|
:returns: a dictionary with timers data.
|
||||||
"""
|
"""
|
||||||
request = context["request"] or None
|
request = context["request"] or None
|
||||||
timers = Timer.objects.filter(active=active)
|
timers = Timer.objects.filter()
|
||||||
children = Child.objects.all()
|
children = Child.objects.all()
|
||||||
perms = context["perms"] or None
|
perms = context["perms"] or None
|
||||||
# The 'next' parameter is currently not used.
|
# The 'next' parameter is currently not used.
|
||||||
|
|
|
@ -122,27 +122,23 @@ class InitialValuesTestCase(FormsTestCaseBase):
|
||||||
self.assertEqual(page.context["form"].initial["type"], f_three.type)
|
self.assertEqual(page.context["form"].initial["type"], f_three.type)
|
||||||
self.assertEqual(page.context["form"].initial["method"], f_three.method)
|
self.assertEqual(page.context["form"].initial["method"], f_three.method)
|
||||||
|
|
||||||
def test_timer_set(self):
|
def test_timer_form_field_set(self):
|
||||||
self.timer.stop()
|
self.timer.stop()
|
||||||
|
|
||||||
page = self.c.get("/sleep/add/")
|
page = self.c.get("/sleep/add/")
|
||||||
self.assertTrue("start" not in page.context["form"].initial)
|
self.assertTrue("start" not in page.context["form"].initial)
|
||||||
self.assertTrue("end" not in page.context["form"].initial)
|
self.assertTrue("end" not in page.context["form"].initial)
|
||||||
|
|
||||||
page = self.c.get("/sleep/add/?timer={}".format(self.timer.id))
|
|
||||||
self.assertEqual(page.context["form"].initial["start"], self.timer.start)
|
|
||||||
self.assertEqual(page.context["form"].initial["end"], self.timer.end)
|
|
||||||
|
|
||||||
def test_timer_stop_on_save(self):
|
def test_timer_stop_on_save(self):
|
||||||
end = timezone.localtime()
|
timer = models.Timer.objects.create(
|
||||||
|
user=self.user, start=timezone.localtime() - timezone.timedelta(minutes=30)
|
||||||
|
)
|
||||||
params = {
|
params = {
|
||||||
"child": self.child.id,
|
"child": self.child.id,
|
||||||
"start": self.localtime_string(self.timer.start),
|
"start": self.localtime_string(self.timer.start),
|
||||||
"end": self.localtime_string(end),
|
"end": self.localtime_string(),
|
||||||
}
|
}
|
||||||
page = self.c.post(
|
page = self.c.post("/sleep/add/?timer={}".format(timer.id), params, follow=True)
|
||||||
"/sleep/add/?timer={}".format(self.timer.id), params, follow=True
|
|
||||||
)
|
|
||||||
self.assertEqual(page.status_code, 200)
|
self.assertEqual(page.status_code, 200)
|
||||||
self.timer.refresh_from_db()
|
self.timer.refresh_from_db()
|
||||||
self.assertFalse(self.timer.active)
|
self.assertFalse(self.timer.active)
|
||||||
|
|
|
@ -270,11 +270,9 @@ class TimerTestCase(TestCase):
|
||||||
)
|
)
|
||||||
self.user = get_user_model().objects.first()
|
self.user = get_user_model().objects.first()
|
||||||
self.named = models.Timer.objects.create(
|
self.named = models.Timer.objects.create(
|
||||||
name="Named", end=timezone.localtime(), user=self.user, child=child
|
name="Named", user=self.user, child=child
|
||||||
)
|
|
||||||
self.unnamed = models.Timer.objects.create(
|
|
||||||
end=timezone.localtime(), user=self.user
|
|
||||||
)
|
)
|
||||||
|
self.unnamed = models.Timer.objects.create(user=self.user)
|
||||||
|
|
||||||
def test_timer_create(self):
|
def test_timer_create(self):
|
||||||
self.assertEqual(self.named, models.Timer.objects.get(name="Named"))
|
self.assertEqual(self.named, models.Timer.objects.get(name="Named"))
|
||||||
|
@ -302,19 +300,7 @@ class TimerTestCase(TestCase):
|
||||||
|
|
||||||
def test_timer_restart(self):
|
def test_timer_restart(self):
|
||||||
self.named.restart()
|
self.named.restart()
|
||||||
self.assertIsNone(self.named.end)
|
self.assertGreaterEqual(timezone.localtime(), self.named.start)
|
||||||
self.assertIsNone(self.named.duration)
|
|
||||||
self.assertTrue(self.named.active)
|
|
||||||
|
|
||||||
def test_timer_stop(self):
|
|
||||||
stop_time = timezone.localtime()
|
|
||||||
self.unnamed.stop(end=stop_time)
|
|
||||||
self.assertEqual(self.unnamed.end, stop_time)
|
|
||||||
self.assertEqual(
|
|
||||||
self.unnamed.duration.seconds,
|
|
||||||
(self.unnamed.end - self.unnamed.start).seconds,
|
|
||||||
)
|
|
||||||
self.assertFalse(self.unnamed.active)
|
|
||||||
|
|
||||||
def test_timer_duration(self):
|
def test_timer_duration(self):
|
||||||
timer = models.Timer.objects.create(user=get_user_model().objects.first())
|
timer = models.Timer.objects.create(user=get_user_model().objects.first())
|
||||||
|
@ -322,9 +308,9 @@ class TimerTestCase(TestCase):
|
||||||
timer.save()
|
timer.save()
|
||||||
timer.refresh_from_db()
|
timer.refresh_from_db()
|
||||||
|
|
||||||
self.assertEqual(timer.duration.seconds, timezone.timedelta(minutes=30).seconds)
|
self.assertEqual(
|
||||||
timer.stop()
|
timer.duration().seconds, timezone.timedelta(minutes=30).seconds
|
||||||
self.assertEqual(timer.duration.seconds, timezone.timedelta(minutes=30).seconds)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TummyTimeTestCase(TestCase):
|
class TummyTimeTestCase(TestCase):
|
||||||
|
|
|
@ -178,28 +178,11 @@ class ViewsTestCase(TestCase):
|
||||||
page = self.c.get("/timers/{}/delete/".format(entry.id))
|
page = self.c.get("/timers/{}/delete/".format(entry.id))
|
||||||
self.assertEqual(page.status_code, 200)
|
self.assertEqual(page.status_code, 200)
|
||||||
|
|
||||||
page = self.c.get("/timers/{}/stop/".format(entry.id))
|
|
||||||
self.assertEqual(page.status_code, 405)
|
|
||||||
page = self.c.post("/timers/{}/stop/".format(entry.id), follow=True)
|
|
||||||
self.assertEqual(page.status_code, 200)
|
|
||||||
|
|
||||||
page = self.c.get("/timers/{}/restart/".format(entry.id))
|
page = self.c.get("/timers/{}/restart/".format(entry.id))
|
||||||
self.assertEqual(page.status_code, 405)
|
self.assertEqual(page.status_code, 405)
|
||||||
page = self.c.post("/timers/{}/restart/".format(entry.id), follow=True)
|
page = self.c.post("/timers/{}/restart/".format(entry.id), follow=True)
|
||||||
self.assertEqual(page.status_code, 200)
|
self.assertEqual(page.status_code, 200)
|
||||||
|
|
||||||
page = self.c.get("/timers/delete-inactive/", follow=True)
|
|
||||||
self.assertEqual(page.status_code, 200)
|
|
||||||
messages = list(page.context["messages"])
|
|
||||||
self.assertEqual(len(messages), 1)
|
|
||||||
self.assertEqual(str(messages[0]), "No inactive timers exist.")
|
|
||||||
|
|
||||||
entry = models.Timer.objects.first()
|
|
||||||
entry.stop()
|
|
||||||
page = self.c.get("/timers/delete-inactive/")
|
|
||||||
self.assertEqual(page.status_code, 200)
|
|
||||||
self.assertEqual(page.context["timer_count"], 1)
|
|
||||||
|
|
||||||
def test_timeline_views(self):
|
def test_timeline_views(self):
|
||||||
child = models.Child.objects.first()
|
child = models.Child.objects.first()
|
||||||
response = self.c.get("/timeline/")
|
response = self.c.get("/timeline/")
|
||||||
|
|
|
@ -72,15 +72,9 @@ urlpatterns = [
|
||||||
path("timers/<int:pk>/", views.TimerDetail.as_view(), name="timer-detail"),
|
path("timers/<int:pk>/", views.TimerDetail.as_view(), name="timer-detail"),
|
||||||
path("timers/<int:pk>/edit/", views.TimerUpdate.as_view(), name="timer-update"),
|
path("timers/<int:pk>/edit/", views.TimerUpdate.as_view(), name="timer-update"),
|
||||||
path("timers/<int:pk>/delete/", views.TimerDelete.as_view(), name="timer-delete"),
|
path("timers/<int:pk>/delete/", views.TimerDelete.as_view(), name="timer-delete"),
|
||||||
path("timers/<int:pk>/stop/", views.TimerStop.as_view(), name="timer-stop"),
|
|
||||||
path(
|
path(
|
||||||
"timers/<int:pk>/restart/", views.TimerRestart.as_view(), name="timer-restart"
|
"timers/<int:pk>/restart/", views.TimerRestart.as_view(), name="timer-restart"
|
||||||
),
|
),
|
||||||
path(
|
|
||||||
"timers/delete-inactive/",
|
|
||||||
views.TimerDeleteInactive.as_view(),
|
|
||||||
name="timer-delete-inactive",
|
|
||||||
),
|
|
||||||
path("tummy-time/", views.TummyTimeList.as_view(), name="tummytime-list"),
|
path("tummy-time/", views.TummyTimeList.as_view(), name="tummytime-list"),
|
||||||
path("tummy-time/add/", views.TummyTimeAdd.as_view(), name="tummytime-add"),
|
path("tummy-time/add/", views.TummyTimeAdd.as_view(), name="tummytime-add"),
|
||||||
path(
|
path(
|
||||||
|
|
|
@ -404,7 +404,7 @@ class TimerList(PermissionRequiredMixin, BabyBuddyFilterView):
|
||||||
template_name = "core/timer_list.html"
|
template_name = "core/timer_list.html"
|
||||||
permission_required = ("core.view_timer",)
|
permission_required = ("core.view_timer",)
|
||||||
paginate_by = 10
|
paginate_by = 10
|
||||||
filterset_fields = ("active", "user")
|
filterset_fields = ("user",)
|
||||||
|
|
||||||
|
|
||||||
class TimerDetail(PermissionRequiredMixin, DetailView):
|
class TimerDetail(PermissionRequiredMixin, DetailView):
|
||||||
|
@ -477,55 +477,12 @@ class TimerRestart(PermissionRequiredMixin, RedirectView):
|
||||||
return reverse("core:timer-detail", kwargs={"pk": kwargs["pk"]})
|
return reverse("core:timer-detail", kwargs={"pk": kwargs["pk"]})
|
||||||
|
|
||||||
|
|
||||||
class TimerStop(PermissionRequiredMixin, SuccessMessageMixin, RedirectView):
|
|
||||||
http_method_names = ["post"]
|
|
||||||
permission_required = ("core.change_timer",)
|
|
||||||
success_message = _("%(timer)s stopped.")
|
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
instance = models.Timer.objects.get(id=kwargs["pk"])
|
|
||||||
instance.stop()
|
|
||||||
messages.success(request, "{} stopped.".format(instance))
|
|
||||||
return super(TimerStop, self).get(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def get_redirect_url(self, *args, **kwargs):
|
|
||||||
return reverse("core:timer-detail", kwargs={"pk": kwargs["pk"]})
|
|
||||||
|
|
||||||
|
|
||||||
class TimerDelete(CoreDeleteView):
|
class TimerDelete(CoreDeleteView):
|
||||||
model = models.Timer
|
model = models.Timer
|
||||||
permission_required = ("core.delete_timer",)
|
permission_required = ("core.delete_timer",)
|
||||||
success_url = reverse_lazy("core:timer-list")
|
success_url = reverse_lazy("core:timer-list")
|
||||||
|
|
||||||
|
|
||||||
class TimerDeleteInactive(PermissionRequiredMixin, SuccessMessageMixin, FormView):
|
|
||||||
permission_required = ("core.delete_timer",)
|
|
||||||
form_class = Form
|
|
||||||
template_name = "core/timer_confirm_delete_inactive.html"
|
|
||||||
success_url = reverse_lazy("core:timer-list")
|
|
||||||
success_message = _("All inactive timers deleted.")
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
kwargs = super().get_context_data(**kwargs)
|
|
||||||
kwargs["timer_count"] = self.get_instances().count()
|
|
||||||
return kwargs
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
# Redirect back to list if there are no inactive timers.
|
|
||||||
if self.get_instances().count() == 0:
|
|
||||||
messages.warning(request, _("No inactive timers exist."))
|
|
||||||
return HttpResponseRedirect(self.success_url)
|
|
||||||
return super().get(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def form_valid(self, form):
|
|
||||||
self.get_instances().delete()
|
|
||||||
return super().form_valid(form)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_instances():
|
|
||||||
return models.Timer.objects.filter(active=False)
|
|
||||||
|
|
||||||
|
|
||||||
class TummyTimeList(PermissionRequiredMixin, BabyBuddyFilterView):
|
class TummyTimeList(PermissionRequiredMixin, BabyBuddyFilterView):
|
||||||
model = models.TummyTime
|
model = models.TummyTime
|
||||||
template_name = "core/tummytime_list.html"
|
template_name = "core/tummytime_list.html"
|
||||||
|
|
|
@ -3,16 +3,16 @@
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
<a href="{% url "core:timer-list" %}">
|
<a href="{% url "core:timer-list" %}">
|
||||||
{% trans "Active Timers" %}
|
{% trans "Timers" %}
|
||||||
</a>
|
</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% with instances|length as count %}
|
{% with instances|length as count %}
|
||||||
{% blocktrans trimmed count counter=count %}
|
{% blocktrans trimmed count counter=count %}
|
||||||
{{ count }} active timer
|
{{ count }} timer
|
||||||
{% plural %}
|
{% plural %}
|
||||||
{{ count }} active timers
|
{{ count }} timers
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -707,10 +707,10 @@ def card_timer_list(context, child=None):
|
||||||
if child:
|
if child:
|
||||||
# Get active instances for the selected child _or_ None (no child).
|
# Get active instances for the selected child _or_ None (no child).
|
||||||
instances = models.Timer.objects.filter(
|
instances = models.Timer.objects.filter(
|
||||||
Q(active=True), Q(child=child) | Q(child=None)
|
Q(child=child) | Q(child=None)
|
||||||
).order_by("-start")
|
).order_by("-start")
|
||||||
else:
|
else:
|
||||||
instances = models.Timer.objects.filter(active=True).order_by("-start")
|
instances = models.Timer.objects.order_by("-start")
|
||||||
empty = len(instances) == 0
|
empty = len(instances) == 0
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -323,7 +323,7 @@ class TemplateTagsTestCase(TestCase):
|
||||||
|
|
||||||
data = cards.card_timer_list(self.context)
|
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"]), 4)
|
||||||
|
|
||||||
data = cards.card_timer_list(self.context, child)
|
data = cards.card_timer_list(self.context, child)
|
||||||
self.assertIsInstance(data["instances"][0], models.Timer)
|
self.assertIsInstance(data["instances"][0], models.Timer)
|
||||||
|
|
22
docs/api.md
22
docs/api.md
|
@ -222,9 +222,7 @@ Note the timer `id` in the response:
|
||||||
"child": 1,
|
"child": 1,
|
||||||
"name": null,
|
"name": null,
|
||||||
"start": "2022-05-28T19:59:40.013914Z",
|
"start": "2022-05-28T19:59:40.013914Z",
|
||||||
"end": null,
|
|
||||||
"duration": null,
|
"duration": null,
|
||||||
"active": true,
|
|
||||||
"user": 1
|
"user": 1
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -252,25 +250,7 @@ Note that `child` and `start` match the timer values (and `end` is auto-populate
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Also note that the timer has been stopped:
|
Also note that the timer has been deleted.
|
||||||
|
|
||||||
```shell
|
|
||||||
curl --location --request GET '[...]/api/timers/5' \
|
|
||||||
--header 'Authorization: Token [...]'
|
|
||||||
```
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"id": 5,
|
|
||||||
"child": 1,
|
|
||||||
"name": null,
|
|
||||||
"start": "2022-05-28T19:59:40.013914Z",
|
|
||||||
"end": "2022-05-28T20:01:13.549099Z",
|
|
||||||
"duration": "00:01:33.535185",
|
|
||||||
"active": false,
|
|
||||||
"user": 1
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Response
|
### Response
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ an overview of all elements related to your child, including:
|
||||||
- Last sleep is the start of the last nap/sleep, and includes the nap duration below.
|
- Last sleep is the start of the last nap/sleep, and includes the nap duration below.
|
||||||
- Last feeding method is a quick view of how the baby was last fed. This is particularly useful for nursing mothers to remember which breast they started with on the previous feed.
|
- Last feeding method is a quick view of how the baby was last fed. This is particularly useful for nursing mothers to remember which breast they started with on the previous feed.
|
||||||
- Today's Feeding is a snapshot of the total numbers of daily feeds.
|
- Today's Feeding is a snapshot of the total numbers of daily feeds.
|
||||||
- Active timers let you know if you have a timer running.
|
- Timers let you know if you have a timer running.
|
||||||
- Statistics is a snapshot of various statistics – these can be scrolled through or select Statistics from the menu bar to see more.
|
- Statistics is a snapshot of various statistics – these can be scrolled through or select Statistics from the menu bar to see more.
|
||||||
- Today's Sleep lists the total number of hours slept for the day.
|
- Today's Sleep lists the total number of hours slept for the day.
|
||||||
- Today's Naps lists the number of naps taken that day in bold and the total nap time below.
|
- Today's Naps lists the number of naps taken that day in bold and the total nap time below.
|
||||||
|
|
|
@ -0,0 +1,206 @@
|
||||||
|
if (typeof jQuery === 'undefined') {
|
||||||
|
throw new Error('Baby Buddy requires jQuery.')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Baby Buddy Namespace
|
||||||
|
*
|
||||||
|
* Default namespace for the Baby Buddy app.
|
||||||
|
*
|
||||||
|
* @type {{}}
|
||||||
|
*/
|
||||||
|
var BabyBuddy = function () {
|
||||||
|
return {};
|
||||||
|
}();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pull to refresh.
|
||||||
|
*
|
||||||
|
* @type {{init: BabyBuddy.PullToRefresh.init, onRefresh: BabyBuddy.PullToRefresh.onRefresh}}
|
||||||
|
*/
|
||||||
|
BabyBuddy.PullToRefresh = function(ptr) {
|
||||||
|
return {
|
||||||
|
init: function () {
|
||||||
|
ptr.init({
|
||||||
|
mainElement: 'body',
|
||||||
|
onRefresh: this.onRefresh
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onRefresh: function() {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}(PullToRefresh);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fix for duplicate form submission from double pressing submit
|
||||||
|
*/
|
||||||
|
function preventDoubleSubmit() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$('form').off("submit", preventDoubleSubmit);
|
||||||
|
$("form").on("submit", function() {
|
||||||
|
$(this).on("submit", preventDoubleSubmit);
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Baby Buddy Timer
|
||||||
|
*
|
||||||
|
* Uses a supplied ID to run a timer. The element using the ID must have
|
||||||
|
* three children with the following classes:
|
||||||
|
* * timer-seconds
|
||||||
|
* * timer-minutes
|
||||||
|
* * timer-hours
|
||||||
|
*/
|
||||||
|
BabyBuddy.Timer = function ($) {
|
||||||
|
var runIntervalId = null;
|
||||||
|
var timerId = null;
|
||||||
|
var timerElement = null;
|
||||||
|
var lastUpdate = new Date();
|
||||||
|
var hidden = null;
|
||||||
|
|
||||||
|
var Timer = {
|
||||||
|
run: function(timer_id, element_id) {
|
||||||
|
timerId = timer_id;
|
||||||
|
timerElement = $('#' + element_id);
|
||||||
|
|
||||||
|
if (timerElement.length === 0) {
|
||||||
|
console.error('BBTimer: Timer element not found.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timerElement.find('.timer-seconds').length === 0
|
||||||
|
|| timerElement.find('.timer-minutes').length === 0
|
||||||
|
|| timerElement.find('.timer-hours').length === 0) {
|
||||||
|
console.error('BBTimer: Element does not contain expected children.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
runIntervalId = setInterval(this.tick, 1000);
|
||||||
|
|
||||||
|
// If the page just came in to view, update the timer data with the
|
||||||
|
// current actual duration. This will (potentially) help mobile
|
||||||
|
// phones that lock with the timer page open.
|
||||||
|
if (typeof document.hidden !== "undefined") {
|
||||||
|
hidden = "hidden";
|
||||||
|
}
|
||||||
|
else if (typeof document.msHidden !== "undefined") {
|
||||||
|
hidden = "msHidden";
|
||||||
|
}
|
||||||
|
else if (typeof document.webkitHidden !== "undefined") {
|
||||||
|
hidden = "webkitHidden";
|
||||||
|
}
|
||||||
|
window.addEventListener('focus', Timer.handleVisibilityChange, false);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleVisibilityChange: function() {
|
||||||
|
if (!document[hidden] && (new Date()) - lastUpdate > 1) {
|
||||||
|
Timer.update();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
tick: function() {
|
||||||
|
var s = timerElement.find('.timer-seconds');
|
||||||
|
var seconds = Number(s.text());
|
||||||
|
if (seconds < 59) {
|
||||||
|
s.text(seconds + 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
s.text(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
var m = timerElement.find('.timer-minutes');
|
||||||
|
var minutes = Number(m.text());
|
||||||
|
if (minutes < 59) {
|
||||||
|
m.text(minutes + 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m.text(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
var h = timerElement.find('.timer-hours');
|
||||||
|
var hours = Number(h.text());
|
||||||
|
h.text(hours + 1);
|
||||||
|
},
|
||||||
|
|
||||||
|
update: function() {
|
||||||
|
$.get('/api/timers/' + timerId + '/', function(data) {
|
||||||
|
if (data && 'duration' in data) {
|
||||||
|
clearInterval(runIntervalId);
|
||||||
|
var duration = data.duration.split(/[\s:.]/)
|
||||||
|
if (duration.length === 5) {
|
||||||
|
duration[0] = parseInt(duration[0]) * 24 + parseInt(duration[1]);
|
||||||
|
duration[1] = duration[2];
|
||||||
|
duration[2] = duration[3];
|
||||||
|
}
|
||||||
|
timerElement.find('.timer-hours').text(parseInt(duration[0]));
|
||||||
|
timerElement.find('.timer-minutes').text(parseInt(duration[1]));
|
||||||
|
timerElement.find('.timer-seconds').text(parseInt(duration[2]));
|
||||||
|
lastUpdate = new Date()
|
||||||
|
runIntervalId = setInterval(Timer.tick, 1000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return Timer;
|
||||||
|
}(jQuery);
|
||||||
|
|
||||||
|
/* Baby Buddy Dashboard
|
||||||
|
*
|
||||||
|
* Provides a "watch" function to update the dashboard at one minute intervals
|
||||||
|
* and/or on visibility state changes.
|
||||||
|
*/
|
||||||
|
BabyBuddy.Dashboard = function ($) {
|
||||||
|
var runIntervalId = null;
|
||||||
|
var dashboardElement = null;
|
||||||
|
var hidden = null;
|
||||||
|
|
||||||
|
var Dashboard = {
|
||||||
|
watch: function(element_id, refresh_rate) {
|
||||||
|
dashboardElement = $('#' + element_id);
|
||||||
|
|
||||||
|
if (dashboardElement.length == 0) {
|
||||||
|
console.error('Baby Buddy: Dashboard element not found.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof document.hidden !== "undefined") {
|
||||||
|
hidden = "hidden";
|
||||||
|
}
|
||||||
|
else if (typeof document.msHidden !== "undefined") {
|
||||||
|
hidden = "msHidden";
|
||||||
|
}
|
||||||
|
else if (typeof document.webkitHidden !== "undefined") {
|
||||||
|
hidden = "webkitHidden";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof window.addEventListener === "undefined" || typeof document.hidden === "undefined") {
|
||||||
|
if (refresh_rate) {
|
||||||
|
runIntervalId = setInterval(this.update, refresh_rate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
window.addEventListener('focus', Dashboard.handleVisibilityChange, false);
|
||||||
|
if (refresh_rate) {
|
||||||
|
runIntervalId = setInterval(Dashboard.handleVisibilityChange, refresh_rate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handleVisibilityChange: function() {
|
||||||
|
if (!document[hidden]) {
|
||||||
|
Dashboard.update();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
update: function() {
|
||||||
|
// TODO: Someday maybe update in place?
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return Dashboard;
|
||||||
|
}(jQuery);
|
Binary file not shown.
|
@ -1 +1,206 @@
|
||||||
if("undefined"==typeof jQuery)throw new Error("Baby Buddy requires jQuery.");var BabyBuddy={};function preventDoubleSubmit(){return!1}BabyBuddy.PullToRefresh=function(e){return{init:function(){e.init({mainElement:"body",onRefresh:this.onRefresh})},onRefresh:function(){window.location.reload()}}}(PullToRefresh),$("form").off("submit",preventDoubleSubmit),$("form").on("submit",function(){$(this).on("submit",preventDoubleSubmit)}),BabyBuddy.Timer=function(e){var n=null,t=null,i=null,d=new Date,r=null,o={run:function(d,u){return t=d,0===(i=e("#"+u)).length?(console.error("BBTimer: Timer element not found."),!1):0===i.find(".timer-seconds").length||0===i.find(".timer-minutes").length||0===i.find(".timer-hours").length?(console.error("BBTimer: Element does not contain expected children."),!1):(n=setInterval(this.tick,1e3),void 0!==document.hidden?r="hidden":void 0!==document.msHidden?r="msHidden":void 0!==document.webkitHidden&&(r="webkitHidden"),void window.addEventListener("focus",o.handleVisibilityChange,!1))},handleVisibilityChange:function(){!document[r]&&new Date-d>1&&o.update()},tick:function(){var e=i.find(".timer-seconds"),n=Number(e.text());if(n<59)e.text(n+1);else{e.text(0);var t=i.find(".timer-minutes"),d=Number(t.text());if(d<59)t.text(d+1);else{t.text(0);var r=i.find(".timer-hours"),o=Number(r.text());r.text(o+1)}}},update:function(){e.get("/api/timers/"+t+"/",function(e){if(e&&"duration"in e){clearInterval(n);var t=e.duration.split(/[\s:.]/);5===t.length&&(t[0]=24*parseInt(t[0])+parseInt(t[1]),t[1]=t[2],t[2]=t[3]),i.find(".timer-hours").text(parseInt(t[0])),i.find(".timer-minutes").text(parseInt(t[1])),i.find(".timer-seconds").text(parseInt(t[2])),d=new Date,e.active?n=setInterval(o.tick,1e3):i.addClass("timer-stopped")}})}};return o}(jQuery),BabyBuddy.Dashboard=function(e){var n=null,t={watch:function(i,d){if(0==e("#"+i).length)return console.error("Baby Buddy: Dashboard element not found."),!1;void 0!==document.hidden?n="hidden":void 0!==document.msHidden?n="msHidden":void 0!==document.webkitHidden&&(n="webkitHidden"),void 0===window.addEventListener||void 0===document.hidden?d&&setInterval(this.update,d):(window.addEventListener("focus",t.handleVisibilityChange,!1),d&&setInterval(t.handleVisibilityChange,d))},handleVisibilityChange:function(){document[n]||t.update()},update:function(){location.reload()}};return t}(jQuery);
|
if (typeof jQuery === 'undefined') {
|
||||||
|
throw new Error('Baby Buddy requires jQuery.')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Baby Buddy Namespace
|
||||||
|
*
|
||||||
|
* Default namespace for the Baby Buddy app.
|
||||||
|
*
|
||||||
|
* @type {{}}
|
||||||
|
*/
|
||||||
|
var BabyBuddy = function () {
|
||||||
|
return {};
|
||||||
|
}();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pull to refresh.
|
||||||
|
*
|
||||||
|
* @type {{init: BabyBuddy.PullToRefresh.init, onRefresh: BabyBuddy.PullToRefresh.onRefresh}}
|
||||||
|
*/
|
||||||
|
BabyBuddy.PullToRefresh = function(ptr) {
|
||||||
|
return {
|
||||||
|
init: function () {
|
||||||
|
ptr.init({
|
||||||
|
mainElement: 'body',
|
||||||
|
onRefresh: this.onRefresh
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onRefresh: function() {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}(PullToRefresh);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fix for duplicate form submission from double pressing submit
|
||||||
|
*/
|
||||||
|
function preventDoubleSubmit() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$('form').off("submit", preventDoubleSubmit);
|
||||||
|
$("form").on("submit", function() {
|
||||||
|
$(this).on("submit", preventDoubleSubmit);
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Baby Buddy Timer
|
||||||
|
*
|
||||||
|
* Uses a supplied ID to run a timer. The element using the ID must have
|
||||||
|
* three children with the following classes:
|
||||||
|
* * timer-seconds
|
||||||
|
* * timer-minutes
|
||||||
|
* * timer-hours
|
||||||
|
*/
|
||||||
|
BabyBuddy.Timer = function ($) {
|
||||||
|
var runIntervalId = null;
|
||||||
|
var timerId = null;
|
||||||
|
var timerElement = null;
|
||||||
|
var lastUpdate = new Date();
|
||||||
|
var hidden = null;
|
||||||
|
|
||||||
|
var Timer = {
|
||||||
|
run: function(timer_id, element_id) {
|
||||||
|
timerId = timer_id;
|
||||||
|
timerElement = $('#' + element_id);
|
||||||
|
|
||||||
|
if (timerElement.length === 0) {
|
||||||
|
console.error('BBTimer: Timer element not found.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timerElement.find('.timer-seconds').length === 0
|
||||||
|
|| timerElement.find('.timer-minutes').length === 0
|
||||||
|
|| timerElement.find('.timer-hours').length === 0) {
|
||||||
|
console.error('BBTimer: Element does not contain expected children.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
runIntervalId = setInterval(this.tick, 1000);
|
||||||
|
|
||||||
|
// If the page just came in to view, update the timer data with the
|
||||||
|
// current actual duration. This will (potentially) help mobile
|
||||||
|
// phones that lock with the timer page open.
|
||||||
|
if (typeof document.hidden !== "undefined") {
|
||||||
|
hidden = "hidden";
|
||||||
|
}
|
||||||
|
else if (typeof document.msHidden !== "undefined") {
|
||||||
|
hidden = "msHidden";
|
||||||
|
}
|
||||||
|
else if (typeof document.webkitHidden !== "undefined") {
|
||||||
|
hidden = "webkitHidden";
|
||||||
|
}
|
||||||
|
window.addEventListener('focus', Timer.handleVisibilityChange, false);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleVisibilityChange: function() {
|
||||||
|
if (!document[hidden] && (new Date()) - lastUpdate > 1) {
|
||||||
|
Timer.update();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
tick: function() {
|
||||||
|
var s = timerElement.find('.timer-seconds');
|
||||||
|
var seconds = Number(s.text());
|
||||||
|
if (seconds < 59) {
|
||||||
|
s.text(seconds + 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
s.text(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
var m = timerElement.find('.timer-minutes');
|
||||||
|
var minutes = Number(m.text());
|
||||||
|
if (minutes < 59) {
|
||||||
|
m.text(minutes + 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m.text(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
var h = timerElement.find('.timer-hours');
|
||||||
|
var hours = Number(h.text());
|
||||||
|
h.text(hours + 1);
|
||||||
|
},
|
||||||
|
|
||||||
|
update: function() {
|
||||||
|
$.get('/api/timers/' + timerId + '/', function(data) {
|
||||||
|
if (data && 'duration' in data) {
|
||||||
|
clearInterval(runIntervalId);
|
||||||
|
var duration = data.duration.split(/[\s:.]/)
|
||||||
|
if (duration.length === 5) {
|
||||||
|
duration[0] = parseInt(duration[0]) * 24 + parseInt(duration[1]);
|
||||||
|
duration[1] = duration[2];
|
||||||
|
duration[2] = duration[3];
|
||||||
|
}
|
||||||
|
timerElement.find('.timer-hours').text(parseInt(duration[0]));
|
||||||
|
timerElement.find('.timer-minutes').text(parseInt(duration[1]));
|
||||||
|
timerElement.find('.timer-seconds').text(parseInt(duration[2]));
|
||||||
|
lastUpdate = new Date()
|
||||||
|
runIntervalId = setInterval(Timer.tick, 1000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return Timer;
|
||||||
|
}(jQuery);
|
||||||
|
|
||||||
|
/* Baby Buddy Dashboard
|
||||||
|
*
|
||||||
|
* Provides a "watch" function to update the dashboard at one minute intervals
|
||||||
|
* and/or on visibility state changes.
|
||||||
|
*/
|
||||||
|
BabyBuddy.Dashboard = function ($) {
|
||||||
|
var runIntervalId = null;
|
||||||
|
var dashboardElement = null;
|
||||||
|
var hidden = null;
|
||||||
|
|
||||||
|
var Dashboard = {
|
||||||
|
watch: function(element_id, refresh_rate) {
|
||||||
|
dashboardElement = $('#' + element_id);
|
||||||
|
|
||||||
|
if (dashboardElement.length == 0) {
|
||||||
|
console.error('Baby Buddy: Dashboard element not found.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof document.hidden !== "undefined") {
|
||||||
|
hidden = "hidden";
|
||||||
|
}
|
||||||
|
else if (typeof document.msHidden !== "undefined") {
|
||||||
|
hidden = "msHidden";
|
||||||
|
}
|
||||||
|
else if (typeof document.webkitHidden !== "undefined") {
|
||||||
|
hidden = "webkitHidden";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof window.addEventListener === "undefined" || typeof document.hidden === "undefined") {
|
||||||
|
if (refresh_rate) {
|
||||||
|
runIntervalId = setInterval(this.update, refresh_rate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
window.addEventListener('focus', Dashboard.handleVisibilityChange, false);
|
||||||
|
if (refresh_rate) {
|
||||||
|
runIntervalId = setInterval(Dashboard.handleVisibilityChange, refresh_rate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handleVisibilityChange: function() {
|
||||||
|
if (!document[hidden]) {
|
||||||
|
Dashboard.update();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
update: function() {
|
||||||
|
// TODO: Someday maybe update in place?
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return Dashboard;
|
||||||
|
}(jQuery);
|
||||||
|
|
Binary file not shown.
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue