Showing posts with label plone. Show all posts
Showing posts with label plone. Show all posts

Tuesday, June 02, 2009

Testing ZODB changes

Came up with a handy decorator for asserting that a given method does not modify the ZODB.

It can be used in tests to ensure that a given test does not change the ZODB in any way (for example views).

I came up with it because a regression was discovered where a read-only view was modifying some things in ZODB causing ConflictErrors.To ensure it would not come up again I had to come up with a failing test.

def readonly(fun):
  """
    requires that self is at least a PortalTestCase
    
    Usage hint:  Use a savepoint to reset the changed object listing.
    """

  def decorated(self, *args, **kwargs):
      # this resets registered objects
      transaction.savepoint()
      before = list(self.portal._p_jar._registered_objects)
      result = fun(self, *args, **kwargs)
      after = self.portal._p_jar._registered_objects
      if before != after:
          s = SequenceMatcher(a=before, b=after)
          for tag, i1, i2, j1, j2 in s.get_opcodes():
              print ("%7s a[%d:%d] (%s) b[%d:%d] (%s)" % (tag, i1, i2, before[i1:i2], j1, j2, after[j1:j2]))
          raise RuntimeError("ZODB was changed")
      return result
  return decorated

An example of usage:

class ReadonlyTests(YourTestCase):
 @readonly
 # This one will fail
 def test_isnt_readonly(self):
     self.portal.invokeFactory('Document', 'test')
     self.portal.test.setTitle('foo')

 @readonly
 def test_is_readonly(self):
     pass

Tuesday, November 11, 2008

World Plone Day Toronto 2008 Reflections

On Friday November 7th, the Toronto Plone Users put on a World Plone Day event at the Center for Social Innovation in Toronto, Canada.

Over 40 people showed up to listen to 5 talks that ranged from an introduction to Plone, several case studies and a technical talk covering rapid application development with Plone. We raffled away two books that were generously donated by O'Reilly.

We streamed live via ustream.tv and the recordings and slides from the presenters are now available.

Based on our pre-event survey we had a wide range of attendees: new to Plone, developers, managers and end-users from corporate, non-profit and government sectors. We consider the event to be a huge success!

The event was sponsored by my company Scryent, and co-sponsors Digital Opportunity Trust (DOT) and Softwerke.

Hugh Ranalli of DOT also blogged about the event.

The November Toronto Plone Users is meeting today, Tuesday November 11.

Sunday, November 02, 2008

World Plone Day Toronto

Only 5 days before World Plone Day on November 7!

In Toronto we're organizing a half-day of talks and case studies on Plone at the Center for Social Innovation (near Queen & Spadina).

I'll be giving two talks at the event: one mainly geared to decision makers who want to know why Plone is a competitive CMS solution and what it can do for them. The second talk is "Intranet in 25 minutes" where I'll show building an intranet in real time that truly showcases the power of Plone out of the box and also showcase a couple key add-ons.

Along with myself will be 3 other speakers covering case studies of Plone in government and non-profit organizations, along with using ArchGenXML to generate Plone applications from UML diagrams.

There's more info available at the Toronto Plone Users website.

There's a free registration and those who do register will get a chance to win one of two Python books donated by O'Reilly!

Space in our room is limited so register now!

Thursday, May 22, 2008

quick way to debug testbrowser in doctests

Writing doctests I am sometimes wondering what is going on under the hood. Keeping this function around in my test environment makes it a bit easier to find out. After rendering the output to the browser it will pause waiting for a keypress to continue the test.  Note, this code works only on OSX though it should be trivial to modify for any OS.
>>> def render_browser(browser):
...   open('/tmp/browser.html', 'w').write(browser.contents)
...   import os
...   os.system("open /tmp/browser.html")
...   _ = raw_input()

Monday, October 15, 2007

buildout recipe for installing Selenium RC

This weekend at the Plone Naples Sprint one of the things I worked on was a buildout to download and install Selenium RC. It is now available from PYPI under the name collective.recipe.seleniumrc For some reason the Selenium RC distribution zip file contains version numbers on each subdirectory so this meant I couldn't just use one of the existing download scripts. But I didn't want to re-invent the download recipe wheel, so I decided to base this recipe on a well-written existing download recipe, hexagonit.recipe.download. I asked on IRC if the author was present at the sprint and ironically enough he was sitting in the same room as me only two rows away ;) With some simple changes to his recipe I was able to derive collective.recipe.seleniumrc from it and gain features like download caching and md5 file hash verification. Along the way I learned that if you want your buildout recipe to be derived by others you should make it a new-style Python class so that super() works. Also that it helps to factor your install() method into submethods for overriding. The changes I made to hexagonit.recipe.download were cleaned up and integrated into a 1.1.0 release that we made yesterday, along with the 0.1 release of collective.recipe.seleniumrc If you want to try out the Selenium RC recipe just create a simple buildout.cfg:
[buildout]
parts=seleniumrc

