appy.gen: first Ploneless version.

This commit is contained in:
Gaetan Delannay 2011-11-25 18:01:20 +01:00
parent 5672c81553
commit d0cbe7e573
360 changed files with 1003 additions and 1017 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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()
@ -1089,6 +1108,68 @@ class BaseMixin:
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'
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View file

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

View file

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

View file

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

View file

@ -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;
} }

View file

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

View file

@ -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!>]

View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 339 B

View file

@ -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.'''

View file

@ -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('<','&lt;').replace('>','&gt;') sourceMsg = sourceMsg.replace('<','&lt;').replace('>','&gt;')
sourceMsg = sourceMsg.replace('\n', '<br/>') sourceMsg = sourceMsg.replace('\n', '<br/>')
return '<div class="translationLabel"><acronym title="%s">' \ return '<div class="translationLabel"><acronym title="%s">' \

View file

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

View file

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

View file

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

View file

@ -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;}

View file

@ -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";
} }
} }

View file

Before

Width:  |  Height:  |  Size: 899 B

After

Width:  |  Height:  |  Size: 899 B

View file

Before

Width:  |  Height:  |  Size: 232 B

After

Width:  |  Height:  |  Size: 232 B

View file

Before

Width:  |  Height:  |  Size: 212 B

After

Width:  |  Height:  |  Size: 212 B

View file

Before

Width:  |  Height:  |  Size: 218 B

After

Width:  |  Height:  |  Size: 218 B

View file

Before

Width:  |  Height:  |  Size: 212 B

After

Width:  |  Height:  |  Size: 212 B

View file

Before

Width:  |  Height:  |  Size: 216 B

After

Width:  |  Height:  |  Size: 216 B

View file

Before

Width:  |  Height:  |  Size: 232 B

After

Width:  |  Height:  |  Size: 232 B

View file

Before

Width:  |  Height:  |  Size: 953 B

After

Width:  |  Height:  |  Size: 953 B

View file

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View file

Before

Width:  |  Height:  |  Size: 70 B

After

Width:  |  Height:  |  Size: 70 B

View file

Before

Width:  |  Height:  |  Size: 244 B

After

Width:  |  Height:  |  Size: 244 B

View file

Before

Width:  |  Height:  |  Size: 239 B

After

Width:  |  Height:  |  Size: 239 B

View file

Before

Width:  |  Height:  |  Size: 818 B

After

Width:  |  Height:  |  Size: 818 B

View file

Before

Width:  |  Height:  |  Size: 248 B

After

Width:  |  Height:  |  Size: 248 B

View file

Before

Width:  |  Height:  |  Size: 475 B

After

Width:  |  Height:  |  Size: 475 B

View file

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

View file

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

Before

Width:  |  Height:  |  Size: 72 B

After

Width:  |  Height:  |  Size: 72 B

View file

Before

Width:  |  Height:  |  Size: 372 B

After

Width:  |  Height:  |  Size: 372 B

View file

Before

Width:  |  Height:  |  Size: 62 B

After

Width:  |  Height:  |  Size: 62 B

BIN
gen/ui/folder.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 903 B

View file

Before

Width:  |  Height:  |  Size: 719 B

After

Width:  |  Height:  |  Size: 719 B

View file

Before

Width:  |  Height:  |  Size: 225 B

After

Width:  |  Height:  |  Size: 225 B

View file

Before

Width:  |  Height:  |  Size: 591 B

After

Width:  |  Height:  |  Size: 591 B

View file

Before

Width:  |  Height:  |  Size: 589 B

After

Width:  |  Height:  |  Size: 589 B

View file

Before

Width:  |  Height:  |  Size: 705 B

After

Width:  |  Height:  |  Size: 705 B

View file

@ -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';

View file

Before

Width:  |  Height:  |  Size: 51 B

After

Width:  |  Height:  |  Size: 51 B

View file

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View file

Before

Width:  |  Height:  |  Size: 636 B

After

Width:  |  Height:  |  Size: 636 B

View file

@ -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">&nbsp; <td class="discreet">&nbsp;
<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)"/>&nbsp;<b>//</b> <span tal:replace="python: startNumber+len(objs)"/>&nbsp;<b>//</b>
<span tal:replace="python: totalNumber"/>&nbsp;&nbsp; <span tal:replace="python: totalNumber"/>&nbsp;&nbsp;
</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 &lt; totalNumber"><img style="cursor:pointer" tal:condition="python: sNumber &lt; 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">&nbsp; <td class="discreet">&nbsp;
@ -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>

View file

Before

Width:  |  Height:  |  Size: 951 B

After

Width:  |  Height:  |  Size: 951 B

View file

Before

Width:  |  Height:  |  Size: 211 B

After

Width:  |  Height:  |  Size: 211 B

View file

Before

Width:  |  Height:  |  Size: 192 B

After

Width:  |  Height:  |  Size: 192 B

BIN
gen/ui/object.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 B

View file

Before

Width:  |  Height:  |  Size: 754 B

After

Width:  |  Height:  |  Size: 754 B

View file

@ -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"/>&nbsp; id="appyHistory_img"/>&nbsp;
<span i18n:translate="label_history" i18n:domain="plone" class="historyLabel">History</span> ||&nbsp; <span i18n:translate="label_history" i18n:domain="plone" class="historyLabel">History</span> ||&nbsp;
</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"/>
&mdash; &mdash;
</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>

View file

Before

Width:  |  Height:  |  Size: 228 B

After

Width:  |  Height:  |  Size: 228 B

View file

Before

Width:  |  Height:  |  Size: 246 B

After

Width:  |  Height:  |  Size: 246 B

View file

@ -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']"/>&nbsp; onClick python:'toggleCookie(\'%s\')' % group['labelId']"/>&nbsp;
<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>&nbsp;&nbsp;<img tal:attributes="src string: $appUrl/skyn/nextPhase.png"/></td> <td>&nbsp;&nbsp;<img tal:attributes="src string: $appUrl/ui/nextPhase.png"/></td>
</tr> </tr>
</tal:phase> </tal:phase>
</table> </table>

View file

Before

Width:  |  Height:  |  Size: 905 B

After

Width:  |  Height:  |  Size: 905 B

View file

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

View file

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

Before

Width:  |  Height:  |  Size: 53 B

After

Width:  |  Height:  |  Size: 53 B

View file

@ -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'">
&nbsp;&mdash;&nbsp;<i><a tal:attributes="href newSearchUrl" &nbsp;&mdash;&nbsp;<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"/>&nbsp;&nbsp;&nbsp; <metal:pod use-macro="context/ui/widgets/show/macros/field"/>&nbsp;&nbsp;&nbsp;
</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>

View file

Before

Width:  |  Height:  |  Size: 225 B

After

Width:  |  Height:  |  Size: 225 B

View file

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

Before

Width:  |  Height:  |  Size: 189 B

After

Width:  |  Height:  |  Size: 189 B

View file

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

View file

Before

Width:  |  Height:  |  Size: 235 B

After

Width:  |  Height:  |  Size: 235 B

View file

Before

Width:  |  Height:  |  Size: 367 B

After

Width:  |  Height:  |  Size: 367 B

View file

Before

Width:  |  Height:  |  Size: 316 B

After

Width:  |  Height:  |  Size: 316 B

View file

Before

Width:  |  Height:  |  Size: 97 B

After

Width:  |  Height:  |  Size: 97 B

View file

Before

Width:  |  Height:  |  Size: 97 B

After

Width:  |  Height:  |  Size: 97 B

View file

Before

Width:  |  Height:  |  Size: 43 B

After

Width:  |  Height:  |  Size: 43 B

View file

Before

Width:  |  Height:  |  Size: 149 B

After

Width:  |  Height:  |  Size: 149 B

View file

Before

Width:  |  Height:  |  Size: 168 B

After

Width:  |  Height:  |  Size: 168 B

View file

Before

Width:  |  Height:  |  Size: 203 B

After

Width:  |  Height:  |  Size: 203 B

View file

Before

Width:  |  Height:  |  Size: 180 B

After

Width:  |  Height:  |  Size: 180 B

View file

Before

Width:  |  Height:  |  Size: 207 B

After

Width:  |  Height:  |  Size: 207 B

View file

Before

Width:  |  Height:  |  Size: 171 B

After

Width:  |  Height:  |  Size: 171 B

View file

@ -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) &lt;= 5" asLinks python: len(languages) &lt;= 5"
tal:condition="python: len(appLangs) &gt;= 2 and (suffix not in ('edit', 'query', 'search'))"> tal:condition="python: len(languages) &gt;= 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>

View file

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Some files were not shown because too many files have changed in this diff Show more