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:
helmutm 2008-02-10 09:56:42 +00:00
parent c6ff15c196
commit 5271981119
83 changed files with 3726 additions and 872 deletions

View file

@ -593,7 +593,7 @@ Actions
>>> request = TestRequest() >>> request = TestRequest()
>>> view = NodeView(m112, request) >>> view = NodeView(m112, request)
>>> view.controller = Controller(view, request) >>> view.controller = Controller(view, request)
>>> view.setupController() >>> #view.setupController()
>>> actions = view.getActions('portlet') >>> actions = view.getActions('portlet')
>>> len(actions) >>> len(actions)
@ -758,7 +758,7 @@ The new technique uses the ``fields`` and ``data`` attributes...
linkUrl textline False None linkUrl textline False None
>>> view.data >>> view.data
{'linkUrl': u'', 'contentType': u'', 'data': u'', {'linkUrl': u'http://', 'contentType': 'text/restructured', 'data': u'',
'title': u'Test Note'} 'title': u'Test Note'}
The object is changed via a FormController adapter created for The object is changed via a FormController adapter created for

View file

@ -1,5 +1,5 @@
# #
# Copyright (c) 2007 Helmut Merz helmutm@cy55.de # Copyright (c) 2008 Helmut Merz helmutm@cy55.de
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -27,43 +27,7 @@ from zope import component
from zope.app.pagetemplate import ViewPageTemplateFile from zope.app.pagetemplate import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy from zope.cachedescriptors.property import Lazy
action_macros = ViewPageTemplateFile('action_macros.pt') from cybertools.browser.action import Action
class Action(object):
template = action_macros
macroName = 'action'
condition = True
permission = None
url = '.'
viewName = ''
targetWindow = ''
title = ''
description = ''
icon = ''
cssClass = ''
onClick = ''
innerHtmlId = ''
def __init__(self, view, **kw):
self.view = view
for k, v in kw.items():
setattr(self, k, v)
@Lazy
def macro(self):
return self.template.macros[self.macroName]
@Lazy
def url(self):
return self.getActionUrl(self.view.url)
def getActionUrl(self, baseUrl):
if self.viewName:
return '/'.join((baseUrl, self.viewName))
else:
return baseUrl
class TargetAction(Action): class TargetAction(Action):

View file

@ -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>

View file

@ -1,5 +1,5 @@
# #
# Copyright (c) 2006 Helmut Merz helmutm@cy55.de # Copyright (c) 2008 Helmut Merz helmutm@cy55.de
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -22,7 +22,6 @@ Common base class for loops browser view classes.
$Id$ $Id$
""" """
from zope.app import zapi
from zope import component from zope import component
from zope.app.form.browser.interfaces import ITerms from zope.app.form.browser.interfaces import ITerms
from zope.app.i18n.interfaces import ITranslationDomain from zope.app.i18n.interfaces import ITranslationDomain
@ -35,24 +34,27 @@ from zope.formlib import form
from zope.formlib.form import FormFields from zope.formlib.form import FormFields
from zope.formlib.namedtemplate import NamedTemplate from zope.formlib.namedtemplate import NamedTemplate
from zope.interface import Interface, implements from zope.interface import Interface, implements
from zope.proxy import removeAllProxies
from zope.publisher.browser import applySkin from zope.publisher.browser import applySkin
from zope.publisher.interfaces.browser import IBrowserSkinType from zope.publisher.interfaces.browser import IBrowserSkinType
from zope import schema from zope import schema
from zope.schema.vocabulary import SimpleTerm from zope.schema.vocabulary import SimpleTerm
from zope.security import canAccess, canWrite, checkPermission from zope.security import canAccess, checkPermission
from zope.security.interfaces import ForbiddenAttribute, Unauthorized from zope.security.interfaces import ForbiddenAttribute, Unauthorized
from zope.security.proxy import removeSecurityProxy from zope.security.proxy import removeSecurityProxy
from zope.traversing.browser import absoluteURL from zope.traversing.browser import absoluteURL
from zope.traversing.api import getName from zope.traversing.api import getName, getParent
from cybertools.ajax.dojo import dojoMacroTemplate
from cybertools.browser.view import GenericView from cybertools.browser.view import GenericView
from cybertools.relation.interfaces import IRelationRegistry from cybertools.relation.interfaces import IRelationRegistry
from cybertools.text import mimetypes from cybertools.text import mimetypes
from cybertools.typology.interfaces import IType, ITypeManager from cybertools.typology.interfaces import IType, ITypeManager
from loops.common import adapted from loops.common import adapted
from loops.i18n.browser import I18NView from loops.i18n.browser import I18NView
from loops.interfaces import IView from loops.interfaces import IView, INode
from loops.resource import Resource from loops.resource import Resource
from loops.security.common import canAccessObject, canListObject, canWriteObject
from loops.type import ITypeConcept from loops.type import ITypeConcept
from loops import util from loops import util
from loops.util import _ from loops.util import _
@ -90,29 +92,35 @@ class EditForm(form.EditForm):
template = NamedTemplate('loops.pageform') template = NamedTemplate('loops.pageform')
def deleteObjectAction(self): def deleteObjectAction(self):
return None # better not to show the edit button at the moment return None # better not to show the delete button at the moment
parent = zapi.getParent(self.context) parent = getParent(self.context)
parentUrl = absoluteURL(parent, self.request) parentUrl = absoluteURL(parent, self.request)
return parentUrl + '/contents.html' return parentUrl + '/contents.html'
class BaseView(GenericView, I18NView): class BaseView(GenericView, I18NView):
actions = {} # default only, don't update actions = {}
def __init__(self, context, request): def __init__(self, context, request):
super(BaseView, self).__init__(context, request) super(BaseView, self).__init__(context, request)
# TODO: get rid of removeSecurityProxy() call # TODO: get rid of removeSecurityProxy() call - not yet...
self.context = removeSecurityProxy(context) self.context = removeSecurityProxy(context)
#self.context = context
#self.setSkin(self.loopsRoot.skinName) #self.setSkin(self.loopsRoot.skinName)
self.checkLanguage() #self.checkLanguage()
try: try:
if not canAccess(context, 'title'): if not canAccessObject(context):
raise Unauthorized raise Unauthorized
#request.response.redirect('login.html') #request.response.redirect('login.html')
except ForbiddenAttribute: # ignore when testing except ForbiddenAttribute: # ignore when testing
pass pass
def update(self):
result = super(BaseView, self).update()
self.checkLanguage()
return result
@Lazy @Lazy
def target(self): def target(self):
# allow for having a separate object the view acts upon # allow for having a separate object the view acts upon
@ -242,6 +250,16 @@ class BaseView(GenericView, I18NView):
for o in objs: for o in objs:
yield BaseView(o, request) yield BaseView(o, request)
def renderText(self, text, contentType):
typeKey = util.renderingFactories.get(contentType, None)
if typeKey is None:
if contentType == u'text/html':
return text
return u'<pre>%s</pre>' % util.html_quote(text)
source = component.createObject(typeKey, text)
view = component.getMultiAdapter((removeAllProxies(source), self.request))
return view.render()
# type listings # type listings
def listTypes(self, include=None, exclude=None, sortOn='title'): def listTypes(self, include=None, exclude=None, sortOn='title'):
@ -340,7 +358,7 @@ class BaseView(GenericView, I18NView):
@Lazy @Lazy
def editable(self): def editable(self):
return canWrite(self.context, 'title') return canWriteObject(self.context)
def getActions(self, category='object', page=None): def getActions(self, category='object', page=None):
""" Return a list of actions that provide the view and edit actions """ Return a list of actions that provide the view and edit actions
@ -364,7 +382,7 @@ class BaseView(GenericView, I18NView):
return False return False
if ct.startswith('text/') and ct != 'text/rtf': if ct.startswith('text/') and ct != 'text/rtf':
return checkPermission('loops.ManageSite', self.context) return checkPermission('loops.ManageSite', self.context)
return canWrite(self.context, 'title') return canWriteObject(self.context)
@Lazy @Lazy
def inlineEditingActive(self): def inlineEditingActive(self):
@ -385,8 +403,27 @@ class BaseView(GenericView, I18NView):
def registerDojo(self): def registerDojo(self):
cm = self.controller.macros cm = self.controller.macros
cm.register('js', 'dojo.js', resourceName='ajax.dojo/dojo.js') cm.register('js', 'dojo.js', template=dojoMacroTemplate, name='main',
#cm.register('js', 'dojo.js', resourceName='ajax.dojo1/dojo/dojo.js') position=0,
#djConfig='isDebug: true, parseOnLoad: true, usePlainJson: true, '
djConfig='parseOnLoad: true, usePlainJson: true, '
'locale: "%s"' % self.languageInfo.language)
jsCall = 'dojo.require("dojo.parser");'
cm.register('js-execute', jsCall, jsCall=jsCall)
cm.register('css', identifier='tundra.css', position=0,
resourceName='ajax.dojo/dijit/themes/tundra/tundra.css', media='all')
cm.register('css', identifier='dojo.css', position=1,
resourceName='ajax.dojo/dojo/resources/dojo.css', media='all')
def registerDojoDateWidget(self):
self.registerDojo()
jsCall = 'dojo.require("dijit.form.DateTextBox");'
self.controller.macros.register('js-execute', jsCall, jsCall=jsCall)
def registerDojoTextWidget(self):
self.registerDojo()
jsCall = 'dojo.require("dijit.form.ValidationTextBox");'
self.controller.macros.register('js-execute', jsCall, jsCall=jsCall)
# vocabulary stuff # vocabulary stuff
@ -411,7 +448,7 @@ class LoopsTerms(object):
def getTerm(self, value): def getTerm(self, value):
#if value is None: #if value is None:
# return SimpleTerm(None, '', u'not assigned') # return SimpleTerm(None, '', u'not assigned')
title = value.title or zapi.getName(value) title = value.title or getName(value)
token = self.loopsRoot.getLoopsUri(value) token = self.loopsRoot.getLoopsUri(value)
return SimpleTerm(value, token, title) return SimpleTerm(value, token, title)

View file

@ -1,5 +1,5 @@
# #
# Copyright (c) 2007 Helmut Merz helmutm@cy55.de # Copyright (c) 2008 Helmut Merz helmutm@cy55.de
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -44,7 +44,11 @@ from zope.schema.interfaces import IIterableSource
from zope.security.proxy import removeSecurityProxy from zope.security.proxy import removeSecurityProxy
from zope.traversing.api import getName from zope.traversing.api import getName
from cybertools.browser.action import actions
from cybertools.composer.interfaces import IInstance
from cybertools.composer.schema.interfaces import ISchemaFactory
from cybertools.typology.interfaces import IType, ITypeManager from cybertools.typology.interfaces import IType, ITypeManager
from cybertools.util.jeep import Jeep
from loops.browser.common import EditForm, BaseView, LoopsTerms, conceptMacrosTemplate from loops.browser.common import EditForm, BaseView, LoopsTerms, conceptMacrosTemplate
from loops.common import adapted from loops.common import adapted
from loops.concept import Concept, ConceptTypeSourceList, PredicateSourceList from loops.concept import Concept, ConceptTypeSourceList, PredicateSourceList
@ -56,8 +60,14 @@ from loops.versioning.util import getVersion
class ConceptEditForm(EditForm, I18NView): class ConceptEditForm(EditForm, I18NView):
""" Classic-style (zope.formlib-based) form for editing concepts.
"""
@Lazy #@Lazy # zope.formlib does not issue a redirect after changes, so that
# it tries to redisplay the old form even after a type change that
# changes the set of available attributes. So the typeInterface
# must be recalculated e.g. after an update of the context object.
@property
def typeInterface(self): def typeInterface(self):
return IType(self.context).typeInterface return IType(self.context).typeInterface
@ -79,7 +89,8 @@ class ConceptEditForm(EditForm, I18NView):
def setUpWidgets(self, ignore_request=False): def setUpWidgets(self, ignore_request=False):
# TODO: get rid of removeSecurityProxy(): use ConceptSchema in interfaces # TODO: get rid of removeSecurityProxy(): use ConceptSchema in interfaces
adapter = removeSecurityProxy(adapted(self.context, self.languageInfo)) #adapter = removeSecurityProxy(adapted(self.context, self.languageInfo))
adapter = adapted(self.context, self.languageInfo)
self.adapters = {self.typeInterface: adapter, self.adapters = {self.typeInterface: adapter,
IConceptSchema: adapter} IConceptSchema: adapter}
self.widgets = setUpEditWidgets( self.widgets = setUpEditWidgets(
@ -89,9 +100,87 @@ class ConceptEditForm(EditForm, I18NView):
if desc: if desc:
desc.height = 2 desc.height = 2
class ConceptRelationView(BaseView):
""" For displaying children and resources lists, used by ConceptView.
"""
def __init__(self, relation, request, contextIsSecond=False):
if contextIsSecond:
self.context = relation.second
self.other = relation.first
else:
self.context = relation.first
self.other = relation.second
self.context = getVersion(self.context, request)
self.predicate = relation.predicate
self.relation = relation
self.request = request
@Lazy
def adapted(self):
return adapted(self.context, self.languageInfo)
@Lazy
def data(self):
return self.instance.applyTemplate()
@Lazy
def instance(self):
instance = IInstance(self.adapted)
instance.template = self.schema
instance.view = self
return instance
@Lazy
def schema(self):
ti = self.typeInterface or IConceptSchema
schemaFactory = component.getAdapter(self.adapted, ISchemaFactory)
return schemaFactory(ti, manager=self, request=self.request)
@Lazy
def title(self):
return self.adapted.title or getName(self.context)
@Lazy
def description(self):
return self.adapted.description
@Lazy
def token(self):
return ':'.join((self.loopsRoot.getLoopsUri(self.context),
self.loopsRoot.getLoopsUri(self.predicate)))
@Lazy
def uidToken(self):
return ':'.join((util.getUidForObject(self.context),
util.getUidForObject(self.predicate)))
@Lazy
def isProtected(self):
return getName(self.predicate) == 'hasType'
@Lazy
def predicateTitle(self):
return self.predicate.title
@Lazy
def predicateUrl(self):
return zapi.absoluteURL(self.predicate, self.request)
@Lazy
def relevance(self):
return self.relation.relevance
@Lazy
def order(self):
return self.relation.order
class ConceptView(BaseView): class ConceptView(BaseView):
template = ViewPageTemplateFile('concept_macros.pt') template = ViewPageTemplateFile('concept_macros.pt')
childViewFactory = ConceptRelationView
@Lazy @Lazy
def macro(self): def macro(self):
@ -108,7 +197,7 @@ class ConceptView(BaseView):
self.request.principal)): self.request.principal)):
cont.macros.register('portlet_right', 'parents', title=_(u'Parents'), cont.macros.register('portlet_right', 'parents', title=_(u'Parents'),
subMacro=self.template.macros['parents'], subMacro=self.template.macros['parents'],
position=0, info=self) priority=20, info=self)
@Lazy @Lazy
def adapted(self): def adapted(self):
@ -123,7 +212,8 @@ class ConceptView(BaseView):
return self.adapted.description return self.adapted.description
def fieldData(self): def fieldData(self):
# TODO: use cybertools.composer.schema.instance, see loops.browser.form # obsolete - use getData() instead
# TODO: change view macros accordingly
ti = IType(self.context).typeInterface ti = IType(self.context).typeInterface
if not ti: if not ti:
return return
@ -140,12 +230,35 @@ class ConceptView(BaseView):
widget.setRenderedValue(value) widget.setRenderedValue(value)
yield dict(title=f.title, value=value, id=n, widget=widget) yield dict(title=f.title, value=value, id=n, widget=widget)
def children(self, topLevelOnly=True, sort=True): def getData(self, omit=('title', 'description')):
data = self.instance.applyTemplate()
for k in omit:
if k in data:
del data[k]
return data
@Lazy
def data(self):
return self.getData()
@Lazy
def instance(self):
instance = IInstance(self.adapted)
instance.template = self.schema
instance.view = self
return instance
@Lazy
def schema(self):
ti = self.typeInterface or IConceptSchema
schemaFactory = component.getAdapter(self.adapted, ISchemaFactory)
return schemaFactory(ti, manager=self, request=self.request)
def getChildren(self, topLevelOnly=True, sort=True):
cm = self.loopsRoot.getConceptManager() cm = self.loopsRoot.getConceptManager()
hasType = cm.getTypePredicate() hasType = cm.getTypePredicate()
standard = cm.getDefaultPredicate() standard = cm.getDefaultPredicate()
#rels = self.context.getChildRelations() rels = (self.childViewFactory(r, self.request, contextIsSecond=True)
rels = (ConceptRelationView(r, self.request, contextIsSecond=True)
for r in self.context.getChildRelations(sort=None)) for r in self.context.getChildRelations(sort=None))
if sort: if sort:
rels = sorted(rels, key=lambda r: (r.order, r.title.lower())) rels = sorted(rels, key=lambda r: (r.order, r.title.lower()))
@ -160,27 +273,37 @@ class ConceptView(BaseView):
if skip: continue if skip: continue
yield r yield r
# Override in subclass to control what is displayd in listings:
children = getChildren
def childrenAlphaGroups(self): def childrenAlphaGroups(self):
letters = [] result = Jeep()
relations = {} rels = self.getChildren(topLevelOnly=False, sort=False)
rels = self.children(topLevelOnly=False, sort=False)
rels = sorted(rels, key=lambda r: r.title.lower()) rels = sorted(rels, key=lambda r: r.title.lower())
for letter, group in groupby(rels, lambda r: r.title.lower()[0]): for letter, group in groupby(rels, lambda r: r.title.lower()[0]):
letter = letter.upper() letter = letter.upper()
letters.append(letter) result[letter] = list(group)
relations[letter] = list(group) return result
return dict(letters=letters, relations=relations)
def childrenByType(self):
result = Jeep()
rels = self.getChildren(topLevelOnly=False, sort=False)
rels = sorted(rels, key=lambda r: (r.typeTitle.lower(), r.title.lower()))
for type, group in groupby(rels, lambda r: r.type):
typeName = getName(type.typeProvider)
result[typeName] = list(group)
return result
def parents(self): def parents(self):
rels = sorted(self.context.getParentRelations(), rels = sorted(self.context.getParentRelations(),
key=(lambda x: x.first.title.lower())) key=(lambda x: x.first.title.lower()))
for r in rels: for r in rels:
yield ConceptRelationView(r, self.request) yield self.childViewFactory(r, self.request)
def resources(self): def resources(self):
rels = self.context.getResourceRelations() rels = self.context.getResourceRelations()
for r in rels: for r in rels:
yield ConceptRelationView(r, self.request, contextIsSecond=True) yield self.childViewFactory(r, self.request, contextIsSecond=True)
@Lazy @Lazy
def view(self): def view(self):
@ -213,6 +336,14 @@ class ConceptView(BaseView):
for node in self.context.getClients(): for node in self.context.getClients():
yield NodeView(node, self.request) yield NodeView(node, self.request)
def getActions(self, category='object', page=None):
t = IType(self.context)
actInfo = t.optionsDict.get('action.' + category, '')
actNames = [n.strip() for n in actInfo.split(',')]
if actNames:
return actions.get(category, actNames, view=self, page=page)
return []
class ConceptConfigureView(ConceptView): class ConceptConfigureView(ConceptView):
@ -321,7 +452,7 @@ class ConceptConfigureView(ConceptView):
else: else:
start = end = searchType start = end = searchType
criteria['loops_type'] = (start, end) criteria['loops_type'] = (start, end)
cat = zapi.getUtility(ICatalog) cat = component.getUtility(ICatalog)
result = cat.searchResults(**criteria) result = cat.searchResults(**criteria)
# TODO: can this be done in a faster way? # TODO: can this be done in a faster way?
result = [r for r in result if r.getLoopsRoot() == self.loopsRoot] result = [r for r in result if r.getLoopsRoot() == self.loopsRoot]
@ -333,64 +464,8 @@ class ConceptConfigureView(ConceptView):
def predicates(self): def predicates(self):
preds = PredicateSourceList(self.context) preds = PredicateSourceList(self.context)
terms = zapi.getMultiAdapter((preds, self.request), ITerms) terms = component.getMultiAdapter((preds, self.request), ITerms)
for pred in preds: for pred in preds:
yield terms.getTerm(pred) yield terms.getTerm(pred)
class ConceptRelationView(BaseView):
def __init__(self, relation, request, contextIsSecond=False):
if contextIsSecond:
self.context = relation.second
self.other = relation.first
else:
self.context = relation.first
self.other = relation.second
self.context = getVersion(self.context, request)
self.predicate = relation.predicate
self.relation = relation
self.request = request
@Lazy
def adapted(self):
return adapted(self.context, self.languageInfo)
@Lazy
def title(self):
return self.adapted.title or getName(self.context)
@Lazy
def description(self):
return self.adapted.description
@Lazy
def token(self):
return ':'.join((self.loopsRoot.getLoopsUri(self.context),
self.loopsRoot.getLoopsUri(self.predicate)))
@Lazy
def uidToken(self):
return ':'.join((util.getUidForObject(self.context),
util.getUidForObject(self.predicate)))
@Lazy
def isProtected(self):
return zapi.getName(self.predicate) == 'hasType'
@Lazy
def predicateTitle(self):
return self.predicate.title
@Lazy
def predicateUrl(self):
return zapi.absoluteURL(self.predicate, self.request)
@Lazy
def relevance(self):
return self.relation.relevance
@Lazy
def order(self):
return self.relation.order

View file

