use formlib for creating a new loops site; automatically create subobjects
git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@1246 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
		
							parent
							
								
									f6f863eeb6
								
							
						
					
					
						commit
						db5a60a6a3
					
				
					 10 changed files with 223 additions and 41 deletions
				
			
		|  | @ -28,11 +28,14 @@ from zope.app.form.browser.interfaces import ITerms | ||||||
| from zope.app.intid.interfaces import IIntIds | from zope.app.intid.interfaces import IIntIds | ||||||
| from zope.cachedescriptors.property import Lazy | from zope.cachedescriptors.property import Lazy | ||||||
| from zope.dottedname.resolve import resolve | from zope.dottedname.resolve import resolve | ||||||
|  | from zope.formlib.form import FormFields | ||||||
| from zope.formlib.form import EditForm as BaseEditForm | from zope.formlib.form import EditForm as BaseEditForm | ||||||
|  | from zope.formlib.form import AddForm as BaseAddForm | ||||||
| from zope.formlib.namedtemplate import NamedTemplate | from zope.formlib.namedtemplate import NamedTemplate | ||||||
| from zope.interface import implements | from zope.interface import Interface, implements | ||||||
| from zope.app.publisher.browser import applySkin | from zope.app.publisher.browser import applySkin | ||||||
| from zope.publisher.interfaces.browser import ISkin | from zope.publisher.interfaces.browser import ISkin | ||||||
|  | from zope import schema | ||||||
| from zope.schema.vocabulary import SimpleTerm | from zope.schema.vocabulary import SimpleTerm | ||||||
| from zope.security import canAccess, canWrite | from zope.security import canAccess, canWrite | ||||||
| from zope.security.proxy import removeSecurityProxy | from zope.security.proxy import removeSecurityProxy | ||||||
|  | @ -40,6 +43,29 @@ from zope.security.proxy import removeSecurityProxy | ||||||
| from cybertools.typology.interfaces import IType | from cybertools.typology.interfaces import IType | ||||||
| from loops.interfaces import IView | from loops.interfaces import IView | ||||||
| from loops import util | from loops import util | ||||||
|  | from loops.util import _ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class NameField(schema.ASCIILine): | ||||||
|  | 
 | ||||||
|  |     def _validate(self, value): | ||||||
|  |         super(NameField, self)._validate(value) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class IAddForm(Interface): | ||||||
|  | 
 | ||||||
