Personal tools
You are here: Home Weblog
Document Actions
  • Send this page to somebody
  • Print this page
  • Add Bookmarklet

Weblog

2016-12-02

Change the authentication cookie name in Plone

Filed Under:

Not obvious of first, there are two places to change the cookie name used in login:

/acl_users/credentials_cookie_auth/manage_propertiesForm

and

/acl_users/session/manage_propertiesForm

2016-10-13

How to make the linked object editable in droppable collective.cover tiles

Filed Under:

By default, collective.cover offers one mechanism to "drop" objects to their tiles, by using the "Add content" button at the top. I've received feedback that the button will not be very friendly to editors, so my solution, in this case, is really simple.

In the tile schema, instead of the default:

    uuid = schema.TextLine(
        title=_(u'UUID'),
        required=False,
        readonly=True,
    )

redefine uuid to be such as:

from plone.formwidget.contenttree import UUIDSourceBinder
from z3c.relationfield.schema import RelationChoice

class IMyTile(IPersistentCoverTile):
    uuid = RelationChoice(
        title=u"Linked object",
        source=UUIDSourceBinder(),
        required=False,
    )
This simple change should make the uuid editable with the default contenttree widget.

2016-09-15

Trigger cron style jobs in Plone sites without passwords

Filed Under:

For some time the plone.recipe.zope2instance added support to execute scripts in the context of a full "Zope 2 environment", by using it such as 

bin/instance run /path/to/script

This can be used to launch Python scripts with full support of the Zope machinery, connected to the Zeo server, etc. The script can be generated as a console script from any Plone addon. Here's a small snippet to be used to get a "fully integrated" Plone site:

HOST = 'www.example.com'
PLONE_PATH = '/Plone'    # physical Path of Plone website

def get_plone_site():
    import Zope2
    app = Zope2.app()
    from Testing.ZopeTestCase import utils
    utils._Z2HOST = HOST

    path = PLONE_PATH.split('/')
    plone = path[-1]

    app = utils.makerequest(app)
    app.REQUEST['PARENTS'] = [app]
    app.REQUEST.other['VirtualRootPhysicalPath'] = path
    from zope.globalrequest import setRequest
    setRequest(app.REQUEST)

    from AccessControl.SpecialUsers import system as user
    from AccessControl.SecurityManagement import newSecurityManager
    newSecurityManager(None, user)

    _site = app[plone]
    site = _site.__of__(app)

    from zope.site.hooks import setSite
    setSite(site)

    return site

def main():
    site = get_plone_site()
    site['portal_catalog'].searchResults(...)

Now the "main" function can be registered as console script in the setup.py of the package

setup(
...
      entry_points="""
      # -*- Entry points: -*-
      [console_scripts]
      myscript = my.package.scripts:main
      """,
...
)

 Note: There's also clockserver support in Zope, you can configure as:

<clock-server>
    # starts a clock which calls /foo/bar every 30 seconds
    method /foo/bar
    period 30
    user admin
    password 123
    host www.example.com
</clock-server>

What I don't like about this: it involves saving passwords in config files, which is not very maintainable. Also, the run-based method can be called "on demand", for example by scripts watching a RabbitMQ queue, etc.

2016-08-31

Abusing Plone Content Rules to allow Site Admin customizations of sent emails

This would be a sort of tutorial on how to implement a new plone.app.contentrules Trigger Event, how to write a new plone.stringinterp variable substitutor and how to trigger the event from a browser page (or z3c.form).

This allows configuring a new trigger event from the Content Rules Plone control panel configlet and to assign a new Email action, with customizable body template.

Let's say our task is to implement a contact form:

from plone.directives import form
from plone.api.portal import show_message
from zope.event import notify

class IContactForm(form.Schema):
    name = schema.TextLine(title=u"Your Name", required=True)
    email = Email(title=u"Contact eMail:", required=True)

class ContactInformation(Implicit):
    """ A container to be passed to plone.app.contentrules for contact info
    """

    def __init__(self, name=None, email=None):
        self.name = name
        self.email = email

