From 4a0b31b34d88621d791554b62dce6e184e812b78 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Fri, 30 Oct 2015 10:50:05 +0100 Subject: [PATCH 1/7] notifications listing fully operative with marking notifications as read and filtering --- organize/personal/browser/configure.zcml | 7 +++ organize/personal/browser/notification.py | 31 +++++++++--- organize/personal/browser/personal_macros.pt | 50 ++++++++++++-------- organize/personal/notification.py | 7 ++- 4 files changed, 67 insertions(+), 28 deletions(-) diff --git a/organize/personal/browser/configure.zcml b/organize/personal/browser/configure.zcml index 5d7ff50..5291df4 100644 --- a/organize/personal/browser/configure.zcml +++ b/organize/personal/browser/configure.zcml @@ -35,4 +35,11 @@ class="loops.organize.personal.browser.notification.NotificationsListing" permission="zope.View" /> + + diff --git a/organize/personal/browser/notification.py b/organize/personal/browser/notification.py index 90697f3..075cf7b 100644 --- a/organize/personal/browser/notification.py +++ b/organize/personal/browser/notification.py @@ -24,7 +24,8 @@ from zope import component from zope.app.pagetemplate import ViewPageTemplateFile from zope.cachedescriptors.property import Lazy -from cybertools.util.date import formatTimeStamp +from cybertools.browser.form import FormController +from cybertools.util.date import formatTimeStamp, getTimeStamp from loops.browser.concept import ConceptView from loops.common import adapted, baseObject from loops.organize.personal.notification import Notifications @@ -49,11 +50,12 @@ class NotificationsListing(ConceptView): def notifications(self): return Notifications(self.person) - def getNotifications(self, unreadOnly=True): - tracks = self.notifications.listTracks() + def getNotifications(self, unreadOnly): + tracks = self.notifications.listTracks(unreadOnly) return tracks - def getNotificationsFormatted(self, unreadOnly=True): + def getNotificationsFormatted(self): + unreadOnly = not self.request.form.get('show_all') result = [] for track in self.getNotifications(unreadOnly): data = track.data @@ -61,8 +63,11 @@ class NotificationsListing(ConceptView): sender = dict(label=s.title, url=self.nodeView.getUrlForTarget(baseObject(s))) obj = util.getObjectForUid(track.taskId) - object = dict(label=obj.title, - url=self.nodeView.getUrlForTarget(baseObject(obj))) + ov = self.nodeView.getViewForTarget(obj) + url = '%s?form.action=notification_read&track=%s' % ( + self.nodeView.getUrlForTarget(obj), + util.getUidForObject(track)) + object = dict(label=ov.title, url=url) read_ts = self.formatTimeStamp(data.get('read_ts')) item = dict(timeStamp=self.formatTimeStamp(track.timeStamp), sender=sender, @@ -71,3 +76,17 @@ class NotificationsListing(ConceptView): read_ts=read_ts) result.append(item) return result + + +class ReadNotification(FormController): + + def update(self): + form = self.request.form + trackId = form.get('track') + track = util.getObjectForUid(trackId) + data = track.data + alreadyRead = data.get('read_ts') + if not alreadyRead: + data['read_ts'] = getTimeStamp() + track.data = data + return True diff --git a/organize/personal/browser/personal_macros.pt b/organize/personal/browser/personal_macros.pt index c5988f8..0a76c59 100644 --- a/organize/personal/browser/personal_macros.pt +++ b/organize/personal/browser/personal_macros.pt @@ -50,25 +50,35 @@
- - - - - - - - - - - - -
Date/TimeSenderObjectTextDate/Time read
- - - - -
+
+ + +
  + + + + + + + + + + + + +
