#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# ~license~
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
from appy.px import Px
from appy.model.fields import Field
from appy.ui.layout import Layouts, Layout
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
class Boolean(Field):
'''Field for storing boolean values'''
yesNo = {'true': 'yes', 'false': 'no', True: 'yes', False: 'no'}
trueFalse = {True: 'true', False: 'false'}
# Values coming from the request and being considered as True
trueValues = ('True', 1, '1')
# In some situations, if may be appropriate to consider False as an empty
# value for a boolean field.
nullValuesVariants = {
True: (None, False), # False is considered to represent emptiness
False: (None,) # False does not represent emptiness
}
class Layouts(Layouts):
'''Boolean-specific layouts'''
es = 'f;lrv;-'
ds = 'flrv;=d'
# Default layout (render = "checkbox") ("b" stands for "base"), followed
# by the "grid" variant (if the field is in a group with "grid" style).
b = Layouts(edit=Layout(es, width=None), view='lf')
g = Layouts(edit=Layout('frvl', width=None), view='fl')
d = Layouts(edit=Layout(ds, width=None), view='lf')
t = Layouts(edit=Layout(es, width=None, css='topSpace'), view='lf')
# With bottom space
bP = {'css': 'bottomSpaceS'}
bb = Layouts(edit=Layout('fl', **bP) , view=Layout('lf', **bP))
bbd = Layouts(edit=Layout('fl-d', **bP), view=Layout('lf', **bP))
# *d*escription also visible on "view"
dv = Layouts(edit=d['edit'], view='lf-d')
# *d*escription, with *t*op space
dt = Layouts(edit=Layout(ds, width=None, css='topSpace'), view='lf')
h = Layouts(edit=Layout('flhv', width=None), view='lf')
dh = Layouts(edit=Layout('flhv-d', width=None), view='lf')
gd = Layouts(edit=Layout('f;dv-', width=None), view='fl')
# The base layout, plus bottom space
bs = Layouts(edit=Layout(es, width=None, css='bottomSpaceS'), view='lf')
# The "long" version of the previous layout (if the description is
# long), with vertical alignment on top instead of middle.
gdl = Layouts(edit=Layout('f;dv=', width=None), view='fl')
# Centered layout, no description
c = Layouts(edit='flrv|', view='lf|')
# Layout for radio buttons (render = "radios")
r = Layouts(edit='f', view='f')
rl = Layouts(edit='l-f', view='lf')
rld = Layouts(edit='l-d-f', view='lf')
grl = Layouts(edit='fl', view='fl')
gdr = Layouts(edit=Layout('d-fv=', width=None), view='fl')
rt = r.clone(css='topSpace')
@classmethod
def getDefault(class_, field):
'''Default layouts for this Boolean p_field'''
if field.asRadios: return class_.r
return class_.g if field.inGrid() else class_.b
# The name of the index class storing values of this field in the catalog
indexType = 'BooleanIndex'
view = cell = buttons = Px('''
''')
def __init__(self, validator=None, multiplicity=(0,1), default=None,
defaultOnEdit=None, show=True, renderable=None, page='main', group=None,
layouts=None, move=0, indexed=False, mustIndex=True, indexValue=None,
searchable=False, filterField=None, readPermission='read',
writePermission='write', width=None, height=None, maxChars=None,
colspan=1, master=None, masterValue=None, focus=False, historized=False,
mapping=None, generateLabel=None, label=None, sdefault=False, scolspan=1,
swidth=None, sheight=None, persist=True, render='checkbox',
falseFirst=True, inlineEdit=False, view=None, cell=None, buttons=None,
edit=None, custom=None, xml=None, translations=None,
falseMeansEmpty=False, disabled=False, confirm=False):
# The following p_render modes are available.
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# "checkbox" | (the default) The boolean field is render as a checkbox
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# "radios" | The field is rendered via 2 radio buttons, with custom
# | labels corresponding to the 2 truth values.
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# "switch" | The field is rendered as a checkbox on the edit layout,
# | but as a clickable, button-like "on/off" switch on the
# | view/cell layout (for those having write permission).
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
self.render = render
self.asRadios = render == 'radios'
# When render is "radios", in what order should the buttons appear ?
self.falseFirst = falseFirst
# When render is "switch", the field may be rendered on button layouts
if renderable is None and render == 'switch':
renderable = Layouts.onButtons
# When render is "switch", and p_self.confirm is True, when clicking on
# the switch, a confirmation popup will appear first. In that case, 2
# i18n labels will be generated: one as confirmation text before
# switching to False, another as confirmation text for switching to
# True. Setting p_self.confirm to True for a Boolean field whose render
# mode is not "switch" has no effect.
self.confirm = confirm
# Call the base constructor
super().__init__(validator, multiplicity, default, defaultOnEdit, show,
renderable, page, group, layouts, move, indexed, mustIndex,
indexValue, None, searchable, filterField, readPermission,
writePermission, width, height, None, colspan, master, masterValue,
focus, historized, mapping, generateLabel, label, sdefault, scolspan,
swidth, sheight, persist, inlineEdit, view, cell, buttons, edit,
custom, xml, translations)
self.pythonType = bool
# Must value False be interpreted as an empty value or not ?
self.nullValues = Boolean.nullValuesVariants[falseMeansEmpty]
# Must the edit widget, for p_self, be rendered as a disabled widget ?
# It it only applicable:
# - when render mode is "radios", on the "edit" layout,
# - when render mode is "switch", when the switch is actually rendered.
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# When render | p_disabled must be ...
# mode is ... |
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# "radios" | a boolean value or a method computing it ;
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# "switch" | a method returning False or None if the widget must be
# | enabled, or returning a translated text if the widget
# | must be enabled. This text will appear as a tooltip on
# | on the switch.
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
self.disabled = disabled
def getValue(self, o, name=None, layout=None, single=None, at=None):
'''Do not return "None": return "True" or "False", even if "None" is
stored in the DB (or no value is stored), excepted if render is
"radios".'''
value = super().getValue(o, name, layout, single, at)
if self.asRadios:
return value # Can be None
# In any other case, value is always True or False
return False if value is None else value
def getValueLabel(self, value):
'''Returns the label for p_value (True or False): if self.render is
"checkbox", the label is simply the translated version of "yes" or
"no"; if self.render is "radios", there are specific labels.'''
if self.asRadios:
if value is None:
r = '-'
else:
r = f'{self.labelId}_{self.trueFalse[value]}'
return r
return self.yesNo[bool(value)]
def getFormattedValue(self, o, value, layout='view', showChanges=False,
language=None):
return o.translate(self.getValueLabel(value), language=language)
def getMasterTag(self, layout):
'''The tag driving slaves is the hidden field'''
return f'{self.name}_hidden' if layout == 'edit' else self.name
def getOnChange(self, o, layout, className=None):
'''Updates the hidden field storing the actual UI value for the field'''
r = 'updateHiddenBool(this)'
# Call the base behaviour
base = Field.getOnChange(self, o, layout, className=className)
return f'{r};{base}' if base else r
def getStorableValue(self, o, value, single=False):
'''Converts this string p_value to a boolean value'''
if not self.isEmptyValue(o, value):
r = eval(value)
return r
def getSearchValue(self, req, value=None):
'''Converts the raw search value from p_form into a boolean value'''
return eval(Field.getSearchValue(self, req, value=value))
def isSortable(self, inRefs=False):
'''Can this field be sortable ?'''
return True if inRefs else Field.isSortable(self) # Sortable in Refs
def isTrue(self, o, name, dbValue):
'''When the UI widget is rendered, must it store True or False ?'''
req = o.req
# Get the value we must compare (from request or from database)
return req[name] in self.trueValues if name in req else dbValue
# Template for a radio button
radioTemplate = '