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() | ||||
|   >>> view = NodeView(m112, request) | ||||
|   >>> view.controller = Controller(view, request) | ||||
|   >>> view.setupController() | ||||
|   >>> #view.setupController() | ||||
| 
 | ||||
|   >>> actions = view.getActions('portlet') | ||||
|   >>> len(actions) | ||||
|  | @ -758,7 +758,7 @@ The new technique uses the ``fields`` and ``data`` attributes... | |||
|   linkUrl textline False None | ||||
| 
 | ||||
|   >>> view.data | ||||
|   {'linkUrl': u'', 'contentType': u'', 'data': u'', | ||||
|   {'linkUrl': u'http://', 'contentType': 'text/restructured', 'data': u'', | ||||
|    'title': u'Test Note'} | ||||
| 
 | ||||
| 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 | ||||
| #  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.cachedescriptors.property import Lazy | ||||
| 
 | ||||
| action_macros = ViewPageTemplateFile('action_macros.pt') | ||||
| 
 | ||||
| 
 | ||||
| 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 | ||||
| from cybertools.browser.action import 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 | ||||
| #  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$ | ||||
| """ | ||||
| 
 | ||||
| from zope.app import zapi | ||||
| from zope import component | ||||
| from zope.app.form.browser.interfaces import ITerms | ||||
| 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.namedtemplate import NamedTemplate | ||||
| from zope.interface import Interface, implements | ||||
| from zope.proxy import removeAllProxies | ||||
| from zope.publisher.browser import applySkin | ||||
| from zope.publisher.interfaces.browser import IBrowserSkinType | ||||
| from zope import schema | ||||
| 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.proxy import removeSecurityProxy | ||||
| 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.relation.interfaces import IRelationRegistry | ||||
| from cybertools.text import mimetypes | ||||
| from cybertools.typology.interfaces import IType, ITypeManager | ||||
| from loops.common import adapted | ||||
| from loops.i18n.browser import I18NView | ||||
| from loops.interfaces import IView | ||||
| from loops.interfaces import IView, INode | ||||
| from loops.resource import Resource | ||||
| from loops.security.common import canAccessObject, canListObject, canWriteObject | ||||
| from loops.type import ITypeConcept | ||||
| from loops import util | ||||
| from loops.util import _ | ||||
|  | @ -90,29 +92,35 @@ class EditForm(form.EditForm): | |||
|     template = NamedTemplate('loops.pageform') | ||||
| 
 | ||||
|     def deleteObjectAction(self): | ||||
|         return None  # better not to show the edit button at the moment | ||||
|         parent = zapi.getParent(self.context) | ||||
|         return None  # better not to show the delete button at the moment | ||||
|         parent = getParent(self.context) | ||||
|         parentUrl = absoluteURL(parent, self.request) | ||||
|         return parentUrl + '/contents.html' | ||||
| 
 | ||||
| 
 | ||||
| class BaseView(GenericView, I18NView): | ||||
| 
 | ||||
|     actions = {}  # default only, don't update | ||||
|     actions = {} | ||||
| 
 | ||||
|     def __init__(self, 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 = context | ||||
|         #self.setSkin(self.loopsRoot.skinName) | ||||
|         self.checkLanguage() | ||||
|         #self.checkLanguage() | ||||
|         try: | ||||
|             if not canAccess(context, 'title'): | ||||
|             if not canAccessObject(context): | ||||
|                 raise Unauthorized | ||||
|                 #request.response.redirect('login.html') | ||||
|         except ForbiddenAttribute:  # ignore when testing | ||||
|             pass | ||||
| 
 | ||||
|     def update(self): | ||||
|         result = super(BaseView, self).update() | ||||
|         self.checkLanguage() | ||||
|         return result | ||||
| 
 | ||||
|     @Lazy | ||||
|     def target(self): | ||||
|         # allow for having a separate object the view acts upon | ||||
|  | @ -242,6 +250,16 @@ class BaseView(GenericView, I18NView): | |||
|         for o in objs: | ||||
|             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 | ||||
| 
 | ||||
|     def listTypes(self, include=None, exclude=None, sortOn='title'): | ||||
|  | @ -340,7 +358,7 @@ class BaseView(GenericView, I18NView): | |||
| 
 | ||||
|     @Lazy | ||||
|     def editable(self): | ||||
|         return canWrite(self.context, 'title') | ||||
|         return canWriteObject(self.context) | ||||
| 
 | ||||
|     def getActions(self, category='object', page=None): | ||||
|         """ Return a list of actions that provide the view and edit actions | ||||
|  | @ -364,7 +382,7 @@ class BaseView(GenericView, I18NView): | |||
|             return False | ||||
|         if ct.startswith('text/') and ct != 'text/rtf': | ||||
|             return checkPermission('loops.ManageSite', self.context) | ||||
|         return canWrite(self.context, 'title') | ||||
|         return canWriteObject(self.context) | ||||
| 
 | ||||
|     @Lazy | ||||
|     def inlineEditingActive(self): | ||||
|  | @ -385,8 +403,27 @@ class BaseView(GenericView, I18NView): | |||
| 
 | ||||
|     def registerDojo(self): | ||||
|         cm = self.controller.macros | ||||
|         cm.register('js', 'dojo.js', resourceName='ajax.dojo/dojo.js') | ||||
|         #cm.register('js', 'dojo.js', resourceName='ajax.dojo1/dojo/dojo.js') | ||||
|         cm.register('js', 'dojo.js', template=dojoMacroTemplate, name='main', | ||||
|                     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 | ||||
|  | @ -411,7 +448,7 @@ class LoopsTerms(object): | |||
|     def getTerm(self, value): | ||||
|         #if value is None: | ||||
|         #    return SimpleTerm(None, '', u'not assigned') | ||||
|         title = value.title or zapi.getName(value) | ||||
|         title = value.title or getName(value) | ||||
|         token = self.loopsRoot.getLoopsUri(value) | ||||
|         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 | ||||
| #  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.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.util.jeep import Jeep | ||||
| from loops.browser.common import EditForm, BaseView, LoopsTerms, conceptMacrosTemplate | ||||
| from loops.common import adapted | ||||
| from loops.concept import Concept, ConceptTypeSourceList, PredicateSourceList | ||||
|  | @ -56,8 +60,14 @@ from loops.versioning.util import getVersion | |||
| 
 | ||||
| 
 | ||||
| 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): | ||||
|         return IType(self.context).typeInterface | ||||
| 
 | ||||
|  | @ -79,7 +89,8 @@ class ConceptEditForm(EditForm, I18NView): | |||
| 
 | ||||
|     def setUpWidgets(self, ignore_request=False): | ||||
|         # 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, | ||||
|                          IConceptSchema: adapter} | ||||
|         self.widgets = setUpEditWidgets( | ||||
|  | @ -89,9 +100,87 @@ class ConceptEditForm(EditForm, I18NView): | |||
|         if desc: | ||||
|             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): | ||||
| 
 | ||||
|     template = ViewPageTemplateFile('concept_macros.pt') | ||||
|     childViewFactory = ConceptRelationView | ||||
| 
 | ||||
|     @Lazy | ||||
|     def macro(self): | ||||
|  | @ -108,7 +197,7 @@ class ConceptView(BaseView): | |||
|                                                     self.request.principal)): | ||||
|             cont.macros.register('portlet_right', 'parents', title=_(u'Parents'), | ||||
|                          subMacro=self.template.macros['parents'], | ||||
|                          position=0, info=self) | ||||
|                          priority=20, info=self) | ||||
| 
 | ||||
|     @Lazy | ||||
|     def adapted(self): | ||||
|  | @ -123,7 +212,8 @@ class ConceptView(BaseView): | |||
|         return self.adapted.description | ||||
| 
 | ||||
|     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 | ||||
|         if not ti: | ||||
|             return | ||||
|  | @ -140,12 +230,35 @@ class ConceptView(BaseView): | |||
|             widget.setRenderedValue(value) | ||||
|             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() | ||||
|         hasType = cm.getTypePredicate() | ||||
|         standard = cm.getDefaultPredicate() | ||||
|         #rels = self.context.getChildRelations() | ||||
|         rels = (ConceptRelationView(r, self.request, contextIsSecond=True) | ||||
|         rels = (self.childViewFactory(r, self.request, contextIsSecond=True) | ||||
|                 for r in self.context.getChildRelations(sort=None)) | ||||
|         if sort: | ||||
|             rels = sorted(rels, key=lambda r: (r.order, r.title.lower())) | ||||
|  | @ -160,27 +273,37 @@ class ConceptView(BaseView): | |||
|                 if skip: continue | ||||
|             yield r | ||||
| 
 | ||||
|     # Override in subclass to control what is displayd in listings: | ||||
|     children = getChildren | ||||
| 
 | ||||
|     def childrenAlphaGroups(self): | ||||
|         letters = [] | ||||
|         relations = {} | ||||
|         rels = self.children(topLevelOnly=False, sort=False) | ||||
|         result = Jeep() | ||||
|         rels = self.getChildren(topLevelOnly=False, sort=False) | ||||
|         rels = sorted(rels, key=lambda r: r.title.lower()) | ||||
|         for letter, group in groupby(rels, lambda r: r.title.lower()[0]): | ||||
|             letter = letter.upper() | ||||
|             letters.append(letter) | ||||
|             relations[letter] = list(group) | ||||
|         return dict(letters=letters, relations=relations) | ||||
|             result[letter] = list(group) | ||||
|         return result | ||||
| 
 | ||||
|     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): | ||||
|         rels = sorted(self.context.getParentRelations(), | ||||
|                       key=(lambda x: x.first.title.lower())) | ||||
|         for r in rels: | ||||
|             yield ConceptRelationView(r, self.request) | ||||
|             yield self.childViewFactory(r, self.request) | ||||
| 
 | ||||
|     def resources(self): | ||||
|         rels = self.context.getResourceRelations() | ||||
|         for r in rels: | ||||
|             yield ConceptRelationView(r, self.request, contextIsSecond=True) | ||||
|             yield self.childViewFactory(r, self.request, contextIsSecond=True) | ||||
| 
 | ||||
|     @Lazy | ||||
|     def view(self): | ||||
|  | @ -213,6 +336,14 @@ class ConceptView(BaseView): | |||
|         for node in self.context.getClients(): | ||||
|             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): | ||||
| 
 | ||||
|  | @ -321,7 +452,7 @@ class ConceptConfigureView(ConceptView): | |||
|                 else: | ||||
|                     start = end = searchType | ||||
|                 criteria['loops_type'] = (start, end) | ||||
|             cat = zapi.getUtility(ICatalog) | ||||
|             cat = component.getUtility(ICatalog) | ||||
|             result = cat.searchResults(**criteria) | ||||
|             # TODO: can this be done in a faster way? | ||||
|             result = [r for r in result if r.getLoopsRoot() == self.loopsRoot] | ||||
|  | @ -333,64 +464,8 @@ class ConceptConfigureView(ConceptView): | |||
| 
 | ||||
|     def predicates(self): | ||||
|         preds = PredicateSourceList(self.context) | ||||
|         terms = zapi.getMultiAdapter((preds, self.request), ITerms) | ||||
|         terms = component.getMultiAdapter((preds, self.request), ITerms) | ||||
|         for pred in preds: | ||||
|             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"> | ||||
|   <div tal:attributes="class string:content-$level;"> | ||||
|           <metal:fields use-macro="item/template/macros/concepttitle" /><br /> | ||||
|           <metal:fields use-macro="item/template/macros/conceptfields" /><br /> | ||||
|           <metal:fields use-macro="item/template/macros/conceptchildren" /><br /> | ||||
|           <metal:fields use-macro="item/template/macros/concepttitle" /> | ||||
|           <metal:fields use-macro="item/template/macros/conceptfields" /> | ||||
|           <metal:fields use-macro="item/template/macros/conceptchildren" /> | ||||
|           <metal:fields use-macro="item/template/macros/conceptresources" /> | ||||
|   </div> | ||||
| </metal:data> | ||||
|  | @ -10,7 +10,8 @@ | |||
| 
 | ||||
| <metal:title define-macro="concepttitle"> | ||||
|     <h1 tal:attributes="ondblclick item/openEditWindow"> | ||||
|       <span tal:content="item/title">Title</span> | ||||
|       <a name="top" | ||||
|          tal:content="item/title">Title</a> | ||||
|     </h1> | ||||
|       <p tal:define="description description|item/description" | ||||
|          tal:condition="description"> | ||||
|  | @ -19,13 +20,16 @@ | |||
| 
 | ||||
| 
 | ||||
| <metal:fields define-macro="conceptfields"> | ||||
|   <fieldset class="box" | ||||
|             tal:define="data python: list(item.fieldData())" | ||||
|             tal:condition="data"> | ||||
|     <table tal:attributes="ondblclick item/openEditWindow"> | ||||
|       <tr tal:repeat="field item/fieldData"> | ||||
|         <td><span tal:content="field/title" | ||||
|                   i18n:translate="" />:</td> | ||||
|       <tr tal:repeat="field data"> | ||||
|         <td><b tal:content="field/title" i18n:translate="" />:</td> | ||||
|         <td tal:content="structure field/widget"></td> | ||||
|       </tr> | ||||
|     </table> | ||||
|   </fieldset> | ||||
| </metal:fields> | ||||
| 
 | ||||
| 
 | ||||
