2009-06-29 07:06:01 -05:00
|
|
|
'''This package contains stuff used at run-time for installing a generated
|
|
|
|
Plone product.'''
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
2010-01-17 15:01:14 -06:00
|
|
|
import os, os.path, time
|
2009-06-29 07:06:01 -05:00
|
|
|
from StringIO import StringIO
|
|
|
|
from sets import Set
|
2009-10-18 07:52:27 -05:00
|
|
|
import appy
|
2010-08-05 11:23:17 -05:00
|
|
|
from appy.gen import Type, Ref
|
2011-01-14 02:06:25 -06:00
|
|
|
from appy.gen.po import PoParser
|
2009-06-29 07:06:01 -05:00
|
|
|
from appy.gen.utils import produceNiceMessage
|
|
|
|
from appy.gen.plone25.utils import updateRolesForPermission
|
2011-01-14 02:06:25 -06:00
|
|
|
from appy.shared.data import languages
|
2009-06-29 07:06:01 -05:00
|
|
|
|
2010-08-05 11:23:17 -05:00
|
|
|
class ZCTextIndexInfo:
|
|
|
|
'''Silly class used for storing information about a ZCTextIndex.'''
|
|
|
|
lexicon_id = "plone_lexicon"
|
|
|
|
index_type = 'Okapi BM25 Rank'
|
|
|
|
|
2009-06-29 07:06:01 -05:00
|
|
|
class PloneInstaller:
|
|
|
|
'''This Plone installer runs every time the generated Plone product is
|
|
|
|
installed or uninstalled (in the Plone configuration interface).'''
|
2010-09-02 09:16:08 -05:00
|
|
|
def __init__(self, reinstall, ploneSite, config):
|
|
|
|
# p_cfg is the configuration module of the Plone product.
|
2009-06-29 07:06:01 -05:00
|
|
|
self.reinstall = reinstall # Is it a fresh install or a re-install?
|
|
|
|
self.ploneSite = ploneSite
|
2010-09-02 09:16:08 -05:00
|
|
|
self.config = cfg = config
|
|
|
|
# Unwrap some useful variables from config
|
|
|
|
self.productName = cfg.PROJECTNAME
|
|
|
|
self.minimalistPlone = cfg.minimalistPlone
|
|
|
|
self.appClasses = cfg.appClasses
|
|
|
|
self.appClassNames = cfg.appClassNames
|
|
|
|
self.allClassNames = cfg.allClassNames
|
|
|
|
self.catalogMap = cfg.catalogMap
|
|
|
|
self.applicationRoles = cfg.applicationRoles # Roles defined in the app
|
|
|
|
self.defaultAddRoles = cfg.defaultAddRoles
|
|
|
|
self.appFrontPage = cfg.appFrontPage
|
|
|
|
self.showPortlet = cfg.showPortlet
|
|
|
|
self.languages = cfg.languages
|
|
|
|
self.languageSelector = cfg.languageSelector
|
|
|
|
self.attributes = cfg.attributes
|
|
|
|
# A buffer for logging purposes
|
2009-06-29 07:06:01 -05:00
|
|
|
self.toLog = StringIO()
|
|
|
|
self.typeAliases = {'sharing': '', 'gethtml': '',
|
2009-11-20 13:17:06 -06:00
|
|
|
'(Default)': 'skynView', 'edit': 'skyn/edit',
|
2009-06-29 07:06:01 -05:00
|
|
|
'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()
|
|
|
|
|
2010-08-05 11:23:17 -05:00
|
|
|
@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
|
2010-11-30 10:41:18 -06:00
|
|
|
zopeCatalog = catalog._catalog
|
2010-08-05 11:23:17 -05:00
|
|
|
for indexName, indexType in indexInfo.iteritems():
|
2010-11-30 10:41:18 -06:00
|
|
|
# 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:
|
2010-08-05 11:23:17 -05:00
|
|
|
# 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))
|
2009-06-29 07:06:01 -05:00
|
|
|
|
|
|
|
actionsToHide = {
|
|
|
|
'portal_actions': ('sitemap', 'accessibility', 'change_state','sendto'),
|
|
|
|
'portal_membership': ('mystuff', 'preferences'),
|
|
|
|
'portal_undo': ('undo',)
|
|
|
|
}
|
|
|
|
def customizePlone(self):
|
|
|
|
'''Hides some UI elements that appear by default in Plone.'''
|
|
|
|
for portalName, toHide in self.actionsToHide.iteritems():
|
|
|
|
portal = getattr(self.ploneSite, portalName)
|
|
|
|
portalActions = portal.listActions()
|
|
|
|
for action in portalActions:
|
|
|
|
if action.id in toHide: action.visible = False
|
|
|
|
|
|
|
|
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
|
2009-10-18 07:52:27 -05:00
|
|
|
root classes. Creates also the 'appy' folder (more precisely,
|
|
|
|
a Filesystem Directory View) at the root of the site, for storing
|
|
|
|
appy-wide ZPTs an images.'''
|
2009-06-29 07:06:01 -05:00
|
|
|
# Register first our own Appy folder type if needed.
|
|
|
|
site = self.ploneSite
|
|
|
|
if not hasattr(site.portal_types, self.appyFolderType):
|
|
|
|
self.registerAppyFolderType()
|
|
|
|
# Create the folder
|
|
|
|
if not hasattr(site.aq_base, self.productName):
|
|
|
|
# Temporarily allow me to create Appy large plone folders
|
|
|
|
getattr(site.portal_types, self.appyFolderType).global_allow = 1
|
2009-09-18 07:42:31 -05:00
|
|
|
# Allow to create Appy large folders in the plone site
|
|
|
|
getattr(site.portal_types,
|
|
|
|
'Plone Site').allowed_content_types += (self.appyFolderType,)
|
2009-06-29 07:06:01 -05:00
|
|
|
site.invokeFactory(self.appyFolderType, self.productName,
|
|
|
|
title=self.productName)
|
|
|
|
getattr(site.portal_types, self.appyFolderType).global_allow = 0
|
2010-09-13 14:04:10 -05:00
|
|
|
# Manager has been granted Add permissions for all root classes.
|
|
|
|
# This may not be desired, so remove this.
|
|
|
|
appFolder = getattr(site, self.productName)
|
|
|
|
for className in self.config.rootClasses:
|
|
|
|
permission = self.getAddPermission(className)
|
|
|
|
appFolder.manage_permission(permission, (), acquire=0)
|
|
|
|
else:
|
|
|
|
appFolder = getattr(site, self.productName)
|
2009-06-29 07:06:01 -05:00
|
|
|
# All roles defined as creators should be able to create the
|
|
|
|
# corresponding root content types in this folder.
|
|
|
|
i = -1
|
|
|
|
allCreators = set()
|
|
|
|
for klass in self.appClasses:
|
|
|
|
i += 1
|
2010-08-27 01:59:53 -05:00
|
|
|
if not klass.__dict__.has_key('root') or not klass.__dict__['root']:
|
|
|
|
continue # It is not a root class
|
|
|
|
creators = getattr(klass, 'creators', None)
|
|
|
|
if not creators: creators = self.defaultAddRoles
|
|
|
|
allCreators = allCreators.union(creators)
|
|
|
|
className = self.appClassNames[i]
|
|
|
|
permission = self.getAddPermission(className)
|
|
|
|
updateRolesForPermission(permission, tuple(creators), appFolder)
|
2009-06-29 07:06:01 -05:00
|
|
|
# Beyond content-type-specific "add" permissions, creators must also
|
|
|
|
# have the main permission "Add portal content".
|
2010-08-27 01:59:53 -05:00
|
|
|
permission = 'Add portal content'
|
|
|
|
updateRolesForPermission(permission, tuple(allCreators), appFolder)
|
2009-10-18 07:52:27 -05:00
|
|
|
# Creates the "appy" Directory view
|
2010-08-27 01:59:53 -05:00
|
|
|
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.
|
2010-09-02 09:16:08 -05:00
|
|
|
addDirView = self.config.manage_addDirectoryView
|
2010-08-27 01:59:53 -05:00
|
|
|
addDirView(site, appy.getPath() + '/gen/plone25/skin', id='skyn')
|
2009-06-29 07:06:01 -05:00
|
|
|
|
|
|
|
def installTypes(self):
|
|
|
|
'''Registers and configures the Plone content types that correspond to
|
|
|
|
gen-classes.'''
|
|
|
|
site = self.ploneSite
|
|
|
|
# Do Plone-based type registration
|
2010-09-02 09:16:08 -05:00
|
|
|
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__)
|
2009-06-29 07:06:01 -05:00
|
|
|
# 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':
|
2009-11-20 13:17:06 -06:00
|
|
|
page = 'skynView'
|
2009-06-29 07:06:01 -05:00
|
|
|
action.edit(action='string:${object_url}/%s' % page)
|
|
|
|
elif action.id == 'edit':
|
2009-10-18 07:52:27 -05:00
|
|
|
page = 'skyn/edit'
|
2009-06-29 07:06:01 -05:00
|
|
|
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)
|
|
|
|
|
|
|
|
# Configure CatalogMultiplex: tell what types will be catalogued or not.
|
2010-09-02 09:16:08 -05:00
|
|
|
atTool = getattr(site, self.config.ARCHETYPETOOLNAME)
|
2009-06-29 07:06:01 -05:00
|
|
|
for meta_type in self.catalogMap:
|
|
|
|
submap = self.catalogMap[meta_type]
|
|
|
|
current_catalogs = Set(
|
|
|
|
[c.id for c in atTool.getCatalogsByType(meta_type)])
|
|
|
|
if 'white' in submap:
|
|
|
|
for catalog in submap['white']:
|
|
|
|
current_catalogs.update([catalog])
|
|
|
|
if 'black' in submap:
|
|
|
|
for catalog in submap['black']:
|
|
|
|
if catalog in current_catalogs:
|
|
|
|
current_catalogs.remove(catalog)
|
|
|
|
atTool.setCatalogsByType(meta_type, list(current_catalogs))
|
|
|
|
|
|
|
|
def updatePodTemplates(self):
|
2010-10-14 07:43:56 -05:00
|
|
|
'''Creates or updates the POD templates in the tool according to pod
|
2009-06-29 07:06:01 -05:00
|
|
|
declarations in the application classes.'''
|
2010-10-14 07:43:56 -05:00
|
|
|
# Creates the templates for Pod fields if they do not exist.
|
2010-08-05 11:23:17 -05:00
|
|
|
for contentType, appyTypes in self.attributes.iteritems():
|
2010-02-12 03:59:42 -06:00
|
|
|
appyClass = self.tool.getAppyClass(contentType)
|
2010-04-26 11:19:34 -05:00
|
|
|
if not appyClass: continue # May be an abstract class
|
2010-08-05 11:23:17 -05:00
|
|
|
for appyType in appyTypes:
|
2010-10-14 07:43:56 -05:00
|
|
|
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)
|
2010-12-17 07:46:55 -06:00
|
|
|
self.appyTool.log('Imported "%s" in the tool in ' \
|
|
|
|
'attribute "%s"'% (fileName,attrName))
|
2010-10-14 07:43:56 -05:00
|
|
|
else:
|
|
|
|
self.appyTool.log('Template "%s" was not found!' % \
|
|
|
|
fileName, type='error')
|
2009-06-29 07:06:01 -05:00
|
|
|
|
|
|
|
def installTool(self):
|
2010-10-14 07:43:56 -05:00
|
|
|
'''Configures the application tool.'''
|
2009-06-29 07:06:01 -05:00
|
|
|
# Register the tool in Plone
|
|
|
|
try:
|
|
|
|
self.ploneSite.manage_addProduct[
|
|
|
|
self.productName].manage_addTool(self.toolName)
|
2010-09-02 09:16:08 -05:00
|
|
|
except self.config.BadRequest:
|
2009-06-29 07:06:01 -05:00
|
|
|
# If an instance with the same name already exists, this error will
|
|
|
|
# be unelegantly raised by Zope.
|
|
|
|
pass
|
|
|
|
except:
|
|
|
|
e = sys.exc_info()
|
|
|
|
if e[0] != 'Bad Request': raise
|
|
|
|
|
|
|
|
# Hide the tool from the search form
|
|
|
|
portalProperties = self.ploneSite.portal_properties
|
|
|
|
if portalProperties is not None:
|
|
|
|
siteProperties = getattr(portalProperties, 'site_properties', None)
|
|
|
|
if siteProperties is not None and \
|
|
|
|
siteProperties.hasProperty('types_not_searched'):
|
|
|
|
current = list(siteProperties.getProperty('types_not_searched'))
|
|
|
|
if self.toolName not in current:
|
|
|
|
current.append(self.toolName)
|
|
|
|
siteProperties.manage_changeProperties(
|
|
|
|
**{'types_not_searched' : current})
|
|
|
|
|
|
|
|
# Hide the tool in the navigation
|
|
|
|
if portalProperties is not None:
|
|
|
|
nvProps = getattr(portalProperties, 'navtree_properties', None)
|
|
|
|
if nvProps is not None and nvProps.hasProperty('idsNotToList'):
|
|
|
|
current = list(nvProps.getProperty('idsNotToList'))
|
|
|
|
if self.toolInstanceName not in current:
|
|
|
|
current.append(self.toolInstanceName)
|
|
|
|
nvProps.manage_changeProperties(**{'idsNotToList': current})
|
|
|
|
|
|
|
|
self.tool = getattr(self.ploneSite, self.toolInstanceName)
|
2010-02-08 01:53:30 -06:00
|
|
|
self.appyTool = self.tool.appy()
|
2009-06-29 07:06:01 -05:00
|
|
|
if self.reinstall:
|
2010-08-05 11:23:17 -05:00
|
|
|
self.tool.createOrUpdate(False, None)
|
2009-06-29 07:06:01 -05:00
|
|
|
else:
|
2010-08-05 11:23:17 -05:00
|
|
|
self.tool.createOrUpdate(True, None)
|
2009-10-20 09:57:00 -05:00
|
|
|
|
2009-06-29 07:06:01 -05:00
|
|
|
self.updatePodTemplates()
|
|
|
|
|
|
|
|
# Uncatalog tool
|
|
|
|
self.tool.unindexObject()
|
|
|
|
|
|
|
|
# Register tool as configlet
|
|
|
|
portalControlPanel = self.ploneSite.portal_controlpanel
|
|
|
|
portalControlPanel.unregisterConfiglet(self.toolName)
|
|
|
|
portalControlPanel.registerConfiglet(
|
|
|
|
self.toolName, self.productName,
|
|
|
|
'string:${portal_url}/%s' % self.toolInstanceName, 'python:True',
|
|
|
|
'Manage portal', # Access permission
|
|
|
|
'Products', # Section to which the configlet should be added:
|
|
|
|
# (Plone, Products (default) or Member)
|
|
|
|
1, # Visibility
|
|
|
|
'%sID' % self.toolName, 'site_icon.gif', # Icon in control_panel
|
|
|
|
self.productName, None)
|
|
|
|
|
2011-01-14 02:06:25 -06:00
|
|
|
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))
|
|
|
|
|
2009-06-29 07:06:01 -05:00
|
|
|
def installRolesAndGroups(self):
|
2010-08-05 11:23:17 -05:00
|
|
|
'''Registers roles used by workflows and classes defined in this
|
|
|
|
application if they are not registered yet. Creates the corresponding
|
|
|
|
groups if needed.'''
|
2009-06-29 07:06:01 -05:00
|
|
|
site = self.ploneSite
|
|
|
|
data = list(site.__ac_roles__)
|
2010-09-02 09:16:08 -05:00
|
|
|
for role in self.config.applicationRoles:
|
2009-06-29 07:06:01 -05:00
|
|
|
if not role in data:
|
|
|
|
data.append(role)
|
|
|
|
# Add to portal_role_manager
|
2011-09-06 18:33:09 -05:00
|
|
|
prm = site.acl_users.portal_role_manager
|
2009-06-29 07:06:01 -05:00
|
|
|
try:
|
2011-09-06 18:33:09 -05:00
|
|
|
prm.addRole(role, role, 'Added by "%s"' % self.productName)
|
|
|
|
except KeyError: # Role already exists
|
2009-06-29 07:06:01 -05:00
|
|
|
pass
|
2010-09-02 09:16:08 -05:00
|
|
|
# If it is a global role, create a specific group and grant him
|
|
|
|
# this role
|
|
|
|
if role not in self.config.applicationGlobalRoles: continue
|
2009-06-29 07:06:01 -05:00
|
|
|
group = '%s_group' % role
|
2010-09-02 09:16:08 -05:00
|
|
|
if site.portal_groups.getGroupById(group): continue # Already there
|
|
|
|
site.portal_groups.addGroup(group, title=group)
|
|
|
|
site.portal_groups.setRolesForGroup(group, [role])
|
2009-06-29 07:06:01 -05:00
|
|
|
site.__ac_roles__ = tuple(data)
|
|
|
|
|
|
|
|
def installStyleSheet(self):
|
|
|
|
'''Registers In Plone the stylesheet linked to this application.'''
|
|
|
|
cssName = self.productName + '.css'
|
|
|
|
cssTitle = self.productName + ' CSS styles'
|
|
|
|
cssInfo = {'id': cssName, 'title': cssTitle}
|
2010-09-02 09:16:08 -05:00
|
|
|
portalCss = self.ploneSite.portal_css
|
2009-06-29 07:06:01 -05:00
|
|
|
try:
|
2010-09-02 09:16:08 -05:00
|
|
|
portalCss.unregisterResource(cssInfo['id'])
|
2009-06-29 07:06:01 -05:00
|
|
|
except:
|
|
|
|
pass
|
2010-09-02 09:16:08 -05:00
|
|
|
defaults = {'id': '', 'media': 'all', 'enabled': True}
|
|
|
|
defaults.update(cssInfo)
|
|
|
|
portalCss.registerStylesheet(**defaults)
|
2009-06-29 07:06:01 -05:00
|
|
|
|
2009-08-04 07:39:43 -05:00
|
|
|
def managePortlets(self):
|
|
|
|
'''Shows or hides the application-specific portlet and configures other
|
|
|
|
Plone portlets if relevant.'''
|
2009-06-29 07:06:01 -05:00
|
|
|
portletName= 'here/%s_portlet/macros/portlet' % self.productName.lower()
|
|
|
|
site = self.ploneSite
|
|
|
|
leftPortlets = site.getProperty('left_slots')
|
|
|
|
if not leftPortlets: leftPortlets = []
|
|
|
|
else: leftPortlets = list(leftPortlets)
|
2009-08-04 07:39:43 -05:00
|
|
|
|
|
|
|
if self.showPortlet and (portletName not in leftPortlets):
|
2009-06-29 07:06:01 -05:00
|
|
|
leftPortlets.insert(0, portletName)
|
2009-08-04 07:39:43 -05:00
|
|
|
if not self.showPortlet and (portletName in leftPortlets):
|
|
|
|
leftPortlets.remove(portletName)
|
2009-06-29 07:06:01 -05:00
|
|
|
# Remove some basic Plone portlets that make less sense when building
|
|
|
|
# web applications.
|
|
|
|
portletsToRemove = ["here/portlet_navigation/macros/portlet",
|
|
|
|
"here/portlet_recent/macros/portlet",
|
|
|
|
"here/portlet_related/macros/portlet"]
|
|
|
|
if not self.minimalistPlone: portletsToRemove = []
|
|
|
|
for p in portletsToRemove:
|
|
|
|
if p in leftPortlets:
|
|
|
|
leftPortlets.remove(p)
|
|
|
|
site.manage_changeProperties(left_slots=tuple(leftPortlets))
|
|
|
|
if self.minimalistPlone:
|
|
|
|
site.manage_changeProperties(right_slots=())
|
|
|
|
|
2010-08-05 11:23:17 -05:00
|
|
|
def manageIndexes(self):
|
|
|
|
'''For every indexed field, this method installs and updates the
|
|
|
|
corresponding index if it does not exist yet.'''
|
|
|
|
indexInfo = {}
|
|
|
|
for className, appyTypes in self.attributes.iteritems():
|
|
|
|
for appyType in appyTypes:
|
2010-12-17 07:46:55 -06:00
|
|
|
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()
|
2010-08-05 11:23:17 -05:00
|
|
|
if indexInfo:
|
|
|
|
PloneInstaller.updateIndexes(self.ploneSite, indexInfo, self)
|
|
|
|
|
2010-09-02 09:16:08 -05:00
|
|
|
def manageLanguages(self):
|
|
|
|
'''Manages the languages supported by the application.'''
|
|
|
|
if self.languageSelector:
|
|
|
|
# We must install the PloneLanguageTool if not done yet
|
|
|
|
qi = self.ploneSite.portal_quickinstaller
|
|
|
|
if not qi.isProductInstalled('PloneLanguageTool'):
|
|
|
|
qi.installProduct('PloneLanguageTool')
|
|
|
|
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)
|
|
|
|
|
2009-06-29 07:06:01 -05:00
|
|
|
def finalizeInstallation(self):
|
|
|
|
'''Performs some final installation steps.'''
|
|
|
|
site = self.ploneSite
|
|
|
|
# Do not generate an action (tab) for each root folder
|
|
|
|
if self.minimalistPlone:
|
|
|
|
site.portal_properties.site_properties.manage_changeProperties(
|
|
|
|
disable_folder_sections=True)
|
|
|
|
# Do not allow an anonymous user to register himself as new user
|
|
|
|
site.manage_permission('Add portal member', ('Manager',), acquire=0)
|
|
|
|
# Call custom installer if any
|
|
|
|
if hasattr(self.appyTool, 'install'):
|
|
|
|
self.tool.executeAppyAction('install', reindex=False)
|
2010-01-17 15:01:14 -06:00
|
|
|
# Patch the "logout" action with a custom Appy one that updates the
|
|
|
|
# list of currently logged users.
|
|
|
|
for action in site.portal_membership._actions:
|
|
|
|
if action.id == 'logout':
|
|
|
|
action.setActionExpression(
|
|
|
|
'string:${portal_url}/%s/logout' % self.toolInstanceName)
|
2009-06-29 07:06:01 -05:00
|
|
|
# Replace Plone front-page with an application-specific page if needed
|
|
|
|
if self.appFrontPage:
|
|
|
|
frontPageName = self.productName + 'FrontPage'
|
|
|
|
site.manage_changeProperties(default_page=frontPageName)
|
|
|
|
|
2010-12-17 07:46:55 -06:00
|
|
|
def info(self, msg): return self.appyTool.log(msg)
|
2009-06-29 07:06:01 -05:00
|
|
|
|
|
|
|
def install(self):
|
|
|
|
if self.minimalistPlone: self.customizePlone()
|
|
|
|
self.installRootFolder()
|
|
|
|
self.installTypes()
|
|
|
|
self.installTool()
|
2011-01-14 02:06:25 -06:00
|
|
|
self.installTranslations()
|
2009-06-29 07:06:01 -05:00
|
|
|
self.installRolesAndGroups()
|
|
|
|
self.installStyleSheet()
|
2009-08-04 07:39:43 -05:00
|
|
|
self.managePortlets()
|
2010-08-05 11:23:17 -05:00
|
|
|
self.manageIndexes()
|
2010-09-02 09:16:08 -05:00
|
|
|
self.manageLanguages()
|
2009-06-29 07:06:01 -05:00
|
|
|
self.finalizeInstallation()
|
2011-01-17 07:49:56 -06:00
|
|
|
self.appyTool.log("Installation done.")
|
2009-06-29 07:06:01 -05:00
|
|
|
|
|
|
|
def uninstallTool(self):
|
|
|
|
site = self.ploneSite
|
|
|
|
# Unmention tool in the search form
|
|
|
|
portalProperties = getattr(site, 'portal_properties', None)
|
|
|
|
if portalProperties is not None:
|
|
|
|
siteProperties = getattr(portalProperties, 'site_properties', None)
|
|
|
|
if siteProperties is not None and \
|
|
|
|
siteProperties.hasProperty('types_not_searched'):
|
|
|
|
current = list(siteProperties.getProperty('types_not_searched'))
|
|
|
|
if self.toolName in current:
|
|
|
|
current.remove(self.toolName)
|
|
|
|
siteProperties.manage_changeProperties(
|
|
|
|
**{'types_not_searched' : current})
|
|
|
|
|
|
|
|
# Unmention tool in the navigation
|
|
|
|
if portalProperties is not None:
|
|
|
|
nvProps = getattr(portalProperties, 'navtree_properties', None)
|
|
|
|
if nvProps is not None and nvProps.hasProperty('idsNotToList'):
|
|
|
|
current = list(nvProps.getProperty('idsNotToList'))
|
|
|
|
if self.toolInstanceName in current:
|
|
|
|
current.remove(self.toolInstanceName)
|
|
|
|
nvProps.manage_changeProperties(**{'idsNotToList': current})
|
|
|
|
|
|
|
|
def uninstall(self):
|
|
|
|
self.uninstallTool()
|
2010-12-17 07:46:55 -06:00
|
|
|
return 'Done.'
|
2009-06-29 07:06:01 -05:00
|
|
|
|
2010-01-17 15:01:14 -06:00
|
|
|
# Stuff for tracking user activity ---------------------------------------------
|
|
|
|
loggedUsers = {}
|
|
|
|
originalTraverse = None
|
|
|
|
doNotTrack = ('.jpg','.gif','.png','.js','.class','.css')
|
|
|
|
|
|
|
|
def traverseWrapper(self, path, response=None, validated_hook=None):
|
|
|
|
'''This function is called every time a users gets a URL, this is used for
|
|
|
|
tracking user activity. self is a BaseRequest'''
|
|
|
|
res = originalTraverse(self, path, response, validated_hook)
|
|
|
|
t = time.time()
|
|
|
|
if os.path.splitext(path)[-1].lower() not in doNotTrack:
|
|
|
|
# Do nothing when the user gets non-pages
|
|
|
|
userId = self['AUTHENTICATED_USER'].getId()
|
2011-06-10 10:20:09 -05:00
|
|
|
if userId:
|
|
|
|
loggedUsers[userId] = t
|
|
|
|
# "Touch" the SESSION object. Else, expiration won't occur.
|
|
|
|
session = self.SESSION
|
2010-01-17 15:01:14 -06:00
|
|
|
return res
|
|
|
|
|
2011-06-10 10:20:09 -05:00
|
|
|
def onDelSession(sessionObject, container):
|
|
|
|
'''This function is called when a session expires.'''
|
|
|
|
rq = container.REQUEST
|
|
|
|
if rq.cookies.has_key('__ac') and rq.cookies.has_key('_ZopeId') and \
|
|
|
|
(rq['_ZopeId'] == sessionObject.token):
|
|
|
|
# The request comes from a guy whose session has expired.
|
|
|
|
resp = rq.RESPONSE
|
|
|
|
resp.expireCookie('__ac', path='/')
|
|
|
|
resp.write('<center>For security reasons, your session has ' \
|
|
|
|
'expired.</center>')
|
|
|
|
|
2009-06-29 07:06:01 -05:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
class ZopeInstaller:
|
|
|
|
'''This Zope installer runs every time Zope starts and encounters this
|
|
|
|
generated Zope product.'''
|
2010-09-02 09:16:08 -05:00
|
|
|
def __init__(self, zopeContext, toolClass, config, classes):
|
2009-06-29 07:06:01 -05:00
|
|
|
self.zopeContext = zopeContext
|
|
|
|
self.toolClass = toolClass
|
2010-09-02 09:16:08 -05:00
|
|
|
self.config = cfg = config
|
2010-08-05 11:23:17 -05:00
|
|
|
self.classes = classes
|
2010-09-02 09:16:08 -05:00
|
|
|
# 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
|
2010-08-05 11:23:17 -05:00
|
|
|
|
|
|
|
def completeAppyTypes(self):
|
|
|
|
'''We complete here the initialisation process of every Appy type of
|
|
|
|
every gen-class of the application.'''
|
|
|
|
for klass in self.classes:
|
|
|
|
for baseClass in klass.wrapperClass.__bases__:
|
|
|
|
for name, appyType in baseClass.__dict__.iteritems():
|
|
|
|
if isinstance(appyType, Type):
|
|
|
|
appyType.init(name, baseClass, self.productName)
|
|
|
|
# Do not forget back references
|
|
|
|
if isinstance(appyType, Ref):
|
|
|
|
bAppyType = appyType.back
|
|
|
|
bAppyType.init(bAppyType.attribute, appyType.klass,
|
|
|
|
self.productName)
|
|
|
|
bAppyType.klass = baseClass
|
2009-06-29 07:06:01 -05:00
|
|
|
|
|
|
|
def installApplication(self):
|
|
|
|
'''Performs some application-wide installation steps.'''
|
2010-09-02 09:16:08 -05:00
|
|
|
register = self.config.DirectoryView.registerDirectory
|
|
|
|
register('skins', self.config.__dict__)
|
2009-10-18 07:52:27 -05:00
|
|
|
# Register the appy skin folder among DirectoryView'able folders
|
|
|
|
register('skin', appy.getPath() + '/gen/plone25')
|
2009-06-29 07:06:01 -05:00
|
|
|
|
|
|
|
def installTool(self):
|
|
|
|
'''Installs the tool.'''
|
2010-09-02 09:16:08 -05:00
|
|
|
self.config.ToolInit(self.productName + ' Tools',
|
2009-06-29 07:06:01 -05:00
|
|
|
tools = [self.toolClass], icon='tool.gif').initialize(
|
|
|
|
self.zopeContext)
|
|
|
|
|
|
|
|
def installTypes(self):
|
|
|
|
'''Installs and configures the types defined in the application.'''
|
2010-09-02 09:16:08 -05:00
|
|
|
contentTypes, constructors, ftis = self.config.process_types(
|
|
|
|
self.config.listTypes(self.productName), self.productName)
|
2009-06-29 07:06:01 -05:00
|
|
|
|
2010-09-02 09:16:08 -05:00
|
|
|
self.config.cmfutils.ContentInit(self.productName + ' Content',
|
2009-06-29 07:06:01 -05:00
|
|
|
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])
|
|
|
|
|
2010-01-17 15:01:14 -06:00
|
|
|
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.
|
2010-09-02 09:16:08 -05:00
|
|
|
BaseRequest = self.config.BaseRequest
|
2010-01-17 15:01:14 -06:00
|
|
|
originalTraverse = BaseRequest.traverse
|
|
|
|
BaseRequest.traverse = traverseWrapper
|
|
|
|
|
2009-06-29 07:06:01 -05:00
|
|
|
def finalizeInstallation(self):
|
|
|
|
'''Performs some final installation steps.'''
|
2011-03-03 02:55:20 -06:00
|
|
|
cfg = self.config
|
2009-06-29 07:06:01 -05:00
|
|
|
# Apply customization policy if any
|
2011-03-03 02:55:20 -06:00
|
|
|
cp = cfg.CustomizationPolicy
|
2009-06-29 07:06:01 -05:00
|
|
|
if cp and hasattr(cp, 'register'): cp.register(context)
|
2011-03-03 02:55:20 -06:00
|
|
|
# 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)
|
2011-06-10 10:20:09 -05:00
|
|
|
# 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)
|
2009-06-29 07:06:01 -05:00
|
|
|
|
|
|
|
def install(self):
|
|
|
|
self.logger.info('is being installed...')
|
2010-08-05 11:23:17 -05:00
|
|
|
self.completeAppyTypes()
|
2009-06-29 07:06:01 -05:00
|
|
|
self.installApplication()
|
|
|
|
self.installTool()
|
|
|
|
self.installTypes()
|
2010-01-17 15:01:14 -06:00
|
|
|
self.enableUserTracking()
|
2009-06-29 07:06:01 -05:00
|
|
|
self.finalizeInstallation()
|
|
|
|
# ------------------------------------------------------------------------------
|