Implemented blueprints https://blueprints.launchpad.net/appy/+spec/gen-create-root-objects, https://blueprints.launchpad.net/appy/+spec/gen-get-flavour and https://blueprints.launchpad.net/appy/+spec/pod-define-variables
This commit is contained in:
parent
ec17f900a6
commit
10eea7d735
|
@ -226,12 +226,6 @@ class Text2Html:
|
||||||
self.txtFile.close()
|
self.txtFile.close()
|
||||||
self.htmlFile.close()
|
self.htmlFile.close()
|
||||||
|
|
||||||
class TodoConverter(Text2Html):
|
|
||||||
title = 'To do'
|
|
||||||
firstChar = 1 # Position of the first relevant char in each line
|
|
||||||
def retainLine(self, line):
|
|
||||||
return line.startswith('v') and len(line) > 2
|
|
||||||
|
|
||||||
class VersionsConverter(Text2Html):
|
class VersionsConverter(Text2Html):
|
||||||
title = 'Versions'
|
title = 'Versions'
|
||||||
firstChar = 0
|
firstChar = 0
|
||||||
|
@ -482,7 +476,6 @@ class Publisher:
|
||||||
f.write('verbose = "%s"' % self.versionLong)
|
f.write('verbose = "%s"' % self.versionLong)
|
||||||
f.close()
|
f.close()
|
||||||
# Remove unwanted files
|
# Remove unwanted files
|
||||||
os.remove('%s/todo.txt' % self.genFolder)
|
|
||||||
os.remove('%s/version.txt' % self.genFolder)
|
os.remove('%s/version.txt' % self.genFolder)
|
||||||
os.remove('%s/license.txt' % self.genFolder)
|
os.remove('%s/license.txt' % self.genFolder)
|
||||||
os.remove('%s/template.html' % self.genFolder)
|
os.remove('%s/template.html' % self.genFolder)
|
||||||
|
@ -492,10 +485,7 @@ class Publisher:
|
||||||
for dirName in dirs:
|
for dirName in dirs:
|
||||||
if dirName == '.svn':
|
if dirName == '.svn':
|
||||||
FolderDeleter.delete(os.path.join(root, dirName))
|
FolderDeleter.delete(os.path.join(root, dirName))
|
||||||
# Generates the "to do" and "versions" pages, based on todo.txt and
|
# Generates the "versions" page, based on version.txt
|
||||||
# version.txt
|
|
||||||
TodoConverter('%s/doc/todo.txt' % appyPath,
|
|
||||||
'%s/todo.html' % self.genFolder).run()
|
|
||||||
VersionsConverter('%s/doc/version.txt' % appyPath,
|
VersionsConverter('%s/doc/version.txt' % appyPath,
|
||||||
'%s/version.html' % self.genFolder).run()
|
'%s/version.html' % self.genFolder).run()
|
||||||
|
|
||||||
|
|
69
doc/todo.txt
69
doc/todo.txt
|
@ -1,69 +0,0 @@
|
||||||
vpod
|
|
||||||
v- Error in buffers.py line 422 (in method evaluate): evalEntry.action may be None but I don't know why (maybe due to buffers cut?)
|
|
||||||
v- Document the ability to read the template from memory, not only from the file system (it seems to work)
|
|
||||||
v- Size of resulting ODTs is too big.
|
|
||||||
v- Try to convert xhtml chunks that are not utf-8-encoded
|
|
||||||
v- XHTML conversion is not implemented for all XHTML tags.
|
|
||||||
v- XHTML conversion produces empty results for some XHTML tag combinations
|
|
||||||
v- Implement a function "pod" that allows to insert the content of another pod template into the current file
|
|
||||||
v- Implement a "retry n times" function when calling OO in server mode ("n" should be parameterized)
|
|
||||||
v- Add a "do bullet" (li, ul) ?
|
|
||||||
v- Add a function "text" that replaces "carriage returns" with </p><p> (ou <br/>?) and that replaces leading dashes with bullets.
|
|
||||||
v- Deprecate "xhtml" function -> New function text(format=...) with format="xhtml" or "text" or...
|
|
||||||
v- ImageImporter: adding 'g' to file names may lead to too long file names.
|
|
||||||
v- Finalize function "document" (manage Microsoft formats: doc, xls...)
|
|
||||||
v- converter.py: allow to call converter.py without updating sections and/or indexes (may cause performance problems?)
|
|
||||||
v- When an error occurs in a for loop, the iteratr variable may not be incremented, which duplicates the errors in subsequent iterations. Try:finally?
|
|
||||||
|
|
||||||
vgen
|
|
||||||
v- View on AppyFolder must be query.pt
|
|
||||||
v- Build automatically a research page (included fields are selectable through the flavour) only with "searchable" fields.
|
|
||||||
v- Correct some navigation problems (after actions like delete, edit, etc)
|
|
||||||
v- Validation system: check if it is possible to forget about duplicating page and group labels in child classes. Possible to remove xxx_list labels?
|
|
||||||
v- Add a button for checking if current Python running Zope is UNO-compliant.
|
|
||||||
v- __setattr__ for Refs on Wrappers do not work, but the problem is solved in method "create" (inspiration source).
|
|
||||||
v- Generate a static site from a gen-application
|
|
||||||
v- Xml export (on file system or via webdav/REST)
|
|
||||||
v- Add an option to generator, where un-managed i18n labels are removed (useful during development, when fields are creates, deleted, renamed...)
|
|
||||||
v- Make the list of reserved attribute and method names (tool, trigger, state, ...) and raise errors on generation if those names are used by a gen-class
|
|
||||||
v- produceNiceDefault: manage numbers (when a number is encountered, add a space, etc).
|
|
||||||
v- When editing (not creating) a flavour (or when reinstalling the product), create ALL the portal_types if they do not exist (some may have been added after flavour creation).
|
|
||||||
v- By flavour or date object, add 2 params: date format with and without hour. Create a appy-specific macro for displaying dates, that will use those params.
|
|
||||||
v- Move some generated code (appyMixins, CommonMethods...) in appy.gen (minimize generated code).
|
|
||||||
v- Create the "pod" field type
|
|
||||||
v- Add on tool special action "uninstall" (similar to special action "install") + question "update workflow settings or not" ?
|
|
||||||
v- At startup, patch actions "tool.install" -> show = si selon Plone le Product doit etre reinstallé, i18n messages, etc.
|
|
||||||
v- Allow to create objects in the app root folder (and not only in the tool) from the tool (or anywhere else).
|
|
||||||
v- On dashboards and Ref tables, fields rendering does not always take into account permissions, show, etc.
|
|
||||||
v- Add "self.flavour" on any wrapper.
|
|
||||||
v- Config instance: add supposed number of flavours (for generating corresponding i18n labels _1, _2, etc, for flavour-specific content-types)
|
|
||||||
v- Config instance: create a defaultWorkflow attribute, similar to defaultCreators
|
|
||||||
v- Duplicate all special fields (optionalFieldsFor, defaultValueFor, etc) generated in a flavour for all child classes of a given class.
|
|
||||||
v- Permissions-to-roles mappings: if you don't specify a value for a given field-specific read/write permission, by default it will take the same value as for the whole-gen-class read/write permission.
|
|
||||||
v- Implement abstract workflows
|
|
||||||
v- displayTabs: do not display tabs for which no widget shows up (ie edit tabs on which no Ref widget is shown)
|
|
||||||
v- Add i18n labels for messages when transitions are triggered (! transition may potentially return a custom message!)
|
|
||||||
v- On all widgets: implement "warning method" -> kind of condition. If True, a warning message appears somewhere near the field. Useful for actions, transitions and pod templates.
|
|
||||||
v- Action fields: add param "confirm" (also for workflow transitions): if True a popup appears (with possibility to define a i18n message)
|
|
||||||
v- Ref fields: add parameter "sort" for sorting Ref fields (this param may be the name of a field or a custom method). On Refs with link=True, it is used for sorting the list of all references that may be linked with the current object; on Refs with add=True, it is used for inserting the newly created object.
|
|
||||||
v- tool.getReferenceLabel: use brain if fields are indexed (wake up object only when needed)
|
|
||||||
v- Delete tool.maxListBoxWidth and use param "width" of Ref field instead.
|
|
||||||
v- Comments in ZPTs: use everywhere tal tag "comment"
|
|
||||||
v- Common method "fieldValueSelected" -> does it produce the right value when the page is shown again due to a validation error? Must we check this case explicitly via emptyness of dict "errors" in request?
|
|
||||||
v- Document how to write your own code generator
|
|
||||||
v- pod integratio: when returning a PDF/ODT file, file name is not set (or file extension).
|
|
||||||
v- Add a param to pod fields will allow to freeze generated result, and to unfreeze or re-generate them (with admin access only or...?)
|
|
||||||
v- Take into account field-specific permissions when displaying backward fields
|
|
||||||
v- Improve graphical rendering of pages with additional classes (Group, Page,...) that may be specified instead of strings for parameters "group", "page" on fields
|
|
||||||
v- Add an event management system (generalize the mail notification mechanism from PloneMeeting) that one may customize per flavour, with some predefined action types (emails, log, freeze pdf,...)
|
|
||||||
v- Interoperability: class and worklow inheritance from one product to another and Refs to objects coming from other standard non Appy Plone products.
|
|
||||||
v- Add a mechanism that will allow developers to add things like ZPTs, etc, into the Plone product.
|
|
||||||
v- Write a test system.
|
|
||||||
v- Bug (seems to come from standard Plone): when defining a field multiselection mandatory, when unselecting all values, it does not do anything (if the field is not mandatory it works).
|
|
||||||
v- When we copy fields in class Flavour (because of editDefault), we must also recopy methods that were defined on the original Appy class on the FLavour class generated in appyWrappers.py.
|
|
||||||
v- Check accessors get_flavour defined multiple times in PodTemplate_Wrapper
|
|
||||||
v- Bug: when we specify encoding in a Python file, the generated Python AST is empty.
|
|
||||||
v- plone25: remove warnings by setting deprecated=True for all generated classes in configure.zcml
|
|
||||||
v- Workflow dependencies: permissions of a given object can depend on the ones defined on another (containing?).
|
|
||||||
v- Add an on-line help system (popup for every field)
|
|
||||||
|
|
|
@ -408,9 +408,11 @@ class Generator(AbstractGenerator):
|
||||||
self.copyFile('workflows.py', repls, destFolder='Extensions')
|
self.copyFile('workflows.py', repls, destFolder='Extensions')
|
||||||
|
|
||||||
def generateWrapperProperty(self, attrName, appyType):
|
def generateWrapperProperty(self, attrName, appyType):
|
||||||
# Generate getter
|
'''Generates the getter for attribute p_attrName having type
|
||||||
|
p_appyType.'''
|
||||||
res = ' def get_%s(self):\n' % attrName
|
res = ' def get_%s(self):\n' % attrName
|
||||||
blanks = ' '*8
|
blanks = ' '*8
|
||||||
|
getterName = 'get%s%s' % (attrName[0].upper(), attrName[1:])
|
||||||
if isinstance(appyType, Ref):
|
if isinstance(appyType, Ref):
|
||||||
res += blanks + 'return self.o._appy_getRefs("%s", ' \
|
res += blanks + 'return self.o._appy_getRefs("%s", ' \
|
||||||
'noListIfSingleObj=True)\n' % attrName
|
'noListIfSingleObj=True)\n' % attrName
|
||||||
|
@ -418,8 +420,11 @@ class Generator(AbstractGenerator):
|
||||||
res += blanks + 'appyType = getattr(self.klass, "%s")\n' % attrName
|
res += blanks + 'appyType = getattr(self.klass, "%s")\n' % attrName
|
||||||
res += blanks + 'return self.o.getComputedValue(' \
|
res += blanks + 'return self.o.getComputedValue(' \
|
||||||
'appyType.__dict__)\n'
|
'appyType.__dict__)\n'
|
||||||
|
elif isinstance(appyType, File):
|
||||||
|
res += blanks + 'v = self.o.%s()\n' % getterName
|
||||||
|
res += blanks + 'if not v: return None\n'
|
||||||
|
res += blanks + 'else: return FileWrapper(v)\n'
|
||||||
else:
|
else:
|
||||||
getterName = 'get%s%s' % (attrName[0].upper(), attrName[1:])
|
|
||||||
if attrName in ArchetypeFieldDescriptor.specialParams:
|
if attrName in ArchetypeFieldDescriptor.specialParams:
|
||||||
getterName = attrName.capitalize()
|
getterName = attrName.capitalize()
|
||||||
res += blanks + 'return self.o.%s()\n' % getterName
|
res += blanks + 'return self.o.%s()\n' % getterName
|
||||||
|
|
|
@ -142,9 +142,14 @@ class ToolMixin(AbstractMixin):
|
||||||
else:
|
else:
|
||||||
# Create a FieldDescr instance
|
# Create a FieldDescr instance
|
||||||
appyType = anObject.getAppyType(fieldName)
|
appyType = anObject.getAppyType(fieldName)
|
||||||
atField = anObject.schema.get(fieldName)
|
if not appyType:
|
||||||
fieldDescr = FieldDescr(atField, appyType, None)
|
res.append({'atField': None, 'name': fieldName})
|
||||||
res.append(fieldDescr.get())
|
# The field name is wrong.
|
||||||
|
# We return it so we can show it in an error message.
|
||||||
|
else:
|
||||||
|
atField = anObject.schema.get(fieldName)
|
||||||
|
fieldDescr = FieldDescr(atField, appyType, None)
|
||||||
|
res.append(fieldDescr.get())
|
||||||
return res
|
return res
|
||||||
|
|
||||||
xhtmlToText = re.compile('<.*?>', re.S)
|
xhtmlToText = re.compile('<.*?>', re.S)
|
||||||
|
|
|
@ -654,7 +654,8 @@
|
||||||
<tal:comment replace="nothing">Columns corresponding to other fields</tal:comment>
|
<tal:comment replace="nothing">Columns corresponding to other fields</tal:comment>
|
||||||
<tal:otherFields repeat="fieldDescr fieldDescrs">
|
<tal:otherFields repeat="fieldDescr fieldDescrs">
|
||||||
<tal:standardField condition="python: fieldDescr != 'workflowState'">
|
<tal:standardField condition="python: fieldDescr != 'workflowState'">
|
||||||
<td tal:attributes="id python:'field_%s' % fieldDescr['atField'].getName()">
|
<td tal:condition="fieldDescr/atField"
|
||||||
|
tal:attributes="id python:'field_%s' % fieldDescr['atField'].getName()">
|
||||||
<tal:field define="contextObj python:obj;
|
<tal:field define="contextObj python:obj;
|
||||||
isEdit python:False;
|
isEdit python:False;
|
||||||
showLabel python:False;
|
showLabel python:False;
|
||||||
|
@ -663,6 +664,9 @@
|
||||||
<metal:field use-macro="here/<!macros!>/macros/showArchetypesField"/>
|
<metal:field use-macro="here/<!macros!>/macros/showArchetypesField"/>
|
||||||
</tal:field>
|
</tal:field>
|
||||||
</td>
|
</td>
|
||||||
|
<td tal:condition="not: fieldDescr/atField" style="color:red">Field
|
||||||
|
<span tal:replace="fieldDescr/name"/> not found.
|
||||||
|
</td>
|
||||||
</tal:standardField>
|
</tal:standardField>
|
||||||
<tal:workflowState condition="python: fieldDescr == 'workflowState'">
|
<tal:workflowState condition="python: fieldDescr == 'workflowState'">
|
||||||
<td id="field_workflow_state" i18n:translate="" tal:content="obj/getWorkflowLabel"></td>
|
<td id="field_workflow_state" i18n:translate="" tal:content="obj/getWorkflowLabel"></td>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
from appy.gen import *
|
from appy.gen import *
|
||||||
from appy.gen.plone25.wrappers import AbstractWrapper
|
from appy.gen.plone25.wrappers import AbstractWrapper, FileWrapper
|
||||||
from appy.gen.plone25.wrappers.ToolWrapper import ToolWrapper
|
from appy.gen.plone25.wrappers.ToolWrapper import ToolWrapper
|
||||||
from appy.gen.plone25.wrappers.FlavourWrapper import FlavourWrapper
|
from appy.gen.plone25.wrappers.FlavourWrapper import FlavourWrapper
|
||||||
from appy.gen.plone25.wrappers.PodTemplateWrapper import PodTemplateWrapper
|
from appy.gen.plone25.wrappers.PodTemplateWrapper import PodTemplateWrapper
|
||||||
|
|
|
@ -18,6 +18,7 @@ from OFS.Image import File
|
||||||
from DateTime import DateTime
|
from DateTime import DateTime
|
||||||
from Products.CMFCore.utils import getToolByName
|
from Products.CMFCore.utils import getToolByName
|
||||||
from Products.CMFPlone.PloneBatch import Batch
|
from Products.CMFPlone.PloneBatch import Batch
|
||||||
|
from OFS.Image import File
|
||||||
import logging
|
import logging
|
||||||
logger = logging.getLogger('<!applicationName!>')
|
logger = logging.getLogger('<!applicationName!>')
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,14 @@
|
||||||
developer the real classes used by the undelrying web framework.'''
|
developer the real classes used by the undelrying web framework.'''
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
import time
|
import time, os.path, mimetypes
|
||||||
|
from appy.gen.utils import sequenceTypes
|
||||||
|
from appy.shared.utils import getOsTempFolder
|
||||||
|
|
||||||
|
# Some error messages ----------------------------------------------------------
|
||||||
|
WRONG_FILE_TUPLE = 'This is not the way to set a file. You can specify a ' \
|
||||||
|
'2-tuple (fileName, fileContent) or a 3-tuple (fileName, fileContent, ' \
|
||||||
|
'mimeType).'
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
class AbstractWrapper:
|
class AbstractWrapper:
|
||||||
|
@ -10,8 +17,56 @@ class AbstractWrapper:
|
||||||
of this class.'''
|
of this class.'''
|
||||||
def __init__(self, o):
|
def __init__(self, o):
|
||||||
self.__dict__['o'] = 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):
|
||||||
|
exec "self.o.set%s%s(v)" % (name[0].upper(), name[1:])
|
||||||
|
elif isinstance(v, FileWrapper):
|
||||||
|
setattr(self, 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, 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, name, ploneFile)
|
||||||
def __setattr__(self, name, v):
|
def __setattr__(self, name, v):
|
||||||
exec "self.o.set%s%s(v)" % (name[0].upper(), name[1:])
|
appyType = self.o.getAppyType(name)
|
||||||
|
if not appyType and (name != 'title'):
|
||||||
|
raise 'Attribute "%s" does not exist.' % name
|
||||||
|
if appyType and (appyType['type'] == 'File'):
|
||||||
|
self._set_file_attribute(name, v)
|
||||||
|
else:
|
||||||
|
exec "self.o.set%s%s(v)" % (name[0].upper(), name[1:])
|
||||||
def __cmp__(self, other):
|
def __cmp__(self, other):
|
||||||
if other:
|
if other:
|
||||||
return cmp(self.o, other.o)
|
return cmp(self.o, other.o)
|
||||||
|
@ -20,6 +75,9 @@ class AbstractWrapper:
|
||||||
def get_tool(self):
|
def get_tool(self):
|
||||||
return self.o.getTool()._appy_getWrapper(force=True)
|
return self.o.getTool()._appy_getWrapper(force=True)
|
||||||
tool = property(get_tool)
|
tool = property(get_tool)
|
||||||
|
def get_flavour(self):
|
||||||
|
return self.o.getTool().getFlavour(self.o, appy=True)
|
||||||
|
flavour = property(get_flavour)
|
||||||
def get_session(self):
|
def get_session(self):
|
||||||
return self.o.REQUEST.SESSION
|
return self.o.REQUEST.SESSION
|
||||||
session = property(get_session)
|
session = property(get_session)
|
||||||
|
@ -61,21 +119,37 @@ class AbstractWrapper:
|
||||||
sortedRefField
|
sortedRefField
|
||||||
getattr(self.o, sortedRefField).append(obj.UID())
|
getattr(self.o, sortedRefField).append(obj.UID())
|
||||||
|
|
||||||
def create(self, fieldName, **kwargs):
|
def create(self, fieldNameOrClass, **kwargs):
|
||||||
'''This method allows to create an object and link it to the current
|
'''If p_fieldNameOfClass is the name of a field, this method allows to
|
||||||
one through reference field named p_fieldName.'''
|
create an object and link it to the current one (self) through
|
||||||
# Determine object id and portal type
|
reference field named p_fieldName.
|
||||||
portalType = self.o.getAppyRefPortalType(fieldName)
|
If p_fieldNameOrClass is a class from the gen-application, it must
|
||||||
|
correspond to a root class and this method allows to create a
|
||||||
|
root object in the application folder.'''
|
||||||
|
isField = isinstance(fieldNameOrClass, basestring)
|
||||||
|
# Determine the portal type of the object to create
|
||||||
|
if isField:
|
||||||
|
fieldName = fieldNameOrClass
|
||||||
|
idPrefix = fieldName
|
||||||
|
portalType = self.o.getAppyRefPortalType(fieldName)
|
||||||
|
else:
|
||||||
|
theClass = fieldNameOrClass
|
||||||
|
idPrefix = theClass.__name__
|
||||||
|
portalType = self.o._appy_getAtType(theClass, self.flavour.o)
|
||||||
|
# Determine object id
|
||||||
if kwargs.has_key('id'):
|
if kwargs.has_key('id'):
|
||||||
objId = kwargs['id']
|
objId = kwargs['id']
|
||||||
del kwargs['id']
|
del kwargs['id']
|
||||||
else:
|
else:
|
||||||
objId = '%s.%f' % (fieldName, time.time())
|
objId = '%s.%f' % (idPrefix, time.time())
|
||||||
# Where must I create te object?
|
# Where must I create the object?
|
||||||
if hasattr(self, 'folder') and self.folder:
|
if not isField:
|
||||||
folder = self.o
|
folder = self.o.getTool().getAppFolder()
|
||||||
else:
|
else:
|
||||||
folder = self.o.getParentNode()
|
if hasattr(self, 'folder') and self.folder:
|
||||||
|
folder = self.o
|
||||||
|
else:
|
||||||
|
folder = self.o.getParentNode()
|
||||||
# Create the object
|
# Create the object
|
||||||
folder.invokeFactory(portalType, objId)
|
folder.invokeFactory(portalType, objId)
|
||||||
ploneObj = getattr(folder, objId)
|
ploneObj = getattr(folder, objId)
|
||||||
|
@ -93,13 +167,15 @@ class AbstractWrapper:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
getattr(ploneObj, setterName)(attrValue)
|
getattr(ploneObj, setterName)(attrValue)
|
||||||
# Link the object to this one
|
if isField:
|
||||||
self.link(fieldName, ploneObj)
|
# Link the object to this one
|
||||||
|
self.link(fieldName, ploneObj)
|
||||||
|
self.o.reindexObject()
|
||||||
|
# Call custom initialization
|
||||||
try:
|
try:
|
||||||
appyObj.onEdit(True) # Call custom initialization
|
appyObj.onEdit(True)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
self.o.reindexObject()
|
|
||||||
ploneObj.reindexObject()
|
ploneObj.reindexObject()
|
||||||
return appyObj
|
return appyObj
|
||||||
|
|
||||||
|
@ -133,4 +209,49 @@ class AbstractWrapper:
|
||||||
self.o._v_appy_do = {'doAction': doAction, 'doNotify': doNotify}
|
self.o._v_appy_do = {'doAction': doAction, 'doNotify': doNotify}
|
||||||
wfTool.doActionFor(self.o, transitionName, comment=comment)
|
wfTool.doActionFor(self.o, transitionName, comment=comment)
|
||||||
del self.o._v_appy_do
|
del self.o._v_appy_do
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
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, atFile):
|
||||||
|
'''This constructor is only used by Appy to create a nice File instance
|
||||||
|
from a Plone/Zope corresponding instance (p_atFile). 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['_atFile'] = atFile # Not for you!
|
||||||
|
d['name'] = atFile.filename
|
||||||
|
d['content'] = atFile.data
|
||||||
|
d['mimeType'] = atFile.content_type
|
||||||
|
d['size'] = atFile.size # In bytes
|
||||||
|
|
||||||
|
def __setattr__(self, name, v):
|
||||||
|
d = self.__dict__
|
||||||
|
if name == 'name':
|
||||||
|
self._atFile.filename = v
|
||||||
|
d['name'] = v
|
||||||
|
elif name == 'content':
|
||||||
|
self._atFile.update_data(v, self.mimeType, len(v))
|
||||||
|
d['content'] = v
|
||||||
|
d['size'] = len(v)
|
||||||
|
elif name == 'mimeType':
|
||||||
|
self._atFile.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):
|
||||||
|
'''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 absoulte path name of the dumped file is returned.'''
|
||||||
|
if not filePath:
|
||||||
|
filePath = '%s/file%f.%s' % (getOsTempFolder(), time.time(),
|
||||||
|
self.name)
|
||||||
|
f = file(filePath, 'w')
|
||||||
|
f.write(self.content)
|
||||||
|
f.close()
|
||||||
|
return filePath
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -95,7 +95,7 @@ class BufferAction:
|
||||||
|
|
||||||
class IfAction(BufferAction):
|
class IfAction(BufferAction):
|
||||||
'''Action that determines if we must include the content of the buffer in
|
'''Action that determines if we must include the content of the buffer in
|
||||||
the result or not.'''
|
the result or not.'''
|
||||||
def do(self):
|
def do(self):
|
||||||
if self.exprResult:
|
if self.exprResult:
|
||||||
self.evaluateBuffer()
|
self.evaluateBuffer()
|
||||||
|
@ -122,7 +122,7 @@ class ElseAction(IfAction):
|
||||||
|
|
||||||
class ForAction(BufferAction):
|
class ForAction(BufferAction):
|
||||||
'''Actions that will include the content of the buffer as many times as
|
'''Actions that will include the content of the buffer as many times as
|
||||||
specified by the action parameters.'''
|
specified by the action parameters.'''
|
||||||
def __init__(self, name, buffer, expr, elem, minus, iter, source, fromExpr):
|
def __init__(self, name, buffer, expr, elem, minus, iter, source, fromExpr):
|
||||||
BufferAction.__init__(self, name, buffer, expr, elem, minus, source,
|
BufferAction.__init__(self, name, buffer, expr, elem, minus, source,
|
||||||
fromExpr)
|
fromExpr)
|
||||||
|
@ -202,4 +202,28 @@ class NullAction(BufferAction):
|
||||||
allows to insert in a buffer arbitrary odt content.'''
|
allows to insert in a buffer arbitrary odt content.'''
|
||||||
def do(self):
|
def do(self):
|
||||||
self.evaluateBuffer()
|
self.evaluateBuffer()
|
||||||
|
|
||||||
|
class VariableAction(BufferAction):
|
||||||
|
'''Action that allows to define a variable somewhere in the template.'''
|
||||||
|
def __init__(self, name, buffer, expr, elem, minus, varName, source,
|
||||||
|
fromExpr):
|
||||||
|
BufferAction.__init__(self, name, buffer, expr, elem, minus, source,
|
||||||
|
fromExpr)
|
||||||
|
self.varName = varName # Name of the variable
|
||||||
|
def do(self):
|
||||||
|
context = self.buffer.env.context
|
||||||
|
# Remember the variable hidden by our variable definition, if any
|
||||||
|
hasHiddenVariable = False
|
||||||
|
if context.has_key(self.varName):
|
||||||
|
hiddenVariable = context[self.varName]
|
||||||
|
hasHiddenVariable = True
|
||||||
|
# Add the variable to the context
|
||||||
|
context[self.varName] = self.exprResult
|
||||||
|
# Evaluate the buffer
|
||||||
|
self.evaluateBuffer()
|
||||||
|
# Restore hidden variable if any
|
||||||
|
if hasHiddenVariable:
|
||||||
|
context[self.varName] = hiddenVariable
|
||||||
|
else:
|
||||||
|
del context[self.varName]
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -21,7 +21,8 @@ import re
|
||||||
|
|
||||||
from appy.pod import PodError, XML_SPECIAL_CHARS
|
from appy.pod import PodError, XML_SPECIAL_CHARS
|
||||||
from appy.pod.elements import *
|
from appy.pod.elements import *
|
||||||
from appy.pod.actions import IfAction, ElseAction, ForAction, NullAction
|
from appy.pod.actions import IfAction, ElseAction, ForAction, VariableAction, \
|
||||||
|
NullAction
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
class ParsingError(Exception): pass
|
class ParsingError(Exception): pass
|
||||||
|
@ -62,6 +63,12 @@ ELSE_WITHOUT_IF = 'No previous "if" statement could be found for this "else" ' \
|
||||||
ELSE_WITHOUT_NAMED_IF = 'I could not find an "if" statement named "%s".'
|
ELSE_WITHOUT_NAMED_IF = 'I could not find an "if" statement named "%s".'
|
||||||
BAD_FOR_EXPRESSION = 'Bad "for" expression "%s". A "for" expression ' + \
|
BAD_FOR_EXPRESSION = 'Bad "for" expression "%s". A "for" expression ' + \
|
||||||
FOR_EXPRESSION
|
FOR_EXPRESSION
|
||||||
|
BAD_VAR_EXPRESSION = 'Bad variable definition "%s". A variable definition ' \
|
||||||
|
'must have the form {name} = {expression}. {name} must be a Python-' \
|
||||||
|
'compliant variable name. {expression} is a Python expression. When ' \
|
||||||
|
'encountering such a statement, pod will define, in the specified part ' \
|
||||||
|
'of the document, a variable {name} whose value will be the evaluated ' \
|
||||||
|
'{expression}.'
|
||||||
EVAL_EXPR_ERROR = 'Error while evaluating expression "%s". %s'
|
EVAL_EXPR_ERROR = 'Error while evaluating expression "%s". %s'
|
||||||
NULL_ACTION_ERROR = 'There was a problem with this action. Possible causes: ' \
|
NULL_ACTION_ERROR = 'There was a problem with this action. Possible causes: ' \
|
||||||
'(1) you specified no action (ie "do text") while not ' \
|
'(1) you specified no action (ie "do text") while not ' \
|
||||||
|
@ -168,8 +175,9 @@ class FileBuffer(Buffer):
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
class MemoryBuffer(Buffer):
|
class MemoryBuffer(Buffer):
|
||||||
actionRex = re.compile('(?:(\w+)\s*\:\s*)?do\s+(\w+)(-)?' \
|
actionRex = re.compile('(?:(\w+)\s*\:\s*)?do\s+(\w+)(-)?' \
|
||||||
'(?:\s+(for|if|else)\s*(.*))?')
|
'(?:\s+(for|if|else|with)\s*(.*))?')
|
||||||
forRex = re.compile('\s*([\w\-_]+)\s+in\s+(.*)')
|
forRex = re.compile('\s*([\w\-_]+)\s+in\s+(.*)')
|
||||||
|
varRex = re.compile('\s*([\w\-_]+)\s+=\s+(.*)')
|
||||||
def __init__(self, env, parent):
|
def __init__(self, env, parent):
|
||||||
Buffer.__init__(self, env, parent)
|
Buffer.__init__(self, env, parent)
|
||||||
self.content = u''
|
self.content = u''
|
||||||
|
@ -338,6 +346,13 @@ class MemoryBuffer(Buffer):
|
||||||
iter, subExpr = forRes.groups()
|
iter, subExpr = forRes.groups()
|
||||||
self.action = ForAction(statementName, self, subExpr, podElem,
|
self.action = ForAction(statementName, self, subExpr, podElem,
|
||||||
minus, iter, source, fromClause)
|
minus, iter, source, fromClause)
|
||||||
|
elif actionType == 'with':
|
||||||
|
varRes = MemoryBuffer.varRex.match(subExpr.strip())
|
||||||
|
if not varRes:
|
||||||
|
raise ParsingError(BAD_VAR_EXPRESSION % subExpr)
|
||||||
|
varName, subExpr = varRes.groups()
|
||||||
|
self.action = VariableAction(statementName, self, subExpr,
|
||||||
|
podElem, minus, varName, source, fromClause)
|
||||||
else: # null action
|
else: # null action
|
||||||
if not fromClause:
|
if not fromClause:
|
||||||
raise ParsingError(NULL_ACTION_ERROR)
|
raise ParsingError(NULL_ACTION_ERROR)
|
||||||
|
|
|
@ -48,4 +48,7 @@ class OdfEnvironment(XmlEnvironment):
|
||||||
|
|
||||||
class OdfParser(XmlParser):
|
class OdfParser(XmlParser):
|
||||||
'''XML parser that is specific for parsing ODF files.'''
|
'''XML parser that is specific for parsing ODF files.'''
|
||||||
|
def __init__(self, env=None, caller=None):
|
||||||
|
if not env: env = OdfEnvironment()
|
||||||
|
XmlParser.__init__(self, env, caller)
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -285,7 +285,7 @@ class Renderer:
|
||||||
except OSError, oe:
|
except OSError, oe:
|
||||||
raise PodError(CANT_WRITE_RESULT % (self.result, oe))
|
raise PodError(CANT_WRITE_RESULT % (self.result, oe))
|
||||||
except IOError, ie:
|
except IOError, ie:
|
||||||
raise PodError(CANT_WRITE_RESULT % (self.result, oe))
|
raise PodError(CANT_WRITE_RESULT % (self.result, ie))
|
||||||
self.result = os.path.abspath(self.result)
|
self.result = os.path.abspath(self.result)
|
||||||
os.remove(self.result)
|
os.remove(self.result)
|
||||||
# Check that temp folder does not exist
|
# Check that temp folder does not exist
|
||||||
|
|
3082
pod/test/Tests.rtf
3082
pod/test/Tests.rtf
File diff suppressed because it is too large
Load diff
2
pod/test/contexts/VarStatements.py
Executable file
2
pod/test/contexts/VarStatements.py
Executable file
|
@ -0,0 +1,2 @@
|
||||||
|
var1 = 'VAR1 not overridden'
|
||||||
|
var2 = 'VAR2 not overridden'
|
BIN
pod/test/results/varDef.odt
Normal file
BIN
pod/test/results/varDef.odt
Normal file
Binary file not shown.
BIN
pod/test/templates/VarStatements.odt
Executable file
BIN
pod/test/templates/VarStatements.odt
Executable file
Binary file not shown.
|
@ -46,3 +46,15 @@ class Traceback:
|
||||||
get = staticmethod(get)
|
get = staticmethod(get)
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
def getOsTempFolder():
|
||||||
|
tmp = '/tmp'
|
||||||
|
if os.path.exists(tmp) and os.path.isdir(tmp):
|
||||||
|
res = tmp
|
||||||
|
elif os.environ.has_key('TMP'):
|
||||||
|
res = os.environ['TMP']
|
||||||
|
elif os.environ.has_key('TEMP'):
|
||||||
|
res = os.environ['TEMP']
|
||||||
|
else:
|
||||||
|
raise "Sorry, I can't find a temp folder on your machine."
|
||||||
|
return res
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -85,11 +85,12 @@ class XmlParser(ContentHandler, ErrorHandler):
|
||||||
'''Basic XML content handler that does things like :
|
'''Basic XML content handler that does things like :
|
||||||
- remembering the currently parsed element;
|
- remembering the currently parsed element;
|
||||||
- managing namespace declarations.'''
|
- managing namespace declarations.'''
|
||||||
def __init__(self, env, caller=None):
|
def __init__(self, env=None, caller=None):
|
||||||
'''p_env should be an instance of a class that inherits from
|
'''p_env should be an instance of a class that inherits from
|
||||||
XmlEnvironment: it specifies the environment to use for this SAX
|
XmlEnvironment: it specifies the environment to use for this SAX
|
||||||
parser.'''
|
parser.'''
|
||||||
ContentHandler.__init__(self)
|
ContentHandler.__init__(self)
|
||||||
|
if not env: env = XmlEnvironment()
|
||||||
self.env = env
|
self.env = env
|
||||||
self.env.parser = self
|
self.env.parser = self
|
||||||
self.caller = caller # The class calling this parser
|
self.caller = caller # The class calling this parser
|
||||||
|
|
Binary file not shown.
Loading…
Reference in a new issue