appy.gen: first Ploneless version.
This commit is contained in:
parent
5672c81553
commit
d0cbe7e573
360 changed files with 1003 additions and 1017 deletions
gen/plone25/mixins
|
@ -6,8 +6,7 @@ class TestMixin:
|
|||
'''This class is mixed in with any PloneTestCase.'''
|
||||
def createUser(self, userId, roles):
|
||||
'''Creates a user with id p_userId with some p_roles.'''
|
||||
pms = self.portal.portal_membership
|
||||
pms.addMember(userId, 'password', [], [])
|
||||
self.acl_users.addMember(userId, 'password', [], [])
|
||||
self.setRoles(roles, name=userId)
|
||||
|
||||
def changeUser(self, userId):
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
import re, os, os.path, time, types
|
||||
import re, os, os.path, time, random, types, base64, urllib
|
||||
from appy.shared import mimeTypes
|
||||
from appy.shared.utils import getOsTempFolder
|
||||
from appy.shared.data import languages
|
||||
import appy.gen
|
||||
from appy.gen import Type, Search, Selection
|
||||
from appy.gen.utils import SomeObjects, sequenceTypes, getClassName
|
||||
from appy.gen.plone25.mixins import BaseMixin
|
||||
from appy.gen.plone25.wrappers import AbstractWrapper
|
||||
from appy.gen.plone25.descriptors import ClassDescriptor
|
||||
try:
|
||||
from AccessControl.ZopeSecurityPolicy import _noroles
|
||||
except ImportError:
|
||||
_noroles = []
|
||||
|
||||
# Errors -----------------------------------------------------------------------
|
||||
jsMessages = ('no_elem_selected', 'delete_confirm')
|
||||
|
@ -27,13 +32,17 @@ class ToolMixin(BaseMixin):
|
|||
res = '%s%s' % (elems[1], elems[4])
|
||||
return res
|
||||
|
||||
def getCatalog(self):
|
||||
'''Returns the catalog object.'''
|
||||
return self.getParentNode().catalog
|
||||
|
||||
def getApp(self):
|
||||
'''Returns the root application object.'''
|
||||
return self.portal_url.getPortalObject()
|
||||
'''Returns the root Zope object.'''
|
||||
return self.getPhysicalRoot()
|
||||
|
||||
def getSiteUrl(self):
|
||||
'''Returns the absolute URL of this site.'''
|
||||
return self.portal_url.getPortalObject().absolute_url()
|
||||
return self.getApp().absolute_url()
|
||||
|
||||
def getPodInfo(self, obj, name):
|
||||
'''Gets the available POD formats for Pod field named p_name on
|
||||
|
@ -61,20 +70,43 @@ class ToolMixin(BaseMixin):
|
|||
return res.content
|
||||
|
||||
def getAttr(self, name):
|
||||
'''Gets attribute named p_attrName. Useful because we can't use getattr
|
||||
directly in Zope Page Templates.'''
|
||||
'''Gets attribute named p_name.'''
|
||||
return getattr(self.appy(), name, None)
|
||||
|
||||
def getAppName(self):
|
||||
'''Returns the name of this application.'''
|
||||
'''Returns the name of the application.'''
|
||||
return self.getProductConfig().PROJECTNAME
|
||||
|
||||
def getAppFolder(self):
|
||||
'''Returns the folder at the root of the Plone site that is dedicated
|
||||
to this application.'''
|
||||
cfg = self.getProductConfig()
|
||||
portal = cfg.getToolByName(self, 'portal_url').getPortalObject()
|
||||
return getattr(portal, self.getAppName())
|
||||
def getPath(self, path):
|
||||
'''Returns the folder or object whose absolute path p_path.'''
|
||||
res = self.getPhysicalRoot()
|
||||
if path == '/': return res
|
||||
path = path[1:]
|
||||
if '/' not in path: return res._getOb(path) # For performance
|
||||
for elem in path.split('/'): res = res._getOb(elem)
|
||||
return res
|
||||
|
||||
def getLanguages(self):
|
||||
'''Returns the supported languages. First one is the default.'''
|
||||
return self.getProductConfig().languages
|
||||
|
||||
def getLanguageName(self, code):
|
||||
'''Gets the language name (in this language) from a 2-chars language
|
||||
p_code.'''
|
||||
return languages.get(code)[2]
|
||||
|
||||
def getMessages(self):
|
||||
'''Returns the list of messages to return to the user.'''
|
||||
if hasattr(self.REQUEST, 'messages'):
|
||||
# Empty the messages and return it
|
||||
res = self.REQUEST.messages
|
||||
del self.REQUEST.messages
|
||||
else:
|
||||
res = []
|
||||
# Add portal_status_message key if present
|
||||
if 'portal_status_message' in self.REQUEST:
|
||||
res.append( ('info', self.REQUEST['portal_status_message']) )
|
||||
return res
|
||||
|
||||
def getRootClasses(self):
|
||||
'''Returns the list of root classes for this application.'''
|
||||
|
@ -124,7 +156,7 @@ class ToolMixin(BaseMixin):
|
|||
return {'fields': fields, 'nbOfColumns': nbOfColumns,
|
||||
'fieldDicts': fieldDicts}
|
||||
|
||||
queryParamNames = ('type_name', 'search', 'sortKey', 'sortOrder',
|
||||
queryParamNames = ('className', 'search', 'sortKey', 'sortOrder',
|
||||
'filterKey', 'filterValue')
|
||||
def getQueryInfo(self):
|
||||
'''If we are showing search results, this method encodes in a string all
|
||||
|
@ -160,8 +192,8 @@ class ToolMixin(BaseMixin):
|
|||
return [importParams['headers'], elems]
|
||||
|
||||
def showPortlet(self, context):
|
||||
if self.portal_membership.isAnonymousUser(): return False
|
||||
if context.id == 'skyn': context = context.getParentNode()
|
||||
if self.userIsAnon(): return False
|
||||
if context.id == 'ui': context = context.getParentNode()
|
||||
res = True
|
||||
if not self.getRootClasses():
|
||||
res = False
|
||||
|
@ -170,24 +202,25 @@ class ToolMixin(BaseMixin):
|
|||
if (self.id in context.absolute_url()): res = True
|
||||
return res
|
||||
|
||||
def getObject(self, uid, appy=False):
|
||||
def getObject(self, uid, appy=False, brain=False):
|
||||
'''Allows to retrieve an object from its p_uid.'''
|
||||
res = self.portal_catalog(UID=uid)
|
||||
if res:
|
||||
res = res[0].getObject()
|
||||
if appy:
|
||||
res = res.appy()
|
||||
return res
|
||||
res = self.getPhysicalRoot().catalog(UID=uid)
|
||||
if not res: return
|
||||
res = res[0]
|
||||
if brain: return res
|
||||
res = res.getObject()
|
||||
if not appy: return res
|
||||
return res.appy()
|
||||
|
||||
def executeQuery(self, contentType, searchName=None, startNumber=0,
|
||||
def executeQuery(self, className, searchName=None, startNumber=0,
|
||||
search=None, remember=False, brainsOnly=False,
|
||||
maxResults=None, noSecurity=False, sortBy=None,
|
||||
sortOrder='asc', filterKey=None, filterValue=None,
|
||||
refObject=None, refField=None):
|
||||
'''Executes a query on a given p_contentType (or several, separated
|
||||
with commas) in portal_catalog. If p_searchName is specified, it
|
||||
corresponds to:
|
||||
1) a search defined on p_contentType: additional search criteria
|
||||
'''Executes a query on instances of a given p_className (or several,
|
||||
separated with commas) in the catalog. If p_searchName is specified,
|
||||
it corresponds to:
|
||||
1) a search defined on p_className: additional search criteria
|
||||
will be added to the query, or;
|
||||
2) "_advanced": in this case, additional search criteria will also
|
||||
be added to the query, but those criteria come from the session
|
||||
|
@ -224,16 +257,16 @@ class ToolMixin(BaseMixin):
|
|||
If p_refObject and p_refField are given, the query is limited to the
|
||||
objects that are referenced from p_refObject through p_refField.'''
|
||||
# Is there one or several content types ?
|
||||
if contentType.find(',') != -1:
|
||||
portalTypes = contentType.split(',')
|
||||
if className.find(',') != -1:
|
||||
classNames = className.split(',')
|
||||
else:
|
||||
portalTypes = contentType
|
||||
params = {'portal_type': portalTypes}
|
||||
classNames = className
|
||||
params = {'ClassName': classNames}
|
||||
if not brainsOnly: params['batch'] = True
|
||||
# Manage additional criteria from a search when relevant
|
||||
if searchName:
|
||||
# In this case, contentType must contain a single content type.
|
||||
appyClass = self.getAppyClass(contentType)
|
||||
# In this case, className must contain a single content type.
|
||||
appyClass = self.getAppyClass(className)
|
||||
if searchName != '_advanced':
|
||||
search = ClassDescriptor.getSearch(appyClass, searchName)
|
||||
else:
|
||||
|
@ -273,7 +306,7 @@ class ToolMixin(BaseMixin):
|
|||
# Determine what method to call on the portal catalog
|
||||
if noSecurity: catalogMethod = 'unrestrictedSearchResults'
|
||||
else: catalogMethod = 'searchResults'
|
||||
exec 'brains = self.portal_catalog.%s(**params)' % catalogMethod
|
||||
exec 'brains = self.getPath("/catalog").%s(**params)' % catalogMethod
|
||||
if brainsOnly:
|
||||
# Return brains only.
|
||||
if not maxResults: return brains
|
||||
|
@ -290,8 +323,8 @@ class ToolMixin(BaseMixin):
|
|||
# time a page for an element is consulted.
|
||||
if remember:
|
||||
if not searchName:
|
||||
# It is the global search for all objects pf p_contentType
|
||||
searchName = contentType
|
||||
# It is the global search for all objects pf p_className
|
||||
searchName = className
|
||||
uids = {}
|
||||
i = -1
|
||||
for obj in res.objects:
|
||||
|
@ -339,15 +372,6 @@ class ToolMixin(BaseMixin):
|
|||
return '<acronym title="%s">%s</acronym>' % \
|
||||
(text, text[:width] + '...')
|
||||
|
||||
translationMapping = {'portal_path': ''}
|
||||
def translateWithMapping(self, label):
|
||||
'''Translates p_label in the application domain, with a default
|
||||
translation mapping.'''
|
||||
if not self.translationMapping['portal_path']:
|
||||
self.translationMapping['portal_path'] = \
|
||||
self.portal_url.getPortalPath()
|
||||
return self.translate(label, mapping=self.translationMapping)
|
||||
|
||||
def getPublishedObject(self):
|
||||
'''Gets the currently published object, if its meta_class is among
|
||||
application classes.'''
|
||||
|
@ -355,24 +379,24 @@ class ToolMixin(BaseMixin):
|
|||
# If we are querying object, there is no published object (the truth is:
|
||||
# the tool is the currently published object but we don't want to
|
||||
# consider it this way).
|
||||
if not req['ACTUAL_URL'].endswith('/skyn/view'): return
|
||||
if not req['ACTUAL_URL'].endswith('/ui/view'): return
|
||||
obj = self.REQUEST['PUBLISHED']
|
||||
parent = obj.getParentNode()
|
||||
if parent.id == 'skyn': obj = parent.getParentNode()
|
||||
if parent.id == 'ui': obj = parent.getParentNode()
|
||||
if obj.meta_type in self.getProductConfig().attributes: return obj
|
||||
|
||||
def getAppyClass(self, contentType, wrapper=False):
|
||||
'''Gets the Appy Python class that is related to p_contentType.'''
|
||||
# Retrieve first the Archetypes class corresponding to p_ContentType
|
||||
portalType = self.portal_types.get(contentType)
|
||||
if not portalType: return
|
||||
atClassName = portalType.getProperty('content_meta_type')
|
||||
appName = self.getProductConfig().PROJECTNAME
|
||||
exec 'from Products.%s.%s import %s as atClass' % \
|
||||
(appName, atClassName, atClassName)
|
||||
# Get then the Appy Python class
|
||||
if wrapper: return atClass.wrapperClass
|
||||
else: return atClass.wrapperClass.__bases__[-1]
|
||||
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)
|
||||
return C
|
||||
|
||||
def getAppyClass(self, zopeName, wrapper=False):
|
||||
'''Gets the Appy class corresponding to the Zope class named p_name.
|
||||
If p_wrapper is True, it returns the Appy wrapper. Else, it returns
|
||||
the user-defined class.'''
|
||||
zopeClass = self.getZopeClass(zopeName)
|
||||
if wrapper: return zopeClass.wrapperClass
|
||||
else: return zopeClass.wrapperClass.__bases__[-1]
|
||||
|
||||
def getCreateMeans(self, contentTypeOrAppyClass):
|
||||
'''Gets the different ways objects of p_contentTypeOrAppyClass (which
|
||||
|
@ -417,9 +441,9 @@ class ToolMixin(BaseMixin):
|
|||
'''This method is called when the user wants to create objects from
|
||||
external data.'''
|
||||
rq = self.REQUEST
|
||||
appyClass = self.getAppyClass(rq.get('type_name'))
|
||||
appyClass = self.getAppyClass(rq.get('className'))
|
||||
importPaths = rq.get('importPath').split('|')
|
||||
appFolder = self.getAppFolder()
|
||||
appFolder = self.getPath('/data')
|
||||
for importPath in importPaths:
|
||||
if not importPath: continue
|
||||
objectId = os.path.basename(importPath)
|
||||
|
@ -428,9 +452,9 @@ class ToolMixin(BaseMixin):
|
|||
return self.goto(rq['HTTP_REFERER'])
|
||||
|
||||
def isAlreadyImported(self, contentType, importPath):
|
||||
appFolder = self.getAppFolder()
|
||||
data = self.getPath('/data')
|
||||
objectId = os.path.basename(importPath)
|
||||
if hasattr(appFolder.aq_base, objectId):
|
||||
if hasattr(data.aq_base, objectId):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
@ -553,8 +577,8 @@ class ToolMixin(BaseMixin):
|
|||
if refInfo: criteria['_ref'] = refInfo
|
||||
rq.SESSION['searchCriteria'] = criteria
|
||||
# Go to the screen that displays search results
|
||||
backUrl = '%s/skyn/query?type_name=%s&&search=_advanced' % \
|
||||
(self.absolute_url(), rq['type_name'])
|
||||
backUrl = '%s/ui/query?className=%s&&search=_advanced' % \
|
||||
(self.absolute_url(), rq['className'])
|
||||
return self.goto(backUrl)
|
||||
|
||||
def getJavascriptMessages(self):
|
||||
|
@ -614,8 +638,8 @@ 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() + '/skyn'
|
||||
baseParams = 'type_name=%s' % contentType
|
||||
baseUrl = self.absolute_url() + '/ui'
|
||||
baseParams = 'className=%s' % contentType
|
||||
rq = self.REQUEST
|
||||
if rq.get('ref'): baseParams += '&ref=%s' % rq.get('ref')
|
||||
# Manage start number
|
||||
|
@ -663,7 +687,7 @@ class ToolMixin(BaseMixin):
|
|||
res['backText'] = self.translate(label)
|
||||
else:
|
||||
fieldName, pageName = d2.split(':')
|
||||
sourceObj = self.portal_catalog(UID=d1)[0].getObject()
|
||||
sourceObj = self.getObject(d1)
|
||||
label = '%s_%s' % (sourceObj.meta_type, fieldName)
|
||||
res['backText'] = '%s : %s' % (sourceObj.Title(),
|
||||
self.translate(label))
|
||||
|
@ -739,9 +763,9 @@ class ToolMixin(BaseMixin):
|
|||
except KeyError: pass
|
||||
except IndexError: pass
|
||||
if uid:
|
||||
brain = self.portal_catalog(UID=uid)
|
||||
brain = self.getObject(uid, brain=True)
|
||||
if brain:
|
||||
sibling = brain[0].getObject()
|
||||
sibling = brain.getObject()
|
||||
res[urlKey] = sibling.getUrl(nav=newNav % (index + 1),
|
||||
page='main')
|
||||
return res
|
||||
|
@ -777,6 +801,9 @@ class ToolMixin(BaseMixin):
|
|||
'''Gets the translated month name of month numbered p_monthNumber.'''
|
||||
return self.translate(self.monthsIds[int(monthNumber)], domain='plone')
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Authentication-related methods
|
||||
# --------------------------------------------------------------------------
|
||||
def performLogin(self):
|
||||
'''Logs the user in.'''
|
||||
rq = self.REQUEST
|
||||
|
@ -788,11 +815,14 @@ class ToolMixin(BaseMixin):
|
|||
msg = self.translate(u'You must enable cookies before you can ' \
|
||||
'log in.', domain='plone')
|
||||
return self.goto(urlBack, msg.encode('utf-8'))
|
||||
|
||||
# Perform the Zope-level authentication
|
||||
self.acl_users.credentials_cookie_auth.login()
|
||||
login = rq['login_name']
|
||||
if self.portal_membership.isAnonymousUser():
|
||||
login = rq.get('__ac_name', '')
|
||||
password = rq.get('__ac_password', '')
|
||||
cookieValue = base64.encodestring('%s:%s' % (login, password)).rstrip()
|
||||
cookieValue = urllib.quote(cookieValue)
|
||||
rq.RESPONSE.setCookie('__ac', cookieValue, path='/')
|
||||
user = self.acl_users.validate(rq)
|
||||
if self.userIsAnon():
|
||||
rq.RESPONSE.expireCookie('__ac', path='/')
|
||||
msg = self.translate(u'Login failed', domain='plone')
|
||||
logMsg = 'Authentication failed (tried with login "%s")' % login
|
||||
|
@ -803,7 +833,7 @@ class ToolMixin(BaseMixin):
|
|||
msg = msg.encode('utf-8')
|
||||
self.log(logMsg)
|
||||
# Bring Managers to the config, leave others on the main page.
|
||||
user = self.portal_membership.getAuthenticatedMember()
|
||||
user = self.getUser()
|
||||
if user.has_role('Manager'):
|
||||
# Bring the user to the configuration
|
||||
url = self.goto(self.absolute_url(), msg)
|
||||
|
@ -814,28 +844,72 @@ class ToolMixin(BaseMixin):
|
|||
def performLogout(self):
|
||||
'''Logs out the current user when he clicks on "disconnect".'''
|
||||
rq = self.REQUEST
|
||||
userId = self.portal_membership.getAuthenticatedMember().getId()
|
||||
userId = self.getUser().getId()
|
||||
# Perform the logout in acl_users
|
||||
try:
|
||||
self.acl_users.logout(rq)
|
||||
except:
|
||||
pass
|
||||
skinvar = self.portal_skins.getRequestVarname()
|
||||
path = '/' + self.absolute_url(1)
|
||||
if rq.has_key(skinvar) and not self.portal_skins.getCookiePersistence():
|
||||
rq.RESPONSE.expireCookie(skinvar, path=path)
|
||||
# Invalidate existing sessions, but only if they exist.
|
||||
rq.RESPONSE.expireCookie('__ac', path='/')
|
||||
# Invalidate existing sessions.
|
||||
sdm = self.session_data_manager
|
||||
session = sdm.getSessionData(create=0)
|
||||
if session is not None:
|
||||
session.invalidate()
|
||||
from Products.CMFPlone import transaction_note
|
||||
transaction_note('Logged out')
|
||||
self.log('User "%s" has been logged out.' % userId)
|
||||
# Remove user from variable "loggedUsers"
|
||||
from appy.gen.plone25.installer import loggedUsers
|
||||
if loggedUsers.has_key(userId): del loggedUsers[userId]
|
||||
return self.goto(self.getParentNode().absolute_url())
|
||||
return self.goto(self.getApp().absolute_url())
|
||||
|
||||
def validate(self, request, auth='', roles=_noroles):
|
||||
'''This method performs authentication and authorization. It is used as
|
||||
a replacement for Zope's AccessControl.User.BasicUserFolder.validate,
|
||||
that allows to manage cookie-based authentication.'''
|
||||
v = request['PUBLISHED'] # The published object
|
||||
# v is the object (value) we're validating access to
|
||||
# n is the name used to access the object
|
||||
# a is the object the object was accessed through
|
||||
# c is the physical container of the object
|
||||
a, c, n, v = self._getobcontext(v, request)
|
||||
# Try to get user name and password from basic authentication
|
||||
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))
|
||||
login, password = cookieValue.split(':')
|
||||
# Try to authenticate this user
|
||||
user = self.authenticate(login, password, request)
|
||||
emergency = self._emergency_user
|
||||
if emergency and user is emergency:
|
||||
# It is the emergency user.
|
||||
return emergency.__of__(self)
|
||||
elif user is None:
|
||||
# Login and/or password incorrect. Try to authorize and return the
|
||||
# anonymous user.
|
||||
if self.authorize(self._nobody, a, c, n, v, roles):
|
||||
return self._nobody.__of__(self)
|
||||
else:
|
||||
return # Anonymous can't acces this object
|
||||
else:
|
||||
# We found a user and his password was correct. Try to authorize him
|
||||
# against the published object.
|
||||
if self.authorize(user, a, c, n, v, roles):
|
||||
return user.__of__(self)
|
||||
# That didn't work. Try to authorize the anonymous user.
|
||||
elif self.authorize(self._nobody, a, c, n, v, roles):
|
||||
return self._nobody.__of__(self)
|
||||
else:
|
||||
return
|
||||
|
||||
# Patch BasicUserFolder with our version of m_validate above.
|
||||
from AccessControl.User import BasicUserFolder
|
||||
BasicUserFolder.validate = validate
|
||||
|
||||
def tempFile(self):
|
||||
'''A temp file has been created in a temp folder. This method returns
|
||||
|
@ -864,11 +938,16 @@ class ToolMixin(BaseMixin):
|
|||
def getUserLine(self, user):
|
||||
'''Returns a one-line user info as shown on every page.'''
|
||||
res = [user.getId()]
|
||||
name = user.getProperty('fullname')
|
||||
if name: res.insert(0, name)
|
||||
rolesToShow = [r for r in user.getRoles() \
|
||||
if r not in ('Authenticated', 'Member')]
|
||||
if rolesToShow:
|
||||
res.append(', '.join([self.translate(r) for r in rolesToShow]))
|
||||
return ' | '.join(res)
|
||||
|
||||
def generateUid(self, className):
|
||||
'''Generates a UID for an instance of p_className.'''
|
||||
name = className.replace('_', '')
|
||||
randomNumber = str(random.random()).split('.')[1]
|
||||
timestamp = ('%f' % time.time()).replace('.', '')
|
||||
return '%s%s%s' % (name, timestamp, randomNumber)
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -15,8 +15,8 @@ from appy.gen.plone25.descriptors import ClassDescriptor
|
|||
|
||||
# ------------------------------------------------------------------------------
|
||||
class BaseMixin:
|
||||
'''Every Archetype class generated by appy.gen inherits from this class or
|
||||
a subclass of it.'''
|
||||
'''Every Zope class generated by appy.gen inherits from this class or a
|
||||
subclass of it.'''
|
||||
_appy_meta_type = 'Class'
|
||||
|
||||
def get_o(self):
|
||||
|
@ -31,31 +31,36 @@ class BaseMixin:
|
|||
initiator=None, initiatorField=None):
|
||||
'''This method creates (if p_created is True) or updates an object.
|
||||
p_values are manipulated versions of those from the HTTP request.
|
||||
In the case of an object creation (p_created is True), p_self is a
|
||||
temporary object created in the request by portal_factory, and this
|
||||
method creates the corresponding final object. In the case of an
|
||||
update, this method simply updates fields of p_self.'''
|
||||
rq = self.REQUEST
|
||||
In the case of an object creation from the web (p_created is True
|
||||
and a REQUEST object is present), p_self is a temporary object
|
||||
created in /temp_folder, and this method moves it at its "final"
|
||||
place. In the case of an update, this method simply updates fields
|
||||
of p_self.'''
|
||||
rq = getattr(self, 'REQUEST', None)
|
||||
obj = self
|
||||
if created:
|
||||
# portal_factory creates the final object from the temp object.
|
||||
obj = self.portal_factory.doCreate(self, self.id)
|
||||
if created and rq:
|
||||
# Create the final object and put it at the right place.
|
||||
tool = self.getTool()
|
||||
id = tool.generateUid(obj.portal_type)
|
||||
if not initiator:
|
||||
folder = tool.getPath('/data')
|
||||
else:
|
||||
if initiator.isPrincipiaFolderish:
|
||||
folder = initiator
|
||||
else:
|
||||
folder = initiator.getParentNode()
|
||||
obj = createObject(folder, id, obj.portal_type, tool.getAppName())
|
||||
previousData = None
|
||||
if not created: previousData = self.rememberPreviousData()
|
||||
# Perform the change on the object, unless self is a tool being created.
|
||||
if (obj._appy_meta_type == 'Tool') and created:
|
||||
# We do not process form data (=real update on the object) if the
|
||||
# tool itself is being created.
|
||||
pass
|
||||
else:
|
||||
if not created: previousData = obj.rememberPreviousData()
|
||||
# Perform the change on the object
|
||||
if rq:
|
||||
# Store in the database the new value coming from the form
|
||||
for appyType in self.getAppyTypes('edit', rq.get('page')):
|
||||
value = getattr(values, appyType.name, None)
|
||||
appyType.store(obj, value)
|
||||
if created: obj.unmarkCreationFlag()
|
||||
if previousData:
|
||||
# Keep in history potential changes on historized fields
|
||||
self.historizeData(previousData)
|
||||
obj.historizeData(previousData)
|
||||
|
||||
# Manage potential link with an initiator object
|
||||
if created and initiator: initiator.appy().link(initiatorField, obj)
|
||||
|
@ -69,7 +74,7 @@ class BaseMixin:
|
|||
appyObject = obj.appy()
|
||||
if hasattr(appyObject, 'onEdit'):
|
||||
msg = appyObject.onEdit(created)
|
||||
obj.reindexObject()
|
||||
obj.reindex()
|
||||
return obj, msg
|
||||
|
||||
def delete(self):
|
||||
|
@ -99,9 +104,11 @@ class BaseMixin:
|
|||
|
||||
def onCreate(self):
|
||||
'''This method is called when a user wants to create a root object in
|
||||
the application folder or an object through a reference field.'''
|
||||
the "data" folder or an object through a reference field. A temporary
|
||||
object is created in /temp_folder and the edit page to it is
|
||||
returned.'''
|
||||
rq = self.REQUEST
|
||||
typeName = rq.get('type_name')
|
||||
className = rq.get('className')
|
||||
# Create the params to add to the URL we will redirect the user to
|
||||
# create the object.
|
||||
urlParams = {'mode':'edit', 'page':'main', 'nav':''}
|
||||
|
@ -112,15 +119,12 @@ class BaseMixin:
|
|||
splitted = rq.get('nav').split('.')
|
||||
splitted[-1] = splitted[-2] = str(int(splitted[-1])+1)
|
||||
urlParams['nav'] = '.'.join(splitted)
|
||||
# Determine base URL
|
||||
baseUrl = self.absolute_url()
|
||||
if (self._appy_meta_type == 'Tool') and not urlParams['nav']:
|
||||
# This is the creation of a root object in the app folder
|
||||
baseUrl = self.getAppFolder().absolute_url()
|
||||
objId = self.generateUniqueId(typeName)
|
||||
editUrl = '%s/portal_factory/%s/%s/skyn/edit' % \
|
||||
(baseUrl, typeName, objId)
|
||||
return self.goto(self.getUrl(editUrl, **urlParams))
|
||||
# Create a temp object in /temp_folder
|
||||
tool = self.getTool()
|
||||
id = tool.generateUid(className)
|
||||
appName = tool.getAppName()
|
||||
obj = createObject(tool.getPath('/temp_folder'), id, className, appName)
|
||||
return self.goto(obj.getUrl(**urlParams))
|
||||
|
||||
def onCreateWithoutForm(self):
|
||||
'''This method is called when a user wants to create a object from a
|
||||
|
@ -175,10 +179,10 @@ class BaseMixin:
|
|||
|
||||
def onUpdate(self):
|
||||
'''This method is executed when a user wants to update an object.
|
||||
The object may be a temporary object created by portal_factory in
|
||||
the request. In this case, the update consists in the creation of
|
||||
the "final" object in the database. If the object is not a temporary
|
||||
one, this method updates its fields in the database.'''
|
||||
The object may be a temporary object created in /temp_folder.
|
||||
In this case, the update consists in moving it to its "final" place.
|
||||
If the object is not a temporary one, this method updates its
|
||||
fields in the database.'''
|
||||
rq = self.REQUEST
|
||||
tool = self.getTool()
|
||||
errorMessage = self.translate(
|
||||
|
@ -244,7 +248,7 @@ class BaseMixin:
|
|||
# object like a one-shot form and has already been deleted in method
|
||||
# onEdit), redirect to the main site page.
|
||||
if not getattr(obj.getParentNode().aq_base, obj.id, None):
|
||||
obj.unindexObject()
|
||||
obj.unindex()
|
||||
return self.goto(tool.getSiteUrl(), msg)
|
||||
# If the user can't access the object anymore, redirect him to the
|
||||
# main site page.
|
||||
|
@ -295,16 +299,42 @@ class BaseMixin:
|
|||
return self.goto(obj.getUrl())
|
||||
return obj.gotoEdit()
|
||||
|
||||
def reindex(self, indexes=None, unindex=False):
|
||||
'''Reindexes this object the catalog. If names of indexes are specified
|
||||
in p_indexes, recataloging is limited to those indexes. If p_unindex
|
||||
is True, instead of cataloguing the object, it uncatalogs it.'''
|
||||
url = self.absolute_url_path()
|
||||
catalog = self.getPhysicalRoot().catalog
|
||||
if unindex:
|
||||
method = catalog.uncatalog_object
|
||||
else:
|
||||
method = catalog.catalog_object
|
||||
if indexes:
|
||||
return method(self, url)
|
||||
else:
|
||||
return method(self, url, idxs=indexes)
|
||||
|
||||
def unindex(self, indexes=None):
|
||||
'''Undatalog this object.'''
|
||||
url = self.absolute_url_path()
|
||||
catalog = self.getPhysicalRoot().catalog
|
||||
if indexes:
|
||||
return catalog.catalog_object(self, url)
|
||||
else:
|
||||
return catalog.catalog_object(self, url, idxs=indexes)
|
||||
|
||||
def say(self, msg, type='info'):
|
||||
'''Prints a p_msg in the user interface. p_logLevel may be "info",
|
||||
"warning" or "error".'''
|
||||
mType = type
|
||||
rq = self.REQUEST
|
||||
if not hasattr(rq, 'messages'):
|
||||
messages = rq.messages = []
|
||||
else:
|
||||
messages = rq.messages
|
||||
if mType == 'warning': mType = 'warn'
|
||||
elif mType == 'error': mType = 'stop'
|
||||
try:
|
||||
self.plone_utils.addPortalMessage(msg, type=mType)
|
||||
except UnicodeDecodeError:
|
||||
self.plone_utils.addPortalMessage(msg.decode('utf-8'), type=mType)
|
||||
messages.append( (mType, msg) )
|
||||
|
||||
def log(self, msg, type='info'):
|
||||
'''Logs a p_msg in the log file. p_logLevel may be "info", "warning"
|
||||
|
@ -315,29 +345,15 @@ class BaseMixin:
|
|||
else: logMethod = logger.info
|
||||
logMethod(msg)
|
||||
|
||||
def getState(self, name=True, initial=False):
|
||||
'''Returns information about the current object state. If p_name is
|
||||
True, the returned info is the state name. Else, it is the State
|
||||
instance. If p_initial is True, instead of returning info about the
|
||||
current state, it returns info about the workflow initial state.'''
|
||||
wf = self.getWorkflow()
|
||||
if initial or not hasattr(self.aq_base, 'workflow_history'):
|
||||
# No workflow information is available (yet) on this object, or
|
||||
# initial state is asked. In both cases, return info about this
|
||||
# initial state.
|
||||
res = 'active'
|
||||
for elem in dir(wf):
|
||||
attr = getattr(wf, elem)
|
||||
if (attr.__class__.__name__ == 'State') and attr.initial:
|
||||
res = elem
|
||||
break
|
||||
def do(self):
|
||||
'''Performs some action from the user interface.'''
|
||||
rq = self.REQUEST
|
||||
action = rq['action']
|
||||
if rq.get('objectUid', None):
|
||||
obj = self.getTool().getObject(rq['objectUid'])
|
||||
else:
|
||||
# Return info about the current object state
|
||||
key = self.workflow_history.keys()[0]
|
||||
res = self.workflow_history[key][-1]['review_state']
|
||||
# Return state name or state definition?
|
||||
if name: return res
|
||||
else: return getattr(wf, res)
|
||||
obj = self
|
||||
return obj.getMethod('on'+action)()
|
||||
|
||||
def rememberPreviousData(self):
|
||||
'''This method is called before updating an object and remembers, for
|
||||
|
@ -351,12 +367,12 @@ class BaseMixin:
|
|||
|
||||
def addHistoryEvent(self, action, **kw):
|
||||
'''Adds an event in the object history.'''
|
||||
userId = self.portal_membership.getAuthenticatedMember().getId()
|
||||
userId = self.getUser().getId()
|
||||
from DateTime import DateTime
|
||||
event = {'action': action, 'actor': userId, 'time': DateTime(),
|
||||
'comments': ''}
|
||||
event.update(kw)
|
||||
if 'review_state' not in event: event['review_state']=self.getState()
|
||||
if 'review_state' not in event: event['review_state'] = self.State()
|
||||
# Add the event to the history
|
||||
histKey = self.workflow_history.keys()[0]
|
||||
self.workflow_history[histKey] += (event,)
|
||||
|
@ -423,7 +439,7 @@ class BaseMixin:
|
|||
for field in self.getAppyTypes('edit', page):
|
||||
if (field.type == 'String') and (field.format == 3):
|
||||
self.REQUEST.set(field.name, '')
|
||||
return self.skyn.edit(self)
|
||||
return self.ui.edit(self)
|
||||
|
||||
def showField(self, name, layoutType='view'):
|
||||
'''Must I show field named p_name on this p_layoutType ?'''
|
||||
|
@ -657,7 +673,7 @@ class BaseMixin:
|
|||
'''Returns information about the states that are related to p_phase.
|
||||
If p_currentOnly is True, we return the current state, even if not
|
||||
related to p_phase.'''
|
||||
currentState = self.getState()
|
||||
currentState = self.State()
|
||||
if currentOnly:
|
||||
return [StateDescr(currentState, 'current').get()]
|
||||
res = []
|
||||
|
@ -690,7 +706,7 @@ class BaseMixin:
|
|||
'''
|
||||
res = []
|
||||
wf = self.getWorkflow()
|
||||
currentState = self.getState(name=False)
|
||||
currentState = self.State(name=False)
|
||||
# Loop on every transition
|
||||
for name in dir(wf):
|
||||
transition = getattr(wf, name)
|
||||
|
@ -855,7 +871,7 @@ class BaseMixin:
|
|||
related data on the object.'''
|
||||
wf = self.getWorkflow()
|
||||
# Get the initial workflow state
|
||||
initialState = self.getState(name=False)
|
||||
initialState = self.State(name=False)
|
||||
# Create a Transition instance representing the initial transition.
|
||||
initialTransition = Transition((initialState, initialState))
|
||||
initialTransition.trigger('_init_', self, wf, '')
|
||||
|
@ -876,7 +892,7 @@ class BaseMixin:
|
|||
'''Gets the i18n label for p_stateName, or for the current object state
|
||||
if p_stateName is not given. Note that if p_stateName is given, it
|
||||
can also represent the name of a transition.'''
|
||||
stateName = stateName or self.getState()
|
||||
stateName = stateName or self.State()
|
||||
return '%s_%s' % (self.getWorkflow(name=True), stateName)
|
||||
|
||||
def refreshSecurity(self):
|
||||
|
@ -885,15 +901,15 @@ class BaseMixin:
|
|||
wf = self.getWorkflow()
|
||||
try:
|
||||
# Get the state definition of the object's current state.
|
||||
state = getattr(wf, self.getState())
|
||||
state = getattr(wf, self.State())
|
||||
except AttributeError:
|
||||
# The workflow information for this object does not correspond to
|
||||
# its current workflow attribution. Add a new fake event
|
||||
# representing passage of this object to the initial state of his
|
||||
# currently attributed workflow.
|
||||
stateName = self.getState(name=True, initial=True)
|
||||
stateName = self.State(name=True, initial=True)
|
||||
self.addHistoryEvent(None, review_state=stateName)
|
||||
state = self.getState(name=False, initial=True)
|
||||
state = self.State(name=False, initial=True)
|
||||
self.log('Wrong workflow info for a "%s"; is not in state "%s".' % \
|
||||
(self.meta_type, stateName))
|
||||
# Update permission attributes on the object if required
|
||||
|
@ -939,9 +955,11 @@ class BaseMixin:
|
|||
'''Executes action with p_fieldName on this object.'''
|
||||
appyType = self.getAppyType(actionName)
|
||||
actionRes = appyType(self.appy())
|
||||
if self.getParentNode().get(self.id):
|
||||
parent = self.getParentNode()
|
||||
parentAq = getattr(parent, 'aq_base', parent)
|
||||
if not hasattr(parentAq, self.id):
|
||||
# Else, it means that the action has led to self's deletion.
|
||||
self.reindexObject()
|
||||
self.reindex()
|
||||
return appyType.result, actionRes
|
||||
|
||||
def onExecuteAppyAction(self):
|
||||
|
@ -982,8 +1000,8 @@ class BaseMixin:
|
|||
# the user.
|
||||
return self.goto(msg)
|
||||
|
||||
def do(self, transitionName, comment='', doAction=True, doNotify=True,
|
||||
doHistory=True, doSay=True):
|
||||
def trigger(self, transitionName, comment='', doAction=True, doNotify=True,
|
||||
doHistory=True, doSay=True):
|
||||
'''Triggers transition named p_transitionName.'''
|
||||
# Check that this transition exists.
|
||||
wf = self.getWorkflow()
|
||||
|
@ -998,12 +1016,12 @@ class BaseMixin:
|
|||
transition.trigger(transitionName, self, wf, comment, doAction=doAction,
|
||||
doNotify=doNotify, doHistory=doHistory, doSay=doSay)
|
||||
|
||||
def onDo(self):
|
||||
def onTrigger(self):
|
||||
'''This method is called whenever a user wants to trigger a workflow
|
||||
transition on an object.'''
|
||||
rq = self.REQUEST
|
||||
self.do(rq['workflow_action'], comment=rq.get('comment', ''))
|
||||
self.reindexObject()
|
||||
self.trigger(rq['workflow_action'], comment=rq.get('comment', ''))
|
||||
self.reindex()
|
||||
return self.goto(self.getUrl(rq['HTTP_REFERER']))
|
||||
|
||||
def fieldValueSelected(self, fieldName, vocabValue, dbValue):
|
||||
|
@ -1079,7 +1097,8 @@ class BaseMixin:
|
|||
'''Returns a wrapper object allowing to manipulate p_self the Appy
|
||||
way.'''
|
||||
# Create the dict for storing Appy wrapper on the REQUEST if needed.
|
||||
rq = self.REQUEST
|
||||
rq = getattr(self, 'REQUEST', None)
|
||||
if not rq: rq = Object()
|
||||
if not hasattr(rq, 'appyWrappers'): rq.appyWrappers = {}
|
||||
# Return the Appy wrapper from rq.appyWrappers if already there
|
||||
uid = self.UID()
|
||||
|
@ -1088,7 +1107,69 @@ class BaseMixin:
|
|||
wrapper = self.wrapperClass(self)
|
||||
rq.appyWrappers[uid] = wrapper
|
||||
return wrapper
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Standard methods for computing values of standard Appy indexes
|
||||
# --------------------------------------------------------------------------
|
||||
def UID(self):
|
||||
'''Returns the unique identifier for this object.'''
|
||||
return self._at_uid
|
||||
|
||||
def Title(self):
|
||||
'''Returns the title for this object.'''
|
||||
title = self.getAppyType('title')
|
||||
if title: return title.getValue(self)
|
||||
return self.id
|
||||
|
||||
def SortableTitle(self):
|
||||
'''Returns the title as must be stored in index "SortableTitle".'''
|
||||
return self.Title()
|
||||
|
||||
def SearchableText(self):
|
||||
'''This method concatenates the content of every field with
|
||||
searchable=True for indexing purposes.'''
|
||||
res = []
|
||||
for field in self.getAllAppyTypes():
|
||||
if not field.searchable: continue
|
||||
res.append(field.getIndexValue(self, forSearch=True))
|
||||
return res
|
||||
|
||||
def Creator(self):
|
||||
'''Who create this object?'''
|
||||
return self.creator
|
||||
|
||||
def Created(self):
|
||||
'''When was this object created ?'''
|
||||
return self.created
|
||||
|
||||
def State(self, name=True, initial=False):
|
||||
'''Returns information about the current object state. If p_name is
|
||||
True, the returned info is the state name. Else, it is the State
|
||||
instance. If p_initial is True, instead of returning info about the
|
||||
current state, it returns info about the workflow initial state.'''
|
||||
wf = self.getWorkflow()
|
||||
if initial or not hasattr(self.aq_base, 'workflow_history'):
|
||||
# No workflow information is available (yet) on this object, or
|
||||
# initial state is asked. In both cases, return info about this
|
||||
# initial state.
|
||||
res = 'active'
|
||||
for elem in dir(wf):
|
||||
attr = getattr(wf, elem)
|
||||
if (attr.__class__.__name__ == 'State') and attr.initial:
|
||||
res = elem
|
||||
break
|
||||
else:
|
||||
# Return info about the current object state
|
||||
key = self.workflow_history.keys()[0]
|
||||
res = self.workflow_history[key][-1]['review_state']
|
||||
# Return state name or state definition?
|
||||
if name: return res
|
||||
else: return getattr(wf, res)
|
||||
|
||||
def ClassName(self):
|
||||
'''Returns the name of the (Zope) class for self.'''
|
||||
return self.portal_type
|
||||
|
||||
def _appy_showState(self, workflow, stateShow):
|
||||
'''Must I show a state whose "show value" is p_stateShow?'''
|
||||
if callable(stateShow):
|
||||
|
@ -1129,11 +1210,6 @@ class BaseMixin:
|
|||
# Update the permissions
|
||||
for permission, creators in allCreators.iteritems():
|
||||
updateRolesForPermission(permission, tuple(creators), folder)
|
||||
# Beyond content-type-specific "add" permissions, creators must also
|
||||
# have the main permission "Add portal content".
|
||||
permission = 'Add portal content'
|
||||
for creators in allCreators.itervalues():
|
||||
updateRolesForPermission(permission, tuple(creators), folder)
|
||||
|
||||
def _appy_getPortalType(self, request):
|
||||
'''Guess the portal_type of p_self from info about p_self and
|
||||
|
@ -1162,7 +1238,7 @@ class BaseMixin:
|
|||
param will not be included in the URL at all).'''
|
||||
# Define the URL suffix
|
||||
suffix = ''
|
||||
if mode != 'raw': suffix = '/skyn/%s' % mode
|
||||
if mode != 'raw': suffix = '/ui/%s' % mode
|
||||
# Define base URL if omitted
|
||||
if not base:
|
||||
base = self.absolute_url() + suffix
|
||||
|
@ -1171,7 +1247,7 @@ class BaseMixin:
|
|||
if '?' in base: base = base[:base.index('?')]
|
||||
base = base.strip('/')
|
||||
for mode in ('view', 'edit'):
|
||||
suffix = 'skyn/%s' % mode
|
||||
suffix = 'ui/%s' % mode
|
||||
if base.endswith(suffix):
|
||||
base = base[:-len(suffix)].strip('/')
|
||||
break
|
||||
|
@ -1195,6 +1271,19 @@ 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 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.'''
|
||||
# Try first the "LANGUAGE" key from the request
|
||||
|
@ -1291,12 +1380,11 @@ class BaseMixin:
|
|||
layout = defaultPageLayouts[layoutType]
|
||||
return layout.get()
|
||||
|
||||
def getPageTemplate(self, skyn, templateName):
|
||||
'''Returns, in the skyn folder, the page template corresponding to
|
||||
def getPageTemplate(self, ui, templateName):
|
||||
'''Returns, in the ui folder, the page template corresponding to
|
||||
p_templateName.'''
|
||||
res = skyn
|
||||
for name in templateName.split('/'):
|
||||
res = res.get(name)
|
||||
res = ui
|
||||
for name in templateName.split('/'): res = getattr(res, name)
|
||||
return res
|
||||
|
||||
def download(self):
|
||||
|
@ -1316,15 +1404,6 @@ class BaseMixin:
|
|||
response.setHeader('Expires', 'Thu, 11 Dec 1975 12:05:05 GMT')
|
||||
return theFile.index_html(self.REQUEST, self.REQUEST.RESPONSE)
|
||||
|
||||
def SearchableText(self):
|
||||
'''This method concatenates the content of every field with
|
||||
searchable=True for indexing purposes.'''
|
||||
res = []
|
||||
for field in self.getAllAppyTypes():
|
||||
if not field.searchable: continue
|
||||
res.append(field.getIndexValue(self, forSearch=True))
|
||||
return res
|
||||
|
||||
def allows(self, permission):
|
||||
'''Has the logged user p_permission on p_self ?'''
|
||||
# Get first the roles that have this permission on p_self.
|
||||
|
@ -1332,8 +1411,10 @@ class BaseMixin:
|
|||
if not hasattr(self.aq_base, zopeAttr): return
|
||||
allowedRoles = getattr(self.aq_base, zopeAttr)
|
||||
# Has the user one of those roles?
|
||||
user = self.portal_membership.getAuthenticatedMember()
|
||||
ids = [user.getId()] + user.getGroups()
|
||||
user = self.getUser()
|
||||
# XXX no groups at present
|
||||
#ids = [user.getId()] + user.getGroups()
|
||||
ids = [user.getId()]
|
||||
userGlobalRoles = user.getRoles()
|
||||
for role in allowedRoles:
|
||||
# Has the user this role ? Check in the local roles first.
|
||||
|
@ -1347,4 +1428,11 @@ class BaseMixin:
|
|||
field p_name.'''
|
||||
return 'tinyMCE.init({\nmode : "textareas",\ntheme : "simple",\n' \
|
||||
'elements : "%s",\neditor_selector : "rich_%s"\n});'% (name,name)
|
||||
|
||||
def isTemporary(self):
|
||||
'''Is this object temporary ?'''
|
||||
parent = self.getParentNode()
|
||||
if not parent: # Is propably being created through code
|
||||
return False
|
||||
return parent.getId() == 'temp_folder'
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue