appy.gen: various bugfixes (creation of ZCTextIndexes and of the associated lexicon...).

This commit is contained in:
Gaetan Delannay 2011-12-01 20:53:13 +01:00
parent a321257e55
commit 6733f4c7dc
6 changed files with 42 additions and 129 deletions

View file

@ -315,9 +315,6 @@ class Search:
# Indeed, for field 'title', Appy has a specific index # Indeed, for field 'title', Appy has a specific index
# 'SortableTitle', because index 'Title' is a ZCTextIndex # 'SortableTitle', because index 'Title' is a ZCTextIndex
# (for searchability) and can't be used for sorting. # (for searchability) and can't be used for sorting.
elif fieldName == 'description':
if usage == 'search': return 'Description'
else: return None
else: else:
return 'get%s%s'% (fieldName[0].upper(),fieldName[1:]) return 'get%s%s'% (fieldName[0].upper(),fieldName[1:])
@staticmethod @staticmethod

View file

@ -142,7 +142,6 @@ class Generator(AbstractGenerator):
self.generateTool() self.generateTool()
self.generateInit() self.generateInit()
self.generateTests() self.generateTests()
self.copyFile('Install.py', self.repls, destFolder='Extensions')
self.generateConfigureZcml() self.generateConfigureZcml()
self.copyFile('import_steps.xml', self.repls, self.copyFile('import_steps.xml', self.repls,
destFolder='profiles/default') destFolder='profiles/default')

View file

