Netsight Blog

Cool stuff Netsight are up to in Zope and Plone

Blog > Entries tagged cmis

Today I read a great article by Jeff Potts of Optaros: A CMIS API library for Python, Part 1: Introducing cmislib. It talks about how to use the CMIS python library to mess about with CMIS and access CMIS repositories.

I've been mulling over the significance of CMIS for a while now, and still a little undecided. Yes I think its a great idea, but in reality will it actually be useful? As it is a lowest-common-denominator approach to interoperability, I'm betting that much of the value will be lost when using it for widescale migration from one CMS to another. There are already plenty or protocols for getting data in and out of systems, why add another?

I do however think it might be interesting to use it for Plone to allow Plone to access other systems as a content repository. In this case, I'm not talking deep-down integration (ie, I'm not talking about replacing the ZODB with a CMIS backend or even an Archetypes storage layer). At the moment I'm thinking quite lightweight integration at specific points. Ie imagine a portlet on a Plone-based intranet which lists the 10 most recent policy documents stored in an Alfresco/Sharepoint/Filenet system.

So I grabbed Jeff's library and had a quick play with it this morning to see what sort of thing could be done. I've kept my scope pretty small, and of course there is way more that could be done here, but I just wanted to hack together something quick to just see how it worked.

Results

The results worked pretty nicely. The version of cmislib on pypi (0.3dev) has some issues with Alfresco's latest server, meaning I couldn't do much, other than do a search and list the titles, but Jeff says that the svn trunk version fixes these.

I add my 'CMIS View' object to a Plone site, and give it the repository url, username and password:

CMISView Edit Screen (click for larger image)

And hit Save, and then I get a listing of the items on the CMIS repository. Here we have a listing of all items in the Alfresco public repository which contain the word 'test'.

CMISView View Screen (click for larger image)

How I did it

It was actually pretty simple, in fact most of my time was spent working out how to re-use the existing plone folder_contents template for my listing and how to actually use CMIS.

So firstly I added the cmislib package and my package I was going to develop to a fresh Plone 4.0b1 buildout:

[buildout]
...
eggs =
     cmislib
     collective.cmisview

# Reference any eggs you are developing here, one per line
# e.g.: develop = src/my.package
develop =
     src/collective.cmisview

[instance]
...
zcml =
    collective.cmisview

I then created a simple package:

matt-hamiltons-macbook-air-3:src matth$ ../../bin/paster create -t archetype
matt-hamiltons-macbook-air-3:src matth$ ../../bin/paster create -t archetype
Selected and implied templates:
  ZopeSkel#basic_namespace  A basic Python project with a namespace package
  ZopeSkel#plone            A project for Plone products
  ZopeSkel#archetype        A Plone project that uses Archetypes content types

Enter project name: collective.cmisview
Variables:
  egg:      collective.cmisview
  package:  collectivecmisview
  project:  collective.cmisview
Expert Mode? (What question mode would you like? (easy/expert/all)?) ['easy']:
Project Title (Title of the project) ['Example Name']: CMIS View
Version (Version number for project) ['1.0']: 0.1
Description (One-line description of the project) ['']: A simple content type for Plone that allows viewing a \
CMIS repository
Creating template basic_namespace
Creating directory ./collective.cmisview
...

And added a simple content type:

matt-hamiltons-macbook-air-3:collective.cmisview matth$ ../../../bin/paster addcontent contenttype
Enter contenttype_name (Content type name ) ['Example Type']: CMIS View
Enter contenttype_description (Content type description ) ['Description of the Example Type']: View of a remot\
e CMIS repository
Enter folderish (True/False: Content type is Folderish ) [False]:
Enter global_allow (True/False: Globally addable ) [True]:
Enter allow_discussion (True/False: Allow discussion ) [False]:
  Inserting from README.txt_insert into /Development/py26nsp2/cmis/src/collective.cmisview/collective/cmisview\
/README.txt
...

I then added the schema to the generated cmisview.py file:

CMISViewSchema = schemata.ATContentTypeSchema.copy() + atapi.Schema((

        atapi.StringField(
            name='cmisrepourl',
            widget=atapi.StringWidget(
                label='CMIS Repository URL',
                ),
            ),

        atapi.StringField(
            name='cmisrepouser',
            widget=atapi.StringWidget(
                label='CMIS Repository Username',
                ),
            ),

        atapi.StringField(
            name='cmisrepopass',
            widget=atapi.PasswordWidget(
                label='CMIS Repository Password',
                ),
            ),

))

and added a view class in browser/cmisbrowserview.py with a method to do the actual CMIS search:

     def getdocuments(self):
	client = CmisClient(self.context.getCmisrepourl(),
                            self.context.getCmisrepouser(),
                            self.context.getCmisrepopass()
                            )
	repo = client.defaultRepository
	res = repo.query("select * from cmis:document where contains('test')")
	return res

I added a method to iterate over the results and build up something that the standard folder_view template would like. Obviously if I was doing this in ernest I would create a custom template specifically suited to the CMIS results view.

    @property
    def items(self):
	results = []
	i = 0
        for doc in self.docs:
            if (i + 1) % 2 == 0:
                table_row_class = "draggable even"
            else:
                table_row_class = "draggable odd"

            props = doc.getProperties()
            results.append(dict(
                url = '',
	        url_href_title = '',
	        id  = doc.id,
	        quoted_id = urllib.quote_plus(doc.id),
		path = '',
                title_or_id = safe_unicode(doc.title),
	        obj_type = '',
	        size = props['cmis:contentStreamLength'],
		modified = props['cmis:lastModificationDate'],
		icon = '',
		type_class = '',
                wf_state = '',
                state_title = '',
                state_class = '',
                is_browser_default = False,
                folderish = False,
                relative_url = '',
		view_url = '',
                table_row_class = table_row_class,
	        is_expired = False,
	    ))

            i += 1
        return results

And that is pretty much it. Clearly there is a lot more you could do here, such as tidying up the info coming back (formatting the size in the listing) and registering a traversal adapter which would enable you to actually download the document listed. Maybe I'll do this once the next version of cmislib hits pypi.