First version of a searches system, where queries may be defined on every root class. The portlet and query pages have been deeply revised. Ajax is now used for displaying query results and appy-specific ajax-based navigation is used for refs as well as for queries in portal_catalog.
This commit is contained in:
parent
2b907fee32
commit
1c0744da85
|
@ -35,7 +35,7 @@ class Search:
|
||||||
self.sortBy = sortBy
|
self.sortBy = sortBy
|
||||||
self.limit = limit
|
self.limit = limit
|
||||||
self.fields = fields # This is a dict whose keys are indexed field
|
self.fields = fields # This is a dict whose keys are indexed field
|
||||||
# names and whosse values are search values.
|
# names and whose values are search values.
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
class Type:
|
class Type:
|
||||||
|
@ -162,7 +162,8 @@ class String(Type):
|
||||||
character is ignored. If p_complement is True, it does compute the
|
character is ignored. If p_complement is True, it does compute the
|
||||||
complement of modulo 97 instead of modulo 97. p_obj is not used;
|
complement of modulo 97 instead of modulo 97. p_obj is not used;
|
||||||
it will be given by the Appy validation machinery, so it must be
|
it will be given by the Appy validation machinery, so it must be
|
||||||
specified as parameter.'''
|
specified as parameter. The function returns True if the check is
|
||||||
|
successful.'''
|
||||||
if not value: return True # Plone calls me erroneously for
|
if not value: return True # Plone calls me erroneously for
|
||||||
# non-mandatory fields.
|
# non-mandatory fields.
|
||||||
# First, remove any non-digit char
|
# First, remove any non-digit char
|
||||||
|
|
|
@ -116,9 +116,6 @@ class ArchetypeFieldDescriptor:
|
||||||
self.widgetParams['size'] = 50
|
self.widgetParams['size'] = 50
|
||||||
if self.appyType.width:
|
if self.appyType.width:
|
||||||
self.widgetParams['size'] = self.appyType.width
|
self.widgetParams['size'] = self.appyType.width
|
||||||
# Manage index
|
|
||||||
if self.appyType.searchable:
|
|
||||||
self.fieldParams['index'] = 'FieldIndex'
|
|
||||||
elif self.appyType.format == String.TEXT:
|
elif self.appyType.format == String.TEXT:
|
||||||
self.fieldType = 'TextField'
|
self.fieldType = 'TextField'
|
||||||
self.widgetType = 'TextAreaWidget'
|
self.widgetType = 'TextAreaWidget'
|
||||||
|
@ -132,9 +129,6 @@ class ArchetypeFieldDescriptor:
|
||||||
else:
|
else:
|
||||||
self.fieldType = 'StringField'
|
self.fieldType = 'StringField'
|
||||||
self.widgetType = 'StringWidget'
|
self.widgetType = 'StringWidget'
|
||||||
# Manage searchability
|
|
||||||
if self.appyType.searchable:
|
|
||||||
self.fieldParams['searchable'] = True
|
|
||||||
|
|
||||||
def walkComputed(self):
|
def walkComputed(self):
|
||||||
'''How to generate a computed field? We generate an Archetypes String
|
'''How to generate a computed field? We generate an Archetypes String
|
||||||
|
@ -206,11 +200,9 @@ class ArchetypeFieldDescriptor:
|
||||||
self.fieldParams['default'] = self.appyType.default
|
self.fieldParams['default'] = self.appyType.default
|
||||||
# - required?
|
# - required?
|
||||||
if self.appyType.multiplicity[0] >= 1:
|
if self.appyType.multiplicity[0] >= 1:
|
||||||
if self.appyType.type != 'Ref':
|
if self.appyType.type != 'Ref': self.fieldParams['required'] = True
|
||||||
# Indeed, if it is a ref appy will manage itself field updates
|
# Indeed, if it is a ref appy will manage itself field updates in
|
||||||
# in at_post_create_script, so Archetypes must not enforce
|
# onEdit, so Archetypes must not enforce required=True
|
||||||
# required=True
|
|
||||||
self.fieldParams['required'] = True
|
|
||||||
# - optional ?
|
# - optional ?
|
||||||
if self.appyType.optional:
|
if self.appyType.optional:
|
||||||
Flavour._appy_addOptionalField(self)
|
Flavour._appy_addOptionalField(self)
|
||||||
|
@ -222,14 +214,20 @@ class ArchetypeFieldDescriptor:
|
||||||
methodName = 'getDefaultValueFor%s' % self.fieldName
|
methodName = 'getDefaultValueFor%s' % self.fieldName
|
||||||
self.fieldParams['default_method'] = methodName
|
self.fieldParams['default_method'] = methodName
|
||||||
self.classDescr.addDefaultMethod(methodName, self)
|
self.classDescr.addDefaultMethod(methodName, self)
|
||||||
|
# - put an index on this field?
|
||||||
|
if self.appyType.indexed:
|
||||||
|
if (self.appyType.type == 'String') and \
|
||||||
|
(self.appyType.isMultiValued()):
|
||||||
|
self.fieldParams['index'] = 'ZCTextIndex, lexicon_id=' \
|
||||||
|
'plone_lexicon, index_type=Okapi BM25 Rank'
|
||||||
|
else:
|
||||||
|
self.fieldParams['index'] = 'FieldIndex'
|
||||||
# - searchable ?
|
# - searchable ?
|
||||||
if self.appyType.searchable and (self.appyType.type != 'String'):
|
if self.appyType.searchable: self.fieldParams['searchable'] = True
|
||||||
self.fieldParams['index'] = 'FieldIndex'
|
|
||||||
# - slaves ?
|
# - slaves ?
|
||||||
if self.appyType.slaves:
|
if self.appyType.slaves: self.widgetParams['visible'] = False
|
||||||
self.widgetParams['visible'] = False # Archetypes will believe the
|
# Archetypes will believe the field is invisible; we will display it
|
||||||
# field is invisible; we will display it ourselves (like for Ref
|
# ourselves (like for Ref fields)
|
||||||
# fields)
|
|
||||||
# - need to generate a field validator?
|
# - need to generate a field validator?
|
||||||
# In all cases, add an i18n message for the validation error for this
|
# In all cases, add an i18n message for the validation error for this
|
||||||
# field.
|
# field.
|
||||||
|
@ -490,6 +488,14 @@ class ArchetypesClassDescriptor(ClassDescriptor):
|
||||||
else: res.append(search)
|
else: res.append(search)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def getSearch(klass, searchName):
|
||||||
|
'''Gets the search named p_searchName.'''
|
||||||
|
for search in ArchetypesClassDescriptor.getSearches(klass):
|
||||||
|
if search.name == searchName:
|
||||||
|
return search
|
||||||
|
return None
|
||||||
|
|
||||||
def addGenerateDocMethod(self):
|
def addGenerateDocMethod(self):
|
||||||
m = self.methods
|
m = self.methods
|
||||||
spaces = TABS
|
spaces = TABS
|
||||||
|
|
|
@ -660,13 +660,17 @@ class Generator(AbstractGenerator):
|
||||||
'implements': implements, 'baseSchema': baseSchema,
|
'implements': implements, 'baseSchema': baseSchema,
|
||||||
'register': register, 'toolInstanceName': self.toolInstanceName})
|
'register': register, 'toolInstanceName': self.toolInstanceName})
|
||||||
fileName = '%s.py' % classDescr.name
|
fileName = '%s.py' % classDescr.name
|
||||||
# Remember i18n labels that will be generated in the i18n file
|
# Create i18n labels (class name, description and plural form)
|
||||||
poMsg = PoMessage(classDescr.name, '', classDescr.klass.__name__)
|
poMsg = PoMessage(classDescr.name, '', classDescr.klass.__name__)
|
||||||
poMsg.produceNiceDefault()
|
poMsg.produceNiceDefault()
|
||||||
self.labels.append(poMsg)
|
self.labels.append(poMsg)
|
||||||
poMsgDescr = PoMessage('%s_edit_descr' % classDescr.name, '', ' ')
|
poMsgDescr = PoMessage('%s_edit_descr' % classDescr.name, '', ' ')
|
||||||
self.labels.append(poMsgDescr)
|
self.labels.append(poMsgDescr)
|
||||||
# Remember i18n labels for flavoured variants
|
poMsgPl = PoMessage('%s_plural' % classDescr.name, '',
|
||||||
|
classDescr.klass.__name__+'s')
|
||||||
|
poMsgPl.produceNiceDefault()
|
||||||
|
self.labels.append(poMsgPl)
|
||||||
|
# Create i18n labels for flavoured variants
|
||||||
for i in range(2,10):
|
for i in range(2,10):
|
||||||
poMsg = PoMessage('%s_%d' % (classDescr.name, i), '',
|
poMsg = PoMessage('%s_%d' % (classDescr.name, i), '',
|
||||||
classDescr.klass.__name__)
|
classDescr.klass.__name__)
|
||||||
|
@ -675,7 +679,11 @@ class Generator(AbstractGenerator):
|
||||||
poMsgDescr = PoMessage('%s_%d_edit_descr' % (classDescr.name, i),
|
poMsgDescr = PoMessage('%s_%d_edit_descr' % (classDescr.name, i),
|
||||||
'', ' ')
|
'', ' ')
|
||||||
self.labels.append(poMsgDescr)
|
self.labels.append(poMsgDescr)
|
||||||
# Remember i18n labels for searches
|
poMsgPl = PoMessage('%s_%d_plural' % (classDescr.name, i), '',
|
||||||
|
classDescr.klass.__name__+'s')
|
||||||
|
poMsgPl.produceNiceDefault()
|
||||||
|
self.labels.append(poMsgPl)
|
||||||
|
# Create i18n labels for searches
|
||||||
for search in classDescr.getSearches(classDescr.klass):
|
for search in classDescr.getSearches(classDescr.klass):
|
||||||
searchLabelId = '%s_search_%s' % (classDescr.name, search.name)
|
searchLabelId = '%s_search_%s' % (classDescr.name, search.name)
|
||||||
searchDescrId = '%s_descr' % searchLabelId
|
searchDescrId = '%s_descr' % searchLabelId
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
import re, os, os.path
|
import re, os, os.path
|
||||||
from appy.gen.utils import FieldDescr
|
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
|
||||||
from appy.gen.plone25.wrappers import AbstractWrapper
|
from appy.gen.plone25.wrappers import AbstractWrapper
|
||||||
|
from appy.gen.plone25.descriptors import ArchetypesClassDescriptor
|
||||||
|
|
||||||
_PY = 'Please specify a file corresponding to a Python interpreter ' \
|
_PY = 'Please specify a file corresponding to a Python interpreter ' \
|
||||||
'(ie "/usr/bin/python").'
|
'(ie "/usr/bin/python").'
|
||||||
|
@ -97,24 +98,46 @@ class ToolMixin(AbstractMixin):
|
||||||
def showPortlet(self):
|
def showPortlet(self):
|
||||||
return not self.portal_membership.isAnonymousUser()
|
return not self.portal_membership.isAnonymousUser()
|
||||||
|
|
||||||
def executeQuery(self, queryName, flavourNumber):
|
def executeQuery(self, contentType, flavourNumber=1, searchName=None,
|
||||||
if queryName.find(',') != -1:
|
startNumber=0):
|
||||||
|
'''Executes a query on a given p_contentType (or several, separated
|
||||||
|
with commas) in Plone's portal_catalog. Portal types are from the
|
||||||
|
flavour numbered p_flavourNumber. If p_searchName is specified, it
|
||||||
|
corresponds to a search defined on p_contentType: additional search
|
||||||
|
criteria will be added to the query. We will retrieve objects from
|
||||||
|
p_startNumber.'''
|
||||||
|
# Is there one or several content types ?
|
||||||
|
if contentType.find(',') != -1:
|
||||||
# Several content types are specified
|
# Several content types are specified
|
||||||
portalTypes = queryName.split(',')
|
portalTypes = contentType.split(',')
|
||||||
if flavourNumber != 1:
|
if flavourNumber != 1:
|
||||||
portalTypes = ['%s_%d' % (pt, flavourNumber) \
|
portalTypes = ['%s_%d' % (pt, flavourNumber) \
|
||||||
for pt in portalTypes]
|
for pt in portalTypes]
|
||||||
else:
|
else:
|
||||||
portalTypes = queryName
|
portalTypes = contentType
|
||||||
params = {'portal_type': portalTypes, 'batch': True}
|
params = {'portal_type': portalTypes, 'batch': True}
|
||||||
res = self.portal_catalog.searchResults(**params)
|
# Manage additional criteria from a search when relevant
|
||||||
batchStart = self.REQUEST.get('b_start', 0)
|
if searchName:
|
||||||
res = self.getProductConfig().Batch(res,
|
# In this case, contentType must contain a single content type.
|
||||||
self.getNumberOfResultsPerPage(), int(batchStart), orphan=0)
|
appyClass = self.getAppyClass(contentType)
|
||||||
return res
|
# Find the search
|
||||||
|
search = ArchetypesClassDescriptor.getSearch(appyClass, searchName)
|
||||||
|
for fieldName, fieldValue in search.fields.iteritems():
|
||||||
|
appyType = getattr(appyClass, fieldName)
|
||||||
|
attrName = fieldName
|
||||||
|
if (appyType.type == 'String') and appyType.isMultiValued():
|
||||||
|
attrName = 'get%s%s' % (fieldName[0].upper(), fieldName[1:])
|
||||||
|
params[attrName] = fieldValue
|
||||||
|
brains = self.portal_catalog.searchResults(**params)
|
||||||
|
print 'Number of results per page is', self.getNumberOfResultsPerPage()
|
||||||
|
print 'StartNumber is', startNumber
|
||||||
|
res = SomeObjects(brains, self.getNumberOfResultsPerPage(), startNumber)
|
||||||
|
res.brainsToObjects()
|
||||||
|
print 'Res?', res.totalNumber, res.batchSize, res.startNumber
|
||||||
|
return res.__dict__
|
||||||
|
|
||||||
def getResultColumnsNames(self, queryName):
|
def getResultColumnsNames(self, contentType):
|
||||||
contentTypes = queryName.strip(',').split(',')
|
contentTypes = contentType.strip(',').split(',')
|
||||||
resSet = None # Temporary set for computing intersections.
|
resSet = None # Temporary set for computing intersections.
|
||||||
res = [] # Final, sorted result.
|
res = [] # Final, sorted result.
|
||||||
flavour = None
|
flavour = None
|
||||||
|
@ -137,12 +160,12 @@ class ToolMixin(AbstractMixin):
|
||||||
res.append(fieldName)
|
res.append(fieldName)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def getResultColumns(self, anObject, queryName):
|
def getResultColumns(self, anObject, contentType):
|
||||||
'''What columns must I show when displaying a list of root class
|
'''What columns must I show when displaying a list of root class
|
||||||
instances? Result is a list of tuples containing the name of the
|
instances? Result is a list of tuples containing the name of the
|
||||||
column (=name of the field) and a FieldDescr instance.'''
|
column (=name of the field) and a FieldDescr instance.'''
|
||||||
res = []
|
res = []
|
||||||
for fieldName in self.getResultColumnsNames(queryName):
|
for fieldName in self.getResultColumnsNames(contentType):
|
||||||
if fieldName == 'workflowState':
|
if fieldName == 'workflowState':
|
||||||
# We do not return a FieldDescr instance if the attributes is
|
# We do not return a FieldDescr instance if the attributes is
|
||||||
# not a *real* attribute but the workfow state.
|
# not a *real* attribute but the workfow state.
|
||||||
|
@ -297,4 +320,20 @@ class ToolMixin(AbstractMixin):
|
||||||
for msg in jsMessages:
|
for msg in jsMessages:
|
||||||
res += 'var %s = "%s";\n' % (msg, self.translate(msg))
|
res += 'var %s = "%s";\n' % (msg, self.translate(msg))
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def getSearches(self, contentType):
|
||||||
|
'''Returns the searches that are defined for p_contentType.'''
|
||||||
|
appyClass = self.getAppyClass(contentType)
|
||||||
|
return [s.__dict__ for s in \
|
||||||
|
ArchetypesClassDescriptor.getSearches(appyClass)]
|
||||||
|
|
||||||
|
def getQueryUrl(self, contentType, flavourNumber, searchName):
|
||||||
|
'''This method creates the URL that allows to perform an ajax GET
|
||||||
|
request for getting queried objects from a search named p_searchName
|
||||||
|
on p_contentType from flavour numbered p_flavourNumber.'''
|
||||||
|
return self.getAppFolder().absolute_url() + '/skyn/ajax?objectUid=%s' \
|
||||||
|
'&page=macros¯o=queryResult&contentType=%s&flavourNumber=%s' \
|
||||||
|
'&searchName=%s&startNumber=' % (self.UID(), contentType,
|
||||||
|
flavourNumber, searchName)
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -10,7 +10,7 @@ import os, os.path, sys, types, mimetypes
|
||||||
import appy.gen
|
import appy.gen
|
||||||
from appy.gen import String
|
from appy.gen import String
|
||||||
from appy.gen.utils import FieldDescr, GroupDescr, PhaseDescr, StateDescr, \
|
from appy.gen.utils import FieldDescr, GroupDescr, PhaseDescr, StateDescr, \
|
||||||
ValidationErrors, sequenceTypes, RefObjects
|
ValidationErrors, sequenceTypes, SomeObjects
|
||||||
from appy.gen.plone25.descriptors import ArchetypesClassDescriptor
|
from appy.gen.plone25.descriptors import ArchetypesClassDescriptor
|
||||||
from appy.gen.plone25.utils import updateRolesForPermission, getAppyRequest
|
from appy.gen.plone25.utils import updateRolesForPermission, getAppyRequest
|
||||||
|
|
||||||
|
@ -210,7 +210,7 @@ class AbstractMixin:
|
||||||
for uid in toDelete:
|
for uid in toDelete:
|
||||||
sortedUids.remove(uid)
|
sortedUids.remove(uid)
|
||||||
# Prepare the result
|
# Prepare the result
|
||||||
res = RefObjects()
|
res = SomeObjects()
|
||||||
res.totalNumber = res.batchSize = len(sortedUids)
|
res.totalNumber = res.batchSize = len(sortedUids)
|
||||||
if batchNeeded:
|
if batchNeeded:
|
||||||
res.batchSize = appyType['maxPerPage']
|
res.batchSize = appyType['maxPerPage']
|
||||||
|
@ -218,7 +218,7 @@ class AbstractMixin:
|
||||||
res.startNumber = startNumber
|
res.startNumber = startNumber
|
||||||
# Get the needed referred objects
|
# Get the needed referred objects
|
||||||
i = res.startNumber
|
i = res.startNumber
|
||||||
# Is is possible and more efficient to perform a single query in
|
# Is it possible and more efficient to perform a single query in
|
||||||
# uid_catalog and get the result in the order of specified uids?
|
# uid_catalog and get the result in the order of specified uids?
|
||||||
while i < (res.startNumber + res.batchSize):
|
while i < (res.startNumber + res.batchSize):
|
||||||
if i >= res.totalNumber: break
|
if i >= res.totalNumber: break
|
||||||
|
@ -245,7 +245,7 @@ class AbstractMixin:
|
||||||
startNumber=startNumber).__dict__
|
startNumber=startNumber).__dict__
|
||||||
else:
|
else:
|
||||||
# Note Pagination is not yet implemented for backward ref.
|
# Note Pagination is not yet implemented for backward ref.
|
||||||
return RefObjects(self.getBRefs(fieldName)).__dict__
|
return SomeObjects(self.getBRefs(fieldName)).__dict__
|
||||||
|
|
||||||
def getAppyRefIndex(self, fieldName, obj):
|
def getAppyRefIndex(self, fieldName, obj):
|
||||||
'''Gets the position of p_obj within Ref field named p_fieldName.'''
|
'''Gets the position of p_obj within Ref field named p_fieldName.'''
|
||||||
|
|
|
@ -541,131 +541,28 @@
|
||||||
</script>
|
</script>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div metal:define-macro="queryResult">
|
<metal:queryResults define-macro="queryResult"
|
||||||
|
tal:define="tool python: contextObj;
|
||||||
|
contentType request/contentType;
|
||||||
|
flavourNumber python: int(request['flavourNumber']);
|
||||||
|
startNumber python:test(request['startNumber']=='', '0', request['startNumber']);
|
||||||
|
startNumber python: int(startNumber);
|
||||||
|
searchName request/searchName;
|
||||||
|
severalTypes python: contentType and (contentType.find(',') != -1);
|
||||||
|
queryResult python: tool.executeQuery(contentType, flavourNumber, searchName, startNumber);
|
||||||
|
objs queryResult/objects;
|
||||||
|
totalNumber queryResult/totalNumber;
|
||||||
|
batchSize queryResult/batchSize;
|
||||||
|
ajaxHookId python:'queryResult';
|
||||||
|
baseUrl python: tool.getQueryUrl(contentType, flavourNumber, searchName)">
|
||||||
|
|
||||||
<script language="javascript">
|
<tal:result condition="objs">
|
||||||
<!--
|
|
||||||
function getSortValue(row, fieldName) {
|
|
||||||
// Find, from p_fieldName, the cell that is used for sorting.
|
|
||||||
var cellId = "field_" + fieldName;
|
|
||||||
var cells = row.cells;
|
|
||||||
for (var i=0; i < cells.length; i++) {
|
|
||||||
if (cells[i].id == cellId) {
|
|
||||||
// Ok we have the cell on which we must sort.
|
|
||||||
// Now get the cell content.
|
|
||||||
// If the cell contains links, content is the 1st link content
|
|
||||||
var innerLinks = cells[i].getElementsByTagName("a");
|
|
||||||
if (innerLinks.length > 0) {
|
|
||||||
return innerLinks[0].innerHTML;
|
|
||||||
} else {
|
|
||||||
return cells[i].innerHTML;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function sortRows(fieldName, ascending) {
|
<tal:comment replace="nothing">Appy (top) navigation</tal:comment>
|
||||||
var queryRows = cssQuery('#query_row');
|
<metal:nav use-macro="here/skyn/macros/macros/appyNavigate"/>
|
||||||
// Create a wrapper for sorting
|
|
||||||
var RowWrapper = function(row, fieldName) {
|
|
||||||
this.value = getSortValue(row, fieldName);
|
|
||||||
this.cloned_node = row.cloneNode(true);
|
|
||||||
this.toString = function() {
|
|
||||||
if (this.value.toString) {
|
|
||||||
return this.value.toString();
|
|
||||||
} else {
|
|
||||||
return this.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Wrap nodes
|
|
||||||
var items = new Array();
|
|
||||||
for (var i=0; i<queryRows.length; i++) {
|
|
||||||
items.push(new RowWrapper(queryRows[i], fieldName));
|
|
||||||
}
|
|
||||||
// Sort nodes
|
|
||||||
items.sort();
|
|
||||||
if (!ascending) {
|
|
||||||
items.reverse();
|
|
||||||
}
|
|
||||||
// Reorder nodes
|
|
||||||
for (var i=0; i<items.length; i++) {
|
|
||||||
var dest = queryRows[i];
|
|
||||||
dest.parentNode.replaceChild(items[i].cloned_node, dest);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function onSort(fieldName){
|
|
||||||
// First, switch the sort arrow (up->down or down->up)
|
|
||||||
var arrow = document.getElementById("arrow_" + fieldName);
|
|
||||||
var sortAscending = (arrow.src.indexOf('arrowDown.gif') != -1);
|
|
||||||
if (sortAscending){
|
|
||||||
// Display "up" image
|
|
||||||
arrow.src = arrow.src.replace('arrowDown.gif', 'arrowUp.gif')
|
|
||||||
}
|
|
||||||
else { // Display "down" image
|
|
||||||
arrow.src = arrow.src.replace('arrowUp.gif', 'arrowDown.gif')
|
|
||||||
}
|
|
||||||
// Then, sort the rows on column "fieldName".
|
|
||||||
sortRows(fieldName, sortAscending);
|
|
||||||
}
|
|
||||||
|
|
||||||
function cellMatches(cell, searchValue) {
|
|
||||||
// This function returns true if the HTML p_cell contains p_searchValue
|
|
||||||
var innerLinks = cell.getElementsByTagName("a");
|
|
||||||
// If the cell contains links, we search within the link contents
|
|
||||||
for (var i=0; i < innerLinks.length; i++){
|
|
||||||
var linkContent = innerLinks[i].innerHTML.toLowerCase();
|
|
||||||
if (linkContent.indexOf(searchValue) != -1) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If we are here, we still have no match. Let's search directly within
|
|
||||||
// the cell.
|
|
||||||
var cellContent = cell.innerHTML.toLowerCase();
|
|
||||||
if (cellContent.indexOf(searchValue) != -1) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onTextEntered(fieldName) {
|
|
||||||
// Is called whenever text is entered into field named p_fieldName.
|
|
||||||
var cellId = "field_" + fieldName
|
|
||||||
var field = document.getElementById("filter_" + fieldName);
|
|
||||||
var fieldValue = field.value.toLowerCase();
|
|
||||||
if (fieldValue.length >= 3) {
|
|
||||||
// Browse all rows and check if it should be visible or not.
|
|
||||||
var queryRows = cssQuery('#query_row');
|
|
||||||
for (var i=0; i < queryRows.length; i++) {
|
|
||||||
// Find the value of the cell.
|
|
||||||
var queryCells = queryRows[i].cells;
|
|
||||||
for (var j=0; j < queryCells.length; j++) {
|
|
||||||
if (queryCells[j].id == cellId) {
|
|
||||||
if (cellMatches(queryCells[j], fieldValue)) {
|
|
||||||
queryRows[i].style.display = "";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
queryRows[i].style.display = "none";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Show all rows
|
|
||||||
var queryRows = cssQuery('#query_row');
|
|
||||||
for (var i=0; i < queryRows.length; i++) {
|
|
||||||
queryRows[i].style.display = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-->
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<table class="vertical listing" width="100%"
|
|
||||||
tal:define="fieldDescrs python: tool.getResultColumns(queryResult[0].getObject(), queryName);">
|
|
||||||
|
|
||||||
|
<table tal:define="fieldDescrs python: tool.getResultColumns(objs[0], contentType)"
|
||||||
|
class="vertical listing" width="100%" cellpadding="0" cellspacing="0">
|
||||||
<tal:comment replace="nothing">Every item in fieldDescr is a FieldDescr instance,
|
<tal:comment replace="nothing">Every item in fieldDescr is a FieldDescr instance,
|
||||||
excepted for workflow state (which is not a field): in this case it is simply the
|
excepted for workflow state (which is not a field): in this case it is simply the
|
||||||
string "workflowState".</tal:comment>
|
string "workflowState".</tal:comment>
|
||||||
|
@ -677,7 +574,7 @@
|
||||||
onClick python:'javascript:onSort(\'title\')';"
|
onClick python:'javascript:onSort(\'title\')';"
|
||||||
id="arrow_title" style="cursor:pointer"/>
|
id="arrow_title" style="cursor:pointer"/>
|
||||||
<span tal:content="python: tool.translate('ref_name')"/>
|
<span tal:content="python: tool.translate('ref_name')"/>
|
||||||
<input id="filter_title" type="text" size="10" onkeyup="javascript:onTextEntered('title')"/>
|
<input id="filter_title" type="text" size="5" onkeyup="javascript:onTextEntered('title')"/>
|
||||||
</th>
|
</th>
|
||||||
|
|
||||||
<tal:comment replace="nothing">Columns corresponding to other fields</tal:comment>
|
<tal:comment replace="nothing">Columns corresponding to other fields</tal:comment>
|
||||||
|
@ -695,19 +592,19 @@
|
||||||
<tal:workflowState condition="python: fieldName == 'workflow_state'">
|
<tal:workflowState condition="python: fieldName == 'workflow_state'">
|
||||||
<span tal:replace="python: tool.translate('workflow_state')"/>
|
<span tal:replace="python: tool.translate('workflow_state')"/>
|
||||||
</tal:workflowState>
|
</tal:workflowState>
|
||||||
<input type="text" size="10"
|
<input type="text" size="5"
|
||||||
tal:attributes="id python: 'filter_%s' % fieldName;
|
tal:attributes="id python: 'filter_%s' % fieldName;
|
||||||
onkeyup python:'javascript:onTextEntered(\'%s\')' % fieldName"/>
|
onkeyup python:'javascript:onTextEntered(\'%s\')' % fieldName"/>
|
||||||
</th>
|
</th>
|
||||||
</tal:columnHeader>
|
</tal:columnHeader>
|
||||||
|
|
||||||
<tal:comment replace="nothing">Column "Object type", shown if we are on tab "consult all"</tal:comment>
|
<tal:comment replace="nothing">Column "Object type", shown if instances of several types are shown</tal:comment>
|
||||||
<th tal:condition="mainTabSelected"><img
|
<th tal:condition="severalTypes"><img
|
||||||
tal:attributes= "src string: $portal_url/arrowDown.gif;
|
tal:attributes= "src string: $portal_url/arrowDown.gif;
|
||||||
onClick python:'javascript:onSort(\'root_type\')';"
|
onClick python:'javascript:onSort(\'root_type\')';"
|
||||||
id = "arrow_root_type" style="cursor:pointer"/>
|
id = "arrow_root_type" style="cursor:pointer"/>
|
||||||
<span tal:replace="python: tool.translate('root_type')"/>
|
<span tal:replace="python: tool.translate('root_type')"/>
|
||||||
<input type="text" size="10" id="filter_root_type"
|
<input type="text" size="5" id="filter_root_type"
|
||||||
tal:attributes="onkeyup python:'javascript:onTextEntered(\'root_type\')'"/>
|
tal:attributes="onkeyup python:'javascript:onTextEntered(\'root_type\')'"/>
|
||||||
</th>
|
</th>
|
||||||
|
|
||||||
|
@ -716,11 +613,10 @@
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tal:comment replace="nothing">Results</tal:comment>
|
<tal:comment replace="nothing">Results</tal:comment>
|
||||||
<tr tal:repeat="brain queryResult" id="query_row">
|
<tr tal:repeat="obj objs" id="query_row">
|
||||||
<tal:row define="obj brain/getObject">
|
|
||||||
|
|
||||||
<tal:comment replace="nothing">Mandatory column "Title"/"Name"</tal:comment>
|
<tal:comment replace="nothing">Mandatory column "Title"/"Name"</tal:comment>
|
||||||
<td id="field_title"><a tal:content="brain/Title" tal:attributes="href obj/getUrl"></a></td>
|
<td id="field_title"><a tal:content="obj/Title" tal:attributes="href obj/getUrl"></a></td>
|
||||||
|
|
||||||
<tal:comment replace="nothing">Columns corresponding to other fields</tal:comment>
|
<tal:comment replace="nothing">Columns corresponding to other fields</tal:comment>
|
||||||
<tal:otherFields repeat="fieldDescr fieldDescrs">
|
<tal:otherFields repeat="fieldDescr fieldDescrs">
|
||||||
|
@ -744,8 +640,8 @@
|
||||||
</tal:workflowState>
|
</tal:workflowState>
|
||||||
</tal:otherFields>
|
</tal:otherFields>
|
||||||
|
|
||||||
<tal:comment replace="nothing">Column "Object type", shown if we are on tab "consult all"</tal:comment>
|
<tal:comment replace="nothing">Column "Object type", shown if instances of several types are shown</tal:comment>
|
||||||
<td tal:condition="mainTabSelected" id="field_root_type"
|
<td tal:condition="severalTypes" id="field_root_type"
|
||||||
tal:content="python: tool.translate(obj.portal_type)"></td>
|
tal:content="python: tool.translate(obj.portal_type)"></td>
|
||||||
|
|
||||||
<tal:comment replace="nothing">Column "Actions"</tal:comment>
|
<tal:comment replace="nothing">Column "Actions"</tal:comment>
|
||||||
|
@ -766,12 +662,18 @@
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</td>
|
</td>
|
||||||
</tal:row>
|
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<div metal:use-macro="context/batch_macros/macros/navigation" />
|
<tal:comment replace="nothing">Appy (bottom) navigation</tal:comment>
|
||||||
</div>
|
<metal:nav use-macro="here/skyn/macros/macros/appyNavigate"/>
|
||||||
|
</tal:result>
|
||||||
|
|
||||||
|
<span tal:condition="not: objs"
|
||||||
|
tal:content="python: tool.translate('query_no_result')">No result.
|
||||||
|
</span>
|
||||||
|
|
||||||
|
</metal:queryResults>
|
||||||
|
|
||||||
<metal:phases define-macro="phases">
|
<metal:phases define-macro="phases">
|
||||||
<tal:comment replace="nothing">This macro displays phases defined for a given content type,
|
<tal:comment replace="nothing">This macro displays phases defined for a given content type,
|
||||||
|
@ -840,10 +742,19 @@
|
||||||
</form>
|
</form>
|
||||||
</metal:transitions>
|
</metal:transitions>
|
||||||
|
|
||||||
<metal:portletContent define-macro="portletContent">
|
<metal:portletContent define-macro="portletContent"
|
||||||
|
tal:define="queryUrl python: '%s/skyn/query' % appFolder.absolute_url();
|
||||||
|
currentSearch request/search|nothing;
|
||||||
|
currentType request/type_name|nothing;">
|
||||||
<tal:comment replace="nothing">Portlet title, with link to tool.</tal:comment>
|
<tal:comment replace="nothing">Portlet title, with link to tool.</tal:comment>
|
||||||
<dt class="portletHeader">
|
<dt class="portletHeader">
|
||||||
<span tal:replace="python: tool.translate(appName)"/>
|
<tal:comment replace="nothing">If there is only one flavour, clicking on the portlet
|
||||||
|
title allows to see all root objects in the database.</tal:comment>
|
||||||
|
<a tal:condition="python: len(flavours)==1"
|
||||||
|
tal:attributes="href python:'%s?type_name=%s&flavourNumber=1' % (queryUrl, ','.join(rootClasses))"
|
||||||
|
tal:content="python: tool.translate(appName)"></a>
|
||||||
|
<span tal:condition="python: len(flavours)>1"
|
||||||
|
tal:replace="python: tool.translate(appName)"/>
|
||||||
<img style="cursor:pointer"
|
<img style="cursor:pointer"
|
||||||
tal:condition="python: member.has_role('Manager')"
|
tal:condition="python: member.has_role('Manager')"
|
||||||
tal:attributes="onClick python: 'href: window.location=\'%s/skyn/view\'' % tool.absolute_url();
|
tal:attributes="onClick python: 'href: window.location=\'%s/skyn/view\'' % tool.absolute_url();
|
||||||
|
@ -851,15 +762,59 @@
|
||||||
src string:$portal_url/skyn/appyConfig.gif"/>
|
src string:$portal_url/skyn/appyConfig.gif"/>
|
||||||
</dt>
|
</dt>
|
||||||
|
|
||||||
<tal:comment replace="nothing">Links to flavours</tal:comment>
|
<tal:comment replace="nothing">TODO: implement a widget for selecting the needed flavour.</tal:comment>
|
||||||
<dt class="portletAppyItem" tal:repeat="flavourInfo tool/getFlavoursInfo">
|
|
||||||
|
<tal:comment replace="nothing">Create a section for every root class.</tal:comment>
|
||||||
|
<tal:section repeat="rootClass rootClasses" define="flavourNumber python:1">
|
||||||
|
<tal:comment replace="nothing">Section title, with action icons</tal:comment>
|
||||||
|
<dt tal:attributes="class python:test(repeat['rootClass'].number()==1, 'portletAppyItem', 'portletAppyItem portletSep')">
|
||||||
|
<table width="100%" cellspacing="0" cellpadding="0" class="no-style-table">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a tal:attributes="href python: '%s?type_name=%s&flavourNumber=%s' % (queryUrl, rootClass, flavourNumber);
|
||||||
|
class python:test(not currentSearch and (currentType==rootClass), 'portletCurrent', '')"
|
||||||
|
tal:content="python: tool.translate(rootClass + '_plural')"></a>
|
||||||
|
</td>
|
||||||
|
<td align="right"
|
||||||
|
tal:define="addPermission python: '%s: Add %s' % (appName, rootClass);
|
||||||
|
userMayAdd python: member.has_permission(addPermission, appFolder);
|
||||||
|
createMeans python: tool.getCreateMeans(rootClass)">
|
||||||
|
<tal:comment replace="nothing">Create a new object from a web form</tal:comment>
|
||||||
|
<img style="cursor:pointer"
|
||||||
|
tal:condition="python: ('form' in createMeans) and userMayAdd"
|
||||||
|
tal:attributes="onClick python: 'href: window.location=\'%s/skyn/do?action=Create&type_name=%s\'' % (appFolder.absolute_url(), rootClass);
|
||||||
|
src string: $portal_url/skyn/plus.png;
|
||||||
|
title python: tool.translate('query_create')"/>
|
||||||
|
<tal:comment replace="nothing">Create (a) new object(s) by importing data</tal:comment>
|
||||||
|
<img style="cursor:pointer"
|
||||||
|
tal:condition="python: ('import' in createMeans) and userMayAdd"
|
||||||
|
tal:attributes="onClick python: 'href: window.location=\'%s/skyn/import?type_name=%s\'' % (appFolder.absolute_url(), rootClass);
|
||||||
|
src string: $portal_url/skyn/import.png;
|
||||||
|
title python: tool.translate('query_import')"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</dt>
|
||||||
|
<tal:comment replace="nothing">Searches for this content type.</tal:comment>
|
||||||
|
<dt class="portletAppyItem portletSearch" tal:repeat="search python: tool.getSearches(rootClass)">
|
||||||
|
<a tal:define="searchLabel python: '%s_search_%s' % (rootClass, search['name']);
|
||||||
|
searchDescr python: '%s_descr' % searchLabel"
|
||||||
|
tal:attributes="href python: '%s?type_name=%s&flavourNumber=%s&search=%s' % (queryUrl, rootClass, flavourNumber, search['name']);
|
||||||
|
title python: tool.translate(searchDescr);
|
||||||
|
class python: test(search['name'] == currentSearch, 'portletCurrent', '')"
|
||||||
|
tal:content="structure python: tool.translate(searchLabel)"></a>
|
||||||
|
</dt>
|
||||||
|
</tal:section>
|
||||||
|
|
||||||
|
<tal:comment replace="nothing">All objects in flavour</tal:comment>
|
||||||
|
<!--dt class="portletAppyItem" tal:define="flavourInfo python: flavours[0]">
|
||||||
<a tal:define="flavourNumber flavourInfo/number;
|
<a tal:define="flavourNumber flavourInfo/number;
|
||||||
rootTypes python: test(flavourNumber==1, rootClasses, ['%s_%s' % (rc, flavourNumber) for rc in rootClasses]);
|
rootTypes python: test(flavourNumber==1, rootClasses, ['%s_%s' % (rc, flavourNumber) for rc in rootClasses]);
|
||||||
rootClassesQuery python:','.join(rootTypes)"
|
rootClassesQuery python:','.join(rootTypes)"
|
||||||
tal:content="flavourInfo/title"
|
tal:content="flavourInfo/title"
|
||||||
tal:attributes="title python: tool.translate('query_consult_all');
|
tal:attributes="title python: tool.translate('query_consult_all');
|
||||||
href python:'%s/skyn/query?query=%s&flavourNumber=%d' % (appFolder.absolute_url(), rootClassesQuery, flavourNumber)"></a>
|
href python:'%s?type_name=%s&flavourNumber=%d' % (queryUrl, rootClassesQuery, flavourNumber)"></a>
|
||||||
</dt>
|
</dt-->
|
||||||
|
|
||||||
<dt class="portletAppyItem" tal:define="contextObj tool/getPublishedObject"
|
<dt class="portletAppyItem" tal:define="contextObj tool/getPublishedObject"
|
||||||
tal:condition="python: contextObj.meta_type in rootClasses">
|
tal:condition="python: contextObj.meta_type in rootClasses">
|
||||||
|
@ -867,12 +822,11 @@
|
||||||
</dt>
|
</dt>
|
||||||
</metal:portletContent>
|
</metal:portletContent>
|
||||||
|
|
||||||
<tal:comment replace="nothing">
|
<div metal:define-macro="appyNavigate" tal:condition="python: totalNumber > batchSize" align="right">
|
||||||
Buttons for navigating among a list of elements (next, back, first, last, etc).
|
<tal:comment replace="nothing">
|
||||||
</tal:comment>
|
Buttons for navigating among a list of elements (next, back, first, last, etc).
|
||||||
<metal:appyNavigate define-macro="appyNavigate" tal:condition="python: totalNumber > batchSize">
|
</tal:comment>
|
||||||
<table cellpadding="0" cellspacing="0" align="right" class="appyNav"
|
<table cellpadding="0" cellspacing="0" class="appyNav">
|
||||||
tal:define="baseUrl python: contextObj.getUrl('showRef', **{'fieldName': fieldName, 'isBack': isBack, 'innerRef': innerRef, 'labelId': labelId, 'descrId': descrId}) + '&%s_startNumber=' % ajaxHookId">
|
|
||||||
<tr>
|
<tr>
|
||||||
<tal:comment replace="nothing">Go to the first page</tal:comment>
|
<tal:comment replace="nothing">Go to the first page</tal:comment>
|
||||||
<td><img style="cursor:pointer" tal:condition="python: (startNumber != 0) and (startNumber != batchSize)"
|
<td><img style="cursor:pointer" tal:condition="python: (startNumber != 0) and (startNumber != batchSize)"
|
||||||
|
@ -886,10 +840,11 @@
|
||||||
title python: tool.translate('goto_previous');
|
title python: tool.translate('goto_previous');
|
||||||
onClick python: 'askAjaxChunk(\'%s\', \'%s\')' % (ajaxHookId, baseUrl+str(sNumber))"/></td>
|
onClick python: 'askAjaxChunk(\'%s\', \'%s\')' % (ajaxHookId, baseUrl+str(sNumber))"/></td>
|
||||||
<tal:comment replace="nothing">Explain which elements are currently shown</tal:comment>
|
<tal:comment replace="nothing">Explain which elements are currently shown</tal:comment>
|
||||||
<td class="discreet">
|
<td class="discreet" valign="middle">
|
||||||
<span tal:replace="python: startNumber+1"/>
|
<span tal:replace="python: startNumber+1"/>
|
||||||
<img tal:attributes="src string: $portal_url/skyn/to.png"/>
|
<img tal:attributes="src string: $portal_url/skyn/to.png"/>
|
||||||
<span tal:replace="python: startNumber+len(objs)"/>
|
<span tal:replace="python: startNumber+len(objs)"/> <b>//</b>
|
||||||
|
<span tal:replace="python: totalNumber"/>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<tal:comment replace="nothing">Go to the next page</tal:comment>
|
<tal:comment replace="nothing">Go to the next page</tal:comment>
|
||||||
|
@ -909,4 +864,4 @@
|
||||||
onClick python: 'askAjaxChunk(\'%s\', \'%s\')' % (ajaxHookId, baseUrl+str(sNumber))"/></td>
|
onClick python: 'askAjaxChunk(\'%s\', \'%s\')' % (ajaxHookId, baseUrl+str(sNumber))"/></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</metal:appyNavigate>
|
</div>
|
||||||
|
|
|
@ -15,62 +15,146 @@
|
||||||
tal:define="appFolder context/getParentNode;
|
tal:define="appFolder context/getParentNode;
|
||||||
appName appFolder/id;
|
appName appFolder/id;
|
||||||
tool python: portal.get('portal_%s' % appName.lower());
|
tool python: portal.get('portal_%s' % appName.lower());
|
||||||
queryName python:context.REQUEST.get('query');
|
contentType python:context.REQUEST.get('type_name');
|
||||||
flavourNumber python:context.REQUEST.get('flavourNumber');
|
flavourNumber python:int(context.REQUEST.get('flavourNumber'));
|
||||||
rootClasses tool/getRootClasses;
|
searchName python:context.REQUEST.get('search', '');
|
||||||
rootTypes python: test(flavourNumber=='1', rootClasses, ['%s_%s' % (rc, flavourNumber) for rc in rootClasses]);
|
searchLabel python: '%s_search_%s' % (contentType, searchName);
|
||||||
rootClassesQuery python:','.join(rootClasses);
|
searchDescr python: '%s_descr' % searchLabel;
|
||||||
mainTabSelected python: queryName.find(',') != -1">
|
severalTypes python: contentType and (contentType.find(',') != -1)">
|
||||||
|
|
||||||
<div metal:use-macro="here/skyn/macros/macros/pagePrologue"/>
|
<div metal:use-macro="here/skyn/macros/macros/pagePrologue"/>
|
||||||
<span tal:condition="python: queryName and (queryName != 'none')">
|
<script language="javascript">
|
||||||
<span tal:define="queryResult python: tool.executeQuery(queryName, int(flavourNumber));
|
<!--
|
||||||
batch queryResult">
|
function getSortValue(row, fieldName) {
|
||||||
|
// Find, from p_fieldName, the cell that is used for sorting.
|
||||||
|
var cellId = "field_" + fieldName;
|
||||||
|
var cells = row.cells;
|
||||||
|
for (var i=0; i < cells.length; i++) {
|
||||||
|
if (cells[i].id == cellId) {
|
||||||
|
// Ok we have the cell on which we must sort.
|
||||||
|
// Now get the cell content.
|
||||||
|
// If the cell contains links, content is the 1st link content
|
||||||
|
var innerLinks = cells[i].getElementsByTagName("a");
|
||||||
|
if (innerLinks.length > 0) {
|
||||||
|
return innerLinks[0].innerHTML;
|
||||||
|
} else {
|
||||||
|
return cells[i].innerHTML;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
<tal:comment replace="nothing">Tabs</tal:comment>
|
function sortRows(fieldName, ascending) {
|
||||||
<ul class="contentViews appyTabs">
|
var queryRows = cssQuery('#query_row');
|
||||||
<tal:comment replace="nothing">Tab "All objects"</tal:comment>
|
// Create a wrapper for sorting
|
||||||
<li tal:define="selected python:mainTabSelected"
|
var RowWrapper = function(row, fieldName) {
|
||||||
tal:attributes="class python:test(selected, 'selected', 'plain')"
|
this.value = getSortValue(row, fieldName);
|
||||||
tal:condition="python: len(rootClasses)>1">
|
this.cloned_node = row.cloneNode(true);
|
||||||
|
this.toString = function() {
|
||||||
|
if (this.value.toString) {
|
||||||
|
return this.value.toString();
|
||||||
|
} else {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Wrap nodes
|
||||||
|
var items = new Array();
|
||||||
|
for (var i=0; i<queryRows.length; i++) {
|
||||||
|
items.push(new RowWrapper(queryRows[i], fieldName));
|
||||||
|
}
|
||||||
|
// Sort nodes
|
||||||
|
items.sort();
|
||||||
|
if (!ascending) {
|
||||||
|
items.reverse();
|
||||||
|
}
|
||||||
|
// Reorder nodes
|
||||||
|
for (var i=0; i<items.length; i++) {
|
||||||
|
var dest = queryRows[i];
|
||||||
|
dest.parentNode.replaceChild(items[i].cloned_node, dest);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
<a tal:content="python: tool.translate('query_consult_all')"
|
function onSort(fieldName){
|
||||||
tal:attributes="href python: '%s/skyn/query?query=%s&flavourNumber=%s' % (appFolder.absolute_url(), rootClassesQuery, flavourNumber)"></a>
|
// First, switch the sort arrow (up->down or down->up)
|
||||||
</li>
|
var arrow = document.getElementById("arrow_" + fieldName);
|
||||||
<tal:comment replace="nothing">One tab for each root content type</tal:comment>
|
var sortAscending = (arrow.src.indexOf('arrowDown.gif') != -1);
|
||||||
<tal:tab repeat="rootContentType rootTypes">
|
if (sortAscending){
|
||||||
<li tal:define="selected python:queryName == rootContentType;
|
// Display "up" image
|
||||||
addPermission python: '%s: Add %s' % (appName, rootContentType);
|
arrow.src = arrow.src.replace('arrowDown.gif', 'arrowUp.gif')
|
||||||
userMayAdd python: member.has_permission(addPermission, appFolder);
|
}
|
||||||
createMeans python: tool.getCreateMeans(rootContentType)"
|
else { // Display "down" image
|
||||||
tal:attributes="class python:test(selected, 'selected', 'plain')">
|
arrow.src = arrow.src.replace('arrowUp.gif', 'arrowDown.gif')
|
||||||
<a tal:content="python: tool.translate(rootContentType)"
|
}
|
||||||
tal:attributes="href python: '%s/skyn/query?query=%s&flavourNumber=%s' % (appFolder.absolute_url(), rootContentType, flavourNumber)"/>
|
// Then, sort the rows on column "fieldName".
|
||||||
<tal:comment replace="nothing">Create a new object from a web form</tal:comment>
|
sortRows(fieldName, sortAscending);
|
||||||
<img style="cursor:pointer" class="appyPlusImg"
|
}
|
||||||
tal:condition="python: ('form' in createMeans) and userMayAdd"
|
|
||||||
tal:attributes="onClick python: 'href: window.location=\'%s/skyn/do?action=Create&type_name=%s\'' % (appFolder.absolute_url(), rootContentType);
|
|
||||||
src string: $portal_url/skyn/plus.png;
|
|
||||||
title python: tool.translate('query_create')"/>
|
|
||||||
<tal:comment replace="nothing">Create (a) new object(s) by importing data</tal:comment>
|
|
||||||
<img style="cursor:pointer" class="appyPlusImg"
|
|
||||||
tal:condition="python: ('import' in createMeans) and userMayAdd"
|
|
||||||
tal:attributes="onClick python: 'href: window.location=\'%s/skyn/import?type_name=%s\'' % (appFolder.absolute_url(), rootContentType);
|
|
||||||
src string: $portal_url/skyn/import.png;
|
|
||||||
title python: tool.translate('query_import')"/>
|
|
||||||
</li>
|
|
||||||
</tal:tab>
|
|
||||||
</ul>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<tal:comment replace="nothing">Query result</tal:comment>
|
function cellMatches(cell, searchValue) {
|
||||||
<span tal:condition="queryResult">
|
// This function returns true if the HTML p_cell contains p_searchValue
|
||||||
<span metal:use-macro="here/skyn/macros/macros/queryResult"></span>
|
var innerLinks = cell.getElementsByTagName("a");
|
||||||
</span>
|
// If the cell contains links, we search within the link contents
|
||||||
<span tal:condition="not: queryResult"
|
for (var i=0; i < innerLinks.length; i++){
|
||||||
tal:content="python: tool.translate('query_no_result')">No result.</span>
|
var linkContent = innerLinks[i].innerHTML.toLowerCase();
|
||||||
</span>
|
if (linkContent.indexOf(searchValue) != -1) {
|
||||||
</span>
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If we are here, we still have no match. Let's search directly within
|
||||||
|
// the cell.
|
||||||
|
var cellContent = cell.innerHTML.toLowerCase();
|
||||||
|
if (cellContent.indexOf(searchValue) != -1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTextEntered(fieldName) {
|
||||||
|
// Is called whenever text is entered into field named p_fieldName.
|
||||||
|
var cellId = "field_" + fieldName
|
||||||
|
var field = document.getElementById("filter_" + fieldName);
|
||||||
|
var fieldValue = field.value.toLowerCase();
|
||||||
|
if (fieldValue.length >= 3) {
|
||||||
|
// Browse all rows and check if it should be visible or not.
|
||||||
|
var queryRows = cssQuery('#query_row');
|
||||||
|
for (var i=0; i < queryRows.length; i++) {
|
||||||
|
// Find the value of the cell.
|
||||||
|
var queryCells = queryRows[i].cells;
|
||||||
|
for (var j=0; j < queryCells.length; j++) {
|
||||||
|
if (queryCells[j].id == cellId) {
|
||||||
|
if (cellMatches(queryCells[j], fieldValue)) {
|
||||||
|
queryRows[i].style.display = "";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
queryRows[i].style.display = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Show all rows
|
||||||
|
var queryRows = cssQuery('#query_row');
|
||||||
|
for (var i=0; i < queryRows.length; i++) {
|
||||||
|
queryRows[i].style.display = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
-->
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<tal:comment replace="nothing">Query title and description</tal:comment>
|
||||||
|
<h1 tal:content="structure python: test(searchName, tool.translate(searchLabel), test(severalTypes, tool.translate(appName), tool.translate('%s_plural' % contentType)))"></h1>
|
||||||
|
<div class="discreet" tal:condition="searchName"
|
||||||
|
tal:content="structure python: tool.translate(searchDescr)+'<br/><br/>'"></div>
|
||||||
|
|
||||||
|
<tal:comment replace="nothing">Query result</tal:comment>
|
||||||
|
<div id="queryResult"></div>
|
||||||
|
|
||||||
|
<script language="javascript"
|
||||||
|
tal:define="ajaxUrl python: tool.getQueryUrl(contentType, flavourNumber, searchName)"
|
||||||
|
tal:content="python: 'askAjaxChunk(\'queryResult\',\'%s\')' % ajaxUrl">
|
||||||
|
</script>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -107,7 +107,8 @@
|
||||||
showPlusIcon python:not isBack and appyType['add'] and not maxReached and member.has_permission(addPermission, folder);
|
showPlusIcon python:not isBack and appyType['add'] and not maxReached and member.has_permission(addPermission, folder);
|
||||||
atMostOneRef python: (multiplicity[1] == 1) and (len(objs)<=1);
|
atMostOneRef python: (multiplicity[1] == 1) and (len(objs)<=1);
|
||||||
label python: tool.translate(labelId);
|
label python: tool.translate(labelId);
|
||||||
description python: tool.translate(descrId)">
|
description python: tool.translate(descrId);
|
||||||
|
baseUrl python: contextObj.getUrl('showRef', **{'fieldName': fieldName, 'isBack': isBack, 'innerRef': innerRef, 'labelId': labelId, 'descrId': descrId}) + '&%s_startNumber=' % ajaxHookId">
|
||||||
|
|
||||||
<tal:comment replace="nothing">This macro displays the Reference widget on a "consult" page.
|
<tal:comment replace="nothing">This macro displays the Reference widget on a "consult" page.
|
||||||
|
|
||||||
|
@ -144,12 +145,11 @@
|
||||||
<fieldset tal:attributes="class python:test(innerRef, 'innerAppyFieldset', '')">
|
<fieldset tal:attributes="class python:test(innerRef, 'innerAppyFieldset', '')">
|
||||||
<legend tal:condition="python: not innerRef or showPlusIcon">
|
<legend tal:condition="python: not innerRef or showPlusIcon">
|
||||||
<span tal:condition="not: innerRef" tal:content="label"/>
|
<span tal:condition="not: innerRef" tal:content="label"/>
|
||||||
<tal:numberOfRefs>(<span tal:replace="totalNumber"/>)</tal:numberOfRefs>
|
|
||||||
<metal:plusIcon use-macro="here/skyn/ref/macros/plusIcon"/>
|
<metal:plusIcon use-macro="here/skyn/ref/macros/plusIcon"/>
|
||||||
</legend>
|
</legend>
|
||||||
|
|
||||||
<tal:comment replace="nothing">Object description</tal:comment>
|
<tal:comment replace="nothing">Object description</tal:comment>
|
||||||
<p tal:condition="python: not innerRef and description"
|
<p tal:condition="python: not innerRef and description.strip()"
|
||||||
tal:content="description" class="discreet" ></p>
|
tal:content="description" class="discreet" ></p>
|
||||||
|
|
||||||
<tal:comment replace="nothing">Appy (top) navigation</tal:comment>
|
<tal:comment replace="nothing">Appy (top) navigation</tal:comment>
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 214 B After Width: | Height: | Size: 211 B |
|
@ -8,7 +8,8 @@
|
||||||
<metal:block metal:use-macro="here/global_defines/macros/defines" />
|
<metal:block metal:use-macro="here/global_defines/macros/defines" />
|
||||||
<dl tal:define="rootClasses tool/getRootClasses;
|
<dl tal:define="rootClasses tool/getRootClasses;
|
||||||
appName string:<!applicationName!>;
|
appName string:<!applicationName!>;
|
||||||
appFolder tool/getAppFolder" class="portlet">
|
appFolder tool/getAppFolder;
|
||||||
|
flavours tool/getFlavoursInfo" class="portlet">
|
||||||
<metal:content use-macro="here/skyn/macros/macros/portletContent"/>
|
<metal:content use-macro="here/skyn/macros/macros/portletContent"/>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -212,12 +212,27 @@ fieldset {
|
||||||
margin: 0 0.2em 0.2em 0;
|
margin: 0 0.2em 0.2em 0;
|
||||||
}
|
}
|
||||||
/* Portlet elements */
|
/* Portlet elements */
|
||||||
|
.portletHeader {
|
||||||
|
text-transform: none;
|
||||||
|
padding: 1px 0.5em;
|
||||||
|
}
|
||||||
.portletAppyItem {
|
.portletAppyItem {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 1px 0.5em;
|
padding: 1px 0.5em;
|
||||||
border-left: 1px solid #8cacbb;
|
border-left: 1px solid #8cacbb;
|
||||||
border-right: 1px solid #8cacbb;
|
border-right: 1px solid #8cacbb;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
.portletSep {
|
||||||
|
border-top: 1px dashed #8cacbb;
|
||||||
|
}
|
||||||
|
.portletSearch {
|
||||||
|
padding: 0 0 0 0.6em;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.portletCurrent {
|
||||||
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Uncomment this if you want to hide breadcrumbs */
|
/* Uncomment this if you want to hide breadcrumbs */
|
||||||
|
|
21
gen/utils.py
21
gen/utils.py
|
@ -172,13 +172,22 @@ class AppyRequest:
|
||||||
return res
|
return res
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
class RefObjects:
|
class SomeObjects:
|
||||||
'''Represents a bunch of objects retrieved from a reference.'''
|
'''Represents a bunch of objects retrieved from a reference or a query in
|
||||||
def __init__(self, objects=None):
|
portal_catalog.'''
|
||||||
|
def __init__(self, objects=None, batchSize=None, startNumber=0):
|
||||||
self.objects = objects or [] # The objects
|
self.objects = objects or [] # The objects
|
||||||
self.totalNumber = len(self.objects) # self.objects may only represent a
|
self.totalNumber = len(self.objects) # self.objects may only represent a
|
||||||
# part of all available objects.
|
# part of all available objects.
|
||||||
self.batchSize = self.totalNumber # The max length of self.objects.
|
self.batchSize = batchSize or self.totalNumber # The max length of
|
||||||
self.startNumber = 0 # The index of first object in self.objects in
|
# self.objects.
|
||||||
# the whole list.
|
self.startNumber = startNumber # The index of first object in
|
||||||
|
# self.objects in the whole list.
|
||||||
|
def brainsToObjects(self):
|
||||||
|
'''self.objects has been populated from brains from the portal_catalog,
|
||||||
|
not from True objects. This method turns them (or some of them
|
||||||
|
depending on batchSize and startNumber) into real objects.'''
|
||||||
|
start = self.startNumber
|
||||||
|
brains = self.objects[start:start + self.batchSize]
|
||||||
|
self.objects = [b.getObject() for b in brains]
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
Loading…
Reference in a new issue