merged Dojo 1.0 branch
git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@2388 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
		
							parent
							
								
									c6ff15c196
								
							
						
					
					
						commit
						5271981119
					
				
					 83 changed files with 3726 additions and 872 deletions
				
			
		|  | @ -593,7 +593,7 @@ Actions | ||||||
|   >>> request = TestRequest() |   >>> request = TestRequest() | ||||||
|   >>> view = NodeView(m112, request) |   >>> view = NodeView(m112, request) | ||||||
|   >>> view.controller = Controller(view, request) |   >>> view.controller = Controller(view, request) | ||||||
|   >>> view.setupController() |   >>> #view.setupController() | ||||||
| 
 | 
 | ||||||
|   >>> actions = view.getActions('portlet') |   >>> actions = view.getActions('portlet') | ||||||
|   >>> len(actions) |   >>> len(actions) | ||||||
|  | @ -758,7 +758,7 @@ The new technique uses the ``fields`` and ``data`` attributes... | ||||||
|   linkUrl textline False None |   linkUrl textline False None | ||||||
| 
 | 
 | ||||||
|   >>> view.data |   >>> view.data | ||||||
|   {'linkUrl': u'', 'contentType': u'', 'data': u'', |   {'linkUrl': u'http://', 'contentType': 'text/restructured', 'data': u'', | ||||||
|    'title': u'Test Note'} |    'title': u'Test Note'} | ||||||
| 
 | 
 | ||||||
| The object is changed via a FormController adapter created for | The object is changed via a FormController adapter created for | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| # | # | ||||||
| #  Copyright (c) 2007 Helmut Merz helmutm@cy55.de | #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||||
| # | # | ||||||
| #  This program is free software; you can redistribute it and/or modify | #  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 | #  it under the terms of the GNU General Public License as published by | ||||||
|  | @ -27,43 +27,7 @@ from zope import component | ||||||
| from zope.app.pagetemplate import ViewPageTemplateFile | from zope.app.pagetemplate import ViewPageTemplateFile | ||||||
| from zope.cachedescriptors.property import Lazy | from zope.cachedescriptors.property import Lazy | ||||||
| 
 | 
 | ||||||
| action_macros = ViewPageTemplateFile('action_macros.pt') | from cybertools.browser.action import Action | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class Action(object): |  | ||||||
| 
 |  | ||||||
|     template = action_macros |  | ||||||
|     macroName = 'action' |  | ||||||
|     condition = True |  | ||||||
|     permission = None |  | ||||||
|     url = '.' |  | ||||||
|     viewName = '' |  | ||||||
|     targetWindow = '' |  | ||||||
|     title = '' |  | ||||||
|     description = '' |  | ||||||
|     icon = '' |  | ||||||
|     cssClass = '' |  | ||||||
|     onClick = '' |  | ||||||
|     innerHtmlId = '' |  | ||||||
| 
 |  | ||||||
|     def __init__(self, view, **kw): |  | ||||||
|         self.view = view |  | ||||||
|         for k, v in kw.items(): |  | ||||||
|             setattr(self, k, v) |  | ||||||
| 
 |  | ||||||
|     @Lazy |  | ||||||
|     def macro(self): |  | ||||||
|         return self.template.macros[self.macroName] |  | ||||||
| 
 |  | ||||||
|     @Lazy |  | ||||||
|     def url(self): |  | ||||||
|         return self.getActionUrl(self.view.url) |  | ||||||
| 
 |  | ||||||
|     def getActionUrl(self, baseUrl): |  | ||||||
|         if self.viewName: |  | ||||||
|             return '/'.join((baseUrl, self.viewName)) |  | ||||||
|         else: |  | ||||||
|             return baseUrl |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class TargetAction(Action): | class TargetAction(Action): | ||||||
|  |  | ||||||
|  | @ -1,22 +0,0 @@ | ||||||
| <!-- action macros --> |  | ||||||
| 
 |  | ||||||
| <metal:action define-macro="action"> |  | ||||||
|     <div tal:condition="action/condition"> |  | ||||||
|         <a href="#" target="target_window" title="Description text" |  | ||||||
|            i18n:attributes="title" |  | ||||||
|            tal:attributes="href action/url; |  | ||||||
|                            target action/targetWindow; |  | ||||||
|                            title action/description; |  | ||||||
|                            onClick action/onClick;"><img |  | ||||||
|                src="#" alt="icon" |  | ||||||
|                tal:condition="action/icon" |  | ||||||
|                tal:attributes="src action/icon; |  | ||||||
|                                alt action/description" /><span |  | ||||||
|                i18n:translate="" |  | ||||||
|                tal:condition="action/title" |  | ||||||
|                tal:content="action/title">Action Title</span></a> |  | ||||||
|     </div> |  | ||||||
|     <span id="inner.Id" |  | ||||||
|           tal:condition="action/innerHtmlId" |  | ||||||
|           tal:attributes="id action/innerHtmlId"></span> |  | ||||||
| </metal:action> |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| # | # | ||||||
| #  Copyright (c) 2006 Helmut Merz helmutm@cy55.de | #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||||
| # | # | ||||||
| #  This program is free software; you can redistribute it and/or modify | #  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 | #  it under the terms of the GNU General Public License as published by | ||||||
|  | @ -22,7 +22,6 @@ Common base class for loops browser view classes. | ||||||
| $Id$ | $Id$ | ||||||
| """ | """ | ||||||
| 
 | 
 | ||||||
| from zope.app import zapi |  | ||||||
| from zope import component | from zope import component | ||||||
| from zope.app.form.browser.interfaces import ITerms | from zope.app.form.browser.interfaces import ITerms | ||||||
| from zope.app.i18n.interfaces import ITranslationDomain | from zope.app.i18n.interfaces import ITranslationDomain | ||||||
|  | @ -35,24 +34,27 @@ from zope.formlib import form | ||||||
| from zope.formlib.form import FormFields | from zope.formlib.form import FormFields | ||||||
| from zope.formlib.namedtemplate import NamedTemplate | from zope.formlib.namedtemplate import NamedTemplate | ||||||
| from zope.interface import Interface, implements | from zope.interface import Interface, implements | ||||||
|  | from zope.proxy import removeAllProxies | ||||||
| from zope.publisher.browser import applySkin | from zope.publisher.browser import applySkin | ||||||
| from zope.publisher.interfaces.browser import IBrowserSkinType | from zope.publisher.interfaces.browser import IBrowserSkinType | ||||||
| from zope import schema | from zope import schema | ||||||
| from zope.schema.vocabulary import SimpleTerm | from zope.schema.vocabulary import SimpleTerm | ||||||
| from zope.security import canAccess, canWrite, checkPermission | from zope.security import canAccess, checkPermission | ||||||
| from zope.security.interfaces import ForbiddenAttribute, Unauthorized | from zope.security.interfaces import ForbiddenAttribute, Unauthorized | ||||||
| from zope.security.proxy import removeSecurityProxy | from zope.security.proxy import removeSecurityProxy | ||||||
| from zope.traversing.browser import absoluteURL | from zope.traversing.browser import absoluteURL | ||||||
| from zope.traversing.api import getName | from zope.traversing.api import getName, getParent | ||||||
| 
 | 
 | ||||||
|  | from cybertools.ajax.dojo import dojoMacroTemplate | ||||||
| from cybertools.browser.view import GenericView | from cybertools.browser.view import GenericView | ||||||
| from cybertools.relation.interfaces import IRelationRegistry | from cybertools.relation.interfaces import IRelationRegistry | ||||||
| from cybertools.text import mimetypes | from cybertools.text import mimetypes | ||||||
| from cybertools.typology.interfaces import IType, ITypeManager | from cybertools.typology.interfaces import IType, ITypeManager | ||||||
| from loops.common import adapted | from loops.common import adapted | ||||||
| from loops.i18n.browser import I18NView | from loops.i18n.browser import I18NView | ||||||
| from loops.interfaces import IView | from loops.interfaces import IView, INode | ||||||
| from loops.resource import Resource | from loops.resource import Resource | ||||||
|  | from loops.security.common import canAccessObject, canListObject, canWriteObject | ||||||
| from loops.type import ITypeConcept | from loops.type import ITypeConcept | ||||||
| from loops import util | from loops import util | ||||||
| from loops.util import _ | from loops.util import _ | ||||||
|  | @ -90,29 +92,35 @@ class EditForm(form.EditForm): | ||||||
|     template = NamedTemplate('loops.pageform') |     template = NamedTemplate('loops.pageform') | ||||||
| 
 | 
 | ||||||
|     def deleteObjectAction(self): |     def deleteObjectAction(self): | ||||||
|         return None  # better not to show the edit button at the moment |         return None  # better not to show the delete button at the moment | ||||||
|         parent = zapi.getParent(self.context) |         parent = getParent(self.context) | ||||||
|         parentUrl = absoluteURL(parent, self.request) |         parentUrl = absoluteURL(parent, self.request) | ||||||
|         return parentUrl + '/contents.html' |         return parentUrl + '/contents.html' | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class BaseView(GenericView, I18NView): | class BaseView(GenericView, I18NView): | ||||||
| 
 | 
 | ||||||
|     actions = {}  # default only, don't update |     actions = {} | ||||||
| 
 | 
 | ||||||
|     def __init__(self, context, request): |     def __init__(self, context, request): | ||||||
|         super(BaseView, self).__init__(context, request) |         super(BaseView, self).__init__(context, request) | ||||||
|         # TODO: get rid of removeSecurityProxy() call |         # TODO: get rid of removeSecurityProxy() call - not yet... | ||||||
|         self.context = removeSecurityProxy(context) |         self.context = removeSecurityProxy(context) | ||||||
|  |         #self.context = context | ||||||
|         #self.setSkin(self.loopsRoot.skinName) |         #self.setSkin(self.loopsRoot.skinName) | ||||||
|         self.checkLanguage() |         #self.checkLanguage() | ||||||
|         try: |         try: | ||||||
|             if not canAccess(context, 'title'): |             if not canAccessObject(context): | ||||||
|                 raise Unauthorized |                 raise Unauthorized | ||||||
|                 #request.response.redirect('login.html') |                 #request.response.redirect('login.html') | ||||||
|         except ForbiddenAttribute:  # ignore when testing |         except ForbiddenAttribute:  # ignore when testing | ||||||
|             pass |             pass | ||||||
| 
 | 
 | ||||||
|  |     def update(self): | ||||||
|  |         result = super(BaseView, self).update() | ||||||
|  |         self.checkLanguage() | ||||||
|  |         return result | ||||||
|  | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def target(self): |     def target(self): | ||||||
|         # allow for having a separate object the view acts upon |         # allow for having a separate object the view acts upon | ||||||
|  | @ -242,6 +250,16 @@ class BaseView(GenericView, I18NView): | ||||||
|         for o in objs: |         for o in objs: | ||||||
|             yield BaseView(o, request) |             yield BaseView(o, request) | ||||||
| 
 | 
 | ||||||
|  |     def renderText(self, text, contentType): | ||||||
|  |         typeKey = util.renderingFactories.get(contentType, None) | ||||||
|  |         if typeKey is None: | ||||||
|  |             if contentType == u'text/html': | ||||||
|  |                 return text | ||||||
|  |             return u'<pre>%s</pre>' % util.html_quote(text) | ||||||
|  |         source = component.createObject(typeKey, text) | ||||||
|  |         view = component.getMultiAdapter((removeAllProxies(source), self.request)) | ||||||
|  |         return view.render() | ||||||
|  | 
 | ||||||
|     # type listings |     # type listings | ||||||
| 
 | 
 | ||||||
|     def listTypes(self, include=None, exclude=None, sortOn='title'): |     def listTypes(self, include=None, exclude=None, sortOn='title'): | ||||||
|  | @ -340,7 +358,7 @@ class BaseView(GenericView, I18NView): | ||||||
| 
 | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def editable(self): |     def editable(self): | ||||||
|         return canWrite(self.context, 'title') |         return canWriteObject(self.context) | ||||||
| 
 | 
 | ||||||
|     def getActions(self, category='object', page=None): |     def getActions(self, category='object', page=None): | ||||||
|         """ Return a list of actions that provide the view and edit actions |         """ Return a list of actions that provide the view and edit actions | ||||||
|  | @ -364,7 +382,7 @@ class BaseView(GenericView, I18NView): | ||||||
|             return False |             return False | ||||||
|         if ct.startswith('text/') and ct != 'text/rtf': |         if ct.startswith('text/') and ct != 'text/rtf': | ||||||
|             return checkPermission('loops.ManageSite', self.context) |             return checkPermission('loops.ManageSite', self.context) | ||||||
|         return canWrite(self.context, 'title') |         return canWriteObject(self.context) | ||||||
| 
 | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def inlineEditingActive(self): |     def inlineEditingActive(self): | ||||||
|  | @ -385,8 +403,27 @@ class BaseView(GenericView, I18NView): | ||||||
| 
 | 
 | ||||||
|     def registerDojo(self): |     def registerDojo(self): | ||||||
|         cm = self.controller.macros |         cm = self.controller.macros | ||||||
|         cm.register('js', 'dojo.js', resourceName='ajax.dojo/dojo.js') |         cm.register('js', 'dojo.js', template=dojoMacroTemplate, name='main', | ||||||
|         #cm.register('js', 'dojo.js', resourceName='ajax.dojo1/dojo/dojo.js') |                     position=0, | ||||||
|  |                     #djConfig='isDebug: true, parseOnLoad: true, usePlainJson: true, ' | ||||||
|  |                     djConfig='parseOnLoad: true, usePlainJson: true, ' | ||||||
|  |                              'locale: "%s"' % self.languageInfo.language) | ||||||
|  |         jsCall = 'dojo.require("dojo.parser");' | ||||||
|  |         cm.register('js-execute', jsCall, jsCall=jsCall) | ||||||
|  |         cm.register('css', identifier='tundra.css', position=0, | ||||||
|  |                     resourceName='ajax.dojo/dijit/themes/tundra/tundra.css', media='all') | ||||||
|  |         cm.register('css', identifier='dojo.css', position=1, | ||||||
|  |                     resourceName='ajax.dojo/dojo/resources/dojo.css', media='all') | ||||||
|  | 
 | ||||||
|  |     def registerDojoDateWidget(self): | ||||||
|  |         self.registerDojo() | ||||||
|  |         jsCall = 'dojo.require("dijit.form.DateTextBox");' | ||||||
|  |         self.controller.macros.register('js-execute', jsCall, jsCall=jsCall) | ||||||
|  | 
 | ||||||
|  |     def registerDojoTextWidget(self): | ||||||
|  |         self.registerDojo() | ||||||
|  |         jsCall = 'dojo.require("dijit.form.ValidationTextBox");' | ||||||
|  |         self.controller.macros.register('js-execute', jsCall, jsCall=jsCall) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # vocabulary stuff | # vocabulary stuff | ||||||
|  | @ -411,7 +448,7 @@ class LoopsTerms(object): | ||||||
|     def getTerm(self, value): |     def getTerm(self, value): | ||||||
|         #if value is None: |         #if value is None: | ||||||
|         #    return SimpleTerm(None, '', u'not assigned') |         #    return SimpleTerm(None, '', u'not assigned') | ||||||
|         title = value.title or zapi.getName(value) |         title = value.title or getName(value) | ||||||
|         token = self.loopsRoot.getLoopsUri(value) |         token = self.loopsRoot.getLoopsUri(value) | ||||||
|         return SimpleTerm(value, token, title) |         return SimpleTerm(value, token, title) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| # | # | ||||||
| #  Copyright (c) 2007 Helmut Merz helmutm@cy55.de | #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||||
| # | # | ||||||
| #  This program is free software; you can redistribute it and/or modify | #  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 | #  it under the terms of the GNU General Public License as published by | ||||||
|  | @ -44,7 +44,11 @@ from zope.schema.interfaces import IIterableSource | ||||||
| from zope.security.proxy import removeSecurityProxy | from zope.security.proxy import removeSecurityProxy | ||||||
| from zope.traversing.api import getName | from zope.traversing.api import getName | ||||||
| 
 | 
 | ||||||
|  | from cybertools.browser.action import actions | ||||||
|  | from cybertools.composer.interfaces import IInstance | ||||||
|  | from cybertools.composer.schema.interfaces import ISchemaFactory | ||||||
| from cybertools.typology.interfaces import IType, ITypeManager | from cybertools.typology.interfaces import IType, ITypeManager | ||||||
|  | from cybertools.util.jeep import Jeep | ||||||
| from loops.browser.common import EditForm, BaseView, LoopsTerms, conceptMacrosTemplate | from loops.browser.common import EditForm, BaseView, LoopsTerms, conceptMacrosTemplate | ||||||
| from loops.common import adapted | from loops.common import adapted | ||||||
| from loops.concept import Concept, ConceptTypeSourceList, PredicateSourceList | from loops.concept import Concept, ConceptTypeSourceList, PredicateSourceList | ||||||
|  | @ -56,8 +60,14 @@ from loops.versioning.util import getVersion | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class ConceptEditForm(EditForm, I18NView): | class ConceptEditForm(EditForm, I18NView): | ||||||
|  |     """ Classic-style (zope.formlib-based) form for editing concepts. | ||||||
|  |     """ | ||||||
| 
 | 
 | ||||||
|     @Lazy |     #@Lazy  # zope.formlib does not issue a redirect after changes, so that | ||||||
|  |             # it tries to redisplay the old form even after a type change that | ||||||
|  |             # changes the set of available attributes. So the typeInterface | ||||||
|  |             # must be recalculated e.g. after an update of the context object. | ||||||
|  |     @property | ||||||
|     def typeInterface(self): |     def typeInterface(self): | ||||||
|         return IType(self.context).typeInterface |         return IType(self.context).typeInterface | ||||||
| 
 | 
 | ||||||
|  | @ -79,7 +89,8 @@ class ConceptEditForm(EditForm, I18NView): | ||||||
| 
 | 
 | ||||||
|     def setUpWidgets(self, ignore_request=False): |     def setUpWidgets(self, ignore_request=False): | ||||||
|         # TODO: get rid of removeSecurityProxy(): use ConceptSchema in interfaces |         # TODO: get rid of removeSecurityProxy(): use ConceptSchema in interfaces | ||||||
|         adapter = removeSecurityProxy(adapted(self.context, self.languageInfo)) |         #adapter = removeSecurityProxy(adapted(self.context, self.languageInfo)) | ||||||
|  |         adapter = adapted(self.context, self.languageInfo) | ||||||
|         self.adapters = {self.typeInterface: adapter, |         self.adapters = {self.typeInterface: adapter, | ||||||
|                          IConceptSchema: adapter} |                          IConceptSchema: adapter} | ||||||
|         self.widgets = setUpEditWidgets( |         self.widgets = setUpEditWidgets( | ||||||
|  | @ -89,9 +100,87 @@ class ConceptEditForm(EditForm, I18NView): | ||||||
|         if desc: |         if desc: | ||||||
|             desc.height = 2 |             desc.height = 2 | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | class ConceptRelationView(BaseView): | ||||||
|  |     """ For displaying children and resources lists, used by ConceptView. | ||||||
|  |     """ | ||||||
|  | 
 | ||||||
|  |     def __init__(self, relation, request, contextIsSecond=False): | ||||||
|  |         if contextIsSecond: | ||||||
|  |             self.context = relation.second | ||||||
|  |             self.other = relation.first | ||||||
|  |         else: | ||||||
|  |             self.context = relation.first | ||||||
|  |             self.other = relation.second | ||||||
|  |         self.context = getVersion(self.context, request) | ||||||
|  |         self.predicate = relation.predicate | ||||||
|  |         self.relation = relation | ||||||
|  |         self.request = request | ||||||
|  | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def adapted(self): | ||||||
|  |         return adapted(self.context, self.languageInfo) | ||||||
|  | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def data(self): | ||||||
|  |         return self.instance.applyTemplate() | ||||||
|  | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def instance(self): | ||||||
|  |         instance = IInstance(self.adapted) | ||||||
|  |         instance.template = self.schema | ||||||
|  |         instance.view = self | ||||||
|  |         return instance | ||||||
|  | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def schema(self): | ||||||
|  |         ti = self.typeInterface or IConceptSchema | ||||||
|  |         schemaFactory = component.getAdapter(self.adapted, ISchemaFactory) | ||||||
|  |         return schemaFactory(ti, manager=self, request=self.request) | ||||||
|  | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def title(self): | ||||||
|  |         return self.adapted.title or getName(self.context) | ||||||
|  | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def description(self): | ||||||
|  |         return self.adapted.description | ||||||
|  | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def token(self): | ||||||
|  |         return ':'.join((self.loopsRoot.getLoopsUri(self.context), | ||||||
|  |                          self.loopsRoot.getLoopsUri(self.predicate))) | ||||||
|  | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def uidToken(self): | ||||||
|  |         return ':'.join((util.getUidForObject(self.context), | ||||||
|  |                          util.getUidForObject(self.predicate))) | ||||||
|  | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def isProtected(self): | ||||||
|  |         return getName(self.predicate) == 'hasType' | ||||||
|  | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def predicateTitle(self): | ||||||
|  |         return self.predicate.title | ||||||
|  | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def predicateUrl(self): | ||||||
|  |         return zapi.absoluteURL(self.predicate, self.request) | ||||||
|  | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def relevance(self): | ||||||
|  |         return self.relation.relevance | ||||||
|  | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def order(self): | ||||||
|  |         return self.relation.order | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class ConceptView(BaseView): | class ConceptView(BaseView): | ||||||
| 
 | 
 | ||||||
|     template = ViewPageTemplateFile('concept_macros.pt') |     template = ViewPageTemplateFile('concept_macros.pt') | ||||||
|  |     childViewFactory = ConceptRelationView | ||||||
| 
 | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def macro(self): |     def macro(self): | ||||||
|  | @ -108,7 +197,7 @@ class ConceptView(BaseView): | ||||||
|                                                     self.request.principal)): |                                                     self.request.principal)): | ||||||
|             cont.macros.register('portlet_right', 'parents', title=_(u'Parents'), |             cont.macros.register('portlet_right', 'parents', title=_(u'Parents'), | ||||||
|                          subMacro=self.template.macros['parents'], |                          subMacro=self.template.macros['parents'], | ||||||
|                          position=0, info=self) |                          priority=20, info=self) | ||||||
| 
 | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def adapted(self): |     def adapted(self): | ||||||
|  | @ -123,7 +212,8 @@ class ConceptView(BaseView): | ||||||
|         return self.adapted.description |         return self.adapted.description | ||||||
| 
 | 
 | ||||||
|     def fieldData(self): |     def fieldData(self): | ||||||
|         # TODO: use cybertools.composer.schema.instance, see loops.browser.form |         # obsolete - use getData() instead | ||||||
|  |         # TODO: change view macros accordingly | ||||||
|         ti = IType(self.context).typeInterface |         ti = IType(self.context).typeInterface | ||||||
|         if not ti: |         if not ti: | ||||||
|             return |             return | ||||||
|  | @ -140,12 +230,35 @@ class ConceptView(BaseView): | ||||||
|             widget.setRenderedValue(value) |             widget.setRenderedValue(value) | ||||||
|             yield dict(title=f.title, value=value, id=n, widget=widget) |             yield dict(title=f.title, value=value, id=n, widget=widget) | ||||||
| 
 | 
 | ||||||
|     def children(self, topLevelOnly=True, sort=True): |     def getData(self, omit=('title', 'description')): | ||||||
|  |         data = self.instance.applyTemplate() | ||||||
|  |         for k in omit: | ||||||
|  |             if k in data: | ||||||
|  |                 del data[k] | ||||||
|  |         return data | ||||||
|  | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def data(self): | ||||||
|  |         return self.getData() | ||||||
|  | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def instance(self): | ||||||
|  |         instance = IInstance(self.adapted) | ||||||
|  |         instance.template = self.schema | ||||||
|  |         instance.view = self | ||||||
|  |         return instance | ||||||
|  | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def schema(self): | ||||||
|  |         ti = self.typeInterface or IConceptSchema | ||||||
|  |         schemaFactory = component.getAdapter(self.adapted, ISchemaFactory) | ||||||
|  |         return schemaFactory(ti, manager=self, request=self.request) | ||||||
|  | 
 | ||||||
|  |     def getChildren(self, topLevelOnly=True, sort=True): | ||||||
|         cm = self.loopsRoot.getConceptManager() |         cm = self.loopsRoot.getConceptManager() | ||||||
|         hasType = cm.getTypePredicate() |         hasType = cm.getTypePredicate() | ||||||
|         standard = cm.getDefaultPredicate() |         standard = cm.getDefaultPredicate() | ||||||
|         #rels = self.context.getChildRelations() |         rels = (self.childViewFactory(r, self.request, contextIsSecond=True) | ||||||
|         rels = (ConceptRelationView(r, self.request, contextIsSecond=True) |  | ||||||
|                 for r in self.context.getChildRelations(sort=None)) |                 for r in self.context.getChildRelations(sort=None)) | ||||||
|         if sort: |         if sort: | ||||||
|             rels = sorted(rels, key=lambda r: (r.order, r.title.lower())) |             rels = sorted(rels, key=lambda r: (r.order, r.title.lower())) | ||||||
|  | @ -160,27 +273,37 @@ class ConceptView(BaseView): | ||||||
|                 if skip: continue |                 if skip: continue | ||||||
|             yield r |             yield r | ||||||
| 
 | 
 | ||||||
|  |     # Override in subclass to control what is displayd in listings: | ||||||
|  |     children = getChildren | ||||||
|  | 
 | ||||||
|     def childrenAlphaGroups(self): |     def childrenAlphaGroups(self): | ||||||
|         letters = [] |         result = Jeep() | ||||||
|         relations = {} |         rels = self.getChildren(topLevelOnly=False, sort=False) | ||||||
|         rels = self.children(topLevelOnly=False, sort=False) |  | ||||||
|         rels = sorted(rels, key=lambda r: r.title.lower()) |         rels = sorted(rels, key=lambda r: r.title.lower()) | ||||||
|         for letter, group in groupby(rels, lambda r: r.title.lower()[0]): |         for letter, group in groupby(rels, lambda r: r.title.lower()[0]): | ||||||
|             letter = letter.upper() |             letter = letter.upper() | ||||||
|             letters.append(letter) |             result[letter] = list(group) | ||||||
|             relations[letter] = list(group) |         return result | ||||||
|         return dict(letters=letters, relations=relations) | 
 | ||||||
|  |     def childrenByType(self): | ||||||
|  |         result = Jeep() | ||||||
|  |         rels = self.getChildren(topLevelOnly=False, sort=False) | ||||||
|  |         rels = sorted(rels, key=lambda r: (r.typeTitle.lower(), r.title.lower())) | ||||||
|  |         for type, group in groupby(rels, lambda r: r.type): | ||||||
|  |             typeName = getName(type.typeProvider) | ||||||
|  |             result[typeName] = list(group) | ||||||
|  |         return result | ||||||
| 
 | 
 | ||||||
|     def parents(self): |     def parents(self): | ||||||
|         rels = sorted(self.context.getParentRelations(), |         rels = sorted(self.context.getParentRelations(), | ||||||
|                       key=(lambda x: x.first.title.lower())) |                       key=(lambda x: x.first.title.lower())) | ||||||
|         for r in rels: |         for r in rels: | ||||||
|             yield ConceptRelationView(r, self.request) |             yield self.childViewFactory(r, self.request) | ||||||
| 
 | 
 | ||||||
|     def resources(self): |     def resources(self): | ||||||
|         rels = self.context.getResourceRelations() |         rels = self.context.getResourceRelations() | ||||||
|         for r in rels: |         for r in rels: | ||||||
|             yield ConceptRelationView(r, self.request, contextIsSecond=True) |             yield self.childViewFactory(r, self.request, contextIsSecond=True) | ||||||
| 
 | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def view(self): |     def view(self): | ||||||
|  | @ -213,6 +336,14 @@ class ConceptView(BaseView): | ||||||
|         for node in self.context.getClients(): |         for node in self.context.getClients(): | ||||||
|             yield NodeView(node, self.request) |             yield NodeView(node, self.request) | ||||||
| 
 | 
 | ||||||
|  |     def getActions(self, category='object', page=None): | ||||||
|  |         t = IType(self.context) | ||||||
|  |         actInfo = t.optionsDict.get('action.' + category, '') | ||||||
|  |         actNames = [n.strip() for n in actInfo.split(',')] | ||||||
|  |         if actNames: | ||||||
|  |             return actions.get(category, actNames, view=self, page=page) | ||||||
|  |         return [] | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| class ConceptConfigureView(ConceptView): | class ConceptConfigureView(ConceptView): | ||||||
| 
 | 
 | ||||||
|  | @ -321,7 +452,7 @@ class ConceptConfigureView(ConceptView): | ||||||
|                 else: |                 else: | ||||||
|                     start = end = searchType |                     start = end = searchType | ||||||
|                 criteria['loops_type'] = (start, end) |                 criteria['loops_type'] = (start, end) | ||||||
|             cat = zapi.getUtility(ICatalog) |             cat = component.getUtility(ICatalog) | ||||||
|             result = cat.searchResults(**criteria) |             result = cat.searchResults(**criteria) | ||||||
|             # TODO: can this be done in a faster way? |             # TODO: can this be done in a faster way? | ||||||
|             result = [r for r in result if r.getLoopsRoot() == self.loopsRoot] |             result = [r for r in result if r.getLoopsRoot() == self.loopsRoot] | ||||||
|  | @ -333,64 +464,8 @@ class ConceptConfigureView(ConceptView): | ||||||
| 
 | 
 | ||||||
|     def predicates(self): |     def predicates(self): | ||||||
|         preds = PredicateSourceList(self.context) |         preds = PredicateSourceList(self.context) | ||||||
|         terms = zapi.getMultiAdapter((preds, self.request), ITerms) |         terms = component.getMultiAdapter((preds, self.request), ITerms) | ||||||
|         for pred in preds: |         for pred in preds: | ||||||
|             yield terms.getTerm(pred) |             yield terms.getTerm(pred) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class ConceptRelationView(BaseView): |  | ||||||
| 
 |  | ||||||
|     def __init__(self, relation, request, contextIsSecond=False): |  | ||||||
|         if contextIsSecond: |  | ||||||
|             self.context = relation.second |  | ||||||
|             self.other = relation.first |  | ||||||
|         else: |  | ||||||
|             self.context = relation.first |  | ||||||
|             self.other = relation.second |  | ||||||
|         self.context = getVersion(self.context, request) |  | ||||||
|         self.predicate = relation.predicate |  | ||||||
|         self.relation = relation |  | ||||||
|         self.request = request |  | ||||||
| 
 |  | ||||||
|     @Lazy |  | ||||||
|     def adapted(self): |  | ||||||
|         return adapted(self.context, self.languageInfo) |  | ||||||
| 
 |  | ||||||
|     @Lazy |  | ||||||
|     def title(self): |  | ||||||
|         return self.adapted.title or getName(self.context) |  | ||||||
| 
 |  | ||||||
|     @Lazy |  | ||||||
|     def description(self): |  | ||||||
|         return self.adapted.description |  | ||||||
| 
 |  | ||||||
|     @Lazy |  | ||||||
|     def token(self): |  | ||||||
|         return ':'.join((self.loopsRoot.getLoopsUri(self.context), |  | ||||||
|                          self.loopsRoot.getLoopsUri(self.predicate))) |  | ||||||
| 
 |  | ||||||
|     @Lazy |  | ||||||
|     def uidToken(self): |  | ||||||
|         return ':'.join((util.getUidForObject(self.context), |  | ||||||
|                          util.getUidForObject(self.predicate))) |  | ||||||
| 
 |  | ||||||
|     @Lazy |  | ||||||
|     def isProtected(self): |  | ||||||
|         return zapi.getName(self.predicate) == 'hasType' |  | ||||||
| 
 |  | ||||||
|     @Lazy |  | ||||||
|     def predicateTitle(self): |  | ||||||
|         return self.predicate.title |  | ||||||
| 
 |  | ||||||
|     @Lazy |  | ||||||
|     def predicateUrl(self): |  | ||||||
|         return zapi.absoluteURL(self.predicate, self.request) |  | ||||||
| 
 |  | ||||||
|     @Lazy |  | ||||||
|     def relevance(self): |  | ||||||
|         return self.relation.relevance |  | ||||||
| 
 |  | ||||||
|     @Lazy |  | ||||||
|     def order(self): |  | ||||||
|         return self.relation.order |  | ||||||
| 
 |  | ||||||
|  |  | ||||||
|  | @ -1,8 +1,8 @@ | ||||||
| <metal:data define-macro="conceptdata"> | <metal:data define-macro="conceptdata"> | ||||||
|   <div tal:attributes="class string:content-$level;"> |   <div tal:attributes="class string:content-$level;"> | ||||||
|           <metal:fields use-macro="item/template/macros/concepttitle" /><br /> |           <metal:fields use-macro="item/template/macros/concepttitle" /> | ||||||
|           <metal:fields use-macro="item/template/macros/conceptfields" /><br /> |           <metal:fields use-macro="item/template/macros/conceptfields" /> | ||||||
|           <metal:fields use-macro="item/template/macros/conceptchildren" /><br /> |           <metal:fields use-macro="item/template/macros/conceptchildren" /> | ||||||
|           <metal:fields use-macro="item/template/macros/conceptresources" /> |           <metal:fields use-macro="item/template/macros/conceptresources" /> | ||||||
|   </div> |   </div> | ||||||
| </metal:data> | </metal:data> | ||||||
|  | @ -10,7 +10,8 @@ | ||||||
| 
 | 
 | ||||||
| <metal:title define-macro="concepttitle"> | <metal:title define-macro="concepttitle"> | ||||||
|     <h1 tal:attributes="ondblclick item/openEditWindow"> |     <h1 tal:attributes="ondblclick item/openEditWindow"> | ||||||
|       <span tal:content="item/title">Title</span> |       <a name="top" | ||||||
|  |          tal:content="item/title">Title</a> | ||||||
|     </h1> |     </h1> | ||||||
|       <p tal:define="description description|item/description" |       <p tal:define="description description|item/description" | ||||||
|          tal:condition="description"> |          tal:condition="description"> | ||||||
|  | @ -19,13 +20,16 @@ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| <metal:fields define-macro="conceptfields"> | <metal:fields define-macro="conceptfields"> | ||||||
|  |   <fieldset class="box" | ||||||
|  |             tal:define="data python: list(item.fieldData())" | ||||||
|  |             tal:condition="data"> | ||||||
|     <table tal:attributes="ondblclick item/openEditWindow"> |     <table tal:attributes="ondblclick item/openEditWindow"> | ||||||
|       <tr tal:repeat="field item/fieldData"> |       <tr tal:repeat="field data"> | ||||||
|         <td><span tal:content="field/title" |         <td><b tal:content="field/title" i18n:translate="" />:</td> | ||||||
|                   i18n:translate="" />:</td> |  | ||||||
|         <td tal:content="structure field/widget"></td> |         <td tal:content="structure field/widget"></td> | ||||||
|       </tr> |       </tr> | ||||||
|     </table> |     </table> | ||||||
|  |   </fieldset> | ||||||
| </metal:fields> | </metal:fields> | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -47,7 +51,7 @@ | ||||||
|                            ondblclick python: item.openEditWindow('configure.html')" |                            ondblclick python: item.openEditWindow('configure.html')" | ||||||
|            tal:define="children python: list(item.children())" |            tal:define="children python: list(item.children())" | ||||||
|            tal:condition="children"> |            tal:condition="children"> | ||||||
|         <h2 i18n:translate="">Children</h2><br /> |         <h2 i18n:translate="">Children</h2> | ||||||
|         <table class="listing"> |         <table class="listing"> | ||||||
|           <tr> |           <tr> | ||||||
|             <th i18n:translate="">Title</th> |             <th i18n:translate="">Title</th> | ||||||
|  | @ -81,7 +85,7 @@ | ||||||
|                            ondblclick python: item.openEditWindow('resources.html')" |                            ondblclick python: item.openEditWindow('resources.html')" | ||||||
|            tal:define="resources python: list(item.resources())" |            tal:define="resources python: list(item.resources())" | ||||||
|            tal:condition="resources"> |            tal:condition="resources"> | ||||||
|         <h2 i18n:translate="">Resources</h2><br /> |         <h2 i18n:translate="">Resources</h2> | ||||||
|         <table class="listing"> |         <table class="listing"> | ||||||
|           <tr> |           <tr> | ||||||
|             <th i18n:translate="">Title</th> |             <th i18n:translate="">Title</th> | ||||||
|  |  | ||||||
|  | @ -15,8 +15,6 @@ | ||||||
| 
 | 
 | ||||||
|   <resource name="loops.css" file="loops.css" /> |   <resource name="loops.css" file="loops.css" /> | ||||||
|   <resource name="loops.js" file="loops.js" /> |   <resource name="loops.js" file="loops.js" /> | ||||||
|   <resource name="loops1.js" file="loops1.js" /> |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
|   <!-- new style pages --> |   <!-- new style pages --> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| # | # | ||||||
| #  Copyright (c) 2006 Helmut Merz helmutm@cy55.de | #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||||
| # | # | ||||||
| #  This program is free software; you can redistribute it and/or modify | #  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 | #  it under the terms of the GNU General Public License as published by | ||||||
|  | @ -29,8 +29,8 @@ from zope.cachedescriptors.property import Lazy | ||||||
| from zope.security.proxy import removeSecurityProxy | from zope.security.proxy import removeSecurityProxy | ||||||
| from cStringIO import StringIO | from cStringIO import StringIO | ||||||
| 
 | 
 | ||||||
| from loops.external import IExternalContentSource | from loops.external.external import IExternalContentSource | ||||||
| from loops.external import ILoader, IExporter | from loops.external.external import ILoader, IExporter | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class NodesExportImport(object): | class NodesExportImport(object): | ||||||
|  | @ -38,7 +38,8 @@ class NodesExportImport(object): | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
|     def __init__(self, context, request): |     def __init__(self, context, request): | ||||||
|         self.context = removeSecurityProxy(context) |         #self.context = removeSecurityProxy(context) | ||||||
|  |         self.context = context | ||||||
|         self.request = request |         self.request = request | ||||||
|         self.message = u'' |         self.message = u'' | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| # | # | ||||||
| #  Copyright (c) 2007 Helmut Merz helmutm@cy55.de | #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||||
| # | # | ||||||
| #  This program is free software; you can redistribute it and/or modify | #  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 | #  it under the terms of the GNU General Public License as published by | ||||||
|  | @ -30,11 +30,9 @@ from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent | ||||||
| 
 | 
 | ||||||
| from zope.app.container.interfaces import INameChooser | from zope.app.container.interfaces import INameChooser | ||||||
| from zope.app.container.contained import NameChooser | from zope.app.container.contained import NameChooser | ||||||
| from zope.app.form.browser.textwidgets import FileWidget, TextAreaWidget |  | ||||||
| from zope.app.pagetemplate import ViewPageTemplateFile | from zope.app.pagetemplate import ViewPageTemplateFile | ||||||
| from zope.cachedescriptors.property import Lazy | from zope.cachedescriptors.property import Lazy | ||||||
| from zope.contenttype import guess_content_type | from zope.contenttype import guess_content_type | ||||||
| #from zope.formlib.form import Form, EditForm, FormFields |  | ||||||
| from zope.publisher.browser import FileUpload | from zope.publisher.browser import FileUpload | ||||||
| from zope.publisher.interfaces import BadRequest | from zope.publisher.interfaces import BadRequest | ||||||
| from zope.security.proxy import isinstance, removeSecurityProxy | from zope.security.proxy import isinstance, removeSecurityProxy | ||||||
|  | @ -81,7 +79,8 @@ class ObjectForm(NodeView): | ||||||
| 
 | 
 | ||||||
|     def closeAction(self, submit=False): |     def closeAction(self, submit=False): | ||||||
|         if self.isInnerHtml: |         if self.isInnerHtml: | ||||||
|             return 'dialogs["%s"].hide()' % self.dialog_name |             return ("closeDataWidget(%s); dialog.hide();" % | ||||||
|  |                         (submit and 'true' or 'false')) | ||||||
|         if submit: |         if submit: | ||||||
|             return "xhrSubmitPopup('dialog_form', '%s'); return false" % (self.request.URL) |             return "xhrSubmitPopup('dialog_form', '%s'); return false" % (self.request.URL) | ||||||
|         return 'window.close()' |         return 'window.close()' | ||||||
|  | @ -101,7 +100,10 @@ class ObjectForm(NodeView): | ||||||
| 
 | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def fieldRenderers(self): |     def fieldRenderers(self): | ||||||
|         return schema_macros.macros |         renderers = dict(schema_macros.macros) | ||||||
|  |         # replace HTML edit widget with Dojo Editor | ||||||
|  |         renderers['input_html'] = self.template.macros['input_html'] | ||||||
|  |         return renderers | ||||||
| 
 | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def fieldEditRenderers(self): |     def fieldEditRenderers(self): | ||||||
|  | @ -109,19 +111,17 @@ class ObjectForm(NodeView): | ||||||
| 
 | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def schema(self): |     def schema(self): | ||||||
|         #ti = self.typeInterface or Interface #IConcept |  | ||||||
|         ti = self.typeInterface or IConceptSchema |         ti = self.typeInterface or IConceptSchema | ||||||
|         schemaFactory = component.getAdapter(self.adapted, ISchemaFactory) |         schemaFactory = component.getAdapter(self.adapted, ISchemaFactory) | ||||||
|         return schemaFactory(ti, manager=self, request=self.request) |         return schemaFactory(ti, manager=self, request=self.request) | ||||||
| 
 | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def fields(self): |     def fields(self): | ||||||
|         return self.schema.fields |         return [f for f in self.schema.fields if not f.readonly] | ||||||
| 
 | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def data(self): |     def data(self): | ||||||
|         instance = self.instance |         instance = self.instance | ||||||
|         instance.template = self.schema |  | ||||||
|         data = instance.applyTemplate(mode='edit') |         data = instance.applyTemplate(mode='edit') | ||||||
|         form = self.request.form |         form = self.request.form | ||||||
|         for k, v in data.items(): |         for k, v in data.items(): | ||||||
|  | @ -132,11 +132,15 @@ class ObjectForm(NodeView): | ||||||
| 
 | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def instance(self): |     def instance(self): | ||||||
|         return IInstance(self.adapted) |         instance = IInstance(self.adapted) | ||||||
|  |         instance.template = self.schema | ||||||
|  |         instance.view = self | ||||||
|  |         return instance | ||||||
| 
 | 
 | ||||||
|     def __call__(self): |     def __call__(self): | ||||||
|         if self.isInnerHtml: |         if self.isInnerHtml: | ||||||
|             response = self.request.response |             response = self.request.response | ||||||
|  |             #response.setHeader('Content-Type', 'text/html; charset=UTF-8') | ||||||
|             response.setHeader('Expires', 'Sat, 1 Jan 2000 00:00:00 GMT') |             response.setHeader('Expires', 'Sat, 1 Jan 2000 00:00:00 GMT') | ||||||
|             response.setHeader('Pragma', 'no-cache') |             response.setHeader('Pragma', 'no-cache') | ||||||
|             return innerHtml(self) |             return innerHtml(self) | ||||||
|  | @ -240,11 +244,16 @@ class CreateObjectForm(ObjectForm): | ||||||
| 
 | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def adapted(self): |     def adapted(self): | ||||||
|         return self.typeInterface(Resource()) |         ad = self.typeInterface(Resource()) | ||||||
|  |         ad.storageName = 'unknown'  # hack for file objects: don't try to retrieve data | ||||||
|  |         return (ad) | ||||||
| 
 | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def instance(self): |     def instance(self): | ||||||
|         return IInstance(Resource()) |         instance = IInstance(self.adapted) | ||||||
|  |         instance.template = self.schema | ||||||
|  |         instance.view = self | ||||||
|  |         return instance | ||||||
| 
 | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def typeInterface(self): |     def typeInterface(self): | ||||||
|  | @ -278,8 +287,9 @@ class CreateObjectPopup(CreateObjectForm): | ||||||
|         cm = self.controller.macros |         cm = self.controller.macros | ||||||
|         cm.register('css', identifier='popup.css', resourceName='popup.css', |         cm.register('css', identifier='popup.css', resourceName='popup.css', | ||||||
|                     media='all', position=4) |                     media='all', position=4) | ||||||
|         jsCall = ('dojo.require("dojo.widget.Dialog");' |         jsCall = ('dojo.require("dojo.parser");' | ||||||
|                   'dojo.require("dojo.widget.ComboBox");') |                   'dojo.require("dijit.form.FilteringSelect");' | ||||||
|  |                   'dojo.require("dojox.data.QueryReadStore");') | ||||||
|         cm.register('js-execute', jsCall, jsCall=jsCall) |         cm.register('js-execute', jsCall, jsCall=jsCall) | ||||||
|         return True |         return True | ||||||
| 
 | 
 | ||||||
|  | @ -298,14 +308,19 @@ class CreateConceptForm(CreateObjectForm): | ||||||
| 
 | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def adapted(self): |     def adapted(self): | ||||||
|  |         c = Concept() | ||||||
|         ti = self.typeInterface |         ti = self.typeInterface | ||||||
|         if ti is None: |         if ti is None: | ||||||
|             return Concept() |             return c | ||||||
|         return ti(Concept()) |         ad = ti(c) | ||||||
|  |         return ad | ||||||
| 
 | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def instance(self): |     def instance(self): | ||||||
|         return IInstance(Concept()) |         instance = IInstance(self.adapted) | ||||||
|  |         instance.template = self.schema | ||||||
|  |         instance.view = self | ||||||
|  |         return instance | ||||||
| 
 | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def typeInterface(self): |     def typeInterface(self): | ||||||
|  | @ -355,9 +370,6 @@ class EditObject(FormController, I18NView): | ||||||
|     prefix = 'form.' |     prefix = 'form.' | ||||||
|     conceptPrefix = 'assignments.' |     conceptPrefix = 'assignments.' | ||||||
| 
 | 
 | ||||||
|     old = None |  | ||||||
|     selected = None |  | ||||||
| 
 |  | ||||||
|     @Lazy |     @Lazy | ||||||
|     def adapted(self): |     def adapted(self): | ||||||
|         return adapted(self.object, self.languageInfoForUpdate) |         return adapted(self.object, self.languageInfoForUpdate) | ||||||
|  | @ -377,7 +389,10 @@ class EditObject(FormController, I18NView): | ||||||
| 
 | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def instance(self): |     def instance(self): | ||||||
|         return component.getAdapter(self.adapted, IInstance, name='editor') |         instance = component.getAdapter(self.adapted, IInstance, name='editor') | ||||||
|  |         instance.template = self.schema | ||||||
|  |         instance.view = self.view | ||||||
|  |         return instance | ||||||
| 
 | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def loopsRoot(self): |     def loopsRoot(self): | ||||||
|  | @ -406,14 +421,17 @@ class EditObject(FormController, I18NView): | ||||||
|         obj = self.object |         obj = self.object | ||||||
|         form = self.request.form |         form = self.request.form | ||||||
|         instance = self.instance |         instance = self.instance | ||||||
|         instance.template = self.schema |         #instance.template = self.schema | ||||||
|         formState = instance.applyTemplate(data=form, fieldHandlers=self.fieldHandlers) |         formState = instance.applyTemplate(data=form, fieldHandlers=self.fieldHandlers) | ||||||
|  |         self.selected = [] | ||||||
|  |         self.old = [] | ||||||
|         for k in form.keys(): |         for k in form.keys(): | ||||||
|             if k.startswith(self.prefix): |             if k.startswith(self.prefix): | ||||||
|                 fn = k[len(self.prefix):] |                 fn = k[len(self.prefix):] | ||||||
|                 value = form[k] |                 value = form[k] | ||||||
|                 if fn.startswith(self.conceptPrefix) and value: |                 if fn.startswith(self.conceptPrefix) and value: | ||||||
|                     self.collectConcepts(fn[len(self.conceptPrefix):], value) |                     self.collectConcepts(fn[len(self.conceptPrefix):], value) | ||||||
|  |         self.collectAutoConcepts() | ||||||
|         if self.old or self.selected: |         if self.old or self.selected: | ||||||
|             self.assignConcepts(obj) |             self.assignConcepts(obj) | ||||||
|         notify(ObjectModifiedEvent(obj)) |         notify(ObjectModifiedEvent(obj)) | ||||||
|  | @ -441,14 +459,15 @@ class EditObject(FormController, I18NView): | ||||||
|     def collectConcepts(self, fieldName, value): |     def collectConcepts(self, fieldName, value): | ||||||
|         if self.old is None: |         if self.old is None: | ||||||
|             self.old = [] |             self.old = [] | ||||||
|         if self.selected is None: |  | ||||||
|             self.selected = [] |  | ||||||
|         for v in value: |         for v in value: | ||||||
|             if fieldName == 'old': |             if fieldName == 'old': | ||||||
|                 self.old.append(v) |                 self.old.append(v) | ||||||
|             elif fieldName == 'selected' and v not in self.selected: |             elif fieldName == 'selected' and v not in self.selected: | ||||||
|                 self.selected.append(v) |                 self.selected.append(v) | ||||||
| 
 | 
 | ||||||
|  |     def collectAutoConcepts(self): | ||||||
|  |         pass | ||||||
|  | 
 | ||||||
|     def assignConcepts(self, obj): |     def assignConcepts(self, obj): | ||||||
|         for v in self.old: |         for v in self.old: | ||||||
|             if v not in self.selected: |             if v not in self.selected: | ||||||
|  | @ -532,6 +551,10 @@ class CreateObject(EditObject): | ||||||
| 
 | 
 | ||||||
| class EditConcept(EditObject): | class EditConcept(EditObject): | ||||||
| 
 | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def typeInterface(self): | ||||||
|  |         return IType(self.object).typeInterface or IConceptSchema | ||||||
|  | 
 | ||||||
|     def getConceptRelations(self, obj, predicates, concept): |     def getConceptRelations(self, obj, predicates, concept): | ||||||
|         return obj.getParentRelations(predicates=predicates, parent=concept) |         return obj.getParentRelations(predicates=predicates, parent=concept) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,7 +2,8 @@ | ||||||
|      $Id$ --> |      $Id$ --> | ||||||
| 
 | 
 | ||||||
| <metal:block define-macro="edit" i18n:domain="loops"> | <metal:block define-macro="edit" i18n:domain="loops"> | ||||||
|     <form method="post" enctype="multipart/form-data" id="dialog_form" |     <form method="post" enctype="multipart/form-data" | ||||||
|  |           id="dialog_form" class="dialog" | ||||||
|           tal:define="langInfo view/languageInfo; |           tal:define="langInfo view/languageInfo; | ||||||
|                       languages langInfo/availableLanguages; |                       languages langInfo/availableLanguages; | ||||||
|                       language langInfo/language; |                       language langInfo/language; | ||||||
|  | @ -15,8 +16,8 @@ | ||||||
|       <table cellpadding="3" class="form"> |       <table cellpadding="3" class="form"> | ||||||
|         <tbody> |         <tbody> | ||||||
|           <tr> |           <tr> | ||||||
|             <th colspan="5" |             <th colspan="5" class="headline" | ||||||
|                 tal:attributes="colspan python: useI18N and 4 or 5"><br /> |                 tal:attributes="colspan python: useI18N and 4 or 5"> | ||||||
|               <span tal:replace="view/title" |               <span tal:replace="view/title" | ||||||
|                     i18n:translate="">Edit Information Object</span> |                     i18n:translate="">Edit Information Object</span> | ||||||
|             </th> |             </th> | ||||||
|  | @ -44,7 +45,7 @@ | ||||||
|         <tbody> |         <tbody> | ||||||
|           <tr> |           <tr> | ||||||
|             <td colspan="5" class="headline" |             <td colspan="5" class="headline" | ||||||
|                 i18n:translate="">Concept Assignments</td> |                 i18n:translate="">Assign Parent Concepts</td> | ||||||
|           </tr> |           </tr> | ||||||
|           <tr metal:use-macro="view/template/macros/assignments" /> |           <tr metal:use-macro="view/template/macros/assignments" /> | ||||||
|           <tr metal:use-macro="view/template/macros/search_concepts" /> |           <tr metal:use-macro="view/template/macros/search_concepts" /> | ||||||
|  | @ -61,7 +62,8 @@ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| <metal:block define-macro="create" i18n:domain="loops"> | <metal:block define-macro="create" i18n:domain="loops"> | ||||||
|     <form method="post" enctype="multipart/form-data" id="dialog_form" |     <form method="post" enctype="multipart/form-data" | ||||||
|  |           id="dialog_form" class="dialog" | ||||||
|           tal:define="qualifier request/qualifier | string:resource; |           tal:define="qualifier request/qualifier | string:resource; | ||||||
|                       innerForm request/inner_form | string:inner_form.html; |                       innerForm request/inner_form | string:inner_form.html; | ||||||
|                       typeToken python: request.get('form.type') |                       typeToken python: request.get('form.type') | ||||||
|  | @ -70,8 +72,8 @@ | ||||||
|       <input type="hidden" name="form.action" value="create" |       <input type="hidden" name="form.action" value="create" | ||||||
|              tal:attributes="value view/form_action" /> |              tal:attributes="value view/form_action" /> | ||||||
|       <table cellpadding="3" class="form"> |       <table cellpadding="3" class="form"> | ||||||
|         <tbody><tr><th colspan="5"><br /> |         <tbody><tr><th colspan="5" class="headline"> | ||||||
|               <span tal:replace="view/title" |               <span tal:content="view/title" | ||||||
|                     i18n:translate="">Create Information Object</span> |                     i18n:translate="">Create Information Object</span> | ||||||
|               <select name="form.type" id="form.type" |               <select name="form.type" id="form.type" | ||||||
|                       tal:condition="not:fixedType" |                       tal:condition="not:fixedType" | ||||||
|  | @ -154,9 +156,7 @@ | ||||||
|     <td><label for="concept.search.type"><span i18n:translate="">Type</span>:</label></td> |     <td><label for="concept.search.type"><span i18n:translate="">Type</span>:</label></td> | ||||||
|     <td> |     <td> | ||||||
|       <select id="concept.search.type" |       <select id="concept.search.type" | ||||||
|               tal:attributes="onChange |               onChange="setConceptTypeForComboBox('concept.search.type', 'concept.search.text')"> | ||||||
|                 string:setConceptTypeForComboBox( |  | ||||||
|                     'concept.search.type', 'concept.search.text')"> |  | ||||||
|         <tal:types repeat="type view/conceptTypesForSearch"> |         <tal:types repeat="type view/conceptTypesForSearch"> | ||||||
|           <option value="loops:*" |           <option value="loops:*" | ||||||
|                   i18n:translate="" |                   i18n:translate="" | ||||||
|  | @ -172,19 +172,12 @@ | ||||||
|       <input type="hidden" |       <input type="hidden" | ||||||
|              id="concept.search.predicate" |              id="concept.search.predicate" | ||||||
|              tal:attributes="value view/defaultPredicateUid" /> |              tal:attributes="value view/defaultPredicateUid" /> | ||||||
|       <input dojoType="comboBox" mode="remote" autoComplete="False" |  | ||||||
|              name="concept.search.text" id="concept.search.text" |  | ||||||
|              tal:attributes="dataUrl |  | ||||||
|                 string:listConceptsForComboBox.js?searchString=%{searchString}&searchType=" /> |  | ||||||
|       <tal:dojo1 condition="nothing"> |  | ||||||
|       <div dojoType="dojox.data.QueryReadStore" jsId="conceptSearch" |       <div dojoType="dojox.data.QueryReadStore" jsId="conceptSearch" | ||||||
|              tal:attributes="url |            url="listConceptsForComboBox.js?searchType=" > | ||||||
|                   string:listConceptsForComboBox.js?searchString=%{searchString}&searchType=" > |  | ||||||
|       </div> |       </div> | ||||||
|         <input dojoType="dijit.form.ComboBox" store="conceptSearch" |       <input dojoType="dijit.form.FilteringSelect" store="conceptSearch" | ||||||
|                autoComplete="False" |              autoComplete="False" labelAttr="label" | ||||||
|              name="concept.search.text" id="concept.search.text" /> |              name="concept.search.text" id="concept.search.text" /> | ||||||
|       </tal:dojo1> |  | ||||||
|     </td> |     </td> | ||||||
|     <td> |     <td> | ||||||
|       <input type="button" value="Select" |       <input type="button" value="Select" | ||||||
|  | @ -222,7 +215,7 @@ | ||||||
| </metal:versioning> | </metal:versioning> | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| <tr metal:define-macro="buttons" i18n:domain=""> | <tr metal:define-macro="buttons" i18n:domain="" class="buttons"> | ||||||
|             <td colspan="5"> |             <td colspan="5"> | ||||||
|               <input value="Save" type="submit" |               <input value="Save" type="submit" | ||||||
|                      i18n:attributes="value" |                      i18n:attributes="value" | ||||||
|  | @ -232,3 +225,19 @@ | ||||||
|                      tal:attributes="onClick view/closeAction"> |                      tal:attributes="onClick view/closeAction"> | ||||||
|             </td> |             </td> | ||||||
| </tr> | </tr> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | <!-- overridden field renderers --> | ||||||
|  | 
 | ||||||
|  | <metal:html define-macro="input_html"> | ||||||
|  |     <textarea name="field" rows="3" style="width: 450px" | ||||||
|  |            dojoType="dijit.Editor" | ||||||
|  |            tal:define="width field/width|nothing; | ||||||
|  |                        height field/height|python:3" | ||||||
|  |            tal:attributes="name name; id name; | ||||||
|  |                            rows python: height or 3; | ||||||
|  |                            style python: | ||||||
|  |                                 'width: %s' % (width and str(width)+'px' or '450px');" | ||||||
|  |            tal:content="data/?name|string:"> | ||||||
|  |     </textarea> | ||||||
|  | </metal:html> | ||||||
|  |  | ||||||
|  | @ -11,20 +11,28 @@ a[href]:hover { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pre { | pre { | ||||||
|  |     background-color: #f4f4f4; | ||||||
|     overflow: scroll; |     overflow: scroll; | ||||||
|     max-height: 35em; |     max-height: 35em; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | ul, p { | ||||||
|  |     margin-top: 0.4em; | ||||||
|  |     margin-bottom: 0.5em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| table.listing td { | table.listing td { | ||||||
|     white-space: normal; |     white-space: normal; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .box div.body div.even { | fieldset.box { | ||||||
|     background-color: #f4f4f4; |     margin: 1em 0 0.5em 0; | ||||||
|  |     padding: 0.5em; | ||||||
|  |     border: 1px solid #ccc; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .box { | fieldset.box td { | ||||||
|     margin: 12px; |     padding: 0.2em 0.2em 0.2em 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #body { | #body { | ||||||
|  | @ -37,45 +45,77 @@ table.listing td { | ||||||
|     top: 1em; |     top: 1em; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /*.content-1 h1 { */ | .top image { | ||||||
| h1 { |     margin-top: -1px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .content-1 h1, h1 { | ||||||
|     font-size: 160%; |     font-size: 160%; | ||||||
|     font-weight: bold; |     margin-top: 0.8em; | ||||||
|  |     padding-bottom: 3px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .content-2 h1, h2 { | .content-2 h1, .content-1 h2, h2 { | ||||||
|     font-size: 140%; |     font-size: 140%; | ||||||
|  |     font-weight: normal; | ||||||
|  |     margin-top: 0.7em; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .content-3 h1, .content-2 h2, h3 { | .content-3 h1, .content-2 h2, .content-1 h3, h3 { | ||||||
|     font-size: 130%; |     font-size: 130%; | ||||||
|     font-weight: bold; |     font-weight: normal; | ||||||
|  |     margin-top: 0.7em; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .content-4 h1, .content-3 h2, .content-2 h3, .content-1 h4 { | .content-4 h1, .content-3 h2, .content-2 h3, .content-1 h4, h4 { | ||||||
|     font-size: 120%; |     font-size: 120%; | ||||||
|  |     font-weight: normal; | ||||||
|  |     margin-top: 0.7em; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .content-5 h1, .content-4 h2, .content-3 h3, content-2 h4 { | .content-5 h1, .content-4 h2, .content-3 h3, content-2 h4 { | ||||||
|     font-size: 100%; |     font-size: 100%; | ||||||
|     border: none; |     border: none; | ||||||
| } |     margin-top: 0.7em; | ||||||
| 
 |  | ||||||
| .subcolumn { |  | ||||||
|     display: inline; |  | ||||||
|     float: left; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .box { | .box { | ||||||
|     margin: 5px; |     margin: 12px; | ||||||
|  |     /*margin: 5px;*/ | ||||||
|     padding: 6px; |     padding: 6px; | ||||||
|     padding-top: 0; |     padding-top: 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | div.box { | ||||||
|  |     margin: 12px 12px 8px 12px; | ||||||
|  |     border: 1px solid #ccc; | ||||||
|  |     border-bottom: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | div.box h4 { | ||||||
|  |     font: 110% Verdana, Tahoma, Arial, Helvetica, sans-serif; | ||||||
|  |     color: #000040; | ||||||
|  |     border: none; | ||||||
|  |     border-bottom: 1px solid #ccc; | ||||||
|  |     padding: 4px; | ||||||
|  |     padding-top: 1px; | ||||||
|  |     padding-bottom: 3px; | ||||||
|  |     background-color: #ddd; | ||||||
|  |     height: auto; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .box h1, .box h2, .box h3 { | .box h1, .box h2, .box h3 { | ||||||
|     border-bottom: None; |     border-bottom: None; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .box div.body div.even { | ||||||
|  |     background-color: #f4f4f4; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .box div.body div { | ||||||
|  |     padding: 0.2em 0.2em 0.2em 0.3em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| div.menu-1, div.menu-2 { | div.menu-1, div.menu-2 { | ||||||
|     border-top: 1px solid #eeeeee; |     border-top: 1px solid #eeeeee; | ||||||
|     font-weight: bold; |     font-weight: bold; | ||||||
|  | @ -83,7 +123,7 @@ div.menu-1, div.menu-2 { | ||||||
| 
 | 
 | ||||||
| .box div.body div.menu-3 { | .box div.body div.menu-3 { | ||||||
|     border-top: none; |     border-top: none; | ||||||
|     padding-left: 1.5em; |     padding: 0.1em 0 0.2em 1.5em; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .box div.body div.menu-4 { | .box div.body div.menu-4 { | ||||||
|  | @ -91,6 +131,19 @@ div.menu-1, div.menu-2 { | ||||||
|     font-size: 90% |     font-size: 90% | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .subcolumn { | ||||||
|  |     display: inline; | ||||||
|  |     float: left; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .footer { | ||||||
|  |     text-align: center; | ||||||
|  |     border-top: 1px solid #ccc; | ||||||
|  |     border-bottom: none; | ||||||
|  |     margin-top: 12px; | ||||||
|  |     padding-top: 6px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .flow-left { | .flow-left { | ||||||
|     float: left; |     float: left; | ||||||
| } | } | ||||||
|  | @ -128,6 +181,45 @@ img.notselected { | ||||||
|     margin: 1em 0 0.5em 0; |     margin: 1em 0 0.5em 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .button { | ||||||
|  |     margin: 1em 0 1em 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .button a, .button a:visited { | ||||||
|  |     padding: 2px 4px 2px 4px; | ||||||
|  |     background-color: #e8e8e8; | ||||||
|  |     text-decoration: None; | ||||||
|  |     color: Black; | ||||||
|  |     border-width: 2px; | ||||||
|  |     border-style: solid; | ||||||
|  |     border-color: #f4f4f4 #989898 #989898 #f4f4f4; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .button a:active { | ||||||
|  |     border-color: #989898 #f4f4f4 #f4f4f4 #989898; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | table.listing { | ||||||
|  |     margin: 1px; | ||||||
|  |     margin-top: 6px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | table.listing th { | ||||||
|  |     font-family: Verdana, Tahoma, Arial, Helvetica, sans-serif; | ||||||
|  |     color: #000040; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .itemViews { | ||||||
|  |     border-bottom-width: 2px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .dialog .headline { | ||||||
|  |     font-size: 120%; | ||||||
|  |     font-weight: bold; | ||||||
|  |     text-align: center; | ||||||
|  |     padding: 1em 0 1em 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| /* search stuff */ | /* search stuff */ | ||||||
| 
 | 
 | ||||||
|  | @ -139,34 +231,70 @@ img.notselected { | ||||||
|     font-weight: bold; |     font-weight: bold; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* dojo stuff */ | /* blog */ | ||||||
| 
 | 
 | ||||||
| /*.dojoComboBox { | .blog .description { | ||||||
|     width: 200px; |     font-size: 90%; | ||||||
| }*/ |     color: #666666; | ||||||
| 
 |  | ||||||
| .dojoDialog { |  | ||||||
|     background: #eee; |  | ||||||
|     border: 1px solid #999; |  | ||||||
|     -moz-border-radius: 5px; |  | ||||||
|     padding: 4px; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .dojoDialog th { | .blogpost .description { | ||||||
|  |     font-weight: bold; | ||||||
|  |     font-size: 90%; | ||||||
|  |     color: #666666; | ||||||
|  |     padding-top: 0.4em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .blog .info, .blogpost .info { | ||||||
|  |     font-style: italic; | ||||||
|  |     font-size: 90%; | ||||||
|  |     color: #666666; | ||||||
|  |     padding-top: 0.4em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* dojo stuff */ | ||||||
|  | 
 | ||||||
|  | .dijitDialog { | ||||||
|  |     background-color: #aaaaaaa; | ||||||
|  |     border: 1px solid #999; | ||||||
|  |     padding: 5px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .dijitDialogPaneContent { | ||||||
|  |     background-color: #aaaaaaa; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .dijitDialog th { | ||||||
|     font-size: 120%; |     font-size: 120%; | ||||||
|  |     font-weight: bold; | ||||||
|  |     text-align: center; | ||||||
|     padding: 0 5px 8px 5px; |     padding: 0 5px 8px 5px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .dojoDialog .headline { | .dijitDialog td { | ||||||
|  |     padding: 2px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .dijitDialog .headline { | ||||||
|     font-weight: bold; |     font-weight: bold; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .dojoDialog input.text { | .dijitDialog input.text { | ||||||
|     width: 100%; |     width: 100%; | ||||||
|     margin-right: 10px; |     margin-right: 10px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .dojoDialog input.submit { | .dijitDialog input.submit { | ||||||
|     font-weight: bold; |     font-weight: bold; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .dijitDialogUnderlay { | ||||||
|  |     background-color: White; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | div.RichTextEditable { | ||||||
|  |     border-top: 2px solid grey; | ||||||
|  |     border-left: 2px solid grey; | ||||||
|  |     border-right: 2px solid #eeeeee; | ||||||
|  |     border-bottom: 2px solid #eeeeee; | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										216
									
								
								browser/loops.js
									
										
									
									
									
								
							
							
						
						
									
										216
									
								
								browser/loops.js
									
										
									
									
									
								
							|  | @ -13,144 +13,122 @@ function focusOpener() { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | function validate(nodeName, required) { | ||||||
|  |     // (work in progress) - may be used for onBlur event handler
 | ||||||
|  |     var w = dojo.byId(nodeName); | ||||||
|  |     if (required && w.value == '') { | ||||||
|  |         w.setAttribute('style','background-color: #ffff00'); | ||||||
|  |         w.focus(); | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function destroyWidgets(node) { | ||||||
|  |     dojo.forEach(dojo.query('[widgetId]', node), function(n) { | ||||||
|  |         w = dijit.byNode(n); | ||||||
|  |         if (w != undefined) { | ||||||
|  |             w.destroyRecursive(); | ||||||
|  |         } | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function replaceNode(html, nodeName) { | ||||||
|  |     var newNode = document.createElement('div'); | ||||||
|  |     newNode.innerHTML = html; | ||||||
|  |     if (nodeName == undefined) { | ||||||
|  |         nodeName = newNode.firstChild.getAttribute('id'); | ||||||
|  |     } | ||||||
|  |     if (nodeName != undefined) { | ||||||
|  |         newNode.id = nodeName; | ||||||
|  |         var node = dojo.byId(nodeName); | ||||||
|  |         destroyWidgets(node); | ||||||
|  |         var parent = node.parentNode; | ||||||
|  |         parent.replaceChild(newNode, node); | ||||||
|  |         dojo.parser.parse(newNode); | ||||||
|  |     } else { | ||||||
|  |         window.location.href = url; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function updateNode(url, nodeName) { | ||||||
|  |     dojo.xhrGet({ | ||||||
|  |         url: url, | ||||||
|  |         handleAs: 'text', | ||||||
|  |         load: function(response, ioArgs) { | ||||||
|  |             replaceNode(response, nodeName); | ||||||
|  |         } | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| function replaceFieldsNode(targetId, typeId, url) { | function replaceFieldsNode(targetId, typeId, url) { | ||||||
|     token = dojo.byId(typeId).value; |     token = dojo.byId(typeId).value; | ||||||
|     uri = url + '?form.type=' + token; |     uri = url + '?form.type=' + token; | ||||||
|     dojo.io.updateNode(targetId, uri); |     updateNode(uri, 'form.fields'); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function replaceFieldsNodeForLanguage(targetId, langId, url) { | function replaceFieldsNodeForLanguage(targetId, langId, url) { | ||||||
|     lang = dojo.byId(langId).value; |     lang = dojo.byId(langId).value; | ||||||
|     uri = url + '?loops.language=' + lang; |     uri = url + '?loops.language=' + lang; | ||||||
|     dojo.io.updateNode(targetId, uri); |     updateNode(uri, 'form.fields'); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function submitReplacing(targetId, formId, actionUrl) { | function submitReplacing(targetId, formId, url) { | ||||||
|     dojo.io.updateNode(targetId, { |     dojo.xhrPost({ | ||||||
|             url: actionUrl, |         url: url, | ||||||
|             formNode: dojo.byId(formId), |         form: dojo.byId(formId), | ||||||
|             method: 'post' |  | ||||||
|         }); |  | ||||||
|     return false; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function xhrSubmitPopup(formId, actionUrl) { |  | ||||||
|     dojo.io.bind({ |  | ||||||
|         url: actionUrl, |  | ||||||
|         formNode: dojo.byId(formId), |  | ||||||
|         method: 'post', |  | ||||||
|         mimetype: "text/html", |         mimetype: "text/html", | ||||||
|         load: function(t, d, e) { |         load: function(response, ioArgs) { | ||||||
|  |             replaceNode(response, targetId); | ||||||
|  |         } | ||||||
|  |     }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function xhrSubmitPopup(formId, url) { | ||||||
|  |     dojo.xhrPost({ | ||||||
|  |         url: url, | ||||||
|  |         form: dojo.byId(formId), | ||||||
|  |         mimetype: "text/html", | ||||||
|  |         load: function(response, ioArgs) { | ||||||
|             window.close(); |             window.close(); | ||||||
|         } |         } | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function submitReplacingOrReloading(targetId, formId, actionUrl) { |  | ||||||
|     node = dojo.byId(targetId); |  | ||||||
|     var args = { |  | ||||||
|         url: actionUrl, |  | ||||||
|         formNode: dojo.byId(formId), |  | ||||||
|         method: 'post', |  | ||||||
|         mimetype: "text/html" |  | ||||||
|     }; |  | ||||||
|     args.load = function (t, d, e) { |  | ||||||
|         if (d.length < 10) { |  | ||||||
|             document.location.reload(false); |  | ||||||
|         } else { |  | ||||||
|             while (node.firstChild) { |  | ||||||
|                 dojo.dom.destroyNode(node.firstChild); |  | ||||||
|             } |  | ||||||
|             node.innerHTML = d; |  | ||||||
|         } |  | ||||||
|     }; |  | ||||||
|     dojo.io.bind(args); |  | ||||||
|     return false; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function inlineEdit(id, saveUrl) { |  | ||||||
|     var iconNode = dojo.byId('inlineedit_icon'); |  | ||||||
|     iconNode.style.visibility = 'hidden'; |  | ||||||
|     editor = dojo.widget.createWidget('Editor', |  | ||||||
|         {items: ['save', '|', 'formatblock', '|', |  | ||||||
|                  'insertunorderedlist', 'insertorderedlist', '|', |  | ||||||
|                  'bold', 'italic', '|', 'createLink', 'insertimage'], |  | ||||||
|          saveUrl: saveUrl, |  | ||||||
|          //closeOnSave: true,
 |  | ||||||
|          htmlEditing: true |  | ||||||
|         //onClose: function() {
 |  | ||||||
|         /* onSave: function() { |  | ||||||
|             this.disableToolbar(true); |  | ||||||
|             iconNode.style.visibility = 'visible'; |  | ||||||
|             //window.location.reload();
 |  | ||||||
|          }*/ |  | ||||||
|     }, dojo.byId(id)); |  | ||||||
|     editor._save = function (e) { |  | ||||||
|             if (!this._richText.isClosed) { |  | ||||||
|                 if (this.saveUrl.length) { |  | ||||||
|                     var content = {}; |  | ||||||
|                     this._richText.contentFilters = []; |  | ||||||
|                     content[this.saveArgName] = this.getHtml(); |  | ||||||
|                     content['version'] = 'this'; |  | ||||||
|                     dojo.io.bind({method:this.saveMethod, |  | ||||||
|                                   url:this.saveUrl, |  | ||||||
|                                   content:content, |  | ||||||
|                                   handle:function(type, data, ti, kwargs) { |  | ||||||
|                                     location.reload(false); |  | ||||||
|                                   } |  | ||||||
|                                  }); //alert('save');
 |  | ||||||
|                 } else { |  | ||||||
|                     dojo.debug("please set a saveUrl for the editor"); |  | ||||||
|                 } |  | ||||||
|                 if (this.closeOnSave) { |  | ||||||
|                     this._richText.close(e.getName().toLowerCase() == "save"); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|     } |  | ||||||
|     return false; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function setConceptTypeForComboBox(typeId, cbId) { | function setConceptTypeForComboBox(typeId, cbId) { | ||||||
|     var t = dojo.byId(typeId).value; |     var t = dojo.byId(typeId).value; | ||||||
|     var cb = dojo.widget.manager.getWidgetById(cbId); |     var cb = dijit.byId(cbId); | ||||||
|     var dp = cb.dataProvider; |     var dp = cb.store; | ||||||
|     var baseUrl = dp.searchUrl.split('&')[0]; |     var baseUrl = dp.url.split('?')[0]; | ||||||
|     var newUrl = baseUrl + '&searchType=' + t; |     var newUrl = baseUrl + '?searchType=' + t; | ||||||
|     dp.searchUrl = newUrl; |     dp.url = newUrl; | ||||||
|     cb.setValue(''); |     cb.setDisplayedValue(''); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var dialogs = {} | var dialog; | ||||||
|  | var dialogName | ||||||
| 
 | 
 | ||||||
| function objectDialog(dlgName, url) { | function objectDialog(dlgName, url) { | ||||||
|     dojo.require('dojo.widget.Dialog'); |     dojo.require('dijit.Dialog'); | ||||||
|     dojo.require('dojo.widget.ComboBox'); |     dojo.require('dojo.parser'); | ||||||
|     dlg = dialogs[dlgName]; |     dojo.require('dijit.form.FilteringSelect'); | ||||||
|     if (!dlg) { |     dojo.require('dojox.data.QueryReadStore'); | ||||||
|         //dlg = dojo.widget.fromScript('Dialog',
 |     if (dialogName == undefined || dialogName != dlgName) { | ||||||
|         dlg = dojo.widget.createWidget('Dialog', |         if (dialog != undefined) { | ||||||
|             {bgColor: 'white', bgOpacity: 0.5, toggle: 'fade', |             dialog.destroyRecursive(); | ||||||
|              toggleDuration: 250, |         } | ||||||
|              executeScripts: true, |         dialogName = dlgName; | ||||||
|  |         dialog = new dijit.Dialog({ | ||||||
|                     href: url |                     href: url | ||||||
|                         }, dojo.byId('dialog.' + dlgName)); |                         }, dojo.byId('dialog.' + dlgName)); | ||||||
|         dialogs[dlgName] = dlg; |  | ||||||
|     } |     } | ||||||
|     dlg.show(); |     dialog.show(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function addConceptAssignment(prefix, suffix) { | function addConceptAssignment(prefix, suffix) { | ||||||
|     dojo.require('dojo.html') |  | ||||||
|     node = dojo.byId('form.' + suffix); |     node = dojo.byId('form.' + suffix); | ||||||
|     els = document.forms[0].elements; |     widget = dijit.byId(prefix + '.search.text'); | ||||||
|     for (var i=0; i<els.length; i++) { //getElementsByName does not work here in IE
 |     cToken = widget.getValue(); | ||||||
|         el = els[i]; |     title = widget.getDisplayedValue(); | ||||||
|         if (el.name == prefix + '.search.text_selected') { |  | ||||||
|             cToken = el.value; |  | ||||||
|         } else if (el.name == prefix + '.search.text') { |  | ||||||
|             title = el.value; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     if (cToken.length == 0) { |     if (cToken.length == 0) { | ||||||
|         alert('Please select a concept!'); |         alert('Please select a concept!'); | ||||||
|         return false; |         return false; | ||||||
|  | @ -165,3 +143,23 @@ function addConceptAssignment(prefix, suffix) { | ||||||
|     node.appendChild(tr); |     node.appendChild(tr); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | function closeDataWidget(save) { | ||||||
|  |     var widget = dijit.byId('data'); | ||||||
|  |     if (widget != undefined && save) { | ||||||
|  |         value = widget.getValue(); | ||||||
|  |         //widget.close(save);
 | ||||||
|  |         form = dojo.byId('dialog_form'); | ||||||
|  |         var ta = document.createElement('textarea'); | ||||||
|  |         ta.name = 'data'; | ||||||
|  |         ta.value = value; | ||||||
|  |         form.appendChild(ta); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var editor; | ||||||
|  | 
 | ||||||
|  | function inlineEdit(id) { | ||||||
|  |     if (editor == undefined) { | ||||||
|  |         editor = new dijit.Editor({}, dojo.byId(id)); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -1,148 +0,0 @@ | ||||||
| /* $Id: loops.js 1965 2007-08-27 17:33:07Z helmutm $ */ |  | ||||||
| 
 |  | ||||||
| function openEditWindow(url) { |  | ||||||
|     zmi = window.open(url, 'zmi'); |  | ||||||
|     zmi.focus(); |  | ||||||
|     return false; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function focusOpener() { |  | ||||||
|     if (typeof(opener) != 'undefined' && opener != null) { |  | ||||||
|         opener.location.reload(); |  | ||||||
|         opener.focus(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function replaceFieldsNode(targetId, typeId, url) { |  | ||||||
|     token = dojo.byId(typeId).value; |  | ||||||
|     uri = url + '?form.type=' + token; |  | ||||||
|     dojo.io.updateNode(targetId, uri); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function submitReplacing(targetId, formId, actionUrl) { |  | ||||||
|     dojo.io.updateNode(targetId, { |  | ||||||
|             url: actionUrl, |  | ||||||
|             formNode: dojo.byId(formId), |  | ||||||
|             method: 'post' |  | ||||||
|         }); |  | ||||||
|     return false; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function submitReplacingOrReloading(targetId, formId, actionUrl) { |  | ||||||
|     node = dojo.byId(targetId); |  | ||||||
|     var args = { |  | ||||||
|         url: actionUrl, |  | ||||||
|         formNode: dojo.byId(formId), |  | ||||||
|         method: 'post', |  | ||||||
|         mimetype: "text/html" |  | ||||||
|     }; |  | ||||||
|     args.load = function (t, d, e) { |  | ||||||
|         if (d.length < 10) { |  | ||||||
|             document.location.reload(false); |  | ||||||
|         } else { |  | ||||||
|             while (node.firstChild) { |  | ||||||
|                 dojo.dom.destroyNode(node.firstChild); |  | ||||||
|             } |  | ||||||
|             node.innerHTML = d; |  | ||||||
|         } |  | ||||||
|     }; |  | ||||||
|     dojo.io.bind(args); |  | ||||||
|     return false; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function inlineEdit(id, saveUrl) { |  | ||||||
|     var iconNode = dojo.byId('inlineedit_icon'); |  | ||||||
|     iconNode.style.visibility = 'hidden'; |  | ||||||
|     editor = dojo.widget.createWidget('Editor', |  | ||||||
|         {items: ['save', '|', 'formatblock', '|', |  | ||||||
|                  'insertunorderedlist', 'insertorderedlist', '|', |  | ||||||
|                  'bold', 'italic', '|', 'createLink', 'insertimage'], |  | ||||||
|          saveUrl: saveUrl, |  | ||||||
|          //closeOnSave: true,
 |  | ||||||
|          htmlEditing: true |  | ||||||
|         //onClose: function() {
 |  | ||||||
|         /* onSave: function() { |  | ||||||
|             this.disableToolbar(true); |  | ||||||
|             iconNode.style.visibility = 'visible'; |  | ||||||
|             //window.location.reload();
 |  | ||||||
|          }*/ |  | ||||||
|     }, dojo.byId(id)); |  | ||||||
|     editor._save = function (e) { |  | ||||||
|             if (!this._richText.isClosed) { |  | ||||||
|                 if (this.saveUrl.length) { |  | ||||||
|                     var content = {}; |  | ||||||
|                     this._richText.contentFilters = []; |  | ||||||
|                     content[this.saveArgName] = this.getHtml(); |  | ||||||
|                     content['version'] = 'this'; |  | ||||||
|                     dojo.io.bind({method:this.saveMethod, |  | ||||||
|                                   url:this.saveUrl, |  | ||||||
|                                   content:content, |  | ||||||
|                                   handle:function(type, data, ti, kwargs) { |  | ||||||
|                                     location.reload(false); |  | ||||||
|                                   } |  | ||||||
|                                  }); //alert('save');
 |  | ||||||
|                 } else { |  | ||||||
|                     dojo.debug("please set a saveUrl for the editor"); |  | ||||||
|                 } |  | ||||||
|                 if (this.closeOnSave) { |  | ||||||
|                     this._richText.close(e.getName().toLowerCase() == "save"); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|     } |  | ||||||
|     return false; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function setConceptTypeForComboBox(typeId, cbId) { |  | ||||||
|     var t = dojo.byId(typeId).value; |  | ||||||
|     var cb = dijit.byId(cbId) |  | ||||||
|     var dp = cb.store; |  | ||||||
|     var baseUrl = dp.url.split('&')[0]; |  | ||||||
|     var newUrl = baseUrl + '&searchType=' + t; |  | ||||||
|     dp.url = newUrl; |  | ||||||
|     cb.setValue(''); |  | ||||||
| } |  | ||||||
| x |  | ||||||
| var dialogs = {} |  | ||||||
| 
 |  | ||||||
| function objectDialog(dlgName, url) { |  | ||||||
|     dojo.require('dijit.Dialog'); |  | ||||||
|     dojo.require('dijit.form.ComboBox'); |  | ||||||
|     dojo.require('dojox.data.QueryReadStore'); |  | ||||||
|     dlg = dialogs[dlgName]; |  | ||||||
|     if (!dlg) { |  | ||||||
|         dlg = new dijit.Dialog( |  | ||||||
|             {bgColor: 'white', bgOpacity: 0.5, toggle: 'fade', toggleDuration: 250, |  | ||||||
|              executeScripts: true, |  | ||||||
|              href: url |  | ||||||
|             }, dojo.byId('dialog.' + dlgName)); |  | ||||||
|         dialogs[dlgName] = dlg; |  | ||||||
|     } |  | ||||||
|     dlg.show(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function addConceptAssignment() { |  | ||||||
|     dojo.require('dojo.html') |  | ||||||
|     node = dojo.byId('form.assignments'); |  | ||||||
|     els = document.forms[0].elements; |  | ||||||
|     for (var i=0; i<els.length; i++) { //getElementsByName does not work here in IE
 |  | ||||||
|         el = els[i]; |  | ||||||
|         if (el.name == 'concept.search.text_selected') { |  | ||||||
|             cToken = el.value; |  | ||||||
|         } else if (el.name == 'concept.search.text') { |  | ||||||
|             title = el.value; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     if (cToken.length == 0) { |  | ||||||
|         alert('Please select a concept!'); |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
|     pToken = dojo.byId('concept.search.predicate').value; |  | ||||||
|     token = cToken + ':' + pToken; |  | ||||||
|     var td = document.createElement('td'); |  | ||||||
|     td.colSpan = 5; |  | ||||||
|     td.innerHTML = '<input type="checkbox" name="form.assignments.selected:list" value="' + token + '" checked><span>' + title + '</span>'; |  | ||||||
|     var tr = document.createElement('tr'); |  | ||||||
|     tr.appendChild(td); |  | ||||||
|     node.appendChild(tr); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
|  | @ -16,7 +16,7 @@ | ||||||
|   <metal:block fill-slot="ecmascript_slot" |   <metal:block fill-slot="ecmascript_slot" | ||||||
|                tal:condition="view/inlineEditingActive | nothing"> |                tal:condition="view/inlineEditingActive | nothing"> | ||||||
|     <script> |     <script> | ||||||
|         dojo.require("dojo.widget.Editor"); |         dojo.require("dijit.Editor"); | ||||||
|     </script> |     </script> | ||||||
|   </metal:block> |   </metal:block> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -44,16 +44,17 @@ from zope.security.proxy import removeSecurityProxy | ||||||
| 
 | 
 | ||||||
| from cybertools.ajax import innerHtml | from cybertools.ajax import innerHtml | ||||||
| from cybertools.browser import configurator | from cybertools.browser import configurator | ||||||
|  | from cybertools.browser.action import Action | ||||||
| from cybertools.browser.view import GenericView | from cybertools.browser.view import GenericView | ||||||
| from cybertools.typology.interfaces import IType, ITypeManager | from cybertools.typology.interfaces import IType, ITypeManager | ||||||
| from cybertools.xedit.browser import ExternalEditorView | from cybertools.xedit.browser import ExternalEditorView | ||||||
|  | from loops.browser.action import DialogAction | ||||||
| from loops.i18n.browser import i18n_macros | from loops.i18n.browser import i18n_macros | ||||||
| from loops.interfaces import IConcept, IResource, IDocument, IMediaAsset, INode | from loops.interfaces import IConcept, IResource, IDocument, IMediaAsset, INode | ||||||
| from loops.interfaces import IViewConfiguratorSchema | from loops.interfaces import IViewConfiguratorSchema | ||||||
| from loops.resource import MediaAsset | from loops.resource import MediaAsset | ||||||
| from loops import util | from loops import util | ||||||
| from loops.util import _ | from loops.util import _ | ||||||
| from loops.browser.action import Action, DialogAction, TargetAction |  | ||||||
| from loops.browser.common import BaseView | from loops.browser.common import BaseView | ||||||
| from loops.browser.concept import ConceptView | from loops.browser.concept import ConceptView | ||||||
| from loops.versioning.util import getVersion | from loops.versioning.util import getVersion | ||||||
|  | @ -65,8 +66,8 @@ node_macros = ViewPageTemplateFile('node_macros.pt') | ||||||
| class NodeView(BaseView): | class NodeView(BaseView): | ||||||
| 
 | 
 | ||||||
|     _itemNum = 0 |     _itemNum = 0 | ||||||
| 
 |  | ||||||
|     template = node_macros |     template = node_macros | ||||||
|  |     nextUrl = None | ||||||
| 
 | 
 | ||||||
|     def __init__(self, context, request): |     def __init__(self, context, request): | ||||||
|         super(NodeView, self).__init__(context, request) |         super(NodeView, self).__init__(context, request) | ||||||
|  | @ -80,23 +81,31 @@ class NodeView(BaseView): | ||||||
|     def setupController(self): |     def setupController(self): | ||||||
|         cm = self.controller.macros |         cm = self.controller.macros | ||||||
|         cm.register('css', identifier='loops.css', resourceName='loops.css', |         cm.register('css', identifier='loops.css', resourceName='loops.css', | ||||||
|                     media='all', position=3) |                     media='all', priority=60) | ||||||
|         cm.register('js', 'loops.js', resourceName='loops.js') |         cm.register('js', 'loops.js', resourceName='loops.js', priority=60) | ||||||
|         #cm.register('js', 'loops.js', resourceName='loops1.js') |  | ||||||
|         cm.register('top_actions', 'top_actions', name='multi_actions', |         cm.register('top_actions', 'top_actions', name='multi_actions', | ||||||
|                     subMacros=[i18n_macros.macros['language_switch']]) |                     subMacros=[i18n_macros.macros['language_switch']]) | ||||||
|         cm.register('portlet_left', 'navigation', title='Navigation', |         cm.register('portlet_left', 'navigation', title='Navigation', | ||||||
|                     subMacro=node_macros.macros['menu']) |                     subMacro=node_macros.macros['menu']) | ||||||
|         #if not IUnauthenticatedPrincipal.providedBy(self.request.principal): |  | ||||||
|         if canWrite(self.context, 'title'): |         if canWrite(self.context, 'title'): | ||||||
|             #cm.register('portlet_right', 'clipboard', title='Clipboard', |             #cm.register('portlet_right', 'clipboard', title='Clipboard', | ||||||
|             #            subMacro=self.template.macros['clipboard']) |             #            subMacro=self.template.macros['clipboard']) | ||||||
|             # this belongs to loops.organize; how to register portlets |             # this belongs to loops.organize | ||||||
|             # from sub- (other) packages? |  | ||||||
|             # see controller / configurator: use multiple configurators; |  | ||||||
|             # register additional configurators (adapters) from within package. |  | ||||||
|             cm.register('portlet_right', 'actions', title=_(u'Actions'), |             cm.register('portlet_right', 'actions', title=_(u'Actions'), | ||||||
|                         subMacro=node_macros.macros['actions']) |                         subMacro=node_macros.macros['actions'], | ||||||
|  |                         priority=100) | ||||||
|  |         if not IUnauthenticatedPrincipal.providedBy(self.request.principal): | ||||||
|  |             mi = self.controller.memberInfo | ||||||
|  |             title = mi.title.value or _(u'Personal Informations') | ||||||
|  |             obj = mi.get('object') | ||||||
|  |             url = obj is not None and self.getUrlForTarget(obj.value) or None | ||||||
|  |             cm.register('portlet_right', 'personal', title=title, | ||||||
|  |                         subMacro=node_macros.macros['personal'], | ||||||
|  |                         icon='cybertools.icons/user.png', | ||||||
|  |                         url=url, | ||||||
|  |                         priority=10) | ||||||
|  |         # force early portlet registrations by target by setting up target view | ||||||
|  |         self.virtualTarget | ||||||
|         # force early portlet registrations by target by setting up target view |         # force early portlet registrations by target by setting up target view | ||||||
|         self.virtualTarget |         self.virtualTarget | ||||||
| 
 | 
 | ||||||
|  | @ -117,7 +126,7 @@ class NodeView(BaseView): | ||||||
|         #target = self.virtualTargetObject  # ignores page even for direktly assignd target |         #target = self.virtualTargetObject  # ignores page even for direktly assignd target | ||||||
|         target = self.request.annotations.get('loops.view', {}).get('target') |         target = self.request.annotations.get('loops.view', {}).get('target') | ||||||
|         if target is not None: |         if target is not None: | ||||||
|             basicView = zapi.getMultiAdapter((target, self.request), name=viewName) |             basicView = component.getMultiAdapter((target, self.request), name=viewName) | ||||||
|             # xxx: obsolete when self.targetObject is virtual target: |             # xxx: obsolete when self.targetObject is virtual target: | ||||||
|             return basicView.view |             return basicView.view | ||||||
|         return self.page |         return self.page | ||||||
|  | @ -154,7 +163,7 @@ class NodeView(BaseView): | ||||||
|         if text.startswith('<'):  # seems to be HTML |         if text.startswith('<'):  # seems to be HTML | ||||||
|             return text |             return text | ||||||
|         source = zapi.createObject(self.context.contentType, text) |         source = zapi.createObject(self.context.contentType, text) | ||||||
|         view = zapi.getMultiAdapter((removeAllProxies(source), self.request)) |         view = component.getMultiAdapter((removeAllProxies(source), self.request)) | ||||||
|         return view.render() |         return view.render() | ||||||
| 
 | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|  | @ -170,7 +179,7 @@ class NodeView(BaseView): | ||||||
|     def targetObjectView(self): |     def targetObjectView(self): | ||||||
|         obj = self.targetObject |         obj = self.targetObject | ||||||
|         if obj is not None: |         if obj is not None: | ||||||
|             basicView = zapi.getMultiAdapter((obj, self.request)) |             basicView = component.getMultiAdapter((obj, self.request)) | ||||||
|             basicView._viewName = self.context.viewName |             basicView._viewName = self.context.viewName | ||||||
|             return basicView.view |             return basicView.view | ||||||
| 
 | 
 | ||||||
|  | @ -315,7 +324,7 @@ class NodeView(BaseView): | ||||||
|     def virtualTarget(self): |     def virtualTarget(self): | ||||||
|         obj = self.virtualTargetObject |         obj = self.virtualTargetObject | ||||||
|         if obj is not None: |         if obj is not None: | ||||||
|             basicView = zapi.getMultiAdapter((obj, self.request)) |             basicView = component.getMultiAdapter((obj, self.request)) | ||||||
|             if obj == self.targetObject: |             if obj == self.targetObject: | ||||||
|                 basicView._viewName = self.context.viewName |                 basicView._viewName = self.context.viewName | ||||||
|             return basicView.view |             return basicView.view | ||||||
|  | @ -376,8 +385,6 @@ class NodeView(BaseView): | ||||||
| 
 | 
 | ||||||
|     actions = dict(portlet=getPortletActions) |     actions = dict(portlet=getPortletActions) | ||||||
| 
 | 
 | ||||||
|     nextUrl = None |  | ||||||
| 
 |  | ||||||
|     @Lazy |     @Lazy | ||||||
|     def popupCreateObjectForm(self): |     def popupCreateObjectForm(self): | ||||||
|         return ("javascript:function%%20openDialog(url){" |         return ("javascript:function%%20openDialog(url){" | ||||||
|  | @ -401,11 +408,19 @@ class NodeView(BaseView): | ||||||
|     def inlineEdit(self, id): |     def inlineEdit(self, id): | ||||||
|         self.registerDojo() |         self.registerDojo() | ||||||
|         cm = self.controller.macros |         cm = self.controller.macros | ||||||
|         jsCall = 'dojo.require("dojo.widget.Editor")' |         jsCall = 'dojo.require("dijit.Editor")' | ||||||
|         cm.register('js-execute', jsCall, jsCall=jsCall) |         cm.register('js-execute', jsCall, jsCall=jsCall) | ||||||
|         return ('return inlineEdit("%s", "%s/inline_save")' |         return ('return inlineEdit("%s", "%s/inline_save")' | ||||||
|                                         % (id, self.virtualTargetUrl)) |                                         % (id, self.virtualTargetUrl)) | ||||||
| 
 | 
 | ||||||
|  |     def checkRTE(self): | ||||||
|  |         target = self.virtualTarget | ||||||
|  |         if target and target.inlineEditable: | ||||||
|  |             self.registerDojo() | ||||||
|  |             cm = self.controller.macros | ||||||
|  |             jsCall = 'dojo.require("dijit.Editor")' | ||||||
|  |             cm.register('js-execute', jsCall, jsCall=jsCall) | ||||||
|  | 
 | ||||||
|     def externalEdit(self): |     def externalEdit(self): | ||||||
|         target = self.virtualTargetObject |         target = self.virtualTargetObject | ||||||
|         if target is None: |         if target is None: | ||||||
|  | @ -423,7 +438,7 @@ class NodeView(BaseView): | ||||||
|     def registerDojoDialog(self): |     def registerDojoDialog(self): | ||||||
|         self.registerDojo() |         self.registerDojo() | ||||||
|         cm = self.controller.macros |         cm = self.controller.macros | ||||||
|         jsCall = 'dojo.require("dojo.widget.Dialog")' |         jsCall = 'dojo.require("dijit.Dialog")' | ||||||
|         cm.register('js-execute', jsCall, jsCall=jsCall) |         cm.register('js-execute', jsCall, jsCall=jsCall) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -527,7 +542,7 @@ class ConfigureView(NodeView): | ||||||
|     def target(self): |     def target(self): | ||||||
|         obj = self.targetObject |         obj = self.targetObject | ||||||
|         if obj is not None: |         if obj is not None: | ||||||
|             return zapi.getMultiAdapter((obj, self.request)) |             return component.getMultiAdapter((obj, self.request)) | ||||||
| 
 | 
 | ||||||
|     def update(self): |     def update(self): | ||||||
|         request = self.request |         request = self.request | ||||||
|  | @ -604,7 +619,7 @@ class ConfigureView(NodeView): | ||||||
|                 else: |                 else: | ||||||
|                     start = end = searchType |                     start = end = searchType | ||||||
|                 criteria['loops_type'] = (start, end) |                 criteria['loops_type'] = (start, end) | ||||||
|             cat = zapi.getUtility(ICatalog) |             cat = component.getUtility(ICatalog) | ||||||
|             result = cat.searchResults(**criteria) |             result = cat.searchResults(**criteria) | ||||||
|             # TODO: can this be done in a faster way? |             # TODO: can this be done in a faster way? | ||||||
|             result = [r for r in result if r.getLoopsRoot() == self.loopsRoot] |             result = [r for r in result if r.getLoopsRoot() == self.loopsRoot] | ||||||
|  | @ -664,7 +679,7 @@ class ViewPropertiesConfigurator(object): | ||||||
|     options = property(getOptions, setOptions) |     options = property(getOptions, setOptions) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class NodeViewConfigurator(configurator.ViewConfigurator): | class NodeViewConfigurator(configurator.AnnotationViewConfigurator): | ||||||
|     """ Take properties from next menu item... |     """ Take properties from next menu item... | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -27,24 +27,25 @@ | ||||||
|                tal:condition="nocall:target"> |                tal:condition="nocall:target"> | ||||||
|             <tal:ignore condition="nothing"> |             <tal:ignore condition="nothing"> | ||||||
|                 <div metal:define-macro="editicons" |                 <div metal:define-macro="editicons" | ||||||
|                      style="float: right; border: 1px solid #ccc; |                      style="float: right;"> | ||||||
|                             margin-top: 1em"> |  | ||||||
|                   <span id="xedit_icon" |                   <span id="xedit_icon" | ||||||
|                        tal:condition="target/xeditable | nothing"> |                        tal:condition="target/xeditable | nothing"> | ||||||
|                     <a href="#" title="Edit" style="padding: 5px" |                     <a href="#" title="Edit" | ||||||
|                        tal:attributes="href string:${item/realTargetUrl}/external_edit?version=this; |                        tal:attributes="href string:${item/realTargetUrl}/external_edit?version=this; | ||||||
|                                        title string:Edit '${target/title}' with External Editor"><img |                                        title string:Edit '${target/title}' with External Editor"><img | ||||||
|                                   src="edit.gif" alt="Edit" |                                   src="edit.gif" alt="Edit" | ||||||
|                                   tal:attributes="src context/++resource++edit.gif" /></a> |                                   tal:attributes="src context/++resource++edit.gif" /></a> | ||||||
|                   </span> |                   </span> | ||||||
|                   <span id="inlineedit_icon" |                   <span id="inlineedit_icon" | ||||||
|                        tal:condition="item/inlineEditable"> |                         xtal:condition="item/inlineEditable" | ||||||
|  |                         tal:condition="nothing"> | ||||||
|                     <a href="#" title="Edit" style="padding: 5px" |                     <a href="#" title="Edit" style="padding: 5px" | ||||||
|                        tal:attributes="title string:Edit '${target/title}' with Inline Editor; |                        tal:attributes="title string:Edit '${target/title}' with Inline Editor; | ||||||
|                                        onclick python: item.inlineEdit(id)"><img |                                        onclick python: item.inlineEdit(id)"><img | ||||||
|                                   src="edit.gif" alt="Edit" |                                   src="edit.gif" alt="Edit" | ||||||
|                                   tal:attributes="src context/++resource++edit.gif" /></a> |                                   tal:attributes="src context/++resource++edit.gif" /></a> | ||||||
|                   </span> |                   </span> | ||||||
|  |                   <tal:rte define="dummy item/checkRTE" /> | ||||||
|                 </div> |                 </div> | ||||||
|             </tal:ignore> |             </tal:ignore> | ||||||
|             <div class="content-1 subcolumn" id="1.body" |             <div class="content-1 subcolumn" id="1.body" | ||||||
|  | @ -178,7 +179,7 @@ | ||||||
| 
 | 
 | ||||||
| <metal:menu define-macro="menu" | <metal:menu define-macro="menu" | ||||||
|             tal:define="item nocall:view/menu | nothing; |             tal:define="item nocall:view/menu | nothing; | ||||||
|                         level level|python: 1" |                         level level|python: 1;" | ||||||
|             tal:condition="nocall:item"> |             tal:condition="nocall:item"> | ||||||
|     <metal:sub define-macro="submenu"> |     <metal:sub define-macro="submenu"> | ||||||
|           <div class="menu-3" |           <div class="menu-3" | ||||||
|  | @ -219,6 +220,14 @@ | ||||||
| </metal:actions> | </metal:actions> | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | <metal:actions define-macro="personal"> | ||||||
|  |     <div><a href="logout.html" i18n:translate="">Log out</a></div> | ||||||
|  |     <tal:actions repeat="action python:view.getActions('personal')"> | ||||||
|  |       <metal:action use-macro="action/macro" /> | ||||||
|  |     </tal:actions> | ||||||
|  | </metal:actions> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| <!-- inner HTML macros --> | <!-- inner HTML macros --> | ||||||
| 
 | 
 | ||||||
| <div metal:define-macro="inline_edit" | <div metal:define-macro="inline_edit" | ||||||
|  |  | ||||||
|  | @ -25,7 +25,6 @@ $Id$ | ||||||
| import urllib | import urllib | ||||||
| from zope.cachedescriptors.property import Lazy | from zope.cachedescriptors.property import Lazy | ||||||
| from zope import component | from zope import component | ||||||
| from zope.app import zapi |  | ||||||
| from zope.app.catalog.interfaces import ICatalog | from zope.app.catalog.interfaces import ICatalog | ||||||
| from zope.app.container.interfaces import INameChooser | from zope.app.container.interfaces import INameChooser | ||||||
| from zope.app.form.browser.textwidgets import FileWidget | from zope.app.form.browser.textwidgets import FileWidget | ||||||
|  | @ -38,14 +37,14 @@ from zope.schema.interfaces import IBytes | ||||||
| from zope.security import canAccess, canWrite | from zope.security import canAccess, canWrite | ||||||
| from zope.security.proxy import removeSecurityProxy | from zope.security.proxy import removeSecurityProxy | ||||||
| from zope.traversing.api import getName, getParent | from zope.traversing.api import getName, getParent | ||||||
|  | from zope.traversing.browser import absoluteURL | ||||||
| 
 | 
 | ||||||
| from cybertools.typology.interfaces import IType | from cybertools.typology.interfaces import IType | ||||||
| from cybertools.xedit.browser import ExternalEditorView, fromUnicode | from cybertools.xedit.browser import ExternalEditorView, fromUnicode | ||||||
| from loops.browser.action import Action, DialogAction, TargetAction | from loops.browser.action import DialogAction, TargetAction | ||||||
| from loops.browser.common import EditForm, BaseView | from loops.browser.common import EditForm, BaseView | ||||||
| from loops.browser.concept import ConceptRelationView, ConceptConfigureView | from loops.browser.concept import ConceptRelationView, ConceptConfigureView | ||||||
| from loops.browser.node import NodeView, node_macros | from loops.browser.node import NodeView, node_macros | ||||||
| from loops.browser.util import html_quote |  | ||||||
| from loops.common import adapted, NameChooser | from loops.common import adapted, NameChooser | ||||||
| from loops.interfaces import IBaseResource, IDocument, IMediaAsset, ITextDocument | from loops.interfaces import IBaseResource, IDocument, IMediaAsset, ITextDocument | ||||||
| from loops.interfaces import ITypeConcept | from loops.interfaces import ITypeConcept | ||||||
|  | @ -53,14 +52,6 @@ from loops.versioning.browser import version_macros | ||||||
| from loops.versioning.interfaces import IVersionable | from loops.versioning.interfaces import IVersionable | ||||||
| from loops.util import _ | from loops.util import _ | ||||||
| 
 | 
 | ||||||
| renderingFactories = { |  | ||||||
|     'text/plain': 'zope.source.plaintext', |  | ||||||
|     'text/stx': 'zope.source.stx', |  | ||||||
|     'text/structured': 'zope.source.stx', |  | ||||||
|     'text/rest': 'zope.source.rest', |  | ||||||
|     'text/restructured': 'zope.source.rest', |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| class CustomFileWidget(FileWidget): | class CustomFileWidget(FileWidget): | ||||||
| 
 | 
 | ||||||
|  | @ -120,16 +111,18 @@ class ResourceView(BaseView): | ||||||
|         super(ResourceView, self).__init__(context, request) |         super(ResourceView, self).__init__(context, request) | ||||||
|         if not IUnauthenticatedPrincipal.providedBy(self.request.principal): |         if not IUnauthenticatedPrincipal.providedBy(self.request.principal): | ||||||
|             cont = self.controller |             cont = self.controller | ||||||
|             if cont is not None and list(self.relatedConcepts()): |             if cont is not None: | ||||||
|                 cont.macros.register('portlet_right', 'related', title=_(u'Related Items'), |                 if list(self.relatedConcepts()): | ||||||
|  |                     cont.macros.register('portlet_right', 'related', | ||||||
|  |                                 title=_(u'Related Items'), | ||||||
|                                 subMacro=self.template.macros['related'], |                                 subMacro=self.template.macros['related'], | ||||||
|                              position=0, info=self) |                                 priority=20, info=self) | ||||||
|                 versionable = IVersionable(self.context, None) |                 versionable = IVersionable(self.context, None) | ||||||
|                 if versionable is not None and len(versionable.versions) > 1: |                 if versionable is not None and len(versionable.versions) > 1: | ||||||
|                         cont.macros.register('portlet_right', 'versions', |                         cont.macros.register('portlet_right', 'versions', | ||||||
|                                 title='Version ' + versionable.versionId, |                                 title='Version ' + versionable.versionId, | ||||||
|                                 subMacro=version_macros.macros['portlet_versions'], |                                 subMacro=version_macros.macros['portlet_versions'], | ||||||
|                                 position=1, info=self) |                                 priority=25, info=self) | ||||||
| 
 | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def view(self): |     def view(self): | ||||||
|  | @ -150,10 +143,6 @@ class ResourceView(BaseView): | ||||||
| 
 | 
 | ||||||
|     def show(self, useAttachment=False): |     def show(self, useAttachment=False): | ||||||
|         """ show means: "download"...""" |         """ show means: "download"...""" | ||||||
|         #data = self.openForView() |  | ||||||
|         #response.setHeader('Content-Disposition', |  | ||||||
|         #                   'attachment; filename=%s' % zapi.getName(self.context)) |  | ||||||
|         #return data |  | ||||||
|         context = self.context |         context = self.context | ||||||
|         ti = IType(context).typeInterface |         ti = IType(context).typeInterface | ||||||
|         if ti is not None: |         if ti is not None: | ||||||
|  | @ -222,6 +211,10 @@ class ResourceView(BaseView): | ||||||
| 
 | 
 | ||||||
| class ResourceConfigureView(ResourceView, ConceptConfigureView): | class ResourceConfigureView(ResourceView, ConceptConfigureView): | ||||||
| 
 | 
 | ||||||
|  |     #def __init__(self, context, request): | ||||||
|  |     #    # avoid calling ConceptView.__init__() | ||||||
|  |     #    ResourceView.__init__(self, context, request) | ||||||
|  | 
 | ||||||
|     def update(self): |     def update(self): | ||||||
|         request = self.request |         request = self.request | ||||||
|         action = request.get('action') |         action = request.get('action') | ||||||
|  | @ -269,7 +262,7 @@ class ResourceConfigureView(ResourceView, ConceptConfigureView): | ||||||
|                 else: |                 else: | ||||||
|                     start = end = searchType |                     start = end = searchType | ||||||
|                 criteria['loops_type'] = (start, end) |                 criteria['loops_type'] = (start, end) | ||||||
|             cat = zapi.getUtility(ICatalog) |             cat = component.getUtility(ICatalog) | ||||||
|             result = cat.searchResults(**criteria) |             result = cat.searchResults(**criteria) | ||||||
|             # TODO: can this be done in a faster way? |             # TODO: can this be done in a faster way? | ||||||
|             result = [r for r in result if r.getLoopsRoot() == self.loopsRoot] |             result = [r for r in result if r.getLoopsRoot() == self.loopsRoot] | ||||||
|  | @ -292,19 +285,10 @@ class DocumentView(ResourceView): | ||||||
|     def render(self): |     def render(self): | ||||||
|         """ Return the rendered content (data) of the context object. |         """ Return the rendered content (data) of the context object. | ||||||
|         """ |         """ | ||||||
|         #text = self.context.data |  | ||||||
|         ctx = adapted(self.context) |         ctx = adapted(self.context) | ||||||
|         text = ctx.data |         text = ctx.data | ||||||
|         #contentType = self.context.contentType |  | ||||||
|         contentType = ctx.contentType |         contentType = ctx.contentType | ||||||
|         typeKey = renderingFactories.get(contentType, None) |         return self.renderText(ctx.data, ctx.contentType) | ||||||
|         if typeKey is None: |  | ||||||
|             if contentType == u'text/html': |  | ||||||
|                 return text |  | ||||||
|             return u'<pre>%s</pre>' % html_quote(text) |  | ||||||
|         source = zapi.createObject(typeKey, text) |  | ||||||
|         view = zapi.getMultiAdapter((removeAllProxies(source), self.request)) |  | ||||||
|         return view.render() |  | ||||||
| 
 | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def inlineEditable(self): |     def inlineEditable(self): | ||||||
|  | @ -316,10 +300,12 @@ class DocumentView(ResourceView): | ||||||
| class ExternalEditorView(ExternalEditorView): | class ExternalEditorView(ExternalEditorView): | ||||||
| 
 | 
 | ||||||
|     def load(self, url=None): |     def load(self, url=None): | ||||||
|         context = removeSecurityProxy(self.context) |         #context = removeSecurityProxy(self.context) | ||||||
|  |         context = self.context | ||||||
|         data = adapted(context).data |         data = adapted(context).data | ||||||
|         r = [] |         r = [] | ||||||
|         r.append('url:' + (url or zapi.absoluteURL(context, self.request))) |         context = removeSecurityProxy(context) | ||||||
|  |         r.append('url:' + (url or absoluteURL(context, self.request))) | ||||||
|         r.append('content_type:' + str(context.contentType)) |         r.append('content_type:' + str(context.contentType)) | ||||||
|         r.append('meta_type:' + '.'.join((context.__module__, context.__class__.__name__))) |         r.append('meta_type:' + '.'.join((context.__module__, context.__class__.__name__))) | ||||||
|         auth = self.request.get('_auth') |         auth = self.request.get('_auth') | ||||||
|  |  | ||||||
|  | @ -10,6 +10,11 @@ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     <metal:footer fill-slot="footer"> |     <metal:footer fill-slot="footer"> | ||||||
|  |       <div tal:condition="view/editable"> | ||||||
|  |         <span i18n:translate="">For quick creation of notes/links bookmark this link</span>: | ||||||
|  |         <a href="#" | ||||||
|  |            tal:attributes="href view/popupCreateObjectForm">Create loops Note</a> | ||||||
|  |       </div> | ||||||
|       Powered by <b><a href="http://www.python.org">Python</a></b> · |       Powered by <b><a href="http://www.python.org">Python</a></b> · | ||||||
|       <b><a href="http://wiki.zope.org/zope3">Zope 3</a></b> · |       <b><a href="http://wiki.zope.org/zope3">Zope 3</a></b> · | ||||||
|       <b><a href="http://loops.cy55.de">loops</a></b>. |       <b><a href="http://loops.cy55.de">loops</a></b>. | ||||||
|  |  | ||||||
|  | @ -12,9 +12,12 @@ Let's set up a loops site with basic and example concepts and resources. | ||||||
|   >>> from zope.app.testing.setup import placefulSetUp, placefulTearDown |   >>> from zope.app.testing.setup import placefulSetUp, placefulTearDown | ||||||
|   >>> site = placefulSetUp(True) |   >>> site = placefulSetUp(True) | ||||||
| 
 | 
 | ||||||
|  |   >>> from loops.organize.setup import SetupManager | ||||||
|  |   >>> component.provideAdapter(SetupManager, name='organize') | ||||||
|   >>> from loops.tests.setup import TestSite |   >>> from loops.tests.setup import TestSite | ||||||
|   >>> t = TestSite(site) |   >>> t = TestSite(site) | ||||||
|   >>> concepts, resources, views = t.setup() |   >>> concepts, resources, views = t.setup() | ||||||
|  |   >>> loopsRoot = site['loops'] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| Compund Objects - Hierarchies with Ordered Components | Compund Objects - Hierarchies with Ordered Components | ||||||
|  | @ -74,3 +77,244 @@ And remove a part from the compound. | ||||||
|   >>> [getName(p) for p in aC01.getParts()] |   >>> [getName(p) for p in aC01.getParts()] | ||||||
|   [u'd002.txt', u'd003.txt', u'd001.txt'] |   [u'd002.txt', u'd003.txt', u'd001.txt'] | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | Blogs | ||||||
|  | ===== | ||||||
|  | 
 | ||||||
|  |   >>> from loops.compound.blog.post import BlogPost | ||||||
|  |   >>> from loops.compound.blog.interfaces import IBlogPost | ||||||
|  |   >>> component.provideAdapter(BlogPost, provides=IBlogPost) | ||||||
|  | 
 | ||||||
|  |   >>> tBlog = addAndConfigureObject(concepts, Concept, 'blog', title=u'Blog', | ||||||
|  |   ...                               conceptType=tType) | ||||||
|  |   >>> tBlogPost = addAndConfigureObject(concepts, Concept, 'blogpost', | ||||||
|  |   ...                               title=u'Blog Post', conceptType=tType, | ||||||
|  |   ...                               typeInterface=IBlogPost) | ||||||
|  | 
 | ||||||
|  |   >>> myBlog = addAndConfigureObject(concepts, Concept, 'myblog', title=u'My Blog', | ||||||
|  |   ...                               conceptType=tBlog) | ||||||
|  | 
 | ||||||
|  |   >>> firstPost = addAndConfigureObject(concepts, Concept, 'firstpost', | ||||||
|  |   ...                               title=u'My first post', conceptType=tBlogPost) | ||||||
|  | 
 | ||||||
|  |   >>> aFirstPost = adapted(firstPost) | ||||||
|  |   >>> aFirstPost.date | ||||||
|  |   >>> aFirstPost.text = u'My first blog post.' | ||||||
|  |   >>> aFirstPost.text | ||||||
|  |   u'My first blog post.' | ||||||
|  |   >>> aFirstPost.creator | ||||||
|  | 
 | ||||||
|  | Blog and BlogPost views | ||||||
|  | ----------------------- | ||||||
|  | 
 | ||||||
|  |   >>> from loops.compound.blog.browser import BlogView, BlogPostView | ||||||
|  |   >>> #from zope.publisher.browser import TestRequest | ||||||
|  |   >>> from loops.tests.auth import TestRequest | ||||||
|  | 
 | ||||||
|  | The blog view automatically provides a portlet action for creating | ||||||
|  | a new post. | ||||||
|  | 
 | ||||||
|  |   >>> view = BlogView(myBlog, TestRequest()) | ||||||
|  |   >>> for act in view.getActions('portlet'): | ||||||
|  |   ...     print act.name | ||||||
|  |   createBlogPost | ||||||
|  | 
 | ||||||
|  |   >>> view = BlogPostView(firstPost, TestRequest()) | ||||||
|  |   >>> data = view.data | ||||||
|  | 
 | ||||||
|  | Automatic assignment of a blog post to the personal blog of its owner | ||||||
|  | --------------------------------------------------------------------- | ||||||
|  | 
 | ||||||
|  | As all the following stuff relies on a blog being assigned to a person | ||||||
|  | we need the corresponding scaffolding from the loops.organize package. | ||||||
|  | 
 | ||||||
|  |   >>> from loops.organize.tests import setupUtilitiesAndAdapters | ||||||
|  |   >>> setupData = setupUtilitiesAndAdapters(loopsRoot) | ||||||
|  | 
 | ||||||
|  | We also have to set up some components for automatic setting of | ||||||
|  | security properties upon object creation. | ||||||
|  | 
 | ||||||
|  |   >>> from loops.security.common import setDefaultSecurity | ||||||
|  |   >>> component.provideHandler(setDefaultSecurity) | ||||||
|  |   >>> from loops.compound.blog.security import BlogPostSecuritySetter | ||||||
|  |   >>> component.provideAdapter(BlogPostSecuritySetter) | ||||||
|  | 
 | ||||||
|  | Let's start with defining a user (more precisely: a principal) | ||||||
|  | and a corresponding person. | ||||||
|  | 
 | ||||||
|  |   >>> auth = setupData.auth | ||||||
|  |   >>> tPerson = concepts['person'] | ||||||
|  | 
 | ||||||
|  |   >>> userJohn = auth.definePrincipal('users.john', u'John', login='john') | ||||||
|  |   >>> persJohn = addAndConfigureObject(concepts, Concept, 'person.john', | ||||||
|  |   ...                   title=u'John Smith', conceptType=tPerson, | ||||||
|  |   ...                   userId='users.john') | ||||||
|  | 
 | ||||||
|  |   >>> blogJohn = addAndConfigureObject(concepts, Concept, 'blog.john', | ||||||
|  |   ...                   title=u'John\'s Blog', conceptType=tBlog) | ||||||
|  |   >>> persJohn.assignChild(blogJohn) | ||||||
|  | 
 | ||||||
|  | Let's now login as the newly defined user. | ||||||
|  | 
 | ||||||
|  |   >>> from loops.tests.auth import login | ||||||
|  |   >>> login(userJohn) | ||||||
|  | 
 | ||||||
|  | Let's also provide some general permission settings. These are necessary | ||||||
|  | as after logging in the permissions of the user will be checked by the | ||||||
|  | standard checker defined in the test setup. | ||||||
|  | 
 | ||||||
|  |   >>> grantPermission = setupData.rolePermissions.grantPermissionToRole | ||||||
|  |   >>> assignRole = setupData.principalRoles.assignRoleToPrincipal | ||||||
|  | 
 | ||||||
|  |   >>> grantPermission('zope.View', 'zope.Member') | ||||||
|  |   >>> grantPermission('zope.View', 'loops.Owner') | ||||||
|  |   >>> grantPermission('zope.ManageContent', 'zope.ContentManager') | ||||||
|  |   >>> grantPermission('loops.ViewRestricted', 'loops.Owner') | ||||||
|  | 
 | ||||||
|  |   >>> assignRole('zope.Member', 'users.john') | ||||||
|  | 
 | ||||||
|  | The automatic assignment of the blog post is done in the form controller | ||||||
|  | used for creating the blog post. | ||||||
|  | 
 | ||||||
|  |   >>> from loops.compound.blog.browser import CreateBlogPostForm, CreateBlogPost | ||||||
|  |   >>> input = {'title': u'John\'s first post', 'text': u'Text of John\'s post', | ||||||
|  |   ...          'date': '2008-02-02T15:54:11', | ||||||
|  |   ...          'privateComment': u'John\'s private comment', | ||||||
|  |   ...          'form.type': '.loops/concepts/blogpost'} | ||||||
|  |   >>> cbpForm = CreateBlogPostForm(myBlog, TestRequest(form=input)) | ||||||
|  |   >>> cbpController = CreateBlogPost(cbpForm, cbpForm.request) | ||||||
|  |   >>> cbpController.update() | ||||||
|  |   False | ||||||
|  | 
 | ||||||
|  |   >>> posts = blogJohn.getChildren() | ||||||
|  |   >>> len(posts) | ||||||
|  |   1 | ||||||
|  |   >>> postJohn0 = posts[0] | ||||||
|  |   >>> postJohn0.title | ||||||
|  |   u"John's first post" | ||||||
|  | 
 | ||||||
|  |   >>> postJohn0Text = adapted(postJohn0.getResources()[0]) | ||||||
|  |   >>> postJohn0Text.data | ||||||
|  |   u"Text of John's post" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | Security | ||||||
|  | ======== | ||||||
|  | 
 | ||||||
|  | We first have to define some checkers that will be invoked when checking access | ||||||
|  | to attributes. | ||||||
|  | 
 | ||||||
|  |   >>> from zope.security.checker import Checker, defineChecker | ||||||
|  |   >>> checker = Checker(dict(title='zope.View', privateComment='loops.ViewRestricted'), | ||||||
|  |   ...                   dict(title='zope.ManageContent', | ||||||
|  |   ...                        privateComment='zope.ManageContent')) | ||||||
|  |   >>> #defineChecker(Concept, checker) | ||||||
|  |   >>> defineChecker(BlogPost, checker) | ||||||
|  | 
 | ||||||
|  |   >>> from loops.resource import Resource, TextDocumentAdapter | ||||||
|  |   >>> checker = Checker(dict(title='zope.View', data='zope.View'), | ||||||
|  |   ...                   dict(title='zope.ManageContent', data='zope.ManageContent')) | ||||||
|  |   >>> #defineChecker(Resource, checker) | ||||||
|  |   >>> defineChecker(TextDocumentAdapter, checker) | ||||||
|  | 
 | ||||||
|  | Standard security settings for blogs | ||||||
|  | ------------------------------------ | ||||||
|  | 
 | ||||||
|  | TODO... | ||||||
|  | 
 | ||||||
|  | A personal blog is a blog that is a direct child of a person with | ||||||
|  | an associated principal (i.e. a user id). | ||||||
|  | 
 | ||||||
|  | Blog posts in a personal blog can only be created by the owner of the blog. | ||||||
|  | More generally: A personal blog may receive only blog posts as children | ||||||
|  | that have the same owner as the blog itself. | ||||||
|  | 
 | ||||||
|  | A personal blog may only be assigned to other parents by the owner of | ||||||
|  | the blog. | ||||||
|  | 
 | ||||||
|  | Standard security settings for blog posts | ||||||
|  | ----------------------------------------- | ||||||
|  | 
 | ||||||
|  | Blog posts may (only!) be edited by their owner (i.e. only the owner | ||||||
|  | has the ManageContent permission). (TODO: Also their parent assignments may be | ||||||
|  | changed only by the owner). | ||||||
|  | 
 | ||||||
|  | Note that we still are logged-in as user John. | ||||||
|  | 
 | ||||||
|  |   >>> from zope.security import canAccess, canWrite, checkPermission | ||||||
|  |   >>> canAccess(postJohn0, 'title') | ||||||
|  |   True | ||||||
|  |   >>> canWrite(postJohn0, 'title') | ||||||
|  |   True | ||||||
|  | 
 | ||||||
|  | This settings are also valid for children/resources that are assigned | ||||||
|  | via an `is Part of` relation. | ||||||
|  | 
 | ||||||
|  |   >>> canAccess(postJohn0Text, 'data') | ||||||
|  |   True | ||||||
|  |   >>> canWrite(postJohn0Text, 'data') | ||||||
|  |   True | ||||||
|  | 
 | ||||||
|  | The private comment is only visible (and editable, of course) for the | ||||||
|  | owner of the blog post. | ||||||
|  | 
 | ||||||
|  |   >>> aPostJohn0 = adapted(postJohn0) | ||||||
|  |   >>> canAccess(aPostJohn0, 'privateComment') | ||||||
|  |   True | ||||||
|  |   >>> canWrite(aPostJohn0, 'privateComment') | ||||||
|  |   True | ||||||
|  | 
 | ||||||
|  | So let's now switch to another user. On a global level, Martha also has | ||||||
|  | the ContentManager role, i.e. she is allowed to edit content objects. | ||||||
|  | Nevertheless she is not allowed to change John's blog post. | ||||||
|  | 
 | ||||||
|  |   >>> userMartha = auth.definePrincipal('users.martha', u'Martha', login='martha') | ||||||
|  |   >>> assignRole('zope.Member', 'users.martha') | ||||||
|  |   >>> assignRole('zope.ContentManager', 'users.martha') | ||||||
|  | 
 | ||||||
|  |   >>> login(userMartha) | ||||||
|  | 
 | ||||||
|  |   >>> canAccess(postJohn0, 'title') | ||||||
|  |   True | ||||||
|  |   >>> canWrite(postJohn0, 'title') | ||||||
|  |   False | ||||||
|  | 
 | ||||||
|  |   >>> canAccess(postJohn0Text, 'data') | ||||||
|  |   True | ||||||
|  |   >>> canWrite(postJohn0Text, 'data') | ||||||
|  |   False | ||||||
|  | 
 | ||||||
|  |   >>> canAccess(aPostJohn0, 'privateComment') | ||||||
|  |   False | ||||||
|  |   >>> canWrite(aPostJohn0, 'privateComment') | ||||||
|  |   False | ||||||
|  | 
 | ||||||
|  | A blog post marked as private is only visible for its owner. | ||||||
|  | 
 | ||||||
|  |   >>> login(userJohn) | ||||||
|  |   >>> aPostJohn0.private = True | ||||||
|  |   >>> canAccess(postJohn0, 'title') | ||||||
|  |   True | ||||||
|  |   >>> canAccess(postJohn0Text, 'data') | ||||||
|  |   True | ||||||
|  | 
 | ||||||
|  |   >>> login(userMartha) | ||||||
|  |   >>> canAccess(postJohn0, 'data') | ||||||
|  |   False | ||||||
|  |   >>> canAccess(postJohn0Text, 'data') | ||||||
|  |   False | ||||||
|  | 
 | ||||||
|  | When we clear the `private` flag the post becomes visible again. | ||||||
|  | 
 | ||||||
|  |   >>> aPostJohn0.private = False | ||||||
|  |   >>> canAccess(postJohn0, 'title') | ||||||
|  |   True | ||||||
|  |   >>> canAccess(postJohn0Text, 'title') | ||||||
|  |   True | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | Fin de partie | ||||||
|  | ============= | ||||||
|  | 
 | ||||||
|  |   >>> placefulTearDown() | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | @ -34,22 +34,28 @@ class Compound(AdapterBase): | ||||||
| 
 | 
 | ||||||
|     implements(ICompound) |     implements(ICompound) | ||||||
| 
 | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def compoundPredicate(self): | ||||||
|  |         return self.context.getConceptManager()[compoundPredicateName] | ||||||
|  | 
 | ||||||
|     def getParts(self): |     def getParts(self): | ||||||
|         return self.context.getChildren([self.partOf]) |         if self.context.__parent__ is None: | ||||||
|  |             return [] | ||||||
|  |         return self.context.getResources([self.partOf]) | ||||||
| 
 | 
 | ||||||
|     def add(self, obj, position=None): |     def add(self, obj, position=None): | ||||||
|         if position is None: |         if position is None: | ||||||
|             order = self.getMaxOrder() + 1 |             order = self.getMaxOrder() + 1 | ||||||
|         else: |         else: | ||||||
|             order = self.getOrderForPosition(position) |             order = self.getOrderForPosition(position) | ||||||
|         self.context.assignChild(obj, self.partOf, order=order) |         self.context.assignResource(obj, self.partOf, order=order) | ||||||
| 
 | 
 | ||||||
|     def remove(self, obj, position=None): |     def remove(self, obj, position=None): | ||||||
|         if position is None: |         if position is None: | ||||||
|             self.context.deassignChild(obj, [self.partOf]) |             self.context.deassignResource(obj, [self.partOf]) | ||||||
|         else: |         else: | ||||||
|             rel = self.getPartRelations()[position] |             rel = self.getPartRelations()[position] | ||||||
|             self.context.deassignChild(obj, [self.partOf], order=rel.order) |             self.context.deassignResource(obj, [self.partOf], order=rel.order) | ||||||
| 
 | 
 | ||||||
|     def reorder(self, parts): |     def reorder(self, parts): | ||||||
|         existing = list(self.getPartRelations()) |         existing = list(self.getPartRelations()) | ||||||
|  | @ -71,7 +77,7 @@ class Compound(AdapterBase): | ||||||
|     # helper methods and properties |     # helper methods and properties | ||||||
| 
 | 
 | ||||||
|     def getPartRelations(self): |     def getPartRelations(self): | ||||||
|         return self.context.getChildRelations([self.partOf]) |         return self.context.getResourceRelations([self.partOf]) | ||||||
| 
 | 
 | ||||||
|     def getMaxOrder(self): |     def getMaxOrder(self): | ||||||
|         rels = self. getPartRelations() |         rels = self. getPartRelations() | ||||||
|  | @ -105,6 +111,10 @@ class Compound(AdapterBase): | ||||||
|     def conceptManager(self): |     def conceptManager(self): | ||||||
|         return self.context.getConceptManager() |         return self.context.getConceptManager() | ||||||
| 
 | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def resourceManager(self): | ||||||
|  |         return self.getLoopsRoot().getResourceManager() | ||||||
|  | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def partOf(self): |     def partOf(self): | ||||||
|         return self.conceptManager[compoundPredicateName] |         return self.conceptManager[compoundPredicateName] | ||||||
|  |  | ||||||
							
								
								
									
										4
									
								
								compound/blog/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								compound/blog/__init__.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | ||||||
|  | """ | ||||||
|  | $Id$ | ||||||
|  | """ | ||||||
|  | 
 | ||||||
							
								
								
									
										190
									
								
								compound/blog/browser.py
									
										
									
									
									
										Executable file
									
								
							
							
						
						
									
										190
									
								
								compound/blog/browser.py
									
										
									
									
									
										Executable file
									
								
							|  | @ -0,0 +1,190 @@ | ||||||
|  | # | ||||||
|  | #  Copyright (c) 2008 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 | ||||||
|  | # | ||||||
|  | 
 | ||||||
|  | """ | ||||||
|  | View classes for glossary and glossary items. | ||||||
|  | 
 | ||||||
|  | $Id$ | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | import itertools | ||||||
|  | from zope import component | ||||||
|  | from zope.app.pagetemplate import ViewPageTemplateFile | ||||||
|  | from zope.cachedescriptors.property import Lazy | ||||||
|  | 
 | ||||||
|  | from cybertools.browser.action import actions | ||||||
|  | from cybertools.browser.member import IMemberInfoProvider | ||||||
|  | from loops.browser.action import DialogAction | ||||||
|  | from loops.browser.concept import ConceptView, ConceptRelationView | ||||||
|  | from loops.browser.form import CreateConceptForm, EditConceptForm | ||||||
|  | from loops.browser.form import CreateConcept, EditConcept | ||||||
|  | from loops.common import adapted | ||||||
|  | from loops.organize.party import getPersonForUser | ||||||
|  | from loops.security.common import checkPermission | ||||||
|  | from loops import util | ||||||
|  | from loops.util import _ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | view_macros = ViewPageTemplateFile('view_macros.pt') | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | actions.register('createBlogPost', 'portlet', DialogAction, | ||||||
|  |         title=_(u'Create Blog Post...'), | ||||||
|  |         description=_(u'Create a new blog post.'), | ||||||
|  |         viewName='create_blogpost.html', | ||||||
|  |         dialogName='createBlogPost', | ||||||
|  |         typeToken='.loops/concepts/blogpost', | ||||||
|  |         fixedType=True, | ||||||
|  |         innerForm='inner_concept_form.html', | ||||||
|  |         prerequisites=['registerDojoDateWidget'], # +'registerDojoTextWidget'? | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def supplyCreator(self, data): | ||||||
|  |     creator = data.get('creator') | ||||||
|  |     data['creatorId'] = creator | ||||||
|  |     if creator: | ||||||
|  |         mip = component.getMultiAdapter((self.context, self.request), | ||||||
|  |                                         IMemberInfoProvider) | ||||||
|  |         mi = mip.getData(creator) | ||||||
|  |         data['creator'] = mi.title.value or creator | ||||||
|  |         obj = mi.get('object') | ||||||
|  |         if obj is not None: | ||||||
|  |             data['creatorUrl'] = self.controller.view.getUrlForTarget(obj.value) | ||||||
|  |     return data | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class BlogRelationView(ConceptRelationView): | ||||||
|  | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def data(self): | ||||||
|  |         data = super(BlogRelationView, self).data | ||||||
|  |         return supplyCreator(self, data) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class BlogView(ConceptView): | ||||||
|  | 
 | ||||||
|  |     childViewFactory = BlogRelationView | ||||||
|  | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def macro(self): | ||||||
|  |         return view_macros.macros['blog'] | ||||||
|  | 
 | ||||||
|  |     def getActions(self, category='object', page=None): | ||||||
|  |         blogOwnerId = self.blogOwnerId | ||||||
|  |         if blogOwnerId: | ||||||
|  |             principal = self.request.principal | ||||||
|  |             if principal and principal.id != blogOwnerId: | ||||||
|  |                 return [] | ||||||
|  |         return actions.get(category, ['createBlogPost'], view=self, page=page) | ||||||
|  | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def blogOwnerId(self): | ||||||
|  |         pType = self.loopsRoot.getConceptManager()['person'] | ||||||
|  |         persons = [p for p in self.context.getParents() if p.conceptType == pType] | ||||||
|  |         if len(persons) == 1: | ||||||
|  |             return adapted(persons[0]).userId | ||||||
|  |         return '' | ||||||
|  | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def allChildren(self): | ||||||
|  |         return self.childrenByType() | ||||||
|  | 
 | ||||||
|  |     def blogPosts(self): | ||||||
|  |         posts = self.allChildren.get('blogpost', []) | ||||||
|  |         return reversed(sorted(posts, key=lambda x: x.adapted.date)) | ||||||
|  | 
 | ||||||
|  |     def children(self, topLevelOnly=True, sort=True): | ||||||
|  |         rels = itertools.chain(*[self.allChildren[k] | ||||||
|  |                                  for k in self.allChildren.keys() | ||||||
|  |                                  if k != 'blogpost']) | ||||||
|  |         return sorted(rels, key=lambda r: (r.order, r.title.lower())) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class BlogPostView(ConceptView): | ||||||
|  | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def macro(self): | ||||||
|  |         return view_macros.macros['blogpost'] | ||||||
|  | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def data(self): | ||||||
|  |         data = super(BlogPostView, self).data | ||||||
|  |         data = supplyCreator(self, data) | ||||||
|  |         if not checkPermission('loops.ViewRestricted', self.context): | ||||||
|  |             data['privateComment'] = u'' | ||||||
|  |         return data | ||||||
|  | 
 | ||||||
|  |     def getActions(self, category='object', page=None): | ||||||
|  |         actions = [] | ||||||
|  |         if category == 'portlet' and self.editable: | ||||||
|  |             actions.append(DialogAction(self, title=_(u'Edit Blog Post...'), | ||||||
|  |                   description=_(u'Modify blog post.'), | ||||||
|  |                   viewName='edit_blogpost.html', | ||||||
|  |                   dialogName='editBlogPost', | ||||||
|  |                   page=page)) | ||||||
|  |             #self.registerDojoTextWidget() | ||||||
|  |             self.registerDojoDateWidget() | ||||||
|  |         return actions | ||||||
|  | 
 | ||||||
|  |     def render(self): | ||||||
|  |         return self.renderText(self.data['text'], self.adapted.textContentType) | ||||||
|  | 
 | ||||||
|  |     def resources(self): | ||||||
|  |         stdPred = self.loopsRoot.getConceptManager().getDefaultPredicate() | ||||||
|  |         rels = self.context.getResourceRelations([stdPred]) | ||||||
|  |         for r in rels: | ||||||
|  |             yield self.childViewFactory(r, self.request, contextIsSecond=True) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class EditBlogPostForm(EditConceptForm): | ||||||
|  | 
 | ||||||
|  |     title = _(u'Edit Blog Post') | ||||||
|  |     form_action = 'edit_blogpost' | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class CreateBlogPostForm(CreateConceptForm): | ||||||
|  | 
 | ||||||
|  |     title = _(u'Create Blog Post') | ||||||
|  |     form_action = 'create_blogpost' | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class EditBlogPost(EditConcept): | ||||||
|  | 
 | ||||||
|  |     pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class CreateBlogPost(CreateConcept): | ||||||
|  | 
 | ||||||
|  |     def collectAutoConcepts(self): | ||||||
|  |         #super(CreateBlogPost, self).collectConcepts(fieldName, value) | ||||||
|  |         person = getPersonForUser(self.container, self.request) | ||||||
|  |         if person is not None: | ||||||
|  |             concepts = self.loopsRoot.getConceptManager() | ||||||
|  |             blogType = concepts.get('blog') | ||||||
|  |             if blogType is not None: | ||||||
|  |                 blogs = [c for c in person.getChildren() | ||||||
|  |                            if c.conceptType == blogType] | ||||||
|  |                 if blogs: | ||||||
|  |                     blogUid = util.getUidForObject(blogs[0]) | ||||||
|  |                     predUid = util.getUidForObject(concepts.getDefaultPredicate()) | ||||||
|  |                     token = '%s:%s' % (blogUid, predUid) | ||||||
|  |                     if token not in self.selected: | ||||||
|  |                         self.selected.append(token) | ||||||
|  | 
 | ||||||
							
								
								
									
										74
									
								
								compound/blog/configure.zcml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								compound/blog/configure.zcml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,74 @@ | ||||||
|  | <!-- $Id$ --> | ||||||
|  | 
 | ||||||
|  | <configure | ||||||
|  |    xmlns:zope="http://namespaces.zope.org/zope" | ||||||
|  |    xmlns:browser="http://namespaces.zope.org/browser" | ||||||
|  |    i18n_domain="zope"> | ||||||
|  | 
 | ||||||
|  |   <zope:adapter | ||||||
|  |         factory="loops.compound.blog.post.BlogPost" | ||||||
|  |         provides="loops.compound.blog.interfaces.IBlogPost" | ||||||
|  |         trusted="True" /> | ||||||
|  |   <zope:class class="loops.compound.blog.post.BlogPost"> | ||||||
|  |     <require permission="zope.View" | ||||||
|  |         interface="loops.compound.blog.interfaces.IBlogPost" /> | ||||||
|  |     <require permission="zope.ManageContent" | ||||||
|  |         set_schema="loops.compound.blog.interfaces.IBlogPost" /> | ||||||
|  |   </zope:class> | ||||||
|  | 
 | ||||||
|  |   <zope:adapter factory="loops.compound.blog.schema.BlogPostSchemaFactory" /> | ||||||
|  | 
 | ||||||
|  |   <zope:adapter factory="loops.compound.blog.security.BlogPostSecuritySetter" /> | ||||||
|  | 
 | ||||||
|  |   <!-- views --> | ||||||
|  | 
 | ||||||
|  |   <zope:adapter | ||||||
|  |       name="blog.html" | ||||||
|  |       for="loops.interfaces.IConcept | ||||||
|  |            zope.publisher.interfaces.browser.IBrowserRequest" | ||||||
|  |       provides="zope.interface.Interface" | ||||||
|  |       factory="loops.compound.blog.browser.BlogView" | ||||||
|  |       permission="zope.View" | ||||||
|  |       /> | ||||||
|  | 
 | ||||||
|  |   <zope:adapter | ||||||
|  |       name="blogpost.html" | ||||||
|  |       for="loops.interfaces.IConcept | ||||||
|  |            zope.publisher.interfaces.browser.IBrowserRequest" | ||||||
|  |       provides="zope.interface.Interface" | ||||||
|  |       factory="loops.compound.blog.browser.BlogPostView" | ||||||
|  |       permission="zope.View" | ||||||
|  |       /> | ||||||
|  | 
 | ||||||
|  |   <browser:page | ||||||
|  |       name="create_blogpost.html" | ||||||
|  |       for="loops.interfaces.INode" | ||||||
|  |       class="loops.compound.blog.browser.CreateBlogPostForm" | ||||||
|  |       permission="zope.ManageContent" | ||||||
|  |       /> | ||||||
|  | 
 | ||||||
|  |   <browser:page | ||||||
|  |       name="edit_blogpost.html" | ||||||
|  |       for="loops.interfaces.INode" | ||||||
|  |       class="loops.compound.blog.browser.EditBlogPostForm" | ||||||
|  |       permission="zope.ManageContent" | ||||||
|  |       /> | ||||||
|  | 
 | ||||||
|  |   <zope:adapter | ||||||
|  |       name="create_blogpost" | ||||||
|  |       for="loops.browser.node.NodeView | ||||||
|  |            zope.publisher.interfaces.browser.IBrowserRequest" | ||||||
|  |       provides="zope.interface.Interface" | ||||||
|  |       factory="loops.compound.blog.browser.CreateBlogPost" | ||||||
|  |       permission="zope.ManageContent" | ||||||
|  |       /> | ||||||
|  | 
 | ||||||
|  |   <zope:adapter | ||||||
|  |       name="edit_blogpost" | ||||||
|  |       for="loops.browser.node.NodeView | ||||||
|  |            zope.publisher.interfaces.browser.IBrowserRequest" | ||||||
|  |       factory="loops.compound.blog.browser.EditBlogPost" | ||||||
|  |       permission="zope.ManageContent" | ||||||
|  |       /> | ||||||
|  | 
 | ||||||
|  | </configure> | ||||||
							
								
								
									
										62
									
								
								compound/blog/interfaces.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								compound/blog/interfaces.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,62 @@ | ||||||
|  | # | ||||||
|  | #  Copyright (c) 2008 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 | ||||||
|  | # | ||||||
|  | 
 | ||||||
|  | """ | ||||||
|  | Blogs (weblogs) and blog posts. | ||||||
|  | 
 | ||||||
|  | $Id$ | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | from datetime import datetime | ||||||
|  | from zope.interface import Interface, Attribute | ||||||
|  | from zope import interface, component, schema | ||||||
|  | 
 | ||||||
|  | from loops.compound.interfaces import ICompound | ||||||
|  | from loops.util import _ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class IBlogPost(ICompound): | ||||||
|  |     """ An item on a blog, sort of a diary item. | ||||||
|  |     """ | ||||||
|  | 
 | ||||||
|  |     date = schema.Datetime( | ||||||
|  |                 title=_(u'Date/Time'), | ||||||
|  |                 description=_(u'The date and time the information ' | ||||||
|  |                         'was posted.'), | ||||||
|  |                 required=True,) | ||||||
|  |     date.default_method = datetime.now | ||||||
|  |     private = schema.Bool( | ||||||
|  |                 title=_(u'Private'), | ||||||
|  |                 description=_(u'Check this field if the blog post ' | ||||||
|  |                         'should be accessible only for a limited audience.'), | ||||||
|  |                 required=False) | ||||||
|  |     creator = schema.ASCIILine( | ||||||
|  |                 title=_(u'Creator'), | ||||||
|  |                 description=_(u'The principal id of the user that created ' | ||||||
|  |                         'the blog post.'), | ||||||
|  |                 readonly=True, | ||||||
|  |                 required=False,) | ||||||
|  |     text = schema.Text( | ||||||
|  |                 title=_(u'Text'), | ||||||
|  |                 description=_(u'The text of your blog entry'), | ||||||
|  |                 required=False) | ||||||
|  |     privateComment = schema.Text( | ||||||
|  |                 title=_(u'Private Comment'), | ||||||
|  |                 description=_(u'A text that is not visible for other users.'), | ||||||
|  |                 required=False) | ||||||
|  | 
 | ||||||
							
								
								
									
										94
									
								
								compound/blog/post.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								compound/blog/post.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,94 @@ | ||||||
|  | # | ||||||
|  | #  Copyright (c) 2008 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 | ||||||
|  | # | ||||||
|  | 
 | ||||||
|  | """ | ||||||
|  | Blogs and blog posts. | ||||||
|  | 
 | ||||||
|  | $Id$ | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | from zope.cachedescriptors.property import Lazy | ||||||
|  | from zope.dublincore.interfaces import IZopeDublinCore | ||||||
|  | from zope.interface import implements | ||||||
|  | from zope.event import notify | ||||||
|  | from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent | ||||||
|  | from zope import schema | ||||||
|  | from zope.traversing.api import getName | ||||||
|  | 
 | ||||||
|  | from loops.common import adapted | ||||||
|  | from loops.compound.base import Compound | ||||||
|  | from loops.compound.blog.interfaces import IBlogPost | ||||||
|  | from loops.resource import Resource | ||||||
|  | from loops.security.common import restrictView | ||||||
|  | from loops.setup import addAndConfigureObject | ||||||
|  | from loops.type import TypeInterfaceSourceList | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | TypeInterfaceSourceList.typeInterfaces += (IBlogPost,) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class BlogPost(Compound): | ||||||
|  | 
 | ||||||
|  |     implements(IBlogPost) | ||||||
|  | 
 | ||||||
|  |     _adapterAttributes = Compound._adapterAttributes + ('text', 'private', 'creator',) | ||||||
|  |     _contextAttributes = Compound._contextAttributes + ['date', 'privateComment'] | ||||||
|  | 
 | ||||||
|  |     defaultTextContentType = 'text/restructured' | ||||||
|  |     textContentType = defaultTextContentType | ||||||
|  | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def isPartOf(self): | ||||||
|  |         return self.context.getLoopsRoot().getConceptManager()['ispartof'] | ||||||
|  | 
 | ||||||
|  |     def getPrivate(self): | ||||||
|  |         return getattr(self.context, '_private', False) | ||||||
|  |     def setPrivate(self, value): | ||||||
|  |         self.context._private = value | ||||||
|  |         restrictView(self.context, revert=not value) | ||||||
|  |         for r in self.context.getResources([self.isPartOf], noSecurityCheck=True): | ||||||
|  |             restrictView(r, revert=not value) | ||||||
|  |     private = property(getPrivate, setPrivate) | ||||||
|  | 
 | ||||||
|  |     def getText(self): | ||||||
|  |         res = self.getParts() | ||||||
|  |         if len(res) > 0: | ||||||
|  |             return adapted(res[0]).data | ||||||
|  |         return u'' | ||||||
|  |     def setText(self, value): | ||||||
|  |         res = self.getParts() | ||||||
|  |         if len(res) > 0: | ||||||
|  |             res = adapted(res[0]) | ||||||
|  |         else: | ||||||
|  |             tTextDocument = self.conceptManager['textdocument'] | ||||||
|  |             name = getName(self.context) + '_text' | ||||||
|  |             res = addAndConfigureObject(self.resourceManager, Resource, name, | ||||||
|  |                     title=self.title, contentType=self.defaultTextContentType, | ||||||
|  |                     resourceType=tTextDocument) | ||||||
|  |             self.add(res, position=0) | ||||||
|  |             notify(ObjectCreatedEvent(res)) | ||||||
|  |             res = adapted(res) | ||||||
|  |         res.data = value | ||||||
|  |         notify(ObjectModifiedEvent(res.context)) | ||||||
|  |     text = property(getText, setText) | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def creator(self): | ||||||
|  |         cr = IZopeDublinCore(self.context).creators | ||||||
|  |         return cr and cr[0] or None | ||||||
|  | 
 | ||||||
							
								
								
									
										39
									
								
								compound/blog/schema.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								compound/blog/schema.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,39 @@ | ||||||
|  | # | ||||||
|  | #  Copyright (c) 2008 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 | ||||||
|  | # | ||||||
|  | 
 | ||||||
|  | """ | ||||||
|  | Specialized schema factories | ||||||
|  | 
 | ||||||
|  | $Id$ | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | from zope.component import adapts | ||||||
|  | 
 | ||||||
|  | from cybertools.composer.schema.factory import SchemaFactory | ||||||
|  | from loops.compound.blog.interfaces import IBlogPost | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class BlogPostSchemaFactory(SchemaFactory): | ||||||
|  | 
 | ||||||
|  |     adapts(IBlogPost) | ||||||
|  | 
 | ||||||
|  |     def __call__(self, interface, **kw): | ||||||
|  |         schema = super(BlogPostSchemaFactory, self).__call__(interface, **kw) | ||||||
|  |         schema.fields.text.height = 10 | ||||||
|  |         return schema | ||||||
|  | 
 | ||||||
							
								
								
									
										66
									
								
								compound/blog/security.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								compound/blog/security.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,66 @@ | ||||||
|  | # | ||||||
|  | #  Copyright (c) 2008 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 | ||||||
|  | # | ||||||
|  | 
 | ||||||
|  | """ | ||||||
|  | Security settings for blogs and blog posts. | ||||||
|  | 
 | ||||||
|  | $Id$ | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | from zope.cachedescriptors.property import Lazy | ||||||
|  | from zope.component import adapts | ||||||
|  | from zope.interface import implements | ||||||
|  | from zope.traversing.api import getName | ||||||
|  | 
 | ||||||
|  | from loops.compound.blog.interfaces import IBlogPost | ||||||
|  | from loops.security.common import allowEditingForOwner, assignOwner, restrictView | ||||||
|  | from loops.security.common import getCurrentPrincipal | ||||||
|  | from loops.security.setter import BaseSecuritySetter | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class BlogPostSecuritySetter(BaseSecuritySetter): | ||||||
|  | 
 | ||||||
|  |     adapts(IBlogPost) | ||||||
|  | 
 | ||||||
|  |     def setDefaultRolePermissions(self): | ||||||
|  |         allowEditingForOwner(self.context.context) | ||||||
|  | 
 | ||||||
|  |     def setDefaultPrincipalRoles(self): | ||||||
|  |         assignOwner(self.context.context, self.principalId) | ||||||
|  | 
 | ||||||
|  |     def setAcquiredRolePermissions(self, relation, revert=False): | ||||||
|  |         if isAcquiring(relation.predicate): | ||||||
|  |             allowEditingForOwner(relation.second, revert=revert) | ||||||
|  |             if self.context.private: | ||||||
|  |                 restrictView(relation.second, revert=revert) | ||||||
|  | 
 | ||||||
|  |     def setAcquiredPrincipalRoles(self, relation, revert=False): | ||||||
|  |         if isAcquiring(relation.predicate): | ||||||
|  |             if revert: | ||||||
|  |                 removeOwner(relation.second, self.principalId) | ||||||
|  |             else: | ||||||
|  |                 assignOwner(relation.second, self.principalId) | ||||||
|  | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def principalId(self): | ||||||
|  |         return getCurrentPrincipal().id | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def isAcquiring(predicate): | ||||||
|  |     # TODO: use a predicate property for this. | ||||||
|  |     return getName(predicate) in ('ispartof',) | ||||||
							
								
								
									
										71
									
								
								compound/blog/view_macros.pt
									
										
									
									
									
										Executable file
									
								
							
							
						
						
									
										71
									
								
								compound/blog/view_macros.pt
									
										
									
									
									
										Executable file
									
								
							|  | @ -0,0 +1,71 @@ | ||||||
|  | <!-- ZPT macros for loops.knowledge.glossary views | ||||||
|  |      $Id$ --> | ||||||
|  | 
 | ||||||
|  | <metal:block define-macro="blog"> | ||||||
|  |   <metal:title use-macro="item/conceptMacros/concepttitle" /> | ||||||
|  |   <div tal:repeat="related item/blogPosts" | ||||||
|  |            class="blog"> | ||||||
|  |     <tal:child define="data related/data"> | ||||||
|  |       <h1 class="headline"> | ||||||
|  |         <a href="#" | ||||||
|  |            tal:content="related/title" | ||||||
|  |            tal:attributes="href python: view.getUrlForTarget(related);">Post</a> | ||||||
|  |       </h1> | ||||||
|  |       <div class="info" | ||||||
|  |            tal:define="url data/creatorUrl|nothing"> | ||||||
|  |         <span tal:content="data/date">2008-01-02</span> / | ||||||
|  |         <a tal:omit-tag="not:url" | ||||||
|  |            tal:content="data/creator" | ||||||
|  |            tal:attributes="href url">Will Smith</a> | ||||||
|  |       </div> | ||||||
|  |       <div class="description" | ||||||
|  |            tal:define="description data/description" | ||||||
|  |            tal:condition="description"> | ||||||
|  |         <span tal:content="structure python: | ||||||
|  |                 item.renderText(description, 'text/restructured')">Description</span></div> | ||||||
|  |       <div class="text" | ||||||
|  |            tal:condition="nothing" | ||||||
|  |            xtal:condition="python: repeat['related'].index() < 3"> | ||||||
|  |         <span tal:content="structure python: | ||||||
|  |                 item.renderText(data['text'], related.adapted.textContentType)"></span> | ||||||
|  |       </div> | ||||||
|  |     </tal:child> | ||||||
|  |   </div> | ||||||
|  |   <metal:resources use-macro="item/conceptMacros/conceptchildren" /> | ||||||
|  |   <metal:resources use-macro="item/conceptMacros/conceptresources" /> | ||||||
|  | </metal:block> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | <div metal:define-macro="blogpost" | ||||||
|  |      tal:define="data item/data" | ||||||
|  |      class="blogpost"> | ||||||
|  |   <h1 tal:attributes="ondblclick item/openEditWindow"> | ||||||
|  |     <span tal:content="item/title">Title</span> | ||||||
|  |   </h1> | ||||||
|  |   <div class="info" | ||||||
|  |        tal:define="url data/creatorUrl|nothing"> | ||||||
|  |     <span tal:content="data/date">2008-01-02</span> / | ||||||
|  |     <a tal:omit-tag="not:url" | ||||||
|  |        tal:content="data/creator" | ||||||
|  |        tal:attributes="href url">Will Smith</a> | ||||||
|  |     <span tal:condition="item/adapted/private"> | ||||||
|  |       (<span i18n:translate="">Private</span>) | ||||||
|  |     </span> | ||||||
|  |   </div> | ||||||
|  |   <div class="description" | ||||||
|  |        tal:define="description description|item/description" | ||||||
|  |        tal:condition="description"> | ||||||
|  |     <span tal:content="structure python: | ||||||
|  |                 item.renderText(description, 'text/restructured')">Description</span> | ||||||
|  |   </div> | ||||||
|  |   <div class="text" | ||||||
|  |        tal:content="structure item/render">Here comes the text...</div> | ||||||
|  |   <div class="comment" | ||||||
|  |        tal:define="comment data/privateComment" | ||||||
|  |        tal:condition="comment"> | ||||||
|  |     <h4 i18n:translate="" class="headline">Private Comment</h4> | ||||||
|  |     <div tal:content="structure python: | ||||||
|  |                 item.renderText(comment, 'text/restructured')">Comment</div></div> | ||||||
|  |   <metal:resources use-macro="item/conceptMacros/conceptchildren" /> | ||||||
|  |   <metal:resources use-macro="item/conceptMacros/conceptresources" /> | ||||||
|  | </div> | ||||||
|  | @ -28,13 +28,14 @@ $Id$ | ||||||
| from zope.interface import Interface, Attribute | from zope.interface import Interface, Attribute | ||||||
| from zope import interface, component, schema | from zope import interface, component, schema | ||||||
| 
 | 
 | ||||||
|  | from loops.interfaces import IConceptSchema | ||||||
| from loops.util import _ | from loops.util import _ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| compoundPredicateName = 'ispartof' | compoundPredicateName = 'ispartof' | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class ICompound(Interface): | class ICompound(IConceptSchema): | ||||||
|     """ A compound is a concept that is built up of other objects, its |     """ A compound is a concept that is built up of other objects, its | ||||||
|         parts or components. |         parts or components. | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										87
									
								
								concept.py
									
										
									
									
									
								
							
							
						
						
									
										87
									
								
								concept.py
									
										
									
									
									
								
							|  | @ -1,5 +1,5 @@ | ||||||
| # | # | ||||||
| #  Copyright (c) 2007 Helmut Merz helmutm@cy55.de | #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||||
| # | # | ||||||
| #  This program is free software; you can redistribute it and/or modify | #  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 | #  it under the terms of the GNU General Public License as published by | ||||||
|  | @ -27,6 +27,8 @@ from zope.app.container.btree import BTreeContainer | ||||||
| from zope.app.container.contained import Contained | from zope.app.container.contained import Contained | ||||||
| from zope.cachedescriptors.property import Lazy | from zope.cachedescriptors.property import Lazy | ||||||
| from zope.component import adapts | from zope.component import adapts | ||||||
|  | from zope.component.interfaces import ObjectEvent | ||||||
|  | from zope.event import notify | ||||||
| from zope.interface import implements | from zope.interface import implements | ||||||
| from zope.interface import alsoProvides, directlyProvides, directlyProvidedBy | from zope.interface import alsoProvides, directlyProvides, directlyProvidedBy | ||||||
| from zope.security.proxy import removeSecurityProxy, isinstance | from zope.security.proxy import removeSecurityProxy, isinstance | ||||||
|  | @ -35,7 +37,6 @@ from persistent import Persistent | ||||||
| 
 | 
 | ||||||
| from cybertools.relation import DyadicRelation | from cybertools.relation import DyadicRelation | ||||||
| from cybertools.relation.registry import getRelations | from cybertools.relation.registry import getRelations | ||||||
| from cybertools.relation.registry import getRelationSingle, setRelationSingle |  | ||||||
| from cybertools.relation.interfaces import IRelationRegistry, IRelatable | from cybertools.relation.interfaces import IRelationRegistry, IRelatable | ||||||
| from cybertools.typology.interfaces import IType, ITypeManager | from cybertools.typology.interfaces import IType, ITypeManager | ||||||
| from cybertools.util.jeep import Jeep | from cybertools.util.jeep import Jeep | ||||||
|  | @ -46,6 +47,8 @@ from loops.interfaces import IConcept, IConceptRelation, IConceptView | ||||||
| from loops.interfaces import IConceptManager, IConceptManagerContained | from loops.interfaces import IConceptManager, IConceptManagerContained | ||||||
| from loops.interfaces import ILoopsContained | from loops.interfaces import ILoopsContained | ||||||
| from loops.interfaces import IIndexAttributes | from loops.interfaces import IIndexAttributes | ||||||
|  | from loops.interfaces import IAssignmentEvent, IDeassignmentEvent | ||||||
|  | from loops.security.common import canListObject | ||||||
| from loops import util | from loops import util | ||||||
| from loops.view import TargetRelation | from loops.view import TargetRelation | ||||||
| 
 | 
 | ||||||
|  | @ -159,29 +162,35 @@ class Concept(Contained, Persistent): | ||||||
|         if relationships is None: |         if relationships is None: | ||||||
|             relationships = [TargetRelation] |             relationships = [TargetRelation] | ||||||
|         rels = getRelations(second=self, relationships=relationships) |         rels = getRelations(second=self, relationships=relationships) | ||||||
|         return [r.first for r in rels] |         return [r.first for r in rels if canListObject(r.first)] | ||||||
| 
 | 
 | ||||||
|     def getChildRelations(self, predicates=None, child=None, sort='default'): |     def getChildRelations(self, predicates=None, child=None, sort='default', | ||||||
|  |                           noSecurityCheck=False): | ||||||
|         predicates = predicates is None and ['*'] or predicates |         predicates = predicates is None and ['*'] or predicates | ||||||
|         relationships = [ConceptRelation(self, None, p) for p in predicates] |         relationships = [ConceptRelation(self, None, p) for p in predicates] | ||||||
|         if sort == 'default': |         if sort == 'default': | ||||||
|             sort = lambda x: (x.order, x.second.title.lower()) |             sort = lambda x: (x.order, x.second.title.lower()) | ||||||
|         return sorted(getRelations(first=self, second=child, relationships=relationships), |         rels = (r for r in getRelations(self, child, relationships=relationships) | ||||||
|                       key=sort) |                   if canListObject(r.second, noSecurityCheck)) | ||||||
|  |         return sorted(rels, key=sort) | ||||||
| 
 | 
 | ||||||
|     def getChildren(self, predicates=None, sort='default'): |     def getChildren(self, predicates=None, sort='default', noSecurityCheck=False): | ||||||
|         return [r.second for r in self.getChildRelations(predicates, sort=sort)] |         return [r.second for r in self.getChildRelations(predicates, sort=sort, | ||||||
|  |                                                 noSecurityCheck=noSecurityCheck)] | ||||||
| 
 | 
 | ||||||
|     def getParentRelations (self, predicates=None, parent=None, sort='default'): |     def getParentRelations (self, predicates=None, parent=None, sort='default', | ||||||
|  |                             noSecurityCheck=False): | ||||||
|         predicates = predicates is None and ['*'] or predicates |         predicates = predicates is None and ['*'] or predicates | ||||||
|         relationships = [ConceptRelation(None, self, p) for p in predicates] |         relationships = [ConceptRelation(None, self, p) for p in predicates] | ||||||
|         if sort == 'default': |         if sort == 'default': | ||||||
|             sort = lambda x: (x.order, x.first.title.lower()) |             sort = lambda x: (x.order, x.first.title.lower()) | ||||||
|         return sorted(getRelations(first=parent, second=self, relationships=relationships), |         rels = (r for r in getRelations(parent, self, relationships=relationships) | ||||||
|                       key=sort) |                   if canListObject(r.first, noSecurityCheck)) | ||||||
|  |         return sorted(rels, key=sort) | ||||||
| 
 | 
 | ||||||
|     def getParents(self, predicates=None, sort='default'): |     def getParents(self, predicates=None, sort='default', noSecurityCheck=False): | ||||||
|         return [r.first for r in self.getParentRelations(predicates, sort=sort)] |         return [r.first for r in self.getParentRelations(predicates, sort=sort, | ||||||
|  |                                                 noSecurityCheck=noSecurityCheck)] | ||||||
| 
 | 
 | ||||||
|     def assignChild(self, concept, predicate=None, order=0, relevance=1.0): |     def assignChild(self, concept, predicate=None, order=0, relevance=1.0): | ||||||
|         if predicate is None: |         if predicate is None: | ||||||
|  | @ -194,6 +203,7 @@ class Concept(Contained, Persistent): | ||||||
|             rel.relevance = relevance |             rel.relevance = relevance | ||||||
|         # TODO (?): avoid duplicates |         # TODO (?): avoid duplicates | ||||||
|         registry.register(rel) |         registry.register(rel) | ||||||
|  |         notify(AssignmentEvent(self, rel)) | ||||||
| 
 | 
 | ||||||
|     def setChildren(self, predicate, concepts): |     def setChildren(self, predicate, concepts): | ||||||
|         existing = self.getChildren([predicate]) |         existing = self.getChildren([predicate]) | ||||||
|  | @ -211,6 +221,7 @@ class Concept(Contained, Persistent): | ||||||
|         registry = component.getUtility(IRelationRegistry) |         registry = component.getUtility(IRelationRegistry) | ||||||
|         for rel in self.getChildRelations(predicates, child): |         for rel in self.getChildRelations(predicates, child): | ||||||
|             if order is None or rel.order == order: |             if order is None or rel.order == order: | ||||||
|  |                 notify(DeassignmentEvent(self, rel)) | ||||||
|                 registry.unregister(rel) |                 registry.unregister(rel) | ||||||
| 
 | 
 | ||||||
|     def deassignParent(self, parent, predicates=None): |     def deassignParent(self, parent, predicates=None): | ||||||
|  | @ -218,17 +229,19 @@ class Concept(Contained, Persistent): | ||||||
| 
 | 
 | ||||||
|     # resource relations |     # resource relations | ||||||
| 
 | 
 | ||||||
|     def getResourceRelations(self, predicates=None, resource=None, sort='default'): |     def getResourceRelations(self, predicates=None, resource=None, sort='default', | ||||||
|  |                              noSecurityCheck=False): | ||||||
|         predicates = predicates is None and ['*'] or predicates |         predicates = predicates is None and ['*'] or predicates | ||||||
|         relationships = [ResourceRelation(self, None, p) for p in predicates] |         relationships = [ResourceRelation(self, None, p) for p in predicates] | ||||||
|         if sort == 'default': |         if sort == 'default': | ||||||
|             sort = lambda x: (x.order, x.second.title.lower()) |             sort = lambda x: (x.order, x.second.title.lower()) | ||||||
|         return sorted(getRelations( |         rels = (r for r in getRelations(self, resource, relationships=relationships) | ||||||
|                         first=self, second=resource, relationships=relationships), |                   if canListObject(r.second, noSecurityCheck)) | ||||||
|                       key=sort) |         return sorted(rels, key=sort) | ||||||
| 
 | 
 | ||||||
|     def getResources(self, predicates=None): |     def getResources(self, predicates=None, sort='default', noSecurityCheck=False): | ||||||
|         return [r.second for r in self.getResourceRelations(predicates)] |         return [r.second for r in self.getResourceRelations(predicates, sort=sort, | ||||||
|  |                                                 noSecurityCheck=noSecurityCheck)] | ||||||
| 
 | 
 | ||||||
|     def assignResource(self, resource, predicate=None, order=0, relevance=1.0): |     def assignResource(self, resource, predicate=None, order=0, relevance=1.0): | ||||||
|         if predicate is None: |         if predicate is None: | ||||||
|  | @ -241,12 +254,27 @@ class Concept(Contained, Persistent): | ||||||
|             rel.relevance = relevance |             rel.relevance = relevance | ||||||
|         # TODO (?): avoid duplicates |         # TODO (?): avoid duplicates | ||||||
|         registry.register(rel) |         registry.register(rel) | ||||||
|  |         notify(AssignmentEvent(self, rel)) | ||||||
| 
 | 
 | ||||||
|     def deassignResource(self, resource, predicates=None): |     def deassignResource(self, resource, predicates=None, order=None): | ||||||
|         registry = component.getUtility(IRelationRegistry) |         registry = component.getUtility(IRelationRegistry) | ||||||
|         for rel in self.getResourceRelations(predicates, resource): |         for rel in self.getResourceRelations(predicates, resource): | ||||||
|  |             if order is None or rel.order == order: | ||||||
|  |                 notify(DeassignmentEvent(self, rel)) | ||||||
|                 registry.unregister(rel) |                 registry.unregister(rel) | ||||||
| 
 | 
 | ||||||
|  |     # combined children+resources query | ||||||
|  | 
 | ||||||
|  |     def getChildAndResourceRelations(self, predicates=None, sort='default'): | ||||||
|  |         if predicates is None: | ||||||
|  |             predicates = [self.getConceptManager().getDefaultPredicate()] | ||||||
|  |         relationships = ([ResourceRelation(self, None, p) for p in predicates] | ||||||
|  |                        + [ConceptRelation(None, self, p) for p in predicates]) | ||||||
|  |         if sort == 'default': | ||||||
|  |             sort = lambda x: (x.order, x.second.title.lower()) | ||||||
|  |         rels = (r for r in getRelations(self, child, relationships=relationships) | ||||||
|  |                   if canListObject(r.second)) | ||||||
|  |         return sorted(rels, key=sort) | ||||||
| 
 | 
 | ||||||
| # concept manager | # concept manager | ||||||
| 
 | 
 | ||||||
|  | @ -360,3 +388,22 @@ class IndexAttributes(object): | ||||||
|         return ' '.join((getName(context), |         return ' '.join((getName(context), | ||||||
|                          context.title, context.description)).strip() |                          context.title, context.description)).strip() | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | # events | ||||||
|  | 
 | ||||||
|  | class AssignmentEvent(ObjectEvent): | ||||||
|  | 
 | ||||||
|  |     implements(IAssignmentEvent) | ||||||
|  | 
 | ||||||
|  |     def __init__(self, obj, relation): | ||||||
|  |         super(AssignmentEvent, self).__init__(obj) | ||||||
|  |         self.relation = relation | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class DeassignmentEvent(ObjectEvent): | ||||||
|  | 
 | ||||||
|  |     implements(IDeassignmentEvent) | ||||||
|  | 
 | ||||||
|  |     def __init__(self, obj, relation): | ||||||
|  |         super(DeassignmentEvent, self).__init__(obj) | ||||||
|  |         self.relation = relation | ||||||
|  |  | ||||||
|  | @ -9,43 +9,14 @@ | ||||||
| 
 | 
 | ||||||
|   <!-- security definitions --> |   <!-- security definitions --> | ||||||
| 
 | 
 | ||||||
|   <permission |   <include file="security.zcml" /> | ||||||
|       id="loops.xmlrpc.ManageConcepts" |  | ||||||
|       title="[loops-xmlrpc-manage-concepts-permission] loops: Manage Concepts (XML-RPC)" |  | ||||||
|       /> |  | ||||||
| 
 |  | ||||||
|   <role |  | ||||||
|       id="loops.xmlrpc.ConceptManager" |  | ||||||
|       title="[xmlrpc-manage-concepts-role] loops: Concept Manager (XML-RPC)" /> |  | ||||||
| 
 |  | ||||||
|   <grant |  | ||||||
|       permission="loops.xmlrpc.ManageConcepts" |  | ||||||
|       role="loops.xmlrpc.ConceptManager" /> |  | ||||||
| 
 |  | ||||||
|   <permission |  | ||||||
|       id="loops.ManageSite" |  | ||||||
|       title="[loops-manage-site-permission] loops: Manage Site" |  | ||||||
|       /> |  | ||||||
| 
 |  | ||||||
|   <role |  | ||||||
|       id="loops.SiteManager" |  | ||||||
|       title="[loops-manage-site-role] loops: Site Manager" /> |  | ||||||
| 
 |  | ||||||
|   <grant |  | ||||||
|       permission="loops.ManageSite" |  | ||||||
|       role="loops.SiteManager" /> |  | ||||||
| 
 |  | ||||||
|   <grant |  | ||||||
|       permission="loops.xmlrpc.ManageConcepts" |  | ||||||
|       role="loops.SiteManager" /> |  | ||||||
| 
 | 
 | ||||||
|   <!-- event subscribers --> |   <!-- event subscribers --> | ||||||
| 
 | 
 | ||||||
|   <subscriber |   <subscriber | ||||||
|         for=".interfaces.ITargetRelation |         for=".interfaces.ITargetRelation | ||||||
|              cybertools.relation.interfaces.IRelationInvalidatedEvent" |              cybertools.relation.interfaces.IRelationInvalidatedEvent" | ||||||
|         handler=".util.removeTargetRelation" |         handler=".util.removeTargetRelation" /> | ||||||
|         /> |  | ||||||
| 
 | 
 | ||||||
|   <!-- loops top-level container --> |   <!-- loops top-level container --> | ||||||
| 
 | 
 | ||||||
|  | @ -120,22 +91,22 @@ | ||||||
|       type="zope.app.content.interfaces.IContentType" /> |       type="zope.app.content.interfaces.IContentType" /> | ||||||
| 
 | 
 | ||||||
|   <class class=".concept.Concept"> |   <class class=".concept.Concept"> | ||||||
|  |     <implements interface="zope.annotation.interfaces.IAttributeAnnotatable" /> | ||||||
|  |     <factory id="loops.Concept" description="Concept object" /> | ||||||
|  |     <require permission="zope.View" interface=".interfaces.IConcept" /> | ||||||
|  |     <require permission="zope.ManageContent" set_schema=".interfaces.IConcept" /> | ||||||
|  |   </class> | ||||||
| 
 | 
 | ||||||
|     <implements |   <class class="loops.concept.ConceptRelation"> | ||||||
|        interface="zope.annotation.interfaces.IAttributeAnnotatable" /> |     <require permission="zope.View" interface="loops.interfaces.IConceptRelation" /> | ||||||
| 
 |     <require permission="zope.ManageContent" | ||||||
|     <factory |              set_schema="loops.interfaces.IConceptRelation" /> | ||||||
|         id="loops.Concept" |   </class> | ||||||
|         description="Concept object" /> |  | ||||||
| 
 |  | ||||||
|     <require |  | ||||||
|         permission="zope.View" |  | ||||||
|         interface=".interfaces.IConcept" /> |  | ||||||
| 
 |  | ||||||
|     <require |  | ||||||
|         permission="zope.ManageContent" |  | ||||||
|         set_schema=".interfaces.IConcept" /> |  | ||||||
| 
 | 
 | ||||||
|  |   <class class="loops.concept.ResourceRelation"> | ||||||
|  |     <require permission="zope.View" interface="loops.interfaces.IConceptRelation" /> | ||||||
|  |     <require permission="zope.ManageContent" | ||||||
|  |              set_schema="loops.interfaces.IConceptRelation" /> | ||||||
|   </class> |   </class> | ||||||
| 
 | 
 | ||||||
|   <!-- resource manager and resource --> |   <!-- resource manager and resource --> | ||||||
|  | @ -225,7 +196,6 @@ | ||||||
|         interface=".interfaces.IBaseResource |         interface=".interfaces.IBaseResource | ||||||
|                    zope.size.interfaces.ISized" /> |                    zope.size.interfaces.ISized" /> | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|     <require |     <require | ||||||
|         permission="zope.ManageContent" |         permission="zope.ManageContent" | ||||||
|         set_schema=".interfaces.IBaseResource" /> |         set_schema=".interfaces.IBaseResource" /> | ||||||
|  | @ -398,6 +368,10 @@ | ||||||
|   <adapter factory="cybertools.composer.schema.field.FieldInstance" /> |   <adapter factory="cybertools.composer.schema.field.FieldInstance" /> | ||||||
|   <adapter factory="cybertools.composer.schema.field.NumberFieldInstance" |   <adapter factory="cybertools.composer.schema.field.NumberFieldInstance" | ||||||
|            name="number" /> |            name="number" /> | ||||||
|  |   <adapter factory="cybertools.composer.schema.field.DateFieldInstance" | ||||||
|  |            name="date" /> | ||||||
|  |   <adapter factory="cybertools.composer.schema.field.BooleanFieldInstance" | ||||||
|  |            name="boolean" /> | ||||||
|   <adapter factory="cybertools.composer.schema.field.FileUploadFieldInstance" |   <adapter factory="cybertools.composer.schema.field.FileUploadFieldInstance" | ||||||
|            name="fileupload" /> |            name="fileupload" /> | ||||||
| 
 | 
 | ||||||
|  | @ -480,7 +454,9 @@ | ||||||
| 
 | 
 | ||||||
|   <include package=".browser" /> |   <include package=".browser" /> | ||||||
|   <include package=".classifier" /> |   <include package=".classifier" /> | ||||||
|  |   <include package=".compound.blog" /> | ||||||
|   <include package=".constraint" /> |   <include package=".constraint" /> | ||||||
|  |   <include package=".external" /> | ||||||
|   <include package=".i18n" /> |   <include package=".i18n" /> | ||||||
|   <include package=".integrator" /> |   <include package=".integrator" /> | ||||||
|   <include package=".knowledge" /> |   <include package=".knowledge" /> | ||||||
|  | @ -488,6 +464,7 @@ | ||||||
|   <include package=".process" /> |   <include package=".process" /> | ||||||
|   <include package=".rest" /> |   <include package=".rest" /> | ||||||
|   <include package=".search" /> |   <include package=".search" /> | ||||||
|  |   <include package=".security" /> | ||||||
|   <include package=".versioning" /> |   <include package=".versioning" /> | ||||||
|   <include package=".xmlrpc" /> |   <include package=".xmlrpc" /> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -32,7 +32,7 @@ configuration): | ||||||
|   >>> #sorted(concepts) |   >>> #sorted(concepts) | ||||||
|   >>> #sorted(resources) |   >>> #sorted(resources) | ||||||
|   >>> len(concepts) + len(resources) |   >>> len(concepts) + len(resources) | ||||||
|   35 |   38 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| Type- and Text-based Queries | Type- and Text-based Queries | ||||||
|  | @ -41,11 +41,11 @@ Type- and Text-based Queries | ||||||
|   >>> from loops.expert import query |   >>> from loops.expert import query | ||||||
|   >>> qu = query.Title('ty*') |   >>> qu = query.Title('ty*') | ||||||
|   >>> list(qu.apply()) |   >>> list(qu.apply()) | ||||||
|   [0, 1, 41] |   [0, 1, 50] | ||||||
| 
 | 
 | ||||||
|   >>> qu = query.Type('loops:*') |   >>> qu = query.Type('loops:*') | ||||||
|   >>> len(list(qu.apply())) |   >>> len(list(qu.apply())) | ||||||
|   35 |   38 | ||||||
| 
 | 
 | ||||||
|   >>> qu = query.Type('loops:concept:predicate') |   >>> qu = query.Type('loops:concept:predicate') | ||||||
|   >>> len(list(qu.apply())) |   >>> len(list(qu.apply())) | ||||||
|  | @ -67,7 +67,7 @@ syntax (that in turn is based on hurry.query). | ||||||
|   >>> stateNew = concepts['new'] |   >>> stateNew = concepts['new'] | ||||||
|   >>> qu = query.Resources(stateNew) |   >>> qu = query.Resources(stateNew) | ||||||
|   >>> list(qu.apply()) |   >>> list(qu.apply()) | ||||||
|   [57, 62] |   [66, 71] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| Fin de partie | Fin de partie | ||||||
|  |  | ||||||
|  | @ -5,63 +5,32 @@ $Id$ | ||||||
| """ | """ | ||||||
| 
 | 
 | ||||||
| from zope import component | from zope import component | ||||||
| from zope.app.catalog.catalog import Catalog |  | ||||||
| from zope.app.catalog.interfaces import ICatalog | from zope.app.catalog.interfaces import ICatalog | ||||||
| from zope.app.catalog.field import FieldIndex |  | ||||||
| from zope.app.catalog.text import TextIndex |  | ||||||
| 
 | 
 | ||||||
| from cybertools.relation.tests import IntIdsStub |  | ||||||
| from cybertools.relation.registry import RelationRegistry |  | ||||||
| from cybertools.relation.interfaces import IRelationRegistry |  | ||||||
| from cybertools.relation.registry import IndexableRelationAdapter |  | ||||||
| from cybertools.typology.interfaces import IType | from cybertools.typology.interfaces import IType | ||||||
| 
 |  | ||||||
| from loops.base import Loops |  | ||||||
| from loops import util | from loops import util | ||||||
| from loops.interfaces import IIndexAttributes |  | ||||||
| from loops.concept import Concept | from loops.concept import Concept | ||||||
| from loops.concept import IndexAttributes as ConceptIndexAttributes |  | ||||||
| from loops.resource import Resource | from loops.resource import Resource | ||||||
| from loops.resource import IndexAttributes as ResourceIndexAttributes |  | ||||||
| from loops.knowledge.setup import SetupManager as KnowledgeSetupManager | from loops.knowledge.setup import SetupManager as KnowledgeSetupManager | ||||||
| from loops.setup import SetupManager, addObject | from loops.setup import SetupManager, addObject | ||||||
|  | from loops.tests.setup import TestSite as BaseTestSite | ||||||
| from loops.type import ConceptType, ResourceType, TypeConcept | from loops.type import ConceptType, ResourceType, TypeConcept | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class TestSite(object): | class TestSite(BaseTestSite): | ||||||
| 
 | 
 | ||||||
|     def __init__(self, site): |     def __init__(self, site): | ||||||
|         self.site = site |         self.site = site | ||||||
| 
 | 
 | ||||||
|     def setup(self): |     def setup(self): | ||||||
|  |         super(TestSite, self).setup() | ||||||
|         site = self.site |         site = self.site | ||||||
| 
 |         loopsRoot = site['loops'] | ||||||
|         component.provideUtility(IntIdsStub()) |  | ||||||
|         relations = RelationRegistry() |  | ||||||
|         relations.setupIndexes() |  | ||||||
|         component.provideUtility(relations, IRelationRegistry) |  | ||||||
|         component.provideAdapter(IndexableRelationAdapter) |  | ||||||
| 
 |  | ||||||
|         component.provideAdapter(ConceptType) |  | ||||||
|         component.provideAdapter(ResourceType) |  | ||||||
|         component.provideAdapter(TypeConcept) |  | ||||||
| 
 |  | ||||||
|         catalog = Catalog() |  | ||||||
|         component.provideUtility(catalog, ICatalog) |  | ||||||
| 
 |  | ||||||
|         catalog['loops_title'] = TextIndex('title', IIndexAttributes, True) |  | ||||||
|         catalog['loops_text'] = TextIndex('text', IIndexAttributes, True) |  | ||||||
|         catalog['loops_type'] = FieldIndex('tokenForSearch', IType, False) |  | ||||||
| 
 |  | ||||||
|         loopsRoot = site['loops'] = Loops() |  | ||||||
| 
 | 
 | ||||||
|         component.provideAdapter(KnowledgeSetupManager, name='knowledge') |         component.provideAdapter(KnowledgeSetupManager, name='knowledge') | ||||||
|         setup = SetupManager(loopsRoot) |         setup = SetupManager(loopsRoot) | ||||||
|         concepts, resources, views = setup.setup() |         concepts, resources, views = setup.setup() | ||||||
| 
 | 
 | ||||||
|         component.provideAdapter(ConceptIndexAttributes) |  | ||||||
|         component.provideAdapter(ResourceIndexAttributes) |  | ||||||
| 
 |  | ||||||
|         tType = concepts.getTypeConcept() |         tType = concepts.getTypeConcept() | ||||||
|         tDomain = concepts['domain'] |         tDomain = concepts['domain'] | ||||||
|         tTextDocument = concepts['textdocument'] |         tTextDocument = concepts['textdocument'] | ||||||
|  | @ -123,6 +92,7 @@ class TestSite(object): | ||||||
|         d003.assignConcept(stateNew) |         d003.assignConcept(stateNew) | ||||||
|         d003.assignConcept(dtStudy) |         d003.assignConcept(dtStudy) | ||||||
| 
 | 
 | ||||||
|  |         catalog = component.getUtility(ICatalog) | ||||||
|         for c in concepts.values(): |         for c in concepts.values(): | ||||||
|              catalog.index_doc(int(util.getUidForObject(c)), c) |              catalog.index_doc(int(util.getUidForObject(c)), c) | ||||||
|         for r in resources.values(): |         for r in resources.values(): | ||||||
|  | @ -130,4 +100,3 @@ class TestSite(object): | ||||||
| 
 | 
 | ||||||
|         return concepts, resources, views |         return concepts, resources, views | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|  |  | ||||||
							
								
								
									
										90
									
								
								external/README.txt
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								external/README.txt
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,90 @@ | ||||||
|  | =============================================================== | ||||||
|  | loops - Linked Objects for Organization and Processing Services | ||||||
|  | =============================================================== | ||||||
|  | 
 | ||||||
|  |   ($Id$) | ||||||
|  | 
 | ||||||
|  |   >>> from zope import component | ||||||
|  |   >>> from zope.traversing.api import getName | ||||||
|  | 
 | ||||||
|  | Let's set up a loops site with basic and example concepts and resources. | ||||||
|  | 
 | ||||||
|  |   >>> from zope.app.testing.setup import placefulSetUp, placefulTearDown | ||||||
|  |   >>> site = placefulSetUp(True) | ||||||
|  | 
 | ||||||
|  |   >>> from loops.tests.setup import TestSite | ||||||
|  |   >>> t = TestSite(site) | ||||||
|  |   >>> concepts, resources, views = t.setup() | ||||||
|  |   >>> loopsRoot = site['loops'] | ||||||
|  |   >>> len(concepts), len(resources), len(views) | ||||||
|  |   (11, 3, 0) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | Importing loops Objects | ||||||
|  | ======================= | ||||||
|  | 
 | ||||||
|  | Reading object information from an external source | ||||||
|  | -------------------------------------------------- | ||||||
|  | 
 | ||||||
|  |   >>> from loops.external.pyfunc import PyReader | ||||||
|  | 
 | ||||||
|  |   >>> input = "concept('myquery', u'My Query', 'query', viewName='mystuff.html')" | ||||||
|  |   >>> reader = PyReader() | ||||||
|  |   >>> elements = reader.read(input) | ||||||
|  |   >>> elements | ||||||
|  |   [{'type': 'query', 'name': 'myquery', 'viewName': 'mystuff.html', 'title': u'My Query'}] | ||||||
|  | 
 | ||||||
|  | Creating the corresponding objects | ||||||
|  | ---------------------------------- | ||||||
|  | 
 | ||||||
|  |   >>> from loops.external.base import Loader | ||||||
|  | 
 | ||||||
|  |   >>> loader = Loader(loopsRoot) | ||||||
|  |   >>> loader.load(elements) | ||||||
|  |   >>> len(concepts), len(resources), len(views) | ||||||
|  |   (12, 3, 0) | ||||||
|  | 
 | ||||||
|  |   >>> from loops.common import adapted | ||||||
|  |   >>> adapted(concepts['myquery']).viewName | ||||||
|  |   'mystuff.html' | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | Exporting loops Objects | ||||||
|  | ======================= | ||||||
|  | 
 | ||||||
|  | Extracting elements | ||||||
|  | ------------------- | ||||||
|  | 
 | ||||||
|  |   >>> from loops.external.base import Extractor | ||||||
|  | 
 | ||||||
|  |   >>> extractor = Extractor(loopsRoot) | ||||||
|  |   >>> elements = list(extractor.extract()) | ||||||
|  |   >>> len(elements) | ||||||
|  |   13 | ||||||
|  | 
 | ||||||
|  | Writing object information to the external storage | ||||||
|  | -------------------------------------------------- | ||||||
|  | 
 | ||||||
|  |   >>> from loops.external.pyfunc import PyWriter | ||||||
|  |   >>> from cStringIO import StringIO | ||||||
|  | 
 | ||||||
|  |   >>> output = StringIO() | ||||||
|  |   >>> writer = PyWriter() | ||||||
|  |   >>> writer.write(elements, output) | ||||||
|  |   >>> print output.getvalue() | ||||||
|  |   type(u'customer', u'Customer', viewName=u'')... | ||||||
|  |   type(u'query', u'Query', typeInterface='loops.query.IQueryConcept', viewName=u'')... | ||||||
|  |   concept(u'myquery', u'My Query', u'query', viewName='mystuff.html')... | ||||||
|  |   child(u'projects', u'customer', u'standard')... | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | The Export/Import View | ||||||
|  | ====================== | ||||||
|  | 
 | ||||||
|  |   >>> from loops.external.browser import ExportImport | ||||||
|  |   >>> from zope.publisher.browser import TestRequest | ||||||
|  | 
 | ||||||
|  |   >>> input = {'field.data': output} | ||||||
|  |   >>> view = ExportImport(loopsRoot, TestRequest(input)) | ||||||
|  |   >>> view.upload() | ||||||
|  |   False | ||||||
							
								
								
									
										5
									
								
								external/__init__.py
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								external/__init__.py
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | ||||||
|  | """ | ||||||
|  | $Id$ | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | from loops.external.external import NodesLoader, NodesExporter, NodesImporter | ||||||
							
								
								
									
										143
									
								
								external/base.py
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								external/base.py
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,143 @@ | ||||||
|  | # | ||||||
|  | #  Copyright (c) 2008 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 | ||||||
|  | # | ||||||
|  | 
 | ||||||
|  | """ | ||||||
|  | Reading and writing loops objects (represented by IElement objects) | ||||||
|  | in Python function notation. | ||||||
|  | 
 | ||||||
|  | $Id$ | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | from cStringIO import StringIO | ||||||
|  | import itertools | ||||||
|  | from zope import component | ||||||
|  | from zope.cachedescriptors.property import Lazy | ||||||
|  | from zope.interface import implements | ||||||
|  | from zope.traversing.api import getName | ||||||
|  | 
 | ||||||
|  | from cybertools.composer.interfaces import IInstance | ||||||
|  | from cybertools.composer.schema.interfaces import ISchemaFactory | ||||||
|  | from cybertools.typology.interfaces import IType | ||||||
|  | from loops.common import adapted | ||||||
|  | from loops.external.interfaces import ILoader, IExtractor | ||||||
|  | from loops.external.element import elementTypes | ||||||
|  | from loops.interfaces import IConceptSchema | ||||||
|  | from loops.setup import SetupManager | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Base(object): | ||||||
|  | 
 | ||||||
|  |     def __init__(self, context): | ||||||
|  |         self.context = context | ||||||
|  | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def concepts(self): | ||||||
|  |         return self.context.getConceptManager() | ||||||
|  | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def typeConcept(self): | ||||||
|  |         return self.concepts.getTypeConcept() | ||||||
|  | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def typePredicate(self): | ||||||
|  |         return self.concepts.getTypePredicate() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Loader(Base, SetupManager): | ||||||
|  | 
 | ||||||
|  |     implements(ILoader) | ||||||
|  | 
 | ||||||
|  |     def __init__(self, context): | ||||||
|  |         self.context = context | ||||||
|  |         self.logger = StringIO() | ||||||
|  | 
 | ||||||
|  |     def load(self, elements): | ||||||
|  |         for element in elements: | ||||||
|  |             element(self) | ||||||
|  | 
 | ||||||
|  |     # TODO: care for setting attributes via Instance (Editor) | ||||||
|  |     # instead of using SetupManager methods: | ||||||
|  |     # def addConcept(self, ...): | ||||||
|  | 
 | ||||||
|  | class Extractor(Base): | ||||||
|  | 
 | ||||||
|  |     implements(IExtractor) | ||||||
|  | 
 | ||||||
|  |     def extract(self): | ||||||
|  |         return itertools.chain(self.extractTypes(), | ||||||
|  |                                self.extractConcepts(), | ||||||
|  |                                self.extractChildren(), | ||||||
|  |                                #self.extractResources(), | ||||||
|  |                                #self.extractResourceRelations(), | ||||||
|  |                                #self.extractNodes(), | ||||||
|  |                                #self.extractTargets(), | ||||||
|  |                               ) | ||||||
|  | 
 | ||||||
|  |     def extractTypes(self): | ||||||
|  |         typeElement = elementTypes['type'] | ||||||
|  |         for obj in self.typeConcept.getChildren([self.typePredicate]): | ||||||
|  |             data = self.getObjectData(obj) | ||||||
|  |             yield typeElement(getName(obj), obj.title, **data) | ||||||
|  | 
 | ||||||
|  |     def extractConcepts(self): | ||||||
|  |         conceptElement = elementTypes['concept'] | ||||||
|  |         typeConcept = self.typeConcept | ||||||
|  |         for name, obj in self.concepts.items(): | ||||||
|  |             if obj.conceptType != typeConcept: | ||||||
|  |                 data = self.getObjectData(obj) | ||||||
|  |                 tp = getName(obj.conceptType) | ||||||
|  |                 yield conceptElement(name, obj.title, tp, **data) | ||||||
|  | 
 | ||||||
|  |     def extractResources(self): | ||||||
|  |         resourceElement = elementTypes['resource'] | ||||||
|  |         for name, obj in self.resources.items(): | ||||||
|  |             # TODO: handle ``data`` attribute... | ||||||
|  |             data = self.getObjectData(obj) | ||||||
|  |             tp = getName(obj.resourceType) | ||||||
|  |             yield resourceElement(name, obj.title, tp, **data) | ||||||
|  | 
 | ||||||
|  |     def getObjectData(self, obj): | ||||||
|  |         aObj = adapted(obj) | ||||||
|  |         schemaFactory = component.getAdapter(aObj, ISchemaFactory) | ||||||
|  |         ti = IType(obj).typeInterface or IConceptSchema | ||||||
|  |         schema = schemaFactory(ti, manager=self) #, request=self.request) | ||||||
|  |         instance = IInstance(aObj) | ||||||
|  |         instance.template = schema | ||||||
|  |         # TODO: use ``_not_exportable`` attribute of adapter to control export | ||||||
|  |         #data = instance.applyTemplate(mode='export') | ||||||
|  |         data = instance.applyTemplate(mode='edit') | ||||||
|  |         if 'title' in data: | ||||||
|  |             del data['title'] | ||||||
|  |         data['description'] = obj.description | ||||||
|  |         if not data['description']: | ||||||
|  |             del data['description'] | ||||||
|  |         return data | ||||||
|  | 
 | ||||||
|  |     def extractChildren(self): | ||||||
|  |         childElement = elementTypes['child'] | ||||||
|  |         typePredicate = self.typePredicate | ||||||
|  |         for c in self.concepts.values(): | ||||||
|  |             for r in c.getChildRelations(): | ||||||
|  |                 if r.predicate != typePredicate: | ||||||
|  |                     args = [getName(r.first), getName(r.second), getName(r.predicate)] | ||||||
|  |                     if r.order != 0: | ||||||
|  |                         args.append(r.order) | ||||||
|  |                     if r.relevance != 1.0: | ||||||
|  |                         args.append(r.relevance) | ||||||
|  |                     yield childElement(*args) | ||||||
|  | 
 | ||||||
							
								
								
									
										83
									
								
								external/browser.py
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								external/browser.py
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,83 @@ | ||||||
|  | # | ||||||
|  | #  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 | ||||||
|  | # | ||||||
|  | 
 | ||||||
|  | """ | ||||||
|  | view class(es) for import/export. | ||||||
|  | 
 | ||||||
|  | $Id$ | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | from zope import component | ||||||
|  | from zope.interface import Interface, implements | ||||||
|  | from zope.app import zapi | ||||||
|  | from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile | ||||||
|  | from zope.cachedescriptors.property import Lazy | ||||||
|  | from zope.security.proxy import removeSecurityProxy | ||||||
|  | from cStringIO import StringIO | ||||||
|  | 
 | ||||||
|  | from loops.external.base import Loader, Extractor | ||||||
|  | from loops.external.interfaces import IReader, IWriter | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class ExportImport(object): | ||||||
|  |     """ View providing export and import functionality. | ||||||
|  |     """ | ||||||
|  | 
 | ||||||
|  |     def __init__(self, context, request): | ||||||
|  |         self.context = removeSecurityProxy(context) | ||||||
|  |         self.request = request | ||||||
|  |         self.message = u'' | ||||||
|  | 
 | ||||||
|  |     def submit(self): | ||||||
|  |         action = self.request.get('loops.action', None) | ||||||
|  |         if action: | ||||||
|  |             method = getattr(self, action, None) | ||||||
|  |             if method: | ||||||
|  |                 return method() | ||||||
|  |         return False | ||||||
|  | 
 | ||||||
|  |     def export(self): | ||||||
|  |         f = StringIO() | ||||||
|  |         extractor = Extractor(self.context) | ||||||
|  |         elements = extractor.extract() | ||||||
|  |         writer = component.getUtility(IWriter) | ||||||
|  |         writer.write(elements, f) | ||||||
|  |         text = f.getvalue() | ||||||
|  |         f.close() | ||||||
|  |         self.setDownloadHeader(self.request, text) | ||||||
|  |         return text | ||||||
|  | 
 | ||||||
|  |     def upload(self): | ||||||
|  |         data = self.request.get('field.data', None) | ||||||
|  |         if not data: | ||||||
|  |             return False | ||||||
|  |         reader = component.getUtility(IReader) | ||||||
|  |         elements = reader.read(data) | ||||||
|  |         loader = Loader(self.context) | ||||||
|  |         loader.load(elements) | ||||||
|  |         self.message = u'Content uploaded and imported.' | ||||||
|  |         return False | ||||||
|  | 
 | ||||||
|  |     def setDownloadHeader(self, request, text): | ||||||
|  |         response = request.response | ||||||
|  |         response.setHeader('Content-Disposition', | ||||||
|  |                            'attachment; filename=loopscontent.dmp') | ||||||
|  |         response.setHeader('Content-Type', 'text/plain') | ||||||
|  |         response.setHeader('Content-Length', len(text)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
							
								
								
									
										25
									
								
								external/configure.zcml
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								external/configure.zcml
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,25 @@ | ||||||
|  | <!-- $Id$ --> | ||||||
|  | 
 | ||||||
|  | <configure | ||||||
|  |    xmlns:zope="http://namespaces.zope.org/zope" | ||||||
|  |    xmlns:browser="http://namespaces.zope.org/browser" | ||||||
|  |    i18n_domain="zope"> | ||||||
|  | 
 | ||||||
|  |   <zope:utility factory="loops.external.pyfunc.PyReader" /> | ||||||
|  | 
 | ||||||
|  |   <zope:utility factory="loops.external.pyfunc.PyWriter" /> | ||||||
|  | 
 | ||||||
|  |   <browser:pages for="loops.interfaces.ILoops" | ||||||
|  |          class="loops.external.browser.ExportImport" | ||||||
|  |          permission="zope.ManageSite"> | ||||||
|  | 
 | ||||||
|  |     <browser:page name="exportimport.html" | ||||||
|  |           template="exportimport.pt" | ||||||
|  |           menu="zmi_views" title="Export/Import"  /> | ||||||
|  | 
 | ||||||
|  |     <browser:page name="export_loops.html" | ||||||
|  |           attribute="export" /> | ||||||
|  | 
 | ||||||
|  |   </browser:pages> | ||||||
|  | 
 | ||||||
|  | </configure> | ||||||
							
								
								
									
										107
									
								
								external/element.py
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								external/element.py
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,107 @@ | ||||||
|  | # | ||||||
|  | #  Copyright (c) 2008 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 | ||||||
|  | # | ||||||
|  | 
 | ||||||
|  | """ | ||||||
|  | Basic implementation of the elements used for the intermediate format for export | ||||||
|  | and import of loops objects. | ||||||
|  | 
 | ||||||
|  | $Id$ | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | from zope.cachedescriptors.property import Lazy | ||||||
|  | from zope.dottedname.resolve import resolve | ||||||
|  | from zope.interface import implements | ||||||
|  | from zope.traversing.api import getName | ||||||
|  | 
 | ||||||
|  | from loops.external.interfaces import IElement | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Element(dict): | ||||||
|  | 
 | ||||||
|  |     implements(IElement) | ||||||
|  | 
 | ||||||
|  |     elementType = '' | ||||||
|  | 
 | ||||||
|  |     def __init__(self, name, title, type=None, *args, **kw): | ||||||
|  |         self['name'] = name | ||||||
|  |         self['title'] = title | ||||||
|  |         if type: | ||||||
|  |             self['type'] = type | ||||||
|  |         for k, v in kw.items(): | ||||||
|  |             self[k] = v | ||||||
|  | 
 | ||||||
|  |     def __call__(self, loader): | ||||||
|  |         pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class ConceptElement(Element): | ||||||
|  | 
 | ||||||
|  |     elementType = 'concept' | ||||||
|  |     posArgs = ('name', 'title', 'type') | ||||||
|  | 
 | ||||||
|  |     def __call__(self, loader): | ||||||
|  |         type = loader.concepts[self['type']] | ||||||
|  |         kw = dict((k, v) for k, v in self.items() | ||||||
|  |                          if k not in ('name', 'title', 'type')) | ||||||
|  |         loader.addConcept(self['name'], self['title'], type, **kw) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class TypeElement(ConceptElement): | ||||||
|  | 
 | ||||||
|  |     elementType = 'type' | ||||||
|  |     posArgs = ('name', 'title') | ||||||
|  | 
 | ||||||
|  |     def __init__(self, name, title, *args, **kw): | ||||||
|  |         super(TypeElement, self).__init__(name, title, *args, **kw) | ||||||
|  |         ti = self['typeInterface'] | ||||||
|  |         if ti: | ||||||
|  |             self['typeInterface'] = '.'.join((ti.__module__, ti.__name__)) | ||||||
|  |         else: | ||||||
|  |             del self['typeInterface'] | ||||||
|  | 
 | ||||||
|  |     def __call__(self, loader): | ||||||
|  |         kw = dict((k, v) for k, v in self.items() | ||||||
|  |                 if k not in ('name', 'title', 'type', 'typeInterface')) | ||||||
|  |         kw['typeInterface'] = resolve(self['typeInterface']) | ||||||
|  |         loader.addConcept(self['name'], self['title'], 'type', **kw) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class ResourceElement(ConceptElement): | ||||||
|  | 
 | ||||||
|  |     elementType = 'resource' | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class ChildElement(Element): | ||||||
|  | 
 | ||||||
|  |     elementType = 'child' | ||||||
|  |     posArgs = ('first', 'second', 'predicate', 'order', 'relevance') | ||||||
|  | 
 | ||||||
|  |     def __init__(self, *args): | ||||||
|  |         for idx, arg in enumerate(args): | ||||||
|  |             self[self.posArgs[idx]] = arg | ||||||
|  | 
 | ||||||
|  |     def __call__(self, loader): | ||||||
|  |         loader.assignChild(self['first'], self['second'], self['predicate']) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | elementTypes = dict( | ||||||
|  |     type=TypeElement, | ||||||
|  |     concept=ConceptElement, | ||||||
|  |     resource=ResourceElement, | ||||||
|  |     child=ChildElement, | ||||||
|  | ) | ||||||
							
								
								
									
										60
									
								
								external/exportimport.pt
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								external/exportimport.pt
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,60 @@ | ||||||
|  | <tal:show condition="not:view/submit"> | ||||||
|  | <html metal:use-macro="context/@@standard_macros/view"> | ||||||
|  | <head> | ||||||
|  | </head> | ||||||
|  | <body> | ||||||
|  | 
 | ||||||
|  |   <metal:body fill-slot="body"> | ||||||
|  | 
 | ||||||
|  |     <h3>Export/Import loops Site</h3> | ||||||
|  | 
 | ||||||
|  |     <div tal:define="message view/message | request/message | nothing" | ||||||
|  |          tal:condition="message" | ||||||
|  |          tal:content="message">Message</div> | ||||||
|  | 
 | ||||||
|  |     <div> </div> | ||||||
|  |     <div> | ||||||
|  |       This form allows you to export the objects in a loops site to a | ||||||
|  |       file and upload a file created by a content export. | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  |     <form action="." method="post" | ||||||
|  |           tal:attributes="action string:${request/URL/-1}/export_loops.html"> | ||||||
|  |       <input type="hidden" name="loops.action" value="export" /> | ||||||
|  |       <div> </div> | ||||||
|  |       <h4>Export Site</h4> | ||||||
|  |       <div> </div> | ||||||
|  |       <div class="row"> | ||||||
|  |         <div class="controls"> | ||||||
|  |           <input type="submit" name="loops.export" value="Export" /> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </form> | ||||||
|  | 
 | ||||||
|  |     <form action="." method="post" enctype="multipart/form-data" | ||||||
|  |           tal:attributes="action request/URL"> | ||||||
|  |       <input type="hidden" name="loops.action" value="upload" /> | ||||||
|  |       <div> </div> | ||||||
|  |       <h4>Import Site</h4> | ||||||
|  |       <div class="row"> | ||||||
|  |         <div class="label"> | ||||||
|  |           <label for="field.data" | ||||||
|  |                  title="The file to be uploaded.">File</label> | ||||||
|  |         </div> | ||||||
|  |         <div class="field"> | ||||||
|  |           <input class="fileType" id="field.data" name="field.data" | ||||||
|  |                  size="20" type="file" /> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |       <div class="row"> | ||||||
|  |         <div class="controls"> | ||||||
|  |           <input type="submit" name="loops.upload" value="Upload" /> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </form> | ||||||
|  | 
 | ||||||
|  |   </metal:body> | ||||||
|  | 
 | ||||||
|  | </body> | ||||||
|  | </html> | ||||||
|  | </tal:show> | ||||||
							
								
								
									
										2
									
								
								external.py → external/external.py
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								external.py → external/external.py
									
										
									
									
										vendored
									
									
								
							|  | @ -1,5 +1,5 @@ | ||||||
| # | # | ||||||
| #  Copyright (c) 2005 Helmut Merz helmutm@cy55.de | #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||||
| # | # | ||||||
| #  This program is free software; you can redistribute it and/or modify | #  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 | #  it under the terms of the GNU General Public License as published by | ||||||
							
								
								
									
										85
									
								
								external/interfaces.py
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								external/interfaces.py
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,85 @@ | ||||||
|  | # | ||||||
|  | #  Copyright (c) 2008 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 | ||||||
|  | # | ||||||
|  | 
 | ||||||
|  | """ | ||||||
|  | Interfaces for export and import of loops objects. | ||||||
|  | 
 | ||||||
|  | Maybe part of this stuff should be moved to cybertools.external. | ||||||
|  | 
 | ||||||
|  | $Id$ | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | from zope.interface import Interface, Attribute | ||||||
|  | from zope import interface, component, schema | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class IElement(Interface): | ||||||
|  |     """ A dicionary-like information element that is able to represent a | ||||||
|  |         loops object, a relation between loops objects or a special attribute. | ||||||
|  |         The attributes of the object are represented by items of | ||||||
|  |         the dictionary; the attribute values may be strings, unicode strings, | ||||||
|  |         or IElement objects. | ||||||
|  |     """ | ||||||
|  | 
 | ||||||
|  |     def __call__(loader): | ||||||
|  |         """ Create the object that is specified by the element in the | ||||||
|  |             context of the loader and return it. | ||||||
|  |         """ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class IReader(Interface): | ||||||
|  |     """ Provides objects in an intermediate format from an external source. | ||||||
|  |         Will typically be implemented by an utility or an adapter. | ||||||
|  |     """ | ||||||
|  | 
 | ||||||
|  |     def read(): | ||||||
|  |         """ Retrieve content from the external source returning a sequence | ||||||
|  |             of IElement objects. | ||||||
|  |         """ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class ILoader(Interface): | ||||||
|  |     """ Inserts data provided by an IReader object into the | ||||||
|  |         loops database/the context object. Will typically be used as an adapter. | ||||||
|  |     """ | ||||||
|  | 
 | ||||||
|  |     def load(elements): | ||||||
|  |         """ Create the objects and relations specified by the ``elements`` | ||||||
|  |             argument given. | ||||||
|  |         """ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class IWriter(Interface): | ||||||
|  |     """ Transforms object information to an external storage. | ||||||
|  |     """ | ||||||
|  | 
 | ||||||
|  |     def write(elements): | ||||||
|  |         """ Write the sequence of elements given in an external format. | ||||||
|  |         """ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class IExtractor(Interface): | ||||||
|  |     """ Extracts information from loops objects and provides them as | ||||||
|  |         IElement objects. Will typically be used as an adapter. | ||||||
|  |     """ | ||||||
|  | 
 | ||||||
|  |     def extract(): | ||||||
|  |         """ Creates and returns a sequence of IElement objects by scanning | ||||||
|  |             the content of the context object. | ||||||
|  |         """ | ||||||
|  | 
 | ||||||
							
								
								
									
										73
									
								
								external/pyfunc.py
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								external/pyfunc.py
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,73 @@ | ||||||
|  | # | ||||||
|  | #  Copyright (c) 2008 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 | ||||||
|  | # | ||||||
|  | 
 | ||||||
|  | """ | ||||||
|  | Reading and writing loops objects (represented by IElement objects) | ||||||
|  | in Python function notation. | ||||||
|  | 
 | ||||||
|  | $Id$ | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | from zope.cachedescriptors.property import Lazy | ||||||
|  | from zope.interface import implements | ||||||
|  | 
 | ||||||
|  | from loops.external.interfaces import IReader, IWriter | ||||||
|  | from loops.external.element import elementTypes | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class PyReader(dict): | ||||||
|  | 
 | ||||||
|  |     implements(IReader) | ||||||
|  | 
 | ||||||
|  |     def __init__(self): | ||||||
|  |         self.elements = [] | ||||||
|  | 
 | ||||||
|  |     def read(self, input): | ||||||
|  |         if not isinstance(input, str): | ||||||
|  |             input = input.read() | ||||||
|  |         exec input in self | ||||||
|  |         return self.elements | ||||||
|  | 
 | ||||||
|  |     def __getitem__(self, key): | ||||||
|  |         def factory(*args, **kw): | ||||||
|  |             element = elementTypes[key](*args, **kw) | ||||||
|  |             self.elements.append(element) | ||||||
|  |             return element | ||||||
|  |         return factory | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class PyWriter(object): | ||||||
|  | 
 | ||||||
|  |     implements(IWriter) | ||||||
|  | 
 | ||||||
|  |     def write(self, elements, output): | ||||||
|  |         for element in elements: | ||||||
|  |             args = [] | ||||||
|  |             for arg in element.posArgs: | ||||||
|  |                 if arg in element: | ||||||
|  |                     args.append(repr(element[arg])) | ||||||
|  |             for k, v in element.items(): | ||||||
|  |                 if k not in element.posArgs: | ||||||
|  |                     args.append("%s=%s" % (str(k), repr(v))) | ||||||
|  |             output.write('%s(%s)\n' % (element.elementType, ', '.join(args))) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def toStr(value): | ||||||
|  |     if isinstance(value, unicode): | ||||||
|  |         return value.encode('UTF-8') | ||||||
|  |     return str(value) | ||||||
							
								
								
									
										23
									
								
								external/tests.py
									
										
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										23
									
								
								external/tests.py
									
										
									
									
										vendored
									
									
										Executable file
									
								
							|  | @ -0,0 +1,23 @@ | ||||||
|  | # $Id$ | ||||||
|  | 
 | ||||||
|  | import unittest, doctest | ||||||
|  | from zope.testing.doctestunit import DocFileSuite | ||||||
|  | from zope.interface.verify import verifyClass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Test(unittest.TestCase): | ||||||
|  |     "Basic tests for the loops.external package." | ||||||
|  | 
 | ||||||
|  |     def testSomething(self): | ||||||
|  |         pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_suite(): | ||||||
|  |     flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS | ||||||
|  |     return unittest.TestSuite(( | ||||||
|  |                 unittest.makeSuite(Test), | ||||||
|  |                 DocFileSuite('README.txt', optionflags=flags), | ||||||
|  |             )) | ||||||
|  | 
 | ||||||
|  | if __name__ == '__main__': | ||||||
|  |     unittest.main(defaultTest='test_suite') | ||||||
|  | @ -65,7 +65,8 @@ class LanguageInfo(object): | ||||||
|         lang = self.request.get('loops.language') |         lang = self.request.get('loops.language') | ||||||
|         if lang is not None and lang in self.availableLanguages: |         if lang is not None and lang in self.availableLanguages: | ||||||
|             return lang |             return lang | ||||||
|         return (negotiator.getLanguage(self.availableLanguages, self.request) |         available = self.availableLanguages or ('en', 'de',) | ||||||
|  |         return (negotiator.getLanguage(available, self.request) | ||||||
|                 or self.defaultLanguage) |                 or self.defaultLanguage) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,8 +1,10 @@ | ||||||
| <!-- $Id$ --> | <!-- $Id$ --> | ||||||
| 
 | 
 | ||||||
| <metal:actions define-macro="language_switch" | <metal:actions define-macro="language_switch" | ||||||
|                tal:define="langInfo view/languageInfo"> |                tal:define="langInfo view/languageInfo; | ||||||
|     <tal:lang repeat="lang langInfo/availableLanguages"> |                            available langInfo/availableLanguages" | ||||||
|  |                tal:condition="python: len(available) > 1"> | ||||||
|  |     <tal:lang repeat="lang available"> | ||||||
|       <a href="#" |       <a href="#" | ||||||
|          tal:attributes="href string:switch_language?loops.language=$lang&keep=yes; |          tal:attributes="href string:switch_language?loops.language=$lang&keep=yes; | ||||||
|                          title lang"><img src="us.gif" |                          title lang"><img src="us.gif" | ||||||
|  |  | ||||||
|  | @ -29,8 +29,9 @@ from zope.app.container.constraints import contains, containers | ||||||
| from zope.app.container.interfaces import IContainer, IOrderedContainer | from zope.app.container.interfaces import IContainer, IOrderedContainer | ||||||
| from zope.app.file.interfaces import IImage as IBaseAsset | from zope.app.file.interfaces import IImage as IBaseAsset | ||||||
| from zope.app.folder.interfaces import IFolder | from zope.app.folder.interfaces import IFolder | ||||||
|  | from zope.component.interfaces import IObjectEvent | ||||||
| from zope.size.interfaces import ISized | from zope.size.interfaces import ISized | ||||||
| from cybertools.relation.interfaces import IRelation | from cybertools.relation.interfaces import IDyadicRelation | ||||||
| 
 | 
 | ||||||
| import util | import util | ||||||
| from util import _ | from util import _ | ||||||
|  | @ -563,16 +564,19 @@ class ILoopsContained(Interface): | ||||||
| 
 | 
 | ||||||
| # relation interfaces | # relation interfaces | ||||||
| 
 | 
 | ||||||
| class ITargetRelation(IRelation): | class ITargetRelation(IDyadicRelation): | ||||||
|     """ (Marker) interfaces for relations pointing to a target |     """ (Marker) interfaces for relations pointing to a target | ||||||
|         of a view or node. |         of a view or node. | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class IConceptRelation(IRelation): | class IConceptRelation(IDyadicRelation): | ||||||
|     """ (Marker) interfaces for relations originating from a concept. |     """ (Marker) interfaces for relations originating from a concept. | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
|  |     predicate = Attribute("A concept of type 'predicate' that defines the " | ||||||
|  |                     "type of the relation-") | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| # interfaces for catalog indexes | # interfaces for catalog indexes | ||||||
| 
 | 
 | ||||||
|  | @ -685,6 +689,22 @@ class INote(ITextDocument): | ||||||
|         required=False) |         required=False) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | # events | ||||||
|  | 
 | ||||||
|  | class IAssignmentEvent(IObjectEvent): | ||||||
|  |     """ A child or resource has been assigned to a concept. | ||||||
|  |     """ | ||||||
|  | 
 | ||||||
|  |     relation = Attribute('The relation that has been assigned to the concept.') | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class IDeassignmentEvent(IObjectEvent): | ||||||
|  |     """ A child or resource will be deassigned from a concept. | ||||||
|  |     """ | ||||||
|  | 
 | ||||||
|  |     relation = Attribute('The relation that will be removed from the concept.') | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| # view configurator stuff | # view configurator stuff | ||||||
| 
 | 
 | ||||||
| class IViewConfiguratorSchema(Interface): | class IViewConfiguratorSchema(Interface): | ||||||
|  |  | ||||||
|  | @ -26,7 +26,7 @@ $Id$ | ||||||
| from zope.cachedescriptors.property import Lazy | from zope.cachedescriptors.property import Lazy | ||||||
| from zope.app.pagetemplate import ViewPageTemplateFile | from zope.app.pagetemplate import ViewPageTemplateFile | ||||||
| 
 | 
 | ||||||
| from loops.browser.action import Action, DialogAction | from loops.browser.action import DialogAction | ||||||
| from loops.browser.concept import ConceptView | from loops.browser.concept import ConceptView | ||||||
| from loops.browser.form import CreateConceptForm, EditConceptForm | from loops.browser.form import CreateConceptForm, EditConceptForm | ||||||
| from loops.browser.form import CreateConcept, EditConcept | from loops.browser.form import CreateConcept, EditConcept | ||||||
|  | @ -107,6 +107,7 @@ class EditGlossaryItemForm(EditConceptForm, ConceptView): | ||||||
| 
 | 
 | ||||||
| class CreateGlossaryItemForm(CreateConceptForm, EditGlossaryItemForm): | class CreateGlossaryItemForm(CreateConceptForm, EditGlossaryItemForm): | ||||||
| 
 | 
 | ||||||
|  |     title = _(u'Create Glossary Item') | ||||||
|     form_action = 'create_glossaryitem' |     form_action = 'create_glossaryitem' | ||||||
| 
 | 
 | ||||||
|     def children(self): |     def children(self): | ||||||
|  |  | ||||||
|  | @ -8,26 +8,26 @@ | ||||||
|     <span tal:repeat="letter python: [chr(c) for c in range(ord('A'), ord('Z')+1)]" |     <span tal:repeat="letter python: [chr(c) for c in range(ord('A'), ord('Z')+1)]" | ||||||
|           class="navlink"> |           class="navlink"> | ||||||
|       <a href="#" |       <a href="#" | ||||||
|          tal:omit-tag="python: letter not in data['letters']" |          tal:omit-tag="python: letter not in data.keys()" | ||||||
|          tal:attributes="href string:${request/URL/-1}#$letter" |          tal:attributes="href string:${request/URL/-1}#$letter" | ||||||
|          tal:content="letter">A</a> |          tal:content="letter">A</a> | ||||||
|     </span> |     </span> | ||||||
|   </div> |   </div> | ||||||
|   <div> </div> |   <div> </div> | ||||||
|   <div tal:repeat="letter data/letters"> |   <div tal:repeat="letter data/keys"> | ||||||
|     <div class="subtitle"><a name="A" href="#top" |     <div class="subtitle"><a name="A" href="#top" | ||||||
|            tal:attributes="name letter; |            tal:attributes="name letter; | ||||||
|                            href string:${request/URL/-1}#top" |                            href string:${request/URL/-1}#top" | ||||||
|            tal:content="letter">A</a> |            tal:content="letter">A</a> | ||||||
|     </div> |     </div> | ||||||
|     <div tal:repeat="related data/relations/?letter|python:[]"> |     <div tal:repeat="related data/?letter|python:[]"> | ||||||
|       <a href="#" |       <a href="#" | ||||||
|          tal:content="related/title" |          tal:content="related/title" | ||||||
|          tal:attributes="href python: view.getUrlForTarget(related); |          tal:attributes="href python: view.getUrlForTarget(related); | ||||||
|                          title related/description"> |                          title related/description"> | ||||||
|         Topic |         Topic | ||||||
|       </a> |       </a> | ||||||
|       <span tal:define="translations related/adapted/translations" |       <span tal:define="translations related/adapted/translations|python:[]" | ||||||
|             tal:condition="translations"> |             tal:condition="translations"> | ||||||
|         (<tal:trans repeat="trans translations"><a href="#" |         (<tal:trans repeat="trans translations"><a href="#" | ||||||
|              tal:attributes="href python: '%s?loops.language=%s' % |              tal:attributes="href python: '%s?loops.language=%s' % | ||||||
|  | @ -44,7 +44,7 @@ | ||||||
| 
 | 
 | ||||||
| <metal:block define-macro="glossaryitem"> | <metal:block define-macro="glossaryitem"> | ||||||
|   <metal:title use-macro="item/conceptMacros/concepttitle" /> |   <metal:title use-macro="item/conceptMacros/concepttitle" /> | ||||||
|   <p tal:define="translations item/adapted/translations" |   <p tal:define="translations item/adapted/translations|python:[]" | ||||||
|         tal:condition="translations"> |         tal:condition="translations"> | ||||||
|     <span i18n:translate="">Translations</span>: |     <span i18n:translate="">Translations</span>: | ||||||
|     <tal:trans repeat="trans translations"><a href="#" |     <tal:trans repeat="trans translations"><a href="#" | ||||||
|  | @ -98,10 +98,12 @@ | ||||||
|       <input type="hidden" |       <input type="hidden" | ||||||
|              id="child.search.predicate" |              id="child.search.predicate" | ||||||
|              tal:attributes="value view/relatedPredicateUid" /> |              tal:attributes="value view/relatedPredicateUid" /> | ||||||
|       <input dojoType="comboBox" mode="remote" autoComplete="False" |       <div dojoType="dojox.data.QueryReadStore" jsId="childSearch" | ||||||
|              name="child.search.text" id="child.search.text" |            url="listConceptsForComboBox.js?searchType=loops:concept:glossaryitem" > | ||||||
|              tal:attributes="dataUrl |       </div> | ||||||
|                 string:listConceptsForComboBox.js?searchString=%{searchString}&searchType=loops:concept:glossaryitem" /> |       <input dojoType="dijit.form.FilteringSelect" store="childSearch" | ||||||
|  |              autoComplete="False" labelAttr="label" | ||||||
|  |              name="child.search.text" id="child.search.text" /> | ||||||
|     </td> |     </td> | ||||||
|     <td> |     <td> | ||||||
|       <input type="button" value="Select" |       <input type="button" value="Select" | ||||||
|  |  | ||||||
										
											Binary file not shown.
										
									
								
							|  | @ -3,7 +3,7 @@ msgstr "" | ||||||
| 
 | 
 | ||||||
| "Project-Id-Version: $Id$\n" | "Project-Id-Version: $Id$\n" | ||||||
| "POT-Creation-Date: 2007-05-22 12:00 CET\n" | "POT-Creation-Date: 2007-05-22 12:00 CET\n" | ||||||
| "PO-Revision-Date: 2007-12-17 12:00 CET\n" | "PO-Revision-Date: 2008-01-23 12:00 CET\n" | ||||||
| "Last-Translator: Helmut Merz <helmutm@cy55.de>\n" | "Last-Translator: Helmut Merz <helmutm@cy55.de>\n" | ||||||
| "Language-Team: loops developers <helmutm@cy55.de>\n" | "Language-Team: loops developers <helmutm@cy55.de>\n" | ||||||
| "MIME-Version: 1.0\n" | "MIME-Version: 1.0\n" | ||||||
|  | @ -17,12 +17,54 @@ msgstr "Begriff" | ||||||
| msgid "Resource" | msgid "Resource" | ||||||
| msgstr "Ressource" | msgstr "Ressource" | ||||||
| 
 | 
 | ||||||
|  | msgid "Log out" | ||||||
|  | msgstr "Abmelden" | ||||||
|  | 
 | ||||||
|  | msgid "Create" | ||||||
|  | msgstr "Anlegen" | ||||||
|  | 
 | ||||||
| msgid "Edit Concept Map" | msgid "Edit Concept Map" | ||||||
| msgstr "Concept Map bearbeiten" | msgstr "Concept Map bearbeiten" | ||||||
| 
 | 
 | ||||||
| msgid "Create Resource..." | msgid "Create Resource..." | ||||||
| msgstr "Ressource anlegen..." | msgstr "Ressource anlegen..." | ||||||
| 
 | 
 | ||||||
|  | msgid "Create a new resource object." | ||||||
|  | msgstr "Eine neue Ressource erzeugen" | ||||||
|  | 
 | ||||||
|  | msgid "Edit Blog Post..." | ||||||
|  | msgstr "Eintrag bearbeiten..." | ||||||
|  | 
 | ||||||
|  | msgid "Edit Blog Post" | ||||||
|  | msgstr "Tagebucheintrag bearbeiten" | ||||||
|  | 
 | ||||||
|  | msgid "Modify blog post." | ||||||
|  | msgstr "Tagebucheintrag ändern." | ||||||
|  | 
 | ||||||
|  | msgid "Create Blog Post..." | ||||||
|  | msgstr "Tagebucheintrag anlegen..." | ||||||
|  | 
 | ||||||
|  | msgid "Create a new blog post." | ||||||
|  | msgstr "Einen neuen Tagebucheintrag erzeugen" | ||||||
|  | 
 | ||||||
|  | msgid "Create Blog Post" | ||||||
|  | msgstr "Tagebucheintrag anlegen" | ||||||
|  | 
 | ||||||
|  | msgid "Glossary Item" | ||||||
|  | msgstr "Glossareintrag" | ||||||
|  | 
 | ||||||
|  | msgid "Edit Glossary Item..." | ||||||
|  | msgstr "Glossareintrag bearbeiten..." | ||||||
|  | 
 | ||||||
|  | msgid "Edit Glossary Item" | ||||||
|  | msgstr "Glossareintrag bearbeiten" | ||||||
|  | 
 | ||||||
|  | msgid "Create Glossary Item..." | ||||||
|  | msgstr "Glossareintrag anlegen..." | ||||||
|  | 
 | ||||||
|  | msgid "Create Glossary Item" | ||||||
|  | msgstr "Glossareintrag anlegen" | ||||||
|  | 
 | ||||||
| msgid "Actions" | msgid "Actions" | ||||||
| msgstr "Aktionen" | msgstr "Aktionen" | ||||||
| 
 | 
 | ||||||
|  | @ -47,9 +89,15 @@ msgstr "Unterbegriffe" | ||||||
| msgid "Title" | msgid "Title" | ||||||
| msgstr "Titel" | msgstr "Titel" | ||||||
| 
 | 
 | ||||||
|  | msgid "Title of the concept" | ||||||
|  | msgstr "Überschrift, sprechende Bezeichnung des Begriffs" | ||||||
|  | 
 | ||||||
| msgid "Description" | msgid "Description" | ||||||
| msgstr "Beschreibung" | msgstr "Beschreibung" | ||||||
| 
 | 
 | ||||||
|  | msgid "A medium-length description describing the content and the purpose of the object" | ||||||
|  | msgstr "Eine ausführlichere Beschreibung des Inhalts und Zwecks des Objekts" | ||||||
|  | 
 | ||||||
| msgid "Related Items" | msgid "Related Items" | ||||||
| msgstr "Verwandte Begriffe" | msgstr "Verwandte Begriffe" | ||||||
| 
 | 
 | ||||||
|  | @ -224,4 +272,30 @@ msgstr "Die Passwort-Wiederholung stimmt nicht mit dem eingegebenen Passwort üb | ||||||
| msgid "Your password has been changed." | msgid "Your password has been changed." | ||||||
| msgstr "Ihr Passwort wurde geändert." | msgstr "Ihr Passwort wurde geändert." | ||||||
| 
 | 
 | ||||||
|  | msgid "Date/Time" | ||||||
|  | msgstr "Datum/Uhrzeit" | ||||||
|  | 
 | ||||||
|  | msgid "The date and time the information was posted." | ||||||
|  | msgstr "Datum und Uhrzeit der Bereitstellung der Information" | ||||||
|  | 
 | ||||||
|  | msgid "Private" | ||||||
|  | msgstr "privat" | ||||||
|  | 
 | ||||||
|  | msgid "Check this field if the blog post should be accessible only for a limited audience." | ||||||
|  | msgstr "Markieren Sie dieses Feld, wenn der Tagebucheintrag nicht für alle Benutzer sichtbar sein soll" | ||||||
|  | 
 | ||||||
|  | msgid "The text of your blog entry" | ||||||
|  | msgstr "Der eigentliche Text des Tagebucheintrags" | ||||||
|  | 
 | ||||||
|  | msgid "Private Comment" | ||||||
|  | msgstr "Privater Kommentar" | ||||||
|  | 
 | ||||||
|  | msgid "A text that is not visible for other users." | ||||||
|  | msgstr "Ein Text, der für andere Benutzer nicht sichtbar sein soll" | ||||||
|  | 
 | ||||||
|  | msgid "For quick creation of notes/links bookmark this link" | ||||||
|  | msgstr "Für Notizen diesen Link zu Favoriten/Lesezeichen hinzufügen" | ||||||
|  | 
 | ||||||
|  | msgid "Create loops Note" | ||||||
|  | msgstr "loops-Notiz anlegen" | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -26,6 +26,9 @@ ZCML setup): | ||||||
|   >>> loopsRoot = site['loops'] |   >>> loopsRoot = site['loops'] | ||||||
|   >>> loopsId = util.getUidForObject(loopsRoot) |   >>> loopsId = util.getUidForObject(loopsRoot) | ||||||
| 
 | 
 | ||||||
|  |   >>> from loops.organize.tests import setupUtilitiesAndAdapters | ||||||
|  |   >>> setupData = setupUtilitiesAndAdapters(loopsRoot) | ||||||
|  | 
 | ||||||
|   >>> type = concepts['type'] |   >>> type = concepts['type'] | ||||||
|   >>> person = concepts['person'] |   >>> person = concepts['person'] | ||||||
| 
 | 
 | ||||||
|  | @ -39,10 +42,7 @@ Organizations: Persons (and Users), Institutions, Addresses... | ||||||
| 
 | 
 | ||||||
| The classes used in this package are just adapters to IConcept. | The classes used in this package are just adapters to IConcept. | ||||||
| 
 | 
 | ||||||
|   >>> from loops.interfaces import IConcept |  | ||||||
|   >>> from loops.organize.interfaces import IPerson |   >>> from loops.organize.interfaces import IPerson | ||||||
|   >>> from loops.organize.party import Person |  | ||||||
|   >>> component.provideAdapter(Person, (IConcept,), IPerson) |  | ||||||
| 
 | 
 | ||||||
|   >>> john = IPerson(johnC) |   >>> john = IPerson(johnC) | ||||||
|   >>> john.title |   >>> john.title | ||||||
|  | @ -74,15 +74,8 @@ the person(s) belonging to a user/principal. | ||||||
| For testing, we first have to provide the needed utilities and settings | For testing, we first have to provide the needed utilities and settings | ||||||
| (in real life this is all done during Zope startup): | (in real life this is all done during Zope startup): | ||||||
| 
 | 
 | ||||||
|   >>> from zope.app.security.interfaces import IAuthentication |   >>> auth = setupData.auth | ||||||
|   >>> from zope.app.security.principalregistry import PrincipalRegistry |   >>> principalAnnotations = setupData.principalAnnotations | ||||||
|   >>> auth = PrincipalRegistry() |  | ||||||
|   >>> component.provideUtility(auth, IAuthentication) |  | ||||||
| 
 |  | ||||||
|   >>> from zope.app.principalannotation.interfaces import IPrincipalAnnotationUtility |  | ||||||
|   >>> from zope.app.principalannotation import PrincipalAnnotationUtility |  | ||||||
|   >>> principalAnnotations = PrincipalAnnotationUtility() |  | ||||||
|   >>> component.provideUtility(principalAnnotations, IPrincipalAnnotationUtility) |  | ||||||
| 
 | 
 | ||||||
|   >>> principal = auth.definePrincipal('users.john', u'John', login='john') |   >>> principal = auth.definePrincipal('users.john', u'John', login='john') | ||||||
|   >>> john.userId = 'users.john' |   >>> john.userId = 'users.john' | ||||||
|  | @ -116,6 +109,7 @@ principal annotation: | ||||||
|   >>> from zope.app.container.contained import ObjectRemovedEvent |   >>> from zope.app.container.contained import ObjectRemovedEvent | ||||||
|   >>> from zope.event import notify |   >>> from zope.event import notify | ||||||
|   >>> from zope.interface import Interface |   >>> from zope.interface import Interface | ||||||
|  |   >>> from loops.interfaces import IConcept | ||||||
|   >>> from loops.organize.party import removePersonReferenceFromPrincipal |   >>> from loops.organize.party import removePersonReferenceFromPrincipal | ||||||
|   >>> from zope.app.testing import ztapi |   >>> from zope.app.testing import ztapi | ||||||
|   >>> ztapi.subscribe([IConcept, IObjectRemovedEvent], None, |   >>> ztapi.subscribe([IConcept, IObjectRemovedEvent], None, | ||||||
|  | @ -157,6 +151,7 @@ with a principal folder: | ||||||
| 
 | 
 | ||||||
|   >>> from zope.app.appsetup.bootstrap import ensureUtility |   >>> from zope.app.appsetup.bootstrap import ensureUtility | ||||||
|   >>> from zope.app.authentication.authentication import PluggableAuthentication |   >>> from zope.app.authentication.authentication import PluggableAuthentication | ||||||
|  |   >>> from zope.app.security.interfaces import IAuthentication | ||||||
|   >>> ensureUtility(site, IAuthentication, '', PluggableAuthentication, |   >>> ensureUtility(site, IAuthentication, '', PluggableAuthentication, | ||||||
|   ...               copy_to_zlog=False, asObject=True) |   ...               copy_to_zlog=False, asObject=True) | ||||||
|   <...PluggableAuthentication...> |   <...PluggableAuthentication...> | ||||||
|  | @ -212,7 +207,6 @@ Now we can also retrieve it from the authentication utility: | ||||||
|   >>> pau.getPrincipal('loops.newuser').title |   >>> pau.getPrincipal('loops.newuser').title | ||||||
|   u'Tom Sawyer' |   u'Tom Sawyer' | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| Change Password | Change Password | ||||||
| --------------- | --------------- | ||||||
| 
 | 
 | ||||||
|  | @ -229,18 +223,100 @@ We need a principal for testing the login stuff: | ||||||
|   >>> principal = InternalPrincipal('scott', 'tiger', 'Scotty') |   >>> principal = InternalPrincipal('scott', 'tiger', 'Scotty') | ||||||
|   >>> request.setPrincipal(principal) |   >>> request.setPrincipal(principal) | ||||||
| 
 | 
 | ||||||
|   >>> from cybertools.composer.schema.factory import SchemaFactory |  | ||||||
|   >>> from cybertools.composer.schema.field import FieldInstance |  | ||||||
|   >>> component.provideAdapter(SchemaFactory) |  | ||||||
|   >>> component.provideAdapter(FieldInstance) |  | ||||||
| 
 |  | ||||||
|   >>> from loops.organize.browser import PasswordChange |   >>> from loops.organize.browser import PasswordChange | ||||||
|   >>> pwcView = PasswordChange(menu, request) |   >>> pwcView = PasswordChange(menu, request) | ||||||
|   >>> pwcView.update() |   >>> pwcView.update() | ||||||
|   False |   False | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | Security | ||||||
|  | ======== | ||||||
|  | 
 | ||||||
|  | Automatic security settings on persons | ||||||
|  | -------------------------------------- | ||||||
|  | 
 | ||||||
|  |   >>> from zope.traversing.api import getName | ||||||
|  |   >>> list(sorted(getName(c) for c in concepts['person'].getChildren())) | ||||||
|  |   [u'john', u'martha', u'person.newuser'] | ||||||
|  | 
 | ||||||
|  | Person objects that have a user assigned to them receive this user | ||||||
|  | (principal) as their owner. | ||||||
|  | 
 | ||||||
|  |   >>> from zope.app.securitypolicy.interfaces import IPrincipalRoleMap | ||||||
|  |   >>> IPrincipalRoleMap(concepts['john']).getPrincipalsAndRoles() | ||||||
|  |   [('loops.Owner', 'users.john', PermissionSetting: Allow)] | ||||||
|  |   >>> IPrincipalRoleMap(concepts['person.newuser']).getPrincipalsAndRoles() | ||||||
|  |   [('loops.Owner', u'loops.newuser', PermissionSetting: Allow)] | ||||||
|  | 
 | ||||||
|  | The person ``martha`` hasn't got a user id, so there is no role assigned | ||||||
|  | to it. | ||||||
|  | 
 | ||||||
|  |   >>> IPrincipalRoleMap(concepts['martha']).getPrincipalsAndRoles() | ||||||
|  |   [] | ||||||
|  | 
 | ||||||
|  | Only the owner (and a few other privileged people) should be able | ||||||
|  | to edit a person object. | ||||||
|  | 
 | ||||||
|  | We also need an interaction with a participation based on the principal | ||||||
|  | whose permissions we want to check. | ||||||
|  | 
 | ||||||
|  |   >>> from zope.app.authentication.principalfolder import Principal | ||||||
|  |   >>> pJohn = Principal('users.john', 'xxx', u'John') | ||||||
|  | 
 | ||||||
|  |   >>> from loops.tests.auth import login | ||||||
|  |   >>> login(pJohn) | ||||||
|  | 
 | ||||||
|  | We also want to grant some global permissions and roles, i.e. on the site or | ||||||
|  | loops root level. | ||||||
|  | 
 | ||||||
|  |   >>> rolePermissions = setupData.rolePermissions | ||||||
|  |   >>> rolePermissions.grantPermissionToRole('zope.View', 'zope.Member') | ||||||
|  | 
 | ||||||
|  |   >>> principalRoles = setupData.principalRoles | ||||||
|  |   >>> principalRoles.assignRoleToPrincipal('zope.Member', 'users.john') | ||||||
|  | 
 | ||||||
|  | Now we are ready to look for the real stuff - what John is allowed to do. | ||||||
|  | 
 | ||||||
|  |   >>> from zope.security import canAccess, canWrite, checkPermission | ||||||
|  |   >>> john = concepts['john'] | ||||||
|  | 
 | ||||||
|  |   >>> canAccess(john, 'title') | ||||||
|  |   True | ||||||
|  | 
 | ||||||
|  | Person objects that have an owner may be modified by this owner. | ||||||
|  | 
 | ||||||
|  |   >>> canWrite(john, 'title') | ||||||
|  |   True | ||||||
|  | 
 | ||||||
|  | So let's try with another user with another role setting. | ||||||
|  | 
 | ||||||
|  |   >>> rolePermissions.grantPermissionToRole('zope.ManageContent', 'loops.Staff') | ||||||
|  |   >>> principalRoles.assignRoleToPrincipal('loops.Staff', 'users.martha') | ||||||
|  |   >>> principalRoles.assignRoleToPrincipal('zope.Member', 'users.martha') | ||||||
|  | 
 | ||||||
|  |   >>> pMartha = Principal('users.martha', 'xxx', u'Martha') | ||||||
|  |   >>> login(pMartha) | ||||||
|  | 
 | ||||||
|  |   >>> canAccess(john, 'title') | ||||||
|  |   True | ||||||
|  |   >>> canWrite(john, 'title') | ||||||
|  |   False | ||||||
|  | 
 | ||||||
|  | If we clear the userId attribute from a person object others may be allowed | ||||||
|  | again to edit it... | ||||||
|  | 
 | ||||||
|  |   >>> adapted(john).userId = '' | ||||||
|  |   >>> canWrite(john, 'title') | ||||||
|  |   True | ||||||
|  | 
 | ||||||
|  | ... but John no more... | ||||||
|  | 
 | ||||||
|  |   >>> login(pJohn) | ||||||
|  |   >>> canWrite(john, 'title') | ||||||
|  |   False | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| Fin de partie | Fin de partie | ||||||
| ============= | ============= | ||||||
| 
 | 
 | ||||||
|   >>> placefulTearDown() |   >>> placefulTearDown() | ||||||
| 
 |  | ||||||
|  |  | ||||||
|  | @ -93,6 +93,13 @@ | ||||||
| 
 | 
 | ||||||
|   <!-- other adapters --> |   <!-- other adapters --> | ||||||
| 
 | 
 | ||||||
|  |   <zope:adapter | ||||||
|  |         for="loops.interfaces.ILoopsObject | ||||||
|  |              zope.publisher.interfaces.browser.IBrowserRequest" | ||||||
|  |         factory="loops.organize.memberinfo.MemberInfoProvider" | ||||||
|  |         permission="zope.Public" | ||||||
|  |         /> | ||||||
|  | 
 | ||||||
|   <zope:adapter factory="loops.organize.setup.SetupManager" |   <zope:adapter factory="loops.organize.setup.SetupManager" | ||||||
|                 name="organize" /> |                 name="organize" /> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -30,7 +30,8 @@ from zope.app.security.interfaces import IAuthentication, PrincipalLookupError | ||||||
| from zope.security.proxy import removeSecurityProxy | from zope.security.proxy import removeSecurityProxy | ||||||
| 
 | 
 | ||||||
| from cybertools.organize.interfaces import IPerson as IBasePerson | from cybertools.organize.interfaces import IPerson as IBasePerson | ||||||
| from loops.organize.util import getPrincipalFolder, authPluginId | from loops.interfaces import IConceptSchema | ||||||
|  | from loops.organize.util import getPrincipalFolder | ||||||
| from loops.util import _ | from loops.util import _ | ||||||
| 
 | 
 | ||||||
| ANNOTATION_KEY = 'loops.organize.person' | ANNOTATION_KEY = 'loops.organize.person' | ||||||
|  | @ -83,7 +84,7 @@ class LoginName(schema.TextLine): | ||||||
|                   mapping=dict(userId=userId))) |                   mapping=dict(userId=userId))) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class IPerson(IBasePerson): | class IPerson(IConceptSchema, IBasePerson): | ||||||
|     """ Resembles a human being with a name (first and last name), |     """ Resembles a human being with a name (first and last name), | ||||||
|         a birth date, and a set of addresses. This interface only |         a birth date, and a set of addresses. This interface only | ||||||
|         lists fields used in addition to those provided by the |         lists fields used in addition to those provided by the | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| # | # | ||||||
| #  Copyright (c) 2006 Helmut Merz helmutm@cy55.de | #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||||
| # | # | ||||||
| #  This program is free software; you can redistribute it and/or modify | #  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 | #  it under the terms of the GNU General Public License as published by | ||||||
|  | @ -36,10 +36,13 @@ from zope.i18nmessageid import MessageFactory | ||||||
| from zope.cachedescriptors.property import Lazy | from zope.cachedescriptors.property import Lazy | ||||||
| 
 | 
 | ||||||
| from cybertools.typology.interfaces import IType | from cybertools.typology.interfaces import IType | ||||||
| from loops.interfaces import ILoops | from loops.common import adapted | ||||||
| from loops.concept import Concept | from loops.concept import Concept | ||||||
|  | from loops.interfaces import ILoops | ||||||
| from loops.organize.interfaces import IMemberRegistrationManager | from loops.organize.interfaces import IMemberRegistrationManager | ||||||
| from loops.organize.util import getPrincipalFolder, authPluginId, getInternalPrincipal | from loops.organize.util import getPrincipalFolder, getGroupsFolder | ||||||
|  | from loops.organize.util import getInternalPrincipal | ||||||
|  | from loops.type import getOptionsDict | ||||||
| from loops.util import _ | from loops.util import _ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -51,29 +54,54 @@ class MemberRegistrationManager(object): | ||||||
|     def __init__(self, context): |     def __init__(self, context): | ||||||
|         self.context = context |         self.context = context | ||||||
| 
 | 
 | ||||||
|     def register(self, userId, password, lastName, firstName=u'', **kw): |     def register(self, userId, password, lastName, firstName=u'', | ||||||
|  |                  groups=[], useExisting=False, **kw): | ||||||
|         # step 1: create an internal principal in the loops principal folder: |         # step 1: create an internal principal in the loops principal folder: | ||||||
|         pFolder = getPrincipalFolder(self.context) |         pFolder = getPrincipalFolder(self.context) | ||||||
|         title = firstName and ' '.join((firstName, lastName)) or lastName |         title = firstName and ' '.join((firstName, lastName)) or lastName | ||||||
|         principal = InternalPrincipal(userId, password, title) |         principal = InternalPrincipal(userId, password, title) | ||||||
|  |         if useExisting: | ||||||
|  |             if userId not in pFolder: | ||||||
|                 pFolder[userId] = principal |                 pFolder[userId] = principal | ||||||
|         # step 2: create a corresponding person concept: |         else: | ||||||
|  |             pFolder[userId] = principal | ||||||
|  |         # step 2 (optional): assign to group(s) | ||||||
|  |         personType = self.context.getLoopsRoot().getConceptManager()['person'] | ||||||
|  |         od = getOptionsDict(adapted(personType).options) | ||||||
|  |         groupInfo = od.get('group') | ||||||
|  |         if groupInfo: | ||||||
|  |             gfName, groupNames = groupInfo.split(':') | ||||||
|  |             gFolder = getGroupsFolder(gfName) | ||||||
|  |             if not groups: | ||||||
|  |                 groups = groupNames.split(',') | ||||||
|  |         else: | ||||||
|  |             gFolder = getGroupsFolder() | ||||||
|  |         if gFolder is not None: | ||||||
|  |             for g in groups: | ||||||
|  |                 group = gFolder.get(g) | ||||||
|  |                 if group is not None: | ||||||
|  |                     members = list(group.principals) | ||||||
|  |                     members.append(pFolder.prefix + userId) | ||||||
|  |                     group.principals = members | ||||||
|  |         # step 3: create a corresponding person concept: | ||||||
|         cm = self.context.getConceptManager() |         cm = self.context.getConceptManager() | ||||||
|         id = baseId = 'person.' + userId |         id = baseId = 'person.' + userId | ||||||
|         # TODO: use NameChooser |         # TODO: use NameChooser | ||||||
|  |         if useExisting and id in cm: | ||||||
|  |             person = cm[id] | ||||||
|  |         else: | ||||||
|             num = 0 |             num = 0 | ||||||
|             while id in cm: |             while id in cm: | ||||||
|                 num +=1 |                 num +=1 | ||||||
|                 id = baseId + str(num) |                 id = baseId + str(num) | ||||||
|             person = cm[id] = Concept(title) |             person = cm[id] = Concept(title) | ||||||
|         # TODO: the name of the person type object must be kept flexible! |  | ||||||
|         # So we have to search for a type concept that has IPerson as |  | ||||||
|         # its typeInterface... |  | ||||||
|         person.conceptType = cm['person'] |         person.conceptType = cm['person'] | ||||||
|         personAdapter = IType(person).typeInterface(person) |         personAdapter = adapted(person) | ||||||
|         personAdapter.firstName = firstName |         personAdapter.firstName = firstName | ||||||
|         personAdapter.lastName = lastName |         personAdapter.lastName = lastName | ||||||
|         personAdapter.userId = '.'.join((authPluginId, userId)) |         personAdapter.userId = pFolder.prefix + userId | ||||||
|  |         for k, v in kw.items(): | ||||||
|  |             setattr(personAdapter, k, v) | ||||||
|         notify(ObjectCreatedEvent(person)) |         notify(ObjectCreatedEvent(person)) | ||||||
|         notify(ObjectModifiedEvent(person)) |         notify(ObjectModifiedEvent(person)) | ||||||
|         return personAdapter |         return personAdapter | ||||||
|  |  | ||||||
							
								
								
									
										57
									
								
								organize/memberinfo.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								organize/memberinfo.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,57 @@ | ||||||
|  | # | ||||||
|  | #  Copyright (c) 2008 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 | ||||||
|  | # | ||||||
|  | 
 | ||||||
|  | """ | ||||||
|  | Provide member properties based on person data. | ||||||
|  | 
 | ||||||
|  | $Id$ | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | from zope import component | ||||||
|  | from zope.app.security.interfaces import IAuthentication | ||||||
|  | from zope.cachedescriptors.property import Lazy | ||||||
|  | 
 | ||||||
|  | from cybertools.browser.member import MemberInfoProvider as BaseMemberInfoProvider | ||||||
|  | from cybertools.browser.member import MemberProperty | ||||||
|  | from cybertools.util.jeep import Jeep | ||||||
|  | from loops.common import adapted | ||||||
|  | from loops.organize.party import getPersonForUser | ||||||
|  | from loops import util | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class MemberInfoProvider(BaseMemberInfoProvider): | ||||||
|  | 
 | ||||||
|  |     def getData(self, principalId=None): | ||||||
|  |         if principalId is None: | ||||||
|  |             principal = self.request.principal | ||||||
|  |         else: | ||||||
|  |             #auth = component.getUtility(IAuthentication, self.context) | ||||||
|  |             auth = component.getUtility(IAuthentication) | ||||||
|  |             principal = auth.getPrincipal(principalId) | ||||||
|  |         person = getPersonForUser(self.context, self.request, principal) | ||||||
|  |         if person is None: | ||||||
|  |             return super(MemberInfoProvider, self).getData(principalId) | ||||||
|  |         aPerson = adapted(person) | ||||||
|  |         return Jeep((MemberProperty('id', principal.id, u'ID'), | ||||||
|  |                      MemberProperty('title', aPerson.title, u'Title'), | ||||||
|  |                      MemberProperty('description', aPerson.description, | ||||||
|  |                                     u'Description'), | ||||||
|  |                      MemberProperty('object', person, u'Object'), | ||||||
|  |                      MemberProperty('adapted', aPerson, u'Adapted object'), | ||||||
|  |                    )) | ||||||
|  | 
 | ||||||
|  | @ -38,10 +38,11 @@ from cybertools.organize.interfaces import IAddress | ||||||
| from cybertools.organize.party import Person as BasePerson | from cybertools.organize.party import Person as BasePerson | ||||||
| from cybertools.relation.interfaces import IRelationRegistry | from cybertools.relation.interfaces import IRelationRegistry | ||||||
| from cybertools.typology.interfaces import IType | from cybertools.typology.interfaces import IType | ||||||
|  | from loops.common import AdapterBase | ||||||
| from loops.concept import Concept | from loops.concept import Concept | ||||||
| from loops.interfaces import IConcept | from loops.interfaces import IConcept | ||||||
| from loops.organize.interfaces import IPerson, ANNOTATION_KEY | from loops.organize.interfaces import IPerson, ANNOTATION_KEY | ||||||
| from loops.common import AdapterBase | from loops.security.common import assignOwner, removeOwner, allowEditingForOwner | ||||||
| from loops.type import TypeInterfaceSourceList | from loops.type import TypeInterfaceSourceList | ||||||
| from loops import util | from loops import util | ||||||
| 
 | 
 | ||||||
|  | @ -54,6 +55,8 @@ TypeInterfaceSourceList.typeInterfaces += (IPerson, IAddress) | ||||||
| def getPersonForUser(context, request=None, principal=None): | def getPersonForUser(context, request=None, principal=None): | ||||||
|     if principal is None: |     if principal is None: | ||||||
|         principal = request.principal |         principal = request.principal | ||||||
|  |     if principal is None: | ||||||
|  |         return None | ||||||
|     loops = context.getLoopsRoot() |     loops = context.getLoopsRoot() | ||||||
|     pa = annotations(principal).get(ANNOTATION_KEY, None) |     pa = annotations(principal).get(ANNOTATION_KEY, None) | ||||||
|     if pa is None: |     if pa is None: | ||||||
|  | @ -88,13 +91,16 @@ class Person(AdapterBase, BasePerson): | ||||||
|             pa = annotations(principal) |             pa = annotations(principal) | ||||||
|             loopsId = util.getUidForObject(self.context.getLoopsRoot()) |             loopsId = util.getUidForObject(self.context.getLoopsRoot()) | ||||||
|             ann = pa.get(ANNOTATION_KEY) |             ann = pa.get(ANNOTATION_KEY) | ||||||
|             if ann is None: |             if ann is None: # or not isinstance(ann, PersistentMapping): | ||||||
|                 ann = pa[ANNOTATION_KEY] = PersistentMapping() |                 ann = pa[ANNOTATION_KEY] = PersistentMapping() | ||||||
|             ann[loopsId] = self.context |             ann[loopsId] = self.context | ||||||
|  |             assignOwner(self.context, userId) | ||||||
|         oldUserId = self.userId |         oldUserId = self.userId | ||||||
|         if oldUserId and oldUserId != userId: |         if oldUserId and oldUserId != userId: | ||||||
|             self.removeReferenceFromPrincipal(oldUserId) |             self.removeReferenceFromPrincipal(oldUserId) | ||||||
|  |             removeOwner(self.context, oldUserId) | ||||||
|         self.context._userId = userId |         self.context._userId = userId | ||||||
|  |         allowEditingForOwner(self.context, revert=not userId) | ||||||
|     userId = property(getUserId, setUserId) |     userId = property(getUserId, setUserId) | ||||||
| 
 | 
 | ||||||
|     def removeReferenceFromPrincipal(self, userId): |     def removeReferenceFromPrincipal(self, userId): | ||||||
|  |  | ||||||
|  | @ -4,8 +4,35 @@ import unittest, doctest | ||||||
| from zope.testing.doctestunit import DocFileSuite | from zope.testing.doctestunit import DocFileSuite | ||||||
| from zope.app.testing import ztapi | from zope.app.testing import ztapi | ||||||
| from zope.interface.verify import verifyClass | from zope.interface.verify import verifyClass | ||||||
|  | 
 | ||||||
|  | from zope import component | ||||||
|  | from zope.app.principalannotation.interfaces import IPrincipalAnnotationUtility | ||||||
|  | from zope.app.principalannotation import PrincipalAnnotationUtility | ||||||
|  | from zope.app.principalannotation import annotations | ||||||
|  | from zope.app.security.interfaces import IAuthentication | ||||||
|  | from zope.app.security.principalregistry import PrincipalRegistry | ||||||
|  | from zope.app.securitypolicy.interfaces import IRolePermissionManager | ||||||
|  | from zope.app.securitypolicy.interfaces import IPrincipalRoleManager | ||||||
|  | 
 | ||||||
|  | from cybertools.util.jeep import Jeep | ||||||
|  | from loops.organize.interfaces import IPerson | ||||||
| from loops.organize.party import Person | from loops.organize.party import Person | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | def setupUtilitiesAndAdapters(loopsRoot): | ||||||
|  |     auth = PrincipalRegistry() | ||||||
|  |     component.provideUtility(auth, IAuthentication) | ||||||
|  |     principalAnnotations = PrincipalAnnotationUtility() | ||||||
|  |     component.provideUtility(principalAnnotations, IPrincipalAnnotationUtility) | ||||||
|  |     component.provideAdapter(Person, provides=IPerson) | ||||||
|  |     return Jeep(( | ||||||
|  |             ('auth', auth), | ||||||
|  |             ('principalAnnotations', principalAnnotations), | ||||||
|  |             ('rolePermissions', IRolePermissionManager(loopsRoot)), | ||||||
|  |             ('principalRoles', IPrincipalRoleManager(loopsRoot)), | ||||||
|  |     )) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class Test(unittest.TestCase): | class Test(unittest.TestCase): | ||||||
|     "Basic tests for the organize sub-package." |     "Basic tests for the organize sub-package." | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -27,22 +27,39 @@ from zope.app.authentication.interfaces import IPluggableAuthentication | ||||||
| from zope.app.authentication.interfaces import IAuthenticatorPlugin | from zope.app.authentication.interfaces import IAuthenticatorPlugin | ||||||
| from zope.app.security.interfaces import IAuthentication | from zope.app.security.interfaces import IAuthentication | ||||||
| 
 | 
 | ||||||
| authPluginId = 'loops' | from loops.common import adapted | ||||||
|  | from loops.type import getOptionsDict | ||||||
|  | 
 | ||||||
|  | defaultAuthPluginId = 'loops' | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def getPrincipalFolder(context=None): | def getPrincipalFolder(context=None, authPluginId=None, ignoreErrors=False): | ||||||
|     pau = component.getUtility(IAuthentication, context=context) |     pau = component.getUtility(IAuthentication, context=context) | ||||||
|     if not IPluggableAuthentication.providedBy(pau): |     if not IPluggableAuthentication.providedBy(pau): | ||||||
|  |         if ignoreErrors: | ||||||
|  |             return None | ||||||
|         raise ValueError(u'There is no pluggable authentication ' |         raise ValueError(u'There is no pluggable authentication ' | ||||||
|                           'utility available.') |                           'utility available.') | ||||||
|     if not authPluginId in pau.authenticatorPlugins: |     if authPluginId is None and context is not None: | ||||||
|         raise ValueError(u'There is no loops authenticator ' |         person = context.getLoopsRoot().getConceptManager()['person'] | ||||||
|                           'plugin available.') |         od = getOptionsDict(adapted(person).options) | ||||||
|  |         authPluginId = od.get('principalfolder', defaultAuthPluginId) | ||||||
|  |     if authPluginId is None: | ||||||
|  |         authPluginId = defaultAuthPluginId | ||||||
|  |     if authPluginId not in pau.authenticatorPlugins: | ||||||
|  |         if ignoreErrors: | ||||||
|  |             return None | ||||||
|  |         raise ValueError(u"There is no loops authenticator " | ||||||
|  |                           "plugin '%s' available." % authPluginId) | ||||||
|     for name, plugin in pau.getAuthenticatorPlugins(): |     for name, plugin in pau.getAuthenticatorPlugins(): | ||||||
|         if name == authPluginId: |         if name == authPluginId: | ||||||
|             return plugin |             return plugin | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def getGroupsFolder(context=None, name='gloops'): | ||||||
|  |     return getPrincipalFolder(authPluginId=name, ignoreErrors=True) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| def getInternalPrincipal(id, context=None): | def getInternalPrincipal(id, context=None): | ||||||
|     pau = component.getUtility(IAuthentication, context=context) |     pau = component.getUtility(IAuthentication, context=context) | ||||||
|     if not IPluggableAuthentication.providedBy(pau): |     if not IPluggableAuthentication.providedBy(pau): | ||||||
|  |  | ||||||
							
								
								
									
										8
									
								
								query.py
									
										
									
									
									
								
							
							
						
						
									
										8
									
								
								query.py
									
										
									
									
									
								
							|  | @ -24,13 +24,13 @@ $Id$ | ||||||
| 
 | 
 | ||||||
| from zope import schema, component | from zope import schema, component | ||||||
| from zope.interface import Interface, Attribute, implements | from zope.interface import Interface, Attribute, implements | ||||||
| from zope import traversing |  | ||||||
| from zope.app.catalog.interfaces import ICatalog | from zope.app.catalog.interfaces import ICatalog | ||||||
| from zope.cachedescriptors.property import Lazy | from zope.cachedescriptors.property import Lazy | ||||||
| 
 | 
 | ||||||
| from cybertools.typology.interfaces import IType | from cybertools.typology.interfaces import IType | ||||||
| from loops.interfaces import IConcept, IConceptSchema |  | ||||||
| from loops.common import AdapterBase | from loops.common import AdapterBase | ||||||
|  | from loops.interfaces import IConcept, IConceptSchema | ||||||
|  | from loops.security.common import canListObject | ||||||
| from loops.type import TypeInterfaceSourceList | from loops.type import TypeInterfaceSourceList | ||||||
| from loops.versioning.util import getVersion | from loops.versioning.util import getVersion | ||||||
| from loops import util | from loops import util | ||||||
|  | @ -72,7 +72,8 @@ class BaseQuery(object): | ||||||
|             result = cat.searchResults(loops_type=(start, end), loops_title=title) |             result = cat.searchResults(loops_type=(start, end), loops_title=title) | ||||||
|         else: |         else: | ||||||
|             result = cat.searchResults(loops_type=(start, end)) |             result = cat.searchResults(loops_type=(start, end)) | ||||||
|         result = set(r for r in result if r.getLoopsRoot() == self.loopsRoot) |         result = set(r for r in result if r.getLoopsRoot() == self.loopsRoot | ||||||
|  |                      and canListObject(r)) | ||||||
|         if 'exclude' in kw: |         if 'exclude' in kw: | ||||||
|             r1 = set() |             r1 = set() | ||||||
|             for r in result: |             for r in result: | ||||||
|  | @ -139,6 +140,7 @@ class FullQuery(BaseQuery): | ||||||
|                 result = rc |                 result = rc | ||||||
|         result = set(r for r in result |         result = set(r for r in result | ||||||
|                             if r.getLoopsRoot() == self.loopsRoot |                             if r.getLoopsRoot() == self.loopsRoot | ||||||
|  |                                and canListObject(r) | ||||||
|                                and getVersion(r) == r) |                                and getVersion(r) == r) | ||||||
|         return result |         return result | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										27
									
								
								resource.py
									
										
									
									
									
								
							
							
						
						
									
										27
									
								
								resource.py
									
										
									
									
									
								
							|  | @ -1,5 +1,5 @@ | ||||||
| # | # | ||||||
| #  Copyright (c) 2005 Helmut Merz helmutm@cy55.de | #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||||
| # | # | ||||||
| #  This program is free software; you can redistribute it and/or modify | #  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 | #  it under the terms of the GNU General Public License as published by | ||||||
|  | @ -22,6 +22,8 @@ Definition of the Concept class. | ||||||
| $Id$ | $Id$ | ||||||
| """ | """ | ||||||
| 
 | 
 | ||||||
|  | from cStringIO import StringIO | ||||||
|  | from persistent import Persistent | ||||||
| from zope import component, schema | from zope import component, schema | ||||||
| from zope.app import zapi | from zope.app import zapi | ||||||
| from zope.app.container.btree import BTreeContainer | from zope.app.container.btree import BTreeContainer | ||||||
|  | @ -38,9 +40,6 @@ from zope.interface import implements | ||||||
| from zope.size.interfaces import ISized | from zope.size.interfaces import ISized | ||||||
| from zope.security.proxy import removeSecurityProxy | from zope.security.proxy import removeSecurityProxy | ||||||
| from zope.traversing.api import getName, getParent | from zope.traversing.api import getName, getParent | ||||||
| from persistent import Persistent |  | ||||||
| from cStringIO import StringIO |  | ||||||
| 
 |  | ||||||
| from zope.lifecycleevent import ObjectModifiedEvent, Attributes | from zope.lifecycleevent import ObjectModifiedEvent, Attributes | ||||||
| from zope.event import notify | from zope.event import notify | ||||||
| 
 | 
 | ||||||
|  | @ -50,7 +49,6 @@ from cybertools.storage.interfaces import IExternalStorage | ||||||
| from cybertools.text.interfaces import ITextTransform | from cybertools.text.interfaces import ITextTransform | ||||||
| from cybertools.typology.interfaces import IType, ITypeManager | from cybertools.typology.interfaces import IType, ITypeManager | ||||||
| from cybertools.util.jeep import Jeep | from cybertools.util.jeep import Jeep | ||||||
| 
 |  | ||||||
| from loops.base import ParentInfo | from loops.base import ParentInfo | ||||||
| from loops.common import ResourceAdapterBase, adapted | from loops.common import ResourceAdapterBase, adapted | ||||||
| from loops.concept import ResourceRelation | from loops.concept import ResourceRelation | ||||||
|  | @ -62,6 +60,7 @@ from loops.interfaces import IResourceManager, IResourceManagerContained | ||||||
| from loops.interfaces import ITypeConcept | from loops.interfaces import ITypeConcept | ||||||
| from loops.interfaces import ILoopsContained | from loops.interfaces import ILoopsContained | ||||||
| from loops.interfaces import IIndexAttributes | from loops.interfaces import IIndexAttributes | ||||||
|  | from loops.security.common import canListObject | ||||||
| from loops import util | from loops import util | ||||||
| from loops.versioning.util import getMaster | from loops.versioning.util import getMaster | ||||||
| from loops.view import TargetRelation | from loops.view import TargetRelation | ||||||
|  | @ -189,20 +188,24 @@ class Resource(Image, Contained): | ||||||
|             relationships = [TargetRelation] |             relationships = [TargetRelation] | ||||||
|         obj = getMaster(self)  # use the master version for relations |         obj = getMaster(self)  # use the master version for relations | ||||||
|         rels = getRelations(second=obj, relationships=relationships) |         rels = getRelations(second=obj, relationships=relationships) | ||||||
|         return [r.first for r in rels] |         return [r.first for r in rels if canListObject(r.first)] | ||||||
| 
 | 
 | ||||||
|     def getConceptRelations (self, predicates=None, concept=None, sort='default'): |     def getConceptRelations (self, predicates=None, concept=None, sort='default', | ||||||
|  |                              noSecurityCheck=False): | ||||||
|         predicates = predicates is None and ['*'] or predicates |         predicates = predicates is None and ['*'] or predicates | ||||||
|         obj = getMaster(self) |         obj = getMaster(self) | ||||||
|         relationships = [ResourceRelation(None, obj, p) for p in predicates] |         relationships = [ResourceRelation(None, obj, p) for p in predicates] | ||||||
|         if sort == 'default': |         if sort == 'default': | ||||||
|             sort = lambda x: (x.order, x.first.title.lower()) |             sort = lambda x: (x.order, x.first.title.lower()) | ||||||
|         return sorted(getRelations(first=concept, second=obj, relationships=relationships), |         rels = (r for r in getRelations(first=concept, second=obj, | ||||||
|                             key=sort) |                                         relationships=relationships) | ||||||
|  |                   if canListObject(r.first, noSecurityCheck)) | ||||||
|  |         return sorted(rels, key=sort) | ||||||
| 
 | 
 | ||||||
|     def getConcepts(self, predicates=None): |     def getConcepts(self, predicates=None, noSecurityCheck=False): | ||||||
|         obj = getMaster(self) |         obj = getMaster(self) | ||||||
|         return [r.first for r in obj.getConceptRelations(predicates)] |         return [r.first for r in obj.getConceptRelations(predicates, | ||||||
|  |                                                 noSecurityCheck=noSecurityCheck)] | ||||||
| 
 | 
 | ||||||
|     def assignConcept(self, concept, predicate=None, order=0, relevance=1.0): |     def assignConcept(self, concept, predicate=None, order=0, relevance=1.0): | ||||||
|         obj = getMaster(self) |         obj = getMaster(self) | ||||||
|  | @ -372,6 +375,8 @@ class ExternalFileAdapter(FileAdapter): | ||||||
|         self.storageName = storageName |         self.storageName = storageName | ||||||
| 
 | 
 | ||||||
|     def getData(self): |     def getData(self): | ||||||
|  |         if self.storageName == 'unknown':    # object not set up yet | ||||||
|  |             return '' | ||||||
|         storage = component.getUtility(IExternalStorage, name=self.storageName) |         storage = component.getUtility(IExternalStorage, name=self.storageName) | ||||||
|         return storage.getData(self.externalAddress, params=self.storageParams) |         return storage.getData(self.externalAddress, params=self.storageParams) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -35,6 +35,8 @@ class ResourceSchemaFactory(SchemaFactory): | ||||||
|     def __call__(self, interface, **kw): |     def __call__(self, interface, **kw): | ||||||
|         schema = super(ResourceSchemaFactory, self).__call__(interface, **kw) |         schema = super(ResourceSchemaFactory, self).__call__(interface, **kw) | ||||||
|         schema.fields.data.height = 10 |         schema.fields.data.height = 10 | ||||||
|  |         if self.context.contentType == 'text/html': | ||||||
|  |             schema.fields.data.fieldType = 'html' | ||||||
|         return schema |         return schema | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -90,8 +90,8 @@ a controller attribute for the search view. | ||||||
|   >>> searchView.controller = Controller(searchView, request) |   >>> searchView.controller = Controller(searchView, request) | ||||||
| 
 | 
 | ||||||
|   >>> searchView.submitReplacing('1.results', '1.search.form', pageView) |   >>> searchView.submitReplacing('1.results', '1.search.form', pageView) | ||||||
|   'return submitReplacing("1.results", "1.search.form", |   'submitReplacing("1.results", "1.search.form", | ||||||
|        "http://127.0.0.1/loops/views/page/.target29/@@searchresults.html")' |        "http://127.0.0.1/loops/views/page/.target29/@@searchresults.html");...' | ||||||
| 
 | 
 | ||||||
| Basic (text/title) search | Basic (text/title) search | ||||||
| ------------------------- | ------------------------- | ||||||
|  | @ -164,7 +164,9 @@ Now we can fill our search form and execute the query; note that all concepts | ||||||
| found are listed, plus all their children and all resources associated | found are listed, plus all their children and all resources associated | ||||||
| with them: | with them: | ||||||
| 
 | 
 | ||||||
|   >>> form = {'search.3.type': 'loops:concept:topic', 'search.3.text': u'zope'} |   >>> from loops import util | ||||||
|  |   >>> uid = util.getUidForObject(concepts['zope']) | ||||||
|  |   >>> form = {'search.3.type': 'loops:concept:topic', 'search.3.text': uid} | ||||||
|   >>> request = TestRequest(form=form) |   >>> request = TestRequest(form=form) | ||||||
|   >>> resultsView = SearchResults(page, request) |   >>> resultsView = SearchResults(page, request) | ||||||
|   >>> results = list(resultsView.results) |   >>> results = list(resultsView.results) | ||||||
|  | @ -173,7 +175,8 @@ with them: | ||||||
|   >>> results[0].context.__name__ |   >>> results[0].context.__name__ | ||||||
|   u'plone' |   u'plone' | ||||||
| 
 | 
 | ||||||
|   >>> form = {'search.3.type': 'loops:concept:topic', 'search.3.text': u'zope3'} |   >>> uid = util.getUidForObject(concepts['zope3']) | ||||||
|  |   >>> form = {'search.3.type': 'loops:concept:topic', 'search.3.text': uid} | ||||||
|   >>> request = TestRequest(form=form) |   >>> request = TestRequest(form=form) | ||||||
|   >>> resultsView = SearchResults(page, request) |   >>> resultsView = SearchResults(page, request) | ||||||
|   >>> results = list(resultsView.results) |   >>> results = list(resultsView.results) | ||||||
|  | @ -186,11 +189,11 @@ To support easy entry of concepts to search for we can preselect the available | ||||||
| concepts (optionally restricted to a certain type) by entering text parts | concepts (optionally restricted to a certain type) by entering text parts | ||||||
| of the concepts' titles: | of the concepts' titles: | ||||||
| 
 | 
 | ||||||
|   >>> form = {'searchType': 'loops:concept:topic', 'searchString': u'zope'} |   >>> form = {'searchType': 'loops:concept:topic', 'name': u'zope'} | ||||||
|   >>> request = TestRequest(form=form) |   >>> request = TestRequest(form=form) | ||||||
|   >>> view = Search(page, request) |   >>> view = Search(page, request) | ||||||
|   >>> view.listConcepts() |   >>> view.listConcepts() | ||||||
|   "[['Zope (Topic)', '33']]" |   u"{identifier: 'id', items: [{label: 'Zope (Topic)', name: 'Zope', id: '33'}]}" | ||||||
| 
 | 
 | ||||||
| Preset Concept Types on Search Forms | Preset Concept Types on Search Forms | ||||||
| ------------------------------------ | ------------------------------------ | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| # | # | ||||||
| #  Copyright (c) 2004 Helmut Merz helmutm@cy55.de | #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||||
| # | # | ||||||
| #  This program is free software; you can redistribute it and/or modify | #  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 | #  it under the terms of the GNU General Public License as published by | ||||||
|  | @ -83,30 +83,56 @@ class Search(BaseView): | ||||||
|     def initDojo(self): |     def initDojo(self): | ||||||
|         self.registerDojo() |         self.registerDojo() | ||||||
|         cm = self.controller.macros |         cm = self.controller.macros | ||||||
|         jsCall = 'dojo.require("dojo.widget.ComboBox")' |         jsCall = ('dojo.require("dojo.parser");' | ||||||
|  |                   'dojo.require("dijit.form.FilteringSelect");' | ||||||
|  |                   'dojo.require("dojox.data.QueryReadStore");') | ||||||
|         cm.register('js-execute', jsCall, jsCall=jsCall) |         cm.register('js-execute', jsCall, jsCall=jsCall) | ||||||
| 
 | 
 | ||||||
|     def listConcepts(self): |     def listConcepts(self): | ||||||
|         """ Used for dojo.widget.ComboBox. |         """ Used for dijit.FilteringSelect. | ||||||
|         """ |         """ | ||||||
|         request = self.request |         request = self.request | ||||||
|         request.response.setHeader('Content-Type', 'text/plain; charset=UTF-8') |         request.response.setHeader('Content-Type', 'text/plain; charset=UTF-8') | ||||||
|         title = request.get('searchString', '').replace('(', ' ').replace(')', ' ') |         title = request.get('name') | ||||||
|         type = request.get('searchType') or 'loops:concept:*' |         if title == '*': | ||||||
|         result = ConceptQuery(self).query(title=title, type=type, exclude=('system',)) |             title = None | ||||||
|         #registry = component.getUtility(IRelationRegistry) |         type = request.get('searchType') | ||||||
|         # simple way to provide JSON format: |         data = [] | ||||||
|         return str(sorted([[`adapted(o, self.languageInfo).title`[2:-1] |         if title or type: | ||||||
|                                 + ' (%s)' % `o.conceptType.title`[2:-1], |             if title is not None: | ||||||
|                             `int(util.getUidForObject(o))`] |                 title = title.replace('(', ' ').replace(')', ' ').replace(' -', ' ') | ||||||
|                         for o in result |                 #title = title.split(' ', 1)[0] | ||||||
|                         if o.getLoopsRoot() == self.loopsRoot])).replace('\\\\x', '\\x') |             if not type: | ||||||
|         #return str(sorted([[`o.title`[2:-1], `traversing.api.getName(o)`[2:-1]] |                 type = 'loops:concept:*' | ||||||
|         #                for o in result])).replace('\\\\x', '\\x') |             result = ConceptQuery(self).query(title=title or None, type=type, | ||||||
|  |                                               exclude=('system',)) | ||||||
|  |             for o in result: | ||||||
|  |                 if o.getLoopsRoot() == self.loopsRoot: | ||||||
|  |                     name = adapted(o, self.languageInfo).title | ||||||
|  |                     if title and title.endswith('*'): | ||||||
|  |                         title = title[:-1] | ||||||
|  |                     sort = ((title and name.startswith(title) and '0' or '1') | ||||||
|  |                             + name.lower()) | ||||||
|  |                     if o.conceptType is None: | ||||||
|  |                         raise ValueError('Concept Type missing for %r.' % name) | ||||||
|  |                     data.append({'label': '%s (%s)' % (name, o.conceptType.title), | ||||||
|  |                                  'name': name, | ||||||
|  |                                  'id': util.getUidForObject(o), | ||||||
|  |                                  'sort': sort}) | ||||||
|  |         data.sort(key=lambda x: x['sort']) | ||||||
|  |         if not title: | ||||||
|  |             data.insert(0, {'label': '', 'name': '', 'id': ''}) | ||||||
|  |         json = [] | ||||||
|  |         for item in data: | ||||||
|  |             json.append("{label: '%s', name: '%s', id: '%s'}" % | ||||||
|  |                           (item['label'], item['name'], item['id'])) | ||||||
|  |         json = "{identifier: 'id', items: [%s]}" % ', '.join(json) | ||||||
|  |         #print '***', json | ||||||
|  |         return json | ||||||
| 
 | 
 | ||||||
|     def submitReplacing(self, targetId, formId, view): |     def submitReplacing(self, targetId, formId, view): | ||||||
|         self.registerDojo() |         self.registerDojo() | ||||||
|         return 'return submitReplacing("%s", "%s", "%s")' % ( |         return 'submitReplacing("%s", "%s", "%s"); return false;' % ( | ||||||
|                     targetId, formId, |                     targetId, formId, | ||||||
|                     '%s/.target%s/@@searchresults.html' % (view.url, self.uniqueId)) |                     '%s/.target%s/@@searchresults.html' % (view.url, self.uniqueId)) | ||||||
| 
 | 
 | ||||||
|  | @ -131,13 +157,14 @@ class SearchResults(BaseView): | ||||||
|         useTitle = form.get('search.2.title') |         useTitle = form.get('search.2.title') | ||||||
|         useFull = form.get('search.2.full') |         useFull = form.get('search.2.full') | ||||||
|         conceptType = form.get('search.3.type', 'loops:concept:*') |         conceptType = form.get('search.3.type', 'loops:concept:*') | ||||||
|         conceptTitle = form.get('search.3.text') |         #conceptTitle = form.get('search.3.text') | ||||||
|         if conceptTitle is not None: |         #if conceptTitle is not None: | ||||||
|             conceptTitle = util.toUnicode(conceptTitle, encoding='ISO8859-15') |         #    conceptTitle = util.toUnicode(conceptTitle, encoding='ISO8859-15') | ||||||
|         conceptUid = form.get('search.3.text_selected') |         conceptUid = form.get('search.3.text') | ||||||
|         result = FullQuery(self).query(text=text, type=type, |         result = FullQuery(self).query(text=text, type=type, | ||||||
|                            useTitle=useTitle, useFull=useFull, |                            useTitle=useTitle, useFull=useFull, | ||||||
|                            conceptTitle=conceptTitle, conceptUid=conceptUid, |                            #conceptTitle=conceptTitle, | ||||||
|  |                            conceptUid=conceptUid, | ||||||
|                            conceptType=conceptType) |                            conceptType=conceptType) | ||||||
|         rowNum = 4 |         rowNum = 4 | ||||||
|         while rowNum < 10: |         while rowNum < 10: | ||||||
|  |  | ||||||
|  | @ -4,11 +4,11 @@ | ||||||
|                    idPrefix string:${view/itemNum}.search; |                    idPrefix string:${view/itemNum}.search; | ||||||
|                    formId string:$idPrefix.form; |                    formId string:$idPrefix.form; | ||||||
|                    resultsId string:$idPrefix.results"> |                    resultsId string:$idPrefix.results"> | ||||||
|     <h3 tal:attributes="class string:content-$level; |     <h1 tal:attributes="class string:content-$level; | ||||||
|                         ondblclick item/openEditWindow" |                         ondblclick item/openEditWindow" | ||||||
|       tal:content="item/title"> |       tal:content="item/title"> | ||||||
|       Search |       Search | ||||||
|     </h3> |     </h1> | ||||||
| 
 | 
 | ||||||
|     <div metal:define-macro="search_form" class="searchForm"> |     <div metal:define-macro="search_form" class="searchForm"> | ||||||
|       <fieldset class="box"> |       <fieldset class="box"> | ||||||
|  | @ -46,7 +46,7 @@ | ||||||
|          i18n:domain="loops"> |          i18n:domain="loops"> | ||||||
|       <fieldset class="box" |       <fieldset class="box" | ||||||
|                 tal:condition="request/search.submitted | nothing"> |                 tal:condition="request/search.submitted | nothing"> | ||||||
|         <legend i18n:translate="">Search results</legend> |         <h2 i18n:translate="">Search results</h2> | ||||||
|         <table class="listing" summary="Search results" |         <table class="listing" summary="Search results" | ||||||
|                i18n:attributes="summary"> |                i18n:attributes="summary"> | ||||||
|           <thead> |           <thead> | ||||||
|  | @ -113,7 +113,7 @@ | ||||||
|   <tr> |   <tr> | ||||||
|     <td metal:use-macro="macros/minus"/> |     <td metal:use-macro="macros/minus"/> | ||||||
|     <td colspan="3"> |     <td colspan="3"> | ||||||
|       <h3 i18n:translate="">Type(s) to search for</h3> |       <h2 i18n:translate="">Type(s) to search for</h2> | ||||||
|     </td> |     </td> | ||||||
|   <tr> |   <tr> | ||||||
|     <td></td> |     <td></td> | ||||||
|  | @ -144,7 +144,7 @@ | ||||||
|   <tr> |   <tr> | ||||||
|     <td metal:use-macro="macros/minus"/> |     <td metal:use-macro="macros/minus"/> | ||||||
|     <td colspan="3"> |     <td colspan="3"> | ||||||
|       <h3 i18n:translate="">Text-based search</h3> |       <h2 i18n:translate="">Text-based search</h2> | ||||||
|     </td> |     </td> | ||||||
|   <tr> |   <tr> | ||||||
|     <td></td> |     <td></td> | ||||||
|  | @ -185,7 +185,7 @@ | ||||||
|   <tr> |   <tr> | ||||||
|     <td metal:use-macro="macros/minus"/> |     <td metal:use-macro="macros/minus"/> | ||||||
|     <td colspan="3"> |     <td colspan="3"> | ||||||
|       <h3 i18n:translate="">Search via related concepts</h3> |       <h2 i18n:translate="">Search via related concepts</h2> | ||||||
|     </td> |     </td> | ||||||
|   <tr tal:repeat="type item/presetSearchTypes"> |   <tr tal:repeat="type item/presetSearchTypes"> | ||||||
|     <tal:preset define="rowNum item/rowNum; |     <tal:preset define="rowNum item/rowNum; | ||||||
|  | @ -249,10 +249,14 @@ | ||||||
|     </td> |     </td> | ||||||
|     <td> |     <td> | ||||||
|       <tal:combo tal:define="dummy item/initDojo"> |       <tal:combo tal:define="dummy item/initDojo"> | ||||||
|         <input dojoType="comboBox" mode="remote" autoComplete="False" |         <div dojoType="dojox.data.QueryReadStore" jsId="conceptSearch" | ||||||
|  |              url="listConceptsForComboBox.js?searchType=" > | ||||||
|  |         </div> | ||||||
|  |         <input dojoType="dijit.form.FilteringSelect" store="conceptSearch" | ||||||
|  |                autoComplete="False" labelAttr="label" | ||||||
|  |                name="concept.search.text" id="concept.search.text" | ||||||
|                tal:attributes="name string:$namePrefix.text; |                tal:attributes="name string:$namePrefix.text; | ||||||
|                                id string:$idPrefix.text; |                                id string:$idPrefix.text" /> | ||||||
|                                dataUrl string:${context/@@absolute_url}/listConceptsForComboBox.js?searchString=%{searchString}&searchType=" /> |  | ||||||
|       </tal:combo> |       </tal:combo> | ||||||
|     </td> |     </td> | ||||||
|   </tr> |   </tr> | ||||||
|  |  | ||||||
							
								
								
									
										49
									
								
								security.zcml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								security.zcml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,49 @@ | ||||||
|  | <!-- $Id$ --> | ||||||
|  | 
 | ||||||
|  | <configure | ||||||
|  |    xmlns="http://namespaces.zope.org/zope" | ||||||
|  |    xmlns:i18n="http://namespaces.zope.org/i18n" | ||||||
|  |    i18n_domain="loops"> | ||||||
|  | 
 | ||||||
|  |   <!-- permissions --> | ||||||
|  | 
 | ||||||
|  |   <permission | ||||||
|  |       id="loops.xmlrpc.ManageConcepts" | ||||||
|  |       title="[loops-xmlrpc-manage-concepts-permission] loops: Manage Concepts (XML-RPC)" /> | ||||||
|  | 
 | ||||||
|  |   <permission | ||||||
|  |       id="loops.ManageSite" | ||||||
|  |       title="[loops-manage-site-permission] loops: Manage Site" /> | ||||||
|  | 
 | ||||||
|  |   <permission | ||||||
|  |       id="loops.ViewRestricted" | ||||||
|  |       title="[loops-view-restricted-permission] loops: View Restricted Information" /> | ||||||
|  | 
 | ||||||
|  |   <!-- roles and default permissions for roles --> | ||||||
|  | 
 | ||||||
|  |   <role id="loops.SiteManager" | ||||||
|  |         title="[loops-manage-site-role] loops: Site Manager" /> | ||||||
|  |   <grant role="loops.SiteManager" permission="loops.ManageSite" /> | ||||||
|  |   <grant role="loops.SiteManager" permission="loops.xmlrpc.ManageConcepts" /> | ||||||
|  |   <grant role="loops.SiteManager" permission="zope.ManageContent" /> | ||||||
|  |   <grant role="loops.SiteManager" permission="zope.View" /> | ||||||
|  | 
 | ||||||
|  |   <role id="loops.Staff" | ||||||
|  |         title="[loops-staff-role] loops: Staff" /> | ||||||
|  |   <grant role="loops.Staff" permission="zope.ManageContent" /> | ||||||
|  |   <grant role="loops.Staff" permission="zope.View" /> | ||||||
|  | 
 | ||||||
|  |   <role id="loops.Master" | ||||||
|  |         title="[loops-master-role] loops: Master" /> | ||||||
|  | 
 | ||||||
|  |   <role id="loops.xmlrpc.ConceptManager" | ||||||
|  |         title="[xmlrpc-manage-concepts-role] loops: Concept Manager (XML-RPC)" /> | ||||||
|  |   <grant role="loops.xmlrpc.ConceptManager" permission="loops.xmlrpc.ManageConcepts" /> | ||||||
|  | 
 | ||||||
|  |   <role id="loops.Owner" | ||||||
|  |         title="[loops-owner-role] Owner" /> | ||||||
|  |   <grant role="loops.Owner" permission="zope.ManageContent" /> | ||||||
|  |   <grant role="loops.Owner" permission="loops.ViewRestricted" /> | ||||||
|  |   <grant role="loops.Owner" permission="zope.View" /> | ||||||
|  | 
 | ||||||
|  | </configure> | ||||||
							
								
								
									
										3
									
								
								security/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								security/__init__.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | ||||||
|  | """ | ||||||
|  | $Id$ | ||||||
|  | """ | ||||||
							
								
								
									
										133
									
								
								security/common.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								security/common.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,133 @@ | ||||||
|  | # | ||||||
|  | #  Copyright (c) 2008 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 | ||||||
|  | # | ||||||
|  | 
 | ||||||
|  | """ | ||||||
|  | Common functions and other stuff for working with permissions and roles. | ||||||
|  | 
 | ||||||
|  | $Id$ | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | from zope import component | ||||||
|  | from zope.app.securitypolicy.interfaces import IPrincipalRoleManager | ||||||
|  | from zope.app.securitypolicy.interfaces import IRolePermissionManager | ||||||
|  | from zope.cachedescriptors.property import Lazy | ||||||
|  | from zope.interface import implements | ||||||
|  | from zope.lifecycleevent import IObjectCreatedEvent | ||||||
|  | from zope.security import canAccess, canWrite | ||||||
|  | from zope.security import checkPermission as baseCheckPermission | ||||||
|  | from zope.security.management import getInteraction | ||||||
|  | 
 | ||||||
|  | from loops.common import adapted | ||||||
|  | from loops.interfaces import ILoopsObject, IConcept | ||||||
|  | from loops.interfaces import IAssignmentEvent, IDeassignmentEvent | ||||||
|  | from loops.security.interfaces import ISecuritySetter | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | allRolesExceptOwner = ( | ||||||
|  |         #'zope.SiteManager' - no, not this one... | ||||||
|  |         'zope.Anonymous', 'zope.Member', 'zope.ContentManager', 'loops.Staff', | ||||||
|  |         'loops.xmlrpc.ConceptManager', # relevant for local security? | ||||||
|  |         #'loops.SiteManager', | ||||||
|  |         'loops.Master',) | ||||||
|  | allRolesExceptOwnerAndMaster = tuple(allRolesExceptOwner[:-1]) | ||||||
|  | minorPrivilegedRoles = ('zope.Anonymous', 'zope.Member',) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # checking and querying functions | ||||||
|  | 
 | ||||||
|  | def canAccessObject(obj): | ||||||
|  |     return canAccess(obj, 'title') | ||||||
|  | 
 | ||||||
|  | def canListObject(obj, noCheck=False): | ||||||
|  |     if noCheck: | ||||||
|  |         return True | ||||||
|  |     return canAccess(obj, 'title') | ||||||
|  | 
 | ||||||
|  | def canWriteObject(obj): | ||||||
|  |     return canWrite(obj, 'title') | ||||||
|  | 
 | ||||||
|  | def checkPermission(permission, obj): | ||||||
|  |     return baseCheckPermission(permission, obj) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def getCurrentPrincipal(): | ||||||
|  |     interaction = getInteraction() | ||||||
|  |     if interaction is not None: | ||||||
|  |         parts = interaction.participations | ||||||
|  |         if parts: | ||||||
|  |             return parts[0].principal | ||||||
|  |     return None | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # functions for setting security properties | ||||||
|  | 
 | ||||||
|  | def assignOwner(obj, principalId): | ||||||
|  |     prm = IPrincipalRoleManager(obj) | ||||||
|  |     prm.assignRoleToPrincipal('loops.Owner', principalId) | ||||||
|  | 
 | ||||||
|  | def removeOwner(obj, principalId): | ||||||
|  |     prm = IPrincipalRoleManager(obj) | ||||||
|  |     prm.removeRoleFromPrincipal('loops.Owner', principalId) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def allowEditingForOwner(obj, deny=allRolesExceptOwner, revert=False): | ||||||
|  |     rpm = IRolePermissionManager(obj) | ||||||
|  |     if revert: | ||||||
|  |         for role in deny: | ||||||
|  |             rpm.unsetPermissionFromRole('zope.ManageContent', role) | ||||||
|  |         rpm.unsetPermissionFromRole('zope.ManageContent', 'loops.Owner') | ||||||
|  |     else: | ||||||
|  |         for role in deny: | ||||||
|  |             rpm.denyPermissionToRole('zope.ManageContent', role) | ||||||
|  |         rpm.grantPermissionToRole('zope.ManageContent', 'loops.Owner') | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def restrictView(obj, roles=allRolesExceptOwnerAndMaster, revert=False): | ||||||
|  |     rpm = IRolePermissionManager(obj) | ||||||
|  |     if revert: | ||||||
|  |         for role in roles: | ||||||
|  |             rpm.unsetPermissionFromRole('zope.View', role) | ||||||
|  |     else: | ||||||
|  |         for role in roles: | ||||||
|  |             rpm.denyPermissionToRole('zope.View', role) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # event handlers | ||||||
|  | 
 | ||||||
|  | @component.adapter(ILoopsObject, IObjectCreatedEvent) | ||||||
|  | def setDefaultSecurity(obj, event): | ||||||
|  |     aObj = adapted(obj) | ||||||
|  |     setter = ISecuritySetter(aObj) | ||||||
|  |     setter.setDefaultRolePermissions() | ||||||
|  |     setter.setDefaultPrincipalRoles() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @component.adapter(IConcept, IAssignmentEvent) | ||||||
|  | def grantAcquiredSecurity(obj, event): | ||||||
|  |     aObj = adapted(obj) | ||||||
|  |     setter = ISecuritySetter(aObj) | ||||||
|  |     setter.setAcquiredRolePermissions(event.relation) | ||||||
|  |     setter.setAcquiredPrincipalRoles(event.relation) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @component.adapter(IConcept, IDeassignmentEvent) | ||||||
|  | def revokeAcquiredSecurity(obj, event): | ||||||
|  |     aObj = adapted(obj) | ||||||
|  |     setter = ISecuritySetter(aObj) | ||||||
|  |     setter.setAcquiredRolePermissions(event.relation, revert=True) | ||||||
|  |     setter.setAcquiredPrincipalRoles(event.relation, revert=True) | ||||||
							
								
								
									
										37
									
								
								security/configure.zcml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								security/configure.zcml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,37 @@ | ||||||
|  | <!-- $Id$ --> | ||||||
|  | 
 | ||||||
|  | <configure | ||||||
|  |    xmlns:zope="http://namespaces.zope.org/zope" | ||||||
|  |    xmlns:browser="http://namespaces.zope.org/browser" | ||||||
|  |    i18n_domain="zope"> | ||||||
|  | 
 | ||||||
|  |   <zope:adapter factory="loops.security.setter.BaseSecuritySetter" /> | ||||||
|  | 
 | ||||||
|  |   <zope:adapter | ||||||
|  |         for="loops.interfaces.IConcept" | ||||||
|  |         factory="zope.app.securitypolicy.rolepermission.AnnotationRolePermissionManager" | ||||||
|  |         trusted="true" /> | ||||||
|  |   <zope:adapter | ||||||
|  |         for="loops.interfaces.IResource" | ||||||
|  |         factory="zope.app.securitypolicy.rolepermission.AnnotationRolePermissionManager" | ||||||
|  |         trusted="true" /> | ||||||
|  |   <zope:adapter | ||||||
|  |         for="loops.interfaces.IView" | ||||||
|  |         factory="zope.app.securitypolicy.rolepermission.AnnotationRolePermissionManager" | ||||||
|  |         trusted="true" /> | ||||||
|  | 
 | ||||||
|  |   <zope:subscriber handler="loops.security.common.setDefaultSecurity" /> | ||||||
|  |   <zope:subscriber handler="loops.security.common.grantAcquiredSecurity" /> | ||||||
|  |   <zope:subscriber handler="loops.security.common.revokeAcquiredSecurity" /> | ||||||
|  | 
 | ||||||
|  |   <browser:page name="permissions.html" for="*" | ||||||
|  |         class=".perm.PermissionView" | ||||||
|  |         template="manage_permissionform.pt" | ||||||
|  |         permission="zope.Security" /> | ||||||
|  | 
 | ||||||
|  |   <browser:menuItem for="*" action="@@permissions.html" | ||||||
|  |         menu="zmi_actions" title="Edit Permissions" | ||||||
|  |         filter="python: context.__name__ not in ('views', 'concepts', 'resources')" | ||||||
|  |         permission="zope.Security" /> | ||||||
|  | 
 | ||||||
|  | </configure> | ||||||
							
								
								
									
										54
									
								
								security/interfaces.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								security/interfaces.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,54 @@ | ||||||
|  | # | ||||||
|  | #  Copyright (c) 2008 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 | ||||||
|  | # | ||||||
|  | 
 | ||||||
|  | """ | ||||||
|  | Interfaces for loops security management. | ||||||
|  | 
 | ||||||
|  | $Id$ | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | from zope.interface import Interface, Attribute | ||||||
|  | from zope import interface, component, schema | ||||||
|  | 
 | ||||||
|  | from loops.util import _ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class ISecuritySetter(Interface): | ||||||
|  | 
 | ||||||
|  |     def setDefaultRolePermissions(): | ||||||
|  |         """ Set some default role permission assignments (grants) on the | ||||||
|  |             context object. | ||||||
|  |         """ | ||||||
|  | 
 | ||||||
|  |     def setDefaultPrincipalRoles(): | ||||||
|  |         """ Assign default roles (e.g. owner) to certain principals | ||||||
|  |             (e.g. the user that created the object). | ||||||
|  |         """ | ||||||
|  | 
 | ||||||
|  |     def setAcquiredRolePermissions(relation, revert=False): | ||||||
|  |         """ Grant role permissions on children/resources for the relation given. | ||||||
|  | 
 | ||||||
|  |             If the ``revert`` argument is true unset the corresponding settings. | ||||||
|  |         """ | ||||||
|  | 
 | ||||||
|  |     def setAcquiredPrincipalRoles(relation, revert=False): | ||||||
|  |         """ Assign roles on children/resources for the relation given. | ||||||
|  | 
 | ||||||
|  |             If the ``revert`` argument is true unset the corresponding settings. | ||||||
|  |         """ | ||||||
|  | 
 | ||||||
							
								
								
									
										115
									
								
								security/manage_permissionform.pt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								security/manage_permissionform.pt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,115 @@ | ||||||
|  | <html metal:use-macro="context/@@standard_macros/view" | ||||||
|  |     i18n:domain="zope"> | ||||||
|  | <head> | ||||||
|  |   <style metal:fill-slot="headers" type="text/css"> | ||||||
|  |     <!-- | ||||||
|  |     .row-normal { | ||||||
|  |       background-color: #ffffff; | ||||||
|  |       border: none; | ||||||
|  |     } | ||||||
|  |     .row-hilite { | ||||||
|  |       background-color: #efefef; | ||||||
|  |       border: none; | ||||||
|  |     } | ||||||
|  |     --> | ||||||
|  |   </style> | ||||||
|  | </head> | ||||||
|  | 
 | ||||||
|  | <body> | ||||||
|  | <div metal:fill-slot="body"> | ||||||
|  |   <p tal:define="status view/update" | ||||||
|  |      tal:condition="status" | ||||||
|  |      tal:content="status" /> | ||||||
|  | 
 | ||||||
|  |   <div tal:define="permId view/permissionId; | ||||||
|  |                    perm view/permission;"> | ||||||
|  |     <form> | ||||||
|  |       <select name="permission_to_manage"> | ||||||
|  |         <option tal:repeat="pId view/getPermissions" | ||||||
|  |                 tal:attributes="value pId; | ||||||
|  |                                 selected python: pId == permId" | ||||||
|  |                 tal:content="pId" /> | ||||||
|  |       </select> | ||||||
|  |       <input type="submit" name="select_permission" | ||||||
|  |              i18n:attributes="value" | ||||||
|  |              value="Select Permission" /> | ||||||
|  |     </form> | ||||||
|  |     <p class="form-text" i18n:translate=""> | ||||||
|  |       Roles assigned to the permission | ||||||
|  |       <strong tal:content="perm/title" | ||||||
|  |           i18n:name="perm_title" i18n:translate="">Change DTML Methods</strong> | ||||||
|  |       (id: <strong tal:content="permId" | ||||||
|  |           i18n:name="perm_id">Zope.Some.Permission</strong>) | ||||||
|  |     </p> | ||||||
|  | 
 | ||||||
|  |     <form method="post"> | ||||||
|  |       <input type="hidden" name="permission_to_manage" value="Permission Name" | ||||||
|  |              tal:attributes="value permId" /> | ||||||
|  |       <input type="hidden" name="permission_id" value="Permission Name" | ||||||
|  |              tal:attributes="value permId" /> | ||||||
|  |       <div class="form-element"> | ||||||
|  | 
 | ||||||
|  |           <table width="100%" cellspacing="0" cellpadding="2" border="0" | ||||||
|  |               nowrap="nowrap"> | ||||||
|  | 
 | ||||||
|  |             <tr class="list-header"> | ||||||
|  |               <td><strong i18n:translate="">Role</strong></td> | ||||||
|  |               <td><strong i18n:translate="">Users/Groups</strong></td> | ||||||
|  |               <td><strong i18n:translate="">Acquired Setting</strong></td> | ||||||
|  |               <td><strong i18n:translate="">Setting</strong></td> | ||||||
|  |             </tr> | ||||||
|  | 
 | ||||||
|  |             <tr class="row-normal" | ||||||
|  |                 tal:repeat="setting perm/roleSettings" | ||||||
|  |                 tal:attributes="class python: | ||||||
|  |                     path('repeat/setting/even') and 'row-normal' or 'row-hilite'"> | ||||||
|  |               <tal:role define="ir repeat/setting/index; | ||||||
|  |                                 roleId python:path('view/roles')[ir].id"> | ||||||
|  |               <td align="left" valign="top" | ||||||
|  |                   tal:content="roleId"> | ||||||
|  |                 Manager | ||||||
|  |               </td> | ||||||
|  |               <td> | ||||||
|  |                 <span tal:define="users python: view.listUsersForRole(roleId)" | ||||||
|  |                       tal:replace="structure users"> | ||||||
|  |                   User xy | ||||||
|  |                 </span> | ||||||
|  |               </td> | ||||||
|  |               <td> | ||||||
|  |                 <span tal:replace="python: | ||||||
|  |                         view.getAcquiredPermissionSetting(roleId, permId)" /> | ||||||
|  |               </td> | ||||||
|  |               <td> | ||||||
|  |                 <select name="settings:list"> | ||||||
|  |                     <option value="Unset" | ||||||
|  |                        tal:repeat="option view/availableSettings" | ||||||
|  |                        tal:attributes="value option/id; | ||||||
|  |                                        selected python:setting == option['id']" | ||||||
|  |                        tal:content="option/shorttitle" | ||||||
|  |                        i18n:translate="">+</option> | ||||||
|  |                 </select> | ||||||
|  |               </td> | ||||||
|  |               </tal:role> | ||||||
|  |             </tr> | ||||||
|  |             <tr tal:define="principals view/getPrincipalPermissions" | ||||||
|  |                 tal:condition="principals"> | ||||||
|  |               <td> | ||||||
|  |                 <strong i18n:translate="">Direct Settings</strong> | ||||||
|  |               </td> | ||||||
|  |               <td colspan="3" tal:content="structure principals">+xyz</td> | ||||||
|  |             </tr> | ||||||
|  |           </table> | ||||||
|  | 
 | ||||||
|  |       </div> | ||||||
|  | 
 | ||||||
|  |       <br /> | ||||||
|  |       <div class="form-element"> | ||||||
|  |         <input class="form-element" type="submit" name="SUBMIT_PERMS" | ||||||
|  |             value="Save Changes" i18n:attributes="value save-changes-button"/> | ||||||
|  |       </div> | ||||||
|  |     </form> | ||||||
|  | 
 | ||||||
|  |   </div> | ||||||
|  | </div> | ||||||
|  | </body> | ||||||
|  | </html> | ||||||
							
								
								
									
										139
									
								
								security/perm.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								security/perm.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,139 @@ | ||||||
|  | # | ||||||
|  | #  Copyright (c) 2008 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 | ||||||
|  | # | ||||||
|  | 
 | ||||||
|  | """ | ||||||
|  | Authentication view. | ||||||
|  | 
 | ||||||
|  | $Id$ | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | from zope import component | ||||||
|  | from zope.interface import implements | ||||||
|  | from zope.cachedescriptors.property import Lazy | ||||||
|  | from zope.app.security.interfaces import IPermission | ||||||
|  | from zope.app.securitypolicy.browser.rolepermissionview import RolePermissionView | ||||||
|  | from zope.app.securitypolicy.interfaces import IPrincipalRoleManager, IRolePermissionMap | ||||||
|  | from zope.app.securitypolicy.interfaces import IPrincipalPermissionManager, \ | ||||||
|  |                                                IPrincipalPermissionMap | ||||||
|  | from zope.app.securitypolicy.zopepolicy import SettingAsBoolean | ||||||
|  | from zope.publisher.browser import BrowserView | ||||||
|  | from zope.security.proxy import removeSecurityProxy | ||||||
|  | from zope.traversing.api import getParents | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class PermissionView(object): | ||||||
|  |     """ View for permission editing. | ||||||
|  |     """ | ||||||
|  | 
 | ||||||
|  |     def __init__(self, context, request): | ||||||
|  |         self.context = context | ||||||
|  |         # make sure the real view (delegate) updates our context when | ||||||
|  |         # talking about the context's parent: | ||||||
|  |         self.__parent__ = context | ||||||
|  |         self.request = request | ||||||
|  |         self.delegate = RolePermissionView() | ||||||
|  |         self.delegate.context = self | ||||||
|  |         self.delegate.request = request | ||||||
|  |         self.permissionId = request.get('permission_to_manage') or 'zope.View' | ||||||
|  | 
 | ||||||
|  |     def pagetip(self): | ||||||
|  |         return self.delegate.pagetip() | ||||||
|  | 
 | ||||||
|  |     def roles(self): | ||||||
|  |         return self.delegate.roles() | ||||||
|  | 
 | ||||||
|  |     def permissions(self): | ||||||
|  |         return self.delegate.permissions() | ||||||
|  | 
 | ||||||
|  |     def availableSettings(self, noacquire=False): | ||||||
|  |         return self.delegate.availableSettings(noacquire) | ||||||
|  | 
 | ||||||
|  |     def permissionRoles(self): | ||||||
|  |         return self.delegate.permissionRoles() | ||||||
|  | 
 | ||||||
|  |     def permissionForID(self, pid): | ||||||
|  |         return self.delegate.permissionForID(pid) | ||||||
|  | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def permission(self): | ||||||
|  |         return self.permissionForID(self.permissionId) | ||||||
|  | 
 | ||||||
|  |     def roleForID(self, rid): | ||||||
|  |          return self.delegate.roleForID(rid) | ||||||
|  | 
 | ||||||
|  |     def update(self, testing=None): | ||||||
|  |         return self.delegate.update(testing) | ||||||
|  | 
 | ||||||
|  |     def getAcquiredPermissionSetting(self, role, perm): | ||||||
|  |         for obj in getParents(self.context): | ||||||
|  |             rpm = IRolePermissionMap(obj, None) | ||||||
|  |             if rpm is not None: | ||||||
|  |                 setting = rpm.getSetting(perm, role) | ||||||
|  |                 setting = SettingAsBoolean[setting] | ||||||
|  |                 if setting is not None: | ||||||
|  |                     return setting and '+' or '-' | ||||||
|  |         return '' | ||||||
|  | 
 | ||||||
|  |     def listUsersForRole(self, rid): | ||||||
|  |         result = '' | ||||||
|  |         direct = IPrincipalRoleManager(self.context).getPrincipalsForRole(rid) | ||||||
|  |         if direct: | ||||||
|  |             result = '<strong>' + self.renderEntry(direct) + '</strong>' | ||||||
|  |         acquired = [] | ||||||
|  |         for obj in getParents(self.context): | ||||||
|  |             prm = IPrincipalRoleManager(obj, None) | ||||||
|  |             if prm is not None: | ||||||
|  |                 entry = prm.getPrincipalsForRole(rid) | ||||||
|  |                 if entry: | ||||||
|  |                     acquired.append(self.renderEntry(entry)) | ||||||
|  |         if acquired: | ||||||
|  |             if result: | ||||||
|  |                 result += '<br />' | ||||||
|  |             result += '<br />'.join(acquired) | ||||||
|  |         return result | ||||||
|  | 
 | ||||||
|  |     def renderEntry(self, entry): | ||||||
|  |         result = [] | ||||||
|  |         for e in entry: | ||||||
|  |             value = SettingAsBoolean[e[1]] | ||||||
|  |             value = (value is False and '-') or (value and '+') or '' | ||||||
|  |             result.append(value + e[0]) | ||||||
|  |         return ', '.join(result) | ||||||
|  | 
 | ||||||
|  |     def getPrincipalPermissions(self): | ||||||
|  |         result = '' | ||||||
|  |         ppm = IPrincipalPermissionMap(self.context) | ||||||
|  |         direct = ppm.getPrincipalsForPermission(self.permissionId) | ||||||
|  |         if direct: | ||||||
|  |             result = '<strong>' + self.renderEntry(direct) + '</strong>' | ||||||
|  |         acquired = [] | ||||||
|  |         for obj in getParents(self.context): | ||||||
|  |             ppm = IPrincipalPermissionMap(obj, None) | ||||||
|  |             if ppm is not None: | ||||||
|  |                 entry = ppm.getPrincipalsForPermission(self.permissionId) | ||||||
|  |                 if entry: | ||||||
|  |                     acquired.append(self.renderEntry(entry)) | ||||||
|  |         if acquired: | ||||||
|  |             if result: | ||||||
|  |                 result += '<br />' | ||||||
|  |             result += '<br />'.join(acquired) | ||||||
|  |         return result | ||||||
|  | 
 | ||||||
|  |     def getPermissions(self): | ||||||
|  |         return sorted(name for name, perm in component.getUtilitiesFor(IPermission)) | ||||||
|  | 
 | ||||||
							
								
								
									
										96
									
								
								security/policy.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								security/policy.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,96 @@ | ||||||
|  | # | ||||||
|  | #  Copyright (c) 2008 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 | ||||||
|  | # | ||||||
|  | 
 | ||||||
|  | """ | ||||||
|  | A loops-specific security policy. Intended mainly to deal with checking | ||||||
|  | concept map parents in addition to containers for collecting principal roles. | ||||||
|  | 
 | ||||||
|  | $Id$ | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | from zope.app.securitypolicy.interfaces import IPrincipalRoleMap | ||||||
|  | from zope.app.securitypolicy.zopepolicy import ZopeSecurityPolicy | ||||||
|  | from zope.app.securitypolicy.zopepolicy import SettingAsBoolean, globalRolesForPrincipal | ||||||
|  | from zope import component | ||||||
|  | from zope.component import adapts | ||||||
|  | from zope.cachedescriptors.property import Lazy | ||||||
|  | from zope.interface import classProvides | ||||||
|  | from zope.security.interfaces import ISecurityPolicy | ||||||
|  | from zope.security.proxy import removeSecurityProxy | ||||||
|  | 
 | ||||||
|  | from loops.interfaces import IConcept, IResource | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class LoopsSecurityPolicy(ZopeSecurityPolicy): | ||||||
|  | 
 | ||||||
|  |     classProvides(ISecurityPolicy) | ||||||
|  | 
 | ||||||
|  |     def cached_principal_roles(self, obj, principal, checked=None): | ||||||
|  |         if checked is None: | ||||||
|  |             checked = [] | ||||||
|  |         obj = removeSecurityProxy(obj) | ||||||
|  |         cache = self.cache(obj) | ||||||
|  |         try: | ||||||
|  |             cache_principal_roles = cache.principal_roles | ||||||
|  |         except AttributeError: | ||||||
|  |             cache_principal_roles = cache.principal_roles = {} | ||||||
|  |         try: | ||||||
|  |             return cache_principal_roles[principal] | ||||||
|  |         except KeyError: | ||||||
|  |             pass | ||||||
|  |         if obj is None: | ||||||
|  |             roles = dict([(role, SettingAsBoolean[setting]) | ||||||
|  |                             for (role, setting) | ||||||
|  |                                     in globalRolesForPrincipal(principal)]) | ||||||
|  |             roles['zope.Anonymous'] = True # Everybody has Anonymous | ||||||
|  |             cache_principal_roles[principal] = roles | ||||||
|  |             return roles | ||||||
|  |         roles = {} | ||||||
|  |         for p in self.getParents(obj, checked): | ||||||
|  |             # TODO: care for correct combination if there is more than | ||||||
|  |             # one parent | ||||||
|  |             roles.update(self.cached_principal_roles(p, principal)) | ||||||
|  |         prinrole = IPrincipalRoleMap(obj, None) | ||||||
|  |         if prinrole: | ||||||
|  |             roles = roles.copy() | ||||||
|  |             for role, setting in prinrole.getRolesForPrincipal(principal): | ||||||
|  |                 roles[role] = SettingAsBoolean[setting] | ||||||
|  |         cache_principal_roles[principal] = roles | ||||||
|  |         #print '***', roles | ||||||
|  |         return roles | ||||||
|  | 
 | ||||||
|  |     def getParents(self, obj, checked): | ||||||
|  |         if obj in checked:      # cycle - leave the concept map | ||||||
|  |             return [getattr(obj, '__parent__', None)] | ||||||
|  |         # keep concept parents in cache | ||||||
|  |         cache = self.cache(obj) | ||||||
|  |         try: | ||||||
|  |             parents = cache.parents | ||||||
|  |         except AttributeError: | ||||||
|  |             parents = [] | ||||||
|  |             if IConcept.providedBy(obj): | ||||||
|  |                 parents = [p for p in obj.getParents(noSecurityCheck=True) | ||||||
|  |                              if p != obj] | ||||||
|  |             elif IResource.providedBy(obj): | ||||||
|  |                 parents = [p for p in obj.getConcepts(noSecurityCheck=True) | ||||||
|  |                              if p != obj] | ||||||
|  |             cache.parents = parents | ||||||
|  |         if not parents: | ||||||
|  |             parents = [getattr(obj, '__parent__', None)] | ||||||
|  |         checked.append(obj) | ||||||
|  |         return parents | ||||||
							
								
								
									
										52
									
								
								security/setter.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								security/setter.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,52 @@ | ||||||
|  | # | ||||||
|  | #  Copyright (c) 2008 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 | ||||||
|  | # | ||||||
|  | 
 | ||||||
|  | """ | ||||||
|  | Base classes for security setters, i.e. adapters that provide standardized | ||||||
|  | methods for setting role permissions and other security-related stuff. | ||||||
|  | 
 | ||||||
|  | $Id$ | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | from zope import component | ||||||
|  | from zope.component import adapts | ||||||
|  | from zope.cachedescriptors.property import Lazy | ||||||
|  | from zope.interface import implements, Interface | ||||||
|  | 
 | ||||||
|  | from loops.security.interfaces import ISecuritySetter | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class BaseSecuritySetter(object): | ||||||
|  | 
 | ||||||
|  |     implements(ISecuritySetter) | ||||||
|  |     adapts(Interface) | ||||||
|  | 
 | ||||||
|  |     def __init__(self, context): | ||||||
|  |         self.context = context | ||||||
|  | 
 | ||||||
|  |     def setDefaultRolePermissions(self): | ||||||
|  |         pass | ||||||
|  | 
 | ||||||
|  |     def setDefaultPrincipalRoles(self): | ||||||
|  |         pass | ||||||
|  | 
 | ||||||
|  |     def setAcquiredRolePermissions(self, relation, revert=False): | ||||||
|  |         pass | ||||||
|  | 
 | ||||||
|  |     def setAcquiredPrincipalRoles(self, relation, revert=False): | ||||||
|  |         pass | ||||||
							
								
								
									
										104
									
								
								setup.py
									
										
									
									
									
								
							
							
						
						
									
										104
									
								
								setup.py
									
										
									
									
									
								
							|  | @ -1,5 +1,5 @@ | ||||||
| # | # | ||||||
| #  Copyright (c) 2006 Helmut Merz helmutm@cy55.de | #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||||
| # | # | ||||||
| #  This program is free software; you can redistribute it and/or modify | #  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 | #  it under the terms of the GNU General Public License as published by | ||||||
|  | @ -25,13 +25,16 @@ $Id$ | ||||||
| from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent | from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent | ||||||
| from zope.event import notify | from zope.event import notify | ||||||
| from zope import component | from zope import component | ||||||
|  | from zope.cachedescriptors.property import Lazy | ||||||
| from zope.component import adapts | from zope.component import adapts | ||||||
| from zope.interface import implements, Interface | from zope.interface import implements, Interface | ||||||
|  | from zope.traversing.api import getName | ||||||
| 
 | 
 | ||||||
| from cybertools.typology.interfaces import IType | from cybertools.typology.interfaces import IType | ||||||
|  | from loops.common import adapted | ||||||
|  | from loops.concept import ConceptManager, Concept | ||||||
| from loops.interfaces import ILoops, ITypeConcept | from loops.interfaces import ILoops, ITypeConcept | ||||||
| from loops.interfaces import IFile, IImage, ITextDocument, INote | from loops.interfaces import IFile, IImage, ITextDocument, INote | ||||||
| from loops.concept import ConceptManager, Concept |  | ||||||
| from loops.query import IQueryConcept | from loops.query import IQueryConcept | ||||||
| from loops.resource import ResourceManager, Resource | from loops.resource import ResourceManager, Resource | ||||||
| from loops.view import ViewManager, Node | from loops.view import ViewManager, Node | ||||||
|  | @ -79,7 +82,6 @@ class SetupManager(object): | ||||||
|         domain = self.addObject(conceptManager, Concept, 'domain', title=u'Domain') |         domain = self.addObject(conceptManager, Concept, 'domain', title=u'Domain') | ||||||
|         query = self.addObject(conceptManager, Concept, 'query', title=u'Query') |         query = self.addObject(conceptManager, Concept, 'query', title=u'Query') | ||||||
|         file = self.addObject(conceptManager, Concept, 'file', title=u'File') |         file = self.addObject(conceptManager, Concept, 'file', title=u'File') | ||||||
|         #image = self.addObject(conceptManager, Concept, 'image', title=u'Image') |  | ||||||
|         textdocument = self.addObject(conceptManager, Concept, |         textdocument = self.addObject(conceptManager, Concept, | ||||||
|                                       'textdocument', title=u'Text') |                                       'textdocument', title=u'Text') | ||||||
|         note = self.addObject(conceptManager, Concept, 'note', title=u'Note') |         note = self.addObject(conceptManager, Concept, 'note', title=u'Note') | ||||||
|  | @ -89,13 +91,105 @@ class SetupManager(object): | ||||||
|         ITypeConcept(typeConcept).typeInterface = ITypeConcept |         ITypeConcept(typeConcept).typeInterface = ITypeConcept | ||||||
|         ITypeConcept(query).typeInterface = IQueryConcept |         ITypeConcept(query).typeInterface = IQueryConcept | ||||||
|         ITypeConcept(file).typeInterface = IFile |         ITypeConcept(file).typeInterface = IFile | ||||||
|         #ITypeConcept(image).typeInterface = IImage |  | ||||||
|         ITypeConcept(textdocument).typeInterface = ITextDocument |         ITypeConcept(textdocument).typeInterface = ITextDocument | ||||||
|         ITypeConcept(note).typeInterface = INote |         ITypeConcept(note).typeInterface = INote | ||||||
|         ITypeConcept(note).viewName = 'note.html' # leads to error in DocTest |         ITypeConcept(note).viewName = 'note.html' | ||||||
|         hasType.conceptType = predicate |         hasType.conceptType = predicate | ||||||
|         standard.conceptType = predicate |         standard.conceptType = predicate | ||||||
| 
 | 
 | ||||||
|  |     # standard properties and methods | ||||||
|  | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def concepts(self): | ||||||
|  |         return self.context.getConceptManager() | ||||||
|  | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def resources(self): | ||||||
|  |         return self.context.getResourceManager() | ||||||
|  | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def views(self): | ||||||
|  |         return self.context.getViewManager() | ||||||
|  | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def typeConcept(self): | ||||||
|  |         return self.concepts.getTypeConcept() | ||||||
|  | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def predicateType(self): | ||||||
|  |         return self.concepts.getPredicateType() | ||||||
|  | 
 | ||||||
|  |     def addType(self, name, title, typeInterface=None, **kw): | ||||||
|  |         c = self.addConcept(name, title, self.typeConcept, | ||||||
|  |                             typeInterface=typeInterface, **kw) | ||||||
|  |         return c | ||||||
|  | 
 | ||||||
|  |     def addPredicate(self, name, title, **kw): | ||||||
|  |         c = self.addConcept(name, title, self.predicateType, **kw) | ||||||
|  |         return c | ||||||
|  | 
 | ||||||
|  |     def addConcept(self, name, title, conceptType, description=u'', | ||||||
|  |                    parentName=None, **kw): | ||||||
|  |         if name in self.concepts: | ||||||
|  |             self.log("Concept '%s' ('%s') already exists." % (name, title)) | ||||||
|  |             c = self.concepts[name] | ||||||
|  |             if c.conceptType != conceptType: | ||||||
|  |                 self.log("Wrong concept type for '%s': '%s' instead of '%s'." % | ||||||
|  |                          (name, getName(c.conceptType), getName(conceptType))) | ||||||
|  |         else: | ||||||
|  |             c = addAndConfigureObject(self.concepts, Concept, name, title=title, | ||||||
|  |                               description=description, | ||||||
|  |                               conceptType=conceptType, **kw) | ||||||
|  |             self.log("Concept '%s' ('%s') created." % (name, title)) | ||||||
|  |         if parentName is not None: | ||||||
|  |             self.assignChild(parentName, name) | ||||||
|  |         return c | ||||||
|  | 
 | ||||||
|  |     def setConceptAttribute(self, concept, attr, value): | ||||||
|  |         setattr(adapted(concept), attr, value) | ||||||
|  |         self.log("Setting Attribute '%s' of '%s' to '%s'" % | ||||||
|  |                  (attr, getName(concept), repr(value))) | ||||||
|  | 
 | ||||||
|  |     def assignChild(self, conceptName, childName, predicate=None): | ||||||
|  |         if predicate is None: | ||||||
|  |             predicate = self.concepts.getDefaultPredicate() | ||||||
|  |         if isinstance(predicate, str): | ||||||
|  |             predicate = self.concepts[predicate] | ||||||
|  |         concept = self.concepts[conceptName] | ||||||
|  |         child = self.concepts[childName] | ||||||
|  |         if child in concept.getChildren([predicate]): | ||||||
|  |             self.log("Concept '%s' is already a child of '%s with predicate '%s'.'" % | ||||||
|  |                      (childName, conceptName, getName(predicate))) | ||||||
|  |         else: | ||||||
|  |             concept.assignChild(child, predicate) | ||||||
|  | 
 | ||||||
|  |     def addNode(self, name, title, container=None, nodeType='page', | ||||||
|  |                 description=u'', body=u'', targetName=None, **kw): | ||||||
|  |         if container is None: | ||||||
|  |             container = self.views | ||||||
|  |             nodeType = 'menu' | ||||||
|  |         if name in container: | ||||||
|  |             self.log("Node '%s' ('%s') already exists in '%s'." % | ||||||
|  |                      (name, title, getName(container))) | ||||||
|  |             n = container[name] | ||||||
|  |             if n.nodeType != nodeType: | ||||||
|  |                 self.log("Wrong node type for '%s': '%s' instead of '%s'." % | ||||||
|  |                          (name, n.nodeType, nodeType)) | ||||||
|  |         else: | ||||||
|  |             n = addAndConfigureObject(container, Node, name, title=title, | ||||||
|  |                               description=description, body=body, | ||||||
|  |                               nodeType=nodeType, **kw) | ||||||
|  |             self.log("Node '%s' ('%s') created." % (name, title)) | ||||||
|  |         if targetName is not None: | ||||||
|  |             if targetName in self.concepts: | ||||||
|  |                 n.target = self.concepts[targetName] | ||||||
|  |         return n | ||||||
|  | 
 | ||||||
|  |     def log(self, message): | ||||||
|  |         if isinstance(message, unicode): | ||||||
|  |             message = message.encode('UTF-8') | ||||||
|  |         print >> self.logger, message | ||||||
|  | 
 | ||||||
|     def addObject(self, container, class_, name, **kw): |     def addObject(self, container, class_, name, **kw): | ||||||
|         return addObject(container, class_, name, **kw) |         return addObject(container, class_, name, **kw) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										60
									
								
								tests/auth.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								tests/auth.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,60 @@ | ||||||
|  | # | ||||||
|  | #  Copyright (c) 2008 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 | ||||||
|  | # | ||||||
|  | 
 | ||||||
|  | """ | ||||||
|  | Elements helpful for testing with authenticated users. | ||||||
|  | 
 | ||||||
|  | $Id$ | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | from zope.cachedescriptors.property import Lazy | ||||||
|  | from zope.publisher.browser import TestRequest as BaseTestRequest | ||||||
|  | from zope.security.management import getInteraction, newInteraction, endInteraction | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Participation(object): | ||||||
|  |     """ Dummy Participation class for testing. | ||||||
|  |     """ | ||||||
|  | 
 | ||||||
|  |     interaction = None | ||||||
|  | 
 | ||||||
|  |     def __init__(self, principal): | ||||||
|  |         self.principal = principal | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class TestRequest(BaseTestRequest): | ||||||
|  | 
 | ||||||
|  |     basePrincipal = BaseTestRequest.principal | ||||||
|  | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def principal(self): | ||||||
|  |         interaction = getInteraction() | ||||||
|  |         if interaction is not None: | ||||||
|  |             parts = interaction.participations | ||||||
|  |             if parts: | ||||||
|  |                 prin = parts[0].principal | ||||||
|  |                 if prin is not None: | ||||||
|  |                     return prin | ||||||
|  |         return self.basePrincipal | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def login(principal): | ||||||
|  |     endInteraction() | ||||||
|  |     newInteraction(Participation(principal)) | ||||||
|  | 
 | ||||||
|  | @ -6,21 +6,31 @@ $Id$ | ||||||
| 
 | 
 | ||||||
| from zope import component | from zope import component | ||||||
| from zope.annotation.attribute import AttributeAnnotations | from zope.annotation.attribute import AttributeAnnotations | ||||||
|  | from zope.annotation.interfaces import IAnnotatable | ||||||
| from zope.app.catalog.catalog import Catalog | from zope.app.catalog.catalog import Catalog | ||||||
| from zope.app.catalog.interfaces import ICatalog | from zope.app.catalog.interfaces import ICatalog | ||||||
| from zope.app.catalog.field import FieldIndex | from zope.app.catalog.field import FieldIndex | ||||||
| from zope.app.catalog.text import TextIndex | from zope.app.catalog.text import TextIndex | ||||||
| from zope.app.container.interfaces import IObjectRemovedEvent | from zope.app.container.interfaces import IObjectRemovedEvent | ||||||
|  | from zope.app.principalannotation import PrincipalAnnotationUtility | ||||||
|  | from zope.app.principalannotation.interfaces import IPrincipalAnnotationUtility | ||||||
| from zope.app.security.principalregistry import principalRegistry | from zope.app.security.principalregistry import principalRegistry | ||||||
| from zope.app.security.interfaces import IAuthentication | from zope.app.security.interfaces import IAuthentication | ||||||
|  | from zope.app.securitypolicy.zopepolicy import ZopeSecurityPolicy | ||||||
|  | from zope.app.securitypolicy.principalrole import AnnotationPrincipalRoleManager | ||||||
|  | from zope.app.securitypolicy.rolepermission import AnnotationRolePermissionManager | ||||||
| from zope.app.session.interfaces import IClientIdManager, ISessionDataContainer | from zope.app.session.interfaces import IClientIdManager, ISessionDataContainer | ||||||
| from zope.app.session import session | from zope.app.session import session | ||||||
| from zope.dublincore.annotatableadapter import ZDCAnnotatableAdapter | from zope.dublincore.annotatableadapter import ZDCAnnotatableAdapter | ||||||
| from zope.dublincore.interfaces import IZopeDublinCore | from zope.dublincore.interfaces import IZopeDublinCore | ||||||
| from zope.interface import implements | from zope.interface import Interface, implements | ||||||
|  | from zope.publisher.interfaces.browser import IBrowserRequest, IBrowserView | ||||||
|  | from zope.security.checker import Checker, defineChecker | ||||||
| 
 | 
 | ||||||
|  | from cybertools.browser.controller import Controller | ||||||
| from cybertools.composer.schema.factory import SchemaFactory | from cybertools.composer.schema.factory import SchemaFactory | ||||||
| from cybertools.composer.schema.field import FieldInstance, NumberFieldInstance | from cybertools.composer.schema.field import FieldInstance, NumberFieldInstance | ||||||
|  | from cybertools.composer.schema.field import DateFieldInstance, BooleanFieldInstance | ||||||
| from cybertools.composer.schema.instance import Instance, Editor | from cybertools.composer.schema.instance import Instance, Editor | ||||||
| from cybertools.relation.tests import IntIdsStub | from cybertools.relation.tests import IntIdsStub | ||||||
| from cybertools.relation.registry import RelationRegistry | from cybertools.relation.registry import RelationRegistry | ||||||
|  | @ -34,14 +44,21 @@ from loops.base import Loops | ||||||
| from loops import util | from loops import util | ||||||
| from loops.browser.node import ViewPropertiesConfigurator | from loops.browser.node import ViewPropertiesConfigurator | ||||||
| from loops.common import NameChooser | from loops.common import NameChooser | ||||||
| from loops.interfaces import ILoopsObject, IIndexAttributes |  | ||||||
| from loops.interfaces import IDocument, IFile, ITextDocument |  | ||||||
| from loops.concept import Concept | from loops.concept import Concept | ||||||
| from loops.concept import IndexAttributes as ConceptIndexAttributes | from loops.concept import IndexAttributes as ConceptIndexAttributes | ||||||
|  | from loops.interfaces import ILoopsObject, IIndexAttributes | ||||||
|  | from loops.interfaces import IDocument, IFile, ITextDocument | ||||||
|  | from loops.organize.memberinfo import MemberInfoProvider | ||||||
|  | from loops.query import QueryConcept | ||||||
| from loops.query import QueryConcept | from loops.query import QueryConcept | ||||||
| from loops.resource import Resource, FileAdapter, TextDocumentAdapter | from loops.resource import Resource, FileAdapter, TextDocumentAdapter | ||||||
|  | from loops.resource import Document, MediaAsset | ||||||
| from loops.resource import IndexAttributes as ResourceIndexAttributes | from loops.resource import IndexAttributes as ResourceIndexAttributes | ||||||
| from loops.schema import ResourceSchemaFactory, FileSchemaFactory, NoteSchemaFactory | from loops.schema import ResourceSchemaFactory, FileSchemaFactory, NoteSchemaFactory | ||||||
|  | from loops.security.common import grantAcquiredSecurity, revokeAcquiredSecurity | ||||||
|  | from zope.security.management import setSecurityPolicy | ||||||
|  | from loops.security.policy import LoopsSecurityPolicy | ||||||
|  | from loops.security.setter import BaseSecuritySetter | ||||||
| from loops.setup import SetupManager, addObject | from loops.setup import SetupManager, addObject | ||||||
| from loops.type import LoopsType, ConceptType, ResourceType, TypeConcept | from loops.type import LoopsType, ConceptType, ResourceType, TypeConcept | ||||||
| from loops.view import NodeAdapter | from loops.view import NodeAdapter | ||||||
|  | @ -62,14 +79,25 @@ class TestSite(object): | ||||||
|     def baseSetup(self): |     def baseSetup(self): | ||||||
|         site = self.site |         site = self.site | ||||||
| 
 | 
 | ||||||
|  |         #oldPolicy = setSecurityPolicy(ZopeSecurityPolicy) | ||||||
|  |         oldPolicy = setSecurityPolicy(LoopsSecurityPolicy) | ||||||
|  |         checker = Checker(dict(title='zope.View', data='zope.View'), | ||||||
|  |                           dict(title='zope.ManageContent')) | ||||||
|  |         defineChecker(Concept, checker) | ||||||
|  |         defineChecker(Resource, checker) | ||||||
|  |         defineChecker(Document, checker) | ||||||
|  | 
 | ||||||
|         component.provideUtility(IntIdsStub()) |         component.provideUtility(IntIdsStub()) | ||||||
|         relations = RelationRegistry() |         relations = RelationRegistry() | ||||||
|         relations.setupIndexes() |         relations.setupIndexes() | ||||||
|         component.provideUtility(relations, IRelationRegistry) |         component.provideUtility(relations, IRelationRegistry) | ||||||
|         component.provideAdapter(IndexableRelationAdapter) |  | ||||||
| 
 | 
 | ||||||
|  |         component.provideUtility(PrincipalAnnotationUtility(), IPrincipalAnnotationUtility) | ||||||
|  |         component.provideAdapter(IndexableRelationAdapter) | ||||||
|         component.provideAdapter(ZDCAnnotatableAdapter, (ILoopsObject,), IZopeDublinCore) |         component.provideAdapter(ZDCAnnotatableAdapter, (ILoopsObject,), IZopeDublinCore) | ||||||
|         component.provideAdapter(AttributeAnnotations, (ILoopsObject,)) |         component.provideAdapter(AttributeAnnotations, (ILoopsObject,)) | ||||||
|  |         component.provideAdapter(AnnotationPrincipalRoleManager, (ILoopsObject,)) | ||||||
|  |         component.provideAdapter(AnnotationRolePermissionManager, (ILoopsObject,)) | ||||||
|         component.provideUtility(principalRegistry, IAuthentication) |         component.provideUtility(principalRegistry, IAuthentication) | ||||||
|         component.provideAdapter(session.ClientId) |         component.provideAdapter(session.ClientId) | ||||||
|         component.provideAdapter(session.Session) |         component.provideAdapter(session.Session) | ||||||
|  | @ -86,16 +114,26 @@ class TestSite(object): | ||||||
|         component.provideAdapter(NodeAdapter) |         component.provideAdapter(NodeAdapter) | ||||||
|         component.provideAdapter(ViewPropertiesConfigurator) |         component.provideAdapter(ViewPropertiesConfigurator) | ||||||
|         component.provideAdapter(NameChooser) |         component.provideAdapter(NameChooser) | ||||||
|  |         component.provideHandler(grantAcquiredSecurity) | ||||||
|  |         component.provideHandler(revokeAcquiredSecurity) | ||||||
|  |         component.provideAdapter(BaseSecuritySetter) | ||||||
|  | 
 | ||||||
|         component.provideAdapter(Instance) |         component.provideAdapter(Instance) | ||||||
|         component.provideAdapter(Editor, name='editor') |         component.provideAdapter(Editor, name='editor') | ||||||
|         component.provideAdapter(FieldInstance) |         component.provideAdapter(FieldInstance) | ||||||
|         component.provideAdapter(NumberFieldInstance, name='number') |         component.provideAdapter(NumberFieldInstance, name='number') | ||||||
| 
 |         component.provideAdapter(DateFieldInstance, name='date') | ||||||
|  |         component.provideAdapter(BooleanFieldInstance, name='boolean') | ||||||
|         component.provideAdapter(SchemaFactory) |         component.provideAdapter(SchemaFactory) | ||||||
|         component.provideAdapter(ResourceSchemaFactory) |         component.provideAdapter(ResourceSchemaFactory) | ||||||
|         component.provideAdapter(FileSchemaFactory) |         component.provideAdapter(FileSchemaFactory) | ||||||
|         component.provideAdapter(NoteSchemaFactory) |         component.provideAdapter(NoteSchemaFactory) | ||||||
| 
 | 
 | ||||||
|  |         component.provideAdapter(Controller, (Interface, IBrowserRequest), | ||||||
|  |                             IBrowserView, name='controller') | ||||||
|  |         component.provideAdapter(MemberInfoProvider, | ||||||
|  |                                  (ILoopsObject, IBrowserRequest)) | ||||||
|  | 
 | ||||||
|         component.getSiteManager().registerHandler(invalidateRelations, |         component.getSiteManager().registerHandler(invalidateRelations, | ||||||
|                             (ILoopsObject, IObjectRemovedEvent)) |                             (ILoopsObject, IObjectRemovedEvent)) | ||||||
|         component.getSiteManager().registerHandler(removeRelation, |         component.getSiteManager().registerHandler(removeRelation, | ||||||
|  |  | ||||||
							
								
								
									
										20
									
								
								type.py
									
										
									
									
									
								
							
							
						
						
									
										20
									
								
								type.py
									
										
									
									
									
								
							|  | @ -88,7 +88,6 @@ class LoopsType(BaseType): | ||||||
|         addQualifiers = self.optionsDict.get('qualifier') |         addQualifiers = self.optionsDict.get('qualifier') | ||||||
|         if addQualifiers: |         if addQualifiers: | ||||||
|             qu.extend(addQualifiers.split(',')) |             qu.extend(addQualifiers.split(',')) | ||||||
|         # how to set a type to 'hidden'? |  | ||||||
|         return tuple(qu) |         return tuple(qu) | ||||||
| 
 | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|  | @ -112,14 +111,7 @@ class LoopsType(BaseType): | ||||||
| 
 | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def optionsDict(self): |     def optionsDict(self): | ||||||
|         result = {'default': []} |         return getOptionsDict(self.options) | ||||||
|         for opt in self.options: |  | ||||||
|             if ':' in opt: |  | ||||||
|                 key, value = opt.split(':', 1) |  | ||||||
|                 result[key] = value |  | ||||||
|             else: |  | ||||||
|                 result['default'].append(opt) |  | ||||||
|         return result |  | ||||||
| 
 | 
 | ||||||
|     # general infos |     # general infos | ||||||
| 
 | 
 | ||||||
|  | @ -291,3 +283,13 @@ class TypeInterfaceSourceList(object): | ||||||
|     def __len__(self): |     def __len__(self): | ||||||
|         return len(self.typeInterfaces) |         return len(self.typeInterfaces) | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | def getOptionsDict(options): | ||||||
|  |     result = {'default': []} | ||||||
|  |     for opt in options: | ||||||
|  |         if ':' in opt: | ||||||
|  |             key, value = opt.split(':', 1) | ||||||
|  |             result[key] = value | ||||||
|  |         else: | ||||||
|  |             result['default'].append(opt) | ||||||
|  |     return result | ||||||
|  |  | ||||||
							
								
								
									
										12
									
								
								util.py
									
										
									
									
									
								
							
							
						
						
									
										12
									
								
								util.py
									
										
									
									
									
								
							|  | @ -27,12 +27,22 @@ from zope.app.intid.interfaces import IIntIds | ||||||
| from zope.interface import directlyProvides, directlyProvidedBy | from zope.interface import directlyProvides, directlyProvidedBy | ||||||
| from zope.i18nmessageid import MessageFactory | from zope.i18nmessageid import MessageFactory | ||||||
| from zope.schema import vocabulary | from zope.schema import vocabulary | ||||||
| #from view import TargetRelation | 
 | ||||||
|  | from loops.browser.util import html_quote | ||||||
| 
 | 
 | ||||||
| _ = MessageFactory('loops') | _ = MessageFactory('loops') | ||||||
| #_ = MessageFactory('zope')  # it's easier not use a special i18n domain? | #_ = MessageFactory('zope')  # it's easier not use a special i18n domain? | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | renderingFactories = { | ||||||
|  |     'text/plain': 'zope.source.plaintext', | ||||||
|  |     'text/stx': 'zope.source.stx', | ||||||
|  |     'text/structured': 'zope.source.stx', | ||||||
|  |     'text/rest': 'zope.source.rest', | ||||||
|  |     'text/restructured': 'zope.source.rest', | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class KeywordVocabulary(vocabulary.SimpleVocabulary): | class KeywordVocabulary(vocabulary.SimpleVocabulary): | ||||||
| 
 | 
 | ||||||
|     def __init__(self, items, *interfaces): |     def __init__(self, items, *interfaces): | ||||||
|  |  | ||||||
|  | @ -48,6 +48,7 @@ class VersionableResource(object): | ||||||
| 
 | 
 | ||||||
|     def __init__(self, context): |     def __init__(self, context): | ||||||
|         self.context = context |         self.context = context | ||||||
|  |         self.__parent__ = context | ||||||
| 
 | 
 | ||||||
|     def getVersioningAttribute(self, attr, default): |     def getVersioningAttribute(self, attr, default): | ||||||
|         attrName = attrPattern % attr |         attrName = attrPattern % attr | ||||||
|  |  | ||||||
							
								
								
									
										5
									
								
								view.py
									
										
									
									
									
								
							
							
						
						
									
										5
									
								
								view.py
									
										
									
									
									
								
							|  | @ -1,5 +1,5 @@ | ||||||
| # | # | ||||||
| #  Copyright (c) 2005 Helmut Merz helmutm@cy55.de | #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||||
| # | # | ||||||
| #  This program is free software; you can redistribute it and/or modify | #  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 | #  it under the terms of the GNU General Public License as published by | ||||||
|  | @ -27,13 +27,12 @@ from zope.app import zapi | ||||||
| from zope.app.container.btree import BTreeContainer | from zope.app.container.btree import BTreeContainer | ||||||
| from zope.app.container.contained import Contained | from zope.app.container.contained import Contained | ||||||
| from zope.app.container.ordered import OrderedContainer | from zope.app.container.ordered import OrderedContainer | ||||||
| from zope.app.container.traversal import ContainerTraverser, ItemTraverser |  | ||||||
| from zope.app.container.traversal import ContainerTraversable |  | ||||||
| from zope.app.intid.interfaces import IIntIds | from zope.app.intid.interfaces import IIntIds | ||||||
| from zope.cachedescriptors.property import Lazy, readproperty | from zope.cachedescriptors.property import Lazy, readproperty | ||||||
| from zope.component import adapts | from zope.component import adapts | ||||||
| from zope.interface import implements | from zope.interface import implements | ||||||
| from zope.interface import alsoProvides, directlyProvides, directlyProvidedBy | from zope.interface import alsoProvides, directlyProvides, directlyProvidedBy | ||||||
|  | from zope.publisher.browser import applySkin | ||||||
| from zope.security.proxy import removeSecurityProxy | from zope.security.proxy import removeSecurityProxy | ||||||
| from persistent import Persistent | from persistent import Persistent | ||||||
| from cybertools.relation import DyadicRelation | from cybertools.relation import DyadicRelation | ||||||
|  |  | ||||||
|  | @ -8,57 +8,39 @@ Let's do some basic set up | ||||||
| 
 | 
 | ||||||
|   >>> from zope.app.testing.setup import placefulSetUp, placefulTearDown |   >>> from zope.app.testing.setup import placefulSetUp, placefulTearDown | ||||||
|   >>> site = placefulSetUp(True) |   >>> site = placefulSetUp(True) | ||||||
| 
 |  | ||||||
|   >>> from zope import component, interface |   >>> from zope import component, interface | ||||||
|   >>> from zope.publisher.browser import TestRequest |   >>> from zope.publisher.browser import TestRequest | ||||||
|   >>> from loops.concept import Concept |   >>> from loops.concept import Concept | ||||||
|   >>> from loops.resource import Resource |   >>> from loops.resource import Resource | ||||||
|  |   >>> | ||||||
| 
 | 
 | ||||||
| and set up a simple loops site with a concept manager and some concepts | and set up a simple loops site with a concept manager and some concepts | ||||||
| (with all the type machinery, what in real life is done via standard | (with all the type machinery, what in real life is done via standard | ||||||
| ZCML setup): | ZCML setup): | ||||||
| 
 | 
 | ||||||
|   >>> from cybertools.relation.registry import DummyRelationRegistry |   >>> from loops.setup import addObject | ||||||
|   >>> component.provideUtility(DummyRelationRegistry()) |  | ||||||
|   >>> from cybertools.relation.tests import IntIdsStub |  | ||||||
|   >>> intIds = IntIdsStub() |  | ||||||
|   >>> component.provideUtility(intIds) |  | ||||||
| 
 |  | ||||||
|   >>> from loops.type import LoopsType, ConceptType, TypeConcept |  | ||||||
|   >>> component.provideAdapter(LoopsType) |  | ||||||
|   >>> component.provideAdapter(ConceptType) |  | ||||||
|   >>> component.provideAdapter(TypeConcept) |  | ||||||
|   >>> from loops.common import NameChooser |  | ||||||
|   >>> component.provideAdapter(NameChooser) |  | ||||||
| 
 |  | ||||||
|   >>> from loops.base import Loops |  | ||||||
|   >>> loopsRoot = site['loops'] = Loops() |  | ||||||
| 
 |  | ||||||
|   >>> from loops.setup import SetupManager |  | ||||||
|   >>> from loops.organize.setup import SetupManager as OrganizeSetupManager |   >>> from loops.organize.setup import SetupManager as OrganizeSetupManager | ||||||
|   >>> component.provideAdapter(OrganizeSetupManager, name='organize') |   >>> component.provideAdapter(OrganizeSetupManager, name='organize') | ||||||
|   >>> setup = SetupManager(loopsRoot) |   >>> from loops.knowledge.setup import SetupManager as KnowledgeSetupManager | ||||||
|   >>> concepts, resources, views = setup.setup() |   >>> component.provideAdapter(KnowledgeSetupManager, name='knowledge') | ||||||
|  |   >>> from loops.tests.setup import TestSite | ||||||
|  |   >>> t = TestSite(site) | ||||||
|  |   >>> concepts, resources, views = t.setup() | ||||||
|  | 
 | ||||||
|  |   >>> from loops import util | ||||||
|  |   >>> loopsRoot = site['loops'] | ||||||
|  |   >>> loopsId = util.getUidForObject(loopsRoot) | ||||||
| 
 | 
 | ||||||
| Let's look what setup has provided us with: | Let's look what setup has provided us with: | ||||||
| 
 | 
 | ||||||
|   >>> sorted(concepts) |   >>> len(concepts) | ||||||
|   [u'domain', u'file', u'hasType', u'note', u'ownedby', u'person', |   19 | ||||||
|    u'predicate', u'query', u'standard', u'textdocument', u'type'] |  | ||||||
| 
 | 
 | ||||||
| Now let's add a few more concepts: | Now let's add a few more concepts: | ||||||
| 
 | 
 | ||||||
|   >>> topic = concepts[u'topic'] = Concept(u'Topic') |   >>> topic = concepts[u'topic'] | ||||||
|   >>> intIds.register(topic) |   >>> zope = addObject(concepts, Concept, 'zope', title=u'Zope', conceptType=topic) | ||||||
|   11 |   >>> zope3 = addObject(concepts, Concept, 'zope3', title=u'Zope 3', conceptType=topic) | ||||||
|   >>> zope = concepts[u'zope'] = Concept(u'Zope') |  | ||||||
|   >>> zope.conceptType = topic |  | ||||||
|   >>> intIds.register(zope) |  | ||||||
|   12 |  | ||||||
|   >>> zope3 = concepts[u'zope3'] = Concept(u'Zope 3') |  | ||||||
|   >>> zope3.conceptType = topic |  | ||||||
|   >>> intIds.register(zope3) |  | ||||||
|   13 |  | ||||||
| 
 | 
 | ||||||
| Navigation typically starts at a start object, which by default ist the | Navigation typically starts at a start object, which by default ist the | ||||||
| domain concept (if present, otherwise the top-level type concept): | domain concept (if present, otherwise the top-level type concept): | ||||||
|  | @ -70,16 +52,16 @@ domain concept (if present, otherwise the top-level type concept): | ||||||
|   ['children', 'description', 'id', 'name', 'parents', 'resources', |   ['children', 'description', 'id', 'name', 'parents', 'resources', | ||||||
|    'title', 'type', 'viewName'] |    'title', 'type', 'viewName'] | ||||||
|   >>> startObj['id'], startObj['name'], startObj['title'], startObj['type'] |   >>> startObj['id'], startObj['name'], startObj['title'], startObj['type'] | ||||||
|   ('1', u'domain', u'Domain', '0') |   ('3', u'domain', u'Domain', '0') | ||||||
| 
 | 
 | ||||||
| There are a few standard objects we can retrieve directly: | There are a few standard objects we can retrieve directly: | ||||||
| 
 | 
 | ||||||
|   >>> defaultPred = xrf.getDefaultPredicate() |   >>> defaultPred = xrf.getDefaultPredicate() | ||||||
|   >>> defaultPred['id'], defaultPred['name'] |   >>> defaultPred['id'], defaultPred['name'] | ||||||
|   ('8', u'standard') |   ('16', u'standard') | ||||||
|   >>> typePred = xrf.getTypePredicate() |   >>> typePred = xrf.getTypePredicate() | ||||||
|   >>> typePred['id'], typePred['name'] |   >>> typePred['id'], typePred['name'] | ||||||
|   ('7', u'hasType') |   ('1', u'hasType') | ||||||
|   >>> typeConcept = xrf.getTypeConcept() |   >>> typeConcept = xrf.getTypeConcept() | ||||||
|   >>> typeConcept['id'], typeConcept['name'] |   >>> typeConcept['id'], typeConcept['name'] | ||||||
|   ('0', u'type') |   ('0', u'type') | ||||||
|  | @ -89,19 +71,19 @@ note that the 'hasType' predicate is not shown as it should not be | ||||||
| applied in an explicit assignment. | applied in an explicit assignment. | ||||||
| 
 | 
 | ||||||
|   >>> sorted(t['name'] for t in xrf.getConceptTypes()) |   >>> sorted(t['name'] for t in xrf.getConceptTypes()) | ||||||
|   [u'domain', u'file', u'note', u'person', u'predicate', u'query', |   [u'customer', u'domain', u'file', u'note', u'person', u'predicate', u'query', | ||||||
|    u'textdocument', u'type'] |    u'task', u'textdocument', u'topic', u'type'] | ||||||
|   >>> sorted(t['name'] for t in xrf.getPredicates()) |   >>> sorted(t['name'] for t in xrf.getPredicates()) | ||||||
|   [u'ownedby', u'standard'] |   [u'depends', u'knows', u'ownedby', u'provides', u'requires', u'standard'] | ||||||
| 
 | 
 | ||||||
| We can also retrieve a certain object by its id or its name: | We can also retrieve a certain object by its id or its name: | ||||||
| 
 | 
 | ||||||
|   >>> obj2 = xrf.getObjectById('2') |   >>> obj2 = xrf.getObjectById('5') | ||||||
|   >>> obj2['id'], obj2['name'] |   >>> obj2['id'], obj2['name'] | ||||||
|   ('2', u'query') |   ('5', u'query') | ||||||
|   >>> textdoc = xrf.getObjectByName(u'textdocument') |   >>> textdoc = xrf.getObjectByName(u'textdocument') | ||||||
|   >>> textdoc['id'], textdoc['name'] |   >>> textdoc['id'], textdoc['name'] | ||||||
|   ('5', u'textdocument') |   ('11', u'textdocument') | ||||||
| 
 | 
 | ||||||
| All methods that retrieve one object also returns its children and parents: | All methods that retrieve one object also returns its children and parents: | ||||||
| 
 | 
 | ||||||
|  | @ -111,8 +93,8 @@ All methods that retrieve one object also returns its children and parents: | ||||||
|   >>> ch[0]['name'] |   >>> ch[0]['name'] | ||||||
|   u'hasType' |   u'hasType' | ||||||
|   >>> sorted(c['name'] for c in ch[0]['objects']) |   >>> sorted(c['name'] for c in ch[0]['objects']) | ||||||
|   [u'domain', u'file', u'note', u'person', u'predicate', u'query', |   [u'customer', u'domain', u'file', u'note', u'person', u'predicate', | ||||||
|    u'textdocument', u'type'] |    u'query', u'task', u'textdocument', u'topic', u'type'] | ||||||
| 
 | 
 | ||||||
|   >>> pa = defaultPred['parents'] |   >>> pa = defaultPred['parents'] | ||||||
|   >>> len(pa) |   >>> len(pa) | ||||||
|  | @ -130,8 +112,8 @@ We can also retrieve children and parents explicitely: | ||||||
|   >>> ch[0]['name'] |   >>> ch[0]['name'] | ||||||
|   u'hasType' |   u'hasType' | ||||||
|   >>> sorted(c['name'] for c in ch[0]['objects']) |   >>> sorted(c['name'] for c in ch[0]['objects']) | ||||||
|   [u'domain', u'file', u'note', u'person', u'predicate', u'query', |   [u'customer', u'domain', u'file', u'note', u'person', u'predicate', | ||||||
|    u'textdocument', u'type'] |    u'query', u'task', u'textdocument', u'topic', u'type'] | ||||||
| 
 | 
 | ||||||
|   >>> pa = xrf.getParents('7') |   >>> pa = xrf.getParents('7') | ||||||
|   >>> len(pa) |   >>> len(pa) | ||||||
|  | @ -139,7 +121,7 @@ We can also retrieve children and parents explicitely: | ||||||
|   >>> pa[0]['name'] |   >>> pa[0]['name'] | ||||||
|   u'hasType' |   u'hasType' | ||||||
|   >>> sorted(p['name'] for p in pa[0]['objects']) |   >>> sorted(p['name'] for p in pa[0]['objects']) | ||||||
|   [u'predicate'] |   [u'type'] | ||||||
| 
 | 
 | ||||||
| Resources | Resources | ||||||
| --------- | --------- | ||||||
|  | @ -184,19 +166,23 @@ Updating the concept map | ||||||
| 
 | 
 | ||||||
|   >>> topicId = xrf.getObjectByName('topic')['id'] |   >>> topicId = xrf.getObjectByName('topic')['id'] | ||||||
|   >>> xrf.createConcept(topicId, u'zope2', u'Zope 2') |   >>> xrf.createConcept(topicId, u'zope2', u'Zope 2') | ||||||
|   {'description': u'', 'title': u'Zope 2', 'type': '11', 'id': '17', |   {'description': u'', 'title': u'Zope 2', 'type': '22', 'id': '56', | ||||||
|    'name': u'zope2'} |    'name': u'zope2'} | ||||||
| 
 | 
 | ||||||
| The name of the concept is checked by a name chooser; if the corresponding | The name of the concept is checked by a name chooser; if the corresponding | ||||||
| parameter is empty, the name will be generated from the title. | parameter is empty, the name will be generated from the title. | ||||||
| 
 | 
 | ||||||
|   >>> xrf.createConcept(topicId, u'', u'Python') |   >>> xrf.createConcept(topicId, u'', u'Python') | ||||||
|   {'description': u'', 'title': u'Python', 'type': '11', 'id': '18', |   {'description': u'', 'title': u'Python', 'type': '22', 'id': '58', | ||||||
|    'name': u'python'} |    'name': u'python'} | ||||||
| 
 | 
 | ||||||
| Changing the attributes of a concept | Changing the attributes of a concept | ||||||
| ------------------------------------ | ------------------------------------ | ||||||
| 
 | 
 | ||||||
|  |   >>> from loops.knowledge.knowledge import Person | ||||||
|  |   >>> from loops.knowledge.interfaces import IPerson | ||||||
|  |   >>> component.provideAdapter(Person, provides=IPerson) | ||||||
|  | 
 | ||||||
|   >>> xrf.editConcept(john['id'], 'firstName', u'John') |   >>> xrf.editConcept(john['id'], 'firstName', u'John') | ||||||
|   'OK' |   'OK' | ||||||
|   >>> john = xrf.getObjectById(john['id']) |   >>> john = xrf.getObjectById(john['id']) | ||||||
|  |  | ||||||
|  | @ -162,7 +162,7 @@ def objectAsDict(obj, langInfo=None): | ||||||
|                'title': adapter.title, 'description': adapter.description, |                'title': adapter.title, 'description': adapter.description, | ||||||
|                'type': getUidForObject(objType.typeProvider)} |                'type': getUidForObject(objType.typeProvider)} | ||||||
|     ti = objType.typeInterface |     ti = objType.typeInterface | ||||||
|     if ti is not None: |     if ti is not None and adapter != obj: | ||||||
|         #for attr in (list(adapter._adapterAttributes) + list(ti)): |         #for attr in (list(adapter._adapterAttributes) + list(ti)): | ||||||
|         for attr in list(ti): |         for attr in list(ti): | ||||||
|             if attr not in ('__parent__', 'context', 'id', 'name', |             if attr not in ('__parent__', 'context', 'id', 'name', | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 helmutm
						helmutm