appypod-rattail/gen/utils.py

232 lines
10 KiB
Python

# ------------------------------------------------------------------------------
import re, os, os.path, base64, urllib
from appy.shared import utils as sutils
# Function for creating a Zope object ------------------------------------------
def createObject(folder, id, className, appName, wf=True, noSecurity=False):
'''Creates, in p_folder, object with some p_id. Object will be an instance
of p_className from application p_appName. In a very special case (the
creation of the config object), computing workflow-related info is not
possible at this time. This is why this function can be called with
p_wf=False.'''
exec 'from Products.%s.%s import %s as ZopeClass' % \
(appName, className, className)
# Get the tool. It may not be present yet, maybe are we creating it now.
if folder.meta_type.endswith('Folder'):
# p_folder is a standard Zope (temp) folder.
tool = getattr(folder, 'config', None)
else:
# p_folder is an instance of a gen-class.
tool = folder.getTool()
# Get the currently logged user
user = None
if tool:
user = tool.getUser()
# Checks whether the user an create this object if security is enabled.
if not noSecurity:
klass = ZopeClass.wrapperClass.__bases__[-1]
if not tool.userMayCreate(klass):
from AccessControl import Unauthorized
raise Unauthorized("User can't create instances of %s" % \
klass.__name__)
# Create the object
obj = ZopeClass(id)
folder._objects = folder._objects + ({'id':id, 'meta_type':className},)
folder._setOb(id, obj)
obj = folder._getOb(id) # Important. Else, obj is not really in the folder.
obj.portal_type = className
obj.id = id
obj._at_uid = id
# If no user object is there, we are at startup, before default User
# instances are created.
userId = user and user.login or 'system'
obj.creator = userId
from DateTime import DateTime
obj.created = DateTime()
obj.modified = obj.created
from persistent.mapping import PersistentMapping
obj.__ac_local_roles__ = PersistentMapping({ userId: ['Owner'] })
if wf: obj.initializeWorkflow()
return obj
# ------------------------------------------------------------------------------
upperLetter = re.compile('[A-Z]')
def produceNiceMessage(msg):
'''Transforms p_msg into a nice msg.'''
res = ''
if msg:
res = msg[0].upper()
for c in msg[1:]:
if c == '_':
res += ' '
elif upperLetter.match(c):
res += ' ' + c.lower()
else:
res += c
return res
# ------------------------------------------------------------------------------
class SomeObjects:
'''Represents a bunch of objects retrieved from a reference or a query in
the catalog.'''
def __init__(self, objects=None, batchSize=None, startNumber=0,
noSecurity=False):
self.objects = objects or [] # The objects
self.totalNumber = len(self.objects) # self.objects may only represent a
# part of all available objects.
self.batchSize = batchSize or self.totalNumber # The max length of
# self.objects.
self.startNumber = startNumber # The index of first object in
# self.objects in the whole list.
self.noSecurity = noSecurity
def brainsToObjects(self):
'''self.objects has been populated from brains from the catalog,
not from True objects. This method turns them (or some of them
depending on batchSize and startNumber) into real objects.
If self.noSecurity is True, it gets the objects even if the logged
user does not have the right to get them.'''
start = self.startNumber
brains = self.objects[start:start + self.batchSize]
if self.noSecurity: getMethod = '_unrestrictedGetObject'
else: getMethod = 'getObject'
self.objects = [getattr(b, getMethod)() for b in brains]
# ------------------------------------------------------------------------------
class Keywords:
'''This class allows to handle keywords that a user enters and that will be
used as basis for performing requests in a TextIndex/XhtmlIndex.'''
toRemove = '?-+*()'
def __init__(self, keywords, operator='AND'):
# Clean the p_keywords that the user has entered.
words = sutils.normalizeText(keywords)
if words == '*': words = ''
for c in self.toRemove: words = words.replace(c, ' ')
self.keywords = words.split()
# Store the operator to apply to the keywords (AND or OR)
self.operator = operator
def merge(self, other, append=False):
'''Merges our keywords with those from p_other. If p_append is True,
p_other keywords are appended at the end; else, keywords are appended
at the begin.'''
for word in other.keywords:
if word not in self.keywords:
if append:
self.keywords.append(word)
else:
self.keywords.insert(0, word)
def get(self):
'''Returns the keywords as needed by the TextIndex.'''
if self.keywords:
op = ' %s ' % self.operator
return op.join(self.keywords)+'*'
return ''
# ------------------------------------------------------------------------------
def getClassName(klass, appName=None):
'''Generates, from appy-class p_klass, the name of the corresponding
Zope class. For some classes, name p_appName is required: it is
part of the class name.'''
moduleName = klass.__module__
if (moduleName == 'appy.gen.model') or moduleName.endswith('.wrappers'):
# This is a model (generation time or run time)
res = appName + klass.__name__
elif klass.__bases__ and (klass.__bases__[-1].__module__ == 'appy.gen'):
# This is a customized class (inherits from appy.gen.Tool, User,...)
res = appName + klass.__bases__[-1].__name__
else: # This is a standard class
res = klass.__module__.replace('.', '_') + '_' + klass.__name__
return res
# ------------------------------------------------------------------------------
def callMethod(obj, method, klass=None, cache=True):
'''This function is used to call a p_method on some Appy p_obj. m_method
can be an instance method on p_obj; it can also be a static method. In
this latter case, p_obj is the tool and the static method, defined in
p_klass, will be called with the tool as unique arg.
A method cache is implemented on the request object (available at
p_obj.request). So while handling a single request from the ui, every
method is called only once. Some method calls must not be cached (ie,
values of Computed fields). In this case, p_cache will be False.'''
rq = obj.request
# Create the method cache if it does not exist on the request
if not hasattr(rq, 'methodCache'): rq.methodCache = {}
# If m_method is a static method or an instance method, unwrap the true
# Python function object behind it.
methodType = method.__class__.__name__
if methodType == 'staticmethod':
method = method.__get__(klass)
elif methodType == 'instancemethod':
method = method.im_func
# Call the method if cache is not needed.
if not cache: return method(obj)
# If first arg of method is named "tool" instead of the traditional "self",
# we cheat and will call the method with the tool as first arg. This will
# allow to consider this method as if it was a static method on the tool.
# Every method call, even on different instances, will be cached in a unique
# key.
cheat = False
if not klass and (method.func_code.co_varnames[0] == 'tool'):
prefix = obj.klass.__name__
obj = obj.tool
cheat = True
# Build the key of this method call in the cache.
# First part of the key: the p_obj's uid (if p_method is an instance method)
# or p_className (if p_method is a static method).
if not cheat:
if klass:
prefix = klass.__name__
else:
prefix = obj.uid
# Second part of the key: p_method name
key = '%s:%s' % (prefix, method.func_name)
# Return the cached value if present in the method cache.
if key in rq.methodCache:
return rq.methodCache[key]
# No cached value: call the method, cache the result and return it
res = method(obj)
rq.methodCache[key] = res
return res
# Functions for manipulating the authentication cookie -------------------------
def readCookie(request):
'''Returns the tuple (login, password) read from the authentication
cookie received in p_request. If no user is logged, its returns
(None, None).'''
cookie = request.get('_appy_', None)
if not cookie: return None, None
cookieValue = base64.decodestring(urllib.unquote(cookie))
if ':' in cookieValue: return cookieValue.split(':')
return None, None
def writeCookie(login, password, request):
'''Encode p_login and p_password into the cookie set in the p_request.'''
cookieValue = base64.encodestring('%s:%s' % (login, password)).rstrip()
cookieValue = urllib.quote(cookieValue)
request.RESPONSE.setCookie('_appy_', cookieValue, path='/')
# ------------------------------------------------------------------------------
def initMasterValue(v):
'''Standardizes p_v as a list of strings, excepted if p_v is a method.'''
if callable(v): return v
if not isinstance(v, bool) and not v: res = []
elif type(v) not in sutils.sequenceTypes: res = [v]
else: res = v
return [str(v) for v in res]
# ------------------------------------------------------------------------------
class No:
'''When you write a workflow condition method and you want to return False
but you want to give to the user some explanations about why a transition
can't be triggered, do not return False, return an instance of No
instead. When creating such an instance, you can specify an error
message.'''
def __init__(self, msg): self.msg = msg
def __nonzero__(self): return False
def __repr__(self): return '<No: %s>' % self.msg
# ------------------------------------------------------------------------------