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

@ -6,7 +6,7 @@ from appy.shared.utils import FolderDeleter, cleanFolder
# ------------------------------------------------------------------------------
class Cleaner:
def run(self, verbose=True):
cleanFolder(appyPath, verbose=True)
cleanFolder(appyPath, verbose=verbose)
# Remove all files in temp folders
for tempFolder in ('%s/temp' % appyPath,
'%s/pod/test/temp' % appyPath):

178
bin/eggify.py Normal file
View file

@ -0,0 +1,178 @@
'''This sript allows to wrap a Python module into an egg.'''
# ------------------------------------------------------------------------------
import os, os.path, sys, zipfile, appy
from appy.bin.clean import Cleaner
from appy.shared.utils import FolderDeleter, copyFolder
from optparse import OptionParser
# ------------------------------------------------------------------------------
class EggifierError(Exception): pass
ERROR_CODE = 1
eggInfo = '''from setuptools import setup, find_packages
import os
setup(name = "%s", version = "%s", description = "%s",
long_description = "%s",
author = "%s", author_email = "%s",
license = "GPL", keywords = "plone, appy", url = '%s',
classifiers = ["Framework :: Appy", "Programming Language :: Python",],
packages=find_packages(exclude=['ez_setup']), include_package_data = True,
namespace_packages=['%s'], zip_safe = False,
install_requires=['setuptools'],)'''
initInfo = '''
# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
try:
__import__('pkg_resources').declare_namespace(__name__)
except ImportError:
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
'''
# ------------------------------------------------------------------------------
class EggifyScript:
'''usage: python eggify.py pythonModule [options]
pythonModule is the path to a Python module or the name of a Python file.
Available options are:
-a --appy If specified, the Appy module (light version, without
test code) will be included in the egg.
-r --result The path where to create the egg (defaults to the
current working directory)
-p --products If specified, the module will be packaged in the
"Products" namespace.
-v --version Egg version. Defaults to 1.0.0.
'''
def createSetupFile(self, eggTempFolder):
'''Creates the setup.py file in the egg.'''
content = eggInfo % (self.moduleName, self.version, 'Appy module',
'Appy module', 'Gaetan Delannay',
'gaetan.delannay AT gmail.com',
'http://appyframework.org',
self.moduleName.split('.')[0])
f = file(os.path.join(eggTempFolder, 'setup.py'), 'w')
f.write(content)
f.close()
def createInitFile(self, eggTempFolder):
'''Creates the ez_setup-compliant __init__ files.'''
initPath = os.path.join(eggTempFolder,self.moduleName.split('.')[0])
f = file(os.path.join(initPath, '__init__.py'), 'w')
f.write(initInfo)
f.close()
def getEggName(self):
'''Creates the egg name.'''
return '%s-%s.egg' % (self.moduleName, self.version)
zipExclusions = ('.bzr', 'doc', 'test', 'versions')
def dirInZip(self, dir):
'''Returns True if the p_dir must be included in the zip.'''
for exclusion in self.zipExclusions:
if dir.endswith(exclusion) or ('/%s/' % exclusion in dir):
return False
return True
def zipResult(self, eggFullName, eggTempFolder):
'''Zips the result and removes the egg temp folder.'''
zipFile = zipfile.ZipFile(eggFullName, 'w', zipfile.ZIP_DEFLATED)
# Put the Python module inside the egg.
prefix = os.path.dirname(eggTempFolder)
for dir, dirnames, filenames in os.walk(eggTempFolder):
for f in filenames:
fileName = os.path.join(dir, f)
zipFile.write(fileName, fileName[len(prefix):])
# Put the Appy module inside it if required.
if self.includeAppy:
eggPrefix = '%s/%s' % (eggTempFolder[len(prefix):],
self.moduleName.replace('.', '/'))
# Where is Appy?
appyPath = os.path.dirname(appy.__file__)
appyPrefix = os.path.dirname(appyPath)
# Clean the Appy folder
Cleaner().run(verbose=False)
# Insert appy files into the zip
for dir, dirnames, filenames in os.walk(appyPath):
if not self.dirInZip(dir): continue
for f in filenames:
fileName = os.path.join(dir, f)
zipName = eggPrefix + fileName[len(appyPrefix):]
zipFile.write(fileName, zipName)
zipFile.close()
# Remove the temp egg folder.
FolderDeleter.delete(eggTempFolder)
def eggify(self):
'''Let's wrap a nice Python module into an ugly egg.'''
j = os.path.join
# Create the egg folder
eggFullName = j(self.eggFolder, self.eggName)
if os.path.exists(eggFullName):
os.remove(eggFullName)
print 'Existing "%s" was removed.' % eggFullName
# Create a temp folder where to store the egg
eggTempFolder = os.path.splitext(eggFullName)[0]
if os.path.exists(eggTempFolder):
FolderDeleter.delete(eggTempFolder)
print 'Removed "%s" that was in my way.' % eggTempFolder
os.mkdir(eggTempFolder)
# Create the "Products" sub-folder if we must wrap the package in this
# namespace
eggModulePath = j(j(eggTempFolder, self.moduleName.replace('.', '/')))
# Copy the Python module into the egg.
os.makedirs(eggModulePath)
copyFolder(self.pythonModule, eggModulePath)
# Create setup files in the root egg folder
self.createSetupFile(eggTempFolder)
self.createInitFile(eggTempFolder)
self.zipResult(eggFullName, eggTempFolder)
def checkArgs(self, options, args):
# Check that we have the correct number of args.
if len(args) != 1: raise EggifierError('Wrong number of arguments.')
# Check that the arg corresponds to an existing Python module
if not os.path.exists(args[0]):
raise EggifierError('Path "%s" does not correspond to an ' \
'existing Python package.' % args[0])
self.pythonModule = args[0]
# At present I only manage Python modules, not 1-file Python packages.
if not os.path.isdir(self.pythonModule):
raise EggifierError('"%s" is not a folder. One-file Python ' \
'packages are not supported yet.' % args[0])
self.eggFolder = options.result
if not os.path.exists(self.eggFolder):
raise EggifierError('"%s" does not exist. Please create this ' \
'folder first.' % self.eggFolder)
self.includeAppy = options.appy
self.inProducts = options.products
self.version = options.version
self.moduleName = os.path.basename(self.pythonModule)
if self.inProducts:
self.moduleName = 'Products.' + self.moduleName
self.eggName = self.getEggName()
def run(self):
optParser = OptionParser(usage=EggifyScript.__doc__)
optParser.add_option("-r", "--result", dest="result",
help="The folder where to create the egg",
default=os.getcwd(), metavar="RESULT",
type='string')
optParser.add_option("-a", "--appy", action="store_true",
help="Includes the Appy module in the egg")
optParser.add_option("-p", "--products", action="store_true",
help="Includes the module in the 'Products' " \
"namespace")
optParser.add_option("-v", "--version", dest="version",
help="The module version", default='1.0.0',
metavar="VERSION", type='string')
options, args = optParser.parse_args()
try:
self.checkArgs(options, args)
self.eggify()
except EggifierError, ee:
sys.stderr.write(str(ee) + '\nRun eggify.py -h for getting help.\n')
sys.exit(ERROR_CODE)
# ------------------------------------------------------------------------------
if __name__ == '__main__':
EggifyScript().run()
# ------------------------------------------------------------------------------

View file

@ -395,7 +395,7 @@ class Publisher:
f.close()
privateScripts = ('publish.py', 'zip.py', 'runOpenOffice.sh')
def prepareGenFolder(self):
def prepareGenFolder(self, minimalist=False):
'''Creates the basic structure of the temp folder where the appy
website will be generated.'''
# Reinitialise temp folder where the generated website will be dumped
@ -413,6 +413,8 @@ class Publisher:
# Remove some scripts from bin
for script in self.privateScripts:
os.remove('%s/bin/%s' % (genSrcFolder, script))
if minimalist:
FolderDeleter.delete('%s/pod/test' % genSrcFolder)
# Write the appy version into the code itself (in appy/version.py)'''
print 'Publishing version %s...' % self.versionShort
# Dump version info in appy/version.py
@ -436,7 +438,9 @@ class Publisher:
# Perform a small analysis on the Appy code
LinesCounter(appy).run()
print 'Generating site in %s...' % self.genFolder
self.prepareGenFolder()
minimalist = askQuestion('Minimalist (shipped without tests)?',
default='no')
self.prepareGenFolder(minimalist)
self.createDocToc()
self.applyTemplate()
self.createZipRelease()

View file

@ -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,14 +1352,31 @@ 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:
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:
import OFS.Image
existingValue = OFS.Image.File(self.name, '', '')
existingValue = OFSImageFile(self.name, '', '')
# Set mimetype
if value.headers.has_key('content-type'):
mimeType = value.headers['content-type']
@ -1368,15 +1385,47 @@ class File(Type):
existingValue.content_type = mimeType
# Set filename
fileName = value.filename
filename = fileName[max(fileName.rfind('/'), fileName.rfind('\\'),
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:
# What must I do: delete the existing file or keep it ?
action = obj.REQUEST.get('%s_delete' % self.name)
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:
# 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.

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.'''
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.'''
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,6 +754,8 @@ class BaseMixin:
'''Executes action with p_fieldName on this object.'''
appyType = self.getAppyType(actionName)
actionRes = appyType(self.appy())
if self.getParentNode().get(self.id):
# Else, it means that the action has led to self's removal.
self.reindexObject()
return appyType.result, actionRes
@ -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
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)

View file

@ -486,11 +486,18 @@ class MemoryBuffer(Buffer):
else:
res = self.getLength()
return res
reTagContent = re.compile('<(?P<p>[\w-]+):(?P<f>[\w-]+)(.*?)>.*</(?P=p):' \
'(?P=f)>', re.S)
def evaluate(self, subElements=True, removeMainElems=False):
#print 'Evaluating buffer', self.content.encode('utf-8'), self.elements
result = self.getFileBuffer()
if not subElements:
result.write(self.content)
# Dump the root tag in this buffer, but not its content.
res = self.reTagContent.match(self.content.strip())
if not res: result.write(self.content)
else:
g = res.group
result.write('<%s:%s%s></%s:%s>' % (g(1),g(2),g(3),g(1),g(2)))
else:
iter = BufferIterator(self)
currentIndex = self.getStartIndex(removeMainElems)
@ -505,8 +512,6 @@ class MemoryBuffer(Buffer):
PodError.dump(result, EVAL_EXPR_ERROR % (
evalEntry.expr, e), dumpTb=False)
else: # It is a subBuffer
#print '******Subbuffer*************'
# This is a bug.
if evalEntry.action:
evalEntry.action.execute()
else:

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,17 @@
class Student:
def __init__(self, **kwargs):
for k, v in kwargs.iteritems():
setattr(self, k, v)
students = [
Student(parent_guardian='Parent 1', street='Street 1', city='Flawinne',
state='Namur', zip='5020', lname='Name 1', fname='First name 1'),
Student(parent_guardian='Parent 2', street='Street 2', city='Flawinne',
state='Namur', zip='5020', lname='Name 2', fname='First name 2'),
Student(parent_guardian='Parent 3', street='Street 3', city='Flawinne',
state='Namur', zip='5020', lname='Name 3', fname='First name 3'),
Student(parent_guardian='Parent 4', street='Street 4', city='Flawinne',
state='Namur', zip='5020', lname='Name 4', fname='First name 4'),
Student(parent_guardian='Parent 5', street='Street 5', city='Flawinne',
state='Namur', zip='5020', lname='Name 5', fname='First name 5'),
]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.