diff --git a/api/filters.py b/api/filters.py index b95c15e8..f1b1bcf1 100644 --- a/api/filters.py +++ b/api/filters.py @@ -99,7 +99,7 @@ class TemperatureFilter(TimeFieldFilter, TagsFieldFilter): class TimerFilter(StartEndFieldFilter): class Meta(StartEndFieldFilter.Meta): model = models.Timer - fields = sorted(StartEndFieldFilter.Meta.fields + ["active", "user"]) + fields = sorted(StartEndFieldFilter.Meta.fields + ["user"]) class TummyTimeFilter(StartEndFieldFilter, TagsFieldFilter): diff --git a/api/serializers.py b/api/serializers.py index 23c46fe6..af3982b6 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -77,16 +77,12 @@ class CoreModelWithDurationSerializer(CoreModelSerializer): timer = attrs["timer"] attrs.pop("timer") - if timer.end: - end = timer.end - else: - end = timezone.now() if timer.child: attrs["child"] = timer.child # Overwrites values provided directly! attrs["start"] = timer.start - attrs["end"] = end + attrs["end"] = timezone.now() # The "child", "start", and "end" field should all be set at this # 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. if timer: - timer.stop(attrs["end"]) + timer.stop() return attrs @@ -232,10 +228,11 @@ class TimerSerializer(CoreModelSerializer): queryset=get_user_model().objects.all(), required=False, ) + duration = serializers.DurationField(read_only=True, required=False) class Meta: model = models.Timer - fields = ("id", "child", "name", "start", "end", "duration", "active", "user") + fields = ("id", "child", "name", "start", "duration", "user") def validate(self, attrs): attrs = super(TimerSerializer, self).validate(attrs) diff --git a/api/tests.py b/api/tests.py index ed5f8505..535d1efa 100644 --- a/api/tests.py +++ b/api/tests.py @@ -47,7 +47,6 @@ class TestBase: ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) timer.refresh_from_db() - self.assertTrue(timer.active) child = models.Child.objects.first() self.timer_test_data["child"] = child.id @@ -55,11 +54,9 @@ class TestBase: self.endpoint, self.timer_test_data, format="json" ) 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"]) self.assertEqual(obj.start, start) - self.assertEqual(obj.end, timer.end) + self.assertIsNotNone(obj.end) def test_post_with_timer_with_child(self): if not self.timer_test_data: @@ -73,12 +70,10 @@ class TestBase: self.endpoint, self.timer_test_data, format="json" ) 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"]) - self.assertEqual(obj.child, timer.child) + self.assertIsNotNone(obj.child) self.assertEqual(obj.start, start) - self.assertEqual(obj.end, timer.end) + self.assertIsNotNone(obj.end) class BMIAPITestCase(TestBase.BabyBuddyAPITestCaseBase): @@ -703,19 +698,7 @@ class TimerAPITestCase(TestBase.BabyBuddyAPITestCaseBase): def test_get(self): response = self.client.get(self.endpoint) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual( - 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, - }, - ) + self.assertEqual(response.data["results"][0]["id"], 1) def test_post(self): 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.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) response = self.client.get(endpoint) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertFalse(response.data["active"]) response = self.client.patch(f"{endpoint}restart/") self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertTrue(response.data["active"]) # Restart twice is allowed response = self.client.patch(f"{endpoint}restart/") 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): diff --git a/api/views.py b/api/views.py index edad1ed6..7f513e92 100644 --- a/api/views.py +++ b/api/views.py @@ -118,12 +118,6 @@ class TimerViewSet(viewsets.ModelViewSet): ordering_fields = ("duration", "end", "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"]) def restart(self, request, pk=None): timer = self.get_object() diff --git a/babybuddy/fixtures/tests.json b/babybuddy/fixtures/tests.json index 1eed6953..711e9aa0 100644 --- a/babybuddy/fixtures/tests.json +++ b/babybuddy/fixtures/tests.json @@ -393,9 +393,6 @@ { "name": "Fake timer", "start": "2017-11-18T04:30:00Z", - "end": "2017-11-18T05:30:00Z", - "duration": "01:00:00", - "active": false, "user": 1 } }, diff --git a/core/admin.py b/core/admin.py index 7f2d8757..69550068 100644 --- a/core/admin.py +++ b/core/admin.py @@ -216,8 +216,8 @@ class TemperatureAdmin(ImportExportMixin, ExportActionMixin, admin.ModelAdmin): @admin.register(models.Timer) class TimerAdmin(admin.ModelAdmin): - list_display = ("name", "child", "start", "end", "duration", "active", "user") - list_filter = ("child", "active", "user") + list_display = ("name", "child", "start", "duration", "user") + list_filter = ("child", "user") search_fields = ("child__first_name", "child__last_name", "name", "user") diff --git a/core/forms.py b/core/forms.py index 22c8e65d..06b49602 100644 --- a/core/forms.py +++ b/core/forms.py @@ -44,7 +44,7 @@ def set_initial_values(kwargs, form_type): if timer_id: timer = models.Timer.objects.get(id=timer_id) 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. @@ -83,7 +83,7 @@ class CoreModelForm(forms.ModelForm): instance = super(CoreModelForm, self).save(commit=False) if self.timer_id: timer = models.Timer.objects.get(id=self.timer_id) - timer.stop(instance.end) + timer.stop() if commit: instance.save() self.save_m2m() diff --git a/core/migrations/0027_alter_timer_options_remove_timer_duration_and_more.py b/core/migrations/0027_alter_timer_options_remove_timer_duration_and_more.py new file mode 100644 index 00000000..bed2cb2e --- /dev/null +++ b/core/migrations/0027_alter_timer_options_remove_timer_duration_and_more.py @@ -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 + ), + ] diff --git a/core/models.py b/core/models.py index 0a5658c8..e6b77c25 100644 --- a/core/models.py +++ b/core/models.py @@ -559,12 +559,6 @@ class Timer(models.Model): start = models.DateTimeField( 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")) user = models.ForeignKey( "auth.User", @@ -577,7 +571,7 @@ class Timer(models.Model): class Meta: default_permissions = ("view", "add", "change", "delete") - ordering = ["-active", "-start", "-end"] + ordering = ["-start"] verbose_name = _("Timer") verbose_name_plural = _("Timers") @@ -600,42 +594,24 @@ class Timer(models.Model): return self.user.get_full_name() return self.user.get_username() - @classmethod - def from_db(cls, db, field_names, values): - instance = super(Timer, cls).from_db(db, field_names, values) - if not instance.duration: - instance.duration = timezone.now() - instance.start - return instance + def duration(self): + return timezone.now() - self.start def restart(self): """Restart the timer.""" self.start = timezone.now() - self.end = None - self.duration = None - self.active = True self.save() - def stop(self, end=None): - """Stop the timer.""" - if not end: - end = timezone.now() - self.end = end - self.save() + def stop(self): + """Stop (delete) the timer.""" + self.delete() def save(self, *args, **kwargs): - self.active = self.end is 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) def clean(self): validate_time(self.start, "start") - if self.end: - validate_time(self.end, "end") - validate_duration(self) class TummyTime(models.Model): diff --git a/core/static_src/js/timer.js b/core/static_src/js/timer.js index bb594ca0..d3b8f598 100644 --- a/core/static_src/js/timer.js +++ b/core/static_src/js/timer.js @@ -93,13 +93,7 @@ BabyBuddy.Timer = function ($) { timerElement.find('.timer-minutes').text(parseInt(duration[1])); timerElement.find('.timer-seconds').text(parseInt(duration[2])); lastUpdate = new Date() - - if (data['active']) { - runIntervalId = setInterval(Timer.tick, 1000); - } - else { - timerElement.addClass('timer-stopped'); - } + runIntervalId = setInterval(Timer.tick, 1000); } }); } diff --git a/core/templates/core/timer_confirm_delete_inactive.html b/core/templates/core/timer_confirm_delete_inactive.html deleted file mode 100644 index ad6bc4ba..00000000 --- a/core/templates/core/timer_confirm_delete_inactive.html +++ /dev/null @@ -1,26 +0,0 @@ -{% extends 'babybuddy/page.html' %} -{% load humanize i18n widget_tweaks %} - -{% block title %} - {% blocktrans %}Delete All Inactive Timers{% endblocktrans %} -{% endblock %} - -{% block breadcrumbs %} - - -{% endblock %} - -{% block content %} -
- {% csrf_token %} -

