appy.gen: first Ploneless version.
|
@ -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)
|
||||
|
|
|
@ -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__)
|
||||
|
|
|
@ -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()
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -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'
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
## Python Script "do.py"
|
||||
##bind context=context
|
||||
##parameters=action
|
||||
rq = context.REQUEST
|
||||
|
||||
# Get the object impacted by the action.
|
||||
if rq.get('objectUid', None):
|
||||
obj = context.portal_catalog(UID=rq['objectUid'])[0].getObject()
|
||||
else:
|
||||
obj = context.getParentNode() # An appy obj or in some cases the app folder.
|
||||
if obj.portal_type == 'AppyFolder':
|
||||
from Products.CMFCore.utils import getToolByName
|
||||
portal = getToolByName(obj, 'portal_url').getPortalObject()
|
||||
obj = portal.get('portal_%s' % obj.id.lower()) # The tool
|
||||
return obj.getMethod('on'+action)()
|
|
@ -1,17 +0,0 @@
|
|||
<tal:main define="tool context/getTool">
|
||||
<html metal:use-macro="context/skyn/template/macros/main">
|
||||
<metal:fill fill-slot="content"
|
||||
tal:define="contextObj python:context.getParentNode();
|
||||
portal_type python:here.getPortalTypeName().lower().replace(' ', '_');
|
||||
errors python:req.get('errors', {});
|
||||
layoutType python:'view';
|
||||
layout python: contextObj.getPageLayout(layoutType);
|
||||
phaseInfo python: contextObj.getAppyPhases(currentOnly=True, layoutType='view');
|
||||
page req/page|python:'main';
|
||||
phase phaseInfo/name;">
|
||||
<metal:prologue use-macro="here/skyn/page/macros/prologue"/>
|
||||
<metal:show use-macro="here/skyn/page/macros/show"/>
|
||||
<metal:footer use-macro="here/skyn/page/macros/footer"/>
|
||||
</metal:fill>
|
||||
</html>
|
||||
</tal:main>
|
|
@ -1,34 +1,43 @@
|
|||
<!codeHeader!>
|
||||
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!>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -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!>]
|
||||
|
|
|
@ -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>
|
||||
|
|
Before Width: | Height: | Size: 339 B |
|
@ -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.'''
|
||||
|
|
|
@ -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('<','<').replace('>','>')
|
||||
sourceMsg = sourceMsg.replace('\n', '<br/>')
|
||||
return '<div class="translationLabel"><acronym title="%s">' \
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'));
|
|
@ -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;}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 899 B After Width: | Height: | Size: 899 B |
Before Width: | Height: | Size: 232 B After Width: | Height: | Size: 232 B |
Before Width: | Height: | Size: 212 B After Width: | Height: | Size: 212 B |
Before Width: | Height: | Size: 218 B After Width: | Height: | Size: 218 B |
Before Width: | Height: | Size: 212 B After Width: | Height: | Size: 212 B |
Before Width: | Height: | Size: 216 B After Width: | Height: | Size: 216 B |
Before Width: | Height: | Size: 232 B After Width: | Height: | Size: 232 B |
Before Width: | Height: | Size: 953 B After Width: | Height: | Size: 953 B |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 70 B After Width: | Height: | Size: 70 B |
Before Width: | Height: | Size: 244 B After Width: | Height: | Size: 244 B |
Before Width: | Height: | Size: 239 B After Width: | Height: | Size: 239 B |
Before Width: | Height: | Size: 818 B After Width: | Height: | Size: 818 B |
Before Width: | Height: | Size: 248 B After Width: | Height: | Size: 248 B |
Before Width: | Height: | Size: 475 B After Width: | Height: | Size: 475 B |
|
@ -1,5 +1,5 @@
|
|||
<tal:main define="tool context/getTool">
|
||||
<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>
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 72 B After Width: | Height: | Size: 72 B |
Before Width: | Height: | Size: 372 B After Width: | Height: | Size: 372 B |
Before Width: | Height: | Size: 62 B After Width: | Height: | Size: 62 B |
BIN
gen/ui/folder.gif
Normal file
After Width: | Height: | Size: 903 B |
Before Width: | Height: | Size: 719 B After Width: | Height: | Size: 719 B |
Before Width: | Height: | Size: 225 B After Width: | Height: | Size: 225 B |
Before Width: | Height: | Size: 591 B After Width: | Height: | Size: 591 B |
Before Width: | Height: | Size: 589 B After Width: | Height: | Size: 589 B |
Before Width: | Height: | Size: 705 B After Width: | Height: | Size: 705 B |
|
@ -14,12 +14,12 @@
|
|||
<body>
|
||||
<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';
|
Before Width: | Height: | Size: 51 B After Width: | Height: | Size: 51 B |
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 636 B After Width: | Height: | Size: 636 B |
|
@ -11,26 +11,26 @@
|
|||
<tr valign="middle">
|
||||
<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">
|
||||
<span tal:replace="python: startNumber+1"/>
|
||||
<img tal:attributes="src string: $appUrl/skyn/to.png"/>
|
||||
<img tal:attributes="src string: $appUrl/ui/to.png"/>
|
||||
<span tal:replace="python: startNumber+len(objs)"/> <b>//</b>
|
||||
<span tal:replace="python: totalNumber"/>
|
||||
</td>
|
||||
<tal:comment replace="nothing">Go to the next page</tal:comment>
|
||||
<td tal:define="sNumber python: startNumber + batchSize"
|
||||
tal:condition="python: sNumber < totalNumber"><img style="cursor:pointer"
|
||||
tal:attributes="src string: $appUrl/skyn/arrowRightSimple.png;
|
||||
tal:attributes="src string: $appUrl/ui/arrowRightSimple.png;
|
||||
title python: tool.translate('goto_next');
|
||||
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">
|
||||
|
@ -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>
|
Before Width: | Height: | Size: 951 B After Width: | Height: | Size: 951 B |
Before Width: | Height: | Size: 211 B After Width: | Height: | Size: 211 B |
Before Width: | Height: | Size: 192 B After Width: | Height: | Size: 192 B |
BIN
gen/ui/object.gif
Normal file
After Width: | Height: | Size: 114 B |
Before Width: | Height: | Size: 754 B After Width: | Height: | Size: 754 B |
|
@ -6,7 +6,7 @@
|
|||
<script language="javascript" tal:content="tool/getJavascriptMessages"></script>
|
||||
|
||||
<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"/>
|
||||
<span i18n:translate="label_history" i18n:domain="plone" class="historyLabel">History</span> ||
|
||||
</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"/>
|
||||
—
|
||||
</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>
|
Before Width: | Height: | Size: 228 B After Width: | Height: | Size: 228 B |
Before Width: | Height: | Size: 246 B After Width: | Height: | Size: 246 B |
|
@ -2,28 +2,27 @@
|
|||
This macro displays the content of the application portlet.
|
||||
</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']"/>
|
||||
<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> <img tal:attributes="src string: $appUrl/skyn/nextPhase.png"/></td>
|
||||
<td> <img tal:attributes="src string: $appUrl/ui/nextPhase.png"/></td>
|
||||
</tr>
|
||||
</tal:phase>
|
||||
</table>
|
Before Width: | Height: | Size: 905 B After Width: | Height: | Size: 905 B |
|
@ -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>
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 53 B After Width: | Height: | Size: 53 B |
|
@ -1,5 +1,5 @@
|
|||
<metal:queryResults define-macro="queryResult"
|
||||
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'">
|
||||
— <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"/>
|
||||
<metal:pod use-macro="context/ui/widgets/show/macros/field"/>
|
||||
</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>
|
||||
|
Before Width: | Height: | Size: 225 B After Width: | Height: | Size: 225 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 189 B After Width: | Height: | Size: 189 B |
|
@ -1,25 +1,25 @@
|
|||
<tal:main define="tool context/getParentNode">
|
||||
<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>
|
Before Width: | Height: | Size: 235 B After Width: | Height: | Size: 235 B |
Before Width: | Height: | Size: 367 B After Width: | Height: | Size: 367 B |
Before Width: | Height: | Size: 316 B After Width: | Height: | Size: 316 B |
Before Width: | Height: | Size: 97 B After Width: | Height: | Size: 97 B |
Before Width: | Height: | Size: 97 B After Width: | Height: | Size: 97 B |
Before Width: | Height: | Size: 43 B After Width: | Height: | Size: 43 B |
Before Width: | Height: | Size: 149 B After Width: | Height: | Size: 149 B |
Before Width: | Height: | Size: 168 B After Width: | Height: | Size: 168 B |
Before Width: | Height: | Size: 203 B After Width: | Height: | Size: 203 B |
Before Width: | Height: | Size: 180 B After Width: | Height: | Size: 180 B |
Before Width: | Height: | Size: 207 B After Width: | Height: | Size: 207 B |
Before Width: | Height: | Size: 171 B After Width: | Height: | Size: 171 B |
|
@ -1,10 +1,10 @@
|
|||
<html metal:define-macro="main"
|
||||
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) <= 5"
|
||||
tal:condition="python: len(appLangs) >= 2 and (suffix not in ('edit', 'query', 'search'))">
|
||||
asLinks python: len(languages) <= 5"
|
||||
tal:condition="python: len(languages) >= 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>
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |