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.'''
if fieldName == 'title':
if usage == 'search': return 'Title'
else: return 'sortable_title'
# Indeed, for field 'title', Plone has created a specific index
# 'sortable_title', because index 'Title' is a ZCTextIndex
else: return 'SortableTitle'
# Indeed, for field 'title', Appy has a specific index
# 'SortableTitle', because index 'Title' is a ZCTextIndex
# (for searchability) and can't be used for sorting.
elif fieldName == 'description':
if usage == 'search': return 'Description'
@ -1229,12 +1229,12 @@ class String(Type):
res = Type.getIndexValue(self, obj, forSearch)
if isinstance(res, unicode):
res = res.encode('utf-8')
# Ugly portal_catalog: if I give an empty tuple as index value,
# portal_catalog keeps the previous value! If I give him a tuple
# containing an empty string, it is ok.
# Ugly catalog: if I give an empty tuple as index value, it keeps the
# previous value. If I give him a tuple containing an empty string, it
# is ok.
if isinstance(res, tuple) and not res: res = self.emptyStringTuple
# Ugly portal_catalog: if value is an empty string or None, it keeps
# the previous index value!
# Ugly catalog: if value is an empty string or None, it keeps the
# previous index value.
if res in self.emptyValuesCatalogIgnored: res = ' '
return res
@ -1757,7 +1757,7 @@ class Ref(Type):
if type == 'uids':
ref = uids[i]
else:
ref = obj.uid_catalog(UID=uids[i])[0].getObject()
ref = obj.getTool().getObject(uids[i])
if type == 'objects':
ref = ref.appy()
res.objects.append(ref)
@ -1907,14 +1907,14 @@ class Computed(Type):
def callMacro(self, obj, macroPath):
'''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
macroPage = obj.skyn.callMacro
macroPage = obj.ui.callMacro
# Get, from p_macroPath, the page where the macro lies, and the macro
# name.
names = self.method.split('/')
# Get the page where the macro lies
page = obj.skyn
page = obj.ui
for name in names[:-1]:
page = getattr(page, name)
macroName = names[-1]
@ -2140,7 +2140,7 @@ class Pod(Type):
exec cmd
# (re-)execute the query, but without any limit on the number of
# 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,
filterValue=filterValue, maxResults='NO_LIMIT')
podContext['objects'] = [o.appy() for o in objs['objects']]
@ -2376,7 +2376,7 @@ class State:
effectively updated.'''
attr = Permission.getZopeAttrName(zopePermission)
if not hasattr(obj.aq_base, attr) or \
(getattr(obj.aq_base, attr) != roleNames):
(getattr(obj.aq_base, attr) != roleNames):
setattr(obj, attr, roleNames)
return True
return False
@ -2476,7 +2476,7 @@ class Transition:
wf = wf.__instance__ # We need the prototypical instance here.
# Checks that the current state of the object is a start state for this
# transition.
objState = obj.getState(name=False)
objState = obj.State(name=False)
if self.isSingle():
if objState != self.states[0]: return False
else:
@ -2487,7 +2487,7 @@ class Transition:
break
if not startFound: return False
# Check that the condition is met
user = obj.portal_membership.getAuthenticatedMember()
user = obj.getUser()
if isinstance(self.condition, Role):
# Condition is a role. Transition may be triggered if the user has
# this role.
@ -2552,7 +2552,7 @@ class Transition:
targetState = self.states[1]
targetStateName = targetState.getName(wf)
else:
startState = obj.getState(name=False)
startState = obj.State(name=False)
for sState, tState in self.states:
if startState == sState:
targetState = tState
@ -2568,7 +2568,7 @@ class Transition:
targetState.updatePermissions(wf, obj)
# Refresh catalog-related security if required
if not obj.isTemporary():
obj.reindexObject(idxs=('allowedRolesAndUsers', 'getState'))
obj.reindex(indexes=('allowedRolesAndUsers', 'State'))
# Execute the related action if needed
msg = ''
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
Plone 2.5-compliant product.'''
'''This file contains the main Generator class used for generating a Zope
product.'''
# ------------------------------------------------------------------------------
import os, os.path, re, sys
@ -13,20 +13,6 @@ from descriptors import ClassDescriptor, ToolClassDescriptor, \
UserClassDescriptor, TranslationClassDescriptor
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):
'''This generator generates a Plone 2.5-compliant product from a given
@ -45,15 +31,10 @@ class Generator(AbstractGenerator):
# i18n labels to generate
self.labels = [] # i18n labels
self.toolInstanceName = 'portal_%s' % self.applicationName.lower()
self.skinsFolder = 'skins/%s' % self.applicationName
# The following dict, pre-filled in the abstract generator, contains a
# series of replacements that need to be applied to file templates to
# generate files.
commonMethods = COMMON_METHODS % \
(self.toolInstanceName, self.applicationName)
self.repls.update(
{'toolInstanceName': self.toolInstanceName,
'commonMethods': commonMethods})
self.repls.update({'toolInstanceName': self.toolInstanceName})
self.referers = {}
versionRex = re.compile('(.*?\s+build)\s+(\d+)')
@ -160,14 +141,12 @@ class Generator(AbstractGenerator):
self.generateTool()
self.generateInit()
self.generateTests()
if self.config.frontPage: self.generateFrontPage()
self.copyFile('Install.py', self.repls, destFolder='Extensions')
self.generateConfigureZcml()
self.copyFile('import_steps.xml', self.repls,
destFolder='profiles/default')
self.copyFile('ProfileInit.py', self.repls, destFolder='profiles',
destName='__init__.py')
self.copyFile('tool.gif', {})
# Create version.txt
f = open(os.path.join(self.outputFolder, 'version.txt'), 'w')
f.write(self.version)
@ -334,7 +313,7 @@ class Generator(AbstractGenerator):
['"%s"' % r for r in self.config.defaultCreators])
# Compute list of add permissions
addPermissions = ''
for classDescr in classesButTool:
for classDescr in classesAll:
addPermissions += ' "%s":"%s: Add %s",\n' % (classDescr.name,
self.applicationName, classDescr.name)
repls['addPermissions'] = addPermissions
@ -492,30 +471,6 @@ class Generator(AbstractGenerator):
repls['modulesWithTests'] = ','.join(modules)
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):
'''Generates the Plone tool that corresponds to this application.'''
Msg = PoMessage
@ -536,13 +491,9 @@ class Generator(AbstractGenerator):
Msg('%s_plural' % klass.name,'', klass.name+'s')]
repls = self.repls.copy()
repls.update({'methods': klass.methods, 'genClassName': klass.name,
'imports': '','baseMixin':'BaseMixin', 'baseSchema': 'BaseSchema',
'global_allow': 1, 'parents': 'BaseMixin, BaseContent',
'static': '',
'baseMixin':'BaseMixin', 'parents': 'BaseMixin, SimpleItem',
'classDoc': 'User class for %s' % self.applicationName,
'implements': "(getattr(BaseContent,'__implements__',()),)",
'register': "registerType(%s, '%s')" % (klass.name,
self.applicationName)})
'icon':'object.gif'})
self.copyFile('Class.py', repls, destName='%s.py' % klass.name)
# Before generating the Tool class, finalize it with query result
@ -567,18 +518,9 @@ class Generator(AbstractGenerator):
# Generate the Tool class
repls = self.repls.copy()
repls.update({'methods': self.tool.methods,
'genClassName': self.tool.name, 'imports':'', 'baseMixin':'ToolMixin',
'baseSchema': 'OrderedBaseFolderSchema', 'global_allow': 0,
'parents': 'ToolMixin, UniqueObject, OrderedBaseFolder',
'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)})
'genClassName': self.tool.name, 'baseMixin':'ToolMixin',
'parents': 'ToolMixin, Folder', 'icon': 'folder.gif',
'classDoc': 'Tool class for %s' % self.applicationName})
self.copyFile('Class.py', repls, destName='%s.py' % self.tool.name)
def generateClass(self, classDescr):
@ -588,45 +530,19 @@ class Generator(AbstractGenerator):
print 'Generating %s.%s (gen-class)...' % (k.__module__, k.__name__)
if not classDescr.isAbstract():
self.tool.addWorkflowFields(classDescr)
# Determine base archetypes schema and class
baseClass = 'BaseContent'
baseSchema = 'BaseSchema'
if classDescr.isFolder():
baseClass = 'OrderedBaseFolder'
baseSchema = 'OrderedBaseFolderSchema'
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 = ''
# Determine base Zope class
isFolder = classDescr.isFolder()
baseClass = isFolder and 'Folder' or 'SimpleItem'
icon = isFolder and 'folder.gif' or 'object.gif'
parents = 'BaseMixin, %s' % baseClass
classDoc = classDescr.klass.__doc__ or 'Appy class.'
repls = self.repls.copy()
classDescr.generateSchema()
repls.update({
'imports': '\n'.join(imports), 'parents': parents,
'className': classDescr.klass.__name__, 'global_allow': 1,
'parents': parents, 'className': classDescr.klass.__name__,
'genClassName': classDescr.name, 'baseMixin':'BaseMixin',
'classDoc': classDoc, 'applicationName': self.applicationName,
'methods': classDescr.methods, 'implements': implements,
'baseSchema': baseSchema, 'static': '', 'register': register,
'toolInstanceName': self.toolInstanceName})
'methods': classDescr.methods, 'icon':icon})
fileName = '%s.py' % classDescr.name
# Create i18n labels (class name and plural form)
poMsg = PoMessage(classDescr.name, '', classDescr.klass.__name__)

View file

@ -7,18 +7,38 @@ from StringIO import StringIO
from sets import Set
import appy
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.utils import produceNiceMessage, updateRolesForPermission
from appy.gen.utils import produceNiceMessage, updateRolesForPermission, \
createObject
from appy.shared.data import languages
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:
'''This Plone installer runs every time the generated Plone product is
installed or uninstalled (in the Plone configuration interface).'''
@ -40,78 +60,11 @@ class PloneInstaller:
self.attributes = cfg.attributes
# A buffer for logging purposes
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.appyTool = None # The Appy version of the application tool
self.toolName = '%sTool' % self.productName
self.toolInstanceName = 'portal_%s' % self.productName.lower()
@staticmethod
def updateIndexes(ploneSite, indexInfo, logger):
'''This method creates or updates, in a p_ploneSite, definitions of
indexes in its portal_catalog, based on index-related information
given in p_indexInfo. p_indexInfo is a dictionary of the form
{s_indexName:s_indexType}. Here are some examples of index types:
"FieldIndex", "ZCTextIndex", "DateIndex".'''
catalog = ploneSite.portal_catalog
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):
'''Creates and/or configures, at the root of the Plone site and if
needed, the folder where the application will store instances of
@ -158,128 +111,6 @@ class PloneInstaller:
# have the main permission "Add portal content".
permission = 'Add portal content'
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):
'''Registers roles used by workflows and classes defined in this
@ -305,33 +136,6 @@ class PloneInstaller:
site.portal_groups.setRolesForGroup(group, [role])
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):
'''Performs some final installation steps.'''
site = self.ploneSite
@ -345,26 +149,6 @@ class PloneInstaller:
self.appyTool.appyVersion = appy.version.short
self.info('Appy version is %s.' % self.appyTool.appyVersion)
# 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 ---------------------------------------------
loggedUsers = {}
@ -400,18 +184,226 @@ def onDelSession(sessionObject, container):
class ZopeInstaller:
'''This Zope installer runs every time Zope starts and encounters this
generated Zope product.'''
def __init__(self, zopeContext, toolClass, config, classes):
def __init__(self, zopeContext, config, classes):
self.zopeContext = zopeContext
self.toolClass = toolClass
self.config = cfg = config
self.app = zopeContext._ProductContext__app # The root of the Zope tree
self.config = config
self.classes = classes
# Unwrap some useful config variables
self.productName = cfg.PROJECTNAME
self.logger = cfg.logger
self.defaultAddContentPermission = cfg.DEFAULT_ADD_CONTENT_PERMISSION
self.addContentPermissions = cfg.ADD_CONTENT_PERMISSIONS
self.productName = config.PROJECTNAME
self.languages = config.languages
self.logger = config.logger
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
every gen-class of the application.'''
appName = self.productName
@ -434,74 +426,15 @@ class ZopeInstaller:
continue # Back refs are initialised within fw refs
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):
self.logger.info('is being installed...')
self.completeAppyTypes()
self.installApplication()
self.installTool()
self.installTypes()
# Create the "admin" user if no user is present in the database
self.installAppyTypes()
self.installZopeClasses()
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.'''
def createUser(self, userId, roles):
'''Creates a user with id p_userId with some p_roles.'''
pms = self.portal.portal_membership
pms.addMember(userId, 'password', [], [])
self.acl_users.addMember(userId, 'password', [], [])
self.setRoles(roles, name=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.utils import getOsTempFolder
from appy.shared.data import languages
import appy.gen
from appy.gen import Type, Search, Selection
from appy.gen.utils import SomeObjects, sequenceTypes, getClassName
from appy.gen.plone25.mixins import BaseMixin
from appy.gen.plone25.wrappers import AbstractWrapper
from appy.gen.plone25.descriptors import ClassDescriptor
try:
from AccessControl.ZopeSecurityPolicy import _noroles
except ImportError:
_noroles = []
# Errors -----------------------------------------------------------------------
jsMessages = ('no_elem_selected', 'delete_confirm')
@ -27,13 +32,17 @@ class ToolMixin(BaseMixin):
res = '%s%s' % (elems[1], elems[4])
return res
def getCatalog(self):
'''Returns the catalog object.'''
return self.getParentNode().catalog
def getApp(self):
'''Returns the root application object.'''
return self.portal_url.getPortalObject()
'''Returns the root Zope object.'''
return self.getPhysicalRoot()
def getSiteUrl(self):
'''Returns the absolute URL of this site.'''
return self.portal_url.getPortalObject().absolute_url()
return self.getApp().absolute_url()
def getPodInfo(self, obj, name):
'''Gets the available POD formats for Pod field named p_name on
@ -61,20 +70,43 @@ class ToolMixin(BaseMixin):
return res.content
def getAttr(self, name):
'''Gets attribute named p_attrName. Useful because we can't use getattr
directly in Zope Page Templates.'''
'''Gets attribute named p_name.'''
return getattr(self.appy(), name, None)
def getAppName(self):
'''Returns the name of this application.'''
'''Returns the name of the application.'''
return self.getProductConfig().PROJECTNAME
def getAppFolder(self):
'''Returns the folder at the root of the Plone site that is dedicated
to this application.'''
cfg = self.getProductConfig()
portal = cfg.getToolByName(self, 'portal_url').getPortalObject()
return getattr(portal, self.getAppName())
def getPath(self, path):
'''Returns the folder or object whose absolute path p_path.'''
res = self.getPhysicalRoot()
if path == '/': return res
path = path[1:]
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):
'''Returns the list of root classes for this application.'''
@ -124,7 +156,7 @@ class ToolMixin(BaseMixin):
return {'fields': fields, 'nbOfColumns': nbOfColumns,
'fieldDicts': fieldDicts}
queryParamNames = ('type_name', 'search', 'sortKey', 'sortOrder',
queryParamNames = ('className', 'search', 'sortKey', 'sortOrder',
'filterKey', 'filterValue')
def getQueryInfo(self):
'''If we are showing search results, this method encodes in a string all
@ -160,8 +192,8 @@ class ToolMixin(BaseMixin):
return [importParams['headers'], elems]
def showPortlet(self, context):
if self.portal_membership.isAnonymousUser(): return False
if context.id == 'skyn': context = context.getParentNode()
if self.userIsAnon(): return False
if context.id == 'ui': context = context.getParentNode()
res = True
if not self.getRootClasses():
res = False
@ -170,24 +202,25 @@ class ToolMixin(BaseMixin):
if (self.id in context.absolute_url()): res = True
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.'''
res = self.portal_catalog(UID=uid)
if res:
res = res[0].getObject()
if appy:
res = res.appy()
return res
res = self.getPhysicalRoot().catalog(UID=uid)
if not res: return
res = res[0]
if brain: return res
res = res.getObject()
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,
maxResults=None, noSecurity=False, sortBy=None,
sortOrder='asc', filterKey=None, filterValue=None,
refObject=None, refField=None):
'''Executes a query on a given p_contentType (or several, separated
with commas) in portal_catalog. If p_searchName is specified, it
corresponds to:
1) a search defined on p_contentType: additional search criteria
'''Executes a query on instances of a given p_className (or several,
separated with commas) in the catalog. If p_searchName is specified,
it corresponds to:
1) a search defined on p_className: additional search criteria
will be added to the query, or;
2) "_advanced": in this case, additional search criteria will also
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
objects that are referenced from p_refObject through p_refField.'''
# Is there one or several content types ?
if contentType.find(',') != -1:
portalTypes = contentType.split(',')
if className.find(',') != -1:
classNames = className.split(',')
else:
portalTypes = contentType
params = {'portal_type': portalTypes}
classNames = className
params = {'ClassName': classNames}
if not brainsOnly: params['batch'] = True
# Manage additional criteria from a search when relevant
if searchName:
# In this case, contentType must contain a single content type.
appyClass = self.getAppyClass(contentType)
# In this case, className must contain a single content type.
appyClass = self.getAppyClass(className)
if searchName != '_advanced':
search = ClassDescriptor.getSearch(appyClass, searchName)
else:
@ -273,7 +306,7 @@ class ToolMixin(BaseMixin):
# Determine what method to call on the portal catalog
if noSecurity: catalogMethod = 'unrestrictedSearchResults'
else: catalogMethod = 'searchResults'
exec 'brains = self.portal_catalog.%s(**params)' % catalogMethod
exec 'brains = self.getPath("/catalog").%s(**params)' % catalogMethod
if brainsOnly:
# Return brains only.
if not maxResults: return brains
@ -290,8 +323,8 @@ class ToolMixin(BaseMixin):
# time a page for an element is consulted.
if remember:
if not searchName:
# It is the global search for all objects pf p_contentType
searchName = contentType
# It is the global search for all objects pf p_className
searchName = className
uids = {}
i = -1
for obj in res.objects:
@ -339,15 +372,6 @@ class ToolMixin(BaseMixin):
return '<acronym title="%s">%s</acronym>' % \
(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):
'''Gets the currently published object, if its meta_class is among
application classes.'''
@ -355,24 +379,24 @@ class ToolMixin(BaseMixin):
# 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
# 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']
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
def getAppyClass(self, contentType, wrapper=False):
'''Gets the Appy Python class that is related to p_contentType.'''
# Retrieve first the Archetypes class corresponding to p_ContentType
portalType = self.portal_types.get(contentType)
if not portalType: return
atClassName = portalType.getProperty('content_meta_type')
appName = self.getProductConfig().PROJECTNAME
exec 'from Products.%s.%s import %s as atClass' % \
(appName, atClassName, atClassName)
# Get then the Appy Python class
if wrapper: return atClass.wrapperClass
else: return atClass.wrapperClass.__bases__[-1]
def getZopeClass(self, name):
'''Returns the Zope class whose name is p_name.'''
exec 'from Products.%s.%s import %s as C'% (self.getAppName(),name,name)
return C
def getAppyClass(self, zopeName, wrapper=False):
'''Gets the Appy class corresponding to the Zope class named p_name.
If p_wrapper is True, it returns the Appy wrapper. Else, it returns
the user-defined class.'''
zopeClass = self.getZopeClass(zopeName)
if wrapper: return zopeClass.wrapperClass
else: return zopeClass.wrapperClass.__bases__[-1]
def getCreateMeans(self, contentTypeOrAppyClass):
'''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
external data.'''
rq = self.REQUEST
appyClass = self.getAppyClass(rq.get('type_name'))
appyClass = self.getAppyClass(rq.get('className'))
importPaths = rq.get('importPath').split('|')
appFolder = self.getAppFolder()
appFolder = self.getPath('/data')
for importPath in importPaths:
if not importPath: continue
objectId = os.path.basename(importPath)
@ -428,9 +452,9 @@ class ToolMixin(BaseMixin):
return self.goto(rq['HTTP_REFERER'])
def isAlreadyImported(self, contentType, importPath):
appFolder = self.getAppFolder()
data = self.getPath('/data')
objectId = os.path.basename(importPath)
if hasattr(appFolder.aq_base, objectId):
if hasattr(data.aq_base, objectId):
return True
else:
return False
@ -553,8 +577,8 @@ class ToolMixin(BaseMixin):
if refInfo: criteria['_ref'] = refInfo
rq.SESSION['searchCriteria'] = criteria
# Go to the screen that displays search results
backUrl = '%s/skyn/query?type_name=%s&&search=_advanced' % \
(self.absolute_url(), rq['type_name'])
backUrl = '%s/ui/query?className=%s&&search=_advanced' % \
(self.absolute_url(), rq['className'])
return self.goto(backUrl)
def getJavascriptMessages(self):
@ -614,8 +638,8 @@ class ToolMixin(BaseMixin):
'''This method creates the URL that allows to perform a (non-Ajax)
request for getting queried objects from a search named p_searchName
on p_contentType.'''
baseUrl = self.absolute_url() + '/skyn'
baseParams = 'type_name=%s' % contentType
baseUrl = self.absolute_url() + '/ui'
baseParams = 'className=%s' % contentType
rq = self.REQUEST
if rq.get('ref'): baseParams += '&ref=%s' % rq.get('ref')
# Manage start number
@ -663,7 +687,7 @@ class ToolMixin(BaseMixin):
res['backText'] = self.translate(label)
else:
fieldName, pageName = d2.split(':')
sourceObj = self.portal_catalog(UID=d1)[0].getObject()
sourceObj = self.getObject(d1)
label = '%s_%s' % (sourceObj.meta_type, fieldName)
res['backText'] = '%s : %s' % (sourceObj.Title(),
self.translate(label))
@ -739,9 +763,9 @@ class ToolMixin(BaseMixin):
except KeyError: pass
except IndexError: pass
if uid:
brain = self.portal_catalog(UID=uid)
brain = self.getObject(uid, brain=True)
if brain:
sibling = brain[0].getObject()
sibling = brain.getObject()
res[urlKey] = sibling.getUrl(nav=newNav % (index + 1),
page='main')
return res
@ -777,6 +801,9 @@ class ToolMixin(BaseMixin):
'''Gets the translated month name of month numbered p_monthNumber.'''
return self.translate(self.monthsIds[int(monthNumber)], domain='plone')
# --------------------------------------------------------------------------
# Authentication-related methods
# --------------------------------------------------------------------------
def performLogin(self):
'''Logs the user in.'''
rq = self.REQUEST
@ -788,11 +815,14 @@ class ToolMixin(BaseMixin):
msg = self.translate(u'You must enable cookies before you can ' \
'log in.', domain='plone')
return self.goto(urlBack, msg.encode('utf-8'))
# Perform the Zope-level authentication
self.acl_users.credentials_cookie_auth.login()
login = rq['login_name']
if self.portal_membership.isAnonymousUser():
login = rq.get('__ac_name', '')
password = rq.get('__ac_password', '')
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='/')
msg = self.translate(u'Login failed', domain='plone')
logMsg = 'Authentication failed (tried with login "%s")' % login
@ -803,7 +833,7 @@ class ToolMixin(BaseMixin):
msg = msg.encode('utf-8')
self.log(logMsg)
# Bring Managers to the config, leave others on the main page.
user = self.portal_membership.getAuthenticatedMember()
user = self.getUser()
if user.has_role('Manager'):
# Bring the user to the configuration
url = self.goto(self.absolute_url(), msg)
@ -814,28 +844,72 @@ class ToolMixin(BaseMixin):
def performLogout(self):
'''Logs out the current user when he clicks on "disconnect".'''
rq = self.REQUEST
userId = self.portal_membership.getAuthenticatedMember().getId()
userId = self.getUser().getId()
# Perform the logout in acl_users
try:
self.acl_users.logout(rq)
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.
rq.RESPONSE.expireCookie('__ac', path='/')
# Invalidate existing sessions.
sdm = self.session_data_manager
session = sdm.getSessionData(create=0)
if session is not None:
session.invalidate()
from Products.CMFPlone import transaction_note
transaction_note('Logged out')
self.log('User "%s" has been logged out.' % userId)
# Remove user from variable "loggedUsers"
from appy.gen.plone25.installer import loggedUsers
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):
'''A temp file has been created in a temp folder. This method returns
@ -864,11 +938,16 @@ class ToolMixin(BaseMixin):
def getUserLine(self, user):
'''Returns a one-line user info as shown on every page.'''
res = [user.getId()]
name = user.getProperty('fullname')
if name: res.insert(0, name)
rolesToShow = [r for r in user.getRoles() \
if r not in ('Authenticated', 'Member')]
if rolesToShow:
res.append(', '.join([self.translate(r) for r in rolesToShow]))
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:
'''Every Archetype class generated by appy.gen inherits from this class or
a subclass of it.'''
'''Every Zope class generated by appy.gen inherits from this class or a
subclass of it.'''
_appy_meta_type = 'Class'
def get_o(self):
@ -31,31 +31,36 @@ class BaseMixin:
initiator=None, initiatorField=None):
'''This method creates (if p_created is True) or updates an object.
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
temporary object created in the request by portal_factory, and this
method creates the corresponding final object. In the case of an
update, this method simply updates fields of p_self.'''
rq = self.REQUEST
In the case of an object creation from the web (p_created is True
and a REQUEST object is present), p_self is a temporary object
created in /temp_folder, and this method moves it at its "final"
place. In the case of an update, this method simply updates fields
of p_self.'''
rq = getattr(self, 'REQUEST', None)
obj = self
if created:
# portal_factory creates the final object from the temp object.
obj = self.portal_factory.doCreate(self, self.id)
if created and rq:
# Create the final object and put it at the right place.
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
if not created: previousData = self.rememberPreviousData()
# Perform the change on the object, unless self is a tool being created.
if (obj._appy_meta_type == 'Tool') and created:
# We do not process form data (=real update on the object) if the
# tool itself is being created.
pass
else:
if not created: previousData = obj.rememberPreviousData()
# Perform the change on the object
if rq:
# Store in the database the new value coming from the form
for appyType in self.getAppyTypes('edit', rq.get('page')):
value = getattr(values, appyType.name, None)
appyType.store(obj, value)
if created: obj.unmarkCreationFlag()
if previousData:
# Keep in history potential changes on historized fields
self.historizeData(previousData)
obj.historizeData(previousData)
# Manage potential link with an initiator object
if created and initiator: initiator.appy().link(initiatorField, obj)
@ -69,7 +74,7 @@ class BaseMixin:
appyObject = obj.appy()
if hasattr(appyObject, 'onEdit'):
msg = appyObject.onEdit(created)
obj.reindexObject()
obj.reindex()
return obj, msg
def delete(self):
@ -99,9 +104,11 @@ class BaseMixin:
def onCreate(self):
'''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
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 object.
urlParams = {'mode':'edit', 'page':'main', 'nav':''}
@ -112,15 +119,12 @@ class BaseMixin:
splitted = rq.get('nav').split('.')
splitted[-1] = splitted[-2] = str(int(splitted[-1])+1)
urlParams['nav'] = '.'.join(splitted)
# Determine base URL
baseUrl = self.absolute_url()
if (self._appy_meta_type == 'Tool') and not urlParams['nav']:
# This is the creation of a root object in the app folder
baseUrl = self.getAppFolder().absolute_url()
objId = self.generateUniqueId(typeName)
editUrl = '%s/portal_factory/%s/%s/skyn/edit' % \
(baseUrl, typeName, objId)
return self.goto(self.getUrl(editUrl, **urlParams))
# Create a temp object in /temp_folder
tool = self.getTool()
id = tool.generateUid(className)
appName = tool.getAppName()
obj = createObject(tool.getPath('/temp_folder'), id, className, appName)
return self.goto(obj.getUrl(**urlParams))
def onCreateWithoutForm(self):
'''This method is called when a user wants to create a object from a
@ -175,10 +179,10 @@ class BaseMixin:
def onUpdate(self):
'''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 request. In this case, the update consists in the creation of
the "final" object in the database. If the object is not a temporary
one, this method updates its fields in the database.'''
The object may be a temporary object created in /temp_folder.
In this case, the update consists in moving it to its "final" place.
If the object is not a temporary one, this method updates its
fields in the database.'''
rq = self.REQUEST
tool = self.getTool()
errorMessage = self.translate(
@ -244,7 +248,7 @@ class BaseMixin:
# object like a one-shot form and has already been deleted in method
# onEdit), redirect to the main site page.
if not getattr(obj.getParentNode().aq_base, obj.id, None):
obj.unindexObject()
obj.unindex()
return self.goto(tool.getSiteUrl(), msg)
# If the user can't access the object anymore, redirect him to the
# main site page.
@ -295,16 +299,42 @@ class BaseMixin:
return self.goto(obj.getUrl())
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'):
'''Prints a p_msg in the user interface. p_logLevel may be "info",
"warning" or "error".'''
mType = type
rq = self.REQUEST
if not hasattr(rq, 'messages'):
messages = rq.messages = []
else:
messages = rq.messages
if mType == 'warning': mType = 'warn'
elif mType == 'error': mType = 'stop'
try:
self.plone_utils.addPortalMessage(msg, type=mType)
except UnicodeDecodeError:
self.plone_utils.addPortalMessage(msg.decode('utf-8'), type=mType)
messages.append( (mType, msg) )
def log(self, msg, type='info'):
'''Logs a p_msg in the log file. p_logLevel may be "info", "warning"
@ -315,29 +345,15 @@ class BaseMixin:
else: logMethod = logger.info
logMethod(msg)
def getState(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
def do(self):
'''Performs some action from the user interface.'''
rq = self.REQUEST
action = rq['action']
if rq.get('objectUid', None):
obj = self.getTool().getObject(rq['objectUid'])
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)
obj = self
return obj.getMethod('on'+action)()
def rememberPreviousData(self):
'''This method is called before updating an object and remembers, for
@ -351,12 +367,12 @@ class BaseMixin:
def addHistoryEvent(self, action, **kw):
'''Adds an event in the object history.'''
userId = self.portal_membership.getAuthenticatedMember().getId()
userId = self.getUser().getId()
from DateTime import DateTime
event = {'action': action, 'actor': userId, 'time': DateTime(),
'comments': ''}
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
histKey = self.workflow_history.keys()[0]
self.workflow_history[histKey] += (event,)
@ -423,7 +439,7 @@ class BaseMixin:
for field in self.getAppyTypes('edit', page):
if (field.type == 'String') and (field.format == 3):
self.REQUEST.set(field.name, '')
return self.skyn.edit(self)
return self.ui.edit(self)
def showField(self, name, layoutType='view'):
'''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.
If p_currentOnly is True, we return the current state, even if not
related to p_phase.'''
currentState = self.getState()
currentState = self.State()
if currentOnly:
return [StateDescr(currentState, 'current').get()]
res = []
@ -690,7 +706,7 @@ class BaseMixin:
'''
res = []
wf = self.getWorkflow()
currentState = self.getState(name=False)
currentState = self.State(name=False)
# Loop on every transition
for name in dir(wf):
transition = getattr(wf, name)
@ -855,7 +871,7 @@ class BaseMixin:
related data on the object.'''
wf = self.getWorkflow()
# Get the initial workflow state
initialState = self.getState(name=False)
initialState = self.State(name=False)
# Create a Transition instance representing the initial transition.
initialTransition = Transition((initialState, initialState))
initialTransition.trigger('_init_', self, wf, '')
@ -876,7 +892,7 @@ class BaseMixin:
'''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
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)
def refreshSecurity(self):
@ -885,15 +901,15 @@ class BaseMixin:
wf = self.getWorkflow()
try:
# Get the state definition of the object's current state.
state = getattr(wf, self.getState())
state = getattr(wf, self.State())
except AttributeError:
# The workflow information for this object does not correspond to
# its current workflow attribution. Add a new fake event
# representing passage of this object to the initial state of his
# currently attributed workflow.
stateName = self.getState(name=True, initial=True)
stateName = self.State(name=True, initial=True)
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.meta_type, stateName))
# Update permission attributes on the object if required
@ -939,9 +955,11 @@ class BaseMixin:
'''Executes action with p_fieldName on this object.'''
appyType = self.getAppyType(actionName)
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.
self.reindexObject()
self.reindex()
return appyType.result, actionRes
def onExecuteAppyAction(self):
@ -982,8 +1000,8 @@ class BaseMixin:
# the user.
return self.goto(msg)
def do(self, transitionName, comment='', doAction=True, doNotify=True,
doHistory=True, doSay=True):
def trigger(self, transitionName, comment='', doAction=True, doNotify=True,
doHistory=True, doSay=True):
'''Triggers transition named p_transitionName.'''
# Check that this transition exists.
wf = self.getWorkflow()
@ -998,12 +1016,12 @@ class BaseMixin:
transition.trigger(transitionName, self, wf, comment, doAction=doAction,
doNotify=doNotify, doHistory=doHistory, doSay=doSay)
def onDo(self):
def onTrigger(self):
'''This method is called whenever a user wants to trigger a workflow
transition on an object.'''
rq = self.REQUEST
self.do(rq['workflow_action'], comment=rq.get('comment', ''))
self.reindexObject()
self.trigger(rq['workflow_action'], comment=rq.get('comment', ''))
self.reindex()
return self.goto(self.getUrl(rq['HTTP_REFERER']))
def fieldValueSelected(self, fieldName, vocabValue, dbValue):
@ -1079,7 +1097,8 @@ class BaseMixin:
'''Returns a wrapper object allowing to manipulate p_self the Appy
way.'''
# 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 = {}
# Return the Appy wrapper from rq.appyWrappers if already there
uid = self.UID()
@ -1088,7 +1107,69 @@ class BaseMixin:
wrapper = self.wrapperClass(self)
rq.appyWrappers[uid] = 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):
'''Must I show a state whose "show value" is p_stateShow?'''
if callable(stateShow):
@ -1129,11 +1210,6 @@ class BaseMixin:
# Update the permissions
for permission, creators in allCreators.iteritems():
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):
'''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).'''
# Define the URL suffix
suffix = ''
if mode != 'raw': suffix = '/skyn/%s' % mode
if mode != 'raw': suffix = '/ui/%s' % mode
# Define base URL if omitted
if not base:
base = self.absolute_url() + suffix
@ -1171,7 +1247,7 @@ class BaseMixin:
if '?' in base: base = base[:base.index('?')]
base = base.strip('/')
for mode in ('view', 'edit'):
suffix = 'skyn/%s' % mode
suffix = 'ui/%s' % mode
if base.endswith(suffix):
base = base[:-len(suffix)].strip('/')
break
@ -1195,6 +1271,19 @@ class BaseMixin:
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):
'''Gets the language (code) of the current user.'''
# Try first the "LANGUAGE" key from the request
@ -1291,12 +1380,11 @@ class BaseMixin:
layout = defaultPageLayouts[layoutType]
return layout.get()
def getPageTemplate(self, skyn, templateName):
'''Returns, in the skyn folder, the page template corresponding to
def getPageTemplate(self, ui, templateName):
'''Returns, in the ui folder, the page template corresponding to
p_templateName.'''
res = skyn
for name in templateName.split('/'):
res = res.get(name)
res = ui
for name in templateName.split('/'): res = getattr(res, name)
return res
def download(self):
@ -1316,15 +1404,6 @@ class BaseMixin:
response.setHeader('Expires', 'Thu, 11 Dec 1975 12:05:05 GMT')
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):
'''Has the logged user p_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
allowedRoles = getattr(self.aq_base, zopeAttr)
# Has the user one of those roles?
user = self.portal_membership.getAuthenticatedMember()
ids = [user.getId()] + user.getGroups()
user = self.getUser()
# XXX no groups at present
#ids = [user.getId()] + user.getGroups()
ids = [user.getId()]
userGlobalRoles = user.getRoles()
for role in allowedRoles:
# Has the user this role ? Check in the local roles first.
@ -1347,4 +1428,11 @@ class BaseMixin:
field p_name.'''
return 'tinyMCE.init({\nmode : "textareas",\ntheme : "simple",\n' \
'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!>
from OFS.SimpleItem import SimpleItem
from OFS.Folder import Folder
from appy.gen.utils import createObject
from AccessControl import ClassSecurityInfo
from DateTime import DateTime
from Products.Archetypes.atapi import *
import Products.<!applicationName!>.config
from Products.CMFCore.utils import UniqueObject
import Products.<!applicationName!>.config as cfg
from appy.gen.plone25.mixins import BaseMixin
from appy.gen.plone25.mixins.ToolMixin import ToolMixin
from Extensions.appyWrappers import <!genClassName!>_Wrapper
<!imports!>
from Extensions.appyWrappers import <!genClassName!>_Wrapper as Wrapper
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!>):
'''<!classDoc!>'''
security = ClassSecurityInfo()
__implements__ = <!implements!>
archetype_name = '<!genClassName!>'
meta_type = '<!genClassName!>'
portal_type = '<!genClassName!>'
allowed_content_types = ()
filter_content_types = 0
global_allow = <!global_allow!>
immediate_view = 'skyn/view'
default_view = 'skyn/view'
suppl_views = ()
typeDescription = '<!genClassName!>'
typeDescMsgId = '<!genClassName!>'
i18nDomain = '<!applicationName!>'
wrapperClass = <!genClassName!>_Wrapper
schema = <!baseSchema!>.copy()
global_allow = 1
icon = "ui/<!icon!>"
wrapperClass = Wrapper
for elem in dir(<!baseMixin!>):
if not elem.startswith('__'): security.declarePublic(elem)
<!static!>
<!commonMethods!>
def getTool(self): return self.getPhysicalRoot().config
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!>
<!register!>

View file

@ -58,7 +58,7 @@ fieldset {
}
.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;
}

View file

@ -34,6 +34,5 @@ def initialize(context):
# I need to do those imports here; else, types and add permissions will not
# be registered.
classes = [<!classes!>]
ZopeInstaller(context, <!applicationName!>Tool.<!applicationName!>Tool,
config, classes).install()
ZopeInstaller(context, config, classes).install()
# ------------------------------------------------------------------------------

View file

@ -1,7 +1,6 @@
<!codeHeader!>
import os, os.path, sys, copy
import appy.gen
from Products.CMFCore.permissions import setDefaultRoles
import Extensions.appyWrappers as wraps
<!imports!>
@ -12,27 +11,11 @@ import Extensions.appyWrappers as wraps
from persistent.list import PersistentList
from zExceptions import BadRequest
from ZPublisher.HTTPRequest import BaseRequest
try:
import CustomizationPolicy
except ImportError:
CustomizationPolicy = None
from OFS.Image import File
from ZPublisher.HTTPRequest import FileUpload
from AccessControl import getSecurityManager
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.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
import appy.gen
import logging
@ -42,10 +25,8 @@ logger = logging.getLogger('<!applicationName!>')
PROJECTNAME = '<!applicationName!>'
diskFolder = os.path.dirname(<!applicationName!>.__file__)
defaultAddRoles = [<!defaultAddRoles!>]
DEFAULT_ADD_CONTENT_PERMISSION = "Add portal content"
ADD_CONTENT_PERMISSIONS = {
<!addPermissions!>}
setDefaultRoles(DEFAULT_ADD_CONTENT_PERMISSION, tuple(defaultAddRoles))
# Applications classes, in various formats
rootClasses = [<!rootClasses!>]

View file

@ -1,5 +1,5 @@
<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>
</html>
</tal:main>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 339 B

View file

@ -38,12 +38,8 @@ class ToolWrapper(AbstractWrapper):
def getInitiator(self):
'''Retrieves the object that triggered the creation of the object
being currently created (if any).'''
res = None
rq = self.o.REQUEST
if rq.get('nav', ''):
initiatorUid = rq['nav'].split('.')[1]
res = self.o.portal_catalog(UID=initiatorUid)[0].getObject().appy()
return res
nav = self.o.REQUEST.get('nav', '')
if nav: return self.getObject(nav.split('.')[1])
def getObject(self, 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
# in the translation.
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('\n', '<br/>')
return '<div class="translationLabel"><acronym title="%s">' \

View file

@ -56,38 +56,38 @@ class UserWrapper(AbstractWrapper):
def onEdit(self, created):
self.title = self.firstName + ' ' + self.name
pm = self.o.portal_membership
aclUsers = self.o.acl_users
login = self.login
if created:
# Create the corresponding Plone user
pm.addMember(self.login, self.password1, ('Member',), None)
# Create the corresponding Zope user
aclUsers._doAddUser(login, self.password1, self.roles, ())
# Remove our own password copies
self.password1 = self.password2 = ''
# Perform updates on the corresponding Plone user
ploneUser = self.o.portal_membership.getMemberById(self.login)
ploneUser.setMemberProperties({'fullname': self.title})
zopeUser = aclUsers.getUserById(login)
# This object must be owned by its Plone user
if 'Owner' not in self.o.get_local_roles_for_userid(self.login):
self.o.manage_addLocalRoles(self.login, ('Owner',))
if 'Owner' not in self.o.get_local_roles_for_userid(login):
self.o.manage_addLocalRoles(login, ('Owner',))
# Change group membership according to self.roles. Indeed, instead of
# granting roles directly to the user, we will add the user to a
# Appy-created group having this role.
userRoles = self.roles
userGroups = ploneUser.getGroups()
for role in self.o.getProductConfig().grantableRoles:
# Retrieve the group corresponding to this role
groupName = '%s_group' % role
if role == 'Manager': groupName = 'Administrators'
elif role == 'Reviewer': groupName = 'Reviewers'
group = self.o.portal_groups.getGroupById(groupName)
# Add or remove the user from this group according to its role(s).
if role in userRoles:
# Add the user if not already present in the group
if groupName not in userGroups:
group.addMember(self.login)
else:
# Remove the user if it was in the corresponding group
if groupName in userGroups:
group.removeMember(self.login)
#userGroups = zopeUser.getGroups()
# for role in self.o.getProductConfig().grantableRoles:
# # Retrieve the group corresponding to this role
# groupName = '%s_group' % role
# if role == 'Manager': groupName = 'Administrators'
# elif role == 'Reviewer': groupName = 'Reviewers'
# group = self.o.portal_groups.getGroupById(groupName)
# # Add or remove the user from this group according to its role(s).
# if role in userRoles:
# # Add the user if not already present in the group
# if groupName not in userGroups:
# group.addMember(self.login)
# else:
# # Remove the user if it was in the corresponding group
# if groupName in userGroups:
# group.removeMember(self.login)
return self._callCustom('onEdit', created)
def onDelete(self):

View file

@ -2,10 +2,10 @@
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
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.xml_parser import XmlMarshaller
from appy.shared.csv_parser import CsvMarshaller
@ -43,7 +43,7 @@ class AbstractWrapper(object):
elif name == 'uid': return self.o.UID()
elif name == 'klass': return self.__class__.__bases__[-1]
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':
o = self.o
appName = o.getProductConfig().PROJECTNAME
@ -53,7 +53,7 @@ class AbstractWrapper(object):
key = o.workflow_history.keys()[0]
return o.workflow_history[key]
elif name == 'user':
return self.o.portal_membership.getAuthenticatedMember()
return self.o.getUser()
elif name == 'fields': return self.o.getAllAppyTypes()
# Now, let's try to return a real attribute.
res = object.__getattribute__(self, name)
@ -100,40 +100,36 @@ class AbstractWrapper(object):
'''Sorts referred elements linked to p_self via p_fieldName according
to a given p_sortKey which must be an attribute set on referred
objects ("title", by default).'''
selfO = self.o
refs = getattr(selfO, fieldName, None)
refs = getattr(self.o, fieldName, None)
if not refs: return
c = selfO.portal_catalog
refs.sort(lambda x,y: \
cmp(getattr(c(UID=x)[0].getObject().appy(), sortKey),
getattr(c(UID=y)[0].getObject().appy(), sortKey)))
if reverse:
refs.reverse()
tool = self.tool
refs.sort(lambda x,y: cmp(getattr(tool.getObject(x), sortKey),
getattr(tool.getObject(y), sortKey)))
if reverse: refs.reverse()
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
reference field named p_fieldName.
If p_fieldNameOrClass is a class from the gen-application, it must
correspond to a root class and this method allows to create a
root object in the application folder.'''
isField = isinstance(fieldNameOrClass, basestring)
tool = self.tool.o
# Determine the portal type of the object to create
if isField:
fieldName = idPrefix = fieldNameOrClass
fieldName = fieldNameOrClass
appyType = self.o.getAppyType(fieldName)
portalType = self.tool.o.getPortalType(appyType.klass)
portalType = tool.getPortalType(appyType.klass)
else:
klass = fieldNameOrClass
idPrefix = klass.__name__
portalType = self.tool.o.getPortalType(klass)
portalType = tool.getPortalType(klass)
# Determine object id
if kwargs.has_key('id'):
objId = kwargs['id']
del kwargs['id']
else:
objId = '%s.%f.%s' % (idPrefix, time.time(),
str(random.random()).split('.')[1])
objId = tool.generateUid(portalType)
# Determine if object must be created from external data
externalData = None
if kwargs.has_key('_data'):
@ -141,36 +137,27 @@ class AbstractWrapper(object):
del kwargs['_data']
# Where must I create the object?
if not isField:
folder = self.o.getTool().getAppFolder()
folder = tool.getPath('/data')
else:
if hasattr(self, 'folder') and self.folder:
folder = self.o
else:
folder = self.o.getParentNode()
# Create the object
# -------------------- Try to replace invokeFactory --------------------
#folder._objects = folder._objects + ({'id':id,'meta_type':portalType},)
#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()
zopeObj = createObject(folder, objId,portalType, tool.getAppName())
appyObj = zopeObj.appy()
# Set object attributes
for attrName, attrValue in kwargs.iteritems():
setattr(appyObj, attrName, attrValue)
if isField:
# Link the object to this one
appyType.linkObject(self.o, ploneObj)
ploneObj._appy_managePermissions()
appyType.linkObject(self.o, zopeObj)
zopeObj._appy_managePermissions()
# Call custom initialization
if externalData: param = externalData
else: param = True
if hasattr(appyObj, 'onEdit'): appyObj.onEdit(param)
ploneObj.reindexObject()
zopeObj.reindex()
return appyObj
def freeze(self, fieldName, doAction=False):
@ -222,8 +209,8 @@ class AbstractWrapper(object):
doHistory=True):
'''This method allows to trigger on p_self a workflow p_transition
programmatically. See doc in self.o.do.'''
return self.o.do(transition, comment, doAction=doAction,
doNotify=doNotify, doHistory=doHistory, doSay=False)
return self.o.trigger(transition, comment, doAction=doAction,
doNotify=doNotify, doHistory=doHistory, doSay=False)
def log(self, message, type='info'): return self.o.log(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,
even if the logged user does not have the permission to view it.'''
# 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
search = Search('customSearch', sortBy=sortBy, **fields)
if not maxResults:
maxResults = 'NO_LIMIT'
# If I let maxResults=None, only a subset of the results will be
# returned by method executeResult.
res = self.tool.o.executeQuery(contentType, search=search,
maxResults=maxResults, noSecurity=noSecurity)
res = tool.executeQuery(contentType, search=search,
maxResults=maxResults, noSecurity=noSecurity)
return [o.appy() for o in res['objects']]
def count(self, klass, noSecurity=False, **fields):
'''Identical to m_search above, but returns the number of objects that
match the search instead of returning the objects themselves. Use
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)
res = self.tool.o.executeQuery(contentType, search=search,
brainsOnly=True, noSecurity=noSecurity)
res = tool.executeQuery(contentType, search=search, brainsOnly=True,
noSecurity=noSecurity)
if res: return res._len # It is a LazyMap instance
else: return 0
@ -289,11 +278,12 @@ class AbstractWrapper(object):
"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)
# Initialize the context variable "ctx"
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):
# Get the Appy object from the brain
if noSecurity: method = '_unrestrictedGetObject'
@ -309,7 +299,7 @@ class AbstractWrapper(object):
this object automatically. But if your code modifies other objects,
Appy may not know that they must be reindexed, too. So use this
method in those cases.'''
self.o.reindexObject()
self.o.reindex()
def export(self, at='string', format='xml', include=None, exclude=None):
'''Creates an "exportable" version of this object. p_format is "xml" by

View file

@ -11,10 +11,10 @@
page req/page;
macro req/macro;
action req/action|nothing;
user contextObj/portal_membership/getAuthenticatedMember;
user contextObj/getUser;
app tool/getApp;
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('Expires', 'Mon, 11 Dec 1975 12:05:05 GMT');
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;}
ul { line-height: 1.2em; margin: 0 0 0.2em 0.6em; padding: 0;
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;}
img {border: 0;}

View file

@ -58,7 +58,7 @@ function getAjaxChunk(pos) {
if (xhrObjects[pos].xhr.readyState == 1) {
// The request has been initialized: display the waiting radar
var hookElem = document.getElementById(hook);
if (hookElem) hookElem.innerHTML = "<div align=\"center\"><img src=\"skyn/waiting.gif\"/><\/div>";
if (hookElem) hookElem.innerHTML = "<div align=\"center\"><img src=\"ui/waiting.gif\"/><\/div>";
}
if (xhrObjects[pos].xhr.readyState == 4) {
// 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;
}
// Construct the URL to call
var urlFull = url + '/skyn/ajax';
var urlFull = url + '/ui/ajax';
if (mode == 'GET') {
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
an Ajax request. */
function askQueryResult(hookId, objectUrl, contentType, searchName,
function askQueryResult(hookId, objectUrl, className, searchName,
startNumber, sortKey, sortOrder, filterKey) {
// 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};
if (sortKey) params['sortKey'] = sortKey;
if (sortOrder) params['sortOrder'] = sortOrder;
@ -351,11 +351,11 @@ function toggleCookie(cookieId) {
// the HTML document that needs to be shown or hidden.
var displayValue = 'none';
var newState = 'collapsed';
var imgSrc = 'skyn/expand.gif';
var imgSrc = 'ui/expand.gif';
if (state == 'collapsed') {
// Show the HTML zone
displayValue = 'block';
imgSrc = 'skyn/collapse.gif';
imgSrc = 'ui/collapse.gif';
newState = 'expanded';
}
// Update the corresponding HTML element
@ -459,14 +459,14 @@ function manageTab(tabId, action) {
var tab = document.getElementById('tab_' + tabId);
var right = document.getElementById('tab_' + tabId + '_right');
if (action == 'show') {
left.src = "skyn/tabLeft.png";
tab.style.backgroundImage = "url(skyn/tabBg.png)";
right.src = "skyn/tabRight.png";
left.src = "ui/tabLeft.png";
tab.style.backgroundImage = "url(ui/tabBg.png)";
right.src = "ui/tabRight.png";
}
if (action == 'hide') {
left.src = "skyn/tabLeftu.png";
tab.style.backgroundImage = "url(skyn/tabBgu.png)";
right.src = "skyn/tabRightu.png";
left.src = "ui/tabLeftu.png";
tab.style.backgroundImage = "url(ui/tabBgu.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">
<html metal:use-macro="context/skyn/template/macros/main">
<html metal:use-macro="context/ui/template/macros/main">
<metal:fill fill-slot="content"
tal:define="contextObj context/getParentNode;
errors request/errors | python:{};
@ -13,25 +13,25 @@
<tal:comment replace="nothing">Include type-specific CSS and JS.</tal:comment>
<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"
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"
tal:attributes="action python: contextObj.absolute_url()+'/skyn/do';
tal:attributes="action python: contextObj.absolute_url()+'/do';
class python: test(confirmMsg, 'atBaseEditForm', 'enableUnloadProtection atBaseEditForm')">
<input type="hidden" name="action" value="Update"/>
<input type="hidden" name="page" tal:attributes="value page"/>
<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="confirmed" value="False"/>
<metal:show use-macro="here/skyn/page/macros/show"/>
<metal:show use-macro="context/ui/page/macros/show"/>
</form>
<script tal:condition="confirmMsg"
tal:content="python: 'askConfirm(\'script\', \'postConfirmedEditForm()\', \'%s\')' % confirmMsg">
</script>
<metal:footer use-macro="here/skyn/page/macros/footer"/>
<metal:footer use-macro="context/ui/page/macros/footer"/>
</metal:fill>
</html>
</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>
<metal:fill fill-slot="main"
tal:define="appFolder context/getParentNode;
contentType request/type_name;
className request/className;
tool python: portal.get('portal_%s' % appFolder.id.lower());
importElems python: tool.getImportElements(contentType);
importElems python: tool.getImportElements(className);
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">
<!--
var importedElemsShown = false;
@ -68,9 +68,9 @@
</script>
<tal:comment replace="nothing">Form for importing several meetings at once.</tal:comment>
<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="type_name" tal:attributes="value contentType"/>
<input type="hidden" name="className" tal:attributes="value className"/>
<input type="hidden" name="importPath" value=""/>
</form>
@ -79,19 +79,19 @@
<tr>
<th tal:repeat="columnHeader python: importElems[0]">
<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')"
style="cursor:pointer" onClick="toggleViewableElements()" align="left" />
<span tal:replace="columnHeader"/>
</th>
<th tal:content="python: tool.translate('ref_actions')"></th>
<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')"
onClick="toggleCheckboxes()" style="cursor:pointer"/>
</tr>
<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;
odd repeat/row/odd"
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">
<tal:comment replace="nothing">Go to the first page</tal:comment>
<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');
onClick python: navBaseCall.replace('**v**', '0'+sortAndFilter)"/></td>
<tal:comment replace="nothing">Go to the previous page</tal:comment>
<td tal:define="sNumber python: startNumber - batchSize"
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');
onClick python: navBaseCall.replace('**v**', str(sNumber)+sortAndFilter)"/></td>
<tal:comment replace="nothing">Explain which elements are currently shown</tal:comment>
<td class="discreet">&nbsp;
<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: totalNumber"/>&nbsp;&nbsp;
</td>
<tal:comment replace="nothing">Go to the next page</tal:comment>
<td tal:define="sNumber python: startNumber + batchSize"
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');
onClick python: navBaseCall.replace('**v**', str(sNumber)+sortAndFilter)"/></td>
<tal:comment replace="nothing">Go to the last page</tal:comment>
@ -39,7 +39,7 @@
nbOfCountedPages python: test(lastPageIsIncomplete, nbOfCompletePages, nbOfCompletePages-1);
sNumber python: (nbOfCountedPages*batchSize)"
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');
onClick python: navBaseCall.replace('**v**', str(sNumber)+sortAndFilter)"/></td>
</tr>
@ -64,15 +64,15 @@
<tr valign="middle">
<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"
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>
<tal:comment replace="nothing">Go to the first page</tal:comment>
<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>
<tal:comment replace="nothing">Go to the previous page</tal:comment>
<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>
<tal:comment replace="nothing">Explain which element is currently shown</tal:comment>
<td class="discreet">&nbsp;
@ -81,11 +81,11 @@
</td>
<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"
tal:attributes="src string: $appUrl/skyn/arrowRightSimple.png;
tal:attributes="src string: $appUrl/ui/arrowRightSimple.png;
title python: tool.translate('goto_next')"/></a></td>
<tal:comment replace="nothing">Go to the last page</tal:comment>
<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>
</tr>
</table>
@ -97,11 +97,11 @@
</tal:comment>
<metal:sortAndFilter define-macro="sortAndFilter" tal:define="fieldName widget/name">
<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))"
tal:condition="python: (sortKey != fieldName) or (sortOrder == 'desc')"
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))"
tal:condition="python: (sortKey != fieldName) or (sortOrder == 'asc')"
style="cursor:pointer"/>
@ -110,7 +110,7 @@
<input type="text" size="7"
tal:attributes="id python: '%s_%s' % (ajaxHookId, fieldName);
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))"
style="cursor:pointer"/>
</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>
<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="objectUid"/>
</form>
@ -30,7 +30,7 @@
will be rendered.
</tal:comment>
<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>
<tal:comment replace="nothing">
@ -44,10 +44,10 @@
tal:attributes="width layout/width">
<tr tal:repeat="widget python: contextObj.getGroupedAppyTypes(layoutType, page)">
<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 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>
</tr>
</table>
@ -68,7 +68,7 @@
<tal:comment replace="nothing">Table containing the history</tal:comment>
<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">
<tr i18n:domain="plone">
<th align="left" i18n:translate="listingheader_action">Action</th>
@ -86,11 +86,7 @@
<td tal:condition="not: isDataChange"
tal:content="python: tool.translate(contextObj.getWorkflowLabel(event['action']))"
tal:attributes="class string:state-${state}"/>
<td tal:define="actorid python:event.get('actor');
actor python:contextObj.portal_membership.getMemberInfo(actorid);
fullname actor/fullname|nothing;
username actor/username|nothing"
tal:content="python:fullname or username or actorid"/>
<td tal:define="actorid python:event.get('actor');" tal:content="actorid"/>
<td tal:content="python:contextObj.restrictedTraverse('@@plone').toLocalizedTime(event['time'],long_format=True)"/>
<td tal:condition="not: isDataChange"><tal:comment condition="rhComments" tal:content="structure rhComments"/>
<tal:noComment condition="not: rhComments" i18n:translate="no_comments" i18n:domain="plone"/></td>
@ -136,7 +132,7 @@
tal:content="python: _(contextObj.getWorkflowLabel(stateInfo['name']))">
</td>
<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>
</tal:state>
</tr>
@ -150,8 +146,8 @@
tal:define="transitions contextObj/getAppyTransitions"
tal:condition="transitions">
<form id="triggerTransitionForm" method="post"
tal:attributes="action python: contextObj.absolute_url() + '/skyn/do'">
<input type="hidden" name="action" value="Do"/>
tal:attributes="action python: contextObj.absolute_url() + '/do'">
<input type="hidden" name="action" value="Trigger"/>
<input type="hidden" name="workflow_action"/>
<table>
<tr valign="middle">
@ -203,22 +199,19 @@
<tal:comment replace="nothing">Plus/minus icon for accessing history</tal:comment>
<tal:accessHistory condition="hasHistory">
<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;
<span i18n:translate="label_history" i18n:domain="plone" class="historyLabel">History</span> ||&nbsp;
</tal:accessHistory>
<tal:comment replace="nothing">Show document creator</tal:comment>
<span class="by" tal:condition="creator"
tal:define="author python:contextObj.portal_membership.getMemberInfo(creator)">
<span i18n:domain="plone" i18n:translate="label_by_author">
by <span tal:content="python:author and author['fullname'] or creator"
tal:omit-tag="not:author" i18n:name="author"/>
<span class="by" tal:condition="creator">
<span>by <span tal:replace="creator"/>
&mdash;
</span>
</span>
<tal:comment replace="nothing">Show last modification date</tal:comment>
<span tal:replace="python:contextObj.restrictedTraverse('@@plone').toLocalizedTime(contextObj.ModificationDate(),long_format=1)"></span>
<tal:comment replace="nothing">Show creation date</tal:comment>
<span tal:replace="python:contextObj.created"></span>
</td>
</tr>
<tal:comment replace="nothing">Object history</tal:comment>
@ -239,8 +232,8 @@
<td colspan="2">
<table width="100%">
<tr>
<td><metal:states use-macro="here/skyn/page/macros/states"/></td>
<td align="right"><metal:states use-macro="here/skyn/page/macros/transitions"/></td>
<td><metal:states use-macro="here/ui/page/macros/states"/></td>
<td align="right"><metal:states use-macro="here/ui/page/macros/transitions"/></td>
</tr>
</table>
</td>
@ -250,7 +243,6 @@
<tal:comment replace="nothing">The page footer.</tal:comment>
<metal:footer define-macro="footer">
<tal:dummy define="messages app/plone_utils/showPortalMessages"/>
<script language="javascript">
<!--
initSlaves();
@ -271,12 +263,12 @@
<tal:button condition="isEdit">
<input type="image" class="imageInput" style="cursor:pointer" name="buttonPrevious"
title="label_previous" i18n:attributes="title" i18n:domain="plone"
tal:attributes="src string:$appUrl/skyn/previous.png"/>
tal:attributes="src string:$appUrl/ui/previous.png"/>
<input type="hidden" name="previousPage" tal:attributes="value previousPage"/>
</tal:button>
<tal:link condition="not: isEdit">
<a tal:attributes="href python: contextObj.getUrl(page=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"/>
</a>
</tal:link>
@ -285,38 +277,38 @@
<tal:save condition="python: isEdit and pageInfo['showSave']">
<input type="image" class="imageInput" style="cursor:pointer" name="buttonOk"
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:cancel condition="python: isEdit and pageInfo['showCancel']">
<input type="image" class="imageInput" style="cursor:pointer" name="buttonCancel"
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:edit condition="python: not isEdit and pageInfo['showOnEdit']">
<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);
src string: $appUrl/skyn/editBig.png"
src string: $appUrl/ui/editBig.png"
tal:condition="python: contextObj.allows('Modify portal content')"/>
</tal:edit>
<tal:refresh condition="contextObj/isDebug">
<img title="Refresh" style="cursor:pointer"
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:next condition="python: nextPage and pageInfo['showNext']">
<tal:button condition="isEdit">
<input type="image" class="imageInput" style="cursor:pointer" name="buttonNext"
title="label_next" i18n:attributes="title" i18n:domain="plone"
tal:attributes="src string:$appUrl/skyn/next.png"/>
tal:attributes="src string:$appUrl/ui/next.png"/>
<input type="hidden" name="nextPage" tal:attributes="value nextPage"/>
</tal:button>
<tal:link condition="not: isEdit">
<a tal:attributes="href python: contextObj.getUrl(page=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"/>
</a>
</tal:link>
@ -326,14 +318,6 @@
<tal:comment replace="nothing">
This macro displays the global message on the page.
</tal:comment>
<metal:message define-macro="message">
<tal:comment replace="nothing">Single message from portal_status_message request key</tal:comment>
<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 define-macro="message" tal:define="messages tool/getMessages" tal:condition="messages">
<div class="message" tal:content="structure python: ''.join([m[1] for m in messages])"></div>
</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.
</tal:comment>
<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;
currentSearch req/search|nothing;
currentType req/type_name|nothing;
currentClass req/className|nothing;
contextObj tool/getPublishedObject;
rootClasses tool/getRootClasses">
<tal:publishedObject condition="python: contextObj and contextObj.mayNavigate()">
<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: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:comment replace="nothing">Section title, with action icons</tal:comment>
<table width="100%"
tal:define="afUrl appFolder/absolute_url"
tal:attributes="class python:test((repeat['rootClass'].number()==1) and not contextObj, '', 'portletSep')">
<tr>
<td>
<a tal:attributes="href python: '%s?type_name=%s' % (queryUrl, rootClass);
class python:test(not currentSearch and (currentType==rootClass), 'portletCurrent', '')"
<a tal:attributes="href python: '%s?className=%s' % (queryUrl, rootClass);
class python:test(not currentSearch and (currentClass==rootClass), 'portletCurrent', '')"
tal:content="structure python: _(rootClass + '_plural')"></a>
</td>
<td align="right"
@ -33,21 +32,21 @@
<tal:comment replace="nothing">Create a new object from a web form</tal:comment>
<img style="cursor:pointer"
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);
src string: $appUrl/skyn/plus.png;
tal:attributes="onClick python: 'href: window.location=\'%s/do?action=Create&className=%s\'' % (toolUrl, rootClass);
src string: $appUrl/ui/plus.png;
title python: _('query_create')"/>
<tal:comment replace="nothing">Create (a) new object(s) by importing data</tal:comment>
<img style="cursor:pointer"
tal:condition="python: ('import' in createMeans) and userMayAdd"
tal:attributes="onClick python: 'href: window.location=\'%s/skyn/import?type_name=%s\'' % (toolUrl, rootClass);
src string: $appUrl/skyn/import.png;
tal:attributes="onClick python: 'href: window.location=\'%s/ui/import?className=%s\'' % (toolUrl, rootClass);
src string: $appUrl/ui/import.png;
title python: _('query_import')"/>
<tal:comment replace="nothing">Search objects of this type</tal:comment>
<img style="cursor:pointer"
tal:define="showSearch python: tool.getAttr('enableAdvancedSearchFor%s' % rootClass)"
tal:condition="showSearch"
tal:attributes="onClick python: 'href: window.location=\'%s/skyn/search?type_name=%s\'' % (toolUrl, rootClass);
src string: $appUrl/skyn/search.gif;
tal:attributes="onClick python: 'href: window.location=\'%s/ui/search?className=%s\'' % (toolUrl, rootClass);
src string: $appUrl/ui/search.gif;
title python: _('search_objects')"/>
</td>
</tr>
@ -61,7 +60,7 @@
<dt class="portletAppyItem portletGroup">
<img align="left" style="cursor:pointer"
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;
<span tal:replace="group/label"/>
</dt>
@ -69,7 +68,7 @@
<span tal:attributes="id group/labelId;
style python:test(expanded, 'display:block', 'display:none')">
<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;
class python: test(search['name'] == currentSearch, 'portletCurrent', '');"
tal:content="structure search/label"></a>
@ -80,7 +79,7 @@
<dt tal:define="search searchOrGroup" tal:condition="not: searchOrGroup/isGroup"
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;
class python: test(search['name'] == currentSearch, 'portletCurrent', '');"
tal:content="structure search/label"></a>
@ -120,7 +119,7 @@
<td align="right">
<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);
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']"/>
</td>
</tr>
@ -139,7 +138,7 @@
<td align="right">
<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);
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']"/>
</td>
</tr>
@ -149,7 +148,7 @@
</tr>
<tal:comment replace="nothing">The down arrow pointing to the next phase (if any)</tal:comment>
<tr tal:condition="python: phase['name'] != phases[-1]['name']">
<td>&nbsp;&nbsp;<img tal:attributes="src string: $appUrl/skyn/nextPhase.png"/></td>
<td>&nbsp;&nbsp;<img tal:attributes="src string: $appUrl/ui/nextPhase.png"/></td>
</tr>
</tal:phase>
</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">
<html metal:use-macro="context/skyn/template/macros/main">
<html metal:use-macro="context/ui/template/macros/main">
<metal:fill fill-slot="content"
tal:define="contentType request/type_name;
tal:define="className request/className;
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>
<div id="queryResult"></div>
<script type="text/javascript"
tal:define="ajaxUrl python: tool.getQueryUrl(contentType, searchName)"
tal:content="python: 'askQueryResult(\'queryResult\', \'%s\',\'%s\',\'%s\',0)' % (tool.absolute_url(), contentType, searchName)">
tal:define="ajaxUrl python: tool.getQueryUrl(className, searchName)"
tal:content="python: 'askQueryResult(\'queryResult\', \'%s\',\'%s\',\'%s\',0)' % (tool.absolute_url(), className, searchName)">
</script>
</metal:fill>
</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"
tal:define="contentType request/type_name;
tal:define="className request/className;
refInfo tool/getRefInfo;
refObject python: refInfo[0];
refField python: refInfo[1];
@ -7,27 +7,27 @@
startNumber request/startNumber|python:'0';
startNumber python: int(startNumber);
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;
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:'';
sortOrder request/sortOrder| python:'asc';
filterKey request/filterKey| 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;
totalNumber queryResult/totalNumber;
batchSize queryResult/batchSize;
ajaxHookId python:'queryResult';
navBaseCall python: 'askQueryResult(\'%s\',\'%s\',\'%s\',\'%s\',**v**)' % (ajaxHookId, tool.absolute_url(), contentType, searchName);
newSearchUrl python: '%s/skyn/search?type_name=%s%s' % (tool.absolute_url(), contentType, refUrlPart)">
navBaseCall python: 'askQueryResult(\'%s\',\'%s\',\'%s\',\'%s\',**v**)' % (ajaxHookId, tool.absolute_url(), className, searchName);
newSearchUrl python: '%s/ui/search?className=%s%s' % (tool.absolute_url(), className, refUrlPart)">
<tal:result condition="objs">
<fieldset>
<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"/>)
<tal:newSearch condition="python: searchName == '_advanced'">
&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>
<table align="right"
tal:define="widgets python: tool.getResultPodFields(contentType);
tal:define="widgets python: tool.getResultPodFields(className);
layoutType python:'view'"
tal:condition="python: objs and widgets">
<tr><td tal:define="contextObj python: objs[0]"
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>
</table>
@ -56,21 +56,21 @@
</tal:descr>
<td align="right" width="25%">
<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>
</tr>
</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);"
class="list" width="100%">
<tal:comment replace="nothing">Headers, with filters and sort arrows</tal:comment>
<tr>
<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;">
<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>
</tal:header>
<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>
<td id="field_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>
</td>
@ -107,7 +107,7 @@
layoutType python:'cell';
innerRef python:True"
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>
</td>
</tal:fields>
@ -122,17 +122,17 @@
<tr>
<tal:comment replace="nothing">Edit the element</tal:comment>
<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:condition="python: obj.allows('Modify portal content')">
<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>
<tal:comment replace="nothing">Delete the element</tal:comment>
<td>
<img tal:condition="python: obj.allows('Delete objects') and obj.mayDelete()"
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()"/>
</td>
</tr>
@ -143,7 +143,7 @@
</table>
<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>
</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">
<html metal:use-macro="context/skyn/template/macros/main">
<html metal:use-macro="context/ui/template/macros/main">
<metal:fill fill-slot="content"
tal:define="contentType request/type_name;
tal:define="className request/className;
refInfo request/ref|nothing;
searchInfo python: tool.getSearchInfo(contentType, refInfo);
searchInfo python: tool.getSearchInfo(className, refInfo);
cssJs python: tool.getCssAndJs(searchInfo['fields'], 'edit')">
<tal:comment replace="nothing">Include type-specific CSS and JS.</tal:comment>
<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"
tal:attributes="src string:$appUrl/skyn/$jsFile"></script>
tal:attributes="src string:$appUrl/ui/$jsFile"></script>
<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/>
<tal:comment replace="nothing">Form for searching objects of request/type_name.</tal:comment>
<form name="search" tal:attributes="action python: appFolder.absolute_url()+'/skyn/do'" method="post">
<tal:comment replace="nothing">Form for searching objects of request/className.</tal:comment>
<form name="search" tal:attributes="action python: tool.absolute_url()+'/do'" method="post">
<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"/>
<table width="100%">
@ -30,7 +30,7 @@
<tal:show define="name widget/name;
widgetName python: 'w_%s' % name;
macroPage python: widget['type'].lower()">
<metal:call use-macro="python: appFolder.skyn.widgets.get(macroPage).macros.get('search')"/>
<metal:call use-macro="python: getattr(appFolder.ui.widgets, macroPage).macros['search']"/>
</tal:show>
</tal:field><br class="discreet"/>
</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"
tal:define="user context/portal_membership/getAuthenticatedMember;
isAnon python: user.getUserName() == 'Anonymous User';
tal:define="user tool/getUser;
isAnon tool/userIsAnon;
app tool/getApp;
appUrl app/absolute_url;
appFolder tool/getAppFolder;
appName appFolder/getId;
appFolder app/data;
appName tool/getAppName;
_ python: tool.translate;
req python: request;
resp req/RESPONSE;
@ -14,8 +14,8 @@
<head>
<title tal:content="tool/getAppName"></title>
<link rel="stylesheet" type="text/css" tal:attributes="href string:$appUrl/skyn/appy.css"/>
<script type="text/javascript" tal:attributes="src string:$appUrl/skyn/appy.js"></script>
<link rel="stylesheet" type="text/css" tal:attributes="href string:$appUrl/ui/appy.css"/>
<script type="text/javascript" tal:attributes="src string:$appUrl/ui/appy.js"></script>
</head>
<body>
@ -26,30 +26,29 @@
<table width="100%">
<tr valign="top">
<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>
<td align="right"
tal:define="appLangs app/portal_languages/listSupportedLanguages;
defLang python: app.portal_languages.getLanguageBindings()[0];
tal:define="languages tool/getLanguages;
defaultLanguage python: languages[0];
suffix python: req.get('ACTUAL_URL').split('/')[-1];
asLinks python: len(appLangs) &lt;= 5"
tal:condition="python: len(appLangs) &gt;= 2 and (suffix not in ('edit', 'query', 'search'))">
asLinks python: len(languages) &lt;= 5"
tal:condition="python: len(languages) &gt;= 2 and (suffix not in ('edit', 'query', 'search'))">
<table tal:condition="asLinks">
<tr>
<td tal:repeat="lang appLangs">
<td tal:repeat="lang languages">
<a class="lang"
tal:attributes="href python: req.get('ACTUAL_URL')+'/switchLanguage?set_language=%s' % lang[0];
title python: app.portal_languages.getNameForLanguageCode(lang[1])"
tal:content="python: lang[0]"></a>
tal:attributes="href python: req.get('ACTUAL_URL')+'/switchLanguage?set_language=%s' % lang;
title python: tool.getLanguageName(lang)"
tal:content="python: lang"></a>
</td>
</tr>
</table>
<select tal:condition="not: asLinks"
tal:attributes="onchange string:window.location='${context/absolute_url}/switchLanguage?set_language=' + this.options[this.selectedIndex].value">
<option tal:repeat="lang appLangs"
tal:content="python:app.portal_languages.getNameForLanguageCode(lang[0]) or lang[1]"
tal:attributes="selected python:defLanguage == lang[0];
value python:lang[0]">
<option tal:repeat="lang languages"
tal:content="python: tool.getLanguageName(lang)"
tal:attributes="selected python:defaultLanguage == lang; value lang">
</option>
</select>
</td>
@ -61,7 +60,7 @@
<tr>
<td>
<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>
<tal:comment replace="nothing">Grey background shown when popups are shown</tal:comment>
<div id="grey" class="grey"></div>
@ -89,8 +88,7 @@
<tr>
<td>
<tal:comment replace="nothing">The user login form for anonymous users</tal:comment>
<table align="center" tal:condition="isAnon" class="login"
tal:define="auth nocall:app/acl_users/credentials_cookie_auth">
<table align="center" tal:condition="isAnon" class="login">
<tr><td>
<form name="loginform" method="post"
tal:attributes="action python: tool.absolute_url() + '/performLogin'">
@ -115,17 +113,17 @@
<td>
<!-- Go 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>
<!-- Config -->
<img style="cursor:pointer" tal:condition="python: user.has_role('Manager')"
tal:attributes="onClick python: 'href: window.location=\'%s\'' % tool.getUrl(page='main', nav='');
title python: _('%sTool' % appName);
src string:$appUrl/skyn/appyConfig.gif"/>
src string:$appUrl/ui/appyConfig.gif"/>
<!-- Logout -->
<a tal:attributes="href python: tool.absolute_url() + '/performLogout';
title python: _('logout')">
<img tal:attributes="src string: $appUrl/skyn/logout.gif"/>
<img tal:attributes="src string: $appUrl/ui/logout.gif"/>
</a>
</td>
<td align="right" tal:content="python: tool.getUserLine(user)"></td>
@ -142,7 +140,7 @@
<tr valign="top">
<tal:comment replace="nothing">Portlet</tal:comment>
<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>
<tal:comment replace="nothing">Page content</tal:comment>
<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