added favorites portlet with add and remove functionality

git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@2452 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
helmutm 2008-03-13 17:42:05 +00:00
parent d503e5d0e5
commit 60af09cedb
12 changed files with 340 additions and 11 deletions

View file

@ -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;

View file

@ -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:

Binary file not shown.

View file

@ -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"

View file

@ -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
[<Favorite ['27', 1, '33', '...']: {}>,
<Favorite ['31', 1, '33', '...']: {}>]
[<Favorite ['27', 1, '33', '...']: {}>]
>>> 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

View file

@ -0,0 +1,62 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
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]

View file

@ -0,0 +1,23 @@
<!-- $Id$ -->
<configure
xmlns:zope="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser"
i18n_domain="loops">
<zope:adapter
factory="loops.organize.personal.browser.configurator.PortletConfigurator"
for="loops.interfaces.INode
zope.publisher.interfaces.browser.IBrowserRequest"
name="loops.organize.personal" />
<browser:pages
class="loops.organize.personal.browser.favorite.FavoriteView"
for="loops.interfaces.INode"
permission="zope.View">
<page name="favorites.html" />
<page name="addFavorite.html" attribute="add" />
<page name="removeFavorite.html" attribute="remove" />
</browser:pages>
</configure>

View file

@ -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)

View file

@ -0,0 +1,24 @@
<metal:actions define-macro="favorites_portlet"
tal:define="view nocall:context/@@favorites.html;
targetUid view/targetUid">
<div tal:repeat="item view/listFavorites">
<span style="float:right" class="delete-item">&nbsp;<a href="removeFavorite.html"
tal:attributes="href
string:${view/virtualTargetUrl}/removeFavorite.html?id=${item/uid};
title string:Remove from favorites"
i18n:attributes="title">X</a>&nbsp;</span>
<a href=""
tal:attributes="href item/url;
title item/description"
tal:content="item/title">Some object</a>
</div>
<div id="addFavorite" class="action"
tal:condition="targetUid">
<a href="addFavorite.html"
i18n:translate=""
tal:attributes="href
string:${view/virtualTargetUrl}/addFavorite.html?id=$targetUid;
title string:Add current object to favorites"
i18n:attributes="title">Add to Favorites</a>
</div>
</metal:actions>

View file

@ -2,7 +2,7 @@
<configure
xmlns="http://namespaces.zope.org/zope"
i18n_domain="zope">
i18n_domain="loops">
<class class="cybertools.tracking.btree.TrackingStorage">
<require permission="zope.View"
@ -22,7 +22,15 @@
set_schema="loops.organize.personal.interfaces.IFavorite" />
</class>
<adapter factory="loops.organize.personal.favorite.Favorites" trusted="True" />
<class class="loops.organize.personal.favorite.Favorites">
<require permission="zope.View"
interface="loops.organize.personal.interfaces.IFavorites" />
</class>
<adapter factory="loops.organize.personal.setup.SetupManager"
name="organize.personal" />
<include package=".browser" />
</configure>

View file

@ -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'

View file

@ -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.
"""