class ContactForm(form.SchemaForm):

    schema = IContactForm
    ignoreContext = True

    label = u"Contact Form"

    fields = field.Fields(IContactForm)

    @button.buttonAndHandler(u"Submit")
    def handleApply(self, action):
        data, errors = self.extractData()
        if errors:
            self.status = self.formErrorsMessage
            return

        name = data.get('name')
        email = data.get('email')

        obj = ContactInformation(name=name, email=email)
        obj = obj.__of__(self.context)
        notify(ContactEvent(obj))
        show_message(message=msg, request=self.request, type='info')

The ContactInformation object is needed to pass information from the form to the content rules machinery. It needs to be acquisition aware, as plone.app.contentrules will try to trigger events up its chain of acquisition.

We need a custom event with an event handler that triggers the content rules execution:

from zope.interface import implements
from zope.component.interfaces import ObjectEvent, IObjectEvent
from plone.app.contentrules.handlers import execute_rules

class IContactEvent(IObjectEvent):
    """
    """

class ContactEvent(ObjectEvent):
    implements(IContactEvent)

def trigger_contentrules(event):
    execute_rules(event)

Now, a bit of zcml to register the event with plone.app.contentrules and setup the event handler:

<interface
    interface=".events.IContactEvent"
    type="plone.contentrules.rule.interfaces.IRuleEventType"
    name="Contact form triggered"
    />

<subscriber for=".events.IContactEvent" handler=".events.trigger_contentrules" />

This will make the "Contact form triggered" event available for the Plone Content Rules and, thanks to the notify() call in the form handler, trigger the content rules execution.

One last thing that we need is to access the information submitted in the form. The ContactInformation object serves as a container for that information, so we'll need special variables for the Email action:

class contact_email(BaseSubstitution):
    description = u"Contact email"
    category = 'ContactForm'

    adapts(Interface)

    def safe_call(self):
        return self.context.email


class contact_name(BaseSubstitution):
    description = u"Contact name"
    category = 'ContactForm'

    adapts(Interface)

    def safe_call(self):
        return self.context.name
and the zcml for this:
<adapter
    for="*"
    provides="plone.stringinterp.interfaces.IStringSubstitution"
    factory=".stringinterp.contact_email"
    name="contact_email"
    />

<adapter
    for="*"
    provides="plone.stringinterp.interfaces.IStringSubstitution"
    factory=".stringinterp.contact_name"
    name="contact_name"
    />
So why use this mechanism instead of something like PloneFormGen? In my case, the site administrator is used to changing email templates for various events (object created, published, etc) from the Content Rules panel, so why not keep everything together and accustomed to?

2016-08-18

How to fake fix broken persistent objects in ZODB

I have a Zope / Plone website with some old objects created by Products.feedfeeder and they store (for some weird reason) instances of BeautifulSoup objects. These objects were created with BeautifulSoup 3 and the installed version of BS is 4, which moved its classes in the bs4.* namespace. Now, running full-sweep searches in the site or a full catalog reindex fails because of these, now broken, objects.

My solution, because I didn't care for those stored BeautifulSoup objects, was to fake the BeautifulSoup module and patch it into sys.modules:

class NavigableString(unicode):
    def __new__(cls):
        return unicode.__new__(cls)

    def __getstate__(self):
        return self.__dict__


class Tag(object):
    def __getstate__(self):
        return self.__dict__


class BeautifulSoup(object):
    def __getstate__(self):
        return self.__dict__


class fake_bs3(object):
    NavigableString = NavigableString
    Tag = Tag
    BeautifulSoup = BeautifulSoup

import sys
sys.modules['BeautifulSoup'] = fake_bs3
 

2016-06-30

How to completely disable Diazo on a specific path

I'm working on an (inherited) website that uses Plone and BackboneJS to offer a streamlined search interface over a catalog of items.

My task was to apply a new Diazo theme, which worked great for the rest of the website, except for this search page. The items inserted by the Backbone app would be all garbled and wrong. In the end, I managed to isolate the problem to a single page template that would load only that SPA, and the problem still persisted, (on my development machine), while the same code ran ok on the production server.

