Added script eggify.py for wrapping a Python module into an egg, and plenty of minor improvements and refactorings.
This commit is contained in:
parent
aea19a819e
commit
52816ec343
23 changed files with 2048 additions and 1760 deletions
143
gen/__init__.py
143
gen/__init__.py
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# ------------------------------------------------------------------------------
|
||||
import re, time, copy, sys, types, os, os.path
|
||||
import re, time, copy, sys, types, os, os.path, mimetypes
|
||||
from appy.shared.utils import Traceback
|
||||
from appy.gen.layout import Table
|
||||
from appy.gen.layout import defaultFieldLayouts
|
||||
|
@ -1203,7 +1203,7 @@ class String(Type):
|
|||
def store(self, obj, value):
|
||||
if self.isMultiValued() and isinstance(value, basestring):
|
||||
value = [value]
|
||||
setattr(obj, self.name, value)
|
||||
exec 'obj.%s = value' % self.name
|
||||
|
||||
class Boolean(Type):
|
||||
def __init__(self, validator=None, multiplicity=(0,1), index=None,
|
||||
|
@ -1352,31 +1352,80 @@ class File(Type):
|
|||
|
||||
defaultMimeType = 'application/octet-stream'
|
||||
def store(self, obj, value):
|
||||
'''Stores the p_value (produced by m_getStorableValue) that complies to
|
||||
p_self type definition on p_obj.'''
|
||||
'''Stores the p_value that represents some file. p_value can be:
|
||||
* an instance of Zope class ZPublisher.HTTPRequest.FileUpload. In
|
||||
this case, it is file content coming from a HTTP POST;
|
||||
* an instance of Zope class OFS.Image.File;
|
||||
* an instance of appy.gen.utils.FileWrapper, which wraps an instance
|
||||
of OFS.Image.File and adds useful methods for manipulating it;
|
||||
* a string. In this case, the string represents the path of a file
|
||||
on disk;
|
||||
* a 2-tuple (fileName, fileContent) where:
|
||||
- fileName is the name of the file (ie "myFile.odt")
|
||||
- fileContent is the binary or textual content of the file or an
|
||||
open file handler.
|
||||
* a 3-tuple (fileName, fileContent, mimeType) where
|
||||
- fileName and fileContent have the same meaning than above;
|
||||
- mimeType is the MIME type of the file.
|
||||
'''
|
||||
if value:
|
||||
# Retrieve the existing value, or create one if None
|
||||
existingValue = getattr(obj, self.name, None)
|
||||
if not existingValue:
|
||||
import OFS.Image
|
||||
existingValue = OFS.Image.File(self.name, '', '')
|
||||
# Set mimetype
|
||||
if value.headers.has_key('content-type'):
|
||||
mimeType = value.headers['content-type']
|
||||
else:
|
||||
mimeType = File.defaultMimeType
|
||||
existingValue.content_type = mimeType
|
||||
# Set filename
|
||||
fileName = value.filename
|
||||
filename = fileName[max(fileName.rfind('/'), fileName.rfind('\\'),
|
||||
fileName.rfind(':'))+1:]
|
||||
existingValue.filename = fileName
|
||||
# Set content
|
||||
existingValue.manage_upload(value)
|
||||
setattr(obj, self.name, existingValue)
|
||||
ZFileUpload = self.o.getProductConfig().FileUpload
|
||||
OFSImageFile = self.o.getProductConfig().File
|
||||
if isinstance(value, ZFileUpload):
|
||||
# The file content comes from a HTTP POST.
|
||||
# Retrieve the existing value, or create one if None
|
||||
existingValue = getattr(obj, self.name, None)
|
||||
if not existingValue:
|
||||
existingValue = OFSImageFile(self.name, '', '')
|
||||
# Set mimetype
|
||||
if value.headers.has_key('content-type'):
|
||||
mimeType = value.headers['content-type']
|
||||
else:
|
||||
mimeType = File.defaultMimeType
|
||||
existingValue.content_type = mimeType
|
||||
# Set filename
|
||||
fileName = value.filename
|
||||
filename= fileName[max(fileName.rfind('/'),fileName.rfind('\\'),
|
||||
fileName.rfind(':'))+1:]
|
||||
existingValue.filename = fileName
|
||||
# Set content
|
||||
existingValue.manage_upload(value)
|
||||
setattr(obj, self.name, existingValue)
|
||||
elif isinstance(value, OFSImageFile):
|
||||
setattr(obj, self.name, value)
|
||||
elif isinstance(value, FileWrapper):
|
||||
setattr(obj, self.name, value._atFile)
|
||||
elif isinstance(value, basestring):
|
||||
f = file(value)
|
||||
fileName = os.path.basename(value)
|
||||
fileId = 'file.%f' % time.time()
|
||||
zopeFile = OFSImageFile(fileId, fileName, f)
|
||||
zopeFile.filename = fileName
|
||||
zopeFile.content_type = mimetypes.guess_type(fileName)[0]
|
||||
setattr(obj, self.name, zopeFile)
|
||||
f.close()
|
||||
elif type(value) in sequenceTypes:
|
||||
# It should be a 2-tuple or 3-tuple
|
||||
fileName = None
|
||||
mimeType = None
|
||||
if len(value) == 2:
|
||||
fileName, fileContent = value
|
||||
elif len(value) == 3:
|
||||
fileName, fileContent, mimeType = value
|
||||
else:
|
||||
raise WRONG_FILE_TUPLE
|
||||
if fileName:
|
||||
fileId = 'file.%f' % time.time()
|
||||
zopeFile = OFSImageFile(fileId, fileName, fileContent)
|
||||
zopeFile.filename = fileName
|
||||
if not mimeType:
|
||||
mimeType = mimetypes.guess_type(fileName)[0]
|
||||
zopeFile.content_type = mimeType
|
||||
setattr(obj, self.name, zopeFile)
|
||||
else:
|
||||
# What must I do: delete the existing file or keep it ?
|
||||
action = obj.REQUEST.get('%s_delete' % self.name)
|
||||
# I store value "None", excepted if I find in the request the desire
|
||||
# to keep the file unchanged.
|
||||
action = obj.REQUEST.get('%s_delete' % self.name, None)
|
||||
if action == 'nochange': pass
|
||||
else: setattr(obj, self.name, None)
|
||||
|
||||
|
@ -1542,21 +1591,30 @@ class Ref(Type):
|
|||
return obj.translate('max_ref_violated')
|
||||
|
||||
def store(self, obj, value):
|
||||
'''Stores on p_obj, the p_value, which can be None, an object UID or a
|
||||
list of UIDs coming from the request. This method is only called for
|
||||
Ref fields with link=True.'''
|
||||
# Security check
|
||||
if not self.isShowable(obj, 'edit'): return
|
||||
'''Stores on p_obj, the p_value, which can be:
|
||||
* None;
|
||||
* an object UID (=string);
|
||||
* a list of object UIDs (=list of strings). Generally, UIDs or lists
|
||||
of UIDs come from Ref fields with link:True edited through the web;
|
||||
* a Zope object;
|
||||
* a Appy object;
|
||||
* a list of Appy or Zope objects.
|
||||
'''
|
||||
# Standardize the way p_value is expressed
|
||||
uids = value
|
||||
if not value: uids = []
|
||||
if isinstance(value, basestring): uids = [value]
|
||||
refs = value
|
||||
if not refs: refs = []
|
||||
if type(refs) not in sequenceTypes: refs = [refs]
|
||||
for i in range(len(refs)):
|
||||
if isinstance(refs[i], basestring):
|
||||
# Get the Zope object from the UID
|
||||
refs[i] = obj.uid_catalog(UID=refs[i])[0].getObject()
|
||||
else:
|
||||
refs[i] = refs[i].o # Now we are sure to have a Zope object.
|
||||
# Update the field storing on p_obj the ordered list of UIDs
|
||||
sortedRefs = obj._appy_getSortedField(self.name)
|
||||
del sortedRefs[:]
|
||||
for uid in uids: sortedRefs.append(uid)
|
||||
# Update the refs
|
||||
refs = [obj.uid_catalog(UID=uid)[0].getObject() for uid in uids]
|
||||
for ref in refs: sortedRefs.append(ref.UID())
|
||||
# Update the Archetypes Ref field.
|
||||
exec 'obj.set%s%s(refs)' % (self.name[0].upper(), self.name[1:])
|
||||
|
||||
def clone(self, forTool=True):
|
||||
|
@ -1618,11 +1676,14 @@ class Action(Type):
|
|||
master=None, masterValue=None, focus=False, historized=False):
|
||||
# Can be a single method or a list/tuple of methods
|
||||
self.action = action
|
||||
# For the following field, value 'computation' means that the action
|
||||
# will simply compute things and redirect the user to the same page,
|
||||
# with some status message about execution of the action. 'file' means
|
||||
# that the result is the binary content of a file that the user will
|
||||
# download.
|
||||
# For the following field:
|
||||
# * value 'computation' means that the action will simply compute
|
||||
# things and redirect the user to the same page, with some status
|
||||
# message about execution of the action;
|
||||
# * 'file' means that the result is the binary content of a file that
|
||||
# the user will download.
|
||||
# * 'redirect' means that the action will lead to the user being
|
||||
# redirected to some other page.
|
||||
self.result = result
|
||||
# If following field "confirm" is True, a popup will ask the user if
|
||||
# she is really sure about triggering this action.
|
||||
|
|
|
@ -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.'''
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue