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
					
				
					 19 changed files with 1770 additions and 1644 deletions
				
			
		|  | @ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Gaetan Delannay
						Gaetan Delannay