Merge pull request #627 from babybuddy/v2

v2
This commit is contained in:
Christopher Charbonneau Wells 2023-05-03 06:38:43 -07:00 committed by GitHub
commit 8545fb360b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
171 changed files with 24210 additions and 31299 deletions

View File

@ -25,4 +25,4 @@ runs:
run: pipenv graph run: pipenv graph
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: 16 node-version: 18

View File

@ -1,9 +1,11 @@
name: "CodeQL" name: "CodeQL"
on: on:
push: push:
branches: [ master ] branches:
- master
pull_request: pull_request:
branches: [ master ] branches:
- master
schedule: schedule:
- cron: '33 0 * * 3' - cron: '33 0 * * 3'
jobs: jobs:

View File

@ -6,7 +6,7 @@ name = "pypi"
[packages] [packages]
boto3 = "*" boto3 = "*"
dj-database-url = "*" dj-database-url = "*"
django = ">=4.0.6" django = "*"
django-axes = "*" django-axes = "*"
django-filter = "*" django-filter = "*"
django-imagekit = "*" django-imagekit = "*"
@ -25,6 +25,7 @@ uritemplate = "*"
whitenoise = "*" whitenoise = "*"
django-taggit = "*" django-taggit = "*"
django-qr-code = "*" django-qr-code = "*"
django-dbsettings = "*"
[dev-packages] [dev-packages]
coveralls = "*" coveralls = "*"
@ -33,3 +34,4 @@ mkdocs = "*"
mkdocs-material = "*" mkdocs-material = "*"
tblib = "*" tblib = "*"
black = "*" black = "*"
pysnooper = "*"

View File

@ -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):

View File

@ -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)

View File

@ -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):

View File

@ -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()

View File

@ -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
} }
}, },

View File

@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# Add custom "short" version of `MONTH_DAY_FORMAT`. This customization will
# only work with the locale format locale specified by this file.
SHORT_MONTH_DAY_FORMAT = "M j"

View File

@ -1,11 +0,0 @@
# -*- coding: utf-8 -*-
from django.conf.locale.pt import formats
# Limit datetime input formats to those support by moment.
formats_supported = list(
filter(
lambda dt_format: not dt_format.startswith("%Y-%m-%d"),
formats.DATETIME_INPUT_FORMATS,
)
)
DATETIME_INPUT_FORMATS = formats_supported

View File

@ -1,9 +0,0 @@
# -*- coding: utf-8 -*-
from django.conf.locale.tr import formats
# Add formats supported by moment.
DATETIME_INPUT_FORMATS = [
"%d.%m.%Y %H:%M:%S",
"%d.%m.%Y %H:%M",
*formats.DATETIME_INPUT_FORMATS,
]

View File

@ -5,68 +5,9 @@ import pytz
from django.conf import settings from django.conf import settings
from django.utils import timezone, translation from django.utils import timezone, translation
from django.conf.locale.en import formats as formats_en_us
from django.conf.locale.en_GB import formats as formats_en_gb
from django.contrib.auth.middleware import RemoteUserMiddleware from django.contrib.auth.middleware import RemoteUserMiddleware
def update_en_us_date_formats():
"""
Update the datetime formats for the en-US locale. This is handled here and
not using `FORMAT_MODULE_PATH` because the processing of format modules
does not allow us to distinguish appropriately between en-US and en-GB
based on user settings.
"""
if settings.USE_24_HOUR_TIME_FORMAT:
formats_en_us.DATETIME_FORMAT = "N j, Y, H:i:s"
custom_input_formats = [
"%m/%d/%Y %H:%M:%S", # '10/25/2006 14:30:59'
"%m/%d/%Y %H:%M", # '10/25/2006 14:30'
]
formats_en_us.SHORT_DATETIME_FORMAT = "m/d/Y G:i:s"
formats_en_us.TIME_FORMAT = "H:i:s"
else:
# These formats are added to support the locale style of Baby Buddy's
# frontend library, which uses momentjs.
custom_input_formats = [
"%m/%d/%Y %I:%M:%S %p", # '10/25/2006 2:30:59 PM'
"%m/%d/%Y %I:%M %p", # '10/25/2006 2:30 PM'
]
# Add custom "short" version of `MONTH_DAY_FORMAT`.
formats_en_us.SHORT_MONTH_DAY_FORMAT = "M j"
# Append all other input formats from the base locale.
formats_en_us.DATETIME_INPUT_FORMATS = (
custom_input_formats + formats_en_us.DATETIME_INPUT_FORMATS
)
def update_en_gb_date_formats():
if settings.USE_24_HOUR_TIME_FORMAT:
# 25 October 2006 14:30:00
formats_en_gb.DATETIME_FORMAT = "j F Y H:i:s"
custom_input_formats = [
"%d/%m/%Y %H:%M:%S", # '25/10/2006 14:30:59'
"%d/%m/%Y %H:%M", # '25/10/2006 14:30'
]
formats_en_gb.SHORT_DATETIME_FORMAT = "d/m/Y H:i"
formats_en_gb.TIME_FORMAT = "H:i"
else:
formats_en_gb.DATETIME_FORMAT = "j F Y f a" # 25 October 2006 2:30 p.m
# These formats are added to support the locale style of Baby Buddy's
# frontend library, which uses momentjs.
custom_input_formats = [
"%d/%m/%Y %I:%M:%S %p", # '25/10/2006 2:30:59 PM'
"%d/%m/%Y %I:%M %p", # '25/10/2006 2:30 PM'
]
# Append all other input formats from the base locale.
formats_en_gb.DATETIME_INPUT_FORMATS = (
custom_input_formats + formats_en_gb.DATETIME_INPUT_FORMATS
)
class UserLanguageMiddleware: class UserLanguageMiddleware:
""" """
Customizes settings based on user language setting. Customizes settings based on user language setting.
@ -85,11 +26,6 @@ class UserLanguageMiddleware:
language = settings.LANGUAGE_CODE language = settings.LANGUAGE_CODE
if language: if language:
if language == "en-US":
update_en_us_date_formats()
elif language == "en-GB":
update_en_gb_date_formats()
# Set the language before generating the response. # Set the language before generating the response.
translation.activate(language) translation.activate(language)
@ -104,10 +40,9 @@ class UserLanguageMiddleware:
class UserTimezoneMiddleware: class UserTimezoneMiddleware:
""" """
Sets the timezone based on a user specific setting that falls back on Sets the timezone based on a user specific setting. This middleware must run after
`settings.TIME_ZONE`. This middleware must run after `django.contrib.auth.middleware.AuthenticationMiddleware` because it uses the
`django.contrib.auth.middleware.AuthenticationMiddleware` because it uses request.user object.
the request.user object.
""" """
def __init__(self, get_response): def __init__(self, get_response):

View File

