Thursday, January 28, 2010

Cataloging objects in BFG

Cataloging objects in BFG

One way to catalog the objects in a BFG application is to use subscribers .
The procedure followed to catalog is :
  1. Add a catalog file in the root that has a method to catalog the objects.
  2. In models.py import the catalog method you have written and call it in the appmaker() .
  3. Implement zope events interfaces that you require in events.py (you can use any name)
  4. Then write the functions which you want to execute on firing of above mentioned/decalred events.
  5. Now use subscriber tag in configure.zcml and register the subscriber function to the events declared.

Let us see how we can catalog objects.

  • catalog.py
Create a python file called catalog.py in the root. Add a method populate_catalog and place the indexes that are required.

  1. import datetime
  2. import time
  3. from zope.interface import providedBy
  4. from zope.component import queryAdapter
  5. from zope.interface.declarations import Declaration
  6. from repoze.bfg.traversal import model_path
  7. from repoze.bfg.traversal import find_interface
  8. from repoze.catalog.catalog import Catalog
  9. from repoze.catalog.indexes.field import CatalogFieldIndex
  10. from repoze.catalog.indexes.keyword import CatalogKeywordIndex
  11. from repoze.catalog.indexes.text import CatalogTextIndex
  12. from repoze.catalog.indexes.path import CatalogPathIndex
  13. from repoze.catalog.indexes.path2 import CatalogPathIndex2
  14. from repoze.catalog.document import DocumentMap
  15. from repoze.bfg.security import principals_allowed_by_permission
  16. from leavemnt.interfaces import IRoot
  17. from leavemnt.interfaces import ISearchText
  18. from leavemnt.interfaces import IVirtualData
  19. from leavemnt.interfaces import ILeave
  20. from leavemnt.interfaces import IProfile
  21. from leavemnt.utils import coarse_datetime_repr
  22. from leavemnt.utils import find_users
  23. def get_search_text(object, default):
  24. adapter = queryAdapter(object, ISearchText)
  25. if adapter is None:
  26. return default
  27. return adapter()
  28. def get_creator(object, default):
  29. return getattr(object, 'creator', default)
  30. def _get_date(object, default, name):
  31. date = getattr(object, 'modified', None)
  32. if date is None:
  33. return default
  34. # we can't store datetimes directly in the catalog because they
  35. # can't be compared with anything
  36. timetime = time.mktime(date.timetuple())
  37. # creation "minute" actually to prevent too-granular storage
  38. date = int(str(int(timetime))[:-2])
  39. return date
  40. def get_modified_date(object, default):
  41. return _get_date(object, default, 'modified')
  42. def get_created_date(object, default):
  43. print _get_date(object, default, 'created')
  44. return _get_date(object, default, 'created')
  45. def get_interfaces(object, default):
  46. # we unwind all derived and immediate interfaces using spec.flattened()
  47. # (providedBy would just give us the immediate interfaces)
  48. provided_by = list(providedBy(object))
  49. spec = Declaration(provided_by)
  50. ifaces = list(spec.flattened())
  51. return ifaces
  52. def get_security_state(obj, default):
  53. return getattr(obj, 'security_state', default)
  54. def populate_catalog(site):
  55. catalog = site.catalog = Catalog()
  56. catalog['text'] = CatalogTextIndex(get_search_text)
  57. catalog['interfaces'] = CatalogKeywordIndex(get_interfaces)
  58. catalog['creator'] = CatalogFieldIndex(get_creator)
  59. catalog['modified'] = CatalogFieldIndex(get_modified_date)
  60. catalog['created'] = CatalogFieldIndex(get_created_date)
  61. catalog['security_state']= CatalogFieldIndex(get_security_state)
  62. catalog.document_map = DocumentMap()
  63. def find_catalog(context):
  64. return find_interface(context, IRoot).catalog

  • In models.py
In models , import populate_catalog from the catalog.py along with the necessary catalog methods from repoze.catalog .
Then in the appmaker function , make a site instance , and pass it as a parameter to the populate_catalog method.

  1. def appmaker(root):
  2. if not root.has_key(SITE_ID):
  3. #site = root['site'] = Site()
  4. site = Site()
  5. root[SITE_ID] = site
  6. ## TODO: cleanup catalog creation.
  7. populate_catalog(site)
  8. leavefolder = LeaveFolder()
  9. site['leavefolder'] = leavefolder
  10. data = DefaultInitialData()
  11. transaction.commit()
  12. return root[SITE_ID]

Then the site will contain the catalog mechanism. For every subsequent creation, modification and deletion there should be a mechanism to update the catalog.

  • subscribers.py
The following code indexes, reindexes and unindexes on corresponding events.

  1. import datetime
  2. from zope.component import queryAdapter
  3. from repoze.bfg.traversal import model_path
  4. from repoze.folder.interfaces import IFolder
  5. from leavemnt.catalog import find_catalog
  6. from leavemnt.interfaces import IMetadata
  7. def postorder(startnode):
  8. def visit(node):
  9. if IFolder.providedBy(node):
  10. for child in node.values():
  11. for result in visit(child):
  12. yield result
  13. yield node
  14. return visit(startnode)
  15. def index_content(object, event):
  16. """ Index content (an IObjectAddedEvent subscriber) """
  17. catalog = find_catalog(object)
  18. if catalog is not None:
  19. for node in postorder(object):
  20. path = model_path(node)
  21. docid = catalog.document_map.add(path)
  22. catalog.index_doc(docid, node)
  23. adapter = queryAdapter(node, IMetadata)
  24. if adapter is not None:
  25. metadata = adapter()
  26. catalog.document_map.add_metadata(docid, metadata)
  27. def unindex_content(object, event):
  28. """ Unindex content (an IObjectWillBeRemovedEvent subscriber) """
  29. catalog = find_catalog(object)
  30. if catalog is not None:
  31. path = model_path(object)
  32. num, docids = catalog.search(path=path)
  33. for docid in docids:
  34. catalog.unindex_doc(docid)
  35. catalog.document_map.remove_docid(docid)
  36. def reindex_content(object, event):
  37. """ Reindex a single piece of content (non-recursive); an
  38. IObjectModifed event subscriber """
  39. catalog = find_catalog(object)
  40. if catalog is not None:
  41. path = model_path(object)
  42. docid = catalog.document_map.docid_for_address(path)
  43. catalog.reindex_doc(docid, object)
  44. #catalog.document_map.remove_metadata(docid)
  45. adapter = queryAdapter(object, IMetadata)
  46. if adapter is not None:
  47. metadata = adapter()
  48. catalog.document_map.add_metadata(docid, metadata)

  49. def set_modified(object, event):
  50. """ Set the modified date on a single piece of content
  51. unconditionally (non-recursive); an IObjectModified event
  52. subscriber"""
  53. now = datetime.datetime.now()
  54. object.modified = now
  55. def set_created(object, event):
  56. """ Add modified and created attributes to nodes which do not yet
  57. have them (recursively); an IObjectWillBeAddedEvent subscriber"""
  58. now = datetime.datetime.now()
  59. for node in postorder(object):
  60. if not getattr(node, 'modified', None):
  61. node.modified = now
  62. if not getattr(node, 'created', None):
  63. node.created = now


When ever the event subscribed in configure.zcml is fired, approriate methods (in subscribers.py) is called.

  • configure.zcml
In configure.zcml, make a subscriber tag and define the corresponding handlers, for an event.








  1. name="static"
  2. path="templates/static"
  3. />


  4. for="leavemnt.interfaces.ILeave
  5. repoze.folder.interfaces.IObjectWillBeRemovedEvent"
  6. handler=".subscribers.unindex_content" />


  7. for="leavemnt.interfaces.ILeave
  8. repoze.folder.interfaces.IObjectAddedEvent"
  9. handler=".subscribers.index_content" />

  10. for="leavemnt.interfaces.ILeave
  11. .interfaces.IObjectModifiedEvent"
  12. handler=".subscribers.reindex_content" />

  13. for="*
  14. repoze.folder.interfaces.IObjectWillBeAddedEvent"
  15. handler=".subscribers.set_created" />

  16. for="leavemnt.interfaces.IProfile
  17. repoze.folder.interfaces.IObjectWillBeRemovedEvent"
  18. handler=".subscribers.unindex_content" />


  19. for="leavemnt.interfaces.IProfile
  20. repoze.folder.interfaces.IObjectAddedEvent"
  21. handler=".subscribers.index_content" />

  22. for="leavemnt.interfaces.IProfile
  23. .interfaces.IObjectModifiedEvent"
  24. handler=".subscribers.reindex_content" />

  25. for="*
  26. .interfaces.IObjectModifiedEvent"
  27. handler=".subscribers.set_modified" />


  • events.py
in events.py define the events as required.

  1. from leavemnt.interfaces import IObjectModifiedEvent
  2. from leavemnt.interfaces import IObjectWillBeModifiedEvent
  3. from zope.interface import implements
  4. class ObjectModifiedEvent(object):
  5. implements(IObjectModifiedEvent)
  6. def __init__(self, object):
  7. print object
  8. self.object = object
  9. class ObjectWillBeModifiedEvent(object):
  10. implements(IObjectWillBeModifiedEvent)
  11. def __init__(self, object):
  12. self.object = object


1 comment:

  1. Where to fire the IObjectWillBeModifiedEvent.
    repoze.folder will not fire it.

    ReplyDelete