diff --git a/browser/compound/__init__.py b/browser/compound/__init__.py new file mode 100644 index 0000000..e12cdd8 --- /dev/null +++ b/browser/compound/__init__.py @@ -0,0 +1,4 @@ +""" +package loops.browser.compound +""" + diff --git a/browser/compound/configure.zcml b/browser/compound/configure.zcml new file mode 100644 index 0000000..fac57ba --- /dev/null +++ b/browser/compound/configure.zcml @@ -0,0 +1,14 @@ + + + + + diff --git a/browser/compound/standard.py b/browser/compound/standard.py new file mode 100644 index 0000000..4e5576f --- /dev/null +++ b/browser/compound/standard.py @@ -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 + diff --git a/browser/compound/view_macros.pt b/browser/compound/view_macros.pt new file mode 100644 index 0000000..c854558 --- /dev/null +++ b/browser/compound/view_macros.pt @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/browser/concept.py b/browser/concept.py index b550830..f06c419 100644 --- a/browser/concept.py +++ b/browser/concept.py @@ -235,6 +235,7 @@ class ConceptView(BaseView): subMacro=concept_macros.macros['parents'], priority=20, info=self) + # the part-based layout is now implemented in loops.browser.compound def getParts(self): parts = (self.params.get('parts') or []) # deprecated! if not parts: diff --git a/browser/configure.zcml b/browser/configure.zcml index 37f0542..311b87b 100644 --- a/browser/configure.zcml +++ b/browser/configure.zcml @@ -772,6 +772,7 @@ attribute="cleanup" permission="zope.ManageSite" /> + diff --git a/browser/resource.py b/browser/resource.py index 8ec1e95..0960cb2 100644 --- a/browser/resource.py +++ b/browser/resource.py @@ -132,6 +132,9 @@ class ResourceView(BaseView): def macro(self): if 'image/' in self.context.contentType: return self.template.macros['image'] + #elif 'audio/' in self.context.contentType: + # self.registerDojoAudio() + # return self.template.macros['audio'] else: return self.template.macros['download'] diff --git a/knowledge/qualification/base.py b/knowledge/qualification/base.py index 32c9910..8a667e2 100644 --- a/knowledge/qualification/base.py +++ b/knowledge/qualification/base.py @@ -26,6 +26,7 @@ from zope.component import adapts from zope.interface import implementer, implements from loops.common import AdapterBase +from loops.interfaces import IConcept from loops.knowledge.qualification.interfaces import ICompetence from loops.type import TypeInterfaceSourceList diff --git a/knowledge/qualification/configure.zcml b/knowledge/qualification/configure.zcml index d8cbc74..dea246f 100644 --- a/knowledge/qualification/configure.zcml +++ b/knowledge/qualification/configure.zcml @@ -4,7 +4,14 @@ i18n_domain="loops"> + factory="loops.knowledge.qualification.base.Competence" + trusted="True" /> + + + + diff --git a/knowledge/qualification/interfaces.py b/knowledge/qualification/interfaces.py index 85a002a..87b3e2d 100644 --- a/knowledge/qualification/interfaces.py +++ b/knowledge/qualification/interfaces.py @@ -23,11 +23,11 @@ Interfaces for knowledge management and elearning with loops. from zope.interface import Interface, Attribute from zope import interface, component, schema -from loops.interfaces import IConceptSchema +from loops.interfaces import IConceptSchema, ILoopsAdapter from loops.util import _ -class ICompetence(IConceptSchema): +class ICompetence(ILoopsAdapter): """ The competence of a person. Maybe assigned to the person via a 'knows' relation or diff --git a/locales/de/LC_MESSAGES/loops.mo b/locales/de/LC_MESSAGES/loops.mo index 4308de5..0a244f1 100644 Binary files a/locales/de/LC_MESSAGES/loops.mo and b/locales/de/LC_MESSAGES/loops.mo differ diff --git a/locales/de/LC_MESSAGES/loops.po b/locales/de/LC_MESSAGES/loops.po index 77ed12b..afe516f 100644 --- a/locales/de/LC_MESSAGES/loops.po +++ b/locales/de/LC_MESSAGES/loops.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: 0.13.0\n" "POT-Creation-Date: 2007-05-22 12:00 CET\n" -"PO-Revision-Date: 2013-06-20 12:00 CET\n" +"PO-Revision-Date: 2013-07-15 12:00 CET\n" "Last-Translator: Helmut Merz \n" "Language-Team: loops developers \n" "MIME-Version: 1.0\n" @@ -965,6 +965,9 @@ msgstr "Kalender" msgid "Work Items" msgstr "Aktivitäten" +msgid "Work Items for $title" +msgstr "Aktivitäten für $title" + msgid "Day" msgstr "Tag" diff --git a/organize/stateful/base.py b/organize/stateful/base.py index fc2d942..be2a257 100644 --- a/organize/stateful/base.py +++ b/organize/stateful/base.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 # it under the terms of the GNU General Public License as published by @@ -18,8 +18,6 @@ """ Basic implementations for stateful objects and adapters. - -$Id$ """ from zope.app.catalog.interfaces import ICatalog @@ -27,6 +25,7 @@ from zope.cachedescriptors.property import Lazy from zope import component from zope.component import adapts, adapter +from cybertools.composer.schema.field import Field from cybertools.meta.interfaces import IOptions from cybertools.stateful.base import Stateful as BaseStateful 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.interfaces import ILoopsObject, IConcept, IResource from loops import util +from loops.util import _ class Stateful(BaseStateful): @@ -93,3 +93,10 @@ def handleTransition(obj, event): if next != previous: cat = component.getUtility(ICatalog) 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) diff --git a/organize/stateful/browser.py b/organize/stateful/browser.py index 1265b45..c98b073 100644 --- a/organize/stateful/browser.py +++ b/organize/stateful/browser.py @@ -28,8 +28,6 @@ from zope.i18n import translate from zope.lifecycleevent import ObjectModifiedEvent, Attributes from cybertools.browser.action import Action, actions -from cybertools.composer.schema.field import Field -from cybertools.composer.schema.interfaces import ISchemaFactory from cybertools.composer.schema.schema import Schema from cybertools.stateful.interfaces import IStateful, IStatesDefinition from loops.browser.common import BaseView @@ -124,8 +122,18 @@ class ChangeStateBase(object): 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(ObjectForm, ChangeStateBase): + +class ChangeStateForm(ChangeStateBase, ObjectForm): form_action = 'change_state_action' data = {} @@ -138,24 +146,26 @@ class ChangeStateForm(ObjectForm, ChangeStateBase): def title(self): return self.virtualTargetObject.title - @Lazy - def schema(self): - # TODO: use field information specified in transition - commentsField = Field('comments', _(u'label_transition_comments'), - 'textarea', - description=_(u'desc_transition_comments')) - fields = [commentsField] - return Schema(name='change_state', request=self.request, - manager=self, *fields) - -class ChangeState(EditObject, ChangeStateBase): +class ChangeState(ChangeStateBase, EditObject): def update(self): - comments = self.request.form.get('comments') or u'' + formData = self.request.form + # store data in context (unless field.nostore) + self.object = self.context + 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, - dict(transition=self.action, comments=comments))) + notify(ObjectModifiedEvent(self.view.virtualTargetObject, trackData)) return True diff --git a/organize/stateful/task.py b/organize/stateful/task.py index 5e60ee5..dea88d4 100644 --- a/organize/stateful/task.py +++ b/organize/stateful/task.py @@ -26,12 +26,15 @@ from zope.component import adapter from zope.interface import implementer from zope.traversing.api import getName +from cybertools.composer.schema.schema import Schema from cybertools.stateful.definition import StatesDefinition from cybertools.stateful.definition import State, Transition from cybertools.stateful.interfaces import IStatesDefinition, IStateful from loops.common import adapted +from loops.organize.stateful.base import commentsField from loops.organize.stateful.base import StatefulLoopsObject from loops.security.interfaces import ISecuritySetter +from loops.util import _ def setPermissionsForRoles(settings): @@ -42,6 +45,10 @@ def setPermissionsForRoles(settings): return setSecurity +defaultSchema = Schema(commentsField, + name='change_state') + + @implementer(IStatesDefinition) def taskStates(): return StatesDefinition('task_states', @@ -55,11 +62,11 @@ def taskStates(): color='x'), State('archived', 'archived', ('reopen',), color='grey'), - Transition('release', 'release', 'active'), - Transition('finish', 'finish', 'finished'), - Transition('cancel', 'cancel', 'cancelled'), - Transition('reopen', 're-open', 'draft'), - Transition('archive', 'archive', 'archived'), + Transition('release', 'release', 'active', schema=defaultSchema), + Transition('finish', 'finish', 'finished', schema=defaultSchema), + Transition('cancel', 'cancel', 'cancelled', schema=defaultSchema), + Transition('reopen', 're-open', 'draft', schema=defaultSchema), + Transition('archive', 'archive', 'archived', schema=defaultSchema), initialState='draft') diff --git a/organize/work/browser.py b/organize/work/browser.py index 564f90b..3a78a10 100644 --- a/organize/work/browser.py +++ b/organize/work/browser.py @@ -43,13 +43,14 @@ from loops.browser.concept import ConceptView from loops.browser.form import ObjectForm, EditObject from loops.browser.node import NodeView from loops.common import adapted +from loops.organize.interfaces import IPerson from loops.organize.party import getPersonForUser from loops.organize.stateful.browser import StateAction from loops.organize.tracking.browser import BaseTrackView from loops.organize.tracking.report import TrackDetails from loops.organize.work.base import WorkItem 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.util import _ @@ -228,6 +229,10 @@ class BaseWorkItemsView(object): def macro(self): return self.work_macros['workitems_query'] + @Lazy + def title(self): + return _(u'Work Items for $title', mapping=dict(title=self.context.title)) + @Lazy def workItems(self): rm = self.loopsRoot.getRecordManager() @@ -312,19 +317,25 @@ class RelatedTaskWorkItems(AllWorkItems): 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']) + def checkPermissions(self): + return canAccessRestricted(self.context) + def getCriteria(self): return self.baseCriteria def listWorkItems(self): criteria = self.getCriteria() - for target in self.context.getParents([self.defaultPredicate]): - un = criteria.setdefault('userName', []) - un.append(util.getUidForObject(target)) + 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)) return sorted(self.query(**criteria), key=lambda x: x.track.timeStamp) diff --git a/security/common.py b/security/common.py index 1a5fb58..13e3835 100644 --- a/security/common.py +++ b/security/common.py @@ -74,6 +74,9 @@ def canListObject(obj, noCheck=False): return True return canAccess(obj, 'title') +def canAccessRestricted(obj): + return checkPermission('loops.ViewRestricted', obj) + def canWriteObject(obj): return canWrite(obj, 'title') or canAssignAsParent(obj) diff --git a/util.py b/util.py index 9d7e46a..6ba91a9 100644 --- a/util.py +++ b/util.py @@ -145,4 +145,7 @@ def saveRequest(request): local_data.request = request def getRequest(): - return local_data.request + try: + return local_data.request + except AttributeError: + return None