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

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
# it under the terms of the GNU General Public License as published by
@ -27,43 +27,7 @@ from zope import component
from zope.app.pagetemplate import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy
action_macros = ViewPageTemplateFile('action_macros.pt')
class Action(object):
template = action_macros
macroName = 'action'
condition = True
permission = None
url = '.'
viewName = ''
targetWindow = ''
title = ''
description = ''
icon = ''
cssClass = ''
onClick = ''
innerHtmlId = ''
def __init__(self, view, **kw):
self.view = view
for k, v in kw.items():
setattr(self, k, v)
@Lazy
def macro(self):
return self.template.macros[self.macroName]
@Lazy
def url(self):
return self.getActionUrl(self.view.url)
def getActionUrl(self, baseUrl):
if self.viewName:
return '/'.join((baseUrl, self.viewName))
else:
return baseUrl
from cybertools.browser.action import Action
class TargetAction(Action):

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

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

View file

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

View file

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

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
# it under the terms of the GNU General Public License as published by
@ -29,8 +29,8 @@ from zope.cachedescriptors.property import Lazy
from zope.security.proxy import removeSecurityProxy
from cStringIO import StringIO
from loops.external import IExternalContentSource
from loops.external import ILoader, IExporter
from loops.external.external import IExternalContentSource
from loops.external.external import ILoader, IExporter
class NodesExportImport(object):
@ -38,7 +38,8 @@ class NodesExportImport(object):
"""
def __init__(self, context, request):
self.context = removeSecurityProxy(context)
#self.context = removeSecurityProxy(context)
self.context = context
self.request = request
self.message = u''

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

View file

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

View file

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

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) {
token = dojo.byId(typeId).value;
uri = url + '?form.type=' + token;
dojo.io.updateNode(targetId, uri);
updateNode(uri, 'form.fields');
}
function replaceFieldsNodeForLanguage(targetId, langId, url) {
lang = dojo.byId(langId).value;
uri = url + '?loops.language=' + lang;
dojo.io.updateNode(targetId, uri);
updateNode(uri, 'form.fields');
}
function submitReplacing(targetId, formId, actionUrl) {
dojo.io.updateNode(targetId, {
url: actionUrl,
formNode: dojo.byId(formId),
method: 'post'
});
return false;
}
function xhrSubmitPopup(formId, actionUrl) {
dojo.io.bind({
url: actionUrl,
formNode: dojo.byId(formId),
method: 'post',
function submitReplacing(targetId, formId, url) {
dojo.xhrPost({
url: url,
form: dojo.byId(formId),
mimetype: "text/html",
load: function(t, d, e) {
load: function(response, ioArgs) {
replaceNode(response, targetId);
}
})
}
function xhrSubmitPopup(formId, url) {
dojo.xhrPost({
url: url,
form: dojo.byId(formId),
mimetype: "text/html",
load: function(response, ioArgs) {
window.close();
}
});
}
function submitReplacingOrReloading(targetId, formId, actionUrl) {
node = dojo.byId(targetId);
var args = {
url: actionUrl,
formNode: dojo.byId(formId),
method: 'post',
mimetype: "text/html"
};
args.load = function (t, d, e) {
if (d.length < 10) {
document.location.reload(false);
} else {
while (node.firstChild) {
dojo.dom.destroyNode(node.firstChild);
}
node.innerHTML = d;
}
};
dojo.io.bind(args);
return false;
}
function inlineEdit(id, saveUrl) {
var iconNode = dojo.byId('inlineedit_icon');
iconNode.style.visibility = 'hidden';
editor = dojo.widget.createWidget('Editor',
{items: ['save', '|', 'formatblock', '|',
'insertunorderedlist', 'insertorderedlist', '|',
'bold', 'italic', '|', 'createLink', 'insertimage'],
saveUrl: saveUrl,
//closeOnSave: true,
htmlEditing: true
//onClose: function() {
/* onSave: function() {
this.disableToolbar(true);
iconNode.style.visibility = 'visible';
//window.location.reload();
}*/
}, dojo.byId(id));
editor._save = function (e) {
if (!this._richText.isClosed) {
if (this.saveUrl.length) {
var content = {};
this._richText.contentFilters = [];
content[this.saveArgName] = this.getHtml();
content['version'] = 'this';
dojo.io.bind({method:this.saveMethod,
url:this.saveUrl,
content:content,
handle:function(type, data, ti, kwargs) {
location.reload(false);
}
}); //alert('save');
} else {
dojo.debug("please set a saveUrl for the editor");
}
if (this.closeOnSave) {
this._richText.close(e.getName().toLowerCase() == "save");
}
}
}
return false;
}
function setConceptTypeForComboBox(typeId, cbId) {
var t = dojo.byId(typeId).value;
var cb = dojo.widget.manager.getWidgetById(cbId);
var dp = cb.dataProvider;
var baseUrl = dp.searchUrl.split('&')[0];
var newUrl = baseUrl + '&searchType=' + t;
dp.searchUrl = newUrl;
cb.setValue('');
var cb = dijit.byId(cbId);
var dp = cb.store;
var baseUrl = dp.url.split('?')[0];
var newUrl = baseUrl + '?searchType=' + t;
dp.url = newUrl;
cb.setDisplayedValue('');
}
var dialogs = {}
var dialog;
var dialogName
function objectDialog(dlgName, url) {
dojo.require('dojo.widget.Dialog');
dojo.require('dojo.widget.ComboBox');
dlg = dialogs[dlgName];
if (!dlg) {
//dlg = dojo.widget.fromScript('Dialog',
dlg = dojo.widget.createWidget('Dialog',
{bgColor: 'white', bgOpacity: 0.5, toggle: 'fade',
toggleDuration: 250,
executeScripts: true,
href: url
}, dojo.byId('dialog.' + dlgName));
dialogs[dlgName] = dlg;
dojo.require('dijit.Dialog');
dojo.require('dojo.parser');
dojo.require('dijit.form.FilteringSelect');
dojo.require('dojox.data.QueryReadStore');
if (dialogName == undefined || dialogName != dlgName) {
if (dialog != undefined) {
dialog.destroyRecursive();
}
dialogName = dlgName;
dialog = new dijit.Dialog({
href: url
}, dojo.byId('dialog.' + dlgName));
}
dlg.show();
dialog.show();
}
function addConceptAssignment(prefix, suffix) {
dojo.require('dojo.html')
node = dojo.byId('form.' + suffix);
els = document.forms[0].elements;
for (var i=0; i<els.length; i++) { //getElementsByName does not work here in IE
el = els[i];
if (el.name == prefix + '.search.text_selected') {
cToken = el.value;
} else if (el.name == prefix + '.search.text') {
title = el.value;
}
}
widget = dijit.byId(prefix + '.search.text');
cToken = widget.getValue();
title = widget.getDisplayedValue();
if (cToken.length == 0) {
alert('Please select a concept!');
return false;
@ -159,9 +137,29 @@ function addConceptAssignment(prefix, suffix) {
token = cToken + ':' + pToken;
var td = document.createElement('td');
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');
tr.appendChild(td);
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"
tal:condition="view/inlineEditingActive | nothing">
<script>
dojo.require("dojo.widget.Editor");
dojo.require("dijit.Editor");
</script>
</metal:block>

View file

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

View file

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

View file

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

View file

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

View file

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

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 import interface, component, schema
from loops.interfaces import IConceptSchema
from loops.util import _
compoundPredicateName = 'ispartof'
class ICompound(Interface):
class ICompound(IConceptSchema):
""" A compound is a concept that is built up of other objects, its
parts or components.

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

View file

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

View file

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

View file

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

90
external/README.txt vendored Normal file
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
# 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)
class IExternalContentSource(Interface):
""" Provides content from an external source.
"""
@ -112,7 +112,7 @@ class NodesExporter(object):
for child in self.context.values():
self.extractNodeData(child, '', data)
return data
def extractNodeData(self, item, path, data):
name = zapi.getName(item)
data.append({
@ -126,7 +126,7 @@ class NodesExporter(object):
path = path and '%s/%s' % (path, name) or name
for child in item.values():
self.extractNodeData(child, path, data)
def dumpData(self, file=None, noclose=False):
if file is None:
@ -147,7 +147,7 @@ class NodesImporter(object):
def __init__(self, context):
self.context = context
def getData(self, file=None):
if file is None:
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')
if lang is not None and lang in self.availableLanguages:
return lang
return (negotiator.getLanguage(self.availableLanguages, self.request)
available = self.availableLanguages or ('en', 'de',)
return (negotiator.getLanguage(available, self.request)
or self.defaultLanguage)

View file

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

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

View file

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

View file

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

Binary file not shown.

View file

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

View file

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

View file

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

View file

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

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
# it under the terms of the GNU General Public License as published by
@ -36,10 +36,13 @@ from zope.i18nmessageid import MessageFactory
from zope.cachedescriptors.property import Lazy
from cybertools.typology.interfaces import IType
from loops.interfaces import ILoops
from loops.common import adapted
from loops.concept import Concept
from loops.interfaces import ILoops
from loops.organize.interfaces import IMemberRegistrationManager
from loops.organize.util import getPrincipalFolder, authPluginId, getInternalPrincipal
from loops.organize.util import getPrincipalFolder, getGroupsFolder
from loops.organize.util import getInternalPrincipal
from loops.type import getOptionsDict
from loops.util import _
@ -51,29 +54,54 @@ class MemberRegistrationManager(object):
def __init__(self, context):
self.context = context
def register(self, userId, password, lastName, firstName=u'', **kw):
def register(self, userId, password, lastName, firstName=u'',
groups=[], useExisting=False, **kw):
# step 1: create an internal principal in the loops principal folder:
pFolder = getPrincipalFolder(self.context)
title = firstName and ' '.join((firstName, lastName)) or lastName
principal = InternalPrincipal(userId, password, title)
pFolder[userId] = principal
# step 2: create a corresponding person concept:
if useExisting:
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()
id = baseId = 'person.' + userId
# TODO: use NameChooser
num = 0
while id in cm:
num +=1
id = baseId + str(num)
person = cm[id] = Concept(title)
# TODO: the name of the person type object must be kept flexible!
# So we have to search for a type concept that has IPerson as
# its typeInterface...
if useExisting and id in cm:
person = cm[id]
else:
num = 0
while id in cm:
num +=1
id = baseId + str(num)
person = cm[id] = Concept(title)
person.conceptType = cm['person']
personAdapter = IType(person).typeInterface(person)
personAdapter = adapted(person)
personAdapter.firstName = firstName
personAdapter.lastName = lastName
personAdapter.userId = '.'.join((authPluginId, userId))
personAdapter.userId = pFolder.prefix + userId
for k, v in kw.items():
setattr(personAdapter, k, v)
notify(ObjectCreatedEvent(person))
notify(ObjectModifiedEvent(person))
return personAdapter

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

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

View file

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

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

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

20
type.py
View file

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

12
util.py
View file

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

View file

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

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
# it under the terms of the GNU General Public License as published by
@ -27,13 +27,12 @@ from zope.app import zapi
from zope.app.container.btree import BTreeContainer
from zope.app.container.contained import Contained
from zope.app.container.ordered import OrderedContainer
from zope.app.container.traversal import ContainerTraverser, ItemTraverser
from zope.app.container.traversal import ContainerTraversable
from zope.app.intid.interfaces import IIntIds
from zope.cachedescriptors.property import Lazy, readproperty
from zope.component import adapts
from zope.interface import implements
from zope.interface import alsoProvides, directlyProvides, directlyProvidedBy
from zope.publisher.browser import applySkin
from zope.security.proxy import removeSecurityProxy
from persistent import Persistent
from cybertools.relation import DyadicRelation

View file

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

View file

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