Skip to content

Commit 14b1609

Browse files
committed
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
1 parent b203db6 commit 14b1609

File tree

8 files changed

+177
-17
lines changed

8 files changed

+177
-17
lines changed

AUTHORS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ answer newbie questions, and generally made Django that much better:
256256
Eugene Lazutkin <http://lazutkin.com/blog/>
257257
258258
Jeong-Min Lee <[email protected]>
259+
259260
Jannis Leidel <[email protected]>
260261
Christopher Lenz <http://www.cmlenz.net/>
261262
@@ -295,7 +296,6 @@ answer newbie questions, and generally made Django that much better:
295296
Aljosa Mohorovic <[email protected]>
296297
Ramiro Morales <[email protected]>
297298
Eric Moritz <http://eric.themoritzfamily.com/>
298-
mrmachine <[email protected]>
299299
Robin Munn <http://www.geekforgod.com/>
300300
James Murty
301301
msundstr

django/contrib/localflavor/generic/forms.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,13 @@ class DateTimeField(forms.DateTimeField):
3636
def __init__(self, input_formats=None, *args, **kwargs):
3737
input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS
3838
super(DateTimeField, self).__init__(input_formats=input_formats, *args, **kwargs)
39+
40+
class SplitDateTimeField(forms.SplitDateTimeField):
41+
"""
42+
Split date and time input fields which use non-US date and time input
43+
formats by default.
44+
"""
45+
def __init__(self, input_date_formats=None, input_time_formats=None, *args, **kwargs):
46+
input_date_formats = input_date_formats or DEFAULT_DATE_INPUT_FORMATS
47+
super(SplitDateTimeField, self).__init__(input_date_formats=input_date_formats,
48+
input_time_formats=input_time_formats, *args, **kwargs)

django/forms/fields.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from django.utils.encoding import smart_unicode, smart_str
2929

3030
from util import ErrorList, ValidationError
31-
from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput, TimeInput, SplitDateTimeWidget, SplitHiddenDateTimeWidget
31+
from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateInput, DateTimeInput, TimeInput, SplitDateTimeWidget, SplitHiddenDateTimeWidget
3232
from django.core.files.uploadedfile import SimpleUploadedFile as UploadedFile
3333

3434
__all__ = (
@@ -283,6 +283,7 @@ def clean(self, value):
283283
)
284284

285285
class DateField(Field):
286+
widget = DateInput
286287
default_error_messages = {
287288
'invalid': _(u'Enter a valid date.'),
288289
}
@@ -850,13 +851,13 @@ class SplitDateTimeField(MultiValueField):
850851
'invalid_time': _(u'Enter a valid time.'),
851852
}
852853

853-
def __init__(self, *args, **kwargs):
854+
def __init__(self, input_date_formats=None, input_time_formats=None, *args, **kwargs):
854855
errors = self.default_error_messages.copy()
855856
if 'error_messages' in kwargs:
856857
errors.update(kwargs['error_messages'])
857858
fields = (
858-
DateField(error_messages={'invalid': errors['invalid_date']}),
859-
TimeField(error_messages={'invalid': errors['invalid_time']}),
859+
DateField(input_formats=input_date_formats, error_messages={'invalid': errors['invalid_date']}),
860+
TimeField(input_formats=input_time_formats, error_messages={'invalid': errors['invalid_time']}),
860861
)
861862
super(SplitDateTimeField, self).__init__(fields, *args, **kwargs)
862863

