import decimal from decimal import Decimal import wtforms.fields as wtfields from otree.currency import Currency, to_dec from otree.decimal import DecimalUnit from otree.i18n import format_number_for_form from otree import common from otree import settings from . import widgets as wg def handle_localized_number_input(val): if val is None: return val return val.replace(',', '.') class FloatField(wtfields.FloatField): widget = wg.FloatWidget() def process_formdata(self, valuelist): if valuelist: try: self.data = float(handle_localized_number_input(valuelist[0])) except ValueError: self.data = None # hack: hide this from pybabel, which seems to scan for all # functions that end with 'gettext('. # wtforms already contains these translations, so they should # not end up in our .po file. _gt = self.gettext raise ValueError(_gt('Not a valid float value')) def _value(self): if self.data is None: return '' return format_number_for_form(self.data, places=common.FULL_DECIMAL_PLACES) class CurrencyField(wtfields.Field): widget = wg.CurrencyWidget() def process_formdata(self, valuelist): if valuelist and valuelist[0]: try: data = Currency(handle_localized_number_input(valuelist[0])) except (decimal.InvalidOperation, ValueError): self.data = None # see the note above about gettext _gt = self.gettext raise ValueError(_gt('Not a valid decimal value')) else: data = None self.data = data def _value(self): if self.data is None: return '' return format_number_for_form( to_dec(self.data), places=common.FULL_DECIMAL_PLACES ) class DecimalFormField(wtfields.Field): def __init__(self, label=None, validators=None, unit=None, **kwargs): super().__init__(label, validators, **kwargs) self.unit = unit self.widget = wg.DecimalWidget(unit=unit) def process_formdata(self, valuelist): if valuelist and valuelist[0]: try: input_val = handle_localized_number_input(valuelist[0]) # Round to form precision before creating DecimalUnit input_places = self.unit.input_places quant = Decimal('0.1') ** input_places rounded = Decimal(input_val).quantize( quant, rounding=decimal.ROUND_HALF_UP ) data = self.unit(rounded) except (decimal.InvalidOperation, ValueError): self.data = None _gt = self.gettext raise ValueError(_gt('Not a valid decimal value')) else: data = None self.data = data def _value(self): if self.data is None: return '' places = self.unit.input_places return format_number_for_form(to_dec(self.data), places=places) class StringField(wtfields.StringField): widget = wg.TextInput() class IntegerField(wtfields.IntegerField): widget = wg.IntegerWidget() def _selectfield_getitem(self, index): if not isinstance(index, int): raise IndexError for i, choice in enumerate(self): if index == i: return choice raise IndexError def __iter__(self): """ Add 'required' attribute to HTML: https://github.com/wtforms/wtforms/pull/615/files """ opts = dict( widget=self.option_widget, _name=self.name, _form=None, _meta=self.meta, validators=self.validators, ) for i, (value, label, checked) in enumerate(self.iter_choices()): opt = self._Option(label=label, id='%s-%d' % (self.id, i), **opts) opt.process(None, value) opt.checked = checked yield opt class RadioField(wtfields.RadioField): widget = wg.RadioSelect() option_widget = wg.RadioOption() __getitem__ = _selectfield_getitem __iter__ = __iter__ class RadioFieldHorizontal(wtfields.RadioField): widget = wg.RadioSelectHorizontal() option_widget = wg.RadioOption() __getitem__ = _selectfield_getitem __iter__ = __iter__ class DropdownField(wtfields.SelectField): widget = wg.Select() option_widget = wg.SelectOption() __getitem__ = _selectfield_getitem __iter__ = __iter__ class TextAreaField(StringField): """ This field represents an HTML ``