appy.bin: updated publish.py, that is now able to generate a DistUtils tar.gz for Appy; publish.py can now be called with option '-s' (silent): in this mode no question is asked to the user, default values are used; updated new.py that generates a better Plone4-ready simple Zope instance; appy: moved FileWrapper from appy.gen.utils to appy.shared.utils to avoid circular package dependencies; appy.gen: use of .pyt extensions for template Python classes in appy.gen.templates in order to avoid byte-compilation errors when distutils installs the package; appy.pod: when using function 'document' in 'from' statements, first arg can now be a appy.shared.utils.FileWrapper instance.

This commit is contained in:
Gaetan Delannay 2011-12-15 22:56:53 +01:00
parent e78cf62694
commit 6ece750d9a
15 changed files with 237 additions and 210 deletions

View file

@ -5,12 +5,13 @@ from appy import Object
from appy.gen.layout import Table
from appy.gen.layout import defaultFieldLayouts
from appy.gen.po import PoMessage
from appy.gen.utils import sequenceTypes, GroupDescr, Keywords, FileWrapper, \
getClassName, SomeObjects
from appy.gen.utils import sequenceTypes, GroupDescr, Keywords, getClassName, \
SomeObjects
import appy.pod
from appy.pod.renderer import Renderer
from appy.shared.data import countries
from appy.shared.utils import Traceback, getOsTempFolder, formatNumber
from appy.shared.utils import Traceback, getOsTempFolder, formatNumber, \
FileWrapper
# Default Appy permissions -----------------------------------------------------
r, w, d = ('read', 'write', 'delete')
@ -1505,7 +1506,7 @@ class File(Type):
def getRequestValue(self, request):
return request.get('%s_file' % self.name)
def getDefaultLayouts(self): return {'view':'lf','edit':'lrv-f'}
def getDefaultLayouts(self): return {'view':'l-f','edit':'lrv-f'}
def isEmptyValue(self, value, obj=None):
'''Must p_value be considered as empty?'''
@ -1536,8 +1537,9 @@ class File(Type):
* 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;
* an instance of appy.shared.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:

View file

