Fixed #8962 -- Consistently support format and input_format in the various (individual, combined, split) date and time form fields and widgets.

Many thanks to Tai Lee for doing all the work here.


git-svn-id: http://code.djangoproject.com/svn/django/trunk@10115 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Karen Tracey 2009-03-22 16:13:06 +00:00
parent b203db6ec8
commit 14b160957e
8 changed files with 177 additions and 17 deletions

View File

@ -256,6 +256,7 @@ answer newbie questions, and generally made Django that much better:
Eugene Lazutkin <http://lazutkin.com/blog/> Eugene Lazutkin <http://lazutkin.com/blog/>
lcordier@point45.com lcordier@point45.com
Jeong-Min Lee <falsetru@gmail.com> Jeong-Min Lee <falsetru@gmail.com>
Tai Lee <real.human@mrmachine.net>
Jannis Leidel <jl@websushi.org> Jannis Leidel <jl@websushi.org>
Christopher Lenz <http://www.cmlenz.net/> Christopher Lenz <http://www.cmlenz.net/>
lerouxb@gmail.com lerouxb@gmail.com
@ -295,7 +296,6 @@ answer newbie questions, and generally made Django that much better:
Aljosa Mohorovic <aljosa.mohorovic@gmail.com> Aljosa Mohorovic <aljosa.mohorovic@gmail.com>
Ramiro Morales <rm0@gmx.net> Ramiro Morales <rm0@gmx.net>
Eric Moritz <http://eric.themoritzfamily.com/> Eric Moritz <http://eric.themoritzfamily.com/>
mrmachine <real.human@mrmachine.net>
Robin Munn <http://www.geekforgod.com/> Robin Munn <http://www.geekforgod.com/>
James Murty James Murty
msundstr msundstr

View File

@ -36,3 +36,13 @@ class DateTimeField(forms.DateTimeField):
def __init__(self, input_formats=None, *args, **kwargs): def __init__(self, input_formats=None, *args, **kwargs):
input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS
super(DateTimeField, self).__init__(input_formats=input_formats, *args, **kwargs) super(DateTimeField, self).__init__(input_formats=input_formats, *args, **kwargs)
class SplitDateTimeField(forms.SplitDateTimeField):
"""
Split date and time input fields which use non-US date and time input
formats by default.
"""
def __init__(self, input_date_formats=None, input_time_formats=None, *args, **kwargs):
input_date_formats = input_date_formats or DEFAULT_DATE_INPUT_FORMATS
super(SplitDateTimeField, self).__init__(input_date_formats=input_date_formats,
input_time_formats=input_time_formats, *args, **kwargs)

View File

@ -28,7 +28,7 @@ from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import smart_unicode, smart_str from django.utils.encoding import smart_unicode, smart_str
from util import ErrorList, ValidationError from util import ErrorList, ValidationError
from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput, TimeInput, SplitDateTimeWidget, SplitHiddenDateTimeWidget from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateInput, DateTimeInput, TimeInput, SplitDateTimeWidget, SplitHiddenDateTimeWidget
from django.core.files.uploadedfile import SimpleUploadedFile as UploadedFile from django.core.files.uploadedfile import SimpleUploadedFile as UploadedFile
__all__ = ( __all__ = (
@ -283,6 +283,7 @@ DEFAULT_DATE_INPUT_FORMATS = (
) )
class DateField(Field): class DateField(Field):
widget = DateInput
default_error_messages = { default_error_messages = {
'invalid': _(u'Enter a valid date.'), 'invalid': _(u'Enter a valid date.'),
} }
@ -850,13 +851,13 @@ class SplitDateTimeField(MultiValueField):
'invalid_time': _(u'Enter a valid time.'), 'invalid_time': _(u'Enter a valid time.'),
} }
def __init__(self, *args, **kwargs): def __init__(self, input_date_formats=None, input_time_formats=None, *args, **kwargs):
errors = self.default_error_messages.copy() errors = self.default_error_messages.copy()
if 'error_messages' in kwargs: if 'error_messages' in kwargs:
errors.update(kwargs['error_messages']) errors.update(kwargs['error_messages'])
fields = ( fields = (
DateField(error_messages={'invalid': errors['invalid_date']}), DateField(input_formats=input_date_formats, error_messages={'invalid': errors['invalid_date']}),
TimeField(error_messages={'invalid': errors['invalid_time']}), TimeField(input_formats=input_time_formats, error_messages={'invalid': errors['invalid_time']}),
) )
super(SplitDateTimeField, self).__init__(fields, *args, **kwargs) super(SplitDateTimeField, self).__init__(fields, *args, **kwargs)

View File

