Gaetan Delannay 2009-07-10 15:01:50 +02:00
parent ec17f900a6
commit 10eea7d735
19 changed files with 1770 additions and 1644 deletions

View file

@ -226,12 +226,6 @@ class Text2Html:
self.txtFile.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):
title = 'Versions'
firstChar = 0
@ -482,7 +476,6 @@ class Publisher:
f.write('verbose = "%s"' % self.versionLong)
f.close()
# Remove unwanted files
os.remove('%s/todo.txt' % self.genFolder)
os.remove('%s/version.txt' % self.genFolder)
os.remove('%s/license.txt' % self.genFolder)
os.remove('%s/template.html' % self.genFolder)
@ -492,10 +485,7 @@ class Publisher:
for dirName in dirs:
if dirName == '.svn':
FolderDeleter.delete(os.path.join(root, dirName))
# Generates the "to do" and "versions" pages, based on todo.txt and
# version.txt
TodoConverter('%s/doc/todo.txt' % appyPath,
'%s/todo.html' % self.genFolder).run()
# Generates the "versions" page, based on version.txt
VersionsConverter('%s/doc/version.txt' % appyPath,
'%s/version.html' % self.genFolder).run()

View file

@ -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)

View file

@ -408,9 +408,11 @@ class Generator(AbstractGenerator):
self.copyFile('workflows.py', repls, destFolder='Extensions')
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
blanks = ' '*8
getterName = 'get%s%s' % (attrName[0].upper(), attrName[1:])
if isinstance(appyType, Ref):
res += blanks + 'return self.o._appy_getRefs("%s", ' \
'noListIfSingleObj=True)\n' % attrName
@ -418,8 +420,11 @@ class Generator(AbstractGenerator):
res += blanks + 'appyType = getattr(self.klass, "%s")\n' % attrName
res += blanks + 'return self.o.getComputedValue(' \
'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:
getterName = 'get%s%s' % (attrName[0].upper(), attrName[1:])
if attrName in ArchetypeFieldDescriptor.specialParams:
getterName = attrName.capitalize()
res += blanks + 'return self.o.%s()\n' % getterName

View file

@ -142,9 +142,14 @@ class ToolMixin(AbstractMixin):
else:
# Create a FieldDescr instance
appyType = anObject.getAppyType(fieldName)
atField = anObject.schema.get(fieldName)
fieldDescr = FieldDescr(atField, appyType, None)
res.append(fieldDescr.get())
if not appyType:
res.append({'atField': None, 'name': fieldName})
# 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
xhtmlToText = re.compile('<.*?>', re.S)

View file

@ -654,7 +654,8 @@
<tal:comment replace="nothing">Columns corresponding to other fields</tal:comment>
<tal:otherFields repeat="fieldDescr fieldDescrs">
<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;
isEdit python:False;
showLabel python:False;
@ -663,6 +664,9 @@
<metal:field use-macro="here/<!macros!>/macros/showArchetypesField"/>
</tal:field>
</td>
<td tal:condition="not: fieldDescr/atField" style="color:red">Field
<span tal:replace="fieldDescr/name"/> not found.
</td>
</tal:standardField>
<tal:workflowState condition="python: fieldDescr == 'workflowState'">
<td id="field_workflow_state" i18n:translate="" tal:content="obj/getWorkflowLabel"></td>

View file

@ -1,6 +1,6 @@
# ------------------------------------------------------------------------------
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.FlavourWrapper import FlavourWrapper
from appy.gen.plone25.wrappers.PodTemplateWrapper import PodTemplateWrapper

View file

@ -18,6 +18,7 @@ from OFS.Image import File
from DateTime import DateTime
from Products.CMFCore.utils import getToolByName
from Products.CMFPlone.PloneBatch import Batch
from OFS.Image import File
import logging
logger = logging.getLogger('<!applicationName!>')

View file

