<?xml version="1.0" encoding="UTF-8"?>
<!-- generator="wordpress/wordpress-mu-1.2.5" -->
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
        xmlns:opencore="http://www.openplans.org/opencore"
	>

<channel>
	<title>Topp Engineering</title>
	<link>http://www.coactivate.org/projects/topp-engineering/blog</link>
	<description>Just another  weblog</description>
	<pubDate>Tue, 03 Nov 2009 16:20:25 +0000</pubDate>
	<generator>http://wordpress.org/?v=wordpress-mu-1.2.5</generator>
	<language>en</language>
			<item>
		<title>topp and django</title>
		<link>http://www.coactivate.org/projects/topp-engineering/blog/2009/11/03/topp-and-django/</link>
		<comments>http://www.coactivate.org/projects/topp-engineering/blog/2009/11/03/topp-and-django/#comments</comments>
        	<slash:comments>5</slash:comments> 
		<pubDate>Tue, 03 Nov 2009 16:20:25 +0000</pubDate>
		<dc:creator>Robert Marianski</dc:creator>
                <opencore:userid>rmarianski</opencore:userid>
		
		<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://www.coactivate.org/projects/topp-engineering/blog/2009/11/03/topp-and-django/</guid>
		<description><![CDATA[We have expertise in a number of different frameworks, but one that hasn&#8217;t caught on much historically at topp has been django. I can think of a variety of reasons for this, but despite these, I think taking a fresh look at django is worthwhile for topp.
Django comes with a lot of batteries included. A [...]]]></description>
			<content:encoded xml:base="http://www.coactivate.org/projects/topp-engineering/blog/2009/11/03/topp-and-django/"><![CDATA[<p>We have expertise in a number of different frameworks, but one that hasn&#8217;t caught on much historically at topp has been django. I can think of a variety of reasons for this, but despite these, I think taking a fresh look at django is worthwhile for topp.</p>
<p>Django comes with a lot of batteries included. A lot of core decisions have been made about how things interact and connect. What you gain for buying into those decisions is a whole lot of functionality. These core decisions allow many higher level pieces to be written, which adds up to a whole lot of functionality quickly.</p>
<p>On top of this, <a isempty="function () { var base = {}; if (this == base) { return true; } for (var p in this) { if (this[p] &amp;&amp; (this[p].constructor != String &amp;&amp; this[p].constructor != Array &amp;&amp; this[p].constructor != Object || !this[p].isEmpty())) { var inBase = false; for (var x in base) { if (p == x &amp;&amp; this[p] == base[x]) { inBase = true; break; } } if (inBase) { continue; } return false; } } return true; }" href="http://pinaxproject.com/">pinax</a> has been gaining more momentum as well. It adds another layer on top of the already rich django core, which provides many of the features that typical web sites demand. There&#8217;s a good introductory talk to pinax here: <a isempty="function () { var base = {}; if (this == base) { return true; } for (var p in this) { if (this[p] &amp;&amp; (this[p].constructor != String &amp;&amp; this[p].constructor != Array &amp;&amp; this[p].constructor != Object || !this[p].isEmpty())) { var inBase = false; for (var x in base) { if (p == x &amp;&amp; this[p] == base[x]) { inBase = true; break; } } if (inBase) { continue; } return false; } } return true; }" href="http://blip.tv/file/1952623">http://blip.tv/file/1952623</a></p>
<p>I gave pinax an honest shot, and there were a couple of things that impressed me about it. First of all, the build worked consistently, and so did all of the project templates that I&#8217;ve tried, probably because they&#8217;re based on pip and virtualenv <img src='http://www.coactivate.org/projects/topp-engineering/blog/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> Secondly, any kinds of trivial modifications that I attempted, were in fact trivial to do. It almost felt like a cms to me, but if I wanted to change some text, style, functionality, you name it, it was pretty easy to do so without having to understand any complex systems.</p>
<p>In the talk linked to above, it&#8217;s mentioned that one of the reasons for pinax&#8217;s good integration story is that most cms&#8217;s typically have an underlying system for everything, and then you write your own hooks to plug into that system. Pinax inherits the core underlying system from django, but instead of building up its own system, it flips the perspective around and tries to provide all the pieces that are needed, and controlling how those pieces connect is application specific. In the end, most of the common changes are intuitive.</p>
<p>Specifically to topp, I think django/pinax is important for a number of reasons:<br />
<br />1. It&#8217;s popular, and it looks like that popularity is growing. If we want to build tools the masses will use, we should work with the masses, instead of trying to convince them otherwise.<br />
<br />2. It has a strong geo component. While it&#8217;s certainly possible with other frameworks and back-ends, django has very good integration with its orm geo-wise.<br />
<br />3. Not only are the batteries included, it comes with several flash lights to choose from.</p>
<p>It looks like we&#8217;re moving in the direction of creating more prototype style web apps. With many of these apps, we try to focus on what makes it unique, and don&#8217;t really care much about the other mundane details. Interestingly enough, this is in fact pinax&#8217;s tagline.</p>
<p>To sum up, topp is still imho a light, flexible organization capable of changing directions quickly. While we figure out the answers to some of our tougher internal questions, we may very well need to move quickly, in a hurry. Taking advantage of django/pinax can be one way for us to do so.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.coactivate.org/projects/topp-engineering/blog/2009/11/03/topp-and-django/feed/</wfw:commentRss>
		</item>
		<item>
		<title></title>
		<link>http://www.coactivate.org/projects/topp-engineering/blog/2009/09/28/32/</link>
		<comments>http://www.coactivate.org/projects/topp-engineering/blog/2009/09/28/32/#comments</comments>
        	<slash:comments>2</slash:comments> 
		<pubDate>Mon, 28 Sep 2009 15:55:01 +0000</pubDate>
		<dc:creator>k0s</dc:creator>
                <opencore:userid>k0s</opencore:userid>
		
		<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://www.coactivate.org/projects/topp-engineering/blog/2009/09/28/32/</guid>
		<description><![CDATA[
= LDAP and Trac =
In desperation, since the problem-space is too vast to understand and
I am making no forward progress, I am taking to writing to hopefully
at least record my efforts.
My mission is to get Trac + AccountManager to authenticate using
LDAP.  I know people do this, so&#8230;.how hard can it be?  Well, its
probably [...]]]></description>
			<content:encoded xml:base="http://www.coactivate.org/projects/topp-engineering/blog/2009/09/28/32/"><![CDATA[<div class="document">
<p>= LDAP and Trac =</p>
<p>In desperation, since the problem-space is too vast to understand and<br />
I am making no forward progress, I am taking to writing to hopefully<br />
at least record my efforts.</p>
<p>My mission is to get Trac + AccountManager to authenticate using<br />
LDAP.  I know people do this, so&#8230;.how hard can it be?  Well, its<br />
probably one of the vast majority of things that is easy if you know<br />
what you&#8217;re doing and very hard if you don&#8217;t.  I don&#8217;t know what I&#8217;m<br />
doing, so I&#8217;ve devoted several hours to this so far.</p>
<p>So I go to the AccountManager wiki page and look at the section on<br />
LDAP ( <a class="reference" href="http://trac-hacks.org/wiki/AccountManagerPlugin#LDAP">http://trac-hacks.org/wiki/AccountManagerPlugin#LDAP</a> ).<br />
Its&#8230;well, after reading I know I&#8217;m going to be in for the ride:</p>
<p>&quot;&quot;&quot;<br />
Check LDAPAuthStore regarding how to link LdapPlugin to AccountManagerPlugin.<br />
&quot;&quot;&quot;</p>
<p>Well, it&#8217;d be cool if LDAPAuthStore linked to a wiki page&#8230;but it<br />
actually links to <a class="reference" href="http://trac-hacks.org/ticket/1147">http://trac-hacks.org/ticket/1147</a> , which is, as one<br />
might expect, a very long ticket.</p>
<p>Aside:  Don&#8217;t put documentation in tickets!  Its a horrible idea.<br />
Documentation == something humans can read that lets them know how<br />
things work.  Tickets are conversations (like mailing lists and blog<br />
posts). If I wanted to learn about cars, I probably wouldn&#8217;t start by<br />
listening to Henry Ford&#8217;s conversations with Alexander Malcomson.</p>
<p>So I read the ticket, including the very helpful recollection of CC<br />
changes and all of that.  There is a python module &#8212; actually,<br />
thirteen modules and patches &#8212; attached to the ticket that is an LDAP<br />
authentication backend.  It also requires the LdapPlugin (more on that<br />
later).</p>
<p>So I read the ticket.  And I read again, trying to figure out what to<br />
do.  Well, one helpful tidbit:</p>
<p>&quot;&quot;&quot;<br />
Leave the apache setting same as after AccountManager is<br />
installed. Don&#8217;t follow LdapPlugin&#8217;s apache setting.</p>
<p>Follow LdapPlugin&#8217;s trac.ini setting. didn&#8217;t use its<br />
Permission/Groups part. It requires customize attributes<br />
(tracperm) to be added to the LDAP server schema.<br />
&quot;&quot;&quot;</p>
<p>Several hundred lines later, a stnank is documented<br />
(<a class="reference" href="http://trac-hacks.org/ticket/1147#comment:11">http://trac-hacks.org/ticket/1147#comment:11</a> and<br />
<a class="reference" href="http://trac-hacks.org/ticket/1147#comment:12">http://trac-hacks.org/ticket/1147#comment:12</a>) :</p>
<p>&quot;&quot;&quot;<br />
01/27/09 09:53:41 changed by hoffmann&#64;&#8230;</p>
<blockquote>
<ul class="simple">
<li>status changed from closed to reopened.</li>
<li>resolution deleted.</li>
</ul>
</blockquote>
<p>Replying to <a class="reference" href="mailto:iamer&#64;open-craft.com">iamer&#64;open-craft.com</a>:</p>
<blockquote><p>
It is working for me, can you please check your trac<br />
configuration, and try to describe the problem more clearly ? Also<br />
turn on debugging and see if there are any related messages<br />
there. I am not the original author of the patches, I just merged<br />
them and did a little modification.</p></blockquote>
<p>Same dor me, it is not working. I am getting ERROR: Skipping<br />
&quot;acct_mgr.ldap_store = acct_mgr.ldap_store&quot;: (can&#8217;t import &quot;No module<br />
named tracusermanager.api&quot;) inside my logfile. I am using trac 0.11.2<br />
Might that bew the problem?<br />
01/27/09 10:15:49 changed by anonymous ¶</p>
<blockquote>
<ul class="simple">
<li>status changed from reopened to closed.</li>
<li>resolution set to fixed.</li>
</ul>
</blockquote>
<p>Installing the UserManagerPlugin resolved the issue<br />
&quot;&quot;&quot;</p>
<p>Ah, so I need the UserManagerPlugin too.  Um, even though all I really<br />
want (right now, anyway) is authentication.  Hmm, well, good to know.</p>
<p>Way way way later on in the ticket (<br />
<a class="reference" href="http://trac-hacks.org/ticket/1147#comment:26">http://trac-hacks.org/ticket/1147#comment:26</a> ) mgood rises to the<br />
level of sanity (too bad his suggestion was never taken):</p>
<p>&quot;&quot;&quot;<br />
05/20/09 06:25:17 changed by mgood  ¶</p>
<blockquote>
<ul class="simple">
<li>status changed from reopened to closed.</li>
<li>type changed from defect to enhancement.</li>
<li>resolution set to wontfix.</li>
<li>summary changed from IndexError: list index out of range to Add</li>
<li>LDAP authentication backend.</li>
</ul>
</blockquote>
<p>Please create a separate plugin for this backend. I&#8217;d rather not add<br />
the extra dependencies that this requires, but it could benefit from<br />
being in version control and having its own issue list. It should make<br />
it easier if users can install that plugin rather than trying to keep<br />
track of the all these patches.<br />
&quot;&quot;&quot;</p>
<p>Yeah, that would be nice.  Too bad after that things proceeded as<br />
normal instead of discussing this further.  The last comment on the<br />
ticket ( <a class="reference" href="http://trac-hacks.org/ticket/1147#comment:32">http://trac-hacks.org/ticket/1147#comment:32</a> )is pretty telling too (reminds me of the boat I am in):</p>
<p>&quot;&quot;&quot;<br />
08/15/09 23:17:14 changed by rgrant&#64;&#8230;</p>
<p>Is there some concise list of tasks to perform on a new install of<br />
TRAC to get AccountManager working with LDAP? This forum seems to be<br />
focused on fixing bugs in existing installs.<br />
&quot;&quot;&quot;</p>
<p>In an attempt to get out of further spelunking, I followed a<br />
lead in a recent email to trac-users and looked at the<br />
TracLDAPPlugin.  This started off challenging and ended with no<br />
results.  Firstly, its not hosted on trac-hacks.org, so I had to<br />
resort to google to find it.  Turns out it is here:<br />
<a class="reference" href="http://pypi.python.org/pypi/TracLDAPAuth/">http://pypi.python.org/pypi/TracLDAPAuth/</a> . Interestingly, the pypi<br />
page says the homepage is on trac-hacks:<br />
<a class="reference" href="http://trac-hacks.org/wiki/LDAPAuthPlugin">http://trac-hacks.org/wiki/LDAPAuthPlugin</a> . But going to that page,<br />
the first line is &quot;I&#8217;m not the developer or maintainer of the<br />
LDAPAuthPlugin, and this is not a real reference&#8230;&quot;. Wonderful.  But<br />
looking at the plugin, it looks like exactly what I want.  Instead of<br />
being a UserManager tie in (which is more than I need, at least right<br />
now), the TracLDAPAuth plugin is only an IPasswordStore for<br />
AccountManager, which is exactly what I want.</p>
<p>So I install the plugin, which goes rather smoothly except installing<br />
python-ldap, which of course fails to compile the C extensions due to<br />
a missing .h file.  python, as a language designed for human beings<br />
rather than computers, should really solve this problem instead of<br />
just invoking a C compiler and running off into the blue, or if not<br />
&quot;solve&quot; at least &quot;make better&quot;.  Like a nice warning, &quot;Oh, you don&#8217;t<br />
have this needed .h file.  Well, you should get it here.&quot;  That would<br />
be friggin nice.  But I digress.  So I get bored and apt-get install<br />
python-ldap, which of course isn&#8217;t listed in a dependency of setup.py<br />
(and neither is AccountManager).  The installing the plugin works<br />
smoothly.  I fill out my [ldap] section (correctly, mind you, and yes,<br />
I&#8217;m sure because I tested this later) and enable the plugin (correctly<br />
again) and I go to login.  Nothing.  Denied.  So I look at the<br />
<cite>check_password</cite> code.  Its rather short:</p>
<blockquote>
<dl class="docutils">
<dt>def check_password(self, user, password):</dt>
<dd>
<p class="first">self.log.debug(&#8217;LDAPAuth: Checking password for user %s&#8217;,<br />
user)<br />
try:</p>
<blockquote><p>
l = ldap.open(self.server_host)<br />
bind_name = (self.bind_dn%user).encode(&#8217;utf8&#8242;)<br />
self.log.debug(&#8217;LDAPAuth: Using bind DN %s&#8217;, bind_name)<br />
l.simple_bind_s(bind_name, password.encode(&#8217;utf8&#8242;))<br />
return True</p></blockquote>
<dl class="last docutils">
<dt>except ldap.LDAPError:</dt>
<dd>return False</dd>
</dl>
</dd>
</dl>
</blockquote>
<p>Hell, short enough to debug by hand. So I do.  The following results:</p>
<pre class="doctest-block">
&gt;&gt;&gt; l.simple_bind_s(bind_name, password.encode('utf8'))
Traceback (most recent call last):
  File &quot;&lt;stdin&gt;&quot;, line 1, in ?
  File &quot;/usr/lib/python2.4/site-packages/ldap/ldapobject.py&quot;, line
  199, in simple_bind_s
    return self.result(msgid,all=1,timeout=self.timeout)
  File &quot;/usr/lib/python2.4/site-packages/ldap/ldapobject.py&quot;, line
  428, in result
    res_type,res_data,res_msgid = self.result2(msgid,all,timeout)
  File &quot;/usr/lib/python2.4/site-packages/ldap/ldapobject.py&quot;, line
  432, in result2
    res_type, res_data, res_msgid, srv_ctrls =
    self.result3(msgid,all,timeout)
  File &quot;/usr/lib/python2.4/site-packages/ldap/ldapobject.py&quot;, line
  438, in result3
    rtype, rdata, rmsgid, serverctrls =
    self._ldap_call(self._l.result3,msgid,all,timeout)
  File &quot;/usr/lib/python2.4/site-packages/ldap/ldapobject.py&quot;, line 97,
  in _ldap_call
    result = func(*args,**kwargs)
ldap.CONFIDENTIALITY_REQUIRED: {'info': 'TLS confidentiality
required', 'desc': 'Confidentiality required'}
</pre>
<p>Oh yeah, we have TLS, which the TracLDAPAuth plugin doesn&#8217;t<br />
enable. But since its not on trac-hacks, my first step is to get it<br />
there so I can ticket and/or make a vendor branch.  So I&#8217;ll play with<br />
TLS via the LDAP module.  I look for the docs and find them at<br />
<a class="reference" href="http://www.python-ldap.org/">http://www.python-ldap.org/</a> . The first question in the FAQ isn&#8217;t<br />
exactly promising:</p>
<p>Q: Is python-ldap yet another abandon-ware project on SourceForge?</p>
<p>Yiy.  Well, let&#8217;s ignore that for now and look at the API.  There is<br />
an ill-documented <cite>start_tls_s</cite> function that looks like what I want:<br />
<a class="reference" href="http://www.python-ldap.org/doc/html/ldap.html#ldap.LDAPObject.start_tls_s">http://www.python-ldap.org/doc/html/ldap.html#ldap.LDAPObject.start_tls_s</a><br />
. Let&#8217;s try it:</p>
<pre class="doctest-block">
&gt;&gt;&gt; l.start_tls_s()
Traceback (most recent call last):
  File &quot;&lt;stdin&gt;&quot;, line 1, in ?
  File &quot;/usr/lib/python2.4/site-packages/ldap/ldapobject.py&quot;, line
  528, in start_tls_s
    return self._ldap_call(self._l.start_tls_s)
  File &quot;/usr/lib/python2.4/site-packages/ldap/ldapobject.py&quot;, line 97,
  in _ldap_call
    result = func(*args,**kwargs)
ldap.CONNECT_ERROR: {'desc': 'Connect error'}
</pre>
<p>Hmm, well, that&#8217;s awful.  But I try it on my server and it magically<br />
works!</p>
<p>Well, what&#8217;s the difference?  Running strace, <cite>strace python -c<br />
&#8216;import ldap; l = ldap.open(&quot;ldap-master.openplans.org&quot;);<br />
l.start_tls_s()&#8217;</cite> yields the difference that on the server there is an<br />
open call that reads from <cite>/etc/openldap.ldap.conf</cite> and on my box it<br />
reads from <cite>/etc/ldap/ldap.conf</cite>.  It does not try the other file in<br />
either case, and no other relevent configuration (that I can tell,<br />
anyway) is read after the python is loaded.  I installed all of the<br />
openldap packages and still on my machine it tries to read from<br />
<cite>/etc/ldap/ldap.conf</cite>.</p>
<p>I mailed the author of <cite>TracLDAPAuth</cite> and requested his permission to<br />
put it on trac-hacks so that I may more sanely tackle this issue.  I<br />
also asked the mailing list (link not sited as google groups fails to<br />
put them in the email messages) what plugin I should use for LDAP.  I<br />
was nudged towards <a class="reference" href="http://trac-hacks.org/ticket/1147">http://trac-hacks.org/ticket/1147</a> .  So I decided<br />
to bark down that road again.</p>
<p>I follow mgood&#8217;s suggestion and make a real host-to-live plugin out of<br />
it and install and enabled for my Trac instance.  I spent several<br />
hours trying to get this to work with my LDAP server.  No go.  Not<br />
sure if the plugin is fuxored or if the LdapPlugin is fuxored or if I<br />
just don&#8217;t know enough about Ldap to configure it.  But that this<br />
point, I had wasted three days on the problem and was tired of it.  I<br />
did do something nice and put my plugin on<br />
<a class="reference" href="http://trac-hacks.org/wiki/LdapAuthStorePlugin">http://trac-hacks.org/wiki/LdapAuthStorePlugin</a> so that tickets could<br />
be filed against it, someone could adopt it, it could be versioned,<br />
and all of those nice things associated with sane software<br />
development.  If you want it, just mail me and its yours.</p>
<p>So figuring that <a class="reference" href="http://pypi.python.org/pypi/TracLDAPAuth/">http://pypi.python.org/pypi/TracLDAPAuth/</a> seemed to<br />
magically work on the servers&#8230;wait, an aside. When things magically<br />
work &#8212; that is, they work and I don&#8217;t understand why, or in this case<br />
they work on our PRODUCTION SERVERS and not on my development and<br />
testing box, I get really worried.  Quick + dirty says, &quot;It works,<br />
don&#8217;t worry about it.&quot;  But I&#8217;ve been doing this long enough to know<br />
that a tiger lurks in that closet.  Meaning:  lots more hours when<br />
things stop magically working, possible unknown side-effects, possible<br />
security problems (I mean, we <em>are</em> dealing with LDAP here&#8230;like, you<br />
know, that thing we keep our passwords in.  Let&#8217;s be careful, shall<br />
we?), etc.  Well, back to topic, I could rant all day.</p>
<p>So figuring that <a class="reference" href="http://pypi.python.org/pypi/TracLDAPAuth/">http://pypi.python.org/pypi/TracLDAPAuth/</a> seemed to<br />
magically work on the servers, I decided to put this on trac-hacks and<br />
hopefully the author wouldn&#8217;t complain too much and then add the two<br />
lines necessary to support TLS and other fixes that might be<br />
necessary.  So I did: <a class="reference" href="http://trac-hacks.org/wiki/TracLdapAuthPlugin">http://trac-hacks.org/wiki/TracLdapAuthPlugin</a><br />
. And I emailed the author telling him what I did.  I hope he&#8217;ll take<br />
ownership or at least won&#8217;t mind.  I try to add <cite>python-ldap</cite><br />
(remember that messy thing?) to <cite>install_requires</cite> in the setup.py<br />
file.  Well, that didn&#8217;t work so well, so I had to add the following<br />
code to the top of the file:</p>
<dl class="docutils">
<dt>try:</dt>
<dd>import ldap</dd>
<dt>except ImportError:</dt>
<dd>print &quot;&quot;&quot;The python-ldap package isn&#8217;t installed on your system</dd>
<dt>(<cite>import ldap</cite> failed).  I would just put this in <cite>install_requires</cite>,</dt>
<dd>but</dd>
</dl>
<p>I <em>do</em> have python-ldap on my system and I get this:</p>
<p>{{{<br />
Processing dependencies for TracLDAPAuth==1.0<br />
Searching for python-ldap==2.3.1<br />
Reading <a class="reference" href="http://pypi.python.org/simple/python-ldap/">http://pypi.python.org/simple/python-ldap/</a><br />
Reading <a class="reference" href="http://python-ldap.sourceforge.net/">http://python-ldap.sourceforge.net/</a><br />
Reading<br />
<a class="reference" href="http://sourceforge.net/project/showfiles.php?group_id=2072&amp;package_id=2011">http://sourceforge.net/project/showfiles.php?group_id=2072&amp;package_id=2011</a><br />
No local packages or download links found for python-ldap==2.3.1<br />
error: Could not find suitable distribution for<br />
Requirement.parse(&#8217;python-ldap==2.3.1&#8242;)<br />
}}}</p>
<p>Well, that&#8217;s awful.  If you know how to fix this, please file a ticket<br />
at<br />
<a class="reference" href="http://trac-hacks.org/newticket?component=TracLdapAuthPlugin&amp;owner=k0s">http://trac-hacks.org/newticket?component=TracLdapAuthPlugin&amp;owner=k0s</a><br />
&quot;&quot;&quot;</p>
<div class="system-message">
<p class="system-message-title">System Message: ERROR/3 (<tt class="docutils">trac-ldap.txt</tt>, line 285)</p>
<p>Unexpected indentation.</p></div>
<blockquote><p>
sys.exit(1)</p></blockquote>
<p>Well, such is the state of python packaging (FIX IT! FIX IT! FIX<br />
IT!). And I added the two lines that will add TLS (<br />
<a class="reference" href="http://trac-hacks.org/changeset/6606">http://trac-hacks.org/changeset/6606</a> ).  So I&#8217;m going to mail TOPP<br />
Labs Operations just to make sure we want to test and develop<br />
code directly related to our security.</p>
<p>Looking at <a class="reference" href="http://trac-hacks.org/tags/'ldap">http://trac-hacks.org/tags/&#8217;ldap</a>&#8216; , there are at least a<br />
few other related resources in this twisted web:</p>
<blockquote>
<ul class="simple">
<li><a class="reference" href="http://trac-hacks.org/ticket/1600">http://trac-hacks.org/ticket/1600</a> : a patch for AccountManager that<br />
also claims to provide LDAP authentication.  Haven&#8217;t looked at it</li>
<li><a class="reference" href="http://trac-hacks.org/wiki/AccountLdapPlugin">http://trac-hacks.org/wiki/AccountLdapPlugin</a> : &quot;Allows you to<br />
change your password defined in LDAP. Also moved the basic<br />
properties of LDAP (user and mail) to the corresponding properties<br />
in Trac&quot;.  So it doesn&#8217;t claim to do auth, but it is heavily<br />
overlapping with the above patches and plugins that do</li>
<li>LdapPlugin : &quot;LDAP support with group management has been added as<br />
a Trac extension. This extension enables to use existing LDAP<br />
groups to grant permissions rather than defining permissions for<br />
every single user on the system.&quot; So again no support for auth, but<br />
other plugins depend on it.  Ho hum.  In my opinion, auth should go<br />
with this plugin.  If people <em>don&#8217;t</em> want to use the auth<br />
component, then don&#8217;t enable it.</li>
<li><a class="reference" href="http://trac-hacks.org/wiki/TracSysgroupsPlugin">http://trac-hacks.org/wiki/TracSysgroupsPlugin</a> : &quot;A permission<br />
system-group provider for Trac. It asks linux / unix system to<br />
which groups a validated user belongs. If one of this groups<br />
matches a permission group name created with trac-admin permission<br />
add command, these permissions are enabled for logged-on user.&quot;<br />
So I&#8217;m not sure why its tagged with &quot;ldap&quot;, save for the loose<br />
association that unix can use LDAP for authentication and groups.</li>
</ul>
</blockquote>
<p>I also find it &quot;funny&quot; that AccountManager is tagged with &quot;ldap&quot; even<br />
though it doesn&#8217;t support it OOTB.</p>
<p>So concludes my foray in LDAP + Trac.  I&#8217;m still confused, but maybe,<br />
just maybe you&#8217;ll be less confused than you were before upon reading this.</p>
</div>
]]></content:encoded>
			<wfw:commentRss>http://www.coactivate.org/projects/topp-engineering/blog/2009/09/28/32/feed/</wfw:commentRss>
		</item>
		<item>
		<title>repoze.bfg sprint</title>
		<link>http://www.coactivate.org/projects/topp-engineering/blog/2009/08/25/repozebfg-sprint/</link>
		<comments>http://www.coactivate.org/projects/topp-engineering/blog/2009/08/25/repozebfg-sprint/#comments</comments>
        	<slash:comments>0</slash:comments> 
		<pubDate>Tue, 25 Aug 2009 17:07:35 +0000</pubDate>
		<dc:creator>Robert Marianski</dc:creator>
                <opencore:userid>rmarianski</opencore:userid>
		
		<category><![CDATA[sprint]]></category>

		<guid isPermaLink="false">http://www.coactivate.org/projects/topp-engineering/blog/2009/08/25/repozebfg-sprint/</guid>
		<description><![CDATA[I attended a great repoze.bfg sprint last weekend. We discussed creating a foundation on top of repoze.bfg that would allow for a complex content management system to be built. Tres wrote down the requirements of an example system here


  To summarize, in a typical large content management system, there are multiple sources of content. [...]]]></description>
			<content:encoded xml:base="http://www.coactivate.org/projects/topp-engineering/blog/2009/08/25/repozebfg-sprint/"><![CDATA[<p><img alt="rg-gray-sh.png" src="/projects/topp-engineering/project-home/rg-gray-sh.png" height="60" align="right" width="77" />I attended a great repoze.bfg sprint last weekend. We discussed creating a foundation on top of repoze.bfg that would allow for a complex content management system to be built. Tres wrote down the requirements of an example system <a isempty="function () { var base = {}; if (this == base) { return true; } for (var p in this) { if (this[p] &amp;&amp; (this[p].constructor != String &amp;&amp; this[p].constructor != Array &amp;&amp; this[p].constructor != Object || !this[p].isEmpty())) { var inBase = false; for (var x in base) { if (p == x &amp;&amp; this[p] == base[x]) { inBase = true; break; } } if (inBase) { continue; } return false; } } return true; }" href="http://lists.repoze.org/pipermail/cmf3k/2009-August/000096.html">here</a>
</p>
<p>
  <br />To summarize, in a typical large content management system, there are multiple sources of content. Some can come from rss feeds, others are authored, and still others are media on a filesystem. Frequently, the various types of content come from different departments, and they each have their own applications/workflows to work with their content.<br />
  <br /><img alt="cmf3k.png" src="/projects/topp-engineering/project-home/cmf3k.png" height="503" align="right" width="453" /><br />
  <br />We divided up the entire application into several sections: the content authoring environments, the content management system, and the retail, or client application.&nbsp; There can be several content authoring environments, which represent the mini applications that are geared towards a particular type of content. The content repositories would expose a thin interface that would interact with content through content_ids unique across the system. The content management system manages how the content appears on the retail site. This involves url structure, layout of the content, and who gets to see what. And the retail application itself is the end user front of the entire application.</p>
<p>The code that was produced can be currently found here: <a isempty="function () { var base = {}; if (this == base) { return true; } for (var p in this) { if (this[p] &amp;&amp; (this[p].constructor != String &amp;&amp; this[p].constructor != Array &amp;&amp; this[p].constructor != Object || !this[p].isEmpty())) { var inBase = false; for (var x in base) { if (p == x &amp;&amp; this[p] == base[x]) { inBase = true; break; } } if (inBase) { continue; } return false; } } return true; }" href="http://svn.repoze.org/plite/trunk/">http://svn.repoze.org/plite/trunk/ </a><br />
  <br />The name is not set in stone, so if it changes, then that link may break.</p>
<p>Creating a generic interface for a complex system is challenging. Going through interfaces that are too thin make client code more difficult, while making the interface too specific can preclude certain content repository implementations. We began creating the various pieces of the application in hopes that they would help drive certain interface decisions.&nbsp; When it was time for me to go, a basic content repository was created that would store files/pages. The content management/retail aspects were yet to be fleshed out, although they&#8217;re probably further along now.</p>
<p>All in all, I found that I got a great deal out of the sprint. I personally haven&#8217;t had to work with sites of this size/complexity, so it was very useful for me to hear the different opinions on design tradeoffs.</p>
<p>The repoze.bfg framework itself had gotten better in a number of ways since I last looked at it. If you haven&#8217;t already, it&#8217;s definitely worth checking out. The code itself is of extremely high quality, being very clean and organized. Additionally, it&#8217;s very well tested.</p>
<p>I look forward to seeing everyone at the next sprint!<br />
  </p>
]]></content:encoded>
			<wfw:commentRss>http://www.coactivate.org/projects/topp-engineering/blog/2009/08/25/repozebfg-sprint/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Using shapefiles for PostGIS query</title>
		<link>http://www.coactivate.org/projects/topp-engineering/blog/2009/08/21/using-shapefiles-for-postgis-query/</link>
		<comments>http://www.coactivate.org/projects/topp-engineering/blog/2009/08/21/using-shapefiles-for-postgis-query/#comments</comments>
        	<slash:comments>0</slash:comments> 
		<pubDate>Fri, 21 Aug 2009 16:47:53 +0000</pubDate>
		<dc:creator>k0s</dc:creator>
                <opencore:userid>k0s</opencore:userid>
		
		<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://www.coactivate.org/projects/topp-engineering/blog/2009/08/21/using-shapefiles-for-postgis-query/</guid>
		<description><![CDATA[ As part of the GeoTrac project ( http://projects.openplans.org/GeoTrac/ ), I set off to be able to query on tickets via shapefiles ( http://projects.openplans.org/GeoTrac/ticket/78 ). I didn&#8217;t know anything about shapefiles before beginning this, and I still don&#8217;t know much, except that I&#8217;m not a big fan. So I consulted the font of knowledge: http://en.wikipedia.org/wiki/Shapefile

Shapefiles [...]]]></description>
			<content:encoded xml:base="http://www.coactivate.org/projects/topp-engineering/blog/2009/08/21/using-shapefiles-for-postgis-query/"><![CDATA[<p> As part of the GeoTrac project ( http://projects.openplans.org/GeoTrac/ ), I set off to be able to query on tickets via shapefiles ( http://projects.openplans.org/GeoTrac/ticket/78 ). I didn&#8217;t know anything about shapefiles before beginning this, and I still don&#8217;t know much, except that I&#8217;m not a big fan. So I consulted the font of knowledge: http://en.wikipedia.org/wiki/Shapefile
</p>
<p>Shapefiles are several files that have different extensions but the same &#8220;first name&#8221;. Its the kind of convention someone would come up with if they were being &#8220;clever&#8221; (see http://www.statusq.org/archives/2006/11/03/1193/ ). Three files are mandatory:
</p>
<p>* .shp — shape format; the feature geometry itself
</p>
<p>* .shx — shape index format; a positional index of the feature geometry to allow seeking forwards and backwards quickly
</p>
<p>* .dbf — attribute format; columnar attributes for each shape, in dBase III format (from http://en.wikipedia.org/wiki/Shapefile )
</p>
<p>Unfortunately, these three files contain no projection information and so do not actually give you enough information to tell where the geometry is on a map. There is an optional .prj file * .prj — projection format; the coordinate system and projection information, a plain text file describing the projection using well-known text format (from http://en.wikipedia.org/wiki/Shapefile ). So while we didn&#8217;t have a .prj file to start off with or know the projection that we needed to figure out where the geometry lived on the map, this seemed the right approach to specifying geometry.
</p>
<p>We wrote a Trac (see http://trac.edgewall.org ) component for the GeoTicket plugin ( http://trac-hacks.org/wiki/GeoTicketPlugin ) that will support querying tickets based on region: http://trac-hacks.org/svn/geoticketplugin/0.11/geoticket/regions.py
</p>
<p>The import of the shapefile is done with shp2pgsql ( http://postgis.refractions.net/docs/ch04.html ). The resulting SQL is then read and used to create a table in the PostgreSQL database. The administrator of the GeoTrac site is then prompted which piece of metadata attached to the polygons should be used for query. The import was fairly easy to do. Next came the query itself. A SQL query was used to select the tickets within the region:
</p>
<pre>SELECT ticket FROM ticket_location WHERE ST_CONTAINS((SELECT the_geom FROM georegions WHERE commdist=212),
st_pointfromtext('POINT(' || longitude || ' ' || latitude || ')')); </pre>
<p>However, without specifying the SRIDs, this gives an error:
</p>
<pre>ERROR: Operation on two geometries with different SRIDs CONTEXT: SQL function "st_contains" statement 1 </pre>
<p>This may be corrected by providing the SRID for the ticket location point:
</p>
<pre>SELECT ticket FROM ticket_location WHERE ST_CONTAINS((SELECT the_geom FROM georegions WHERE commdist=212),
st_pointfromtext('POINT(' || longitude || ' ' || latitude || ')', 4326)); </pre>
<p>However, by default, the SRID of the_geom is -1. So this gives the same error, and the previous case silently &#8220;succeeds&#8221;, though of course the projection is wrong you won&#8217;t get any data back. You can see the SRID from the geometry_columns table:
</p>
<pre>trac_fleem=# select * from geometry_columns;
f_table_catalog | f_table_schema | f_table_name | f_geometry_column | coord_dimension | srid | type
----------------+----------------+--------------+-------------------+-----------------+------+-------------- |
                  public         | georegions   | the_geom          | 2               | -1   | MULTIPOLYGON
(1 row) </pre>
<p>As a makeshift solution, I&#8217;ve included a text input to specify the SRID:
</p>
<p>Import Shapefiles Upload Shapefiles Shapefile shape format (.shp) ____________________ Shapefile shape index format (.shx) ____________________ Shapefile attribute format (.dbf) ____________________ SRID ____________________ Submit
</p>
<p>So, given the SRID, you *should* be able to transform the_geom to the SRID you care about, using `shp2pgsql -s`. Maybe. I haven&#8217;t finished the query yet [TODO!].
</p>
<p>So the problem remains of how to get the SRID from the .prj file. After asking, we were given the .prj file:
</p>
<pre>PROJCS["NAD_1983_UTM_Zone_18N",GEOGCS["GCS_North_American_1983",DATUM["D_North_American_1983",SPHEROID["GRS_1980",6378137,298.257222101]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]],PROJECTION["Transverse_Mercator"],PARAMETER["False_Easting",500000],PARAMETER["False_Northing",0],PARAMETER["Central_Meridian",-75],PARAMETER["Scale_Factor",0.9996],PARAMETER["Latitude_Of_Origin",0],UNIT["Meter",1]] </pre>
<p>Ideally, in addition to allowing the GeoTrac administrator to specify the SRID (especially for the case where there is no .prj file), it would seem plausible to obtain the SRID from the .prj information.
</p>
<p>So I looked around and tried a bunch of things and was refered to a project called GDAL: http://gdal.org/ . Specifically, I was told that ogr2ogr would be useful for shapefile vector data ( see http://gdal.org/ogr2ogr.html ). GDAL has python bindings ( http://trac.osgeo.org/gdal/wiki/GdalOgrInPython ), but unfortunately, it is not easy_install-able:
</p>
<pre>(Trac-2.4)&gt; easy_install GDAL
Searching for GDAL
Reading http://pypi.python.org/simple/GDAL/
Reading http://www.gdal.org
Best match: GDAL 1.6.1
Downloading http://pypi.python.org/packages/source/G/GDAL/GDAL-1.6.1.tar.gz#md5=e8671d4a77041cf0f7a027f3f3e8280c
Processing GDAL-1.6.1.tar.gz
Running GDAL-1.6.1/setup.py -q bdist_egg --dist-dir /tmp/easy_install-jFd9bm/GDAL-1.6.1/egg-dist-tmp-MJllDq numpy include /home/jhammel/Trac-2.4/lib/python2.4/site-packages/numpy-1.3.0-py2.4-linux-i686.egg/numpy/core/include
Could not run gdal-config!!!!
gcc: error trying to exec 'cc1plus': execvp: No such file or directory
error: Setup script exited with error:
command 'gcc' failed with exit status 1 </pre>
<p>I haven&#8217;t been able to compile GDAL by hand, though it is available on modern package systems. So at best, requiring GDAL makes a component optional (see http://peak.telecommunity.com/DevCenter/setuptools#declaring-extras-optional-features-with-their-own-dependencies and the terribly apt http://www.reddit.com/r/Python/comments/91bnb/the_terrible_magic_of_setuptools/c0b3lhe ).
</p>
<p>So how do you use ogr? An example is given here: http://www.gis.usu.edu/~chrisg/python/2009/lectures/ospy_demo1.py
</p>
<p>So we try this on our own shapefile:
</p>
<pre>&gt;&gt;&gt; import ogr
&gt;&gt;&gt; driver = ogr.GetDriverByName('ESRI Shapefile')
&gt;&gt;&gt; ds = driver.Open("sen02.shp") </pre>
<p>Docstrings are not available for much of the python bindings:
</p>
<pre>class DataSource |
Methods defined here: | |
CopyLayer(self, src_layer, new_name, options=[]) | |
CreateLayer(self, name, srs=None, geom_type=0, options=[]) | |
DeleteLayer(self, iLayer) | |
Dereference(self) | |
 Destroy(self) | |
ExecuteSQL(self, statement, region='NULL', dialect='') | |
GetDriver(self) | Returns the driver of the datasource | |
GetLayer(self, iLayer=0) | Return the layer given an index or a name | |
GetLayerByName(self, name) | |
GetLayerCount(self) | Returns the number of layers on the datasource | |
GetName(self) | Returns the name of the datasource | |
GetRefCount(self) | |
GetSummaryRefCount(self) | |
Reference(self) | |
Release(self) | |
ReleaseResultSet(self, layer) | |
TestCapability(self, cap) |
Test the capabilities of the DataSource. |
See the constants at the top of ogr.py | |
__getitem__(self, value) | Support dictionary, list, and slice -like access to the datasource. |
ds[0] would return the first layer on the datasource. | ds['aname'] would return the layer named "aname".
| ds[0:4] would return a list of the first four layers. | |
__init__(self, obj=None) | | __len__(self) | Returns the number of layers on the datasource </pre>
<p>So it is hard to tell how to get the SRID.
</p>
<p>A manual solution is available at: http://help.nceas.ucsb.edu/PostGIS#Import_a_shapefile_into_a_PostGIS_database<br />
  <br />The key is the first string, NAD_1983_UTM_Zone_18N. If I do a query like the one in http://help.nceas.ucsb.edu/PostGIS#Import_a_shapefile_into_a_PostGIS_database I get a row:
</p>
<pre>trac_fleem=# select * from spatial_ref_sys where srtext like '%NAD83 / UTM zone 18N%';
srid   | auth_name | auth_srid | srtext                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | proj4text
-------+-----------+-----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------------------------------------
26918  | EPSG      | 26918     | PROJCS["NAD83 / UTM zone 18N",GEOGCS["NAD83",DATUM["North_American_Datum_1983",SPHEROID["GRS 1980",6378137,298.257222101,AUTHORITY["EPSG","7019"]],AUTHORITY["EPSG","6269"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4269"]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",-75],PARAMETER["scale_factor",0.9996],PARAMETER["false_easting",500000],PARAMETER["false_northing",0],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AUTHORITY["EPSG","26918"]] | +proj=utm +zone=18 +ellps=GRS80 +datum=NAD83 +units=m +no_defs
(1 row) </pre>
<p>In other words, I can tell, as a human being looking at the file, that the `WHERE srtext LIKE` clause should be transformed from the string &#8220;NAD_1983_UTM_Zone_18N&#8221; to the string %NAD83 / UTM zone 18N%&#8217;. But while you could tell a computer how to do this&#8230;.it would either be hard, or I don&#8217;t know how to do it (and couldn&#8217;t find any good documentation).
</p>
<p>One of the takeaways from this is that this is hard and it shouldn&#8217;t be. I don&#8217;t know enough about the Geo world to say exactly why this is. Maybe the .prj file should be able to support SRIDs. Maybe `shp2pgsql` should figure out the SRID from the .prj file. Maybe the string should be canonical so that select from spatial_ref_sys query could be more robust, assuming I figured out how to parse the .prj file.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.coactivate.org/projects/topp-engineering/blog/2009/08/21/using-shapefiles-for-postgis-query/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Breathe Life</title>
		<link>http://www.coactivate.org/projects/topp-engineering/blog/2009/06/26/breathe-life/</link>
		<comments>http://www.coactivate.org/projects/topp-engineering/blog/2009/06/26/breathe-life/#comments</comments>
        	<slash:comments>1</slash:comments> 
		<pubDate>Fri, 26 Jun 2009 22:20:50 +0000</pubDate>
		<dc:creator>Robert Marianski</dc:creator>
                <opencore:userid>rmarianski</opencore:userid>
		
		<category><![CDATA[best-practices]]></category>

		<guid isPermaLink="false">http://www.coactivate.org/projects/topp-engineering/blog/2009/06/26/breathe-life/</guid>
		<description><![CDATA[The problem

Javascript usually applies behavior to elements on page load. Usually, there&#8217;s a list or some sort of registry of behaviors, and it&#8217;s iterated through on document ready time, and applied then. For elements that get created by javascript though, behaviors don&#8217;t get automatically applied to them. The application of those behaviors happens only at [...]]]></description>
			<content:encoded xml:base="http://www.coactivate.org/projects/topp-engineering/blog/2009/06/26/breathe-life/"><![CDATA[<h2>The problem<br />
</h2>
<p>Javascript usually applies behavior to elements on page load. Usually, there&#8217;s a list or some sort of registry of behaviors, and it&#8217;s iterated through on document ready time, and applied then. For elements that get created by javascript though, behaviors don&#8217;t get automatically applied to them. The application of those behaviors happens only at document ready time, and new elements don&#8217;t get processed magically.
</p>
<h2>Opencore&#8217;s solution<br />
</h2>
<p>We&#8217;ve handled this in the past by explicitly calling a generic function that would apply the behaviors to elements. So when a new element was added, we would follow up the addition with an explicit &#8220;breatheLife&#8221; call. This worked, but we had to remember to call the breatheLife function every time we added elements that we wanted particular behaviors attached to.<br />
  
</p>
<h2>Jquery solution<br />
</h2>
<p>Jquery has a particular solution for this problem for a particular subset of behaviors: <a isempty="function () { var base = {}; if (this == base) { return true; } for (var p in this) { if (this[p] &amp;&amp; (this[p].constructor != String &amp;&amp; this[p].constructor != Array &amp;&amp; this[p].constructor != Object || !this[p].isEmpty())) { var inBase = false; for (var x in base) { if (p == x &amp;&amp; this[p] == base[x]) { inBase = true; break; } } if (inBase) { continue; } return false; } } return true; }" href="http://docs.jquery.com/Events/live">live events</a>. The solution however, is a little bit different from ours. Instead of applying behaviors to new elements as they are attached to the dom, jquery gets the event as it bubbles up, and then figures out if its coming from an element that has special behavior. This technique is called <a isempty="function () { var base = {}; if (this == base) { return true; } for (var p in this) { if (this[p] &amp;&amp; (this[p].constructor != String &amp;&amp; this[p].constructor != Array &amp;&amp; this[p].constructor != Object || !this[p].isEmpty())) { var inBase = false; for (var x in base) { if (p == x &amp;&amp; this[p] == base[x]) { inBase = true; break; } } if (inBase) { continue; } return false; } } return true; }" href="http://www.sitepoint.com/blogs/2008/07/23/javascript-event-delegation-is-easier-than-you-think/">event delegation</a>. Currently, not all event types are supported, but a common set are.
</p>
<h2>Other applications<br />
</h2>
<p>Using event delegation is also useful for situations when there are a lot of elements that need behavior applied to them. For example, if we had a huge table of links, or a large number of list items, we could add a handler to the table or unordered list, and figure out the target of the event from there. Instead of adding an event handler for each element, we can get away with only adding one that can handle all of the elements.<br />
  </p>
]]></content:encoded>
			<wfw:commentRss>http://www.coactivate.org/projects/topp-engineering/blog/2009/06/26/breathe-life/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Almanac update</title>
		<link>http://www.coactivate.org/projects/topp-engineering/blog/2009/06/26/almanac-update/</link>
		<comments>http://www.coactivate.org/projects/topp-engineering/blog/2009/06/26/almanac-update/#comments</comments>
        	<slash:comments>0</slash:comments> 
		<pubDate>Fri, 26 Jun 2009 21:44:54 +0000</pubDate>
		<dc:creator>Robert Marianski</dc:creator>
                <opencore:userid>rmarianski</opencore:userid>
		
		<category><![CDATA[best-practices]]></category>

		<category><![CDATA[programming]]></category>

		<category><![CDATA[ui]]></category>

		<guid isPermaLink="false">http://www.coactivate.org/projects/topp-engineering/blog/2009/06/26/almanac-update/</guid>
		<description><![CDATA[­Almanac ui

We&#8217;re nearing the end of another community almanac development cycle, where we changed the ui quite a bit. Here are some of my thoughts on it.

The operation that we wanted to optimize for was contributing a page to an almanac. We used to call them &#8220;stories&#8221; but we tried to stress the book/page metaphor [...]]]></description>
			<content:encoded xml:base="http://www.coactivate.org/projects/topp-engineering/blog/2009/06/26/almanac-update/"><![CDATA[<h2>­Almanac ui<br />
</h2>
<p>We&#8217;re nearing the end of another community almanac development cycle, where we changed the ui quite a bit. Here are some of my thoughts on it.
</p>
<p>The operation that we wanted to optimize for was contributing a page to an almanac. We used to call them &#8220;stories&#8221; but we tried to stress the book/page metaphor a little bit more now. We also tried to make sure that adding a simple page was very easy, while also providing flexibility for the user.
</p>
<p>Previously, we had taken a wizard-like approach for this case. A user was presented with a series of questions, taking them through the process of adding their story. This forced the user to go through the process of adding a story in a very strict way, and presented more questions to the user than was usually necessary, at least for the simple cases.
</p>
<h2>A different approach<br />
</h2>
<p>This go around, we took an approach that feels a bit like basecamp. Instead of providing a wizard, or series of questions that the user should enter, the user is presented with a set of tools that control the type of content that the user is contributing. This allows the user to add the type of data that they are interested in, without forcing the concept of &#8220;metadata&#8221; to the user.
</p>
<p>An advantage with this model is that it offers more flexibility for creating pages. Users can add the types of data that they want, in a way that makes the page flow more naturally. They can now have several locations, descriptions, or none at all. It also feels a lot more like what the site should be all about: adding pages to a community&#8217;s almanac.
</p>
<h2>Under the hood<br />
</h2>
<p>Implementation wise, this means that we have to be able to generate lots of forms on demand through javascript. There are a couple of ways to manage this, but we decided to have the server generate these forms, and the client side would issue ajax requests to send/receive all data. This keeps the javascript simple, with the server handling most of the logic.
</p>
<p>This is similar to how we handled opencore&#8217;s javascript. The client sends a request to the server (usually getting the url from an anchor or the form that the object is in), and knows how to handle a couple of different responses in a generic fashion. Usually the variations are simply to take some html returned from the server and put it somewhere, either by replacing an element, or adding it somewhere else on the page.
</p>
<p>There was an additional complication on the almanac side, which was that we needed to apply some side effects for the content to be displayed properly. This is sort of like the &#8220;breathe life&#8221; problem, except we needed it to simply display the content properly, as opposed to interacting with additional events. For example, when a map is returned, some javascript needs to run to display the map correctly, with the right features displayed on it. For audio playback, some flowplayer behavior needs to get applied to it before it can play in the browser through flash. We did also have to tackle adding behavior as well (so the forms that got returned where submitted through ajax), but we were able to use jquery&#8217;s <a isempty="function () { var base = {}; if (this == base) { return true; } for (var p in this) { if (this[p] &amp;&amp; (this[p].constructor != String &amp;&amp; this[p].constructor != Array &amp;&amp; this[p].constructor != Object || !this[p].isEmpty())) { var inBase = false; for (var x in base) { if (p == x &amp;&amp; this[p] == base[x]) { inBase = true; break; } } if (inBase) { continue; } return false; } } return true; }" href="http://docs.jquery.com/Events/live">live events</a> for a lot of it.
</p>
<h2>Always something<br />
</h2>
<p>Since we&#8217;re loading a lot of javascript on page loads as well, we ran into an issue where you could click on a link before it got its document.ready behavior applied to it. The user gets presented with a download dialog box, asking to download the json return value. We worked around this by having empty onclick handlers in the markup itself, which prevented the dialog boxes. Ideally we would have non-javascript fallbacks everywhere, but we didn&#8217;t focus on dealing with that issue because a large portion of the site deals with map interactions, which isn&#8217;t really feasible without javascript. You can say that you can have page loads for each click, like panning or zooming, but drawing features on a map without javascript is tough.
</p>
<h2>Thoughts for the future<br />
</h2>
<p>The similarities of the ajax approaches of both almanac and opencore got me thinking if these are both more specific instances of a general pattern. <a isempty="function () { var base = {}; if (this == base) { return true; } for (var p in this) { if (this[p] &amp;&amp; (this[p].constructor != String &amp;&amp; this[p].constructor != Array &amp;&amp; this[p].constructor != Object || !this[p].isEmpty())) { var inBase = false; for (var x in base) { if (p == x &amp;&amp; this[p] == base[x]) { inBase = true; break; } } if (inBase) { continue; } return false; } } return true; }" href="http://kssproject.org/">Kss</a> as a solution comes to mind. What worries me about kss however is that it seems focused on completely removing all javascript from the application. But on the other hand, it seems like the concept of applying js behavior to elements in the same kind of way as style is applied to elements is a reasonable approach. And kss is a way of standardizing the behaviors, in the same sort of way that styles standardize on the display. In fact, most of the javascript that I write involves selecting some elements, and applying a simple behavior to them. Behavior will probably vary a bit more than style, but I definitely prefer having a solution for most cases than no solution at all. In any case though, I definitely think kss is a cool concept.
</p>
<p>­</p>
]]></content:encoded>
			<wfw:commentRss>http://www.coactivate.org/projects/topp-engineering/blog/2009/06/26/almanac-update/feed/</wfw:commentRss>
		</item>
		<item>
		<title>pycon wsgi sprint</title>
		<link>http://www.coactivate.org/projects/topp-engineering/blog/2009/04/02/pycon-wsgi-sprint/</link>
		<comments>http://www.coactivate.org/projects/topp-engineering/blog/2009/04/02/pycon-wsgi-sprint/#comments</comments>
        	<slash:comments>5</slash:comments> 
		<pubDate>Thu, 02 Apr 2009 22:32:07 +0000</pubDate>
		<dc:creator>Robert Marianski</dc:creator>
                <opencore:userid>rmarianski</opencore:userid>
		
		<category><![CDATA[pycon]]></category>

		<guid isPermaLink="false">http://www.openplans.org/projects/topp-engineering/blog/2009/04/02/pycon-wsgi-sprint/</guid>
		<description><![CDATA[At this year&#8217;s pycon, I participated in the wsgi sprint. We shared the room with some of the zope guys, and it was good to see some familiar faces. There were a lot of projects going on, and I participated in just a couple of them:
mongodb

There was a buzz in the air about mongo. I&#8217;ve [...]]]></description>
			<content:encoded xml:base="http://www.coactivate.org/projects/topp-engineering/blog/2009/04/02/pycon-wsgi-sprint/"><![CDATA[<p>At this year&#8217;s pycon, I participated in the wsgi sprint. We shared the room with some of the zope guys, and it was good to see some familiar faces. There were a lot of projects going on, and I participated in just a couple of them:</p>
<h3>mongodb<br />
</h3>
<p>There was a buzz in the air about mongo. I&#8217;ve heard good things about it from a number of people. I paired with <a href="/people/reedobrien">Reed</a> on getting a proof of concept working with repoze.bfg. We attempted to simulate the zodb&#8217;s transparent nested dictionary structure with it, but I mostly used it as an excuse to play around with mongo <img src='http://www.coactivate.org/projects/topp-engineering/blog/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> I&#8217;m still a little bit skeptical as to whether simulating the zodb api can work without having the abstraction leak, but I&#8217;m willing to be convinced. Anyway, there are nice python bindings to mongo that make it pretty easy to work with. The mongodb site has a list of <a href="http://www.mongodb.org/display/DOCS/Use+Cases">use cases</a> where mongodb is well suited.</p>
<h3>frameworks<br />
</h3>
<p>Amongst the python frameworks, there was talk about sharing a common configuration system (minus django of course). I&#8217;m all for this if it happens, because it may lead to having the frameworks share even more code underneath in the future.</p>
<p>There was some general application framework exploration as well. It was mostly trying to see if we could come up with good answers to common integration questions. What would ideal client code look like? How would the configuration take place? Can we mix and match behaviors easily? Can we set up a transaction system in a general way that makes it easy to integrate other components with different data stores? What types of services does it make sense for the application framework to provide?</p>
<h3>karl<br />
</h3>
<p>The repoze guys have created an application that is very ploney. It&#8217;s still in the works, but there&#8217;s a lot of functionality already there. In terms of it offers, it&#8217;s very similar to the current incarnation of the openplans suite. I spent some time looking through the code, and was really impressed with what I saw. The code is very clean, well factored, and easy to understand. Naturally, it&#8217;s based on repoze.bfg and a number of repoze packages. I think that it&#8217;s a great source for repoze best practices. If you want to check it out, the buildout is <a href="http://osi.agendaless.com/bfgsvn/buildout">here</a> (it works last time I ran it!) If people are interested, I can put it up on a demo server as well.
</p>
<hr />
<p>­That pretty much sums up the different projects that I checked out. For some reason ponies, and the django pony specifically, were a central theme. Ask me offline for some more info if you&#8217;re interested <img src='http://www.coactivate.org/projects/topp-engineering/blog/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </p>
<p>I&#8217;ll end by saying that it&#8217;s always a pleasure just to be around so many smart people at once. There was no shortage of that this year at the pycon sprints!<br />
  </p>
]]></content:encoded>
			<wfw:commentRss>http://www.coactivate.org/projects/topp-engineering/blog/2009/04/02/pycon-wsgi-sprint/feed/</wfw:commentRss>
		</item>
		<item>
		<title>To ZODB Or Not to ZODB</title>
		<link>http://www.coactivate.org/projects/topp-engineering/blog/2009/03/20/to-zodb-or-not-to-zodb/</link>
		<comments>http://www.coactivate.org/projects/topp-engineering/blog/2009/03/20/to-zodb-or-not-to-zodb/#comments</comments>
        	<slash:comments>3</slash:comments> 
		<pubDate>Fri, 20 Mar 2009 18:25:12 +0000</pubDate>
		<dc:creator>Paul Winkler</dc:creator>
                <opencore:userid>slinkp</opencore:userid>
		
		<category><![CDATA[best-practices]]></category>

		<category><![CDATA[programming]]></category>

		<category><![CDATA[plone]]></category>

		<guid isPermaLink="false">http://www.openplans.org/projects/topp-engineering/blog/2009/03/20/to-zodb-or-not-to-zodb/</guid>
		<description><![CDATA[The question has arisen on two different projects (one hypothetical, one already live) at TOPP in the past week: 

Should we (continue to) use ZODB as the primary data store, or should we use &#8230; something else? 

 Well. I&#8217;ve been using ZODB in my daily work since at least 2001, so I&#8217;m pretty well [...]]]></description>
			<content:encoded xml:base="http://www.coactivate.org/projects/topp-engineering/blog/2009/03/20/to-zodb-or-not-to-zodb/"><![CDATA[<p>The question has arisen on two different projects (one hypothetical, one <a href="http://communityalmanac.org">already live</a>) at TOPP in the past week: <em></em>
</p>
<p><strong><em>Should we (continue to) use ZODB as the primary data store, or should we use &#8230; something else? </em></strong>
</p>
<p><strong> </strong>Well. I&#8217;ve been using ZODB in my daily work since at least 2001, so I&#8217;m pretty well familiar with it by now. By contrast, I&#8217;ve done a little bit of work with MySQL, a tiny bit with other relational systems (SQLite, SQL Server, and Oracle), none with PostgreSQL, and almost no work with any of the current crop of object-relational mappers &#8230; nor with any of the hip new kids on the block like CouchDB.
</p>
<p>With that background, you&#8217;d expect me to be a fairly ignorant guy who always prefers ZODB just because he doesn&#8217;t know any better, right?
</p>
<p>But there&#8217;s also the truism that familiarity breeds contempt. Consider yourself warned about the rest of this post <img src='http://www.coactivate.org/projects/topp-engineering/blog/wp-includes/images/smilies/icon_wink.gif' alt=';-)' class='wp-smiley' />
</p>
<p>That said, I&#8217;ll start by listing some of the things I really <strong>do</strong> like about ZODB.
</p>
<h2>What&#8217;s Good about ZODB<br />
</h2>
<ul>
<li> <strong>Persistence is very transparent (to your application)</strong> &#8230; Getting started with ZODB is really, really easy. If your app is not performance-sensitive, you can get away with very little attention to storing your data, it mostly Just Works. And you hardly have to do a damn thing. Neat. (When performance is a problem, you have to start paying a little more attention - or maybe a lot.)</li>
<li> <strong>Schema, shmema.</strong> You just throw Python objects in there and they just stick. Neat.</li>
<li> <strong>Undo.</strong> When you can use it, this is fantastic to have built in. </li>
<li> <strong>Transactions that mostly just do what you want.</strong> Okay, this is really about the default transaction policy in Zope, not ZODB per se. But this is something that I really think Zope got right. (And thanks to<a href="http://repoze.org/viewcvs/repoze.tm/trunk/docs/index.rst?view=markup"> repoze.tm</a>, it&#8217;s something that any WSGI app can now do with any data store that supports transactions.) </li>
<li> <strong>Text Indexing</strong> &#8230; there are some nice full-text indexes available for ZODB. On the other hand, these work at the application layer - i.e. you have to do the work of updating the index yourself, it doesn&#8217;t happen magically when you save data. And they don&#8217;t play nice with Undo. I gather there are now some that you can use without the whole Zope 2 ball of wax, which is great. </li>
<li> <strong>Container hierarchies of arbitrary depth are trivial. </strong>This is really nice and something that&#8217;s easy for a ZODB user to take for granted. Doing the equivalent in an RDBMS is typically not so fun, I gather. (Haven&#8217;t had to do it yet myself, but some quick browsing suggests that it&#8217;s <a href="http://www.sitepoint.com/article/hierarchical-data-database/">really pretty icky.</a> I suspect though that traversing a ZODB object graph will have the same performance characteristics as the fairly simple &#8220;adjacency model&#8221; described in that article.) </li>
<li> <strong>Scaling is pretty transparent.</strong> For a while at least. ZEO is trivial to set up, and to the application, it looks no different than running against a local storage. Multiple mount points (analogous to mounting different physical storage at different directories on a filesystem) also help you scale transparently. </li>
</ul>
<h2> What&#8217;s Bad<br />
</h2>
<ul>
<li> <strong>Persistence is very opaque (to anything other than your application).</strong> </li>
<p>Let&#8217;s unpack that a little:
  </p>
<ul>
<li> <strong>No ad-hoc queries.</strong>
<p>This is more and more often the showstopper for me.</p>
<p>With an RDBMS, or CouchDB for that matter, as long as the database server is running, you can poke around and see what&#8217;s there. With even a small bit of application knowledge, this can be enormously useful for troubleshooting, quickly repairing data problems, and some simple migrations, to say nothing of actual feature development. With ZODB, you have to know a lot more about the app just to look around and guess what you&#8217;re looking at.<br />
     </li>
<li> <strong>You can&#8217;t even load the data without exactly the right application software installed.</strong>
<p>This is closely related to the previous point. It doesn&#8217;t bite you as often, but when it does, it is NO FUN.</p>
<p>It&#8217;s not just that non-Python applications can&#8217;t talk to the database at all, ever. It&#8217;s that your database depends on your code too much.</p>
<p>If you botch an install, or are trying to resurrect a really old one for some reason, sometimes you&#8217;ll make a mistake like having a slightly incorrect version of some dependency, such that some container class can&#8217;t be loaded. Since the ZODB is strictly a tree structure, there is <em>no</em> way to access any of the children of a broken container instance. Which could, if you are unlucky, translate to <em>all your data.</em></p>
<p>Think about that for a second: if you ever lose the ability to perfectly reconstruct your code stack, <em>you also lose your data. </em>Well, you could maybe try to parse something out of raw pickles, but that sure doesn&#8217;t sound fun!</p>
<p>Of course, normally everything&#8217;s fine because you have the right software installed. But what if you&#8217;re doing forensic work and you don&#8217;t have enough information to know what that is? Or what if the build scripts that used to work perfectly no longer work just because some third-party upstream release is no longer compatible?</p>
<p>Here&#8217;s a little story. I once did a quick job for a non-technical nonprofit that was in a bind. Their initial email went something like: &#8220;Hi, we hired a contractor to build our Plone site on a shoestring, and now he&#8217;s gone, and our production server crashed, and all we have is a .zexp export of the site, and zipfile of the code but we&#8217;re not sure if it&#8217;s the same as the production version. Can you help us get Plone started or at least get the documents and images out so we can throw up some kind of temporary static site?&#8221;</p>
<p>I gave it a go for a couple days, but I was thoroughly defeated. I felt so bad I only charged them for a couple hours and felt guilty for even doing that. I never want to put an employer or client in that position again, ever. </li>
</ul>
<li> <strong>No non-container relations.</strong> Expressing something like a many-to-many relation in ZODB means writing the code yourself, or installing something like <a href="http://pypi.python.org/pypi/zc.relation">http://pypi.python.org/pypi/zc.relation</a> &#8230; which presumably works fine, but I really can&#8217;t get excited about it: I&#8217;d rather spend my time learning relational technology that might actually be portable to other systems.</li>
<li> <strong>The ZEO server is still a single point of failure and potential bottleneck; </strong><strong>No live replication.</strong> There&#8217;s no free workaround. There is an <a href="http://www.zope.com/products/zope_replication_services.html">expensive solution from Zope Corp.</a> <a href="http://pypi.python.org/pypi/RelStorage#faqs">RelStorage</a> could theoretically solve this problem for free by deferring to the underlying RDBMS replication, but it&#8217;s apparently not been tested.
<p>It&#8217;s important to note that <em>I have never actually run a site where the ZEO server was the bottleneck,</em> but the sites I&#8217;ve worked on have relatively small user bases, and the largest-scale ZEO cluster I&#8217;ve heard of was a news site: <em>very</em> read-heavy with a relatively small user base doing relatively few writes. I&#8217;ve never heard of anybody doing a large site with lots of writes from lots of users. If your goal is to build the next Facebook or Wikipedia, I don&#8217;t think there are any relevantly large real-world ZODB case studies you can emulate.</p>
<p>Most of us just make do without replication or failover of any kind, and hope we will never really need it.<br />
   </li>
<li> <strong>No fine-grained control over which (and how much) data you retrieve.<br />
  <br /></strong>In any SQL database, you can trivially do &#8220;select foo from bar&#8221; and get only the values in the foo column, regardless of what other gunk is in each row. In ZODB, you get a whole object - think of this as the equivalent of every query starting with &#8220;select *&#8221;, so you always get the entire row(s). Results tend to be fat and you have no ad-hoc control over that, short of reorganizing the database. Which leads me to&#8230;<br />
   </li>
<li> <strong>Migrations are inconvenient and expensive.</strong> Migrations with ZODB typically take the form of a script containing two functions: one which updates a single instance of a particular persistent class, and another function which finds all the instances to upgrade. The latter is the non-trivial part, because there&#8217;s actually no way to find all objects of a given type short of walking the entire object tree. If you&#8217;re using Zope 2, you may have a ZCatalog handy that you can use, if it knows about all the objects you want to upgrade; or you can use the old ZopeFind API which is just a convenient (and no less expensive) way to walk the entire tree.
<p>And you can&#8217;t really do a migration atomically on a live site, because you&#8217;re sure to get ConflictErrors if you try to do it in a single transaction. You can solve this by taking the site down for the duration of the migration. If that&#8217;s not an option, you have to try committing and starting a new transaction after every N objects touched, which practically speaking means you&#8217;re not going to want to undo your migration. AFAIK there is no existing infrastructure for the latter approach, which means you have to rewrite it in every migration script you ever write.</li>
<li> <strong>Often, you can&#8217;t actually <em>use</em> undo.</strong> If a transaction touches a frequently-updated object (like oh, say, the catalog indexes), you probably won&#8217;t be able to undo that transaction for very long, because other transactions will have since touched the same object, so undoing it would cause a conflict. A transaction is not a database-wide savepoint, like a revision in Subversion; rather, a transaction only knows about the objects that were changed during its lifetime. There&#8217;s no way to revert to an arbitrary point in the past.</li>
<li> <strong>Indexing is not transparent.</strong> I very often see code in Zope applications to ensure that some index is properly updated after some value changes. It gets tiresome. By contrast, indexes in an RDB typically require no attention from the developer&#8230; but they don&#8217;t serve the same purpose.<br />
   </li>
</ul>
<h2>What&#8217;s Debatable<br />
</h2>
<p>This section could grow endlessly, but I&#8217;ll just list a couple items off the top of my head:
</p>
<h3>Speed<br />
</h3>
<p>For years, the accepted wisdom was that ZODB was pretty fast for reads, and slow for writes. Some people claim that it&#8217;s <a href="http://www.upfrontsystems.co.za/Members/roche/where-im-calling-from/zodb-benchmarks-revisited">actually fast for writes too</a>. I don&#8217;t care much about raw benchmarks except insofar as they translate to real applications. The ZODB <em>application</em> I actually get to use the most - Opencore, built on Plone 3 - <em>feels</em> quite slow at the storage layer (some of this is catalog stress, some of it is due to storing binary files in CMFEditions, which we now know was a terrible mistake.)
</p>
<p>I have no hard numbers to offer, hence putting this in the &#8220;Debatable&#8221; category.
</p>
<h3>Partitioning<br />
</h3>
<p>Object traversal in Zope encourages you to map your ZODB tree directly to your URL space. I actually quite like this as it&#8217;s really easy to understand. But it makes it harder to reorganize your data for scalability reasons (eg. horizontal partitioning aka sharding) without also reorganizing the URL space and breaking links.</p>
<p>This is another one of those problems that&#8217;s mostly theoretical to me so far - I haven&#8217;t actually needed to do sharding on a ZODB app yet. And most people never will, but if you&#8217;re building something very ambitious, it&#8217;s something to be aware of.
</p>
<p>As noted above, the ZODB can do one kind of partitioning by &#8220;mounting&#8221; databases in the object graph, like filesystems mounted in one Unix file tree; this is great and easy, but it&#8217;s only transparent if the mount point can replace an existing folder; it doesn&#8217;t help you with flat but dense data. Also, mounting multiple storages can be problematic when objects under one mount point refer to objects outside that mount point; see eg. the notes at the bottom of http://apidoc.zope.org/++apidoc++/Book/zodb/crossref/show.html
</p>
<p>This is another case where I wonder if creative use of <a href="http://pypi.python.org/pypi/RelStorage#faqs">RelStorage</a> might help, although I&#8217;ve no idea how you&#8217;d know where to split the partitions.
</p>
<h3>Optimizing is Weird<br />
</h3>
<p>When addressing bottlenecks in an app written against a given RDBMS, there are typically pretty decent docs available that help even a novice get started with tuning their queries and setting up the proper indexes. If I google &#8220;mysql query optimization&#8221;, I find a lot of useful results on the first page. With ZODB, there are some general strategies that typically are learned the hard way. Good luck googling for docs or tips. One of the few things I found was from a <a href="http://74.125.47.132/search?q=cache:nYeomZz9ff4J:plone.org/events/regional/nola05/collateral/Chris%20McDonough-ZODB%20Tips%20and%20Tricks.pdf/at_download/file+zodb+optimization&amp;cd=9&amp;hl=en&amp;ct=clnk&amp;gl=us">presentation</a> (PDF) that Chris McDonough made about ZODB: &#8220;The most important optimization you can perform is to write efficient code. Unfortunately, this is also the hardest way to optimize, because you need to manage all the details.&#8221;
</p>
<h2> Finally: You Can&#8217;t Take It With You<br />
</h2>
<p>You may have noticed a theme running through some of the above.
</p>
<p>I&#8217;m tired of feeling like I&#8217;m in a programming ghetto, and frankly ZODB feels rather marginalized. Not because of any of the things that I think are wrong with it, but just because <em>almost nobody uses it.</em> This has a lot of implications - lost opportunities for re-using innovative work and so forth. This is the <a href="http://www.youtube.com/watch?v=fipFKyW2FA4">danger that Mark Ramm recently warned the Django world about</a> (video link, sorry; there&#8217;s no transcript anywhere AFAICT).
</p>
<p>But more personally, I just don&#8217;t feel that I&#8217;m young enough to waste much more of my career on dead-end tech. Python is a plenty big pond for me to swim in; but most of the fish in that pond wouldn&#8217;t touch ZODB with a ten-foot pole. That&#8217;s a shame, maybe, but perception is reality, and if this was going to change, it would have changed by now. It&#8217;s been over 10 years now.
</p>
<p>Given that, the time that I spend using ZODB could be better spent learning skills that I <em>can</em> more realistically apply on more future projects. Maybe even (gasp) non-Python projects.
</p>
<p>It may seem selfish to harp on how this affects an individual&#8217;s career, <a href="http://slinkp.com/photo_album/riley_jan_2009/riley_in_nursery_just_after_birth_20090123.jpg/view?display=large">but sometimes the events in your life push you in that direction.</a> Having ZODB and Zope on my resumé gives me a certain amount of hireability right now, I think because I&#8217;m a relatively experienced fish in this tiny (and, AFAICT, not growing) pond where demand (for now) seems to slightly outstrip supply. Will ZODB be even that relevant in, say, 10 years?
</p>
<p><a href="http://web.ics.purdue.edu/~ssanty/cgi-bin/eightball.cgi">Let&#8217;s ask:</a>
</p>
<p> <img alt="Outlook not so good." src="/projects/topp-engineering/project-home/Magic-Eight-Ball-CGI_1237569425311.1.png" height="341" width="385" />
</p>
<p>Okay, I&#8217;m putting my flame suit on now <img src='http://www.coactivate.org/projects/topp-engineering/blog/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' />
</p>
<p>(For a more generally enthusiastic take on ZODB, you should have a look at Chris McDonough&#8217;s blog post on <a href="http://plope.com/Members/chrism/wherefore_couchdb">ZODB compared to CouchDB.</a> )</p>
]]></content:encoded>
			<wfw:commentRss>http://www.coactivate.org/projects/topp-engineering/blog/2009/03/20/to-zodb-or-not-to-zodb/feed/</wfw:commentRss>
		</item>
		<item>
		<title>FeedBacker: What It Is and Where It&#8217;s At</title>
		<link>http://www.coactivate.org/projects/topp-engineering/blog/2009/03/05/feedbacker-what-it-is-and-where-its-at/</link>
		<comments>http://www.coactivate.org/projects/topp-engineering/blog/2009/03/05/feedbacker-what-it-is-and-where-its-at/#comments</comments>
        	<slash:comments>2</slash:comments> 
		<pubDate>Thu, 05 Mar 2009 22:51:41 +0000</pubDate>
		<dc:creator>Rob Miller</dc:creator>
                <opencore:userid>ra</opencore:userid>
		
		<category><![CDATA[programming]]></category>

		<guid isPermaLink="false">http://www.openplans.org/projects/topp-engineering/blog/2009/03/05/feedbacker-what-it-is-and-where-its-at/</guid>
		<description><![CDATA[FeedBacker: An Introduction
  

 For the last few weeks I&#8217;ve been working on FeedBacker, a small REST application that stores and serves up feed items. &#160; FeedBacker also provides a rudimentary query interface to the stored feed items, serving up the result set as a feed.&#160; The idea is that feed items will be [...]]]></description>
			<content:encoded xml:base="http://www.coactivate.org/projects/topp-engineering/blog/2009/03/05/feedbacker-what-it-is-and-where-its-at/"><![CDATA[<h2>FeedBacker: An Introduction<br />
  <br />
</h2>
<p> For the last few weeks I&#8217;ve been working on <a href="http://trac.openplans.org/openplans/browser/FeedBacker">FeedBacker</a>, a small REST application that stores and serves up feed items. &nbsp; FeedBacker also provides a rudimentary query interface to the stored feed items, serving up the result set as a feed.&nbsp; The idea is that feed items will be stuffed into the FeedBacker database whenever activity happens on an Opencore site, allowing us to easily generate feeds of arbitrary activity history simply by constructing the appropriate FeedBacker queries.&nbsp; Seeing David Turner&#8217;s update and debrief on his Henge efforts yesterday reminded me that I wanted to write a similar post about FeedBacker, so here I am.
</p>
<p>Conceptually, FeedBacker is very simple.&nbsp; It defines a simple FeedItem data model, based on the Atom syndication format, and lets you use POST requests to create them.&nbsp; GET requests can return either a collection of stored FeedItems or a single FeedItem, in either HTML (using the hAtom microformat) or XML (using Atom)&nbsp; RSS and/or other formats later may be added later.&nbsp; There are, however, a few additional features that are worth a closer look.
</p>
<h3>Metadata<br />
</h3>
<p>Our use cases require us to support fairly arbitrary data on our FeedItems.&nbsp; These data points are needed for later retrieval and also for querying purposes.&nbsp; Since our ultimate goal is to generate feeds of site activity, we need to be able to store such values as the type of action that happened (e.g. &#8216;created&#8217;, &#8216;edited&#8217;, &#8216;joined&#8217;), the type of object on which the action was performed (e.g. &#8216;wiki page&#8217;, &#8216;discussion&#8217;, &#8216;blog post&#8217;), and the project where the action took place, if any.&nbsp; So FeedBacker allows you to define extra fields on your FeedItems, either through HTTP or by setting an &#8216;extra_fields&#8217; value in the PasteDeploy ini file.&nbsp; Each extra field is typed, either as int, string, unicode, datetime, or a binary BLOB.&nbsp; Any non-BLOB extra field can be used in the queries.&nbsp; Also, any non-BLOB extra field can be specified as &#8216;in_results&#8217;, i.e. the contents of that field will be included in the FeedItem&#8217;s rendering when you make queries or visit the FeedItem resource directly.&nbsp; When results are returned in Atom format, the extra fields are included in the results as custom tags in the &#8216;feedbacker&#8217; namespace, e.g:
</p>
<pre> &lt;feedbacker:project&gt;StreetsBlogNet&lt;/feedbacker:project&gt;
</pre>
<h3>Filters<br />
</h3>
<p>The querying facility will get us most of the way towards the feeds that we want, but there are some types of feed parameters that can&#8217;t be easily represented as a query against the result set.&nbsp; For this reason &#8216;feed filters&#8217; were introduced.&nbsp; Feed filters are just callable functions that expect to receive the results of a feed query and will return a filtered result set on the other side.&nbsp; Feed filters will help us handle cases like removing near-duplicates (e.g. multiple small page edits in rapid succession) and enforcing security policies so as not to expose a closed project&#8217;s activity to anyone not on the project.&nbsp; Feed filters can be registered programmatically, or by use of a &#8216;feed_filters&#8217; setting in the PasteDeploy ini file.&nbsp; There is no support for using HTTP to register feed filters at this time.
</p>
<p>&nbsp;
</p>
<h2>Technical Debrief<br />
</h2>
<p>FeedBacker was written in Python using the <a href="http://static.repoze.org/bfgdocs/">repoze.bfg</a> web framework, with <a href="http://www.sqlalchemy.org/">SQLAlchemy</a> for persistence.&nbsp; Since I&#8217;ve been working in Zope-land for so long, this was the first project in quite some time I&#8217;ve done using a relational database as a back end.&nbsp; The expressiveness of SQL for a query language is very nice, and SQLAlchemy provides a surprisingly good abstraction layer, but you have to jump through so many hoops to model more complicated data structures in a queryable manner.&nbsp; It&#8217;s quite nice to be able to just stick a dictionary (or a rich object, even) in a pickle and not have to worry about mapping all of the keys and values to relational tables.
</p>
<h3>Object-Relational Mapping<br />
  <br />
</h3>
<p>A significant amount of FeedBacker&#8217;s complexity right now is in the object-relational mapping.&nbsp; To it&#8217;s credit, SQLAlchemy was able to do exactly what I wanted it to do, so the complexity of the mapping is absolutely hidden to the front end code.&nbsp; All of the data points, even the arbitrarily defined extra fields, show up transparently as attributes on the FeedItem objects.&nbsp; But it took me a few days to get it all dialed in correctly, and then a few later tweaks to make sure everything works correctly even when there are multiple values for a single field.&nbsp; It remains to be seen how well this will scale with very large data sets or high traffic volumes.
</p>
<h3>HTML to SQL Query Translation<br />
  <br />
</h3>
<p>The other place where there is a bit of complexity is in the translation of the HTTP GET requests to the SQL queries that actually do the work.&nbsp; FeedBacker provides a hand-rolled syntax to allow you express fairly arbitrary queries against the storage (nice HTML docs coming soon, for now see the <a href="http://trac.openplans.org/openplans/browser/FeedBacker/trunk/feedbacker/lib.py?rev=23178#L57">docstring</a> for details) which needs to be parsed and converted to a SQL query that can actually be run.&nbsp; Again, I was very impressed w/ SQLAlchemy&#8217;s help here; the ability to construct query clauses piecemeal, passing them around and nesting them further within other clauses, made it relatively easy to pick apart the HTTP query arguments and iteratively construct a query in the process.&nbsp; Again, we&#8217;ll see what the performance ends up looking like under high load, after the right indexes have been applied.
</p>
<p>&nbsp;
</p>
<h2>Current Status<br />
</h2>
<p>For this week I&#8217;ve been working on tying everything together by actually implementing a FeedBacker-backed page in the Livable Streets stack.&nbsp; Things have been coming quite well, although I of course had to revisit a few things when the truth of the requirements butted up against some of FeedBacker&#8217;s original assumptions.&nbsp; The page I&#8217;ve been working on is the member account page, based on what is proposed in a <a href="http://skitch.com/scrollie/s384/lsg-account-page">mock-up</a> Rollie put together a couple of months back.&nbsp; Not everything you can see in the mock-up will be making it into the first deployment (there&#8217;s not yet support for being a &#8216;fan&#8217; of a project, for instance), but it gives you a good idea what type of functionality we&#8217;re trying to support.&nbsp; Ultimately, the hope is that the account page will become similar to a user&#8217;s home page on FaceBook, where all of the activity that the user cares about will roll by in one place.
</p>
<p>The page works by making an HTTP query request of FeedBacker, where the query represents what should show up on the page.&nbsp; It asks for the results in atom format and then uses Python&#8217;s universal feedparser to parse the results and construct the output.&nbsp; The page is nearly usable; at this point I&#8217;m just working on getting all the links and formatting correct, getting the icons to show up, etc.&nbsp; I&#8217;m also working on making sure that as much info as we need is embedded in the feed item data itself.&nbsp; If we have to visit a large number of Zope objects in order to fill in the details of the page, then we&#8217;ve lost, the page will be very slow.&nbsp; I haven&#8217;t hit anything that I&#8217;d call a roadblock, however, so I&#8217;m feeling pretty confident that this is going to be a good starting point.
</p>
<p>My goal is to get the implementation of the page finished up today, and to deploy it to a dev server so we can start comprehensive testing tomorrow.&nbsp; And, if I wrap this blog post up and get back to it, I just might.<br />
  </p>
]]></content:encoded>
			<wfw:commentRss>http://www.coactivate.org/projects/topp-engineering/blog/2009/03/05/feedbacker-what-it-is-and-where-its-at/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Henge status</title>
		<link>http://www.coactivate.org/projects/topp-engineering/blog/2009/03/04/henge-status/</link>
		<comments>http://www.coactivate.org/projects/topp-engineering/blog/2009/03/04/henge-status/#comments</comments>
        	<slash:comments>1</slash:comments> 
		<pubDate>Thu, 05 Mar 2009 03:23:40 +0000</pubDate>
		<dc:creator>David Turner</dc:creator>
                <opencore:userid>novalis</opencore:userid>
		
		<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://www.openplans.org/projects/topp-engineering/blog/2009/03/04/henge-status/</guid>
		<description><![CDATA[ I started working on a simple calendar for opencore called Henge. The idea is that projects could post a calendar of events. My initial version doesn&#8217;t even let you describe events &#8212; each event just has a link field that you can use.
I&#8217;ve decided to put the project on hiatus, because we&#8217;re considering rewriting [...]]]></description>
			<content:encoded xml:base="http://www.coactivate.org/projects/topp-engineering/blog/2009/03/04/henge-status/"><![CDATA[<p> I started working on a simple calendar for opencore called Henge. The idea is that projects could post a calendar of events. My initial version doesn&#8217;t even let you describe events &#8212; each event just has a link field that you can use.</p>
<p>I&#8217;ve decided to put the project on hiatus, because we&#8217;re considering rewriting opencore. I think we&#8217;re finally starting to figure out how to write applications in the dvhoster, openplans_hooks, transcluder, cookieauth, etc framework. But being able to do things doesn&#8217;t mean being able to do them well or efficiently. While debugging henge, I did run into various issues caused by the stack: deliverance hides error messages; I couldn&#8217;t figure out what file I had to change to map URLs to project/x/calendar; debugging the project member http call was hassle. And I didn&#8217;t even get to the opencore featurelet.</p>
<p>Also, I was starting to hit the limits of SQLObject pretty hard. I wasn&#8217;t doing anything especially complicated but I was trying to write an undo mode that made sense. The data model is:</p>
<p>Projects have many Events.<br />
  <br />Events have many EventDates.</p>
<p>When an event is deleted, its associated dates should be deleted &#8212; this is trivial. But when an event is restored, its EventDates should be restored &#8212; and only those EventDates that it had when it was deleted should be restored (not any that might have been deleted earlier). I tried a bunch of strategies to make this work well, and ended up having to do both metaprogramming and hand-coding to make it work. I think a better programmer could have done it all through metaprogramming, but it would have taken me more time than it was worth. In particular, I wanted objects to have uuids as primary keys, and that was impossible. As it turned out, that wouldn&#8217;t have gotten me what I needed, but I still should have been able to do it.</p>
<p>What&#8217;s actually going on is that an Event is a composite object composed of some general data and a bunch of dates. But SQLObject doesn&#8217;t natively have a way to talk about a row and its associated rows from other tables. You can walk through the metadata and find one-to-many relationships, but that&#8217;s not the same thing as ownership. If there were a way to talk about this naturally, it would be easier to support things like undo and versioning of composite objects.</p>
<p>I think the lesson is that SQLObject is not really powerful enough for applications which want to support complex interactions with data. This isn&#8217;t a complaint about SQLObject &#8212; it&#8217;s not meant for these sorts of complex cases. I&#8217;m not sure if SQLAlchemy is the solution, but I&#8217;m certainly interested to find out.</p>
<p>Anyway, there&#8217;s a few weeks of work left before I could put Henge live, and I find myself without the time to do them.
</p>
<ul>
<li> the aforementioned featurelet</li>
<li>events should have descriptions</li>
<li>there should be an agenda view</li>
<li>a javascript date picker</li>
<li>event permalinks which survive event deletion</li>
</ul>
<p>Of course, there are a ton of ways to complexify things, many of which are obvious &#8212; presently, there is no interface to add multiple dates to an event. But really, the above is all that&#8217;s needed to launch.<br />
</p>
]]></content:encoded>
			<wfw:commentRss>http://www.coactivate.org/projects/topp-engineering/blog/2009/03/04/henge-status/feed/</wfw:commentRss>
		</item>
	</channel>
</rss>