@ -1,8 +1,8 @@
<metal:data define-macro="conceptdata"> <metal:data define-macro="conceptdata">
<div tal:attributes="class string:content-$level;"> <div tal:attributes="class string:content-$level;">
<metal:fields use-macro="item/template/macros/concepttitle" /><br /> <metal:fields use-macro="item/template/macros/concepttitle" />
<metal:fields use-macro="item/template/macros/conceptfields" /><br /> <metal:fields use-macro="item/template/macros/conceptfields" />
<metal:fields use-macro="item/template/macros/conceptchildren" /><br /> <metal:fields use-macro="item/template/macros/conceptchildren" />
<metal:fields use-macro="item/template/macros/conceptresources" /> <metal:fields use-macro="item/template/macros/conceptresources" />
</div> </div>
</metal:data> </metal:data>
@ -10,7 +10,8 @@
<metal:title define-macro="concepttitle"> <metal:title define-macro="concepttitle">
<h1 tal:attributes="ondblclick item/openEditWindow"> <h1 tal:attributes="ondblclick item/openEditWindow">
<span tal:content="item/title">Title</span> <a name="top"
tal:content="item/title">Title</a>
</h1> </h1>
<p tal:define="description description|item/description" <p tal:define="description description|item/description"
tal:condition="description"> tal:condition="description">
@ -19,13 +20,16 @@
<metal:fields define-macro="conceptfields"> <metal:fields define-macro="conceptfields">
<fieldset class="box"
tal:define="data python: list(item.fieldData())"
tal:condition="data">
<table tal:attributes="ondblclick item/openEditWindow"> <table tal:attributes="ondblclick item/openEditWindow">
<tr tal:repeat="field item/fieldData"> <tr tal:repeat="field data">
<td><span tal:content="field/title" <td><b tal:content="field/title" i18n:translate="" />:</td>
i18n:translate="" />:</td>
<td tal:content="structure field/widget"></td> <td tal:content="structure field/widget"></td>
</tr> </tr>
</table> </table>
</fieldset>
</metal:fields> </metal:fields>
@ -47,7 +51,7 @@
ondblclick python: item.openEditWindow('configure.html')" ondblclick python: item.openEditWindow('configure.html')"
tal:define="children python: list(item.children())" tal:define="children python: list(item.children())"
tal:condition="children"> tal:condition="children">
<h2 i18n:translate="">Children</h2><br /> <h2 i18n:translate="">Children</h2>
<table class="listing"> <table class="listing">
<tr> <tr>
<th i18n:translate="">Title</th> <th i18n:translate="">Title</th>
@ -81,7 +85,7 @@
ondblclick python: item.openEditWindow('resources.html')" ondblclick python: item.openEditWindow('resources.html')"
tal:define="resources python: list(item.resources())" tal:define="resources python: list(item.resources())"
tal:condition="resources"> tal:condition="resources">
<h2 i18n:translate="">Resources</h2><br /> <h2 i18n:translate="">Resources</h2>
<table class="listing"> <table class="listing">
<tr> <tr>
<th i18n:translate="">Title</th> <th i18n:translate="">Title</th>

View file

@ -15,8 +15,6 @@
<resource name="loops.css" file="loops.css" /> <resource name="loops.css" file="loops.css" />
<resource name="loops.js" file="loops.js" /> <resource name="loops.js" file="loops.js" />
<resource name="loops1.js" file="loops1.js" />
<!-- new style pages --> <!-- new style pages -->

View file

@ -1,5 +1,5 @@
# #
# Copyright (c) 2006 Helmut Merz helmutm@cy55.de # Copyright (c) 2008 Helmut Merz helmutm@cy55.de
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -29,8 +29,8 @@ from zope.cachedescriptors.property import Lazy
from zope.security.proxy import removeSecurityProxy from zope.security.proxy import removeSecurityProxy
from cStringIO import StringIO from cStringIO import StringIO
from loops.external import IExternalContentSource from loops.external.external import IExternalContentSource
from loops.external import ILoader, IExporter from loops.external.external import ILoader, IExporter
class NodesExportImport(object): class NodesExportImport(object):
@ -38,7 +38,8 @@ class NodesExportImport(object):
""" """
def __init__(self, context, request): def __init__(self, context, request):
self.context = removeSecurityProxy(context) #self.context = removeSecurityProxy(context)
self.context = context
self.request = request self.request = request
self.message = u'' self.message = u''

View file

@ -1,5 +1,5 @@
# #
# Copyright (c) 2007 Helmut Merz helmutm@cy55.de # Copyright (c) 2008 Helmut Merz helmutm@cy55.de
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -30,11 +30,9 @@ from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent
from zope.app.container.interfaces import INameChooser from zope.app.container.interfaces import INameChooser
from zope.app.container.contained import NameChooser from zope.app.container.contained import NameChooser
from zope.app.form.browser.textwidgets import FileWidget, TextAreaWidget
from zope.app.pagetemplate import ViewPageTemplateFile from zope.app.pagetemplate import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy from zope.cachedescriptors.property import Lazy
from zope.contenttype import guess_content_type from zope.contenttype import guess_content_type
#from zope.formlib.form import Form, EditForm, FormFields
from zope.publisher.browser import FileUpload from zope.publisher.browser import FileUpload
from zope.publisher.interfaces import BadRequest from zope.publisher.interfaces import BadRequest
from zope.security.proxy import isinstance, removeSecurityProxy from zope.security.proxy import isinstance, removeSecurityProxy
@ -81,7 +79,8 @@ class ObjectForm(NodeView):
def closeAction(self, submit=False): def closeAction(self, submit=False):
if self.isInnerHtml: if self.isInnerHtml:
return 'dialogs["%s"].hide()' % self.dialog_name return ("closeDataWidget(%s); dialog.hide();" %
(submit and 'true' or 'false'))
if submit: if submit:
return "xhrSubmitPopup('dialog_form', '%s'); return false" % (self.request.URL) return "xhrSubmitPopup('dialog_form', '%s'); return false" % (self.request.URL)
return 'window.close()' return 'window.close()'
@ -101,7 +100,10 @@ class ObjectForm(NodeView):
@Lazy @Lazy
def fieldRenderers(self): def fieldRenderers(self):
return schema_macros.macros renderers = dict(schema_macros.macros)
# replace HTML edit widget with Dojo Editor
renderers['input_html'] = self.template.macros['input_html']
return renderers
@Lazy @Lazy
def fieldEditRenderers(self): def fieldEditRenderers(self):
@ -109,19 +111,17 @@ class ObjectForm(NodeView):
@Lazy @Lazy
def schema(self): def schema(self):
#ti = self.typeInterface or Interface #IConcept
ti = self.typeInterface or IConceptSchema ti = self.typeInterface or IConceptSchema
schemaFactory = component.getAdapter(self.adapted, ISchemaFactory) schemaFactory = component.getAdapter(self.adapted, ISchemaFactory)
return schemaFactory(ti, manager=self, request=self.request) return schemaFactory(ti, manager=self, request=self.request)
@Lazy @Lazy
def fields(self): def fields(self):
return self.schema.fields return [f for f in self.schema.fields if not f.readonly]
@Lazy @Lazy
def data(self): def data(self):
instance = self.instance instance = self.instance
instance.template = self.schema
data = instance.applyTemplate(mode='edit') data = instance.applyTemplate(mode='edit')
form = self.request.form form = self.request.form
for k, v in data.items(): for k, v in data.items():
@ -132,11 +132,15 @@ class ObjectForm(NodeView):
@Lazy @Lazy
def instance(self): def instance(self):
return IInstance(self.adapted) instance = IInstance(self.adapted)
instance.template = self.schema
instance.view = self
return instance
def __call__(self): def __call__(self):
if self.isInnerHtml: if self.isInnerHtml:
response = self.request.response response = self.request.response
#response.setHeader('Content-Type', 'text/html; charset=UTF-8')
response.setHeader('Expires', 'Sat, 1 Jan 2000 00:00:00 GMT') response.setHeader('Expires', 'Sat, 1 Jan 2000 00:00:00 GMT')
response.setHeader('Pragma', 'no-cache') response.setHeader('Pragma', 'no-cache')
return innerHtml(self) return innerHtml(self)
@ -240,11 +244,16 @@ class CreateObjectForm(ObjectForm):
@Lazy @Lazy
def adapted(self): def adapted(self):
return self.typeInterface(Resource()) ad = self.typeInterface(Resource())
ad.storageName = 'unknown' # hack for file objects: don't try to retrieve data
return (ad)
@Lazy @Lazy
def instance(self): def instance(self):
return IInstance(Resource()) instance = IInstance(self.adapted)
instance.template = self.schema
instance.view = self
return instance
@Lazy @Lazy
def typeInterface(self): def typeInterface(self):
@ -278,8 +287,9 @@ class CreateObjectPopup(CreateObjectForm):
cm = self.controller.macros cm = self.controller.macros
cm.register('css', identifier='popup.css', resourceName='popup.css', cm.register('css', identifier='popup.css', resourceName='popup.css',
media='all', position=4) media='all', position=4)
jsCall = ('dojo.require("dojo.widget.Dialog");' jsCall = ('dojo.require("dojo.parser");'
'dojo.require("dojo.widget.ComboBox");') 'dojo.require("dijit.form.FilteringSelect");'
'dojo.require("dojox.data.QueryReadStore");')
cm.register('js-execute', jsCall, jsCall=jsCall) cm.register('js-execute', jsCall, jsCall=jsCall)
return True return True
@ -298,14 +308,19 @@ class CreateConceptForm(CreateObjectForm):
@Lazy @Lazy
def adapted(self): def adapted(self):
c = Concept()
ti = self.typeInterface ti = self.typeInterface
if ti is None: if ti is None:
return Concept() return c
return ti(Concept()) ad = ti(c)
return ad
@Lazy @Lazy
def instance(self): def instance(self):
return IInstance(Concept()) instance = IInstance(self.adapted)
instance.template = self.schema
instance.view = self
return instance
@Lazy @Lazy
def typeInterface(self): def typeInterface(self):
@ -355,9 +370,6 @@ class EditObject(FormController, I18NView):
prefix = 'form.' prefix = 'form.'
conceptPrefix = 'assignments.' conceptPrefix = 'assignments.'
old = None
selected = None
@Lazy @Lazy
def adapted(self): def adapted(self):
return adapted(self.object, self.languageInfoForUpdate) return adapted(self.object, self.languageInfoForUpdate)
@ -377,7 +389,10 @@ class EditObject(FormController, I18NView):
@Lazy @Lazy
def instance(self): def instance(self):
return component.getAdapter(self.adapted, IInstance, name='editor') instance = component.getAdapter(self.adapted, IInstance, name='editor')
instance.template = self.schema
instance.view = self.view
return instance
@Lazy @Lazy
def loopsRoot(self): def loopsRoot(self):
@ -406,14 +421,17 @@ class EditObject(FormController, I18NView):
obj = self.object obj = self.object
form = self.request.form form = self.request.form
instance = self.instance instance = self.instance
instance.template = self.schema #instance.template = self.schema
formState = instance.applyTemplate(data=form, fieldHandlers=self.fieldHandlers) formState = instance.applyTemplate(data=form, fieldHandlers=self.fieldHandlers)
self.selected = []
self.old = []
for k in form.keys(): for k in form.keys():
if k.startswith(self.prefix): if k.startswith(self.prefix):
fn = k[len(self.prefix):] fn = k[len(self.prefix):]
value = form[k] value = form[k]
if fn.startswith(self.conceptPrefix) and value: if fn.startswith(self.conceptPrefix) and value:
self.collectConcepts(fn[len(self.conceptPrefix):], value) self.collectConcepts(fn[len(self.conceptPrefix):], value)
self.collectAutoConcepts()
if self.old or self.selected: if self.old or self.selected:
self.assignConcepts(obj) self.assignConcepts(obj)
notify(ObjectModifiedEvent(obj)) notify(ObjectModifiedEvent(obj))
@ -441,14 +459,15 @@ class EditObject(FormController, I18NView):
def collectConcepts(self, fieldName, value): def collectConcepts(self, fieldName, value):
if self.old is None: if self.old is None:
self.old = [] self.old = []
if self.selected is None:
self.selected = []
for v in value: for v in value:
if fieldName == 'old': if fieldName == 'old':
self.old.append(v) self.old.append(v)
elif fieldName == 'selected' and v not in self.selected: elif fieldName == 'selected' and v not in self.selected:
self.selected.append(v) self.selected.append(v)
def collectAutoConcepts(self):
pass
def assignConcepts(self, obj): def assignConcepts(self, obj):
for v in self.old: for v in self.old:
if v not in self.selected: if v not in self.selected:
@ -532,6 +551,10 @@ class CreateObject(EditObject):
class EditConcept(EditObject): class EditConcept(EditObject):
@Lazy
def typeInterface(self):
return IType(self.object).typeInterface or IConceptSchema
def getConceptRelations(self, obj, predicates, concept): def getConceptRelations(self, obj, predicates, concept):
return obj.getParentRelations(predicates=predicates, parent=concept) return obj.getParentRelations(predicates=predicates, parent=concept)

View file

@ -2,7 +2,8 @@
$Id$ --> $Id$ -->
<metal:block define-macro="edit" i18n:domain="loops"> <metal:block define-macro="edit" i18n:domain="loops">
<form method="post" enctype="multipart/form-data" id="dialog_form" <form method="post" enctype="multipart/form-data"
id="dialog_form" class="dialog"
tal:define="langInfo view/languageInfo; tal:define="langInfo view/languageInfo;
languages langInfo/availableLanguages; languages langInfo/availableLanguages;
language langInfo/language; language langInfo/language;
@ -15,8 +16,8 @@
<table cellpadding="3" class="form"> <table cellpadding="3" class="form">
<tbody> <tbody>
<tr> <tr>
<th colspan="5" <th colspan="5" class="headline"
tal:attributes="colspan python: useI18N and 4 or 5"><br /> tal:attributes="colspan python: useI18N and 4 or 5">
<span tal:replace="view/title" <span tal:replace="view/title"
i18n:translate="">Edit Information Object</span> i18n:translate="">Edit Information Object</span>
</th> </th>
@ -44,7 +45,7 @@
<tbody> <tbody>
<tr> <tr>
<td colspan="5" class="headline" <td colspan="5" class="headline"
i18n:translate="">Concept Assignments</td> i18n:translate="">Assign Parent Concepts</td>
</tr> </tr>
<tr metal:use-macro="view/template/macros/assignments" /> <tr metal:use-macro="view/template/macros/assignments" />
<tr metal:use-macro="view/template/macros/search_concepts" /> <tr metal:use-macro="view/template/macros/search_concepts" />
@ -61,7 +62,8 @@
<metal:block define-macro="create" i18n:domain="loops"> <metal:block define-macro="create" i18n:domain="loops">
<form method="post" enctype="multipart/form-data" id="dialog_form" <form method="post" enctype="multipart/form-data"
id="dialog_form" class="dialog"
tal:define="qualifier request/qualifier | string:resource; tal:define="qualifier request/qualifier | string:resource;
innerForm request/inner_form | string:inner_form.html; innerForm request/inner_form | string:inner_form.html;
typeToken python: request.get('form.type') typeToken python: request.get('form.type')
@ -70,8 +72,8 @@
<input type="hidden" name="form.action" value="create" <input type="hidden" name="form.action" value="create"
tal:attributes="value view/form_action" /> tal:attributes="value view/form_action" />
<table cellpadding="3" class="form"> <table cellpadding="3" class="form">
<tbody><tr><th colspan="5"><br /> <tbody><tr><th colspan="5" class="headline">
<span tal:replace="view/title" <span tal:content="view/title"
i18n:translate="">Create Information Object</span> i18n:translate="">Create Information Object</span>
<select name="form.type" id="form.type" <select name="form.type" id="form.type"
tal:condition="not:fixedType" tal:condition="not:fixedType"
@ -154,9 +156,7 @@
<td><label for="concept.search.type"><span i18n:translate="">Type</span>:</label></td> <td><label for="concept.search.type"><span i18n:translate="">Type</span>:</label></td>
<td> <td>
<select id="concept.search.type" <select id="concept.search.type"
tal:attributes="onChange onChange="setConceptTypeForComboBox('concept.search.type', 'concept.search.text')">
string:setConceptTypeForComboBox(
'concept.search.type', 'concept.search.text')">
<tal:types repeat="type view/conceptTypesForSearch"> <tal:types repeat="type view/conceptTypesForSearch">
<option value="loops:*" <option value="loops:*"
i18n:translate="" i18n:translate=""
@ -172,19 +172,12 @@
<input type="hidden" <input type="hidden"
id="concept.search.predicate" id="concept.search.predicate"
tal:attributes="value view/defaultPredicateUid" /> tal:attributes="value view/defaultPredicateUid" />
<input dojoType="comboBox" mode="remote" autoComplete="False" <div dojoType="dojox.data.QueryReadStore" jsId="conceptSearch"
name="concept.search.text" id="concept.search.text" url="listConceptsForComboBox.js?searchType=" >
tal:attributes="dataUrl </div>
string:listConceptsForComboBox.js?searchString=%{searchString}&searchType=" /> <input dojoType="dijit.form.FilteringSelect" store="conceptSearch"
<tal:dojo1 condition="nothing"> autoComplete="False" labelAttr="label"
<div dojoType="dojox.data.QueryReadStore" jsId="conceptSearch" name="concept.search.text" id="concept.search.text" />
tal:attributes="url
string:listConceptsForComboBox.js?searchString=%{searchString}&searchType=" >
</div>
<input dojoType="dijit.form.ComboBox" store="conceptSearch"
autoComplete="False"
name="concept.search.text" id="concept.search.text" />
</tal:dojo1>
</td> </td>
<td> <td>
<input type="button" value="Select" <input type="button" value="Select"
@ -222,7 +215,7 @@
</metal:versioning> </metal:versioning>
<tr metal:define-macro="buttons" i18n:domain=""> <tr metal:define-macro="buttons" i18n:domain="" class="buttons">
<td colspan="5"> <td colspan="5">
<input value="Save" type="submit" <input value="Save" type="submit"
i18n:attributes="value" i18n:attributes="value"
@ -232,3 +225,19 @@
tal:attributes="onClick view/closeAction"> tal:attributes="onClick view/closeAction">
</td> </td>
</tr> </tr>
<!-- overridden field renderers -->
<metal:html define-macro="input_html">
<textarea name="field" rows="3" style="width: 450px"
dojoType="dijit.Editor"
tal:define="width field/width|nothing;
height field/height|python:3"
tal:attributes="name name; id name;
rows python: height or 3;
style python:
'width: %s' % (width and str(width)+'px' or '450px');"
tal:content="data/?name|string:">
</textarea>
</metal:html>

View file

