Skip to content. | Skip to navigation

Personal tools

>>> ''.join(word[:3].lower() for word in 'David Isaac Glick'.split())

‘davisagli’

Navigation

You are here: Home

David Glick – Plone developer

by admin posted Apr 05, 2010 11:48 PM

Extending kupu's initialization with a Javascript wrapper decorator

by David Glick posted Jul 20, 2009 03:15 PM

Today I found myself struggling to do something in Javascript that I'm used to doing with ease in Python -- replace an existing method (defined by code I don't want to touch) with a wrapper that calls the original method and then also performs some additional actions. (Yeah, it's a monkey patch. But sometimes it's a cleaner and more maintainable way to extend something than the alternatives.)

In particular, I was trying to adjust the default kupu configuration without overriding kupuploneinit.js to add commands directly to the initPloneKupu method. Here's the snippet that got me there:

var augmentKupuInit = function(orig_fn) {
  return function(){
    var editor = orig_fn.apply(this, arguments);
    // do what you need to on the editor object here.
    // For example, I was trying to prevent kupu from
    // filtering the 'fb:fan' tag of Facebook's "Fan Box"
    // widget, like so:
    editor.xhtmlvalid.tagAttributes['fb:fan'] = ['*'];
    return editor;
  };
};
initPloneKupu = augmentKupuInit(initPloneKupu);

This defines a decorator function called augmentKupuInit that can be used to wrap another function. Then it uses it to wrap the original initPloneKupu method, calling the newly generated function initPloneKupu. As long as this snippet is registered in such a way that it loads after kupuploneinit.js and before the initPloneKupu method is called, it works like a charm!

(Many thanks to http://stackoverflow.com/questions/326596/how-do-i-wrap-a-function-in-javascript, which finally pointed me in the right direction.)

1 comment

Visualizing the ZODB with graphviz

by David Glick posted May 22, 2009 11:25 AM

While digging around in the ZEXP export code, I realized that it wouldn't be too hard to modify it to dump a representation of a ZODB in graphviz .dot format. Here's a Zope external method I devised to do that:

 

# Generic ZODB walker and graphviz exporter

####################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
####################################################################

import logging
import cPickle, cStringIO
from ZODB.utils import u64

logger = logging.getLogger('ZODB.ExportImport')

def get_reference_dumper(refs):
    # This is a callback which will be called whenever a reference is found.
    def dump_reference(oid, roid):
        refs.append('%s -> %s\n' % (u64(oid), u64(roid)))
    return dump_reference

def export_graphviz(self):
    """
    Walks a ZODB database and dumps the object graph in graphviz .dot format.
    """
    context = self
    f = open('plone.dot', 'w')
    f.write('digraph plone {\n')
    refs = []
    reference_dumper = get_reference_dumper(refs)
    for oid, p in walk_database(context, reference_callback=reference_dumper):
        # Walk to all the objects in the database and examine their references.
        # Whenever a reference is found, it will be recorded via the
        # reference_dumper.  Whenever a new object is found, it will be yieled
        # to this loop.

        # Read the module and class from the pickle bytestream without actually
        # loading the object.
        module, klass = p.split('\n')[:2]
        module = module[2:]
        
        f.write('%s [label="%s.%s"]\n' % (u64(oid), module, klass))
    for ref in refs:
        f.write(ref)
    f.write('}\n')
    f.close()

def walk_database(context, reference_callback=None):
    # Get the object ID and database connection of the starting object.
    base_oid = context._p_oid
    conn = context._p_jar
    
    # oids is used to keep track of found oids that need to be visited.
    # done_oids is used to keep track of which oids have already been yielded.
    oids = [base_oid]
    done_oids = {}
    while oids:
        # loop while references remain to objects we haven't exported yet
        oid = oids.pop(0)
        if oid in done_oids:
            continue
        done_oids[oid] = True
        
        try:
            # fetch the pickle
            p, serial = conn._storage.load(oid, conn._version)
        except:
            logger.debug("broken reference for oid %s", repr(oid),
                         exc_info=True)
        else:
            # If the Unpickler's persistent_load attribute is set to a list,
            # then that list will be populated with the references found in
            # the pickle when noload is called, without actually loading the
            # object.
            refs = []
            u = cPickle.Unpickler(cStringIO.StringIO(p))
            u.persistent_load = refs
            # noload must be called the same # of times it was called when
            # pickling
            u.noload()
            u.noload()

            # loop through the references found on this object
            for ref in refs:

                # look for the various reference types supported by the ZODB
                # (see the docs in ZODB/serialize.py for details)
                if isinstance(ref, tuple):
                    roid = ref[0]
                elif isinstance(ref, str):
                    roid = ref
                else:
                    try:
                        ref_type, args = ref
                    except ValueError:
                        # weakref - not supported
                        continue
                    else:
                        if ref_type in ('m', 'n'):
                            # cross-database ref - not supported
                            continue
                if roid:
                    # record this reference
                    if reference_callback:
                        reference_callback(oid, roid)

                    # add the referenced object to the list of objects we need
                    # to visit
                    oids.append(roid)

            # yield the oid and pickle
            yield oid, p

Download graphviz_export.py

And after running this on a fresh Plone site, sending the result through dot and loading it in zgrviewer, here's the result:

ZODB graphviz visualization

The site root is toward the upper right; most of the graph is persistent tools and such rather than actual content, since there is minimal content in a fresh Plone installation. That hairy mess on the left is the mimetype registry. Any resemblance to the shape of the BFG logo is entirely coincidental.

I'm not really sure what sort of useful information one might be able to get using this sort of technique, but I'm sure there are some possibilities, so please let me know if you have ideas or if you modify this to do something cool.

I want to try this on a site that has real data in it, but at the moment I'm waiting for the latest XCode to download so that I can build the newest graphviz which includes sfdp which is supposed to be better for handling really big graphs.

2 comments

New product: collective.weightedportlets

by David Glick posted May 01, 2009 04:45 AM

Ever been frustrated with not being able to control exactly what order your portlets show up in, if you've got inherited portlets, content type portlets, and group portlets? I just released collective.weightedportlets, which allows you to specify an integer weight for each portlet assignment, which will be taken into account in the final ordering. See the Plone.org product page for details.

1 comment
David Glick

David Glick

I am a problem solver trying to make websites easier to build.

Currently I do this in my spare time as a member of the Plone core team, and during the day as an independent web developer specializing in Plone and custom Python web applications.