So what was the difference? The development machine had a Diazo theme active. As soon as I've disabled the Diazo theme, the problem disappeared. Oh well, time for a <notheme /> I said, but that didn't work either: no rule was applied in the transform. After debugging through plone.transformchain.zpublisher.applyTransform and plone.app.theming.transform.ThemeTransform, I came to the conclusion that the serializer used just "breaks" the HTML. It doesn't break it, it fixes it the problems in that HTML template file, but the fixes break the Underscore templates that will transform that file.

So, my fix is a hack:

from ZODB.POSException import ConflictError
from ZPublisher.interfaces import IPubAfterTraversal
from plone.transformchain.interfaces import DISABLE_TRANSFORM_REQUEST_KEY
from zope.component import adapter
import logging

logger = logging.getLogger('pkg.diazotheme')


@adapter(IPubAfterTraversal)
def disable_diazo_for_templates(event):
    """ Code modeled after plone.app.caching.hooks.intercept
    """
    try:
        request = event.request
        if not ("/spa/template" in request.getURL()):
            return

        if DISABLE_TRANSFORM_REQUEST_KEY not in request.environ:
            request.environ[DISABLE_TRANSFORM_REQUEST_KEY] = True

    except ConflictError:
        raise
    except:
        logging.exception(
            "Swallowed exception in pkg.diazotheme "
            "IPubAfterTraversal event handler")

And register this subscriber with:

<subscriber handler=".events.disable_diazo_for_templates" />

But this fix is wrong. My template files came as .html.dtml files placed in a portal_skins layer. The simplest fix would be to just make their content type text/plain. Like, simply renaming them to .txt. With that Content-Type they would be ignored completely by Diazo. Another good way would be (probably) to move them in a static browser resource folder.

2016-06-13

How to use pgloader to migrate sqlite database to postgresql

I needed to migrate a Kotti database, from its default sqlite file store, to Postgresql. Clued in by StackOverflow, I've tried using pgloader, but the version coming with Ubuntu is old: 2.x instead of the brand new 3.x. But the jump to 3.x meant a switch in programming languages as well: the new one is written in Lisp. I didn't want to install and compile the whole Lisp bundle just to run pgloader and I didn't find a binary distribution either, and after a recent exposure to Docker, I thought I'll give the dockerized version of pgloader a try.