@ -37,6 +37,7 @@ INSTALLED_APPS = [
"storages", "storages",
"import_export", "import_export",
"qr_code", "qr_code",
"dbsettings",
"django.contrib.admin", "django.contrib.admin",
"django.contrib.auth", "django.contrib.auth",
"django.contrib.contenttypes", "django.contrib.contenttypes",
@ -78,7 +79,7 @@ ROOT_URLCONF = "babybuddy.urls"
TEMPLATES = [ TEMPLATES = [
{ {
"BACKEND": "django.template.backends.django.DjangoTemplates", "BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": ["babybuddy/templates/error"], "DIRS": ["babybuddy/templates", "babybuddy/templates/error"],
"APP_DIRS": True, "APP_DIRS": True,
"OPTIONS": { "OPTIONS": {
"context_processors": [ "context_processors": [
@ -158,7 +159,7 @@ if REVERSE_PROXY_AUTH:
USE_TZ = True USE_TZ = True
TIME_ZONE = os.environ.get("TIME_ZONE") or "UTC" TIME_ZONE = "UTC"
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/4.0/topics/i18n/ # https://docs.djangoproject.com/en/4.0/topics/i18n/
@ -201,16 +202,6 @@ USE_L10N = True
FORMAT_MODULE_PATH = ["babybuddy.formats"] FORMAT_MODULE_PATH = ["babybuddy.formats"]
# Custom setting that can be used to override the locale-based time set by
# USE_L10N _for specific locales_ to use 24-hour format. In order for this to
# work with a given locale it must be set at the FORMAT_MODULE_PATH with
# conditionals on this setting. See babybuddy/forms/en/formats.py for an example
# implementation for the English locale.
USE_24_HOUR_TIME_FORMAT = bool(
strtobool(os.environ.get("USE_24_HOUR_TIME_FORMAT") or "False")
)
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.0/howto/static-files/ # https://docs.djangoproject.com/en/4.0/howto/static-files/
@ -364,7 +355,5 @@ DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
BABY_BUDDY = { BABY_BUDDY = {
"ALLOW_UPLOADS": bool(strtobool(os.environ.get("ALLOW_UPLOADS") or "True")), "ALLOW_UPLOADS": bool(strtobool(os.environ.get("ALLOW_UPLOADS") or "True")),
"NAP_START_MAX": os.environ.get("NAP_START_MAX") or "18:00",
"NAP_START_MIN": os.environ.get("NAP_START_MIN") or "06:00",
"READ_ONLY_GROUP_NAME": "read_only", "READ_ONLY_GROUP_NAME": "read_only",
} }

View File

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
from datetime import time
from django.utils.translation import gettext_lazy as _
import dbsettings
from .widgets import TimeInput
class NapSettings(dbsettings.Group):
nap_start_min = dbsettings.TimeValue(
default=time(6),
description=_("Default minimum nap start time"),
help_text=_(
"The minimum default time that a sleep entry is consider a nap. If set the "
"nap property will be preselected if the start time is within the bounds."
),
widget=TimeInput,
)
nap_start_max = dbsettings.TimeValue(
default=time(18),
description=_("Default maximum nap start time"),
help_text=_(
"The maximum default time that a sleep entry is consider a nap. If set the "
"nap property will be preselected if the start time is within the bounds."
),
widget=TimeInput,
)

View File

@ -1,9 +1,6 @@
if (typeof jQuery === 'undefined') { if (typeof jQuery === 'undefined') {
throw new Error('Baby Buddy requires jQuery.') throw new Error('Baby Buddy requires jQuery.')
} }
if (typeof moment === 'undefined') {
throw new Error('Baby Buddy requires moment.js.')
}
/** /**
* Baby Buddy Namespace * Baby Buddy Namespace
@ -16,42 +13,6 @@ var BabyBuddy = function () {
return {}; return {};
}(); }();
/**
* Datetime Picker.
*
* Provides modifications and defaults for the base datetime picker widget.
*
* @type {{init: BabyBuddy.DatetimePicker.init}}
*/
BabyBuddy.DatetimePicker = function ($, moment) {
return {
init: function (element, options) {
var defaultOptions = {
buttons: { showToday: true, showClose: true },
defaultDate: 'now',
focusOnShow: false,
format: 'L LT',
ignoreReadonly: true,
locale: moment.locale(),
useCurrent: false,
icons: {
time: 'icon-clock',
date: 'icon-calendar',
up: 'icon-arrow-up',
down: 'icon-arrow-down',
previous: 'icon-angle-circled-left',
next: 'icon-angle-circled-right',
today: 'icon-today',
clear: 'icon-delete',
close: 'icon-cancel'
},
viewMode: 'times',
};
element.datetimepicker($.extend(defaultOptions, options));
}
};
}(jQuery, moment);
/** /**
* Pull to refresh. * Pull to refresh.
* *

View File

@ -174,7 +174,7 @@
this.apiTagsUrl = widget.getAttribute('data-tags-url'); this.apiTagsUrl = widget.getAttribute('data-tags-url');
this.createTagInputs = widget.querySelector('.create-tag-inputs'); this.createTagInputs = widget.querySelector('.create-tag-inputs');
this.addTagInput = this.createTagInputs.querySelector('input[type="text"]'); this.addTagInput = this.createTagInputs.querySelector('input[type="text"]');
this.addTagButton = this.createTagInputs.querySelector('.btn-add-new-tag'); this.addTagButton = this.createTagInputs.querySelector('#add-tag');
this.addTagInput.value = ""; this.addTagInput.value = "";

View File

@ -1,3 +0,0 @@
@import '../../../node_modules/bootstrap/scss/functions';
// Baby Buddy site-wide custom functions.

View File

@ -1,3 +0,0 @@
@import '../../../node_modules/bootstrap/scss/mixins';
// Baby Buddy site-wide custom mixins.

View File

@ -1,5 +1,136 @@
@import 'functions'; @use 'sass:color';
@import '../../../node_modules/bootstrap/scss/variables';
@import 'themes/blueorange';
// Baby Buddy site-wide variables. // Theme variables.
// Color system
$white: #fff;
$gray-100: #f8f9fa;
$gray-200: #e9ecef;
$gray-300: #dee2e6;
$gray-400: #ced4da;
$gray-500: #adb5bd;
$gray-600: #6c757d;
$gray-700: #495057;
$gray-800: #343a40;
$gray-900: #212529;
$black: #000;
$primary: #37abe9;
$danger: #a72431;
$error: $danger;
$secondary: #ff8f00;
$warning: #ffbe42;
$success: #239556;
$debug: #5abccc;
$info: #44c4dd;
$light: $gray-100;
$dark: $gray-800;
// Body
// Settings for the `<body>` element.
$body-bg: $gray-900;
$body-color: $gray-400;
// Links
// Style anchor elements.
$link-color: $info;
$link-decoration: none;
$link-hover-color: color.adjust($link-color, $lightness: -15%);
// Components
// Define common padding and border radius sizes and more.
$border-color: $gray-200;
$component-active-color: $gray-400;
$component-active-bg: $primary;
// Fonts
// Font, line-height, and color for body text, headings, and more.
$text-muted: $gray-600 !default;
$blockquote-small-color: $gray-600 !default;
$hr-border-color: rgba($black, .1) !default;
$mark-bg: #fcf8e3 !default;
// Forms
$input-bg: $white;
$input-disabled-bg: $gray-600;
$input-color: $black;
$input-border-color: rgba($gray-600, .15);
$input-group-addon-bg: $gray-500;
// Tables
$table-cell-padding-y: 0.75rem;
$table-cell-padding-x: 0.75rem;
$table-striped-bg: $dark;
// Dropdowns
// Dropdown menu container and contents.
$dropdown-bg: $gray-700;
$dropdown-divider-bg: $gray-800;
$dropdown-link-color: $body-color;
$dropdown-link-hover-color: color.adjust($body-color, $lightness: -5%);
$dropdown-link-hover-bg: $primary;
$dropdown-link-active-color: $component-active-color;
$dropdown-link-active-bg: $component-active-bg;
$dropdown-header-color: color.adjust($body-color, $lightness: -25%);
// Pagination
$pagination-color: $link-color;
$pagination-bg: $dark;
$pagination-border-color: color.adjust($dark, $lightness: 5%);
$pagination-hover-color: $link-hover-color;
$pagination-hover-bg: $gray-900;
$pagination-hover-border-color: $gray-800;
$pagination-active-color: $body-color;
$pagination-active-bg: $primary;
$pagination-active-border-color: $primary;
$pagination-disabled-color: $gray-600;
$pagination-disabled-bg: $pagination-bg;
$pagination-disabled-border-color: $pagination-border-color;
// Cards
$card-bg: $dark;
$card-cap-bg: rgba($light, .05);
// Progress bars
$progress-bg: $gray-600;
// List group
$list-group-bg: $dark;
$list-group-hover-bg: color.adjust($list-group-bg, $lightness: -5%);
$list-group-active-color: $component-active-color !default;
$list-group-active-bg: $component-active-bg !default;
$list-group-active-border-color: $list-group-active-bg !default;
$list-group-action-color: $gray-400;
$list-group-action-hover-color: $list-group-action-color;
// Breadcrumbs
$breadcrumb-active-color: $gray-600;
$breadcrumb-bg: none;
$breadcrumb-padding-y: 0.75rem;
$breadcrumb-padding-x: 1rem;

View File

@ -1,8 +1,5 @@
@import 'functions';
@import 'variables'; @import 'variables';
@import 'mixins';
@import '../../../node_modules/bootstrap/scss/bootstrap'; @import '../../../node_modules/bootstrap/scss/bootstrap';
@import '../../../node_modules/tempusdominus-bootstrap-4/src/sass/tempusdominus-bootstrap-4';
@import '../../../**/static_src/scss/*'; @import '../../../**/static_src/scss/*';
@import '../fontello/css/babybuddy'; @import '../fontello/css/babybuddy';

View File

@ -1,3 +1,5 @@
@use 'sass:map';
// Baby Buddy form style customizations. // Baby Buddy form style customizations.
// BB form fields do not follow typical BS4 style that enables this display. // BB form fields do not follow typical BS4 style that enables this display.
@ -18,40 +20,6 @@
pointer-events: none; pointer-events: none;
} }
.bootstrap-datetimepicker-widget {
// Set default text color for datetime picker.
color: $gray-400;
// Ensure widget appears above other elements (e.g. .submit-primary).
z-index: 1050;
}
// Datetime picker input styles.
.input-group {
&.datetimepicker {
// Make calendar icon large and use the primary theme color.
.input-group-text {
background: none;
border: 0;
color: theme-color('primary');
padding-left: 0;
padding-right: 0;
}
// Style readonly input to look less like an input element.
.datetimepicker-input {
&[readonly] {
background: none;
border: 0;
color: $white;
cursor: pointer;
font-weight: bold;
}
}
}
}
// Use a full width, fixed button on smaller screens. // Use a full width, fixed button on smaller screens.
.submit-primary { .submit-primary {
display: block; display: block;

View File

@ -1,3 +1,5 @@
@use 'sass:map';
// Baby Buddy site-wide custom styles. // Baby Buddy site-wide custom styles.
// Remove extra margin below site breadcrumb. // Remove extra margin below site breadcrumb.
@ -7,7 +9,7 @@
// Extra-small button. // Extra-small button.
.btn-xs { .btn-xs {
@include button-size(.2rem, .12rem, .75rem, 1, .2rem); @include button-size(.2rem, .12rem, .75rem, .2rem);
} }
// Right align main dropdown menu. // Right align main dropdown menu.
@ -18,33 +20,10 @@
// PullToRefresh elements. // PullToRefresh elements.
.ptr--ptr { .ptr--ptr {
background: theme-color('dark'); background: map.get($theme-colors, 'dark');
.ptr--text, .ptr--icon { .ptr--text, .ptr--icon {
// "!important" must be used to override inline styling from JS. // "!important" must be used to override inline styling from JS.
color: theme-color('light') !important; color: map.get($theme-colors, 'light') !important;
}
}
// Basic table of model instances.
.table-instances {
tr {
td, th {
vertical-align: middle;
}
&.odd {
background: $table-accent-bg;
}
&.even {
background: none;
}
&.row-details {
td {
color: $gray-500;
padding-top: 0;
border-top: 0;
}
}
} }
} }
@ -55,5 +34,5 @@
// All modals // All modals
.modal-content { .modal-content {
color: theme-color('dark'); color: map.get($theme-colors, 'dark');
} }

View File

@ -1,149 +0,0 @@
@use 'sass:color';
// Blue Orange theme variables.
// Color system
$blue: #37abe9;
$indigo: #472395;
$purple: #712395;
$pink: #952393;
$red: #a72431;
$orange: #ff8f00;
$yellow: #ffbe42;
$green: #239556;
$teal: #5abccc;
$cyan: #44c4dd;
$theme-colors: (
primary: $blue,
secondary: $orange,
success: $green,
info: $cyan,
debug: $cyan,
warning: $yellow,
danger: $red,
error: $red,
light: $gray-100,
dark: $gray-800
);
// Body
// Settings for the `<body>` element.
$body-bg: $gray-900;
$body-color: $gray-400;
// Links
// Style anchor elements.
$link-color: theme-color('info');
$link-hover-color: color.adjust($link-color, $lightness: -15%);
// Components
// Define common padding and border radius sizes and more.
$border-color: $gray-200;
$component-active-color: $gray-400;
$component-active-bg: theme-color('primary');
// Fonts
// Font, line-height, and color for body text, headings, and more.
$text-muted: $gray-600 !default;
$blockquote-small-color: $gray-600 !default;
$hr-border-color: rgba($black, .1) !default;
$mark-bg: #fcf8e3 !default;
// Tables
// Customizes the `.table` component with basic values, each used across all table variations.
$table-border-color: $gray-800;
$table-color: $body-color;
$table-head-bg: theme-color('primary');
$table-hover-color: $body-color;
$table-inverse-bg: theme-color('primary');
$table-accent-bg: rgba(theme-color('primary'), .1);
// Forms
$input-bg: $white;
$input-disabled-bg: $gray-600;
$input-color: $black;
$input-border-color: rgba($gray-600, .15);
$input-group-addon-bg: $gray-500;
// Dropdowns
// Dropdown menu container and contents.
$dropdown-bg: $gray-700;
$dropdown-divider-bg: $gray-800;
$dropdown-link-color: $body-color;
$dropdown-link-hover-color: color.adjust($body-color, $lightness: -5%);
$dropdown-link-hover-bg: theme-color('primary');
$dropdown-link-active-color: $component-active-color;
$dropdown-link-active-bg: $component-active-bg;
$dropdown-header-color: color.adjust($body-color, $lightness: -25%);
// Pagination
$pagination-color: $link-color;
$pagination-bg: theme-color('dark');
$pagination-border-color: color.adjust(theme-color('dark'), $lightness: 5%);
$pagination-hover-color: $link-hover-color;
$pagination-hover-bg: $gray-900;
$pagination-hover-border-color: $gray-800;
$pagination-active-color: $body-color;
$pagination-active-bg: theme-color('primary');
$pagination-active-border-color: theme-color('primary');
$pagination-disabled-color: $gray-600;
$pagination-disabled-bg: $pagination-bg;
$pagination-disabled-border-color: $pagination-border-color;
// Jumbotron
$jumbotron-bg: theme-color('dark');
// Cards
$card-bg: theme-color('dark');
$card-cap-bg: rgba(theme-color('light'), .05);
// Progress bars
$progress-bg: $gray-600;
// List group
$list-group-bg: theme-color('dark');
$list-group-hover-bg: color.adjust($list-group-bg, $lightness: -5%);
$list-group-active-color: $component-active-color !default;
$list-group-active-bg: $component-active-bg !default;
$list-group-active-border-color: $list-group-active-bg !default;
$list-group-action-color: $gray-400;
$list-group-action-hover-color: $list-group-action-color;
// Breadcrumbs
$breadcrumb-bg: none;
$breadcrumb-active-color: $gray-600;
// Datetimepicker library (Tempus Dominus)
$bs-datetimepicker-btn-hover-bg: $gray-800;

View File

@ -42,8 +42,6 @@
{% block page %}{% endblock %} {% block page %}{% endblock %}
<script src="{% static "babybuddy/js/vendor.js" %}"></script> <script src="{% static "babybuddy/js/vendor.js" %}"></script>
<script>moment.locale('{{ LOCALE }}');</script>
<script>moment.tz.setDefault('{{ TIMEZONE }}');</script>
<script src="{% static "babybuddy/js/app.js" %}"></script> <script src="{% static "babybuddy/js/app.js" %}"></script>
{% if user.is_authenticated %} {% if user.is_authenticated %}
<script>BabyBuddy.PullToRefresh.init()</script> <script>BabyBuddy.PullToRefresh.init()</script>

View File

@ -8,14 +8,14 @@
</label> </label>
<div class="col-xs-10 col-sm-auto"> <div class="col-xs-10 col-sm-auto">
{% if 'choice' or 'boolean' in field|field_type %} {% if 'choice' or 'boolean' in field|field_type %}
{{ field|add_class:"custom-select custom-select-sm" }} {{ field|add_class:"form-select form-select-sm" }}
{% else %} {% else %}
{{ field|add_class:"form-control form-control-sm" }} {{ field|add_class:"form-control form-control-sm" }}
{% endif %} {% endif %}
</div> </div>
{% endfor %} {% endfor %}
<div class="col-xs-12 col-sm-auto mt-3 mt-sm-0"> <div class="col-xs-12 col-sm-auto mt-3 mt-sm-0">
<button type="submit" class="btn btn-sm btn-primary mr-2">{% trans "Filter" %}</button> <button type="submit" class="btn btn-sm btn-primary me-2">{% trans "Filter" %}</button>
<a href="{{ request.path }}" class="btn btn-sm btn-error">{% trans "Reset" %}</a> <a href="{{ request.path }}" class="btn btn-sm btn-error">{% trans "Reset" %}</a>
</div> </div>
</div> </div>
@ -24,7 +24,7 @@
<p> <p>
<a class="btn btn-dark btn-sm" <a class="btn btn-dark btn-sm"
data-toggle="collapse" data-bs-toggle="collapse"
href="#filter_form" href="#filter_form"
role="button" role="button"
aria-expanded="false" aria-expanded="false"

View File

@ -7,7 +7,7 @@
{% csrf_token %} {% csrf_token %}
{% for field in form %} {% for field in form %}
{{ field.widget }} {{ field.widget }}
<div class="form-group row"> <div class="row">
{% include 'babybuddy/form_field.html' %} {% include 'babybuddy/form_field.html' %}
</div> </div>
{% endfor %} {% endfor %}

View File

@ -1,50 +1,37 @@
{% load widget_tweaks %} {% load widget_tweaks %}
<label for="id_{{ field.name }}" class="col-sm-2 col-form-label{% if field|field_type == 'booleanfield' %} boolean-label{% endif %}"> <div class="row mb-3">
{% if field|field_type != "booleanfield" %} <label for="id_{{ field.name }}" class="col-sm-2 col-form-label{% if field|field_type == 'booleanfield' %} boolean-label{% endif %}">
{{ field.label }} {{ field.label }}
{% endif %} </label>
</label> <div class="col-sm-10{% if field|widget_type == 'childradioselect' %} overflow-auto"{% endif %}">
<div class="col-sm-10{% if field|widget_type == 'childradioselect' %} overflow-auto"{% endif %}"> {% if field|field_type == "booleanfield" %}
{% if field|field_type == "booleanfield" %} {% if field.errors %}
<div class="btn-group-toggle" data-toggle="buttons"> {{ field|add_class:"btn-check is-invalid" }}
{% else %}
{{ field|add_class:"btn-check" }}
{% endif %}
<label for="id_{{ field.name }}" class="btn btn-outline-light btn-no-hover{% if field.value %} active{% endif %}"> <label for="id_{{ field.name }}" class="btn btn-outline-light btn-no-hover{% if field.value %} active{% endif %}">
{% if field.errors %}
{{ field|add_class:"is-invalid" }}
{% else %}
{{ field }}
{% endif %}
{{ field.label }} {{ field.label }}
</label> </label>
</div> {% elif 'choice' in field|field_type %}
{% elif field|field_type == "datetimefield" or field|field_type == "datefield" %}
<div class="input-group input-group-lg datetimepicker" id="datetimepicker_{{ field.name }}" data-target-input="nearest">
<div class="input-group-prepend px-2 rounded-left bg-dark" data-target="#datetimepicker_{{ field.name }}" data-toggle="datetimepicker">
<span class="input-group-text"><i class="icon-calendar"></i></span>
</div>
{% if field.errors %} {% if field.errors %}
{{ field|add_class:"datetimepicker-input form-control form-control-lg is-invalid" }} {{ field|add_class:"form-select is-invalid" }}
{% else %} {% else %}
{{ field|add_class:"datetimepicker-input form-control form-control-lg" }} {{ field|add_class:"form-select" }}
{% endif %} {% endif %}
</div>
{% elif 'choice' in field|field_type %}
{% if field.errors %}
{{ field|add_class:"custom-select is-invalid" }}
{% else %} {% else %}
{{ field|add_class:"custom-select" }} {% if field.errors %}
{{ field|add_class:"form-control is-invalid" }}
{% else %}
{{ field|add_class:"form-control" }}
{% endif %}
{% endif %}
{% if field.help_text %}
<div class="help-block"><small>{{ field.help_text }}</small></div>
{% endif %} {% endif %}
{% else %}
{% if field.errors %} {% if field.errors %}
{{ field|add_class:"form-control is-invalid" }} <div class="invalid-feedback">{% for error in field.errors %}{{ error }}{% endfor %}</div>
{% else %}
{{ field|add_class:"form-control" }}
{% endif %} {% endif %}
{% endif %} </div>
{% if field.help_text %}
<div class="help-block"><small>{{ field.help_text }}</small></div>
{% endif %}
{% if field.errors %}
<div class="invalid-feedback">{% for error in field.errors %}{{ error }}{% endfor %}</div>
{% endif %}
</div> </div>

View File

@ -5,9 +5,7 @@
{% for message in messages %} {% for message in messages %}
<div class="alert{% if message.tags %} alert-{{ message.tags }}{% endif %} alert-dismissible fade show" role="alert"> <div class="alert{% if message.tags %} alert-{{ message.tags }}{% endif %} alert-dismissible fade show" role="alert">
{{ message }} {{ message }}
<button type="button" class="close" data-dismiss="alert" aria-label="Close"> <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
<span aria-hidden="true">&times;</span>
</button>
</div> </div>
{% endfor %} {% endfor %}
{% endif %} {% endif %}

View File

@ -3,86 +3,85 @@
{% block nav %} {% block nav %}
<nav class="navbar navbar-expand-md navbar-dark bg-dark sticky-top"> <nav class="navbar navbar-expand-md navbar-dark bg-dark sticky-top">
<a class="navbar-brand mr-2" href={% url "babybuddy:root-router" %}> <div class="container-fluid">
<img src="{% static "babybuddy/logo/icon-brand.png" %}" width="30" height="30" class="d-inline-block align-top" alt=""> <a class="navbar-brand me-2" href={% url "babybuddy:root-router" %}>
<span class="d-none d-lg-inline-block"> <img src="{% static "babybuddy/logo/icon-brand.png" %}" width="30" height="30"
<span class="text-primary">Baby</span> Buddy class="d-inline-block align-top" alt="">
</span> <span class="d-none d-lg-inline-block">
</a> <span class="text-primary">Baby</span> Buddy
<div class="d-lg-none d-md-none d-flex mr-auto p-0 ml-2"> </span>
<div> </a>
<a class="text-muted" <div class="d-lg-none d-md-none d-flex me-auto p-0 ms-2">
href="{% url 'dashboard:dashboard' %}" <div>
aria-expanded="false"><i class="icon-2x icon-dashboard" aria-hidden="true"></i> <a class="text-muted"
</a> href="{% url 'dashboard:dashboard' %}"
&nbsp; aria-expanded="false"><i class="icon-2x icon-dashboard" aria-hidden="true"></i>
</div> </a>
<div> &nbsp;
<a class="text-muted" </div>
href="{% url 'core:timeline' %}" <div>
aria-expanded="false"><i class="icon-2x icon-timeline" aria-hidden="true"></i> <a class="text-muted"
</a> href="{% url 'core:timeline' %}"
&nbsp; aria-expanded="false"><i class="icon-2x icon-timeline" aria-hidden="true"></i>
</div> </a>
</div> &nbsp;
<div class="d-lg-none d-md-none d-flex ml-auto p-0 mr-2">
{% quick_timer_nav %}
<div class="dropdown show">
<a class="text-success"
href="#"
role="button"
id="nav-quick-add-link"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"><i class="icon-2x icon-add" aria-hidden="true"></i>
</a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="nav-quick-add-link">
{% if perms.core.add_diaperchange %}
<a class="dropdown-item p-2" href="{% url 'core:diaperchange-add' %}">
<i class="icon-diaperchange" aria-hidden="true"></i>
{% trans "Diaper Change" %}
</a>
{% endif %}
{% if perms.core.add_feeding %}
<a class="dropdown-item p-2" href="{% url 'core:feeding-add' %}">
<i class="icon-feeding" aria-hidden="true"></i>
{% trans "Feeding" %}
</a>
{% endif %}
{% if perms.core.add_note %}
<a class="dropdown-item p-2" href="{% url 'core:note-add' %}">
<i class="icon-note" aria-hidden="true"></i>
{% trans "Note" %}
</a>
{% endif %}
{% if perms.core.add_sleep %}
<a class="dropdown-item p-2" href="{% url 'core:sleep-add' %}">
<i class="icon-sleep" aria-hidden="true"></i>
{% trans "Sleep" %}
</a>
{% endif %}
{% if perms.core.add_tummytime %}
<a class="dropdown-item p-2" href="{% url 'core:tummytime-add' %}">
<i class="icon-tummytime" aria-hidden="true"></i>
{% trans "Tummy Time" %}
</a>
{% endif %}
</div> </div>
</div> </div>
</div> <div class="d-lg-none d-md-none d-flex ms-auto p-0 me-2">
{% quick_timer_nav %}
<div class="dropdown show">
<a class="text-success"
href="#"
role="button"
id="nav-quick-add-link"
data-bs-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"><i class="icon-2x icon-add" aria-hidden="true"></i>
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" <div class="dropdown-menu dropdown-menu-end" aria-labelledby="nav-quick-add-link">
data-target="#navbar-app" aria-controls="navbar-app" {% if perms.core.add_diaperchange %}
aria-expanded="false" aria-label="Toggle navigation"> <a class="dropdown-item p-2" href="{% url 'core:diaperchange-add' %}">
<i class="icon-diaperchange" aria-hidden="true"></i>
{% trans "Diaper Change" %}
</a>
{% endif %}
{% if perms.core.add_feeding %}
<a class="dropdown-item p-2" href="{% url 'core:feeding-add' %}">
<i class="icon-feeding" aria-hidden="true"></i>
{% trans "Feeding" %}
</a>
{% endif %}
{% if perms.core.add_note %}
<a class="dropdown-item p-2" href="{% url 'core:note-add' %}">
<i class="icon-note" aria-hidden="true"></i>
{% trans "Note" %}
</a>
{% endif %}
{% if perms.core.add_sleep %}
<a class="dropdown-item p-2" href="{% url 'core:sleep-add' %}">
<i class="icon-sleep" aria-hidden="true"></i>
{% trans "Sleep" %}
</a>
{% endif %}
{% if perms.core.add_tummytime %}
<a class="dropdown-item p-2" href="{% url 'core:tummytime-add' %}">
<i class="icon-tummytime" aria-hidden="true"></i>
{% trans "Tummy Time" %}
</a>
{% endif %}
</div>
</div>
</div>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
data-bs-target="#navbar-app" aria-controls="navbar-app"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
<div class="collapse navbar-collapse" id="navbar-app"> <div class="collapse navbar-collapse" id="navbar-app">
<ul class="navbar-nav mr-auto"> <ul class="navbar-nav me-auto">
<li class="nav-item{% if request.path == '/' %} active{% endif %}"> <li class="nav-item{% if request.path == '/' %} active{% endif %}">
<a class="nav-link" href="{% url 'dashboard:dashboard' %}"> <a class="nav-link" href="{% url 'dashboard:dashboard' %}">
<i class="icon-dashboard" aria-hidden="true"></i> <i class="icon-dashboard" aria-hidden="true"></i>
@ -101,7 +100,7 @@
<a id="nav-children-menu-link" <a id="nav-children-menu-link"
class="nav-link dropdown-toggle" class="nav-link dropdown-toggle"
href="#" href="#"
data-toggle="dropdown" data-bs-toggle="dropdown"
aria-haspopup="true" aria-haspopup="true"
aria-expanded="false"><i class="icon-child" aria-hidden="true"></i> aria-expanded="false"><i class="icon-child" aria-hidden="true"></i>
{% trans "Children" %} {% trans "Children" %}
@ -116,7 +115,7 @@
</a> </a>
{% endif %} {% endif %}
{% if perms.core.add_child %} {% if perms.core.add_child %}
<a class="dropdown-item pl-5{% if request.path == '/children/add/' %} active{% endif %}" <a class="dropdown-item ps-5{% if request.path == '/children/add/' %} active{% endif %}"
href="{% url 'core:child-add' %}"><i class="icon-add" aria-hidden="true"></i> href="{% url 'core:child-add' %}"><i class="icon-add" aria-hidden="true"></i>
{% trans "Child" %} {% trans "Child" %}
</a> </a>
@ -130,7 +129,7 @@
</a> </a>
{% endif %} {% endif %}
{% if perms.core.add_note %} {% if perms.core.add_note %}
<a class="dropdown-item pl-5{% if request.path == '/notes/add/' %} active{% endif %}" <a class="dropdown-item ps-5{% if request.path == '/notes/add/' %} active{% endif %}"
href="{% url 'core:note-add' %}"><i class="icon-add" aria-hidden="true"></i> href="{% url 'core:note-add' %}"><i class="icon-add" aria-hidden="true"></i>
{% trans "Note" %} {% trans "Note" %}
</a> </a>
@ -143,7 +142,7 @@
<a id="nav-measurements-menu-link" <a id="nav-measurements-menu-link"
class="nav-link dropdown-toggle" class="nav-link dropdown-toggle"
href="#" href="#"
data-toggle="dropdown" data-bs-toggle="dropdown"
aria-haspopup="true" aria-haspopup="true"
aria-expanded="false"><i class="icon-measurements" aria-hidden="true"></i> aria-expanded="false"><i class="icon-measurements" aria-hidden="true"></i>
{% trans "Measurements" %} {% trans "Measurements" %}
@ -158,7 +157,7 @@
</a> </a>
{% endif %} {% endif %}
{% if perms.core.add_bmi %} {% if perms.core.add_bmi %}
<a class="dropdown-item pl-5{% if request.path == '/bmi/add/' %} active{% endif %}" <a class="dropdown-item ps-5{% if request.path == '/bmi/add/' %} active{% endif %}"
href="{% url 'core:bmi-add' %}"><i class="icon-add" aria-hidden="true"></i> href="{% url 'core:bmi-add' %}"><i class="icon-add" aria-hidden="true"></i>
{% trans "BMI entry" %} {% trans "BMI entry" %}
</a> </a>
@ -172,7 +171,7 @@
</a> </a>
{% endif %} {% endif %}
{% if perms.core.add_head_circumference %} {% if perms.core.add_head_circumference %}
<a class="dropdown-item pl-5{% if request.path == '/head-circumference/add/' %} active{% endif %}" <a class="dropdown-item ps-5{% if request.path == '/head-circumference/add/' %} active{% endif %}"
href="{% url 'core:head-circumference-add' %}"><i class="icon-add" aria-hidden="true"></i> href="{% url 'core:head-circumference-add' %}"><i class="icon-add" aria-hidden="true"></i>
{% trans "Head Circumference entry" %} {% trans "Head Circumference entry" %}
</a> </a>
@ -186,7 +185,7 @@
</a> </a>
{% endif %} {% endif %}
{% if perms.core.add_height %} {% if perms.core.add_height %}
<a class="dropdown-item pl-5{% if request.path == '/height/add/' %} active{% endif %}" <a class="dropdown-item ps-5{% if request.path == '/height/add/' %} active{% endif %}"
href="{% url 'core:height-add' %}"><i class="icon-add" aria-hidden="true"></i> href="{% url 'core:height-add' %}"><i class="icon-add" aria-hidden="true"></i>
{% trans "Height entry" %} {% trans "Height entry" %}
</a> </a>
@ -200,7 +199,7 @@
</a> </a>
{% endif %} {% endif %}
{% if perms.core.add_temperature %} {% if perms.core.add_temperature %}
<a class="dropdown-item pl-5{% if request.path == '/temperature/add/' %} active{% endif %}" <a class="dropdown-item ps-5{% if request.path == '/temperature/add/' %} active{% endif %}"
href="{% url 'core:temperature-add' %}"><i class="icon-add" aria-hidden="true"></i> href="{% url 'core:temperature-add' %}"><i class="icon-add" aria-hidden="true"></i>
{% trans "Temperature reading" %} {% trans "Temperature reading" %}
</a> </a>
@ -214,7 +213,7 @@
</a> </a>
{% endif %} {% endif %}
{% if perms.core.add_weight %} {% if perms.core.add_weight %}
<a class="dropdown-item pl-5{% if request.path == '/weight/add/' %} active{% endif %}" <a class="dropdown-item ps-5{% if request.path == '/weight/add/' %} active{% endif %}"
href="{% url 'core:weight-add' %}"><i class="icon-add" aria-hidden="true"></i> href="{% url 'core:weight-add' %}"><i class="icon-add" aria-hidden="true"></i>
{% trans "Weight entry" %} {% trans "Weight entry" %}
</a> </a>
@ -222,12 +221,11 @@
</div> </div>
</li> </li>
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a id="nav-activity-menu-link" <a id="nav-activity-menu-link"
class="nav-link dropdown-toggle" class="nav-link dropdown-toggle"
href="#" href="#"
data-toggle="dropdown" data-bs-toggle="dropdown"
aria-haspopup="true" aria-haspopup="true"
aria-expanded="false"><i class="icon-activities" aria-hidden="true"></i> aria-expanded="false"><i class="icon-activities" aria-hidden="true"></i>
{% trans "Activities" %} {% trans "Activities" %}
@ -236,12 +234,13 @@
{% if perms.core.view_diaperchange %} {% if perms.core.view_diaperchange %}
<a class="dropdown-item{% if request.path == '/changes/' %} active{% endif %}" <a class="dropdown-item{% if request.path == '/changes/' %} active{% endif %}"
href="{% url 'core:diaperchange-list' %}"><i class="icon-diaperchange" aria-hidden="true"></i> href="{% url 'core:diaperchange-list' %}"><i class="icon-diaperchange"
aria-hidden="true"></i>
{% trans "Changes" %} {% trans "Changes" %}
</a> </a>
{% endif %} {% endif %}
{% if perms.core.add_diaperchange %} {% if perms.core.add_diaperchange %}
<a class="dropdown-item pl-5{% if request.path == '/changes/add/' %} active{% endif %}" <a class="dropdown-item ps-5{% if request.path == '/changes/add/' %} active{% endif %}"
href="{% url 'core:diaperchange-add' %}"><i class="icon-add" aria-hidden="true"></i> href="{% url 'core:diaperchange-add' %}"><i class="icon-add" aria-hidden="true"></i>
{% trans "Change" %} {% trans "Change" %}
</a> </a>
@ -254,7 +253,7 @@
</a> </a>
{% endif %} {% endif %}
{% if perms.core.add_feeding %} {% if perms.core.add_feeding %}
<a class="dropdown-item pl-5{% if request.path == '/feedings/add/' %} active{% endif %}" <a class="dropdown-item ps-5{% if request.path == '/feedings/add/' %} active{% endif %}"
href="{% url 'core:feeding-add' %}"><i class="icon-add" aria-hidden="true"></i> href="{% url 'core:feeding-add' %}"><i class="icon-add" aria-hidden="true"></i>
{% trans "Feeding" %} {% trans "Feeding" %}
</a> </a>
@ -268,7 +267,7 @@
</a> </a>
{% endif %} {% endif %}
{% if perms.core.add_pumping %} {% if perms.core.add_pumping %}
<a class="dropdown-item pl-5{% if request.path == '/pumping/add/' %} active{% endif %}" <a class="dropdown-item ps-5{% if request.path == '/pumping/add/' %} active{% endif %}"
href="{% url 'core:pumping-add' %}"><i class="icon-add" aria-hidden="true"></i> href="{% url 'core:pumping-add' %}"><i class="icon-add" aria-hidden="true"></i>
{% trans "Pumping entry" %} {% trans "Pumping entry" %}
</a> </a>
@ -281,7 +280,7 @@
</a> </a>
{% endif %} {% endif %}
{% if perms.core.add_sleep %} {% if perms.core.add_sleep %}
<a class="dropdown-item pl-5{% if request.path == '/sleep/add/' %} active{% endif %}" <a class="dropdown-item ps-5{% if request.path == '/sleep/add/' %} active{% endif %}"
href="{% url 'core:sleep-add' %}"><i class="icon-add" aria-hidden="true"></i> href="{% url 'core:sleep-add' %}"><i class="icon-add" aria-hidden="true"></i>
{% trans "Sleep entry" %} {% trans "Sleep entry" %}
</a> </a>
@ -289,12 +288,13 @@
{% if perms.core.view_tummytime %} {% if perms.core.view_tummytime %}
<a class="dropdown-item{% if request.path == '/tummy-time/' %} active{% endif %}" <a class="dropdown-item{% if request.path == '/tummy-time/' %} active{% endif %}"
href="{% url 'core:tummytime-list' %}"><i class="icon-tummytime" aria-hidden="true"></i> href="{% url 'core:tummytime-list' %}"><i class="icon-tummytime"
aria-hidden="true"></i>
{% trans "Tummy Time" %} {% trans "Tummy Time" %}
</a> </a>
{% endif %} {% endif %}
{% if perms.core.add_tummytime %} {% if perms.core.add_tummytime %}
<a class="dropdown-item pl-5{% if request.path == '/tummy-time/add/' %} active{% endif %}" <a class="dropdown-item ps-5{% if request.path == '/tummy-time/add/' %} active{% endif %}"
href="{% url 'core:tummytime-add' %}"><i class="icon-add" aria-hidden="true"></i> href="{% url 'core:tummytime-add' %}"><i class="icon-add" aria-hidden="true"></i>
{% trans "Tummy Time entry" %} {% trans "Tummy Time entry" %}
</a> </a>
@ -308,21 +308,23 @@
</ul> </ul>
{% if request.user %} {% if request.user %}
<ul class="navbar-nav ml-auto"> <ul class="navbar-nav ms-auto">
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a id="nav-user-menu-link" <a id="nav-user-menu-link"
class="nav-link dropdown-toggle" class="nav-link dropdown-toggle"
href="#" href="#"
data-toggle="dropdown" data-bs-toggle="dropdown"
aria-haspopup="true" aria-haspopup="true"
aria-expanded="false"> aria-expanded="false">
<i class="icon-user" aria-hidden="true"></i> <i class="icon-user" aria-hidden="true"></i>
{% firstof user.get_full_name user.get_username %} {% firstof user.get_full_name user.get_username %}
</a> </a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="nav-user-menu-link"> <div class="dropdown-menu dropdown-menu-end" aria-labelledby="nav-user-menu-link">
<h6 class="dropdown-header">{% trans "User" %}</h6> <h6 class="dropdown-header">{% trans "User" %}</h6>
<a href="{% url 'babybuddy:user-settings' %}" class="dropdown-item">{% trans "Settings" %}</a> <a href="{% url 'babybuddy:user-settings' %}"
<a href="{% url 'babybuddy:user-password' %}" class="dropdown-item">{% trans "Password" %}</a> class="dropdown-item">{% trans "Settings" %}</a>
<a href="{% url 'babybuddy:user-password' %}"
class="dropdown-item">{% trans "Password" %}</a>
<a href="{% url 'babybuddy:user-add-device' %}" class="dropdown-item">{% trans "Add a device" %}</a> <a href="{% url 'babybuddy:user-add-device' %}" class="dropdown-item">{% trans "Add a device" %}</a>
<form action="{% url 'babybuddy:logout' %}" role="form" method="post"> <form action="{% url 'babybuddy:logout' %}" role="form" method="post">
{% csrf_token %} {% csrf_token %}
@ -333,8 +335,12 @@
<h6 class="dropdown-header">{% trans "Site" %}</h6> <h6 class="dropdown-header">{% trans "Site" %}</h6>
<a href="{% url 'api:api-root' %}" class="dropdown-item">{% trans "API Browser" %}</a> <a href="{% url 'api:api-root' %}" class="dropdown-item">{% trans "API Browser" %}</a>
{% if request.user.is_staff %} {% if request.user.is_staff %}
<a href="{% url 'babybuddy:user-list' %}" class="dropdown-item">{% trans "Users" %}</a> <a href="{% url 'babybuddy:site_settings' %}"
<a href="{% url 'admin:index' %}" class="dropdown-item">{% trans "Database Admin" %}</a> class="dropdown-item">{% trans "Settings" %}</a>
<a href="{% url 'babybuddy:user-list' %}"
class="dropdown-item">{% trans "Users" %}</a>
<a href="{% url 'admin:index' %}"
class="dropdown-item">{% trans "Database Admin" %}</a>
{% endif %} {% endif %}
<h6 class="dropdown-header">{% trans "Support" %}</h6> <h6 class="dropdown-header">{% trans "Support" %}</h6>
<a href="https://github.com/babybuddy/babybuddy" class="dropdown-item"> <a href="https://github.com/babybuddy/babybuddy" class="dropdown-item">
@ -347,5 +353,6 @@
</ul> </ul>
{% endif %} {% endif %}
</div> </div>
</div>
</nav> </nav>
{% endblock %} {% endblock %}

View File

@ -8,7 +8,7 @@
<li class="page-item"> <li class="page-item">
<a class="page-link" href="{% relative_url 'page' page_obj.previous_page_number %}" aria-label="Previous"> <a class="page-link" href="{% relative_url 'page' page_obj.previous_page_number %}" aria-label="Previous">
<i class="icon-angle-circled-left" aria-hidden="true"></i> <i class="icon-angle-circled-left" aria-hidden="true"></i>
<span class="sr-only">{% trans "Previous" %}</span> <span class="visually-hidden">{% trans "Previous" %}</span>
</a> </a>
</li> </li>
{% endif %} {% endif %}
@ -25,7 +25,7 @@
<li class="page-item"> <li class="page-item">
<a class="page-link" href="{% relative_url 'page' page_obj.next_page_number %}" aria-label="Next"> <a class="page-link" href="{% relative_url 'page' page_obj.next_page_number %}" aria-label="Next">
<i class="icon-angle-circled-right" aria-hidden="true"></i> <i class="icon-angle-circled-right" aria-hidden="true"></i>
<span class="sr-only">{% trans "Next" %}</span> <span class="visually-hidden">{% trans "Next" %}</span>
</a> </a>
</li> </li>
{% endif %} {% endif %}

View File

@ -12,7 +12,7 @@
{% block breadcrumbs %} {% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'babybuddy:user-list' %}">{% trans "Users" %}</a></li> <li class="breadcrumb-item"><a href="{% url 'babybuddy:user-list' %}">{% trans "Users" %}</a></li>
{% if object %} {% if object %}
<li class="breadcrumb-item font-weight-bold">{{ object }}</li> <li class="breadcrumb-item fw-bold">{{ object }}</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Update" %}</li> <li class="breadcrumb-item active" aria-current="page">{% trans "Update" %}</li>
{% else %} {% else %}
<li class="breadcrumb-item active" aria-current="page">{% trans "Create User" %}</li> <li class="breadcrumb-item active" aria-current="page">{% trans "Create User" %}</li>

View File

@ -11,8 +11,8 @@
<h1>Users</h1> <h1>Users</h1>
{% include 'babybuddy/filter.html' %} {% include 'babybuddy/filter.html' %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-striped table-hover user-list"> <table class="table table-borderless table-striped table-hover align-middle">
<thead class="thead-inverse"> <thead>
<tr> <tr>
<th>{% trans "User" %}</th> <th>{% trans "User" %}</th>
<th>{% trans "First Name" %}</th> <th>{% trans "First Name" %}</th>

View File

@ -8,66 +8,76 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="jumbotron"> <div class="px-2 py-5 bg-dark rounded-3">
<h1 class="display-3">{% trans "Welcome to Baby Buddy!" %}</h1> <div class="container-fluid">
<p class="lead"> <h1 class="display-3">{% trans "Welcome to Baby Buddy!" %}</h1>
{% blocktrans trimmed%} <p class="lead">
Learn about and predict baby's needs without (<em>as much</em>) {% blocktrans trimmed%}
guess work by using Baby Buddy to track &mdash; Learn about and predict baby's needs without (<em>as much</em>)
{% endblocktrans %} guess work by using Baby Buddy to track &mdash;
</p> {% endblocktrans %}
<hr class="my-4"> </p>
<div class="card-deck"> <hr class="my-4">
<div class="card card-diaperchange"> <div class="row gy-4">
<div class="card-header text-center"> <div class="col-12 col-md-6 col-lg-3">
<i class="icon-2x icon-diaperchange" aria-hidden="true"></i> <div class="card card-diaperchange">
<div class="card-header text-center">
<i class="icon-2x icon-diaperchange" aria-hidden="true"></i>
</div>
<div class="card-body">
<h3 class="card-title text-center">{% trans "Diaper Changes" %}</h3>
</div>
</div>
</div> </div>
<div class="card-body"> <div class="col-12 col-md-6 col-lg-3">
<h3 class="card-title text-center">{% trans "Diaper Changes" %}</h3> <div class="card card-feeding">
</div> <div class="card-header text-center">
</div> <i class="icon-2x icon-feeding" aria-hidden="true"></i>
<div class="card card-feeding"> </div>
<div class="card-header text-center"> <div class="card-body">
<i class="icon-2x icon-feeding" aria-hidden="true"></i> <h3 class="card-title text-center">{% trans "Feedings" %}</h3>
</div> </div>
<div class="card-body"> </div>
<h3 class="card-title text-center">{% trans "Feedings" %}</h3> </div>
</div> <div class="col-12 col-md-6 col-lg-3">
</div> <div class="card card-sleep">
<div class="card card-sleep"> <div class="card-header text-center">
<div class="card-header text-center"> <i class="icon-2x icon-sleep" aria-hidden="true"></i>
<i class="icon-2x icon-sleep" aria-hidden="true"></i> </div>
</div> <div class="card-body">
<div class="card-body"> <h3 class="card-title text-center">{% trans "Sleep" %}</h3>
<h3 class="card-title text-center">{% trans "Sleep" %}</h3> </div>
</div> </div>
</div> </div>
<div class="card card-tummytime"> <div class="col-12 col-md-6 col-lg-3">
<div class="card-header text-center"> <div class="card card-tummytime">
<i class="icon-2x icon-tummytime" aria-hidden="true"></i> <div class="card-header text-center">
</div> <i class="icon-2x icon-tummytime" aria-hidden="true"></i>
<div class="card-body"> </div>
<h3 class="card-title text-center">{% trans "Tummy Time" %}</h3> <div class="card-body">
<h3 class="card-title text-center">{% trans "Tummy Time" %}</h3>
</div>
</div>
</div> </div>
</div> </div>
<hr class="my-4">
<p class="lead">
{% blocktrans trimmed %}
As the amount of entries grows, Baby Buddy will help parents
and caregivers to identify small patterns in baby's habits
using the dashboard and graphs. Baby Buddy is mobile-friendly
and uses a dark theme to help weary moms and dads with 2AM
feedings and changings. To get started, just click the button
below to add your first (or second, third, etc.) child!
{% endblocktrans %}
</p>
<p class="text-center">
{% if perms.core.add_child %}
<a href="{% url 'core:child-add' %}" class="btn btn-lg btn-success">
<i class="icon-child" aria-hidden="true"></i> {% trans "Add a Child" %}
</a>
{% endif %}
</p>
</div> </div>
<hr class="my-4">
<p class="lead">
{% blocktrans trimmed %}
As the amount of entries grows, Baby Buddy will help parents
and caregivers to identify small patterns in baby's habits
using the dashboard and graphs. Baby Buddy is mobile-friendly
and uses a dark theme to help weary moms and dads with 2AM
feedings and changings. To get started, just click the button
below to add your first (or second, third, etc.) child!
{% endblocktrans %}
</p>
<p class="text-center">
{% if perms.core.add_child %}
<a href="{% url 'core:child-add' %}" class="btn btn-lg btn-success">
<i class="icon-child" aria-hidden="true"></i> {% trans "Add a Child" %}
</a>
{% endif %}
</p>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -0,0 +1,14 @@
{% extends 'babybuddy/page.html' %}
{% load i18n %}
{% block title %}{% trans "Site Settings" %}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item">{% trans "Site" %}</li>
<li class="breadcrumb-item active">{% trans "Settings" %}</li>
{% endblock %}
{% block content %}
<h1>{% trans "Site Settings" %}</h1>
{% include 'babybuddy/form.html' %}
{% endblock %}

View File

@ -10,12 +10,14 @@
<div class="alert alert-danger" role="alert"> <div class="alert alert-danger" role="alert">
{{ main }} {{ reason }} {{ main }} {{ reason }}
</div> </div>
<div class="jumbotron"> <div class="px-2 py-5 bg-dark rounded-3">
<h2>{% trans "How to Fix" %}</h2> <div class="container-fluid">
{% blocktrans trimmed with origin=origin %} <h2>{% trans "How to Fix" %}</h2>
Add <samp>{{ origin }}</samp> to the <code>CSRF_TRUSTED_ORIGINS</code> {% blocktrans trimmed with origin=origin %}
environment variable. If multiple origins are required separate Add <samp>{{ origin }}</samp> to the <code>CSRF_TRUSTED_ORIGINS</code>
with commas. environment variable. If multiple origins are required separate
{% endblocktrans %} with commas.
{% endblocktrans %}
</div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -8,7 +8,7 @@
<div class="row justify-content-md-center"> <div class="row justify-content-md-center">
<div class="col-lg-12 mb-4"> <div class="col-lg-12 mb-4">
<div class="d-sm-flex"> <div class="d-sm-flex">
<img class="d-inline-block align-self-top mr-2 mt-2 text-center" src="{% static "babybuddy/logo/logo-sad.png" %}" width="65" height="65"> <img class="d-inline-block align-self-top me-2 mt-2 text-center" src="{% static "babybuddy/logo/logo-sad.png" %}" width="65" height="65">
<div class="p-2 flex-grow-1"> <div class="p-2 flex-grow-1">
{% block content %}{% endblock %} {% block content %}{% endblock %}
<a href="{% url "babybuddy:root-router" %}" class="btn btn-outline-primary mt-3">{% trans "Return to Baby Buddy" %}</a> <a href="{% url "babybuddy:root-router" %}" class="btn btn-outline-primary mt-3">{% trans "Return to Baby Buddy" %}</a>

View File

@ -7,7 +7,7 @@
<div id="view-{{ request.resolver_match.view_name }}" class="container"> <div id="view-{{ request.resolver_match.view_name }}" class="container">
<div class="text-center pt-3"> <div class="text-center pt-3">
<img src="{% static "babybuddy/logo/icon-brand.png" %}" width="65" height="65" class="d-inline-block align-top" alt=""> <img src="{% static "babybuddy/logo/icon-brand.png" %}" width="65" height="65" class="d-inline-block align-top" alt="">
<h1 class="d-none d-md-inline-block display-4 ml-2"> <h1 class="d-none d-md-inline-block display-4 ms-2">
<span class="text-primary">Baby</span> Buddy <span class="text-primary">Baby</span> Buddy
</h1> </h1>
</a> </a>

View File

@ -8,23 +8,19 @@
<input type="hidden" name="next" value="{{ next }}"> <input type="hidden" name="next" value="{{ next }}">
<label class="sr-only" for="username-input-group"> <label class="visually-hidden" for="username-input-group">
{{ form.username.label }} {{ form.username.label }}
</label> </label>
<div class="input-group mb-3 fade-in"> <div class="input-group mb-3 fade-in">
<div class="input-group-prepend"> <span class="input-group-text text-muted"><i class="icon-user" aria-hidden="true"></i></span>
<span class="input-group-text"><i class="icon-user" aria-hidden="true"></i></span>
</div>
{% render_field form.username name='username' class+='form-control' id='username-input-group' placeholder=form.username.label %} {% render_field form.username name='username' class+='form-control' id='username-input-group' placeholder=form.username.label %}
</div> </div>
<label class="sr-only" for="password-input-group"> <label class="visually-hidden" for="password-input-group">
{{ form.password.label }} {{ form.password.label }}
</label> </label>
<div class="input-group mb-3 fade-in"> <div class="input-group mb-3 fade-in">
<div class="input-group-prepend"> <span class="input-group-text text-muted"><i class="icon-lock" aria-hidden="true"></i></span>
<span class="input-group-text"><i class="icon-lock" aria-hidden="true"></i></span>
</div>
{% render_field form.password name='password' class+='form-control' id='password-input-group' placeholder=form.password.label %} {% render_field form.password name='password' class+='form-control' id='password-input-group' placeholder=form.password.label %}
</div> </div>

View File

@ -22,23 +22,19 @@
<p class="mb-0">{% trans "Enter your new password in each field below." %}</p> <p class="mb-0">{% trans "Enter your new password in each field below." %}</p>
</div> </div>
<label class="sr-only" for="password1-input-group"> <label class="visually-hidden" for="password1-input-group">
{{ form.new_password1.label }} {{ form.new_password1.label }}
</label> </label>
<div class="input-group mb-3 fade-in"> <div class="input-group mb-3 fade-in">
<div class="input-group-prepend"> <span class="input-group-text text-muted"><i class="icon-lock" aria-hidden="true"></i></span>
<span class="input-group-text"><i class="icon-lock" aria-hidden="true"></i></span>
</div>
{% render_field form.new_password1 name='new_password1' class+='form-control' id='password1-input-group' %} {% render_field form.new_password1 name='new_password1' class+='form-control' id='password1-input-group' %}
</div> </div>
<label class="sr-only" for="password2-input-group"> <label class="visually-hidden" for="password2-input-group">
{{ form.new_password2.label }} {{ form.new_password2.label }}
</label> </label>
<div class="input-group mb-3 fade-in"> <div class="input-group mb-3 fade-in">
<div class="input-group-prepend"> <span class="input-group-text text-muted"><i class="icon-lock" aria-hidden="true"></i></span>
<span class="input-group-text"><i class="icon-lock" aria-hidden="true"></i></span>
</div>
{% render_field form.new_password2 name='new_password2' class+='form-control' id='password2-input-group' %} {% render_field form.new_password2 name='new_password2' class+='form-control' id='password2-input-group' %}
</div> </div>

View File

@ -17,13 +17,11 @@
<form method="post"> <form method="post">
{% csrf_token %} {% csrf_token %}
<label class="sr-only" for="email-input-group"> <label class="visually-hidden" for="email-input-group">
{{ form.email.label }} {{ form.email.label }}
</label> </label>
<div class="input-group mb-3 fade-in"> <div class="input-group mb-3 fade-in">
<div class="input-group-prepend"> <span class="input-group-text text-muted"><i class="icon-mail" aria-hidden="true"></i></span>
<span class="input-group-text"><i class="icon-mail" aria-hidden="true"></i></span>
</div>
{% render_field form.email name='email' class+='form-control' id='email-input-group' placeholder=form.email.label %} {% render_field form.email name='email' class+='form-control' id='email-input-group' placeholder=form.email.label %}
</div> </div>

View File

@ -0,0 +1,11 @@
# -*- coding: utf-8 -*-
import datetime
from django.test import TestCase
from django.utils.formats import date_format
class FormatsTestCase(TestCase):
def test_short_month_day_format(self):
dt = datetime.datetime(year=2021, month=7, day=31, hour=5, minute=5, second=5)
self.assertEqual(date_format(dt, "SHORT_MONTH_DAY_FORMAT"), "Jul 31")

View File

@ -1,70 +0,0 @@
# -*- coding: utf-8 -*-
import datetime
from django.core.exceptions import ValidationError
from django.forms.fields import DateTimeField
from django.test import TestCase, override_settings # , tag
from django.utils.formats import date_format, time_format
from babybuddy.middleware import update_en_gb_date_formats
class GbFormatsTestCase(TestCase):
@override_settings(LANGUAGE_CODE="en-GB")
def test_datetime_input_formats(self):
update_en_gb_date_formats()
field = DateTimeField()
supported_custom_examples = [
"20/01/2020",
"20/01/2020 9:30 AM",
"20/01/2020 9:30:03 AM",
"01/10/2020 11:30 PM",
"01/10/2020 11:30:03 AM",
]
for example in supported_custom_examples:
try:
result = field.to_python(example)
self.assertIsInstance(result, datetime.datetime)
except ValidationError:
self.fail('Format of "{}" not recognized!'.format(example))
with self.assertRaises(ValidationError):
field.to_python("invalid date string!")
# @tag('isolate')
@override_settings(LANGUAGE_CODE="en-GB", USE_24_HOUR_TIME_FORMAT=True)
def test_use_24_hour_time_format(self):
update_en_gb_date_formats()
field = DateTimeField()
supported_custom_examples = [
"25/10/2006 2:30:59",
"25/10/2006 2:30",
"25/10/2006 14:30:59",
"25/10/2006 14:30",
]
for example in supported_custom_examples:
try:
result = field.to_python(example)
self.assertIsInstance(result, datetime.datetime)
except ValidationError:
self.fail('Format of "{}" not recognized!'.format(example))
with self.assertRaises(ValidationError):
field.to_python("invalid date string!")
dt = datetime.datetime(year=2011, month=11, day=4, hour=23, minute=5, second=59)
self.assertEqual(date_format(dt, "DATETIME_FORMAT"), "4 November 2011 23:05:59")
dt = datetime.datetime(year=2011, month=11, day=4, hour=2, minute=5, second=59)
self.assertEqual(date_format(dt, "SHORT_DATETIME_FORMAT"), "04/11/2011 02:05")
t = datetime.time(hour=16, minute=2, second=25)
self.assertEqual(time_format(t), "16:02")
# def test_short_month_day_format(self):
# update_en_gb_date_formats()
# dt = datetime.datetime(year=2021, month=7, day=31, hour=5, minute=5,
# second=5)
# self.assertEqual(date_format(dt, 'SHORT_MONTH_DAY_FORMAT'), '31 Jul')

View File

@ -1,67 +0,0 @@
# -*- coding: utf-8 -*-
import datetime
from django.core.exceptions import ValidationError
from django.forms.fields import DateTimeField
from django.test import TestCase, override_settings, tag
from django.utils.formats import date_format, time_format
from babybuddy.middleware import update_en_us_date_formats
class FormatsTestCase(TestCase):
def test_datetime_input_formats(self):
update_en_us_date_formats()
field = DateTimeField()
supported_custom_examples = [
"01/20/2020 9:30 AM",
"01/20/2020 9:30:03 AM",
"10/01/2020 11:30 PM",
"10/01/2020 11:30:03 AM",
]
for example in supported_custom_examples:
try:
result = field.to_python(example)
self.assertIsInstance(result, datetime.datetime)
except ValidationError:
self.fail('Format of "{}" not recognized!'.format(example))
with self.assertRaises(ValidationError):
field.to_python("invalid date string!")
@tag("isolate")
@override_settings(LANGUAGE_CODE="en-US", USE_24_HOUR_TIME_FORMAT=True)
def test_use_24_hour_time_format(self):
update_en_us_date_formats()
field = DateTimeField()
supported_custom_examples = [
"10/25/2006 2:30:59",
"10/25/2006 2:30",
"10/25/2006 14:30:59",
"10/25/2006 14:30",
]
for example in supported_custom_examples:
try:
result = field.to_python(example)
self.assertIsInstance(result, datetime.datetime)
except ValidationError:
self.fail('Format of "{}" not recognized!'.format(example))
with self.assertRaises(ValidationError):
field.to_python("invalid date string!")
dt = datetime.datetime(year=2011, month=11, day=4, hour=23, minute=5, second=59)
self.assertEqual(date_format(dt, "DATETIME_FORMAT"), "Nov. 4, 2011, 23:05:59")
dt = datetime.datetime(year=2011, month=11, day=4, hour=2, minute=5, second=59)
self.assertEqual(date_format(dt, "SHORT_DATETIME_FORMAT"), "11/04/2011 2:05:59")
t = datetime.time(hour=16, minute=2, second=25)
self.assertEqual(time_format(t), "16:02:25")
def test_short_month_day_format(self):
update_en_us_date_formats()
dt = datetime.datetime(year=2021, month=7, day=31, hour=5, minute=5, second=5)
self.assertEqual(date_format(dt, "SHORT_MONTH_DAY_FORMAT"), "Jul 31")

View File

@ -222,11 +222,10 @@ class FormsTestCase(TestCase):
page = self.c.post("/user/settings/", data=params, follow=True) page = self.c.post("/user/settings/", data=params, follow=True)
self.assertContains(page, "Paramètres utilisateur") self.assertContains(page, "Paramètres utilisateur")
@override_settings(TIME_ZONE="US/Eastern")
def test_user_settings_timezone(self): def test_user_settings_timezone(self):
self.c.login(**self.credentials) self.c.login(**self.credentials)
self.assertEqual(timezone.get_default_timezone_name(), "US/Eastern") self.assertEqual(timezone.get_default_timezone_name(), "UTC")
params = self.settings_template.copy() params = self.settings_template.copy()
params["timezone"] = "US/Pacific" params["timezone"] = "US/Pacific"
page = self.c.post("/user/settings/", data=params, follow=True) page = self.c.post("/user/settings/", data=params, follow=True)

View File

@ -0,0 +1,73 @@
# -*- coding: utf-8 -*-
from django.contrib.auth import get_user_model
from django.core.management import call_command
from django.test import Client as HttpClient, TestCase
from faker import Faker
from core.models import Sleep
class SiteSettingsTestCase(TestCase):
@classmethod
def setUpClass(cls):
super(SiteSettingsTestCase, cls).setUpClass()
fake = Faker()
call_command("migrate", verbosity=0)
call_command("fake", verbosity=0)
cls.c = HttpClient()
fake_user = fake.simple_profile()
cls.credentials = {
"username": fake_user["username"],
"password": fake.password(),
}
cls.user = get_user_model().objects.create_user(
is_superuser=True, is_staff=True, **cls.credentials
)
def test_default_settings(self):
self.c.login(**self.credentials)
page = self.c.get("/settings/")
self.assertEqual(page.status_code, 200)
self.assertEqual(
page.context["form"]["core.models__Sleep__nap_start_max"].value(),
"18:00:00",
)
self.assertEqual(
page.context["form"]["core.models__Sleep__nap_start_min"].value(),
"06:00:00",
)
def test_nap_start_settings(self):
self.c.login(**self.credentials)
self.assert_naps()
params = {
"core.models__Sleep__nap_start_max": "20:00:00",
"core.models__Sleep__nap_start_min": "09:00:00",
}
page = self.c.post("/settings/", params, follow=True)
self.assertEqual(page.status_code, 200)
self.assertEqual(
Sleep.settings.nap_start_max.strftime("%H:%M:%S"),
params["core.models__Sleep__nap_start_max"],
)
self.assertEqual(
Sleep.settings.nap_start_min.strftime("%H:%M:%S"),
params["core.models__Sleep__nap_start_min"],
)
self.assert_naps()
def assert_naps(self):
"""
Asserts sleep instances filtered with nap start min and max match nap instances.
"""
instances = Sleep.objects.filter(
start__time__range=(
Sleep.settings.nap_start_min.strftime("%H:%M:%S"),
Sleep.settings.nap_start_max.strftime("%H:%M:%S"),
)
)
naps = Sleep.naps.all()
self.assertQuerySetEqual(instances, naps)

View File

@ -44,6 +44,7 @@ app_patterns = [
path("user/password/", views.UserPassword.as_view(), name="user-password"), path("user/password/", views.UserPassword.as_view(), name="user-password"),
path("user/settings/", views.UserSettings.as_view(), name="user-settings"), path("user/settings/", views.UserSettings.as_view(), name="user-settings"),
path("user/add-device/", views.UserAddDevice.as_view(), name="user-add-device"), path("user/add-device/", views.UserAddDevice.as_view(), name="user-add-device"),
path("settings/", include("dbsettings.urls")),
] ]
urlpatterns = [ urlpatterns = [

29
babybuddy/widgets.py Normal file
View File

@ -0,0 +1,29 @@
import datetime
from django.forms import widgets
class DateTimeBaseInput(widgets.DateTimeBaseInput):
def format_value(self, value):
if isinstance(value, datetime.datetime):
value = value.isoformat()
return value
class DateTimeInput(DateTimeBaseInput):
input_type = "datetime-local"
def build_attrs(self, base_attrs, extra_attrs=None):
attrs = super().build_attrs(base_attrs, extra_attrs)
# Default to seconds granularity. Required for client validation in Safari.
if "step" not in attrs:
attrs["step"] = 1
return attrs
class DateInput(DateTimeBaseInput):
input_type = "date"
class TimeInput(DateTimeBaseInput):
input_type = "time"

View File

@ -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")

View File

@ -7,6 +7,7 @@ from django.utils.translation import gettext as _
from taggit.forms import TagField from taggit.forms import TagField
from babybuddy.widgets import DateInput, DateTimeInput
from core import models from core import models
from core.widgets import TagsEditor, ChildRadioSelect from core.widgets import TagsEditor, ChildRadioSelect
@ -44,7 +45,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 +84,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()
@ -97,12 +98,7 @@ class ChildForm(forms.ModelForm):
if settings.BABY_BUDDY["ALLOW_UPLOADS"]: if settings.BABY_BUDDY["ALLOW_UPLOADS"]:
fields.append("picture") fields.append("picture")
widgets = { widgets = {
"birth_date": forms.DateInput( "birth_date": DateInput(),
attrs={
"autocomplete": "off",
"data-target": "#datetimepicker_birth_date",
}
),
} }
@ -144,12 +140,7 @@ class PumpingForm(CoreModelForm, TaggableModelForm):
fields = ["child", "amount", "time", "notes", "tags"] fields = ["child", "amount", "time", "notes", "tags"]
widgets = { widgets = {
"child": ChildRadioSelect, "child": ChildRadioSelect,
"time": forms.DateTimeInput( "time": DateTimeInput(),
attrs={
"autocomplete": "off",
"data-target": "#datetimepicker_time",
}
),
"notes": forms.Textarea(attrs={"rows": 5}), "notes": forms.Textarea(attrs={"rows": 5}),
} }
@ -160,12 +151,7 @@ class DiaperChangeForm(CoreModelForm, TaggableModelForm):
fields = ["child", "time", "wet", "solid", "color", "amount", "notes", "tags"] fields = ["child", "time", "wet", "solid", "color", "amount", "notes", "tags"]
widgets = { widgets = {
"child": ChildRadioSelect(), "child": ChildRadioSelect(),
"time": forms.DateTimeInput( "time": DateTimeInput(),
attrs={
"autocomplete": "off",
"data-target": "#datetimepicker_time",
}
),
"notes": forms.Textarea(attrs={"rows": 5}), "notes": forms.Textarea(attrs={"rows": 5}),
} }
@ -176,18 +162,8 @@ class FeedingForm(CoreModelForm, TaggableModelForm):
fields = ["child", "start", "end", "type", "method", "amount", "notes", "tags"] fields = ["child", "start", "end", "type", "method", "amount", "notes", "tags"]
widgets = { widgets = {
"child": ChildRadioSelect, "child": ChildRadioSelect,
"start": forms.DateTimeInput( "start": DateTimeInput(),
attrs={ "end": DateTimeInput(),
"autocomplete": "off",
"data-target": "#datetimepicker_start",
}
),
"end": forms.DateTimeInput(
attrs={
"autocomplete": "off",
"data-target": "#datetimepicker_end",
}
),
"notes": forms.Textarea(attrs={"rows": 5}), "notes": forms.Textarea(attrs={"rows": 5}),
} }
@ -198,12 +174,7 @@ class NoteForm(CoreModelForm, TaggableModelForm):
fields = ["child", "note", "time", "tags"] fields = ["child", "note", "time", "tags"]
widgets = { widgets = {
"child": ChildRadioSelect, "child": ChildRadioSelect,
"time": forms.DateTimeInput( "time": DateTimeInput(),
attrs={
"autocomplete": "off",
"data-target": "#datetimepicker_time",
}
),
} }
@ -213,18 +184,8 @@ class SleepForm(CoreModelForm, TaggableModelForm):
fields = ["child", "start", "end", "notes", "tags"] fields = ["child", "start", "end", "notes", "tags"]
widgets = { widgets = {
"child": ChildRadioSelect, "child": ChildRadioSelect,
"start": forms.DateTimeInput( "start": DateTimeInput(),
attrs={ "end": DateTimeInput(),
"autocomplete": "off",
"data-target": "#datetimepicker_start",
}
),
"end": forms.DateTimeInput(
attrs={
"autocomplete": "off",
"data-target": "#datetimepicker_end",
}
),
"notes": forms.Textarea(attrs={"rows": 5}), "notes": forms.Textarea(attrs={"rows": 5}),
} }
@ -235,12 +196,7 @@ class TemperatureForm(CoreModelForm, TaggableModelForm):
fields = ["child", "temperature", "time", "notes", "tags"] fields = ["child", "temperature", "time", "notes", "tags"]
widgets = { widgets = {
"child": ChildRadioSelect, "child": ChildRadioSelect,
"time": forms.DateTimeInput( "time": DateTimeInput(),
attrs={
"autocomplete": "off",
"data-target": "#datetimepicker_time",
}
),
"notes": forms.Textarea(attrs={"rows": 5}), "notes": forms.Textarea(attrs={"rows": 5}),
} }
@ -251,12 +207,7 @@ class TimerForm(CoreModelForm):
fields = ["child", "name", "start"] fields = ["child", "name", "start"]
widgets = { widgets = {
"child": ChildRadioSelect, "child": ChildRadioSelect,
"start": forms.DateTimeInput( "start": DateTimeInput(),
attrs={
"autocomplete": "off",
"data-target": "#datetimepicker_start",
}
),
} }
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -276,18 +227,8 @@ class TummyTimeForm(CoreModelForm, TaggableModelForm):
fields = ["child", "start", "end", "milestone", "tags"] fields = ["child", "start", "end", "milestone", "tags"]
widgets = { widgets = {
"child": ChildRadioSelect, "child": ChildRadioSelect,
"start": forms.DateTimeInput( "start": DateTimeInput(),
attrs={ "end": DateTimeInput(),
"autocomplete": "off",
"data-target": "#datetimepicker_start",
}
),
"end": forms.DateTimeInput(
attrs={
"autocomplete": "off",
"data-target": "#datetimepicker_end",
}
),
} }
@ -297,12 +238,7 @@ class WeightForm(CoreModelForm, TaggableModelForm):
fields = ["child", "weight", "date", "notes", "tags"] fields = ["child", "weight", "date", "notes", "tags"]
widgets = { widgets = {
"child": ChildRadioSelect, "child": ChildRadioSelect,
"date": forms.DateInput( "date": DateInput(),
attrs={
"autocomplete": "off",
"data-target": "#datetimepicker_date",
}
),
"notes": forms.Textarea(attrs={"rows": 5}), "notes": forms.Textarea(attrs={"rows": 5}),
} }
@ -313,12 +249,7 @@ class HeightForm(CoreModelForm, TaggableModelForm):
fields = ["child", "height", "date", "notes", "tags"] fields = ["child", "height", "date", "notes", "tags"]
widgets = { widgets = {
"child": ChildRadioSelect, "child": ChildRadioSelect,
"date": forms.DateInput( "date": DateInput(),
attrs={
"autocomplete": "off",
"data-target": "#datetimepicker_date",
}
),
"notes": forms.Textarea(attrs={"rows": 5}), "notes": forms.Textarea(attrs={"rows": 5}),
} }
@ -329,12 +260,7 @@ class HeadCircumferenceForm(CoreModelForm, TaggableModelForm):
fields = ["child", "head_circumference", "date", "notes", "tags"] fields = ["child", "head_circumference", "date", "notes", "tags"]
widgets = { widgets = {
"child": ChildRadioSelect, "child": ChildRadioSelect,
"date": forms.DateInput( "date": DateInput(),
attrs={
"autocomplete": "off",
"data-target": "#datetimepicker_date",
}
),
"notes": forms.Textarea(attrs={"rows": 5}), "notes": forms.Textarea(attrs={"rows": 5}),
} }
@ -345,12 +271,7 @@ class BMIForm(CoreModelForm, TaggableModelForm):
fields = ["child", "bmi", "date", "notes", "tags"] fields = ["child", "bmi", "date", "notes", "tags"]
widgets = { widgets = {
"child": ChildRadioSelect, "child": ChildRadioSelect,
"date": forms.DateInput( "date": DateInput(),
attrs={
"autocomplete": "off",
"data-target": "#datetimepicker_date",
}
),
"notes": forms.Textarea(attrs={"rows": 5}), "notes": forms.Textarea(attrs={"rows": 5}),
} }

View File

@ -0,0 +1,62 @@
# Generated by Django 4.0.4 on 2022-06-10 03:34
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
("core", "0025_pumping_tags"),
]
operations = [
migrations.AlterField(
model_name="feeding",
name="end",
field=models.DateTimeField(
default=django.utils.timezone.localtime, verbose_name="End time"
),
),
migrations.AlterField(
model_name="feeding",
name="start",
field=models.DateTimeField(
default=django.utils.timezone.localtime, verbose_name="Start time"
),
),
migrations.AlterField(
model_name="pumping",
name="time",
field=models.DateTimeField(
default=django.utils.timezone.localtime, verbose_name="Time"
),
),
migrations.AlterField(
model_name="sleep",
name="end",
field=models.DateTimeField(
default=django.utils.timezone.localtime, verbose_name="End time"
),
),
migrations.AlterField(
model_name="sleep",
name="start",
field=models.DateTimeField(
default=django.utils.timezone.localtime, verbose_name="Start time"
),
),
migrations.AlterField(
model_name="tummytime",
name="end",
field=models.DateTimeField(
default=django.utils.timezone.localtime, verbose_name="End time"
),
),
migrations.AlterField(
model_name="tummytime",
name="start",
field=models.DateTimeField(
default=django.utils.timezone.localtime, verbose_name="Start time"
),
),
]

View File

@ -0,0 +1,37 @@
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
),
]

View File

@ -2,7 +2,6 @@
import re import re
from datetime import timedelta from datetime import timedelta
from django.conf import settings
from django.core.cache import cache from django.core.cache import cache
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator from django.core.validators import RegexValidator
@ -13,6 +12,7 @@ from django.utils.translation import gettext_lazy as _
from taggit.managers import TaggableManager as TaggitTaggableManager from taggit.managers import TaggableManager as TaggitTaggableManager
from taggit.models import GenericTaggedItemBase, TagBase from taggit.models import GenericTaggedItemBase, TagBase
from babybuddy.site_settings import NapSettings
from core.utils import random_color from core.utils import random_color
@ -273,8 +273,15 @@ class Feeding(models.Model):
related_name="feeding", related_name="feeding",
verbose_name=_("Child"), verbose_name=_("Child"),
) )
start = models.DateTimeField(blank=False, null=False, verbose_name=_("Start time")) start = models.DateTimeField(
end = models.DateTimeField(blank=False, null=False, verbose_name=_("End time")) blank=False,
default=timezone.localtime,
null=False,
verbose_name=_("Start time"),
)
end = models.DateTimeField(
blank=False, default=timezone.localtime, null=False, verbose_name=_("End time")
)
duration = models.DurationField( duration = models.DurationField(
editable=False, null=True, verbose_name=_("Duration") editable=False, null=True, verbose_name=_("Duration")
) )
@ -426,7 +433,9 @@ class Pumping(models.Model):
verbose_name=_("Child"), verbose_name=_("Child"),
) )
amount = models.FloatField(blank=False, null=False, verbose_name=_("Amount")) amount = models.FloatField(blank=False, null=False, verbose_name=_("Amount"))
time = models.DateTimeField(blank=False, null=False, verbose_name=_("Time")) time = models.DateTimeField(
blank=False, default=timezone.localtime, null=False, verbose_name=_("Time")
)
notes = models.TextField(blank=True, null=True, verbose_name=_("Notes")) notes = models.TextField(blank=True, null=True, verbose_name=_("Notes"))
tags = TaggableManager(blank=True, through=Tagged) tags = TaggableManager(blank=True, through=Tagged)
@ -451,8 +460,15 @@ class Sleep(models.Model):
"Child", on_delete=models.CASCADE, related_name="sleep", verbose_name=_("Child") "Child", on_delete=models.CASCADE, related_name="sleep", verbose_name=_("Child")
) )
napping = models.BooleanField(editable=False, null=True, verbose_name=_("Napping")) napping = models.BooleanField(editable=False, null=True, verbose_name=_("Napping"))
start = models.DateTimeField(blank=False, null=False, verbose_name=_("Start time")) start = models.DateTimeField(
end = models.DateTimeField(blank=False, null=False, verbose_name=_("End time")) blank=False,
default=timezone.localtime,
null=False,
verbose_name=_("Start time"),
)
end = models.DateTimeField(
blank=False, default=timezone.localtime, null=False, verbose_name=_("End time")
)
duration = models.DurationField( duration = models.DurationField(
editable=False, null=True, verbose_name=_("Duration") editable=False, null=True, verbose_name=_("Duration")
) )
@ -461,6 +477,7 @@ class Sleep(models.Model):
objects = models.Manager() objects = models.Manager()
naps = NapsManager() naps = NapsManager()
settings = NapSettings(_("Nap settings"))
class Meta: class Meta:
default_permissions = ("view", "add", "change", "delete") default_permissions = ("view", "add", "change", "delete")
@ -473,14 +490,12 @@ class Sleep(models.Model):
@property @property
def nap(self): def nap(self):
nap_start_min = timezone.datetime.strptime(
settings.BABY_BUDDY["NAP_START_MIN"], "%H:%M"
).time()
nap_start_max = timezone.datetime.strptime(
settings.BABY_BUDDY["NAP_START_MAX"], "%H:%M"
).time()
local_start_time = timezone.localtime(self.start).time() local_start_time = timezone.localtime(self.start).time()
return nap_start_min <= local_start_time <= nap_start_max return (
Sleep.settings.nap_start_min
<= local_start_time
<= Sleep.settings.nap_start_max
)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if self.start and self.end: if self.start and self.end:
@ -543,12 +558,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",
@ -561,7 +570,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")
@ -584,42 +593,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):
@ -630,8 +621,15 @@ class TummyTime(models.Model):
related_name="tummy_time", related_name="tummy_time",
verbose_name=_("Child"), verbose_name=_("Child"),
) )
start = models.DateTimeField(blank=False, null=False, verbose_name=_("Start time")) start = models.DateTimeField(
end = models.DateTimeField(blank=False, null=False, verbose_name=_("End time")) blank=False,
default=timezone.localtime,
null=False,
verbose_name=_("Start time"),
)
end = models.DateTimeField(
blank=False, default=timezone.localtime, null=False, verbose_name=_("End time")
)
duration = models.DurationField( duration = models.DurationField(
editable=False, null=True, verbose_name=_("Duration") editable=False, null=True, verbose_name=_("Duration")
) )

View File

@ -10,7 +10,7 @@ BabyBuddy.Timer = function ($) {
var runIntervalId = null; var runIntervalId = null;
var timerId = null; var timerId = null;
var timerElement = null; var timerElement = null;
var lastUpdate = moment(); var lastUpdate = new Date();
var hidden = null; var hidden = null;
var Timer = { var Timer = {
@ -18,14 +18,14 @@ BabyBuddy.Timer = function ($) {
timerId = timer_id; timerId = timer_id;
timerElement = $('#' + element_id); timerElement = $('#' + element_id);
if (timerElement.length == 0) { if (timerElement.length === 0) {
console.error('BBTimer: Timer element not found.'); console.error('BBTimer: Timer element not found.');
return false; return false;
} }
if (timerElement.find('.timer-seconds').length == 0 if (timerElement.find('.timer-seconds').length === 0
|| timerElement.find('.timer-minutes').length == 0 || timerElement.find('.timer-minutes').length === 0
|| timerElement.find('.timer-hours').length == 0) { || timerElement.find('.timer-hours').length === 0) {
console.error('BBTimer: Element does not contain expected children.'); console.error('BBTimer: Element does not contain expected children.');
return false; return false;
} }
@ -48,7 +48,7 @@ BabyBuddy.Timer = function ($) {
}, },
handleVisibilityChange: function() { handleVisibilityChange: function() {
if (!document[hidden] && moment().diff(lastUpdate) > 10000) { if (!document[hidden] && (new Date()) - lastUpdate > 1) {
Timer.update(); Timer.update();
} }
}, },
@ -83,18 +83,17 @@ BabyBuddy.Timer = function ($) {
$.get('/api/timers/' + timerId + '/', function(data) { $.get('/api/timers/' + timerId + '/', function(data) {
if (data && 'duration' in data) { if (data && 'duration' in data) {
clearInterval(runIntervalId); clearInterval(runIntervalId);
var duration = moment.duration(data.duration); var duration = data.duration.split(/[\s:.]/)
timerElement.find('.timer-hours').text(duration.hours()); if (duration.length === 5) {
timerElement.find('.timer-minutes').text(duration.minutes()); duration[0] = parseInt(duration[0]) * 24 + parseInt(duration[1]);
timerElement.find('.timer-seconds').text(duration.seconds()); duration[1] = duration[2];
lastUpdate = moment(); duration[2] = duration[3];
if (data['active']) {
runIntervalId = setInterval(Timer.tick, 1000);
}
else {
timerElement.addClass('timer-stopped');
} }
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);
} }
}); });
} }

View File

@ -4,21 +4,6 @@
} }
} }
#view-core\:child-list {
.picture-column {
text-align: center;
width: 65px;
min-width: 65px;
}
.child-list {
th,
td {
vertical-align: middle;
}
}
}
@include media-breakpoint-up(md) { @include media-breakpoint-up(md) {
#view-core\:child { #view-core\:child {
.child-detail-column { .child-detail-column {

View File

@ -1,3 +1,5 @@
@use 'sass:map';
// Adapted for Bootstrap 4 from https://www.bootply.com/SzXin8KDZJ. // Adapted for Bootstrap 4 from https://www.bootply.com/SzXin8KDZJ.
$card-shadow: rgba(0, 0, 0, .175); $card-shadow: rgba(0, 0, 0, .175);
@ -12,7 +14,7 @@ $card-shadow: rgba(0, 0, 0, .175);
position: absolute; position: absolute;
content: ' '; content: ' ';
width: 3px; width: 3px;
background-color: theme-color('primary'); background-color: map.get($theme-colors, 'primary');
left: 50%; left: 50%;
margin-left: -1.5px; margin-left: -1.5px;
@ -34,7 +36,7 @@ $card-shadow: rgba(0, 0, 0, .175);
} }
.card { .card {
width: 47%; width: 46%;
float: left; float: left;
position: relative; position: relative;
box-shadow: 0 1px 6px $card-shadow; box-shadow: 0 1px 6px $card-shadow;
@ -45,8 +47,8 @@ $card-shadow: rgba(0, 0, 0, .175);
right: -15px; right: -15px;
display: inline-block; display: inline-block;
border-top: 15px solid transparent; border-top: 15px solid transparent;
border-left: 15px solid theme-color('dark'); border-left: 15px solid map.get($theme-colors, 'dark');
border-right: 0 solid theme-color('dark'); border-right: 0 solid map.get($theme-colors, 'dark');
border-bottom: 15px solid transparent; border-bottom: 15px solid transparent;
content: ' '; content: ' ';
} }
@ -57,8 +59,8 @@ $card-shadow: rgba(0, 0, 0, .175);
right: -14px; right: -14px;
display: inline-block; display: inline-block;
border-top: 14px solid transparent; border-top: 14px solid transparent;
border-left: 14px solid theme-color('dark'); border-left: 14px solid map.get($theme-colors, 'dark');
border-right: 0 solid theme-color('dark'); border-right: 0 solid map.get($theme-colors, 'dark');
border-bottom: 14px solid transparent; border-bottom: 14px solid transparent;
content: ' '; content: ' ';
} }
@ -95,7 +97,7 @@ $card-shadow: rgba(0, 0, 0, .175);
top: 16px; top: 16px;
left: 50%; left: 50%;
margin-left: -25px; margin-left: -25px;
background-color: theme-color('dark'); background-color: map.get($theme-colors, 'dark');
z-index: 100; z-index: 100;
border-radius: 50%; border-radius: 50%;
@ -110,7 +112,7 @@ $card-shadow: rgba(0, 0, 0, .175);
top: 26px; top: 26px;
left: 50%; left: 50%;
margin-left: -25px; margin-left: -25px;
background-color: theme-color('dark'); background-color: map.get($theme-colors, 'dark');
z-index: 100; z-index: 100;
border-radius: 50%; border-radius: 50%;
} }

View File

@ -1,16 +1,7 @@
@use 'sass:map';
#timer-status { #timer-status {
font-size: $h1-font-size;
font-weight: $display1-weight;
&.timer-stopped { &.timer-stopped {
color: theme-color('danger'); color: map.get($theme-colors, 'dark');
}
}
@include media-breakpoint-up(sm) {
#view-core\:timer-detail {
#timer-status {
font-size: $display1-size;
}
} }
} }

View File

@ -28,12 +28,3 @@
{% endif %} {% endif %}
{% include 'babybuddy/form.html' %} {% include 'babybuddy/form.html' %}
{% endblock %} {% endblock %}
{% block javascript %}
<script type="text/javascript">
BabyBuddy.DatetimePicker.init($('#datetimepicker_date'), {
format: 'L',
extraFormats: ['YYYY-MM-DD']
});
</script>
{% endblock %}

View File

@ -18,8 +18,8 @@
</h1> </h1>
{% include 'babybuddy/filter.html' %} {% include 'babybuddy/filter.html' %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-instances table-striped table-hover"> <table class="table table-instances table-borderless table-striped table-hover align-middle">
<thead class="thead-inverse"> <thead>
<tr> <tr>
<th>{% trans "Actions" %}</th> <th>{% trans "Actions" %}</th>
<th>{% trans "Date" %}</th> <th>{% trans "Date" %}</th>
@ -32,8 +32,7 @@
</thead> </thead>
<tbody> <tbody>
{% for bmi in object_list %} {% for bmi in object_list %}
{% cycle "odd" "even" as row_class silent %} <tr>
<tr class="{{ row_class }}">
<td> <td>
<div class="btn-group btn-group-sm" role="group" aria-label="{% trans "Actions" %}"> <div class="btn-group btn-group-sm" role="group" aria-label="{% trans "Actions" %}">
@ -62,7 +61,7 @@
</tr> </tr>
{% if bmi.notes %} {% if bmi.notes %}
<tr class="{{ row_class }} row-details"> <tr class="{{ row_class }} row-details">
<td colspan="5"><i class="icon-note mr-2" aria-hidden="true"></i>{{ bmi.notes }}</td> <td colspan="5"><i class="icon-note me-2" aria-hidden="true"></i>{{ bmi.notes }}</td>
</tr> </tr>
{% endif %} {% endif %}
{% empty %} {% empty %}

View File

@ -5,7 +5,7 @@
{% block breadcrumbs %} {% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'core:child-list' %}">{% trans "Children" %}</a></li> <li class="breadcrumb-item"><a href="{% url 'core:child-list' %}">{% trans "Children" %}</a></li>
<li class="breadcrumb-item font-weight-bold"><a href="{% url 'core:child' object.slug %}">{{ object }}</a></li> <li class="breadcrumb-item fw-bold"><a href="{% url 'core:child' object.slug %}">{{ object }}</a></li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Delete" %}</li> <li class="breadcrumb-item active" aria-current="page">{% trans "Delete" %}</li>
{% endblock %} {% endblock %}

View File

@ -5,7 +5,7 @@
{% block breadcrumbs %} {% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'core:child-list' %}">{% trans "Children" %}</a></li> <li class="breadcrumb-item"><a href="{% url 'core:child-list' %}">{% trans "Children" %}</a></li>
<li class="breadcrumb-item font-weight-bold"> <li class="breadcrumb-item fw-bold">
{% child_quick_switch object 'core:child' %} {% child_quick_switch object 'core:child' %}
</li> </li>
{% endblock %} {% endblock %}

View File

@ -12,7 +12,7 @@
{% block breadcrumbs %} {% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'core:child-list' %}">{% trans "Children" %}</a></li> <li class="breadcrumb-item"><a href="{% url 'core:child-list' %}">{% trans "Children" %}</a></li>
{% if object %} {% if object %}
<li class="breadcrumb-item font-weight-bold"><a href="{% url 'core:child' object.slug %}">{{ object }}</a></li> <li class="breadcrumb-item fw-bold"><a href="{% url 'core:child' object.slug %}">{{ object }}</a></li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Update" %}</li> <li class="breadcrumb-item active" aria-current="page">{% trans "Update" %}</li>
{% else %} {% else %}
<li class="breadcrumb-item active" aria-current="page">{% trans "Add a Child" %}</li> <li class="breadcrumb-item active" aria-current="page">{% trans "Add a Child" %}</li>
@ -28,14 +28,4 @@
<h1>{% trans "Add a Child" %}</h1> <h1>{% trans "Add a Child" %}</h1>
{% endif %} {% endif %}
{% include 'babybuddy/form.html' %} {% include 'babybuddy/form.html' %}
{% endblock %}
{% block javascript %}
<script type="text/javascript">
BabyBuddy.DatetimePicker.init($('#datetimepicker_birth_date'), {
format: 'L',
viewMode: 'years',
extraFormats: ['YYYY-MM-DD']
});
</script>
{% endblock %} {% endblock %}

View File

@ -18,10 +18,10 @@
</h1> </h1>
{% include 'babybuddy/filter.html' %} {% include 'babybuddy/filter.html' %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-striped table-hover child-list"> <table class="table table-borderless table-striped table-hover align-middle">
<thead class="thead-inverse"> <thead>
<tr> <tr>
<th class="picture-column"><i class="icon-camera" aria-hidden="true"></i></th> <th><i class="icon-camera" aria-hidden="true"></i></th>
<th>{% trans "First Name" %}</th> <th>{% trans "First Name" %}</th>
<th>{% trans "Last Name" %}</th> <th>{% trans "Last Name" %}</th>
<th>{% trans "Birth Date" %}</th> <th>{% trans "Birth Date" %}</th>
@ -31,8 +31,12 @@
<tbody> <tbody>
{% for child in object_list %} {% for child in object_list %}
<tr> <tr>
<td class="picture-column"> <td>
{% include "core/child_thumbnail.html" %} {% if child.picture %}
{% include "core/child_thumbnail.html" %}
{% else %}
<img src="{% static 'babybuddy/img/core/child-placeholder.png' %}" width="40" height="40" class="img-fluid rounded-circle" />
{% endif %}
</td> </td>
<th scope="row"> <th scope="row">
<a href="{% url 'core:child' child.slug %}">{{ child.first_name }}</a> <a href="{% url 'core:child' child.slug %}">{{ child.first_name }}</a>

View File

@ -2,8 +2,8 @@
<a href="{% url 'core:child' current_child.slug %}">{{ current_child }}</a> <a href="{% url 'core:child' current_child.slug %}">{{ current_child }}</a>
{% if children.count > 0 %} {% if children.count > 0 %}
<a href="#" class="ml-1 pl-1 pr-1 dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <a href="#" class="ms-1 ps-1 pe-1 dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="sr-only">{% trans 'Switch child' %}</span> <span class="visually-hidden">{% trans 'Switch child' %}</span>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<path d="M3.626 6.832A.5.5 0 0 1 4 6h8a.5.5 0 0 1 .374.832l-4 4.5a.5.5 0 0 1-.748 0l-4-4.5z"></path> <path d="M3.626 6.832A.5.5 0 0 1 4 6h8a.5.5 0 0 1 .374.832l-4 4.5a.5.5 0 0 1-.748 0l-4-4.5z"></path>
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2zm15 0a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2z"></path> <path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2zm15 0a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2z"></path>
@ -14,7 +14,7 @@
{% for child in children %} {% for child in children %}
<a class="dropdown-item d-flex align-items-center" href="{% url target_url child.slug %}"> <a class="dropdown-item d-flex align-items-center" href="{% url target_url child.slug %}">
{% include "core/child_thumbnail.html" %} {% include "core/child_thumbnail.html" %}
<span class="text-wrap ml-2">{{ child }}</span> <span class="text-wrap ms-2">{{ child }}</span>
</a> </a>
{% endfor %} {% endfor %}
</div> </div>

View File

@ -1,10 +1,7 @@
{% load imagekit static %} {% load imagekit static %}
{% if widget.wrap_label %}
<label{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %} class="btn btn-outline-light btn-no-hover">
{% endif %}
{% include "django/forms/widgets/input.html" %} {% include "django/forms/widgets/input.html" %}
{% if widget.wrap_label %} <label{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %} class="btn btn-outline-light btn-no-hover">
{% include "core/child_thumbnail.html" with child=widget %} {% include "core/child_thumbnail.html" with child=widget %}
{{ widget.label }}</label> {{ widget.label }}
{% endif %} </label>

View File

@ -28,11 +28,3 @@
{% endif %} {% endif %}
{% include 'babybuddy/form.html' %} {% include 'babybuddy/form.html' %}
{% endblock %} {% endblock %}
{% block javascript %}
<script type="text/javascript">
BabyBuddy.DatetimePicker.init($('#datetimepicker_time'), {
format: '{% datetimepicker_format %}'
});
</script>
{% endblock %}

View File

@ -18,8 +18,8 @@
</h1> </h1>
{% include 'babybuddy/filter.html' %} {% include 'babybuddy/filter.html' %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-instances table-striped table-hover"> <table class="table table-instances table-borderless table-striped table-hover align-middle">
<thead class="thead-inverse"> <thead>
<tr> <tr>
<th>{% trans "Actions" %}</th> <th>{% trans "Actions" %}</th>
<th>{% trans "Time" %}</th> <th>{% trans "Time" %}</th>
@ -34,8 +34,7 @@
</thead> </thead>
<tbody> <tbody>
{% for change in object_list %} {% for change in object_list %}
{% cycle "odd" "even" as row_class silent %} <tr>
<tr class="{{ row_class }}">
<td> <td>
<div class="btn-group btn-group-sm" role="group" aria-label="{% trans "Actions" %}"> <div class="btn-group btn-group-sm" role="group" aria-label="{% trans "Actions" %}">
@ -69,7 +68,7 @@
</tr> </tr>
{% if change.notes %} {% if change.notes %}
<tr class="{{ row_class }} row-details"> <tr class="{{ row_class }} row-details">
<td colspan="7"><i class="icon-note mr-2" aria-hidden="true"></i>{{ change.notes }}</td> <td colspan="7"><i class="icon-note me-2" aria-hidden="true"></i>{{ change.notes }}</td>
</tr> </tr>
{% endif %} {% endif %}
{% empty %} {% empty %}

View File

@ -28,26 +28,3 @@
{% endif %} {% endif %}
{% include 'babybuddy/form.html' %} {% include 'babybuddy/form.html' %}
{% endblock %} {% endblock %}
{% block javascript %}
<script type="text/javascript">
BabyBuddy.DatetimePicker.init($('#datetimepicker_start'), {
format: '{% datetimepicker_format %}'
});
BabyBuddy.DatetimePicker.init($('#datetimepicker_end'), {
format: '{% datetimepicker_format %}'
});
$('#id_type').change(function() {
var feed_type=$('#id_type').val();
if (feed_type === 'formula' || feed_type === 'fortified breast milk') {
$('#id_method').val('bottle');
}
});
$('#id_method').change(function() {
var method = $('#id_method').val();
if (['left breast', 'right breast', 'both breasts'].includes(method)) {
$('#id_type').val('breast milk');
}
});
</script>
{% endblock %}

View File

@ -18,8 +18,8 @@
</h1> </h1>
{% include 'babybuddy/filter.html' %} {% include 'babybuddy/filter.html' %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-instances table-striped table-hover"> <table class="table table-instances table-borderless table-striped table-hover align-middle">
<thead class="thead-inverse"> <thead>
<tr> <tr>
<th>{% trans "Actions" %}</th> <th>{% trans "Actions" %}</th>
<th>{% trans "Date" %}</th> <th>{% trans "Date" %}</th>
@ -36,8 +36,7 @@
</thead> </thead>
<tbody> <tbody>
{% for feeding in object_list %} {% for feeding in object_list %}
{% cycle "odd" "even" as row_class silent %} <tr>
<tr class="{{ row_class }}">
<td> <td>
<div class="btn-group btn-group-sm" role="group" aria-label="{% trans "Actions" %}"> <div class="btn-group btn-group-sm" role="group" aria-label="{% trans "Actions" %}">
@ -73,7 +72,7 @@
</tr> </tr>
{% if feeding.notes %} {% if feeding.notes %}
<tr class="{{ row_class }} row-details"> <tr class="{{ row_class }} row-details">
<td colspan="8"><i class="icon-note mr-2" aria-hidden="true"></i>{{ feeding.notes }}</td> <td colspan="8"><i class="icon-note me-2" aria-hidden="true"></i>{{ feeding.notes }}</td>
</tr> </tr>
{% endif %} {% endif %}
{% empty %} {% empty %}

View File

@ -28,12 +28,3 @@
{% endif %} {% endif %}
{% include 'babybuddy/form.html' %} {% include 'babybuddy/form.html' %}
{% endblock %} {% endblock %}
{% block javascript %}
<script type="text/javascript">
BabyBuddy.DatetimePicker.init($('#datetimepicker_date'), {
format: 'L',
extraFormats: ['YYYY-MM-DD']
});
</script>
{% endblock %}

View File

@ -18,8 +18,8 @@
</h1> </h1>
{% include 'babybuddy/filter.html' %} {% include 'babybuddy/filter.html' %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-instances table-striped table-hover"> <table class="table table-instances table-borderless table-striped table-hover align-middle">
<thead class="thead-inverse"> <thead>
<tr> <tr>
<th>{% trans "Actions" %}</th> <th>{% trans "Actions" %}</th>
<th>{% trans "Date" %}</th> <th>{% trans "Date" %}</th>
@ -32,8 +32,7 @@
</thead> </thead>
<tbody> <tbody>
{% for head_circumference in object_list %} {% for head_circumference in object_list %}
{% cycle "odd" "even" as row_class silent %} <tr>
<tr class="{{ row_class }}">
<td> <td>
<div class="btn-group btn-group-sm" role="group" aria-label="{% trans "Actions" %}"> <div class="btn-group btn-group-sm" role="group" aria-label="{% trans "Actions" %}">
@ -62,7 +61,7 @@
</tr> </tr>
{% if head_circumference.notes %} {% if head_circumference.notes %}
<tr class="{{ row_class }} row-details"> <tr class="{{ row_class }} row-details">
<td colspan="5"><i class="icon-note mr-2" aria-hidden="true"></i>{{ head_circumference.notes }}</td> <td colspan="5"><i class="icon-note me-2" aria-hidden="true"></i>{{ head_circumference.notes }}</td>
</tr> </tr>
{% endif %} {% endif %}
{% empty %} {% empty %}

View File

@ -28,12 +28,3 @@
{% endif %} {% endif %}
{% include 'babybuddy/form.html' %} {% include 'babybuddy/form.html' %}
{% endblock %} {% endblock %}
{% block javascript %}
<script type="text/javascript">
BabyBuddy.DatetimePicker.init($('#datetimepicker_date'), {
format: 'L',
extraFormats: ['YYYY-MM-DD']
});
</script>
{% endblock %}

View File

@ -18,8 +18,8 @@
</h1> </h1>
{% include 'babybuddy/filter.html' %} {% include 'babybuddy/filter.html' %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-instances table-striped table-hover"> <table class="table table-instances table-borderless table-striped table-hover align-middle">
<thead class="thead-inverse"> <thead>
<tr> <tr>
<th>{% trans "Actions" %}</th> <th>{% trans "Actions" %}</th>
<th>{% trans "Date" %}</th> <th>{% trans "Date" %}</th>
@ -32,8 +32,7 @@
</thead> </thead>
<tbody> <tbody>
{% for height in object_list %} {% for height in object_list %}
{% cycle "odd" "even" as row_class silent %} <tr>
<tr class="{{ row_class }}">
<td> <td>
<div class="btn-group btn-group-sm" role="group" aria-label="{% trans "Actions" %}"> <div class="btn-group btn-group-sm" role="group" aria-label="{% trans "Actions" %}">
@ -62,7 +61,7 @@
</tr> </tr>
{% if height.notes %} {% if height.notes %}
<tr class="{{ row_class }} row-details"> <tr class="{{ row_class }} row-details">
<td colspan="5"><i class="icon-note mr-2" aria-hidden="true"></i>{{ height.notes }}</td> <td colspan="5"><i class="icon-note me-2" aria-hidden="true"></i>{{ height.notes }}</td>
</tr> </tr>
{% endif %} {% endif %}
{% empty %} {% empty %}

View File

@ -28,11 +28,3 @@
{% endif %} {% endif %}
{% include 'babybuddy/form.html' %} {% include 'babybuddy/form.html' %}
{% endblock %} {% endblock %}
{% block javascript %}
<script type="text/javascript">
BabyBuddy.DatetimePicker.init($('#datetimepicker_time'), {
format: '{% datetimepicker_format %}'
});
</script>
{% endblock %}

View File

@ -18,8 +18,8 @@
</h1> </h1>
{% include 'babybuddy/filter.html' %} {% include 'babybuddy/filter.html' %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-instances table-striped table-hover"> <table class="table table-instances table-borderless table-striped table-hover align-middle">
<thead class="thead-inverse"> <thead>
<tr> <tr>
<th>{% trans "Actions" %}</th> <th>{% trans "Actions" %}</th>
<th>{% trans "Time" %}</th> <th>{% trans "Time" %}</th>

View File

@ -28,11 +28,3 @@
{% endif %} {% endif %}
{% include 'babybuddy/form.html' %} {% include 'babybuddy/form.html' %}
{% endblock %} {% endblock %}
{% block javascript %}
<script type="text/javascript">
BabyBuddy.DatetimePicker.init($('#datetimepicker_time'), {
format: '{% datetimepicker_format %}'
});
</script>
{% endblock %}

View File

@ -18,8 +18,8 @@
</h1> </h1>
{% include 'babybuddy/filter.html' %} {% include 'babybuddy/filter.html' %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-instances table-striped table-hover"> <table class="table table-instances table-borderless table-striped table-hover align-middle">
<thead class="thead-inverse"> <thead>
<tr> <tr>
<th>{% trans "Actions" %}</th> <th>{% trans "Actions" %}</th>
<th>{% trans "Time" %}</th> <th>{% trans "Time" %}</th>
@ -32,8 +32,7 @@
</thead> </thead>
<tbody> <tbody>
{% for pumping in object_list %} {% for pumping in object_list %}
{% cycle "odd" "even" as row_class silent %} <tr>
<tr class="{{ row_class }}">
<td> <td>
<div class="btn-group btn-group-sm" role="group" aria-label="{% trans "Actions" %}"> <div class="btn-group btn-group-sm" role="group" aria-label="{% trans "Actions" %}">
@ -62,7 +61,7 @@
</tr> </tr>
{% if pumping.notes %} {% if pumping.notes %}
<tr class="{{ row_class }} row-details"> <tr class="{{ row_class }} row-details">
<td colspan="4"><i class="icon-note mr-2" aria-hidden="true"></i>{{ pumping.notes }}</td> <td colspan="4"><i class="icon-note me-2" aria-hidden="true"></i>{{ pumping.notes }}</td>
</tr> </tr>
{% endif %} {% endif %}
{% empty %} {% empty %}

View File

@ -20,13 +20,13 @@
{% for child in children %} {% for child in children %}
<button class="dropdown-item d-flex align-items-center" type="submit" name="child" value="{{ child.pk }}"> <button class="dropdown-item d-flex align-items-center" type="submit" name="child" value="{{ child.pk }}">
{% include "core/child_thumbnail.html" %} {% include "core/child_thumbnail.html" %}
<span class="text-wrap ml-2">{{ child }}</span> <span class="text-wrap ms-2">{{ child }}</span>
</button> </button>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
{% else %} {% else %}
<label class="sr-only">{% trans "Quick Start Timer" %}</label> <label class="visually-hidden">{% trans "Quick Start Timer" %}</label>
<button class="btn m-0 p-0 text-success"> <button class="btn m-0 p-0 text-success">
<i class="icon-2x icon-timer" aria-hidden="true"></i> <i class="icon-2x icon-timer" aria-hidden="true"></i>
</button> </button>

View File

@ -28,15 +28,3 @@
{% endif %} {% endif %}
{% include 'babybuddy/form.html' %} {% include 'babybuddy/form.html' %}
{% endblock %} {% endblock %}
{% block javascript %}
<script type="text/javascript">
BabyBuddy.DatetimePicker.init($('#datetimepicker_start'), {
defaultDate: false,
format: '{% datetimepicker_format %}'
});
BabyBuddy.DatetimePicker.init($('#datetimepicker_end'), {
format: '{% datetimepicker_format %}'
});
</script>
{% endblock %}

View File

@ -18,8 +18,8 @@
</h1> </h1>
{% include 'babybuddy/filter.html' %} {% include 'babybuddy/filter.html' %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-instances table-striped table-hover"> <table class="table table-instances table-borderless table-striped table-hover align-middle">
<thead class="thead-inverse"> <thead>
<tr> <tr>
<th>{% trans "Actions" %}</th> <th>{% trans "Actions" %}</th>
<th>{% trans "Start" %}</th> <th>{% trans "Start" %}</th>
@ -34,8 +34,7 @@
</thead> </thead>
<tbody> <tbody>
{% for sleep in object_list %} {% for sleep in object_list %}
{% cycle "odd" "even" as row_class silent %} <tr>
<tr class="{{ row_class }}">
<td> <td>
<div class="btn-group btn-group-sm" role="group" aria-label="{% trans "Actions" %}"> <div class="btn-group btn-group-sm" role="group" aria-label="{% trans "Actions" %}">
@ -66,7 +65,7 @@
</tr> </tr>
{% if sleep.notes %} {% if sleep.notes %}
<tr class="{{ row_class }} row-details"> <tr class="{{ row_class }} row-details">
<td colspan="7"><i class="icon-note mr-2" aria-hidden="true"></i>{{ sleep.notes }}</td> <td colspan="7"><i class="icon-note me-2" aria-hidden="true"></i>{{ sleep.notes }}</td>
</tr> </tr>
{% endif %} {% endif %}
{% empty %} {% empty %}

View File

@ -28,11 +28,3 @@
{% endif %} {% endif %}
{% include 'babybuddy/form.html' %} {% include 'babybuddy/form.html' %}
{% endblock %} {% endblock %}
{% block javascript %}
<script type="text/javascript">
BabyBuddy.DatetimePicker.init($('#datetimepicker_time'), {
format: '{% datetimepicker_format %}'
});
</script>
{% endblock %}

View File

@ -18,8 +18,8 @@
</h1> </h1>
{% include 'babybuddy/filter.html' %} {% include 'babybuddy/filter.html' %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-instances table-striped table-hover"> <table class="table table-instances table-borderless table-striped table-hover align-middle">
<thead class="thead-inverse"> <thead>
<tr> <tr>
<th>{% trans "Actions" %}</th> <th>{% trans "Actions" %}</th>
<th>{% trans "Time" %}</th> <th>{% trans "Time" %}</th>
@ -32,8 +32,7 @@
</thead> </thead>
<tbody> <tbody>
{% for temperature in object_list %} {% for temperature in object_list %}
{% cycle "odd" "even" as row_class silent %} <tr>
<tr class="{{ row_class }}">
<td> <td>
<div class="btn-group btn-group-sm" role="group" aria-label="{% trans "Actions" %}"> <div class="btn-group btn-group-sm" role="group" aria-label="{% trans "Actions" %}">
@ -62,7 +61,7 @@
</tr> </tr>
{% if temperature.notes %} {% if temperature.notes %}
<tr class="{{ row_class }} row-details"> <tr class="{{ row_class }} row-details">
<td colspan="5"><i class="icon-note mr-2" aria-hidden="true"></i>{{ temperature.notes }}</td> <td colspan="5"><i class="icon-note me-2" aria-hidden="true"></i>{{ temperature.notes }}</td>
</tr> </tr>
{% endif %} {% endif %}
{% empty %} {% empty %}

View File

@ -7,7 +7,7 @@
{% block breadcrumbs %} {% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'core:timer-list' %}">{% trans "Timers" %}</a></li> <li class="breadcrumb-item"><a href="{% url 'core:timer-list' %}">{% trans "Timers" %}</a></li>
<li class="breadcrumb-item font-weight-bold"><a href="{% url 'core:timer-detail' object.id %}">{{ object }}</a></li> <li class="breadcrumb-item fw-bold"><a href="{% url 'core:timer-detail' object.id %}">{{ object }}</a></li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Delete" %}</li> <li class="breadcrumb-item active" aria-current="page">{% trans "Delete" %}</li>
{% endblock %} {% endblock %}

View File

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

View File

@ -6,94 +6,85 @@
{% block breadcrumbs %} {% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'core:timer-list' %}">{% trans "Timers" %}</a></li> <li class="breadcrumb-item"><a href="{% url 'core:timer-list' %}">{% trans "Timers" %}</a></li>
<li class="breadcrumb-item font-weight-bold">{{ object }}</li> <li class="breadcrumb-item fw-bold">{{ object }}</li>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="jumbotron text-center"> <div class="p-5 mb-4 bg-dark rounded-3 text-center">
<div class="container-fluid py-1">
<h1 id="timer-status"
class="display-1">
<span class="timer-hours">{{ object.duration|hours }}</span>h
<span class="timer-minutes">{{ object.duration|minutes }}</span>m
<span class="timer-seconds">{{ object.duration|seconds }}</span>s
</h1>
<h1 {% if not object.active %}class="timer-stopped" {% endif %}id="timer-status"> {% if timer.child and CHILD_COUNT > 1 %}
<span class="timer-hours">{{ object.duration|hours }}</span>h <h2 class="text-muted">
<span class="timer-minutes">{{ object.duration|minutes }}</span>m {{ timer.child }}
<span class="timer-seconds">{{ object.duration|seconds }}</span>s </h2>
</h1>
{% if timer.child and CHILD_COUNT > 1 %}
<h2 class="text-muted">
{{ timer.child }}
</h2>
{% endif %}
<p class="lead text-secondary">
{% trans "Started" %} {{ object.start }}
{% if not object.active %}
/ {% trans "Stopped" %} {{ object.end }}
{% endif %}
</p>
<p class="text-muted">
{% blocktrans trimmed with user=object.user_username %}
{{ timer }} created by {{ user }}
{% endblocktrans %}
</p>
{% if perms.core.add_feeding %}
<a class="btn btn-success btn-lg btn-block mb-3"
href="{% instance_add_url 'core:feeding-add' %}"
role="button"><i class="icon-feeding" aria-hidden="true"></i>
{% trans "Feeding" %}
</a>
{% endif %}
{% if perms.core.add_sleep %}
<a class="btn btn-success btn-lg btn-block mb-3"
href="{% instance_add_url 'core:sleep-add' %}"
role="button"><i class="icon-sleep" aria-hidden="true"></i>
{% trans "Sleep" %}
</a>
{% endif %}
{% if perms.core.add_tummytime %}
<a class="btn btn-success btn-lg btn-block mb-3"
href="{% instance_add_url 'core:tummytime-add' %}"
role="button"><i class="icon-tummytime" aria-hidden="true"></i>
{% trans "Tummy Time" %}
</a>
{% endif %}
<div class="center-block" role="group" aria-label="{% trans "Timer actions" %}">
{% if perms.core.delete_timer %}
<a class="btn btn-lg btn-danger"
href="{% url 'core:timer-delete' timer.id %}"
role="button"><i class="icon-delete" aria-hidden="true"></i></a>
{% endif %} {% endif %}
{% if perms.core.change_timer %} <p class="lead text-secondary">
<a class="btn btn-lg btn-primary" {% trans "Started" %} {{ object.start }}
href="{% url 'core:timer-update' timer.id %}" </p>
role="button"><i class="icon-update" aria-hidden="true"></i></a> <p class="text-muted">
{% blocktrans trimmed with user=object.user_username %}
{{ timer }} created by {{ user }}
{% endblocktrans %}
</p>
<form action="{% url 'core:timer-restart' timer.id %}" role="form" method="post" class="d-inline"> <div class="d-grid gap-4 mb-4">
{% csrf_token %} {% if perms.core.add_feeding %}
<label class="sr-only">{% trans "Restart timer" %}</label> <a class="btn btn-success btn-lg"
<button type="submit" class="btn btn-lg btn-secondary"><i class="icon-refresh" aria-hidden="true"></i></button> href="{% instance_add_url 'core:feeding-add' %}"
</form> role="button"><i class="icon-feeding" aria-hidden="true"></i>
{% trans "Feeding" %}
</a>
{% endif %}
{% if object.active %} {% if perms.core.add_sleep %}
<form action="{% url 'core:timer-stop' timer.id %}" role="form" method="post" class="d-inline"> <a class="btn btn-success btn-lg"
href="{% instance_add_url 'core:sleep-add' %}"
role="button"><i class="icon-sleep" aria-hidden="true"></i>
{% trans "Sleep" %}
</a>
{% endif %}
{% if perms.core.add_tummytime %}
<a class="btn btn-success btn-lg"
href="{% instance_add_url 'core:tummytime-add' %}"
role="button"><i class="icon-tummytime" aria-hidden="true"></i>
{% trans "Tummy Time" %}
</a>
{% endif %}
</div>
<div class="center-block" role="group" aria-label="{% trans "Timer actions" %}">
{% if perms.core.delete_timer %}
<a class="btn btn-lg btn-danger"
href="{% url 'core:timer-delete' timer.id %}"
role="button"><i class="icon-delete" aria-hidden="true"></i></a>
{% endif %}
{% if perms.core.change_timer %}
<a class="btn btn-lg btn-primary"
href="{% url 'core:timer-update' timer.id %}"
role="button"><i class="icon-update" aria-hidden="true"></i></a>
<form action="{% url 'core:timer-restart' timer.id %}" role="form" method="post" class="d-inline">
{% csrf_token %} {% csrf_token %}
<label class="sr-only">{% trans "Delete timer" %}</label> <label class="visually-hidden">{% trans "Restart timer" %}</label>
<button type="submit" class="btn btn-lg btn-warning"><i class="icon-stop" 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>
{% endif %} {% endif %}
{% endif %} </div>
</div> </div>
</div> </div>
{% 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 %}

View File

@ -6,7 +6,7 @@
{% block breadcrumbs %} {% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'core:timer-list' %}">{% trans "Timers" %}</a></li> <li class="breadcrumb-item"><a href="{% url 'core:timer-list' %}">{% trans "Timers" %}</a></li>
{% if object %} {% if object %}
<li class="breadcrumb-item font-weight-bold"><a href="{% url 'core:timer-detail' object.id %}">{{ object }}</a></li> <li class="breadcrumb-item fw-bold"><a href="{% url 'core:timer-detail' object.id %}">{{ object }}</a></li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Update" %}</li> <li class="breadcrumb-item active" aria-current="page">{% trans "Update" %}</li>
{% else %} {% else %}
<li class="breadcrumb-item active" aria-current="page">{% trans "Start" %}</li> <li class="breadcrumb-item active" aria-current="page">{% trans "Start" %}</li>
@ -23,11 +23,3 @@
{% endif %} {% endif %}
{% include 'babybuddy/form.html' %} {% include 'babybuddy/form.html' %}
{% endblock %} {% endblock %}
{% block javascript %}
<script type="text/javascript">
BabyBuddy.DatetimePicker.init($('#datetimepicker_start'), {
format: '{% datetimepicker_format 'L LTS' %}'
});
</script>
{% endblock %}

View File

@ -18,17 +18,14 @@
</h1> </h1>
{% include 'babybuddy/filter.html' %} {% include 'babybuddy/filter.html' %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-instances table-striped table-hover"> <table class="table table-instances table-borderless table-striped table-hover align-middle">
<thead class="thead-inverse"> <thead>
<tr> <tr>
<th>{% trans "Start" %}</th> <th>{% trans "Start" %}</th>
<th>{% trans "Name" %}</th> <th>{% trans "Name" %}</th>
{% if not unique_child %} {% if not unique_child %}
<th>{% trans "Child" %}</th> <th>{% trans "Child" %}</th>
{% endif %} {% endif %}
<th>{% trans "Duration" %}</th>
<th>{% trans "End" %}</th>
<th>{% trans "Active" %}</th>
<th>{% trans "User" %}</th> <th>{% trans "User" %}</th>
</tr> </tr>
</thead> </thead>
@ -44,13 +41,6 @@
{% endif %} {% endif %}
</td> </td>
{% endif %} {% endif %}
<td>{{ timer.duration|duration_string }}</td>
<td>
{% if timer.end %}
{{ timer.end|datetime_short }}
{% endif %}
</td>
<td>{{ timer.active|bool_icon }}</td>
<td>{{ timer.user_username }}</td> <td>{{ timer.user_username }}</td>
</tr> </tr>
{% empty %} {% empty %}
@ -62,11 +52,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 %}

View File

@ -4,7 +4,7 @@
<a id="nav-timer-menu-link" <a id="nav-timer-menu-link"
class="nav-link dropdown-toggle" class="nav-link dropdown-toggle"
href="#" href="#"
data-toggle="dropdown" data-bs-toggle="dropdown"
aria-haspopup="true" aria-haspopup="true"
aria-expanded="false"><i class="icon-timer" aria-hidden="true"></i> aria-expanded="false"><i class="icon-timer" aria-hidden="true"></i>
{% trans "Timers" %} {% trans "Timers" %}
@ -29,7 +29,7 @@
{% for child in children %} {% for child in children %}
<button class="dropdown-item d-flex align-items-center" type="submit" name="child" value="{{ child.pk }}"> <button class="dropdown-item d-flex align-items-center" type="submit" name="child" value="{{ child.pk }}">
{% include "core/child_thumbnail.html" %} {% include "core/child_thumbnail.html" %}
<span class="text-wrap ml-2">{{ child }}</span> <span class="text-wrap ms-2">{{ child }}</span>
</button> </button>
{% endfor %} {% endfor %}
{% else %} {% else %}
@ -41,7 +41,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 }}

View File

@ -28,15 +28,3 @@
{% endif %} {% endif %}
{% include 'babybuddy/form.html' %} {% include 'babybuddy/form.html' %}
{% endblock %} {% endblock %}
{% block javascript %}
<script type="text/javascript">
BabyBuddy.DatetimePicker.init($('#datetimepicker_start'), {
defaultDate: false,
format: '{% datetimepicker_format 'L LTS' %}'
});
BabyBuddy.DatetimePicker.init($('#datetimepicker_end'), {
format: '{% datetimepicker_format 'L LTS' %}'
});
</script>
{% endblock %}

View File

@ -17,8 +17,8 @@
</h1> </h1>
{% include 'babybuddy/filter.html' %} {% include 'babybuddy/filter.html' %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-instances table-striped table-hover"> <table class="table table-instances table-borderless table-striped table-hover align-middle">
<thead class="thead-inverse"> <thead>
<tr> <tr>
<th>{% trans "Actions" %}</th> <th>{% trans "Actions" %}</th>
<th>{% trans "Start" %}</th> <th>{% trans "Start" %}</th>

View File

@ -28,12 +28,3 @@
{% endif %} {% endif %}
{% include 'babybuddy/form.html' %} {% include 'babybuddy/form.html' %}
{% endblock %} {% endblock %}
{% block javascript %}
<script type="text/javascript">
BabyBuddy.DatetimePicker.init($('#datetimepicker_date'), {
format: 'L',
extraFormats: ['YYYY-MM-DD']
});
</script>
{% endblock %}

View File

@ -18,8 +18,8 @@
</h1> </h1>
{% include 'babybuddy/filter.html' %} {% include 'babybuddy/filter.html' %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-instances table-striped table-hover"> <table class="table table-instances table-borderless table-striped table-hover align-middle">
<thead class="thead-inverse"> <thead>
<tr> <tr>
<th>{% trans "Actions" %}</th> <th>{% trans "Actions" %}</th>
<th>{% trans "Date" %}</th> <th>{% trans "Date" %}</th>
@ -32,8 +32,7 @@
</thead> </thead>
<tbody> <tbody>
{% for weight in object_list %} {% for weight in object_list %}
{% cycle "odd" "even" as row_class silent %} <tr>
<tr class="{{ row_class }}">
<td> <td>
<div class="btn-group btn-group-sm" role="group" aria-label="{% trans "Actions" %}"> <div class="btn-group btn-group-sm" role="group" aria-label="{% trans "Actions" %}">
@ -62,7 +61,7 @@
</tr> </tr>
{% if weight.notes %} {% if weight.notes %}
<tr class="{{ row_class }} row-details"> <tr class="{{ row_class }} row-details">
<td colspan="5"><i class="icon-note mr-2" aria-hidden="true"></i>{{ weight.notes }}</td> <td colspan="5"><i class="icon-note me-2" aria-hidden="true"></i>{{ weight.notes }}</td>
</tr> </tr>
{% endif %} {% endif %}
{% empty %} {% empty %}

View File

@ -5,39 +5,31 @@
{{ k }}="{{ v }}" {{ k }}="{{ v }}"
{% endfor %}> {% endfor %}>
{% csrf_token %} {% csrf_token %}
<span class="prototype-tag btn badge badge-pill cursor-pointer mr-1" style="display: none;"> <span class="prototype-tag btn badge badge-pill cursor-pointer me-1" style="display: none;">
UNINITIALIZED PROTOTYPE UNINITIALIZED PROTOTYPE
<span class="add-remove-icon pl-1 pr-1">+ or -</span> <span class="add-remove-icon ps-1 pe-1">+ or -</span>
</span> </span>
<div class="current_tags" style="min-height: 2em;"> <div class="current_tags" style="min-height: 2em;">
{% for t in widget.value %} {% for t in widget.value %}
<span data-value="{{ t.name }}" data-color="{{ t.color }}" class="tag btn badge badge-pill cursor-pointer mr-1" style="background-color: {{ t.color }};"> <span data-value="{{ t.name }}" data-color="{{ t.color }}" class="tag btn badge badge-pill cursor-pointer me-1" style="background-color: {{ t.color }};">
{{ t.name }} {{ t.name }}
<span class="add-remove-icon pl-1 pr-1">-</span> <span class="add-remove-icon ps-1 pe-1">-</span>
</span> </span>
{% endfor %} {% endfor %}
</div> </div>
<div class="new-tags"> <div class="new-tags">
<div class="create-tag-inputs input-group"> <div class="create-tag-inputs input-group">
<input class="form-control" type="text" name="" placeholder="{% trans "Tag name" %}"> <input class="form-control" type="text" name="" placeholder="{% trans "Tag name" %}">
<div class="input-group-append"> <button id="add-tag" class="btn btn-outline-primary bg-dark" type="button">{% trans "Add" %}</button>
<button class="btn btn-outline-primary bg-dark btn-add-new-tag" type="button">{% trans "Add" %}</button>
</div>
</div> </div>
{% if widget.tag_suggestions.quick %} {% if widget.tag_suggestions.quick %}
<span>{% trans "Recently used:" %}</span> <span>{% trans "Recently used:" %}</span>
{% for t in widget.tag_suggestions.quick %} {% for t in widget.tag_suggestions.quick %}
<span data-value="{{ t.name }}" data-color="{{ t.color }}" class="tag btn badge badge-pill cursor-pointer mr-1" style="background-color: {{ t.color }};"> <span data-value="{{ t.name }}" data-color="{{ t.color }}" class="tag btn badge badge-pill cursor-pointer me-1" style="background-color: {{ t.color }};">
{{ t.name }} {{ t.name }}
<span class="add-remove-icon pl-1 pr-1">+</span> <span class="add-remove-icon ps-1 pe-1">+</span>
</span> </span>
{% endfor %} {% endfor %}
{% else %}
<style>
.help-block{
display: none;
}
</style>
{%endif%} {%endif%}
</div> </div>
<input <input
@ -51,7 +43,7 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h4 class="modal-title">{% trans "Error" context "Error modal" %}</h4> <h4 class="modal-title">{% trans "Error" context "Error modal" %}</h4>
<button type="button" class="close" data-dismiss="modal">&times;</button> <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
@ -62,7 +54,7 @@
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-danger" data-dismiss="modal"> <button type="button" class="btn btn-danger" data-bs-dismiss="modal">
{% trans "Close" context "Error modal" %} {% trans "Close" context "Error modal" %}
</button> </button>
</div> </div>

View File

@ -4,14 +4,14 @@
{% if date_previous %} {% if date_previous %}
<a class="btn btn-sm btn-default" href="?date={{ date_previous|date:"Y-m-d" }}" aria-label="{% trans "Previous" %}"> <a class="btn btn-sm btn-default" href="?date={{ date_previous|date:"Y-m-d" }}" aria-label="{% trans "Previous" %}">
<i class="icon-2x icon-angle-circled-left" aria-hidden="true"></i> <i class="icon-2x icon-angle-circled-left" aria-hidden="true"></i>
<span class="sr-only">{% trans "Previous" %}</span> <span class="visually-hidden">{% trans "Previous" %}</span>
</a> </a>
{% endif %} {% endif %}
{{ date|date }} {{ date|date }}
{% if date_next %} {% if date_next %}
<a class="btn btn-sm btn-default" href="?date={{ date_next|date:"Y-m-d" }}" aria-label="{% trans "Next" %}"> <a class="btn btn-sm btn-default" href="?date={{ date_next|date:"Y-m-d" }}" aria-label="{% trans "Next" %}">
<i class="icon-2x icon-angle-circled-right" aria-hidden="true"></i> <i class="icon-2x icon-angle-circled-right" aria-hidden="true"></i>
<span class="sr-only">{% trans "Next" %}</span> <span class="visually-hidden">{% trans "Next" %}</span>
</a> </a>
{% endif %} {% endif %}
</h3> </h3>
@ -22,7 +22,7 @@
<div class="timeline-badge {% if object.type == "start" %}bg-success{% elif object.type == "end" %}bg-danger{% else %}bg-info{% endif %}"> <div class="timeline-badge {% if object.type == "start" %}bg-success{% elif object.type == "end" %}bg-danger{% else %}bg-info{% endif %}">
<i class="icon-{{ object.model_name }}"></i> <i class="icon-{{ object.model_name }}"></i>
</div> </div>
<div class="card text-right"> <div class="card text-end">
<div class="card-body"> <div class="card-body">
{{ object.event }} {{ object.event }}
{% for detail in object.details %} {% for detail in object.details %}
@ -70,14 +70,14 @@
{% if date_previous %} {% if date_previous %}
<a class="btn btn-sm btn-default" href="?date={{ date_previous|date:"Y-m-d" }}" aria-label="{% trans "Previous" %}"> <a class="btn btn-sm btn-default" href="?date={{ date_previous|date:"Y-m-d" }}" aria-label="{% trans "Previous" %}">
<i class="icon-2x icon-angle-circled-left" aria-hidden="true"></i> <i class="icon-2x icon-angle-circled-left" aria-hidden="true"></i>
<span class="sr-only">{% trans "Previous" %}</span> <span class="visually-hidden">{% trans "Previous" %}</span>
</a> </a>
{% endif %} {% endif %}
{{ date|date }} {{ date|date }}
{% if date_next %} {% if date_next %}
<a class="btn btn-sm btn-default" href="?date={{ date_next|date:"Y-m-d" }}" aria-label="{% trans "Next" %}"> <a class="btn btn-sm btn-default" href="?date={{ date_next|date:"Y-m-d" }}" aria-label="{% trans "Next" %}">
<i class="icon-2x icon-angle-circled-right" aria-hidden="true"></i> <i class="icon-2x icon-angle-circled-right" aria-hidden="true"></i>
<span class="sr-only">{% trans "Next" %}</span> <span class="visually-hidden">{% trans "Next" %}</span>
</a> </a>
{% endif %} {% endif %}
</h3> </h3>

View File

@ -4,7 +4,7 @@
{% block title %}{% trans "Timeline" %}{% endblock %} {% block title %}{% trans "Timeline" %}{% endblock %}
{% block breadcrumbs %} {% block breadcrumbs %}
<li class="breadcrumb-item font-weight-bold">{% trans "Timeline" %}</li> <li class="breadcrumb-item fw-bold">{% trans "Timeline" %}</li>
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@ -7,40 +7,6 @@ from django.utils.translation import gettext_lazy as _
register = template.Library() register = template.Library()
@register.simple_tag(takes_context=True)
def datetimepicker_format(context, format_string="L LT"):
"""
Return a datetime format string for momentjs, with support for 24 hour time
override setting.
:param context: caller context data
:param format_string: the default format string (locale based)
:return: the format string to use, as 24 hour time if configured.
"""
try:
user = context["request"].user
if hasattr(user, "settings") and user.settings.language:
language = user.settings.language
else:
language = settings.LANGUAGE_CODE
except KeyError:
language = None
if settings.USE_24_HOUR_TIME_FORMAT:
if format_string == "L LT":
format_string = "L HH:mm"
elif format_string == "L LTS":
format_string = "L HH:mm:ss"
elif language and language == "en-GB":
# Force 12-hour format if 24 hour format is not configured for en-GB
# (Django default is 12H, momentjs default is 24H).
if format_string == "L LT":
format_string = "L h:mm a"
elif format_string == "L LTS":
format_string = "L h:mm:ss a"
return format_string
@register.filter() @register.filter()
def datetime_short(date): def datetime_short(date):
""" """

View File

@ -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.

View File

@ -122,32 +122,13 @@ 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):
end = timezone.localtime()
params = {
"child": self.child.id,
"start": self.localtime_string(self.timer.start),
"end": self.localtime_string(end),
}
page = self.c.post(
"/sleep/add/?timer={}".format(self.timer.id), params, follow=True
)
self.assertEqual(page.status_code, 200)
self.timer.refresh_from_db()
self.assertFalse(self.timer.active)
self.assertEqual(self.localtime_string(self.timer.end), params["end"])
class BMIFormsTestCase(FormsTestCaseBase): class BMIFormsTestCase(FormsTestCaseBase):
@classmethod @classmethod
@ -783,29 +764,6 @@ class TimerFormsTestCase(FormsTestCaseBase):
self.timer.refresh_from_db() self.timer.refresh_from_db()
self.assertEqual(self.localtime_string(self.timer.start), params["start"]) self.assertEqual(self.localtime_string(self.timer.start), params["start"])
def test_edit_stopped(self):
self.timer.stop()
params = {
"name": "Edit stopped timer",
"start": self.localtime_string(self.timer.start),
"end": self.localtime_string(self.timer.end),
}
page = self.c.post(
"/timers/{}/edit/".format(self.timer.id), params, follow=True
)
self.assertEqual(page.status_code, 200)
def test_delete_inactive(self):
models.Timer.objects.create(user=self.user)
self.assertEqual(models.Timer.objects.count(), 2)
self.timer.stop()
page = self.c.post("/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]), "All inactive timers deleted.")
self.assertEqual(models.Timer.objects.count(), 1)
class ValidationsTestCase(FormsTestCaseBase): class ValidationsTestCase(FormsTestCaseBase):
def test_validate_date(self): def test_validate_date(self):

View File

@ -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):

Some files were not shown because too many files have changed in this diff Show More