|  |     name = NameField( | ||||||
|  |             title=_(u'Object name'), | ||||||
|  |             description=_(u'Name of the object - will be used for addressing the ' | ||||||
|  |                         'object via a URL; should therefore be unique within ' | ||||||
|  |                         'the container and not contain special characters') | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class AddForm(BaseAddForm): | ||||||
|  | 
 | ||||||
|  |     form_fields = FormFields(IAddForm) | ||||||
|  |     template = NamedTemplate('loops.pageform') | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class EditForm(BaseEditForm): | class EditForm(BaseEditForm): | ||||||
|  |  | ||||||
|  | @ -85,13 +85,11 @@ | ||||||
| 
 | 
 | ||||||
|   <!-- loops top-level container --> |   <!-- loops top-level container --> | ||||||
| 
 | 
 | ||||||
|   <addform |   <page | ||||||
|       label="Add loops Container" |  | ||||||
|       name="AddLoopsContainer.html" |       name="AddLoopsContainer.html" | ||||||
|       schema="loops.interfaces.ILoops" |       for="zope.app.container.interfaces.IAdding" | ||||||
|       content_factory="loops.Loops" |       class="loops.browser.manager.LoopsAddForm" | ||||||
|       template="add.pt" |       permission="zope.ManageApplication" | ||||||
|       permission="zope.ManageContent" |  | ||||||
|       /> |       /> | ||||||
| 
 | 
 | ||||||
|   <addMenuItem |   <addMenuItem | ||||||
|  | @ -108,14 +106,13 @@ | ||||||
|       add="zope.ManageContent" |       add="zope.ManageContent" | ||||||
|       /> |       /> | ||||||
| 
 | 
 | ||||||
|   <editform |   <page | ||||||
|       label="Edit Loops Container" |  | ||||||
|       name="edit.html" |       name="edit.html" | ||||||
|       schema="loops.interfaces.ILoops" |  | ||||||
|       fields="skinName" |  | ||||||
|       for="loops.interfaces.ILoops" |       for="loops.interfaces.ILoops" | ||||||
|       permission="zope.ManageContent" |       class="loops.browser.manager.LoopsEditForm" | ||||||
|       menu="zmi_views" title="Edit" />       |       permission="zope.ManageApplication" | ||||||
|  |       menu="zmi_views" title="Edit" | ||||||
|  |       /> | ||||||
| 
 | 
 | ||||||
|   <!-- concept manager --> |   <!-- concept manager --> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										60
									
								
								browser/manager.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								browser/manager.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,60 @@ | ||||||
|  | # | ||||||
|  | #  Copyright (c) 2006 Helmut Merz helmutm@cy55.de | ||||||
|  | # | ||||||
|  | #  This program is free software; you can redistribute it and/or modify | ||||||
|  | #  it under the terms of the GNU General Public License as published by | ||||||
|  | #  the Free Software Foundation; either version 2 of the License, or | ||||||
|  | #  (at your option) any later version. | ||||||
|  | # | ||||||
|  | #  This program is distributed in the hope that it will be useful, | ||||||
|  | #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | #  GNU General Public License for more details. | ||||||
|  | # | ||||||
|  | #  You should have received a copy of the GNU General Public License | ||||||
|  | #  along with this program; if not, write to the Free Software | ||||||
|  | #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||||
|  | # | ||||||
|  | 
 | ||||||
|  | """ | ||||||
|  | Definition of view classes for the top-level loops container. | ||||||
|  | 
 | ||||||
|  | $Id$ | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | from zope.app import zapi | ||||||
|  | from zope.app.event.objectevent import ObjectCreatedEvent, ObjectModifiedEvent | ||||||
|  | from zope.event import notify | ||||||
|  | from zope.formlib.form import FormFields | ||||||
|  | from loops import Loops | ||||||
|  | from loops.interfaces import ILoops | ||||||
|  | from loops.browser.common import AddForm, EditForm, BaseView | ||||||
|  | from loops.setup import ISetupManager | ||||||
|  | from loops.util import _ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class LoopsAddForm(AddForm): | ||||||
|  | 
 | ||||||
|  |     form_fields = AddForm.form_fields + FormFields(ILoops) | ||||||
|  |     label = _(u'Create Loops Site') | ||||||
|  | 
 | ||||||
|  |     def createAndAdd(self, data): | ||||||
|  |         container = self.context.context | ||||||
|  |         name = data.pop('name', 'loopsdemo') | ||||||
|  |         loops = Loops() | ||||||
|  |         self.context.contentName = name | ||||||
|  |         for attr in data: | ||||||
|  |             setattr(loops, attr, data[attr]) | ||||||
|  |         notify(ObjectCreatedEvent(loops)) | ||||||
|  |         self.context.add(loops) | ||||||
|  |         setup = ISetupManager(loops, None) | ||||||
|  |         if setup is not None: | ||||||
|  |             setup.setup() | ||||||
|  |         return loops | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class LoopsEditForm(EditForm): | ||||||
|  | 
 | ||||||
|  |     form_fields = FormFields(ILoops) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @ -41,12 +41,16 @@ function toggleFormFieldHelp(ob,state) { | ||||||
| 
 | 
 | ||||||
| <div id="viewspace" metal:define-slot="viewspace"> | <div id="viewspace" metal:define-slot="viewspace"> | ||||||
| 
 | 
 | ||||||
|   <h1  i18n:translate="heading_editform" |   <h1 tal:condition="not:view/label" | ||||||
|        metal:define-slot="heading">Edit |       i18n:translate="heading_editform" | ||||||
|  |       metal:define-slot="heading">Edit | ||||||
|     <span i18n:name="title" |     <span i18n:name="title" | ||||||
|           tal:content="context/title">Concept</span> |           tal:content="context/title|context/zope:name">Concept</span> | ||||||
|   </h1> |   </h1> | ||||||
| 
 | 
 | ||||||
|  |   <h1 tal:condition="view/label" | ||||||
|  |       tal:content="view/label">Headline</h1> | ||||||
|  | 
 | ||||||
|   <metal:block define-macro="header"> |   <metal:block define-macro="header"> | ||||||
| 
 | 
 | ||||||
|     <div class="form-status" |     <div class="form-status" | ||||||
|  |  | ||||||
|  | @ -284,6 +284,7 @@ | ||||||
|   </class> |   </class> | ||||||
| 
 | 
 | ||||||
|    |    | ||||||
|  |   <adapter factory="loops.setup.SetupManager" /> | ||||||
|   <adapter factory="loops.external.NodesLoader" /> |   <adapter factory="loops.external.NodesLoader" /> | ||||||
|   <adapter factory="loops.external.NodesExporter" /> |   <adapter factory="loops.external.NodesExporter" /> | ||||||
|   <adapter factory="loops.external.NodesImporter" /> |   <adapter factory="loops.external.NodesImporter" /> | ||||||
|  |  | ||||||
							
								
								
									
										46
									
								
								helpers.txt
									
										
									
									
									
								
							
							
						
						
									
										46
									
								
								helpers.txt
									
										
									
									
									
								
							|  | @ -19,26 +19,30 @@ Let's first do some basic imports | ||||||
|   >>> from zope.interface import Interface |   >>> from zope.interface import Interface | ||||||
|   >>> from zope.publisher.browser import TestRequest |   >>> from zope.publisher.browser import TestRequest | ||||||
| 
 | 
 | ||||||
| and setup a simple loops site with its manager objects, | and provide a relation registry: | ||||||
|  | 
 | ||||||
|  |   >>> from cybertools.relation.interfaces import IRelationRegistry | ||||||
|  |   >>> from cybertools.relation.registry import DummyRelationRegistry | ||||||
|  |   >>> from zope.app.testing import ztapi | ||||||
|  |   >>> ztapi.provideUtility(IRelationRegistry, DummyRelationRegistry()) | ||||||
|  | 
 | ||||||
|  | Now we can setup a simple loops site with its manager objects, using a | ||||||
|  | loops setup manager: | ||||||
|    |    | ||||||
|   >>> from loops import Loops |   >>> from loops import Loops | ||||||
|   >>> site['loops'] = Loops() |   >>> site['loops'] = Loops() | ||||||
|   >>> loopsRoot = site['loops'] |   >>> loopsRoot = site['loops'] | ||||||
| 
 | 
 | ||||||
|   >>> from loops.concept import ConceptManager, Concept |   >>> from loops.setup import SetupManager | ||||||
|   >>> loopsRoot['concepts'] = ConceptManager() |   >>> setup = SetupManager(loopsRoot) | ||||||
|  |   >>> setup.setup() | ||||||
|   >>> concepts = loopsRoot['concepts'] |   >>> concepts = loopsRoot['concepts'] | ||||||
| 
 |  | ||||||
|   >>> from loops.resource import ResourceManager, Document, MediaAsset |  | ||||||
|   >>> loopsRoot['resources'] = ResourceManager() |  | ||||||
|   >>> resources = loopsRoot['resources'] |   >>> resources = loopsRoot['resources'] | ||||||
| 
 |  | ||||||
|   >>> from loops.view import ViewManager, Node |  | ||||||
|   >>> loopsRoot['views'] = ViewManager() |  | ||||||
|   >>> views = loopsRoot['views'] |   >>> views = loopsRoot['views'] | ||||||
| 
 | 
 | ||||||
| some concepts, | We also add some example concepts, | ||||||
|    |    | ||||||
|  |   >>> from loops.concept import Concept | ||||||
|   >>> cc1 = Concept(u'Zope') |   >>> cc1 = Concept(u'Zope') | ||||||
|   >>> concepts['cc1'] = cc1 |   >>> concepts['cc1'] = cc1 | ||||||
|   >>> cc1.title |   >>> cc1.title | ||||||
|  | @ -51,6 +55,7 @@ some concepts, | ||||||
| 
 | 
 | ||||||
| resources, | resources, | ||||||
| 
 | 
 | ||||||
|  |   >>> from loops.resource import Document, MediaAsset | ||||||
|   >>> doc1 = Document(u'Zope Info') |   >>> doc1 = Document(u'Zope Info') | ||||||
|   >>> resources['doc1'] = doc1 |   >>> resources['doc1'] = doc1 | ||||||
|   >>> doc1.title |   >>> doc1.title | ||||||
|  | @ -61,6 +66,7 @@ resources, | ||||||
| 
 | 
 | ||||||
| and nodes (in view space): | and nodes (in view space): | ||||||
| 
 | 
 | ||||||
|  |   >>> from loops.view import Node | ||||||
|   >>> m1 = Node(u'Home') |   >>> m1 = Node(u'Home') | ||||||
|   >>> views['m1'] = m1 |   >>> views['m1'] = m1 | ||||||
|   >>> m1.nodeType = 'menu' |   >>> m1.nodeType = 'menu' | ||||||
|  | @ -69,12 +75,6 @@ and nodes (in view space): | ||||||
|   >>> m1['p1'] = m1p1 |   >>> m1['p1'] = m1p1 | ||||||
|   >>> m1p1.nodeType = 'page' |   >>> m1p1.nodeType = 'page' | ||||||
| 
 | 
 | ||||||
| Finally, we also need a relation registry: |  | ||||||
| 
 |  | ||||||
|   >>> from cybertools.relation.interfaces import IRelationRegistry |  | ||||||
|   >>> from cybertools.relation.registry import DummyRelationRegistry |  | ||||||
|   >>> from zope.app.testing import ztapi |  | ||||||
|   >>> ztapi.provideUtility(IRelationRegistry, DummyRelationRegistry()) |  | ||||||
| 
 | 
 | ||||||
| Type management with typology | Type management with typology | ||||||
| ============================= | ============================= | ||||||
|  | @ -98,11 +98,10 @@ As we have not yet associated a type with one of our content objects we get | ||||||
|   >>> cc1_type.token |   >>> cc1_type.token | ||||||
|   '.unknown' |   '.unknown' | ||||||
| 
 | 
 | ||||||
| So we create two special concepts: one ('hasType') as the predicate signifying | During setup we created two special concepts: one ('hasType') as the predicate | ||||||
| a type relation, and the other ('type') as the one and only type concept: | signifying a type relation, and the other ('type') as the one and only type | ||||||
|  | concept: | ||||||
| 
 | 
 | ||||||
|   >>> concepts['hasType'] = Concept(u'has type') |  | ||||||
|   >>> concepts['type'] = Concept(u'Type') |  | ||||||
|   >>> typeObject = concepts['type'] |   >>> typeObject = concepts['type'] | ||||||
| 
 | 
 | ||||||
| Assigning a type to a concept is a core functionality of concepts as | Assigning a type to a concept is a core functionality of concepts as | ||||||
|  | @ -189,8 +188,9 @@ get a type manager from all loops objects, always with the same context: | ||||||
| 
 | 
 | ||||||
|   >>> types = typeManager.types |   >>> types = typeManager.types | ||||||
|   >>> sorted(t.token for t in types) |   >>> sorted(t.token for t in types) | ||||||
|   ['.loops/concepts/topic', '.loops/concepts/type', |     ['.loops/concepts/predicate', '.loops/concepts/topic', | ||||||
|    'loops.resource.Document', 'loops.resource.MediaAsset'] |      '.loops/concepts/type', 'loops.resource.Document', | ||||||
|  |      'loops.resource.MediaAsset'] | ||||||
| 
 | 
 | ||||||
|   >>> typeManager.getType('.loops/concepts/topic') == cc1_type |   >>> typeManager.getType('.loops/concepts/topic') == cc1_type | ||||||
|   True |   True | ||||||
|  | @ -200,7 +200,7 @@ condition: | ||||||
| 
 | 
 | ||||||
|   >>> types = typeManager.listTypes(include=('concept',)) |   >>> types = typeManager.listTypes(include=('concept',)) | ||||||
|   >>> sorted(t.token for t in types) |   >>> sorted(t.token for t in types) | ||||||
|   ['.loops/concepts/topic', '.loops/concepts/type'] |   ['.loops/concepts/predicate', '.loops/concepts/topic', '.loops/concepts/type'] | ||||||
|   >>> types = typeManager.listTypes(exclude=('concept',)) |   >>> types = typeManager.listTypes(exclude=('concept',)) | ||||||
|   >>> sorted(t.token for t in types) |   >>> sorted(t.token for t in types) | ||||||
|   ['loops.resource.Document', 'loops.resource.MediaAsset'] |   ['loops.resource.Document', 'loops.resource.MediaAsset'] | ||||||
|  |  | ||||||
|  | @ -32,8 +32,7 @@ from zope.app.folder.interfaces import IFolder | ||||||
| from cybertools.relation.interfaces import IRelation | from cybertools.relation.interfaces import IRelation | ||||||
| 
 | 
 | ||||||
| import util | import util | ||||||
| 
 | from util import _ | ||||||
| _ = MessageFactory('loops') |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # common interfaces | # common interfaces | ||||||
|  |  | ||||||
							
								
								
									
										86
									
								
								setup.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								setup.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,86 @@ | ||||||
|  | # | ||||||
|  | #  Copyright (c) 2006 Helmut Merz helmutm@cy55.de | ||||||
|  | # | ||||||
|  | #  This program is free software; you can redistribute it and/or modify | ||||||
|  | #  it under the terms of the GNU General Public License as published by | ||||||
|  | #  the Free Software Foundation; either version 2 of the License, or | ||||||
|  | #  (at your option) any later version. | ||||||
|  | # | ||||||
|  | #  This program is distributed in the hope that it will be useful, | ||||||
|  | #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | #  GNU General Public License for more details. | ||||||
|  | # | ||||||
|  | #  You should have received a copy of the GNU General Public License | ||||||
|  | #  along with this program; if not, write to the Free Software | ||||||
|  | #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||||
|  | # | ||||||
|  | 
 | ||||||
|  | """ | ||||||
|  | Automatic setup of a loops site. | ||||||
|  | 
 | ||||||
|  | $Id$ | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | import transaction | ||||||
|  | from zope.app.event.objectevent import ObjectCreatedEvent, ObjectModifiedEvent | ||||||
|  | from zope.event import notify | ||||||
|  | from zope.component import adapts | ||||||
|  | from zope.interface import implements, Interface | ||||||
|  | from zope.cachedescriptors.property import Lazy | ||||||
|  | 
 | ||||||
|  | from loops.interfaces import ILoops | ||||||
|  | from loops.concept import ConceptManager, Concept | ||||||
|  | from loops.resource import ResourceManager | ||||||
|  | from loops.view import ViewManager, Node | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class ISetupManager(Interface): | ||||||
|  |     """ An object that controls the setup of a loops site. | ||||||
|  |     """ | ||||||
|  | 
 | ||||||
|  |     def setup(): | ||||||
|  |         """ Set up a loops site: create all necessary objects and the | ||||||
|  |             relations between them. | ||||||
|  |         """ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class SetupManager(object): | ||||||
|  | 
 | ||||||
|  |     adapts(ILoops) | ||||||
|  |     implements(ISetupManager) | ||||||
|  | 
 | ||||||
|  |     def __init__(self, context): | ||||||
|  |         self.context = context | ||||||
|  | 
 | ||||||
|  |     def setup(self): | ||||||
|  |         concepts, resources, views = self.setupManagers() | ||||||
|  |         self.setupCoreConcepts(concepts) | ||||||
|  | 
 | ||||||
|  |     def setupManagers(self): | ||||||
|  |         loopsRoot = self.context | ||||||
|  |         concepts = self.addObject(loopsRoot, ConceptManager, 'concepts') | ||||||
|  |         resources = self.addObject(loopsRoot, ResourceManager, 'resources') | ||||||
|  |         views = self.addObject(loopsRoot, ViewManager, 'views') | ||||||
|  |         return concepts, resources, views | ||||||
|  |          | ||||||
|  |     def setupCoreConcepts(self, conceptManager): | ||||||
|  |         typeConcept = self.addObject(conceptManager, Concept, 'type', u'Type') | ||||||
|  |         hasType = self.addObject(conceptManager, Concept, 'hasType', u'has type') | ||||||
|  |         predicate = self.addObject(conceptManager, Concept, 'predicate', u'Predicate') | ||||||
|  |         standard = self.addObject(conceptManager, Concept, 'standard', u'subobject') | ||||||
|  |         typeConcept.conceptType = typeConcept | ||||||
|  |         predicate.conceptType = typeConcept | ||||||
|  |         hasType.conceptType = predicate | ||||||
|  |         standard.conceptType = predicate | ||||||
|  | 
 | ||||||
|  |     def addObject(self, container, class_, name, title=None): | ||||||
|  |         if name in container: | ||||||
|  |             return container[name] | ||||||
|  |         if title: | ||||||
|  |             obj = container[name] = class_(title) | ||||||
|  |         else: | ||||||
|  |             obj = container[name] = class_() | ||||||
|  |         notify(ObjectCreatedEvent(obj)) | ||||||
|  |         notify(ObjectModifiedEvent(obj)) | ||||||
|  |         return obj | ||||||
|  | @ -17,6 +17,12 @@ from loops.concept import Concept, ConceptManager | ||||||
| from loops.resource import Document, MediaAsset, ResourceManager | from loops.resource import Document, MediaAsset, ResourceManager | ||||||
| from loops.view import Node, ViewManager | from loops.view import Node, ViewManager | ||||||
| 
 | 
 | ||||||
|  | # just for making sure there aren't any syntax and other errors during | ||||||
|  | # Zope startup: | ||||||
|  | 
 | ||||||
|  | from loops.browser.manager import LoopsAddForm, LoopsEditForm | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class Test(unittest.TestCase): | class Test(unittest.TestCase): | ||||||
|     "Basic tests for the loops package." |     "Basic tests for the loops package." | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										3
									
								
								util.py
									
										
									
									
									
								
							
							
						
						
									
										3
									
								
								util.py
									
										
									
									
									
								
							|  | @ -23,9 +23,12 @@ $Id$ | ||||||
| """ | """ | ||||||
| 
 | 
 | ||||||
| from zope.interface import directlyProvides, directlyProvidedBy | from zope.interface import directlyProvides, directlyProvidedBy | ||||||
|  | from zope.i18nmessageid import MessageFactory | ||||||
| from zope.schema import vocabulary | from zope.schema import vocabulary | ||||||
| #from view import TargetRelation | #from view import TargetRelation | ||||||
| 
 | 
 | ||||||
|  | _ = MessageFactory('loops') | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| class KeywordVocabulary(vocabulary.SimpleVocabulary): | class KeywordVocabulary(vocabulary.SimpleVocabulary): | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 helmutm
						helmutm