Reworked AJAX framework with a lot of new sorting and filtering possibilities.

This commit is contained in:
Gaetan Delannay 2010-04-30 12:05:29 +02:00
parent 46cda3f755
commit fd775e17a2
11 changed files with 851 additions and 890 deletions

View file

@ -105,10 +105,11 @@ class ToolMixin(AbstractMixin):
if res: return res[0].getObject()
return None
_sortFields = {'title': 'sortable_title'}
def executeQuery(self, contentType, flavourNumber=1, searchName=None,
startNumber=0, search=None, remember=False,
brainsOnly=False, maxResults=None, noSecurity=False):
brainsOnly=False, maxResults=None, noSecurity=False,
sortBy=None, sortOrder='asc',
filterKey=None, filterValue=None):
'''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
@ -136,7 +137,16 @@ class ToolMixin(AbstractMixin):
p_maxResults equals string "NO_LIMIT".
If p_noSecurity is True, it gets all the objects, even those that the
currently logged user can't see.'''
currently logged user can't see.
The result is sorted according to the potential sort key defined in
the Search instance (Search.sortBy). But if parameter p_sortBy is
given, it defines or overrides the sort. In this case, p_sortOrder
gives the order (*asc*ending or *desc*ending).
If p_filterKey is given, it represents an additional search parameter
to take into account: the corresponding search value is in
p_filterValue.'''
# Is there one or several content types ?
if contentType.find(',') != -1:
# Several content types are specified
@ -149,59 +159,40 @@ class ToolMixin(AbstractMixin):
params = {'portal_type': portalTypes}
if not brainsOnly: params['batch'] = True
# Manage additional criteria from a search when relevant
if searchName or search:
if searchName:
# In this case, contentType must contain a single content type.
appyClass = self.getAppyClass(contentType)
if searchName:
if searchName != '_advanced':
search = ArchetypesClassDescriptor.getSearch(
appyClass, searchName)
else:
fields = self.REQUEST.SESSION['searchCriteria']
search = Search('customSearch', **fields)
if searchName != '_advanced':
search = ArchetypesClassDescriptor.getSearch(
appyClass, searchName)
else:
fields = self.REQUEST.SESSION['searchCriteria']
search = Search('customSearch', **fields)
if search:
# Add additional search criteria
for fieldName, fieldValue in search.fields.iteritems():
# Make the correspondance between the name of the field and the
# name of the corresponding index.
attrName = fieldName
if attrName == 'title': attrName = 'Title'
elif attrName == 'description': attrName = 'Description'
elif attrName == 'state': attrName = 'review_state'
else: attrName = 'get%s%s'% (fieldName[0].upper(),fieldName[1:])
attrName = Search.getIndexName(fieldName)
# Express the field value in the way needed by the index
if isinstance(fieldValue, basestring) and \
fieldValue.endswith('*'):
v = fieldValue[:-1]
params[attrName] = {'query':(v,v+'z'), 'range':'min:max'}
# Warning: 'z' is higher than 'Z'!
elif type(fieldValue) in sequenceTypes:
if fieldValue and isinstance(fieldValue[0], basestring):
# We have a list of string values (ie: we need to
# search v1 or v2 or...)
params[attrName] = fieldValue
else:
# We have a range of (int, float, DateTime...) values
minv, maxv = fieldValue
rangev = 'minmax'
queryv = fieldValue
if minv == None:
rangev = 'max'
queryv = maxv
elif maxv == None:
rangev = 'min'
queryv = minv
params[attrName] = {'query':queryv, 'range':rangev}
else:
params[attrName] = fieldValue
params[attrName] = Search.getSearchValue(fieldName, fieldValue)
# Add a sort order if specified
sb = search.sortBy
if sb:
# For field 'title', Plone has created a specific index
# 'sortable_title', because index 'Title' is a ZCTextIndex
# (for searchability) and can't be used for sorting.
if self._sortFields.has_key(sb): sb = self._sortFields[sb]
params['sort_on'] = sb
sortKey = search.sortBy
if sortKey:
params['sort_on'] = Search.getIndexName(sortKey, usage='sort')
# Determine or override sort if specified.
if sortBy:
params['sort_on'] = Search.getIndexName(sortBy, usage='sort')
if sortOrder == 'desc': params['sort_order'] = 'reverse'
else: params['sort_order'] = None
# If defined, add the filter among search parameters.
if filterKey:
filterKey = Search.getIndexName(filterKey)
filterValue = Search.getSearchValue(filterKey, filterValue)
params[filterKey] = filterValue
# TODO This value needs to be merged with an existing one if already
# in params, or, in a first step, we should avoid to display the
# corresponding filter widget on the screen.
# Determine what method to call on the portal catalog
if noSecurity: catalogMethod = 'unrestrictedSearchResults'
else: catalogMethod = 'searchResults'
@ -544,14 +535,13 @@ class ToolMixin(AbstractMixin):
if cookieValue: return cookieValue.value
return default
def getQueryUrl(self, contentType, flavourNumber, searchName, ajax=True,
def getQueryUrl(self, contentType, flavourNumber, searchName,
startNumber=None):
'''This method creates the URL that allows to perform an ajax GET
'''This method creates the URL that allows to perform a (non-Ajax)
request for getting queried objects from a search named p_searchName
on p_contentType from flavour numbered p_flavourNumber. If p_ajax
is False, it returns the non-ajax URL.'''
on p_contentType from flavour numbered p_flavourNumber.'''
baseUrl = self.getAppFolder().absolute_url() + '/skyn'
baseParams = 'type_name=%s&flavourNumber=%s'%(contentType,flavourNumber)
baseParams= 'type_name=%s&flavourNumber=%s' %(contentType,flavourNumber)
# Manage start number
rq = self.REQUEST
if startNumber != None:
@ -559,12 +549,8 @@ class ToolMixin(AbstractMixin):
elif rq.has_key('startNumber'):
baseParams += '&startNumber=%s' % rq['startNumber']
# Manage search name
if searchName or ajax: baseParams += '&search=%s' % searchName
if ajax:
return '%s/ajax?objectUid=%s&page=macros&macro=queryResult&%s' % \
(baseUrl, self.UID(), baseParams)
else:
return '%s/query?%s' % (baseUrl, baseParams)
if searchName: baseParams += '&search=%s' % searchName
return '%s/query?%s' % (baseUrl, baseParams)
def computeStartNumberFrom(self, currentNumber, totalNumber, batchSize):
'''Returns the number (start at 0) of the first element in a list
@ -666,7 +652,7 @@ class ToolMixin(AbstractMixin):
startNumber = self.computeStartNumberFrom(res['currentNumber']-1,
res['totalNumber'], batchSize)
res['sourceUrl'] = self.getQueryUrl(contentType, flavourNumber,
searchName, ajax=False, startNumber=startNumber)
searchName, startNumber=startNumber)
# Compute URLs
for urlType in ('previous', 'next', 'first', 'last'):
exec 'needIt = %sNeeded' % urlType

View file

@ -659,6 +659,15 @@ class AbstractMixin:
isDelta = True
self.changeRefOrder(rq['fieldName'], rq['refObjectUid'], move, isDelta)
def onSortReference(self):
'''This method is called when the user wants to sort the content of a
reference field.'''
rq = self.REQUEST
fieldName = rq.get('fieldName')
sortKey = rq.get('sortKey')
reverse = rq.get('reverse') == 'True'
self.appy().sort(fieldName, sortKey=sortKey, reverse=reverse)
def getWorkflow(self, appy=True):
'''Returns the Appy workflow instance that is relevant for this
object. If p_appy is False, it returns the DC workflow.'''
@ -1243,27 +1252,9 @@ class AbstractMixin:
exec 'self.set%s%s([])' % (fieldName[0].upper(),
fieldName[1:])
def getUrl(self, t='view', **kwargs):
'''This method returns various URLs about this object.'''
baseUrl = self.absolute_url()
params = ''
rq = self.REQUEST
for k, v in kwargs.iteritems(): params += '&%s=%s' % (k, v)
if params: params = params[1:]
if t == 'showRef':
chunk = '/skyn/ajax?objectUid=%s&page=ref&' \
'macro=showReferenceContent&' % self.UID()
startKey = '%s%s_startNumber' % (self.UID(), kwargs['fieldName'])
if rq.has_key(startKey) and not kwargs.has_key(startKey):
params += '&%s=%s' % (startKey, rq[startKey])
return baseUrl + chunk + params
elif t == 'showHistory':
chunk = '/skyn/ajax?objectUid=%s&page=macros&macro=history' % \
self.UID()
if params: params = '&' + params
return baseUrl + chunk + params
else: # We consider t=='view'
return baseUrl + '/skyn/view' + params
def getUrl(self):
'''Returns the Appy URL for viewing this object.'''
return self.absolute_url() + '/skyn/view'
def translate(self, label, mapping={}, domain=None, default=None):
'''Translates a given p_label into p_domain with p_mapping.'''