<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-3058608860238047362</id><updated>2011-12-14T08:31:28.057-08:00</updated><category term='linux'/><category term='xml'/><category term='mysql'/><category term='ajax'/><category term='Archetypes'/><category term='development'/><category term='migration'/><category term='buildout'/><category term='utf-8'/><category term='web framework'/><category term='eggs'/><category term='mysqlda'/><category term='software development'/><category term='plone'/><category term='python'/><category term='cms'/><category term='easy_install'/><category term='rails'/><category term='zope'/><category term='email'/><category term='uml'/><category term='standards'/><category term='unicode'/><category term='ubuntu'/><category term='csv'/><category term='deliverance'/><category term='json'/><category term='rant'/><title type='text'>mxm - IT's Mad Science</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://mxm-mad-science.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://mxm-mad-science.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Max M</name><uri>http://www.blogger.com/profile/01654969463596586272</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://bp0.blogger.com/_vdQTPcBkW6M/R5-HEb6ishI/AAAAAAAAAAM/rCEOHhgIjDI/S220/maxm.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>24</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-3058608860238047362.post-3475782316268233677</id><published>2011-11-08T06:53:00.000-08:00</published><updated>2011-11-08T06:58:29.873-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='migration'/><category scheme='http://www.blogger.com/atom/ns#' term='plone'/><title type='text'>How to export all redirects from portal_redirection in an older Plone site</title><content type='html'>Just add the method below to the RedirectionTool and call it from the browser as:&lt;br /&gt;&lt;br /&gt;http://localhost:8080/site/portal_redirection/getAllRedirects&lt;br /&gt;&lt;br /&gt;Assuming that the site is running at loaclhost:8080 that is :-S&lt;br /&gt;&lt;br /&gt;That will show a list of redirects that can be imported into plone 4.x&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;   security.declareProtected(View, 'getAllRedirects')&lt;br /&gt;   def getAllRedirects(self):&lt;br /&gt;       "get'm'all"&lt;br /&gt;       result = []&lt;br /&gt;       reference_tool = getToolByName(self, 'reference_catalog')&lt;br /&gt;       for k,uuid in self._redirectionmap.items():&lt;br /&gt;           obj = reference_tool.lookupObject(uuid)&lt;br /&gt;           if obj is None:&lt;br /&gt;               print 'could not find redirect from: %s to %s' % (k, uuid)&lt;br /&gt;           else:&lt;br /&gt;               path = '/'.join(('',)+obj.getPhysicalPath()[2:])&lt;br /&gt;               result.append( '%s,%s' % (k,path) )&lt;br /&gt;       return '\n'.join(result)&lt;br /&gt;&lt;/code&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3058608860238047362-3475782316268233677?l=mxm-mad-science.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mxm-mad-science.blogspot.com/feeds/3475782316268233677/comments/default' title='Kommentarer til indlægget'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3058608860238047362&amp;postID=3475782316268233677' title='1 kommentarer'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/3475782316268233677'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/3475782316268233677'/><link rel='alternate' type='text/html' href='http://mxm-mad-science.blogspot.com/2011/11/how-to-export-all-redirects-from.html' title='How to export all redirects from portal_redirection in an older Plone site'/><author><name>Max M</name><uri>http://www.blogger.com/profile/01654969463596586272</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://bp0.blogger.com/_vdQTPcBkW6M/R5-HEb6ishI/AAAAAAAAAAM/rCEOHhgIjDI/S220/maxm.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3058608860238047362.post-3035203081648759792</id><published>2009-11-06T05:29:00.000-08:00</published><updated>2009-11-06T05:36:19.954-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='software development'/><category scheme='http://www.blogger.com/atom/ns#' term='plone'/><category scheme='http://www.blogger.com/atom/ns#' term='zope'/><title type='text'>Another micro hack</title><content type='html'>I often write a pagetemplate that needs to be published later. But page templates has no workflow state.&lt;br /&gt;&lt;br /&gt;The problem is when the customer needs to test the page on the live page, but others must not be able to see it yet.&lt;br /&gt;&lt;br /&gt;I have a small hack to ensure that only managers can see the page.&lt;br /&gt;&lt;br /&gt;I call a low overhead method that is protected by the "Manage portal" permission. Like:&lt;br /&gt;&lt;br /&gt;&amp;lt;tal:managerCheck tal:define="managerCheck python: here.portal_migration.getInstanceVersion()" /&amp;gt;&lt;br /&gt;&lt;br /&gt;When the site is to be published I just remove the line.&lt;br /&gt;&lt;br /&gt;Well I told you it was a micro hack!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3058608860238047362-3035203081648759792?l=mxm-mad-science.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mxm-mad-science.blogspot.com/feeds/3035203081648759792/comments/default' title='Kommentarer til indlægget'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3058608860238047362&amp;postID=3035203081648759792' title='5 kommentarer'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/3035203081648759792'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/3035203081648759792'/><link rel='alternate' type='text/html' href='http://mxm-mad-science.blogspot.com/2009/11/another-micro-hack.html' title='Another micro hack'/><author><name>Max M</name><uri>http://www.blogger.com/profile/01654969463596586272</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://bp0.blogger.com/_vdQTPcBkW6M/R5-HEb6ishI/AAAAAAAAAAM/rCEOHhgIjDI/S220/maxm.jpg'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3058608860238047362.post-8057940947871544098</id><published>2009-01-26T08:19:00.000-08:00</published><updated>2009-01-26T08:23:54.614-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='software development'/><category scheme='http://www.blogger.com/atom/ns#' term='linux'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><title type='text'>School of Small Hacks</title><content type='html'>Being a web developer it bothers me that I cannot have seperate sessions open in Firefox. You can do it in IE. If you open a new browser you get a new session.&lt;br /&gt;&lt;br /&gt;So I (mis)use the hosts file to do something similar in Firefox.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;127.0.0.1 localhost&lt;br /&gt;127.0.0.1 localtest # insert this.&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Then I can go to http://localhost/ in one tab and http://localtest/ in another. That way the browser sees it as different session.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3058608860238047362-8057940947871544098?l=mxm-mad-science.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mxm-mad-science.blogspot.com/feeds/8057940947871544098/comments/default' title='Kommentarer til indlægget'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3058608860238047362&amp;postID=8057940947871544098' title='1 kommentarer'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/8057940947871544098'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/8057940947871544098'/><link rel='alternate' type='text/html' href='http://mxm-mad-science.blogspot.com/2009/01/school-of-small-hacks.html' title='School of Small Hacks'/><author><name>Max M</name><uri>http://www.blogger.com/profile/01654969463596586272</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://bp0.blogger.com/_vdQTPcBkW6M/R5-HEb6ishI/AAAAAAAAAAM/rCEOHhgIjDI/S220/maxm.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3058608860238047362.post-3743461604285665892</id><published>2009-01-06T12:06:00.000-08:00</published><updated>2009-01-06T12:27:29.570-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='plone'/><category scheme='http://www.blogger.com/atom/ns#' term='zope'/><title type='text'>A small trick for socket timouts in Plone</title><content type='html'>A few times I have had the need for a socket to timout in Plone. Eg when calling an external site that might be down.&lt;br /&gt;&lt;br /&gt;You can set the timeout in Plone, but that is not a good idea as you sometime need long running processes to end properly, eg for packing etc.&lt;br /&gt;&lt;br /&gt;A workaround is spwaning seperate threads and callback routines. But that is a bother to set up.&lt;br /&gt;&lt;br /&gt;Suddenly it stroke me today that it would be possible to change the timout temporarily for a single thread:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;import socket&lt;br /&gt;import feedparser&lt;br /&gt;&lt;br /&gt;def parseUrl(self, url):&lt;br /&gt;    "Fetch the url"&lt;br /&gt;    orig_timout = socket.getdefaulttimeout() # get original timeout&lt;br /&gt;    # give it a 20 second timout&lt;br /&gt;    socket.setdefaulttimeout(20)             # set to 20 seconds&lt;br /&gt;    try:&lt;br /&gt;        result = feedparser.parse(url)['entries']&lt;br /&gt;    except:&lt;br /&gt;        result = []&lt;br /&gt;    socket.setdefaulttimeout(orig_timout)    # set back to original&lt;br /&gt;    endresult = []&lt;br /&gt;    for r in result:&lt;br /&gt;        r2 = {}&lt;br /&gt;        r2.update(r)&lt;br /&gt;        endresult.append(r2)&lt;br /&gt;    # sort for latest first&lt;br /&gt;    decorated = [(r['updated_parsed'], r) for r in endresult]&lt;br /&gt;    decorated.sort()&lt;br /&gt;    decorated.reverse()&lt;br /&gt;    sorted = [d[-1] for d in decorated] &lt;br /&gt;    return sorted&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Sweet and simple ...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3058608860238047362-3743461604285665892?l=mxm-mad-science.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mxm-mad-science.blogspot.com/feeds/3743461604285665892/comments/default' title='Kommentarer til indlægget'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3058608860238047362&amp;postID=3743461604285665892' title='4 kommentarer'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/3743461604285665892'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/3743461604285665892'/><link rel='alternate' type='text/html' href='http://mxm-mad-science.blogspot.com/2009/01/small-trick-for-socket-timouts-in-plone.html' title='A small trick for socket timouts in Plone'/><author><name>Max M</name><uri>http://www.blogger.com/profile/01654969463596586272</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://bp0.blogger.com/_vdQTPcBkW6M/R5-HEb6ishI/AAAAAAAAAAM/rCEOHhgIjDI/S220/maxm.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3058608860238047362.post-4880637621758980768</id><published>2008-11-28T14:46:00.000-08:00</published><updated>2008-11-28T14:48:33.102-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ajax'/><title type='text'>Put on the Pyjamas and code</title><content type='html'>Javascript is a nice language, but Python is nicer. So how about writing in Python and having it automatically translated to Javascript. I will have to check that out.&lt;br /&gt;&lt;br /&gt;http://pyjs.org/&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3058608860238047362-4880637621758980768?l=mxm-mad-science.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mxm-mad-science.blogspot.com/feeds/4880637621758980768/comments/default' title='Kommentarer til indlægget'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3058608860238047362&amp;postID=4880637621758980768' title='0 kommentarer'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/4880637621758980768'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/4880637621758980768'/><link rel='alternate' type='text/html' href='http://mxm-mad-science.blogspot.com/2008/11/put-on-pyjamas-and-code.html' title='Put on the Pyjamas and code'/><author><name>Max M</name><uri>http://www.blogger.com/profile/01654969463596586272</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://bp0.blogger.com/_vdQTPcBkW6M/R5-HEb6ishI/AAAAAAAAAAM/rCEOHhgIjDI/S220/maxm.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3058608860238047362.post-6574251862093452881</id><published>2008-09-02T01:44:00.000-07:00</published><updated>2008-09-02T01:46:17.796-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='buildout'/><category scheme='http://www.blogger.com/atom/ns#' term='deliverance'/><category scheme='http://www.blogger.com/atom/ns#' term='web framework'/><category scheme='http://www.blogger.com/atom/ns#' term='plone'/><title type='text'>Installing Deliverance</title><content type='html'>Deliverance is a cool tool, that makes it possible to give web based software a new skin.&lt;br /&gt;Without changing the original software.&lt;br /&gt;&lt;br /&gt;It opens a lot of new options to development, migration and integration.&lt;br /&gt;&lt;br /&gt;You can have site running that uses Plone, plain html, mailman, blogger etc. And still looks like a single coherent site.&lt;br /&gt;&lt;br /&gt;I had few problems installing it using the manual method, so this is the code from how I actually got it to work.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;easy_install virtualenv&lt;br /&gt;virtualenv deliverance_env&lt;br /&gt;cd deliverance_env/&lt;br /&gt;source bin/activate&lt;br /&gt;svn co http://codespeak.net/svn/z3/deliverance/buildout/trunk .&lt;br /&gt;python bootstrap/bootstrap.py&lt;br /&gt;./bin/buildout&lt;br /&gt;./bin/deliverance&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;It started up fine, but the first page I tried to get threw an exception:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;Error - exceptions.ImportError: No module named HTML4&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Turned out that the default generated deliverance-proxy.ini had a bug:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;serializer = deliverance.serializers.HTML4&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Changing it to:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;serializer = deliverance.serializers:HTML4&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;works&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3058608860238047362-6574251862093452881?l=mxm-mad-science.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mxm-mad-science.blogspot.com/feeds/6574251862093452881/comments/default' title='Kommentarer til indlægget'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3058608860238047362&amp;postID=6574251862093452881' title='0 kommentarer'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/6574251862093452881'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/6574251862093452881'/><link rel='alternate' type='text/html' href='http://mxm-mad-science.blogspot.com/2008/09/installing-deliverance.html' title='Installing Deliverance'/><author><name>Max M</name><uri>http://www.blogger.com/profile/01654969463596586272</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://bp0.blogger.com/_vdQTPcBkW6M/R5-HEb6ishI/AAAAAAAAAAM/rCEOHhgIjDI/S220/maxm.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3058608860238047362.post-6784154226981421582</id><published>2008-06-04T12:18:00.000-07:00</published><updated>2008-06-04T12:22:54.103-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='migration'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='json'/><category scheme='http://www.blogger.com/atom/ns#' term='plone'/><category scheme='http://www.blogger.com/atom/ns#' term='zope'/><title type='text'>json_migrator Alpha release.</title><content type='html'>The code for my Plone json migrator has been "sort of functional" for a while now. I will probably not change it before the summer vacation, but that is no reason not to let other look at it.&lt;br /&gt;&lt;br /&gt;It is only available in the Collective.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://svn.plone.org/svn/collective/json_migrator/trunk/"&gt;http://svn.plone.org/svn/collective/json_migrator/trunk/&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;It was successfully used to migrate a 2.0.x site to 3.1.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3058608860238047362-6784154226981421582?l=mxm-mad-science.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mxm-mad-science.blogspot.com/feeds/6784154226981421582/comments/default' title='Kommentarer til indlægget'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3058608860238047362&amp;postID=6784154226981421582' title='3 kommentarer'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/6784154226981421582'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/6784154226981421582'/><link rel='alternate' type='text/html' href='http://mxm-mad-science.blogspot.com/2008/06/jsonmigrator-alpha-release.html' title='json_migrator Alpha release.'/><author><name>Max M</name><uri>http://www.blogger.com/profile/01654969463596586272</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://bp0.blogger.com/_vdQTPcBkW6M/R5-HEb6ishI/AAAAAAAAAAM/rCEOHhgIjDI/S220/maxm.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3058608860238047362.post-7665917143272168981</id><published>2008-05-23T16:11:00.000-07:00</published><updated>2008-05-23T16:16:08.465-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='csv'/><category scheme='http://www.blogger.com/atom/ns#' term='migration'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='plone'/><title type='text'>Export of member data from Plone as a CSV file</title><content type='html'>I needed to export member data from Plone for mail merging etc. There was nothing really suitable available so I wrote this small product for it.&lt;br /&gt;&lt;br /&gt;The manual is:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;Install "portal_memberdata_export" with the quickinstaller.&lt;br /&gt;&lt;br /&gt;Export by calling the memberdataExportCSV script, like http://www.example.com/memberdataExportCSV&lt;br /&gt;&lt;br /&gt;You need to have the "Manage Portal" permission.&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;So far it is only available from the collective.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://svn.plone.org/svn/collective/memberdata_export/trunk/"&gt;http://svn.plone.org/svn/collective/memberdata_export/trunk/&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3058608860238047362-7665917143272168981?l=mxm-mad-science.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mxm-mad-science.blogspot.com/feeds/7665917143272168981/comments/default' title='Kommentarer til indlægget'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3058608860238047362&amp;postID=7665917143272168981' title='0 kommentarer'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/7665917143272168981'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/7665917143272168981'/><link rel='alternate' type='text/html' href='http://mxm-mad-science.blogspot.com/2008/05/export-of-member-data-from-plone-as-csv.html' title='Export of member data from Plone as a CSV file'/><author><name>Max M</name><uri>http://www.blogger.com/profile/01654969463596586272</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://bp0.blogger.com/_vdQTPcBkW6M/R5-HEb6ishI/AAAAAAAAAAM/rCEOHhgIjDI/S220/maxm.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3058608860238047362.post-7363416221901937087</id><published>2008-05-23T14:09:00.000-07:00</published><updated>2008-05-23T14:12:41.705-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='csv'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><title type='text'>Note to self. Practical csv import module</title><content type='html'>It seems that I have a bunch of data import projects recently. I have made this simple module for converting a csv file to a list of dicts with unicode keys and values. So I just write a note to future self to remember to use it.&lt;br /&gt;&lt;br /&gt;Max, it is here: &lt;a href="http://www.mxm.dk/papers/csv-import"&gt;http://www.mxm.dk/papers/csv-import&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3058608860238047362-7363416221901937087?l=mxm-mad-science.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mxm-mad-science.blogspot.com/feeds/7363416221901937087/comments/default' title='Kommentarer til indlægget'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3058608860238047362&amp;postID=7363416221901937087' title='2 kommentarer'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/7363416221901937087'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/7363416221901937087'/><link rel='alternate' type='text/html' href='http://mxm-mad-science.blogspot.com/2008/05/note-to-self-practical-csv-import.html' title='Note to self. Practical csv import module'/><author><name>Max M</name><uri>http://www.blogger.com/profile/01654969463596586272</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://bp0.blogger.com/_vdQTPcBkW6M/R5-HEb6ishI/AAAAAAAAAAM/rCEOHhgIjDI/S220/maxm.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3058608860238047362.post-8770762017148536856</id><published>2008-05-22T16:02:00.000-07:00</published><updated>2008-05-22T16:12:00.869-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='email'/><category scheme='http://www.blogger.com/atom/ns#' term='plone'/><category scheme='http://www.blogger.com/atom/ns#' term='zope'/><title type='text'>Tooting my mailhorn - MailHostMonkeyPatch</title><content type='html'>I have been writing some simple maillist software the last few days. A simple form that can send an email message to all members on a site. &lt;br /&gt;&lt;br /&gt;It made me remember my MailHostMonkeyPatch which patches the zope/plone mailhost so that emails are saved in a directory structure instead of being sent to innocent recipients.&lt;br /&gt;&lt;br /&gt;One use is that you don't need an smtp host to test your mail software.&lt;br /&gt;&lt;br /&gt;Another is that you can see the emails that users will actually be receiving. In this case it has string substitution of member data in each email. So it is *very* practical to be able to see the mails.&lt;br /&gt;&lt;br /&gt;If you are writing Plone software that uses the MailHost, then this is highly recommended for debugging.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.mxm.dk/products/public/mailhost-monky/"&gt;http://www.mxm.dk/products/public/mailhost-monky/&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;A word of warning though. It works on MailHost and SecureMailHost, but I have only tried it up to Plone 2.5. So on 3.0 it might not work.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3058608860238047362-8770762017148536856?l=mxm-mad-science.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mxm-mad-science.blogspot.com/feeds/8770762017148536856/comments/default' title='Kommentarer til indlægget'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3058608860238047362&amp;postID=8770762017148536856' title='0 kommentarer'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/8770762017148536856'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/8770762017148536856'/><link rel='alternate' type='text/html' href='http://mxm-mad-science.blogspot.com/2008/05/tooting-my-mailhorn-mailhostmonkeypatch.html' title='Tooting my mailhorn - MailHostMonkeyPatch'/><author><name>Max M</name><uri>http://www.blogger.com/profile/01654969463596586272</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://bp0.blogger.com/_vdQTPcBkW6M/R5-HEb6ishI/AAAAAAAAAAM/rCEOHhgIjDI/S220/maxm.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3058608860238047362.post-953857999527247174</id><published>2008-05-05T01:24:00.000-07:00</published><updated>2008-05-05T07:55:19.771-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ubuntu'/><title type='text'>Upgraded my Gutsy Gibbon notebook to Hardy Heron</title><content type='html'>&lt;span style="font-weight:bold;"&gt;It was almost effortless. I had a few minor issues though.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;First it stalled during the upgrade with a package called "wvdial". I googled it, and it turned out to just be a modem dialer. I have not used a modem for 10 years, so I just did a "ps -A" to find it, and a "kill". Then the install continued without any more problems.&lt;br /&gt;&lt;br /&gt;After the install everything seemed to run fine, but I noticed something off with the video display. Windows where not scaling smoothly and video clips played where choppy and had lower framerates when they where resized or fullscreen.&lt;br /&gt;&lt;br /&gt;After another bit of googling about my ATI graphics adapter I found a tip to remove xserver-glx. I needed xserver-glx under Gibson to make that run smoothly. But apparently not under Heron. So now I have working video and a smooth desktop.&lt;br /&gt;&lt;br /&gt;My custom and buildout based Python/Plone stuff continued to work, which was my major worry.&lt;br /&gt;&lt;br /&gt;All in all a very pleasant experience for such a major upgrade.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;It turned out that I was a bit premature&lt;/span&gt;:&lt;br /&gt;&lt;br /&gt;Java is broken in Firefox 3. I use it mostly for netbanking. But that is pretty important. So I just spend an hour reinstalling firefox 2 and then had to:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;cd /usr/lib/firefox/plugins&lt;br /&gt;sudo ln -s /usr/lib/jvm/java-6-sun-1.6.0.06/jre/plugin/i386/ns7/libjavaplugin_oji.so&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Then I could use my netbanking again.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Turned out that Eclipse broke too&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I had quickly started and stopped eclipse, so I thought it worked. But it turned out that it crashed every time it tried to "build workspace".&lt;br /&gt;&lt;br /&gt;I renamed the workspace in my home dir and created a new one with Eclipse. The new one did not crash. So I can manually add my projects and keep working. Worst part is that at lost my font settings etc.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3058608860238047362-953857999527247174?l=mxm-mad-science.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mxm-mad-science.blogspot.com/feeds/953857999527247174/comments/default' title='Kommentarer til indlægget'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3058608860238047362&amp;postID=953857999527247174' title='0 kommentarer'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/953857999527247174'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/953857999527247174'/><link rel='alternate' type='text/html' href='http://mxm-mad-science.blogspot.com/2008/05/upgraded-my-gutsy-gibbon-notebook-to.html' title='Upgraded my Gutsy Gibbon notebook to Hardy Heron'/><author><name>Max M</name><uri>http://www.blogger.com/profile/01654969463596586272</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://bp0.blogger.com/_vdQTPcBkW6M/R5-HEb6ishI/AAAAAAAAAAM/rCEOHhgIjDI/S220/maxm.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3058608860238047362.post-5480776128163763063</id><published>2008-05-02T04:52:00.000-07:00</published><updated>2008-05-02T05:03:25.954-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='rant'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='plone'/><category scheme='http://www.blogger.com/atom/ns#' term='zope'/><title type='text'>This article about the Rails comunity makes me happy to be a Zopista ..</title><content type='html'>I have never ever experienced anything like this in the Zope, Plone, Python comunity.&lt;br /&gt;&lt;br /&gt;http://www.zedshaw.com/rants/rails_is_a_ghetto.html&lt;br /&gt;&lt;br /&gt;"After Mongrel I couldn’t get a gang of monkeys to rape me, so forget any jobs. Sure people would contact me for their tiny little start-ups, but I’d eventually catch on that they just want to use me to implement their ideas. Their ideas were horrendously lame. I swear if someone says they’re starting a social network I’m gonna beat them with the heel of my shoe."&lt;br /&gt;&lt;br /&gt;This is the first time it has occured to me that the relative difficulty of getting in to Zope/Plone actually makes for a higher quality of the comunity.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3058608860238047362-5480776128163763063?l=mxm-mad-science.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mxm-mad-science.blogspot.com/feeds/5480776128163763063/comments/default' title='Kommentarer til indlægget'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3058608860238047362&amp;postID=5480776128163763063' title='1 kommentarer'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/5480776128163763063'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/5480776128163763063'/><link rel='alternate' type='text/html' href='http://mxm-mad-science.blogspot.com/2008/05/this-article-about-rails-comunity-makes.html' title='This article about the Rails comunity makes me happy to be a Zopista ..'/><author><name>Max M</name><uri>http://www.blogger.com/profile/01654969463596586272</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://bp0.blogger.com/_vdQTPcBkW6M/R5-HEb6ishI/AAAAAAAAAAM/rCEOHhgIjDI/S220/maxm.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3058608860238047362.post-2944302494329020426</id><published>2008-04-14T03:24:00.000-07:00</published><updated>2008-04-14T03:36:35.618-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='migration'/><category scheme='http://www.blogger.com/atom/ns#' term='standards'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='json'/><category scheme='http://www.blogger.com/atom/ns#' term='plone'/><category scheme='http://www.blogger.com/atom/ns#' term='zope'/><title type='text'>We Got Dublin Core MetaData But What About "The Rest Of The Core" DataData?</title><content type='html'>&lt;b&gt;Dublin core metadata is very useful. But there is a lot of other attributes that could be defined as well. Why don't we make an interface for standard Zope attributes?&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;I have just finished the migration a medium sized site. about 8000 content objects of which 4000 ended up on the migrated site.&lt;br /&gt;&lt;br /&gt;I used the Json migrator that I have described in previous postings.&lt;br /&gt;&lt;br /&gt;The old site consisted of a mixture of CMF and AT based types. There was several custom AT products. The new site was Plone 3 based and consists of standard Plone sites and some Custom products.&lt;br /&gt;&lt;br /&gt;Some of the old custom content was migrated to standard Plone types as Plone has been catching up in functionality, and some was migrated to new content types.&lt;br /&gt;&lt;br /&gt;All of these content types used different attribute names for basically the same attributes.&lt;br /&gt;&lt;br /&gt;I tried partly to develop a reusable migration tool and, partly to finish a concrete project.&lt;br /&gt;&lt;br /&gt;One of the ugly things I did to get finished faster was to use the getter's of the old site as keys in the json dicts.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;{'getText': obj.getText()}&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;This results in a typical json file looking like this:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;{&lt;br /&gt;    "Publisher": "No publisher", &lt;br /&gt;    "CreationDate": "2007-11-04 03:39:56", &lt;br /&gt;    "-*-*-*-datafields-*-*-*-": {&lt;br /&gt;        "text": {&lt;br /&gt;            "mimetype": "text\/html", &lt;br /&gt;            "name": "text", &lt;br /&gt;            "fname": "document_3428.json.0.blahblah_2007-11-08.html"&lt;br /&gt;        }&lt;br /&gt;    }, &lt;br /&gt;    "Language": "da", &lt;br /&gt;    "Title": "Some Title", &lt;br /&gt;    "getPhysicalPath": [&lt;br /&gt;        "lokalt", &lt;br /&gt;        "somewehere", &lt;br /&gt;        "dokumenter", &lt;br /&gt;        "blahblah_2007-11-08"&lt;br /&gt;    ], &lt;br /&gt;    "portal_type": "Document", &lt;br /&gt;    "Creator": "jrh", &lt;br /&gt;    "site-encoding": "utf-8", &lt;br /&gt;    "getId": "blahblah_2007-11-08", &lt;br /&gt;    "ModificationDate": "2008-03-02 08:55:31", &lt;br /&gt;    "EffectiveDate": "2007-11-04 03:49:38", &lt;br /&gt;    "getOwner": "jrh", &lt;br /&gt;    "Format": "text\/html", &lt;br /&gt;    "meta_type": "Document", &lt;br /&gt;    "Date": "2007-11-04 03:49:38", &lt;br /&gt;    "review_state": "published", &lt;br /&gt;    "Identifier": "http:\/\/localhost:8081\/plone\/lokalt\/somewehere\/dokumenter\/blahblah_2007-11-08", &lt;br /&gt;    "Type": " Side"&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;This annoys me and made a long standing problem pop up once again. The afore mentioned lack of uniform naming of attributes in Zope/CMF/Plone/Zope3.&lt;br /&gt;&lt;br /&gt;We have had several defacto standards since the plain Zope days, from a mix of often used Zope, CMF and AT content types.&lt;br /&gt;&lt;br /&gt;The HTML body of a document has been named text, body, content, data etc. in different content types.&lt;br /&gt;&lt;br /&gt;There is no real reason that they could not have the same name. Except that no standard has been defined.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt; Zope standard attributes &lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;The next step in my json migrator project is to make it work from any Plone version to any Plone version. Well at least to make the framework so that it is possible.&lt;br /&gt;&lt;br /&gt;Making Python work from 2.3 to 2.4 was 4 lines of code (I use sets). So that is not the problem. Making it work on even older Python versions would be no biggie either.&lt;br /&gt;&lt;br /&gt;Currently it migrates fine from Plone 2.0.x to 3.0.x, but there are a lot of interim versions. and parts of it might break due to different attribute names.&lt;br /&gt;&lt;br /&gt;So what I need to move on is a meaningful standard for Zope attributes.&lt;br /&gt;&lt;br /&gt;I can then define an interface and write a wrapper for each content type in each version of Plone I want to migrate. Something like:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;class IZopeStandardAttributesData:&lt;br /&gt;&lt;br /&gt;    """&lt;br /&gt;    Interface for data attributes&lt;br /&gt;    """&lt;br /&gt;&lt;br /&gt;    def AttributeName():&lt;br /&gt;        "Data fields attribute name on the content type"&lt;br /&gt;&lt;br /&gt;    def Data():&lt;br /&gt;        "The raw data"&lt;br /&gt;&lt;br /&gt;    def ContentType():&lt;br /&gt;        "Like 'application/msoffice'"&lt;br /&gt;&lt;br /&gt;    def Filename():&lt;br /&gt;        "Like 'Some Text Document.doc'"&lt;br /&gt; &lt;br /&gt;&lt;br /&gt;class IZopeStandardAttributes:&lt;br /&gt;&lt;br /&gt;    """&lt;br /&gt;    Standard attributes name for Zope content types. Version 1.0&lt;br /&gt;    """&lt;br /&gt;&lt;br /&gt;    def HTML():&lt;br /&gt;        "Main content For document types. Raw HTML."&lt;br /&gt;&lt;br /&gt;    def HTMLSafe():&lt;br /&gt;        "Main content For document types. Safe html."&lt;br /&gt;&lt;br /&gt;    def StartDate():&lt;br /&gt;        "For calendaring event types"&lt;br /&gt;&lt;br /&gt;    def EndDate():&lt;br /&gt;        "For calendaring event types"&lt;br /&gt;&lt;br /&gt;    def DataFields():&lt;br /&gt;        "Sequence of data fields"&lt;br /&gt;&lt;br /&gt;    def Participants():&lt;br /&gt;        "Sequence of participant for groupware content types"&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Naturally there would need to be many more.&lt;br /&gt;&lt;br /&gt;I could then use wrappers to convert all content types to Json using these attributes names. Converting from Json data to Plone content objects would be just the same. Having standard attributes that all content types would be converted to and from is probably the only way to make this work in reality.&lt;br /&gt;&lt;br /&gt;I believe that this would both help in my current migrations story, and would also make Zope/Plone programming easier for newcomers to grasp. Everyone would not need to make up new attribute names for their own products.&lt;br /&gt;&lt;br /&gt;UML tools and the like could even support it so they would suggest these attributes in new types.&lt;br /&gt;&lt;br /&gt;The interface should be put in the collective or somewhere else that is suitable.&lt;br /&gt;&lt;br /&gt;Is this an idea worth pursuing as a community project, or should I just make it for my migrator and shut up?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3058608860238047362-2944302494329020426?l=mxm-mad-science.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mxm-mad-science.blogspot.com/feeds/2944302494329020426/comments/default' title='Kommentarer til indlægget'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3058608860238047362&amp;postID=2944302494329020426' title='9 kommentarer'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/2944302494329020426'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/2944302494329020426'/><link rel='alternate' type='text/html' href='http://mxm-mad-science.blogspot.com/2008/04/we-got-dublin-core-metadata-but-what.html' title='We Got Dublin Core MetaData But What About &quot;The Rest Of The Core&quot; DataData?'/><author><name>Max M</name><uri>http://www.blogger.com/profile/01654969463596586272</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://bp0.blogger.com/_vdQTPcBkW6M/R5-HEb6ishI/AAAAAAAAAAM/rCEOHhgIjDI/S220/maxm.jpg'/></author><thr:total>9</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3058608860238047362.post-6520376090020900167</id><published>2008-04-02T13:54:00.000-07:00</published><updated>2008-04-02T14:00:10.367-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='cms'/><category scheme='http://www.blogger.com/atom/ns#' term='web framework'/><category scheme='http://www.blogger.com/atom/ns#' term='xml'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='plone'/><category scheme='http://www.blogger.com/atom/ns#' term='zope'/><title type='text'>What Does A Web Framework Really Need?</title><content type='html'>&lt;b&gt;&lt;br /&gt;There has been some discussions recently about the role of Plone. Is it a framework or a platform. Should we make a new CMS with Z3/Grok. So I will try to take a fresh approach and look at an absolute minimal system, that would not need a new version for many years.&lt;br /&gt;&lt;br /&gt;That got me to think about what is really needed in a web framework to make a coherent user experience. Notice that I mean end-user experience. Not the programmers experience.&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt; Plone has modules for many things &lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;They are all coupled together in a tightly knit system. This is what makes Plone so usable out of the box. It is also the reason that Plone is so hard to learn.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&lt;b&gt;access control&lt;/b&gt;, authentication.&lt;br /&gt;&lt;b&gt;security&lt;/b&gt;, roles and permissions, Workflow.&lt;br /&gt;&lt;b&gt;data storage&lt;/b&gt;, ZODB, ZEO, SQLAdapters.&lt;br /&gt;&lt;b&gt;traversing&lt;/b&gt;, ZPublisher, VirtualHostMonster.&lt;br /&gt;&lt;b&gt;content types&lt;/b&gt;, OFS types (Old Zope), CMS Types, AT Types, Calendaring. Content Type Translations (html -&gt; txt etc.), Version Control.&lt;br /&gt;&lt;b&gt;form handling&lt;/b&gt;&lt;br /&gt;&lt;b&gt;css structure&lt;/b&gt;, resource registry.&lt;br /&gt;&lt;b&gt;Templating&lt;/b&gt;, zpt, dtml, python scripts, portal_skins.&lt;br /&gt;&lt;b&gt;Editing content&lt;/b&gt;, stx, rest, html, kupu, ajax.&lt;br /&gt;&lt;b&gt;Data Querying&lt;/b&gt;, ZCatalog, SQLAdapters.&lt;br /&gt;&lt;b&gt;Language support&lt;/b&gt;, PlacelessTranslationService, PloneLanguageTool.&lt;br /&gt;&lt;b&gt;Mail Handling&lt;/b&gt;&lt;br /&gt;&lt;b&gt;Product Installation&lt;/b&gt;&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;But if we are talking about a framework, most of these things are not necessary. you only need 3-4 technologies to make a stable and usable web framework. The most radical idea I have is a completely new way to think about the templating system.&lt;br /&gt;&lt;br /&gt;In the following a "page" is any unique url in the site. AT is Archetypes.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt; CSS and design guidelines &lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;If you are the user of a website, all you want is for the site to have a strong internal logic. The most important part of that is that the entire site should look the same.&lt;br /&gt;&lt;br /&gt;That makes a common css for a site the most important part of a web framework ... go figure.&lt;br /&gt;&lt;br /&gt;If all pages developed for a website shares the css, then you can work around the rest. Pages can be made in html, zope2, zope3, php etc. It will not matter as long as they follow the same design guidelines.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt; Templating &lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;The second most important feature for the user to get a coherent feel for a site, is a system that makes as many of the pages follow a common page structure as possible.&lt;br /&gt;&lt;br /&gt;In Plone there is the famous o-ring design. A Page Template called main_template that defines the look of the entire site. This model works well in most of the cases, but it hinders in many other.&lt;br /&gt;&lt;br /&gt;If you want to create a new content type in Plone using AT, the O-ring design and shared css is a *big* help.&lt;br /&gt;&lt;br /&gt;This was the main difference between plain old Zope 2 and Plone. You could add a product and it would look and feel the same as any other product.&lt;br /&gt;&lt;br /&gt;AT got the look and feel integration locked down even tighter, AT content types would share the edit forms machinery.&lt;br /&gt;&lt;br /&gt;But that was also where it started to get difficult to work in anything but AT. Because Plone and AT got so tightly integrated.&lt;br /&gt;&lt;br /&gt;The main problem with the shared templates being integrated into the system, is that it is hard to use non framework software. Mailman is a great mail list system. Integrating it into Plone? Good luck! There are a lot of BBS systems written in php or other inferior languages ;-) Integrating them into Plone? Good luck!&lt;br /&gt;&lt;br /&gt;Imagine if all that was required from the templating system was that the content would generate a well formed XML file with a defined DTD. Then the templating system would just convert that XML file into the actual file served by the http server. It would layer the o-ring, css etc. on top.&lt;br /&gt;&lt;br /&gt;Outside applications would just need to be rewritten to output its content as XML and Python developers could use their templating system of choice.&lt;br /&gt;&lt;br /&gt;I believe that the site theming / templating system currently is in the wrong layer. I guess it is the same thing that Paul Everit keeps talking about with his XML/XSLT stuff. This would be like WSGI in steroids. A kind of rendering layer between WSGI and the web apps.&lt;br /&gt;&lt;br /&gt;Another way to look at it would be as a proxy that fetches content from several urls and rerenders them to fit a common template and site structure.&lt;br /&gt;&lt;br /&gt;There could even be a visual tool for changing the layout, that all web apps could share.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt; Authentication &lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;The two first two features are for anonymous users. If you want your users to be able to log in to the site you must have a common form of authentication. Separate pages requiring separate logins is just not considered cool by users.&lt;br /&gt;&lt;br /&gt;Currently it is done in Plone. But it could just as easily be done by a separate authentication server that could run on another port or server:&lt;br /&gt;&lt;br /&gt;    http://localhost:8081/authenticate?user=admin&amp;email=some@example.com&amp;password=admin&lt;br /&gt;&lt;br /&gt;That would make it pretty easy to share authentication.&lt;br /&gt;&lt;br /&gt;Username and passwords could also be handed down from a traverser to each page, to automatically log on to 3rd part software.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt; Traverser &lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;I am not to sure about this one. I don't know much about how the ZPublisher works. It seems like a job for WSGI, or a configuration system to make sure that a certain branch of pages is handled by (proxied from) a specific app server.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt; Conclusion &lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;So that's it. I think you could make a very good web framework with just those few items. The rest of the things from the Plone stack does not really need to be in a core framework.&lt;br /&gt;&lt;br /&gt;That is especially clear with something like content types or templating where many solutions have been used. We cannot get rid of any of them as it will destroy backwards compatibility. Even refactoring will destroy backwards compatibility.&lt;br /&gt;&lt;br /&gt;If it was made as I described above, you could use Plone from some parts of a site and Zope 3 for other parts. A specific framework could be used for what it was especially good for. Like Plone for Content management and you could change a discussion board from PloneBoard to PHPBoard in a part of the site, while maintaining the look and feel.&lt;br /&gt;&lt;br /&gt;Comments? Would a minimal web app like that be useful? A sort of meta framework.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3058608860238047362-6520376090020900167?l=mxm-mad-science.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mxm-mad-science.blogspot.com/feeds/6520376090020900167/comments/default' title='Kommentarer til indlægget'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3058608860238047362&amp;postID=6520376090020900167' title='5 kommentarer'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/6520376090020900167'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/6520376090020900167'/><link rel='alternate' type='text/html' href='http://mxm-mad-science.blogspot.com/2008/04/what-does-web-framework-really-need.html' title='What Does A Web Framework Really Need?'/><author><name>Max M</name><uri>http://www.blogger.com/profile/01654969463596586272</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://bp0.blogger.com/_vdQTPcBkW6M/R5-HEb6ishI/AAAAAAAAAAM/rCEOHhgIjDI/S220/maxm.jpg'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3058608860238047362.post-8072702400675426425</id><published>2008-03-30T13:34:00.000-07:00</published><updated>2008-03-30T13:40:17.787-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='utf-8'/><category scheme='http://www.blogger.com/atom/ns#' term='unicode'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='plone'/><category scheme='http://www.blogger.com/atom/ns#' term='zope'/><title type='text'>Python Unicode lessons from the school of hard knocks</title><content type='html'>&lt;b&gt;&lt;br /&gt;Unicode is becomming increasingly popular, but is often misunderstood. That is a pitty, as it is really not that difficult. Especially since unicode is a really powerfull tool to know, and it becomes the standard string type in Python 3K.&lt;br /&gt;&lt;br /&gt;There are many unicode articles around, but they are often very "theoretical" and computer science like. So I have collected af few examples I have learned in the school of hard knocks, that are practically oriented.&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;I will use "unicode" as shorthand for a unicode string, and "string" as a shorthand for a plain Python string. I live in Europe and am used to using latin-1 as default encoding. Whenever I write latin-1 just substitute that with your own language default encoding. Should work just fine.&lt;br /&gt;&lt;br /&gt;I remember when I tried to understand unicode that I could not get my head around when to use encode and decode. In these examples I practically only go from unicode to string. So I use the encode() method for most examples. I believe this makes it easier to understand and remember.&lt;br /&gt;&lt;br /&gt;If there is popular demand I might write an article using only the decode() method.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;  Working with unicode in code &lt;/h2&gt; &lt;br /&gt;&lt;br /&gt;First examples from a python console:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&gt;&gt;&gt; 'this is a python string'&lt;br /&gt;'this is a python string'&lt;br /&gt;&lt;br /&gt;&gt;&gt;&gt; u'this is a python unicode string'&lt;br /&gt;u'this is a python unicode string'&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Not much difference there. That is because they both contain only ascii characters. When I try to insert a danish character it changes:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&gt;&gt;&gt; 'this is a pythøn string'&lt;br /&gt;'this is a pyth\xc3\xb8n string'&lt;br /&gt;&lt;br /&gt;&gt;&gt;&gt; u'this is a pythøn unicode string'&lt;br /&gt;u'this is a pyth\xf8n unicode string'&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;The string example shows something interresting as it shows the ø as '\xc3\xb8'. Whenever you see international characters showing up as two encoded characters/bytes like this, it is usually a sign that you are seeing a utf-8 encoded string.&lt;br /&gt;&lt;br /&gt;It is by no means the law, but it is a good rule of thumb.&lt;br /&gt;&lt;br /&gt;In this example the string is encoded to utf-8 because that is the default I use in the console window that runs the examples.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;  Thinking about unicode &lt;/h2&gt; &lt;br /&gt;&lt;br /&gt;A good way to think about the difference of unicode and string is as a text and a binary file. &lt;br /&gt;&lt;br /&gt;Unicode is the text and the string is the binary file format. So when you want to save your text somewhere you save it in a file format. Different file formats you can use are latin-1 (iso-8859-1), ascii, utf-8 etc.&lt;br /&gt;&lt;br /&gt;You convert to the correct file format by using the 'encode()' method. Like this:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&gt;&gt;&gt; u'this is a pythøn unicode string'.encode('latin-1')&lt;br /&gt;'this is a pyth\xf8n unicode string'&lt;br /&gt;&lt;br /&gt;&gt;&gt;&gt; u'this is a pythøn unicode string'.encode('utf-8')&lt;br /&gt;'this is a pyth\xc3\xb8n unicode string'&lt;br /&gt;&lt;br /&gt;&gt;&gt;&gt; u'this is a pythøn unicode string'.encode('ascii')&lt;br /&gt;Traceback (most recent call last):&lt;br /&gt;  File "&lt;stdin&gt;", line 1, in ?&lt;br /&gt;UnicodeEncodeError: 'ascii' codec can't encode character u'\xf8' in position 14: ordinal not in range(128)&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Whoops. ascii is an illegal file format for text with international characters. That is an important lesson.&lt;br /&gt;&lt;br /&gt;You can get from unicode to every other encoding without any other information about the unicode string. If the encoding supports the characters your unicode use. So unicode is the singular type from and to which all the others text formats can be converted.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt; unicode to anything &lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;String encodings are archeological. Well sometimes you even get the feeling that they are geological. Especially when writing something to support an RFC. Each geological layer is there for backwards compatibility.&lt;br /&gt;&lt;br /&gt;First there was ascii, then iso-8859-1 (latin-1), iso-8859-15 (adds the € sign and more) and finally utf-8. They are mostly supported in that order too. The older the encoding, the better support it has in software out there in the wild.&lt;br /&gt;&lt;br /&gt;Theoretically you can just use utf-8 for it all end be done with it. But I have had to rewrite almost any email/maillist application I have written to support latin-1 or other encodings. Many mail clients supports utf-8 correctly these days. But many web based clients do not. Neither hotmail nor gmail does actually.&lt;br /&gt;&lt;br /&gt;Most likely you will be writing software that works perfectly, and passes every test and mail client you and your customer have in your organisations. But when the brand new maillist goes live, customers complains about unreadable characters.&lt;br /&gt;&lt;br /&gt;So it can make good sense to try and encode unicode into a gradually more "modern" encoding. Trying the older encodings first, and if that fails then try newer ones. This function does that::&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;# -*- coding: utf-8 -*-&lt;br /&gt;&gt;&gt;&gt; def optimal_encode(st):&lt;br /&gt;&gt;&gt;&gt;     for encoding in ['ascii','iso-8859-1','iso-8859-15','utf-8']:&lt;br /&gt;&gt;&gt;&gt;         try:&lt;br /&gt;&gt;&gt;&gt;             return (encoding, st.encode(encoding))            &lt;br /&gt;&gt;&gt;&gt;         except UnicodeEncodeError:&lt;br /&gt;&gt;&gt;&gt;             pass&lt;br /&gt;&gt;&gt;&gt;     raise UnicodeError, 'Could not find encoding'&lt;br /&gt;&gt;&gt;&gt; &lt;br /&gt;&lt;br /&gt;&gt;&gt;&gt; st = u'this is a pythøn unicod€ string'&lt;br /&gt;&gt;&gt;&gt; print optimal_encode(st)&lt;br /&gt;&lt;br /&gt;('iso-8859-15', 'this is a pyth\xf8n unicod\xa4 string')&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;The € sign makes it return 'iso-8859-15'. If that encoding was not in the list, it would return::&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;('utf-8', 'this is a pyth\xc3\xb8n unicod\xe2\x82\xac string')&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt; Working with unicode in your editor &lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;Any modern text editor can handle utf-8 encoding. So preferably you should use that in your Python files. You tell Python that your files are utf-8 encoded by adding an encoding declaration to the top of you file.&lt;br /&gt;&lt;br /&gt;# -*- coding: utf-8 -*-&lt;br /&gt;&lt;br /&gt;When this is done, you can write international characters directly in your source code and every string in your file is utf-8 encoded. This makes it true that:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;u'pythøn'.encode('utf-8') == 'pythøn'&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt; Saving unicode in files &lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;Unicode only exists in memory. You cannot write it to a text file unless you write it as pickled data. But nobody else would then be able to read it, and you cannot look at it in an ordinary text editor.&lt;br /&gt;&lt;br /&gt;To put your data into a file, you must encode it first.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&gt;&gt;&gt; st&lt;br /&gt;u'this is a pyth\xf8n unicode string'&lt;br /&gt;&gt;&gt;&gt; f = open('unicodetest.txt', 'w')&lt;br /&gt;&gt;&gt;&gt; f.write(st)&lt;br /&gt;Traceback (most recent call last):&lt;br /&gt;  File "&lt;stdin&gt;", line 1, in ?&lt;br /&gt;UnicodeEncodeError: 'ascii' codec can't encode character u'\xf8' in position 14: ordinal not in range(128)&lt;br /&gt;&gt;&gt;&gt; st_utf8 = st.encode('utf-8')&lt;br /&gt;&gt;&gt;&gt; f.write(st_utf8)&lt;br /&gt;&gt;&gt;&gt; f.close()&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;When you try to write the unicode string to a file, it tries to convert it to a string and fails. But when you encode it first there is no problem.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;There is a special 2 byte code that can be inserted into the beginning of a text file to mark it as some kind of unicode encoded string. It is called a Byte Order Mark (BOM)&lt;br /&gt;&lt;br /&gt;There are a few of those for the different encodings, but I have only ever had to use utf-8&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&gt;&gt;&gt; import codecs&lt;br /&gt;&gt;&gt;&gt; codecs.BOM_UTF8&lt;br /&gt;'\xef\xbb\xbf'&lt;br /&gt;&gt;&gt;&gt; f.write(codecs.BOM_UTF8 + st_utf8)&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;The Byte Order Mark (BOM) is used especially on Windows. Frankly it is a bit of a mess in Python until 2.5. But many text editors can recognize it and then knows automatically that the file is utf-8 encoded.&lt;br /&gt;&lt;br /&gt;As far as I can figure it out, the Page Template skin system in Zope does recognize the BOM. So you can use international characters in Page Templates.&lt;br /&gt;&lt;br /&gt;At least I have tried editing files that where utf-8 encoded, but the international characters displayed wrong. They looked like: 'this is a pyth\xc3\xb8n string' So Zopes Page Template system got them wrong. Other times I have written them and they looked like 'this is a pythøn string'.&lt;br /&gt;&lt;br /&gt;I asume that is due to the difference in having a correct BOM or not that Zope can recognize.&lt;br /&gt;&lt;br /&gt;Generally though I work on many different Zope/OS system combos. So when I see these kind of problems in html, I generally take the lazy way out and just use html entities so it looks like: 'this is a pyth&amp;oslash;n string' :-s&lt;br /&gt;&lt;br /&gt;&lt;h2&gt; Unicode in html &lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;In html you can represent international characters as both html entities (eg &amp;oslash;) and as encoded strings. Normally you will use utf-8 for encoded strings.&lt;br /&gt;&lt;br /&gt;If you choose the encoded strings you must tell what the encoding is. You do this by setting it as meta data in the head:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&amp;lt;meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /&amp;gt;&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Cou can then use normal procedures for converting unicode html to utf-8 strings.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt; Encoding strings &lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;Unicode is not the only python class to have an encode() method. The string object also have one. That can be handy in many cases.&lt;br /&gt;&lt;br /&gt;If you need to use a url, or any other string with special characters, as a filename it will cause you problems. Any string can be encoded in a simpler format that can be used as a filename or even as part of a path.&lt;br /&gt;&lt;br /&gt;The hex encoding is the simplest, but base64 is the most space efficient.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&gt;&gt;&gt; 'http://mxm-mad-science.blogspot.com/'.encode('hex')&lt;br /&gt;'687474703a2f2f6d786d2d6d61642d736369656e63652e626c6f6773706f742e636f6d2f'&lt;br /&gt;&lt;br /&gt;&gt;&gt;&gt; 'http://mxm-mad-science.blogspot.com/'.encode('base64')&lt;br /&gt;'aHR0cDovL214bS1tYWQtc2NpZW5jZS5ibG9nc3BvdC5jb20v\n'&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;A caveat here is that base64 always have a newline character in the end, and it might have a '=' as padding. So you need to modify it a bit to remove those:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&gt;&gt;&gt; 'http://mxm-mad-science.blogspot.com/'.encode('base64')[:-1].strip('=')&lt;br /&gt;'aHR0cDovL214bS1tYWQtc2NpZW5jZS5ibG9nc3BvdC5jb20v'&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;And when you want to decode it again you must always append a padding character. Otherwise it will fail about 50% of the time ...:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&gt;&gt;&gt; ('aHR0cDovL214bS1tYWQtc2NpZW5jZS5ibG9nc3BvdC5jb20v' + '=').decode('base64')&lt;br /&gt;'http://mxm-mad-science.blogspot.com/'&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;I once made some mailinglist software in Zope, where The best solution was to save the subscribers with their emails as the id of the subscriber object. But Zope does not accept @ in a path. All I then had to do to make it work was:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&gt;&gt;&gt; id = 'maxm@mxm.dk'.encode('hex')&lt;br /&gt;&gt;&gt;&gt; id&lt;br /&gt;'6d61786d406d786d2e646b'&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;What have this got to do with unicode you might ask? Well you can use this method to use unicode string as filenames without needing to remove special characters.&lt;br /&gt;&lt;br /&gt;First make the unicode string, then convert it to utf-8 and then convert that to hex or base64.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&gt;&gt;&gt; st = u'this is a pythøn unicod€ string/text'&lt;br /&gt;&gt;&gt;&gt; st_utf8 = st.encode('utf-8')&lt;br /&gt;&gt;&gt;&gt; st_utf8&lt;br /&gt;'this is a pyth\xc3\xb8n unicod\xe2\x82\xac string/text'&lt;br /&gt;&gt;&gt;&gt; st_hex = st_utf8.encode('hex')&lt;br /&gt;&gt;&gt;&gt; st_hex&lt;br /&gt;'7468697320697320612070797468c3b86e20756e69636f64e282ac20737472696e672f74657874'&lt;br /&gt;&gt;&gt;&gt; st == st_hex.decode('hex').decode('utf-8')&lt;br /&gt;True&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt; Unicode in emails &lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;Once upon a time when Bill Gates invented email he thought "7 bits is more than enough for every character." Well ok. Maybe it was not Bill Gates. But someone must have thought it. As that is the foundation that email is built upon.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt; Body Text &lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;Actully, email messages can contain 8 bit characters. But SMTP can only transmit 7 bit messages. And as Dolly Parton once said: You cannot put 10 punds of potatoes into a five pound sack. So if your are sending your email over SMTP. like everyone is, you must convert your code to 7 bits. This is called content transfer encoding. &lt;br /&gt;&lt;br /&gt;First you make the body text in unicode. &lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&gt;&gt;&gt; st = u'this is a pythøn unicode string'&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Then you must convert it to the string encoding you want to send it as. Like latin-1, utf-8 etc.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&gt;&gt;&gt; st_latin1 = st.encode('iso-8859-1')&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Python recognise 'latin-1' as an encoding. Mail systems does not, so it is safer to use 'iso-8859-1'. Python will translate it automatically in the email module, but I have been bitten when composing emails wihtout it, so I have made it a habbit to always use the long form.&lt;br /&gt;&lt;br /&gt;Latin-1 is still an 8 bit string. So we must content transfer encode that to a 7 bit ascii string.&lt;br /&gt;&lt;br /&gt;A simple email message is made like this. Note that the set_payload() method does not do any encoding. You merely tell it what encoding your string is already in:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&gt;&gt;&gt; st = u'this is a pythøn unicode string'&lt;br /&gt;&gt;&gt;&gt; st_latin1 = st.encode('iso-8859-1')&lt;br /&gt;&gt;&gt;&gt; from email.Message import Message&lt;br /&gt;&gt;&gt;&gt; msg = Message()&lt;br /&gt;&gt;&gt;&gt; msg.set_payload(st_latin1, 'iso-8859-1')&lt;br /&gt;&gt;&gt;&gt; str(msg)&lt;br /&gt;'From nobody Sun Mar 30 15:13:31 2008\nMIME-Version: 1.0\nContent-Type: text/plain; charset="iso-8859-1"\nContent-Transfer-Encoding: quoted-printable\n\nthis is a pyth=F8n unicode string'&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;The email module can also encode as base64, but that is mostly used for 8 bit binary content as it makes the files smaller than quoted-printable would.&lt;br /&gt;&lt;br /&gt;You could use base64 for all email content types, but keeping text messages human readable is simply more practical.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt; Sending HTML &lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;Sending HTML works exactly like plain text from a unicode point of view. HTML files can also be made as unicode and then encoded as utf-8 or latin-1 etc. Just set the content type:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&gt;&gt;&gt; del msg['Content-Type']&lt;br /&gt;&gt;&gt;&gt; msg.add_header('Content-Type', 'text/html', charset='iso-8859-1')&lt;br /&gt;&gt;&gt;&gt; str(msg)&lt;br /&gt;'From nobody Sun Mar 30 15:23:14 2008\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\nContent-Transfer-Encoding: base64\nContent-Type: text/html; charset="iso-8859-1"\n\ndGhpcyBpcyBhIHB5dGg9RjhuIHVuaWNvZGUgc3RyaW5n'&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Based on the conent type the email module chooses to convert html to base64 for you.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt; Email Headers &lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;Email headers are special, and especially ugly. You press send on your new maillist software that has worked correctly during testing. Then all of a sudden there is one of them nasty international characters in the subject.&lt;br /&gt;&lt;br /&gt;You did set the charset to iso-8859-1 in the add_header() method. So why does it fail?&lt;br /&gt;&lt;br /&gt;Well because each header field needs to be set individually. The content of the headers has nothing to do with the message content of the text (payload).&lt;br /&gt;&lt;br /&gt;you can set it like this:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&gt;&gt;&gt; from email.Header import Header&lt;br /&gt;&gt;&gt;&gt; from email.Message import Message&lt;br /&gt;&gt;&gt;&gt; msg = Message()&lt;br /&gt;&gt;&gt;&gt; subject = u'This is a pythøn subject'&lt;br /&gt;&gt;&gt;&gt; subject_latin1 = subject.encode('latin-1')&lt;br /&gt;&gt;&gt;&gt; h = Header(subject_latin1, 'iso-8859-1')&lt;br /&gt;&gt;&gt;&gt; msg['Subject'] = h&lt;br /&gt;&gt;&gt;&gt; msg.as_string()&lt;br /&gt;'Subject: =?iso-8859-1?q?This_is_a_pyth=F8n_subject?=\n\n'&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;The email headers like To, From etc. are a little bit tricky.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&gt;&gt;&gt; from email.Utils import formataddr&lt;br /&gt;&gt;&gt;&gt; email = u'maxm@mxm.dk'.encode('latin-1')&lt;br /&gt;&gt;&gt;&gt; name = u'max møller'.encode('latin-1')&lt;br /&gt;&gt;&gt;&gt; From = formataddr( (name, email) )&lt;br /&gt;&gt;&gt;&gt; From&lt;br /&gt;'max m\xf8ller &lt;maxm@mxm.dk&gt;'&lt;br /&gt;&gt;&gt;&gt; from email.Header import Header&lt;br /&gt;&gt;&gt;&gt; h = Header(From, 'iso-8859-1')&lt;br /&gt;&gt;&gt;&gt; str(h)&lt;br /&gt;'=?iso-8859-1?q?max_m=F8ller_=3Cmaxm=40mxm=2Edk=3E?='&lt;br /&gt;&gt;&gt;&gt; msg = Message()&lt;br /&gt;&gt;&gt;&gt; msg['From'] = From&lt;br /&gt;&gt;&gt;&gt; msg.as_string()&lt;br /&gt;'From: max m\xf8ller &lt;maxm@mxm.dk&gt;\n\n'&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt; UTF-7 and IMAP &lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;If you are writing an email client in Python you will most likely need to support Imap. Imap has folders, and their names use a special encoding not seen anywhere else. It is an encoding based on utf-7. It is not a common problem so I will not use much space on it here. But I have written an encoder for it that you can get here:&lt;br /&gt;&lt;br /&gt;http://svn.plone.org/svn/collective/mxmImapClient/trunk/imapUTF7.py&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&gt;&gt;&gt; from imapUTF7 import imapUTF7Encode&lt;br /&gt;&gt;&gt;&gt; st_utf7_imap = imapUTF7Encode(st)&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;More about the issue can be found here:&lt;br /&gt;&lt;br /&gt;5.1.3.  Mailbox International Naming Convention&lt;br /&gt;http://www.faqs.org/rfcs/rfc2060.html&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt; More info and links &lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;http://www.tbray.org/ongoing/When/200x/2003/04/26/UTF&lt;br /&gt;&lt;br /&gt;http://evanjones.ca/python-utf8.html&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3058608860238047362-8072702400675426425?l=mxm-mad-science.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mxm-mad-science.blogspot.com/feeds/8072702400675426425/comments/default' title='Kommentarer til indlægget'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3058608860238047362&amp;postID=8072702400675426425' title='3 kommentarer'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/8072702400675426425'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/8072702400675426425'/><link rel='alternate' type='text/html' href='http://mxm-mad-science.blogspot.com/2008/03/python-unicode-lessons-from-school-of.html' title='Python Unicode lessons from the school of hard knocks'/><author><name>Max M</name><uri>http://www.blogger.com/profile/01654969463596586272</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://bp0.blogger.com/_vdQTPcBkW6M/R5-HEb6ishI/AAAAAAAAAAM/rCEOHhgIjDI/S220/maxm.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3058608860238047362.post-1865711921185525525</id><published>2008-02-15T13:38:00.000-08:00</published><updated>2008-02-15T13:45:09.706-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Archetypes'/><category scheme='http://www.blogger.com/atom/ns#' term='software development'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='json'/><category scheme='http://www.blogger.com/atom/ns#' term='plone'/><category scheme='http://www.blogger.com/atom/ns#' term='zope'/><title type='text'>Migrating Plone Site via JSON files #2</title><content type='html'>I sometimes wonder if I am particularly stupid, or if everybody goes through the same process as I do. Every programming problem I try to solve I basically approach with an idea of how I want to do it. Then I make a first version. No more planning than that.&lt;br /&gt;&lt;br /&gt;If I have made something similar before, the solution is very close to my initial idea. If it is a new area of expertice the solution often ends up very differently from what I had visualized. I find a problem, i fix it and refactor the code. I find a new problem i fix it and refactor the code.&lt;br /&gt;&lt;br /&gt;It works for me, and I find that it usually produces quality code in the end. Especially since I am pretty rutheless with the refactoring.&lt;br /&gt;&lt;br /&gt;But I keep wondering if I would code faster if I did more upfront design.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Export is done&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;I think that I am finally finished with the export part of this json migrator. Well ok. This is a customer project and I am focusing on a specific subset of the content types. So it is not a general tool, but that is ok for now. I keep refactoring and using general methods to solve the problems.&lt;br /&gt;&lt;br /&gt;I can export folders, files, document and images. Events should be easy too. I just don't need them yet. I can also export some custom types. Each custom type need about 15 lines of code.&lt;br /&gt;&lt;br /&gt;The major thing I have not looked at yet is discussions/talkbacks.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Recap of the data structure&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;The (simplified) content object now looks like this.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;class Document:&lt;br /&gt;   getId='my-document'&lt;br /&gt;   meta_type='Document'&lt;br /&gt;   Title = 'My Title'&lt;br /&gt;   Description = 'An example content object'&lt;br /&gt;   content_type = 'text/html'&lt;br /&gt;   text = 'The text in the content object'&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;A file like this&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;class Image:&lt;br /&gt;   getId='this-is-an-image.jpg'&lt;br /&gt;   meta_type='Portal Image'&lt;br /&gt;   Title = 'This is an image'&lt;br /&gt;   Description = 'An example image object'&lt;br /&gt;   content_type = 'image/jpg'&lt;br /&gt;   data = 'dagfhh3gh3gf3hgf' # mysterious data that is a jpeg image :-s&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;The document type was easy to save as json. I just mapped every attribute to a key/val in a dict.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;document = {&lt;br /&gt;   u'getId':u'my-document',&lt;br /&gt;   u'meta_type':u'Document',&lt;br /&gt;   u'Title':u'My Title',&lt;br /&gt;   u'Description':u'An example content object',&lt;br /&gt;   u'getRawText':u'The text in the content object',&lt;br /&gt;   u'content_type':u'text/html',&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;The paths chosen, with many backtracks&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;A minor problem was that the large amount of html, in some of the documents, made it harder to read the json files in the text editor. Just like xml files with cdata sections.&lt;br /&gt;&lt;br /&gt;A bigger problem was the image/file content type. I first tried to save the "data" attribute as a value in the dict. But the json writer did not like that. It turned out that there was some chars that it could not escape. It probably needs to be utf-8 or something like that. I did not digg to deep into the reason, but chose to encode the data.&lt;br /&gt;&lt;br /&gt;The data is a string, so it is pretty simple to just use base64:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&gt;&gt;&gt; 'this will be encoded'.encode('base64')&lt;br /&gt;'dGhpcyB3aWxsIGJlIGVuY29kZWQ=\n'&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;But large image files did not exactly improve the visual signal to noise ration in the json files either. I tried for about 15 minutes to find a library that would linebreak the encoded data to at least improve the readability a bit, but finally realised that I had fallen in love with the idea of the json purity and neede to let go.&lt;br /&gt;&lt;br /&gt;It is like that sometimes. You get an idea and have some resistance to letting it go. Authors have an expression called '&lt;a href="http://brendacoulter.blogspot.com/2008/01/how-to-kill-your-darlings-without.html"&gt;kill your darlings&lt;/a&gt;'. Same thing.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Base64 encoded data is also about 30% larger, and when the first 19 MByte (.bmp) files turned up in my export dir I was sure I was on the wrong track again.&lt;br /&gt;&lt;br /&gt;The huge .bmp image file was just a portait of some random person. there was absolutely no reason for it to take up more than 1/2 MB as a reasonably compressed jpeg file.&lt;br /&gt;&lt;br /&gt;There is one thing I have learned through the years. Users do not do what they should. They do what they can. And if they can upload a 12 MB bmp image, they will. So I expect this to be a normal use case on many sites.&lt;br /&gt;&lt;br /&gt;A 19 MBytes file would need to be loaded into ram, and then converted back into the binary file, that would be about 12 MBytes. Meaning that I would use 30 MBytes of ram just for this single json file.&lt;br /&gt;&lt;br /&gt;With a Plone site of several thousand objecs it can get ugly real fast.&lt;br /&gt;&lt;br /&gt;Besides that an object made with AT can easily have several image fields each with a multi MB image.&lt;br /&gt;&lt;br /&gt;My idea of using just json for export did not look to good.&lt;br /&gt;&lt;br /&gt;Then I played with the idea of keeping the data in the data field and then swap it out with a filename when I wrote it out to disc. So this structure:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;document = {&lt;br /&gt;   ...&lt;br /&gt;   u'data':u'dagfhh3gh3gf3hgf' # jpeg image&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;Would be changed to this when saved:&lt;br /&gt;&lt;br /&gt;document = {&lt;br /&gt;   ...&lt;br /&gt;   u'data':u'file.1.json.1.bin' # jpeg image file name&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;And it would be saved as two seperate files on the filesystem.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;file.1.json         # the json file&lt;br /&gt;file.1.json.1.bin   # the data file&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;That was pretty clean, and I liked that it was easy to see which data file belonged to which json file. The json file was kind of a meta data file for the data file.&lt;br /&gt;&lt;br /&gt;But now that the data was saved seperately it annoyed me that I could not see what kind of file it was. If I want to view the file "file.1.json.1.bin" i have to look in "file.1.json", see that it was a jpeg file and then rename it to "file.1.json.1.jpg". And then I could still not guess the content from the name.&lt;br /&gt;&lt;br /&gt;Sometimes unskilled labor is a better solution than an overpriced programmer. If you only have a few files that you care about in your old CMS, it can be practical to just export them and then do a manual import.&lt;br /&gt;&lt;br /&gt;If you can see all the files from your site in one directory it can also give a good overview of the work you need to do. &lt;br /&gt;&lt;br /&gt;For these reasons I found that I had to rewrite once more and give the data files some sort of meaningfull names.&lt;br /&gt;&lt;br /&gt;This turned out to be surpricingly difficult. As usual the devil is in the details.&lt;br /&gt;&lt;br /&gt;More in the next article&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3058608860238047362-1865711921185525525?l=mxm-mad-science.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mxm-mad-science.blogspot.com/feeds/1865711921185525525/comments/default' title='Kommentarer til indlægget'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3058608860238047362&amp;postID=1865711921185525525' title='0 kommentarer'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/1865711921185525525'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/1865711921185525525'/><link rel='alternate' type='text/html' href='http://mxm-mad-science.blogspot.com/2008/02/migrating-plone-site-via-json-files-2.html' title='Migrating Plone Site via JSON files #2'/><author><name>Max M</name><uri>http://www.blogger.com/profile/01654969463596586272</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://bp0.blogger.com/_vdQTPcBkW6M/R5-HEb6ishI/AAAAAAAAAAM/rCEOHhgIjDI/S220/maxm.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3058608860238047362.post-5372018631705287085</id><published>2008-02-14T03:04:00.000-08:00</published><updated>2008-02-14T03:13:17.328-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='software development'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='json'/><category scheme='http://www.blogger.com/atom/ns#' term='plone'/><category scheme='http://www.blogger.com/atom/ns#' term='zope'/><title type='text'>Migrating Plone Site via JSON files #1</title><content type='html'>&lt;span style="font-weight:bold;"&gt;I am currently migratin an old Plone 2.0.1 site to 3.0. Normally you would try portal_migrations, and then upgrade one minor Plone version at a time.&lt;br /&gt;&lt;br /&gt;I have tried this several times in the past. It sucks!&lt;br /&gt;&lt;br /&gt;On every step one of your custom products, and quite a few of the built ins, will break. Making every step slow and frustrating. I have had 0% succes rate with making this method work without glitches.&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Going from a 2.x to a 3.x site is especially fraught with danger as the old site contains a mixture of CMF and AT based content.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;A New Approach&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;So this time I wanted to try a different approach. Export the content object as data on the disc from the old site, and then importing it to the new site from those files.&lt;br /&gt;&lt;br /&gt;I also want the exported files to be human readable, as it makes debugging a whole lot easier. I suck at reading pickled data. Besides that, the old site is running Python 2.3 and another is running 2.4 so pickles might break.&lt;br /&gt;&lt;br /&gt;The method also has the advantage that json is cross platform, so when the data is exported it can even be imported into completely different CMS systems.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Remapping the Contents Type and Location&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;When the content is imported to the new site, it also needs to be remapped a lot. Some older content objects are no longer neede, and the data should be put into Documents or custom content types. Some of the folders are also moved around.&lt;br /&gt;&lt;br /&gt;I could convert custom content types to Documents before upgrading, but then I would be unable to migrate special stuff like custom views, or other things that made us drop the custom types to begin with.&lt;br /&gt;&lt;br /&gt;I can also convert the types ind the end, but then I would need to make the custom types compatible with every step in the migration.&lt;br /&gt;&lt;br /&gt;Doing this will take som custom scripting anyay. I might as well do it in one swoop while migrating. It would be difficult with the old method.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Goal&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I expect to end up with a migration script that can be run first on the old instance, and then on the new. Which will move and "massage" the content while both instances are up and running.&lt;br /&gt;&lt;br /&gt;I also aim to make a general framework for migration via json.&lt;br /&gt;&lt;br /&gt;I write this as I try out things, so it might not succed in the end, but it would be a real long posting if I wait until the end.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Here we go then&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;A (simplified) content object like the one below cannot be easily saved to disc:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;class Document:&lt;br /&gt;    portal_type='Document'&lt;br /&gt;    Title = 'My Title'&lt;br /&gt;    Description = 'An example content object'&lt;br /&gt;    getRawText = 'The text in the content object'&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;I &lt;span style="font-weight: bold;"&gt;could&lt;/span&gt; save it as a xml:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&amp;lt;content&amp;gt;&lt;br /&gt;    &amp;lt;portaltype&amp;gt;Document&amp;lt;/portaltype&amp;gt;&lt;br /&gt;    &amp;lt;title&amp;gt;My Title&amp;lt;/title&amp;gt;&lt;br /&gt;    &amp;lt;description&amp;gt;An example content object&amp;lt;/description&amp;gt;&lt;br /&gt;    &amp;lt;getrawtext&amp;gt;The text in the content object&amp;lt;/getrawtext&amp;gt;&lt;br /&gt;&amp;lt;/content&amp;gt;&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;but quite frankly I find xml parsing a bore. Even with the brilliant lxml library. I would also have to do xml generation, which can be tricky.&lt;br /&gt;&lt;br /&gt;So I decided to try out json as the format. It is best described as a data format using only primitive JavaScript data types. Luckilly these are almost exactly like primitive Python data types.&lt;br /&gt;&lt;br /&gt;The trick here is that I can just generate primitive Python data structures, and the use one of the many json python libraries to convert to json that can be stored on disc. My simple data structure looks like this:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;document = {&lt;br /&gt;    u'portal_type':u'Document',&lt;br /&gt;    u'Title':u'My Title',&lt;br /&gt;    u'Description':u'An example content object',&lt;br /&gt;    u'getRawText':u'The text in the content object',&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;And this is how easy it is to save this structure:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;import simplejson&lt;br /&gt;f = open('mydocument.json', 'wb')&lt;br /&gt;simplejson.dump(document, f)&lt;br /&gt;f.close()&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Opening and getting it back to a primitive Python data structure is just as simple.&lt;br /&gt;&lt;br /&gt;More in next article.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3058608860238047362-5372018631705287085?l=mxm-mad-science.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mxm-mad-science.blogspot.com/feeds/5372018631705287085/comments/default' title='Kommentarer til indlægget'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3058608860238047362&amp;postID=5372018631705287085' title='8 kommentarer'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/5372018631705287085'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/5372018631705287085'/><link rel='alternate' type='text/html' href='http://mxm-mad-science.blogspot.com/2008/02/migrating-plone-site-via-json-files-1.html' title='Migrating Plone Site via JSON files #1'/><author><name>Max M</name><uri>http://www.blogger.com/profile/01654969463596586272</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://bp0.blogger.com/_vdQTPcBkW6M/R5-HEb6ishI/AAAAAAAAAAM/rCEOHhgIjDI/S220/maxm.jpg'/></author><thr:total>8</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3058608860238047362.post-188169722550272248</id><published>2008-02-12T05:19:00.000-08:00</published><updated>2008-02-12T06:37:53.912-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='uml'/><category scheme='http://www.blogger.com/atom/ns#' term='Archetypes'/><category scheme='http://www.blogger.com/atom/ns#' term='plone'/><category scheme='http://www.blogger.com/atom/ns#' term='zope'/><title type='text'>Running ArgoUML with Java Webstart on Ubuntu</title><content type='html'>I had no idea what &lt;a href="http://java.sun.com/products/javawebstart/"&gt;Java Web Start&lt;/a&gt; was, but it turns out to be ok. Well I have not used it long enough to see the pitfalls, but initial impressions are good.&lt;br /&gt;&lt;br /&gt;I have relatively recently changed from Windows to Ubuntu on my development maschine. It was not that big a deal as I have been using Linux and BSD on servers for ages. But I am generally satisfied.&lt;br /&gt;&lt;br /&gt;In that process I am trying to keep up with the Joneses in Plone development. &lt;a href="http://plone.org/documentation/tutorial/buildout"&gt;Buildout&lt;/a&gt; is great, and so is &lt;a href="http://plone.org/documentation/how-to/use-paster"&gt;Paster&lt;/a&gt; and &lt;a href="http://plone.org/products/zopeskel"&gt;ZopeSkel&lt;/a&gt;. So I tried to make a new content type using the new &lt;a href="http://www.mustap.com/pythonzone_post_234_zopeskel-with-local-commands"&gt;local command&lt;/a&gt; for Paster. And it went sort of bad. I have not seen it documented anywhere, but it only works for Plone 3 templates.&lt;br /&gt;&lt;br /&gt;I really needed to start up a new content type on a Plone 2.5 site. So it was back to &lt;a href="http://argouml.tigris.org/"&gt;ArgoUML&lt;/a&gt; and &lt;a href="http://plone.org/documentation/manual/archgenxml2"&gt;AGX&lt;/a&gt;. Just like the good old days ...&lt;br /&gt;&lt;br /&gt;On the argouml website there was a link to something called Java Webstart. I clicked on it, and ArgoUML started up. Just like that. No install options no nothing.  So I closed it down.&lt;br /&gt;&lt;br /&gt;Now I wanted to start it again without clicking. It was not in my ubuntu menu. I had not used synaptic so that was unsurprising. It downloaded a pretty big file, so I knew I had a local copy somewhere. But I could not find it. I tried a few commands that I googled. To no avail. In the end it turned out to be very simple:&lt;br /&gt;&lt;code&gt;javaws http://argouml-downloads.tigris.org/jws/argouml-latest-stable.jnlp&lt;/code&gt;Running software directly from an url. Sweet.&lt;br /&gt;&lt;br /&gt;Next step was trying to get it to work with the agx_argouml_profile, so that I could just click on the tagged values etc. I could not figure out how to do that. But I really did not dig that deep. I tried passing it the option as I would have done with plain java. That did not work:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;javaws -Dargo.defaultModel==agx_argouml_profile http://argouml-downloads.tigris.org/jws/argouml-latest-stable.jnlp&lt;br /&gt;&lt;/code&gt;If any of you have tried it with succes, please let me know.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3058608860238047362-188169722550272248?l=mxm-mad-science.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mxm-mad-science.blogspot.com/feeds/188169722550272248/comments/default' title='Kommentarer til indlægget'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3058608860238047362&amp;postID=188169722550272248' title='1 kommentarer'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/188169722550272248'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/188169722550272248'/><link rel='alternate' type='text/html' href='http://mxm-mad-science.blogspot.com/2008/02/running-argouml-with-java-webstart-on.html' title='Running ArgoUML with Java Webstart on Ubuntu'/><author><name>Max M</name><uri>http://www.blogger.com/profile/01654969463596586272</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://bp0.blogger.com/_vdQTPcBkW6M/R5-HEb6ishI/AAAAAAAAAAM/rCEOHhgIjDI/S220/maxm.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3058608860238047362.post-2739622084656942260</id><published>2008-02-06T04:09:00.000-08:00</published><updated>2008-02-06T04:51:19.193-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='buildout'/><category scheme='http://www.blogger.com/atom/ns#' term='eggs'/><category scheme='http://www.blogger.com/atom/ns#' term='easy_install'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='plone'/><category scheme='http://www.blogger.com/atom/ns#' term='zope'/><title type='text'>Python eggs - a Simple Introduction</title><content type='html'>Python eggs used to be the wave of the future. But for Zope and Plone developers this has evolved into a true tsunami. They are everywhere now.&lt;br /&gt;&lt;br /&gt;Yet there is a lot of confusion of what they are and how to use them.&lt;br /&gt;&lt;br /&gt;To understand them, you need to understand Pythons way of organizing code files.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Module&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;The basic unit of code reusability in Python: a block of code imported by some other code. It is most often a module written in Python and contained in a single .py file. Also called a script.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;hello.py&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Let us say that this hello.py contains a function:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;def helloworld():&lt;br /&gt;    print 'Hello World'&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Then it is possible to import that function like this:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;from hello import helloworld&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Package&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;A module that contains other modules; typically contained in a directory in the filesystem and distinguished from other directories by the presence of a file __init__.py.&lt;br /&gt;&lt;br /&gt;A step up from a script is a module, which is a library with an __init__.py file in it.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;hello/&lt;br /&gt;    __init__.py&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;You can then put the helloworld function into the __init__.py script, and import it like you did before:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;from hello import helloworld&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;You could also keep it in the hello.py file from before.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;hello/&lt;br /&gt;    __init__.py&lt;br /&gt;    hello.py&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;But then you must import it like this:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;from hello.hello import helloworld&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Unless you import it into the module namespace. You do this in the __init__.py script:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;from hello import helloworld&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Then you will once more be able to write:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;from hello import helloworld&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;This ensures the you can reorganize your code and still remain backwards compatibility.&lt;br /&gt;&lt;br /&gt;You can have modules inside modules. A python library is just a module, or a structure of modules.&lt;br /&gt;&lt;br /&gt;A structure of modules is called a package.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Distutils&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;So far it has all been about writing and organizing Python code. But the next step is distribution af said code. First step in this direction is distutils.&lt;br /&gt;&lt;br /&gt;Distutils was written to have a single unified way to install Python modules and packages. Basically you just cd to the directory of the module and write:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;python setup.py install&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Then the module will automagically install itself in the python it was enwoked with.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Distutils defines a directory/file structure outside your module, that has nothing to do with the module per se, but is used distribute the module.&lt;br /&gt;&lt;br /&gt;If you want to make a distribution of the hello module you must put it inside a directory that also contains a setup.py file.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;somedir/&lt;br /&gt;    setup.py&lt;br /&gt;    hello/&lt;br /&gt;        __init__.py&lt;br /&gt;        hello.py&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;The setup.py could contains this code, that runs the setup function:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;from distutils.core import setup&lt;br /&gt;&lt;br /&gt;setup(name='hello',&lt;br /&gt;    version='1.0',&lt;br /&gt;    packages=['hello',],&lt;br /&gt;)&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;You then run the code like this:&lt;br /&gt;&lt;br /&gt;python setup.py sdist&lt;br /&gt;&lt;br /&gt;And it will create a new directory structure like this:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;somedir/&lt;br /&gt;    setup.py&lt;br /&gt;    hello/&lt;br /&gt;        __init__.py&lt;br /&gt;        hello.py&lt;br /&gt;    dist/&lt;br /&gt;        hello-1.0.tar.gz&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;The hello-1.0.tar.gz then contains the package distribution. It has this structure when unpacked:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;hello-1.0/&lt;br /&gt;    PKG-INFO&lt;br /&gt;    setup.py&lt;br /&gt;    hello/&lt;br /&gt;        __init__.py&lt;br /&gt;        hello.py&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;The hello package is inside it. It is just a copy of your own package with no changes.&lt;br /&gt;&lt;br /&gt;setup.py is there too. It is also just a copy of the one you wrote to create the package with. The clever thing about distutils is that it can use the same script to create the distribution as it use to install the package.&lt;br /&gt;&lt;br /&gt;PKG-INFO is a new file and it just contains some metadata for the package. Those can be set in the setup.py.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Setuptools&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;Setuptools is built on top of distutils. It makes it possible to save modules in pypi, or somewhere else. It uses eggs for distribution.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;eggs&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;An egg is created very much like a distutil package. You just have to change a line in your setup.py&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;from setuptools import setup # this is new&lt;br /&gt;&lt;br /&gt;setup(name='hello',&lt;br /&gt;    version='1.0',&lt;br /&gt;    packages=['hello',],&lt;br /&gt;)&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Then you call it with:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;python setup.py bdist_egg&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;And you get a new file in your dist directory:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;dist/&lt;br /&gt;    hello-1.0-py2.4.egg&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;This is the egg that you can put on your website, or even better, publish to pypi. you can get an account on pypi, and then you will be able to add your eggs via the command line like:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;setup.py bdist_egg upload&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Easy Install&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;When you have uploaded your egg, all the world is able to use it by installing it with easy_install:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;easy_install hello&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Easy install will then find the egg on pypi, download it, compile if necessary and add it to your sys.path so that Python will find it.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Buildout&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;Buildout is a configuration based system for making complicated but repeatable setups for large systems.&lt;br /&gt;&lt;br /&gt;Phew. That sounds complicated. Well buildout can be. But what is interresting from an eggs based point of view is that you configure what eggs are to be installed in your system.&lt;br /&gt;&lt;br /&gt;Inside your buildout.cfg you can have a line like:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;eggs =&lt;br /&gt;    hello&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Then buildout will automatically download and install the hello package in your system.&lt;br /&gt;&lt;br /&gt;Buildouts can themself be distributed as eggs, and you can extend a buildout to add new packages. This is how you can install a Plone buildout and then add your own packages to it. Basically creating your own custom Plone distributions.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Resources&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Python modules and packages&lt;br /&gt;    &lt;br /&gt;    http://www.python.org/doc/2.4.4/tut/node8.html&lt;br /&gt;&lt;br /&gt;Distutils&lt;br /&gt;&lt;br /&gt;    http://docs.python.org/lib/module-distutils.html&lt;br /&gt;&lt;br /&gt;Setuptools&lt;br /&gt;&lt;br /&gt;    http://peak.telecommunity.com/DevCenter/setuptools&lt;br /&gt;&lt;br /&gt;Eggs&lt;br /&gt;&lt;br /&gt;    http://peak.telecommunity.com/DevCenter/PythonEggs&lt;br /&gt;    &lt;br /&gt;Buildout&lt;br /&gt;&lt;br /&gt;    http://pypi.python.org/pypi/zc.buildout&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3058608860238047362-2739622084656942260?l=mxm-mad-science.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mxm-mad-science.blogspot.com/feeds/2739622084656942260/comments/default' title='Kommentarer til indlægget'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3058608860238047362&amp;postID=2739622084656942260' title='10 kommentarer'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/2739622084656942260'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/2739622084656942260'/><link rel='alternate' type='text/html' href='http://mxm-mad-science.blogspot.com/2008/02/python-eggs-simple-introduction.html' title='Python eggs - a Simple Introduction'/><author><name>Max M</name><uri>http://www.blogger.com/profile/01654969463596586272</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://bp0.blogger.com/_vdQTPcBkW6M/R5-HEb6ishI/AAAAAAAAAAM/rCEOHhgIjDI/S220/maxm.jpg'/></author><thr:total>10</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3058608860238047362.post-9205197230825202999</id><published>2008-02-04T02:50:00.000-08:00</published><updated>2008-02-04T02:52:02.884-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='buildout'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='plone'/><category scheme='http://www.blogger.com/atom/ns#' term='zope'/><title type='text'>Oh glory days... buildout has changed the game</title><content type='html'>I have worked on many projects with developers scattered all around the globe. In that process I have spend a lot of time making setup scripts for Plone sites. Basically a product that sets up the site without content.&lt;br /&gt;&lt;br /&gt;These days it is called a policy product.&lt;br /&gt;&lt;br /&gt;They made it possible to have a local development version that was in sync with the global development server. At least it was possible to install a subset of the solution on your local dev machine that would make it possible to do your own part of the solution.&lt;br /&gt;&lt;br /&gt;The problem arised as soon as the site went live. The setup script would be run one final time, and then never touched again, and it would never be updated any more.&lt;br /&gt;&lt;br /&gt;The dependencies would be too large on a big live site that anybody would actually like to install a complete copy of the production site back on the dev machine.&lt;br /&gt;&lt;br /&gt;Finding and downloading every little version of dependencies was a big enough problem to try and avoid it.&lt;br /&gt;&lt;br /&gt;For that reason I have always had a love / hate relationship with those policy products. A lot of necessary work that would never be used again as soon as the site went live.&lt;br /&gt;&lt;br /&gt;Then a year or two later when you need to do a modification to the site it would be a real mess to get a dev site up and running.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Buildout has changed the game dramatically. The automatic installation and fetching of dependencies actually makes it feasible to run a perfect copy of the production server.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;This is big!&lt;br /&gt;&lt;br /&gt;We are going to save so many hours not doing manual installs of dev sites.&lt;br /&gt;&lt;br /&gt;At some time we are going to see problems when products and libraries dissapearing from the net. But I am pretty sure that most companies will have local proxies for downloads soon.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3058608860238047362-9205197230825202999?l=mxm-mad-science.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mxm-mad-science.blogspot.com/feeds/9205197230825202999/comments/default' title='Kommentarer til indlægget'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3058608860238047362&amp;postID=9205197230825202999' title='0 kommentarer'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/9205197230825202999'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/9205197230825202999'/><link rel='alternate' type='text/html' href='http://mxm-mad-science.blogspot.com/2008/02/oh-glory-days-buildout-has-changed-game.html' title='Oh glory days... buildout has changed the game'/><author><name>Max M</name><uri>http://www.blogger.com/profile/01654969463596586272</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://bp0.blogger.com/_vdQTPcBkW6M/R5-HEb6ishI/AAAAAAAAAAM/rCEOHhgIjDI/S220/maxm.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3058608860238047362.post-5138025782615905317</id><published>2008-01-31T05:06:00.000-08:00</published><updated>2008-01-31T05:14:38.204-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='mysql'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='plone'/><category scheme='http://www.blogger.com/atom/ns#' term='mysqlda'/><category scheme='http://www.blogger.com/atom/ns#' term='ubuntu'/><category scheme='http://www.blogger.com/atom/ns#' term='zope'/><title type='text'>Installing ZMySQLDA in Zope using buildout on Ubuntu and perhaps Debian in general</title><content type='html'>Another note to future self. This is how I did it.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;libmysql++-dev&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;To make Zope work with Mysql I needed to compile the database adapter. For this I needed the mysql dev libraries. On Ubuntu that is done like this:&lt;br /&gt;&lt;blockquote&gt;aptitude install libmysql++-dev&lt;br /&gt;&lt;/blockquote&gt;&lt;span style="font-weight: bold;"&gt;&lt;br /&gt;MySQL-Python&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;For ZMySQLDA to work I needed the plain Python MySQL-Python library installed and compiled.&lt;br /&gt;&lt;br /&gt;Luckily the MySQL-Python package is distributed as an egg, so it can just be added to buildout.cfg&lt;br /&gt;&lt;blockquote&gt;eggs =&lt;br /&gt;  ... other packages&lt;br /&gt;  MySQL-Python&lt;br /&gt;&lt;/blockquote&gt;Buildout will then download, compile and install the package automatically.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;ZMySQLDA&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;ZMySQLDA is old. Latest release 2.0.8 is from 2001. But that is just because it is stable. No worry.&lt;br /&gt;&lt;br /&gt;When I tried to install it with by adding it in the buildout.cfg:&lt;br /&gt;&lt;blockquote&gt;urls =          http://www.zope.org/Members/adustman/Products/ZMySQLDA/2.0.8/ZMySQLDA-2.0.8.tar.gz&lt;br /&gt;&lt;/blockquote&gt;It made a small tarbomb and put itself inside a directory structure like:&lt;br /&gt;&lt;blockquote&gt;lib/python/Products/ZMySQLDA&lt;/blockquote&gt;And Zope was not be able to find it.&lt;br /&gt;&lt;br /&gt;Buildout to the rescue again. Radim Novotny has made a buildout recipe for doing it right:&lt;br /&gt;&lt;blockquote&gt;http://pypi.python.org/pypi/cns.recipe.zmysqlda/&lt;/blockquote&gt;So all I needed was a few additions to the buildout.cfg&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;[buildout]&lt;br /&gt;parts =&lt;br /&gt;  ...&lt;br /&gt;  zmysqlda&lt;/blockquote&gt;and&lt;br /&gt;&lt;blockquote&gt;[zmysqlda]&lt;br /&gt;recipe = cns.recipe.zmysqlda&lt;br /&gt;target = ${buildout:directory}/products&lt;/blockquote&gt;&lt;br /&gt;And then I was good to go.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3058608860238047362-5138025782615905317?l=mxm-mad-science.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mxm-mad-science.blogspot.com/feeds/5138025782615905317/comments/default' title='Kommentarer til indlægget'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3058608860238047362&amp;postID=5138025782615905317' title='2 kommentarer'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/5138025782615905317'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/5138025782615905317'/><link rel='alternate' type='text/html' href='http://mxm-mad-science.blogspot.com/2008/01/installing-zmysqlda-in-zope-using.html' title='Installing ZMySQLDA in Zope using buildout on Ubuntu and perhaps Debian in general'/><author><name>Max M</name><uri>http://www.blogger.com/profile/01654969463596586272</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://bp0.blogger.com/_vdQTPcBkW6M/R5-HEb6ishI/AAAAAAAAAAM/rCEOHhgIjDI/S220/maxm.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3058608860238047362.post-4818862328380311517</id><published>2008-01-30T07:03:00.000-08:00</published><updated>2008-01-30T09:19:34.191-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='linux'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='plone'/><category scheme='http://www.blogger.com/atom/ns#' term='ubuntu'/><category scheme='http://www.blogger.com/atom/ns#' term='zope'/><title type='text'>Installing Python, Zope and Plone on Ubuntu 7.10 - Gutsy Gibbon</title><content type='html'>&lt;span style="font-weight: bold;"&gt;Last time I installed plone on a Ubuntu system, I wrote a log of what I did. It might be that I am getting old, but I cannot seem to remember how I do it from time to time. So this is as much a note to future me as a help to others.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;It is a newer version of this article on my main site.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.mxm.dk/papers/installing-plone"&gt;http://www.mxm.dk/papers/installing-plone&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;With a little luck you should be able to run it as a script on your own server, or at least copy/paste the lines into your terminal, and the see where it fails.&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;####################################&lt;br /&gt;# System installs, libraries etc.&lt;br /&gt;&lt;br /&gt;# Installing python and plone on ubuntu server 7.10 Gutsy Gibbon&lt;br /&gt;# much of it can probably be used on any debian system.&lt;br /&gt;&lt;br /&gt;sudo bash&lt;br /&gt;# as root: install system packages and libraries for compiling python&lt;br /&gt;aptitude install gcc g++ make&lt;br /&gt;&lt;br /&gt;# readline is needed for an interactive python prompt&lt;br /&gt;aptitude install libreadline5 libreadline5-dev&lt;br /&gt;&lt;br /&gt;# zlib is needed for zope &amp;amp; PIL&lt;br /&gt;aptitude install zlib1g zlib1g-dev&lt;br /&gt;&lt;br /&gt;# libjpeg is neded for PIL&lt;br /&gt;aptitude install libjpeg62 libjpeg62-dev&lt;br /&gt;&lt;br /&gt;# libssl is needed by buildout for downloading with the https protocol&lt;br /&gt;aptitude install libssl0.9.8 libssl-dev&lt;br /&gt;&lt;br /&gt;# create a user called zope.&lt;br /&gt;adduser zope&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;####################################&lt;br /&gt;# zope, plone specific install&lt;br /&gt;&lt;br /&gt;# Do the rest as this user&lt;br /&gt;su zope&lt;br /&gt;# from the users home dir /home/zope&lt;br /&gt;cd ~&lt;br /&gt;# create the directory structure&lt;br /&gt;mkdir downloads&lt;br /&gt;mkdir pythons&lt;br /&gt;mkdir instances&lt;br /&gt;&lt;br /&gt;# download and install python in the zope users home dirs pythons directory&lt;br /&gt;cd ~/downloads&lt;br /&gt;wget http://www.python.org/ftp/python/2.4.4/Python-2.4.4.tgz&lt;br /&gt;tar xzf Python-2.4.4.tgz&lt;br /&gt;cd Python-2.4.4&lt;br /&gt;./configure --prefix=/home/zope/pythons/python-2.4.4&lt;br /&gt;make&lt;br /&gt;make altinstall&lt;br /&gt;&lt;br /&gt;# make a symbolic link for this python. This is optional. But you avoid having&lt;br /&gt;# to type "python2.4" instead of "python"&lt;br /&gt;ln -s /home/zope/pythons/python-2.4.4/bin/python2.4 /home/zope/pythons/python-2.4.4/bin/python&lt;br /&gt;# make this the default python to use by pushing it to the front of the path with this command&lt;br /&gt;PATH=/home/zope/pythons/python-2.4.4/bin:$PATH&lt;br /&gt;# you can add the command to the zope users ~/.bashrc&lt;br /&gt;# to automatically use that python at login. I normally do this.&lt;br /&gt;&lt;br /&gt;# download and install PIL&lt;br /&gt;cd ~/downloads&lt;br /&gt;wget http://effbot.org/media/downloads/Imaging-1.1.6.tar.gz&lt;br /&gt;tar xfz Imaging-1.1.6.tar.gz&lt;br /&gt;cd Imaging-1.1.6&lt;br /&gt;python setup.py build&lt;br /&gt;python setup.py install&lt;br /&gt;&lt;br /&gt;# download and install easy install&lt;br /&gt;cd ~/downloads&lt;br /&gt;wget http://peak.telecommunity.com/dist/ez_setup.py&lt;br /&gt;python ez_setup.py&lt;br /&gt;&lt;br /&gt;# create a default buildout config file. This way your eggs and downloads will&lt;br /&gt;# automatically end up in the same dir.&lt;br /&gt;mkdir ~/.buildout&lt;br /&gt;vi ~/.buildout/default.cfg&lt;br /&gt;&lt;br /&gt;# insert this text (without the "## ") and save&lt;br /&gt;## [buildout]&lt;br /&gt;## executable = /home/zope/pythons/python-2.4.4/bin/python2.4&lt;br /&gt;## eggs-directory = /home/zope/.buildout/eggs&lt;br /&gt;## download-directory = /home/zope/.buildout/downloads&lt;br /&gt;&lt;br /&gt;# then install ZopeSkel with easy install&lt;br /&gt;easy_install -U ZopeSkel&lt;br /&gt;&lt;br /&gt;# you can see what buildouts can be created with this command&lt;br /&gt;paster create --list-templates&lt;br /&gt;&lt;br /&gt;# as I write this, the list looks like this.&lt;br /&gt;#Available templates:&lt;br /&gt;#  archetype:          A Plone project that uses Archetypes&lt;br /&gt;#  basic_namespace:    A project with a namespace package&lt;br /&gt;#  basic_package:      A basic setuptools-enabled package&lt;br /&gt;#  basic_zope:         A Zope project&lt;br /&gt;#  nested_namespace:   A project with two nested namespaces.&lt;br /&gt;#  paste_deploy:       A web application deployed through paste.deploy&lt;br /&gt;#  plone:              A Plone project&lt;br /&gt;#  plone2.5_buildout:  A buildout for Plone 2.5 projects&lt;br /&gt;#  plone2.5_theme:     A Theme for Plone 2.5&lt;br /&gt;#  plone2_theme:       A Theme Product for Plone 2.1 &amp;amp; Plone 2.5&lt;br /&gt;#  plone3_buildout:    A buildout for Plone 3 projects&lt;br /&gt;#  plone3_portlet:     A Plone 3 portlet&lt;br /&gt;#  plone3_theme:       A Theme for Plone 3.0&lt;br /&gt;#  plone_app:          A Plone App project&lt;br /&gt;#  plone_hosting:      Plone hosting: buildout with ZEO and any Plone version&lt;br /&gt;#  recipe:             A recipe project for zc.buildout&lt;br /&gt;&lt;br /&gt;# so to create a plone 3 server you write&lt;br /&gt;&lt;br /&gt;cd ~/instances&lt;br /&gt;paster create -t plone3_buildout p3test&lt;br /&gt;&lt;br /&gt;# It asks a few questions. If you are in doubt just press enter for default values.&lt;br /&gt;# now run the bootstrap script&lt;br /&gt;&lt;br /&gt;cd p3test&lt;br /&gt;python bootstrap.py&lt;br /&gt;&lt;br /&gt;# at this point you should check out builout documentation for your options,&lt;br /&gt;# if you want to run anything but a plane plone site.&lt;br /&gt;# you can edit you buildout.cfg file in p3test. There is a good intro at:&lt;br /&gt;# http://plone.org/documentation/tutorial/buildout/understanding-buildout.cfg&lt;br /&gt;&lt;br /&gt;# now let buildout install and compile Plone. Zope will be compiled automatically.&lt;br /&gt;&lt;br /&gt;./bin/buildout -v&lt;br /&gt;&lt;br /&gt;# then start up and test the site with&lt;br /&gt;./bin/instance fg&lt;/blockquote&gt;&lt;br /&gt;Good luck!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3058608860238047362-4818862328380311517?l=mxm-mad-science.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mxm-mad-science.blogspot.com/feeds/4818862328380311517/comments/default' title='Kommentarer til indlægget'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3058608860238047362&amp;postID=4818862328380311517' title='8 kommentarer'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/4818862328380311517'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/4818862328380311517'/><link rel='alternate' type='text/html' href='http://mxm-mad-science.blogspot.com/2008/01/last-time-i-installed-plone-on-ubuntu.html' title='Installing Python, Zope and Plone on Ubuntu 7.10 - Gutsy Gibbon'/><author><name>Max M</name><uri>http://www.blogger.com/profile/01654969463596586272</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://bp0.blogger.com/_vdQTPcBkW6M/R5-HEb6ishI/AAAAAAAAAAM/rCEOHhgIjDI/S220/maxm.jpg'/></author><thr:total>8</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3058608860238047362.post-3748536238931477909</id><published>2008-01-30T03:58:00.000-08:00</published><updated>2008-01-30T04:10:53.990-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='buildout'/><category scheme='http://www.blogger.com/atom/ns#' term='plone'/><category scheme='http://www.blogger.com/atom/ns#' term='zope'/><title type='text'>Small lesson about extending buildout configs</title><content type='html'>It turns out that it is very easy to have a &lt;span style="font-weight: bold;"&gt;develop.cfg&lt;/span&gt; that only changes minor stuff in the &lt;span style="font-weight: bold;"&gt;buildout.cfg&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;I had a production server running on 8082, and I prefer my development instances to run on 8080. So I tested out how little extra configuration I could get away with. I was pleasantly surprised.&lt;br /&gt;&lt;blockquote&gt;development.cfg&lt;br /&gt;&lt;br /&gt;[buildout]&lt;br /&gt;extends =&lt;br /&gt;   buildout.cfg&lt;br /&gt;parts =&lt;br /&gt;   instance&lt;br /&gt;&lt;br /&gt;[instance]&lt;br /&gt;http-address = 8080&lt;br /&gt;debug-mode = on&lt;br /&gt;verbose-security = on&lt;br /&gt;&lt;/blockquote&gt;So the production server is build with:&lt;br /&gt;&lt;blockquote&gt;./bin/buildout&lt;/blockquote&gt;And the development server with:&lt;br /&gt;&lt;blockquote&gt;./bin/buildout -c development.cfg&lt;/blockquote&gt;This is just a simpler version of Martin Aspelis tutorial found &lt;a href="http://plone.org/documentation/tutorial/buildout/a-deployment-configuration"&gt;here...&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;But it is so nice when software actually makes your life easier, and buildout is a tremendous time savior in group development situations.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3058608860238047362-3748536238931477909?l=mxm-mad-science.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mxm-mad-science.blogspot.com/feeds/3748536238931477909/comments/default' title='Kommentarer til indlægget'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3058608860238047362&amp;postID=3748536238931477909' title='0 kommentarer'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/3748536238931477909'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/3748536238931477909'/><link rel='alternate' type='text/html' href='http://mxm-mad-science.blogspot.com/2008/01/small-lesson-about-extending-buildout.html' title='Small lesson about extending buildout configs'/><author><name>Max M</name><uri>http://www.blogger.com/profile/01654969463596586272</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://bp0.blogger.com/_vdQTPcBkW6M/R5-HEb6ishI/AAAAAAAAAAM/rCEOHhgIjDI/S220/maxm.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3058608860238047362.post-4983342471836645687</id><published>2008-01-29T11:58:00.000-08:00</published><updated>2008-01-29T12:15:22.586-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='software development'/><category scheme='http://www.blogger.com/atom/ns#' term='plone'/><category scheme='http://www.blogger.com/atom/ns#' term='zope'/><title type='text'>Death to deprecations!</title><content type='html'>Sometimes I love how easy it is to get start a project in Plone. At other times it makes me really furious.&lt;p&gt;When I started programming &lt;span style="font-style: italic;"&gt;(omg like 25 years ago)&lt;/span&gt; I was really fascinated about the idea of only having to do a boring job once, and then build on top of it.&lt;/p&gt;&lt;p&gt;For the rest of my life the machine would do my boring job for me. Yeah right! In this respect Plone has failed miserably!&lt;/p&gt;&lt;p&gt;About a year ago I had a relatively complex task of writing a code for importing adresses from xml files. Not that the task was that difficult in itself. But the details made it take about a week.&lt;/p&gt;&lt;p&gt;It ran on a very old Plone version on Python 2.3.&lt;/p&gt;&lt;p&gt;I knew that I would later have to reuse the code in an upcomming Plone version, so I wrote it very conservatively. I made a simple Zope 2 product as a tool, and saved tha data on that tool in OOBTrees, OOSets etc.&lt;/p&gt;&lt;p&gt;The last week I had to rewrite it for Plone 3. I basically had to change the transaction semantics from get_transaction().commit() to transaction.commit(). It was the easiest rewrite I have done in years. Oh yeah and the 20K adresse ran so fast that it shook me to my AT bones.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;This for me is the core of the problem with Plone. It is layer upon layer of backwards incompatible stuff. For every new Plone release my products have been broken. Everytime! I have spent far more time rewriting my products to new Plone versions than I have ever spent writing them in the first place.&lt;/p&gt;&lt;p&gt;And I have made a lot of complicated products like webmail, workgroups, calendaring, contact databases with ics support, page aggregators etc.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;Currently they are not being maintained anymore unless a customer specifically needs them and pay for the upgrade. It is simply to much of a bother to do it for free, as it will easily take several days per project.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;The problem with every new layer that has been added to Plone, is that it doesn't really change the programming efficiency very much, but stops old code from working. And what worse is, is that you still need to understand every goddarn layer to code efficiently in Plone.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;Plones style of 'sort of' backwards compatibility is a real serious problem for the project. A new layer of code should generally never break something that allready works, and new layers should be independent of the old layers.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;I think that the best approach for Plone and Zope 3 would be to just develop them in parallel, so they can run on the same instance, on the same zodb, and then make shure that they do not break each other.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3058608860238047362-4983342471836645687?l=mxm-mad-science.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mxm-mad-science.blogspot.com/feeds/4983342471836645687/comments/default' title='Kommentarer til indlægget'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3058608860238047362&amp;postID=4983342471836645687' title='0 kommentarer'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/4983342471836645687'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3058608860238047362/posts/default/4983342471836645687'/><link rel='alternate' type='text/html' href='http://mxm-mad-science.blogspot.com/2008/01/death-to-deprecations.html' title='Death to deprecations!'/><author><name>Max M</name><uri>http://www.blogger.com/profile/01654969463596586272</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://bp0.blogger.com/_vdQTPcBkW6M/R5-HEb6ishI/AAAAAAAAAAM/rCEOHhgIjDI/S220/maxm.jpg'/></author><thr:total>0</thr:total></entry></feed>