@ -1,19 +1,14 @@
'''This package contains stuff used at run-time for installing a generated '''This package contains stuff used at run-time for installing a generated
Plone product.''' Zope product.'''
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import os, os.path, time import os, os.path, time
from StringIO import StringIO
from sets import Set
import appy import appy
import appy.version import appy.version
from appy.gen import Type, Ref, String, File from appy.gen import Type, Ref, String, File
from appy.gen.po import PoParser from appy.gen.po import PoParser
from appy.gen.utils import produceNiceMessage, updateRolesForPermission, \ from appy.gen.utils import updateRolesForPermission, createObject
createObject
from appy.shared.data import languages from appy.shared.data import languages
from migrator import Migrator
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
homePage = ''' homePage = '''
@ -38,99 +33,6 @@ errorPage = '''
</html> </html>
</tal:main> </tal:main>
''' '''
# ------------------------------------------------------------------------------
class PloneInstaller:
'''This Plone installer runs every time the generated Plone product is
installed or uninstalled (in the Plone configuration interface).'''
def __init__(self, reinstall, ploneSite, config):
# p_cfg is the configuration module of the Plone product.
self.reinstall = reinstall # Is it a fresh install or a re-install?
self.ploneSite = ploneSite
self.config = cfg = config
# Unwrap some useful variables from config
self.productName = cfg.PROJECTNAME
self.appClasses = cfg.appClasses
self.appClassNames = cfg.appClassNames
self.allClassNames = cfg.allClassNames
self.applicationRoles = cfg.applicationRoles # Roles defined in the app
self.defaultAddRoles = cfg.defaultAddRoles
self.appFrontPage = cfg.appFrontPage
self.languages = cfg.languages
self.languageSelector = cfg.languageSelector
self.attributes = cfg.attributes
# A buffer for logging purposes
self.toLog = StringIO()
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()
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
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.'''
# 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
# Allow to create Appy large folders in the plone site
getattr(site.portal_types,
'Plone Site').allowed_content_types += (self.appyFolderType,)
site.invokeFactory(self.appyFolderType, self.productName,
title=self.productName)
getattr(site.portal_types, self.appyFolderType).global_allow = 0
else:
appFolder = getattr(site, self.productName)
# Beyond content-type-specific "add" permissions, creators must also
# have the main permission "Add portal content".
permission = 'Add portal content'
updateRolesForPermission(permission, tuple(allCreators), appFolder)
def installRolesAndGroups(self):
'''Registers roles used by workflows and classes defined in this
application if they are not registered yet. Creates the corresponding
groups if needed.'''
site = self.ploneSite
data = list(site.__ac_roles__)
for role in self.config.applicationRoles:
if not role in data:
data.append(role)
# Add to portal_role_manager
prm = site.acl_users.portal_role_manager
try:
prm.addRole(role, role, 'Added by "%s"' % self.productName)
except KeyError: # Role already exists
pass
# If it is a global role, create a specific group and grant him
# this role
if role not in self.config.applicationGlobalRoles: continue
group = '%s_group' % role
if site.portal_groups.getGroupById(group): continue # Already there
site.portal_groups.addGroup(group, title=group)
site.portal_groups.setRolesForGroup(group, [role])
site.__ac_roles__ = tuple(data)
def finalizeInstallation(self):
'''Performs some final installation steps.'''
site = self.ploneSite
# Do not allow an anonymous user to register himself as new user
site.manage_permission('Add portal member', ('Manager',), acquire=0)
# Replace Plone front-page with an application-specific page if needed
if self.appFrontPage:
frontPageName = self.productName + 'FrontPage'
site.manage_changeProperties(default_page=frontPageName)
# Store the used Appy version (used for detecting new versions)
self.appyTool.appyVersion = appy.version.short
self.info('Appy version is %s.' % self.appyTool.appyVersion)
# Call custom installer if any
# Stuff for tracking user activity --------------------------------------------- # Stuff for tracking user activity ---------------------------------------------
loggedUsers = {} loggedUsers = {}
@ -162,6 +64,11 @@ def onDelSession(sessionObject, container):
resp.write('<center>For security reasons, your session has ' \ resp.write('<center>For security reasons, your session has ' \
'expired.</center>') 'expired.</center>')
class ZCTextIndexInfo:
'''Silly class used for storing information about a ZCTextIndex.'''
lexicon_id = "lexicon"
index_type = 'Okapi BM25 Rank'
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
class ZopeInstaller: class ZopeInstaller:
'''This Zope installer runs every time Zope starts and encounters this '''This Zope installer runs every time Zope starts and encounters this
@ -238,12 +145,20 @@ class ZopeInstaller:
(indexName, oldType, indexType)) (indexName, oldType, indexType))
if indexName not in catalog.indexes(): if indexName not in catalog.indexes():
# We need to create this index # We need to create this index
type = indexType if indexType != 'ZCTextIndex':
if type == 'ZCTextIndex': type = 'TextIndex' catalog.addIndex(indexName, indexType)
catalog.addIndex(indexName, type) else:
catalog.addIndex(indexName, indexType,extra=ZCTextIndexInfo)
catalog.reindexIndex(indexName, self.app.REQUEST)
logger.info('Created index "%s" of type "%s"...' % \ logger.info('Created index "%s" of type "%s"...' % \
(indexName, type)) (indexName, indexType))
# Indexing database content based on this index.
lexiconInfos = [
appy.Object(group='Case Normalizer', name='Case Normalizer'),
appy.Object(group='Stop Words', name=" Don't remove stop words"),
appy.Object(group='Word Splitter', name='Whitespace splitter')
]
def installCatalog(self): def installCatalog(self):
'''Create the catalog at the root of Zope if id does not exist.''' '''Create the catalog at the root of Zope if id does not exist.'''
if 'catalog' not in self.app.objectIds(): if 'catalog' not in self.app.objectIds():
@ -251,10 +166,17 @@ class ZopeInstaller:
from Products.ZCatalog.ZCatalog import manage_addZCatalog from Products.ZCatalog.ZCatalog import manage_addZCatalog
manage_addZCatalog(self.app, 'catalog', '') manage_addZCatalog(self.app, 'catalog', '')
self.logger.info('Appy catalog created.') self.logger.info('Appy catalog created.')
# Create a lexicon for ZCTextIndexes
if 'lexicon' not in self.app.catalog.objectIds():
from Products.ZCTextIndex.ZCTextIndex import manage_addLexicon
manage_addLexicon(self.app.catalog, 'lexicon',
elements=self.lexiconInfos)
# Create or update Appy-wide indexes and field-related indexes # Create or update Appy-wide indexes and field-related indexes
indexInfo = {'State': 'FieldIndex', 'UID': 'FieldIndex', indexInfo = {'State': 'FieldIndex', 'UID': 'FieldIndex',
'Title': 'TextIndex', 'SortableTitle': 'FieldIndex', 'Title': 'ZCTextIndex', 'SortableTitle': 'FieldIndex',
'SearchableText': 'FieldIndex', 'Creator': 'FieldIndex', 'SearchableText': 'ZCTextIndex', 'Creator': 'FieldIndex',
'Created': 'DateIndex', 'ClassName': 'FieldIndex', 'Created': 'DateIndex', 'ClassName': 'FieldIndex',
'Allowed': 'KeywordIndex'} 'Allowed': 'KeywordIndex'}
tool = self.app.config tool = self.app.config
@ -314,6 +236,7 @@ class ZopeInstaller:
tool.createOrUpdate(True, None) tool.createOrUpdate(True, None)
tool.refreshSecurity() tool.refreshSecurity()
appyTool = tool.appy() appyTool = tool.appy()
appyTool.log('Appy version is "%s".' % appy.version.short)
# Create the admin user if no user exists. # Create the admin user if no user exists.
if not self.app.acl_users.getUsers(): if not self.app.acl_users.getUsers():
@ -457,8 +380,17 @@ class ZopeInstaller:
if role not in roles: roles.append(role) if role not in roles: roles.append(role)
self.app.__ac_roles__ = tuple(roles) self.app.__ac_roles__ = tuple(roles)
def installDependencies(self):
'''Zope products are installed in alphabetical order. But here, we need
ZCTextIndex to be installed before our Appy application. So, we cheat
and force Zope to install it now.'''
from OFS.Application import install_product
import Products
install_product(self.app, Products.__path__[1], 'ZCTextIndex', [], {})
def install(self): def install(self):
self.logger.info('is being installed...') self.logger.info('is being installed...')
self.installDependencies()
self.installRoles() self.installRoles()
self.installAppyTypes() self.installAppyTypes()
self.installZopeClasses() self.installZopeClasses()

