Entries For: August 2006
2006-08-31
Small snippet to read the title for a DCWorkflow state
portal_workflow.getTitleForStateOnType(obj.review_state, obj.portal_type)
Well, that's about it. I'll add other relevant API methods to work with the workflow tool when I'll have a need for them again.
2006-08-30
Improve Plone-based website performance with simple Apache caching
I won't go too much into details, and this is more for my own personal reference, but using this short recipe I was able to increase by up to 100 times the performance of a plone based website. For a lot more details and more advanced techniques for caching, integration with squid and cachefu, visit the Plone documentation HowTo section.
The website hosts mostly news updates, and is edited mostly by an editorial staff, under a 24 hours cycle. The configuration file is extremely simple and can be droped in for any website that has a similar profile (or any website that has a closed editorial staff)
Requirements: apache 2 + mod_disk_cache + mod_expires
<LocationMatch "^[^/]">
Deny from all
</LocationMatch>
<IfModule mod_disk_cache.c>
CacheEnable disk /
CacheRoot /var/cache/http
CacheSize 5
CacheMaxExpire 24
CacheLastModifiedFactor 0.1
CacheDefaultExpire 3
#expires in 3 hours
CacheGcInterval 3
#check each hour the cache and delete the obsolete files
</IfModule>
<IfModule mod_deflate.c>
SetOutputFilter DEFLATE
</IfModule>
ExpiresActive On
ExpiresByType image/gif A10800
ExpiresByType image/png A10800
ExpiresByType image/jpeg A10800
ExpiresByType text/css A10800
ExpiresByType text/javascript A10800
ExpiresByType application/x-javascript A10800
ExpiresByType text/html A10800
ExpiresByType text/xml A10800
2006-08-29
Checking for a role or a permission in a context
There are two very useful methods to check for certain security settings offered by the Plone API. First, to check if a user has a certain role, the following snippet can be used:
roles_user_has = portal_membership.getCandidateLocalRoles(here)
To check if a user has a certain permission, this API method can be used:
portal_membership.checkPermission('Manage portal', here)2006-08-27
Short recipe for membrane based user content types
- Create a membrane based content item (it just needs to implement certain interfaces defined in membrane.interfaces)
- Register the type with membranetool
- Important: Set the active workflow status (the state in which a member can login)
Allowing the anonymous to add portal content
In order for anonymous users to be able to add a content item inside a folder, the following permissions need to be had:
In the parent folder:
- View
- Add portal content
- Access content information
- Create XXX (for example, if the content item has a "creation_permission", this permission needs to be had.
On the object:
- View
- Modify portal content
- Access content information
Creating an Archetypes validator
Archetypes validators are used in the schema definition for a field. Default validators include isEmail, isURL, etc.
This is how to create a new validator:
First, the validator has to be registered with Archetypes, or zope will complain at startup and ignore the validator. So add something like this in the __init__.py of the product:
from Products.validation import validationNext, the source code for the validator (validators.py):
from validators import SamePasswordValidator
validation.register(SamePasswordValidator('isSamePassword'))
from Products.validation.interfaces import ivalidator
class SamePasswordValidator:
__implements__ = (ivalidator,)
def __init__(self, name):
self.name = name
def __call__(self, value, *args, **kwargs):
instance = kwargs.get('instance', None)
req = kwargs['REQUEST']
pass1 = req.form.get('password', None)
if pass1 <> value:
return """Validation failed: You need to enter the password twice."""
Specify creation roles for AGX generated content
An observation: when you want to specify the roles that are required for a user to have in order to create a piece of content, you'll have to also specify a creation permission.
This means that the following tagged values are required, to something like:
creation_permission = Create MyContent
creation_roles = python: ('Anonymous', 'Member')
This generates the following code in config.py:
DEFAULT_ADD_CONTENT_PERMISSION = "Add portal content"
setDefaultRoles(DEFAULT_ADD_CONTENT_PERMISSION, ('Manager', 'Owner'))
ADD_CONTENT_PERMISSIONS = {
'MyContent': 'Create MyContent',
}
setDefaultRoles('Create MyContent', ('Anonymous', 'Member'))
2006-08-23
Creating and managing a Windows service (part 4)
The final step is to "compile" everything into Windows executables using py2exe. For this I have the following setup.py file:
from distutils.core import setup
import py2exe
import sys
# If run without args, build executables, in quiet mode.
if len(sys.argv) == 1:
sys.argv.append("py2exe")
sys.argv.append("-q")
class Target:
def __init__(self, **kw):
self.__dict__.update(kw)
# for the versioninfo resources
self.version = "0.1.0"
self.company_name = "Pixelblaster Romania"
self.copyright = "Copyright 2006 Pixelblaster Romania"
self.name = "MyService service"
################################################################
# THE SERVICE
myservice_service = Target(
description = "MyService daemon",
version = "0.1.0",
modules = ["myservice"]
)
################################################################
# THE CONFIGURATOR
manifest_template = '''
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
version="5.0.0.0"
processorArchitecture="x86"
name="%(prog)s"
type="win32"
/>
<description>%(prog)s Program</description>
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="X86"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
</assembly>
'''
RT_MANIFEST = 24
service_configurator = Target(
description = "The Service configurator",
script = "service_configurator.py",
other_resources = [(RT_MANIFEST, 1, manifest_template % dict(prog="configurator"))],
## icon_resources = [(1, "icon.ico")],
dest_base = "service_configurator")
#==============================
#CREATE SERVICE
create_service = Target(
description = "Creates the service",
script = "create_service.py",
dest_base = "create_service")
#==============================
#REMOVE SERVICE
remove_service = Target(
description = "Removes the service",
script = "remove_service.py",
dest_base = "remove_service")
################################################################
# THE SERVICE CONTROLER
################################################################
service_controler = Target(
description = "The configurator",
script = "service_controler.py",
other_resources = [(RT_MANIFEST, 1, manifest_template % dict(prog="configurator"))],
## icon_resources = [(1, "icon.ico")],
dest_base = "service_controler")
excludes = ["pywin", "pywin.debugger", "pywin.debugger.dbgcon",
"pywin.dialogs", "pywin.dialogs.list"]
setup(
options = {"py2exe": {
"compressed": 1,
"optimize": 2,
"excludes": excludes}},
zipfile = "lib/shared.zip",
service = [service],
windows = [configurator, service_controler],
console = [create_service, remove_service]
)
I think that's it. All that is left is to wrap everything to a nice
installer using InnoTools and just deploy it to the customer. :-)
Creating and managing a Windows service (part 3)
This third part shows the code needed to start/stop and set the startup type for the service. Actually, most of the necessary code sits in a ActiveState python-cookbook recipe, found here.
So, the code, using the recipe is simple (well, except that it's presented as a wxwidgets windows):
#!/usr/bin/env python
# -*- coding: ISO-8859-1 -*-
# generated by wxGlade 0.4.1cvs on Mon Aug 21 19:12:22 2006
import wx
from dlgControlerMainFrame import dlgControlerMainFrame
from service_utils import WService
from traceback_format import hook
import sys
class Alert:
def __init__ (self, parent, msg, style=wx.OK | wx.ICON_EXCLAMATION):
self.msgDlg = wx.MessageDialog(parent, msg, style=style)
def __call__(self):
res = self.msgDlg.ShowModal()
return res
STARTUP_TYPES = [ "boot", "system", "automatic", "manual", "disabled" ]
class MyApp(wx.App):
def OnInit(self):
wx.InitAllImageHandlers()
MainFrame = dlgControlerMainFrame(None, -1, "")
self.SetTopWindow(MainFrame)
MainFrame.Show()
self.dlg = MainFrame
def conf_hook(tb_type, tb_value, tb):
msg = hook(tb_type, tb_value, tb)
Alert(self.dlg, msg)()
sys.excepthook = conf_hook
self.extra_init()
return 1
def extra_init(self):
self.service = WService('Epaca')
self.dlg.button_3.Bind(wx.EVT_BUTTON, self.clickStart)
self.dlg.button_4.Bind(wx.EVT_BUTTON, self.clickStop)
self.dlg.button_2.Bind(wx.EVT_BUTTON, self.clickStatus)
self.dlg.combo_box_1.Bind(wx.EVT_COMBOBOX, self.comboClick)
self.reload()
def reload(self):
status = self.service.status(0)
self.dlg.MainFrame_statusbar.SetStatusText("The Epaca service is " + status)
start_type = self.service.infostartup()
ix = STARTUP_TYPES.index(start_type)
self.dlg.combo_box_1.SetSelection(ix)
def clickStart(self, event):
self.service.start()
self.service.fetchstatus('RUNNING')
self.reload()
def clickStop(self, event):
self.service.stop()
self.service.fetchstatus('STOPPED')
self.reload()
def clickStatus(self, event):
self.reload()
def comboClick(self, event):
val = self.dlg.combo_box_1.GetValue().strip().lower()
self.service.setstartup(val)
self.dlg.MainFrame_statusbar.SetStatusText('Service startup was changed')
# end of class MyApp
if __name__ == "__main__":
service_controler = MyApp(0)
service_controler.MainLoop()
The dialog window is generated with wxGlade in dlgControlerMainFrame.py , and has the following source code:
# -*- coding: ISO-8859-1 -*-Next part, the setup.py script that will create the exe files.
# generated by wxGlade 0.4.1cvs on Mon Aug 21 19:10:50 2006
import wx
# begin wxGlade: dependencies
# end wxGlade
class dlgControlerMainFrame(wx.Frame):
def __init__(self, *args, **kwds):
# begin wxGlade: dlgControlerMainFrame.__init__
kwds["style"] = wx.CAPTION|wx.CLOSE_BOX|wx.SYSTEM_MENU
wx.Frame.__init__(self, *args, **kwds)
self.panel_2 = wx.Panel(self, -1)
self.panel_1 = wx.Panel(self, -1)
self.MainFrame_statusbar = self.CreateStatusBar(1, 0)
self.button_3 = wx.Button(self.panel_1, -1, "Start")
self.button_4 = wx.Button(self.panel_1, -1, "Stop")
self.button_2 = wx.Button(self.panel_1, -1, "Refresh")
self.label_1 = wx.StaticText(self.panel_1, -1, "Set startup type:")
self.combo_box_1 = wx.ComboBox(self.panel_1, -1, choices=["Boot", "System", "Automatic", "Manual", "Disabled"], style=wx.CB_DROPDOWN|wx.CB_READONLY)
self.label_2 = wx.StaticText(self.panel_2, -1, "STARTUP TYPES:\n\nAutomatic: start at boot time\n\nManual: use the Epaca Service Controler \nto start the service\n\nDisabled: the service will not start", style=wx.ALIGN_RIGHT)
self.__set_properties()
self.__do_layout()
# end wxGlade
def __set_properties(self):
# begin wxGlade: dlgControlerMainFrame.__set_properties
self.SetTitle("Configure the Epaca Service")
self.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE))
self.MainFrame_statusbar.SetStatusWidths([-1])
# statusbar fields
MainFrame_statusbar_fields = ["MainFrame_statusbar"]
for i in range(len(MainFrame_statusbar_fields)):
self.MainFrame_statusbar.SetStatusText(MainFrame_statusbar_fields[i], i)
self.combo_box_1.SetSelection(-1)
# end wxGlade
def __do_layout(self):
# begin wxGlade: dlgControlerMainFrame.__do_layout
sizer_1 = wx.BoxSizer(wx.VERTICAL)
sizer_3 = wx.BoxSizer(wx.HORIZONTAL)
sizer_2 = wx.BoxSizer(wx.HORIZONTAL)
sizer_2.Add(self.button_3, 0, wx.ALL|wx.ADJUST_MINSIZE, 6)
sizer_2.Add(self.button_4, 0, wx.ALL|wx.ADJUST_MINSIZE, 6)
sizer_2.Add(self.button_2, 0, wx.ALL|wx.ADJUST_MINSIZE, 6)
sizer_2.Add(self.label_1, 0, wx.TOP|wx.ADJUST_MINSIZE, 10)
sizer_2.Add(self.combo_box_1, 0, wx.ALL|wx.ADJUST_MINSIZE, 6)
self.panel_1.SetAutoLayout(True)
self.panel_1.SetSizer(sizer_2)
sizer_2.Fit(self.panel_1)
sizer_2.SetSizeHints(self.panel_1)
sizer_1.Add(self.panel_1, 0, wx.ALL|wx.EXPAND, 6)
sizer_3.Add(self.label_2, 1, wx.ALL|wx.EXPAND|wx.ADJUST_MINSIZE, 6)
self.panel_2.SetAutoLayout(True)
self.panel_2.SetSizer(sizer_3)
sizer_3.Fit(self.panel_2)
sizer_3.SetSizeHints(self.panel_2)
sizer_1.Add(self.panel_2, 1, wx.EXPAND, 0)
self.SetAutoLayout(True)
self.SetSizer(sizer_1)
sizer_1.Fit(self)
sizer_1.SetSizeHints(self)
self.Layout()
self.Centre()
# end wxGlade
# end of class dlgControlerMainFrame
Creating and managing a Windows service (part 2)
import sys
import win32service, win32serviceutil
from config import SERVICE_NAME, SERVICE_DISPLAY_NAME, SOFTWAREPATH
service_path = "%s\myservice.exe" % SOFTWAREPATH
def debug(msg):
print msg
def removeSvc():
debug('called removeSvc()')
win32serviceutil.RemoveService(SERVICE_NAME)
debug('...service was removed')
def installSvc():
debug('installSvc()')
hscm = win32service.OpenSCManager(None,None,win32service.SC_MANAGER_ALL_ACCESS)
debug('...opened svc manager')
try:
try:
hs = win32service.CreateService(hscm,
SERVICE_NAME,
SERVICE_DISPLAY_NAME,
win32service.SERVICE_ALL_ACCESS, # desired access
win32service.SERVICE_WIN32_OWN_PROCESS, # service type
win32service.SERVICE_DEMAND_START,
win32service.SERVICE_ERROR_NORMAL, # error control type
service_path,
None,
0,
None,
None,
None)
debug('...installed service')
win32service.CloseServiceHandle(hs)
except:
debug('...failed to install service')
debug('...%s' % sys.exc_info()[0])
debug('...%s' % sys.exc_info()[1])
finally:
debug('...closing service handle')
win32service.CloseServiceHandle(hscm)
if __name__ == "__main__":
installSvc()
raw_input('Press <ENTER> to continue...')
To delete the service, use this code:
import sys
import win32service, win32serviceutil
from config import SERVICE_NAME, SERVICE_DISPLAY_NAME
def debug(msg):
print msg
def removeSvc():
debug('called removeSvc()')
win32serviceutil.RemoveService(SERVICE_NAME)
debug('...service was removed')
if __name__ == "__main__":
removeSvc()
raw_input('Press <ENTER> to continue...')
Next, how to start/stop/set startup for this service.
Creating and managing a Windows service (part 1)
I'm starting a longer piece on creating and managing a python based Windows service, so look for the other parts in this blog for the complete "recipe"
First, our tools: py2exe has, among its deployment targets, a "windows service" option, so we'll need that. To manage the service and interact with the Windows event log, the Python win32 extension is needed.
Py2exe has a sample service in the samples/advanced folder, on which I've based my code.
import win32serviceutil
import win32service
import win32event
import win32evtlogutil
import time, sys, StringIO
class MyService(win32serviceutil.ServiceFramework):
_svc_name_ = "MyService"
_svc_display_name_ = "My pretty pretty service"
_svc_deps_ = ["EventLog"]
def __init__(self, args):
win32serviceutil.ServiceFramework.__init__(self, args)
self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
def SvcStop(self):
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
win32event.SetEvent(self.hWaitStop)
def SvcDoRun(self):
# Write a 'started' event to the event log...
win32evtlogutil.ReportEvent(self._svc_name_,
servicemanager.PYS_SERVICE_STARTED,
0, # category
servicemanager.EVENTLOG_INFORMATION_TYPE,
(self._svc_name_, ''))
import servicemanager #another way to write to the event log
servicemanager.LogInfoMsg("Started the service") #seems to fix the msgids that are logged into the event log
while True:
# wait for beeing stopped...
rc = win32event.WaitForSingleObject(self.hWaitStop, 1)
if rc==win32event.WAIT_OBJECT_0:
break
time.sleep(2)
servicemanager.LogInfoMsg('next loop')
# and write a 'stopped' event to the event log.
win32evtlogutil.ReportEvent(self._svc_name_,
servicemanager.PYS_SERVICE_STOPPED,
0, # category
servicemanager.EVENTLOG_INFORMATION_TYPE,
(self._svc_name_, ''))
if __name__ == '__main__':
# Note that this code will not be run in the 'frozen' exe-file!!!
win32serviceutil.HandleCommandLine(MyService)
Next, how to create a Windows service from python.
Capturing print statements output
Sometimes you have code that has a lot of print statements, which are helping you with debugging. But what happens if you try to port that code to an environment without a console? In the best case, you won't see the prints anymore, in the worse case, the code will fail because there's no "valid file descriptor" to which to write.
The solution to this problem is to replace python's stdout file, which will enable the code output to be recorded and maybe logged for debugging purposes.
The following snippet shows how to achieve this:
>>> import StringIO, sys
>>> out = StringIO.StringIO()
>>> sys.stdout = out
>>> print "Test"
>>> log = out.getvalue()
>>> assert (log == "Test\n")
>>>
Short guide to dns with bind on Fedora
I'm replacing a tinydns server with bind9, so I may as well put the setup here, as future reference.
First,
yum install bind-chroot
to install the chrooted bind server.
Next, edit the /var/named/chroot/etc/named.conf
options {
directory "/var/named";
dump-file "/var/named/data/cache_dump.db";
statistics-file "/var/named/data/named_stats.txt";
recursion no;
};
//root global
//life
zone "life.org.ro" {
type master;
file "/etc/db.life.org.ro";
notify yes;
};
zone "0.0.127.in-addr.arpa" {
type master;
file "/etc/db.localhost";
allow-update { none; };
};
zone "58.77.82.in-addr.arpa" {
type master;
file "/etc/db.82.77.58.133";
};
zone "caleidoscop.org.ro" {
type master;
file "/etc/db.caleidoscop.org.ro";
notify yes;
};
include "/etc/rndc.key";
I'm defining four zones: life.org.ro, reverse localhost (127.0.0.1), reverse dns for the IP and an extra host, caleidoscop.org.ro.
Now, the content of db.life.org.ro
$TTL 86400
life.org.ro. IN SOA a.ns.life.org.ro. hostmaster.life.org.ro (
2006082102 ; serial
1h ; refresh
15m ; retry
15d ; expire
1h ) ; negative caching
; NAMESERVER
life.org.ro. IN NS a.ns.life.org.ro. ; nameserver
;
; hosts (canonical names)
;
life.org.ro. IN A 82.77.58.133
a.ns.life.org.ro. IN A 82.77.58.133
mail.life.org.ro. IN A 82.77.58.133
www.life.org.ro. IN A 82.77.58.133
;
; mail exchanger
;
life.org.ro. IN MX 10 mail.life.org.ro.
; SPF records
life.org.ro. IN TXT "v=spf1 a mx ~all"
mail.life.org.ro. IN TXT "v=spf1 a mx -all"
The domain is registered at RNC (Romanian central dns registry) with 82.77.58.133 a.ns.life.org.ro, so I'm setting a.ns.life.org.ro as the authoritative nameserver in line 2, then define the nameserver, the hosts, mail exchanger and the SPF records (thanks to the http://openspf.org wizard). The server in itself has only one internet connection, with only one IP address, (no redundancies), so I've just defined one nameserver.
Next, the reverse IP entry for 82.77.58.133, in db.82.77.58.133
$TTL 3h
58.77.82.in-addr.arpa. IN SOA a.ns.life.org.ro. hostmaster.life.org.ro (
1 ; serial
1h ; refresh
15m ; retry
30d ; expire
1h ) ; negative caching
; NAMESERVER
58.77.82.in-addr.arpa. IN NS a.ns.life.org.ro. ; nameserver
;
; hosts (canonical names)
;
133.58.77.82.in-addr.arpa. IN PTR life.org.ro.
Just added a PTR record for life.org.ro
Now, the file db.caleidoscop.org.ro. I've defined the host, primary name server and SPF record.
$TTL 86400
caleidoscop.org.ro. IN SOA a.ns.life.org.ro. hostmaster.caleidoscop.org.ro (
1 ; serial
1h ; refresh
15m ; retry
30d ; expire
1h ) ; negative caching
;
; hosts (canonical names)
;
caleidoscop.org.ro. IN A 82.77.58.133
www.caleidoscop.org.ro. IN A 82.77.58.133
;
; Aliases
;
;mail.life.org.ro. IN CNAME server.life.org.ro.
;
; mail exchanger
;
caleidoscop.org.ro. IN MX 10 mail.life.org.ro.
; SPF record
caleidoscop.org.ro. IN TXT "v=spf1 a mx ~all"
Finally, the entry for db.localhost
$TTL 3h
0.0.127.in-addr.arpa. IN SOA a.ns.life.org.ro. hostmaster.life.org.ro. (
1 ; Serial
3h ; Refresh after 3 hours
1h ; Retry after 1 hour
1w ; Expire after 1 week
1h ) ; Negative caching TTL of 1 hour
0.0.127.in-addr.arpa. IN NS a.ns.life.org.ro.
1.0.0.127.in-addr.arpa. IN PTR localhost.
That's about it. I may have made some mistakes, but checking the domains with dnsreport yields good reports, so I'll leave it like this.
2006-08-16
Creating arbitrary objects in the current module namespace
This short recipe will show how to create arbitrary named objects in the current module namespace. Either globals() or locals() can be used for the task, depending on the namespace where the variables have to be created (global or local).
>>> for i in range(10):
>>> globals()['test%s' % i] = i
>>> print test0
0
>>> print test1
1
2006-08-14
Short intro to ZEO
Joel Burton is holding ad-hoc tutorials on IRC :-)
Anyway, here's his recipe to running ZEO
<joelburton> having your site running under zeo also allows you to "zopectl debug" it while it's still running, which is insanely useful. i _always_ run zope under zeo, even during development, even on my laptop, etc., and i recommend that to others.
<joelburton> thegoldenaura: zeo just splits zope-the-app-server and zope-the-database into two
<joelburton> the goldenaura: for small sites, where you expect to have just one zope server for your zeo server, i do it like this:
<joelburton> 1) use $ZOPE/bin/mkzeoinstance to make a zeo instance. for these small instances, i put it in $INSTANCE_HOME/zeo
<joelburton> 2) stop your zope instance; move your Data.fs from $INSTANCE_HOME/var to $INSTANCE_HOME/zeo/var
<joelburton> 3) edit your $INSTANCE_HOME/zope.conf to comment out the normal main storage, and ot use the ZEO storage (both at the bottom)
<joelburton> 4) start up zeo and zope
2006-08-13
Small tip when using wxWidgets
Always put your controls in a sizer inside a panel. So, the actual tree would be frame > sizer > panel > sizer > ... controls
The reason is that the panel component will enable tab traversal. Also, the panel can so be used to group the widgets into logical groups, to facilitate traversing them with the tab key. Oh, did I mention wxGlade (from cvs) rules?
Miniguide to openldap
LDAP is a lot easier then one might think at first sight. First, reading this short introduction to LDAP will tell us that LDAP is just an object database, that holds trees of objects and schemas of those objects. This is nice and easy for anyone acustomed to ZODB and Archetypes.
Next, installing. The OpenLDAP server on Debian is called slapd, so apt-get install slapd. After that, run dpkg-reconfigure -plow slapd which will allow reconfiguring the domains. For example, I've set my domain to pixelblaster.ro and the organization unit (ou) to Pixelblaster, which would result in base dn of dc=pixelblaster,dc=ro for the server and ou=Pixelblaster,dc=pixelblaster,dc=ro for the Pixelblaster branch. Another common setup is to create the following base dn: ou=programmers,o=Pixelblaster
With a tools such as JXplorer a connection to the server, to the dc=pixelblaster,dc=ro base db, with a binding authentication of cn=admin,dc=pixelblaster,dc=ro
Some of the common shortcuts used by ldap:
o = organization
ou = organization unit
dc = domain component
cn = common name
sn = surname
To make an address book, I have created the following dn: ou=people,o=Pixelblaster,dc=pixelblaster,dc=ro Now I just have to add inetOrgPerson objects that will act as addressbook entries. Some tools dedicated to this tasks are:
- directoryassistant, a nice python based utility that can be used to search and edit this address book
- kaddressbook is a much more complex tool, integrated with the KDE desktop
- just as fun is luma, a python based Qt app that can be used, among many other functions, as addressbook
Next step is to put the LDAP server to work and make it serve samba and unix accounts
2006-08-12
Tricks of the mind
I've been playing in my mind, for the last two days, for no apparent reason, "Birds of a feather" by Phish. I even asked myself at one point what's with the song that keeps appearing in my mind. Well, I think I found the reason. For the last two days, the first portlet on my customized slashdot homepage was KDENews, which had a news item on a "Birds of a Feather" KDE event. The weird thing is that I didn't consciously made the connection with the song, but every time I visited the slashdot page, I got the song in the back of my mind. No wonder the stupid commercials work most of the time...
Overriding the form controler script for AT content
Just like the edit form or the view page can be overriden for an AT type, so can the form controler script that would be called by the form. This recipe is lifted from SignupSheet (which I think got an inspiration from POI):
The form controler script (mytype_post.cpy):
##bind container=container
##bind context=context
##bind namespace=
##bind script=script
##bind state=state
##bind subpath=traverse_subpath
##parameters=
##title=Do something
##
#do something here, like for example, changing the workflow status of the object
return state
The metadata file (mytype_post.cpy.metadata):
[actions]
action.success=redirect_to:string:${folder_url}/thank_you_message
action.failure=traverse_to_action:string:edit
And now, the magic happens in Extensions/Install.py
def addFormControllerAction(self, out, controller, template, status,
contentType, button, actionType, action):
"""Add the given action to the portalFormController"""
controller.addFormAction(template, status, contentType,
button, actionType, action)
print >> out, "Added action %s to %s" % (action, template)
def Install(portal):
controller = getToolByName(self, 'portal_form_controller')
addFormControllerAction(self, out, controller, 'validate_integrity',
'success', 'MyType', None, 'traverse_to', 'string:mytype_post')
A bit of explanation: validate_integrity is the last script called in the normal AT edit process. The install script will add another transition, so after the validate_integrity is executed, next comes the mytype_post script to be executed.
Cool use of Z3 tech in a Plone product
Easycommenting is a product that enables commenting on any content item. Rather dull, but the technology used to create it is very interesting. The skeleton is generated with ArchGenXML, but the content is zope 3 aware and can be adapted to use by any other object.
My main interest is to regard it as a documentation, as its implementation is clean and covers several areas of functionality that I'm very interested these days (z3 content in plone, adaptors, views, etc).