[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