After following the steps to install Docker, took me a bit to figure out the process (note: I'm running all this in a VMWare virtual machine, so I can afford taking a lot of unsecure shortcuts):

First, the local Postgresql database needs to be configured to run on an IP, to allow the dockerized pgloader process to connect to it.

sudo vim  /etc/postgresql/9.3/main/pg_hba.conf

Change the network settings to allow connections from all:

# IPv4 local connections:
host    all             all             0.0.0.0/0               md5
Also, we will need to enable listening on TCP connections:
sudo vim  /etc/postgresql/9.3/main/postgresql.conf
and add a listen_addresses line
listen_addresses = '*'		# what IP address(es) to listen on;
Install Dockerized pgloader according to instructions:
docker pull dimitri/pgloader

I've created a file named "convert" with the commands for pgloader, to do the conversion:

load database  
    from 'mydb.db'  
    into postgresql://user:password@192.168.1.20/mydb

with reset sequences, create no tables, include no drop, create no indexes, disable triggers
set work_mem to '200MB', maintenance_work_mem to '512 MB';

For the IP of the postgresql I've used the one attached to eth0.

Because I didn't trust pgloader to convert all the nuances of the database relations, before running the conversion, I've started once Kotti, bin/pserve app.ini so that it will create the initial database structure. After that, truncated the nodes table so that it will erase most of the database content:
psql mydb
truncate table nodes cascade
truncate table nodes principals
Now, the trick is to put this 'convert' file, together with the 'mydb.db' sqlite file in the same folder and map that folder as a volume when running the pgloader command:
sudo docker run --rm --name pgloader -v /path/to/data/:/data dimitri/pgloader:latest pgloader /data/convert

The case of the strange RichText widgets

Filed Under:

On a Plone 4.3 with plone.app.widgets 1.8.0 and plone.app.contenttypes 1.1b5 installed, there's one weird bit of inconsistency:

The TinyMCE widget rendered by the plone.app.contenttype's IRichText behaviour is different from any other RichText field added in the dexterity model. Even on the same page, for example, if I edit the Document dexterity type and add a rich text field, the resulting widget is different. How do I know? Try inserting an image by selecting it, in the popup dialog, from the site content browser. In the case of the IRichText text field, it will work, but it will not work for the default text field. So why is that? The IRichText schema could be nothing simpler:

from plone.app.textfield import RichText as RichTextField


@provider(IFormFieldProvider)
class IRichText(model.Schema):

    text = RichTextField(
        title=_(u'Text'),
        description=u"",
        required=False,
    )

The answer is: it's a different widget, not the one coming from plone.app.textfield. plone.app.widgets replaces the widget for the IRichText.text field:

(see dx_bbb.py):

from plone.app.widgets.dx import RichTextWidget

try:
    from plone.app.contenttypes.behaviors.collection import ICollection
    from plone.app.contenttypes.behaviors.richtext import IRichText
    HAS_PAC = True
except ImportError:
    HAS_PAC = False
...
if HAS_PAC:
...
    @adapter(getSpecification(IRichText['text']), IWidgetsLayer)
    @implementer(IFieldWidget)
    def RichTextFieldWidget(field, request):
        return FieldWidget(field, RichTextWidget(request))

And this is the code that actually fixes the relatedItems popup widget that enables embeding images in the text (from plone.app.widgets.utils.get_tinymce_options ):

        args['pattern_options'] = {
            'relatedItems': {
                'vocabularyUrl': '{0}/{1}'.format(
                    config['portal_url'],
                    '@@getVocabulary?name=plone.app.vocabularies.Catalog'
                ),
                'mode': 'browse',
                'basePath': folder_path,
                'folderTypes': utility.containsobjects.split('\n')
            },
            'upload': {
                'initialFolder': initial,
                'currentPath': folder_url_relative,
                'baseUrl': config['document_base_url'],
                'relativePath': '@@fileUpload',
                'uploadMultiple': False,
                'maxFiles': 1,
                'showTitle': False
            },
            'tiny': config,
            # This is for loading the languages on tinymce
            'loadingBaseUrl': '++resource++plone.app.widgets.tinymce',
            'prependToUrl': 'resolveuid/',
            'linkAttribute': 'UID',
            'prependToScalePart': '/@@images/image/',
            'folderTypes': utility.containsobjects.split('\n'),
            'imageTypes': utility.imageobjects.split('\n'),
            'anchorSelector': utility.anchor_selector,
            'linkableTypes': utility.linkable.split('\n')
        }
The relatedItems options is not filled in in the TinyMCE widget rendered directly by plone.app.textfield. 
 
So, in my case, the fix is to register an adapter:
    Fix RichText widget: the plone.app.widgets provided one has proper relatedItems configuration
    <adapter
        for="plone.app.textfield.interfaces.IRichText mypackage.IMyLayer"
        factory="plone.app.widgets.dx.RichTextFieldWidget"
        />
Which only exposes a bug in collective.cover / plone.app.tiles, due to the way get_tinymce_options works. Oh well, moving on...

2015-05-05

ZODB: How to get and read objects from an undo information

Filed Under:

This is useful for example if you have transactions that cause writes to the database and you don't know what has been written. First, identify the ID of the transaction that you're interested. In the Undo tab of Zope, inspect the checkbox for the transaction and copy the part that looks like an id from its value.

Then, in a zope debugging shell (started with bin/instance debug), I've done:

>>> import base64, cPickle
>>> webtid = "QTY1MjhoaytpMVU9"

# DB will be the "database", as represented in Zope.
>>> db = app._p_jar.db()

# Storage will be an instance of FileStorage. I don't think 
# it's possible to achieve this connected through ZEO.
>>> storage = db.storage

# This is the "real" transaction id
>>> tid = base64.decodestring(webtid + "\n")

# Now we'll get a "transaction position", a position 
# in the filestorage where the transaction begins
>>> tpos = storage._txn_find(tid, True)

# This will be the transaction header. This is the info that's 
# shown in the Undo UI.
>>> th = storage._read_txn_header(tpos)
>>> print th.status, th.descr, th.user

# Now we get the "data position", the position in the zodb where 
# the objects of that transaction sit
>>> pos = tpos + th.headerlen()

# This will be the data header
>>> dh = storage._read_data_header(pos)

# we're very much interested in the oid
>>> oid = dh.oid
>>> pickle_data, tid = storage.load(oid)

# heh, zodb is just a pickle store, you knew that, right?
>>> print pickle_data
>>> print cPickle.loads(pickle_data)

# but it's easier to get the object using the zope machinery
>>> obj = app._p_jar[oid] 
>>> print obj

The zope process needs to be configured with a FileStorage, not a ClientStorage (aka Zeo client).

2015-04-20

The wrong way to sudo su, and the right way

Filed Under:

I've never had a formal training on Linux. My knowledge comes from my experience working on it for the last 12 years, but some things I've just learned as simple recipes and never bother looking them up.

One such thing was changing users with sudo. I've been doing it as:

sudo su <user> -

Notice the dash at the end, which makes provides a "real" environment for <user>, just as if I would have logged in with that user, directly.

But this has ceased to work properly, apparently due to a bug fix in Linux TTY.

The error that I got was:

tibi@localhost:~$ sudo su zope -                                  
bash: cannot set terminal process group (-1): Inappropriate ioctl for device
bash: no job control in this shell

The solution is to switch the position of the dash, to:

sudo su - <user>

2015-03-24

Essential Firefox addons

Filed Under:

Without these addons, Firefox may not be my favourite browser. With them, it's a beast that has no match among all other browsers. So, a top 10 of my favourite extensions:

  • Vimperator: the essential plugin, makes Firefox behave in a modal manner similar to VIM. Includes many shortcuts similar to vim. Integrates an external editor (like GVim) with any textarea. My favourite things with it: really fast opening/closing of tabs, URL address copying and pasting, follow links, etc. Just awsome. The external editor thingy has no easy answer in Chrome, where you'd have to be running an http proxy, because addons are sandboxed and can't access the disk.
  • TreeStyleTabs: another awesome plugin, with no match in the Chrome world, where sidebars are an external window.
  • SessionManager: Firefox has a built-in, but SM is better
  • LastPass: while I have Firefox sync enabled, it is better to be able to access those login information from my mobile devices.
  • Auto Unload Tab: when you have 100+ tabs loaded, it's a requirement. Firefox has an automatic unload tab feature, but it can't be controlled and in my experience, it didn't work well.
  • Ghostery + uBlock: get rid of annoying stuff
  • Flashgot: I use it sometimes to make backups of video streams. For youtube I use youtube-dl.
  • Clearly: easy saving content to Evernote

In the past, Firebug would have been at the front of this list, but right now it's buggy, slows down the browser (switching tabs), so it gets ignored in favour of Chromium.

2012-01-16

A simple epub file renaming utility

Filed Under:

I have a couple of epub files that have random names assigned to them, and I wanted to rename them based on their metadata, in the form Author - Title. Below is what I came up with:

#!/usr/bin/python

from zipfile import ZipFile
import lxml.etree
import os.path
import shutil
import sys

namespaces = {
        'u':"urn:oasis:names:tc:opendocument:xmlns:container",
        'xsi':"http://www.w3.org/2001/XMLSchema-instance",
        'opf':"http://www.idpf.org/2007/opf", 
        'dcterms':"http://purl.org/dc/terms/",
        'calibre':"http://calibre.kovidgoyal.net/2009/metadata",
        'dc':"http://purl.org/dc/elements/1.1/",
        }

def main():
    if len(sys.argv) != 2:
        print "Need a path."
        sys.exit(1)

    fpath = sys.argv[1]
    zip = ZipFile(fpath)
    meta = zip.read("META-INF/container.xml")
    e = lxml.etree.fromstring(meta)
    rootfile = e.xpath("/u:container/u:rootfiles/u:rootfile", 
            namespaces=namespaces)[0]
    path = rootfile.get('full-path')
    opf = zip.read(path)
    e = lxml.etree.fromstring(opf)
    title = e.xpath("//dc:title", namespaces=namespaces)[0].text
    author = e.xpath("//dc:creator", namespaces=namespaces)[0].text


    base = os.path.dirname(fpath)
    new_name = "%s - %s.epub" % (author, title)
    shutil.move(fpath, os.path.join(base, new_name))

if __name__ == "__main__":
    main()

2011-06-28

Getting the superclasses for a python object

Filed Under:

Zope 2 (and Plone) persistent objects usually have an intricate inheritance tree. Finding what classes an object inherits can be a time consuming task, hunting through the various eggs for the relevant source code. Below is a little snippet that shows how to easily get the list of superclasses:

(Pdb) pp type(ff).mro()
(<class 'plone.app.blob.subtypes.image.ExtensionBlobField'>,
 <class 'archetypes.schemaextender.field.TranslatableExtensionField'>,
 <class 'archetypes.schemaextender.field.BaseExtensionField'>,
 <class 'plone.app.blob.field.BlobField'>,
 <class 'Products.Archetypes.Field.ObjectField'>,
 <class 'Products.Archetypes.Field.Field'>,
 <class 'Products.Archetypes.Layer.DefaultLayerContainer'>,
 <class 'plone.app.blob.mixins.ImageFieldMixin'>,
 <class 'Products.Archetypes.Field.ImageField'>,
 <class 'Products.Archetypes.Field.FileField'>,
 <type 'ExtensionClass.Base'>,
 <type 'object'>)

 Credit goes to the original post where I found this.

2011-05-02

Version conflict: zc.buildout's version of madness

Filed Under:

I'm not even trying to understand what happens, because it's aggravating to see buildouts fail like this:

While:
  Installing.
  Getting section zope2.
  Initializing section zope2.
  Installing recipe plone.recipe.zope2install.
Error: There is a version conflict.
We already have: setuptools 0.6c9

or, worse, this:

While:
  Installing.
  Getting section zope2.
  Initializing section zope2.
  Installing recipe plone.recipe.zope2install.
Error: There is a version conflict.
We already have: zc.buildout 1.5.2

Well, technically I know what happens: for example, zc.buildout is latest 2.0a1 now, but I've already installed 1.5.2 in my virtualenv (the bootstrap process failed hard, there are a tons of bugs there, I've had more failures in bootstrap then success, lately) and I had one product in my buildout which depended on zc.buildout, so it tried to pull the latest, only to get a version conflict.

