Skip to content

Commit

Permalink
Added new precision value "sub-cents" (interfaces.SUBCENTS), which
Browse files Browse the repository at this point in the history
allows precision beyond pennies, which is needed for financial and other
business applications.
  • Loading branch information
strichter committed Sep 27, 2013
1 parent d9a595e commit 5508619
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 14 deletions.
6 changes: 4 additions & 2 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
CHANGES
=======

1.0.1 (unreleased)
1.1.0 (2013-09-23)
------------------

- Nothing changed yet.
- Added new precision value "sub-cents" (``interfaces.SUBCENTS``), which
allows precision beyond pennies, which is needed for financial and other
business applications.


1.0.0 (2013-08-16)
Expand Down
4 changes: 1 addition & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
#
##############################################################################
"""Setup
$Id$
"""
import os
from setuptools import setup, find_packages
Expand All @@ -24,7 +22,7 @@ def read(*rnames):

setup (
name='z3c.currency',
version='1.0.1.dev0',
version='1.1.0dev0',
author = "Stephan Richter and the Zope Community",
author_email = "zope3-dev@zope.org",
description = "A currency field and support for ``z3c.form``.",
Expand Down
47 changes: 42 additions & 5 deletions src/z3c/currency/README.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,19 @@ unit -- in the US dollar and cents. By default this field is set to cents:
>>> price.precision is interfaces.CENTS
True

It can be set to be dollars
It can be set to be dollars:

>>> price.precision = interfaces.DOLLARS
>>> price.precision is interfaces.DOLLARS
True

For financial applications, we also sometimes needsub-cents:

>>> price.precision = interfaces.SUBCENTS
>>> price.precision is interfaces.SUBCENTS
True


Note: Is there a more "internationalized" word for the whole unit of a
currency?

Expand Down Expand Up @@ -81,11 +88,11 @@ must be a whole number:
...
IncorrectValuePrecision: 0

When the precision is set to cents, ...
When the precision is set to cents,

>>> price.precision = interfaces.CENTS

... then values only with two decimal places are accepted:
then values only with two decimal places are accepted:

>>> price.validate(decimal.Decimal('12.00'))

Expand All @@ -99,6 +106,16 @@ When the precision is set to cents, ...
...
IncorrectValuePrecision: 1

If we allow sub-cents,

>>> price.precision = interfaces.SUBCENTS

any precision is allowed:

>>> price.validate(decimal.Decimal('12.0'))
>>> price.validate(decimal.Decimal('12'))
>>> price.validate(decimal.Decimal('12.00001'))

If the field is not required, ...

>>> price.required = False
Expand Down Expand Up @@ -150,16 +167,26 @@ precision to DOLLARS:
>>> conv.toWidgetValue(decimal.Decimal('12.00'))
u'12'

>>> conv.field.precision = interfaces.CENTS
Let's try sub-cents as well:

>>> conv.field.precision = interfaces.SUBCENTS

>>> conv.toWidgetValue(decimal.Decimal('12.00'))
u'12.00'
>>> conv.toWidgetValue(decimal.Decimal('12'))
u'12'
>>> conv.toWidgetValue(decimal.Decimal('12.0001'))
u'12.0001'

If the value is missing, then handle it gracefully.

>>> conv.toWidgetValue(None)
u''

Let's now parse a value. The parser is a little bit for flexible, not only
Let's now parse a value. The parser is a little bit flexible, not only
accepting the output values, ...

>>> conv.field.precision = interfaces.CENTS
>>> conv.toFieldValue(u'12')
Decimal('12.00')
>>> conv.toFieldValue(u'1,200')
Expand All @@ -175,6 +202,16 @@ accepting the output values, ...
>>> conv.toFieldValue(u'12.00')
Decimal('12')

>>> conv.field.precision = interfaces.SUBCENTS
>>> conv.toFieldValue(u'12')
Decimal('12')
>>> conv.toFieldValue(u'12.00')
Decimal('12.00')
>>> conv.toFieldValue(u'12.0000')
Decimal('12.0000')
>>> conv.toFieldValue(u'12.0001')
Decimal('12.0001')

but also other input values:

>>> conv.toFieldValue(u'1200')
Expand Down
12 changes: 9 additions & 3 deletions src/z3c/currency/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,12 @@ class CurrencyConverter(object):
zope.component.adapts(interfaces.ICurrency, IWidget)
zope.interface.implements(IDataConverter)

inputPatterns = ('#,##0;-#,##0', '#,##0.00;-#,##0.00')
outputPatterns = ('#,##0;-#,##0', '#,##0.00;-#,##0.00')
inputPatterns = (
'#,##0;-#,##0', '#,##0.00;-#,##0.00', '#,##0.00###;-#,##0.00###')
outputPatterns = (
'#,##0;-#,##0', '#,##0.00;-#,##0.00', '#,##0.00###;-#,##0.00###')
quantization = ('1', '0.01', None)


def __init__(self, field, widget):
self.field = field
Expand All @@ -52,8 +56,10 @@ def toFieldValue(self, value):
except (format.NumberParseError, ValueError):
continue
# Make sure that the resulting decimal has the right precision.
if self.field.precision == interfaces.SUBCENTS:
return res
return res.quantize(
decimal.Decimal('0.01' if self.field.precision else '1'))
decimal.Decimal(self.quantization[self.field.precision]))
raise ValueError('Could not parse %r.' %value)

def __repr__(self):
Expand Down
4 changes: 3 additions & 1 deletion src/z3c/currency/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
# Precisions
DOLLARS = 0
CENTS = 1
SUBCENTS = 2

class WrongCurrencyType(zope.schema.ValidationError):
_("""The value is not a Decimal.""")
Expand All @@ -36,8 +37,9 @@ class ICurrency(interfaces.IMinMax, interfaces.IField):
title=_('Precision'),
description=_('The precision in which the amount is kept.'),
vocabulary=vocabulary.SimpleVocabulary([
vocabulary.SimpleTerm(DOLLARS, str(DOLLARS), u'Dollars'),
vocabulary.SimpleTerm(CENTS, str(CENTS), u'Cents'),
vocabulary.SimpleTerm(DOLLARS, str(DOLLARS), u'Dollars')
vocabulary.SimpleTerm(SUBCENTS, str(SUBCENTS), u'Sub-Cents'),
]),
default=CENTS,
required=True)
Expand Down

0 comments on commit 5508619

Please sign in to comment.