Added a new system for layouting production-ready forms without any HTML coding, many performance improvements and more independence towards Archetypes.

This commit is contained in:
Gaetan Delannay 2010-08-05 18:23:17 +02:00
parent 309ea921fa
commit bfd2357f69
84 changed files with 4663 additions and 3549 deletions

View file

@ -1,17 +1,21 @@
'''job.py must be executed by a "zopectl run" command and, as single arg,
must get a string with the following format:
<ZopeAdmin><PloneInstancePath>:<ApplicationName>:<ToolMethodName>[:<args>].
<ZopeAdmin>:<PloneInstancePath>:<ApplicationName>:<ToolMethodName>[:<args>].
<ZopeAdmin> is the userName of the Zope administrator for this instance.
<PloneInstancePath> is the path, within Zope, to the Plone Site object (if
not at the root of the Zope hierarchy, use '/' as
folder separator);
<ApplicationName> is the name of the Appy application;
<ApplicationName> is the name of the Appy application. If it begins with
"path=", it does not represent an Appy application, but
the path, within <PloneInstancePath>, to any Zope object
(use '/' as folder separator)
<ToolMethodName> is the name of the method to call on the tool in this
Appy application;
Appy application, or the method to call on the arbitrary
Zope object if previous param starts with "path=".
<args> (optional) are the arguments to give to this method (only strings
are supported). Several arguments must be separated by '*'.'''
@ -43,18 +47,24 @@ else:
from AccessControl.SecurityManagement import newSecurityManager
user = app.acl_users.getUserById(zopeUser)
if not hasattr(user, 'aq_base'):
user = user.__of__(uf)
user = user.__of__(app.acl_users)
newSecurityManager(None, user)
# Get the Plone site
ploneSite = app # Initialised with the Zope root object.
for elem in plonePath.split('/'):
ploneSite = getattr(ploneSite, elem)
# Get the tool corresponding to the Appy application
toolName = 'portal_%s' % appName.lower()
tool = getattr(ploneSite, toolName).appy()
# Execute the method on the tool
# If we are in a Appy application, the object on which we will call the
# method is the tool within this application.
if not appName.startswith('path='):
objectName = 'portal_%s' % appName.lower()
targetObject = getattr(ploneSite, objectName).appy()
else:
# It can be any object within the Plone site.
targetObject = ploneSite
for elem in appName[5:].split('/'):
targetObject = getattr(targetObject, elem)
# Execute the method on the target object
if args: args = args.split('*')
exec 'tool.%s(*args)' % toolMethod
exec 'targetObject.%s(*args)' % toolMethod
transaction.commit()
# ------------------------------------------------------------------------------

View file

@ -1 +1 @@
0.5.5
0.6.0

File diff suppressed because it is too large Load diff

View file

@ -4,10 +4,13 @@ from appy.gen import State, Transition, Type
# ------------------------------------------------------------------------------
class Descriptor: # Abstract
def __init__(self, klass, orderedAttributes, generator):
self.klass = klass # The corresponding Python class
self.orderedAttributes = orderedAttributes # Names of the static appy-
# compliant attributes declared in self.klass
self.generator = generator # A reference to the code generator.
# The corresponding Python class
self.klass = klass
# The names of the static appy-compliant attributes declared in
# self.klass
self.orderedAttributes = orderedAttributes
# A reference to the code generator.
self.generator = generator
def __repr__(self): return '<Class %s>' % self.klass.__name__
@ -19,14 +22,22 @@ class ClassDescriptor(Descriptor):
the condition will be returned. p_condition must be a string
containing an expression that will be evaluated with, in its context,
"self" being this ClassDescriptor and "attrValue" being the current
Type instance.'''
res = []
Type instance.
Order of returned attributes already takes into account type's
"move" attributes.'''
attrs = []
# First, get the attributes for the current class
for attrName in self.orderedAttributes:
try:
attrValue = getattr(self.klass, attrName)
hookClass = self.klass
except AttributeError:
attrValue = getattr(self.modelClass, attrName)
hookClass = self.modelClass
if isinstance(attrValue, Type):
if not condition or eval(condition):
res.append( (attrName, attrValue) )
attrs.append( (attrName, attrValue, hookClass) )
# Then, add attributes from parent classes
for baseClass in self.klass.__bases__:
# Find the classDescr that corresponds to baseClass
@ -36,7 +47,17 @@ class ClassDescriptor(Descriptor):
baseClassDescr = classDescr
break
if baseClassDescr:
res = baseClassDescr.getOrderedAppyAttributes() + res
attrs = baseClassDescr.getOrderedAppyAttributes() + attrs
# Modify attributes order by using "move" attributes
res = []
for name, appyType, klass in attrs:
if appyType.move:
newPosition = len(res) - abs(appyType.move)
if newPosition <= 0:
newPosition = 0
res.insert(newPosition, (name, appyType, klass))
else:
res.append((name, appyType, klass))
return res
def getChildren(self):
@ -51,7 +72,7 @@ class ClassDescriptor(Descriptor):
def getPhases(self):
'''Gets the phases defined on fields of this class.'''
res = []
for fieldName, appyType in self.getOrderedAppyAttributes():
for fieldName, appyType, klass in self.getOrderedAppyAttributes():
if appyType.phase not in res:
res.append(appyType.phase)
return res

View file

@ -131,14 +131,9 @@ class Generator:
if not os.path.exists(self.templatesFolder):
print WARN_NO_TEMPLATE % self.templatesFolder
# Default descriptor classes
self.classDescriptor = ClassDescriptor
self.workflowDescriptor = WorkflowDescriptor
self.customToolClassDescriptor = ClassDescriptor
self.customFlavourClassDescriptor = ClassDescriptor
# Custom tool and flavour classes, if they are defined in the
# application
self.customToolDescr = None
self.customFlavourDescr = None
self.descriptorClasses = {
'class': ClassDescriptor, 'tool': ClassDescriptor,
'flavour': ClassDescriptor, 'workflow': WorkflowDescriptor}
# The following dict contains a series of replacements that need to be
# applied to file templates to generate files.
self.repls = {'applicationName': self.applicationName,
@ -146,6 +141,8 @@ class Generator:
'codeHeader': CODE_HEADER}
# List of Appy classes and workflows found in the application
self.classes = []
self.tool = None
self.flavour = None
self.workflows = []
self.initialize()
self.config = Config.getDefault()
@ -225,24 +222,30 @@ class Generator:
# of their definition).
attrs = astClasses[moduleElem.__name__].attributes
if appyType == 'class':
# Determine the class type (standard, tool, flavour...)
if issubclass(moduleElem, Tool):
descrClass = self.customToolClassDescriptor
self.customToolDescr = descrClass(
moduleElem, attrs, self)
elif issubclass(moduleElem, Flavour):
descrClass = self.customFlavourClassDescriptor
self.customFlavourDescr = descrClass(
moduleElem, attrs, self)
if not self.tool:
klass = self.descriptorClasses['tool']
self.tool = klass(moduleElem, attrs, self)
else:
descrClass = self.classDescriptor
self.classes.append(
descrClass(moduleElem, attrs, self))
self.tool.update(moduleElem, attrs)
elif issubclass(moduleElem, Flavour):
if not self.flavour:
klass = self.descriptorClasses['flavour']
self.flavour = klass(moduleElem, attrs, self)
else:
self.flavour.update(moduleElem, attrs)
else:
descriptorClass = self.descriptorClasses['class']
descriptor = descriptorClass(moduleElem,attrs, self)
self.classes.append(descriptor)
# Manage classes containing tests
if self.containsTests(moduleElem):
self.modulesWithTests.add(moduleObj.__name__)
elif appyType == 'workflow':
descrClass = self.workflowDescriptor
self.workflows.append(
descrClass(moduleElem, attrs, self))
descriptorClass = self.descriptorClasses['workflow']
descriptor = descriptorClass(moduleElem, attrs, self)
self.workflows.append(descriptor)
if self.containsTests(moduleElem):
self.modulesWithTests.add(moduleObj.__name__)
elif isinstance(moduleElem, Config):
@ -331,8 +334,8 @@ class Generator:
def run(self):
self.walkApplication()
for classDescr in self.classes: self.generateClass(classDescr)
for wfDescr in self.workflows: self.generateWorkflow(wfDescr)
for descriptor in self.classes: self.generateClass(descriptor)
for descriptor in self.workflows: self.generateWorkflow(descriptor)
self.finalize()
msg = ''
if self.totalNumberOfTests:

188
gen/layout.py Normal file
View file

@ -0,0 +1,188 @@
'''This module contains classes used for layouting graphical elements
(fields, widgets, groups, ...).'''
# A layout defines how a given field is rendered in a given context. Several
# contexts exist:
# "view" represents a given page for a given Appy class, in read-only mode.
# "edit" represents a given page for a given Appy class, in edit mode.
# "cell" represents a cell in a table, like when we need to render a field
# value in a query result or in a reference table.
# Layout elements for a class or page ------------------------------------------
# s - The page summary, containing summarized information about the page or
# class, workflow information and object history.
# w - The widgets of the current page/class
# n - The navigation panel (inter-objects navigation)
# b - The range of buttons (intra-object navigation, save, edit, delete...)
# m - The global status message sometimes shown.
# Layout elements for a field --------------------------------------------------
# l - "label" The field label
# d - "description" The field description (a description is always visible)
# h - "help" Help for the field (typically rendered as an icon,
# clicking on it shows a popup with online help
# v - "validation" The icon that is shown when a validation error occurs
# (typically only used on "edit" layouts)
# r - "required" The icon that specified that the field is required (if
# relevant; typically only used on "edit" layouts)
# f - "field" The field value, or input for entering a value.
# For every field of a Appy class, you can define, for every layout context,
# what field-related information will appear, and how it will be rendered.
# Variables defaultPageLayouts and defaultFieldLayouts defined below give the
# default layouts for pages and fields respectively.
#
# How to express a layout? You simply define a string that is made of the
# letters corresponding to the field elements you want to render. The order of
# elements correspond to the order into which they will be rendered.
# ------------------------------------------------------------------------------
rowDelimiters = {'-':'middle', '=':'top', '_':'bottom'}
cellDelimiters = {'|': 'center', ';': 'left', '!': 'right'}
macroDict = {
# Page-related elements
's': ('page', 'header'), 'w': ('page', 'widgets'),
'n': ('navigate', 'objectNavigate'), 'b': ('page', 'buttons'),
'm': ('page', 'message'),
# Field-related elements
'l': ('show', 'label'), 'd': ('show', 'description'),
'h': ('show', 'help'), 'v': ('show', 'validation'),
'r': ('show', 'required')
}
# ------------------------------------------------------------------------------
class LayoutElement:
'''Abstract base class for any layout element.'''
def get(self): return self.__dict__
class Cell(LayoutElement):
'''Represents a cell in a row in a table.'''
def __init__(self, content, align, isHeader=False):
self.align = align
self.width = None
self.content = None
self.colspan = 1
if isHeader:
self.width = content
else:
self.content = [] # The list of widgets to render in the cell
self.decodeContent(content)
def decodeContent(self, content):
digits = '' # We collect the digits that will give the colspan
for char in content:
if char.isdigit():
digits += char
else:
# It is a letter corresponding to a macro
if char in macroDict:
self.content.append(macroDict[char])
elif char == 'f':
# The exact macro to call will be known at render-time
self.content.append('?')
# Manage the colspan
if digits:
self.colspan = int(digits)
# ------------------------------------------------------------------------------
class Row(LayoutElement):
'''Represents a row in a table.'''
def __init__(self, content, valign, isHeader=False):
self.valign = valign
self.cells = []
self.decodeCells(content, isHeader)
# Compute the row length
length = 0
for cell in self.cells:
length += cell['colspan']
self.length = length
def decodeCells(self, content, isHeader):
'''Decodes the given chunk of layout string p_content containing
column-related information (if p_isHeader is True) or cell content
(if p_isHeader is False) and produces a list of Cell instances.'''
cellContent = ''
for char in content:
if char in cellDelimiters:
align = cellDelimiters[char]
self.cells.append(Cell(cellContent, align, isHeader).get())
cellContent = ''
else:
cellContent += char
# Manage the last cell if any
if cellContent:
self.cells.append(Cell(cellContent, 'left', isHeader).get())
# ------------------------------------------------------------------------------
class Table(LayoutElement):
'''Represents a table where to dispose graphical elements.'''
def __init__(self, layoutString, style=None, css_class='', cellpadding=0,
cellspacing=0, width='100%', align='left'):
self.style = style
self.css_class = css_class
self.cellpadding = cellpadding
self.cellspacing = cellspacing
self.width = width
self.align = align
# The following attribute will store a special Row instance used for
# defining column properties.
self.headerRow = None
# The content rows are stored hereafter.
self.rows = []
self.layoutString = layoutString
self.decodeRows(layoutString)
def addCssClasses(self, css_class):
'''Adds a single or a group of p_css_class.'''
classes = self.css_class
if classes == None:
classes = ''
if not classes:
self.css_class = css_class
else:
self.css_class += ' ' + css_classes
# Ensures that every class appears once
self.css_class = ' '.join(set(self.css_class.split()))
def isHeaderRow(self, rowContent):
'''Determines if p_rowContent specified the table header row or a
content row.'''
# Find the first char that is a number or a letter
for char in rowContent:
if char not in cellDelimiters:
if char.isdigit(): return True
else: return False
return True
def decodeRows(self, layoutString):
'''Decodes the given p_layoutString and produces a list of Row
instances.'''
# Split the p_layoutString with the row delimiters
rowContent = ''
for char in layoutString:
if char in rowDelimiters:
valign = rowDelimiters[char]
if self.isHeaderRow(rowContent):
self.headerRow = Row(rowContent,valign,isHeader=True).get()
else:
self.rows.append(Row(rowContent, valign).get())
rowContent = ''
else:
rowContent += char
# Manage the last row if any
if rowContent:
self.rows.append(Row(rowContent, 'middle').get())
def removeElement(self, elem):
'''Removes given p_elem from myself.'''
macroToRemove = macroDict[elem]
for row in self.rows:
for cell in row['cells']:
if macroToRemove in cell['content']:
cell['content'].remove(macroToRemove)
# ------------------------------------------------------------------------------
defaultPageLayouts = {
'view': Table('m;-s|-n!-w;-b|'), 'edit': Table('m;-s|-n!-w;-b|')}
defaultFieldLayouts = {'view': 'l;f!', 'edit': 'lrv;f!'}
# ------------------------------------------------------------------------------

View file

@ -46,7 +46,7 @@ class Generator(AbstractGenerator):
into the ODT file. This method returns the list of "dumpable"
fields.'''
res = []
for fieldName, field in classDescr.getOrderedAppyAttributes():
for fieldName, field, klass in classDescr.getOrderedAppyAttributes():
if (field.type not in self.undumpable) and \
(not self.fieldIsStaticallyInvisible(field)):
res.append((fieldName, field))

View file

@ -18,7 +18,7 @@ from appy.gen.utils import GroupDescr, PageDescr, produceNiceMessage, \
TABS = 4 # Number of blanks in a Python indentation.
# ------------------------------------------------------------------------------
class ArchetypeFieldDescriptor:
class FieldDescriptor:
'''This class allows to gather information needed to generate an Archetypes
definition (field + widget) from an Appy type. An Appy type is used for
defining the type of attributes defined in the user application.'''
@ -39,7 +39,6 @@ class ArchetypeFieldDescriptor:
self.widgetParams = {}
self.fieldType = None
self.widgetType = None
self.walkAppyType()
def __repr__(self):
return '<Field %s, %s>' % (self.fieldName, self.classDescr)
@ -59,8 +58,8 @@ class ArchetypeFieldDescriptor:
return res
def produceMessage(self, msgId, isLabel=True):
'''Gets the default label or description (if p_isLabel is False) for
i18n message p_msgId.'''
'''Gets the default label, description or help (depending on p_msgType)
for i18n message p_msgId.'''
default = ' '
produceNice = False
if isLabel:
@ -75,82 +74,22 @@ class ArchetypeFieldDescriptor:
msg.produceNiceDefault()
return msg
def walkBasicType(self):
'''How to dump a basic type?'''
self.fieldType = '%sField' % self.appyType.type
self.widgetType = "%sWidget" % self.appyType.type
if self.appyType.type == 'Date':
self.fieldType = 'DateTimeField'
self.widgetType = 'CalendarWidget'
if self.appyType.format == Date.WITHOUT_HOUR:
self.widgetParams['show_hm'] = False
self.widgetParams['starting_year'] = self.appyType.startYear
self.widgetParams['ending_year'] = self.appyType.endYear
elif self.appyType.type == 'Float':
self.widgetType = 'DecimalWidget'
elif self.appyType.type == 'File':
if self.appyType.isImage:
self.fieldType = 'ImageField'
self.widgetType = 'ImageWidget'
self.fieldParams['storage'] = 'python:AttributeStorage()'
def walkString(self):
'''How to generate an Appy String?'''
if self.appyType.format == String.LINE:
if self.appyType.isSelection():
if self.appyType.isMultiValued():
self.fieldType = 'LinesField'
self.widgetType = 'MultiSelectionWidget'
self.fieldParams['multiValued'] = True
if (type(self.appyType.validator) in sequenceTypes) and \
len(self.appyType.validator) <= 5:
self.widgetParams['format'] = 'checkbox'
else:
self.fieldType = 'StringField'
self.widgetType = 'SelectionWidget'
self.widgetParams['format'] = 'select'
# Elements common to all selection fields
methodName = 'list_%s_values' % self.fieldName
self.fieldParams['vocabulary'] = methodName
self.classDescr.addSelectMethod(methodName, self)
self.fieldParams['enforceVocabulary'] = True
else:
self.fieldType = 'StringField'
self.widgetType = 'StringWidget'
self.widgetParams['size'] = 50
if self.appyType.width:
self.widgetParams['size'] = self.appyType.width
elif self.appyType.format == String.TEXT:
self.fieldType = 'TextField'
self.widgetType = 'TextAreaWidget'
if self.appyType.height:
self.widgetParams['rows'] = self.appyType.height
elif self.appyType.format == String.XHTML:
self.fieldType = 'TextField'
self.widgetType = 'RichWidget'
self.fieldParams['allowable_content_types'] = ('text/html',)
self.fieldParams['default_output_type'] = "text/html"
elif self.appyType.format == String.PASSWORD:
self.fieldType = 'StringField'
self.widgetType = 'PasswordWidget'
if self.appyType.width:
self.widgetParams['size'] = self.appyType.width
def walkComputed(self):
'''How to generate a computed field? We generate an Archetypes String
field.'''
self.fieldType = 'StringField'
self.widgetType = 'StringWidget'
self.widgetParams['visible'] = False # Archetypes will believe the
# field is invisible; we will display it ourselves (like for Ref fields)
if self.appyType.isSelect and \
(type(self.appyType.validator) in (list, tuple)):
# Generate i18n messages for every possible value if the list
# of values is fixed.
for value in self.appyType.validator:
msgLabel = '%s_%s_list_%s' % (self.classDescr.name,
self.fieldName, value)
poMsg = PoMessage(msgLabel, '', value)
poMsg.produceNiceDefault()
self.generator.labels.append(poMsg)
def walkAction(self):
'''How to generate an action field ? We generate an Archetypes String
field.'''
self.fieldType = 'StringField'
self.widgetType = 'StringWidget'
self.widgetParams['visible'] = False # Archetypes will believe the
# field is invisible; we will display it ourselves (like for Ref fields)
# Add action-specific i18n messages
for suffix in ('ok', 'ko'):
label = '%s_%s_action_%s' % (self.classDescr.name, self.fieldName,
@ -168,36 +107,22 @@ class ArchetypeFieldDescriptor:
self.fieldParams['relationship'] = relationship
if self.appyType.isMultiValued():
self.fieldParams['multiValued'] = True
self.widgetParams['visible'] = False
# Update the list of referers
self.generator.addReferer(self, relationship)
# Add the widget label for the back reference
refClassName = ArchetypesClassDescriptor.getClassName(
self.appyType.klass)
refClassName = ClassDescriptor.getClassName(self.appyType.klass)
if issubclass(self.appyType.klass, ModelClass):
refClassName = self.applicationName + self.appyType.klass.__name__
elif issubclass(self.appyType.klass, appy.gen.Tool):
refClassName = '%sTool' % self.applicationName
elif issubclass(self.appyType.klass, appy.gen.Flavour):
refClassName = '%sFlavour' % self.applicationName
backLabel = "%s_%s_back" % (refClassName, self.appyType.back.attribute)
backLabel = "%s_%s" % (refClassName, self.appyType.back.attribute)
poMsg = PoMessage(backLabel, '', self.appyType.back.attribute)
poMsg.produceNiceDefault()
self.generator.labels.append(poMsg)
def walkInfo(self):
'''How to generate an Info field? We generate an Archetypes String
field.'''
self.fieldType = 'StringField'
self.widgetType = 'StringWidget'
self.widgetParams['visible'] = False # Archetypes will believe the
# field is invisible; we will display it ourselves (like for Ref fields)
def walkPod(self):
'''How to dump a Pod type?'''
self.fieldType = 'FileField'
self.widgetType = 'FileWidget'
self.fieldParams['storage'] = 'python:AttributeStorage()'
# Add i18n-specific messages
if self.appyType.askAction:
label = '%s_%s_askaction' % (self.classDescr.name, self.fieldName)
@ -207,47 +132,23 @@ class ArchetypeFieldDescriptor:
# Add the POD-related fields on the Flavour
Flavour._appy_addPodRelatedFields(self)
alwaysAValidatorFor = ('Ref', 'Integer', 'Float')
notToValidateFields = ('Info', 'Computed', 'Action', 'Pod')
def walkAppyType(self):
'''Walks into the Appy type definition and gathers data about the
Archetype elements to generate.'''
i18n labels.'''
# Manage things common to all Appy types
# - special accessor for fields "title" and "description"
if self.fieldName in self.specialParams:
self.fieldParams['accessor'] = self.fieldName.capitalize()
# - default value
if self.appyType.default != None:
self.fieldParams['default'] = self.appyType.default
# - required?
if self.appyType.multiplicity[0] >= 1:
if self.appyType.type != 'Ref': self.fieldParams['required'] = True
# Indeed, if it is a ref appy will manage itself field updates in
# onEdit, so Archetypes must not enforce required=True
# - optional ?
if self.appyType.optional:
Flavour._appy_addOptionalField(self)
self.widgetParams['condition'] = ' python: ' \
'here.fieldIsUsed("%s")'% self.fieldName
# - edit default value ?
if self.appyType.editDefault:
Flavour._appy_addDefaultField(self)
methodName = 'getDefaultValueFor%s' % self.fieldName
self.fieldParams['default_method'] = methodName
self.classDescr.addDefaultMethod(methodName, self)
# - put an index on this field?
if self.appyType.indexed:
if (self.appyType.type == 'String') and (self.appyType.isSelect):
self.fieldParams['index'] = 'ZCTextIndex, lexicon_id=' \
'plone_lexicon, index_type=Okapi BM25 Rank'
else:
self.fieldParams['index'] = 'FieldIndex'
# - searchable ?
if self.appyType.searchable: self.fieldParams['searchable'] = True
# - slaves ?
if self.appyType.slaves: self.widgetParams['visible'] = False
# Archetypes will believe the field is invisible; we will display it
# ourselves (like for Ref fields).
if self.appyType.indexed and \
(self.fieldName not in ('title', 'description')):
self.classDescr.addIndexMethod(self)
# - searchable ? TODO
#if self.appyType.searchable: self.fieldParams['searchable'] = True
# - need to generate a field validator?
# In all cases excepted for "immutable" fields, add an i18n message for
# the validation error for this field.
@ -255,43 +156,18 @@ class ArchetypeFieldDescriptor:
label = '%s_%s_valid' % (self.classDescr.name, self.fieldName)
poMsg = PoMessage(label, '', PoMessage.DEFAULT_VALID_ERROR)
self.generator.labels.append(poMsg)
# Generate a validator for the field if needed.
if (type(self.appyType.validator) == types.FunctionType) or \
(type(self.appyType.validator) == type(String.EMAIL)) or \
(self.appyType.type in self.alwaysAValidatorFor):
# For references, we always add a validator because gen validates
# itself things like multiplicities;
# For integers and floats, we also need validators because, by
# default, Archetypes produces an exception if the field value does
# not have the correct type, for example.
methodName = 'validate_%s' % self.fieldName
# Add a validate method for this
specificType = None
if self.appyType.type in self.alwaysAValidatorFor:
specificType = self.appyType.type
self.classDescr.addValidateMethod(methodName, label, self,
specificType=specificType)
# Manage specific permissions
permFieldName = '%s %s' % (self.classDescr.name, self.fieldName)
if self.appyType.specificReadPermission:
self.fieldParams['read_permission'] = '%s: Read %s' % \
(self.generator.applicationName, permFieldName)
if self.appyType.specificWritePermission:
self.fieldParams['write_permission'] = '%s: Write %s' % \
(self.generator.applicationName, permFieldName)
# i18n labels
i18nPrefix = "%s_%s" % (self.classDescr.name, self.fieldName)
wp = self.widgetParams
wp['label'] = self.fieldName
wp['label_msgid'] = '%s' % i18nPrefix
wp['description'] = '%sDescr' % i18nPrefix
wp['description_msgid'] = '%s_descr' % i18nPrefix
wp['i18n_domain'] = self.applicationName
# Create labels for generating them in i18n files.
messages = self.generator.labels
messages.append(self.produceMessage(wp['label_msgid']))
messages.append(self.produceMessage(wp['description_msgid'],
isLabel=False))
if self.appyType.hasLabel:
messages.append(self.produceMessage(i18nPrefix))
if self.appyType.hasDescr:
descrId = i18nPrefix + '_descr'
messages.append(self.produceMessage(descrId,isLabel=False))
if self.appyType.hasHelp:
helpId = i18nPrefix + '_help'
messages.append(self.produceMessage(helpId, isLabel=False))
# Create i18n messages linked to pages and phases
messages = self.generator.labels
pageMsgId = '%s_page_%s' % (self.classDescr.name, self.appyType.page)
@ -305,34 +181,22 @@ class ArchetypeFieldDescriptor:
messages.append(poMsg)
self.classDescr.labelsToPropagate.append(poMsg)
# Create i18n messages linked to groups
if self.appyType.group:
groupName, cols = GroupDescr.getGroupInfo(self.appyType.group)
msgId = '%s_group_%s' % (self.classDescr.name, groupName)
poMsg = PoMessage(msgId, '', groupName)
poMsg.produceNiceDefault()
if poMsg not in messages:
messages.append(poMsg)
self.classDescr.labelsToPropagate.append(poMsg)
# Manage schemata
if self.appyType.page != 'main':
self.fieldParams['schemata'] = self.appyType.page
# Manage things which are specific to basic types
if self.appyType.type in self.singleValuedTypes: self.walkBasicType()
group = self.appyType.group
if group:
group.generateLabels(messages, self.classDescr, set())
# Manage things which are specific to String types
elif self.appyType.type == 'String': self.walkString()
# Manage things which are specific to Computed types
elif self.appyType.type == 'Computed': self.walkComputed()
if self.appyType.type == 'String': self.walkString()
# Manage things which are specific to Actions
elif self.appyType.type == 'Action': self.walkAction()
# Manage things which are specific to Ref types
elif self.appyType.type == 'Ref': self.walkRef()
# Manage things which are specific to Info types
elif self.appyType.type == 'Info': self.walkInfo()
# Manage things which are specific to Pod types
elif self.appyType.type == 'Pod': self.walkPod()
def generate(self):
'''Produces the Archetypes field definition as a string.'''
'''Generates the i18n labels for this type.'''
self.walkAppyType()
if self.appyType.type != 'Ref': return
res = ''
s = stringify
spaces = TABS
@ -376,89 +240,41 @@ class ClassDescriptor(appy.gen.descriptors.ClassDescriptor):
# for child classes of this class as well, but at this time we don't
# know yet every sub-class. So we store field definitions here; the
# Generator will propagate them later.
def generateSchema(self):
'''Generates the corresponding Archetypes schema in self.schema.'''
for attrName in self.orderedAttributes:
attrValue = getattr(self.klass, attrName)
if isinstance(attrValue, Type):
field = ArchetypeFieldDescriptor(attrName, attrValue, self)
self.schema += '\n' + field.generate()
def addSelectMethod(self, methodName, fieldDescr):
'''For the selection field p_fieldDescr I need to generate a method
named p_methodName that will generate the vocabulary for
p_fieldDescr.'''
# Generate the method signature
m = self.methods
s = stringify
spaces = TABS
m += '\n' + ' '*spaces + 'def %s(self):\n' % methodName
spaces += TABS
appyType = fieldDescr.appyType
if type(appyType.validator) in (list, tuple):
# Generate i18n messages for every possible value
f = fieldDescr
labels = []
for value in appyType.validator:
msgLabel = '%s_%s_list_%s' % (f.classDescr.name, f.fieldName,
value)
labels.append(msgLabel) # I will need it later
poMsg = PoMessage(msgLabel, '', value)
poMsg.produceNiceDefault()
self.generator.labels.append(poMsg)
# Generate a method that returns a DisplayList
appName = self.generator.applicationName
allValues = appyType.validator
if not appyType.isMultiValued():
allValues = [''] + appyType.validator
labels.insert(0, 'choose_a_value')
m += ' '*spaces + 'return self._appy_getDisplayList' \
'(%s, %s, %s)\n' % (s(allValues), s(labels), s(appName))
elif isinstance(appyType.validator, Selection):
# Call the custom method that will produce dynamically the list of
# values.
m += ' '*spaces + 'return self._appy_getDynamicDisplayList' \
'(%s)\n' % s(appyType.validator.methodName)
self.methods = m
def addValidateMethod(self, methodName, label, fieldDescr,
specificType=None):
'''For the field p_fieldDescr I need to generate a validation method.
If p_specificType is not None, it corresponds to the name of a type
like Ref, Integer or Float, for which specific validation is needed,
beyond the potential custom validation specified by a user-defined
validator method.'''
# Generate the method signature
m = self.methods
s = stringify
spaces = TABS
m += '\n' + ' '*spaces + 'def %s(self, value):\n' % methodName
spaces += TABS
m += ' '*spaces + 'return self._appy_validateField(%s, value, %s, ' \
'%s)\n' % (s(fieldDescr.fieldName), s(label), s(specificType))
self.methods = m
def addDefaultMethod(self, methodName, fieldDescr):
'''When the default value of a field may be edited, we must add a method
that will gather the default value from the flavour.'''
m = self.methods
spaces = TABS
m += '\n' + ' '*spaces + 'def %s(self):\n' % methodName
spaces += TABS
m += ' '*spaces + 'return self.getDefaultValueFor("%s")\n' % \
fieldDescr.fieldName
self.methods = m
class ArchetypesClassDescriptor(ClassDescriptor):
'''Represents an Archetypes-compliant class that corresponds to an
application class.'''
predefined = False
def __init__(self, klass, orderedAttributes, generator):
ClassDescriptor.__init__(self, klass, orderedAttributes, generator)
if not hasattr(self, 'name'):
self.name = self.getClassName(klass)
self.generateSchema()
self.predefined = False
self.customized = False
def getParents(self, allClasses):
parentWrapper = 'AbstractWrapper'
parentClass = '%s.%s' % (self.klass.__module__, self.klass.__name__)
if self.klass.__bases__:
baseClassName = self.klass.__bases__[0].__name__
for k in allClasses:
if self.klass.__name__ == baseClassName:
parentWrapper = '%s_Wrapper' % k.name
return (parentWrapper, parentClass)
def generateSchema(self, configClass=False):
'''Generates the corresponding Archetypes schema in self.schema. If we
are generating a schema for a class that is in the configuration
(tool, flavour, etc) we must avoid having attributes that rely on
the configuration (ie attributes that are optional, with
editDefault=True, etc).'''
for attrName in self.orderedAttributes:
try:
attrValue = getattr(self.klass, attrName)
except AttributeError:
attrValue = getattr(self.modelClass, attrName)
if isinstance(attrValue, Type):
if configClass:
attrValue = copy.copy(attrValue)
attrValue.optional = False
attrValue.editDefault = False
field = FieldDescriptor(attrName, attrValue, self)
fieldDef = field.generate()
if fieldDef:
# Currently, we generate Archetypes fields for Refs only.
self.schema += '\n' + fieldDef
@staticmethod
def getClassName(klass):
@ -502,6 +318,13 @@ class ArchetypesClassDescriptor(ClassDescriptor):
res = self.isFolder(theClass.__bases__[0])
return res
def getCreators(self):
'''Gets the specific creators defined for this class.'''
res = []
if self.klass.__dict__.has_key('creators') and self.klass.creators:
res += list(self.klass.creators)
return res
def getCreateMean(self, type='Import'):
'''Returns the mean for this class that corresponds to p_type, or
None if the class does not support this create mean.'''
@ -534,75 +357,87 @@ class ArchetypesClassDescriptor(ClassDescriptor):
@staticmethod
def getSearch(klass, searchName):
'''Gets the search named p_searchName.'''
for search in ArchetypesClassDescriptor.getSearches(klass):
for search in ClassDescriptor.getSearches(klass):
if search.name == searchName:
return search
return None
class ToolClassDescriptor(ClassDescriptor):
'''Represents the POD-specific fields that must be added to the tool.'''
predefined = True
def __init__(self, klass, generator):
ClassDescriptor.__init__(self, klass, klass._appy_attributes, generator)
self.name = '%sTool' % generator.applicationName
def isFolder(self, klass=None): return True
def isRoot(self): return False
def addUnoValidator(self):
def addIndexMethod(self, field):
'''For indexed p_field, this method generates a method that allows to
get the value of the field as must be copied into the corresponding
index.'''
m = self.methods
spaces = TABS
m += '\n' + ' '*spaces + 'def validate_unoEnabledPython(self, value):\n'
n = field.fieldName
m += '\n' + ' '*spaces + 'def get%s%s(self):\n' % (n[0].upper(), n[1:])
spaces += TABS
m += ' '*spaces + 'return self._appy_validateUnoEnabledPython(value)\n'
m += ' '*spaces + "'''Gets indexable value of field \"%s\".'''\n" % n
m += ' '*spaces + 'return self.getAppyType("%s").getValue(self)\n' % n
self.methods = m
class ToolClassDescriptor(ClassDescriptor):
'''Represents the POD-specific fields that must be added to the tool.'''
def __init__(self, klass, generator):
ClassDescriptor.__init__(self,klass,klass._appy_attributes[:],generator)
self.name = '%sTool' % generator.applicationName
self.modelClass = self.klass
self.predefined = True
self.customized = False
def getParents(self, allClasses=()):
res = ['Tool']
if self.customized:
res.append('%s.%s' % (self.klass.__module__, self.klass.__name__))
return res
def update(self, klass, attributes):
'''This method is called by the generator when he finds a custom tool
definition. We must then add the custom tool elements in this default
Tool descriptor.'''
self.orderedAttributes += attributes
self.klass = klass
self.customized = True
def isFolder(self, klass=None): return True
def isRoot(self): return False
def generateSchema(self):
ClassDescriptor.generateSchema(self)
self.addUnoValidator()
ClassDescriptor.generateSchema(self, configClass=True)
class FlavourClassDescriptor(ClassDescriptor):
'''Represents an Archetypes-compliant class that corresponds to the Flavour
for the generated application.'''
predefined = True
def __init__(self, klass, generator):
ClassDescriptor.__init__(self, klass, klass._appy_attributes, generator)
ClassDescriptor.__init__(self,klass,klass._appy_attributes[:],generator)
self.name = '%sFlavour' % generator.applicationName
self.attributesByClass = klass._appy_classes
# We don't generate the schema automatically here because we need to
# add more fields.
self.modelClass = self.klass
self.predefined = True
self.customized = False
def getParents(self, allClasses=()):
res = ['Flavour']
if self.customized:
res.append('%s.%s' % (self.klass.__module__, self.klass.__name__))
return res
def update(self, klass, attributes):
'''This method is called by the generator when he finds a custom flavour
definition. We must then add the custom flavour elements in this
default Flavour descriptor.'''
self.orderedAttributes += attributes
self.klass = klass
self.customized = True
def isFolder(self, klass=None): return True
def isRoot(self): return False
def generateSchema(self):
ClassDescriptor.generateSchema(self, configClass=True)
class PodTemplateClassDescriptor(ClassDescriptor):
'''Represents a POD template.'''
predefined = True
def __init__(self, klass, generator):
ClassDescriptor.__init__(self, klass, klass._appy_attributes, generator)
ClassDescriptor.__init__(self,klass,klass._appy_attributes[:],generator)
self.name = '%sPodTemplate' % generator.applicationName
self.modelClass = self.klass
self.predefined = True
self.customized = False
def getParents(self, allClasses=()): return ['PodTemplate']
def isRoot(self): return False
class CustomToolClassDescriptor(ArchetypesClassDescriptor):
'''If the user defines a class that inherits from Tool, we will add those
fields to the tool.'''
predefined = False
def __init__(self, *args):
self.name = '%sTool' % args[2].applicationName
ArchetypesClassDescriptor.__init__(self, *args)
def generateSchema(self):
'''Custom tool fields may not use the variability mechanisms, ie
'optional' or 'editDefault' attributes.'''
for attrName in self.orderedAttributes:
attrValue = getattr(self.klass, attrName)
if isinstance(attrValue, Type):
attrValue = copy.copy(attrValue)
attrValue.optional = False
attrValue.editDefault = False
field = ArchetypeFieldDescriptor(attrName, attrValue, self)
self.schema += '\n' + field.generate()
class CustomFlavourClassDescriptor(CustomToolClassDescriptor):
def __init__(self, *args):
self.name = '%sFlavour' % args[2].applicationName
ArchetypesClassDescriptor.__init__(self, *args)
class WorkflowDescriptor(appy.gen.descriptors.WorkflowDescriptor):
'''Represents a workflow.'''
# How to map Appy permissions to Plone permissions ?