@ -675,7 +675,7 @@ class ZopeGenerator(Generator):
repls['languages'] = ','.join('"%s"' % l for l in self.config.languages)
repls['languageSelector'] = self.config.languageSelector
repls['sourceLanguage'] = self.config.sourceLanguage
self.copyFile('config.py', repls)
self.copyFile('config.pyt', repls, destName='config.py')
def generateInit(self):
# Compute imports
@ -690,7 +690,7 @@ class ZopeGenerator(Generator):
repls['imports'] = '\n'.join(imports)
repls['classes'] = ','.join(classNames)
repls['totalNumberOfTests'] = self.totalNumberOfTests
self.copyFile('__init__.py', repls)
self.copyFile('__init__.pyt', repls, destName='__init__.py')
def getClassesInOrder(self, allClasses):
'''When generating wrappers, classes mut be dumped in order (else, it
@ -757,7 +757,7 @@ class ZopeGenerator(Generator):
for klass in self.getClasses(include='predefined'):
modelClass = klass.modelClass
repls['%s' % modelClass.__name__] = modelClass._appy_getBody()
self.copyFile('wrappers.py', repls)
self.copyFile('wrappers.pyt', repls, destName='wrappers.py')
def generateTests(self):
'''Generates the file needed for executing tests.'''
@ -765,7 +765,8 @@ class ZopeGenerator(Generator):
modules = self.modulesWithTests
repls['imports'] = '\n'.join(['import %s' % m for m in modules])
repls['modulesWithTests'] = ','.join(modules)
self.copyFile('testAll.py', repls, destFolder='tests')
self.copyFile('testAll.pyt', repls, destName='testAll.py',
destFolder='tests')
def generateTool(self):
'''Generates the tool that corresponds to this application.'''
@ -790,7 +791,7 @@ class ZopeGenerator(Generator):
repls.update({'methods': klass.methods, 'genClassName': klass.name,
'baseMixin':'BaseMixin', 'parents': 'BaseMixin, SimpleItem',
'classDoc': 'Standard Appy class', 'icon':'object.gif'})
self.copyFile('Class.py', repls, destName='%s.py' % klass.name)
self.copyFile('Class.pyt', repls, destName='%s.py' % klass.name)
# Before generating the Tool class, finalize it with query result
# columns, with fields to propagate, workflow-related fields.
@ -817,7 +818,7 @@ class ZopeGenerator(Generator):
'genClassName': self.tool.name, 'baseMixin':'ToolMixin',
'parents': 'ToolMixin, Folder', 'icon': 'folder.gif',
'classDoc': 'Tool class for %s' % self.applicationName})
self.copyFile('Class.py', repls, destName='%s.py' % self.tool.name)
self.copyFile('Class.pyt', repls, destName='%s.py' % self.tool.name)
def generateClass(self, classDescr):
'''Is called each time an Appy class is found in the application, for
@ -863,7 +864,7 @@ class ZopeGenerator(Generator):
if poMsg not in self.labels:
self.labels.append(poMsg)
# Generate the resulting Zope class.
self.copyFile('Class.py', repls, destName=fileName)
self.copyFile('Class.pyt', repls, destName=fileName)
def generateWorkflow(self, wfDescr):
'''This method creates the i18n labels related to the workflow described

View file

@ -781,7 +781,7 @@ class ToolMixin(BaseMixin):
if brain:
sibling = brain.getObject()
res[urlKey] = sibling.getUrl(nav=newNav % (index + 1),
page='main')
page=self.REQUEST.get('page', 'main'))
return res
def tabularize(self, data, numberOfRows):

View file

@ -1,7 +1,6 @@
# ------------------------------------------------------------------------------
import re, os, os.path, time
import re, os, os.path
import appy.pod
from appy.shared.utils import getOsTempFolder, normalizeString, executeCommand
sequenceTypes = (list, tuple)
# Function for creating a Zope object ------------------------------------------
@ -242,84 +241,6 @@ class Keywords:
return op.join(self.keywords)+'*'
return ''
# ------------------------------------------------------------------------------
CONVERSION_ERROR = 'An error occurred while executing command "%s". %s'
class FileWrapper:
'''When you get, from an appy object, the value of a File attribute, you
get an instance of this class.'''
def __init__(self, zopeFile):
'''This constructor is only used by Appy to create a nice File instance
from a Zope corresponding instance (p_zopeFile). If you need to
create a new file and assign it to a File attribute, use the
attribute setter, do not create yourself an instance of this
class.'''
d = self.__dict__
d['_zopeFile'] = zopeFile # Not for you!
d['name'] = zopeFile.filename
d['content'] = zopeFile.data
d['mimeType'] = zopeFile.content_type
d['size'] = zopeFile.size # In bytes
def __setattr__(self, name, v):
d = self.__dict__
if name == 'name':
self._zopeFile.filename = v
d['name'] = v
elif name == 'content':
self._zopeFile.update_data(v, self.mimeType, len(v))
d['content'] = v
d['size'] = len(v)
elif name == 'mimeType':
self._zopeFile.content_type = self.mimeType = v
else:
raise 'Impossible to set attribute %s. "Settable" attributes ' \
'are "name", "content" and "mimeType".' % name
def dump(self, filePath=None, format=None, tool=None):
'''Writes the file on disk. If p_filePath is specified, it is the
path name where the file will be dumped; folders mentioned in it
must exist. If not, the file will be dumped in the OS temp folder.
The absolute path name of the dumped file is returned.
If an error occurs, the method returns None. If p_format is
specified, OpenOffice will be called for converting the dumped file
to the desired format. In this case, p_tool, a Appy tool, must be
provided. Indeed, any Appy tool contains parameters for contacting
OpenOffice in server mode.'''
if not filePath:
filePath = '%s/file%f.%s' % (getOsTempFolder(), time.time(),
normalizeString(self.name))
f = file(filePath, 'w')
if self.content.__class__.__name__ == 'Pdata':
# The file content is splitted in several chunks.
f.write(self.content.data)
nextPart = self.content.next
while nextPart:
f.write(nextPart.data)
nextPart = nextPart.next
else:
# Only one chunk
f.write(self.content)
f.close()
if format:
if not tool: return
# Convert the dumped file using OpenOffice
errorMessage = tool.convert(filePath, format)
# Even if we have an "error" message, it could be a simple warning.
# So we will continue here and, as a subsequent check for knowing if
# an error occurred or not, we will test the existence of the
# converted file (see below).
os.remove(filePath)
# Return the name of the converted file.
baseName, ext = os.path.splitext(filePath)
if (ext == '.%s' % format):
filePath = '%s.res.%s' % (baseName, format)
else:
filePath = '%s.%s' % (baseName, format)
if not os.path.exists(filePath):
tool.log(CONVERSION_ERROR % (cmd, errorMessage), type='error')
return
return filePath
# ------------------------------------------------------------------------------
def getClassName(klass, appName=None):
'''Generates, from appy-class p_klass, the name of the corresponding