Wednesday, December 24, 2008

Disguising context.translate() as _

Edit (Dec. 2009): I currently think that it's better to define MyProductMessageFactory in __init__.py, and import that as _ in your scripts and filesystem code.
from zope.i18nmessageid import MessageFactory
MyProductMessageFactory = MessageFactory('MyProduct')

In the case of scripts, you'll have to add a security declaration:

from AccessControl.SecurityInfo import ModuleSecurityInfo
security = ModuleSecurityInfo('Products.MyProductMessageFactory')
security.declarePublic('MyProductMessageFactory')

And of course, see also the link that Mikko added: http://worldcookery.com/files/fivei18n/

The outdated manual



To make i18ndude aware of the strings you pass context.translate(), you can wrap these calls in a '_' method like so (example taken from PloneInvite):
def _(message):
""" Fake MessageFactory, allows calling context.translate() as _(u"") in
order to be able to use i18ndude on this script.

We want to use context.translate, but if we do, its input strings aren't
recognized by i18ndude as translatable strings. If we call it as _(u""),
i18ndude will recognize it and create translation strings in the .pot file.
"""
domain = 'PloneInvite'
msgid = message
return context.translate(default=message, domain=domain, msgid=msgid)

Now you can call it as portalmessage = _(u"Sent invitation to %s.") % invite_to_address, and i18ndude will create the appropriate strings in the .pot file.

If you have several scripts in your product which produce messages that should be translated, you might want to consider moving the wrapper method to a separate class. In PloneInvite, i added this to the __init__.py:
# FakeMessageFactory for translating strings in scripts
from AccessControl import ModuleSecurityInfo
from fakemessagefactory import FakeMessageFactory
ModuleSecurityInfo('Products.PloneInvite').declarePublic('FakeMessageFactory')

fakemessagefactory.py reads:
class FakeMessageFactory:

def __init__(self, context, domain):
self.context = context
self.domain = domain

def __call__(self, message):
msgid = message
return self.context.translate(default=message, domain=self.domain, msgid=msgid)

And i call it in the script like this:
from Products.PloneInvite import FakeMessageFactory
_ = FakeMessageFactory(context,'PloneInvite')

portalmessage = _(u"Sent invitation to %s.") % invite_to_address

Translating messages in python controller scripts

For some reason, using Plone's MesageFactory didn't work for me. However, context.translate() does all the work. Call it like so:
context.translate(
msgid = u'some_message',
default = u'Some message',
domain = 'myproductsi18domain')

and make sure you have something like
"Domain: myproductsi18domain\n"

#. Default: "Some message"
#: skins/myproduct/your_controller_script.cpy:18
msgid = "some_message"
msgstr = "Een bericht"

in the relevant file (probably i18n/myproductsi18ndomain-nl.po).

This has the disadvantage that the message strings are not recognized by i18ndude, so i18ndude will not automatically create the relevant strings for you in your .pot file. There's a workaround for this, but that's another blog post.

Tuesday, December 23, 2008

Spreading strings over multiple lines in .po files

It's a bother working with i18n files that have strings wider than your editor screen. But it's possible to cut up these long strings, and put the parts one different lines in your .po file. The string is easier to read and edit this way:

msgstr ""
"${name} invited you to join this site.\n"
"\n"
Click this link to join:\n"
"\n"
"${link}\n"


This is handy in the case of e-mail templates. Of course, the \n's are here to render line breaks in the translated message, the have nothing to do with line breaks in the .po-file.

Thursday, December 11, 2008

Saving prompt space

When using vi's :tabe command (opens a file in another "tab" in the same terminal window), i noticed it shows the path to the file in an abbreviated form: /home/kees/path/to/file becomes /h/k/p/t/file. This saves space, while preserving some information about where you are.

This seems like a useful feature in a prompt as well. For example, when \u@\h \w \$ yields something as long as ~/Documents/manuals/plone/code for the aspeli book/optilux-code-2007-10-21/chapter-09/examples, you're likely to get annoyed after a while.

So now my PS1="\u@\h \$(abbr_path.sh) \$ ", and the script is pwd | perl -p -e 's/([^\/])[^\/]*[\/]/$1\//g'. Let's see how long this setup keeps me happy. I might also replace the regexp with 's/([^\/]{1,3})[^\/]*[\/]/$1\//g' if i want more than one character per directory.

Wednesday, November 26, 2008

MailHost issue: validateSingleEmailAddress

When registering a new member, i got an error message that said mail could not be sent because validateSingleEmailAddress failed.

I also found that i could not delete my MailHost object's ESMTP loginname and password, neither through the Plone interface nor through the ZMI.

This link provided the answer: Throw away your MailHost, and replace it by a SecureMailHost with id 'MailHost'. Which works!

Although i still cannot empty the ESMTP settings, but that doesn't keep me from registering members.

Wednesday, November 19, 2008

Using Plone's Title-to-id conversion in a script using an External Method

I tried to plug into Plone's title-to-id conversion machinery in a script which creates new objects. Unfortunately, due to restricted python, doing certain imports is not allowed. I fixed it using an external method. I placed the module "MyProductExternalMethods.py" my product's Extensions directory:

from zope.component import queryUtility
from plone.i18n.normalizer.interfaces import IIDNormalizer

