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:
parent
d503e5d0e5
commit
60af09cedb
12 changed files with 340 additions and 11 deletions
|
@ -137,6 +137,10 @@ div.box h4 {
|
||||||
padding: 0.2em 0.2em 0.2em 0.3em;
|
padding: 0.2em 0.2em 0.2em 0.3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.action {
|
||||||
|
border-top: 1px solid #eeeeee;
|
||||||
|
}
|
||||||
|
|
||||||
div.menu-1, div.menu-2 {
|
div.menu-1, div.menu-2 {
|
||||||
border-top: 1px solid #eeeeee;
|
border-top: 1px solid #eeeeee;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
@ -152,6 +156,12 @@ div.menu-1, div.menu-2 {
|
||||||
font-size: 90%
|
font-size: 90%
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.delete-item a[href] {
|
||||||
|
color: #ff7777;
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
.subcolumn {
|
.subcolumn {
|
||||||
display: inline;
|
display: inline;
|
||||||
float: left;
|
float: left;
|
||||||
|
|
|
@ -290,6 +290,13 @@ class NodeView(BaseView):
|
||||||
target = getVersion(target, self.request)
|
target = getVersion(target, self.request)
|
||||||
return target
|
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'):
|
def targetView(self, name='index.html', methodName='show'):
|
||||||
target = self.virtualTargetObject
|
target = self.virtualTargetObject
|
||||||
if target is not None:
|
if target is not None:
|
||||||
|
|
Binary file not shown.
|
@ -65,6 +65,18 @@ msgstr "Glossareintrag anlegen..."
|
||||||
msgid "Create Glossary Item"
|
msgid "Create Glossary Item"
|
||||||
msgstr "Glossareintrag anlegen"
|
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"
|
msgid "Actions"
|
||||||
msgstr "Aktionen"
|
msgstr "Aktionen"
|
||||||
|
|
||||||
|
@ -134,6 +146,9 @@ msgstr "Format"
|
||||||
msgid "Link URL"
|
msgid "Link URL"
|
||||||
msgstr "URL"
|
msgstr "URL"
|
||||||
|
|
||||||
|
msgid "Link text"
|
||||||
|
msgstr "Link Text"
|
||||||
|
|
||||||
msgid "Assign Parent Concepts"
|
msgid "Assign Parent Concepts"
|
||||||
msgstr "Oberbegriffe zuordnen"
|
msgstr "Oberbegriffe zuordnen"
|
||||||
|
|
||||||
|
|
|
@ -79,6 +79,15 @@ Finally, we log in as the newly created user.
|
||||||
>>> from loops.tests.auth import login
|
>>> from loops.tests.auth import login
|
||||||
>>> login(pJohn)
|
>>> 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
|
Working with the favorites storage
|
||||||
----------------------------------
|
----------------------------------
|
||||||
|
|
||||||
|
@ -93,23 +102,26 @@ can remember as favorites.
|
||||||
>>> d003Id = util.getUidForObject(resources['d003.txt'])
|
>>> d003Id = util.getUidForObject(resources['d003.txt'])
|
||||||
>>> johnCId = util.getUidForObject(johnC)
|
>>> 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'
|
'0000001'
|
||||||
>>> favorites.saveUserTrack(d003Id, runId, johnCId, {})
|
|
||||||
'0000002'
|
|
||||||
|
|
||||||
So we are now ready to query the favorites.
|
So we are now ready to query the favorites.
|
||||||
|
|
||||||
>>> favs = favorites.query(userName=johnCId)
|
>>> favs = favorites.query(userName=johnCId)
|
||||||
>>> favs
|
>>> favs
|
||||||
[<Favorite ['27', 1, '33', '...']: {}>,
|
[<Favorite ['27', 1, '33', '...']: {}>]
|
||||||
<Favorite ['31', 1, '33', '...']: {}>]
|
|
||||||
|
>>> list(favAdapted.list(johnC))
|
||||||
|
['27']
|
||||||
|
|
||||||
>>> util.getObjectForUid(favs[0].taskId) is resources['d001.txt']
|
>>> util.getObjectForUid(favs[0].taskId) is resources['d001.txt']
|
||||||
True
|
True
|
||||||
|
@ -117,3 +129,33 @@ So we are now ready to query the favorites.
|
||||||
User interface
|
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
|
||||||
|
|
62
organize/personal/browser/configurator.py
Normal file
62
organize/personal/browser/configurator.py
Normal 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]
|
||||||
|
|
23
organize/personal/browser/configure.zcml
Normal file
23
organize/personal/browser/configure.zcml
Normal 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>
|
88
organize/personal/browser/favorite.py
Normal file
88
organize/personal/browser/favorite.py
Normal 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)
|
24
organize/personal/browser/personal_macros.pt
Normal file
24
organize/personal/browser/personal_macros.pt
Normal 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"> <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> </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>
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
<configure
|
<configure
|
||||||
xmlns="http://namespaces.zope.org/zope"
|
xmlns="http://namespaces.zope.org/zope"
|
||||||
i18n_domain="zope">
|
i18n_domain="loops">
|
||||||
|
|
||||||
<class class="cybertools.tracking.btree.TrackingStorage">
|
<class class="cybertools.tracking.btree.TrackingStorage">
|
||||||
<require permission="zope.View"
|
<require permission="zope.View"
|
||||||
|
@ -22,7 +22,15 @@
|
||||||
set_schema="loops.organize.personal.interfaces.IFavorite" />
|
set_schema="loops.organize.personal.interfaces.IFavorite" />
|
||||||
</class>
|
</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"
|
<adapter factory="loops.organize.personal.setup.SetupManager"
|
||||||
name="organize.personal" />
|
name="organize.personal" />
|
||||||
|
|
||||||
|
<include package=".browser" />
|
||||||
|
|
||||||
</configure>
|
</configure>
|
||||||
|
|
|
@ -22,10 +22,51 @@ Base classes for a notification framework.
|
||||||
$Id$
|
$Id$
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from zope.component import adapts
|
||||||
from zope.interface import implements
|
from zope.interface import implements
|
||||||
|
|
||||||
from cybertools.tracking.btree import Track
|
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):
|
class Favorite(Track):
|
||||||
|
@ -33,3 +74,4 @@ class Favorite(Track):
|
||||||
implements(IFavorite)
|
implements(IFavorite)
|
||||||
|
|
||||||
typeName = 'Favorite'
|
typeName = 'Favorite'
|
||||||
|
|
||||||
|
|
|
@ -28,10 +28,18 @@ from zope import schema
|
||||||
from cybertools.tracking.interfaces import ITrack
|
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):
|
class IFavorite(ITrack):
|
||||||
""" A favorite references a content object via the
|
""" A favorite references a content object via the
|
||||||
task id attribute; the user name references
|
task id attribute; the user name references
|
||||||
the user/person for which the favorite is to be stored.
|
the user/person for which the favorite is to be stored.
|
||||||
The tracking storage's run management is not used.
|
The tracking storage's run management is not used.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue