Added script eggify.py for wrapping a Python module into an egg, and plenty of minor improvements and refactorings.

This commit is contained in:
Gaetan Delannay 2010-11-26 17:30:46 +01:00
parent aea19a819e
commit 52816ec343
23 changed files with 2048 additions and 1760 deletions

View file

@ -452,6 +452,16 @@ class ToolMixin(BaseMixin):
return pythonClass.maySearch(self.appy())
return True
def userMayNavigate(self, someClass):
'''This method checks if the currently logged user can display the
navigation panel within the portlet. This is done by calling method
"mayNavigate" on the class whose currently shown object is an
instance of. If no such method exists, we return True.'''
pythonClass = self.getAppyClass(someClass)
if 'mayNavigate' in pythonClass.__dict__:
return pythonClass.mayNavigate(self.appy())
return True
def onImportObjects(self):
'''This method is called when the user wants to create objects from
external data.'''

View file

@ -77,6 +77,10 @@ class BaseMixin:
def delete(self):
'''This methods is self's suicide.'''
# Call a custom "onDelete" if it exists
appyObj = self.appy()
if hasattr(appyObj, 'onDelete'): appyObj.onDelete()
# Delete the object
self.getParentNode().manage_delObjects([self.id])
def onDelete(self):
@ -245,8 +249,11 @@ class BaseMixin:
return self.goto(obj.getUrl())
if rq.get('buttonNext.x', None):
# Go to the next page for this object
# 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, rq['page'])
pageName, pageInfo = self.getNextPage(phaseInfo, pageName)
if pageName:
# Return to the edit or view page?
if pageInfo['showOnEdit']:
@ -576,7 +583,13 @@ class BaseMixin:
def getPreviousPage(self, phase, page):
'''Returns the page that precedes p_page which is in p_phase.'''
pageIndex = phase['pages'].index(page)
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]
@ -594,7 +607,13 @@ class BaseMixin:
def getNextPage(self, phase, page):
'''Returns the page that follows p_page which is in p_phase.'''
pageIndex = phase['pages'].index(page)
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]
@ -735,7 +754,9 @@ class BaseMixin:
'''Executes action with p_fieldName on this object.'''
appyType = self.getAppyType(actionName)
actionRes = appyType(self.appy())
self.reindexObject()
if self.getParentNode().get(self.id):
# Else, it means that the action has led to self's removal.
self.reindexObject()
return appyType.result, actionRes
def onExecuteAppyAction(self):
@ -755,13 +776,17 @@ class BaseMixin:
if (resultType == 'computation') or not successfull:
self.plone_utils.addPortalMessage(msg)
return self.goto(self.getUrl(rq['HTTP_REFERER']))
else:
elif resultType == 'file':
# msg does not contain a message, but a complete file to show as is.
# (or, if your prefer, the message must be shown directly to the
# user, not encapsulated in a Plone page).
res = self.getProductConfig().File(msg.name, msg.name, msg,
content_type=mimetypes.guess_type(msg.name)[0])
return res.index_html(rq, rq.RESPONSE)
elif resultType == 'redirect':
# msg does not contain a message, but the URL where to redirect
# the user.
return self.goto(msg)
def onTriggerTransition(self):
'''This method is called whenever a user wants to trigger a workflow

View file

@ -28,11 +28,8 @@
</td>
</dt>
<tal:publishedObject condition="python: contextObj">
<dt class="portletAppyItem portletCurrent">
<a tal:attributes="href python: contextObj.getUrl(page='main')"
tal:content="contextObj/Title"></a>
</dt>
<tal:publishedObject condition="python: contextObj and tool.userMayNavigate(contextObj.meta_type)">
<dt class="portletAppyItem portletCurrent"><b tal:content="contextObj/Title"></b></dt>
<dt class="portletAppyItem"><metal:phases use-macro="here/skyn/portlet/macros/phases"/></dt>
</tal:publishedObject>

View file

@ -17,6 +17,7 @@ try:
except ImportError:
CustomizationPolicy = None
from OFS.Image import File
from ZPublisher.HTTPRequest import FileUpload
from DateTime import DateTime
from Products.CMFCore import utils as cmfutils
from Products.CMFCore.utils import getToolByName

View file

@ -100,4 +100,13 @@ class UserWrapper(AbstractWrapper):
if groupName in userGroups:
group.removeMember(self.login)
self._callCustom('onEdit', created)
def onDelete(self):
'''Before deleting myself, I must delete the corresponding Plone
user.'''
# Delete the corresponding Plone user
self.o.acl_users._doDelUser(self.login)
self.log('Plone user "%s" deleted.' % self.login)
# Call a custom "onDelete" if any.
self._callCustom('onDelete')
# ------------------------------------------------------------------------------

View file

@ -5,7 +5,7 @@
import os, os.path, time, mimetypes, random
import appy.pod
from appy.gen import Search
from appy.gen.utils import sequenceTypes, FileWrapper
from appy.gen.utils import sequenceTypes
from appy.shared.utils import getOsTempFolder, executeCommand, normalizeString
from appy.shared.xml_parser import XmlMarshaller
@ -18,97 +18,62 @@ WRONG_FILE_TUPLE = 'This is not the way to set a file. You can specify a ' \
class AbstractWrapper:
'''Any real web framework object has a companion object that is an instance
of this class.'''
def __init__(self, o):
self.__dict__['o'] = o
def _set_file_attribute(self, name, v):
'''Updates the value of a file attribute named p_name with value p_v.
p_v may be:
- a string value containing the path to a file on disk;
- a 2-tuple (fileName, fileContent) where
* fileName = the name of the file (ie "myFile.odt")
* fileContent = the binary or textual content of the file or an
open file handler.
- a 3-tuple (fileName, fileContent, mimeType) where mimeType is the
v MIME type of the file.'''
ploneFileClass = self.o.getProductConfig().File
if isinstance(v, ploneFileClass):
setattr(self.o, name, v)
elif isinstance(v, FileWrapper):
setattr(self.o, name, v._atFile)
elif isinstance(v, basestring):
f = file(v)
fileName = os.path.basename(v)
fileId = 'file.%f' % time.time()
ploneFile = ploneFileClass(fileId, fileName, f)
ploneFile.filename = fileName
ploneFile.content_type = mimetypes.guess_type(fileName)[0]
setattr(self.o, name, ploneFile)
f.close()
elif type(v) in sequenceTypes:
# It should be a 2-tuple or 3-tuple
fileName = None
mimeType = None
if len(v) == 2:
fileName, fileContent = v
elif len(v) == 3:
fileName, fileContent, mimeType = v
else:
raise WRONG_FILE_TUPLE
if fileName:
fileId = 'file.%f' % time.time()
ploneFile = ploneFileClass(fileId, fileName, fileContent)
ploneFile.filename = fileName
if not mimeType:
mimeType = mimetypes.guess_type(fileName)[0]
ploneFile.content_type = mimeType
setattr(self.o, name, ploneFile)
def __setattr__(self, name, v):
if name == 'title':
self.o.setTitle(v)
return
def __init__(self, o): self.__dict__['o'] = o
def __setattr__(self, name, value):
appyType = self.o.getAppyType(name)
if not appyType:
raise 'Attribute "%s" does not exist.' % name
if appyType.type == 'File':
self._set_file_attribute(name, v)
elif appyType.type == 'Ref':
raise "Use methods 'link' or 'create' to modify references."
else:
setattr(self.o, name, v)
appyType.store(self.o, value)
def __repr__(self):
return '<%s wrapper at %s>' % (self.klass.__name__, id(self))
return '<%s appyobj at %s>' % (self.klass.__name__, id(self))
def __cmp__(self, other):
if other: return cmp(self.o, other.o)
else: return 1
else: return 1
def get_tool(self): return self.o.getTool().appy()
tool = property(get_tool)
def get_request(self): return self.o.REQUEST
request = property(get_request)
def get_session(self): return self.o.REQUEST.SESSION
session = property(get_session)
def get_typeName(self): return self.__class__.__bases__[-1].__name__
typeName = property(get_typeName)
def get_id(self): return self.o.id
id = property(get_id)
def get_uid(self): return self.o.UID()
uid = property(get_uid)
def get_state(self):
return self.o.portal_workflow.getInfoFor(self.o, 'review_state')
state = property(get_state)
def get_stateLabel(self):
appName = self.o.getProductConfig().PROJECTNAME
return self.o.utranslate(self.o.getWorkflowLabel(), domain=appName)
return self.o.translate(self.o.getWorkflowLabel(), domain=appName)
stateLabel = property(get_stateLabel)
def get_klass(self): return self.__class__.__bases__[-1]
klass = property(get_klass)
def get_url(self): return self.o.absolute_url()
url = property(get_url)
def get_history(self):
key = self.o.workflow_history.keys()[0]
return self.o.workflow_history[key]
history = property(get_history)
def get_user(self): return self.o.portal_membership.getAuthenticatedMember()
user = property(get_user)
def get_fields(self): return self.o.getAllAppyTypes()
fields = property(get_fields)
@ -210,6 +175,10 @@ class AbstractWrapper:
ploneObj.reindexObject()
return appyObj
def delete(self):
'''Deletes myself.'''
self.o.delete()
def translate(self, label, mapping={}, domain=None, language=None):
'''Check documentation of self.o.translate.'''
return self.o.translate(label, mapping, domain, language=language)