diff options
| author | Marc Garcia <garcia.marc@gmail.com> | 2009-07-08 00:52:25 +0000 |
|---|---|---|
| committer | Marc Garcia <garcia.marc@gmail.com> | 2009-07-08 00:52:25 +0000 |
| commit | 07ed7fea103ee7f507fb2e49cdd6d983fe54c7b0 (patch) | |
| tree | b2a05285d0f10c466d5da7040c44ac424d72d67b | |
| parent | 5b616f27c67f087b9f93f10804b175dee4c46839 (diff) | |
[soc2009/i18n] formatting system in development
git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/i18n-improvements@11204 bcc190cf-cafb-0310-a4f2-bffc1f526a37
| -rw-r--r-- | django/conf/global_settings.py | 25 | ||||
| -rw-r--r-- | django/conf/locale/ca/formats.py | 4 | ||||
| -rw-r--r-- | django/contrib/admin/templatetags/admin_list.py | 14 | ||||
| -rw-r--r-- | django/template/defaultfilters.py | 5 | ||||
| -rw-r--r-- | django/utils/formats.py | 48 | ||||
| -rw-r--r-- | django/utils/numberformat.py | 32 | ||||
| -rw-r--r-- | docs/ref/settings.txt | 61 | ||||
| -rw-r--r-- | tests/regressiontests/i18n/tests.py | 59 |
8 files changed, 203 insertions, 45 deletions
diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index a8f180a426..e09164e530 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -259,6 +259,12 @@ FILE_UPLOAD_TEMP_DIR = None # you'd pass directly to os.chmod; see http://docs.python.org/lib/os-file-dir.html. FILE_UPLOAD_PERMISSIONS = None +# Python module path where user will place custom format definition. +# The directory where this setting is pointing should contain subdirectories +# named as the locales, containing a formats.py file +# (i.e. "myproject.locale" for myproject/locale/en/formats.py etc. use) +FORMAT_MODULE_PATH = None + # Default formatting for date objects. See all available format strings here: # http://docs.djangoproject.com/en/dev/ref/templates/builtins/#now DATE_FORMAT = 'N j, Y' @@ -283,21 +289,28 @@ MONTH_DAY_FORMAT = 'F j' # Default shortformatting for date objects. See all available format strings here: # http://docs.djangoproject.com/en/dev/ref/templates/builtins/#now -SHORT_DATE_FORMAT = 'N j, Y' +SHORT_DATE_FORMAT = 'm/d/Y' # Default short formatting for datetime objects. # See all available format strings here: # http://docs.djangoproject.com/en/dev/ref/templates/builtins/#now -SHORT_DATETIME_FORMAT = 'N j, Y, P' +SHORT_DATETIME_FORMAT = 'm/d/Y P' -# Default first day of week, to be used on calendars -# 0 means Sunday +# First day of week, to be used on calendars +# 0 means Sunday, 1 means Monday... FIRST_DAY_OF_WEEK = 0 -# Default decimal separator symbol +# Decimal separator symbol DECIMAL_SEPARATOR = '.' -# Default thousand separator symbol +# Boolean that sets whether to add thousand separator when formatting numbers +USE_THOUSAND_SEPARATOR = False + +# Number of digits that will be togheter, when spliting them by THOUSAND_SEPARATOR +# 0 means no grouping, 3 means splitting by thousands... +NUMBER_GROUPING = 0 + +# Thousand separator symbol THOUSAND_SEPARATOR = ',' # Do you want to manage transactions manually? diff --git a/django/conf/locale/ca/formats.py b/django/conf/locale/ca/formats.py index 414fe86e26..cc8a494f7c 100644 --- a/django/conf/locale/ca/formats.py +++ b/django/conf/locale/ca/formats.py @@ -3,7 +3,7 @@ DATE_FORMAT = 'j \de F \de Y' DATETIME_FORMAT = 'j \de F \de Y \\a \le\s H:i' -TIME_FORMAT = 'P' +TIME_FORMAT = 'H:i' YEAR_MONTH_FORMAT = 'F \de Y' MONTH_DAY_FORMAT = 'j \de F' @@ -14,5 +14,5 @@ FIRST_DAY_OF_WEEK = 1 # 0 means Sunday DECIMAL_SEPARATOR = ',' THOUSAND_SEPARATOR = '.' - +NUMBER_GROUPING = 3 diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py index 45bab2d6b4..add72037bb 100644 --- a/django/contrib/admin/templatetags/admin_list.py +++ b/django/contrib/admin/templatetags/admin_list.py @@ -8,7 +8,6 @@ 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 ugettext as _ -from django.utils.formats import localize from django.utils.encoding import smart_unicode, smart_str, force_unicode from django.template import Library import datetime @@ -183,14 +182,17 @@ def items_for_result(cl, result, form): else: result_repr = EMPTY_CHANGELIST_VALUE # Dates and times are special: They're formatted in a certain way. - elif isinstance(f, models.DateField) or isinstance(f, models.TimeField) or isinstance(f, models.DecimalField): + elif isinstance(f, models.DateField) or isinstance(f, models.TimeField): if field_val: - result_repr = localize(field_val) - # result_repr = ('%%.%sf' % f.decimal_places) % field_val + result_repr = formats.localize(field_val) else: result_repr = EMPTY_CHANGELIST_VALUE - if not isinstance(f, models.DecimalField): - row_class = ' class="nowrap"' + 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"' # Booleans are special: We use images. elif isinstance(f, models.BooleanField) or isinstance(f, models.NullBooleanField): result_repr = _boolean_icon(field_val) diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index 47e116cd1a..94108f2d23 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 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 diff --git a/django/utils/formats.py b/django/utils/formats.py index ad22e7600c..2b8e2704c0 100644 --- a/django/utils/formats.py +++ b/django/utils/formats.py @@ -1,6 +1,8 @@ import decimal import datetime +from django.conf import settings +from django.utils.translation import get_language from django.utils.importlib import import_module from django.utils import dateformat from django.utils import numberformat @@ -11,7 +13,6 @@ def getformat_null(format_type): set on the settings. format_type is the name of the format, for example 'DATE_FORMAT' """ - from django.conf import settings return getattr(settings, format_type) def getformat_real(format_type): @@ -20,15 +21,21 @@ def getformat_real(format_type): current language (locale) defaulting to the format on settings. format_type is the name of the format, for example 'DATE_FORMAT' """ - from django.utils.translation import get_language import_formats = lambda s: import_module('.formats', 'django.conf.locale.%s' % s) - tmp = import_formats('ca') - format = None - try: - module = import_formats(get_language()) - except ImportError: - pass - else: + module = format = None + if settings.FORMAT_MODULE_PATH: + try: + module = import_module('.formats', '%s.%s' % (settings.FORMAT_MODULE_PATH, get_language())) + except ImportError: + pass + + if not module: + try: + module = import_module('.formats', 'django.conf.locale.%s' % get_language()) + except ImportError: + pass + + if module: try: format = getattr(module, format_type) except AttributeError: @@ -37,28 +44,43 @@ def getformat_real(format_type): # getformat will just return the value on setings if # we don't use i18n in our project -from django.conf import settings if settings.USE_I18N and settings.USE_FORMAT_I18N: getformat = getformat_real else: getformat = getformat_null def date_format(value, format=None): + """ + Formats a datetime.date or datetime.datetime object using a + localizable format + """ return dateformat.format(value, getformat(format or 'DATE_FORMAT')) -def number_format(value): +def number_format(value, decimal_pos=None): + """ + Formats a numeric value using localization settings + """ return numberformat.format( value, getformat('DECIMAL_SEPARATOR'), - 2, # TODO: decide how to set decimal positions later - 3, # TODO: get it from formats + decimal_pos, + getformat('NUMBER_GROUPING'), getformat('THOUSAND_SEPARATOR'), ) 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 + """ if settings.USE_I18N and settings.USE_FORMAT_I18N: 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): return date_format(value, 'DATETIME_FORMAT') elif isinstance(value, datetime.date): diff --git a/django/utils/numberformat.py b/django/utils/numberformat.py index 0abf5b35c5..6223dbc26f 100644 --- a/django/utils/numberformat.py +++ b/django/utils/numberformat.py @@ -1,5 +1,13 @@ -def format(number, decimal_sep, decimal_pos, groupping=0, thousand_sep=''): +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 number < 0: @@ -7,28 +15,26 @@ def format(number, decimal_sep, decimal_pos, groupping=0, thousand_sep=''): else: sign = '' # decimal part - str_number = str(abs(number)) + str_number = unicode(number) + if str_number[0] == '-': + str_number = str_number[1:] if '.' in str_number: int_part, dec_part = str_number.split('.') - dec_part = dec_part[:decimal_pos] + if decimal_pos: + dec_part = dec_part[:decimal_pos] else: int_part, dec_part = str_number, '' - dec_part = dec_part + ('0' * (decimal_pos - len(dec_part))) + if decimal_pos: + dec_part = dec_part + ('0' * (decimal_pos - len(dec_part))) if dec_part: dec_part = decimal_sep + dec_part - # groupping - if groupping: + # grouping + if settings.USE_THOUSAND_SEPARATOR and grouping: int_part_gd = '' for cnt, digit in enumerate(int_part[::-1]): - if cnt and not cnt % groupping: + 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 -if __name__ == '__main__': - import decimal - print format_number(-1000000, ',', 4, 3, '.') - print format_number(-100.100, ',', 4, 3, '.') - print format_number(decimal.Decimal('1000.100'), ',', 4) diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 597aeaa89d..13bcbd24e3 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -302,7 +302,6 @@ 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 @@ -310,7 +309,7 @@ DECIMAL_SEPARATOR Default: ``'.'`` (Dot) -The default decimal separator used when formatting decimal numbers. +Default decimal separator used when formatting decimal numbers. .. setting:: DEFAULT_CHARSET @@ -537,6 +536,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 specially 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 @@ -783,6 +797,21 @@ locales have different formats. For example, U.S. English would say See :ttag:`allowed date format strings <now>`. 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 @@ -1119,6 +1148,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 @@ -1186,6 +1227,20 @@ 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_FORMAT_I18N + +USE_FORMAT_I18N +--------------- + +Default ``False`` + +A boolean that specifies if data will be localized by default or not. If this is +set to ``True``, Django will display numbers and dates using the format of the +current locale. It is required to set ``USE_I18N`` to ``True`` to allow data +format localization. + +See also ``USE_I18N`` + .. setting:: USE_I18N USE_I18N @@ -1198,6 +1253,8 @@ 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_FORMAT_I18N`` + .. setting:: YEAR_MONTH_FORMAT YEAR_MONTH_FORMAT diff --git a/tests/regressiontests/i18n/tests.py b/tests/regressiontests/i18n/tests.py index dc875c6a31..a67b84712d 100644 --- a/tests/regressiontests/i18n/tests.py +++ b/tests/regressiontests/i18n/tests.py @@ -65,13 +65,70 @@ status. >>> print s Password ->>> from django.utils.formats import getformat +Localization of dates and numbers + +>>> import datetime +>>> import decimal +>>> from django.utils.formats import getformat, date_format, number_format, localize + +>>> n = decimal.Decimal('66666.666') +>>> d = datetime.date(2009, 7, 6) +>>> dt = datetime.datetime(2009, 7, 6, 20, 50) + +Locale independent + +>>> number_format(n, decimal_sep='.', decimal_pos=2, grouping=3, thousand_sep=',') +'66,666.66' +>>> number_format(n, decimal_sep='A', decimal_pos=1, grouping=1, thousand_sep='B') +'6B6B6B6B6BA6' + +English locale + >>> activate('en') >>> getformat('DATE_FORMAT') 'N j, Y' +>>> getformat('FIRST_DAY_OF_WEEK') +0 +>>> getformat('DECIMAL_SEPARATOR') +'.' +>>> date_format(d) +'July 6, 2009' +>>> date_format(d, 'YEAR_MONTH_FORMAT') +'July 2009' +>>> date_format(d, 'SHORT_DATETIME_FORMAT') +'07/06/2009 8:50 p.m.' +>>> localize('No localizable') +'No localizable' +>>> localize(n) +'66666.666' +>>> localize(d) +'July 6, 2009' +>>> localize(dt) +'July 6, 2009, 8:50 p.m.' + +Catalan locale + >>> activate('ca') >>> getformat('DATE_FORMAT') 'j \de N \de Y' +>>> getformat('FIRST_DAY_OF_WEEK') +1 +>>> getformat('DECIMAL_SEPARATOR') +',' +>>> date_format(d) +'6 de juliol de 2009' +>>> date_format(d, 'YEAR_MONTH_FORMAT') +'juliol de 2009' +>>> date_format(d, 'SHORT_DATETIME_FORMAT') +'06/07/2009 20:50' +>>> localize('No localizable') +'No localizable' +>>> localize(n) +'66.666,666' +>>> localize(d) +'6 de juliol de 2009' +>>> localize(dt) +'6 de juliol de 2009 a les 20:50' """ |