View file

@ -8,10 +8,9 @@ from appy.gen import *
from appy.gen.po import PoMessage, PoFile, PoParser
from appy.gen.generator import Generator as AbstractGenerator
from model import ModelClass, PodTemplate, Flavour, Tool
from descriptors import ArchetypeFieldDescriptor, ArchetypesClassDescriptor, \
from descriptors import FieldDescriptor, ClassDescriptor, \
WorkflowDescriptor, ToolClassDescriptor, \
FlavourClassDescriptor, PodTemplateClassDescriptor, \
CustomToolClassDescriptor, CustomFlavourClassDescriptor
FlavourClassDescriptor, PodTemplateClassDescriptor
# Common methods that need to be defined on every Archetype class --------------
COMMON_METHODS = '''
@ -30,6 +29,13 @@ class Generator(AbstractGenerator):
def __init__(self, *args, **kwargs):
Flavour._appy_clean()
AbstractGenerator.__init__(self, *args, **kwargs)
# Set our own Descriptor classes
self.descriptorClasses['class'] = ClassDescriptor
self.descriptorClasses['workflow'] = WorkflowDescriptor
# Create our own Tool, Flavour and PodTemplate instances
self.tool = ToolClassDescriptor(Tool, self)
self.flavour = FlavourClassDescriptor(Flavour, self)
self.podTemplate = PodTemplateClassDescriptor(PodTemplate, self)
# i18n labels to generate
self.labels = [] # i18n labels
self.toolName = '%sTool' % self.applicationName
@ -50,19 +56,10 @@ class Generator(AbstractGenerator):
'toolInstanceName': self.toolInstanceName,
'podTemplateName': self.podTemplateName,
'commonMethods': commonMethods})
# Predefined class descriptors
self.toolDescr = ToolClassDescriptor(Tool, self)
self.flavourDescr = FlavourClassDescriptor(Flavour, self)
self.podTemplateDescr = PodTemplateClassDescriptor(PodTemplate,self)
self.referers = {}
versionRex = re.compile('(.*?\s+build)\s+(\d+)')
def initialize(self):
# Use customized class descriptors
self.classDescriptor = ArchetypesClassDescriptor
self.workflowDescriptor = WorkflowDescriptor
self.customToolClassDescriptor = CustomToolClassDescriptor
self.customFlavourClassDescriptor = CustomFlavourClassDescriptor
# Determine version number of the Plone product
self.version = '0.1 build 1'
versionTxt = os.path.join(self.outputFolder, 'version.txt')
@ -91,6 +88,7 @@ class Generator(AbstractGenerator):
poMsg = msg(app, '', app); poMsg.produceNiceDefault()
self.labels += [poMsg,
msg('workflow_state', '', msg.WORKFLOW_STATE),
msg('appy_title', '', msg.APPY_TITLE),
msg('data_change', '', msg.DATA_CHANGE),
msg('modified_field', '', msg.MODIFIED_FIELD),
msg('previous_value', '', msg.PREVIOUS_VALUE),
@ -127,11 +125,12 @@ class Generator(AbstractGenerator):
msg('search_or', '', msg.SEARCH_OR),
msg('search_and', '', msg.SEARCH_AND),
msg('ref_invalid_index', '', msg.REF_INVALID_INDEX),
msg('bad_int', '', msg.BAD_INT),
msg('bad_long', '', msg.BAD_LONG),
msg('bad_float', '', msg.BAD_FLOAT),
msg('bad_email', '', msg.BAD_EMAIL),
msg('bad_url', '', msg.BAD_URL),
msg('bad_alphanumeric', '', msg.BAD_ALPHANUMERIC),
msg('bad_select_value', '', msg.BAD_SELECT_VALUE),
msg('select_delesect', '', msg.SELECT_DESELECT),
msg('no_elem_selected', '', msg.NO_SELECTION),
msg('delete_confirm', '', msg.DELETE_CONFIRM),
@ -145,6 +144,9 @@ class Generator(AbstractGenerator):
msg('confirm', '', msg.CONFIRM),
msg('yes', '', msg.YES),
msg('no', '', msg.NO),
msg('field_required', '', msg.FIELD_REQUIRED),
msg('file_required', '', msg.FILE_REQUIRED),
msg('image_required', '', msg.IMAGE_REQUIRED),
]
# Create basic files (config.py, Install.py, etc)
self.generateTool()
@ -229,10 +231,10 @@ class Generator(AbstractGenerator):
ploneRoles = ('Manager', 'Member', 'Owner', 'Reviewer')
def getAllUsedRoles(self, appOnly=False):
'''Produces a list of all the roles used within all workflows defined
in this application. If p_appOnly is True, it returns only roles
which are specific to this application (ie it removes predefined
Plone roles like Member, Manager, etc.'''
'''Produces a list of all the roles used within all workflows and
classes defined in this application. If p_appOnly is True, it
returns only roles which are specific to this application (ie it
removes predefined Plone roles like Member, Manager, etc.'''
res = []
for wfDescr in self.workflows:
# Browse states and transitions
@ -241,6 +243,8 @@ class Generator(AbstractGenerator):
if isinstance(attrValue, State) or \
isinstance(attrValue, Transition):
res += attrValue.getUsedRoles()
for cDescr in self.getClasses(include='all'):
res += cDescr.getCreators()
res = list(set(res))
if appOnly:
for ploneRole in self.ploneRoles:
@ -259,28 +263,22 @@ class Generator(AbstractGenerator):
elif issubclass(k, appy.gen.Flavour):
refClassName = '%sFlavour' % self.applicationName
else:
refClassName = ArchetypesClassDescriptor.getClassName(k)
refClassName = ClassDescriptor.getClassName(k)
if not self.referers.has_key(refClassName):
self.referers[refClassName] = []
self.referers[refClassName].append( (fieldDescr, relationship))
def generateConfig(self):
# Compute referers
referers = ''
for className, refInfo in self.referers.iteritems():
referers += '"%s":[' % className
for fieldDescr, relationship in refInfo:
refClass = fieldDescr.classDescr.klass
if issubclass(refClass, ModelClass):
refClassName = 'Extensions.appyWrappers.%s' % \
refClass.__name__
def getAppyTypePath(self, name, appyType, klass, isBack=False):
'''Gets the path to the p_appyType when a direct reference to an
appyType must be generated in a Python file.'''
if issubclass(klass, ModelClass):
res = 'wraps.%s.%s' % (klass.__name__, name)
else:
refClassName = '%s.%s' % (refClass.__module__,
refClass.__name__)
referers += '(%s.%s' % (refClassName, fieldDescr.fieldName)
referers += ',"%s"' % relationship
referers += '),'
referers += '],\n'
res = '%s.%s.%s' % (klass.__module__, klass.__name__, name)
if isBack: res += '.back'
return res
def generateConfig(self):
# Compute workflow instances initialisation
wfInit = ''
for workflowDescr in self.workflows:
@ -301,21 +299,11 @@ class Generator(AbstractGenerator):
wfInit += 'workflowInstances[%s] = wf\n' % className
# Compute imports
imports = ['import %s' % self.applicationName]
classDescrs = self.classes[:]
if self.customToolDescr:
classDescrs.append(self.customToolDescr)
if self.customFlavourDescr:
classDescrs.append(self.customFlavourDescr)
classDescrs = self.getClasses(include='custom')
for classDescr in (classDescrs + self.workflows):
theImport = 'import %s' % classDescr.klass.__module__
if theImport not in imports:
imports.append(theImport)
# Compute ordered lists of attributes for every Appy class.
attributes = []
for classDescr in classDescrs:
classAttrs = [a[0] for a in classDescr.getOrderedAppyAttributes()]
attrs = ','.join([('"%s"' % a) for a in classAttrs])
attributes.append('"%s":[%s]' % (classDescr.name, attrs))
# Compute root classes
rootClasses = ''
for classDescr in self.classes:
@ -327,14 +315,49 @@ class Generator(AbstractGenerator):
addPermissions += ' "%s":"%s: Add %s",\n' % (classDescr.name,
self.applicationName, classDescr.name)
repls = self.repls.copy()
# Compute the list of ordered attributes (foward and backward, inherited
# included) for every Appy class.
attributes = []
attributesDict = []
for classDescr in self.getClasses(include='all'):
titleFound = False
attrs = []
attrNames = []
for name, appyType, klass in classDescr.getOrderedAppyAttributes():
attrs.append(self.getAppyTypePath(name, appyType, klass))
attrNames.append(name)
if name == 'title': titleFound = True
# Add the "title" mandatory field if not found
if not titleFound:
attrs.insert(0, 'copy.deepcopy(appy.gen.title)')
attrNames.insert(0, 'title')
# Any backward attributes to append?
if classDescr.name in self.referers:
for field, rel in self.referers[classDescr.name]:
try:
getattr(field.classDescr.klass, field.fieldName)
klass = field.classDescr.klass
except AttributeError:
klass = field.classDescr.modelClass
attrs.append(self.getAppyTypePath(field.fieldName,
field.appyType, klass, isBack=True))
attrNames.append(field.appyType.back.attribute)
attributes.append('"%s":[%s]' % (classDescr.name,','.join(attrs)))
aDict = ''
i = -1
for attr in attrs:
i += 1
aDict += '"%s":attributes["%s"][%d],' % \
(attrNames[i], classDescr.name, i)
attributesDict.append('"%s":{%s}' % (classDescr.name, aDict))
# Compute list of used roles for registering them if needed
repls['roles'] = ','.join(['"%s"' % r for r in \
self.getAllUsedRoles(appOnly=True)])
repls['rootClasses'] = rootClasses
repls['referers'] = referers
repls['workflowInstancesInit'] = wfInit
repls['imports'] = '\n'.join(imports)
repls['attributes'] = ',\n '.join(attributes)
repls['attributesDict'] = ',\n '.join(attributesDict)
repls['defaultAddRoles'] = ','.join(
['"%s"' % r for r in self.config.defaultCreators])
repls['addPermissions'] = addPermissions
@ -342,15 +365,16 @@ class Generator(AbstractGenerator):
def generateInit(self):
# Compute imports
imports = [' import %s' % self.toolName,
' import %s' % self.flavourName,
' import %s' % self.podTemplateName]
for c in self.classes:
imports = []
classNames = []
for c in self.getClasses(include='all'):
importDef = ' import %s' % c.name
if importDef not in imports:
imports.append(importDef)
classNames.append("%s.%s" % (c.name, c.name))
repls = self.repls.copy()
repls['imports'] = '\n'.join(imports)
repls['classes'] = ','.join(classNames)
repls['totalNumberOfTests'] = self.totalNumberOfTests
self.copyFile('__init__.py', repls)
@ -380,12 +404,7 @@ class Generator(AbstractGenerator):
"['portal_catalog']\n" % blackClass
# Compute workflows
workflows = ''
allClasses = self.classes[:]
if self.customToolDescr:
allClasses.append(self.customToolDescr)
if self.customFlavourDescr:
allClasses.append(self.customFlavourDescr)
for classDescr in allClasses:
for classDescr in self.getClasses(include='all'):
if hasattr(classDescr.klass, 'workflow'):
wfName = WorkflowDescriptor.getWorkflowName(
classDescr.klass.workflow)
@ -437,40 +456,30 @@ class Generator(AbstractGenerator):
repls['workflows'] = workflows
self.copyFile('workflows.py', repls, destFolder='Extensions')
def generateWrapperProperty(self, attrName, appyType):
'''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).objects\n' % attrName
elif isinstance(appyType, Computed):
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'
elif isinstance(appyType, String) and appyType.isMultiValued():
res += blanks + 'return list(self.o.%s())\n' % getterName
def generateWrapperProperty(self, name):
'''Generates the getter for attribute p_name.'''
res = ' def get_%s(self):\n ' % name
if name == 'title':
res += 'return self.o.Title()\n'
else:
if attrName in ArchetypeFieldDescriptor.specialParams:
getterName = attrName.capitalize()
res += blanks + 'return self.o.%s()\n' % getterName
res += ' %s = property(get_%s)\n\n' % (attrName, attrName)
res += 'return self.o.getAppyType("%s").getValue(self.o)\n' % name
res += ' %s = property(get_%s)\n\n' % (name, name)
return res
def generateWrapperPropertyBack(self, attrName, rel):
'''Generates a wrapper property for accessing the back reference named
p_attrName through Archetypes relationship p_rel.'''
res = ' def get_%s(self):\n' % attrName
blanks = ' '*8
res += blanks + 'return self.o._appy_getRefsBack("%s", "%s", ' \
'noListIfSingleObj=True)\n' % (attrName, rel)
res += ' %s = property(get_%s)\n\n' % (attrName, attrName)
def getClasses(self, include=None):
'''Returns the descriptors for all the classes in the generated
gen-application. If p_include is "all", it includes the descriptors
for the config-related classes (tool, flavour, etc); if
p_include is "custom", it includes descriptors for the
config-related classes for which the user has created a sub-class.'''
if not include: return self.classes
else:
res = self.classes[:]
configClasses = [self.tool, self.flavour, self.podTemplate]
if include == 'all':
res += configClasses
elif include == 'custom':
res += [c for c in configClasses if c.customized]
return res
def getClassesInOrder(self, allClasses):
@ -506,57 +515,45 @@ class Generator(AbstractGenerator):
# We must generate imports and wrapper definitions
imports = []
wrappers = []
allClasses = self.classes[:]
# Add predefined classes (Tool, Flavour, PodTemplate)
allClasses += [self.toolDescr, self.flavourDescr, self.podTemplateDescr]
if self.customToolDescr:
allClasses.append(self.customToolDescr)
if self.customFlavourDescr:
allClasses.append(self.customFlavourDescr)
allClasses = self.getClasses(include='all')
for c in self.getClassesInOrder(allClasses):
if not c.predefined:
if not c.predefined or c.customized:
moduleImport = 'import %s' % c.klass.__module__
if moduleImport not in imports:
imports.append(moduleImport)
# Determine parent wrapper and class
parentWrapper = 'AbstractWrapper'
parentClass = '%s.%s' % (c.klass.__module__, c.klass.__name__)
if c.predefined:
parentClass = c.klass.__name__
if c.klass.__bases__:
baseClassName = c.klass.__bases__[0].__name__
for k in allClasses:
if k.klass.__name__ == baseClassName:
parentWrapper = '%s_Wrapper' % k.name
wrapperDef = 'class %s_Wrapper(%s, %s):\n' % \
(c.name, parentWrapper, parentClass)
parentClasses = c.getParents(allClasses)
wrapperDef = 'class %s_Wrapper(%s):\n' % \
(c.name, ','.join(parentClasses))
wrapperDef += ' security = ClassSecurityInfo()\n'
titleFound = False
for attrName in c.orderedAttributes:
if attrName == 'title':
titleFound = True
try:
attrValue = getattr(c.klass, attrName)
except AttributeError:
attrValue = getattr(c.modelClass, attrName)
if isinstance(attrValue, Type):
wrapperDef += self.generateWrapperProperty(attrName,
attrValue)
wrapperDef += self.generateWrapperProperty(attrName)
# Generate properties for back references
if self.referers.has_key(c.name):
for refDescr, rel in self.referers[c.name]:
attrName = refDescr.appyType.back.attribute
wrapperDef += self.generateWrapperPropertyBack(attrName,rel)
wrapperDef += self.generateWrapperProperty(attrName)
if not titleFound:
# Implicitly, the title will be added by Archetypes. So I need
# to define a property for it.
wrapperDef += self.generateWrapperProperty('title', String())
if isinstance(c, CustomToolClassDescriptor) or \
isinstance(c, CustomFlavourClassDescriptor):
wrapperDef += self.generateWrapperProperty('title')
if c.customized:
# For custom tool and flavour, add a call to a method that
# allows to customize elements from the base class.
wrapperDef += " if hasattr(%s, 'update'):\n " \
"%s.update(%s.__bases__[1])\n" % (
parentClass, parentClass, parentWrapper)
"%s.update(%s)\n" % (parentClasses[1], parentClasses[1],
parentClasses[0])
# For custom tool and flavour, add security declaration that
# will allow to call their methods from ZPTs.
for parentClass in parentClasses:
wrapperDef += " for elem in dir(%s):\n " \
"if not elem.startswith('_'): security.declarePublic" \
"(elem)\n" % (parentClass)
@ -587,20 +584,12 @@ class Generator(AbstractGenerator):
repls = self.repls.copy()
# Manage predefined fields
Tool.flavours.klass = Flavour
if self.customFlavourDescr:
Tool.flavours.klass = self.customFlavourDescr.klass
self.toolDescr.generateSchema()
repls['predefinedFields'] = self.toolDescr.schema
repls['predefinedMethods'] = self.toolDescr.methods
# Manage custom fields
repls['fields'] = ''
repls['methods'] = ''
repls['wrapperClass'] = '%s_Wrapper' % self.toolDescr.name
if self.customToolDescr:
repls['fields'] = self.customToolDescr.schema
repls['methods'] = self.customToolDescr.methods
wrapperClass = '%s_Wrapper' % self.customToolDescr.name
repls['wrapperClass'] = wrapperClass
if self.flavour.customized:
Tool.flavours.klass = self.flavour.klass
self.tool.generateSchema()
repls['fields'] = self.tool.schema
repls['methods'] = self.tool.methods
repls['wrapperClass'] = '%s_Wrapper' % self.tool.name
self.copyFile('ToolTemplate.py', repls, destName='%s.py'% self.toolName)
repls = self.repls.copy()
# Create i18n-related messages
@ -623,35 +612,32 @@ class Generator(AbstractGenerator):
importMean = classDescr.getCreateMean('Import')
if importMean:
Flavour._appy_addImportRelatedFields(classDescr)
Flavour._appy_addWorkflowFields(self.flavourDescr)
Flavour._appy_addWorkflowFields(self.podTemplateDescr)
Flavour._appy_addWorkflowFields(self.flavour)
Flavour._appy_addWorkflowFields(self.podTemplate)
# Complete self.flavour.orderedAttributes from the attributes that we
# just added to the Flavour model class.
for fieldName in Flavour._appy_attributes:
if fieldName not in self.flavour.orderedAttributes:
self.flavour.orderedAttributes.append(fieldName)
# Generate the flavour class and related i18n messages
self.flavourDescr.generateSchema()
self.flavour.generateSchema()
self.labels += [ Msg(self.flavourName, '', Msg.FLAVOUR),
Msg('%s_edit_descr' % self.flavourName, '', ' ')]
repls = self.repls.copy()
repls['predefinedFields'] = self.flavourDescr.schema
repls['predefinedMethods'] = self.flavourDescr.methods
# Manage custom fields
repls['fields'] = ''
repls['methods'] = ''
repls['wrapperClass'] = '%s_Wrapper' % self.flavourDescr.name
if self.customFlavourDescr:
repls['fields'] = self.customFlavourDescr.schema
repls['methods'] = self.customFlavourDescr.methods
wrapperClass = '%s_Wrapper' % self.customFlavourDescr.name
repls['wrapperClass'] = wrapperClass
repls['fields'] = self.flavour.schema
repls['methods'] = self.flavour.methods
repls['wrapperClass'] = '%s_Wrapper' % self.flavour.name
repls['metaTypes'] = [c.name for c in self.classes]
self.copyFile('FlavourTemplate.py', repls,
destName='%s.py'% self.flavourName)
# Generate the PodTemplate class
self.podTemplateDescr.generateSchema()
self.podTemplate.generateSchema()
self.labels += [ Msg(self.podTemplateName, '', Msg.POD_TEMPLATE),
Msg('%s_edit_descr' % self.podTemplateName, '', ' ')]
repls = self.repls.copy()
repls['fields'] = self.podTemplateDescr.schema
repls['methods'] = self.podTemplateDescr.methods
repls['wrapperClass'] = '%s_Wrapper' % self.podTemplateDescr.name
repls['fields'] = self.podTemplate.schema
repls['methods'] = self.podTemplate.methods
repls['wrapperClass'] = '%s_Wrapper' % self.podTemplate.name
self.copyFile('PodTemplate.py', repls,
destName='%s.py' % self.podTemplateName)
@ -676,7 +662,7 @@ class Generator(AbstractGenerator):
implements = [baseClass]
for baseClass in classDescr.klass.__bases__:
if self.determineAppyType(baseClass) == 'class':
bcName = ArchetypesClassDescriptor.getClassName(baseClass)
bcName = ClassDescriptor.getClassName(baseClass)
parents.remove('ClassMixin')
parents.append(bcName)
implements.append(bcName)
@ -695,6 +681,7 @@ class Generator(AbstractGenerator):
if classDescr.isAbstract():
register = ''
repls = self.repls.copy()
classDescr.generateSchema()
repls.update({
'imports': '\n'.join(imports), 'parents': parents,
'className': classDescr.klass.__name__,

View file

@ -6,9 +6,15 @@ import os, os.path, time
from StringIO import StringIO
from sets import Set
import appy
from appy.gen import Type, Ref
from appy.gen.utils import produceNiceMessage
from appy.gen.plone25.utils import updateRolesForPermission
class ZCTextIndexInfo:
'''Silly class used for storing information about a ZCTextIndex.'''
lexicon_id = "plone_lexicon"
index_type = 'Okapi BM25 Rank'
class PloneInstaller:
'''This Plone installer runs every time the generated Plone product is
installed or uninstalled (in the Plone configuration interface).'''
@ -45,6 +51,28 @@ class PloneInstaller:
self.toolName = '%sTool' % self.productName
self.toolInstanceName = 'portal_%s' % self.productName.lower()
@staticmethod
def updateIndexes(ploneSite, indexInfo, logger):
'''This method creates or updates, in a p_ploneSite, definitions of
indexes in its portal_catalog, based on index-related information
given in p_indexInfo. p_indexInfo is a dictionary of the form
{s_indexName:s_indexType}. Here are some examples of index types:
"FieldIndex", "ZCTextIndex", "DateIndex".'''
catalog = ploneSite.portal_catalog
indexNames = catalog.indexes()
for indexName, indexType in indexInfo.iteritems():
if indexName not in indexNames:
# We need to create this index
if indexType != 'ZCTextIndex':
catalog.addIndex(indexName, indexType)
else:
catalog.addIndex(indexName,indexType,extra=ZCTextIndexInfo)
# Indexing database content based on this index.
catalog.reindexIndex(indexName, ploneSite.REQUEST)
logger.info('Created index "%s" of type "%s"...' % \
(indexName, indexType))
# TODO: if the index already exists but has not the same type, we
# re-create it with the same type and we reindex it.
actionsToHide = {
'portal_actions': ('sitemap', 'accessibility', 'change_state','sendto'),
@ -235,18 +263,17 @@ class PloneInstaller:
title=produceNiceMessage(templateName))
f.close()
# Creates the new-way templates for Pod fields if they do not exist.
for contentType, attrNames in self.attributes.iteritems():
for contentType, appyTypes in self.attributes.iteritems():
appyClass = self.tool.getAppyClass(contentType)
if not appyClass: continue # May be an abstract class
for attrName in attrNames:
appyType = getattr(appyClass, attrName)
for appyType in appyTypes:
if appyType.type == 'Pod':
# For every flavour, find the attribute that stores the
# template, and store on it the default one specified in
# the appyType if no template is stored yet.
for flavour in self.appyTool.flavours:
attrName = flavour.getAttributeName(
'podTemplate', appyClass, attrName)
'podTemplate', appyClass, appyType.name)
fileObject = getattr(flavour, attrName)
if not fileObject or (fileObject.size == 0):
# There is no file. Put the one specified in the
@ -298,9 +325,9 @@ class PloneInstaller:
self.tool = getattr(self.ploneSite, self.toolInstanceName)
self.appyTool = self.tool.appy()
if self.reinstall:
self.tool.createOrUpdate(False)
self.tool.createOrUpdate(False, None)
else:
self.tool.createOrUpdate(True)
self.tool.createOrUpdate(True, None)
if not self.appyTool.flavours:
# Create the default flavour
@ -324,9 +351,9 @@ class PloneInstaller:
self.productName, None)
def installRolesAndGroups(self):
'''Registers roles used by workflows defined in this application if
they are not registered yet. Creates the corresponding groups if
needed.'''
'''Registers roles used by workflows and classes defined in this
application if they are not registered yet. Creates the corresponding
groups if needed.'''
site = self.ploneSite
data = list(site.__ac_roles__)
for role in self.applicationRoles:
@ -412,6 +439,22 @@ class PloneInstaller:
if self.minimalistPlone:
site.manage_changeProperties(right_slots=())
def manageIndexes(self):
'''For every indexed field, this method installs and updates the
corresponding index if it does not exist yet.'''
indexInfo = {}
for className, appyTypes in self.attributes.iteritems():
for appyType in appyTypes:
if appyType.indexed:
n = appyType.name
indexName = 'get%s%s' % (n[0].upper(), n[1:])
indexType = 'FieldIndex'
if (appyType.type == 'String') and appyType.isSelect:
indexType = 'ZCTextIndex'
indexInfo[indexName] = indexType
if indexInfo:
PloneInstaller.updateIndexes(self.ploneSite, indexInfo, self)
def finalizeInstallation(self):
'''Performs some final installation steps.'''
site = self.ploneSite
@ -435,7 +478,8 @@ class PloneInstaller:
frontPageName = self.productName + 'FrontPage'
site.manage_changeProperties(default_page=frontPageName)
def log(self, msg): print >> self.toLog, msg
def log(self, msg): print msg
def info(self, msg): return self.log(msg)
def install(self):
self.log("Installation of %s:" % self.productName)
@ -447,9 +491,9 @@ class PloneInstaller:
self.installWorkflows()
self.installStyleSheet()
self.managePortlets()
self.manageIndexes()
self.finalizeInstallation()
self.log("Installation of %s done." % self.productName)
return self.toLog.getvalue()
def uninstallTool(self):
site = self.ploneSite
@ -502,7 +546,7 @@ class ZopeInstaller:
generated Zope product.'''
def __init__(self, zopeContext, productName, toolClass,
defaultAddContentPermission, addContentPermissions,
logger, ploneStuff):
logger, ploneStuff, classes):
self.zopeContext = zopeContext
self.productName = productName
self.toolClass = toolClass
@ -510,6 +554,22 @@ class ZopeInstaller:
self.addContentPermissions = addContentPermissions
self.logger = logger
self.ploneStuff = ploneStuff # A dict of some Plone functions or vars
self.classes = classes
def completeAppyTypes(self):
'''We complete here the initialisation process of every Appy type of
every gen-class of the application.'''
for klass in self.classes:
for baseClass in klass.wrapperClass.__bases__:
for name, appyType in baseClass.__dict__.iteritems():
if isinstance(appyType, Type):
appyType.init(name, baseClass, self.productName)
# Do not forget back references
if isinstance(appyType, Ref):
bAppyType = appyType.back
bAppyType.init(bAppyType.attribute, appyType.klass,
self.productName)
bAppyType.klass = baseClass
def installApplication(self):
'''Performs some application-wide installation steps.'''
@ -562,6 +622,7 @@ class ZopeInstaller:
def install(self):
self.logger.info('is being installed...')
self.completeAppyTypes()
self.installApplication()
self.installTool()
self.installTypes()

View file

@ -3,40 +3,5 @@ from appy.gen.plone25.mixins import AbstractMixin
# ------------------------------------------------------------------------------
class ClassMixin(AbstractMixin):
_appy_meta_type = 'class'
def _appy_fieldIsUsed(self, portalTypeName, fieldName):
tool = self.getTool()
flavour = tool.getFlavour(portalTypeName)
optionalFieldsAccessor = 'getOptionalFieldsFor%s' % self.meta_type
exec 'usedFields = flavour.%s()' % optionalFieldsAccessor
res = False
if fieldName in usedFields:
res = True
return res
def _appy_getDefaultValueFor(self, portalTypeName, fieldName):
tool = self.getTool()
flavour = tool.getFlavour(portalTypeName)
fieldFound = False
klass = self.__class__
while not fieldFound:
metaType = klass.meta_type
defValueAccessor = 'getDefaultValueFor%s_%s' % (metaType, fieldName)
if not hasattr(flavour, defValueAccessor):
# The field belongs to a super-class.
klass = klass.__bases__[-1]
else:
fieldFound = True
exec 'res = flavour.%s()' % defValueAccessor
return res
def fieldIsUsed(self, fieldName):
'''Checks in the corresponding flavour if p_fieldName is used.'''
portalTypeName = self._appy_getPortalType(self.REQUEST)
return self._appy_fieldIsUsed(portalTypeName, fieldName)
def getDefaultValueFor(self, fieldName):
'''Gets in the flavour the default value for p_fieldName.'''
portalTypeName = self._appy_getPortalType(self.REQUEST)
return self._appy_getDefaultValueFor(portalTypeName,fieldName)
_appy_meta_type = 'Class'
# ------------------------------------------------------------------------------

View file

@ -8,7 +8,7 @@ from appy.pod.renderer import Renderer
import appy.gen
from appy.gen import Type
from appy.gen.plone25.mixins import AbstractMixin
from appy.gen.plone25.descriptors import ArchetypesClassDescriptor
from appy.gen.plone25.descriptors import ClassDescriptor
# Errors -----------------------------------------------------------------------
DELETE_TEMP_DOC_ERROR = 'A temporary document could not be removed. %s.'
@ -17,7 +17,7 @@ POD_ERROR = 'An error occurred while generating the document. Please ' \
# ------------------------------------------------------------------------------
class FlavourMixin(AbstractMixin):
_appy_meta_type = 'flavour'
_appy_meta_type = 'Flavour'
def getPortalType(self, metaTypeOrAppyType):
'''Returns the name of the portal_type that is based on
p_metaTypeOrAppyType in this flavour.'''
@ -26,7 +26,7 @@ class FlavourMixin(AbstractMixin):
isAppy = False
appName = self.getProductConfig().PROJECTNAME
if not isinstance(res, basestring):
res = ArchetypesClassDescriptor.getClassName(res)
res = ClassDescriptor.getClassName(res)
isAppy = True
if res.find('Extensions_appyWrappers') != -1:
isPredefined = True
@ -41,8 +41,9 @@ class FlavourMixin(AbstractMixin):
isPredefined = True
res = '%sFlavour' % appName
if not isPredefined:
if self.getNumber() != 1:
res = '%s_%d' % (res, self.number)
number = self.appy().number
if number != 1:
res = '%s_%d' % (res, number)
return res
def registerPortalTypes(self):
@ -129,9 +130,9 @@ class FlavourMixin(AbstractMixin):
n = appyFlavour.getAttributeName('podTemplate', appyClass, fieldName)
res['template'] = getattr(appyFlavour, n)
appyType = ploneObj.getAppyType(fieldName)
res['title'] = self.translate(appyType['label'])
res['context'] = appyType['context']
res['action'] = appyType['action']
res['title'] = self.translate(appyType.labelId)
res['context'] = appyType.context
res['action'] = appyType.action
return res
def generateDocument(self):
@ -225,18 +226,18 @@ class FlavourMixin(AbstractMixin):
appyTool.log(DELETE_TEMP_DOC_ERROR % str(ie), type='warning')
return res
def getAttr(self, attrName):
def getAttr(self, name):
'''Gets on this flavour attribute named p_attrName. Useful because we
can't use getattr directly in Zope Page Templates.'''
return getattr(self, attrName, None)
return getattr(self.appy(), name, None)
def _appy_getAllFields(self, contentType):
'''Returns the (translated) names of fields of p_contentType.'''
res = []
for attrName in self.getProductConfig().attributes[contentType]:
if attrName != 'title': # Will be included by default.
label = '%s_%s' % (contentType, attrName)
res.append((attrName, self.translate(label)))
for appyType in self.getProductConfig().attributes[contentType]:
if appyType.name != 'title': # Will be included by default.
label = '%s_%s' % (contentType, appyType.name)
res.append((appyType.name, self.translate(label)))
# Add object state
res.append(('workflowState', self.translate('workflow_state')))
return res
@ -244,15 +245,10 @@ class FlavourMixin(AbstractMixin):
def _appy_getSearchableFields(self, contentType):
'''Returns the (translated) names of fields that may be searched on
objects of type p_contentType (=indexed fields).'''
tool = self.getParentNode()
appyClass = tool.getAppyClass(contentType)
attrNames = self.getProductConfig().attributes[contentType]
res = []
for attrName in attrNames:
attr = getattr(appyClass, attrName)
if isinstance(attr, Type) and attr.indexed:
label = '%s_%s' % (contentType, attrName)
res.append((attrName, self.translate(label)))
for appyType in self.getProductConfig().attributes[contentType]:
if appyType.indexed:
res.append((appyType.name, self.translate(appyType.labelId)))
return res
def getSearchableFields(self, contentType):
@ -260,11 +256,10 @@ class FlavourMixin(AbstractMixin):
the list of fields that the user has configured in the flavour as
being effectively used in the search screen.'''
res = []
appyClass = self.getAppyClass(contentType)
for attrName in getattr(self, 'searchFieldsFor%s' % contentType, ()):
attr = getattr(appyClass, attrName)
dAttr = self._appy_getTypeAsDict(attrName, attr, appyClass)
res.append((attrName, dAttr))
fieldNames = getattr(self.appy(), 'searchFieldsFor%s' % contentType, ())
for name in fieldNames:
appyType = self.getAppyType(name, asDict=True,className=contentType)
res.append(appyType)
return res
def getImportElements(self, contentType):

View file

@ -3,5 +3,5 @@ from appy.gen.plone25.mixins import AbstractMixin
# ------------------------------------------------------------------------------
class PodTemplateMixin(AbstractMixin):
_appy_meta_type = 'podtemplate'
_appy_meta_type = 'PodTemplate'
# ------------------------------------------------------------------------------

View file

@ -1,40 +1,18 @@
# ------------------------------------------------------------------------------
import re, os, os.path, Cookie
from appy.shared.utils import getOsTempFolder
from appy.gen import Type, Search, Selection
from appy.gen.utils import FieldDescr, SomeObjects, sequenceTypes
from appy.gen.utils import SomeObjects, sequenceTypes
from appy.gen.plone25.mixins import AbstractMixin
from appy.gen.plone25.mixins.FlavourMixin import FlavourMixin
from appy.gen.plone25.wrappers import AbstractWrapper
from appy.gen.plone25.descriptors import ArchetypesClassDescriptor
from appy.gen.plone25.descriptors import ClassDescriptor
_PY = 'Please specify a file corresponding to a Python interpreter ' \
'(ie "/usr/bin/python").'
FILE_NOT_FOUND = 'Path "%s" was not found.'
VALUE_NOT_FILE = 'Path "%s" is not a file. ' + _PY
NO_PYTHON = "Name '%s' does not starts with 'python'. " + _PY
NOT_UNO_ENABLED_PYTHON = '"%s" is not a UNO-enabled Python interpreter. ' \
'To check if a Python interpreter is UNO-enabled, ' \
'launch it and type "import uno". If you have no ' \
'ImportError exception it is ok.'
jsMessages = ('no_elem_selected', 'delete_confirm')
# ------------------------------------------------------------------------------
class ToolMixin(AbstractMixin):
_appy_meta_type = 'tool'
def _appy_validateUnoEnabledPython(self, value):
'''This method represents the validator for field unoEnabledPython.
This field is present on the Tool only if POD is needed.'''
if value:
if not os.path.exists(value):
return FILE_NOT_FOUND % value
if not os.path.isfile(value):
return VALUE_NOT_FILE % value
if not os.path.basename(value).startswith('python'):
return NO_PYTHON % value
if os.system('%s -c "import uno"' % value):
return NOT_UNO_ENABLED_PYTHON % value
return None
_appy_meta_type = 'Tool'
def getFlavour(self, contextObjOrPortalType, appy=False):
'''Gets the flavour that corresponds to p_contextObjOrPortalType.'''
if isinstance(contextObjOrPortalType, basestring):
@ -102,8 +80,11 @@ class ToolMixin(AbstractMixin):
def getObject(self, uid, appy=False):
'''Allows to retrieve an object from its p_uid.'''
res = self.uid_catalog(UID=uid)
if res: return res[0].getObject()
return None
if res:
res = res[0].getObject()
if appy:
res = res.appy()
return res
def executeQuery(self, contentType, flavourNumber=1, searchName=None,
startNumber=0, search=None, remember=False,
@ -133,7 +114,7 @@ class ToolMixin(AbstractMixin):
useful for some usages like knowing the number of objects without
needing to get information about them). If no p_maxResults is
specified, the method returns maximum
self.getNumberOfResultsPerPage(). The method returns all objects if
self.numberOfResultsPerPage. The method returns all objects if
p_maxResults equals string "NO_LIMIT".
If p_noSecurity is True, it gets all the objects, even those that the
@ -163,7 +144,7 @@ class ToolMixin(AbstractMixin):
# In this case, contentType must contain a single content type.
appyClass = self.getAppyClass(contentType)
if searchName != '_advanced':
search = ArchetypesClassDescriptor.getSearch(
search = ClassDescriptor.getSearch(
appyClass, searchName)
else:
fields = self.REQUEST.SESSION['searchCriteria']
@ -201,7 +182,7 @@ class ToolMixin(AbstractMixin):
# Return brains only.
if not maxResults: return brains
else: return brains[:maxResults]
if not maxResults: maxResults = self.getNumberOfResultsPerPage()
if not maxResults: maxResults = self.appy().numberOfResultsPerPage
elif maxResults == 'NO_LIMIT': maxResults = None
res = SomeObjects(brains, maxResults, startNumber,noSecurity=noSecurity)
res.brainsToObjects()
@ -249,26 +230,31 @@ class ToolMixin(AbstractMixin):
def getResultColumns(self, anObject, contentType):
'''What columns must I show when displaying a list of root class
instances? Result is a list of tuples containing the name of the
column (=name of the field) and a FieldDescr instance.'''
column (=name of the field) and the corresponding appyType (dict
version).'''
res = []
for fieldName in self.getResultColumnsNames(contentType):
if fieldName == 'workflowState':
# We do not return a FieldDescr instance if the attributes is
# not a *real* attribute but the workfow state.
# We do not return a appyType if the attribute is not a *real*
# attribute, but the workfow state.
res.append(fieldName)
else:
# Create a FieldDescr instance
appyType = anObject.getAppyType(fieldName)
appyType = anObject.getAppyType(fieldName, asDict=True)
if not appyType:
res.append({'atField': None, 'name': fieldName})
res.append({'name': fieldName, '_wrong': True})
# 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())
res.append(appyType)
return res
def truncateValue(self, value, appyType):
'''Truncates the p_value according to p_appyType width.'''
maxWidth = appyType['width']
if len(value) > maxWidth:
return value[:maxWidth] + '...'
return value
xhtmlToText = re.compile('<.*?>', re.S)
def getReferenceLabel(self, brain, appyType):
'''p_appyType is a Ref with link=True. I need to display, on an edit
@ -288,8 +274,8 @@ class ToolMixin(AbstractMixin):
elif isinstance(value, basestring):
value = value.decode('utf-8')
refAppyType = appyObj.o.getAppyType(fieldName)
if refAppyType and (refAppyType['type'] == 'String') and \
(refAppyType['format'] == 2):
if refAppyType and (refAppyType.type == 'String') and \
(refAppyType.format == 2):
value = self.xhtmlToText.sub(' ', value)
else:
value = str(value)
@ -312,13 +298,21 @@ class ToolMixin(AbstractMixin):
appName = self.getProductConfig().PROJECTNAME
return self.utranslate(label, self.translationMapping, domain=appName)
def getPublishedObject(self):
'''Gets the currently published object.'''
def getPublishedObject(self, rootClasses):
'''Gets the currently published object, if its meta_class is among
p_rootClasses or if it is the corresponding tool or flavour.'''
rq = self.REQUEST
obj = rq['PUBLISHED']
parent = obj.getParentNode()
if parent.id == 'skyn': return parent.getParentNode()
return rq['PUBLISHED']
if parent.id == 'skyn':
obj = parent.getParentNode()
if obj.meta_type in rootClasses:
return obj
else:
appName = self.getAppName()
if obj.meta_type in ('%sTool' % appName, '%sFlavour' % appName):
return obj
return None
def getAppyClass(self, contentType):
'''Gets the Appy Python class that is related to p_contentType.'''
@ -359,6 +353,17 @@ class ToolMixin(AbstractMixin):
res[means.id] = means.__dict__
return res
def userMayAdd(self, rootClass):
'''For deciding if a user may add a new instance of a class, beyond the
permission-based check, we can have a custom method that proposes an
additional condition. This method checks if there is such a custom
method (must be named "mayCreate") define on p_rootClass, and calls
it if yes. If no, it returns True.'''
pythonClass = self.getAppyClass(rootClass)
if 'mayCreate' in pythonClass.__dict__:
return pythonClass.mayCreate(self.appy())
return True
def onImportObjects(self):
'''This method is called when the user wants to create objects from
external data.'''
@ -503,7 +508,7 @@ class ToolMixin(AbstractMixin):
appyClass = self.getAppyClass(contentType)
res = []
visitedGroups = {} # Names of already visited search groups
for search in ArchetypesClassDescriptor.getSearches(appyClass):
for search in ClassDescriptor.getSearches(appyClass):
# Determine first group label, we will need it.
groupLabel = ''
if search.group:
@ -610,7 +615,7 @@ class ToolMixin(AbstractMixin):
if t == 'ref': # Manage navigation from a reference
fieldName = d2
masterObj = self.getObject(d1)
batchSize = masterObj.getAppyType(fieldName)['maxPerPage']
batchSize = masterObj.getAppyType(fieldName).maxPerPage
uids = getattr(masterObj, '_appy_%s' % fieldName)
# In the case of a reference, we retrieve ALL surrounding objects.
@ -679,7 +684,6 @@ class ToolMixin(AbstractMixin):
into a list of lists, where every sub-list has length p_numberOfRows.
This method is typically used for rendering elements in a table of
p_numberOfRows rows.'''
if numberOfRows > 1:
res = []
row = []
for elem in data:
@ -692,8 +696,6 @@ class ToolMixin(AbstractMixin):
while len(row) < numberOfRows: row.append(None)
res.append(row)
return res
else:
return data
def truncate(self, value, numberOfChars):
'''Truncates string p_value to p_numberOfChars.'''
@ -708,22 +710,6 @@ class ToolMixin(AbstractMixin):
'''Gets the translated month name of month numbered p_monthNumber.'''
return self.translate(self.monthsIds[int(monthNumber)], domain='plone')
def getSelectValues(self, appyType):
'''Return the possible values (with their translation) of String type
p_appyType (dict version) which is a string whose validator limits
the possible values, either statically (validator is simply a list
of values) or dynamically (validator is a Selection instance).'''
validator = appyType['validator']
if isinstance(validator, Selection):
vocab = self._appy_getDynamicDisplayList(validator.methodName)
return vocab.items()
else:
res = []
for v in validator:
text = self.translate('%s_list_%s' % (appyType['label'], v))
res.append((v, self.truncate(text, 30)))
return res
def logout(self):
'''Logs out the current user when he clicks on "disconnect".'''
rq = self.REQUEST
@ -748,4 +734,20 @@ class ToolMixin(AbstractMixin):
from appy.gen.plone25.installer import loggedUsers
if loggedUsers.has_key(userId): del loggedUsers[userId]
return self.goto(self.getParentNode().absolute_url())
def tempFile(self):
'''A temp file has been created in a temp folder. This method returns
this file to the browser.'''
rq = self.REQUEST
baseFolder = os.path.join(getOsTempFolder(), self.getAppName())
baseFolder = os.path.join(baseFolder, rq.SESSION.id)
fileName = os.path.join(baseFolder, rq.get('name', ''))
if os.path.exists(fileName):
f = file(fileName)
content = f.read()
f.close()
# Remove the temp file
os.remove(fileName)
return content
return 'File does not exist'
# ------------------------------------------------------------------------------

File diff suppressed because it is too large Load diff

View file

@ -7,7 +7,7 @@
# ------------------------------------------------------------------------------
import copy, types
from appy.gen import Type, Integer, String, File, Ref, Boolean, Selection
from appy.gen import Type, Integer, String, File, Ref, Boolean, Selection, Group
# ------------------------------------------------------------------------------
class ModelClass:
@ -17,10 +17,12 @@ class ModelClass:
prefixed with _appy_ in order to avoid name conflicts with user-defined
parts of the application model.'''
_appy_attributes = [] # We need to keep track of attributes order.
_appy_notinit = ('id', 'type', 'pythonType', 'slaves', 'selfClass',
'phase', 'pageShow', 'isSelect') # When creating a new
# instance of a ModelClass, those attributes must not be
# given in the constructor.
# When creating a new instance of a ModelClass, the following attributes
# must not be given in the constructor (they are computed attributes).
_appy_notinit = ('id', 'type', 'pythonType', 'slaves', 'phase', 'pageShow',
'isSelect', 'hasLabel', 'hasDescr', 'hasHelp',
'master_css', 'layouts', 'required', 'filterable',
'validable', 'backd', 'isBack')
@classmethod
def _appy_addField(klass, fieldName, fieldType, classDescr):
@ -38,8 +40,11 @@ class ModelClass:
continue
if isinstance(attrValue, basestring):
attrValue = '"%s"' % attrValue
elif isinstance(attrValue, Type):
elif isinstance(attrValue, Ref):
if attrValue.isBack:
attrValue = klass._appy_getTypeBody(attrValue)
else:
continue
elif type(attrValue) == type(ModelClass):
moduleName = attrValue.__module__
if moduleName.startswith('appy.gen'):
@ -48,6 +53,10 @@ class ModelClass:
attrValue = '%s.%s' % (moduleName, attrValue.__name__)
elif isinstance(attrValue, Selection):
attrValue = 'Selection("%s")' % attrValue.methodName
elif isinstance(attrValue, Group):
attrValue = 'Group("%s")' % attrValue.name
elif type(attrValue) == types.FunctionType:
attrValue = '%sWrapper.%s'% (klass.__name__, attrValue.__name__)
typeArgs += '%s=%s,' % (attrName, attrValue)
return '%s(%s)' % (appyType.__class__.__name__, typeArgs)
@ -106,10 +115,15 @@ class Flavour(ModelClass):
'''From a given p_appyType, produce a type definition suitable for
storing the default value for this field.'''
res = copy.copy(appyType)
# A fiekd in the flavour can't have parameters that would lead to the
# creation of new fields in the flavour.
res.editDefault = False
res.optional = False
res.show = True
res.group = copy.copy(appyType.group)
res.phase = 'main'
# Set default layouts for all Flavour fields
res.layouts = None
res.specificReadPermission = False
res.specificWritePermission = False
res.multiplicity = (0, appyType.multiplicity[1])
@ -136,7 +150,7 @@ class Flavour(ModelClass):
klass._appy_addField(fieldName, fieldType, fieldDescr.classDescr)
fieldType.validator.append(fieldDescr.fieldName)
fieldType.page = 'data'
fieldType.group = fieldDescr.classDescr.klass.__name__
fieldType.group = Group(fieldDescr.classDescr.klass.__name__)
@classmethod
def _appy_addDefaultField(klass, fieldDescr):
@ -145,7 +159,7 @@ class Flavour(ModelClass):
fieldType = klass._appy_copyField(fieldDescr.appyType)
klass._appy_addField(fieldName, fieldType, fieldDescr.classDescr)
fieldType.page = 'data'
fieldType.group = fieldDescr.classDescr.klass.__name__
fieldType.group = Group(fieldDescr.classDescr.klass.__name__)
@classmethod
def _appy_addPodRelatedFields(klass, fieldDescr):
@ -276,7 +290,9 @@ class Tool(ModelClass):
# First arg is None because we don't know yet if it will link
# to the predefined Flavour class or a custom class defined
# in the application.
unoEnabledPython = String(group="connectionToOpenOffice")
def validPythonWithUno(self, value): pass
unoEnabledPython = String(group="connectionToOpenOffice",
validator=validPythonWithUno)
openOfficePort = Integer(default=2002, group="connectionToOpenOffice")
numberOfResultsPerPage = Integer(default=30)
listBoxesMaximumWidth = Integer(default=100)

View file

@ -12,6 +12,7 @@
member context/portal_membership/getAuthenticatedMember;
portal context/portal_url/getPortalObject;
portal_url context/portal_url/getPortalPath;
template python: contextObj.getPageTemplate(portal.skyn, page);
dummy python: response.setHeader('Content-Type','text/html;;charset=utf-8');
dummy2 python: response.setHeader('Expires', 'Mon, 11 Dec 1975 12:05:05 GMT');
dummy3 python: response.setHeader('CacheControl', 'no-cache')">
@ -19,7 +20,7 @@
this page. Indeed, this page is retrieved through an asynchronous XMLHttpRequest by the browser, and
IE caches this by default.</tal:comment>
<tal:executeAction condition="action">
<tal:do define="dummy python: contextObj.getAppyValue('on'+action)()" omit-tag=""/>
<tal:do define="dummy python: contextObj.getMethod('on'+action)()" omit-tag=""/>
</tal:executeAction>
<metal:callMacro use-macro="python: portal.skyn.get(page).macros.get(macro)"/>
<metal:callMacro use-macro="python: template.macros.get(macro)"/>
</tal:ajax>

BIN
gen/plone25/skin/cancel.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View file

@ -12,4 +12,4 @@ else:
from Products.CMFCore.utils import getToolByName
portal = getToolByName(obj, 'portal_url').getPortalObject()
obj = portal.get('portal_%s' % obj.id.lower()) # The tool
return obj.getAppyValue('on'+action)()
return obj.getMethod('on'+action)()

View file

@ -1,24 +1,18 @@
<tal:block metal:define-macro="master"
<tal:edit metal:define-macro="master"
define="contextObj python:context.getParentNode();
errors request/errors | python:{};
schematas contextObj/Schemata;
fieldsets python:[key for key in schematas.keys() if (key != 'metadata') and (schematas[key].editableFields(contextObj, visible_only=True))];
default_fieldset python:(not schematas or schematas.has_key('default')) and 'default' or fieldsets[0];
fieldset request/fieldset|options/fieldset|default_fieldset;
fields python:schematas[fieldset].editableFields(contextObj);
lockable python:hasattr(contextObj, 'wl_isLocked');
isLocked python:lockable and contextObj.wl_isLocked();
isEdit python:True;
layoutType python:'edit';
layout python: contextObj.getPageLayout(layoutType);
tool contextObj/getTool;
flavour python: tool.getFlavour(contextObj);
appFolder tool/getAppFolder;
appName appFolder/id;
css python:contextObj.getUniqueWidgetAttr(fields, 'helper_css');
js python:contextObj.getUniqueWidgetAttr(fields, 'helper_js');
phaseInfo python: contextObj.getAppyPhases(fieldset=fieldset, forPlone=True);
phase request/phase|phaseInfo/name;
appyPages python: contextObj.getAppyPages(phase);
pageName python: contextObj.getAppyPage(isEdit, phaseInfo, appyPages);">
appName appFolder/getId;
page request/page|python:'main';
cssAndJs python: contextObj.getCssAndJs(layoutType, page);
css python: cssAndJs[0];
js python: cssAndJs[1];
phaseInfo python: contextObj.getAppyPhases(page=page);
phase phaseInfo/name">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"
xmlns:tal="http://xml.zope.org/namespaces/tal"
@ -32,80 +26,38 @@
<div tal:define="dummy python:request.set('disable_border', 1)" />
</div>
<tal:comment replace="nothing">Archetypes stuff for managing Javascript and CSS.
If I remove this stuff, Javascript popup for dates does not work anyore.</tal:comment>
<metal:javascript_head fill-slot="javascript_head_slot">
<tal:block define="macro here/archetypes_custom_js/macros/javascript_head | nothing"
condition="macro">
<metal:block use-macro="macro" />
</tal:block>
<tal:js condition="js" repeat="item js">
<tal:comment replace="nothing">Include type-specific CSS and JS.</tal:comment>
<metal:js fill-slot="javascript_head_slot">
<tal:js condition="js" repeat="jsFile js">
<script type="text/javascript" charset="iso-8859-1"
tal:condition="python:exists('portal/%s' % item)"
tal:attributes="src string:$portal_url/$item">
tal:condition="python:exists('portal/%s' % jsFile)"
tal:attributes="src string:$portal_url/$jsFile">
</script>
</tal:js>
<tal:block define="macro edit_macros/javascript_head | nothing" condition="macro">
<metal:block use-macro="macro" />
</tal:block>
</metal:javascript_head>
</metal:js>
<metal:css fill-slot="css_slot">
<tal:css condition="css" repeat="item css">
<tal:css condition="css" repeat="cssFile css">
<style type="text/css" media="all"
tal:condition="python:exists('portal/%s' % item)"
tal:content="structure string:<!-- @import url($portal_url/$item); -->">
tal:condition="python:exists('portal/%s' % cssFile)"
tal:content="structure string:<!-- @import url($portal_url/$cssFile); -->">
</style>
</tal:css>
<tal:block define="macro edit_macros/css | nothing" condition="macro">
<metal:block use-macro="macro" />
</tal:block>
</metal:css>
<body>
<metal:fill fill-slot="main">
<div metal:use-macro="here/skyn/macros/macros/pagePrologue"/>
<div metal:use-macro="here/skyn/macros/macros/showPageHeader"/>
<metal:prologue use-macro="here/skyn/page/macros/prologue"/>
<form name="edit_form" method="post" enctype="multipart/form-data"
class="enableUnloadProtection atBaseEditForm"
tal:attributes="action python: contextObj.absolute_url()+'/skyn/do'">
<div metal:use-macro="here/skyn/macros/macros/listFields" /><br/>
<input type="hidden" name="action" value="Update"/>
<input type="hidden" name="fieldset" tal:attributes="value fieldset"/>
<input type="hidden" name="pageName" tal:attributes="value pageName"/>
<input type="hidden" name="phase" tal:attributes="value phase"/>
<input type="hidden" name="page" tal:attributes="value page"/>
<input type="hidden" name="nav" tal:attributes="value python:request.get('nav', '')"/>
<input type="hidden" name="is_new"
tal:attributes="value python: '/portal_factory/' in contextObj.absolute_url()"/>
<tal:comment replace="nothing">Buttons (Previous, Next, Save, etc)</tal:comment>
<metal:block define-slot="buttons"
tal:define="pages phaseInfo/pages;
pageIndex python:pages.index(fieldset);
numberOfPages python:len(pages)">
<tal:previousButton condition="python: pageIndex &gt; 0">
<input class="context" type="submit" name="buttonPrevious"
i18n:attributes="value label_previous;" i18n:domain="plone"
tal:attributes="disabled python:test(isLocked, 'disabled', None);"/>
<input type="hidden" name="previousPage" tal:attributes="value python: pages[pageIndex-1]"/>
</tal:previousButton>
<tal:nextButton condition="python: pageIndex &lt; numberOfPages - 1">
<input class="context" type="submit" name="buttonNext" value="Next"
i18n:attributes="value label_next;" i18n:domain="plone"
tal:attributes="disabled python:test(isLocked, 'disabled', None);"/>
<input type="hidden" name="nextPage" tal:attributes="value python: pages[pageIndex+1]"/>
</tal:nextButton>
<input class="context" type="submit" name="buttonOk"
i18n:attributes="value label_save;" i18n:domain="plone"
tal:attributes="disabled python:test(isLocked, 'disabled', None);"/>
<input class="standalone" type="submit" name="buttonCancel"
i18n:attributes="value label_cancel;" i18n:domain="plone"/>
</metal:block>
<input type="hidden" name="is_new" tal:attributes="value contextObj/checkCreationFlag"/>
<metal:show use-macro="here/skyn/page/macros/show"/>
</form>
<div metal:use-macro="here/skyn/macros/macros/showPageFooter"/>
<metal:footer use-macro="here/skyn/page/macros/footer"/>
</metal:fill>
</body>
</html>
</tal:block>
</tal:edit>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
gen/plone25/skin/help.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 B

BIN
gen/plone25/skin/help.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 735 B

View file

@ -20,7 +20,7 @@
importElems python: flavour.getImportElements(contentType);
global allAreImported python:True">
<div metal:use-macro="here/skyn/macros/macros/pagePrologue"/>
<div metal:use-macro="here/skyn/page/macros/prologue"/>
<script language="javascript">
<!--
var importedElemsShown = false;

View file

@ -1,814 +1,3 @@
<div metal:define-macro="listPodTemplates" class="appyPod" tal:condition="podTemplates"
tal:define="podTemplates python: flavour.getAvailablePodTemplates(contextObj, phase);">
<tal:podTemplates define="maxShownTemplates python: flavour.getMaxShownTemplates(contextObj)">
<tal:comment replace="nothing">Display templates as links if a few number of templates must be shown</tal:comment>
<span class="discreet" tal:condition="python: len(podTemplates)&lt;=maxShownTemplates"
tal:repeat="podTemplate podTemplates">
<a style="cursor: pointer"
tal:define="podFormat podTemplate/getPodFormat"
tal:attributes="onclick python: 'javascript:generatePodDocument(\'%s\',\'%s\', \'\', \'\')' % (contextObj.UID(), podTemplate.UID())" >
<img tal:attributes="src string: $portal_url/skyn/$podFormat.png"/>
<span tal:replace="podTemplate/Title"/>
</a>
&nbsp;</span>
<tal:comment replace="nothing">Display templates as a list if a lot of templates must be shown</tal:comment>
<select tal:condition="python: len(podTemplates)&gt;maxShownTemplates">
<option value="" tal:content="python: tool.translate('choose_a_doc')"></option>
<option tal:repeat="podTemplate podTemplates" tal:content="podTemplate/Title"
tal:attributes="onclick python: 'javascript:generatePodDocument(\'%s\',\'%s\', \'\', \'\')' % (contextObj.UID(), podTemplate.UID())" />
</select>
</tal:podTemplates>
</div>
<metal:editString define-macro="editString" tal:define="vocab python:field.Vocabulary(contextObj)">
<label tal:attributes="for fieldName" tal:condition="showLabel" tal:content="label"/>&nbsp;
<span class="fieldRequired" tal:condition="python: appyType['multiplicity'][0] &gt; 0"></span>
<div class="discreet" tal:content="structure description"/>
<select tal:attributes="name fieldName;
id fieldName;
tabindex tabindex/next;
multiple isMultiple;
onchange python: 'javascript:updateSlaves(getMasterValue(this), \'%s\')' % appyType['id'];
class python: contextObj.getCssClasses(appyType, asSlave=False)">
<option tal:repeat="item vocab" i18n:translate=""
tal:attributes="value item;
selected python:contextObj.fieldValueSelected(fieldName, value, item)"
tal:content="python:here.translate(vocab.getMsgId(item), default=vocab.getValue(item))"/>
</select>
</metal:editString>
<metal:editBoolean define-macro="editBoolean">
<input type="checkbox"
tal:attributes="tabindex tabindex/next;
name python: fieldName + '_visible';
id fieldName;
checked python:contextObj.checkboxChecked(fieldName, value);
onClick python:'toggleCheckbox(\'%s\', \'%s_hidden\');;updateSlaves(getMasterValue(this), \'%s\')' % (fieldName, fieldName, appyType['id']);
class python: 'noborder ' + contextObj.getCssClasses(appyType, asSlave=False)"/>
<input tal:attributes="name fieldName;
id string:${fieldName}_hidden;
value python: test(contextObj.checkboxChecked(fieldName, value), 'True', 'False')"
type="hidden" />
<span class="fieldRequired" tal:condition="python: appyType['multiplicity'][0] &gt; 0"></span>
<label tal:attributes="for fieldName" tal:condition="showLabel" tal:content="label"/>
<div class="discreet" tal:content="structure description"/>
</metal:editBoolean>
<div metal:define-macro="editField"
tal:define="fieldName field/getName;
isMultiple python:test(appyType['multiplicity'][1]!=1, 'multiple', '');
inError python:test(errors.has_key(fieldName), True, False);
value python:field.getAccessor(contextObj)();
defaultValue python: contextObj.getDefault(fieldName)"
tal:attributes="class python:'field ' + test(inError, ' error', '')">
<div tal:condition="inError" tal:content="python: errors[fieldName]"></div>
<tal:stringField condition="python: appyType['type'] == 'String'">
<metal:edit use-macro="here/skyn/macros/macros/editString"/>
</tal:stringField>
<tal:booleanField condition="python: appyType['type'] == 'Boolean'">
<metal:edit use-macro="here/skyn/macros/macros/editBoolean"/>
</tal:booleanField>
</div>
<div metal:define-macro="showComputedField">
<span class="appyLabel" tal:condition="showLabel" tal:content="label"></span>
<span class="formHelp" tal:content="structure description"></span>
<tal:showValue define="theValue python: contextObj.getComputedValue(appyType)">
<span tal:condition="appyType/plainText" tal:replace="theValue"/>
<span tal:condition="not: appyType/plainText" tal:replace="structure theValue"/>
</tal:showValue>
</div>
<div metal:define-macro="showInfoField">
<span class="appyLabel" tal:content="structure label"></span>
<span class="formHelp" tal:content="structure description"></span>
</div>
<div metal:define-macro="showActionField">
<form name="executeAppyAction"
tal:define="formId python: '%s_%s' % (contextObj.UID(), field.getName())"
tal:attributes="id formId; action python: contextObj.absolute_url()+'/skyn/do'">
<input type="hidden" name="action" value="ExecuteAppyAction"/>
<input type="hidden" name="objectUid" tal:attributes="value contextObj/UID"/>
<input type="hidden" name="fieldName" tal:attributes="value field/getName"/>
<input type="button" tal:condition="appyType/confirm"
tal:attributes="value label; onClick python: 'javascript:askConfirm(\'%s\')' % formId"/>
<input type="submit" name="do" tal:condition="not: appyType/confirm"
tal:attributes="value label" onClick="javascript:;"/>
<tal:comment replace="nothing">The previous onClick is simply used to prevent Plone
from adding a CSS class that displays a popup when the user triggers the form multiple
times.</tal:comment>
</form>
</div>
<metal:showDate define-macro="showDateField"
tal:define="v python: contextObj.getAppyValue(field.getName(), appyType)">
<span tal:condition="showLabel" tal:content="label" class="appyLabel"></span>
<span tal:replace="v"></span>
</metal:showDate>
<metal:showFloat define-macro="showFloatField"
tal:define="v python: contextObj.getAppyValue(field.getName(), appyType)">
<span tal:condition="showLabel" tal:content="label"
tal:attributes="class python: 'appyLabel ' + contextObj.getCssClasses(appyType, asSlave=False);
id python: v"></span>
<span tal:replace="v"></span>
</metal:showFloat>
<metal:showString define-macro="showStringField"
tal:define="v python: contextObj.getAppyValue(field.getName(), appyType);
fmt python: appyType['format'];
maxMult python: appyType['multiplicity'][1];
severalValues python: (maxMult == None) or (maxMult &gt; 1)">
<tal:simpleString condition="python: fmt in (0, 3)">
<span tal:condition="showLabel" tal:content="label" class="appyLabel"
tal:attributes="class python: 'appyLabel ' + contextObj.getCssClasses(appyType, asSlave=False);
id python: contextObj.getAppyValue(field.getName(), appyType, forMasterId=True)"></span>
<ul class="appyList" tal:condition="python: v and severalValues">
<li class="appyBullet" tal:repeat="sv v"><i tal:content="structure sv"></i></li>
</ul>
<tal:singleValue condition="python: v and not severalValues">
<span tal:condition="python: fmt != 3" tal:replace="structure v"/>
<span tal:condition="python: fmt == 3">********</span>
</tal:singleValue>
</tal:simpleString>
<tal:formattedString condition="python: fmt not in (0, 3)">
<fieldset>
<legend tal:condition="showLabel" tal:content="label"></legend>
<span tal:condition="python: v and (appyType['format'] == 1)"
tal:replace="structure python: v.replace('\n', '&lt;br&gt;')"/>
<span tal:condition="python: v and (appyType['format'] == 2)" tal:replace="structure v"/>
</fieldset>
</tal:formattedString>
</metal:showString>
<metal:showPod define-macro="showPodField"
tal:define="fieldName field/getName">
<tal:askAction condition="appyType/askAction"
define="doLabel python:'%s_askaction' % appyType['label'];
chekboxId python: '%s_%s' % (contextObj.UID(), fieldName)">
<input type="checkbox" tal:attributes="name doLabel; id chekboxId"/>
<label tal:attributes="for chekboxId" class="discreet"
tal:content="python: tool.translate(doLabel)"></label>
</tal:askAction>
<img tal:repeat="podFormat python:flavour.getPodInfo(contextObj, fieldName)['formats']"
tal:attributes="src string: $portal_url/skyn/${podFormat}.png;
title label;
onClick python: 'javascript:generatePodDocument(\'%s\',\'\',\'%s\',\'%s\')' % (contextObj.UID(), fieldName, podFormat)"
style="cursor:pointer"/>
</metal:showPod>
<div metal:define-macro="showArchetypesField"
tal:define="field fieldDescr/atField|widgetDescr/atField;
appyType fieldDescr/appyType|widgetDescr/appyType;
showLabel showLabel|python:True;
labelId field/widget/label_msgid;
label python: tool.translate(labelId);
descrId field/widget/description_msgid|python:'';
description python: tool.translate(descrId)"
tal:attributes="class python: contextObj.getCssClasses(appyType, asSlave=True)">
<tal:comment replace="nothing">For some fields we simply use the standard Archetypes
macro for showing it. Special Appy field types like Ref and Computed have their
corresponding Archetypes fields set as invisible, so they won't be shown by the following
tal:showField.</tal:comment>
<tal:showField define="mode python:test(isEdit, 'edit', 'view');"
tal:condition="python: test(isEdit, member.has_permission(field.write_permission, contextObj), member.has_permission(field.read_permission, contextObj))">
<tal:editField condition="isEdit">
<metal:editMacro use-macro="python:contextObj.widget(field.getName(), mode='edit', use_label=showLabel)" />
</tal:editField>
<tal:viewField tal:condition="not: isEdit">
<tal:fileField condition="python: (appyType['type'] == 'File')">
<span tal:condition="showLabel" tal:content="label" class="appyLabel"></span>
<metal:viewField use-macro="python: contextObj.widget(field.getName(), 'view', use_label=0)"/>
</tal:fileField>
<tal:date condition="python: appyType['type'] == 'Date'">
<metal:showDate use-macro="here/skyn/macros/macros/showDateField"/>
</tal:date>
<tal:string condition="python: appyType['type'] == 'String'">
<metal:showString use-macro="here/skyn/macros/macros/showStringField"/>
</tal:string>
<tal:float condition="python: appyType['type'] == 'Float'">
<metal:showFloat use-macro="here/skyn/macros/macros/showFloatField"/>
</tal:float>
<tal:simpleField condition="python: (appyType['type'] in ('Integer', 'Boolean'))">
<span tal:condition="showLabel" tal:content="label"
tal:attributes="class python: 'appyLabel ' + contextObj.getCssClasses(appyType, asSlave=False);
id python: field.getAccessor(contextObj)()"></span>
<metal:viewField use-macro="python: contextObj.widget(field.getName(), 'view', use_label=0)"/>
</tal:simpleField>
</tal:viewField>
</tal:showField>
<tal:comment replace="nothing">For other fields like Refs we use specific view/edit macros.</tal:comment>
<tal:viewRef condition="python: (not isEdit) and (appyType['type'] == 'Ref')">
<tal:ref define="isBack python:False;
fieldName field/getName;
innerRef innerRef|python:False">
<metal:viewRef use-macro="here/skyn/ref/macros/showReference" />
</tal:ref>
</tal:viewRef>
<tal:editRef condition="python: isEdit and (appyType['type'] == 'Ref')">
<tal:ref define="appyType fieldDescr/appyType|widgetDescr/appyType"
condition="python: appyType['link']==True">
<metal:editRef use-macro="here/skyn/ref/macros/editReference" />
</tal:ref>
</tal:editRef>
<tal:computedField condition="python: appyType['type'] == 'Computed'">
<metal:cf use-macro="here/skyn/macros/macros/showComputedField" />
</tal:computedField>
<tal:actionField condition="python: appyType['type'] == 'Action'">
<metal:af use-macro="here/skyn/macros/macros/showActionField" />
</tal:actionField>
<tal:masterString condition="python: isEdit and (appyType['type'] in ('String', 'Boolean')) and (appyType['slaves'])">
<metal:mf use-macro="here/skyn/macros/macros/editField" />
</tal:masterString>
<tal:infoField condition="python: appyType['type'] == 'Info'">
<metal:af use-macro="here/skyn/macros/macros/showInfoField" />
</tal:infoField>
<tal:podField condition="python: (not isEdit) and (appyType['type'] == 'Pod')">
<metal:af use-macro="here/skyn/macros/macros/showPodField" />
</tal:podField>
</div>
<div metal:define-macro="showBackwardField"
tal:define="isBack python:True;
appyType widgetDescr/appyType;
fieldName widgetDescr/fieldRel;
labelId python: '%s_%s_back' % (contextObj.meta_type, appyType['backd']['attribute']);
descrId python: '';
innerRef innerRef|python:False">
<div metal:use-macro="here/skyn/ref/macros/showReference" />
</div>
<metal:group define-macro="showGroup">
<fieldset class="appyGroup">
<legend><i tal:define="groupDescription python:contextObj.translate('%s_group_%s' % (contextObj.meta_type, widgetDescr['name']))"
tal:content="structure groupDescription"></i></legend>
<table tal:define="global fieldNb python:-1" width="100%">
<tr valign="top" tal:repeat="rowNb python:range(widgetDescr['rows'])">
<td tal:repeat="colNb python:range(widgetDescr['cols'])"
tal:attributes="width python: str(100.0/widgetDescr['cols']) + '%'">
<tal:showField define="global fieldNb python:fieldNb+1;
hasFieldDescr python: test(fieldNb < len(widgetDescr['fields']), True, False);"
tal:condition="hasFieldDescr">
<tal:field define="fieldDescr python:widgetDescr['fields'][fieldNb]">
<tal:archetypesField condition="python: fieldDescr['widgetType'] == 'field'">
<metal:atField use-macro="here/skyn/macros/macros/showArchetypesField"/>
</tal:archetypesField>
<tal:backwardRef tal:condition="python: (not isEdit) and (fieldDescr['widgetType'] == 'backField')">
<metal:backRef use-macro="here/skyn/macros/macros/showBackwardField" />
</tal:backwardRef>
</tal:field>
</tal:showField>
</td>
</tr>
</table>
</fieldset>
<br/>
</metal:group>
<metal:fields define-macro="listFields"
tal:repeat="widgetDescr python: contextObj.getAppyFields(isEdit, pageName)">
<tal:displayArchetypesField condition="python: widgetDescr['widgetType'] == 'field'">
<tal:atField condition="python: widgetDescr['page'] == pageName">
<metal:field use-macro="here/skyn/macros/macros/showArchetypesField" />
</tal:atField>
</tal:displayArchetypesField>
<tal:displayBackwardRef condition="python: (not isEdit) and (widgetDescr['widgetType'] == 'backField')">
<tal:backRef condition="python: widgetDescr['appyType']['backd']['page'] == pageName">
<metal:field metal:use-macro="here/skyn/macros/macros/showBackwardField" />
</tal:backRef>
</tal:displayBackwardRef>
<tal:displayGroup condition="python: widgetDescr['widgetType'] == 'group'">
<tal:displayG condition="python: widgetDescr['page'] == pageName">
<metal:group metal:use-macro="here/skyn/macros/macros/showGroup" />
</tal:displayG>
</tal:displayGroup>
</metal:fields>
<metal:history define-macro="history"
tal:define="startNumber request/startNumber|python:0;
startNumber python: int(startNumber);
historyInfo python: contextObj.getHistory(startNumber);
objs historyInfo/events;
batchSize historyInfo/batchSize;
totalNumber historyInfo/totalNumber;
ajaxHookId python:'appyHistory';
navBaseCall python: 'askObjectHistory(\'%s\',\'%s\',**v**)' % (ajaxHookId, contextObj.absolute_url());
tool contextObj/getTool">
<tal:comment replace="nothing">Table containing the history</tal:comment>
<tal:history condition="objs">
<metal:nav use-macro="here/skyn/navigate/macros/appyNavigate"/>
<table width="100%" class="listing nosort">
<tr i18n:domain="plone">
<th i18n:translate="listingheader_action"/>
<th i18n:translate="listingheader_performed_by"/>
<th i18n:translate="listingheader_date_and_time"/>
<th i18n:translate="listingheader_comment"/>
</tr>
<tal:event repeat="event objs">
<tr tal:define="odd repeat/event/odd;
rhComments event/comments|nothing;
state event/review_state|nothing;
isDataChange python: event['action'] == '_datachange_'"
tal:attributes="class python:test(odd, 'even', 'odd')" valign="top">
<td tal:condition="isDataChange" tal:content="python: tool.translate('data_change')"></td>
<td tal:condition="not: isDataChange"
tal:content="python: tool.translate(contextObj.getWorkflowLabel(event['action']))"
tal:attributes="class string:state-${state}"/>
<td tal:define="actorid python:event.get('actor');
actor python:contextObj.portal_membership.getMemberInfo(actorid);
fullname actor/fullname|nothing;
username actor/username|nothing"
tal:content="python:fullname or username or actorid"/>
<td tal:content="python:contextObj.restrictedTraverse('@@plone').toLocalizedTime(event['time'],long_format=True)"/>
<td tal:condition="not: isDataChange"><tal:comment condition="rhComments" tal:content="structure rhComments"/>
<tal:noComment condition="not: rhComments" i18n:translate="no_comments" i18n:domain="plone"/></td>
<td tal:condition="isDataChange">
<tal:comment replace="nothing">
Display the previous values of the fields whose value were modified in this change.</tal:comment>
<table class="appyChanges" width="100%">
<tr>
<th align="left" width="30%" tal:content="python: tool.translate('modified_field')"></th>
<th align="left" width="70%" tal:content="python: tool.translate('previous_value')"></th>
</tr>
<tr tal:repeat="change event/changes/items" valign="top">
<td tal:content="python: tool.translate(change[1][1])"></td>
<td tal:define="appyType python:contextObj.getAppyType(change[0]);
appyValue python: contextObj.getAppyValue(change[0], appyType, True, change[1][0]);
severalValues python: (appyType['multiplicity'][1] &gt; 1) or (appyType['multiplicity'][1] == None)">
<span tal:condition="not: severalValues" tal:replace="appyValue"></span>
<ul tal:condition="python: severalValues">
<li tal:repeat="av appyValue" tal:content="av"></li>
</ul>
</td>
</tr>
</table>
</td>
</tr>
</tal:event>
</table>
</tal:history>
</metal:history>
<div metal:define-macro="pagePrologue">
<tal:comment replace="nothing">Global elements used in every page.</tal:comment>
<tal:comment replace="nothing">Javascript messages</tal:comment>
<script language="javascript" tal:content="tool/getJavascriptMessages"></script>
<tal:comment replace="nothing">"Static" javascripts</tal:comment>
<script language="javascript">
<!--
var isIe = (navigator.appName == "Microsoft Internet Explorer");
// AJAX machinery
var xhrObjects = new Array(); // An array of XMLHttpRequest objects
function XhrObject() { // Wraps a XmlHttpRequest object
this.freed = 1; // Is this xhr object already dealing with a request or not?
this.xhr = false;
if (window.XMLHttpRequest) this.xhr = new XMLHttpRequest();
else this.xhr = new ActiveXObject("Microsoft.XMLHTTP");
this.hook = ''; /* The ID of the HTML element in the page that will be
replaced by result of executing the Ajax request. */
this.onGet = ''; /* The name of a Javascript function to call once we
receive the result. */
this.info = {}; /* An associative array for putting anything else. */
}
function getAjaxChunk(pos) {
// This function is the callback called by the AJAX machinery (see function
// askAjaxChunk below) when an Ajax response is available.
// First, find back the correct XMLHttpRequest object
if ( (typeof(xhrObjects[pos]) != 'undefined') &&
(xhrObjects[pos].freed == 0)) {
var hook = xhrObjects[pos].hook;
if (xhrObjects[pos].xhr.readyState == 1) {
// The request has been initialized: display the waiting radar
var hookElem = document.getElementById(hook);
if (hookElem) hookElem.innerHTML = "<div align=\"center\"><img src=\"skyn/waiting.gif\"/><\/div>";
}
if (xhrObjects[pos].xhr.readyState == 4) {
// We have received the HTML chunk
var hookElem = document.getElementById(hook);
if (hookElem && (xhrObjects[pos].xhr.status == 200)) {
hookElem.innerHTML = xhrObjects[pos].xhr.responseText;
// Call a custom Javascript function if required
if (xhrObjects[pos].onGet) {
xhrObjects[pos].onGet(xhrObjects[pos], hookElem);
}
}
xhrObjects[pos].freed = 1;
}
}
}
function askAjaxChunk(hook,mode,url,page,macro,params,beforeSend,onGet) {
/* This function will ask to get a chunk of HTML on the server through a
XMLHttpRequest. p_mode can be 'GET' or 'POST'. p_url is the URL of a
given server object. On this URL we will call the page "ajax.pt" that
will call a specific p_macro in a given p_page with some additional
p_params (must be an associative array) if required.
p_hook is the ID of the HTML element that will be filled with the HTML
result from the server.
p_beforeSend is a Javascript function to call before sending the request.
This function will get 2 args: the XMLHttpRequest object and the
p_params. This method can return, in a string, additional parameters to
send, ie: "&param1=blabla&param2=blabla".
p_onGet is a Javascript function to call when we will receive the answer.
This function will get 2 args, too: the XMLHttpRequest object and the
HTML node element into which the result has been inserted.
*/
// First, get a non-busy XMLHttpRequest object.
var pos = -1;
for (var i=0; i < xhrObjects.length; i++) {
if (xhrObjects[i].freed == 1) { pos = i; break; }
}
if (pos == -1) {
pos = xhrObjects.length;
xhrObjects[pos] = new XhrObject();
}
xhrObjects[pos].hook = hook;
xhrObjects[pos].onGet = onGet;
if (xhrObjects[pos].xhr) {
var rq = xhrObjects[pos];
rq.freed = 0;
// Construct parameters
var paramsFull = 'page=' + page + '&macro=' + macro;
if (params) {
for (var paramName in params)
paramsFull = paramsFull + '&' + paramName + '=' + params[paramName];
}
// Call beforeSend if required
if (beforeSend) {
var res = beforeSend(rq, params);
if (res) paramsFull = paramsFull + res;
}
// Construct the URL to call
var urlFull = url + '/skyn/ajax';
if (mode == 'GET') {
urlFull = urlFull + '?' + paramsFull;
}
// Perform the asynchronous HTTP GET or POST
rq.xhr.open(mode, urlFull, true);
if (mode == 'POST') {
// Set the correct HTTP headers
rq.xhr.setRequestHeader(
"Content-Type", "application/x-www-form-urlencoded");
rq.xhr.setRequestHeader("Content-length", paramsFull.length);
rq.xhr.setRequestHeader("Connection", "close");
rq.xhr.onreadystatechange = function(){ getAjaxChunk(pos); }
rq.xhr.send(paramsFull);
}
else if (mode == 'GET') {
rq.xhr.onreadystatechange = function() { getAjaxChunk(pos); }
if (window.XMLHttpRequest) { rq.xhr.send(null); }
else if (window.ActiveXObject) { rq.xhr.send(); }
}
}
}
/* The functions below wrap askAjaxChunk for getting specific content through
an Ajax request. */
function askQueryResult(hookId, objectUrl, contentType, flavourNumber,
searchName, startNumber, sortKey, sortOrder, filterKey) {
// Sends an Ajax request for getting the result of a query.
var params = {'type_name': contentType, 'flavourNumber': flavourNumber,
'search': searchName, 'startNumber': startNumber};
if (sortKey) params['sortKey'] = sortKey;
if (sortOrder) params['sortOrder'] = sortOrder;
if (filterKey) {
var filterWidget = document.getElementById(hookId + '_' + filterKey);
if (filterWidget && filterWidget.value) {
params['filterKey'] = filterKey;
params['filterValue'] = filterWidget.value;
}
}
askAjaxChunk(hookId,'GET',objectUrl,'macros','queryResult',params);
}
function askObjectHistory(hookId, objectUrl, startNumber) {
// Sends an Ajax request for getting the history of an object
var params = {'startNumber': startNumber};
askAjaxChunk(hookId, 'GET', objectUrl, 'macros', 'history', params);
}
function askRefField(hookId, objectUrl, fieldName, isBack, innerRef, labelId,
descrId, startNumber, action, actionParams){
// Sends an Ajax request for getting the content of a reference field.
var startKey = hookId + '_startNumber';
var params = {'fieldName': fieldName, 'isBack': isBack,
'innerRef': innerRef, 'labelId': labelId,
'descrId': descrId };
params[startKey] = startNumber;
if (action) params['action'] = action;
if (actionParams) {
for (key in actionParams) { params[key] = actionParams[key]; };
}
askAjaxChunk(hookId, 'GET', objectUrl, 'ref', 'showReferenceContent',
params);
}
// Function used by checkbox widgets for having radio-button-like behaviour
function toggleCheckbox(visibleCheckbox, hiddenBoolean) {
vis = document.getElementById(visibleCheckbox);
hidden = document.getElementById(hiddenBoolean);
if (vis.checked) hidden.value = 'True';
else hidden.value = 'False';
}
// Functions used for master/slave relationships between widgets
function getMasterValue(widget) {
// Returns an array of selected options in a select widget
res = new Array();
if (widget.type == 'checkbox') {
var mv = widget.checked + '';
mv = mv.charAt(0).toUpperCase() + mv.substr(1);
res.push(mv);
}
else { // SELECT widget
for (var i=0; i < widget.options.length; i++) {
if (widget.options[i].selected) res.push(widget.options[i].value);
}
}
return res;
}
function updateSlaves(masterValues, appyTypeId) {
// Given the value(s) selected in a master field, this function updates the
// state of all corresponding slaves.
var slaves = cssQuery('div.slave_' + appyTypeId);
for (var i=0; i< slaves.length; i++){
slaves[i].style.display = "none";
}
for (var i=0; i < masterValues.length; i++) {
var activeSlaves = cssQuery('div.slaveValue_' + appyTypeId + '_' + masterValues[i]);
for (var j=0; j < activeSlaves.length; j++){
activeSlaves[j].style.display = "";
}
}
}
// Function used for triggering a workflow transition
function triggerTransition(transitionId) {
var theForm = document.getElementById('triggerTransitionForm');
theForm.workflow_action.value = transitionId;
theForm.submit();
}
function onDeleteObject(objectUid) {
if (confirm(delete_confirm)) {
f = document.getElementById('deleteForm');
f.objectUid.value = objectUid;
f.submit();
}
}
function toggleCookie(cookieId) {
// What is the state of this boolean (expanded/collapsed) cookie?
var state = readCookie(cookieId);
if ((state != 'collapsed') && (state != 'expanded')) {
// No cookie yet, create it.
createCookie(cookieId, 'collapsed');
state = 'collapsed';
}
var hook = document.getElementById(cookieId); // The hook is the part of
// the HTML document that needs to be shown or hidden.
var displayValue = 'none';
var newState = 'collapsed';
var imgSrc = 'skyn/expand.gif';
if (state == 'collapsed') {
// Show the HTML zone
displayValue = 'block';
imgSrc = 'skyn/collapse.gif';
newState = 'expanded';
}
// Update the corresponding HTML element
hook.style.display = displayValue;
var img = document.getElementById(cookieId + '_img');
img.src = imgSrc;
// Inverse the cookie value
createCookie(cookieId, newState);
}
// Function that allows to generate a document from a pod template.
function generatePodDocument(contextUid, templateUid, fieldName, podFormat) {
var theForm = document.forms["podTemplateForm"];
theForm.objectUid.value = contextUid;
theForm.templateUid.value = templateUid;
theForm.fieldName.value = fieldName;
theForm.podFormat.value = podFormat;
theForm.askAction.value = "False";
var askActionWidget = document.getElementById(contextUid + '_' + fieldName);
if (askActionWidget && askActionWidget.checked) {
theForm.askAction.value = "True";
}
theForm.submit();
}
// Functions for opening and closing a popup
function openPopup(popupId) {
// Open the popup
var popup = document.getElementById(popupId);
// Put it at the right place on the screen
var scrollTop = window.pageYOffset || document.documentElement.scrollTop || 0;
popup.style.top = (scrollTop + 150) + 'px';
popup.style.display = "block";
// Show the greyed zone
var greyed = document.getElementById('appyGrey');
greyed.style.top = scrollTop + 'px';
greyed.style.display = "block";
}
function closePopup(popupId) {
// Close the popup
var popup = document.getElementById(popupId);
popup.style.display = "none";
// Hide the greyed zone
var greyed = document.getElementById('appyGrey');
greyed.style.display = "none";
}
// Function triggered when an action needs to be confirmed by the user
function askConfirm(formId) {
// Store the ID of the form to send if the users confirms.
var confirmForm = document.getElementById('confirmActionForm');
confirmForm.actionFormId.value = formId;
openPopup("confirmActionPopup");
}
// Function triggered when an action confirmed by the user must be performed
function doConfirm() {
// The user confirmed: retrieve the form to send and send it.
var confirmForm = document.getElementById('confirmActionForm');
var actionFormId = confirmForm.actionFormId.value;
var actionForm = document.getElementById(actionFormId);
actionForm.submit();
}
-->
</script>
<tal:comment replace="nothing">Global form for deleting an object</tal:comment>
<form id="deleteForm" method="post" action="skyn/do">
<input type="hidden" name="action" value="Delete"/>
<input type="hidden" name="objectUid"/>
</form>
<tal:comment replace="nothing">Global form for generating a document from a pod template.</tal:comment>
<form name="podTemplateForm" method="post"
tal:attributes="action python: flavour.absolute_url() + '/generateDocument'">
<input type="hidden" name="objectUid"/>
<tal:comment replace="nothing">templateUid is given if class-wide pod, fieldName and podFormat are given if podField.</tal:comment>
<input type="hidden" name="templateUid"/>
<input type="hidden" name="fieldName"/>
<input type="hidden" name="podFormat"/>
<input type="hidden" name="askAction"/>
</form>
</div>
<div metal:define-macro="showPageHeader"
tal:define="showCommonInfo python: not isEdit;
hasHistory contextObj/hasHistory;
historyExpanded python: tool.getCookieValue('appyHistory', default='collapsed') == 'expanded';
creator contextObj/Creator"
tal:condition="not: contextObj/isTemporary">
<tal:comment replace="nothing">Information that is common to all tabs (object title, state, etc)</tal:comment>
<table width="100%" tal:condition="showCommonInfo" class="appyCommonInfo">
<tr valign="bottom">
<tal:comment replace="nothing">Title, edit icon and state</tal:comment>
<td width="80%">
<b class="appyTitle" tal:content="contextObj/title_or_id"></b>
<tal:comment replace="nothing">Show the phase name tied to this page</tal:comment>
<span class="discreet" tal:condition="python: phaseInfo['totalNbOfPhases']&gt;1">&minus;
<span tal:replace="python:contextObj.translate('phase')"/>:
<span tal:replace="python:tool.translate('%s_phase_%s' % (contextObj.meta_type, phase))"/>
</span>
<tal:comment replace="nothing">When no tabs are shown, we provide an edit icon.</tal:comment>
<img tal:define="editPageName python:test(pageName=='main', 'default', pageName);
nav request/nav|nothing;
nav python: test(nav, '&nav=%s' % nav, '')"
title="Edit" i18n:domain="plone" i18n:attributes="title" style="cursor:pointer"
tal:attributes="onClick python: 'href: window.location=\'%s/skyn/edit?fieldset=%s&phase=%s%s\'' % (contextObj.absolute_url(), editPageName, phase, nav);
src string: $portal_url/skyn/edit.gif"
tal:condition="python: (len(appyPages)==1) and member.has_permission('Modify portal content', contextObj)"/>
</td>
<td><metal:actions use-macro="here/document_actions/macros/document_actions"/>
</td>
</tr>
<tr tal:define="descrLabel python: contextObj.translate('%s_edit_descr' % contextObj.portal_type)"
tal:condition="descrLabel/strip" >
<tal:comment replace="nothing">Content type description</tal:comment>
<td colspan="2" class="discreet" tal:content="descrLabel"/>
</tr>
<tr>
<td class="documentByLine">
<tal:comment replace="nothing">Creator and last modification date</tal:comment>
<tal:comment replace="nothing">Plus/minus icon for accessing history</tal:comment>
<tal:accessHistory condition="hasHistory">
<img align="left" style="cursor:pointer" onClick="javascript:toggleCookie('appyHistory')"
tal:attributes="src python:test(historyExpanded, 'skyn/collapse.gif', 'skyn/expand.gif');"
id="appyHistory_img"/>&nbsp;
<span i18n:translate="label_history" i18n:domain="plone" class="appyHistory"></span>&nbsp;
</tal:accessHistory>
<tal:comment replace="nothing">Show document creator</tal:comment>
<tal:creator condition="creator"
define="author python:contextObj.portal_membership.getMemberInfo(creator)">
<span class="documentAuthor" i18n:domain="plone" i18n:translate="label_by_author">
by <a tal:attributes="href string:${portal_url}/author/${creator}"
tal:content="python:author and author['fullname'] or creator"
tal:omit-tag="not:author" i18n:name="author"/>
&mdash;
</span>
</tal:creator>
<tal:comment replace="nothing">Show last modification date</tal:comment>
<span i18n:translate="box_last_modified" i18n:domain="plone"></span>
<span tal:replace="python:contextObj.restrictedTraverse('@@plone').toLocalizedTime(contextObj.ModificationDate(),long_format=1)"></span>
</td>
<td valign="top"><metal:pod use-macro="here/skyn/macros/macros/listPodTemplates"/>
</td>
</tr>
<tal:comment replace="nothing">Object history</tal:comment>
<tr tal:condition="hasHistory">
<td colspan="2">
<span id="appyHistory"
tal:attributes="style python:test(historyExpanded, 'display:block', 'display:none')">
<div tal:define="ajaxHookId python: contextObj.UID() + '_history';"
tal:attributes="id ajaxHookId">
<script language="javascript" tal:content="python: 'askObjectHistory(\'%s\',\'%s\',0)' % (ajaxHookId, contextObj.absolute_url())">
</script>
</div>
</span>
</td>
</tr>
<tal:comment replace="nothing">Workflow-related information and actions</tal:comment>
<tr tal:condition="python: showWorkflow and contextObj.getWorkflowLabel()">
<td colspan="2" class="appyWorkflow">
<table width="100%">
<tr>
<td><metal:states use-macro="here/skyn/macros/macros/states"/></td>
<td align="right"><metal:states use-macro="here/skyn/macros/macros/transitions"/></td>
</tr>
</table>
</td>
</tr>
</table>
<metal:nav use-macro="here/skyn/navigate/macros/objectNavigate"/>
<tal:comment replace="nothing">Tabs</tal:comment>
<ul class="contentViews appyTabs" tal:condition="python: len(appyPages)&gt;1">
<li tal:repeat="thePage appyPages"
tal:attributes="class python:test(thePage == pageName, 'selected', 'plain')">
<tal:tab define="pageLabel python: tool.translate('%s_page_%s' % (contextObj.meta_type, thePage))">
<a tal:content="pageLabel"
tal:attributes="href python: contextObj.absolute_url() + '/skyn/view?phase=%s&pageName=%s' % (phase, thePage)">
</a>
<img tal:define="editPageName python:test(thePage=='main', 'default', thePage);
nav request/nav|nothing;
nav python: test(nav, '&nav=%s' % nav, '')"
title="Edit" i18n:domain="plone" i18n:attributes="title" style="cursor:pointer" class="appyPlusImg"
tal:attributes="onClick python: 'href: window.location=\'%s/skyn/edit?fieldset=%s&phase=%s%s\'' % (contextObj.absolute_url(), editPageName, phase, nav);
src string: $portal_url/skyn/edit.gif"
tal:condition="python: member.has_permission('Modify portal content', contextObj)"/>
</tal:tab>
</li>
</ul>
</div>
<div metal:define-macro="showPageFooter">
<script language="javascript">
<!--
// When the current page is loaded, we must set the correct state for all slave fields.
var masters = cssQuery('.appyMaster');
for (var i=0; i < masters.length; i++) {
var cssClasses = masters[i].className.split(' ');
for (var j=0; j < cssClasses.length; j++) {
if (cssClasses[j].indexOf('master_') == 0) {
var appyId = cssClasses[j].split('_')[1];
var masterValue = [];
if (masters[i].nodeName == 'SPAN'){
var idField = masters[i].id;
if (idField == '') {
masterValue.push(idField);
}
else {
if (idField[0] == '(') {
// There are multiple values, split it
var subValues = idField.substring(1, idField.length-1).split(',');
for (var k=0; k < subValues.length; k++){
var subValue = subValues[k].strip();
masterValue.push(subValue.substring(1, subValue.length-1));
}
}
else { masterValue.push(masters[i].id);
}
}
}
else { masterValue = getMasterValue(masters[i]);
}
updateSlaves(masterValue, appyId);
}
}
}
-->
</script>
</div>
<metal:queryResults define-macro="queryResult"
tal:define="tool python: contextObj;
contentType request/type_name;
@ -856,7 +45,7 @@
<table tal:define="fieldDescrs python: tool.getResultColumns(objs[0], contentType)"
class="listing nosort" width="100%" cellpadding="0" cellspacing="0">
<tal:comment replace="nothing">Every item in fieldDescr is a FieldDescr instance,
<tal:comment replace="nothing">Every item in fieldDescrs is an Appy type (dict version),
excepted for workflow state (which is not a field): in this case it is simply the
string "workflow_state".</tal:comment>
@ -871,12 +60,12 @@
<tal:comment replace="nothing">Columns corresponding to other fields</tal:comment>
<tal:columnHeader repeat="fieldDescr fieldDescrs">
<th tal:define="fieldName fieldDescr/atField/getName|string:workflow_state;
sortable fieldDescr/sortable|nothing;
<th tal:define="fieldName fieldDescr/name|string:workflow_state;
sortable fieldDescr/indexed|nothing;
filterable fieldDescr/filterable|nothing;">
<tal:comment replace="nothing">Display header for a "standard" field</tal:comment>
<tal:standardField condition="python: fieldName != 'workflow_state'">
<span tal:replace="python: tool.translate(fieldDescr['atField'].widget.label_msgid)"/>
<span tal:replace="python: tool.translate(fieldDescr['labelId'])"/>
</tal:standardField>
<tal:comment replace="nothing">Display header for the workflow state</tal:comment>
<tal:workflowState condition="python: fieldName == 'workflow_state'">
@ -906,23 +95,22 @@
tal:content="obj/Title" tal:attributes="href python: obj.getUrl() + '/?' + navInfo"></a></td>
<tal:comment replace="nothing">Columns corresponding to other fields</tal:comment>
<tal:otherFields repeat="fieldDescr fieldDescrs">
<tal:standardField condition="python: fieldDescr != 'workflowState'">
<td tal:condition="fieldDescr/atField"
tal:attributes="id python:'field_%s' % fieldDescr['atField'].getName()">
<tal:otherFields repeat="widget fieldDescrs">
<tal:standardField condition="python: widget != 'workflowState'">
<td tal:condition="python: '_wrong' not in widget"
tal:attributes="id python:'field_%s' % widget['name']">
<tal:field define="contextObj python:obj;
isEdit python:False;
showLabel python:False;
layoutType python:'cell';
innerRef python:True"
condition="python: contextObj.showField(fieldDescr)">
<metal:field use-macro="here/skyn/macros/macros/showArchetypesField"/>
condition="python: contextObj.showField(widget['name'], 'view')">
<metal:field use-macro="here/skyn/widgets/show/macros/field"/>
</tal:field>
</td>
<td tal:condition="not: fieldDescr/atField" style="color:red">Field
<span tal:replace="fieldDescr/name"/> not found.
<td tal:condition="python: '_wrong' in widget" style="color:red">Field
<span tal:replace="widget/name"/> not found.
</td>
</tal:standardField>
<tal:workflowState condition="python: fieldDescr == 'workflowState'">
<tal:workflowState condition="python: widget == 'workflowState'">
<td id="field_workflow_state" tal:content="python: tool.translate(obj.getWorkflowLabel())"></td>
</tal:workflowState>
</tal:otherFields>
@ -969,70 +157,3 @@
</tal:noResult>
</metal:queryResults>
<metal:phases define-macro="phases">
<tal:comment replace="nothing">This macro displays phases defined for a given content type,
only if more than one phase is defined.</tal:comment>
<table width="100%" tal:define="phases contextObj/getAppyPhases|nothing"
tal:condition="python: phases and (len(phases)&gt;1)" cellspacing="1" cellpadding="0">
<tal:phase repeat="phase phases">
<tr>
<td tal:define="label python:'%s_phase_%s' % (contextObj.meta_type, phase['name']);
displayLink python: (phase['phaseStatus'] != 'Future') and ('/portal_factory' not in contextObj.absolute_url())"
tal:attributes="class python: 'appyPhase step' + phase['phaseStatus']">
<a tal:attributes="href python: '%s?phase=%s&pageName=%s' % (contextObj.getUrl(), phase['name'], phase['pages'][0]);" tal:condition="displayLink"
tal:content="python: tool.translate(label)"/>
<span tal:condition="not: displayLink" tal:content="python: tool.translate(label)"/>
</td>
</tr>
<tr tal:condition="python: phase['name'] != phases[-1]['name']">
<td align="center"><img tal:attributes="src string: $portal_url/skyn/nextPhase.png"/></td>
</tr>
</tal:phase>
</table>
</metal:phases>
<metal:states define-macro="states"
tal:define="showAllStatesInPhase python: flavour.getAttr('showAllStatesInPhaseFor' + contextObj.meta_type);
states python: contextObj.getAppyStates(phase, currentOnly=not showAllStatesInPhase)"
tal:condition="python: test(showAllStatesInPhase, len(states)&gt;1, True)">
<table>
<tr>
<tal:state repeat="stateInfo states">
<td tal:attributes="class python: 'appyState step' + stateInfo['stateStatus']"
tal:content="python: tool.translate(contextObj.getWorkflowLabel(stateInfo['name']))">
</td>
<td tal:condition="python: stateInfo['name'] != states[-1]['name']">
<img tal:attributes="src string: $portal_url/skyn/nextState.png"/>
</td>
</tal:state>
</tr>
</table>
</metal:states>
<metal:transitions define-macro="transitions"
tal:define="transitions contextObj/getAppyTransitions"
tal:condition="transitions">
<form id="triggerTransitionForm" method="post"
tal:attributes="action python: contextObj.absolute_url() + '/skyn/do'">
<input type="hidden" name="action" value="TriggerTransition"/>
<input type="hidden" name="workflow_action"/>
<table>
<tr>
<tal:comment replace="nothing">Input field allowing to enter a comment before triggering a transition</tal:comment>
<td tal:define="showCommentsField python:flavour.getAttr('showWorkflowCommentFieldFor'+contextObj.meta_type)"
align="right" tal:condition="showCommentsField">
<span tal:content="python: tool.translate('workflow_comment')" class="discreet"></span>
<input type="text" id="comment" name="comment" size="35"/>
</td>
<tal:comment replace="nothing">Buttons for triggering transitions</tal:comment>
<td align="right" tal:repeat="transition transitions">
<input type="button" class="context"
tal:attributes="value python: tool.translate(transition['name']);
onClick python: 'javascript: triggerTransition(\'%s\')' % transition['id'];"/>
</td>
</tr>
</table>
</form>
</metal:transitions>

BIN
gen/plone25/skin/next.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 951 B

728
gen/plone25/skin/page.pt Normal file
View file

@ -0,0 +1,728 @@
<tal:comment replace="nothing">
This macro contains global page-related Javascripts.
</tal:comment>
<div metal:define-macro="prologue">
<tal:comment replace="nothing">Global elements used in every page.</tal:comment>
<tal:comment replace="nothing">Javascript messages</tal:comment>
<script language="javascript" tal:content="tool/getJavascriptMessages"></script>
<tal:comment replace="nothing">"Static" javascripts</tal:comment>
<script language="javascript">
<!--
var isIe = (navigator.appName == "Microsoft Internet Explorer");
// AJAX machinery
var xhrObjects = new Array(); // An array of XMLHttpRequest objects
function XhrObject() { // Wraps a XmlHttpRequest object
this.freed = 1; // Is this xhr object already dealing with a request or not?
this.xhr = false;
if (window.XMLHttpRequest) this.xhr = new XMLHttpRequest();
else this.xhr = new ActiveXObject("Microsoft.XMLHTTP");
this.hook = ''; /* The ID of the HTML element in the page that will be
replaced by result of executing the Ajax request. */
this.onGet = ''; /* The name of a Javascript function to call once we
receive the result. */
this.info = {}; /* An associative array for putting anything else. */
}
function getAjaxChunk(pos) {
// This function is the callback called by the AJAX machinery (see function
// askAjaxChunk below) when an Ajax response is available.
// First, find back the correct XMLHttpRequest object
if ( (typeof(xhrObjects[pos]) != 'undefined') &&
(xhrObjects[pos].freed == 0)) {
var hook = xhrObjects[pos].hook;
if (xhrObjects[pos].xhr.readyState == 1) {
// The request has been initialized: display the waiting radar
var hookElem = document.getElementById(hook);
if (hookElem) hookElem.innerHTML = "<div align=\"center\"><img src=\"skyn/waiting.gif\"/><\/div>";
}
if (xhrObjects[pos].xhr.readyState == 4) {
// We have received the HTML chunk
var hookElem = document.getElementById(hook);
if (hookElem && (xhrObjects[pos].xhr.status == 200)) {
hookElem.innerHTML = xhrObjects[pos].xhr.responseText;
// Call a custom Javascript function if required
if (xhrObjects[pos].onGet) {
xhrObjects[pos].onGet(xhrObjects[pos], hookElem);
}
}
xhrObjects[pos].freed = 1;
}
}
}
function askAjaxChunk(hook,mode,url,page,macro,params,beforeSend,onGet) {
/* This function will ask to get a chunk of HTML on the server through a
XMLHttpRequest. p_mode can be 'GET' or 'POST'. p_url is the URL of a
given server object. On this URL we will call the page "ajax.pt" that
will call a specific p_macro in a given p_page with some additional
p_params (must be an associative array) if required.
p_hook is the ID of the HTML element that will be filled with the HTML
result from the server.
p_beforeSend is a Javascript function to call before sending the request.
This function will get 2 args: the XMLHttpRequest object and the
p_params. This method can return, in a string, additional parameters to
send, ie: "&param1=blabla&param2=blabla".
p_onGet is a Javascript function to call when we will receive the answer.
This function will get 2 args, too: the XMLHttpRequest object and the
HTML node element into which the result has been inserted.
*/
// First, get a non-busy XMLHttpRequest object.
var pos = -1;
for (var i=0; i < xhrObjects.length; i++) {
if (xhrObjects[i].freed == 1) { pos = i; break; }
}
if (pos == -1) {
pos = xhrObjects.length;
xhrObjects[pos] = new XhrObject();
}
xhrObjects[pos].hook = hook;
xhrObjects[pos].onGet = onGet;
if (xhrObjects[pos].xhr) {
var rq = xhrObjects[pos];
rq.freed = 0;
// Construct parameters
var paramsFull = 'page=' + page + '&macro=' + macro;
if (params) {
for (var paramName in params)
paramsFull = paramsFull + '&' + paramName + '=' + params[paramName];
}
// Call beforeSend if required
if (beforeSend) {
var res = beforeSend(rq, params);
if (res) paramsFull = paramsFull + res;
}
// Construct the URL to call
var urlFull = url + '/skyn/ajax';
if (mode == 'GET') {
urlFull = urlFull + '?' + paramsFull;
}
// Perform the asynchronous HTTP GET or POST
rq.xhr.open(mode, urlFull, true);
if (mode == 'POST') {
// Set the correct HTTP headers
rq.xhr.setRequestHeader(
"Content-Type", "application/x-www-form-urlencoded");
rq.xhr.setRequestHeader("Content-length", paramsFull.length);
rq.xhr.setRequestHeader("Connection", "close");
rq.xhr.onreadystatechange = function(){ getAjaxChunk(pos); }
rq.xhr.send(paramsFull);
}
else if (mode == 'GET') {
rq.xhr.onreadystatechange = function() { getAjaxChunk(pos); }
if (window.XMLHttpRequest) { rq.xhr.send(null); }
else if (window.ActiveXObject) { rq.xhr.send(); }
}
}
}
/* The functions below wrap askAjaxChunk for getting specific content through
an Ajax request. */
function askQueryResult(hookId, objectUrl, contentType, flavourNumber,
searchName, startNumber, sortKey, sortOrder, filterKey) {
// Sends an Ajax request for getting the result of a query.
var params = {'type_name': contentType, 'flavourNumber': flavourNumber,
'search': searchName, 'startNumber': startNumber};
if (sortKey) params['sortKey'] = sortKey;
if (sortOrder) params['sortOrder'] = sortOrder;
if (filterKey) {
var filterWidget = document.getElementById(hookId + '_' + filterKey);
if (filterWidget && filterWidget.value) {
params['filterKey'] = filterKey;
params['filterValue'] = filterWidget.value;
}
}
askAjaxChunk(hookId,'GET',objectUrl,'macros','queryResult',params);
}
function askObjectHistory(hookId, objectUrl, startNumber) {
// Sends an Ajax request for getting the history of an object
var params = {'startNumber': startNumber};
askAjaxChunk(hookId, 'GET', objectUrl, 'page', 'objectHistory', params);
}
function askRefField(hookId, objectUrl, fieldName, innerRef, startNumber,
action, actionParams){
// Sends an Ajax request for getting the content of a reference field.
var startKey = hookId + '_startNumber';
var params = {'fieldName': fieldName, 'innerRef': innerRef, };
params[startKey] = startNumber;
if (action) params['action'] = action;
if (actionParams) {
for (key in actionParams) { params[key] = actionParams[key]; };
}
askAjaxChunk(hookId, 'GET', objectUrl, 'widgets/ref', 'viewContent',params);
}
// Function used by checkbox widgets for having radio-button-like behaviour
function toggleCheckbox(visibleCheckbox, hiddenBoolean) {
vis = document.getElementById(visibleCheckbox);
hidden = document.getElementById(hiddenBoolean);
if (vis.checked) hidden.value = 'True';
else hidden.value = 'False';
}
// Functions used for master/slave relationships between widgets
function getMasterValue(widget) {
// Returns an array of selected options in a select widget
res = new Array();
if (widget.type == 'checkbox') {
var mv = widget.checked + '';
mv = mv.charAt(0).toUpperCase() + mv.substr(1);
res.push(mv);
}
else { // SELECT widget
for (var i=0; i < widget.options.length; i++) {
if (widget.options[i].selected) res.push(widget.options[i].value);
}
}
return res;
}
function updateSlaves(masterValues, appyTypeId) {
// Given the value(s) selected in a master field, this function updates the
// state of all corresponding slaves.
var slaves = cssQuery('table.slave_' + appyTypeId);
for (var i=0; i< slaves.length; i++){
slaves[i].style.display = "none";
}
for (var i=0; i < masterValues.length; i++) {
var activeSlaves = cssQuery('table.slaveValue_' + appyTypeId + '_' + masterValues[i]);
for (var j=0; j < activeSlaves.length; j++){
activeSlaves[j].style.display = "";
}
}
}
// Function used for triggering a workflow transition
function triggerTransition(transitionId) {
var theForm = document.getElementById('triggerTransitionForm');
theForm.workflow_action.value = transitionId;
theForm.submit();
}
function onDeleteObject(objectUid) {
if (confirm(delete_confirm)) {
f = document.getElementById('deleteForm');
f.objectUid.value = objectUid;
f.submit();
}
}
function toggleCookie(cookieId) {
// What is the state of this boolean (expanded/collapsed) cookie?
var state = readCookie(cookieId);
if ((state != 'collapsed') && (state != 'expanded')) {
// No cookie yet, create it.
createCookie(cookieId, 'collapsed');
state = 'collapsed';
}
var hook = document.getElementById(cookieId); // The hook is the part of
// the HTML document that needs to be shown or hidden.
var displayValue = 'none';
var newState = 'collapsed';
var imgSrc = 'skyn/expand.gif';
if (state == 'collapsed') {
// Show the HTML zone
displayValue = 'block';
imgSrc = 'skyn/collapse.gif';
newState = 'expanded';
}
// Update the corresponding HTML element
hook.style.display = displayValue;
var img = document.getElementById(cookieId + '_img');
img.src = imgSrc;
// Inverse the cookie value
createCookie(cookieId, newState);
}
// Function that allows to generate a document from a pod template.
function generatePodDocument(contextUid, templateUid, fieldName, podFormat) {
var theForm = document.forms["podTemplateForm"];
theForm.objectUid.value = contextUid;
theForm.templateUid.value = templateUid;
theForm.fieldName.value = fieldName;
theForm.podFormat.value = podFormat;
theForm.askAction.value = "False";
var askActionWidget = document.getElementById(contextUid + '_' + fieldName);
if (askActionWidget && askActionWidget.checked) {
theForm.askAction.value = "True";
}
theForm.submit();
}
// Functions for opening and closing a popup
function openPopup(popupId) {
// Open the popup
var popup = document.getElementById(popupId);
// Put it at the right place on the screen
var scrollTop = window.pageYOffset || document.documentElement.scrollTop || 0;
popup.style.top = (scrollTop + 150) + 'px';
popup.style.display = "block";
// Show the greyed zone
var greyed = document.getElementById('appyGrey');
greyed.style.top = scrollTop + 'px';
greyed.style.display = "block";
}
function closePopup(popupId) {
// Close the popup
var popup = document.getElementById(popupId);
popup.style.display = "none";
// Hide the greyed zone
var greyed = document.getElementById('appyGrey');
greyed.style.display = "none";
}
// Function triggered when an action needs to be confirmed by the user
function askConfirm(formId) {
// Store the ID of the form to send if the users confirms.
var confirmForm = document.getElementById('confirmActionForm');
confirmForm.actionFormId.value = formId;
openPopup("confirmActionPopup");
}
// Function triggered when an action confirmed by the user must be performed
function doConfirm() {
// The user confirmed: retrieve the form to send and send it.
var confirmForm = document.getElementById('confirmActionForm');
var actionFormId = confirmForm.actionFormId.value;
var actionForm = document.getElementById(actionFormId);
actionForm.submit();
}
// Function that shows or hides a tab. p_action is 'show' or 'hide'.
function manageTab(tabId, action) {
// Manage the tab content (show it or hide it)
var content = document.getElementById('tabcontent_' + tabId);
if (action == 'show') { content.style.display = 'table-row'; }
else { content.style.display = 'none'; }
// Manage the tab itself (show as selected or unselected)
var left = document.getElementById('tab_' + tabId + '_left');
var tab = document.getElementById('tab_' + tabId);
var right = document.getElementById('tab_' + tabId + '_right');
if (action == 'show') {
left.src = "skyn/tabLeft.png";
tab.style.backgroundImage = "url(skyn/tabBg.png)";
right.src = "skyn/tabRight.png";
}
if (action == 'hide') {
left.src = "skyn/tabLeftu.png";
tab.style.backgroundImage = "url(skyn/tabBgu.png)";
right.src = "skyn/tabRightu.png";
}
}
// Function used for displaying/hiding content of a tab
function showTab(tabId) {
// 1st, show the tab to show
manageTab(tabId, 'show');
// Compute the number of tabs.
var idParts = tabId.split('_');
var prefix = idParts[0] + '_';
// Store the currently selected tab in a cookie.
createCookie('tab_' + idParts[0], tabId);
var nbOfTabs = idParts[2]*1;
// Then, hide the other tabs.
for (var i=0; i<nbOfTabs; i++) {
var idTab = prefix + (i+1) + '_' + nbOfTabs;
if (idTab != tabId) {
manageTab(idTab, 'hide');
}
}
}
// Function that initializes the state of a tab
function initTab(cookieId, defaultValue) {
var toSelect = readCookie(cookieId);
if (!toSelect) { showTab(defaultValue) }
else { showTab(toSelect); }
}
-->
</script>
<tal:comment replace="nothing">Global form for deleting an object</tal:comment>
<form id="deleteForm" method="post" action="skyn/do">
<input type="hidden" name="action" value="Delete"/>
<input type="hidden" name="objectUid"/>
</form>
<tal:comment replace="nothing">Global form for generating a document from a pod template.</tal:comment>
<form name="podTemplateForm" method="post"
tal:attributes="action python: flavour.absolute_url() + '/generateDocument'">
<input type="hidden" name="objectUid"/>
<tal:comment replace="nothing">templateUid is given if class-wide pod, fieldName and podFormat are given if podField.</tal:comment>
<input type="hidden" name="templateUid"/>
<input type="hidden" name="fieldName"/>
<input type="hidden" name="podFormat"/>
<input type="hidden" name="askAction"/>
</form>
</div>
<tal:comment replace="nothing">
This macro shows the content of page. Because a page is a layouted object,
we simply call the macro that displays a layouted object.
contextObj The Zope object for which this page must be shown
layoutType The kind of layout: "view"? "edit"? "cell"?
layout The layout object that will dictate how object content
will be rendered.
</tal:comment>
<metal:show define-macro="show">
<metal:layout use-macro="here/skyn/widgets/show/macros/layout"/>
</metal:show>
<tal:comment replace="nothing">
This macro displays all widgets of a given page. It requires:
contextObj The Zope object for which widgets must be shown
page We show widgets of a given page
layoutType We must know if we must render the widgets in a "view",
"edit" or "cell" layout
</tal:comment>
<table metal:define-macro="widgets" cellpadding="0" cellspacing="0">
<tr tal:repeat="widget python: contextObj.getGroupedAppyTypes(layoutType, page)">
<td tal:condition="python: widget['type'] == 'group'">
<metal:call use-macro="portal/skyn/widgets/show/macros/group"/>
</td>
<td tal:condition="python: widget['type'] != 'group'">
<metal:call use-macro="portal/skyn/widgets/show/macros/field"/>
</td>
</tr>
</table>
<tal:comment replace="nothing">
This macro lists the POD templates that are available. It is used by macro "header" below.
</tal:comment>
<div metal:define-macro="listPodTemplates" class="appyPod" tal:condition="podTemplates"
tal:define="podTemplates python: flavour.getAvailablePodTemplates(contextObj, phase);">
<tal:podTemplates define="maxShownTemplates python: flavour.getMaxShownTemplates(contextObj)">
<tal:comment replace="nothing">Display templates as links if a few number of templates must be shown</tal:comment>
<span class="discreet" tal:condition="python: len(podTemplates)&lt;=maxShownTemplates"
tal:repeat="podTemplate podTemplates">
<a style="cursor: pointer"
tal:define="podFormat podTemplate/getPodFormat"
tal:attributes="onclick python: 'javascript:generatePodDocument(\'%s\',\'%s\', \'\', \'\')' % (contextObj.UID(), podTemplate.UID())" >
<img tal:attributes="src string: $portal_url/skyn/$podFormat.png"/>
<span tal:replace="podTemplate/Title"/>
</a>
&nbsp;</span>
<tal:comment replace="nothing">Display templates as a list if a lot of templates must be shown</tal:comment>
<select tal:condition="python: len(podTemplates)&gt;maxShownTemplates">
<option value="" tal:content="python: tool.translate('choose_a_doc')"></option>
<option tal:repeat="podTemplate podTemplates" tal:content="podTemplate/Title"
tal:attributes="onclick python: 'javascript:generatePodDocument(\'%s\',\'%s\', \'\', \'\')' % (contextObj.UID(), podTemplate.UID())" />
</select>
</tal:podTemplates>
</div>
<tal:comment replace="nothing">
This macro displays an object's history. It is used by macro "header" below.
</tal:comment>
<metal:history define-macro="objectHistory"
tal:define="startNumber request/startNumber|python:0;
startNumber python: int(startNumber);
historyInfo python: contextObj.getHistory(startNumber);
objs historyInfo/events;
batchSize historyInfo/batchSize;
totalNumber historyInfo/totalNumber;
ajaxHookId python:'appyHistory';
navBaseCall python: 'askObjectHistory(\'%s\',\'%s\',**v**)' % (ajaxHookId, contextObj.absolute_url());
tool contextObj/getTool">
<tal:comment replace="nothing">Table containing the history</tal:comment>
<tal:history condition="objs">
<metal:nav use-macro="here/skyn/navigate/macros/appyNavigate"/>
<table width="100%" class="listing nosort">
<tr i18n:domain="plone">
<th i18n:translate="listingheader_action"/>
<th i18n:translate="listingheader_performed_by"/>
<th i18n:translate="listingheader_date_and_time"/>
<th i18n:translate="listingheader_comment"/>
</tr>
<tal:event repeat="event objs">
<tr tal:define="odd repeat/event/odd;
rhComments event/comments|nothing;
state event/review_state|nothing;
isDataChange python: event['action'] == '_datachange_'"
tal:attributes="class python:test(odd, 'even', 'odd')" valign="top">
<td tal:condition="isDataChange" tal:content="python: tool.translate('data_change')"></td>
<td tal:condition="not: isDataChange"
tal:content="python: tool.translate(contextObj.getWorkflowLabel(event['action']))"
tal:attributes="class string:state-${state}"/>
<td tal:define="actorid python:event.get('actor');
actor python:contextObj.portal_membership.getMemberInfo(actorid);
fullname actor/fullname|nothing;
username actor/username|nothing"
tal:content="python:fullname or username or actorid"/>
<td tal:content="python:contextObj.restrictedTraverse('@@plone').toLocalizedTime(event['time'],long_format=True)"/>
<td tal:condition="not: isDataChange"><tal:comment condition="rhComments" tal:content="structure rhComments"/>
<tal:noComment condition="not: rhComments" i18n:translate="no_comments" i18n:domain="plone"/></td>
<td tal:condition="isDataChange">
<tal:comment replace="nothing">
Display the previous values of the fields whose value were modified in this change.</tal:comment>
<table class="appyChanges" width="100%">
<tr>
<th align="left" width="30%" tal:content="python: tool.translate('modified_field')"></th>
<th align="left" width="70%" tal:content="python: tool.translate('previous_value')"></th>
</tr>
<tr tal:repeat="change event/changes/items" valign="top">
<td tal:content="python: tool.translate(change[1][1])"></td>
<td tal:define="appyValue python: contextObj.getFormattedValue(change[0], useParamValue=True, value=change[1][0]);
appyType python:contextObj.getAppyType(change[0], asDict=True);
severalValues python: (appyType['multiplicity'][1] &gt; 1) or (appyType['multiplicity'][1] == None)">
<span tal:condition="not: severalValues" tal:replace="appyValue"></span>
<ul tal:condition="python: severalValues">
<li tal:repeat="av appyValue" tal:content="av"></li>
</ul>
</td>
</tr>
</table>
</td>
</tr>
</tal:event>
</table>
</tal:history>
</metal:history>
<tal:comment replace="nothing">
This macro displays an object's state(s). It is used by macro "header" below.
</tal:comment>
<metal:states define-macro="states"
tal:define="showAllStatesInPhase python: flavour.getAttr('showAllStatesInPhaseFor' + contextObj.meta_type);
states python: contextObj.getAppyStates(phase, currentOnly=not showAllStatesInPhase)"
tal:condition="python: test(showAllStatesInPhase, len(states)&gt;1, True)">
<table>
<tr>
<tal:state repeat="stateInfo states">
<td tal:attributes="class python: 'appyState step' + stateInfo['stateStatus']"
tal:content="python: tool.translate(contextObj.getWorkflowLabel(stateInfo['name']))">
</td>
<td tal:condition="python: stateInfo['name'] != states[-1]['name']">
<img tal:attributes="src string: $portal_url/skyn/nextState.png"/>
</td>
</tal:state>
</tr>
</table>
</metal:states>
<tal:comment replace="nothing">
This macro displays an object's transitions(s). It is used by macro "header" below.
</tal:comment>
<metal:transitions define-macro="transitions"
tal:define="transitions contextObj/getAppyTransitions"
tal:condition="transitions">
<form id="triggerTransitionForm" method="post"
tal:attributes="action python: contextObj.absolute_url() + '/skyn/do'">
<input type="hidden" name="action" value="TriggerTransition"/>
<input type="hidden" name="workflow_action"/>
<table>
<tr>
<tal:comment replace="nothing">Input field allowing to enter a comment before triggering a transition</tal:comment>
<td tal:define="showCommentsField python:flavour.getAttr('showWorkflowCommentFieldFor'+contextObj.meta_type)"
align="right" tal:condition="showCommentsField">
<span tal:content="python: tool.translate('workflow_comment')" class="discreet"></span>
<input type="text" id="comment" name="comment" size="35"/>
</td>
<tal:comment replace="nothing">Buttons for triggering transitions</tal:comment>
<td align="right" tal:repeat="transition transitions">
<input type="button" class="context"
tal:attributes="value python: tool.translate(transition['name']);
onClick python: 'javascript: triggerTransition(\'%s\')' % transition['id'];"/>
</td>
</tr>
</table>
</form>
</metal:transitions>
<tal:comment replace="nothing">
This macros displays the page header, containing object title,
workflow-related info, object history, etc.
</tal:comment>
<div metal:define-macro="header"
tal:define="showCommonInfo python: layoutType == 'view';
hasHistory contextObj/hasHistory;
historyExpanded python: tool.getCookieValue('appyHistory', default='collapsed') == 'expanded';
creator contextObj/Creator"
tal:condition="not: contextObj/isTemporary">
<tal:comment replace="nothing">Information that is common to all tabs (object title, state, etc)</tal:comment>
<table width="100%" tal:condition="showCommonInfo" class="appyCommonInfo">
<tr valign="bottom">
<tal:comment replace="nothing">Title and state</tal:comment>
<td width="80%">
<b class="appyTitle" tal:content="contextObj/title_or_id"></b>
</td>
<td><metal:actions use-macro="here/document_actions/macros/document_actions"/>
</td>
</tr>
<tr tal:define="descrLabel python: contextObj.translate('%s_edit_descr' % contextObj.portal_type)"
tal:condition="descrLabel/strip" >
<tal:comment replace="nothing">Content type description</tal:comment>
<td colspan="2" class="discreet" tal:content="descrLabel"/>
</tr>
<tr>
<td class="documentByLine">
<tal:comment replace="nothing">Creator and last modification date</tal:comment>
<tal:comment replace="nothing">Plus/minus icon for accessing history</tal:comment>
<tal:accessHistory condition="hasHistory">
<img align="left" style="cursor:pointer" onClick="javascript:toggleCookie('appyHistory')"
tal:attributes="src python:test(historyExpanded, 'skyn/collapse.gif', 'skyn/expand.gif');"
id="appyHistory_img"/>&nbsp;
<span i18n:translate="label_history" i18n:domain="plone" class="appyHistory"></span>&nbsp;
</tal:accessHistory>
<tal:comment replace="nothing">Show document creator</tal:comment>
<tal:creator condition="creator"
define="author python:contextObj.portal_membership.getMemberInfo(creator)">
<span class="documentAuthor" i18n:domain="plone" i18n:translate="label_by_author">
by <a tal:attributes="href string:${portal_url}/author/${creator}"
tal:content="python:author and author['fullname'] or creator"
tal:omit-tag="not:author" i18n:name="author"/>
&mdash;
</span>
</tal:creator>
<tal:comment replace="nothing">Show last modification date</tal:comment>
<span i18n:translate="box_last_modified" i18n:domain="plone"></span>
<span tal:replace="python:contextObj.restrictedTraverse('@@plone').toLocalizedTime(contextObj.ModificationDate(),long_format=1)"></span>
</td>
<td valign="top"><metal:pod use-macro="here/skyn/page/macros/listPodTemplates"/>
</td>
</tr>
<tal:comment replace="nothing">Object history</tal:comment>
<tr tal:condition="hasHistory">
<td colspan="2">
<span id="appyHistory"
tal:attributes="style python:test(historyExpanded, 'display:block', 'display:none')">
<div tal:define="ajaxHookId python: contextObj.UID() + '_history';"
tal:attributes="id ajaxHookId">
<script language="javascript" tal:content="python: 'askObjectHistory(\'%s\',\'%s\',0)' % (ajaxHookId, contextObj.absolute_url())">
</script>
</div>
</span>
</td>
</tr>
<tal:comment replace="nothing">Workflow-related information and actions</tal:comment>
<tr tal:condition="python: showWorkflow and contextObj.getWorkflowLabel()">
<td colspan="2" class="appyWorkflow">
<table width="100%">
<tr>
<td><metal:states use-macro="here/skyn/page/macros/states"/></td>
<td align="right"><metal:states use-macro="here/skyn/page/macros/transitions"/></td>
</tr>
</table>
</td>
</tr>
</table>
</div>
<tal:comment replace="nothing">
The page footer.
</tal:comment>
<metal:footer define-macro="footer">
<tal:dummy define="messages putils/showPortalMessages"/>
<script language="javascript">
<!--
// When the current page is loaded, we must set the correct state for all slave fields.
var masters = cssQuery('.appyMaster');
for (var i=0; i < masters.length; i++) {
var cssClasses = masters[i].className.split(' ');
for (var j=0; j < cssClasses.length; j++) {
if (cssClasses[j].indexOf('master_') == 0) {
var appyId = cssClasses[j].split('_')[1];
var masterValue = [];
if (masters[i].nodeName == 'SPAN'){
var idField = masters[i].id;
if (idField == '') {
masterValue.push(idField);
}
else {
if (idField[0] == '(') {
// There are multiple values, split it
var subValues = idField.substring(1, idField.length-1).split(',');
for (var k=0; k < subValues.length; k++){
var subValue = subValues[k].strip();
masterValue.push(subValue.substring(1, subValue.length-1));
}
}
else { masterValue.push(masters[i].id);
}
}
}
else { masterValue = getMasterValue(masters[i]);
}
updateSlaves(masterValue, appyId);
}
}
}
-->
</script>
</metal:footer>
<tal:comment replace="nothing">
This macro shows the range of buttons (next, previous, save,...).
</tal:comment>
<div metal:define-macro="buttons"
tal:define="previousPage python: contextObj.getPreviousPage(phaseInfo, page);
nextPage python: contextObj.getNextPage(phaseInfo, page);
isEdit python: layoutType == 'edit';">
<br/>
<tal:previousButton condition="previousPage">
<tal:button condition="isEdit">
<input type="image" class="imageInput" style="cursor:pointer" name="buttonPrevious"
title="label_previous" i18n:attributes="title" i18n:domain="plone"
tal:attributes="src string:$portal_url/skyn/previous.png"/>
<input type="hidden" name="previousPage" tal:attributes="value previousPage"/>
</tal:button>
<tal:link condition="not: isEdit">
<a tal:attributes="href python: contextObj.getUrl()+'?page=%s' % previousPage">
<img tal:attributes="src string:$portal_url/skyn/previous.png"
title="label_previous" i18n:attributes="title" i18n:domain="plone"/>
</a>
</tal:link>
</tal:previousButton>
<tal:saveButton condition="isEdit">
<input type="image" class="imageInput" style="cursor:pointer" name="buttonOk"
title="label_save" i18n:attributes="title" i18n:domain="plone"
tal:attributes="src string:$portal_url/skyn/save.png"/>
</tal:saveButton>
<tal:cancelButton condition="isEdit">
<input type="image" class="imageInput" style="cursor:pointer" name="buttonCancel"
title="label_cancel" i18n:attributes="title" i18n:domain="plone"
tal:attributes="src string:$portal_url/skyn/cancel.png"/>
</tal:cancelButton>
<tal:editLink condition="not:isEdit">
<img tal:define="nav request/nav|nothing;
nav python: test(nav, '&nav=%s' % nav, '')"
title="Edit" i18n:domain="plone" i18n:attributes="title" style="cursor:pointer"
tal:attributes="onClick python: 'href: window.location=\'%s/skyn/edit?page=%s%s\'' % (contextObj.absolute_url(), page, nav);
src string: $portal_url/skyn/editBig.png"
tal:condition="python: member.has_permission('Modify portal content', contextObj)"/>
</tal:editLink>
<tal:nextButton condition="nextPage">
<tal:button condition="isEdit">
<input type="image" class="imageInput" style="cursor:pointer" name="buttonNext"
title="label_next" i18n:attributes="title" i18n:domain="plone"
tal:attributes="src string:$portal_url/skyn/next.png"/>
<input type="hidden" name="nextPage" tal:attributes="value nextPage"/>
</tal:button>
<tal:link condition="not: isEdit">
<a tal:attributes="href python: contextObj.getUrl()+'?&page=%s' % nextPage">
<img tal:attributes="src string:$portal_url/skyn/next.png"
title="label_next" i18n:attributes="title" i18n:domain="plone"/>
</a>
</tal:link>
</tal:nextButton>
</div>
<tal:comment replace="nothing">
This macro displays the global message on the page.
</tal:comment>
<metal:message define-macro="message" i18n:domain="plone" >
<tal:comment replace="nothing">Single message from portal_status_message request key</tal:comment>
<div tal:define="msg request/portal_status_message | nothing"
tal:condition="msg" class="portalMessage" tal:content="msg" i18n:translate=""></div>
<tal:comment replace="nothing">Messages added via plone_utils</tal:comment>
<tal:messages define="messages putils/showPortalMessages" condition="messages">
<tal:msgs define="type_css_map python: {'info':'portalMessage', 'warn':'portalWarningMessage',
'stop':'portalStopMessage'};"
repeat="msg messages">
<div tal:define="mtype msg/type | nothing;"
tal:attributes="class python:mtype and type_css_map[mtype] or 'info';"
tal:content="structure msg/message | nothing" i18n:translate=""></div>
</tal:msgs>
</tal:messages>
</metal:message>

View file

@ -4,7 +4,8 @@
<metal:portletContent define-macro="portletContent"
tal:define="queryUrl python: '%s/skyn/query' % appFolder.absolute_url();
currentSearch request/search|nothing;
currentType request/type_name|nothing;">
currentType request/type_name|nothing;
contextObj python: tool.getPublishedObject(rootClasses)">
<tal:comment replace="nothing">Portlet title, with link to tool.</tal:comment>
<dt class="portletHeader">
<tal:comment replace="nothing">If there is only one flavour, clicking on the portlet
@ -27,6 +28,14 @@
</td>
</dt>
<tal:publishedObject condition="python: contextObj">
<dt class="portletAppyItem portletCurrent">
<a tal:attributes="href python: contextObj.getUrl() + '?page=main'"
tal:content="contextObj/Title"></a>
</dt>
<dt class="portletAppyItem"><metal:phases use-macro="here/skyn/portlet/macros/phases"/></dt>
</tal:publishedObject>
<tal:comment replace="nothing">TODO: implement a widget for selecting the needed flavour.</tal:comment>
<tal:comment replace="nothing">Create a section for every root class.</tal:comment>
@ -34,7 +43,7 @@
define="flavourNumber python:1;
flavour python: tool.getFlavour('Dummy_%d' % flavourNumber)">
<tal:comment replace="nothing">Section title, with action icons</tal:comment>
<dt tal:attributes="class python:test(repeat['rootClass'].number()==1, 'portletAppyItem', 'portletAppyItem portletSep')">
<dt tal:attributes="class python:test((repeat['rootClass'].number()==1) and not contextObj, 'portletAppyItem', 'portletAppyItem portletSep')">
<table width="100%" cellspacing="0" cellpadding="0" class="no-style-table">
<tr>
<td>
@ -44,7 +53,7 @@
</td>
<td align="right"
tal:define="addPermission python: '%s: Add %s' % (appName, rootClass);
userMayAdd python: member.has_permission(addPermission, appFolder);
userMayAdd python: member.has_permission(addPermission, appFolder) and tool.userMayAdd(rootClass);
createMeans python: tool.getCreateMeans(rootClass)">
<tal:comment replace="nothing">Create a new object from a web form</tal:comment>
<img style="cursor:pointer"
@ -115,11 +124,6 @@
href python:'%s?type_name=%s&flavourNumber=%d' % (queryUrl, rootClassesQuery, flavourNumber)"></a>
</dt-->
<dt class="portletAppyItem" tal:define="contextObj tool/getPublishedObject"
tal:condition="python: contextObj.meta_type in rootClasses">
<metal:phases use-macro="here/skyn/macros/macros/phases"/>
</dt>
<tal:comment replace="nothing">
Greyed transparent zone that is deployed on the
whole screen when a popup is displayed.
@ -140,3 +144,52 @@
</form>
</div>
</metal:portletContent>
<tal:comment replace="nothing">
This macro displays, within the portlet, the navigation tree for the
currently shown object, made of phases and contained pages.
</tal:comment>
<metal:phases define-macro="phases">
<table tal:define="phases contextObj/getAppyPhases|nothing;
page python: request.get('page', 'main')"
tal:condition="python: phases and not ((len(phases)==1) and len(phases[0]['pages'])==1)"
cellspacing="1" cellpadding="0" width="100%">
<tal:phase repeat="phase phases">
<tal:comment replace="nothing">The box containing phase-related information</tal:comment>
<tr>
<td tal:define="label python:'%s_phase_%s' % (contextObj.meta_type, phase['name']);
displayLink python: (phase['phaseStatus'] != 'Future') and ('/portal_factory' not in contextObj.absolute_url()) and (len(phase['pages']) == 1)"
tal:attributes="class python: 'appyPhase step' + phase['phaseStatus']">
<span class="portletGroup" tal:condition="python: len(phases) &gt; 1">
<a tal:attributes="href python: '%s?page=%s' % (contextObj.getUrl(), phase['pages'][0]);"
tal:condition="displayLink"
tal:content="python: tool.translate(label)"></a>
<span tal:condition="not: displayLink" tal:replace="python: tool.translate(label)"/>
</span>
<table width="100%" cellpadding="0" cellspacing="0"
tal:condition="python: len(phase['pages']) &gt; 1">
<tr tal:repeat="aPage phase/pages" valign="top">
<td tal:attributes="class python: test(aPage == page, 'portletCurrent portletPageItem', 'portletPageItem')">
<a tal:attributes="href python: contextObj.getUrl()+'?page=%s' % aPage"
tal:content="structure python: tool.translate('%s_page_%s' % (contextObj.meta_type, aPage))">
</a>
</td>
<td>
<img tal:define="nav request/nav|nothing;
nav python: test(nav, '&nav=%s' % nav, '')"
title="Edit" i18n:domain="plone" i18n:attributes="title" style="cursor:pointer"
tal:attributes="onClick python: 'href: window.location=\'%s/skyn/edit?page=%s%s\'' % (contextObj.absolute_url(), aPage, nav);
src string: $portal_url/skyn/edit.gif"
tal:condition="python: member.has_permission('Modify portal content', contextObj)"/>
</td>
</tr>
</table>
</td>
</tr>
<tal:comment replace="nothing">The down arrow pointing to the next phase (if any)</tal:comment>
<tr tal:condition="python: phase['name'] != phases[-1]['name']">
<td>&nbsp;&nbsp;<img tal:attributes="src string: $portal_url/skyn/nextPhase.png"/></td>
</tr>
</tal:phase>
</table>
</metal:phases>

Binary file not shown.

After

Width:  |  Height:  |  Size: 905 B

View file

@ -20,7 +20,7 @@
flavourNumber python:int(context.REQUEST.get('flavourNumber'));
searchName python:context.REQUEST.get('search', '')">
<div metal:use-macro="here/skyn/macros/macros/pagePrologue"/>
<div metal:use-macro="here/skyn/page/macros/prologue"/>
<tal:comment replace="nothing">Query result</tal:comment>
<div id="queryResult"></div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 B

BIN
gen/plone25/skin/save.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -32,13 +32,13 @@
<table class="no-style-table" cellpadding="0" cellspacing="0" width="100%"
tal:define="numberOfColumns python: flavour.getAttr('numberOfSearchColumnsFor%s' % contentType)">
<tr tal:repeat="searchRow python: tool.tabularize(searchableFields, numberOfColumns)" valign="top">
<td tal:repeat="searchField searchRow" tal:attributes="width python:'%d%%' % (100/numberOfColumns)">
<tal:field condition="searchField">
<tal:showSearchField define="fieldName python:searchField[0];
appyType python:searchField[1];
widgetName python: 'w_%s' % fieldName">
<metal:searchField use-macro="python: appFolder.skyn.widgets.macros.get('search%s' % searchField[1]['type'])"/>
</tal:showSearchField>
<td tal:repeat="widget searchRow" tal:attributes="width python:'%d%%' % (100/numberOfColumns)">
<tal:field condition="widget">
<tal:show define="name widget/name;
widgetName python: 'w_%s' % name;
macroPage python: widget['type'].lower()">
<metal:call use-macro="python: appFolder.skyn.widgets.get(macroPage).macros.get('search')"/>
</tal:show>
</tal:field><br class="discreet"/>
</td>
</tr>

BIN
gen/plone25/skin/space.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 B

BIN
gen/plone25/skin/tabBg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 B

BIN
gen/plone25/skin/tabBgu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 B

View file

@ -18,20 +18,19 @@
tal:define="contextObj python:context.getParentNode();
portal_type python:here.getPortalTypeName().lower().replace(' ', '_');
errors python:request.get('errors', {});
isEdit python:False;
layoutType python:'view';
layout python: contextObj.getPageLayout(layoutType);
tool contextObj/getTool;
flavour python: tool.getFlavour(contextObj);
appFolder tool/getAppFolder;
appName appFolder/id;
phaseInfo python: contextObj.getAppyPhases(currentOnly=True, forPlone=False);
phase request/phase|phaseInfo/name;
appyPages python: contextObj.getAppyPages(phase);
pageName python: contextObj.getAppyPage(isEdit, phaseInfo, appyPages);
appName appFolder/getId;
page request/page|python:'main';
phaseInfo python: contextObj.getAppyPhases(page=page);
phase phaseInfo/name;
showWorkflow python: flavour.getAttr('showWorkflowFor' + contextObj.meta_type)">
<metal:prologue use-macro="here/skyn/macros/macros/pagePrologue"/>
<metal:header use-macro="here/skyn/macros/macros/showPageHeader"/>
<metal:fields use-macro="here/skyn/macros/macros/listFields" />
<metal:footer use-macro="here/skyn/macros/macros/showPageFooter"/>
<metal:prologue use-macro="here/skyn/page/macros/prologue"/>
<metal:show use-macro="here/skyn/page/macros/show"/>
<metal:footer use-macro="here/skyn/page/macros/footer"/>
</metal:fill>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 B

View file

@ -1,142 +0,0 @@
<metal:searchInteger define-macro="searchInteger">
<label tal:content="python: tool.translate(appyType['label'])"></label><br>&nbsp;&nbsp;
<tal:from define="fromName python: '%s*int' % widgetName">
<label tal:attributes="for fromName" tal:content="python: tool.translate('search_from')"></label>
<input type="text" tal:attributes="name fromName" size="4"/>
</tal:from>
<tal:to define="toName python: '%s_to' % fieldName">
<label tal:attributes="for toName" tal:content="python: tool.translate('search_to')"></label>
<input type="text" tal:attributes="name toName" size="4"/>
</tal:to><br/>
</metal:searchInteger>
<metal:searchFloat define-macro="searchFloat">
<label tal:content="python: tool.translate(appyType['label'])"></label><br>&nbsp;&nbsp;
<tal:from define="fromName python: '%s*float' % widgetName">
<label tal:attributes="for fromName" tal:content="python: tool.translate('search_from')"></label>
<input type="text" tal:attributes="name fromName" size="4"/>
</tal:from>
<tal:to define="toName python: '%s_to' % fieldName">
<label tal:attributes="for toName" tal:content="python: tool.translate('search_to')"></label>
<input type="text" tal:attributes="name toName" size="4"/>
</tal:to><br/>
</metal:searchFloat>
<metal:searchString define-macro="searchString">
<label tal:attributes="for widgetName" tal:content="python: tool.translate(appyType['label'])"></label><br>&nbsp;&nbsp;
<tal:comment replace="nothing">Show a simple search field for most String fields.</tal:comment>
<tal:simpleSearch condition="not: appyType/isSelect">
<input type="text" tal:attributes="name python: '%s*string-%s' % (widgetName, appyType['transform']);
style python: 'text-transform:%s' % appyType['transform']"/>
</tal:simpleSearch>
<tal:comment replace="nothing">Show a multi-selection box for fields whose validator defines a list of values, with a "AND/OR" checkbox.</tal:comment>
<tal:selectSearch condition="appyType/isSelect">
<tal:comment replace="nothing">The "and" / "or" radio buttons</tal:comment>
<tal:operator define="operName python: 'o_%s' % fieldName;
orName python: '%s_or' % operName;
andName python: '%s_and' % operName;"
condition="python: appyType['multiplicity'][1]!=1">
<input type="radio" class="noborder" tal:attributes="name operName; id orName" checked="checked" value="or"/>
<label tal:attributes="for orName" tal:content="python: tool.translate('search_or')"></label>
<input type="radio" class="noborder" tal:attributes="name operName; id andName" value="and"/>
<label tal:attributes="for andName" tal:content="python: tool.translate('search_and')"></label><br/>
</tal:operator>
<tal:comment replace="nothing">The list of values</tal:comment>
<select tal:attributes="name widgetName" multiple="multiple" size="5">
<option tal:repeat="v python:tool.getSelectValues(appyType)"
tal:attributes="value python:v[0]" tal:content="python: v[1]">
</option>
</select>
</tal:selectSearch><br/>
</metal:searchString>
<metal:searchBoolean define-macro="searchBoolean"
tal:define="typedWidget python:'%s*bool' % widgetName">
<label tal:attributes="for widgetName" tal:content="python: tool.translate(appyType['label'])"></label><br>&nbsp;&nbsp;
<tal:yes define="valueId python:'%s_yes' % fieldName">
<input type="radio" class="noborder" value="True" tal:attributes="name typedWidget; id valueId"/>
<label tal:attributes="for valueId" i18n:translate="yes" i18n:domain="plone"></label>
</tal:yes>
<tal:no define="valueId python:'%s_no' % fieldName">
<input type="radio" class="noborder" value="False" tal:attributes="name typedWidget; id valueId"/>
<label tal:attributes="for valueId" i18n:translate="no" i18n:domain="plone"></label>
</tal:no>
<tal:whatever define="valueId python:'%s_whatever' % fieldName">
<input type="radio" class="noborder" value="" tal:attributes="name typedWidget; id valueId" checked="checked"/>
<label tal:attributes="for valueId" tal:content="python: tool.translate('whatever')"></label>
</tal:whatever><br/>
</metal:searchBoolean>
<metal:searchDate define-macro="searchDate">
<label tal:content="python: tool.translate(appyType['label'])"></label>
<table cellpadding="0" cellspacing="0">
<tal:comment replace="nothing">From</tal:comment>
<tr tal:define="fromName python: '%s*date' % widgetName">
<td width="10px">&nbsp;</td>
<td>
<label tal:content="python: tool.translate('search_from')"></label>
</td>
<td>
<select tal:attributes="name fromName">
<option value="">--</option>
<option tal:repeat="value python:range(appyType['startYear'], appyType['endYear']+1)"
tal:content="value" tal:attributes="value value"></option>
</select> /
<select tal:attributes="name python: '%s_from_month' % fieldName">
<option value="">--</option>
<option tal:repeat="value python: [str(v).zfill(2) for v in range(1, 13)]"
tal:content="python:tool.getMonthName(value)" tal:attributes="value value"></option>
</select> /
<select tal:attributes="name python: '%s_from_day' % fieldName">
<option value="">--</option>
<option tal:repeat="value python: [str(v).zfill(2) for v in range(1, 32)]"
tal:content="value" tal:attributes="value value"></option>
</select>
</td>
</tr>
<tal:comment replace="nothing">To</tal:comment>
<tr>
<td></td>
<td>
<label tal:content="python: tool.translate('search_to')"></label>
</td>
<td>
<select tal:attributes="name python: '%s_to_year' % fieldName">
<option value="">--</option>
<option tal:repeat="value python:range(appyType['startYear'], appyType['endYear']+1)"
tal:content="value" tal:attributes="value value"></option>
</select> /
<select tal:attributes="name python: '%s_to_month' % fieldName">
<option value="">--</option>
<option tal:repeat="value python: [str(v).zfill(2) for v in range(1, 13)]"
tal:content="python:tool.getMonthName(value)" tal:attributes="value value"></option>
</select> /
<select tal:attributes="name python: '%s_to_day' % fieldName">
<option value="">--</option>
<option tal:repeat="value python: [str(v).zfill(2) for v in range(1, 32)]"
tal:content="value" tal:attributes="value value"></option>
</select>
</td>
</tr>
</table>
</metal:searchDate>
<metal:searchFile define-macro="searchFile">
<p tal:content="fieldName"></p>
</metal:searchFile>
<metal:searchRef define-macro="searchRef">
<p tal:content="fieldName"></p>
</metal:searchRef>
<metal:searchComputed define-macro="searchComputed">
<p tal:content="fieldName"></p>
</metal:searchComputed>
<metal:searchAction define-macro="searchAction">
<p tal:content="fieldName"></p>
</metal:searchAction>
<metal:searchInfo define-macro="searchInfo">
<p tal:content="fieldName"></p>
</metal:searchInfo>

View file

@ -0,0 +1,30 @@
<tal:comment replace="nothing">View macro for an Action.</tal:comment>
<metal:view define-macro="view">
<form name="executeAppyAction"
tal:define="formId python: '%s_%s' % (contextObj.UID(), name);
label python: contextObj.translate(widget['labelId'])"
tal:attributes="id formId; action python: contextObj.absolute_url()+'/skyn/do'">
<input type="hidden" name="action" value="ExecuteAppyAction"/>
<input type="hidden" name="objectUid" tal:attributes="value contextObj/UID"/>
<input type="hidden" name="fieldName" tal:attributes="value name"/>
<input type="button" tal:condition="widget/confirm"
tal:attributes="value label;
onClick python: 'javascript:askConfirm(\'%s\')' % formId"/>
<input type="submit" name="do" tal:condition="not: widget/confirm"
tal:attributes="value label" onClick="javascript:;"/>
<tal:comment replace="nothing">The previous onClick is simply used to prevent Plone
from adding a CSS class that displays a popup when the user triggers the form multiple
times.</tal:comment>
</form>
</metal:view>
<tal:comment replace="nothing">Edit macro for an Action.</tal:comment>
<metal:edit define-macro="edit"></metal:edit>
<tal:comment replace="nothing">Cell macro for an Action.</tal:comment>
<metal:cell define-macro="cell">
<metal:call use-macro="portal/skyn/widgets/action/macros/view"/>
</metal:cell>
<tal:comment replace="nothing">Search macro for an Action.</tal:comment>
<metal:search define-macro="search"></metal:search>

View file

@ -0,0 +1,39 @@
<tal:comment replace="nothing">View macro for a Boolean.</tal:comment>
<metal:view define-macro="view"><span tal:replace="value"/></metal:view>
<tal:comment replace="nothing">Edit macro for an Boolean.</tal:comment>
<metal:edit define-macro="edit">
<input type="checkbox"
tal:attributes="name python: name + '_visible';
id name;
checked python:contextObj.checkboxChecked(name);
onClick python:'toggleCheckbox(\'%s\', \'%s_hidden\');;updateSlaves(getMasterValue(this), \'%s\')' % (name, name, widget['id']);
class python: 'noborder ' + widget['master_css']"/>
<input tal:attributes="name name;
id string:${name}_hidden;
value python: test(contextObj.checkboxChecked(name), 'True', 'False')"
type="hidden" />&nbsp;
</metal:edit>
<tal:comment replace="nothing">Cell macro for an Boolean.</tal:comment>
<metal:cell define-macro="cell">
<metal:call use-macro="portal/skyn/widgets/boolean/macros/view"/>
</metal:cell>
<tal:comment replace="nothing">Search macro for an Boolean.</tal:comment>
<metal:search define-macro="search"
tal:define="typedWidget python:'%s*bool' % widgetName">
<label tal:attributes="for widgetName" tal:content="python: tool.translate(widget['labelId'])"></label><br>&nbsp;&nbsp;
<tal:yes define="valueId python:'%s_yes' % name">
<input type="radio" class="noborder" value="True" tal:attributes="name typedWidget; id valueId"/>
<label tal:attributes="for valueId" i18n:translate="yes" i18n:domain="plone"></label>
</tal:yes>
<tal:no define="valueId python:'%s_no' % name">
<input type="radio" class="noborder" value="False" tal:attributes="name typedWidget; id valueId"/>
<label tal:attributes="for valueId" i18n:translate="no" i18n:domain="plone"></label>
</tal:no>
<tal:whatever define="valueId python:'%s_whatever' % name">
<input type="radio" class="noborder" value="" tal:attributes="name typedWidget; id valueId" checked="checked"/>
<label tal:attributes="for valueId" tal:content="python: tool.translate('whatever')"></label>
</tal:whatever><br/>
</metal:search>

View file

@ -0,0 +1,18 @@
<tal:comment replace="nothing">View macro for a Computed.</tal:comment>
<metal:view define-macro="view">
<span tal:condition="widget/plainText" tal:replace="value"/>
<span tal:condition="not: widget/plainText" tal:replace="structure value"/>
</metal:view>
<tal:comment replace="nothing">Edit macro for a Computed.</tal:comment>
<metal:edit define-macro="edit">
<metal:call use-macro="portal/skyn/widgets/computed/macros/view"/>
</metal:edit>
<tal:comment replace="nothing">Cell macro for a Computed.</tal:comment>
<metal:cell define-macro="cell">
<metal:call use-macro="portal/skyn/widgets/computed/macros/view"/>
</metal:cell>
<tal:comment replace="nothing">Search macro for a Computed.</tal:comment>
<metal:search define-macro="search"></metal:search>

View file

@ -0,0 +1,132 @@
<tal:comment replace="nothing">View macro for a Date.</tal:comment>
<metal:view define-macro="view"><span tal:replace="value"/></metal:view>
<tal:comment replace="nothing">Edit macro for an Date.</tal:comment>
<metal:edit define-macro="edit"
tal:define="years python:range(widget['startYear'], widget['endYear']+1);
dummyName python: '_d_ummy_%s' % name">
<tal:comment replace="nothing">This field is not used but required by the Javascript popup.</tal:comment>
<input type="hidden" tal:attributes="name dummyName; id dummyName"/>
<tal:comment replace="nothing">Day</tal:comment>
<select tal:define="days python:range(1,32)"
tal:attributes="name string:${name}_day;
id string:${name}_day;">
<option value="">-</option>
<tal:days repeat="day days">
<option tal:define="zDay python: str(day).zfill(2)"
tal:attributes="value zDay;
selected python:contextObj.dateValueSelected(name, 'day', day)"
tal:content="zDay"></option>
</tal:days>
</select>
<tal:comment replace="nothing">Month</tal:comment>
<select tal:define="months python:range(1,13)"
tal:attributes="name string:${name}_month;
id string:${name}_month;">
<option value="">-</option>
<tal:months repeat="month months">
<option tal:define="zMonth python: str(month).zfill(2)"
tal:attributes="value zMonth;
selected python:contextObj.dateValueSelected(name, 'month', month)"
tal:content="zMonth"></option>
</tal:months>
</select>
<tal:comment replace="nothing">Year</tal:comment>
<select tal:attributes="name string:${name}_year;
id string:${name}_year;">
<option value="">-</option>
<option tal:repeat="year years"
tal:attributes="value year;
selected python:contextObj.dateValueSelected(name, 'year', year)"
tal:content="year"></option>
</select>
<tal:comment replace="nothing">The icon for displaying the date chooser</tal:comment>
<a tal:attributes="onclick python: 'return showJsCalendar(\'%s_month\', \'%s\', \'%s_year\', \'%s_month\', \'%s_day\', null, null, %d, %d)' % (name, dummyName, name, name, name, years[0], years[-1])"><img tal:attributes="src string: $portal_url/popup_calendar.gif"/></a>
<tal:hour condition="python: widget['format'] == 0">
<select tal:define="hours python:range(0,24);"
tal:attributes="name string:${name}_hour;
id string:${name}_hour;">
<option value="">-</option>
<tal:hours repeat="hour hours">
<option tal:define="zHour python: str(hour).zfill(2)"
tal:attributes="value zHour;
selected python:contextObj.dateValueSelected(name, 'hour', hour)"
tal:content="zHour"></option>
</tal:hours>
</select> :
<select tal:define="minutes python:range(0,60,5);"
tal:attributes="name string:${name}_minute;
id string:${name}_minute;">
<option value="">-</option>
<tal:minutes repeat="minute minutes">
<option tal:define="zMinute python: str(minute).zfill(2)"
tal:attributes="value zMinute;
selected python:contextObj.dateValueSelected(name, 'minute', minute)"
tal:content="zMinute"></option>
</tal:minutes>
</select>
</tal:hour>
</metal:edit>
<tal:comment replace="nothing">Cell macro for an Date.</tal:comment>
<metal:cell define-macro="cell">
<metal:call use-macro="portal/skyn/widgets/date/macros/view"/>
</metal:cell>
<tal:comment replace="nothing">Search macro for an Date.</tal:comment>
<metal:search define-macro="search">
<label tal:content="python: tool.translate(widget['labelId'])"></label>
<table cellpadding="0" cellspacing="0">
<tal:comment replace="nothing">From</tal:comment>
<tr tal:define="fromName python: '%s*date' % widgetName">
<td width="10px">&nbsp;</td>
<td>
<label tal:content="python: tool.translate('search_from')"></label>
</td>
<td>
<select tal:attributes="name fromName">
<option value="">--</option>
<option tal:repeat="value python:range(widget['startYear'], widget['endYear']+1)"
tal:content="value" tal:attributes="value value"></option>
</select> /
<select tal:attributes="name python: '%s_from_month' % name">
<option value="">--</option>
<option tal:repeat="value python: [str(v).zfill(2) for v in range(1, 13)]"
tal:content="python:tool.getMonthName(value)" tal:attributes="value value"></option>
</select> /
<select tal:attributes="name python: '%s_from_day' % name">
<option value="">--</option>
<option tal:repeat="value python: [str(v).zfill(2) for v in range(1, 32)]"
tal:content="value" tal:attributes="value value"></option>
</select>
</td>
</tr>
<tal:comment replace="nothing">To</tal:comment>
<tr>
<td></td>
<td>
<label tal:content="python: tool.translate('search_to')"></label>
</td>
<td>
<select tal:attributes="name python: '%s_to_year' % name">
<option value="">--</option>
<option tal:repeat="value python:range(widget['startYear'], widget['endYear']+1)"
tal:content="value" tal:attributes="value value"></option>
</select> /
<select tal:attributes="name python: '%s_to_month' % name">
<option value="">--</option>
<option tal:repeat="value python: [str(v).zfill(2) for v in range(1, 13)]"
tal:content="python:tool.getMonthName(value)" tal:attributes="value value"></option>
</select> /
<select tal:attributes="name python: '%s_to_day' % name">
<option value="">--</option>
<option tal:repeat="value python: [str(v).zfill(2) for v in range(1, 32)]"
tal:content="value" tal:attributes="value value"></option>
</select>
</td>
</tr>
</table>
</metal:search>

View file

@ -0,0 +1,73 @@
<tal:comment replace="nothing">View macro for a File.</tal:comment>
<metal:view define-macro="view"
tal:define="empty python: not value or not value.size;
imageSrc string:${contextObj/absolute_url}/download?name=$name">
<tal:file condition="python: not empty and not widget['isImage']">
<img tal:define="icon value/getBestIcon"
tal:condition="icon" tal:attributes="src string: $portal_url/$icon"/>
<a tal:attributes="href imageSrc"
tal:content="value/filename">
</a>&nbsp;&nbsp;-
<i class="discreet" tal:content="python:'%sKb' % (value.size / 1024)"></i>
</tal:file>
<tal:image condition="python: not empty and widget['isImage']">
<img tal:attributes="src python: imageSrc" />
</tal:image>
<tal:nothing tal:condition="empty">-</tal:nothing>
</metal:view>
<tal:comment replace="nothing">Edit macro for an File.</tal:comment>
<metal:edit define-macro="edit">
<tal:showFile condition="python: value and value.size">
<metal:call use-macro="portal/skyn/widgets/file/macros/view"/><br/>
</tal:showFile>
<tal:editButtons condition="python: value and value.size">
<tal:comment replace="nothing">Keep the file untouched.</tal:comment>
<input class="noborder" type="radio" value="nochange"
tal:attributes="checked python:test(value.size!=0, 'checked', None);
name string:${name}_delete;
id string:${name}_nochange;
onclick string:document.getElementById('${name}_file').disabled=true;"/>
<label tal:attributes="for string:${name}_nochange"
i18n:translate="nochange_file" i18n:domain="plone">
</label>
<br/>
<tal:comment replace="nothing">Delete the file.</tal:comment>
<tal:delete condition="not: widget/required">
<input class="noborder" type="radio" value="delete"
tal:attributes="name string:${name}_delete;
id string:${name}_delete;
onclick string:document.getElementById('${name}_file').disabled=true;"/>
<label tal:attributes="for string:${name}_delete"
i18n:translate="delete_file" i18n:domain="plone">
</label>
<br/>
</tal:delete>
<tal:comment replace="nothing">Replace with a new file.</tal:comment>
<input class="noborder" type="radio" value=""
tal:attributes="checked python:test(value.size==0, 'checked', None);
name string:${name}_delete;
id string:${name}_upload;
onclick string:document.getElementById('${name}_file').disabled=false"/>
<label tal:attributes="for string:${name}_upload;"
i18n:translate="upload_file" i18n:domain="plone">
</label>
<br/>
</tal:editButtons>
<tal:comment replace="nothing">The upload field.</tal:comment>
<input type="file" size="30"
tal:attributes="name string:${name}_file;
id string:${name}_file;"/>
<script type="text/javascript"
tal:define="isDisabled python:test(value and value.size, 'true', 'false')"
tal:content="string: document.getElementById('${name}_file').disabled=$isDisabled;">
</script>
</metal:edit>
<tal:comment replace="nothing">Cell macro for an File.</tal:comment>
<metal:cell define-macro="cell">
<metal:call use-macro="portal/skyn/widgets/file/macros/view"/>
</metal:cell>
<tal:comment replace="nothing">Search macro for an File.</tal:comment>
<metal:search define-macro="search"></metal:search>

View file

@ -0,0 +1,29 @@
<tal:comment replace="nothing">View macro for a Float.</tal:comment>
<metal:view define-macro="view">
<span tal:content="value"
tal:attributes="id value; class widget/master_css"></span>
</metal:view>
<tal:comment replace="nothing">Edit macro for an Float.</tal:comment>
<metal:edit define-macro="edit">
<input tal:attributes="id name; name name; size widget/width;
value python: test(inRequest, requestValue, value)" type="text"/>
</metal:edit>
<tal:comment replace="nothing">Cell macro for an Float.</tal:comment>
<metal:cell define-macro="cell">
<metal:call use-macro="portal/skyn/widgets/float/macros/view"/>
</metal:cell>
<tal:comment replace="nothing">Search macro for an Float.</tal:comment>
<metal:search define-macro="search">
<label tal:content="python: tool.translate(widget['labelId'])"></label><br>&nbsp;&nbsp;
<tal:from define="fromName python: '%s*float' % widgetName">
<label tal:attributes="for fromName" tal:content="python: tool.translate('search_from')"></label>
<input type="text" tal:attributes="name fromName" size="4"/>
</tal:from>
<tal:to define="toName python: '%s_to' % name">
<label tal:attributes="for toName" tal:content="python: tool.translate('search_to')"></label>
<input type="text" tal:attributes="name toName" size="4"/>
</tal:to><br/>
</metal:search>

View file

@ -0,0 +1,11 @@
<tal:comment replace="nothing">View macro for an Info.</tal:comment>
<metal:view define-macro="view"><tal:comment replace="nothing">Shows nothing more.</tal:comment></metal:view>
<tal:comment replace="nothing">Edit macro for an Info.</tal:comment>
<metal:edit define-macro="edit"></metal:edit>
<tal:comment replace="nothing">Cell macro for an Info.</tal:comment>
<metal:cell define-macro="cell"></metal:cell>
<tal:comment replace="nothing">Search macro for an Info.</tal:comment>
<metal:search define-macro="search"></metal:search>

View file

@ -0,0 +1,28 @@
<tal:comment replace="nothing">View macro for an Integer.</tal:comment>
<metal:view define-macro="view">
<span tal:content="value" tal:attributes="id value; class widget/master_css"></span>
</metal:view>
<tal:comment replace="nothing">Edit macro for an Integer.</tal:comment>
<metal:edit define-macro="edit">
<input tal:attributes="id name; name name; size widget/width;
value python: test(inRequest, requestValue, value)" type="text"/>
</metal:edit>
<tal:comment replace="nothing">Cell macro for an Integer.</tal:comment>
<metal:cell define-macro="cell">
<metal:call use-macro="portal/skyn/widgets/integer/macros/view"/>
</metal:cell>
<tal:comment replace="nothing">Search macro for an Integer.</tal:comment>
<metal:search define-macro="search">
<label tal:content="python: tool.translate(widget['labelId'])"></label><br>&nbsp;&nbsp;
<tal:from define="fromName python: '%s*int' % widgetName">
<label tal:attributes="for fromName" tal:content="python: tool.translate('search_from')"></label>
<input type="text" tal:attributes="name fromName" size="4"/>
</tal:from>
<tal:to define="toName python: '%s_to' % name">
<label tal:attributes="for toName" tal:content="python: tool.translate('search_to')"></label>
<input type="text" tal:attributes="name toName" size="4"/>
</tal:to><br/>
</metal:search>

View file

@ -0,0 +1,26 @@
<tal:comment replace="nothing">View macro for a Pod.</tal:comment>
<metal:view define-macro="view">
<tal:askAction condition="widget/askAction"
define="doLabel python:'%s_askaction' % widget['labelId'];
chekboxId python: '%s_%s' % (contextObj.UID(), name)">
<input type="checkbox" tal:attributes="name doLabel; id chekboxId"/>
<label tal:attributes="for chekboxId" class="discreet"
tal:content="python: tool.translate(doLabel)"></label>
</tal:askAction>
<img tal:repeat="podFormat python:flavour.getPodInfo(contextObj, name)['formats']"
tal:attributes="src string: $portal_url/skyn/${podFormat}.png;
onClick python: 'javascript:generatePodDocument(\'%s\',\'\',\'%s\',\'%s\')' % (contextObj.UID(), name, podFormat);
title podFormat/capitalize"
style="cursor:pointer"/>
</metal:view>
<tal:comment replace="nothing">Edit macro for a Pod.</tal:comment>
<metal:edit define-macro="edit"></metal:edit>
<tal:comment replace="nothing">Cell macro for a Pod.</tal:comment>
<metal:cell define-macro="cell">
<metal:call use-macro="portal/skyn/widgets/pod/macros/view"/>
</metal:cell>
<tal:comment replace="nothing">Search macro for a Pod.</tal:comment>
<metal:search define-macro="search"></metal:search>

View file

@ -1,5 +1,6 @@
<tal:comment replace="nothing"> We begin with some sub-macros used within
macro "showReference" defined below.</tal:comment>
<tal:comment replace="nothing">
We begin with some sub-macros used within macro "show" defined below.
</tal:comment>
<metal:objectTitle define-macro="objectTitle">
<tal:comment replace="nothing">Displays the title of a referenced object, with a link on
@ -9,7 +10,7 @@
from one object to the next/previous on skyn/view.</tal:comment>
<a tal:define="viewUrl obj/getUrl;
navInfo python:'nav=ref.%s.%s.%d.%d' % (contextObj.UID(), fieldName, repeat['obj'].number()+startNumber, totalNumber);
fullUrl python: test(isBack, viewUrl + '/?pageName=%s&phase=%s' % (appyType['page'], appyType['phase']), viewUrl + '/?' + navInfo)"
fullUrl python: test(appyType['isBack'], viewUrl + '/?page=%s' % appyType['page'], viewUrl + '/?' + navInfo)"
tal:attributes="href fullUrl" tal:content="obj/Title"></a>
</metal:objectTitle>
@ -77,42 +78,28 @@
onClick python: ajaxBaseCall.replace('**v**', 'True')"/>
</metal:sortIcons>
<tal:comment replace="nothing">
This macro shows a reference field. More precisely, it shows nothing, but calls
a Javascript function that will asynchonously call (via a XmlHttpRequest object) the
macro 'showReferenceContent' defined below, that will really show content.
It requires:
- isBack (bool) Is the reference a backward or forward reference?
- fieldName (string) The name of the reference field (if it is a forward reference)
or the name of the Archetypes relationship (if it is a backward reference)
- innerRef (bool) Are we rendering a reference within a reference or not?
- contextObj (object) the object from which the reference starts
- labelId (string) the i18n id of the reference field label
- descrId (string) the i18n id of the reference field description
</tal:comment>
<div metal:define-macro="showReference"
tal:define="ajaxHookId python: contextObj.UID() + fieldName"
<tal:comment replace="nothing">View macro for a Ref.</tal:comment>
<div metal:define-macro="view"
tal:define= "innerRef innerRef|python:False;
ajaxHookId python: contextObj.UID() + name"
tal:attributes = "id ajaxHookId">
<script language="javascript"
tal:content="python: 'askRefField(\'%s\',\'%s\',\'%s\',\'%s\',\'%s\',\'%s\',\'%s\',0)' % (ajaxHookId, contextObj.absolute_url(), fieldName, isBack, innerRef, labelId, descrId)">
tal:content="python: 'askRefField(\'%s\',\'%s\',\'%s\',\'%s\',0)' % (ajaxHookId, contextObj.absolute_url(), name, innerRef)">
</script>
</div>
<tal:comment replace="nothing">
This macro is called by a XmlHttpRequest for displaying the paginated referred objects
of a reference field.
This macro is called by a XmlHttpRequest for displaying the paginated
referred objects of a reference field.
</tal:comment>
<div metal:define-macro="showReferenceContent"
<div metal:define-macro="viewContent"
tal:define="fieldName request/fieldName;
isBack python: test(request['isBack']=='True', True, False);
appyType python: contextObj.getAppyType(fieldName, asDict=True);
innerRef python: test(request['innerRef']=='True', True, False);
labelId request/labelId;
descrId request/descrId;
ajaxHookId python: contextObj.UID()+fieldName;
startNumber python: int(request.get('%s_startNumber' % ajaxHookId, 0));
appyType python: contextObj.getAppyType(fieldName, not isBack);
tool contextObj/getTool;
refObjects python:contextObj.getAppyRefs(fieldName, not isBack, startNumber);
refObjects python:contextObj.getAppyRefs(appyType, startNumber);
objs refObjects/objects;
totalNumber refObjects/totalNumber;
batchSize refObjects/batchSize;
@ -120,14 +107,14 @@
flavour python:tool.getFlavour(contextObj);
linkedPortalType python:flavour.getPortalType(appyType['klass']);
addPermission python: '%s: Add %s' % (tool.getAppName(), linkedPortalType);
canWrite python: not isBack and member.has_permission(contextObj.getField(fieldName).write_permission, contextObj);
multiplicity python:test(isBack, appyType['backd']['multiplicity'], appyType['multiplicity']);
canWrite python: not appyType['isBack'] and member.has_permission(appyType['writePermission'], contextObj);
multiplicity appyType/multiplicity;
maxReached python:(multiplicity[1] != None) and (len(objs) >= multiplicity[1]);
showPlusIcon python:not isBack and appyType['add'] and not maxReached and member.has_permission(addPermission, folder) and canWrite;
showPlusIcon python:not appyType['isBack'] and appyType['add'] and not maxReached and member.has_permission(addPermission, folder) and canWrite;
atMostOneRef python: (multiplicity[1] == 1) and (len(objs)&lt;=1);
label python: tool.translate(labelId);
description python: tool.translate(descrId);
navBaseCall python: 'askRefField(\'%s\',\'%s\',\'%s\',\'%s\',\'%s\',\'%s\',\'%s\',**v**)' % (ajaxHookId, contextObj.absolute_url(), fieldName, isBack, innerRef, labelId, descrId)">
label python: tool.translate(appyType['labelId']);
description python: tool.translate(appyType['descrId']);
navBaseCall python: 'askRefField(\'%s\',\'%s\',\'%s\',\'%s\',**v**)' % (ajaxHookId, contextObj.absolute_url(), fieldName, innerRef)">
<tal:comment replace="nothing">This macro displays the Reference widget on a "consult" page.
@ -139,20 +126,20 @@
<tal:comment replace="nothing">Display a simplified widget if maximum number of
referenced objects is 1.</tal:comment>
<table class="no-style-table" cellpadding="0" cellspacing="0"><tr valign="top">
<td><span class="appyLabel" tal:condition="not: innerRef" tal:content="label"></span></td>
<td><span class="appyLabel" tal:condition="not: innerRef" tal:content="structure label"></span></td>
<tal:comment replace="nothing">If there is no object...</tal:comment>
<tal:noObject condition="not:objs">
<td tal:content="python: tool.translate('no_ref')"></td>
<td><metal:plusIcon use-macro="here/skyn/ref/macros/plusIcon"/></td>
<td><metal:plusIcon use-macro="portal/skyn/widgets/ref/macros/plusIcon"/></td>
</tal:noObject>
<tal:comment replace="nothing">If there is an object...</tal:comment>
<tal:objectIsPresent condition="python: len(objs) == 1">
<tal:obj repeat="obj objs">
<td><metal:showObjectTitle use-macro="here/skyn/ref/macros/objectTitle" /></td>
<td tal:condition="not: isBack">
<metal:showObjectActions use-macro="here/skyn/ref/macros/objectActions" />
<td><metal:showObjectTitle use-macro="portal/skyn/widgets/ref/macros/objectTitle" /></td>
<td tal:condition="not: appyType/isBack">
<metal:showObjectActions use-macro="portal/skyn/widgets/ref/macros/objectActions" />
</td>
</tal:obj>
</tal:objectIsPresent>
@ -165,12 +152,12 @@
<legend tal:condition="python: not innerRef or showPlusIcon">
<span tal:condition="not: innerRef" tal:content="label"/>
(<span tal:replace="totalNumber"/>)
<metal:plusIcon use-macro="here/skyn/ref/macros/plusIcon"/>
<metal:plusIcon use-macro="portal/skyn/widgets/ref/macros/plusIcon"/>
</legend>
<tal:comment replace="nothing">Object description</tal:comment>
<p tal:condition="python: not innerRef and description.strip()"
tal:content="description" class="discreet" ></p>
<!--p tal:condition="python: not innerRef and description.strip()"
tal:content="description" class="discreet" ></p-->
<tal:comment replace="nothing">Appy (top) navigation</tal:comment>
<metal:nav use-macro="here/skyn/navigate/macros/appyNavigate"/>
@ -184,35 +171,29 @@
<tal:comment replace="nothing">Show backward reference(s)</tal:comment>
<table class="no-style-table" cellspacing="0" cellpadding="0"
tal:condition="python: isBack and objs">
tal:condition="python: appyType['isBack'] and objs">
<tr tal:repeat="obj objs">
<td><metal:showObjectTitle use-macro="here/skyn/ref/macros/objectTitle" />
<td><metal:showObjectTitle use-macro="portal/skyn/widgets/ref/macros/objectTitle" />
</td>
</tr>
</table>
<tal:comment replace="nothing">Show forward reference(s)</tal:comment>
<table tal:attributes="class python:test(innerRef, '', 'listing nosort');
width python:test(innerRef, '100%', test(appyType['wide'], '100%', ''))"
align="right" tal:condition="python: not isBack and objs" cellpadding="0" cellspacing="0">
width python:test(innerRef, '100%', appyType['layouts']['view']['width']);"
align="right" tal:condition="python: not appyType['isBack'] and objs" cellpadding="0" cellspacing="0">
<tal:widgets define="widgets python: objs[0].getAppyTypesFromNames(appyType['shownInfo'])">
<tr tal:condition="appyType/showHeaders">
<th tal:condition="python: 'title' not in appyType['shownInfo']"
tal:define="shownField python:'title'">
<span tal:content="python: tool.translate('ref_name')"></span>
<metal:sortIcons use-macro="here/skyn/ref/macros/sortIcons" />
<metal:sortIcons use-macro="portal/skyn/widgets/ref/macros/sortIcons" />
</th>
<th tal:repeat="shownField appyType/shownInfo">
<tal:showHeader condition="python: objs[0].getField(shownField)">
<tal:titleHeader condition="python: shownField == 'title'">
<span tal:content="python: tool.translate('ref_name')"></span>
<metal:sortIcons use-macro="here/skyn/ref/macros/sortIcons" />
</tal:titleHeader>
<tal:otherHeader condition="python: shownField != 'title'"
define="labelId python: objs[0].getField(shownField).widget.label_msgid">
<span tal:content="python: tool.translate(labelId)"></span>
<metal:sortIcons use-macro="here/skyn/ref/macros/sortIcons" />
</tal:otherHeader>
</tal:showHeader>
<th tal:repeat="widget widgets">
<tal:header define="shownField widget/name">
<span tal:content="python: tool.translate(widget['labelId'])"></span>
<metal:sortIcons use-macro="portal/skyn/widgets/ref/macros/sortIcons" />
</tal:header>
</th>
<th tal:content="python: tool.translate('ref_actions')"></th>
</tr>
@ -222,41 +203,27 @@
<tal:comment replace="nothing">Object title, shown here if not specified somewhere
else in appyType.shownInfo.</tal:comment>
<td tal:condition="python: 'title' not in appyType['shownInfo']"><metal:showObjectTitle
use-macro="here/skyn/ref/macros/objectTitle"/>&nbsp;
use-macro="portal/skyn/widgets/ref/macros/objectTitle"/>&nbsp;
</td>
<tal:comment replace="nothing">Additional fields that must be shown</tal:comment>
<td tal:repeat="shownField appyType/shownInfo">
<tal:showTitle condition="python: shownField == 'title'">
<metal:showObjectTitle use-macro="here/skyn/ref/macros/objectTitle"/>
<td tal:repeat="widget widgets">
<tal:showTitle condition="python: widget['name'] == 'title'">
<metal:showObjectTitle use-macro="portal/skyn/widgets/ref/macros/objectTitle"/>
</tal:showTitle>
<tal:showOtherField define="appyType python: obj.getAppyType(shownField);
field python:obj.getField(shownField);
contextObj python:obj;"
condition="python: appyType and (shownField != 'title')">
<tal:showNormalField condition="python: appyType['type'] not in ('Ref', 'Computed', 'Action')">
<metal:viewField use-macro="python: obj.widget(shownField, 'view', use_label=0)"/>
</tal:showNormalField>
<tal:showRef condition="python: appyType['type'] == 'Ref'">
<tal:ref tal:define="isBack python:appyType['isBack'];
fieldName python: test(isBack, field.relationship, field.getName());
innerRef python:True">
<metal:showField use-macro="here/skyn/ref/macros/showReference" />
</tal:ref>
</tal:showRef>
<tal:showComputed condition="python: appyType['type'] == 'Computed'">
<tal:computed content="python: obj.getComputedValue(appyType)"/>
</tal:showComputed>
<tal:showAction condition="python: appyType['type'] == 'Action'">
<metal:action use-macro="here/skyn/macros/macros/showActionField" />
</tal:showAction>
<tal:showOtherField define="contextObj python:obj;
layoutType python: 'cell';
innerRef python:True"
condition="python: widget['name'] != 'title'">
<metal:showField use-macro="portal/skyn/widgets/show/macros/field" />
</tal:showOtherField>&nbsp;
</td>
<tal:comment replace="nothing">Actions</tal:comment>
<td align="right">
<metal:showObjectActions use-macro="here/skyn/ref/macros/objectActions" />
<metal:showObjectActions use-macro="portal/skyn/widgets/ref/macros/objectActions" />
</td>
</tr>
</tal:row>
</tal:widgets>
</table>
</td></tr>
@ -272,18 +239,17 @@
</tal:anyNumberOfReferences>
</div>
<div metal:define-macro="editReference"
tal:define="refPortalType python:here.getAppyRefPortalType(field.getName());
appyType python:here.getAppyType(field.getName());
<tal:comment replace="nothing">Edit macro for an Ref.</tal:comment>
<div define-macro="edit"
tal:condition="widget/link"
tal:define="refPortalType python: contextObj.getAppyRefPortalType(name);
allBrains python:here.uid_catalog(portal_type=refPortalType);
brains python:here.callAppySelect(appyType['select'], allBrains);
refUids python: [o.UID() for o in here.getAppyRefs(field.getName())['objects']];
isMultiple python:test(appyType['multiplicity'][1]!=1, 'multiple', '');
appyFieldName python: 'appy_ref_%s' % field.getName();
inError python:test(errors.has_key(field.getName()), True, False);
defaultValue python: contextObj.getDefault(field.getName());
defaultValueUID defaultValue/UID|nothing;
isBeingCreated python: contextObj.portal_factory.isTemporary(contextObj) or ('/portal_factory/' in contextObj.absolute_url())"
brains python:contextObj.callAppySelect(widget['select'], allBrains);
refUids python: [o.UID() for o in here.getAppyRefs(name)['objects']];
isMultiple python:test(widget['multiplicity'][1]!=1, 'multiple', '');
appyFieldName python: 'appy_ref_%s' % name;
inError python:test(errors.has_key(name), True, False);
isBeingCreated python: contextObj.isTemporary() or ('/portal_factory/' in contextObj.absolute_url())"
tal:attributes="class python:'appyRefEdit field' + test(inError, ' error', '')">
<tal:comment replace="nothing">This macro displays the Reference widget on an "edit" page</tal:comment>
@ -301,3 +267,11 @@
selected python:test((valueIsInReq and (brain.UID in request.get(appyFieldName, []))) or (not valueIsInReq and ((brain.UID in refUids) or (isBeingCreated and (brain.UID==defaultValueUID)))), True, False)"/>
</select>
</div>
<tal:comment replace="nothing">Cell macro for a Ref.</tal:comment>
<metal:cell define-macro="cell">
<metal:call use-macro="portal/skyn/widgets/ref/macros/view"/>
</metal:cell>
<tal:comment replace="nothing">Search macro for a Ref.</tal:comment>
<metal:search define-macro="search"></metal:search>

View file

@ -0,0 +1,203 @@
<tal:comment replace="nothing">
This macro shows the content of a layouted object, like a page or widget.
It requires:
contextObj The Zope object on which we are working
layoutType The kind of layout: "view"? "edit"? "cell"?
layout The layout object that will dictate how object content
will be rendered.
Options:
contextMacro The base on folder containing the macros to call for
rendering the elements within the layout.
Defaults to portal.skyn
</tal:comment>
<metal:show define-macro="layout"
tal:define="contextMacro contextMacro| python: portal.skyn">
<table tal:attributes="cellpadding layout/cellpadding;
cellspacing layout/cellspacing;
width layout/width;
align layout/align;
class layout/css_class;
style layout/style">
<tal:comment replace="nothing">The table header row</tal:comment>
<tr tal:condition="layout/headerRow" tal:attributes="valign layout/headerRow/valign">
<th tal:repeat="cell layout/headerRow/cells"
tal:attributes="align cell/align; width cell/width;">
</th>
</tr>
<tal:comment replace="nothing">The table content</tal:comment>
<tr tal:repeat="row layout/rows" tal:attributes="valign row/valign">
<td tal:repeat="cell row/cells"
tal:attributes="align cell/align; colspan cell/colspan;
style python: test(repeat['cell'].end, '', 'padding-right: 0.4em')">
<tal:content repeat="elem cell/content">
<tal:field condition="python: elem == '?'">
<metal:call use-macro="python: contextMacro.get(widget['type'].lower()).macros.get(layoutType)"/>
</tal:field>
<tal:other condition="python: elem != '?'">
<metal:call use-macro="python: contextMacro.get(elem[0]).macros.get(elem[1])"/>
</tal:other>
<img tal:condition="not: repeat/elem/end" tal:attributes="src string: $portal_url/skyn/space.gif"/>
</tal:content>
</td>
</tr>
</table>
</metal:show>
<tal:comment replace="nothing">
This macro displays the widget corresponding to a given field. It requires:
contextObj The Zope object for which this widget must be rendered
page The page where the widget lies
layoutType "edit"? "view"? "cell?"
widget The widget to render
</tal:comment>
<metal:field define-macro="field"
tal:define="contextMacro python: portal.skyn.widgets;
layout python: widget['layouts'][layoutType];
name widget/name;
value python: contextObj.getFormattedValue(name);
requestValue python: request.get(name, None);
inRequest python: request.has_key(name);
errors errors | python: ();
inError python: test(widget['name'] in errors, True, False)">
<metal:layout use-macro="here/skyn/widgets/show/macros/layout"/>
</metal:field>
<tal:comment replace="nothing">
This macro displays the widget corresponding to a group of widgets.
It requires:
contextObj The Zope object for which this widget must be rendered
page The page where the widget lies
layoutType "edit"? "view"? "cell?"
widget The widget to render
</tal:comment>
<metal:group define-macro="group">
<fieldset tal:condition="python: widget['style'] == 'fieldset'">
<legend tal:condition="widget/hasLabel">
<i tal:content="structure python: contextObj.translate(widget['labelId'])"></i>
<tal:help condition="widget/hasHelp">
<metal:call use-macro="portal/skyn/widgets/show/macros/help"/>
</tal:help>
</legend>
<div tal:condition="widget/hasDescr" class="discreet"
tal:content="structure python: contextObj.translate(widget['descrId'])"></div>
<metal:content use-macro="portal/skyn/widgets/show/macros/groupContent"/>
</fieldset>
<tal:asSection condition="python: widget['style'] not in ('fieldset', 'tabs')">
<metal:content use-macro="portal/skyn/widgets/show/macros/groupContent"/>
</tal:asSection>
<tal:asTabs condition="python: widget['style'] == 'tabs'">
<table cellpadding="0" cellspacing="0" tal:attributes="width python: test(widget['wide'], '100%', '')">
<tal:comment replace="nothing">First row: the tabs.</tal:comment>
<tr><td style="border-bottom: 1px solid #ff8040">
<table cellpadding="0" cellspacing="0" style="position:relative; bottom:-1px;">
<tr valign="bottom">
<tal:tab repeat="widgetRow widget/widgets">
<tal:id define="tabId python:'tab_%s_%d_%d' % (widget['name'], repeat['widgetRow'].number(), len(widget['widgets']))">
<td><img tal:attributes="src string: $portal_url/skyn/tabLeft.png;
id python: '%s_left' % tabId"/><td>
<td tal:attributes="style python:'background-image: url(%s/skyn/tabBg.png)' % portal_url;
id tabId">
<a style="cursor:pointer"
tal:content="python: tool.translate('%s_col%d' % (widget['labelId'], repeat['widgetRow'].number()))"
tal:attributes="onClick python: 'javascript:showTab(\'%s_%d_%d\')' % (widget['name'], repeat['widgetRow'].number(), len(widget['widgets']))"></a>
</td>
<td><img tal:attributes="src string: $portal_url/skyn/tabRight.png;
id python: '%s_right' % tabId"/><td>
</tal:id>
</tal:tab>
</tr>
</table>
</td></tr>
<tal:comment replace="nothing">Other rows: the widgets.</tal:comment>
<tr tal:repeat="widgetRow widget/widgets"
tal:attributes="id python: 'tabcontent_%s_%d_%d' % (widget['name'], repeat['widgetRow'].number(), len(widget['widgets']));
style python: test(repeat['widgetRow'].number()==1, 'display:table-row', 'display:none')">
<td tal:define="widget python: widgetRow[0]">
<tal:group condition="python: widget['type'] == 'group'">
<metal:call use-macro="portal/skyn/widgets/show/macros/group"/>
</tal:group>
<tal:field condition="python: widget['type'] != 'group'">
<metal:call use-macro="portal/skyn/widgets/show/macros/field"/>
</tal:field>
</td>
</tr>
</table>
<script language="javascript"
tal:content="python: 'initTab(\'tab_%s\', \'%s_1_%d\')' % (widget['name'], widget['name'], len(widget['widgets']))"></script>
</tal:asTabs>
</metal:group>
<tal:comment replace="nothing">
This macro displays the content of a group of widgets.
It is exclusively called by macro "group" above.
</tal:comment>
<table metal:define-macro="groupContent" align="center"
tal:attributes="width python: test(widget['wide'], '100%', '')">
<tal:comment replace="nothing">Display the title of the group if it is not rendered a fieldset.</tal:comment>
<tr tal:condition="python: (widget['style'] != 'fieldset') and widget['hasLabel']">
<td tal:attributes="colspan python: len(widget['columnsWidths']);
class widget/style">
<span tal:replace="structure python: contextObj.translate(widget['labelId'])"/>
<tal:help condition="widget/hasHelp">
<metal:call use-macro="portal/skyn/widgets/show/macros/help"/>
</tal:help>
</td>
</tr>
<tr tal:condition="python: (widget['style'] != 'fieldset') and widget['hasDescr']">
<td tal:attributes="colspan python: len(widget['columnsWidths'])" class="discreet"
tal:content="structure python: contextObj.translate(widget['descrId'])">
</td>
</tr>
<tr> <tal:comment replace="nothing">The column headers</tal:comment>
<th tal:repeat="colNb python:range(len(widget['columnsWidths']))"
tal:attributes="width python:widget['columnsWidths'][colNb];
align python:widget['columnsAligns'][colNb]"
tal:content="structure python: test(widget['hasHeaders'], contextObj.translate('%s_col%d' % (widget['labelId'], colNb+1)), '')">
</th>
</tr>
<tal:comment replace="nothing">The rows of widgets</tal:comment>
<tr valign="top" tal:repeat="widgetRow widget/widgets">
<td tal:repeat="widget widgetRow"
tal:attributes="colspan widget/colspan|python:1;
style python: test(repeat['widget'].number() != len(widgetRow), 'padding-right: 0.6em', '')">
<tal:showWidget condition="widget">
<tal:group condition="python: widget['type'] == 'group'">
<metal:call use-macro="portal/skyn/widgets/show/macros/group"/>
</tal:group>
<tal:field condition="python: widget['type'] != 'group'">
<metal:call use-macro="portal/skyn/widgets/show/macros/field"/>
</tal:field>
</tal:showWidget>
</td>
</tr>
</table>
<tal:comment replace="nothing">Displays a field label.</tal:comment>
<tal:label metal:define-macro="label" condition="widget/hasLabel">
<label tal:attributes="for widget/name"
tal:condition="python: widget['type'] not in ('Action', 'Ref')"
tal:content="structure python: contextObj.translate(widget['labelId'])"></label>
</tal:label>
<tal:comment replace="nothing">Displays a field description.</tal:comment>
<tal:description metal:define-macro="description" condition="widget/hasDescr">
<span class="discreet" tal:content="structure python: contextObj.translate(widget['descrId'])"></span>
</tal:description>
<tal:comment replace="nothing">Displays a field help.</tal:comment>
<tal:help metal:define-macro="help">
<acronym tal:attributes="title python: contextObj.translate(widget['helpId'])">
<img tal:attributes="src string: $portal_url/skyn/help.png"/>
</acronym>
</tal:help>
<tal:comment replace="nothing">Displays validation-error-related info about a field.</tal:comment>
<tal:validation metal:define-macro="validation">
<acronym tal:condition="inError" tal:attributes="title python: errors[name]">
<img tal:attributes="src string: $portal_url/skyn/warning.png"/>
</acronym>
<img tal:condition="not: inError" tal:attributes="src string: $portal_url/skyn/warning_no.png"/>
</tal:validation>
<tal:comment replace="nothing">Displays the fact that a field is required.</tal:comment>
<tal:required metal:define-macro="required"><img tal:attributes="src string: $portal_url/skyn/required.png"/></tal:required>

View file

@ -0,0 +1,102 @@
<tal:comment replace="nothing">View macro for a String.</tal:comment>
<metal:view define-macro="view"
tal:define="fmt widget/format;
maxMult python: widget['multiplicity'][1];
severalValues python: (maxMult == None) or (maxMult &gt; 1)">
<span tal:condition="python: fmt in (0, 3)"
tal:attributes="class widget/master_css;
id python: contextObj.getFormattedValue(name, forMasterId=True)">
<ul class="appyList" tal:condition="python: value and severalValues">
<li class="appyBullet" tal:repeat="sv value"><i tal:content="structure sv"></i></li>
</ul>
<tal:singleValue condition="python: value and not severalValues">
<span tal:condition="python: fmt != 3" tal:replace="structure value"/>
<span tal:condition="python: fmt == 3">********</span>
</tal:singleValue>
</span>
<tal:formattedString condition="python: fmt not in (0, 3)">
<span tal:condition="python: value and (fmt == 1)"
tal:replace="structure python: value.replace('\n', '&lt;br&gt;')"/>
<span tal:condition="python: value and (fmt == 2)" tal:replace="structure value"/>
</tal:formattedString>
</metal:view>
<tal:comment replace="nothing">Edit macro for a String.</tal:comment>
<metal:edit define-macro="edit"
tal:define="fmt widget/format;
isSelect widget/isSelect;
isMaster widget/slaves;
isOneLine python: fmt in (0,3)">
<tal:choice condition="isSelect">
<select tal:define="possibleValues python:contextObj.getPossibleValues(name, withTranslations=True, withBlankValue=True)"
tal:attributes="name name;
id name;
multiple python: test(widget['multiplicity'][1] != 1, 'multiple', '');
onchange python: test(isMaster, 'javascript:updateSlaves(getMasterValue(this), \'%s\')' % widget['id'], '');
class widget/master_css">
<option tal:repeat="possibleValue possibleValues"
tal:attributes="value python: possibleValue[0];
selected python:contextObj.fieldValueSelected(name, possibleValue[0])"
tal:content="python:tool.truncateValue(possibleValue[1], widget)"></option>
</select>
</tal:choice>
<tal:line condition="python: isOneLine and not isSelect">
<input tal:attributes="id name; name name; size widget/width;
value python: test(inRequest, requestValue, value)" type="text"/>
</tal:line>
<tal:textarea condition="python: fmt == 1">
<textarea tal:attributes="id name; name name;
cols widget/width;
rows widget/height;"
tal:content="python: test(inRequest, requestValue, value)">
</textarea>
<input type="hidden" value="text/plain" originalvalue="text/plain"
tal:attributes="name python: '%s_text_format' % name"/>
</tal:textarea>
<tal:rich condition="python: fmt == 2">
<tal:editor define="editor python: member.getProperty('wysiwyg_editor','').lower();
macrosFile python: path('nocall:here/%s_wysiwyg_support' % editor);
fieldName name;
inputname name;
inputvalue python: test(inRequest, requestValue, value);
dummy python: request.set('%s_text_format' % name, 'text/html')">
<metal:box use-macro="macrosFile/macros/wysiwygEditorBox"/>
</tal:editor>
</tal:rich>
</metal:edit>
<tal:comment replace="nothing">Cell macro for a String.</tal:comment>
<metal:cell define-macro="cell">
<metal:call use-macro="portal/skyn/widgets/string/macros/view"/>
</metal:cell>
<tal:comment replace="nothing">Search macro for a String.</tal:comment>
<metal:search define-macro="search">
<label tal:attributes="for widgetName" tal:content="python: tool.translate(widget['labelId'])"></label><br>&nbsp;&nbsp;
<tal:comment replace="nothing">Show a simple search field for most String fields.</tal:comment>
<tal:simpleSearch condition="not: widget/isSelect">
<input type="text" tal:attributes="name python: '%s*string-%s' % (widgetName, widget['transform']);
style python: 'text-transform:%s' % widget['transform']"/>
</tal:simpleSearch>
<tal:comment replace="nothing">Show a multi-selection box for fields whose
validator defines a list of values, with a "AND/OR" checkbox.</tal:comment>
<tal:selectSearch condition="widget/isSelect">
<tal:comment replace="nothing">The "and" / "or" radio buttons</tal:comment>
<tal:operator define="operName python: 'o_%s' % fieldName;
orName python: '%s_or' % operName;
andName python: '%s_and' % operName;"
condition="python: widget['multiplicity'][1]!=1">
<input type="radio" class="noborder" tal:attributes="name operName; id orName" checked="checked" value="or"/>
<label tal:attributes="for orName" tal:content="python: tool.translate('search_or')"></label>
<input type="radio" class="noborder" tal:attributes="name operName; id andName" value="and"/>
<label tal:attributes="for andName" tal:content="python: tool.translate('search_and')"></label><br/>
</tal:operator>
<tal:comment replace="nothing">The list of values</tal:comment>
<select tal:attributes="name widgetName" multiple="multiple" size="5">
<option tal:repeat="v python:tool.getPossibleValues(name, withTranslations=True, withBlankValue=False, className=contentType)"
tal:attributes="value python:v[0]" tal:content="python: v[1]">
</option>
</select>
</tal:selectSearch><br/>
</metal:search>

View file

@ -26,7 +26,6 @@ class <!genClassName!>(<!parents!>):
suppl_views = ()
typeDescription = '<!genClassName!>'
typeDescMsgId = '<!genClassName!>_edit_descr'
_at_rename_after_creation = True
i18nDomain = '<!applicationName!>'
schema = fullSchema
wrapperClass = <!genClassName!>_Wrapper

View file

@ -6,11 +6,9 @@ import Products.<!applicationName!>.config
from appy.gen.plone25.mixins.FlavourMixin import FlavourMixin
from Extensions.appyWrappers import <!wrapperClass!>
predefinedSchema = Schema((<!predefinedFields!>
),)
schema = Schema((<!fields!>
),)
fullSchema = OrderedBaseFolderSchema.copy() + predefinedSchema.copy() + schema.copy()
fullSchema = OrderedBaseFolderSchema.copy() + schema.copy()
class <!flavourName!>(OrderedBaseFolder, FlavourMixin):
'''Configuration flavour class for <!applicationName!>.'''
@ -32,10 +30,8 @@ class <!flavourName!>(OrderedBaseFolder, FlavourMixin):
schema = fullSchema
allMetaTypes = <!metaTypes!>
wrapperClass = <!wrapperClass!>
_at_rename_after_creation = True
for elem in dir(FlavourMixin):
if not elem.startswith('__'): security.declarePublic(elem)
<!commonMethods!>
<!predefinedMethods!>
<!methods!>
registerType(<!flavourName!>, '<!applicationName!>')

View file

@ -27,7 +27,6 @@ class <!applicationName!>PodTemplate(BaseContent, PodTemplateMixin):
suppl_views = ()
typeDescription = "<!applicationName!>PodTemplate"
typeDescMsgId = '<!applicationName!>_edit_descr'
_at_rename_after_creation = True
wrapperClass = <!wrapperClass!>
schema = fullSchema
for elem in dir(PodTemplateMixin):

View file

@ -7,7 +7,7 @@
flavour python: tool.getFlavour(tool);"
tal:condition="tool/showPortlet">
<metal:block metal:use-macro="here/global_defines/macros/defines" />
<metal:prologue use-macro="here/skyn/macros/macros/pagePrologue"/>
<metal:prologue use-macro="here/skyn/page/macros/prologue"/>
<dl tal:define="rootClasses tool/getRootClasses;
appName string:<!applicationName!>;
appFolder tool/getAppFolder;

View file

@ -4,23 +4,16 @@
#portal-breadcrumbs { display: none; }
#importedElem { color: grey; font-style: italic; }
label { font-weight: bold; font-style: italic; }
.discreet { font-size: 94%; }
.appyList { line-height: 1.1em; margin: 0 0 0.5em 1.2em; padding: 0; }
.appyBullet { margin: 0; }
.appyPod { float:right; }
.appyNav { padding: 0.4em 0 0.4em 0; }
.appyFocus { color: #900101; }
.appyTabs { margin-bottom: 1em; }
.appyTabs li a { border-bottom:1px solid transparent; font-size: 90%; }
.appyTabs li a:visited { color: #578308; }
.appyTitle { padding-top: 0.5em; font-size: 110%; }
.appyLabel { font-weight: bold; padding-right: 0.4em; }
.appyRefEdit { line-height: 1.5em; }
.appyWorkflow {
text-align: center;
background-color: &dtml-globalBackgroundColor;;
}
.appyWorkflow { text-align: center; background-color: &dtml-globalBackgroundColor;;}
.appyPlusImg {
vertical-align: top;
@ -30,10 +23,9 @@
}
.appyPhase {
border-style: solid;
border-style: dashed;
border-width: thin;
text-align: center;
padding: 0 1em 0 1.3em;
padding: 0 0.1em 0 1em;
}
.appyState {
@ -75,14 +67,14 @@
background-color: #cde2a7;
background-image: url(&dtml-portal_url;/skyn/done.png);
background-repeat: no-repeat;
background-position: center left;
background-position: -1px 4px;
}
.stepCurrent {
background-color: #ffce7b;
background-color: #eef3f5;
background-image: url(&dtml-portal_url;/skyn/current.png);
background-repeat: no-repeat;
background-position: center left;
background-position: -1px 4px;
}
.stepFuture {
@ -104,13 +96,7 @@
}
/* With fields layout in columns, standard error frame is too large */
.error {
padding: 0.4em;
}
.odd {
background-color: white;
}
.odd { background-color: white; }
/* Table styles */
.no-style-table {
@ -125,18 +111,41 @@
margin: 0 !important;
}
/* Minor layout changes in fieldsets and tables */
fieldset {
margin: 0 0 0 0;
line-height: 1em;
border: 2px solid #8CACBB;
margin: 0.5em 0em 0.5em 0em;
padding: 0 0.7em 0.5em;
}
.fieldset {
line-height: 1em;
th {
font-style: italic;
font-weight: normal;
}
/* Group fieldsets */
.appyGroup {
border-width: 2px;
.section1 {
font-size: 120%;
margin: 0.45em 0em 0.1em 0;
padding: 0.3em 0em 0.2em 0.1em;
background-color: #eef3f5;
border-top: 1px solid #8CACBB;
border-bottom: 1px solid #8CACBB;
}
.section2 {
font-size: 110%;
font-style: italic;
margin: 0.45em 0em 0.1em 0;
border-bottom: 2px solid #8CACBB;
}
.section3 {
font-size: 100%;
font-style: italic;
margin: 0.45em 0em 0.1em 0;
background-color: #efeae8;
text-align: center;
color: grey;
}
.imageInput {
@ -169,6 +178,7 @@ fieldset {
padding-left: 0.3em;
padding-top: 0.3em;
padding-bottom: 0em;
border-top : 1px solid #8CACBB;
}
.vertical td {
@ -212,9 +222,6 @@ fieldset {
font-weight: normal;
text-transform: none;
}
.portletSep {
border-top: 1px dashed #8cacbb;
}
.portletSearch {
padding: 0 0 0 0.6em;
font-style: normal;
@ -225,13 +232,11 @@ fieldset {
font-weight: bold;
font-style: normal;
}
.portletGroupItem {
padding-left: 0.8em;
font-style: italic;
}
.portletCurrent {
font-weight: bold;
}
.portletSep { border-top: 1px dashed #8cacbb; }
.portletGroupItem { padding-left: 0.8em; font-style: italic; }
.portletPageItem { font-style: italic; }
.portletCurrent { font-weight: bold; }
div.appyGrey {
display: none;
position: absolute;

View file

@ -7,11 +7,9 @@ import Products.<!applicationName!>.config
from appy.gen.plone25.mixins.ToolMixin import ToolMixin
from Extensions.appyWrappers import AbstractWrapper, <!wrapperClass!>
predefinedSchema = Schema((<!predefinedFields!>
),)
schema = Schema((<!fields!>
),)
fullSchema = OrderedBaseFolderSchema.copy() + predefinedSchema.copy() + schema.copy()
fullSchema = OrderedBaseFolderSchema.copy() + schema.copy()
class <!toolName!>(UniqueObject, OrderedBaseFolder, ToolMixin):
'''Tool for <!applicationName!>.'''
@ -32,7 +30,6 @@ class <!toolName!>(UniqueObject, OrderedBaseFolder, ToolMixin):
typeDescMsgId = '<!toolName!>_edit_descr'
i18nDomain = '<!applicationName!>'
wrapperClass = <!wrapperClass!>
_at_rename_after_creation = True
schema = fullSchema
schema["id"].widget.visible = False
schema["title"].widget.visible = False
@ -47,6 +44,5 @@ class <!toolName!>(UniqueObject, OrderedBaseFolder, ToolMixin):
OrderedBaseFolder.__init__(self, '<!toolInstanceName!>')
self.setTitle('<!applicationName!>')
<!commonMethods!>
<!predefinedMethods!>
<!methods!>
registerType(<!toolName!>, '<!applicationName!>')

View file

@ -45,8 +45,9 @@ def initialize(context):
<!imports!>
# I need to do those imports here; else, types and add permissions will not
# be registered.
classes = [<!classes!>]
ZopeInstaller(context, PROJECTNAME,
<!applicationName!>Tool.<!applicationName!>Tool,
DEFAULT_ADD_CONTENT_PERMISSION, ADD_CONTENT_PERMISSIONS,
logger, globals()).install()
logger, globals(), classes).install()
# ------------------------------------------------------------------------------

View file

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

@ -1,11 +1,8 @@
<!codeHeader!>
import os, os.path, sys
try: # New CMF
import os, os.path, sys, copy
import appy.gen
from Products.CMFCore.permissions import setDefaultRoles
except ImportError: # Old CMF
from Products.CMFCore.CMFCorePermissions import setDefaultRoles
import Extensions.appyWrappers
import Extensions.appyWrappers as wraps
<!imports!>
# The following imports are here for allowing mixin classes to access those
@ -17,7 +14,6 @@ from OFS.Image import File
from DateTime import DateTime
from Products.CMFCore.utils import getToolByName
from Products.CMFPlone.PloneBatch import Batch
from Products.Archetypes.utils import DisplayList
import logging
logger = logging.getLogger('<!applicationName!>')
@ -32,9 +28,7 @@ setDefaultRoles(DEFAULT_ADD_CONTENT_PERMISSION, tuple(defaultAddRoles))
product_globals = globals()
applicationRoles = [<!roles!>]
rootClasses = [<!rootClasses!>]
referers = {
<!referers!>
}
# In the following dict, we keep one instance for every Appy workflow defined
# in the application. Those prototypical instances will be used for executing
# user-defined actions and transitions. For each instance, we add a special
@ -44,6 +38,9 @@ workflowInstances = {}
<!workflowInstancesInit!>
# In the following dict, we store, for every Appy class, the ordered list of
# attributes (included inherited attributes).
# appy types (included inherited ones).
attributes = {<!attributes!>}
# In the followinf dict, we store, for every Appy class, a dict of appy types
# keyed by their names.
attributesDict = {<!attributesDict!>}
# ------------------------------------------------------------------------------

View file

@ -1,20 +1,5 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" i18n:domain="plone">
<body>
<tal:message i18n:domain="plone" metal:define-macro="portal_message">
<tal:comment replace="nothing">Single message from portal_status_message request key</tal:comment>
<div tal:define="msg request/portal_status_message | nothing"
tal:condition="msg" class="portalMessage" tal:content="msg" i18n:translate=""></div>
<tal:comment replace="nothing">Messages added via plone_utils</tal:comment>
<tal:messages define="messages putils/showPortalMessages" condition="messages">
<tal:msgs define="type_css_map python: {'info':'portalMessage', 'warn':'portalWarningMessage',
'stop':'portalStopMessage'};"
repeat="msg messages">
<div tal:define="mtype msg/type | nothing;"
tal:attributes="class python:mtype and type_css_map[mtype] or 'info';"
tal:content="structure msg/message | nothing" i18n:translate=""></div>
</tal:msgs>
</tal:messages>
</tal:message>
<tal:message metal:define-macro="portal_message"></tal:message>
</body>
</html>

View file

@ -33,11 +33,4 @@ def updateRolesForPermission(permission, roles, obj):
existingRoles = perm.getRoles()
allRoles = set(existingRoles).union(roles)
obj.manage_permission(permission, tuple(allRoles), acquire=0)
# ------------------------------------------------------------------------------
from appy.gen.utils import AppyRequest
def getAppyRequest(zopeRequest, obj=None):
'''This method creates a nice (Appy) object representation of a
dictionary-like Zope REQUEST object.'''
return AppyRequest(zopeRequest, obj)
# ------------------------------------------------------------------------------

View file

@ -1,6 +1,8 @@
# ------------------------------------------------------------------------------
class FlavourWrapper:
from appy.gen.plone25.wrappers import AbstractWrapper
# ------------------------------------------------------------------------------
class FlavourWrapper(AbstractWrapper):
def onEdit(self, created):
if created:
nbOfFlavours = len(self.tool.flavours)
@ -8,8 +10,7 @@ class FlavourWrapper:
self.number = nbOfFlavours
self.o.registerPortalTypes()
# Call the custom flavour "onEdit" method if it exists
customFlavour = self.__class__.__bases__[1]
if customFlavour.__name__ != 'Flavour':
if len(self.__class__.__bases__) > 1:
# There is a custom flavour
if customFlavour.__dict__.has_key('onEdit'):
customFlavour.__dict__['onEdit'](self, created)

View file

@ -1,3 +1,6 @@
# ------------------------------------------------------------------------------
class PodTemplateWrapper: pass
from appy.gen.plone25.wrappers import AbstractWrapper
# ------------------------------------------------------------------------------
class PodTemplateWrapper(AbstractWrapper): pass
# ------------------------------------------------------------------------------

View file

@ -1,5 +1,32 @@
# ------------------------------------------------------------------------------
class ToolWrapper:
from appy.gen.plone25.wrappers import AbstractWrapper
# ------------------------------------------------------------------------------
_PY = 'Please specify a file corresponding to a Python interpreter ' \
'(ie "/usr/bin/python").'
FILE_NOT_FOUND = 'Path "%s" was not found.'
VALUE_NOT_FILE = 'Path "%s" is not a file. ' + _PY
NO_PYTHON = "Name '%s' does not starts with 'python'. " + _PY
NOT_UNO_ENABLED_PYTHON = '"%s" is not a UNO-enabled Python interpreter. ' \
'To check if a Python interpreter is UNO-enabled, ' \
'launch it and type "import uno". If you have no ' \
'ImportError exception it is ok.'
# ------------------------------------------------------------------------------
class ToolWrapper(AbstractWrapper):
def validPythonWithUno(self, value):
'''This method represents the validator for field unoEnabledPython.'''
if value:
if not os.path.exists(value):
return FILE_NOT_FOUND % value
if not os.path.isfile(value):
return VALUE_NOT_FILE % value
if not os.path.basename(value).startswith('python'):
return NO_PYTHON % value
if os.system('%s -c "import uno"' % value):
return NOT_UNO_ENABLED_PYTHON % value
return None
def getInitiator(self):
'''Retrieves the object that triggered the creation of the object
being currently created (if any).'''

View file

@ -5,7 +5,7 @@
import os, os.path, time, mimetypes, random
import appy.pod
from appy.gen import Search
from appy.gen.utils import sequenceTypes
from appy.gen.utils import sequenceTypes, FileWrapper
from appy.shared.utils import getOsTempFolder, executeCommand, normalizeString
from appy.shared.xml_parser import XmlMarshaller
@ -32,9 +32,9 @@ class AbstractWrapper:
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:])
setattr(self.o, name, v)
elif isinstance(v, FileWrapper):
setattr(self, name, v._atFile)
setattr(self.o, name, v._atFile)
elif isinstance(v, basestring):
f = file(v)
fileName = os.path.basename(v)
@ -42,7 +42,7 @@ class AbstractWrapper:
ploneFile = ploneFileClass(fileId, fileName, f)
ploneFile.filename = fileName
ploneFile.content_type = mimetypes.guess_type(fileName)[0]
setattr(self, name, ploneFile)
setattr(self.o, name, ploneFile)
f.close()
elif type(v) in sequenceTypes:
# It should be a 2-tuple or 3-tuple
@ -61,15 +61,20 @@ class AbstractWrapper:
if not mimeType:
mimeType = mimetypes.guess_type(fileName)[0]
ploneFile.content_type = mimeType
setattr(self, name, ploneFile)
setattr(self.o, name, ploneFile)
def __setattr__(self, name, v):
if name == 'title':
self.o.setTitle(v)
return
appyType = self.o.getAppyType(name)
if not appyType and (name != 'title'):
if not appyType:
raise 'Attribute "%s" does not exist.' % name
if appyType and (appyType['type'] == 'File'):
if appyType.type == 'File':
self._set_file_attribute(name, v)
elif appyType.type == 'Ref':
raise "Use methods 'link' or 'create' to modify references."
else:
exec "self.o.set%s%s(v)" % (name[0].upper(), name[1:])
setattr(self.o, name, v)
def __repr__(self):
return '<%s wrapper at %s>' % (self.klass.__name__, id(self))
def __cmp__(self, other):
@ -94,17 +99,18 @@ class AbstractWrapper:
appName = self.o.getProductConfig().PROJECTNAME
return self.o.utranslate(self.o.getWorkflowLabel(), domain=appName)
stateLabel = property(get_stateLabel)
def get_klass(self): return self.__class__.__bases__[1]
def get_klass(self): return self.__class__.__bases__[-1]
klass = property(get_klass)
def get_url(self): return self.o.absolute_url()+'/skyn/view'
def get_url(self): return self.o.absolute_url()
url = property(get_url)
def get_history(self):
key = self.o.workflow_history.keys()[0]
return self.o.workflow_history[key]
history = property(get_history)
def get_user(self):
return self.o.portal_membership.getAuthenticatedMember()
def get_user(self): return self.o.portal_membership.getAuthenticatedMember()
user = property(get_user)
def get_fields(self): return self.o.getAllAppyTypes()
fields = property(get_fields)
def link(self, fieldName, obj):
'''This method links p_obj to this one through reference field
@ -178,7 +184,6 @@ class AbstractWrapper:
appyObj = ploneObj.appy()
# Set object attributes
for attrName, attrValue in kwargs.iteritems():
setterName = 'set%s%s' % (attrName[0].upper(), attrName[1:])
if isinstance(attrValue, AbstractWrapper):
try:
refAppyType = getattr(appyObj.__class__.__bases__[-1],
@ -187,7 +192,7 @@ class AbstractWrapper:
except AttributeError, ae:
pass
else:
getattr(ploneObj, setterName)(attrValue)
setattr(appyObj, attrName, attrValue)
if isField:
# Link the object to this one
self.link(fieldName, ploneObj)
@ -375,85 +380,4 @@ class AbstractWrapper:
p_data must be a dictionary whose keys are field names (strings) and
whose values are the previous field values.'''
self.o.addDataChange(data, labels=False)
# ------------------------------------------------------------------------------
CONVERSION_ERROR = 'An error occurred while executing command "%s". %s'
class FileWrapper:
'''When you get, from an appy object, the value of a File attribute, you
get an instance of this class.'''
def __init__(self, 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, format=None, tool=None):
'''Writes the file on disk. If p_filePath is specified, it is the
path name where the file will be dumped; folders mentioned in it
must exist. If not, the file will be dumped in the OS temp folder.
The absolute path name of the dumped file is returned.
If an error occurs, the method returns None. If p_format is
specified, OpenOffice will be called for converting the dumped file
to the desired format. In this case, p_tool, a Appy tool, must be
provided. Indeed, any Appy tool contains parameters for contacting
OpenOffice in server mode.'''
if not filePath:
filePath = '%s/file%f.%s' % (getOsTempFolder(), time.time(),
normalizeString(self.name))
f = file(filePath, 'w')
if self.content.__class__.__name__ == 'Pdata':
# The file content is splitted in several chunks.
f.write(self.content.data)
nextPart = self.content.next
while nextPart:
f.write(nextPart.data)
nextPart = nextPart.next
else:
# Only one chunk
f.write(self.content)
f.close()
if format:
if not tool: return
# Convert the dumped file using OpenOffice
convScript = '%s/converter.py' % os.path.dirname(appy.pod.__file__)
cmd = '%s %s "%s" %s -p%d' % (tool.unoEnabledPython, convScript,
filePath, format, tool.openOfficePort)
errorMessage = executeCommand(cmd)
# Even if we have an "error" message, it could be a simple warning.
# So we will continue here and, as a subsequent check for knowing if
# an error occurred or not, we will test the existence of the
# converted file (see below).
os.remove(filePath)
# Return the name of the converted file.
baseName, ext = os.path.splitext(filePath)
if (ext == '.%s' % format):
filePath = '%s.res.%s' % (baseName, format)
else:
filePath = '%s.%s' % (baseName, format)
if not os.path.exists(filePath):
tool.log(CONVERSION_ERROR % (cmd, errorMessage), type='error')
return
return filePath
# ------------------------------------------------------------------------------

View file

@ -4,35 +4,7 @@
# ------------------------------------------------------------------------------
from appy.gen.plone25.installer import PloneInstaller as Plone25Installer
class ZCTextIndexInfo:
'''Silly class used for storing information about a ZCTextIndex.'''
lexicon_id = "plone_lexicon"
index_type = 'Okapi BM25 Rank'
class PloneInstaller(Plone25Installer):
'''This Plone installer runs every time the generated Plone product is
installed or uninstalled (in the Plone configuration interface).'''
@staticmethod
def updateIndexes(ploneSite, indexInfo, logger):
'''This method creates or updates, in a p_ploneSite, definitions of
indexes in its portal_catalog, based on index-related information
given in p_indexInfo. p_indexInfo is a dictionary of the form
{s_indexName:s_indexType}. Here are some examples of index types:
"FieldIndex", "ZCTextIndex", "DateIndex".'''
catalog = ploneSite.portal_catalog
indexNames = catalog.indexes()
for indexName, indexType in indexInfo.iteritems():
if indexName not in indexNames:
# We need to create this index
if indexType != 'ZCTextIndex':
catalog.addIndex(indexName, indexType)
else:
catalog.addIndex(indexName,indexType,extra=ZCTextIndexInfo)
logger.info('Creating index "%s" of type "%s"...' % \
(indexName, indexType))
# Indexing database content based on this index.
catalog.reindexIndex(indexName, ploneSite.REQUEST)
logger.info('Done.')
# TODO: if the index already exists but has not the same type, we
# re-create it with the same type and we reindex it.
installed or uninstalled (in the Plone configuration panel).'''
# ------------------------------------------------------------------------------

View file

@ -69,6 +69,7 @@ class PoMessage:
SEARCH_AND = 'and'
WORKFLOW_COMMENT = 'Optional comment'
WORKFLOW_STATE = 'state'
APPY_TITLE = 'Title'
DATA_CHANGE = 'Data change'
MODIFIED_FIELD = 'Modified field'
PREVIOUS_VALUE = 'Previous value'
@ -78,12 +79,13 @@ class PoMessage:
CHOOSE_A_DOC = '[ Documents ]'
MIN_REF_VIOLATED = 'You must choose more elements here.'
MAX_REF_VIOLATED = 'Too much elements are selected here.'
BAD_INT = 'An integer value is expected; do not enter any space.'
BAD_LONG = 'An integer value is expected; do not enter any space.'
BAD_FLOAT = 'A floating-point number is expected; use the dot as decimal ' \
'separator, not a comma; do not enter any space.'
BAD_EMAIL = 'Please enter a valid email.'
BAD_URL = 'Please enter a valid URL.'
BAD_ALPHANUMERIC = 'Please enter a valid alphanumeric value.'
BAD_SELECT_VALUE = 'The value is not among possible values for this field.'
ACTION_OK = 'The action has been successfully executed.'
ACTION_KO = 'A problem occurred while executing the action.'
FRONT_PAGE_TEXT = 'Welcome to this Appy-powered Plone site.'
@ -103,11 +105,16 @@ class PoMessage:
CONFIRM = 'Are you sure ?'
YES = 'Yes'
NO = 'No'
FIELD_REQUIRED = 'Please fill this field.'
FILE_REQUIRED = 'Please select a file.'
IMAGE_REQUIRED = 'The uploaded file must be an image.'
def __init__(self, id, msg, default, fuzzy=False, comments=[]):
def __init__(self, id, msg, default, fuzzy=False, comments=[],
niceDefault=False):
self.id = id
self.msg = msg
self.default = default
if niceDefault: self.produceNiceDefault()
self.fuzzy = fuzzy # True if the default value has changed in the pot
# file: the msg in the po file needs to be translated again.
self.comments = comments

View file

@ -1,6 +1,6 @@
# ------------------------------------------------------------------------------
import re
import re, os, os.path, time
from appy.shared.utils import getOsTempFolder, normalizeString
sequenceTypes = (list, tuple)
# Classes used by edit/view templates for accessing information ----------------
@ -8,111 +8,100 @@ class Descr:
'''Abstract class for description classes.'''
def get(self): return self.__dict__
class FieldDescr(Descr):
def __init__(self, atField, appyType, fieldRel):
# The corresponding Archetypes field (may be None in the case of
# backward references)
self.atField = atField
# The corresponding Appy type
self.appyType = appyType
# The field relationship, needed when the field description is a
# backward reference.
self.fieldRel = fieldRel
# Can we sort this field ?
at = self.appyType
self.sortable = False
if not fieldRel and ((self.atField.getName() == 'title') or \
(at['indexed'])):
self.sortable = True
# Can we filter this field?
self.filterable = False
if not fieldRel and at['indexed'] and (at['type'] == 'String') and \
(at['format'] == 0) and not at['isSelect']:
self.filterable = True
if fieldRel:
self.widgetType = 'backField'
self.group = appyType['backd']['group']
self.show = appyType['backd']['show']
self.page = appyType['backd']['page']
else:
self.widgetType = 'field'
self.group = appyType['group']
self.show = appyType['show']
self.page = appyType['page']
fieldName = self.atField.getName()
class GroupDescr(Descr):
def __init__(self, name, cols, page):
self.name = name
self.cols = cols # The nb of columns for placing fields into the group
self.rows = None # The number of rows
def __init__(self, group, page, metaType):
'''Creates the data structure manipulated in ZPTs from p_group, the
Group instance used in the field definition.'''
self.type = 'group'
# All p_group attributes become self attributes.
for name, value in group.__dict__.iteritems():
if not name.startswith('_'):
setattr(self, name, value)
self.columnsWidths = [col.width for col in group.columns]
self.columnsAligns = [col.align for col in group.columns]
# Names of i18n labels
self.labelId = '%s_group_%s' % (metaType, self.name)
self.descrId = self.labelId + '_descr'
self.helpId = self.labelId + '_help'
# The name of the page where the group lies
self.page = page
self.fields = []
self.widgetType = 'group'
# The widgets belonging to the group that the current user may see.
# They will be stored by m_addWidget below as a list of lists because
# they will be rendered as a table.
self.widgets = [[]]
def computeRows(groupDict):
'''Computes self.rows. But because at this time the object has already
been converted to a dict (for being maniputated within ZPTs, this
method is a static method that takes the dict as arg.'''
groupDict['rows'] = len(groupDict['fields']) / groupDict['cols']
if len(groupDict['fields']) % groupDict['cols']:
groupDict['rows'] += 1
computeRows = staticmethod(computeRows)
@staticmethod
def addWidget(groupDict, newWidget):
'''Adds p_newWidget into p_groupDict['widgets']. We try first to add
p_newWidget into the last widget row. If it is not possible, we
create a new row.
def getGroupInfo(groupName):
'''In the group name, the user may optionally specify at the end the
number of columns for placing fields into the group. This method
returns the real group name and the number of columns.'''
res = groupName.rsplit('_', 1)
if len(res) == 1:
res.append(1) # Append the default numer of columns
This method is a static method taking p_groupDict as first param
instead of being an instance method because at this time the object
has already been converted to a dict (for being maniputated within
ZPTs).'''
# Get the last row
widgetRow = groupDict['widgets'][-1]
numberOfColumns = len(groupDict['columnsWidths'])
# Computes the number of columns already filled by widgetRow
rowColumns = 0
for widget in widgetRow: rowColumns += widget['colspan']
freeColumns = numberOfColumns - rowColumns
if freeColumns >= newWidget['colspan']:
# We can add the widget in the last row.
widgetRow.append(newWidget)
else:
try:
res[1] = int(res[1])
except ValueError:
res[1] = 1
return res
getGroupInfo = staticmethod(getGroupInfo)
if freeColumns:
# Terminate the current row by appending empty cells
for i in range(freeColumns): widgetRow.append('')
# Create a new row
newRow = [newWidget]
groupDict['widgets'].append(newRow)
class PageDescr(Descr):
def getPageInfo(pageOrName, pageKlass):
@staticmethod
def getPageInfo(pageOrName):
'''pageOrName can be:
- a string containing the name of the page
- a string containing <pageName>_<phaseName>
- a appy.gen.Page instance for a more detailed page description.
This method returns a normalized tuple containing page-related
information.'''
if isinstance(pageOrName, pageKlass):
res = [pageOrName.name, pageOrName.phase, pageOrName.show]
else:
if isinstance(pageOrName, basestring):
res = pageOrName.rsplit('_', 1)
if len(res) == 1:
res.append('main')
res.append(True)
else:
res = [pageOrName.name, pageOrName.phase, pageOrName.show]
return res
getPageInfo = staticmethod(getPageInfo)
class PhaseDescr(Descr):
def __init__(self, name, states, forPlone, ploneObj):
def __init__(self, name, states, obj):
self.name = name
self.states = states
self.forPlone = forPlone
self.ploneObj = ploneObj
self.obj = obj
self.phaseStatus = None
self.pages = [] # The list of pages in this phase
self.totalNbOfPhases = None
# The following attributes allows to browse, from a given page, to the
# last page of the previous phase and to the first page of the following
# phase if allowed by phase state.
self.previousPhase = None
self.nextPhase = None
def addPage(self, appyType, obj):
toAdd = appyType['page']
if (toAdd == 'main') and self.forPlone:
toAdd = 'default'
toAdd = appyType.page
if (toAdd not in self.pages) and \
obj._appy_showPage(appyType['page'], appyType['pageShow']):
obj._appy_showPage(appyType.page, appyType.pageShow):
self.pages.append(toAdd)
def computeStatus(self):
def computeStatus(self, allPhases):
'''Compute status of whole phase based on individual status of states
in this phase. If this phase includes no state, the concept of phase
is simply used as a tab, and its status depends on the page currently
shown.'''
shown. This method also fills fields "previousPhase" and "nextPhase"
if relevant, based on list of p_allPhases.'''
res = 'Current'
if self.states:
# Compute status base on states
@ -124,17 +113,19 @@ class PhaseDescr(Descr):
break
else:
# Compute status based on current page
rq = self.ploneObj.REQUEST
if rq.has_key('fieldset'):
pageName = rq['fieldset']
if not self.forPlone and (pageName == 'default'):
pageName = 'main'
else:
pageName = rq.get('pageName', 'main')
if pageName in self.pages:
page = self.obj.REQUEST.get('page', 'main')
if page in self.pages:
res = 'Current'
else:
res = 'Deselected'
# Identify previous and next phases
for phaseInfo in allPhases:
if phaseInfo['name'] == self.name:
i = allPhases.index(phaseInfo)
if i > 0:
self.previousPhase = allPhases[i-1]
if i < (len(allPhases)-1):
self.nextPhase = allPhases[i+1]
self.phaseStatus = res
class StateDescr(Descr):
@ -159,32 +150,7 @@ def produceNiceMessage(msg):
return res
# ------------------------------------------------------------------------------
class ValidationErrors: pass
class AppyRequest:
def __init__(self, zopeRequest, appyObj=None):
self.zopeRequest = zopeRequest
self.appyObj = appyObj
def __str__(self): return '<AppyRequest object>'
def __repr__(self): return '<AppyRequest object>'
def __getattr__(self, attr):
res = None
if self.appyObj:
# I can retrieve type information from the ploneObj.
appyType = self.appyObj.o.getAppyType(attr)
if appyType['type'] == 'Ref':
res = self.zopeRequest.get('appy_ref_%s' % attr, None)
else:
res = self.zopeRequest.get(attr, None)
if appyType['pythonType']:
try:
exec 'res = %s' % res # bool('False') gives True, so we
# can't write: res = appyType['pythonType'](res)
except SyntaxError, se:
# Can happen when for example, an Integer value is empty
res = None
else:
res = self.zopeRequest.get(attr, None)
return res
class AppyObject: pass
# ------------------------------------------------------------------------------
class SomeObjects:
@ -261,4 +227,85 @@ class FakeBrain:
def pretty_title_or_id(self): return self.Title
def getObject(self, REQUEST=None): return self
def getRID(self): return self.url
# ------------------------------------------------------------------------------
CONVERSION_ERROR = 'An error occurred while executing command "%s". %s'
class FileWrapper:
'''When you get, from an appy object, the value of a File attribute, you
get an instance of this class.'''
def __init__(self, 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, format=None, tool=None):
'''Writes the file on disk. If p_filePath is specified, it is the
path name where the file will be dumped; folders mentioned in it
must exist. If not, the file will be dumped in the OS temp folder.
The absolute path name of the dumped file is returned.
If an error occurs, the method returns None. If p_format is
specified, OpenOffice will be called for converting the dumped file
to the desired format. In this case, p_tool, a Appy tool, must be
provided. Indeed, any Appy tool contains parameters for contacting
OpenOffice in server mode.'''
if not filePath:
filePath = '%s/file%f.%s' % (getOsTempFolder(), time.time(),
normalizeString(self.name))
f = file(filePath, 'w')
if self.content.__class__.__name__ == 'Pdata':
# The file content is splitted in several chunks.
f.write(self.content.data)
nextPart = self.content.next
while nextPart:
f.write(nextPart.data)
nextPart = nextPart.next
else:
# Only one chunk
f.write(self.content)
f.close()
if format:
if not tool: return
# Convert the dumped file using OpenOffice
convScript = '%s/converter.py' % os.path.dirname(appy.pod.__file__)
cmd = '%s %s "%s" %s -p%d' % (tool.unoEnabledPython, convScript,
filePath, format, tool.openOfficePort)
errorMessage = executeCommand(cmd)
# Even if we have an "error" message, it could be a simple warning.
# So we will continue here and, as a subsequent check for knowing if
# an error occurred or not, we will test the existence of the
# converted file (see below).
os.remove(filePath)
# Return the name of the converted file.
baseName, ext = os.path.splitext(filePath)
if (ext == '.%s' % format):
filePath = '%s.res.%s' % (baseName, format)
else:
filePath = '%s.%s' % (baseName, format)
if not os.path.exists(filePath):
tool.log(CONVERSION_ERROR % (cmd, errorMessage), type='error')
return
return filePath
# ------------------------------------------------------------------------------

View file

@ -23,6 +23,7 @@ from appy.pod import PodError, XML_SPECIAL_CHARS
from appy.pod.elements import *
from appy.pod.actions import IfAction, ElseAction, ForAction, VariableAction, \
NullAction
from appy.shared import xmlPrologue
# ------------------------------------------------------------------------------
class ParsingError(Exception): pass
@ -158,7 +159,7 @@ class FileBuffer(Buffer):
Buffer.__init__(self, env, None)
self.result = result
self.content = file(result, 'w')
self.content.write('<?xml version="1.0" encoding="UTF-8"?>')
self.content.write(xmlPrologue)
def getLength(self): return 0
# getLength is used to manage insertions into sub-buffers. But in the case
# of a FileBuffer, we will only have 1 sub-buffer at a time, and we don't

View file

@ -45,6 +45,10 @@ class DocImporter:
self.svgNs = ns[OdfEnvironment.NS_SVG]
self.tempFolder = tempFolder
self.importFolder = self.getImportFolder()
# If the importer generates one or several images, we will retain their
# names here, because we will need to declare them in
# META-INF/manifest.xml
self.fileNames = []
if self.at:
# Check that the file exists
if not os.path.isfile(self.at):
@ -142,6 +146,7 @@ class PdfImporter(DocImporter):
self.tempFolder, self.ns)
imgImporter.setAnchor('paragraph')
self.res += imgImporter.run()
self.fileNames += imgImporter.fileNames
os.remove(nextImage)
else:
noMoreImages = True
@ -214,6 +219,7 @@ class ImageImporter(DocImporter):
# Compute path to image
i = self.importPath.rfind('/Pictures/')
imagePath = self.importPath[i+1:]
self.fileNames.append(imagePath)
# Compute image size
width, height = getSize(self.importPath, self.format)
if width != None:

View file

@ -17,7 +17,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA.
# ------------------------------------------------------------------------------
import zipfile, shutil, xml.sax, os, os.path, re
import zipfile, shutil, xml.sax, os, os.path, re, mimetypes, time
from UserDict import UserDict
@ -134,33 +134,45 @@ class Renderer:
self.stylesManager = None # Manages the styles defined into the ODT
# template
self.tempFolder = None
self.curdir = os.getcwd()
self.env = None
self.pyPath = pythonWithUnoPath
self.ooPort = ooPort
self.forceOoCall = forceOoCall
self.finalizeFunction = finalizeFunction
# Retain potential files or images that will be included through
# "do ... from document" statements: we will need to declare them in
# META-INF/manifest.xml.
self.fileNames = []
self.prepareFolders()
# Unzip template
self.unzipFolder = os.path.join(self.tempFolder, 'unzip')
os.mkdir(self.unzipFolder)
for zippedFile in self.templateZip.namelist():
# Before writing the zippedFile into self.unzipFolder, create the
# intermediary subfolder(s) if needed.
fileName = None
if zippedFile.endswith('/') or zippedFile.endswith(os.sep):
# This is an empty folder. Create it nevertheless.
os.makedirs(os.path.join(self.unzipFolder, zippedFile))
else:
fileName = os.path.basename(zippedFile)
folderName = os.path.dirname(zippedFile)
# Create folder if needed
fullFolderName = self.unzipFolder
if folderName:
fullFolderName = os.path.join(fullFolderName, folderName)
if not os.path.exists(fullFolderName):
os.makedirs(fullFolderName)
# Unzip file
# Unzip the file in self.unzipFolder
if fileName:
fullFileName = os.path.join(fullFolderName, fileName)
f = open(fullFileName, 'wb')
fileContent = self.templateZip.read(zippedFile)
if fileName == 'content.xml':
if (fileName == 'content.xml') and not folderName:
# content.xml files may reside in subfolders.
# We modify only the one in the root folder.
self.contentXml = fileContent
elif fileName == 'styles.xml':
elif (fileName == 'styles.xml') and not folderName:
# Same remark as above.
self.stylesManager = StylesManager(fileContent)
self.stylesXml = fileContent
f.write(fileContent)
@ -268,7 +280,10 @@ class Renderer:
imp = importer(content, at, format, self.tempFolder, ns)
if isImage:
imp.setAnchor(anchor)
return imp.run()
res = imp.run()
if imp.fileNames:
self.fileNames += imp.fileNames
return res
def prepareFolders(self):
# Check if I can write the result
@ -293,6 +308,27 @@ class Renderer:
except OSError, oe:
raise PodError(CANT_WRITE_TEMP_FOLDER % (self.result, oe))
def patchManifest(self):
'''Declares, in META-INF/manifest.xml, images or files included via the
"do... from document" statements if any.'''
if self.fileNames:
j = os.path.join
toInsert = ''
for fileName in self.fileNames:
mimeType = mimetypes.guess_type(fileName)[0]
toInsert += ' <manifest:file-entry manifest:media-type="%s" ' \
'manifest:full-path="%s"/>\n' % (mimeType, fileName)
manifestName = j(self.unzipFolder, j('META-INF', 'manifest.xml'))
f = file(manifestName)
manifestContent = f.read()
hook = '</manifest:manifest>'
manifestContent = manifestContent.replace(hook, toInsert+hook)
f.close()
# Write the new manifest content
f = file(manifestName, 'w')
f.write(manifestContent)
f.close()
# Public interface
def run(self):
'''Renders the result.'''
@ -303,6 +339,8 @@ class Renderer:
self.currentParser = self.stylesParser
# Create the resulting styles.xml
self.currentParser.parse(self.stylesXml)
# Patch META-INF/manifest.xml
self.patchManifest()
# Re-zip the result
self.finalize()
@ -397,11 +435,18 @@ class Renderer:
resultOdt = zipfile.ZipFile(resultOdtName,'w', zipfile.ZIP_DEFLATED)
except RuntimeError:
resultOdt = zipfile.ZipFile(resultOdtName,'w')
os.chdir(self.unzipFolder)
for dir, dirnames, filenames in os.walk('.'):
for dir, dirnames, filenames in os.walk(self.unzipFolder):
for f in filenames:
resultOdt.write(os.path.join(dir, f)[2:])
# [2:] is there to avoid havin './' in the path in the zip file.
folderName = dir[len(self.unzipFolder)+1:]
resultOdt.write(os.path.join(dir, f),
os.path.join(folderName, f))
if not dirnames and not filenames:
# This is an empty leaf folder. We must create an entry in the
# zip for him
folderName = dir[len(self.unzipFolder):]
zInfo = zipfile.ZipInfo("%s/" % folderName,time.localtime()[:6])
zInfo.external_attr = 48
resultOdt.writestr(zInfo, '')
resultOdt.close()
resultType = os.path.splitext(self.result)[1]
try:
@ -431,6 +476,5 @@ class Renderer:
raise PodError(CONVERT_ERROR % output)
os.rename(resultName, self.result)
finally:
os.chdir(self.curdir)
FolderDeleter.delete(self.tempFolder)
# ------------------------------------------------------------------------------

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,5 @@
johnScore = 25
markScore = 53
wilsonScore = 12
meghuScore = 59

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

Binary file not shown.

Binary file not shown.

View file

@ -18,7 +18,7 @@ mimeTypesExts = {
'image/jpeg' : 'jpg',
'image/gif' : 'gif'
}
xmlPrologue = '<?xml version="1.0" encoding="utf-8"?>'
xmlPrologue = '<?xml version="1.0" encoding="utf-8"?>\n'
# ------------------------------------------------------------------------------
class UnmarshalledObject:

58
shared/odf.py Normal file
View file

@ -0,0 +1,58 @@
'''This module contains some useful classes for constructing ODF documents
programmatically.'''
# ------------------------------------------------------------------------------
class OdtTable:
'''This class allows to construct an ODT table programmatically.'''
# Some namespace definitions
tns = 'table:'
txns = 'text:'
def __init__(self, tableName, paraStyle, cellStyle,
paraHeaderStyle, cellHeaderStyle, nbOfCols):
self.tableName = tableName
self.paraStyle = paraStyle
self.cellStyle = cellStyle
self.paraHeaderStyle = paraHeaderStyle
self.cellHeaderStyle = cellHeaderStyle
self.nbOfCols = nbOfCols
self.res = ''
def dumpCell(self, content, span=1, header=False):
if header:
paraStyleName = self.paraHeaderStyle
cellStyleName = self.cellHeaderStyle
else:
paraStyleName = self.paraStyle
cellStyleName = self.cellStyle
self.res += '<%stable-cell %sstyle-name="%s" ' \
'%snumber-columns-spanned="%d">' % \
(self.tns, self.tns, cellStyleName, self.tns, span)
self.res += '<%sp %sstyle-name="%s">%s</%sp>' % \
(self.txns, self.txns, paraStyleName, content, self.txns)
self.res += '</%stable-cell>' % self.tns
def startRow(self):
self.res += '<%stable-row>' % self.tns
def endRow(self):
self.res += '</%stable-row>' % self.tns
def startTable(self):
self.res += '<%stable %sname="AnalysisTable">' % (self.tns, self.tns)
self.res += '<%stable-column %snumber-columns-repeated="%d"/>' % \
(self.tns, self.tns, self.nbOfCols)
def endTable(self):
self.res += '</%stable>' % self.tns
def dumpFloat(self, number):
return str(round(number, 2))
def get(self):
'''Returns the whole table.'''
self.startTable()
self.getRows()
self.endTable()
return self.res.decode('utf-8')
# ------------------------------------------------------------------------------

View file

@ -526,8 +526,7 @@ class XmlMarshaller:
mustDump = True
if mustDump:
self.dumpField(res, fieldName, fieldValue)
elif objectType in ('archetype', 'appy'):
fields = instance.schema.fields()
elif objectType == 'archetype':
for field in instance.schema.fields():
# Dump only needed fields
mustDump = False
@ -550,7 +549,21 @@ class XmlMarshaller:
fieldType = 'ref'
self.dumpField(res, field.getName(),field.get(instance),
fieldType=fieldType)
if objectType == 'appy':
elif objectType == 'appy':
for field in instance.getAllAppyTypes():
# Dump only needed fields
if field.name in self.fieldsToExclude: continue
if (field.type == 'Ref') and field.isBack: continue
if (type(self.fieldsToMarshall) in self.sequenceTypes) \
and (field.name not in self.fieldsToMarshall): continue
# Determine field type
fieldType = 'basic'
if field.type == 'File':
fieldType = 'file'
elif field.type == 'Ref':
fieldType = 'ref'
self.dumpField(res, field.name,field.getValue(instance),
fieldType=fieldType)
# Dump the object history.
res.write('<history type="list">')
wfInfo = instance.portal_workflow.getWorkflowsFor(instance)