@ -11,71 +11,111 @@ a[href]:hover {
} }
pre { pre {
background-color: #f4f4f4;
overflow: scroll; overflow: scroll;
max-height: 35em; max-height: 35em;
} }
ul, p {
margin-top: 0.4em;
margin-bottom: 0.5em;
}
table.listing td { table.listing td {
white-space: normal; white-space: normal;
} }
.box div.body div.even { fieldset.box {
background-color: #f4f4f4; margin: 1em 0 0.5em 0;
padding: 0.5em;
border: 1px solid #ccc;
} }
.box { fieldset.box td {
margin: 12px; padding: 0.2em 0.2em 0.2em 0;
} }
#body { #body {
margin-left: 5px; margin-left: 5px;
} }
.top-actions{ .top-actions {
position: absolute; position: absolute;
right: 2em; right: 2em;
top: 1em; top: 1em;
} }
/*.content-1 h1 { */ .top image {
h1 { margin-top: -1px;
}
.content-1 h1, h1 {
font-size: 160%; font-size: 160%;
font-weight: bold; margin-top: 0.8em;
padding-bottom: 3px;
} }
.content-2 h1, h2 { .content-2 h1, .content-1 h2, h2 {
font-size: 140%; font-size: 140%;
font-weight: normal;
margin-top: 0.7em;
} }
.content-3 h1, .content-2 h2, h3 { .content-3 h1, .content-2 h2, .content-1 h3, h3 {
font-size: 130%; font-size: 130%;
font-weight: bold; font-weight: normal;
margin-top: 0.7em;
} }
.content-4 h1, .content-3 h2, .content-2 h3, .content-1 h4 { .content-4 h1, .content-3 h2, .content-2 h3, .content-1 h4, h4 {
font-size: 120%; font-size: 120%;
font-weight: normal;
margin-top: 0.7em;
} }
.content-5 h1, .content-4 h2, .content-3 h3, content-2 h4 { .content-5 h1, .content-4 h2, .content-3 h3, content-2 h4 {
font-size: 100%; font-size: 100%;
border: none; border: none;
} margin-top: 0.7em;
.subcolumn {
display: inline;
float: left;
} }
.box { .box {
margin: 5px; margin: 12px;
/*margin: 5px;*/
padding: 6px; padding: 6px;
padding-top: 0; padding-top: 0;
} }
div.box {
margin: 12px 12px 8px 12px;
border: 1px solid #ccc;
border-bottom: none;
}
div.box h4 {
font: 110% Verdana, Tahoma, Arial, Helvetica, sans-serif;
color: #000040;
border: none;
border-bottom: 1px solid #ccc;
padding: 4px;
padding-top: 1px;
padding-bottom: 3px;
background-color: #ddd;
height: auto;
}
.box h1, .box h2, .box h3 { .box h1, .box h2, .box h3 {
border-bottom: None; border-bottom: None;
} }
.box div.body div.even {
background-color: #f4f4f4;
}
.box div.body div {
padding: 0.2em 0.2em 0.2em 0.3em;
}
div.menu-1, div.menu-2 { div.menu-1, div.menu-2 {
border-top: 1px solid #eeeeee; border-top: 1px solid #eeeeee;
font-weight: bold; font-weight: bold;
@ -83,7 +123,7 @@ div.menu-1, div.menu-2 {
.box div.body div.menu-3 { .box div.body div.menu-3 {
border-top: none; border-top: none;
padding-left: 1.5em; padding: 0.1em 0 0.2em 1.5em;
} }
.box div.body div.menu-4 { .box div.body div.menu-4 {
@ -91,6 +131,19 @@ div.menu-1, div.menu-2 {
font-size: 90% font-size: 90%
} }
.subcolumn {
display: inline;
float: left;
}
.footer {
text-align: center;
border-top: 1px solid #ccc;
border-bottom: none;
margin-top: 12px;
padding-top: 6px;
}
.flow-left { .flow-left {
float: left; float: left;
} }
@ -128,6 +181,45 @@ img.notselected {
margin: 1em 0 0.5em 0; margin: 1em 0 0.5em 0;
} }
.button {
margin: 1em 0 1em 0;
}
.button a, .button a:visited {
padding: 2px 4px 2px 4px;
background-color: #e8e8e8;
text-decoration: None;
color: Black;
border-width: 2px;
border-style: solid;
border-color: #f4f4f4 #989898 #989898 #f4f4f4;
}
.button a:active {
border-color: #989898 #f4f4f4 #f4f4f4 #989898;
}
table.listing {
margin: 1px;
margin-top: 6px;
}
table.listing th {
font-family: Verdana, Tahoma, Arial, Helvetica, sans-serif;
color: #000040;
}
.itemViews {
border-bottom-width: 2px;
}
.dialog .headline {
font-size: 120%;
font-weight: bold;
text-align: center;
padding: 1em 0 1em 0;
}
/* search stuff */ /* search stuff */
@ -139,34 +231,70 @@ img.notselected {
font-weight: bold; font-weight: bold;
} }
/* dojo stuff */ /* blog */
/*.dojoComboBox { .blog .description {
width: 200px; font-size: 90%;
}*/ color: #666666;
.dojoDialog {
background: #eee;
border: 1px solid #999;
-moz-border-radius: 5px;
padding: 4px;
} }
.dojoDialog th { .blogpost .description {
font-weight: bold;
font-size: 90%;
color: #666666;
padding-top: 0.4em;
}
.blog .info, .blogpost .info {
font-style: italic;
font-size: 90%;
color: #666666;
padding-top: 0.4em;
}
/* dojo stuff */
.dijitDialog {
background-color: #aaaaaaa;
border: 1px solid #999;
padding: 5px;
}
.dijitDialogPaneContent {
background-color: #aaaaaaa;
}
.dijitDialog th {
font-size: 120%; font-size: 120%;
font-weight: bold;
text-align: center;
padding: 0 5px 8px 5px; padding: 0 5px 8px 5px;
} }
.dojoDialog .headline { .dijitDialog td {
padding: 2px;
}
.dijitDialog .headline {
font-weight: bold; font-weight: bold;
} }
.dojoDialog input.text { .dijitDialog input.text {
width: 100%; width: 100%;
margin-right: 10px; margin-right: 10px;
} }
.dojoDialog input.submit { .dijitDialog input.submit {
font-weight: bold; font-weight: bold;
} }
.dijitDialogUnderlay {
background-color: White;
}
div.RichTextEditable {
border-top: 2px solid grey;
border-left: 2px solid grey;
border-right: 2px solid #eeeeee;
border-bottom: 2px solid #eeeeee;
}

View file

@ -13,144 +13,122 @@ function focusOpener() {
} }
} }
function validate(nodeName, required) {
// (work in progress) - may be used for onBlur event handler
var w = dojo.byId(nodeName);
if (required && w.value == '') {
w.setAttribute('style','background-color: #ffff00');
w.focus();
return false;
}
}
function destroyWidgets(node) {
dojo.forEach(dojo.query('[widgetId]', node), function(n) {
w = dijit.byNode(n);
if (w != undefined) {
w.destroyRecursive();
}
});
}
function replaceNode(html, nodeName) {
var newNode = document.createElement('div');
newNode.innerHTML = html;
if (nodeName == undefined) {
nodeName = newNode.firstChild.getAttribute('id');
}
if (nodeName != undefined) {
newNode.id = nodeName;
var node = dojo.byId(nodeName);
destroyWidgets(node);
var parent = node.parentNode;
parent.replaceChild(newNode, node);
dojo.parser.parse(newNode);
} else {
window.location.href = url;
}
}
function updateNode(url, nodeName) {
dojo.xhrGet({
url: url,
handleAs: 'text',
load: function(response, ioArgs) {
replaceNode(response, nodeName);
}
});
}
function replaceFieldsNode(targetId, typeId, url) { function replaceFieldsNode(targetId, typeId, url) {
token = dojo.byId(typeId).value; token = dojo.byId(typeId).value;
uri = url + '?form.type=' + token; uri = url + '?form.type=' + token;
dojo.io.updateNode(targetId, uri); updateNode(uri, 'form.fields');
} }
function replaceFieldsNodeForLanguage(targetId, langId, url) { function replaceFieldsNodeForLanguage(targetId, langId, url) {
lang = dojo.byId(langId).value; lang = dojo.byId(langId).value;
uri = url + '?loops.language=' + lang; uri = url + '?loops.language=' + lang;
dojo.io.updateNode(targetId, uri); updateNode(uri, 'form.fields');
} }
function submitReplacing(targetId, formId, actionUrl) { function submitReplacing(targetId, formId, url) {
dojo.io.updateNode(targetId, { dojo.xhrPost({
url: actionUrl, url: url,
formNode: dojo.byId(formId), form: dojo.byId(formId),
method: 'post'
});
return false;
}
function xhrSubmitPopup(formId, actionUrl) {
dojo.io.bind({
url: actionUrl,
formNode: dojo.byId(formId),
method: 'post',
mimetype: "text/html", mimetype: "text/html",
load: function(t, d, e) { load: function(response, ioArgs) {
replaceNode(response, targetId);
}
})
}
function xhrSubmitPopup(formId, url) {
dojo.xhrPost({
url: url,
form: dojo.byId(formId),
mimetype: "text/html",
load: function(response, ioArgs) {
window.close(); window.close();
} }
}); });
} }
function submitReplacingOrReloading(targetId, formId, actionUrl) {
node = dojo.byId(targetId);
var args = {
url: actionUrl,
formNode: dojo.byId(formId),
method: 'post',
mimetype: "text/html"
};
args.load = function (t, d, e) {
if (d.length < 10) {
document.location.reload(false);
} else {
while (node.firstChild) {
dojo.dom.destroyNode(node.firstChild);
}
node.innerHTML = d;
}
};
dojo.io.bind(args);
return false;
}
function inlineEdit(id, saveUrl) {
var iconNode = dojo.byId('inlineedit_icon');
iconNode.style.visibility = 'hidden';
editor = dojo.widget.createWidget('Editor',
{items: ['save', '|', 'formatblock', '|',
'insertunorderedlist', 'insertorderedlist', '|',
'bold', 'italic', '|', 'createLink', 'insertimage'],
saveUrl: saveUrl,
//closeOnSave: true,
htmlEditing: true
//onClose: function() {
/* onSave: function() {
this.disableToolbar(true);
iconNode.style.visibility = 'visible';
//window.location.reload();
}*/
}, dojo.byId(id));
editor._save = function (e) {
if (!this._richText.isClosed) {
if (this.saveUrl.length) {
var content = {};
this._richText.contentFilters = [];
content[this.saveArgName] = this.getHtml();
content['version'] = 'this';
dojo.io.bind({method:this.saveMethod,
url:this.saveUrl,
content:content,
handle:function(type, data, ti, kwargs) {
location.reload(false);
}
}); //alert('save');
} else {
dojo.debug("please set a saveUrl for the editor");
}
if (this.closeOnSave) {
this._richText.close(e.getName().toLowerCase() == "save");
}
}
}
return false;
}
function setConceptTypeForComboBox(typeId, cbId) { function setConceptTypeForComboBox(typeId, cbId) {
var t = dojo.byId(typeId).value; var t = dojo.byId(typeId).value;
var cb = dojo.widget.manager.getWidgetById(cbId); var cb = dijit.byId(cbId);
var dp = cb.dataProvider; var dp = cb.store;
var baseUrl = dp.searchUrl.split('&')[0]; var baseUrl = dp.url.split('?')[0];
var newUrl = baseUrl + '&searchType=' + t; var newUrl = baseUrl + '?searchType=' + t;
dp.searchUrl = newUrl; dp.url = newUrl;
cb.setValue(''); cb.setDisplayedValue('');
} }
var dialogs = {} var dialog;
var dialogName
function objectDialog(dlgName, url) { function objectDialog(dlgName, url) {
dojo.require('dojo.widget.Dialog'); dojo.require('dijit.Dialog');
dojo.require('dojo.widget.ComboBox'); dojo.require('dojo.parser');
dlg = dialogs[dlgName]; dojo.require('dijit.form.FilteringSelect');
if (!dlg) { dojo.require('dojox.data.QueryReadStore');
//dlg = dojo.widget.fromScript('Dialog', if (dialogName == undefined || dialogName != dlgName) {
dlg = dojo.widget.createWidget('Dialog', if (dialog != undefined) {
{bgColor: 'white', bgOpacity: 0.5, toggle: 'fade', dialog.destroyRecursive();
toggleDuration: 250, }
executeScripts: true, dialogName = dlgName;
href: url dialog = new dijit.Dialog({
}, dojo.byId('dialog.' + dlgName)); href: url
dialogs[dlgName] = dlg; }, dojo.byId('dialog.' + dlgName));
} }
dlg.show(); dialog.show();
} }
function addConceptAssignment(prefix, suffix) { function addConceptAssignment(prefix, suffix) {
dojo.require('dojo.html')
node = dojo.byId('form.' + suffix); node = dojo.byId('form.' + suffix);
els = document.forms[0].elements; widget = dijit.byId(prefix + '.search.text');
for (var i=0; i<els.length; i++) { //getElementsByName does not work here in IE cToken = widget.getValue();
el = els[i]; title = widget.getDisplayedValue();
if (el.name == prefix + '.search.text_selected') {
cToken = el.value;
} else if (el.name == prefix + '.search.text') {
title = el.value;
}
}
if (cToken.length == 0) { if (cToken.length == 0) {
alert('Please select a concept!'); alert('Please select a concept!');
return false; return false;
@ -159,9 +137,29 @@ function addConceptAssignment(prefix, suffix) {
token = cToken + ':' + pToken; token = cToken + ':' + pToken;
var td = document.createElement('td'); var td = document.createElement('td');
td.colSpan = 5; td.colSpan = 5;
td.innerHTML = '<input type="checkbox" name="form.' + suffix + '.selected:list" value="' + token + '" checked><span>' + title + '</span>'; td.innerHTML = '<input type="checkbox" name="form.' + suffix + '.selected:list" value="' + token + '" checked> <span>' + title + '</span>';
var tr = document.createElement('tr'); var tr = document.createElement('tr');
tr.appendChild(td); tr.appendChild(td);
node.appendChild(tr); node.appendChild(tr);
} }
function closeDataWidget(save) {
var widget = dijit.byId('data');
if (widget != undefined && save) {
value = widget.getValue();
//widget.close(save);
form = dojo.byId('dialog_form');
var ta = document.createElement('textarea');
ta.name = 'data';
ta.value = value;
form.appendChild(ta);
}
}
var editor;
function inlineEdit(id) {
if (editor == undefined) {
editor = new dijit.Editor({}, dojo.byId(id));
}
}

View file

@ -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);
}

View file

@ -16,7 +16,7 @@
<metal:block fill-slot="ecmascript_slot" <metal:block fill-slot="ecmascript_slot"
tal:condition="view/inlineEditingActive | nothing"> tal:condition="view/inlineEditingActive | nothing">
<script> <script>
dojo.require("dojo.widget.Editor"); dojo.require("dijit.Editor");
</script> </script>
</metal:block> </metal:block>

View file

@ -44,16 +44,17 @@ from zope.security.proxy import removeSecurityProxy
from cybertools.ajax import innerHtml from cybertools.ajax import innerHtml
from cybertools.browser import configurator from cybertools.browser import configurator
from cybertools.browser.action import Action
from cybertools.browser.view import GenericView from cybertools.browser.view import GenericView
from cybertools.typology.interfaces import IType, ITypeManager from cybertools.typology.interfaces import IType, ITypeManager
from cybertools.xedit.browser import ExternalEditorView from cybertools.xedit.browser import ExternalEditorView
from loops.browser.action import DialogAction
from loops.i18n.browser import i18n_macros from loops.i18n.browser import i18n_macros
from loops.interfaces import IConcept, IResource, IDocument, IMediaAsset, INode from loops.interfaces import IConcept, IResource, IDocument, IMediaAsset, INode
from loops.interfaces import IViewConfiguratorSchema from loops.interfaces import IViewConfiguratorSchema
from loops.resource import MediaAsset from loops.resource import MediaAsset
from loops import util from loops import util
from loops.util import _ from loops.util import _
from loops.browser.action import Action, DialogAction, TargetAction
from loops.browser.common import BaseView from loops.browser.common import BaseView
from loops.browser.concept import ConceptView from loops.browser.concept import ConceptView
from loops.versioning.util import getVersion from loops.versioning.util import getVersion
@ -65,8 +66,8 @@ node_macros = ViewPageTemplateFile('node_macros.pt')
class NodeView(BaseView): class NodeView(BaseView):
_itemNum = 0 _itemNum = 0
template = node_macros template = node_macros
nextUrl = None
def __init__(self, context, request): def __init__(self, context, request):
super(NodeView, self).__init__(context, request) super(NodeView, self).__init__(context, request)
@ -80,23 +81,31 @@ class NodeView(BaseView):
def setupController(self): def setupController(self):
cm = self.controller.macros cm = self.controller.macros
cm.register('css', identifier='loops.css', resourceName='loops.css', cm.register('css', identifier='loops.css', resourceName='loops.css',
media='all', position=3) media='all', priority=60)
cm.register('js', 'loops.js', resourceName='loops.js') cm.register('js', 'loops.js', resourceName='loops.js', priority=60)
#cm.register('js', 'loops.js', resourceName='loops1.js')
cm.register('top_actions', 'top_actions', name='multi_actions', cm.register('top_actions', 'top_actions', name='multi_actions',
subMacros=[i18n_macros.macros['language_switch']]) subMacros=[i18n_macros.macros['language_switch']])
cm.register('portlet_left', 'navigation', title='Navigation', cm.register('portlet_left', 'navigation', title='Navigation',
subMacro=node_macros.macros['menu']) subMacro=node_macros.macros['menu'])
#if not IUnauthenticatedPrincipal.providedBy(self.request.principal):
if canWrite(self.context, 'title'): if canWrite(self.context, 'title'):
#cm.register('portlet_right', 'clipboard', title='Clipboard', #cm.register('portlet_right', 'clipboard', title='Clipboard',
# subMacro=self.template.macros['clipboard']) # subMacro=self.template.macros['clipboard'])
# this belongs to loops.organize; how to register portlets # this belongs to loops.organize
# from sub- (other) packages?
# see controller / configurator: use multiple configurators;
# register additional configurators (adapters) from within package.
cm.register('portlet_right', 'actions', title=_(u'Actions'), cm.register('portlet_right', 'actions', title=_(u'Actions'),
subMacro=node_macros.macros['actions']) subMacro=node_macros.macros['actions'],
priority=100)
if not IUnauthenticatedPrincipal.providedBy(self.request.principal):
mi = self.controller.memberInfo
title = mi.title.value or _(u'Personal Informations')
obj = mi.get('object')
url = obj is not None and self.getUrlForTarget(obj.value) or None
cm.register('portlet_right', 'personal', title=title,
subMacro=node_macros.macros['personal'],
icon='cybertools.icons/user.png',
url=url,
priority=10)
# force early portlet registrations by target by setting up target view
self.virtualTarget
# force early portlet registrations by target by setting up target view # force early portlet registrations by target by setting up target view
self.virtualTarget self.virtualTarget
@ -117,7 +126,7 @@ class NodeView(BaseView):
#target = self.virtualTargetObject # ignores page even for direktly assignd target #target = self.virtualTargetObject # ignores page even for direktly assignd target
target = self.request.annotations.get('loops.view', {}).get('target') target = self.request.annotations.get('loops.view', {}).get('target')
if target is not None: if target is not None:
basicView = zapi.getMultiAdapter((target, self.request), name=viewName) basicView = component.getMultiAdapter((target, self.request), name=viewName)
# xxx: obsolete when self.targetObject is virtual target: # xxx: obsolete when self.targetObject is virtual target:
return basicView.view return basicView.view
return self.page return self.page
@ -154,7 +163,7 @@ class NodeView(BaseView):
if text.startswith('<'): # seems to be HTML if text.startswith('<'): # seems to be HTML
return text return text
source = zapi.createObject(self.context.contentType, text) source = zapi.createObject(self.context.contentType, text)
view = zapi.getMultiAdapter((removeAllProxies(source), self.request)) view = component.getMultiAdapter((removeAllProxies(source), self.request))
return view.render() return view.render()
@Lazy @Lazy
@ -170,7 +179,7 @@ class NodeView(BaseView):
def targetObjectView(self): def targetObjectView(self):
obj = self.targetObject obj = self.targetObject
if obj is not None: if obj is not None:
basicView = zapi.getMultiAdapter((obj, self.request)) basicView = component.getMultiAdapter((obj, self.request))
basicView._viewName = self.context.viewName basicView._viewName = self.context.viewName
return basicView.view return basicView.view
@ -315,7 +324,7 @@ class NodeView(BaseView):
def virtualTarget(self): def virtualTarget(self):
obj = self.virtualTargetObject obj = self.virtualTargetObject
if obj is not None: if obj is not None:
basicView = zapi.getMultiAdapter((obj, self.request)) basicView = component.getMultiAdapter((obj, self.request))
if obj == self.targetObject: if obj == self.targetObject:
basicView._viewName = self.context.viewName basicView._viewName = self.context.viewName
return basicView.view return basicView.view
@ -376,8 +385,6 @@ class NodeView(BaseView):
actions = dict(portlet=getPortletActions) actions = dict(portlet=getPortletActions)
nextUrl = None
@Lazy @Lazy
def popupCreateObjectForm(self): def popupCreateObjectForm(self):
return ("javascript:function%%20openDialog(url){" return ("javascript:function%%20openDialog(url){"
@ -401,11 +408,19 @@ class NodeView(BaseView):
def inlineEdit(self, id): def inlineEdit(self, id):
self.registerDojo() self.registerDojo()
cm = self.controller.macros cm = self.controller.macros
jsCall = 'dojo.require("dojo.widget.Editor")' jsCall = 'dojo.require("dijit.Editor")'
cm.register('js-execute', jsCall, jsCall=jsCall) cm.register('js-execute', jsCall, jsCall=jsCall)
return ('return inlineEdit("%s", "%s/inline_save")' return ('return inlineEdit("%s", "%s/inline_save")'
% (id, self.virtualTargetUrl)) % (id, self.virtualTargetUrl))
def checkRTE(self):
target = self.virtualTarget
if target and target.inlineEditable:
self.registerDojo()
cm = self.controller.macros
jsCall = 'dojo.require("dijit.Editor")'
cm.register('js-execute', jsCall, jsCall=jsCall)
def externalEdit(self): def externalEdit(self):
target = self.virtualTargetObject target = self.virtualTargetObject
if target is None: if target is None:
@ -423,7 +438,7 @@ class NodeView(BaseView):
def registerDojoDialog(self): def registerDojoDialog(self):
self.registerDojo() self.registerDojo()
cm = self.controller.macros cm = self.controller.macros
jsCall = 'dojo.require("dojo.widget.Dialog")' jsCall = 'dojo.require("dijit.Dialog")'
cm.register('js-execute', jsCall, jsCall=jsCall) cm.register('js-execute', jsCall, jsCall=jsCall)
@ -527,7 +542,7 @@ class ConfigureView(NodeView):
def target(self): def target(self):
obj = self.targetObject obj = self.targetObject
if obj is not None: if obj is not None:
return zapi.getMultiAdapter((obj, self.request)) return component.getMultiAdapter((obj, self.request))
def update(self): def update(self):
request = self.request request = self.request
@ -604,7 +619,7 @@ class ConfigureView(NodeView):
else: else:
start = end = searchType start = end = searchType
criteria['loops_type'] = (start, end) criteria['loops_type'] = (start, end)
cat = zapi.getUtility(ICatalog) cat = component.getUtility(ICatalog)
result = cat.searchResults(**criteria) result = cat.searchResults(**criteria)
# TODO: can this be done in a faster way? # TODO: can this be done in a faster way?
result = [r for r in result if r.getLoopsRoot() == self.loopsRoot] result = [r for r in result if r.getLoopsRoot() == self.loopsRoot]
@ -664,7 +679,7 @@ class ViewPropertiesConfigurator(object):
options = property(getOptions, setOptions) options = property(getOptions, setOptions)
class NodeViewConfigurator(configurator.ViewConfigurator): class NodeViewConfigurator(configurator.AnnotationViewConfigurator):
""" Take properties from next menu item... """ Take properties from next menu item...
""" """

View file

@ -27,24 +27,25 @@
tal:condition="nocall:target"> tal:condition="nocall:target">
<tal:ignore condition="nothing"> <tal:ignore condition="nothing">
<div metal:define-macro="editicons" <div metal:define-macro="editicons"
style="float: right; border: 1px solid #ccc; style="float: right;">
margin-top: 1em">
<span id="xedit_icon" <span id="xedit_icon"
tal:condition="target/xeditable | nothing"> tal:condition="target/xeditable | nothing">
<a href="#" title="Edit" style="padding: 5px" <a href="#" title="Edit"
tal:attributes="href string:${item/realTargetUrl}/external_edit?version=this; tal:attributes="href string:${item/realTargetUrl}/external_edit?version=this;
title string:Edit '${target/title}' with External Editor"><img title string:Edit '${target/title}' with External Editor"><img
src="edit.gif" alt="Edit" src="edit.gif" alt="Edit"
tal:attributes="src context/++resource++edit.gif" /></a> tal:attributes="src context/++resource++edit.gif" /></a>
</span> </span>
<span id="inlineedit_icon" <span id="inlineedit_icon"
tal:condition="item/inlineEditable"> xtal:condition="item/inlineEditable"
tal:condition="nothing">
<a href="#" title="Edit" style="padding: 5px" <a href="#" title="Edit" style="padding: 5px"
tal:attributes="title string:Edit '${target/title}' with Inline Editor; tal:attributes="title string:Edit '${target/title}' with Inline Editor;
onclick python: item.inlineEdit(id)"><img onclick python: item.inlineEdit(id)"><img
src="edit.gif" alt="Edit" src="edit.gif" alt="Edit"
tal:attributes="src context/++resource++edit.gif" /></a> tal:attributes="src context/++resource++edit.gif" /></a>
</span> </span>
<tal:rte define="dummy item/checkRTE" />
</div> </div>
</tal:ignore> </tal:ignore>
<div class="content-1 subcolumn" id="1.body" <div class="content-1 subcolumn" id="1.body"
@ -178,7 +179,7 @@
<metal:menu define-macro="menu" <metal:menu define-macro="menu"
tal:define="item nocall:view/menu | nothing; tal:define="item nocall:view/menu | nothing;
level level|python: 1" level level|python: 1;"
tal:condition="nocall:item"> tal:condition="nocall:item">
<metal:sub define-macro="submenu"> <metal:sub define-macro="submenu">
<div class="menu-3" <div class="menu-3"
@ -219,6 +220,14 @@
</metal:actions> </metal:actions>
<metal:actions define-macro="personal">
<div><a href="logout.html" i18n:translate="">Log out</a></div>
<tal:actions repeat="action python:view.getActions('personal')">
<metal:action use-macro="action/macro" />
</tal:actions>
</metal:actions>
<!-- inner HTML macros --> <!-- inner HTML macros -->
<div metal:define-macro="inline_edit" <div metal:define-macro="inline_edit"

View file

@ -25,7 +25,6 @@ $Id$
import urllib import urllib
from zope.cachedescriptors.property import Lazy from zope.cachedescriptors.property import Lazy
from zope import component from zope import component
from zope.app import zapi
from zope.app.catalog.interfaces import ICatalog from zope.app.catalog.interfaces import ICatalog
from zope.app.container.interfaces import INameChooser from zope.app.container.interfaces import INameChooser
from zope.app.form.browser.textwidgets import FileWidget from zope.app.form.browser.textwidgets import FileWidget
@ -38,14 +37,14 @@ from zope.schema.interfaces import IBytes
from zope.security import canAccess, canWrite from zope.security import canAccess, canWrite
from zope.security.proxy import removeSecurityProxy from zope.security.proxy import removeSecurityProxy
from zope.traversing.api import getName, getParent from zope.traversing.api import getName, getParent
from zope.traversing.browser import absoluteURL
from cybertools.typology.interfaces import IType from cybertools.typology.interfaces import IType
from cybertools.xedit.browser import ExternalEditorView, fromUnicode from cybertools.xedit.browser import ExternalEditorView, fromUnicode
from loops.browser.action import Action, DialogAction, TargetAction from loops.browser.action import DialogAction, TargetAction
from loops.browser.common import EditForm, BaseView from loops.browser.common import EditForm, BaseView
from loops.browser.concept import ConceptRelationView, ConceptConfigureView from loops.browser.concept import ConceptRelationView, ConceptConfigureView
from loops.browser.node import NodeView, node_macros from loops.browser.node import NodeView, node_macros
from loops.browser.util import html_quote
from loops.common import adapted, NameChooser from loops.common import adapted, NameChooser
from loops.interfaces import IBaseResource, IDocument, IMediaAsset, ITextDocument from loops.interfaces import IBaseResource, IDocument, IMediaAsset, ITextDocument
from loops.interfaces import ITypeConcept from loops.interfaces import ITypeConcept
@ -53,14 +52,6 @@ from loops.versioning.browser import version_macros
from loops.versioning.interfaces import IVersionable from loops.versioning.interfaces import IVersionable
from loops.util import _ from loops.util import _
renderingFactories = {
'text/plain': 'zope.source.plaintext',
'text/stx': 'zope.source.stx',
'text/structured': 'zope.source.stx',
'text/rest': 'zope.source.rest',
'text/restructured': 'zope.source.rest',
}
class CustomFileWidget(FileWidget): class CustomFileWidget(FileWidget):
@ -120,16 +111,18 @@ class ResourceView(BaseView):
super(ResourceView, self).__init__(context, request) super(ResourceView, self).__init__(context, request)
if not IUnauthenticatedPrincipal.providedBy(self.request.principal): if not IUnauthenticatedPrincipal.providedBy(self.request.principal):
cont = self.controller cont = self.controller
if cont is not None and list(self.relatedConcepts()): if cont is not None:
cont.macros.register('portlet_right', 'related', title=_(u'Related Items'), if list(self.relatedConcepts()):
subMacro=self.template.macros['related'], cont.macros.register('portlet_right', 'related',
position=0, info=self) title=_(u'Related Items'),
subMacro=self.template.macros['related'],
priority=20, info=self)
versionable = IVersionable(self.context, None) versionable = IVersionable(self.context, None)
if versionable is not None and len(versionable.versions) > 1: if versionable is not None and len(versionable.versions) > 1:
cont.macros.register('portlet_right', 'versions', cont.macros.register('portlet_right', 'versions',
title='Version ' + versionable.versionId, title='Version ' + versionable.versionId,
subMacro=version_macros.macros['portlet_versions'], subMacro=version_macros.macros['portlet_versions'],
position=1, info=self) priority=25, info=self)
@Lazy @Lazy
def view(self): def view(self):
@ -150,10 +143,6 @@ class ResourceView(BaseView):
def show(self, useAttachment=False): def show(self, useAttachment=False):
""" show means: "download"...""" """ show means: "download"..."""
#data = self.openForView()
#response.setHeader('Content-Disposition',
# 'attachment; filename=%s' % zapi.getName(self.context))
#return data
context = self.context context = self.context
ti = IType(context).typeInterface ti = IType(context).typeInterface
if ti is not None: if ti is not None:
@ -222,6 +211,10 @@ class ResourceView(BaseView):
class ResourceConfigureView(ResourceView, ConceptConfigureView): class ResourceConfigureView(ResourceView, ConceptConfigureView):
#def __init__(self, context, request):
# # avoid calling ConceptView.__init__()
# ResourceView.__init__(self, context, request)
def update(self): def update(self):
request = self.request request = self.request
action = request.get('action') action = request.get('action')
@ -269,7 +262,7 @@ class ResourceConfigureView(ResourceView, ConceptConfigureView):
else: else:
start = end = searchType start = end = searchType
criteria['loops_type'] = (start, end) criteria['loops_type'] = (start, end)
cat = zapi.getUtility(ICatalog) cat = component.getUtility(ICatalog)
result = cat.searchResults(**criteria) result = cat.searchResults(**criteria)
# TODO: can this be done in a faster way? # TODO: can this be done in a faster way?
result = [r for r in result if r.getLoopsRoot() == self.loopsRoot] result = [r for r in result if r.getLoopsRoot() == self.loopsRoot]
@ -292,19 +285,10 @@ class DocumentView(ResourceView):
def render(self): def render(self):
""" Return the rendered content (data) of the context object. """ Return the rendered content (data) of the context object.
""" """
#text = self.context.data
ctx = adapted(self.context) ctx = adapted(self.context)
text = ctx.data text = ctx.data
#contentType = self.context.contentType
contentType = ctx.contentType contentType = ctx.contentType
typeKey = renderingFactories.get(contentType, None) return self.renderText(ctx.data, ctx.contentType)
if typeKey is None:
if contentType == u'text/html':
return text
return u'<pre>%s</pre>' % html_quote(text)
source = zapi.createObject(typeKey, text)
view = zapi.getMultiAdapter((removeAllProxies(source), self.request))
return view.render()
@Lazy @Lazy
def inlineEditable(self): def inlineEditable(self):
@ -316,10 +300,12 @@ class DocumentView(ResourceView):
class ExternalEditorView(ExternalEditorView): class ExternalEditorView(ExternalEditorView):
def load(self, url=None): def load(self, url=None):
context = removeSecurityProxy(self.context) #context = removeSecurityProxy(self.context)
context = self.context
data = adapted(context).data data = adapted(context).data
r = [] r = []
r.append('url:' + (url or zapi.absoluteURL(context, self.request))) context = removeSecurityProxy(context)
r.append('url:' + (url or absoluteURL(context, self.request)))
r.append('content_type:' + str(context.contentType)) r.append('content_type:' + str(context.contentType))
r.append('meta_type:' + '.'.join((context.__module__, context.__class__.__name__))) r.append('meta_type:' + '.'.join((context.__module__, context.__class__.__name__)))
auth = self.request.get('_auth') auth = self.request.get('_auth')

View file

@ -10,6 +10,11 @@
<metal:footer fill-slot="footer"> <metal:footer fill-slot="footer">
<div tal:condition="view/editable">
<span i18n:translate="">For quick creation of notes/links bookmark this link</span>:
<a href="#"
tal:attributes="href view/popupCreateObjectForm">Create loops Note</a>
</div>
Powered by <b><a href="http://www.python.org">Python</a></b> &middot; Powered by <b><a href="http://www.python.org">Python</a></b> &middot;
<b><a href="http://wiki.zope.org/zope3">Zope 3</a></b> &middot; <b><a href="http://wiki.zope.org/zope3">Zope 3</a></b> &middot;
<b><a href="http://loops.cy55.de">loops</a></b>. <b><a href="http://loops.cy55.de">loops</a></b>.

View file

@ -12,9 +12,12 @@ Let's set up a loops site with basic and example concepts and resources.
>>> from zope.app.testing.setup import placefulSetUp, placefulTearDown >>> from zope.app.testing.setup import placefulSetUp, placefulTearDown
>>> site = placefulSetUp(True) >>> site = placefulSetUp(True)
>>> from loops.organize.setup import SetupManager
>>> component.provideAdapter(SetupManager, name='organize')
>>> from loops.tests.setup import TestSite >>> from loops.tests.setup import TestSite
>>> t = TestSite(site) >>> t = TestSite(site)
>>> concepts, resources, views = t.setup() >>> concepts, resources, views = t.setup()
>>> loopsRoot = site['loops']
Compund Objects - Hierarchies with Ordered Components Compund Objects - Hierarchies with Ordered Components
@ -74,3 +77,244 @@ And remove a part from the compound.
>>> [getName(p) for p in aC01.getParts()] >>> [getName(p) for p in aC01.getParts()]
[u'd002.txt', u'd003.txt', u'd001.txt'] [u'd002.txt', u'd003.txt', u'd001.txt']
Blogs
=====
>>> from loops.compound.blog.post import BlogPost
>>> from loops.compound.blog.interfaces import IBlogPost
>>> component.provideAdapter(BlogPost, provides=IBlogPost)
>>> tBlog = addAndConfigureObject(concepts, Concept, 'blog', title=u'Blog',
... conceptType=tType)
>>> tBlogPost = addAndConfigureObject(concepts, Concept, 'blogpost',
... title=u'Blog Post', conceptType=tType,
... typeInterface=IBlogPost)
>>> myBlog = addAndConfigureObject(concepts, Concept, 'myblog', title=u'My Blog',
... conceptType=tBlog)
>>> firstPost = addAndConfigureObject(concepts, Concept, 'firstpost',
... title=u'My first post', conceptType=tBlogPost)
>>> aFirstPost = adapted(firstPost)
>>> aFirstPost.date
>>> aFirstPost.text = u'My first blog post.'
>>> aFirstPost.text
u'My first blog post.'
>>> aFirstPost.creator
Blog and BlogPost views
-----------------------
>>> from loops.compound.blog.browser import BlogView, BlogPostView
>>> #from zope.publisher.browser import TestRequest
>>> from loops.tests.auth import TestRequest
The blog view automatically provides a portlet action for creating
a new post.
>>> view = BlogView(myBlog, TestRequest())
>>> for act in view.getActions('portlet'):
... print act.name
createBlogPost
>>> view = BlogPostView(firstPost, TestRequest())
>>> data = view.data
Automatic assignment of a blog post to the personal blog of its owner
---------------------------------------------------------------------
As all the following stuff relies on a blog being assigned to a person
we need the corresponding scaffolding from the loops.organize package.
>>> from loops.organize.tests import setupUtilitiesAndAdapters
>>> setupData = setupUtilitiesAndAdapters(loopsRoot)
We also have to set up some components for automatic setting of
security properties upon object creation.
>>> from loops.security.common import setDefaultSecurity
>>> component.provideHandler(setDefaultSecurity)
>>> from loops.compound.blog.security import BlogPostSecuritySetter
>>> component.provideAdapter(BlogPostSecuritySetter)
Let's start with defining a user (more precisely: a principal)
and a corresponding person.
>>> auth = setupData.auth
>>> tPerson = concepts['person']
>>> userJohn = auth.definePrincipal('users.john', u'John', login='john')
>>> persJohn = addAndConfigureObject(concepts, Concept, 'person.john',
... title=u'John Smith', conceptType=tPerson,
... userId='users.john')
>>> blogJohn = addAndConfigureObject(concepts, Concept, 'blog.john',
... title=u'John\'s Blog', conceptType=tBlog)
>>> persJohn.assignChild(blogJohn)
Let's now login as the newly defined user.
>>> from loops.tests.auth import login
>>> login(userJohn)
Let's also provide some general permission settings. These are necessary
as after logging in the permissions of the user will be checked by the
standard checker defined in the test setup.
>>> grantPermission = setupData.rolePermissions.grantPermissionToRole
>>> assignRole = setupData.principalRoles.assignRoleToPrincipal
>>> grantPermission('zope.View', 'zope.Member')
>>> grantPermission('zope.View', 'loops.Owner')
>>> grantPermission('zope.ManageContent', 'zope.ContentManager')
>>> grantPermission('loops.ViewRestricted', 'loops.Owner')
>>> assignRole('zope.Member', 'users.john')
The automatic assignment of the blog post is done in the form controller
used for creating the blog post.
>>> from loops.compound.blog.browser import CreateBlogPostForm, CreateBlogPost
>>> input = {'title': u'John\'s first post', 'text': u'Text of John\'s post',
... 'date': '2008-02-02T15:54:11',
... 'privateComment': u'John\'s private comment',
... 'form.type': '.loops/concepts/blogpost'}
>>> cbpForm = CreateBlogPostForm(myBlog, TestRequest(form=input))
>>> cbpController = CreateBlogPost(cbpForm, cbpForm.request)
>>> cbpController.update()
False
>>> posts = blogJohn.getChildren()
>>> len(posts)
1
>>> postJohn0 = posts[0]
>>> postJohn0.title
u"John's first post"
>>> postJohn0Text = adapted(postJohn0.getResources()[0])
>>> postJohn0Text.data
u"Text of John's post"
Security
========
We first have to define some checkers that will be invoked when checking access
to attributes.
>>> from zope.security.checker import Checker, defineChecker
>>> checker = Checker(dict(title='zope.View', privateComment='loops.ViewRestricted'),
... dict(title='zope.ManageContent',
... privateComment='zope.ManageContent'))
>>> #defineChecker(Concept, checker)
>>> defineChecker(BlogPost, checker)
>>> from loops.resource import Resource, TextDocumentAdapter
>>> checker = Checker(dict(title='zope.View', data='zope.View'),
... dict(title='zope.ManageContent', data='zope.ManageContent'))
>>> #defineChecker(Resource, checker)
>>> defineChecker(TextDocumentAdapter, checker)
Standard security settings for blogs
------------------------------------
TODO...
A personal blog is a blog that is a direct child of a person with
an associated principal (i.e. a user id).
Blog posts in a personal blog can only be created by the owner of the blog.
More generally: A personal blog may receive only blog posts as children
that have the same owner as the blog itself.
A personal blog may only be assigned to other parents by the owner of
the blog.
Standard security settings for blog posts
-----------------------------------------
Blog posts may (only!) be edited by their owner (i.e. only the owner
has the ManageContent permission). (TODO: Also their parent assignments may be
changed only by the owner).
Note that we still are logged-in as user John.
>>> from zope.security import canAccess, canWrite, checkPermission
>>> canAccess(postJohn0, 'title')
True
>>> canWrite(postJohn0, 'title')
True
This settings are also valid for children/resources that are assigned
via an `is Part of` relation.
>>> canAccess(postJohn0Text, 'data')
True
>>> canWrite(postJohn0Text, 'data')
True
The private comment is only visible (and editable, of course) for the
owner of the blog post.
>>> aPostJohn0 = adapted(postJohn0)
>>> canAccess(aPostJohn0, 'privateComment')
True
>>> canWrite(aPostJohn0, 'privateComment')
True
So let's now switch to another user. On a global level, Martha also has
the ContentManager role, i.e. she is allowed to edit content objects.
Nevertheless she is not allowed to change John's blog post.
>>> userMartha = auth.definePrincipal('users.martha', u'Martha', login='martha')
>>> assignRole('zope.Member', 'users.martha')
>>> assignRole('zope.ContentManager', 'users.martha')
>>> login(userMartha)
>>> canAccess(postJohn0, 'title')
True
>>> canWrite(postJohn0, 'title')
False
>>> canAccess(postJohn0Text, 'data')
True
>>> canWrite(postJohn0Text, 'data')
False
>>> canAccess(aPostJohn0, 'privateComment')
False
>>> canWrite(aPostJohn0, 'privateComment')
False
A blog post marked as private is only visible for its owner.
>>> login(userJohn)
>>> aPostJohn0.private = True
>>> canAccess(postJohn0, 'title')
True
>>> canAccess(postJohn0Text, 'data')
True
>>> login(userMartha)
>>> canAccess(postJohn0, 'data')
False
>>> canAccess(postJohn0Text, 'data')
False
When we clear the `private` flag the post becomes visible again.
>>> aPostJohn0.private = False
>>> canAccess(postJohn0, 'title')
True
>>> canAccess(postJohn0Text, 'title')
True
Fin de partie
=============
>>> placefulTearDown()

View file

@ -34,22 +34,28 @@ class Compound(AdapterBase):
implements(ICompound) implements(ICompound)
@Lazy
def compoundPredicate(self):
return self.context.getConceptManager()[compoundPredicateName]
def getParts(self): def getParts(self):
return self.context.getChildren([self.partOf]) if self.context.__parent__ is None:
return []
return self.context.getResources([self.partOf])
def add(self, obj, position=None): def add(self, obj, position=None):
if position is None: if position is None:
order = self.getMaxOrder() + 1 order = self.getMaxOrder() + 1
else: else:
order = self.getOrderForPosition(position) order = self.getOrderForPosition(position)
self.context.assignChild(obj, self.partOf, order=order) self.context.assignResource(obj, self.partOf, order=order)
def remove(self, obj, position=None): def remove(self, obj, position=None):
if position is None: if position is None:
self.context.deassignChild(obj, [self.partOf]) self.context.deassignResource(obj, [self.partOf])
else: else:
rel = self.getPartRelations()[position] rel = self.getPartRelations()[position]
self.context.deassignChild(obj, [self.partOf], order=rel.order) self.context.deassignResource(obj, [self.partOf], order=rel.order)
def reorder(self, parts): def reorder(self, parts):
existing = list(self.getPartRelations()) existing = list(self.getPartRelations())
@ -71,7 +77,7 @@ class Compound(AdapterBase):
# helper methods and properties # helper methods and properties
def getPartRelations(self): def getPartRelations(self):
return self.context.getChildRelations([self.partOf]) return self.context.getResourceRelations([self.partOf])
def getMaxOrder(self): def getMaxOrder(self):
rels = self. getPartRelations() rels = self. getPartRelations()
@ -105,6 +111,10 @@ class Compound(AdapterBase):
def conceptManager(self): def conceptManager(self):
return self.context.getConceptManager() return self.context.getConceptManager()
@Lazy
def resourceManager(self):
return self.getLoopsRoot().getResourceManager()
@Lazy @Lazy
def partOf(self): def partOf(self):
return self.conceptManager[compoundPredicateName] return self.conceptManager[compoundPredicateName]

View file

@ -0,0 +1,4 @@
"""
$Id$
"""

190
compound/blog/browser.py Executable file
View 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)

View 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>

View 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
View 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
View 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
View 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
View 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>

View file

@ -28,13 +28,14 @@ $Id$
from zope.interface import Interface, Attribute from zope.interface import Interface, Attribute
from zope import interface, component, schema from zope import interface, component, schema
from loops.interfaces import IConceptSchema
from loops.util import _ from loops.util import _
compoundPredicateName = 'ispartof' compoundPredicateName = 'ispartof'
class ICompound(Interface): class ICompound(IConceptSchema):
""" A compound is a concept that is built up of other objects, its """ A compound is a concept that is built up of other objects, its
parts or components. parts or components.

View file

@ -1,5 +1,5 @@
# #
# Copyright (c) 2007 Helmut Merz helmutm@cy55.de # Copyright (c) 2008 Helmut Merz helmutm@cy55.de
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -27,6 +27,8 @@ from zope.app.container.btree import BTreeContainer
from zope.app.container.contained import Contained from zope.app.container.contained import Contained
from zope.cachedescriptors.property import Lazy from zope.cachedescriptors.property import Lazy
from zope.component import adapts from zope.component import adapts
from zope.component.interfaces import ObjectEvent
from zope.event import notify
from zope.interface import implements from zope.interface import implements
from zope.interface import alsoProvides, directlyProvides, directlyProvidedBy from zope.interface import alsoProvides, directlyProvides, directlyProvidedBy
from zope.security.proxy import removeSecurityProxy, isinstance from zope.security.proxy import removeSecurityProxy, isinstance
@ -35,7 +37,6 @@ from persistent import Persistent
from cybertools.relation import DyadicRelation from cybertools.relation import DyadicRelation
from cybertools.relation.registry import getRelations from cybertools.relation.registry import getRelations
from cybertools.relation.registry import getRelationSingle, setRelationSingle
from cybertools.relation.interfaces import IRelationRegistry, IRelatable from cybertools.relation.interfaces import IRelationRegistry, IRelatable
from cybertools.typology.interfaces import IType, ITypeManager from cybertools.typology.interfaces import IType, ITypeManager
from cybertools.util.jeep import Jeep from cybertools.util.jeep import Jeep
@ -46,6 +47,8 @@ from loops.interfaces import IConcept, IConceptRelation, IConceptView
from loops.interfaces import IConceptManager, IConceptManagerContained from loops.interfaces import IConceptManager, IConceptManagerContained
from loops.interfaces import ILoopsContained from loops.interfaces import ILoopsContained
from loops.interfaces import IIndexAttributes from loops.interfaces import IIndexAttributes
from loops.interfaces import IAssignmentEvent, IDeassignmentEvent
from loops.security.common import canListObject
from loops import util from loops import util
from loops.view import TargetRelation from loops.view import TargetRelation
@ -159,29 +162,35 @@ class Concept(Contained, Persistent):
if relationships is None: if relationships is None:
relationships = [TargetRelation] relationships = [TargetRelation]
rels = getRelations(second=self, relationships=relationships) rels = getRelations(second=self, relationships=relationships)
return [r.first for r in rels] return [r.first for r in rels if canListObject(r.first)]
def getChildRelations(self, predicates=None, child=None, sort='default'): def getChildRelations(self, predicates=None, child=None, sort='default',
noSecurityCheck=False):
predicates = predicates is None and ['*'] or predicates predicates = predicates is None and ['*'] or predicates
relationships = [ConceptRelation(self, None, p) for p in predicates] relationships = [ConceptRelation(self, None, p) for p in predicates]
if sort == 'default': if sort == 'default':
sort = lambda x: (x.order, x.second.title.lower()) sort = lambda x: (x.order, x.second.title.lower())
return sorted(getRelations(first=self, second=child, relationships=relationships), rels = (r for r in getRelations(self, child, relationships=relationships)
key=sort) if canListObject(r.second, noSecurityCheck))
return sorted(rels, key=sort)
def getChildren(self, predicates=None, sort='default'): def getChildren(self, predicates=None, sort='default', noSecurityCheck=False):
return [r.second for r in self.getChildRelations(predicates, sort=sort)] return [r.second for r in self.getChildRelations(predicates, sort=sort,
noSecurityCheck=noSecurityCheck)]
def getParentRelations (self, predicates=None, parent=None, sort='default'): def getParentRelations (self, predicates=None, parent=None, sort='default',
noSecurityCheck=False):
predicates = predicates is None and ['*'] or predicates predicates = predicates is None and ['*'] or predicates
relationships = [ConceptRelation(None, self, p) for p in predicates] relationships = [ConceptRelation(None, self, p) for p in predicates]
if sort == 'default': if sort == 'default':
sort = lambda x: (x.order, x.first.title.lower()) sort = lambda x: (x.order, x.first.title.lower())
return sorted(getRelations(first=parent, second=self, relationships=relationships), rels = (r for r in getRelations(parent, self, relationships=relationships)
key=sort) if canListObject(r.first, noSecurityCheck))
return sorted(rels, key=sort)
def getParents(self, predicates=None, sort='default'): def getParents(self, predicates=None, sort='default', noSecurityCheck=False):
return [r.first for r in self.getParentRelations(predicates, sort=sort)] return [r.first for r in self.getParentRelations(predicates, sort=sort,
noSecurityCheck=noSecurityCheck)]
def assignChild(self, concept, predicate=None, order=0, relevance=1.0): def assignChild(self, concept, predicate=None, order=0, relevance=1.0):
if predicate is None: if predicate is None:
@ -194,6 +203,7 @@ class Concept(Contained, Persistent):
rel.relevance = relevance rel.relevance = relevance
# TODO (?): avoid duplicates # TODO (?): avoid duplicates
registry.register(rel) registry.register(rel)
notify(AssignmentEvent(self, rel))
def setChildren(self, predicate, concepts): def setChildren(self, predicate, concepts):
existing = self.getChildren([predicate]) existing = self.getChildren([predicate])
@ -211,6 +221,7 @@ class Concept(Contained, Persistent):
registry = component.getUtility(IRelationRegistry) registry = component.getUtility(IRelationRegistry)
for rel in self.getChildRelations(predicates, child): for rel in self.getChildRelations(predicates, child):
if order is None or rel.order == order: if order is None or rel.order == order:
notify(DeassignmentEvent(self, rel))
registry.unregister(rel) registry.unregister(rel)
def deassignParent(self, parent, predicates=None): def deassignParent(self, parent, predicates=None):
@ -218,17 +229,19 @@ class Concept(Contained, Persistent):
# resource relations # resource relations
def getResourceRelations(self, predicates=None, resource=None, sort='default'): def getResourceRelations(self, predicates=None, resource=None, sort='default',
noSecurityCheck=False):
predicates = predicates is None and ['*'] or predicates predicates = predicates is None and ['*'] or predicates
relationships = [ResourceRelation(self, None, p) for p in predicates] relationships = [ResourceRelation(self, None, p) for p in predicates]
if sort == 'default': if sort == 'default':
sort = lambda x: (x.order, x.second.title.lower()) sort = lambda x: (x.order, x.second.title.lower())
return sorted(getRelations( rels = (r for r in getRelations(self, resource, relationships=relationships)
first=self, second=resource, relationships=relationships), if canListObject(r.second, noSecurityCheck))
key=sort) return sorted(rels, key=sort)
def getResources(self, predicates=None): def getResources(self, predicates=None, sort='default', noSecurityCheck=False):
return [r.second for r in self.getResourceRelations(predicates)] return [r.second for r in self.getResourceRelations(predicates, sort=sort,
noSecurityCheck=noSecurityCheck)]
def assignResource(self, resource, predicate=None, order=0, relevance=1.0): def assignResource(self, resource, predicate=None, order=0, relevance=1.0):
if predicate is None: if predicate is None:
@ -241,12 +254,27 @@ class Concept(Contained, Persistent):
rel.relevance = relevance rel.relevance = relevance
# TODO (?): avoid duplicates # TODO (?): avoid duplicates
registry.register(rel) registry.register(rel)
notify(AssignmentEvent(self, rel))
def deassignResource(self, resource, predicates=None): def deassignResource(self, resource, predicates=None, order=None):
registry = component.getUtility(IRelationRegistry) registry = component.getUtility(IRelationRegistry)
for rel in self.getResourceRelations(predicates, resource): for rel in self.getResourceRelations(predicates, resource):
registry.unregister(rel) 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 # concept manager
@ -360,3 +388,22 @@ class IndexAttributes(object):
return ' '.join((getName(context), return ' '.join((getName(context),
context.title, context.description)).strip() context.title, context.description)).strip()
# events
class AssignmentEvent(ObjectEvent):
implements(IAssignmentEvent)
def __init__(self, obj, relation):
super(AssignmentEvent, self).__init__(obj)
self.relation = relation
class DeassignmentEvent(ObjectEvent):
implements(IDeassignmentEvent)
def __init__(self, obj, relation):
super(DeassignmentEvent, self).__init__(obj)
self.relation = relation

View file

@ -9,43 +9,14 @@
<!-- security definitions --> <!-- security definitions -->
<permission <include file="security.zcml" />
id="loops.xmlrpc.ManageConcepts"
title="[loops-xmlrpc-manage-concepts-permission] loops: Manage Concepts (XML-RPC)"
/>
<role
id="loops.xmlrpc.ConceptManager"
title="[xmlrpc-manage-concepts-role] loops: Concept Manager (XML-RPC)" />
<grant
permission="loops.xmlrpc.ManageConcepts"
role="loops.xmlrpc.ConceptManager" />
<permission
id="loops.ManageSite"
title="[loops-manage-site-permission] loops: Manage Site"
/>
<role
id="loops.SiteManager"
title="[loops-manage-site-role] loops: Site Manager" />
<grant
permission="loops.ManageSite"
role="loops.SiteManager" />
<grant
permission="loops.xmlrpc.ManageConcepts"
role="loops.SiteManager" />
<!-- event subscribers --> <!-- event subscribers -->
<subscriber <subscriber
for=".interfaces.ITargetRelation for=".interfaces.ITargetRelation
cybertools.relation.interfaces.IRelationInvalidatedEvent" cybertools.relation.interfaces.IRelationInvalidatedEvent"
handler=".util.removeTargetRelation" handler=".util.removeTargetRelation" />
/>
<!-- loops top-level container --> <!-- loops top-level container -->
@ -120,22 +91,22 @@
type="zope.app.content.interfaces.IContentType" /> type="zope.app.content.interfaces.IContentType" />
<class class=".concept.Concept"> <class class=".concept.Concept">
<implements interface="zope.annotation.interfaces.IAttributeAnnotatable" />
<factory id="loops.Concept" description="Concept object" />
<require permission="zope.View" interface=".interfaces.IConcept" />
<require permission="zope.ManageContent" set_schema=".interfaces.IConcept" />
</class>
<implements <class class="loops.concept.ConceptRelation">
interface="zope.annotation.interfaces.IAttributeAnnotatable" /> <require permission="zope.View" interface="loops.interfaces.IConceptRelation" />
<require permission="zope.ManageContent"
<factory set_schema="loops.interfaces.IConceptRelation" />
id="loops.Concept" </class>
description="Concept object" />
<require
permission="zope.View"
interface=".interfaces.IConcept" />
<require
permission="zope.ManageContent"
set_schema=".interfaces.IConcept" />
<class class="loops.concept.ResourceRelation">
<require permission="zope.View" interface="loops.interfaces.IConceptRelation" />
<require permission="zope.ManageContent"
set_schema="loops.interfaces.IConceptRelation" />
</class> </class>
<!-- resource manager and resource --> <!-- resource manager and resource -->
@ -225,7 +196,6 @@
interface=".interfaces.IBaseResource interface=".interfaces.IBaseResource
zope.size.interfaces.ISized" /> zope.size.interfaces.ISized" />
<require <require
permission="zope.ManageContent" permission="zope.ManageContent"
set_schema=".interfaces.IBaseResource" /> set_schema=".interfaces.IBaseResource" />
@ -398,6 +368,10 @@
<adapter factory="cybertools.composer.schema.field.FieldInstance" /> <adapter factory="cybertools.composer.schema.field.FieldInstance" />
<adapter factory="cybertools.composer.schema.field.NumberFieldInstance" <adapter factory="cybertools.composer.schema.field.NumberFieldInstance"
name="number" /> name="number" />
<adapter factory="cybertools.composer.schema.field.DateFieldInstance"
name="date" />
<adapter factory="cybertools.composer.schema.field.BooleanFieldInstance"
name="boolean" />
<adapter factory="cybertools.composer.schema.field.FileUploadFieldInstance" <adapter factory="cybertools.composer.schema.field.FileUploadFieldInstance"
name="fileupload" /> name="fileupload" />
@ -480,7 +454,9 @@
<include package=".browser" /> <include package=".browser" />
<include package=".classifier" /> <include package=".classifier" />
<include package=".compound.blog" />
<include package=".constraint" /> <include package=".constraint" />
<include package=".external" />
<include package=".i18n" /> <include package=".i18n" />
<include package=".integrator" /> <include package=".integrator" />
<include package=".knowledge" /> <include package=".knowledge" />
@ -488,6 +464,7 @@
<include package=".process" /> <include package=".process" />
<include package=".rest" /> <include package=".rest" />
<include package=".search" /> <include package=".search" />
<include package=".security" />
<include package=".versioning" /> <include package=".versioning" />
<include package=".xmlrpc" /> <include package=".xmlrpc" />

View file

@ -32,7 +32,7 @@ configuration):
>>> #sorted(concepts) >>> #sorted(concepts)
>>> #sorted(resources) >>> #sorted(resources)
>>> len(concepts) + len(resources) >>> len(concepts) + len(resources)
35 38
Type- and Text-based Queries Type- and Text-based Queries
@ -41,11 +41,11 @@ Type- and Text-based Queries
>>> from loops.expert import query >>> from loops.expert import query
>>> qu = query.Title('ty*') >>> qu = query.Title('ty*')
>>> list(qu.apply()) >>> list(qu.apply())
[0, 1, 41] [0, 1, 50]
>>> qu = query.Type('loops:*') >>> qu = query.Type('loops:*')
>>> len(list(qu.apply())) >>> len(list(qu.apply()))
35 38
>>> qu = query.Type('loops:concept:predicate') >>> qu = query.Type('loops:concept:predicate')
>>> len(list(qu.apply())) >>> len(list(qu.apply()))
@ -67,7 +67,7 @@ syntax (that in turn is based on hurry.query).
>>> stateNew = concepts['new'] >>> stateNew = concepts['new']
>>> qu = query.Resources(stateNew) >>> qu = query.Resources(stateNew)
>>> list(qu.apply()) >>> list(qu.apply())
[57, 62] [66, 71]
Fin de partie Fin de partie

View file

@ -5,63 +5,32 @@ $Id$
""" """
from zope import component from zope import component
from zope.app.catalog.catalog import Catalog
from zope.app.catalog.interfaces import ICatalog from zope.app.catalog.interfaces import ICatalog
from zope.app.catalog.field import FieldIndex
from zope.app.catalog.text import TextIndex
from cybertools.relation.tests import IntIdsStub
from cybertools.relation.registry import RelationRegistry
from cybertools.relation.interfaces import IRelationRegistry
from cybertools.relation.registry import IndexableRelationAdapter
from cybertools.typology.interfaces import IType from cybertools.typology.interfaces import IType
from loops.base import Loops
from loops import util from loops import util
from loops.interfaces import IIndexAttributes
from loops.concept import Concept from loops.concept import Concept
from loops.concept import IndexAttributes as ConceptIndexAttributes
from loops.resource import Resource from loops.resource import Resource
from loops.resource import IndexAttributes as ResourceIndexAttributes
from loops.knowledge.setup import SetupManager as KnowledgeSetupManager from loops.knowledge.setup import SetupManager as KnowledgeSetupManager
from loops.setup import SetupManager, addObject from loops.setup import SetupManager, addObject
from loops.tests.setup import TestSite as BaseTestSite
from loops.type import ConceptType, ResourceType, TypeConcept from loops.type import ConceptType, ResourceType, TypeConcept
class TestSite(object): class TestSite(BaseTestSite):
def __init__(self, site): def __init__(self, site):
self.site = site self.site = site
def setup(self): def setup(self):
super(TestSite, self).setup()
site = self.site site = self.site
loopsRoot = site['loops']
component.provideUtility(IntIdsStub())
relations = RelationRegistry()
relations.setupIndexes()
component.provideUtility(relations, IRelationRegistry)
component.provideAdapter(IndexableRelationAdapter)
component.provideAdapter(ConceptType)
component.provideAdapter(ResourceType)
component.provideAdapter(TypeConcept)
catalog = Catalog()
component.provideUtility(catalog, ICatalog)
catalog['loops_title'] = TextIndex('title', IIndexAttributes, True)
catalog['loops_text'] = TextIndex('text', IIndexAttributes, True)
catalog['loops_type'] = FieldIndex('tokenForSearch', IType, False)
loopsRoot = site['loops'] = Loops()
component.provideAdapter(KnowledgeSetupManager, name='knowledge') component.provideAdapter(KnowledgeSetupManager, name='knowledge')
setup = SetupManager(loopsRoot) setup = SetupManager(loopsRoot)
concepts, resources, views = setup.setup() concepts, resources, views = setup.setup()
component.provideAdapter(ConceptIndexAttributes)
component.provideAdapter(ResourceIndexAttributes)
tType = concepts.getTypeConcept() tType = concepts.getTypeConcept()
tDomain = concepts['domain'] tDomain = concepts['domain']
tTextDocument = concepts['textdocument'] tTextDocument = concepts['textdocument']
@ -123,6 +92,7 @@ class TestSite(object):
d003.assignConcept(stateNew) d003.assignConcept(stateNew)
d003.assignConcept(dtStudy) d003.assignConcept(dtStudy)
catalog = component.getUtility(ICatalog)
for c in concepts.values(): for c in concepts.values():
catalog.index_doc(int(util.getUidForObject(c)), c) catalog.index_doc(int(util.getUidForObject(c)), c)
for r in resources.values(): for r in resources.values():
@ -130,4 +100,3 @@ class TestSite(object):
return concepts, resources, views return concepts, resources, views

90
external/README.txt vendored Normal file
View 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
View file

@ -0,0 +1,5 @@
"""
$Id$
"""
from loops.external.external import NodesLoader, NodesExporter, NodesImporter

143
external/base.py vendored Normal file
View 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
View 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
View 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
View 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
View 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>&nbsp;</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>&nbsp;</div>
<h4>Export Site</h4>
<div>&nbsp;</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>&nbsp;</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>

View file

@ -1,5 +1,5 @@
# #
# Copyright (c) 2005 Helmut Merz helmutm@cy55.de # Copyright (c) 2008 Helmut Merz helmutm@cy55.de
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -31,7 +31,7 @@ from loops.view import Node
# import/export interfaces (for adapters) # import/export interfaces (for adapters)
class IExternalContentSource(Interface): class IExternalContentSource(Interface):
""" Provides content from an external source. """ Provides content from an external source.
""" """
@ -112,7 +112,7 @@ class NodesExporter(object):
for child in self.context.values(): for child in self.context.values():
self.extractNodeData(child, '', data) self.extractNodeData(child, '', data)
return data return data
def extractNodeData(self, item, path, data): def extractNodeData(self, item, path, data):
name = zapi.getName(item) name = zapi.getName(item)
data.append({ data.append({
@ -126,7 +126,7 @@ class NodesExporter(object):
path = path and '%s/%s' % (path, name) or name path = path and '%s/%s' % (path, name) or name
for child in item.values(): for child in item.values():
self.extractNodeData(child, path, data) self.extractNodeData(child, path, data)
def dumpData(self, file=None, noclose=False): def dumpData(self, file=None, noclose=False):
if file is None: if file is None:
@ -147,7 +147,7 @@ class NodesImporter(object):
def __init__(self, context): def __init__(self, context):
self.context = context self.context = context
def getData(self, file=None): def getData(self, file=None):
if file is None: if file is None:
file = open(self.filename, 'r') file = open(self.filename, 'r')

85
external/interfaces.py vendored Normal file
View 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
View 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
View 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')

View file

@ -65,7 +65,8 @@ class LanguageInfo(object):
lang = self.request.get('loops.language') lang = self.request.get('loops.language')
if lang is not None and lang in self.availableLanguages: if lang is not None and lang in self.availableLanguages:
return lang return lang
return (negotiator.getLanguage(self.availableLanguages, self.request) available = self.availableLanguages or ('en', 'de',)
return (negotiator.getLanguage(available, self.request)
or self.defaultLanguage) or self.defaultLanguage)

View file

@ -1,8 +1,10 @@
<!-- $Id$ --> <!-- $Id$ -->
<metal:actions define-macro="language_switch" <metal:actions define-macro="language_switch"
tal:define="langInfo view/languageInfo"> tal:define="langInfo view/languageInfo;
<tal:lang repeat="lang langInfo/availableLanguages"> available langInfo/availableLanguages"
tal:condition="python: len(available) > 1">
<tal:lang repeat="lang available">
<a href="#" <a href="#"
tal:attributes="href string:switch_language?loops.language=$lang&keep=yes; tal:attributes="href string:switch_language?loops.language=$lang&keep=yes;
title lang"><img src="us.gif" title lang"><img src="us.gif"

View file

@ -29,8 +29,9 @@ from zope.app.container.constraints import contains, containers
from zope.app.container.interfaces import IContainer, IOrderedContainer from zope.app.container.interfaces import IContainer, IOrderedContainer
from zope.app.file.interfaces import IImage as IBaseAsset from zope.app.file.interfaces import IImage as IBaseAsset
from zope.app.folder.interfaces import IFolder from zope.app.folder.interfaces import IFolder
from zope.component.interfaces import IObjectEvent
from zope.size.interfaces import ISized from zope.size.interfaces import ISized
from cybertools.relation.interfaces import IRelation from cybertools.relation.interfaces import IDyadicRelation
import util import util
from util import _ from util import _
@ -563,16 +564,19 @@ class ILoopsContained(Interface):
# relation interfaces # relation interfaces
class ITargetRelation(IRelation): class ITargetRelation(IDyadicRelation):
""" (Marker) interfaces for relations pointing to a target """ (Marker) interfaces for relations pointing to a target
of a view or node. of a view or node.
""" """
class IConceptRelation(IRelation): class IConceptRelation(IDyadicRelation):
""" (Marker) interfaces for relations originating from a concept. """ (Marker) interfaces for relations originating from a concept.
""" """
predicate = Attribute("A concept of type 'predicate' that defines the "
"type of the relation-")
# interfaces for catalog indexes # interfaces for catalog indexes
@ -685,6 +689,22 @@ class INote(ITextDocument):
required=False) required=False)
# events
class IAssignmentEvent(IObjectEvent):
""" A child or resource has been assigned to a concept.
"""
relation = Attribute('The relation that has been assigned to the concept.')
class IDeassignmentEvent(IObjectEvent):
""" A child or resource will be deassigned from a concept.
"""
relation = Attribute('The relation that will be removed from the concept.')
# view configurator stuff # view configurator stuff
class IViewConfiguratorSchema(Interface): class IViewConfiguratorSchema(Interface):

View file

@ -26,7 +26,7 @@ $Id$
from zope.cachedescriptors.property import Lazy from zope.cachedescriptors.property import Lazy
from zope.app.pagetemplate import ViewPageTemplateFile from zope.app.pagetemplate import ViewPageTemplateFile
from loops.browser.action import Action, DialogAction from loops.browser.action import DialogAction
from loops.browser.concept import ConceptView from loops.browser.concept import ConceptView
from loops.browser.form import CreateConceptForm, EditConceptForm from loops.browser.form import CreateConceptForm, EditConceptForm
from loops.browser.form import CreateConcept, EditConcept from loops.browser.form import CreateConcept, EditConcept
@ -107,6 +107,7 @@ class EditGlossaryItemForm(EditConceptForm, ConceptView):
class CreateGlossaryItemForm(CreateConceptForm, EditGlossaryItemForm): class CreateGlossaryItemForm(CreateConceptForm, EditGlossaryItemForm):
title = _(u'Create Glossary Item')
form_action = 'create_glossaryitem' form_action = 'create_glossaryitem'
def children(self): def children(self):

View file

@ -8,26 +8,26 @@
<span tal:repeat="letter python: [chr(c) for c in range(ord('A'), ord('Z')+1)]" <span tal:repeat="letter python: [chr(c) for c in range(ord('A'), ord('Z')+1)]"
class="navlink"> class="navlink">
<a href="#" <a href="#"
tal:omit-tag="python: letter not in data['letters']" tal:omit-tag="python: letter not in data.keys()"
tal:attributes="href string:${request/URL/-1}#$letter" tal:attributes="href string:${request/URL/-1}#$letter"
tal:content="letter">A</a> tal:content="letter">A</a>
</span> </span>
</div> </div>
<div>&nbsp;</div> <div>&nbsp;</div>
<div tal:repeat="letter data/letters"> <div tal:repeat="letter data/keys">
<div class="subtitle"><a name="A" href="#top" <div class="subtitle"><a name="A" href="#top"
tal:attributes="name letter; tal:attributes="name letter;
href string:${request/URL/-1}#top" href string:${request/URL/-1}#top"
tal:content="letter">A</a> tal:content="letter">A</a>
</div> </div>
<div tal:repeat="related data/relations/?letter|python:[]"> <div tal:repeat="related data/?letter|python:[]">
<a href="#" <a href="#"
tal:content="related/title" tal:content="related/title"
tal:attributes="href python: view.getUrlForTarget(related); tal:attributes="href python: view.getUrlForTarget(related);
title related/description"> title related/description">
Topic Topic
</a> </a>
<span tal:define="translations related/adapted/translations" <span tal:define="translations related/adapted/translations|python:[]"
tal:condition="translations"> tal:condition="translations">
(<tal:trans repeat="trans translations"><a href="#" (<tal:trans repeat="trans translations"><a href="#"
tal:attributes="href python: '%s?loops.language=%s' % tal:attributes="href python: '%s?loops.language=%s' %
@ -44,7 +44,7 @@
<metal:block define-macro="glossaryitem"> <metal:block define-macro="glossaryitem">
<metal:title use-macro="item/conceptMacros/concepttitle" /> <metal:title use-macro="item/conceptMacros/concepttitle" />
<p tal:define="translations item/adapted/translations" <p tal:define="translations item/adapted/translations|python:[]"
tal:condition="translations"> tal:condition="translations">
<span i18n:translate="">Translations</span>: <span i18n:translate="">Translations</span>:
<tal:trans repeat="trans translations"><a href="#" <tal:trans repeat="trans translations"><a href="#"
@ -98,10 +98,12 @@
<input type="hidden" <input type="hidden"
id="child.search.predicate" id="child.search.predicate"
tal:attributes="value view/relatedPredicateUid" /> tal:attributes="value view/relatedPredicateUid" />
<input dojoType="comboBox" mode="remote" autoComplete="False" <div dojoType="dojox.data.QueryReadStore" jsId="childSearch"
name="child.search.text" id="child.search.text" url="listConceptsForComboBox.js?searchType=loops:concept:glossaryitem" >
tal:attributes="dataUrl </div>
string:listConceptsForComboBox.js?searchString=%{searchString}&searchType=loops:concept:glossaryitem" /> <input dojoType="dijit.form.FilteringSelect" store="childSearch"
autoComplete="False" labelAttr="label"
name="child.search.text" id="child.search.text" />
</td> </td>
<td> <td>
<input type="button" value="Select" <input type="button" value="Select"

Binary file not shown.

View file

@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: $Id$\n" "Project-Id-Version: $Id$\n"
"POT-Creation-Date: 2007-05-22 12:00 CET\n" "POT-Creation-Date: 2007-05-22 12:00 CET\n"
"PO-Revision-Date: 2007-12-17 12:00 CET\n" "PO-Revision-Date: 2008-01-23 12:00 CET\n"
"Last-Translator: Helmut Merz <helmutm@cy55.de>\n" "Last-Translator: Helmut Merz <helmutm@cy55.de>\n"
"Language-Team: loops developers <helmutm@cy55.de>\n" "Language-Team: loops developers <helmutm@cy55.de>\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
@ -17,12 +17,54 @@ msgstr "Begriff"
msgid "Resource" msgid "Resource"
msgstr "Ressource" msgstr "Ressource"
msgid "Log out"
msgstr "Abmelden"
msgid "Create"
msgstr "Anlegen"
msgid "Edit Concept Map" msgid "Edit Concept Map"
msgstr "Concept Map bearbeiten" msgstr "Concept Map bearbeiten"
msgid "Create Resource..." msgid "Create Resource..."
msgstr "Ressource anlegen..." msgstr "Ressource anlegen..."
msgid "Create a new resource object."
msgstr "Eine neue Ressource erzeugen"
msgid "Edit Blog Post..."
msgstr "Eintrag bearbeiten..."
msgid "Edit Blog Post"
msgstr "Tagebucheintrag bearbeiten"
msgid "Modify blog post."
msgstr "Tagebucheintrag ändern."
msgid "Create Blog Post..."
msgstr "Tagebucheintrag anlegen..."
msgid "Create a new blog post."
msgstr "Einen neuen Tagebucheintrag erzeugen"
msgid "Create Blog Post"
msgstr "Tagebucheintrag anlegen"
msgid "Glossary Item"
msgstr "Glossareintrag"
msgid "Edit Glossary Item..."
msgstr "Glossareintrag bearbeiten..."
msgid "Edit Glossary Item"
msgstr "Glossareintrag bearbeiten"
msgid "Create Glossary Item..."
msgstr "Glossareintrag anlegen..."
msgid "Create Glossary Item"
msgstr "Glossareintrag anlegen"
msgid "Actions" msgid "Actions"
msgstr "Aktionen" msgstr "Aktionen"
@ -47,9 +89,15 @@ msgstr "Unterbegriffe"
msgid "Title" msgid "Title"
msgstr "Titel" msgstr "Titel"
msgid "Title of the concept"
msgstr "Überschrift, sprechende Bezeichnung des Begriffs"
msgid "Description" msgid "Description"
msgstr "Beschreibung" msgstr "Beschreibung"
msgid "A medium-length description describing the content and the purpose of the object"
msgstr "Eine ausführlichere Beschreibung des Inhalts und Zwecks des Objekts"
msgid "Related Items" msgid "Related Items"
msgstr "Verwandte Begriffe" msgstr "Verwandte Begriffe"
@ -224,4 +272,30 @@ msgstr "Die Passwort-Wiederholung stimmt nicht mit dem eingegebenen Passwort üb
msgid "Your password has been changed." msgid "Your password has been changed."
msgstr "Ihr Passwort wurde geändert." msgstr "Ihr Passwort wurde geändert."
msgid "Date/Time"
msgstr "Datum/Uhrzeit"
msgid "The date and time the information was posted."
msgstr "Datum und Uhrzeit der Bereitstellung der Information"
msgid "Private"
msgstr "privat"
msgid "Check this field if the blog post should be accessible only for a limited audience."
msgstr "Markieren Sie dieses Feld, wenn der Tagebucheintrag nicht für alle Benutzer sichtbar sein soll"
msgid "The text of your blog entry"
msgstr "Der eigentliche Text des Tagebucheintrags"
msgid "Private Comment"
msgstr "Privater Kommentar"
msgid "A text that is not visible for other users."
msgstr "Ein Text, der für andere Benutzer nicht sichtbar sein soll"
msgid "For quick creation of notes/links bookmark this link"
msgstr "Für Notizen diesen Link zu Favoriten/Lesezeichen hinzufügen"
msgid "Create loops Note"
msgstr "loops-Notiz anlegen"

View file

@ -26,6 +26,9 @@ ZCML setup):
>>> loopsRoot = site['loops'] >>> loopsRoot = site['loops']
>>> loopsId = util.getUidForObject(loopsRoot) >>> loopsId = util.getUidForObject(loopsRoot)
>>> from loops.organize.tests import setupUtilitiesAndAdapters
>>> setupData = setupUtilitiesAndAdapters(loopsRoot)
>>> type = concepts['type'] >>> type = concepts['type']
>>> person = concepts['person'] >>> person = concepts['person']
@ -39,10 +42,7 @@ Organizations: Persons (and Users), Institutions, Addresses...
The classes used in this package are just adapters to IConcept. The classes used in this package are just adapters to IConcept.
>>> from loops.interfaces import IConcept
>>> from loops.organize.interfaces import IPerson >>> from loops.organize.interfaces import IPerson
>>> from loops.organize.party import Person
>>> component.provideAdapter(Person, (IConcept,), IPerson)
>>> john = IPerson(johnC) >>> john = IPerson(johnC)
>>> john.title >>> john.title
@ -74,15 +74,8 @@ the person(s) belonging to a user/principal.
For testing, we first have to provide the needed utilities and settings For testing, we first have to provide the needed utilities and settings
(in real life this is all done during Zope startup): (in real life this is all done during Zope startup):
>>> from zope.app.security.interfaces import IAuthentication >>> auth = setupData.auth
>>> from zope.app.security.principalregistry import PrincipalRegistry >>> principalAnnotations = setupData.principalAnnotations
>>> auth = PrincipalRegistry()
>>> component.provideUtility(auth, IAuthentication)
>>> from zope.app.principalannotation.interfaces import IPrincipalAnnotationUtility
>>> from zope.app.principalannotation import PrincipalAnnotationUtility
>>> principalAnnotations = PrincipalAnnotationUtility()
>>> component.provideUtility(principalAnnotations, IPrincipalAnnotationUtility)
>>> principal = auth.definePrincipal('users.john', u'John', login='john') >>> principal = auth.definePrincipal('users.john', u'John', login='john')
>>> john.userId = 'users.john' >>> john.userId = 'users.john'
@ -116,6 +109,7 @@ principal annotation:
>>> from zope.app.container.contained import ObjectRemovedEvent >>> from zope.app.container.contained import ObjectRemovedEvent
>>> from zope.event import notify >>> from zope.event import notify
>>> from zope.interface import Interface >>> from zope.interface import Interface
>>> from loops.interfaces import IConcept
>>> from loops.organize.party import removePersonReferenceFromPrincipal >>> from loops.organize.party import removePersonReferenceFromPrincipal
>>> from zope.app.testing import ztapi >>> from zope.app.testing import ztapi
>>> ztapi.subscribe([IConcept, IObjectRemovedEvent], None, >>> ztapi.subscribe([IConcept, IObjectRemovedEvent], None,
@ -157,6 +151,7 @@ with a principal folder:
>>> from zope.app.appsetup.bootstrap import ensureUtility >>> from zope.app.appsetup.bootstrap import ensureUtility
>>> from zope.app.authentication.authentication import PluggableAuthentication >>> from zope.app.authentication.authentication import PluggableAuthentication
>>> from zope.app.security.interfaces import IAuthentication
>>> ensureUtility(site, IAuthentication, '', PluggableAuthentication, >>> ensureUtility(site, IAuthentication, '', PluggableAuthentication,
... copy_to_zlog=False, asObject=True) ... copy_to_zlog=False, asObject=True)
<...PluggableAuthentication...> <...PluggableAuthentication...>
@ -212,7 +207,6 @@ Now we can also retrieve it from the authentication utility:
>>> pau.getPrincipal('loops.newuser').title >>> pau.getPrincipal('loops.newuser').title
u'Tom Sawyer' u'Tom Sawyer'
Change Password Change Password
--------------- ---------------
@ -229,18 +223,100 @@ We need a principal for testing the login stuff:
>>> principal = InternalPrincipal('scott', 'tiger', 'Scotty') >>> principal = InternalPrincipal('scott', 'tiger', 'Scotty')
>>> request.setPrincipal(principal) >>> request.setPrincipal(principal)
>>> from cybertools.composer.schema.factory import SchemaFactory
>>> from cybertools.composer.schema.field import FieldInstance
>>> component.provideAdapter(SchemaFactory)
>>> component.provideAdapter(FieldInstance)
>>> from loops.organize.browser import PasswordChange >>> from loops.organize.browser import PasswordChange
>>> pwcView = PasswordChange(menu, request) >>> pwcView = PasswordChange(menu, request)
>>> pwcView.update() >>> pwcView.update()
False False
Security
========
Automatic security settings on persons
--------------------------------------
>>> from zope.traversing.api import getName
>>> list(sorted(getName(c) for c in concepts['person'].getChildren()))
[u'john', u'martha', u'person.newuser']
Person objects that have a user assigned to them receive this user
(principal) as their owner.
>>> from zope.app.securitypolicy.interfaces import IPrincipalRoleMap
>>> IPrincipalRoleMap(concepts['john']).getPrincipalsAndRoles()
[('loops.Owner', 'users.john', PermissionSetting: Allow)]
>>> IPrincipalRoleMap(concepts['person.newuser']).getPrincipalsAndRoles()
[('loops.Owner', u'loops.newuser', PermissionSetting: Allow)]
The person ``martha`` hasn't got a user id, so there is no role assigned
to it.
>>> IPrincipalRoleMap(concepts['martha']).getPrincipalsAndRoles()
[]
Only the owner (and a few other privileged people) should be able
to edit a person object.
We also need an interaction with a participation based on the principal
whose permissions we want to check.
>>> from zope.app.authentication.principalfolder import Principal
>>> pJohn = Principal('users.john', 'xxx', u'John')
>>> from loops.tests.auth import login
>>> login(pJohn)
We also want to grant some global permissions and roles, i.e. on the site or
loops root level.
>>> rolePermissions = setupData.rolePermissions
>>> rolePermissions.grantPermissionToRole('zope.View', 'zope.Member')
>>> principalRoles = setupData.principalRoles
>>> principalRoles.assignRoleToPrincipal('zope.Member', 'users.john')
Now we are ready to look for the real stuff - what John is allowed to do.
>>> from zope.security import canAccess, canWrite, checkPermission
>>> john = concepts['john']
>>> canAccess(john, 'title')
True
Person objects that have an owner may be modified by this owner.
>>> canWrite(john, 'title')
True
So let's try with another user with another role setting.
>>> rolePermissions.grantPermissionToRole('zope.ManageContent', 'loops.Staff')
>>> principalRoles.assignRoleToPrincipal('loops.Staff', 'users.martha')
>>> principalRoles.assignRoleToPrincipal('zope.Member', 'users.martha')
>>> pMartha = Principal('users.martha', 'xxx', u'Martha')
>>> login(pMartha)
>>> canAccess(john, 'title')
True
>>> canWrite(john, 'title')
False
If we clear the userId attribute from a person object others may be allowed
again to edit it...
>>> adapted(john).userId = ''
>>> canWrite(john, 'title')
True
... but John no more...
>>> login(pJohn)
>>> canWrite(john, 'title')
False
Fin de partie Fin de partie
============= =============
>>> placefulTearDown() >>> placefulTearDown()

View file

@ -93,6 +93,13 @@
<!-- other adapters --> <!-- other adapters -->
<zope:adapter
for="loops.interfaces.ILoopsObject
zope.publisher.interfaces.browser.IBrowserRequest"
factory="loops.organize.memberinfo.MemberInfoProvider"
permission="zope.Public"
/>
<zope:adapter factory="loops.organize.setup.SetupManager" <zope:adapter factory="loops.organize.setup.SetupManager"
name="organize" /> name="organize" />

View file

@ -30,7 +30,8 @@ from zope.app.security.interfaces import IAuthentication, PrincipalLookupError
from zope.security.proxy import removeSecurityProxy from zope.security.proxy import removeSecurityProxy
from cybertools.organize.interfaces import IPerson as IBasePerson from cybertools.organize.interfaces import IPerson as IBasePerson
from loops.organize.util import getPrincipalFolder, authPluginId from loops.interfaces import IConceptSchema
from loops.organize.util import getPrincipalFolder
from loops.util import _ from loops.util import _
ANNOTATION_KEY = 'loops.organize.person' ANNOTATION_KEY = 'loops.organize.person'
@ -83,7 +84,7 @@ class LoginName(schema.TextLine):
mapping=dict(userId=userId))) mapping=dict(userId=userId)))
class IPerson(IBasePerson): class IPerson(IConceptSchema, IBasePerson):
""" Resembles a human being with a name (first and last name), """ Resembles a human being with a name (first and last name),
a birth date, and a set of addresses. This interface only a birth date, and a set of addresses. This interface only
lists fields used in addition to those provided by the lists fields used in addition to those provided by the

View file

@ -1,5 +1,5 @@
# #
# Copyright (c) 2006 Helmut Merz helmutm@cy55.de # Copyright (c) 2008 Helmut Merz helmutm@cy55.de
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -36,10 +36,13 @@ from zope.i18nmessageid import MessageFactory
from zope.cachedescriptors.property import Lazy from zope.cachedescriptors.property import Lazy
from cybertools.typology.interfaces import IType from cybertools.typology.interfaces import IType
from loops.interfaces import ILoops from loops.common import adapted
from loops.concept import Concept from loops.concept import Concept
from loops.interfaces import ILoops
from loops.organize.interfaces import IMemberRegistrationManager from loops.organize.interfaces import IMemberRegistrationManager
from loops.organize.util import getPrincipalFolder, authPluginId, getInternalPrincipal from loops.organize.util import getPrincipalFolder, getGroupsFolder
from loops.organize.util import getInternalPrincipal
from loops.type import getOptionsDict
from loops.util import _ from loops.util import _
@ -51,29 +54,54 @@ class MemberRegistrationManager(object):
def __init__(self, context): def __init__(self, context):
self.context = context self.context = context
def register(self, userId, password, lastName, firstName=u'', **kw): def register(self, userId, password, lastName, firstName=u'',
groups=[], useExisting=False, **kw):
# step 1: create an internal principal in the loops principal folder: # step 1: create an internal principal in the loops principal folder:
pFolder = getPrincipalFolder(self.context) pFolder = getPrincipalFolder(self.context)
title = firstName and ' '.join((firstName, lastName)) or lastName title = firstName and ' '.join((firstName, lastName)) or lastName
principal = InternalPrincipal(userId, password, title) principal = InternalPrincipal(userId, password, title)
pFolder[userId] = principal if useExisting:
# step 2: create a corresponding person concept: if userId not in pFolder:
pFolder[userId] = principal
else:
pFolder[userId] = principal
# step 2 (optional): assign to group(s)
personType = self.context.getLoopsRoot().getConceptManager()['person']
od = getOptionsDict(adapted(personType).options)
groupInfo = od.get('group')
if groupInfo:
gfName, groupNames = groupInfo.split(':')
gFolder = getGroupsFolder(gfName)
if not groups:
groups = groupNames.split(',')
else:
gFolder = getGroupsFolder()
if gFolder is not None:
for g in groups:
group = gFolder.get(g)
if group is not None:
members = list(group.principals)
members.append(pFolder.prefix + userId)
group.principals = members
# step 3: create a corresponding person concept:
cm = self.context.getConceptManager() cm = self.context.getConceptManager()
id = baseId = 'person.' + userId id = baseId = 'person.' + userId
# TODO: use NameChooser # TODO: use NameChooser
num = 0 if useExisting and id in cm:
while id in cm: person = cm[id]
num +=1 else:
id = baseId + str(num) num = 0
person = cm[id] = Concept(title) while id in cm:
# TODO: the name of the person type object must be kept flexible! num +=1
# So we have to search for a type concept that has IPerson as id = baseId + str(num)
# its typeInterface... person = cm[id] = Concept(title)
person.conceptType = cm['person'] person.conceptType = cm['person']
personAdapter = IType(person).typeInterface(person) personAdapter = adapted(person)
personAdapter.firstName = firstName personAdapter.firstName = firstName
personAdapter.lastName = lastName personAdapter.lastName = lastName
personAdapter.userId = '.'.join((authPluginId, userId)) personAdapter.userId = pFolder.prefix + userId
for k, v in kw.items():
setattr(personAdapter, k, v)
notify(ObjectCreatedEvent(person)) notify(ObjectCreatedEvent(person))
notify(ObjectModifiedEvent(person)) notify(ObjectModifiedEvent(person))
return personAdapter return personAdapter

57
organize/memberinfo.py Normal file
View 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'),
))

View file

@ -38,10 +38,11 @@ from cybertools.organize.interfaces import IAddress
from cybertools.organize.party import Person as BasePerson from cybertools.organize.party import Person as BasePerson
from cybertools.relation.interfaces import IRelationRegistry from cybertools.relation.interfaces import IRelationRegistry
from cybertools.typology.interfaces import IType from cybertools.typology.interfaces import IType
from loops.common import AdapterBase
from loops.concept import Concept from loops.concept import Concept
from loops.interfaces import IConcept from loops.interfaces import IConcept
from loops.organize.interfaces import IPerson, ANNOTATION_KEY from loops.organize.interfaces import IPerson, ANNOTATION_KEY
from loops.common import AdapterBase from loops.security.common import assignOwner, removeOwner, allowEditingForOwner
from loops.type import TypeInterfaceSourceList from loops.type import TypeInterfaceSourceList
from loops import util from loops import util
@ -54,6 +55,8 @@ TypeInterfaceSourceList.typeInterfaces += (IPerson, IAddress)
def getPersonForUser(context, request=None, principal=None): def getPersonForUser(context, request=None, principal=None):
if principal is None: if principal is None:
principal = request.principal principal = request.principal
if principal is None:
return None
loops = context.getLoopsRoot() loops = context.getLoopsRoot()
pa = annotations(principal).get(ANNOTATION_KEY, None) pa = annotations(principal).get(ANNOTATION_KEY, None)
if pa is None: if pa is None:
@ -88,13 +91,16 @@ class Person(AdapterBase, BasePerson):
pa = annotations(principal) pa = annotations(principal)
loopsId = util.getUidForObject(self.context.getLoopsRoot()) loopsId = util.getUidForObject(self.context.getLoopsRoot())
ann = pa.get(ANNOTATION_KEY) ann = pa.get(ANNOTATION_KEY)
if ann is None: if ann is None: # or not isinstance(ann, PersistentMapping):
ann = pa[ANNOTATION_KEY] = PersistentMapping() ann = pa[ANNOTATION_KEY] = PersistentMapping()
ann[loopsId] = self.context ann[loopsId] = self.context
assignOwner(self.context, userId)
oldUserId = self.userId oldUserId = self.userId
if oldUserId and oldUserId != userId: if oldUserId and oldUserId != userId:
self.removeReferenceFromPrincipal(oldUserId) self.removeReferenceFromPrincipal(oldUserId)
removeOwner(self.context, oldUserId)
self.context._userId = userId self.context._userId = userId
allowEditingForOwner(self.context, revert=not userId)
userId = property(getUserId, setUserId) userId = property(getUserId, setUserId)
def removeReferenceFromPrincipal(self, userId): def removeReferenceFromPrincipal(self, userId):