@ -2,7 +2,14 @@
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:
@ -10,8 +17,56 @@ class AbstractWrapper:
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):
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):
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):
if other:
return cmp(self.o, other.o)
@ -20,6 +75,9 @@ class AbstractWrapper:
def get_tool(self):
return self.o.getTool()._appy_getWrapper(force=True)
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):
return self.o.REQUEST.SESSION
session = property(get_session)
@ -61,21 +119,37 @@ class AbstractWrapper:
sortedRefField
getattr(self.o, sortedRefField).append(obj.UID())
def create(self, fieldName, **kwargs):
'''This method allows to create an object and link it to the current
one through reference field named p_fieldName.'''
# Determine object id and portal type
portalType = self.o.getAppyRefPortalType(fieldName)
def create(self, fieldNameOrClass, **kwargs):
'''If p_fieldNameOfClass is the name of a field, this method allows to
create an object and link it to the current one (self) through
reference field named p_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'):
objId = kwargs['id']
del kwargs['id']
else:
objId = '%s.%f' % (fieldName, time.time())
# Where must I create te object?
if hasattr(self, 'folder') and self.folder:
folder = self.o
objId = '%s.%f' % (idPrefix, time.time())
# Where must I create the object?
if not isField:
folder = self.o.getTool().getAppFolder()
else:
folder = self.o.getParentNode()
if hasattr(self, 'folder') and self.folder:
folder = self.o
else:
folder = self.o.getParentNode()
# Create the object
folder.invokeFactory(portalType, objId)
ploneObj = getattr(folder, objId)
@ -93,13 +167,15 @@ class AbstractWrapper:
pass
else:
getattr(ploneObj, setterName)(attrValue)
# Link the object to this one
self.link(fieldName, ploneObj)
if isField:
# Link the object to this one
self.link(fieldName, ploneObj)
self.o.reindexObject()
# Call custom initialization
try:
appyObj.onEdit(True) # Call custom initialization
appyObj.onEdit(True)
except AttributeError:
pass
self.o.reindexObject()
ploneObj.reindexObject()
return appyObj
@ -133,4 +209,49 @@ class AbstractWrapper:
self.o._v_appy_do = {'doAction': doAction, 'doNotify': doNotify}
wfTool.doActionFor(self.o, transitionName, comment=comment)
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
# ------------------------------------------------------------------------------

View file

@ -95,7 +95,7 @@ class BufferAction:
class IfAction(BufferAction):
'''Action that determines if we must include the content of the buffer in
the result or not.'''
the result or not.'''
def do(self):
if self.exprResult:
self.evaluateBuffer()
@ -122,7 +122,7 @@ class ElseAction(IfAction):
class ForAction(BufferAction):
'''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):
BufferAction.__init__(self, name, buffer, expr, elem, minus, source,
fromExpr)
@ -202,4 +202,28 @@ class NullAction(BufferAction):
allows to insert in a buffer arbitrary odt content.'''
def do(self):
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]
# ------------------------------------------------------------------------------

View file

@ -21,7 +21,8 @@ import re
from appy.pod import PodError, XML_SPECIAL_CHARS
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
@ -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".'
BAD_FOR_EXPRESSION = 'Bad "for" expression "%s". A "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'
NULL_ACTION_ERROR = 'There was a problem with this action. Possible causes: ' \
'(1) you specified no action (ie "do text") while not ' \
@ -168,8 +175,9 @@ class FileBuffer(Buffer):
# ------------------------------------------------------------------------------
class MemoryBuffer(Buffer):
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+(.*)')
varRex = re.compile('\s*([\w\-_]+)\s+=\s+(.*)')
def __init__(self, env, parent):
Buffer.__init__(self, env, parent)
self.content = u''
@ -338,6 +346,13 @@ class MemoryBuffer(Buffer):
iter, subExpr = forRes.groups()
self.action = ForAction(statementName, self, subExpr, podElem,
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
if not fromClause:
raise ParsingError(NULL_ACTION_ERROR)

View file

@ -48,4 +48,7 @@ class OdfEnvironment(XmlEnvironment):
class OdfParser(XmlParser):
'''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)
# ------------------------------------------------------------------------------

View file

@ -285,7 +285,7 @@ class Renderer:
except OSError, oe:
raise PodError(CANT_WRITE_RESULT % (self.result, oe))
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)
os.remove(self.result)
# Check that temp folder does not exist

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,2 @@
var1 = 'VAR1 not overridden'
var2 = 'VAR2 not overridden'

BIN
pod/test/results/varDef.odt Normal file

Binary file not shown.

Binary file not shown.

View file

@ -46,3 +46,15 @@ class Traceback:
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
# ------------------------------------------------------------------------------

View file

@ -85,11 +85,12 @@ class XmlParser(ContentHandler, ErrorHandler):
'''Basic XML content handler that does things like :
- remembering the currently parsed element;
- 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
XmlEnvironment: it specifies the environment to use for this SAX
parser.'''
ContentHandler.__init__(self)
if not env: env = XmlEnvironment()
self.env = env
self.env.parser = self
self.caller = caller # The class calling this parser

Binary file not shown.