Abdullah Solutions Using Python

I like programming in Python. I like learning about it, I like using it, and so I wish to share it.

Friday, July 10, 2009

ArgoUML & ArchGenXML Howto

For a more detailed understanding of these instructions, go to it's source at the Plone docs.
These instructions has been tested with ArgoUML 0.26.2 and 0.28.



ArchGenXML Buildout



The recommended way now to get the ArchGenXML product is through a buildout.


  1. Create a fresh directory and go into it.



  2. In that directory copy the bootstrap.py file.




  3. Then write a buildout.cfg file with the following contents:



    [buildout]
    parts =
    archgenxml
    [archgenxml]
    recipe = zc.recipe.egg:scripts
    eggs = archgenxml


  4. Finally bootstap buildout and run it:



    $ python bootstrap.py
    $ bin/buildout








Generate ArgoUML Profile


Then you should generate the argouml profile by doing:



$ bin/agx_argouml_profile

That would create the archgenxml_profile.xmi.




Configure ArgoUML to use the generated profile



The steps are:



  1. Open argouml

  2. Go to the menu Edit->Settings. Go to the Profiles tab.

  3. Click on the Add button and select the folder where you generated the profile (Just now we generated it at the root of the archgenxml product).

  4. Click OK.

  5. Restart argouml (Close argouml and open it again).

  6. Go back to Edit->Settings->Profiles page and AGXProfile should be available in the Available Profiles field. Select it into the Default Profiles and click ok and again restart argouml.

  7. Now your default argouml settings are ready.







Use AGXProfile for your projects



To use the AGXProfile for your project, the steps are:


  1. Click the Project Properties Button (4th one from the left on the Toolbar)

  2. Click the Profiles tab

  3. Remove UML 1.4 from Active Profile (The AGXProfile should already be in the Active Profile field)

  4. Click ok and you're ready to rock and roll




Saturday, July 4, 2009

2 Level Navigation In Plone 3

This implementation is based on the Two Level Navigation In Plone. In the original how-to, the author customized global_sections template which has been deprecated for Plone 3 onwards. This how-to shows how to do it by modifying the viewlet for the navigation based on a theme product. For more details about modifying the viewlet, please refer to the Overriding Template View how-to.
Step By Step

These are the steps to implement it:

1. First you have to have your theme product ready. If you do not know how to do this then please refer to How To Create a Plone 3 Theme Product on the Filesystem. For this example let's assume the theme product is inigo.theme.
2. Then inside your theme product you should have a directory called viewlet. I used this method with the idea that any modifications for the viewlets which you would like to do for your theme should be put in here.
3. In the viewlet folder you should have 4 file:
* __init__.py
* common.py
* configure.zcml
* sections.pt
4. Contents of those files should be:
* __init__.py : empty file. Should be there just so that you could package the folder as part of your egg
* common.py :


from zope.interface import implements, alsoProvides
from zope.component import getMultiAdapter
from zope.viewlet.interfaces import IViewlet
from zope.deprecation.deprecation import deprecate

from plone.app.layout.globals.interfaces import IViewView

from AccessControl import getSecurityManager
from Acquisition import aq_base, aq_inner
from Products.CMFPlone.utils import safe_unicode
from Products.Five.browser import BrowserView
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from Products.CMFCore.utils import getToolByName
from Products.CMFPlone import utils
from Products.CMFPlone.browser.navigation import get_url,get_id,get_view_url
from cgi import escape
from urllib import quote_plus
from plone.app.layout.navigation.root import getNavigationRoot

class ViewletBase(BrowserView):
""" Base class with common functions for link viewlets.
"""
implements(IViewlet)

def __init__(self, context, request, view, manager):
super(ViewletBase, self).__init__(context, request)
self.__parent__ = view
self.context = context
self.request = request
self.view = view
self.manager = manager

@property
@deprecate("Use site_url instead. ViewletBase.portal_url will be removed in Plone 4")
def portal_url(self):
return self.site_url


def update(self):
self.portal_state = getMultiAdapter((self.context, self.request),
name=u'plone_portal_state')
self.site_url = self.portal_state.portal_url()

def render(self):
# defer to index method, because that's what gets overridden by the template ZCML attribute
return self.index()