View file

@ -4,8 +4,35 @@ import unittest, doctest
from zope.testing.doctestunit import DocFileSuite from zope.testing.doctestunit import DocFileSuite
from zope.app.testing import ztapi from zope.app.testing import ztapi
from zope.interface.verify import verifyClass from zope.interface.verify import verifyClass
from zope import component
from zope.app.principalannotation.interfaces import IPrincipalAnnotationUtility
from zope.app.principalannotation import PrincipalAnnotationUtility
from zope.app.principalannotation import annotations
from zope.app.security.interfaces import IAuthentication
from zope.app.security.principalregistry import PrincipalRegistry
from zope.app.securitypolicy.interfaces import IRolePermissionManager
from zope.app.securitypolicy.interfaces import IPrincipalRoleManager
from cybertools.util.jeep import Jeep
from loops.organize.interfaces import IPerson
from loops.organize.party import Person from loops.organize.party import Person
def setupUtilitiesAndAdapters(loopsRoot):
auth = PrincipalRegistry()
component.provideUtility(auth, IAuthentication)
principalAnnotations = PrincipalAnnotationUtility()
component.provideUtility(principalAnnotations, IPrincipalAnnotationUtility)
component.provideAdapter(Person, provides=IPerson)
return Jeep((
('auth', auth),
('principalAnnotations', principalAnnotations),
('rolePermissions', IRolePermissionManager(loopsRoot)),
('principalRoles', IPrincipalRoleManager(loopsRoot)),
))
class Test(unittest.TestCase): class Test(unittest.TestCase):
"Basic tests for the organize sub-package." "Basic tests for the organize sub-package."

View file

@ -27,22 +27,39 @@ from zope.app.authentication.interfaces import IPluggableAuthentication
from zope.app.authentication.interfaces import IAuthenticatorPlugin from zope.app.authentication.interfaces import IAuthenticatorPlugin
from zope.app.security.interfaces import IAuthentication from zope.app.security.interfaces import IAuthentication
authPluginId = 'loops' from loops.common import adapted
from loops.type import getOptionsDict
defaultAuthPluginId = 'loops'
def getPrincipalFolder(context=None): def getPrincipalFolder(context=None, authPluginId=None, ignoreErrors=False):
pau = component.getUtility(IAuthentication, context=context) pau = component.getUtility(IAuthentication, context=context)
if not IPluggableAuthentication.providedBy(pau): if not IPluggableAuthentication.providedBy(pau):
if ignoreErrors:
return None
raise ValueError(u'There is no pluggable authentication ' raise ValueError(u'There is no pluggable authentication '
'utility available.') 'utility available.')
if not authPluginId in pau.authenticatorPlugins: if authPluginId is None and context is not None:
raise ValueError(u'There is no loops authenticator ' person = context.getLoopsRoot().getConceptManager()['person']
'plugin available.') od = getOptionsDict(adapted(person).options)
authPluginId = od.get('principalfolder', defaultAuthPluginId)
if authPluginId is None:
authPluginId = defaultAuthPluginId
if authPluginId not in pau.authenticatorPlugins:
if ignoreErrors:
return None
raise ValueError(u"There is no loops authenticator "
"plugin '%s' available." % authPluginId)
for name, plugin in pau.getAuthenticatorPlugins(): for name, plugin in pau.getAuthenticatorPlugins():
if name == authPluginId: if name == authPluginId:
return plugin return plugin
def getGroupsFolder(context=None, name='gloops'):
return getPrincipalFolder(authPluginId=name, ignoreErrors=True)
def getInternalPrincipal(id, context=None): def getInternalPrincipal(id, context=None):
pau = component.getUtility(IAuthentication, context=context) pau = component.getUtility(IAuthentication, context=context)
if not IPluggableAuthentication.providedBy(pau): if not IPluggableAuthentication.providedBy(pau):

View file

