Wednesday, April 28, 2010

Leave management System - An Open Source Contribution from Mahiti Infotech Bangalore


Hi All

We have developed an application using Repoze.BFG and KARL as basis. The application, is named as LMS( Leave management System ). We want to contribute this project for Open Source Community. We wish , LMS can act as a reference , for any one who is interested in learning KARL based repoze applications.
Link to download, source code is : https://sourceforge.net/projects/mahiti-lms

Please feel free to tell us how it is and our lackings if any.

Srikanth.T
Senior Programmer
Sowmya.MK
Team Lead
Sreekanth S Rameshaiah
CEO
Mahiti Infotech Pvt. Ltd.
# 33-34, 2nd Floor,
Hennur Cross, Hennur Road,
Bangalore, India - 560043
Phone: +91 80 4115 0580/1

Sunday, February 14, 2010

How to create Workflow for a repoze.BFG application

Let us see , how we can create a workflow for a bfg application.

To add a workflow add a file named workflow.py at the root of your site. you can name it as you like.
Add methods related to the states and transitions as per your requirement.

For Example, the following files does the work.

  • workflow.py

from repoze.bfg.security import Allow
from repoze.bfg.security import Deny
from repoze.bfg.security import Everyone
from repoze.bfg.traversal import find_interface
from repoze.bfg.traversal import model_path

from project.security_policies.policy import ADMINISTRATOR_PERMS
from project.security_policies.policy import GUEST_PERMS
from project.security_policies.policy import MODERATOR_PERMS
from project.security_policies.policy import MEMBER_PERMS
from project.security_policies.policy import CREATE
from project.security_policies.policy import NO_INHERIT
from project.security_policies.workflow import postorder
from project.security_policies.workflow import acl_diff
from project.security_policies.workflow import reset_security_workflow


def find_showEvent(context):
return find_interface(context, IshowEvent)

def ts(*args):
return '\t'.join([str(x) for x in args])

#------------------------------------------------------------------------------
# Electors
#------------------------------------------------------------------------------

def not_intranets_containment(context):
return not intranets_containment(context)

def intranets_containment(context):
return False

def private_showEvent_containment(context):
if not_intranets_containment(context):
showEvent = find_showEvent(context)

if showEvent is None:

return False

return getattr(showEvent, 'seproject.security_policies.policycurity_state', None) == 'private'

return False

def public_showEvent_containment(context):
if not_intranets_containment(context):
showEvent = find_showEvent(context)
if showEvent is None:
return False
return getattr(showEvent, 'security_state', None) == 'public'
return False


#------------------------------------------------------------------------------
# Workflow for showEvent
#------------------------------------------------------------------------------

def showEvent_to_private(ob, info):
showEvent = find_showEvent(ob)
acl = []
moderators_group_name = showEvent.moderators_group_name
members_group_name = showEvent.members_group_name
acl.append((Allow, 'group.KarlAdmin', ADMINISTRATOR_PERMS))
acl.append((Allow, moderators_group_name, MODERATOR_PERMS))
acl.append((Allow, members_group_name, MEMBER_PERMS))
acl.append(NO_INHERIT)
msg = None
added, removed = acl_diff(showEvent, acl)
if added or removed:
showEvent.__acl__ = acl
msg = ts('showEvent-private', model_path(showEvent), added, removed)
_reindex(showEvent)
return msg

def showEvent_to_public(ob, info):
showEvent = find_showEvent(ob)
acl = []
moderators_group_name = showEvent.moderators_group_name
members_group_name = showEvent.members_group_name
acl.append((Allow, 'group.KarlAdmin', ADMINISTRATOR_PERMS))
acl.append((Allow, members_group_name, GUEST_PERMS))
acl.append((Allow, 'group.KarlStaff', GUEST_PERMS))
acl.append(NO_INHERIT)
msg = None
added, removed = acl_diff(showEvent, acl)
if added or removed:
showEvent.__acl__ = acl
msg = ts('showEvent-public', model_path(showEvent), added, removed)
_reindex(showEvent)
return msg


def showEvent_to_pending(ob, info):
showEvent = find_showEvent(ob)
acl = []
moderators_group_name = showEvent.moderators_group_name
members_group_name = showEvent.members_group_name
acl.append((Allow, 'group.KarlAdmin', ADMINISTRATOR_PERMS))
acl.append((Allow, members_group_name, GUEST_PERMS)) #When the showEvent is pending user cannot delete,create, edit
acl.append((Allow, 'group.KarlStaff', GUEST_PERMS))
acl.append(NO_INHERIT)
msg = None
added, removed = acl_diff(showEvent, acl)
if added or removed:
showEvent.__acl__ = acl
msg = ts('showEvent-pending', model_path(showEvent), added, removed)
_reindex(showEvent)
return msg

def showEvent_to_rejected(ob,info):
showEvent = find_showEvent(ob)
acl = []
moderators_group_name = showEvent.moderators_group_name
members_group_name = showEvent.members_group_name
acl.append((Allow, 'group.KarlAdmin', ADMINISTRATOR_PERMS))
acl.append((Allow, members_group_name, GUEST_PERMS))
acl.append((Allow, 'group.KarlStaff', GUEST_PERMS))
acl.append(NO_INHERIT) #temp
msg = None
added, removed = acl_diff(showEvent, acl)
if added or removed:
showEvent.__acl__ = acl
msg = ts('showEvent-pending', model_path(showEvent), added, removed)
_reindex(showEvent)
return msg

In workflow.py permissions were imported from policy file. Below is the policy file of security_policies of project.

  • policy.py

from BTrees.IFBTree import multiunion
from BTrees.IFBTree import IFSet

from repoze.bfg.security import Allow
from repoze.bfg.security import Deny
from repoze.bfg.security import Everyone
from repoze.bfg.security import AllPermissionsList


VIEW = 'view'
EDIT = 'edit'
CREATE = 'create'
DELETE = 'delete'
MODERATE = 'moderate'
ADMINISTER = 'administer'
COMMENT#acl.append((Allow, moderators_group_name, MODERATOR_PERMS)) #Creator or member is the part of default member group = 'comment'

GUEST_PERMS = (VIEW, COMMENT)
MEMBER_PERMS = GUEST_PERMS + (EDIT, CREATE, DELETE)
MODERATOR_PERMS = MEMBER_PERMS + (MODERATE,)
ADMINISTRATOR_PERMS = MODERATOR_PERMS + (ADMINISTER,)

ALL = AllPermissionsList()
NO_INHERIT = (Deny, Everyone, ALL)


Add workflow.zcml and declare the states and transitions as follows.

  • workflow.zcml


< configure xmlns="http://namespaces.repoze.org/bfg">
< include package="repoze.workflow" file="meta.zcml">

< workflow type="security"
name="showEvent"
description="WF for showEvent"
elector="project.workflow.not_intranets_containment"
content_types="project.interfaces.IshowEvent"
initial_state="private"
state_attr="security_state"
permission_checker="repoze.bfg.security.has_permission"
>

< state name="public" title="Public"
callback="project.workflow.showEvent_to_public">

< /state>

< state name="private" title="Private"
callback="project.workflow.showEvent_to_private">
< alias name="initial">
< /state>

< state name="pending" title="Pending"ar
callback="project.workflow.showEvent_to_pending">
< /state>

< state name="rejected" title="Rejected"
callback="project.workflow.showEvent_to_rejected">
< alias name="initial">
< /state>

< transition name="submit"
to_state="pending"
from_state="private"/>

< transition name="approve"
to_state="public"
from_state="pending"/>

< transition name="retract"
to_state="private"
from_state="pending"/>

< transition name="reject"
to_state="rejected"
from_state="pending"/>

< transition name="unapprove"
to_state="pending"
from_state="public"/>

</workflow>


Now register the workflow , by declaring it in configure.zcml file.

  • configure.zcml



<configure xmlns="http://namespaces.repoze.org/bfg">

<!-- this must be included for the view declarations to work -->

<include package="repoze.bfg.includes">
<include package="project.security_policies">
<include package="project.views">
<include package="project" file="workflow.zcml">

</configure>

This way, we were able to create a workflow for our project.

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