View file

@ -371,6 +371,7 @@ class ToolMixin(BaseMixin):
def truncateValue(self, value, appyType): def truncateValue(self, value, appyType):
'''Truncates the p_value according to p_appyType width.''' '''Truncates the p_value according to p_appyType width.'''
maxWidth = appyType['width'] maxWidth = appyType['width']
if isinstance(value, str): value = value.decode('utf-8')
if len(value) > maxWidth: if len(value) > maxWidth:
return value[:maxWidth] + '...' return value[:maxWidth] + '...'
return value return value
@ -378,10 +379,9 @@ class ToolMixin(BaseMixin):
def truncateText(self, text, width=15): def truncateText(self, text, width=15):
'''Truncates p_text to max p_width chars. If the text is longer than '''Truncates p_text to max p_width chars. If the text is longer than
p_width, the truncated part is put in a "acronym" html tag.''' p_width, the truncated part is put in a "acronym" html tag.'''
if isinstance(text, str): text = text.decode('utf-8')
if len(text) <= width: return text if len(text) <= width: return text
else: return '<acronym title="%s">%s</acronym>' % (text, text[:width] + '...')
return '<acronym title="%s">%s</acronym>' % \
(text, text[:width] + '...')
def getPublishedObject(self): def getPublishedObject(self):
'''Gets the currently published object, if its meta_class is among '''Gets the currently published object, if its meta_class is among

View file

@ -1,15 +0,0 @@
<!codeHeader!>
import appy.gen
from appy.gen.plone25.installer import PloneInstaller
import Products.<!applicationName!>.config as config
# ------------------------------------------------------------------------------
def install(self, reinstall=False):
'''Installation procedure.'''
return PloneInstaller(reinstall, self, config).install()
# ------------------------------------------------------------------------------
def uninstall(self, reinstall=False):
'''Uninstallation procedure.'''
return PloneInstaller(reinstall, self, config).uninstall()
# ------------------------------------------------------------------------------

View file

@ -29,7 +29,7 @@ class TranslationWrapper(AbstractWrapper):
sourceMsg = sourceMsg.replace('<','&lt;').replace('>','&gt;') sourceMsg = sourceMsg.replace('<','&lt;').replace('>','&gt;')
sourceMsg = sourceMsg.replace('\n', '<br/>') sourceMsg = sourceMsg.replace('\n', '<br/>')
return '<div class="translationLabel"><acronym title="%s">' \ return '<div class="translationLabel"><acronym title="%s">' \
'<img src="help.png"/></acronym>%s</div>' % \ '<img src="ui/help.png"/></acronym>%s</div>' % \
(fieldName, sourceMsg) (fieldName, sourceMsg)
def show(self, field): def show(self, field):