I wish zc.buildout would behave more inteligently. Even specifying 

prefer-final = true

didn't do much to solve my problems. The only way to solve the problem was to add the following to the buildout.cfg file:

[buildout]
...
versions = versions

[versions]
zc.buildout =1.5.2
setuptools = 0.6c9

UPDATE: a better solution is to have:

[versions]
zc.buildout =
setuptools =

2011-05-01

Building PIL with JPEG support on Ubuntu 11.04

Filed Under:

I had problems building PIL with jpeg support on the latest Ubuntu. There are now two libjpeg libraries: one called libjpeg62 and one libjpeg8. Every guide on the net explaining how to compile PIL with jpeg support points to installing libjpeg62-dev. Needless to say, libjpeg8-dev is actually needed to properly build PIL. My reason for initially avoiding libjpeg8 is that it causes libsdlimage-dev to be uninstalled, so it looks like I'll have to juggle packages whenever I want to compile something that requires SDL.

2011-03-09

Export/import users in and out of Plone

Filed Under:

A dirty quick method of importing and exporting the users (only usernames and passwords) out of Plone, using 2 external methods. Code below, not much else to say.

import cPickle

def export(self):
    pas = self.acl_users
    users = pas.source_users
    passwords = users._user_passwords
    result = dict(passwords)

    f = open('/tmp/out.blob', 'w')
    cPickle.dump(result, f)
    f.close()

    return "done"