def convertTitleToId(title):
"""Plug into plone's id-from-title machinery.
"""
title = title.decode('utf-8')
newid = queryUtility(IIDNormalizer).normalize(title)
return newid

To my setuphandlers.py, i added a new method:

def installExternalMethod(portal, out, module, method, project, id=None):
"""
Install external method
"""
if not id:
id = method
print >> out, "Installing external method %s for %s" % (id, project)
if not id in portal.objectIds():
em = ExternalMethod(
id=id,
title='%s(%s)' % (id, method),
module='%s.%s' % (project, module) ,
function=method)
portal._setObject(id, em)

and i added
installExternalMethod(site, out, 'MyProductExternalMethods', 'convertTitleToId', PROJECTNAME)
to the postInstall method.

I might also have created a browser view which has this method, but i was not sure how to call that from a script, and curious if external methods still work in Plone 3. They do. I can now convert a title to an id in my script by doing
id = context.convertTitleToId(title)
.

Monday, November 10, 2008

Testing form input type in restricted python

My python controller script (.cpy), which should parse input from a form (.cpt), gets a parameter my_param which could be a single string or a list of strings. (If it's a single item, i want it converted to a list, because i'm going to loop over a list of items.)
However, "from types import type, ListType" is not available in a simple script. Luckily, we have something else: same_type

if not same_type(my_param,[]):
my_param = [my_param,]

Wednesday, November 5, 2008

AttributeError: 'module' object has no attribute 'Surrogate'

My Zope 2.9.9 instance wouldn't start, and failed with the message in the title. The hint in http://www.zope.org/Products/Zope/2.9.6/Zope-2_9_6-released/talkback/1172016438/discussionitem_view proved to be useful: after removing the zope 3 interface egg-info and the entire zope directory from /usr/lib/python2.4/site-packages, things are back to normal.

My guess is that this was installed after my upgrade to Ubuntu 8.10. For some reason, i had a package called python-zopeinterface installed.

Tuesday, November 4, 2008

Timeout during installation of new product

Today, while installing a new product on a production site with a large catalog, my connection timed out. This seems to be due to Plone re-cataloging all objects in the site. I know of two solutions:

  • Before installing, move profiles/default/catalog.xml to disabled_catalog.xml. Move it back after installing, and run the generic setup profile through the ZMI. (See also http://markmail.org/message/yi3qchvx3pua25du for another catalog installation issue.)

  • Patch setuphandlers.py to remove the updateRoleMappings step, like so:
    def dummyUpdateRoleMappings(context):
    """do nothing..."""
    pass

    updateRoleMappings = dummyUpdateRoleMappings

Sunday, November 2, 2008

Fast user switch on Intrepid using fglrx

Yesterday, I dist-upgraded our home computer to Ubuntu 8.10. The installed graphics driver (radeonhd, i believe) did not work with our ATI Radeon HD 2350: I got a blank screen. Setting the driver to 'vesa' did work.

After a user switch however, the colors would be messed up. This happens when using the "fast switch user" option, not when logging out and loggin in as someone else. Fast user switching is very convenient when I am playing music on Amarok, and H. wants to read her e-mail. There's plenty of literature on messed-up colors at launchpad.

So i went looking for the right driver. It seems that 'radeon' is the one that should work, but for some reason it doesn't. After trying the drivers 'ati', 'radeon' and 'radeonhd' with varying xorg.conf files, i finally installed the proprietary 'fglrx' driver, which worked.

Wednesday, October 29, 2008

Special characters in VDEX vocabularies?

If this title is a question, the answer must be "no". I had modified the VDEX vocabularies in my product 'coachlink', and installing them resulted in an ExpatError:

Module , line 6, in _facade
Module AccessControl.requestmethod, line 64, in _curried
Module Products.CMFQuickInstallerTool.QuickInstallerTool, line 643, in reinstallProducts
Module Products.CMFQuickInstallerTool.QuickInstallerTool, line 584, in installProducts
Module Products.CMFQuickInstallerTool.QuickInstallerTool, line 521, in installProduct
- __traceback_info__: ('coachlink',)
Module Products.GenericSetup.tool, line 390, in runAllImportStepsFromProfile
- __traceback_info__: profile-Products.coachlink:default
Module Products.GenericSetup.tool, line 1182, in _runImportStepsFromContext
Module Products.GenericSetup.tool, line 1093, in _doRunImportStep
- __traceback_info__: coachlink-Install-Vocabularies
Module Products.coachlink.setuphandlers, line 92, in installVocabularies
Module Products.ATVocabularyManager.types.vdex.vocabulary, line 274, in importXMLBinding
Module xml.dom.minidom, line 1925, in parseString
Module xml.dom.expatbuilder, line 940, in parseString
Module xml.dom.expatbuilder, line 223, in parseString
ExpatError: undefined entity: line 34, column 40

One of the changes was putting an ‘ instead of ‘ somewhere, so that was a clue... Removing everything that began with & from the vocabulary helped.

Monday, October 27, 2008

Query portal catalog by interface: object_provides

Edit 2010-05-19: This is now a how-to on plone.org

Suppose you have several types (for example, event types like 'Birthday','Wedding','Graduation') in your portal which implement the same interface (for example, IIsCauseForCelebration), and you want to get items of these types from the catalog.
Rather than name the types explicitly (like portal_type=['Birthday','Wedding','Graduation' ]) you may query the wanted interface:

from Products.MyProduct.path.to import IIsCauseForCelebration
catalog(object_provides=IIsCauseForCelebration.__identifier__)

This has the additional advantage that if products added or modified later add types which implement the interface, these new types will also show up in your query.

Wednesday, October 22, 2008

How to pretty-print an "ugly HTML" document

Using Python and the BeautifulSoup module:

from BeautifulSoup import BeautifulSoup
html = '' # insert UHTML here
soup=BeautifulSoup(html)
print soup.prettify()

Monday, October 20, 2008

default_method for ReferenceField?

Trying to set a default value (via the default_method tagged value in UML) on a reference field, and wondering why it didn't work, i ran into this discussion, and i found this post in the plone issue tracker, marking it as a requested enhancement. It seems that setting a default method for a ReferenceField not yet possible.

Monday, October 13, 2008

Why does user.hasRole('Manager') always return True?

I wouldn't know, but this may be a clue.

Bottom line: If you want to check for a user's roles from TAL, use "user.has_role()", not "user.hasRole()".

Example:
user.has_role(['Owner','Editor',], context)
checks for roles on current context.

Wednesday, October 8, 2008

Stand Eredivisie

Friday, October 3, 2008

Indexing files in Plone 3

Why doesn't Plone index my pdf/doc/xls attachments, or why aren't they shown in the search results?
Possible reasons (in random order):

  • Because file conversion tools aren't available on the command line. For a list of required converters, see
    http://www.zopyx.de/projects/TextIndexNG3/documentation/external-converters

  • Because the file transform isn't registered in Portal Transforms. To add a pdf to text converter: Go to portal_transforms, click 'Add' (Transform), give it an id of 'pdf_to_text' and Module 'Products.PortalTransforms.transforms.pdf_to_text'. This step may be required when converters weren't available on the system during the Zope installation.

  • Because the "File" type was disabled in @@search-controlpanel

Tuesday, September 30, 2008

Foto's downloaden van mijnalbum.nl

[edit 21-10-2008: album-id meegeven vanaf command line]

Update 12-09-2015: Dit scriptje is overbodig geworden omdat mijnalbum.nl nu een knop heeft waarmee je het hele album kunt downloaden. Klik op "Foto's opslaan",  dan op "Album downloaden".

Mijnalbum.nl is een site waar je foto's kan neerzetten. Het enige nadeel is dat deze foto's alleen te bekijken zijn via de site, dat is wat onhandig bladeren. Het zou dan ook fijner zijn alle foto's in een keer te downloaden, zodat je er op je gemak doorheen kunt bladeren. Gelukkig hebben ze dat niet al te moeilijk gemaakt: leve python en de lxml-module!
#!/usr/bin/python

import sys
from lxml import etree
from lxml import objectify
from urllib import urlopen

# pass the album id as a command line argument
album_id = sys.argv[1]
thumbs_url = 'http://www.mijnalbum.nl/index.php?m=albumview&a=2&key='
thumbs_url += album_id

parser = etree.HTMLParser()
page = urlopen(thumbs_url)
html = page.read()

htmltree  = etree.HTML(html)

for element in htmltree.xpath("body/table/tr/td[@class='thumbfoto']"):
    id = element.get('id')
    to_be_stripped = 'thumbcont-'
    foto_id = id.replace(to_be_stripped,'',1)
    foto_url = 'http://www.mijnalbum.nl/GroteFoto-'+foto_id+'.jpg'
    print foto_url

# Just pass the output to wget using "| xargs wget".

Dit maakte het downloaden van alle foto's van de Ronde van Winsum een stuk makkelijker. Hieronder is een sleutelmoment in deze legendarisch ronde te zien.

Sunday, September 28, 2008

Stand Eredivisie

Een mooi plaatje om te bewaren:

Friday, September 26, 2008

Using base_class tag instead of drawing lines in UML

When you're extending one class many time, and all the lines are cluttering up your UML model, you can use the tag base_class on the extending classes instead of a line.

Thursday, September 25, 2008

i18ndude says "No entries for domain" when you pass filenames

If you pass a new version i18ndude a filename or filetype, it will say "No entries for domain".

See
http://plone.org/products/i18ndude/i18ndudetracker/20

Define a RecordsField through UML (Archgenxml 2.1)

In Archgenxml 1.5, a field called "record" or "records" would be converted to a RecordsField. Somewhere between 1.5 and 2.1, this functionality was broken. In current agx, you may define a RecordsField as a stub in a separate stub package "fields" like so:

Set import_from=Products.ATExtensions.field.records, and make sure to add the "dependency" lines. You will probably also want the accompanying widget, see the model below. Import the widget from Products.ATExtensions.widget
There is a difference between a RecordField/RecordWidget and a RecordsField/RecordsWidget, to use a RecordField import it from Products.ATExtensions.field.records

After having done this, you may define a field of type Record(s)field, and set a tagged value widget=RecordsWidget (or RecordWidget).

Wednesday, September 24, 2008

Extracting information from CVS

CVS history:
cvs history -acf path/to/file | sort

By default, CVS will not show it for all users (a) and also not on all modifications (commits, c). The results are not always in chronological order, so sort them.

CVS log prints the information that was entered to describe the update:
cvs log path/to/file


To compare to another revision:
cvs diff -r 1.44 -r 1.45 path/to/file

Saturday, September 20, 2008

Goldmund, Wyldebeast & Wunderfahrrad

Sommigen hebben een auto van de zaak, bij Goldmund, Wyldebeast & Wunderliebe doen we niet mee aan die gekkigheid. CO2-neutraal en bevorderlijk voor des werknemers welbevinden worden er bedrijfsfietsen uitgedeeld.

Uitslag Ronde van Winsum 13 september 2008

Heren
1. Wilfred van Holst
2. Peter Wolters
3. Henk Bakker
4. Arend Mulder
5. Kees Hink
6. Sybren Mulder
7. Jaap
8. Paul Boon
de rest DNF

Dames
1. Chantal
2. Marieke Falkmann
3. Frenke Bolt

Dit zijn de uitslagen van de etappe, er was ook nog een proloog, maar voor de heren is de klassering ongewijzigd.

Zie verder de hyve.

Thursday, September 18, 2008

Setting a field value by its fieldname (Archetypes)

Suppose you have a long list of fieldnames that you want to change on an Archetypes content type. You might want to do obj.setField1(), obj.setField2() etc. for all these fields, but it would be nicer to pass in a list of fieldnames. This is possible on Archetypes content types as follows:

# the fields to be set are in a dictionary adres_dict
for fieldname in adres_dict.keys():
# value to be set on the field
value = adres_dict[fieldname]
# get the field from the schema
field = obj.Schema().getField(fieldname)
# get the mutator name
mutator = field.mutator
# get the mutator function
func = getattr(obj, mutator)
# set the value using the mutator function
func(value)
print " Inserted value %s into field %s" % (value,fieldname,)

Tuesday, September 2, 2008

archgenxml + i18ndude + Plone 2.5: AttributeError: 'GlobalAdapterRegistry' object has no attribute '_null'

A small addition to the last post, intended for use with Plone 2.5:

When i run archgenxml on a Plone 2.5 project (i set PYTHONPATH to my Zope 2.9.9's lib/python),
i got this error: AttributeError: 'GlobalAdapterRegistry' object has no attribute '_null'
(see http://paste.plone.org/23497)

I am running archgenxml + i18ndude from a virtualenv, to prevent corrupting my Zope libraries
with i18ndude's eggs. Before easy_installing i18ndude, it worked fine. For Zope 2.10.6, it still
works fine.

Removing from my virtualenv the extra eggs that i18ndude pulled in (zope.testing-3.6.0-py2.4.egg,
zope.interface-3.4.1-py2.4-linux-x86_64.egg, zope.tal-3.5.0-py2.4.egg) made it work.

(googling yields http://zope3.pov.lt/irclogs/%23zope3-dev.2006-09-22.log.html, not sure if it's
related)

Friday, August 29, 2008

Plone won't start after installing i18ndude

To generate a generated.pot file from your uml diagram, ArchGenXML can use i18ndude if that module is installed in python.
When i installed i18ndude in my system-wide python2.4 (the same one used by Zope) using easy_install, at least one of my Zope/Plone instances wouldn't start afterwards. This is because i18ndude installs all kinds of dependency eggs. One solution is to just remove all these extra eggs after you install i18ndude: Go to site-packages and remove the eggs that were installed at the same time as i18ndude.
Another, i think better, way is to use virtualenv. in my ~/bin directory, i created a directory for AGX called archgenxml-virtualenv. I virtualenv'ed this directory and installed archgenxml and i18ndude in it. Last, i made a symlink from my ~/bin directory to the AGX executable and the i18ndude executable.
To sum it up:

sudo apt-get install virtualenv
mkdir ~/bin
cd ~/bin
mkdir archgenxml-virtualenv
virtualenv archgenxml-virtualenv
. archgenxml-virtualenv/bin/activate
easy_install archgenxml
easy_install i18ndude
deactivate
ln -s archgenxml-virtualenv/bin/archgenxml .
ln -s archgenxml-virtualenv/bin/i18ndude .

Thursday, August 28, 2008

Translating a content type's name

So you have created a nice MyProduct-xx.po file, but how to translate the names of your custom content-types? This goes outside the MyProduct-domain. Place a MyProduct-plone-xx.po in your i18n directory, with domain set to "plone". And just enter strings and translations.

May also be used to override Plone's default translations.

Tuesday, August 19, 2008

A mysterious error solved (TypeError: expected string or buffer)

Today, I ran into an error message when creating a new object. The object was going to be an instance of "ImportedObject", which i created with ArchGenXML. Here is the traceback:

2008-08-19 11:51:27 ERROR Plone
Traceback (most recent call last):
File "/opt/zope/prd/www.domain.com/Products/CMFPlone/FactoryTool.py", line 147, in __getitem__
File "/opt/zope/prd/www.domain.com/Products/CMFPlone/PloneFolder.py", line 399, in invokeFactory
File "/opt/zope/prd/www.domain.com/Products/CMFCore/TypesTool.py", line 934, in constructContent
File "/opt/zope/prd/www.domain.com/Products/CMFCore/TypesTool.py", line 343, in constructInstance
File "/opt/zope/prd/www.domain.com/Products/CMFCore/TypesTool.py", line 574, in _constructInstance
File "", line 4, in addImportedProject
File "/home/kees/zope-2.9.9/lib/python/OFS/ObjectManager.py", line 333, in _setObject
notify(ObjectAddedEvent(ob, self, id))
File "/home/kees/zope-2.9.9/lib/python/zope/event/__init__.py", line 23, in notify
subscriber(event)
File "/home/kees/zope-2.9.9/lib/python/zope/app/event/dispatching.py", line 66, in dispatch
for ignored in subscribers(event, None):
File "/home/kees/zope-2.9.9/lib/python/zope/component/__init__.py", line 181, in subscribers
return sitemanager.subscribers(objects, interface)
File "/home/kees/zope-2.9.9/lib/python/zope/component/site.py", line 89, in subscribers
return self.adapters.subscribers(required, provided)
File "/home/kees/zope-2.9.9/lib/python/zope/interface/adapter.py", line 481, in subscribers
subscribers = [subscription(*objects)
File "/home/kees/zope-2.9.9/lib/python/zope/app/event/objectevent.py", line 192, in objectEventNotify
adapters = subscribers((event.object, event), None)
File "/home/kees/zope-2.9.9/lib/python/zope/component/__init__.py", line 181, in subscribers
return sitemanager.subscribers(objects, interface)
File "/home/kees/zope-2.9.9/lib/python/zope/component/site.py", line 89, in subscribers
return self.adapters.subscribers(required, provided)
File "/home/kees/zope-2.9.9/lib/python/zope/interface/adapter.py", line 481, in subscribers
subscribers = [subscription(*objects)
File "/home/kees/zope-2.9.9/lib/python/OFS/subscribers.py", line 114, in dispatchObjectMovedEvent
callManageAfterAdd(ob, event.object, event.newParent)
File "/home/kees/zope-2.9.9/lib/python/OFS/subscribers.py", line 144, in callManageAfterAdd
ob.manage_afterAdd(item, container)
File "/opt/zope/prd/www.domain.com/Products/Archetypes/BaseFolder.py", line 88, in manage_afterAdd
File "/opt/zope/prd/www.domain.com/Products/CMFCore/CMFCatalogAware.py", line 202, in manage_afterAdd
File "/opt/zope/prd/www.domain.com/Products/Archetypes/CatalogMultiplex.py", line 34, in indexObject
File "/opt/zope/prd/www.domain.com/Products/I18NFolder/patches.py", line 281, in catalog_object
File "/opt/zope/prd/noorderlink/client0/Products/CacheSetup/patch.py", line 100, in catalog_object
File "/opt/zope/prd/noorderlink/client0/Products/CacheSetup/patch_utils.py", line 6, in call
File "/home/kees/zope-2.9.9/lib/python/Products/ZCatalog/ZCatalog.py", line 567, in catalog_object
update_metadata=update_metadata)
File "/home/kees/zope-2.9.9/lib/python/Products/ZCatalog/Catalog.py", line 339, in catalogObject
index = self.updateMetadata(object, uid)
File "/home/kees/zope-2.9.9/lib/python/Products/ZCatalog/Catalog.py", line 277, in updateMetadata
newDataRecord = self.recordify(object)
File "/home/kees/zope-2.9.9/lib/python/Products/ZCatalog/Catalog.py", line 416, in recordify
attr=getattr(object, x, MV)
File "/opt/zope/prd/www.domain.com/Products/CMFPlone/CatalogTool.py", line 99, in __getattr__
File "/opt/zope/prd/www.domain.com/Products/CMFPlone/CatalogTool.py", line 206, in getObjSize
File "/opt/zope/prd/www.domain.com/Products/Archetypes/BaseObject.py", line 583, in get_size
File "/opt/zope/prd/www.domain.com/Products/Archetypes/Field.py", line 1311, in get_size
File "/opt/zope/prd/www.domain.com/Products/Archetypes/Field.py", line 1305, in getBaseUnit
File "/opt/zope/prd/www.domain.com/Products/Archetypes/Field.py", line 1278, in get
File "/opt/zope/prd/www.domain.com/Products/Archetypes/Field.py", line 1033, in _wrapValue
File "/opt/zope/prd/www.domain.com/Products/Archetypes/Field.py", line 1168, in _make_file
File "/opt/zope/prd/www.domain.com/Products/Archetypes/BaseUnit.py", line 34, in __init__
File "/opt/zope/prd/www.domain.com/Products/Archetypes/BaseUnit.py", line 56, in update
File "/opt/zope/prd/www.domain.com/Products/MimetypesRegistry/MimeTypesRegistry.py", line 370, in __call__
File "/opt/zope/prd/www.domain.com/Products/MimetypesRegistry/MimeTypesRegistry.py", line 319, in classify
File "/opt/zope/prd/www.domain.com/Products/MimetypesRegistry/mime_types/mtr_mimetypes.py", line 82, in classify
File "/usr/lib/python2.4/sre.py", line 134, in search
return _compile(pattern, flags).search(string)
TypeError: expected string or buffer

When i renamed the field 'update' on this class to 'myUpdate', it works fine. So clearly 'update' is a name to avoid for your fields. It would be interesting to keep a list somewhere of all unwise choices for field names.

Friday, August 15, 2008

How to install plone.app.form on Plone 2.5

This is probably not good enough for a how-to on plone.org, but worthwile recording anyway. I had a bit of a hard time installing plone.app.form on Plone 2.5. It's required by slideshowfolder 4.0, which has some nice extra features compared to 1.2.2.

Like the instructions in slideshowfolder 4.0 told me, i got plone.app.form for Plone 2.5 from http://svn.plone.org/svn/plone/plone.app.form/branches/plone-2.5. The instructions don't tell you to build an egg from this, like so: python2.4 setup.py bdist_egg The egg will go in the dist/ directory.

The best way to install this in your Zope instance (not in your system-wide python2.4) is to create a workingenv for your instance, as indicated in http://plone.org/documentation/how-to/zope-python-packages-using-easy_install. Then you can activate your workingenv, and use easy_install, except you don't easy_install plone.app.form, but you point easy_install to your created egg: easy_install-2.4 /path/to/dist/plone.app.form*.egg. It'll probably complain about a site.py already being there, just remove it and run easy_install again.

Finally, do modifications to $INSTANCE_HOME/etc/site.zcml, $INSTANCE_HOME/etc/site-packages/configure.zcml and $INSTANCE_HOME/etc/site-packages/overrides.zcml as indicated in the plone.app.form checkout's docs/INSTALL.txt.

Friday, August 8, 2008

Using /profiles/default/structure with custom folderish type: 'Could not adapt'

Using ArgoUML and ArchGenXML, I created a special type ImportedProjectsFolder to hold items of type ImportedProject. I did this by just giving it a stereotype of <<folder>> or <<large>>. Although this did give me the required folderish type, Generic Setup did not want to create the folder: on installing the product, it said:

('Could not adapt', , ) 
The solution was to not use a stereotype, but to use a generalisation from ATFolder (<<stub>>). This way, the ImportedProjectsFolder inherited the required interface implementation, and installation was succesful.

Tuesday, August 5, 2008

Why does Plone say "404 Not Found" when i call my tool's method?

Tool methods must have a docstring, at least if you want to call them through the web! You may have seen some docstrings in Plone that say "This method is called TTW, so it needs a docstring". Today I tried this method:

class MyTool(UniqueObject, SimpleItem, Persistent):
"""
"""
security.declarePublic('sayHello')
def sayHello(self):
"""Say something
"""
return 'Hello!'

which only started working when I remembered that line and added the docstring.

Friday, August 1, 2008

Adding a custom permission to a Plone 2.5 product

Figuring out how to register a new permission in a Zope product took me some time. Here's how i did it: In Extensions/Install.py, put this::

from Products.CMFCore.permissions import setDefaultRoles

def install(self, reinstall=False):
setDefaultRoles('MyProduct: MyPermission', ())

And that's all i have to say about that.

Edit: This is now a how-to on plone.org.

Monday, July 28, 2008

Installing Plone 2.5 on Ubuntu 8.04 Hardy

I created this script hoping someday it might save me a lot of time installing a development environment for Plone on Ubuntu. (The unified installer has its shortcomings for a development environment.)
I haven't tested if it runs all the way, I'll test that when i have to install a new box. In the meantime, i hope it will be of use as a kind of cookbook.
Update 2008/08/17: It's much easier if you use the Ubuntu python2.4 package, but you'll have to install python2.4-dev as well (in order to compile Zope)!
# install_plone.sh
# An installer for Zope and Plone on Ubuntu 8.04 Hardy

# Author: Kees Hink
# Date: 17 August 2008

# A cookbook list on how to install a Zope+Plone development
# environment on a vanilla Ubuntu 8.04 (Hardy Heron) system.

# The development environment consists of a Zope software
# environment in the user's homedir, with an Zope instance
# created in it. A separate folder for Zope/Plone products is
# also created in the user's homedir.
# It's easy to add new instances, make them run on different
# ports, symlink to various products (or versions of products)
# in the products directory, use different Data.fs files.
# You can also install additional Python modules in a single
# instance by using workingenv: see
# http://plone.org/documentation/how-to/zope-python-packages-using-easy_install
# I find this a suitable setup for development.

# Echo executed commands and shell input lines
set -x
set -v

# Some directories for downloading and installing.
# (You may want to remove these when you're done.)
DOWNLOAD_DIR=~/downloads
BUILDS_DIR=~/builddirs
# Basic Plone products (CMFPlone, etc.) go here. (This is also a
# good place to put other Zope/Plone products later on.)
MY_PRODUCTS_DIR=~/products
# Zope software (2.9.9 is currently the latest Zope, but 2.9.8
# is most used in Plone sites)
ZOPE_TARBALLDIR=http://www.zope.org/Products/Zope/2.9.9
ZOPE_TARBALL=Zope-2.9.9-final.tgz
ZOPE_WGET_ADDRESS=$ZOPE_TARBALLDIR/$ZOPE_TARBALL
ZOPE_BUILDDIR=$BUILDS_DIR/Zope-2.9.9-final
# Where do you want your Zope software to live?
SOFTWARE_HOME=~/zope-2.9.9
# Plone software
PLONE_TARBALL_DIR=https://launchpadlibrarian.net/10741323
PLONE_TARBALL=Plone-2.5.5.tar.gz
PLONE_VERSION=Plone-2.5.5
PLONE_WGET_ADDRESS=$PLONE_TARBALL_DIR/$PLONE_TARBALL
# What do you want your Zope instance to be called?
INSTANCE_HOME=$SOFTWARE_HOME/Plone
# Zope instance admin user username/password
ROOT_LOGIN='admin'
ROOT_PASSWORD='admin'

# Preliminaries
sudo apt-get install g++
mkdir $BUILDS_DIR $DOWNLOAD_DIR $MY_PRODUCTS_DIR

# Install Python 2.4
sudo apt-get install python2.4
sudo apt-get install python2.4-dev
sudo apt-get install python2.4-elementtree
# PIL and libxml2 come standard with python2.4.
# python-2.4-dev is needed for compiling Zope.

# You may want to verify that you can now start python2.4,
# and import the modules PIL, elementtree and libxml2.

# Get and install Zope 2.9
wget $ZOPE_WGET_ADDRESS -P $DOWNLOAD_DIR
tar -xzf $DOWNLOAD_DIR/$ZOPE_TARBALL -C $BUILDS_DIR
cd $ZOPE_BUILDDIR
./configure --prefix=$SOFTWARE_HOME
make
make install
# Create a Zope instance
$SOFTWARE_HOME/bin/mkzopeinstance.py -d $INSTANCE_HOME -u $ROOT_LOGIN:$ROOT_PASSWORD

# You may now start your Zope instance by doing:
# $INSTANCE_HOME/bin/runzope
# You should now be able to connect to localhost:8080 and access
# the ZMI.

# Get and install Plone 2.5
wget $PLONE_WGET_ADDRESS -P $DOWNLOAD_DIR
# Extract Plone products to your own products folder
tar -xzf $DOWNLOAD_DIR/$PLONE_TARBALL -C $MY_PRODUCTS_DIR
# Turn your Zope instance into a Plone site by creating symlinks
# to Plone products
for i in $MY_PRODUCTS_DIR/$PLONE_VERSION/*;
do echo $i;
ln -s $i $INSTANCE_HOME/Products/;
done

# If you (re)start Zope, you should now be able to add a Plone
# site from the ZMI.

Friday, June 27, 2008

How to search in file fields in custom content-types in Plone 2.5

If you have a file upload field in your custom content type, you have to mark it as implementing IIndexableContent and provide a method indexableContent. For my custom type, which has a file field called "file", the method looks like this:

def indexableContent(self, fields):
if 'SearchableText' in fields:
file = self.getFile()
if file:
# file is a file object
mimetype = file.getContentType()
icc = ICC()
icc.addBinary('SearchableText',
str(file),
mimetype,
'iso-8859-15',
None)
return icc
return None

Tuesday, June 24, 2008

How to search in files (doc, pdf, etc.) in Plone 2.5

Plone 3 will index uploaded files out of the box, even Word docs. In Plone 2.5, you have to go to some extra trouble if you want uploaded files to be searched (indexed).

The key here is the TextIndexNG product. Here are the steps to take (from doc/README):

Installation on Plone:
- follow the steps above
- uncommented all directives in TextIndex3/adapters/configure.zcml (by
removing the HTML comments ''
- go to "Plone setup" -> "Add/remove programs"
- choose TextIndexNG3 to be added as new product
- a new configlet for TextIndexNG3 will appear on the setup screen (left
side)
- click on the configlet and choose the only option to replace the
existing index setup with TextIndexNG3 indexes

edit 2008/09/23:
It's worth noting that you may replace the first two steps by:

- (possibly, first virtualenv your zope instance)
- easy_install "Products.TextIndexNG3<3.2"

About converters: on Ubuntu you might use wvWare (apt-get install wv) for MS-Word and xpdf for PDF. See http://www.zopyx.de/projects/TextIndexNG3/documentation/external-converters.

Friday, June 20, 2008

Plone: Listing all available permissions in the site

For a future PAS plugin, i wanted to create a (multi)select field on a content type that takes the available site permissions as a vocbulary. It took me a while to figure out how to get a list of all available permissions, so i'll put it here:


from Products.CMFCore.utils import getToolByName

portal_url = getToolByName(context, "portal_url")
portal = portal_url.getPortalObject()

all_permissions = []
# context has a method valid_roles
for role in portal.valid_roles():
# role is a string of 'Contributor', 'Reader', etc.
permissions = portal.permissionsOfRole(role)
# permissions is a list of dicts
for permission in permissions:
# permission is a dict with keys 'selected' and 'name'
if permission['name'] not in all_permissions:
all_permissions.append(permission['name'])
all_permissions.sort()
return all_permissions

Thursday, June 12, 2008

Tricks for massive file manipulation

(modified 2010-01-26)

# List all .pyc files (or directories) in current directory and below:
find . -name '*.pyc'

# Remove all .pyc files (or directories) from current directory and below:
find . -name '*.pyc' -exec mv {} ~/.trash \;

# Remove all files from build directory and below, and also remove them from cvs:
find . -type f -wholename './build/*' -exec rm {} \; -exec cvs remove {} \;

# Find all .py.metadata files (or directories) in or below /path/to/directory, print the lines containing 'proxy', also list the filename (-H)
find /path/to/directory -name '*.py.metadata' -exec grep -H proxy {} \;

# Find all files or directories in current directory and print only their names:
find . -regex './[^/]*' -printf "%p"

# Find all files older than 30 days (by modification date) and print the modification date and name:
find . -type f -mtime +30 -printf '%t\t%p\n'

# Find directories in current folder not modified in the last 30 days, print modification date and name, and delete:
find . -regex './[^/]*' -type d -mtime +30 -printf '%t\t%p\n' -delete

# Find
# - files and directories in current folder:
# - not accessed in last month (if it's a folder)
# and do this:
# - delete it
# - (and possible subfolders)
find . -maxdepth 1 -name "*" -atime +31 -print0 | xargs -0 rm -rf

# Replace all occurrences of 'foo' by 'bar' in all files below this level
find . -name "*" -type f | xargs perl -pi -e 's/foo/bar/g'

# Rename files: replace 'foo' in filename with 'bar'.
# This may give errors if 'foo' occurs in more than one place on the path,
# but you can simply run it more than once.
find . -name "*foo*" | xargs rename 's/(.*)foo(.*)/$1bar$2/'

Thursday, May 29, 2008

Remember: overriding Fullname by computed field

I created a new member class using remember. I wanted to override the fullname field, computing the fullname for a member from their first name and surname.

I did this before for Plone 2.5 (hand-coded then, using ArgoUML now) and it was fairly easy: create a new member type based on remember, insert a computedfield "fullname" with accessor "Fullname" and that was that. On Plone 3.1.1 it was slightly harder.

First, I noticed i had to change "accessor=getFullname" instead of "accessor=Fullname", because if i didn't my new member wouldn't even get a join_form but an attribute error for getFullname.

Second, when i registered a member of my new member type i got an "AttributeError: setFullname". It turns out that this method is called in remember.content.member, in register:
self.setFullname(str(unicode_name))

(Funny thing is, i did this same thing on Plone 2.5 (remember 1.0b1, membrane 1.0, Archetypes 1.4.6 final) and it worked. The self.setFullname in remember was also present in this version.)

My solution was to add a dummy method setFullname(self, name) to the member class. ArchGenXML didn't create the parameters I specified in the UML, so i inserted them manually and they are preserved each generator run.

Thursday, April 17, 2008

How to make members accept terms of use

As done on Plone 2.5:
On your member content type, which should be a remember sterotype, add this field.
The validator does all the work.
You have will have to import ExpressionValidator.

atapi.BooleanField(
name='acceptTerms',
required=1,
regfield=1,
validators = ( ExpressionValidator('python: (value == 1) or (value ==\'1\') or (value == True)', 'You must accept the terms in order to become a member.'), ),
widget=atapi.BooleanWidget(
label="Accept terms of use",
description="By clicking this box, you acknowledge that you have read and understood the terms of use, and will use your account in compliance with these terms.",

Monday, March 31, 2008

How to create default content in Plone's Member Area.

For a portal we're developing, I wanted each member to get certain content put in their Member Folder by default.

For this reason, I created a special content type called MemberFolder which generalizes from ATFolder. However, a at_post_create_script on this doesn't work when invoked by Plone's "member add mechanism" (only when invoked directly, for example via the "add" menu.

It turns out that Plone's membership tool calls a method called notifyMemberAreaCreated on the newly created member folder. So all you have to do is add this method to your portal's default Member Folder type, and put your object creation stuff in it.

edit 22-05-2008:
It's even easier, because by acquisition the script doesn't have to be on the content type itself. Thanks Wiggert Akkerman for this hint.
See the complete how-to on plone.org

Wednesday, February 27, 2008

Obbema: Ja wy kinne

Een plaatje van een artikel uit een krant dat de achtergrond van Obama toelicht.

Tuesday, February 5, 2008

Plone redirect to home folder after login

One way, but possibly not best way, to automatically redirect a user to their Member area, directly after login, is to customize login_success and put

<tal:block define="redirect python: here.portal_membership.getHomeUrl();
foo python: request.RESPONSE.redirect(redirect)" />

directly after <div tal:condition="not:isAnon">.

edit 22-05-2008:
A better way is to modify login_next.cpy to include this line:

# modified: set came_from to member's profile page
came_from = membership_tool.getAuthenticatedMember().absolute_url()

Sunday, February 3, 2008

Untis op linux

Een beetje mosterd na de maaltijd voor een roostermaker in ruste, maar GP-Untis blijkt nu ook prima te draaien op linux (onder Wine). Nu heb ik helemaal geen enkele reden meer om ooit Windows te gebruiken.

Wednesday, January 30, 2008

iPod beheren vanaf Linux

Vanochtend muziek op Hannekes nieuwe iPod geïnstalleerd. Is vanaf Ubuntu lastiger dan via Mac of Windows, maar het lukt!