from decimal import Decimal
from markupsafe import escape, Markup
from wtforms.compat import text_type
from wtforms.widgets import html_params
from otree import settings
from otree.currency import CURRENCY_SYMBOLS
from otree.i18n import core_gettext
# the below code is adapted from wtforms
class BaseWidget:
has_value = True
def __call__(self, field, **render_kw):
self.field = field
render_kw.setdefault('id', field.id)
if self.has_value and 'value' not in render_kw:
render_kw['value'] = field._value()
if 'required' not in render_kw and 'required' in getattr(field, 'flags', []):
render_kw['required'] = True
self.render_kw = render_kw
return Markup(''.join(self.get_html_fragments()))
def get_html_fragments(self):
raise NotImplementedError
def attrs(self):
return html_params(name=self.field.name, **self.render_kw)
class CheckboxInput(BaseWidget):
def __call__(self, field, **render_kw):
if getattr(field, 'checked', field.data):
render_kw['checked'] = True
return super().__call__(field, **render_kw)
def get_html_fragments(self):
yield ('' % (self.attrs()))
class IntegerWidget(BaseWidget):
"""
better to use number input when we can, because:
- on mobile it pops up the number keypad
- better validation of numbers
"""
def get_html_fragments(self):
yield f'' % self.attrs()
class FloatWidget(BaseWidget):
"""
use input type='text' instead of 'number' because:
- it allows comma as decimal separator
- no annoying spinner widget
- no problems dealing with step= argument.
"""
def get_html_fragments(self):
yield f'' % self.attrs()
class CurrencyWidget(BaseWidget):
def __init__(self):
if settings.USE_POINTS:
if getattr(settings, 'POINTS_CUSTOM_NAME', None):
CURRENCY_SYMBOL = settings.POINTS_CUSTOM_NAME
places = settings.POINTS_DECIMAL_PLACES
else:
# Translators: the label next to a "points" input field
CURRENCY_SYMBOL = core_gettext('points')
places = settings.REAL_WORLD_CURRENCY_DECIMAL_PLACES
else:
CURRENCY_SYMBOL = CURRENCY_SYMBOLS.get(
settings.REAL_WORLD_CURRENCY_CODE, settings.REAL_WORLD_CURRENCY_CODE
)
places = settings.REAL_WORLD_CURRENCY_DECIMAL_PLACES
self.symbol = CURRENCY_SYMBOL
self.places = places
def get_html_fragments(self):
yield '''
'
class TextInput(BaseWidget):
def get_html_fragments(self):
yield '' % self.attrs()
class TextArea(BaseWidget):
"""
Renders a multi-line text area.
`rows` and `cols` ought to be passed as keyword args when rendering.
"""
def get_html_fragments(self):
yield (
''
% (self.attrs(), escape(self.field._value()))
)
class Select(BaseWidget):
"""
Renders a select field.
The field must provide an `iter_choices()` method which the widget will
call on rendering; this method must yield tuples of
`(value, label, selected)`.
"""
has_value = False
def get_html_fragments(self):
yield ''
@classmethod
def render_option(cls, value, label, selected, **kwargs):
if value is True:
# Handle the special case of a 'True' value.
value = text_type(value)
options = dict(kwargs, value=value)
if selected:
options['selected'] = True
return Markup(
'' % (html_params(**options), escape(label))
)
class SelectOption(object):
"""
Renders the individual option from a select field.
This is just a convenience for various custom rendering situations, and an
option by itself does not constitute an entire field.
"""
def __call__(self, field, **kwargs):
return Select.render_option(
field._value(), field.label.text, field.checked, **kwargs
)
class RadioSelect(BaseWidget):
has_value = False
def get_html_fragments(self):
yield '
' % html_params(**self.render_kw)
for subfield in self.field:
# 'required' attribute is missing in wtforms
# https://github.com/wtforms/wtforms/pull/615
# let's wait until that change gets released,
# before fixing the case of {{ for option in field }}...
# which unfortunately skips past this workaround
subfield_html = (
subfield(required=True)
if self.render_kw.get('required')
else subfield()
)
yield '
%s %s
' % (
subfield_html,
subfield.label,
)
yield '
'
class RadioSelectHorizontal(BaseWidget):
has_value = False
def get_html_fragments(self):
for subfield in self.field:
subfield_html = (
subfield(required=True)
if self.render_kw.get('required')
else subfield()
)
yield f'''
{subfield_html}
'''
class RadioOption(BaseWidget):
def __call__(self, field, **kwargs):
if field.checked:
kwargs['checked'] = True
# see comment above about missing required attribute.
return super().__call__(field, **kwargs)
def get_html_fragments(self):
yield '' % self.attrs()