- {% 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 %} -

- - {% trans "Cancel" %} -
-{% endblock %} \ No newline at end of file diff --git a/core/templates/core/timer_detail.html b/core/templates/core/timer_detail.html index fa373b50..e091d7f9 100644 --- a/core/templates/core/timer_detail.html +++ b/core/templates/core/timer_detail.html @@ -13,7 +13,7 @@

+ class="display-1"> {{ object.duration|hours }}h {{ object.duration|minutes }}m {{ object.duration|seconds }}s @@ -27,9 +27,6 @@

{% trans "Started" %} {{ object.start }} - {% if not object.active %} - / {% trans "Stopped" %} {{ object.end }} - {% endif %}

{% blocktrans trimmed with user=object.user_username %} @@ -80,14 +77,6 @@ - - {% if object.active %} -

- {% csrf_token %} - - -
- {% endif %} {% endif %}

@@ -95,9 +84,7 @@ {% endblock %} {% block javascript %} - {% if object.active %} - - {% endif %} + {% endblock %} \ No newline at end of file diff --git a/core/templates/core/timer_list.html b/core/templates/core/timer_list.html index df383838..c9eb433e 100644 --- a/core/templates/core/timer_list.html +++ b/core/templates/core/timer_list.html @@ -62,11 +62,4 @@ {% include 'babybuddy/paginator.html' %} - - {% if object_list and perms.core.delete_timer %} - - {% trans "Delete Inactive Timers" %} - - {% endif %} - {% endblock %} \ No newline at end of file diff --git a/core/templates/core/timer_nav.html b/core/templates/core/timer_nav.html index d439fd8d..ae8109a6 100644 --- a/core/templates/core/timer_nav.html +++ b/core/templates/core/timer_nav.html @@ -49,7 +49,7 @@ {% endif %} {% if timers %} - + {% for timer in timers %} {{ timer.title_with_child }} diff --git a/core/templatetags/timers.py b/core/templatetags/timers.py index 15b48962..2dd48c35 100644 --- a/core/templatetags/timers.py +++ b/core/templatetags/timers.py @@ -8,15 +8,14 @@ register = template.Library() @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 active: the state of Timers to filter. :returns: a dictionary with timers data. """ request = context["request"] or None - timers = Timer.objects.filter(active=active) + timers = Timer.objects.filter() children = Child.objects.all() perms = context["perms"] or None # The 'next' parameter is currently not used. diff --git a/core/tests/tests_forms.py b/core/tests/tests_forms.py index 1fed7a34..9466c588 100644 --- a/core/tests/tests_forms.py +++ b/core/tests/tests_forms.py @@ -122,27 +122,23 @@ class InitialValuesTestCase(FormsTestCaseBase): self.assertEqual(page.context["form"].initial["type"], f_three.type) 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() page = self.c.get("/sleep/add/") self.assertTrue("start" 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): - end = timezone.localtime() + timer = models.Timer.objects.create( + user=self.user, start=timezone.localtime() - timezone.timedelta(minutes=30) + ) params = { "child": self.child.id, "start": self.localtime_string(self.timer.start), - "end": self.localtime_string(end), + "end": self.localtime_string(), } - page = self.c.post( - "/sleep/add/?timer={}".format(self.timer.id), params, follow=True - ) + page = self.c.post("/sleep/add/?timer={}".format(timer.id), params, follow=True) self.assertEqual(page.status_code, 200) self.timer.refresh_from_db() self.assertFalse(self.timer.active) diff --git a/core/tests/tests_models.py b/core/tests/tests_models.py index fd55f308..d413847a 100644 --- a/core/tests/tests_models.py +++ b/core/tests/tests_models.py @@ -270,11 +270,9 @@ class TimerTestCase(TestCase): ) self.user = get_user_model().objects.first() self.named = models.Timer.objects.create( - name="Named", end=timezone.localtime(), user=self.user, child=child - ) - self.unnamed = models.Timer.objects.create( - end=timezone.localtime(), user=self.user + name="Named", user=self.user, child=child ) + self.unnamed = models.Timer.objects.create(user=self.user) def test_timer_create(self): self.assertEqual(self.named, models.Timer.objects.get(name="Named")) @@ -302,19 +300,7 @@ class TimerTestCase(TestCase): def test_timer_restart(self): self.named.restart() - self.assertIsNone(self.named.end) - 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) + self.assertGreaterEqual(timezone.localtime(), self.named.start) def test_timer_duration(self): timer = models.Timer.objects.create(user=get_user_model().objects.first()) @@ -322,9 +308,9 @@ class TimerTestCase(TestCase): timer.save() timer.refresh_from_db() - self.assertEqual(timer.duration.seconds, timezone.timedelta(minutes=30).seconds) - timer.stop() - self.assertEqual(timer.duration.seconds, timezone.timedelta(minutes=30).seconds) + self.assertEqual( + timer.duration().seconds, timezone.timedelta(minutes=30).seconds + ) class TummyTimeTestCase(TestCase): diff --git a/core/tests/tests_views.py b/core/tests/tests_views.py index 3a0ea1a0..53ad6bef 100644 --- a/core/tests/tests_views.py +++ b/core/tests/tests_views.py @@ -178,28 +178,11 @@ class ViewsTestCase(TestCase): page = self.c.get("/timers/{}/delete/".format(entry.id)) 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)) self.assertEqual(page.status_code, 405) page = self.c.post("/timers/{}/restart/".format(entry.id), follow=True) 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): child = models.Child.objects.first() response = self.c.get("/timeline/") diff --git a/core/urls.py b/core/urls.py index c660942c..efbce751 100644 --- a/core/urls.py +++ b/core/urls.py @@ -72,15 +72,9 @@ urlpatterns = [ path("timers//", views.TimerDetail.as_view(), name="timer-detail"), path("timers//edit/", views.TimerUpdate.as_view(), name="timer-update"), path("timers//delete/", views.TimerDelete.as_view(), name="timer-delete"), - path("timers//stop/", views.TimerStop.as_view(), name="timer-stop"), path( "timers//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/add/", views.TummyTimeAdd.as_view(), name="tummytime-add"), path( diff --git a/core/views.py b/core/views.py index 436fd8a1..9bac66d0 100644 --- a/core/views.py +++ b/core/views.py @@ -404,7 +404,7 @@ class TimerList(PermissionRequiredMixin, BabyBuddyFilterView): template_name = "core/timer_list.html" permission_required = ("core.view_timer",) paginate_by = 10 - filterset_fields = ("active", "user") + filterset_fields = ("user",) class TimerDetail(PermissionRequiredMixin, DetailView): @@ -477,55 +477,12 @@ class TimerRestart(PermissionRequiredMixin, RedirectView): 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): model = models.Timer permission_required = ("core.delete_timer",) 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): model = models.TummyTime template_name = "core/tummytime_list.html" diff --git a/dashboard/templates/cards/timer_list.html b/dashboard/templates/cards/timer_list.html index 2fc3567d..eb6f8e63 100644 --- a/dashboard/templates/cards/timer_list.html +++ b/dashboard/templates/cards/timer_list.html @@ -3,16 +3,16 @@ {% block header %} - {% trans "Active Timers" %} + {% trans "Timers" %} {% endblock %} {% block title %} {% with instances|length as count %} {% blocktrans trimmed count counter=count %} - {{ count }} active timer + {{ count }} timer {% plural %} - {{ count }} active timers + {{ count }} timers {% endblocktrans %} {% endwith %} {% endblock %} diff --git a/dashboard/templatetags/cards.py b/dashboard/templatetags/cards.py index 17ef75ac..11e9f14b 100644 --- a/dashboard/templatetags/cards.py +++ b/dashboard/templatetags/cards.py @@ -707,10 +707,10 @@ def card_timer_list(context, child=None): if child: # Get active instances for the selected child _or_ None (no child). instances = models.Timer.objects.filter( - Q(active=True), Q(child=child) | Q(child=None) + Q(child=child) | Q(child=None) ).order_by("-start") else: - instances = models.Timer.objects.filter(active=True).order_by("-start") + instances = models.Timer.objects.order_by("-start") empty = len(instances) == 0 return { diff --git a/dashboard/tests/tests_templatetags.py b/dashboard/tests/tests_templatetags.py index 1c515b30..f26e96c6 100644 --- a/dashboard/tests/tests_templatetags.py +++ b/dashboard/tests/tests_templatetags.py @@ -323,7 +323,7 @@ class TemplateTagsTestCase(TestCase): data = cards.card_timer_list(self.context) 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) self.assertIsInstance(data["instances"][0], models.Timer) diff --git a/docs/api.md b/docs/api.md index 26c0c142..266aafff 100644 --- a/docs/api.md +++ b/docs/api.md @@ -222,9 +222,7 @@ Note the timer `id` in the response: "child": 1, "name": null, "start": "2022-05-28T19:59:40.013914Z", - "end": null, "duration": null, - "active": true, "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: - -```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 -} -``` +Also note that the timer has been deleted. ### Response diff --git a/docs/user-guide/getting-started.md b/docs/user-guide/getting-started.md index 4d0aebad..f113c317 100644 --- a/docs/user-guide/getting-started.md +++ b/docs/user-guide/getting-started.md @@ -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 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. -- 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. - 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. diff --git a/static/babybuddy/js/app.d20500d757a5.js b/static/babybuddy/js/app.d20500d757a5.js new file mode 100644 index 00000000..9c004f47 --- /dev/null +++ b/static/babybuddy/js/app.d20500d757a5.js @@ -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); diff --git a/static/babybuddy/js/app.d20500d757a5.js.gz b/static/babybuddy/js/app.d20500d757a5.js.gz new file mode 100644 index 00000000..3f61214c Binary files /dev/null and b/static/babybuddy/js/app.d20500d757a5.js.gz differ diff --git a/static/babybuddy/js/app.js b/static/babybuddy/js/app.js index 23143cbc..9c004f47 100644 --- a/static/babybuddy/js/app.js +++ b/static/babybuddy/js/app.js @@ -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); \ No newline at end of file +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); diff --git a/static/babybuddy/js/app.js.gz b/static/babybuddy/js/app.js.gz index a26b6700..3f61214c 100644 Binary files a/static/babybuddy/js/app.js.gz and b/static/babybuddy/js/app.js.gz differ diff --git a/static/staticfiles.json b/static/staticfiles.json index fa4647f1..93a51e7f 100644 --- a/static/staticfiles.json +++ b/static/staticfiles.json @@ -1 +1 @@ -{"paths": {"admin/js/vendor/select2/i18n/cs.js": "admin/js/vendor/select2/i18n/cs.4f43e8e7d33a.js", "admin/js/vendor/select2/i18n/en.js": "admin/js/vendor/select2/i18n/en.cf932ba09a98.js", "admin/js/vendor/select2/i18n/ko.js": "admin/js/vendor/select2/i18n/ko.e7be6c20e673.js", "admin/js/vendor/select2/i18n/dsb.js": "admin/js/vendor/select2/i18n/dsb.56372c92d2f1.js", "admin/js/vendor/select2/i18n/lv.js": "admin/js/vendor/select2/i18n/lv.08e62128eac1.js", "admin/js/vendor/select2/i18n/hsb.js": "admin/js/vendor/select2/i18n/hsb.fa3b55265efe.js", "admin/js/vendor/select2/i18n/km.js": "admin/js/vendor/select2/i18n/km.c23089cb06ca.js", "admin/js/vendor/select2/i18n/pl.js": "admin/js/vendor/select2/i18n/pl.6031b4f16452.js", "admin/js/vendor/select2/i18n/de.js": "admin/js/vendor/select2/i18n/de.8a1c222b0204.js", "admin/js/vendor/select2/i18n/sr.js": "admin/js/vendor/select2/i18n/sr.5ed85a48f483.js", "admin/js/vendor/select2/i18n/ru.js": "admin/js/vendor/select2/i18n/ru.934aa95f5b5f.js", "admin/js/vendor/select2/i18n/he.js": "admin/js/vendor/select2/i18n/he.e420ff6cd3ed.js", "admin/js/vendor/select2/i18n/el.js": "admin/js/vendor/select2/i18n/el.27097f071856.js", "admin/js/vendor/select2/i18n/pt-BR.js": "admin/js/vendor/select2/i18n/pt-BR.e1b294433e7f.js", "admin/js/vendor/select2/i18n/uk.js": "admin/js/vendor/select2/i18n/uk.8cede7f4803c.js", "admin/js/vendor/select2/i18n/nl.js": "admin/js/vendor/select2/i18n/nl.997868a37ed8.js", "admin/js/vendor/select2/i18n/sv.js": "admin/js/vendor/select2/i18n/sv.7a9c2f71e777.js", "admin/js/vendor/select2/i18n/mk.js": "admin/js/vendor/select2/i18n/mk.dabbb9087130.js", "admin/js/vendor/select2/i18n/bg.js": "admin/js/vendor/select2/i18n/bg.39b8be30d4f0.js", "admin/js/vendor/select2/i18n/zh-CN.js": "admin/js/vendor/select2/i18n/zh-CN.2cff662ec5f9.js", "admin/js/vendor/select2/i18n/vi.js": "admin/js/vendor/select2/i18n/vi.097a5b75b3e1.js", "admin/js/vendor/select2/i18n/tr.js": "admin/js/vendor/select2/i18n/tr.b5a0643d1545.js", "admin/js/vendor/select2/i18n/tk.js": "admin/js/vendor/select2/i18n/tk.7c572a68c78f.js", "admin/js/vendor/select2/i18n/fr.js": "admin/js/vendor/select2/i18n/fr.05e0542fcfe6.js", "admin/js/vendor/select2/i18n/gl.js": "admin/js/vendor/select2/i18n/gl.d99b1fedaa86.js", "admin/js/vendor/select2/i18n/ps.js": "admin/js/vendor/select2/i18n/ps.38dfa47af9e0.js", "admin/js/vendor/select2/i18n/hr.js": "admin/js/vendor/select2/i18n/hr.a2b092cc1147.js", "admin/js/vendor/select2/i18n/eu.js": "admin/js/vendor/select2/i18n/eu.adfe5c97b72c.js", "admin/js/vendor/select2/i18n/ar.js": "admin/js/vendor/select2/i18n/ar.65aa8e36bf5d.js", "admin/js/vendor/select2/i18n/af.js": "admin/js/vendor/select2/i18n/af.4f6fcd73488c.js", "admin/js/vendor/select2/i18n/zh-TW.js": "admin/js/vendor/select2/i18n/zh-TW.04554a227c2b.js", "admin/js/vendor/select2/i18n/ka.js": "admin/js/vendor/select2/i18n/ka.2083264a54f0.js", "admin/js/vendor/select2/i18n/th.js": "admin/js/vendor/select2/i18n/th.f38c20b0221b.js", "admin/js/vendor/select2/i18n/sr-Cyrl.js": "admin/js/vendor/select2/i18n/sr-Cyrl.f254bb8c4c7c.js", "admin/js/vendor/select2/i18n/lt.js": "admin/js/vendor/select2/i18n/lt.23c7ce903300.js", "admin/js/vendor/select2/i18n/hy.js": "admin/js/vendor/select2/i18n/hy.c7babaeef5a6.js", "admin/js/vendor/select2/i18n/sq.js": "admin/js/vendor/select2/i18n/sq.5636b60d29c9.js", "admin/js/vendor/select2/i18n/et.js": "admin/js/vendor/select2/i18n/et.2b96fd98289d.js", "admin/js/vendor/select2/i18n/sl.js": "admin/js/vendor/select2/i18n/sl.131a78bc0752.js", "admin/js/vendor/select2/i18n/bs.js": "admin/js/vendor/select2/i18n/bs.91624382358e.js", "admin/js/vendor/select2/i18n/fi.js": "admin/js/vendor/select2/i18n/fi.614ec42aa9ba.js", "admin/js/vendor/select2/i18n/hi.js": "admin/js/vendor/select2/i18n/hi.70640d41628f.js", "admin/js/vendor/select2/i18n/ja.js": "admin/js/vendor/select2/i18n/ja.170ae885d74f.js", "admin/js/vendor/select2/i18n/ne.js": "admin/js/vendor/select2/i18n/ne.3d79fd3f08db.js", "admin/js/vendor/select2/i18n/sk.js": "admin/js/vendor/select2/i18n/sk.33d02cef8d11.js", "admin/js/vendor/select2/i18n/da.js": "admin/js/vendor/select2/i18n/da.766346afe4dd.js", "admin/js/vendor/select2/i18n/id.js": "admin/js/vendor/select2/i18n/id.04debded514d.js", "admin/js/vendor/select2/i18n/es.js": "admin/js/vendor/select2/i18n/es.66dbc2652fb1.js", "admin/js/vendor/select2/i18n/it.js": "admin/js/vendor/select2/i18n/it.be4fe8d365b5.js", "admin/js/vendor/select2/i18n/hu.js": "admin/js/vendor/select2/i18n/hu.6ec6039cb8a3.js", "admin/js/vendor/select2/i18n/bn.js": "admin/js/vendor/select2/i18n/bn.6d42b4dd5665.js", "admin/js/vendor/select2/i18n/ms.js": "admin/js/vendor/select2/i18n/ms.4ba82c9a51ce.js", "admin/js/vendor/select2/i18n/fa.js": "admin/js/vendor/select2/i18n/fa.3b5bd1961cfd.js", "admin/js/vendor/select2/i18n/nb.js": "admin/js/vendor/select2/i18n/nb.da2fce143f27.js", "admin/js/vendor/select2/i18n/az.js": "admin/js/vendor/select2/i18n/az.270c257daf81.js", "admin/js/vendor/select2/i18n/ca.js": "admin/js/vendor/select2/i18n/ca.a166b745933a.js", "admin/js/vendor/select2/i18n/ro.js": "admin/js/vendor/select2/i18n/ro.f75cb460ec3b.js", "admin/js/vendor/select2/i18n/pt.js": "admin/js/vendor/select2/i18n/pt.33b4a3b44d43.js", "admin/js/vendor/select2/i18n/is.js": "admin/js/vendor/select2/i18n/is.3ddd9a6a97e9.js", "admin/js/vendor/xregexp/LICENSE.txt": "admin/js/vendor/xregexp/LICENSE.bf79e414957a.txt", "admin/js/vendor/xregexp/xregexp.min.js": "admin/js/vendor/xregexp/xregexp.min.b0439563a5d3.js", "admin/js/vendor/xregexp/xregexp.js": "admin/js/vendor/xregexp/xregexp.efda034b9537.js", "admin/js/vendor/select2/LICENSE.md": "admin/js/vendor/select2/LICENSE.f94142512c91.md", "admin/js/vendor/select2/select2.full.js": "admin/js/vendor/select2/select2.full.c2afdeda3058.js", "admin/js/vendor/select2/select2.full.min.js": "admin/js/vendor/select2/select2.full.min.fcd7500d8e13.js", "admin/js/vendor/jquery/jquery.js": "admin/js/vendor/jquery/jquery.2849239b95f5.js", "admin/js/vendor/jquery/LICENSE.txt": "admin/js/vendor/jquery/LICENSE.de877aa6d744.txt", "admin/js/vendor/jquery/jquery.min.js": "admin/js/vendor/jquery/jquery.min.8fb8fee4fcc3.js", "admin/css/vendor/select2/select2.min.css": "admin/css/vendor/select2/select2.min.9f54e6414f87.css", "admin/css/vendor/select2/select2.css": "admin/css/vendor/select2/select2.a2194c262648.css", "admin/css/vendor/select2/LICENSE-SELECT2.md": "admin/css/vendor/select2/LICENSE-SELECT2.f94142512c91.md", "babybuddy/img/core/child-placeholder.png": "babybuddy/img/core/child-placeholder.7c0a81f0d7f0.png", "rest_framework/docs/js/jquery.json-view.min.js": "rest_framework/docs/js/jquery.json-view.min.b7c2d6981377.js", "rest_framework/docs/js/highlight.pack.js": "rest_framework/docs/js/highlight.pack.479b5f21dcba.js", "rest_framework/docs/js/api.js": "rest_framework/docs/js/api.18a5ba8a1bd8.js", "rest_framework/docs/css/base.css": "rest_framework/docs/css/base.e630f8f4990e.css", "rest_framework/docs/css/jquery.json-view.min.css": "rest_framework/docs/css/jquery.json-view.min.a2e6beeb6710.css", "rest_framework/docs/css/highlight.css": "rest_framework/docs/css/highlight.e0e4d973c6d7.css", "rest_framework/docs/img/grid.png": "rest_framework/docs/img/grid.a4b938cf382b.png", "rest_framework/docs/img/favicon.ico": "rest_framework/docs/img/favicon.5195b4d0f3eb.ico", "admin/js/admin/DateTimeShortcuts.js": "admin/js/admin/DateTimeShortcuts.300591891b2b.js", "admin/js/admin/RelatedObjectLookups.js": "admin/js/admin/RelatedObjectLookups.de5309ac06dd.js", "admin/img/gis/move_vertex_on.svg": "admin/img/gis/move_vertex_on.0047eba25b67.svg", "admin/img/gis/move_vertex_off.svg": "admin/img/gis/move_vertex_off.7a23bf31ef8a.svg", "babybuddy/logo/icon-brand.png": "babybuddy/logo/icon-brand.32cbedf6aee3.png", "babybuddy/logo/logo-sad.png": "babybuddy/logo/logo-sad.47c3d5c2d397.png", "babybuddy/logo/logo.png": "babybuddy/logo/logo.62870041cc83.png", "babybuddy/logo/icon.png": "babybuddy/logo/icon.df80640f0465.png", "babybuddy/js/app.js": "babybuddy/js/app.124d3a444540.js", "babybuddy/js/graph.js": "babybuddy/js/graph.ad87e6353f28.js", "babybuddy/js/tags_editor.js": "babybuddy/js/tags_editor.cf5018f5a70a.js", "babybuddy/css/app.css": "babybuddy/css/app.a8ba77c4f554.css", "babybuddy/root/site.webmanifest": "babybuddy/root/site.c6c4158e40df.webmanifest", "babybuddy/root/favicon.ico": "babybuddy/root/favicon.ee5ebcd40fb9.ico", "babybuddy/root/apple-touch-startup-image.png": "babybuddy/root/apple-touch-startup-image.749726217484.png", "babybuddy/root/favicon.svg": "babybuddy/root/favicon.12fe726d0bac.svg", "babybuddy/root/android-chrome-512x512.png": "babybuddy/root/android-chrome-512x512.e1fd38ad828c.png", "babybuddy/root/mstile-150x150.png": "babybuddy/root/mstile-150x150.08524a406cf2.png", "babybuddy/root/apple-touch-icon.png": "babybuddy/root/apple-touch-icon.bdc75cec89fa.png", "babybuddy/root/safari-pinned-tab.svg": "babybuddy/root/safari-pinned-tab.e8c8ac2f55f5.svg", "babybuddy/root/android-chrome-192x192.png": "babybuddy/root/android-chrome-192x192.ac7d2baba4df.png", "babybuddy/root/browserconfig.xml": "babybuddy/root/browserconfig.84708aade0e5.xml", "babybuddy/font/babybuddy.ttf": "babybuddy/font/babybuddy.b6a356bd9752.ttf", "babybuddy/font/babybuddy.eot": "babybuddy/font/babybuddy.ae7049e685dd.eot", "babybuddy/font/babybuddy.svg": "babybuddy/font/babybuddy.ef799e0dd5c7.svg", "babybuddy/font/babybuddy.woff2": "babybuddy/font/babybuddy.998e9bc52faf.woff2", "babybuddy/font/babybuddy.woff": "babybuddy/font/babybuddy.1913791605fb.woff", "rest_framework/js/prettify-min.js": "rest_framework/js/prettify-min.709bfcc456c6.js", "rest_framework/js/default.js": "rest_framework/js/default.5b08897dbdc3.js", "rest_framework/js/jquery-3.5.1.min.js": "rest_framework/js/jquery-3.5.1.min.dc5e7f18c8d3.js", "rest_framework/js/csrf.js": "rest_framework/js/csrf.969930007329.js", "rest_framework/js/bootstrap.min.js": "rest_framework/js/bootstrap.min.2f34b630ffe3.js", "rest_framework/js/ajax-form.js": "rest_framework/js/ajax-form.0ea6e6052ab5.js", "rest_framework/js/coreapi-0.1.1.js": "rest_framework/js/coreapi-0.1.1.e580e3854595.js", "rest_framework/css/prettify.css": "rest_framework/css/prettify.a987f72342ee.css", "rest_framework/css/font-awesome-4.0.3.css": "rest_framework/css/font-awesome-4.0.3.c1e1ea213abf.css", "rest_framework/css/bootstrap-tweaks.css": "rest_framework/css/bootstrap-tweaks.46ed116b0edd.css", "rest_framework/css/default.css": "rest_framework/css/default.789dfb5732d7.css", "rest_framework/css/bootstrap-theme.min.css": "rest_framework/css/bootstrap-theme.min.1d4b05b397c3.css", "rest_framework/css/bootstrap.min.css.map": "rest_framework/css/bootstrap.min.css.cafbda9c0e9e.map", "rest_framework/css/bootstrap.min.css": "rest_framework/css/bootstrap.min.f17d4516b026.css", "rest_framework/css/bootstrap-theme.min.css.map": "rest_framework/css/bootstrap-theme.min.css.51806092cc05.map", "rest_framework/fonts/fontawesome-webfont.eot": "rest_framework/fonts/fontawesome-webfont.8b27bc96115c.eot", "rest_framework/fonts/glyphicons-halflings-regular.svg": "rest_framework/fonts/glyphicons-halflings-regular.08eda92397ae.svg", "rest_framework/fonts/glyphicons-halflings-regular.woff2": "rest_framework/fonts/glyphicons-halflings-regular.448c34a56d69.woff2", "rest_framework/fonts/glyphicons-halflings-regular.ttf": "rest_framework/fonts/glyphicons-halflings-regular.e18bbf611f2a.ttf", "rest_framework/fonts/fontawesome-webfont.ttf": "rest_framework/fonts/fontawesome-webfont.dcb26c7239d8.ttf", "rest_framework/fonts/fontawesome-webfont.svg": "rest_framework/fonts/fontawesome-webfont.83e37a11f9d7.svg", "rest_framework/fonts/glyphicons-halflings-regular.eot": "rest_framework/fonts/glyphicons-halflings-regular.f4769f9bdb74.eot", "rest_framework/fonts/glyphicons-halflings-regular.woff": "rest_framework/fonts/glyphicons-halflings-regular.fa2772327f55.woff", "rest_framework/fonts/fontawesome-webfont.woff": "rest_framework/fonts/fontawesome-webfont.3293616ec0c6.woff", "rest_framework/img/grid.png": "rest_framework/img/grid.a4b938cf382b.png", "rest_framework/img/glyphicons-halflings.png": "rest_framework/img/glyphicons-halflings.90233c9067e9.png", "rest_framework/img/glyphicons-halflings-white.png": "rest_framework/img/glyphicons-halflings-white.9bbc6e960299.png", "admin/js/calendar.js": "admin/js/calendar.f8a5d055eb33.js", "admin/js/SelectBox.js": "admin/js/SelectBox.8161741c7647.js", "admin/js/urlify.js": "admin/js/urlify.25cc3eac8123.js", "admin/js/popup_response.js": "admin/js/popup_response.c6cc78ea5551.js", "admin/js/autocomplete.js": "admin/js/autocomplete.01591ab27be7.js", "admin/js/collapse.js": "admin/js/collapse.f84e7410290f.js", "admin/js/change_form.js": "admin/js/change_form.9d8ca4f96b75.js", "admin/js/jquery.init.js": "admin/js/jquery.init.b7781a0897fc.js", "admin/js/actions.js": "admin/js/actions.eac7e3441574.js", "admin/js/prepopulate_init.js": "admin/js/prepopulate_init.6cac7f3105b8.js", "admin/js/inlines.js": "admin/js/inlines.22d4d93c00b4.js", "admin/js/prepopulate.js": "admin/js/prepopulate.bd2361dfd64d.js", "admin/js/filters.js": "admin/js/filters.295a9d3d8b6a.js", "admin/js/cancel.js": "admin/js/cancel.ecc4c5ca7b32.js", "admin/js/SelectFilter2.js": "admin/js/SelectFilter2.3f53e33c88d6.js", "admin/js/nav_sidebar.js": "admin/js/nav_sidebar.36a64ecb39ed.js", "admin/js/core.js": "admin/js/core.5d6b384a08b5.js", "admin/css/changelists.css": "admin/css/changelists.ae46354f4e80.css", "admin/css/login.css": "admin/css/login.586129c60a93.css", "admin/css/fonts.css": "admin/css/fonts.168bab448fee.css", "admin/css/responsive.css": "admin/css/responsive.02281633b5f1.css", "admin/css/nav_sidebar.css": "admin/css/nav_sidebar.30423191f399.css", "admin/css/autocomplete.css": "admin/css/autocomplete.4a81fc4242d0.css", "admin/css/base.css": "admin/css/base.01580fff1759.css", "admin/css/widgets.css": "admin/css/widgets.00318bc424d3.css", "admin/css/forms.css": "admin/css/forms.c192d1ec6902.css", "admin/css/dashboard.css": "admin/css/dashboard.be83f13e4369.css", "admin/css/rtl.css": "admin/css/rtl.8473f45bd49b.css", "admin/css/dark_mode.css": "admin/css/dark_mode.4e3d1504ca81.css", "admin/css/responsive_rtl.css": "admin/css/responsive_rtl.e13ae754cceb.css", "admin/fonts/LICENSE.txt": "admin/fonts/LICENSE.d273d63619c9.txt", "admin/fonts/Roboto-Bold-webfont.woff": "admin/fonts/Roboto-Bold-webfont.50d75e48e0a3.woff", "admin/fonts/Roboto-Light-webfont.woff": "admin/fonts/Roboto-Light-webfont.c73eb1ceba33.woff", "admin/fonts/README.txt": "admin/fonts/README.ab99e6b541ea.txt", "admin/fonts/Roboto-Regular-webfont.woff": "admin/fonts/Roboto-Regular-webfont.35b07eb2f871.woff", "admin/img/icon-changelink.svg": "admin/img/icon-changelink.18d2fd706348.svg", "admin/img/icon-no.svg": "admin/img/icon-no.439e821418cd.svg", "admin/img/selector-icons.svg": "admin/img/selector-icons.b4555096cea2.svg", "admin/img/search.svg": "admin/img/search.7cf54ff789c6.svg", "admin/img/icon-yes.svg": "admin/img/icon-yes.d2f9f035226a.svg", "admin/img/icon-addlink.svg": "admin/img/icon-addlink.d519b3bab011.svg", "admin/img/icon-clock.svg": "admin/img/icon-clock.e1d4dfac3f2b.svg", "admin/img/LICENSE": "admin/img/LICENSE.2c54f4e1ca1c", "admin/img/icon-viewlink.svg": "admin/img/icon-viewlink.41eb31f7826e.svg", "admin/img/icon-unknown.svg": "admin/img/icon-unknown.a18cb4398978.svg", "admin/img/README.txt": "admin/img/README.a70711a38d87.txt", "admin/img/icon-calendar.svg": "admin/img/icon-calendar.ac7aea671bea.svg", "admin/img/sorting-icons.svg": "admin/img/sorting-icons.3a097b59f104.svg", "admin/img/tooltag-arrowright.svg": "admin/img/tooltag-arrowright.bbfb788a849e.svg", "admin/img/icon-deletelink.svg": "admin/img/icon-deletelink.564ef9dc3854.svg", "admin/img/tooltag-add.svg": "admin/img/tooltag-add.e59d620a9742.svg", "admin/img/inline-delete.svg": "admin/img/inline-delete.fec1b761f254.svg", "admin/img/icon-alert.svg": "admin/img/icon-alert.034cc7d8a67f.svg", "admin/img/icon-unknown-alt.svg": "admin/img/icon-unknown-alt.81536e128bb6.svg", "admin/img/calendar-icons.svg": "admin/img/calendar-icons.39b290681a8b.svg", "import_export/guess_format.js": "import_export/guess_format.1e929842623e.js", "import_export/action_formats.js": "import_export/action_formats.11c3e817b80a.js", "import_export/import.css": "import_export/import.87299a479910.css"}, "version": "1.0"} \ No newline at end of file +{"paths": {"admin/js/vendor/select2/i18n/id.js": "admin/js/vendor/select2/i18n/id.04debded514d.js", "admin/js/vendor/select2/i18n/dsb.js": "admin/js/vendor/select2/i18n/dsb.56372c92d2f1.js", "admin/js/vendor/select2/i18n/mk.js": "admin/js/vendor/select2/i18n/mk.dabbb9087130.js", "admin/js/vendor/select2/i18n/fr.js": "admin/js/vendor/select2/i18n/fr.05e0542fcfe6.js", "admin/js/vendor/select2/i18n/fi.js": "admin/js/vendor/select2/i18n/fi.614ec42aa9ba.js", "admin/js/vendor/select2/i18n/az.js": "admin/js/vendor/select2/i18n/az.270c257daf81.js", "admin/js/vendor/select2/i18n/af.js": "admin/js/vendor/select2/i18n/af.4f6fcd73488c.js", "admin/js/vendor/select2/i18n/sv.js": "admin/js/vendor/select2/i18n/sv.7a9c2f71e777.js", "admin/js/vendor/select2/i18n/ps.js": "admin/js/vendor/select2/i18n/ps.38dfa47af9e0.js", "admin/js/vendor/select2/i18n/ja.js": "admin/js/vendor/select2/i18n/ja.170ae885d74f.js", "admin/js/vendor/select2/i18n/bn.js": "admin/js/vendor/select2/i18n/bn.6d42b4dd5665.js", "admin/js/vendor/select2/i18n/km.js": "admin/js/vendor/select2/i18n/km.c23089cb06ca.js", "admin/js/vendor/select2/i18n/gl.js": "admin/js/vendor/select2/i18n/gl.d99b1fedaa86.js", "admin/js/vendor/select2/i18n/ne.js": "admin/js/vendor/select2/i18n/ne.3d79fd3f08db.js", "admin/js/vendor/select2/i18n/hi.js": "admin/js/vendor/select2/i18n/hi.70640d41628f.js", "admin/js/vendor/select2/i18n/el.js": "admin/js/vendor/select2/i18n/el.27097f071856.js", "admin/js/vendor/select2/i18n/eu.js": "admin/js/vendor/select2/i18n/eu.adfe5c97b72c.js", "admin/js/vendor/select2/i18n/hsb.js": "admin/js/vendor/select2/i18n/hsb.fa3b55265efe.js", "admin/js/vendor/select2/i18n/sq.js": "admin/js/vendor/select2/i18n/sq.5636b60d29c9.js", "admin/js/vendor/select2/i18n/pt-BR.js": "admin/js/vendor/select2/i18n/pt-BR.e1b294433e7f.js", "admin/js/vendor/select2/i18n/tr.js": "admin/js/vendor/select2/i18n/tr.b5a0643d1545.js", "admin/js/vendor/select2/i18n/zh-CN.js": "admin/js/vendor/select2/i18n/zh-CN.2cff662ec5f9.js", "admin/js/vendor/select2/i18n/ko.js": "admin/js/vendor/select2/i18n/ko.e7be6c20e673.js", "admin/js/vendor/select2/i18n/is.js": "admin/js/vendor/select2/i18n/is.3ddd9a6a97e9.js", "admin/js/vendor/select2/i18n/hr.js": "admin/js/vendor/select2/i18n/hr.a2b092cc1147.js", "admin/js/vendor/select2/i18n/vi.js": "admin/js/vendor/select2/i18n/vi.097a5b75b3e1.js", "admin/js/vendor/select2/i18n/lt.js": "admin/js/vendor/select2/i18n/lt.23c7ce903300.js", "admin/js/vendor/select2/i18n/de.js": "admin/js/vendor/select2/i18n/de.8a1c222b0204.js", "admin/js/vendor/select2/i18n/ka.js": "admin/js/vendor/select2/i18n/ka.2083264a54f0.js", "admin/js/vendor/select2/i18n/et.js": "admin/js/vendor/select2/i18n/et.2b96fd98289d.js", "admin/js/vendor/select2/i18n/zh-TW.js": "admin/js/vendor/select2/i18n/zh-TW.04554a227c2b.js", "admin/js/vendor/select2/i18n/lv.js": "admin/js/vendor/select2/i18n/lv.08e62128eac1.js", "admin/js/vendor/select2/i18n/nb.js": "admin/js/vendor/select2/i18n/nb.da2fce143f27.js", "admin/js/vendor/select2/i18n/hy.js": "admin/js/vendor/select2/i18n/hy.c7babaeef5a6.js", "admin/js/vendor/select2/i18n/th.js": "admin/js/vendor/select2/i18n/th.f38c20b0221b.js", "admin/js/vendor/select2/i18n/ms.js": "admin/js/vendor/select2/i18n/ms.4ba82c9a51ce.js", "admin/js/vendor/select2/i18n/bs.js": "admin/js/vendor/select2/i18n/bs.91624382358e.js", "admin/js/vendor/select2/i18n/he.js": "admin/js/vendor/select2/i18n/he.e420ff6cd3ed.js", "admin/js/vendor/select2/i18n/hu.js": "admin/js/vendor/select2/i18n/hu.6ec6039cb8a3.js", "admin/js/vendor/select2/i18n/bg.js": "admin/js/vendor/select2/i18n/bg.39b8be30d4f0.js", "admin/js/vendor/select2/i18n/en.js": "admin/js/vendor/select2/i18n/en.cf932ba09a98.js", "admin/js/vendor/select2/i18n/ar.js": "admin/js/vendor/select2/i18n/ar.65aa8e36bf5d.js", "admin/js/vendor/select2/i18n/pt.js": "admin/js/vendor/select2/i18n/pt.33b4a3b44d43.js", "admin/js/vendor/select2/i18n/cs.js": "admin/js/vendor/select2/i18n/cs.4f43e8e7d33a.js", "admin/js/vendor/select2/i18n/sk.js": "admin/js/vendor/select2/i18n/sk.33d02cef8d11.js", "admin/js/vendor/select2/i18n/tk.js": "admin/js/vendor/select2/i18n/tk.7c572a68c78f.js", "admin/js/vendor/select2/i18n/da.js": "admin/js/vendor/select2/i18n/da.766346afe4dd.js", "admin/js/vendor/select2/i18n/sl.js": "admin/js/vendor/select2/i18n/sl.131a78bc0752.js", "admin/js/vendor/select2/i18n/ro.js": "admin/js/vendor/select2/i18n/ro.f75cb460ec3b.js", "admin/js/vendor/select2/i18n/ru.js": "admin/js/vendor/select2/i18n/ru.934aa95f5b5f.js", "admin/js/vendor/select2/i18n/fa.js": "admin/js/vendor/select2/i18n/fa.3b5bd1961cfd.js", "admin/js/vendor/select2/i18n/es.js": "admin/js/vendor/select2/i18n/es.66dbc2652fb1.js", "admin/js/vendor/select2/i18n/pl.js": "admin/js/vendor/select2/i18n/pl.6031b4f16452.js", "admin/js/vendor/select2/i18n/it.js": "admin/js/vendor/select2/i18n/it.be4fe8d365b5.js", "admin/js/vendor/select2/i18n/uk.js": "admin/js/vendor/select2/i18n/uk.8cede7f4803c.js", "admin/js/vendor/select2/i18n/sr.js": "admin/js/vendor/select2/i18n/sr.5ed85a48f483.js", "admin/js/vendor/select2/i18n/nl.js": "admin/js/vendor/select2/i18n/nl.997868a37ed8.js", "admin/js/vendor/select2/i18n/ca.js": "admin/js/vendor/select2/i18n/ca.a166b745933a.js", "admin/js/vendor/select2/i18n/sr-Cyrl.js": "admin/js/vendor/select2/i18n/sr-Cyrl.f254bb8c4c7c.js", "admin/js/vendor/jquery/jquery.min.js": "admin/js/vendor/jquery/jquery.min.8fb8fee4fcc3.js", "admin/js/vendor/jquery/LICENSE.txt": "admin/js/vendor/jquery/LICENSE.de877aa6d744.txt", "admin/js/vendor/jquery/jquery.js": "admin/js/vendor/jquery/jquery.2849239b95f5.js", "admin/js/vendor/select2/LICENSE.md": "admin/js/vendor/select2/LICENSE.f94142512c91.md", "admin/js/vendor/select2/select2.full.js": "admin/js/vendor/select2/select2.full.c2afdeda3058.js", "admin/js/vendor/select2/select2.full.min.js": "admin/js/vendor/select2/select2.full.min.fcd7500d8e13.js", "admin/js/vendor/xregexp/xregexp.js": "admin/js/vendor/xregexp/xregexp.efda034b9537.js", "admin/js/vendor/xregexp/LICENSE.txt": "admin/js/vendor/xregexp/LICENSE.bf79e414957a.txt", "admin/js/vendor/xregexp/xregexp.min.js": "admin/js/vendor/xregexp/xregexp.min.b0439563a5d3.js", "admin/css/vendor/select2/select2.css": "admin/css/vendor/select2/select2.a2194c262648.css", "admin/css/vendor/select2/select2.min.css": "admin/css/vendor/select2/select2.min.9f54e6414f87.css", "admin/css/vendor/select2/LICENSE-SELECT2.md": "admin/css/vendor/select2/LICENSE-SELECT2.f94142512c91.md", "babybuddy/img/core/child-placeholder.png": "babybuddy/img/core/child-placeholder.7c0a81f0d7f0.png", "rest_framework/docs/js/jquery.json-view.min.js": "rest_framework/docs/js/jquery.json-view.min.b7c2d6981377.js", "rest_framework/docs/js/highlight.pack.js": "rest_framework/docs/js/highlight.pack.479b5f21dcba.js", "rest_framework/docs/js/api.js": "rest_framework/docs/js/api.c9743eab7a4f.js", "rest_framework/docs/img/favicon.ico": "rest_framework/docs/img/favicon.5195b4d0f3eb.ico", "rest_framework/docs/img/grid.png": "rest_framework/docs/img/grid.a4b938cf382b.png", "rest_framework/docs/css/jquery.json-view.min.css": "rest_framework/docs/css/jquery.json-view.min.a2e6beeb6710.css", "rest_framework/docs/css/highlight.css": "rest_framework/docs/css/highlight.e0e4d973c6d7.css", "rest_framework/docs/css/base.css": "rest_framework/docs/css/base.e630f8f4990e.css", "admin/js/admin/RelatedObjectLookups.js": "admin/js/admin/RelatedObjectLookups.b4d76b6aaf0b.js", "admin/js/admin/DateTimeShortcuts.js": "admin/js/admin/DateTimeShortcuts.5548f99471bf.js", "admin/img/gis/move_vertex_on.svg": "admin/img/gis/move_vertex_on.0047eba25b67.svg", "admin/img/gis/move_vertex_off.svg": "admin/img/gis/move_vertex_off.7a23bf31ef8a.svg", "babybuddy/font/babybuddy.ttf": "babybuddy/font/babybuddy.b6a356bd9752.ttf", "babybuddy/font/babybuddy.eot": "babybuddy/font/babybuddy.ae7049e685dd.eot", "babybuddy/font/babybuddy.woff2": "babybuddy/font/babybuddy.998e9bc52faf.woff2", "babybuddy/font/babybuddy.woff": "babybuddy/font/babybuddy.1913791605fb.woff", "babybuddy/font/babybuddy.svg": "babybuddy/font/babybuddy.ef799e0dd5c7.svg", "babybuddy/logo/icon.png": "babybuddy/logo/icon.df80640f0465.png", "babybuddy/logo/icon-brand.png": "babybuddy/logo/icon-brand.32cbedf6aee3.png", "babybuddy/logo/logo.png": "babybuddy/logo/logo.62870041cc83.png", "babybuddy/logo/logo-sad.png": "babybuddy/logo/logo-sad.47c3d5c2d397.png", "babybuddy/root/favicon.ico": "babybuddy/root/favicon.ee5ebcd40fb9.ico", "babybuddy/root/mstile-150x150.png": "babybuddy/root/mstile-150x150.08524a406cf2.png", "babybuddy/root/favicon.svg": "babybuddy/root/favicon.12fe726d0bac.svg", "babybuddy/root/site.webmanifest": "babybuddy/root/site.c6c4158e40df.webmanifest", "babybuddy/root/browserconfig.xml": "babybuddy/root/browserconfig.84708aade0e5.xml", "babybuddy/root/safari-pinned-tab.svg": "babybuddy/root/safari-pinned-tab.e8c8ac2f55f5.svg", "babybuddy/root/apple-touch-startup-image.png": "babybuddy/root/apple-touch-startup-image.749726217484.png", "babybuddy/root/android-chrome-192x192.png": "babybuddy/root/android-chrome-192x192.ac7d2baba4df.png", "babybuddy/root/android-chrome-512x512.png": "babybuddy/root/android-chrome-512x512.e1fd38ad828c.png", "babybuddy/root/apple-touch-icon.png": "babybuddy/root/apple-touch-icon.bdc75cec89fa.png", "babybuddy/js/vendor.js": "babybuddy/js/vendor.d42b27d2c123.js", "babybuddy/js/tags_editor.js": "babybuddy/js/tags_editor.07f7b7a22846.js", "babybuddy/js/graph.js": "babybuddy/js/graph.5f9828355633.js", "babybuddy/js/app.js": "babybuddy/js/app.d20500d757a5.js", "babybuddy/css/app.css": "babybuddy/css/app.531dd9e6521f.css", "rest_framework/js/jquery-3.5.1.min.js": "rest_framework/js/jquery-3.5.1.min.dc5e7f18c8d3.js", "rest_framework/js/bootstrap.min.js": "rest_framework/js/bootstrap.min.2f34b630ffe3.js", "rest_framework/js/default.js": "rest_framework/js/default.5b08897dbdc3.js", "rest_framework/js/prettify-min.js": "rest_framework/js/prettify-min.709bfcc456c6.js", "rest_framework/js/coreapi-0.1.1.js": "rest_framework/js/coreapi-0.1.1.e580e3854595.js", "rest_framework/js/ajax-form.js": "rest_framework/js/ajax-form.0ea6e6052ab5.js", "rest_framework/js/csrf.js": "rest_framework/js/csrf.969930007329.js", "rest_framework/fonts/glyphicons-halflings-regular.svg": "rest_framework/fonts/glyphicons-halflings-regular.08eda92397ae.svg", "rest_framework/fonts/fontawesome-webfont.eot": "rest_framework/fonts/fontawesome-webfont.8b27bc96115c.eot", "rest_framework/fonts/glyphicons-halflings-regular.woff": "rest_framework/fonts/glyphicons-halflings-regular.fa2772327f55.woff", "rest_framework/fonts/fontawesome-webfont.ttf": "rest_framework/fonts/fontawesome-webfont.dcb26c7239d8.ttf", "rest_framework/fonts/fontawesome-webfont.svg": "rest_framework/fonts/fontawesome-webfont.83e37a11f9d7.svg", "rest_framework/fonts/glyphicons-halflings-regular.ttf": "rest_framework/fonts/glyphicons-halflings-regular.e18bbf611f2a.ttf", "rest_framework/fonts/glyphicons-halflings-regular.woff2": "rest_framework/fonts/glyphicons-halflings-regular.448c34a56d69.woff2", "rest_framework/fonts/glyphicons-halflings-regular.eot": "rest_framework/fonts/glyphicons-halflings-regular.f4769f9bdb74.eot", "rest_framework/fonts/fontawesome-webfont.woff": "rest_framework/fonts/fontawesome-webfont.3293616ec0c6.woff", "rest_framework/img/glyphicons-halflings-white.png": "rest_framework/img/glyphicons-halflings-white.9bbc6e960299.png", "rest_framework/img/glyphicons-halflings.png": "rest_framework/img/glyphicons-halflings.90233c9067e9.png", "rest_framework/img/grid.png": "rest_framework/img/grid.a4b938cf382b.png", "rest_framework/css/bootstrap-theme.min.css": "rest_framework/css/bootstrap-theme.min.66b84a04375e.css", "rest_framework/css/bootstrap.min.css": "rest_framework/css/bootstrap.min.77017a69879a.css", "rest_framework/css/default.css": "rest_framework/css/default.789dfb5732d7.css", "rest_framework/css/font-awesome-4.0.3.css": "rest_framework/css/font-awesome-4.0.3.c1e1ea213abf.css", "rest_framework/css/prettify.css": "rest_framework/css/prettify.a987f72342ee.css", "rest_framework/css/bootstrap-tweaks.css": "rest_framework/css/bootstrap-tweaks.46ed116b0edd.css", "admin/js/calendar.js": "admin/js/calendar.f8a5d055eb33.js", "admin/js/cancel.js": "admin/js/cancel.ecc4c5ca7b32.js", "admin/js/SelectBox.js": "admin/js/SelectBox.8161741c7647.js", "admin/js/change_form.js": "admin/js/change_form.9d8ca4f96b75.js", "admin/js/prepopulate_init.js": "admin/js/prepopulate_init.e056047b7a7e.js", "admin/js/jquery.init.js": "admin/js/jquery.init.b7781a0897fc.js", "admin/js/autocomplete.js": "admin/js/autocomplete.c508b167ab61.js", "admin/js/collapse.js": "admin/js/collapse.f84e7410290f.js", "admin/js/prepopulate.js": "admin/js/prepopulate.bd2361dfd64d.js", "admin/js/inlines.js": "admin/js/inlines.fb1617228dbe.js", "admin/js/nav_sidebar.js": "admin/js/nav_sidebar.36a64ecb39ed.js", "admin/js/popup_response.js": "admin/js/popup_response.c6cc78ea5551.js", "admin/js/SelectFilter2.js": "admin/js/SelectFilter2.d250dcb52a9a.js", "admin/js/actions.js": "admin/js/actions.eac7e3441574.js", "admin/js/core.js": "admin/js/core.5d6b384a08b5.js", "admin/js/urlify.js": "admin/js/urlify.25cc3eac8123.js", "admin/fonts/Roboto-Regular-webfont.woff": "admin/fonts/Roboto-Regular-webfont.35b07eb2f871.woff", "admin/fonts/Roboto-Bold-webfont.woff": "admin/fonts/Roboto-Bold-webfont.50d75e48e0a3.woff", "admin/fonts/LICENSE.txt": "admin/fonts/LICENSE.d273d63619c9.txt", "admin/fonts/Roboto-Light-webfont.woff": "admin/fonts/Roboto-Light-webfont.c73eb1ceba33.woff", "admin/fonts/README.txt": "admin/fonts/README.ab99e6b541ea.txt", "admin/img/icon-unknown.svg": "admin/img/icon-unknown.a18cb4398978.svg", "admin/img/icon-no.svg": "admin/img/icon-no.439e821418cd.svg", "admin/img/selector-icons.svg": "admin/img/selector-icons.b4555096cea2.svg", "admin/img/icon-addlink.svg": "admin/img/icon-addlink.d519b3bab011.svg", "admin/img/icon-changelink.svg": "admin/img/icon-changelink.18d2fd706348.svg", "admin/img/search.svg": "admin/img/search.7cf54ff789c6.svg", "admin/img/icon-alert.svg": "admin/img/icon-alert.034cc7d8a67f.svg", "admin/img/icon-yes.svg": "admin/img/icon-yes.d2f9f035226a.svg", "admin/img/icon-calendar.svg": "admin/img/icon-calendar.ac7aea671bea.svg", "admin/img/calendar-icons.svg": "admin/img/calendar-icons.39b290681a8b.svg", "admin/img/LICENSE": "admin/img/LICENSE.2c54f4e1ca1c", "admin/img/sorting-icons.svg": "admin/img/sorting-icons.3a097b59f104.svg", "admin/img/icon-deletelink.svg": "admin/img/icon-deletelink.564ef9dc3854.svg", "admin/img/icon-clock.svg": "admin/img/icon-clock.e1d4dfac3f2b.svg", "admin/img/tooltag-add.svg": "admin/img/tooltag-add.e59d620a9742.svg", "admin/img/inline-delete.svg": "admin/img/inline-delete.fec1b761f254.svg", "admin/img/icon-unknown-alt.svg": "admin/img/icon-unknown-alt.81536e128bb6.svg", "admin/img/README.txt": "admin/img/README.a70711a38d87.txt", "admin/img/icon-viewlink.svg": "admin/img/icon-viewlink.41eb31f7826e.svg", "admin/img/tooltag-arrowright.svg": "admin/img/tooltag-arrowright.bbfb788a849e.svg", "admin/css/nav_sidebar.css": "admin/css/nav_sidebar.e32d345464bd.css", "admin/css/widgets.css": "admin/css/widgets.694d845b2cb1.css", "admin/css/fonts.css": "admin/css/fonts.168bab448fee.css", "admin/css/forms.css": "admin/css/forms.332ab41432e2.css", "admin/css/dashboard.css": "admin/css/dashboard.be83f13e4369.css", "admin/css/responsive.css": "admin/css/responsive.b9e1565b3609.css", "admin/css/autocomplete.css": "admin/css/autocomplete.4a81fc4242d0.css", "admin/css/responsive_rtl.css": "admin/css/responsive_rtl.e13ae754cceb.css", "admin/css/changelists.css": "admin/css/changelists.cd4dd90ae1a1.css", "admin/css/base.css": "admin/css/base.1f418065fc2c.css", "admin/css/login.css": "admin/css/login.8b76a9f7cbf6.css", "admin/css/rtl.css": "admin/css/rtl.4bc23eb90919.css", "import_export/import.css": "import_export/import.87299a479910.css", "import_export/action_formats.js": "import_export/action_formats.11c3e817b80a.js"}, "version": "1.0"} \ No newline at end of file