Improved advanced search functionality + completed String fields with Selection instance as validator.
This commit is contained in:
parent
f8baeee4f7
commit
d6607d7815
|
@ -21,15 +21,28 @@ class Page:
|
||||||
class Import:
|
class Import:
|
||||||
'''Used for describing the place where to find the data to use for creating
|
'''Used for describing the place where to find the data to use for creating
|
||||||
an object.'''
|
an object.'''
|
||||||
def __init__(self, path, columnMethod=None, columnHeaders=(),
|
def __init__(self, path, onElement=None, headers=(), sort=None):
|
||||||
sortMethod=None):
|
|
||||||
self.id = 'import'
|
self.id = 'import'
|
||||||
self.path = path
|
self.path = path
|
||||||
self.columnMethod = columnMethod
|
# p_onElement hereafter must be a function (or a static method) that
|
||||||
# This method allows to split every element into subElements that can
|
# will be called every time an element to import is found. It takes a
|
||||||
# be shown as column values in a table.
|
# single arg that is the absolute filen name of the file to import,
|
||||||
self.columnHeaders = columnHeaders
|
# within p_path. It must return a list of info about the element, or
|
||||||
self.sortMethod = sortMethod
|
# None if the element must be ignored. The list will be used to display
|
||||||
|
# information about the element in a tabular form.
|
||||||
|
self.onElement = onElement
|
||||||
|
# The following attribute must contain the names of the column headers
|
||||||
|
# of the table that will display elements to import (retrieved from
|
||||||
|
# calls to self.onElement). Every not-None element retrieved from
|
||||||
|
# self.onElement must have the same length as self.headers.
|
||||||
|
self.headers = headers
|
||||||
|
# The following attribute must store a function or static method that
|
||||||
|
# will be used to sort elements to import. It will be called with a
|
||||||
|
# single param containing the list of all not-None elements as retrieved
|
||||||
|
# by calls to self.onElement (but with one additional first element in
|
||||||
|
# every list, which is the absolute file name of the element to import)
|
||||||
|
# and must return a similar, sorted, list.
|
||||||
|
self.sort = sort
|
||||||
|
|
||||||
class Search:
|
class Search:
|
||||||
'''Used for specifying a search for a given type.'''
|
'''Used for specifying a search for a given type.'''
|
||||||
|
@ -561,7 +574,25 @@ class Selection:
|
||||||
'''Instances of this class may be given as validator of a String, in order
|
'''Instances of this class may be given as validator of a String, in order
|
||||||
to tell Appy that the validator is a selection that will be computed
|
to tell Appy that the validator is a selection that will be computed
|
||||||
dynamically.'''
|
dynamically.'''
|
||||||
pass
|
def __init__(self, methodName):
|
||||||
|
# The p_methodName parameter must be the name of a method that will be
|
||||||
|
# called every time Appy will need to get the list of possible values
|
||||||
|
# for the related field. It must correspond to an instance method of
|
||||||
|
# the class defining the related field. This method accepts no argument
|
||||||
|
# and must return a list (or tuple) of pairs (lists or tuples):
|
||||||
|
# (id, text), where "id" is one of the possible values for the field,
|
||||||
|
# and "text" is the value as will be shown on the screen. You can use
|
||||||
|
# self.translate within this method to produce an internationalized
|
||||||
|
# "text" if needed.
|
||||||
|
self.methodName = methodName
|
||||||
|
|
||||||
|
def getText(self, obj, value):
|
||||||
|
'''Gets the text that corresponds to p_value.'''
|
||||||
|
vocab = obj._appy_getDynamicDisplayList(self.methodName)
|
||||||
|
if type(value) in sequenceTypes:
|
||||||
|
return [vocab.getValue(v) for v in value]
|
||||||
|
else:
|
||||||
|
return vocab.getValue(value)
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
class Tool:
|
class Tool:
|
||||||
|
|
|
@ -13,14 +13,19 @@ class Descriptor: # Abstract
|
||||||
|
|
||||||
class ClassDescriptor(Descriptor):
|
class ClassDescriptor(Descriptor):
|
||||||
'''This class gives information about an Appy class.'''
|
'''This class gives information about an Appy class.'''
|
||||||
def getOrderedAppyAttributes(self):
|
def getOrderedAppyAttributes(self, condition=None):
|
||||||
'''Returns the appy types for all attributes of this class and parent
|
'''Returns the appy types for all attributes of this class and parent
|
||||||
class(es).'''
|
class(es). If a p_condition is specified, ony Appy types matching
|
||||||
|
the condition will be returned. p_condition must be a string
|
||||||
|
containing an expression that will be evaluated with, in its context,
|
||||||
|
"self" being this ClassDescriptor and "attrValue" being the current
|
||||||
|
Type instance.'''
|
||||||
res = []
|
res = []
|
||||||
# First, get the attributes for the current class
|
# First, get the attributes for the current class
|
||||||
for attrName in self.orderedAttributes:
|
for attrName in self.orderedAttributes:
|
||||||
attrValue = getattr(self.klass, attrName)
|
attrValue = getattr(self.klass, attrName)
|
||||||
if isinstance(attrValue, Type):
|
if isinstance(attrValue, Type):
|
||||||
|
if not condition or eval(condition):
|
||||||
res.append( (attrName, attrValue) )
|
res.append( (attrName, attrValue) )
|
||||||
# Then, add attributes from parent classes
|
# Then, add attributes from parent classes
|
||||||
for baseClass in self.klass.__bases__:
|
for baseClass in self.klass.__bases__:
|
||||||
|
|
|
@ -11,7 +11,7 @@ from utils import stringify
|
||||||
import appy.gen
|
import appy.gen
|
||||||
import appy.gen.descriptors
|
import appy.gen.descriptors
|
||||||
from appy.gen.po import PoMessage
|
from appy.gen.po import PoMessage
|
||||||
from appy.gen import Date, String, State, Transition, Type, Search
|
from appy.gen import Date, String, State, Transition, Type, Search, Selection
|
||||||
from appy.gen.utils import GroupDescr, PageDescr, produceNiceMessage, \
|
from appy.gen.utils import GroupDescr, PageDescr, produceNiceMessage, \
|
||||||
sequenceTypes
|
sequenceTypes
|
||||||
TABS = 4 # Number of blanks in a Python indentation.
|
TABS = 4 # Number of blanks in a Python indentation.
|
||||||
|
@ -111,8 +111,7 @@ class ArchetypeFieldDescriptor:
|
||||||
# Elements common to all selection fields
|
# Elements common to all selection fields
|
||||||
methodName = 'list_%s_values' % self.fieldName
|
methodName = 'list_%s_values' % self.fieldName
|
||||||
self.fieldParams['vocabulary'] = methodName
|
self.fieldParams['vocabulary'] = methodName
|
||||||
self.classDescr.addSelectMethod(
|
self.classDescr.addSelectMethod(methodName, self)
|
||||||
methodName, self, self.appyType.isMultiValued())
|
|
||||||
self.fieldParams['enforceVocabulary'] = True
|
self.fieldParams['enforceVocabulary'] = True
|
||||||
else:
|
else:
|
||||||
self.fieldType = 'StringField'
|
self.fieldType = 'StringField'
|
||||||
|
@ -370,7 +369,7 @@ class ClassDescriptor(appy.gen.descriptors.ClassDescriptor):
|
||||||
field = ArchetypeFieldDescriptor(attrName, attrValue, self)
|
field = ArchetypeFieldDescriptor(attrName, attrValue, self)
|
||||||
self.schema += '\n' + field.generate()
|
self.schema += '\n' + field.generate()
|
||||||
|
|
||||||
def addSelectMethod(self, methodName, fieldDescr, isMultivalued=False):
|
def addSelectMethod(self, methodName, fieldDescr):
|
||||||
'''For the selection field p_fieldDescr I need to generate a method
|
'''For the selection field p_fieldDescr I need to generate a method
|
||||||
named p_methodName that will generate the vocabulary for
|
named p_methodName that will generate the vocabulary for
|
||||||
p_fieldDescr.'''
|
p_fieldDescr.'''
|
||||||
|
@ -395,11 +394,16 @@ class ClassDescriptor(appy.gen.descriptors.ClassDescriptor):
|
||||||
# Generate a method that returns a DisplayList
|
# Generate a method that returns a DisplayList
|
||||||
appName = self.generator.applicationName
|
appName = self.generator.applicationName
|
||||||
allValues = appyType.validator
|
allValues = appyType.validator
|
||||||
if not isMultivalued:
|
if not appyType.isMultiValued():
|
||||||
allValues = [''] + appyType.validator
|
allValues = [''] + appyType.validator
|
||||||
labels.insert(0, 'choose_a_value')
|
labels.insert(0, 'choose_a_value')
|
||||||
m += ' '*spaces + 'return self._appy_getDisplayList' \
|
m += ' '*spaces + 'return self._appy_getDisplayList' \
|
||||||
'(%s, %s, %s)\n' % (s(allValues), s(labels), s(appName))
|
'(%s, %s, %s)\n' % (s(allValues), s(labels), s(appName))
|
||||||
|
elif isinstance(appyType.validator, Selection):
|
||||||
|
# Call the custom method that will produce dynamically the list of
|
||||||
|
# values.
|
||||||
|
m += ' '*spaces + 'return self._appy_getDynamicDisplayList' \
|
||||||
|
'(%s)\n' % s(appyType.validator.methodName)
|
||||||
self.methods = m
|
self.methods = m
|
||||||
|
|
||||||
def addValidateMethod(self, methodName, label, fieldDescr,
|
def addValidateMethod(self, methodName, label, fieldDescr,
|
||||||
|
|
|
@ -121,6 +121,7 @@ class Generator(AbstractGenerator):
|
||||||
msg('search_objects', '', msg.SEARCH_OBJECTS),
|
msg('search_objects', '', msg.SEARCH_OBJECTS),
|
||||||
msg('search_results', '', msg.SEARCH_RESULTS),
|
msg('search_results', '', msg.SEARCH_RESULTS),
|
||||||
msg('search_results_descr', '', ' '),
|
msg('search_results_descr', '', ' '),
|
||||||
|
msg('search_new', '', msg.SEARCH_NEW),
|
||||||
msg('ref_invalid_index', '', msg.REF_INVALID_INDEX),
|
msg('ref_invalid_index', '', msg.REF_INVALID_INDEX),
|
||||||
msg('bad_int', '', msg.BAD_INT),
|
msg('bad_int', '', msg.BAD_INT),
|
||||||
msg('bad_float', '', msg.BAD_FLOAT),
|
msg('bad_float', '', msg.BAD_FLOAT),
|
||||||
|
@ -136,6 +137,7 @@ class Generator(AbstractGenerator):
|
||||||
msg('goto_next', '', msg.GOTO_NEXT),
|
msg('goto_next', '', msg.GOTO_NEXT),
|
||||||
msg('goto_last', '', msg.GOTO_LAST),
|
msg('goto_last', '', msg.GOTO_LAST),
|
||||||
msg('goto_source', '', msg.GOTO_SOURCE),
|
msg('goto_source', '', msg.GOTO_SOURCE),
|
||||||
|
msg('whatever', '', msg.WHATEVER),
|
||||||
]
|
]
|
||||||
# Create basic files (config.py, Install.py, etc)
|
# Create basic files (config.py, Install.py, etc)
|
||||||
self.generateTool()
|
self.generateTool()
|
||||||
|
@ -299,6 +301,12 @@ class Generator(AbstractGenerator):
|
||||||
theImport = 'import %s' % classDescr.klass.__module__
|
theImport = 'import %s' % classDescr.klass.__module__
|
||||||
if theImport not in imports:
|
if theImport not in imports:
|
||||||
imports.append(theImport)
|
imports.append(theImport)
|
||||||
|
# Compute ordered lists of attributes for every Appy class.
|
||||||
|
attributes = []
|
||||||
|
for classDescr in classDescrs:
|
||||||
|
classAttrs = [a[0] for a in classDescr.getOrderedAppyAttributes()]
|
||||||
|
attrs = ','.join([('"%s"' % a) for a in classAttrs])
|
||||||
|
attributes.append('"%s":[%s]' % (classDescr.name, attrs))
|
||||||
# Compute root classes
|
# Compute root classes
|
||||||
rootClasses = ''
|
rootClasses = ''
|
||||||
for classDescr in self.classes:
|
for classDescr in self.classes:
|
||||||
|
@ -317,6 +325,7 @@ class Generator(AbstractGenerator):
|
||||||
repls['referers'] = referers
|
repls['referers'] = referers
|
||||||
repls['workflowInstancesInit'] = wfInit
|
repls['workflowInstancesInit'] = wfInit
|
||||||
repls['imports'] = '\n'.join(imports)
|
repls['imports'] = '\n'.join(imports)
|
||||||
|
repls['attributes'] = ',\n '.join(attributes)
|
||||||
repls['defaultAddRoles'] = ','.join(
|
repls['defaultAddRoles'] = ','.join(
|
||||||
['"%s"' % r for r in self.config.defaultCreators])
|
['"%s"' % r for r in self.config.defaultCreators])
|
||||||
repls['addPermissions'] = addPermissions
|
repls['addPermissions'] = addPermissions
|
||||||
|
@ -598,9 +607,10 @@ class Generator(AbstractGenerator):
|
||||||
fieldType.group = childDescr.klass.__name__
|
fieldType.group = childDescr.klass.__name__
|
||||||
Flavour._appy_addField(childFieldName,fieldType,childDescr)
|
Flavour._appy_addField(childFieldName,fieldType,childDescr)
|
||||||
if classDescr.isRoot():
|
if classDescr.isRoot():
|
||||||
# We must be able to configure query results from the
|
# We must be able to configure query results from the flavour.
|
||||||
# flavour.
|
|
||||||
Flavour._appy_addQueryResultColumns(classDescr)
|
Flavour._appy_addQueryResultColumns(classDescr)
|
||||||
|
# Add the search-related fields.
|
||||||
|
Flavour._appy_addSearchRelatedFields(classDescr)
|
||||||
Flavour._appy_addWorkflowFields(self.flavourDescr)
|
Flavour._appy_addWorkflowFields(self.flavourDescr)
|
||||||
Flavour._appy_addWorkflowFields(self.podTemplateDescr)
|
Flavour._appy_addWorkflowFields(self.podTemplateDescr)
|
||||||
# Generate the flavour class and related i18n messages
|
# Generate the flavour class and related i18n messages
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
import appy.gen
|
import appy.gen
|
||||||
|
from appy.gen import Type
|
||||||
from appy.gen.plone25.mixins import AbstractMixin
|
from appy.gen.plone25.mixins import AbstractMixin
|
||||||
from appy.gen.plone25.descriptors import ArchetypesClassDescriptor
|
from appy.gen.plone25.descriptors import ArchetypesClassDescriptor
|
||||||
|
|
||||||
|
@ -110,4 +111,41 @@ class FlavourMixin(AbstractMixin):
|
||||||
'''Gets on this flavour attribute named p_attrName. Useful because we
|
'''Gets on this flavour attribute named p_attrName. Useful because we
|
||||||
can't use getattr directly in Zope Page Templates.'''
|
can't use getattr directly in Zope Page Templates.'''
|
||||||
return getattr(self, attrName, None)
|
return getattr(self, attrName, None)
|
||||||
|
|
||||||
|
def _appy_getAllFields(self, contentType):
|
||||||
|
'''Returns the (translated) names of fields of p_contentType.'''
|
||||||
|
res = []
|
||||||
|
for attrName in self.getProductConfig().attributes[contentType]:
|
||||||
|
if attrName != 'title': # Will be included by default.
|
||||||
|
label = '%s_%s' % (contentType, attrName)
|
||||||
|
res.append((attrName, self.translate(label)))
|
||||||
|
# Add object state
|
||||||
|
res.append(('workflowState', self.translate('workflow_state')))
|
||||||
|
return res
|
||||||
|
|
||||||
|
def _appy_getSearchableFields(self, contentType):
|
||||||
|
'''Returns the (translated) names of fields that may be searched on
|
||||||
|
objects of type p_contentType (=indexed fields).'''
|
||||||
|
tool = self.getParentNode()
|
||||||
|
appyClass = tool.getAppyClass(contentType)
|
||||||
|
attrNames = self.getProductConfig().attributes[contentType]
|
||||||
|
res = []
|
||||||
|
for attrName in attrNames:
|
||||||
|
attr = getattr(appyClass, attrName)
|
||||||
|
if isinstance(attr, Type) and attr.indexed:
|
||||||
|
label = '%s_%s' % (contentType, attrName)
|
||||||
|
res.append((attrName, self.translate(label)))
|
||||||
|
return res
|
||||||
|
|
||||||
|
def getSearchableFields(self, contentType):
|
||||||
|
'''Returns, among the list of all searchable fields (see method above),
|
||||||
|
the list of fields that the user has configured in the flavour as
|
||||||
|
being effectively used in the search screen.'''
|
||||||
|
res = []
|
||||||
|
appyClass = self.getAppyClass(contentType)
|
||||||
|
for attrName in getattr(self, 'searchFieldsFor%s' % contentType):
|
||||||
|
attr = getattr(appyClass, attrName)
|
||||||
|
dAttr = self._appy_getTypeAsDict(attrName, attr, appyClass)
|
||||||
|
res.append((attrName, dAttr))
|
||||||
|
return res
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
import re, os, os.path, Cookie
|
import re, os, os.path, Cookie
|
||||||
from appy.gen import Type
|
from appy.gen import Type, Search
|
||||||
from appy.gen.utils import FieldDescr, SomeObjects
|
from appy.gen.utils import FieldDescr, SomeObjects
|
||||||
from appy.gen.plone25.mixins import AbstractMixin
|
from appy.gen.plone25.mixins import AbstractMixin
|
||||||
from appy.gen.plone25.mixins.FlavourMixin import FlavourMixin
|
from appy.gen.plone25.mixins.FlavourMixin import FlavourMixin
|
||||||
|
@ -117,7 +117,7 @@ class ToolMixin(AbstractMixin):
|
||||||
will be added to the query, or;
|
will be added to the query, or;
|
||||||
2) "_advanced": in this case, additional search criteria will also
|
2) "_advanced": in this case, additional search criteria will also
|
||||||
be added to the query, but those criteria come from the session
|
be added to the query, but those criteria come from the session
|
||||||
and were created from search.pt.
|
(in key "searchCriteria") and were created from search.pt.
|
||||||
|
|
||||||
We will retrieve objects from p_startNumber. If p_search is defined,
|
We will retrieve objects from p_startNumber. If p_search is defined,
|
||||||
it corresponds to a custom Search instance (instead of a predefined
|
it corresponds to a custom Search instance (instead of a predefined
|
||||||
|
@ -154,6 +154,9 @@ class ToolMixin(AbstractMixin):
|
||||||
if searchName != '_advanced':
|
if searchName != '_advanced':
|
||||||
search = ArchetypesClassDescriptor.getSearch(
|
search = ArchetypesClassDescriptor.getSearch(
|
||||||
appyClass, searchName)
|
appyClass, searchName)
|
||||||
|
else:
|
||||||
|
fields = self.REQUEST.SESSION['searchCriteria']
|
||||||
|
search = Search('customSearch', **fields)
|
||||||
if search:
|
if search:
|
||||||
# Add additional search criteria
|
# Add additional search criteria
|
||||||
for fieldName, fieldValue in search.fields.iteritems():
|
for fieldName, fieldValue in search.fields.iteritems():
|
||||||
|
@ -162,6 +165,11 @@ class ToolMixin(AbstractMixin):
|
||||||
elif attrName == 'description': attrName = 'Description'
|
elif attrName == 'description': attrName = 'Description'
|
||||||
elif attrName == 'state': attrName = 'review_state'
|
elif attrName == 'state': attrName = 'review_state'
|
||||||
else: attrName = 'get%s%s'% (fieldName[0].upper(),fieldName[1:])
|
else: attrName = 'get%s%s'% (fieldName[0].upper(),fieldName[1:])
|
||||||
|
if isinstance(fieldValue, basestring) and \
|
||||||
|
fieldValue.endswith('*'):
|
||||||
|
v = fieldValue[:-1]
|
||||||
|
params[attrName] = {'query':[v,v+'Z'], 'range':'minmax'}
|
||||||
|
else:
|
||||||
params[attrName] = fieldValue
|
params[attrName] = fieldValue
|
||||||
# Add a sort order if specified
|
# Add a sort order if specified
|
||||||
sb = search.sortBy
|
sb = search.sortBy
|
||||||
|
@ -335,19 +343,20 @@ class ToolMixin(AbstractMixin):
|
||||||
p_contentType.'''
|
p_contentType.'''
|
||||||
appyClass = self.getAppyClass(contentType)
|
appyClass = self.getAppyClass(contentType)
|
||||||
importParams = self.getCreateMeans(appyClass)['import']
|
importParams = self.getCreateMeans(appyClass)['import']
|
||||||
columnMethod = importParams['columnMethod'].__get__('')
|
onElement = importParams['onElement'].__get__('')
|
||||||
sortMethod = importParams['sortMethod']
|
sortMethod = importParams['sort']
|
||||||
if sortMethod: sortMethod = sortMethod.__get__('')
|
if sortMethod: sortMethod = sortMethod.__get__('')
|
||||||
elems = []
|
elems = []
|
||||||
for elem in os.listdir(importParams['path']):
|
for elem in os.listdir(importParams['path']):
|
||||||
elemFullPath = os.path.join(importParams['path'], elem)
|
elemFullPath = os.path.join(importParams['path'], elem)
|
||||||
niceElem = columnMethod(elemFullPath)
|
elemInfo = onElement(elemFullPath)
|
||||||
niceElem.insert(0, elemFullPath) # To the result, I add the full
|
if elemInfo:
|
||||||
|
elemInfo.insert(0, elemFullPath) # To the result, I add the full
|
||||||
# path of the elem, which will not be shown.
|
# path of the elem, which will not be shown.
|
||||||
elems.append(niceElem)
|
elems.append(elemInfo)
|
||||||
if sortMethod:
|
if sortMethod:
|
||||||
elems = sortMethod(elems)
|
elems = sortMethod(elems)
|
||||||
return [importParams['columnHeaders'], elems]
|
return [importParams['headers'], elems]
|
||||||
|
|
||||||
def onImportObjects(self):
|
def onImportObjects(self):
|
||||||
'''This method is called when the user wants to create objects from
|
'''This method is called when the user wants to create objects from
|
||||||
|
@ -371,22 +380,25 @@ class ToolMixin(AbstractMixin):
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def getSearchableFields(self, contentType):
|
|
||||||
'''Returns the list of fields that may be searched on objects on type
|
|
||||||
p_contentType (=indexed fields).'''
|
|
||||||
appyClass = self.getAppyClass(contentType)
|
|
||||||
res = []
|
|
||||||
for attrName in dir(appyClass):
|
|
||||||
attr = getattr(appyClass, attrName)
|
|
||||||
if isinstance(attr, Type) and attr.indexed:
|
|
||||||
dAttr = self._appy_getTypeAsDict(attrName, attr, appyClass)
|
|
||||||
res.append((attrName, dAttr))
|
|
||||||
return res
|
|
||||||
|
|
||||||
def onSearchObjects(self):
|
def onSearchObjects(self):
|
||||||
'''This method is called when the user triggers a search from
|
'''This method is called when the user triggers a search from
|
||||||
search.pt.'''
|
search.pt.'''
|
||||||
rq = self.REQUEST
|
rq = self.REQUEST
|
||||||
|
# Store the search criteria in the session
|
||||||
|
criteria = {}
|
||||||
|
for attrName in rq.form.keys():
|
||||||
|
if attrName.startswith('w_'):
|
||||||
|
attrValue = rq.form[attrName]
|
||||||
|
if attrValue:
|
||||||
|
if attrName.find('*') != -1:
|
||||||
|
attrName, attrType = attrName.split('*')
|
||||||
|
if attrType == 'bool':
|
||||||
|
exec 'attrValue = %s' % attrValue
|
||||||
|
if isinstance(attrValue, list):
|
||||||
|
attrValue = ' OR '.join(attrValue)
|
||||||
|
criteria[attrName[2:]] = attrValue
|
||||||
|
rq.SESSION['searchCriteria'] = criteria
|
||||||
|
# Goto the screen that displays search results
|
||||||
backUrl = '%s/query?type_name=%s&flavourNumber=%d&search=_advanced' % \
|
backUrl = '%s/query?type_name=%s&flavourNumber=%d&search=_advanced' % \
|
||||||
(os.path.dirname(rq['URL']), rq['type_name'], rq['flavourNumber'])
|
(os.path.dirname(rq['URL']), rq['type_name'], rq['flavourNumber'])
|
||||||
return self.goto(backUrl)
|
return self.goto(backUrl)
|
||||||
|
@ -556,4 +568,30 @@ class ToolMixin(AbstractMixin):
|
||||||
navUrl = baseUrl + '/?nav=' + newNav % (index + 1)
|
navUrl = baseUrl + '/?nav=' + newNav % (index + 1)
|
||||||
res['%sUrl' % urlType] = navUrl
|
res['%sUrl' % urlType] = navUrl
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def tabularize(self, data, numberOfRows):
|
||||||
|
'''This method transforms p_data, which must be a "flat" list or tuple,
|
||||||
|
into a list of lists, where every sub-list has length p_numberOfRows.
|
||||||
|
This method is typically used for rendering elements in a table of
|
||||||
|
p_numberOfRows rows.'''
|
||||||
|
if numberOfRows > 1:
|
||||||
|
res = []
|
||||||
|
row = []
|
||||||
|
for elem in data:
|
||||||
|
row.append(elem)
|
||||||
|
if len(row) == numberOfRows:
|
||||||
|
res.append(row)
|
||||||
|
row = []
|
||||||
|
# Complete the last unfinished line if required.
|
||||||
|
if row:
|
||||||
|
while len(row) < numberOfRows: row.append(None)
|
||||||
|
res.append(row)
|
||||||
|
return res
|
||||||
|
else:
|
||||||
|
return data
|
||||||
|
|
||||||
|
def truncate(self, value, numberOfChars):
|
||||||
|
'''Truncates string p_value to p_numberOfChars.'''
|
||||||
|
if len(value) > numberOfChars: return value[:numberOfChars] + '...'
|
||||||
|
return value
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
import os, os.path, sys, types, mimetypes
|
import os, os.path, sys, types, mimetypes
|
||||||
import appy.gen
|
import appy.gen
|
||||||
from appy.gen import String
|
from appy.gen import String, Selection
|
||||||
from appy.gen.utils import FieldDescr, GroupDescr, PhaseDescr, StateDescr, \
|
from appy.gen.utils import FieldDescr, GroupDescr, PhaseDescr, StateDescr, \
|
||||||
ValidationErrors, sequenceTypes, SomeObjects
|
ValidationErrors, sequenceTypes, SomeObjects
|
||||||
from appy.gen.plone25.descriptors import ArchetypesClassDescriptor
|
from appy.gen.plone25.descriptors import ArchetypesClassDescriptor
|
||||||
|
@ -216,6 +216,13 @@ class AbstractMixin:
|
||||||
elif vType == 'String':
|
elif vType == 'String':
|
||||||
if not v: return v
|
if not v: return v
|
||||||
if appyType['isSelect']:
|
if appyType['isSelect']:
|
||||||
|
validator = appyType['validator']
|
||||||
|
if isinstance(validator, Selection):
|
||||||
|
# Value(s) come from a dynamic vocabulary
|
||||||
|
return validator.getText(self, v)
|
||||||
|
else:
|
||||||
|
# Value(s) come from a fixed vocabulary whose texts are in
|
||||||
|
# i18n files.
|
||||||
maxMult = appyType['multiplicity'][1]
|
maxMult = appyType['multiplicity'][1]
|
||||||
t = self.translate
|
t = self.translate
|
||||||
if (maxMult == None) or (maxMult > 1):
|
if (maxMult == None) or (maxMult > 1):
|
||||||
|
@ -866,6 +873,10 @@ class AbstractMixin:
|
||||||
# I create a new entry "backd"; if I put the dict in "back" I
|
# I create a new entry "backd"; if I put the dict in "back" I
|
||||||
# really modify the initial appyType object and I don't want to do
|
# really modify the initial appyType object and I don't want to do
|
||||||
# this.
|
# this.
|
||||||
|
# Add the i18n label for the field
|
||||||
|
if not res.has_key('label'):
|
||||||
|
res['label'] = '%s_%s' % (self._appy_getAtType(appyType.selfClass),
|
||||||
|
fieldName)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def _appy_getAtType(self, appyClass, flavour=None):
|
def _appy_getAtType(self, appyClass, flavour=None):
|
||||||
|
@ -979,6 +990,26 @@ class AbstractMixin:
|
||||||
res.append( (v, self.utranslate(labels[i], domain=domain)))
|
res.append( (v, self.utranslate(labels[i], domain=domain)))
|
||||||
return self.getProductConfig().DisplayList(tuple(res))
|
return self.getProductConfig().DisplayList(tuple(res))
|
||||||
|
|
||||||
|
def _appy_getDynamicDisplayList(self, methodName):
|
||||||
|
'''Calls the method named p_methodName for producing a DisplayList from
|
||||||
|
values computed dynamically. If methodName begins with _appy_, it is
|
||||||
|
a special Appy method: we will call it on the Mixin directly. Else,
|
||||||
|
it is a user method: we will call it on the wrapper. Some args can
|
||||||
|
be hidden into p_methodName, separated with stars, like in this
|
||||||
|
example: method1*arg1*arg2. Only string params are supported.'''
|
||||||
|
# Unwrap parameters if any.
|
||||||
|
if methodName.find('*') != -1:
|
||||||
|
elems = methodName.split('*')
|
||||||
|
methodName = elems[0]
|
||||||
|
args = elems[1:]
|
||||||
|
else:
|
||||||
|
args = ()
|
||||||
|
if methodName.startswith('_appy_'):
|
||||||
|
exec 'res = self.%s(*args)' % methodName
|
||||||
|
else:
|
||||||
|
exec 'res = self.appy().%s(*args)' % methodName
|
||||||
|
return self.getProductConfig().DisplayList(tuple(res))
|
||||||
|
|
||||||
nullValues = (None, '', ' ')
|
nullValues = (None, '', ' ')
|
||||||
numbersMap = {'Integer': 'int', 'Float': 'float'}
|
numbersMap = {'Integer': 'int', 'Float': 'float'}
|
||||||
validatorTypes = (types.FunctionType, type(String.EMAIL))
|
validatorTypes = (types.FunctionType, type(String.EMAIL))
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
import copy, types
|
import copy, types
|
||||||
from appy.gen import Type, Integer, String, File, Ref, Boolean
|
from appy.gen import Type, Integer, String, File, Ref, Boolean, Selection
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
class ModelClass:
|
class ModelClass:
|
||||||
|
@ -22,13 +22,14 @@ class ModelClass:
|
||||||
# instance of a ModelClass, those attributes must not be
|
# instance of a ModelClass, those attributes must not be
|
||||||
# given in the constructor.
|
# given in the constructor.
|
||||||
|
|
||||||
|
@classmethod
|
||||||
def _appy_addField(klass, fieldName, fieldType, classDescr):
|
def _appy_addField(klass, fieldName, fieldType, classDescr):
|
||||||
exec "klass.%s = fieldType" % fieldName
|
exec "klass.%s = fieldType" % fieldName
|
||||||
klass._appy_attributes.append(fieldName)
|
klass._appy_attributes.append(fieldName)
|
||||||
if hasattr(klass, '_appy_classes'):
|
if hasattr(klass, '_appy_classes'):
|
||||||
klass._appy_classes[fieldName] = classDescr.name
|
klass._appy_classes[fieldName] = classDescr.name
|
||||||
_appy_addField = classmethod(_appy_addField)
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
def _appy_getTypeBody(klass, appyType):
|
def _appy_getTypeBody(klass, appyType):
|
||||||
'''This method returns the code declaration for p_appyType.'''
|
'''This method returns the code declaration for p_appyType.'''
|
||||||
typeArgs = ''
|
typeArgs = ''
|
||||||
|
@ -45,10 +46,12 @@ class ModelClass:
|
||||||
attrValue = attrValue.__name__
|
attrValue = attrValue.__name__
|
||||||
else:
|
else:
|
||||||
attrValue = '%s.%s' % (moduleName, attrValue.__name__)
|
attrValue = '%s.%s' % (moduleName, attrValue.__name__)
|
||||||
|
elif isinstance(attrValue, Selection):
|
||||||
|
attrValue = 'Selection("%s")' % attrValue.methodName
|
||||||
typeArgs += '%s=%s,' % (attrName, attrValue)
|
typeArgs += '%s=%s,' % (attrName, attrValue)
|
||||||
return '%s(%s)' % (appyType.__class__.__name__, typeArgs)
|
return '%s(%s)' % (appyType.__class__.__name__, typeArgs)
|
||||||
_appy_getTypeBody = classmethod(_appy_getTypeBody)
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
def _appy_getBody(klass):
|
def _appy_getBody(klass):
|
||||||
'''This method returns the code declaration of this class. We will dump
|
'''This method returns the code declaration of this class. We will dump
|
||||||
this in appyWrappers.py in the resulting product.'''
|
this in appyWrappers.py in the resulting product.'''
|
||||||
|
@ -57,7 +60,6 @@ class ModelClass:
|
||||||
exec 'appyType = klass.%s' % attrName
|
exec 'appyType = klass.%s' % attrName
|
||||||
res += ' %s=%s\n' % (attrName, klass._appy_getTypeBody(appyType))
|
res += ' %s=%s\n' % (attrName, klass._appy_getTypeBody(appyType))
|
||||||
return res
|
return res
|
||||||
_appy_getBody = classmethod(_appy_getBody)
|
|
||||||
|
|
||||||
class PodTemplate(ModelClass):
|
class PodTemplate(ModelClass):
|
||||||
description = String(format=String.TEXT)
|
description = String(format=String.TEXT)
|
||||||
|
@ -87,6 +89,7 @@ class Flavour(ModelClass):
|
||||||
# We need to remember the original classes related to the flavour attributes
|
# We need to remember the original classes related to the flavour attributes
|
||||||
_appy_attributes = list(defaultFlavourAttrs)
|
_appy_attributes = list(defaultFlavourAttrs)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
def _appy_clean(klass):
|
def _appy_clean(klass):
|
||||||
toClean = []
|
toClean = []
|
||||||
for k, v in klass.__dict__.iteritems():
|
for k, v in klass.__dict__.iteritems():
|
||||||
|
@ -97,8 +100,8 @@ class Flavour(ModelClass):
|
||||||
exec 'del klass.%s' % k
|
exec 'del klass.%s' % k
|
||||||
klass._appy_attributes = list(defaultFlavourAttrs)
|
klass._appy_attributes = list(defaultFlavourAttrs)
|
||||||
klass._appy_classes = {}
|
klass._appy_classes = {}
|
||||||
_appy_clean = classmethod(_appy_clean)
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
def _appy_copyField(klass, appyType):
|
def _appy_copyField(klass, appyType):
|
||||||
'''From a given p_appyType, produce a type definition suitable for
|
'''From a given p_appyType, produce a type definition suitable for
|
||||||
storing the default value for this field.'''
|
storing the default value for this field.'''
|
||||||
|
@ -121,8 +124,8 @@ class Flavour(ModelClass):
|
||||||
res.back.show = False
|
res.back.show = False
|
||||||
res.select = None # Not callable from flavour
|
res.select = None # Not callable from flavour
|
||||||
return res
|
return res
|
||||||
_appy_copyField = classmethod(_appy_copyField)
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
def _appy_addOptionalField(klass, fieldDescr):
|
def _appy_addOptionalField(klass, fieldDescr):
|
||||||
className = fieldDescr.classDescr.name
|
className = fieldDescr.classDescr.name
|
||||||
fieldName = 'optionalFieldsFor%s' % className
|
fieldName = 'optionalFieldsFor%s' % className
|
||||||
|
@ -134,8 +137,8 @@ class Flavour(ModelClass):
|
||||||
fieldType.validator.append(fieldDescr.fieldName)
|
fieldType.validator.append(fieldDescr.fieldName)
|
||||||
fieldType.page = 'data'
|
fieldType.page = 'data'
|
||||||
fieldType.group = fieldDescr.classDescr.klass.__name__
|
fieldType.group = fieldDescr.classDescr.klass.__name__
|
||||||
_appy_addOptionalField = classmethod(_appy_addOptionalField)
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
def _appy_addDefaultField(klass, fieldDescr):
|
def _appy_addDefaultField(klass, fieldDescr):
|
||||||
className = fieldDescr.classDescr.name
|
className = fieldDescr.classDescr.name
|
||||||
fieldName = 'defaultValueFor%s_%s' % (className, fieldDescr.fieldName)
|
fieldName = 'defaultValueFor%s_%s' % (className, fieldDescr.fieldName)
|
||||||
|
@ -143,8 +146,8 @@ class Flavour(ModelClass):
|
||||||
klass._appy_addField(fieldName, fieldType, fieldDescr.classDescr)
|
klass._appy_addField(fieldName, fieldType, fieldDescr.classDescr)
|
||||||
fieldType.page = 'data'
|
fieldType.page = 'data'
|
||||||
fieldType.group = fieldDescr.classDescr.klass.__name__
|
fieldType.group = fieldDescr.classDescr.klass.__name__
|
||||||
_appy_addDefaultField = classmethod(_appy_addDefaultField)
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
def _appy_addPodField(klass, classDescr):
|
def _appy_addPodField(klass, classDescr):
|
||||||
'''Adds a POD field to the flavour and also an integer field that will
|
'''Adds a POD field to the flavour and also an integer field that will
|
||||||
determine the maximum number of documents to show at once on consult
|
determine the maximum number of documents to show at once on consult
|
||||||
|
@ -163,21 +166,47 @@ class Flavour(ModelClass):
|
||||||
klass._appy_addField(fieldName, fieldType, classDescr)
|
klass._appy_addField(fieldName, fieldType, classDescr)
|
||||||
classDescr.flavourFieldsToPropagate.append(
|
classDescr.flavourFieldsToPropagate.append(
|
||||||
('podMaxShownTemplatesFor%s', copy.copy(fieldType)) )
|
('podMaxShownTemplatesFor%s', copy.copy(fieldType)) )
|
||||||
_appy_addPodField = classmethod(_appy_addPodField)
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
def _appy_addQueryResultColumns(klass, classDescr):
|
def _appy_addQueryResultColumns(klass, classDescr):
|
||||||
|
'''Adds, for class p_classDescr, the attribute in the flavour that
|
||||||
|
allows to select what default columns will be shown on query
|
||||||
|
results.'''
|
||||||
className = classDescr.name
|
className = classDescr.name
|
||||||
fieldName = 'resultColumnsFor%s' % className
|
fieldName = 'resultColumnsFor%s' % className
|
||||||
attrNames = [a[0] for a in classDescr.getOrderedAppyAttributes()]
|
fieldType = String(multiplicity=(0,None), validator=Selection(
|
||||||
attrNames.append('workflowState') # Object state from workflow
|
'_appy_getAllFields*%s' % className), page='userInterface',
|
||||||
if 'title' in attrNames:
|
|
||||||
attrNames.remove('title') # Included by default.
|
|
||||||
fieldType = String(multiplicity=(0,None), validator=attrNames,
|
|
||||||
page='userInterface',
|
|
||||||
group=classDescr.klass.__name__)
|
group=classDescr.klass.__name__)
|
||||||
klass._appy_addField(fieldName, fieldType, classDescr)
|
klass._appy_addField(fieldName, fieldType, classDescr)
|
||||||
_appy_addQueryResultColumns = classmethod(_appy_addQueryResultColumns)
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _appy_addSearchRelatedFields(klass, classDescr):
|
||||||
|
'''Adds, for class p_classDescr, attributes related to the search
|
||||||
|
functionality for class p_classDescr.'''
|
||||||
|
className = classDescr.name
|
||||||
|
# Field that defines if advanced search is enabled for class
|
||||||
|
# p_classDescr or not.
|
||||||
|
fieldName = 'enableAdvancedSearchFor%s' % className
|
||||||
|
fieldType = Boolean(default=True, page='userInterface',
|
||||||
|
group=classDescr.klass.__name__)
|
||||||
|
klass._appy_addField(fieldName, fieldType, classDescr)
|
||||||
|
# Field that defines how many columns are shown on the custom search
|
||||||
|
# screen.
|
||||||
|
fieldName = 'numberOfSearchColumnsFor%s' % className
|
||||||
|
fieldType = Integer(default=3, page='userInterface',
|
||||||
|
group=classDescr.klass.__name__)
|
||||||
|
klass._appy_addField(fieldName, fieldType, classDescr)
|
||||||
|
# Field that allows to select, among all indexed fields, what fields
|
||||||
|
# must really be used in the search screen.
|
||||||
|
fieldName = 'searchFieldsFor%s' % className
|
||||||
|
defaultValue = [a[0] for a in classDescr.getOrderedAppyAttributes(
|
||||||
|
condition='attrValue.indexed')]
|
||||||
|
fieldType = String(multiplicity=(0,None), validator=Selection(
|
||||||
|
'_appy_getSearchableFields*%s' % className), default=defaultValue,
|
||||||
|
page='userInterface', group=classDescr.klass.__name__)
|
||||||
|
klass._appy_addField(fieldName, fieldType, classDescr)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
def _appy_addWorkflowFields(klass, classDescr):
|
def _appy_addWorkflowFields(klass, classDescr):
|
||||||
'''Adds, for a given p_classDescr, the workflow-related fields.'''
|
'''Adds, for a given p_classDescr, the workflow-related fields.'''
|
||||||
className = classDescr.name
|
className = classDescr.name
|
||||||
|
@ -209,8 +238,6 @@ class Flavour(ModelClass):
|
||||||
group=groupName)
|
group=groupName)
|
||||||
klass._appy_addField(fieldName, fieldType, classDescr)
|
klass._appy_addField(fieldName, fieldType, classDescr)
|
||||||
|
|
||||||
_appy_addWorkflowFields = classmethod(_appy_addWorkflowFields)
|
|
||||||
|
|
||||||
class Tool(ModelClass):
|
class Tool(ModelClass):
|
||||||
flavours = Ref(None, multiplicity=(1,None), add=True, link=False,
|
flavours = Ref(None, multiplicity=(1,None), add=True, link=False,
|
||||||
back=Ref(attribute='tool'))
|
back=Ref(attribute='tool'))
|
||||||
|
|
|
@ -647,7 +647,8 @@
|
||||||
totalNumber queryResult/totalNumber;
|
totalNumber queryResult/totalNumber;
|
||||||
batchSize queryResult/batchSize;
|
batchSize queryResult/batchSize;
|
||||||
ajaxHookId python:'queryResult';
|
ajaxHookId python:'queryResult';
|
||||||
baseUrl python: tool.getQueryUrl(contentType, flavourNumber, searchName, startNumber='**v**')">
|
baseUrl python: tool.getQueryUrl(contentType, flavourNumber, searchName, startNumber='**v**');
|
||||||
|
newSearchUrl python: '%s/skyn/search?type_name=%s&flavourNumber=%d' % (tool.getAppFolder().absolute_url(), contentType, flavourNumber);">
|
||||||
|
|
||||||
<tal:result condition="objs">
|
<tal:result condition="objs">
|
||||||
|
|
||||||
|
@ -655,6 +656,10 @@
|
||||||
<legend>
|
<legend>
|
||||||
<span tal:replace="structure python: test(searchName, tool.translate(searchLabel), test(severalTypes, tool.translate(tool.getAppName()), tool.translate('%s_plural' % contentType)))"/>
|
<span tal:replace="structure python: test(searchName, tool.translate(searchLabel), test(severalTypes, tool.translate(tool.getAppName()), tool.translate('%s_plural' % contentType)))"/>
|
||||||
(<span tal:replace="totalNumber"/>)
|
(<span tal:replace="totalNumber"/>)
|
||||||
|
<tal:newSearch condition="python: searchName == '_advanced'">
|
||||||
|
— <i><a tal:attributes="href newSearchUrl"
|
||||||
|
tal:content="python: tool.translate('search_new')"></a></i>
|
||||||
|
</tal:newSearch>
|
||||||
</legend>
|
</legend>
|
||||||
|
|
||||||
<table cellpadding="0" cellspacing="0" width="100%"><tr>
|
<table cellpadding="0" cellspacing="0" width="100%"><tr>
|
||||||
|
@ -787,9 +792,13 @@
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</tal:result>
|
</tal:result>
|
||||||
|
|
||||||
<span tal:condition="not: objs"
|
<tal:noResult condition="not: objs">
|
||||||
tal:content="python: tool.translate('query_no_result')">No result.
|
<span tal:replace="python: tool.translate('query_no_result')"/>
|
||||||
</span>
|
<tal:newSearch condition="python: searchName == '_advanced'">
|
||||||
|
<br/><i class="discreet"><a tal:attributes="href newSearchUrl"
|
||||||
|
tal:content="python: tool.translate('search_new')"></a></i>
|
||||||
|
</tal:newSearch>
|
||||||
|
</tal:noResult>
|
||||||
|
|
||||||
</metal:queryResults>
|
</metal:queryResults>
|
||||||
|
|
||||||
|
@ -883,7 +892,9 @@
|
||||||
<tal:comment replace="nothing">TODO: implement a widget for selecting the needed flavour.</tal:comment>
|
<tal:comment replace="nothing">TODO: implement a widget for selecting the needed flavour.</tal:comment>
|
||||||
|
|
||||||
<tal:comment replace="nothing">Create a section for every root class.</tal:comment>
|
<tal:comment replace="nothing">Create a section for every root class.</tal:comment>
|
||||||
<tal:section repeat="rootClass rootClasses" define="flavourNumber python:1">
|
<tal:section repeat="rootClass rootClasses"
|
||||||
|
define="flavourNumber python:1;
|
||||||
|
flavour python: tool.getFlavour('Dummy_%d' % flavourNumber)">
|
||||||
<tal:comment replace="nothing">Section title, with action icons</tal:comment>
|
<tal:comment replace="nothing">Section title, with action icons</tal:comment>
|
||||||
<dt tal:attributes="class python:test(repeat['rootClass'].number()==1, 'portletAppyItem', 'portletAppyItem portletSep')">
|
<dt tal:attributes="class python:test(repeat['rootClass'].number()==1, 'portletAppyItem', 'portletAppyItem portletSep')">
|
||||||
<table width="100%" cellspacing="0" cellpadding="0" class="no-style-table">
|
<table width="100%" cellspacing="0" cellpadding="0" class="no-style-table">
|
||||||
|
@ -911,6 +922,8 @@
|
||||||
title python: tool.translate('query_import')"/>
|
title python: tool.translate('query_import')"/>
|
||||||
<tal:comment replace="nothing">Search objects of this type (todo: update flavourNumber)</tal:comment>
|
<tal:comment replace="nothing">Search objects of this type (todo: update flavourNumber)</tal:comment>
|
||||||
<img style="cursor:pointer"
|
<img style="cursor:pointer"
|
||||||
|
tal:define="showSearch python: flavour.getAttr('enableAdvancedSearchFor%s' % rootClass)"
|
||||||
|
tal:condition="showSearch"
|
||||||
tal:attributes="onClick python: 'href: window.location=\'%s/skyn/search?type_name=%s&flavourNumber=1\'' % (appFolder.absolute_url(), rootClass);
|
tal:attributes="onClick python: 'href: window.location=\'%s/skyn/search?type_name=%s&flavourNumber=1\'' % (appFolder.absolute_url(), rootClass);
|
||||||
src string: $portal_url/skyn/search.gif;
|
src string: $portal_url/skyn/search.gif;
|
||||||
title python: tool.translate('search_objects')"/>
|
title python: tool.translate('search_objects')"/>
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
<table class="no-style-table" cellpadding="0" cellspacing="0">
|
<table class="no-style-table" cellpadding="0" cellspacing="0">
|
||||||
<tr>
|
<tr>
|
||||||
<tal:comment replace="nothing">Arrows for moving objects up or down</tal:comment>
|
<tal:comment replace="nothing">Arrows for moving objects up or down</tal:comment>
|
||||||
<td class="noPadding" tal:condition="python: (len(objs)>1) and member.has_permission('Modify portal content', obj)">
|
<td class="noPadding" tal:condition="python: (len(objs)>1) and member.has_permission('Modify portal content', contextObj)">
|
||||||
<tal:moveRef define="objectIndex python:contextObj.getAppyRefIndex(fieldName, obj);
|
<tal:moveRef define="objectIndex python:contextObj.getAppyRefIndex(fieldName, obj);
|
||||||
baseUrl python: contextObj.getUrl('showRef', **{'fieldName': fieldName, 'isBack': isBack, 'innerRef': innerRef, 'labelId': labelId, 'descrId': descrId, '%s_startNumber' % ajaxHookId: startNumber, 'action':'ChangeRefOrder', 'refObjectUid': obj.UID()})">
|
baseUrl python: contextObj.getUrl('showRef', **{'fieldName': fieldName, 'isBack': isBack, 'innerRef': innerRef, 'labelId': labelId, 'descrId': descrId, '%s_startNumber' % ajaxHookId: startNumber, 'action':'ChangeRefOrder', 'refObjectUid': obj.UID()})">
|
||||||
<tal:comment replace="nothing">Move up</tal:comment>
|
<tal:comment replace="nothing">Move up</tal:comment>
|
||||||
|
|
|
@ -16,10 +16,12 @@
|
||||||
tal:define="appFolder context/getParentNode;
|
tal:define="appFolder context/getParentNode;
|
||||||
contentType request/type_name;
|
contentType request/type_name;
|
||||||
tool python: portal.get('portal_%s' % appFolder.id.lower());
|
tool python: portal.get('portal_%s' % appFolder.id.lower());
|
||||||
searchableFields python: tool.getSearchableFields(contentType)">
|
flavour python: tool.getFlavour('Dummy_%s' % request['flavourNumber']);
|
||||||
|
searchableFields python: flavour.getSearchableFields(contentType)">
|
||||||
|
|
||||||
<tal:comment replace="nothing">Search title</tal:comment>
|
<tal:comment replace="nothing">Search title</tal:comment>
|
||||||
<h1 tal:content="python: tool.translate('search_title')"></h1><br/>
|
<h1><span tal:replace="python: tool.translate('%s_plural' % contentType)"/> —
|
||||||
|
<span tal:replace="python: tool.translate('search_title')"/></h1><br/>
|
||||||
|
|
||||||
<tal:comment replace="nothing">Form for searching objects of request/type_name.</tal:comment>
|
<tal:comment replace="nothing">Form for searching objects of request/type_name.</tal:comment>
|
||||||
<form name="search" tal:attributes="action python: appFolder.absolute_url()+'/skyn/do'" method="post">
|
<form name="search" tal:attributes="action python: appFolder.absolute_url()+'/skyn/do'" method="post">
|
||||||
|
@ -27,12 +29,20 @@
|
||||||
<input type="hidden" name="type_name" tal:attributes="value contentType"/>
|
<input type="hidden" name="type_name" tal:attributes="value contentType"/>
|
||||||
<input type="hidden" name="flavourNumber:int" tal:attributes="value request/flavourNumber"/>
|
<input type="hidden" name="flavourNumber:int" tal:attributes="value request/flavourNumber"/>
|
||||||
|
|
||||||
<tal:searchFields repeat="searchField searchableFields">
|
<table class="no-style-table" cellpadding="0" cellspacing="0" width="100%"
|
||||||
|
tal:define="numberOfColumns python: flavour.getAttr('numberOfSearchColumnsFor%s' % contentType)">
|
||||||
|
<tr tal:repeat="searchRow python: tool.tabularize(searchableFields, numberOfColumns)" valign="top" class="appySearchRow">
|
||||||
|
<td tal:repeat="searchField searchRow">
|
||||||
|
<tal:field condition="searchField">
|
||||||
<tal:showSearchField define="fieldName python:searchField[0];
|
<tal:showSearchField define="fieldName python:searchField[0];
|
||||||
appyType python:searchField[1]">
|
appyType python:searchField[1];
|
||||||
|
widgetName python: 'w_%s' % fieldName">
|
||||||
<metal:searchField use-macro="python: appFolder.skyn.widgets.macros.get('search%s' % searchField[1]['type'])"/>
|
<metal:searchField use-macro="python: appFolder.skyn.widgets.macros.get('search%s' % searchField[1]['type'])"/>
|
||||||
</tal:showSearchField>
|
</tal:showSearchField>
|
||||||
</tal:searchFields>
|
</tal:field><br/><br class="discreet"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
<tal:comment replace="nothing">Submit button</tal:comment>
|
<tal:comment replace="nothing">Submit button</tal:comment>
|
||||||
<p align="right"><br/>
|
<p align="right"><br/>
|
||||||
|
|
|
@ -7,11 +7,35 @@
|
||||||
</metal:searchFloat>
|
</metal:searchFloat>
|
||||||
|
|
||||||
<metal:searchString define-macro="searchString">
|
<metal:searchString define-macro="searchString">
|
||||||
<p tal:content="fieldName">Hello</p>
|
<label tal:attributes="for widgetName" tal:content="python: tool.translate(appyType['label'])"></label><br>
|
||||||
|
<tal:comment replace="nothing">Show a simple search field for most String fields.</tal:comment>
|
||||||
|
<tal:simpleSearch condition="not: appyType/isSelect">
|
||||||
|
<input type="text" tal:attributes="name widgetName"/>
|
||||||
|
</tal:simpleSearch>
|
||||||
|
<tal:comment replace="nothing">Show a multi-selection box for fields whose validator defines a list of values.</tal:comment>
|
||||||
|
<tal:selectSearch condition="appyType/isSelect">
|
||||||
|
<select tal:attributes="name widgetName" multiple="multiple" size="5">
|
||||||
|
<option tal:repeat="v appyType/validator" tal:attributes="value v"
|
||||||
|
tal:content="python: tool.truncate(tool.translate('%s_list_%s' % (appyType['label'], v)), 70)">
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</tal:selectSearch>
|
||||||
</metal:searchString>
|
</metal:searchString>
|
||||||
|
|
||||||
<metal:searchBoolean define-macro="searchBoolean">
|
<metal:searchBoolean define-macro="searchBoolean" tal:define="typedWidget python:'%s*bool' % widgetName">
|
||||||
<p tal:content="fieldName">Hello</p>
|
<label tal:attributes="for widgetName" tal:content="python: tool.translate(appyType['label'])"></label><br>
|
||||||
|
<tal:yes define="valueId python:'%s_yes' % fieldName">
|
||||||
|
<input type="radio" class="noborder" value="True" tal:attributes="name typedWidget; id valueId"/>
|
||||||
|
<label tal:attributes="for valueId" i18n:translate="yes" i18n:domain="plone"></label>
|
||||||
|
</tal:yes>
|
||||||
|
<tal:no define="valueId python:'%s_no' % fieldName">
|
||||||
|
<input type="radio" class="noborder" value="False" tal:attributes="name typedWidget; id valueId"/>
|
||||||
|
<label tal:attributes="for valueId" i18n:translate="no" i18n:domain="plone"></label>
|
||||||
|
</tal:no>
|
||||||
|
<tal:whatever define="valueId python:'%s_whatever' % fieldName">
|
||||||
|
<input type="radio" class="noborder" value="" tal:attributes="name typedWidget; id valueId" checked="checked"/>
|
||||||
|
<label tal:attributes="for valueId" tal:content="python: tool.translate('whatever')"></label>
|
||||||
|
</tal:whatever>
|
||||||
</metal:searchBoolean>
|
</metal:searchBoolean>
|
||||||
|
|
||||||
<metal:searchDate define-macro="searchDate">
|
<metal:searchDate define-macro="searchDate">
|
||||||
|
|
|
@ -42,4 +42,8 @@ referers = {
|
||||||
# names of DC transitions.
|
# names of DC transitions.
|
||||||
workflowInstances = {}
|
workflowInstances = {}
|
||||||
<!workflowInstancesInit!>
|
<!workflowInstancesInit!>
|
||||||
|
|
||||||
|
# In the following dict, we store, for every Appy class, the ordered list of
|
||||||
|
# attributes (included inherited attributes).
|
||||||
|
attributes = {<!attributes!>}
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -41,6 +41,18 @@ class FlavourWrapper:
|
||||||
Stores the list of columns that must be show when displaying
|
Stores the list of columns that must be show when displaying
|
||||||
instances of the a given root p_klass.
|
instances of the a given root p_klass.
|
||||||
|
|
||||||
|
"enableAdvancedSearch"
|
||||||
|
Determines if the advanced search screen must be enabled for
|
||||||
|
p_klass.
|
||||||
|
|
||||||
|
"numberOfSearchColumns"
|
||||||
|
Determines in how many columns the search screen for p_klass
|
||||||
|
is rendered.
|
||||||
|
|
||||||
|
"searchFields"
|
||||||
|
Determines, among all indexed fields for p_klass, which one will
|
||||||
|
really be used in the search screen.
|
||||||
|
|
||||||
"optionalFields"
|
"optionalFields"
|
||||||
Stores the list of optional attributes that are in use in the
|
Stores the list of optional attributes that are in use in the
|
||||||
current flavour for the given p_klass.
|
current flavour for the given p_klass.
|
||||||
|
|
|
@ -61,6 +61,7 @@ class PoMessage:
|
||||||
SEARCH_BUTTON = 'Search'
|
SEARCH_BUTTON = 'Search'
|
||||||
SEARCH_OBJECTS = 'Search objects of this type.'
|
SEARCH_OBJECTS = 'Search objects of this type.'
|
||||||
SEARCH_RESULTS = 'Search results'
|
SEARCH_RESULTS = 'Search results'
|
||||||
|
SEARCH_NEW = 'New search'
|
||||||
WORKFLOW_COMMENT = 'Optional comment'
|
WORKFLOW_COMMENT = 'Optional comment'
|
||||||
WORKFLOW_STATE = 'state'
|
WORKFLOW_STATE = 'state'
|
||||||
DATA_CHANGE = 'Data change'
|
DATA_CHANGE = 'Data change'
|
||||||
|
@ -93,6 +94,7 @@ class PoMessage:
|
||||||
GOTO_NEXT = 'Go to next'
|
GOTO_NEXT = 'Go to next'
|
||||||
GOTO_LAST = 'Go to end'
|
GOTO_LAST = 'Go to end'
|
||||||
GOTO_SOURCE = 'Go back'
|
GOTO_SOURCE = 'Go back'
|
||||||
|
WHATEVER = 'Whatever'
|
||||||
|
|
||||||
def __init__(self, id, msg, default, fuzzy=False, comments=[]):
|
def __init__(self, id, msg, default, fuzzy=False, comments=[]):
|
||||||
self.id = id
|
self.id = id
|
||||||
|
|
Loading…
Reference in a new issue