# Currency.py """ Currency = Decimal + Babel >>> Currency() Decimal("0.00") """ from decimal import * from babel.numbers import format_decimal, format_currency, parse_decimal, parse_number, get_decimal_symbol, get_group_symbol, get_currency_symbol, NumberFormatError from django.conf import settings from django.utils.translation import ugettext_lazy as _ default_error_messages = { 'decimal_symbol': _(u'Ensure that there is only one decimal symbol (%s).'), 'invalid_format': _(u'Invalid currency format. Please use the format 9%s999%s00') } TWOPLACES = Decimal(10) ** -2 def _getSymbols(value): retVal = '' for x in value: if x < u'0' or x > u'9': retVal += x return retVal def _getCodes(): l_currency_language_code = 'en_US' l_currency_code = 'USD' try: l_currency_language_code = settings.CURRENCY_LANGUAGE_CODE l_currency_code = '' except AttributeError: pass try: l_currency_code = settings.CURRENCY_CODE except AttributeError: pass return (l_currency_language_code, l_currency_code) def parse_value(value): """ Accepts a string value and attempts to parce it as a currency value. Returns the extracted numeric value converted to a string """ l_currency_language_code, l_currency_code = _getCodes() curSym = get_currency_symbol(l_currency_code, l_currency_language_code) grpSym = get_group_symbol(locale=l_currency_language_code.lower()) decSym = get_decimal_symbol(locale=l_currency_language_code.lower()) # Convert the Official characters into what comes from the keyboard. # This section may need to grow over time. # - Character 160 is a non-breaking space, which is different from a typed space if ord(grpSym) == 160: value = value.replace(u' ', grpSym) allSym = _getSymbols(value) invalidSym = allSym.replace(curSym, '').replace(grpSym, '').replace(decSym, '').replace(u'-', '') value = value.replace(curSym, '') if allSym.count(decSym) > 1: raise NumberFormatError(default_error_messages['decimal_symbol'] % decSym) elif (allSym.count(decSym) == 1 and allSym[-1] != decSym) or len(invalidSym) > 0: raise NumberFormatError(default_error_messages['invalid_format'] % (grpSym, decSym)) elif value.count(decSym) == 1: value = parse_decimal(value, locale=l_currency_language_code.lower()) else: value = parse_number(value, locale=l_currency_language_code.lower()) # The value is converted into a string because the parse functions return floats return str(value) class Currency(Decimal): """ A Currency data type that extends the Decimal type and integrates the Bable libraries. Accepts any numeric value or formated currency string as input. Testing different numeric inputs and rounding >>> Currency(.1) Decimal("0.10") >>> Currency(1) Decimal("1.00") >>> Currency(.015) Decimal("0.02") >>> Currency(.014) Decimal("0.01") Testing string input and format validation using en_US currency format >>> import os >>> os.environ['DJANGO_SETTINGS_MODULE'] = 'django.conf.global_settings' >>> Currency("1") Decimal("1.00") >>> Currency("1,234.00") Decimal("1234.00") >>> Currency("1,234.0.0") Traceback (most recent call last): ... NumberFormatError: Ensure that there is only one decimal symbol (.). >>> Currency("1,2,34.0") Decimal("1234.00") >>> Currency("1,234.00").format() u'1,234.00' >>> Currency("1,234.00").format_pretty() u'$1,234.00' >>> Currency("-1,234.00").format_pretty() u'$-1,234.00' >>> Currency("1 234.00") Traceback (most recent call last): ... NumberFormatError: Invalid currency format. Please use the format 9,999.00 >>> Currency("1.234,00") Traceback (most recent call last): ... NumberFormatError: Invalid currency format. Please use the format 9,999.00 >>> Currency("1.234") Decimal("1.23") >>> Currency("$1,234.00") Decimal("1234.00") >>> Currency("$1,234.00", format="#,##0").format() u'1,234' >>> Currency("$-1,234.00", format_pretty=u"#,##0 \xa4").format_pretty() u'-1,234 $' Testing string input and format validation using pt_BR currency format >>> from django.conf import settings >>> settings.CURRENCY_LANGUAGE_CODE = 'pt_BR' >>> Currency("1 234.00") Traceback (most recent call last): ... NumberFormatError: Invalid currency format. Please use the format 9.999,00 >>> Currency("1.234") Decimal("1234.00") >>> Currency("1.234").format() u'1.234,00' """ def __new__(cls, value="0", format=None, format_pretty=None, parse_string=False, context=None): """ Create a new Currency object value: Can be any number (integer, decimal, or float) or a properly formated string format: The format to use in the format() method format_pretty: The format used in the format_pretty() method parse_string: *IMPORTANT* Set this to True if you are passing a string formatted in a currency other than the standard decimal format of #,###.## context: How to handle a malformed string value """ if value != "0" and isinstance(value, basestring) and parse_string: value = parse_value(value) elif isinstance(value, float): value = str(value) if format: cls._format = format else: cls._format = '#,##0.00;-#' if format_pretty: cls._formatPretty = format_pretty else: cls._formatPretty = u'\xa4#,##0.00;\xa4-#' ld_rounded = Decimal(value).quantize(TWOPLACES, ROUND_HALF_UP) return super(Currency, cls).__new__(cls, value=ld_rounded, context=context) def format(self): l_currency_language_code, l_currency_code = _getCodes() return format_decimal(self, format=self._format, locale=l_currency_language_code) def format_pretty(self): l_currency_language_code, l_currency_code = _getCodes() return format_currency(self, l_currency_code, format=self._formatPretty, locale=l_currency_language_code) def _test(): import doctest doctest.testmod() if __name__ == "__main__": _test() # Additions to Setting.py #CURRENCY_LANGUAGE_CODE = 'pt_BR' #CURRENCY_CODE = '' # If one exists like 'USD', 'EUR'