appy.gen: first Ploneless version.
|
@ -311,9 +311,9 @@ class Search:
|
||||||
named p_fieldName can't be used for p_usage.'''
|
named p_fieldName can't be used for p_usage.'''
|
||||||
if fieldName == 'title':
|
if fieldName == 'title':
|
||||||
if usage == 'search': return 'Title'
|
if usage == 'search': return 'Title'
|
||||||
else: return 'sortable_title'
|
else: return 'SortableTitle'
|
||||||
# Indeed, for field 'title', Plone has created a specific index
|
# Indeed, for field 'title', Appy has a specific index
|
||||||
# 'sortable_title', because index 'Title' is a ZCTextIndex
|
# 'SortableTitle', because index 'Title' is a ZCTextIndex
|
||||||
# (for searchability) and can't be used for sorting.
|
# (for searchability) and can't be used for sorting.
|
||||||
elif fieldName == 'description':
|
elif fieldName == 'description':
|
||||||
if usage == 'search': return 'Description'
|
if usage == 'search': return 'Description'
|
||||||
|
@ -1229,12 +1229,12 @@ class String(Type):
|
||||||
res = Type.getIndexValue(self, obj, forSearch)
|
res = Type.getIndexValue(self, obj, forSearch)
|
||||||
if isinstance(res, unicode):
|
if isinstance(res, unicode):
|
||||||
res = res.encode('utf-8')
|
res = res.encode('utf-8')
|
||||||
# Ugly portal_catalog: if I give an empty tuple as index value,
|
# Ugly catalog: if I give an empty tuple as index value, it keeps the
|
||||||
# portal_catalog keeps the previous value! If I give him a tuple
|
# previous value. If I give him a tuple containing an empty string, it
|
||||||
# containing an empty string, it is ok.
|
# is ok.
|
||||||
if isinstance(res, tuple) and not res: res = self.emptyStringTuple
|
if isinstance(res, tuple) and not res: res = self.emptyStringTuple
|
||||||
# Ugly portal_catalog: if value is an empty string or None, it keeps
|
# Ugly catalog: if value is an empty string or None, it keeps the
|
||||||
# the previous index value!
|
# previous index value.
|
||||||
if res in self.emptyValuesCatalogIgnored: res = ' '
|
if res in self.emptyValuesCatalogIgnored: res = ' '
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
@ -1757,7 +1757,7 @@ class Ref(Type):
|
||||||
if type == 'uids':
|
if type == 'uids':
|
||||||
ref = uids[i]
|
ref = uids[i]
|
||||||
else:
|
else:
|
||||||
ref = obj.uid_catalog(UID=uids[i])[0].getObject()
|
ref = obj.getTool().getObject(uids[i])
|
||||||
if type == 'objects':
|
if type == 'objects':
|
||||||
ref = ref.appy()
|
ref = ref.appy()
|
||||||
res.objects.append(ref)
|
res.objects.append(ref)
|
||||||
|
@ -1907,14 +1907,14 @@ class Computed(Type):
|
||||||
|
|
||||||
def callMacro(self, obj, macroPath):
|
def callMacro(self, obj, macroPath):
|
||||||
'''Returns the macro corresponding to p_macroPath. The base folder
|
'''Returns the macro corresponding to p_macroPath. The base folder
|
||||||
where we search is "skyn".'''
|
where we search is "ui".'''
|
||||||
# Get the special page in Appy that allows to call a macro
|
# Get the special page in Appy that allows to call a macro
|
||||||
macroPage = obj.skyn.callMacro
|
macroPage = obj.ui.callMacro
|
||||||
# Get, from p_macroPath, the page where the macro lies, and the macro
|
# Get, from p_macroPath, the page where the macro lies, and the macro
|
||||||
# name.
|
# name.
|
||||||
names = self.method.split('/')
|
names = self.method.split('/')
|
||||||
# Get the page where the macro lies
|
# Get the page where the macro lies
|
||||||
page = obj.skyn
|
page = obj.ui
|
||||||
for name in names[:-1]:
|
for name in names[:-1]:
|
||||||
page = getattr(page, name)
|
page = getattr(page, name)
|
||||||
macroName = names[-1]
|
macroName = names[-1]
|
||||||
|
@ -2140,7 +2140,7 @@ class Pod(Type):
|
||||||
exec cmd
|
exec cmd
|
||||||
# (re-)execute the query, but without any limit on the number of
|
# (re-)execute the query, but without any limit on the number of
|
||||||
# results; return Appy objects.
|
# results; return Appy objects.
|
||||||
objs = tool.o.executeQuery(type_name, searchName=search,
|
objs = tool.o.executeQuery(obj.o.portal_type, searchName=search,
|
||||||
sortBy=sortKey, sortOrder=sortOrder, filterKey=filterKey,
|
sortBy=sortKey, sortOrder=sortOrder, filterKey=filterKey,
|
||||||
filterValue=filterValue, maxResults='NO_LIMIT')
|
filterValue=filterValue, maxResults='NO_LIMIT')
|
||||||
podContext['objects'] = [o.appy() for o in objs['objects']]
|
podContext['objects'] = [o.appy() for o in objs['objects']]
|
||||||
|
@ -2376,7 +2376,7 @@ class State:
|
||||||
effectively updated.'''
|
effectively updated.'''
|
||||||
attr = Permission.getZopeAttrName(zopePermission)
|
attr = Permission.getZopeAttrName(zopePermission)
|
||||||
if not hasattr(obj.aq_base, attr) or \
|
if not hasattr(obj.aq_base, attr) or \
|
||||||
(getattr(obj.aq_base, attr) != roleNames):
|
(getattr(obj.aq_base, attr) != roleNames):
|
||||||
setattr(obj, attr, roleNames)
|
setattr(obj, attr, roleNames)
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
@ -2476,7 +2476,7 @@ class Transition:
|
||||||
wf = wf.__instance__ # We need the prototypical instance here.
|
wf = wf.__instance__ # We need the prototypical instance here.
|
||||||
# Checks that the current state of the object is a start state for this
|
# Checks that the current state of the object is a start state for this
|
||||||
# transition.
|
# transition.
|
||||||
objState = obj.getState(name=False)
|
objState = obj.State(name=False)
|
||||||
if self.isSingle():
|
if self.isSingle():
|
||||||
if objState != self.states[0]: return False
|
if objState != self.states[0]: return False
|
||||||
else:
|
else:
|
||||||
|
@ -2487,7 +2487,7 @@ class Transition:
|
||||||
break
|
break
|
||||||
if not startFound: return False
|
if not startFound: return False
|
||||||
# Check that the condition is met
|
# Check that the condition is met
|
||||||
user = obj.portal_membership.getAuthenticatedMember()
|
user = obj.getUser()
|
||||||
if isinstance(self.condition, Role):
|
if isinstance(self.condition, Role):
|
||||||
# Condition is a role. Transition may be triggered if the user has
|
# Condition is a role. Transition may be triggered if the user has
|
||||||
# this role.
|
# this role.
|
||||||
|
@ -2552,7 +2552,7 @@ class Transition:
|
||||||
targetState = self.states[1]
|
targetState = self.states[1]
|
||||||
targetStateName = targetState.getName(wf)
|
targetStateName = targetState.getName(wf)
|
||||||
else:
|
else:
|
||||||
startState = obj.getState(name=False)
|
startState = obj.State(name=False)
|
||||||
for sState, tState in self.states:
|
for sState, tState in self.states:
|
||||||
if startState == sState:
|
if startState == sState:
|
||||||
targetState = tState
|
targetState = tState
|
||||||
|
@ -2568,7 +2568,7 @@ class Transition:
|
||||||
targetState.updatePermissions(wf, obj)
|
targetState.updatePermissions(wf, obj)
|
||||||
# Refresh catalog-related security if required
|
# Refresh catalog-related security if required
|
||||||
if not obj.isTemporary():
|
if not obj.isTemporary():
|
||||||
obj.reindexObject(idxs=('allowedRolesAndUsers', 'getState'))
|
obj.reindex(indexes=('allowedRolesAndUsers', 'State'))
|
||||||
# Execute the related action if needed
|
# Execute the related action if needed
|
||||||
msg = ''
|
msg = ''
|
||||||
if doAction and self.action: msg = self.executeAction(obj, wf)
|
if doAction and self.action: msg = self.executeAction(obj, wf)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
'''This file contains the main Generator class used for generating a
|
'''This file contains the main Generator class used for generating a Zope
|
||||||
Plone 2.5-compliant product.'''
|
product.'''
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
import os, os.path, re, sys
|
import os, os.path, re, sys
|
||||||
|
@ -13,20 +13,6 @@ from descriptors import ClassDescriptor, ToolClassDescriptor, \
|
||||||
UserClassDescriptor, TranslationClassDescriptor
|
UserClassDescriptor, TranslationClassDescriptor
|
||||||
from model import ModelClass, User, Tool, Translation
|
from model import ModelClass, User, Tool, Translation
|
||||||
|
|
||||||
# Common methods that need to be defined on every Archetype class --------------
|
|
||||||
COMMON_METHODS = '''
|
|
||||||
def getTool(self): return self.%s
|
|
||||||
def getProductConfig(self): return Products.%s.config
|
|
||||||
def skynView(self):
|
|
||||||
"""Redirects to skyn/view. Transfers the status message if any."""
|
|
||||||
rq = self.REQUEST
|
|
||||||
msg = rq.get('portal_status_message', '')
|
|
||||||
if msg:
|
|
||||||
url = self.getUrl(portal_status_message=msg)
|
|
||||||
else:
|
|
||||||
url = self.getUrl()
|
|
||||||
return rq.RESPONSE.redirect(url)
|
|
||||||
'''
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
class Generator(AbstractGenerator):
|
class Generator(AbstractGenerator):
|
||||||
'''This generator generates a Plone 2.5-compliant product from a given
|
'''This generator generates a Plone 2.5-compliant product from a given
|
||||||
|
@ -45,15 +31,10 @@ class Generator(AbstractGenerator):
|
||||||
# i18n labels to generate
|
# i18n labels to generate
|
||||||
self.labels = [] # i18n labels
|
self.labels = [] # i18n labels
|
||||||
self.toolInstanceName = 'portal_%s' % self.applicationName.lower()
|
self.toolInstanceName = 'portal_%s' % self.applicationName.lower()
|
||||||
self.skinsFolder = 'skins/%s' % self.applicationName
|
|
||||||
# The following dict, pre-filled in the abstract generator, contains a
|
# The following dict, pre-filled in the abstract generator, contains a
|
||||||
# series of replacements that need to be applied to file templates to
|
# series of replacements that need to be applied to file templates to
|
||||||
# generate files.
|
# generate files.
|
||||||
commonMethods = COMMON_METHODS % \
|
self.repls.update({'toolInstanceName': self.toolInstanceName})
|
||||||
(self.toolInstanceName, self.applicationName)
|
|
||||||
self.repls.update(
|
|
||||||
{'toolInstanceName': self.toolInstanceName,
|
|
||||||
'commonMethods': commonMethods})
|
|
||||||
self.referers = {}
|
self.referers = {}
|
||||||
|
|
||||||
versionRex = re.compile('(.*?\s+build)\s+(\d+)')
|
versionRex = re.compile('(.*?\s+build)\s+(\d+)')
|
||||||
|
@ -160,14 +141,12 @@ class Generator(AbstractGenerator):
|
||||||
self.generateTool()
|
self.generateTool()
|
||||||
self.generateInit()
|
self.generateInit()
|
||||||
self.generateTests()
|
self.generateTests()
|
||||||
if self.config.frontPage: self.generateFrontPage()
|
|
||||||
self.copyFile('Install.py', self.repls, destFolder='Extensions')
|
self.copyFile('Install.py', self.repls, destFolder='Extensions')
|
||||||
self.generateConfigureZcml()
|
self.generateConfigureZcml()
|
||||||
self.copyFile('import_steps.xml', self.repls,
|
self.copyFile('import_steps.xml', self.repls,
|
||||||
destFolder='profiles/default')
|
destFolder='profiles/default')
|
||||||
self.copyFile('ProfileInit.py', self.repls, destFolder='profiles',
|
self.copyFile('ProfileInit.py', self.repls, destFolder='profiles',
|
||||||
destName='__init__.py')
|
destName='__init__.py')
|
||||||
self.copyFile('tool.gif', {})
|
|
||||||
# Create version.txt
|
# Create version.txt
|
||||||
f = open(os.path.join(self.outputFolder, 'version.txt'), 'w')
|
f = open(os.path.join(self.outputFolder, 'version.txt'), 'w')
|
||||||
f.write(self.version)
|
f.write(self.version)
|
||||||
|
@ -334,7 +313,7 @@ class Generator(AbstractGenerator):
|
||||||
['"%s"' % r for r in self.config.defaultCreators])
|
['"%s"' % r for r in self.config.defaultCreators])
|
||||||
# Compute list of add permissions
|
# Compute list of add permissions
|
||||||
addPermissions = ''
|
addPermissions = ''
|
||||||
for classDescr in classesButTool:
|
for classDescr in classesAll:
|
||||||
addPermissions += ' "%s":"%s: Add %s",\n' % (classDescr.name,
|
addPermissions += ' "%s":"%s: Add %s",\n' % (classDescr.name,
|
||||||
self.applicationName, classDescr.name)
|
self.applicationName, classDescr.name)
|
||||||
repls['addPermissions'] = addPermissions
|
repls['addPermissions'] = addPermissions
|
||||||
|
@ -492,30 +471,6 @@ class Generator(AbstractGenerator):
|
||||||
repls['modulesWithTests'] = ','.join(modules)
|
repls['modulesWithTests'] = ','.join(modules)
|
||||||
self.copyFile('testAll.py', repls, destFolder='tests')
|
self.copyFile('testAll.py', repls, destFolder='tests')
|
||||||
|
|
||||||
def generateFrontPage(self):
|
|
||||||
fp = self.config.frontPage
|
|
||||||
repls = self.repls.copy()
|
|
||||||
template = 'frontPage.pt'
|
|
||||||
if self.config.frontPageTemplate== 'appy': template = 'frontPageAppy.pt'
|
|
||||||
if fp == True:
|
|
||||||
# We need a front page, but no specific one has been given.
|
|
||||||
# So we will create a basic one that will simply display
|
|
||||||
# some translated text.
|
|
||||||
self.labels.append(PoMessage('front_page_text', '',
|
|
||||||
PoMessage.FRONT_PAGE_TEXT))
|
|
||||||
repls['pageContent'] = '<span tal:replace="structure python: ' \
|
|
||||||
'tool.translateWithMapping(\'front_page_text\')"/>'
|
|
||||||
else:
|
|
||||||
# The user has specified a macro to show. So in the generated front
|
|
||||||
# page, we will call this macro. The user will need to add itself
|
|
||||||
# a .pt file containing this macro in the skins folder of the
|
|
||||||
# generated Plone product.
|
|
||||||
page, macro = fp.split('/')
|
|
||||||
repls['pageContent'] = '<metal:call use-macro=' \
|
|
||||||
'"context/%s/macros/%s"/>' % (page, macro)
|
|
||||||
self.copyFile(template, repls, destFolder=self.skinsFolder,
|
|
||||||
destName='%sFrontPage.pt' % self.applicationName)
|
|
||||||
|
|
||||||
def generateTool(self):
|
def generateTool(self):
|
||||||
'''Generates the Plone tool that corresponds to this application.'''
|
'''Generates the Plone tool that corresponds to this application.'''
|
||||||
Msg = PoMessage
|
Msg = PoMessage
|
||||||
|
@ -536,13 +491,9 @@ class Generator(AbstractGenerator):
|
||||||
Msg('%s_plural' % klass.name,'', klass.name+'s')]
|
Msg('%s_plural' % klass.name,'', klass.name+'s')]
|
||||||
repls = self.repls.copy()
|
repls = self.repls.copy()
|
||||||
repls.update({'methods': klass.methods, 'genClassName': klass.name,
|
repls.update({'methods': klass.methods, 'genClassName': klass.name,
|
||||||
'imports': '','baseMixin':'BaseMixin', 'baseSchema': 'BaseSchema',
|
'baseMixin':'BaseMixin', 'parents': 'BaseMixin, SimpleItem',
|
||||||
'global_allow': 1, 'parents': 'BaseMixin, BaseContent',
|
|
||||||
'static': '',
|
|
||||||
'classDoc': 'User class for %s' % self.applicationName,
|
'classDoc': 'User class for %s' % self.applicationName,
|
||||||
'implements': "(getattr(BaseContent,'__implements__',()),)",
|
'icon':'object.gif'})
|
||||||
'register': "registerType(%s, '%s')" % (klass.name,
|
|
||||||
self.applicationName)})
|
|
||||||
self.copyFile('Class.py', repls, destName='%s.py' % klass.name)
|
self.copyFile('Class.py', repls, destName='%s.py' % klass.name)
|
||||||
|
|
||||||
# Before generating the Tool class, finalize it with query result
|
# Before generating the Tool class, finalize it with query result
|
||||||
|
@ -567,18 +518,9 @@ class Generator(AbstractGenerator):
|
||||||
# Generate the Tool class
|
# Generate the Tool class
|
||||||
repls = self.repls.copy()
|
repls = self.repls.copy()
|
||||||
repls.update({'methods': self.tool.methods,
|
repls.update({'methods': self.tool.methods,
|
||||||
'genClassName': self.tool.name, 'imports':'', 'baseMixin':'ToolMixin',
|
'genClassName': self.tool.name, 'baseMixin':'ToolMixin',
|
||||||
'baseSchema': 'OrderedBaseFolderSchema', 'global_allow': 0,
|
'parents': 'ToolMixin, Folder', 'icon': 'folder.gif',
|
||||||
'parents': 'ToolMixin, UniqueObject, OrderedBaseFolder',
|
'classDoc': 'Tool class for %s' % self.applicationName})
|
||||||
'classDoc': 'Tool class for %s' % self.applicationName,
|
|
||||||
'implements': "(getattr(UniqueObject,'__implements__',()),) + " \
|
|
||||||
"(getattr(OrderedBaseFolder,'__implements__',()),)",
|
|
||||||
'register': "registerType(%s, '%s')" % (self.tool.name,
|
|
||||||
self.applicationName),
|
|
||||||
'static': "def __init__(self, id=None):\n " \
|
|
||||||
" OrderedBaseFolder.__init__(self, '%s')\n " \
|
|
||||||
" self.setTitle('%s')\n" % (self.toolInstanceName,
|
|
||||||
self.applicationName)})
|
|
||||||
self.copyFile('Class.py', repls, destName='%s.py' % self.tool.name)
|
self.copyFile('Class.py', repls, destName='%s.py' % self.tool.name)
|
||||||
|
|
||||||
def generateClass(self, classDescr):
|
def generateClass(self, classDescr):
|
||||||
|
@ -588,45 +530,19 @@ class Generator(AbstractGenerator):
|
||||||
print 'Generating %s.%s (gen-class)...' % (k.__module__, k.__name__)
|
print 'Generating %s.%s (gen-class)...' % (k.__module__, k.__name__)
|
||||||
if not classDescr.isAbstract():
|
if not classDescr.isAbstract():
|
||||||
self.tool.addWorkflowFields(classDescr)
|
self.tool.addWorkflowFields(classDescr)
|
||||||
# Determine base archetypes schema and class
|
# Determine base Zope class
|
||||||
baseClass = 'BaseContent'
|
isFolder = classDescr.isFolder()
|
||||||
baseSchema = 'BaseSchema'
|
baseClass = isFolder and 'Folder' or 'SimpleItem'
|
||||||
if classDescr.isFolder():
|
icon = isFolder and 'folder.gif' or 'object.gif'
|
||||||
baseClass = 'OrderedBaseFolder'
|
parents = 'BaseMixin, %s' % baseClass
|
||||||
baseSchema = 'OrderedBaseFolderSchema'
|
classDoc = classDescr.klass.__doc__ or 'Appy class.'
|
||||||
parents = ['BaseMixin', baseClass]
|
|
||||||
imports = []
|
|
||||||
implements = [baseClass]
|
|
||||||
for baseClass in classDescr.klass.__bases__:
|
|
||||||
if self.determineAppyType(baseClass) == 'class':
|
|
||||||
bcName = getClassName(baseClass)
|
|
||||||
parents.remove('BaseMixin')
|
|
||||||
parents.insert(0, bcName)
|
|
||||||
implements.append(bcName)
|
|
||||||
imports.append('from %s import %s' % (bcName, bcName))
|
|
||||||
baseSchema = '%s.schema' % bcName
|
|
||||||
break
|
|
||||||
parents = ','.join(parents)
|
|
||||||
implements = '+'.join(['(getattr(%s,"__implements__",()),)' % i \
|
|
||||||
for i in implements])
|
|
||||||
classDoc = classDescr.klass.__doc__
|
|
||||||
if not classDoc:
|
|
||||||
classDoc = 'Class generated with appy.gen.'
|
|
||||||
# If the class is abstract I will not register it
|
|
||||||
register = "registerType(%s, '%s')" % (classDescr.name,
|
|
||||||
self.applicationName)
|
|
||||||
if classDescr.isAbstract():
|
|
||||||
register = ''
|
|
||||||
repls = self.repls.copy()
|
repls = self.repls.copy()
|
||||||
classDescr.generateSchema()
|
classDescr.generateSchema()
|
||||||
repls.update({
|
repls.update({
|
||||||
'imports': '\n'.join(imports), 'parents': parents,
|
'parents': parents, 'className': classDescr.klass.__name__,
|
||||||
'className': classDescr.klass.__name__, 'global_allow': 1,
|
|
||||||
'genClassName': classDescr.name, 'baseMixin':'BaseMixin',
|
'genClassName': classDescr.name, 'baseMixin':'BaseMixin',
|
||||||
'classDoc': classDoc, 'applicationName': self.applicationName,
|
'classDoc': classDoc, 'applicationName': self.applicationName,
|
||||||
'methods': classDescr.methods, 'implements': implements,
|
'methods': classDescr.methods, 'icon':icon})
|
||||||
'baseSchema': baseSchema, 'static': '', 'register': register,
|
|
||||||
'toolInstanceName': self.toolInstanceName})
|
|
||||||
fileName = '%s.py' % classDescr.name
|
fileName = '%s.py' % classDescr.name
|
||||||
# Create i18n labels (class name and plural form)
|
# Create i18n labels (class name and plural form)
|
||||||
poMsg = PoMessage(classDescr.name, '', classDescr.klass.__name__)
|
poMsg = PoMessage(classDescr.name, '', classDescr.klass.__name__)
|
||||||
|
|
|
@ -7,18 +7,38 @@ from StringIO import StringIO
|
||||||
from sets import Set
|
from sets import Set
|
||||||
import appy
|
import appy
|
||||||
import appy.version
|
import appy.version
|
||||||
from appy.gen import Type, Ref, String
|
from appy.gen import Type, Ref, String, File
|
||||||
from appy.gen.po import PoParser
|
from appy.gen.po import PoParser
|
||||||
from appy.gen.utils import produceNiceMessage, updateRolesForPermission
|
from appy.gen.utils import produceNiceMessage, updateRolesForPermission, \
|
||||||
|
createObject
|
||||||
from appy.shared.data import languages
|
from appy.shared.data import languages
|
||||||
from migrator import Migrator
|
from migrator import Migrator
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
class ZCTextIndexInfo:
|
|
||||||
'''Silly class used for storing information about a ZCTextIndex.'''
|
|
||||||
lexicon_id = "plone_lexicon"
|
|
||||||
index_type = 'Okapi BM25 Rank'
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
homePage = '''
|
||||||
|
<tal:main define="tool python: context.config">
|
||||||
|
<html metal:use-macro="context/ui/template/macros/main">
|
||||||
|
<div metal:fill-slot="content">
|
||||||
|
<span tal:replace="structure python: tool.translate('front_page_text')"/>
|
||||||
|
</div>
|
||||||
|
</html>
|
||||||
|
</tal:main>
|
||||||
|
'''
|
||||||
|
errorPage = '''
|
||||||
|
<tal:main define="tool python: context.config">
|
||||||
|
<html metal:use-macro="context/ui/template/macros/main">
|
||||||
|
<div metal:fill-slot="content" tal:define="o python:options">
|
||||||
|
<p tal:condition="o/error_message"
|
||||||
|
tal:content="structure o/error_message"></p>
|
||||||
|
<p>Error type: <b><span tal:replace="o/error_type"/></b></p>
|
||||||
|
<p>Error value: <b><span tal:replace="o/error_value"/></b></p>
|
||||||
|
<p tal:content="structure o/error_tb"></p>
|
||||||
|
</div>
|
||||||
|
</html>
|
||||||
|
</tal:main>
|
||||||
|
'''
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
class PloneInstaller:
|
class PloneInstaller:
|
||||||
'''This Plone installer runs every time the generated Plone product is
|
'''This Plone installer runs every time the generated Plone product is
|
||||||
installed or uninstalled (in the Plone configuration interface).'''
|
installed or uninstalled (in the Plone configuration interface).'''
|
||||||
|
@ -40,78 +60,11 @@ class PloneInstaller:
|
||||||
self.attributes = cfg.attributes
|
self.attributes = cfg.attributes
|
||||||
# A buffer for logging purposes
|
# A buffer for logging purposes
|
||||||
self.toLog = StringIO()
|
self.toLog = StringIO()
|
||||||
self.typeAliases = {'sharing': '', 'gethtml': '',
|
|
||||||
'(Default)': 'skynView', 'edit': 'skyn/edit',
|
|
||||||
'index.html': '', 'properties': '', 'view': ''}
|
|
||||||
self.tool = None # The Plone version of the application tool
|
self.tool = None # The Plone version of the application tool
|
||||||
self.appyTool = None # The Appy version of the application tool
|
self.appyTool = None # The Appy version of the application tool
|
||||||
self.toolName = '%sTool' % self.productName
|
self.toolName = '%sTool' % self.productName
|
||||||
self.toolInstanceName = 'portal_%s' % self.productName.lower()
|
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
|
|
||||||
zopeCatalog = catalog._catalog
|
|
||||||
for indexName, indexType in indexInfo.iteritems():
|
|
||||||
# If this index already exists but with a different type, remove it.
|
|
||||||
if (indexName in zopeCatalog.indexes):
|
|
||||||
oldType = zopeCatalog.indexes[indexName].__class__.__name__
|
|
||||||
if oldType != indexType:
|
|
||||||
catalog.delIndex(indexName)
|
|
||||||
logger.info('Existing index "%s" of type "%s" was removed:'\
|
|
||||||
' we need to recreate it with type "%s".' % \
|
|
||||||
(indexName, oldType, indexType))
|
|
||||||
if indexName not in zopeCatalog.indexes:
|
|
||||||
# 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))
|
|
||||||
|
|
||||||
appyFolderType = 'AppyFolder'
|
|
||||||
def registerAppyFolderType(self):
|
|
||||||
'''We need a specific content type for the folder that will hold all
|
|
||||||
objects created from this application, in order to remove it from
|
|
||||||
Plone navigation settings. We will create a new content type based
|
|
||||||
on Large Plone Folder.'''
|
|
||||||
if not hasattr(self.ploneSite.portal_types, self.appyFolderType):
|
|
||||||
portal_types = self.ploneSite.portal_types
|
|
||||||
lpf = 'Large Plone Folder'
|
|
||||||
largePloneFolder = getattr(portal_types, lpf)
|
|
||||||
typeInfoName = 'ATContentTypes: ATBTreeFolder (ATBTreeFolder)'
|
|
||||||
portal_types.manage_addTypeInformation(
|
|
||||||
largePloneFolder.meta_type, id=self.appyFolderType,
|
|
||||||
typeinfo_name=typeInfoName)
|
|
||||||
appyFolder = getattr(portal_types, self.appyFolderType)
|
|
||||||
appyFolder.title = 'Appy folder'
|
|
||||||
#appyFolder.factory = largePloneFolder.factory
|
|
||||||
#appyFolder.product = largePloneFolder.product
|
|
||||||
# Copy actions and aliases
|
|
||||||
appyFolder._actions = tuple(largePloneFolder._cloneActions())
|
|
||||||
# Copy aliases from the base portal type
|
|
||||||
appyFolder.setMethodAliases(largePloneFolder.getMethodAliases())
|
|
||||||
# Prevent Appy folders to be visible in standard Plone navigation
|
|
||||||
nv = self.ploneSite.portal_properties.navtree_properties
|
|
||||||
metaTypesNotToList = list(nv.getProperty('metaTypesNotToList'))
|
|
||||||
if self.appyFolderType not in metaTypesNotToList:
|
|
||||||
metaTypesNotToList.append(self.appyFolderType)
|
|
||||||
nv.manage_changeProperties(
|
|
||||||
metaTypesNotToList=tuple(metaTypesNotToList))
|
|
||||||
|
|
||||||
def getAddPermission(self, className):
|
|
||||||
'''What is the name of the permission allowing to create instances of
|
|
||||||
class whose name is p_className?'''
|
|
||||||
return self.productName + ': Add ' + className
|
|
||||||
|
|
||||||
def installRootFolder(self):
|
def installRootFolder(self):
|
||||||
'''Creates and/or configures, at the root of the Plone site and if
|
'''Creates and/or configures, at the root of the Plone site and if
|
||||||
needed, the folder where the application will store instances of
|
needed, the folder where the application will store instances of
|
||||||
|
@ -158,128 +111,6 @@ class PloneInstaller:
|
||||||
# have the main permission "Add portal content".
|
# have the main permission "Add portal content".
|
||||||
permission = 'Add portal content'
|
permission = 'Add portal content'
|
||||||
updateRolesForPermission(permission, tuple(allCreators), appFolder)
|
updateRolesForPermission(permission, tuple(allCreators), appFolder)
|
||||||
# Creates the "appy" Directory view
|
|
||||||
if hasattr(site.aq_base, 'skyn'):
|
|
||||||
site.manage_delObjects(['skyn'])
|
|
||||||
# This way, if Appy has moved from one place to the other, the
|
|
||||||
# directory view will always refer to the correct place.
|
|
||||||
addDirView = self.config.manage_addDirectoryView
|
|
||||||
addDirView(site, appy.getPath() + '/gen/plone25/skin', id='skyn')
|
|
||||||
|
|
||||||
def installTypes(self):
|
|
||||||
'''Registers and configures the Plone content types that correspond to
|
|
||||||
gen-classes.'''
|
|
||||||
site = self.ploneSite
|
|
||||||
# Do Plone-based type registration
|
|
||||||
classes = self.config.listTypes(self.productName)
|
|
||||||
self.config.installTypes(site, self.toLog, classes, self.productName)
|
|
||||||
self.config.install_subskin(site, self.toLog, self.config.__dict__)
|
|
||||||
# Set appy view/edit pages for every created type
|
|
||||||
for className in self.allClassNames + ['%sTool' % self.productName]:
|
|
||||||
# I did not put the app tool in self.allClassNames because it
|
|
||||||
# must not be registered in portal_factory
|
|
||||||
if hasattr(site.portal_types, className):
|
|
||||||
# className may correspond to an abstract class that has no
|
|
||||||
# corresponding Plone content type
|
|
||||||
typeInfo = getattr(site.portal_types, className)
|
|
||||||
typeInfo.setMethodAliases(self.typeAliases)
|
|
||||||
# Update edit and view actions
|
|
||||||
typeActions = typeInfo.listActions()
|
|
||||||
for action in typeActions:
|
|
||||||
if action.id == 'view':
|
|
||||||
page = 'skynView'
|
|
||||||
action.edit(action='string:${object_url}/%s' % page)
|
|
||||||
elif action.id == 'edit':
|
|
||||||
page = 'skyn/edit'
|
|
||||||
action.edit(action='string:${object_url}/%s' % page)
|
|
||||||
|
|
||||||
# Configure types for instance creation through portal_factory
|
|
||||||
factoryTool = site.portal_factory
|
|
||||||
factoryTypes = self.allClassNames + factoryTool.getFactoryTypes().keys()
|
|
||||||
factoryTool.manage_setPortalFactoryTypes(listOfTypeIds=factoryTypes)
|
|
||||||
|
|
||||||
# Whitelist tool in Archetypes, because now UID is in portal_catalog
|
|
||||||
atTool = getattr(site, self.config.ARCHETYPETOOLNAME)
|
|
||||||
atTool.setCatalogsByType(self.toolName, ['portal_catalog'])
|
|
||||||
|
|
||||||
def updatePodTemplates(self):
|
|
||||||
'''Creates or updates the POD templates in the tool according to pod
|
|
||||||
declarations in the application classes.'''
|
|
||||||
# Creates the templates for Pod fields if they do not exist.
|
|
||||||
for contentType in self.attributes.iterkeys():
|
|
||||||
appyClass = self.tool.getAppyClass(contentType)
|
|
||||||
if not appyClass: continue # May be an abstract class
|
|
||||||
wrapperClass = self.tool.getAppyClass(contentType, wrapper=True)
|
|
||||||
for appyType in wrapperClass.__fields__:
|
|
||||||
if appyType.type != 'Pod': continue
|
|
||||||
# Find the attribute that stores the template, and store on
|
|
||||||
# it the default one specified in the appyType if no
|
|
||||||
# template is stored yet.
|
|
||||||
attrName = self.appyTool.getAttributeName(
|
|
||||||
'podTemplate', appyClass, appyType.name)
|
|
||||||
fileObject = getattr(self.appyTool, attrName)
|
|
||||||
if not fileObject or (fileObject.size == 0):
|
|
||||||
# There is no file. Put the one specified in the appyType.
|
|
||||||
fileName = os.path.join(self.appyTool.getDiskFolder(),
|
|
||||||
appyType.template)
|
|
||||||
if os.path.exists(fileName):
|
|
||||||
setattr(self.appyTool, attrName, fileName)
|
|
||||||
self.appyTool.log('Imported "%s" in the tool in ' \
|
|
||||||
'attribute "%s"'% (fileName,attrName))
|
|
||||||
else:
|
|
||||||
self.appyTool.log('Template "%s" was not found!' % \
|
|
||||||
fileName, type='error')
|
|
||||||
|
|
||||||
def installTool(self):
|
|
||||||
'''Configures the application tool.'''
|
|
||||||
# Register the tool in Plone
|
|
||||||
try:
|
|
||||||
self.ploneSite.manage_addProduct[
|
|
||||||
self.productName].manage_addTool(self.toolName)
|
|
||||||
except self.config.BadRequest:
|
|
||||||
# If an instance with the same name already exists, this error will
|
|
||||||
# be unelegantly raised by Zope.
|
|
||||||
pass
|
|
||||||
|
|
||||||
self.tool = getattr(self.ploneSite, self.toolInstanceName)
|
|
||||||
self.tool.refreshSecurity()
|
|
||||||
self.appyTool = self.tool.appy()
|
|
||||||
if self.reinstall:
|
|
||||||
self.tool.createOrUpdate(False, None)
|
|
||||||
else:
|
|
||||||
self.tool.createOrUpdate(True, None)
|
|
||||||
|
|
||||||
def installTranslations(self):
|
|
||||||
'''Creates or updates the translation objects within the tool.'''
|
|
||||||
translations = [t.o.id for t in self.appyTool.translations]
|
|
||||||
# We browse the languages supported by this application and check
|
|
||||||
# whether we need to create the corresponding Translation objects.
|
|
||||||
for language in self.languages:
|
|
||||||
if language in translations: continue
|
|
||||||
# We will create, in the tool, the translation object for this
|
|
||||||
# language. Determine first its title.
|
|
||||||
langId, langEn, langNat = languages.get(language)
|
|
||||||
if langEn != langNat:
|
|
||||||
title = '%s (%s)' % (langEn, langNat)
|
|
||||||
else:
|
|
||||||
title = langEn
|
|
||||||
self.appyTool.create('translations', id=language, title=title)
|
|
||||||
self.appyTool.log('Translation object created for "%s".' % language)
|
|
||||||
# Now, we synchronise every Translation object with the corresponding
|
|
||||||
# "po" file on disk.
|
|
||||||
appFolder = self.config.diskFolder
|
|
||||||
appName = self.config.PROJECTNAME
|
|
||||||
dn = os.path.dirname
|
|
||||||
jn = os.path.join
|
|
||||||
i18nFolder = jn(jn(jn(dn(dn(dn(appFolder))),'Products'),appName),'i18n')
|
|
||||||
for translation in self.appyTool.translations:
|
|
||||||
# Get the "po" file
|
|
||||||
poName = '%s-%s.po' % (appName, translation.id)
|
|
||||||
poFile = PoParser(jn(i18nFolder, poName)).parse()
|
|
||||||
for message in poFile.messages:
|
|
||||||
setattr(translation, message.id, message.getMessage())
|
|
||||||
self.appyTool.log('Translation "%s" updated from "%s".' % \
|
|
||||||
(translation.id, poName))
|
|
||||||
|
|
||||||
def installRolesAndGroups(self):
|
def installRolesAndGroups(self):
|
||||||
'''Registers roles used by workflows and classes defined in this
|
'''Registers roles used by workflows and classes defined in this
|
||||||
|
@ -305,33 +136,6 @@ class PloneInstaller:
|
||||||
site.portal_groups.setRolesForGroup(group, [role])
|
site.portal_groups.setRolesForGroup(group, [role])
|
||||||
site.__ac_roles__ = tuple(data)
|
site.__ac_roles__ = tuple(data)
|
||||||
|
|
||||||
def manageIndexes(self):
|
|
||||||
'''For every indexed field, this method installs and updates the
|
|
||||||
corresponding index if it does not exist yet.'''
|
|
||||||
# Create a special index for object state, that does not correspond to
|
|
||||||
# a field.
|
|
||||||
indexInfo = {'getState': 'FieldIndex', 'UID': 'FieldIndex'}
|
|
||||||
for className in self.attributes.iterkeys():
|
|
||||||
wrapperClass = self.tool.getAppyClass(className, wrapper=True)
|
|
||||||
for appyType in wrapperClass.__fields__:
|
|
||||||
if not appyType.indexed or (appyType.name == 'title'): continue
|
|
||||||
n = appyType.name
|
|
||||||
indexName = 'get%s%s' % (n[0].upper(), n[1:])
|
|
||||||
indexInfo[indexName] = appyType.getIndexType()
|
|
||||||
if indexInfo:
|
|
||||||
PloneInstaller.updateIndexes(self.ploneSite, indexInfo, self)
|
|
||||||
|
|
||||||
def manageLanguages(self):
|
|
||||||
'''Manages the languages supported by the application.'''
|
|
||||||
languageTool = self.ploneSite.portal_languages
|
|
||||||
defLanguage = self.languages[0]
|
|
||||||
languageTool.manage_setLanguageSettings(defaultLanguage=defLanguage,
|
|
||||||
supportedLanguages=self.languages, setContentN=None,
|
|
||||||
setCookieN=True, setRequestN=True, setPathN=True,
|
|
||||||
setForcelanguageUrls=True, setAllowContentLanguageFallback=None,
|
|
||||||
setUseCombinedLanguageCodes=None, displayFlags=False,
|
|
||||||
startNeutral=False)
|
|
||||||
|
|
||||||
def finalizeInstallation(self):
|
def finalizeInstallation(self):
|
||||||
'''Performs some final installation steps.'''
|
'''Performs some final installation steps.'''
|
||||||
site = self.ploneSite
|
site = self.ploneSite
|
||||||
|
@ -345,26 +149,6 @@ class PloneInstaller:
|
||||||
self.appyTool.appyVersion = appy.version.short
|
self.appyTool.appyVersion = appy.version.short
|
||||||
self.info('Appy version is %s.' % self.appyTool.appyVersion)
|
self.info('Appy version is %s.' % self.appyTool.appyVersion)
|
||||||
# Call custom installer if any
|
# Call custom installer if any
|
||||||
if hasattr(self.appyTool, 'install'):
|
|
||||||
self.tool.executeAppyAction('install', reindex=False)
|
|
||||||
|
|
||||||
def info(self, msg): return self.appyTool.log(msg)
|
|
||||||
|
|
||||||
def install(self):
|
|
||||||
# Begin with a migration if required.
|
|
||||||
self.installTool()
|
|
||||||
if self.reinstall: Migrator(self).run()
|
|
||||||
self.installRootFolder()
|
|
||||||
self.installTypes()
|
|
||||||
self.manageLanguages()
|
|
||||||
self.manageIndexes()
|
|
||||||
self.updatePodTemplates()
|
|
||||||
self.installTranslations()
|
|
||||||
self.installRolesAndGroups()
|
|
||||||
self.finalizeInstallation()
|
|
||||||
self.appyTool.log("Installation done.")
|
|
||||||
|
|
||||||
def uninstall(self): return 'Done.'
|
|
||||||
|
|
||||||
# Stuff for tracking user activity ---------------------------------------------
|
# Stuff for tracking user activity ---------------------------------------------
|
||||||
loggedUsers = {}
|
loggedUsers = {}
|
||||||
|
@ -400,18 +184,226 @@ def onDelSession(sessionObject, container):
|
||||||
class ZopeInstaller:
|
class ZopeInstaller:
|
||||||
'''This Zope installer runs every time Zope starts and encounters this
|
'''This Zope installer runs every time Zope starts and encounters this
|
||||||
generated Zope product.'''
|
generated Zope product.'''
|
||||||
def __init__(self, zopeContext, toolClass, config, classes):
|
def __init__(self, zopeContext, config, classes):
|
||||||
self.zopeContext = zopeContext
|
self.zopeContext = zopeContext
|
||||||
self.toolClass = toolClass
|
self.app = zopeContext._ProductContext__app # The root of the Zope tree
|
||||||
self.config = cfg = config
|
self.config = config
|
||||||
self.classes = classes
|
self.classes = classes
|
||||||
# Unwrap some useful config variables
|
# Unwrap some useful config variables
|
||||||
self.productName = cfg.PROJECTNAME
|
self.productName = config.PROJECTNAME
|
||||||
self.logger = cfg.logger
|
self.languages = config.languages
|
||||||
self.defaultAddContentPermission = cfg.DEFAULT_ADD_CONTENT_PERMISSION
|
self.logger = config.logger
|
||||||
self.addContentPermissions = cfg.ADD_CONTENT_PERMISSIONS
|
self.addContentPermissions = config.ADD_CONTENT_PERMISSIONS
|
||||||
|
|
||||||
def completeAppyTypes(self):
|
def installUi(self):
|
||||||
|
'''Installs the user interface.'''
|
||||||
|
# Delete the existing folder if it existed.
|
||||||
|
zopeContent = self.app.objectIds()
|
||||||
|
if 'ui' in zopeContent: self.app.manage_delObjects(['ui'])
|
||||||
|
self.app.manage_addFolder('ui')
|
||||||
|
# Some useful imports
|
||||||
|
from Products.PythonScripts.PythonScript import PythonScript
|
||||||
|
from Products.PageTemplates.ZopePageTemplate import \
|
||||||
|
manage_addPageTemplate
|
||||||
|
# Browse the physical folder and re-create it in the Zope folder
|
||||||
|
j = os.path.join
|
||||||
|
ui = j(j(appy.getPath(), 'gen'), 'ui')
|
||||||
|
for root, dirs, files in os.walk(ui):
|
||||||
|
folderName = root[len(ui):]
|
||||||
|
# Get the Zope folder that corresponds to this name
|
||||||
|
zopeFolder = self.app.ui
|
||||||
|
if folderName:
|
||||||
|
for name in folderName.strip(os.sep).split(os.sep):
|
||||||
|
zopeFolder = zopeFolder._getOb(name)
|
||||||
|
# Create sub-folders at this level
|
||||||
|
for name in dirs: zopeFolder.manage_addFolder(name)
|
||||||
|
# Create files at this level
|
||||||
|
for name in files:
|
||||||
|
baseName, ext = os.path.splitext(name)
|
||||||
|
f = file(j(root, name))
|
||||||
|
if ext in File.imageExts:
|
||||||
|
zopeFolder.manage_addImage(name, f)
|
||||||
|
elif ext == '.pt':
|
||||||
|
manage_addPageTemplate(zopeFolder, baseName, '', f.read())
|
||||||
|
elif ext == '.py':
|
||||||
|
obj = PythonScript(baseName)
|
||||||
|
zopeFolder._setObject(baseName, obj)
|
||||||
|
zopeFolder._getOb(baseName).write(f.read())
|
||||||
|
else:
|
||||||
|
zopeFolder.manage_addFile(name, f)
|
||||||
|
f.close()
|
||||||
|
# Update the home page
|
||||||
|
if 'index_html' in zopeContent:
|
||||||
|
self.app.manage_delObjects(['index_html'])
|
||||||
|
manage_addPageTemplate(self.app, 'index_html', '', homePage)
|
||||||
|
# Update the error page
|
||||||
|
if 'standard_error_message' in zopeContent:
|
||||||
|
self.app.manage_delObjects(['standard_error_message'])
|
||||||
|
manage_addPageTemplate(self.app, 'standard_error_message', '',errorPage)
|
||||||
|
|
||||||
|
def installIndexes(self, indexInfo):
|
||||||
|
'''Updates indexes in the catalog.'''
|
||||||
|
catalog = self.app.catalog
|
||||||
|
logger = self.logger
|
||||||
|
for indexName, indexType in indexInfo.iteritems():
|
||||||
|
# If this index already exists but with a different type, remove it.
|
||||||
|
if indexName in catalog.indexes():
|
||||||
|
oldType = catalog.Indexes[indexName].__class__.__name__
|
||||||
|
if oldType != indexType:
|
||||||
|
catalog.delIndex(indexName)
|
||||||
|
logger.info('Existing index "%s" of type "%s" was removed:'\
|
||||||
|
' we need to recreate it with type "%s".' % \
|
||||||
|
(indexName, oldType, indexType))
|
||||||
|
if indexName not in catalog.indexes():
|
||||||
|
# We need to create this index
|
||||||
|
type = indexType
|
||||||
|
if type == 'ZCTextIndex': type = 'TextIndex'
|
||||||
|
catalog.addIndex(indexName, type)
|
||||||
|
logger.info('Created index "%s" of type "%s"...' % \
|
||||||
|
(indexName, type))
|
||||||
|
|
||||||
|
def installCatalog(self):
|
||||||
|
'''Create the catalog at the root of Zope if id does not exist.'''
|
||||||
|
if 'catalog' not in self.app.objectIds():
|
||||||
|
# Create the catalog
|
||||||
|
from Products.ZCatalog.ZCatalog import manage_addZCatalog
|
||||||
|
manage_addZCatalog(self.app, 'catalog', '')
|
||||||
|
self.logger.info('Appy catalog created.')
|
||||||
|
# Create or update Appy-wide indexes and field-related indexes
|
||||||
|
indexInfo = {'State': 'FieldIndex', 'UID': 'FieldIndex',
|
||||||
|
'Title': 'TextIndex', 'SortableTitle': 'FieldIndex',
|
||||||
|
'SearchableText': 'FieldIndex', 'Creator': 'FieldIndex',
|
||||||
|
'Created': 'DateIndex', 'ClassName': 'FieldIndex'}
|
||||||
|
tool = self.app.config
|
||||||
|
for className in self.config.attributes.iterkeys():
|
||||||
|
wrapperClass = tool.getAppyClass(className, wrapper=True)
|
||||||
|
for appyType in wrapperClass.__fields__:
|
||||||
|
if not appyType.indexed or (appyType.name == 'title'): continue
|
||||||
|
n = appyType.name
|
||||||
|
indexName = 'get%s%s' % (n[0].upper(), n[1:])
|
||||||
|
indexInfo[indexName] = appyType.getIndexType()
|
||||||
|
self.installIndexes(indexInfo)
|
||||||
|
|
||||||
|
def installBaseObjects(self):
|
||||||
|
'''Creates the tool and the root data folder if they do not exist.'''
|
||||||
|
# Create or update the base folder for storing data
|
||||||
|
zopeContent = self.app.objectIds()
|
||||||
|
if 'data' not in zopeContent: self.app.manage_addFolder('data')
|
||||||
|
if 'config' not in zopeContent:
|
||||||
|
toolName = '%sTool' % self.productName
|
||||||
|
createObject(self.app, 'config', toolName,self.productName,wf=False)
|
||||||
|
# Remove some default objects created by Zope but not useful
|
||||||
|
for name in ('standard_html_footer', 'standard_html_header',\
|
||||||
|
'standard_template.pt'):
|
||||||
|
if name in zopeContent: self.app.manage_delObjects([name])
|
||||||
|
|
||||||
|
def installTool(self):
|
||||||
|
'''Updates the tool (now that the catalog is created) and updates its
|
||||||
|
inner objects (translations, documents).'''
|
||||||
|
tool = self.app.config
|
||||||
|
tool.createOrUpdate(True, None)
|
||||||
|
tool.refreshSecurity()
|
||||||
|
appyTool = tool.appy()
|
||||||
|
|
||||||
|
# Create the admin user if no user exists.
|
||||||
|
if not self.app.acl_users.getUsers():
|
||||||
|
appyTool.create('users', name='min', firstName='ad',
|
||||||
|
login='admin', password1='admin',
|
||||||
|
password2='admin', roles=['Manager'])
|
||||||
|
appyTool.log('Admin user "admin" created.')
|
||||||
|
# Create POD templates within the tool if required
|
||||||
|
for contentType in self.config.attributes.iterkeys():
|
||||||
|
appyClass = tool.getAppyClass(contentType)
|
||||||
|
if not appyClass: continue # May be an abstract class
|
||||||
|
wrapperClass = tool.getAppyClass(contentType, wrapper=True)
|
||||||
|
for appyType in wrapperClass.__fields__:
|
||||||
|
if appyType.type != 'Pod': continue
|
||||||
|
# Find the attribute that stores the template, and store on
|
||||||
|
# it the default one specified in the appyType if no
|
||||||
|
# template is stored yet.
|
||||||
|
attrName = appyTool.getAttributeName('podTemplate', appyClass,
|
||||||
|
appyType.name)
|
||||||
|
fileObject = getattr(appyTool, attrName)
|
||||||
|
if not fileObject or (fileObject.size == 0):
|
||||||
|
# There is no file. Put the one specified in the appyType.
|
||||||
|
fileName = os.path.join(appyTool.getDiskFolder(),
|
||||||
|
appyType.template)
|
||||||
|
if os.path.exists(fileName):
|
||||||
|
setattr(appyTool, attrName, fileName)
|
||||||
|
appyTool.log('Imported "%s" in the tool in ' \
|
||||||
|
'attribute "%s"'% (fileName, attrName))
|
||||||
|
else:
|
||||||
|
appyTool.log('Template "%s" was not found!' % \
|
||||||
|
fileName, type='error')
|
||||||
|
|
||||||
|
# Create or update Translation objects
|
||||||
|
translations = [t.o.id for t in appyTool.translations]
|
||||||
|
# We browse the languages supported by this application and check
|
||||||
|
# whether we need to create the corresponding Translation objects.
|
||||||
|
for language in self.languages:
|
||||||
|
if language in translations: continue
|
||||||
|
# We will create, in the tool, the translation object for this
|
||||||
|
# language. Determine first its title.
|
||||||
|
langId, langEn, langNat = languages.get(language)
|
||||||
|
if langEn != langNat:
|
||||||
|
title = '%s (%s)' % (langEn, langNat)
|
||||||
|
else:
|
||||||
|
title = langEn
|
||||||
|
appyTool.create('translations', id=language, title=title)
|
||||||
|
appyTool.log('Translation object created for "%s".' % language)
|
||||||
|
# Now, we synchronise every Translation object with the corresponding
|
||||||
|
# "po" file on disk.
|
||||||
|
appFolder = self.config.diskFolder
|
||||||
|
appName = self.config.PROJECTNAME
|
||||||
|
dn = os.path.dirname
|
||||||
|
jn = os.path.join
|
||||||
|
i18nFolder = jn(jn(jn(dn(dn(dn(appFolder))),'Products'),appName),'i18n')
|
||||||
|
for translation in appyTool.translations:
|
||||||
|
# Get the "po" file
|
||||||
|
poName = '%s-%s.po' % (appName, translation.id)
|
||||||
|
poFile = PoParser(jn(i18nFolder, poName)).parse()
|
||||||
|
for message in poFile.messages:
|
||||||
|
setattr(translation, message.id, message.getMessage())
|
||||||
|
appyTool.log('Translation "%s" updated from "%s".' % \
|
||||||
|
(translation.id, poName))
|
||||||
|
|
||||||
|
# Execute custom installation code if any
|
||||||
|
if hasattr(appyTool, 'install'):
|
||||||
|
tool.executeAppyAction('install', reindex=False)
|
||||||
|
|
||||||
|
def configureSessions(self):
|
||||||
|
'''Configure the session machinery.'''
|
||||||
|
# Register a function warning us when a session object is deleted. When
|
||||||
|
# launching Zope, the temp folder does not exist.
|
||||||
|
if not hasattr(self.app, 'temp_folder'): return
|
||||||
|
self.app.temp_folder.session_data.setDelNotificationTarget(onDelSession)
|
||||||
|
|
||||||
|
def enableUserTracking(self):
|
||||||
|
'''Enables the machinery allowing to know who is currently logged in.
|
||||||
|
Information about logged users will be stored in RAM, in the variable
|
||||||
|
named loggedUsers defined above.'''
|
||||||
|
global originalTraverse
|
||||||
|
if not originalTraverse:
|
||||||
|
# User tracking is not enabled yet. Do it now.
|
||||||
|
BaseRequest = self.config.BaseRequest
|
||||||
|
originalTraverse = BaseRequest.traverse
|
||||||
|
BaseRequest.traverse = traverseWrapper
|
||||||
|
|
||||||
|
def installZopeClasses(self):
|
||||||
|
'''Zope-level class registration.'''
|
||||||
|
for klass in self.classes:
|
||||||
|
name = klass.__name__
|
||||||
|
module = klass.__module__
|
||||||
|
wrapper = klass.wrapperClass
|
||||||
|
exec 'from %s import manage_add%s as ctor' % (module, name)
|
||||||
|
self.zopeContext.registerClass(meta_type=name,
|
||||||
|
constructors = (ctor,),
|
||||||
|
permission = self.addContentPermissions[name])
|
||||||
|
# Create workflow prototypical instances in __instance__ attributes
|
||||||
|
wf = getattr(klass.wrapperClass, 'workflow', None)
|
||||||
|
if wf and not hasattr(wf, '__instance__'): wf.__instance__ = wf()
|
||||||
|
|
||||||
|
def installAppyTypes(self):
|
||||||
'''We complete here the initialisation process of every Appy type of
|
'''We complete here the initialisation process of every Appy type of
|
||||||
every gen-class of the application.'''
|
every gen-class of the application.'''
|
||||||
appName = self.productName
|
appName = self.productName
|
||||||
|
@ -434,74 +426,15 @@ class ZopeInstaller:
|
||||||
continue # Back refs are initialised within fw refs
|
continue # Back refs are initialised within fw refs
|
||||||
appyType.init(name, baseClass, appName)
|
appyType.init(name, baseClass, appName)
|
||||||
|
|
||||||
def installApplication(self):
|
|
||||||
'''Performs some application-wide installation steps.'''
|
|
||||||
register = self.config.DirectoryView.registerDirectory
|
|
||||||
register('skins', self.config.__dict__)
|
|
||||||
# Register the appy skin folder among DirectoryView'able folders
|
|
||||||
register('skin', appy.getPath() + '/gen/plone25')
|
|
||||||
|
|
||||||
def installTool(self):
|
|
||||||
'''Installs the tool.'''
|
|
||||||
self.config.ToolInit(self.productName + ' Tools',
|
|
||||||
tools = [self.toolClass], icon='tool.gif').initialize(
|
|
||||||
self.zopeContext)
|
|
||||||
|
|
||||||
def installTypes(self):
|
|
||||||
'''Installs and configures the types defined in the application.'''
|
|
||||||
self.config.listTypes(self.productName)
|
|
||||||
contentTypes, constructors, ftis = self.config.process_types(
|
|
||||||
self.config.listTypes(self.productName), self.productName)
|
|
||||||
self.config.cmfutils.ContentInit(self.productName + ' Content',
|
|
||||||
content_types = contentTypes,
|
|
||||||
permission = self.defaultAddContentPermission,
|
|
||||||
extra_constructors = constructors, fti = ftis).initialize(
|
|
||||||
self.zopeContext)
|
|
||||||
# Define content-specific "add" permissions
|
|
||||||
for i in range(0, len(contentTypes)):
|
|
||||||
className = contentTypes[i].__name__
|
|
||||||
if not className in self.addContentPermissions: continue
|
|
||||||
self.zopeContext.registerClass(meta_type = ftis[i]['meta_type'],
|
|
||||||
constructors = (constructors[i],),
|
|
||||||
permission = self.addContentPermissions[className])
|
|
||||||
# Create workflow prototypical instances in __instance__ attributes
|
|
||||||
for contentType in contentTypes:
|
|
||||||
wf = getattr(contentType.wrapperClass, 'workflow', None)
|
|
||||||
if wf and not hasattr(wf, '__instance__'):
|
|
||||||
wf.__instance__ = wf()
|
|
||||||
|
|
||||||
def enableUserTracking(self):
|
|
||||||
'''Enables the machinery allowing to know who is currently logged in.
|
|
||||||
Information about logged users will be stored in RAM, in the variable
|
|
||||||
named loggedUsers defined above.'''
|
|
||||||
global originalTraverse
|
|
||||||
if not originalTraverse:
|
|
||||||
# User tracking is not enabled yet. Do it now.
|
|
||||||
BaseRequest = self.config.BaseRequest
|
|
||||||
originalTraverse = BaseRequest.traverse
|
|
||||||
BaseRequest.traverse = traverseWrapper
|
|
||||||
|
|
||||||
def finalizeInstallation(self):
|
|
||||||
'''Performs some final installation steps.'''
|
|
||||||
cfg = self.config
|
|
||||||
# Apply customization policy if any
|
|
||||||
cp = cfg.CustomizationPolicy
|
|
||||||
if cp and hasattr(cp, 'register'): cp.register(context)
|
|
||||||
# Install the default profile
|
|
||||||
cfg.profile_registry.registerProfile(self.productName, self.productName,
|
|
||||||
'Installation of %s' % self.productName, 'profiles/default',
|
|
||||||
self.productName, cfg.EXTENSION, for_=cfg.IPloneSiteRoot)
|
|
||||||
# Register a function warning us when a session object is deleted.
|
|
||||||
app = self.zopeContext._ProductContext__app
|
|
||||||
if hasattr(app, 'temp_folder'): # This is not the case in test mode
|
|
||||||
app.temp_folder.session_data.setDelNotificationTarget(onDelSession)
|
|
||||||
|
|
||||||
def install(self):
|
def install(self):
|
||||||
self.logger.info('is being installed...')
|
self.logger.info('is being installed...')
|
||||||
self.completeAppyTypes()
|
# Create the "admin" user if no user is present in the database
|
||||||
self.installApplication()
|
self.installAppyTypes()
|
||||||
self.installTool()
|
self.installZopeClasses()
|
||||||
self.installTypes()
|
|
||||||
self.enableUserTracking()
|
self.enableUserTracking()
|
||||||
self.finalizeInstallation()
|
self.configureSessions()
|
||||||
|
self.installBaseObjects()
|
||||||
|
self.installCatalog()
|
||||||
|
self.installTool()
|
||||||
|
self.installUi()
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -6,8 +6,7 @@ class TestMixin:
|
||||||
'''This class is mixed in with any PloneTestCase.'''
|
'''This class is mixed in with any PloneTestCase.'''
|
||||||
def createUser(self, userId, roles):
|
def createUser(self, userId, roles):
|
||||||
'''Creates a user with id p_userId with some p_roles.'''
|
'''Creates a user with id p_userId with some p_roles.'''
|
||||||
pms = self.portal.portal_membership
|
self.acl_users.addMember(userId, 'password', [], [])
|
||||||
pms.addMember(userId, 'password', [], [])
|
|
||||||
self.setRoles(roles, name=userId)
|
self.setRoles(roles, name=userId)
|
||||||
|
|
||||||
def changeUser(self, userId):
|
def changeUser(self, userId):
|
||||||
|
|
|
@ -1,13 +1,18 @@
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
import re, os, os.path, time, types
|
import re, os, os.path, time, random, types, base64, urllib
|
||||||
from appy.shared import mimeTypes
|
from appy.shared import mimeTypes
|
||||||
from appy.shared.utils import getOsTempFolder
|
from appy.shared.utils import getOsTempFolder
|
||||||
|
from appy.shared.data import languages
|
||||||
import appy.gen
|
import appy.gen
|
||||||
from appy.gen import Type, Search, Selection
|
from appy.gen import Type, Search, Selection
|
||||||
from appy.gen.utils import SomeObjects, sequenceTypes, getClassName
|
from appy.gen.utils import SomeObjects, sequenceTypes, getClassName
|
||||||
from appy.gen.plone25.mixins import BaseMixin
|
from appy.gen.plone25.mixins import BaseMixin
|
||||||
from appy.gen.plone25.wrappers import AbstractWrapper
|
from appy.gen.plone25.wrappers import AbstractWrapper
|
||||||
from appy.gen.plone25.descriptors import ClassDescriptor
|
from appy.gen.plone25.descriptors import ClassDescriptor
|
||||||
|
try:
|
||||||
|
from AccessControl.ZopeSecurityPolicy import _noroles
|
||||||
|
except ImportError:
|
||||||
|
_noroles = []
|
||||||
|
|
||||||
# Errors -----------------------------------------------------------------------
|
# Errors -----------------------------------------------------------------------
|
||||||
jsMessages = ('no_elem_selected', 'delete_confirm')
|
jsMessages = ('no_elem_selected', 'delete_confirm')
|
||||||
|
@ -27,13 +32,17 @@ class ToolMixin(BaseMixin):
|
||||||
res = '%s%s' % (elems[1], elems[4])
|
res = '%s%s' % (elems[1], elems[4])
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def getCatalog(self):
|
||||||
|
'''Returns the catalog object.'''
|
||||||
|
return self.getParentNode().catalog
|
||||||
|
|
||||||
def getApp(self):
|
def getApp(self):
|
||||||
'''Returns the root application object.'''
|
'''Returns the root Zope object.'''
|
||||||
return self.portal_url.getPortalObject()
|
return self.getPhysicalRoot()
|
||||||
|
|
||||||
def getSiteUrl(self):
|
def getSiteUrl(self):
|
||||||
'''Returns the absolute URL of this site.'''
|
'''Returns the absolute URL of this site.'''
|
||||||
return self.portal_url.getPortalObject().absolute_url()
|
return self.getApp().absolute_url()
|
||||||
|
|
||||||
def getPodInfo(self, obj, name):
|
def getPodInfo(self, obj, name):
|
||||||
'''Gets the available POD formats for Pod field named p_name on
|
'''Gets the available POD formats for Pod field named p_name on
|
||||||
|
@ -61,20 +70,43 @@ class ToolMixin(BaseMixin):
|
||||||
return res.content
|
return res.content
|
||||||
|
|
||||||
def getAttr(self, name):
|
def getAttr(self, name):
|
||||||
'''Gets attribute named p_attrName. Useful because we can't use getattr
|
'''Gets attribute named p_name.'''
|
||||||
directly in Zope Page Templates.'''
|
|
||||||
return getattr(self.appy(), name, None)
|
return getattr(self.appy(), name, None)
|
||||||
|
|
||||||
def getAppName(self):
|
def getAppName(self):
|
||||||
'''Returns the name of this application.'''
|
'''Returns the name of the application.'''
|
||||||
return self.getProductConfig().PROJECTNAME
|
return self.getProductConfig().PROJECTNAME
|
||||||
|
|
||||||
def getAppFolder(self):
|
def getPath(self, path):
|
||||||
'''Returns the folder at the root of the Plone site that is dedicated
|
'''Returns the folder or object whose absolute path p_path.'''
|
||||||
to this application.'''
|
res = self.getPhysicalRoot()
|
||||||
cfg = self.getProductConfig()
|
if path == '/': return res
|
||||||
portal = cfg.getToolByName(self, 'portal_url').getPortalObject()
|
path = path[1:]
|
||||||
return getattr(portal, self.getAppName())
|
if '/' not in path: return res._getOb(path) # For performance
|
||||||
|
for elem in path.split('/'): res = res._getOb(elem)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def getLanguages(self):
|
||||||
|
'''Returns the supported languages. First one is the default.'''
|
||||||
|
return self.getProductConfig().languages
|
||||||
|
|
||||||
|
def getLanguageName(self, code):
|
||||||
|
'''Gets the language name (in this language) from a 2-chars language
|
||||||
|
p_code.'''
|
||||||
|
return languages.get(code)[2]
|
||||||
|
|
||||||
|
def getMessages(self):
|
||||||
|
'''Returns the list of messages to return to the user.'''
|
||||||
|
if hasattr(self.REQUEST, 'messages'):
|
||||||
|
# Empty the messages and return it
|
||||||
|
res = self.REQUEST.messages
|
||||||
|
del self.REQUEST.messages
|
||||||
|
else:
|
||||||
|
res = []
|
||||||
|
# Add portal_status_message key if present
|
||||||
|
if 'portal_status_message' in self.REQUEST:
|
||||||
|
res.append( ('info', self.REQUEST['portal_status_message']) )
|
||||||
|
return res
|
||||||
|
|
||||||
def getRootClasses(self):
|
def getRootClasses(self):
|
||||||
'''Returns the list of root classes for this application.'''
|
'''Returns the list of root classes for this application.'''
|
||||||
|
@ -124,7 +156,7 @@ class ToolMixin(BaseMixin):
|
||||||
return {'fields': fields, 'nbOfColumns': nbOfColumns,
|
return {'fields': fields, 'nbOfColumns': nbOfColumns,
|
||||||
'fieldDicts': fieldDicts}
|
'fieldDicts': fieldDicts}
|
||||||
|
|
||||||
queryParamNames = ('type_name', 'search', 'sortKey', 'sortOrder',
|
queryParamNames = ('className', 'search', 'sortKey', 'sortOrder',
|
||||||
'filterKey', 'filterValue')
|
'filterKey', 'filterValue')
|
||||||
def getQueryInfo(self):
|
def getQueryInfo(self):
|
||||||
'''If we are showing search results, this method encodes in a string all
|
'''If we are showing search results, this method encodes in a string all
|
||||||
|
@ -160,8 +192,8 @@ class ToolMixin(BaseMixin):
|
||||||
return [importParams['headers'], elems]
|
return [importParams['headers'], elems]
|
||||||
|
|
||||||
def showPortlet(self, context):
|
def showPortlet(self, context):
|
||||||
if self.portal_membership.isAnonymousUser(): return False
|
if self.userIsAnon(): return False
|
||||||
if context.id == 'skyn': context = context.getParentNode()
|
if context.id == 'ui': context = context.getParentNode()
|
||||||
res = True
|
res = True
|
||||||
if not self.getRootClasses():
|
if not self.getRootClasses():
|
||||||
res = False
|
res = False
|
||||||
|
@ -170,24 +202,25 @@ class ToolMixin(BaseMixin):
|
||||||
if (self.id in context.absolute_url()): res = True
|
if (self.id in context.absolute_url()): res = True
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def getObject(self, uid, appy=False):
|
def getObject(self, uid, appy=False, brain=False):
|
||||||
'''Allows to retrieve an object from its p_uid.'''
|
'''Allows to retrieve an object from its p_uid.'''
|
||||||
res = self.portal_catalog(UID=uid)
|
res = self.getPhysicalRoot().catalog(UID=uid)
|
||||||
if res:
|
if not res: return
|
||||||
res = res[0].getObject()
|
res = res[0]
|
||||||
if appy:
|
if brain: return res
|
||||||
res = res.appy()
|
res = res.getObject()
|
||||||
return res
|
if not appy: return res
|
||||||
|
return res.appy()
|
||||||
|
|
||||||
def executeQuery(self, contentType, searchName=None, startNumber=0,
|
def executeQuery(self, className, searchName=None, startNumber=0,
|
||||||
search=None, remember=False, brainsOnly=False,
|
search=None, remember=False, brainsOnly=False,
|
||||||
maxResults=None, noSecurity=False, sortBy=None,
|
maxResults=None, noSecurity=False, sortBy=None,
|
||||||
sortOrder='asc', filterKey=None, filterValue=None,
|
sortOrder='asc', filterKey=None, filterValue=None,
|
||||||
refObject=None, refField=None):
|
refObject=None, refField=None):
|
||||||
'''Executes a query on a given p_contentType (or several, separated
|
'''Executes a query on instances of a given p_className (or several,
|
||||||
with commas) in portal_catalog. If p_searchName is specified, it
|
separated with commas) in the catalog. If p_searchName is specified,
|
||||||
corresponds to:
|
it corresponds to:
|
||||||
1) a search defined on p_contentType: additional search criteria
|
1) a search defined on p_className: additional search criteria
|
||||||
will be added to the query, or;
|
will be added to the query, or;
|
||||||
2) "_advanced": in this case, additional search criteria will also
|
2) "_advanced": in this case, additional search criteria will also
|
||||||
be added to the query, but those criteria come from the session
|
be added to the query, but those criteria come from the session
|
||||||
|
@ -224,16 +257,16 @@ class ToolMixin(BaseMixin):
|
||||||
If p_refObject and p_refField are given, the query is limited to the
|
If p_refObject and p_refField are given, the query is limited to the
|
||||||
objects that are referenced from p_refObject through p_refField.'''
|
objects that are referenced from p_refObject through p_refField.'''
|
||||||
# Is there one or several content types ?
|
# Is there one or several content types ?
|
||||||
if contentType.find(',') != -1:
|
if className.find(',') != -1:
|
||||||
portalTypes = contentType.split(',')
|
classNames = className.split(',')
|
||||||
else:
|
else:
|
||||||
portalTypes = contentType
|
classNames = className
|
||||||
params = {'portal_type': portalTypes}
|
params = {'ClassName': classNames}
|
||||||
if not brainsOnly: params['batch'] = True
|
if not brainsOnly: params['batch'] = True
|
||||||
# Manage additional criteria from a search when relevant
|
# Manage additional criteria from a search when relevant
|
||||||
if searchName:
|
if searchName:
|
||||||
# In this case, contentType must contain a single content type.
|
# In this case, className must contain a single content type.
|
||||||
appyClass = self.getAppyClass(contentType)
|
appyClass = self.getAppyClass(className)
|
||||||
if searchName != '_advanced':
|
if searchName != '_advanced':
|
||||||
search = ClassDescriptor.getSearch(appyClass, searchName)
|
search = ClassDescriptor.getSearch(appyClass, searchName)
|
||||||
else:
|
else:
|
||||||
|
@ -273,7 +306,7 @@ class ToolMixin(BaseMixin):
|
||||||
# Determine what method to call on the portal catalog
|
# Determine what method to call on the portal catalog
|
||||||
if noSecurity: catalogMethod = 'unrestrictedSearchResults'
|
if noSecurity: catalogMethod = 'unrestrictedSearchResults'
|
||||||
else: catalogMethod = 'searchResults'
|
else: catalogMethod = 'searchResults'
|
||||||
exec 'brains = self.portal_catalog.%s(**params)' % catalogMethod
|
exec 'brains = self.getPath("/catalog").%s(**params)' % catalogMethod
|
||||||
if brainsOnly:
|
if brainsOnly:
|
||||||
# Return brains only.
|
# Return brains only.
|
||||||
if not maxResults: return brains
|
if not maxResults: return brains
|
||||||
|
@ -290,8 +323,8 @@ class ToolMixin(BaseMixin):
|
||||||
# time a page for an element is consulted.
|
# time a page for an element is consulted.
|
||||||
if remember:
|
if remember:
|
||||||
if not searchName:
|
if not searchName:
|
||||||
# It is the global search for all objects pf p_contentType
|
# It is the global search for all objects pf p_className
|
||||||
searchName = contentType
|
searchName = className
|
||||||
uids = {}
|
uids = {}
|
||||||
i = -1
|
i = -1
|
||||||
for obj in res.objects:
|
for obj in res.objects:
|
||||||
|
@ -339,15 +372,6 @@ class ToolMixin(BaseMixin):
|
||||||
return '<acronym title="%s">%s</acronym>' % \
|
return '<acronym title="%s">%s</acronym>' % \
|
||||||
(text, text[:width] + '...')
|
(text, text[:width] + '...')
|
||||||
|
|
||||||
translationMapping = {'portal_path': ''}
|
|
||||||
def translateWithMapping(self, label):
|
|
||||||
'''Translates p_label in the application domain, with a default
|
|
||||||
translation mapping.'''
|
|
||||||
if not self.translationMapping['portal_path']:
|
|
||||||
self.translationMapping['portal_path'] = \
|
|
||||||
self.portal_url.getPortalPath()
|
|
||||||
return self.translate(label, mapping=self.translationMapping)
|
|
||||||
|
|
||||||
def getPublishedObject(self):
|
def getPublishedObject(self):
|
||||||
'''Gets the currently published object, if its meta_class is among
|
'''Gets the currently published object, if its meta_class is among
|
||||||
application classes.'''
|
application classes.'''
|
||||||
|
@ -355,24 +379,24 @@ class ToolMixin(BaseMixin):
|
||||||
# If we are querying object, there is no published object (the truth is:
|
# If we are querying object, there is no published object (the truth is:
|
||||||
# the tool is the currently published object but we don't want to
|
# the tool is the currently published object but we don't want to
|
||||||
# consider it this way).
|
# consider it this way).
|
||||||
if not req['ACTUAL_URL'].endswith('/skyn/view'): return
|
if not req['ACTUAL_URL'].endswith('/ui/view'): return
|
||||||
obj = self.REQUEST['PUBLISHED']
|
obj = self.REQUEST['PUBLISHED']
|
||||||
parent = obj.getParentNode()
|
parent = obj.getParentNode()
|
||||||
if parent.id == 'skyn': obj = parent.getParentNode()
|
if parent.id == 'ui': obj = parent.getParentNode()
|
||||||
if obj.meta_type in self.getProductConfig().attributes: return obj
|
if obj.meta_type in self.getProductConfig().attributes: return obj
|
||||||
|
|
||||||
def getAppyClass(self, contentType, wrapper=False):
|
def getZopeClass(self, name):
|
||||||
'''Gets the Appy Python class that is related to p_contentType.'''
|
'''Returns the Zope class whose name is p_name.'''
|
||||||
# Retrieve first the Archetypes class corresponding to p_ContentType
|
exec 'from Products.%s.%s import %s as C'% (self.getAppName(),name,name)
|
||||||
portalType = self.portal_types.get(contentType)
|
return C
|
||||||
if not portalType: return
|
|
||||||
atClassName = portalType.getProperty('content_meta_type')
|
def getAppyClass(self, zopeName, wrapper=False):
|
||||||
appName = self.getProductConfig().PROJECTNAME
|
'''Gets the Appy class corresponding to the Zope class named p_name.
|
||||||
exec 'from Products.%s.%s import %s as atClass' % \
|
If p_wrapper is True, it returns the Appy wrapper. Else, it returns
|
||||||
(appName, atClassName, atClassName)
|
the user-defined class.'''
|
||||||
# Get then the Appy Python class
|
zopeClass = self.getZopeClass(zopeName)
|
||||||
if wrapper: return atClass.wrapperClass
|
if wrapper: return zopeClass.wrapperClass
|
||||||
else: return atClass.wrapperClass.__bases__[-1]
|
else: return zopeClass.wrapperClass.__bases__[-1]
|
||||||
|
|
||||||
def getCreateMeans(self, contentTypeOrAppyClass):
|
def getCreateMeans(self, contentTypeOrAppyClass):
|
||||||
'''Gets the different ways objects of p_contentTypeOrAppyClass (which
|
'''Gets the different ways objects of p_contentTypeOrAppyClass (which
|
||||||
|
@ -417,9 +441,9 @@ class ToolMixin(BaseMixin):
|
||||||
'''This method is called when the user wants to create objects from
|
'''This method is called when the user wants to create objects from
|
||||||
external data.'''
|
external data.'''
|
||||||
rq = self.REQUEST
|
rq = self.REQUEST
|
||||||
appyClass = self.getAppyClass(rq.get('type_name'))
|
appyClass = self.getAppyClass(rq.get('className'))
|
||||||
importPaths = rq.get('importPath').split('|')
|
importPaths = rq.get('importPath').split('|')
|
||||||
appFolder = self.getAppFolder()
|
appFolder = self.getPath('/data')
|
||||||
for importPath in importPaths:
|
for importPath in importPaths:
|
||||||
if not importPath: continue
|
if not importPath: continue
|
||||||
objectId = os.path.basename(importPath)
|
objectId = os.path.basename(importPath)
|
||||||
|
@ -428,9 +452,9 @@ class ToolMixin(BaseMixin):
|
||||||
return self.goto(rq['HTTP_REFERER'])
|
return self.goto(rq['HTTP_REFERER'])
|
||||||
|
|
||||||
def isAlreadyImported(self, contentType, importPath):
|
def isAlreadyImported(self, contentType, importPath):
|
||||||
appFolder = self.getAppFolder()
|
data = self.getPath('/data')
|
||||||
objectId = os.path.basename(importPath)
|
objectId = os.path.basename(importPath)
|
||||||
if hasattr(appFolder.aq_base, objectId):
|
if hasattr(data.aq_base, objectId):
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
@ -553,8 +577,8 @@ class ToolMixin(BaseMixin):
|
||||||
if refInfo: criteria['_ref'] = refInfo
|
if refInfo: criteria['_ref'] = refInfo
|
||||||
rq.SESSION['searchCriteria'] = criteria
|
rq.SESSION['searchCriteria'] = criteria
|
||||||
# Go to the screen that displays search results
|
# Go to the screen that displays search results
|
||||||
backUrl = '%s/skyn/query?type_name=%s&&search=_advanced' % \
|
backUrl = '%s/ui/query?className=%s&&search=_advanced' % \
|
||||||
(self.absolute_url(), rq['type_name'])
|
(self.absolute_url(), rq['className'])
|
||||||
return self.goto(backUrl)
|
return self.goto(backUrl)
|
||||||
|
|
||||||
def getJavascriptMessages(self):
|
def getJavascriptMessages(self):
|
||||||
|
@ -614,8 +638,8 @@ class ToolMixin(BaseMixin):
|
||||||
'''This method creates the URL that allows to perform a (non-Ajax)
|
'''This method creates the URL that allows to perform a (non-Ajax)
|
||||||
request for getting queried objects from a search named p_searchName
|
request for getting queried objects from a search named p_searchName
|
||||||
on p_contentType.'''
|
on p_contentType.'''
|
||||||
baseUrl = self.absolute_url() + '/skyn'
|
baseUrl = self.absolute_url() + '/ui'
|
||||||
baseParams = 'type_name=%s' % contentType
|
baseParams = 'className=%s' % contentType
|
||||||
rq = self.REQUEST
|
rq = self.REQUEST
|
||||||
if rq.get('ref'): baseParams += '&ref=%s' % rq.get('ref')
|
if rq.get('ref'): baseParams += '&ref=%s' % rq.get('ref')
|
||||||
# Manage start number
|
# Manage start number
|
||||||
|
@ -663,7 +687,7 @@ class ToolMixin(BaseMixin):
|
||||||
res['backText'] = self.translate(label)
|
res['backText'] = self.translate(label)
|
||||||
else:
|
else:
|
||||||
fieldName, pageName = d2.split(':')
|
fieldName, pageName = d2.split(':')
|
||||||
sourceObj = self.portal_catalog(UID=d1)[0].getObject()
|
sourceObj = self.getObject(d1)
|
||||||
label = '%s_%s' % (sourceObj.meta_type, fieldName)
|
label = '%s_%s' % (sourceObj.meta_type, fieldName)
|
||||||
res['backText'] = '%s : %s' % (sourceObj.Title(),
|
res['backText'] = '%s : %s' % (sourceObj.Title(),
|
||||||
self.translate(label))
|
self.translate(label))
|
||||||
|
@ -739,9 +763,9 @@ class ToolMixin(BaseMixin):
|
||||||
except KeyError: pass
|
except KeyError: pass
|
||||||
except IndexError: pass
|
except IndexError: pass
|
||||||
if uid:
|
if uid:
|
||||||
brain = self.portal_catalog(UID=uid)
|
brain = self.getObject(uid, brain=True)
|
||||||
if brain:
|
if brain:
|
||||||
sibling = brain[0].getObject()
|
sibling = brain.getObject()
|
||||||
res[urlKey] = sibling.getUrl(nav=newNav % (index + 1),
|
res[urlKey] = sibling.getUrl(nav=newNav % (index + 1),
|
||||||
page='main')
|
page='main')
|
||||||
return res
|
return res
|
||||||
|
@ -777,6 +801,9 @@ class ToolMixin(BaseMixin):
|
||||||
'''Gets the translated month name of month numbered p_monthNumber.'''
|
'''Gets the translated month name of month numbered p_monthNumber.'''
|
||||||
return self.translate(self.monthsIds[int(monthNumber)], domain='plone')
|
return self.translate(self.monthsIds[int(monthNumber)], domain='plone')
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
# Authentication-related methods
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
def performLogin(self):
|
def performLogin(self):
|
||||||
'''Logs the user in.'''
|
'''Logs the user in.'''
|
||||||
rq = self.REQUEST
|
rq = self.REQUEST
|
||||||
|
@ -788,11 +815,14 @@ class ToolMixin(BaseMixin):
|
||||||
msg = self.translate(u'You must enable cookies before you can ' \
|
msg = self.translate(u'You must enable cookies before you can ' \
|
||||||
'log in.', domain='plone')
|
'log in.', domain='plone')
|
||||||
return self.goto(urlBack, msg.encode('utf-8'))
|
return self.goto(urlBack, msg.encode('utf-8'))
|
||||||
|
|
||||||
# Perform the Zope-level authentication
|
# Perform the Zope-level authentication
|
||||||
self.acl_users.credentials_cookie_auth.login()
|
login = rq.get('__ac_name', '')
|
||||||
login = rq['login_name']
|
password = rq.get('__ac_password', '')
|
||||||
if self.portal_membership.isAnonymousUser():
|
cookieValue = base64.encodestring('%s:%s' % (login, password)).rstrip()
|
||||||
|
cookieValue = urllib.quote(cookieValue)
|
||||||
|
rq.RESPONSE.setCookie('__ac', cookieValue, path='/')
|
||||||
|
user = self.acl_users.validate(rq)
|
||||||
|
if self.userIsAnon():
|
||||||
rq.RESPONSE.expireCookie('__ac', path='/')
|
rq.RESPONSE.expireCookie('__ac', path='/')
|
||||||
msg = self.translate(u'Login failed', domain='plone')
|
msg = self.translate(u'Login failed', domain='plone')
|
||||||
logMsg = 'Authentication failed (tried with login "%s")' % login
|
logMsg = 'Authentication failed (tried with login "%s")' % login
|
||||||
|
@ -803,7 +833,7 @@ class ToolMixin(BaseMixin):
|
||||||
msg = msg.encode('utf-8')
|
msg = msg.encode('utf-8')
|
||||||
self.log(logMsg)
|
self.log(logMsg)
|
||||||
# Bring Managers to the config, leave others on the main page.
|
# Bring Managers to the config, leave others on the main page.
|
||||||
user = self.portal_membership.getAuthenticatedMember()
|
user = self.getUser()
|
||||||
if user.has_role('Manager'):
|
if user.has_role('Manager'):
|
||||||
# Bring the user to the configuration
|
# Bring the user to the configuration
|
||||||
url = self.goto(self.absolute_url(), msg)
|
url = self.goto(self.absolute_url(), msg)
|
||||||
|
@ -814,28 +844,72 @@ class ToolMixin(BaseMixin):
|
||||||
def performLogout(self):
|
def performLogout(self):
|
||||||
'''Logs out the current user when he clicks on "disconnect".'''
|
'''Logs out the current user when he clicks on "disconnect".'''
|
||||||
rq = self.REQUEST
|
rq = self.REQUEST
|
||||||
userId = self.portal_membership.getAuthenticatedMember().getId()
|
userId = self.getUser().getId()
|
||||||
# Perform the logout in acl_users
|
# Perform the logout in acl_users
|
||||||
try:
|
rq.RESPONSE.expireCookie('__ac', path='/')
|
||||||
self.acl_users.logout(rq)
|
# Invalidate existing sessions.
|
||||||
except:
|
|
||||||
pass
|
|
||||||
skinvar = self.portal_skins.getRequestVarname()
|
|
||||||
path = '/' + self.absolute_url(1)
|
|
||||||
if rq.has_key(skinvar) and not self.portal_skins.getCookiePersistence():
|
|
||||||
rq.RESPONSE.expireCookie(skinvar, path=path)
|
|
||||||
# Invalidate existing sessions, but only if they exist.
|
|
||||||
sdm = self.session_data_manager
|
sdm = self.session_data_manager
|
||||||
session = sdm.getSessionData(create=0)
|
session = sdm.getSessionData(create=0)
|
||||||
if session is not None:
|
if session is not None:
|
||||||
session.invalidate()
|
session.invalidate()
|
||||||
from Products.CMFPlone import transaction_note
|
|
||||||
transaction_note('Logged out')
|
|
||||||
self.log('User "%s" has been logged out.' % userId)
|
self.log('User "%s" has been logged out.' % userId)
|
||||||
# Remove user from variable "loggedUsers"
|
# Remove user from variable "loggedUsers"
|
||||||
from appy.gen.plone25.installer import loggedUsers
|
from appy.gen.plone25.installer import loggedUsers
|
||||||
if loggedUsers.has_key(userId): del loggedUsers[userId]
|
if loggedUsers.has_key(userId): del loggedUsers[userId]
|
||||||
return self.goto(self.getParentNode().absolute_url())
|
return self.goto(self.getApp().absolute_url())
|
||||||
|
|
||||||
|
def validate(self, request, auth='', roles=_noroles):
|
||||||
|
'''This method performs authentication and authorization. It is used as
|
||||||
|
a replacement for Zope's AccessControl.User.BasicUserFolder.validate,
|
||||||
|
that allows to manage cookie-based authentication.'''
|
||||||
|
v = request['PUBLISHED'] # The published object
|
||||||
|
# v is the object (value) we're validating access to
|
||||||
|
# n is the name used to access the object
|
||||||
|
# a is the object the object was accessed through
|
||||||
|
# c is the physical container of the object
|
||||||
|
a, c, n, v = self._getobcontext(v, request)
|
||||||
|
# Try to get user name and password from basic authentication
|
||||||
|
login, password = self.identify(auth)
|
||||||
|
if not login:
|
||||||
|
# Try to get them from a cookie
|
||||||
|
cookie = request.get('__ac', None)
|
||||||
|
login = request.get('__ac_name', None)
|
||||||
|
if login and request.form.has_key('__ac_password'):
|
||||||
|
# The user just entered his credentials. The cookie has not been
|
||||||
|
# set yet (it will come in the upcoming HTTP response when the
|
||||||
|
# current request will be served).
|
||||||
|
login = request.get('__ac_name', '')
|
||||||
|
password = request.get('__ac_password', '')
|
||||||
|
elif cookie and (cookie != 'deleted'):
|
||||||
|
cookieValue = base64.decodestring(urllib.unquote(cookie))
|
||||||
|
login, password = cookieValue.split(':')
|
||||||
|
# Try to authenticate this user
|
||||||
|
user = self.authenticate(login, password, request)
|
||||||
|
emergency = self._emergency_user
|
||||||
|
if emergency and user is emergency:
|
||||||
|
# It is the emergency user.
|
||||||
|
return emergency.__of__(self)
|
||||||
|
elif user is None:
|
||||||
|
# Login and/or password incorrect. Try to authorize and return the
|
||||||
|
# anonymous user.
|
||||||
|
if self.authorize(self._nobody, a, c, n, v, roles):
|
||||||
|
return self._nobody.__of__(self)
|
||||||
|
else:
|
||||||
|
return # Anonymous can't acces this object
|
||||||
|
else:
|
||||||
|
# We found a user and his password was correct. Try to authorize him
|
||||||
|
# against the published object.
|
||||||
|
if self.authorize(user, a, c, n, v, roles):
|
||||||
|
return user.__of__(self)
|
||||||
|
# That didn't work. Try to authorize the anonymous user.
|
||||||
|
elif self.authorize(self._nobody, a, c, n, v, roles):
|
||||||
|
return self._nobody.__of__(self)
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Patch BasicUserFolder with our version of m_validate above.
|
||||||
|
from AccessControl.User import BasicUserFolder
|
||||||
|
BasicUserFolder.validate = validate
|
||||||
|
|
||||||
def tempFile(self):
|
def tempFile(self):
|
||||||
'''A temp file has been created in a temp folder. This method returns
|
'''A temp file has been created in a temp folder. This method returns
|
||||||
|
@ -864,11 +938,16 @@ class ToolMixin(BaseMixin):
|
||||||
def getUserLine(self, user):
|
def getUserLine(self, user):
|
||||||
'''Returns a one-line user info as shown on every page.'''
|
'''Returns a one-line user info as shown on every page.'''
|
||||||
res = [user.getId()]
|
res = [user.getId()]
|
||||||
name = user.getProperty('fullname')
|
|
||||||
if name: res.insert(0, name)
|
|
||||||
rolesToShow = [r for r in user.getRoles() \
|
rolesToShow = [r for r in user.getRoles() \
|
||||||
if r not in ('Authenticated', 'Member')]
|
if r not in ('Authenticated', 'Member')]
|
||||||
if rolesToShow:
|
if rolesToShow:
|
||||||
res.append(', '.join([self.translate(r) for r in rolesToShow]))
|
res.append(', '.join([self.translate(r) for r in rolesToShow]))
|
||||||
return ' | '.join(res)
|
return ' | '.join(res)
|
||||||
|
|
||||||
|
def generateUid(self, className):
|
||||||
|
'''Generates a UID for an instance of p_className.'''
|
||||||
|
name = className.replace('_', '')
|
||||||
|
randomNumber = str(random.random()).split('.')[1]
|
||||||
|
timestamp = ('%f' % time.time()).replace('.', '')
|
||||||
|
return '%s%s%s' % (name, timestamp, randomNumber)
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -15,8 +15,8 @@ from appy.gen.plone25.descriptors import ClassDescriptor
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
class BaseMixin:
|
class BaseMixin:
|
||||||
'''Every Archetype class generated by appy.gen inherits from this class or
|
'''Every Zope class generated by appy.gen inherits from this class or a
|
||||||
a subclass of it.'''
|
subclass of it.'''
|
||||||
_appy_meta_type = 'Class'
|
_appy_meta_type = 'Class'
|
||||||
|
|
||||||
def get_o(self):
|
def get_o(self):
|
||||||
|
@ -31,31 +31,36 @@ class BaseMixin:
|
||||||
initiator=None, initiatorField=None):
|
initiator=None, initiatorField=None):
|
||||||
'''This method creates (if p_created is True) or updates an object.
|
'''This method creates (if p_created is True) or updates an object.
|
||||||
p_values are manipulated versions of those from the HTTP request.
|
p_values are manipulated versions of those from the HTTP request.
|
||||||
In the case of an object creation (p_created is True), p_self is a
|
In the case of an object creation from the web (p_created is True
|
||||||
temporary object created in the request by portal_factory, and this
|
and a REQUEST object is present), p_self is a temporary object
|
||||||
method creates the corresponding final object. In the case of an
|
created in /temp_folder, and this method moves it at its "final"
|
||||||
update, this method simply updates fields of p_self.'''
|
place. In the case of an update, this method simply updates fields
|
||||||
rq = self.REQUEST
|
of p_self.'''
|
||||||
|
rq = getattr(self, 'REQUEST', None)
|
||||||
obj = self
|
obj = self
|
||||||
if created:
|
if created and rq:
|
||||||
# portal_factory creates the final object from the temp object.
|
# Create the final object and put it at the right place.
|
||||||
obj = self.portal_factory.doCreate(self, self.id)
|
tool = self.getTool()
|
||||||
|
id = tool.generateUid(obj.portal_type)
|
||||||
|
if not initiator:
|
||||||
|
folder = tool.getPath('/data')
|
||||||
|
else:
|
||||||
|
if initiator.isPrincipiaFolderish:
|
||||||
|
folder = initiator
|
||||||
|
else:
|
||||||
|
folder = initiator.getParentNode()
|
||||||
|
obj = createObject(folder, id, obj.portal_type, tool.getAppName())
|
||||||
previousData = None
|
previousData = None
|
||||||
if not created: previousData = self.rememberPreviousData()
|
if not created: previousData = obj.rememberPreviousData()
|
||||||
# Perform the change on the object, unless self is a tool being created.
|
# Perform the change on the object
|
||||||
if (obj._appy_meta_type == 'Tool') and created:
|
if rq:
|
||||||
# We do not process form data (=real update on the object) if the
|
|
||||||
# tool itself is being created.
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
# Store in the database the new value coming from the form
|
# Store in the database the new value coming from the form
|
||||||
for appyType in self.getAppyTypes('edit', rq.get('page')):
|
for appyType in self.getAppyTypes('edit', rq.get('page')):
|
||||||
value = getattr(values, appyType.name, None)
|
value = getattr(values, appyType.name, None)
|
||||||
appyType.store(obj, value)
|
appyType.store(obj, value)
|
||||||
if created: obj.unmarkCreationFlag()
|
|
||||||
if previousData:
|
if previousData:
|
||||||
# Keep in history potential changes on historized fields
|
# Keep in history potential changes on historized fields
|
||||||
self.historizeData(previousData)
|
obj.historizeData(previousData)
|
||||||
|
|
||||||
# Manage potential link with an initiator object
|
# Manage potential link with an initiator object
|
||||||
if created and initiator: initiator.appy().link(initiatorField, obj)
|
if created and initiator: initiator.appy().link(initiatorField, obj)
|
||||||
|
@ -69,7 +74,7 @@ class BaseMixin:
|
||||||
appyObject = obj.appy()
|
appyObject = obj.appy()
|
||||||
if hasattr(appyObject, 'onEdit'):
|
if hasattr(appyObject, 'onEdit'):
|
||||||
msg = appyObject.onEdit(created)
|
msg = appyObject.onEdit(created)
|
||||||
obj.reindexObject()
|
obj.reindex()
|
||||||
return obj, msg
|
return obj, msg
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
|
@ -99,9 +104,11 @@ class BaseMixin:
|
||||||
|
|
||||||
def onCreate(self):
|
def onCreate(self):
|
||||||
'''This method is called when a user wants to create a root object in
|
'''This method is called when a user wants to create a root object in
|
||||||
the application folder or an object through a reference field.'''
|
the "data" folder or an object through a reference field. A temporary
|
||||||
|
object is created in /temp_folder and the edit page to it is
|
||||||
|
returned.'''
|
||||||
rq = self.REQUEST
|
rq = self.REQUEST
|
||||||
typeName = rq.get('type_name')
|
className = rq.get('className')
|
||||||
# Create the params to add to the URL we will redirect the user to
|
# Create the params to add to the URL we will redirect the user to
|
||||||
# create the object.
|
# create the object.
|
||||||
urlParams = {'mode':'edit', 'page':'main', 'nav':''}
|
urlParams = {'mode':'edit', 'page':'main', 'nav':''}
|
||||||
|
@ -112,15 +119,12 @@ class BaseMixin:
|
||||||
splitted = rq.get('nav').split('.')
|
splitted = rq.get('nav').split('.')
|
||||||
splitted[-1] = splitted[-2] = str(int(splitted[-1])+1)
|
splitted[-1] = splitted[-2] = str(int(splitted[-1])+1)
|
||||||
urlParams['nav'] = '.'.join(splitted)
|
urlParams['nav'] = '.'.join(splitted)
|
||||||
# Determine base URL
|
# Create a temp object in /temp_folder
|
||||||
baseUrl = self.absolute_url()
|
tool = self.getTool()
|
||||||
if (self._appy_meta_type == 'Tool') and not urlParams['nav']:
|
id = tool.generateUid(className)
|
||||||
# This is the creation of a root object in the app folder
|
appName = tool.getAppName()
|
||||||
baseUrl = self.getAppFolder().absolute_url()
|
obj = createObject(tool.getPath('/temp_folder'), id, className, appName)
|
||||||
objId = self.generateUniqueId(typeName)
|
return self.goto(obj.getUrl(**urlParams))
|
||||||
editUrl = '%s/portal_factory/%s/%s/skyn/edit' % \
|
|
||||||
(baseUrl, typeName, objId)
|
|
||||||
return self.goto(self.getUrl(editUrl, **urlParams))
|
|
||||||
|
|
||||||
def onCreateWithoutForm(self):
|
def onCreateWithoutForm(self):
|
||||||
'''This method is called when a user wants to create a object from a
|
'''This method is called when a user wants to create a object from a
|
||||||
|
@ -175,10 +179,10 @@ class BaseMixin:
|
||||||
|
|
||||||
def onUpdate(self):
|
def onUpdate(self):
|
||||||
'''This method is executed when a user wants to update an object.
|
'''This method is executed when a user wants to update an object.
|
||||||
The object may be a temporary object created by portal_factory in
|
The object may be a temporary object created in /temp_folder.
|
||||||
the request. In this case, the update consists in the creation of
|
In this case, the update consists in moving it to its "final" place.
|
||||||
the "final" object in the database. If the object is not a temporary
|
If the object is not a temporary one, this method updates its
|
||||||
one, this method updates its fields in the database.'''
|
fields in the database.'''
|
||||||
rq = self.REQUEST
|
rq = self.REQUEST
|
||||||
tool = self.getTool()
|
tool = self.getTool()
|
||||||
errorMessage = self.translate(
|
errorMessage = self.translate(
|
||||||
|
@ -244,7 +248,7 @@ class BaseMixin:
|
||||||
# object like a one-shot form and has already been deleted in method
|
# object like a one-shot form and has already been deleted in method
|
||||||
# onEdit), redirect to the main site page.
|
# onEdit), redirect to the main site page.
|
||||||
if not getattr(obj.getParentNode().aq_base, obj.id, None):
|
if not getattr(obj.getParentNode().aq_base, obj.id, None):
|
||||||
obj.unindexObject()
|
obj.unindex()
|
||||||
return self.goto(tool.getSiteUrl(), msg)
|
return self.goto(tool.getSiteUrl(), msg)
|
||||||
# If the user can't access the object anymore, redirect him to the
|
# If the user can't access the object anymore, redirect him to the
|
||||||
# main site page.
|
# main site page.
|
||||||
|
@ -295,16 +299,42 @@ class BaseMixin:
|
||||||
return self.goto(obj.getUrl())
|
return self.goto(obj.getUrl())
|
||||||
return obj.gotoEdit()
|
return obj.gotoEdit()
|
||||||
|
|
||||||
|
def reindex(self, indexes=None, unindex=False):
|
||||||
|
'''Reindexes this object the catalog. If names of indexes are specified
|
||||||
|
in p_indexes, recataloging is limited to those indexes. If p_unindex
|
||||||
|
is True, instead of cataloguing the object, it uncatalogs it.'''
|
||||||
|
url = self.absolute_url_path()
|
||||||
|
catalog = self.getPhysicalRoot().catalog
|
||||||
|
if unindex:
|
||||||
|
method = catalog.uncatalog_object
|
||||||
|
else:
|
||||||
|
method = catalog.catalog_object
|
||||||
|
if indexes:
|
||||||
|
return method(self, url)
|
||||||
|
else:
|
||||||
|
return method(self, url, idxs=indexes)
|
||||||
|
|
||||||
|
def unindex(self, indexes=None):
|
||||||
|
'''Undatalog this object.'''
|
||||||
|
url = self.absolute_url_path()
|
||||||
|
catalog = self.getPhysicalRoot().catalog
|
||||||
|
if indexes:
|
||||||
|
return catalog.catalog_object(self, url)
|
||||||
|
else:
|
||||||
|
return catalog.catalog_object(self, url, idxs=indexes)
|
||||||
|
|
||||||
def say(self, msg, type='info'):
|
def say(self, msg, type='info'):
|
||||||
'''Prints a p_msg in the user interface. p_logLevel may be "info",
|
'''Prints a p_msg in the user interface. p_logLevel may be "info",
|
||||||
"warning" or "error".'''
|
"warning" or "error".'''
|
||||||
mType = type
|
mType = type
|
||||||
|
rq = self.REQUEST
|
||||||
|
if not hasattr(rq, 'messages'):
|
||||||
|
messages = rq.messages = []
|
||||||
|
else:
|
||||||
|
messages = rq.messages
|
||||||
if mType == 'warning': mType = 'warn'
|
if mType == 'warning': mType = 'warn'
|
||||||
elif mType == 'error': mType = 'stop'
|
elif mType == 'error': mType = 'stop'
|
||||||
try:
|
messages.append( (mType, msg) )
|
||||||
self.plone_utils.addPortalMessage(msg, type=mType)
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
self.plone_utils.addPortalMessage(msg.decode('utf-8'), type=mType)
|
|
||||||
|
|
||||||
def log(self, msg, type='info'):
|
def log(self, msg, type='info'):
|
||||||
'''Logs a p_msg in the log file. p_logLevel may be "info", "warning"
|
'''Logs a p_msg in the log file. p_logLevel may be "info", "warning"
|
||||||
|
@ -315,29 +345,15 @@ class BaseMixin:
|
||||||
else: logMethod = logger.info
|
else: logMethod = logger.info
|
||||||
logMethod(msg)
|
logMethod(msg)
|
||||||
|
|
||||||
def getState(self, name=True, initial=False):
|
def do(self):
|
||||||
'''Returns information about the current object state. If p_name is
|
'''Performs some action from the user interface.'''
|
||||||
True, the returned info is the state name. Else, it is the State
|
rq = self.REQUEST
|
||||||
instance. If p_initial is True, instead of returning info about the
|
action = rq['action']
|
||||||
current state, it returns info about the workflow initial state.'''
|
if rq.get('objectUid', None):
|
||||||
wf = self.getWorkflow()
|
obj = self.getTool().getObject(rq['objectUid'])
|
||||||
if initial or not hasattr(self.aq_base, 'workflow_history'):
|
|
||||||
# No workflow information is available (yet) on this object, or
|
|
||||||
# initial state is asked. In both cases, return info about this
|
|
||||||
# initial state.
|
|
||||||
res = 'active'
|
|
||||||
for elem in dir(wf):
|
|
||||||
attr = getattr(wf, elem)
|
|
||||||
if (attr.__class__.__name__ == 'State') and attr.initial:
|
|
||||||
res = elem
|
|
||||||
break
|
|
||||||
else:
|
else:
|
||||||
# Return info about the current object state
|
obj = self
|
||||||
key = self.workflow_history.keys()[0]
|
return obj.getMethod('on'+action)()
|
||||||
res = self.workflow_history[key][-1]['review_state']
|
|
||||||
# Return state name or state definition?
|
|
||||||
if name: return res
|
|
||||||
else: return getattr(wf, res)
|
|
||||||
|
|
||||||
def rememberPreviousData(self):
|
def rememberPreviousData(self):
|
||||||
'''This method is called before updating an object and remembers, for
|
'''This method is called before updating an object and remembers, for
|
||||||
|
@ -351,12 +367,12 @@ class BaseMixin:
|
||||||
|
|
||||||
def addHistoryEvent(self, action, **kw):
|
def addHistoryEvent(self, action, **kw):
|
||||||
'''Adds an event in the object history.'''
|
'''Adds an event in the object history.'''
|
||||||
userId = self.portal_membership.getAuthenticatedMember().getId()
|
userId = self.getUser().getId()
|
||||||
from DateTime import DateTime
|
from DateTime import DateTime
|
||||||
event = {'action': action, 'actor': userId, 'time': DateTime(),
|
event = {'action': action, 'actor': userId, 'time': DateTime(),
|
||||||
'comments': ''}
|
'comments': ''}
|
||||||
event.update(kw)
|
event.update(kw)
|
||||||
if 'review_state' not in event: event['review_state']=self.getState()
|
if 'review_state' not in event: event['review_state'] = self.State()
|
||||||
# Add the event to the history
|
# Add the event to the history
|
||||||
histKey = self.workflow_history.keys()[0]
|
histKey = self.workflow_history.keys()[0]
|
||||||
self.workflow_history[histKey] += (event,)
|
self.workflow_history[histKey] += (event,)
|
||||||
|
@ -423,7 +439,7 @@ class BaseMixin:
|
||||||
for field in self.getAppyTypes('edit', page):
|
for field in self.getAppyTypes('edit', page):
|
||||||
if (field.type == 'String') and (field.format == 3):
|
if (field.type == 'String') and (field.format == 3):
|
||||||
self.REQUEST.set(field.name, '')
|
self.REQUEST.set(field.name, '')
|
||||||
return self.skyn.edit(self)
|
return self.ui.edit(self)
|
||||||
|
|
||||||
def showField(self, name, layoutType='view'):
|
def showField(self, name, layoutType='view'):
|
||||||
'''Must I show field named p_name on this p_layoutType ?'''
|
'''Must I show field named p_name on this p_layoutType ?'''
|
||||||
|
@ -657,7 +673,7 @@ class BaseMixin:
|
||||||
'''Returns information about the states that are related to p_phase.
|
'''Returns information about the states that are related to p_phase.
|
||||||
If p_currentOnly is True, we return the current state, even if not
|
If p_currentOnly is True, we return the current state, even if not
|
||||||
related to p_phase.'''
|
related to p_phase.'''
|
||||||
currentState = self.getState()
|
currentState = self.State()
|
||||||
if currentOnly:
|
if currentOnly:
|
||||||
return [StateDescr(currentState, 'current').get()]
|
return [StateDescr(currentState, 'current').get()]
|
||||||
res = []
|
res = []
|
||||||
|
@ -690,7 +706,7 @@ class BaseMixin:
|
||||||
'''
|
'''
|
||||||
res = []
|
res = []
|
||||||
wf = self.getWorkflow()
|
wf = self.getWorkflow()
|
||||||
currentState = self.getState(name=False)
|
currentState = self.State(name=False)
|
||||||
# Loop on every transition
|
# Loop on every transition
|
||||||
for name in dir(wf):
|
for name in dir(wf):
|
||||||
transition = getattr(wf, name)
|
transition = getattr(wf, name)
|
||||||
|
@ -855,7 +871,7 @@ class BaseMixin:
|
||||||
related data on the object.'''
|
related data on the object.'''
|
||||||
wf = self.getWorkflow()
|
wf = self.getWorkflow()
|
||||||
# Get the initial workflow state
|
# Get the initial workflow state
|
||||||
initialState = self.getState(name=False)
|
initialState = self.State(name=False)
|
||||||
# Create a Transition instance representing the initial transition.
|
# Create a Transition instance representing the initial transition.
|
||||||
initialTransition = Transition((initialState, initialState))
|
initialTransition = Transition((initialState, initialState))
|
||||||
initialTransition.trigger('_init_', self, wf, '')
|
initialTransition.trigger('_init_', self, wf, '')
|
||||||
|
@ -876,7 +892,7 @@ class BaseMixin:
|
||||||
'''Gets the i18n label for p_stateName, or for the current object state
|
'''Gets the i18n label for p_stateName, or for the current object state
|
||||||
if p_stateName is not given. Note that if p_stateName is given, it
|
if p_stateName is not given. Note that if p_stateName is given, it
|
||||||
can also represent the name of a transition.'''
|
can also represent the name of a transition.'''
|
||||||
stateName = stateName or self.getState()
|
stateName = stateName or self.State()
|
||||||
return '%s_%s' % (self.getWorkflow(name=True), stateName)
|
return '%s_%s' % (self.getWorkflow(name=True), stateName)
|
||||||
|
|
||||||
def refreshSecurity(self):
|
def refreshSecurity(self):
|
||||||
|
@ -885,15 +901,15 @@ class BaseMixin:
|
||||||
wf = self.getWorkflow()
|
wf = self.getWorkflow()
|
||||||
try:
|
try:
|
||||||
# Get the state definition of the object's current state.
|
# Get the state definition of the object's current state.
|
||||||
state = getattr(wf, self.getState())
|
state = getattr(wf, self.State())
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# The workflow information for this object does not correspond to
|
# The workflow information for this object does not correspond to
|
||||||
# its current workflow attribution. Add a new fake event
|
# its current workflow attribution. Add a new fake event
|
||||||
# representing passage of this object to the initial state of his
|
# representing passage of this object to the initial state of his
|
||||||
# currently attributed workflow.
|
# currently attributed workflow.
|
||||||
stateName = self.getState(name=True, initial=True)
|
stateName = self.State(name=True, initial=True)
|
||||||
self.addHistoryEvent(None, review_state=stateName)
|
self.addHistoryEvent(None, review_state=stateName)
|
||||||
state = self.getState(name=False, initial=True)
|
state = self.State(name=False, initial=True)
|
||||||
self.log('Wrong workflow info for a "%s"; is not in state "%s".' % \
|
self.log('Wrong workflow info for a "%s"; is not in state "%s".' % \
|
||||||
(self.meta_type, stateName))
|
(self.meta_type, stateName))
|
||||||
# Update permission attributes on the object if required
|
# Update permission attributes on the object if required
|
||||||
|
@ -939,9 +955,11 @@ class BaseMixin:
|
||||||
'''Executes action with p_fieldName on this object.'''
|
'''Executes action with p_fieldName on this object.'''
|
||||||
appyType = self.getAppyType(actionName)
|
appyType = self.getAppyType(actionName)
|
||||||
actionRes = appyType(self.appy())
|
actionRes = appyType(self.appy())
|
||||||
if self.getParentNode().get(self.id):
|
parent = self.getParentNode()
|
||||||
|
parentAq = getattr(parent, 'aq_base', parent)
|
||||||
|
if not hasattr(parentAq, self.id):
|
||||||
# Else, it means that the action has led to self's deletion.
|
# Else, it means that the action has led to self's deletion.
|
||||||
self.reindexObject()
|
self.reindex()
|
||||||
return appyType.result, actionRes
|
return appyType.result, actionRes
|
||||||
|
|
||||||
def onExecuteAppyAction(self):
|
def onExecuteAppyAction(self):
|
||||||
|
@ -982,8 +1000,8 @@ class BaseMixin:
|
||||||
# the user.
|
# the user.
|
||||||
return self.goto(msg)
|
return self.goto(msg)
|
||||||
|
|
||||||
def do(self, transitionName, comment='', doAction=True, doNotify=True,
|
def trigger(self, transitionName, comment='', doAction=True, doNotify=True,
|
||||||
doHistory=True, doSay=True):
|
doHistory=True, doSay=True):
|
||||||
'''Triggers transition named p_transitionName.'''
|
'''Triggers transition named p_transitionName.'''
|
||||||
# Check that this transition exists.
|
# Check that this transition exists.
|
||||||
wf = self.getWorkflow()
|
wf = self.getWorkflow()
|
||||||
|
@ -998,12 +1016,12 @@ class BaseMixin:
|
||||||
transition.trigger(transitionName, self, wf, comment, doAction=doAction,
|
transition.trigger(transitionName, self, wf, comment, doAction=doAction,
|
||||||
doNotify=doNotify, doHistory=doHistory, doSay=doSay)
|
doNotify=doNotify, doHistory=doHistory, doSay=doSay)
|
||||||
|
|
||||||
def onDo(self):
|
def onTrigger(self):
|
||||||
'''This method is called whenever a user wants to trigger a workflow
|
'''This method is called whenever a user wants to trigger a workflow
|
||||||
transition on an object.'''
|
transition on an object.'''
|
||||||
rq = self.REQUEST
|
rq = self.REQUEST
|
||||||
self.do(rq['workflow_action'], comment=rq.get('comment', ''))
|
self.trigger(rq['workflow_action'], comment=rq.get('comment', ''))
|
||||||
self.reindexObject()
|
self.reindex()
|
||||||
return self.goto(self.getUrl(rq['HTTP_REFERER']))
|
return self.goto(self.getUrl(rq['HTTP_REFERER']))
|
||||||
|
|
||||||
def fieldValueSelected(self, fieldName, vocabValue, dbValue):
|
def fieldValueSelected(self, fieldName, vocabValue, dbValue):
|
||||||
|
@ -1079,7 +1097,8 @@ class BaseMixin:
|
||||||
'''Returns a wrapper object allowing to manipulate p_self the Appy
|
'''Returns a wrapper object allowing to manipulate p_self the Appy
|
||||||
way.'''
|
way.'''
|
||||||
# Create the dict for storing Appy wrapper on the REQUEST if needed.
|
# Create the dict for storing Appy wrapper on the REQUEST if needed.
|
||||||
rq = self.REQUEST
|
rq = getattr(self, 'REQUEST', None)
|
||||||
|
if not rq: rq = Object()
|
||||||
if not hasattr(rq, 'appyWrappers'): rq.appyWrappers = {}
|
if not hasattr(rq, 'appyWrappers'): rq.appyWrappers = {}
|
||||||
# Return the Appy wrapper from rq.appyWrappers if already there
|
# Return the Appy wrapper from rq.appyWrappers if already there
|
||||||
uid = self.UID()
|
uid = self.UID()
|
||||||
|
@ -1088,7 +1107,69 @@ class BaseMixin:
|
||||||
wrapper = self.wrapperClass(self)
|
wrapper = self.wrapperClass(self)
|
||||||
rq.appyWrappers[uid] = wrapper
|
rq.appyWrappers[uid] = wrapper
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
# Standard methods for computing values of standard Appy indexes
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
def UID(self):
|
||||||
|
'''Returns the unique identifier for this object.'''
|
||||||
|
return self._at_uid
|
||||||
|
|
||||||
|
def Title(self):
|
||||||
|
'''Returns the title for this object.'''
|
||||||
|
title = self.getAppyType('title')
|
||||||
|
if title: return title.getValue(self)
|
||||||
|
return self.id
|
||||||
|
|
||||||
|
def SortableTitle(self):
|
||||||
|
'''Returns the title as must be stored in index "SortableTitle".'''
|
||||||
|
return self.Title()
|
||||||
|
|
||||||
|
def SearchableText(self):
|
||||||
|
'''This method concatenates the content of every field with
|
||||||
|
searchable=True for indexing purposes.'''
|
||||||
|
res = []
|
||||||
|
for field in self.getAllAppyTypes():
|
||||||
|
if not field.searchable: continue
|
||||||
|
res.append(field.getIndexValue(self, forSearch=True))
|
||||||
|
return res
|
||||||
|
|
||||||
|
def Creator(self):
|
||||||
|
'''Who create this object?'''
|
||||||
|
return self.creator
|
||||||
|
|
||||||
|
def Created(self):
|
||||||
|
'''When was this object created ?'''
|
||||||
|
return self.created
|
||||||
|
|
||||||
|
def State(self, name=True, initial=False):
|
||||||
|
'''Returns information about the current object state. If p_name is
|
||||||
|
True, the returned info is the state name. Else, it is the State
|
||||||
|
instance. If p_initial is True, instead of returning info about the
|
||||||
|
current state, it returns info about the workflow initial state.'''
|
||||||
|
wf = self.getWorkflow()
|
||||||
|
if initial or not hasattr(self.aq_base, 'workflow_history'):
|
||||||
|
# No workflow information is available (yet) on this object, or
|
||||||
|
# initial state is asked. In both cases, return info about this
|
||||||
|
# initial state.
|
||||||
|
res = 'active'
|
||||||
|
for elem in dir(wf):
|
||||||
|
attr = getattr(wf, elem)
|
||||||
|
if (attr.__class__.__name__ == 'State') and attr.initial:
|
||||||
|
res = elem
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# Return info about the current object state
|
||||||
|
key = self.workflow_history.keys()[0]
|
||||||
|
res = self.workflow_history[key][-1]['review_state']
|
||||||
|
# Return state name or state definition?
|
||||||
|
if name: return res
|
||||||
|
else: return getattr(wf, res)
|
||||||
|
|
||||||
|
def ClassName(self):
|
||||||
|
'''Returns the name of the (Zope) class for self.'''
|
||||||
|
return self.portal_type
|
||||||
|
|
||||||
def _appy_showState(self, workflow, stateShow):
|
def _appy_showState(self, workflow, stateShow):
|
||||||
'''Must I show a state whose "show value" is p_stateShow?'''
|
'''Must I show a state whose "show value" is p_stateShow?'''
|
||||||
if callable(stateShow):
|
if callable(stateShow):
|
||||||
|
@ -1129,11 +1210,6 @@ class BaseMixin:
|
||||||
# Update the permissions
|
# Update the permissions
|
||||||
for permission, creators in allCreators.iteritems():
|
for permission, creators in allCreators.iteritems():
|
||||||
updateRolesForPermission(permission, tuple(creators), folder)
|
updateRolesForPermission(permission, tuple(creators), folder)
|
||||||
# Beyond content-type-specific "add" permissions, creators must also
|
|
||||||
# have the main permission "Add portal content".
|
|
||||||
permission = 'Add portal content'
|
|
||||||
for creators in allCreators.itervalues():
|
|
||||||
updateRolesForPermission(permission, tuple(creators), folder)
|
|
||||||
|
|
||||||
def _appy_getPortalType(self, request):
|
def _appy_getPortalType(self, request):
|
||||||
'''Guess the portal_type of p_self from info about p_self and
|
'''Guess the portal_type of p_self from info about p_self and
|
||||||
|
@ -1162,7 +1238,7 @@ class BaseMixin:
|
||||||
param will not be included in the URL at all).'''
|
param will not be included in the URL at all).'''
|
||||||
# Define the URL suffix
|
# Define the URL suffix
|
||||||
suffix = ''
|
suffix = ''
|
||||||
if mode != 'raw': suffix = '/skyn/%s' % mode
|
if mode != 'raw': suffix = '/ui/%s' % mode
|
||||||
# Define base URL if omitted
|
# Define base URL if omitted
|
||||||
if not base:
|
if not base:
|
||||||
base = self.absolute_url() + suffix
|
base = self.absolute_url() + suffix
|
||||||
|
@ -1171,7 +1247,7 @@ class BaseMixin:
|
||||||
if '?' in base: base = base[:base.index('?')]
|
if '?' in base: base = base[:base.index('?')]
|
||||||
base = base.strip('/')
|
base = base.strip('/')
|
||||||
for mode in ('view', 'edit'):
|
for mode in ('view', 'edit'):
|
||||||
suffix = 'skyn/%s' % mode
|
suffix = 'ui/%s' % mode
|
||||||
if base.endswith(suffix):
|
if base.endswith(suffix):
|
||||||
base = base[:-len(suffix)].strip('/')
|
base = base[:-len(suffix)].strip('/')
|
||||||
break
|
break
|
||||||
|
@ -1195,6 +1271,19 @@ class BaseMixin:
|
||||||
params = ''
|
params = ''
|
||||||
return '%s%s' % (base, params)
|
return '%s%s' % (base, params)
|
||||||
|
|
||||||
|
def getUser(self):
|
||||||
|
'''Gets the Zope object representing the authenticated user.'''
|
||||||
|
from AccessControl import getSecurityManager
|
||||||
|
user = getSecurityManager().getUser()
|
||||||
|
if not user:
|
||||||
|
from AccessControl.User import nobody
|
||||||
|
return nobody
|
||||||
|
return user
|
||||||
|
|
||||||
|
def userIsAnon(self):
|
||||||
|
'''Is the currently logged user anonymous ?'''
|
||||||
|
return self.getUser().getUserName() == 'Anonymous User'
|
||||||
|
|
||||||
def getUserLanguage(self):
|
def getUserLanguage(self):
|
||||||
'''Gets the language (code) of the current user.'''
|
'''Gets the language (code) of the current user.'''
|
||||||
# Try first the "LANGUAGE" key from the request
|
# Try first the "LANGUAGE" key from the request
|
||||||
|
@ -1291,12 +1380,11 @@ class BaseMixin:
|
||||||
layout = defaultPageLayouts[layoutType]
|
layout = defaultPageLayouts[layoutType]
|
||||||
return layout.get()
|
return layout.get()
|
||||||
|
|
||||||
def getPageTemplate(self, skyn, templateName):
|
def getPageTemplate(self, ui, templateName):
|
||||||
'''Returns, in the skyn folder, the page template corresponding to
|
'''Returns, in the ui folder, the page template corresponding to
|
||||||
p_templateName.'''
|
p_templateName.'''
|
||||||
res = skyn
|
res = ui
|
||||||
for name in templateName.split('/'):
|
for name in templateName.split('/'): res = getattr(res, name)
|
||||||
res = res.get(name)
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def download(self):
|
def download(self):
|
||||||
|
@ -1316,15 +1404,6 @@ class BaseMixin:
|
||||||
response.setHeader('Expires', 'Thu, 11 Dec 1975 12:05:05 GMT')
|
response.setHeader('Expires', 'Thu, 11 Dec 1975 12:05:05 GMT')
|
||||||
return theFile.index_html(self.REQUEST, self.REQUEST.RESPONSE)
|
return theFile.index_html(self.REQUEST, self.REQUEST.RESPONSE)
|
||||||
|
|
||||||
def SearchableText(self):
|
|
||||||
'''This method concatenates the content of every field with
|
|
||||||
searchable=True for indexing purposes.'''
|
|
||||||
res = []
|
|
||||||
for field in self.getAllAppyTypes():
|
|
||||||
if not field.searchable: continue
|
|
||||||
res.append(field.getIndexValue(self, forSearch=True))
|
|
||||||
return res
|
|
||||||
|
|
||||||
def allows(self, permission):
|
def allows(self, permission):
|
||||||
'''Has the logged user p_permission on p_self ?'''
|
'''Has the logged user p_permission on p_self ?'''
|
||||||
# Get first the roles that have this permission on p_self.
|
# Get first the roles that have this permission on p_self.
|
||||||
|
@ -1332,8 +1411,10 @@ class BaseMixin:
|
||||||
if not hasattr(self.aq_base, zopeAttr): return
|
if not hasattr(self.aq_base, zopeAttr): return
|
||||||
allowedRoles = getattr(self.aq_base, zopeAttr)
|
allowedRoles = getattr(self.aq_base, zopeAttr)
|
||||||
# Has the user one of those roles?
|
# Has the user one of those roles?
|
||||||
user = self.portal_membership.getAuthenticatedMember()
|
user = self.getUser()
|
||||||
ids = [user.getId()] + user.getGroups()
|
# XXX no groups at present
|
||||||
|
#ids = [user.getId()] + user.getGroups()
|
||||||
|
ids = [user.getId()]
|
||||||
userGlobalRoles = user.getRoles()
|
userGlobalRoles = user.getRoles()
|
||||||
for role in allowedRoles:
|
for role in allowedRoles:
|
||||||
# Has the user this role ? Check in the local roles first.
|
# Has the user this role ? Check in the local roles first.
|
||||||
|
@ -1347,4 +1428,11 @@ class BaseMixin:
|
||||||
field p_name.'''
|
field p_name.'''
|
||||||
return 'tinyMCE.init({\nmode : "textareas",\ntheme : "simple",\n' \
|
return 'tinyMCE.init({\nmode : "textareas",\ntheme : "simple",\n' \
|
||||||
'elements : "%s",\neditor_selector : "rich_%s"\n});'% (name,name)
|
'elements : "%s",\neditor_selector : "rich_%s"\n});'% (name,name)
|
||||||
|
|
||||||
|
def isTemporary(self):
|
||||||
|
'''Is this object temporary ?'''
|
||||||
|
parent = self.getParentNode()
|
||||||
|
if not parent: # Is propably being created through code
|
||||||
|
return False
|
||||||
|
return parent.getId() == 'temp_folder'
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
## Python Script "do.py"
|
|
||||||
##bind context=context
|
|
||||||
##parameters=action
|
|
||||||
rq = context.REQUEST
|
|
||||||
|
|
||||||
# Get the object impacted by the action.
|
|
||||||
if rq.get('objectUid', None):
|
|
||||||
obj = context.portal_catalog(UID=rq['objectUid'])[0].getObject()
|
|
||||||
else:
|
|
||||||
obj = context.getParentNode() # An appy obj or in some cases the app folder.
|
|
||||||
if obj.portal_type == 'AppyFolder':
|
|
||||||
from Products.CMFCore.utils import getToolByName
|
|
||||||
portal = getToolByName(obj, 'portal_url').getPortalObject()
|
|
||||||
obj = portal.get('portal_%s' % obj.id.lower()) # The tool
|
|
||||||
return obj.getMethod('on'+action)()
|
|
|
@ -1,17 +0,0 @@
|
||||||
<tal:main define="tool context/getTool">
|
|
||||||
<html metal:use-macro="context/skyn/template/macros/main">
|
|
||||||
<metal:fill fill-slot="content"
|
|
||||||
tal:define="contextObj python:context.getParentNode();
|
|
||||||
portal_type python:here.getPortalTypeName().lower().replace(' ', '_');
|
|
||||||
errors python:req.get('errors', {});
|
|
||||||
layoutType python:'view';
|
|
||||||
layout python: contextObj.getPageLayout(layoutType);
|
|
||||||
phaseInfo python: contextObj.getAppyPhases(currentOnly=True, layoutType='view');
|
|
||||||
page req/page|python:'main';
|
|
||||||
phase phaseInfo/name;">
|
|
||||||
<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>
|
|
||||||
</html>
|
|
||||||
</tal:main>
|
|
|
@ -1,34 +1,43 @@
|
||||||
<!codeHeader!>
|
<!codeHeader!>
|
||||||
|
from OFS.SimpleItem import SimpleItem
|
||||||
|
from OFS.Folder import Folder
|
||||||
|
from appy.gen.utils import createObject
|
||||||
from AccessControl import ClassSecurityInfo
|
from AccessControl import ClassSecurityInfo
|
||||||
from DateTime import DateTime
|
import Products.<!applicationName!>.config as cfg
|
||||||
from Products.Archetypes.atapi import *
|
|
||||||
import Products.<!applicationName!>.config
|
|
||||||
from Products.CMFCore.utils import UniqueObject
|
|
||||||
from appy.gen.plone25.mixins import BaseMixin
|
from appy.gen.plone25.mixins import BaseMixin
|
||||||
from appy.gen.plone25.mixins.ToolMixin import ToolMixin
|
from appy.gen.plone25.mixins.ToolMixin import ToolMixin
|
||||||
from Extensions.appyWrappers import <!genClassName!>_Wrapper
|
from Extensions.appyWrappers import <!genClassName!>_Wrapper as Wrapper
|
||||||
<!imports!>
|
|
||||||
|
def manage_add<!genClassName!>(self, id, title='', REQUEST=None):
|
||||||
|
'''Creates instances of this class.'''
|
||||||
|
createObject(self, id, '<!genClassName!>', '<!applicationName!>')
|
||||||
|
if REQUEST is not None: return self.manage_main(self, REQUEST)
|
||||||
|
|
||||||
class <!genClassName!>(<!parents!>):
|
class <!genClassName!>(<!parents!>):
|
||||||
'''<!classDoc!>'''
|
'''<!classDoc!>'''
|
||||||
security = ClassSecurityInfo()
|
security = ClassSecurityInfo()
|
||||||
__implements__ = <!implements!>
|
|
||||||
archetype_name = '<!genClassName!>'
|
|
||||||
meta_type = '<!genClassName!>'
|
meta_type = '<!genClassName!>'
|
||||||
portal_type = '<!genClassName!>'
|
portal_type = '<!genClassName!>'
|
||||||
allowed_content_types = ()
|
allowed_content_types = ()
|
||||||
filter_content_types = 0
|
filter_content_types = 0
|
||||||
global_allow = <!global_allow!>
|
global_allow = 1
|
||||||
immediate_view = 'skyn/view'
|
icon = "ui/<!icon!>"
|
||||||
default_view = 'skyn/view'
|
wrapperClass = Wrapper
|
||||||
suppl_views = ()
|
|
||||||
typeDescription = '<!genClassName!>'
|
|
||||||
typeDescMsgId = '<!genClassName!>'
|
|
||||||
i18nDomain = '<!applicationName!>'
|
|
||||||
wrapperClass = <!genClassName!>_Wrapper
|
|
||||||
schema = <!baseSchema!>.copy()
|
|
||||||
for elem in dir(<!baseMixin!>):
|
for elem in dir(<!baseMixin!>):
|
||||||
if not elem.startswith('__'): security.declarePublic(elem)
|
if not elem.startswith('__'): security.declarePublic(elem)
|
||||||
<!static!>
|
def getTool(self): return self.getPhysicalRoot().config
|
||||||
<!commonMethods!>
|
def getProductConfig(self): return cfg
|
||||||
|
def index_html(self):
|
||||||
|
"""Redirects to /ui. Transfers the status message if any."""
|
||||||
|
rq = self.REQUEST
|
||||||
|
msg = rq.get('portal_status_message', '')
|
||||||
|
if msg:
|
||||||
|
url = self.getUrl(portal_status_message=msg)
|
||||||
|
else:
|
||||||
|
url = self.getUrl()
|
||||||
|
return rq.RESPONSE.redirect(url)
|
||||||
|
def do(self):
|
||||||
|
'''BaseMixin.do can't be traversed by Zope if this class is the tool.
|
||||||
|
So here, we redefine this method.'''
|
||||||
|
return BaseMixin.do(self)
|
||||||
<!methods!>
|
<!methods!>
|
||||||
<!register!>
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ fieldset {
|
||||||
}
|
}
|
||||||
|
|
||||||
.fakeButton {
|
.fakeButton {
|
||||||
background: #ffd5c0 url(&dtml-portal_url;/skyn/fakeTransition.gif) 5px 1px no-repeat;
|
background: #ffd5c0 url(&dtml-portal_url;/ui/fakeTransition.gif) 5px 1px no-repeat;
|
||||||
padding: 3px 4px 3px 12px;
|
padding: 3px 4px 3px 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,5 @@ def initialize(context):
|
||||||
# I need to do those imports here; else, types and add permissions will not
|
# I need to do those imports here; else, types and add permissions will not
|
||||||
# be registered.
|
# be registered.
|
||||||
classes = [<!classes!>]
|
classes = [<!classes!>]
|
||||||
ZopeInstaller(context, <!applicationName!>Tool.<!applicationName!>Tool,
|
ZopeInstaller(context, config, classes).install()
|
||||||
config, classes).install()
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<!codeHeader!>
|
<!codeHeader!>
|
||||||
import os, os.path, sys, copy
|
import os, os.path, sys, copy
|
||||||
import appy.gen
|
import appy.gen
|
||||||
from Products.CMFCore.permissions import setDefaultRoles
|
|
||||||
import Extensions.appyWrappers as wraps
|
import Extensions.appyWrappers as wraps
|
||||||
<!imports!>
|
<!imports!>
|
||||||
|
|
||||||
|
@ -12,27 +11,11 @@ import Extensions.appyWrappers as wraps
|
||||||
from persistent.list import PersistentList
|
from persistent.list import PersistentList
|
||||||
from zExceptions import BadRequest
|
from zExceptions import BadRequest
|
||||||
from ZPublisher.HTTPRequest import BaseRequest
|
from ZPublisher.HTTPRequest import BaseRequest
|
||||||
try:
|
|
||||||
import CustomizationPolicy
|
|
||||||
except ImportError:
|
|
||||||
CustomizationPolicy = None
|
|
||||||
from OFS.Image import File
|
from OFS.Image import File
|
||||||
from ZPublisher.HTTPRequest import FileUpload
|
from ZPublisher.HTTPRequest import FileUpload
|
||||||
from AccessControl import getSecurityManager
|
from AccessControl import getSecurityManager
|
||||||
from DateTime import DateTime
|
from DateTime import DateTime
|
||||||
from Products.CMFCore import utils as cmfutils
|
|
||||||
from Products.CMFCore.utils import getToolByName
|
|
||||||
from Products.CMFPlone.PloneBatch import Batch
|
|
||||||
from Products.CMFPlone.utils import ToolInit
|
|
||||||
from Products.CMFPlone.interfaces import IPloneSiteRoot
|
|
||||||
from Products.CMFCore import DirectoryView
|
|
||||||
from Products.CMFCore.DirectoryView import manage_addDirectoryView
|
|
||||||
from Products.ExternalMethod.ExternalMethod import ExternalMethod
|
from Products.ExternalMethod.ExternalMethod import ExternalMethod
|
||||||
from Products.Archetypes.Extensions.utils import installTypes
|
|
||||||
from Products.Archetypes.Extensions.utils import install_subskin
|
|
||||||
from Products.Archetypes.config import TOOL_NAME as ARCHETYPETOOLNAME
|
|
||||||
from Products.Archetypes import listTypes, process_types
|
|
||||||
from Products.GenericSetup import EXTENSION, profile_registry
|
|
||||||
from Products.Transience.Transience import TransientObjectContainer
|
from Products.Transience.Transience import TransientObjectContainer
|
||||||
import appy.gen
|
import appy.gen
|
||||||
import logging
|
import logging
|
||||||
|
@ -42,10 +25,8 @@ logger = logging.getLogger('<!applicationName!>')
|
||||||
PROJECTNAME = '<!applicationName!>'
|
PROJECTNAME = '<!applicationName!>'
|
||||||
diskFolder = os.path.dirname(<!applicationName!>.__file__)
|
diskFolder = os.path.dirname(<!applicationName!>.__file__)
|
||||||
defaultAddRoles = [<!defaultAddRoles!>]
|
defaultAddRoles = [<!defaultAddRoles!>]
|
||||||
DEFAULT_ADD_CONTENT_PERMISSION = "Add portal content"
|
|
||||||
ADD_CONTENT_PERMISSIONS = {
|
ADD_CONTENT_PERMISSIONS = {
|
||||||
<!addPermissions!>}
|
<!addPermissions!>}
|
||||||
setDefaultRoles(DEFAULT_ADD_CONTENT_PERMISSION, tuple(defaultAddRoles))
|
|
||||||
|
|
||||||
# Applications classes, in various formats
|
# Applications classes, in various formats
|
||||||
rootClasses = [<!rootClasses!>]
|
rootClasses = [<!rootClasses!>]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<tal:main define="tool python: context.<!toolInstanceName!>">
|
<tal:main define="tool python: context.<!toolInstanceName!>">
|
||||||
<html metal:use-macro="context/skyn/template/macros/main">
|
<html metal:use-macro="context/ui/template/macros/main">
|
||||||
<div metal:fill-slot="content"><!pageContent!></div>
|
<div metal:fill-slot="content"><!pageContent!></div>
|
||||||
</html>
|
</html>
|
||||||
</tal:main>
|
</tal:main>
|
||||||
|
|
Before Width: | Height: | Size: 339 B |
|
@ -38,12 +38,8 @@ class ToolWrapper(AbstractWrapper):
|
||||||
def getInitiator(self):
|
def getInitiator(self):
|
||||||
'''Retrieves the object that triggered the creation of the object
|
'''Retrieves the object that triggered the creation of the object
|
||||||
being currently created (if any).'''
|
being currently created (if any).'''
|
||||||
res = None
|
nav = self.o.REQUEST.get('nav', '')
|
||||||
rq = self.o.REQUEST
|
if nav: return self.getObject(nav.split('.')[1])
|
||||||
if rq.get('nav', ''):
|
|
||||||
initiatorUid = rq['nav'].split('.')[1]
|
|
||||||
res = self.o.portal_catalog(UID=initiatorUid)[0].getObject().appy()
|
|
||||||
return res
|
|
||||||
|
|
||||||
def getObject(self, uid):
|
def getObject(self, uid):
|
||||||
'''Allow to retrieve an object from its unique identifier p_uid.'''
|
'''Allow to retrieve an object from its unique identifier p_uid.'''
|
||||||
|
|
|
@ -25,7 +25,7 @@ class TranslationWrapper(AbstractWrapper):
|
||||||
# This way, the translator sees the HTML tags and can reproduce them
|
# This way, the translator sees the HTML tags and can reproduce them
|
||||||
# in the translation.
|
# in the translation.
|
||||||
url = self.request['URL']
|
url = self.request['URL']
|
||||||
if url.endswith('/skyn/edit') or url.endswith('/skyn/do'):
|
if url.endswith('/ui/edit') or url.endswith('/do'):
|
||||||
sourceMsg = sourceMsg.replace('<','<').replace('>','>')
|
sourceMsg = sourceMsg.replace('<','<').replace('>','>')
|
||||||
sourceMsg = sourceMsg.replace('\n', '<br/>')
|
sourceMsg = sourceMsg.replace('\n', '<br/>')
|
||||||
return '<div class="translationLabel"><acronym title="%s">' \
|
return '<div class="translationLabel"><acronym title="%s">' \
|
||||||
|
|
|
@ -56,38 +56,38 @@ class UserWrapper(AbstractWrapper):
|
||||||
|
|
||||||
def onEdit(self, created):
|
def onEdit(self, created):
|
||||||
self.title = self.firstName + ' ' + self.name
|
self.title = self.firstName + ' ' + self.name
|
||||||
pm = self.o.portal_membership
|
aclUsers = self.o.acl_users
|
||||||
|
login = self.login
|
||||||
if created:
|
if created:
|
||||||
# Create the corresponding Plone user
|
# Create the corresponding Zope user
|
||||||
pm.addMember(self.login, self.password1, ('Member',), None)
|
aclUsers._doAddUser(login, self.password1, self.roles, ())
|
||||||
# Remove our own password copies
|
# Remove our own password copies
|
||||||
self.password1 = self.password2 = ''
|
self.password1 = self.password2 = ''
|
||||||
# Perform updates on the corresponding Plone user
|
# Perform updates on the corresponding Plone user
|
||||||
ploneUser = self.o.portal_membership.getMemberById(self.login)
|
zopeUser = aclUsers.getUserById(login)
|
||||||
ploneUser.setMemberProperties({'fullname': self.title})
|
|
||||||
# This object must be owned by its Plone user
|
# This object must be owned by its Plone user
|
||||||
if 'Owner' not in self.o.get_local_roles_for_userid(self.login):
|
if 'Owner' not in self.o.get_local_roles_for_userid(login):
|
||||||
self.o.manage_addLocalRoles(self.login, ('Owner',))
|
self.o.manage_addLocalRoles(login, ('Owner',))
|
||||||
# Change group membership according to self.roles. Indeed, instead of
|
# Change group membership according to self.roles. Indeed, instead of
|
||||||
# granting roles directly to the user, we will add the user to a
|
# granting roles directly to the user, we will add the user to a
|
||||||
# Appy-created group having this role.
|
# Appy-created group having this role.
|
||||||
userRoles = self.roles
|
userRoles = self.roles
|
||||||
userGroups = ploneUser.getGroups()
|
#userGroups = zopeUser.getGroups()
|
||||||
for role in self.o.getProductConfig().grantableRoles:
|
# for role in self.o.getProductConfig().grantableRoles:
|
||||||
# Retrieve the group corresponding to this role
|
# # Retrieve the group corresponding to this role
|
||||||
groupName = '%s_group' % role
|
# groupName = '%s_group' % role
|
||||||
if role == 'Manager': groupName = 'Administrators'
|
# if role == 'Manager': groupName = 'Administrators'
|
||||||
elif role == 'Reviewer': groupName = 'Reviewers'
|
# elif role == 'Reviewer': groupName = 'Reviewers'
|
||||||
group = self.o.portal_groups.getGroupById(groupName)
|
# group = self.o.portal_groups.getGroupById(groupName)
|
||||||
# Add or remove the user from this group according to its role(s).
|
# # Add or remove the user from this group according to its role(s).
|
||||||
if role in userRoles:
|
# if role in userRoles:
|
||||||
# Add the user if not already present in the group
|
# # Add the user if not already present in the group
|
||||||
if groupName not in userGroups:
|
# if groupName not in userGroups:
|
||||||
group.addMember(self.login)
|
# group.addMember(self.login)
|
||||||
else:
|
# else:
|
||||||
# Remove the user if it was in the corresponding group
|
# # Remove the user if it was in the corresponding group
|
||||||
if groupName in userGroups:
|
# if groupName in userGroups:
|
||||||
group.removeMember(self.login)
|
# group.removeMember(self.login)
|
||||||
return self._callCustom('onEdit', created)
|
return self._callCustom('onEdit', created)
|
||||||
|
|
||||||
def onDelete(self):
|
def onDelete(self):
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
developer the real classes used by the underlying web framework.'''
|
developer the real classes used by the underlying web framework.'''
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
import os, os.path, time, mimetypes, random
|
import os, os.path, mimetypes
|
||||||
import appy.pod
|
import appy.pod
|
||||||
from appy.gen import Type, Search, Ref, String
|
from appy.gen import Type, Search, Ref, String
|
||||||
from appy.gen.utils import sequenceTypes
|
from appy.gen.utils import sequenceTypes, createObject
|
||||||
from appy.shared.utils import getOsTempFolder, executeCommand, normalizeString
|
from appy.shared.utils import getOsTempFolder, executeCommand, normalizeString
|
||||||
from appy.shared.xml_parser import XmlMarshaller
|
from appy.shared.xml_parser import XmlMarshaller
|
||||||
from appy.shared.csv_parser import CsvMarshaller
|
from appy.shared.csv_parser import CsvMarshaller
|
||||||
|
@ -43,7 +43,7 @@ class AbstractWrapper(object):
|
||||||
elif name == 'uid': return self.o.UID()
|
elif name == 'uid': return self.o.UID()
|
||||||
elif name == 'klass': return self.__class__.__bases__[-1]
|
elif name == 'klass': return self.__class__.__bases__[-1]
|
||||||
elif name == 'url': return self.o.absolute_url()
|
elif name == 'url': return self.o.absolute_url()
|
||||||
elif name == 'state': return self.o.getState()
|
elif name == 'state': return self.o.State()
|
||||||
elif name == 'stateLabel':
|
elif name == 'stateLabel':
|
||||||
o = self.o
|
o = self.o
|
||||||
appName = o.getProductConfig().PROJECTNAME
|
appName = o.getProductConfig().PROJECTNAME
|
||||||
|
@ -53,7 +53,7 @@ class AbstractWrapper(object):
|
||||||
key = o.workflow_history.keys()[0]
|
key = o.workflow_history.keys()[0]
|
||||||
return o.workflow_history[key]
|
return o.workflow_history[key]
|
||||||
elif name == 'user':
|
elif name == 'user':
|
||||||
return self.o.portal_membership.getAuthenticatedMember()
|
return self.o.getUser()
|
||||||
elif name == 'fields': return self.o.getAllAppyTypes()
|
elif name == 'fields': return self.o.getAllAppyTypes()
|
||||||
# Now, let's try to return a real attribute.
|
# Now, let's try to return a real attribute.
|
||||||
res = object.__getattribute__(self, name)
|
res = object.__getattribute__(self, name)
|
||||||
|
@ -100,40 +100,36 @@ class AbstractWrapper(object):
|
||||||
'''Sorts referred elements linked to p_self via p_fieldName according
|
'''Sorts referred elements linked to p_self via p_fieldName according
|
||||||
to a given p_sortKey which must be an attribute set on referred
|
to a given p_sortKey which must be an attribute set on referred
|
||||||
objects ("title", by default).'''
|
objects ("title", by default).'''
|
||||||
selfO = self.o
|
refs = getattr(self.o, fieldName, None)
|
||||||
refs = getattr(selfO, fieldName, None)
|
|
||||||
if not refs: return
|
if not refs: return
|
||||||
c = selfO.portal_catalog
|
tool = self.tool
|
||||||
refs.sort(lambda x,y: \
|
refs.sort(lambda x,y: cmp(getattr(tool.getObject(x), sortKey),
|
||||||
cmp(getattr(c(UID=x)[0].getObject().appy(), sortKey),
|
getattr(tool.getObject(y), sortKey)))
|
||||||
getattr(c(UID=y)[0].getObject().appy(), sortKey)))
|
if reverse: refs.reverse()
|
||||||
if reverse:
|
|
||||||
refs.reverse()
|
|
||||||
|
|
||||||
def create(self, fieldNameOrClass, **kwargs):
|
def create(self, fieldNameOrClass, **kwargs):
|
||||||
'''If p_fieldNameOfClass is the name of a field, this method allows to
|
'''If p_fieldNameOrClass is the name of a field, this method allows to
|
||||||
create an object and link it to the current one (self) through
|
create an object and link it to the current one (self) through
|
||||||
reference field named p_fieldName.
|
reference field named p_fieldName.
|
||||||
If p_fieldNameOrClass is a class from the gen-application, it must
|
If p_fieldNameOrClass is a class from the gen-application, it must
|
||||||
correspond to a root class and this method allows to create a
|
correspond to a root class and this method allows to create a
|
||||||
root object in the application folder.'''
|
root object in the application folder.'''
|
||||||
isField = isinstance(fieldNameOrClass, basestring)
|
isField = isinstance(fieldNameOrClass, basestring)
|
||||||
|
tool = self.tool.o
|
||||||
# Determine the portal type of the object to create
|
# Determine the portal type of the object to create
|
||||||
if isField:
|
if isField:
|
||||||
fieldName = idPrefix = fieldNameOrClass
|
fieldName = fieldNameOrClass
|
||||||
appyType = self.o.getAppyType(fieldName)
|
appyType = self.o.getAppyType(fieldName)
|
||||||
portalType = self.tool.o.getPortalType(appyType.klass)
|
portalType = tool.getPortalType(appyType.klass)
|
||||||
else:
|
else:
|
||||||
klass = fieldNameOrClass
|
klass = fieldNameOrClass
|
||||||
idPrefix = klass.__name__
|
portalType = tool.getPortalType(klass)
|
||||||
portalType = self.tool.o.getPortalType(klass)
|
|
||||||
# Determine object id
|
# Determine object id
|
||||||
if kwargs.has_key('id'):
|
if kwargs.has_key('id'):
|
||||||
objId = kwargs['id']
|
objId = kwargs['id']
|
||||||
del kwargs['id']
|
del kwargs['id']
|
||||||
else:
|
else:
|
||||||
objId = '%s.%f.%s' % (idPrefix, time.time(),
|
objId = tool.generateUid(portalType)
|
||||||
str(random.random()).split('.')[1])
|
|
||||||
# Determine if object must be created from external data
|
# Determine if object must be created from external data
|
||||||
externalData = None
|
externalData = None
|
||||||
if kwargs.has_key('_data'):
|
if kwargs.has_key('_data'):
|
||||||
|
@ -141,36 +137,27 @@ class AbstractWrapper(object):
|
||||||
del kwargs['_data']
|
del kwargs['_data']
|
||||||
# Where must I create the object?
|
# Where must I create the object?
|
||||||
if not isField:
|
if not isField:
|
||||||
folder = self.o.getTool().getAppFolder()
|
folder = tool.getPath('/data')
|
||||||
else:
|
else:
|
||||||
if hasattr(self, 'folder') and self.folder:
|
if hasattr(self, 'folder') and self.folder:
|
||||||
folder = self.o
|
folder = self.o
|
||||||
else:
|
else:
|
||||||
folder = self.o.getParentNode()
|
folder = self.o.getParentNode()
|
||||||
# Create the object
|
# Create the object
|
||||||
# -------------------- Try to replace invokeFactory --------------------
|
zopeObj = createObject(folder, objId,portalType, tool.getAppName())
|
||||||
#folder._objects = folder._objects + ({'id':id,'meta_type':portalType},)
|
appyObj = zopeObj.appy()
|
||||||
#folder._setOb(id, ob)
|
|
||||||
#ploneObj = self._getOb(id)
|
|
||||||
#ob._setPortalTypeName(self.getId())
|
|
||||||
#ob.notifyWorkflowCreated()
|
|
||||||
# + Check what's done in Archetypes/ClassGen.py in m_genCtor
|
|
||||||
# ------------------------------ Try end -------------------------------
|
|
||||||
folder.invokeFactory(portalType, objId)
|
|
||||||
ploneObj = getattr(folder, objId)
|
|
||||||
appyObj = ploneObj.appy()
|
|
||||||
# Set object attributes
|
# Set object attributes
|
||||||
for attrName, attrValue in kwargs.iteritems():
|
for attrName, attrValue in kwargs.iteritems():
|
||||||
setattr(appyObj, attrName, attrValue)
|
setattr(appyObj, attrName, attrValue)
|
||||||
if isField:
|
if isField:
|
||||||
# Link the object to this one
|
# Link the object to this one
|
||||||
appyType.linkObject(self.o, ploneObj)
|
appyType.linkObject(self.o, zopeObj)
|
||||||
ploneObj._appy_managePermissions()
|
zopeObj._appy_managePermissions()
|
||||||
# Call custom initialization
|
# Call custom initialization
|
||||||
if externalData: param = externalData
|
if externalData: param = externalData
|
||||||
else: param = True
|
else: param = True
|
||||||
if hasattr(appyObj, 'onEdit'): appyObj.onEdit(param)
|
if hasattr(appyObj, 'onEdit'): appyObj.onEdit(param)
|
||||||
ploneObj.reindexObject()
|
zopeObj.reindex()
|
||||||
return appyObj
|
return appyObj
|
||||||
|
|
||||||
def freeze(self, fieldName, doAction=False):
|
def freeze(self, fieldName, doAction=False):
|
||||||
|
@ -222,8 +209,8 @@ class AbstractWrapper(object):
|
||||||
doHistory=True):
|
doHistory=True):
|
||||||
'''This method allows to trigger on p_self a workflow p_transition
|
'''This method allows to trigger on p_self a workflow p_transition
|
||||||
programmatically. See doc in self.o.do.'''
|
programmatically. See doc in self.o.do.'''
|
||||||
return self.o.do(transition, comment, doAction=doAction,
|
return self.o.trigger(transition, comment, doAction=doAction,
|
||||||
doNotify=doNotify, doHistory=doHistory, doSay=False)
|
doNotify=doNotify, doHistory=doHistory, doSay=False)
|
||||||
|
|
||||||
def log(self, message, type='info'): return self.o.log(message, type)
|
def log(self, message, type='info'): return self.o.log(message, type)
|
||||||
def say(self, message, type='info'): return self.o.say(message, type)
|
def say(self, message, type='info'): return self.o.say(message, type)
|
||||||
|
@ -242,25 +229,27 @@ class AbstractWrapper(object):
|
||||||
p_maxResults. If p_noSecurity is specified, you get all objects,
|
p_maxResults. If p_noSecurity is specified, you get all objects,
|
||||||
even if the logged user does not have the permission to view it.'''
|
even if the logged user does not have the permission to view it.'''
|
||||||
# Find the content type corresponding to p_klass
|
# Find the content type corresponding to p_klass
|
||||||
contentType = self.tool.o.getPortalType(klass)
|
tool = self.tool.o
|
||||||
|
contentType = tool.getPortalType(klass)
|
||||||
# Create the Search object
|
# Create the Search object
|
||||||
search = Search('customSearch', sortBy=sortBy, **fields)
|
search = Search('customSearch', sortBy=sortBy, **fields)
|
||||||
if not maxResults:
|
if not maxResults:
|
||||||
maxResults = 'NO_LIMIT'
|
maxResults = 'NO_LIMIT'
|
||||||
# If I let maxResults=None, only a subset of the results will be
|
# If I let maxResults=None, only a subset of the results will be
|
||||||
# returned by method executeResult.
|
# returned by method executeResult.
|
||||||
res = self.tool.o.executeQuery(contentType, search=search,
|
res = tool.executeQuery(contentType, search=search,
|
||||||
maxResults=maxResults, noSecurity=noSecurity)
|
maxResults=maxResults, noSecurity=noSecurity)
|
||||||
return [o.appy() for o in res['objects']]
|
return [o.appy() for o in res['objects']]
|
||||||
|
|
||||||
def count(self, klass, noSecurity=False, **fields):
|
def count(self, klass, noSecurity=False, **fields):
|
||||||
'''Identical to m_search above, but returns the number of objects that
|
'''Identical to m_search above, but returns the number of objects that
|
||||||
match the search instead of returning the objects themselves. Use
|
match the search instead of returning the objects themselves. Use
|
||||||
this method instead of writing len(self.search(...)).'''
|
this method instead of writing len(self.search(...)).'''
|
||||||
contentType = self.tool.o.getPortalType(klass)
|
tool = self.tool.o
|
||||||
|
contentType = tool.getPortalType(klass)
|
||||||
search = Search('customSearch', **fields)
|
search = Search('customSearch', **fields)
|
||||||
res = self.tool.o.executeQuery(contentType, search=search,
|
res = tool.executeQuery(contentType, search=search, brainsOnly=True,
|
||||||
brainsOnly=True, noSecurity=noSecurity)
|
noSecurity=noSecurity)
|
||||||
if res: return res._len # It is a LazyMap instance
|
if res: return res._len # It is a LazyMap instance
|
||||||
else: return 0
|
else: return 0
|
||||||
|
|
||||||
|
@ -289,11 +278,12 @@ class AbstractWrapper(object):
|
||||||
|
|
||||||
"for obj in self.search(MyClass,...)"
|
"for obj in self.search(MyClass,...)"
|
||||||
'''
|
'''
|
||||||
contentType = self.tool.o.getPortalType(klass)
|
tool = self.tool.o
|
||||||
|
contentType = tool.getPortalType(klass)
|
||||||
search = Search('customSearch', sortBy=sortBy, **fields)
|
search = Search('customSearch', sortBy=sortBy, **fields)
|
||||||
# Initialize the context variable "ctx"
|
# Initialize the context variable "ctx"
|
||||||
ctx = context
|
ctx = context
|
||||||
for brain in self.tool.o.executeQuery(contentType, search=search, \
|
for brain in tool.executeQuery(contentType, search=search, \
|
||||||
brainsOnly=True, maxResults=maxResults, noSecurity=noSecurity):
|
brainsOnly=True, maxResults=maxResults, noSecurity=noSecurity):
|
||||||
# Get the Appy object from the brain
|
# Get the Appy object from the brain
|
||||||
if noSecurity: method = '_unrestrictedGetObject'
|
if noSecurity: method = '_unrestrictedGetObject'
|
||||||
|
@ -309,7 +299,7 @@ class AbstractWrapper(object):
|
||||||
this object automatically. But if your code modifies other objects,
|
this object automatically. But if your code modifies other objects,
|
||||||
Appy may not know that they must be reindexed, too. So use this
|
Appy may not know that they must be reindexed, too. So use this
|
||||||
method in those cases.'''
|
method in those cases.'''
|
||||||
self.o.reindexObject()
|
self.o.reindex()
|
||||||
|
|
||||||
def export(self, at='string', format='xml', include=None, exclude=None):
|
def export(self, at='string', format='xml', include=None, exclude=None):
|
||||||
'''Creates an "exportable" version of this object. p_format is "xml" by
|
'''Creates an "exportable" version of this object. p_format is "xml" by
|
||||||
|
|
|
@ -11,10 +11,10 @@
|
||||||
page req/page;
|
page req/page;
|
||||||
macro req/macro;
|
macro req/macro;
|
||||||
action req/action|nothing;
|
action req/action|nothing;
|
||||||
user contextObj/portal_membership/getAuthenticatedMember;
|
user contextObj/getUser;
|
||||||
app tool/getApp;
|
app tool/getApp;
|
||||||
appUrl app/absolute_url;
|
appUrl app/absolute_url;
|
||||||
template python: contextObj.getPageTemplate(app.skyn, page);
|
template python: contextObj.getPageTemplate(app.ui, page);
|
||||||
x python: resp.setHeader('Content-Type','text/html;;charset=utf-8');
|
x python: resp.setHeader('Content-Type','text/html;;charset=utf-8');
|
||||||
x python: resp.setHeader('Expires', 'Mon, 11 Dec 1975 12:05:05 GMT');
|
x python: resp.setHeader('Expires', 'Mon, 11 Dec 1975 12:05:05 GMT');
|
||||||
x python: resp.setHeader('Content-Language', req.get('language', 'en'));
|
x python: resp.setHeader('Content-Language', req.get('language', 'en'));
|
|
@ -29,7 +29,7 @@ label { font-weight: 600; font-style: italic; line-height: 1.4em;}
|
||||||
legend { padding-bottom: 2px; padding-right: 3px; color: black;}
|
legend { padding-bottom: 2px; padding-right: 3px; color: black;}
|
||||||
ul { line-height: 1.2em; margin: 0 0 0.2em 0.6em; padding: 0;
|
ul { line-height: 1.2em; margin: 0 0 0.2em 0.6em; padding: 0;
|
||||||
list-style: none outside none;}
|
list-style: none outside none;}
|
||||||
li { margin: 0; background-image: url("skyn/li.gif"); padding-left: 10px;
|
li { margin: 0; background-image: url("ui/li.gif"); padding-left: 10px;
|
||||||
background-repeat: no-repeat; background-position: 0 4px;}
|
background-repeat: no-repeat; background-position: 0 4px;}
|
||||||
img {border: 0;}
|
img {border: 0;}
|
||||||
|
|
|
@ -58,7 +58,7 @@ function getAjaxChunk(pos) {
|
||||||
if (xhrObjects[pos].xhr.readyState == 1) {
|
if (xhrObjects[pos].xhr.readyState == 1) {
|
||||||
// The request has been initialized: display the waiting radar
|
// The request has been initialized: display the waiting radar
|
||||||
var hookElem = document.getElementById(hook);
|
var hookElem = document.getElementById(hook);
|
||||||
if (hookElem) hookElem.innerHTML = "<div align=\"center\"><img src=\"skyn/waiting.gif\"/><\/div>";
|
if (hookElem) hookElem.innerHTML = "<div align=\"center\"><img src=\"ui/waiting.gif\"/><\/div>";
|
||||||
}
|
}
|
||||||
if (xhrObjects[pos].xhr.readyState == 4) {
|
if (xhrObjects[pos].xhr.readyState == 4) {
|
||||||
// We have received the HTML chunk
|
// We have received the HTML chunk
|
||||||
|
@ -125,7 +125,7 @@ function askAjaxChunk(hook,mode,url,page,macro,params,beforeSend,onGet) {
|
||||||
if (res) paramsFull = paramsFull + res;
|
if (res) paramsFull = paramsFull + res;
|
||||||
}
|
}
|
||||||
// Construct the URL to call
|
// Construct the URL to call
|
||||||
var urlFull = url + '/skyn/ajax';
|
var urlFull = url + '/ui/ajax';
|
||||||
if (mode == 'GET') {
|
if (mode == 'GET') {
|
||||||
urlFull = urlFull + '?' + paramsFull;
|
urlFull = urlFull + '?' + paramsFull;
|
||||||
}
|
}
|
||||||
|
@ -150,10 +150,10 @@ function askAjaxChunk(hook,mode,url,page,macro,params,beforeSend,onGet) {
|
||||||
|
|
||||||
/* The functions below wrap askAjaxChunk for getting specific content through
|
/* The functions below wrap askAjaxChunk for getting specific content through
|
||||||
an Ajax request. */
|
an Ajax request. */
|
||||||
function askQueryResult(hookId, objectUrl, contentType, searchName,
|
function askQueryResult(hookId, objectUrl, className, searchName,
|
||||||
startNumber, sortKey, sortOrder, filterKey) {
|
startNumber, sortKey, sortOrder, filterKey) {
|
||||||
// Sends an Ajax request for getting the result of a query.
|
// Sends an Ajax request for getting the result of a query.
|
||||||
var params = {'type_name': contentType, 'search': searchName,
|
var params = {'className': className, 'search': searchName,
|
||||||
'startNumber': startNumber};
|
'startNumber': startNumber};
|
||||||
if (sortKey) params['sortKey'] = sortKey;
|
if (sortKey) params['sortKey'] = sortKey;
|
||||||
if (sortOrder) params['sortOrder'] = sortOrder;
|
if (sortOrder) params['sortOrder'] = sortOrder;
|
||||||
|
@ -351,11 +351,11 @@ function toggleCookie(cookieId) {
|
||||||
// the HTML document that needs to be shown or hidden.
|
// the HTML document that needs to be shown or hidden.
|
||||||
var displayValue = 'none';
|
var displayValue = 'none';
|
||||||
var newState = 'collapsed';
|
var newState = 'collapsed';
|
||||||
var imgSrc = 'skyn/expand.gif';
|
var imgSrc = 'ui/expand.gif';
|
||||||
if (state == 'collapsed') {
|
if (state == 'collapsed') {
|
||||||
// Show the HTML zone
|
// Show the HTML zone
|
||||||
displayValue = 'block';
|
displayValue = 'block';
|
||||||
imgSrc = 'skyn/collapse.gif';
|
imgSrc = 'ui/collapse.gif';
|
||||||
newState = 'expanded';
|
newState = 'expanded';
|
||||||
}
|
}
|
||||||
// Update the corresponding HTML element
|
// Update the corresponding HTML element
|
||||||
|
@ -459,14 +459,14 @@ function manageTab(tabId, action) {
|
||||||
var tab = document.getElementById('tab_' + tabId);
|
var tab = document.getElementById('tab_' + tabId);
|
||||||
var right = document.getElementById('tab_' + tabId + '_right');
|
var right = document.getElementById('tab_' + tabId + '_right');
|
||||||
if (action == 'show') {
|
if (action == 'show') {
|
||||||
left.src = "skyn/tabLeft.png";
|
left.src = "ui/tabLeft.png";
|
||||||
tab.style.backgroundImage = "url(skyn/tabBg.png)";
|
tab.style.backgroundImage = "url(ui/tabBg.png)";
|
||||||
right.src = "skyn/tabRight.png";
|
right.src = "ui/tabRight.png";
|
||||||
}
|
}
|
||||||
if (action == 'hide') {
|
if (action == 'hide') {
|
||||||
left.src = "skyn/tabLeftu.png";
|
left.src = "ui/tabLeftu.png";
|
||||||
tab.style.backgroundImage = "url(skyn/tabBgu.png)";
|
tab.style.backgroundImage = "url(ui/tabBgu.png)";
|
||||||
right.src = "skyn/tabRightu.png";
|
right.src = "ui/tabRightu.png";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Before Width: | Height: | Size: 899 B After Width: | Height: | Size: 899 B |
Before Width: | Height: | Size: 232 B After Width: | Height: | Size: 232 B |
Before Width: | Height: | Size: 212 B After Width: | Height: | Size: 212 B |
Before Width: | Height: | Size: 218 B After Width: | Height: | Size: 218 B |
Before Width: | Height: | Size: 212 B After Width: | Height: | Size: 212 B |
Before Width: | Height: | Size: 216 B After Width: | Height: | Size: 216 B |
Before Width: | Height: | Size: 232 B After Width: | Height: | Size: 232 B |
Before Width: | Height: | Size: 953 B After Width: | Height: | Size: 953 B |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 70 B After Width: | Height: | Size: 70 B |
Before Width: | Height: | Size: 244 B After Width: | Height: | Size: 244 B |
Before Width: | Height: | Size: 239 B After Width: | Height: | Size: 239 B |
Before Width: | Height: | Size: 818 B After Width: | Height: | Size: 818 B |
Before Width: | Height: | Size: 248 B After Width: | Height: | Size: 248 B |
Before Width: | Height: | Size: 475 B After Width: | Height: | Size: 475 B |
|
@ -1,5 +1,5 @@
|
||||||
<tal:main define="tool context/getTool">
|
<tal:main define="tool context/getTool">
|
||||||
<html metal:use-macro="context/skyn/template/macros/main">
|
<html metal:use-macro="context/ui/template/macros/main">
|
||||||
<metal:fill fill-slot="content"
|
<metal:fill fill-slot="content"
|
||||||
tal:define="contextObj context/getParentNode;
|
tal:define="contextObj context/getParentNode;
|
||||||
errors request/errors | python:{};
|
errors request/errors | python:{};
|
||||||
|
@ -13,25 +13,25 @@
|
||||||
|
|
||||||
<tal:comment replace="nothing">Include type-specific CSS and JS.</tal:comment>
|
<tal:comment replace="nothing">Include type-specific CSS and JS.</tal:comment>
|
||||||
<link tal:repeat="cssFile cssJs/css" rel="stylesheet" type="text/css"
|
<link tal:repeat="cssFile cssJs/css" rel="stylesheet" type="text/css"
|
||||||
tal:attributes="href string:$appUrl/skyn/$cssFile"/>
|
tal:attributes="href string:$appUrl/ui/$cssFile"/>
|
||||||
<script tal:repeat="jsFile cssJs/js" type="text/javascript"
|
<script tal:repeat="jsFile cssJs/js" type="text/javascript"
|
||||||
tal:attributes="src string:$appUrl/skyn/$jsFile"></script>
|
tal:attributes="src string:$appUrl/ui/$jsFile"></script>
|
||||||
|
|
||||||
<metal:prologue use-macro="here/skyn/page/macros/prologue"/>
|
<metal:prologue use-macro="context/ui/page/macros/prologue"/>
|
||||||
<form id="appyEditForm" name="appyEditForm" method="post" enctype="multipart/form-data"
|
<form id="appyEditForm" name="appyEditForm" method="post" enctype="multipart/form-data"
|
||||||
tal:attributes="action python: contextObj.absolute_url()+'/skyn/do';
|
tal:attributes="action python: contextObj.absolute_url()+'/do';
|
||||||
class python: test(confirmMsg, 'atBaseEditForm', 'enableUnloadProtection atBaseEditForm')">
|
class python: test(confirmMsg, 'atBaseEditForm', 'enableUnloadProtection atBaseEditForm')">
|
||||||
<input type="hidden" name="action" value="Update"/>
|
<input type="hidden" name="action" value="Update"/>
|
||||||
<input type="hidden" name="page" tal:attributes="value page"/>
|
<input type="hidden" name="page" tal:attributes="value page"/>
|
||||||
<input type="hidden" name="nav" tal:attributes="value request/nav|nothing"/>
|
<input type="hidden" name="nav" tal:attributes="value request/nav|nothing"/>
|
||||||
<input type="hidden" name="is_new" tal:attributes="value contextObj/isTemporary"/>
|
<input type="hidden" name="is_new" tal:attributes="value contextObj/isTemporary"/>
|
||||||
<input type="hidden" name="confirmed" value="False"/>
|
<input type="hidden" name="confirmed" value="False"/>
|
||||||
<metal:show use-macro="here/skyn/page/macros/show"/>
|
<metal:show use-macro="context/ui/page/macros/show"/>
|
||||||
</form>
|
</form>
|
||||||
<script tal:condition="confirmMsg"
|
<script tal:condition="confirmMsg"
|
||||||
tal:content="python: 'askConfirm(\'script\', \'postConfirmedEditForm()\', \'%s\')' % confirmMsg">
|
tal:content="python: 'askConfirm(\'script\', \'postConfirmedEditForm()\', \'%s\')' % confirmMsg">
|
||||||
</script>
|
</script>
|
||||||
<metal:footer use-macro="here/skyn/page/macros/footer"/>
|
<metal:footer use-macro="context/ui/page/macros/footer"/>
|
||||||
</metal:fill>
|
</metal:fill>
|
||||||
</html>
|
</html>
|
||||||
</tal:main>
|
</tal:main>
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 72 B After Width: | Height: | Size: 72 B |
Before Width: | Height: | Size: 372 B After Width: | Height: | Size: 372 B |
Before Width: | Height: | Size: 62 B After Width: | Height: | Size: 62 B |
BIN
gen/ui/folder.gif
Normal file
After Width: | Height: | Size: 903 B |
Before Width: | Height: | Size: 719 B After Width: | Height: | Size: 719 B |
Before Width: | Height: | Size: 225 B After Width: | Height: | Size: 225 B |
Before Width: | Height: | Size: 591 B After Width: | Height: | Size: 591 B |
Before Width: | Height: | Size: 589 B After Width: | Height: | Size: 589 B |
Before Width: | Height: | Size: 705 B After Width: | Height: | Size: 705 B |
|
@ -14,12 +14,12 @@
|
||||||
<body>
|
<body>
|
||||||
<metal:fill fill-slot="main"
|
<metal:fill fill-slot="main"
|
||||||
tal:define="appFolder context/getParentNode;
|
tal:define="appFolder context/getParentNode;
|
||||||
contentType request/type_name;
|
className request/className;
|
||||||
tool python: portal.get('portal_%s' % appFolder.id.lower());
|
tool python: portal.get('portal_%s' % appFolder.id.lower());
|
||||||
importElems python: tool.getImportElements(contentType);
|
importElems python: tool.getImportElements(className);
|
||||||
global allAreImported python:True">
|
global allAreImported python:True">
|
||||||
|
|
||||||
<div metal:use-macro="here/skyn/page/macros/prologue"/>
|
<div metal:use-macro="context/ui/page/macros/prologue"/>
|
||||||
<script language="javascript">
|
<script language="javascript">
|
||||||
<!--
|
<!--
|
||||||
var importedElemsShown = false;
|
var importedElemsShown = false;
|
||||||
|
@ -68,9 +68,9 @@
|
||||||
</script>
|
</script>
|
||||||
<tal:comment replace="nothing">Form for importing several meetings at once.</tal:comment>
|
<tal:comment replace="nothing">Form for importing several meetings at once.</tal:comment>
|
||||||
<form name="importElements"
|
<form name="importElements"
|
||||||
tal:attributes="action python: appFolder.absolute_url()+'/skyn/do'" method="post">
|
tal:attributes="action python: appFolder.absolute_url()+'/do'" method="post">
|
||||||
<input type="hidden" name="action" value="ImportObjects"/>
|
<input type="hidden" name="action" value="ImportObjects"/>
|
||||||
<input type="hidden" name="type_name" tal:attributes="value contentType"/>
|
<input type="hidden" name="className" tal:attributes="value className"/>
|
||||||
<input type="hidden" name="importPath" value=""/>
|
<input type="hidden" name="importPath" value=""/>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
@ -79,19 +79,19 @@
|
||||||
<tr>
|
<tr>
|
||||||
<th tal:repeat="columnHeader python: importElems[0]">
|
<th tal:repeat="columnHeader python: importElems[0]">
|
||||||
<img tal:condition="python: repeat['columnHeader'].number() == 1"
|
<img tal:condition="python: repeat['columnHeader'].number() == 1"
|
||||||
tal:attributes="src string:$appUrl/skyn/eye.png;
|
tal:attributes="src string:$appUrl/ui/eye.png;
|
||||||
title python: tool.translate('import_show_hide')"
|
title python: tool.translate('import_show_hide')"
|
||||||
style="cursor:pointer" onClick="toggleViewableElements()" align="left" />
|
style="cursor:pointer" onClick="toggleViewableElements()" align="left" />
|
||||||
<span tal:replace="columnHeader"/>
|
<span tal:replace="columnHeader"/>
|
||||||
</th>
|
</th>
|
||||||
<th tal:content="python: tool.translate('ref_actions')"></th>
|
<th tal:content="python: tool.translate('ref_actions')"></th>
|
||||||
<th width="20px"><img
|
<th width="20px"><img
|
||||||
tal:attributes="src string: $appUrl/skyn/select_elems.png;
|
tal:attributes="src string: $appUrl/ui/select_elems.png;
|
||||||
title python: tool.translate('select_delesect')"
|
title python: tool.translate('select_delesect')"
|
||||||
onClick="toggleCheckboxes()" style="cursor:pointer"/>
|
onClick="toggleCheckboxes()" style="cursor:pointer"/>
|
||||||
</tr>
|
</tr>
|
||||||
<tal:row repeat="row python: importElems[1]">
|
<tal:row repeat="row python: importElems[1]">
|
||||||
<tr tal:define="alreadyImported python: tool.isAlreadyImported(contentType, row[0]);
|
<tr tal:define="alreadyImported python: tool.isAlreadyImported(className, row[0]);
|
||||||
global allAreImported python: allAreImported and alreadyImported;
|
global allAreImported python: allAreImported and alreadyImported;
|
||||||
odd repeat/row/odd"
|
odd repeat/row/odd"
|
||||||
tal:attributes="id python: alreadyImported and 'importedElem' or 'notImportedElem';
|
tal:attributes="id python: alreadyImported and 'importedElem' or 'notImportedElem';
|
Before Width: | Height: | Size: 51 B After Width: | Height: | Size: 51 B |
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 636 B After Width: | Height: | Size: 636 B |
|
@ -11,26 +11,26 @@
|
||||||
<tr valign="middle">
|
<tr valign="middle">
|
||||||
<tal:comment replace="nothing">Go to the first page</tal:comment>
|
<tal:comment replace="nothing">Go to the first page</tal:comment>
|
||||||
<td tal:condition="python: (startNumber != 0) and (startNumber != batchSize)"><img style="cursor:pointer"
|
<td tal:condition="python: (startNumber != 0) and (startNumber != batchSize)"><img style="cursor:pointer"
|
||||||
tal:attributes="src string: $appUrl/skyn/arrowLeftDouble.png;
|
tal:attributes="src string: $appUrl/ui/arrowLeftDouble.png;
|
||||||
title python: tool.translate('goto_first');
|
title python: tool.translate('goto_first');
|
||||||
onClick python: navBaseCall.replace('**v**', '0'+sortAndFilter)"/></td>
|
onClick python: navBaseCall.replace('**v**', '0'+sortAndFilter)"/></td>
|
||||||
<tal:comment replace="nothing">Go to the previous page</tal:comment>
|
<tal:comment replace="nothing">Go to the previous page</tal:comment>
|
||||||
<td tal:define="sNumber python: startNumber - batchSize"
|
<td tal:define="sNumber python: startNumber - batchSize"
|
||||||
tal:condition="python: startNumber != 0"><img style="cursor:pointer"
|
tal:condition="python: startNumber != 0"><img style="cursor:pointer"
|
||||||
tal:attributes="src string: $appUrl/skyn/arrowLeftSimple.png;
|
tal:attributes="src string: $appUrl/ui/arrowLeftSimple.png;
|
||||||
title python: tool.translate('goto_previous');
|
title python: tool.translate('goto_previous');
|
||||||
onClick python: navBaseCall.replace('**v**', str(sNumber)+sortAndFilter)"/></td>
|
onClick python: navBaseCall.replace('**v**', str(sNumber)+sortAndFilter)"/></td>
|
||||||
<tal:comment replace="nothing">Explain which elements are currently shown</tal:comment>
|
<tal:comment replace="nothing">Explain which elements are currently shown</tal:comment>
|
||||||
<td class="discreet">
|
<td class="discreet">
|
||||||
<span tal:replace="python: startNumber+1"/>
|
<span tal:replace="python: startNumber+1"/>
|
||||||
<img tal:attributes="src string: $appUrl/skyn/to.png"/>
|
<img tal:attributes="src string: $appUrl/ui/to.png"/>
|
||||||
<span tal:replace="python: startNumber+len(objs)"/> <b>//</b>
|
<span tal:replace="python: startNumber+len(objs)"/> <b>//</b>
|
||||||
<span tal:replace="python: totalNumber"/>
|
<span tal:replace="python: totalNumber"/>
|
||||||
</td>
|
</td>
|
||||||
<tal:comment replace="nothing">Go to the next page</tal:comment>
|
<tal:comment replace="nothing">Go to the next page</tal:comment>
|
||||||
<td tal:define="sNumber python: startNumber + batchSize"
|
<td tal:define="sNumber python: startNumber + batchSize"
|
||||||
tal:condition="python: sNumber < totalNumber"><img style="cursor:pointer"
|
tal:condition="python: sNumber < totalNumber"><img style="cursor:pointer"
|
||||||
tal:attributes="src string: $appUrl/skyn/arrowRightSimple.png;
|
tal:attributes="src string: $appUrl/ui/arrowRightSimple.png;
|
||||||
title python: tool.translate('goto_next');
|
title python: tool.translate('goto_next');
|
||||||
onClick python: navBaseCall.replace('**v**', str(sNumber)+sortAndFilter)"/></td>
|
onClick python: navBaseCall.replace('**v**', str(sNumber)+sortAndFilter)"/></td>
|
||||||
<tal:comment replace="nothing">Go to the last page</tal:comment>
|
<tal:comment replace="nothing">Go to the last page</tal:comment>
|
||||||
|
@ -39,7 +39,7 @@
|
||||||
nbOfCountedPages python: test(lastPageIsIncomplete, nbOfCompletePages, nbOfCompletePages-1);
|
nbOfCountedPages python: test(lastPageIsIncomplete, nbOfCompletePages, nbOfCompletePages-1);
|
||||||
sNumber python: (nbOfCountedPages*batchSize)"
|
sNumber python: (nbOfCountedPages*batchSize)"
|
||||||
tal:condition="python: (startNumber != sNumber) and (startNumber != sNumber-batchSize)"><img style="cursor:pointer"
|
tal:condition="python: (startNumber != sNumber) and (startNumber != sNumber-batchSize)"><img style="cursor:pointer"
|
||||||
tal:attributes="src string: $appUrl/skyn/arrowRightDouble.png;
|
tal:attributes="src string: $appUrl/ui/arrowRightDouble.png;
|
||||||
title python: tool.translate('goto_last');
|
title python: tool.translate('goto_last');
|
||||||
onClick python: navBaseCall.replace('**v**', str(sNumber)+sortAndFilter)"/></td>
|
onClick python: navBaseCall.replace('**v**', str(sNumber)+sortAndFilter)"/></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -64,15 +64,15 @@
|
||||||
<tr valign="middle">
|
<tr valign="middle">
|
||||||
<tal:comment replace="nothing">Go to the source URL (search or referred object)</tal:comment>
|
<tal:comment replace="nothing">Go to the source URL (search or referred object)</tal:comment>
|
||||||
<td tal:condition="sourceUrl"><a tal:attributes="href sourceUrl"><img style="cursor:pointer"
|
<td tal:condition="sourceUrl"><a tal:attributes="href sourceUrl"><img style="cursor:pointer"
|
||||||
tal:attributes="src string: $appUrl/skyn/gotoSource.png;
|
tal:attributes="src string: $appUrl/ui/gotoSource.png;
|
||||||
title python: backText + ' : ' + tool.translate('goto_source')"/></a></td>
|
title python: backText + ' : ' + tool.translate('goto_source')"/></a></td>
|
||||||
<tal:comment replace="nothing">Go to the first page</tal:comment>
|
<tal:comment replace="nothing">Go to the first page</tal:comment>
|
||||||
<td tal:condition="firstUrl"><a tal:attributes="href firstUrl"><img style="cursor:pointer"
|
<td tal:condition="firstUrl"><a tal:attributes="href firstUrl"><img style="cursor:pointer"
|
||||||
tal:attributes="src string: $appUrl/skyn/arrowLeftDouble.png;
|
tal:attributes="src string: $appUrl/ui/arrowLeftDouble.png;
|
||||||
title python: tool.translate('goto_first')"/></a></td>
|
title python: tool.translate('goto_first')"/></a></td>
|
||||||
<tal:comment replace="nothing">Go to the previous page</tal:comment>
|
<tal:comment replace="nothing">Go to the previous page</tal:comment>
|
||||||
<td tal:condition="previousUrl"><a tal:attributes="href previousUrl"><img style="cursor:pointer"
|
<td tal:condition="previousUrl"><a tal:attributes="href previousUrl"><img style="cursor:pointer"
|
||||||
tal:attributes="src string: $appUrl/skyn/arrowLeftSimple.png;
|
tal:attributes="src string: $appUrl/ui/arrowLeftSimple.png;
|
||||||
title python: tool.translate('goto_previous')"/></a></td>
|
title python: tool.translate('goto_previous')"/></a></td>
|
||||||
<tal:comment replace="nothing">Explain which element is currently shown</tal:comment>
|
<tal:comment replace="nothing">Explain which element is currently shown</tal:comment>
|
||||||
<td class="discreet">
|
<td class="discreet">
|
||||||
|
@ -81,11 +81,11 @@
|
||||||
</td>
|
</td>
|
||||||
<tal:comment replace="nothing">Go to the next page</tal:comment>
|
<tal:comment replace="nothing">Go to the next page</tal:comment>
|
||||||
<td tal:condition="python: nextUrl"><a tal:attributes="href nextUrl"><img style="cursor:pointer"
|
<td tal:condition="python: nextUrl"><a tal:attributes="href nextUrl"><img style="cursor:pointer"
|
||||||
tal:attributes="src string: $appUrl/skyn/arrowRightSimple.png;
|
tal:attributes="src string: $appUrl/ui/arrowRightSimple.png;
|
||||||
title python: tool.translate('goto_next')"/></a></td>
|
title python: tool.translate('goto_next')"/></a></td>
|
||||||
<tal:comment replace="nothing">Go to the last page</tal:comment>
|
<tal:comment replace="nothing">Go to the last page</tal:comment>
|
||||||
<td tal:condition="lastUrl"><a tal:attributes="href lastUrl"><img style="cursor:pointer"
|
<td tal:condition="lastUrl"><a tal:attributes="href lastUrl"><img style="cursor:pointer"
|
||||||
tal:attributes="src string: $appUrl/skyn/arrowRightDouble.png;
|
tal:attributes="src string: $appUrl/ui/arrowRightDouble.png;
|
||||||
title python: tool.translate('goto_last')"/></a></td>
|
title python: tool.translate('goto_last')"/></a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
@ -97,11 +97,11 @@
|
||||||
</tal:comment>
|
</tal:comment>
|
||||||
<metal:sortAndFilter define-macro="sortAndFilter" tal:define="fieldName widget/name">
|
<metal:sortAndFilter define-macro="sortAndFilter" tal:define="fieldName widget/name">
|
||||||
<tal:sort condition="sortable">
|
<tal:sort condition="sortable">
|
||||||
<img tal:attributes="src string: $appUrl/skyn/sortDown.gif;
|
<img tal:attributes="src string: $appUrl/ui/sortDown.gif;
|
||||||
onClick python: navBaseCall.replace('**v**', '0,\'%s\',\'asc\',\'%s\'' % (fieldName, filterKey))"
|
onClick python: navBaseCall.replace('**v**', '0,\'%s\',\'asc\',\'%s\'' % (fieldName, filterKey))"
|
||||||
tal:condition="python: (sortKey != fieldName) or (sortOrder == 'desc')"
|
tal:condition="python: (sortKey != fieldName) or (sortOrder == 'desc')"
|
||||||
style="cursor:pointer"/>
|
style="cursor:pointer"/>
|
||||||
<img tal:attributes="src string: $appUrl/skyn/sortUp.gif;
|
<img tal:attributes="src string: $appUrl/ui/sortUp.gif;
|
||||||
onClick python: navBaseCall.replace('**v**', '0,\'%s\',\'desc\',\'%s\'' % (fieldName, filterKey))"
|
onClick python: navBaseCall.replace('**v**', '0,\'%s\',\'desc\',\'%s\'' % (fieldName, filterKey))"
|
||||||
tal:condition="python: (sortKey != fieldName) or (sortOrder == 'asc')"
|
tal:condition="python: (sortKey != fieldName) or (sortOrder == 'asc')"
|
||||||
style="cursor:pointer"/>
|
style="cursor:pointer"/>
|
||||||
|
@ -110,7 +110,7 @@
|
||||||
<input type="text" size="7"
|
<input type="text" size="7"
|
||||||
tal:attributes="id python: '%s_%s' % (ajaxHookId, fieldName);
|
tal:attributes="id python: '%s_%s' % (ajaxHookId, fieldName);
|
||||||
value python: test(filterKey == fieldName, filterValue, '')"/>
|
value python: test(filterKey == fieldName, filterValue, '')"/>
|
||||||
<img tal:attributes="src string: $appUrl/skyn/funnel.png;
|
<img tal:attributes="src string: $appUrl/ui/funnel.png;
|
||||||
onClick python: navBaseCall.replace('**v**', '0,\'%s\',\'%s\',\'%s\'' % (sortKey, sortOrder, fieldName))"
|
onClick python: navBaseCall.replace('**v**', '0,\'%s\',\'%s\',\'%s\'' % (sortKey, sortOrder, fieldName))"
|
||||||
style="cursor:pointer"/>
|
style="cursor:pointer"/>
|
||||||
</tal:filter>
|
</tal:filter>
|
Before Width: | Height: | Size: 951 B After Width: | Height: | Size: 951 B |
Before Width: | Height: | Size: 211 B After Width: | Height: | Size: 211 B |
Before Width: | Height: | Size: 192 B After Width: | Height: | Size: 192 B |
BIN
gen/ui/object.gif
Normal file
After Width: | Height: | Size: 114 B |
Before Width: | Height: | Size: 754 B After Width: | Height: | Size: 754 B |
|
@ -6,7 +6,7 @@
|
||||||
<script language="javascript" tal:content="tool/getJavascriptMessages"></script>
|
<script language="javascript" tal:content="tool/getJavascriptMessages"></script>
|
||||||
|
|
||||||
<tal:comment replace="nothing">Global form for deleting an object</tal:comment>
|
<tal:comment replace="nothing">Global form for deleting an object</tal:comment>
|
||||||
<form id="deleteForm" method="post" action="skyn/do">
|
<form id="deleteForm" method="post" action="do">
|
||||||
<input type="hidden" name="action" value="Delete"/>
|
<input type="hidden" name="action" value="Delete"/>
|
||||||
<input type="hidden" name="objectUid"/>
|
<input type="hidden" name="objectUid"/>
|
||||||
</form>
|
</form>
|
||||||
|
@ -30,7 +30,7 @@
|
||||||
will be rendered.
|
will be rendered.
|
||||||
</tal:comment>
|
</tal:comment>
|
||||||
<metal:show define-macro="show">
|
<metal:show define-macro="show">
|
||||||
<metal:layout use-macro="here/skyn/widgets/show/macros/layout"/>
|
<metal:layout use-macro="context/ui/widgets/show/macros/layout"/>
|
||||||
</metal:show>
|
</metal:show>
|
||||||
|
|
||||||
<tal:comment replace="nothing">
|
<tal:comment replace="nothing">
|
||||||
|
@ -44,10 +44,10 @@
|
||||||
tal:attributes="width layout/width">
|
tal:attributes="width layout/width">
|
||||||
<tr tal:repeat="widget python: contextObj.getGroupedAppyTypes(layoutType, page)">
|
<tr tal:repeat="widget python: contextObj.getGroupedAppyTypes(layoutType, page)">
|
||||||
<td tal:condition="python: widget['type'] == 'group'">
|
<td tal:condition="python: widget['type'] == 'group'">
|
||||||
<metal:call use-macro="app/skyn/widgets/show/macros/group"/>
|
<metal:call use-macro="app/ui/widgets/show/macros/group"/>
|
||||||
</td>
|
</td>
|
||||||
<td tal:condition="python: widget['type'] != 'group'">
|
<td tal:condition="python: widget['type'] != 'group'">
|
||||||
<metal:call use-macro="app/skyn/widgets/show/macros/field"/>
|
<metal:call use-macro="app/ui/widgets/show/macros/field"/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
@ -68,7 +68,7 @@
|
||||||
|
|
||||||
<tal:comment replace="nothing">Table containing the history</tal:comment>
|
<tal:comment replace="nothing">Table containing the history</tal:comment>
|
||||||
<tal:history condition="objs">
|
<tal:history condition="objs">
|
||||||
<metal:nav use-macro="here/skyn/navigate/macros/appyNavigate"/>
|
<metal:nav use-macro="context/ui/navigate/macros/appyNavigate"/>
|
||||||
<table width="100%" class="history">
|
<table width="100%" class="history">
|
||||||
<tr i18n:domain="plone">
|
<tr i18n:domain="plone">
|
||||||
<th align="left" i18n:translate="listingheader_action">Action</th>
|
<th align="left" i18n:translate="listingheader_action">Action</th>
|
||||||
|
@ -86,11 +86,7 @@
|
||||||
<td tal:condition="not: isDataChange"
|
<td tal:condition="not: isDataChange"
|
||||||
tal:content="python: tool.translate(contextObj.getWorkflowLabel(event['action']))"
|
tal:content="python: tool.translate(contextObj.getWorkflowLabel(event['action']))"
|
||||||
tal:attributes="class string:state-${state}"/>
|
tal:attributes="class string:state-${state}"/>
|
||||||
<td tal:define="actorid python:event.get('actor');
|
<td tal:define="actorid python:event.get('actor');" tal:content="actorid"/>
|
||||||
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:content="python:contextObj.restrictedTraverse('@@plone').toLocalizedTime(event['time'],long_format=True)"/>
|
||||||
<td tal:condition="not: isDataChange"><tal:comment condition="rhComments" tal:content="structure rhComments"/>
|
<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>
|
<tal:noComment condition="not: rhComments" i18n:translate="no_comments" i18n:domain="plone"/></td>
|
||||||
|
@ -136,7 +132,7 @@
|
||||||
tal:content="python: _(contextObj.getWorkflowLabel(stateInfo['name']))">
|
tal:content="python: _(contextObj.getWorkflowLabel(stateInfo['name']))">
|
||||||
</td>
|
</td>
|
||||||
<td tal:condition="python: stateInfo['name'] != states[-1]['name']">
|
<td tal:condition="python: stateInfo['name'] != states[-1]['name']">
|
||||||
<img tal:attributes="src string: $appUrl/skyn/nextState.png"/>
|
<img tal:attributes="src string: $appUrl/ui/nextState.png"/>
|
||||||
</td>
|
</td>
|
||||||
</tal:state>
|
</tal:state>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -150,8 +146,8 @@
|
||||||
tal:define="transitions contextObj/getAppyTransitions"
|
tal:define="transitions contextObj/getAppyTransitions"
|
||||||
tal:condition="transitions">
|
tal:condition="transitions">
|
||||||
<form id="triggerTransitionForm" method="post"
|
<form id="triggerTransitionForm" method="post"
|
||||||
tal:attributes="action python: contextObj.absolute_url() + '/skyn/do'">
|
tal:attributes="action python: contextObj.absolute_url() + '/do'">
|
||||||
<input type="hidden" name="action" value="Do"/>
|
<input type="hidden" name="action" value="Trigger"/>
|
||||||
<input type="hidden" name="workflow_action"/>
|
<input type="hidden" name="workflow_action"/>
|
||||||
<table>
|
<table>
|
||||||
<tr valign="middle">
|
<tr valign="middle">
|
||||||
|
@ -203,22 +199,19 @@
|
||||||
<tal:comment replace="nothing">Plus/minus icon for accessing history</tal:comment>
|
<tal:comment replace="nothing">Plus/minus icon for accessing history</tal:comment>
|
||||||
<tal:accessHistory condition="hasHistory">
|
<tal:accessHistory condition="hasHistory">
|
||||||
<img align="left" style="cursor:pointer" onClick="toggleCookie('appyHistory')"
|
<img align="left" style="cursor:pointer" onClick="toggleCookie('appyHistory')"
|
||||||
tal:attributes="src python:test(historyExpanded, 'skyn/collapse.gif', 'skyn/expand.gif');"
|
tal:attributes="src python:test(historyExpanded, 'ui/collapse.gif', 'ui/expand.gif');"
|
||||||
id="appyHistory_img"/>
|
id="appyHistory_img"/>
|
||||||
<span i18n:translate="label_history" i18n:domain="plone" class="historyLabel">History</span> ||
|
<span i18n:translate="label_history" i18n:domain="plone" class="historyLabel">History</span> ||
|
||||||
</tal:accessHistory>
|
</tal:accessHistory>
|
||||||
|
|
||||||
<tal:comment replace="nothing">Show document creator</tal:comment>
|
<tal:comment replace="nothing">Show document creator</tal:comment>
|
||||||
<span class="by" tal:condition="creator"
|
<span class="by" tal:condition="creator">
|
||||||
tal:define="author python:contextObj.portal_membership.getMemberInfo(creator)">
|
<span>by <span tal:replace="creator"/>
|
||||||
<span i18n:domain="plone" i18n:translate="label_by_author">
|
|
||||||
by <span tal:content="python:author and author['fullname'] or creator"
|
|
||||||
tal:omit-tag="not:author" i18n:name="author"/>
|
|
||||||
—
|
—
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<tal:comment replace="nothing">Show last modification date</tal:comment>
|
<tal:comment replace="nothing">Show creation date</tal:comment>
|
||||||
<span tal:replace="python:contextObj.restrictedTraverse('@@plone').toLocalizedTime(contextObj.ModificationDate(),long_format=1)"></span>
|
<span tal:replace="python:contextObj.created"></span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tal:comment replace="nothing">Object history</tal:comment>
|
<tal:comment replace="nothing">Object history</tal:comment>
|
||||||
|
@ -239,8 +232,8 @@
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<table width="100%">
|
<table width="100%">
|
||||||
<tr>
|
<tr>
|
||||||
<td><metal:states use-macro="here/skyn/page/macros/states"/></td>
|
<td><metal:states use-macro="here/ui/page/macros/states"/></td>
|
||||||
<td align="right"><metal:states use-macro="here/skyn/page/macros/transitions"/></td>
|
<td align="right"><metal:states use-macro="here/ui/page/macros/transitions"/></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</td>
|
</td>
|
||||||
|
@ -250,7 +243,6 @@
|
||||||
|
|
||||||
<tal:comment replace="nothing">The page footer.</tal:comment>
|
<tal:comment replace="nothing">The page footer.</tal:comment>
|
||||||
<metal:footer define-macro="footer">
|
<metal:footer define-macro="footer">
|
||||||
<tal:dummy define="messages app/plone_utils/showPortalMessages"/>
|
|
||||||
<script language="javascript">
|
<script language="javascript">
|
||||||
<!--
|
<!--
|
||||||
initSlaves();
|
initSlaves();
|
||||||
|
@ -271,12 +263,12 @@
|
||||||
<tal:button condition="isEdit">
|
<tal:button condition="isEdit">
|
||||||
<input type="image" class="imageInput" style="cursor:pointer" name="buttonPrevious"
|
<input type="image" class="imageInput" style="cursor:pointer" name="buttonPrevious"
|
||||||
title="label_previous" i18n:attributes="title" i18n:domain="plone"
|
title="label_previous" i18n:attributes="title" i18n:domain="plone"
|
||||||
tal:attributes="src string:$appUrl/skyn/previous.png"/>
|
tal:attributes="src string:$appUrl/ui/previous.png"/>
|
||||||
<input type="hidden" name="previousPage" tal:attributes="value previousPage"/>
|
<input type="hidden" name="previousPage" tal:attributes="value previousPage"/>
|
||||||
</tal:button>
|
</tal:button>
|
||||||
<tal:link condition="not: isEdit">
|
<tal:link condition="not: isEdit">
|
||||||
<a tal:attributes="href python: contextObj.getUrl(page=previousPage)">
|
<a tal:attributes="href python: contextObj.getUrl(page=previousPage)">
|
||||||
<img tal:attributes="src string:$appUrl/skyn/previous.png"
|
<img tal:attributes="src string:$appUrl/ui/previous.png"
|
||||||
title="label_previous" i18n:attributes="title" i18n:domain="plone"/>
|
title="label_previous" i18n:attributes="title" i18n:domain="plone"/>
|
||||||
</a>
|
</a>
|
||||||
</tal:link>
|
</tal:link>
|
||||||
|
@ -285,38 +277,38 @@
|
||||||
<tal:save condition="python: isEdit and pageInfo['showSave']">
|
<tal:save condition="python: isEdit and pageInfo['showSave']">
|
||||||
<input type="image" class="imageInput" style="cursor:pointer" name="buttonOk"
|
<input type="image" class="imageInput" style="cursor:pointer" name="buttonOk"
|
||||||
title="label_save" i18n:attributes="title" i18n:domain="plone"
|
title="label_save" i18n:attributes="title" i18n:domain="plone"
|
||||||
tal:attributes="src string:$appUrl/skyn/save.png"/>
|
tal:attributes="src string:$appUrl/ui/save.png"/>
|
||||||
</tal:save>
|
</tal:save>
|
||||||
|
|
||||||
<tal:cancel condition="python: isEdit and pageInfo['showCancel']">
|
<tal:cancel condition="python: isEdit and pageInfo['showCancel']">
|
||||||
<input type="image" class="imageInput" style="cursor:pointer" name="buttonCancel"
|
<input type="image" class="imageInput" style="cursor:pointer" name="buttonCancel"
|
||||||
title="label_cancel" i18n:attributes="title" i18n:domain="plone"
|
title="label_cancel" i18n:attributes="title" i18n:domain="plone"
|
||||||
tal:attributes="src string:$appUrl/skyn/cancel.png"/>
|
tal:attributes="src string:$appUrl/ui/cancel.png"/>
|
||||||
</tal:cancel>
|
</tal:cancel>
|
||||||
|
|
||||||
<tal:edit condition="python: not isEdit and pageInfo['showOnEdit']">
|
<tal:edit condition="python: not isEdit and pageInfo['showOnEdit']">
|
||||||
<img title="Edit" i18n:domain="plone" i18n:attributes="title" style="cursor:pointer"
|
<img title="Edit" i18n:domain="plone" i18n:attributes="title" style="cursor:pointer"
|
||||||
tal:attributes="onClick python: 'href: window.location=\'%s\'' % contextObj.getUrl(mode='edit', page=page);
|
tal:attributes="onClick python: 'href: window.location=\'%s\'' % contextObj.getUrl(mode='edit', page=page);
|
||||||
src string: $appUrl/skyn/editBig.png"
|
src string: $appUrl/ui/editBig.png"
|
||||||
tal:condition="python: contextObj.allows('Modify portal content')"/>
|
tal:condition="python: contextObj.allows('Modify portal content')"/>
|
||||||
</tal:edit>
|
</tal:edit>
|
||||||
|
|
||||||
<tal:refresh condition="contextObj/isDebug">
|
<tal:refresh condition="contextObj/isDebug">
|
||||||
<img title="Refresh" style="cursor:pointer"
|
<img title="Refresh" style="cursor:pointer"
|
||||||
tal:attributes="onClick python: 'href: window.location=\'%s\'' % contextObj.getUrl(mode=layoutType, page=page, refresh='yes');
|
tal:attributes="onClick python: 'href: window.location=\'%s\'' % contextObj.getUrl(mode=layoutType, page=page, refresh='yes');
|
||||||
src string: $appUrl/skyn/refresh.png"/>
|
src string: $appUrl/ui/refresh.png"/>
|
||||||
</tal:refresh>
|
</tal:refresh>
|
||||||
|
|
||||||
<tal:next condition="python: nextPage and pageInfo['showNext']">
|
<tal:next condition="python: nextPage and pageInfo['showNext']">
|
||||||
<tal:button condition="isEdit">
|
<tal:button condition="isEdit">
|
||||||
<input type="image" class="imageInput" style="cursor:pointer" name="buttonNext"
|
<input type="image" class="imageInput" style="cursor:pointer" name="buttonNext"
|
||||||
title="label_next" i18n:attributes="title" i18n:domain="plone"
|
title="label_next" i18n:attributes="title" i18n:domain="plone"
|
||||||
tal:attributes="src string:$appUrl/skyn/next.png"/>
|
tal:attributes="src string:$appUrl/ui/next.png"/>
|
||||||
<input type="hidden" name="nextPage" tal:attributes="value nextPage"/>
|
<input type="hidden" name="nextPage" tal:attributes="value nextPage"/>
|
||||||
</tal:button>
|
</tal:button>
|
||||||
<tal:link condition="not: isEdit">
|
<tal:link condition="not: isEdit">
|
||||||
<a tal:attributes="href python: contextObj.getUrl(page=nextPage)">
|
<a tal:attributes="href python: contextObj.getUrl(page=nextPage)">
|
||||||
<img tal:attributes="src string:$appUrl/skyn/next.png"
|
<img tal:attributes="src string:$appUrl/ui/next.png"
|
||||||
title="label_next" i18n:attributes="title" i18n:domain="plone"/>
|
title="label_next" i18n:attributes="title" i18n:domain="plone"/>
|
||||||
</a>
|
</a>
|
||||||
</tal:link>
|
</tal:link>
|
||||||
|
@ -326,14 +318,6 @@
|
||||||
<tal:comment replace="nothing">
|
<tal:comment replace="nothing">
|
||||||
This macro displays the global message on the page.
|
This macro displays the global message on the page.
|
||||||
</tal:comment>
|
</tal:comment>
|
||||||
<metal:message define-macro="message">
|
<metal:message define-macro="message" tal:define="messages tool/getMessages" tal:condition="messages">
|
||||||
<tal:comment replace="nothing">Single message from portal_status_message request key</tal:comment>
|
<div class="message" tal:content="structure python: ''.join([m[1] for m in messages])"></div>
|
||||||
<div tal:define="msg req/portal_status_message | nothing"
|
|
||||||
tal:condition="msg" class="message" tal:content="structure msg"></div>
|
|
||||||
|
|
||||||
<tal:comment replace="nothing">Messages added via plone_utils</tal:comment>
|
|
||||||
<tal:messages define="messages python: ''.join([m.message for m in app.plone_utils.showPortalMessages()])"
|
|
||||||
condition="messages">
|
|
||||||
<div class="message" tal:content="structure messages"></div>
|
|
||||||
</tal:messages>
|
|
||||||
</metal:message>
|
</metal:message>
|
Before Width: | Height: | Size: 228 B After Width: | Height: | Size: 228 B |
Before Width: | Height: | Size: 246 B After Width: | Height: | Size: 246 B |
|
@ -2,28 +2,27 @@
|
||||||
This macro displays the content of the application portlet.
|
This macro displays the content of the application portlet.
|
||||||
</tal:comment>
|
</tal:comment>
|
||||||
<metal:portlet define-macro="portlet"
|
<metal:portlet define-macro="portlet"
|
||||||
tal:define="queryUrl python: '%s/skyn/query' % tool.absolute_url();
|
tal:define="queryUrl python: '%s/ui/query' % tool.absolute_url();
|
||||||
toolUrl tool/absolute_url;
|
toolUrl tool/absolute_url;
|
||||||
currentSearch req/search|nothing;
|
currentSearch req/search|nothing;
|
||||||
currentType req/type_name|nothing;
|
currentClass req/className|nothing;
|
||||||
contextObj tool/getPublishedObject;
|
contextObj tool/getPublishedObject;
|
||||||
rootClasses tool/getRootClasses">
|
rootClasses tool/getRootClasses">
|
||||||
|
|
||||||
<tal:publishedObject condition="python: contextObj and contextObj.mayNavigate()">
|
<tal:publishedObject condition="python: contextObj and contextObj.mayNavigate()">
|
||||||
<div class="portletTitle" tal:content="contextObj/Title"></div>
|
<div class="portletTitle" tal:content="contextObj/Title"></div>
|
||||||
<span><metal:phases use-macro="here/skyn/portlet/macros/phases"/></span>
|
<span><metal:phases use-macro="here/ui/portlet/macros/phases"/></span>
|
||||||
</tal:publishedObject>
|
</tal:publishedObject>
|
||||||
|
|
||||||
<tal:comment replace="nothing">One section for every searchable root class.</tal:comment>
|
<tal:comment replace="nothing">One section for every searchable root class.</tal:comment>
|
||||||
<tal:section repeat="rootClass python: [rc for rc in rootClasses if tool.userMaySearch(rc)]">
|
<tal:section repeat="rootClass python: [rc for rc in rootClasses if tool.userMaySearch(rc)]">
|
||||||
<tal:comment replace="nothing">Section title, with action icons</tal:comment>
|
<tal:comment replace="nothing">Section title, with action icons</tal:comment>
|
||||||
<table width="100%"
|
<table width="100%"
|
||||||
tal:define="afUrl appFolder/absolute_url"
|
|
||||||
tal:attributes="class python:test((repeat['rootClass'].number()==1) and not contextObj, '', 'portletSep')">
|
tal:attributes="class python:test((repeat['rootClass'].number()==1) and not contextObj, '', 'portletSep')">
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a tal:attributes="href python: '%s?type_name=%s' % (queryUrl, rootClass);
|
<a tal:attributes="href python: '%s?className=%s' % (queryUrl, rootClass);
|
||||||
class python:test(not currentSearch and (currentType==rootClass), 'portletCurrent', '')"
|
class python:test(not currentSearch and (currentClass==rootClass), 'portletCurrent', '')"
|
||||||
tal:content="structure python: _(rootClass + '_plural')"></a>
|
tal:content="structure python: _(rootClass + '_plural')"></a>
|
||||||
</td>
|
</td>
|
||||||
<td align="right"
|
<td align="right"
|
||||||
|
@ -33,21 +32,21 @@
|
||||||
<tal:comment replace="nothing">Create a new object from a web form</tal:comment>
|
<tal:comment replace="nothing">Create a new object from a web form</tal:comment>
|
||||||
<img style="cursor:pointer"
|
<img style="cursor:pointer"
|
||||||
tal:condition="python: ('form' in createMeans) and userMayAdd"
|
tal:condition="python: ('form' in createMeans) and userMayAdd"
|
||||||
tal:attributes="onClick python: 'href: window.location=\'%s/skyn/do?action=Create&type_name=%s\'' % (afUrl, rootClass);
|
tal:attributes="onClick python: 'href: window.location=\'%s/do?action=Create&className=%s\'' % (toolUrl, rootClass);
|
||||||
src string: $appUrl/skyn/plus.png;
|
src string: $appUrl/ui/plus.png;
|
||||||
title python: _('query_create')"/>
|
title python: _('query_create')"/>
|
||||||
<tal:comment replace="nothing">Create (a) new object(s) by importing data</tal:comment>
|
<tal:comment replace="nothing">Create (a) new object(s) by importing data</tal:comment>
|
||||||
<img style="cursor:pointer"
|
<img style="cursor:pointer"
|
||||||
tal:condition="python: ('import' in createMeans) and userMayAdd"
|
tal:condition="python: ('import' in createMeans) and userMayAdd"
|
||||||
tal:attributes="onClick python: 'href: window.location=\'%s/skyn/import?type_name=%s\'' % (toolUrl, rootClass);
|
tal:attributes="onClick python: 'href: window.location=\'%s/ui/import?className=%s\'' % (toolUrl, rootClass);
|
||||||
src string: $appUrl/skyn/import.png;
|
src string: $appUrl/ui/import.png;
|
||||||
title python: _('query_import')"/>
|
title python: _('query_import')"/>
|
||||||
<tal:comment replace="nothing">Search objects of this type</tal:comment>
|
<tal:comment replace="nothing">Search objects of this type</tal:comment>
|
||||||
<img style="cursor:pointer"
|
<img style="cursor:pointer"
|
||||||
tal:define="showSearch python: tool.getAttr('enableAdvancedSearchFor%s' % rootClass)"
|
tal:define="showSearch python: tool.getAttr('enableAdvancedSearchFor%s' % rootClass)"
|
||||||
tal:condition="showSearch"
|
tal:condition="showSearch"
|
||||||
tal:attributes="onClick python: 'href: window.location=\'%s/skyn/search?type_name=%s\'' % (toolUrl, rootClass);
|
tal:attributes="onClick python: 'href: window.location=\'%s/ui/search?className=%s\'' % (toolUrl, rootClass);
|
||||||
src string: $appUrl/skyn/search.gif;
|
src string: $appUrl/ui/search.gif;
|
||||||
title python: _('search_objects')"/>
|
title python: _('search_objects')"/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -61,7 +60,7 @@
|
||||||
<dt class="portletAppyItem portletGroup">
|
<dt class="portletAppyItem portletGroup">
|
||||||
<img align="left" style="cursor:pointer"
|
<img align="left" style="cursor:pointer"
|
||||||
tal:attributes="id python: '%s_img' % group['labelId'];
|
tal:attributes="id python: '%s_img' % group['labelId'];
|
||||||
src python:test(expanded, 'skyn/collapse.gif', 'skyn/expand.gif');
|
src python:test(expanded, 'ui/collapse.gif', 'ui/expand.gif');
|
||||||
onClick python:'toggleCookie(\'%s\')' % group['labelId']"/>
|
onClick python:'toggleCookie(\'%s\')' % group['labelId']"/>
|
||||||
<span tal:replace="group/label"/>
|
<span tal:replace="group/label"/>
|
||||||
</dt>
|
</dt>
|
||||||
|
@ -69,7 +68,7 @@
|
||||||
<span tal:attributes="id group/labelId;
|
<span tal:attributes="id group/labelId;
|
||||||
style python:test(expanded, 'display:block', 'display:none')">
|
style python:test(expanded, 'display:block', 'display:none')">
|
||||||
<dt class="portletAppyItem portletSearch portletGroupItem" tal:repeat="search group/searches">
|
<dt class="portletAppyItem portletSearch portletGroupItem" tal:repeat="search group/searches">
|
||||||
<a tal:attributes="href python: '%s?type_name=%s&search=%s' % (queryUrl, rootClass, search['name']);
|
<a tal:attributes="href python: '%s?className=%s&search=%s' % (queryUrl, rootClass, search['name']);
|
||||||
title search/descr;
|
title search/descr;
|
||||||
class python: test(search['name'] == currentSearch, 'portletCurrent', '');"
|
class python: test(search['name'] == currentSearch, 'portletCurrent', '');"
|
||||||
tal:content="structure search/label"></a>
|
tal:content="structure search/label"></a>
|
||||||
|
@ -80,7 +79,7 @@
|
||||||
<dt tal:define="search searchOrGroup" tal:condition="not: searchOrGroup/isGroup"
|
<dt tal:define="search searchOrGroup" tal:condition="not: searchOrGroup/isGroup"
|
||||||
class="portletAppyItem portletSearch">
|
class="portletAppyItem portletSearch">
|
||||||
|
|
||||||
<a tal:attributes="href python: '%s?type_name=%s&search=%s' % (queryUrl, rootClass, search['name']);
|
<a tal:attributes="href python: '%s?className=%s&search=%s' % (queryUrl, rootClass, search['name']);
|
||||||
title search/descr;
|
title search/descr;
|
||||||
class python: test(search['name'] == currentSearch, 'portletCurrent', '');"
|
class python: test(search['name'] == currentSearch, 'portletCurrent', '');"
|
||||||
tal:content="structure search/label"></a>
|
tal:content="structure search/label"></a>
|
||||||
|
@ -120,7 +119,7 @@
|
||||||
<td align="right">
|
<td align="right">
|
||||||
<img title="Edit" i18n:domain="plone" i18n:attributes="title" style="cursor:pointer"
|
<img title="Edit" i18n:domain="plone" i18n:attributes="title" style="cursor:pointer"
|
||||||
tal:attributes="onClick python: 'href: window.location=\'%s\'' % contextObj.getUrl(mode='edit', page=pageName);
|
tal:attributes="onClick python: 'href: window.location=\'%s\'' % contextObj.getUrl(mode='edit', page=pageName);
|
||||||
src string: $appUrl/skyn/edit.gif"
|
src string: $appUrl/ui/edit.gif"
|
||||||
tal:condition="python: contextObj.allows('Modify portal content') and phase['pagesInfo'][pageName]['showOnEdit']"/>
|
tal:condition="python: contextObj.allows('Modify portal content') and phase['pagesInfo'][pageName]['showOnEdit']"/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -139,7 +138,7 @@
|
||||||
<td align="right">
|
<td align="right">
|
||||||
<img title="Edit" i18n:domain="plone" i18n:attributes="title" style="cursor:pointer"
|
<img title="Edit" i18n:domain="plone" i18n:attributes="title" style="cursor:pointer"
|
||||||
tal:attributes="onClick python: 'href: window.location=\'%s\'' % contextObj.getUrl(mode='edit', page=aPage);
|
tal:attributes="onClick python: 'href: window.location=\'%s\'' % contextObj.getUrl(mode='edit', page=aPage);
|
||||||
src string: $appUrl/skyn/edit.gif"
|
src string: $appUrl/ui/edit.gif"
|
||||||
tal:condition="python: user.has_permission('Modify portal content', contextObj) and phase['pagesInfo'][aPage]['showOnEdit']"/>
|
tal:condition="python: user.has_permission('Modify portal content', contextObj) and phase['pagesInfo'][aPage]['showOnEdit']"/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -149,7 +148,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tal:comment replace="nothing">The down arrow pointing to the next phase (if any)</tal:comment>
|
<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']">
|
<tr tal:condition="python: phase['name'] != phases[-1]['name']">
|
||||||
<td> <img tal:attributes="src string: $appUrl/skyn/nextPhase.png"/></td>
|
<td> <img tal:attributes="src string: $appUrl/ui/nextPhase.png"/></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tal:phase>
|
</tal:phase>
|
||||||
</table>
|
</table>
|
Before Width: | Height: | Size: 905 B After Width: | Height: | Size: 905 B |
|
@ -1,16 +1,16 @@
|
||||||
<tal:main define="tool context/getParentNode">
|
<tal:main define="tool context/getParentNode">
|
||||||
<html metal:use-macro="context/skyn/template/macros/main">
|
<html metal:use-macro="context/ui/template/macros/main">
|
||||||
<metal:fill fill-slot="content"
|
<metal:fill fill-slot="content"
|
||||||
tal:define="contentType request/type_name;
|
tal:define="className request/className;
|
||||||
searchName request/search|python:''">
|
searchName request/search|python:''">
|
||||||
|
|
||||||
<div metal:use-macro="here/skyn/page/macros/prologue"/>
|
<div metal:use-macro="context/ui/page/macros/prologue"/>
|
||||||
<tal:comment replace="nothing">Query result</tal:comment>
|
<tal:comment replace="nothing">Query result</tal:comment>
|
||||||
<div id="queryResult"></div>
|
<div id="queryResult"></div>
|
||||||
|
|
||||||
<script type="text/javascript"
|
<script type="text/javascript"
|
||||||
tal:define="ajaxUrl python: tool.getQueryUrl(contentType, searchName)"
|
tal:define="ajaxUrl python: tool.getQueryUrl(className, searchName)"
|
||||||
tal:content="python: 'askQueryResult(\'queryResult\', \'%s\',\'%s\',\'%s\',0)' % (tool.absolute_url(), contentType, searchName)">
|
tal:content="python: 'askQueryResult(\'queryResult\', \'%s\',\'%s\',\'%s\',0)' % (tool.absolute_url(), className, searchName)">
|
||||||
</script>
|
</script>
|
||||||
</metal:fill>
|
</metal:fill>
|
||||||
</html>
|
</html>
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 53 B After Width: | Height: | Size: 53 B |
|
@ -1,5 +1,5 @@
|
||||||
<metal:queryResults define-macro="queryResult"
|
<metal:queryResults define-macro="queryResult"
|
||||||
tal:define="contentType request/type_name;
|
tal:define="className request/className;
|
||||||
refInfo tool/getRefInfo;
|
refInfo tool/getRefInfo;
|
||||||
refObject python: refInfo[0];
|
refObject python: refInfo[0];
|
||||||
refField python: refInfo[1];
|
refField python: refInfo[1];
|
||||||
|
@ -7,27 +7,27 @@
|
||||||
startNumber request/startNumber|python:'0';
|
startNumber request/startNumber|python:'0';
|
||||||
startNumber python: int(startNumber);
|
startNumber python: int(startNumber);
|
||||||
searchName request/search;
|
searchName request/search;
|
||||||
labelId python: searchName and ('%s_search_%s' % (contentType, searchName)) or '';
|
labelId python: searchName and ('%s_search_%s' % (className, searchName)) or '';
|
||||||
labelId python: (searchName == '_advanced') and 'search_results' or labelId;
|
labelId python: (searchName == '_advanced') and 'search_results' or labelId;
|
||||||
searchLabel python: labelId and tool.translate(labelId) or '';
|
searchLabel python: labelId and tool.translate(labelId) or '';
|
||||||
severalTypes python: contentType and (contentType.find(',') != -1);
|
severalTypes python: className and (className.find(',') != -1);
|
||||||
sortKey request/sortKey| python:'';
|
sortKey request/sortKey| python:'';
|
||||||
sortOrder request/sortOrder| python:'asc';
|
sortOrder request/sortOrder| python:'asc';
|
||||||
filterKey request/filterKey| python:'';
|
filterKey request/filterKey| python:'';
|
||||||
filterValue request/filterValue | python:'';
|
filterValue request/filterValue | python:'';
|
||||||
queryResult python: tool.executeQuery(contentType, searchName, startNumber, remember=True, sortBy=sortKey, sortOrder=sortOrder, filterKey=filterKey, filterValue=filterValue, refObject=refObject, refField=refField);
|
queryResult python: tool.executeQuery(className, searchName, startNumber, remember=True, sortBy=sortKey, sortOrder=sortOrder, filterKey=filterKey, filterValue=filterValue, refObject=refObject, refField=refField);
|
||||||
objs queryResult/objects;
|
objs queryResult/objects;
|
||||||
totalNumber queryResult/totalNumber;
|
totalNumber queryResult/totalNumber;
|
||||||
batchSize queryResult/batchSize;
|
batchSize queryResult/batchSize;
|
||||||
ajaxHookId python:'queryResult';
|
ajaxHookId python:'queryResult';
|
||||||
navBaseCall python: 'askQueryResult(\'%s\',\'%s\',\'%s\',\'%s\',**v**)' % (ajaxHookId, tool.absolute_url(), contentType, searchName);
|
navBaseCall python: 'askQueryResult(\'%s\',\'%s\',\'%s\',\'%s\',**v**)' % (ajaxHookId, tool.absolute_url(), className, searchName);
|
||||||
newSearchUrl python: '%s/skyn/search?type_name=%s%s' % (tool.absolute_url(), contentType, refUrlPart)">
|
newSearchUrl python: '%s/ui/search?className=%s%s' % (tool.absolute_url(), className, refUrlPart)">
|
||||||
|
|
||||||
<tal:result condition="objs">
|
<tal:result condition="objs">
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>
|
<legend>
|
||||||
<span tal:replace="structure python: test(searchName, searchLabel, test(severalTypes, tool.translate(tool.getAppName()), tool.translate('%s_plural' % contentType)))"/>
|
<span tal:replace="structure python: test(searchName, searchLabel, test(severalTypes, tool.translate(tool.getAppName()), tool.translate('%s_plural' % className)))"/>
|
||||||
(<span tal:replace="totalNumber"/>)
|
(<span tal:replace="totalNumber"/>)
|
||||||
<tal:newSearch condition="python: searchName == '_advanced'">
|
<tal:newSearch condition="python: searchName == '_advanced'">
|
||||||
— <i><a tal:attributes="href newSearchUrl"
|
— <i><a tal:attributes="href newSearchUrl"
|
||||||
|
@ -37,12 +37,12 @@
|
||||||
|
|
||||||
<tal:comment replace="nothing">Display here POD templates if required.</tal:comment>
|
<tal:comment replace="nothing">Display here POD templates if required.</tal:comment>
|
||||||
<table align="right"
|
<table align="right"
|
||||||
tal:define="widgets python: tool.getResultPodFields(contentType);
|
tal:define="widgets python: tool.getResultPodFields(className);
|
||||||
layoutType python:'view'"
|
layoutType python:'view'"
|
||||||
tal:condition="python: objs and widgets">
|
tal:condition="python: objs and widgets">
|
||||||
<tr><td tal:define="contextObj python: objs[0]"
|
<tr><td tal:define="contextObj python: objs[0]"
|
||||||
tal:repeat="widget widgets">
|
tal:repeat="widget widgets">
|
||||||
<metal:pod use-macro="here/skyn/widgets/show/macros/field"/>
|
<metal:pod use-macro="context/ui/widgets/show/macros/field"/>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
@ -56,21 +56,21 @@
|
||||||
</tal:descr>
|
</tal:descr>
|
||||||
<td align="right" width="25%">
|
<td align="right" width="25%">
|
||||||
<tal:comment replace="nothing">Appy (top) navigation</tal:comment>
|
<tal:comment replace="nothing">Appy (top) navigation</tal:comment>
|
||||||
<metal:nav use-macro="here/skyn/navigate/macros/appyNavigate"/>
|
<metal:nav use-macro="context/ui/navigate/macros/appyNavigate"/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<table tal:define="fieldNames python: tool.getResultColumnsNames(contentType, refInfo);
|
<table tal:define="fieldNames python: tool.getResultColumnsNames(className, refInfo);
|
||||||
widgets python: objs[0].getAppyTypesFromNames(fieldNames);"
|
widgets python: objs[0].getAppyTypesFromNames(fieldNames);"
|
||||||
class="list" width="100%">
|
class="list" width="100%">
|
||||||
<tal:comment replace="nothing">Headers, with filters and sort arrows</tal:comment>
|
<tal:comment replace="nothing">Headers, with filters and sort arrows</tal:comment>
|
||||||
<tr>
|
<tr>
|
||||||
<tal:header repeat="widget widgets">
|
<tal:header repeat="widget widgets">
|
||||||
<th tal:define="sortable python: tool.isSortable(widget['name'], contentType, 'search');
|
<th tal:define="sortable python: tool.isSortable(widget['name'], className, 'search');
|
||||||
filterable widget/filterable|nothing;">
|
filterable widget/filterable|nothing;">
|
||||||
<span tal:replace="structure python: tool.truncateText(tool.translate(widget['labelId']))"/>
|
<span tal:replace="structure python: tool.truncateText(tool.translate(widget['labelId']))"/>
|
||||||
<metal:icons use-macro="here/skyn/navigate/macros/sortAndFilter"/>
|
<metal:icons use-macro="context/ui/navigate/macros/sortAndFilter"/>
|
||||||
</th>
|
</th>
|
||||||
</tal:header>
|
</tal:header>
|
||||||
<tal:comment replace="nothing">Object type, shown if instances of several types are shown</tal:comment>
|
<tal:comment replace="nothing">Object type, shown if instances of several types are shown</tal:comment>
|
||||||
|
@ -90,7 +90,7 @@
|
||||||
<tal:comment replace="nothing">Title</tal:comment>
|
<tal:comment replace="nothing">Title</tal:comment>
|
||||||
<td id="field_title"
|
<td id="field_title"
|
||||||
tal:condition="python: widget['name'] == 'title'">
|
tal:condition="python: widget['name'] == 'title'">
|
||||||
<a tal:define="navInfo python:'search.%s.%s.%d.%d' % (contentType, searchName, repeat['obj'].number()+startNumber, totalNumber);"
|
<a tal:define="navInfo python:'search.%s.%s.%d.%d' % (className, searchName, repeat['obj'].number()+startNumber, totalNumber);"
|
||||||
tal:content="obj/Title" tal:attributes="href python: obj.getUrl(nav=navInfo, page='main')"></a>
|
tal:content="obj/Title" tal:attributes="href python: obj.getUrl(nav=navInfo, page='main')"></a>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
@ -107,7 +107,7 @@
|
||||||
layoutType python:'cell';
|
layoutType python:'cell';
|
||||||
innerRef python:True"
|
innerRef python:True"
|
||||||
condition="python: contextObj.showField(widget['name'], 'view')">
|
condition="python: contextObj.showField(widget['name'], 'view')">
|
||||||
<metal:field use-macro="here/skyn/widgets/show/macros/field"/>
|
<metal:field use-macro="context/ui/widgets/show/macros/field"/>
|
||||||
</tal:field>
|
</tal:field>
|
||||||
</td>
|
</td>
|
||||||
</tal:fields>
|
</tal:fields>
|
||||||
|
@ -122,17 +122,17 @@
|
||||||
<tr>
|
<tr>
|
||||||
<tal:comment replace="nothing">Edit the element</tal:comment>
|
<tal:comment replace="nothing">Edit the element</tal:comment>
|
||||||
<td>
|
<td>
|
||||||
<a tal:define="navInfo python:'search.%s.%s.%d.%d' % (contentType, searchName, repeat['obj'].number()+startNumber, totalNumber);"
|
<a tal:define="navInfo python:'search.%s.%s.%d.%d' % (className, searchName, repeat['obj'].number()+startNumber, totalNumber);"
|
||||||
tal:attributes="href python: obj.getUrl(mode='edit', page='main', nav=navInfo)"
|
tal:attributes="href python: obj.getUrl(mode='edit', page='main', nav=navInfo)"
|
||||||
tal:condition="python: obj.allows('Modify portal content')">
|
tal:condition="python: obj.allows('Modify portal content')">
|
||||||
<img title="Edit" i18n:domain="plone" i18n:attributes="title"
|
<img title="Edit" i18n:domain="plone" i18n:attributes="title"
|
||||||
tal:attributes="src string: $appUrl/skyn/edit.gif"/>
|
tal:attributes="src string: $appUrl/ui/edit.gif"/>
|
||||||
</a></td>
|
</a></td>
|
||||||
<tal:comment replace="nothing">Delete the element</tal:comment>
|
<tal:comment replace="nothing">Delete the element</tal:comment>
|
||||||
<td>
|
<td>
|
||||||
<img tal:condition="python: obj.allows('Delete objects') and obj.mayDelete()"
|
<img tal:condition="python: obj.allows('Delete objects') and obj.mayDelete()"
|
||||||
title="Delete" i18n:domain="plone" i18n:attributes="title" style="cursor:pointer"
|
title="Delete" i18n:domain="plone" i18n:attributes="title" style="cursor:pointer"
|
||||||
tal:attributes="src string: $appUrl/skyn/delete.png;
|
tal:attributes="src string: $appUrl/ui/delete.png;
|
||||||
onClick python:'onDeleteObject(\'%s\')' % obj.UID()"/>
|
onClick python:'onDeleteObject(\'%s\')' % obj.UID()"/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -143,7 +143,7 @@
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<tal:comment replace="nothing">Appy (bottom) navigation</tal:comment>
|
<tal:comment replace="nothing">Appy (bottom) navigation</tal:comment>
|
||||||
<metal:nav use-macro="here/skyn/navigate/macros/appyNavigate"/>
|
<metal:nav use-macro="here/ui/navigate/macros/appyNavigate"/>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</tal:result>
|
</tal:result>
|
||||||
|
|
Before Width: | Height: | Size: 225 B After Width: | Height: | Size: 225 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 189 B After Width: | Height: | Size: 189 B |
|
@ -1,25 +1,25 @@
|
||||||
<tal:main define="tool context/getParentNode">
|
<tal:main define="tool context/getParentNode">
|
||||||
<html metal:use-macro="context/skyn/template/macros/main">
|
<html metal:use-macro="context/ui/template/macros/main">
|
||||||
<metal:fill fill-slot="content"
|
<metal:fill fill-slot="content"
|
||||||
tal:define="contentType request/type_name;
|
tal:define="className request/className;
|
||||||
refInfo request/ref|nothing;
|
refInfo request/ref|nothing;
|
||||||
searchInfo python: tool.getSearchInfo(contentType, refInfo);
|
searchInfo python: tool.getSearchInfo(className, refInfo);
|
||||||
cssJs python: tool.getCssAndJs(searchInfo['fields'], 'edit')">
|
cssJs python: tool.getCssAndJs(searchInfo['fields'], 'edit')">
|
||||||
|
|
||||||
<tal:comment replace="nothing">Include type-specific CSS and JS.</tal:comment>
|
<tal:comment replace="nothing">Include type-specific CSS and JS.</tal:comment>
|
||||||
<link tal:repeat="cssFile cssJs/css" rel="stylesheet" type="text/css"
|
<link tal:repeat="cssFile cssJs/css" rel="stylesheet" type="text/css"
|
||||||
tal:attributes="href string:$appUrl/skyn/$cssFile"/>
|
tal:attributes="href string:$appUrl/ui/$cssFile"/>
|
||||||
<script tal:repeat="jsFile cssJs/js" type="text/javascript"
|
<script tal:repeat="jsFile cssJs/js" type="text/javascript"
|
||||||
tal:attributes="src string:$appUrl/skyn/$jsFile"></script>
|
tal:attributes="src string:$appUrl/ui/$jsFile"></script>
|
||||||
|
|
||||||
<tal:comment replace="nothing">Search title</tal:comment>
|
<tal:comment replace="nothing">Search title</tal:comment>
|
||||||
<h1><span tal:replace="python: tool.translate('%s_plural' % contentType)"/> —
|
<h1><span tal:replace="python: tool.translate('%s_plural' % className)"/> —
|
||||||
<span tal:replace="python: tool.translate('search_title')"/></h1><br/>
|
<span tal:replace="python: tool.translate('search_title')"/></h1><br/>
|
||||||
|
|
||||||
<tal:comment replace="nothing">Form for searching objects of request/type_name.</tal:comment>
|
<tal:comment replace="nothing">Form for searching objects of request/className.</tal:comment>
|
||||||
<form name="search" tal:attributes="action python: appFolder.absolute_url()+'/skyn/do'" method="post">
|
<form name="search" tal:attributes="action python: tool.absolute_url()+'/do'" method="post">
|
||||||
<input type="hidden" name="action" value="SearchObjects"/>
|
<input type="hidden" name="action" value="SearchObjects"/>
|
||||||
<input type="hidden" name="type_name" tal:attributes="value contentType"/>
|
<input type="hidden" name="className" tal:attributes="value className"/>
|
||||||
<input tal:condition="refInfo" type="hidden" name="ref" tal:attributes="value refInfo"/>
|
<input tal:condition="refInfo" type="hidden" name="ref" tal:attributes="value refInfo"/>
|
||||||
|
|
||||||
<table width="100%">
|
<table width="100%">
|
||||||
|
@ -30,7 +30,7 @@
|
||||||
<tal:show define="name widget/name;
|
<tal:show define="name widget/name;
|
||||||
widgetName python: 'w_%s' % name;
|
widgetName python: 'w_%s' % name;
|
||||||
macroPage python: widget['type'].lower()">
|
macroPage python: widget['type'].lower()">
|
||||||
<metal:call use-macro="python: appFolder.skyn.widgets.get(macroPage).macros.get('search')"/>
|
<metal:call use-macro="python: getattr(appFolder.ui.widgets, macroPage).macros['search']"/>
|
||||||
</tal:show>
|
</tal:show>
|
||||||
</tal:field><br class="discreet"/>
|
</tal:field><br class="discreet"/>
|
||||||
</td>
|
</td>
|
Before Width: | Height: | Size: 235 B After Width: | Height: | Size: 235 B |
Before Width: | Height: | Size: 367 B After Width: | Height: | Size: 367 B |
Before Width: | Height: | Size: 316 B After Width: | Height: | Size: 316 B |
Before Width: | Height: | Size: 97 B After Width: | Height: | Size: 97 B |
Before Width: | Height: | Size: 97 B After Width: | Height: | Size: 97 B |
Before Width: | Height: | Size: 43 B After Width: | Height: | Size: 43 B |
Before Width: | Height: | Size: 149 B After Width: | Height: | Size: 149 B |
Before Width: | Height: | Size: 168 B After Width: | Height: | Size: 168 B |
Before Width: | Height: | Size: 203 B After Width: | Height: | Size: 203 B |
Before Width: | Height: | Size: 180 B After Width: | Height: | Size: 180 B |
Before Width: | Height: | Size: 207 B After Width: | Height: | Size: 207 B |
Before Width: | Height: | Size: 171 B After Width: | Height: | Size: 171 B |
|
@ -1,10 +1,10 @@
|
||||||
<html metal:define-macro="main"
|
<html metal:define-macro="main"
|
||||||
tal:define="user context/portal_membership/getAuthenticatedMember;
|
tal:define="user tool/getUser;
|
||||||
isAnon python: user.getUserName() == 'Anonymous User';
|
isAnon tool/userIsAnon;
|
||||||
app tool/getApp;
|
app tool/getApp;
|
||||||
appUrl app/absolute_url;
|
appUrl app/absolute_url;
|
||||||
appFolder tool/getAppFolder;
|
appFolder app/data;
|
||||||
appName appFolder/getId;
|
appName tool/getAppName;
|
||||||
_ python: tool.translate;
|
_ python: tool.translate;
|
||||||
req python: request;
|
req python: request;
|
||||||
resp req/RESPONSE;
|
resp req/RESPONSE;
|
||||||
|
@ -14,8 +14,8 @@
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title tal:content="tool/getAppName"></title>
|
<title tal:content="tool/getAppName"></title>
|
||||||
<link rel="stylesheet" type="text/css" tal:attributes="href string:$appUrl/skyn/appy.css"/>
|
<link rel="stylesheet" type="text/css" tal:attributes="href string:$appUrl/ui/appy.css"/>
|
||||||
<script type="text/javascript" tal:attributes="src string:$appUrl/skyn/appy.js"></script>
|
<script type="text/javascript" tal:attributes="src string:$appUrl/ui/appy.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
@ -26,30 +26,29 @@
|
||||||
<table width="100%">
|
<table width="100%">
|
||||||
<tr valign="top">
|
<tr valign="top">
|
||||||
<tal:comment replace="nothing">Logo</tal:comment>
|
<tal:comment replace="nothing">Logo</tal:comment>
|
||||||
<td><a tal:attributes="href appUrl"><img tal:attributes="src string: $appUrl/skyn/logo.jpg"/></a></td>
|
<td><a tal:attributes="href appUrl"><img tal:attributes="src string: $appUrl/ui/logo.jpg"/></a></td>
|
||||||
<tal:comment replace="nothing">Language selector (links or listbox)</tal:comment>
|
<tal:comment replace="nothing">Language selector (links or listbox)</tal:comment>
|
||||||
<td align="right"
|
<td align="right"
|
||||||
tal:define="appLangs app/portal_languages/listSupportedLanguages;
|
tal:define="languages tool/getLanguages;
|
||||||
defLang python: app.portal_languages.getLanguageBindings()[0];
|
defaultLanguage python: languages[0];
|
||||||
suffix python: req.get('ACTUAL_URL').split('/')[-1];
|
suffix python: req.get('ACTUAL_URL').split('/')[-1];
|
||||||
asLinks python: len(appLangs) <= 5"
|
asLinks python: len(languages) <= 5"
|
||||||
tal:condition="python: len(appLangs) >= 2 and (suffix not in ('edit', 'query', 'search'))">
|
tal:condition="python: len(languages) >= 2 and (suffix not in ('edit', 'query', 'search'))">
|
||||||
<table tal:condition="asLinks">
|
<table tal:condition="asLinks">
|
||||||
<tr>
|
<tr>
|
||||||
<td tal:repeat="lang appLangs">
|
<td tal:repeat="lang languages">
|
||||||
<a class="lang"
|
<a class="lang"
|
||||||
tal:attributes="href python: req.get('ACTUAL_URL')+'/switchLanguage?set_language=%s' % lang[0];
|
tal:attributes="href python: req.get('ACTUAL_URL')+'/switchLanguage?set_language=%s' % lang;
|
||||||
title python: app.portal_languages.getNameForLanguageCode(lang[1])"
|
title python: tool.getLanguageName(lang)"
|
||||||
tal:content="python: lang[0]"></a>
|
tal:content="python: lang"></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<select tal:condition="not: asLinks"
|
<select tal:condition="not: asLinks"
|
||||||
tal:attributes="onchange string:window.location='${context/absolute_url}/switchLanguage?set_language=' + this.options[this.selectedIndex].value">
|
tal:attributes="onchange string:window.location='${context/absolute_url}/switchLanguage?set_language=' + this.options[this.selectedIndex].value">
|
||||||
<option tal:repeat="lang appLangs"
|
<option tal:repeat="lang languages"
|
||||||
tal:content="python:app.portal_languages.getNameForLanguageCode(lang[0]) or lang[1]"
|
tal:content="python: tool.getLanguageName(lang)"
|
||||||
tal:attributes="selected python:defLanguage == lang[0];
|
tal:attributes="selected python:defaultLanguage == lang; value lang">
|
||||||
value python:lang[0]">
|
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
|
@ -61,7 +60,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<div style="position: relative" align="right">
|
<div style="position: relative" align="right">
|
||||||
<metal:msg use-macro="app/skyn/page/macros/message"/>
|
<metal:msg use-macro="app/ui/page/macros/message"/>
|
||||||
</div>
|
</div>
|
||||||
<tal:comment replace="nothing">Grey background shown when popups are shown</tal:comment>
|
<tal:comment replace="nothing">Grey background shown when popups are shown</tal:comment>
|
||||||
<div id="grey" class="grey"></div>
|
<div id="grey" class="grey"></div>
|
||||||
|
@ -89,8 +88,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<tal:comment replace="nothing">The user login form for anonymous users</tal:comment>
|
<tal:comment replace="nothing">The user login form for anonymous users</tal:comment>
|
||||||
<table align="center" tal:condition="isAnon" class="login"
|
<table align="center" tal:condition="isAnon" class="login">
|
||||||
tal:define="auth nocall:app/acl_users/credentials_cookie_auth">
|
|
||||||
<tr><td>
|
<tr><td>
|
||||||
<form name="loginform" method="post"
|
<form name="loginform" method="post"
|
||||||
tal:attributes="action python: tool.absolute_url() + '/performLogin'">
|
tal:attributes="action python: tool.absolute_url() + '/performLogin'">
|
||||||
|
@ -115,17 +113,17 @@
|
||||||
<td>
|
<td>
|
||||||
<!-- Go home -->
|
<!-- Go home -->
|
||||||
<a tal:attributes="href appUrl; title python: _('home')">
|
<a tal:attributes="href appUrl; title python: _('home')">
|
||||||
<img tal:attributes="src string: $appUrl/skyn/home.gif"/>
|
<img tal:attributes="src string: $appUrl/ui/home.gif"/>
|
||||||
</a>
|
</a>
|
||||||
<!-- Config -->
|
<!-- Config -->
|
||||||
<img style="cursor:pointer" tal:condition="python: user.has_role('Manager')"
|
<img style="cursor:pointer" tal:condition="python: user.has_role('Manager')"
|
||||||
tal:attributes="onClick python: 'href: window.location=\'%s\'' % tool.getUrl(page='main', nav='');
|
tal:attributes="onClick python: 'href: window.location=\'%s\'' % tool.getUrl(page='main', nav='');
|
||||||
title python: _('%sTool' % appName);
|
title python: _('%sTool' % appName);
|
||||||
src string:$appUrl/skyn/appyConfig.gif"/>
|
src string:$appUrl/ui/appyConfig.gif"/>
|
||||||
<!-- Logout -->
|
<!-- Logout -->
|
||||||
<a tal:attributes="href python: tool.absolute_url() + '/performLogout';
|
<a tal:attributes="href python: tool.absolute_url() + '/performLogout';
|
||||||
title python: _('logout')">
|
title python: _('logout')">
|
||||||
<img tal:attributes="src string: $appUrl/skyn/logout.gif"/>
|
<img tal:attributes="src string: $appUrl/ui/logout.gif"/>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td align="right" tal:content="python: tool.getUserLine(user)"></td>
|
<td align="right" tal:content="python: tool.getUserLine(user)"></td>
|
||||||
|
@ -142,7 +140,7 @@
|
||||||
<tr valign="top">
|
<tr valign="top">
|
||||||
<tal:comment replace="nothing">Portlet</tal:comment>
|
<tal:comment replace="nothing">Portlet</tal:comment>
|
||||||
<td tal:condition="python: tool.showPortlet(context)" class="portlet">
|
<td tal:condition="python: tool.showPortlet(context)" class="portlet">
|
||||||
<metal:portlet use-macro="app/skyn/portlet/macros/portlet"/>
|
<metal:portlet use-macro="app/ui/portlet/macros/portlet"/>
|
||||||
</td>
|
</td>
|
||||||
<tal:comment replace="nothing">Page content</tal:comment>
|
<tal:comment replace="nothing">Page content</tal:comment>
|
||||||
<td class="content"><span metal:define-slot="content"></span></td>
|
<td class="content"><span metal:define-slot="content"></span></td>
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |