[Checkins] SVN: lovely.tag/trunk/src/lovely/tag/ added management
methods, changed signature of getCloud to the same as getTags
Bernd Dorn
bernd.dorn at lovelysystems.com
Thu Dec 21 12:27:40 EST 2006
Log message for revision 71642:
added management methods, changed signature of getCloud to the same as getTags
Changed:
U lovely.tag/trunk/src/lovely/tag/README.txt
U lovely.tag/trunk/src/lovely/tag/browser/configure.zcml
A lovely.tag/trunk/src/lovely/tag/browser/engine.py
A lovely.tag/trunk/src/lovely/tag/browser/manage.pt
U lovely.tag/trunk/src/lovely/tag/configure.zcml
U lovely.tag/trunk/src/lovely/tag/engine.py
U lovely.tag/trunk/src/lovely/tag/interfaces.py
U lovely.tag/trunk/src/lovely/tag/tagging.py
-=-
Modified: lovely.tag/trunk/src/lovely/tag/README.txt
===================================================================
--- lovely.tag/trunk/src/lovely/tag/README.txt 2006-12-21 06:21:51 UTC (rev 71641)
+++ lovely.tag/trunk/src/lovely/tag/README.txt 2006-12-21 17:27:38 UTC (rev 71642)
@@ -18,8 +18,8 @@
<TaggingEngine entries=0>
The first step is to associate tags with an item for a user. Items are
-referenced by id, the user is a system-wide unique string and the tags is a
-simple list of strings.
+referenced by their intId, the user is a system-wide unique string and
+the tags is a simple list of strings.
Before updating the engine we need to ensure that persistent objects can be
adapted to key references:
@@ -207,6 +207,7 @@
>>> sorted(engine.getUsers())
[u'jodok']
+In order to delete entries globaly use the delete method described below.
Tag Object
----------
@@ -264,7 +265,8 @@
>>> zope.component.provideUtility(engine, tag.interfaces.ITaggingEngine)
>>> from zope.app import intid
- >>> zope.component.provideUtility(intid.IntIds(), intid.interfaces.IIntIds)
+ >>> intIds = intid.IntIds()
+ >>> zope.component.provideUtility(intIds, intid.interfaces.IIntIds)
Adapting the file to be tagged should fail:
@@ -393,7 +395,7 @@
Tag clouds contain tags and their frequency.
The ``getCloud`` method returns a set of tuples in the form of
-('tag', frequency).
+('tag', frequency). It takes the same arguments as getTags.
>>> type(engine.getCloud())
<type 'set'>
@@ -414,22 +416,20 @@
Of course you can generate clouds on item basis. You can't pass a tuple of
items, only a single one is allowed:
- >>> sorted(engine.getCloud(item=1))
+ >>> sorted(engine.getCloud(items=[1]))
[(u'USA', 1)]
The same applies to queries by user:
- >>> sorted(engine.getCloud(user=u'srichter'))
+ >>> sorted(engine.getCloud(users=[u'srichter']))
[(u'guru', 1), (u'zope3', 1)]
-It makes no sense to combine user and item. This will never be a cloud.
+Or more users, and a few items.
- >>> engine.getCloud(item=1, user=u'srichter')
- Traceback (most recent call last):
- ...
- ValueError: You cannot specify both, an item and an user.
+ >>> sorted(engine.getCloud(items=[1, 2, 3], users=[u'srichter', u'jodok']))
+ [(u'Austria', 1), (u'USA', 1), (u'austria', 1),
+ (u'lovely', 1), (u'personal', 1), (u'work', 1)]
-
Related Tags
------------
@@ -465,3 +465,118 @@
[(u'Austria', 2), (u'USA', 3), (u'jukart', 0)]
+Removal of Tag objects
+----------------------
+
+
+When an object is unregistered from the intids utility it will be
+removed from each engine. Let us see how much items we have so far.
+
+ >>> len(engine.getItems())
+ 5
+ >>> len(namedEngine.getItems())
+ 1
+
+We can use the delete method of the tagging engine to delete tag
+objects by defining the user, item or a tag name.
+
+ >>> u'austria' in engine.getTags()
+ True
+ >>> engine.delete(tag=u'austria')
+ >>> u'austria' in engine.getTags()
+ False
+
+If we delete tags for a user, the tags still exists for other users.
+
+ >>> sorted(engine.getTags(users=(u'jodok',)))
+ [u'Austria', u'USA', u'dornbirn', u'lovely',
+ u'personal', u'vacation', u'work']
+ >>> engine.delete(user=u'jodok')
+ >>> sorted(engine.getTags(users=(u'jodok',)))
+ []
+ >>> sorted(engine.getTags())
+ [u'Austria', u'Bizau', u'USA', u'guru', u'lovely', u'zope3']
+
+This is also possible with items.
+
+ >>> sorted(engine.getTags(items=(3,)))
+ [u'Austria', u'Bizau']
+
+Let us add a tag tag from the item to another item to show the behaviour.
+
+ >>> engine.update(2, u'srichter', [u'Austria'])
+ >>> engine.delete(item=3)
+ >>> sorted(engine.getTags(items=(3,)))
+ []
+
+The 'Austria' tag is still there.
+
+ >>> sorted(engine.getTags())
+ [u'Austria', u'USA', u'guru', u'lovely', u'zope3']
+
+Let us setup the handler and events.
+
+ >>> from zope.component import eventtesting
+ >>> from zope import event
+ >>> from lovely.tag.engine import removeItemSubscriber
+ >>> from zope.app.intid.interfaces import IntIdRemovedEvent
+ >>> from zope.app.intid import removeIntIdSubscriber
+ >>> zope.component.provideHandler(removeItemSubscriber)
+
+If we now fire the intid remove event with our image object, it should
+get removed in both engines.
+
+ >>> len(namedEngine.getItems())
+ 1
+ >>> len(engine.getItems())
+ 2
+ >>> removeIntIdSubscriber(image, None)
+ >>> len(namedEngine.getItems())
+ 0
+ >>> len(engine.getItems())
+ 1
+
+
+Removing Stale Items
+--------------------
+
+You can remove stale items from the tagging engine. Stale means that
+the item is not available anymore by the intids utility.
+
+Because we removed any objects with intids before, we have an empty
+intid utility.
+
+ >>> sorted(intIds.refs.keys())
+ []
+
+But above we defined an item with an id that does not exist. So this
+is a stale item.
+
+ >>> sorted(engine.getItems())
+ [2]
+
+Let us add our image object again.
+
+ >>> tagging = tag.interfaces.ITagging(image)
+ >>> tagging.update(u'srichter', u'newtag')
+
+This is our first and only entry in the intid util
+
+ >>> intIds.refs.keys()[0] in engine.getItems()
+ True
+
+Our stale entry is 2. The intids of the items deleted are returned.
+
+ >>> 2 in engine.getItems()
+ True
+ >>> engine.cleanStaleItems()
+ [2]
+
+We now only have our real image item.
+
+ >>> 2 in engine.getItems()
+ False
+ >>> len(engine.getItems())
+ 1
+ >>> sorted(engine.getItems())[0] == intIds.refs.keys()[0]
+ True
Modified: lovely.tag/trunk/src/lovely/tag/browser/configure.zcml
===================================================================
--- lovely.tag/trunk/src/lovely/tag/browser/configure.zcml 2006-12-21 06:21:51 UTC (rev 71641)
+++ lovely.tag/trunk/src/lovely/tag/browser/configure.zcml 2006-12-21 17:27:38 UTC (rev 71642)
@@ -43,5 +43,15 @@
class=".tag.UserTagForm"
name="editTags.html"
/>
+
+ <browser:page
+ menu="zmi_views"
+ title="Manage"
+ for="lovely.tag.interfaces.ITaggingEngine"
+ permission="lovely.tag.ManageEngine"
+ class=".engine.ManageView"
+ name="manage.html"
+ />
+
</configure>
Added: lovely.tag/trunk/src/lovely/tag/browser/engine.py
===================================================================
--- lovely.tag/trunk/src/lovely/tag/browser/engine.py 2006-12-21 06:21:51 UTC (rev 71641)
+++ lovely.tag/trunk/src/lovely/tag/browser/engine.py 2006-12-21 17:27:38 UTC (rev 71642)
@@ -0,0 +1,38 @@
+##############################################################################
+#
+# Copyright (c) 2006 Lovely Systems and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Tagging Views
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+
+from zope.publisher.browser import BrowserView
+from zope import component, schema
+from zope.cachedescriptors.property import Lazy
+from zope.app.pagetemplate import ViewPageTemplateFile
+from zope.formlib import form
+from lovely.tag import _
+
+from zope.formlib import form
+
+class ManageView(form.PageForm):
+
+ label=_(u"Manage Tagging Engine")
+
+ form_fields = form.Fields()
+
+ @form.action(label=_(u'Clean Stale Items'))
+ def cleanStale(self, action, data):
+ cleaned = self.context.cleanStaleItems()
+ self.status = u'Cleaned out %s items' % len(cleaned)
Property changes on: lovely.tag/trunk/src/lovely/tag/browser/engine.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: lovely.tag/trunk/src/lovely/tag/browser/manage.pt
===================================================================
--- lovely.tag/trunk/src/lovely/tag/browser/manage.pt 2006-12-21 06:21:51 UTC (rev 71641)
+++ lovely.tag/trunk/src/lovely/tag/browser/manage.pt 2006-12-21 17:27:38 UTC (rev 71642)
@@ -0,0 +1,10 @@
+<html metal:use-macro="context/@@standard_macros/view"
+ i18n:domain="zope">
+ <body>
+
+ <div metal:fill-slot="body" class="content">
+
+
+ </div>
+ </body>
+</html>
Property changes on: lovely.tag/trunk/src/lovely/tag/browser/manage.pt
___________________________________________________________________
Name: svn:eol-style
+ native
Modified: lovely.tag/trunk/src/lovely/tag/configure.zcml
===================================================================
--- lovely.tag/trunk/src/lovely/tag/configure.zcml 2006-12-21 06:21:51 UTC (rev 71641)
+++ lovely.tag/trunk/src/lovely/tag/configure.zcml 2006-12-21 17:27:38 UTC (rev 71642)
@@ -16,6 +16,12 @@
description="Allow accessing any tags related statistics."
/>
+ <permission
+ id="lovely.tag.ManageEngine"
+ title="Manage Tagging Engine"
+ description="Allow management of a Tagging Engine"
+ />
+
<!-- Tagging Engine Setup -->
<class class=".engine.TaggingEngine">
@@ -27,6 +33,10 @@
attributes="update"
/>
<require
+ permission="lovely.tag.ManageEngine"
+ attributes="cleanStaleItems delete"
+ />
+ <require
permission="lovely.tag.AccessTag"
attributes="getTags getItems getUsers getCloud getRelatedTags"
/>
@@ -71,6 +81,10 @@
locate="True"
/>
+ <!-- handler for removing items from the tagging engine -->
+
+ <subscriber handler=".engine.removeItemSubscriber"/>
+
<include package=".browser" />
<include package=".generations" />
Modified: lovely.tag/trunk/src/lovely/tag/engine.py
===================================================================
--- lovely.tag/trunk/src/lovely/tag/engine.py 2006-12-21 06:21:51 UTC (rev 71641)
+++ lovely.tag/trunk/src/lovely/tag/engine.py 2006-12-21 17:27:38 UTC (rev 71642)
@@ -20,16 +20,17 @@
import persistent
import persistent.list
import zope.interface
+from zope import component
from BTrees import IOBTree, OOBTree
from zope.app.container import contained
from zope.app import intid
-
+from zope.app.intid.interfaces import IIntIdRemovedEvent, IIntIds
from lovely.tag import interfaces, tag
class TaggingEngine(persistent.Persistent, contained.Contained):
zope.interface.implements(interfaces.ITaggingEngine,
interfaces.ITaggingStatistics)
-
+
def __init__(self):
super(TaggingEngine, self).__init__()
self._reset()
@@ -63,6 +64,7 @@
tags_user = set(self._user_to_tagids.get(user, ()))
old_tag_ids = tags_item.intersection(tags_user)
+
old_tags = set([self._tag_ids.getObject(id)
for id in old_tag_ids])
@@ -96,49 +98,83 @@
else:
ids.insert(id)
+ self._delTags(del_tags)
+
+ def _delTags(self, del_tags):
+ """deletes tags in iterable"""
for tagObj in del_tags:
id = self._tag_ids.getId(tagObj)
self._tag_ids.unregister(tagObj)
self._tags.remove(tagObj)
-
+
self._user_to_tagids[tagObj.user].remove(id)
if not len(self._user_to_tagids[tagObj.user]):
del self._user_to_tagids[tagObj.user]
-
+
self._item_to_tagids[tagObj.item].remove(id)
if not len(self._item_to_tagids[tagObj.item]):
del self._item_to_tagids[tagObj.item]
-
+
self._name_to_tagids[tagObj.name].remove(id)
if not len(self._name_to_tagids[tagObj.name]):
del self._name_to_tagids[tagObj.name]
+ def delete(self, item=None, user=None, tag=None):
+ tags = None
+ if item is not None:
+ tags = set(self._item_to_tagids.get(item, ()))
+ if user is not None:
+ user_tags = set(self._user_to_tagids.get(user, ()))
+ if tags is not None:
+ tags = tags.intersection(user_tags)
+ else:
+ tags = user_tags
+ if tag is not None:
+ name_tags = set(self._name_to_tagids.get(tag, ()))
+ if tags is not None:
+ tags = tags.intersection(name_tags)
+ else:
+ tags = name_tags
+ # make objects
+ tags = map(self._tag_ids.getObject, tags)
+ self._delTags(tags)
+
def getTags(self, items=None, users=None):
"""See interfaces.ITaggingEngine"""
if items is None and users is None:
+ # shortcut
return set(self._name_to_tagids.keys())
+ result = self._getTagObjects(items, users)
+ return set([tag.name for tag in result])
+
+ def _getTagObjects(self, items, users):
+
+ if items is None and users is None:
+ users_result = set()
+ for v in self._item_to_tagids.values():
+ users_result.update(v)
+
if items is not None:
items_result = set()
for item in items:
items_result.update(self._item_to_tagids.get(item, set()))
-
+
if users is not None:
users_result = set()
for user in users:
users_result.update(self._user_to_tagids.get(user, set()))
-
+
if items is None:
result = users_result
elif users is None:
result = items_result
else:
result = items_result.intersection(users_result)
+ return set([self._tag_ids.getObject(id) for id in result])
- return set([self._tag_ids.getObject(id).name for id in result])
-
def getItems(self, tags=None, users=None):
"""See interfaces.ITaggingEngine"""
if tags is None and users is None:
@@ -213,29 +249,23 @@
result.remove(tag)
return result
- def getCloud(self, item=None, user=None):
+ def getCloud(self, items=None, users=None):
"""See interfaces.ITaggingEngine"""
- if item is not None and user is not None:
- raise ValueError('You cannot specify both, an item and an user.')
+ import types
+ if type(items) == types.IntType:
+ items = [items]
+ if type(users) in types.StringTypes:
+ users = [users]
- if item is None and user is None:
- return set([(name, len(tags))
- for name, tags in self._name_to_tagids.items()])
+ tags = self._getTagObjects(items=items, users=users)
+ d = {}
+ for tag in tags:
+ if d.has_key(tag.name):
+ d[tag.name] += 1
+ else:
+ d[tag.name] = 1
+ return set(d.items())
- if item is not None:
- ids = self._item_to_tagids.get(item, [])
-
- if user is not None:
- ids = self._user_to_tagids.get(user, [])
-
- result = {}
- for id in ids:
- tagObj = self._tag_ids.getObject(id)
- result.setdefault(tagObj.name, 0)
- result[tagObj.name] += 1
-
- return set(result.items())
-
def getFrequency(self, tags):
"""See interfaces.ITaggingEngine"""
result = {}
@@ -248,3 +278,30 @@
def __repr__(self):
return '<%s entries=%i>' %(self.__class__.__name__, len(self._tags))
+
+
+ def cleanStaleItems(self):
+ """clean out stale items which have no associated object"""
+ intIds = zope.component.getUtility(IIntIds, context=self)
+ cleaned = []
+ for uid in self.getItems():
+ obj = intIds.queryObject(uid)
+ if obj is None:
+ self.delete(item=uid)
+ cleaned.append(uid)
+ return cleaned
+
+ at component.adapter(IIntIdRemovedEvent)
+def removeItemSubscriber(event):
+
+ """A subscriber to IntIdRemovedEvent which removes an item from
+ the tagging engine"""
+ ob = event.object
+ if not interfaces.ITaggable.providedBy(ob):
+ return
+ for engine in zope.component.getAllUtilitiesRegisteredFor(
+ interfaces.ITaggingEngine, context=ob):
+ uid = zope.component.getUtility(IIntIds, context=engine).queryId(ob)
+ if uid is not None:
+ engine.delete(uid)
+
Modified: lovely.tag/trunk/src/lovely/tag/interfaces.py
===================================================================
--- lovely.tag/trunk/src/lovely/tag/interfaces.py 2006-12-21 06:21:51 UTC (rev 71641)
+++ lovely.tag/trunk/src/lovely/tag/interfaces.py 2006-12-21 17:27:38 UTC (rev 71642)
@@ -60,7 +60,7 @@
This method always overwrites the old tag settings. However, existing
tags will not be readded and are just skipped.
"""
-
+
def getTags(items=None, users=None):
"""Get all tags matching the specified items and users.
@@ -69,6 +69,10 @@
The method returns a set of *normalized* tag names.
"""
+ def getCloud(items=None, users=None):
+ """Get a set of tuples in the form of ('tag',
+ frequency). Arguments are the same as getTags."""
+
def getItems(tags=None, users=None):
"""Get all items matching the specified items and users.
@@ -88,16 +92,26 @@
def getRelatedTags(tag, degree=1):
"""Get a set of all related tags."""
- def getCloud(item=None, user=None):
- """Get a set of tuples in the form of ('tag', frequency)."""
-
def getFrequency(tags):
"""Get the frequency of all tags
Returns tuples in the form of ('tag', frequency)
"""
+ def cleanStaleItems(self):
+ """clean out stale items which have no associated object.
+ A list of the cleaned out uids is returned"""
+
+ def delete(item=None, user=None, tag=None):
+
+ """Globall delete all tag entries filtered by the given
+ criteria, if a criteria is None it is ignored
+
+ e.g. to delete all entries of a user do:
+ delete(user=u'username')
+ """
+
class ITaggingStatistics(zope.interface.Interface):
"""A tagging engine that provides statistical information about itself"""
Modified: lovely.tag/trunk/src/lovely/tag/tagging.py
===================================================================
--- lovely.tag/trunk/src/lovely/tag/tagging.py 2006-12-21 06:21:51 UTC (rev 71641)
+++ lovely.tag/trunk/src/lovely/tag/tagging.py 2006-12-21 17:27:38 UTC (rev 71642)
@@ -36,7 +36,7 @@
@Lazy
def docId(self):
ids = zope.component.getUtility(intid.interfaces.IIntIds,
- context=self.context)
+ context=self.engine)
id = ids.queryId(self.context)
if id is None:
ids.register(self.context)
More information about the Checkins
mailing list