diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py index 0392b642bb..120df94cf3 100644 --- a/django/contrib/admin/widgets.py +++ b/django/contrib/admin/widgets.py @@ -41,21 +41,21 @@ class FilteredSelectMultiple(forms.SelectMultiple): (name, self.verbose_name.replace('"', '\\"'), int(self.is_stacked), settings.ADMIN_MEDIA_PREFIX)) return mark_safe(u''.join(output)) -class AdminDateWidget(forms.TextInput): +class AdminDateWidget(forms.DateTimeInput): class Media: js = (settings.ADMIN_MEDIA_PREFIX + "js/calendar.js", settings.ADMIN_MEDIA_PREFIX + "js/admin/DateTimeShortcuts.js") - def __init__(self, attrs={}): - super(AdminDateWidget, self).__init__(attrs={'class': 'vDateField', 'size': '10'}) + def __init__(self, attrs={}, format=None): + super(AdminDateWidget, self).__init__(attrs={'class': 'vDateField', 'size': '10'}, format=format) -class AdminTimeWidget(forms.TextInput): +class AdminTimeWidget(forms.TimeInput): class Media: js = (settings.ADMIN_MEDIA_PREFIX + "js/calendar.js", settings.ADMIN_MEDIA_PREFIX + "js/admin/DateTimeShortcuts.js") - def __init__(self, attrs={}): - super(AdminTimeWidget, self).__init__(attrs={'class': 'vTimeField', 'size': '8'}) + def __init__(self, attrs={}, format=None): + super(AdminTimeWidget, self).__init__(attrs={'class': 'vTimeField', 'size': '8'}, format=format) class AdminSplitDateTime(forms.SplitDateTimeWidget): """ diff --git a/django/forms/widgets.py b/django/forms/widgets.py index aabd1b017d..c16c239de1 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -10,8 +10,7 @@ from django.utils.html import escape, conditional_escape from django.utils.translation import ugettext from django.utils.encoding import StrAndUnicode, force_unicode from django.utils.safestring import mark_safe -from django.utils.formats import localize -from django.utils import datetime_safe +from django.utils import datetime_safe, formats from datetime import time from util import flatatt from urlparse import urljoin @@ -209,7 +208,7 @@ class Input(Widget): final_attrs = self.build_attrs(attrs, type=self.input_type, name=name) if value != '': # Only add the 'value' attribute if a value is non-empty. - final_attrs['value'] = force_unicode(localize(value, is_input=True)) + final_attrs['value'] = force_unicode(formats.localize_input(value)) return mark_safe(u'' % flatatt(final_attrs)) class TextInput(Input): @@ -284,7 +283,7 @@ class Textarea(Widget): class DateInput(Input): input_type = 'text' - format = '%Y-%m-%d' # '2006-10-25' + format = None def __init__(self, attrs=None, format=None): super(DateInput, self).__init__(attrs) @@ -295,8 +294,7 @@ class DateInput(Input): if value is None: return '' elif hasattr(value, 'strftime'): - value = datetime_safe.new_date(value) - return value.strftime(self.format) + return formats.localize_input(value, self.format) return value def render(self, name, value, attrs=None): @@ -308,7 +306,7 @@ class DateInput(Input): class DateTimeInput(Input): input_type = 'text' - format = '%Y-%m-%d %H:%M:%S' # '2006-10-25 14:30:59' + format = None def __init__(self, attrs=None, format=None): super(DateTimeInput, self).__init__(attrs) @@ -319,8 +317,7 @@ class DateTimeInput(Input): if value is None: return '' elif hasattr(value, 'strftime'): - value = datetime_safe.new_datetime(value) - return value.strftime(self.format) + return formats.localize_input(value, self.format) return value def render(self, name, value, attrs=None): @@ -332,7 +329,7 @@ class DateTimeInput(Input): class TimeInput(Input): input_type = 'text' - format = '%H:%M:%S' # '14:30:59' + format = None def __init__(self, attrs=None, format=None): super(TimeInput, self).__init__(attrs) @@ -343,7 +340,7 @@ class TimeInput(Input): if value is None: return '' elif hasattr(value, 'strftime'): - return value.strftime(self.format) + return formats.localize_input(value, self.format) return value def render(self, name, value, attrs=None): diff --git a/django/utils/formats.py b/django/utils/formats.py index e18e120b36..92e7e7f538 100644 --- a/django/utils/formats.py +++ b/django/utils/formats.py @@ -4,8 +4,8 @@ import datetime from django.conf import settings from django.utils.translation import get_language, to_locale, check_for_language from django.utils.importlib import import_module -from django.utils import dateformat -from django.utils import numberformat +from django.utils.encoding import smart_str +from django.utils import dateformat, numberformat, datetime_safe def get_format_modules(): """ @@ -65,11 +65,10 @@ def number_format(value, decimal_pos=None): get_format('THOUSAND_SEPARATOR'), ) -def localize(value, is_input=False): +def localize(value): """ - Checks value, and if it has a localizable type (date, - number...) it returns the value as a string using - current locale format + Checks if value is a localizable type (date, number...) and returns it + formatted as a string using current locale format """ if settings.USE_L10N: if isinstance(value, decimal.Decimal): @@ -79,19 +78,27 @@ def localize(value, is_input=False): elif isinstance(value, int): return number_format(value) elif isinstance(value, datetime.datetime): - if not is_input: - return date_format(value, 'DATETIME_FORMAT') - else: - return value.strftime(get_format('DATETIME_INPUT_FORMATS')[0]) + return date_format(value, 'DATETIME_FORMAT') elif isinstance(value, datetime.date): - if not is_input: - return date_format(value) - else: - return value.strftime(get_format('DATE_INPUT_FORMATS')[0]) + return date_format(value) elif isinstance(value, datetime.time): - if not is_input: - return date_format(value, 'TIME_FORMAT') - else: - return value.strftime(get_format('TIME_INPUT_FORMATS')[0]) + return date_format(value, 'TIME_FORMAT') return value +def localize_input(value, default=None): + """ + Checks if an input value is a localizable type and returns it + formatted with the appropriate formatting string of the current locale. + """ + if isinstance(value, datetime.datetime): + value = datetime_safe.new_datetime(value) + format = smart_str(default or get_format('DATETIME_INPUT_FORMATS')[0]) + return value.strftime(format) + elif isinstance(value, datetime.date): + value = datetime_safe.new_date(value) + format = smart_str(default or get_format('DATE_INPUT_FORMATS')[0]) + return value.strftime(format) + elif isinstance(value, datetime.time): + format = smart_str(default or get_format('TIME_INPUT_FORMATS')[0]) + return value.strftime(format) + return value diff --git a/tests/regressiontests/admin_widgets/models.py b/tests/regressiontests/admin_widgets/models.py index 0c81ed3119..b9fe67c182 100644 --- a/tests/regressiontests/admin_widgets/models.py +++ b/tests/regressiontests/admin_widgets/models.py @@ -77,6 +77,9 @@ __test__ = {'WIDGETS_TESTS': """ >>> from django.contrib.admin.widgets import FilteredSelectMultiple, AdminSplitDateTime >>> from django.contrib.admin.widgets import AdminFileWidget, ForeignKeyRawIdWidget, ManyToManyRawIdWidget >>> from django.contrib.admin.widgets import RelatedFieldWidgetWrapper +>>> from django.utils.translation import activate, deactivate +>>> from django.conf import settings + Calling conditional_escape on the output of widget.render will simulate what happens in the template. This is easier than setting up a template and context @@ -94,6 +97,12 @@ HTML escaped. >>> w = AdminSplitDateTime() >>> print conditional_escape(w.render('test', datetime(2007, 12, 1, 9, 30)))

Date:
Time:

+>>> activate('de-at') +>>> settings.USE_L10N = True +>>> print conditional_escape(w.render('test', datetime(2007, 12, 1, 9, 30))) +

Datum:
Zeit:

+>>> deactivate() +>>> settings.USE_L10N = False >>> band = Band.objects.create(pk=1, name='Linkin Park') >>> album = band.album_set.create(name='Hybrid Theory', cover_art=r'albums\hybrid_theory.jpg') diff --git a/tests/regressiontests/admin_widgets/tests.py b/tests/regressiontests/admin_widgets/tests.py index 85651542bb..5601b49193 100644 --- a/tests/regressiontests/admin_widgets/tests.py +++ b/tests/regressiontests/admin_widgets/tests.py @@ -3,6 +3,7 @@ from django.contrib import admin from django.contrib.admin import widgets from unittest import TestCase from django.test import TestCase as DjangoTestCase +from django.db.models import DateField import models class AdminFormfieldForDBFieldTests(TestCase): @@ -89,7 +90,7 @@ class AdminFormfieldForDBFieldTests(TestCase): def testFormfieldOverrides(self): self.assertFormfield(models.Event, 'start_date', forms.TextInput, - formfield_overrides={'widget': forms.TextInput}) + formfield_overrides={DateField: {'widget': forms.TextInput}}) def testFieldWithChoices(self): self.assertFormfield(models.Member, 'gender', forms.Select) diff --git a/tests/regressiontests/forms/widgets.py b/tests/regressiontests/forms/widgets.py index 84ae61f527..6ea9298d4c 100644 --- a/tests/regressiontests/forms/widgets.py +++ b/tests/regressiontests/forms/widgets.py @@ -10,6 +10,8 @@ tests = r""" ... from decimal import Decimal ... except ImportError: ... from django.utils._decimal import Decimal +>>> from django.utils.translation import activate, deactivate +>>> from django.conf import settings ########### # Widgets # @@ -1082,6 +1084,13 @@ True False >>> w._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), [u'06/05/2008', u'12:41']) True +>>> activate('de-at') +>>> settings.USE_L10N = True +>>> w._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), [u'06.05.2008', u'12:41']) +True +>>> deactivate() +>>> settings.USE_L10N = False + # DateTimeInput ############################################################### @@ -1099,6 +1108,12 @@ u'' u'' >>> w.render('date', datetime.datetime(2007, 9, 17, 12, 51)) u'' +>>> activate('de-at') +>>> settings.USE_L10N = True +>>> w.render('date', d) +u'' +>>> deactivate() +>>> settings.USE_L10N = False Use 'format' to change the way a value is displayed. >>> w = DateTimeInput(format='%d/%m/%Y %H:%M') @@ -1107,6 +1122,7 @@ u'' >>> w._has_changed(d, '17/09/2007 12:51') False + # DateInput ################################################################### >>> w = DateInput() @@ -1125,6 +1141,13 @@ We should be able to initialize from a unicode value. >>> w.render('date', u'2007-09-17') u'' +>>> activate('de-at') +>>> settings.USE_L10N = True +>>> w.render('date', d) +u'' +>>> deactivate() +>>> settings.USE_L10N = False + Use 'format' to change the way a value is displayed. >>> w = DateInput(format='%d/%m/%Y') >>> w.render('date', d) @@ -1153,6 +1176,13 @@ We should be able to initialize from a unicode value. >>> w.render('time', u'13:12:11') u'' +>>> activate('de-at') +>>> settings.USE_L10N = True +>>> w.render('date', d) +u'' +>>> deactivate() +>>> settings.USE_L10N = False + Use 'format' to change the way a value is displayed. >>> w = TimeInput(format='%H:%M') >>> w.render('time', t) @@ -1176,6 +1206,12 @@ u'' >>> w.render('date', datetime.datetime(2007, 9, 17, 12, 51)) u'' +>>> activate('de-at') +>>> settings.USE_L10N = True +>>> w.render('date', datetime.datetime(2007, 9, 17, 12, 51)) +u'' +>>> deactivate() +>>> settings.USE_L10N = False """ diff --git a/tests/regressiontests/i18n/forms.py b/tests/regressiontests/i18n/forms.py index b8244f6ca7..8df066c6fd 100644 --- a/tests/regressiontests/i18n/forms.py +++ b/tests/regressiontests/i18n/forms.py @@ -1,5 +1,6 @@ from django import template, forms from django.forms.extras import SelectDateWidget +from models import Company class I18nForm(forms.Form): decimal_field = forms.DecimalField() @@ -11,3 +12,6 @@ class I18nForm(forms.Form): class SelectDateForm(forms.Form): date_field = forms.DateField(widget=SelectDateWidget) +class CompanyForm(forms.ModelForm): + class Meta: + model = Company diff --git a/tests/regressiontests/i18n/models.py b/tests/regressiontests/i18n/models.py index e1eefb8477..56f158578b 100644 --- a/tests/regressiontests/i18n/models.py +++ b/tests/regressiontests/i18n/models.py @@ -1,12 +1,16 @@ +from datetime import datetime from django.db import models from django.utils.translation import ugettext_lazy as _ class TestModel(models.Model): text = models.CharField(max_length=10, default=_('Anything')) +class Company(models.Model): + name = models.CharField(max_length=50) + date_added = models.DateTimeField(default=datetime(1799,1,31,23,59,59,0)) + __test__ = {'API_TESTS': ''' >>> tm = TestModel() >>> tm.save() ''' } - diff --git a/tests/regressiontests/i18n/tests.py b/tests/regressiontests/i18n/tests.py index 42dfe91659..a8f3d92d87 100644 --- a/tests/regressiontests/i18n/tests.py +++ b/tests/regressiontests/i18n/tests.py @@ -4,12 +4,13 @@ import datetime from django.template import Template, Context from django.conf import settings -from django.utils.formats import get_format, date_format, number_format, localize +from django.utils.formats import get_format, date_format, number_format, localize, localize_input from django.utils.numberformat import format from django.test import TestCase, client from django.utils.translation import ugettext, ugettext_lazy, activate, deactivate, gettext_lazy -from forms import I18nForm, SelectDateForm, SelectDateWidget +from forms import I18nForm, SelectDateForm, SelectDateWidget, CompanyForm + class TranslationTests(TestCase): @@ -323,6 +324,28 @@ class FormattingTests(TestCase): finally: deactivate() + def test_localized_input(self): + """ + Tests if form input is correctly localized + """ + settings.USE_L10N = True + activate('de-at') + try: + form6 = CompanyForm({ + 'name': u'acme', + 'date_added': datetime.datetime(2009, 12, 31, 6, 0, 0), + }) + form6.save() + self.assertEqual(True, form6.is_valid()) + self.assertEqual( + form6.as_ul(), + u'
  • \n
  • ' + ) + self.assertEqual(localize_input(datetime.datetime(2009, 12, 31, 6, 0, 0)), '31.12.2009 06:00:00') + self.assertEqual(datetime.datetime(2009, 12, 31, 6, 0, 0), form6.cleaned_data['date_added']) + finally: + deactivate() + class MiscTests(TestCase): def test_parse_spec_http_header(self):