Date/TimeSenderObjectMessageDate/Time read
+ + + + +
+
diff --git a/organize/personal/notification.py b/organize/personal/notification.py index 5f65f60..f575e8e 100644 --- a/organize/personal/notification.py +++ b/organize/personal/notification.py @@ -33,9 +33,12 @@ class Notifications(Favorites): self.context = (baseObject(person). getLoopsRoot().getRecordManager()['favorites']) - def listTracks(self): - return super(Notifications, self).listTracks( + def listTracks(self, unreadOnly=False): + tracks = super(Notifications, self).listTracks( baseObject(self.person), type='notification') + if unreadOnly: + tracks = [t for t in tracks if not t.data.get('read_ts')] + return tracks def add(self, obj, sender, text): senderUid = util.getUidForObject(baseObject(sender)) From ec28357ba7b1eff8d5ef4479e0a7937962f055e6 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Fri, 30 Oct 2015 12:52:42 +0100 Subject: [PATCH 2/7] notifications: portlet, translations --- locales/de/LC_MESSAGES/loops.mo | Bin 24706 -> 25166 bytes locales/de/LC_MESSAGES/loops.po | 26 +++++++++++++++- organize/personal/browser/configurator.py | 30 ++++++++++++++++--- organize/personal/browser/configure.zcml | 6 ++++ organize/personal/browser/notification.py | 8 ++++- organize/personal/browser/personal_macros.pt | 18 ++++++++++- 6 files changed, 81 insertions(+), 7 deletions(-) diff --git a/locales/de/LC_MESSAGES/loops.mo b/locales/de/LC_MESSAGES/loops.mo index 0a244f173ecc1ea52203c0a912b2be6fdf7e081d..e8b16d3313e0f9fd7d5253bcb3682cac003859cf 100644 GIT binary patch delta 9502 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 diff --git a/locales/de/LC_MESSAGES/loops.po b/locales/de/LC_MESSAGES/loops.po index afe516f..7d440da 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-07-15 12:00 CET\n" +"PO-Revision-Date: 2015-10-30 12:00 CET\n" "Last-Translator: Helmut Merz \n" "Language-Team: loops developers \n" "MIME-Version: 1.0\n" @@ -483,6 +483,30 @@ msgstr "Lesezeichen für aktuelles Objekt hinzufügen" msgid "Remove from favorites" msgstr "Lesezeichen entfernen" +msgid "Notifications" +msgstr "Nachrichten" + +msgid "No new notifications" +msgstr "Keine neuen Nachrichten" + +msgid "${numNews} new notification" +msgstr "${numNews} neue Nachricht" + +msgid "${numNews} new notifications" +msgstr "${numNews} neue Nachrichten" + +msgid "Show all notifications" +msgstr "Alle Nachrichten anzeigen" + +msgid "Sender" +msgstr "Absender" + +msgid "Object" +msgstr "Objekt" + +msgid "Date/Time Read" +msgstr "Gelesen" + msgid "Personal Informations" msgstr "Persönliche Informationen" diff --git a/organize/personal/browser/configurator.py b/organize/personal/browser/configurator.py index 8ce2f9e..80f6089 100644 --- a/organize/personal/browser/configurator.py +++ b/organize/personal/browser/configurator.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2010 Helmut Merz helmutm@cy55.de +# Copyright (c) 2015 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 @@ """ A view configurator provides configuration data for a view controller. - -$Id$ """ from zope import component @@ -30,6 +28,7 @@ from zope.traversing.browser.absoluteurl import absoluteURL from cybertools.browser.configurator import ViewConfigurator, MacroViewProperty from cybertools.meta.interfaces import IOptions +from loops.browser.node import NodeView from loops.organize.party import getPersonForUser from loops.util import _ @@ -44,10 +43,11 @@ class PortletConfigurator(ViewConfigurator): def __init__(self, context, request): self.context = context self.request = request + self.view = NodeView(self.context, self.request) @property def viewProperties(self): - return self.favorites + self.filters + return self.favorites + self.filters + self.notifications @Lazy def records(self): @@ -97,3 +97,25 @@ class PortletConfigurator(ViewConfigurator): #url=absoluteURL(self.context, self.request) + '/@@filters.html', )) return [filters] + + @property + def notifications(self): + if self.person is None: + return [] + notif = self.view.globalOptions.organize.showNotifications + if not notif: + return [] + if isinstance(notif, list): + notifPage = notif[0] + else: + notifPage = 'notifications' + portlet = MacroViewProperty(self.context, self.request) + portlet.setParams(dict( + slot='portlet_left', + identifier='loops.organize.notifications', + title=_(u'Notifications'), + subMacro=personal_macros.macros['notifications_portlet'], + priority=10, + url='%s/%s' % (self.view.menu.url, notifPage), + )) + return [portlet] diff --git a/organize/personal/browser/configure.zcml b/organize/personal/browser/configure.zcml index 5291df4..7eadee8 100644 --- a/organize/personal/browser/configure.zcml +++ b/organize/personal/browser/configure.zcml @@ -35,6 +35,12 @@ class="loops.organize.personal.browser.notification.NotificationsListing" permission="zope.View" /> + + +
No new notifications
+ + + + +
@@ -65,7 +81,7 @@ Sender Object Message - Date/Time read + Date/Time Read From a2dca10e150e47a113ecf620e7bee2ce4c9bdc7c Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sat, 31 Oct 2015 10:05:13 +0100 Subject: [PATCH 3/7] show portlet only if there are any notifications --- organize/personal/README.txt | 7 ++++++- organize/personal/browser/configurator.py | 3 +++ organize/personal/notification.py | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/organize/personal/README.txt b/organize/personal/README.txt index ca8ff4d..59c0ff6 100644 --- a/organize/personal/README.txt +++ b/organize/personal/README.txt @@ -146,9 +146,14 @@ When the notification is marked as read the read timestamp will be set. It's possible to store more than one notification concerning the same object. >>> notifications.add(d001, person, 'I send myself another letter.') - >>> len(list(notifications.listTracks())) + >>> len(list(notifications.listTracks(unreadOnly=False))) 2 +Only unread notifications are listed by default. + + >>> len(list(notifications.listTracks())) + 1 + User interface -------------- diff --git a/organize/personal/browser/configurator.py b/organize/personal/browser/configurator.py index 80f6089..34cae7e 100644 --- a/organize/personal/browser/configurator.py +++ b/organize/personal/browser/configurator.py @@ -30,6 +30,7 @@ from cybertools.browser.configurator import ViewConfigurator, MacroViewProperty from cybertools.meta.interfaces import IOptions from loops.browser.node import NodeView from loops.organize.party import getPersonForUser +from loops.organize.personal.notification import Notifications from loops.util import _ @@ -105,6 +106,8 @@ class PortletConfigurator(ViewConfigurator): notif = self.view.globalOptions.organize.showNotifications if not notif: return [] + if not Notifications(self.person).listTracks(unreadOnly=False): + return [] if isinstance(notif, list): notifPage = notif[0] else: diff --git a/organize/personal/notification.py b/organize/personal/notification.py index f575e8e..76617f2 100644 --- a/organize/personal/notification.py +++ b/organize/personal/notification.py @@ -33,7 +33,7 @@ class Notifications(Favorites): self.context = (baseObject(person). getLoopsRoot().getRecordManager()['favorites']) - def listTracks(self, unreadOnly=False): + def listTracks(self, unreadOnly=True): tracks = super(Notifications, self).listTracks( baseObject(self.person), type='notification') if unreadOnly: From 5c90a19859b03416a49dd6c36e4494d681d7c07a Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sat, 31 Oct 2015 10:56:09 +0100 Subject: [PATCH 4/7] notification: minor fixes --- organize/personal/browser/configurator.py | 2 +- organize/personal/browser/notification.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/organize/personal/browser/configurator.py b/organize/personal/browser/configurator.py index 34cae7e..e93e44e 100644 --- a/organize/personal/browser/configurator.py +++ b/organize/personal/browser/configurator.py @@ -106,7 +106,7 @@ class PortletConfigurator(ViewConfigurator): notif = self.view.globalOptions.organize.showNotifications if not notif: return [] - if not Notifications(self.person).listTracks(unreadOnly=False): + if not list(Notifications(self.person).listTracks(unreadOnly=False)): return [] if isinstance(notif, list): notifPage = notif[0] diff --git a/organize/personal/browser/notification.py b/organize/personal/browser/notification.py index 19404c1..8d1a82d 100644 --- a/organize/personal/browser/notification.py +++ b/organize/personal/browser/notification.py @@ -52,6 +52,8 @@ class NotificationsListing(ConceptView): return Notifications(self.person) def getNotifications(self, unreadOnly=True): + if self.person is None: + return [] tracks = self.notifications.listTracks(unreadOnly) return tracks From 357c660659698b8d28f0b2640cb5e772c151f9c2 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Tue, 3 Nov 2015 17:26:34 +0100 Subject: [PATCH 5/7] notification: avoid error because of user without person --- organize/personal/browser/notification.py | 7 +++++-- organize/personal/browser/personal_macros.pt | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/organize/personal/browser/notification.py b/organize/personal/browser/notification.py index 8d1a82d..2258125 100644 --- a/organize/personal/browser/notification.py +++ b/organize/personal/browser/notification.py @@ -63,8 +63,11 @@ class NotificationsListing(ConceptView): for track in self.getNotifications(unreadOnly): data = track.data s = util.getObjectForUid(data.get('sender')) - sender = dict(label=s.title, - url=self.nodeView.getUrlForTarget(baseObject(s))) + if s is None: + sender = dict(label=u'???', url=u'') + else: + sender = dict(label=s.title, + url=self.nodeView.getUrlForTarget(baseObject(s))) obj = util.getObjectForUid(track.taskId) ov = self.nodeView.getViewForTarget(obj) url = '%s?form.action=notification_read&track=%s' % ( diff --git a/organize/personal/browser/personal_macros.pt b/organize/personal/browser/personal_macros.pt index 01cbb8a..a127124 100644 --- a/organize/personal/browser/personal_macros.pt +++ b/organize/personal/browser/personal_macros.pt @@ -86,7 +86,8 @@ - Date: Wed, 4 Nov 2015 09:27:50 +0100 Subject: [PATCH 6/7] show link to notifications page also for 'no new notifications' --- organize/personal/browser/personal_macros.pt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/organize/personal/browser/personal_macros.pt b/organize/personal/browser/personal_macros.pt index a127124..91792f6 100644 --- a/organize/personal/browser/personal_macros.pt +++ b/organize/personal/browser/personal_macros.pt @@ -50,8 +50,10 @@ -
No new notifications
+
From a3b649582cbd6a23f161394f1c7f347d00e0a9c4 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Wed, 4 Nov 2015 14:59:35 +0100 Subject: [PATCH 7/7] add states definition for person --- organize/stateful/README.txt | 6 +++ organize/stateful/browser.py | 1 + organize/stateful/configure.zcml | 14 +++++++ organize/stateful/person.py | 63 ++++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+) create mode 100644 organize/stateful/person.py diff --git a/organize/stateful/README.txt b/organize/stateful/README.txt index 11182e4..d9906f1 100644 --- a/organize/stateful/README.txt +++ b/organize/stateful/README.txt @@ -181,6 +181,12 @@ Querying objects by state [<...>] +Person States +============= + + >>> from loops.organize.stateful.person import personStates + + Task States =========== diff --git a/organize/stateful/browser.py b/organize/stateful/browser.py index 2d05748..03363fe 100644 --- a/organize/stateful/browser.py +++ b/organize/stateful/browser.py @@ -44,6 +44,7 @@ template = ViewPageTemplateFile('view_macros.pt') statefulActions = ('classification_quality', 'simple_publishing', + 'person_states', 'task_states', 'publishable_task',) diff --git a/organize/stateful/configure.zcml b/organize/stateful/configure.zcml index 30ad487..89e0c73 100644 --- a/organize/stateful/configure.zcml +++ b/organize/stateful/configure.zcml @@ -35,6 +35,20 @@ set_schema="cybertools.stateful.interfaces.IStateful" /> + + + + + + + + diff --git a/organize/stateful/person.py b/organize/stateful/person.py new file mode 100644 index 0000000..06ee292 --- /dev/null +++ b/organize/stateful/person.py @@ -0,0 +1,63 @@ +# +# Copyright (c) 2015 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 +# + +""" +Implementations for stateful persons. +""" + +from zope.app.security.settings import Allow, Deny, Unset +from zope import component +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 _ + + +defaultSchema = Schema(commentsField, + name='change_state') + + +@implementer(IStatesDefinition) +def personStates(): + return StatesDefinition('person_states', + State('prospective', 'prospective', ('activate', 'inactivate',), + color='blue'), + State('active', 'active', ('reset', 'inactivate',), + color='green'), + State('inactive', 'inactive', ('reactivate',), + color='x'), + Transition('activate', 'activate', 'active', schema=defaultSchema), + Transition('reset', 'reset', 'prospective', schema=defaultSchema), + Transition('inactivate', 'inactivate', 'inactive', schema=defaultSchema), + Transition('reactivate', 'reactivate', 'active', schema=defaultSchema), + initialState='active') + + +class StatefulPerson(StatefulLoopsObject): + + statesDefinition = 'person_states' +