@ -23,7 +23,7 @@ from urlparse import urljoin
__all__ = ( __all__ = (
'Media', 'MediaDefiningClass', 'Widget', 'TextInput', 'PasswordInput', 'Media', 'MediaDefiningClass', 'Widget', 'TextInput', 'PasswordInput',
'HiddenInput', 'MultipleHiddenInput', 'HiddenInput', 'MultipleHiddenInput',
'FileInput', 'DateTimeInput', 'TimeInput', 'Textarea', 'CheckboxInput', 'FileInput', 'DateInput', 'DateTimeInput', 'TimeInput', 'Textarea', 'CheckboxInput',
'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect', 'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect',
'CheckboxSelectMultiple', 'MultiWidget', 'CheckboxSelectMultiple', 'MultiWidget',
'SplitDateTimeWidget', 'SplitDateTimeWidget',
@ -285,6 +285,23 @@ class Textarea(Widget):
return mark_safe(u'<textarea%s>%s</textarea>' % (flatatt(final_attrs), return mark_safe(u'<textarea%s>%s</textarea>' % (flatatt(final_attrs),
conditional_escape(force_unicode(value)))) conditional_escape(force_unicode(value))))
class DateInput(Input):
input_type = 'text'
format = '%Y-%m-%d' # '2006-10-25'
def __init__(self, attrs=None, format=None):
super(DateInput, self).__init__(attrs)
if format:
self.format = format
def render(self, name, value, attrs=None):
if value is None:
value = ''
elif hasattr(value, 'strftime'):
value = datetime_safe.new_date(value)
value = value.strftime(self.format)
return super(DateInput, self).render(name, value, attrs)
class DateTimeInput(Input): class DateTimeInput(Input):
input_type = 'text' input_type = 'text'
format = '%Y-%m-%d %H:%M:%S' # '2006-10-25 14:30:59' format = '%Y-%m-%d %H:%M:%S' # '2006-10-25 14:30:59'
@ -304,12 +321,18 @@ class DateTimeInput(Input):
class TimeInput(Input): class TimeInput(Input):
input_type = 'text' input_type = 'text'
format = '%H:%M:%S' # '14:30:59'
def __init__(self, attrs=None, format=None):
super(TimeInput, self).__init__(attrs)
if format:
self.format = format
def render(self, name, value, attrs=None): def render(self, name, value, attrs=None):
if value is None: if value is None:
value = '' value = ''
elif isinstance(value, time): elif hasattr(value, 'strftime'):
value = value.replace(microsecond=0) value = value.strftime(self.format)
return super(TimeInput, self).render(name, value, attrs) return super(TimeInput, self).render(name, value, attrs)
class CheckboxInput(Widget): class CheckboxInput(Widget):
@ -654,8 +677,16 @@ class SplitDateTimeWidget(MultiWidget):
""" """
A Widget that splits datetime input into two <input type="text"> boxes. A Widget that splits datetime input into two <input type="text"> boxes.
""" """
def __init__(self, attrs=None): date_format = DateInput.format
widgets = (TextInput(attrs=attrs), TextInput(attrs=attrs)) time_format = TimeInput.format
def __init__(self, attrs=None, date_format=None, time_format=None):
if date_format:
self.date_format = date_format
if time_format:
self.time_format = time_format
widgets = (DateInput(attrs=attrs, format=self.date_format),
TimeInput(attrs=attrs, format=self.time_format))
super(SplitDateTimeWidget, self).__init__(widgets, attrs) super(SplitDateTimeWidget, self).__init__(widgets, attrs)
def decompress(self, value): def decompress(self, value):
@ -670,4 +701,3 @@ class SplitHiddenDateTimeWidget(SplitDateTimeWidget):
def __init__(self, attrs=None): def __init__(self, attrs=None):
widgets = (HiddenInput(attrs=attrs), HiddenInput(attrs=attrs)) widgets = (HiddenInput(attrs=attrs), HiddenInput(attrs=attrs))
super(SplitDateTimeWidget, self).__init__(widgets, attrs) super(SplitDateTimeWidget, self).__init__(widgets, attrs)

View File

@ -66,9 +66,9 @@ Countries currently supported by :mod:`~django.contrib.localflavor` are:
* `United States of America`_ * `United States of America`_
The ``django.contrib.localflavor`` package also includes a ``generic`` subpackage, The ``django.contrib.localflavor`` package also includes a ``generic`` subpackage,
containing useful code that is not specific to one particular country or containing useful code that is not specific to one particular country or culture.
culture. Currently, it defines date and datetime input fields based on those Currently, it defines date, datetime and split datetime input fields based on
from :ref:`forms <topics-forms-index>`, but with non-US default formats. those from :ref:`forms <topics-forms-index>`, but with non-US default formats.
Here's an example of how to use them:: Here's an example of how to use them::
from django import forms from django import forms

View File

@ -398,7 +398,7 @@ Takes extra arguments:
.. class:: DateField(**kwargs) .. class:: DateField(**kwargs)
* Default widget: ``TextInput`` * Default widget: ``DateInput``
* Empty value: ``None`` * Empty value: ``None``
* Normalizes to: A Python ``datetime.date`` object. * Normalizes to: A Python ``datetime.date`` object.
* Validates that the given value is either a ``datetime.date``, * Validates that the given value is either a ``datetime.date``,
@ -420,6 +420,10 @@ If no ``input_formats`` argument is provided, the default input formats are::
'%B %d %Y', '%B %d, %Y', # 'October 25 2006', 'October 25, 2006' '%B %d %Y', '%B %d, %Y', # 'October 25 2006', 'October 25, 2006'
'%d %B %Y', '%d %B, %Y', # '25 October 2006', '25 October, 2006' '%d %B %Y', '%d %B, %Y', # '25 October 2006', '25 October, 2006'
.. versionchanged:: 1.1
The ``DateField`` previously used a ``TextInput`` widget by default. It now
uses a ``DateInput`` widget.
``DateTimeField`` ``DateTimeField``
~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
@ -739,6 +743,36 @@ The following are not yet documented.
.. class:: SplitDateTimeField(**kwargs) .. class:: SplitDateTimeField(**kwargs)
* Default widget: ``SplitDateTimeWidget``
* Empty value: ``None``
* Normalizes to: A Python ``datetime.datetime`` object.
* Validates that the given value is a ``datetime.datetime`` or string
formatted in a particular datetime format.
* Error message keys: ``required``, ``invalid``
Takes two optional arguments:
.. attribute:: SplitDateTimeField.input_date_formats
A list of formats used to attempt to convert a string to a valid
``datetime.date`` object.
If no ``input_date_formats`` argument is provided, the default input formats
for ``DateField`` are used.
.. attribute:: SplitDateTimeField.input_time_formats
A list of formats used to attempt to convert a string to a valid
``datetime.time`` object.
If no ``input_time_formats`` argument is provided, the default input formats
for ``TimeField`` are used.
.. versionchanged:: 1.1
The ``SplitDateTimeField`` previously used two ``TextInput`` widgets by
default. The ``input_date_formats`` and ``input_time_formats`` arguments
are also new.
Fields which handle relationships Fields which handle relationships
--------------------------------- ---------------------------------

View File

@ -36,12 +36,49 @@ commonly used groups of widgets:
File upload input: ``<input type='file' ...>`` File upload input: ``<input type='file' ...>``
.. class:: DateInput
.. versionadded:: 1.1
Date input as a simple text box: ``<input type='text' ...>``
Takes one optional argument:
.. attribute:: DateInput.format
The format in which this field's initial value will be displayed.
If no ``format`` argument is provided, the default format is ``'%Y-%m-%d'``.
.. class:: DateTimeInput .. class:: DateTimeInput
.. versionadded:: 1.0 .. versionadded:: 1.0
Date/time input as a simple text box: ``<input type='text' ...>`` Date/time input as a simple text box: ``<input type='text' ...>``
Takes one optional argument:
.. attribute:: DateTimeInput.format
The format in which this field's initial value will be displayed.
If no ``format`` argument is provided, the default format is ``'%Y-%m-%d %H:%M:%S'``.
.. class:: TimeInput
Time input as a simple text box: ``<input type='text' ...>``
Takes one optional argument:
.. attribute:: TimeInput.format
The format in which this field's initial value will be displayed.
If no ``format`` argument is provided, the default format is ``'%H:%M:%S'``.
.. versionchanged:: 1.1
The ``format`` argument was not supported in Django 1.0.
.. class:: Textarea .. class:: Textarea
Text area: ``<textarea>...</textarea>`` Text area: ``<textarea>...</textarea>``
@ -91,8 +128,15 @@ commonly used groups of widgets:
.. class:: SplitDateTimeWidget .. class:: SplitDateTimeWidget
Wrapper around two ``TextInput`` widgets: one for the date, and one for the Wrapper around two widgets: ``DateInput`` for the date, and ``TimeInput``
time. for the time.
Takes two optional arguments, ``date_format`` and ``time_format``, which
work just like the ``format`` argument for ``DateInput`` and ``TimeInput``.
.. versionchanged:: 1.1
The ``date_format`` and ``time_format`` arguments were not supported in Django 1.0.
Specifying widgets Specifying widgets
------------------ ------------------

View File

@ -1071,6 +1071,11 @@ included on both widgets.
>>> w.render('date', datetime.datetime(2006, 1, 10, 7, 30)) >>> w.render('date', datetime.datetime(2006, 1, 10, 7, 30))
u'<input type="text" class="pretty" value="2006-01-10" name="date_0" /><input type="text" class="pretty" value="07:30:00" name="date_1" />' u'<input type="text" class="pretty" value="2006-01-10" name="date_0" /><input type="text" class="pretty" value="07:30:00" name="date_1" />'
Use 'date_format' and 'time_format' to change the way a value is displayed.
>>> w = SplitDateTimeWidget(date_format='%d/%m/%Y', time_format='%H:%M')
>>> w.render('date', datetime.datetime(2006, 1, 10, 7, 30))
u'<input type="text" name="date_0" value="10/01/2006" /><input type="text" name="date_1" value="07:30" />'
>>> w._has_changed(datetime.datetime(2008, 5, 5, 12, 40, 00), [u'2008-05-05', u'12:40:00']) >>> w._has_changed(datetime.datetime(2008, 5, 5, 12, 40, 00), [u'2008-05-05', u'12:40:00'])
False False
>>> w._has_changed(datetime.datetime(2008, 5, 5, 12, 40, 00), [u'2008-05-05', u'12:41:00']) >>> w._has_changed(datetime.datetime(2008, 5, 5, 12, 40, 00), [u'2008-05-05', u'12:41:00'])
@ -1093,6 +1098,34 @@ u'<input type="text" name="date" value="2007-09-17 12:51:34" />'
>>> w.render('date', datetime.datetime(2007, 9, 17, 12, 51)) >>> w.render('date', datetime.datetime(2007, 9, 17, 12, 51))
u'<input type="text" name="date" value="2007-09-17 12:51:00" />' u'<input type="text" name="date" value="2007-09-17 12:51:00" />'
Use 'format' to change the way a value is displayed.
>>> w = DateTimeInput(format='%d/%m/%Y %H:%M')
>>> w.render('date', d)
u'<input type="text" name="date" value="17/09/2007 12:51" />'
# DateInput ###################################################################
>>> w = DateInput()
>>> w.render('date', None)
u'<input type="text" name="date" />'
>>> d = datetime.date(2007, 9, 17)
>>> print d
2007-09-17
>>> w.render('date', d)
u'<input type="text" name="date" value="2007-09-17" />'
>>> w.render('date', datetime.date(2007, 9, 17))
u'<input type="text" name="date" value="2007-09-17" />'
We should be able to initialize from a unicode value.
>>> w.render('date', u'2007-09-17')
u'<input type="text" name="date" value="2007-09-17" />'
Use 'format' to change the way a value is displayed.
>>> w = DateInput(format='%d/%m/%Y')
>>> w.render('date', d)
u'<input type="text" name="date" value="17/09/2007" />'
# TimeInput ################################################################### # TimeInput ###################################################################
>>> w = TimeInput() >>> w = TimeInput()
@ -1114,6 +1147,11 @@ We should be able to initialize from a unicode value.
>>> w.render('time', u'13:12:11') >>> w.render('time', u'13:12:11')
u'<input type="text" name="time" value="13:12:11" />' u'<input type="text" name="time" value="13:12:11" />'
Use 'format' to change the way a value is displayed.
>>> w = TimeInput(format='%H:%M')
>>> w.render('time', t)
u'<input type="text" name="time" value="12:51" />'
# SplitHiddenDateTimeWidget ################################################### # SplitHiddenDateTimeWidget ###################################################
>>> from django.forms.widgets import SplitHiddenDateTimeWidget >>> from django.forms.widgets import SplitHiddenDateTimeWidget
@ -1121,6 +1159,9 @@ u'<input type="text" name="time" value="13:12:11" />'
>>> w = SplitHiddenDateTimeWidget() >>> w = SplitHiddenDateTimeWidget()
>>> w.render('date', '') >>> w.render('date', '')
u'<input type="hidden" name="date_0" /><input type="hidden" name="date_1" />' u'<input type="hidden" name="date_0" /><input type="hidden" name="date_1" />'
>>> d = datetime.datetime(2007, 9, 17, 12, 51, 34, 482548)
>>> print d
2007-09-17 12:51:34.482548
>>> w.render('date', d) >>> w.render('date', d)
u'<input type="hidden" name="date_0" value="2007-09-17" /><input type="hidden" name="date_1" value="12:51:34" />' u'<input type="hidden" name="date_0" value="2007-09-17" /><input type="hidden" name="date_1" value="12:51:34" />'
>>> w.render('date', datetime.datetime(2007, 9, 17, 12, 51, 34)) >>> w.render('date', datetime.datetime(2007, 9, 17, 12, 51, 34))