def index(self):
raise NotImplementedError(
'`index` method must be implemented by subclass.')

class GlobalSectionsViewlet(ViewletBase):
index = ViewPageTemplateFile('sections.pt')

def update(self):

context_state = getMultiAdapter((self.context, self.request),
name=u'plone_context_state')
actions = context_state.actions()
portal_tabs_view = getMultiAdapter((self.context, self.request),
name='portal_tabs_view')
self.portal_tabs = portal_tabs_view.topLevelTabs(actions=actions)

selectedTabs = self.context.restrictedTraverse('selectedTabs')
self.selected_tabs = selectedTabs('index_html',
self.context,
self.portal_tabs)
self.selected_portal_tab = self.selected_tabs['portal']

for tab in self.portal_tabs:
if tab['id']!='Members':
tab['subtab']=self.getsubtab(self.context,tab)
else:
tab['subtab']=[]

def getsubtab(self,context,tab):
query={}
result=[]
portal_properties = getToolByName(context, 'portal_properties')
portal_catalog = getToolByName(context, 'portal_catalog')
navtree_properties = getattr(portal_properties, 'navtree_properties')
site_properties = getattr(portal_properties, 'site_properties')

rootPath = getNavigationRoot(context)
dpath='/'.join([rootPath,tab['id']])
query['path'] = {'query' : dpath, 'depth' : 1}

query['portal_type'] = utils.typesToList(context)

sortAttribute = navtree_properties.getProperty('sortAttribute', None)
if sortAttribute is not None:
query['sort_on'] = sortAttribute

sortOrder = navtree_properties.getProperty('sortOrder', None)
if sortOrder is not None:
query['sort_order'] = sortOrder

if navtree_properties.getProperty('enable_wf_state_filtering', False):
query['review_state'] = navtree_properties.getProperty('wf_states_to_show', [])

query['is_default_page'] = False

if site_properties.getProperty('disable_nonfolderish_sections', False):
query['is_folderish'] = True

# Get ids not to list and make a dict to make the search fast
idsNotToList = navtree_properties.getProperty('idsNotToList', ())
excludedIds = {}
for id in idsNotToList:
excludedIds[id]=1

rawresult = portal_catalog.searchResults(**query)

# now add the content to results
for item in rawresult:
if not (excludedIds.has_key(item.getId) or item.exclude_from_nav):
id, item_url = get_view_url(item)
data = {'name' : utils.pretty_title_or_id(context, item),
'id' : item.getId,
'url' : item_url,
'description': item.Description}
result.append(data)
return result


* configure.zcml:

<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:five="http://namespaces.zope.org/five"
xmlns:browser="http://namespaces.zope.org/browser">

<browser:viewlet
name="plone.global_sections"
manager="plone.app.layout.viewlets.interfaces.IPortalHeader"
class=".common.GlobalSectionsViewlet"
permission="zope2.View"
layer="inigo.theme.browser.interfaces.IThemeSpecific"
/>

</configure>


* sections.pt:


<tal:tabs tal:condition="view/portal_tabs"
i18n:domain="plone">
<h5 class="hiddenStructure" i18n:translate="heading_sections">Sections</h5>

<ul id="portal-globalnav">
<tal:tabs tal:repeat="tab view/portal_tabs"><li tal:attributes="id string:portaltab-${tab/id};
class python:view.selected_portal_tab==tab['id'] and 'selected' or 'plain'"
><a href=""
tal:content="tab/name"
tal:attributes="href tab/url;
title tab/description|nothing">
Tab Name
</a>
<tal:block omit-tag=""
tal:define="subnav tab/subtab;">
<tal:block omit-tag="" tal:condition="subnav">
<ul class="nn-twolevel-subnav">
<tal:tabs tal:repeat="subtab subnav">
<li tal:attributes="id string:portaltab-${subtab/id};">
<a href="" class="" tal:attributes="href subtab/url;" accesskey="accesskeys-tabs" i18n:attributes="accesskey">
<tal:block omit-tag="" i18n:translate="" >
<span tal:replace="subtab/name">Tab Name</span>
</tal:block>
</a>
</li>
</tal:tabs>
</ul>
</tal:block>
</tal:block>
</li></tal:tabs>
</ul>
</tal:tabs>


5. Once the viewlet folder is complete, you need to add it to your theme product configure.zcml by adding:


<include package=".viewlet" />


6. Then you should modify your theme main css file (usually public.css.dtml) so that it would hide the submenu and reveal it only on hover of the main tab:


#portal-globalnav {
position:relative;
display: inline;
}

#portal-globalnav li ul {
display: none;
visibility: hidden;
position: absolute;
z-index: 200;
}

#portal-globalnav li ul li {
display: block;
margin: 0;
padding: 0;
width: 100%;
clear: left;
}

#portal-globalnav li ul li a {
display: block;
}

#portal-globalnav li:hover ul {
visibility: visible;
display: block;
}

#portal-globalnav li {
display: inline;
position: relative;
float: left;
}


Additional Information

The code for the getsubtab function was mostly copied from the CMFPlone/browser/navigation.py topLevelTabs function. This is so that the same behaviour can be expected for our sub menu as our normal tabs (eg. hide from navigation etc). The CSS given above is mostly just to hide and show the sub-menu. It would require additional mark-up to make it compeletely compatible and completely blend with your theme.

Friday, June 19, 2009

Export scripts run from Plone/Zope application server

To extract data from a plone instance, the easiest way is to use the plone_catalog tool. Using this tool we could search for the data (according to type, path and other criteria of our choosing) and iterate over every row that it finds.

To do that first we have to initialize the tool by importing the getToolByName function and assigning the portal_catalog tool to a variable.

from Products.CMFCore.utils import getToolByName

catalog = getToolByName(obj,'portal_catalog')

And then we can use that catalog to collect the data which we want. For example to collect all the pages that is in the entire site we would do something like so:

pages = catalog.searchResults(portal_type='Page')

And then we would iterate over the results:

for page in pages:
print page.title

Or maybe we want to limit our search to only pages inside a certain folder. Then we would have to specify the path to that folder as criteria to our search like so:

pages = catalog.searchResults(path='/inigo/projects',portal_type='Page')

The query above would return only the pages under the folder projects in the inigo folder.

If the id of the item that is wanted is already known, then we can also specify that as a criteria:

pages = catalog.searchResults(id='project_members',portal_type='Page')

Every criteria can have additional value to compare with that is combined with 'or' or 'and'. For example if you wanted to look for all the pages and folders under the '/inigo/projects' folder, you would it like so:

pages = catalog.searchResults(path='/inigo/projects',portal_type={'query':['Page','Folder'],'operator':'or'})

Note that the results we get from the portal_catalog tools are in the form of brains which is basically the indexed data of the object we have searched for. This is to prevent the search from 'waking up' the object which would be quite costly in terms of processing. But if we require some data to be read from the results which is not indexed, then we need to refer to the object itself like so:

pages = catalog.searchResults(portal_type='Page')
for page in pages:
page_object = page.getObject()

Example Script

#!/usr/bin/env python
from Products.CMFCore.utils import getToolByName
from Testing import makerequest

def runit(app):
app=makerequest.makerequest(app)
catalog = getToolByName(app.inigo, 'portal_catalog')
pages = catalog.unrestrictedSearchResults(portal_type="Document")
print '%d pages found' % len(pages)
for page in pages:
pageobj=page._unrestrictedGetObject()
print 'Title:',pageobj.title
print 'Description:',pageobj.description
print 'Content:',pageobj.getText()

runit(app)

The script above will display the pages(documents) available on the inigo site in a plone instance. If the script above is saved in the root plone directory as showpages.py, then it can be executed by running:

bin/instance run showpages.py

The unrestrictedSearchResults function is used so that even pages that are private or not published are listed. But to be able to get those pages the function _unrestrictedGetObject have to be used to get the object.

Wednesday, June 17, 2009

Running python scripts on a plone instance

Recently I had to do some heavy importing from MySQL into ZODB (aka Plone). It turns out to be easier than I expected. You can virtually run any python script with a zope instance. Just do a:


bin/instance run <name of script>

So any script you want would be able to access the global variable 'app' to do things to that instance. For example if you had a Plone site called 'taskmanager' which has the id 'taskmanager', then you'd access it in your script using:

app.taskmanager

Once you've got the object from the instance you can start adding stuff to it. I used the _createObjectByType function to avoid any kind of permission checking about creating the object. So first include it into your script and then go crazy.. :P:

from Products.CMFPlone.utils import _createObjectByType
newfolder = _createObjectByType('Folder',app.taskmanager,id='Task',title='Task')

You can guess what's the variable of the function. First the type of object you want to create. Then the parent of that object. Then the other stuff like id and title and all the other attribute of that object which you might want to give.

Thursday, February 19, 2009

Install python-ldap with easy_install

Kept getting an error from gcc. Until finally I figured out to do:


sudo apt-get install libldap2-dev

And then it was able to finally compile.

Wednesday, February 18, 2009

Learning Plone Fast Forward

Currently developing a system to manage tasks using plone as it's base. Because of that I've recently learned a lot but have not had the time to properly document all that I've learned. Here's a few snippet of what I've learned.

How to add pagination
First you should define what is the data you want to paginate and also include the require macro into your template.


<div class="eventlist"
tal:define="results options/events;
Batch python:modules['Products.CMFPlone'].Batch;
b_size python:10;
b_start python:0;
b_start request/b_start | b_start;">


and then you need to make sure the list contain data and apply the list to the macro.

<div tal:condition="results"
tal:define="batch python:Batch(results, b_size, int(b_start), orphan=1);">


And of course after that you need to loop through all the data of the page.

<div tal:repeat="event batch">


And do you want to display the list of pages and allow users to navigate them?

<div metal:use-macro="here/batch_macros/macros/navigation" />


How to limit the user interface based on user groups
First we have to find out whether the user is in the said group.

def ksnview(self):
mt = getToolByName(self, 'portal_membership')
if mt.isAnonymousUser(): # the user has not logged in
return False
else:
gt = getToolByName(self, 'portal_groups')
member = mt.getAuthenticatedMember()
ksngroup = gt.getGroupById('KSN')
if ksngroup and member.id in ksngroup.getAllGroupMemberIds():
return True
return False

And then do a tal:condition on the style to hide what they do not need.

How to do query and also sort them
First get an instance of the portal_catalog

catalog = getToolByName(self.context, 'portal_catalog')

After that get the searchResult

event_brains = catalog.searchResults(sort_on="end",getStatus={'query':['Not Started','In Progress','Delayed'],'operator':'or'},portal_type={'query':['Event','Task'],'operator':'or'},path=path, **kw)


**Some of the above use the getToolByName function which should be added at the top like this

from Products.CMFCore.utils import getToolByName

Friday, February 6, 2009

Plone GenericSetup

GenericSetup is a cool feature in Plone that can be used to create profiles for products that can be imported when the product is installed. The original reference which I used can be found here. Basically you can setup one site exactly how you want it to be with all the details on the folders structures, portlets and permissions even. And then you can export the profile of that portal so that it can be imported into any other sites you might be developing.

To export it go to the ZMI, under portal_setup->export. At the very bottom there is an "Export all" button. If that is pressed then you'll be able to download a tarball which contain all the specification needed to recreate the portal you have just made. You won't need all of them but you can use whichever one you need.

Those files would go under profiles/default directory in a common archgenxml product. I've had problems customizing portlets because the exported portlets.xml gave a 'ConstratintNotSatisfied' error. Found out from here that I needed to delete some entries in the tag, namely:


<property name=”name” />
<property name=”root” />

Also learnt from there how to remove portlets:

<assignment remove=”true” name=”login” category=”context” key=”/”
manager=”plone.leftcolumn” type=”portlets.Login” />
<assignment remove=”true” name=”calendar” category=”context” key=”/”
manager=”plone.rightcolumn” type=”portlets.Calendar” />
<assignment remove=”true” name=”events” category=”context” key=”/”
manager=”plone.rightcolumn” type=”portlets.Events” />
<assignment remove=”true” name=”news” category=”context” key=”/”
manager=”plone.rightcolumn” type=”portlets.News” />
<assignment remove=”true” name=”recent-items” category=”context” key=”/”
manager=”plone.rightcolumn” type=”portlets.recent” />
<assignment remove=”true” name=”review-list” category=”context” key=”/”
manager=”plone.rightcolumn” type=”portlets.review” />

About Me

My Photo
Abdullah Zainul Abidin
I am what I am because of what I am...
View my complete profile