|  | @ -47,7 +51,7 @@ | |||
|                            ondblclick python: item.openEditWindow('configure.html')" | ||||
|            tal:define="children python: list(item.children())" | ||||
|            tal:condition="children"> | ||||
|         <h2 i18n:translate="">Children</h2><br /> | ||||
|         <h2 i18n:translate="">Children</h2> | ||||
|         <table class="listing"> | ||||
|           <tr> | ||||
|             <th i18n:translate="">Title</th> | ||||
|  | @ -81,7 +85,7 @@ | |||
|                            ondblclick python: item.openEditWindow('resources.html')" | ||||
|            tal:define="resources python: list(item.resources())" | ||||
|            tal:condition="resources"> | ||||
|         <h2 i18n:translate="">Resources</h2><br /> | ||||
|         <h2 i18n:translate="">Resources</h2> | ||||
|         <table class="listing"> | ||||
|           <tr> | ||||
|             <th i18n:translate="">Title</th> | ||||
|  |  | |||
|  | @ -15,8 +15,6 @@ | |||
| 
 | ||||
|   <resource name="loops.css" file="loops.css" /> | ||||
|   <resource name="loops.js" file="loops.js" /> | ||||
|   <resource name="loops1.js" file="loops1.js" /> | ||||
| 
 | ||||
| 
 | ||||
|   <!-- 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 | ||||
| #  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 cStringIO import StringIO | ||||
| 
 | ||||
| from loops.external import IExternalContentSource | ||||
| from loops.external import ILoader, IExporter | ||||
| from loops.external.external import IExternalContentSource | ||||
| from loops.external.external import ILoader, IExporter | ||||
| 
 | ||||
| 
 | ||||
| class NodesExportImport(object): | ||||
|  | @ -38,7 +38,8 @@ class NodesExportImport(object): | |||
|     """ | ||||
| 
 | ||||
|     def __init__(self, context, request): | ||||
|         self.context = removeSecurityProxy(context) | ||||
|         #self.context = removeSecurityProxy(context) | ||||
|         self.context = context | ||||
|         self.request = request | ||||
|         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 | ||||
| #  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.contained import NameChooser | ||||
| from zope.app.form.browser.textwidgets import FileWidget, TextAreaWidget | ||||
| from zope.app.pagetemplate import ViewPageTemplateFile | ||||
| from zope.cachedescriptors.property import Lazy | ||||
| from zope.contenttype import guess_content_type | ||||
| #from zope.formlib.form import Form, EditForm, FormFields | ||||
| from zope.publisher.browser import FileUpload | ||||
| from zope.publisher.interfaces import BadRequest | ||||
| from zope.security.proxy import isinstance, removeSecurityProxy | ||||
|  | @ -81,7 +79,8 @@ class ObjectForm(NodeView): | |||
| 
 | ||||
|     def closeAction(self, submit=False): | ||||
|         if self.isInnerHtml: | ||||
|             return 'dialogs["%s"].hide()' % self.dialog_name | ||||
|             return ("closeDataWidget(%s); dialog.hide();" % | ||||
|                         (submit and 'true' or 'false')) | ||||
|         if submit: | ||||
|             return "xhrSubmitPopup('dialog_form', '%s'); return false" % (self.request.URL) | ||||
|         return 'window.close()' | ||||
|  | @ -101,7 +100,10 @@ class ObjectForm(NodeView): | |||
| 
 | ||||
|     @Lazy | ||||
|     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 | ||||
|     def fieldEditRenderers(self): | ||||
|  | @ -109,19 +111,17 @@ class ObjectForm(NodeView): | |||
| 
 | ||||
|     @Lazy | ||||
|     def schema(self): | ||||
|         #ti = self.typeInterface or Interface #IConcept | ||||
|         ti = self.typeInterface or IConceptSchema | ||||
|         schemaFactory = component.getAdapter(self.adapted, ISchemaFactory) | ||||
|         return schemaFactory(ti, manager=self, request=self.request) | ||||
| 
 | ||||
|     @Lazy | ||||
|     def fields(self): | ||||
|         return self.schema.fields | ||||
|         return [f for f in self.schema.fields if not f.readonly] | ||||
| 
 | ||||
|     @Lazy | ||||
|     def data(self): | ||||
|         instance = self.instance | ||||
|         instance.template = self.schema | ||||
|         data = instance.applyTemplate(mode='edit') | ||||
|         form = self.request.form | ||||
|         for k, v in data.items(): | ||||
|  | @ -132,11 +132,15 @@ class ObjectForm(NodeView): | |||
| 
 | ||||
|     @Lazy | ||||
|     def instance(self): | ||||
|         return IInstance(self.adapted) | ||||
|         instance = IInstance(self.adapted) | ||||
|         instance.template = self.schema | ||||
|         instance.view = self | ||||
|         return instance | ||||
| 
 | ||||
|     def __call__(self): | ||||
|         if self.isInnerHtml: | ||||
|             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('Pragma', 'no-cache') | ||||
|             return innerHtml(self) | ||||
|  | @ -240,11 +244,16 @@ class CreateObjectForm(ObjectForm): | |||
| 
 | ||||
|     @Lazy | ||||
|     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 | ||||
|     def instance(self): | ||||
|         return IInstance(Resource()) | ||||
|         instance = IInstance(self.adapted) | ||||
|         instance.template = self.schema | ||||
|         instance.view = self | ||||
|         return instance | ||||
| 
 | ||||
|     @Lazy | ||||
|     def typeInterface(self): | ||||
|  | @ -278,8 +287,9 @@ class CreateObjectPopup(CreateObjectForm): | |||
|         cm = self.controller.macros | ||||
|         cm.register('css', identifier='popup.css', resourceName='popup.css', | ||||
|                     media='all', position=4) | ||||
|         jsCall = ('dojo.require("dojo.widget.Dialog");' | ||||
|                   '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) | ||||
|         return True | ||||
| 
 | ||||
|  | @ -298,14 +308,19 @@ class CreateConceptForm(CreateObjectForm): | |||
| 
 | ||||
|     @Lazy | ||||
|     def adapted(self): | ||||
|         c = Concept() | ||||
|         ti = self.typeInterface | ||||
|         if ti is None: | ||||
|             return Concept() | ||||
|         return ti(Concept()) | ||||
|             return c | ||||
|         ad = ti(c) | ||||
|         return ad | ||||
| 
 | ||||
|     @Lazy | ||||
|     def instance(self): | ||||
|         return IInstance(Concept()) | ||||
|         instance = IInstance(self.adapted) | ||||
|         instance.template = self.schema | ||||
|         instance.view = self | ||||
|         return instance | ||||
| 
 | ||||
|     @Lazy | ||||
|     def typeInterface(self): | ||||
|  | @ -355,9 +370,6 @@ class EditObject(FormController, I18NView): | |||
|     prefix = 'form.' | ||||
|     conceptPrefix = 'assignments.' | ||||
| 
 | ||||
|     old = None | ||||
|     selected = None | ||||
| 
 | ||||
|     @Lazy | ||||
|     def adapted(self): | ||||
|         return adapted(self.object, self.languageInfoForUpdate) | ||||
|  | @ -377,7 +389,10 @@ class EditObject(FormController, I18NView): | |||
| 
 | ||||
|     @Lazy | ||||
|     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 | ||||
|     def loopsRoot(self): | ||||
|  | @ -406,14 +421,17 @@ class EditObject(FormController, I18NView): | |||
|         obj = self.object | ||||
|         form = self.request.form | ||||
|         instance = self.instance | ||||
|         instance.template = self.schema | ||||
|         #instance.template = self.schema | ||||
|         formState = instance.applyTemplate(data=form, fieldHandlers=self.fieldHandlers) | ||||
|         self.selected = [] | ||||
|         self.old = [] | ||||
|         for k in form.keys(): | ||||
|             if k.startswith(self.prefix): | ||||
|                 fn = k[len(self.prefix):] | ||||
|                 value = form[k] | ||||
|                 if fn.startswith(self.conceptPrefix) and value: | ||||
|                     self.collectConcepts(fn[len(self.conceptPrefix):], value) | ||||
|         self.collectAutoConcepts() | ||||
|         if self.old or self.selected: | ||||
|             self.assignConcepts(obj) | ||||
|         notify(ObjectModifiedEvent(obj)) | ||||
|  | @ -441,14 +459,15 @@ class EditObject(FormController, I18NView): | |||
|     def collectConcepts(self, fieldName, value): | ||||
|         if self.old is None: | ||||
|             self.old = [] | ||||
|         if self.selected is None: | ||||
|             self.selected = [] | ||||
|         for v in value: | ||||
|             if fieldName == 'old': | ||||
|                 self.old.append(v) | ||||
|             elif fieldName == 'selected' and v not in self.selected: | ||||
|                 self.selected.append(v) | ||||
| 
 | ||||
|     def collectAutoConcepts(self): | ||||
|         pass | ||||
| 
 | ||||
|     def assignConcepts(self, obj): | ||||
|         for v in self.old: | ||||
|             if v not in self.selected: | ||||
|  | @ -532,6 +551,10 @@ class CreateObject(EditObject): | |||
| 
 | ||||
| class EditConcept(EditObject): | ||||
| 
 | ||||
|     @Lazy | ||||
|     def typeInterface(self): | ||||
|         return IType(self.object).typeInterface or IConceptSchema | ||||
| 
 | ||||
|     def getConceptRelations(self, obj, predicates, concept): | ||||
|         return obj.getParentRelations(predicates=predicates, parent=concept) | ||||
| 
 | ||||
|  |  | |||
|  | @ -2,7 +2,8 @@ | |||
|      $Id$ --> | ||||
| 
 | ||||
| <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; | ||||
|                       languages langInfo/availableLanguages; | ||||
|                       language langInfo/language; | ||||
|  | @ -15,8 +16,8 @@ | |||
|       <table cellpadding="3" class="form"> | ||||
|         <tbody> | ||||
|           <tr> | ||||
|             <th colspan="5" | ||||
|                 tal:attributes="colspan python: useI18N and 4 or 5"><br /> | ||||
|             <th colspan="5" class="headline" | ||||
|                 tal:attributes="colspan python: useI18N and 4 or 5"> | ||||
|               <span tal:replace="view/title" | ||||
|                     i18n:translate="">Edit Information Object</span> | ||||
|             </th> | ||||
|  | @ -44,7 +45,7 @@ | |||
|         <tbody> | ||||
|           <tr> | ||||
|             <td colspan="5" class="headline" | ||||
|                 i18n:translate="">Concept Assignments</td> | ||||
|                 i18n:translate="">Assign Parent Concepts</td> | ||||
|           </tr> | ||||
|           <tr metal:use-macro="view/template/macros/assignments" /> | ||||
|           <tr metal:use-macro="view/template/macros/search_concepts" /> | ||||
|  | @ -61,7 +62,8 @@ | |||
| 
 | ||||
| 
 | ||||
| <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; | ||||
|                       innerForm request/inner_form | string:inner_form.html; | ||||
|                       typeToken python: request.get('form.type') | ||||
|  | @ -70,8 +72,8 @@ | |||
|       <input type="hidden" name="form.action" value="create" | ||||
|              tal:attributes="value view/form_action" /> | ||||
|       <table cellpadding="3" class="form"> | ||||
|         <tbody><tr><th colspan="5"><br /> | ||||
|               <span tal:replace="view/title" | ||||
|         <tbody><tr><th colspan="5" class="headline"> | ||||
|               <span tal:content="view/title" | ||||
|                     i18n:translate="">Create Information Object</span> | ||||
|               <select name="form.type" id="form.type" | ||||
|                       tal:condition="not:fixedType" | ||||
|  | @ -154,9 +156,7 @@ | |||
|     <td><label for="concept.search.type"><span i18n:translate="">Type</span>:</label></td> | ||||
|     <td> | ||||
|       <select id="concept.search.type" | ||||
|               tal:attributes="onChange | ||||
|                 string:setConceptTypeForComboBox( | ||||
|                     'concept.search.type', 'concept.search.text')"> | ||||
|               onChange="setConceptTypeForComboBox('concept.search.type', 'concept.search.text')"> | ||||
|         <tal:types repeat="type view/conceptTypesForSearch"> | ||||
|           <option value="loops:*" | ||||
|                   i18n:translate="" | ||||
|  | @ -172,19 +172,12 @@ | |||
|       <input type="hidden" | ||||
|              id="concept.search.predicate" | ||||
|              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" | ||||
|              tal:attributes="url | ||||
|                   string:listConceptsForComboBox.js?searchString=%{searchString}&searchType=" > | ||||
|            url="listConceptsForComboBox.js?searchType=" > | ||||
|       </div> | ||||
|         <input dojoType="dijit.form.ComboBox" store="conceptSearch" | ||||
|                autoComplete="False" | ||||
|       <input dojoType="dijit.form.FilteringSelect" store="conceptSearch" | ||||
|              autoComplete="False" labelAttr="label" | ||||
|              name="concept.search.text" id="concept.search.text" /> | ||||
|       </tal:dojo1> | ||||
|     </td> | ||||
|     <td> | ||||
|       <input type="button" value="Select" | ||||
|  | @ -222,7 +215,7 @@ | |||
| </metal:versioning> | ||||
| 
 | ||||
| 
 | ||||
| <tr metal:define-macro="buttons" i18n:domain=""> | ||||
| <tr metal:define-macro="buttons" i18n:domain="" class="buttons"> | ||||
|             <td colspan="5"> | ||||
|               <input value="Save" type="submit" | ||||
|                      i18n:attributes="value" | ||||
|  | @ -232,3 +225,19 @@ | |||
|                      tal:attributes="onClick view/closeAction"> | ||||
|             </td> | ||||
| </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 { | ||||
|     background-color: #f4f4f4; | ||||
|     overflow: scroll; | ||||
|     max-height: 35em; | ||||
| } | ||||
| 
 | ||||
| ul, p { | ||||
|     margin-top: 0.4em; | ||||
|     margin-bottom: 0.5em; | ||||
| } | ||||
| 
 | ||||
| table.listing td { | ||||
|     white-space: normal; | ||||
| } | ||||
| 
 | ||||
| .box div.body div.even { | ||||
|     background-color: #f4f4f4; | ||||
| fieldset.box { | ||||
|     margin: 1em 0 0.5em 0; | ||||
|     padding: 0.5em; | ||||
|     border: 1px solid #ccc; | ||||
| } | ||||
| 
 | ||||
| .box { | ||||
|     margin: 12px; | ||||
| fieldset.box td { | ||||
|     padding: 0.2em 0.2em 0.2em 0; | ||||
| } | ||||
| 
 | ||||
| #body { | ||||
|  | @ -37,45 +45,77 @@ table.listing td { | |||
|     top: 1em; | ||||
| } | ||||
| 
 | ||||
| /*.content-1 h1 { */ | ||||
| h1 { | ||||
| .top image { | ||||
|     margin-top: -1px; | ||||
| } | ||||
| 
 | ||||
| .content-1 h1, h1 { | ||||
|     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-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-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-weight: normal; | ||||
|     margin-top: 0.7em; | ||||
| } | ||||
| 
 | ||||
| .content-5 h1, .content-4 h2, .content-3 h3, content-2 h4 { | ||||
|     font-size: 100%; | ||||
|     border: none; | ||||
| } | ||||
| 
 | ||||
| .subcolumn { | ||||
|     display: inline; | ||||
|     float: left; | ||||
|     margin-top: 0.7em; | ||||
| } | ||||
| 
 | ||||
| .box { | ||||
|     margin: 5px; | ||||
|     margin: 12px; | ||||
|     /*margin: 5px;*/ | ||||
|     padding: 6px; | ||||
|     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 { | ||||
|     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 { | ||||
|     border-top: 1px solid #eeeeee; | ||||
|     font-weight: bold; | ||||
|  | @ -83,7 +123,7 @@ div.menu-1, div.menu-2 { | |||
| 
 | ||||
| .box div.body div.menu-3 { | ||||
|     border-top: none; | ||||
|     padding-left: 1.5em; | ||||
|     padding: 0.1em 0 0.2em 1.5em; | ||||
| } | ||||
| 
 | ||||
| .box div.body div.menu-4 { | ||||
|  | @ -91,6 +131,19 @@ div.menu-1, div.menu-2 { | |||
|     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 { | ||||
|     float: left; | ||||
| } | ||||
|  | @ -128,6 +181,45 @@ img.notselected { | |||
|     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 */ | ||||
| 
 | ||||
|  | @ -139,34 +231,70 @@ img.notselected { | |||
|     font-weight: bold; | ||||
| } | ||||
| 
 | ||||
| /* dojo stuff */ | ||||
| /* blog */ | ||||
| 
 | ||||
| /*.dojoComboBox { | ||||
|     width: 200px; | ||||
| }*/ | ||||
| 
 | ||||
| .dojoDialog { | ||||
|     background: #eee; | ||||
|     border: 1px solid #999; | ||||
|     -moz-border-radius: 5px; | ||||
|     padding: 4px; | ||||
| .blog .description { | ||||
|     font-size: 90%; | ||||
|     color: #666666; | ||||
| } | ||||
| 
 | ||||
| .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-weight: bold; | ||||
|     text-align: center; | ||||
|     padding: 0 5px 8px 5px; | ||||
| } | ||||
| 
 | ||||
| .dojoDialog .headline { | ||||
| .dijitDialog td { | ||||
|     padding: 2px; | ||||
| } | ||||
| 
 | ||||
| .dijitDialog .headline { | ||||
|     font-weight: bold; | ||||
| } | ||||
| 
 | ||||
| .dojoDialog input.text { | ||||
| .dijitDialog input.text { | ||||
|     width: 100%; | ||||
|     margin-right: 10px; | ||||
| } | ||||
| 
 | ||||
| .dojoDialog input.submit { | ||||
| .dijitDialog input.submit { | ||||
|     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) { | ||||
|     token = dojo.byId(typeId).value; | ||||
|     uri = url + '?form.type=' + token; | ||||
|     dojo.io.updateNode(targetId, uri); | ||||
|     updateNode(uri, 'form.fields'); | ||||
| } | ||||
| 
 | ||||
| function replaceFieldsNodeForLanguage(targetId, langId, url) { | ||||
|     lang = dojo.byId(langId).value; | ||||
|     uri = url + '?loops.language=' + lang; | ||||
|     dojo.io.updateNode(targetId, uri); | ||||
|     updateNode(uri, 'form.fields'); | ||||
| } | ||||
| 
 | ||||
| function submitReplacing(targetId, formId, actionUrl) { | ||||
|     dojo.io.updateNode(targetId, { | ||||
|             url: actionUrl, | ||||
|             formNode: dojo.byId(formId), | ||||
|             method: 'post' | ||||
|         }); | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| function xhrSubmitPopup(formId, actionUrl) { | ||||
|     dojo.io.bind({ | ||||
|         url: actionUrl, | ||||
|         formNode: dojo.byId(formId), | ||||
|         method: 'post', | ||||
| function submitReplacing(targetId, formId, url) { | ||||
|     dojo.xhrPost({ | ||||
|         url: url, | ||||
|         form: dojo.byId(formId), | ||||
|         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(); | ||||
|         } | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| 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 = dojo.widget.manager.getWidgetById(cbId); | ||||
|     var dp = cb.dataProvider; | ||||
|     var baseUrl = dp.searchUrl.split('&')[0]; | ||||
|     var newUrl = baseUrl + '&searchType=' + t; | ||||
|     dp.searchUrl = newUrl; | ||||
|     cb.setValue(''); | ||||
|     var cb = dijit.byId(cbId); | ||||
|     var dp = cb.store; | ||||
|     var baseUrl = dp.url.split('?')[0]; | ||||
|     var newUrl = baseUrl + '?searchType=' + t; | ||||
|     dp.url = newUrl; | ||||
|     cb.setDisplayedValue(''); | ||||
| } | ||||
| 
 | ||||
| var dialogs = {} | ||||
| var dialog; | ||||
| var dialogName | ||||
| 
 | ||||
| function objectDialog(dlgName, url) { | ||||
|     dojo.require('dojo.widget.Dialog'); | ||||
|     dojo.require('dojo.widget.ComboBox'); | ||||
|     dlg = dialogs[dlgName]; | ||||
|     if (!dlg) { | ||||
|         //dlg = dojo.widget.fromScript('Dialog',
 | ||||
|         dlg = dojo.widget.createWidget('Dialog', | ||||
|             {bgColor: 'white', bgOpacity: 0.5, toggle: 'fade', | ||||
|              toggleDuration: 250, | ||||
|              executeScripts: true, | ||||
|     dojo.require('dijit.Dialog'); | ||||
|     dojo.require('dojo.parser'); | ||||
|     dojo.require('dijit.form.FilteringSelect'); | ||||
|     dojo.require('dojox.data.QueryReadStore'); | ||||
|     if (dialogName == undefined || dialogName != dlgName) { | ||||
|         if (dialog != undefined) { | ||||
|             dialog.destroyRecursive(); | ||||
|         } | ||||
|         dialogName = dlgName; | ||||
|         dialog = new dijit.Dialog({ | ||||
|                     href: url | ||||
|                         }, dojo.byId('dialog.' + dlgName)); | ||||
|         dialogs[dlgName] = dlg; | ||||
|     } | ||||
|     dlg.show(); | ||||
|     dialog.show(); | ||||
| } | ||||
| 
 | ||||
| function addConceptAssignment(prefix, suffix) { | ||||
|     dojo.require('dojo.html') | ||||
|     node = dojo.byId('form.' + suffix); | ||||
|     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 == prefix + '.search.text_selected') { | ||||
|             cToken = el.value; | ||||
|         } else if (el.name == prefix + '.search.text') { | ||||
|             title = el.value; | ||||
|         } | ||||
|     } | ||||
|     widget = dijit.byId(prefix + '.search.text'); | ||||
|     cToken = widget.getValue(); | ||||
|     title = widget.getDisplayedValue(); | ||||
|     if (cToken.length == 0) { | ||||
|         alert('Please select a concept!'); | ||||
|         return false; | ||||
|  | @ -165,3 +143,23 @@ function addConceptAssignment(prefix, suffix) { | |||
|     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" | ||||
|                tal:condition="view/inlineEditingActive | nothing"> | ||||
|     <script> | ||||
|         dojo.require("dojo.widget.Editor"); | ||||
|         dojo.require("dijit.Editor"); | ||||
|     </script> | ||||
|   </metal:block> | ||||
| 
 | ||||
|  |  | |||
|  | @ -44,16 +44,17 @@ from zope.security.proxy import removeSecurityProxy | |||
| 
 | ||||
| from cybertools.ajax import innerHtml | ||||
| from cybertools.browser import configurator | ||||
| from cybertools.browser.action import Action | ||||
| from cybertools.browser.view import GenericView | ||||
| from cybertools.typology.interfaces import IType, ITypeManager | ||||
| from cybertools.xedit.browser import ExternalEditorView | ||||
| from loops.browser.action import DialogAction | ||||
| from loops.i18n.browser import i18n_macros | ||||
| from loops.interfaces import IConcept, IResource, IDocument, IMediaAsset, INode | ||||
| from loops.interfaces import IViewConfiguratorSchema | ||||
| from loops.resource import MediaAsset | ||||
| from loops import util | ||||
| from loops.util import _ | ||||
| from loops.browser.action import Action, DialogAction, TargetAction | ||||
| from loops.browser.common import BaseView | ||||
| from loops.browser.concept import ConceptView | ||||
| from loops.versioning.util import getVersion | ||||
|  | @ -65,8 +66,8 @@ node_macros = ViewPageTemplateFile('node_macros.pt') | |||
| class NodeView(BaseView): | ||||
| 
 | ||||
|     _itemNum = 0 | ||||
| 
 | ||||
|     template = node_macros | ||||
|     nextUrl = None | ||||
| 
 | ||||
|     def __init__(self, context, request): | ||||
|         super(NodeView, self).__init__(context, request) | ||||
|  | @ -80,23 +81,31 @@ class NodeView(BaseView): | |||
|     def setupController(self): | ||||
|         cm = self.controller.macros | ||||
|         cm.register('css', identifier='loops.css', resourceName='loops.css', | ||||
|                     media='all', position=3) | ||||
|         cm.register('js', 'loops.js', resourceName='loops.js') | ||||
|         #cm.register('js', 'loops.js', resourceName='loops1.js') | ||||
|                     media='all', priority=60) | ||||
|         cm.register('js', 'loops.js', resourceName='loops.js', priority=60) | ||||
|         cm.register('top_actions', 'top_actions', name='multi_actions', | ||||
|                     subMacros=[i18n_macros.macros['language_switch']]) | ||||
|         cm.register('portlet_left', 'navigation', title='Navigation', | ||||
|                     subMacro=node_macros.macros['menu']) | ||||
|         #if not IUnauthenticatedPrincipal.providedBy(self.request.principal): | ||||
|         if canWrite(self.context, 'title'): | ||||
|             #cm.register('portlet_right', 'clipboard', title='Clipboard', | ||||
|             #            subMacro=self.template.macros['clipboard']) | ||||
|             # this belongs to loops.organize; how to register portlets | ||||
|             # from sub- (other) packages? | ||||
|             # see controller / configurator: use multiple configurators; | ||||
|             # register additional configurators (adapters) from within package. | ||||
|             # this belongs to loops.organize | ||||
|             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 | ||||
|         self.virtualTarget | ||||
| 
 | ||||
|  | @ -117,7 +126,7 @@ class NodeView(BaseView): | |||
|         #target = self.virtualTargetObject  # ignores page even for direktly assignd target | ||||
|         target = self.request.annotations.get('loops.view', {}).get('target') | ||||
|         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: | ||||
|             return basicView.view | ||||
|         return self.page | ||||
|  | @ -154,7 +163,7 @@ class NodeView(BaseView): | |||
|         if text.startswith('<'):  # seems to be HTML | ||||
|             return 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() | ||||
| 
 | ||||
|     @Lazy | ||||
|  | @ -170,7 +179,7 @@ class NodeView(BaseView): | |||
|     def targetObjectView(self): | ||||
|         obj = self.targetObject | ||||
|         if obj is not None: | ||||
|             basicView = zapi.getMultiAdapter((obj, self.request)) | ||||
|             basicView = component.getMultiAdapter((obj, self.request)) | ||||
|             basicView._viewName = self.context.viewName | ||||
|             return basicView.view | ||||
| 
 | ||||
|  | @ -315,7 +324,7 @@ class NodeView(BaseView): | |||
|     def virtualTarget(self): | ||||
|         obj = self.virtualTargetObject | ||||
|         if obj is not None: | ||||
|             basicView = zapi.getMultiAdapter((obj, self.request)) | ||||
|             basicView = component.getMultiAdapter((obj, self.request)) | ||||
|             if obj == self.targetObject: | ||||
|                 basicView._viewName = self.context.viewName | ||||
|             return basicView.view | ||||
|  | @ -376,8 +385,6 @@ class NodeView(BaseView): | |||
| 
 | ||||
|     actions = dict(portlet=getPortletActions) | ||||
| 
 | ||||
|     nextUrl = None | ||||
| 
 | ||||
|     @Lazy | ||||
|     def popupCreateObjectForm(self): | ||||
|         return ("javascript:function%%20openDialog(url){" | ||||
|  | @ -401,11 +408,19 @@ class NodeView(BaseView): | |||
|     def inlineEdit(self, id): | ||||
|         self.registerDojo() | ||||
|         cm = self.controller.macros | ||||
|         jsCall = 'dojo.require("dojo.widget.Editor")' | ||||
|         jsCall = 'dojo.require("dijit.Editor")' | ||||
|         cm.register('js-execute', jsCall, jsCall=jsCall) | ||||
|         return ('return inlineEdit("%s", "%s/inline_save")' | ||||
|                                         % (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): | ||||
|         target = self.virtualTargetObject | ||||
|         if target is None: | ||||
|  | @ -423,7 +438,7 @@ class NodeView(BaseView): | |||
|     def registerDojoDialog(self): | ||||
|         self.registerDojo() | ||||
|         cm = self.controller.macros | ||||
|         jsCall = 'dojo.require("dojo.widget.Dialog")' | ||||
|         jsCall = 'dojo.require("dijit.Dialog")' | ||||
|         cm.register('js-execute', jsCall, jsCall=jsCall) | ||||
| 
 | ||||
| 
 | ||||
|  | @ -527,7 +542,7 @@ class ConfigureView(NodeView): | |||
|     def target(self): | ||||
|         obj = self.targetObject | ||||
|         if obj is not None: | ||||
|             return zapi.getMultiAdapter((obj, self.request)) | ||||
|             return component.getMultiAdapter((obj, self.request)) | ||||
| 
 | ||||
|     def update(self): | ||||
|         request = self.request | ||||
|  | @ -604,7 +619,7 @@ class ConfigureView(NodeView): | |||
|                 else: | ||||
|                     start = end = searchType | ||||
|                 criteria['loops_type'] = (start, end) | ||||
|             cat = zapi.getUtility(ICatalog) | ||||
|             cat = component.getUtility(ICatalog) | ||||
|             result = cat.searchResults(**criteria) | ||||
|             # TODO: can this be done in a faster way? | ||||
|             result = [r for r in result if r.getLoopsRoot() == self.loopsRoot] | ||||
|  | @ -664,7 +679,7 @@ class ViewPropertiesConfigurator(object): | |||
|     options = property(getOptions, setOptions) | ||||
| 
 | ||||
| 
 | ||||
| class NodeViewConfigurator(configurator.ViewConfigurator): | ||||
| class NodeViewConfigurator(configurator.AnnotationViewConfigurator): | ||||
|     """ Take properties from next menu item... | ||||
|     """ | ||||
| 
 | ||||
|  |  | |||
|  | @ -27,24 +27,25 @@ | |||
|                tal:condition="nocall:target"> | ||||
|             <tal:ignore condition="nothing"> | ||||
|                 <div metal:define-macro="editicons" | ||||
|                      style="float: right; border: 1px solid #ccc; | ||||
|                             margin-top: 1em"> | ||||
|                      style="float: right;"> | ||||
|                   <span id="xedit_icon" | ||||
|                        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; | ||||
|                                        title string:Edit '${target/title}' with External Editor"><img | ||||
|                                   src="edit.gif" alt="Edit" | ||||
|                                   tal:attributes="src context/++resource++edit.gif" /></a> | ||||
|                   </span> | ||||
|                   <span id="inlineedit_icon" | ||||
|                        tal:condition="item/inlineEditable"> | ||||
|                         xtal:condition="item/inlineEditable" | ||||
|                         tal:condition="nothing"> | ||||
|                     <a href="#" title="Edit" style="padding: 5px" | ||||
|                        tal:attributes="title string:Edit '${target/title}' with Inline Editor; | ||||
|                                        onclick python: item.inlineEdit(id)"><img | ||||
|                                   src="edit.gif" alt="Edit" | ||||
|                                   tal:attributes="src context/++resource++edit.gif" /></a> | ||||
|                   </span> | ||||
|                   <tal:rte define="dummy item/checkRTE" /> | ||||
|                 </div> | ||||
|             </tal:ignore> | ||||
|             <div class="content-1 subcolumn" id="1.body" | ||||
|  | @ -178,7 +179,7 @@ | |||
| 
 | ||||
| <metal:menu define-macro="menu" | ||||
|             tal:define="item nocall:view/menu | nothing; | ||||
|                         level level|python: 1" | ||||
|                         level level|python: 1;" | ||||
|             tal:condition="nocall:item"> | ||||
|     <metal:sub define-macro="submenu"> | ||||
|           <div class="menu-3" | ||||
|  | @ -219,6 +220,14 @@ | |||
| </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 --> | ||||
| 
 | ||||
| <div metal:define-macro="inline_edit" | ||||
|  |  | |||
|  | @ -25,7 +25,6 @@ $Id$ | |||
| import urllib | ||||
| from zope.cachedescriptors.property import Lazy | ||||
| from zope import component | ||||
| from zope.app import zapi | ||||
| from zope.app.catalog.interfaces import ICatalog | ||||
| from zope.app.container.interfaces import INameChooser | ||||
| 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.proxy import removeSecurityProxy | ||||
| from zope.traversing.api import getName, getParent | ||||
| from zope.traversing.browser import absoluteURL | ||||
| 
 | ||||
| from cybertools.typology.interfaces import IType | ||||
| 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.concept import ConceptRelationView, ConceptConfigureView | ||||
| from loops.browser.node import NodeView, node_macros | ||||
| from loops.browser.util import html_quote | ||||
| from loops.common import adapted, NameChooser | ||||
| from loops.interfaces import IBaseResource, IDocument, IMediaAsset, ITextDocument | ||||
| from loops.interfaces import ITypeConcept | ||||
|  | @ -53,14 +52,6 @@ from loops.versioning.browser import version_macros | |||
| from loops.versioning.interfaces import IVersionable | ||||
| 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): | ||||
| 
 | ||||
|  | @ -120,16 +111,18 @@ class ResourceView(BaseView): | |||
|         super(ResourceView, self).__init__(context, request) | ||||
|         if not IUnauthenticatedPrincipal.providedBy(self.request.principal): | ||||
|             cont = self.controller | ||||
|             if cont is not None and list(self.relatedConcepts()): | ||||
|                 cont.macros.register('portlet_right', 'related', title=_(u'Related Items'), | ||||
|             if cont is not None: | ||||
|                 if list(self.relatedConcepts()): | ||||
|                     cont.macros.register('portlet_right', 'related', | ||||
|                                 title=_(u'Related Items'), | ||||
|                                 subMacro=self.template.macros['related'], | ||||
|                              position=0, info=self) | ||||
|                                 priority=20, info=self) | ||||
|                 versionable = IVersionable(self.context, None) | ||||
|                 if versionable is not None and len(versionable.versions) > 1: | ||||
|                         cont.macros.register('portlet_right', 'versions', | ||||
|                                 title='Version ' + versionable.versionId, | ||||
|                                 subMacro=version_macros.macros['portlet_versions'], | ||||
|                                 position=1, info=self) | ||||
|                                 priority=25, info=self) | ||||
| 
 | ||||
|     @Lazy | ||||
|     def view(self): | ||||
|  | @ -150,10 +143,6 @@ class ResourceView(BaseView): | |||
| 
 | ||||
|     def show(self, useAttachment=False): | ||||
|         """ show means: "download"...""" | ||||
|         #data = self.openForView() | ||||
|         #response.setHeader('Content-Disposition', | ||||
|         #                   'attachment; filename=%s' % zapi.getName(self.context)) | ||||
|         #return data | ||||
|         context = self.context | ||||
|         ti = IType(context).typeInterface | ||||
|         if ti is not None: | ||||
|  | @ -222,6 +211,10 @@ class ResourceView(BaseView): | |||
| 
 | ||||
| class ResourceConfigureView(ResourceView, ConceptConfigureView): | ||||
| 
 | ||||
|     #def __init__(self, context, request): | ||||
|     #    # avoid calling ConceptView.__init__() | ||||
|     #    ResourceView.__init__(self, context, request) | ||||
| 
 | ||||
|     def update(self): | ||||
|         request = self.request | ||||
|         action = request.get('action') | ||||
|  | @ -269,7 +262,7 @@ class ResourceConfigureView(ResourceView, ConceptConfigureView): | |||
|                 else: | ||||
|                     start = end = searchType | ||||
|                 criteria['loops_type'] = (start, end) | ||||
|             cat = zapi.getUtility(ICatalog) | ||||
|             cat = component.getUtility(ICatalog) | ||||
|             result = cat.searchResults(**criteria) | ||||
|             # TODO: can this be done in a faster way? | ||||
|             result = [r for r in result if r.getLoopsRoot() == self.loopsRoot] | ||||
|  | @ -292,19 +285,10 @@ class DocumentView(ResourceView): | |||
|     def render(self): | ||||
|         """ Return the rendered content (data) of the context object. | ||||
|         """ | ||||
|         #text = self.context.data | ||||
|         ctx = adapted(self.context) | ||||
|         text = ctx.data | ||||
|         #contentType = self.context.contentType | ||||
|         contentType = ctx.contentType | ||||
|         typeKey = renderingFactories.get(contentType, None) | ||||
|         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() | ||||
|         return self.renderText(ctx.data, ctx.contentType) | ||||
| 
 | ||||
|     @Lazy | ||||
|     def inlineEditable(self): | ||||
|  | @ -316,10 +300,12 @@ class DocumentView(ResourceView): | |||
| class ExternalEditorView(ExternalEditorView): | ||||
| 
 | ||||
|     def load(self, url=None): | ||||
|         context = removeSecurityProxy(self.context) | ||||
|         #context = removeSecurityProxy(self.context) | ||||
|         context = self.context | ||||
|         data = adapted(context).data | ||||
|         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('meta_type:' + '.'.join((context.__module__, context.__class__.__name__))) | ||||
|         auth = self.request.get('_auth') | ||||
|  |  | |||
|  | @ -10,6 +10,11 @@ | |||
| 
 | ||||
| 
 | ||||
|     <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> · | ||||
|       <b><a href="http://wiki.zope.org/zope3">Zope 3</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 | ||||
|   >>> site = placefulSetUp(True) | ||||
| 
 | ||||
|   >>> from loops.organize.setup import SetupManager | ||||
|   >>> component.provideAdapter(SetupManager, name='organize') | ||||
|   >>> from loops.tests.setup import TestSite | ||||
|   >>> t = TestSite(site) | ||||
|   >>> concepts, resources, views = t.setup() | ||||
|   >>> loopsRoot = site['loops'] | ||||
| 
 | ||||
| 
 | ||||
| Compund Objects - Hierarchies with Ordered Components | ||||
|  | @ -74,3 +77,244 @@ And remove a part from the compound. | |||
|   >>> [getName(p) for p in aC01.getParts()] | ||||
|   [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) | ||||
| 
 | ||||
|     @Lazy | ||||
|     def compoundPredicate(self): | ||||
|         return self.context.getConceptManager()[compoundPredicateName] | ||||
| 
 | ||||
|     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): | ||||
|         if position is None: | ||||
|             order = self.getMaxOrder() + 1 | ||||
|         else: | ||||
|             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): | ||||
|         if position is None: | ||||
|             self.context.deassignChild(obj, [self.partOf]) | ||||
|             self.context.deassignResource(obj, [self.partOf]) | ||||
|         else: | ||||
|             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): | ||||
|         existing = list(self.getPartRelations()) | ||||
|  | @ -71,7 +77,7 @@ class Compound(AdapterBase): | |||
|     # helper methods and properties | ||||
| 
 | ||||
|     def getPartRelations(self): | ||||
|         return self.context.getChildRelations([self.partOf]) | ||||
|         return self.context.getResourceRelations([self.partOf]) | ||||
| 
 | ||||
|     def getMaxOrder(self): | ||||
|         rels = self. getPartRelations() | ||||
|  | @ -105,6 +111,10 @@ class Compound(AdapterBase): | |||
|     def conceptManager(self): | ||||
|         return self.context.getConceptManager() | ||||
| 
 | ||||
|     @Lazy | ||||
|     def resourceManager(self): | ||||
|         return self.getLoopsRoot().getResourceManager() | ||||
| 
 | ||||
|     @Lazy | ||||
|     def partOf(self): | ||||
|         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 import interface, component, schema | ||||
| 
 | ||||
| from loops.interfaces import IConceptSchema | ||||
| from loops.util import _ | ||||
| 
 | ||||
| 
 | ||||
| compoundPredicateName = 'ispartof' | ||||
| 
 | ||||
| 
 | ||||
| class ICompound(Interface): | ||||
| class ICompound(IConceptSchema): | ||||
|     """ A compound is a concept that is built up of other objects, its | ||||
|         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 | ||||
| #  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.cachedescriptors.property import Lazy | ||||
| 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 alsoProvides, directlyProvides, directlyProvidedBy | ||||
| from zope.security.proxy import removeSecurityProxy, isinstance | ||||
|  | @ -35,7 +37,6 @@ from persistent import Persistent | |||
| 
 | ||||
| from cybertools.relation import DyadicRelation | ||||
| from cybertools.relation.registry import getRelations | ||||
| from cybertools.relation.registry import getRelationSingle, setRelationSingle | ||||
| from cybertools.relation.interfaces import IRelationRegistry, IRelatable | ||||
| from cybertools.typology.interfaces import IType, ITypeManager | ||||
| 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 ILoopsContained | ||||
| from loops.interfaces import IIndexAttributes | ||||
| from loops.interfaces import IAssignmentEvent, IDeassignmentEvent | ||||
| from loops.security.common import canListObject | ||||
| from loops import util | ||||
| from loops.view import TargetRelation | ||||
| 
 | ||||
|  | @ -159,29 +162,35 @@ class Concept(Contained, Persistent): | |||
|         if relationships is None: | ||||
|             relationships = [TargetRelation] | ||||
|         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 | ||||
|         relationships = [ConceptRelation(self, None, p) for p in predicates] | ||||
|         if sort == 'default': | ||||
|             sort = lambda x: (x.order, x.second.title.lower()) | ||||
|         return sorted(getRelations(first=self, second=child, relationships=relationships), | ||||
|                       key=sort) | ||||
|         rels = (r for r in getRelations(self, child, relationships=relationships) | ||||
|                   if canListObject(r.second, noSecurityCheck)) | ||||
|         return sorted(rels, key=sort) | ||||
| 
 | ||||
|     def getChildren(self, predicates=None, sort='default'): | ||||
|         return [r.second for r in self.getChildRelations(predicates, sort=sort)] | ||||
|     def getChildren(self, predicates=None, sort='default', noSecurityCheck=False): | ||||
|         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 | ||||
|         relationships = [ConceptRelation(None, self, p) for p in predicates] | ||||
|         if sort == 'default': | ||||
|             sort = lambda x: (x.order, x.first.title.lower()) | ||||
|         return sorted(getRelations(first=parent, second=self, relationships=relationships), | ||||
|                       key=sort) | ||||
|         rels = (r for r in getRelations(parent, self, relationships=relationships) | ||||
|                   if canListObject(r.first, noSecurityCheck)) | ||||
|         return sorted(rels, key=sort) | ||||
| 
 | ||||
|     def getParents(self, predicates=None, sort='default'): | ||||
|         return [r.first for r in self.getParentRelations(predicates, sort=sort)] | ||||
|     def getParents(self, predicates=None, sort='default', noSecurityCheck=False): | ||||
|         return [r.first for r in self.getParentRelations(predicates, sort=sort, | ||||
|                                                 noSecurityCheck=noSecurityCheck)] | ||||
| 
 | ||||
|     def assignChild(self, concept, predicate=None, order=0, relevance=1.0): | ||||
|         if predicate is None: | ||||
|  | @ -194,6 +203,7 @@ class Concept(Contained, Persistent): | |||
|             rel.relevance = relevance | ||||
|         # TODO (?): avoid duplicates | ||||
|         registry.register(rel) | ||||
|         notify(AssignmentEvent(self, rel)) | ||||
| 
 | ||||
|     def setChildren(self, predicate, concepts): | ||||
|         existing = self.getChildren([predicate]) | ||||
|  | @ -211,6 +221,7 @@ class Concept(Contained, Persistent): | |||
|         registry = component.getUtility(IRelationRegistry) | ||||
|         for rel in self.getChildRelations(predicates, child): | ||||
|             if order is None or rel.order == order: | ||||
|                 notify(DeassignmentEvent(self, rel)) | ||||
|                 registry.unregister(rel) | ||||
| 
 | ||||
|     def deassignParent(self, parent, predicates=None): | ||||
|  | @ -218,17 +229,19 @@ class Concept(Contained, Persistent): | |||
| 
 | ||||
|     # 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 | ||||
|         relationships = [ResourceRelation(self, None, p) for p in predicates] | ||||
|         if sort == 'default': | ||||
|             sort = lambda x: (x.order, x.second.title.lower()) | ||||
|         return sorted(getRelations( | ||||
|                         first=self, second=resource, relationships=relationships), | ||||
|                       key=sort) | ||||
|         rels = (r for r in getRelations(self, resource, relationships=relationships) | ||||
|                   if canListObject(r.second, noSecurityCheck)) | ||||
|         return sorted(rels, key=sort) | ||||
| 
 | ||||
|     def getResources(self, predicates=None): | ||||
|         return [r.second for r in self.getResourceRelations(predicates)] | ||||
|     def getResources(self, predicates=None, sort='default', noSecurityCheck=False): | ||||
|         return [r.second for r in self.getResourceRelations(predicates, sort=sort, | ||||
|                                                 noSecurityCheck=noSecurityCheck)] | ||||
| 
 | ||||
