Entries For: 2008
- December (1)
- November (3)
- October (1)
- September (2)
- August (1)
- July (1)
- June (1)
- April (2)
- March (5)
- January (3)
2008-12-03
Specific imports versus module imports in Python
I've always been a fan of explicit, separate imports in Python, vs generic module imports. Maybe it's because I like things to be explicit, or I've been spoiled by the way Eclipse Pydev deals with auto-importing, but I have an aversion towards generic module imports. To keep the code style consistent, I even rewrite to this style any foreign code that ends up in my code.
Specifically, I'm talking about:
from foo import Bar Bar()
vs
import foo foo.Bar()
Needless to say, I like the first style better.
There are advantages and disadvantages for each of the above methods:
- general module imports demand less from the editor (the programmer needs to type less "import" lines and the editor doesn't need to be very much aware of Python - emacs and vim users will probably favor this style)
- but it makes it hard sometimes to figure out where a module comes from. Example: you're deep down reading a module and you encounter a line referencing the component module. Now, where does that component module comes from? Could be both of the following imports:
from zope.app import component from zope import component
Of course, some people sensed this problem and write code like:
class MyView(object):
template = zope.app.pagetemplate.ViewPageTemplateFile('template.pt')
But this code is hard to read, hard to write and is almost at the limit with the self-imposed line length of 80 characters, which means most of the times it needs to be broken in two lines.
Today I became aware of what I consider the biggest advantage of using specific import (from foo import Bar). Heavy refactoring, in the absence of a comprehensive test suite, is a lot easier! When starting the program, the imports will fail and you get an immediate pointer to where you need to make a fix. If I would have used a generic module import, the error will have appeared only when trying to use the piece of code that calls foo.Bar().
TL;DR: use specific imports! If your editor doesn't support it, take some time to look at Eclipse Pydev or Netbeans, two free IDEs with Python support. You'll get:
- easier refactoring
- better code legibility
- I'll be happier when I need to reuse code snippets :-)
2008-11-23
Moving to Intrepid and KDE 4.1
I've migrated my laptop (my main workstation, these days) and I've done
a complete migration to the KDE 4.1 desktop. Things are not perfect,
(on the old KDE 3.5 I'd say things were 99% according to my tastes),
but I'm trying to accomodate and find replacements. I didn't like the
KDE 4.1 launcher at first and I've even tested Launcelot for a while,
but now I'm back to the default menu and I'm starting to like it (on
3.5 I was using Tasty Menu).
I'm using Scultptura style with the Scultura-Stone color scheme and Sculptura window decoration. I needed to add another plasmoid from kde-look, the panel spacer, to make the systray smaller and separate the buttons on the right side to those on the left.
Things that I'm missing:
- no "window" list menu widget, which I've tried to replace with a plasmoid. I might as well remove it, because it doesn't list all the windows from all desktops, just the current desktop.
- I don't like the double spacing of the clock & date widget, I wish that it was a single line. I saw that there's a plasmoid on kde-look.org, I might try that.
- I don't like the black background in the systray (at the right top)
- There's no working "app shortcuts buttons" plasmoid, although I may try to simulate that with the Quick Access plasmoid.
Things that I'm happy with:
- Shiny new software!
- KDE 4.1 is actually not that horrible, once I tweak it (although Kde 3.5 had the same problem, closest to my "visual" tastes comes Gnome, but I like Kde applications better).
- NetworkManager seems to deal a bit better with my wireless connection (when my girlfriend opens her Macbook I get terrible disconnects from the access point)
2008-11-18
Subversion 1.5 + default instalation Plone 3.1 buildout: no problems here
I've hit the setuptools + subversion 1.5 problem again with a freshly install Plone 3.1.7 buildout: I've added my egg develop folder in zinstance/src/, I've added the egg in the relevant sections in buildout.cfg, but it wouldn't work because of the incompatibility with the old setuptools version.
The solution is to upgrade the installed setuptools to the latest version. For example, for a standalone Plone installed at /home/tibi/Plone, we have this structure:
/home/tibi/Plone /Python-2.4 /buildout-cache /zinstance
I've changed directory to the Python-2.4 from above and ran:
bin/easy_install -U setuptools
Then I could succesfully run the buildout in the zinstance folder.
2008-11-01
A wxPython based tagcloud renderer
This is a small example app that will render a tag cloud with various font weights/height, based on their weight in the cloud. Not much to say here, hope it is useful to someone. It has actually been easier to design and create then expected, the only difficulty was in figuring out how to resize the buttons based on the size of their label. The algorithm could be improved to generate the cloud in a single pass, but I'm not gonna bother, it works fast enough right now.
import wx
TAGS = [
['animals', 0],
['architecture', 2],
['art', 5],
['august', 1],
['australia', 1],
['autumn', 3],
['baby', 5],
['band', 1],
['barcelona', 3],
['beach', 2],
['berlin', 5],
['bird', 1],
['birthday', 0],
['black', 1],
['blackandwhite', 5],
['blue', 2],
['boston', 3],
['bw', 5],
['california', 1],
['cameraphone', 1],
['camping', 1],
['canada', 4],
['canon', 0],
['car', 5],
['cat', 3],
['chicago', 4],
['china', 5],
['christmas', 0],
['church', 0],
['city', 1],
['clouds', 3],
['color', 5],
['concert', 5],
['cute', 3],
['dance', 0],
['day', 5],
['de', 4],
['dog', 0],
['england', 5],
['europe', 4],
['fall', 1],
['family', 4],
['festival', 1],
['film', 1],
['florida', 2],
['flower', 1]
]
WIDTH = 500
def getwidth(line):
return sum([l.GetSize()[0] for l in line])
def getheight(line):
return max([l.GetSize()[1] for l in line])
class TopFrame(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
self.SetSize((500,400))
lines = []
for tag in TAGS:
l = wx.Button(self, -1, tag[0], style=wx.NO_BORDER|wx.BU_EXACTFIT)
weight = ((tag[1] % 2) and wx.BOLD) or wx.NORMAL
l.SetFont(wx.Font(10 + tag[1], wx.DEFAULT, wx.NORMAL, weight, 0, ""))
l.SetSize(l.GetBestSize())
w, _h = l.GetSize()
if lines:
line_width = getwidth(lines[-1])
if (line_width + w) > WIDTH: #make a new line
lines.append([l])
else:
lines[-1].append(l)
else: #lines is empty
lines.append([l])
row_pos = 0
for line in lines:
height = getheight(line)
row_w = 0
w = getwidth(line)
spacer = (WIDTH - w) / len(line)
for l in line:
dh = row_pos + height - l.GetSize()[1]
l.MoveXY(row_w, dh)
row_w += l.GetSize()[0] + spacer
row_pos += height
class MyApp(wx.App):
def OnInit(self):
wx.InitAllImageHandlers()
frame = TopFrame(None, - 1, "")
self.SetTopWindow(frame)
frame.Show()
return 1
def start():
app = MyApp(0)
app.MainLoop()
if __name__ == "__main__":
start()
2008-10-08
RichText control with wxpython: saving, loading and converting from internal XML to HTML
I tend to be an angry caveman these days (see my previous post) and this doesn't lead to too much clear thinking. I've been dealing with the wx.richtext.RichTextCtrl for the past couple of days and I think I'm now close to finishing the tasks that I set myself to do with it. I'm trying to run a bunch of richtext controls on the same page, that would increase their size and show a toolbar when focused. My first problems came from the fact that all 5 of them flashed a caret which I couldn't hide. Trying to do ctrl.GetCaret().Hide() would result in a crash. After struggling with various possible solutions - none of which worked, I've realized that I should try the latest wxpython distribution, I was even decided to compile it manually. Fortunately, the wxwidgets project offers a repository for Ubuntu and sure enough, after I've upgraded, things started to work (I still needed to hide the caret for all the richtext controls and show it when they were focused, but that's no problem).
Now, unto the next tasks: saving and loading the content of these fields. I wanted to display the content in an HtmlWindow control, so converting the content to html was one thing I had to discover how to do. First step that I've tried, saving the html and loading it through the RichTextCtrl and its closely related buffers and handlers didn't work, so I've settled for a solution that involves one RichTextHTMLHandler and one RichTextXmlHandler. To help me understand how this thing works, I've created a clean example class where I could play with this thing. I hope it helps someone, as I haven't found too much info on this on the web (the Load/SaveStream methods are not even documented anywhere).
The toolbar class is taken from my project, created with wxGlade, while the frame class is created by me, manually (actually, this is the first frame that I have created by hand). To demonstrate the loading and saving, a variable is used to keep the content (self.content). Change the text, add some formatting, hit Save and then Load. This will take the text that was saved from the richtext control (in xml format), load it in a buffer and convert it to html which is then displayed in the HtmlWindow. I've only tried this on Linux, hope it works on Windows too. Oh, and you need to supply your own icons if you want to try the code (hint: I've used /usr/share/icons/gnome/32x32/actions as source of my icons.
#!/usr/bin/env python
from StringIO import StringIO
import wx
import wx.html
import wx.richtext
class TextFormatToolBar(wx.ToolBar):
def __init__(self, *args, **kwds):
self.text_ctrl = kwds.pop('text_ctrl')
kwds["style"] = wx.TB_FLAT|wx.TB_3DBUTTONS
wx.ToolBar.__init__(self, *args, **kwds)
self.AddLabelTool(wx.ID_CUT, "Cut", wx.Bitmap("edit-cut.png", wx.BITMAP_TYPE_ANY),
wx.NullBitmap, wx.ITEM_NORMAL, "Cut selection", "")
self.AddLabelTool(wx.ID_COPY, "Copy", wx.Bitmap("edit-copy.png", wx.BITMAP_TYPE_ANY),
wx.NullBitmap, wx.ITEM_NORMAL, "", "")
self.AddLabelTool(wx.ID_PASTE, "Paste", wx.Bitmap("edit-paste.png", wx.BITMAP_TYPE_ANY),
wx.NullBitmap, wx.ITEM_NORMAL, "", "")
self.AddSeparator()
self.AddLabelTool(wx.ID_UNDO, "Undo", wx.Bitmap("edit-undo.png", wx.BITMAP_TYPE_ANY),
wx.NullBitmap, wx.ITEM_NORMAL, "", "")
self.AddLabelTool(wx.ID_REDO, "Redo", wx.Bitmap("edit-redo.png", wx.BITMAP_TYPE_ANY),
wx.NullBitmap, wx.ITEM_NORMAL, "", "")
self.AddSeparator()
self.AddLabelTool(wx.ID_BOLD, "Bold", wx.Bitmap("format-text-bold.png", wx.BITMAP_TYPE_ANY),
wx.NullBitmap, wx.ITEM_NORMAL, "", "")
self.AddLabelTool(wx.ID_ITALIC, "Italic", wx.Bitmap("format-text-italic.png", wx.BITMAP_TYPE_ANY),
wx.NullBitmap, wx.ITEM_NORMAL, "", "")
self.AddLabelTool(wx.ID_UNDERLINE, "Underline", wx.Bitmap("format-text-underline.png",
wx.BITMAP_TYPE_ANY), wx.NullBitmap, wx.ITEM_NORMAL, "", "")
self.Realize()
self.Bind(wx.EVT_TOOL, self.handle_bold, id=wx.ID_BOLD)
self.Bind(wx.EVT_TOOL, self.handle_italic, id=wx.ID_ITALIC)
self.Bind(wx.EVT_TOOL, self.handle_underline, id=wx.ID_UNDERLINE)
self.Bind(wx.EVT_TOOL, self.handle_paste, id=wx.ID_PASTE)
self.Bind(wx.EVT_TOOL, self.handle_copy, id=wx.ID_COPY)
self.Bind(wx.EVT_TOOL, self.handle_cut, id=wx.ID_CUT)
self.Bind(wx.EVT_TOOL, self.handle_undo, id=wx.ID_UNDO)
self.Bind(wx.EVT_TOOL, self.handle_redo, id=wx.ID_REDO)
def handle_bold(self, event):
self.text_ctrl.ApplyBoldToSelection()
def handle_italic(self, event):
self.text_ctrl.ApplyItalicToSelection()
def handle_underline(self, event):
self.text_ctrl.ApplyUnderlineToSelection()
def handle_paste(self, event):
self.text_ctrl.Paste()
def handle_copy(self, event):
self.text_ctrl.Copy()
def handle_cut(self, event):
self.text_ctrl.Cut()
def handle_undo(self, event):
self.text_ctrl.Undo()
def handle_redo(self, event):
self.text_ctrl.Redo()
class TopFrame(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
self.Freeze()
sizer = wx.BoxSizer(wx.VERTICAL)
rt = wx.richtext.RichTextCtrl(self, -1)
toolbar = TextFormatToolBar(self, text_ctrl=rt)
rt.SetMinSize((300,200))
htmlwindow = wx.html.HtmlWindow(self)
htmlwindow.SetMinSize((300,200))
save_button = wx.Button(self, label="Save")
load_button = wx.Button(self, label="Load")
btn_sizer = wx.BoxSizer(wx.HORIZONTAL)
btn_sizer.Add(load_button, 0, wx.EXPAND|wx.ALL, 6)
btn_sizer.Add(save_button, 0, wx.EXPAND|wx.ALL, 6)
self.Bind(wx.EVT_BUTTON, self.handle_save, save_button)
self.Bind(wx.EVT_BUTTON, self.handle_load, load_button)
sizer.Add(toolbar, 0, wx.EXPAND|wx.ALL, 6)
sizer.Add(rt, 1, wx.EXPAND|wx.ALL, 6)
sizer.Add(htmlwindow, 1, wx.EXPAND|wx.ALL, 6)
sizer.Add(btn_sizer)
self.SetSizer(sizer)
sizer.Fit(self)
self.Thaw()
self.rt = rt
self.htmlwindow = htmlwindow
def handle_save(self, event):
out = StringIO()
handler = wx.richtext.RichTextXMLHandler()
buffer = self.rt.GetBuffer()
handler.SaveStream(buffer, out)
out.seek(0)
self.content = out.read()
def handle_load(self, event):
out = StringIO()
handler = wx.richtext.RichTextXMLHandler()
buffer = self.rt.GetBuffer()
buffer.AddHandler(handler)
out.write(self.content)
out.seek(0)
handler.LoadStream(buffer, out)
self.rt.Refresh()
cio = StringIO()
cio.write(self.content)
cio.seek(0)
cout = StringIO()
xmlhandler = wx.richtext.RichTextXMLHandler()
htmlhandler = wx.richtext.RichTextHTMLHandler()
newbuff = wx.richtext.RichTextBuffer()
newbuff.AddHandler(htmlhandler)
xmlhandler.LoadStream(newbuff, cio) #load xml into buffer
newbuff.SaveStream(cout, wx.richtext.RICHTEXT_TYPE_HTML)
cout.seek(0)
self.htmlwindow.SetPage(cout.read())
class MyApp(wx.App):
def OnInit(self):
wx.InitAllImageHandlers()
frame = TopFrame(None, - 1, "")
self.SetTopWindow(frame)
frame.Show()
return 1
def start():
app = MyApp(0)
app.MainLoop()
if __name__ == "__main__":
start()
2008-09-15
Stupid Internet Explorer and stupid IE bugs
I'm placing the finishing touches on a web application I've been writing on and off for the last year. At last, I've reached Internet Explorer bugs, which are usually the last to be fixed. Among several other stupid but documented bugs, I've found one which I couldn't find documented in a shallow search on Google: it seems IE has an algorithm for comparing strings that is different then the one that is used internally when sorting an array of strings.
Take this code, for example, where I have separated the issue:
<html>
<body>
<script>
var x = [
"Zwischenwasse",
"Gurtis",
"Götzis",
"Partenen",
"Raggal",
"Rietz",
"Schnifis",
"Vösendorf",
"Bludenz",
"Galtür"
];
var y = [
[34241, "Zwischenwasse"],
[11223, "Gurtis"],
[12321, "Götzis"],
[12345, "Partenen"],
[32454, "Raggal"],
[34355, "Rietz"],
[43453, "Schnifis"],
[42321, "Vösendorf"],
[43435, "Bludenz"],
[43222, "Galtür"]
];
x.sort();
document.write(x);
document.write("<br/>");
var sorted = y.sort(function(a,b){
return a[1] > b[1];
});
for (var i=0;i<sorted.length;i++) {
document.write(sorted[i][1]+ ",");
}
</script>
</html>
The value which will be written in Internet Explorer when running this html document is:
Bludenz,Galtür,Gurtis,Götzis,Partenen,Raggal,Rietz,Schnifis,Vösendorf,Zwischenwasse Bludenz,Galtür,Zwischenwasse,Gurtis,Götzis,Partenen,Raggal,Rietz,Schnifis,Vösendorf,
Needless to say, in Firefox both lines will be the same, displayed in the proper order. On a side note, while I'm bashing Internet Explorer, let me just say that Internet Explorer 8 is a broken piece of software, at least on the side that I'm concerned. The developer tools are really buggy and tend to block the browser for even trivial operations (that is, when it doesn't crash it completely).
Update: apparently my mind is clouded with too much Python (I admit that Javascript is not a language that I use often). The problem is that the inline function used as sorting discriminator should return numeric values of -1, 0, 1, like this:
var sorted = y.sort(function(a,b){
if (a[1] > b[1]){
return 1;
}
if (a[1] < b[1]){
return -1
}
return 0;
});
2008-09-14
Workingenv, setuptools and svn 1.5 redux
I'm back to dealing with the incompatibility between setuptools and svn 1.5 which I have installed. Last time I've solved the problem by doing a svn checkout of http://svn.python.org/projects/sandbox/branches/setuptools-0.6/ and running sudo python setup.py install. Now I'm trying to do setup a virtualenv to play with repoze.catalog and I'm hitting the same problem. Of course, virtualenv comes with a hardcoded setuptools package and so it will setup a "broken" setuptools in the virtual environment. The solution is of course the same, adapted to use the virtualenv python script:
virtualenv myenv cd myenv svn co http://svn.python.org/projects/sandbox/branches/setuptools-0.6/ setuptools cd setuptools ../bin/python setup.py install
This will upgrade the easy_install script to use the new setuptools library
2008-08-22
Bug in PyPi
The zope.app.form PyPi page looks awful, it should be fixed. Who's fault is that? Django, the framework that sits underneath (AFAIK), or the docutils libraries that probably parse the RST pages?
2008-07-11
Hosting Plone and Zope 3 applications using nginx
I'm doing a setup on a new server, I've decided to replace the default Apache 2.2 with an nginx http server. The setup which is needed for Zope 3 and Plone applications is the following:
[buildout]
parts =
nginx
nginxctl
[nginx]
recipe = gocept.cmmi
url = http://sysoev.ru/nginx/nginx-0.7.6.tar.gz
md5sum = ae7ce6f66a2cf5a5970d9a9a0da0cf7d
[nginxctl]
recipe = gocept.nginx
hostname = localhost
port = 80
configuration =
worker_processes 1;
events {
worker_connections 1024;
}
http {
upstream z3 {
server 127.0.0.1:8080;
}
upstream plone {
server 127.0.0.1:9080;
}
server {
listen ${nginxctl:port};
server_name z3.example.org;
root html;
include /etc/nginx/proxy.conf
location / {
proxy_pass http://z3/++lang++ro/++skin++myskin/mysite/++vh++http:z3.example.org:80/++/;
}
}
server {
server_name plone.example.org;
include /etc/nginx/proxy.conf
location / {
proxy_pass http://plone/VirtualHostBase/http/plone.example.org:80/t1/VirtualHostRoot/;
}
}
server {
server_name plone.example.org;
rewrite ^/(.*) /VirtualHostBase/http/plone.example.org:80/t1/VirtualHostRoot/$1 last;
location / {
proxy_pass http://plone;
}
}
}
Note: this is a buildout.cfg. Using it together with zc.buildout makes the nginx instalation a very simple process: install zc.buildout (easy_install zc.buildout), and then run buildout in the folder that contains the .cfg file.
The settings in proxy.conf are important. Without a valid proxy_temp_path, for some reason delivery of all content that came from a Plone 2.5 site that used CacheFu setup with no proxy cache was freezing at 16014 bytes. The paths in /var/nginx need to be created and set to be writable by the nginx process (user nobody in my case).
client_max_body_size 0; client_body_buffer_size 128k; client_body_temp_path /var/nginx/client_body_temp; proxy_connect_timeout 90; proxy_send_timeout 90; proxy_read_timeout 90; proxy_buffer_size 4k; proxy_buffers 4 32k; proxy_busy_buffers_size 64k; proxy_temp_file_write_size 64k; proxy_temp_path /var/nginx/proxy_temp; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
Resources
A more complete nginx sample configuration file (but that only covers how to configure Plone)
Grok guide on hosting Zope 3 with nginx (note, at this moment the document is wrong, the setup line is missing a slash at the end).
2008-06-01
Using views as information mixins in templates
This may be basic trick for some, a non-obvious usage of views for others, who knows, I'm documenting it here anyway. I've been using this technique for quite some time without giving it much thought.
There are times when I have an object in a template. I want to display information associated with that object. This information is already coded in a @@detail view on this object. Suppose this example (in mostly pseudocode):
class PersonDetail(BrowserView):
"""Show detail about a person"""
def name(self):
return compute_somehow_name()
class CommentDetail(BrowserView):
"""Show details about a comment"""
Now we have the following template for the CommentDetail view:
<div tal:define='person comment/author; person_info nocall:person/@@detail'>
<a tal:attributes="href person/@@absolute_url" tal:content="person_info/name">The author</a>
</div>
Notice the nocall: keyword placed in from of the person/@@detail call. This ensures that the @@detail view is instantiated, but not called (so it is not rendered). This way we have access to the view class attributes, properly associated to the Person context.
2008-04-15
In case you're having problems installing ssl-for-setuptools...
I've stumbled on this error when trying to install a easyshop buildout:
Exception: No SSL support found
An error occured when trying to install ssl-for-setuptools 1.10.Look above this message for any errors thatwere output by easy_install.
While:
Installing instance.
Getting distribution for 'ssl-for-setuptools'.
Error: Couldn't install: ssl-for-setuptools 1.10
After scratching my head for a while, I've found the solution:
# apt-get install libssl-dev
2008-04-08
A tip on debugging Zope 2 with ZEO
I'm having some troubles with CacheFu, and I've resorted to deleting some objects from a debug prompt, which is very easy to get at if one runs the Zope under ZEO. Still, I couldn't delete the objects because I was getting Unauthorized errors. Luckily, there is #plone, from which I got the following tip:
<naro> from AccessControl.SecurityManagement import newSecurityManager
<naro> user = app.acl_users.getUser('admin')
<naro> newSecurityManager(None, user)
Thanks, naro!
Update: I realize now that I didn't write how to connect to ZEO:
#bin/instance debug
or
#bin/zopectl debug
This will connect the ZEO client and drop you in a python shell
2008-03-28
Ubuntu Hardy Heron: some things are bad
Note to self: always create a root account on Ubuntu.
I've updated my laptop to Kubuntu Hardy Heron, and while some things worked fine, there are a couple of stupid bugs that chained to make my life hard.
First of all, why did the Network Configuration applet in Settings Manager in KDE saw fit to delete the hostname of my localhost, tibi-laptop, from /etc/hosts? Now I can't run anything with sudo, as it imediately aborts with an error "No hostname tibi-laptop". Why can't my laptop find any Access Point if there wasn't one accessible at boot time? Why, when I've started Ubuntu in single mode, was I greeted with a dialog that asked me to select an option (continue normally, drop in root shell, fix X) but which didn't allow me to select anything (the keyboard wasn't properly recognized, even though that I have a perfectly regular keyboard on my laptop). Why does the reboot never finishes and maxes out the CPU?
The only way I could fix the hosts problem, while avoiding to hunt for some Linux cd, was to boot in Windows and install Ext2fsd and after that I was able to mount and change the /etc/hosts file.
These things are basic, they should just work. I'm aware that Hardy is beta right now and I'm using the KDE part of Hardy, but with Gutsy, (even though that applet always screwed up my network settings), I never had these problems. Shouldn't things go forward instead of backwards?
2008-03-19
Why do I use Zope 3?
I'm in the process of beginning a new project and I'm debating on what framework to use. Of course it will be Zope 3, but why do I use it. Well, it's sure something that has to do with these facts:
- it's open source, with a strong, mature community around it
- while it's still actively development, it has a stable API
- it's written in Python, one of the easiest and most powerful languages
- it's built around a component architecture, which means writing pluggable applications comes naturally
- solves the problem of publishing objects through the web
- everything is transaction based. You won't lose data with it, you won't get garbage data inside your database
- it has fast, configurable storages (ZODB, Relstorage)
- internationalization and localization is easy
- it's a library as much as it is an application server. When it's a library, it's very slim and as a full application server has a lot going on, including XMLRPC, ftp, webdav, sql connectivity, its own http server, etc.
- has a cool, extensible templating language (the Zope Page Templates)
- makes it extremely easy to create and store objects through ZODB
- has facilities to query for objects using indexed data, through zope.catalog and extensions
- writing a new catalog index is not an extremely complex task
- it has an event framework; no complex application should run without an event framework
- has advanced, flexible form libraries that can generate forms introspected from the models (zope.formlib, z3c.form)
- has advanced templating concepts and content placement, such as the pagelets and viewlets
- it's "enterprise ready": it's possible to load balance zope clients using ZEO or relstorage
- comes with extensible authentication and user sources
- promotes an extensible build system for applications (zc.buildout)
- follows python standards, for the most part: library packaged as independent eggs and while it's not built around WSGI, it has full support for it
- it can do document workflows (hurry.workflow)
- it has some very cool packages from the community:
- zc.table
- z3c.form
- Storm and sqlalchemy wrappers
- zc.resourcelibrary, z3c.resourceinclude
- z3c.pagelet
- lovely.remotetask
- grok
- gocept.registration
- z3c.traverser
- is very well documented
- it fits comfortably in my brain
2008-03-18
A few ATReferenceBrowserWidget tips
On a Plone 2.5 project I'm working I have a content type that has 3 reference association to another content type. ArchGenXML generated the fields with the same name, which means that in the interface there will be just one field, as they overwrite each other. To have them working I need to rename them, but how to do this from the model? Agx, at first glance, doesn't have support for this. While playing and even trying to change agx to support this use case, I've noticed that agx considers the "association end" as the one meaningful: all tagged values set to it are reflected in the generated case. So the solution to this problem is to set a "name" tagged value in the "end" of the direct associations relationships. Check the following screenshot from Poseidon to see what I mean:
Another issue, this time directly related to the ATReferenceBrowserIssue, is the startup directory support. The widget has support for defaults set in portal_properties/site_properties, a property called refwidget_startupdirectories. The wording of this property's description in the README left me, as a non-native English speaker, a bit clueless. After some digging and debugging, I found out that the format for the lines there is:
/pathA:/pathB
where pathA is a path relative to the Plone site root; when you call the reference browser widget for an object in this path, the second path, pathB is returned as a base for browsing for objects. Even after I have understood the thing about the two colon separated path, it didn't work for me because I was refering to paths with their absolute paths (based in the Zope root) instead of their paths relative to the Plone site.
2008-03-17
GHOP Plone skins overview
Plone has very few skins available from the community, when compared to just about anything. The skin incompatibilities that appeared between Plone versions 2.0/2.1/3.0 further deepen this problem. As a result of the 2007 Plone - GHOP there are some skins placed in the collective, but they're not visible anywhere (they're not published in the PSC, only some of them are available as eggs in PyPI, to get a glimpse of them you need to install them). These skins are placed under the plonetheme namespace, along with other skins, unrelated to GHOP.
To make them a bit more visible (and to serve as a personal reminder for their design), I'm showcasing them here. In reviewing them I've used Plone 3.1 (the trunk of the ploneout package). After each skin was installed and snapshotted, I've uninstalled it and installed the next one.
Click on each thumbnail to get to the full image.
Andreas 01
One of the nicer skin, it stands out through its center align design, different sized columns and its simplicity. I can see myself using this skin as a base for a skin customization.
BlueBlog
This skin, as its name implies, is better suited for a blog, through its tall, thin design. Not a programmers blog though, and that's too bad, as it's a good skin.
Essay
Nice skin, I especially like the portlet design. Somewhat simple, though. If I'd customize this skin, I'd make it wider (maybe make it whole page instead of center-centred)
Green Community
A non-conformist skin, but still good looking. From what I have seen watching the PyPI, it's still under active development. There are some problems with it, though: the login portlet at the top is not fully visible, the "editor" version is missing some bits of the full skin. Still, it could be used for blog-ish type of sites, after some customizations.
Grok
This skin is very similar (might be the same) to the one used for the Grok project website. Probably not a GHOP skin. Although it seems like a very simple customization of default Plone, there are many issues with it that would mean that it would require heavy work to make it publishable for a real website.
Napoli
This is an almost empty skin, no point in showing it here
Pizza
Not a GHOP skin, this skin was created in a sprint. I believe its aim is to be one of the official supported skin. It is somewhat similar to NuPlone. Still needs work, though.
Plone2Kiss
Another empty skin
Python
Very similar to the current python.org skin. It feels rough and unfinished, but otherwise a nice, clean skin.
Relic
Ugly, amateurish skin. Needs a lot of work to get it into something usable. Probably you're better looking for something else.
Sonia
Not a GHOP, it just sits in the Collective svn repository. Promising, but would need a lot of customizations to make it work.
Conclusion: the problem remains: there are very few community-produced plone skins, and the ones available are usually low quality. We, the community, have got to do something about this.
2008-03-13
Tutorial: run ArchGenXML 2.0 under virtualenv
The "modern" (post 1.5) version of ArchGenXML is packaged as egg, available on pypi. While in theory you could run "sudo easy_install archgenxml" and have it running with minimal effort, because it depends on zope.component and zope.configuration, things tend to get muddy and complicated. If you'll "easy_install zope.component" you'll get a bunch of zope eggs installed in python's site-packages and this will probably cause problems. When I've started developing with Zope 3 I had some hard time tracking some problems that ultimately turned out to be caused by zope packages installed in the system python "conflicting" with my regular zope instances. Even the Plone installer prefers to create its own Python directory, to keep the its packages separated from other python packages installed in the system. To solve this problem, ArchGenXML has a mechanism to specify a path to the zope packages, of which I've already blogged about, but I now consider it an extra step which can be avoided by using virtualenv to install the archgenxml egg.
This following recipe is specific to Ubuntu, but probably adaptable to any Linux system, and even Windows. First, if you don't have easy_install in your system, install it with:
#sudo apt-get install python-setuptools
#easy_install virtualenv
In my ~/Software folder I've ran:
#virtualenv agx
This will create a ~/Software/agx virtual environment. We need to activate it:
#source agx/bin/activate
Now all python commands we will run (python, easy_install) will 'belong' to this virtual environment. We can safely install agx, zope.component and zope.configuration, as they will be installed there:
(agx)#easy_install archgenxml
(agx)#easy_install zope.component
(agx)#easy_install zope.configuration
The ArchGenXML egg has defined several console scripts, they're available as scripts "tied" to this virtual python environment, for example, this is the 'archgenxml' script that was installed in my ~/Software/agx/bin folder:
#!/home/tibi/tmp/arch/bin/python2.4
# EASY-INSTALL-ENTRY-SCRIPT: 'archgenxml==2.0-rc1','console_scripts','archgenxml'
__requires__ = 'archgenxml==2.0-rc1'
import sys
from pkg_resources import load_entry_point
sys.exit(
load_entry_point('archgenxml==2.0-rc1', 'console_scripts', 'archgenxml')()
)
What this tells us is that you can safely run this script from "outside" the virtual environment, without needing to first "source bin/activate" it.
2008-01-31
The 763 projects in the Collective SVN
Today I was curious about the number of projects found in the collective svn. They always seemed a lot, but I never knew how many. Until today, when I ran a xpath query over the subversion page and I found that there are 763! This number shocked me, I was expecting at most 200. My number of projects there is very small though, I can only claim ownership over 2 of them.
2008-01-28
Monty Python sketches and download script
A friend pointed me to a page with many many links to Monty Python sketches videos.
Being a Monty Python fan, I've countered with this script:
#!/usr/bin/python2.4
downloader = "/home/tibi/Software/youtube-dl.py '%s' -o '%s'"
url = "http://onemansblog.com/2006/12/01/a-compendium-of-150-monty-python-sketches/"
import lxml.html
import urllib
import os
content = urllib.urlopen(url)
etree = lxml.html.parse(content)
links = etree.xpath('//ol/li/a')
for link in links:
print "Downloading ", link.text
cmd = downloader % (link.get('href'), link.text + '.flv')
print os.popen(cmd).read()
It needs the YouTube downloader script and lxml 2.0.
2008-01-27
Problem with jQuery version 1.2.2 and getJSON
It took me a couple of hours to track this (I was just starting a new project using jQuery), but I finally nailed it: jQuery version 1.2.2 has a problem with loading JSON data from absolute urls. Use the trunk version and you'll be fine.