@ -24,13 +24,13 @@ $Id$
from zope import schema, component from zope import schema, component
from zope.interface import Interface, Attribute, implements from zope.interface import Interface, Attribute, implements
from zope import traversing
from zope.app.catalog.interfaces import ICatalog from zope.app.catalog.interfaces import ICatalog
from zope.cachedescriptors.property import Lazy from zope.cachedescriptors.property import Lazy
from cybertools.typology.interfaces import IType from cybertools.typology.interfaces import IType
from loops.interfaces import IConcept, IConceptSchema
from loops.common import AdapterBase from loops.common import AdapterBase
from loops.interfaces import IConcept, IConceptSchema
from loops.security.common import canListObject
from loops.type import TypeInterfaceSourceList from loops.type import TypeInterfaceSourceList
from loops.versioning.util import getVersion from loops.versioning.util import getVersion
from loops import util from loops import util
@ -72,7 +72,8 @@ class BaseQuery(object):
result = cat.searchResults(loops_type=(start, end), loops_title=title) result = cat.searchResults(loops_type=(start, end), loops_title=title)
else: else:
result = cat.searchResults(loops_type=(start, end)) result = cat.searchResults(loops_type=(start, end))
result = set(r for r in result if r.getLoopsRoot() == self.loopsRoot) result = set(r for r in result if r.getLoopsRoot() == self.loopsRoot
and canListObject(r))
if 'exclude' in kw: if 'exclude' in kw:
r1 = set() r1 = set()
for r in result: for r in result:
@ -139,6 +140,7 @@ class FullQuery(BaseQuery):
result = rc result = rc
result = set(r for r in result result = set(r for r in result
if r.getLoopsRoot() == self.loopsRoot if r.getLoopsRoot() == self.loopsRoot
and canListObject(r)
and getVersion(r) == r) and getVersion(r) == r)
return result return result

View file

@ -1,5 +1,5 @@
# #
# Copyright (c) 2005 Helmut Merz helmutm@cy55.de # Copyright (c) 2008 Helmut Merz helmutm@cy55.de
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -22,6 +22,8 @@ Definition of the Concept class.
$Id$ $Id$
""" """
from cStringIO import StringIO
from persistent import Persistent
from zope import component, schema from zope import component, schema
from zope.app import zapi from zope.app import zapi
from zope.app.container.btree import BTreeContainer from zope.app.container.btree import BTreeContainer
@ -38,9 +40,6 @@ from zope.interface import implements
from zope.size.interfaces import ISized from zope.size.interfaces import ISized
from zope.security.proxy import removeSecurityProxy from zope.security.proxy import removeSecurityProxy
from zope.traversing.api import getName, getParent from zope.traversing.api import getName, getParent
from persistent import Persistent
from cStringIO import StringIO
from zope.lifecycleevent import ObjectModifiedEvent, Attributes from zope.lifecycleevent import ObjectModifiedEvent, Attributes
from zope.event import notify from zope.event import notify
@ -50,7 +49,6 @@ from cybertools.storage.interfaces import IExternalStorage
from cybertools.text.interfaces import ITextTransform from cybertools.text.interfaces import ITextTransform
from cybertools.typology.interfaces import IType, ITypeManager from cybertools.typology.interfaces import IType, ITypeManager
from cybertools.util.jeep import Jeep from cybertools.util.jeep import Jeep
from loops.base import ParentInfo from loops.base import ParentInfo
from loops.common import ResourceAdapterBase, adapted from loops.common import ResourceAdapterBase, adapted
from loops.concept import ResourceRelation from loops.concept import ResourceRelation
@ -62,6 +60,7 @@ from loops.interfaces import IResourceManager, IResourceManagerContained
from loops.interfaces import ITypeConcept from loops.interfaces import ITypeConcept
from loops.interfaces import ILoopsContained from loops.interfaces import ILoopsContained
from loops.interfaces import IIndexAttributes from loops.interfaces import IIndexAttributes
from loops.security.common import canListObject
from loops import util from loops import util
from loops.versioning.util import getMaster from loops.versioning.util import getMaster
from loops.view import TargetRelation from loops.view import TargetRelation
@ -189,20 +188,24 @@ class Resource(Image, Contained):
relationships = [TargetRelation] relationships = [TargetRelation]
obj = getMaster(self) # use the master version for relations obj = getMaster(self) # use the master version for relations
rels = getRelations(second=obj, relationships=relationships) rels = getRelations(second=obj, relationships=relationships)
return [r.first for r in rels] return [r.first for r in rels if canListObject(r.first)]
def getConceptRelations (self, predicates=None, concept=None, sort='default'): def getConceptRelations (self, predicates=None, concept=None, sort='default',
noSecurityCheck=False):
predicates = predicates is None and ['*'] or predicates predicates = predicates is None and ['*'] or predicates
obj = getMaster(self) obj = getMaster(self)
relationships = [ResourceRelation(None, obj, p) for p in predicates] relationships = [ResourceRelation(None, obj, p) for p in predicates]
if sort == 'default': if sort == 'default':
sort = lambda x: (x.order, x.first.title.lower()) sort = lambda x: (x.order, x.first.title.lower())
return sorted(getRelations(first=concept, second=obj, relationships=relationships), rels = (r for r in getRelations(first=concept, second=obj,
key=sort) relationships=relationships)
if canListObject(r.first, noSecurityCheck))
return sorted(rels, key=sort)
def getConcepts(self, predicates=None): def getConcepts(self, predicates=None, noSecurityCheck=False):
obj = getMaster(self) obj = getMaster(self)
return [r.first for r in obj.getConceptRelations(predicates)] return [r.first for r in obj.getConceptRelations(predicates,
noSecurityCheck=noSecurityCheck)]
def assignConcept(self, concept, predicate=None, order=0, relevance=1.0): def assignConcept(self, concept, predicate=None, order=0, relevance=1.0):
obj = getMaster(self) obj = getMaster(self)
@ -372,6 +375,8 @@ class ExternalFileAdapter(FileAdapter):
self.storageName = storageName self.storageName = storageName
def getData(self): def getData(self):
if self.storageName == 'unknown': # object not set up yet
return ''
storage = component.getUtility(IExternalStorage, name=self.storageName) storage = component.getUtility(IExternalStorage, name=self.storageName)
return storage.getData(self.externalAddress, params=self.storageParams) return storage.getData(self.externalAddress, params=self.storageParams)

View file

@ -35,6 +35,8 @@ class ResourceSchemaFactory(SchemaFactory):
def __call__(self, interface, **kw): def __call__(self, interface, **kw):
schema = super(ResourceSchemaFactory, self).__call__(interface, **kw) schema = super(ResourceSchemaFactory, self).__call__(interface, **kw)
schema.fields.data.height = 10 schema.fields.data.height = 10
if self.context.contentType == 'text/html':
schema.fields.data.fieldType = 'html'
return schema return schema

View file

@ -90,8 +90,8 @@ a controller attribute for the search view.
>>> searchView.controller = Controller(searchView, request) >>> searchView.controller = Controller(searchView, request)
>>> searchView.submitReplacing('1.results', '1.search.form', pageView) >>> searchView.submitReplacing('1.results', '1.search.form', pageView)
'return submitReplacing("1.results", "1.search.form", 'submitReplacing("1.results", "1.search.form",
"http://127.0.0.1/loops/views/page/.target29/@@searchresults.html")' "http://127.0.0.1/loops/views/page/.target29/@@searchresults.html");...'
Basic (text/title) search Basic (text/title) search
------------------------- -------------------------
@ -164,7 +164,9 @@ Now we can fill our search form and execute the query; note that all concepts
found are listed, plus all their children and all resources associated found are listed, plus all their children and all resources associated
with them: with them:
>>> form = {'search.3.type': 'loops:concept:topic', 'search.3.text': u'zope'} >>> from loops import util
>>> uid = util.getUidForObject(concepts['zope'])
>>> form = {'search.3.type': 'loops:concept:topic', 'search.3.text': uid}
>>> request = TestRequest(form=form) >>> request = TestRequest(form=form)
>>> resultsView = SearchResults(page, request) >>> resultsView = SearchResults(page, request)
>>> results = list(resultsView.results) >>> results = list(resultsView.results)
@ -173,7 +175,8 @@ with them:
>>> results[0].context.__name__ >>> results[0].context.__name__
u'plone' u'plone'
>>> form = {'search.3.type': 'loops:concept:topic', 'search.3.text': u'zope3'} >>> uid = util.getUidForObject(concepts['zope3'])
>>> form = {'search.3.type': 'loops:concept:topic', 'search.3.text': uid}
>>> request = TestRequest(form=form) >>> request = TestRequest(form=form)
>>> resultsView = SearchResults(page, request) >>> resultsView = SearchResults(page, request)
>>> results = list(resultsView.results) >>> results = list(resultsView.results)
@ -186,11 +189,11 @@ To support easy entry of concepts to search for we can preselect the available
concepts (optionally restricted to a certain type) by entering text parts concepts (optionally restricted to a certain type) by entering text parts
of the concepts' titles: of the concepts' titles:
>>> form = {'searchType': 'loops:concept:topic', 'searchString': u'zope'} >>> form = {'searchType': 'loops:concept:topic', 'name': u'zope'}
>>> request = TestRequest(form=form) >>> request = TestRequest(form=form)
>>> view = Search(page, request) >>> view = Search(page, request)
>>> view.listConcepts() >>> view.listConcepts()
"[['Zope (Topic)', '33']]" u"{identifier: 'id', items: [{label: 'Zope (Topic)', name: 'Zope', id: '33'}]}"
Preset Concept Types on Search Forms Preset Concept Types on Search Forms
------------------------------------ ------------------------------------

View file

@ -1,5 +1,5 @@
# #
# Copyright (c) 2004 Helmut Merz helmutm@cy55.de # Copyright (c) 2008 Helmut Merz helmutm@cy55.de
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -83,30 +83,56 @@ class Search(BaseView):
def initDojo(self): def initDojo(self):
self.registerDojo() self.registerDojo()
cm = self.controller.macros cm = self.controller.macros
jsCall = 'dojo.require("dojo.widget.ComboBox")' jsCall = ('dojo.require("dojo.parser");'
'dojo.require("dijit.form.FilteringSelect");'
'dojo.require("dojox.data.QueryReadStore");')
cm.register('js-execute', jsCall, jsCall=jsCall) cm.register('js-execute', jsCall, jsCall=jsCall)
def listConcepts(self): def listConcepts(self):
""" Used for dojo.widget.ComboBox. """ Used for dijit.FilteringSelect.
""" """
request = self.request request = self.request
request.response.setHeader('Content-Type', 'text/plain; charset=UTF-8') request.response.setHeader('Content-Type', 'text/plain; charset=UTF-8')
title = request.get('searchString', '').replace('(', ' ').replace(')', ' ') title = request.get('name')
type = request.get('searchType') or 'loops:concept:*' if title == '*':
result = ConceptQuery(self).query(title=title, type=type, exclude=('system',)) title = None
#registry = component.getUtility(IRelationRegistry) type = request.get('searchType')
# simple way to provide JSON format: data = []
return str(sorted([[`adapted(o, self.languageInfo).title`[2:-1] if title or type:
+ ' (%s)' % `o.conceptType.title`[2:-1], if title is not None:
`int(util.getUidForObject(o))`] title = title.replace('(', ' ').replace(')', ' ').replace(' -', ' ')
for o in result #title = title.split(' ', 1)[0]
if o.getLoopsRoot() == self.loopsRoot])).replace('\\\\x', '\\x') if not type:
#return str(sorted([[`o.title`[2:-1], `traversing.api.getName(o)`[2:-1]] type = 'loops:concept:*'
# for o in result])).replace('\\\\x', '\\x') result = ConceptQuery(self).query(title=title or None, type=type,
exclude=('system',))
for o in result:
if o.getLoopsRoot() == self.loopsRoot:
name = adapted(o, self.languageInfo).title
if title and title.endswith('*'):
title = title[:-1]
sort = ((title and name.startswith(title) and '0' or '1')
+ name.lower())
if o.conceptType is None:
raise ValueError('Concept Type missing for %r.' % name)
data.append({'label': '%s (%s)' % (name, o.conceptType.title),
'name': name,
'id': util.getUidForObject(o),
'sort': sort})
data.sort(key=lambda x: x['sort'])
if not title:
data.insert(0, {'label': '', 'name': '', 'id': ''})
json = []
for item in data:
json.append("{label: '%s', name: '%s', id: '%s'}" %
(item['label'], item['name'], item['id']))
json = "{identifier: 'id', items: [%s]}" % ', '.join(json)
#print '***', json
return json
def submitReplacing(self, targetId, formId, view): def submitReplacing(self, targetId, formId, view):
self.registerDojo() self.registerDojo()
return 'return submitReplacing("%s", "%s", "%s")' % ( return 'submitReplacing("%s", "%s", "%s"); return false;' % (
targetId, formId, targetId, formId,
'%s/.target%s/@@searchresults.html' % (view.url, self.uniqueId)) '%s/.target%s/@@searchresults.html' % (view.url, self.uniqueId))
@ -131,13 +157,14 @@ class SearchResults(BaseView):
useTitle = form.get('search.2.title') useTitle = form.get('search.2.title')
useFull = form.get('search.2.full') useFull = form.get('search.2.full')
conceptType = form.get('search.3.type', 'loops:concept:*') conceptType = form.get('search.3.type', 'loops:concept:*')
conceptTitle = form.get('search.3.text') #conceptTitle = form.get('search.3.text')
if conceptTitle is not None: #if conceptTitle is not None:
conceptTitle = util.toUnicode(conceptTitle, encoding='ISO8859-15') # conceptTitle = util.toUnicode(conceptTitle, encoding='ISO8859-15')
conceptUid = form.get('search.3.text_selected') conceptUid = form.get('search.3.text')
result = FullQuery(self).query(text=text, type=type, result = FullQuery(self).query(text=text, type=type,
useTitle=useTitle, useFull=useFull, useTitle=useTitle, useFull=useFull,
conceptTitle=conceptTitle, conceptUid=conceptUid, #conceptTitle=conceptTitle,
conceptUid=conceptUid,
conceptType=conceptType) conceptType=conceptType)
rowNum = 4 rowNum = 4
while rowNum < 10: while rowNum < 10:

View file

@ -4,11 +4,11 @@
idPrefix string:${view/itemNum}.search; idPrefix string:${view/itemNum}.search;
formId string:$idPrefix.form; formId string:$idPrefix.form;
resultsId string:$idPrefix.results"> resultsId string:$idPrefix.results">
<h3 tal:attributes="class string:content-$level; <h1 tal:attributes="class string:content-$level;
ondblclick item/openEditWindow" ondblclick item/openEditWindow"
tal:content="item/title"> tal:content="item/title">
Search Search
</h3> </h1>
<div metal:define-macro="search_form" class="searchForm"> <div metal:define-macro="search_form" class="searchForm">
<fieldset class="box"> <fieldset class="box">
@ -46,7 +46,7 @@
i18n:domain="loops"> i18n:domain="loops">
<fieldset class="box" <fieldset class="box"
tal:condition="request/search.submitted | nothing"> tal:condition="request/search.submitted | nothing">
<legend i18n:translate="">Search results</legend> <h2 i18n:translate="">Search results</h2>
<table class="listing" summary="Search results" <table class="listing" summary="Search results"
i18n:attributes="summary"> i18n:attributes="summary">
<thead> <thead>
@ -113,7 +113,7 @@
<tr> <tr>
<td metal:use-macro="macros/minus"/> <td metal:use-macro="macros/minus"/>
<td colspan="3"> <td colspan="3">
<h3 i18n:translate="">Type(s) to search for</h3> <h2 i18n:translate="">Type(s) to search for</h2>
</td> </td>
<tr> <tr>
<td></td> <td></td>
@ -144,7 +144,7 @@
<tr> <tr>
<td metal:use-macro="macros/minus"/> <td metal:use-macro="macros/minus"/>
<td colspan="3"> <td colspan="3">
<h3 i18n:translate="">Text-based search</h3> <h2 i18n:translate="">Text-based search</h2>
</td> </td>
<tr> <tr>
<td></td> <td></td>
@ -185,7 +185,7 @@
<tr> <tr>
<td metal:use-macro="macros/minus"/> <td metal:use-macro="macros/minus"/>
<td colspan="3"> <td colspan="3">
<h3 i18n:translate="">Search via related concepts</h3> <h2 i18n:translate="">Search via related concepts</h2>
</td> </td>
<tr tal:repeat="type item/presetSearchTypes"> <tr tal:repeat="type item/presetSearchTypes">
<tal:preset define="rowNum item/rowNum; <tal:preset define="rowNum item/rowNum;
@ -249,10 +249,14 @@
</td> </td>
<td> <td>
<tal:combo tal:define="dummy item/initDojo"> <tal:combo tal:define="dummy item/initDojo">
<input dojoType="comboBox" mode="remote" autoComplete="False" <div dojoType="dojox.data.QueryReadStore" jsId="conceptSearch"
url="listConceptsForComboBox.js?searchType=" >
</div>
<input dojoType="dijit.form.FilteringSelect" store="conceptSearch"
autoComplete="False" labelAttr="label"
name="concept.search.text" id="concept.search.text"
tal:attributes="name string:$namePrefix.text; tal:attributes="name string:$namePrefix.text;
id string:$idPrefix.text; id string:$idPrefix.text" />
dataUrl string:${context/@@absolute_url}/listConceptsForComboBox.js?searchString=%{searchString}&searchType=" />
</tal:combo> </tal:combo>
</td> </td>
</tr> </tr>

49
security.zcml Normal file
View 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
View file

@ -0,0 +1,3 @@
"""
$Id$
"""