[seleniumrc]
recipe=collective.recipe.seleniumrc
After you are finished running this buildout you will get a nice wrapper script in your bin folder named by the part, in this case seleniumrc. Happy testing!

Wednesday, August 15, 2007

WebDAV, cadaver and metadata

Recently I wanted to automate some document management processes against a Plone site and was trying to figure out how to do set the Title property on the newly created objects via WebDAV, using the cadaver command line client. This led me to look a little more closely at how WebDAV does properties. In fact it is not like a simple Python dictionary, rather there is a nested structure like a dictionary of dictionaries. Each application can have its own namespace. By default cadaver will set properties in its own custom namespace. So you need to use the set namespace command to use the Zope namespace identified by its namespace: http://www.zope.org/propsets/default The following example uses cadaver and creates a collection (folder) and sets the title property:
mkcol just_a_folder
set namespace http://www.zope.org/propsets/default
propset just_a_folder title “A long title”

Monday, April 16, 2007

Buildouts

How annoying is it to deploy an application? How prone to errors due to the sheer repetitiveness of the common deployment? Buildouts to the rescue! What is a buildout? It's a automated set of steps for creating a deployment environment. This could mean one little application, or it could be several and they would setup to talk to each other. zc.buildout came out sometime last year and I've had my eye on it since then. I'd wanted to do Jim Fulton's tutorial on it at Pycon this year but wasn't able to schedule it in! I've had the opportunity in the last couple weeks to start using a bit more and I really appreciate the conceptual basis for this framework. The idea that a deployment consists of parts is something you can explain to anyone, for example. A web app needs a webserver, some kind of storage backend, the application code and some preexisting data: parts=apache mysql application-data application or full Zope-based solution: parts=apache zope zeo plone mywebsite mywebsite-data What I've noticed so far is that for a particular application even though someone might have already wrote a recipe for it actually getting it configured the way you want usually requires extending that recipe slightly. If the recipe was written with this in mind its easy enough to extend. I did have a bit of problems extending some of the plone.recipe.* recipes because they were old style classes and so I couldn't use super() to extend the default behaviour. Easy to fix though!

ZOPE / ZEO buildout recipe with multiple database support

I created a branch that adds the ability to configure multiple databases in your configuration files which I needed for a project, so this should be a fairly decent example for anyone else needed to something similar. See the jbb666-multiple-dbs branch for zeo instance and the corresponding branch for the zope recipe. For a lot of the Zope recipes the config knobs that need to be exposed will be determined and you won't need to touch the underlying recipe except in well exceptional circumstances.

Thursday, April 12, 2007

copying a plone site

Copying sites has recently gotten a lot better since 2.5.2 fixed some bugs with modification times getting reset but still you generally can't use copy & paste to copy sites as I have found it doesn't retain the review state of your documents (and this is by design actually I think). Another approach is to import/export. This actually works great so I put together a script that will automate this process. You need to call this from either an External Method or from zopectl debug (or ipython). That's an exercise up to the reader... # moveSites.py
# Move a Plone site without losing the modification times and workflow status
# we rely on export/import to do this
# You need to provide a valid REQUEST so that the import doesn't fail
# if running from zopectl debug you can use makerequest to wrap your app object
# with a fake request
# Author: Jordan Baker (jbbNOASPM@scryent.com)
import tempfile
import os

def moveSite(portal, new_location):
# export the site to a tempfile
f = tempfile.NamedTemporaryFile()
portal._p_jar.exportFile(portal._p_oid, f)
# re-import in the correct location
f.seek(0)
new_location._importObjectFromFile(f, verify=False, set_owner=False)
# delete the old site
oid = portal.id
portal.aq_parent.manage_delObjects(ids=[oid])
# update the catalog in the new site
new_location[oid].portal_catalog.refreshCatalog(clear=True)
# NamedTemporaryFile should delete the underlying temporary file
# when its object is garbage collected