diff --git a/browser/loops.css b/browser/loops.css index 0e483b4..dbf9962 100644 --- a/browser/loops.css +++ b/browser/loops.css @@ -137,6 +137,10 @@ div.box h4 { padding: 0.2em 0.2em 0.2em 0.3em; } +div.action { + border-top: 1px solid #eeeeee; +} + div.menu-1, div.menu-2 { border-top: 1px solid #eeeeee; font-weight: bold; @@ -152,6 +156,12 @@ div.menu-1, div.menu-2 { font-size: 90% } +.delete-item a[href] { + color: #ff7777; + font-weight: bold; + text-decoration: none; +} + .subcolumn { display: inline; float: left; diff --git a/browser/node.py b/browser/node.py index 9603cd1..6aa1d67 100644 --- a/browser/node.py +++ b/browser/node.py @@ -290,6 +290,13 @@ class NodeView(BaseView): target = getVersion(target, self.request) return target + @Lazy + def targetUid(self): + if self.virtualTargetObject: + return util.getUidForObject(self.virtualTargetObject) + else: + return None + def targetView(self, name='index.html', methodName='show'): target = self.virtualTargetObject if target is not None: diff --git a/locales/de/LC_MESSAGES/loops.mo b/locales/de/LC_MESSAGES/loops.mo index 33c19e1..529e1f0 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 ba77769..932cf8b 100644 --- a/locales/de/LC_MESSAGES/loops.po +++ b/locales/de/LC_MESSAGES/loops.po @@ -65,6 +65,18 @@ msgstr "Glossareintrag anlegen..." msgid "Create Glossary Item" msgstr "Glossareintrag anlegen" +msgid "Favorites" +msgstr "Lesezeichen" + +msgid "Add to Favorites" +msgstr "Zu Lesezeichen hinzufügen" + +msgid "Add current object to favorites" +msgstr "Aktuelles Objekt zu Lesezeichen hinzufügen" + +msgid "Remove from favorites" +msgstr "Aus Lesezeichen entfernen" + msgid "Actions" msgstr "Aktionen" @@ -134,6 +146,9 @@ msgstr "Format" msgid "Link URL" msgstr "URL" +msgid "Link text" +msgstr "Link Text" + msgid "Assign Parent Concepts" msgstr "Oberbegriffe zuordnen" diff --git a/organize/personal/README.txt b/organize/personal/README.txt index b4ab241..a7dd4c9 100644 --- a/organize/personal/README.txt +++ b/organize/personal/README.txt @@ -79,6 +79,15 @@ Finally, we log in as the newly created user. >>> from loops.tests.auth import login >>> login(pJohn) +One step is still missing: As we are now working with a real principal +the security checks e.g. in views are active. So we have to provide +our user with the necessary permissions. + + >>> grantPermission = setupData.rolePermissions.grantPermissionToRole + >>> assignRole = setupData.principalRoles.assignRoleToPrincipal + >>> grantPermission('zope.View', 'zope.Member') + >>> assignRole('zope.Member', 'users.john') + Working with the favorites storage ---------------------------------- @@ -93,23 +102,26 @@ can remember as favorites. >>> d003Id = util.getUidForObject(resources['d003.txt']) >>> johnCId = util.getUidForObject(johnC) -(We always need a "run" - can we try to ignore this for favorites?) +We do not access the favorites storage directly but by using an adapter. - >>> runId = favorites.startRun() + >>> from loops.organize.personal.favorite import Favorites + >>> component.provideAdapter(Favorites) + >>> from loops.organize.personal.interfaces import IFavorites + >>> favAdapted = IFavorites(favorites) -For favorites we don't need any data... +The adapter provides convenience methods for accessing the favorites storage. - >>> favorites.saveUserTrack(d001Id, runId, johnCId, {}) + >>> favAdapted.add(resources['d001.txt'], johnC) '0000001' - >>> favorites.saveUserTrack(d003Id, runId, johnCId, {}) - '0000002' So we are now ready to query the favorites. >>> favs = favorites.query(userName=johnCId) >>> favs - [, - ] + [] + + >>> list(favAdapted.list(johnC)) + ['27'] >>> util.getObjectForUid(favs[0].taskId) is resources['d001.txt'] True @@ -117,3 +129,33 @@ So we are now ready to query the favorites. User interface -------------- + >>> from loops.view import Node + >>> home = views['home'] = Node() + >>> from loops.tests.auth import TestRequest + >>> from loops.organize.personal.browser.configurator import PortletConfigurator + + >>> portletConf = PortletConfigurator(home, TestRequest()) + >>> len(portletConf.viewProperties) + 1 + + >>> from loops.organize.personal.browser.favorite import FavoriteView + >>> view = FavoriteView(home, TestRequest()) + +Let's now trigger the saving of a favorite. + + >>> d002Id = util.getUidForObject(resources['d002.txt']) + >>> request = TestRequest(form=dict(id=d002Id)) + >>> view = FavoriteView(home, request) + + >>> view.add() + + >>> len(favorites.query(userName=johnCId)) + 2 + + >>> d002Id = util.getUidForObject(resources['d001.txt']) + >>> request = TestRequest(form=dict(id=d002Id)) + >>> view = FavoriteView(home, request) + >>> view.remove() + + >>> len(favorites.query(userName=johnCId)) + 1 diff --git a/organize/personal/browser/configurator.py b/organize/personal/browser/configurator.py new file mode 100644 index 0000000..2249c20 --- /dev/null +++ b/organize/personal/browser/configurator.py @@ -0,0 +1,62 @@ +# +# Copyright (c) 2008 Helmut Merz helmutm@cy55.de +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +""" +A view configurator provides configuration data for a view controller. + +$Id$ +""" + +from zope import component +from zope.app.pagetemplate import ViewPageTemplateFile +from zope.app.security.interfaces import IUnauthenticatedPrincipal +from zope.cachedescriptors.property import Lazy +from zope.traversing.browser.absoluteurl import absoluteURL + +from cybertools.browser.configurator import ViewConfigurator, MacroViewProperty +from loops.organize.party import getPersonForUser +from loops.util import _ + + +personal_macros = ViewPageTemplateFile('personal_macros.pt') + + +class PortletConfigurator(ViewConfigurator): + """ Specify portlets. + """ + + def __init__(self, context, request): + self.context = context + self.request = request + + @property + def viewProperties(self): + if getPersonForUser(self.context, self.request) is None: + #if IUnauthenticatedPrincipal.providedBy(self.request.principal): + return [] + favorites = MacroViewProperty(self.context, self.request) + favorites.setParams(dict( + slot='portlet_right', + identifier='loops.organize.favorites', + title=_(u'Favorites'), + subMacro=personal_macros.macros['favorites_portlet'], + priority=200, + #url=absoluteURL(self.context, self.request) + '/@@favorites.html', + )) + return [favorites] + diff --git a/organize/personal/browser/configure.zcml b/organize/personal/browser/configure.zcml new file mode 100644 index 0000000..f5bd7ce --- /dev/null +++ b/organize/personal/browser/configure.zcml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + diff --git a/organize/personal/browser/favorite.py b/organize/personal/browser/favorite.py new file mode 100644 index 0000000..6f376c3 --- /dev/null +++ b/organize/personal/browser/favorite.py @@ -0,0 +1,88 @@ +# +# Copyright (c) 2008 Helmut Merz helmutm@cy55.de +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +""" +A view (to be used by listings, portlets, ...) for favorites. + +$Id$ +""" + +from zope import component +from zope.app.pagetemplate import ViewPageTemplateFile +from zope.cachedescriptors.property import Lazy + +from cybertools.browser.configurator import ViewConfigurator, MacroViewProperty +from loops.browser.node import NodeView +from loops.organize.party import getPersonForUser +from loops.organize.personal.interfaces import IFavorites +from loops import util + + +personal_macros = ViewPageTemplateFile('personal_macros.pt') + + +class FavoriteView(NodeView): + + @Lazy + def item(self): + return self + + @Lazy + def person(self): + return getPersonForUser(self.context, self.request) + + @Lazy + def favorites(self): + records = self.loopsRoot.getRecordManager() + if records is not None: + storage = records.get('favorites') + if storage is not None: + return IFavorites(storage) + return None + + def listFavorites(self): + if self.favorites is None: + return + for uid in self.favorites.list(self.person): + obj = util.getObjectForUid(uid) + if obj is not None: + yield dict(url=self.getUrlForTarget(obj), + uid=uid, + title=obj.title, + description=obj.description, + object=obj) + + def add(self): + if self.favorites is None: + return + uid = self.request.get('id') + if not uid: + return + obj = util.getObjectForUid(uid) + self.favorites.add(obj, self.person) + self.request.response.redirect(self.virtualTargetUrl) + + def remove(self): + if self.favorites is None: + return + uid = self.request.get('id') + if not uid: + return + obj = util.getObjectForUid(uid) + self.favorites.remove(obj, self.person) + self.request.response.redirect(self.virtualTargetUrl) diff --git a/organize/personal/browser/personal_macros.pt b/organize/personal/browser/personal_macros.pt new file mode 100644 index 0000000..a6f9b52 --- /dev/null +++ b/organize/personal/browser/personal_macros.pt @@ -0,0 +1,24 @@ + +
+  X  + Some object +
+ +
diff --git a/organize/personal/configure.zcml b/organize/personal/configure.zcml index bfcbd68..c946b74 100644 --- a/organize/personal/configure.zcml +++ b/organize/personal/configure.zcml @@ -2,7 +2,7 @@ + i18n_domain="loops"> + + + + + + + diff --git a/organize/personal/favorite.py b/organize/personal/favorite.py index 826bd2a..2f7cd3b 100644 --- a/organize/personal/favorite.py +++ b/organize/personal/favorite.py @@ -22,10 +22,51 @@ Base classes for a notification framework. $Id$ """ +from zope.component import adapts from zope.interface import implements from cybertools.tracking.btree import Track -from loops.organize.personal.interfaces import IFavorite +from cybertools.tracking.interfaces import ITrackingStorage +from loops.organize.personal.interfaces import IFavorites, IFavorite +from loops import util + + +class Favorites(object): + + implements(IFavorites) + adapts(ITrackingStorage) + + def __init__(self, context): + self.context = context + + def list(self, person, sortKey=None): + if person is None: + return + personUid = util.getUidForObject(person) + if sortKey is None: + sortKey = lambda x: -x.timeStamp + for item in sorted(self.context.query(userName=personUid), key=sortKey): + yield item.taskId + + def add(self, obj, person): + if None in (obj, person): + return False + uid = util.getUidForObject(obj) + personUid = util.getUidForObject(person) + if self.context.query(userName=personUid, taskId=uid): + return False + return self.context.saveUserTrack(uid, 0, personUid, {}) + + def remove(self, obj, person): + if None in (obj, person): + return False + uid = util.getUidForObject(obj) + personUid = util.getUidForObject(person) + changed = False + for t in self.context.query(userName=personUid, taskId=uid): + changed = True + self.context.removeTrack(t) + return changed class Favorite(Track): @@ -33,3 +74,4 @@ class Favorite(Track): implements(IFavorite) typeName = 'Favorite' + diff --git a/organize/personal/interfaces.py b/organize/personal/interfaces.py index b3fe7ad..adfdea7 100644 --- a/organize/personal/interfaces.py +++ b/organize/personal/interfaces.py @@ -28,10 +28,18 @@ from zope import schema from cybertools.tracking.interfaces import ITrack +class IFavorites(Interface): + """ A collection of favorites. + """ + + def add(objectId, personId): + """ Add an object to the person's favorites collection. + """ + + class IFavorite(ITrack): """ A favorite references a content object via the task id attribute; the user name references the user/person for which the favorite is to be stored. The tracking storage's run management is not used. """ -