Vocabulary library Plone. Central, Pluggable, TTW, with IMS VDEX Support
ATVocabularyManager: a vocabulary managing portal tool for Plone
Contents
ATVocabularyManager offers central through the Plone management of dynamic vocabularies.
This product is based on Archetypes and made to work with Archetypes as well as with other Products. It is intended use is within Archetypes Fields. Using it as a vocabulary provider for CMFMetadata worked out fine too. Integration with different other products will work as well.
to use a managed vocabulary simply add the term vocabulary = NamedVocabulary("myvocabulary") to the fields of your Archetypes Schema, import NamedVocabulary from this Product and create your vocabulary with id myvocabulary in 'portal_vocabularies' tool (available through Plone Site-Setup). NamedVocabulary accepts two extra arguments:
- empty_first_item, needs a boolean for getting an empty item on top of the list, defaults to False;
- custom_empty_first_item, needs a list of tuple containing a custom first item, defaults to None.
ATVocabularyManager supports:
value vocabularies
hierachical vocabularies (see Limitations)
with XML Import and Export. VDEX is i18n-aware by its nature and does not need LinguaPlone!
ATVocabularyManager is prepared for extension with your special vocabulary type. ArchGenXML will help you here. Each vocabulary term needs to be an CMF aware content type. Reuse normal rich content as a vocabularies.
ArchGenXML 1.4+ code-generator does full integration of ATVocabularyManager: via tagged value defined named vocabularies are registered transparently, VDEX-XML files are imported at install-time, stub vocabularies are created at install time and custom types are registered by just providing appropriate stereotypes.
ATVM is Linguaplone compatible (only tested with SimpleVocabulary, and TreeVocabulary). Add a simple vocabulary with some items, install and configure Linguaplone, translate the vocabulary to the language(s) of your choice, translate every item to the language(s) of your choice. NamedVocabulary() will return the vocabulary as usual, the keys will stay the same disregarding language settings, the values will show in the currently selected language. VDEX vocabularies are not using LinguaPlone, but are i18n-aware (imo much better than everything else).
You can do hierachy-aware searches on treevocabularies (for more information on this see doc/search_treevocabulary.txt) attention: curently certain changes in the term hierachy require a catalog rebuild (see Limitations).
This addon can be installed has any other addons. please follow official documentation
To speed up ATVocabularyManager you might want to associate it with a Cache-Manager.
Usally the authors are offering professional support. The classical well-working community support is found at the mailing-lists and IRC-channels announced at plone.org:http://plone.org
Products.ATVocabularyManager is under a BSD-like Licence.
Products.ATVocabularyManager is copyright by
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of ATVocabularyManager nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Several parts code was created for the ZUCCARO project. ZUCCARO (Zope-based Universally Configurable Classes for Academic Research Online) is a database framework for the Humanities developed by the Bibliotheca Hertziana, Max Planck Institute for Art History For further information: "zuccaro.biblhertz.it":http://zuccaro.biblhertz.it/
common hierarchic functionality of vdex and tree and possibly other types should be coded in a mixin. code and test once, use everywhere
current limitation:
every change on the structure of a vocabulary (eg: moving a term up the hierarchy) has to trigger not only the invalidation of the cache but also the reindexing of all terms down the hierarchy (uid_catalog search on getTermUidPath, catalog.manage_reindexIndex(ids=('getTermUidPath',)))
currently this is a limitation of treevocabulary. vdex dont need to get cataloged
the worse problem is to reindex the objects that define custom indexes on their path-methods. we don't know which catalog they use (could be portal_catalog, but also any other)
brainstorming:
- store catalog and index in vocab or the atvmtool
- provide a hook somewhere that can be implemented
- maybe use references instead of text-linefields to be able to find out which content uses a certain term.
Simple vocabularies are flat vocabularies.
SimpleVocabularies can be created in the vocabulary library:
>>> atvm = self.portal.portal_vocabularies >>> self.setRoles(['Manager']) >>> _ = atvm.invokeFactory('SimpleVocabulary', 'testvocab', title="test vocabulary")
You can fetch a vocabulary calling getVocabularyByName on the vocabulary library
>>> simple = atvm.getVocabularyByName('testvocab') >>> simple <SimpleVocabulary at /plone/portal_vocabularies/testvocab> >>> simple.Title() 'test vocabulary'
Simple vocabularies can only contain SimpleVocabularyTerms.
>>> simple.allowedContentTypes() [<FactoryTypeInformation at /plone/portal_types/SimpleVocabularyTerm>]
You can add terms using invokeFactory or the method addTerm:
>>> _ = simple.invokeFactory('SimpleVocabularyTerm', 'term1') >>> simple.addTerm('term2', 'first time') True
addTerm can ignore duplicate keys, and returns whether adding has been successfull.
>>> simple.addTerm('term2', 'second time', silentignore=True) False >>> simple.getDisplayList(self) <DisplayList [('term1', ''), ('term2', 'first time')] at ...>
For creating simple vocabularies in python code you can use a convenience method atvm provides for you:
>>> from Products.ATVocabularyManager.utils.vocabs import createSimpleVocabs
This needs to be fed with a dictionary in the following format:
>>> testvocabs = {} >>> testvocabs['sorting'] = ( ... ('c', u'Alpha'), ... ('a', u'Zeppelin'), ... ('y', u'Charly')) >>> createSimpleVocabs(atvm, testvocabs) >>> sorting = atvm.getVocabularyByName('sorting') >>> sorting.contentIds() ['c', 'a', 'y']
You can define the sort order of the vocabularyterms within a simplevocabulary by choosing one of the values in Sort method.
The default sort order is alphabetically by Vales (Title):
>>> sorting.getField('sortMethod').vocabulary.keys() ['getObjPositionInParent', 'lexicographic_values', 'lexicographic_keys'] >>> sorting.getSortMethod() 'lexicographic_values'
Sorting by Values
>>> sorting.getDisplayList(self) <DisplayList [('c', 'Alpha'), ('y', 'Charly'), ('a', 'Zeppelin')] at ...>
Sorting by keys
>>> from Products.ATVocabularyManager.config import SORT_METHOD_LEXICO_KEYS >>> sorting.setSortMethod(SORT_METHOD_LEXICO_KEYS) >>> sorting.getDisplayList(self) <DisplayList [('a', 'Zeppelin'), ('c', 'Alpha'), ('y', 'Charly')] at ...>
Sorting by folder position
>>> sorting.listFolderContents() [<SimpleVocabularyTerm .../sorting/c>, <SimpleVocabularyTerm .../sorting/a>, <SimpleVocabularyTerm .../sorting/y>] >>> from Products.ATVocabularyManager.config import SORT_METHOD_FOLDER_ORDER >>> sorting.setSortMethod(SORT_METHOD_FOLDER_ORDER) >>> sorting.getDisplayList(self) <DisplayList [('c', 'Alpha'), ('a', 'Zeppelin'), ('y', 'Charly')] ...>
Simple vocabularies have full Linguaplone Support, but also work w/o having Linguaplone installed:
>>> self.portal.portal_quickinstaller.isProductInstalled('LinguaPlone') False
If Linguaplone is not installed, our vocabulary works without causing troubles
>>> self._createTestVocabulary() >>> vocab = self.atvm.teststates >>> vocab.getVocabularyDict(vocab) {'ger': 'Germany', 'fin': 'Finland', 'aut': 'Austria', 'nor': 'Norway'}
If Linguaplone is installed, the simplevocabulary is fully supporting translated vocabularyterms:
XXX go on porting the tests/testSimpleVocabulary testTranslations here
Also sorting on translated vocabularies works as excepted: XXX write tests for the following
This tests demonstrates how ATVM enables you to make your contenttype searchable using a treevocabulary field.
imagine the following vocabulary:
engine-type
- electrical
- mechanical
- otto engine
- diesel engine
a search for engine==mechanical should return all entries for otto and diesel engines
You can skip this tutorial if you are no developer and simply want to learn how to do hierarchy-aware searches on your content type. This is demonstrated in the Product ATVocabularyManagerExample
let's create our vocabulary using the utility methods provided by ATVM:
>>> from Products.CMFCore.utils import getToolByName >>> atvm = getToolByName(self.portal, 'portal_vocabularies') >>> from Products.ATVocabularyManager.utils.vocabs import createHierarchicalVocabs >>> dictionary = { ... ('electrical', 'electrical engines'): {}, ... ('mechanical', 'mechanial engines'): { ... ('otto', 'ottomotor'): {}, ... ('diesel', 'diesel engine'): {} ... } ... } >>> hierarchicalVocabs = {} >>> hierarchicalVocabs[('enginetypes', 'Diffetent types of engines')] = dictionary >>> createHierarchicalVocabs(atvm, hierarchicalVocabs)
now we've got our vocabulary available:
>>> engines = atvm.getVocabularyByName('enginetypes') >>> engines <TreeVocabulary at /plone/portal_vocabularies/enginetypes>
This small section tells you how ATMV achieves that an ottomotor is recognized as a mechanical engine too.
The key of a vocabulary term is the UID of the canonical object (in case the vocabulary is translated into different languages) and can be obtained using the method getTermKey
>>> ottomotor = engines.mechanical.otto >>> ottomotor.getTermKey() == ottomotor.UID() True
A TreeVocabularyTerm implements the method getTermKeyPath that returns a list of vocabularyterm-keys of the term itself and all the terms in the hierarchy above it.
>>> ottoPath = ottomotor.getTermKeyPath() >>> engines.mechanical.getTermKey() in ottoPath True
Each VocabularyTerm is indexed using a KeywordIndex on getTermKeyPath in uid_catalog.
The keypath is is available as a metadata-column in uid_catalog, so it can be fetched for via a catalog query without invoking real objects.
our ottomotor term can be found searching for the ottomotor uid:
>>> uid_catalog = getToolByName(self.portal, 'uid_catalog') >>> uid_catalog(getTermKeyPath=ottomotor.UID())[0].getObject() <TreeVocabularyTerm at .../enginetypes/mechanical/otto>
a search for a machanical engine returns 3 vocabularies: the ottomotor, the dieselengine and the term for mechanicalengine too:
>>> result = uid_catalog(getTermKeyPath=engines.mechanical.UID()) >>> [brain.id for brain in result] ['mechanical', 'diesel', 'otto']
a change in the hierarchy of our vocabulary will reindex all vocabularies below the changed term.
XXX make new term below engines: 'fossil'' and move mechanical there. after that the otto and diesel should be available under fossil too
To be able to search for content types assiciated to terms not only directly but also down the hierarchy we need to index our object under the TermKeyPath(s) of the associated vocabularyterm(s).
ATVM provides a fast implemented utility method for this purpose that is working only on the catalog.
You can utilize getKeyPathForTerms in NamedVocabulary:
>>> from Products.ATVocabularyManager import NamedVocabulary >>> nv = NamedVocabulary('engines') >>> keyPath = nv.getKeyPathForTerms(self.portal, ottomotor.getTermKey()) >>> engines.mechanical.getTermKey() in keyPath True >>> ottomotor.getTermKey() in keyPath True
All you need to do is to implement a method that uses the getKeyPathForTerms method and define it as index method:
# .. somewhere in the schemadefinition ReferenceField( name='myfield', widget=... vocabulary=NamedVocabulary("""myvocab"""), index="KeywordIndex", index_method="someIndexMethod" ), # .. somewhere in your content type definition define someIndexMethod(self): """used to index myfield """ # all we have to know is the field name of the field this # index method belongs to fieldName = 'myfield' # the following code need not be touched termUID = self.getField(fieldName).get(self) return self.getField(fieldName).vocabulary.getKeyPathForTerms(self, termUID)
See ATVocabularyManagerExample for a more detailed explanation how to use ATVocabularyManager in your content type.
_ATVocabularyManagerExample: https://svn.plone.org/svn/archetypes/ATVocabularyManagerExample/