2254 lines
85 KiB
Python
2254 lines
85 KiB
Python
# w.c.s. - web application for online forms
|
|
# Copyright (C) 2005-2010 Entr'ouvert
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import copy
|
|
import datetime
|
|
import time
|
|
import random
|
|
import re
|
|
import base64
|
|
import xml.etree.ElementTree as ET
|
|
import collections
|
|
from HTMLParser import HTMLParser
|
|
|
|
from quixote import get_request, get_publisher
|
|
from quixote.html import htmltext, TemplateIO
|
|
|
|
from django.utils.encoding import smart_text
|
|
from django.utils.formats import date_format as django_date_format
|
|
|
|
from qommon import _
|
|
from qommon import evalutils
|
|
from qommon.form import *
|
|
from qommon.misc import localstrftime, strftime, date_format, ellipsize, can_thumbnail
|
|
from qommon.template import Template, TemplateError
|
|
from qommon import get_cfg, get_logger
|
|
|
|
import data_sources
|
|
import portfolio
|
|
from conditions import Condition
|
|
|
|
|
|
class PrefillSelectionWidget(CompositeWidget):
|
|
def __init__(self, name, value=None, field=None, **kwargs):
|
|
CompositeWidget.__init__(self, name, value, **kwargs)
|
|
|
|
if not value:
|
|
value = {}
|
|
|
|
options = [('none', _('None')),
|
|
('string', _('String')),
|
|
('formula', _('Formula (Python)')),
|
|
('user', _('User Field')),
|
|
('geolocation', _('Geolocation')),]
|
|
|
|
if field and field.type == 'items':
|
|
# limit choices to python as items field are prefilled with list
|
|
# of strings
|
|
options = [x for x in options if x[0] in ('none', 'formula')]
|
|
elif field and field.type == 'map':
|
|
# limit choices to geolocation
|
|
options = [x for x in options if x[0] in ('none', 'string', 'geolocation')]
|
|
|
|
self.add(SingleSelectWidget, 'type', options=options, value=value.get('type'),
|
|
attrs={'data-dynamic-display-parent': 'true'})
|
|
|
|
self.parse()
|
|
if not self.value:
|
|
self.value = {}
|
|
|
|
prefill_types = collections.OrderedDict(options)
|
|
self.add(StringWidget, 'value_string', size=80,
|
|
value=value.get('value') if value.get('type') == 'string' else None,
|
|
attrs={'data-dynamic-display-child-of': 'prefill$type',
|
|
'data-dynamic-display-value': prefill_types.get('string')})
|
|
self.add(StringWidget, 'value_formula', size=80,
|
|
value=value.get('value') if value.get('type') == 'formula' else None,
|
|
attrs={'data-dynamic-display-child-of': 'prefill$type',
|
|
'data-dynamic-display-value': prefill_types.get('formula')})
|
|
|
|
formdef = get_publisher().user_class.get_formdef()
|
|
users_cfg = get_cfg('users', {})
|
|
if formdef:
|
|
user_fields = []
|
|
for user_field in formdef.fields:
|
|
if user_field.label in [x[1] for x in user_fields]:
|
|
# do not allow duplicated field names
|
|
continue
|
|
user_fields.append((user_field.id, user_field.label))
|
|
if not users_cfg.get('field_email'):
|
|
user_fields.append(('email', _('Email (builtin)')))
|
|
else:
|
|
user_fields = [('name', _('Name')), ('email', _('Email'))]
|
|
self.add(SingleSelectWidget, 'value_user',
|
|
value=value.get('value') if value.get('type') == 'user' else None,
|
|
options=user_fields,
|
|
attrs={'data-dynamic-display-child-of': 'prefill$type',
|
|
'data-dynamic-display-value': prefill_types.get('user')})
|
|
|
|
if field and field.type == 'map':
|
|
# different prefilling sources on map fields
|
|
geoloc_fields = [('position', _('Position'))]
|
|
else:
|
|
geoloc_fields = [
|
|
('house', _('Number')),
|
|
('road', _('Street')),
|
|
('number-and-street', _('Number and street')),
|
|
('postcode', _('Post Code')),
|
|
('city', _('City')),
|
|
('country', _('Country')),
|
|
]
|
|
self.add(SingleSelectWidget, 'value_geolocation',
|
|
value=value.get('value') if value.get('type') == 'geolocation' else None,
|
|
options=geoloc_fields,
|
|
attrs={'data-dynamic-display-child-of': 'prefill$type',
|
|
'data-dynamic-display-value': prefill_types.get('geolocation')})
|
|
|
|
self._parsed = False
|
|
|
|
|
|
def _parse(self, request):
|
|
values = {}
|
|
type_ = self.get('type')
|
|
if type_:
|
|
values['type'] = type_
|
|
value = self.get('value_%s' % type_)
|
|
if value:
|
|
values['value'] = value
|
|
self.value = values or None
|
|
if values and values['type'] == 'formula' and values.get('value'):
|
|
try:
|
|
compile(values.get('value', ''), '<string>', 'eval')
|
|
except (SyntaxError, TypeError), e:
|
|
self.set_error(_('invalid expression: %s') % e)
|
|
if values and values['type'] == 'string' and Template.is_template_string(values.get('value')):
|
|
try:
|
|
Template(values.get('value'), raises=True)
|
|
except TemplateError as e:
|
|
self.set_error(str(e))
|
|
|
|
def render_content(self):
|
|
r = TemplateIO(html=True)
|
|
for widget in self.get_widgets():
|
|
r += widget.render_content()
|
|
return r.getvalue()
|
|
|
|
|
|
class Field(object):
|
|
id = None
|
|
type = None
|
|
varname = None
|
|
label = None
|
|
extra_css_class = None
|
|
convert_value_from_str = None
|
|
convert_value_to_str = None
|
|
convert_value_from_anything = None
|
|
in_listing = False
|
|
prefill = None
|
|
store_display_value = None
|
|
store_structured_value = None
|
|
condition = None
|
|
|
|
# flag a field for removal by AnonymiseWorkflowStatusItem
|
|
# can be overriden in field' settings
|
|
anonymise = True
|
|
stats = None
|
|
|
|
def __init__(self, **kwargs):
|
|
for k, v in kwargs.items():
|
|
setattr(self, k.replace('-', '_'), v)
|
|
|
|
@classmethod
|
|
def init(cls):
|
|
pass
|
|
|
|
@property
|
|
def unhtmled_label(self):
|
|
charset = get_publisher().site_charset
|
|
return HTMLParser().unescape(unicode(
|
|
re.sub('<.*?>', ' ', self.label), charset)).strip().encode(charset)
|
|
|
|
def get_admin_attributes(self):
|
|
return ['label', 'type', 'condition']
|
|
|
|
def export_to_json(self, include_id=False):
|
|
field = {}
|
|
if include_id:
|
|
extra_fields = ['id']
|
|
else:
|
|
extra_fields = []
|
|
for attribute in self.get_admin_attributes() + extra_fields:
|
|
if attribute in ('in_listing',):
|
|
continue
|
|
if hasattr(self, attribute) and getattr(self, attribute) is not None:
|
|
val = getattr(self, attribute)
|
|
field[attribute] = val
|
|
if not 'type' in field:
|
|
field['type'] = self.key
|
|
return field
|
|
|
|
def init_with_json(self, elem, include_id=False):
|
|
if include_id:
|
|
self.id = elem.get('id')
|
|
for attribute in self.get_admin_attributes():
|
|
if attribute in elem:
|
|
setattr(self, attribute, elem.get(attribute))
|
|
|
|
def export_to_xml(self, charset, include_id=False):
|
|
field = ET.Element('field')
|
|
if include_id:
|
|
extra_fields = ['id']
|
|
else:
|
|
extra_fields = []
|
|
for attribute in self.get_admin_attributes() + extra_fields:
|
|
if hasattr(self, '%s_export_to_xml' % attribute):
|
|
getattr(self, '%s_export_to_xml' % attribute)(field, charset,
|
|
include_id=include_id)
|
|
continue
|
|
if hasattr(self, attribute) and getattr(self, attribute) is not None:
|
|
val = getattr(self, attribute)
|
|
if type(val) is dict and not val:
|
|
continue
|
|
el = ET.SubElement(field, attribute)
|
|
if type(val) is dict:
|
|
for k, v in val.items():
|
|
if isinstance(v, str):
|
|
text_value = unicode(v, charset, 'replace')
|
|
else:
|
|
# field having non str value in dictionnary field must overload
|
|
# import_to_xml to handle import
|
|
text_value = unicode(v)
|
|
ET.SubElement(el, k).text = text_value
|
|
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, 'replace')
|
|
elif type(val) in (str, unicode):
|
|
if type(val) is unicode:
|
|
el.text = val
|
|
else:
|
|
el.text = unicode(val, charset, 'replace')
|
|
else:
|
|
el.text = str(val)
|
|
return field
|
|
|
|
def init_with_xml(self, elem, charset, include_id=False):
|
|
for attribute in self.get_admin_attributes():
|
|
el = elem.find(attribute)
|
|
if hasattr(self, '%s_init_with_xml' % attribute):
|
|
getattr(self, '%s_init_with_xml' % attribute)(el, charset,
|
|
include_id=include_id)
|
|
continue
|
|
if el is None:
|
|
continue
|
|
if list(el):
|
|
if type(getattr(self, attribute)) is list:
|
|
v = [x.text.encode(charset) for x in el]
|
|
elif type(getattr(self, attribute)) is dict:
|
|
v = {}
|
|
for e in el:
|
|
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, el.text == 'True')
|
|
elif type(getattr(self, attribute)) is int:
|
|
setattr(self, attribute, int(el.text.encode(charset)))
|
|
else:
|
|
setattr(self, attribute, el.text.encode(charset))
|
|
if include_id:
|
|
try:
|
|
self.id = elem.find('id').text.encode(charset)
|
|
except:
|
|
pass
|
|
|
|
def condition_init_with_xml(self, node, charset, include_id=False):
|
|
self.condition = None
|
|
if node is None:
|
|
return
|
|
if node.findall('type'):
|
|
self.condition = {
|
|
'type': node.find('type').text.encode(charset),
|
|
'value': node.find('value').text.encode(charset),
|
|
}
|
|
elif node.text:
|
|
self.condition = {'type': 'python', 'value': node.text.strip().encode(charset)}
|
|
|
|
def get_rst_view_value(self, value, indent=''):
|
|
return indent + self.get_view_value(value)
|
|
|
|
def get_csv_heading(self):
|
|
return []
|
|
|
|
def get_csv_value(self, element, **kwargs):
|
|
return []
|
|
|
|
def get_structured_value(self, data):
|
|
if not self.store_structured_value:
|
|
return None
|
|
return data.get('%s_structured' % self.id)
|
|
|
|
def get_prefill_value(self, user=None, force_string=True):
|
|
# returns a tuple with two items,
|
|
# 1. value[str], the value that will be used to prefill
|
|
# 2. verified[bool], a flag to know if this is a "verified" value
|
|
# (that will therefore be marked as readonly etc.)
|
|
t = self.prefill.get('type')
|
|
if t == 'string':
|
|
value = self.prefill.get('value')
|
|
if not Template.is_template_string(value):
|
|
return (value, False)
|
|
|
|
context = get_publisher().substitutions.get_context_variables()
|
|
try:
|
|
return (Template(value, autoescape=False, raises=True).render(context), False)
|
|
except TemplateError:
|
|
return (None, False)
|
|
|
|
elif t == 'user' and user:
|
|
x = self.prefill.get('value')
|
|
if x == 'email':
|
|
return (user.email, 'email' in (user.verified_fields or []))
|
|
elif user.form_data:
|
|
userform = user.get_formdef()
|
|
for userfield in userform.fields:
|
|
if userfield.id == x:
|
|
return (user.form_data.get(x),
|
|
str(userfield.id) in (user.verified_fields or []))
|
|
|
|
elif t == 'formula':
|
|
formula = self.prefill.get('value')
|
|
try:
|
|
ret = eval(formula,
|
|
get_publisher().get_global_eval_dict(),
|
|
get_publisher().substitutions.get_context_variables())
|
|
if isinstance(ret, datetime.time):
|
|
ret = misc.site_encode(django_date_format(ret, format='TIME_FORMAT'))
|
|
if isinstance(ret, datetime.date):
|
|
ret = ret.strftime(date_format())
|
|
if ret:
|
|
if force_string:
|
|
# prefilling is done with strings for most fields so
|
|
# we default to forcing the value as a string.
|
|
# (items field are prefilled with list of strings, and
|
|
# will get the native python object)
|
|
ret = str(ret)
|
|
return (ret, False)
|
|
except:
|
|
pass
|
|
|
|
elif t == 'geolocation':
|
|
return (None, False)
|
|
|
|
return (None, False)
|
|
|
|
def get_prefill_attributes(self):
|
|
t = self.prefill.get('type')
|
|
if t == 'geolocation':
|
|
return {'geolocation': self.prefill.get('value')}
|
|
return None
|
|
|
|
def feed_session(self, value, display_value):
|
|
pass
|
|
|
|
def migrate(self):
|
|
return False
|
|
|
|
def evaluate_condition(self, dict_vars, formdef, condition):
|
|
return PageCondition(condition, {'dict_vars': dict_vars, 'formdef': formdef}).evaluate()
|
|
|
|
def is_visible(self, dict, formdef):
|
|
try:
|
|
return self.evaluate_condition(dict, formdef, self.condition)
|
|
except RuntimeError:
|
|
return True
|
|
|
|
def get_condition_varnames(self):
|
|
return re.findall(r'\bform[_\.]var[_\.]([a-zA-Z0-9_]+?)(?:_raw|\b)', self.condition['value'])
|
|
|
|
def has_live_conditions(self, formdef):
|
|
varnames = self.get_condition_varnames()
|
|
if not varnames:
|
|
return False
|
|
field_position = formdef.fields.index(self)
|
|
# rewind to field page
|
|
for field_position in range(field_position, -1, -1):
|
|
if formdef.fields[field_position].type == 'page':
|
|
break
|
|
else:
|
|
field_position = -1 # form with no page
|
|
# start from there
|
|
for field in formdef.fields[field_position+1:]:
|
|
if field.type == 'page':
|
|
# stop at next page
|
|
break
|
|
if field.varname in varnames:
|
|
return True
|
|
return False
|
|
|
|
|
|
class WidgetField(Field):
|
|
hint = None
|
|
required = True
|
|
in_listing = True
|
|
extra_attributes = []
|
|
prefill = {}
|
|
|
|
widget_class = None
|
|
|
|
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)
|
|
if self.hint and self.hint.startswith('<'):
|
|
hint = htmltext(self.hint)
|
|
else:
|
|
hint = self.hint
|
|
form.add(self.widget_class, 'f%s' % self.id, title=self.label, hint=hint, **kwargs)
|
|
widget = form.get_widget('f%s' % self.id)
|
|
widget.field = self
|
|
if self.extra_css_class:
|
|
if hasattr(widget, 'extra_css_class') and widget.extra_css_class:
|
|
widget.extra_css_class = '%s %s' % (widget.extra_css_class, self.extra_css_class)
|
|
else:
|
|
widget.extra_css_class = self.extra_css_class
|
|
if self.varname:
|
|
widget.div_id = 'var_%s' % self.varname
|
|
return widget
|
|
|
|
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)
|
|
|
|
if self.widget_class is StringWidget and not 'size' in kwargs and value:
|
|
# set a size if there is not one already defined, this will be for
|
|
# example the case with ItemField
|
|
kwargs['size'] = len(value)
|
|
|
|
form.add(self.widget_class, self.field_key, title = self.label,
|
|
value = value, readonly = 'readonly', **kwargs)
|
|
widget = form.get_widget(self.field_key)
|
|
if self.extra_css_class:
|
|
if hasattr(widget, 'extra_css_class') and widget.extra_css_class:
|
|
widget.extra_css_class = '%s %s' % (widget.extra_css_class, self.extra_css_class)
|
|
else:
|
|
widget.extra_css_class = self.extra_css_class
|
|
|
|
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(VarnameWidget, 'varname', title=_('Identifier'),
|
|
value=self.varname, size=30, advanced=(not self.varname),
|
|
hint=_('This is used as suffix for variable names.'))
|
|
form.add(TextWidget, 'hint', title = _('Hint'), value = self.hint,
|
|
cols=60, rows=3)
|
|
form.add(CheckboxWidget, 'in_listing', title = _('Display in listings'),
|
|
value = self.in_listing, advanced=True)
|
|
form.add(StringWidget, 'extra_css_class', title = _('Extra classes for CSS styling'),
|
|
value=self.extra_css_class, size=30, advanced=(not self.extra_css_class))
|
|
prefill_in_advanced = (not self.prefill or self.prefill.get('type') == 'none')
|
|
if prefill_in_advanced and (get_request().form.get('prefill$apply') or
|
|
get_request().form.get('prefill$value')):
|
|
# check if the apply button has been clicked, and get the field out
|
|
# of the "advanced parameters" section if that's the case.
|
|
prefill_in_advanced = False
|
|
form.add(PrefillSelectionWidget, 'prefill', title=_('Prefill'),
|
|
value=self.prefill, advanced=prefill_in_advanced,
|
|
field=self)
|
|
form.add(ConditionWidget, 'condition', title=_('Display Condition'), value=self.condition,
|
|
required=False, size=50, django_only=True,
|
|
advanced=not(bool(self.condition)))
|
|
if 'anonymise' in self.get_admin_attributes():
|
|
# override anonymise flag default value
|
|
form.add(CheckboxWidget, 'anonymise', title=_('Anonymise'),
|
|
value=self.anonymise, advanced=True,
|
|
hint=_('Marks the field data for removal in the anonymisation processes.'))
|
|
|
|
def check_admin_form(self, form):
|
|
return
|
|
|
|
def get_admin_attributes(self):
|
|
return Field.get_admin_attributes(self) + ['required', 'hint',
|
|
'varname', 'in_listing',
|
|
'extra_css_class', 'prefill']
|
|
|
|
def get_csv_heading(self):
|
|
return [self.label]
|
|
|
|
def get_view_value(self, value):
|
|
return str(value) if value else ''
|
|
|
|
def get_view_short_value(self, value, max_len = 30):
|
|
return self.get_view_value(value)
|
|
|
|
def get_csv_value(self, element, **kwargs):
|
|
if self.convert_value_to_str:
|
|
return [self.convert_value_to_str(element)]
|
|
return [element]
|
|
|
|
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):
|
|
extra_attributes = ' data-field-id="%s"' % self.id
|
|
if self.extra_css_class:
|
|
extra_attributes += ' class="%s"' % self.extra_css_class
|
|
widget = HtmlWidget(htmltext('<h3%s>%s</h3>' % (extra_attributes, self.label)))
|
|
form.widgets.append(widget)
|
|
return widget
|
|
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 classes for CSS styling'),
|
|
value=self.extra_css_class, size=30, advanced=(not self.extra_css_class))
|
|
form.add(ConditionWidget, 'condition', title=_('Display Condition'), value=self.condition,
|
|
required=False, size=50, django_only=True,
|
|
advanced=not(bool(self.condition)))
|
|
|
|
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):
|
|
extra_attributes = ' data-field-id="%s"' % self.id
|
|
if self.extra_css_class:
|
|
extra_attributes += ' class="%s"' % self.extra_css_class
|
|
widget = HtmlWidget(htmltext('<h4%s>%s</h4>' % (extra_attributes, self.label)))
|
|
form.widgets.append(widget)
|
|
return widget
|
|
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):
|
|
tag_attributes = 'data-field-id="%s" class="comment-field %s"' % (
|
|
self.id, self.extra_css_class or '')
|
|
|
|
if '\n\n' in self.label:
|
|
label = '<p>' + re.sub('\n\n+', '</p>\n<p>', self.label) + '</p>'
|
|
else:
|
|
label = self.label
|
|
|
|
import wcs.workflows
|
|
label = wcs.workflows.template_on_html_string(label)
|
|
|
|
enclosing_tag = 'p'
|
|
for block_level in ('p', 'div', 'ul', 'ol', 'hr'):
|
|
if '<' + block_level in label:
|
|
enclosing_tag = 'div'
|
|
break
|
|
|
|
widget = HtmlWidget(htmltext('<%s %s>%s</%s>' % (
|
|
enclosing_tag, tag_attributes, label, enclosing_tag)))
|
|
form.widgets.append(widget)
|
|
return widget
|
|
|
|
def add_to_view_form(self, *args):
|
|
pass
|
|
|
|
def fill_admin_form(self, form):
|
|
if self.label and (not self.label.startswith('<') and (
|
|
'\n\n' in self.label or '[end]' in self.label)):
|
|
form.add(TextWidget, 'label', title=_('Label'), value=self.label,
|
|
required=True, cols=70, rows=3, render_br=False)
|
|
else:
|
|
form.add(WysiwygTextWidget, 'label', title=_('Label'), value=self.label,
|
|
required=True)
|
|
form.add(StringWidget, 'extra_css_class', title = _('Extra classes for CSS styling'),
|
|
value=self.extra_css_class, size=30, advanced=(not self.extra_css_class))
|
|
form.add(ConditionWidget, 'condition', title=_('Display Condition'), value=self.condition,
|
|
required=False, size=50, django_only=True,
|
|
advanced=not(bool(self.condition)))
|
|
|
|
def get_admin_attributes(self):
|
|
return Field.get_admin_attributes(self) + ['extra_css_class']
|
|
|
|
|
|
register_field_class(CommentField)
|
|
|
|
def is_datasource_advanced(value):
|
|
data_source_in_advanced = (not value)
|
|
if data_source_in_advanced and (get_request().form.get('data_source$apply') or
|
|
get_request().form.get('data_source$value')):
|
|
data_source_in_advanced = False
|
|
return data_source_in_advanced
|
|
|
|
|
|
class StringField(WidgetField):
|
|
key = 'string'
|
|
description = N_('Text (line)')
|
|
|
|
widget_class = WcsExtraStringWidget
|
|
size = None
|
|
extra_attributes = ['size']
|
|
validation = None
|
|
data_source = {}
|
|
|
|
def perform_more_widget_changes(self, form, kwargs, edit=True):
|
|
if self.data_source:
|
|
real_data_source = data_sources.get_real(self.data_source)
|
|
if real_data_source.get('type') == 'jsonp':
|
|
kwargs['url'] = real_data_source.get('value')
|
|
self.widget_class = AutocompleteStringWidget
|
|
|
|
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, advanced=(not self.validation))
|
|
form.add(data_sources.DataSourceSelectionWidget, 'data_source',
|
|
value=self.data_source,
|
|
title=_('Data Source'),
|
|
hint=_('This will allow autocompletion from an external source.'),
|
|
advanced=is_datasource_advanced(self.data_source),
|
|
required=False)
|
|
|
|
def get_admin_attributes(self):
|
|
return WidgetField.get_admin_attributes(self) + ['size', 'validation', 'data_source', 'anonymise']
|
|
|
|
@classmethod
|
|
def convert_value_from_anything(cls, value):
|
|
if value is None:
|
|
return None
|
|
return str(value)
|
|
|
|
register_field_class(StringField)
|
|
|
|
|
|
class TextField(WidgetField):
|
|
key = 'text'
|
|
description = N_('Long Text')
|
|
|
|
widget_class = TextWidget
|
|
cols = None
|
|
rows = None
|
|
pre = False
|
|
maxlength = None
|
|
extra_attributes = ['cols', 'rows', 'maxlength']
|
|
|
|
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(StringWidget, 'maxlength',
|
|
title=_('Maximum number of characters'),
|
|
value = self.maxlength)
|
|
form.add(CheckboxWidget, 'pre', title = _('Preformatted Text'),
|
|
value=self.pre, advanced=not(self.pre))
|
|
|
|
def get_admin_attributes(self):
|
|
return WidgetField.get_admin_attributes(self) + ['cols', 'rows', 'pre', 'maxlength']
|
|
|
|
def get_view_value(self, value):
|
|
if self.pre:
|
|
return htmltext('<pre>') + value + htmltext('</pre>')
|
|
else:
|
|
try:
|
|
return htmltext('<p>') + htmltext('\n').join(
|
|
[(x or htmltext('</p><p>')) for x in value.splitlines()]) + \
|
|
htmltext('</p>')
|
|
except:
|
|
return ''
|
|
|
|
def get_view_short_value(self, value, max_len = 30):
|
|
return ellipsize(str(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)
|
|
|
|
def get_rst_view_value(self, value, indent=''):
|
|
return indent + value
|
|
|
|
register_field_class(EmailField)
|
|
|
|
|
|
class BoolField(WidgetField):
|
|
key = 'bool'
|
|
description = N_('Check Box (single choice)')
|
|
|
|
widget_class = CheckboxWidget
|
|
required = False
|
|
anonymise = False
|
|
|
|
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=str(value))
|
|
if value and not value == 'False':
|
|
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 ''
|
|
|
|
def convert_value_from_str(self, value):
|
|
if value is None:
|
|
return None
|
|
if value in (True, 'True', _('Yes'), 'yes'):
|
|
return True
|
|
return False
|
|
|
|
def convert_value_to_str(self, value):
|
|
if value is True:
|
|
return 'True'
|
|
elif value is False:
|
|
return 'False'
|
|
return value
|
|
|
|
def stats(self, values):
|
|
no_records = len(values)
|
|
if not no_records:
|
|
return
|
|
r = TemplateIO(html = True)
|
|
r += htmltext('<table class="stats">')
|
|
r += htmltext('<thead><tr><th colspan="4">')
|
|
r += self.label
|
|
r += htmltext('</th></tr></thead>')
|
|
options = (True, False)
|
|
r += htmltext('<tbody>')
|
|
for o in options:
|
|
r += htmltext('<tr>')
|
|
r += htmltext('<td class="label">')
|
|
if o is True:
|
|
r += _('Yes')
|
|
value = True
|
|
else:
|
|
r += _('No')
|
|
value = False
|
|
r += htmltext('</td>')
|
|
no = len([None for x in values if self.convert_value_from_str(x.data.get(self.id)) is value])
|
|
|
|
r += htmltext('<td class="percent">')
|
|
r += htmltext(' %.2f %%') % (100.*no/no_records)
|
|
r += htmltext('</td>')
|
|
r += htmltext('<td class="total">')
|
|
r += '(%d/%d)' % (no, no_records)
|
|
r += htmltext('</td>')
|
|
r += htmltext('</tr>')
|
|
r += htmltext('<tr>')
|
|
r += htmltext('<td class="bar" colspan="3">')
|
|
r += htmltext('<span style="width: %d%%"></span>' % (100*no/no_records))
|
|
r += htmltext('</td>')
|
|
r += htmltext('</tr>')
|
|
r += htmltext('</tbody>')
|
|
r += htmltext('</table>')
|
|
return r.getvalue()
|
|
|
|
register_field_class(BoolField)
|
|
|
|
|
|
class FileField(WidgetField):
|
|
key = 'file'
|
|
description = N_('File Upload')
|
|
document_type = None
|
|
max_file_size = None
|
|
allow_portfolio_picking = True
|
|
|
|
widget_class = FileWithPreviewWidget
|
|
extra_attributes = ['file_type', 'max_file_size', 'allow_portfolio_picking']
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(FileField, self).__init__(*args, **kwargs)
|
|
self.document_type = self.document_type or {}
|
|
|
|
@property
|
|
def file_type(self):
|
|
return (self.document_type or {}).get('mimetypes', [])
|
|
|
|
def fill_admin_form(self, form):
|
|
WidgetField.fill_admin_form(self, form)
|
|
document_types = self.get_document_types()
|
|
cur_dt = self.document_type or {}
|
|
# SingleSelectWidget compare the value and not the keys, so if we want
|
|
# the current value not to be hidden, we must reset it with the corresponding
|
|
# value from settings based on the 'id'
|
|
document_type_id = cur_dt.get('id')
|
|
if document_type_id in document_types \
|
|
and cur_dt != document_types[document_type_id]:
|
|
cur_dt = document_types[document_type_id]
|
|
options = [(None, '---', {})]
|
|
options += [(doc_type, doc_type['label'], key) for key, doc_type in document_types.iteritems()]
|
|
form.add(SingleSelectWidget, 'document_type', title=_('File type suggestion'),
|
|
value=cur_dt, options=options,
|
|
advanced=not(cur_dt))
|
|
form.add(FileSizeWidget, 'max_file_size', title=_('Max file size'),
|
|
value=self.max_file_size,
|
|
advanced=not(self.max_file_size))
|
|
if portfolio.has_portfolio():
|
|
form.add(CheckboxWidget, 'allow_portfolio_picking',
|
|
title=_('Allow user to pick a file from a portfolio'),
|
|
value=self.allow_portfolio_picking,
|
|
advanced=(self.allow_portfolio_picking is True))
|
|
|
|
def get_admin_attributes(self):
|
|
return WidgetField.get_admin_attributes(self) + [
|
|
'document_type', 'max_file_size', 'allow_portfolio_picking']
|
|
|
|
@classmethod
|
|
def convert_value_from_anything(cls, value):
|
|
if value is None:
|
|
return None
|
|
if hasattr(value, 'base_filename'):
|
|
upload = PicklableUpload(value.base_filename,
|
|
value.content_type or 'application/octet-stream')
|
|
upload.receive([value.get_content()])
|
|
return upload
|
|
from wcs.workflows import NamedAttachmentsSubstitutionProxy
|
|
if isinstance(value, NamedAttachmentsSubstitutionProxy):
|
|
upload = PicklableUpload(value.filename, value.content_type)
|
|
upload.receive([value.content])
|
|
return upload
|
|
if isinstance(value, dict):
|
|
# if value is a dictionary we expect it to have a content or
|
|
# b64_content key and a filename keys and an optional
|
|
# content_type key.
|
|
if 'b64_content' in value:
|
|
value['content'] = base64.decodestring(value['b64_content'])
|
|
if 'filename' in value and 'content' in value:
|
|
content_type = value.get('content_type') or 'application/octet-stream'
|
|
if content_type.startswith('text/'):
|
|
charset = 'utf-8'
|
|
else:
|
|
charset = None
|
|
upload = PicklableUpload(value['filename'], content_type, charset)
|
|
upload.receive([value['content']])
|
|
return upload
|
|
raise ValueError('invalid data for file type (%r)' % value)
|
|
|
|
def get_view_short_value(self, value, max_len=30):
|
|
return self.get_view_value(value, include_image_thumbnail=False)
|
|
|
|
def get_view_value(self, value, include_image_thumbnail=True):
|
|
t = TemplateIO(html=True)
|
|
t += htmltext('<div class="file-field">')
|
|
t += htmltext('<a download="%s" href="[download]?f=%s">') % (value.base_filename, self.id)
|
|
if can_thumbnail(value.content_type):
|
|
t += htmltext('<img alt="" src="[download]?f=%s&thumbnail=1"/>') % self.id
|
|
t += htmltext('<span>%s</span>') % value
|
|
t += htmltext('</a></div>')
|
|
return t.getvalue()
|
|
|
|
def get_csv_value(self, value, **kwargs):
|
|
return [str(value) if value else '']
|
|
|
|
def get_json_value(self, value):
|
|
return {
|
|
'field_id': self.id,
|
|
'filename': value.base_filename,
|
|
'content_type': value.content_type or 'application/octet-stream',
|
|
'content': base64.b64encode(value.get_content())
|
|
}
|
|
|
|
def from_json_value(self, value):
|
|
if value and 'filename' in value and 'content' in value:
|
|
content = base64.b64decode(value['content'])
|
|
content_type = value.get('content_type', 'application/octet-stream')
|
|
if content_type.startswith('text/'):
|
|
charset = 'utf-8'
|
|
else:
|
|
charset = None
|
|
upload = PicklableUpload(value['filename'], content_type, charset)
|
|
upload.receive([content])
|
|
return upload
|
|
return None
|
|
|
|
def perform_more_widget_changes(self, form, kwargs, edit = True):
|
|
if not edit:
|
|
value = get_request().get_field(self.field_key)
|
|
if value and hasattr(value, 'token'):
|
|
get_request().form[self.field_key + '$token'] = value.token
|
|
|
|
def get_document_types(self):
|
|
document_types = {
|
|
'_audio': {
|
|
'label': _('Sound files'),
|
|
'mimetypes': ['audio/*'],
|
|
},
|
|
'_video': {
|
|
'label': _('Video files'),
|
|
'mimetypes': ['video/*'],
|
|
},
|
|
'_image': {
|
|
'label': _('Image files'),
|
|
'mimetypes': ['image/*'],
|
|
}
|
|
}
|
|
# Local document types
|
|
document_types.update(get_cfg('filetypes', {}))
|
|
for key, document_type in document_types.iteritems():
|
|
document_type['id'] = key
|
|
# add current file type if it does not exist anymore in the settings
|
|
cur_dt = self.document_type or {}
|
|
if cur_dt and cur_dt['id'] not in document_types:
|
|
document_types[cur_dt['id']] = cur_dt
|
|
return document_types
|
|
|
|
def migrate(self):
|
|
if 'file_type' in self.__dict__:
|
|
self.document_type = {}
|
|
if self.__dict__['file_type']:
|
|
file_type = self.__dict__['file_type']
|
|
document_types = self.get_document_types()
|
|
parts = []
|
|
for key, value in document_types.iteritems():
|
|
if file_type == value.get('mimetypes'):
|
|
self.document_type = value.copy()
|
|
self.document_type['id'] = key
|
|
break
|
|
if not value.get('mimetypes'):
|
|
continue
|
|
if ','.join(value['mimetypes']) in file_type:
|
|
parts.append(value['label'])
|
|
else:
|
|
# self.file_type is a combination of file type, we create a
|
|
# virtual one from them
|
|
if parts and len(parts) > 1:
|
|
label = ', '.join(parts)
|
|
else:
|
|
label = ','.join(file_type)
|
|
self.document_type = {
|
|
'id': '_legacy',
|
|
'label': label,
|
|
'mimetypes': file_type,
|
|
}
|
|
del self.__dict__['file_type']
|
|
return True
|
|
return False
|
|
|
|
def export_to_xml(self, charset, include_id=False):
|
|
# convert some sub-fields to strings as export_to_xml() only supports
|
|
# dictionnaries with strings values
|
|
if self.document_type and self.document_type.get('mimetypes'):
|
|
old_value = self.document_type['mimetypes']
|
|
self.document_type['mimetypes'] = '|'.join(self.document_type['mimetypes'])
|
|
result = super(FileField, self).export_to_xml(charset, include_id=include_id)
|
|
if self.document_type and self.document_type.get('mimetypes'):
|
|
self.document_type['mimetypes'] = old_value
|
|
return result
|
|
|
|
def init_with_xml(self, element, charset, include_id=False):
|
|
super(FileField, self).init_with_xml(element, charset, include_id=include_id)
|
|
# translate fields flattened to strings
|
|
if self.document_type and self.document_type.get('mimetypes'):
|
|
self.document_type['mimetypes'] = self.document_type['mimetypes'].split('|')
|
|
if self.document_type and self.document_type.get('fargo'):
|
|
self.document_type['fargo'] = self.document_type['fargo'] == 'True'
|
|
|
|
|
|
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']
|
|
|
|
@classmethod
|
|
def convert_value_from_anything(cls, value):
|
|
if value is None or value == '':
|
|
return None
|
|
date_value = evalutils.make_date(value).timetuple() # could raise ValueError
|
|
return date_value
|
|
|
|
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_form(self, form, value=None):
|
|
if value and type(value) is not str:
|
|
value = self.convert_value_to_str(value)
|
|
return WidgetField.add_to_form(self, form, value=value)
|
|
|
|
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
|
|
|
|
def get_json_value(self, value):
|
|
try:
|
|
return strftime('%Y-%m-%d', value)
|
|
except TypeError:
|
|
return ''
|
|
|
|
def from_json_value(self, value):
|
|
try:
|
|
return time.strptime(value, '%Y-%m-%d')
|
|
except (TypeError, ValueError):
|
|
return None
|
|
|
|
|
|
register_field_class(DateField)
|
|
|
|
|
|
def item_items_stats(field, values):
|
|
if field.data_source:
|
|
options = data_sources.get_items(field.data_source)
|
|
else:
|
|
options = field.items or []
|
|
if len(options) == 0:
|
|
return None
|
|
no_records = len(values)
|
|
if no_records == 0:
|
|
return None
|
|
r = TemplateIO(html = True)
|
|
r += htmltext('<table class="stats">')
|
|
r += htmltext('<thead><tr><th colspan="4">')
|
|
r += field.label
|
|
r += htmltext('</th></tr></thead>')
|
|
r += htmltext('<tbody>')
|
|
for option in options:
|
|
if type(option) in (tuple, list):
|
|
option_label = option[1]
|
|
option_value = str(option[0])
|
|
else:
|
|
option_label = option
|
|
option_value = option
|
|
if field.type == 'item':
|
|
no = len([None for x in values if x.data.get(field.id) == option_value])
|
|
else:
|
|
no = len([None for x in values if option_value in (x.data.get(field.id) or [])])
|
|
r += htmltext('<tr>')
|
|
r += htmltext('<td class="label">')
|
|
r += option_label
|
|
r += htmltext('</td>')
|
|
|
|
r += htmltext('<td class="percent">')
|
|
r += htmltext(' %.2f %%') % (100.*no/no_records)
|
|
r += htmltext('</td>')
|
|
r += htmltext('<td class="total">')
|
|
r += '(%d/%d)' % (no, no_records)
|
|
r += htmltext('</td>')
|
|
r += htmltext('</tr>')
|
|
r += htmltext('<tr>')
|
|
r += htmltext('<td class="bar" colspan="3">')
|
|
r += htmltext('<span style="width: %d%%"></span>' % (100*no/no_records))
|
|
r += htmltext('</td>')
|
|
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
|
|
anonymise = False
|
|
widget_class = SingleSelectHintWidget
|
|
data_source = {}
|
|
in_filters = False
|
|
display_disabled_items = False
|
|
|
|
def __init__(self, **kwargs):
|
|
self.items = []
|
|
WidgetField.__init__(self, **kwargs)
|
|
|
|
def get_options(self, mode=None):
|
|
if self.data_source:
|
|
return [x[:3] for x in data_sources.get_items(self.data_source, mode=mode)]
|
|
if self.items:
|
|
return [(x, x) for x in self.items]
|
|
return []
|
|
|
|
def perform_more_widget_changes(self, form, kwargs, edit = True):
|
|
real_data_source = data_sources.get_real(self.data_source)
|
|
if real_data_source and real_data_source.get('type') == 'jsonp':
|
|
kwargs['url'] = real_data_source.get('value')
|
|
self.widget_class = JsonpSingleSelectWidget
|
|
elif self.items:
|
|
kwargs['options'] = self.get_options()
|
|
elif self.data_source:
|
|
items = data_sources.get_items(self.data_source,
|
|
include_disabled=self.display_disabled_items)
|
|
kwargs['options'] = [x[:3] for x in items if not x[-1].get('disabled')]
|
|
kwargs['options_with_attributes'] = items[:]
|
|
else:
|
|
kwargs['options'] = self.get_options()
|
|
if not kwargs.get('options'):
|
|
kwargs['options'] = [(None, '---')]
|
|
if self.show_as_radio:
|
|
self.widget_class = RadiobuttonsWidget
|
|
if type(kwargs['options'][0]) is str:
|
|
first_items = [x for x in kwargs['options'][:3]]
|
|
else:
|
|
first_items = [x[1] for x in kwargs['options'][:3]]
|
|
length_first_items = sum([len(x) for x in first_items])
|
|
if len(kwargs['options']) > 3 or length_first_items > 40:
|
|
# TODO: absence/presence of delimitor should be an option
|
|
kwargs['delim'] = htmltext('<br />')
|
|
|
|
def get_display_value(self, value):
|
|
real_value = value
|
|
label_value = str(value or '')
|
|
kwargs = {}
|
|
self.perform_more_widget_changes(None, kwargs, False)
|
|
real_data_source = data_sources.get_real(self.data_source)
|
|
if real_data_source and real_data_source.get('type') == 'jsonp':
|
|
if not get_session().jsonp_display_values:
|
|
get_session().jsonp_display_values = {}
|
|
label_value = get_session().jsonp_display_values.get(
|
|
'%s_%s' % (real_data_source.get('value'), value))
|
|
elif type(kwargs['options'][0]) in (tuple, list):
|
|
if len(kwargs['options'][0]) == 2:
|
|
for key, value in kwargs['options']:
|
|
if str(key) == str(real_value):
|
|
label_value = value
|
|
break
|
|
elif len(kwargs['options'][0]) == 3:
|
|
for key, value, key2 in kwargs['options']:
|
|
if str(key) == str(real_value):
|
|
label_value = value
|
|
break
|
|
return label_value
|
|
|
|
def add_to_view_form(self, form, value = None):
|
|
real_value = value
|
|
label_value = self.get_display_value(value)
|
|
self.field_key = 'f%s' % self.id
|
|
|
|
get_request().form[self.field_key + '_label'] = label_value # :/
|
|
form.add(StringWidget, self.field_key + '_label', title = self.label,
|
|
value=label_value, readonly='readonly', size=len(label_value or '')+2)
|
|
form.add(HiddenWidget, self.field_key, value=real_value)
|
|
widget = form.get_widget(self.field_key + '_label')
|
|
if self.extra_css_class:
|
|
if hasattr(widget, 'extra_css_class') and widget.extra_css_class:
|
|
widget.extra_css_class = '%s %s' % (widget.extra_css_class, self.extra_css_class)
|
|
else:
|
|
widget.extra_css_class = self.extra_css_class
|
|
|
|
def store_display_value(self, data, field_id):
|
|
value = data.get(field_id)
|
|
kwargs = {}
|
|
self.perform_more_widget_changes(None, kwargs, False)
|
|
real_data_source = data_sources.get_real(self.data_source)
|
|
if real_data_source and real_data_source.get('type') == 'jsonp':
|
|
if get_request():
|
|
display_value = get_request().form.get('f%s_display' % field_id)
|
|
if display_value is None:
|
|
if not get_session().jsonp_display_values:
|
|
get_session().jsonp_display_values = {}
|
|
display_value = get_session().jsonp_display_values.get(
|
|
'%s_%s' % (real_data_source.get('value'), value))
|
|
else:
|
|
if not get_session().jsonp_display_values:
|
|
get_session().jsonp_display_values = {}
|
|
get_session().jsonp_display_values[
|
|
'%s_%s' % (real_data_source.get('value'), value)] = display_value
|
|
return display_value
|
|
elif type(kwargs['options'][0]) in (tuple, list):
|
|
if len(kwargs['options'][0]) == 2:
|
|
for key, option_value in kwargs['options']:
|
|
if str(key) == str(value):
|
|
return option_value
|
|
elif len(kwargs['options'][0]) == 3:
|
|
for key, option_value, key_repeat in kwargs['options']:
|
|
if str(key) == str(value):
|
|
return option_value
|
|
return str(value)
|
|
|
|
def store_structured_value(self, data, field_id):
|
|
value = data.get(field_id)
|
|
if not self.data_source:
|
|
return
|
|
structured_options = data_sources.get_structured_items(self.data_source)
|
|
if not structured_options:
|
|
return
|
|
if not set(structured_options[0].keys()) != set(['id', 'text']):
|
|
return
|
|
for structured_option in structured_options:
|
|
if str(structured_option.get('id')) == str(value):
|
|
return structured_option
|
|
return None
|
|
|
|
def fill_admin_form(self, form):
|
|
WidgetField.fill_admin_form(self, form)
|
|
form.add(CheckboxWidget, 'in_filters', title=_('Display in default filters'),
|
|
value=self.in_filters, advanced=True)
|
|
form.add(CheckboxWidget, 'show_as_radio', title = _('Show as radio buttons'),
|
|
value = self.show_as_radio)
|
|
form.add(WidgetList, 'items', title = _('Items'), element_type = StringWidget,
|
|
value = self.items, required = False,
|
|
element_kwargs = {'render_br': False, 'size': 50},
|
|
add_element_label = _('Add item'))
|
|
form.add(data_sources.DataSourceSelectionWidget, 'data_source',
|
|
value=self.data_source,
|
|
title=_('Data Source'),
|
|
hint=_('This will get the available items from an external source.'),
|
|
required=False,
|
|
advanced=is_datasource_advanced(self.data_source))
|
|
form.add(CheckboxWidget, 'display_disabled_items',
|
|
title=_('Display disabled items'),
|
|
value=self.display_disabled_items,
|
|
advanced=not(self.display_disabled_items))
|
|
|
|
def get_admin_attributes(self):
|
|
return WidgetField.get_admin_attributes(self) + ['items',
|
|
'show_as_radio', 'data_source', 'in_filters', 'anonymise',
|
|
'display_disabled_items']
|
|
|
|
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)
|
|
|
|
def feed_session(self, value, display_value):
|
|
real_data_source = data_sources.get_real(self.data_source)
|
|
if real_data_source and real_data_source.get('type') == 'jsonp':
|
|
if not get_session().jsonp_display_values:
|
|
get_session().jsonp_display_values = {}
|
|
get_session().jsonp_display_values[
|
|
'%s_%s' % (real_data_source.get('value'), value)] = display_value
|
|
|
|
def get_csv_heading(self):
|
|
headings = [self.label]
|
|
if self.data_source:
|
|
headings.append('')
|
|
return headings
|
|
|
|
def get_csv_value(self, value, display_value=None, **kwargs):
|
|
values = [value]
|
|
if self.data_source:
|
|
values.append(display_value)
|
|
return values
|
|
|
|
register_field_class(ItemField)
|
|
|
|
|
|
class ItemsField(WidgetField):
|
|
key = 'items'
|
|
description = N_('Multiple choice list')
|
|
|
|
items = []
|
|
max_choices = 0
|
|
data_source = {}
|
|
in_filters = False
|
|
display_disabled_items = False
|
|
|
|
widget_class = CheckboxesWidget
|
|
|
|
_cached_data_source = None
|
|
|
|
def __init__(self, **kwargs):
|
|
self.items = []
|
|
WidgetField.__init__(self, **kwargs)
|
|
|
|
def get_options(self):
|
|
if self.data_source:
|
|
if self._cached_data_source:
|
|
return self._cached_data_source
|
|
self._cached_data_source = [x[:3] for x in data_sources.get_items(self.data_source)]
|
|
return self._cached_data_source[:]
|
|
elif self.items:
|
|
return [(x, x) for x in self.items]
|
|
else:
|
|
return []
|
|
|
|
def perform_more_widget_changes(self, form, kwargs, edit = True):
|
|
kwargs['options'] = self.get_options()
|
|
kwargs['max_choices'] = self.max_choices
|
|
if self.data_source:
|
|
items = data_sources.get_items(self.data_source,
|
|
include_disabled=self.display_disabled_items)
|
|
kwargs['options'] = [x[:3] for x in items if not x[-1].get('disabled')]
|
|
kwargs['options_with_attributes'] = items[:]
|
|
|
|
if len(kwargs['options']) > 3:
|
|
kwargs['inline'] = False
|
|
|
|
def fill_admin_form(self, form):
|
|
WidgetField.fill_admin_form(self, form)
|
|
form.add(CheckboxWidget, 'in_filters', title=_('Display in default filters'),
|
|
value=self.in_filters, advanced=True)
|
|
form.add(WidgetList, 'items', title = _('Items'), element_type = StringWidget,
|
|
value = self.items, required = False,
|
|
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)
|
|
form.add(data_sources.DataSourceSelectionWidget, 'data_source',
|
|
value=self.data_source,
|
|
allow_jsonp=False,
|
|
title=_('Data Source'),
|
|
hint=_('This will get the available items from an external source.'),
|
|
required=False,
|
|
advanced=is_datasource_advanced(self.data_source))
|
|
form.add(CheckboxWidget, 'display_disabled_items',
|
|
title=_('Display disabled items'),
|
|
value=self.display_disabled_items,
|
|
advanced=not(self.display_disabled_items))
|
|
|
|
def get_admin_attributes(self):
|
|
return WidgetField.get_admin_attributes(self) + ['items',
|
|
'max_choices', 'data_source', 'in_filters', 'anonymise',
|
|
'display_disabled_items']
|
|
|
|
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_prefill_value(self, user=None, force_string=True):
|
|
return super(ItemsField, self).get_prefill_value(user=user, force_string=False)
|
|
|
|
def convert_value_to_str(self, value):
|
|
return value
|
|
|
|
def get_view_value(self, value):
|
|
if type(value) is str: # == display_value
|
|
return value
|
|
if value:
|
|
try:
|
|
return ', '.join([(x) for x in value])
|
|
except TypeError:
|
|
pass
|
|
return ''
|
|
|
|
def stats(self, values):
|
|
return item_items_stats(self, values)
|
|
|
|
def get_csv_heading(self):
|
|
labels = [self.label]
|
|
if self.max_choices:
|
|
labels.extend(['']*(self.max_choices-1))
|
|
elif len(self.get_options()):
|
|
labels.extend(['']*(len(self.get_options())-1))
|
|
return labels
|
|
|
|
def get_csv_value(self, value, **kwargs):
|
|
values = []
|
|
for one_value in value:
|
|
values.append(one_value)
|
|
if self.max_choices:
|
|
nb_columns = self.max_choices
|
|
elif len(self.get_options()):
|
|
nb_columns = len(self.get_options())
|
|
else:
|
|
nb_columns = 1
|
|
|
|
if len(values) > nb_columns:
|
|
# this would happen if max_choices is set after forms were already
|
|
# filled with more values
|
|
values = values[:nb_columns]
|
|
elif len(values) < nb_columns:
|
|
values.extend(['']*(nb_columns-len(values)))
|
|
return values
|
|
|
|
def store_display_value(self, data, field_id):
|
|
kwargs = {}
|
|
options = self.get_options()
|
|
choices = []
|
|
for choice in data.get(field_id) or []:
|
|
if type(options[0]) is str:
|
|
choices.append(choice)
|
|
elif type(options[0]) in (tuple, list):
|
|
if len(options[0]) == 2:
|
|
for key, option_value in options:
|
|
if str(key) == str(choice):
|
|
choices.append(option_value)
|
|
break
|
|
elif len(options[0]) == 3:
|
|
for key, option_value, key_repeat in options:
|
|
if str(key) == str(choice):
|
|
choices.append(option_value)
|
|
break
|
|
return ', '.join(choices)
|
|
|
|
def store_structured_value(self, data, field_id):
|
|
value = data.get(field_id)
|
|
if not self.data_source:
|
|
return
|
|
structured_options = data_sources.get_structured_items(self.data_source)
|
|
if not structured_options:
|
|
return
|
|
if not set(structured_options[0].keys()) != set(['id', 'text']):
|
|
return
|
|
structured_value = []
|
|
for structured_option in structured_options:
|
|
for choice in data.get(field_id) or []:
|
|
if str(structured_option.get('id')) == str(choice):
|
|
structured_value.append(structured_option)
|
|
return structured_value
|
|
|
|
register_field_class(ItemsField)
|
|
|
|
|
|
class PostConditionsRowWidget(CompositeWidget):
|
|
def __init__(self, name, value=None, **kwargs):
|
|
CompositeWidget.__init__(self, name, value, **kwargs)
|
|
if not value:
|
|
value = {}
|
|
self.add(ConditionWidget, name='condition', title=_('Condition'),
|
|
value=value.get('condition'), size=50)
|
|
self.add(StringWidget, name='error_message', title=_('Error message if condition is not met'),
|
|
value=value.get('error_message'), size=50)
|
|
|
|
def _parse(self, request):
|
|
if self.get('condition') or self.get('error_message'):
|
|
self.value = {
|
|
'condition': self.get('condition'),
|
|
'error_message': self.get('error_message')
|
|
}
|
|
else:
|
|
self.value = None
|
|
|
|
|
|
class PostConditionsTableWidget(WidgetListAsTable):
|
|
readonly = False
|
|
|
|
def __init__(self, name, **kwargs):
|
|
super(PostConditionsTableWidget, self).__init__(name,
|
|
element_type=PostConditionsRowWidget, **kwargs)
|
|
|
|
def parse(self, request=None):
|
|
super(PostConditionsTableWidget, self).parse(request=request)
|
|
for post_condition in self.value or []:
|
|
if not (post_condition.get('error_message') and post_condition.get('condition')):
|
|
self.set_error(_('Both condition and error message are required.'))
|
|
break
|
|
return self.value
|
|
|
|
|
|
class PageCondition(Condition):
|
|
log_errors = True
|
|
record_errors = False
|
|
|
|
def get_data(self):
|
|
dict_vars = self.context['dict_vars']
|
|
formdef = self.context['formdef']
|
|
|
|
# create variables with values currently being evaluated, not yet
|
|
# available in the formdata.
|
|
from formdata import get_dict_with_varnames
|
|
live_data = {}
|
|
form_live_data = {}
|
|
if dict_vars is not None:
|
|
live_data = get_dict_with_varnames(formdef.fields, dict_vars)
|
|
form_live_data = dict(('form_' + x, y) for x, y in live_data.items())
|
|
|
|
# 1) feed the form_var_* variables in the global substitution system,
|
|
# they will shadow formdata context variables with their new "live"
|
|
# value, this may be useful when evaluating data sources.
|
|
class ConditionVars(object):
|
|
def __init__(self, id_dict_var):
|
|
# keep track of reference dictionary
|
|
self.id_dict_var = id_dict_var
|
|
|
|
def get_substitution_variables(self):
|
|
return form_live_data
|
|
|
|
def __eq__(self, other):
|
|
# Assume all ConditionVars are equal when initialized with
|
|
# the same live data dictionary; this avoids filling
|
|
# the substitution sources with duplicates and invalidating its
|
|
# cache.
|
|
return self.id_dict_var == getattr(other, 'id_dict_var', None)
|
|
|
|
if dict_vars is not None:
|
|
# Add them only if there is a real dict_vars in context,
|
|
# ie do nothing on first page condition
|
|
get_publisher().substitutions.feed(ConditionVars(id(dict_vars)))
|
|
|
|
data = super(PageCondition, self).get_data()
|
|
# 2) add live data as var_ variables for local evaluation only, for
|
|
# backward compatibility. They are not added globally as they would
|
|
# interfere with the var_ prefixed variables used in dynamic jsonp
|
|
# fields. (#9786)
|
|
data = copy.copy(data)
|
|
data.update(live_data)
|
|
if dict_vars is None:
|
|
# ConditionsVars is not set when evaluating first page condition,
|
|
# but we need to have form_var_* variables already; add them from
|
|
# form_live_data (where all variables will have been set to None).
|
|
data.update(form_live_data)
|
|
return data
|
|
|
|
|
|
class PageField(Field):
|
|
key = 'page'
|
|
description = N_('Page')
|
|
|
|
post_conditions = None
|
|
|
|
def post_conditions_init_with_xml(self, node, charset, include_id=False):
|
|
if node is None:
|
|
return
|
|
self.post_conditions = []
|
|
for post_condition_node in node.findall('post_condition'):
|
|
if post_condition_node.findall('condition/type'):
|
|
condition = {
|
|
'type': post_condition_node.find('condition/type').text.encode(charset),
|
|
'value': post_condition_node.find('condition/value').text.encode(charset),
|
|
}
|
|
elif post_condition_node.find('condition').text:
|
|
condition = {
|
|
'type': 'python',
|
|
'value': post_condition_node.find('condition').text.encode(charset),
|
|
}
|
|
else:
|
|
continue
|
|
self.post_conditions.append({
|
|
'condition': condition,
|
|
'error_message': post_condition_node.find('error_message').text.encode(charset),
|
|
})
|
|
|
|
def post_conditions_export_to_xml(self, node, charset, include_id=False):
|
|
if not self.post_conditions:
|
|
return
|
|
|
|
conditions_node = ET.SubElement(node, 'post_conditions')
|
|
for post_condition in self.post_conditions:
|
|
post_condition_node = ET.SubElement(conditions_node, 'post_condition')
|
|
condition_node = ET.SubElement(post_condition_node, 'condition')
|
|
ET.SubElement(condition_node, 'type').text = unicode(
|
|
post_condition['condition'].get('type') or '', charset, 'replace')
|
|
ET.SubElement(condition_node, 'value').text = unicode(
|
|
post_condition['condition'].get('value') or '', charset, 'replace')
|
|
ET.SubElement(post_condition_node, 'error_message').text = unicode(
|
|
post_condition['error_message'] or '', charset, 'replace')
|
|
|
|
def fill_admin_form(self, form):
|
|
form.add(StringWidget, 'label', title = _('Label'), value = self.label,
|
|
required = True, size = 50)
|
|
form.add(ConditionWidget, 'condition', title=_('Display Condition'), value=self.condition,
|
|
required=False, size=50)
|
|
form.add(PostConditionsTableWidget, 'post_conditions',
|
|
title=_('Post Conditions'),
|
|
value=self.post_conditions,
|
|
advanced=not(self.post_conditions))
|
|
|
|
def get_admin_attributes(self):
|
|
return Field.get_admin_attributes(self) + ['post_conditions']
|
|
|
|
def migrate(self):
|
|
changed = super(PageField, self).migrate()
|
|
if isinstance(self.condition, basestring):
|
|
if self.condition:
|
|
self.condition = {'type': 'python', 'value': self.condition}
|
|
else:
|
|
self.condition = {}
|
|
changed = True
|
|
for post_condition in self.post_conditions or []:
|
|
condition = post_condition.get('condition')
|
|
if isinstance(condition, basestring):
|
|
if condition:
|
|
post_condition['condition'] = {'type': 'python', 'value': condition}
|
|
else:
|
|
post_condition['condition'] = {}
|
|
changed = True
|
|
return changed
|
|
|
|
def add_to_view_form(self, *args):
|
|
pass
|
|
|
|
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 = []
|
|
self.columns = []
|
|
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)
|
|
try:
|
|
form.remove('in_listing')
|
|
except KeyError: # perhaps it was already removed
|
|
pass
|
|
try:
|
|
form.remove('prefill')
|
|
except KeyError: # perhaps it was already removed
|
|
pass
|
|
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):
|
|
t = WidgetField.get_admin_attributes(self) + ['rows', 'columns']
|
|
try:
|
|
t.remove('in_listing')
|
|
except ValueError:
|
|
pass
|
|
try:
|
|
t.remove('prefill')
|
|
except ValueError:
|
|
pass
|
|
return t
|
|
|
|
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:
|
|
try:
|
|
r += value[i][j]
|
|
except IndexError:
|
|
pass
|
|
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(smart_text(column)))
|
|
|
|
for i, row in enumerate(value):
|
|
value[i] = [x or '' for x in row]
|
|
|
|
def get_value(i, j):
|
|
try:
|
|
return smart_text(value[i][j])
|
|
except IndexError:
|
|
return '-'
|
|
|
|
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(get_value(i, j)))
|
|
|
|
r.append(' '.join(['='*max_width]*(len(self.columns)+1)))
|
|
r.append(' '.join([smart_text(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 [smart_text(row)] +
|
|
[get_value(i, x) for x in range(len(self.columns))]]))
|
|
r.append(' '.join(['='*max_width]*(len(self.columns)+1)))
|
|
return misc.site_encode('\n'.join([indent + x for x in r]))
|
|
|
|
def get_csv_heading(self):
|
|
if not self.columns:
|
|
return [self.label]
|
|
labels = []
|
|
for col in self.columns:
|
|
for row in self.rows:
|
|
t = '%s / %s' % (col, row)
|
|
if len(labels) == 0:
|
|
labels.append('%s - %s' % (self.label, t))
|
|
else:
|
|
labels.append(t)
|
|
return labels
|
|
|
|
def get_csv_value(self, value, **kwargs):
|
|
if not self.columns:
|
|
return ['']
|
|
values = []
|
|
for i in range(len(self.columns)):
|
|
for j in range(len(self.rows)):
|
|
try:
|
|
values.append(value[j][i])
|
|
except IndexError:
|
|
values.append('')
|
|
return values
|
|
|
|
register_field_class(TableField)
|
|
|
|
|
|
class TableSelectField(TableField):
|
|
key = 'table-select'
|
|
description = N_('Table of Lists')
|
|
|
|
items = None
|
|
|
|
widget_class = SingleSelectTableWidget
|
|
|
|
def __init__(self, **kwargs):
|
|
self.items = []
|
|
TableField.__init__(self, **kwargs)
|
|
|
|
def fill_admin_form(self, form):
|
|
TableField.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'))
|
|
|
|
def perform_more_widget_changes(self, form, kwargs, edit=True):
|
|
TableField.perform_more_widget_changes(self, form, kwargs, edit=edit)
|
|
if edit:
|
|
kwargs['options'] = self.items or [(None, '---')]
|
|
else:
|
|
self.widget_class = TableWidget
|
|
|
|
def get_admin_attributes(self):
|
|
return TableField.get_admin_attributes(self) + ['items']
|
|
|
|
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
|
|
|
|
register_field_class(TableSelectField)
|
|
|
|
|
|
class TableRowsField(WidgetField):
|
|
key = 'tablerows'
|
|
description = N_('Table with rows')
|
|
in_listing = False # no way to represent data in a single cell
|
|
|
|
total_row = True
|
|
columns = None
|
|
|
|
widget_class = TableListRowsWidget
|
|
|
|
def __init__(self, **kwargs):
|
|
self.columns = []
|
|
WidgetField.__init__(self, **kwargs)
|
|
|
|
def perform_more_widget_changes(self, form, kwargs, edit = True):
|
|
kwargs['columns'] = self.columns
|
|
kwargs['add_element_label'] = _('Add row')
|
|
|
|
def fill_admin_form(self, form):
|
|
WidgetField.fill_admin_form(self, form)
|
|
try:
|
|
form.remove('in_listing')
|
|
except KeyError: # perhaps it was already removed
|
|
pass
|
|
try:
|
|
form.remove('prefill')
|
|
except KeyError: # perhaps it was already removed
|
|
pass
|
|
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'))
|
|
form.add(CheckboxWidget, 'total_row', title = _('Total Row'),
|
|
value = self.total_row)
|
|
|
|
def get_admin_attributes(self):
|
|
t = WidgetField.get_admin_attributes(self) + ['columns', 'total_row']
|
|
try:
|
|
t.remove('in_listing')
|
|
except ValueError:
|
|
pass
|
|
try:
|
|
t.remove('prefill')
|
|
except ValueError:
|
|
pass
|
|
return t
|
|
|
|
def get_view_value(self, value):
|
|
r = TemplateIO(html=True)
|
|
r += htmltext('<table><thead><tr>')
|
|
for column in self.columns:
|
|
r += htmltext('<th>%s</th>') % column
|
|
r += htmltext('</tr></thead><tbody>')
|
|
for i, row in enumerate(value):
|
|
r += htmltext('<tr>')
|
|
for j, column in enumerate(self.columns):
|
|
r += htmltext('<td>')
|
|
if value:
|
|
try:
|
|
r += row[j]
|
|
except IndexError:
|
|
pass
|
|
r += htmltext('</td>')
|
|
r += htmltext('</tr>')
|
|
r += htmltext('</tbody>')
|
|
|
|
if self.total_row:
|
|
sums_row = []
|
|
for j, column in enumerate(self.columns):
|
|
sum_column = 0
|
|
for row_value in value:
|
|
try:
|
|
cell_value = row_value[j]
|
|
except IndexError:
|
|
continue
|
|
if cell_value in (None, ''):
|
|
continue
|
|
try:
|
|
sum_column += float(cell_value)
|
|
except ValueError:
|
|
sums_row.append(None)
|
|
break
|
|
else:
|
|
sums_row.append(sum_column)
|
|
if [x for x in sums_row if x is not None]:
|
|
r += htmltext('<tfoot><tr>')
|
|
for sum_column in sums_row:
|
|
if sum_column is None:
|
|
r += htmltext('<td></td>')
|
|
else:
|
|
r += htmltext('<td>%.2f</td>' % sum_column)
|
|
r += htmltext('</tr></tfoot>')
|
|
|
|
r += htmltext('</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(smart_text(column)))
|
|
|
|
for i, row in enumerate(value):
|
|
value[i] = [x or '' for x in row]
|
|
|
|
for i, row_value in enumerate(value):
|
|
for j, column in enumerate(self.columns):
|
|
try:
|
|
max_width = max(max_width, len(smart_text(row_value[j])))
|
|
except IndexError:
|
|
# ignore errors for shorter than expected rows, this is
|
|
# typical of the field gaining new columns after some forms
|
|
# were already saved.
|
|
pass
|
|
|
|
r.append(' '.join(['='*max_width]*(len(self.columns))))
|
|
r.append(' '.join([smart_text(column).center(max_width) for column in self.columns]))
|
|
r.append(' '.join(['='*max_width]*(len(self.columns))))
|
|
for i, row_value in enumerate(value):
|
|
r.append(' '.join([cell.center(max_width) for cell in
|
|
[smart_text(row_value[x]) for x in range(len(self.columns))]]))
|
|
r.append(' '.join(['='*max_width]*(len(self.columns))))
|
|
return misc.site_encode('\n'.join([indent + x for x in r]))
|
|
|
|
def get_csv_value(self, element, **kwargs):
|
|
return [_('unimplemented')] # XXX
|
|
|
|
|
|
register_field_class(TableRowsField)
|
|
|
|
|
|
class MapField(WidgetField):
|
|
key = 'map'
|
|
description = N_('Map')
|
|
|
|
initial_zoom = None
|
|
min_zoom = None
|
|
max_zoom = None
|
|
default_position = None
|
|
init_with_geoloc = False
|
|
|
|
widget_class = MapWidget
|
|
extra_attributes = ['initial_zoom', 'min_zoom', 'max_zoom',
|
|
'default_position', 'init_with_geoloc']
|
|
|
|
def fill_admin_form(self, form):
|
|
WidgetField.fill_admin_form(self, form)
|
|
# 0: whole world, 9: wide area, 11: area, 13: town, 16: small road
|
|
zoom_levels = [(None, '---'),
|
|
('0', _('Whole world')),
|
|
('9', _('Wide area')),
|
|
('11', _('Area')),
|
|
('13', _('Town')),
|
|
('16', _('Small road')),
|
|
('19', _('Ant')),]
|
|
form.add(SingleSelectWidget, 'initial_zoom', title=_('Initial zoom level'),
|
|
value=self.initial_zoom or '13', options=zoom_levels)
|
|
form.add(SingleSelectWidget, 'min_zoom', title=_('Minimal zoom level'),
|
|
value=self.min_zoom, options=zoom_levels, required=False)
|
|
form.add(SingleSelectWidget, 'max_zoom', title=_('Maximal zoom level'),
|
|
value=self.max_zoom, options=zoom_levels, required=False)
|
|
form.add(MapWidget, 'default_position', title=_('Initial Position'),
|
|
value=self.default_position, default_zoom='9', required=False)
|
|
form.add(CheckboxWidget, 'init_with_geoloc',
|
|
title=_('Initialize position using device geolocation'),
|
|
value=self.init_with_geoloc, required=False)
|
|
|
|
def check_admin_form(self, form):
|
|
initial_zoom = form.get_widget('initial_zoom').parse()
|
|
min_zoom = form.get_widget('min_zoom').parse()
|
|
max_zoom = form.get_widget('max_zoom').parse()
|
|
if min_zoom and max_zoom:
|
|
if int(min_zoom) > int(max_zoom):
|
|
form.get_widget('min_zoom').set_error(
|
|
_('Minimal zoom level cannot be greater than maximal zoom level.'))
|
|
if (initial_zoom and min_zoom and int(initial_zoom) < int(min_zoom)) or (
|
|
(initial_zoom and max_zoom and int(initial_zoom) > int(max_zoom))):
|
|
form.get_widget('initial_zoom').set_error(
|
|
_('Initial zoom level must be between minimal and maximal zoom levels.'))
|
|
|
|
def get_admin_attributes(self):
|
|
return WidgetField.get_admin_attributes(self) + ['initial_zoom',
|
|
'min_zoom', 'max_zoom', 'default_position',
|
|
'init_with_geoloc']
|
|
|
|
def get_prefill_value(self, user=None, force_string=True):
|
|
if self.prefill.get('type') != 'string' or not self.prefill.get('value'):
|
|
return (None, False)
|
|
# string is actually interpreted as a template of a string that will be
|
|
# geocoded
|
|
from wcs.wf.geolocate import GeolocateWorkflowStatusItem
|
|
geolocate = GeolocateWorkflowStatusItem()
|
|
geolocate.method = 'address_string'
|
|
geolocate.address_string = self.prefill.get('value')
|
|
coords = geolocate.geolocate_address_string(None)
|
|
if not coords:
|
|
return (None, False)
|
|
return ('%(lat)s;%(lon)s' % coords, False)
|
|
|
|
def get_view_value(self, value):
|
|
widget = self.widget_class('x%s' % random.random(), value, readonly=True)
|
|
return widget.render_widget_content()
|
|
|
|
def get_rst_view_value(self, value, indent=''):
|
|
return indent + value
|
|
|
|
def get_json_value(self, value):
|
|
if not value or ';' not in value:
|
|
return None
|
|
lat, lon = value.split(';')
|
|
try:
|
|
lat = float(lat)
|
|
lon = float(lon)
|
|
except ValueError:
|
|
return None
|
|
return {'lat': lat, 'lon': lon}
|
|
|
|
def from_json_value(self, value):
|
|
if 'lat' in value and 'lon' in value:
|
|
return '%s;%s' % (float(value['lat']), float(value['lon']))
|
|
else:
|
|
return None
|
|
|
|
def get_structured_value(self, data):
|
|
return self.get_json_value(data.get(self.id))
|
|
|
|
|
|
register_field_class(MapField)
|
|
|
|
|
|
class RankedItemsField(WidgetField):
|
|
key = 'ranked-items'
|
|
description = N_('Ranked Items')
|
|
|
|
items = []
|
|
randomize_items = False
|
|
widget_class = RankedItemsWidget
|
|
anonymise = False
|
|
|
|
def perform_more_widget_changes(self, form, kwargs, edit = True):
|
|
kwargs['elements'] = self.items or []
|
|
kwargs['randomize_items'] = self.randomize_items
|
|
|
|
def fill_admin_form(self, form):
|
|
WidgetField.fill_admin_form(self, form)
|
|
try:
|
|
form.remove('prefill')
|
|
except KeyError: # perhaps it was already removed
|
|
pass
|
|
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, 'randomize_items', title = _('Randomize Items'),
|
|
value = self.randomize_items)
|
|
|
|
def get_admin_attributes(self):
|
|
attrs = WidgetField.get_admin_attributes(self) + ['items', 'randomize_items']
|
|
if 'prefill' in attrs:
|
|
attrs.remove('prefill')
|
|
return attrs
|
|
|
|
def get_view_value(self, value):
|
|
r = TemplateIO(html=True)
|
|
r += htmltext('<ul>')
|
|
items = value.items()
|
|
items.sort(lambda x,y: cmp(x[1], y[1]))
|
|
counter = 0
|
|
last_it = None
|
|
for it in items:
|
|
if it[1] is not None:
|
|
if last_it != it[1]:
|
|
counter += 1
|
|
last_it = it[1]
|
|
r += htmltext('<li>%s: %s</li>') % (counter, it[0])
|
|
r += htmltext('</ul>')
|
|
return r.getvalue()
|
|
|
|
def get_rst_view_value(self, value, indent=''):
|
|
items = value.items()
|
|
items.sort(lambda x,y: cmp(x[1], y[1]))
|
|
counter = 0
|
|
last_it = None
|
|
values = []
|
|
for it in items:
|
|
if it[1] is not None:
|
|
if last_it != it[1]:
|
|
counter += 1
|
|
last_it = it[1]
|
|
values.append('%s: %s' % (counter, it[0]))
|
|
return indent + ' / '.join(values)
|
|
|
|
def get_csv_heading(self):
|
|
if not self.items:
|
|
return [self.label]
|
|
return [self.label] + ['']*(len(self.items)-1)
|
|
|
|
def get_csv_value(self, value, **kwargs):
|
|
if not self.items:
|
|
return ['']
|
|
if type(value) is not dict:
|
|
value = {}
|
|
items = value.items()
|
|
items.sort(lambda x,y: cmp(x[1], y[1]))
|
|
ranked = [x[0] for x in items if x[1] is not None]
|
|
return ranked + ['' for x in range(len(self.items)-len(ranked))]
|
|
|
|
|
|
register_field_class(RankedItemsField)
|
|
|
|
|
|
class PasswordField(WidgetField):
|
|
key = 'password'
|
|
description = N_('Password')
|
|
|
|
min_length = 0
|
|
max_length = 0
|
|
count_uppercase = 0
|
|
count_lowercase = 0
|
|
count_digit = 0
|
|
count_special = 0
|
|
confirmation = True
|
|
confirmation_title = None
|
|
strength_indicator = True
|
|
formats = ['sha1']
|
|
extra_attributes = ['formats', 'min_length', 'max_length',
|
|
'count_uppercase', 'count_lowercase', 'count_digit',
|
|
'count_special', 'confirmation', 'confirmation_title',
|
|
'strength_indicator']
|
|
|
|
widget_class = PasswordEntryWidget
|
|
|
|
def get_admin_attributes(self):
|
|
return WidgetField.get_admin_attributes(self) + self.extra_attributes
|
|
|
|
def fill_admin_form(self, form):
|
|
WidgetField.fill_admin_form(self, form)
|
|
formats = [('cleartext', _('Clear text')),
|
|
('md5', _('MD5')),
|
|
('sha1', _('SHA1')),
|
|
]
|
|
form.add(CheckboxesWidget, 'formats', title=_('Storage formats'),
|
|
value=self.formats, options=formats, inline=True)
|
|
form.add(IntWidget, 'min_length', title=_('Minimum length'),
|
|
value=self.min_length)
|
|
form.add(IntWidget, 'max_length', title=_('Maximum password length'),
|
|
value=self.max_length,
|
|
hint=_('0 for unlimited length'))
|
|
form.add(IntWidget, 'count_uppercase',
|
|
title=_('Minimum number of uppercase characters'),
|
|
value=self.count_uppercase)
|
|
form.add(IntWidget, 'count_lowercase',
|
|
title=_('Minimum number of lowercase characters'),
|
|
value=self.count_lowercase)
|
|
form.add(IntWidget, 'count_digit',
|
|
title=_('Minimum number of digits'),
|
|
value=self.count_digit)
|
|
form.add(IntWidget, 'count_special',
|
|
title=_('Minimum number of special characters'),
|
|
value=self.count_special)
|
|
form.add(CheckboxWidget, 'strength_indicator',
|
|
title=_('Add a password strength indicator'),
|
|
value=self.strength_indicator)
|
|
form.add(CheckboxWidget, 'confirmation',
|
|
title=_('Add a confirmation input'),
|
|
value=self.confirmation)
|
|
form.add(StringWidget, 'confirmation_title', size=50,
|
|
title=_('Label for confirmation input'),
|
|
value=self.confirmation_title)
|
|
|
|
def get_view_value(self, value):
|
|
return '*'*8
|
|
|
|
def get_csv_value(self, value, **kwargs):
|
|
return [self.get_view_value(value)]
|
|
|
|
def get_rst_view_value(self, value, indent=''):
|
|
return indent + self.get_view_value(value)
|
|
|
|
register_field_class(PasswordField)
|
|
|
|
|
|
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
|
|
|