[Grok-dev] Bonked
Kevin Teague
kevin at bud.ca
Sat Dec 13 18:17:06 EST 2008
I've made an attempt at a plug-in system for the GUM app that I wrote (http://www.bcgsc.ca/platform/bioinfo/software/gum
). It's possible to create what you describe - while my plug-in system
meets my needs, I can't promise it's the cleanest way of doing
things, but I'll describe what I wanted and how I did it ...
Goal:
Allow add-ons to be managed as seperate Python projects, packagable as
eggs, etc. When an add-on package is grokked, then the application
presents a UI for installing/uninstalling that add-on.
Initial use-case:
When users are added to specific LDAP Groups using GUM, then a JIRA
web service should be invoked which adds that user account to JIRA
(not actually sure if this is 100% necessary with newer JIRA installs
now, but our JIRA deployment was done when it's integration with LDAP
was quite limited).
Application support:
The GUM app provides an 'extensions' container which contains the
installed add-on instances. This part was fairly straight-forward, the
Extensions container class contains a method to query if a given add-
on is installed or not, so that the UI can present a list of installed
and uninstalled extensions.
class Extensions(grok.Container):
def installed(self):
"List of installed extensions"
return self.values()
def is_installed(self, ext):
"Return True if an extension is already installed"
for installed_ext in self.values():
if type(installed_ext) == type(ext):
return True
return False
def available(self):
"List of uninstalled extensions"
return [
ext_maker[1].new()
for ext_maker in component.getUtilitiesFor(IExtensionMaker)
if not self.is_installed(ext_maker[1].new())
]
Then, in a Python project seperate from GUM one can write an
extension. Since available add-ons are discovered using
"component.getUtilitiesFor(IExtensionMaker)" to make an add-on
available, the add-on package just needs to register a global utility
that provides the IExtensionMaker interface. In the add-on package
this looks something like:
import grok
from gum.interfaces import IExtension, IExtensionMaker
class ExtensionMaker(grok.GlobalUtility):
grok.implements(IExtensionMaker)
grok.name('jira')
def new(self):
return Extension()
def load(self):
return grok.getSite()['extensions']['jira']
class Extension(grok.Container):
grok.implements(IExtensionSchema)
grok.name('jira')
title = u'JIRA Account Integrator'
service_url = u''
ldap_groupname = u''
username = u''
password = u''
def __init__(self):
self.changelog = PersistentList()
def add_change(self, change_type, message):
"Appends to the changelog, and also limits size to 50 changes"
self.changelog.append(Change(change_type, message))
if len(self.changelog) > 50:
self.changelog = self.changelog[1:]
Finally, because I wanted add-ons to be able to provide their own
Views for custom UI features, I also depend upon the master layout of
the GUM application in the templates directories for the add-ons:
<html metal:use-macro="context/@@layout/macros/page">
... custom template
</html>
In order to make this work between Python projects, you need to use a
DirectoryResource, which allows you to share static elements such as
JavaScript, images and CSS. This was added to grokcore.view 1.2 (which
is in trunk and will be in Grok 1.0), I've pulled it into Grok 0.14
with:
[versions]
grokcore.view = 1.2 # DirectoryResource - to be included in next Grok
release
And created a directoryresource in gum/layout.py:
class Resources(grokcore.view.DirectoryResource):
grokcore.view.name('resources')
grokcore.view.path('resources')
Finally, the question of deployment. In a normal Grok application,
your app provides it's own buildout.cfg file, and specifies that it
depends upon Grok and other Python projects. An add-on depends upon
your application though. But you can't put a custom buildout.cfg in
each add-ons package, since what you want is to just maintain a list
of installed add-ons as part of the main application deployment. For a
GUM deployment I just have a custom buildout.cfg file which extends
the basic one with:
[buildout]
develop = . src/gum-gsc-systems
extends = base.cfg
[app]
eggs = gum
gum-gsc-systems
This lives in the root of GUM deployment, but since it's specific to a
particular deployment I don't store this in version control. I just
name the file 'production.cfg" and run './bin/buildout -c
production.cfg".
So that's pretty much how I approached the add-on problem. The key
being that the main application has an IExtensionMaker and IExtension
interfaces, and each add-on then implements these interfaces as global
utilities. Then I query the component architecture to see what's being
registered via grokking and build the UI based on that.
Again, I can't promise that I've got the cleanest or most robust
solution, but I did write something that worked for me :)
More information about the Grok-dev
mailing list