<input type='something_not_text'... and to set the 'required' attribute when the
IS_NOT_EMPTY validator is specified.
Modifying SQLFORM to select new defaults
I modified my sqlhtml.py file (in the gluon directory) to use HTML5's input types (rather than everything defaulting to type='text'). So, I added the types: UrlWidget, TelephoneWidget, EmailWidget, ColorWidget, and RangeWidget. And modified the types: IntegerWidget, DoubleWidget, DecimalWidget, TimeWidget, DateWidget and DatetimeWidget.
In class SQLFORM, I added to the
widgets = Storage(dict( list:
telephone = TelephoneWidget, url = UrlWidget, email = EmailWidget, color = ColorWidget, range = RangeWidget,
and then further down within the
__init__ function, I added elif statements to select the corresponding widget when none is specified:
for fieldname in self.fields: ... if cond: ... elif field.type == 'text': inp = self.widgets.text.widget(field, default) #Here default widgets are set based on the field type: #integer, double, decimal, date, time, and datetime #were set to their respective widget types elif field.type == 'integer': inp = self.widgets.integer.widget(field, default) ... elif field.type == 'datetime': inp = self.widgets.datetime.widget(field, default)
Changing or Adding Widget definitions
The main change from StringWidget to the new types is simply setting the
_type in the default
dict() object. For example, for the decimal and double widgets (which additionally require the default step size to be changed from the default of 1):
default = dict( _type = 'number', _step = 'any', #allow any floating point, otherwise default step size is 1 value = (value!=None and str(value)) or '', )
The other changes are to change the inheritance from StringWidget to FormWidget, and to set
attr = WidgetName . See the example at the bottom.
For the widget classes, I wanted to be slightly lazy. If I had
requires = IS_NOT_EMPTY() in my table field definition, then I wanted my html input to have the
required attribute. So I added to all my input type widgets:
if hasattr(field, 'requires') and field.requires: if ("IS_NOT_EMPTY" in str(field.requires)): default['_required'] = 'true'
Technically the attribute should just be "required"; the "required='true'" is just so something is passed in the dictionary object so I wouldn't have to figure out how to modify the INPUT class as well.
Similarly, if I was using an IS_X_IN_RANGE validator, I wanted the min and max attributes of the input tags to be set. So after deciding if
field.requires has IS_INT_IN_RANGE, IS_FLOAT_IN_RANGE, etc. I set min and max to
field.requires.maximum respectively (its probably bad form to grab these values directly, but I was trying to avoid changing other files). So, for example:
if field.requires.minimum != None: default['_min'] = str(field.requires.minimum)
Actually field.requires could be a list, in which case field.requires.minimum would break, so (grabbing from the formatter function inside dal.py which also needs to step through the validators) field.requires is turned into a list if it isn't one, then the list is iterated over.
So finally, as an example, here is my modified DatetimeWidget (which is marginally more complicated because html5 spec for datetime wants format=''%Y-%m-%dT%H:%MZ").
class DatetimeWidget(FormWidget): @staticmethod def widget(field, value, **attributes): """ generates an INPUT datetime tag. see also: :meth:`FormWidget.widget` """ default = dict( _type = 'datetime', value = (value!=None and str(value)) or '', ) if hasattr(field, 'requires') and field.requires: if ("IS_NOT_EMPTY" in str(field.requires)): default['_required'] = 'true' if not isinstance(field.requires, (list, tuple)): requires = [field.requires] elif isinstance(field.requires, tuple): requires = list(field.requires) else: requires = copy.copy(field.requires) requires.reverse() for item in requires: if ("IS_DATETIME_IN_RANGE" in str(item)): #html5 wants yyyy-mm-ddThh:mm:ssZ #or yyyy-mm-ddThh:mm:ss-TimeZoneOffset if item.minimum != None: default['_min'] = str(item.minimum.date())+'T'\ +str(field.requires.minimum.time())+'Z' if item.maximum != None: default['_max'] = str(item.maximum)+'T'\ +str(item.maximum.time())+'Z' attr = DatetimeWidget._attributes(field, default, **attributes) return INPUT(**attr)
Unfortunately, this still fails to set the min and max attributes if the validators are nested (as in
IS_EMPTY_OR(IS_DATE_IN_RANGE(...)) ). So I probably will have to add a function to some of the validators, that can return the max and min when validators get nested
Also there is probably some way to do something smarter with inheritance; most of the new types were: copy-paste, change