merged Dojo 1.0 branch
git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@2388 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
parent
c6ff15c196
commit
5271981119
83 changed files with 3726 additions and 872 deletions
|
@ -593,7 +593,7 @@ Actions
|
|||
>>> request = TestRequest()
|
||||
>>> view = NodeView(m112, request)
|
||||
>>> view.controller = Controller(view, request)
|
||||
>>> view.setupController()
|
||||
>>> #view.setupController()
|
||||
|
||||
>>> actions = view.getActions('portlet')
|
||||
>>> len(actions)
|
||||
|
@ -758,7 +758,7 @@ The new technique uses the ``fields`` and ``data`` attributes...
|
|||
linkUrl textline False None
|
||||
|
||||
>>> view.data
|
||||
{'linkUrl': u'', 'contentType': u'', 'data': u'',
|
||||
{'linkUrl': u'http://', 'contentType': 'text/restructured', 'data': u'',
|
||||
'title': u'Test Note'}
|
||||
|
||||
The object is changed via a FormController adapter created for
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2007 Helmut Merz helmutm@cy55.de
|
||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -27,43 +27,7 @@ from zope import component
|
|||
from zope.app.pagetemplate import ViewPageTemplateFile
|
||||
from zope.cachedescriptors.property import Lazy
|
||||
|
||||
action_macros = ViewPageTemplateFile('action_macros.pt')
|
||||
|
||||
|
||||
class Action(object):
|
||||
|
||||
template = action_macros
|
||||
macroName = 'action'
|
||||
condition = True
|
||||
permission = None
|
||||
url = '.'
|
||||
viewName = ''
|
||||
targetWindow = ''
|
||||
title = ''
|
||||
description = ''
|
||||
icon = ''
|
||||
cssClass = ''
|
||||
onClick = ''
|
||||
innerHtmlId = ''
|
||||
|
||||
def __init__(self, view, **kw):
|
||||
self.view = view
|
||||
for k, v in kw.items():
|
||||
setattr(self, k, v)
|
||||
|
||||
@Lazy
|
||||
def macro(self):
|
||||
return self.template.macros[self.macroName]
|
||||
|
||||
@Lazy
|
||||
def url(self):
|
||||
return self.getActionUrl(self.view.url)
|
||||
|
||||
def getActionUrl(self, baseUrl):
|
||||
if self.viewName:
|
||||
return '/'.join((baseUrl, self.viewName))
|
||||
else:
|
||||
return baseUrl
|
||||
from cybertools.browser.action import Action
|
||||
|
||||
|
||||
class TargetAction(Action):
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
<!-- action macros -->
|
||||
|
||||
<metal:action define-macro="action">
|
||||
<div tal:condition="action/condition">
|
||||
<a href="#" target="target_window" title="Description text"
|
||||
i18n:attributes="title"
|
||||
tal:attributes="href action/url;
|
||||
target action/targetWindow;
|
||||
title action/description;
|
||||
onClick action/onClick;"><img
|
||||
src="#" alt="icon"
|
||||
tal:condition="action/icon"
|
||||
tal:attributes="src action/icon;
|
||||
alt action/description" /><span
|
||||
i18n:translate=""
|
||||
tal:condition="action/title"
|
||||
tal:content="action/title">Action Title</span></a>
|
||||
</div>
|
||||
<span id="inner.Id"
|
||||
tal:condition="action/innerHtmlId"
|
||||
tal:attributes="id action/innerHtmlId"></span>
|
||||
</metal:action>
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2006 Helmut Merz helmutm@cy55.de
|
||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -22,7 +22,6 @@ Common base class for loops browser view classes.
|
|||
$Id$
|
||||
"""
|
||||
|
||||
from zope.app import zapi
|
||||
from zope import component
|
||||
from zope.app.form.browser.interfaces import ITerms
|
||||
from zope.app.i18n.interfaces import ITranslationDomain
|
||||
|
@ -35,24 +34,27 @@ from zope.formlib import form
|
|||
from zope.formlib.form import FormFields
|
||||
from zope.formlib.namedtemplate import NamedTemplate
|
||||
from zope.interface import Interface, implements
|
||||
from zope.proxy import removeAllProxies
|
||||
from zope.publisher.browser import applySkin
|
||||
from zope.publisher.interfaces.browser import IBrowserSkinType
|
||||
from zope import schema
|
||||
from zope.schema.vocabulary import SimpleTerm
|
||||
from zope.security import canAccess, canWrite, checkPermission
|
||||
from zope.security import canAccess, checkPermission
|
||||
from zope.security.interfaces import ForbiddenAttribute, Unauthorized
|
||||
from zope.security.proxy import removeSecurityProxy
|
||||
from zope.traversing.browser import absoluteURL
|
||||
from zope.traversing.api import getName
|
||||
from zope.traversing.api import getName, getParent
|
||||
|
||||
from cybertools.ajax.dojo import dojoMacroTemplate
|
||||
from cybertools.browser.view import GenericView
|
||||
from cybertools.relation.interfaces import IRelationRegistry
|
||||
from cybertools.text import mimetypes
|
||||
from cybertools.typology.interfaces import IType, ITypeManager
|
||||
from loops.common import adapted
|
||||
from loops.i18n.browser import I18NView
|
||||
from loops.interfaces import IView
|
||||
from loops.interfaces import IView, INode
|
||||
from loops.resource import Resource
|
||||
from loops.security.common import canAccessObject, canListObject, canWriteObject
|
||||
from loops.type import ITypeConcept
|
||||
from loops import util
|
||||
from loops.util import _
|
||||
|
@ -90,29 +92,35 @@ class EditForm(form.EditForm):
|
|||
template = NamedTemplate('loops.pageform')
|
||||
|
||||
def deleteObjectAction(self):
|
||||
return None # better not to show the edit button at the moment
|
||||
parent = zapi.getParent(self.context)
|
||||
return None # better not to show the delete button at the moment
|
||||
parent = getParent(self.context)
|
||||
parentUrl = absoluteURL(parent, self.request)
|
||||
return parentUrl + '/contents.html'
|
||||
|
||||
|
||||
class BaseView(GenericView, I18NView):
|
||||
|
||||
actions = {} # default only, don't update
|
||||
actions = {}
|
||||
|
||||
def __init__(self, context, request):
|
||||
super(BaseView, self).__init__(context, request)
|
||||
# TODO: get rid of removeSecurityProxy() call
|
||||
# TODO: get rid of removeSecurityProxy() call - not yet...
|
||||
self.context = removeSecurityProxy(context)
|
||||
#self.context = context
|
||||
#self.setSkin(self.loopsRoot.skinName)
|
||||
self.checkLanguage()
|
||||
#self.checkLanguage()
|
||||
try:
|
||||
if not canAccess(context, 'title'):
|
||||
if not canAccessObject(context):
|
||||
raise Unauthorized
|
||||
#request.response.redirect('login.html')
|
||||
except ForbiddenAttribute: # ignore when testing
|
||||
pass
|
||||
|
||||
def update(self):
|
||||
result = super(BaseView, self).update()
|
||||
self.checkLanguage()
|
||||
return result
|
||||
|
||||
@Lazy
|
||||
def target(self):
|
||||
# allow for having a separate object the view acts upon
|
||||
|
@ -242,6 +250,16 @@ class BaseView(GenericView, I18NView):
|
|||
for o in objs:
|
||||
yield BaseView(o, request)
|
||||
|
||||
def renderText(self, text, contentType):
|
||||
typeKey = util.renderingFactories.get(contentType, None)
|
||||
if typeKey is None:
|
||||
if contentType == u'text/html':
|
||||
return text
|
||||
return u'<pre>%s</pre>' % util.html_quote(text)
|
||||
source = component.createObject(typeKey, text)
|
||||
view = component.getMultiAdapter((removeAllProxies(source), self.request))
|
||||
return view.render()
|
||||
|
||||
# type listings
|
||||
|
||||
def listTypes(self, include=None, exclude=None, sortOn='title'):
|
||||
|
@ -340,7 +358,7 @@ class BaseView(GenericView, I18NView):
|
|||
|
||||
@Lazy
|
||||
def editable(self):
|
||||
return canWrite(self.context, 'title')
|
||||
return canWriteObject(self.context)
|
||||
|
||||
def getActions(self, category='object', page=None):
|
||||
""" Return a list of actions that provide the view and edit actions
|
||||
|
@ -364,7 +382,7 @@ class BaseView(GenericView, I18NView):
|
|||
return False
|
||||
if ct.startswith('text/') and ct != 'text/rtf':
|
||||
return checkPermission('loops.ManageSite', self.context)
|
||||
return canWrite(self.context, 'title')
|
||||
return canWriteObject(self.context)
|
||||
|
||||
@Lazy
|
||||
def inlineEditingActive(self):
|
||||
|
@ -385,8 +403,27 @@ class BaseView(GenericView, I18NView):
|
|||
|
||||
def registerDojo(self):
|
||||
cm = self.controller.macros
|
||||
cm.register('js', 'dojo.js', resourceName='ajax.dojo/dojo.js')
|
||||
#cm.register('js', 'dojo.js', resourceName='ajax.dojo1/dojo/dojo.js')
|
||||
cm.register('js', 'dojo.js', template=dojoMacroTemplate, name='main',
|
||||
position=0,
|
||||
#djConfig='isDebug: true, parseOnLoad: true, usePlainJson: true, '
|
||||
djConfig='parseOnLoad: true, usePlainJson: true, '
|
||||
'locale: "%s"' % self.languageInfo.language)
|
||||
jsCall = 'dojo.require("dojo.parser");'
|
||||
cm.register('js-execute', jsCall, jsCall=jsCall)
|
||||
cm.register('css', identifier='tundra.css', position=0,
|
||||
resourceName='ajax.dojo/dijit/themes/tundra/tundra.css', media='all')
|
||||
cm.register('css', identifier='dojo.css', position=1,
|
||||
resourceName='ajax.dojo/dojo/resources/dojo.css', media='all')
|
||||
|
||||
def registerDojoDateWidget(self):
|
||||
self.registerDojo()
|
||||
jsCall = 'dojo.require("dijit.form.DateTextBox");'
|
||||
self.controller.macros.register('js-execute', jsCall, jsCall=jsCall)
|
||||
|
||||
def registerDojoTextWidget(self):
|
||||
self.registerDojo()
|
||||
jsCall = 'dojo.require("dijit.form.ValidationTextBox");'
|
||||
self.controller.macros.register('js-execute', jsCall, jsCall=jsCall)
|
||||
|
||||
|
||||
# vocabulary stuff
|
||||
|
@ -411,7 +448,7 @@ class LoopsTerms(object):
|
|||
def getTerm(self, value):
|
||||
#if value is None:
|
||||
# return SimpleTerm(None, '', u'not assigned')
|
||||
title = value.title or zapi.getName(value)
|
||||
title = value.title or getName(value)
|
||||
token = self.loopsRoot.getLoopsUri(value)
|
||||
return SimpleTerm(value, token, title)
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2007 Helmut Merz helmutm@cy55.de
|
||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -44,7 +44,11 @@ from zope.schema.interfaces import IIterableSource
|
|||
from zope.security.proxy import removeSecurityProxy
|
||||
from zope.traversing.api import getName
|
||||
|
||||
from cybertools.browser.action import actions
|
||||
from cybertools.composer.interfaces import IInstance
|
||||
from cybertools.composer.schema.interfaces import ISchemaFactory
|
||||
from cybertools.typology.interfaces import IType, ITypeManager
|
||||
from cybertools.util.jeep import Jeep
|
||||
from loops.browser.common import EditForm, BaseView, LoopsTerms, conceptMacrosTemplate
|
||||
from loops.common import adapted
|
||||
from loops.concept import Concept, ConceptTypeSourceList, PredicateSourceList
|
||||
|
@ -56,8 +60,14 @@ from loops.versioning.util import getVersion
|
|||
|
||||
|
||||
class ConceptEditForm(EditForm, I18NView):
|
||||
""" Classic-style (zope.formlib-based) form for editing concepts.
|
||||
"""
|
||||
|
||||
@Lazy
|
||||
#@Lazy # zope.formlib does not issue a redirect after changes, so that
|
||||
# it tries to redisplay the old form even after a type change that
|
||||
# changes the set of available attributes. So the typeInterface
|
||||
# must be recalculated e.g. after an update of the context object.
|
||||
@property
|
||||
def typeInterface(self):
|
||||
return IType(self.context).typeInterface
|
||||
|
||||
|
@ -79,7 +89,8 @@ class ConceptEditForm(EditForm, I18NView):
|
|||
|
||||
def setUpWidgets(self, ignore_request=False):
|
||||
# TODO: get rid of removeSecurityProxy(): use ConceptSchema in interfaces
|
||||
adapter = removeSecurityProxy(adapted(self.context, self.languageInfo))
|
||||
#adapter = removeSecurityProxy(adapted(self.context, self.languageInfo))
|
||||
adapter = adapted(self.context, self.languageInfo)
|
||||
self.adapters = {self.typeInterface: adapter,
|
||||
IConceptSchema: adapter}
|
||||
self.widgets = setUpEditWidgets(
|
||||
|
@ -89,9 +100,87 @@ class ConceptEditForm(EditForm, I18NView):
|
|||
if desc:
|
||||
desc.height = 2
|
||||
|
||||
|
||||
class ConceptRelationView(BaseView):
|
||||
""" For displaying children and resources lists, used by ConceptView.
|
||||
"""
|
||||
|
||||
def __init__(self, relation, request, contextIsSecond=False):
|
||||
if contextIsSecond:
|
||||
self.context = relation.second
|
||||
self.other = relation.first
|
||||
else:
|
||||
self.context = relation.first
|
||||
self.other = relation.second
|
||||
self.context = getVersion(self.context, request)
|
||||
self.predicate = relation.predicate
|
||||
self.relation = relation
|
||||
self.request = request
|
||||
|
||||
@Lazy
|
||||
def adapted(self):
|
||||
return adapted(self.context, self.languageInfo)
|
||||
|
||||
@Lazy
|
||||
def data(self):
|
||||
return self.instance.applyTemplate()
|
||||
|
||||
@Lazy
|
||||
def instance(self):
|
||||
instance = IInstance(self.adapted)
|
||||
instance.template = self.schema
|
||||
instance.view = self
|
||||
return instance
|
||||
|
||||
@Lazy
|
||||
def schema(self):
|
||||
ti = self.typeInterface or IConceptSchema
|
||||
schemaFactory = component.getAdapter(self.adapted, ISchemaFactory)
|
||||
return schemaFactory(ti, manager=self, request=self.request)
|
||||
|
||||
@Lazy
|
||||
def title(self):
|
||||
return self.adapted.title or getName(self.context)
|
||||
|
||||
@Lazy
|
||||
def description(self):
|
||||
return self.adapted.description
|
||||
|
||||
@Lazy
|
||||
def token(self):
|
||||
return ':'.join((self.loopsRoot.getLoopsUri(self.context),
|
||||
self.loopsRoot.getLoopsUri(self.predicate)))
|
||||
|
||||
@Lazy
|
||||
def uidToken(self):
|
||||
return ':'.join((util.getUidForObject(self.context),
|
||||
util.getUidForObject(self.predicate)))
|
||||
|
||||
@Lazy
|
||||
def isProtected(self):
|
||||
return getName(self.predicate) == 'hasType'
|
||||
|
||||
@Lazy
|
||||
def predicateTitle(self):
|
||||
return self.predicate.title
|
||||
|
||||
@Lazy
|
||||
def predicateUrl(self):
|
||||
return zapi.absoluteURL(self.predicate, self.request)
|
||||
|
||||
@Lazy
|
||||
def relevance(self):
|
||||
return self.relation.relevance
|
||||
|
||||
@Lazy
|
||||
def order(self):
|
||||
return self.relation.order
|
||||
|
||||
|
||||
class ConceptView(BaseView):
|
||||
|
||||
template = ViewPageTemplateFile('concept_macros.pt')
|
||||
childViewFactory = ConceptRelationView
|
||||
|
||||
@Lazy
|
||||
def macro(self):
|
||||
|
@ -108,7 +197,7 @@ class ConceptView(BaseView):
|
|||
self.request.principal)):
|
||||
cont.macros.register('portlet_right', 'parents', title=_(u'Parents'),
|
||||
subMacro=self.template.macros['parents'],
|
||||
position=0, info=self)
|
||||
priority=20, info=self)
|
||||
|
||||
@Lazy
|
||||
def adapted(self):
|
||||
|
@ -123,7 +212,8 @@ class ConceptView(BaseView):
|
|||
return self.adapted.description
|
||||
|
||||
def fieldData(self):
|
||||
# TODO: use cybertools.composer.schema.instance, see loops.browser.form
|
||||
# obsolete - use getData() instead
|
||||
# TODO: change view macros accordingly
|
||||
ti = IType(self.context).typeInterface
|
||||
if not ti:
|
||||
return
|
||||
|
@ -140,12 +230,35 @@ class ConceptView(BaseView):
|
|||
widget.setRenderedValue(value)
|
||||
yield dict(title=f.title, value=value, id=n, widget=widget)
|
||||
|
||||
def children(self, topLevelOnly=True, sort=True):
|
||||
def getData(self, omit=('title', 'description')):
|
||||
data = self.instance.applyTemplate()
|
||||
for k in omit:
|
||||
if k in data:
|
||||
del data[k]
|
||||
return data
|
||||
|
||||
@Lazy
|
||||
def data(self):
|
||||
return self.getData()
|
||||
|
||||
@Lazy
|
||||
def instance(self):
|
||||
instance = IInstance(self.adapted)
|
||||
instance.template = self.schema
|
||||
instance.view = self
|
||||
return instance
|
||||
|
||||
@Lazy
|
||||
def schema(self):
|
||||
ti = self.typeInterface or IConceptSchema
|
||||
schemaFactory = component.getAdapter(self.adapted, ISchemaFactory)
|
||||
return schemaFactory(ti, manager=self, request=self.request)
|
||||
|
||||
def getChildren(self, topLevelOnly=True, sort=True):
|
||||
cm = self.loopsRoot.getConceptManager()
|
||||
hasType = cm.getTypePredicate()
|
||||
standard = cm.getDefaultPredicate()
|
||||
#rels = self.context.getChildRelations()
|
||||
rels = (ConceptRelationView(r, self.request, contextIsSecond=True)
|
||||
rels = (self.childViewFactory(r, self.request, contextIsSecond=True)
|
||||
for r in self.context.getChildRelations(sort=None))
|
||||
if sort:
|
||||
rels = sorted(rels, key=lambda r: (r.order, r.title.lower()))
|
||||
|
@ -160,27 +273,37 @@ class ConceptView(BaseView):
|
|||
if skip: continue
|
||||
yield r
|
||||
|
||||
# Override in subclass to control what is displayd in listings:
|
||||
children = getChildren
|
||||
|
||||
def childrenAlphaGroups(self):
|
||||
letters = []
|
||||
relations = {}
|
||||
rels = self.children(topLevelOnly=False, sort=False)
|
||||
result = Jeep()
|
||||
rels = self.getChildren(topLevelOnly=False, sort=False)
|
||||
rels = sorted(rels, key=lambda r: r.title.lower())
|
||||
for letter, group in groupby(rels, lambda r: r.title.lower()[0]):
|
||||
letter = letter.upper()
|
||||
letters.append(letter)
|
||||
relations[letter] = list(group)
|
||||
return dict(letters=letters, relations=relations)
|
||||
result[letter] = list(group)
|
||||
return result
|
||||
|
||||
def childrenByType(self):
|
||||
result = Jeep()
|
||||
rels = self.getChildren(topLevelOnly=False, sort=False)
|
||||
rels = sorted(rels, key=lambda r: (r.typeTitle.lower(), r.title.lower()))
|
||||
for type, group in groupby(rels, lambda r: r.type):
|
||||
typeName = getName(type.typeProvider)
|
||||
result[typeName] = list(group)
|
||||
return result
|
||||
|
||||
def parents(self):
|
||||
rels = sorted(self.context.getParentRelations(),
|
||||
key=(lambda x: x.first.title.lower()))
|
||||
for r in rels:
|
||||
yield ConceptRelationView(r, self.request)
|
||||
yield self.childViewFactory(r, self.request)
|
||||
|
||||
def resources(self):
|
||||
rels = self.context.getResourceRelations()
|
||||
for r in rels:
|
||||
yield ConceptRelationView(r, self.request, contextIsSecond=True)
|
||||
yield self.childViewFactory(r, self.request, contextIsSecond=True)
|
||||
|
||||
@Lazy
|
||||
def view(self):
|
||||
|
@ -213,6 +336,14 @@ class ConceptView(BaseView):
|
|||
for node in self.context.getClients():
|
||||
yield NodeView(node, self.request)
|
||||
|
||||
def getActions(self, category='object', page=None):
|
||||
t = IType(self.context)
|
||||
actInfo = t.optionsDict.get('action.' + category, '')
|
||||
actNames = [n.strip() for n in actInfo.split(',')]
|
||||
if actNames:
|
||||
return actions.get(category, actNames, view=self, page=page)
|
||||
return []
|
||||
|
||||
|
||||
class ConceptConfigureView(ConceptView):
|
||||
|
||||
|
@ -321,7 +452,7 @@ class ConceptConfigureView(ConceptView):
|
|||
else:
|
||||
start = end = searchType
|
||||
criteria['loops_type'] = (start, end)
|
||||
cat = zapi.getUtility(ICatalog)
|
||||
cat = component.getUtility(ICatalog)
|
||||
result = cat.searchResults(**criteria)
|
||||
# TODO: can this be done in a faster way?
|
||||
result = [r for r in result if r.getLoopsRoot() == self.loopsRoot]
|
||||
|
@ -333,64 +464,8 @@ class ConceptConfigureView(ConceptView):
|
|||
|
||||
def predicates(self):
|
||||
preds = PredicateSourceList(self.context)
|
||||
terms = zapi.getMultiAdapter((preds, self.request), ITerms)
|
||||
terms = component.getMultiAdapter((preds, self.request), ITerms)
|
||||
for pred in preds:
|
||||
yield terms.getTerm(pred)
|
||||
|
||||
|
||||
class ConceptRelationView(BaseView):
|
||||
|
||||
def __init__(self, relation, request, contextIsSecond=False):
|
||||
if contextIsSecond:
|
||||
self.context = relation.second
|
||||
self.other = relation.first
|
||||
else:
|
||||
self.context = relation.first
|
||||
self.other = relation.second
|
||||
self.context = getVersion(self.context, request)
|
||||
self.predicate = relation.predicate
|
||||
self.relation = relation
|
||||
self.request = request
|
||||
|
||||
@Lazy
|
||||
def adapted(self):
|
||||
return adapted(self.context, self.languageInfo)
|
||||
|
||||
@Lazy
|
||||
def title(self):
|
||||
return self.adapted.title or getName(self.context)
|
||||
|
||||
@Lazy
|
||||
def description(self):
|
||||
return self.adapted.description
|
||||
|
||||
@Lazy
|
||||
def token(self):
|
||||
return ':'.join((self.loopsRoot.getLoopsUri(self.context),
|
||||
self.loopsRoot.getLoopsUri(self.predicate)))
|
||||
|
||||
@Lazy
|
||||
def uidToken(self):
|
||||
return ':'.join((util.getUidForObject(self.context),
|
||||
util.getUidForObject(self.predicate)))
|
||||
|
||||
@Lazy
|
||||
def isProtected(self):
|
||||
return zapi.getName(self.predicate) == 'hasType'
|
||||
|
||||
@Lazy
|
||||
def predicateTitle(self):
|
||||
return self.predicate.title
|
||||
|
||||
@Lazy
|
||||
def predicateUrl(self):
|
||||
return zapi.absoluteURL(self.predicate, self.request)
|
||||
|
||||
@Lazy
|
||||
def relevance(self):
|
||||
return self.relation.relevance
|
||||
|
||||
@Lazy
|
||||
def order(self):
|
||||
return self.relation.order
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<metal:data define-macro="conceptdata">
|
||||
<div tal:attributes="class string:content-$level;">
|
||||
<metal:fields use-macro="item/template/macros/concepttitle" /><br />
|
||||
<metal:fields use-macro="item/template/macros/conceptfields" /><br />
|
||||
<metal:fields use-macro="item/template/macros/conceptchildren" /><br />
|
||||
<metal:fields use-macro="item/template/macros/concepttitle" />
|
||||
<metal:fields use-macro="item/template/macros/conceptfields" />
|
||||
<metal:fields use-macro="item/template/macros/conceptchildren" />
|
||||
<metal:fields use-macro="item/template/macros/conceptresources" />
|
||||
</div>
|
||||
</metal:data>
|
||||
|
@ -10,7 +10,8 @@
|
|||
|
||||
<metal:title define-macro="concepttitle">
|
||||
<h1 tal:attributes="ondblclick item/openEditWindow">
|
||||
<span tal:content="item/title">Title</span>
|
||||
<a name="top"
|
||||
tal:content="item/title">Title</a>
|
||||
</h1>
|
||||
<p tal:define="description description|item/description"
|
||||
tal:condition="description">
|
||||
|
@ -19,13 +20,16 @@
|
|||
|
||||
|
||||
<metal:fields define-macro="conceptfields">
|
||||
<fieldset class="box"
|
||||
tal:define="data python: list(item.fieldData())"
|
||||
tal:condition="data">
|
||||
<table tal:attributes="ondblclick item/openEditWindow">
|
||||
<tr tal:repeat="field item/fieldData">
|
||||
<td><span tal:content="field/title"
|
||||
i18n:translate="" />:</td>
|
||||
<tr tal:repeat="field data">
|
||||
<td><b tal:content="field/title" i18n:translate="" />:</td>
|
||||
<td tal:content="structure field/widget"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</fieldset>
|
||||
</metal:fields>
|
||||
|
||||
|
||||
|
@ -47,7 +51,7 @@
|
|||
ondblclick python: item.openEditWindow('configure.html')"
|
||||
tal:define="children python: list(item.children())"
|
||||
tal:condition="children">
|
||||
<h2 i18n:translate="">Children</h2><br />
|
||||
<h2 i18n:translate="">Children</h2>
|
||||
<table class="listing">
|
||||
<tr>
|
||||
<th i18n:translate="">Title</th>
|
||||
|
@ -81,7 +85,7 @@
|
|||
ondblclick python: item.openEditWindow('resources.html')"
|
||||
tal:define="resources python: list(item.resources())"
|
||||
tal:condition="resources">
|
||||
<h2 i18n:translate="">Resources</h2><br />
|
||||
<h2 i18n:translate="">Resources</h2>
|
||||
<table class="listing">
|
||||
<tr>
|
||||
<th i18n:translate="">Title</th>
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
|
||||
<resource name="loops.css" file="loops.css" />
|
||||
<resource name="loops.js" file="loops.js" />
|
||||
<resource name="loops1.js" file="loops1.js" />
|
||||
|
||||
|
||||
<!-- new style pages -->
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2006 Helmut Merz helmutm@cy55.de
|
||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -29,8 +29,8 @@ from zope.cachedescriptors.property import Lazy
|
|||
from zope.security.proxy import removeSecurityProxy
|
||||
from cStringIO import StringIO
|
||||
|
||||
from loops.external import IExternalContentSource
|
||||
from loops.external import ILoader, IExporter
|
||||
from loops.external.external import IExternalContentSource
|
||||
from loops.external.external import ILoader, IExporter
|
||||
|
||||
|
||||
class NodesExportImport(object):
|
||||
|
@ -38,7 +38,8 @@ class NodesExportImport(object):
|
|||
"""
|
||||
|
||||
def __init__(self, context, request):
|
||||
self.context = removeSecurityProxy(context)
|
||||
#self.context = removeSecurityProxy(context)
|
||||
self.context = context
|
||||
self.request = request
|
||||
self.message = u''
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2007 Helmut Merz helmutm@cy55.de
|
||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -30,11 +30,9 @@ from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent
|
|||
|
||||
from zope.app.container.interfaces import INameChooser
|
||||
from zope.app.container.contained import NameChooser
|
||||
from zope.app.form.browser.textwidgets import FileWidget, TextAreaWidget
|
||||
from zope.app.pagetemplate import ViewPageTemplateFile
|
||||
from zope.cachedescriptors.property import Lazy
|
||||
from zope.contenttype import guess_content_type
|
||||
#from zope.formlib.form import Form, EditForm, FormFields
|
||||
from zope.publisher.browser import FileUpload
|
||||
from zope.publisher.interfaces import BadRequest
|
||||
from zope.security.proxy import isinstance, removeSecurityProxy
|
||||
|
@ -81,7 +79,8 @@ class ObjectForm(NodeView):
|
|||
|
||||
def closeAction(self, submit=False):
|
||||
if self.isInnerHtml:
|
||||
return 'dialogs["%s"].hide()' % self.dialog_name
|
||||
return ("closeDataWidget(%s); dialog.hide();" %
|
||||
(submit and 'true' or 'false'))
|
||||
if submit:
|
||||
return "xhrSubmitPopup('dialog_form', '%s'); return false" % (self.request.URL)
|
||||
return 'window.close()'
|
||||
|
@ -101,7 +100,10 @@ class ObjectForm(NodeView):
|
|||
|
||||
@Lazy
|
||||
def fieldRenderers(self):
|
||||
return schema_macros.macros
|
||||
renderers = dict(schema_macros.macros)
|
||||
# replace HTML edit widget with Dojo Editor
|
||||
renderers['input_html'] = self.template.macros['input_html']
|
||||
return renderers
|
||||
|
||||
@Lazy
|
||||
def fieldEditRenderers(self):
|
||||
|
@ -109,19 +111,17 @@ class ObjectForm(NodeView):
|
|||
|
||||
@Lazy
|
||||
def schema(self):
|
||||
#ti = self.typeInterface or Interface #IConcept
|
||||
ti = self.typeInterface or IConceptSchema
|
||||
schemaFactory = component.getAdapter(self.adapted, ISchemaFactory)
|
||||
return schemaFactory(ti, manager=self, request=self.request)
|
||||
|
||||
@Lazy
|
||||
def fields(self):
|
||||
return self.schema.fields
|
||||
return [f for f in self.schema.fields if not f.readonly]
|
||||
|
||||
@Lazy
|
||||
def data(self):
|
||||
instance = self.instance
|
||||
instance.template = self.schema
|
||||
data = instance.applyTemplate(mode='edit')
|
||||
form = self.request.form
|
||||
for k, v in data.items():
|
||||
|
@ -132,11 +132,15 @@ class ObjectForm(NodeView):
|
|||
|
||||
@Lazy
|
||||
def instance(self):
|
||||
return IInstance(self.adapted)
|
||||
instance = IInstance(self.adapted)
|
||||
instance.template = self.schema
|
||||
instance.view = self
|
||||
return instance
|
||||
|
||||
def __call__(self):
|
||||
if self.isInnerHtml:
|
||||
response = self.request.response
|
||||
#response.setHeader('Content-Type', 'text/html; charset=UTF-8')
|
||||
response.setHeader('Expires', 'Sat, 1 Jan 2000 00:00:00 GMT')
|
||||
response.setHeader('Pragma', 'no-cache')
|
||||
return innerHtml(self)
|
||||
|
@ -240,11 +244,16 @@ class CreateObjectForm(ObjectForm):
|
|||
|
||||
@Lazy
|
||||
def adapted(self):
|
||||
return self.typeInterface(Resource())
|
||||
ad = self.typeInterface(Resource())
|
||||
ad.storageName = 'unknown' # hack for file objects: don't try to retrieve data
|
||||
return (ad)
|
||||
|
||||
@Lazy
|
||||
def instance(self):
|
||||
return IInstance(Resource())
|
||||
instance = IInstance(self.adapted)
|
||||
instance.template = self.schema
|
||||
instance.view = self
|
||||
return instance
|
||||
|
||||
@Lazy
|
||||
def typeInterface(self):
|
||||
|
@ -278,8 +287,9 @@ class CreateObjectPopup(CreateObjectForm):
|
|||
cm = self.controller.macros
|
||||
cm.register('css', identifier='popup.css', resourceName='popup.css',
|
||||
media='all', position=4)
|
||||
jsCall = ('dojo.require("dojo.widget.Dialog");'
|
||||
'dojo.require("dojo.widget.ComboBox");')
|
||||
jsCall = ('dojo.require("dojo.parser");'
|
||||
'dojo.require("dijit.form.FilteringSelect");'
|
||||
'dojo.require("dojox.data.QueryReadStore");')
|
||||
cm.register('js-execute', jsCall, jsCall=jsCall)
|
||||
return True
|
||||
|
||||
|
@ -298,14 +308,19 @@ class CreateConceptForm(CreateObjectForm):
|
|||
|
||||
@Lazy
|
||||
def adapted(self):
|
||||
c = Concept()
|
||||
ti = self.typeInterface
|
||||
if ti is None:
|
||||
return Concept()
|
||||
return ti(Concept())
|
||||
return c
|
||||
ad = ti(c)
|
||||
return ad
|
||||
|
||||
@Lazy
|
||||
def instance(self):
|
||||
return IInstance(Concept())
|
||||
instance = IInstance(self.adapted)
|
||||
instance.template = self.schema
|
||||
instance.view = self
|
||||
return instance
|
||||
|
||||
@Lazy
|
||||
def typeInterface(self):
|
||||
|
@ -355,9 +370,6 @@ class EditObject(FormController, I18NView):
|
|||
prefix = 'form.'
|
||||
conceptPrefix = 'assignments.'
|
||||
|
||||
old = None
|
||||
selected = None
|
||||
|
||||
@Lazy
|
||||
def adapted(self):
|
||||
return adapted(self.object, self.languageInfoForUpdate)
|
||||
|
@ -377,7 +389,10 @@ class EditObject(FormController, I18NView):
|
|||
|
||||
@Lazy
|
||||
def instance(self):
|
||||
return component.getAdapter(self.adapted, IInstance, name='editor')
|
||||
instance = component.getAdapter(self.adapted, IInstance, name='editor')
|
||||
instance.template = self.schema
|
||||
instance.view = self.view
|
||||
return instance
|
||||
|
||||
@Lazy
|
||||
def loopsRoot(self):
|
||||
|
@ -406,14 +421,17 @@ class EditObject(FormController, I18NView):
|
|||
obj = self.object
|
||||
form = self.request.form
|
||||
instance = self.instance
|
||||
instance.template = self.schema
|
||||
#instance.template = self.schema
|
||||
formState = instance.applyTemplate(data=form, fieldHandlers=self.fieldHandlers)
|
||||
self.selected = []
|
||||
self.old = []
|
||||
for k in form.keys():
|
||||
if k.startswith(self.prefix):
|
||||
fn = k[len(self.prefix):]
|
||||
value = form[k]
|
||||
if fn.startswith(self.conceptPrefix) and value:
|
||||
self.collectConcepts(fn[len(self.conceptPrefix):], value)
|
||||
self.collectAutoConcepts()
|
||||
if self.old or self.selected:
|
||||
self.assignConcepts(obj)
|
||||
notify(ObjectModifiedEvent(obj))
|
||||
|
@ -441,14 +459,15 @@ class EditObject(FormController, I18NView):
|
|||
def collectConcepts(self, fieldName, value):
|
||||
if self.old is None:
|
||||
self.old = []
|
||||
if self.selected is None:
|
||||
self.selected = []
|
||||
for v in value:
|
||||
if fieldName == 'old':
|
||||
self.old.append(v)
|
||||
elif fieldName == 'selected' and v not in self.selected:
|
||||
self.selected.append(v)
|
||||
|
||||
def collectAutoConcepts(self):
|
||||
pass
|
||||
|
||||
def assignConcepts(self, obj):
|
||||
for v in self.old:
|
||||
if v not in self.selected:
|
||||
|
@ -532,6 +551,10 @@ class CreateObject(EditObject):
|
|||
|
||||
class EditConcept(EditObject):
|
||||
|
||||
@Lazy
|
||||
def typeInterface(self):
|
||||
return IType(self.object).typeInterface or IConceptSchema
|
||||
|
||||
def getConceptRelations(self, obj, predicates, concept):
|
||||
return obj.getParentRelations(predicates=predicates, parent=concept)
|
||||
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
$Id$ -->
|
||||
|
||||
<metal:block define-macro="edit" i18n:domain="loops">
|
||||
<form method="post" enctype="multipart/form-data" id="dialog_form"
|
||||
<form method="post" enctype="multipart/form-data"
|
||||
id="dialog_form" class="dialog"
|
||||
tal:define="langInfo view/languageInfo;
|
||||
languages langInfo/availableLanguages;
|
||||
language langInfo/language;
|
||||
|
@ -15,8 +16,8 @@
|
|||
<table cellpadding="3" class="form">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th colspan="5"
|
||||
tal:attributes="colspan python: useI18N and 4 or 5"><br />
|
||||
<th colspan="5" class="headline"
|
||||
tal:attributes="colspan python: useI18N and 4 or 5">
|
||||
<span tal:replace="view/title"
|
||||
i18n:translate="">Edit Information Object</span>
|
||||
</th>
|
||||
|
@ -44,7 +45,7 @@
|
|||
<tbody>
|
||||
<tr>
|
||||
<td colspan="5" class="headline"
|
||||
i18n:translate="">Concept Assignments</td>
|
||||
i18n:translate="">Assign Parent Concepts</td>
|
||||
</tr>
|
||||
<tr metal:use-macro="view/template/macros/assignments" />
|
||||
<tr metal:use-macro="view/template/macros/search_concepts" />
|
||||
|
@ -61,7 +62,8 @@
|
|||
|
||||
|
||||
<metal:block define-macro="create" i18n:domain="loops">
|
||||
<form method="post" enctype="multipart/form-data" id="dialog_form"
|
||||
<form method="post" enctype="multipart/form-data"
|
||||
id="dialog_form" class="dialog"
|
||||
tal:define="qualifier request/qualifier | string:resource;
|
||||
innerForm request/inner_form | string:inner_form.html;
|
||||
typeToken python: request.get('form.type')
|
||||
|
@ -70,8 +72,8 @@
|
|||
<input type="hidden" name="form.action" value="create"
|
||||
tal:attributes="value view/form_action" />
|
||||
<table cellpadding="3" class="form">
|
||||
<tbody><tr><th colspan="5"><br />
|
||||
<span tal:replace="view/title"
|
||||
<tbody><tr><th colspan="5" class="headline">
|
||||
<span tal:content="view/title"
|
||||
i18n:translate="">Create Information Object</span>
|
||||
<select name="form.type" id="form.type"
|
||||
tal:condition="not:fixedType"
|
||||
|
@ -154,9 +156,7 @@
|
|||
<td><label for="concept.search.type"><span i18n:translate="">Type</span>:</label></td>
|
||||
<td>
|
||||
<select id="concept.search.type"
|
||||
tal:attributes="onChange
|
||||
string:setConceptTypeForComboBox(
|
||||
'concept.search.type', 'concept.search.text')">
|
||||
onChange="setConceptTypeForComboBox('concept.search.type', 'concept.search.text')">
|
||||
<tal:types repeat="type view/conceptTypesForSearch">
|
||||
<option value="loops:*"
|
||||
i18n:translate=""
|
||||
|
@ -172,19 +172,12 @@
|
|||
<input type="hidden"
|
||||
id="concept.search.predicate"
|
||||
tal:attributes="value view/defaultPredicateUid" />
|
||||
<input dojoType="comboBox" mode="remote" autoComplete="False"
|
||||
name="concept.search.text" id="concept.search.text"
|
||||
tal:attributes="dataUrl
|
||||
string:listConceptsForComboBox.js?searchString=%{searchString}&searchType=" />
|
||||
<tal:dojo1 condition="nothing">
|
||||
<div dojoType="dojox.data.QueryReadStore" jsId="conceptSearch"
|
||||
tal:attributes="url
|
||||
string:listConceptsForComboBox.js?searchString=%{searchString}&searchType=" >
|
||||
</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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
222
browser/loops.js
222
browser/loops.js
|
@ -13,144 +13,122 @@ function focusOpener() {
|
|||
}
|
||||
}
|
||||
|
||||
function validate(nodeName, required) {
|
||||
// (work in progress) - may be used for onBlur event handler
|
||||
var w = dojo.byId(nodeName);
|
||||
if (required && w.value == '') {
|
||||
w.setAttribute('style','background-color: #ffff00');
|
||||
w.focus();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function destroyWidgets(node) {
|
||||
dojo.forEach(dojo.query('[widgetId]', node), function(n) {
|
||||
w = dijit.byNode(n);
|
||||
if (w != undefined) {
|
||||
w.destroyRecursive();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function replaceNode(html, nodeName) {
|
||||
var newNode = document.createElement('div');
|
||||
newNode.innerHTML = html;
|
||||
if (nodeName == undefined) {
|
||||
nodeName = newNode.firstChild.getAttribute('id');
|
||||
}
|
||||
if (nodeName != undefined) {
|
||||
newNode.id = nodeName;
|
||||
var node = dojo.byId(nodeName);
|
||||
destroyWidgets(node);
|
||||
var parent = node.parentNode;
|
||||
parent.replaceChild(newNode, node);
|
||||
dojo.parser.parse(newNode);
|
||||
} else {
|
||||
window.location.href = url;
|
||||
}
|
||||
}
|
||||
|
||||
function updateNode(url, nodeName) {
|
||||
dojo.xhrGet({
|
||||
url: url,
|
||||
handleAs: 'text',
|
||||
load: function(response, ioArgs) {
|
||||
replaceNode(response, nodeName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function replaceFieldsNode(targetId, typeId, url) {
|
||||
token = dojo.byId(typeId).value;
|
||||
uri = url + '?form.type=' + token;
|
||||
dojo.io.updateNode(targetId, uri);
|
||||
updateNode(uri, 'form.fields');
|
||||
}
|
||||
|
||||
function replaceFieldsNodeForLanguage(targetId, langId, url) {
|
||||
lang = dojo.byId(langId).value;
|
||||
uri = url + '?loops.language=' + lang;
|
||||
dojo.io.updateNode(targetId, uri);
|
||||
updateNode(uri, 'form.fields');
|
||||
}
|
||||
|
||||
function submitReplacing(targetId, formId, actionUrl) {
|
||||
dojo.io.updateNode(targetId, {
|
||||
url: actionUrl,
|
||||
formNode: dojo.byId(formId),
|
||||
method: 'post'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
function xhrSubmitPopup(formId, actionUrl) {
|
||||
dojo.io.bind({
|
||||
url: actionUrl,
|
||||
formNode: dojo.byId(formId),
|
||||
method: 'post',
|
||||
function submitReplacing(targetId, formId, url) {
|
||||
dojo.xhrPost({
|
||||
url: url,
|
||||
form: dojo.byId(formId),
|
||||
mimetype: "text/html",
|
||||
load: function(t, d, e) {
|
||||
load: function(response, ioArgs) {
|
||||
replaceNode(response, targetId);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function xhrSubmitPopup(formId, url) {
|
||||
dojo.xhrPost({
|
||||
url: url,
|
||||
form: dojo.byId(formId),
|
||||
mimetype: "text/html",
|
||||
load: function(response, ioArgs) {
|
||||
window.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function submitReplacingOrReloading(targetId, formId, actionUrl) {
|
||||
node = dojo.byId(targetId);
|
||||
var args = {
|
||||
url: actionUrl,
|
||||
formNode: dojo.byId(formId),
|
||||
method: 'post',
|
||||
mimetype: "text/html"
|
||||
};
|
||||
args.load = function (t, d, e) {
|
||||
if (d.length < 10) {
|
||||
document.location.reload(false);
|
||||
} else {
|
||||
while (node.firstChild) {
|
||||
dojo.dom.destroyNode(node.firstChild);
|
||||
}
|
||||
node.innerHTML = d;
|
||||
}
|
||||
};
|
||||
dojo.io.bind(args);
|
||||
return false;
|
||||
}
|
||||
|
||||
function inlineEdit(id, saveUrl) {
|
||||
var iconNode = dojo.byId('inlineedit_icon');
|
||||
iconNode.style.visibility = 'hidden';
|
||||
editor = dojo.widget.createWidget('Editor',
|
||||
{items: ['save', '|', 'formatblock', '|',
|
||||
'insertunorderedlist', 'insertorderedlist', '|',
|
||||
'bold', 'italic', '|', 'createLink', 'insertimage'],
|
||||
saveUrl: saveUrl,
|
||||
//closeOnSave: true,
|
||||
htmlEditing: true
|
||||
//onClose: function() {
|
||||
/* onSave: function() {
|
||||
this.disableToolbar(true);
|
||||
iconNode.style.visibility = 'visible';
|
||||
//window.location.reload();
|
||||
}*/
|
||||
}, dojo.byId(id));
|
||||
editor._save = function (e) {
|
||||
if (!this._richText.isClosed) {
|
||||
if (this.saveUrl.length) {
|
||||
var content = {};
|
||||
this._richText.contentFilters = [];
|
||||
content[this.saveArgName] = this.getHtml();
|
||||
content['version'] = 'this';
|
||||
dojo.io.bind({method:this.saveMethod,
|
||||
url:this.saveUrl,
|
||||
content:content,
|
||||
handle:function(type, data, ti, kwargs) {
|
||||
location.reload(false);
|
||||
}
|
||||
}); //alert('save');
|
||||
} else {
|
||||
dojo.debug("please set a saveUrl for the editor");
|
||||
}
|
||||
if (this.closeOnSave) {
|
||||
this._richText.close(e.getName().toLowerCase() == "save");
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function setConceptTypeForComboBox(typeId, cbId) {
|
||||
var t = dojo.byId(typeId).value;
|
||||
var cb = dojo.widget.manager.getWidgetById(cbId);
|
||||
var dp = cb.dataProvider;
|
||||
var baseUrl = dp.searchUrl.split('&')[0];
|
||||
var newUrl = baseUrl + '&searchType=' + t;
|
||||
dp.searchUrl = newUrl;
|
||||
cb.setValue('');
|
||||
var cb = dijit.byId(cbId);
|
||||
var dp = cb.store;
|
||||
var baseUrl = dp.url.split('?')[0];
|
||||
var newUrl = baseUrl + '?searchType=' + t;
|
||||
dp.url = newUrl;
|
||||
cb.setDisplayedValue('');
|
||||
}
|
||||
|
||||
var dialogs = {}
|
||||
var dialog;
|
||||
var dialogName
|
||||
|
||||
function objectDialog(dlgName, url) {
|
||||
dojo.require('dojo.widget.Dialog');
|
||||
dojo.require('dojo.widget.ComboBox');
|
||||
dlg = dialogs[dlgName];
|
||||
if (!dlg) {
|
||||
//dlg = dojo.widget.fromScript('Dialog',
|
||||
dlg = dojo.widget.createWidget('Dialog',
|
||||
{bgColor: 'white', bgOpacity: 0.5, toggle: 'fade',
|
||||
toggleDuration: 250,
|
||||
executeScripts: true,
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,148 +0,0 @@
|
|||
/* $Id: loops.js 1965 2007-08-27 17:33:07Z helmutm $ */
|
||||
|
||||
function openEditWindow(url) {
|
||||
zmi = window.open(url, 'zmi');
|
||||
zmi.focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
function focusOpener() {
|
||||
if (typeof(opener) != 'undefined' && opener != null) {
|
||||
opener.location.reload();
|
||||
opener.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function replaceFieldsNode(targetId, typeId, url) {
|
||||
token = dojo.byId(typeId).value;
|
||||
uri = url + '?form.type=' + token;
|
||||
dojo.io.updateNode(targetId, uri);
|
||||
}
|
||||
|
||||
function submitReplacing(targetId, formId, actionUrl) {
|
||||
dojo.io.updateNode(targetId, {
|
||||
url: actionUrl,
|
||||
formNode: dojo.byId(formId),
|
||||
method: 'post'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
function submitReplacingOrReloading(targetId, formId, actionUrl) {
|
||||
node = dojo.byId(targetId);
|
||||
var args = {
|
||||
url: actionUrl,
|
||||
formNode: dojo.byId(formId),
|
||||
method: 'post',
|
||||
mimetype: "text/html"
|
||||
};
|
||||
args.load = function (t, d, e) {
|
||||
if (d.length < 10) {
|
||||
document.location.reload(false);
|
||||
} else {
|
||||
while (node.firstChild) {
|
||||
dojo.dom.destroyNode(node.firstChild);
|
||||
}
|
||||
node.innerHTML = d;
|
||||
}
|
||||
};
|
||||
dojo.io.bind(args);
|
||||
return false;
|
||||
}
|
||||
|
||||
function inlineEdit(id, saveUrl) {
|
||||
var iconNode = dojo.byId('inlineedit_icon');
|
||||
iconNode.style.visibility = 'hidden';
|
||||
editor = dojo.widget.createWidget('Editor',
|
||||
{items: ['save', '|', 'formatblock', '|',
|
||||
'insertunorderedlist', 'insertorderedlist', '|',
|
||||
'bold', 'italic', '|', 'createLink', 'insertimage'],
|
||||
saveUrl: saveUrl,
|
||||
//closeOnSave: true,
|
||||
htmlEditing: true
|
||||
//onClose: function() {
|
||||
/* onSave: function() {
|
||||
this.disableToolbar(true);
|
||||
iconNode.style.visibility = 'visible';
|
||||
//window.location.reload();
|
||||
}*/
|
||||
}, dojo.byId(id));
|
||||
editor._save = function (e) {
|
||||
if (!this._richText.isClosed) {
|
||||
if (this.saveUrl.length) {
|
||||
var content = {};
|
||||
this._richText.contentFilters = [];
|
||||
content[this.saveArgName] = this.getHtml();
|
||||
content['version'] = 'this';
|
||||
dojo.io.bind({method:this.saveMethod,
|
||||
url:this.saveUrl,
|
||||
content:content,
|
||||
handle:function(type, data, ti, kwargs) {
|
||||
location.reload(false);
|
||||
}
|
||||
}); //alert('save');
|
||||
} else {
|
||||
dojo.debug("please set a saveUrl for the editor");
|
||||
}
|
||||
if (this.closeOnSave) {
|
||||
this._richText.close(e.getName().toLowerCase() == "save");
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function setConceptTypeForComboBox(typeId, cbId) {
|
||||
var t = dojo.byId(typeId).value;
|
||||
var cb = dijit.byId(cbId)
|
||||
var dp = cb.store;
|
||||
var baseUrl = dp.url.split('&')[0];
|
||||
var newUrl = baseUrl + '&searchType=' + t;
|
||||
dp.url = newUrl;
|
||||
cb.setValue('');
|
||||
}
|
||||
x
|
||||
var dialogs = {}
|
||||
|
||||
function objectDialog(dlgName, url) {
|
||||
dojo.require('dijit.Dialog');
|
||||
dojo.require('dijit.form.ComboBox');
|
||||
dojo.require('dojox.data.QueryReadStore');
|
||||
dlg = dialogs[dlgName];
|
||||
if (!dlg) {
|
||||
dlg = new dijit.Dialog(
|
||||
{bgColor: 'white', bgOpacity: 0.5, toggle: 'fade', toggleDuration: 250,
|
||||
executeScripts: true,
|
||||
href: url
|
||||
}, dojo.byId('dialog.' + dlgName));
|
||||
dialogs[dlgName] = dlg;
|
||||
}
|
||||
dlg.show();
|
||||
}
|
||||
|
||||
function addConceptAssignment() {
|
||||
dojo.require('dojo.html')
|
||||
node = dojo.byId('form.assignments');
|
||||
els = document.forms[0].elements;
|
||||
for (var i=0; i<els.length; i++) { //getElementsByName does not work here in IE
|
||||
el = els[i];
|
||||
if (el.name == 'concept.search.text_selected') {
|
||||
cToken = el.value;
|
||||
} else if (el.name == 'concept.search.text') {
|
||||
title = el.value;
|
||||
}
|
||||
}
|
||||
if (cToken.length == 0) {
|
||||
alert('Please select a concept!');
|
||||
return false;
|
||||
}
|
||||
pToken = dojo.byId('concept.search.predicate').value;
|
||||
token = cToken + ':' + pToken;
|
||||
var td = document.createElement('td');
|
||||
td.colSpan = 5;
|
||||
td.innerHTML = '<input type="checkbox" name="form.assignments.selected:list" value="' + token + '" checked><span>' + title + '</span>';
|
||||
var tr = document.createElement('tr');
|
||||
tr.appendChild(td);
|
||||
node.appendChild(tr);
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
<metal:block fill-slot="ecmascript_slot"
|
||||
tal:condition="view/inlineEditingActive | nothing">
|
||||
<script>
|
||||
dojo.require("dojo.widget.Editor");
|
||||
dojo.require("dijit.Editor");
|
||||
</script>
|
||||
</metal:block>
|
||||
|
||||
|
|
|
@ -44,16 +44,17 @@ from zope.security.proxy import removeSecurityProxy
|
|||
|
||||
from cybertools.ajax import innerHtml
|
||||
from cybertools.browser import configurator
|
||||
from cybertools.browser.action import Action
|
||||
from cybertools.browser.view import GenericView
|
||||
from cybertools.typology.interfaces import IType, ITypeManager
|
||||
from cybertools.xedit.browser import ExternalEditorView
|
||||
from loops.browser.action import DialogAction
|
||||
from loops.i18n.browser import i18n_macros
|
||||
from loops.interfaces import IConcept, IResource, IDocument, IMediaAsset, INode
|
||||
from loops.interfaces import IViewConfiguratorSchema
|
||||
from loops.resource import MediaAsset
|
||||
from loops import util
|
||||
from loops.util import _
|
||||
from loops.browser.action import Action, DialogAction, TargetAction
|
||||
from loops.browser.common import BaseView
|
||||
from loops.browser.concept import ConceptView
|
||||
from loops.versioning.util import getVersion
|
||||
|
@ -65,8 +66,8 @@ node_macros = ViewPageTemplateFile('node_macros.pt')
|
|||
class NodeView(BaseView):
|
||||
|
||||
_itemNum = 0
|
||||
|
||||
template = node_macros
|
||||
nextUrl = None
|
||||
|
||||
def __init__(self, context, request):
|
||||
super(NodeView, self).__init__(context, request)
|
||||
|
@ -80,23 +81,31 @@ class NodeView(BaseView):
|
|||
def setupController(self):
|
||||
cm = self.controller.macros
|
||||
cm.register('css', identifier='loops.css', resourceName='loops.css',
|
||||
media='all', position=3)
|
||||
cm.register('js', 'loops.js', resourceName='loops.js')
|
||||
#cm.register('js', 'loops.js', resourceName='loops1.js')
|
||||
media='all', priority=60)
|
||||
cm.register('js', 'loops.js', resourceName='loops.js', priority=60)
|
||||
cm.register('top_actions', 'top_actions', name='multi_actions',
|
||||
subMacros=[i18n_macros.macros['language_switch']])
|
||||
cm.register('portlet_left', 'navigation', title='Navigation',
|
||||
subMacro=node_macros.macros['menu'])
|
||||
#if not IUnauthenticatedPrincipal.providedBy(self.request.principal):
|
||||
if canWrite(self.context, 'title'):
|
||||
#cm.register('portlet_right', 'clipboard', title='Clipboard',
|
||||
# subMacro=self.template.macros['clipboard'])
|
||||
# this belongs to loops.organize; how to register portlets
|
||||
# from sub- (other) packages?
|
||||
# see controller / configurator: use multiple configurators;
|
||||
# register additional configurators (adapters) from within package.
|
||||
# this belongs to loops.organize
|
||||
cm.register('portlet_right', 'actions', title=_(u'Actions'),
|
||||
subMacro=node_macros.macros['actions'])
|
||||
subMacro=node_macros.macros['actions'],
|
||||
priority=100)
|
||||
if not IUnauthenticatedPrincipal.providedBy(self.request.principal):
|
||||
mi = self.controller.memberInfo
|
||||
title = mi.title.value or _(u'Personal Informations')
|
||||
obj = mi.get('object')
|
||||
url = obj is not None and self.getUrlForTarget(obj.value) or None
|
||||
cm.register('portlet_right', 'personal', title=title,
|
||||
subMacro=node_macros.macros['personal'],
|
||||
icon='cybertools.icons/user.png',
|
||||
url=url,
|
||||
priority=10)
|
||||
# force early portlet registrations by target by setting up target view
|
||||
self.virtualTarget
|
||||
# force early portlet registrations by target by setting up target view
|
||||
self.virtualTarget
|
||||
|
||||
|
@ -117,7 +126,7 @@ class NodeView(BaseView):
|
|||
#target = self.virtualTargetObject # ignores page even for direktly assignd target
|
||||
target = self.request.annotations.get('loops.view', {}).get('target')
|
||||
if target is not None:
|
||||
basicView = zapi.getMultiAdapter((target, self.request), name=viewName)
|
||||
basicView = component.getMultiAdapter((target, self.request), name=viewName)
|
||||
# xxx: obsolete when self.targetObject is virtual target:
|
||||
return basicView.view
|
||||
return self.page
|
||||
|
@ -154,7 +163,7 @@ class NodeView(BaseView):
|
|||
if text.startswith('<'): # seems to be HTML
|
||||
return text
|
||||
source = zapi.createObject(self.context.contentType, text)
|
||||
view = zapi.getMultiAdapter((removeAllProxies(source), self.request))
|
||||
view = component.getMultiAdapter((removeAllProxies(source), self.request))
|
||||
return view.render()
|
||||
|
||||
@Lazy
|
||||
|
@ -170,7 +179,7 @@ class NodeView(BaseView):
|
|||
def targetObjectView(self):
|
||||
obj = self.targetObject
|
||||
if obj is not None:
|
||||
basicView = zapi.getMultiAdapter((obj, self.request))
|
||||
basicView = component.getMultiAdapter((obj, self.request))
|
||||
basicView._viewName = self.context.viewName
|
||||
return basicView.view
|
||||
|
||||
|
@ -315,7 +324,7 @@ class NodeView(BaseView):
|
|||
def virtualTarget(self):
|
||||
obj = self.virtualTargetObject
|
||||
if obj is not None:
|
||||
basicView = zapi.getMultiAdapter((obj, self.request))
|
||||
basicView = component.getMultiAdapter((obj, self.request))
|
||||
if obj == self.targetObject:
|
||||
basicView._viewName = self.context.viewName
|
||||
return basicView.view
|
||||
|
@ -376,8 +385,6 @@ class NodeView(BaseView):
|
|||
|
||||
actions = dict(portlet=getPortletActions)
|
||||
|
||||
nextUrl = None
|
||||
|
||||
@Lazy
|
||||
def popupCreateObjectForm(self):
|
||||
return ("javascript:function%%20openDialog(url){"
|
||||
|
@ -401,11 +408,19 @@ class NodeView(BaseView):
|
|||
def inlineEdit(self, id):
|
||||
self.registerDojo()
|
||||
cm = self.controller.macros
|
||||
jsCall = 'dojo.require("dojo.widget.Editor")'
|
||||
jsCall = 'dojo.require("dijit.Editor")'
|
||||
cm.register('js-execute', jsCall, jsCall=jsCall)
|
||||
return ('return inlineEdit("%s", "%s/inline_save")'
|
||||
% (id, self.virtualTargetUrl))
|
||||
|
||||
def checkRTE(self):
|
||||
target = self.virtualTarget
|
||||
if target and target.inlineEditable:
|
||||
self.registerDojo()
|
||||
cm = self.controller.macros
|
||||
jsCall = 'dojo.require("dijit.Editor")'
|
||||
cm.register('js-execute', jsCall, jsCall=jsCall)
|
||||
|
||||
def externalEdit(self):
|
||||
target = self.virtualTargetObject
|
||||
if target is None:
|
||||
|
@ -423,7 +438,7 @@ class NodeView(BaseView):
|
|||
def registerDojoDialog(self):
|
||||
self.registerDojo()
|
||||
cm = self.controller.macros
|
||||
jsCall = 'dojo.require("dojo.widget.Dialog")'
|
||||
jsCall = 'dojo.require("dijit.Dialog")'
|
||||
cm.register('js-execute', jsCall, jsCall=jsCall)
|
||||
|
||||
|
||||
|
@ -527,7 +542,7 @@ class ConfigureView(NodeView):
|
|||
def target(self):
|
||||
obj = self.targetObject
|
||||
if obj is not None:
|
||||
return zapi.getMultiAdapter((obj, self.request))
|
||||
return component.getMultiAdapter((obj, self.request))
|
||||
|
||||
def update(self):
|
||||
request = self.request
|
||||
|
@ -604,7 +619,7 @@ class ConfigureView(NodeView):
|
|||
else:
|
||||
start = end = searchType
|
||||
criteria['loops_type'] = (start, end)
|
||||
cat = zapi.getUtility(ICatalog)
|
||||
cat = component.getUtility(ICatalog)
|
||||
result = cat.searchResults(**criteria)
|
||||
# TODO: can this be done in a faster way?
|
||||
result = [r for r in result if r.getLoopsRoot() == self.loopsRoot]
|
||||
|
@ -664,7 +679,7 @@ class ViewPropertiesConfigurator(object):
|
|||
options = property(getOptions, setOptions)
|
||||
|
||||
|
||||
class NodeViewConfigurator(configurator.ViewConfigurator):
|
||||
class NodeViewConfigurator(configurator.AnnotationViewConfigurator):
|
||||
""" Take properties from next menu item...
|
||||
"""
|
||||
|
||||
|
|
|
@ -27,24 +27,25 @@
|
|||
tal:condition="nocall:target">
|
||||
<tal:ignore condition="nothing">
|
||||
<div metal:define-macro="editicons"
|
||||
style="float: right; border: 1px solid #ccc;
|
||||
margin-top: 1em">
|
||||
style="float: right;">
|
||||
<span id="xedit_icon"
|
||||
tal:condition="target/xeditable | nothing">
|
||||
<a href="#" title="Edit" style="padding: 5px"
|
||||
<a href="#" title="Edit"
|
||||
tal:attributes="href string:${item/realTargetUrl}/external_edit?version=this;
|
||||
title string:Edit '${target/title}' with External Editor"><img
|
||||
src="edit.gif" alt="Edit"
|
||||
tal:attributes="src context/++resource++edit.gif" /></a>
|
||||
</span>
|
||||
<span id="inlineedit_icon"
|
||||
tal:condition="item/inlineEditable">
|
||||
xtal:condition="item/inlineEditable"
|
||||
tal:condition="nothing">
|
||||
<a href="#" title="Edit" style="padding: 5px"
|
||||
tal:attributes="title string:Edit '${target/title}' with Inline Editor;
|
||||
onclick python: item.inlineEdit(id)"><img
|
||||
src="edit.gif" alt="Edit"
|
||||
tal:attributes="src context/++resource++edit.gif" /></a>
|
||||
</span>
|
||||
<tal:rte define="dummy item/checkRTE" />
|
||||
</div>
|
||||
</tal:ignore>
|
||||
<div class="content-1 subcolumn" id="1.body"
|
||||
|
@ -178,7 +179,7 @@
|
|||
|
||||
<metal:menu define-macro="menu"
|
||||
tal:define="item nocall:view/menu | nothing;
|
||||
level level|python: 1"
|
||||
level level|python: 1;"
|
||||
tal:condition="nocall:item">
|
||||
<metal:sub define-macro="submenu">
|
||||
<div class="menu-3"
|
||||
|
@ -219,6 +220,14 @@
|
|||
</metal:actions>
|
||||
|
||||
|
||||
<metal:actions define-macro="personal">
|
||||
<div><a href="logout.html" i18n:translate="">Log out</a></div>
|
||||
<tal:actions repeat="action python:view.getActions('personal')">
|
||||
<metal:action use-macro="action/macro" />
|
||||
</tal:actions>
|
||||
</metal:actions>
|
||||
|
||||
|
||||
<!-- inner HTML macros -->
|
||||
|
||||
<div metal:define-macro="inline_edit"
|
||||
|
|
|
@ -25,7 +25,6 @@ $Id$
|
|||
import urllib
|
||||
from zope.cachedescriptors.property import Lazy
|
||||
from zope import component
|
||||
from zope.app import zapi
|
||||
from zope.app.catalog.interfaces import ICatalog
|
||||
from zope.app.container.interfaces import INameChooser
|
||||
from zope.app.form.browser.textwidgets import FileWidget
|
||||
|
@ -38,14 +37,14 @@ from zope.schema.interfaces import IBytes
|
|||
from zope.security import canAccess, canWrite
|
||||
from zope.security.proxy import removeSecurityProxy
|
||||
from zope.traversing.api import getName, getParent
|
||||
from zope.traversing.browser import absoluteURL
|
||||
|
||||
from cybertools.typology.interfaces import IType
|
||||
from cybertools.xedit.browser import ExternalEditorView, fromUnicode
|
||||
from loops.browser.action import Action, DialogAction, TargetAction
|
||||
from loops.browser.action import DialogAction, TargetAction
|
||||
from loops.browser.common import EditForm, BaseView
|
||||
from loops.browser.concept import ConceptRelationView, ConceptConfigureView
|
||||
from loops.browser.node import NodeView, node_macros
|
||||
from loops.browser.util import html_quote
|
||||
from loops.common import adapted, NameChooser
|
||||
from loops.interfaces import IBaseResource, IDocument, IMediaAsset, ITextDocument
|
||||
from loops.interfaces import ITypeConcept
|
||||
|
@ -53,14 +52,6 @@ from loops.versioning.browser import version_macros
|
|||
from loops.versioning.interfaces import IVersionable
|
||||
from loops.util import _
|
||||
|
||||
renderingFactories = {
|
||||
'text/plain': 'zope.source.plaintext',
|
||||
'text/stx': 'zope.source.stx',
|
||||
'text/structured': 'zope.source.stx',
|
||||
'text/rest': 'zope.source.rest',
|
||||
'text/restructured': 'zope.source.rest',
|
||||
}
|
||||
|
||||
|
||||
class CustomFileWidget(FileWidget):
|
||||
|
||||
|
@ -120,16 +111,18 @@ class ResourceView(BaseView):
|
|||
super(ResourceView, self).__init__(context, request)
|
||||
if not IUnauthenticatedPrincipal.providedBy(self.request.principal):
|
||||
cont = self.controller
|
||||
if cont is not None and list(self.relatedConcepts()):
|
||||
cont.macros.register('portlet_right', 'related', title=_(u'Related Items'),
|
||||
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')
|
||||
|
|
|
@ -10,6 +10,11 @@
|
|||
|
||||
|
||||
<metal:footer fill-slot="footer">
|
||||
<div tal:condition="view/editable">
|
||||
<span i18n:translate="">For quick creation of notes/links bookmark this link</span>:
|
||||
<a href="#"
|
||||
tal:attributes="href view/popupCreateObjectForm">Create loops Note</a>
|
||||
</div>
|
||||
Powered by <b><a href="http://www.python.org">Python</a></b> ·
|
||||
<b><a href="http://wiki.zope.org/zope3">Zope 3</a></b> ·
|
||||
<b><a href="http://loops.cy55.de">loops</a></b>.
|
||||
|
|
|
@ -12,9 +12,12 @@ Let's set up a loops site with basic and example concepts and resources.
|
|||
>>> from zope.app.testing.setup import placefulSetUp, placefulTearDown
|
||||
>>> site = placefulSetUp(True)
|
||||
|
||||
>>> from loops.organize.setup import SetupManager
|
||||
>>> component.provideAdapter(SetupManager, name='organize')
|
||||
>>> from loops.tests.setup import TestSite
|
||||
>>> t = TestSite(site)
|
||||
>>> concepts, resources, views = t.setup()
|
||||
>>> loopsRoot = site['loops']
|
||||
|
||||
|
||||
Compund Objects - Hierarchies with Ordered Components
|
||||
|
@ -74,3 +77,244 @@ And remove a part from the compound.
|
|||
>>> [getName(p) for p in aC01.getParts()]
|
||||
[u'd002.txt', u'd003.txt', u'd001.txt']
|
||||
|
||||
|
||||
Blogs
|
||||
=====
|
||||
|
||||
>>> from loops.compound.blog.post import BlogPost
|
||||
>>> from loops.compound.blog.interfaces import IBlogPost
|
||||
>>> component.provideAdapter(BlogPost, provides=IBlogPost)
|
||||
|
||||
>>> tBlog = addAndConfigureObject(concepts, Concept, 'blog', title=u'Blog',
|
||||
... conceptType=tType)
|
||||
>>> tBlogPost = addAndConfigureObject(concepts, Concept, 'blogpost',
|
||||
... title=u'Blog Post', conceptType=tType,
|
||||
... typeInterface=IBlogPost)
|
||||
|
||||
>>> myBlog = addAndConfigureObject(concepts, Concept, 'myblog', title=u'My Blog',
|
||||
... conceptType=tBlog)
|
||||
|
||||
>>> firstPost = addAndConfigureObject(concepts, Concept, 'firstpost',
|
||||
... title=u'My first post', conceptType=tBlogPost)
|
||||
|
||||
>>> aFirstPost = adapted(firstPost)
|
||||
>>> aFirstPost.date
|
||||
>>> aFirstPost.text = u'My first blog post.'
|
||||
>>> aFirstPost.text
|
||||
u'My first blog post.'
|
||||
>>> aFirstPost.creator
|
||||
|
||||
Blog and BlogPost views
|
||||
-----------------------
|
||||
|
||||
>>> from loops.compound.blog.browser import BlogView, BlogPostView
|
||||
>>> #from zope.publisher.browser import TestRequest
|
||||
>>> from loops.tests.auth import TestRequest
|
||||
|
||||
The blog view automatically provides a portlet action for creating
|
||||
a new post.
|
||||
|
||||
>>> view = BlogView(myBlog, TestRequest())
|
||||
>>> for act in view.getActions('portlet'):
|
||||
... print act.name
|
||||
createBlogPost
|
||||
|
||||
>>> view = BlogPostView(firstPost, TestRequest())
|
||||
>>> data = view.data
|
||||
|
||||
Automatic assignment of a blog post to the personal blog of its owner
|
||||
---------------------------------------------------------------------
|
||||
|
||||
As all the following stuff relies on a blog being assigned to a person
|
||||
we need the corresponding scaffolding from the loops.organize package.
|
||||
|
||||
>>> from loops.organize.tests import setupUtilitiesAndAdapters
|
||||
>>> setupData = setupUtilitiesAndAdapters(loopsRoot)
|
||||
|
||||
We also have to set up some components for automatic setting of
|
||||
security properties upon object creation.
|
||||
|
||||
>>> from loops.security.common import setDefaultSecurity
|
||||
>>> component.provideHandler(setDefaultSecurity)
|
||||
>>> from loops.compound.blog.security import BlogPostSecuritySetter
|
||||
>>> component.provideAdapter(BlogPostSecuritySetter)
|
||||
|
||||
Let's start with defining a user (more precisely: a principal)
|
||||
and a corresponding person.
|
||||
|
||||
>>> auth = setupData.auth
|
||||
>>> tPerson = concepts['person']
|
||||
|
||||
>>> userJohn = auth.definePrincipal('users.john', u'John', login='john')
|
||||
>>> persJohn = addAndConfigureObject(concepts, Concept, 'person.john',
|
||||
... title=u'John Smith', conceptType=tPerson,
|
||||
... userId='users.john')
|
||||
|
||||
>>> blogJohn = addAndConfigureObject(concepts, Concept, 'blog.john',
|
||||
... title=u'John\'s Blog', conceptType=tBlog)
|
||||
>>> persJohn.assignChild(blogJohn)
|
||||
|
||||
Let's now login as the newly defined user.
|
||||
|
||||
>>> from loops.tests.auth import login
|
||||
>>> login(userJohn)
|
||||
|
||||
Let's also provide some general permission settings. These are necessary
|
||||
as after logging in the permissions of the user will be checked by the
|
||||
standard checker defined in the test setup.
|
||||
|
||||
>>> grantPermission = setupData.rolePermissions.grantPermissionToRole
|
||||
>>> assignRole = setupData.principalRoles.assignRoleToPrincipal
|
||||
|
||||
>>> grantPermission('zope.View', 'zope.Member')
|
||||
>>> grantPermission('zope.View', 'loops.Owner')
|
||||
>>> grantPermission('zope.ManageContent', 'zope.ContentManager')
|
||||
>>> grantPermission('loops.ViewRestricted', 'loops.Owner')
|
||||
|
||||
>>> assignRole('zope.Member', 'users.john')
|
||||
|
||||
The automatic assignment of the blog post is done in the form controller
|
||||
used for creating the blog post.
|
||||
|
||||
>>> from loops.compound.blog.browser import CreateBlogPostForm, CreateBlogPost
|
||||
>>> input = {'title': u'John\'s first post', 'text': u'Text of John\'s post',
|
||||
... 'date': '2008-02-02T15:54:11',
|
||||
... 'privateComment': u'John\'s private comment',
|
||||
... 'form.type': '.loops/concepts/blogpost'}
|
||||
>>> cbpForm = CreateBlogPostForm(myBlog, TestRequest(form=input))
|
||||
>>> cbpController = CreateBlogPost(cbpForm, cbpForm.request)
|
||||
>>> cbpController.update()
|
||||
False
|
||||
|
||||
>>> posts = blogJohn.getChildren()
|
||||
>>> len(posts)
|
||||
1
|
||||
>>> postJohn0 = posts[0]
|
||||
>>> postJohn0.title
|
||||
u"John's first post"
|
||||
|
||||
>>> postJohn0Text = adapted(postJohn0.getResources()[0])
|
||||
>>> postJohn0Text.data
|
||||
u"Text of John's post"
|
||||
|
||||
|
||||
Security
|
||||
========
|
||||
|
||||
We first have to define some checkers that will be invoked when checking access
|
||||
to attributes.
|
||||
|
||||
>>> from zope.security.checker import Checker, defineChecker
|
||||
>>> checker = Checker(dict(title='zope.View', privateComment='loops.ViewRestricted'),
|
||||
... dict(title='zope.ManageContent',
|
||||
... privateComment='zope.ManageContent'))
|
||||
>>> #defineChecker(Concept, checker)
|
||||
>>> defineChecker(BlogPost, checker)
|
||||
|
||||
>>> from loops.resource import Resource, TextDocumentAdapter
|
||||
>>> checker = Checker(dict(title='zope.View', data='zope.View'),
|
||||
... dict(title='zope.ManageContent', data='zope.ManageContent'))
|
||||
>>> #defineChecker(Resource, checker)
|
||||
>>> defineChecker(TextDocumentAdapter, checker)
|
||||
|
||||
Standard security settings for blogs
|
||||
------------------------------------
|
||||
|
||||
TODO...
|
||||
|
||||
A personal blog is a blog that is a direct child of a person with
|
||||
an associated principal (i.e. a user id).
|
||||
|
||||
Blog posts in a personal blog can only be created by the owner of the blog.
|
||||
More generally: A personal blog may receive only blog posts as children
|
||||
that have the same owner as the blog itself.
|
||||
|
||||
A personal blog may only be assigned to other parents by the owner of
|
||||
the blog.
|
||||
|
||||
Standard security settings for blog posts
|
||||
-----------------------------------------
|
||||
|
||||
Blog posts may (only!) be edited by their owner (i.e. only the owner
|
||||
has the ManageContent permission). (TODO: Also their parent assignments may be
|
||||
changed only by the owner).
|
||||
|
||||
Note that we still are logged-in as user John.
|
||||
|
||||
>>> from zope.security import canAccess, canWrite, checkPermission
|
||||
>>> canAccess(postJohn0, 'title')
|
||||
True
|
||||
>>> canWrite(postJohn0, 'title')
|
||||
True
|
||||
|
||||
This settings are also valid for children/resources that are assigned
|
||||
via an `is Part of` relation.
|
||||
|
||||
>>> canAccess(postJohn0Text, 'data')
|
||||
True
|
||||
>>> canWrite(postJohn0Text, 'data')
|
||||
True
|
||||
|
||||
The private comment is only visible (and editable, of course) for the
|
||||
owner of the blog post.
|
||||
|
||||
>>> aPostJohn0 = adapted(postJohn0)
|
||||
>>> canAccess(aPostJohn0, 'privateComment')
|
||||
True
|
||||
>>> canWrite(aPostJohn0, 'privateComment')
|
||||
True
|
||||
|
||||
So let's now switch to another user. On a global level, Martha also has
|
||||
the ContentManager role, i.e. she is allowed to edit content objects.
|
||||
Nevertheless she is not allowed to change John's blog post.
|
||||
|
||||
>>> userMartha = auth.definePrincipal('users.martha', u'Martha', login='martha')
|
||||
>>> assignRole('zope.Member', 'users.martha')
|
||||
>>> assignRole('zope.ContentManager', 'users.martha')
|
||||
|
||||
>>> login(userMartha)
|
||||
|
||||
>>> canAccess(postJohn0, 'title')
|
||||
True
|
||||
>>> canWrite(postJohn0, 'title')
|
||||
False
|
||||
|
||||
>>> canAccess(postJohn0Text, 'data')
|
||||
True
|
||||
>>> canWrite(postJohn0Text, 'data')
|
||||
False
|
||||
|
||||
>>> canAccess(aPostJohn0, 'privateComment')
|
||||
False
|
||||
>>> canWrite(aPostJohn0, 'privateComment')
|
||||
False
|
||||
|
||||
A blog post marked as private is only visible for its owner.
|
||||
|
||||
>>> login(userJohn)
|
||||
>>> aPostJohn0.private = True
|
||||
>>> canAccess(postJohn0, 'title')
|
||||
True
|
||||
>>> canAccess(postJohn0Text, 'data')
|
||||
True
|
||||
|
||||
>>> login(userMartha)
|
||||
>>> canAccess(postJohn0, 'data')
|
||||
False
|
||||
>>> canAccess(postJohn0Text, 'data')
|
||||
False
|
||||
|
||||
When we clear the `private` flag the post becomes visible again.
|
||||
|
||||
>>> aPostJohn0.private = False
|
||||
>>> canAccess(postJohn0, 'title')
|
||||
True
|
||||
>>> canAccess(postJohn0Text, 'title')
|
||||
True
|
||||
|
||||
|
||||
Fin de partie
|
||||
=============
|
||||
|
||||
>>> placefulTearDown()
|
||||
|
||||
|
|
|
@ -34,22 +34,28 @@ class Compound(AdapterBase):
|
|||
|
||||
implements(ICompound)
|
||||
|
||||
@Lazy
|
||||
def compoundPredicate(self):
|
||||
return self.context.getConceptManager()[compoundPredicateName]
|
||||
|
||||
def getParts(self):
|
||||
return self.context.getChildren([self.partOf])
|
||||
if self.context.__parent__ is None:
|
||||
return []
|
||||
return self.context.getResources([self.partOf])
|
||||
|
||||
def add(self, obj, position=None):
|
||||
if position is None:
|
||||
order = self.getMaxOrder() + 1
|
||||
else:
|
||||
order = self.getOrderForPosition(position)
|
||||
self.context.assignChild(obj, self.partOf, order=order)
|
||||
self.context.assignResource(obj, self.partOf, order=order)
|
||||
|
||||
def remove(self, obj, position=None):
|
||||
if position is None:
|
||||
self.context.deassignChild(obj, [self.partOf])
|
||||
self.context.deassignResource(obj, [self.partOf])
|
||||
else:
|
||||
rel = self.getPartRelations()[position]
|
||||
self.context.deassignChild(obj, [self.partOf], order=rel.order)
|
||||
self.context.deassignResource(obj, [self.partOf], order=rel.order)
|
||||
|
||||
def reorder(self, parts):
|
||||
existing = list(self.getPartRelations())
|
||||
|
@ -71,7 +77,7 @@ class Compound(AdapterBase):
|
|||
# helper methods and properties
|
||||
|
||||
def getPartRelations(self):
|
||||
return self.context.getChildRelations([self.partOf])
|
||||
return self.context.getResourceRelations([self.partOf])
|
||||
|
||||
def getMaxOrder(self):
|
||||
rels = self. getPartRelations()
|
||||
|
@ -105,6 +111,10 @@ class Compound(AdapterBase):
|
|||
def conceptManager(self):
|
||||
return self.context.getConceptManager()
|
||||
|
||||
@Lazy
|
||||
def resourceManager(self):
|
||||
return self.getLoopsRoot().getResourceManager()
|
||||
|
||||
@Lazy
|
||||
def partOf(self):
|
||||
return self.conceptManager[compoundPredicateName]
|
||||
|
|
4
compound/blog/__init__.py
Normal file
4
compound/blog/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
"""
|
||||
$Id$
|
||||
"""
|
||||
|
190
compound/blog/browser.py
Executable file
190
compound/blog/browser.py
Executable file
|
@ -0,0 +1,190 @@
|
|||
#
|
||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
|
||||
"""
|
||||
View classes for glossary and glossary items.
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
||||
|
||||
import itertools
|
||||
from zope import component
|
||||
from zope.app.pagetemplate import ViewPageTemplateFile
|
||||
from zope.cachedescriptors.property import Lazy
|
||||
|
||||
from cybertools.browser.action import actions
|
||||
from cybertools.browser.member import IMemberInfoProvider
|
||||
from loops.browser.action import DialogAction
|
||||
from loops.browser.concept import ConceptView, ConceptRelationView
|
||||
from loops.browser.form import CreateConceptForm, EditConceptForm
|
||||
from loops.browser.form import CreateConcept, EditConcept
|
||||
from loops.common import adapted
|
||||
from loops.organize.party import getPersonForUser
|
||||
from loops.security.common import checkPermission
|
||||
from loops import util
|
||||
from loops.util import _
|
||||
|
||||
|
||||
view_macros = ViewPageTemplateFile('view_macros.pt')
|
||||
|
||||
|
||||
actions.register('createBlogPost', 'portlet', DialogAction,
|
||||
title=_(u'Create Blog Post...'),
|
||||
description=_(u'Create a new blog post.'),
|
||||
viewName='create_blogpost.html',
|
||||
dialogName='createBlogPost',
|
||||
typeToken='.loops/concepts/blogpost',
|
||||
fixedType=True,
|
||||
innerForm='inner_concept_form.html',
|
||||
prerequisites=['registerDojoDateWidget'], # +'registerDojoTextWidget'?
|
||||
)
|
||||
|
||||
|
||||
def supplyCreator(self, data):
|
||||
creator = data.get('creator')
|
||||
data['creatorId'] = creator
|
||||
if creator:
|
||||
mip = component.getMultiAdapter((self.context, self.request),
|
||||
IMemberInfoProvider)
|
||||
mi = mip.getData(creator)
|
||||
data['creator'] = mi.title.value or creator
|
||||
obj = mi.get('object')
|
||||
if obj is not None:
|
||||
data['creatorUrl'] = self.controller.view.getUrlForTarget(obj.value)
|
||||
return data
|
||||
|
||||
|
||||
class BlogRelationView(ConceptRelationView):
|
||||
|
||||
@Lazy
|
||||
def data(self):
|
||||
data = super(BlogRelationView, self).data
|
||||
return supplyCreator(self, data)
|
||||
|
||||
|
||||
class BlogView(ConceptView):
|
||||
|
||||
childViewFactory = BlogRelationView
|
||||
|
||||
@Lazy
|
||||
def macro(self):
|
||||
return view_macros.macros['blog']
|
||||
|
||||
def getActions(self, category='object', page=None):
|
||||
blogOwnerId = self.blogOwnerId
|
||||
if blogOwnerId:
|
||||
principal = self.request.principal
|
||||
if principal and principal.id != blogOwnerId:
|
||||
return []
|
||||
return actions.get(category, ['createBlogPost'], view=self, page=page)
|
||||
|
||||
@Lazy
|
||||
def blogOwnerId(self):
|
||||
pType = self.loopsRoot.getConceptManager()['person']
|
||||
persons = [p for p in self.context.getParents() if p.conceptType == pType]
|
||||
if len(persons) == 1:
|
||||
return adapted(persons[0]).userId
|
||||
return ''
|
||||
|
||||
@Lazy
|
||||
def allChildren(self):
|
||||
return self.childrenByType()
|
||||
|
||||
def blogPosts(self):
|
||||
posts = self.allChildren.get('blogpost', [])
|
||||
return reversed(sorted(posts, key=lambda x: x.adapted.date))
|
||||
|
||||
def children(self, topLevelOnly=True, sort=True):
|
||||
rels = itertools.chain(*[self.allChildren[k]
|
||||
for k in self.allChildren.keys()
|
||||
if k != 'blogpost'])
|
||||
return sorted(rels, key=lambda r: (r.order, r.title.lower()))
|
||||
|
||||
|
||||
class BlogPostView(ConceptView):
|
||||
|
||||
@Lazy
|
||||
def macro(self):
|
||||
return view_macros.macros['blogpost']
|
||||
|
||||
@Lazy
|
||||
def data(self):
|
||||
data = super(BlogPostView, self).data
|
||||
data = supplyCreator(self, data)
|
||||
if not checkPermission('loops.ViewRestricted', self.context):
|
||||
data['privateComment'] = u''
|
||||
return data
|
||||
|
||||
def getActions(self, category='object', page=None):
|
||||
actions = []
|
||||
if category == 'portlet' and self.editable:
|
||||
actions.append(DialogAction(self, title=_(u'Edit Blog Post...'),
|
||||
description=_(u'Modify blog post.'),
|
||||
viewName='edit_blogpost.html',
|
||||
dialogName='editBlogPost',
|
||||
page=page))
|
||||
#self.registerDojoTextWidget()
|
||||
self.registerDojoDateWidget()
|
||||
return actions
|
||||
|
||||
def render(self):
|
||||
return self.renderText(self.data['text'], self.adapted.textContentType)
|
||||
|
||||
def resources(self):
|
||||
stdPred = self.loopsRoot.getConceptManager().getDefaultPredicate()
|
||||
rels = self.context.getResourceRelations([stdPred])
|
||||
for r in rels:
|
||||
yield self.childViewFactory(r, self.request, contextIsSecond=True)
|
||||
|
||||
|
||||
class EditBlogPostForm(EditConceptForm):
|
||||
|
||||
title = _(u'Edit Blog Post')
|
||||
form_action = 'edit_blogpost'
|
||||
|
||||
|
||||
class CreateBlogPostForm(CreateConceptForm):
|
||||
|
||||
title = _(u'Create Blog Post')
|
||||
form_action = 'create_blogpost'
|
||||
|
||||
|
||||
class EditBlogPost(EditConcept):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class CreateBlogPost(CreateConcept):
|
||||
|
||||
def collectAutoConcepts(self):
|
||||
#super(CreateBlogPost, self).collectConcepts(fieldName, value)
|
||||
person = getPersonForUser(self.container, self.request)
|
||||
if person is not None:
|
||||
concepts = self.loopsRoot.getConceptManager()
|
||||
blogType = concepts.get('blog')
|
||||
if blogType is not None:
|
||||
blogs = [c for c in person.getChildren()
|
||||
if c.conceptType == blogType]
|
||||
if blogs:
|
||||
blogUid = util.getUidForObject(blogs[0])
|
||||
predUid = util.getUidForObject(concepts.getDefaultPredicate())
|
||||
token = '%s:%s' % (blogUid, predUid)
|
||||
if token not in self.selected:
|
||||
self.selected.append(token)
|
||||
|
74
compound/blog/configure.zcml
Normal file
74
compound/blog/configure.zcml
Normal file
|
@ -0,0 +1,74 @@
|
|||
<!-- $Id$ -->
|
||||
|
||||
<configure
|
||||
xmlns:zope="http://namespaces.zope.org/zope"
|
||||
xmlns:browser="http://namespaces.zope.org/browser"
|
||||
i18n_domain="zope">
|
||||
|
||||
<zope:adapter
|
||||
factory="loops.compound.blog.post.BlogPost"
|
||||
provides="loops.compound.blog.interfaces.IBlogPost"
|
||||
trusted="True" />
|
||||
<zope:class class="loops.compound.blog.post.BlogPost">
|
||||
<require permission="zope.View"
|
||||
interface="loops.compound.blog.interfaces.IBlogPost" />
|
||||
<require permission="zope.ManageContent"
|
||||
set_schema="loops.compound.blog.interfaces.IBlogPost" />
|
||||
</zope:class>
|
||||
|
||||
<zope:adapter factory="loops.compound.blog.schema.BlogPostSchemaFactory" />
|
||||
|
||||
<zope:adapter factory="loops.compound.blog.security.BlogPostSecuritySetter" />
|
||||
|
||||
<!-- views -->
|
||||
|
||||
<zope:adapter
|
||||
name="blog.html"
|
||||
for="loops.interfaces.IConcept
|
||||
zope.publisher.interfaces.browser.IBrowserRequest"
|
||||
provides="zope.interface.Interface"
|
||||
factory="loops.compound.blog.browser.BlogView"
|
||||
permission="zope.View"
|
||||
/>
|
||||
|
||||
<zope:adapter
|
||||
name="blogpost.html"
|
||||
for="loops.interfaces.IConcept
|
||||
zope.publisher.interfaces.browser.IBrowserRequest"
|
||||
provides="zope.interface.Interface"
|
||||
factory="loops.compound.blog.browser.BlogPostView"
|
||||
permission="zope.View"
|
||||
/>
|
||||
|
||||
<browser:page
|
||||
name="create_blogpost.html"
|
||||
for="loops.interfaces.INode"
|
||||
class="loops.compound.blog.browser.CreateBlogPostForm"
|
||||
permission="zope.ManageContent"
|
||||
/>
|
||||
|
||||
<browser:page
|
||||
name="edit_blogpost.html"
|
||||
for="loops.interfaces.INode"
|
||||
class="loops.compound.blog.browser.EditBlogPostForm"
|
||||
permission="zope.ManageContent"
|
||||
/>
|
||||
|
||||
<zope:adapter
|
||||
name="create_blogpost"
|
||||
for="loops.browser.node.NodeView
|
||||
zope.publisher.interfaces.browser.IBrowserRequest"
|
||||
provides="zope.interface.Interface"
|
||||
factory="loops.compound.blog.browser.CreateBlogPost"
|
||||
permission="zope.ManageContent"
|
||||
/>
|
||||
|
||||
<zope:adapter
|
||||
name="edit_blogpost"
|
||||
for="loops.browser.node.NodeView
|
||||
zope.publisher.interfaces.browser.IBrowserRequest"
|
||||
factory="loops.compound.blog.browser.EditBlogPost"
|
||||
permission="zope.ManageContent"
|
||||
/>
|
||||
|
||||
</configure>
|
62
compound/blog/interfaces.py
Normal file
62
compound/blog/interfaces.py
Normal file
|
@ -0,0 +1,62 @@
|
|||
#
|
||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
|
||||
"""
|
||||
Blogs (weblogs) and blog posts.
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from zope.interface import Interface, Attribute
|
||||
from zope import interface, component, schema
|
||||
|
||||
from loops.compound.interfaces import ICompound
|
||||
from loops.util import _
|
||||
|
||||
|
||||
class IBlogPost(ICompound):
|
||||
""" An item on a blog, sort of a diary item.
|
||||
"""
|
||||
|
||||
date = schema.Datetime(
|
||||
title=_(u'Date/Time'),
|
||||
description=_(u'The date and time the information '
|
||||
'was posted.'),
|
||||
required=True,)
|
||||
date.default_method = datetime.now
|
||||
private = schema.Bool(
|
||||
title=_(u'Private'),
|
||||
description=_(u'Check this field if the blog post '
|
||||
'should be accessible only for a limited audience.'),
|
||||
required=False)
|
||||
creator = schema.ASCIILine(
|
||||
title=_(u'Creator'),
|
||||
description=_(u'The principal id of the user that created '
|
||||
'the blog post.'),
|
||||
readonly=True,
|
||||
required=False,)
|
||||
text = schema.Text(
|
||||
title=_(u'Text'),
|
||||
description=_(u'The text of your blog entry'),
|
||||
required=False)
|
||||
privateComment = schema.Text(
|
||||
title=_(u'Private Comment'),
|
||||
description=_(u'A text that is not visible for other users.'),
|
||||
required=False)
|
||||
|
94
compound/blog/post.py
Normal file
94
compound/blog/post.py
Normal file
|
@ -0,0 +1,94 @@
|
|||
#
|
||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
|
||||
"""
|
||||
Blogs and blog posts.
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
||||
from zope.cachedescriptors.property import Lazy
|
||||
from zope.dublincore.interfaces import IZopeDublinCore
|
||||
from zope.interface import implements
|
||||
from zope.event import notify
|
||||
from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent
|
||||
from zope import schema
|
||||
from zope.traversing.api import getName
|
||||
|
||||
from loops.common import adapted
|
||||
from loops.compound.base import Compound
|
||||
from loops.compound.blog.interfaces import IBlogPost
|
||||
from loops.resource import Resource
|
||||
from loops.security.common import restrictView
|
||||
from loops.setup import addAndConfigureObject
|
||||
from loops.type import TypeInterfaceSourceList
|
||||
|
||||
|
||||
TypeInterfaceSourceList.typeInterfaces += (IBlogPost,)
|
||||
|
||||
|
||||
class BlogPost(Compound):
|
||||
|
||||
implements(IBlogPost)
|
||||
|
||||
_adapterAttributes = Compound._adapterAttributes + ('text', 'private', 'creator',)
|
||||
_contextAttributes = Compound._contextAttributes + ['date', 'privateComment']
|
||||
|
||||
defaultTextContentType = 'text/restructured'
|
||||
textContentType = defaultTextContentType
|
||||
|
||||
@Lazy
|
||||
def isPartOf(self):
|
||||
return self.context.getLoopsRoot().getConceptManager()['ispartof']
|
||||
|
||||
def getPrivate(self):
|
||||
return getattr(self.context, '_private', False)
|
||||
def setPrivate(self, value):
|
||||
self.context._private = value
|
||||
restrictView(self.context, revert=not value)
|
||||
for r in self.context.getResources([self.isPartOf], noSecurityCheck=True):
|
||||
restrictView(r, revert=not value)
|
||||
private = property(getPrivate, setPrivate)
|
||||
|
||||
def getText(self):
|
||||
res = self.getParts()
|
||||
if len(res) > 0:
|
||||
return adapted(res[0]).data
|
||||
return u''
|
||||
def setText(self, value):
|
||||
res = self.getParts()
|
||||
if len(res) > 0:
|
||||
res = adapted(res[0])
|
||||
else:
|
||||
tTextDocument = self.conceptManager['textdocument']
|
||||
name = getName(self.context) + '_text'
|
||||
res = addAndConfigureObject(self.resourceManager, Resource, name,
|
||||
title=self.title, contentType=self.defaultTextContentType,
|
||||
resourceType=tTextDocument)
|
||||
self.add(res, position=0)
|
||||
notify(ObjectCreatedEvent(res))
|
||||
res = adapted(res)
|
||||
res.data = value
|
||||
notify(ObjectModifiedEvent(res.context))
|
||||
text = property(getText, setText)
|
||||
|
||||
@property
|
||||
def creator(self):
|
||||
cr = IZopeDublinCore(self.context).creators
|
||||
return cr and cr[0] or None
|
||||
|
39
compound/blog/schema.py
Normal file
39
compound/blog/schema.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
#
|
||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
|
||||
"""
|
||||
Specialized schema factories
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
||||
from zope.component import adapts
|
||||
|
||||
from cybertools.composer.schema.factory import SchemaFactory
|
||||
from loops.compound.blog.interfaces import IBlogPost
|
||||
|
||||
|
||||
class BlogPostSchemaFactory(SchemaFactory):
|
||||
|
||||
adapts(IBlogPost)
|
||||
|
||||
def __call__(self, interface, **kw):
|
||||
schema = super(BlogPostSchemaFactory, self).__call__(interface, **kw)
|
||||
schema.fields.text.height = 10
|
||||
return schema
|
||||
|
66
compound/blog/security.py
Normal file
66
compound/blog/security.py
Normal file
|
@ -0,0 +1,66 @@
|
|||
#
|
||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
|
||||
"""
|
||||
Security settings for blogs and blog posts.
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
||||
from zope.cachedescriptors.property import Lazy
|
||||
from zope.component import adapts
|
||||
from zope.interface import implements
|
||||
from zope.traversing.api import getName
|
||||
|
||||
from loops.compound.blog.interfaces import IBlogPost
|
||||
from loops.security.common import allowEditingForOwner, assignOwner, restrictView
|
||||
from loops.security.common import getCurrentPrincipal
|
||||
from loops.security.setter import BaseSecuritySetter
|
||||
|
||||
|
||||
class BlogPostSecuritySetter(BaseSecuritySetter):
|
||||
|
||||
adapts(IBlogPost)
|
||||
|
||||
def setDefaultRolePermissions(self):
|
||||
allowEditingForOwner(self.context.context)
|
||||
|
||||
def setDefaultPrincipalRoles(self):
|
||||
assignOwner(self.context.context, self.principalId)
|
||||
|
||||
def setAcquiredRolePermissions(self, relation, revert=False):
|
||||
if isAcquiring(relation.predicate):
|
||||
allowEditingForOwner(relation.second, revert=revert)
|
||||
if self.context.private:
|
||||
restrictView(relation.second, revert=revert)
|
||||
|
||||
def setAcquiredPrincipalRoles(self, relation, revert=False):
|
||||
if isAcquiring(relation.predicate):
|
||||
if revert:
|
||||
removeOwner(relation.second, self.principalId)
|
||||
else:
|
||||
assignOwner(relation.second, self.principalId)
|
||||
|
||||
@Lazy
|
||||
def principalId(self):
|
||||
return getCurrentPrincipal().id
|
||||
|
||||
|
||||
def isAcquiring(predicate):
|
||||
# TODO: use a predicate property for this.
|
||||
return getName(predicate) in ('ispartof',)
|
71
compound/blog/view_macros.pt
Executable file
71
compound/blog/view_macros.pt
Executable file
|
@ -0,0 +1,71 @@
|
|||
<!-- ZPT macros for loops.knowledge.glossary views
|
||||
$Id$ -->
|
||||
|
||||
<metal:block define-macro="blog">
|
||||
<metal:title use-macro="item/conceptMacros/concepttitle" />
|
||||
<div tal:repeat="related item/blogPosts"
|
||||
class="blog">
|
||||
<tal:child define="data related/data">
|
||||
<h1 class="headline">
|
||||
<a href="#"
|
||||
tal:content="related/title"
|
||||
tal:attributes="href python: view.getUrlForTarget(related);">Post</a>
|
||||
</h1>
|
||||
<div class="info"
|
||||
tal:define="url data/creatorUrl|nothing">
|
||||
<span tal:content="data/date">2008-01-02</span> /
|
||||
<a tal:omit-tag="not:url"
|
||||
tal:content="data/creator"
|
||||
tal:attributes="href url">Will Smith</a>
|
||||
</div>
|
||||
<div class="description"
|
||||
tal:define="description data/description"
|
||||
tal:condition="description">
|
||||
<span tal:content="structure python:
|
||||
item.renderText(description, 'text/restructured')">Description</span></div>
|
||||
<div class="text"
|
||||
tal:condition="nothing"
|
||||
xtal:condition="python: repeat['related'].index() < 3">
|
||||
<span tal:content="structure python:
|
||||
item.renderText(data['text'], related.adapted.textContentType)"></span>
|
||||
</div>
|
||||
</tal:child>
|
||||
</div>
|
||||
<metal:resources use-macro="item/conceptMacros/conceptchildren" />
|
||||
<metal:resources use-macro="item/conceptMacros/conceptresources" />
|
||||
</metal:block>
|
||||
|
||||
|
||||
<div metal:define-macro="blogpost"
|
||||
tal:define="data item/data"
|
||||
class="blogpost">
|
||||
<h1 tal:attributes="ondblclick item/openEditWindow">
|
||||
<span tal:content="item/title">Title</span>
|
||||
</h1>
|
||||
<div class="info"
|
||||
tal:define="url data/creatorUrl|nothing">
|
||||
<span tal:content="data/date">2008-01-02</span> /
|
||||
<a tal:omit-tag="not:url"
|
||||
tal:content="data/creator"
|
||||
tal:attributes="href url">Will Smith</a>
|
||||
<span tal:condition="item/adapted/private">
|
||||
(<span i18n:translate="">Private</span>)
|
||||
</span>
|
||||
</div>
|
||||
<div class="description"
|
||||
tal:define="description description|item/description"
|
||||
tal:condition="description">
|
||||
<span tal:content="structure python:
|
||||
item.renderText(description, 'text/restructured')">Description</span>
|
||||
</div>
|
||||
<div class="text"
|
||||
tal:content="structure item/render">Here comes the text...</div>
|
||||
<div class="comment"
|
||||
tal:define="comment data/privateComment"
|
||||
tal:condition="comment">
|
||||
<h4 i18n:translate="" class="headline">Private Comment</h4>
|
||||
<div tal:content="structure python:
|
||||
item.renderText(comment, 'text/restructured')">Comment</div></div>
|
||||
<metal:resources use-macro="item/conceptMacros/conceptchildren" />
|
||||
<metal:resources use-macro="item/conceptMacros/conceptresources" />
|
||||
</div>
|
|
@ -28,13 +28,14 @@ $Id$
|
|||
from zope.interface import Interface, Attribute
|
||||
from zope import interface, component, schema
|
||||
|
||||
from loops.interfaces import IConceptSchema
|
||||
from loops.util import _
|
||||
|
||||
|
||||
compoundPredicateName = 'ispartof'
|
||||
|
||||
|
||||
class ICompound(Interface):
|
||||
class ICompound(IConceptSchema):
|
||||
""" A compound is a concept that is built up of other objects, its
|
||||
parts or components.
|
||||
|
||||
|
|
89
concept.py
89
concept.py
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2007 Helmut Merz helmutm@cy55.de
|
||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -27,6 +27,8 @@ from zope.app.container.btree import BTreeContainer
|
|||
from zope.app.container.contained import Contained
|
||||
from zope.cachedescriptors.property import Lazy
|
||||
from zope.component import adapts
|
||||
from zope.component.interfaces import ObjectEvent
|
||||
from zope.event import notify
|
||||
from zope.interface import implements
|
||||
from zope.interface import alsoProvides, directlyProvides, directlyProvidedBy
|
||||
from zope.security.proxy import removeSecurityProxy, isinstance
|
||||
|
@ -35,7 +37,6 @@ from persistent import Persistent
|
|||
|
||||
from cybertools.relation import DyadicRelation
|
||||
from cybertools.relation.registry import getRelations
|
||||
from cybertools.relation.registry import getRelationSingle, setRelationSingle
|
||||
from cybertools.relation.interfaces import IRelationRegistry, IRelatable
|
||||
from cybertools.typology.interfaces import IType, ITypeManager
|
||||
from cybertools.util.jeep import Jeep
|
||||
|
@ -46,6 +47,8 @@ from loops.interfaces import IConcept, IConceptRelation, IConceptView
|
|||
from loops.interfaces import IConceptManager, IConceptManagerContained
|
||||
from loops.interfaces import ILoopsContained
|
||||
from loops.interfaces import IIndexAttributes
|
||||
from loops.interfaces import IAssignmentEvent, IDeassignmentEvent
|
||||
from loops.security.common import canListObject
|
||||
from loops import util
|
||||
from loops.view import TargetRelation
|
||||
|
||||
|
@ -159,29 +162,35 @@ class Concept(Contained, Persistent):
|
|||
if relationships is None:
|
||||
relationships = [TargetRelation]
|
||||
rels = getRelations(second=self, relationships=relationships)
|
||||
return [r.first for r in rels]
|
||||
return [r.first for r in rels if canListObject(r.first)]
|
||||
|
||||
def getChildRelations(self, predicates=None, child=None, sort='default'):
|
||||
def getChildRelations(self, predicates=None, child=None, sort='default',
|
||||
noSecurityCheck=False):
|
||||
predicates = predicates is None and ['*'] or predicates
|
||||
relationships = [ConceptRelation(self, None, p) for p in predicates]
|
||||
if sort == 'default':
|
||||
sort = lambda x: (x.order, x.second.title.lower())
|
||||
return sorted(getRelations(first=self, second=child, relationships=relationships),
|
||||
key=sort)
|
||||
rels = (r for r in getRelations(self, child, relationships=relationships)
|
||||
if canListObject(r.second, noSecurityCheck))
|
||||
return sorted(rels, key=sort)
|
||||
|
||||
def getChildren(self, predicates=None, sort='default'):
|
||||
return [r.second for r in self.getChildRelations(predicates, sort=sort)]
|
||||
def getChildren(self, predicates=None, sort='default', noSecurityCheck=False):
|
||||
return [r.second for r in self.getChildRelations(predicates, sort=sort,
|
||||
noSecurityCheck=noSecurityCheck)]
|
||||
|
||||
def getParentRelations (self, predicates=None, parent=None, sort='default'):
|
||||
def getParentRelations (self, predicates=None, parent=None, sort='default',
|
||||
noSecurityCheck=False):
|
||||
predicates = predicates is None and ['*'] or predicates
|
||||
relationships = [ConceptRelation(None, self, p) for p in predicates]
|
||||
if sort == 'default':
|
||||
sort = lambda x: (x.order, x.first.title.lower())
|
||||
return sorted(getRelations(first=parent, second=self, relationships=relationships),
|
||||
key=sort)
|
||||
rels = (r for r in getRelations(parent, self, relationships=relationships)
|
||||
if canListObject(r.first, noSecurityCheck))
|
||||
return sorted(rels, key=sort)
|
||||
|
||||
def getParents(self, predicates=None, sort='default'):
|
||||
return [r.first for r in self.getParentRelations(predicates, sort=sort)]
|
||||
def getParents(self, predicates=None, sort='default', noSecurityCheck=False):
|
||||
return [r.first for r in self.getParentRelations(predicates, sort=sort,
|
||||
noSecurityCheck=noSecurityCheck)]
|
||||
|
||||
def assignChild(self, concept, predicate=None, order=0, relevance=1.0):
|
||||
if predicate is None:
|
||||
|
@ -194,6 +203,7 @@ class Concept(Contained, Persistent):
|
|||
rel.relevance = relevance
|
||||
# TODO (?): avoid duplicates
|
||||
registry.register(rel)
|
||||
notify(AssignmentEvent(self, rel))
|
||||
|
||||
def setChildren(self, predicate, concepts):
|
||||
existing = self.getChildren([predicate])
|
||||
|
@ -211,6 +221,7 @@ class Concept(Contained, Persistent):
|
|||
registry = component.getUtility(IRelationRegistry)
|
||||
for rel in self.getChildRelations(predicates, child):
|
||||
if order is None or rel.order == order:
|
||||
notify(DeassignmentEvent(self, rel))
|
||||
registry.unregister(rel)
|
||||
|
||||
def deassignParent(self, parent, predicates=None):
|
||||
|
@ -218,17 +229,19 @@ class Concept(Contained, Persistent):
|
|||
|
||||
# resource relations
|
||||
|
||||
def getResourceRelations(self, predicates=None, resource=None, sort='default'):
|
||||
def getResourceRelations(self, predicates=None, resource=None, sort='default',
|
||||
noSecurityCheck=False):
|
||||
predicates = predicates is None and ['*'] or predicates
|
||||
relationships = [ResourceRelation(self, None, p) for p in predicates]
|
||||
if sort == 'default':
|
||||
sort = lambda x: (x.order, x.second.title.lower())
|
||||
return sorted(getRelations(
|
||||
first=self, second=resource, relationships=relationships),
|
||||
key=sort)
|
||||
rels = (r for r in getRelations(self, resource, relationships=relationships)
|
||||
if canListObject(r.second, noSecurityCheck))
|
||||
return sorted(rels, key=sort)
|
||||
|
||||
def getResources(self, predicates=None):
|
||||
return [r.second for r in self.getResourceRelations(predicates)]
|
||||
def getResources(self, predicates=None, sort='default', noSecurityCheck=False):
|
||||
return [r.second for r in self.getResourceRelations(predicates, sort=sort,
|
||||
noSecurityCheck=noSecurityCheck)]
|
||||
|
||||
def assignResource(self, resource, predicate=None, order=0, relevance=1.0):
|
||||
if predicate is None:
|
||||
|
@ -241,12 +254,27 @@ class Concept(Contained, Persistent):
|
|||
rel.relevance = relevance
|
||||
# TODO (?): avoid duplicates
|
||||
registry.register(rel)
|
||||
notify(AssignmentEvent(self, rel))
|
||||
|
||||
def deassignResource(self, resource, predicates=None):
|
||||
def deassignResource(self, resource, predicates=None, order=None):
|
||||
registry = component.getUtility(IRelationRegistry)
|
||||
for rel in self.getResourceRelations(predicates, resource):
|
||||
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
|
||||
|
|
|
@ -9,43 +9,14 @@
|
|||
|
||||
<!-- security definitions -->
|
||||
|
||||
<permission
|
||||
id="loops.xmlrpc.ManageConcepts"
|
||||
title="[loops-xmlrpc-manage-concepts-permission] loops: Manage Concepts (XML-RPC)"
|
||||
/>
|
||||
|
||||
<role
|
||||
id="loops.xmlrpc.ConceptManager"
|
||||
title="[xmlrpc-manage-concepts-role] loops: Concept Manager (XML-RPC)" />
|
||||
|
||||
<grant
|
||||
permission="loops.xmlrpc.ManageConcepts"
|
||||
role="loops.xmlrpc.ConceptManager" />
|
||||
|
||||
<permission
|
||||
id="loops.ManageSite"
|
||||
title="[loops-manage-site-permission] loops: Manage Site"
|
||||
/>
|
||||
|
||||
<role
|
||||
id="loops.SiteManager"
|
||||
title="[loops-manage-site-role] loops: Site Manager" />
|
||||
|
||||
<grant
|
||||
permission="loops.ManageSite"
|
||||
role="loops.SiteManager" />
|
||||
|
||||
<grant
|
||||
permission="loops.xmlrpc.ManageConcepts"
|
||||
role="loops.SiteManager" />
|
||||
<include file="security.zcml" />
|
||||
|
||||
<!-- event subscribers -->
|
||||
|
||||
<subscriber
|
||||
for=".interfaces.ITargetRelation
|
||||
cybertools.relation.interfaces.IRelationInvalidatedEvent"
|
||||
handler=".util.removeTargetRelation"
|
||||
/>
|
||||
handler=".util.removeTargetRelation" />
|
||||
|
||||
<!-- loops top-level container -->
|
||||
|
||||
|
@ -120,22 +91,22 @@
|
|||
type="zope.app.content.interfaces.IContentType" />
|
||||
|
||||
<class class=".concept.Concept">
|
||||
<implements interface="zope.annotation.interfaces.IAttributeAnnotatable" />
|
||||
<factory id="loops.Concept" description="Concept object" />
|
||||
<require permission="zope.View" interface=".interfaces.IConcept" />
|
||||
<require permission="zope.ManageContent" set_schema=".interfaces.IConcept" />
|
||||
</class>
|
||||
|
||||
<implements
|
||||
interface="zope.annotation.interfaces.IAttributeAnnotatable" />
|
||||
|
||||
<factory
|
||||
id="loops.Concept"
|
||||
description="Concept object" />
|
||||
|
||||
<require
|
||||
permission="zope.View"
|
||||
interface=".interfaces.IConcept" />
|
||||
|
||||
<require
|
||||
permission="zope.ManageContent"
|
||||
set_schema=".interfaces.IConcept" />
|
||||
<class class="loops.concept.ConceptRelation">
|
||||
<require permission="zope.View" interface="loops.interfaces.IConceptRelation" />
|
||||
<require permission="zope.ManageContent"
|
||||
set_schema="loops.interfaces.IConceptRelation" />
|
||||
</class>
|
||||
|
||||
<class class="loops.concept.ResourceRelation">
|
||||
<require permission="zope.View" interface="loops.interfaces.IConceptRelation" />
|
||||
<require permission="zope.ManageContent"
|
||||
set_schema="loops.interfaces.IConceptRelation" />
|
||||
</class>
|
||||
|
||||
<!-- resource manager and resource -->
|
||||
|
@ -225,7 +196,6 @@
|
|||
interface=".interfaces.IBaseResource
|
||||
zope.size.interfaces.ISized" />
|
||||
|
||||
|
||||
<require
|
||||
permission="zope.ManageContent"
|
||||
set_schema=".interfaces.IBaseResource" />
|
||||
|
@ -398,6 +368,10 @@
|
|||
<adapter factory="cybertools.composer.schema.field.FieldInstance" />
|
||||
<adapter factory="cybertools.composer.schema.field.NumberFieldInstance"
|
||||
name="number" />
|
||||
<adapter factory="cybertools.composer.schema.field.DateFieldInstance"
|
||||
name="date" />
|
||||
<adapter factory="cybertools.composer.schema.field.BooleanFieldInstance"
|
||||
name="boolean" />
|
||||
<adapter factory="cybertools.composer.schema.field.FileUploadFieldInstance"
|
||||
name="fileupload" />
|
||||
|
||||
|
@ -480,7 +454,9 @@
|
|||
|
||||
<include package=".browser" />
|
||||
<include package=".classifier" />
|
||||
<include package=".compound.blog" />
|
||||
<include package=".constraint" />
|
||||
<include package=".external" />
|
||||
<include package=".i18n" />
|
||||
<include package=".integrator" />
|
||||
<include package=".knowledge" />
|
||||
|
@ -488,6 +464,7 @@
|
|||
<include package=".process" />
|
||||
<include package=".rest" />
|
||||
<include package=".search" />
|
||||
<include package=".security" />
|
||||
<include package=".versioning" />
|
||||
<include package=".xmlrpc" />
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ configuration):
|
|||
>>> #sorted(concepts)
|
||||
>>> #sorted(resources)
|
||||
>>> len(concepts) + len(resources)
|
||||
35
|
||||
38
|
||||
|
||||
|
||||
Type- and Text-based Queries
|
||||
|
@ -41,11 +41,11 @@ Type- and Text-based Queries
|
|||
>>> from loops.expert import query
|
||||
>>> qu = query.Title('ty*')
|
||||
>>> list(qu.apply())
|
||||
[0, 1, 41]
|
||||
[0, 1, 50]
|
||||
|
||||
>>> qu = query.Type('loops:*')
|
||||
>>> len(list(qu.apply()))
|
||||
35
|
||||
38
|
||||
|
||||
>>> qu = query.Type('loops:concept:predicate')
|
||||
>>> len(list(qu.apply()))
|
||||
|
@ -67,7 +67,7 @@ syntax (that in turn is based on hurry.query).
|
|||
>>> stateNew = concepts['new']
|
||||
>>> qu = query.Resources(stateNew)
|
||||
>>> list(qu.apply())
|
||||
[57, 62]
|
||||
[66, 71]
|
||||
|
||||
|
||||
Fin de partie
|
||||
|
|
|
@ -5,63 +5,32 @@ $Id$
|
|||
"""
|
||||
|
||||
from zope import component
|
||||
from zope.app.catalog.catalog import Catalog
|
||||
from zope.app.catalog.interfaces import ICatalog
|
||||
from zope.app.catalog.field import FieldIndex
|
||||
from zope.app.catalog.text import TextIndex
|
||||
|
||||
from cybertools.relation.tests import IntIdsStub
|
||||
from cybertools.relation.registry import RelationRegistry
|
||||
from cybertools.relation.interfaces import IRelationRegistry
|
||||
from cybertools.relation.registry import IndexableRelationAdapter
|
||||
from cybertools.typology.interfaces import IType
|
||||
|
||||
from loops.base import Loops
|
||||
from loops import util
|
||||
from loops.interfaces import IIndexAttributes
|
||||
from loops.concept import Concept
|
||||
from loops.concept import IndexAttributes as ConceptIndexAttributes
|
||||
from loops.resource import Resource
|
||||
from loops.resource import IndexAttributes as ResourceIndexAttributes
|
||||
from loops.knowledge.setup import SetupManager as KnowledgeSetupManager
|
||||
from loops.setup import SetupManager, addObject
|
||||
from loops.tests.setup import TestSite as BaseTestSite
|
||||
from loops.type import ConceptType, ResourceType, TypeConcept
|
||||
|
||||
|
||||
class TestSite(object):
|
||||
class TestSite(BaseTestSite):
|
||||
|
||||
def __init__(self, site):
|
||||
self.site = site
|
||||
|
||||
def setup(self):
|
||||
super(TestSite, self).setup()
|
||||
site = self.site
|
||||
|
||||
component.provideUtility(IntIdsStub())
|
||||
relations = RelationRegistry()
|
||||
relations.setupIndexes()
|
||||
component.provideUtility(relations, IRelationRegistry)
|
||||
component.provideAdapter(IndexableRelationAdapter)
|
||||
|
||||
component.provideAdapter(ConceptType)
|
||||
component.provideAdapter(ResourceType)
|
||||
component.provideAdapter(TypeConcept)
|
||||
|
||||
catalog = Catalog()
|
||||
component.provideUtility(catalog, ICatalog)
|
||||
|
||||
catalog['loops_title'] = TextIndex('title', IIndexAttributes, True)
|
||||
catalog['loops_text'] = TextIndex('text', IIndexAttributes, True)
|
||||
catalog['loops_type'] = FieldIndex('tokenForSearch', IType, False)
|
||||
|
||||
loopsRoot = site['loops'] = Loops()
|
||||
loopsRoot = site['loops']
|
||||
|
||||
component.provideAdapter(KnowledgeSetupManager, name='knowledge')
|
||||
setup = SetupManager(loopsRoot)
|
||||
concepts, resources, views = setup.setup()
|
||||
|
||||
component.provideAdapter(ConceptIndexAttributes)
|
||||
component.provideAdapter(ResourceIndexAttributes)
|
||||
|
||||
tType = concepts.getTypeConcept()
|
||||
tDomain = concepts['domain']
|
||||
tTextDocument = concepts['textdocument']
|
||||
|
@ -123,6 +92,7 @@ class TestSite(object):
|
|||
d003.assignConcept(stateNew)
|
||||
d003.assignConcept(dtStudy)
|
||||
|
||||
catalog = component.getUtility(ICatalog)
|
||||
for c in concepts.values():
|
||||
catalog.index_doc(int(util.getUidForObject(c)), c)
|
||||
for r in resources.values():
|
||||
|
@ -130,4 +100,3 @@ class TestSite(object):
|
|||
|
||||
return concepts, resources, views
|
||||
|
||||
|
||||
|
|
90
external/README.txt
vendored
Normal file
90
external/README.txt
vendored
Normal file
|
@ -0,0 +1,90 @@
|
|||
===============================================================
|
||||
loops - Linked Objects for Organization and Processing Services
|
||||
===============================================================
|
||||
|
||||
($Id$)
|
||||
|
||||
>>> from zope import component
|
||||
>>> from zope.traversing.api import getName
|
||||
|
||||
Let's set up a loops site with basic and example concepts and resources.
|
||||
|
||||
>>> from zope.app.testing.setup import placefulSetUp, placefulTearDown
|
||||
>>> site = placefulSetUp(True)
|
||||
|
||||
>>> from loops.tests.setup import TestSite
|
||||
>>> t = TestSite(site)
|
||||
>>> concepts, resources, views = t.setup()
|
||||
>>> loopsRoot = site['loops']
|
||||
>>> len(concepts), len(resources), len(views)
|
||||
(11, 3, 0)
|
||||
|
||||
|
||||
Importing loops Objects
|
||||
=======================
|
||||
|
||||
Reading object information from an external source
|
||||
--------------------------------------------------
|
||||
|
||||
>>> from loops.external.pyfunc import PyReader
|
||||
|
||||
>>> input = "concept('myquery', u'My Query', 'query', viewName='mystuff.html')"
|
||||
>>> reader = PyReader()
|
||||
>>> elements = reader.read(input)
|
||||
>>> elements
|
||||
[{'type': 'query', 'name': 'myquery', 'viewName': 'mystuff.html', 'title': u'My Query'}]
|
||||
|
||||
Creating the corresponding objects
|
||||
----------------------------------
|
||||
|
||||
>>> from loops.external.base import Loader
|
||||
|
||||
>>> loader = Loader(loopsRoot)
|
||||
>>> loader.load(elements)
|
||||
>>> len(concepts), len(resources), len(views)
|
||||
(12, 3, 0)
|
||||
|
||||
>>> from loops.common import adapted
|
||||
>>> adapted(concepts['myquery']).viewName
|
||||
'mystuff.html'
|
||||
|
||||
|
||||
Exporting loops Objects
|
||||
=======================
|
||||
|
||||
Extracting elements
|
||||
-------------------
|
||||
|
||||
>>> from loops.external.base import Extractor
|
||||
|
||||
>>> extractor = Extractor(loopsRoot)
|
||||
>>> elements = list(extractor.extract())
|
||||
>>> len(elements)
|
||||
13
|
||||
|
||||
Writing object information to the external storage
|
||||
--------------------------------------------------
|
||||
|
||||
>>> from loops.external.pyfunc import PyWriter
|
||||
>>> from cStringIO import StringIO
|
||||
|
||||
>>> output = StringIO()
|
||||
>>> writer = PyWriter()
|
||||
>>> writer.write(elements, output)
|
||||
>>> print output.getvalue()
|
||||
type(u'customer', u'Customer', viewName=u'')...
|
||||
type(u'query', u'Query', typeInterface='loops.query.IQueryConcept', viewName=u'')...
|
||||
concept(u'myquery', u'My Query', u'query', viewName='mystuff.html')...
|
||||
child(u'projects', u'customer', u'standard')...
|
||||
|
||||
|
||||
The Export/Import View
|
||||
======================
|
||||
|
||||
>>> from loops.external.browser import ExportImport
|
||||
>>> from zope.publisher.browser import TestRequest
|
||||
|
||||
>>> input = {'field.data': output}
|
||||
>>> view = ExportImport(loopsRoot, TestRequest(input))
|
||||
>>> view.upload()
|
||||
False
|
5
external/__init__.py
vendored
Normal file
5
external/__init__.py
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
"""
|
||||
$Id$
|
||||
"""
|
||||
|
||||
from loops.external.external import NodesLoader, NodesExporter, NodesImporter
|
143
external/base.py
vendored
Normal file
143
external/base.py
vendored
Normal file
|
@ -0,0 +1,143 @@
|
|||
#
|
||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
|
||||
"""
|
||||
Reading and writing loops objects (represented by IElement objects)
|
||||
in Python function notation.
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
||||
from cStringIO import StringIO
|
||||
import itertools
|
||||
from zope import component
|
||||
from zope.cachedescriptors.property import Lazy
|
||||
from zope.interface import implements
|
||||
from zope.traversing.api import getName
|
||||
|
||||
from cybertools.composer.interfaces import IInstance
|
||||
from cybertools.composer.schema.interfaces import ISchemaFactory
|
||||
from cybertools.typology.interfaces import IType
|
||||
from loops.common import adapted
|
||||
from loops.external.interfaces import ILoader, IExtractor
|
||||
from loops.external.element import elementTypes
|
||||
from loops.interfaces import IConceptSchema
|
||||
from loops.setup import SetupManager
|
||||
|
||||
|
||||
class Base(object):
|
||||
|
||||
def __init__(self, context):
|
||||
self.context = context
|
||||
|
||||
@Lazy
|
||||
def concepts(self):
|
||||
return self.context.getConceptManager()
|
||||
|
||||
@Lazy
|
||||
def typeConcept(self):
|
||||
return self.concepts.getTypeConcept()
|
||||
|
||||
@Lazy
|
||||
def typePredicate(self):
|
||||
return self.concepts.getTypePredicate()
|
||||
|
||||
|
||||
class Loader(Base, SetupManager):
|
||||
|
||||
implements(ILoader)
|
||||
|
||||
def __init__(self, context):
|
||||
self.context = context
|
||||
self.logger = StringIO()
|
||||
|
||||
def load(self, elements):
|
||||
for element in elements:
|
||||
element(self)
|
||||
|
||||
# TODO: care for setting attributes via Instance (Editor)
|
||||
# instead of using SetupManager methods:
|
||||
# def addConcept(self, ...):
|
||||
|
||||
class Extractor(Base):
|
||||
|
||||
implements(IExtractor)
|
||||
|
||||
def extract(self):
|
||||
return itertools.chain(self.extractTypes(),
|
||||
self.extractConcepts(),
|
||||
self.extractChildren(),
|
||||
#self.extractResources(),
|
||||
#self.extractResourceRelations(),
|
||||
#self.extractNodes(),
|
||||
#self.extractTargets(),
|
||||
)
|
||||
|
||||
def extractTypes(self):
|
||||
typeElement = elementTypes['type']
|
||||
for obj in self.typeConcept.getChildren([self.typePredicate]):
|
||||
data = self.getObjectData(obj)
|
||||
yield typeElement(getName(obj), obj.title, **data)
|
||||
|
||||
def extractConcepts(self):
|
||||
conceptElement = elementTypes['concept']
|
||||
typeConcept = self.typeConcept
|
||||
for name, obj in self.concepts.items():
|
||||
if obj.conceptType != typeConcept:
|
||||
data = self.getObjectData(obj)
|
||||
tp = getName(obj.conceptType)
|
||||
yield conceptElement(name, obj.title, tp, **data)
|
||||
|
||||
def extractResources(self):
|
||||
resourceElement = elementTypes['resource']
|
||||
for name, obj in self.resources.items():
|
||||
# TODO: handle ``data`` attribute...
|
||||
data = self.getObjectData(obj)
|
||||
tp = getName(obj.resourceType)
|
||||
yield resourceElement(name, obj.title, tp, **data)
|
||||
|
||||
def getObjectData(self, obj):
|
||||
aObj = adapted(obj)
|
||||
schemaFactory = component.getAdapter(aObj, ISchemaFactory)
|
||||
ti = IType(obj).typeInterface or IConceptSchema
|
||||
schema = schemaFactory(ti, manager=self) #, request=self.request)
|
||||
instance = IInstance(aObj)
|
||||
instance.template = schema
|
||||
# TODO: use ``_not_exportable`` attribute of adapter to control export
|
||||
#data = instance.applyTemplate(mode='export')
|
||||
data = instance.applyTemplate(mode='edit')
|
||||
if 'title' in data:
|
||||
del data['title']
|
||||
data['description'] = obj.description
|
||||
if not data['description']:
|
||||
del data['description']
|
||||
return data
|
||||
|
||||
def extractChildren(self):
|
||||
childElement = elementTypes['child']
|
||||
typePredicate = self.typePredicate
|
||||
for c in self.concepts.values():
|
||||
for r in c.getChildRelations():
|
||||
if r.predicate != typePredicate:
|
||||
args = [getName(r.first), getName(r.second), getName(r.predicate)]
|
||||
if r.order != 0:
|
||||
args.append(r.order)
|
||||
if r.relevance != 1.0:
|
||||
args.append(r.relevance)
|
||||
yield childElement(*args)
|
||||
|
83
external/browser.py
vendored
Normal file
83
external/browser.py
vendored
Normal file
|
@ -0,0 +1,83 @@
|
|||
#
|
||||
# Copyright (c) 2006 Helmut Merz helmutm@cy55.de
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
|
||||
"""
|
||||
view class(es) for import/export.
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
||||
from zope import component
|
||||
from zope.interface import Interface, implements
|
||||
from zope.app import zapi
|
||||
from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
|
||||
from zope.cachedescriptors.property import Lazy
|
||||
from zope.security.proxy import removeSecurityProxy
|
||||
from cStringIO import StringIO
|
||||
|
||||
from loops.external.base import Loader, Extractor
|
||||
from loops.external.interfaces import IReader, IWriter
|
||||
|
||||
|
||||
class ExportImport(object):
|
||||
""" View providing export and import functionality.
|
||||
"""
|
||||
|
||||
def __init__(self, context, request):
|
||||
self.context = removeSecurityProxy(context)
|
||||
self.request = request
|
||||
self.message = u''
|
||||
|
||||
def submit(self):
|
||||
action = self.request.get('loops.action', None)
|
||||
if action:
|
||||
method = getattr(self, action, None)
|
||||
if method:
|
||||
return method()
|
||||
return False
|
||||
|
||||
def export(self):
|
||||
f = StringIO()
|
||||
extractor = Extractor(self.context)
|
||||
elements = extractor.extract()
|
||||
writer = component.getUtility(IWriter)
|
||||
writer.write(elements, f)
|
||||
text = f.getvalue()
|
||||
f.close()
|
||||
self.setDownloadHeader(self.request, text)
|
||||
return text
|
||||
|
||||
def upload(self):
|
||||
data = self.request.get('field.data', None)
|
||||
if not data:
|
||||
return False
|
||||
reader = component.getUtility(IReader)
|
||||
elements = reader.read(data)
|
||||
loader = Loader(self.context)
|
||||
loader.load(elements)
|
||||
self.message = u'Content uploaded and imported.'
|
||||
return False
|
||||
|
||||
def setDownloadHeader(self, request, text):
|
||||
response = request.response
|
||||
response.setHeader('Content-Disposition',
|
||||
'attachment; filename=loopscontent.dmp')
|
||||
response.setHeader('Content-Type', 'text/plain')
|
||||
response.setHeader('Content-Length', len(text))
|
||||
|
||||
|
25
external/configure.zcml
vendored
Normal file
25
external/configure.zcml
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
<!-- $Id$ -->
|
||||
|
||||
<configure
|
||||
xmlns:zope="http://namespaces.zope.org/zope"
|
||||
xmlns:browser="http://namespaces.zope.org/browser"
|
||||
i18n_domain="zope">
|
||||
|
||||
<zope:utility factory="loops.external.pyfunc.PyReader" />
|
||||
|
||||
<zope:utility factory="loops.external.pyfunc.PyWriter" />
|
||||
|
||||
<browser:pages for="loops.interfaces.ILoops"
|
||||
class="loops.external.browser.ExportImport"
|
||||
permission="zope.ManageSite">
|
||||
|
||||
<browser:page name="exportimport.html"
|
||||
template="exportimport.pt"
|
||||
menu="zmi_views" title="Export/Import" />
|
||||
|
||||
<browser:page name="export_loops.html"
|
||||
attribute="export" />
|
||||
|
||||
</browser:pages>
|
||||
|
||||
</configure>
|
107
external/element.py
vendored
Normal file
107
external/element.py
vendored
Normal file
|
@ -0,0 +1,107 @@
|
|||
#
|
||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
|
||||
"""
|
||||
Basic implementation of the elements used for the intermediate format for export
|
||||
and import of loops objects.
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
||||
from zope.cachedescriptors.property import Lazy
|
||||
from zope.dottedname.resolve import resolve
|
||||
from zope.interface import implements
|
||||
from zope.traversing.api import getName
|
||||
|
||||
from loops.external.interfaces import IElement
|
||||
|
||||
|
||||
class Element(dict):
|
||||
|
||||
implements(IElement)
|
||||
|
||||
elementType = ''
|
||||
|
||||
def __init__(self, name, title, type=None, *args, **kw):
|
||||
self['name'] = name
|
||||
self['title'] = title
|
||||
if type:
|
||||
self['type'] = type
|
||||
for k, v in kw.items():
|
||||
self[k] = v
|
||||
|
||||
def __call__(self, loader):
|
||||
pass
|
||||
|
||||
|
||||
class ConceptElement(Element):
|
||||
|
||||
elementType = 'concept'
|
||||
posArgs = ('name', 'title', 'type')
|
||||
|
||||
def __call__(self, loader):
|
||||
type = loader.concepts[self['type']]
|
||||
kw = dict((k, v) for k, v in self.items()
|
||||
if k not in ('name', 'title', 'type'))
|
||||
loader.addConcept(self['name'], self['title'], type, **kw)
|
||||
|
||||
|
||||
class TypeElement(ConceptElement):
|
||||
|
||||
elementType = 'type'
|
||||
posArgs = ('name', 'title')
|
||||
|
||||
def __init__(self, name, title, *args, **kw):
|
||||
super(TypeElement, self).__init__(name, title, *args, **kw)
|
||||
ti = self['typeInterface']
|
||||
if ti:
|
||||
self['typeInterface'] = '.'.join((ti.__module__, ti.__name__))
|
||||
else:
|
||||
del self['typeInterface']
|
||||
|
||||
def __call__(self, loader):
|
||||
kw = dict((k, v) for k, v in self.items()
|
||||
if k not in ('name', 'title', 'type', 'typeInterface'))
|
||||
kw['typeInterface'] = resolve(self['typeInterface'])
|
||||
loader.addConcept(self['name'], self['title'], 'type', **kw)
|
||||
|
||||
|
||||
class ResourceElement(ConceptElement):
|
||||
|
||||
elementType = 'resource'
|
||||
|
||||
|
||||
class ChildElement(Element):
|
||||
|
||||
elementType = 'child'
|
||||
posArgs = ('first', 'second', 'predicate', 'order', 'relevance')
|
||||
|
||||
def __init__(self, *args):
|
||||
for idx, arg in enumerate(args):
|
||||
self[self.posArgs[idx]] = arg
|
||||
|
||||
def __call__(self, loader):
|
||||
loader.assignChild(self['first'], self['second'], self['predicate'])
|
||||
|
||||
|
||||
elementTypes = dict(
|
||||
type=TypeElement,
|
||||
concept=ConceptElement,
|
||||
resource=ResourceElement,
|
||||
child=ChildElement,
|
||||
)
|
60
external/exportimport.pt
vendored
Normal file
60
external/exportimport.pt
vendored
Normal file
|
@ -0,0 +1,60 @@
|
|||
<tal:show condition="not:view/submit">
|
||||
<html metal:use-macro="context/@@standard_macros/view">
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<metal:body fill-slot="body">
|
||||
|
||||
<h3>Export/Import loops Site</h3>
|
||||
|
||||
<div tal:define="message view/message | request/message | nothing"
|
||||
tal:condition="message"
|
||||
tal:content="message">Message</div>
|
||||
|
||||
<div> </div>
|
||||
<div>
|
||||
This form allows you to export the objects in a loops site to a
|
||||
file and upload a file created by a content export.
|
||||
</div>
|
||||
|
||||
<form action="." method="post"
|
||||
tal:attributes="action string:${request/URL/-1}/export_loops.html">
|
||||
<input type="hidden" name="loops.action" value="export" />
|
||||
<div> </div>
|
||||
<h4>Export Site</h4>
|
||||
<div> </div>
|
||||
<div class="row">
|
||||
<div class="controls">
|
||||
<input type="submit" name="loops.export" value="Export" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form action="." method="post" enctype="multipart/form-data"
|
||||
tal:attributes="action request/URL">
|
||||
<input type="hidden" name="loops.action" value="upload" />
|
||||
<div> </div>
|
||||
<h4>Import Site</h4>
|
||||
<div class="row">
|
||||
<div class="label">
|
||||
<label for="field.data"
|
||||
title="The file to be uploaded.">File</label>
|
||||
</div>
|
||||
<div class="field">
|
||||
<input class="fileType" id="field.data" name="field.data"
|
||||
size="20" type="file" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="controls">
|
||||
<input type="submit" name="loops.upload" value="Upload" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</metal:body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</tal:show>
|
10
external.py → external/external.py
vendored
10
external.py → external/external.py
vendored
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2005 Helmut Merz helmutm@cy55.de
|
||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -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
85
external/interfaces.py
vendored
Normal file
|
@ -0,0 +1,85 @@
|
|||
#
|
||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
|
||||
"""
|
||||
Interfaces for export and import of loops objects.
|
||||
|
||||
Maybe part of this stuff should be moved to cybertools.external.
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
||||
from zope.interface import Interface, Attribute
|
||||
from zope import interface, component, schema
|
||||
|
||||
|
||||
class IElement(Interface):
|
||||
""" A dicionary-like information element that is able to represent a
|
||||
loops object, a relation between loops objects or a special attribute.
|
||||
The attributes of the object are represented by items of
|
||||
the dictionary; the attribute values may be strings, unicode strings,
|
||||
or IElement objects.
|
||||
"""
|
||||
|
||||
def __call__(loader):
|
||||
""" Create the object that is specified by the element in the
|
||||
context of the loader and return it.
|
||||
"""
|
||||
|
||||
|
||||
class IReader(Interface):
|
||||
""" Provides objects in an intermediate format from an external source.
|
||||
Will typically be implemented by an utility or an adapter.
|
||||
"""
|
||||
|
||||
def read():
|
||||
""" Retrieve content from the external source returning a sequence
|
||||
of IElement objects.
|
||||
"""
|
||||
|
||||
|
||||
class ILoader(Interface):
|
||||
""" Inserts data provided by an IReader object into the
|
||||
loops database/the context object. Will typically be used as an adapter.
|
||||
"""
|
||||
|
||||
def load(elements):
|
||||
""" Create the objects and relations specified by the ``elements``
|
||||
argument given.
|
||||
"""
|
||||
|
||||
|
||||
class IWriter(Interface):
|
||||
""" Transforms object information to an external storage.
|
||||
"""
|
||||
|
||||
def write(elements):
|
||||
""" Write the sequence of elements given in an external format.
|
||||
"""
|
||||
|
||||
|
||||
class IExtractor(Interface):
|
||||
""" Extracts information from loops objects and provides them as
|
||||
IElement objects. Will typically be used as an adapter.
|
||||
"""
|
||||
|
||||
def extract():
|
||||
""" Creates and returns a sequence of IElement objects by scanning
|
||||
the content of the context object.
|
||||
"""
|
||||
|
73
external/pyfunc.py
vendored
Normal file
73
external/pyfunc.py
vendored
Normal file
|
@ -0,0 +1,73 @@
|
|||
#
|
||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
|
||||
"""
|
||||
Reading and writing loops objects (represented by IElement objects)
|
||||
in Python function notation.
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
||||
from zope.cachedescriptors.property import Lazy
|
||||
from zope.interface import implements
|
||||
|
||||
from loops.external.interfaces import IReader, IWriter
|
||||
from loops.external.element import elementTypes
|
||||
|
||||
|
||||
class PyReader(dict):
|
||||
|
||||
implements(IReader)
|
||||
|
||||
def __init__(self):
|
||||
self.elements = []
|
||||
|
||||
def read(self, input):
|
||||
if not isinstance(input, str):
|
||||
input = input.read()
|
||||
exec input in self
|
||||
return self.elements
|
||||
|
||||
def __getitem__(self, key):
|
||||
def factory(*args, **kw):
|
||||
element = elementTypes[key](*args, **kw)
|
||||
self.elements.append(element)
|
||||
return element
|
||||
return factory
|
||||
|
||||
|
||||
class PyWriter(object):
|
||||
|
||||
implements(IWriter)
|
||||
|
||||
def write(self, elements, output):
|
||||
for element in elements:
|
||||
args = []
|
||||
for arg in element.posArgs:
|
||||
if arg in element:
|
||||
args.append(repr(element[arg]))
|
||||
for k, v in element.items():
|
||||
if k not in element.posArgs:
|
||||
args.append("%s=%s" % (str(k), repr(v)))
|
||||
output.write('%s(%s)\n' % (element.elementType, ', '.join(args)))
|
||||
|
||||
|
||||
def toStr(value):
|
||||
if isinstance(value, unicode):
|
||||
return value.encode('UTF-8')
|
||||
return str(value)
|
23
external/tests.py
vendored
Executable file
23
external/tests.py
vendored
Executable file
|
@ -0,0 +1,23 @@
|
|||
# $Id$
|
||||
|
||||
import unittest, doctest
|
||||
from zope.testing.doctestunit import DocFileSuite
|
||||
from zope.interface.verify import verifyClass
|
||||
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
"Basic tests for the loops.external package."
|
||||
|
||||
def testSomething(self):
|
||||
pass
|
||||
|
||||
|
||||
def test_suite():
|
||||
flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
|
||||
return unittest.TestSuite((
|
||||
unittest.makeSuite(Test),
|
||||
DocFileSuite('README.txt', optionflags=flags),
|
||||
))
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(defaultTest='test_suite')
|
|
@ -65,7 +65,8 @@ class LanguageInfo(object):
|
|||
lang = self.request.get('loops.language')
|
||||
if lang is not None and lang in self.availableLanguages:
|
||||
return lang
|
||||
return (negotiator.getLanguage(self.availableLanguages, self.request)
|
||||
available = self.availableLanguages or ('en', 'de',)
|
||||
return (negotiator.getLanguage(available, self.request)
|
||||
or self.defaultLanguage)
|
||||
|
||||
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
<!-- $Id$ -->
|
||||
|
||||
<metal:actions define-macro="language_switch"
|
||||
tal:define="langInfo view/languageInfo">
|
||||
<tal:lang repeat="lang langInfo/availableLanguages">
|
||||
tal:define="langInfo view/languageInfo;
|
||||
available langInfo/availableLanguages"
|
||||
tal:condition="python: len(available) > 1">
|
||||
<tal:lang repeat="lang available">
|
||||
<a href="#"
|
||||
tal:attributes="href string:switch_language?loops.language=$lang&keep=yes;
|
||||
title lang"><img src="us.gif"
|
||||
|
|
|
@ -29,8 +29,9 @@ from zope.app.container.constraints import contains, containers
|
|||
from zope.app.container.interfaces import IContainer, IOrderedContainer
|
||||
from zope.app.file.interfaces import IImage as IBaseAsset
|
||||
from zope.app.folder.interfaces import IFolder
|
||||
from zope.component.interfaces import IObjectEvent
|
||||
from zope.size.interfaces import ISized
|
||||
from cybertools.relation.interfaces import IRelation
|
||||
from cybertools.relation.interfaces import IDyadicRelation
|
||||
|
||||
import util
|
||||
from util import _
|
||||
|
@ -563,16 +564,19 @@ class ILoopsContained(Interface):
|
|||
|
||||
# relation interfaces
|
||||
|
||||
class ITargetRelation(IRelation):
|
||||
class ITargetRelation(IDyadicRelation):
|
||||
""" (Marker) interfaces for relations pointing to a target
|
||||
of a view or node.
|
||||
"""
|
||||
|
||||
|
||||
class IConceptRelation(IRelation):
|
||||
class IConceptRelation(IDyadicRelation):
|
||||
""" (Marker) interfaces for relations originating from a concept.
|
||||
"""
|
||||
|
||||
predicate = Attribute("A concept of type 'predicate' that defines the "
|
||||
"type of the relation-")
|
||||
|
||||
|
||||
# interfaces for catalog indexes
|
||||
|
||||
|
@ -685,6 +689,22 @@ class INote(ITextDocument):
|
|||
required=False)
|
||||
|
||||
|
||||
# events
|
||||
|
||||
class IAssignmentEvent(IObjectEvent):
|
||||
""" A child or resource has been assigned to a concept.
|
||||
"""
|
||||
|
||||
relation = Attribute('The relation that has been assigned to the concept.')
|
||||
|
||||
|
||||
class IDeassignmentEvent(IObjectEvent):
|
||||
""" A child or resource will be deassigned from a concept.
|
||||
"""
|
||||
|
||||
relation = Attribute('The relation that will be removed from the concept.')
|
||||
|
||||
|
||||
# view configurator stuff
|
||||
|
||||
class IViewConfiguratorSchema(Interface):
|
||||
|
|
|
@ -26,7 +26,7 @@ $Id$
|
|||
from zope.cachedescriptors.property import Lazy
|
||||
from zope.app.pagetemplate import ViewPageTemplateFile
|
||||
|
||||
from loops.browser.action import Action, DialogAction
|
||||
from loops.browser.action import DialogAction
|
||||
from loops.browser.concept import ConceptView
|
||||
from loops.browser.form import CreateConceptForm, EditConceptForm
|
||||
from loops.browser.form import CreateConcept, EditConcept
|
||||
|
@ -107,6 +107,7 @@ class EditGlossaryItemForm(EditConceptForm, ConceptView):
|
|||
|
||||
class CreateGlossaryItemForm(CreateConceptForm, EditGlossaryItemForm):
|
||||
|
||||
title = _(u'Create Glossary Item')
|
||||
form_action = 'create_glossaryitem'
|
||||
|
||||
def children(self):
|
||||
|
|
|
@ -8,26 +8,26 @@
|
|||
<span tal:repeat="letter python: [chr(c) for c in range(ord('A'), ord('Z')+1)]"
|
||||
class="navlink">
|
||||
<a href="#"
|
||||
tal:omit-tag="python: letter not in data['letters']"
|
||||
tal:omit-tag="python: letter not in data.keys()"
|
||||
tal:attributes="href string:${request/URL/-1}#$letter"
|
||||
tal:content="letter">A</a>
|
||||
</span>
|
||||
</div>
|
||||
<div> </div>
|
||||
<div tal:repeat="letter data/letters">
|
||||
<div tal:repeat="letter data/keys">
|
||||
<div class="subtitle"><a name="A" href="#top"
|
||||
tal:attributes="name letter;
|
||||
href string:${request/URL/-1}#top"
|
||||
tal:content="letter">A</a>
|
||||
</div>
|
||||
<div tal:repeat="related data/relations/?letter|python:[]">
|
||||
<div tal:repeat="related data/?letter|python:[]">
|
||||
<a href="#"
|
||||
tal:content="related/title"
|
||||
tal:attributes="href python: view.getUrlForTarget(related);
|
||||
title related/description">
|
||||
Topic
|
||||
</a>
|
||||
<span tal:define="translations related/adapted/translations"
|
||||
<span tal:define="translations related/adapted/translations|python:[]"
|
||||
tal:condition="translations">
|
||||
(<tal:trans repeat="trans translations"><a href="#"
|
||||
tal:attributes="href python: '%s?loops.language=%s' %
|
||||
|
@ -44,7 +44,7 @@
|
|||
|
||||
<metal:block define-macro="glossaryitem">
|
||||
<metal:title use-macro="item/conceptMacros/concepttitle" />
|
||||
<p tal:define="translations item/adapted/translations"
|
||||
<p tal:define="translations item/adapted/translations|python:[]"
|
||||
tal:condition="translations">
|
||||
<span i18n:translate="">Translations</span>:
|
||||
<tal:trans repeat="trans translations"><a href="#"
|
||||
|
@ -98,10 +98,12 @@
|
|||
<input type="hidden"
|
||||
id="child.search.predicate"
|
||||
tal:attributes="value view/relatedPredicateUid" />
|
||||
<input dojoType="comboBox" mode="remote" autoComplete="False"
|
||||
name="child.search.text" id="child.search.text"
|
||||
tal:attributes="dataUrl
|
||||
string:listConceptsForComboBox.js?searchString=%{searchString}&searchType=loops:concept:glossaryitem" />
|
||||
<div dojoType="dojox.data.QueryReadStore" jsId="childSearch"
|
||||
url="listConceptsForComboBox.js?searchType=loops:concept:glossaryitem" >
|
||||
</div>
|
||||
<input dojoType="dijit.form.FilteringSelect" store="childSearch"
|
||||
autoComplete="False" labelAttr="label"
|
||||
name="child.search.text" id="child.search.text" />
|
||||
</td>
|
||||
<td>
|
||||
<input type="button" value="Select"
|
||||
|
|
Binary file not shown.
|
@ -3,7 +3,7 @@ msgstr ""
|
|||
|
||||
"Project-Id-Version: $Id$\n"
|
||||
"POT-Creation-Date: 2007-05-22 12:00 CET\n"
|
||||
"PO-Revision-Date: 2007-12-17 12:00 CET\n"
|
||||
"PO-Revision-Date: 2008-01-23 12:00 CET\n"
|
||||
"Last-Translator: Helmut Merz <helmutm@cy55.de>\n"
|
||||
"Language-Team: loops developers <helmutm@cy55.de>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
|
@ -17,12 +17,54 @@ msgstr "Begriff"
|
|||
msgid "Resource"
|
||||
msgstr "Ressource"
|
||||
|
||||
msgid "Log out"
|
||||
msgstr "Abmelden"
|
||||
|
||||
msgid "Create"
|
||||
msgstr "Anlegen"
|
||||
|
||||
msgid "Edit Concept Map"
|
||||
msgstr "Concept Map bearbeiten"
|
||||
|
||||
msgid "Create Resource..."
|
||||
msgstr "Ressource anlegen..."
|
||||
|
||||
msgid "Create a new resource object."
|
||||
msgstr "Eine neue Ressource erzeugen"
|
||||
|
||||
msgid "Edit Blog Post..."
|
||||
msgstr "Eintrag bearbeiten..."
|
||||
|
||||
msgid "Edit Blog Post"
|
||||
msgstr "Tagebucheintrag bearbeiten"
|
||||
|
||||
msgid "Modify blog post."
|
||||
msgstr "Tagebucheintrag ändern."
|
||||
|
||||
msgid "Create Blog Post..."
|
||||
msgstr "Tagebucheintrag anlegen..."
|
||||
|
||||
msgid "Create a new blog post."
|
||||
msgstr "Einen neuen Tagebucheintrag erzeugen"
|
||||
|
||||
msgid "Create Blog Post"
|
||||
msgstr "Tagebucheintrag anlegen"
|
||||
|
||||
msgid "Glossary Item"
|
||||
msgstr "Glossareintrag"
|
||||
|
||||
msgid "Edit Glossary Item..."
|
||||
msgstr "Glossareintrag bearbeiten..."
|
||||
|
||||
msgid "Edit Glossary Item"
|
||||
msgstr "Glossareintrag bearbeiten"
|
||||
|
||||
msgid "Create Glossary Item..."
|
||||
msgstr "Glossareintrag anlegen..."
|
||||
|
||||
msgid "Create Glossary Item"
|
||||
msgstr "Glossareintrag anlegen"
|
||||
|
||||
msgid "Actions"
|
||||
msgstr "Aktionen"
|
||||
|
||||
|
@ -47,9 +89,15 @@ msgstr "Unterbegriffe"
|
|||
msgid "Title"
|
||||
msgstr "Titel"
|
||||
|
||||
msgid "Title of the concept"
|
||||
msgstr "Überschrift, sprechende Bezeichnung des Begriffs"
|
||||
|
||||
msgid "Description"
|
||||
msgstr "Beschreibung"
|
||||
|
||||
msgid "A medium-length description describing the content and the purpose of the object"
|
||||
msgstr "Eine ausführlichere Beschreibung des Inhalts und Zwecks des Objekts"
|
||||
|
||||
msgid "Related Items"
|
||||
msgstr "Verwandte Begriffe"
|
||||
|
||||
|
@ -224,4 +272,30 @@ msgstr "Die Passwort-Wiederholung stimmt nicht mit dem eingegebenen Passwort üb
|
|||
msgid "Your password has been changed."
|
||||
msgstr "Ihr Passwort wurde geändert."
|
||||
|
||||
msgid "Date/Time"
|
||||
msgstr "Datum/Uhrzeit"
|
||||
|
||||
msgid "The date and time the information was posted."
|
||||
msgstr "Datum und Uhrzeit der Bereitstellung der Information"
|
||||
|
||||
msgid "Private"
|
||||
msgstr "privat"
|
||||
|
||||
msgid "Check this field if the blog post should be accessible only for a limited audience."
|
||||
msgstr "Markieren Sie dieses Feld, wenn der Tagebucheintrag nicht für alle Benutzer sichtbar sein soll"
|
||||
|
||||
msgid "The text of your blog entry"
|
||||
msgstr "Der eigentliche Text des Tagebucheintrags"
|
||||
|
||||
msgid "Private Comment"
|
||||
msgstr "Privater Kommentar"
|
||||
|
||||
msgid "A text that is not visible for other users."
|
||||
msgstr "Ein Text, der für andere Benutzer nicht sichtbar sein soll"
|
||||
|
||||
msgid "For quick creation of notes/links bookmark this link"
|
||||
msgstr "Für Notizen diesen Link zu Favoriten/Lesezeichen hinzufügen"
|
||||
|
||||
msgid "Create loops Note"
|
||||
msgstr "loops-Notiz anlegen"
|
||||
|
||||
|
|
|
@ -26,6 +26,9 @@ ZCML setup):
|
|||
>>> loopsRoot = site['loops']
|
||||
>>> loopsId = util.getUidForObject(loopsRoot)
|
||||
|
||||
>>> from loops.organize.tests import setupUtilitiesAndAdapters
|
||||
>>> setupData = setupUtilitiesAndAdapters(loopsRoot)
|
||||
|
||||
>>> type = concepts['type']
|
||||
>>> person = concepts['person']
|
||||
|
||||
|
@ -39,10 +42,7 @@ Organizations: Persons (and Users), Institutions, Addresses...
|
|||
|
||||
The classes used in this package are just adapters to IConcept.
|
||||
|
||||
>>> from loops.interfaces import IConcept
|
||||
>>> from loops.organize.interfaces import IPerson
|
||||
>>> from loops.organize.party import Person
|
||||
>>> component.provideAdapter(Person, (IConcept,), IPerson)
|
||||
|
||||
>>> john = IPerson(johnC)
|
||||
>>> john.title
|
||||
|
@ -74,15 +74,8 @@ the person(s) belonging to a user/principal.
|
|||
For testing, we first have to provide the needed utilities and settings
|
||||
(in real life this is all done during Zope startup):
|
||||
|
||||
>>> from zope.app.security.interfaces import IAuthentication
|
||||
>>> from zope.app.security.principalregistry import PrincipalRegistry
|
||||
>>> auth = PrincipalRegistry()
|
||||
>>> component.provideUtility(auth, IAuthentication)
|
||||
|
||||
>>> from zope.app.principalannotation.interfaces import IPrincipalAnnotationUtility
|
||||
>>> from zope.app.principalannotation import PrincipalAnnotationUtility
|
||||
>>> principalAnnotations = PrincipalAnnotationUtility()
|
||||
>>> component.provideUtility(principalAnnotations, IPrincipalAnnotationUtility)
|
||||
>>> auth = setupData.auth
|
||||
>>> principalAnnotations = setupData.principalAnnotations
|
||||
|
||||
>>> principal = auth.definePrincipal('users.john', u'John', login='john')
|
||||
>>> john.userId = 'users.john'
|
||||
|
@ -116,6 +109,7 @@ principal annotation:
|
|||
>>> from zope.app.container.contained import ObjectRemovedEvent
|
||||
>>> from zope.event import notify
|
||||
>>> from zope.interface import Interface
|
||||
>>> from loops.interfaces import IConcept
|
||||
>>> from loops.organize.party import removePersonReferenceFromPrincipal
|
||||
>>> from zope.app.testing import ztapi
|
||||
>>> ztapi.subscribe([IConcept, IObjectRemovedEvent], None,
|
||||
|
@ -157,6 +151,7 @@ with a principal folder:
|
|||
|
||||
>>> from zope.app.appsetup.bootstrap import ensureUtility
|
||||
>>> from zope.app.authentication.authentication import PluggableAuthentication
|
||||
>>> from zope.app.security.interfaces import IAuthentication
|
||||
>>> ensureUtility(site, IAuthentication, '', PluggableAuthentication,
|
||||
... copy_to_zlog=False, asObject=True)
|
||||
<...PluggableAuthentication...>
|
||||
|
@ -212,7 +207,6 @@ Now we can also retrieve it from the authentication utility:
|
|||
>>> pau.getPrincipal('loops.newuser').title
|
||||
u'Tom Sawyer'
|
||||
|
||||
|
||||
Change Password
|
||||
---------------
|
||||
|
||||
|
@ -229,18 +223,100 @@ We need a principal for testing the login stuff:
|
|||
>>> principal = InternalPrincipal('scott', 'tiger', 'Scotty')
|
||||
>>> request.setPrincipal(principal)
|
||||
|
||||
>>> from cybertools.composer.schema.factory import SchemaFactory
|
||||
>>> from cybertools.composer.schema.field import FieldInstance
|
||||
>>> component.provideAdapter(SchemaFactory)
|
||||
>>> component.provideAdapter(FieldInstance)
|
||||
|
||||
>>> from loops.organize.browser import PasswordChange
|
||||
>>> pwcView = PasswordChange(menu, request)
|
||||
>>> pwcView.update()
|
||||
False
|
||||
|
||||
|
||||
Security
|
||||
========
|
||||
|
||||
Automatic security settings on persons
|
||||
--------------------------------------
|
||||
|
||||
>>> from zope.traversing.api import getName
|
||||
>>> list(sorted(getName(c) for c in concepts['person'].getChildren()))
|
||||
[u'john', u'martha', u'person.newuser']
|
||||
|
||||
Person objects that have a user assigned to them receive this user
|
||||
(principal) as their owner.
|
||||
|
||||
>>> from zope.app.securitypolicy.interfaces import IPrincipalRoleMap
|
||||
>>> IPrincipalRoleMap(concepts['john']).getPrincipalsAndRoles()
|
||||
[('loops.Owner', 'users.john', PermissionSetting: Allow)]
|
||||
>>> IPrincipalRoleMap(concepts['person.newuser']).getPrincipalsAndRoles()
|
||||
[('loops.Owner', u'loops.newuser', PermissionSetting: Allow)]
|
||||
|
||||
The person ``martha`` hasn't got a user id, so there is no role assigned
|
||||
to it.
|
||||
|
||||
>>> IPrincipalRoleMap(concepts['martha']).getPrincipalsAndRoles()
|
||||
[]
|
||||
|
||||
Only the owner (and a few other privileged people) should be able
|
||||
to edit a person object.
|
||||
|
||||
We also need an interaction with a participation based on the principal
|
||||
whose permissions we want to check.
|
||||
|
||||
>>> from zope.app.authentication.principalfolder import Principal
|
||||
>>> pJohn = Principal('users.john', 'xxx', u'John')
|
||||
|
||||
>>> from loops.tests.auth import login
|
||||
>>> login(pJohn)
|
||||
|
||||
We also want to grant some global permissions and roles, i.e. on the site or
|
||||
loops root level.
|
||||
|
||||
>>> rolePermissions = setupData.rolePermissions
|
||||
>>> rolePermissions.grantPermissionToRole('zope.View', 'zope.Member')
|
||||
|
||||
>>> principalRoles = setupData.principalRoles
|
||||
>>> principalRoles.assignRoleToPrincipal('zope.Member', 'users.john')
|
||||
|
||||
Now we are ready to look for the real stuff - what John is allowed to do.
|
||||
|
||||
>>> from zope.security import canAccess, canWrite, checkPermission
|
||||
>>> john = concepts['john']
|
||||
|
||||
>>> canAccess(john, 'title')
|
||||
True
|
||||
|
||||
Person objects that have an owner may be modified by this owner.
|
||||
|
||||
>>> canWrite(john, 'title')
|
||||
True
|
||||
|
||||
So let's try with another user with another role setting.
|
||||
|
||||
>>> rolePermissions.grantPermissionToRole('zope.ManageContent', 'loops.Staff')
|
||||
>>> principalRoles.assignRoleToPrincipal('loops.Staff', 'users.martha')
|
||||
>>> principalRoles.assignRoleToPrincipal('zope.Member', 'users.martha')
|
||||
|
||||
>>> pMartha = Principal('users.martha', 'xxx', u'Martha')
|
||||
>>> login(pMartha)
|
||||
|
||||
>>> canAccess(john, 'title')
|
||||
True
|
||||
>>> canWrite(john, 'title')
|
||||
False
|
||||
|
||||
If we clear the userId attribute from a person object others may be allowed
|
||||
again to edit it...
|
||||
|
||||
>>> adapted(john).userId = ''
|
||||
>>> canWrite(john, 'title')
|
||||
True
|
||||
|
||||
... but John no more...
|
||||
|
||||
>>> login(pJohn)
|
||||
>>> canWrite(john, 'title')
|
||||
False
|
||||
|
||||
|
||||
Fin de partie
|
||||
=============
|
||||
|
||||
>>> placefulTearDown()
|
||||
|
||||
|
|
|
@ -93,6 +93,13 @@
|
|||
|
||||
<!-- other adapters -->
|
||||
|
||||
<zope:adapter
|
||||
for="loops.interfaces.ILoopsObject
|
||||
zope.publisher.interfaces.browser.IBrowserRequest"
|
||||
factory="loops.organize.memberinfo.MemberInfoProvider"
|
||||
permission="zope.Public"
|
||||
/>
|
||||
|
||||
<zope:adapter factory="loops.organize.setup.SetupManager"
|
||||
name="organize" />
|
||||
|
||||
|
|
|
@ -30,7 +30,8 @@ from zope.app.security.interfaces import IAuthentication, PrincipalLookupError
|
|||
from zope.security.proxy import removeSecurityProxy
|
||||
|
||||
from cybertools.organize.interfaces import IPerson as IBasePerson
|
||||
from loops.organize.util import getPrincipalFolder, authPluginId
|
||||
from loops.interfaces import IConceptSchema
|
||||
from loops.organize.util import getPrincipalFolder
|
||||
from loops.util import _
|
||||
|
||||
ANNOTATION_KEY = 'loops.organize.person'
|
||||
|
@ -83,7 +84,7 @@ class LoginName(schema.TextLine):
|
|||
mapping=dict(userId=userId)))
|
||||
|
||||
|
||||
class IPerson(IBasePerson):
|
||||
class IPerson(IConceptSchema, IBasePerson):
|
||||
""" Resembles a human being with a name (first and last name),
|
||||
a birth date, and a set of addresses. This interface only
|
||||
lists fields used in addition to those provided by the
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2006 Helmut Merz helmutm@cy55.de
|
||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -36,10 +36,13 @@ from zope.i18nmessageid import MessageFactory
|
|||
from zope.cachedescriptors.property import Lazy
|
||||
|
||||
from cybertools.typology.interfaces import IType
|
||||
from loops.interfaces import ILoops
|
||||
from loops.common import adapted
|
||||
from loops.concept import Concept
|
||||
from loops.interfaces import ILoops
|
||||
from loops.organize.interfaces import IMemberRegistrationManager
|
||||
from loops.organize.util import getPrincipalFolder, authPluginId, getInternalPrincipal
|
||||
from loops.organize.util import getPrincipalFolder, getGroupsFolder
|
||||
from loops.organize.util import getInternalPrincipal
|
||||
from loops.type import getOptionsDict
|
||||
from loops.util import _
|
||||
|
||||
|
||||
|
@ -51,29 +54,54 @@ class MemberRegistrationManager(object):
|
|||
def __init__(self, context):
|
||||
self.context = context
|
||||
|
||||
def register(self, userId, password, lastName, firstName=u'', **kw):
|
||||
def register(self, userId, password, lastName, firstName=u'',
|
||||
groups=[], useExisting=False, **kw):
|
||||
# step 1: create an internal principal in the loops principal folder:
|
||||
pFolder = getPrincipalFolder(self.context)
|
||||
title = firstName and ' '.join((firstName, lastName)) or lastName
|
||||
principal = InternalPrincipal(userId, password, title)
|
||||
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
57
organize/memberinfo.py
Normal file
|
@ -0,0 +1,57 @@
|
|||
#
|
||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
|
||||
"""
|
||||
Provide member properties based on person data.
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
||||
from zope import component
|
||||
from zope.app.security.interfaces import IAuthentication
|
||||
from zope.cachedescriptors.property import Lazy
|
||||
|
||||
from cybertools.browser.member import MemberInfoProvider as BaseMemberInfoProvider
|
||||
from cybertools.browser.member import MemberProperty
|
||||
from cybertools.util.jeep import Jeep
|
||||
from loops.common import adapted
|
||||
from loops.organize.party import getPersonForUser
|
||||
from loops import util
|
||||
|
||||
|
||||
class MemberInfoProvider(BaseMemberInfoProvider):
|
||||
|
||||
def getData(self, principalId=None):
|
||||
if principalId is None:
|
||||
principal = self.request.principal
|
||||
else:
|
||||
#auth = component.getUtility(IAuthentication, self.context)
|
||||
auth = component.getUtility(IAuthentication)
|
||||
principal = auth.getPrincipal(principalId)
|
||||
person = getPersonForUser(self.context, self.request, principal)
|
||||
if person is None:
|
||||
return super(MemberInfoProvider, self).getData(principalId)
|
||||
aPerson = adapted(person)
|
||||
return Jeep((MemberProperty('id', principal.id, u'ID'),
|
||||
MemberProperty('title', aPerson.title, u'Title'),
|
||||
MemberProperty('description', aPerson.description,
|
||||
u'Description'),
|
||||
MemberProperty('object', person, u'Object'),
|
||||
MemberProperty('adapted', aPerson, u'Adapted object'),
|
||||
))
|
||||
|
|
@ -38,10 +38,11 @@ from cybertools.organize.interfaces import IAddress
|
|||
from cybertools.organize.party import Person as BasePerson
|
||||
from cybertools.relation.interfaces import IRelationRegistry
|
||||
from cybertools.typology.interfaces import IType
|
||||
from loops.common import AdapterBase
|
||||
from loops.concept import Concept
|
||||
from loops.interfaces import IConcept
|
||||
from loops.organize.interfaces import IPerson, ANNOTATION_KEY
|
||||
from loops.common import AdapterBase
|
||||
from loops.security.common import assignOwner, removeOwner, allowEditingForOwner
|
||||
from loops.type import TypeInterfaceSourceList
|
||||
from loops import util
|
||||
|
||||
|
@ -54,6 +55,8 @@ TypeInterfaceSourceList.typeInterfaces += (IPerson, IAddress)
|
|||
def getPersonForUser(context, request=None, principal=None):
|
||||
if principal is None:
|
||||
principal = request.principal
|
||||
if principal is None:
|
||||
return None
|
||||
loops = context.getLoopsRoot()
|
||||
pa = annotations(principal).get(ANNOTATION_KEY, None)
|
||||
if pa is None:
|
||||
|
@ -88,13 +91,16 @@ class Person(AdapterBase, BasePerson):
|
|||
pa = annotations(principal)
|
||||
loopsId = util.getUidForObject(self.context.getLoopsRoot())
|
||||
ann = pa.get(ANNOTATION_KEY)
|
||||
if ann is None:
|
||||
if ann is None: # or not isinstance(ann, PersistentMapping):
|
||||
ann = pa[ANNOTATION_KEY] = PersistentMapping()
|
||||
ann[loopsId] = self.context
|
||||
assignOwner(self.context, userId)
|
||||
oldUserId = self.userId
|
||||
if oldUserId and oldUserId != userId:
|
||||
self.removeReferenceFromPrincipal(oldUserId)
|
||||
removeOwner(self.context, oldUserId)
|
||||
self.context._userId = userId
|
||||
allowEditingForOwner(self.context, revert=not userId)
|
||||
userId = property(getUserId, setUserId)
|
||||
|
||||
def removeReferenceFromPrincipal(self, userId):
|
||||
|
|
|
@ -4,8 +4,35 @@ import unittest, doctest
|
|||
from zope.testing.doctestunit import DocFileSuite
|
||||
from zope.app.testing import ztapi
|
||||
from zope.interface.verify import verifyClass
|
||||
|
||||
from zope import component
|
||||
from zope.app.principalannotation.interfaces import IPrincipalAnnotationUtility
|
||||
from zope.app.principalannotation import PrincipalAnnotationUtility
|
||||
from zope.app.principalannotation import annotations
|
||||
from zope.app.security.interfaces import IAuthentication
|
||||
from zope.app.security.principalregistry import PrincipalRegistry
|
||||
from zope.app.securitypolicy.interfaces import IRolePermissionManager
|
||||
from zope.app.securitypolicy.interfaces import IPrincipalRoleManager
|
||||
|
||||
from cybertools.util.jeep import Jeep
|
||||
from loops.organize.interfaces import IPerson
|
||||
from loops.organize.party import Person
|
||||
|
||||
|
||||
def setupUtilitiesAndAdapters(loopsRoot):
|
||||
auth = PrincipalRegistry()
|
||||
component.provideUtility(auth, IAuthentication)
|
||||
principalAnnotations = PrincipalAnnotationUtility()
|
||||
component.provideUtility(principalAnnotations, IPrincipalAnnotationUtility)
|
||||
component.provideAdapter(Person, provides=IPerson)
|
||||
return Jeep((
|
||||
('auth', auth),
|
||||
('principalAnnotations', principalAnnotations),
|
||||
('rolePermissions', IRolePermissionManager(loopsRoot)),
|
||||
('principalRoles', IPrincipalRoleManager(loopsRoot)),
|
||||
))
|
||||
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
"Basic tests for the organize sub-package."
|
||||
|
||||
|
|
|
@ -27,22 +27,39 @@ from zope.app.authentication.interfaces import IPluggableAuthentication
|
|||
from zope.app.authentication.interfaces import IAuthenticatorPlugin
|
||||
from zope.app.security.interfaces import IAuthentication
|
||||
|
||||
authPluginId = 'loops'
|
||||
from loops.common import adapted
|
||||
from loops.type import getOptionsDict
|
||||
|
||||
defaultAuthPluginId = 'loops'
|
||||
|
||||
|
||||
def getPrincipalFolder(context=None):
|
||||
def getPrincipalFolder(context=None, authPluginId=None, ignoreErrors=False):
|
||||
pau = component.getUtility(IAuthentication, context=context)
|
||||
if not IPluggableAuthentication.providedBy(pau):
|
||||
if ignoreErrors:
|
||||
return None
|
||||
raise ValueError(u'There is no pluggable authentication '
|
||||
'utility available.')
|
||||
if not authPluginId in pau.authenticatorPlugins:
|
||||
raise ValueError(u'There is no loops authenticator '
|
||||
'plugin available.')
|
||||
if authPluginId is None and context is not None:
|
||||
person = context.getLoopsRoot().getConceptManager()['person']
|
||||
od = getOptionsDict(adapted(person).options)
|
||||
authPluginId = od.get('principalfolder', defaultAuthPluginId)
|
||||
if authPluginId is None:
|
||||
authPluginId = defaultAuthPluginId
|
||||
if authPluginId not in pau.authenticatorPlugins:
|
||||
if ignoreErrors:
|
||||
return None
|
||||
raise ValueError(u"There is no loops authenticator "
|
||||
"plugin '%s' available." % authPluginId)
|
||||
for name, plugin in pau.getAuthenticatorPlugins():
|
||||
if name == authPluginId:
|
||||
return plugin
|
||||
|
||||
|
||||
def getGroupsFolder(context=None, name='gloops'):
|
||||
return getPrincipalFolder(authPluginId=name, ignoreErrors=True)
|
||||
|
||||
|
||||
def getInternalPrincipal(id, context=None):
|
||||
pau = component.getUtility(IAuthentication, context=context)
|
||||
if not IPluggableAuthentication.providedBy(pau):
|
||||
|
|
8
query.py
8
query.py
|
@ -24,13 +24,13 @@ $Id$
|
|||
|
||||
from zope import schema, component
|
||||
from zope.interface import Interface, Attribute, implements
|
||||
from zope import traversing
|
||||
from zope.app.catalog.interfaces import ICatalog
|
||||
from zope.cachedescriptors.property import Lazy
|
||||
|
||||
from cybertools.typology.interfaces import IType
|
||||
from loops.interfaces import IConcept, IConceptSchema
|
||||
from loops.common import AdapterBase
|
||||
from loops.interfaces import IConcept, IConceptSchema
|
||||
from loops.security.common import canListObject
|
||||
from loops.type import TypeInterfaceSourceList
|
||||
from loops.versioning.util import getVersion
|
||||
from loops import util
|
||||
|
@ -72,7 +72,8 @@ class BaseQuery(object):
|
|||
result = cat.searchResults(loops_type=(start, end), loops_title=title)
|
||||
else:
|
||||
result = cat.searchResults(loops_type=(start, end))
|
||||
result = set(r for r in result if r.getLoopsRoot() == self.loopsRoot)
|
||||
result = set(r for r in result if r.getLoopsRoot() == self.loopsRoot
|
||||
and canListObject(r))
|
||||
if 'exclude' in kw:
|
||||
r1 = set()
|
||||
for r in result:
|
||||
|
@ -139,6 +140,7 @@ class FullQuery(BaseQuery):
|
|||
result = rc
|
||||
result = set(r for r in result
|
||||
if r.getLoopsRoot() == self.loopsRoot
|
||||
and canListObject(r)
|
||||
and getVersion(r) == r)
|
||||
return result
|
||||
|
||||
|
|
27
resource.py
27
resource.py
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2005 Helmut Merz helmutm@cy55.de
|
||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -22,6 +22,8 @@ Definition of the Concept class.
|
|||
$Id$
|
||||
"""
|
||||
|
||||
from cStringIO import StringIO
|
||||
from persistent import Persistent
|
||||
from zope import component, schema
|
||||
from zope.app import zapi
|
||||
from zope.app.container.btree import BTreeContainer
|
||||
|
@ -38,9 +40,6 @@ from zope.interface import implements
|
|||
from zope.size.interfaces import ISized
|
||||
from zope.security.proxy import removeSecurityProxy
|
||||
from zope.traversing.api import getName, getParent
|
||||
from persistent import Persistent
|
||||
from cStringIO import StringIO
|
||||
|
||||
from zope.lifecycleevent import ObjectModifiedEvent, Attributes
|
||||
from zope.event import notify
|
||||
|
||||
|
@ -50,7 +49,6 @@ from cybertools.storage.interfaces import IExternalStorage
|
|||
from cybertools.text.interfaces import ITextTransform
|
||||
from cybertools.typology.interfaces import IType, ITypeManager
|
||||
from cybertools.util.jeep import Jeep
|
||||
|
||||
from loops.base import ParentInfo
|
||||
from loops.common import ResourceAdapterBase, adapted
|
||||
from loops.concept import ResourceRelation
|
||||
|
@ -62,6 +60,7 @@ from loops.interfaces import IResourceManager, IResourceManagerContained
|
|||
from loops.interfaces import ITypeConcept
|
||||
from loops.interfaces import ILoopsContained
|
||||
from loops.interfaces import IIndexAttributes
|
||||
from loops.security.common import canListObject
|
||||
from loops import util
|
||||
from loops.versioning.util import getMaster
|
||||
from loops.view import TargetRelation
|
||||
|
@ -189,20 +188,24 @@ class Resource(Image, Contained):
|
|||
relationships = [TargetRelation]
|
||||
obj = getMaster(self) # use the master version for relations
|
||||
rels = getRelations(second=obj, relationships=relationships)
|
||||
return [r.first for r in rels]
|
||||
return [r.first for r in rels if canListObject(r.first)]
|
||||
|
||||
def getConceptRelations (self, predicates=None, concept=None, sort='default'):
|
||||
def getConceptRelations (self, predicates=None, concept=None, sort='default',
|
||||
noSecurityCheck=False):
|
||||
predicates = predicates is None and ['*'] or predicates
|
||||
obj = getMaster(self)
|
||||
relationships = [ResourceRelation(None, obj, p) for p in predicates]
|
||||
if sort == 'default':
|
||||
sort = lambda x: (x.order, x.first.title.lower())
|
||||
return sorted(getRelations(first=concept, second=obj, relationships=relationships),
|
||||
key=sort)
|
||||
rels = (r for r in getRelations(first=concept, second=obj,
|
||||
relationships=relationships)
|
||||
if canListObject(r.first, noSecurityCheck))
|
||||
return sorted(rels, key=sort)
|
||||
|
||||
def getConcepts(self, predicates=None):
|
||||
def getConcepts(self, predicates=None, noSecurityCheck=False):
|
||||
obj = getMaster(self)
|
||||
return [r.first for r in obj.getConceptRelations(predicates)]
|
||||
return [r.first for r in obj.getConceptRelations(predicates,
|
||||
noSecurityCheck=noSecurityCheck)]
|
||||
|
||||
def assignConcept(self, concept, predicate=None, order=0, relevance=1.0):
|
||||
obj = getMaster(self)
|
||||
|
@ -372,6 +375,8 @@ class ExternalFileAdapter(FileAdapter):
|
|||
self.storageName = storageName
|
||||
|
||||
def getData(self):
|
||||
if self.storageName == 'unknown': # object not set up yet
|
||||
return ''
|
||||
storage = component.getUtility(IExternalStorage, name=self.storageName)
|
||||
return storage.getData(self.externalAddress, params=self.storageParams)
|
||||
|
||||
|
|
|
@ -35,6 +35,8 @@ class ResourceSchemaFactory(SchemaFactory):
|
|||
def __call__(self, interface, **kw):
|
||||
schema = super(ResourceSchemaFactory, self).__call__(interface, **kw)
|
||||
schema.fields.data.height = 10
|
||||
if self.context.contentType == 'text/html':
|
||||
schema.fields.data.fieldType = 'html'
|
||||
return schema
|
||||
|
||||
|
||||
|
|
|
@ -90,8 +90,8 @@ a controller attribute for the search view.
|
|||
>>> searchView.controller = Controller(searchView, request)
|
||||
|
||||
>>> searchView.submitReplacing('1.results', '1.search.form', pageView)
|
||||
'return submitReplacing("1.results", "1.search.form",
|
||||
"http://127.0.0.1/loops/views/page/.target29/@@searchresults.html")'
|
||||
'submitReplacing("1.results", "1.search.form",
|
||||
"http://127.0.0.1/loops/views/page/.target29/@@searchresults.html");...'
|
||||
|
||||
Basic (text/title) search
|
||||
-------------------------
|
||||
|
@ -164,7 +164,9 @@ Now we can fill our search form and execute the query; note that all concepts
|
|||
found are listed, plus all their children and all resources associated
|
||||
with them:
|
||||
|
||||
>>> form = {'search.3.type': 'loops:concept:topic', 'search.3.text': u'zope'}
|
||||
>>> from loops import util
|
||||
>>> uid = util.getUidForObject(concepts['zope'])
|
||||
>>> form = {'search.3.type': 'loops:concept:topic', 'search.3.text': uid}
|
||||
>>> request = TestRequest(form=form)
|
||||
>>> resultsView = SearchResults(page, request)
|
||||
>>> results = list(resultsView.results)
|
||||
|
@ -173,7 +175,8 @@ with them:
|
|||
>>> results[0].context.__name__
|
||||
u'plone'
|
||||
|
||||
>>> form = {'search.3.type': 'loops:concept:topic', 'search.3.text': u'zope3'}
|
||||
>>> uid = util.getUidForObject(concepts['zope3'])
|
||||
>>> form = {'search.3.type': 'loops:concept:topic', 'search.3.text': uid}
|
||||
>>> request = TestRequest(form=form)
|
||||
>>> resultsView = SearchResults(page, request)
|
||||
>>> results = list(resultsView.results)
|
||||
|
@ -186,11 +189,11 @@ To support easy entry of concepts to search for we can preselect the available
|
|||
concepts (optionally restricted to a certain type) by entering text parts
|
||||
of the concepts' titles:
|
||||
|
||||
>>> form = {'searchType': 'loops:concept:topic', 'searchString': u'zope'}
|
||||
>>> form = {'searchType': 'loops:concept:topic', 'name': u'zope'}
|
||||
>>> request = TestRequest(form=form)
|
||||
>>> view = Search(page, request)
|
||||
>>> view.listConcepts()
|
||||
"[['Zope (Topic)', '33']]"
|
||||
u"{identifier: 'id', items: [{label: 'Zope (Topic)', name: 'Zope', id: '33'}]}"
|
||||
|
||||
Preset Concept Types on Search Forms
|
||||
------------------------------------
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2004 Helmut Merz helmutm@cy55.de
|
||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -83,30 +83,56 @@ class Search(BaseView):
|
|||
def initDojo(self):
|
||||
self.registerDojo()
|
||||
cm = self.controller.macros
|
||||
jsCall = 'dojo.require("dojo.widget.ComboBox")'
|
||||
jsCall = ('dojo.require("dojo.parser");'
|
||||
'dojo.require("dijit.form.FilteringSelect");'
|
||||
'dojo.require("dojox.data.QueryReadStore");')
|
||||
cm.register('js-execute', jsCall, jsCall=jsCall)
|
||||
|
||||
def listConcepts(self):
|
||||
""" Used for dojo.widget.ComboBox.
|
||||
""" Used for dijit.FilteringSelect.
|
||||
"""
|
||||
request = self.request
|
||||
request.response.setHeader('Content-Type', 'text/plain; charset=UTF-8')
|
||||
title = request.get('searchString', '').replace('(', ' ').replace(')', ' ')
|
||||
type = request.get('searchType') or 'loops:concept:*'
|
||||
result = ConceptQuery(self).query(title=title, type=type, exclude=('system',))
|
||||
#registry = component.getUtility(IRelationRegistry)
|
||||
# simple way to provide JSON format:
|
||||
return str(sorted([[`adapted(o, self.languageInfo).title`[2:-1]
|
||||
+ ' (%s)' % `o.conceptType.title`[2:-1],
|
||||
`int(util.getUidForObject(o))`]
|
||||
for o in result
|
||||
if o.getLoopsRoot() == self.loopsRoot])).replace('\\\\x', '\\x')
|
||||
#return str(sorted([[`o.title`[2:-1], `traversing.api.getName(o)`[2:-1]]
|
||||
# for o in result])).replace('\\\\x', '\\x')
|
||||
title = request.get('name')
|
||||
if title == '*':
|
||||
title = None
|
||||
type = request.get('searchType')
|
||||
data = []
|
||||
if title or type:
|
||||
if title is not None:
|
||||
title = title.replace('(', ' ').replace(')', ' ').replace(' -', ' ')
|
||||
#title = title.split(' ', 1)[0]
|
||||
if not type:
|
||||
type = 'loops:concept:*'
|
||||
result = ConceptQuery(self).query(title=title or None, type=type,
|
||||
exclude=('system',))
|
||||
for o in result:
|
||||
if o.getLoopsRoot() == self.loopsRoot:
|
||||
name = adapted(o, self.languageInfo).title
|
||||
if title and title.endswith('*'):
|
||||
title = title[:-1]
|
||||
sort = ((title and name.startswith(title) and '0' or '1')
|
||||
+ name.lower())
|
||||
if o.conceptType is None:
|
||||
raise ValueError('Concept Type missing for %r.' % name)
|
||||
data.append({'label': '%s (%s)' % (name, o.conceptType.title),
|
||||
'name': name,
|
||||
'id': util.getUidForObject(o),
|
||||
'sort': sort})
|
||||
data.sort(key=lambda x: x['sort'])
|
||||
if not title:
|
||||
data.insert(0, {'label': '', 'name': '', 'id': ''})
|
||||
json = []
|
||||
for item in data:
|
||||
json.append("{label: '%s', name: '%s', id: '%s'}" %
|
||||
(item['label'], item['name'], item['id']))
|
||||
json = "{identifier: 'id', items: [%s]}" % ', '.join(json)
|
||||
#print '***', json
|
||||
return json
|
||||
|
||||
def submitReplacing(self, targetId, formId, view):
|
||||
self.registerDojo()
|
||||
return 'return submitReplacing("%s", "%s", "%s")' % (
|
||||
return 'submitReplacing("%s", "%s", "%s"); return false;' % (
|
||||
targetId, formId,
|
||||
'%s/.target%s/@@searchresults.html' % (view.url, self.uniqueId))
|
||||
|
||||
|
@ -131,13 +157,14 @@ class SearchResults(BaseView):
|
|||
useTitle = form.get('search.2.title')
|
||||
useFull = form.get('search.2.full')
|
||||
conceptType = form.get('search.3.type', 'loops:concept:*')
|
||||
conceptTitle = form.get('search.3.text')
|
||||
if conceptTitle is not None:
|
||||
conceptTitle = util.toUnicode(conceptTitle, encoding='ISO8859-15')
|
||||
conceptUid = form.get('search.3.text_selected')
|
||||
#conceptTitle = form.get('search.3.text')
|
||||
#if conceptTitle is not None:
|
||||
# conceptTitle = util.toUnicode(conceptTitle, encoding='ISO8859-15')
|
||||
conceptUid = form.get('search.3.text')
|
||||
result = FullQuery(self).query(text=text, type=type,
|
||||
useTitle=useTitle, useFull=useFull,
|
||||
conceptTitle=conceptTitle, conceptUid=conceptUid,
|
||||
#conceptTitle=conceptTitle,
|
||||
conceptUid=conceptUid,
|
||||
conceptType=conceptType)
|
||||
rowNum = 4
|
||||
while rowNum < 10:
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
idPrefix string:${view/itemNum}.search;
|
||||
formId string:$idPrefix.form;
|
||||
resultsId string:$idPrefix.results">
|
||||
<h3 tal:attributes="class string:content-$level;
|
||||
<h1 tal:attributes="class string:content-$level;
|
||||
ondblclick item/openEditWindow"
|
||||
tal:content="item/title">
|
||||
Search
|
||||
</h3>
|
||||
</h1>
|
||||
|
||||
<div metal:define-macro="search_form" class="searchForm">
|
||||
<fieldset class="box">
|
||||
|
@ -46,7 +46,7 @@
|
|||
i18n:domain="loops">
|
||||
<fieldset class="box"
|
||||
tal:condition="request/search.submitted | nothing">
|
||||
<legend i18n:translate="">Search results</legend>
|
||||
<h2 i18n:translate="">Search results</h2>
|
||||
<table class="listing" summary="Search results"
|
||||
i18n:attributes="summary">
|
||||
<thead>
|
||||
|
@ -113,7 +113,7 @@
|
|||
<tr>
|
||||
<td metal:use-macro="macros/minus"/>
|
||||
<td colspan="3">
|
||||
<h3 i18n:translate="">Type(s) to search for</h3>
|
||||
<h2 i18n:translate="">Type(s) to search for</h2>
|
||||
</td>
|
||||
<tr>
|
||||
<td></td>
|
||||
|
@ -144,7 +144,7 @@
|
|||
<tr>
|
||||
<td metal:use-macro="macros/minus"/>
|
||||
<td colspan="3">
|
||||
<h3 i18n:translate="">Text-based search</h3>
|
||||
<h2 i18n:translate="">Text-based search</h2>
|
||||
</td>
|
||||
<tr>
|
||||
<td></td>
|
||||
|
@ -185,7 +185,7 @@
|
|||
<tr>
|
||||
<td metal:use-macro="macros/minus"/>
|
||||
<td colspan="3">
|
||||
<h3 i18n:translate="">Search via related concepts</h3>
|
||||
<h2 i18n:translate="">Search via related concepts</h2>
|
||||
</td>
|
||||
<tr tal:repeat="type item/presetSearchTypes">
|
||||
<tal:preset define="rowNum item/rowNum;
|
||||
|
@ -249,10 +249,14 @@
|
|||
</td>
|
||||
<td>
|
||||
<tal:combo tal:define="dummy item/initDojo">
|
||||
<input dojoType="comboBox" mode="remote" autoComplete="False"
|
||||
<div dojoType="dojox.data.QueryReadStore" jsId="conceptSearch"
|
||||
url="listConceptsForComboBox.js?searchType=" >
|
||||
</div>
|
||||
<input dojoType="dijit.form.FilteringSelect" store="conceptSearch"
|
||||
autoComplete="False" labelAttr="label"
|
||||
name="concept.search.text" id="concept.search.text"
|
||||
tal:attributes="name string:$namePrefix.text;
|
||||
id string:$idPrefix.text;
|
||||
dataUrl string:${context/@@absolute_url}/listConceptsForComboBox.js?searchString=%{searchString}&searchType=" />
|
||||
id string:$idPrefix.text" />
|
||||
</tal:combo>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
49
security.zcml
Normal file
49
security.zcml
Normal file
|
@ -0,0 +1,49 @@
|
|||
<!-- $Id$ -->
|
||||
|
||||
<configure
|
||||
xmlns="http://namespaces.zope.org/zope"
|
||||
xmlns:i18n="http://namespaces.zope.org/i18n"
|
||||
i18n_domain="loops">
|
||||
|
||||
<!-- permissions -->
|
||||
|
||||
<permission
|
||||
id="loops.xmlrpc.ManageConcepts"
|
||||
title="[loops-xmlrpc-manage-concepts-permission] loops: Manage Concepts (XML-RPC)" />
|
||||
|
||||
<permission
|
||||
id="loops.ManageSite"
|
||||
title="[loops-manage-site-permission] loops: Manage Site" />
|
||||
|
||||
<permission
|
||||
id="loops.ViewRestricted"
|
||||
title="[loops-view-restricted-permission] loops: View Restricted Information" />
|
||||
|
||||
<!-- roles and default permissions for roles -->
|
||||
|
||||
<role id="loops.SiteManager"
|
||||
title="[loops-manage-site-role] loops: Site Manager" />
|
||||
<grant role="loops.SiteManager" permission="loops.ManageSite" />
|
||||
<grant role="loops.SiteManager" permission="loops.xmlrpc.ManageConcepts" />
|
||||
<grant role="loops.SiteManager" permission="zope.ManageContent" />
|
||||
<grant role="loops.SiteManager" permission="zope.View" />
|
||||
|
||||
<role id="loops.Staff"
|
||||
title="[loops-staff-role] loops: Staff" />
|
||||
<grant role="loops.Staff" permission="zope.ManageContent" />
|
||||
<grant role="loops.Staff" permission="zope.View" />
|
||||
|
||||
<role id="loops.Master"
|
||||
title="[loops-master-role] loops: Master" />
|
||||
|
||||
<role id="loops.xmlrpc.ConceptManager"
|
||||
title="[xmlrpc-manage-concepts-role] loops: Concept Manager (XML-RPC)" />
|
||||
<grant role="loops.xmlrpc.ConceptManager" permission="loops.xmlrpc.ManageConcepts" />
|
||||
|
||||
<role id="loops.Owner"
|
||||
title="[loops-owner-role] Owner" />
|
||||
<grant role="loops.Owner" permission="zope.ManageContent" />
|
||||
<grant role="loops.Owner" permission="loops.ViewRestricted" />
|
||||
<grant role="loops.Owner" permission="zope.View" />
|
||||
|
||||
</configure>
|
3
security/__init__.py
Normal file
3
security/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
"""
|
||||
$Id$
|
||||
"""
|
133
security/common.py
Normal file
133
security/common.py
Normal file
|
@ -0,0 +1,133 @@
|
|||
#
|
||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
|
||||
"""
|
||||
Common functions and other stuff for working with permissions and roles.
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
||||
from zope import component
|
||||
from zope.app.securitypolicy.interfaces import IPrincipalRoleManager
|
||||
from zope.app.securitypolicy.interfaces import IRolePermissionManager
|
||||
from zope.cachedescriptors.property import Lazy
|
||||
from zope.interface import implements
|
||||
from zope.lifecycleevent import IObjectCreatedEvent
|
||||
from zope.security import canAccess, canWrite
|
||||
from zope.security import checkPermission as baseCheckPermission
|
||||
from zope.security.management import getInteraction
|
||||
|
||||
from loops.common import adapted
|
||||
from loops.interfaces import ILoopsObject, IConcept
|
||||
from loops.interfaces import IAssignmentEvent, IDeassignmentEvent
|
||||
from loops.security.interfaces import ISecuritySetter
|
||||
|
||||
|
||||
allRolesExceptOwner = (
|
||||
#'zope.SiteManager' - no, not this one...
|
||||
'zope.Anonymous', 'zope.Member', 'zope.ContentManager', 'loops.Staff',
|
||||
'loops.xmlrpc.ConceptManager', # relevant for local security?
|
||||
#'loops.SiteManager',
|
||||
'loops.Master',)
|
||||
allRolesExceptOwnerAndMaster = tuple(allRolesExceptOwner[:-1])
|
||||
minorPrivilegedRoles = ('zope.Anonymous', 'zope.Member',)
|
||||
|
||||
|
||||
# checking and querying functions
|
||||
|
||||
def canAccessObject(obj):
|
||||
return canAccess(obj, 'title')
|
||||
|
||||
def canListObject(obj, noCheck=False):
|
||||
if noCheck:
|
||||
return True
|
||||
return canAccess(obj, 'title')
|
||||
|
||||
def canWriteObject(obj):
|
||||
return canWrite(obj, 'title')
|
||||
|
||||
def checkPermission(permission, obj):
|
||||
return baseCheckPermission(permission, obj)
|
||||
|
||||
|
||||
def getCurrentPrincipal():
|
||||
interaction = getInteraction()
|
||||
if interaction is not None:
|
||||
parts = interaction.participations
|
||||
if parts:
|
||||
return parts[0].principal
|
||||
return None
|
||||
|
||||
|
||||
# functions for setting security properties
|
||||
|
||||
def assignOwner(obj, principalId):
|
||||
prm = IPrincipalRoleManager(obj)
|
||||
prm.assignRoleToPrincipal('loops.Owner', principalId)
|
||||
|
||||
def removeOwner(obj, principalId):
|
||||
prm = IPrincipalRoleManager(obj)
|
||||
prm.removeRoleFromPrincipal('loops.Owner', principalId)
|
||||
|
||||
|
||||
def allowEditingForOwner(obj, deny=allRolesExceptOwner, revert=False):
|
||||
rpm = IRolePermissionManager(obj)
|
||||
if revert:
|
||||
for role in deny:
|
||||
rpm.unsetPermissionFromRole('zope.ManageContent', role)
|
||||
rpm.unsetPermissionFromRole('zope.ManageContent', 'loops.Owner')
|
||||
else:
|
||||
for role in deny:
|
||||
rpm.denyPermissionToRole('zope.ManageContent', role)
|
||||
rpm.grantPermissionToRole('zope.ManageContent', 'loops.Owner')
|
||||
|
||||
|
||||
def restrictView(obj, roles=allRolesExceptOwnerAndMaster, revert=False):
|
||||
rpm = IRolePermissionManager(obj)
|
||||
if revert:
|
||||
for role in roles:
|
||||
rpm.unsetPermissionFromRole('zope.View', role)
|
||||
else:
|
||||
for role in roles:
|
||||
rpm.denyPermissionToRole('zope.View', role)
|
||||
|
||||
|
||||
# event handlers
|
||||
|
||||
@component.adapter(ILoopsObject, IObjectCreatedEvent)
|
||||
def setDefaultSecurity(obj, event):
|
||||
aObj = adapted(obj)
|
||||
setter = ISecuritySetter(aObj)
|
||||
setter.setDefaultRolePermissions()
|
||||
setter.setDefaultPrincipalRoles()
|
||||
|
||||
|
||||
@component.adapter(IConcept, IAssignmentEvent)
|
||||
def grantAcquiredSecurity(obj, event):
|
||||
aObj = adapted(obj)
|
||||
setter = ISecuritySetter(aObj)
|
||||
setter.setAcquiredRolePermissions(event.relation)
|
||||
setter.setAcquiredPrincipalRoles(event.relation)
|
||||
|
||||
|
||||
@component.adapter(IConcept, IDeassignmentEvent)
|
||||
def revokeAcquiredSecurity(obj, event):
|
||||
aObj = adapted(obj)
|
||||
setter = ISecuritySetter(aObj)
|
||||
setter.setAcquiredRolePermissions(event.relation, revert=True)
|
||||
setter.setAcquiredPrincipalRoles(event.relation, revert=True)
|
37
security/configure.zcml
Normal file
37
security/configure.zcml
Normal file
|
@ -0,0 +1,37 @@
|
|||
<!-- $Id$ -->
|
||||
|
||||
<configure
|
||||
xmlns:zope="http://namespaces.zope.org/zope"
|
||||
xmlns:browser="http://namespaces.zope.org/browser"
|
||||
i18n_domain="zope">
|
||||
|
||||
<zope:adapter factory="loops.security.setter.BaseSecuritySetter" />
|
||||
|
||||
<zope:adapter
|
||||
for="loops.interfaces.IConcept"
|
||||
factory="zope.app.securitypolicy.rolepermission.AnnotationRolePermissionManager"
|
||||
trusted="true" />
|
||||
<zope:adapter
|
||||
for="loops.interfaces.IResource"
|
||||
factory="zope.app.securitypolicy.rolepermission.AnnotationRolePermissionManager"
|
||||
trusted="true" />
|
||||
<zope:adapter
|
||||
for="loops.interfaces.IView"
|
||||
factory="zope.app.securitypolicy.rolepermission.AnnotationRolePermissionManager"
|
||||
trusted="true" />
|
||||
|
||||
<zope:subscriber handler="loops.security.common.setDefaultSecurity" />
|
||||
<zope:subscriber handler="loops.security.common.grantAcquiredSecurity" />
|
||||
<zope:subscriber handler="loops.security.common.revokeAcquiredSecurity" />
|
||||
|
||||
<browser:page name="permissions.html" for="*"
|
||||
class=".perm.PermissionView"
|
||||
template="manage_permissionform.pt"
|
||||
permission="zope.Security" />
|
||||
|
||||
<browser:menuItem for="*" action="@@permissions.html"
|
||||
menu="zmi_actions" title="Edit Permissions"
|
||||
filter="python: context.__name__ not in ('views', 'concepts', 'resources')"
|
||||
permission="zope.Security" />
|
||||
|
||||
</configure>
|
54
security/interfaces.py
Normal file
54
security/interfaces.py
Normal file
|
@ -0,0 +1,54 @@
|
|||
#
|
||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
|
||||
"""
|
||||
Interfaces for loops security management.
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
||||
from zope.interface import Interface, Attribute
|
||||
from zope import interface, component, schema
|
||||
|
||||
from loops.util import _
|
||||
|
||||
|
||||
class ISecuritySetter(Interface):
|
||||
|
||||
def setDefaultRolePermissions():
|
||||
""" Set some default role permission assignments (grants) on the
|
||||
context object.
|
||||
"""
|
||||
|
||||
def setDefaultPrincipalRoles():
|
||||
""" Assign default roles (e.g. owner) to certain principals
|
||||
(e.g. the user that created the object).
|
||||
"""
|
||||
|
||||
def setAcquiredRolePermissions(relation, revert=False):
|
||||
""" Grant role permissions on children/resources for the relation given.
|
||||
|
||||
If the ``revert`` argument is true unset the corresponding settings.
|
||||
"""
|
||||
|
||||
def setAcquiredPrincipalRoles(relation, revert=False):
|
||||
""" Assign roles on children/resources for the relation given.
|
||||
|
||||
If the ``revert`` argument is true unset the corresponding settings.
|
||||
"""
|
||||
|
115
security/manage_permissionform.pt
Normal file
115
security/manage_permissionform.pt
Normal file
|
@ -0,0 +1,115 @@
|
|||
<html metal:use-macro="context/@@standard_macros/view"
|
||||
i18n:domain="zope">
|
||||
<head>
|
||||
<style metal:fill-slot="headers" type="text/css">
|
||||
<!--
|
||||
.row-normal {
|
||||
background-color: #ffffff;
|
||||
border: none;
|
||||
}
|
||||
.row-hilite {
|
||||
background-color: #efefef;
|
||||
border: none;
|
||||
}
|
||||
-->
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div metal:fill-slot="body">
|
||||
<p tal:define="status view/update"
|
||||
tal:condition="status"
|
||||
tal:content="status" />
|
||||
|
||||
<div tal:define="permId view/permissionId;
|
||||
perm view/permission;">
|
||||
<form>
|
||||
<select name="permission_to_manage">
|
||||
<option tal:repeat="pId view/getPermissions"
|
||||
tal:attributes="value pId;
|
||||
selected python: pId == permId"
|
||||
tal:content="pId" />
|
||||
</select>
|
||||
<input type="submit" name="select_permission"
|
||||
i18n:attributes="value"
|
||||
value="Select Permission" />
|
||||
</form>
|
||||
<p class="form-text" i18n:translate="">
|
||||
Roles assigned to the permission
|
||||
<strong tal:content="perm/title"
|
||||
i18n:name="perm_title" i18n:translate="">Change DTML Methods</strong>
|
||||
(id: <strong tal:content="permId"
|
||||
i18n:name="perm_id">Zope.Some.Permission</strong>)
|
||||
</p>
|
||||
|
||||
<form method="post">
|
||||
<input type="hidden" name="permission_to_manage" value="Permission Name"
|
||||
tal:attributes="value permId" />
|
||||
<input type="hidden" name="permission_id" value="Permission Name"
|
||||
tal:attributes="value permId" />
|
||||
<div class="form-element">
|
||||
|
||||
<table width="100%" cellspacing="0" cellpadding="2" border="0"
|
||||
nowrap="nowrap">
|
||||
|
||||
<tr class="list-header">
|
||||
<td><strong i18n:translate="">Role</strong></td>
|
||||
<td><strong i18n:translate="">Users/Groups</strong></td>
|
||||
<td><strong i18n:translate="">Acquired Setting</strong></td>
|
||||
<td><strong i18n:translate="">Setting</strong></td>
|
||||
</tr>
|
||||
|
||||
<tr class="row-normal"
|
||||
tal:repeat="setting perm/roleSettings"
|
||||
tal:attributes="class python:
|
||||
path('repeat/setting/even') and 'row-normal' or 'row-hilite'">
|
||||
<tal:role define="ir repeat/setting/index;
|
||||
roleId python:path('view/roles')[ir].id">
|
||||
<td align="left" valign="top"
|
||||
tal:content="roleId">
|
||||
Manager
|
||||
</td>
|
||||
<td>
|
||||
<span tal:define="users python: view.listUsersForRole(roleId)"
|
||||
tal:replace="structure users">
|
||||
User xy
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span tal:replace="python:
|
||||
view.getAcquiredPermissionSetting(roleId, permId)" />
|
||||
</td>
|
||||
<td>
|
||||
<select name="settings:list">
|
||||
<option value="Unset"
|
||||
tal:repeat="option view/availableSettings"
|
||||
tal:attributes="value option/id;
|
||||
selected python:setting == option['id']"
|
||||
tal:content="option/shorttitle"
|
||||
i18n:translate="">+</option>
|
||||
</select>
|
||||
</td>
|
||||
</tal:role>
|
||||
</tr>
|
||||
<tr tal:define="principals view/getPrincipalPermissions"
|
||||
tal:condition="principals">
|
||||
<td>
|
||||
<strong i18n:translate="">Direct Settings</strong>
|
||||
</td>
|
||||
<td colspan="3" tal:content="structure principals">+xyz</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
<br />
|
||||
<div class="form-element">
|
||||
<input class="form-element" type="submit" name="SUBMIT_PERMS"
|
||||
value="Save Changes" i18n:attributes="value save-changes-button"/>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
139
security/perm.py
Normal file
139
security/perm.py
Normal file
|
@ -0,0 +1,139 @@
|
|||
#
|
||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
|
||||
"""
|
||||
Authentication view.
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
||||
from zope import component
|
||||
from zope.interface import implements
|
||||
from zope.cachedescriptors.property import Lazy
|
||||
from zope.app.security.interfaces import IPermission
|
||||
from zope.app.securitypolicy.browser.rolepermissionview import RolePermissionView
|
||||
from zope.app.securitypolicy.interfaces import IPrincipalRoleManager, IRolePermissionMap
|
||||
from zope.app.securitypolicy.interfaces import IPrincipalPermissionManager, \
|
||||
IPrincipalPermissionMap
|
||||
from zope.app.securitypolicy.zopepolicy import SettingAsBoolean
|
||||
from zope.publisher.browser import BrowserView
|
||||
from zope.security.proxy import removeSecurityProxy
|
||||
from zope.traversing.api import getParents
|
||||
|
||||
|
||||
class PermissionView(object):
|
||||
""" View for permission editing.
|
||||
"""
|
||||
|
||||
def __init__(self, context, request):
|
||||
self.context = context
|
||||
# make sure the real view (delegate) updates our context when
|
||||
# talking about the context's parent:
|
||||
self.__parent__ = context
|
||||
self.request = request
|
||||
self.delegate = RolePermissionView()
|
||||
self.delegate.context = self
|
||||
self.delegate.request = request
|
||||
self.permissionId = request.get('permission_to_manage') or 'zope.View'
|
||||
|
||||
def pagetip(self):
|
||||
return self.delegate.pagetip()
|
||||
|
||||
def roles(self):
|
||||
return self.delegate.roles()
|
||||
|
||||
def permissions(self):
|
||||
return self.delegate.permissions()
|
||||
|
||||
def availableSettings(self, noacquire=False):
|
||||
return self.delegate.availableSettings(noacquire)
|
||||
|
||||
def permissionRoles(self):
|
||||
return self.delegate.permissionRoles()
|
||||
|
||||
def permissionForID(self, pid):
|
||||
return self.delegate.permissionForID(pid)
|
||||
|
||||
@Lazy
|
||||
def permission(self):
|
||||
return self.permissionForID(self.permissionId)
|
||||
|
||||
def roleForID(self, rid):
|
||||
return self.delegate.roleForID(rid)
|
||||
|
||||
def update(self, testing=None):
|
||||
return self.delegate.update(testing)
|
||||
|
||||
def getAcquiredPermissionSetting(self, role, perm):
|
||||
for obj in getParents(self.context):
|
||||
rpm = IRolePermissionMap(obj, None)
|
||||
if rpm is not None:
|
||||
setting = rpm.getSetting(perm, role)
|
||||
setting = SettingAsBoolean[setting]
|
||||
if setting is not None:
|
||||
return setting and '+' or '-'
|
||||
return ''
|
||||
|
||||
def listUsersForRole(self, rid):
|
||||
result = ''
|
||||
direct = IPrincipalRoleManager(self.context).getPrincipalsForRole(rid)
|
||||
if direct:
|
||||
result = '<strong>' + self.renderEntry(direct) + '</strong>'
|
||||
acquired = []
|
||||
for obj in getParents(self.context):
|
||||
prm = IPrincipalRoleManager(obj, None)
|
||||
if prm is not None:
|
||||
entry = prm.getPrincipalsForRole(rid)
|
||||
if entry:
|
||||
acquired.append(self.renderEntry(entry))
|
||||
if acquired:
|
||||
if result:
|
||||
result += '<br />'
|
||||
result += '<br />'.join(acquired)
|
||||
return result
|
||||
|
||||
def renderEntry(self, entry):
|
||||
result = []
|
||||
for e in entry:
|
||||
value = SettingAsBoolean[e[1]]
|
||||
value = (value is False and '-') or (value and '+') or ''
|
||||
result.append(value + e[0])
|
||||
return ', '.join(result)
|
||||
|
||||
def getPrincipalPermissions(self):
|
||||
result = ''
|
||||
ppm = IPrincipalPermissionMap(self.context)
|
||||
direct = ppm.getPrincipalsForPermission(self.permissionId)
|
||||
if direct:
|
||||
result = '<strong>' + self.renderEntry(direct) + '</strong>'
|
||||
acquired = []
|
||||
for obj in getParents(self.context):
|
||||
ppm = IPrincipalPermissionMap(obj, None)
|
||||
if ppm is not None:
|
||||
entry = ppm.getPrincipalsForPermission(self.permissionId)
|
||||
if entry:
|
||||
acquired.append(self.renderEntry(entry))
|
||||
if acquired:
|
||||
if result:
|
||||
result += '<br />'
|
||||
result += '<br />'.join(acquired)
|
||||
return result
|
||||
|
||||
def getPermissions(self):
|
||||
return sorted(name for name, perm in component.getUtilitiesFor(IPermission))
|
||||
|
96
security/policy.py
Normal file
96
security/policy.py
Normal file
|
@ -0,0 +1,96 @@
|
|||
#
|
||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
|
||||
"""
|
||||
A loops-specific security policy. Intended mainly to deal with checking
|
||||
concept map parents in addition to containers for collecting principal roles.
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
||||
from zope.app.securitypolicy.interfaces import IPrincipalRoleMap
|
||||
from zope.app.securitypolicy.zopepolicy import ZopeSecurityPolicy
|
||||
from zope.app.securitypolicy.zopepolicy import SettingAsBoolean, globalRolesForPrincipal
|
||||
from zope import component
|
||||
from zope.component import adapts
|
||||
from zope.cachedescriptors.property import Lazy
|
||||
from zope.interface import classProvides
|
||||
from zope.security.interfaces import ISecurityPolicy
|
||||
from zope.security.proxy import removeSecurityProxy
|
||||
|
||||
from loops.interfaces import IConcept, IResource
|
||||
|
||||
|
||||
class LoopsSecurityPolicy(ZopeSecurityPolicy):
|
||||
|
||||
classProvides(ISecurityPolicy)
|
||||
|
||||
def cached_principal_roles(self, obj, principal, checked=None):
|
||||
if checked is None:
|
||||
checked = []
|
||||
obj = removeSecurityProxy(obj)
|
||||
cache = self.cache(obj)
|
||||
try:
|
||||
cache_principal_roles = cache.principal_roles
|
||||
except AttributeError:
|
||||
cache_principal_roles = cache.principal_roles = {}
|
||||
try:
|
||||
return cache_principal_roles[principal]
|
||||
except KeyError:
|
||||
pass
|
||||
if obj is None:
|
||||
roles = dict([(role, SettingAsBoolean[setting])
|
||||
for (role, setting)
|
||||
in globalRolesForPrincipal(principal)])
|
||||
roles['zope.Anonymous'] = True # Everybody has Anonymous
|
||||
cache_principal_roles[principal] = roles
|
||||
return roles
|
||||
roles = {}
|
||||
for p in self.getParents(obj, checked):
|
||||
# TODO: care for correct combination if there is more than
|
||||
# one parent
|
||||
roles.update(self.cached_principal_roles(p, principal))
|
||||
prinrole = IPrincipalRoleMap(obj, None)
|
||||
if prinrole:
|
||||
roles = roles.copy()
|
||||
for role, setting in prinrole.getRolesForPrincipal(principal):
|
||||
roles[role] = SettingAsBoolean[setting]
|
||||
cache_principal_roles[principal] = roles
|
||||
#print '***', roles
|
||||
return roles
|
||||
|
||||
def getParents(self, obj, checked):
|
||||
if obj in checked: # cycle - leave the concept map
|
||||
return [getattr(obj, '__parent__', None)]
|
||||
# keep concept parents in cache
|
||||
cache = self.cache(obj)
|
||||
try:
|
||||
parents = cache.parents
|
||||
except AttributeError:
|
||||
parents = []
|
||||
if IConcept.providedBy(obj):
|
||||
parents = [p for p in obj.getParents(noSecurityCheck=True)
|
||||
if p != obj]
|
||||
elif IResource.providedBy(obj):
|
||||
parents = [p for p in obj.getConcepts(noSecurityCheck=True)
|
||||
if p != obj]
|
||||
cache.parents = parents
|
||||
if not parents:
|
||||
parents = [getattr(obj, '__parent__', None)]
|
||||
checked.append(obj)
|
||||
return parents
|
52
security/setter.py
Normal file
52
security/setter.py
Normal file
|
@ -0,0 +1,52 @@
|
|||
#
|
||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
|
||||
"""
|
||||
Base classes for security setters, i.e. adapters that provide standardized
|
||||
methods for setting role permissions and other security-related stuff.
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
||||
from zope import component
|
||||
from zope.component import adapts
|
||||
from zope.cachedescriptors.property import Lazy
|
||||
from zope.interface import implements, Interface
|
||||
|
||||
from loops.security.interfaces import ISecuritySetter
|
||||
|
||||
|
||||
class BaseSecuritySetter(object):
|
||||
|
||||
implements(ISecuritySetter)
|
||||
adapts(Interface)
|
||||
|
||||
def __init__(self, context):
|
||||
self.context = context
|
||||
|
||||
def setDefaultRolePermissions(self):
|
||||
pass
|
||||
|
||||
def setDefaultPrincipalRoles(self):
|
||||
pass
|
||||
|
||||
def setAcquiredRolePermissions(self, relation, revert=False):
|
||||
pass
|
||||
|
||||
def setAcquiredPrincipalRoles(self, relation, revert=False):
|
||||
pass
|
104
setup.py
104
setup.py
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2006 Helmut Merz helmutm@cy55.de
|
||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -25,13 +25,16 @@ $Id$
|
|||
from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent
|
||||
from zope.event import notify
|
||||
from zope import component
|
||||
from zope.cachedescriptors.property import Lazy
|
||||
from zope.component import adapts
|
||||
from zope.interface import implements, Interface
|
||||
from zope.traversing.api import getName
|
||||
|
||||
from cybertools.typology.interfaces import IType
|
||||
from loops.common import adapted
|
||||
from loops.concept import ConceptManager, Concept
|
||||
from loops.interfaces import ILoops, ITypeConcept
|
||||
from loops.interfaces import IFile, IImage, ITextDocument, INote
|
||||
from loops.concept import ConceptManager, Concept
|
||||
from loops.query import IQueryConcept
|
||||
from loops.resource import ResourceManager, Resource
|
||||
from loops.view import ViewManager, Node
|
||||
|
@ -79,7 +82,6 @@ class SetupManager(object):
|
|||
domain = self.addObject(conceptManager, Concept, 'domain', title=u'Domain')
|
||||
query = self.addObject(conceptManager, Concept, 'query', title=u'Query')
|
||||
file = self.addObject(conceptManager, Concept, 'file', title=u'File')
|
||||
#image = self.addObject(conceptManager, Concept, 'image', title=u'Image')
|
||||
textdocument = self.addObject(conceptManager, Concept,
|
||||
'textdocument', title=u'Text')
|
||||
note = self.addObject(conceptManager, Concept, 'note', title=u'Note')
|
||||
|
@ -89,13 +91,105 @@ class SetupManager(object):
|
|||
ITypeConcept(typeConcept).typeInterface = ITypeConcept
|
||||
ITypeConcept(query).typeInterface = IQueryConcept
|
||||
ITypeConcept(file).typeInterface = IFile
|
||||
#ITypeConcept(image).typeInterface = IImage
|
||||
ITypeConcept(textdocument).typeInterface = ITextDocument
|
||||
ITypeConcept(note).typeInterface = INote
|
||||
ITypeConcept(note).viewName = 'note.html' # leads to error in DocTest
|
||||
ITypeConcept(note).viewName = 'note.html'
|
||||
hasType.conceptType = predicate
|
||||
standard.conceptType = predicate
|
||||
|
||||
# standard properties and methods
|
||||
|
||||
@Lazy
|
||||
def concepts(self):
|
||||
return self.context.getConceptManager()
|
||||
|
||||
@Lazy
|
||||
def resources(self):
|
||||
return self.context.getResourceManager()
|
||||
|
||||
@Lazy
|
||||
def views(self):
|
||||
return self.context.getViewManager()
|
||||
|
||||
@Lazy
|
||||
def typeConcept(self):
|
||||
return self.concepts.getTypeConcept()
|
||||
|
||||
@Lazy
|
||||
def predicateType(self):
|
||||
return self.concepts.getPredicateType()
|
||||
|
||||
def addType(self, name, title, typeInterface=None, **kw):
|
||||
c = self.addConcept(name, title, self.typeConcept,
|
||||
typeInterface=typeInterface, **kw)
|
||||
return c
|
||||
|
||||
def addPredicate(self, name, title, **kw):
|
||||
c = self.addConcept(name, title, self.predicateType, **kw)
|
||||
return c
|
||||
|
||||
def addConcept(self, name, title, conceptType, description=u'',
|
||||
parentName=None, **kw):
|
||||
if name in self.concepts:
|
||||
self.log("Concept '%s' ('%s') already exists." % (name, title))
|
||||
c = self.concepts[name]
|
||||
if c.conceptType != conceptType:
|
||||
self.log("Wrong concept type for '%s': '%s' instead of '%s'." %
|
||||
(name, getName(c.conceptType), getName(conceptType)))
|
||||
else:
|
||||
c = addAndConfigureObject(self.concepts, Concept, name, title=title,
|
||||
description=description,
|
||||
conceptType=conceptType, **kw)
|
||||
self.log("Concept '%s' ('%s') created." % (name, title))
|
||||
if parentName is not None:
|
||||
self.assignChild(parentName, name)
|
||||
return c
|
||||
|
||||
def setConceptAttribute(self, concept, attr, value):
|
||||
setattr(adapted(concept), attr, value)
|
||||
self.log("Setting Attribute '%s' of '%s' to '%s'" %
|
||||
(attr, getName(concept), repr(value)))
|
||||
|
||||
def assignChild(self, conceptName, childName, predicate=None):
|
||||
if predicate is None:
|
||||
predicate = self.concepts.getDefaultPredicate()
|
||||
if isinstance(predicate, str):
|
||||
predicate = self.concepts[predicate]
|
||||
concept = self.concepts[conceptName]
|
||||
child = self.concepts[childName]
|
||||
if child in concept.getChildren([predicate]):
|
||||
self.log("Concept '%s' is already a child of '%s with predicate '%s'.'" %
|
||||
(childName, conceptName, getName(predicate)))
|
||||
else:
|
||||
concept.assignChild(child, predicate)
|
||||
|
||||
def addNode(self, name, title, container=None, nodeType='page',
|
||||
description=u'', body=u'', targetName=None, **kw):
|
||||
if container is None:
|
||||
container = self.views
|
||||
nodeType = 'menu'
|
||||
if name in container:
|
||||
self.log("Node '%s' ('%s') already exists in '%s'." %
|
||||
(name, title, getName(container)))
|
||||
n = container[name]
|
||||
if n.nodeType != nodeType:
|
||||
self.log("Wrong node type for '%s': '%s' instead of '%s'." %
|
||||
(name, n.nodeType, nodeType))
|
||||
else:
|
||||
n = addAndConfigureObject(container, Node, name, title=title,
|
||||
description=description, body=body,
|
||||
nodeType=nodeType, **kw)
|
||||
self.log("Node '%s' ('%s') created." % (name, title))
|
||||
if targetName is not None:
|
||||
if targetName in self.concepts:
|
||||
n.target = self.concepts[targetName]
|
||||
return n
|
||||
|
||||
def log(self, message):
|
||||
if isinstance(message, unicode):
|
||||
message = message.encode('UTF-8')
|
||||
print >> self.logger, message
|
||||
|
||||
def addObject(self, container, class_, name, **kw):
|
||||
return addObject(container, class_, name, **kw)
|
||||
|
||||
|
|
60
tests/auth.py
Normal file
60
tests/auth.py
Normal file
|
@ -0,0 +1,60 @@
|
|||
#
|
||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
|
||||
"""
|
||||
Elements helpful for testing with authenticated users.
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
||||
|
||||
from zope.cachedescriptors.property import Lazy
|
||||
from zope.publisher.browser import TestRequest as BaseTestRequest
|
||||
from zope.security.management import getInteraction, newInteraction, endInteraction
|
||||
|
||||
|
||||
class Participation(object):
|
||||
""" Dummy Participation class for testing.
|
||||
"""
|
||||
|
||||
interaction = None
|
||||
|
||||
def __init__(self, principal):
|
||||
self.principal = principal
|
||||
|
||||
|
||||
class TestRequest(BaseTestRequest):
|
||||
|
||||
basePrincipal = BaseTestRequest.principal
|
||||
|
||||
@Lazy
|
||||
def principal(self):
|
||||
interaction = getInteraction()
|
||||
if interaction is not None:
|
||||
parts = interaction.participations
|
||||
if parts:
|
||||
prin = parts[0].principal
|
||||
if prin is not None:
|
||||
return prin
|
||||
return self.basePrincipal
|
||||
|
||||
|
||||
def login(principal):
|
||||
endInteraction()
|
||||
newInteraction(Participation(principal))
|
||||
|
|
@ -6,21 +6,31 @@ $Id$
|
|||
|
||||
from zope import component
|
||||
from zope.annotation.attribute import AttributeAnnotations
|
||||
from zope.annotation.interfaces import IAnnotatable
|
||||
from zope.app.catalog.catalog import Catalog
|
||||
from zope.app.catalog.interfaces import ICatalog
|
||||
from zope.app.catalog.field import FieldIndex
|
||||
from zope.app.catalog.text import TextIndex
|
||||
from zope.app.container.interfaces import IObjectRemovedEvent
|
||||
from zope.app.principalannotation import PrincipalAnnotationUtility
|
||||
from zope.app.principalannotation.interfaces import IPrincipalAnnotationUtility
|
||||
from zope.app.security.principalregistry import principalRegistry
|
||||
from zope.app.security.interfaces import IAuthentication
|
||||
from zope.app.securitypolicy.zopepolicy import ZopeSecurityPolicy
|
||||
from zope.app.securitypolicy.principalrole import AnnotationPrincipalRoleManager
|
||||
from zope.app.securitypolicy.rolepermission import AnnotationRolePermissionManager
|
||||
from zope.app.session.interfaces import IClientIdManager, ISessionDataContainer
|
||||
from zope.app.session import session
|
||||
from zope.dublincore.annotatableadapter import ZDCAnnotatableAdapter
|
||||
from zope.dublincore.interfaces import IZopeDublinCore
|
||||
from zope.interface import implements
|
||||
from zope.interface import Interface, implements
|
||||
from zope.publisher.interfaces.browser import IBrowserRequest, IBrowserView
|
||||
from zope.security.checker import Checker, defineChecker
|
||||
|
||||
from cybertools.browser.controller import Controller
|
||||
from cybertools.composer.schema.factory import SchemaFactory
|
||||
from cybertools.composer.schema.field import FieldInstance, NumberFieldInstance
|
||||
from cybertools.composer.schema.field import DateFieldInstance, BooleanFieldInstance
|
||||
from cybertools.composer.schema.instance import Instance, Editor
|
||||
from cybertools.relation.tests import IntIdsStub
|
||||
from cybertools.relation.registry import RelationRegistry
|
||||
|
@ -34,14 +44,21 @@ from loops.base import Loops
|
|||
from loops import util
|
||||
from loops.browser.node import ViewPropertiesConfigurator
|
||||
from loops.common import NameChooser
|
||||
from loops.interfaces import ILoopsObject, IIndexAttributes
|
||||
from loops.interfaces import IDocument, IFile, ITextDocument
|
||||
from loops.concept import Concept
|
||||
from loops.concept import IndexAttributes as ConceptIndexAttributes
|
||||
from loops.interfaces import ILoopsObject, IIndexAttributes
|
||||
from loops.interfaces import IDocument, IFile, ITextDocument
|
||||
from loops.organize.memberinfo import MemberInfoProvider
|
||||
from loops.query import QueryConcept
|
||||
from loops.query import QueryConcept
|
||||
from loops.resource import Resource, FileAdapter, TextDocumentAdapter
|
||||
from loops.resource import Document, MediaAsset
|
||||
from loops.resource import IndexAttributes as ResourceIndexAttributes
|
||||
from loops.schema import ResourceSchemaFactory, FileSchemaFactory, NoteSchemaFactory
|
||||
from loops.security.common import grantAcquiredSecurity, revokeAcquiredSecurity
|
||||
from zope.security.management import setSecurityPolicy
|
||||
from loops.security.policy import LoopsSecurityPolicy
|
||||
from loops.security.setter import BaseSecuritySetter
|
||||
from loops.setup import SetupManager, addObject
|
||||
from loops.type import LoopsType, ConceptType, ResourceType, TypeConcept
|
||||
from loops.view import NodeAdapter
|
||||
|
@ -62,14 +79,25 @@ class TestSite(object):
|
|||
def baseSetup(self):
|
||||
site = self.site
|
||||
|
||||
#oldPolicy = setSecurityPolicy(ZopeSecurityPolicy)
|
||||
oldPolicy = setSecurityPolicy(LoopsSecurityPolicy)
|
||||
checker = Checker(dict(title='zope.View', data='zope.View'),
|
||||
dict(title='zope.ManageContent'))
|
||||
defineChecker(Concept, checker)
|
||||
defineChecker(Resource, checker)
|
||||
defineChecker(Document, checker)
|
||||
|
||||
component.provideUtility(IntIdsStub())
|
||||
relations = RelationRegistry()
|
||||
relations.setupIndexes()
|
||||
component.provideUtility(relations, IRelationRegistry)
|
||||
component.provideAdapter(IndexableRelationAdapter)
|
||||
|
||||
component.provideUtility(PrincipalAnnotationUtility(), IPrincipalAnnotationUtility)
|
||||
component.provideAdapter(IndexableRelationAdapter)
|
||||
component.provideAdapter(ZDCAnnotatableAdapter, (ILoopsObject,), IZopeDublinCore)
|
||||
component.provideAdapter(AttributeAnnotations, (ILoopsObject,))
|
||||
component.provideAdapter(AnnotationPrincipalRoleManager, (ILoopsObject,))
|
||||
component.provideAdapter(AnnotationRolePermissionManager, (ILoopsObject,))
|
||||
component.provideUtility(principalRegistry, IAuthentication)
|
||||
component.provideAdapter(session.ClientId)
|
||||
component.provideAdapter(session.Session)
|
||||
|
@ -86,16 +114,26 @@ class TestSite(object):
|
|||
component.provideAdapter(NodeAdapter)
|
||||
component.provideAdapter(ViewPropertiesConfigurator)
|
||||
component.provideAdapter(NameChooser)
|
||||
component.provideHandler(grantAcquiredSecurity)
|
||||
component.provideHandler(revokeAcquiredSecurity)
|
||||
component.provideAdapter(BaseSecuritySetter)
|
||||
|
||||
component.provideAdapter(Instance)
|
||||
component.provideAdapter(Editor, name='editor')
|
||||
component.provideAdapter(FieldInstance)
|
||||
component.provideAdapter(NumberFieldInstance, name='number')
|
||||
|
||||
component.provideAdapter(DateFieldInstance, name='date')
|
||||
component.provideAdapter(BooleanFieldInstance, name='boolean')
|
||||
component.provideAdapter(SchemaFactory)
|
||||
component.provideAdapter(ResourceSchemaFactory)
|
||||
component.provideAdapter(FileSchemaFactory)
|
||||
component.provideAdapter(NoteSchemaFactory)
|
||||
|
||||
component.provideAdapter(Controller, (Interface, IBrowserRequest),
|
||||
IBrowserView, name='controller')
|
||||
component.provideAdapter(MemberInfoProvider,
|
||||
(ILoopsObject, IBrowserRequest))
|
||||
|
||||
component.getSiteManager().registerHandler(invalidateRelations,
|
||||
(ILoopsObject, IObjectRemovedEvent))
|
||||
component.getSiteManager().registerHandler(removeRelation,
|
||||
|
|
20
type.py
20
type.py
|
@ -88,7 +88,6 @@ class LoopsType(BaseType):
|
|||
addQualifiers = self.optionsDict.get('qualifier')
|
||||
if addQualifiers:
|
||||
qu.extend(addQualifiers.split(','))
|
||||
# how to set a type to 'hidden'?
|
||||
return tuple(qu)
|
||||
|
||||
@Lazy
|
||||
|
@ -112,14 +111,7 @@ class LoopsType(BaseType):
|
|||
|
||||
@Lazy
|
||||
def optionsDict(self):
|
||||
result = {'default': []}
|
||||
for opt in self.options:
|
||||
if ':' in opt:
|
||||
key, value = opt.split(':', 1)
|
||||
result[key] = value
|
||||
else:
|
||||
result['default'].append(opt)
|
||||
return result
|
||||
return getOptionsDict(self.options)
|
||||
|
||||
# general infos
|
||||
|
||||
|
@ -291,3 +283,13 @@ class TypeInterfaceSourceList(object):
|
|||
def __len__(self):
|
||||
return len(self.typeInterfaces)
|
||||
|
||||
|
||||
def getOptionsDict(options):
|
||||
result = {'default': []}
|
||||
for opt in options:
|
||||
if ':' in opt:
|
||||
key, value = opt.split(':', 1)
|
||||
result[key] = value
|
||||
else:
|
||||
result['default'].append(opt)
|
||||
return result
|
||||
|
|
12
util.py
12
util.py
|
@ -27,12 +27,22 @@ from zope.app.intid.interfaces import IIntIds
|
|||
from zope.interface import directlyProvides, directlyProvidedBy
|
||||
from zope.i18nmessageid import MessageFactory
|
||||
from zope.schema import vocabulary
|
||||
#from view import TargetRelation
|
||||
|
||||
from loops.browser.util import html_quote
|
||||
|
||||
_ = MessageFactory('loops')
|
||||
#_ = MessageFactory('zope') # it's easier not use a special i18n domain?
|
||||
|
||||
|
||||
renderingFactories = {
|
||||
'text/plain': 'zope.source.plaintext',
|
||||
'text/stx': 'zope.source.stx',
|
||||
'text/structured': 'zope.source.stx',
|
||||
'text/rest': 'zope.source.rest',
|
||||
'text/restructured': 'zope.source.rest',
|
||||
}
|
||||
|
||||
|
||||
class KeywordVocabulary(vocabulary.SimpleVocabulary):
|
||||
|
||||
def __init__(self, items, *interfaces):
|
||||
|
|
|
@ -48,6 +48,7 @@ class VersionableResource(object):
|
|||
|
||||
def __init__(self, context):
|
||||
self.context = context
|
||||
self.__parent__ = context
|
||||
|
||||
def getVersioningAttribute(self, attr, default):
|
||||
attrName = attrPattern % attr
|
||||
|
|
5
view.py
5
view.py
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2005 Helmut Merz helmutm@cy55.de
|
||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -27,13 +27,12 @@ from zope.app import zapi
|
|||
from zope.app.container.btree import BTreeContainer
|
||||
from zope.app.container.contained import Contained
|
||||
from zope.app.container.ordered import OrderedContainer
|
||||
from zope.app.container.traversal import ContainerTraverser, ItemTraverser
|
||||
from zope.app.container.traversal import ContainerTraversable
|
||||
from zope.app.intid.interfaces import IIntIds
|
||||
from zope.cachedescriptors.property import Lazy, readproperty
|
||||
from zope.component import adapts
|
||||
from zope.interface import implements
|
||||
from zope.interface import alsoProvides, directlyProvides, directlyProvidedBy
|
||||
from zope.publisher.browser import applySkin
|
||||
from zope.security.proxy import removeSecurityProxy
|
||||
from persistent import Persistent
|
||||
from cybertools.relation import DyadicRelation
|
||||
|
|
|
@ -8,57 +8,39 @@ Let's do some basic set up
|
|||
|
||||
>>> from zope.app.testing.setup import placefulSetUp, placefulTearDown
|
||||
>>> site = placefulSetUp(True)
|
||||
|
||||
>>> from zope import component, interface
|
||||
>>> from zope.publisher.browser import TestRequest
|
||||
>>> from loops.concept import Concept
|
||||
>>> from loops.resource import Resource
|
||||
>>>
|
||||
|
||||
and 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'])
|
||||
|
|
|
@ -162,7 +162,7 @@ def objectAsDict(obj, langInfo=None):
|
|||
'title': adapter.title, 'description': adapter.description,
|
||||
'type': getUidForObject(objType.typeProvider)}
|
||||
ti = objType.typeInterface
|
||||
if ti is not None:
|
||||
if ti is not None and adapter != obj:
|
||||
#for attr in (list(adapter._adapterAttributes) + list(ti)):
|
||||
for attr in list(ti):
|
||||
if attr not in ('__parent__', 'context', 'id', 'name',
|
||||
|
|
Loading…
Add table
Reference in a new issue