Merge branch 'master' of ssh://git.cy55.de/home/git/loops
This commit is contained in:
commit
4178829685
59 changed files with 1377 additions and 512 deletions
|
@ -49,7 +49,7 @@ from zope.security import canAccess
|
||||||
from zope.security.interfaces import ForbiddenAttribute, Unauthorized
|
from zope.security.interfaces import ForbiddenAttribute, Unauthorized
|
||||||
from zope.security.proxy import removeSecurityProxy
|
from zope.security.proxy import removeSecurityProxy
|
||||||
from zope.traversing.browser import absoluteURL
|
from zope.traversing.browser import absoluteURL
|
||||||
from zope.traversing.api import getName, getParent
|
from zope.traversing.api import getName, getParent, traverse
|
||||||
|
|
||||||
from cybertools.ajax.dojo import dojoMacroTemplate
|
from cybertools.ajax.dojo import dojoMacroTemplate
|
||||||
from cybertools.browser.view import GenericView
|
from cybertools.browser.view import GenericView
|
||||||
|
@ -70,7 +70,7 @@ from loops.organize.tracking import access
|
||||||
from loops.resource import Resource
|
from loops.resource import Resource
|
||||||
from loops.security.common import checkPermission
|
from loops.security.common import checkPermission
|
||||||
from loops.security.common import canAccessObject, canListObject, canWriteObject
|
from loops.security.common import canAccessObject, canListObject, canWriteObject
|
||||||
from loops.type import ITypeConcept
|
from loops.type import ITypeConcept, LoopsTypeInfo
|
||||||
from loops import util
|
from loops import util
|
||||||
from loops.util import _, saveRequest
|
from loops.util import _, saveRequest
|
||||||
from loops import version
|
from loops import version
|
||||||
|
@ -481,7 +481,7 @@ class BaseView(GenericView, I18NView):
|
||||||
return absoluteURL(provider, self.request)
|
return absoluteURL(provider, self.request)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def renderText(self, text, contentType):
|
def renderText(self, text, contentType='text/restructured'):
|
||||||
text = util.toUnicode(text)
|
text = util.toUnicode(text)
|
||||||
typeKey = util.renderingFactories.get(contentType, None)
|
typeKey = util.renderingFactories.get(contentType, None)
|
||||||
if typeKey is None:
|
if typeKey is None:
|
||||||
|
@ -531,11 +531,29 @@ class BaseView(GenericView, I18NView):
|
||||||
def conceptTypes(self):
|
def conceptTypes(self):
|
||||||
return util.KeywordVocabulary(self.listTypes(('concept',), ('hidden',)))
|
return util.KeywordVocabulary(self.listTypes(('concept',), ('hidden',)))
|
||||||
|
|
||||||
|
def parentTypesFromOtherSites(self):
|
||||||
|
result = []
|
||||||
|
typeNames = self.typeOptions('foreign_parent_types') or []
|
||||||
|
for path in self.typeOptions('foreign_parent_sites') or []:
|
||||||
|
site = traverse(self.loopsRoot, path, None)
|
||||||
|
if site is None:
|
||||||
|
continue
|
||||||
|
cm = site.getConceptManager()
|
||||||
|
for tname in typeNames:
|
||||||
|
t = cm.get(tname)
|
||||||
|
if t is not None:
|
||||||
|
type = LoopsTypeInfo(t)
|
||||||
|
type.isForeignReference = True
|
||||||
|
result.append(type)
|
||||||
|
return result
|
||||||
|
|
||||||
def listTypesForSearch(self, include=None, exclude=None, sortOn='title'):
|
def listTypesForSearch(self, include=None, exclude=None, sortOn='title'):
|
||||||
types = [dict(token=t.tokenForSearch, title=t.title)
|
types = [dict(token=t.tokenForSearch, title=t.title)
|
||||||
for t in ITypeManager(self.context).listTypes(include, exclude)]
|
for t in ITypeManager(self.context).listTypes(include, exclude)]
|
||||||
if sortOn:
|
if sortOn:
|
||||||
types.sort(key=lambda x: x[sortOn])
|
types.sort(key=lambda x: x[sortOn])
|
||||||
|
for t in self.parentTypesFromOtherSites():
|
||||||
|
types.append(dict(token=t.tokenForSearch, title=t.title))
|
||||||
return types
|
return types
|
||||||
|
|
||||||
def typesForSearch(self):
|
def typesForSearch(self):
|
||||||
|
|
4
browser/compound/__init__.py
Normal file
4
browser/compound/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
"""
|
||||||
|
package loops.browser.compound
|
||||||
|
"""
|
||||||
|
|
14
browser/compound/configure.zcml
Normal file
14
browser/compound/configure.zcml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<configure
|
||||||
|
xmlns:zope="http://namespaces.zope.org/zope"
|
||||||
|
xmlns:browser="http://namespaces.zope.org/browser"
|
||||||
|
i18n_domain="loops">
|
||||||
|
|
||||||
|
<zope:adapter
|
||||||
|
name="compound.html"
|
||||||
|
for="loops.interfaces.IConcept
|
||||||
|
zope.publisher.interfaces.browser.IBrowserRequest"
|
||||||
|
provides="zope.interface.Interface"
|
||||||
|
factory="loops.browser.compound.standard.CompoundView"
|
||||||
|
permission="zope.View" />
|
||||||
|
|
||||||
|
</configure>
|
54
browser/compound/standard.py
Normal file
54
browser/compound/standard.py
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
#
|
||||||
|
# Copyright (c) 2013 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
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
Definition of compound views.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from zope import interface, component
|
||||||
|
from zope.app.pagetemplate import ViewPageTemplateFile
|
||||||
|
from zope.cachedescriptors.property import Lazy
|
||||||
|
|
||||||
|
from loops.browser.concept import ConceptView
|
||||||
|
from loops.util import _
|
||||||
|
|
||||||
|
|
||||||
|
compound_macros = ViewPageTemplateFile('view_macros.pt')
|
||||||
|
|
||||||
|
|
||||||
|
class CompoundView(ConceptView):
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def macro(self):
|
||||||
|
return compound_macros.macros['standard']
|
||||||
|
|
||||||
|
def getParts(self):
|
||||||
|
parts = (self.options('view_parts') or self.typeOptions('view_parts') or [])
|
||||||
|
return self.getPartViews(parts)
|
||||||
|
|
||||||
|
def getPartViews(self, parts):
|
||||||
|
result = []
|
||||||
|
for p in parts:
|
||||||
|
view = component.queryMultiAdapter((self.adapted, self.request), name=p)
|
||||||
|
if view is None:
|
||||||
|
view = component.queryMultiAdapter((self.context, self.request), name=p)
|
||||||
|
if view is not None:
|
||||||
|
view.parent = self
|
||||||
|
result.append(view)
|
||||||
|
return result
|
||||||
|
|
13
browser/compound/view_macros.pt
Normal file
13
browser/compound/view_macros.pt
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<html i18n:domain="loops">
|
||||||
|
|
||||||
|
|
||||||
|
<metal:data define-macro="standard">
|
||||||
|
<tal:part repeat="item item/getParts">
|
||||||
|
<tal:check condition="item/checkPermissions">
|
||||||
|
<metal:part use-macro="item/macro" />
|
||||||
|
</tal:check>
|
||||||
|
</tal:part>
|
||||||
|
</metal:data>
|
||||||
|
|
||||||
|
|
||||||
|
</html>
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2012 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -50,6 +50,7 @@ from cybertools.meta.interfaces import IOptions
|
||||||
from cybertools.typology.interfaces import IType, ITypeManager
|
from cybertools.typology.interfaces import IType, ITypeManager
|
||||||
from cybertools.util.jeep import Jeep
|
from cybertools.util.jeep import Jeep
|
||||||
from loops.browser.common import EditForm, BaseView, LoopsTerms, concept_macros
|
from loops.browser.common import EditForm, BaseView, LoopsTerms, concept_macros
|
||||||
|
from loops.browser.common import ViewMode
|
||||||
from loops.common import adapted
|
from loops.common import adapted
|
||||||
from loops.concept import Concept, ConceptTypeSourceList, PredicateSourceList
|
from loops.concept import Concept, ConceptTypeSourceList, PredicateSourceList
|
||||||
from loops.i18n.browser import I18NView
|
from loops.i18n.browser import I18NView
|
||||||
|
@ -196,6 +197,12 @@ class BaseRelationView(BaseView):
|
||||||
return u''
|
return u''
|
||||||
return self.predicateTitle
|
return self.predicateTitle
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def relationInfo(self):
|
||||||
|
predInfo = ', ' .join(p.title for p in self.predicates
|
||||||
|
if p != self.defaultPredicate)
|
||||||
|
return ' | '.join(t for t in (self.description, predInfo) if t)
|
||||||
|
|
||||||
|
|
||||||
class ConceptView(BaseView):
|
class ConceptView(BaseView):
|
||||||
|
|
||||||
|
@ -228,6 +235,7 @@ class ConceptView(BaseView):
|
||||||
subMacro=concept_macros.macros['parents'],
|
subMacro=concept_macros.macros['parents'],
|
||||||
priority=20, info=self)
|
priority=20, info=self)
|
||||||
|
|
||||||
|
# the part-based layout is now implemented in loops.browser.compound
|
||||||
def getParts(self):
|
def getParts(self):
|
||||||
parts = (self.params.get('parts') or []) # deprecated!
|
parts = (self.params.get('parts') or []) # deprecated!
|
||||||
if not parts:
|
if not parts:
|
||||||
|
@ -740,3 +748,30 @@ class ListTypeInstances(ListChildren):
|
||||||
noDuplicates, useFilter, [self.typePredicate]):
|
noDuplicates, useFilter, [self.typePredicate]):
|
||||||
yield c
|
yield c
|
||||||
|
|
||||||
|
|
||||||
|
class TabbedPage(ConceptView):
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def subpagePredicates(self):
|
||||||
|
pred = self.conceptManager.get('issubpage')
|
||||||
|
if pred is None:
|
||||||
|
pred = self.isPartOfPredicate
|
||||||
|
return [pred]
|
||||||
|
|
||||||
|
def viewModes(self):
|
||||||
|
modes = Jeep()
|
||||||
|
for s in self.getSiblings(self.subpagePredicates):
|
||||||
|
url = self.nodeView.getUrlForTarget(s)
|
||||||
|
modes.append(ViewMode(getName(s), s.title, url))
|
||||||
|
if not modes:
|
||||||
|
return modes
|
||||||
|
modes[getName(self.context)].active = True
|
||||||
|
return modes
|
||||||
|
|
||||||
|
def getSiblings(self, preds):
|
||||||
|
for p in self.context.getParents(preds):
|
||||||
|
parent = p
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
return p.getChildren(preds)
|
||||||
|
|
|
@ -64,7 +64,8 @@
|
||||||
</h1>
|
</h1>
|
||||||
<metal:block use-macro="view/concept_macros/filter_input" />
|
<metal:block use-macro="view/concept_macros/filter_input" />
|
||||||
</metal:title>
|
</metal:title>
|
||||||
<p tal:define="description description|item/renderedDescription"
|
<p metal:define-macro="conceptdescription"
|
||||||
|
tal:define="description description|item/renderedDescription"
|
||||||
tal:condition="description">
|
tal:condition="description">
|
||||||
<i tal:content="structure description">Description</i></p>
|
<i tal:content="structure description">Description</i></p>
|
||||||
</metal:title>
|
</metal:title>
|
||||||
|
@ -158,11 +159,8 @@
|
||||||
tal:attributes="dojoType python:
|
tal:attributes="dojoType python:
|
||||||
item.editable and 'dojo.dnd.Source' or ''">
|
item.editable and 'dojo.dnd.Source' or ''">
|
||||||
<tal:items repeat="related children">
|
<tal:items repeat="related children">
|
||||||
<tal:item define="class python: repeat['related'].odd() and 'even' or 'odd';
|
<tal:item define="class python:
|
||||||
description related/description;
|
repeat['related'].odd() and 'even' or 'odd';">
|
||||||
predicate related/predicateTitle;
|
|
||||||
info python: ' | '.join(
|
|
||||||
t for t in (description, predicate) if t)">
|
|
||||||
<tr tal:attributes="class string:$class dojoDndItem dojoDndHandle;
|
<tr tal:attributes="class string:$class dojoDndItem dojoDndHandle;
|
||||||
id related/uniqueId">
|
id related/uniqueId">
|
||||||
<td tal:condition="item/showCheckboxes|nothing"
|
<td tal:condition="item/showCheckboxes|nothing"
|
||||||
|
@ -172,7 +170,7 @@
|
||||||
tal:attributes="value uid;" /></td>
|
tal:attributes="value uid;" /></td>
|
||||||
<td valign="top">
|
<td valign="top">
|
||||||
<a tal:attributes="href python: view.getUrlForTarget(related);
|
<a tal:attributes="href python: view.getUrlForTarget(related);
|
||||||
title info">
|
title related/relationInfo">
|
||||||
<span tal:replace="related/title">Resource Title</span>
|
<span tal:replace="related/title">Resource Title</span>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
@ -241,11 +239,8 @@
|
||||||
tal:attributes="dojoType python:
|
tal:attributes="dojoType python:
|
||||||
item.editable and 'dojo.dnd.Source' or ''">
|
item.editable and 'dojo.dnd.Source' or ''">
|
||||||
<tal:items repeat="related resources">
|
<tal:items repeat="related resources">
|
||||||
<tal:item define="class python: repeat['related'].odd() and 'even' or 'odd';
|
<tal:item define="class python:
|
||||||
description related/description;
|
repeat['related'].odd() and 'even' or 'odd';">
|
||||||
predicate related/predicateTitle;
|
|
||||||
info python: ' | '.join(
|
|
||||||
t for t in (description, predicate) if t)">
|
|
||||||
<tr tal:attributes="class string:$class dojoDndItem dojoDndHandle;
|
<tr tal:attributes="class string:$class dojoDndItem dojoDndHandle;
|
||||||
id related/uniqueId">
|
id related/uniqueId">
|
||||||
<td tal:condition="item/showCheckboxes|nothing"
|
<td tal:condition="item/showCheckboxes|nothing"
|
||||||
|
@ -262,7 +257,7 @@
|
||||||
<img tal:attributes="src icon/src" />
|
<img tal:attributes="src icon/src" />
|
||||||
</a>
|
</a>
|
||||||
<a tal:attributes="href python: view.getUrlForTarget(related);
|
<a tal:attributes="href python: view.getUrlForTarget(related);
|
||||||
title info">
|
title related/relationInfo">
|
||||||
<div tal:content="related/title">Resource Title</div>
|
<div tal:content="related/title">Resource Title</div>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -553,6 +553,14 @@
|
||||||
factory="loops.browser.concept.ListTypeInstances"
|
factory="loops.browser.concept.ListTypeInstances"
|
||||||
permission="zope.View" />
|
permission="zope.View" />
|
||||||
|
|
||||||
|
<zope:adapter
|
||||||
|
name="tabbed_page.html"
|
||||||
|
for="loops.interfaces.IConcept
|
||||||
|
zope.publisher.interfaces.browser.IBrowserRequest"
|
||||||
|
provides="zope.interface.Interface"
|
||||||
|
factory="loops.browser.concept.TabbedPage"
|
||||||
|
permission="zope.View" />
|
||||||
|
|
||||||
<!-- dialogs/forms (end-user views) -->
|
<!-- dialogs/forms (end-user views) -->
|
||||||
|
|
||||||
<page
|
<page
|
||||||
|
@ -757,6 +765,7 @@
|
||||||
attribute="cleanup"
|
attribute="cleanup"
|
||||||
permission="zope.ManageSite" />
|
permission="zope.ManageSite" />
|
||||||
|
|
||||||
|
<include package=".compound" />
|
||||||
<include package=".skin" />
|
<include package=".skin" />
|
||||||
<include package=".lobo" />
|
<include package=".lobo" />
|
||||||
<include package=".mobile" />
|
<include package=".mobile" />
|
||||||
|
|
|
@ -51,6 +51,8 @@
|
||||||
</th>
|
</th>
|
||||||
</tr></tbody>
|
</tr></tbody>
|
||||||
|
|
||||||
|
<tbody metal:define-slot="custom_header" />
|
||||||
|
|
||||||
<tbody><tr><td colspan="5" style="padding-right: 15px">
|
<tbody><tr><td colspan="5" style="padding-right: 15px">
|
||||||
<div id="form.fields">
|
<div id="form.fields">
|
||||||
<metal:fields use-macro="view/fieldRenderers/fields" />
|
<metal:fields use-macro="view/fieldRenderers/fields" />
|
||||||
|
@ -59,7 +61,7 @@
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr metal:use-macro="view/template/macros/assignments" />
|
<tr metal:use-macro="view/template/macros/assignments" />
|
||||||
<tal:custom define="customMacro view/customMacro"
|
<tal:custom define="customMacro view/customMacro|nothing"
|
||||||
condition="customMacro">
|
condition="customMacro">
|
||||||
<tr metal:use-macro="customMacro" />
|
<tr metal:use-macro="customMacro" />
|
||||||
</tal:custom>
|
</tal:custom>
|
||||||
|
@ -119,6 +121,8 @@
|
||||||
tal:attributes="value typeToken" />
|
tal:attributes="value typeToken" />
|
||||||
</th></tr></tbody>
|
</th></tr></tbody>
|
||||||
|
|
||||||
|
<tbody metal:define-slot="custom_header" />
|
||||||
|
|
||||||
<tbody><tr><td colspan="5">
|
<tbody><tr><td colspan="5">
|
||||||
<div id="form.fields">
|
<div id="form.fields">
|
||||||
<metal:fields use-macro="view/fieldRenderers/fields" />
|
<metal:fields use-macro="view/fieldRenderers/fields" />
|
||||||
|
@ -127,7 +131,7 @@
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr metal:use-macro="view/template/macros/assignments" />
|
<tr metal:use-macro="view/template/macros/assignments" />
|
||||||
<tal:custom define="customMacro view/customMacro"
|
<tal:custom define="customMacro view/customMacro|nothing"
|
||||||
condition="customMacro">
|
condition="customMacro">
|
||||||
<tr metal:use-macro="customMacro" />
|
<tr metal:use-macro="customMacro" />
|
||||||
</tal:custom>
|
</tal:custom>
|
||||||
|
|
|
@ -117,6 +117,7 @@
|
||||||
<tal:img condition="cell/img">
|
<tal:img condition="cell/img">
|
||||||
<a dojoType="dojox.image.Lightbox" group="mediasset"
|
<a dojoType="dojox.image.Lightbox" group="mediasset"
|
||||||
i18n:attributes="title"
|
i18n:attributes="title"
|
||||||
|
tal:omit-tag="python:part.imageSize in ('large',)"
|
||||||
tal:attributes="href cell/img/fullImageUrl;
|
tal:attributes="href cell/img/fullImageUrl;
|
||||||
title python: cell.img['description'] or cell.img['title']">
|
title python: cell.img['description'] or cell.img['title']">
|
||||||
<img tal:condition="showImageLink|python:False"
|
<img tal:condition="showImageLink|python:False"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* $Id$ */
|
/* loops.js */
|
||||||
|
|
||||||
function openEditWindow(url) {
|
function openEditWindow(url) {
|
||||||
zmi = window.open(url, 'zmi');
|
zmi = window.open(url, 'zmi');
|
||||||
|
@ -19,6 +19,12 @@ function toggleCheckBoxes(toggle, fieldName) {
|
||||||
for (i in w) w[i].checked=toggle.checked;
|
for (i in w) w[i].checked=toggle.checked;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setRadioButtons(value) {
|
||||||
|
dojo.forEach(dojo.query('input[type="radio"][value="' + value + '"]'),
|
||||||
|
function(n) {
|
||||||
|
n.checked = true;})
|
||||||
|
}
|
||||||
|
|
||||||
function validate(nodeName, required) {
|
function validate(nodeName, required) {
|
||||||
// (work in progress) - may be used for onBlur event handler
|
// (work in progress) - may be used for onBlur event handler
|
||||||
var w = dojo.byId(nodeName);
|
var w = dojo.byId(nodeName);
|
||||||
|
|
|
@ -195,7 +195,11 @@ class NodeView(BaseView):
|
||||||
subMacro=calendar_macros.macros['main'],
|
subMacro=calendar_macros.macros['main'],
|
||||||
priority=90)
|
priority=90)
|
||||||
# force early portlet registrations by target by setting up target view
|
# force early portlet registrations by target by setting up target view
|
||||||
self.virtualTarget
|
if self.virtualTarget is not None:
|
||||||
|
std = self.virtualTarget.typeOptions('portlet_states')
|
||||||
|
if std:
|
||||||
|
from loops.organize.stateful.browser import registerStatesPortlet
|
||||||
|
registerStatesPortlet(self.controller, self.virtualTarget, std)
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def usersPresent(self):
|
def usersPresent(self):
|
||||||
|
@ -381,6 +385,8 @@ class NodeView(BaseView):
|
||||||
ht = super(NodeView, self).headTitle
|
ht = super(NodeView, self).headTitle
|
||||||
if ht not in parts:
|
if ht not in parts:
|
||||||
parts.append(ht)
|
parts.append(ht)
|
||||||
|
if self.globalOptions('reverseHeadTitle'):
|
||||||
|
parts.reverse()
|
||||||
return ' - ' .join(parts)
|
return ' - ' .join(parts)
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
|
|
|
@ -314,8 +314,13 @@
|
||||||
|
|
||||||
|
|
||||||
<metal:login define-macro="login">
|
<metal:login define-macro="login">
|
||||||
<div><a href="login.html"
|
<div>
|
||||||
|
<a href="login.html"
|
||||||
i18n:translate="">Log in</a></div>
|
i18n:translate="">Log in</a></div>
|
||||||
|
<div tal:define="register python:view.globalOptions('provideLogin')"
|
||||||
|
tal:condition="register">
|
||||||
|
<a tal:attributes="href python:register[0]"
|
||||||
|
i18n:translate="">Register new member</a></div>
|
||||||
</metal:login>
|
</metal:login>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,7 @@ from zope.traversing.browser import absoluteURL
|
||||||
from cybertools.browser.action import actions
|
from cybertools.browser.action import actions
|
||||||
from cybertools.meta.interfaces import IOptions
|
from cybertools.meta.interfaces import IOptions
|
||||||
from cybertools.typology.interfaces import IType
|
from cybertools.typology.interfaces import IType
|
||||||
|
from cybertools.util.html import extractFirstPart
|
||||||
from cybertools.xedit.browser import ExternalEditorView, fromUnicode
|
from cybertools.xedit.browser import ExternalEditorView, fromUnicode
|
||||||
from loops.browser.action import DialogAction, TargetAction
|
from loops.browser.action import DialogAction, TargetAction
|
||||||
from loops.browser.common import EditForm, BaseView
|
from loops.browser.common import EditForm, BaseView
|
||||||
|
@ -131,6 +132,9 @@ class ResourceView(BaseView):
|
||||||
def macro(self):
|
def macro(self):
|
||||||
if 'image/' in self.context.contentType:
|
if 'image/' in self.context.contentType:
|
||||||
return self.template.macros['image']
|
return self.template.macros['image']
|
||||||
|
#elif 'audio/' in self.context.contentType:
|
||||||
|
# self.registerDojoAudio()
|
||||||
|
# return self.template.macros['audio']
|
||||||
else:
|
else:
|
||||||
return self.template.macros['download']
|
return self.template.macros['download']
|
||||||
|
|
||||||
|
@ -252,6 +256,12 @@ class ResourceView(BaseView):
|
||||||
#return util.toUnicode(wp.render(self.request))
|
#return util.toUnicode(wp.render(self.request))
|
||||||
return super(ResourceView, self).renderText(text, contentType)
|
return super(ResourceView, self).renderText(text, contentType)
|
||||||
|
|
||||||
|
def renderShortText(self):
|
||||||
|
return self.renderDescription() or self.createShortText(self.render())
|
||||||
|
|
||||||
|
def createShortText(self, text=None):
|
||||||
|
return extractFirstPart(text or self.render())
|
||||||
|
|
||||||
def download(self):
|
def download(self):
|
||||||
""" Force download, e.g. of a PDF file """
|
""" Force download, e.g. of a PDF file """
|
||||||
return self.show(True)
|
return self.show(True)
|
||||||
|
|
|
@ -30,7 +30,13 @@
|
||||||
<metal:tabs use-macro="views/node_macros/breadcrumbs" />
|
<metal:tabs use-macro="views/node_macros/breadcrumbs" />
|
||||||
</metal:breadcrumbs>
|
</metal:breadcrumbs>
|
||||||
<div metal:define-slot="actions"></div>
|
<div metal:define-slot="actions"></div>
|
||||||
<div metal:define-slot="message"></div>
|
<metal:message define-slot="message">
|
||||||
|
<div class="message"
|
||||||
|
i18n:translate=""
|
||||||
|
tal:define="msg request/loops.message|nothing"
|
||||||
|
tal:condition="msg"
|
||||||
|
tal:content="msg" />
|
||||||
|
</metal:message>
|
||||||
<metal:tabs use-macro="views/node_macros/view_modes" />
|
<metal:tabs use-macro="views/node_macros/view_modes" />
|
||||||
<metal:content define-slot="content">
|
<metal:content define-slot="content">
|
||||||
<tal:content define="item nocall:view/item;
|
<tal:content define="item nocall:view/item;
|
||||||
|
|
|
@ -62,6 +62,10 @@ h1, h2, h3, h4, h5, h6 {
|
||||||
margin-bottom: 0.4em;
|
margin-bottom: 0.4em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
a[href]:hover {
|
a[href]:hover {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: #6060c0;
|
color: #6060c0;
|
||||||
|
@ -120,6 +124,10 @@ thead th {
|
||||||
margin-bottom: 0.3em;
|
margin-bottom: 0.3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.infotext {
|
||||||
|
font-size: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
.fields td {
|
.fields td {
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
@ -163,6 +171,10 @@ table.listing td {
|
||||||
border-bottom: 1px dotted #dddddd;
|
border-bottom: 1px dotted #dddddd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table.listing tr.vpad td {
|
||||||
|
padding: 7px 2px 7px 2px;
|
||||||
|
}
|
||||||
|
|
||||||
fieldset.box table.listing td {
|
fieldset.box table.listing td {
|
||||||
padding: 0 1px 0 1px;
|
padding: 0 1px 0 1px;
|
||||||
}
|
}
|
||||||
|
@ -267,7 +279,8 @@ fieldset.box td {
|
||||||
|
|
||||||
.top-actions {
|
.top-actions {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 30px;
|
top: 40px;
|
||||||
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quicksearch input {
|
.quicksearch input {
|
||||||
|
@ -281,7 +294,7 @@ fieldset.box td {
|
||||||
|
|
||||||
.page-actions {
|
.page-actions {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 55px;
|
top: 75px;
|
||||||
margin-left: 210px;
|
margin-left: 210px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -113,6 +113,9 @@ class AdapterBase(object):
|
||||||
self.context = context
|
self.context = context
|
||||||
self.__parent__ = context # to get the permission stuff right
|
self.__parent__ = context # to get the permission stuff right
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self.context)
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
self.checkAttr(attr)
|
self.checkAttr(attr)
|
||||||
return getattr(self.context, '_' + attr, None)
|
return getattr(self.context, '_' + attr, None)
|
||||||
|
|
|
@ -355,7 +355,7 @@ Books, Sections, and Pages
|
||||||
>>> importPath = os.path.join(os.path.dirname(__file__), 'book')
|
>>> importPath = os.path.join(os.path.dirname(__file__), 'book')
|
||||||
>>> importData(loopsRoot, importPath, 'loops_book_de.dmp')
|
>>> importData(loopsRoot, importPath, 'loops_book_de.dmp')
|
||||||
|
|
||||||
>>> from loops.compound.book.browser import PageLayout
|
>>> from loops.compound.book.browser import BookView, SectionView, TopicView
|
||||||
|
|
||||||
|
|
||||||
Fin de partie
|
Fin de partie
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
#
|
|
||||||
# Copyright (c) 2012 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
|
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
|
||||||
Implementation of book and book components
|
|
||||||
"""
|
|
||||||
|
|
||||||
from zope.cachedescriptors.property import Lazy
|
|
||||||
from zope.interface import implements
|
|
||||||
from zope.traversing.api import getName
|
|
||||||
|
|
||||||
from loops.compound.base import Compound
|
|
||||||
from loops.compound.book.interfaces import IPage
|
|
||||||
from loops.type import TypeInterfaceSourceList
|
|
||||||
|
|
||||||
|
|
||||||
TypeInterfaceSourceList.typeInterfaces += (IPage,)
|
|
||||||
|
|
||||||
|
|
||||||
class Page(Compound):
|
|
||||||
|
|
||||||
implements(IPage)
|
|
||||||
|
|
||||||
compoundPredicateNames = ['ispartof', 'standard']
|
|
||||||
|
|
||||||
@Lazy
|
|
||||||
def documentType(self):
|
|
||||||
return self.context.getConceptManager()['documenttype']
|
|
||||||
|
|
||||||
def getParts(self):
|
|
||||||
result = {}
|
|
||||||
for r in super(Page, self).getParts():
|
|
||||||
for parent in r.getParents():
|
|
||||||
if parent.conceptType == self.documentType:
|
|
||||||
item = result.setdefault(getName(parent), [])
|
|
||||||
item.append(r)
|
|
||||||
return result
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2012 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -26,6 +26,7 @@ from zope.app.pagetemplate import ViewPageTemplateFile
|
||||||
from zope.cachedescriptors.property import Lazy
|
from zope.cachedescriptors.property import Lazy
|
||||||
from zope.traversing.api import getName
|
from zope.traversing.api import getName
|
||||||
|
|
||||||
|
from cybertools.meta.interfaces import IOptions
|
||||||
from cybertools.typology.interfaces import IType
|
from cybertools.typology.interfaces import IType
|
||||||
from loops.browser.lobo import standard
|
from loops.browser.lobo import standard
|
||||||
from loops.browser.concept import ConceptView
|
from loops.browser.concept import ConceptView
|
||||||
|
@ -45,10 +46,22 @@ class Base(object):
|
||||||
def book_macros(self):
|
def book_macros(self):
|
||||||
return book_template.macros
|
return book_template.macros
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def documentTypeType(self):
|
||||||
|
return self.conceptManager['documenttype']
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def sectionType(self):
|
||||||
|
return self.conceptManager['section']
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def isPartOfPredicate(self):
|
def isPartOfPredicate(self):
|
||||||
return self.conceptManager['ispartof']
|
return self.conceptManager['ispartof']
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def showNavigation(self):
|
||||||
|
return self.typeOptions.show_navigation
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def breadcrumbsParent(self):
|
def breadcrumbsParent(self):
|
||||||
for p in self.context.getParents([self.isPartOfPredicate]):
|
for p in self.context.getParents([self.isPartOfPredicate]):
|
||||||
|
@ -82,34 +95,8 @@ class Base(object):
|
||||||
if self.editable:
|
if self.editable:
|
||||||
return 'index.html'
|
return 'index.html'
|
||||||
|
|
||||||
|
|
||||||
class BookOverview(Base, ConceptView):
|
|
||||||
|
|
||||||
@Lazy
|
|
||||||
def macro(self):
|
|
||||||
return book_template.macros['book']
|
|
||||||
|
|
||||||
|
|
||||||
class SectionView(Base, ConceptView):
|
|
||||||
|
|
||||||
@Lazy
|
|
||||||
def macro(self):
|
|
||||||
return book_template.macros['section']
|
|
||||||
|
|
||||||
@Lazy
|
|
||||||
def documentTypeType(self):
|
|
||||||
return self.conceptManager['documenttype']
|
|
||||||
|
|
||||||
@Lazy
|
|
||||||
def showNavigation(self):
|
|
||||||
return self.typeOptions.show_navigation
|
|
||||||
|
|
||||||
@Lazy
|
|
||||||
def sectionType(self):
|
|
||||||
return self.conceptManager['section']
|
|
||||||
|
|
||||||
def getResources(self):
|
def getResources(self):
|
||||||
relViews = super(SectionView, self).getResources()
|
relViews = super(Base, self).getResources()
|
||||||
return relViews
|
return relViews
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
|
@ -132,11 +119,36 @@ class SectionView(Base, ConceptView):
|
||||||
self.images[idx].append(img)
|
self.images[idx].append(img)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def getCssClassForResource(self, r):
|
def getDocumentTypeForResource(self, r):
|
||||||
for c in r.context.getConcepts([self.defaultPredicate]):
|
for c in r.context.getConcepts([self.defaultPredicate]):
|
||||||
if c.conceptType == self.documentTypeType:
|
if c.conceptType == self.documentTypeType:
|
||||||
return getName(c)
|
return c
|
||||||
|
|
||||||
|
def getOptionsForResource(self, r, name):
|
||||||
|
dt = self.getDocumentTypeForResource(r)
|
||||||
|
if dt is not None:
|
||||||
|
return IOptions(adapted(dt))(name)
|
||||||
|
|
||||||
|
def getTitleForResource(self, r):
|
||||||
|
if self.getOptionsForResource(r, 'showtitle'):
|
||||||
|
return r.title
|
||||||
|
|
||||||
|
def getIconForResource(self, r):
|
||||||
|
icon = self.getOptionsForResource(r, 'icon')
|
||||||
|
if icon:
|
||||||
|
return '/'.join((self.controller.resourceBase, icon[0]))
|
||||||
|
|
||||||
|
def getCssClassForResource(self, r):
|
||||||
|
dt = self.getDocumentTypeForResource(r)
|
||||||
|
if dt is None:
|
||||||
return 'textelement'
|
return 'textelement'
|
||||||
|
css = IOptions(adapted(dt))('cssclass')
|
||||||
|
if css:
|
||||||
|
return css
|
||||||
|
return getName(dt)
|
||||||
|
|
||||||
|
def getMacroForResource(self, r):
|
||||||
|
return self.book_macros['default_text']
|
||||||
|
|
||||||
def getParentsForResource(self, r):
|
def getParentsForResource(self, r):
|
||||||
for c in r.context.getConcepts([self.defaultPredicate]):
|
for c in r.context.getConcepts([self.defaultPredicate]):
|
||||||
|
@ -144,64 +156,23 @@ class SectionView(Base, ConceptView):
|
||||||
yield c
|
yield c
|
||||||
|
|
||||||
|
|
||||||
# layout parts - probably obsolete:
|
class BookView(Base, ConceptView):
|
||||||
|
|
||||||
class PageLayout(Base, standard.Layout):
|
@Lazy
|
||||||
|
def macro(self):
|
||||||
def getParts(self):
|
return book_template.macros['book']
|
||||||
parts = ['headline', 'keyquestions', 'quote', 'maintext',
|
|
||||||
'story', 'tip', 'usecase']
|
|
||||||
return self.getPartViews(parts)
|
|
||||||
|
|
||||||
|
|
||||||
class PagePart(object):
|
class SectionView(Base, ConceptView):
|
||||||
|
|
||||||
template = book_template
|
@Lazy
|
||||||
templateName = 'compound.book'
|
def macro(self):
|
||||||
macroName = 'text'
|
return book_template.macros['section']
|
||||||
partName = None # define in subclass
|
|
||||||
gridPattern = ['span-4']
|
|
||||||
|
|
||||||
def getResources(self):
|
|
||||||
result = []
|
|
||||||
res = self.adapted.getParts().get(self.partName) or []
|
|
||||||
for idx, r in enumerate(res):
|
|
||||||
result.append(standard.ResourceView(
|
|
||||||
r, self.request, parent=self, idx=idx))
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
class Headline(PagePart, standard.Header2):
|
class TopicView(Base, ConceptView):
|
||||||
|
|
||||||
macroName = 'headline'
|
@Lazy
|
||||||
|
def macro(self):
|
||||||
|
return book_template.macros['topic']
|
||||||
|
|
||||||
|
|
||||||
class MainText(PagePart, standard.BasePart):
|
|
||||||
|
|
||||||
partName = 'maintext'
|
|
||||||
|
|
||||||
|
|
||||||
class KeyQuestions(PagePart, standard.BasePart):
|
|
||||||
|
|
||||||
partName = 'keyquestions'
|
|
||||||
|
|
||||||
|
|
||||||
class Story(PagePart, standard.BasePart):
|
|
||||||
|
|
||||||
partName = 'story'
|
|
||||||
|
|
||||||
|
|
||||||
class Tip(PagePart, standard.BasePart):
|
|
||||||
|
|
||||||
partName = 'tip'
|
|
||||||
|
|
||||||
|
|
||||||
class UseCase(PagePart, standard.BasePart):
|
|
||||||
|
|
||||||
partName = 'usecase'
|
|
||||||
|
|
||||||
|
|
||||||
class Quote(PagePart, standard.BasePart):
|
|
||||||
|
|
||||||
partName = 'quote'
|
|
||||||
gridPattern = ['span-2 last']
|
|
||||||
|
|
|
@ -3,18 +3,6 @@
|
||||||
xmlns:browser="http://namespaces.zope.org/browser"
|
xmlns:browser="http://namespaces.zope.org/browser"
|
||||||
i18n_domain="loops">
|
i18n_domain="loops">
|
||||||
|
|
||||||
<!-- type adapters -->
|
|
||||||
|
|
||||||
<zope:adapter factory="loops.compound.book.base.Page"
|
|
||||||
provides="loops.compound.book.interfaces.IPage"
|
|
||||||
trusted="True" />
|
|
||||||
<zope:class class="loops.compound.book.base.Page">
|
|
||||||
<require permission="zope.View"
|
|
||||||
interface="loops.compound.book.interfaces.IPage" />
|
|
||||||
<require permission="zope.ManageContent"
|
|
||||||
set_schema="loops.compound.book.interfaces.IPage" />
|
|
||||||
</zope:class>
|
|
||||||
|
|
||||||
<!-- Views -->
|
<!-- Views -->
|
||||||
|
|
||||||
<zope:adapter
|
<zope:adapter
|
||||||
|
@ -22,7 +10,7 @@
|
||||||
for="loops.interfaces.IConcept
|
for="loops.interfaces.IConcept
|
||||||
loops.browser.skin.Lobo"
|
loops.browser.skin.Lobo"
|
||||||
provides="zope.interface.Interface"
|
provides="zope.interface.Interface"
|
||||||
factory="loops.compound.book.browser.BookOverview"
|
factory="loops.compound.book.browser.BookView"
|
||||||
permission="zope.View" />
|
permission="zope.View" />
|
||||||
|
|
||||||
<zope:adapter
|
<zope:adapter
|
||||||
|
@ -34,69 +22,11 @@
|
||||||
permission="zope.View" />
|
permission="zope.View" />
|
||||||
|
|
||||||
<zope:adapter
|
<zope:adapter
|
||||||
name="page_layout"
|
name="book_topic_view"
|
||||||
for="loops.interfaces.IConcept
|
for="loops.interfaces.IConcept
|
||||||
loops.browser.skin.Lobo"
|
loops.browser.skin.Lobo"
|
||||||
provides="zope.interface.Interface"
|
provides="zope.interface.Interface"
|
||||||
factory="loops.compound.book.browser.PageLayout"
|
factory="loops.compound.book.browser.TopicView"
|
||||||
permission="zope.View" />
|
|
||||||
|
|
||||||
<!-- parts -->
|
|
||||||
|
|
||||||
<zope:adapter
|
|
||||||
name="lobo_headline"
|
|
||||||
for="loops.compound.book.interfaces.IPage
|
|
||||||
loops.browser.skin.Lobo"
|
|
||||||
provides="zope.interface.Interface"
|
|
||||||
factory="loops.compound.book.browser.Headline"
|
|
||||||
permission="zope.View" />
|
|
||||||
|
|
||||||
<zope:adapter
|
|
||||||
name="lobo_keyquestions"
|
|
||||||
for="loops.compound.book.interfaces.IPage
|
|
||||||
loops.browser.skin.Lobo"
|
|
||||||
provides="zope.interface.Interface"
|
|
||||||
factory="loops.compound.book.browser.KeyQuestions"
|
|
||||||
permission="zope.View" />
|
|
||||||
|
|
||||||
<zope:adapter
|
|
||||||
name="lobo_maintext"
|
|
||||||
for="loops.compound.book.interfaces.IPage
|
|
||||||
loops.browser.skin.Lobo"
|
|
||||||
provides="zope.interface.Interface"
|
|
||||||
factory="loops.compound.book.browser.MainText"
|
|
||||||
permission="zope.View" />
|
|
||||||
|
|
||||||
<zope:adapter
|
|
||||||
name="lobo_story"
|
|
||||||
for="loops.compound.book.interfaces.IPage
|
|
||||||
loops.browser.skin.Lobo"
|
|
||||||
provides="zope.interface.Interface"
|
|
||||||
factory="loops.compound.book.browser.Story"
|
|
||||||
permission="zope.View" />
|
|
||||||
|
|
||||||
<zope:adapter
|
|
||||||
name="lobo_tip"
|
|
||||||
for="loops.compound.book.interfaces.IPage
|
|
||||||
loops.browser.skin.Lobo"
|
|
||||||
provides="zope.interface.Interface"
|
|
||||||
factory="loops.compound.book.browser.Tip"
|
|
||||||
permission="zope.View" />
|
|
||||||
|
|
||||||
<zope:adapter
|
|
||||||
name="lobo_usecase"
|
|
||||||
for="loops.compound.book.interfaces.IPage
|
|
||||||
loops.browser.skin.Lobo"
|
|
||||||
provides="zope.interface.Interface"
|
|
||||||
factory="loops.compound.book.browser.UseCase"
|
|
||||||
permission="zope.View" />
|
|
||||||
|
|
||||||
<zope:adapter
|
|
||||||
name="lobo_quote"
|
|
||||||
for="loops.compound.book.interfaces.IPage
|
|
||||||
loops.browser.skin.Lobo"
|
|
||||||
provides="zope.interface.Interface"
|
|
||||||
factory="loops.compound.book.browser.Quote"
|
|
||||||
permission="zope.View" />
|
permission="zope.View" />
|
||||||
|
|
||||||
</configure>
|
</configure>
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
#
|
|
||||||
# Copyright (c) 2012 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
|
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
|
||||||
Books, sections, pages...
|
|
||||||
"""
|
|
||||||
|
|
||||||
from zope.interface import Interface, Attribute
|
|
||||||
from zope import interface, component, schema
|
|
||||||
|
|
||||||
from loops.compound.interfaces import ICompound
|
|
||||||
from loops.util import _
|
|
||||||
|
|
||||||
|
|
||||||
class IPage(ICompound):
|
|
||||||
|
|
||||||
pass
|
|
|
@ -1,14 +1,15 @@
|
||||||
type(u'documenttype', u'Dokumentenart', options=u'qualifier:assign',
|
type(u'documenttype', u'Dokumentenart', options=u'qualifier:assign',
|
||||||
|
typeInterface=u'loops.interfaces.IOptions',
|
||||||
viewName=u'')
|
viewName=u'')
|
||||||
|
|
||||||
# book types
|
# book types
|
||||||
type(u'book', u'Buch', viewName=u'book_overview', typeInterface=u'',
|
type(u'book', u'Buch', viewName=u'book_overview', typeInterface=u'',
|
||||||
options=u'action.portlet:create_subtype,edit_concept')
|
options=u'action.portlet:create_subtype,edit_concept')
|
||||||
#type(u'page', u'Seite', viewName=u'page_layout',
|
|
||||||
# typeInterface=u'loops.compound.book.interfaces.IPage',
|
|
||||||
# options=u'action.portlet:edit_concept')
|
|
||||||
type(u'section', u'Kapitel', viewName=u'section_view', typeInterface=u'',
|
type(u'section', u'Kapitel', viewName=u'section_view', typeInterface=u'',
|
||||||
options=u'action.portlet:create_subtype,edit_concept')
|
options=u'action.portlet:create_subtype,edit_concept')
|
||||||
|
#type(u'topic', u'Thema', viewName=u'book_topic_view',
|
||||||
|
# typeInterface=u'loops.knowledge.interfaces.ITopic',
|
||||||
|
# options=u'action.portlet:create_topic,edit_topic')
|
||||||
|
|
||||||
concept(u'system', u'System', u'domain')
|
concept(u'system', u'System', u'domain')
|
||||||
|
|
||||||
|
@ -26,8 +27,8 @@ concept(u'quote', u'Zitat', u'documenttype')
|
||||||
concept(u'story', u'Geschichte', u'documenttype')
|
concept(u'story', u'Geschichte', u'documenttype')
|
||||||
concept(u'tip', u'Tipp', u'documenttype')
|
concept(u'tip', u'Tipp', u'documenttype')
|
||||||
concept(u'usecase', u'Fallbeispiel', u'documenttype')
|
concept(u'usecase', u'Fallbeispiel', u'documenttype')
|
||||||
|
concept(u'warning', u'Warnung', u'documenttype')
|
||||||
|
|
||||||
# book structure
|
# book structure
|
||||||
child(u'book', u'section', u'issubtype', usePredicate=u'ispartof')
|
child(u'book', u'section', u'issubtype', usePredicate=u'ispartof')
|
||||||
child(u'section', u'section', u'issubtype', usePredicate=u'ispartof')
|
child(u'section', u'section', u'issubtype', usePredicate=u'ispartof')
|
||||||
#child(u'section', u'page', u'issubtype', usePredicate=u'ispartof')
|
|
||||||
|
|
|
@ -1,14 +1,22 @@
|
||||||
<html i18n:domain="loops">
|
<html i18n:domain="loops">
|
||||||
|
|
||||||
|
|
||||||
<metal:book define-macro="book">
|
<metal:children define-macro="children">
|
||||||
<metal:info use-macro="view/concept_macros/concepttitle" />
|
<div tal:repeat="related item/children"
|
||||||
<div tal:repeat="related item/children">
|
tal:define="level python:level + 1"
|
||||||
|
tal:attributes="class string:content-$level">
|
||||||
<h3>
|
<h3>
|
||||||
<a tal:attributes="href python:view.getUrlForTarget(related)"
|
<a tal:attributes="href python:view.getUrlForTarget(related)"
|
||||||
tal:content="related/title" /></h3>
|
tal:content="related/title" />
|
||||||
|
</h3>
|
||||||
<div tal:content="structure related/renderedDescription" />
|
<div tal:content="structure related/renderedDescription" />
|
||||||
|
<!-- TODO: show next level (+/-) -->
|
||||||
</div>
|
</div>
|
||||||
|
</metal:children>
|
||||||
|
|
||||||
|
<metal:book define-macro="book">
|
||||||
|
<metal:info use-macro="view/concept_macros/concepttitle" />
|
||||||
|
<metal:info use-macro="item/book_macros/children" />
|
||||||
</metal:book>
|
</metal:book>
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,11 +40,23 @@
|
||||||
</div>
|
</div>
|
||||||
</metal:navigation>
|
</metal:navigation>
|
||||||
<metal:info use-macro="view/concept_macros/concepttitle" />
|
<metal:info use-macro="view/concept_macros/concepttitle" />
|
||||||
<div tal:repeat="related item/textResources">
|
<metal:info use-macro="item/book_macros/children" />
|
||||||
|
<metal:text define-macro="textresources">
|
||||||
|
<div style="clear: both"
|
||||||
|
tal:repeat="related item/textResources">
|
||||||
<div class="span-4">
|
<div class="span-4">
|
||||||
<div tal:attributes="class python:
|
<div metal:define-macro="default_text"
|
||||||
item.getCssClassForResource(related)"
|
tal:attributes="class python:
|
||||||
tal:content="structure related/render" />
|
item.getCssClassForResource(related)">
|
||||||
|
<h3 tal:define="ttitle python:item.getTitleForResource(related)"
|
||||||
|
tal:condition="ttitle"
|
||||||
|
tal:content="ttitle" />
|
||||||
|
<img class="flow-left" style="padding-top: 5px"
|
||||||
|
tal:define="icon python:item.getIconForResource(related)"
|
||||||
|
tal:condition="icon"
|
||||||
|
tal:attributes="src icon" />
|
||||||
|
<span tal:content="structure related/render" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="span-2 last" style="padding-top: 0.4em">
|
<div class="span-2 last" style="padding-top: 0.4em">
|
||||||
<div class="object-actions" style="padding-top: 0"
|
<div class="object-actions" style="padding-top: 0"
|
||||||
|
@ -73,30 +93,40 @@
|
||||||
<img tal:attributes="src image/src;
|
<img tal:attributes="src image/src;
|
||||||
alt image/title" /></a>
|
alt image/title" /></a>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- TODO: links to files -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</metal:text>
|
||||||
<br style="clear: both" />
|
<br style="clear: both" />
|
||||||
<metal:navigation use-macro="item/book_macros/navigation" />
|
<metal:navigation use-macro="item/book_macros/navigation" />
|
||||||
<br />
|
<br />
|
||||||
</metal:section>
|
</metal:section>
|
||||||
|
|
||||||
|
|
||||||
<!-- layout part macros - obsolete? -->
|
<metal:topic define-macro="topic">
|
||||||
|
<metal:info use-macro="view/concept_macros/concepttitle" />
|
||||||
<metal:part define-macro="headline">
|
<h2 i18n:translate=""
|
||||||
<div tal:define="cell part/getView">
|
tal:condition="python: list(item.children())">Children</h2>
|
||||||
<metal:headline use-macro="item/macros/headline" />
|
<metal:children use-macro="item/book_macros/children" />
|
||||||
|
<h2 i18n:translate=""
|
||||||
|
tal:condition="item/textResources">Text Elements</h2>
|
||||||
|
<div>
|
||||||
|
<div tal:repeat="related item/textResources"
|
||||||
|
tal:define="level python:level + 1"
|
||||||
|
tal:attributes="class string:content-$level">
|
||||||
|
<h3>
|
||||||
|
<a tal:attributes="href python:view.getUrlForTarget(related.context)"
|
||||||
|
tal:content="related/title" />
|
||||||
|
</h3>
|
||||||
|
<div>
|
||||||
|
<div tal:replace="structure related/renderShortText" />
|
||||||
|
<p>
|
||||||
|
<a i18n:translate=""
|
||||||
|
tal:attributes="href python:view.getUrlForTarget(related.context)">
|
||||||
|
more...</a></p>
|
||||||
</div>
|
</div>
|
||||||
</metal:part>
|
|
||||||
|
|
||||||
<metal:part define-macro="text">
|
|
||||||
<tal:cell repeat="cell part/getResources">
|
|
||||||
<div tal:attributes="class cell/cssClass">
|
|
||||||
<h3 tal:content="cell/title" />
|
|
||||||
<span tal:content="structure cell/view/render" />
|
|
||||||
</div>
|
</div>
|
||||||
</tal:cell>
|
</metal:topic>
|
||||||
</metal:part>
|
|
||||||
|
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2011 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,14 +19,12 @@
|
||||||
"""
|
"""
|
||||||
Definition of basic view classes and other browser related stuff for the
|
Definition of basic view classes and other browser related stuff for the
|
||||||
loops.expert package.
|
loops.expert package.
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from zope import interface, component
|
from zope import interface, component
|
||||||
from zope.app.pagetemplate import ViewPageTemplateFile
|
from zope.app.pagetemplate import ViewPageTemplateFile
|
||||||
from zope.cachedescriptors.property import Lazy
|
from zope.cachedescriptors.property import Lazy
|
||||||
from zope.traversing.api import getName, getParent
|
from zope.traversing.api import getName, getParent, traverse
|
||||||
|
|
||||||
from cybertools.browser.form import FormController
|
from cybertools.browser.form import FormController
|
||||||
from cybertools.stateful.interfaces import IStateful, IStatesDefinition
|
from cybertools.stateful.interfaces import IStateful, IStatesDefinition
|
||||||
|
@ -150,12 +148,16 @@ class Search(ConceptView):
|
||||||
if not isinstance(types, (list, tuple)):
|
if not isinstance(types, (list, tuple)):
|
||||||
types = [types]
|
types = [types]
|
||||||
for type in types:
|
for type in types:
|
||||||
|
site = self.loopsRoot
|
||||||
|
if type.startswith('/'):
|
||||||
|
parts = type.split(':')
|
||||||
|
site = traverse(self.loopsRoot, parts[0], site)
|
||||||
result = self.executeQuery(title=title or None, type=type,
|
result = self.executeQuery(title=title or None, type=type,
|
||||||
exclude=('hidden',))
|
exclude=('hidden',))
|
||||||
fv = FilterView(self.context, self.request)
|
fv = FilterView(self.context, self.request)
|
||||||
result = fv.apply(result)
|
result = fv.apply(result)
|
||||||
for o in result:
|
for o in result:
|
||||||
if o.getLoopsRoot() == self.loopsRoot:
|
if o.getLoopsRoot() == site:
|
||||||
adObj = adapted(o, self.languageInfo)
|
adObj = adapted(o, self.languageInfo)
|
||||||
if filterMethod is not None and not filterMethod(adObj):
|
if filterMethod is not None and not filterMethod(adObj):
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -18,8 +18,6 @@
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Query concepts management stuff.
|
Query concepts management stuff.
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from BTrees.IOBTree import IOBTree
|
from BTrees.IOBTree import IOBTree
|
||||||
|
@ -29,6 +27,7 @@ from zope.interface import Interface, Attribute, implements
|
||||||
from zope.app.catalog.interfaces import ICatalog
|
from zope.app.catalog.interfaces import ICatalog
|
||||||
from zope.app.intid.interfaces import IIntIds
|
from zope.app.intid.interfaces import IIntIds
|
||||||
from zope.cachedescriptors.property import Lazy
|
from zope.cachedescriptors.property import Lazy
|
||||||
|
from zope.traversing.api import traverse
|
||||||
|
|
||||||
from cybertools.typology.interfaces import IType
|
from cybertools.typology.interfaces import IType
|
||||||
from loops.common import AdapterBase
|
from loops.common import AdapterBase
|
||||||
|
@ -66,6 +65,11 @@ class BaseQuery(object):
|
||||||
return self.context.context.getLoopsRoot()
|
return self.context.context.getLoopsRoot()
|
||||||
|
|
||||||
def queryConcepts(self, title=None, type=None, **kw):
|
def queryConcepts(self, title=None, type=None, **kw):
|
||||||
|
site = self.loopsRoot
|
||||||
|
if type.startswith('/'):
|
||||||
|
parts = type.split(':')
|
||||||
|
site = traverse(self.loopsRoot, parts[0], site)
|
||||||
|
type = 'loops:' + ':'.join(parts[1:])
|
||||||
if type.endswith('*'):
|
if type.endswith('*'):
|
||||||
start = type[:-1]
|
start = type[:-1]
|
||||||
end = start + '\x7f'
|
end = start + '\x7f'
|
||||||
|
@ -76,7 +80,7 @@ class BaseQuery(object):
|
||||||
result = cat.searchResults(loops_type=(start, end), loops_title=title)
|
result = cat.searchResults(loops_type=(start, end), loops_title=title)
|
||||||
else:
|
else:
|
||||||
result = cat.searchResults(loops_type=(start, end))
|
result = cat.searchResults(loops_type=(start, end))
|
||||||
result = set(r for r in result if r.getLoopsRoot() == self.loopsRoot
|
result = set(r for r in result if r.getLoopsRoot() == site
|
||||||
and canListObject(r))
|
and canListObject(r))
|
||||||
if 'exclude' in kw:
|
if 'exclude' in kw:
|
||||||
r1 = set()
|
r1 = set()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2012 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -78,28 +78,28 @@ class OfficeFile(ExternalFileAdapter):
|
||||||
@Lazy
|
@Lazy
|
||||||
def docPropertyDom(self):
|
def docPropertyDom(self):
|
||||||
fn = self.docFilename
|
fn = self.docFilename
|
||||||
dummy = dict(core=[], custom=[])
|
result = dict(core=[], custom=[])
|
||||||
root, ext = os.path.splitext(fn)
|
root, ext = os.path.splitext(fn)
|
||||||
if not ext.lower() in self.fileExtensions:
|
if not ext.lower() in self.fileExtensions:
|
||||||
return dummy
|
return result
|
||||||
try:
|
try:
|
||||||
zf = ZipFile(fn, 'r')
|
zf = ZipFile(fn, 'r')
|
||||||
except IOError, e:
|
except IOError, e:
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
self.logger.warn(e)
|
self.logger.warn(e)
|
||||||
return dummy
|
return result
|
||||||
if self.corePropFileName not in zf.namelist():
|
if self.corePropFileName not in zf.namelist():
|
||||||
self.logger.warn('Core properties not found in file %s.' %
|
self.logger.warn('Core properties not found in file %s.' %
|
||||||
self.externalAddress)
|
self.externalAddress)
|
||||||
|
else:
|
||||||
|
result['core'] = etree.fromstring(zf.read(self.corePropFileName))
|
||||||
if self.propFileName not in zf.namelist():
|
if self.propFileName not in zf.namelist():
|
||||||
self.logger.warn('Custom properties not found in file %s.' %
|
self.logger.warn('Custom properties not found in file %s.' %
|
||||||
self.externalAddress)
|
self.externalAddress)
|
||||||
propsXml = zf.read(self.propFileName)
|
else:
|
||||||
corePropsXml = zf.read(self.corePropFileName)
|
result['custom'] = etree.fromstring(zf.read(self.propFileName))
|
||||||
# TODO: read core.xml, return both trees in dictionary
|
|
||||||
zf.close()
|
zf.close()
|
||||||
return {'custom': etree.fromstring(propsXml),
|
return result
|
||||||
'core': etree.fromstring(corePropsXml)}
|
|
||||||
|
|
||||||
def getDocProperty(self, pname):
|
def getDocProperty(self, pname):
|
||||||
for p in self.docPropertyDom['custom']:
|
for p in self.docPropertyDom['custom']:
|
||||||
|
|
|
@ -690,9 +690,21 @@ class IIndexAttributes(Interface):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# reusable interface elements
|
||||||
|
|
||||||
|
class IOptions(Interface):
|
||||||
|
|
||||||
|
options = schema.List(
|
||||||
|
title=_(u'Options'),
|
||||||
|
description=_(u'Additional settings.'),
|
||||||
|
value_type=schema.TextLine(),
|
||||||
|
default=[],
|
||||||
|
required=False)
|
||||||
|
|
||||||
|
|
||||||
# types stuff
|
# types stuff
|
||||||
|
|
||||||
class ITypeConcept(IConceptSchema, ILoopsAdapter):
|
class ITypeConcept(IConceptSchema, ILoopsAdapter, IOptions):
|
||||||
""" Concepts of type 'type' should be adaptable to this interface.
|
""" Concepts of type 'type' should be adaptable to this interface.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -725,13 +737,6 @@ class ITypeConcept(IConceptSchema, ILoopsAdapter):
|
||||||
default=u'',
|
default=u'',
|
||||||
required=False)
|
required=False)
|
||||||
|
|
||||||
options = schema.List(
|
|
||||||
title=_(u'Options'),
|
|
||||||
description=_(u'Additional settings.'),
|
|
||||||
value_type=schema.TextLine(),
|
|
||||||
default=[],
|
|
||||||
required=False)
|
|
||||||
|
|
||||||
# storage = schema.Choice()
|
# storage = schema.Choice()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ from zope.component import adapts
|
||||||
from zope.interface import implementer, implements
|
from zope.interface import implementer, implements
|
||||||
|
|
||||||
from loops.common import AdapterBase
|
from loops.common import AdapterBase
|
||||||
|
from loops.interfaces import IConcept
|
||||||
from loops.knowledge.qualification.interfaces import ICompetence
|
from loops.knowledge.qualification.interfaces import ICompetence
|
||||||
from loops.type import TypeInterfaceSourceList
|
from loops.type import TypeInterfaceSourceList
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,14 @@
|
||||||
i18n_domain="loops">
|
i18n_domain="loops">
|
||||||
|
|
||||||
<zope:adapter
|
<zope:adapter
|
||||||
factory="loops.knowledge.qualification.base.Competence" />
|
factory="loops.knowledge.qualification.base.Competence"
|
||||||
|
trusted="True" />
|
||||||
|
<zope:class class="loops.knowledge.qualification.base.Competence">
|
||||||
|
<require permission="zope.View"
|
||||||
|
interface="loops.knowledge.qualification.interfaces.ICompetence" />
|
||||||
|
<require permission="zope.ManageContent"
|
||||||
|
set_schema="loops.knowledge.qualification.interfaces.ICompetence" />
|
||||||
|
</zope:class>
|
||||||
|
|
||||||
<!-- views -->
|
<!-- views -->
|
||||||
|
|
||||||
|
|
|
@ -23,11 +23,11 @@ Interfaces for knowledge management and elearning with loops.
|
||||||
from zope.interface import Interface, Attribute
|
from zope.interface import Interface, Attribute
|
||||||
from zope import interface, component, schema
|
from zope import interface, component, schema
|
||||||
|
|
||||||
from loops.interfaces import IConceptSchema
|
from loops.interfaces import IConceptSchema, ILoopsAdapter
|
||||||
from loops.util import _
|
from loops.util import _
|
||||||
|
|
||||||
|
|
||||||
class ICompetence(IConceptSchema):
|
class ICompetence(ILoopsAdapter):
|
||||||
""" The competence of a person.
|
""" The competence of a person.
|
||||||
|
|
||||||
Maybe assigned to the person via a 'knows' relation or
|
Maybe assigned to the person via a 'knows' relation or
|
||||||
|
|
|
@ -62,7 +62,7 @@ class QuestionGroup(AdapterBase, QuestionGroup):
|
||||||
|
|
||||||
_contextAttributes = list(IQuestionGroup)
|
_contextAttributes = list(IQuestionGroup)
|
||||||
_adapterAttributes = AdapterBase._adapterAttributes + (
|
_adapterAttributes = AdapterBase._adapterAttributes + (
|
||||||
'questionnaire', 'questions', 'feedbackItems',)
|
'questionnaire', 'questions', 'feedbackItems')
|
||||||
_noexportAttributes = _adapterAttributes
|
_noexportAttributes = _adapterAttributes
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -109,9 +109,6 @@ class Question(AdapterBase, Question):
|
||||||
def questionnaire(self):
|
def questionnaire(self):
|
||||||
return self.questionGroup.questionnaire
|
return self.questionGroup.questionnaire
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return hash(self.context)
|
|
||||||
|
|
||||||
|
|
||||||
class FeedbackItem(AdapterBase, FeedbackItem):
|
class FeedbackItem(AdapterBase, FeedbackItem):
|
||||||
|
|
||||||
|
@ -125,4 +122,3 @@ class FeedbackItem(AdapterBase, FeedbackItem):
|
||||||
@property
|
@property
|
||||||
def text(self):
|
def text(self):
|
||||||
return self.context.description
|
return self.context.description
|
||||||
|
|
||||||
|
|
|
@ -21,26 +21,40 @@ Definition of view classes and other browser related stuff for
|
||||||
surveys and self-assessments.
|
surveys and self-assessments.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import csv
|
||||||
|
from cStringIO import StringIO
|
||||||
from zope.app.pagetemplate import ViewPageTemplateFile
|
from zope.app.pagetemplate import ViewPageTemplateFile
|
||||||
from zope.cachedescriptors.property import Lazy
|
from zope.cachedescriptors.property import Lazy
|
||||||
|
from zope.i18n import translate
|
||||||
|
|
||||||
from cybertools.knowledge.survey.questionnaire import Response
|
from cybertools.knowledge.survey.questionnaire import Response
|
||||||
|
from cybertools.util.date import formatTimeStamp
|
||||||
from loops.browser.concept import ConceptView
|
from loops.browser.concept import ConceptView
|
||||||
|
from loops.browser.node import NodeView
|
||||||
from loops.common import adapted
|
from loops.common import adapted
|
||||||
|
from loops.knowledge.survey.response import Responses
|
||||||
from loops.organize.party import getPersonForUser
|
from loops.organize.party import getPersonForUser
|
||||||
|
from loops.util import getObjectForUid
|
||||||
|
from loops.util import _
|
||||||
|
|
||||||
|
|
||||||
template = ViewPageTemplateFile('view_macros.pt')
|
template = ViewPageTemplateFile('view_macros.pt')
|
||||||
|
|
||||||
class SurveyView(ConceptView):
|
class SurveyView(ConceptView):
|
||||||
|
|
||||||
tabview = 'index.html'
|
|
||||||
data = None
|
data = None
|
||||||
|
errors = None
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def macro(self):
|
def macro(self):
|
||||||
|
self.registerDojo()
|
||||||
return template.macros['survey']
|
return template.macros['survey']
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def tabview(self):
|
||||||
|
if self.editable:
|
||||||
|
return 'index.html'
|
||||||
|
|
||||||
def results(self):
|
def results(self):
|
||||||
result = []
|
result = []
|
||||||
response = None
|
response = None
|
||||||
|
@ -52,22 +66,114 @@ class SurveyView(ConceptView):
|
||||||
if key.startswith('question_'):
|
if key.startswith('question_'):
|
||||||
uid = key[len('question_'):]
|
uid = key[len('question_'):]
|
||||||
question = adapted(self.getObjectForUid(uid))
|
question = adapted(self.getObjectForUid(uid))
|
||||||
|
if value != 'none':
|
||||||
value = int(value)
|
value = int(value)
|
||||||
self.data[uid] = value
|
self.data[uid] = value
|
||||||
response.values[question] = value
|
response.values[question] = value
|
||||||
# TODO: store self.data in track
|
Responses(self.context).save(self.data)
|
||||||
# else:
|
self.errors = self.check(response)
|
||||||
# get response from track
|
if self.errors:
|
||||||
|
return []
|
||||||
if response is not None:
|
if response is not None:
|
||||||
result = response.getGroupedResult()
|
result = response.getGroupedResult()
|
||||||
return [dict(category=r[0].title, text=r[1].text,
|
return [dict(category=r[0].title, text=r[1].text,
|
||||||
score=int(round(r[2] * 100)))
|
score=int(round(r[2] * 100)))
|
||||||
for r in result]
|
for r in result]
|
||||||
|
|
||||||
def getValues(self, question):
|
def check(self, response):
|
||||||
setting = 0
|
errors = []
|
||||||
if self.data is not None:
|
values = response.values
|
||||||
setting = self.data.get(question.uid) or 0
|
for qu in self.adapted.questions:
|
||||||
return [dict(value=i, checked=(i == setting))
|
if qu.required and qu not in values:
|
||||||
for i in range(question.answerRange)]
|
errors.append('Please answer the obligatory questions.')
|
||||||
|
break
|
||||||
|
qugroups = {}
|
||||||
|
for qugroup in self.adapted.questionGroups:
|
||||||
|
qugroups[qugroup] = 0
|
||||||
|
for qu in values:
|
||||||
|
qugroups[qu.questionGroup] += 1
|
||||||
|
for qugroup, count in qugroups.items():
|
||||||
|
minAnswers = qugroup.minAnswers
|
||||||
|
if minAnswers in (u'', None):
|
||||||
|
minAnswers = len(qugroup.questions)
|
||||||
|
if count < minAnswers:
|
||||||
|
errors.append('Please answer the minimum number of questions.')
|
||||||
|
break
|
||||||
|
return errors
|
||||||
|
|
||||||
|
def getInfoText(self, qugroup):
|
||||||
|
lang = self.languageInfo.language
|
||||||
|
text = qugroup.description
|
||||||
|
info = None
|
||||||
|
if qugroup.minAnswers in (u'', None):
|
||||||
|
info = translate(_(u'Please answer all questions.'), target_language=lang)
|
||||||
|
elif qugroup.minAnswers > 0:
|
||||||
|
info = translate(_(u'Please answer at least $minAnswers questions.',
|
||||||
|
mapping=dict(minAnswers=qugroup.minAnswers)),
|
||||||
|
target_language=lang)
|
||||||
|
if info:
|
||||||
|
text = u'<i>%s</i><br />(%s)' % (text, info)
|
||||||
|
return text
|
||||||
|
|
||||||
|
def getValues(self, question):
|
||||||
|
setting = None
|
||||||
|
if self.data is None:
|
||||||
|
self.data = Responses(self.context).load()
|
||||||
|
if self.data:
|
||||||
|
setting = self.data.get(question.uid)
|
||||||
|
noAnswer = [dict(value='none', checked=(setting == None),
|
||||||
|
radio=(not question.required))]
|
||||||
|
return noAnswer + [dict(value=i, checked=(setting == i), radio=True)
|
||||||
|
for i in reversed(range(question.answerRange))]
|
||||||
|
|
||||||
|
|
||||||
|
class SurveyCsvExport(NodeView):
|
||||||
|
|
||||||
|
encoding = 'ISO8859-15'
|
||||||
|
|
||||||
|
def encode(self, text):
|
||||||
|
text.encode(self.encoding)
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def questions(self):
|
||||||
|
result = []
|
||||||
|
for idx1, qug in enumerate(adapted(self.virtualTargetObject).questionGroups):
|
||||||
|
for idx2, qu in enumerate(qug.questions):
|
||||||
|
result.append((idx1, idx2, qug, qu))
|
||||||
|
return result
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def columns(self):
|
||||||
|
infoCols = ['Name', 'Timestamp']
|
||||||
|
dataCols = ['%02i-%02i' % (item[0], item[1]) for item in self.questions]
|
||||||
|
return infoCols + dataCols
|
||||||
|
|
||||||
|
def getRows(self):
|
||||||
|
for tr in Responses(self.virtualTargetObject).getAllTracks():
|
||||||
|
p = adapted(getObjectForUid(tr.userName))
|
||||||
|
name = p and p.title or u'???'
|
||||||
|
ts = formatTimeStamp(tr.timeStamp)
|
||||||
|
cells = [tr.data.get(qu.uid, -1)
|
||||||
|
for (idx1, idx2, qug, qu) in self.questions]
|
||||||
|
yield [name, ts] + cells
|
||||||
|
|
||||||
|
def __call__(self):
|
||||||
|
f = StringIO()
|
||||||
|
writer = csv.writer(f, delimiter=',')
|
||||||
|
writer.writerow(self.columns)
|
||||||
|
for row in self.getRows():
|
||||||
|
writer.writerow(row)
|
||||||
|
text = f.getvalue()
|
||||||
|
self.setDownloadHeader(text)
|
||||||
|
return text
|
||||||
|
|
||||||
|
def setDownloadHeader(self, text):
|
||||||
|
response = self.request.response
|
||||||
|
filename = 'survey_data.csv'
|
||||||
|
response.setHeader('Content-Disposition',
|
||||||
|
'attachment; filename=%s' % filename)
|
||||||
|
response.setHeader('Cache-Control', '')
|
||||||
|
response.setHeader('Pragma', '')
|
||||||
|
response.setHeader('Content-Length', len(text))
|
||||||
|
response.setHeader('Content-Type', 'text/csv')
|
||||||
|
|
||||||
|
|
|
@ -7,19 +7,56 @@
|
||||||
|
|
||||||
<zope:adapter
|
<zope:adapter
|
||||||
factory="loops.knowledge.survey.base.Questionnaire"
|
factory="loops.knowledge.survey.base.Questionnaire"
|
||||||
provides="loops.knowledge.survey.interfaces.IQuestionnaire" />
|
provides="loops.knowledge.survey.interfaces.IQuestionnaire"
|
||||||
|
trusted="True" />
|
||||||
|
<zope:class class="loops.knowledge.survey.base.Questionnaire">
|
||||||
|
<require permission="zope.View"
|
||||||
|
interface="loops.knowledge.survey.interfaces.IQuestionnaire" />
|
||||||
|
<require permission="zope.ManageContent"
|
||||||
|
set_schema="loops.knowledge.survey.interfaces.IQuestionnaire" />
|
||||||
|
</zope:class>
|
||||||
|
|
||||||
<zope:adapter
|
<zope:adapter
|
||||||
factory="loops.knowledge.survey.base.QuestionGroup"
|
factory="loops.knowledge.survey.base.QuestionGroup"
|
||||||
provides="loops.knowledge.survey.interfaces.IQuestionGroup" />
|
provides="loops.knowledge.survey.interfaces.IQuestionGroup"
|
||||||
|
trusted="True" />
|
||||||
|
<zope:class class="loops.knowledge.survey.base.QuestionGroup">
|
||||||
|
<require permission="zope.View"
|
||||||
|
interface="loops.knowledge.survey.interfaces.IQuestionGroup" />
|
||||||
|
<require permission="zope.ManageContent"
|
||||||
|
set_schema="loops.knowledge.survey.interfaces.IQuestionGroup" />
|
||||||
|
</zope:class>
|
||||||
|
|
||||||
<zope:adapter
|
<zope:adapter
|
||||||
factory="loops.knowledge.survey.base.Question"
|
factory="loops.knowledge.survey.base.Question"
|
||||||
provides="loops.knowledge.survey.interfaces.IQuestion" />
|
provides="loops.knowledge.survey.interfaces.IQuestion"
|
||||||
|
trusted="True" />
|
||||||
|
<zope:class class="loops.knowledge.survey.base.Question">
|
||||||
|
<require permission="zope.View"
|
||||||
|
interface="loops.knowledge.survey.interfaces.IQuestion" />
|
||||||
|
<require permission="zope.ManageContent"
|
||||||
|
set_schema="loops.knowledge.survey.interfaces.IQuestion" />
|
||||||
|
</zope:class>
|
||||||
|
|
||||||
<zope:adapter
|
<zope:adapter
|
||||||
factory="loops.knowledge.survey.base.FeedbackItem"
|
factory="loops.knowledge.survey.base.FeedbackItem"
|
||||||
provides="loops.knowledge.survey.interfaces.IFeedbackItem" />
|
provides="loops.knowledge.survey.interfaces.IFeedbackItem"
|
||||||
|
trusted="True" />
|
||||||
|
<zope:class class="loops.knowledge.survey.base.FeedbackItem">
|
||||||
|
<require permission="zope.View"
|
||||||
|
interface="loops.knowledge.survey.interfaces.IFeedbackItem" />
|
||||||
|
<require permission="zope.ManageContent"
|
||||||
|
set_schema="loops.knowledge.survey.interfaces.IFeedbackItem" />
|
||||||
|
</zope:class>
|
||||||
|
|
||||||
|
<!-- track -->
|
||||||
|
|
||||||
|
<zope:class class="loops.knowledge.survey.response.Response">
|
||||||
|
<require permission="zope.View"
|
||||||
|
interface="cybertools.tracking.interfaces.ITrack" />
|
||||||
|
<require permission="zope.ManageContent"
|
||||||
|
set_schema="cybertools.tracking.interfaces.ITrack" />
|
||||||
|
</zope:class>
|
||||||
|
|
||||||
<!-- views -->
|
<!-- views -->
|
||||||
|
|
||||||
|
@ -31,4 +68,9 @@
|
||||||
factory="loops.knowledge.survey.browser.SurveyView"
|
factory="loops.knowledge.survey.browser.SurveyView"
|
||||||
permission="zope.View" />
|
permission="zope.View" />
|
||||||
|
|
||||||
|
<browser:page name="survey_data.csv"
|
||||||
|
for="loops.interfaces.IView"
|
||||||
|
class="loops.knowledge.survey.browser.SurveyCsvExport"
|
||||||
|
permission="zope.View" />
|
||||||
|
|
||||||
</configure>
|
</configure>
|
||||||
|
|
|
@ -24,7 +24,7 @@ from zope.interface import Interface, Attribute
|
||||||
from zope import interface, component, schema
|
from zope import interface, component, schema
|
||||||
|
|
||||||
from cybertools.knowledge.survey import interfaces
|
from cybertools.knowledge.survey import interfaces
|
||||||
from loops.interfaces import IConceptSchema
|
from loops.interfaces import IConceptSchema, ILoopsAdapter
|
||||||
from loops.util import _
|
from loops.util import _
|
||||||
|
|
||||||
|
|
||||||
|
@ -38,16 +38,43 @@ class IQuestionnaire(IConceptSchema, interfaces.IQuestionnaire):
|
||||||
default=4,
|
default=4,
|
||||||
required=True)
|
required=True)
|
||||||
|
|
||||||
|
feedbackHeader = schema.Text(
|
||||||
|
title=_(u'Feedback Header'),
|
||||||
|
description=_(u'Text that will appear at the top of the feedback page.'),
|
||||||
|
default=u'',
|
||||||
|
missing_value=u'',
|
||||||
|
required=False)
|
||||||
|
|
||||||
|
feedbackFooter = schema.Text(
|
||||||
|
title=_(u'Feedback Footer'),
|
||||||
|
description=_(u'Text that will appear at the end of the feedback page.'),
|
||||||
|
default=u'',
|
||||||
|
missing_value=u'',
|
||||||
|
required=False)
|
||||||
|
|
||||||
|
|
||||||
class IQuestionGroup(IConceptSchema, interfaces.IQuestionGroup):
|
class IQuestionGroup(IConceptSchema, interfaces.IQuestionGroup):
|
||||||
""" A group of questions within a questionnaire.
|
""" A group of questions within a questionnaire.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
minAnswers = schema.Int(
|
||||||
|
title=_(u'Minimum Number of Answers'),
|
||||||
|
description=_(u'Minumum number of questions that have to be answered. '
|
||||||
|
'Empty means all questions have to be answered.'),
|
||||||
|
default=None,
|
||||||
|
required=False)
|
||||||
|
|
||||||
|
|
||||||
class IQuestion(IConceptSchema, interfaces.IQuestion):
|
class IQuestion(IConceptSchema, interfaces.IQuestion):
|
||||||
""" A single question within a questionnaire.
|
""" A single question within a questionnaire.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
required = schema.Bool(
|
||||||
|
title=_(u'Required'),
|
||||||
|
description=_(u'Question must be answered.'),
|
||||||
|
default=False,
|
||||||
|
required=False)
|
||||||
|
|
||||||
revertAnswerOptions = schema.Bool(
|
revertAnswerOptions = schema.Bool(
|
||||||
title=_(u'Negative'),
|
title=_(u'Negative'),
|
||||||
description=_(u'Value inversion: High selection means low value.'),
|
description=_(u'Value inversion: High selection means low value.'),
|
||||||
|
|
|
@ -29,20 +29,34 @@ from loops.knowledge.survey.interfaces import IResponse, IResponses
|
||||||
from loops.organize.tracking.base import BaseRecordManager
|
from loops.organize.tracking.base import BaseRecordManager
|
||||||
|
|
||||||
|
|
||||||
|
class Responses(BaseRecordManager):
|
||||||
|
|
||||||
|
implements(IResponses)
|
||||||
|
|
||||||
|
storageName = 'survey_responses'
|
||||||
|
|
||||||
|
def __init__(self, context):
|
||||||
|
self.context = context
|
||||||
|
|
||||||
|
def save(self, data):
|
||||||
|
if self.personId:
|
||||||
|
self.storage.saveUserTrack(self.uid, 0, self.personId, data,
|
||||||
|
update=True, overwrite=True)
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
if self.personId:
|
||||||
|
tracks = self.storage.getUserTracks(self.uid, 0, self.personId)
|
||||||
|
if tracks:
|
||||||
|
return tracks[0].data
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def getAllTracks(self):
|
||||||
|
return self.storage.query(taskId=self.uid)
|
||||||
|
|
||||||
|
|
||||||
class Response(Track):
|
class Response(Track):
|
||||||
""" A survey response.
|
|
||||||
"""
|
|
||||||
|
|
||||||
implements(IResponse)
|
implements(IResponse)
|
||||||
|
|
||||||
typeName = 'Response'
|
typeName = 'Response'
|
||||||
typeInterface = IResponse
|
|
||||||
|
|
||||||
|
|
||||||
class Responses(BaseRecordManager):
|
|
||||||
""" A tracking storage adapter for survey responses.
|
|
||||||
"""
|
|
||||||
|
|
||||||
implements(IResponses)
|
|
||||||
adapts(ITrackingStorage)
|
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,18 @@
|
||||||
|
|
||||||
|
|
||||||
<metal:block define-macro="survey"
|
<metal:block define-macro="survey"
|
||||||
tal:define="feedback item/results">
|
tal:define="feedback item/results;
|
||||||
<metal:title use-macro="item/conceptMacros/concepttitle" />
|
errors item/errors">
|
||||||
|
<metal:title use-macro="item/conceptMacros/concepttitle_only" />
|
||||||
|
<tal:description condition="not:feedback">
|
||||||
|
<metal:title use-macro="item/conceptMacros/conceptdescription" />
|
||||||
|
</tal:description>
|
||||||
<div tal:condition="feedback">
|
<div tal:condition="feedback">
|
||||||
<h3 i18n:translate="">Feedback</h3>
|
<h3 i18n:translate="">Feedback</h3>
|
||||||
<table>
|
<div tal:define="header item/adapted/feedbackHeader"
|
||||||
|
tal:condition="header"
|
||||||
|
tal:content="structure python:item.renderText(header, 'text/restructured')" />
|
||||||
|
<table class="listing">
|
||||||
<tr>
|
<tr>
|
||||||
<th i18n:translate="">Category</th>
|
<th i18n:translate="">Category</th>
|
||||||
<th i18n:translate="">Response</th>
|
<th i18n:translate="">Response</th>
|
||||||
|
@ -19,42 +26,74 @@
|
||||||
<td tal:content="fbitem/score" />
|
<td tal:content="fbitem/score" />
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
<div class="button" id="show_questionnaire">
|
||||||
|
<a href="" onclick="back(); return false"
|
||||||
|
i18n:translate="">
|
||||||
|
Back to Questionnaire</a>
|
||||||
<br />
|
<br />
|
||||||
</div>
|
</div>
|
||||||
|
<div tal:define="footer item/adapted/feedbackFooter"
|
||||||
|
tal:condition="footer"
|
||||||
|
tal:content="structure python:item.renderText(footer, 'text/restructured')" />
|
||||||
|
</div>
|
||||||
|
<div id="questionnaire"
|
||||||
|
tal:condition="not:feedback">
|
||||||
<h3 i18n:translate="">Questionnaire</h3>
|
<h3 i18n:translate="">Questionnaire</h3>
|
||||||
|
<div class="error"
|
||||||
|
tal:condition="errors">
|
||||||
|
<div tal:repeat="error errors">
|
||||||
|
<span i18n:translate=""
|
||||||
|
tal:content="error" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<form method="post">
|
<form method="post">
|
||||||
<table>
|
<table class="listing">
|
||||||
<tr>
|
<tal:qugroup repeat="qugroup item/adapted/questionGroups">
|
||||||
<th></th>
|
<tr><td colspan="6"> </td></tr>
|
||||||
<th>
|
<tr class="vpad">
|
||||||
<table>
|
<td tal:define="infoText python:item.getInfoText(qugroup)">
|
||||||
<tr>
|
<b tal:content="qugroup/title" />
|
||||||
<td i18n:translate="">Does not apply</td>
|
<div class="infotext"
|
||||||
<td style="text-align: right"
|
tal:condition="infoText">
|
||||||
|
<span tal:content="structure infoText" />
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td style="text-align: center"
|
||||||
|
i18n:translate="">No answer</td>
|
||||||
|
<td colspan="2"
|
||||||
i18n:translate="">Fully applies</td>
|
i18n:translate="">Fully applies</td>
|
||||||
|
<td colspan="2"
|
||||||
|
style="text-align: right"
|
||||||
|
i18n:translate="">Does not apply</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
<tr class="vpad"
|
||||||
</th>
|
tal:repeat="question qugroup/questions">
|
||||||
</tr>
|
|
||||||
<tr tal:repeat="question item/adapted/questions">
|
|
||||||
<td tal:content="question/text" />
|
<td tal:content="question/text" />
|
||||||
<td style="white-space: nowrap">
|
<td style="white-space: nowrap; text-align: center"
|
||||||
<span tal:repeat="value python:item.getValues(question)">
|
tal:repeat="value python:item.getValues(question)">
|
||||||
<input type="radio"
|
<input type="radio"
|
||||||
i18n:attributes="title"
|
i18n:attributes="title"
|
||||||
|
tal:condition="value/radio"
|
||||||
tal:attributes="
|
tal:attributes="
|
||||||
name string:question_${question/uid};
|
name string:question_${question/uid};
|
||||||
value value/value;
|
value value/value;
|
||||||
checked value/checked;
|
checked value/checked;
|
||||||
title string:survey_value_${value/value}" />
|
title string:survey_value_${value/value}" />
|
||||||
|
<span tal:condition="not:value/radio"
|
||||||
|
title="Obligatory question, must be answered"
|
||||||
|
i18n:attributes="title">***
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
</tal:qugroup>
|
||||||
</table>
|
</table>
|
||||||
<br />
|
|
||||||
<input type="submit" name="submit" value="Evaluate Questionnaire"
|
<input type="submit" name="submit" value="Evaluate Questionnaire"
|
||||||
i18n:attributes="value" />
|
i18n:attributes="value" />
|
||||||
|
<input type="button" name="reset_responses" value="Reset Responses Entered"
|
||||||
|
i18n:attributes="value"
|
||||||
|
onclick="setRadioButtons('none'); return false" />
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
</metal:block>
|
</metal:block>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -158,4 +158,5 @@ class TargetLayoutInstance(NodeLayoutInstance):
|
||||||
target = self.viewAnnotations.get('target')
|
target = self.viewAnnotations.get('target')
|
||||||
if target is None:
|
if target is None:
|
||||||
target = adapted(self.context.target)
|
target = adapted(self.context.target)
|
||||||
|
#self.viewAnnotations['target'] = target # TODO: has to be tested!
|
||||||
return target
|
return target
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2009 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -18,8 +18,6 @@
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Base classes for layout-based views.
|
Base classes for layout-based views.
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from zope.app.security.interfaces import IUnauthenticatedPrincipal
|
from zope.app.security.interfaces import IUnauthenticatedPrincipal
|
||||||
|
@ -29,6 +27,7 @@ from zope.proxy import removeAllProxies
|
||||||
from zope.security.proxy import removeSecurityProxy
|
from zope.security.proxy import removeSecurityProxy
|
||||||
from zope.traversing.browser import absoluteURL
|
from zope.traversing.browser import absoluteURL
|
||||||
|
|
||||||
|
from cybertools.meta.interfaces import IOptions
|
||||||
from cybertools.util import format
|
from cybertools.util import format
|
||||||
from loops.common import adapted
|
from loops.common import adapted
|
||||||
from loops.i18n.browser import LanguageInfo
|
from loops.i18n.browser import LanguageInfo
|
||||||
|
@ -170,3 +169,7 @@ class BaseView(object):
|
||||||
def getMetaDescription(self):
|
def getMetaDescription(self):
|
||||||
return self.context.title
|
return self.context.title
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def globalOptions(self):
|
||||||
|
return IOptions(self.loopsRoot)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -18,8 +18,6 @@
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Layout node views.
|
Layout node views.
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from zope.app.security.interfaces import IUnauthenticatedPrincipal
|
from zope.app.security.interfaces import IUnauthenticatedPrincipal
|
||||||
|
@ -66,6 +64,9 @@ class LayoutNodeView(Page, BaseView):
|
||||||
if self.target is not None:
|
if self.target is not None:
|
||||||
targetView = component.getMultiAdapter((self.target, self.request),
|
targetView = component.getMultiAdapter((self.target, self.request),
|
||||||
name='layout')
|
name='layout')
|
||||||
return ' - '.join((self.context.title, targetView.title))
|
parts = [self.context.title, targetView.title]
|
||||||
else:
|
else:
|
||||||
return self.context.title
|
parts = [self.context.title]
|
||||||
|
if self.globalOptions('reverseHeadTitle'):
|
||||||
|
parts.reverse()
|
||||||
|
return ' - '.join(parts)
|
||||||
|
|
Binary file not shown.
|
@ -3,7 +3,7 @@ msgstr ""
|
||||||
|
|
||||||
"Project-Id-Version: 0.13.0\n"
|
"Project-Id-Version: 0.13.0\n"
|
||||||
"POT-Creation-Date: 2007-05-22 12:00 CET\n"
|
"POT-Creation-Date: 2007-05-22 12:00 CET\n"
|
||||||
"PO-Revision-Date: 2013-03-07 12:00 CET\n"
|
"PO-Revision-Date: 2013-07-15 12:00 CET\n"
|
||||||
"Last-Translator: Helmut Merz <helmutm@cy55.de>\n"
|
"Last-Translator: Helmut Merz <helmutm@cy55.de>\n"
|
||||||
"Language-Team: loops developers <helmutm@cy55.de>\n"
|
"Language-Team: loops developers <helmutm@cy55.de>\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
|
@ -86,6 +86,9 @@ msgstr "Thema bearbeiten..."
|
||||||
msgid "Modify topic."
|
msgid "Modify topic."
|
||||||
msgstr "Thema ändern"
|
msgstr "Thema ändern"
|
||||||
|
|
||||||
|
msgid "Please correct the indicated errors."
|
||||||
|
msgstr "Bitte berichtigen Sie die angezeigten Fehler."
|
||||||
|
|
||||||
# blog
|
# blog
|
||||||
|
|
||||||
msgid "Edit Blog Post..."
|
msgid "Edit Blog Post..."
|
||||||
|
@ -175,11 +178,29 @@ msgstr "Glossareintrag anlegen."
|
||||||
msgid "Answer Range"
|
msgid "Answer Range"
|
||||||
msgstr "Abstufung Bewertungen"
|
msgstr "Abstufung Bewertungen"
|
||||||
|
|
||||||
|
msgid "Feedback Footer"
|
||||||
|
msgstr "Auswertungs-Hinweis"
|
||||||
|
|
||||||
|
msgid "Text that will appear at the end of the feedback page."
|
||||||
|
msgstr "Text, der am Ende der Auswertungsseite erscheinen soll."
|
||||||
|
|
||||||
msgid "Number of items (answer options) to select from."
|
msgid "Number of items (answer options) to select from."
|
||||||
msgstr "Anzahl der Abstufungen, aus denen bei der Antwort gewählt werden kann."
|
msgstr "Anzahl der Abstufungen, aus denen bei der Antwort gewählt werden kann."
|
||||||
|
|
||||||
msgid "Negativ"
|
msgid "Minimum Number of Answers"
|
||||||
msgstr "Negativbewertung"
|
msgstr "Mindestanzahl an Antworten"
|
||||||
|
|
||||||
|
msgid "Minumum number of questions that have to be answered. Empty means all questions have to be answered."
|
||||||
|
msgstr "Anzahl der Fragen, die mindestens beantwortet werden müssen. Keine Angabe: Es müssen alle Fragen beantwortet werden."
|
||||||
|
|
||||||
|
msgid "Required"
|
||||||
|
msgstr "Pflichtfrage"
|
||||||
|
|
||||||
|
msgid "Question must be answered."
|
||||||
|
msgstr "Frage muss unbedingt beantwortet werden."
|
||||||
|
|
||||||
|
msgid "Negative"
|
||||||
|
msgstr "Negative Polarität"
|
||||||
|
|
||||||
msgid "Value inversion: High selection means low value."
|
msgid "Value inversion: High selection means low value."
|
||||||
msgstr "Invertierung der Bewertung: Hohe gewählte Stufe bedeutet niedriger Wert."
|
msgstr "Invertierung der Bewertung: Hohe gewählte Stufe bedeutet niedriger Wert."
|
||||||
|
@ -196,27 +217,54 @@ msgstr "Kategorie"
|
||||||
msgid "Response"
|
msgid "Response"
|
||||||
msgstr "Beurteilung"
|
msgstr "Beurteilung"
|
||||||
|
|
||||||
|
msgid "No answer"
|
||||||
|
msgstr "Keine Antwort"
|
||||||
|
|
||||||
msgid "Does not apply"
|
msgid "Does not apply"
|
||||||
msgstr "Trifft nicht zu"
|
msgstr "Trifft nicht zu"
|
||||||
|
|
||||||
msgid "Fully applies"
|
msgid "Fully applies"
|
||||||
msgstr "Trifft voll zu"
|
msgstr "Trifft voll zu"
|
||||||
|
|
||||||
|
msgid "survey_value_none"
|
||||||
|
msgstr "Keine Antwort"
|
||||||
|
|
||||||
msgid "survey_value_0"
|
msgid "survey_value_0"
|
||||||
msgstr "trifft für unser Unternehmen überhaupt nicht zu"
|
msgstr "Trifft für unser Unternehmen überhaupt nicht zu"
|
||||||
|
|
||||||
msgid "survey_value_1"
|
msgid "survey_value_1"
|
||||||
msgstr "trifft eher nicht zu"
|
msgstr "Trifft eher nicht zu"
|
||||||
|
|
||||||
msgid "survey_value_2"
|
msgid "survey_value_2"
|
||||||
msgstr "trifft eher zu"
|
msgstr "Trifft eher zu"
|
||||||
|
|
||||||
msgid "survey_value_3"
|
msgid "survey_value_3"
|
||||||
msgstr "trifft für unser Unternehmen voll und ganz zu"
|
msgstr "Trifft für unser Unternehmen voll und ganz zu"
|
||||||
|
|
||||||
msgid "Evaluate Questionnaire"
|
msgid "Evaluate Questionnaire"
|
||||||
msgstr "Fragebogen auswerten"
|
msgstr "Fragebogen auswerten"
|
||||||
|
|
||||||
|
msgid "Reset Responses Entered"
|
||||||
|
msgstr "Eingaben zurücksetzen"
|
||||||
|
|
||||||
|
msgid "Back to Questionnaire"
|
||||||
|
msgstr "Zurück zum Fragebogen"
|
||||||
|
|
||||||
|
msgid "Please answer at least $minAnswers questions."
|
||||||
|
msgstr "Bitte beantworten Sie mindestens $minAnswers Fragen."
|
||||||
|
|
||||||
|
msgid "Please answer all questions."
|
||||||
|
msgstr "Bitte beantworten Sie alle Fragen."
|
||||||
|
|
||||||
|
msgid "Please answer the obligatory questions."
|
||||||
|
msgstr "Bitte beantworten Sie die Pflichtfragen."
|
||||||
|
|
||||||
|
msgid "Please answer the minimum number of questions."
|
||||||
|
msgstr "Bitte beantworten Sie die angegebene Mindestanzahl an Fragen je Fragengruppe."
|
||||||
|
|
||||||
|
msgid "Obligatory question, must be answered"
|
||||||
|
msgstr "Pflichtfrage, muss beantwortet werden"
|
||||||
|
|
||||||
# competence (qualification)
|
# competence (qualification)
|
||||||
|
|
||||||
msgid "Validity Period (Months)"
|
msgid "Validity Period (Months)"
|
||||||
|
@ -495,6 +543,9 @@ msgstr "Unterbegriffe"
|
||||||
msgid "Resources"
|
msgid "Resources"
|
||||||
msgstr "Ressourcen"
|
msgstr "Ressourcen"
|
||||||
|
|
||||||
|
msgid "Text Elements"
|
||||||
|
msgstr "Texte"
|
||||||
|
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr "Titel"
|
msgstr "Titel"
|
||||||
|
|
||||||
|
@ -660,6 +711,9 @@ msgstr "Zugeordnete Begriffe"
|
||||||
msgid "more..."
|
msgid "more..."
|
||||||
msgstr "Mehr..."
|
msgstr "Mehr..."
|
||||||
|
|
||||||
|
msgid "More..."
|
||||||
|
msgstr "Mehr..."
|
||||||
|
|
||||||
msgid "Versioning"
|
msgid "Versioning"
|
||||||
msgstr "Versionierung"
|
msgstr "Versionierung"
|
||||||
|
|
||||||
|
@ -708,12 +762,27 @@ msgstr "Teilnehmerregistrierung"
|
||||||
msgid "Register"
|
msgid "Register"
|
||||||
msgstr "Benutzer registrieren"
|
msgstr "Benutzer registrieren"
|
||||||
|
|
||||||
|
msgid "Register new member"
|
||||||
|
msgstr "Neu registrieren"
|
||||||
|
|
||||||
|
msgid "Login name already taken."
|
||||||
|
msgstr "Die von Ihnen eingegebene Benutzerkennung ist schon vergeben."
|
||||||
|
|
||||||
msgid "Your old password was not entered correctly."
|
msgid "Your old password was not entered correctly."
|
||||||
msgstr "Sie haben Ihr altes Passwort nicht korrekt eingegeben."
|
msgstr "Sie haben Ihr altes Passwort nicht korrekt eingegeben."
|
||||||
|
|
||||||
msgid "Password and password confirmation do not match."
|
msgid "Password and password confirmation do not match."
|
||||||
msgstr "Die Passwort-Wiederholung stimmt nicht mit dem eingegebenen Passwort überein."
|
msgstr "Die Passwort-Wiederholung stimmt nicht mit dem eingegebenen Passwort überein."
|
||||||
|
|
||||||
|
msgid "confirmation_mail_subject"
|
||||||
|
msgstr "Benutzer-Registrierung"
|
||||||
|
|
||||||
|
msgid "confirmation_mail_text"
|
||||||
|
msgstr "Bitte clicken Sie auf den folgenden Link, um die Anmeldung abzuschließen."
|
||||||
|
|
||||||
|
msgid "The user account has been created."
|
||||||
|
msgstr "Ihr Benutzerkonto wurde eingerichtet."
|
||||||
|
|
||||||
msgid "Your password has been changed."
|
msgid "Your password has been changed."
|
||||||
msgstr "Ihr Passwort wurde geändert."
|
msgstr "Ihr Passwort wurde geändert."
|
||||||
|
|
||||||
|
@ -893,6 +962,9 @@ msgstr "Kalender"
|
||||||
msgid "Work Items"
|
msgid "Work Items"
|
||||||
msgstr "Aktivitäten"
|
msgstr "Aktivitäten"
|
||||||
|
|
||||||
|
msgid "Work Items for $title"
|
||||||
|
msgstr "Aktivitäten für $title"
|
||||||
|
|
||||||
msgid "Day"
|
msgid "Day"
|
||||||
msgstr "Tag"
|
msgstr "Tag"
|
||||||
|
|
||||||
|
@ -956,14 +1028,26 @@ msgid "Restrict to objects with certain states"
|
||||||
msgstr "Auf Objekte mit bestimmtem Status beschränken"
|
msgstr "Auf Objekte mit bestimmtem Status beschränken"
|
||||||
|
|
||||||
msgid "Workflow"
|
msgid "Workflow"
|
||||||
msgstr "Statusdefinition/Workflow"
|
msgstr "Workflow"
|
||||||
|
|
||||||
msgid "States"
|
msgid "States"
|
||||||
msgstr "Statuswerte"
|
msgstr "Statuswerte"
|
||||||
|
|
||||||
|
msgid "States Definition"
|
||||||
|
msgstr "Workflowdefinition"
|
||||||
|
|
||||||
|
msgid "State Transition"
|
||||||
|
msgstr "Workflow-Statusänderung"
|
||||||
|
|
||||||
|
msgid "Transition"
|
||||||
|
msgstr "Aktion"
|
||||||
|
|
||||||
msgid "State information for $definition: $title"
|
msgid "State information for $definition: $title"
|
||||||
msgstr "Status ($definition): $title"
|
msgstr "Status ($definition): $title"
|
||||||
|
|
||||||
|
msgid "Available Transitions"
|
||||||
|
msgstr "Übergänge"
|
||||||
|
|
||||||
msgid "classification_quality"
|
msgid "classification_quality"
|
||||||
msgstr "Klassifizierung"
|
msgstr "Klassifizierung"
|
||||||
|
|
||||||
|
@ -976,6 +1060,12 @@ msgstr "Aufgabe"
|
||||||
msgid "publishable_task"
|
msgid "publishable_task"
|
||||||
msgstr "Aufgabe/Zugriff"
|
msgstr "Aufgabe/Zugriff"
|
||||||
|
|
||||||
|
msgid "label_transition_comments"
|
||||||
|
msgstr "Bemerkung"
|
||||||
|
|
||||||
|
msgid "desc_transition_comments"
|
||||||
|
msgstr "Notizen zum Statusübergang."
|
||||||
|
|
||||||
# state names
|
# state names
|
||||||
|
|
||||||
msgid "accepted"
|
msgid "accepted"
|
||||||
|
|
|
@ -185,7 +185,7 @@ sure that a principal object can be served by a corresponding factory):
|
||||||
... 'lastName': u'Sawyer',
|
... 'lastName': u'Sawyer',
|
||||||
... 'firstName': u'Tom',
|
... 'firstName': u'Tom',
|
||||||
... 'email': u'tommy@sawyer.com',
|
... 'email': u'tommy@sawyer.com',
|
||||||
... 'action': 'update',}
|
... 'form.action': 'update',}
|
||||||
|
|
||||||
and register it.
|
and register it.
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,18 @@
|
||||||
class="loops.organize.browser.member.MemberRegistration"
|
class="loops.organize.browser.member.MemberRegistration"
|
||||||
permission="zope.View" />
|
permission="zope.View" />
|
||||||
|
|
||||||
|
<browser:page
|
||||||
|
for="loops.interfaces.INode"
|
||||||
|
name="selfservice_registration.html"
|
||||||
|
class="loops.organize.browser.member.SecureMemberRegistration"
|
||||||
|
permission="zope.View" />
|
||||||
|
|
||||||
|
<browser:page
|
||||||
|
for="loops.interfaces.INode"
|
||||||
|
name="selfservice_confirmation.html"
|
||||||
|
class="loops.organize.browser.member.ConfirmMemberRegistration"
|
||||||
|
permission="zope.View" />
|
||||||
|
|
||||||
<browser:page
|
<browser:page
|
||||||
for="loops.interfaces.INode"
|
for="loops.interfaces.INode"
|
||||||
name="change_password.html"
|
name="change_password.html"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,10 +19,10 @@
|
||||||
"""
|
"""
|
||||||
Definition of view classes and other browser related stuff for
|
Definition of view classes and other browser related stuff for
|
||||||
members (persons).
|
members (persons).
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from email.MIMEText import MIMEText
|
||||||
from zope import interface, component
|
from zope import interface, component
|
||||||
from zope.app.authentication.principalfolder import InternalPrincipal
|
from zope.app.authentication.principalfolder import InternalPrincipal
|
||||||
from zope.app.form.browser.textwidgets import PasswordWidget as BasePasswordWidget
|
from zope.app.form.browser.textwidgets import PasswordWidget as BasePasswordWidget
|
||||||
|
@ -31,6 +31,8 @@ from zope.app.principalannotation import annotations
|
||||||
from zope.cachedescriptors.property import Lazy
|
from zope.cachedescriptors.property import Lazy
|
||||||
from zope.i18nmessageid import MessageFactory
|
from zope.i18nmessageid import MessageFactory
|
||||||
from zope.security import checkPermission
|
from zope.security import checkPermission
|
||||||
|
from zope.sendmail.interfaces import IMailDelivery
|
||||||
|
from zope.traversing.browser import absoluteURL
|
||||||
|
|
||||||
from cybertools.composer.interfaces import IInstance
|
from cybertools.composer.interfaces import IInstance
|
||||||
from cybertools.composer.schema.browser.common import schema_macros
|
from cybertools.composer.schema.browser.common import schema_macros
|
||||||
|
@ -38,7 +40,8 @@ from cybertools.composer.schema.browser.form import Form, CreateForm
|
||||||
from cybertools.composer.schema.schema import FormState, FormError
|
from cybertools.composer.schema.schema import FormState, FormError
|
||||||
from cybertools.meta.interfaces import IOptions
|
from cybertools.meta.interfaces import IOptions
|
||||||
from cybertools.typology.interfaces import IType
|
from cybertools.typology.interfaces import IType
|
||||||
from loops.browser.common import concept_macros
|
from cybertools.util.randomname import generateName
|
||||||
|
from loops.browser.common import concept_macros, form_macros
|
||||||
from loops.browser.concept import ConceptView, ConceptRelationView
|
from loops.browser.concept import ConceptView, ConceptRelationView
|
||||||
from loops.browser.node import NodeView
|
from loops.browser.node import NodeView
|
||||||
from loops.common import adapted
|
from loops.common import adapted
|
||||||
|
@ -46,7 +49,7 @@ from loops.concept import Concept
|
||||||
from loops.organize.interfaces import ANNOTATION_KEY, IMemberRegistrationManager
|
from loops.organize.interfaces import ANNOTATION_KEY, IMemberRegistrationManager
|
||||||
from loops.organize.interfaces import IMemberRegistration, IPasswordChange
|
from loops.organize.interfaces import IMemberRegistration, IPasswordChange
|
||||||
from loops.organize.party import getPersonForUser, Person
|
from loops.organize.party import getPersonForUser, Person
|
||||||
from loops.organize.util import getInternalPrincipal
|
from loops.organize.util import getInternalPrincipal, getPrincipalForUserId
|
||||||
import loops.browser.util
|
import loops.browser.util
|
||||||
from loops.util import _
|
from loops.util import _
|
||||||
|
|
||||||
|
@ -76,10 +79,11 @@ class PersonalInfo(ConceptView):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
class MemberRegistration(NodeView, CreateForm):
|
class BaseMemberRegistration(NodeView):
|
||||||
|
|
||||||
interface = IMemberRegistration
|
interface = IMemberRegistration # TODO: add company, create institution
|
||||||
message = _(u'The user account has been created.')
|
message = _(u'The user account has been created.')
|
||||||
|
template = form_macros
|
||||||
|
|
||||||
formErrors = dict(
|
formErrors = dict(
|
||||||
confirm_nomatch=FormError(_(u'Password and password confirmation do not match.')),
|
confirm_nomatch=FormError(_(u'Password and password confirmation do not match.')),
|
||||||
|
@ -88,10 +92,23 @@ class MemberRegistration(NodeView, CreateForm):
|
||||||
|
|
||||||
label = _(u'Member Registration')
|
label = _(u'Member Registration')
|
||||||
label_submit = _(u'Register')
|
label_submit = _(u'Register')
|
||||||
|
title = _('Member Registration')
|
||||||
|
|
||||||
permissions_key = u'registration.permissions'
|
permissions_key = u'registration.permissions'
|
||||||
roles_key = u'registration.roles'
|
roles_key = u'registration.roles'
|
||||||
registration_adapter_key = u'registration.adapter'
|
registration_adapter_key = u'registration.adapter'
|
||||||
|
text_names_prefix = 'organize.member.registration'
|
||||||
|
# texts: reg_info, reg_feedback, conf_mail, conf_info, conf_feedback
|
||||||
|
info_key = 'reg_info'
|
||||||
|
feedback_key = 'reg_feedback'
|
||||||
|
|
||||||
|
isInnerHtml = False
|
||||||
|
showAssignments = False
|
||||||
|
form_action = 'register'
|
||||||
|
versionInfo = None
|
||||||
|
|
||||||
|
def closeAction(self, submit=True):
|
||||||
|
return u''
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def macro(self):
|
def macro(self):
|
||||||
|
@ -99,7 +116,7 @@ class MemberRegistration(NodeView, CreateForm):
|
||||||
|
|
||||||
def checkPermissions(self):
|
def checkPermissions(self):
|
||||||
personType = adapted(self.conceptManager['person'])
|
personType = adapted(self.conceptManager['person'])
|
||||||
perms = IOptions(personType)('registration.permission')
|
perms = IOptions(personType)(self.permissions_key)
|
||||||
if perms:
|
if perms:
|
||||||
return checkPermission(perms[0], self.context)
|
return checkPermission(perms[0], self.context)
|
||||||
return checkPermission('loops.ManageSite', self.context)
|
return checkPermission('loops.ManageSite', self.context)
|
||||||
|
@ -108,12 +125,38 @@ class MemberRegistration(NodeView, CreateForm):
|
||||||
def item(self):
|
def item(self):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def data(self):
|
||||||
|
return self.request.form
|
||||||
|
|
||||||
|
def getPrincipalAnnotation(self, principal):
|
||||||
|
return annotations(principal).get(ANNOTATION_KEY, None)
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def infoText(self):
|
||||||
|
name = '.'.join((self.text_names_prefix, self.info_key))
|
||||||
|
text = self.resourceManager.get(name)
|
||||||
|
if text:
|
||||||
|
return self.renderText(text.data)
|
||||||
|
return u''
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def feedbackUrl(self):
|
||||||
|
name = '.'.join((self.text_names_prefix, self.feedback_key))
|
||||||
|
text = self.resourceManager.get(name)
|
||||||
|
if text:
|
||||||
|
return self.getUrlForTarget(text)
|
||||||
|
|
||||||
|
|
||||||
|
class MemberRegistration(BaseMemberRegistration, CreateForm):
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def schema(self):
|
def schema(self):
|
||||||
schema = super(MemberRegistration, self).schema
|
schema = super(MemberRegistration, self).schema
|
||||||
schema.fields.remove('birthDate')
|
schema.fields.remove('birthDate')
|
||||||
schema.fields.reorder(-2, 'loginName')
|
schema.fields.reorder(-2, 'loginName')
|
||||||
return schema
|
return schema
|
||||||
|
# TODO: add company, create institution
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def object(self):
|
def object(self):
|
||||||
|
@ -121,7 +164,7 @@ class MemberRegistration(NodeView, CreateForm):
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
form = self.request.form
|
form = self.request.form
|
||||||
if not form.get('action'):
|
if not form.get('form.action'):
|
||||||
return True
|
return True
|
||||||
instance = component.getAdapter(self.object, IInstance, name='editor')
|
instance = component.getAdapter(self.object, IInstance, name='editor')
|
||||||
instance.template = self.schema
|
instance.template = self.schema
|
||||||
|
@ -157,6 +200,165 @@ class MemberRegistration(NodeView, CreateForm):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class SecureMemberRegistration(BaseMemberRegistration, CreateForm):
|
||||||
|
|
||||||
|
permissions_key = u'secure_registration.permissions'
|
||||||
|
roles_key = u'secure_registration.roles'
|
||||||
|
email_key = 'reg_email'
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def schema(self):
|
||||||
|
schema = super(SecureMemberRegistration, self).schema
|
||||||
|
schema.fields.remove('birthDate')
|
||||||
|
schema.fields.remove('password')
|
||||||
|
schema.fields.remove('passwordConfirm')
|
||||||
|
schema.fields.remove('phoneNumbers')
|
||||||
|
#schema.fields.reorder(-2, 'loginName')
|
||||||
|
return schema
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def macro(self):
|
||||||
|
return organize_macros.macros['register']
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def object(self):
|
||||||
|
return Person(Concept())
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
form = self.request.form
|
||||||
|
if not form.get('form.action'):
|
||||||
|
return True
|
||||||
|
instance = component.getAdapter(self.object, IInstance, name='editor')
|
||||||
|
instance.template = self.schema
|
||||||
|
self.formState = formState = instance.applyTemplate(data=form,
|
||||||
|
fieldHandlers=self.fieldHandlers)
|
||||||
|
if formState.severity > 0:
|
||||||
|
# show form again
|
||||||
|
return True
|
||||||
|
login = form.get('loginName')
|
||||||
|
regMan = IMemberRegistrationManager(self.context.getLoopsRoot())
|
||||||
|
pw = generateName()
|
||||||
|
email = form.get('email')
|
||||||
|
try:
|
||||||
|
result = regMan.register(login, pw,
|
||||||
|
form.get('lastName'), form.get('firstName'),
|
||||||
|
email=email,)
|
||||||
|
except ValueError, e:
|
||||||
|
fi = formState.fieldInstances['loginName']
|
||||||
|
fi.setError('duplicate_loginname', self.formErrors)
|
||||||
|
formState.severity = max(formState.severity, fi.severity)
|
||||||
|
return True
|
||||||
|
self.object = result
|
||||||
|
person = result.context
|
||||||
|
pa = self.getPrincipalAnnotation(
|
||||||
|
getPrincipalForUserId(adapted(person).getUserId()))
|
||||||
|
pa['id'] = generateName()
|
||||||
|
pa['timestamp'] = datetime.utcnow()
|
||||||
|
self.notifyEmail(login, email, pa['id'])
|
||||||
|
if self.feedbackUrl:
|
||||||
|
self.request.response.redirect(self.feedbackUrl)
|
||||||
|
else:
|
||||||
|
msg = self.message
|
||||||
|
self.request.response.redirect('%s?loops.message=%s' % (self.url, msg))
|
||||||
|
return False
|
||||||
|
|
||||||
|
def notifyEmail(self, userid, recipient, id):
|
||||||
|
baseUrl = absoluteURL(self.context.getMenu(), self.request)
|
||||||
|
url = u'%s/selfservice_confirmation.html?login=%s&id=%s' % (
|
||||||
|
baseUrl, userid, id,)
|
||||||
|
recipients = [recipient]
|
||||||
|
subject = _(u'confirmation_mail_subject')
|
||||||
|
name = '.'.join((self.text_names_prefix, self.email_key))
|
||||||
|
text = self.resourceManager.get(name)
|
||||||
|
if text:
|
||||||
|
message = (text.data % url).encode('UTF-8')
|
||||||
|
subject = text.description or subject
|
||||||
|
else:
|
||||||
|
message = _(u'confirmation_mail_text') + u':\n\n'
|
||||||
|
message = (message + url).encode('UTF-8')
|
||||||
|
senderInfo = self.globalOptions('email.sender')
|
||||||
|
sender = senderInfo and senderInfo[0] or 'info@loops.cy55.de'
|
||||||
|
sender = sender.encode('UTF-8')
|
||||||
|
msg = MIMEText(message, 'plain', 'utf-8')
|
||||||
|
msg['Subject'] = subject.encode('UTF-8')
|
||||||
|
msg['From'] = sender
|
||||||
|
msg['To'] = ', '.join(recipients)
|
||||||
|
mailhost = component.getUtility(IMailDelivery, 'Mail')
|
||||||
|
mailhost.send(sender, recipients, msg.as_string())
|
||||||
|
|
||||||
|
|
||||||
|
class ConfirmMemberRegistration(BaseMemberRegistration, Form):
|
||||||
|
|
||||||
|
permissions_key = u'secure_registration.permissions'
|
||||||
|
roles_key = u'secure_registration.roles'
|
||||||
|
info_key = 'confirm_info'
|
||||||
|
feedback_key = 'confirm_feedback'
|
||||||
|
email_key = 'confirm_email'
|
||||||
|
|
||||||
|
form_action = 'confirm_registration'
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def macro(self):
|
||||||
|
return organize_macros.macros['confirm']
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def data(self):
|
||||||
|
form = self.request.form
|
||||||
|
return dict(loginName=form.get('login'), id=form.get('id'))
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def schema(self):
|
||||||
|
schema = super(ConfirmMemberRegistration, self).schema
|
||||||
|
schema.fields.remove('salutation')
|
||||||
|
schema.fields.remove('academicTitle')
|
||||||
|
schema.fields.remove('birthDate')
|
||||||
|
schema.fields.remove('phoneNumbers')
|
||||||
|
schema.fields.remove('loginName')
|
||||||
|
schema.fields.remove('firstName')
|
||||||
|
schema.fields.remove('lastName')
|
||||||
|
schema.fields.remove('email')
|
||||||
|
return schema
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
form = self.request.form
|
||||||
|
if form.get('form.action') != 'confirm_registration':
|
||||||
|
return True
|
||||||
|
if not form.get('login'):
|
||||||
|
return True
|
||||||
|
regMan = IMemberRegistrationManager(self.context.getLoopsRoot())
|
||||||
|
prefix = regMan.getPrincipalFolderFromOption().prefix
|
||||||
|
userId = prefix + form['login']
|
||||||
|
principal = getPrincipalForUserId(userId)
|
||||||
|
pa = self.getPrincipalAnnotation(principal)
|
||||||
|
id = form.get('id')
|
||||||
|
if not id or id != pa.get('id'):
|
||||||
|
return True
|
||||||
|
instance = component.getAdapter(self.object, IInstance, name='editor')
|
||||||
|
instance.template = self.schema
|
||||||
|
self.formState = formState = instance.applyTemplate(data=form,
|
||||||
|
fieldHandlers=self.fieldHandlers)
|
||||||
|
#formState = self.formState = self.validate(form)
|
||||||
|
if formState.severity > 0:
|
||||||
|
return True
|
||||||
|
pw = form.get('password')
|
||||||
|
pwConfirm = form.get('passwordConfirm')
|
||||||
|
if pw != pwConfirm:
|
||||||
|
fi = formState.fieldInstances['password']
|
||||||
|
fi.setError('confirm_nomatch', self.formErrors)
|
||||||
|
formState.severity = max(formState.severity, fi.severity)
|
||||||
|
return True
|
||||||
|
del pa['id']
|
||||||
|
del pa['timestamp']
|
||||||
|
ip = getInternalPrincipal(userId)
|
||||||
|
ip.setPassword(pw)
|
||||||
|
if self.feedbackUrl:
|
||||||
|
self.request.response.redirect(self.feedbackUrl)
|
||||||
|
else:
|
||||||
|
url = '%s?loops.message=%s' % (self.url, self.message)
|
||||||
|
self.request.response.redirect(url)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class PasswordChange(NodeView, Form):
|
class PasswordChange(NodeView, Form):
|
||||||
|
|
||||||
interface = IPasswordChange
|
interface = IPasswordChange
|
||||||
|
|
|
@ -1,5 +1,42 @@
|
||||||
<html i18n:domain="loops">
|
<html i18n:domain="loops">
|
||||||
|
|
||||||
|
|
||||||
|
<metal:block define-macro="register">
|
||||||
|
<metal:data use-macro="view/form_macros/edit">
|
||||||
|
<metal:custom fill-slot="custom_header">
|
||||||
|
<tbody>
|
||||||
|
<tr><td colspan="5">
|
||||||
|
<tal:info content="structure item/infoText" />
|
||||||
|
</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</metal:custom>
|
||||||
|
</metal:data>
|
||||||
|
</metal:block>
|
||||||
|
|
||||||
|
|
||||||
|
<metal:block define-macro="confirm">
|
||||||
|
<metal:data use-macro="view/form_macros/edit">
|
||||||
|
<metal:custom fill-slot="custom_header">
|
||||||
|
<tbody>
|
||||||
|
<tr><td colspan="5">
|
||||||
|
<tal:info content="structure item/infoText" />
|
||||||
|
</td></tr>
|
||||||
|
<tr><td colspan="5">
|
||||||
|
<input type="hidden" name="login"
|
||||||
|
tal:attributes="value item/data/loginName" />
|
||||||
|
<input type="hidden" name="id"
|
||||||
|
tal:attributes="value item/data/id" />
|
||||||
|
<table><tr>
|
||||||
|
<td i18n:translate="">Login Name</td>
|
||||||
|
<td tal:content="item/data/loginName" />
|
||||||
|
</tr></table>
|
||||||
|
</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</metal:custom>
|
||||||
|
</metal:data>
|
||||||
|
</metal:block>
|
||||||
|
|
||||||
|
|
||||||
<metal:task define-macro="task">
|
<metal:task define-macro="task">
|
||||||
<metal:data use-macro="view/concept_macros/conceptdata">
|
<metal:data use-macro="view/concept_macros/conceptdata">
|
||||||
</metal:data>
|
</metal:data>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2012 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -62,19 +62,29 @@ class MemberRegistrationManager(object):
|
||||||
def __init__(self, context):
|
def __init__(self, context):
|
||||||
self.context = context
|
self.context = context
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def personType(self):
|
||||||
|
concepts = self.context.getConceptManager()
|
||||||
|
return adapted(concepts[self.person_typeName])
|
||||||
|
|
||||||
|
def getPrincipalFolderFromOption(self):
|
||||||
|
options = IOptions(self.personType)
|
||||||
|
pfName = options(self.principalfolder_key,
|
||||||
|
(self.default_principalfolder,))[0]
|
||||||
|
return getPrincipalFolder(self.context, pfName)
|
||||||
|
|
||||||
def register(self, userId, password, lastName, firstName=u'',
|
def register(self, userId, password, lastName, firstName=u'',
|
||||||
groups=[], useExisting=False, pfName=None, **kw):
|
groups=[], useExisting=False, pfName=None, **kw):
|
||||||
concepts = self.context.getConceptManager()
|
options = IOptions(self.personType)
|
||||||
personType = adapted(concepts[self.person_typeName])
|
|
||||||
options = IOptions(personType)
|
|
||||||
if pfName is None:
|
if pfName is None:
|
||||||
pfName = options(self.principalfolder_key,
|
pfName = options(self.principalfolder_key,
|
||||||
(self.default_principalfolder,))[0]
|
(self.default_principalfolder,))[0]
|
||||||
self.createPrincipal(pfName, userId, password, lastName, firstName, useExisting=useExisting)
|
self.createPrincipal(pfName, userId, password, lastName, firstName,
|
||||||
if len(groups)==0:
|
useExisting=useExisting)
|
||||||
|
if not groups:
|
||||||
groups = options(self.groups_key, ())
|
groups = options(self.groups_key, ())
|
||||||
self.setGroupsForPrincipal(pfName, userId, groups=groups)
|
self.setGroupsForPrincipal(pfName, userId, groups=groups)
|
||||||
self.createPersonForPrincipal(pfName, userId, lastName, firstName,
|
return self.createPersonForPrincipal(pfName, userId, lastName, firstName,
|
||||||
useExisting, **kw)
|
useExisting, **kw)
|
||||||
|
|
||||||
def createPrincipal(self, pfName, userId, password, lastName,
|
def createPrincipal(self, pfName, userId, password, lastName,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -18,8 +18,6 @@
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Basic implementations for stateful objects and adapters.
|
Basic implementations for stateful objects and adapters.
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from zope.app.catalog.interfaces import ICatalog
|
from zope.app.catalog.interfaces import ICatalog
|
||||||
|
@ -27,6 +25,7 @@ from zope.cachedescriptors.property import Lazy
|
||||||
from zope import component
|
from zope import component
|
||||||
from zope.component import adapts, adapter
|
from zope.component import adapts, adapter
|
||||||
|
|
||||||
|
from cybertools.composer.schema.field import Field
|
||||||
from cybertools.meta.interfaces import IOptions
|
from cybertools.meta.interfaces import IOptions
|
||||||
from cybertools.stateful.base import Stateful as BaseStateful
|
from cybertools.stateful.base import Stateful as BaseStateful
|
||||||
from cybertools.stateful.base import StatefulAdapter, IndexInfo
|
from cybertools.stateful.base import StatefulAdapter, IndexInfo
|
||||||
|
@ -34,6 +33,7 @@ from cybertools.stateful.interfaces import IStatesDefinition, ITransitionEvent
|
||||||
from loops.common import adapted
|
from loops.common import adapted
|
||||||
from loops.interfaces import ILoopsObject, IConcept, IResource
|
from loops.interfaces import ILoopsObject, IConcept, IResource
|
||||||
from loops import util
|
from loops import util
|
||||||
|
from loops.util import _
|
||||||
|
|
||||||
|
|
||||||
class Stateful(BaseStateful):
|
class Stateful(BaseStateful):
|
||||||
|
@ -93,3 +93,10 @@ def handleTransition(obj, event):
|
||||||
if next != previous:
|
if next != previous:
|
||||||
cat = component.getUtility(ICatalog)
|
cat = component.getUtility(ICatalog)
|
||||||
cat.index_doc(int(util.getUidForObject(obj)), obj)
|
cat.index_doc(int(util.getUidForObject(obj)), obj)
|
||||||
|
|
||||||
|
|
||||||
|
# predefined fields for transition forms
|
||||||
|
|
||||||
|
commentsField = Field('comments', _(u'label_transition_comments'), 'textarea',
|
||||||
|
description=_(u'desc_transition_comments'),
|
||||||
|
nostore=True)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2012 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -23,12 +23,16 @@ Views and actions for states management.
|
||||||
from zope import component
|
from zope import component
|
||||||
from zope.app.pagetemplate import ViewPageTemplateFile
|
from zope.app.pagetemplate import ViewPageTemplateFile
|
||||||
from zope.cachedescriptors.property import Lazy
|
from zope.cachedescriptors.property import Lazy
|
||||||
|
from zope.event import notify
|
||||||
from zope.i18n import translate
|
from zope.i18n import translate
|
||||||
|
from zope.lifecycleevent import ObjectModifiedEvent, Attributes
|
||||||
|
|
||||||
from cybertools.browser.action import Action, actions
|
from cybertools.browser.action import Action, actions
|
||||||
|
from cybertools.composer.schema.schema import Schema
|
||||||
from cybertools.stateful.interfaces import IStateful, IStatesDefinition
|
from cybertools.stateful.interfaces import IStateful, IStatesDefinition
|
||||||
from loops.browser.common import BaseView
|
from loops.browser.common import BaseView
|
||||||
from loops.browser.concept import ConceptView
|
from loops.browser.concept import ConceptView
|
||||||
|
from loops.browser.form import ObjectForm, EditObject
|
||||||
from loops.expert.query import And, Or, State, Type, getObjects
|
from loops.expert.query import And, Or, State, Type, getObjects
|
||||||
from loops.expert.browser.search import search_template
|
from loops.expert.browser.search import search_template
|
||||||
from loops.security.common import checkPermission
|
from loops.security.common import checkPermission
|
||||||
|
@ -43,6 +47,16 @@ statefulActions = ('classification_quality',
|
||||||
'publishable_task',)
|
'publishable_task',)
|
||||||
|
|
||||||
|
|
||||||
|
def registerStatesPortlet(controller, view, statesDefs,
|
||||||
|
region='portlet_right', priority=98):
|
||||||
|
cm = controller.macros
|
||||||
|
stfs = [component.getAdapter(view.context, IStateful, name=std)
|
||||||
|
for std in statesDefs]
|
||||||
|
cm.register(region, 'states', title=_(u'Workflow'),
|
||||||
|
subMacro=template.macros['portlet_states'],
|
||||||
|
priority=priority, info=view, stfs=stfs)
|
||||||
|
|
||||||
|
|
||||||
class StateAction(Action):
|
class StateAction(Action):
|
||||||
|
|
||||||
url = None
|
url = None
|
||||||
|
@ -67,8 +81,9 @@ class StateAction(Action):
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def icon(self):
|
def icon(self):
|
||||||
icon = self.stateObject.icon or 'led%s.png' % self.stateObject.color
|
return self.stateObject.stateIcon
|
||||||
return 'cybertools.icons/' + icon
|
#icon = self.stateObject.icon or 'led%s.png' % self.stateObject.color
|
||||||
|
#return 'cybertools.icons/' + icon
|
||||||
|
|
||||||
|
|
||||||
for std in statefulActions:
|
for std in statefulActions:
|
||||||
|
@ -78,11 +93,80 @@ for std in statefulActions:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ChangeStateBase(object):
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def stateful(self):
|
||||||
|
return component.getAdapter(self.view.virtualTargetObject, IStateful,
|
||||||
|
name=self.definition)
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def definition(self):
|
||||||
|
return self.request.form.get('stdef') or u''
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def action(self):
|
||||||
|
return self.request.form.get('action') or u''
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def transition(self):
|
||||||
|
return self.stateful.getStatesDefinition().transitions[self.action]
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def stateObject(self):
|
||||||
|
return self.stateful.getStateObject()
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def schema(self):
|
||||||
|
schema = self.transition.schema
|
||||||
|
if schema is None:
|
||||||
|
return Schema()
|
||||||
|
else:
|
||||||
|
schema.manager = self
|
||||||
|
schema.request = self.request
|
||||||
|
return schema
|
||||||
|
|
||||||
|
|
||||||
|
class ChangeStateForm(ChangeStateBase, ObjectForm):
|
||||||
|
|
||||||
|
form_action = 'change_state_action'
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def macro(self):
|
||||||
|
return template.macros['change_state']
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def title(self):
|
||||||
|
return self.virtualTargetObject.title
|
||||||
|
|
||||||
|
|
||||||
|
class ChangeState(ChangeStateBase, EditObject):
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
formData = self.request.form
|
||||||
|
# store data in target object (unless field.nostore)
|
||||||
|
self.object = self.target
|
||||||
|
formState = self.instance.applyTemplate(data=formData)
|
||||||
|
# TODO: check formState
|
||||||
|
# track all fields
|
||||||
|
trackData = dict(transition=self.action)
|
||||||
|
for f in self.fields:
|
||||||
|
if f.readonly:
|
||||||
|
continue
|
||||||
|
name = f.name
|
||||||
|
fi = formState.fieldInstances[name]
|
||||||
|
rawValue = fi.getRawValue(formData, name, u'')
|
||||||
|
trackData[name] = fi.unmarshall(rawValue)
|
||||||
|
self.stateful.doTransition(self.action)
|
||||||
|
notify(ObjectModifiedEvent(self.view.virtualTargetObject, trackData))
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
#class StateQuery(ConceptView):
|
#class StateQuery(ConceptView):
|
||||||
class StateQuery(BaseView):
|
class StateQuery(BaseView):
|
||||||
|
|
||||||
template = template
|
template = template
|
||||||
|
|
||||||
form_action = 'execute_search_action'
|
form_action = 'execute_search_action'
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
|
|
|
@ -77,7 +77,7 @@
|
||||||
set_schema="cybertools.stateful.interfaces.IStateful" />
|
set_schema="cybertools.stateful.interfaces.IStateful" />
|
||||||
</zope:class>
|
</zope:class>
|
||||||
|
|
||||||
<!-- views -->
|
<!-- views and form controllers -->
|
||||||
|
|
||||||
<browser:page
|
<browser:page
|
||||||
for="loops.interfaces.IConcept"
|
for="loops.interfaces.IConcept"
|
||||||
|
@ -91,6 +91,19 @@
|
||||||
class="loops.organize.stateful.browser.FilterAllStates"
|
class="loops.organize.stateful.browser.FilterAllStates"
|
||||||
permission="zope.View" />
|
permission="zope.View" />
|
||||||
|
|
||||||
|
<browser:page
|
||||||
|
name="change_state.html"
|
||||||
|
for="loops.interfaces.INode"
|
||||||
|
class="loops.organize.stateful.browser.ChangeStateForm"
|
||||||
|
permission="zope.ManageContent" />
|
||||||
|
|
||||||
|
<zope:adapter
|
||||||
|
name="change_state"
|
||||||
|
for="loops.browser.node.NodeView
|
||||||
|
zope.publisher.interfaces.browser.IBrowserRequest"
|
||||||
|
factory="loops.organize.stateful.browser.ChangeState"
|
||||||
|
permission="zope.ManageContent" />
|
||||||
|
|
||||||
<!-- event handlers -->
|
<!-- event handlers -->
|
||||||
|
|
||||||
<zope:subscriber handler="loops.organize.stateful.base.handleTransition" />
|
<zope:subscriber handler="loops.organize.stateful.base.handleTransition" />
|
||||||
|
|
|
@ -26,12 +26,15 @@ from zope.component import adapter
|
||||||
from zope.interface import implementer
|
from zope.interface import implementer
|
||||||
from zope.traversing.api import getName
|
from zope.traversing.api import getName
|
||||||
|
|
||||||
|
from cybertools.composer.schema.schema import Schema
|
||||||
from cybertools.stateful.definition import StatesDefinition
|
from cybertools.stateful.definition import StatesDefinition
|
||||||
from cybertools.stateful.definition import State, Transition
|
from cybertools.stateful.definition import State, Transition
|
||||||
from cybertools.stateful.interfaces import IStatesDefinition, IStateful
|
from cybertools.stateful.interfaces import IStatesDefinition, IStateful
|
||||||
from loops.common import adapted
|
from loops.common import adapted
|
||||||
|
from loops.organize.stateful.base import commentsField
|
||||||
from loops.organize.stateful.base import StatefulLoopsObject
|
from loops.organize.stateful.base import StatefulLoopsObject
|
||||||
from loops.security.interfaces import ISecuritySetter
|
from loops.security.interfaces import ISecuritySetter
|
||||||
|
from loops.util import _
|
||||||
|
|
||||||
|
|
||||||
def setPermissionsForRoles(settings):
|
def setPermissionsForRoles(settings):
|
||||||
|
@ -42,6 +45,10 @@ def setPermissionsForRoles(settings):
|
||||||
return setSecurity
|
return setSecurity
|
||||||
|
|
||||||
|
|
||||||
|
defaultSchema = Schema(commentsField,
|
||||||
|
name='change_state')
|
||||||
|
|
||||||
|
|
||||||
@implementer(IStatesDefinition)
|
@implementer(IStatesDefinition)
|
||||||
def taskStates():
|
def taskStates():
|
||||||
return StatesDefinition('task_states',
|
return StatesDefinition('task_states',
|
||||||
|
@ -55,10 +62,11 @@ def taskStates():
|
||||||
color='x'),
|
color='x'),
|
||||||
State('archived', 'archived', ('reopen',),
|
State('archived', 'archived', ('reopen',),
|
||||||
color='grey'),
|
color='grey'),
|
||||||
Transition('release', 'release', 'active'),
|
Transition('release', 'release', 'active', schema=defaultSchema),
|
||||||
Transition('finish', 'finish', 'finished'),
|
Transition('finish', 'finish', 'finished', schema=defaultSchema),
|
||||||
Transition('cancel', 'cancel', 'cancelled'),
|
Transition('cancel', 'cancel', 'cancelled', schema=defaultSchema),
|
||||||
Transition('reopen', 're-open', 'draft'),
|
Transition('reopen', 're-open', 'draft', schema=defaultSchema),
|
||||||
|
Transition('archive', 'archive', 'archived', schema=defaultSchema),
|
||||||
initialState='draft')
|
initialState='draft')
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -68,4 +68,82 @@
|
||||||
</metal:query>
|
</metal:query>
|
||||||
|
|
||||||
|
|
||||||
|
<metal:actions define-macro="portlet_states">
|
||||||
|
<div tal:repeat="stf macro/stfs">
|
||||||
|
<div tal:condition="python:len(macro.stfs) > 1">
|
||||||
|
<span i18n:translate="">States Definition</span>
|
||||||
|
<span i18n:translate=""
|
||||||
|
tal:content="stf/statesDefinition" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<b i18n:translate="">State</b>:
|
||||||
|
<span i18n:translate=""
|
||||||
|
tal:content="stf/state" />
|
||||||
|
<img style="margin-bottom: -3px"
|
||||||
|
tal:define="stateObject stf/getStateObject"
|
||||||
|
tal:attributes="src string:$resourceBase/${stateObject/stateIcon}" />
|
||||||
|
</div>
|
||||||
|
<div><b i18n:translate="">Available Transitions</b>:
|
||||||
|
<ul>
|
||||||
|
<li tal:repeat="action stf/getAvailableTransitionsForUser">
|
||||||
|
<a i18n:translate=""
|
||||||
|
tal:define="baseUrl view/virtualTargetUrl;
|
||||||
|
url string:$baseUrl/change_state.html?action=${action/name}&stdef=${stf/statesDefinition}"
|
||||||
|
tal:attributes="href url;
|
||||||
|
onClick string:objectDialog('change_state', '$url');;
|
||||||
|
return false;"
|
||||||
|
tal:content="action/title" />
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</metal:actions>
|
||||||
|
|
||||||
|
|
||||||
|
<metal:dialog define-macro="change_state">
|
||||||
|
<form name="stateful_changeState" method="post">
|
||||||
|
<div dojoType="dijit.layout.BorderContainer"
|
||||||
|
style="width: 70em; height: 600px">
|
||||||
|
<div dojoType="dijit.layout.ContentPane" region="top">
|
||||||
|
<h1><span i18n:translate="">State Transition</span> -
|
||||||
|
<span tal:content="view/title" />
|
||||||
|
</h1>
|
||||||
|
<div>
|
||||||
|
<span i18n:translate="">State</span>:
|
||||||
|
<span i18n:translate=""
|
||||||
|
tal:define="stateObject view/stateful/getStateObject"
|
||||||
|
tal:content="stateObject/title" /> -
|
||||||
|
<span i18n:translate="">Transition</span>:
|
||||||
|
<span i18n:translate=""
|
||||||
|
tal:content="view/transition/title" />
|
||||||
|
</div>
|
||||||
|
<input type="hidden" name="form.action" value="change_state">
|
||||||
|
<input type="hidden" name="stdef"
|
||||||
|
tal:attributes="value request/form/stdef|nothing">
|
||||||
|
<input type="hidden" name="action"
|
||||||
|
tal:attributes="value request/form/action|nothing">
|
||||||
|
</div>
|
||||||
|
<div dojoType="dijit.layout.ContentPane" region="center">
|
||||||
|
<table cellpadding="3" class="form">
|
||||||
|
<tbody><tr><td colspan="5" style="padding-right: 15px">
|
||||||
|
<div id="form.fields">
|
||||||
|
<metal:fields use-macro="view/fieldRenderers/fields" />
|
||||||
|
</div>
|
||||||
|
</td></tr></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div dojoType="dijit.layout.ContentPane" region="bottom">
|
||||||
|
<metal:buttons define-slot="buttons">
|
||||||
|
<input value="Save" type="submit"
|
||||||
|
onClick="submit();; return false"
|
||||||
|
i18n:attributes="value">
|
||||||
|
<input type="button" value="Cancel" onClick="dialog.hide();"
|
||||||
|
i18n:attributes="value">
|
||||||
|
</metal:buttons>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</metal:dialog>
|
||||||
|
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -18,10 +18,9 @@
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Base class(es) for track/record managers.
|
Base class(es) for track/record managers.
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from zope.app.security.interfaces import IUnauthenticatedPrincipal
|
||||||
from zope.cachedescriptors.property import Lazy
|
from zope.cachedescriptors.property import Lazy
|
||||||
|
|
||||||
from cybertools.meta.interfaces import IOptions
|
from cybertools.meta.interfaces import IOptions
|
||||||
|
@ -46,6 +45,10 @@ class BaseRecordManager(object):
|
||||||
def loopsRoot(self):
|
def loopsRoot(self):
|
||||||
return self.context.getLoopsRoot()
|
return self.context.getLoopsRoot()
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def uid(self):
|
||||||
|
return util.getUidForObject(self.context)
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def storage(self):
|
def storage(self):
|
||||||
records = self.loopsRoot.getRecordManager()
|
records = self.loopsRoot.getRecordManager()
|
||||||
|
@ -63,6 +66,8 @@ class BaseRecordManager(object):
|
||||||
else:
|
else:
|
||||||
principal = getPrincipalForUserId(userId, context=self.context)
|
principal = getPrincipalForUserId(userId, context=self.context)
|
||||||
if principal is not None:
|
if principal is not None:
|
||||||
|
if IUnauthenticatedPrincipal.providedBy(principal):
|
||||||
|
return None
|
||||||
person = getPersonForUser(self.context, principal=principal)
|
person = getPersonForUser(self.context, principal=principal)
|
||||||
if person is None:
|
if person is None:
|
||||||
return principal.id
|
return principal.id
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -18,8 +18,6 @@
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Recording changes to loops objects.
|
Recording changes to loops objects.
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from zope.app.container.interfaces import IObjectAddedEvent, IObjectRemovedEvent
|
from zope.app.container.interfaces import IObjectAddedEvent, IObjectRemovedEvent
|
||||||
|
@ -53,6 +51,9 @@ class ChangeManager(BaseRecordManager):
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def valid(self):
|
def valid(self):
|
||||||
|
req = util.getRequest()
|
||||||
|
if req and req.form.get('organize.suppress_tracking'):
|
||||||
|
return False
|
||||||
return (not (self.context is None or
|
return (not (self.context is None or
|
||||||
self.storage is None or
|
self.storage is None or
|
||||||
self.personId is None)
|
self.personId is None)
|
||||||
|
@ -70,6 +71,12 @@ class ChangeManager(BaseRecordManager):
|
||||||
if relation is not None:
|
if relation is not None:
|
||||||
data['predicate'] = util.getUidForObject(relation.predicate)
|
data['predicate'] = util.getUidForObject(relation.predicate)
|
||||||
data['second'] = util.getUidForObject(relation.second)
|
data['second'] = util.getUidForObject(relation.second)
|
||||||
|
event = kw.get('event')
|
||||||
|
if event is not None:
|
||||||
|
desc = getattr(event, 'descriptions', ())
|
||||||
|
for item in desc:
|
||||||
|
if isinstance(item, dict):
|
||||||
|
data.update(item)
|
||||||
if update:
|
if update:
|
||||||
self.storage.updateTrack(last, data)
|
self.storage.updateTrack(last, data)
|
||||||
else:
|
else:
|
||||||
|
@ -90,16 +97,18 @@ class ChangeRecord(Track):
|
||||||
|
|
||||||
@adapter(ILoopsObject, IObjectModifiedEvent)
|
@adapter(ILoopsObject, IObjectModifiedEvent)
|
||||||
def recordModification(obj, event):
|
def recordModification(obj, event):
|
||||||
ChangeManager(obj).recordModification()
|
ChangeManager(obj).recordModification(event=event)
|
||||||
|
|
||||||
@adapter(ILoopsObject, IObjectAddedEvent)
|
@adapter(ILoopsObject, IObjectAddedEvent)
|
||||||
def recordAdding(obj, event):
|
def recordAdding(obj, event):
|
||||||
ChangeManager(obj).recordModification('add')
|
ChangeManager(obj).recordModification('add', event=event)
|
||||||
|
|
||||||
@adapter(ILoopsObject, IAssignmentEvent)
|
@adapter(ILoopsObject, IAssignmentEvent)
|
||||||
def recordAssignment(obj, event):
|
def recordAssignment(obj, event):
|
||||||
ChangeManager(obj).recordModification('assign', relation=event.relation)
|
ChangeManager(obj).recordModification('assign',
|
||||||
|
event=event, relation=event.relation)
|
||||||
|
|
||||||
@adapter(ILoopsObject, IDeassignmentEvent)
|
@adapter(ILoopsObject, IDeassignmentEvent)
|
||||||
def recordDeassignment(obj, event):
|
def recordDeassignment(obj, event):
|
||||||
ChangeManager(obj).recordModification('deassign', relation=event.relation)
|
ChangeManager(obj).recordModification('deassign',
|
||||||
|
event=event, relation=event.relation)
|
||||||
|
|
|
@ -43,13 +43,14 @@ from loops.browser.concept import ConceptView
|
||||||
from loops.browser.form import ObjectForm, EditObject
|
from loops.browser.form import ObjectForm, EditObject
|
||||||
from loops.browser.node import NodeView
|
from loops.browser.node import NodeView
|
||||||
from loops.common import adapted
|
from loops.common import adapted
|
||||||
|
from loops.organize.interfaces import IPerson
|
||||||
from loops.organize.party import getPersonForUser
|
from loops.organize.party import getPersonForUser
|
||||||
from loops.organize.stateful.browser import StateAction
|
from loops.organize.stateful.browser import StateAction
|
||||||
from loops.organize.tracking.browser import BaseTrackView
|
from loops.organize.tracking.browser import BaseTrackView
|
||||||
from loops.organize.tracking.report import TrackDetails
|
from loops.organize.tracking.report import TrackDetails
|
||||||
from loops.organize.work.base import WorkItem
|
from loops.organize.work.base import WorkItem
|
||||||
from loops.security.common import canAccessObject, canListObject, canWriteObject
|
from loops.security.common import canAccessObject, canListObject, canWriteObject
|
||||||
from loops.security.common import checkPermission
|
from loops.security.common import canAccessRestricted, checkPermission
|
||||||
from loops import util
|
from loops import util
|
||||||
from loops.util import _
|
from loops.util import _
|
||||||
|
|
||||||
|
@ -228,6 +229,10 @@ class BaseWorkItemsView(object):
|
||||||
def macro(self):
|
def macro(self):
|
||||||
return self.work_macros['workitems_query']
|
return self.work_macros['workitems_query']
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def title(self):
|
||||||
|
return _(u'Work Items for $title', mapping=dict(title=self.context.title))
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def workItems(self):
|
def workItems(self):
|
||||||
rm = self.loopsRoot.getRecordManager()
|
rm = self.loopsRoot.getRecordManager()
|
||||||
|
@ -312,18 +317,24 @@ class RelatedTaskWorkItems(AllWorkItems):
|
||||||
|
|
||||||
|
|
||||||
class PersonWorkItems(BaseWorkItemsView, ConceptView):
|
class PersonWorkItems(BaseWorkItemsView, ConceptView):
|
||||||
""" A query view showing work items for a person, the query's parent.
|
""" A view showing work items for a person or the context object's parents.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
columns = set(['Task', 'Title', 'Day', 'Start', 'End', 'Duration', 'Info'])
|
columns = set(['Task', 'Title', 'Day', 'Start', 'End', 'Duration', 'Info'])
|
||||||
|
|
||||||
|
def checkPermissions(self):
|
||||||
|
return canAccessRestricted(self.context)
|
||||||
|
|
||||||
def getCriteria(self):
|
def getCriteria(self):
|
||||||
return self.baseCriteria
|
return self.baseCriteria
|
||||||
|
|
||||||
def listWorkItems(self):
|
def listWorkItems(self):
|
||||||
criteria = self.getCriteria()
|
criteria = self.getCriteria()
|
||||||
for target in self.context.getParents([self.defaultPredicate]):
|
|
||||||
un = criteria.setdefault('userName', [])
|
un = criteria.setdefault('userName', [])
|
||||||
|
if IPerson.providedBy(self.adapted):
|
||||||
|
un.append(util.getUidForObject(self.context))
|
||||||
|
else:
|
||||||
|
for target in self.context.getParents([self.defaultPredicate]):
|
||||||
un.append(util.getUidForObject(target))
|
un.append(util.getUidForObject(target))
|
||||||
return sorted(self.query(**criteria), key=lambda x: x.track.timeStamp)
|
return sorted(self.query(**criteria), key=lambda x: x.track.timeStamp)
|
||||||
|
|
||||||
|
|
14
query.py
14
query.py
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -18,8 +18,6 @@
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Query management stuff.
|
Query management stuff.
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from BTrees.IOBTree import IOBTree
|
from BTrees.IOBTree import IOBTree
|
||||||
|
@ -33,6 +31,7 @@ from zope.cachedescriptors.property import Lazy
|
||||||
from cybertools.typology.interfaces import IType
|
from cybertools.typology.interfaces import IType
|
||||||
from loops.common import AdapterBase
|
from loops.common import AdapterBase
|
||||||
from loops.interfaces import IConcept, IConceptSchema, ILoopsAdapter
|
from loops.interfaces import IConcept, IConceptSchema, ILoopsAdapter
|
||||||
|
from loops.interfaces import IOptions
|
||||||
from loops.security.common import canListObject
|
from loops.security.common import canListObject
|
||||||
from loops.type import TypeInterfaceSourceList
|
from loops.type import TypeInterfaceSourceList
|
||||||
from loops.versioning.util import getVersion
|
from loops.versioning.util import getVersion
|
||||||
|
@ -182,7 +181,7 @@ class ConceptQuery(BaseQuery):
|
||||||
|
|
||||||
# QueryConcept: concept objects that allow querying the database.
|
# QueryConcept: concept objects that allow querying the database.
|
||||||
|
|
||||||
class IQueryConcept(IConceptSchema, ILoopsAdapter):
|
class IQueryConcept(IConceptSchema, ILoopsAdapter, IOptions):
|
||||||
""" The schema for the query type.
|
""" The schema for the query type.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -194,13 +193,6 @@ class IQueryConcept(IConceptSchema, ILoopsAdapter):
|
||||||
default=u'',
|
default=u'',
|
||||||
required=False)
|
required=False)
|
||||||
|
|
||||||
options = schema.List(
|
|
||||||
title=_(u'Options'),
|
|
||||||
description=_(u'Additional settings.'),
|
|
||||||
value_type=schema.TextLine(),
|
|
||||||
default=[],
|
|
||||||
required=False)
|
|
||||||
|
|
||||||
|
|
||||||
class QueryConcept(AdapterBase):
|
class QueryConcept(AdapterBase):
|
||||||
|
|
||||||
|
|
|
@ -74,6 +74,9 @@ def canListObject(obj, noCheck=False):
|
||||||
return True
|
return True
|
||||||
return canAccess(obj, 'title')
|
return canAccess(obj, 'title')
|
||||||
|
|
||||||
|
def canAccessRestricted(obj):
|
||||||
|
return checkPermission('loops.ViewRestricted', obj)
|
||||||
|
|
||||||
def canWriteObject(obj):
|
def canWriteObject(obj):
|
||||||
return canWrite(obj, 'title') or canAssignAsParent(obj)
|
return canWrite(obj, 'title') or canAssignAsParent(obj)
|
||||||
|
|
||||||
|
|
23
type.py
23
type.py
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2006 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -18,8 +18,6 @@
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Type management stuff.
|
Type management stuff.
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from zope import component, schema
|
from zope import component, schema
|
||||||
|
@ -28,12 +26,13 @@ from zope.interface import implements
|
||||||
from zope.cachedescriptors.property import Lazy
|
from zope.cachedescriptors.property import Lazy
|
||||||
from zope.dottedname.resolve import resolve
|
from zope.dottedname.resolve import resolve
|
||||||
from zope.security.proxy import removeSecurityProxy
|
from zope.security.proxy import removeSecurityProxy
|
||||||
from zope.traversing.api import getName
|
from zope.traversing.api import getName, getPath
|
||||||
|
|
||||||
from cybertools.typology.type import BaseType, TypeManager
|
from cybertools.typology.type import BaseType, TypeManager
|
||||||
from cybertools.typology.interfaces import ITypeManager
|
from cybertools.typology.interfaces import ITypeManager
|
||||||
from loops.interfaces import ILoopsObject, IConcept, IResource
|
from loops.interfaces import ILoopsObject, IConcept, IResource
|
||||||
from loops.interfaces import ITypeConcept
|
from loops.interfaces import ITypeConcept
|
||||||
|
from loops.interfaces import IOptions
|
||||||
from loops.interfaces import IResourceAdapter, IFile, IExternalFile, IImage
|
from loops.interfaces import IResourceAdapter, IFile, IExternalFile, IImage
|
||||||
from loops.interfaces import ITextDocument, INote
|
from loops.interfaces import ITextDocument, INote
|
||||||
from loops.concept import Concept
|
from loops.concept import Concept
|
||||||
|
@ -49,10 +48,15 @@ class LoopsType(BaseType):
|
||||||
#document=Document)
|
#document=Document)
|
||||||
containerMapping = dict(concept='concepts', resource='resources')
|
containerMapping = dict(concept='concepts', resource='resources')
|
||||||
|
|
||||||
|
isForeignReference = False
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def title(self):
|
def title(self):
|
||||||
tp = self.typeProvider
|
tp = self.typeProvider
|
||||||
return tp is None and u'Unknown Type' or tp.title
|
title = tp is None and u'Unknown Type' or tp.title
|
||||||
|
if self.isForeignReference:
|
||||||
|
title += (' (Site: %s)' % getName(self.root))
|
||||||
|
return title
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def token(self):
|
def token(self):
|
||||||
|
@ -63,7 +67,11 @@ class LoopsType(BaseType):
|
||||||
def tokenForSearch(self):
|
def tokenForSearch(self):
|
||||||
tp = self.typeProvider
|
tp = self.typeProvider
|
||||||
typeName = tp is None and 'unknown' or str(getName(tp))
|
typeName = tp is None and 'unknown' or str(getName(tp))
|
||||||
return ':'.join(('loops', self.qualifiers[0], typeName,))
|
if self.isForeignReference:
|
||||||
|
root = getPath(self.root)
|
||||||
|
else:
|
||||||
|
root = 'loops'
|
||||||
|
return ':'.join((root, self.qualifiers[0], typeName,))
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def typeInterface(self):
|
def typeInterface(self):
|
||||||
|
@ -272,7 +280,8 @@ class TypeInterfaceSourceList(object):
|
||||||
|
|
||||||
implements(schema.interfaces.IIterableSource)
|
implements(schema.interfaces.IIterableSource)
|
||||||
|
|
||||||
typeInterfaces = (ITypeConcept, IFile, IExternalFile, ITextDocument, INote)
|
typeInterfaces = (ITypeConcept, IFile, IExternalFile, ITextDocument, INote,
|
||||||
|
IOptions)
|
||||||
|
|
||||||
def __init__(self, context):
|
def __init__(self, context):
|
||||||
self.context = context
|
self.context = context
|
||||||
|
|
3
util.py
3
util.py
|
@ -141,4 +141,7 @@ def saveRequest(request):
|
||||||
local_data.request = request
|
local_data.request = request
|
||||||
|
|
||||||
def getRequest():
|
def getRequest():
|
||||||
|
try:
|
||||||
return local_data.request
|
return local_data.request
|
||||||
|
except AttributeError:
|
||||||
|
return None
|
||||||
|
|
Loading…
Add table
Reference in a new issue