|     def assignResource(self, resource, predicate=None, order=0, relevance=1.0): | ||||
|         if predicate is None: | ||||
|  | @ -241,12 +254,27 @@ class Concept(Contained, Persistent): | |||
|             rel.relevance = relevance | ||||
|         # TODO (?): avoid duplicates | ||||
|         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) | ||||
|         for rel in self.getResourceRelations(predicates, resource): | ||||
|             if order is None or rel.order == order: | ||||
|                 notify(DeassignmentEvent(self, 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 | ||||
| 
 | ||||
|  | @ -360,3 +388,22 @@ class IndexAttributes(object): | |||
|         return ' '.join((getName(context), | ||||
|                          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 --> | ||||
| 
 | ||||
|   <permission | ||||
|       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" /> | ||||
|   <include file="security.zcml" /> | ||||
| 
 | ||||
|   <!-- event subscribers --> | ||||
| 
 | ||||
|   <subscriber | ||||
|         for=".interfaces.ITargetRelation | ||||
|              cybertools.relation.interfaces.IRelationInvalidatedEvent" | ||||
|         handler=".util.removeTargetRelation" | ||||
|         /> | ||||
|         handler=".util.removeTargetRelation" /> | ||||
| 
 | ||||
|   <!-- loops top-level container --> | ||||
| 
 | ||||
|  | @ -120,22 +91,22 @@ | |||
|       type="zope.app.content.interfaces.IContentType" /> | ||||
| 
 | ||||
|   <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 | ||||
|        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 class="loops.concept.ConceptRelation"> | ||||
|     <require permission="zope.View" interface="loops.interfaces.IConceptRelation" /> | ||||
|     <require permission="zope.ManageContent" | ||||
|              set_schema="loops.interfaces.IConceptRelation" /> | ||||
|   </class> | ||||
| 
 | ||||
|   <class class="loops.concept.ResourceRelation"> | ||||
|     <require permission="zope.View" interface="loops.interfaces.IConceptRelation" /> | ||||
|     <require permission="zope.ManageContent" | ||||
|              set_schema="loops.interfaces.IConceptRelation" /> | ||||
|   </class> | ||||
| 
 | ||||
|   <!-- resource manager and resource --> | ||||
|  | @ -225,7 +196,6 @@ | |||
|         interface=".interfaces.IBaseResource | ||||
|                    zope.size.interfaces.ISized" /> | ||||
| 
 | ||||
| 
 | ||||
|     <require | ||||
|         permission="zope.ManageContent" | ||||
|         set_schema=".interfaces.IBaseResource" /> | ||||
|  | @ -398,6 +368,10 @@ | |||
|   <adapter factory="cybertools.composer.schema.field.FieldInstance" /> | ||||
|   <adapter factory="cybertools.composer.schema.field.NumberFieldInstance" | ||||
|            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" | ||||
|            name="fileupload" /> | ||||
| 
 | ||||
|  | @ -480,7 +454,9 @@ | |||
| 
 | ||||
|   <include package=".browser" /> | ||||
|   <include package=".classifier" /> | ||||
|   <include package=".compound.blog" /> | ||||
|   <include package=".constraint" /> | ||||
|   <include package=".external" /> | ||||
|   <include package=".i18n" /> | ||||
|   <include package=".integrator" /> | ||||
|   <include package=".knowledge" /> | ||||
|  | @ -488,6 +464,7 @@ | |||
|   <include package=".process" /> | ||||
|   <include package=".rest" /> | ||||
|   <include package=".search" /> | ||||
|   <include package=".security" /> | ||||
|   <include package=".versioning" /> | ||||
|   <include package=".xmlrpc" /> | ||||
| 
 | ||||
|  |  | |||
|  | @ -32,7 +32,7 @@ configuration): | |||
|   >>> #sorted(concepts) | ||||
|   >>> #sorted(resources) | ||||
|   >>> len(concepts) + len(resources) | ||||
|   35 | ||||
|   38 | ||||
| 
 | ||||
| 
 | ||||
| Type- and Text-based Queries | ||||
|  | @ -41,11 +41,11 @@ Type- and Text-based Queries | |||
|   >>> from loops.expert import query | ||||
|   >>> qu = query.Title('ty*') | ||||
|   >>> list(qu.apply()) | ||||
|   [0, 1, 41] | ||||
|   [0, 1, 50] | ||||
| 
 | ||||
|   >>> qu = query.Type('loops:*') | ||||
|   >>> len(list(qu.apply())) | ||||
|   35 | ||||
|   38 | ||||
| 
 | ||||
|   >>> qu = query.Type('loops:concept:predicate') | ||||
|   >>> len(list(qu.apply())) | ||||
|  | @ -67,7 +67,7 @@ syntax (that in turn is based on hurry.query). | |||
|   >>> stateNew = concepts['new'] | ||||
|   >>> qu = query.Resources(stateNew) | ||||
|   >>> list(qu.apply()) | ||||
|   [57, 62] | ||||
|   [66, 71] | ||||
| 
 | ||||
| 
 | ||||
| Fin de partie | ||||
|  |  | |||
|  | @ -5,63 +5,32 @@ $Id$ | |||
| """ | ||||
| 
 | ||||
| from zope import component | ||||
| from zope.app.catalog.catalog import Catalog | ||||
| 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 loops.base import Loops | ||||
| from loops import util | ||||
| from loops.interfaces import IIndexAttributes | ||||
| from loops.concept import Concept | ||||
| from loops.concept import IndexAttributes as ConceptIndexAttributes | ||||
| from loops.resource import Resource | ||||
| from loops.resource import IndexAttributes as ResourceIndexAttributes | ||||
| from loops.knowledge.setup import SetupManager as KnowledgeSetupManager | ||||
| from loops.setup import SetupManager, addObject | ||||
| from loops.tests.setup import TestSite as BaseTestSite | ||||
| from loops.type import ConceptType, ResourceType, TypeConcept | ||||
| 
 | ||||
| 
 | ||||
| class TestSite(object): | ||||
| class TestSite(BaseTestSite): | ||||
| 
 | ||||
|     def __init__(self, site): | ||||
|         self.site = site | ||||
| 
 | ||||
|     def setup(self): | ||||
|         super(TestSite, self).setup() | ||||
|         site = self.site | ||||
| 
 | ||||
|         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() | ||||
|         loopsRoot = site['loops'] | ||||
| 
 | ||||
|         component.provideAdapter(KnowledgeSetupManager, name='knowledge') | ||||
|         setup = SetupManager(loopsRoot) | ||||
|         concepts, resources, views = setup.setup() | ||||
| 
 | ||||
|         component.provideAdapter(ConceptIndexAttributes) | ||||
|         component.provideAdapter(ResourceIndexAttributes) | ||||
| 
 | ||||
|         tType = concepts.getTypeConcept() | ||||
|         tDomain = concepts['domain'] | ||||
|         tTextDocument = concepts['textdocument'] | ||||
|  | @ -123,6 +92,7 @@ class TestSite(object): | |||
|         d003.assignConcept(stateNew) | ||||
|         d003.assignConcept(dtStudy) | ||||
| 
 | ||||
|         catalog = component.getUtility(ICatalog) | ||||
|         for c in concepts.values(): | ||||
|              catalog.index_doc(int(util.getUidForObject(c)), c) | ||||
|         for r in resources.values(): | ||||
|  | @ -130,4 +100,3 @@ class TestSite(object): | |||
| 
 | ||||
|         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 | ||||
| #  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') | ||||
|         if lang is not None and lang in self.availableLanguages: | ||||
|             return lang | ||||
|         return (negotiator.getLanguage(self.availableLanguages, self.request) | ||||
|         available = self.availableLanguages or ('en', 'de',) | ||||
|         return (negotiator.getLanguage(available, self.request) | ||||
|                 or self.defaultLanguage) | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,8 +1,10 @@ | |||
| <!-- $Id$ --> | ||||
| 
 | ||||
| <metal:actions define-macro="language_switch" | ||||
|                tal:define="langInfo view/languageInfo"> | ||||
|     <tal:lang repeat="lang langInfo/availableLanguages"> | ||||
|                tal:define="langInfo view/languageInfo; | ||||
|                            available langInfo/availableLanguages" | ||||
|                tal:condition="python: len(available) > 1"> | ||||
|     <tal:lang repeat="lang available"> | ||||
|       <a href="#" | ||||
|          tal:attributes="href string:switch_language?loops.language=$lang&keep=yes; | ||||
|                          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.file.interfaces import IImage as IBaseAsset | ||||
| from zope.app.folder.interfaces import IFolder | ||||
| from zope.component.interfaces import IObjectEvent | ||||
| from zope.size.interfaces import ISized | ||||
| from cybertools.relation.interfaces import IRelation | ||||
| from cybertools.relation.interfaces import IDyadicRelation | ||||
| 
 | ||||
| import util | ||||
| from util import _ | ||||
|  | @ -563,16 +564,19 @@ class ILoopsContained(Interface): | |||
| 
 | ||||
| # relation interfaces | ||||
| 
 | ||||
| class ITargetRelation(IRelation): | ||||
| class ITargetRelation(IDyadicRelation): | ||||
|     """ (Marker) interfaces for relations pointing to a target | ||||
|         of a view or node. | ||||
|     """ | ||||
| 
 | ||||
| 
 | ||||
| class IConceptRelation(IRelation): | ||||
| class IConceptRelation(IDyadicRelation): | ||||
|     """ (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 | ||||
| 
 | ||||
|  | @ -685,6 +689,22 @@ class INote(ITextDocument): | |||
|         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 | ||||
| 
 | ||||
| class IViewConfiguratorSchema(Interface): | ||||
|  |  | |||
|  | @ -26,7 +26,7 @@ $Id$ | |||
| from zope.cachedescriptors.property import Lazy | ||||
| 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.form import CreateConceptForm, EditConceptForm | ||||
| from loops.browser.form import CreateConcept, EditConcept | ||||
|  | @ -107,6 +107,7 @@ class EditGlossaryItemForm(EditConceptForm, ConceptView): | |||
| 
 | ||||
| class CreateGlossaryItemForm(CreateConceptForm, EditGlossaryItemForm): | ||||
| 
 | ||||
|     title = _(u'Create Glossary Item') | ||||
|     form_action = 'create_glossaryitem' | ||||
| 
 | ||||
|     def children(self): | ||||
|  |  | |||
|  | @ -8,26 +8,26 @@ | |||
|     <span tal:repeat="letter python: [chr(c) for c in range(ord('A'), ord('Z')+1)]" | ||||
|           class="navlink"> | ||||
|       <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:content="letter">A</a> | ||||
|     </span> | ||||
|   </div> | ||||
|   <div> </div> | ||||
|   <div tal:repeat="letter data/letters"> | ||||
|   <div tal:repeat="letter data/keys"> | ||||
|     <div class="subtitle"><a name="A" href="#top" | ||||
|            tal:attributes="name letter; | ||||
|                            href string:${request/URL/-1}#top" | ||||
|            tal:content="letter">A</a> | ||||
|     </div> | ||||
|     <div tal:repeat="related data/relations/?letter|python:[]"> | ||||
|     <div tal:repeat="related data/?letter|python:[]"> | ||||
|       <a href="#" | ||||
|          tal:content="related/title" | ||||
|          tal:attributes="href python: view.getUrlForTarget(related); | ||||
|                          title related/description"> | ||||
|         Topic | ||||
|       </a> | ||||
|       <span tal:define="translations related/adapted/translations" | ||||
|       <span tal:define="translations related/adapted/translations|python:[]" | ||||
|             tal:condition="translations"> | ||||
|         (<tal:trans repeat="trans translations"><a href="#" | ||||
|              tal:attributes="href python: '%s?loops.language=%s' % | ||||
|  | @ -44,7 +44,7 @@ | |||
| 
 | ||||
| <metal:block define-macro="glossaryitem"> | ||||
|   <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"> | ||||
|     <span i18n:translate="">Translations</span>: | ||||
|     <tal:trans repeat="trans translations"><a href="#" | ||||
|  | @ -98,10 +98,12 @@ | |||
|       <input type="hidden" | ||||
|              id="child.search.predicate" | ||||
|              tal:attributes="value view/relatedPredicateUid" /> | ||||
|       <input dojoType="comboBox" mode="remote" autoComplete="False" | ||||
|              name="child.search.text" id="child.search.text" | ||||
|              tal:attributes="dataUrl | ||||
|                 string:listConceptsForComboBox.js?searchString=%{searchString}&searchType=loops:concept:glossaryitem" /> | ||||
|       <div dojoType="dojox.data.QueryReadStore" jsId="childSearch" | ||||
|            url="listConceptsForComboBox.js?searchType=loops:concept:glossaryitem" > | ||||
|       </div> | ||||
|       <input dojoType="dijit.form.FilteringSelect" store="childSearch" | ||||
|              autoComplete="False" labelAttr="label" | ||||
|              name="child.search.text" id="child.search.text" /> | ||||
|     </td> | ||||
|     <td> | ||||
|       <input type="button" value="Select" | ||||
|  |  | |||
										
											Binary file not shown.
										
									
								
							|  | @ -3,7 +3,7 @@ msgstr "" | |||
| 
 | ||||
| "Project-Id-Version: $Id$\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" | ||||
| "Language-Team: loops developers <helmutm@cy55.de>\n" | ||||
| "MIME-Version: 1.0\n" | ||||
|  | @ -17,12 +17,54 @@ msgstr "Begriff" | |||
| msgid "Resource" | ||||
| msgstr "Ressource" | ||||
| 
 | ||||
| msgid "Log out" | ||||
| msgstr "Abmelden" | ||||
| 
 | ||||
| msgid "Create" | ||||
| msgstr "Anlegen" | ||||
| 
 | ||||
| msgid "Edit Concept Map" | ||||
| msgstr "Concept Map bearbeiten" | ||||
| 
 | ||||
| msgid "Create Resource..." | ||||
| 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" | ||||
| msgstr "Aktionen" | ||||
| 
 | ||||
|  | @ -47,9 +89,15 @@ msgstr "Unterbegriffe" | |||
| msgid "Title" | ||||
| msgstr "Titel" | ||||
| 
 | ||||
| msgid "Title of the concept" | ||||
| msgstr "Überschrift, sprechende Bezeichnung des Begriffs" | ||||
| 
 | ||||
| msgid "Description" | ||||
| 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" | ||||
| msgstr "Verwandte Begriffe" | ||||
| 
 | ||||
|  | @ -224,4 +272,30 @@ msgstr "Die Passwort-Wiederholung stimmt nicht mit dem eingegebenen Passwort üb | |||
| msgid "Your password has been changed." | ||||
| 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'] | ||||
|   >>> loopsId = util.getUidForObject(loopsRoot) | ||||
| 
 | ||||
|   >>> from loops.organize.tests import setupUtilitiesAndAdapters | ||||
|   >>> setupData = setupUtilitiesAndAdapters(loopsRoot) | ||||
| 
 | ||||
|   >>> type = concepts['type'] | ||||
|   >>> person = concepts['person'] | ||||
| 
 | ||||
|  | @ -39,10 +42,7 @@ Organizations: Persons (and Users), Institutions, Addresses... | |||
| 
 | ||||
| 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.party import Person | ||||
|   >>> component.provideAdapter(Person, (IConcept,), IPerson) | ||||
| 
 | ||||
|   >>> john = IPerson(johnC) | ||||
|   >>> 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 | ||||
| (in real life this is all done during Zope startup): | ||||
| 
 | ||||
|   >>> from zope.app.security.interfaces import IAuthentication | ||||
|   >>> from zope.app.security.principalregistry import PrincipalRegistry | ||||
|   >>> 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) | ||||
|   >>> auth = setupData.auth | ||||
|   >>> principalAnnotations = setupData.principalAnnotations | ||||
| 
 | ||||
|   >>> principal = auth.definePrincipal('users.john', u'John', login='john') | ||||
|   >>> john.userId = 'users.john' | ||||
|  | @ -116,6 +109,7 @@ principal annotation: | |||
|   >>> from zope.app.container.contained import ObjectRemovedEvent | ||||
|   >>> from zope.event import notify | ||||
|   >>> from zope.interface import Interface | ||||
|   >>> from loops.interfaces import IConcept | ||||
|   >>> from loops.organize.party import removePersonReferenceFromPrincipal | ||||
|   >>> from zope.app.testing import ztapi | ||||
|   >>> ztapi.subscribe([IConcept, IObjectRemovedEvent], None, | ||||
|  | @ -157,6 +151,7 @@ with a principal folder: | |||
| 
 | ||||
|   >>> from zope.app.appsetup.bootstrap import ensureUtility | ||||
|   >>> from zope.app.authentication.authentication import PluggableAuthentication | ||||
|   >>> from zope.app.security.interfaces import IAuthentication | ||||
|   >>> ensureUtility(site, IAuthentication, '', PluggableAuthentication, | ||||
|   ...               copy_to_zlog=False, asObject=True) | ||||
|   <...PluggableAuthentication...> | ||||
|  | @ -212,7 +207,6 @@ Now we can also retrieve it from the authentication utility: | |||
|   >>> pau.getPrincipal('loops.newuser').title | ||||
|   u'Tom Sawyer' | ||||
| 
 | ||||
| 
 | ||||
| Change Password | ||||
| --------------- | ||||
| 
 | ||||
|  | @ -229,18 +223,100 @@ We need a principal for testing the login stuff: | |||
|   >>> principal = InternalPrincipal('scott', 'tiger', 'Scotty') | ||||
|   >>> 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 | ||||
|   >>> pwcView = PasswordChange(menu, request) | ||||
|   >>> pwcView.update() | ||||
|   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 | ||||
| ============= | ||||
| 
 | ||||
|   >>> placefulTearDown() | ||||
| 
 | ||||
|  |  | |||
|  | @ -93,6 +93,13 @@ | |||
| 
 | ||||
|   <!-- 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" | ||||
|                 name="organize" /> | ||||
| 
 | ||||
|  |  | |||
|  | @ -30,7 +30,8 @@ from zope.app.security.interfaces import IAuthentication, PrincipalLookupError | |||
| from zope.security.proxy import removeSecurityProxy | ||||
| 
 | ||||
| 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 _ | ||||
| 
 | ||||
| ANNOTATION_KEY = 'loops.organize.person' | ||||
|  | @ -83,7 +84,7 @@ class LoginName(schema.TextLine): | |||
|                   mapping=dict(userId=userId))) | ||||
| 
 | ||||
| 
 | ||||
| class IPerson(IBasePerson): | ||||
| class IPerson(IConceptSchema, IBasePerson): | ||||
|     """ Resembles a human being with a name (first and last name), | ||||
|         a birth date, and a set of addresses. This interface only | ||||
|         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 | ||||
| #  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 cybertools.typology.interfaces import IType | ||||
| from loops.interfaces import ILoops | ||||
| from loops.common import adapted | ||||
| from loops.concept import Concept | ||||
| from loops.interfaces import ILoops | ||||
| 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 _ | ||||
| 
 | ||||
| 
 | ||||
|  | @ -51,29 +54,54 @@ class MemberRegistrationManager(object): | |||
|     def __init__(self, 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: | ||||
|         pFolder = getPrincipalFolder(self.context) | ||||
|         title = firstName and ' '.join((firstName, lastName)) or lastName | ||||
|         principal = InternalPrincipal(userId, password, title) | ||||
|         if useExisting: | ||||
|             if userId not in pFolder: | ||||
|                 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() | ||||
|         id = baseId = 'person.' + userId | ||||
|         # TODO: use NameChooser | ||||
|         if useExisting and id in cm: | ||||
|             person = cm[id] | ||||
|         else: | ||||
|             num = 0 | ||||
|             while id in cm: | ||||
|                 num +=1 | ||||
|                 id = baseId + str(num) | ||||
|             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'] | ||||
|         personAdapter = IType(person).typeInterface(person) | ||||
|         personAdapter = adapted(person) | ||||
|         personAdapter.firstName = firstName | ||||
|         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(ObjectModifiedEvent(person)) | ||||
|         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.relation.interfaces import IRelationRegistry | ||||
| from cybertools.typology.interfaces import IType | ||||
| from loops.common import AdapterBase | ||||
| from loops.concept import Concept | ||||
| from loops.interfaces import IConcept | ||||
| 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 import util | ||||
| 
 | ||||
|  | @ -54,6 +55,8 @@ TypeInterfaceSourceList.typeInterfaces += (IPerson, IAddress) | |||
| def getPersonForUser(context, request=None, principal=None): | ||||
|     if principal is None: | ||||
|         principal = request.principal | ||||
|     if principal is None: | ||||
|         return None | ||||
|     loops = context.getLoopsRoot() | ||||
|     pa = annotations(principal).get(ANNOTATION_KEY, None) | ||||
|     if pa is None: | ||||
|  | @ -88,13 +91,16 @@ class Person(AdapterBase, BasePerson): | |||
|             pa = annotations(principal) | ||||
|             loopsId = util.getUidForObject(self.context.getLoopsRoot()) | ||||
|             ann = pa.get(ANNOTATION_KEY) | ||||
|             if ann is None: | ||||
|             if ann is None: # or not isinstance(ann, PersistentMapping): | ||||
|                 ann = pa[ANNOTATION_KEY] = PersistentMapping() | ||||
|             ann[loopsId] = self.context | ||||
|             assignOwner(self.context, userId) | ||||
|         oldUserId = self.userId | ||||
|         if oldUserId and oldUserId != userId: | ||||
|             self.removeReferenceFromPrincipal(oldUserId) | ||||
|             removeOwner(self.context, oldUserId) | ||||
|         self.context._userId = userId | ||||
|         allowEditingForOwner(self.context, revert=not userId) | ||||
|     userId = property(getUserId, setUserId) | ||||
| 
 | ||||
|     def removeReferenceFromPrincipal(self, userId): | ||||
|  |  | |||
|  | @ -4,8 +4,35 @@ import unittest, doctest | |||
| from zope.testing.doctestunit import DocFileSuite | ||||
| from zope.app.testing import ztapi | ||||
| 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 | ||||
| 
 | ||||
| 
 | ||||
| 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): | ||||
|     "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.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) | ||||
|     if not IPluggableAuthentication.providedBy(pau): | ||||
|         if ignoreErrors: | ||||
|             return None | ||||
|         raise ValueError(u'There is no pluggable authentication ' | ||||
|                           'utility available.') | ||||
|     if not authPluginId in pau.authenticatorPlugins: | ||||
|         raise ValueError(u'There is no loops authenticator ' | ||||
|                           'plugin available.') | ||||
|     if authPluginId is None and context is not None: | ||||
|         person = context.getLoopsRoot().getConceptManager()['person'] | ||||
|         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(): | ||||
|         if name == authPluginId: | ||||
|             return plugin | ||||
| 
 | ||||
| 
 | ||||
| def getGroupsFolder(context=None, name='gloops'): | ||||
|     return getPrincipalFolder(authPluginId=name, ignoreErrors=True) | ||||
| 
 | ||||
| 
 | ||||
| def getInternalPrincipal(id, context=None): | ||||
|     pau = component.getUtility(IAuthentication, context=context) | ||||
|     if not IPluggableAuthentication.providedBy(pau): | ||||
|  |  | |||
							
								
								
									
										8
									
								
								query.py
									
										
									
									
									
								
							
							
						
						
									
										8
									
								
								query.py
									
										
									
									
									
								
							|  | @ -24,13 +24,13 @@ $Id$ | |||
| 
 | ||||
| from zope import schema, component | ||||
| from zope.interface import Interface, Attribute, implements | ||||
| from zope import traversing | ||||
| from zope.app.catalog.interfaces import ICatalog | ||||
| from zope.cachedescriptors.property import Lazy | ||||
| 
 | ||||
| from cybertools.typology.interfaces import IType | ||||
| from loops.interfaces import IConcept, IConceptSchema | ||||
| from loops.common import AdapterBase | ||||
| from loops.interfaces import IConcept, IConceptSchema | ||||
| from loops.security.common import canListObject | ||||
| from loops.type import TypeInterfaceSourceList | ||||
| from loops.versioning.util import getVersion | ||||
| from loops import util | ||||
|  | @ -72,7 +72,8 @@ class BaseQuery(object): | |||
|             result = cat.searchResults(loops_type=(start, end), loops_title=title) | ||||
|         else: | ||||
|             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: | ||||
|             r1 = set() | ||||
|             for r in result: | ||||
|  | @ -139,6 +140,7 @@ class FullQuery(BaseQuery): | |||
|                 result = rc | ||||
|         result = set(r for r in result | ||||
|                             if r.getLoopsRoot() == self.loopsRoot | ||||
|                                and canListObject(r) | ||||
|                                and getVersion(r) == r) | ||||
|         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 | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
|  | @ -22,6 +22,8 @@ Definition of the Concept class. | |||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| from cStringIO import StringIO | ||||
| from persistent import Persistent | ||||
| from zope import component, schema | ||||
| from zope.app import zapi | ||||
| from zope.app.container.btree import BTreeContainer | ||||
|  | @ -38,9 +40,6 @@ from zope.interface import implements | |||
| from zope.size.interfaces import ISized | ||||
| from zope.security.proxy import removeSecurityProxy | ||||
| from zope.traversing.api import getName, getParent | ||||
| from persistent import Persistent | ||||
| from cStringIO import StringIO | ||||
| 
 | ||||
| from zope.lifecycleevent import ObjectModifiedEvent, Attributes | ||||
| from zope.event import notify | ||||
| 
 | ||||
|  | @ -50,7 +49,6 @@ from cybertools.storage.interfaces import IExternalStorage | |||
| from cybertools.text.interfaces import ITextTransform | ||||
| from cybertools.typology.interfaces import IType, ITypeManager | ||||
| from cybertools.util.jeep import Jeep | ||||
| 
 | ||||
| from loops.base import ParentInfo | ||||
| from loops.common import ResourceAdapterBase, adapted | ||||
| from loops.concept import ResourceRelation | ||||
|  | @ -62,6 +60,7 @@ from loops.interfaces import IResourceManager, IResourceManagerContained | |||
| from loops.interfaces import ITypeConcept | ||||
| from loops.interfaces import ILoopsContained | ||||
| from loops.interfaces import IIndexAttributes | ||||
| from loops.security.common import canListObject | ||||
| from loops import util | ||||
| from loops.versioning.util import getMaster | ||||
| from loops.view import TargetRelation | ||||
|  | @ -189,20 +188,24 @@ class Resource(Image, Contained): | |||
|             relationships = [TargetRelation] | ||||
|         obj = getMaster(self)  # use the master version for relations | ||||
|         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 | ||||
|         obj = getMaster(self) | ||||
|         relationships = [ResourceRelation(None, obj, p) for p in predicates] | ||||
|         if sort == 'default': | ||||
|             sort = lambda x: (x.order, x.first.title.lower()) | ||||
|         return sorted(getRelations(first=concept, second=obj, relationships=relationships), | ||||
|                             key=sort) | ||||
|         rels = (r for r in getRelations(first=concept, second=obj, | ||||
|                                         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) | ||||
|         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): | ||||
|         obj = getMaster(self) | ||||
|  | @ -372,6 +375,8 @@ class ExternalFileAdapter(FileAdapter): | |||
|         self.storageName = storageName | ||||
| 
 | ||||
|     def getData(self): | ||||
|         if self.storageName == 'unknown':    # object not set up yet | ||||
|             return '' | ||||
|         storage = component.getUtility(IExternalStorage, name=self.storageName) | ||||
|         return storage.getData(self.externalAddress, params=self.storageParams) | ||||
| 
 | ||||
|  |  | |||
|  | @ -35,6 +35,8 @@ class ResourceSchemaFactory(SchemaFactory): | |||
|     def __call__(self, interface, **kw): | ||||
|         schema = super(ResourceSchemaFactory, self).__call__(interface, **kw) | ||||
|         schema.fields.data.height = 10 | ||||
|         if self.context.contentType == 'text/html': | ||||
|             schema.fields.data.fieldType = 'html' | ||||
|         return schema | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -90,8 +90,8 @@ a controller attribute for the search view. | |||
|   >>> searchView.controller = Controller(searchView, request) | ||||
| 
 | ||||
|   >>> searchView.submitReplacing('1.results', '1.search.form', pageView) | ||||
|   'return submitReplacing("1.results", "1.search.form", | ||||
|        "http://127.0.0.1/loops/views/page/.target29/@@searchresults.html")' | ||||
|   'submitReplacing("1.results", "1.search.form", | ||||
|        "http://127.0.0.1/loops/views/page/.target29/@@searchresults.html");...' | ||||
| 
 | ||||
| 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 | ||||
| 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) | ||||
|   >>> resultsView = SearchResults(page, request) | ||||
|   >>> results = list(resultsView.results) | ||||
|  | @ -173,7 +175,8 @@ with them: | |||
|   >>> results[0].context.__name__ | ||||
|   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) | ||||
|   >>> resultsView = SearchResults(page, request) | ||||
|   >>> 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 | ||||
| of the concepts' titles: | ||||
| 
 | ||||
|   >>> form = {'searchType': 'loops:concept:topic', 'searchString': u'zope'} | ||||
|   >>> form = {'searchType': 'loops:concept:topic', 'name': u'zope'} | ||||
|   >>> request = TestRequest(form=form) | ||||
|   >>> view = Search(page, request) | ||||
|   >>> view.listConcepts() | ||||
|   "[['Zope (Topic)', '33']]" | ||||
|   u"{identifier: 'id', items: [{label: 'Zope (Topic)', name: 'Zope', id: '33'}]}" | ||||
| 
 | ||||
| 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 | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
|  | @ -83,30 +83,56 @@ class Search(BaseView): | |||
|     def initDojo(self): | ||||
|         self.registerDojo() | ||||
|         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) | ||||
| 
 | ||||
|     def listConcepts(self): | ||||
|         """ Used for dojo.widget.ComboBox. | ||||
|         """ Used for dijit.FilteringSelect. | ||||
|         """ | ||||
|         request = self.request | ||||
|         request.response.setHeader('Content-Type', 'text/plain; charset=UTF-8') | ||||
|         title = request.get('searchString', '').replace('(', ' ').replace(')', ' ') | ||||
|         type = request.get('searchType') or 'loops:concept:*' | ||||
|         result = ConceptQuery(self).query(title=title, type=type, exclude=('system',)) | ||||
|         #registry = component.getUtility(IRelationRegistry) | ||||
|         # simple way to provide JSON format: | ||||
|         return str(sorted([[`adapted(o, self.languageInfo).title`[2:-1] | ||||
|                                 + ' (%s)' % `o.conceptType.title`[2:-1], | ||||
|                             `int(util.getUidForObject(o))`] | ||||
|                         for o in result | ||||
|                         if o.getLoopsRoot() == self.loopsRoot])).replace('\\\\x', '\\x') | ||||
|         #return str(sorted([[`o.title`[2:-1], `traversing.api.getName(o)`[2:-1]] | ||||
|         #                for o in result])).replace('\\\\x', '\\x') | ||||
|         title = request.get('name') | ||||
|         if title == '*': | ||||
|             title = None | ||||
|         type = request.get('searchType') | ||||
|         data = [] | ||||
|         if title or type: | ||||
|             if title is not None: | ||||
|                 title = title.replace('(', ' ').replace(')', ' ').replace(' -', ' ') | ||||
|                 #title = title.split(' ', 1)[0] | ||||
|             if not type: | ||||
|                 type = 'loops:concept:*' | ||||
|             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): | ||||
|         self.registerDojo() | ||||
|         return 'return submitReplacing("%s", "%s", "%s")' % ( | ||||
|         return 'submitReplacing("%s", "%s", "%s"); return false;' % ( | ||||
|                     targetId, formId, | ||||
|                     '%s/.target%s/@@searchresults.html' % (view.url, self.uniqueId)) | ||||
| 
 | ||||
|  | @ -131,13 +157,14 @@ class SearchResults(BaseView): | |||
|         useTitle = form.get('search.2.title') | ||||
|         useFull = form.get('search.2.full') | ||||
|         conceptType = form.get('search.3.type', 'loops:concept:*') | ||||
|         conceptTitle = form.get('search.3.text') | ||||
|         if conceptTitle is not None: | ||||
|             conceptTitle = util.toUnicode(conceptTitle, encoding='ISO8859-15') | ||||
|         conceptUid = form.get('search.3.text_selected') | ||||
|         #conceptTitle = form.get('search.3.text') | ||||
|         #if conceptTitle is not None: | ||||
|         #    conceptTitle = util.toUnicode(conceptTitle, encoding='ISO8859-15') | ||||
|         conceptUid = form.get('search.3.text') | ||||
|         result = FullQuery(self).query(text=text, type=type, | ||||
|                            useTitle=useTitle, useFull=useFull, | ||||
|                            conceptTitle=conceptTitle, conceptUid=conceptUid, | ||||
|                            #conceptTitle=conceptTitle, | ||||
|                            conceptUid=conceptUid, | ||||
|                            conceptType=conceptType) | ||||
|         rowNum = 4 | ||||
|         while rowNum < 10: | ||||
|  |  | |||
|  | @ -4,11 +4,11 @@ | |||
|                    idPrefix string:${view/itemNum}.search; | ||||
|                    formId string:$idPrefix.form; | ||||
|                    resultsId string:$idPrefix.results"> | ||||
|     <h3 tal:attributes="class string:content-$level; | ||||
|     <h1 tal:attributes="class string:content-$level; | ||||
|                         ondblclick item/openEditWindow" | ||||
|       tal:content="item/title"> | ||||
|       Search | ||||
|     </h3> | ||||
|     </h1> | ||||
| 
 | ||||
|     <div metal:define-macro="search_form" class="searchForm"> | ||||
|       <fieldset class="box"> | ||||
|  | @ -46,7 +46,7 @@ | |||
|          i18n:domain="loops"> | ||||
|       <fieldset class="box" | ||||
|                 tal:condition="request/search.submitted | nothing"> | ||||
|         <legend i18n:translate="">Search results</legend> | ||||
|         <h2 i18n:translate="">Search results</h2> | ||||
|         <table class="listing" summary="Search results" | ||||
|                i18n:attributes="summary"> | ||||
|           <thead> | ||||
|  | @ -113,7 +113,7 @@ | |||
|   <tr> | ||||
|     <td metal:use-macro="macros/minus"/> | ||||
|     <td colspan="3"> | ||||
|       <h3 i18n:translate="">Type(s) to search for</h3> | ||||
|       <h2 i18n:translate="">Type(s) to search for</h2> | ||||
|     </td> | ||||
|   <tr> | ||||
|     <td></td> | ||||
|  | @ -144,7 +144,7 @@ | |||
|   <tr> | ||||
|     <td metal:use-macro="macros/minus"/> | ||||
|     <td colspan="3"> | ||||
|       <h3 i18n:translate="">Text-based search</h3> | ||||
|       <h2 i18n:translate="">Text-based search</h2> | ||||
|     </td> | ||||
|   <tr> | ||||
|     <td></td> | ||||
|  | @ -185,7 +185,7 @@ | |||
|   <tr> | ||||
|     <td metal:use-macro="macros/minus"/> | ||||
|     <td colspan="3"> | ||||
|       <h3 i18n:translate="">Search via related concepts</h3> | ||||
|       <h2 i18n:translate="">Search via related concepts</h2> | ||||
|     </td> | ||||
|   <tr tal:repeat="type item/presetSearchTypes"> | ||||
|     <tal:preset define="rowNum item/rowNum; | ||||
|  | @ -249,10 +249,14 @@ | |||
|     </td> | ||||
|     <td> | ||||
|       <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; | ||||
|                                id string:$idPrefix.text; | ||||
|                                dataUrl string:${context/@@absolute_url}/listConceptsForComboBox.js?searchString=%{searchString}&searchType=" /> | ||||
|                                id string:$idPrefix.text" /> | ||||
|       </tal:combo> | ||||
|     </td> | ||||
|   </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 | ||||
| #  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.event import notify | ||||
| from zope import component | ||||
| from zope.cachedescriptors.property import Lazy | ||||
| from zope.component import adapts | ||||
| from zope.interface import implements, Interface | ||||
| from zope.traversing.api import getName | ||||
| 
 | ||||
| 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 IFile, IImage, ITextDocument, INote | ||||
| from loops.concept import ConceptManager, Concept | ||||
| from loops.query import IQueryConcept | ||||
| from loops.resource import ResourceManager, Resource | ||||
| from loops.view import ViewManager, Node | ||||
|  | @ -79,7 +82,6 @@ class SetupManager(object): | |||
|         domain = self.addObject(conceptManager, Concept, 'domain', title=u'Domain') | ||||
|         query = self.addObject(conceptManager, Concept, 'query', title=u'Query') | ||||
|         file = self.addObject(conceptManager, Concept, 'file', title=u'File') | ||||
|         #image = self.addObject(conceptManager, Concept, 'image', title=u'Image') | ||||
|         textdocument = self.addObject(conceptManager, Concept, | ||||
|                                       'textdocument', title=u'Text') | ||||
|         note = self.addObject(conceptManager, Concept, 'note', title=u'Note') | ||||
|  | @ -89,13 +91,105 @@ class SetupManager(object): | |||
|         ITypeConcept(typeConcept).typeInterface = ITypeConcept | ||||
|         ITypeConcept(query).typeInterface = IQueryConcept | ||||
|         ITypeConcept(file).typeInterface = IFile | ||||
|         #ITypeConcept(image).typeInterface = IImage | ||||
|         ITypeConcept(textdocument).typeInterface = ITextDocument | ||||
|         ITypeConcept(note).typeInterface = INote | ||||
|         ITypeConcept(note).viewName = 'note.html' # leads to error in DocTest | ||||
|         ITypeConcept(note).viewName = 'note.html' | ||||
|         hasType.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): | ||||
|         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.annotation.attribute import AttributeAnnotations | ||||
| from zope.annotation.interfaces import IAnnotatable | ||||
| from zope.app.catalog.catalog import Catalog | ||||
| from zope.app.catalog.interfaces import ICatalog | ||||
| from zope.app.catalog.field import FieldIndex | ||||
| from zope.app.catalog.text import TextIndex | ||||
| 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.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 import session | ||||
| from zope.dublincore.annotatableadapter import ZDCAnnotatableAdapter | ||||
| 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.field import FieldInstance, NumberFieldInstance | ||||
| from cybertools.composer.schema.field import DateFieldInstance, BooleanFieldInstance | ||||
| from cybertools.composer.schema.instance import Instance, Editor | ||||
| from cybertools.relation.tests import IntIdsStub | ||||
| from cybertools.relation.registry import RelationRegistry | ||||
|  | @ -34,14 +44,21 @@ from loops.base import Loops | |||
| from loops import util | ||||
| from loops.browser.node import ViewPropertiesConfigurator | ||||
| 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 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.resource import Resource, FileAdapter, TextDocumentAdapter | ||||
| from loops.resource import Document, MediaAsset | ||||
| from loops.resource import IndexAttributes as ResourceIndexAttributes | ||||
| 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.type import LoopsType, ConceptType, ResourceType, TypeConcept | ||||
| from loops.view import NodeAdapter | ||||
|  | @ -62,14 +79,25 @@ class TestSite(object): | |||
|     def baseSetup(self): | ||||
|         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()) | ||||
|         relations = RelationRegistry() | ||||
|         relations.setupIndexes() | ||||
|         component.provideUtility(relations, IRelationRegistry) | ||||
|         component.provideAdapter(IndexableRelationAdapter) | ||||
| 
 | ||||
|         component.provideUtility(PrincipalAnnotationUtility(), IPrincipalAnnotationUtility) | ||||
|         component.provideAdapter(IndexableRelationAdapter) | ||||
|         component.provideAdapter(ZDCAnnotatableAdapter, (ILoopsObject,), IZopeDublinCore) | ||||
|         component.provideAdapter(AttributeAnnotations, (ILoopsObject,)) | ||||
|         component.provideAdapter(AnnotationPrincipalRoleManager, (ILoopsObject,)) | ||||
|         component.provideAdapter(AnnotationRolePermissionManager, (ILoopsObject,)) | ||||
|         component.provideUtility(principalRegistry, IAuthentication) | ||||
|         component.provideAdapter(session.ClientId) | ||||
|         component.provideAdapter(session.Session) | ||||
|  | @ -86,16 +114,26 @@ class TestSite(object): | |||
|         component.provideAdapter(NodeAdapter) | ||||
|         component.provideAdapter(ViewPropertiesConfigurator) | ||||
|         component.provideAdapter(NameChooser) | ||||
|         component.provideHandler(grantAcquiredSecurity) | ||||
|         component.provideHandler(revokeAcquiredSecurity) | ||||
|         component.provideAdapter(BaseSecuritySetter) | ||||
| 
 | ||||
|         component.provideAdapter(Instance) | ||||
|         component.provideAdapter(Editor, name='editor') | ||||
|         component.provideAdapter(FieldInstance) | ||||
|         component.provideAdapter(NumberFieldInstance, name='number') | ||||
| 
 | ||||
|         component.provideAdapter(DateFieldInstance, name='date') | ||||
|         component.provideAdapter(BooleanFieldInstance, name='boolean') | ||||
|         component.provideAdapter(SchemaFactory) | ||||
|         component.provideAdapter(ResourceSchemaFactory) | ||||
|         component.provideAdapter(FileSchemaFactory) | ||||
|         component.provideAdapter(NoteSchemaFactory) | ||||
| 
 | ||||
|         component.provideAdapter(Controller, (Interface, IBrowserRequest), | ||||
|                             IBrowserView, name='controller') | ||||
|         component.provideAdapter(MemberInfoProvider, | ||||
|                                  (ILoopsObject, IBrowserRequest)) | ||||
| 
 | ||||
|         component.getSiteManager().registerHandler(invalidateRelations, | ||||
|                             (ILoopsObject, IObjectRemovedEvent)) | ||||
|         component.getSiteManager().registerHandler(removeRelation, | ||||
|  |  | |||
							
								
								
									
										20
									
								
								type.py
									
										
									
									
									
								
							
							
						
						
									
										20
									
								
								type.py
									
										
									
									
									
								
							|  | @ -88,7 +88,6 @@ class LoopsType(BaseType): | |||
|         addQualifiers = self.optionsDict.get('qualifier') | ||||
|         if addQualifiers: | ||||
|             qu.extend(addQualifiers.split(',')) | ||||
|         # how to set a type to 'hidden'? | ||||
|         return tuple(qu) | ||||
| 
 | ||||
|     @Lazy | ||||
|  | @ -112,14 +111,7 @@ class LoopsType(BaseType): | |||
| 
 | ||||
|     @Lazy | ||||
|     def optionsDict(self): | ||||
|         result = {'default': []} | ||||
|         for opt in self.options: | ||||
|             if ':' in opt: | ||||
|                 key, value = opt.split(':', 1) | ||||
|                 result[key] = value | ||||
|             else: | ||||
|                 result['default'].append(opt) | ||||
|         return result | ||||
|         return getOptionsDict(self.options) | ||||
| 
 | ||||
|     # general infos | ||||
| 
 | ||||
|  | @ -291,3 +283,13 @@ class TypeInterfaceSourceList(object): | |||
|     def __len__(self): | ||||
|         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.i18nmessageid import MessageFactory | ||||
| from zope.schema import vocabulary | ||||
| #from view import TargetRelation | ||||
| 
 | ||||
| from loops.browser.util import html_quote | ||||
| 
 | ||||
| _ = MessageFactory('loops') | ||||
| #_ = 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): | ||||
| 
 | ||||
|     def __init__(self, items, *interfaces): | ||||
|  |  | |||
|  | @ -48,6 +48,7 @@ class VersionableResource(object): | |||
| 
 | ||||
|     def __init__(self, context): | ||||
|         self.context = context | ||||
|         self.__parent__ = context | ||||
| 
 | ||||
|     def getVersioningAttribute(self, attr, default): | ||||
|         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 | ||||
| #  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.contained import Contained | ||||
| 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.cachedescriptors.property import Lazy, readproperty | ||||
| from zope.component import adapts | ||||
| from zope.interface import implements | ||||
| from zope.interface import alsoProvides, directlyProvides, directlyProvidedBy | ||||
| from zope.publisher.browser import applySkin | ||||
| from zope.security.proxy import removeSecurityProxy | ||||
| from persistent import Persistent | ||||
| from cybertools.relation import DyadicRelation | ||||
|  |  | |||
|  | @ -8,57 +8,39 @@ Let's do some basic set up | |||
| 
 | ||||
|   >>> from zope.app.testing.setup import placefulSetUp, placefulTearDown | ||||
|   >>> site = placefulSetUp(True) | ||||
| 
 | ||||
|   >>> from zope import component, interface | ||||
|   >>> from zope.publisher.browser import TestRequest | ||||
|   >>> from loops.concept import Concept | ||||
|   >>> from loops.resource import Resource | ||||
|   >>> | ||||
| 
 | ||||
| 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 | ||||
| ZCML setup): | ||||
| 
 | ||||
|   >>> from cybertools.relation.registry import DummyRelationRegistry | ||||
|   >>> 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.setup import addObject | ||||
|   >>> from loops.organize.setup import SetupManager as OrganizeSetupManager | ||||
|   >>> component.provideAdapter(OrganizeSetupManager, name='organize') | ||||
|   >>> setup = SetupManager(loopsRoot) | ||||
|   >>> concepts, resources, views = setup.setup() | ||||
|   >>> from loops.knowledge.setup import SetupManager as KnowledgeSetupManager | ||||
|   >>> 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: | ||||
| 
 | ||||
|   >>> sorted(concepts) | ||||
|   [u'domain', u'file', u'hasType', u'note', u'ownedby', u'person', | ||||
|    u'predicate', u'query', u'standard', u'textdocument', u'type'] | ||||
|   >>> len(concepts) | ||||
|   19 | ||||
| 
 | ||||
| Now let's add a few more concepts: | ||||
| 
 | ||||
|   >>> topic = concepts[u'topic'] = Concept(u'Topic') | ||||
|   >>> intIds.register(topic) | ||||
|   11 | ||||
|   >>> 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 | ||||
|   >>> topic = concepts[u'topic'] | ||||
|   >>> zope = addObject(concepts, Concept, 'zope', title=u'Zope', conceptType=topic) | ||||
|   >>> zope3 = addObject(concepts, Concept, 'zope3', title=u'Zope 3', conceptType=topic) | ||||
| 
 | ||||
| Navigation typically starts at a start object, which by default ist the | ||||
| 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', | ||||
|    'title', 'type', 'viewName'] | ||||
|   >>> 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: | ||||
| 
 | ||||
