Added a new system for layouting production-ready forms without any HTML coding, many performance improvements and more independence towards Archetypes.
30
bin/job.py
|
@ -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()
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -1 +1 @@
|
|||
0.5.5
|
||||
0.6.0
|
||||
|
|
1027
gen/__init__.py
|
@ -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
|
||||
|
|
|
@ -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
|
@ -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!'}
|
||||
# ------------------------------------------------------------------------------
|
|
@ -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))
|
||||
|
|
|
@ -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 ?
|
||||
|
|
|
@ -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__,
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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'
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -3,5 +3,5 @@ from appy.gen.plone25.mixins import AbstractMixin
|
|||
|
||||
# ------------------------------------------------------------------------------
|
||||
class PodTemplateMixin(AbstractMixin):
|
||||
_appy_meta_type = 'podtemplate'
|
||||
_appy_meta_type = 'PodTemplate'
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -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'
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
After Width: | Height: | Size: 4.4 KiB |
|
@ -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)()
|
||||
|
|
|
@ -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 > 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 < 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>
|
||||
|
|
BIN
gen/plone25/skin/editBig.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
gen/plone25/skin/help.jpg
Normal file
After Width: | Height: | Size: 415 B |
BIN
gen/plone25/skin/help.png
Normal file
After Width: | Height: | Size: 735 B |
|
@ -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;
|
||||
|
|
|
@ -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)<=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>
|
||||
</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)>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"/>
|
||||
<span class="fieldRequired" tal:condition="python: appyType['multiplicity'][0] > 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] > 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 > 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', '<br>')"/>
|
||||
<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] > 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: "¶m1=blabla¶m2=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 + '¯o=' + 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']>1">−
|
||||
<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"/>
|
||||
<span i18n:translate="label_history" i18n:domain="plone" class="appyHistory"></span>
|
||||
</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"/>
|
||||
—
|
||||
</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)>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)>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)>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
After Width: | Height: | Size: 951 B |
728
gen/plone25/skin/page.pt
Normal 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: "¶m1=blabla¶m2=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 + '¯o=' + 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)<=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>
|
||||
</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)>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] > 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)>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"/>
|
||||
<span i18n:translate="label_history" i18n:domain="plone" class="appyHistory"></span>
|
||||
</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"/>
|
||||
—
|
||||
</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>
|
|
@ -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) > 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']) > 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> <img tal:attributes="src string: $portal_url/skyn/nextPhase.png"/></td>
|
||||
</tr>
|
||||
</tal:phase>
|
||||
</table>
|
||||
</metal:phases>
|
||||
|
|
BIN
gen/plone25/skin/previous.png
Normal file
After Width: | Height: | Size: 905 B |
|
@ -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>
|
||||
|
||||
|
|
BIN
gen/plone25/skin/required.png
Normal file
After Width: | Height: | Size: 423 B |
BIN
gen/plone25/skin/save.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
|
@ -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
After Width: | Height: | Size: 43 B |
BIN
gen/plone25/skin/tabBg.png
Normal file
After Width: | Height: | Size: 149 B |
BIN
gen/plone25/skin/tabBgu.png
Normal file
After Width: | Height: | Size: 168 B |
BIN
gen/plone25/skin/tabLeft.png
Normal file
After Width: | Height: | Size: 203 B |
BIN
gen/plone25/skin/tabLeftu.png
Normal file
After Width: | Height: | Size: 180 B |
BIN
gen/plone25/skin/tabRight.png
Normal file
After Width: | Height: | Size: 207 B |
BIN
gen/plone25/skin/tabRightu.png
Normal file
After Width: | Height: | Size: 171 B |
|
@ -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>
|
||||
|
|
BIN
gen/plone25/skin/warning.png
Normal file
After Width: | Height: | Size: 400 B |
BIN
gen/plone25/skin/warning_no.png
Normal file
After Width: | Height: | Size: 319 B |
|
@ -1,142 +0,0 @@
|
|||
<metal:searchInteger define-macro="searchInteger">
|
||||
<label tal:content="python: tool.translate(appyType['label'])"></label><br>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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"> </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>
|
30
gen/plone25/skin/widgets/action.pt
Normal 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>
|
39
gen/plone25/skin/widgets/boolean.pt
Normal 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" />
|
||||
</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>
|
||||
<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>
|
18
gen/plone25/skin/widgets/computed.pt
Normal 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>
|
132
gen/plone25/skin/widgets/date.pt
Normal 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"> </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>
|
73
gen/plone25/skin/widgets/file.pt
Normal 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> -
|
||||
<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>
|
29
gen/plone25/skin/widgets/float.pt
Normal 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>
|
||||
<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>
|
11
gen/plone25/skin/widgets/info.pt
Normal 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>
|
28
gen/plone25/skin/widgets/integer.pt
Normal 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>
|
||||
<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>
|
26
gen/plone25/skin/widgets/pod.pt
Normal 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>
|
|
@ -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)<=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"/>
|
||||
use-macro="portal/skyn/widgets/ref/macros/objectTitle"/>
|
||||
</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>
|
||||
</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>
|
203
gen/plone25/skin/widgets/show.pt
Normal 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>
|
102
gen/plone25/skin/widgets/string.pt
Normal 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 > 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', '<br>')"/>
|
||||
<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>
|
||||
<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>
|
|
@ -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
|
||||
|
|
|
@ -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!>')
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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!>')
|
||||
|
|
|
@ -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()
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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!>}
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
class PodTemplateWrapper: pass
|
||||
from appy.gen.plone25.wrappers import AbstractWrapper
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class PodTemplateWrapper(AbstractWrapper): pass
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -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).'''
|
||||
|
|
|
@ -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
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -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).'''
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
11
gen/po.py
|
@ -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
|
||||
|
|
267
gen/utils.py
|
@ -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
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
1127
pod/test/Tests.rtf
5
pod/test/contexts/Chart1.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
johnScore = 25
|
||||
markScore = 53
|
||||
wilsonScore = 12
|
||||
meghuScore = 59
|
||||
|
BIN
pod/test/results/chart1.odt
Normal file
BIN
pod/test/templates/Chart1.odt
Normal 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
|
@ -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')
|
||||
# ------------------------------------------------------------------------------
|
|
@ -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)
|
||||
|
|