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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
|
@ -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.
|
@ -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"
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
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
|
||||
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>
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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.
|
||||
"""
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue