From: Hanno S. <svn...@pl...> - 2009-04-25 20:15:11
|
Author: hannosch Date: Sat Apr 25 20:14:56 2009 New Revision: 26677 Added: Products.LinguaPlone/trunk/Products/LinguaPlone/exportimport/reference.py (contents, props changed) Products.LinguaPlone/trunk/Products/LinguaPlone/profiles/default/reference_catalog.xml (contents, props changed) Modified: Products.LinguaPlone/trunk/Products/LinguaPlone/HISTORY.txt Products.LinguaPlone/trunk/Products/LinguaPlone/I18NBaseObject.py Products.LinguaPlone/trunk/Products/LinguaPlone/TODO.txt Products.LinguaPlone/trunk/Products/LinguaPlone/browser/selector.pt Products.LinguaPlone/trunk/Products/LinguaPlone/browser/selector.py Products.LinguaPlone/trunk/Products/LinguaPlone/configure.zcml Products.LinguaPlone/trunk/Products/LinguaPlone/exportimport/configure.zcml Products.LinguaPlone/trunk/Products/LinguaPlone/migrations.py Products.LinguaPlone/trunk/Products/LinguaPlone/profiles/default/catalog.xml Products.LinguaPlone/trunk/Products/LinguaPlone/profiles/default/metadata.xml Products.LinguaPlone/trunk/Products/LinguaPlone/tests/language_policies.txt Log: Reworked the translationOf reference handling. See the HISTORY for details. Modified: Products.LinguaPlone/trunk/Products/LinguaPlone/HISTORY.txt ============================================================================== --- Products.LinguaPlone/trunk/Products/LinguaPlone/HISTORY.txt (original) +++ Products.LinguaPlone/trunk/Products/LinguaPlone/HISTORY.txt Sat Apr 25 20:14:56 2009 @@ -4,6 +4,34 @@ 2.5 - unreleased ---------------- +- Reworked the translationOf reference handling. Instead of relying on the + normal Archetypes reference API, we digg into some of the internals to + optimize the handling for the specific use-case we have: + + * We added Language as additional metadata to the reference catalog. To do + so we needed to add a GenericSetup handler for the catalog to this package + for now. This should be moved to Archetypes itself. An upgrade step for + existing sites is available and needs to be run. The step is advertised in + the add-on control panel of Plone 3.3 and later or available via the + portal_setup tool in the ZMI. + + The new metadata reflects the language of the source of the reference, so + we index the translation languages and not the canonical language. So a + reference inside the at_references folder of a translation, stores the + Language of that translation. It gets it via Acquisition, since neither the + reference nor the at_references OFS.Folder has a Language function. + + * As a second step we use this new metadata to more efficiently query the + reference catalog. In general we avoid getting the real objects where + possible and rely on the catalog internal brains to get all relevant + information. We also bypass getting the actual reference object and + instead look up the source or target of the reference directly by their + uid. + + These changes do not change external API's nor should they cause problems + for other add-ons using the reference engine. + [hannosch] + - Split the canonical status caching of CACHE_TRANSLATIONS into its own config setting via CACHE_CANONICAL. [hannosch] Modified: Products.LinguaPlone/trunk/Products/LinguaPlone/I18NBaseObject.py ============================================================================== --- Products.LinguaPlone/trunk/Products/LinguaPlone/I18NBaseObject.py (original) +++ Products.LinguaPlone/trunk/Products/LinguaPlone/I18NBaseObject.py Sat Apr 25 20:14:56 2009 @@ -37,6 +37,8 @@ from Products.Archetypes.atapi import BaseObject from Products.Archetypes.utils import shasattr from Products.Archetypes.config import LANGUAGE_DEFAULT +from Products.Archetypes.config import REFERENCE_CATALOG +from Products.Archetypes.config import UID_CATALOG from Products.LinguaPlone import events from Products.LinguaPlone import config @@ -110,7 +112,7 @@ """Tells whether this object is used in a i18n context.""" return bool(self.getReferenceImpl(config.RELATIONSHIP) or \ self.getBackReferenceImpl(config.RELATIONSHIP) \ - and self.getLanguage() or False) + and self.Language() or False) security.declareProtected(permissions.AddPortalContent, 'addTranslation') def addTranslation(self, language, *args, **kwargs): @@ -178,8 +180,22 @@ language = language_tool.getPreferredLanguage() else: return self - l = self.getTranslations().get(language, None) - return l and l[0] or l + # Short-cut for self + lang = self.Language() + if lang == language: + return self + # Find and test canonical + canonical = self + if not self.isCanonical(): + canonical = self.getCanonical() + if canonical.Language() == language: + return canonical + brains = canonical.getTranslationBackReferences() + if brains: + found = [b for b in brains if b.Language == language] + if found: + return self._getReferenceObject(uid=found[0].sourceUID) + return None security.declareProtected(permissions.View, 'getTranslationLanguages') def getTranslationLanguages(self): @@ -189,7 +205,13 @@ the translations from the current portal_language selected list, you should use the getTranslatedLanguages script. """ - return self.getTranslations().keys() + canonical = self + if not self.isCanonical(): + canonical = self.getCanonical() + brains = canonical.getTranslationBackReferences() + result = [canonical.Language()] + result.extend([b.Language for b in brains]) + return result security.declareProtected(permissions.View, 'getTranslations') def getTranslations(self): @@ -202,15 +224,15 @@ workflow_tool = getToolByName(self, 'portal_workflow', None) if workflow_tool is None: # No context, most likely FTP or WebDAV - result[self.getLanguage()] = [self, None] + result[self.Language()] = [self, None] return result - lang = self.getLanguage() + lang = self.Language() state = workflow_tool.getInfoFor(self, 'review_state', None) result[lang] = [self, state] - for obj in self.getBRefs(config.RELATIONSHIP): + for obj in self.getTranslationBackReferences(objects=True): if obj is None: continue - lang = obj.getLanguage() + lang = obj.Language() state = workflow_tool.getInfoFor(obj, 'review_state', None) result[lang] = [obj, state] if config.CACHE_TRANSLATIONS: @@ -236,10 +258,7 @@ An object is considered 'canonical' when there's no 'translationOf' references associated. """ - try: - return not bool(self.getReferenceImpl(config.RELATIONSHIP)) - except AttributeError: - return True + return not bool(self.getTranslationReferences()) security.declareProtected(permissions.ModifyPortalContent, 'setCanonical') def setCanonical(self): @@ -256,7 +275,7 @@ security.declareProtected(permissions.View, 'getCanonicalLanguage') def getCanonicalLanguage(self): """Returns the language code for the canonical language.""" - return self.getCanonical().getLanguage() + return self.getCanonical().Language() security.declareProtected(permissions.View, 'getCanonical') def getCanonical(self): @@ -268,8 +287,9 @@ if self.isCanonical(): ret = self else: - refs = self.getRefs(config.RELATIONSHIP) - ret = refs and refs[0] or None + refs = self.getTranslationReferences() + if refs: + ret = self._getReferenceObject(uid=refs[0].targetUID) if config.CACHE_CANONICAL: self._v_canonical = ret @@ -322,6 +342,7 @@ info = parent.manage_cutObjects([self.getId()]) new_parent.manage_pasteObjects(info) self.reindexObject() + self._catalogRefs(self) self.invalidateTranslationCache() security.declarePrivate('defaultLanguage') @@ -357,7 +378,7 @@ if shasattr(self, '_lp_default_page'): delattr(self, '_lp_default_page') - language = self.getLanguage() + language = self.Language() canonical = self.getCanonical() parent = aq_parent(aq_inner(self)) if ITranslatable.providedBy(parent): @@ -402,6 +423,7 @@ self._lp_outdated = True # Because language independent fields may have changed, reindex self.reindexObject() + self._catalogRefs(self) security.declareProtected(permissions.View, 'isOutdated') def isOutdated(self): @@ -427,5 +449,46 @@ return TypeInfoWrapper(ti) return ti + security.declareProtected(permissions.View, 'getTranslationReferences') + def getTranslationReferences(self, objects=False): + """Get all translation references for this object""" + sID = self.UID() + tool = getToolByName(self, REFERENCE_CATALOG) + brains = tool._queryFor(sid=sID, relationship=config.RELATIONSHIP) + if brains: + if objects: + return [ + self._getReferenceObject(brain.targetUID) + for brain in brains + ] + else: + return brains + return [] + + security.declareProtected(permissions.View, 'getTranslationBackReferences') + def getTranslationBackReferences(self, objects=False): + """Get all translation back references for this object""" + tID = self.UID() + tool = getToolByName(self, REFERENCE_CATALOG) + brains = tool._queryFor(tid=tID, relationship=config.RELATIONSHIP) + if brains: + if objects: + return [ + self._getReferenceObject(brain.sourceUID) + for brain in brains + ] + else: + return brains + return [] + + def _getReferenceObject(self, uid): + tool = getToolByName(self, UID_CATALOG, None) + brains = tool(UID=uid) + for brain in brains: + obj = brain.getObject() + if obj is not None: + return obj + return None + InitializeClass(I18NBaseObject) Modified: Products.LinguaPlone/trunk/Products/LinguaPlone/TODO.txt ============================================================================== --- Products.LinguaPlone/trunk/Products/LinguaPlone/TODO.txt (original) +++ Products.LinguaPlone/trunk/Products/LinguaPlone/TODO.txt Sat Apr 25 20:14:56 2009 @@ -38,13 +38,6 @@ shouldn't do. Also for consultants to give to their customers. -Archetypes changes -================== - - - Enable language aware references tool to enable linking to canonical and - retrieval of correct translation (this has to be fixed in Archetypes). - - Additional notes ================ Modified: Products.LinguaPlone/trunk/Products/LinguaPlone/browser/selector.pt ============================================================================== --- Products.LinguaPlone/trunk/Products/LinguaPlone/browser/selector.pt (original) +++ Products.LinguaPlone/trunk/Products/LinguaPlone/browser/selector.pt Sat Apr 25 20:14:56 2009 @@ -1,9 +1,7 @@ -<tal:language - tal:define="available view/available; - languages view/languages; - showFlags view/showFlags;"> +<tal:language tal:condition="view/available"> <ul id="portal-languageselector" - tal:condition="python:available and len(languages)>=2"> + tal:define="showFlags view/showFlags; + languages view/languages;"> <tal:language repeat="lang languages"> <li tal:define="code lang/code; selected lang/selected" Modified: Products.LinguaPlone/trunk/Products/LinguaPlone/browser/selector.py ============================================================================== --- Products.LinguaPlone/trunk/Products/LinguaPlone/browser/selector.py (original) +++ Products.LinguaPlone/trunk/Products/LinguaPlone/browser/selector.py Sat Apr 25 20:14:56 2009 @@ -10,6 +10,13 @@ render = ZopeTwoPageTemplateFile('selector.pt') + def available(self): + if self.tool is not None: + selector = self.tool.showSelector() + languages = len(self.tool.getSupportedLanguages()) > 1 + return selector and languages + return False + def languages(self): results = LanguageSelector.languages(self) translatable = ITranslatable(self.context, None) Modified: Products.LinguaPlone/trunk/Products/LinguaPlone/configure.zcml ============================================================================== --- Products.LinguaPlone/trunk/Products/LinguaPlone/configure.zcml (original) +++ Products.LinguaPlone/trunk/Products/LinguaPlone/configure.zcml Sat Apr 25 20:14:56 2009 @@ -38,4 +38,12 @@ handler="Products.LinguaPlone.migrations.remove_old_import_step" profile="Products.LinguaPlone:LinguaPlone" /> + <genericsetup:upgradeStep + title="LinguaPlone specific reference enhancements" + description="Add Language as metadata to the reference catalog." + source="2.1" + destination="2.2" + handler="Products.LinguaPlone.migrations.add_language_metadata" + profile="Products.LinguaPlone:LinguaPlone" /> + </configure> Modified: Products.LinguaPlone/trunk/Products/LinguaPlone/exportimport/configure.zcml ============================================================================== --- Products.LinguaPlone/trunk/Products/LinguaPlone/exportimport/configure.zcml (original) +++ Products.LinguaPlone/trunk/Products/LinguaPlone/exportimport/configure.zcml Sat Apr 25 20:14:56 2009 @@ -1,10 +1,30 @@ -<configure xmlns="http://namespaces.zope.org/zope"> +<configure + xmlns="http://namespaces.zope.org/zope" + xmlns:genericsetup="http://namespaces.zope.org/genericsetup" + i18n_domain="linguaplone"> -<adapter - factory=".LanguageIndex.LanguageIndexNodeAdapter" - provides="Products.GenericSetup.interfaces.INode" - for="Products.LinguaPlone.interfaces.ILanguageIndex - Products.GenericSetup.interfaces.ISetupEnviron" - /> + <adapter + factory=".LanguageIndex.LanguageIndexNodeAdapter" + provides="Products.GenericSetup.interfaces.INode" + for="Products.LinguaPlone.interfaces.ILanguageIndex + Products.GenericSetup.interfaces.ISetupEnviron" + /> + + <adapter factory=".reference.ReferenceCatalogXMLAdapter"/> + + <genericsetup:importStep + name="reference_catalog" + title="Reference Catalog" + description="Import reference catalog." + handler=".reference.importCatalogTool"> + <depends name="toolset" /> + </genericsetup:importStep> + + <genericsetup:exportStep + name="reference_catalog" + title="Reference Catalog" + description="Export reference catalog." + handler=".reference.exportCatalogTool" + /> </configure> Added: Products.LinguaPlone/trunk/Products/LinguaPlone/exportimport/reference.py ============================================================================== --- (empty file) +++ Products.LinguaPlone/trunk/Products/LinguaPlone/exportimport/reference.py Sat Apr 25 20:14:56 2009 @@ -0,0 +1,57 @@ +from zope.component import adapts + +from Products.Archetypes.interfaces import IReferenceCatalog +from Products.CMFCore.utils import getToolByName +from Products.GenericSetup.interfaces import ISetupEnviron +from Products.GenericSetup.utils import exportObjects +from Products.GenericSetup.utils import importObjects +from Products.GenericSetup.ZCatalog.exportimport import ZCatalogXMLAdapter + + +def importCatalogTool(context): + """Import reference catalog. + """ + site = context.getSite() + tool = getToolByName(site, 'reference_catalog', None) + if tool is not None: + importObjects(tool, '', context) + + +def exportCatalogTool(context): + """Export reference catalog. + """ + site = context.getSite() + tool = getToolByName(site, 'reference_catalog', None) + if tool is None: + logger = context.getLogger('catalog') + logger.info('Nothing to export.') + return + + exportObjects(tool, '', context) + + +class ReferenceCatalogXMLAdapter(ZCatalogXMLAdapter): + """XML im- and exporter for the Reference Catalog. + """ + + adapts(IReferenceCatalog, ISetupEnviron) + + _LOGGER_ID = 'reference_catalog' + + name = 'reference_catalog' + + def _initColumns(self, node): + for child in node.childNodes: + if child.nodeName != 'column': + continue + col = str(child.getAttribute('value')) + if child.hasAttribute('remove'): + # Remove the column if it is there + if col in self.context.schema()[:]: + self.context.delColumn(col) + continue + if col not in self.context.schema()[:]: + self.context.addColumn(col) + # If we added a new column we need to update the + # metadata even if this will take a while + self.context.refreshCatalog() Modified: Products.LinguaPlone/trunk/Products/LinguaPlone/migrations.py ============================================================================== --- Products.LinguaPlone/trunk/Products/LinguaPlone/migrations.py (original) +++ Products.LinguaPlone/trunk/Products/LinguaPlone/migrations.py Sat Apr 25 20:14:56 2009 @@ -1,5 +1,6 @@ import logging +from Products.CMFCore.utils import getToolByName def remove_old_import_step(context): # context is portal_setup which is nice @@ -18,3 +19,14 @@ context._p_changed = True log = logging.getLogger("LinguaPlone") log.info("Old %s import step removed from import registry.", old_step) + + +def add_language_metadata(context): + log = logging.getLogger("LinguaPlone") + tool = getToolByName(context, 'reference_catalog') + if 'Language' not in tool.schema()[:]: + tool.addColumn('Language') + log.info("Added Language to the reference catalog metadata.") + log.info("Updating reference catalog...") + tool.refreshCatalog() + log.info("Reference catalog updated.") Modified: Products.LinguaPlone/trunk/Products/LinguaPlone/profiles/default/catalog.xml ============================================================================== --- Products.LinguaPlone/trunk/Products/LinguaPlone/profiles/default/catalog.xml (original) +++ Products.LinguaPlone/trunk/Products/LinguaPlone/profiles/default/catalog.xml Sat Apr 25 20:14:56 2009 @@ -2,4 +2,3 @@ <object name="portal_catalog"> <column value="Language"/> </object> - Modified: Products.LinguaPlone/trunk/Products/LinguaPlone/profiles/default/metadata.xml ============================================================================== --- Products.LinguaPlone/trunk/Products/LinguaPlone/profiles/default/metadata.xml (original) +++ Products.LinguaPlone/trunk/Products/LinguaPlone/profiles/default/metadata.xml Sat Apr 25 20:14:56 2009 @@ -1,4 +1,4 @@ <?xml version="1.0"?> <metadata> - <version>2.1</version> + <version>2.2</version> </metadata> Added: Products.LinguaPlone/trunk/Products/LinguaPlone/profiles/default/reference_catalog.xml ============================================================================== --- (empty file) +++ Products.LinguaPlone/trunk/Products/LinguaPlone/profiles/default/reference_catalog.xml Sat Apr 25 20:14:56 2009 @@ -0,0 +1,4 @@ +<?xml version="1.0"?> +<object name="reference_catalog"> + <column value="Language" /> +</object> Modified: Products.LinguaPlone/trunk/Products/LinguaPlone/tests/language_policies.txt ============================================================================== --- Products.LinguaPlone/trunk/Products/LinguaPlone/tests/language_policies.txt (original) +++ Products.LinguaPlone/trunk/Products/LinguaPlone/tests/language_policies.txt Sat Apr 25 20:14:56 2009 @@ -68,7 +68,7 @@ >>> english.hasTranslation('fr') False >>> english.getTranslationLanguages() - ['de', 'en'] + ['en', 'de'] Both english and german objects are considered 'translations' >>> english.isTranslation() |