|   >>> defaultPred = xrf.getDefaultPredicate() | ||||
|   >>> defaultPred['id'], defaultPred['name'] | ||||
|   ('8', u'standard') | ||||
|   ('16', u'standard') | ||||
|   >>> typePred = xrf.getTypePredicate() | ||||
|   >>> typePred['id'], typePred['name'] | ||||
|   ('7', u'hasType') | ||||
|   ('1', u'hasType') | ||||
|   >>> typeConcept = xrf.getTypeConcept() | ||||
|   >>> typeConcept['id'], typeConcept['name'] | ||||
|   ('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. | ||||
| 
 | ||||
|   >>> sorted(t['name'] for t in xrf.getConceptTypes()) | ||||
|   [u'domain', u'file', u'note', u'person', u'predicate', u'query', | ||||
|    u'textdocument', u'type'] | ||||
|   [u'customer', u'domain', u'file', u'note', u'person', u'predicate', u'query', | ||||
|    u'task', u'textdocument', u'topic', u'type'] | ||||
|   >>> 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: | ||||
| 
 | ||||
|   >>> obj2 = xrf.getObjectById('2') | ||||
|   >>> obj2 = xrf.getObjectById('5') | ||||
|   >>> obj2['id'], obj2['name'] | ||||
|   ('2', u'query') | ||||
|   ('5', u'query') | ||||
|   >>> textdoc = xrf.getObjectByName(u'textdocument') | ||||
|   >>> textdoc['id'], textdoc['name'] | ||||
|   ('5', u'textdocument') | ||||
|   ('11', u'textdocument') | ||||
| 
 | ||||
| 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'] | ||||
|   u'hasType' | ||||
|   >>> sorted(c['name'] for c in ch[0]['objects']) | ||||
|   [u'domain', u'file', u'note', u'person', u'predicate', u'query', | ||||
|    u'textdocument', u'type'] | ||||
|   [u'customer', u'domain', u'file', u'note', u'person', u'predicate', | ||||
|    u'query', u'task', u'textdocument', u'topic', u'type'] | ||||
| 
 | ||||
|   >>> pa = defaultPred['parents'] | ||||
|   >>> len(pa) | ||||
|  | @ -130,8 +112,8 @@ We can also retrieve children and parents explicitely: | |||
|   >>> ch[0]['name'] | ||||
|   u'hasType' | ||||
|   >>> sorted(c['name'] for c in ch[0]['objects']) | ||||
|   [u'domain', u'file', u'note', u'person', u'predicate', u'query', | ||||
|    u'textdocument', u'type'] | ||||
|   [u'customer', u'domain', u'file', u'note', u'person', u'predicate', | ||||
|    u'query', u'task', u'textdocument', u'topic', u'type'] | ||||
| 
 | ||||
|   >>> pa = xrf.getParents('7') | ||||
|   >>> len(pa) | ||||
|  | @ -139,7 +121,7 @@ We can also retrieve children and parents explicitely: | |||
|   >>> pa[0]['name'] | ||||
|   u'hasType' | ||||
|   >>> sorted(p['name'] for p in pa[0]['objects']) | ||||
|   [u'predicate'] | ||||
|   [u'type'] | ||||
| 
 | ||||
| Resources | ||||
| --------- | ||||
|  | @ -184,19 +166,23 @@ Updating the concept map | |||
| 
 | ||||
|   >>> topicId = xrf.getObjectByName('topic')['id'] | ||||
|   >>> 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'} | ||||
| 
 | ||||
| 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. | ||||
| 
 | ||||
|   >>> 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'} | ||||
| 
 | ||||
| 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') | ||||
|   'OK' | ||||
|   >>> john = xrf.getObjectById(john['id']) | ||||
|  |  | |||
|  | @ -162,7 +162,7 @@ def objectAsDict(obj, langInfo=None): | |||
|                'title': adapter.title, 'description': adapter.description, | ||||
|                'type': getUidForObject(objType.typeProvider)} | ||||
|     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(ti): | ||||
|             if attr not in ('__parent__', 'context', 'id', 'name', | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 helmutm
						helmutm