mirror of https://github.com/snachodog/mybuddy.git
Merge pull request #150 from babybuddy/148-24h-time
Add support for 24 hour time override (#148)
This commit is contained in:
commit
dda1cd4750
16
README.md
16
README.md
|
@ -284,6 +284,7 @@ take precedence over the contents of an `.env` file.**
|
||||||
- [`NAP_START_MIN`](#nap_start_min)
|
- [`NAP_START_MIN`](#nap_start_min)
|
||||||
- [`SECRET_KEY`](#secret_key)
|
- [`SECRET_KEY`](#secret_key)
|
||||||
- [`TIME_ZONE`](#time_zone)
|
- [`TIME_ZONE`](#time_zone)
|
||||||
|
- [`USE_24_HOUR_TIME_FORMAT`](#use_24_hour_time_format)
|
||||||
|
|
||||||
### `ALLOWED_HOSTS`
|
### `ALLOWED_HOSTS`
|
||||||
|
|
||||||
|
@ -369,6 +370,21 @@ The default time zone to use for the instance. See [List of tz database time zon
|
||||||
for all possible values. This value can be overridden per use from the user
|
for all possible values. This value can be overridden per use from the user
|
||||||
settings form.
|
settings form.
|
||||||
|
|
||||||
|
### `USE_24_HOUR_TIME_FORMAT`
|
||||||
|
|
||||||
|
*Default: False*
|
||||||
|
|
||||||
|
Whether to force 24-hour time format for locales that do not ordinarily use it
|
||||||
|
(e.g. `en`). Support for this feature must implemented on a per-locale basis.
|
||||||
|
See format files under [`babybuddy/formats`](babybuddy/formats) for supported
|
||||||
|
locales.
|
||||||
|
|
||||||
|
Note: This value for this setting is interpreted as a boolean from a string
|
||||||
|
using Python's built-in [`strtobool`](https://docs.python.org/3/distutils/apiref.html#distutils.util.strtobool)
|
||||||
|
tool. Only certain strings are supported (e.g. "True" for `True` and "False" for
|
||||||
|
`False`), other unrecognized strings will cause a `ValueError` and prevent Baby
|
||||||
|
Buddy from loading.
|
||||||
|
|
||||||
## Languages
|
## Languages
|
||||||
|
|
||||||
Baby Buddy includes translation support as of v1.2.2. Language can be set on a
|
Baby Buddy includes translation support as of v1.2.2. Language can be set on a
|
||||||
|
|
|
@ -1,24 +1,22 @@
|
||||||
# This files adds two new date input formats to support Baby Buddy's frontend
|
from django.conf import settings
|
||||||
# library formats:
|
from django.conf.locale.en import formats
|
||||||
# - %m/%d/%Y %I:%M:%S %p
|
|
||||||
# - %m/%d/%Y %I:%M %p
|
# Override the regular locale settings to support 24 hour time.
|
||||||
#
|
if settings.USE_24_HOUR_TIME_FORMAT:
|
||||||
# The remaining formats come from the base Django formats.
|
DATETIME_FORMAT = 'N j, Y, H:i:s'
|
||||||
#
|
CUSTOM_INPUT_FORMATS = [
|
||||||
# See django.cong.locale.en.
|
'%m/%d/%Y %H:%M:%S', # '10/25/2006 14:30:59'
|
||||||
DATETIME_INPUT_FORMATS = [
|
'%m/%d/%Y %H:%M', # '10/25/2006 14:30'
|
||||||
'%m/%d/%Y %I:%M:%S %p', # '10/25/2006 2:30:59 PM' (new)
|
]
|
||||||
'%m/%d/%Y %I:%M %p', # '10/25/2006 2:30 PM' (new)
|
SHORT_DATETIME_FORMAT = 'm/d/Y G:i:s'
|
||||||
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
|
TIME_FORMAT = 'H:i:s'
|
||||||
'%Y-%m-%d %H:%M:%S.%f', # '2006-10-25 14:30:59.000200'
|
else:
|
||||||
'%Y-%m-%d %H:%M', # '2006-10-25 14:30'
|
# These formats are added to support the locale style of Baby Buddy's
|
||||||
'%Y-%m-%d', # '2006-10-25'
|
# frontend library, which uses momentjs.
|
||||||
'%m/%d/%Y %H:%M:%S', # '10/25/2006 14:30:59'
|
CUSTOM_INPUT_FORMATS = [
|
||||||
'%m/%d/%Y %H:%M:%S.%f', # '10/25/2006 14:30:59.000200'
|
'%m/%d/%Y %I:%M:%S %p', # '10/25/2006 2:30:59 PM'
|
||||||
'%m/%d/%Y %H:%M', # '10/25/2006 14:30'
|
'%m/%d/%Y %I:%M %p', # '10/25/2006 2:30 PM'
|
||||||
'%m/%d/%Y', # '10/25/2006'
|
]
|
||||||
'%m/%d/%y %H:%M:%S', # '10/25/06 14:30:59'
|
|
||||||
'%m/%d/%y %H:%M:%S.%f', # '10/25/06 14:30:59.000200'
|
# Append all other input formats from the base locale.
|
||||||
'%m/%d/%y %H:%M', # '10/25/06 14:30'
|
DATETIME_INPUT_FORMATS = CUSTOM_INPUT_FORMATS + formats.DATETIME_INPUT_FORMATS
|
||||||
'%m/%d/%y', # '10/25/06'
|
|
||||||
]
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import os
|
import os
|
||||||
|
from distutils.util import strtobool
|
||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from dotenv import load_dotenv, find_dotenv
|
from dotenv import load_dotenv, find_dotenv
|
||||||
|
@ -166,6 +167,14 @@ LANGUAGES = [
|
||||||
|
|
||||||
USE_L10N = True
|
USE_L10N = True
|
||||||
|
|
||||||
|
# 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 = strtobool(os.environ.get('USE_24_HOUR_TIME_FORMAT') or 'False')
|
||||||
|
|
||||||
FORMAT_MODULE_PATH = ['babybuddy.formats']
|
FORMAT_MODULE_PATH = ['babybuddy.formats']
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,8 @@ import datetime
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.forms.fields import DateTimeField
|
from django.forms.fields import DateTimeField
|
||||||
from django.test import TestCase
|
from django.test import TestCase, override_settings, tag
|
||||||
|
from django.utils.formats import date_format, time_format
|
||||||
|
|
||||||
|
|
||||||
class FormatsTestCase(TestCase):
|
class FormatsTestCase(TestCase):
|
||||||
|
@ -17,8 +18,43 @@ class FormatsTestCase(TestCase):
|
||||||
]
|
]
|
||||||
|
|
||||||
for example in supported_custom_examples:
|
for example in supported_custom_examples:
|
||||||
result = field.to_python(example)
|
try:
|
||||||
self.assertIsInstance(result, datetime.datetime)
|
result = field.to_python(example)
|
||||||
|
self.assertIsInstance(result, datetime.datetime)
|
||||||
|
except ValidationError:
|
||||||
|
self.fail('Format of "{}" not recognized!'.format(example))
|
||||||
|
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
field.to_python('invalid date string!')
|
field.to_python('invalid date string!')
|
||||||
|
|
||||||
|
@tag('isolate')
|
||||||
|
@override_settings(LANGUAGE_CODE='en', USE_24_HOUR_TIME_FORMAT=True)
|
||||||
|
def test_use_24_hour_time_format_en(self):
|
||||||
|
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.fromisoformat('2011-11-04 23:05:59')
|
||||||
|
self.assertEqual(
|
||||||
|
date_format(dt, 'DATETIME_FORMAT'), 'Nov. 4, 2011, 23:05:59')
|
||||||
|
|
||||||
|
dt = datetime.datetime.fromisoformat('2011-11-04 02:05:59')
|
||||||
|
self.assertEqual(
|
||||||
|
date_format(dt, 'SHORT_DATETIME_FORMAT'), '11/04/2011 2:05:59')
|
||||||
|
|
||||||
|
t = datetime.time.fromisoformat('16:02:25')
|
||||||
|
self.assertEqual(time_format(t), '16:02:25')
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends 'babybuddy/page.html' %}
|
{% extends 'babybuddy/page.html' %}
|
||||||
{% load i18n %}
|
{% load datetimepicker i18n %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% if request.resolver_match.url_name == 'diaperchange-update' %}
|
{% if request.resolver_match.url_name == 'diaperchange-update' %}
|
||||||
|
@ -31,6 +31,8 @@
|
||||||
|
|
||||||
{% block javascript %}
|
{% block javascript %}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
BabyBuddy.DatetimePicker.init($('#datetimepicker_time'));
|
BabyBuddy.DatetimePicker.init($('#datetimepicker_time'), {
|
||||||
|
format: '{% datetimepicker_format %}'
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends 'babybuddy/page.html' %}
|
{% extends 'babybuddy/page.html' %}
|
||||||
{% load i18n %}
|
{% load datetimepicker i18n %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% if request.resolver_match.url_name == 'feeding-update' %}
|
{% if request.resolver_match.url_name == 'feeding-update' %}
|
||||||
|
@ -32,12 +32,15 @@
|
||||||
{% block javascript %}
|
{% block javascript %}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
BabyBuddy.DatetimePicker.init($('#datetimepicker_start'), {
|
BabyBuddy.DatetimePicker.init($('#datetimepicker_start'), {
|
||||||
defaultDate: false
|
defaultDate: false,
|
||||||
|
format: '{% datetimepicker_format %}'
|
||||||
|
});
|
||||||
|
BabyBuddy.DatetimePicker.init($('#datetimepicker_end'), {
|
||||||
|
format: '{% datetimepicker_format %}'
|
||||||
});
|
});
|
||||||
BabyBuddy.DatetimePicker.init($('#datetimepicker_end'));
|
|
||||||
$('#id_type').change(function() {
|
$('#id_type').change(function() {
|
||||||
var feed_type=$('#id_type').val();
|
var feed_type=$('#id_type').val();
|
||||||
if (feed_type=='formula'||feed_type=='fortified breast milk') {
|
if (feed_type === 'formula' || feed_type === 'fortified breast milk') {
|
||||||
$('#id_method').val('bottle');
|
$('#id_method').val('bottle');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends 'babybuddy/page.html' %}
|
{% extends 'babybuddy/page.html' %}
|
||||||
{% load i18n %}
|
{% load datetimepicker i18n %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% if request.resolver_match.url_name == 'sleep-update' %}
|
{% if request.resolver_match.url_name == 'sleep-update' %}
|
||||||
|
@ -32,8 +32,11 @@
|
||||||
{% block javascript %}
|
{% block javascript %}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
BabyBuddy.DatetimePicker.init($('#datetimepicker_start'), {
|
BabyBuddy.DatetimePicker.init($('#datetimepicker_start'), {
|
||||||
defaultDate: false
|
defaultDate: false,
|
||||||
|
format: '{% datetimepicker_format %}'
|
||||||
|
});
|
||||||
|
BabyBuddy.DatetimePicker.init($('#datetimepicker_end'), {
|
||||||
|
format: '{% datetimepicker_format %}'
|
||||||
});
|
});
|
||||||
BabyBuddy.DatetimePicker.init($('#datetimepicker_end'));
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends 'babybuddy/page.html' %}
|
{% extends 'babybuddy/page.html' %}
|
||||||
{% load i18n %}
|
{% load datetimepicker i18n %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% if object %}
|
{% if object %}
|
||||||
|
@ -31,6 +31,8 @@
|
||||||
|
|
||||||
{% block javascript %}
|
{% block javascript %}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
BabyBuddy.DatetimePicker.init($('#datetimepicker_time'));
|
BabyBuddy.DatetimePicker.init($('#datetimepicker_time'), {
|
||||||
|
format: '{% datetimepicker_format %}'
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends 'babybuddy/page.html' %}
|
{% extends 'babybuddy/page.html' %}
|
||||||
{% load duration i18n %}
|
{% load datetimepicker duration i18n %}
|
||||||
|
|
||||||
{% block title %}{% trans "Timer" %}{% endblock %}
|
{% block title %}{% trans "Timer" %}{% endblock %}
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
{% block javascript %}
|
{% block javascript %}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
BabyBuddy.DatetimePicker.init($('#datetimepicker_start'), {
|
BabyBuddy.DatetimePicker.init($('#datetimepicker_start'), {
|
||||||
format: 'L LTS'
|
format: '{% datetimepicker_format 'L LTS' %}'
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends 'babybuddy/page.html' %}
|
{% extends 'babybuddy/page.html' %}
|
||||||
{% load i18n %}
|
{% load datetimepicker i18n %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% if request.resolver_match.url_name == 'tummytime-update' %}
|
{% if request.resolver_match.url_name == 'tummytime-update' %}
|
||||||
|
@ -33,10 +33,10 @@
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
BabyBuddy.DatetimePicker.init($('#datetimepicker_start'), {
|
BabyBuddy.DatetimePicker.init($('#datetimepicker_start'), {
|
||||||
defaultDate: false,
|
defaultDate: false,
|
||||||
format: 'L LTS'
|
format: '{% datetimepicker_format 'L LTS' %}'
|
||||||
});
|
});
|
||||||
BabyBuddy.DatetimePicker.init($('#datetimepicker_end'), {
|
BabyBuddy.DatetimePicker.init($('#datetimepicker_end'), {
|
||||||
format: 'L LTS'
|
format: '{% datetimepicker_format 'L LTS' %}'
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -0,0 +1,21 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from django import template
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag()
|
||||||
|
def datetimepicker_format(format_string='L LT'):
|
||||||
|
"""
|
||||||
|
Return a datetime format string for momentjs, with support for 24 hour time
|
||||||
|
override setting.
|
||||||
|
:param format_string: the default format string (locale based)
|
||||||
|
:return: the format string to use, as 24 hour time if configured.
|
||||||
|
"""
|
||||||
|
if settings.USE_24_HOUR_TIME_FORMAT:
|
||||||
|
if format_string == 'L LT':
|
||||||
|
return 'L HH:mm'
|
||||||
|
elif format_string == 'L LTS':
|
||||||
|
return 'L HH:mm:ss'
|
||||||
|
return format_string
|
|
@ -4,7 +4,7 @@ from django.test import TestCase
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from core.models import Child, Timer
|
from core.models import Child, Timer
|
||||||
from core.templatetags import bootstrap, duration, timers
|
from core.templatetags import bootstrap, datetimepicker, duration, timers
|
||||||
|
|
||||||
|
|
||||||
class TemplateTagsTestCase(TestCase):
|
class TemplateTagsTestCase(TestCase):
|
||||||
|
@ -60,3 +60,16 @@ class TemplateTagsTestCase(TestCase):
|
||||||
url = timers.instance_add_url({'timer': timer}, 'core:sleep-add')
|
url = timers.instance_add_url({'timer': timer}, 'core:sleep-add')
|
||||||
self.assertEqual(url, '/sleep/add/?timer={}&child={}'.format(
|
self.assertEqual(url, '/sleep/add/?timer={}&child={}'.format(
|
||||||
timer.id, child.slug))
|
timer.id, child.slug))
|
||||||
|
|
||||||
|
def test_datetimepicker_format(self):
|
||||||
|
self.assertEqual(datetimepicker.datetimepicker_format(), 'L LT')
|
||||||
|
self.assertEqual(datetimepicker.datetimepicker_format('L LT'), 'L LT')
|
||||||
|
self.assertEqual(
|
||||||
|
datetimepicker.datetimepicker_format('L LTS'), 'L LTS')
|
||||||
|
|
||||||
|
with self.settings(USE_24_HOUR_TIME_FORMAT=True):
|
||||||
|
self.assertEqual(datetimepicker.datetimepicker_format(), 'L HH:mm')
|
||||||
|
self.assertEqual(
|
||||||
|
datetimepicker.datetimepicker_format('L LT'), 'L HH:mm')
|
||||||
|
self.assertEqual(
|
||||||
|
datetimepicker.datetimepicker_format('L LTS'), 'L HH:mm:ss')
|
||||||
|
|
|
@ -58,6 +58,11 @@ module.exports = {
|
||||||
'babybuddy.scss'
|
'babybuddy.scss'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
testsConfig: {
|
||||||
|
isolated: [
|
||||||
|
'babybuddy.tests.tests_formats.FormatsTestCase.test_use_24_hour_time_format_en'
|
||||||
|
],
|
||||||
|
},
|
||||||
watchConfig: {
|
watchConfig: {
|
||||||
scriptsGlob: [
|
scriptsGlob: [
|
||||||
'*/static_src/js/**/*.js',
|
'*/static_src/js/**/*.js',
|
||||||
|
|
38
gulpfile.js
38
gulpfile.js
|
@ -2,6 +2,7 @@ var gulp = require('gulp');
|
||||||
|
|
||||||
var concat = require('gulp-concat');
|
var concat = require('gulp-concat');
|
||||||
var del = require('del');
|
var del = require('del');
|
||||||
|
var es = require('child_process').execSync;
|
||||||
var flatten = require('gulp-flatten');
|
var flatten = require('gulp-flatten');
|
||||||
var pump = require('pump');
|
var pump = require('pump');
|
||||||
var sass = require('gulp-sass');
|
var sass = require('gulp-sass');
|
||||||
|
@ -40,12 +41,23 @@ function coverage(cb) {
|
||||||
'coverage',
|
'coverage',
|
||||||
'run',
|
'run',
|
||||||
'manage.py',
|
'manage.py',
|
||||||
'test'
|
'test',
|
||||||
|
'--exclude-tag',
|
||||||
|
'isolate'
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
stdio: 'inherit'
|
stdio: 'inherit'
|
||||||
}
|
}
|
||||||
).on('exit', cb);
|
).on('exit', function() {
|
||||||
|
// Add coverage for isolated tests.
|
||||||
|
config.testsConfig.isolated.forEach(function(test_name) {
|
||||||
|
es(
|
||||||
|
'pipenv run coverage run -a manage.py test ' + test_name,
|
||||||
|
{stdio: 'inherit'}
|
||||||
|
);
|
||||||
|
})
|
||||||
|
cb();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -160,14 +172,30 @@ function styles(cb) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs all tests.
|
* Runs all tests _not_ tagged "isolate".
|
||||||
*
|
*
|
||||||
* @param cb
|
* @param cb
|
||||||
*/
|
*/
|
||||||
function test(cb) {
|
function test(cb) {
|
||||||
var command = ['run', 'python', 'manage.py', 'test'];
|
var command = [
|
||||||
|
'run',
|
||||||
|
'python',
|
||||||
|
'manage.py',
|
||||||
|
'test',
|
||||||
|
'--exclude-tag',
|
||||||
|
'isolate'
|
||||||
|
];
|
||||||
command = command.concat(process.argv.splice(3));
|
command = command.concat(process.argv.splice(3));
|
||||||
spawn('pipenv', command, { stdio: 'inherit' }).on('exit', cb);
|
spawn('pipenv', command, { stdio: 'inherit' }).on('exit', function() {
|
||||||
|
// Run isolated tests.
|
||||||
|
config.testsConfig.isolated.forEach(function(test_name) {
|
||||||
|
es(
|
||||||
|
'pipenv run python manage.py test ' + test_name,
|
||||||
|
{stdio: 'inherit'}
|
||||||
|
);
|
||||||
|
})
|
||||||
|
cb();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue