[gen] Refactoring.

This commit is contained in:
Gaetan Delannay 2013-08-21 13:54:56 +02:00
parent 34e3a3083e
commit 1bd77d68c4
8 changed files with 853 additions and 770 deletions

View file

@ -1,15 +1,13 @@
# ------------------------------------------------------------------------------
import types, string
from appy.gen.mail import sendNotification
from appy.gen.indexer import defaultIndexes
from appy.gen import utils as gutils
from appy.shared import utils as sutils
# ------------------------------------------------------------------------------
# Import stuff from appy.fields (and from a few other places too).
# This way, when an app gets "from appy.gen import *", everything is available.
# ------------------------------------------------------------------------------
from appy.fields import Page, Phase, Group, Field, Column, No
from appy.fields import Field
from appy.fields.action import Action
from appy.fields.boolean import Boolean
from appy.fields.computed import Computed
@ -22,9 +20,14 @@ from appy.fields.list import List
from appy.fields.pod import Pod
from appy.fields.ref import Ref, autoref
from appy.fields.string import String, Selection
from appy.fields.search import Search, UiSearch
from appy.fields.group import Group, Column
from appy.fields.page import Page
from appy.fields.phase import Phase
from appy.gen.layout import Table
from appy.px import Px
from appy import Object
No = gutils.No
# Default Appy permissions -----------------------------------------------------
r, w, d = ('read', 'write', 'delete')
@ -55,160 +58,6 @@ class Import:
# and must return a similar, sorted, list.
self.sort = sort
class Search:
'''Used for specifying a search for a given class.'''
def __init__(self, name, group=None, sortBy='', sortOrder='asc', limit=None,
default=False, colspan=1, translated=None, show=True,
translatedDescr=None, **fields):
self.name = name
# Searches may be visually grouped in the portlet.
self.group = Group.get(group)
self.sortBy = sortBy
self.sortOrder = sortOrder
self.limit = limit
# If this search is the default one, it will be triggered by clicking
# on main link.
self.default = default
self.colspan = colspan
# If a translated name or description is already given here, we will
# use it instead of trying to translate from labels.
self.translated = translated
self.translatedDescr = translatedDescr
# Condition for showing or not this search
self.show = show
# In the dict below, keys are indexed field names or names of standard
# indexes, and values are search values.
self.fields = fields
@staticmethod
def getIndexName(fieldName, usage='search'):
'''Gets the name of the technical index that corresponds to field named
p_fieldName. Indexes can be used for searching (p_usage="search") or
for sorting (usage="sort"). The method returns None if the field
named p_fieldName can't be used for p_usage.'''
if fieldName == 'title':
if usage == 'search': return 'Title'
else: return 'SortableTitle'
# Indeed, for field 'title', Appy has a specific index
# 'SortableTitle', because index 'Title' is a TextIndex
# (for searchability) and can't be used for sorting.
elif fieldName == 'state': return 'State'
elif fieldName == 'created': return 'Created'
elif fieldName == 'modified': return 'Modified'
elif fieldName in defaultIndexes: return fieldName
else:
return 'get%s%s'% (fieldName[0].upper(),fieldName[1:])
@staticmethod
def getSearchValue(fieldName, fieldValue, klass):
'''Returns a transformed p_fieldValue for producing a valid search
value as required for searching in the index corresponding to
p_fieldName.'''
field = getattr(klass, fieldName, None)
if (field and (field.getIndexType() == 'TextIndex')) or \
(fieldName == 'SearchableText'):
# For TextIndex indexes. We must split p_fieldValue into keywords.
res = gutils.Keywords(fieldValue).get()
elif isinstance(fieldValue, basestring) and fieldValue.endswith('*'):
v = fieldValue[:-1]
# Warning: 'z' is higher than 'Z'!
res = {'query':(v,v+'z'), 'range':'min:max'}
elif type(fieldValue) in sutils.sequenceTypes:
if fieldValue and isinstance(fieldValue[0], basestring):
# We have a list of string values (ie: we need to
# search v1 or v2 or...)
res = 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
res = {'query':queryv, 'range':rangev}
else:
res = fieldValue
return res
def updateSearchCriteria(self, criteria, klass, advanced=False):
'''This method updates dict p_criteria with all the search criteria
corresponding to this Search instance. If p_advanced is True,
p_criteria correspond to an advanced search, to be stored in the
session: in this case we need to keep the Appy names for parameters
sortBy and sortOrder (and not "resolve" them to Zope's sort_on and
sort_order).'''
# Put search criteria in p_criteria
for fieldName, fieldValue in self.fields.iteritems():
# Management of searches restricted to objects linked through a
# Ref field: not implemented yet.
if fieldName == '_ref': continue
# Make the correspondence between the name of the field and the
# name of the corresponding index, excepted if advanced is True: in
# that case, the correspondence will be done later.
if not advanced:
attrName = Search.getIndexName(fieldName)
# Express the field value in the way needed by the index
criteria[attrName] = Search.getSearchValue(fieldName,
fieldValue, klass)
else:
criteria[fieldName]= fieldValue
# Add a sort order if specified
if self.sortBy:
if not advanced:
criteria['sort_on'] = Search.getIndexName(self.sortBy,
usage='sort')
if self.sortOrder == 'desc': criteria['sort_order'] = 'reverse'
else: criteria['sort_order'] = None
else:
criteria['sortBy'] = self.sortBy
criteria['sortOrder'] = self.sortOrder
def isShowable(self, klass, tool):
'''Is this Search instance (defined in p_klass) showable?'''
if self.show.__class__.__name__ == 'staticmethod':
return gutils.callMethod(tool, self.show, klass=klass)
return self.show
class UiSearch:
'''Instances of this class are generated on-the-fly for manipulating a
Search from the User Interface.'''
# PX for rendering a search.
pxView = Px('''
<div class="portletSearch">
<a href=":'%s?className=%s&amp;search=%s' % \
(queryUrl, rootClass, search['name'])"
class=":search['name'] == currentSearch and 'portletCurrent' or ''"
title=":search['translatedDescr']">:search['translated']</a>
</div>''')
def __init__(self, search, className, tool):
self.search = search
self.name = search.name
self.type = 'search'
self.colspan = search.colspan
if search.translated:
self.translated = search.translated
self.translatedDescr = search.translatedDescr
else:
# The label may be specific in some special cases.
labelDescr = ''
if search.name == 'allSearch':
label = '%s_plural' % className
elif search.name == 'customSearch':
label = 'search_results'
else:
label = '%s_search_%s' % (className, search.name)
labelDescr = label + '_descr'
self.translated = tool.translate(label)
if labelDescr:
self.translatedDescr = tool.translate(labelDescr)
else:
self.translatedDescr = ''
# Workflow-specific types and default workflows --------------------------------
appyToZopePermissions = {
'read': ('View', 'Access contents information'),

View file

@ -1,6 +1,6 @@
# ------------------------------------------------------------------------------
import re, os, os.path, base64, urllib
from appy.shared.utils import normalizeText
from appy.shared import utils as sutils
# Function for creating a Zope object ------------------------------------------
def createObject(folder, id, className, appName, wf=True, noSecurity=False):
@ -96,7 +96,7 @@ class Keywords:
toRemove = '?-+*()'
def __init__(self, keywords, operator='AND'):
# Clean the p_keywords that the user has entered.
words = normalizeText(keywords)
words = sutils.normalizeText(keywords)
if words == '*': words = ''
for c in self.toRemove: words = words.replace(c, ' ')
self.keywords = words.split()
@ -220,4 +220,24 @@ def writeCookie(login, password, request):
cookieValue = base64.encodestring('%s:%s' % (login, password)).rstrip()
cookieValue = urllib.quote(cookieValue)
request.RESPONSE.setCookie('_appy_', cookieValue, path='/')
# ------------------------------------------------------------------------------
def initMasterValue(v):
'''Standardizes p_v as a list of strings.'''
if not isinstance(v, bool) and not v: res = []
elif type(v) not in sutils.sequenceTypes: res = [v]
else: res = v
return [str(v) for v in res]
# ------------------------------------------------------------------------------
class No:
'''When you write a workflow condition method and you want to return False
but you want to give to the user some explanations about why a transition
can't be triggered, do not return False, return an instance of No
instead. When creating such an instance, you can specify an error
message.'''
def __init__(self, msg):
self.msg = msg
def __nonzero__(self):
return False
# ------------------------------------------------------------------------------