diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py
index 5a02ab01be..cd05957197 100644
--- a/django/contrib/admin/templatetags/admin_list.py
+++ b/django/contrib/admin/templatetags/admin_list.py
@@ -3,11 +3,11 @@ from django.contrib.admin.views.main import ALL_VAR, EMPTY_CHANGELIST_VALUE
from django.contrib.admin.views.main import ORDER_VAR, ORDER_TYPE_VAR, PAGE_VAR, SEARCH_VAR
from django.core.exceptions import ObjectDoesNotExist
from django.db import models
-from django.utils import dateformat
+from django.utils import formats
from django.utils.html import escape, conditional_escape
from django.utils.text import capfirst
from django.utils.safestring import mark_safe
-from django.utils.translation import get_date_formats, get_partial_date_formats, ugettext as _
+from django.utils.translation import ugettext as _
from django.utils.encoding import smart_unicode, smart_str, force_unicode
from django.template import Library
import datetime
@@ -189,25 +189,24 @@ def items_for_result(cl, result, form):
# Dates and times are special: They're formatted in a certain way.
elif isinstance(f, models.DateField) or isinstance(f, models.TimeField):
if field_val:
- (date_format, datetime_format, time_format) = get_date_formats()
- if isinstance(f, models.DateTimeField):
- result_repr = capfirst(dateformat.format(field_val, datetime_format))
- elif isinstance(f, models.TimeField):
- result_repr = capfirst(dateformat.time_format(field_val, time_format))
- else:
- result_repr = capfirst(dateformat.format(field_val, date_format))
+ result_repr = formats.localize(field_val)
+ else:
+ result_repr = EMPTY_CHANGELIST_VALUE
+ elif isinstance(f, models.DecimalField):
+ if field_val:
+ result_repr = formats.number_format(field_val, f.decimal_places)
+ else:
+ result_repr = EMPTY_CHANGELIST_VALUE
+ row_class = ' class="nowrap"'
+ elif isinstance(f, models.FloatField):
+ if field_val:
+ result_repr = formats.number_format(field_val)
else:
result_repr = EMPTY_CHANGELIST_VALUE
row_class = ' class="nowrap"'
# Booleans are special: We use images.
elif isinstance(f, models.BooleanField) or isinstance(f, models.NullBooleanField):
result_repr = _boolean_icon(field_val)
- # DecimalFields are special: Zero-pad the decimals.
- elif isinstance(f, models.DecimalField):
- if field_val is not None:
- result_repr = ('%%.%sf' % f.decimal_places) % field_val
- else:
- result_repr = EMPTY_CHANGELIST_VALUE
# Fields with choices are special: Use the representation
# of the choice.
elif f.flatchoices:
@@ -268,7 +267,6 @@ def date_hierarchy(cl):
year_lookup = cl.params.get(year_field)
month_lookup = cl.params.get(month_field)
day_lookup = cl.params.get(day_field)
- year_month_format, month_day_format = get_partial_date_formats()
link = lambda d: cl.get_query_string(d, [field_generic])
@@ -278,9 +276,9 @@ def date_hierarchy(cl):
'show': True,
'back': {
'link': link({year_field: year_lookup, month_field: month_lookup}),
- 'title': dateformat.format(day, year_month_format)
+ 'title': capfirst(formats.date_format(day, 'YEAR_MONTH_FORMAT'))
},
- 'choices': [{'title': dateformat.format(day, month_day_format)}]
+ 'choices': [{'title': capfirst(formats.date_format(day, 'MONTH_DAY_FORMAT'))}]
}
elif year_lookup and month_lookup:
days = cl.query_set.filter(**{year_field: year_lookup, month_field: month_lookup}).dates(field_name, 'day')
@@ -292,7 +290,7 @@ def date_hierarchy(cl):
},
'choices': [{
'link': link({year_field: year_lookup, month_field: month_lookup, day_field: day.day}),
- 'title': dateformat.format(day, month_day_format)
+ 'title': capfirst(formats.date_format(day, 'MONTH_DAY_FORMAT'))
} for day in days]
}
elif year_lookup:
@@ -305,7 +303,7 @@ def date_hierarchy(cl):
},
'choices': [{
'link': link({year_field: year_lookup, month_field: month.month}),
- 'title': dateformat.format(month, year_month_format)
+ 'title': capfirst(formats.date_format(month, 'YEAR_MONTH_FORMAT'))
} for month in months]
}
else:
diff --git a/django/contrib/databrowse/datastructures.py b/django/contrib/databrowse/datastructures.py
index 5fdbdbe134..369f825f0e 100644
--- a/django/contrib/databrowse/datastructures.py
+++ b/django/contrib/databrowse/datastructures.py
@@ -4,9 +4,8 @@ convenience functionality and permalink functions for the databrowse app.
"""
from django.db import models
-from django.utils import dateformat
+from django.utils import formats
from django.utils.text import capfirst
-from django.utils.translation import get_date_formats
from django.utils.encoding import smart_unicode, smart_str, iri_to_uri
from django.utils.safestring import mark_safe
from django.db.models.query import QuerySet
@@ -156,13 +155,12 @@ class EasyInstanceField(object):
objs = dict(self.field.choices).get(self.raw_value, EMPTY_VALUE)
elif isinstance(self.field, models.DateField) or isinstance(self.field, models.TimeField):
if self.raw_value:
- date_format, datetime_format, time_format = get_date_formats()
if isinstance(self.field, models.DateTimeField):
- objs = capfirst(dateformat.format(self.raw_value, datetime_format))
+ objs = capfirst(formats.date_format(self.raw_value, 'DATETIME_FORMAT'))
elif isinstance(self.field, models.TimeField):
- objs = capfirst(dateformat.time_format(self.raw_value, time_format))
+ objs = capfirst(formats.date_format(self.raw_value, 'TIME_FORMAT'))
else:
- objs = capfirst(dateformat.format(self.raw_value, date_format))
+ objs = capfirst(formats.date_format(self.raw_value, 'DATE_FORMAT'))
else:
objs = EMPTY_VALUE
elif isinstance(self.field, models.BooleanField) or isinstance(self.field, models.NullBooleanField):
diff --git a/django/forms/extras/widgets.py b/django/forms/extras/widgets.py
index e36b8a1d67..173ef2e1de 100644
--- a/django/forms/extras/widgets.py
+++ b/django/forms/extras/widgets.py
@@ -8,6 +8,8 @@ import re
from django.forms.widgets import Widget, Select
from django.utils.dates import MONTHS
from django.utils.safestring import mark_safe
+from django.utils.formats import get_format
+from django.conf import settings
__all__ = ('SelectDateWidget',)
@@ -45,38 +47,27 @@ class SelectDateWidget(Widget):
if match:
year_val, month_val, day_val = [int(v) for v in match.groups()]
+ choices = [(i, i) for i in self.years]
+ year_html = self.create_select(name, self.year_field, value, year_val, choices)
+ choices = MONTHS.items()
+ month_html = self.create_select(name, self.month_field, value, month_val, choices)
+ choices = [(i, i) for i in range(1, 32)]
+ day_html = self.create_select(name, self.day_field, value, day_val, choices)
+
+ format = get_format('DATE_FORMAT')
+ escaped = False
output = []
-
- if 'id' in self.attrs:
- id_ = self.attrs['id']
- else:
- id_ = 'id_%s' % name
-
- month_choices = MONTHS.items()
- if not (self.required and value):
- month_choices.append(self.none_value)
- month_choices.sort()
- local_attrs = self.build_attrs(id=self.month_field % id_)
- s = Select(choices=month_choices)
- select_html = s.render(self.month_field % name, month_val, local_attrs)
- output.append(select_html)
-
- day_choices = [(i, i) for i in range(1, 32)]
- if not (self.required and value):
- day_choices.insert(0, self.none_value)
- local_attrs['id'] = self.day_field % id_
- s = Select(choices=day_choices)
- select_html = s.render(self.day_field % name, day_val, local_attrs)
- output.append(select_html)
-
- year_choices = [(i, i) for i in self.years]
- if not (self.required and value):
- year_choices.insert(0, self.none_value)
- local_attrs['id'] = self.year_field % id_
- s = Select(choices=year_choices)
- select_html = s.render(self.year_field % name, year_val, local_attrs)
- output.append(select_html)
-
+ for char in format:
+ if escaped:
+ escaped = False
+ elif char == '\\':
+ escaped = True
+ elif char in 'Yy':
+ output.append(year_html)
+ elif char in 'bFMmNn':
+ output.append(month_html)
+ elif char in 'dj':
+ output.append(day_html)
return mark_safe(u'\n'.join(output))
def id_for_label(self, id_):
@@ -90,5 +81,27 @@ class SelectDateWidget(Widget):
if y == m == d == "0":
return None
if y and m and d:
- return '%s-%s-%s' % (y, m, d)
+ if settings.USE_L10N:
+ input_format = get_format('DATE_INPUT_FORMATS')[0]
+ try:
+ date_value = datetime.date(int(y), int(m), int(d))
+ except ValueError:
+ pass
+ else:
+ return date_value.strftime(input_format)
+ else:
+ return '%s-%s-%s' % (y, m, d)
return data.get(name, None)
+
+ def create_select(self, name, field, value, val, choices):
+ if 'id' in self.attrs:
+ id_ = self.attrs['id']
+ else:
+ id_ = 'id_%s' % name
+ if not (self.required and value):
+ choices.insert(0, self.none_value)
+ local_attrs = self.build_attrs(id=field % id_)
+ s = Select(choices=choices)
+ select_html = s.render(field % name, val, local_attrs)
+ return select_html
+
diff --git a/django/forms/fields.py b/django/forms/fields.py
index c0ee2f0955..a4904d452d 100644
--- a/django/forms/fields.py
+++ b/django/forms/fields.py
@@ -7,6 +7,7 @@ import os
import re
import time
import urlparse
+import warnings
from decimal import Decimal, DecimalException
try:
from cStringIO import StringIO
@@ -17,6 +18,7 @@ import django.core.exceptions
import django.utils.copycompat as copy
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import smart_unicode, smart_str
+from django.utils.formats import get_format
from util import ErrorList, ValidationError
from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateInput, DateTimeInput, TimeInput, SplitDateTimeWidget, SplitHiddenDateTimeWidget
@@ -36,6 +38,20 @@ __all__ = (
# These values, if given to to_python(), will trigger the self.required check.
EMPTY_VALUES = (None, '')
+def en_format(name):
+ """
+ Helper function to stay backward compatible.
+ """
+ from django.conf.locale.en import formats
+ warnings.warn(
+ "`django.forms.fields.DEFAULT_%s` is deprecated; use `django.utils.formats.get_format('%s')` instead." % (name, name),
+ PendingDeprecationWarning
+ )
+ return getattr(formats, name)
+
+DEFAULT_DATE_INPUT_FORMATS = en_format('DATE_INPUT_FORMATS')
+DEFAULT_TIME_INPUT_FORMATS = en_format('TIME_INPUT_FORMATS')
+DEFAULT_DATETIME_INPUT_FORMATS = en_format('DATETIME_INPUT_FORMATS')
class Field(object):
widget = TextInput # Default widget to use when rendering this type of Field.
@@ -200,7 +216,9 @@ class FloatField(Field):
if not self.required and value in EMPTY_VALUES:
return None
try:
- value = float(value)
+ # We always accept dot as decimal separator
+ if isinstance(value, str) or isinstance(value, unicode):
+ value = float(value.replace(get_format('DECIMAL_SEPARATOR'), '.'))
except (ValueError, TypeError):
raise ValidationError(self.error_messages['invalid'])
if self.max_value is not None and value > self.max_value:
@@ -236,7 +254,9 @@ class DecimalField(Field):
return None
value = smart_str(value).strip()
try:
- value = Decimal(value)
+ # We always accept dot as decimal separator
+ if isinstance(value, str) or isinstance(value, unicode):
+ value = Decimal(value.replace(get_format('DECIMAL_SEPARATOR'), '.'))
except DecimalException:
raise ValidationError(self.error_messages['invalid'])
@@ -264,14 +284,6 @@ class DecimalField(Field):
raise ValidationError(self.error_messages['max_whole_digits'] % (self.max_digits - self.decimal_places))
return value
-DEFAULT_DATE_INPUT_FORMATS = (
- '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06'
- '%b %d %Y', '%b %d, %Y', # 'Oct 25 2006', 'Oct 25, 2006'
- '%d %b %Y', '%d %b, %Y', # '25 Oct 2006', '25 Oct, 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'
-)
-
class DateField(Field):
widget = DateInput
default_error_messages = {
@@ -280,7 +292,7 @@ class DateField(Field):
def __init__(self, input_formats=None, *args, **kwargs):
super(DateField, self).__init__(*args, **kwargs)
- self.input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS
+ self.input_formats = input_formats
def clean(self, value):
"""
@@ -294,18 +306,13 @@ class DateField(Field):
return value.date()
if isinstance(value, datetime.date):
return value
- for format in self.input_formats:
+ for format in self.input_formats or get_format('DATE_INPUT_FORMATS'):
try:
return datetime.date(*time.strptime(value, format)[:3])
except ValueError:
continue
raise ValidationError(self.error_messages['invalid'])
-DEFAULT_TIME_INPUT_FORMATS = (
- '%H:%M:%S', # '14:30:59'
- '%H:%M', # '14:30'
-)
-
class TimeField(Field):
widget = TimeInput
default_error_messages = {
@@ -314,7 +321,7 @@ class TimeField(Field):
def __init__(self, input_formats=None, *args, **kwargs):
super(TimeField, self).__init__(*args, **kwargs)
- self.input_formats = input_formats or DEFAULT_TIME_INPUT_FORMATS
+ self.input_formats = input_formats
def clean(self, value):
"""
@@ -326,25 +333,13 @@ class TimeField(Field):
return None
if isinstance(value, datetime.time):
return value
- for format in self.input_formats:
+ for format in self.input_formats or get_format('TIME_INPUT_FORMATS'):
try:
return datetime.time(*time.strptime(value, format)[3:6])
except ValueError:
continue
raise ValidationError(self.error_messages['invalid'])
-DEFAULT_DATETIME_INPUT_FORMATS = (
- '%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
- '%Y-%m-%d %H:%M', # '2006-10-25 14:30'
- '%Y-%m-%d', # '2006-10-25'
- '%m/%d/%Y %H:%M:%S', # '10/25/2006 14:30:59'
- '%m/%d/%Y %H:%M', # '10/25/2006 14:30'
- '%m/%d/%Y', # '10/25/2006'
- '%m/%d/%y %H:%M:%S', # '10/25/06 14:30:59'
- '%m/%d/%y %H:%M', # '10/25/06 14:30'
- '%m/%d/%y', # '10/25/06'
-)
-
class DateTimeField(Field):
widget = DateTimeInput
default_error_messages = {
@@ -353,7 +348,7 @@ class DateTimeField(Field):
def __init__(self, input_formats=None, *args, **kwargs):
super(DateTimeField, self).__init__(*args, **kwargs)
- self.input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS
+ self.input_formats = input_formats
def clean(self, value):
"""
@@ -373,7 +368,7 @@ class DateTimeField(Field):
if len(value) != 2:
raise ValidationError(self.error_messages['invalid'])
value = '%s %s' % tuple(value)
- for format in self.input_formats:
+ for format in self.input_formats or get_format('DATETIME_INPUT_FORMATS'):
try:
return datetime.datetime(*time.strptime(value, format)[:6])
except ValueError:
diff --git a/django/forms/widgets.py b/django/forms/widgets.py
index d59e6343e5..aabd1b017d 100644
--- a/django/forms/widgets.py
+++ b/django/forms/widgets.py
@@ -10,6 +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 datetime import time
from util import flatatt
@@ -208,7 +209,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(value)
+ final_attrs['value'] = force_unicode(localize(value, is_input=True))
return mark_safe(u'' % flatatt(final_attrs))
class TextInput(Input):
diff --git a/django/template/__init__.py b/django/template/__init__.py
index 8764bfada4..4c386bea30 100644
--- a/django/template/__init__.py
+++ b/django/template/__init__.py
@@ -60,6 +60,7 @@ from django.utils.text import smart_split, unescape_string_literal
from django.utils.encoding import smart_unicode, force_unicode, smart_str
from django.utils.translation import ugettext as _
from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping
+from django.utils.formats import localize
from django.utils.html import escape
__all__ = ('Template', 'Context', 'RequestContext', 'compile_string')
@@ -815,6 +816,7 @@ def _render_value_in_context(value, context):
means escaping, if required, and conversion to a unicode object. If value
is a string, it is expected to have already been translated.
"""
+ value = localize(value)
value = force_unicode(value)
if (context.autoescape and not isinstance(value, SafeData)) or isinstance(value, EscapeData):
return escape(value)
diff --git a/django/template/debug.py b/django/template/debug.py
index c58c854858..382fb75ebd 100644
--- a/django/template/debug.py
+++ b/django/template/debug.py
@@ -2,6 +2,7 @@ from django.template import Lexer, Parser, tag_re, NodeList, VariableNode, Templ
from django.utils.encoding import force_unicode
from django.utils.html import escape
from django.utils.safestring import SafeData, EscapeData
+from django.utils.formats import localize
class DebugLexer(Lexer):
def __init__(self, template_string, origin):
@@ -84,7 +85,9 @@ class DebugNodeList(NodeList):
class DebugVariableNode(VariableNode):
def render(self, context):
try:
- output = force_unicode(self.filter_expression.resolve(context))
+ output = self.filter_expression.resolve(context)
+ output = localize(output)
+ output = force_unicode(output)
except TemplateSyntaxError, e:
if not hasattr(e, 'source'):
e.source = self.source
diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py
index a8c25670f6..26b6b5ec4e 100644
--- a/django/template/defaultfilters.py
+++ b/django/template/defaultfilters.py
@@ -18,6 +18,7 @@ from django.conf import settings
from django.utils.translation import ugettext, ungettext
from django.utils.encoding import force_unicode, iri_to_uri
from django.utils.safestring import mark_safe, SafeData
+from django.utils.formats import date_format, number_format
register = Library()
@@ -166,14 +167,14 @@ def floatformat(text, arg=-1):
return input_val
if not m and p < 0:
- return mark_safe(u'%d' % (int(d)))
+ return mark_safe(number_format(u'%d' % (int(d)), 0))
if p == 0:
exp = Decimal(1)
else:
exp = Decimal('1.0') / (Decimal(10) ** abs(p))
try:
- return mark_safe(u'%s' % str(d.quantize(exp, ROUND_HALF_UP)))
+ return mark_safe(number_format(u'%s' % str(d.quantize(exp, ROUND_HALF_UP)), abs(p)))
except InvalidOperation:
return input_val
floatformat.is_safe = True
@@ -685,9 +686,12 @@ def date(value, arg=None):
if arg is None:
arg = settings.DATE_FORMAT
try:
- return format(value, arg)
+ return date_format(value, arg)
except AttributeError:
- return ''
+ try:
+ return format(value, arg)
+ except AttributeError:
+ return ''
date.is_safe = False
def time(value, arg=None):
@@ -698,9 +702,12 @@ def time(value, arg=None):
if arg is None:
arg = settings.TIME_FORMAT
try:
- return time_format(value, arg)
+ return date_format(value, arg)
except AttributeError:
- return ''
+ try:
+ return time_format(value, arg)
+ except AttributeError:
+ return ''
time.is_safe = False
def timesince(value, arg=None):
diff --git a/django/utils/formats.py b/django/utils/formats.py
new file mode 100644
index 0000000000..e18e120b36
--- /dev/null
+++ b/django/utils/formats.py
@@ -0,0 +1,97 @@
+import decimal
+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
+
+def get_format_modules():
+ """
+ Returns an iterator over the format modules found in the project and Django
+ """
+ modules = []
+ if not check_for_language(get_language()):
+ return modules
+ locale = to_locale(get_language())
+ if settings.FORMAT_MODULE_PATH:
+ format_locations = [settings.FORMAT_MODULE_PATH + '.%s']
+ else:
+ format_locations = []
+ format_locations.append('django.conf.locale.%s')
+ for location in format_locations:
+ for l in (locale, locale.split('_')[0]):
+ try:
+ mod = import_module('.formats', location % l)
+ except ImportError:
+ pass
+ else:
+ # Don't return duplicates
+ if mod not in modules:
+ modules.append(mod)
+ return modules
+
+def get_format(format_type):
+ """
+ For a specific format type, returns the format for the current
+ language (locale), defaults to the format in the settings.
+ format_type is the name of the format, e.g. 'DATE_FORMAT'
+ """
+ if settings.USE_L10N:
+ for module in get_format_modules():
+ try:
+ return getattr(module, format_type)
+ except AttributeError:
+ pass
+ return getattr(settings, format_type)
+
+def date_format(value, format=None):
+ """
+ Formats a datetime.date or datetime.datetime object using a
+ localizable format
+ """
+ return dateformat.format(value, get_format(format or 'DATE_FORMAT'))
+
+def number_format(value, decimal_pos=None):
+ """
+ Formats a numeric value using localization settings
+ """
+ return numberformat.format(
+ value,
+ get_format('DECIMAL_SEPARATOR'),
+ decimal_pos,
+ get_format('NUMBER_GROUPING'),
+ get_format('THOUSAND_SEPARATOR'),
+ )
+
+def localize(value, is_input=False):
+ """
+ Checks value, and if it has a localizable type (date,
+ number...) it returns the value as a string using
+ current locale format
+ """
+ if settings.USE_L10N:
+ if isinstance(value, decimal.Decimal):
+ return number_format(value)
+ elif isinstance(value, float):
+ return number_format(value)
+ 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])
+ elif isinstance(value, datetime.date):
+ if not is_input:
+ return date_format(value)
+ else:
+ return value.strftime(get_format('DATE_INPUT_FORMATS')[0])
+ 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 value
+
diff --git a/django/utils/numberformat.py b/django/utils/numberformat.py
new file mode 100644
index 0000000000..78ecb2fbdb
--- /dev/null
+++ b/django/utils/numberformat.py
@@ -0,0 +1,42 @@
+from django.conf import settings
+
+def format(number, decimal_sep, decimal_pos, grouping=0, thousand_sep=''):
+ """
+ Gets a number (as a number or string), and returns it as a string,
+ using formats definied as arguments:
+
+ * decimal_sep: Decimal separator symbol (for example ".")
+ * decimal_pos: Number of decimal positions
+ * grouping: Number of digits in every group limited by thousand separator
+ * thousand_sep: Thousand separator symbol (for example ",")
+
+ """
+ # sign
+ if float(number) < 0:
+ sign = '-'
+ else:
+ sign = ''
+ # decimal part
+ str_number = unicode(number)
+ if str_number[0] == '-':
+ str_number = str_number[1:]
+ if '.' in str_number:
+ int_part, dec_part = str_number.split('.')
+ if decimal_pos:
+ dec_part = dec_part[:decimal_pos]
+ else:
+ int_part, dec_part = str_number, ''
+ if decimal_pos:
+ dec_part = dec_part + ('0' * (decimal_pos - len(dec_part)))
+ if dec_part: dec_part = decimal_sep + dec_part
+ # grouping
+ if settings.USE_THOUSAND_SEPARATOR and grouping:
+ int_part_gd = ''
+ for cnt, digit in enumerate(int_part[::-1]):
+ if cnt and not cnt % grouping:
+ int_part_gd += thousand_sep
+ int_part_gd += digit
+ int_part = int_part_gd[::-1]
+
+ return sign + int_part + dec_part
+
diff --git a/django/utils/translation/trans_null.py b/django/utils/translation/trans_null.py
index 98c6de6197..50f41a2c23 100644
--- a/django/utils/translation/trans_null.py
+++ b/django/utils/translation/trans_null.py
@@ -2,6 +2,7 @@
# that don't actually do anything. This is purely for performance, so that
# settings.USE_I18N = False can use this module rather than trans_real.py.
+import warnings
from django.conf import settings
from django.utils.encoding import force_unicode
from django.utils.safestring import mark_safe, SafeData
@@ -18,10 +19,10 @@ activate = lambda x: None
deactivate = deactivate_all = lambda: None
get_language = lambda: settings.LANGUAGE_CODE
get_language_bidi = lambda: settings.LANGUAGE_CODE in settings.LANGUAGES_BIDI
-get_date_formats = lambda: (settings.DATE_FORMAT, settings.DATETIME_FORMAT, settings.TIME_FORMAT)
-get_partial_date_formats = lambda: (settings.YEAR_MONTH_FORMAT, settings.MONTH_DAY_FORMAT)
check_for_language = lambda x: True
+# date formats shouldn't be used using gettext anymore. This
+# is kept for backward compatibility
TECHNICAL_ID_MAP = {
"DATE_WITH_TIME_FULL": settings.DATETIME_FORMAT,
"DATE_FORMAT": settings.DATE_FORMAT,
@@ -51,3 +52,21 @@ def to_locale(language):
def get_language_from_request(request):
return settings.LANGUAGE_CODE
+
+# get_date_formats and get_partial_date_formats aren't used anymore by Django
+# but are kept for backward compatibility.
+def get_date_formats():
+ warnings.warn(
+ '`django.utils.translation.get_date_formats` is deprecated. '
+ 'Please update your code to use the new i18n aware formatting.',
+ PendingDeprecationWarning
+ )
+ return settings.DATE_FORMAT, settings.DATETIME_FORMAT, settings.TIME_FORMAT
+
+def get_partial_date_formats():
+ warnings.warn(
+ '`django.utils.translation.get_partial_date_formats` is deprecated. '
+ 'Please update your code to use the new i18n aware formatting.',
+ PendingDeprecationWarning
+ )
+ return settings.YEAR_MONTH_FORMAT, settings.MONTH_DAY_FORMAT
diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py
index 48ed7cc885..8b7db0f123 100644
--- a/django/utils/translation/trans_real.py
+++ b/django/utils/translation/trans_real.py
@@ -4,6 +4,7 @@ import locale
import os
import re
import sys
+import warnings
import gettext as gettext_module
from cStringIO import StringIO
@@ -266,15 +267,16 @@ def do_translate(message, translation_function):
translation object to use. If no current translation is activated, the
message will be run through the default translation object.
"""
+ eol_message = message.replace('\r\n', '\n').replace('\r', '\n')
global _default, _active
t = _active.get(currentThread(), None)
if t is not None:
- result = getattr(t, translation_function)(message)
+ result = getattr(t, translation_function)(eol_message)
else:
if _default is None:
from django.conf import settings
_default = translation(settings.LANGUAGE_CODE)
- result = getattr(_default, translation_function)(message)
+ result = getattr(_default, translation_function)(eol_message)
if isinstance(message, SafeData):
return mark_safe(result)
return result
@@ -389,39 +391,6 @@ def get_language_from_request(request):
return settings.LANGUAGE_CODE
-def get_date_formats():
- """
- Checks whether translation files provide a translation for some technical
- message ID to store date and time formats. If it doesn't contain one, the
- formats provided in the settings will be used.
- """
- from django.conf import settings
- date_format = ugettext('DATE_FORMAT')
- datetime_format = ugettext('DATETIME_FORMAT')
- time_format = ugettext('TIME_FORMAT')
- if date_format == 'DATE_FORMAT':
- date_format = settings.DATE_FORMAT
- if datetime_format == 'DATETIME_FORMAT':
- datetime_format = settings.DATETIME_FORMAT
- if time_format == 'TIME_FORMAT':
- time_format = settings.TIME_FORMAT
- return date_format, datetime_format, time_format
-
-def get_partial_date_formats():
- """
- Checks whether translation files provide a translation for some technical
- message ID to store partial date formats. If it doesn't contain one, the
- formats provided in the settings will be used.
- """
- from django.conf import settings
- year_month_format = ugettext('YEAR_MONTH_FORMAT')
- month_day_format = ugettext('MONTH_DAY_FORMAT')
- if year_month_format == 'YEAR_MONTH_FORMAT':
- year_month_format = settings.YEAR_MONTH_FORMAT
- if month_day_format == 'MONTH_DAY_FORMAT':
- month_day_format = settings.MONTH_DAY_FORMAT
- return year_month_format, month_day_format
-
dot_re = re.compile(r'\S')
def blankout(src, char):
"""
@@ -537,3 +506,52 @@ def parse_accept_lang_header(lang_string):
result.append((lang, priority))
result.sort(lambda x, y: -cmp(x[1], y[1]))
return result
+
+# get_date_formats and get_partial_date_formats aren't used anymore by Django
+# and are kept for backward compatibility.
+# Note, it's also important to keep format names marked for translation.
+# For compatibility we still want to have formats on translation catalogs.
+# That makes template code like {{ my_date|date:_('DATE_FORMAT') }} still work
+def get_date_formats():
+ """
+ Checks whether translation files provide a translation for some technical
+ message ID to store date and time formats. If it doesn't contain one, the
+ formats provided in the settings will be used.
+ """
+ warnings.warn(
+ '`django.utils.translation.get_date_formats` is deprecated. '
+ 'Please update your code to use the new i18n aware formatting.',
+ PendingDeprecationWarning
+ )
+ from django.conf import settings
+ date_format = ugettext('DATE_FORMAT')
+ datetime_format = ugettext('DATETIME_FORMAT')
+ time_format = ugettext('TIME_FORMAT')
+ if date_format == 'DATE_FORMAT':
+ date_format = settings.DATE_FORMAT
+ if datetime_format == 'DATETIME_FORMAT':
+ datetime_format = settings.DATETIME_FORMAT
+ if time_format == 'TIME_FORMAT':
+ time_format = settings.TIME_FORMAT
+ return date_format, datetime_format, time_format
+
+def get_partial_date_formats():
+ """
+ Checks whether translation files provide a translation for some technical
+ message ID to store partial date formats. If it doesn't contain one, the
+ formats provided in the settings will be used.
+ """
+ warnings.warn(
+ '`django.utils.translation.get_partial_date_formats` is deprecated. '
+ 'Please update your code to use the new i18n aware formatting.',
+ PendingDeprecationWarning
+ )
+ from django.conf import settings
+ year_month_format = ugettext('YEAR_MONTH_FORMAT')
+ month_day_format = ugettext('MONTH_DAY_FORMAT')
+ if year_month_format == 'YEAR_MONTH_FORMAT':
+ year_month_format = settings.YEAR_MONTH_FORMAT
+ if month_day_format == 'MONTH_DAY_FORMAT':
+ month_day_format = settings.MONTH_DAY_FORMAT
+ return year_month_format, month_day_format
+
diff --git a/django/views/i18n.py b/django/views/i18n.py
index 0280698aae..ddd75203a7 100644
--- a/django/views/i18n.py
+++ b/django/views/i18n.py
@@ -1,10 +1,12 @@
+import os
+import gettext as gettext_module
+
from django import http
from django.conf import settings
from django.utils import importlib
from django.utils.translation import check_for_language, activate, to_locale, get_language
from django.utils.text import javascript_quote
-import os
-import gettext as gettext_module
+from django.utils.formats import get_format_modules
def set_language(request):
"""
@@ -32,6 +34,24 @@ def set_language(request):
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, lang_code)
return response
+def get_formats():
+ """
+ Returns an iterator over all formats in formats file
+ """
+ FORMAT_SETTINGS = ('DATE_FORMAT', 'DATETIME_FORMAT', 'TIME_FORMAT',
+ 'YEAR_MONTH_FORMAT', 'MONTH_DAY_FORMAT', 'SHORT_DATE_FORMAT',
+ 'SHORT_DATETIME_FORMAT', 'FIRST_DAY_OF_WEEK', 'DECIMAL_SEPARATOR',
+ 'THOUSAND_SEPARATOR', 'NUMBER_GROUPING')
+
+ result = {}
+ for module in [settings] + get_format_modules():
+ for attr in FORMAT_SETTINGS:
+ try:
+ result[attr] = getattr(module, attr)
+ except AttributeError:
+ pass
+ return result
+
NullSource = """
/* gettext identity library */
@@ -185,10 +205,13 @@ def javascript_catalog(request, domain='djangojs', packages=None):
else:
raise TypeError, k
csrc.sort()
- for k,v in pdict.items():
+ for k, v in pdict.items():
src.append("catalog['%s'] = [%s];\n" % (javascript_quote(k), ','.join(["''"]*(v+1))))
+ for k, v in get_formats().items():
+ src.append("catalog['%s'] = '%s';\n" % (javascript_quote(k), javascript_quote(unicode(v))))
src.extend(csrc)
src.append(LibFoot)
src.append(InterPolate)
src = ''.join(src)
return http.HttpResponse(src, 'text/javascript')
+
diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt
index 2e44e418bc..1bd58ec0b3 100644
--- a/docs/internals/deprecation.txt
+++ b/docs/internals/deprecation.txt
@@ -62,6 +62,18 @@ their deprecation, as per the :ref:`Django deprecation policy
backwards compatibility. These have been deprecated since the 1.2
release.
+ * ``django.utils.translation.get_date_formats()`` and
+ ``django.utils.translation.get_partial_date_formats()``. These
+ functions are replaced by the new locale aware formatting; use
+ ``django.utils.formats.get_format()`` to get the appropriate
+ formats.
+
+ * In ``django.forms.fields``: ``DEFAULT_DATE_INPUT_FORMATS``,
+ ``DEFAULT_TIME_INPUT_FORMATS`` and
+ ``DEFAULT_DATETIME_INPUT_FORMATS``. Use
+ ``django.utils.formats.get_format()`` to get the appropriate
+ formats.
+
* 2.0
* ``django.views.defaults.shortcut()``. This function has been moved
to ``django.contrib.contenttypes.views.shortcut()`` as part of the
diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
index 1feb34a90b..bd8be27e5f 100644
--- a/docs/ref/settings.txt
+++ b/docs/ref/settings.txt
@@ -372,12 +372,32 @@ DATE_FORMAT
Default: ``'N j, Y'`` (e.g. ``Feb. 4, 2003``)
-The default formatting to use for date fields on Django admin change-list
-pages -- and, possibly, by other parts of the system. See
-:ttag:`allowed date format strings `.
+The default formatting to use for date fields in any part of the system.
+Note that if ``USE_L10N`` is set to ``True``, then locale format will
+be applied. See :ttag:`allowed date format strings `.
-See also ``DATETIME_FORMAT``, ``TIME_FORMAT``, ``YEAR_MONTH_FORMAT``
-and ``MONTH_DAY_FORMAT``.
+See also ``DATETIME_FORMAT``, ``TIME_FORMAT`` and ``SHORT_DATE_FORMAT``.
+
+.. setting:: DATE_INPUT_FORMATS
+
+DATE_INPUT_FORMATS
+------------------
+
+Default::
+
+ ('%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', '%b %d %Y',
+ '%b %d, %Y', '%d %b %Y', '%d %b, %Y', '%B %d %Y',
+ '%B %d, %Y', '%d %B %Y', '%d %B, %Y')
+
+A tuple of formats that will be accepted when inputting data on a date
+field. Formats will be tried in order, using the first valid.
+Note that these format strings are specified in Python's datetime_ module
+syntax, that is different from the one used by Django for formatting dates
+to be displayed.
+
+See also ``DATETIME_INPUT_FORMATS`` and ``TIME_INPUT_FORMATS``.
+
+.. _datetime: http://docs.python.org/library/datetime.html#strftime-behavior
.. setting:: DATETIME_FORMAT
@@ -386,12 +406,32 @@ DATETIME_FORMAT
Default: ``'N j, Y, P'`` (e.g. ``Feb. 4, 2003, 4 p.m.``)
-The default formatting to use for datetime fields on Django admin change-list
-pages -- and, possibly, by other parts of the system. See
-:ttag:`allowed date format strings `.
+The default formatting to use for datetime fields in any part of the system.
+Note that if ``USE_L10N`` is set to ``True``, then locale format will
+be applied. See :ttag:`allowed date format strings `.
-See also ``DATE_FORMAT``, ``DATETIME_FORMAT``, ``TIME_FORMAT``,
-``YEAR_MONTH_FORMAT`` and ``MONTH_DAY_FORMAT``.
+See also ``DATE_FORMAT``, ``TIME_FORMAT`` and ``SHORT_DATETIME_FORMAT``.
+
+.. setting:: DATETIME_INPUT_FORMATS
+
+DATETIME_INPUT_FORMATS
+----------------------
+
+Default::
+
+ ('%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M', '%Y-%m-%d',
+ '%m/%d/%Y %H:%M:%S', '%m/%d/%Y %H:%M', '%m/%d/%Y',
+ '%m/%d/%y %H:%M:%S', '%m/%d/%y %H:%M', '%m/%d/%y')
+
+A tuple of formats that will be accepted when inputting data on a datetime
+field. Formats will be tried in order, using the first valid.
+Note that these format strings are specified in Python's datetime_ module
+syntax, that is different from the one used by Django for formatting dates
+to be displayed.
+
+See also ``DATE_INPUT_FORMATS`` and ``TIME_INPUT_FORMATS``.
+
+.. _datetime: http://docs.python.org/library/datetime.html#strftime-behavior
.. setting:: DEBUG
@@ -431,6 +471,14 @@ will be suppressed, and exceptions will propagate upwards. This can
be useful for some test setups, and should never be used on a live
site.
+.. setting:: DECIMAL_SEPARATOR
+
+DECIMAL_SEPARATOR
+-----------------
+
+Default: ``'.'`` (Dot)
+
+Default decimal separator used when formatting decimal numbers.
.. setting:: DEFAULT_CHARSET
@@ -680,6 +728,21 @@ system's standard umask.
.. _documentation for os.chmod: http://docs.python.org/lib/os-file-dir.html
+.. setting:: FIRST_DAY_OF_WEEK
+
+FIRST_DAY_OF_WEEK
+-----------------
+
+Default: ``0`` (Sunday)
+
+Number representing the first day of the week. This is especially useful
+when displaying a calendar. This value is only used when not using
+format internationalization, or when a format cannot be found for the
+current locale.
+
+The value must be an integer from 0 to 6, where 0 means Sunday, 1 means
+Monday and so on.
+
.. setting:: FIXTURE_DIRS
FIXTURE_DIRS
@@ -701,6 +764,34 @@ environment variable in any HTTP request. This setting can be used to override
the server-provided value of ``SCRIPT_NAME``, which may be a rewritten version
of the preferred value or not supplied at all.
+.. setting:: FORMAT_MODULE_PATH
+
+FORMAT_MODULE_PATH
+------------------
+
+Default: ``None``
+
+A full Python path to a Python package that contains format definitions for
+project locales. If not ``None``, Django will check for a ``formats.py``
+file, under the directory named as the current locale, and will use the
+formats defined on this file.
+
+For example, if ``FORMAT_MODULE_PATH`` is set to ``mysite.formats``, and
+current language is ``en`` (English), Django will expect a directory tree
+like::
+
+ mysite/
+ formats/
+ __init__.py
+ en/
+ __init__.py
+ formats.py
+
+Available formats are ``DATE_FORMAT``, ``TIME_FORMAT``, ``DATETIME_FORMAT``,
+``YEAR_MONTH_FORMAT``, ``MONTH_DAY_FORMAT``, ``SHORT_DATE_FORMAT``,
+``SHORT_DATETIME_FORMAT``, ``FIRST_DAY_OF_WEEK``, ``DECIMAL_SEPARATOR``,
+``THOUSAND_SEPARATOR`` and ``NUMBER_GROUPING``.
+
.. setting:: IGNORABLE_404_ENDS
IGNORABLE_404_ENDS
@@ -845,7 +936,7 @@ LOGIN_URL
Default: ``'/accounts/login/'``
-The URL where requests are redirected for login, specially when using the
+The URL where requests are redirected for login, especially when using the
:func:`~django.contrib.auth.decorators.login_required` decorator.
.. setting:: LOGOUT_URL
@@ -970,6 +1061,21 @@ locales have different formats. For example, U.S. English would say
See :ttag:`allowed date format strings `. See also ``DATE_FORMAT``,
``DATETIME_FORMAT``, ``TIME_FORMAT`` and ``YEAR_MONTH_FORMAT``.
+.. setting:: NUMBER_GROUPING
+
+NUMBER_GROUPING
+----------------
+
+Default: ``0``
+
+Number of digits grouped together on the integer part of a number. Common use
+is to display a thousand separator. If this setting is ``0``, then, no grouping
+will be applied to the number. If this setting is greater than ``0`` then the
+setting ``THOUSAND_SEPARATOR`` will be used as the separator between those
+groups.
+
+See also ``THOUSAND_SEPARATOR``
+
.. setting:: PREPEND_WWW
PREPEND_WWW
@@ -1173,6 +1279,32 @@ Default: ``False``
Whether to save the session data on every request. See
:ref:`topics-http-sessions`.
+.. setting:: SHORT_DATE_FORMAT
+
+SHORT_DATE_FORMAT
+-----------------
+
+Default: ``m/d/Y`` (e.g. ``12/31/2003``)
+
+An available formatting that can be used for date fields on templates.
+Note that if ``USE_L10N`` is set to ``True``, then locale format will
+be applied. See :ttag:`allowed date format strings `.
+
+See also ``DATE_FORMAT`` and ``SHORT_DATETIME_FORMAT``.
+
+.. setting:: SHORT_DATETIME_FORMAT
+
+SHORT_DATETIME_FORMAT
+---------------------
+
+Default: ``m/d/Y P`` (e.g. ``12/31/2003 4 p.m.``)
+
+An available formatting that can be used for datetime fields on templates.
+Note that if ``USE_L10N`` is set to ``True``, then locale format will
+be applied. See :ttag:`allowed date format strings `.
+
+See also ``DATE_FORMAT`` and ``SHORT_DATETIME_FORMAT``.
+
.. setting:: SITE_ID
SITE_ID
@@ -1277,6 +1409,18 @@ The name of the method to use for starting the test suite. See
.. _Testing Django Applications: ../testing/
+.. setting:: THOUSAND_SEPARATOR
+
+THOUSAND_SEPARATOR
+------------------
+
+Default ``,`` (Comma)
+
+Default thousand separator used when formatting numbers. This setting is
+used only when ``NUMBER_GROUPPING`` is set.
+
+See also ``NUMBER_GROUPPING``, ``DECIMAL_SEPARATOR``
+
.. setting:: TIME_FORMAT
TIME_FORMAT
@@ -1284,12 +1428,28 @@ TIME_FORMAT
Default: ``'P'`` (e.g. ``4 p.m.``)
-The default formatting to use for time fields on Django admin change-list
-pages -- and, possibly, by other parts of the system. See
-:ttag:`allowed date format strings `.
+The default formatting to use for time fields in any part of the system.
+Note that if ``USE_L10N`` is set to ``True``, then locale format will
+be applied. See :ttag:`allowed date format strings `.
-See also ``DATE_FORMAT``, ``DATETIME_FORMAT``, ``TIME_FORMAT``,
-``YEAR_MONTH_FORMAT`` and ``MONTH_DAY_FORMAT``.
+See also ``DATE_FORMAT`` and ``DATETIME_FORMAT``.
+
+.. setting:: TIME_INPUT_FORMATS
+
+TIME_INPUT_FORMATS
+------------------
+
+Default: ``('%H:%M:%S', '%H:%M')``
+
+A tuple of formats that will be accepted when inputting data on a time
+field. Formats will be tried in order, using the first valid.
+Note that these format strings are specified in Python's datetime_ module
+syntax, that is different from the one used by Django for formatting dates
+to be displayed.
+
+See also ``DATE_INPUT_FORMATS`` and ``DATETIME_INPUT_FORMATS``.
+
+.. _datetime: http://docs.python.org/library/datetime.html#strftime-behavior
.. setting:: TIME_ZONE
@@ -1344,6 +1504,19 @@ A boolean that specifies whether to output the "Etag" header. This saves
bandwidth but slows down performance. This is only used if ``CommonMiddleware``
is installed (see :ref:`topics-http-middleware`).
+.. setting:: USE_L10N
+
+USE_L10N
+--------
+
+Default ``False``
+
+A boolean that specifies if data will be localized by default or not. If this
+is set to ``True``, e.g. Django will display numbers and dates using the
+format of the current locale.
+
+See also ``USE_I18N`` and ``LANGUAGE_CODE``
+
.. setting:: USE_I18N
USE_I18N
@@ -1356,6 +1529,22 @@ enabled. This provides an easy way to turn it off, for performance. If this is
set to ``False``, Django will make some optimizations so as not to load the
internationalization machinery.
+See also ``USE_L10N``
+
+.. setting:: USE_THOUSAND_SEPARATOR
+
+USE_THOUSAND_SEPARATOR
+----------------------
+
+Default ``False``
+
+A boolean that specifies wheter to display numbers using a thousand separator.
+If this is set to ``True``, Django will use values from ``THOUSAND_SEPARATOR``
+and ``NUMBER_GROUPING`` from current locale, to format the number.
+``USE_L10N`` must be set to ``True``, in order to format numbers.
+
+See also ``THOUSAND_SEPARATOR`` and ``NUMBER_GROUPING``.
+
.. setting:: YEAR_MONTH_FORMAT
YEAR_MONTH_FORMAT
diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt
index bf2d9e899c..b7afa15f7f 100644
--- a/docs/ref/templates/builtins.txt
+++ b/docs/ref/templates/builtins.txt
@@ -1047,7 +1047,12 @@ If ``value`` is ``"String with spaces"``, the output will be ``"Stringwithspaces
date
~~~~
-Formats a date according to the given format (same as the `now`_ tag).
+Formats a date according to the given format.
+
+Given format can be one of the predefined ones ``DATE_FORMAT``,
+``DATETIME_FORMAT``, ``SHORT_DATE_FORMAT`` or ``SHORT_DATETIME_FORMAT``,
+or a custom format, same as the `now`_ tag. Note that predefined formats may
+vary depending on the current locale.
For example::
@@ -1062,7 +1067,7 @@ When used without a format string::
{{ value|date }}
...the formatting string defined in the :setting:`DATE_FORMAT` setting will be
-used.
+used, without applying any localization.
.. templatefilter:: default
@@ -1610,7 +1615,11 @@ output will be ``"Joel is a slug"``.
time
~~~~
-Formats a time according to the given format (same as the `now`_ tag).
+Formats a time according to the given format.
+
+Given format can be the predefined one ``TIME_FORMAT``, or a custom format,
+same as the `now`_ tag. Note that the predefined format is locale depending.
+
The time filter will only accept parameters in the format string that relate
to the time of day, not the date (for obvious reasons). If you need to
format a date, use the `date`_ filter.
@@ -1627,7 +1636,7 @@ When used without a format string::
{{ value|time }}
...the formatting string defined in the :setting:`TIME_FORMAT` setting will be
-used.
+used, without aplying any localization.
.. templatefilter:: timesince
diff --git a/docs/releases/1.2.txt b/docs/releases/1.2.txt
index 2111212054..200fb4a5b6 100644
--- a/docs/releases/1.2.txt
+++ b/docs/releases/1.2.txt
@@ -318,6 +318,41 @@ For more information, see the full
:ref:`messages documentation `. You should begin to
update your code to use the new API immediately.
+Date format helper functions
+----------------------------
+
+``django.utils.translation.get_date_formats()`` and
+``django.utils.translation.get_partial_date_formats()`` have been deprecated
+in favor of the appropriate calls to ``django.utils.formats.get_format()``
+which is locale aware when :setting:`USE_L10N` is set to ``True``, and falls
+back to default settings if set to ``False``.
+
+To get the different date formats, instead of writing::
+
+ from django.utils.translation import get_date_formats
+ date_format, datetime_format, time_format = get_date_formats()
+
+use::
+
+ from django.utils import formats
+
+ date_format = formats.get_format('DATE_FORMAT')
+ datetime_format = formats.get_format('DATETIME_FORMAT')
+ time_format = formats.get_format('TIME_FORMAT')
+
+or, when directly formatting a date value::
+
+ from django.utils import formats
+ value_formatted = formats.date_format(value, 'DATETIME_FORMAT')
+
+The same applies to the globals found in ``django.forms.fields``:
+
+ * ``DEFAULT_DATE_INPUT_FORMATS``
+ * ``DEFAULT_TIME_INPUT_FORMATS``
+ * ``DEFAULT_DATETIME_INPUT_FORMATS``
+
+Use ``django.utils.formats.get_format()`` to get the appropriate formats.
+
What's new in Django 1.2
========================
@@ -440,3 +475,13 @@ The ``test`` subcommand of ``django-admin.py``, and the ``runtests.py`` script
used to run Django's own test suite, support a new ``--failfast`` option.
When specified, this option causes the test runner to exit after
encountering a failure instead of continuing with the test run.
+
+Improved localization
+---------------------
+
+Django's :ref:`internationalization framework ` has been
+expanded by locale aware formatting and form processing. That means, if
+enabled, dates and numbers on templates will be displayed using the format
+specified for the current locale. Django will also use localized formats
+when parsing data in forms.
+See :ref:`Format localization ` for more details.
diff --git a/docs/topics/i18n.txt b/docs/topics/i18n.txt
index c5f4ab6481..993c7b5285 100644
--- a/docs/topics/i18n.txt
+++ b/docs/topics/i18n.txt
@@ -4,20 +4,20 @@
Internationalization
====================
-Django has full support for internationalization of text in code and templates.
-Here's how it works.
+Django has full support for internationalization of text in code and
+templates, and format localization of dates and numbers. Here's how it works.
Overview
========
The goal of internationalization is to allow a single Web application to offer
-its content and functionality in multiple languages.
+its content and functionality in multiple languages and locales.
-You, the Django developer, can accomplish this goal by adding a minimal amount
-of hooks to your Python code and templates. These hooks are called
-**translation strings**. They tell Django: "This text should be translated into
-the end user's language, if a translation for this text is available in that
-language."
+For text translation, you, the Django developer, can accomplish this goal by
+adding a minimal amount of hooks to your Python code and templates. These hooks
+are called **translation strings**. They tell Django: "This text should be
+translated into the end user's language, if a translation for this text is
+available in that language."
Django takes care of using these hooks to translate Web apps, on the fly,
according to users' language preferences.
@@ -29,6 +29,12 @@ Essentially, Django does two things:
* It uses these hooks to translate Web apps for particular users according
to their language preferences.
+For format localization, it's just necessary to set
+:setting:`USE_L10N = True ` in your settings file. If
+:setting:`USE_L10N` is set to ``True``, Django will display
+numbers and dates in the format of the current locale. That includes field
+representation on templates, and allowed input formats on the admin.
+
If you don't need internationalization in your app
==================================================
@@ -1074,3 +1080,53 @@ have been found to not support this command. Do not attempt to use Django
translation utilities with a ``gettext`` package if the command ``xgettext
--version`` entered at a Windows command prompt causes a popup window saying
"xgettext.exe has generated errors and will be closed by Windows".
+
+.. _format-localization:
+
+Format localization
+===================
+
+Django's formatting system is disabled by default. To enable it, it's necessay
+to set :setting:`USE_L10N = True ` in your settings file.
+
+When using Django's formatting system, dates and numbers on templates will be
+displayed using the format specified for the current locale. Two users
+accessing the same content, but in different language, will see date and
+number fields formatted in different ways, depending on the format for their
+current locale.
+
+Django will also use localized formats when parsing data in forms. That means
+Django uses different formats for different locales when guessing the format
+used by the user when inputting data on forms. Note that Django uses different
+formats for displaying data, and for parsing it.
+
+Creating custom format files
+----------------------------
+
+Django provides format definitions for many locales, but sometimes you might
+want to create your own, because a format files doesn't exist for your locale,
+or because you want to overwrite some of the values.
+
+To use custom formats, first thing to do, is to specify the path where you'll
+place format files. To do that, just set your :setting:`FORMAT_MODULE_PATH`
+setting to the the path (in the format ``'foo.bar.baz``) where format files
+will exists.
+
+Files are not placed directly in this directory, but in a directory named as
+the locale, and must be named ``formats.py``.
+
+To customize the English formats, a structure like this would be needed::
+
+ mysite/
+ formats/
+ __init__.py
+ en/
+ __init__.py
+ formats.py
+
+where :file:`formats.py` contains custom format definitions. For example::
+
+ THOUSAND_SEPARATOR = ' '
+
+to use a space as a thousand separator, instead of the default for English,
+a comma.
diff --git a/tests/regressiontests/i18n/misc.py b/tests/regressiontests/i18n/misc.py
deleted file mode 100644
index f8f35ad4e4..0000000000
--- a/tests/regressiontests/i18n/misc.py
+++ /dev/null
@@ -1,114 +0,0 @@
-import sys
-
-tests = """
->>> from django.utils.translation.trans_real import parse_accept_lang_header
->>> p = parse_accept_lang_header
-
-#
-# Testing HTTP header parsing. First, we test that we can parse the values
-# according to the spec (and that we extract all the pieces in the right order).
-#
-
-Good headers.
->>> p('de')
-[('de', 1.0)]
->>> p('en-AU')
-[('en-AU', 1.0)]
->>> p('*;q=1.00')
-[('*', 1.0)]
->>> p('en-AU;q=0.123')
-[('en-AU', 0.123)]
->>> p('en-au;q=0.1')
-[('en-au', 0.10000000000000001)]
->>> p('en-au;q=1.0')
-[('en-au', 1.0)]
->>> p('da, en-gb;q=0.25, en;q=0.5')
-[('da', 1.0), ('en', 0.5), ('en-gb', 0.25)]
->>> p('en-au-xx')
-[('en-au-xx', 1.0)]
->>> p('de,en-au;q=0.75,en-us;q=0.5,en;q=0.25,es;q=0.125,fa;q=0.125')
-[('de', 1.0), ('en-au', 0.75), ('en-us', 0.5), ('en', 0.25), ('es', 0.125), ('fa', 0.125)]
->>> p('*')
-[('*', 1.0)]
->>> p('de;q=0.')
-[('de', 1.0)]
->>> p('')
-[]
-
-Bad headers; should always return [].
->>> p('en-gb;q=1.0000')
-[]
->>> p('en;q=0.1234')
-[]
->>> p('en;q=.2')
-[]
->>> p('abcdefghi-au')
-[]
->>> p('**')
-[]
->>> p('en,,gb')
-[]
->>> p('en-au;q=0.1.0')
-[]
->>> p('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXZ,en')
-[]
->>> p('da, en-gb;q=0.8, en;q=0.7,#')
-[]
->>> p('de;q=2.0')
-[]
->>> p('de;q=0.a')
-[]
->>> p('')
-[]
-
-#
-# Now test that we parse a literal HTTP header correctly.
-#
-
->>> from django.utils.translation.trans_real import get_language_from_request
->>> g = get_language_from_request
->>> from django.http import HttpRequest
->>> r = HttpRequest
->>> r.COOKIES = {}
-
-These tests assumes the es, es_AR, pt and pt_BR translations exit in the Django
-source tree.
->>> r.META = {'HTTP_ACCEPT_LANGUAGE': 'pt-br'}
->>> g(r)
-'pt-br'
->>> r.META = {'HTTP_ACCEPT_LANGUAGE': 'pt'}
->>> g(r)
-'pt'
->>> r.META = {'HTTP_ACCEPT_LANGUAGE': 'es,de'}
->>> g(r)
-'es'
->>> r.META = {'HTTP_ACCEPT_LANGUAGE': 'es-ar,de'}
->>> g(r)
-'es-ar'
-"""
-
-# Python 2.3 and 2.4 return slightly different results for completely bogus
-# locales, so we omit this test for that anything below 2.4. It's relatively
-# harmless in any cases (GIGO). This also means this won't be executed on
-# Jython currently, but life's like that sometimes. (On those platforms,
-# passing in a truly bogus locale will get you the default locale back.)
-if sys.version_info >= (2, 5):
- tests += """
-This test assumes there won't be a Django translation to a US variation
-of the Spanish language, a safe assumption. When the user sets it
-as the preferred language, the main 'es' translation should be selected
-instead.
->>> r.META = {'HTTP_ACCEPT_LANGUAGE': 'es-us'}
->>> g(r)
-'es'
-"""
-
-tests += """
-This tests the following scenario: there isn't a main language (zh)
-translation of Django but there is a translation to variation (zh_CN)
-the user sets zh-cn as the preferred language, it should be selected by
-Django without falling back nor ignoring it.
->>> r.META = {'HTTP_ACCEPT_LANGUAGE': 'zh-cn,de'}
->>> g(r)
-'zh-cn'
-"""
diff --git a/tests/regressiontests/i18n/tests.py b/tests/regressiontests/i18n/tests.py
index 94e792cf54..c356970166 100644
--- a/tests/regressiontests/i18n/tests.py
+++ b/tests/regressiontests/i18n/tests.py
@@ -1,72 +1,375 @@
-# coding: utf-8
-import misc
+import sys
+from django.test import TestCase, client
+from django.utils.translation import ugettext, ugettext_lazy, activate, deactivate, gettext_lazy
-regressions = ur"""
-Format string interpolation should work with *_lazy objects.
+class TranslationTests(TestCase):
->>> from django.utils.translation import ugettext, ugettext_lazy, activate, deactivate, gettext_lazy
->>> s = ugettext_lazy('Add %(name)s')
->>> d = {'name': 'Ringo'}
->>> s % d
-u'Add Ringo'
->>> activate('de')
->>> s % d
-u'Ringo hinzuf\xfcgen'
->>> activate('pl')
->>> s % d
-u'Dodaj Ringo'
->>> deactivate()
+ def test_lazy_objects(self):
+ """
+ Format string interpolation should work with *_lazy objects.
+ """
+ s = ugettext_lazy('Add %(name)s')
+ d = {'name': 'Ringo'}
+ self.assertEqual(u'Add Ringo', s % d)
+ activate('de')
+ self.assertEqual(u'Ringo hinzuf\xfcgen', s % d)
+ activate('pl')
+ self.assertEqual(u'Dodaj Ringo', s % d)
+ deactivate()
-It should be possible to compare *_lazy objects.
+ # It should be possible to compare *_lazy objects.
+ s1 = ugettext_lazy('Add %(name)s')
+ self.assertEqual(True, s == s1)
+ s2 = gettext_lazy('Add %(name)s')
+ s3 = gettext_lazy('Add %(name)s')
+ self.assertEqual(True, s2 == s3)
+ self.assertEqual(True, s == s2)
+ s4 = ugettext_lazy('Some other string')
+ self.assertEqual(False, s == s4)
->>> s1 = ugettext_lazy('Add %(name)s')
->>> s == s1
-True
->>> s2 = gettext_lazy('Add %(name)s')
->>> s3 = gettext_lazy('Add %(name)s')
->>> s2 == s3
-True
->>> s == s2
-True
->>> s4 = ugettext_lazy('Some other string')
->>> s == s4
-False
+ def test_string_concat(self):
+ """
+ unicode(string_concat(...)) should not raise a TypeError - #4796
+ """
+ import django.utils.translation
+ self.assertEqual(django.utils.translation, reload(django.utils.translation))
+ self.assertEqual(u'django', unicode(django.utils.translation.string_concat("dja", "ngo")))
-unicode(string_concat(...)) should not raise a TypeError - #4796
+ def test_safe_status(self):
+ """
+ Translating a string requiring no auto-escaping shouldn't change the "safe" status.
+ """
+ from django.utils.safestring import mark_safe, SafeString, SafeUnicode
+ s = mark_safe('Password')
+ self.assertEqual(SafeString, type(s))
+ activate('de')
+ self.assertEqual(SafeUnicode, type(ugettext(s)))
+ deactivate()
+ self.assertEqual('aPassword', SafeString('a') + s)
+ self.assertEqual('Passworda', s + SafeString('a'))
+ self.assertEqual('Passworda', s + mark_safe('a'))
+ self.assertEqual('aPassword', mark_safe('a') + s)
+ self.assertEqual('as', mark_safe('a') + mark_safe('s'))
+ #self.assertEqual(Password, print s)
->>> import django.utils.translation
->>> reload(django.utils.translation)
-
->>> unicode(django.utils.translation.string_concat("dja", "ngo"))
-u'django'
+ def test_maclines(self):
+ """
+ Translations on files with mac or dos end of lines will be converted
+ to unix eof in .po catalogs, and they have to match when retrieved
+ """
+ from django.utils.translation.trans_real import translation
+ ca_translation = translation('ca')
+ ca_translation._catalog[u'Mac\nEOF\n'] = u'Catalan Mac\nEOF\n'
+ ca_translation._catalog[u'Win\nEOF\n'] = u'Catalan Win\nEOF\n'
+ activate('ca')
+ self.assertEqual(u'Catalan Mac\nEOF\n', ugettext(u'Mac\rEOF\r'))
+ self.assertEqual(u'Catalan Win\nEOF\n', ugettext(u'Win\r\nEOF\r\n'))
+ deactivate()
-Translating a string requiring no auto-escaping shouldn't change the "safe"
-status.
+ def test_dates_and_numbers(self):
+ """
+ Localization of dates and numbers
+ """
+ import datetime
+ import decimal
+ from django.conf import settings
+ from django.utils.formats import get_format, date_format, number_format, localize
+ from django.utils.numberformat import format
+ from django import template, forms
+ from django.forms.extras import SelectDateWidget
->>> from django.utils.safestring import mark_safe, SafeString
->>> s = mark_safe('Password')
->>> type(s)
-
->>> activate('de')
->>> type(ugettext(s))
-
->>> deactivate()
+ old_use_i18n = settings.USE_I18N
+ old_use_l10n = settings.USE_L10N
+ old_use_thousand_separator = settings.USE_THOUSAND_SEPARATOR
->>> SafeString('a') + s
-'aPassword'
->>> s + SafeString('a')
-'Passworda'
->>> s + mark_safe('a')
-'Passworda'
->>> mark_safe('a') + s
-'aPassword'
->>> mark_safe('a') + mark_safe('s')
-'as'
->>> print s
-Password
-"""
+ n = decimal.Decimal('66666.666')
+ f = 99999.999
+ d = datetime.date(2009, 12, 31)
+ dt = datetime.datetime(2009, 12, 31, 20, 50)
+ ctxt = template.Context({'n': n, 'd': d, 'dt': dt, 'f': f})
-__test__ = {
- 'regressions': regressions,
- 'misc': misc.tests,
-}
+ # Locale independent
+
+ class I18nForm(forms.Form):
+ decimal_field = forms.DecimalField()
+ float_field = forms.FloatField()
+ date_field = forms.DateField()
+ datetime_field = forms.DateTimeField()
+ time_field = forms.TimeField()
+
+ class SelectDateForm(forms.Form):
+ date_field = forms.DateField(widget=SelectDateWidget)
+
+ settings.USE_L10N = True
+ settings.USE_THOUSAND_SEPARATOR = False
+ self.assertEqual(u'66666.66', format(n, decimal_sep='.', decimal_pos=2, grouping=3, thousand_sep=','))
+ self.assertEqual(u'66666A6', format(n, decimal_sep='A', decimal_pos=1, grouping=1, thousand_sep='B'))
+
+ settings.USE_THOUSAND_SEPARATOR = True
+ self.assertEqual(u'66,666.66', format(n, decimal_sep='.', decimal_pos=2, grouping=3, thousand_sep=','))
+ self.assertEqual(u'6B6B6B6B6A6', format(n, decimal_sep='A', decimal_pos=1, grouping=1, thousand_sep='B'))
+ self.assertEqual(u'-66666.6', format(-66666.666, decimal_sep='.', decimal_pos=1))
+ self.assertEqual(u'-66666.0', format(int('-66666'), decimal_sep='.', decimal_pos=1))
+
+ # Catalan locale with format i18n disabled translations will be used, but not formats
+
+ settings.USE_L10N = False
+ activate('ca')
+ self.assertEqual('N j, Y', get_format('DATE_FORMAT'))
+ self.assertEqual(0, get_format('FIRST_DAY_OF_WEEK'))
+ self.assertEqual('.', get_format('DECIMAL_SEPARATOR'))
+ self.assertEqual(u'des. 31, 2009', date_format(d))
+ self.assertEqual(u'desembre 2009', date_format(d, 'YEAR_MONTH_FORMAT'))
+ self.assertEqual(u'12/31/2009 8:50 p.m.', date_format(dt, 'SHORT_DATETIME_FORMAT'))
+ self.assertEqual('No localizable', localize('No localizable'))
+ self.assertEqual(decimal.Decimal('66666.666'), localize(n))
+ self.assertEqual(99999.999, localize(f))
+ self.assertEqual(datetime.date(2009, 12, 31), localize(d))
+ self.assertEqual(datetime.datetime(2009, 12, 31, 20, 50), localize(dt))
+ self.assertEqual(u'66666.666', template.Template('{{ n }}').render(ctxt))
+ self.assertEqual(u'99999.999', template.Template('{{ f }}').render(ctxt))
+ self.assertEqual(u'2009-12-31', template.Template('{{ d }}').render(ctxt))
+ self.assertEqual(u'2009-12-31 20:50:00', template.Template('{{ dt }}').render(ctxt))
+ self.assertEqual(u'66666.67', template.Template('{{ n|floatformat:2 }}').render(ctxt))
+ self.assertEqual(u'100000.0', template.Template('{{ f|floatformat }}').render(ctxt))
+ self.assertEqual(u'12/31/2009', template.Template('{{ d|date:"SHORT_DATE_FORMAT" }}').render(ctxt))
+ self.assertEqual(u'12/31/2009 8:50 p.m.', template.Template('{{ dt|date:"SHORT_DATETIME_FORMAT" }}').render(ctxt))
+
+ form = I18nForm({
+ 'decimal_field': u'66666,666',
+ 'float_field': u'99999,999',
+ 'date_field': u'31/12/2009',
+ 'datetime_field': u'31/12/2009 20:50',
+ 'time_field': u'20:50'
+ })
+ self.assertEqual(False, form.is_valid())
+ self.assertEqual([u'Introdu\xefu un n\xfamero.'], form.errors['float_field'])
+ self.assertEqual([u'Introdu\xefu un n\xfamero.'], form.errors['decimal_field'])
+ self.assertEqual([u'Introdu\xefu una data v\xe0lida.'], form.errors['date_field'])
+ self.assertEqual([u'Introdu\xefu una data/hora v\xe0lides.'], form.errors['datetime_field'])
+
+ form2 = SelectDateForm({
+ 'date_field_month': u'12',
+ 'date_field_day': u'31',
+ 'date_field_year': u'2009'
+ })
+ self.assertEqual(True, form2.is_valid())
+ self.assertEqual(datetime.date(2009, 12, 31), form2.cleaned_data['date_field'])
+ self.assertEqual(u'\n\n', SelectDateWidget().render('mydate', datetime.date(2009, 12, 31)))
+ deactivate()
+
+ # Catalan locale
+
+ settings.USE_L10N = True
+ activate('ca')
+ self.assertEqual('j \de F \de Y', get_format('DATE_FORMAT'))
+ self.assertEqual(1, get_format('FIRST_DAY_OF_WEEK'))
+ self.assertEqual(',', get_format('DECIMAL_SEPARATOR'))
+ self.assertEqual(u'31 de desembre de 2009', date_format(d))
+ self.assertEqual(u'desembre del 2009', date_format(d, 'YEAR_MONTH_FORMAT'))
+ self.assertEqual(u'31/12/2009 20:50', date_format(dt, 'SHORT_DATETIME_FORMAT'))
+ self.assertEqual('No localizable', localize('No localizable'))
+
+ settings.USE_THOUSAND_SEPARATOR = True
+ self.assertEqual(u'66.666,666', localize(n))
+ self.assertEqual(u'99.999,999', localize(f))
+
+ settings.USE_THOUSAND_SEPARATOR = False
+ self.assertEqual(u'66666,666', localize(n))
+ self.assertEqual(u'99999,999', localize(f))
+ self.assertEqual(u'31 de desembre de 2009', localize(d))
+ self.assertEqual(u'31 de desembre de 2009 a les 20:50', localize(dt))
+
+ settings.USE_THOUSAND_SEPARATOR = True
+ self.assertEqual(u'66.666,666', template.Template('{{ n }}').render(ctxt))
+ self.assertEqual(u'99.999,999', template.Template('{{ f }}').render(ctxt))
+
+ settings.USE_THOUSAND_SEPARATOR = False
+ self.assertEqual(u'66666,666', template.Template('{{ n }}').render(ctxt))
+ self.assertEqual(u'99999,999', template.Template('{{ f }}').render(ctxt))
+ self.assertEqual(u'31 de desembre de 2009', template.Template('{{ d }}').render(ctxt))
+ self.assertEqual(u'31 de desembre de 2009 a les 20:50', template.Template('{{ dt }}').render(ctxt))
+ self.assertEqual(u'66666,67', template.Template('{{ n|floatformat:2 }}').render(ctxt))
+ self.assertEqual(u'100000,0', template.Template('{{ f|floatformat }}').render(ctxt))
+ self.assertEqual(u'31/12/2009', template.Template('{{ d|date:"SHORT_DATE_FORMAT" }}').render(ctxt))
+ self.assertEqual(u'31/12/2009 20:50', template.Template('{{ dt|date:"SHORT_DATETIME_FORMAT" }}').render(ctxt))
+
+ form3 = I18nForm({
+ 'decimal_field': u'66666,666',
+ 'float_field': u'99999,999',
+ 'date_field': u'31/12/2009',
+ 'datetime_field': u'31/12/2009 20:50',
+ 'time_field': u'20:50'
+ })
+ self.assertEqual(True, form3.is_valid())
+ self.assertEqual(decimal.Decimal('66666.666'), form3.cleaned_data['decimal_field'])
+ self.assertEqual(99999.999, form3.cleaned_data['float_field'])
+ self.assertEqual(datetime.date(2009, 12, 31), form3.cleaned_data['date_field'])
+ self.assertEqual(datetime.datetime(2009, 12, 31, 20, 50), form3.cleaned_data['datetime_field'])
+ self.assertEqual(datetime.time(20, 50), form3.cleaned_data['time_field'])
+
+ form4 = SelectDateForm({
+ 'date_field_month': u'12',
+ 'date_field_day': u'31',
+ 'date_field_year': u'2009'
+ })
+ self.assertEqual(True, form4.is_valid())
+ self.assertEqual(datetime.date(2009, 12, 31), form4.cleaned_data['date_field'])
+ self.assertEqual(u'\n\n', SelectDateWidget().render('mydate', datetime.date(2009, 12, 31)))
+ deactivate()
+
+ # English locale
+
+ settings.USE_L10N = True
+ activate('en')
+ self.assertEqual('N j, Y', get_format('DATE_FORMAT'))
+ self.assertEqual(0, get_format('FIRST_DAY_OF_WEEK'))
+ self.assertEqual('.', get_format('DECIMAL_SEPARATOR'))
+ self.assertEqual(u'Dec. 31, 2009', date_format(d))
+ self.assertEqual(u'December 2009', date_format(d, 'YEAR_MONTH_FORMAT'))
+ self.assertEqual(u'12/31/2009 8:50 p.m.', date_format(dt, 'SHORT_DATETIME_FORMAT'))
+ self.assertEqual('No localizable', localize('No localizable'))
+
+ settings.USE_THOUSAND_SEPARATOR = True
+ self.assertEqual(u'66,666.666', localize(n))
+ self.assertEqual(u'99,999.999', localize(f))
+
+ settings.USE_THOUSAND_SEPARATOR = False
+ self.assertEqual(u'66666.666', localize(n))
+ self.assertEqual(u'99999.999', localize(f))
+ self.assertEqual(u'Dec. 31, 2009', localize(d))
+ self.assertEqual(u'Dec. 31, 2009, 8:50 p.m.', localize(dt))
+
+ settings.USE_THOUSAND_SEPARATOR = True
+ self.assertEqual(u'66,666.666', template.Template('{{ n }}').render(ctxt))
+ self.assertEqual(u'99,999.999', template.Template('{{ f }}').render(ctxt))
+
+ settings.USE_THOUSAND_SEPARATOR = False
+ self.assertEqual(u'66666.666', template.Template('{{ n }}').render(ctxt))
+ self.assertEqual(u'99999.999', template.Template('{{ f }}').render(ctxt))
+ self.assertEqual(u'Dec. 31, 2009', template.Template('{{ d }}').render(ctxt))
+ self.assertEqual(u'Dec. 31, 2009, 8:50 p.m.', template.Template('{{ dt }}').render(ctxt))
+ self.assertEqual(u'66666.67', template.Template('{{ n|floatformat:2 }}').render(ctxt))
+ self.assertEqual(u'100000.0', template.Template('{{ f|floatformat }}').render(ctxt))
+ self.assertEqual(u'12/31/2009', template.Template('{{ d|date:"SHORT_DATE_FORMAT" }}').render(ctxt))
+ self.assertEqual(u'12/31/2009 8:50 p.m.', template.Template('{{ dt|date:"SHORT_DATETIME_FORMAT" }}').render(ctxt))
+
+ form5 = I18nForm({
+ 'decimal_field': u'66666.666',
+ 'float_field': u'99999.999',
+ 'date_field': u'12/31/2009',
+ 'datetime_field': u'12/31/2009 20:50',
+ 'time_field': u'20:50'
+ })
+ self.assertEqual(True, form5.is_valid())
+ self.assertEqual(decimal.Decimal('66666.666'), form5.cleaned_data['decimal_field'])
+ self.assertEqual(99999.999, form5.cleaned_data['float_field'])
+ self.assertEqual(datetime.date(2009, 12, 31), form5.cleaned_data['date_field'])
+ self.assertEqual(datetime.datetime(2009, 12, 31, 20, 50), form5.cleaned_data['datetime_field'])
+ self.assertEqual(datetime.time(20, 50), form5.cleaned_data['time_field'])
+
+ form6 = SelectDateForm({
+ 'date_field_month': u'12',
+ 'date_field_day': u'31',
+ 'date_field_year': u'2009'
+ })
+ self.assertEqual(True, form6.is_valid())
+ self.assertEqual(datetime.date(2009, 12, 31), form6.cleaned_data['date_field'])
+ self.assertEqual(u'\n\n', SelectDateWidget().render('mydate', datetime.date(2009, 12, 31)))
+ deactivate()
+
+ # Check if sublocales fall back to the main locale
+ activate('de-at')
+ settings.USE_THOUSAND_SEPARATOR = True
+ self.assertEqual(u'66.666,666', template.Template('{{ n }}').render(ctxt))
+ deactivate()
+
+ activate('es-ar')
+ self.assertEqual(u'31 de Diciembre de 2009', date_format(d))
+ deactivate()
+
+ # Restore defaults
+ settings.USE_I18N = old_use_i18n
+ settings.USE_L10N = old_use_l10n
+ settings.USE_THOUSAND_SEPARATOR = old_use_thousand_separator
+
+
+class MiscTests(TestCase):
+
+ def test_parse_spec_http_header(self):
+ """
+ Testing HTTP header parsing. First, we test that we can parse the
+ values according to the spec (and that we extract all the pieces in
+ the right order).
+ """
+ from django.utils.translation.trans_real import parse_accept_lang_header
+ p = parse_accept_lang_header
+ # Good headers.
+ self.assertEqual([('de', 1.0)], p('de'))
+ self.assertEqual([('en-AU', 1.0)], p('en-AU'))
+ self.assertEqual([('*', 1.0)], p('*;q=1.00'))
+ self.assertEqual([('en-AU', 0.123)], p('en-AU;q=0.123'))
+ self.assertEqual([('en-au', 0.10000000000000001)], p('en-au;q=0.1'))
+ self.assertEqual([('en-au', 1.0)], p('en-au;q=1.0'))
+ self.assertEqual([('da', 1.0), ('en', 0.5), ('en-gb', 0.25)], p('da, en-gb;q=0.25, en;q=0.5'))
+ self.assertEqual([('en-au-xx', 1.0)], p('en-au-xx'))
+ self.assertEqual([('de', 1.0), ('en-au', 0.75), ('en-us', 0.5), ('en', 0.25), ('es', 0.125), ('fa', 0.125)], p('de,en-au;q=0.75,en-us;q=0.5,en;q=0.25,es;q=0.125,fa;q=0.125'))
+ self.assertEqual([('*', 1.0)], p('*'))
+ self.assertEqual([('de', 1.0)], p('de;q=0.'))
+ self.assertEqual([], p(''))
+
+ # Bad headers; should always return [].
+ self.assertEqual([], p('en-gb;q=1.0000'))
+ self.assertEqual([], p('en;q=0.1234'))
+ self.assertEqual([], p('en;q=.2'))
+ self.assertEqual([], p('abcdefghi-au'))
+ self.assertEqual([], p('**'))
+ self.assertEqual([], p('en,,gb'))
+ self.assertEqual([], p('en-au;q=0.1.0'))
+ self.assertEqual([], p('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXZ,en'))
+ self.assertEqual([], p('da, en-gb;q=0.8, en;q=0.7,#'))
+ self.assertEqual([], p('de;q=2.0'))
+ self.assertEqual([], p('de;q=0.a'))
+ self.assertEqual([], p(''))
+
+ def test_parse_literal_http_header(self):
+ """
+ Now test that we parse a literal HTTP header correctly.
+ """
+ from django.utils.translation.trans_real import get_language_from_request
+ g = get_language_from_request
+ from django.http import HttpRequest
+ r = HttpRequest
+ r.COOKIES = {}
+ r.META = {'HTTP_ACCEPT_LANGUAGE': 'pt-br'}
+ self.assertEqual('pt-br', g(r))
+
+ r.META = {'HTTP_ACCEPT_LANGUAGE': 'pt'}
+ self.assertEqual('pt', g(r))
+
+ r.META = {'HTTP_ACCEPT_LANGUAGE': 'es,de'}
+ self.assertEqual('es', g(r))
+
+ r.META = {'HTTP_ACCEPT_LANGUAGE': 'es-ar,de'}
+ self.assertEqual('es-ar', g(r))
+
+ # Python 2.3 and 2.4 return slightly different results for completely
+ # bogus locales, so we omit this test for that anything below 2.4.
+ # It's relatively harmless in any cases (GIGO). This also means this
+ # won't be executed on Jython currently, but life's like that
+ # sometimes. (On those platforms, passing in a truly bogus locale
+ # will get you the default locale back.)
+ if sys.version_info >= (2, 5):
+ # This test assumes there won't be a Django translation to a US
+ # variation of the Spanish language, a safe assumption. When the
+ # user sets it as the preferred language, the main 'es'
+ # translation should be selected instead.
+ r.META = {'HTTP_ACCEPT_LANGUAGE': 'es-us'}
+ self.assertEqual(g(r), 'es')
+
+ # This tests the following scenario: there isn't a main language (zh)
+ # translation of Django but there is a translation to variation (zh_CN)
+ # the user sets zh-cn as the preferred language, it should be selected
+ # by Django without falling back nor ignoring it.
+ r.META = {'HTTP_ACCEPT_LANGUAGE': 'zh-cn,de'}
+ self.assertEqual(g(r), 'zh-cn')