133
security/common.py Normal file
View 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
View 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
View 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.
"""

View 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
View 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
View 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
View 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
View file

@ -1,5 +1,5 @@
# #
# Copyright (c) 2006 Helmut Merz helmutm@cy55.de # Copyright (c) 2008 Helmut Merz helmutm@cy55.de
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -25,13 +25,16 @@ $Id$
from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent
from zope.event import notify from zope.event import notify
from zope import component from zope import component
from zope.cachedescriptors.property import Lazy
from zope.component import adapts from zope.component import adapts
from zope.interface import implements, Interface from zope.interface import implements, Interface
from zope.traversing.api import getName
from cybertools.typology.interfaces import IType from cybertools.typology.interfaces import IType
from loops.common import adapted
from loops.concept import ConceptManager, Concept
from loops.interfaces import ILoops, ITypeConcept from loops.interfaces import ILoops, ITypeConcept
from loops.interfaces import IFile, IImage, ITextDocument, INote from loops.interfaces import IFile, IImage, ITextDocument, INote
from loops.concept import ConceptManager, Concept
from loops.query import IQueryConcept from loops.query import IQueryConcept
from loops.resource import ResourceManager, Resource from loops.resource import ResourceManager, Resource
from loops.view import ViewManager, Node from loops.view import ViewManager, Node
@ -79,7 +82,6 @@ class SetupManager(object):
domain = self.addObject(conceptManager, Concept, 'domain', title=u'Domain') domain = self.addObject(conceptManager, Concept, 'domain', title=u'Domain')
query = self.addObject(conceptManager, Concept, 'query', title=u'Query') query = self.addObject(conceptManager, Concept, 'query', title=u'Query')
file = self.addObject(conceptManager, Concept, 'file', title=u'File') file = self.addObject(conceptManager, Concept, 'file', title=u'File')
#image = self.addObject(conceptManager, Concept, 'image', title=u'Image')
textdocument = self.addObject(conceptManager, Concept, textdocument = self.addObject(conceptManager, Concept,
'textdocument', title=u'Text') 'textdocument', title=u'Text')
note = self.addObject(conceptManager, Concept, 'note', title=u'Note') note = self.addObject(conceptManager, Concept, 'note', title=u'Note')
@ -89,13 +91,105 @@ class SetupManager(object):
ITypeConcept(typeConcept).typeInterface = ITypeConcept ITypeConcept(typeConcept).typeInterface = ITypeConcept
ITypeConcept(query).typeInterface = IQueryConcept ITypeConcept(query).typeInterface = IQueryConcept
ITypeConcept(file).typeInterface = IFile ITypeConcept(file).typeInterface = IFile
#ITypeConcept(image).typeInterface = IImage
ITypeConcept(textdocument).typeInterface = ITextDocument ITypeConcept(textdocument).typeInterface = ITextDocument
ITypeConcept(note).typeInterface = INote ITypeConcept(note).typeInterface = INote
ITypeConcept(note).viewName = 'note.html' # leads to error in DocTest ITypeConcept(note).viewName = 'note.html'
hasType.conceptType = predicate hasType.conceptType = predicate
standard.conceptType = predicate standard.conceptType = predicate
# standard properties and methods
@Lazy
def concepts(self):
return self.context.getConceptManager()
@Lazy
def resources(self):
return self.context.getResourceManager()
@Lazy
def views(self):
return self.context.getViewManager()
@Lazy
def typeConcept(self):
return self.concepts.getTypeConcept()
@Lazy
def predicateType(self):
return self.concepts.getPredicateType()
def addType(self, name, title, typeInterface=None, **kw):
c = self.addConcept(name, title, self.typeConcept,
typeInterface=typeInterface, **kw)
return c
def addPredicate(self, name, title, **kw):
c = self.addConcept(name, title, self.predicateType, **kw)
return c
def addConcept(self, name, title, conceptType, description=u'',
parentName=None, **kw):
if name in self.concepts:
self.log("Concept '%s' ('%s') already exists." % (name, title))
c = self.concepts[name]
if c.conceptType != conceptType:
self.log("Wrong concept type for '%s': '%s' instead of '%s'." %
(name, getName(c.conceptType), getName(conceptType)))
else:
c = addAndConfigureObject(self.concepts, Concept, name, title=title,
description=description,
conceptType=conceptType, **kw)
self.log("Concept '%s' ('%s') created." % (name, title))
if parentName is not None:
self.assignChild(parentName, name)
return c
def setConceptAttribute(self, concept, attr, value):
setattr(adapted(concept), attr, value)
self.log("Setting Attribute '%s' of '%s' to '%s'" %
(attr, getName(concept), repr(value)))
def assignChild(self, conceptName, childName, predicate=None):
if predicate is None:
predicate = self.concepts.getDefaultPredicate()
if isinstance(predicate, str):
predicate = self.concepts[predicate]
concept = self.concepts[conceptName]
child = self.concepts[childName]
if child in concept.getChildren([predicate]):
self.log("Concept '%s' is already a child of '%s with predicate '%s'.'" %
(childName, conceptName, getName(predicate)))
else:
concept.assignChild(child, predicate)
def addNode(self, name, title, container=None, nodeType='page',
description=u'', body=u'', targetName=None, **kw):
if container is None:
container = self.views
nodeType = 'menu'
if name in container:
self.log("Node '%s' ('%s') already exists in '%s'." %
(name, title, getName(container)))
n = container[name]
if n.nodeType != nodeType:
self.log("Wrong node type for '%s': '%s' instead of '%s'." %
(name, n.nodeType, nodeType))
else:
n = addAndConfigureObject(container, Node, name, title=title,
description=description, body=body,
nodeType=nodeType, **kw)
self.log("Node '%s' ('%s') created." % (name, title))
if targetName is not None:
if targetName in self.concepts:
n.target = self.concepts[targetName]
return n
def log(self, message):
if isinstance(message, unicode):
message = message.encode('UTF-8')
print >> self.logger, message
def addObject(self, container, class_, name, **kw): def addObject(self, container, class_, name, **kw):
return addObject(container, class_, name, **kw) return addObject(container, class_, name, **kw)

60
tests/auth.py Normal file
View 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))

View file

@ -6,21 +6,31 @@ $Id$
from zope import component from zope import component
from zope.annotation.attribute import AttributeAnnotations from zope.annotation.attribute import AttributeAnnotations
from zope.annotation.interfaces import IAnnotatable
from zope.app.catalog.catalog import Catalog from zope.app.catalog.catalog import Catalog
from zope.app.catalog.interfaces import ICatalog from zope.app.catalog.interfaces import ICatalog
from zope.app.catalog.field import FieldIndex from zope.app.catalog.field import FieldIndex
from zope.app.catalog.text import TextIndex from zope.app.catalog.text import TextIndex
from zope.app.container.interfaces import IObjectRemovedEvent from zope.app.container.interfaces import IObjectRemovedEvent
from zope.app.principalannotation import PrincipalAnnotationUtility
from zope.app.principalannotation.interfaces import IPrincipalAnnotationUtility
from zope.app.security.principalregistry import principalRegistry from zope.app.security.principalregistry import principalRegistry
from zope.app.security.interfaces import IAuthentication from zope.app.security.interfaces import IAuthentication
from zope.app.securitypolicy.zopepolicy import ZopeSecurityPolicy
from zope.app.securitypolicy.principalrole import AnnotationPrincipalRoleManager
from zope.app.securitypolicy.rolepermission import AnnotationRolePermissionManager
from zope.app.session.interfaces import IClientIdManager, ISessionDataContainer from zope.app.session.interfaces import IClientIdManager, ISessionDataContainer
from zope.app.session import session from zope.app.session import session
from zope.dublincore.annotatableadapter import ZDCAnnotatableAdapter from zope.dublincore.annotatableadapter import ZDCAnnotatableAdapter
from zope.dublincore.interfaces import IZopeDublinCore from zope.dublincore.interfaces import IZopeDublinCore
from zope.interface import implements from zope.interface import Interface, implements
from zope.publisher.interfaces.browser import IBrowserRequest, IBrowserView
from zope.security.checker import Checker, defineChecker
from cybertools.browser.controller import Controller
from cybertools.composer.schema.factory import SchemaFactory from cybertools.composer.schema.factory import SchemaFactory
from cybertools.composer.schema.field import FieldInstance, NumberFieldInstance from cybertools.composer.schema.field import FieldInstance, NumberFieldInstance
from cybertools.composer.schema.field import DateFieldInstance, BooleanFieldInstance
from cybertools.composer.schema.instance import Instance, Editor from cybertools.composer.schema.instance import Instance, Editor
from cybertools.relation.tests import IntIdsStub from cybertools.relation.tests import IntIdsStub
from cybertools.relation.registry import RelationRegistry from cybertools.relation.registry import RelationRegistry
@ -34,14 +44,21 @@ from loops.base import Loops
from loops import util from loops import util
from loops.browser.node import ViewPropertiesConfigurator from loops.browser.node import ViewPropertiesConfigurator
from loops.common import NameChooser from loops.common import NameChooser
from loops.interfaces import ILoopsObject, IIndexAttributes
from loops.interfaces import IDocument, IFile, ITextDocument
from loops.concept import Concept from loops.concept import Concept
from loops.concept import IndexAttributes as ConceptIndexAttributes from loops.concept import IndexAttributes as ConceptIndexAttributes
from loops.interfaces import ILoopsObject, IIndexAttributes
from loops.interfaces import IDocument, IFile, ITextDocument
from loops.organize.memberinfo import MemberInfoProvider
from loops.query import QueryConcept
from loops.query import QueryConcept from loops.query import QueryConcept
from loops.resource import Resource, FileAdapter, TextDocumentAdapter from loops.resource import Resource, FileAdapter, TextDocumentAdapter
from loops.resource import Document, MediaAsset
from loops.resource import IndexAttributes as ResourceIndexAttributes from loops.resource import IndexAttributes as ResourceIndexAttributes
from loops.schema import ResourceSchemaFactory, FileSchemaFactory, NoteSchemaFactory from loops.schema import ResourceSchemaFactory, FileSchemaFactory, NoteSchemaFactory
from loops.security.common import grantAcquiredSecurity, revokeAcquiredSecurity
from zope.security.management import setSecurityPolicy
from loops.security.policy import LoopsSecurityPolicy
from loops.security.setter import BaseSecuritySetter
from loops.setup import SetupManager, addObject from loops.setup import SetupManager, addObject
from loops.type import LoopsType, ConceptType, ResourceType, TypeConcept from loops.type import LoopsType, ConceptType, ResourceType, TypeConcept
from loops.view import NodeAdapter from loops.view import NodeAdapter
@ -62,14 +79,25 @@ class TestSite(object):
def baseSetup(self): def baseSetup(self):
site = self.site site = self.site
#oldPolicy = setSecurityPolicy(ZopeSecurityPolicy)
oldPolicy = setSecurityPolicy(LoopsSecurityPolicy)
checker = Checker(dict(title='zope.View', data='zope.View'),
dict(title='zope.ManageContent'))
defineChecker(Concept, checker)
defineChecker(Resource, checker)
defineChecker(Document, checker)
component.provideUtility(IntIdsStub()) component.provideUtility(IntIdsStub())
relations = RelationRegistry() relations = RelationRegistry()
relations.setupIndexes() relations.setupIndexes()
component.provideUtility(relations, IRelationRegistry) component.provideUtility(relations, IRelationRegistry)
component.provideAdapter(IndexableRelationAdapter)
component.provideUtility(PrincipalAnnotationUtility(), IPrincipalAnnotationUtility)
component.provideAdapter(IndexableRelationAdapter)
component.provideAdapter(ZDCAnnotatableAdapter, (ILoopsObject,), IZopeDublinCore) component.provideAdapter(ZDCAnnotatableAdapter, (ILoopsObject,), IZopeDublinCore)
component.provideAdapter(AttributeAnnotations, (ILoopsObject,)) component.provideAdapter(AttributeAnnotations, (ILoopsObject,))
component.provideAdapter(AnnotationPrincipalRoleManager, (ILoopsObject,))
component.provideAdapter(AnnotationRolePermissionManager, (ILoopsObject,))
component.provideUtility(principalRegistry, IAuthentication) component.provideUtility(principalRegistry, IAuthentication)
component.provideAdapter(session.ClientId) component.provideAdapter(session.ClientId)
component.provideAdapter(session.Session) component.provideAdapter(session.Session)
@ -86,16 +114,26 @@ class TestSite(object):
component.provideAdapter(NodeAdapter) component.provideAdapter(NodeAdapter)
component.provideAdapter(ViewPropertiesConfigurator) component.provideAdapter(ViewPropertiesConfigurator)
component.provideAdapter(NameChooser) component.provideAdapter(NameChooser)
component.provideHandler(grantAcquiredSecurity)
component.provideHandler(revokeAcquiredSecurity)
component.provideAdapter(BaseSecuritySetter)
component.provideAdapter(Instance) component.provideAdapter(Instance)
component.provideAdapter(Editor, name='editor') component.provideAdapter(Editor, name='editor')
component.provideAdapter(FieldInstance) component.provideAdapter(FieldInstance)
component.provideAdapter(NumberFieldInstance, name='number') component.provideAdapter(NumberFieldInstance, name='number')
component.provideAdapter(DateFieldInstance, name='date')
component.provideAdapter(BooleanFieldInstance, name='boolean')
component.provideAdapter(SchemaFactory) component.provideAdapter(SchemaFactory)
component.provideAdapter(ResourceSchemaFactory) component.provideAdapter(ResourceSchemaFactory)
component.provideAdapter(FileSchemaFactory) component.provideAdapter(FileSchemaFactory)
component.provideAdapter(NoteSchemaFactory) component.provideAdapter(NoteSchemaFactory)
component.provideAdapter(Controller, (Interface, IBrowserRequest),
IBrowserView, name='controller')
component.provideAdapter(MemberInfoProvider,
(ILoopsObject, IBrowserRequest))
component.getSiteManager().registerHandler(invalidateRelations, component.getSiteManager().registerHandler(invalidateRelations,
(ILoopsObject, IObjectRemovedEvent)) (ILoopsObject, IObjectRemovedEvent))
component.getSiteManager().registerHandler(removeRelation, component.getSiteManager().registerHandler(removeRelation,

20
type.py
View file

@ -88,7 +88,6 @@ class LoopsType(BaseType):
addQualifiers = self.optionsDict.get('qualifier') addQualifiers = self.optionsDict.get('qualifier')
if addQualifiers: if addQualifiers:
qu.extend(addQualifiers.split(',')) qu.extend(addQualifiers.split(','))
# how to set a type to 'hidden'?
return tuple(qu) return tuple(qu)
@Lazy @Lazy
@ -112,14 +111,7 @@ class LoopsType(BaseType):
@Lazy @Lazy
def optionsDict(self): def optionsDict(self):
result = {'default': []} return getOptionsDict(self.options)
for opt in self.options:
if ':' in opt:
key, value = opt.split(':', 1)
result[key] = value
else:
result['default'].append(opt)
return result
# general infos # general infos
@ -291,3 +283,13 @@ class TypeInterfaceSourceList(object):
def __len__(self): def __len__(self):
return len(self.typeInterfaces) return len(self.typeInterfaces)
def getOptionsDict(options):
result = {'default': []}
for opt in options:
if ':' in opt:
key, value = opt.split(':', 1)
result[key] = value
else:
result['default'].append(opt)
return result

12
util.py
View file

@ -27,12 +27,22 @@ from zope.app.intid.interfaces import IIntIds
from zope.interface import directlyProvides, directlyProvidedBy from zope.interface import directlyProvides, directlyProvidedBy
from zope.i18nmessageid import MessageFactory from zope.i18nmessageid import MessageFactory
from zope.schema import vocabulary from zope.schema import vocabulary
#from view import TargetRelation
from loops.browser.util import html_quote
_ = MessageFactory('loops') _ = MessageFactory('loops')
#_ = MessageFactory('zope') # it's easier not use a special i18n domain? #_ = MessageFactory('zope') # it's easier not use a special i18n domain?
renderingFactories = {
'text/plain': 'zope.source.plaintext',
'text/stx': 'zope.source.stx',
'text/structured': 'zope.source.stx',
'text/rest': 'zope.source.rest',
'text/restructured': 'zope.source.rest',
}
class KeywordVocabulary(vocabulary.SimpleVocabulary): class KeywordVocabulary(vocabulary.SimpleVocabulary):
def __init__(self, items, *interfaces): def __init__(self, items, *interfaces):

View file

@ -48,6 +48,7 @@ class VersionableResource(object):
def __init__(self, context): def __init__(self, context):
self.context = context self.context = context
self.__parent__ = context
def getVersioningAttribute(self, attr, default): def getVersioningAttribute(self, attr, default):
attrName = attrPattern % attr attrName = attrPattern % attr

View file

@ -1,5 +1,5 @@
# #
# Copyright (c) 2005 Helmut Merz helmutm@cy55.de # Copyright (c) 2008 Helmut Merz helmutm@cy55.de
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -27,13 +27,12 @@ from zope.app import zapi
from zope.app.container.btree import BTreeContainer from zope.app.container.btree import BTreeContainer
from zope.app.container.contained import Contained from zope.app.container.contained import Contained
from zope.app.container.ordered import OrderedContainer from zope.app.container.ordered import OrderedContainer
from zope.app.container.traversal import ContainerTraverser, ItemTraverser
from zope.app.container.traversal import ContainerTraversable
from zope.app.intid.interfaces import IIntIds from zope.app.intid.interfaces import IIntIds
from zope.cachedescriptors.property import Lazy, readproperty from zope.cachedescriptors.property import Lazy, readproperty
from zope.component import adapts from zope.component import adapts
from zope.interface import implements from zope.interface import implements
from zope.interface import alsoProvides, directlyProvides, directlyProvidedBy from zope.interface import alsoProvides, directlyProvides, directlyProvidedBy
from zope.publisher.browser import applySkin
from zope.security.proxy import removeSecurityProxy from zope.security.proxy import removeSecurityProxy
from persistent import Persistent from persistent import Persistent
from cybertools.relation import DyadicRelation from cybertools.relation import DyadicRelation

View file

@ -8,57 +8,39 @@ Let's do some basic set up
>>> from zope.app.testing.setup import placefulSetUp, placefulTearDown >>> from zope.app.testing.setup import placefulSetUp, placefulTearDown
>>> site = placefulSetUp(True) >>> site = placefulSetUp(True)
>>> from zope import component, interface >>> from zope import component, interface
>>> from zope.publisher.browser import TestRequest >>> from zope.publisher.browser import TestRequest
>>> from loops.concept import Concept >>> from loops.concept import Concept
>>> from loops.resource import Resource >>> from loops.resource import Resource
>>>
and setup a simple loops site with a concept manager and some concepts and set up a simple loops site with a concept manager and some concepts
(with all the type machinery, what in real life is done via standard (with all the type machinery, what in real life is done via standard
ZCML setup): ZCML setup):
>>> from cybertools.relation.registry import DummyRelationRegistry >>> from loops.setup import addObject
>>> component.provideUtility(DummyRelationRegistry())
>>> from cybertools.relation.tests import IntIdsStub
>>> intIds = IntIdsStub()
>>> component.provideUtility(intIds)
>>> from loops.type import LoopsType, ConceptType, TypeConcept
>>> component.provideAdapter(LoopsType)
>>> component.provideAdapter(ConceptType)
>>> component.provideAdapter(TypeConcept)
>>> from loops.common import NameChooser
>>> component.provideAdapter(NameChooser)
>>> from loops.base import Loops
>>> loopsRoot = site['loops'] = Loops()
>>> from loops.setup import SetupManager
>>> from loops.organize.setup import SetupManager as OrganizeSetupManager >>> from loops.organize.setup import SetupManager as OrganizeSetupManager
>>> component.provideAdapter(OrganizeSetupManager, name='organize') >>> component.provideAdapter(OrganizeSetupManager, name='organize')
>>> setup = SetupManager(loopsRoot) >>> from loops.knowledge.setup import SetupManager as KnowledgeSetupManager
>>> concepts, resources, views = setup.setup() >>> component.provideAdapter(KnowledgeSetupManager, name='knowledge')
>>> from loops.tests.setup import TestSite
>>> t = TestSite(site)
>>> concepts, resources, views = t.setup()
>>> from loops import util
>>> loopsRoot = site['loops']
>>> loopsId = util.getUidForObject(loopsRoot)
Let's look what setup has provided us with: Let's look what setup has provided us with:
>>> sorted(concepts) >>> len(concepts)
[u'domain', u'file', u'hasType', u'note', u'ownedby', u'person', 19
u'predicate', u'query', u'standard', u'textdocument', u'type']
Now let's add a few more concepts: Now let's add a few more concepts:
>>> topic = concepts[u'topic'] = Concept(u'Topic') >>> topic = concepts[u'topic']
>>> intIds.register(topic) >>> zope = addObject(concepts, Concept, 'zope', title=u'Zope', conceptType=topic)
11 >>> zope3 = addObject(concepts, Concept, 'zope3', title=u'Zope 3', conceptType=topic)
>>> zope = concepts[u'zope'] = Concept(u'Zope')
>>> zope.conceptType = topic
>>> intIds.register(zope)
12
>>> zope3 = concepts[u'zope3'] = Concept(u'Zope 3')
>>> zope3.conceptType = topic
>>> intIds.register(zope3)
13
Navigation typically starts at a start object, which by default ist the Navigation typically starts at a start object, which by default ist the
domain concept (if present, otherwise the top-level type concept): domain concept (if present, otherwise the top-level type concept):
@ -70,16 +52,16 @@ domain concept (if present, otherwise the top-level type concept):
['children', 'description', 'id', 'name', 'parents', 'resources', ['children', 'description', 'id', 'name', 'parents', 'resources',
'title', 'type', 'viewName'] 'title', 'type', 'viewName']
>>> startObj['id'], startObj['name'], startObj['title'], startObj['type'] >>> startObj['id'], startObj['name'], startObj['title'], startObj['type']
('1', u'domain', u'Domain', '0') ('3', u'domain', u'Domain', '0')
There are a few standard objects we can retrieve directly: There are a few standard objects we can retrieve directly:
>>> defaultPred = xrf.getDefaultPredicate() >>> defaultPred = xrf.getDefaultPredicate()
>>> defaultPred['id'], defaultPred['name'] >>> defaultPred['id'], defaultPred['name']
('8', u'standard') ('16', u'standard')
>>> typePred = xrf.getTypePredicate() >>> typePred = xrf.getTypePredicate()
>>> typePred['id'], typePred['name'] >>> typePred['id'], typePred['name']
('7', u'hasType') ('1', u'hasType')
>>> typeConcept = xrf.getTypeConcept() >>> typeConcept = xrf.getTypeConcept()
>>> typeConcept['id'], typeConcept['name'] >>> typeConcept['id'], typeConcept['name']
('0', u'type') ('0', u'type')
@ -89,19 +71,19 @@ note that the 'hasType' predicate is not shown as it should not be
applied in an explicit assignment. applied in an explicit assignment.
>>> sorted(t['name'] for t in xrf.getConceptTypes()) >>> sorted(t['name'] for t in xrf.getConceptTypes())
[u'domain', u'file', u'note', u'person', u'predicate', u'query', [u'customer', u'domain', u'file', u'note', u'person', u'predicate', u'query',
u'textdocument', u'type'] u'task', u'textdocument', u'topic', u'type']
>>> sorted(t['name'] for t in xrf.getPredicates()) >>> sorted(t['name'] for t in xrf.getPredicates())
[u'ownedby', u'standard'] [u'depends', u'knows', u'ownedby', u'provides', u'requires', u'standard']
We can also retrieve a certain object by its id or its name: We can also retrieve a certain object by its id or its name:
>>> obj2 = xrf.getObjectById('2') >>> obj2 = xrf.getObjectById('5')
>>> obj2['id'], obj2['name'] >>> obj2['id'], obj2['name']
('2', u'query') ('5', u'query')
>>> textdoc = xrf.getObjectByName(u'textdocument') >>> textdoc = xrf.getObjectByName(u'textdocument')
>>> textdoc['id'], textdoc['name'] >>> textdoc['id'], textdoc['name']
('5', u'textdocument') ('11', u'textdocument')
All methods that retrieve one object also returns its children and parents: All methods that retrieve one object also returns its children and parents:
@ -111,8 +93,8 @@ All methods that retrieve one object also returns its children and parents:
>>> ch[0]['name'] >>> ch[0]['name']
u'hasType' u'hasType'
>>> sorted(c['name'] for c in ch[0]['objects']) >>> sorted(c['name'] for c in ch[0]['objects'])
[u'domain', u'file', u'note', u'person', u'predicate', u'query', [u'customer', u'domain', u'file', u'note', u'person', u'predicate',
u'textdocument', u'type'] u'query', u'task', u'textdocument', u'topic', u'type']
>>> pa = defaultPred['parents'] >>> pa = defaultPred['parents']
>>> len(pa) >>> len(pa)
@ -130,8 +112,8 @@ We can also retrieve children and parents explicitely:
>>> ch[0]['name'] >>> ch[0]['name']
u'hasType' u'hasType'
>>> sorted(c['name'] for c in ch[0]['objects']) >>> sorted(c['name'] for c in ch[0]['objects'])
[u'domain', u'file', u'note', u'person', u'predicate', u'query', [u'customer', u'domain', u'file', u'note', u'person', u'predicate',
u'textdocument', u'type'] u'query', u'task', u'textdocument', u'topic', u'type']
>>> pa = xrf.getParents('7') >>> pa = xrf.getParents('7')
>>> len(pa) >>> len(pa)
@ -139,7 +121,7 @@ We can also retrieve children and parents explicitely:
>>> pa[0]['name'] >>> pa[0]['name']
u'hasType' u'hasType'
>>> sorted(p['name'] for p in pa[0]['objects']) >>> sorted(p['name'] for p in pa[0]['objects'])
[u'predicate'] [u'type']
Resources Resources
--------- ---------
@ -184,19 +166,23 @@ Updating the concept map
>>> topicId = xrf.getObjectByName('topic')['id'] >>> topicId = xrf.getObjectByName('topic')['id']
>>> xrf.createConcept(topicId, u'zope2', u'Zope 2') >>> xrf.createConcept(topicId, u'zope2', u'Zope 2')
{'description': u'', 'title': u'Zope 2', 'type': '11', 'id': '17', {'description': u'', 'title': u'Zope 2', 'type': '22', 'id': '56',
'name': u'zope2'} 'name': u'zope2'}
The name of the concept is checked by a name chooser; if the corresponding The name of the concept is checked by a name chooser; if the corresponding
parameter is empty, the name will be generated from the title. parameter is empty, the name will be generated from the title.
>>> xrf.createConcept(topicId, u'', u'Python') >>> xrf.createConcept(topicId, u'', u'Python')
{'description': u'', 'title': u'Python', 'type': '11', 'id': '18', {'description': u'', 'title': u'Python', 'type': '22', 'id': '58',
'name': u'python'} 'name': u'python'}
Changing the attributes of a concept Changing the attributes of a concept
------------------------------------ ------------------------------------
>>> from loops.knowledge.knowledge import Person
>>> from loops.knowledge.interfaces import IPerson
>>> component.provideAdapter(Person, provides=IPerson)
>>> xrf.editConcept(john['id'], 'firstName', u'John') >>> xrf.editConcept(john['id'], 'firstName', u'John')
'OK' 'OK'
>>> john = xrf.getObjectById(john['id']) >>> john = xrf.getObjectById(john['id'])

View file

@ -162,7 +162,7 @@ def objectAsDict(obj, langInfo=None):
'title': adapter.title, 'description': adapter.description, 'title': adapter.title, 'description': adapter.description,
'type': getUidForObject(objType.typeProvider)} 'type': getUidForObject(objType.typeProvider)}
ti = objType.typeInterface ti = objType.typeInterface
if ti is not None: if ti is not None and adapter != obj:
#for attr in (list(adapter._adapterAttributes) + list(ti)): #for attr in (list(adapter._adapterAttributes) + list(ti)):
for attr in list(ti): for attr in list(ti):
if attr not in ('__parent__', 'context', 'id', 'name', if attr not in ('__parent__', 'context', 'id', 'name',