[gen] More work ZPT->PX.
This commit is contained in:
parent
2e9a832463
commit
34e3a3083e
31 changed files with 3287 additions and 3067 deletions
|
@ -1,16 +1,16 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
import os, os.path, sys, re, time, random, types, base64, urllib
|
||||
import os, os.path, sys, re, time, random, types
|
||||
from appy import Object
|
||||
import appy.gen
|
||||
from appy.gen import Search, String, Page, ldap
|
||||
from appy.gen.utils import SomeObjects, getClassName, GroupDescr, SearchDescr
|
||||
from appy.gen import Search, UiSearch, String, Page, ldap
|
||||
from appy.gen.layout import ColumnLayout
|
||||
from appy.gen import utils as gutils
|
||||
from appy.gen.mixins import BaseMixin
|
||||
from appy.gen.wrappers import AbstractWrapper
|
||||
from appy.gen.descriptors import ClassDescriptor
|
||||
from appy.gen.mail import sendMail
|
||||
from appy.shared import mimeTypes
|
||||
from appy.shared.utils import getOsTempFolder, sequenceTypes, normalizeString, \
|
||||
splitList
|
||||
from appy.shared import utils as sutils
|
||||
from appy.shared.data import languages
|
||||
try:
|
||||
from AccessControl.ZopeSecurityPolicy import _noroles
|
||||
|
@ -32,7 +32,7 @@ class ToolMixin(BaseMixin):
|
|||
appName = self.getProductConfig().PROJECTNAME
|
||||
res = metaTypeOrAppyClass
|
||||
if not isinstance(metaTypeOrAppyClass, basestring):
|
||||
res = getClassName(metaTypeOrAppyClass, appName)
|
||||
res = gutils.getClassName(metaTypeOrAppyClass, appName)
|
||||
if res.find('_wrappers') != -1:
|
||||
elems = res.split('_')
|
||||
res = '%s%s' % (elems[1], elems[4])
|
||||
|
@ -42,31 +42,31 @@ class ToolMixin(BaseMixin):
|
|||
def home(self):
|
||||
'''Returns the content of px ToolWrapper.pxHome.'''
|
||||
tool = self.appy()
|
||||
return tool.pxHome({'self': tool})
|
||||
return tool.pxHome({'obj': None, 'tool': tool})
|
||||
|
||||
def query(self):
|
||||
'''Returns the content of px ToolWrapper.pxQuery.'''
|
||||
tool = self.appy()
|
||||
return tool.pxQuery({'self': tool})
|
||||
return tool.pxQuery({'obj': None, 'tool': tool})
|
||||
|
||||
def search(self):
|
||||
'''Returns the content of px ToolWrapper.pxSearch.'''
|
||||
tool = self.appy()
|
||||
return tool.pxSearch({'self': tool})
|
||||
return tool.pxSearch({'obj': None, 'tool': tool})
|
||||
|
||||
def getHomePage(self):
|
||||
'''Return the home page when a user hits the app.'''
|
||||
# If the app defines a method "getHomePage", call it.
|
||||
appyTool = self.appy()
|
||||
tool = self.appy()
|
||||
try:
|
||||
url = appyTool.getHomePage()
|
||||
url = tool.getHomePage()
|
||||
except AttributeError:
|
||||
# Bring Managers to the config, lead others to home.pt.
|
||||
user = self.getUser()
|
||||
if user.has_role('Manager'):
|
||||
url = self.goto(self.absolute_url())
|
||||
else:
|
||||
url = self.goto(self.getApp().ui.home.absolute_url())
|
||||
url = self.goto('%s/home' % self.getApp().config.absolute_url())
|
||||
return url
|
||||
|
||||
def getHomeObject(self):
|
||||
|
@ -77,15 +77,12 @@ class ToolMixin(BaseMixin):
|
|||
portlet menu will nevertheless appear: the user will not have the
|
||||
feeling of being lost.'''
|
||||
# If the app defines a method "getHomeObject", call it.
|
||||
appyTool = self.appy()
|
||||
try:
|
||||
obj = appyTool.getHomeObject()
|
||||
if obj: return obj.o
|
||||
return self.appy().getHomeObject()
|
||||
except AttributeError:
|
||||
# For managers, the home object is the config. For others, there is
|
||||
# no default home object.
|
||||
user = self.getUser()
|
||||
if user.has_role('Manager'): return self
|
||||
if self.getUser().has_role('Manager'): return self.appy()
|
||||
|
||||
def getCatalog(self):
|
||||
'''Returns the catalog object.'''
|
||||
|
@ -220,31 +217,29 @@ class ToolMixin(BaseMixin):
|
|||
'''Returns the list of root classes for this application.'''
|
||||
return self.getProductConfig().rootClasses
|
||||
|
||||
def _appy_getAllFields(self, contentType):
|
||||
'''Returns the (translated) names of fields of p_contentType.'''
|
||||
def _appy_getAllFields(self, className):
|
||||
'''Returns the (translated) names of fields of p_className.'''
|
||||
res = []
|
||||
for appyType in self.getAllAppyTypes(className=contentType):
|
||||
res.append((appyType.name, self.translate(appyType.labelId)))
|
||||
for field in self.getAllAppyTypes(className=className):
|
||||
res.append((className.name, self.translate(className.labelId)))
|
||||
# Add object state
|
||||
res.append(('state', self.translate('workflow_state')))
|
||||
return res
|
||||
|
||||
def _appy_getSearchableFields(self, contentType):
|
||||
def _appy_getSearchableFields(self, className):
|
||||
'''Returns the (translated) names of fields that may be searched on
|
||||
objects of type p_contentType (=indexed fields).'''
|
||||
objects of type p_className (=indexed fields).'''
|
||||
res = []
|
||||
for appyType in self.getAllAppyTypes(className=contentType):
|
||||
if appyType.indexed:
|
||||
res.append((appyType.name, self.translate(appyType.labelId)))
|
||||
for field in self.getAllAppyTypes(className=className):
|
||||
if field.indexed:
|
||||
res.append((field.name, self.translate(field.labelId)))
|
||||
return res
|
||||
|
||||
def getSearchInfo(self, contentType, refInfo=None):
|
||||
'''Returns, as a dict:
|
||||
- the list of searchable fields (= some fields among all indexed
|
||||
fields);
|
||||
def getSearchInfo(self, className, refInfo=None):
|
||||
'''Returns, as an object:
|
||||
- the list of searchable fields (some among all indexed fields);
|
||||
- the number of columns for layouting those fields.'''
|
||||
fields = []
|
||||
fieldDicts = []
|
||||
if refInfo:
|
||||
# The search is triggered from a Ref field.
|
||||
refObject, fieldName = self.getRefInfo(refInfo)
|
||||
|
@ -253,16 +248,13 @@ class ToolMixin(BaseMixin):
|
|||
nbOfColumns = refField.queryNbCols
|
||||
else:
|
||||
# The search is triggered from an app-wide search.
|
||||
at = self.appy()
|
||||
fieldNames = getattr(at, 'searchFieldsFor%s' % contentType,())
|
||||
nbOfColumns = getattr(at, 'numberOfSearchColumnsFor%s' %contentType)
|
||||
tool = self.appy()
|
||||
fieldNames = getattr(tool, 'searchFieldsFor%s' % className,())
|
||||
nbOfColumns = getattr(tool, 'numberOfSearchColumnsFor%s' %className)
|
||||
for name in fieldNames:
|
||||
appyType = self.getAppyType(name,asDict=False,className=contentType)
|
||||
appyDict = self.getAppyType(name, asDict=True,className=contentType)
|
||||
fields.append(appyType)
|
||||
fieldDicts.append(appyDict)
|
||||
return {'fields': fields, 'nbOfColumns': nbOfColumns,
|
||||
'fieldDicts': fieldDicts}
|
||||
field = self.getAppyType(name, className=className)
|
||||
fields.append(field)
|
||||
return Object(fields=fields, nbOfColumns=nbOfColumns)
|
||||
|
||||
queryParamNames = ('className', 'search', 'sortKey', 'sortOrder',
|
||||
'filterKey', 'filterValue')
|
||||
|
@ -284,16 +276,16 @@ class ToolMixin(BaseMixin):
|
|||
if hasattr(klass, 'resultMode'): return klass.resultMode
|
||||
return 'list' # The default mode
|
||||
|
||||
def getImportElements(self, contentType):
|
||||
def getImportElements(self, className):
|
||||
'''Returns the list of elements that can be imported from p_path for
|
||||
p_contentType.'''
|
||||
appyClass = self.getAppyClass(contentType)
|
||||
p_className.'''
|
||||
appyClass = self.getAppyClass(className)
|
||||
importParams = self.getCreateMeans(appyClass)['import']
|
||||
onElement = importParams['onElement'].__get__('')
|
||||
sortMethod = importParams['sort']
|
||||
if sortMethod: sortMethod = sortMethod.__get__('')
|
||||
elems = []
|
||||
importType = self.getAppyType('importPathFor%s' % contentType)
|
||||
importType = self.getAppyType('importPathFor%s' % className)
|
||||
importPath = importType.getValue(self)
|
||||
for elem in os.listdir(importPath):
|
||||
elemFullPath = os.path.join(importPath, elem)
|
||||
|
@ -339,17 +331,19 @@ class ToolMixin(BaseMixin):
|
|||
def getAllowedValue(self):
|
||||
'''Gets, for the currently logged user, the value for index
|
||||
"Allowed".'''
|
||||
tool = self.appy()
|
||||
user = self.getUser()
|
||||
rq = tool.request
|
||||
# Get the user roles
|
||||
res = user.getRoles()
|
||||
res = rq.userRoles
|
||||
# Add role "Anonymous"
|
||||
if 'Anonymous' not in res: res.append('Anonymous')
|
||||
# Add the user id if not anonymous
|
||||
userId = user.getId()
|
||||
if userId: res.append('user:%s' % userId)
|
||||
userId = user.login
|
||||
if userId != 'anon': res.append('user:%s' % userId)
|
||||
# Add group ids
|
||||
try:
|
||||
res += ['user:%s' % g for g in user.groups.keys()]
|
||||
res += ['user:%s' % g for g in rq.zopeUser.groups.keys()]
|
||||
except AttributeError, ae:
|
||||
pass # The Zope admin does not have this attribute.
|
||||
return res
|
||||
|
@ -434,7 +428,8 @@ class ToolMixin(BaseMixin):
|
|||
if refField: maxResults = refField.maxPerPage
|
||||
else: maxResults = self.appy().numberOfResultsPerPage
|
||||
elif maxResults == 'NO_LIMIT': maxResults = None
|
||||
res = SomeObjects(brains, maxResults, startNumber,noSecurity=noSecurity)
|
||||
res = gutils.SomeObjects(brains, maxResults, startNumber,
|
||||
noSecurity=noSecurity)
|
||||
res.brainsToObjects()
|
||||
# In some cases (p_remember=True), we need to keep some information
|
||||
# about the query results in the current user's session, allowing him
|
||||
|
@ -454,15 +449,14 @@ class ToolMixin(BaseMixin):
|
|||
self.REQUEST.SESSION['search_%s' % searchName] = uids
|
||||
return res.__dict__
|
||||
|
||||
def getResultColumnsLayouts(self, contentType, refInfo):
|
||||
def getResultColumnsLayouts(self, className, refInfo):
|
||||
'''Returns the column layouts for displaying objects of
|
||||
p_contentType.'''
|
||||
p_className.'''
|
||||
if refInfo[0]:
|
||||
res = refInfo[0].getAppyType(refInfo[1]).shownInfo
|
||||
return refInfo[0].getAppyType(refInfo[1]).shownInfo
|
||||
else:
|
||||
toolFieldName = 'resultColumnsFor%s' % contentType
|
||||
res = getattr(self.appy(), toolFieldName)
|
||||
return res
|
||||
toolFieldName = 'resultColumnsFor%s' % className
|
||||
return getattr(self.appy(), toolFieldName)
|
||||
|
||||
def truncateValue(self, value, width=15):
|
||||
'''Truncates the p_value according to p_width.'''
|
||||
|
@ -487,10 +481,11 @@ class ToolMixin(BaseMixin):
|
|||
def splitList(self, l, sub):
|
||||
'''Returns a list made of the same elements as p_l, but grouped into
|
||||
sub-lists of p_sub elements.'''
|
||||
return splitList(l, sub)
|
||||
return sutils.splitList(l, sub)
|
||||
|
||||
def quote(self, s):
|
||||
'''Returns the quoted version of p_s.'''
|
||||
if not isinstance(s, basestring): s = str(s)
|
||||
if "'" in s: return '"%s"' % s
|
||||
return "'%s'" % s
|
||||
|
||||
|
@ -500,21 +495,6 @@ class ToolMixin(BaseMixin):
|
|||
if url.endswith('/view'): return 'view'
|
||||
if url.endswith('/edit') or url.endswith('/do'): return 'edit'
|
||||
|
||||
def getPublishedObject(self, layoutType):
|
||||
'''Gets the currently published object, if its meta_class is among
|
||||
application classes.'''
|
||||
# In some situations (ie, we are querying objects), the published object
|
||||
# according to Zope is the tool, but we don't want to consider it that
|
||||
# way.
|
||||
if layoutType not in ('edit', 'view'): return
|
||||
obj = self.REQUEST['PUBLISHED']
|
||||
# If URL is a /do, published object is the "do" method.
|
||||
if type(obj) == types.MethodType: obj = obj.im_self
|
||||
else:
|
||||
parent = obj.getParentNode()
|
||||
if parent.id == 'ui': obj = parent.getParentNode()
|
||||
if obj.meta_type in self.getProductConfig().attributes: return obj
|
||||
|
||||
def getZopeClass(self, name):
|
||||
'''Returns the Zope class whose name is p_name.'''
|
||||
exec 'from Products.%s.%s import %s as C'% (self.getAppName(),name,name)
|
||||
|
@ -735,7 +715,7 @@ class ToolMixin(BaseMixin):
|
|||
rq = self.REQUEST
|
||||
self.storeSearchCriteria()
|
||||
# Go to the screen that displays search results
|
||||
backUrl = '%s/ui/query?className=%s&&search=customSearch' % \
|
||||
backUrl = '%s/query?className=%s&&search=customSearch' % \
|
||||
(self.absolute_url(), rq['className'])
|
||||
return self.goto(backUrl)
|
||||
|
||||
|
@ -747,15 +727,31 @@ class ToolMixin(BaseMixin):
|
|||
res += 'var %s = "%s";\n' % (msg, self.translate(msg))
|
||||
return res
|
||||
|
||||
def getColumnsSpecifiers(self, className, columnLayouts, dir):
|
||||
'''Extracts and returns, from a list of p_columnLayouts, info required
|
||||
for displaying columns of field values for instances of p_className,
|
||||
either in a result screen or for a Ref field.'''
|
||||
res = []
|
||||
for info in columnLayouts:
|
||||
fieldName, width, align = ColumnLayout(info).get()
|
||||
align = self.flipLanguageDirection(align, dir)
|
||||
field = self.getAppyType(fieldName, className)
|
||||
if not field:
|
||||
self.log('Field "%s", used in a column specifier, was not ' \
|
||||
'found.' % fieldName, type='warning')
|
||||
else:
|
||||
res.append(Object(field=field, width=width, align=align))
|
||||
return res
|
||||
|
||||
def getRefInfo(self, refInfo=None):
|
||||
'''When a search is restricted to objects referenced through a Ref
|
||||
field, this method returns information about this reference: the
|
||||
source content type and the Ref field (Appy type). If p_refInfo is
|
||||
not given, we search it among search criteria in the session.'''
|
||||
source class and the Ref field. If p_refInfo is not given, we search
|
||||
it among search criteria in the session.'''
|
||||
if not refInfo and (self.REQUEST.get('search', None) == 'customSearch'):
|
||||
criteria = self.REQUEST.SESSION.get('searchCriteria', None)
|
||||
if criteria and criteria.has_key('_ref'): refInfo = criteria['_ref']
|
||||
if not refInfo: return (None, None)
|
||||
if not refInfo: return None, None
|
||||
objectUid, fieldName = refInfo.split(':')
|
||||
obj = self.getObject(objectUid)
|
||||
return obj, fieldName
|
||||
|
@ -771,7 +767,7 @@ class ToolMixin(BaseMixin):
|
|||
res = []
|
||||
default = None # Also retrieve the default one here.
|
||||
groups = {} # The already encountered groups
|
||||
page = Page('main') # A dummy page required by class GroupDescr
|
||||
page = Page('main') # A dummy page required by class UiGroup
|
||||
# Get the searches statically defined on the class
|
||||
searches = ClassDescriptor.getSearches(appyClass, tool=self.appy())
|
||||
# Get the dynamically computed searches
|
||||
|
@ -779,22 +775,21 @@ class ToolMixin(BaseMixin):
|
|||
searches += appyClass.getDynamicSearches(self.appy())
|
||||
for search in searches:
|
||||
# Create the search descriptor
|
||||
sDescr = SearchDescr(search, className, self).get()
|
||||
uiSearch = UiSearch(search, className, self)
|
||||
if not search.group:
|
||||
# Insert the search at the highest level, not in any group.
|
||||
res.append(sDescr)
|
||||
res.append(uiSearch)
|
||||
else:
|
||||
gDescr = search.group.insertInto(res, groups, page, className,
|
||||
forSearch=True)
|
||||
GroupDescr.addWidget(gDescr, sDescr)
|
||||
uiGroup = search.group.insertInto(res, groups, page, className,
|
||||
forSearch=True)
|
||||
uiGroup.addField(uiSearch)
|
||||
# Is this search the default search?
|
||||
if search.default: default = sDescr
|
||||
return Object(searches=res, default=default).__dict__
|
||||
if search.default: default = uiSearch
|
||||
return Object(searches=res, default=default)
|
||||
|
||||
def getSearch(self, className, name, descr=False):
|
||||
'''Gets the Search instance (or a SearchDescr instance if p_descr is
|
||||
True) corresponding to the search named p_name, on class
|
||||
p_className.'''
|
||||
def getSearch(self, className, name, ui=False):
|
||||
'''Gets the Search instance (or a UiSearch instance if p_ui is True)
|
||||
corresponding to the search named p_name, on class p_className.'''
|
||||
if name == 'customSearch':
|
||||
# It is a custom search whose parameters are in the session.
|
||||
fields = self.REQUEST.SESSION['searchCriteria']
|
||||
|
@ -812,8 +807,8 @@ class ToolMixin(BaseMixin):
|
|||
else:
|
||||
# It is the search for every instance of p_className
|
||||
res = Search('allSearch')
|
||||
# Return a SearchDescr if required.
|
||||
if descr: res = SearchDescr(res, className, self).get()
|
||||
# Return a UiSearch if required.
|
||||
if ui: res = UiSearch(res, className, self)
|
||||
return res
|
||||
|
||||
def advancedSearchEnabledFor(self, className):
|
||||
|
@ -829,7 +824,7 @@ class ToolMixin(BaseMixin):
|
|||
'''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.'''
|
||||
baseUrl = self.absolute_url() + '/ui'
|
||||
baseUrl = self.absolute_url()
|
||||
baseParams = 'className=%s' % contentType
|
||||
rq = self.REQUEST
|
||||
if rq.get('ref'): baseParams += '&ref=%s' % rq.get('ref')
|
||||
|
@ -856,13 +851,14 @@ class ToolMixin(BaseMixin):
|
|||
return startNumber
|
||||
|
||||
def getNavigationInfo(self):
|
||||
'''Extracts navigation information from request/nav and returns a dict
|
||||
with the info that a page can use for displaying object
|
||||
'''Extracts navigation information from request/nav and returns an
|
||||
object with the info that a page can use for displaying object
|
||||
navigation.'''
|
||||
res = {}
|
||||
t,d1,d2,currentNumber,totalNumber = self.REQUEST.get('nav').split('.')
|
||||
res['currentNumber'] = int(currentNumber)
|
||||
res['totalNumber'] = int(totalNumber)
|
||||
res = Object()
|
||||
rq = self.REQUEST
|
||||
t, d1, d2, currentNumber, totalNumber = rq.get('nav').split('.')
|
||||
res.currentNumber = int(currentNumber)
|
||||
res.totalNumber = int(totalNumber)
|
||||
# Compute the label of the search, or ref field
|
||||
if t == 'search':
|
||||
searchName = d2
|
||||
|
@ -875,29 +871,28 @@ class ToolMixin(BaseMixin):
|
|||
else:
|
||||
# This is a named, predefined search.
|
||||
label = '%s_search_%s' % (d1.split(':')[0], searchName)
|
||||
res['backText'] = self.translate(label)
|
||||
res.backText = self.translate(label)
|
||||
# If it is a dynamic search this label does not exist.
|
||||
if ('_' in res['backText']): res['backText'] = ''
|
||||
if ('_' in res.backText): res.backText = ''
|
||||
else:
|
||||
fieldName, pageName = d2.split(':')
|
||||
sourceObj = self.getObject(d1)
|
||||
label = '%s_%s' % (sourceObj.meta_type, fieldName)
|
||||
res['backText'] = '%s - %s' % (sourceObj.Title(),
|
||||
self.translate(label))
|
||||
res.backText = '%s - %s' % (sourceObj.Title(),self.translate(label))
|
||||
newNav = '%s.%s.%s.%%d.%s' % (t, d1, d2, totalNumber)
|
||||
# Among, first, previous, next and last, which one do I need?
|
||||
previousNeeded = False # Previous ?
|
||||
previousIndex = res['currentNumber'] - 2
|
||||
if (previousIndex > -1) and (res['totalNumber'] > previousIndex):
|
||||
previousIndex = res.currentNumber - 2
|
||||
if (previousIndex > -1) and (res.totalNumber > previousIndex):
|
||||
previousNeeded = True
|
||||
nextNeeded = False # Next ?
|
||||
nextIndex = res['currentNumber']
|
||||
if nextIndex < res['totalNumber']: nextNeeded = True
|
||||
nextIndex = res.currentNumber
|
||||
if nextIndex < res.totalNumber: nextNeeded = True
|
||||
firstNeeded = False # First ?
|
||||
firstIndex = 0
|
||||
if previousIndex > 0: firstNeeded = True
|
||||
lastNeeded = False # Last ?
|
||||
lastIndex = res['totalNumber'] - 1
|
||||
lastIndex = res.totalNumber - 1
|
||||
if (nextIndex < lastIndex): lastNeeded = True
|
||||
# Get the list of available UIDs surrounding the current object
|
||||
if t == 'ref': # Manage navigation from a reference
|
||||
|
@ -908,16 +903,16 @@ class ToolMixin(BaseMixin):
|
|||
# Display the reference widget at the page where the current object
|
||||
# lies.
|
||||
startNumberKey = '%s%s_startNumber' % (masterObj.UID(), fieldName)
|
||||
startNumber = self.computeStartNumberFrom(res['currentNumber']-1,
|
||||
res['totalNumber'], batchSize)
|
||||
res['sourceUrl'] = masterObj.getUrl(**{startNumberKey:startNumber,
|
||||
'page':pageName, 'nav':''})
|
||||
startNumber = self.computeStartNumberFrom(res.currentNumber-1,
|
||||
res.totalNumber, batchSize)
|
||||
res.sourceUrl = masterObj.getUrl(**{startNumberKey:startNumber,
|
||||
'page':pageName, 'nav':''})
|
||||
else: # Manage navigation from a search
|
||||
contentType = d1
|
||||
searchName = keySuffix = d2
|
||||
batchSize = self.appy().numberOfResultsPerPage
|
||||
if not searchName: keySuffix = contentType
|
||||
s = self.REQUEST.SESSION
|
||||
s = rq.SESSION
|
||||
searchKey = 'search_%s' % keySuffix
|
||||
if s.has_key(searchKey): uids = s[searchKey]
|
||||
else: uids = {}
|
||||
|
@ -928,7 +923,7 @@ class ToolMixin(BaseMixin):
|
|||
# I do not have this UID in session. I will need to
|
||||
# retrigger the query by querying all objects surrounding
|
||||
# this one.
|
||||
newStartNumber = (res['currentNumber']-1) - (batchSize / 2)
|
||||
newStartNumber = (res.currentNumber-1) - (batchSize / 2)
|
||||
if newStartNumber < 0: newStartNumber = 0
|
||||
self.executeQuery(contentType, searchName=searchName,
|
||||
startNumber=newStartNumber, remember=True)
|
||||
|
@ -938,15 +933,15 @@ class ToolMixin(BaseMixin):
|
|||
if not uids.has_key(0): firstNeeded = False
|
||||
if not uids.has_key(lastIndex): lastNeeded = False
|
||||
# Compute URL of source object
|
||||
startNumber = self.computeStartNumberFrom(res['currentNumber']-1,
|
||||
res['totalNumber'], batchSize)
|
||||
res['sourceUrl'] = self.getQueryUrl(contentType, searchName,
|
||||
startNumber=startNumber)
|
||||
startNumber = self.computeStartNumberFrom(res.currentNumber-1,
|
||||
res.totalNumber, batchSize)
|
||||
res.sourceUrl = self.getQueryUrl(contentType, searchName,
|
||||
startNumber=startNumber)
|
||||
# Compute URLs
|
||||
for urlType in ('previous', 'next', 'first', 'last'):
|
||||
exec 'needIt = %sNeeded' % urlType
|
||||
urlKey = '%sUrl' % urlType
|
||||
res[urlKey] = None
|
||||
setattr(res, urlKey, None)
|
||||
if needIt:
|
||||
exec 'index = %sIndex' % urlType
|
||||
uid = None
|
||||
|
@ -959,37 +954,38 @@ class ToolMixin(BaseMixin):
|
|||
brain = self.getObject(uid, brain=True)
|
||||
if brain:
|
||||
sibling = brain.getObject()
|
||||
res[urlKey] = sibling.getUrl(nav=newNav % (index + 1),
|
||||
page=self.REQUEST.get('page', 'main'))
|
||||
setattr(res, urlKey, sibling.getUrl(\
|
||||
nav=newNav % (index + 1),
|
||||
page=rq.get('page', 'main')))
|
||||
return res
|
||||
|
||||
def getGroupedSearchFields(self, searchInfo):
|
||||
'''This method transforms p_searchInfo['fieldDicts'], which is a "flat"
|
||||
'''This method transforms p_searchInfo.fields, which is a "flat"
|
||||
list of fields, into a list of lists, where every sub-list having
|
||||
length p_searchInfo['nbOfColumns']. For every field, scolspan
|
||||
length p_searchInfo.nbOfColumns. For every field, scolspan
|
||||
(=colspan "for search") is taken into account.'''
|
||||
res = []
|
||||
row = []
|
||||
rowLength = 0
|
||||
for field in searchInfo['fieldDicts']:
|
||||
for field in searchInfo.fields:
|
||||
# Can I insert this field in the current row?
|
||||
remaining = searchInfo['nbOfColumns'] - rowLength
|
||||
if field['scolspan'] <= remaining:
|
||||
remaining = searchInfo.nbOfColumns - rowLength
|
||||
if field.scolspan <= remaining:
|
||||
# Yes.
|
||||
row.append(field)
|
||||
rowLength += field['scolspan']
|
||||
rowLength += field.scolspan
|
||||
else:
|
||||
# We must put the field on a new line. Complete the current one
|
||||
# if not complete.
|
||||
while rowLength < searchInfo['nbOfColumns']:
|
||||
while rowLength < searchInfo.nbOfColumns:
|
||||
row.append(None)
|
||||
rowLength += 1
|
||||
res.append(row)
|
||||
row = [field]
|
||||
rowLength = field['scolspan']
|
||||
rowLength = field.scolspan
|
||||
# Complete the last unfinished line if required.
|
||||
if row:
|
||||
while rowLength < searchInfo['nbOfColumns']:
|
||||
while rowLength < searchInfo.nbOfColumns:
|
||||
row.append(None)
|
||||
rowLength += 1
|
||||
res.append(row)
|
||||
|
@ -998,11 +994,6 @@ class ToolMixin(BaseMixin):
|
|||
# --------------------------------------------------------------------------
|
||||
# Authentication-related methods
|
||||
# --------------------------------------------------------------------------
|
||||
def _updateCookie(self, login, password):
|
||||
cookieValue = base64.encodestring('%s:%s' % (login, password)).rstrip()
|
||||
cookieValue = urllib.quote(cookieValue)
|
||||
self.REQUEST.RESPONSE.setCookie('__ac', cookieValue, path='/')
|
||||
|
||||
def _encryptPassword(self, password):
|
||||
'''Returns the encrypted version of clear p_password.'''
|
||||
return self.acl_users._encryptPassword(password)
|
||||
|
@ -1011,7 +1002,7 @@ class ToolMixin(BaseMixin):
|
|||
'''Performs the Zope-level authentication. Returns True if
|
||||
authentication succeeds.'''
|
||||
user = self.acl_users.validate(request)
|
||||
return not self.userIsAnon()
|
||||
return user.getUserName() != 'Anonymous User'
|
||||
|
||||
def _ldapAuthenticate(self, login, password):
|
||||
'''Performs a LDAP-based authentication. Returns True if authentication
|
||||
|
@ -1032,16 +1023,16 @@ class ToolMixin(BaseMixin):
|
|||
if jsEnabled and not cookiesEnabled:
|
||||
msg = self.translate('enable_cookies')
|
||||
return self.goto(urlBack, msg)
|
||||
# Extract the login and password
|
||||
# Extract the login and password, and create an authentication cookie
|
||||
login = rq.get('__ac_name', '')
|
||||
password = rq.get('__ac_password', '')
|
||||
password = rq.get('__ac_password', '')
|
||||
gutils.writeCookie(login, password, rq)
|
||||
# Perform the Zope-level authentication
|
||||
self._updateCookie(login, password)
|
||||
if self._zopeAuthenticate(rq) or self._ldapAuthenticate(login,password):
|
||||
msg = self.translate('login_ok')
|
||||
logMsg = 'User "%s" logged in.' % login
|
||||
else:
|
||||
rq.RESPONSE.expireCookie('__ac', path='/')
|
||||
rq.RESPONSE.expireCookie('_appy_', path='/')
|
||||
msg = self.translate('login_ko')
|
||||
logMsg = 'Authentication failed with login "%s".' % login
|
||||
self.log(logMsg)
|
||||
|
@ -1050,9 +1041,9 @@ class ToolMixin(BaseMixin):
|
|||
def performLogout(self):
|
||||
'''Logs out the current user when he clicks on "disconnect".'''
|
||||
rq = self.REQUEST
|
||||
userId = self.getUser().getId()
|
||||
userId = self.getUser().login
|
||||
# Perform the logout in acl_users
|
||||
rq.RESPONSE.expireCookie('__ac', path='/')
|
||||
rq.RESPONSE.expireCookie('_appy_', path='/')
|
||||
# Invalidate session.
|
||||
try:
|
||||
sdm = self.session_data_manager
|
||||
|
@ -1083,18 +1074,12 @@ class ToolMixin(BaseMixin):
|
|||
login, password = self.identify(auth)
|
||||
if not login:
|
||||
# Try to get them from a cookie
|
||||
cookie = request.get('__ac', None)
|
||||
login = request.get('__ac_name', None)
|
||||
if login and request.form.has_key('__ac_password'):
|
||||
# The user just entered his credentials. The cookie has not been
|
||||
# set yet (it will come in the upcoming HTTP response when the
|
||||
# current request will be served).
|
||||
login = request.get('__ac_name', '')
|
||||
password = request.get('__ac_password', '')
|
||||
elif cookie and (cookie != 'deleted'):
|
||||
cookieValue = base64.decodestring(urllib.unquote(cookie))
|
||||
if ':' in cookieValue:
|
||||
login, password = cookieValue.split(':')
|
||||
login, password = gutils.readCookie(request)
|
||||
if not login:
|
||||
# Maybe the user just entered his credentials. The cookie could
|
||||
# have been set in the response, but is not in the request.
|
||||
login = request.get('__ac_name', None)
|
||||
password = request.get('__ac_password', None)
|
||||
# Try to authenticate this user
|
||||
user = self.authenticate(login, password, request)
|
||||
emergency = self._emergency_user
|
||||
|
@ -1123,11 +1108,82 @@ class ToolMixin(BaseMixin):
|
|||
from AccessControl.User import BasicUserFolder
|
||||
BasicUserFolder.validate = validate
|
||||
|
||||
def getUser(self):
|
||||
'''Gets the User instance (Appy wrapper) corresponding to the current
|
||||
user.'''
|
||||
tool = self.appy()
|
||||
rq = tool.request
|
||||
# Try first to return the user that can be cached on the request.
|
||||
if hasattr(rq, 'user'): return rq.user
|
||||
# Get the user login from the authentication cookie.
|
||||
login, password = gutils.readCookie(rq)
|
||||
if not login: # It is the anonymous user or the system.
|
||||
# If we have a real request object, it is the anonymous user.
|
||||
login = (rq.__class__.__name__ == 'Object') and 'system' or 'anon'
|
||||
# Get the User object from a query in the catalog.
|
||||
user = tool.search1('User', noSecurity=True, login=login)
|
||||
rq.user = user
|
||||
# Precompute some values or this usser for performance reasons
|
||||
rq.userRoles = user.getRoles()
|
||||
rq.zopeUser = user.getZopeUser()
|
||||
return user
|
||||
#from AccessControl import getSecurityManager
|
||||
#user = getSecurityManager().getUser()
|
||||
#if not user:
|
||||
# from AccessControl.User import nobody
|
||||
# return nobody
|
||||
#return user
|
||||
|
||||
def getUserLine(self):
|
||||
'''Returns a info about the currently logged user as a 2-tuple: first
|
||||
elem is the one-line user info as shown on every page; second line is
|
||||
the URL to edit user info.'''
|
||||
user = self.getUser()
|
||||
userRoles = self.appy().request.userRoles
|
||||
info = [user.title]
|
||||
rolesToShow = [r for r in userRoles if r != 'Authenticated']
|
||||
if rolesToShow:
|
||||
info.append(', '.join([self.translate('role_%s' % r) \
|
||||
for r in rolesToShow]))
|
||||
# Edit URL for the user.
|
||||
url = None
|
||||
if user.o.mayEdit():
|
||||
url = user.o.getUrl(mode='edit', page='main', nav='')
|
||||
return (' | '.join(info), url)
|
||||
|
||||
def getUserName(self, login=None, normalized=False):
|
||||
'''Gets the user name corresponding to p_login (or the currently logged
|
||||
login if None), or the p_login itself if the user does not exist
|
||||
anymore. If p_normalized is True, special chars in the first and last
|
||||
names are normalized.'''
|
||||
tool = self.appy()
|
||||
if not login: login = tool.user.login
|
||||
# Manage the special case of an anonymous user.
|
||||
if login == 'Anonymous User':
|
||||
name = self.translate('anonymous')
|
||||
if normalized: name = sutils.normalizeString(name)
|
||||
return name
|
||||
# Manage the case of a "real" user.
|
||||
user = tool.search1('User', noSecurity=True, login=login)
|
||||
if not user: return login
|
||||
firstName = user.firstName
|
||||
name = user.name
|
||||
res = ''
|
||||
if firstName:
|
||||
if normalized: firstName = sutils.normalizeString(firstName)
|
||||
res += firstName
|
||||
if name:
|
||||
if normalized: name = sutils.normalizeString(name)
|
||||
if res: res += ' ' + name
|
||||
else: res = name
|
||||
if not res: res = login
|
||||
return res
|
||||
|
||||
def tempFile(self):
|
||||
'''A temp file has been created in a temp folder. This method returns
|
||||
this file to the browser.'''
|
||||
rq = self.REQUEST
|
||||
baseFolder = os.path.join(getOsTempFolder(), self.getAppName())
|
||||
baseFolder = os.path.join(sutils.getOsTempFolder(), self.getAppName())
|
||||
baseFolder = os.path.join(baseFolder, rq.SESSION.id)
|
||||
fileName = os.path.join(baseFolder, rq.get('name', ''))
|
||||
if os.path.exists(fileName):
|
||||
|
@ -1146,52 +1202,6 @@ class ToolMixin(BaseMixin):
|
|||
if ',' in contentType: return ()
|
||||
return [f.__dict__ for f in self.getAllAppyTypes(contentType) \
|
||||
if (f.type == 'Pod') and (f.show == 'result')]
|
||||
|
||||
def getUserLine(self):
|
||||
'''Returns a info about the currently logged user as a 2-tuple: first
|
||||
elem is the one-line user info as shown on every page; second line is
|
||||
the URL to edit user info.'''
|
||||
appyUser = self.appy().appyUser
|
||||
info = [appyUser.title]
|
||||
rolesToShow = [r for r in appyUser.roles \
|
||||
if r not in ('Authenticated', 'Member')]
|
||||
if rolesToShow:
|
||||
info.append(', '.join([self.translate('role_%s'%r) \
|
||||
for r in rolesToShow]))
|
||||
# Edit URL for the appy user.
|
||||
url = None
|
||||
if appyUser.o.mayEdit():
|
||||
url = appyUser.o.getUrl(mode='edit', page='main', nav='')
|
||||
return (' | '.join(info), url)
|
||||
|
||||
def getUserName(self, login=None, normalized=False):
|
||||
'''Gets the user name corresponding to p_login (or the currently logged
|
||||
login if None), or the p_login itself if the user does not exist
|
||||
anymore. If p_normalized is True, special chars in the first and last
|
||||
names are normalized.'''
|
||||
tool = self.appy()
|
||||
if not login: login = tool.user.getId()
|
||||
# Manage the special case of an anonymous user.
|
||||
if login == 'Anonymous User':
|
||||
name = self.translate('anonymous')
|
||||
if normalized: name = normalizeString(name)
|
||||
return name
|
||||
# Manage the case of a "real" user.
|
||||
user = tool.search1('User', noSecurity=True, login=login)
|
||||
if not user: return login
|
||||
firstName = user.firstName
|
||||
name = user.name
|
||||
res = ''
|
||||
if firstName:
|
||||
if normalized: firstName = normalizeString(firstName)
|
||||
res += firstName
|
||||
if name:
|
||||
if normalized: name = normalizeString(name)
|
||||
if res: res += ' ' + name
|
||||
else: res = name
|
||||
if not res: res = login
|
||||
return res
|
||||
|
||||
def formatDate(self, aDate, withHour=True):
|
||||
'''Returns aDate formatted as specified by tool.dateFormat.
|
||||
If p_withHour is True, hour is appended, with a format specified
|
||||
|
@ -1216,7 +1226,7 @@ class ToolMixin(BaseMixin):
|
|||
htmlMessage = '<a href="%s"><img src="%s/ui/home.gif"/></a>' \
|
||||
'You are not allowed to access this page.' % \
|
||||
(siteUrl, siteUrl)
|
||||
userId = self.appy().user.getId() or 'system|anon'
|
||||
userId = self.appy().user.login
|
||||
textMessage = 'Unauthorized for %s @%s.' % \
|
||||
(userId, self.REQUEST.get('PATH_INFO'))
|
||||
else:
|
||||
|
@ -1256,7 +1266,7 @@ class ToolMixin(BaseMixin):
|
|||
return self.goto(backUrl, msg)
|
||||
# Create a temporary file whose name is the user login and whose
|
||||
# content is a generated token.
|
||||
f = file(os.path.join(getOsTempFolder(), login), 'w')
|
||||
f = file(os.path.join(sutils.getOsTempFolder(), login), 'w')
|
||||
token = String().generatePassword()
|
||||
f.write(token)
|
||||
f.close()
|
||||
|
@ -1277,7 +1287,7 @@ class ToolMixin(BaseMixin):
|
|||
# Check if such token exists in temp folder
|
||||
res = None
|
||||
siteUrl = self.getSiteUrl()
|
||||
tokenFile = os.path.join(getOsTempFolder(), login)
|
||||
tokenFile = os.path.join(sutils.getOsTempFolder(), login)
|
||||
if os.path.exists(tokenFile):
|
||||
f = file(tokenFile)
|
||||
storedToken = f.read()
|
||||
|
|
|
@ -7,7 +7,7 @@ import os, os.path, sys, types, urllib, cgi
|
|||
from appy import Object
|
||||
import appy.gen as gen
|
||||
from appy.gen.utils import *
|
||||
from appy.gen.layout import Table, defaultPageLayouts, ColumnLayout
|
||||
from appy.gen.layout import Table, defaultPageLayouts
|
||||
from appy.gen.descriptors import WorkflowDescriptor, ClassDescriptor
|
||||
from appy.shared.utils import sequenceTypes,normalizeText,Traceback,getMimeType
|
||||
from appy.shared.data import rtlLanguages
|
||||
|
@ -169,7 +169,7 @@ class BaseMixin:
|
|||
self.workflow_history[key] = tuple(history)
|
||||
appy = self.appy()
|
||||
self.log('Data change event deleted by %s for %s (UID=%s).' % \
|
||||
(appy.user.getId(), appy.klass.__name__, appy.uid))
|
||||
(appy.user.login, appy.klass.__name__, appy.uid))
|
||||
self.goto(self.getUrl(rq['HTTP_REFERER']))
|
||||
|
||||
def onUnlink(self):
|
||||
|
@ -213,13 +213,19 @@ class BaseMixin:
|
|||
|
||||
def view(self):
|
||||
'''Returns the view PX.'''
|
||||
appySelf = self.appy()
|
||||
return appySelf.pxView({'self': appySelf})
|
||||
obj = self.appy()
|
||||
return obj.pxView({'obj': obj, 'tool': obj.tool})
|
||||
|
||||
def edit(self):
|
||||
'''Returns the edit PX.'''
|
||||
appySelf = self.appy()
|
||||
return appySelf.pxEdit({'self': appySelf})
|
||||
obj = self.appy()
|
||||
return obj.pxEdit({'obj': obj, 'tool': obj.tool})
|
||||
|
||||
def ajax(self):
|
||||
'''Called via an Ajax request to render some PX whose name is in the
|
||||
request.'''
|
||||
obj = self.appy()
|
||||
return obj.pxAjax({'obj': obj, 'tool': obj.tool})
|
||||
|
||||
def setLock(self, user, page):
|
||||
'''A p_user edits a given p_page on this object: we will set a lock, to
|
||||
|
@ -232,7 +238,7 @@ class BaseMixin:
|
|||
# Raise an error is the page is already locked by someone else. If the
|
||||
# page is already locked by the same user, we don't mind: he could have
|
||||
# used back/forward buttons of its browser...
|
||||
userId = user.getId()
|
||||
userId = user.login
|
||||
if (page in self.locks) and (userId != self.locks[page][0]):
|
||||
from AccessControl import Unauthorized
|
||||
raise Unauthorized('This page is locked.')
|
||||
|
@ -245,7 +251,7 @@ class BaseMixin:
|
|||
mind and consider the page as unlocked. If the page is locked, this
|
||||
method returns the tuple (userId, lockDate).'''
|
||||
if hasattr(self.aq_base, 'locks') and (page in self.locks):
|
||||
if (user.getId() != self.locks[page][0]): return self.locks[page]
|
||||
if (user.login != self.locks[page][0]): return self.locks[page]
|
||||
|
||||
def removeLock(self, page, force=False):
|
||||
'''Removes the lock on the current page. This happens:
|
||||
|
@ -257,7 +263,7 @@ class BaseMixin:
|
|||
# Raise an error if the user that saves changes is not the one that
|
||||
# has locked the page (excepted if p_force is True)
|
||||
if not force:
|
||||
userId = self.getUser().getId()
|
||||
userId = self.getTool().getUser().login
|
||||
if self.locks[page][0] != userId:
|
||||
from AccessControl import Unauthorized
|
||||
raise Unauthorized('This page was locked by someone else.')
|
||||
|
@ -270,7 +276,7 @@ class BaseMixin:
|
|||
view.pt for this page. In this case, we consider that the user has
|
||||
left the edit page in an unexpected way and we remove the lock.'''
|
||||
if hasattr(self.aq_base, 'locks') and (page in self.locks) and \
|
||||
(user.getId() == self.locks[page][0]):
|
||||
(user.login == self.locks[page][0]):
|
||||
del self.locks[page]
|
||||
|
||||
def onUnlock(self):
|
||||
|
@ -420,11 +426,11 @@ class BaseMixin:
|
|||
# previous pages may have changed). Moreover, previous and next
|
||||
# pages may not be available in "edit" mode, so we return the edit
|
||||
# or view pages depending on page.show.
|
||||
phaseInfo = self.getAppyPhases(currentOnly=True, layoutType='edit')
|
||||
pageName, pageInfo = self.getPreviousPage(phaseInfo, rq['page'])
|
||||
phaseObj = self.getAppyPhases(currentOnly=True, layoutType='edit')
|
||||
pageName, pageInfo = phaseObj.getPreviousPage(rq['page'])
|
||||
if pageName:
|
||||
# Return to the edit or view page?
|
||||
if pageInfo['showOnEdit']:
|
||||
if pageInfo.showOnEdit:
|
||||
rq.set('page', pageName)
|
||||
# I do not use gotoEdit here because I really need to
|
||||
# redirect the user to the edit page. Indeed, the object
|
||||
|
@ -440,11 +446,11 @@ class BaseMixin:
|
|||
# We remember page name, because the next method may set a new
|
||||
# current page if the current one is not visible anymore.
|
||||
pageName = rq['page']
|
||||
phaseInfo = self.getAppyPhases(currentOnly=True, layoutType='edit')
|
||||
pageName, pageInfo = self.getNextPage(phaseInfo, pageName)
|
||||
phaseObj = self.getAppyPhases(currentOnly=True, layoutType='edit')
|
||||
pageName, pageInfo = phaseObj.getNextPage(pageName)
|
||||
if pageName:
|
||||
# Return to the edit or view page?
|
||||
if pageInfo['showOnEdit']:
|
||||
if pageInfo.showOnEdit:
|
||||
# Same remark as above (click on "previous").
|
||||
return self.goto(obj.getUrl(mode='edit', page=pageName))
|
||||
else:
|
||||
|
@ -543,7 +549,8 @@ class BaseMixin:
|
|||
|
||||
def addHistoryEvent(self, action, **kw):
|
||||
'''Adds an event in the object history.'''
|
||||
userId = self.getUser().getId()
|
||||
user = self.getTool().getUser()
|
||||
userId = user and user.login or 'system'
|
||||
from DateTime import DateTime
|
||||
event = {'action': action, 'actor': userId, 'time': DateTime(),
|
||||
'comments': ''}
|
||||
|
@ -603,13 +610,13 @@ class BaseMixin:
|
|||
def gotoEdit(self):
|
||||
'''Brings the user to the edit page for this object. This method takes
|
||||
care of not carrying any password value. Unlike m_goto above, there
|
||||
is no HTTP redirect here: we execute directly macro "edit" and we
|
||||
is no HTTP redirect here: we execute directly PX "edit" and we
|
||||
return the result.'''
|
||||
page = self.REQUEST.get('page', 'main')
|
||||
for field in self.getAppyTypes('edit', page):
|
||||
if (field.type == 'String') and (field.format in (3,4)):
|
||||
self.REQUEST.set(field.name, '')
|
||||
return self.ui.edit(self)
|
||||
return self.edit()
|
||||
|
||||
def showField(self, name, layoutType='view'):
|
||||
'''Must I show field named p_name on this p_layoutType ?'''
|
||||
|
@ -755,7 +762,7 @@ class BaseMixin:
|
|||
res.update(parent)
|
||||
return res
|
||||
|
||||
def getAppyType(self, name, asDict=False, className=None):
|
||||
def getAppyType(self, name, className=None):
|
||||
'''Returns the Appy type named p_name. If no p_className is defined, the
|
||||
field is supposed to belong to self's class.'''
|
||||
isInnerType = '*' in name # An inner type lies within a List type.
|
||||
|
@ -770,7 +777,6 @@ class BaseMixin:
|
|||
klass = self.getTool().getAppyClass(className, wrapper=True)
|
||||
res = getattr(klass, name, None)
|
||||
if res and isInnerType: res = res.getField(subName)
|
||||
if res and asDict: return res.__dict__
|
||||
return res
|
||||
|
||||
def getAllAppyTypes(self, className=None):
|
||||
|
@ -782,40 +788,39 @@ class BaseMixin:
|
|||
klass = self.getTool().getAppyClass(className, wrapper=True)
|
||||
return klass.__fields__
|
||||
|
||||
def getGroupedAppyTypes(self, layoutType, pageName, cssJs=None):
|
||||
'''Returns the fields sorted by group. For every field, the appyType
|
||||
(dict version) is given. If a dict is given in p_cssJs, we will add
|
||||
it in the css and js files required by the fields.'''
|
||||
def getGroupedFields(self, layoutType, pageName, cssJs=None):
|
||||
'''Returns the fields sorted by group. If a dict is given in p_cssJs,
|
||||
we will add it in the css and js files required by the fields.'''
|
||||
res = []
|
||||
groups = {} # The already encountered groups
|
||||
groups = {} # The already encountered groups.
|
||||
# If a dict is given in p_cssJs, we must fill it with the CSS and JS
|
||||
# files required for every returned appyType.
|
||||
# files required for every returned field.
|
||||
collectCssJs = isinstance(cssJs, dict)
|
||||
css = js = None
|
||||
# If param "refresh" is there, we must reload the Python class
|
||||
refresh = ('refresh' in self.REQUEST)
|
||||
if refresh:
|
||||
klass = self.getClass(reloaded=True)
|
||||
for appyType in self.getAllAppyTypes():
|
||||
if refresh: appyType = appyType.reload(klass, self)
|
||||
if appyType.page.name != pageName: continue
|
||||
if not appyType.isShowable(self, layoutType): continue
|
||||
for field in self.getAllAppyTypes():
|
||||
if refresh: field = field.reload(klass, self)
|
||||
if field.page.name != pageName: continue
|
||||
if not field.isShowable(self, layoutType): continue
|
||||
if collectCssJs:
|
||||
if css == None: css = []
|
||||
appyType.getCss(layoutType, css)
|
||||
field.getCss(layoutType, css)
|
||||
if js == None: js = []
|
||||
appyType.getJs(layoutType, js)
|
||||
if not appyType.group:
|
||||
res.append(appyType.__dict__)
|
||||
field.getJs(layoutType, js)
|
||||
if not field.group:
|
||||
res.append(field)
|
||||
else:
|
||||
# Insert the GroupDescr instance corresponding to
|
||||
# appyType.group at the right place
|
||||
groupDescr = appyType.group.insertInto(res, groups,
|
||||
appyType.page, self.meta_type)
|
||||
GroupDescr.addWidget(groupDescr, appyType.__dict__)
|
||||
# Insert the UiGroup instance corresponding to field.group at
|
||||
# the right place.
|
||||
uiGroup = field.group.insertInto(res, groups, field.page,
|
||||
self.meta_type)
|
||||
uiGroup.addField(field)
|
||||
if collectCssJs:
|
||||
cssJs['css'] = css
|
||||
cssJs['js'] = js
|
||||
cssJs['css'] = css or ()
|
||||
cssJs['js'] = js or ()
|
||||
return res
|
||||
|
||||
def getAppyTypes(self, layoutType, pageName):
|
||||
|
@ -852,23 +857,6 @@ class BaseMixin:
|
|||
return klass.styles[elem]
|
||||
return elem
|
||||
|
||||
def getColumnsSpecifiers(self, columnLayouts, dir):
|
||||
'''Extracts and returns, from a list of p_columnLayouts, the information
|
||||
that is necessary for displaying a column in a result screen or for
|
||||
a Ref field.'''
|
||||
res = []
|
||||
tool = self.getTool()
|
||||
for info in columnLayouts:
|
||||
fieldName, width, align = ColumnLayout(info).get()
|
||||
align = tool.flipLanguageDirection(align, dir)
|
||||
field = self.getAppyType(fieldName, asDict=True)
|
||||
if not field:
|
||||
self.log('Field "%s", used in a column specifier, was not ' \
|
||||
'found.' % fieldName, type='warning')
|
||||
else:
|
||||
res.append({'field':field, 'width':width, 'align': align})
|
||||
return res
|
||||
|
||||
def getAppyTransitions(self, includeFake=True, includeNotShowable=False):
|
||||
'''This method returns info about transitions that one can trigger from
|
||||
the user interface.
|
||||
|
@ -921,21 +909,21 @@ class BaseMixin:
|
|||
# Get the list of phases
|
||||
res = [] # Ordered list of phases
|
||||
phases = {} # Dict of phases
|
||||
for appyType in self.getAllAppyTypes():
|
||||
typePhase = appyType.page.phase
|
||||
if typePhase not in phases:
|
||||
phase = PhaseDescr(typePhase, self)
|
||||
res.append(phase.__dict__)
|
||||
phases[typePhase] = phase
|
||||
for field in self.getAllAppyTypes():
|
||||
fieldPhase = field.page.phase
|
||||
if fieldPhase not in phases:
|
||||
phase = gen.Phase(fieldPhase, self)
|
||||
res.append(phase)
|
||||
phases[fieldPhase] = phase
|
||||
else:
|
||||
phase = phases[typePhase]
|
||||
phase.addPage(appyType, self, layoutType)
|
||||
if (appyType.type == 'Ref') and appyType.navigable:
|
||||
phase.addPageLinks(appyType, self)
|
||||
phase = phases[fieldPhase]
|
||||
phase.addPage(field, self, layoutType)
|
||||
if (field.type == 'Ref') and field.navigable:
|
||||
phase.addPageLinks(field, self)
|
||||
# Remove phases that have no visible page
|
||||
for i in range(len(res)-1, -1, -1):
|
||||
if not res[i]['pages']:
|
||||
del phases[res[i]['name']]
|
||||
if not res[i].pages:
|
||||
del phases[res[i].name]
|
||||
del res[i]
|
||||
# Compute next/previous phases of every phase
|
||||
for ph in phases.itervalues():
|
||||
|
@ -948,16 +936,16 @@ class BaseMixin:
|
|||
if not page:
|
||||
if layoutType == 'edit': page = self.getDefaultEditPage()
|
||||
else: page = self.getDefaultViewPage()
|
||||
for phaseInfo in res:
|
||||
if page in phaseInfo['pages']:
|
||||
return phaseInfo
|
||||
for phase in res:
|
||||
if page in phase.pages:
|
||||
return phase
|
||||
# If I am here, it means that the page as defined in the request,
|
||||
# or the default page, is not existing nor visible in any phase.
|
||||
# In this case I find the first visible page among all phases.
|
||||
viewAttr = 'showOn%s' % layoutType.capitalize()
|
||||
for phase in res:
|
||||
for page in phase['pages']:
|
||||
if phase['pagesInfo'][page][viewAttr]:
|
||||
for page in phase.pages:
|
||||
if getattr(phase.pagesInfo[page], viewAttr):
|
||||
rq.set('page', page)
|
||||
pageFound = True
|
||||
break
|
||||
|
@ -965,9 +953,9 @@ class BaseMixin:
|
|||
else:
|
||||
# Return an empty list if we have a single, link-free page within
|
||||
# a single phase.
|
||||
if (len(res) == 1) and (len(res[0]['pages']) == 1) and \
|
||||
not res[0]['pagesInfo'][res[0]['pages'][0]].get('links'):
|
||||
return None
|
||||
if (len(res) == 1) and (len(res[0].pages) == 1) and \
|
||||
not res[0].pagesInfo[res[0].pages[0]].links:
|
||||
return
|
||||
return res
|
||||
|
||||
def getSupTitle(self, navInfo=''):
|
||||
|
@ -983,87 +971,6 @@ class BaseMixin:
|
|||
if hasattr(appyObj, 'getSubTitle'): return appyObj.getSubTitle()
|
||||
return ''
|
||||
|
||||
def getPreviousPage(self, phase, page):
|
||||
'''Returns the page that precedes p_page which is in p_phase.'''
|
||||
try:
|
||||
pageIndex = phase['pages'].index(page)
|
||||
except ValueError:
|
||||
# The current page is probably not visible anymore. Return the
|
||||
# first available page in current phase.
|
||||
res = phase['pages'][0]
|
||||
return res, phase['pagesInfo'][res]
|
||||
if pageIndex > 0:
|
||||
# We stay on the same phase, previous page
|
||||
res = phase['pages'][pageIndex-1]
|
||||
resInfo = phase['pagesInfo'][res]
|
||||
return res, resInfo
|
||||
else:
|
||||
if phase['previousPhase']:
|
||||
# We go to the last page of previous phase
|
||||
previousPhase = phase['previousPhase']
|
||||
res = previousPhase['pages'][-1]
|
||||
resInfo = previousPhase['pagesInfo'][res]
|
||||
return res, resInfo
|
||||
else:
|
||||
return None, None
|
||||
|
||||
def getNextPage(self, phase, page):
|
||||
'''Returns the page that follows p_page which is in p_phase.'''
|
||||
try:
|
||||
pageIndex = phase['pages'].index(page)
|
||||
except ValueError:
|
||||
# The current page is probably not visible anymore. Return the
|
||||
# first available page in current phase.
|
||||
res = phase['pages'][0]
|
||||
return res, phase['pagesInfo'][res]
|
||||
if pageIndex < len(phase['pages'])-1:
|
||||
# We stay on the same phase, next page
|
||||
res = phase['pages'][pageIndex+1]
|
||||
resInfo = phase['pagesInfo'][res]
|
||||
return res, resInfo
|
||||
else:
|
||||
if phase['nextPhase']:
|
||||
# We go to the first page of next phase
|
||||
nextPhase = phase['nextPhase']
|
||||
res = nextPhase['pages'][0]
|
||||
resInfo = nextPhase['pagesInfo'][res]
|
||||
return res, resInfo
|
||||
else:
|
||||
return None, None
|
||||
|
||||
def changeRefOrder(self, fieldName, objectUid, newIndex, isDelta):
|
||||
'''This method changes the position of object with uid p_objectUid in
|
||||
reference field p_fieldName to p_newIndex i p_isDelta is False, or
|
||||
to actualIndex+p_newIndex if p_isDelta is True.'''
|
||||
refs = getattr(self.aq_base, fieldName, None)
|
||||
oldIndex = refs.index(objectUid)
|
||||
refs.remove(objectUid)
|
||||
if isDelta:
|
||||
newIndex = oldIndex + newIndex
|
||||
else:
|
||||
pass # To implement later on
|
||||
refs.insert(newIndex, objectUid)
|
||||
|
||||
def onChangeRefOrder(self):
|
||||
'''This method is called when the user wants to change order of an
|
||||
item in a reference field.'''
|
||||
rq = self.REQUEST
|
||||
# Move the item up (-1), down (+1) ?
|
||||
move = -1 # Move up
|
||||
if rq['move'] == 'down':
|
||||
move = 1 # Down
|
||||
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 notifyWorkflowCreated(self):
|
||||
'''This method is called every time an object is created, be it temp or
|
||||
not. The objective here is to initialise workflow-related data on
|
||||
|
@ -1589,9 +1496,9 @@ class BaseMixin:
|
|||
|
||||
getUrlDefaults = {'page':True, 'nav':True}
|
||||
def getUrl(self, base=None, mode='view', **kwargs):
|
||||
'''Returns a Appy URL.
|
||||
'''Returns an URL for this object.
|
||||
* If p_base is None, it will be the base URL for this object
|
||||
(ie, self.absolute_url()).
|
||||
(ie, Zope self.absolute_url()).
|
||||
* p_mode can be "edit", "view" or "raw" (a non-param, base URL)
|
||||
* p_kwargs can store additional parameters to add to the URL.
|
||||
In this dict, every value that is a string will be added to the
|
||||
|
@ -1600,7 +1507,7 @@ class BaseMixin:
|
|||
param will not be included in the URL at all).'''
|
||||
# Define the URL suffix
|
||||
suffix = ''
|
||||
if mode != 'raw': suffix = '/ui/%s' % mode
|
||||
if mode != 'raw': suffix = '/%s' % mode
|
||||
# Define base URL if omitted
|
||||
if not base:
|
||||
base = self.absolute_url() + suffix
|
||||
|
@ -1609,9 +1516,8 @@ class BaseMixin:
|
|||
if '?' in base: base = base[:base.index('?')]
|
||||
base = base.strip('/')
|
||||
for mode in ('view', 'edit'):
|
||||
suffix = 'ui/%s' % mode
|
||||
if base.endswith(suffix):
|
||||
base = base[:-len(suffix)].strip('/')
|
||||
if base.endswith(mode):
|
||||
base = base[:-len(mode)].strip('/')
|
||||
break
|
||||
return base
|
||||
# Manage default args
|
||||
|
@ -1633,15 +1539,6 @@ class BaseMixin:
|
|||
params = ''
|
||||
return '%s%s' % (base, params)
|
||||
|
||||
def getUser(self):
|
||||
'''Gets the Zope object representing the authenticated user.'''
|
||||
from AccessControl import getSecurityManager
|
||||
user = getSecurityManager().getUser()
|
||||
if not user:
|
||||
from AccessControl.User import nobody
|
||||
return nobody
|
||||
return user
|
||||
|
||||
def getTool(self):
|
||||
'''Returns the application tool.'''
|
||||
return self.getPhysicalRoot().config
|
||||
|
@ -1659,7 +1556,8 @@ class BaseMixin:
|
|||
data folder) it returns None.'''
|
||||
parent = self.getParentNode()
|
||||
# Not-Managers can't navigate back to the tool
|
||||
if (parent.id == 'config') and not self.getUser().has_role('Manager'):
|
||||
if (parent.id == 'config') and \
|
||||
not self.getTool().getUser().has_role('Manager'):
|
||||
return False
|
||||
if parent.meta_type not in ('Folder', 'Temporary Folder'): return parent
|
||||
|
||||
|
@ -1678,7 +1576,7 @@ class BaseMixin:
|
|||
return res
|
||||
|
||||
def index_html(self):
|
||||
'''Redirects to /ui.'''
|
||||
'''Redirects to /view.'''
|
||||
rq = self.REQUEST
|
||||
if rq.has_key('do'):
|
||||
# The user wants to call a method on this object and get its result
|
||||
|
@ -1688,10 +1586,6 @@ class BaseMixin:
|
|||
# The user wants to consult the view page for this object
|
||||
return rq.RESPONSE.redirect(self.getUrl())
|
||||
|
||||
def userIsAnon(self):
|
||||
'''Is the currently logged user anonymous ?'''
|
||||
return self.getUser().getUserName() == 'Anonymous User'
|
||||
|
||||
def getUserLanguage(self):
|
||||
'''Gets the language (code) of the current user.'''
|
||||
if not hasattr(self, 'REQUEST'): return 'en'
|
||||
|
@ -1746,15 +1640,13 @@ class BaseMixin:
|
|||
if not domain: domain = cfg.PROJECTNAME
|
||||
# Get the label name, and the field-specific mapping if any.
|
||||
if field:
|
||||
# p_field is the dict version of a appy type or group
|
||||
if field['type'] != 'group':
|
||||
fieldMapping = field['mapping'][label]
|
||||
if field.type != 'group':
|
||||
fieldMapping = field.mapping[label]
|
||||
if fieldMapping:
|
||||
if callable(fieldMapping):
|
||||
appyField = self.getAppyType(field['name'])
|
||||
fieldMapping=appyField.callMethod(self,fieldMapping)
|
||||
fieldMapping = field.callMethod(self, fieldMapping)
|
||||
mapping.update(fieldMapping)
|
||||
label = field['%sId' % label]
|
||||
label = getattr(field, '%sId' % label)
|
||||
# We will get the translation from a Translation object.
|
||||
# In what language must we get the translation?
|
||||
if not language: language = self.getUserLanguage()
|
||||
|
@ -1855,11 +1747,11 @@ class BaseMixin:
|
|||
|
||||
def allows(self, permission, raiseError=False):
|
||||
'''Has the logged user p_permission on p_self ?'''
|
||||
hasPermission = self.getUser().has_permission(permission, self)
|
||||
if not hasPermission and raiseError:
|
||||
res = self.getTool().getUser().has_permission(permission, self)
|
||||
if not res and raiseError:
|
||||
from AccessControl import Unauthorized
|
||||
raise Unauthorized
|
||||
return hasPermission
|
||||
return res
|
||||
|
||||
def getEditorInit(self, name):
|
||||
'''Gets the Javascript init code for displaying a rich editor for
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue