kopia lustrzana https://github.com/wagtail/wagtail
Adds support for custom date and datetime formats (#2595)
It is possible to set default format for date/datetime inputs. This works together with standard django localization. # django settings USE_I18N = True LANGUAGE_CODE = 'sl' # wagtail settings WAGTAIL_DATE_FORMAT = '%d.%m.%Y.' WAGTAIL_DATETIME_FORMAT = '%d.%m.%Y. %H:%M' DateBlock, DateTimeBlock accepts additional keyword argument `format`.pull/3366/merge
rodzic
48949e69a7
commit
0f53afc5a6
|
@ -27,6 +27,7 @@ Changelog
|
|||
* Draft page view is now restricted to users with edit / publish permission over the page (Kees Hink)
|
||||
* Added the option to delete a previously saved focal point on a image (Maarten Kling)
|
||||
* Page explorer menu item, search and summary panel are now hidden for users with no page permissions (Tim Heap)
|
||||
* Added support for custom date and datetime formats in input fields (Bojan Mihelac)
|
||||
* Fix: Marked 'Date from' / 'Date to' strings in wagtailforms for translation (Vorlif)
|
||||
* Fix: "File" field label on image edit form is now translated (Stein Strindhaug)
|
||||
* Fix: Unreliable preview is now reliable by always opening in a new window (Kjartan Sverrisson)
|
||||
|
|
|
@ -383,6 +383,18 @@ When enabled Wagtail shows where a particular image, document or snippet is bein
|
|||
|
||||
The usage count only applies to direct (database) references. Using documents, images and snippets within StreamFields or rich text fields will not be taken into account.
|
||||
|
||||
Date and DateTime inputs
|
||||
------------------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
WAGTAIL_DATE_FORMAT = '%d.%m.%Y.'
|
||||
WAGTAIL_DATETIME_FORMAT = '%d.%m.%Y. %H:%M'
|
||||
|
||||
|
||||
Specifies the date and datetime format to be used in input fields in the Wagtail admin. The format is specified in `Python datetime module syntax <https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior>`_, and must be one of the recognised formats listed in the ``DATE_INPUT_FORMATS`` or ``DATETIME_INPUT_FORMATS`` setting respectively (see `DATE_INPUT_FORMATS <https://docs.djangoproject.com/en/1.10/ref/settings/#std:setting-DATE_INPUT_FORMATS>`_).
|
||||
|
||||
|
||||
URL Patterns
|
||||
~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ Other features
|
|||
* Draft page view is now restricted to users with edit / publish permission over the page (Kees Hink)
|
||||
* Added the option to delete a previously saved focal point on a image (Maarten Kling)
|
||||
* Page explorer menu item, search and summary panel are now hidden for users with no page permissions (Tim Heap)
|
||||
* Added support for custom date and datetime formats in input fields (Bojan Mihelac)
|
||||
|
||||
|
||||
Bug fixes
|
||||
|
|
|
@ -157,7 +157,10 @@ DateBlock
|
|||
|
||||
``wagtail.wagtailcore.blocks.DateBlock``
|
||||
|
||||
A date picker. The keyword arguments ``required`` and ``help_text`` are accepted.
|
||||
A date picker. The keyword arguments ``required``, ``help_text`` and ``format`` are accepted.
|
||||
|
||||
``format`` (default: None)
|
||||
Date format. If not specifed Wagtail will use ``WAGTAIL_DATE_FORMAT`` setting with fallback to '%Y-%m-%d'.
|
||||
|
||||
TimeBlock
|
||||
~~~~~~~~~
|
||||
|
@ -171,7 +174,10 @@ DateTimeBlock
|
|||
|
||||
``wagtail.wagtailcore.blocks.DateTimeBlock``
|
||||
|
||||
A combined date / time picker. The keyword arguments ``required`` and ``help_text`` are accepted.
|
||||
A combined date / time picker. The keyword arguments ``required``, ``help_text`` and ``format`` are accepted.
|
||||
|
||||
``format`` (default: None)
|
||||
Date format. If not specifed Wagtail will use ``WAGTAIL_DATETIME_FORMAT`` setting with fallback to '%Y-%m-%d %H:%M'.
|
||||
|
||||
RichTextBlock
|
||||
~~~~~~~~~~~~~
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
|
||||
# Adapted from https://djangosnippets.org/snippets/10563/
|
||||
# original author bernd-wechner
|
||||
def to_datetimepicker_format(python_format_string):
|
||||
"""
|
||||
Given a python datetime format string, attempts to convert it to
|
||||
the nearest PHP datetime format string possible.
|
||||
"""
|
||||
python2PHP = {
|
||||
"%a": "D",
|
||||
"%A": "l",
|
||||
"%b": "M",
|
||||
"%B": "F",
|
||||
"%c": "",
|
||||
"%d": "d",
|
||||
"%H": "H",
|
||||
"%I": "h",
|
||||
"%j": "z",
|
||||
"%m": "m",
|
||||
"%M": "i",
|
||||
"%p": "A",
|
||||
"%S": "s",
|
||||
"%U": "",
|
||||
"%w": "w",
|
||||
"%W": "W",
|
||||
"%x": "",
|
||||
"%X": "",
|
||||
"%y": "y",
|
||||
"%Y": "Y",
|
||||
"%Z": "e",
|
||||
}
|
||||
|
||||
php_format_string = python_format_string
|
||||
for py, php in python2PHP.items():
|
||||
php_format_string = php_format_string.replace(py, php)
|
||||
|
||||
return php_format_string
|
|
@ -1,6 +1,7 @@
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from wagtail.tests.testapp.models import EventPage, SimplePage
|
||||
from wagtail.wagtailadmin import widgets
|
||||
|
@ -80,3 +81,71 @@ class TestAdminPageChooserWidget(TestCase):
|
|||
self.assertEqual(
|
||||
js_init, "createPageChooser(\"test-id\", [\"wagtailcore.page\"], %d, true);" % self.root_page.id
|
||||
)
|
||||
|
||||
|
||||
class TestAdminDateInput(TestCase):
|
||||
|
||||
def test_render_js_init(self):
|
||||
widget = widgets.AdminDateInput()
|
||||
|
||||
js_init = widget.render_js_init('test-id', 'test', None)
|
||||
|
||||
# we should see the JS initialiser code:
|
||||
# initDateChooser("test-id", {"dayOfWeekStart": 0, "format": "Y-m-d"});
|
||||
# except that we can't predict the order of the config options
|
||||
self.assertIn('initDateChooser("test-id", {', js_init)
|
||||
self.assertIn('"dayOfWeekStart": 0', js_init)
|
||||
self.assertIn('"format": "Y-m-d"', js_init)
|
||||
|
||||
def test_render_js_init_with_format(self):
|
||||
widget = widgets.AdminDateInput(format='%d.%m.%Y.')
|
||||
|
||||
js_init = widget.render_js_init('test-id', 'test', None)
|
||||
self.assertIn(
|
||||
'"format": "d.m.Y."',
|
||||
js_init,
|
||||
)
|
||||
|
||||
@override_settings(WAGTAIL_DATE_FORMAT='%d.%m.%Y.')
|
||||
def test_render_js_init_with_format_from_settings(self):
|
||||
widget = widgets.AdminDateInput()
|
||||
|
||||
js_init = widget.render_js_init('test-id', 'test', None)
|
||||
self.assertIn(
|
||||
'"format": "d.m.Y."',
|
||||
js_init,
|
||||
)
|
||||
|
||||
|
||||
class TestAdminDateTimeInput(TestCase):
|
||||
|
||||
def test_render_js_init(self):
|
||||
widget = widgets.AdminDateTimeInput()
|
||||
|
||||
js_init = widget.render_js_init('test-id', 'test', None)
|
||||
|
||||
# we should see the JS initialiser code:
|
||||
# initDateTimeChooser("test-id", {"dayOfWeekStart": 0, "format": "Y-m-d H:i"});
|
||||
# except that we can't predict the order of the config options
|
||||
self.assertIn('initDateTimeChooser("test-id", {', js_init)
|
||||
self.assertIn('"dayOfWeekStart": 0', js_init)
|
||||
self.assertIn('"format": "Y-m-d H:i"', js_init)
|
||||
|
||||
def test_render_js_init_with_format(self):
|
||||
widget = widgets.AdminDateTimeInput(format='%d.%m.%Y. %H:%M')
|
||||
|
||||
js_init = widget.render_js_init('test-id', 'test', None)
|
||||
self.assertIn(
|
||||
'"format": "d.m.Y. H:i"',
|
||||
js_init,
|
||||
)
|
||||
|
||||
@override_settings(WAGTAIL_DATETIME_FORMAT='%d.%m.%Y. %H:%M')
|
||||
def test_render_js_init_with_format_from_settings(self):
|
||||
widget = widgets.AdminDateTimeInput()
|
||||
|
||||
js_init = widget.render_js_init('test-id', 'test', None)
|
||||
self.assertIn(
|
||||
'"format": "d.m.Y. H:i"',
|
||||
js_init,
|
||||
)
|
||||
|
|
|
@ -17,10 +17,15 @@ from django.utils.translation import ugettext_lazy as _
|
|||
from taggit.forms import TagWidget
|
||||
|
||||
from wagtail.utils.widgets import WidgetWithScript
|
||||
from wagtail.wagtailadmin.datetimepicker import to_datetimepicker_format
|
||||
from wagtail.wagtailcore import hooks
|
||||
from wagtail.wagtailcore.models import Page
|
||||
|
||||
|
||||
DEFAULT_DATE_FORMAT = '%Y-%m-%d'
|
||||
DEFAULT_DATETIME_FORMAT = '%Y-%m-%d %H:%M'
|
||||
|
||||
|
||||
class AdminAutoHeightTextInput(WidgetWithScript, widgets.Textarea):
|
||||
def __init__(self, attrs=None):
|
||||
# Use more appropriate rows default, given autoheight will alter this anyway
|
||||
|
@ -35,16 +40,21 @@ class AdminAutoHeightTextInput(WidgetWithScript, widgets.Textarea):
|
|||
|
||||
|
||||
class AdminDateInput(WidgetWithScript, widgets.DateInput):
|
||||
# Set a default date format to match the one that our JS date picker expects -
|
||||
# it can still be overridden explicitly, but this way it won't be affected by
|
||||
# the DATE_INPUT_FORMATS setting
|
||||
def __init__(self, attrs=None, format='%Y-%m-%d'):
|
||||
super(AdminDateInput, self).__init__(attrs=attrs, format=format)
|
||||
def __init__(self, attrs=None, format=None):
|
||||
fmt = format
|
||||
if fmt is None:
|
||||
fmt = getattr(settings, 'WAGTAIL_DATE_FORMAT', DEFAULT_DATE_FORMAT)
|
||||
self.js_format = to_datetimepicker_format(fmt)
|
||||
super(AdminDateInput, self).__init__(attrs=attrs, format=fmt)
|
||||
|
||||
def render_js_init(self, id_, name, value):
|
||||
config = {
|
||||
'dayOfWeekStart': get_format('FIRST_DAY_OF_WEEK'),
|
||||
'format': self.js_format,
|
||||
}
|
||||
return 'initDateChooser({0}, {1});'.format(
|
||||
json.dumps(id_),
|
||||
json.dumps({'dayOfWeekStart': get_format('FIRST_DAY_OF_WEEK')})
|
||||
json.dumps(config)
|
||||
)
|
||||
|
||||
|
||||
|
@ -57,13 +67,21 @@ class AdminTimeInput(WidgetWithScript, widgets.TimeInput):
|
|||
|
||||
|
||||
class AdminDateTimeInput(WidgetWithScript, widgets.DateTimeInput):
|
||||
def __init__(self, attrs=None, format='%Y-%m-%d %H:%M'):
|
||||
super(AdminDateTimeInput, self).__init__(attrs=attrs, format=format)
|
||||
def __init__(self, attrs=None, format=None):
|
||||
fmt = format
|
||||
if fmt is None:
|
||||
fmt = getattr(settings, 'WAGTAIL_DATETIME_FORMAT', DEFAULT_DATETIME_FORMAT)
|
||||
self.js_format = to_datetimepicker_format(fmt)
|
||||
super(AdminDateTimeInput, self).__init__(attrs=attrs, format=fmt)
|
||||
|
||||
def render_js_init(self, id_, name, value):
|
||||
config = {
|
||||
'dayOfWeekStart': get_format('FIRST_DAY_OF_WEEK'),
|
||||
'format': self.js_format,
|
||||
}
|
||||
return 'initDateTimeChooser({0}, {1});'.format(
|
||||
json.dumps(id_),
|
||||
json.dumps({'dayOfWeekStart': get_format('FIRST_DAY_OF_WEEK')})
|
||||
json.dumps(config)
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -231,14 +231,21 @@ class BooleanBlock(FieldBlock):
|
|||
|
||||
class DateBlock(FieldBlock):
|
||||
|
||||
def __init__(self, required=True, help_text=None, **kwargs):
|
||||
def __init__(self, required=True, help_text=None, format=None, **kwargs):
|
||||
self.field_options = {'required': required, 'help_text': help_text}
|
||||
try:
|
||||
self.field_options['input_formats'] = kwargs.pop('input_formats')
|
||||
except KeyError:
|
||||
pass
|
||||
self.format = format
|
||||
super(DateBlock, self).__init__(**kwargs)
|
||||
|
||||
@cached_property
|
||||
def field(self):
|
||||
from wagtail.wagtailadmin.widgets import AdminDateInput
|
||||
field_kwargs = {'widget': AdminDateInput}
|
||||
field_kwargs = {
|
||||
'widget': AdminDateInput(format=self.format),
|
||||
}
|
||||
field_kwargs.update(self.field_options)
|
||||
return forms.DateField(**field_kwargs)
|
||||
|
||||
|
@ -280,14 +287,17 @@ class TimeBlock(FieldBlock):
|
|||
|
||||
class DateTimeBlock(FieldBlock):
|
||||
|
||||
def __init__(self, required=True, help_text=None, **kwargs):
|
||||
def __init__(self, required=True, help_text=None, format=None, **kwargs):
|
||||
self.field_options = {'required': required, 'help_text': help_text}
|
||||
self.format = format
|
||||
super(DateTimeBlock, self).__init__(**kwargs)
|
||||
|
||||
@cached_property
|
||||
def field(self):
|
||||
from wagtail.wagtailadmin.widgets import AdminDateTimeInput
|
||||
field_kwargs = {'widget': AdminDateTimeInput}
|
||||
field_kwargs = {
|
||||
'widget': AdminDateTimeInput(format=self.format),
|
||||
}
|
||||
field_kwargs.update(self.field_options)
|
||||
return forms.DateTimeField(**field_kwargs)
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import collections
|
|||
import json
|
||||
import unittest
|
||||
import warnings
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
|
||||
# non-standard import name for ugettext_lazy, to prevent strings from being picked up for translation
|
||||
|
@ -2438,6 +2439,55 @@ class TestStaticBlock(unittest.TestCase):
|
|||
self.assertEqual(result, None)
|
||||
|
||||
|
||||
class TestDateBlock(TestCase):
|
||||
|
||||
def test_render_form(self):
|
||||
block = blocks.DateBlock()
|
||||
value = date(2015, 8, 13)
|
||||
result = block.render_form(value, prefix='dateblock')
|
||||
|
||||
# we should see the JS initialiser code:
|
||||
# <script>initDateChooser("dateblock", {"dayOfWeekStart": 0, "format": "Y-m-d"});</script>
|
||||
# except that we can't predict the order of the config options
|
||||
self.assertIn('<script>initDateChooser("dateblock", {', result)
|
||||
self.assertIn('"dayOfWeekStart": 0', result)
|
||||
self.assertIn('"format": "Y-m-d"', result)
|
||||
|
||||
self.assertInHTML(
|
||||
'<input id="dateblock" name="dateblock" placeholder="" type="text" value="2015-08-13" />',
|
||||
result
|
||||
)
|
||||
|
||||
def test_render_form_with_format(self):
|
||||
block = blocks.DateBlock(format='%d.%m.%Y')
|
||||
value = date(2015, 8, 13)
|
||||
result = block.render_form(value, prefix='dateblock')
|
||||
|
||||
self.assertIn('<script>initDateChooser("dateblock", {', result)
|
||||
self.assertIn('"dayOfWeekStart": 0', result)
|
||||
self.assertIn('"format": "d.m.Y"', result)
|
||||
self.assertInHTML(
|
||||
'<input id="dateblock" name="dateblock" placeholder="" type="text" value="13.08.2015" />',
|
||||
result
|
||||
)
|
||||
|
||||
|
||||
class TestDateTimeBlock(TestCase):
|
||||
|
||||
def test_render_form_with_format(self):
|
||||
block = blocks.DateTimeBlock(format='%d.%m.%Y %H:%M')
|
||||
value = datetime(2015, 8, 13, 10, 0)
|
||||
result = block.render_form(value, prefix='datetimeblock')
|
||||
self.assertIn(
|
||||
'"format": "d.m.Y H:i"',
|
||||
result
|
||||
)
|
||||
self.assertInHTML(
|
||||
'<input id="datetimeblock" name="datetimeblock" placeholder="" type="text" value="13.08.2015 10:00" />',
|
||||
result
|
||||
)
|
||||
|
||||
|
||||
class TestSystemCheck(TestCase):
|
||||
def test_name_must_be_nonempty(self):
|
||||
block = blocks.StreamBlock([
|
||||
|
|
Ładowanie…
Reference in New Issue