django/forms/widgets.py

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
__all__ = (
2424
'Media', 'MediaDefiningClass', 'Widget', 'TextInput', 'PasswordInput',
2525
'HiddenInput', 'MultipleHiddenInput',
26-
'FileInput', 'DateTimeInput', 'TimeInput', 'Textarea', 'CheckboxInput',
26+
'FileInput', 'DateInput', 'DateTimeInput', 'TimeInput', 'Textarea', 'CheckboxInput',
2727
'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect',
2828
'CheckboxSelectMultiple', 'MultiWidget',
2929
'SplitDateTimeWidget',
@@ -285,6 +285,23 @@ def render(self, name, value, attrs=None):
285285
return mark_safe(u'<textarea%s>%s</textarea>' % (flatatt(final_attrs),
286286
conditional_escape(force_unicode(value))))
287287

288+
class DateInput(Input):
289+
input_type = 'text'
290+
format = '%Y-%m-%d' # '2006-10-25'
291+
292+
def __init__(self, attrs=None, format=None):
293+
super(DateInput, self).__init__(attrs)
294+
if format:
295+
self.format = format
296+
297+
def render(self, name, value, attrs=None):
298+
if value is None:
299+
value = ''
300+
elif hasattr(value, 'strftime'):
301+
value = datetime_safe.new_date(value)
302+
value = value.strftime(self.format)
303+
return super(DateInput, self).render(name, value, attrs)
304+
288305
class DateTimeInput(Input):
289306
input_type = 'text'
290307
format = '%Y-%m-%d %H:%M:%S' # '2006-10-25 14:30:59'
@@ -304,12 +321,18 @@ def render(self, name, value, attrs=None):
304321

305322
class TimeInput(Input):
306323
input_type = 'text'
324+
format = '%H:%M:%S' # '14:30:59'
325+
326+
def __init__(self, attrs=None, format=None):
327+
super(TimeInput, self).__init__(attrs)
328+
if format:
329+
self.format = format
307330

308331
def render(self, name, value, attrs=None):
309332
if value is None:
310333
value = ''
311-
elif isinstance(value, time):
312-
value = value.replace(microsecond=0)
334+
elif hasattr(value, 'strftime'):
335+
value = value.strftime(self.format)
313336
return super(TimeInput, self).render(name, value, attrs)
314337

315338
class CheckboxInput(Widget):
@@ -654,8 +677,16 @@ class SplitDateTimeWidget(MultiWidget):
654677
"""
655678
A Widget that splits datetime input into two <input type="text"> boxes.
656679
"""
657-
def __init__(self, attrs=None):
658-
widgets = (TextInput(attrs=attrs), TextInput(attrs=attrs))
680+
date_format = DateInput.format
681+
time_format = TimeInput.format
682+
683+
def __init__(self, attrs=None, date_format=None, time_format=None):
684+
if date_format:
685+
self.date_format = date_format
686+
if time_format:
687+
self.time_format = time_format
688+
widgets = (DateInput(attrs=attrs, format=self.date_format),
689+
TimeInput(attrs=attrs, format=self.time_format))
659690
super(SplitDateTimeWidget, self).__init__(widgets, attrs)
660691

661692
def decompress(self, value):
@@ -670,4 +701,3 @@ class SplitHiddenDateTimeWidget(SplitDateTimeWidget):
670701
def __init__(self, attrs=None):
671702
widgets = (HiddenInput(attrs=attrs), HiddenInput(attrs=attrs))
672703
super(SplitDateTimeWidget, self).__init__(widgets, attrs)
673-

docs/ref/contrib/localflavor.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,9 @@ Countries currently supported by :mod:`~django.contrib.localflavor` are:
6666
* `United States of America`_
6767

6868
The ``django.contrib.localflavor`` package also includes a ``generic`` subpackage,
69-
containing useful code that is not specific to one particular country or
70-
culture. Currently, it defines date and datetime input fields based on those
71-
from :ref:`forms <topics-forms-index>`, but with non-US default formats.
69+
containing useful code that is not specific to one particular country or culture.
70+
Currently, it defines date, datetime and split datetime input fields based on
71+
those from :ref:`forms <topics-forms-index>`, but with non-US default formats.
7272
Here's an example of how to use them::
7373

7474
from django import forms

docs/ref/forms/fields.txt

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ Takes extra arguments:
398398

399399
.. class:: DateField(**kwargs)
400400

401-
* Default widget: ``TextInput``
401+
* Default widget: ``DateInput``
402402
* Empty value: ``None``
403403
* Normalizes to: A Python ``datetime.date`` object.
404404
* 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::
420420
'%B %d %Y', '%B %d, %Y', # 'October 25 2006', 'October 25, 2006'
421421
'%d %B %Y', '%d %B, %Y', # '25 October 2006', '25 October, 2006'
422422

423+
.. versionchanged:: 1.1
424+
The ``DateField`` previously used a ``TextInput`` widget by default. It now
425+
uses a ``DateInput`` widget.
426+
423427
``DateTimeField``
424428
~~~~~~~~~~~~~~~~~
425429

@@ -739,6 +743,36 @@ The following are not yet documented.
739743

740744
.. class:: SplitDateTimeField(**kwargs)
741745

746+
* Default widget: ``SplitDateTimeWidget``
747+
* Empty value: ``None``
748+
* Normalizes to: A Python ``datetime.datetime`` object.
749+
* Validates that the given value is a ``datetime.datetime`` or string
750+
formatted in a particular datetime format.
751+
* Error message keys: ``required``, ``invalid``
752+
753+
Takes two optional arguments:
754+
755+
.. attribute:: SplitDateTimeField.input_date_formats
756+
757+
A list of formats used to attempt to convert a string to a valid
758+
``datetime.date`` object.
759+
760+
If no ``input_date_formats`` argument is provided, the default input formats
761+
for ``DateField`` are used.
762+
763+
.. attribute:: SplitDateTimeField.input_time_formats
764+
765+
A list of formats used to attempt to convert a string to a valid
766+
``datetime.time`` object.
767+
768+
If no ``input_time_formats`` argument is provided, the default input formats
769+
for ``TimeField`` are used.
770+
771+
.. versionchanged:: 1.1
772+
The ``SplitDateTimeField`` previously used two ``TextInput`` widgets by
773+
default. The ``input_date_formats`` and ``input_time_formats`` arguments
774+
are also new.
775+
742776
Fields which handle relationships
743777
---------------------------------
744778

docs/ref/forms/widgets.txt

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,49 @@ commonly used groups of widgets:
3636

3737
File upload input: ``<input type='file' ...>``
3838

39+
.. class:: DateInput
40+
41+
.. versionadded:: 1.1
42+
43+
Date input as a simple text box: ``<input type='text' ...>``
44+
45+
Takes one optional argument:
46+
47+
.. attribute:: DateInput.format
48+
49+
The format in which this field's initial value will be displayed.
50+
51+
If no ``format`` argument is provided, the default format is ``'%Y-%m-%d'``.
52+
3953
.. class:: DateTimeInput
4054

4155
.. versionadded:: 1.0
4256

4357
Date/time input as a simple text box: ``<input type='text' ...>``
4458

59+
Takes one optional argument:
60+
61+
.. attribute:: DateTimeInput.format
62+
63+
The format in which this field's initial value will be displayed.
64+
65+
If no ``format`` argument is provided, the default format is ``'%Y-%m-%d %H:%M:%S'``.
66+
67+
.. class:: TimeInput
68+
69+
Time input as a simple text box: ``<input type='text' ...>``
70+
71+
Takes one optional argument:
72+
73+
.. attribute:: TimeInput.format
74+
75+
The format in which this field's initial value will be displayed.
76+
77+
If no ``format`` argument is provided, the default format is ``'%H:%M:%S'``.
78+
79+
.. versionchanged:: 1.1
80+
The ``format`` argument was not supported in Django 1.0.
81+
4582
.. class:: Textarea
4683

4784
Text area: ``<textarea>...</textarea>``
@@ -91,8 +128,15 @@ commonly used groups of widgets:
91128

92129
.. class:: SplitDateTimeWidget
93130

94-
Wrapper around two ``TextInput`` widgets: one for the date, and one for the
95-
time.
131+
Wrapper around two widgets: ``DateInput`` for the date, and ``TimeInput``
132+
for the time.
133+
134+
Takes two optional arguments, ``date_format`` and ``time_format``, which
135+
work just like the ``format`` argument for ``DateInput`` and ``TimeInput``.
136+
137+
.. versionchanged:: 1.1
138+
The ``date_format`` and ``time_format`` arguments were not supported in Django 1.0.
139+
96140

97141
Specifying widgets
98142
------------------

tests/regressiontests/forms/widgets.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,6 +1071,11 @@
10711071
>>> w.render('date', datetime.datetime(2006, 1, 10, 7, 30))
10721072
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" />'
10731073
1074+
Use 'date_format' and 'time_format' to change the way a value is displayed.
1075+
>>> w = SplitDateTimeWidget(date_format='%d/%m/%Y', time_format='%H:%M')
1076+
>>> w.render('date', datetime.datetime(2006, 1, 10, 7, 30))
1077+
u'<input type="text" name="date_0" value="10/01/2006" /><input type="text" name="date_1" value="07:30" />'
1078+
10741079
>>> w._has_changed(datetime.datetime(2008, 5, 5, 12, 40, 00), [u'2008-05-05', u'12:40:00'])
10751080
False
10761081
>>> w._has_changed(datetime.datetime(2008, 5, 5, 12, 40, 00), [u'2008-05-05', u'12:41:00'])
@@ -1093,6 +1098,34 @@
10931098
>>> w.render('date', datetime.datetime(2007, 9, 17, 12, 51))
10941099
u'<input type="text" name="date" value="2007-09-17 12:51:00" />'
10951100
1101+
Use 'format' to change the way a value is displayed.
1102+
>>> w = DateTimeInput(format='%d/%m/%Y %H:%M')
1103+
>>> w.render('date', d)
1104+
u'<input type="text" name="date" value="17/09/2007 12:51" />'
1105+
1106+
# DateInput ###################################################################
1107+
1108+
>>> w = DateInput()
1109+
>>> w.render('date', None)
1110+
u'<input type="text" name="date" />'
1111+
>>> d = datetime.date(2007, 9, 17)
1112+
>>> print d
1113+
2007-09-17
1114+
1115+
>>> w.render('date', d)
1116+
u'<input type="text" name="date" value="2007-09-17" />'
1117+
>>> w.render('date', datetime.date(2007, 9, 17))
1118+
u'<input type="text" name="date" value="2007-09-17" />'
1119+
1120+
We should be able to initialize from a unicode value.
1121+
>>> w.render('date', u'2007-09-17')
1122+
u'<input type="text" name="date" value="2007-09-17" />'
1123+
1124+
Use 'format' to change the way a value is displayed.
1125+
>>> w = DateInput(format='%d/%m/%Y')
1126+
>>> w.render('date', d)
1127+
u'<input type="text" name="date" value="17/09/2007" />'
1128+
10961129
# TimeInput ###################################################################
10971130
10981131
>>> w = TimeInput()
@@ -1114,13 +1147,21 @@
11141147
>>> w.render('time', u'13:12:11')
11151148
u'<input type="text" name="time" value="13:12:11" />'
11161149
1150+
Use 'format' to change the way a value is displayed.
1151+
>>> w = TimeInput(format='%H:%M')
1152+
>>> w.render('time', t)
1153+
u'<input type="text" name="time" value="12:51" />'
1154+
11171155
# SplitHiddenDateTimeWidget ###################################################
11181156
11191157
>>> from django.forms.widgets import SplitHiddenDateTimeWidget
11201158
11211159
>>> w = SplitHiddenDateTimeWidget()
11221160
>>> w.render('date', '')
11231161
u'<input type="hidden" name="date_0" /><input type="hidden" name="date_1" />'
1162+
>>> d = datetime.datetime(2007, 9, 17, 12, 51, 34, 482548)
1163+
>>> print d
1164+
2007-09-17 12:51:34.482548
11241165
>>> w.render('date', d)
11251166
u'<input type="hidden" name="date_0" value="2007-09-17" /><input type="hidden" name="date_1" value="12:51:34" />'
11261167
>>> w.render('date', datetime.datetime(2007, 9, 17, 12, 51, 34))

0 commit comments

Comments
 (0)