wcs/wcs/fields.py

748 lines
24 KiB
Python

import time
try:
import elementtree.ElementTree as ET
except ImportError:
ET = None
from quixote import get_request, get_publisher
from quixote.html import htmltext, TemplateIO
from qommon.form import *
from qommon.misc import localstrftime, date_format, ellipsize
from qommon import get_cfg
from qommon.strftime import strftime
class PrefillSelectionWidget(CompositeWidget):
def __init__(self, name, value = None, **kwargs):
CompositeWidget.__init__(self, name, value, **kwargs)
if not value:
value = {}
options = [('none', _('None')), ('string', _('String')),
('user', _('User Field')), ('wsf', _('WSF Expression'))]
self.add(SingleSelectWidget, 'type', options = options, value = value.get('type'))
self.parse()
if not self.value:
self.value = {}
if self.value.get('type') == 'string':
self.add(StringWidget, 'value', value = value.get('value'))
elif self.value.get('type') == 'user':
from users import User
formdef = User.get_formdef()
users_cfg = get_cfg('users', {})
if formdef:
user_fields = [(x.id, x.label) for x in formdef.fields]
if not users_cfg.get('field_email'):
user_fields.append(('email', _('Email (builtin)')))
else:
user_fields = [('name', _('Name')), ('email', _('Email'))]
self.add(SingleSelectWidget, 'value', value = value.get('value'),
options = user_fields)
elif self.value.get('type') == 'wsf':
self.add(StringWidget, 'value', value = value.get('value'))
self.add(SubmitWidget, 'apply', value = _('Apply'))
self._parsed = False
def _parse(self, request):
values = {}
for name in ('type', 'value'):
value = self.get(name)
if value:
values[name] = value
if values.get('data', '').startswith('------'):
values = None
self.value = values or None
def render_content(self):
r = TemplateIO(html=True)
for widget in self.get_widgets():
r += widget.render_content()
return r.getvalue()
class Field:
id = None
label = None
extra_css_class = None
convert_value_from_str = None
convert_value_to_str = None
in_listing = False
prefill = None
stats = None
def __init__(self, **kwargs):
for k, v in kwargs.items():
setattr(self, k.replace('-', '_'), v)
def init(self):
pass
init = classmethod(init)
def get_admin_attributes(self):
return ['label', 'type']
def export_to_xml(self, charset):
field = ET.Element('field')
for attribute in self.get_admin_attributes():
if hasattr(self, attribute) and getattr(self, attribute) is not None:
el = ET.SubElement(field, attribute)
val = getattr(self, attribute)
if type(val) is dict:
for k, v in val.items():
ET.SubElement(el, k).text = unicode(v, charset)
elif type(val) is list:
if attribute[-1] == 's':
atname = attribute[:-1]
else:
atname = 'item'
for v in val:
ET.SubElement(el, atname).text = unicode(v, charset)
elif type(val) in (str, unicode):
if type(val) is unicode:
el.text = val
else:
el.text = unicode(val, charset)
else:
el.text = str(val)
return field
def init_with_xml(self, elem, charset):
for attribute in self.get_admin_attributes():
el = elem.find(attribute)
if el is None:
continue
if el.getchildren():
if type(getattr(self, attribute)) is list:
v = [x.text.encode(charset) for x in el.getchildren()]
elif type(getattr(self, attribute)) is dict:
v = {}
for e in el.getchildren():
v[e.tag] = e.text.encode(charset)
else:
print 'currently:', self.__dict__
print ' attribute:', attribute
# ???
raise AssertionError
setattr(self, attribute, v)
else:
if el.text is None:
setattr(self, attribute, None)
elif el.text in ('False', 'True'): # bools
setattr(self, attribute, eval(el.text))
else:
setattr(self, attribute, el.text.encode(charset))
def get_rst_view_value(self, value, indent=''):
return indent + self.get_view_value(value)
class WidgetField(Field):
type = None
hint = None
required = True
in_listing = True
wsf_prefill_explicit = False
extra_attributes = []
prefill = {}
def add_to_form(self, form, value = None):
kwargs = {'required': self.required}
if value:
kwargs['value'] = value
for k in self.extra_attributes:
if hasattr(self, k):
kwargs[k] = getattr(self, k)
self.perform_more_widget_changes(form, kwargs)
form.add(self.widget_class, 'f%s' % self.id, title = self.label, hint = self.hint, **kwargs)
widget = form.get_widget('f%s' % self.id)
widget.field = self
widget.extra_css_class = self.extra_css_class
def perform_more_widget_changes(self, form, kwargs, edit = True):
pass
def add_to_view_form(self, form, value = None):
kwargs = {}
self.field_key = 'f%s' % self.id
self.perform_more_widget_changes(form, kwargs, False)
for k in self.extra_attributes:
if hasattr(self, k):
kwargs[k] = getattr(self, k)
form.add(self.widget_class, self.field_key, title = self.label,
value = value, readonly = 'readonly', **kwargs)
def fill_admin_form(self, form):
form.add(StringWidget, 'label', title = _('Label'), value = self.label,
required = True, size = 50)
form.add(CheckboxWidget, 'required', title = _('Required'),
value = self.required)
form.add(StringWidget, 'hint', title = _('Hint'), value = self.hint,
size = 80)
form.add(CheckboxWidget, 'in_listing', title = _('Display in listings'),
value = self.in_listing)
form.add(StringWidget, 'extra_css_class', title = _('Extra class for CSS styling'),
value = self.extra_css_class, size = 30)
form.add(PrefillSelectionWidget, 'prefill', title = _('Prefill'),
value = self.prefill)
if False and self.wsf_support:
form.add(CheckboxWidget, 'wsf_prefill_explicit',
title = _('Needs ID-WSF prefill explicit request'),
value = self.wsf_prefill_explicit)
def check_admin_form(self, form):
return
def get_admin_attributes(self):
return Field.get_admin_attributes(self) + ['required', 'hint', 'in_listing',
'extra_css_class', 'prefill',
'wsf_prefill_explicit']
def get_view_value(self, value):
return value
def get_view_short_value(self, value, max_len = 30):
return self.get_view_value(value)
field_classes = []
field_types = []
def register_field_class(klass):
if not klass in field_classes:
field_classes.append(klass)
if not issubclass(klass, WidgetField):
field_types.append((klass.key, klass.description))
else:
non_widgets = [x for x in field_classes if not issubclass(x, WidgetField)]
if not non_widgets:
field_types.append((klass.key, klass.description))
else:
idx = field_types.index([x for x in field_types if x[0] == non_widgets[0].key][0])
field_types.insert(idx, (klass.key, klass.description))
klass.init()
class TitleField(Field):
key = 'title'
description = N_('Title')
def add_to_form(self, form, value = None):
if self.extra_css_class:
extra_css_class = ' class="%s"' % self.extra_css_class
else:
extra_css_class = ''
form.widgets.append(HtmlWidget(
htmltext('<h3%s>%s</h3>' % (extra_css_class, self.label))))
add_to_view_form = add_to_form
def fill_admin_form(self, form):
form.add(StringWidget, 'label', title = _('Label'), value = self.label,
required = True, size = 50)
form.add(StringWidget, 'extra_css_class', title = _('Extra class for CSS styling'),
value = self.extra_css_class, size = 30)
def get_admin_attributes(self):
return Field.get_admin_attributes(self) + ['extra_css_class']
register_field_class(TitleField)
class SubtitleField(TitleField):
key = 'subtitle'
description = N_('Subtitle')
def add_to_form(self, form, value = None):
if self.extra_css_class:
extra_css_class = ' class="%s"' % self.extra_css_class
else:
extra_css_class = ''
form.widgets.append(HtmlWidget(
htmltext('<h4%s>%s</h4>' % (extra_css_class, self.label))))
add_to_view_form = add_to_form
register_field_class(SubtitleField)
class CommentField(Field):
key = 'comment'
description = N_('Comment')
def add_to_form(self, form, value = None):
if self.extra_css_class:
extra_css_class = ' class="%s"' % self.extra_css_class
else:
extra_css_class = ''
form.widgets.append(HtmlWidget(
htmltext('<p%s>%s</p>' % (extra_css_class, self.label))))
def add_to_view_form(self, *args):
pass
def fill_admin_form(self, form):
form.add(TextWidget, 'label', title = _('Label'), value = self.label,
required = True, cols = 70, rows = 3, render_br = False)
form.add(StringWidget, 'extra_css_class', title = _('Extra class for CSS styling'),
value = self.extra_css_class, size = 30)
def get_admin_attributes(self):
return Field.get_admin_attributes(self) + ['extra_css_class']
register_field_class(CommentField)
class StringField(WidgetField):
key = 'string'
description = N_('Text (line)')
widget_class = WcsExtraStringWidget
size = None
extra_attributes = ['size']
validation = None
def fill_admin_form(self, form):
WidgetField.fill_admin_form(self, form)
form.add(StringWidget, 'size', title = _('Line length'),
value = self.size)
form.add(RegexStringWidget, 'validation', title = _('Validation regex'),
value = self.validation)
def get_admin_attributes(self):
return WidgetField.get_admin_attributes(self) + ['size', 'validation']
register_field_class(StringField)
class TextField(WidgetField):
key = 'text'
description = N_('Text (block)')
widget_class = TextWidget
cols = None
rows = None
pre = False
extra_attributes = ['cols', 'rows']
def fill_admin_form(self, form):
WidgetField.fill_admin_form(self, form)
form.add(StringWidget, 'cols', title = _('Line length'),
value = self.cols)
form.add(StringWidget, 'rows', title = _('Number of rows'),
value = self.rows)
form.add(CheckboxWidget, 'pre', title = _('Preformatted Text'),
value = self.pre)
def get_admin_attributes(self):
return WidgetField.get_admin_attributes(self) + ['cols', 'rows', 'pre']
def get_view_value(self, value):
if self.pre:
return htmltext('<pre>') + value + htmltext('</pre>')
else:
return htmltext('<p>') + htmltext('\n').join(
[(x or htmltext('</p><p>')) for x in value.splitlines()]) + \
htmltext('</p>')
def get_view_short_value(self, value, max_len = 30):
return ellipsize(value, max_len)
register_field_class(TextField)
class EmailField(WidgetField):
key = 'email'
description = N_('Email')
widget_class = EmailWidget
def get_view_value(self, value):
return htmltext('<a href="mailto:%s">%s</a>') % (value, value)
register_field_class(EmailField)
class BoolField(WidgetField):
key = 'bool'
description = N_('Check Box')
widget_class = CheckboxWidget
def perform_more_widget_changes(self, form, kwargs, edit = True):
if not edit:
kwargs['disabled'] = 'disabled'
value = get_request().get_field(self.field_key)
form.add_hidden(self.field_key, value = value)
if value:
self.field_key = 'f%sdisabled' % self.id
get_request().form[self.field_key] = 'yes'
self.field_key = 'f%sdisabled' % self.id
def get_view_value(self, value):
if value is True or value == 'True':
return _('Yes')
elif value is False or value == 'False':
return _('No')
else:
return ''
register_field_class(BoolField)
class FileField(WidgetField):
key = 'file'
description = N_('File Upload')
widget_class = FileWithPreviewWidget
def get_view_value(self, value):
return htmltext('<a href="[download]?f=%s">%s</a>') % (self.id, value)
register_field_class(FileField)
class DateField(WidgetField):
key = 'date'
description = N_('Date')
widget_class = DateWidget
minimum_date = None
maximum_date = None
minimum_is_future = False
date_in_the_past = False
date_can_be_today = False
extra_attributes = ['minimum_date', 'minimum_is_future', 'maximum_date',
'date_in_the_past', 'date_can_be_today']
def fill_admin_form(self, form):
WidgetField.fill_admin_form(self, form)
form.add(DateWidget, 'minimum_date', title = _('Minimum Date'), value = self.minimum_date)
form.add(CheckboxWidget, 'minimum_is_future',
title = _('Date must be in the future'), value = self.minimum_is_future,
hint = _('This option is obviously not compatible with setting a minimum date'))
form.add(DateWidget, 'maximum_date', title = _('Maximum Date'), value = self.maximum_date)
form.add(CheckboxWidget, 'date_in_the_past',
title = _('Date must be in the past'), value = self.date_in_the_past,
hint = _('This option is obviously not compatible with setting a maximum date'))
form.add(CheckboxWidget, 'date_can_be_today',
title = _('Date can be present day'), value = self.date_can_be_today,
hint = _('This option is only useful combined with one of the previous checkboxes.'))
def get_admin_attributes(self):
return WidgetField.get_admin_attributes(self) + [
'minimum_date', 'minimum_is_future', 'maximum_date',
'date_in_the_past', 'date_can_be_today']
def convert_value_from_str(self, value):
return time.strptime(value, date_format())
def convert_value_to_str(self, value):
if value is None:
return ''
try:
return strftime(date_format(), value)
except TypeError:
return ''
def add_to_view_form(self, form, value = None):
value = localstrftime(value)
return WidgetField.add_to_view_form(self, form, value = value)
def get_view_value(self, value):
try:
return strftime(misc.date_format(), value)
except TypeError:
return value
register_field_class(DateField)
def item_items_stats(field, values):
no_records = len(values)
r = TemplateIO(html = True)
r += htmltext('<table class="stats">')
r += htmltext('<thead><tr><th colspan="3">')
r += field.label
r += htmltext('</th></tr></thead>')
options = field.items or []
r += htmltext('<tbody>')
for o in options:
r += htmltext('<tr>')
r += htmltext('<td class="label">')
r += o
r += htmltext('</td>')
if field.type == 'item':
no = len([None for x in values if x.data.get(field.id) == o])
else:
no = len([None for x in values if o in (x.data.get(field.id) or [])])
if no_records:
r += htmltext('<td class="percent">')
r += ' %.2f %%' % (100.*no/no_records)
r += htmltext('</td>')
image_len = 300.*no/no_records
r += htmltext('<td>')
if image_len:
r += htmltext('<img class="bar" src="%simages/bar.png" height="20px" alt="" '
'width="%dpx" />' % (get_publisher().get_root_url(), image_len))
r += htmltext('</td>')
else:
r += htmltext('<td colspan="2"></td>')
#'NaN'
r += htmltext('</tr>')
r += htmltext('</tbody>')
r += htmltext('</table>')
return r.getvalue()
class ItemField(WidgetField):
key = 'item'
description = N_('List')
items = []
show_as_radio = False
widget_class = SingleSelectWidget
def __init__(self, **kwargs):
self.items = []
WidgetField.__init__(self, **kwargs)
def perform_more_widget_changes(self, form, kwargs, edit = True):
if edit:
kwargs['options'] = self.items or [(None, '---')]
if self.show_as_radio:
self.widget_class = RadiobuttonsWidget
if len(kwargs['options']) > 3 or len(self.items[0]) > 10:
# TODO: absence/presence of delimitor should be an option
kwargs['delim'] = htmltext('<br />')
else:
self.widget_class = StringWidget
def fill_admin_form(self, form):
WidgetField.fill_admin_form(self, form)
form.add(WidgetList, 'items', title = _('Items'), element_type = StringWidget,
value = self.items, required = True,
element_kwargs = {'render_br': False, 'size': 50},
add_element_label = _('Add item'))
form.add(CheckboxWidget, 'show-as-radio', title = _('Show as radio buttons'),
value = self.show_as_radio)
def get_admin_attributes(self):
return WidgetField.get_admin_attributes(self) + ['items', 'show-as-radio']
def check_admin_form(self, form):
items = form.get_widget('items').parse()
d = {}
for v in (items or []):
if v in d:
form.set_error('items', _('Duplicated Items'))
return
d[v] = None
def stats(self, values):
return item_items_stats(self, values)
register_field_class(ItemField)
class ItemsField(WidgetField):
key = 'items'
description = N_('Text (closed choice)')
items = []
max_choices = None
widget_class = CheckboxesWidget
def __init__(self, **kwargs):
self.items = []
WidgetField.__init__(self, **kwargs)
def perform_more_widget_changes(self, form, kwargs, edit = True):
kwargs['elements'] = self.items or []
kwargs['max_choices'] = self.max_choices
if len(self.items) > 3:
kwargs['inline'] = False
def fill_admin_form(self, form):
WidgetField.fill_admin_form(self, form)
form.add(WidgetList, 'items', title = _('Items'), element_type = StringWidget,
value = self.items, required = True,
element_kwargs = {'render_br': False, 'size': 50},
add_element_label = _('Add item'))
form.add(IntWidget, 'max_choices', title = _('Maximum number of choices'),
value = self.max_choices, required = False, size = 4)
def get_admin_attributes(self):
return WidgetField.get_admin_attributes(self) + ['items', 'max_choices']
def check_admin_form(self, form):
items = form.get_widget('items').parse()
d = {}
for v in (items or []):
if v in d:
form.set_error('items', _('Duplicated Items'))
return
d[v] = None
def get_view_value(self, value):
if value:
return ', '.join([(x) for x in value])
return ''
def stats(self, values):
return item_items_stats(self, values)
register_field_class(ItemsField)
class PageField(Field):
key = 'page'
description = N_('New Page')
condition = None
def fill_admin_form(self, form):
form.add(StringWidget, 'label', title = _('Label'), value = self.label,
required = True, size = 50)
form.add(StringWidget, 'condition', title = _('Condition'), value = self.condition,
required = False, size = 50)
def get_admin_attributes(self):
return Field.get_admin_attributes(self) + ['condition']
def add_to_view_form(self, *args):
pass
def is_visible(self, dict):
if dict is None:
return True
if not self.condition:
return True
data = {}
for k, v in dict.items():
data['f%s' % k] = v
try:
if eval(self.condition, data):
return True
except:
return True
return False
register_field_class(PageField)
class TableField(WidgetField):
key = 'table'
description = N_('Table')
in_listing = False # no way to represent data in a single cell
rows = None
columns = None
widget_class = TableWidget
def __init__(self, **kwargs):
self.rows = None
self.columns = None
WidgetField.__init__(self, **kwargs)
def perform_more_widget_changes(self, form, kwargs, edit = True):
kwargs['rows'] = self.rows
kwargs['columns'] = self.columns
def fill_admin_form(self, form):
WidgetField.fill_admin_form(self, form)
form.add(WidgetList, 'rows', title = _('Rows'), element_type = StringWidget,
value = self.rows, required = True,
element_kwargs = {'render_br': False, 'size': 50},
add_element_label = _('Add row'))
form.add(WidgetList, 'columns', title = _('Columns'), element_type = StringWidget,
value = self.columns, required = True,
element_kwargs = {'render_br': False, 'size': 50},
add_element_label = _('Add column'))
def get_admin_attributes(self):
return WidgetField.get_admin_attributes(self) + ['rows', 'columns']
def get_view_value(self, value):
r = TemplateIO(html=True)
r += htmltext('<table><thead><tr><td></td>')
for column in self.columns:
r += htmltext('<th>%s</th>') % column
r += htmltext('</tr></thead><tbody>')
for i, row in enumerate(self.rows):
r += htmltext('<tr><th>%s</th>') % row
for j, column in enumerate(self.columns):
r += htmltext('<td>')
if value:
r += value[i][j]
r += htmltext('</td>')
r += htmltext('</tr>')
r += htmltext('</tbody></table>')
return r.getvalue()
def get_rst_view_value(self, value, indent=''):
if not value:
return indent
r = []
max_width = 0
for column in self.columns:
max_width = max(max_width, len(column))
for i, row in enumerate(value):
value[i] = [x or '' for x in row]
for i, row in enumerate(self.rows):
max_width = max(max_width, len(row))
for j, column in enumerate(self.columns):
max_width = max(max_width, len(value[i][j]))
r.append(' '.join(['='*max_width]*(len(self.columns)+1)))
r.append(' '.join([column.center(max_width) for column in ['/']+self.columns]))
r.append(' '.join(['='*max_width]*(len(self.columns)+1)))
for i, row in enumerate(self.rows):
r.append(' '.join([cell.center(max_width) for cell in [row] +
[value[i][x] for x in range(len(self.columns))]]))
r.append(' '.join(['='*max_width]*(len(self.columns)+1)))
return '\n'.join([indent + x for x in r])
register_field_class(TableField)
def get_field_class_by_type(type):
for k in field_classes:
if k.key == type:
return k
raise KeyError()
def get_field_types():
return field_types