def import_users(self):
    pas = self.acl_users
    users = pas.source_users
    f = open('/tmp/out.blob')
    res = cPickle.load(f)
    f.close()

    for uid, pwd in res.items():
        users.addUser(uid, uid, pwd)

    return "done"


2011-02-08

Set product configuration globally in zope.conf

Filed Under:

I have a Zope product that needs to write in a centralized location, across multiple instances. The classic Python solution would be to write a variable in a config.py module and read that location from the code, but this feels unelegant in an environment that uses zc.buildout for deployment. The solution I have found is, as follows:

In buildout.cfg, in the instance part definition, add:

zope-conf-additional =
    <environment>
        mylocation ${buildout:directory}/var/mylocation
    </environment>

Next, inside the product code I have:

from App.config import getConfiguration
import os

conf = getConfiguration()
dest = conf.environment['mylocation']
if not os.path.exists(dest):
    os.mkdir(dest)

There were 2 things that I had to research for this task: reading the global zope configuration (that's done with App.config.getConfiguration()) and the fact that you can't add arbitrary key/values in zope.conf and have to use the <environment> section.

2011-01-04

Running Products.Gloworm on Plone 4

Filed Under:

For some reason, the TTW developer tools tend to get neglected in the Plone world. A valuable tools such as Clouseau has fallen out of favour and now Gloworm, the @@manage-viewlets replacement/complement won't run in Plone 4 (at least at version 1.0, which is the latest right now on PyPI).

Fortunately, Gloworm has been updated in svn trunk. To get the latest version you need to add it to sources (in buildout.cfg or develop.cfg):

[buildout]
eggs +=
     Products.Gloworm

[sources]
Products.Gloworm = svn https://weblion.psu.edu/svn/weblion/weblion/Products.Gloworm/trunk/

Next, run in the shell:

#bin/develop co Products.Gloworm
#bin/develop rb

2011-01-02

A pattern for programatically creating Plone content

Filed Under:

I'm importing content from a legacy system to a new website that I'm doing with Plone 4 (wow! what an improvement, in speed and technology) and was looking at the existing documentation on how to programatically create new Plone content. The issue I'm having with the existing documentation is that it's incomplete. It won't give you automatically created ids, you'll have to manually call mutators if you don't know any better, etc.

This is what I have come up with (this code runs in a browser view):

_id = self.context.generateUniqueId("Document")
_id = self.context.invokeFactory(type_name=type_name, id=_id)
ob = self.context[_id]
ob.edit(
     description = "text...",
     subject     = ('tag1', 'tag2'),
     title       = "some title...",
 )
ob._renameAfterCreation(check_auto_id=True)
_id = ob.getId()

One thing I notice is that Plone 4 starts faster, comes with easily pluggable developer tools, and in general feels a lot more polished then any previous Plone releases. It's a good system to work with.

2010-09-19

A miniguide to Dolmen packages

Filed Under:

I'm finally starting a long-overdue project which I have decided to do with Dolmen. As usual, I start by studying its source code and the packages that are available for it. By itself it can will get me about 60% with the requirements for my project, so it's a pretty good starting base. I plan to also study and use some of the menhir.* packages, which are pretty good as generic CMS content types.

dolmen
Dolmen is an application development framework based on Grok and ZTK which also provides a CMS (Content Management System) out of the box. Dolmen is being made with four main objectives in mind: easily pluggable, rock solid and fast content type development, readability and speed.
dolmen.app.authentication
Users and group management in Dolmen
dolmen.app.breadcrumbs
Provides a breadcrumbs navigation for the Dolmen applications. It registers a viewlet to render the links.
dolmen.app.clipboard
Provides a useable "clipboard", that allows you to cut, copy and paste your objects.
dolmen.app.container
Is a collection of tools to work with containers in Dolmen applications.
dolmen.app.content
Provides out-of-the-box utilities for Dolmen applications content.
dolmen.app.layout
Provides ready-to-use components to get a fully functional and extensively pluggable User Interface for a Dolmen application
dolmen.app.metadatas
Forms and viewlets to edit ZopeDublinCore metadata
dolmen.app.search
Viewlets and utilities for permission-aware searching of objects in a Dolmen site.
dolmen.app.security
Roles and permissions for a Dolmen site
dolmen.app.site
The basic Dolmen objects that serve as roots of Dolmen sites
dolmen.app.viewselector
Allows basic management of alternate views
dolmen.authentication
Basic components for authentication
dolmen.beaker
Zope sessions implementation using beaker
dolmen.blob
A layer above zope.file using ZODB blobs as a storage facility. It offers a BlobFile content type and a BlobProperty property for complex schemas.
dolmen.builtins
A set of interfaces that apply to basic Python types, to better integrate them with ZCA
dolmen.content
Base classes and utilities to create content types
dolmen.field
Additional fields usable in schemas. At this moment there's just GlobalClass
dolmen.file
Allows you to manage and store files within the ZODB. It takes the core functionalities of zope.app.file, and simplifies them, using Grok for views and adapters registrations.
dolmen.forms.base
A package in charge of providing basic functionalities to work with zeam.form Forms.
dolmen.forms.crud
A package which helps developers create their C.R.U.D forms using Grok, zeam.form and dolmen.content. It provides a collection of base classes to add, edit, and access content. It innovates by providing adapters to customize the fields of a form.
dolmen.menu
Aims to provide the most flexible and explicit way to create and manage menus and their entries with Grok.
dolmen.queue
A simple layer on top of zc.async to provide queuing of tasks. Not ready?
dolmen.relations
Is a thin layer above zc.relation, allowing a simple and straightforward implementation of standalone relationships between objects.
dolmen.storage
Defines a clear high-level API to deal with pluggable storage components.
dolmen.thumbnailer
Is package specialized in Thumbnail generation. Using the dolmen.storage mechanisms, it allows a pluggable and flexible thumbnail storage.
dolmen.widget.file
A package that walks hand-in-hand with dolmen.file. It provides a useable and pluggable way to render the dolmen.file.FileField in a zeam.form Form.
dolmen.widget.image
A thin layer above dolmen.widget.file providing a widget suitable to fields implementing IImageField. It adds, thanks to dolmen.thumbnailer a preview of the uploaded image in both input and display mode.
dolmen.widget.tinymce
A package that provides a useable and pluggable way to render a text field as a WYSIWG editor in a zeam.form Form.
dolmen.workflow
Nothing here
megrok.icon
Allows registration of icons and associating them with content types
megrok.resourcemerger
Allows concatanation and packing of browser resources (css and js)
menhir.contenttype.document
An example document content type
menhir.contenttype.file
An example file content type
menhir.contenttype.folder
An example folder content type
menhir.contenttype.image
An example image content type
menhir.contenttype.photoalbum
An example photoalbum content type
menhir.contenttype.rstdocument
An example rstdocument content type
menhir.contenttype.user
An example user content type
menhir.library.tablesorter
Registers a jquery based library for HTML tables sorting
menhir.simple.comments
Simple commenting system with avatar integration
menhir.simple.livesearch
A viewlet that provides a livesearch box
menhir.simple.navtree
A viewlet providing a navigation tree
menhir.simple.tag
A tagging engine based on the lovely.tag
menhir.skin.lightblue
A complete skin for a Dolmen site.
menhir.skin.snappy
A skin for Snappy sites
snappy.site
The Snappy, a video sharing sample site
snappy.transform
Mimetype transform utilities. Not finished?
snappy.video.flasher
Utilities to mark files as Flash and allow to view them.
snappy.video.player
A video player for flash movies
snappy.video.transforms
Convert video files to flash movies and thumbnails
zeam.form.base
A form library designed to be grokish and simple
zeam.form.ztk
zope.schema integration for zeam.form. It provides widgets and default CRUD style actions.
dolmen-documentation
A few tutorials for Dolmen
dolmenproject
A Paste script extension that allows quick bootstrapping of new Dolmen projects

To download all the packages, I've ctrl+selected the git repositories names from http://gitweb.dolmen-project.org/, pasted them into a repositories.txt file and ran the following script:

 

import subprocess
import os

f = open('repositories.txt')
for line in f.readlines():
    git = line.strip()
    pkg = git
    if pkg.endswith('.git'):
        pkg = ".".join(pkg.split('.')[:-1])

    if os.path.exists(pkg):
        subprocess.check_call(['git', 'pull'], cwd=pkg)
    else:
        subprocess.check_call(['git', 'clone', 'git://devel.dolmen-project.org/' + git])

 


Weblog
Atom
RDF
RSS 2.0
Powered by Quills
Creative Commons License
This work is licensed under a Creative Commons Attribution 3.0 License.
 

Powered by Plone CMS, the Open Source Content Management System

This site conforms to the following standards: