Personal tools
You are here: Home Weblog Archive 2006 August

Entries For: August 2006

2006-08-31

Small snippet to read the title for a DCWorkflow state

Filed Under:
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

Filed Under:

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

Filed Under:

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

Filed Under:
  1. Create a membrane based content item (it just needs to implement certain interfaces defined in membrane.interfaces)
  2. Register the type with membranetool
  3. Important: Set the active workflow status (the state in which a member can login)

Allowing the anonymous to add portal content

Filed Under:

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

Filed Under:

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 validation
from validators import SamePasswordValidator
validation.register(SamePasswordValidator('isSamePassword'))
Next, the source code for the validator (validators.py):
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

Filed Under:

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)

Filed Under:

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)

Filed Under:

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 -*-
# 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

Next part, the setup.py script that will create the exe files.

Creating and managing a Windows service (part 2)

Filed Under:
This part of the recipe shows the python code needed to create (install) the Windows service. I didn't write most of this, I just found it somewhere on the internet. Credit due to the original author.
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)

Filed Under:

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

Filed Under:

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

Filed Under:

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

Filed Under:

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

Filed Under:

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

Filed Under:

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

Filed Under:

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

Filed Under:

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

Filed Under:

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

Filed Under:

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).

Weblog
Atom
RDF
RSS 2.0
Powered by Quills
Creative Commons License
This work is licensed under a Creative Commons Attribution 3.0 License.
 

Powered by Plone CMS, the Open Source Content Management System

This site conforms to the following standards: