Personal tools
You are here: Home Weblog Archive 2006

Entries For: 2006

2006-12-21

Why do the GIMP developer hate us?

Filed Under:

I'm working on a website for which I have received PSD templates and I just need to do a Plone skin based on the templates. I'm using GIMP for various tasks, like trimming images, exporting for web, etc. Now that I have mapped some key shortcuts to their old Photoshop equivalents, to which I am used, Gimp seems more bearable. Not by much, though. Why do the Gimp developers decided that it's a cool and clever idea to develop this application in this way? When I'm working on an image, I want it full screen, I don't want the icons on the desktop drawing my attention, I want larger workspace, etc. Unfortunately, the entire interface becomes very cluttered by the tool windows, to which you have no real choice of placing them on top. I mean, I can place the layers dialog, or the tools window on top, but the tool window is actually Gimp, so next time when I open Gimp and try to save a picture, I have to wonder why there's no dialog window (which will appear underneath the picture window, because the picture window will be created as on top).

Krita seems a lot better in regards to normal workflow, but it's missing some features that Gimp seems to have. Don't get me wrong, I think Gimp is good enough for like 30% of the necessary things to do when doing web design, but every time I use it I feel like banging my head to the keyboard. There's a very deep feeling of frustration that shouldn't be caused by OSS software. Really.

2006-12-15

Permission problems with adapters

Filed Under:

Recently I encountered a permission problem that I guess can be tipical when working with adapters based on marker interfaces (such as implementing IRatableItem with a class and adapting it to IRating).

The code is classic simplistic example of adaptation, using the IAnnotation to store a rating, with the adapter being something along these lines:

from interfaces import IRating
from zope.annotation.interfaces import IAnnotations
from zope.interface import implements

class RatingAdapter(object):
    implements(IRating)

    def __init__(self, context):
        self.context=context
        self.KEY="ratingAdapter.stage.12_0"
        try:
            rating=IAnnotations(self.context)[self.KEY]
        except KeyError:
            IAnnotations(self.context)[self.KEY]=0

    def getRating(self):
        return IAnnotations(self.context)[self.KEY]

    def setRating(self, ratingValue):
        IAnnotations(self.context)[self.KEY]=ratingValue

    rating = property(getRating, setRating)

This class sits in a separate package called "rating". The interfaces for this package:

from zope.interface import Interface
from zope.schema import Int

class IRating(Interface):
    """Rating for an item"""
    rating = Int(title=u"Rating")

    def getRating():
        "Returns a rating"

    def setRating(value):
        "Sets a rating"

class IRatable(Interface):
    """Marker interface for rating"""
    pass

The adapter that does the adaption from IRatable to IRating is registered with

<adapter
        for=".interfaces.IRatable"
        provides=".interfaces.IRating"
        factory=".ratingadapter.RatingAdapter"
        trusted="True"
        />

The adapter is set to trusted because it needs access to __annotations__, to be able to store the rating in the annotation. Now all it should needed to do to have ratings on objects is to declare it to implement IRatable and integrate its views with the rating setting methods.

Here comes the problem

I have a content item, let's say IBottle, with an edit form declared like this:

class BottleEditForm (EditForm):
    form_fields = Fields (IBottle, IRating)

The form generation will fail complaining about a forbidden attribute, "rating". Apparently, the adapter class needs to have security declarations as well, with something like this (declared in the rating package):

<class class=".ratingadapter.RatingAdapter">
        <require
            permission="zope.ManageContent"
            set_schema=".interfaces.IRating"
            interface=".interfaces.IRating"
            />
    </class>

I found traces of this problem mentioned around the web, in these two locations:


2006-11-23

Setting a dynamic i18n:domain in a ZPT template

Filed Under:

<h2 tal:define="statusMessage python:request.get('statusMessage');
                   domain python:request.get('domain')"
       tal:attributes="i18n:domain domain;
                       i18n:translate string:">someText</h2>

2006-11-07

Getting the parent object in an acquisition context

Filed Under:

To get the parent of an object, you'd have to use this code:

myparent = aq_inner.aq_parent.aq_self

2006-11-06

One liner to get the common elements of several lists

Filed Under:

While doing an exercise with a mockup catalog and indexes, I ran into the problem of filtering several lists and returning the common elements from the list. The following example demonstrate the usage of reduce(), one of the functional programming constructs that are less common and obvious in their usage.

>>> a = [1,2,3,4]
>>> b = [2,3,4,5]
>>> c = [1,2,4,6]
>>> d = [0,2,3]
>>> z = [a,b,c,d]
>>> z = reduce(lambda i,j:list(set(i).intersection(j)), z)
>>> z
[2]

2006-10-29

Pitfall of building python from source on Ubuntu

Filed Under:

Zope 3.3 doesn't work well with the python 2.4.4 that comes with Ubuntu Edgy. Something about a readline change somewhere in the python standard library. So I had to build python 2.4.3 from source. Being an impatient being, I haven't give it much thought and just unpacked the python tarball, hit ./configure and make install. I ended up with python installed in /usr/local/bin/python, which would be really really cool if Ubuntu wouldn't depend on python and wouldn't place /usr/local/bin at the beginning of $PATH. The problem became apparent when a simple upgrade failed because of a missing xdg python package.
I changed the PATH and erased /usr/local/bin from it, restarted the update and everything was fine. Searching for a bit in /etc revealed a login.defs file that contains the PATH as it would be defined for the users. I changed this file, but I don't have the patience to update now, so I presume that on my next update /usr/local/bin would be moved to the back of the $PATH, as I have specified in it.

2006-10-27

Catching and printing python exceptions

Filed Under:
>>> import traceback, sys
>>> try:
...     1/0
... except:
...     traceback.print_exc(sys.exc_info())
...
Traceback (most recent call last):
  File "<stdin>", line 2, in ?
ZeroDivisionError: integer division or modulo by zero
>>>

2006-10-24

How to define a new skin in Zope 3

Filed Under:

Lifted from zope.app.rotterdam

  • Create a new browser skin layer
from zope.publisher.interfaces.browser import IBrowserRequest
from zope.publisher.interfaces.browser import IDefaultBrowserLayer

class rotterdam(IBrowserRequest):
"""Layer for registering Rotterdam-specific macros."""

class Rotterdam(rotterdam, IDefaultBrowserLayer):
"""The ``Rotterdam`` skin.

It is available via ``++skin++Rotterdam``.
"""
  • Register the skin in configure.zcml
<interface
interface="zope.app.rotterdam.Rotterdam"
type="zope.publisher.interfaces.browser.IBrowserSkinType"
name="Rotterdam"
/>

Declaring a resource and a page for the skin

 <browser:resource
name="zope3.css"
file="zope3.css"
layer="zope.app.rotterdam.rotterdam" />

<browser:page
for="*"
name="standard_macros"
permission="zope.View"
class=".standardmacros.StandardMacros"
layer="zope.app.rotterdam.rotterdam"
allowed_interface="zope.interface.common.mapping.IItemMapping"
/>

2006-10-22

Bulk modifying AT content fields under restricted python scripts

Filed Under:

I'm working on a project that mostly extends an older, legacy based Plone 2.0 project. This project has a CMFFormController form with about 20 fields and based on that input needs to create new AT content items, which is done in the action script of the controller chain. To achieve this, I have the following code:

First, I have a field_mapping that maps the fields from the form to the AT field names. Based on this mapping, I'm retrieving the value from REQUEST and set it back in the mapping (the form generation changes the field names to concatenate with a session id, in order to save the already submited values in a session, as far as I understand).

    #write the values into the above dict
    for key in field_mapping:  
        field = request.get('%s_%s' % (field_mapping[key], sid))
        field_mapping[key] = field

The form also has some field upload fields, which I'm handling with this code:

    #handle the empty file uploads
    for f in ['file1', 'file2', 'file3']:
        fupload = request.get(f, None)
        fname = getattr(fupload, 'filename', '')
        if fname.strip() <> '':
            field_mapping[f] = fupload

In the end, I'm calling the mutators for each field to set the values to those retrieved from REQUEST.   Calling Schema[fieldname].set() is impossible to do from the restricted Python script because of the security settings.

    schema = myobj.Schema()
    for fname in field_mapping:
        field = schema[fname]
        mutator = field.getMutator(proposal)
        mutator(field_mapping[fname] )

I've first tried to pass the field_mapping as **field_mapping argument to the plone_utils.contentEdit method, but it doesn't seem to update AT based objects, just the attributes.

2006-10-21

Deliverence - serving semi-static content out of a live site

Filed Under:

Deliverance is a lightweight, semi-static system for content delivery of CMS resources. It runs in mod_python, generating branded pages and navigation elements, giving high-performance throughput to anonymous visitors.

Sounds interesting, especially in light of my contact with owners of bigger sites and editorial staff.

This product can be downloaded from http://codespeak.net/svn/z3/deliverance/

Custom traversing with Five and ITraversable

Filed Under:

This blog already contains a technique on how to customize the traversal to return any object, using __bobo_traverse__

A more simple, modern and elegant way of doing achieving this is detailed in the newly released ImageRepository. Basically, it uses an adapter to change the implementation for ITraversable when the traversal is done on an object implementing a marker interface. I've lifted the relevant code and pasted it below, but the original sources should be consulted for reference.

class ImageRepositoryTraversable(FiveTraversable):
    """Intercepts traversal for IImageRepository, but only for 'tags'.
    Everything else is left untouched.
    """

    def traverse(self, name, furtherPath):
                 return some_obj

Next, the zcml configuration file contains:

  <five:traversable class=".content.ImageRepository.ImageRepository" />

  <adapter
      for=".interfaces.IImageRepository"
      provides="zope.app.traversing.interfaces.ITraversable"
      factory=".traversal.ImageRepositoryTraversable" />

2006-10-20

Easy access to the zope namespace and modules

Filed Under:

Sometimes I just want to test a package under the zope namespace (installed usually in /opt/Zope2.9/lib/python or /usr/local/Zope3.2/lib/python), like for example the zope.testbrowser the other day. The easiest way to achieve this, without messing around with PYTHONHOME environment variables, or appending paths to sys.path is to change directory to the lib/python folder and start the python interpretor there. Because python will look in the current folder for modules and packages, they will be available in the interpretor. Profit! :-)

2006-10-19

Testing file uploads fields with zope.testbrowser

Filed Under:

For some reason, the set_file method is not available for ListControls file upload objects inside the Browser object. To be able to fill in the file field, one needs to do

myfilecontrol.mech_control.set_file(filestream, mimetype, filename).

The problem gets weirder as set_file() is the method indicated by the README.txt doctest of zope.testbrowser package as the way to upload a file stream in a file upload widget, and I presume the README.txt test doesn't fail.

2006-10-15

Adding an overrides.zcml

Filed Under:

According to this discussion on #zope3-dev, one must do the following to get overrides to take effect:

  • add an product-overrides.zcml in which to include <include package="foo" file="overrides.zcml" />
  • the *-overrides.zcml slugs needs to be loaded with includeOverrides

2006-10-10

AdvancedQuery and other portal_catalog tricks

Filed Under:

The normal ZCatalog queries and indexes are extremely limited. For example, I had a need to check for a "Value not in KeywordIndex" expression, which is impossible to do with the normal catalog. AdvancedQuery comes to the rescue, but things aren't too obvious there either. AQ has a In() expression that can be used in building queries, but it does the reverse thing: check something like "IndexedValue not in ListOfValues".

The solution is to check for Eq(), just like the normal KeywordIndex syntax

query = ~ Eq('getFilteredDomains', domain)

In my query, `getFilteredDomains` is the index name and `domain` holds the value I want to exclude from results.

In related news, who knew that unrestrictedSearchResults() can return results unaffected by the roles or permissions that the user has from a normal ZCatalog. I wish I knew this, I had to resort to all sorts of proxy tricks to do this. The only requirement is that this method is called from inside python code, so no python scripts. Guess I need to read the source and documentation next time :-)

2006-10-07

Little bits of info about CMFFormController

Filed Under:

One thing the documentation doesn't clearly state: you can set the status to a value and have that value defined as an action in the metadata file, basically redirecting the flow of the controller sequence to that action.

To make things clear, I'll paste some code. In the login_initial.cpy.metadata file from CMFPlone we have this bit of code:

[actions]
action.success=traverse_to:string:login_next
action.login_change_password=traverse_to:string:login_password

So basically we have two actions defined, depending on the type of status returned.

In the login_initial.cpy file there's this snippet:

# afterwards, change the password if necessary
if state.getKwargs().get('must_change_password',0):
    state.set(status='login_change_password')
return state

So, if the there's a 'must_change_password' in the state, the next action will be login_password.

Another method to achieve this would have been using the setNextAction method, something like this:

state.setNextAction('redirect_to:string:%s/createObject?type_name=ServiceProviderHome' % home.absolute_url())
return state

NOTE: to use setNextAction you shouldn't define any action in the metadata file. According to the documentation, 'As with the .metadata file, the default action specified in the script can be overridden via the ZMI. This allows site managers to override post-script actions without having to customize your code.'

Hacking at Plone membership's core: different content types for member folders

Filed Under:

I'm using this technique for a site created with Plone 2.1, but it I think it can work on Plone 2.5 as well. Basically, I need a site with different membership types, and each membership type has a "personal area" (different member folder) where the user can add different object types and generally have a completely different browsing experience. I haven't implemented anything exotic such as CMFMember (not future proof) or membrane (not compatible, don't want to mess around yet) so I'm sticking with plain Plone users.

No 1 on the list is monkey patching MembershipTool to be able to specify more then one type of member folders. The module below (patch.py) also shows what monkey patching is about (look for clues at the end of the snippet):

#patching below
from Products.CMFCore.utils import getToolByName
from Products.CMFPlone.utils import _createObjectByType
from AccessControl import ClassSecurityInfo, getSecurityManager
from Products.CMFPlone.MembershipTool import MembershipTool

def createMemberarea(self, member_id=None, minimal=1, areaType=None):
    """
    Create a member area for 'member_id' or the authenticated user.
    areaType is a string that contains a portal type.
    """
... <SNIPED>

    if hasattr(members, member_id):
        # has already this member
        # TODO exception
        return

#START CHANGES
    if areaType is not None:
        memberarea_type = areaType
    else:
        memberarea_type = self.memberarea_type
#END CHANGED

    _createObjectByType(memberarea_type, members, id=member_id)

... <SNIPED>

#monkeypatching

print "Monkey patching Products.CMFPlone.MembershipTool.MembershipTool to provide aditional member folders types..."

security = ClassSecurityInfo()

security.declarePublic('createMemberarea')
MembershipTool.createMemberarea = createMemberarea
MembershipTool.createMemberArea = createMemberarea

print "Done."

In the Product/__init__.py file add a simple 'import patch' to enable loading the patch.

Because I've created a complete alternate login system (again, different user experiences, duplicating the plone_login folder seemed easier to get this running), it is easy for me to plug in the special member folder creation. I've customized logged_in.cpy to contain:

membership_tool.createMemberArea(areaType='ServiceProviderHome')

instead of the usual

membership_tool.createMemberArea() 

I'm not feeling very smart for this site because of the complicated setup: monkey patching, duplicating plone_login, etc, but this is what it works and it's easiest for me right now. Perhaps the future Plone 3 will make this kind of things easier (extending the MembershipTool with new behaviour).

Creating zope content

Filed Under:

I had a need to create new external methods on install of a product. So, even if technique is old and well know to any Zope 2 old-timer, I'm placing this here for my own reference.

def install(portal):
    portal.manage_addProduct['ExternalMethod'].manage_addExternalMethod(
        id = 'emailMe',
        title='emailMe',
        module='MyProduct.emailMe',
        function='emailMe')

The code needs to interact with a legacy module (renamed here emailMe). The idea is that manage_addProduct returns a dictionary of factories and manage_addExternalMethod is the factory method defined in the ExternalMethod module. Most of the old style Zope content can be added this way.

Get a translated object in a particular language

Filed Under:

Sometimes there is a need to get a translated piece of content in a particular language. Some examples include messing around with ATVM or stitching together a front page for a multilanguage website from editable content (site editors love pretty interfaces where they can tweak and edit every piece of a website). This piece of code is a bit older, but still does the job well. The boundLanguages/getLanguageBindings thing could be replaced with getPreferredLanguage() and I'm pretty sure LinguaPlone got a prettier API than this, but it's a starting point in the right direction. I probably should replace this code more "modern" stuff when I get the time.

#bind context=context

page='training_description'

boundLanguages = context.portal_languages.getLanguageBindings()
prefLang = boundLanguages[0]
obj = getattr(context, page, None)
if obj == None:
return ""
trans = obj.getTranslations()

if trans.has_key(prefLang):
return trans[prefLang][0].CookedBody()
else:
return obj.CookedBody()

2006-10-06

Internationalization with Plone

Filed Under:

To generate automatically a .pot file necessary for translating msgids inside template files you need to get i18ndude (really easy since it was placed in cheeseshop):

sudo easy_install i18ndude

Since I'm using ArchGenXML, I already have a generated.pot file created. I've renamed this file to old.pot and ran this (in the i18n folder):

i18ndude rebuild-pot --pot ./generated.pot --merge old.pot --create MyProduct ./../skins/MyProduct/

This command builds a pot file by looking at files in the skins folder and merging the extracted msgids with the ones in old.pot. Of course, the templates need to have the i18n:translate entries inside in order to be recognized by i18ndude. Now that I have the pot file, I can use KBabel to create the translated .po files. Only thing left to do is make sure my templates use the MyProduct i18n domain. To load the catalog I needed to restart zope. If the wrong catalog is loaded, delete the catalog from the Zope Control Panel > PlacelessTranslation service and the .mo file from the <zope_instance>/var/pts, after which zope has to be restarted.

The rest of the internationalization (translated content) is beautifully covered by LinguaPlone.

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: