You are not logged in Log in Join
You are here: Home » Members » rthaden's Home » LocCMFProduct HowTo

Log in
Name

Password

 

LocCMFProduct HowTo

 

Created by rthaden . Last modified 2003-11-20 01:03:27.

HowTo build a CMF product which contains text and metadata in multiple languages with Localizer.

This HowTo explains how to build a file system based product with several folderish and contentish classes inside (resulting in more than one type in the types tool). Also the content is multilingual.
If you don't need the multilingual stuff, just leave out the Localizer things so you have a HowTo for building a simple product for CMF.

This howto was created in Aug. 2002, so there are many changes in the CMF until then. Also Plone came up and Archetypes, so this may be a bit outdated. But you still can learn something here.

Some parts of the sourcecode were inspired or directly used from CMFTutorial by Alexandre Passant (apa@makina-corpus.org) and from PortalContentFolder by Florent Guillaume (fg@nuxeo.com)

The product can be found here

Contents

Some history: I was in the need of building a new website for a department of the university where I am working. First I thought about the different items needed. The central point are the data of the employees (staff, students, secretary, etc.). Then there are research areas and projects categorized by these areas. Each project and area has a contact person. Additionally there are diploma theses and news items. For each of these parts a class is built. Our website is presented in german and english so there must be a solution to have the content multilingual. I will only present a simple version with two classes Employee and Project.

First we need some base classes from CMFCore and CMFDefault to get the basic CMF functionality:

PortalContent to let our objects play with the types tool and CMF UI. It subclasses CMFCatalogAware so our objects are catalogged and workflow aware.
SkinnedFolder makes our objects act like a container, so we can add other items to it. In the actions box, the folder contents action will be displayed. We can e.g. add an image to each object of the type Employee, which we will build. It is also catalogged and workflow aware (as PortalFolder isn't since CMF1.3). More details below. SkinnedFolder also subclasses CMFCatalogAware.
DefaultDublinCoreImpl supports the DublinCore metadata elements.

We want to have a multilingual website so we need a tool to make the attributes of our class multilingual. The Localizer product provides this. Unfortunately the DefaultDublinCoreImpl which provides title, description etc. is not multilingual. We will build a new class overriding these attributes by subclassing LocalPropertyManager and DefaultDublinCoreImpl. The new class is called LocDefaultDublinCoreImpl.

Further we need some utility functions which can be used in PageTemplates and Python scripts. We will put them in a loc_utils.py file and let them be accessible by through-the-web (TTW) code (Python scripts and page templates). Actually in this example, there is no useful application for such a utility method. You'll find two methods in the loc_utils.py, only one is used in the Project_edit_form.

Now the details:
First we add a directory in $ZOPEPATH/lib/python/products with the name of our product (LocCMFProduct). We start to build the employee class in a file Employee.py:

__version__ = "0.1.1"

__doc__ = """Employee (Name, eMail, etc.)"""

## ZOPE imports from Globals import InitializeClass from AccessControl import ClassSecurityInfo from Acquisition import aq_base

## CMF imports from Products.CMFCore.WorkflowCore import WorkflowAction from Products.CMFCore import CMFCorePermissions from Products.CMFDefautl.SkinnedFolder import SkinnedFolder from Products.CMFCore.PortalContent import PortalContent

# instead of DefaultDublinCoreImpl, LocDefaultDublinCoreImpl is used # to provide localizer support for metadata. See LocDublinCore.py from Products.LocCMFProduct.LocDublinCore import LocDefaultDublinCoreImpl

# Import the needed classes from Localizer from Products.Localizer.LocalPropertyManager import LocalProperty from Products.Localizer.LocalPropertyManager import LocalPropertyManager

# provide some default values for DublinCore elements Rights and Subject. # This may not be very elegant, but it works RIGHTS=Copyright 2002, The spanish inquisition SUBJECT={'de':Mitarbeiter der Spanischen Inquisition, \ 'en':'Employee of the spanish inquisition'}

Provide information for the types tool. This information is used in __init__.py to tell the types tool which actions are supported, which string is displayed in the add new contents UI etc.
factory_type_information = (
    {id       : Employee,
    meta_type : Employee,
    description   : (Add data for an employee),
    product   : LocCMFProduct,
    factory   : addEmployee,
    'immediate_view': folder_contents,
    actions   : (
    {id       : view,
     name     : View,
     action   : Employee_view,
     permissions  : (CMFCorePermissions.View,),
     },
     {id      : edit,
      name    : Edit,
      action  : Employee_edit_form,
      permissions : (CMFCorePermissions.ModifyPortalContent,),
         },
    { id      : metadata,
      name    : Metadata,
      action  : loc_metadata_edit_form,
      permissions : (CMFCorePermissions.ModifyPortalContent,),
    },
    ),
    },
)
Construct one object of the class and add it to the database. This method is outside the class because it's called before the instance of the class is constructed. It provides some default values for attributes, which are set in the __init__ method of the class. It is called automatically after construction.
def addEmployee(self,
                id,
                Firstname=Pedro,
                Lastname=Xeminez,
                eMail=pxe@spaninq.com,
                Shorttext=NOBODY expects the spanish inquisition,
                localizer_languages=(de,en)
        ):
    """ Create an empty Employee """
    Employee_object = Employee(id,
                               Firstname,
                               Lastname,
                               eMail,
                               Shorttext,
                               tuple(localizer_languages)
                               )
    self._setObject(id, Employee_object)

localizer_languages provides 2 languages, so right now the languages are set to fixed values. This can be changed to accept any languages that are set in the Localizer instance. I'll leave this to anyone with time and will to do it.

Now we define the Employee class:
It subclasses LocDefaultDublinCoreImpl (DefaultDublinCoreImpl with Localizer support) to provide metadata
SkinnedFolder to be able to contain other objects.
PortalContent to be catalog and workflow aware

When we're subclassing the order is important. The way Python looks up attributes in the base classes that are not in the top class is from left to right and from the top to the bottom. Let's say we want to access the title attribute. Employee itself doesn't have it, so it's looked up in the base classes starting with LocDefaultDublinCoreImpl. title is found there, and returned depending on the currently selected language because it is wrapped by LocalPropertyManager.
If we derived as follows: Employee(PortalContent, LocDefaultDublinCoreImpl, SkinnedFolder) the title attribute is first looked up in PortalContent. This doesn't have a title attribute so it's looked up in the base classes of PortalContent until it is found in SimpleItem. From there we don't get support of multiple languages!

Another important thing is the contentish and folderish behaviour of our class which depends on the order of PortalContent and SkinnedFolder. I tried different versions and digged in the sourcecode and also tried to use PortalFolder instead of SkinnedFolder. Here are some thoughts:

If we use PortalFolder instead of SkinnedFolder then we lose the ability of being catalogged because the methods indexObject, reindexObject and unindexObject are overridden in PortalFolder. They do nothing (pass) so PortalFolders can not be catalogged or being subjected to a workflow since CMF1.3. If we derived from PortalContent before PortalFolder then the indexing methods would behave as desired, because they are found first in CMFCatalogAware which is a base class of PortalContent. But the drawback is that a part of the folderish behaviour is lost because the objectIds, objectItems and objectValues are overridden in SimpleItem which PortalContent is derived from to return an empty tuple, so the contents of our folderish object are not displayed. We could override these methods with the defaults or just use SkinnedFolder which is catalogged and derive from it before deriving from PortalContent. Another important attribute is isPrincipiaFolderish which determines if the class has a folderish behaviour. If this is set to 0 the folder contents action e.g. is not displayed making it impossible to add content to our class instance. If we derived from PortalContent at first isPrincipiaFolderish is set to 0.

So finally our class definition looks like this:

 class Employee(LocDefaultDublinCoreImpl, SkinnedFolder, PortalContent):
 
Since CMF1.3 we have to deliver the portal_type. CMF versions before 1.3 used to set the portal_type to the meta_type automatically
    portal_type = meta_type = Employee
Get an instance of ClassSecurityInfo to tell Zope, which parts of our class are public or private, which means: are the parts accessible by through-the-web code and which permissions are needed to access them.
    security = ClassSecurityInfo()
    security.declareObjectPublic()
One of our properties is multilingual. It is wrapped by the LocalProperty class. Inside LocalProperty a dictionary is used like
'en':'english text'
'de':'german text'
and a call to this property will return the parameter in the language determined by one of the mechanisms Localizer uses (e.g. cookie: LOCALIZER_LANGUAGE=en or path http://localhost/en/obj which adresses the english version of http://localhost/obj if the Localizer resides in the root)
    Shorttext = LocalProperty(Shorttext)
    # LocalPropertyManager needs some metadata,
    # this is similar to the PropertyManager mixin class.
    _local_properties_metadata = ({'id': Shorttext, 'type': 'text'})
The __init__ method is called after constructing the object to initialize it with some default values (which are defined in the addEmployee method above)
    def __init__(self,
                id,
                Firstname='',
                Lastname='',
                eMail='',
                Shorttext='',
                localizer_languages=''
                ):

""" Initialize an instance of the class """

## parents constructors, initialize the LocDefaultDublinCoreImpl LocDefaultDublinCoreImpl.__init__(self)

## set the default values for this object self.id = id self.Firstname=Firstname self.Lastname=Lastname self.eMail=eMail

iterate through localizer_languages and
1. add the language to the underlying LocalPropertyManager
2. assign the default values
        for language in localizer_languages:
            self.manage_addLanguage(language)
            self._setLocalPropValue(Shorttext, language, Shorttext)
            self.setSubject(SUBJECT[language], language)
            self.setLanguage(language, language)
        self.setRights(RIGHTS)
The _edit method changes the attributes of this class. It is not callable by TTW code because it's declared private. Below is an edit method, which is made public and calls _edit The arguments passed here are None by default. So it is possible to pass e.g. only one of the arguments. Inside the method it is checked if an argument is passed before the value is changed.
    security.declarePrivate( _edit )
    def _edit( self,
               Firstname=None,
               Lastname=None,
               eMail=None,
               Shorttext=None,
               localizer_language=None,
               ):

if Firstname is not None: self.Firstname=Firstname if Lastname is not None: self.Lastname=Lastname if eMail is not None: self.eMail=eMail # Since Shorttext is multilingual it has to be treated different from # the other properties: if Shorttext is not None: self._setLocalPropValue(Shorttext, localizer_language, Shorttext)

define an edit method, which is accessible by TTW-code but protected by the Modify Portal Content permission
    security.declareProtected( CMFCorePermissions.ModifyPortalContent, edit )
    edit = WorkflowAction( _edit )

SearchableText returns some property values to the portal_catalog to allow a text search inside these properties. If we want to let multilingual properties to be searched we have to pass a value for each language. LocalPropertyManager allows to access a property in a defined language by adding the language code with an underscore as seen below

    def SearchableText(self):
        """ Method used by search engine """
        return "%s %s %s %s %s" \
                                 %( self.id,
                                    self.title_de,
                                    self.title_en,
                                    self.Firstname,
                                    self.Lastname,
                                   )

########################## ## Class Initialisation ## ##########################

InitializeClass(Employee)

O.K. that was the first class. In the product there's a second class Project, which behaves similarly to this one so it is not documented here. You find it in the downloadable product. Now we have to do some initialization and therefore create a file __init__.py:

__version__ = "0.1.0"

__doc__ = """Initialise Product"""

import Employee import Project from Products.CMFCore import utils, CMFCorePermissions, DirectoryView from AccessControl import ModuleSecurityInfo

import sys this_module = sys.modules[ __name__ ] import loc_utils

loc_utils.py contains some useful Python methods. We collect them in a single file instead of placing dozens of small Python scripts in the skins directory. These methods must be registered in the security machinery to be callable by TTW-code. We use setDefaultAccess(allow) to let all methods be callable.
ModuleSecurityInfo(Products).declarePublic(LocCMFProduct)
ModuleSecurityInfo(Products.LocCMFProduct).declarePublic(loc_utils)
ModuleSecurityInfo(Products.LocCMFProduct.loc_utils).setDefaultAccess(allow)

loc_globals = globals()

We need to register the construction methods and the classes:
contentConstructors = (Employee.addEmployee,
                       Project.addProject,
                       )
contentClasses = (Employee.Employee,
                  Project.Project,
                  )
Register the skins directory:
DirectoryView.registerDirectory(skins, loc_globals)
DirectoryView.registerDirectory(skins/LocCMFProduct, loc_globals)

z_bases = utils.initializeBasesPhase1(contentClasses, this_module)

We need to combine the factory type information of each class here:
factory_type_information = ( Employee.factory_type_information
                           + Project.factory_type_information
                           )
def initialize(context):
    utils.initializeBasesPhase2(z_bases, context)
    utils.ContentInit(LocCMFProduct,
                      content_types = contentClasses,
                      permission = CMFCorePermissions.AddPortalContent,
                      extra_constructors = contentConstructors,
                      fti = factory_type_information
              ).initialize(context)

We have to add a language selector in our pages. We will do this by modifying the main_template. So we copy main_template.pt from \lib\python\Products\CMFDefault\skins\zpt_generic\main_template.pt to our skins/LocCMFProduct directory. We modify it to display a language selection box in the upper right corner and replace

  <p id="Breadcrumbs" style="padding-top: 5px">
   <span tal:repeat="bc here/breadcrumbs"
    ><a href="."
        tal:attributes="href bc/url" tal:content="bc/id"
     >ID</a><span tal:condition="not: repeat/bc/end"> / </span>
   </span>
  </p>
with
   <table id="Breadcrumbs" style="padding-top: 5px" width="100%">
    <tr>
     <td>
      <span tal:repeat="bc here/breadcrumbs">
       <a href="."
            tal:attributes="href bc/url" tal:content="bc/id">ID
       </a>
       <span tal:condition="not: repeat/bc/end"> / </span>
      </span>
     </td>
     <td align="right">
      <tal:block define="Localizer nocall:root/Localizer"
                 content="structure Localizer/changeLanguageForm" />
     </td>
    </tr>
   </table>

Now we need to implement a view and edit interface. We place two files called Employee_view.pt and Employee_edit_form.pt in a directory /skins/LocCMFProduct inside our product directory.

First we edit the view for our class in Employee_view.pt:

Define the namespaces for tal and metal and use the master macro from main_template:

<html xmlns:tal="http://xml.zope.org/namespaces/tal"
      xmlns:metal="http://xml.zope.org/namespaces/metal"
      metal:use-macro="here/main_template/macros/master">
We set the base in the response to our page. If we don't do this the base will be differ if we call http://localhost:8080/folder/emp1/Employee_view or http://localhost:8080/folder/emp1. In the latter case, the base is http:// ... /folder, so e.g. images inside our folderish class will not be referenced correctly.
  <metal:block fill-slot="base">
      <base href=""
            tal:attributes="href python: here.absolute_url() + '/'">
  </metal:block>

<body>

We fill the header slot with nothing
<div metal:fill-slot="header">
 </div><!-- header slot -->
In the main slot we import our module with the utility Python methods and reference to an image called img. We can add an image with the id img to our folderish object later. Additionally we place an image in the folder containing the Employee instances, so that it will be loaded as default if no image is inside an Employee instance.
 <div metal:fill-slot="main">
 <!-- import loc_utils-->
 <div tal:define="loc_utils python:modules['Products.LocCMFProduct.loc_utils']">
    <table cellpadding="2" cellspacing="10">
      <tr>
       <td valign="top"> <img src="#" width="200" 
           tal:attributes="src string:img"> </td>
       <td valign="top">
        <h1>
         <span tal:replace="here/Firstname">Firstname</span>
         <span tal:replace="here/Lastname">Lastname</span>
        </h1>
        <div id="DesktopDescription">
         <span tal:replace="here/description"> Description </span>
        </div>
        <br>
        <table width="100%" border="0" cellspacing="2" cellpadding="2">
          <td>e-Mail:</td>
          <td> <a href="#" tal:attributes="href string: mailto:${here/eMail}"
                                    tal:content="here/eMail">pxe@spaninq.com</a>
          </td>
         </tr>
        </table>
  <hr>
The multilingual property Shorttext is displayed in the language set in the changeLanguageForm which sets a cookie called LOCALIZER_LANGUAGE
  <p tal:condition="exists:here/Shorttext">
   <span tal:replace="here/Shorttext">
     Shorttext, e.g. NOBODY expects the spanish inquisition&
   lt;/span>
  </p>

</div><!-- import loc_utils --> </div><!-- main slot -->

</body> </html>

Next we define the Employee_edit_form:
<html xmlns:tal="http://xml.zope.org/namespaces/tal"
      xmlns:metal="http://xml.zope.org/namespaces/metal"
      metal:use-macro="here/main_template/macros/master">

<body> <div metal:fill-slot="main"> <!-- import loc_utils--> <div tal:define="loc_utils python:modules['Products.LocCMFProduct.loc_utils']">

<div class="Desktop">

<span tal:replace="request/message" tal:condition="request/message|nothing"><hr></span>

<div class="Metadata">

<h2>Edit Data </h2>

The form will post its data to a method called Employee_edit, which is described below.
<form action="Employee_edit" method="post"
      tal:attributes="action string:${here/absolute_url}/Employee_edit">
<table class="FormLayout">
We need to pass the language in an argument called localizer_language which represents the contents of the cookie LOCALIZER_LANGUAGE which is set by the changeLanguageForm
 <input type="hidden" name="localizer_language" value="de"
        tal:attributes="value request/cookies/LOCALIZER_LANGUAGE">

<tr valign="top"> <th align="right"> Title </th> <td colspan="3"> <input type="text" name="title" value="" size="65" tal:attributes="value here/Title"> </td> </tr>

<tr valign="top">

<tr valign="top"> <th align="right"> Firstname </th> <td colspan="3"> <input type="text" name="Firstname" value="" size="65" tal:attributes="value here/Firstname"> </td> </tr>

<tr valign="top"> <th align="right"> Lastname </th> <td colspan="3"> <input type="text" name="Lastname" value="" size="65" tal:attributes="value here/Lastname"> </td> </tr>

<tr valign="top"> <th align="right"> Email </th> <td colspan="3"> <input type="text" name="eMail" value="" size="65" tal:attributes="value here/eMail"> </td> </tr>

<tr valign="top"> <th align="right"> Shorttext </th> <td colspan="3"> <textarea name="Shorttext" value="" rows=10 cols=50 wrap=virtual tal:content="here/Shorttext"> </textarea> </td> </tr>

<tr valign="top"> <th align="right"> Description </th> <td colspan="3"> <textarea name="description:text" rows="5" cols="65" wrap="soft" tal:content="here/Description"></textarea> </td> </tr>

<tr valign="top"> <th align="right"> Subject </th> <td tal:define="subj_lines python: modules['string'].join( here.subjectsList(), \n )"> <textarea name="subject:lines" rows="3" cols="20" tal:content="subj_lines"></textarea> <br> <select name="subject:list" multiple> <option value="" tal:define="items python: here.portal_metadata.listAllowedSubjects(here); subjects here/Subject" tal:repeat="item items" tal:attributes="value item; selected python: item in subjects" tal:content="item"> </option> </select> </td> </tr>

<tr valign="top"> <td> <br> <input type="submit" name="Employee_edit" value=" Change "> </td> </tr>

</table>

</form>

</div> </div> </div><!-- import loc_utils --> </div><!-- main-slot -->

</body> </html>

Adding a language selector to the main_template Here comes the edit method called Employee_edit.py in our skins/LocCMFProduct directory. We edit the instance and metadata in a single form and call both edit methods.
## Script (Python) "Employee_edit"
##bind container=container
##bind context=context
##bind namespace=
##bind script=script
##bind subpath=traverse_subpath
##parameters=REQUEST, RESPONSE, title=None, Firstname=None, Lastname=None,
             eMail=None, Shorttext=None, description=None, subject=None,
             localizer_language=None
##title=
##

request = container.REQUEST RESPONSE = request.RESPONSE

try: context.edit(Firstname=Firstname, Lastname=Lastname, eMail=eMail, Shorttext=Shorttext, localizer_language=localizer_language )

Provide a default value for the title and description. This is overridden in every edit action so the user has no means of changing them via the edit form. If we left this out here, the values could be edited via the edit form.
    desc_string={'':''}
    desc_string['de']=Mitarbeiter der spanischen Inquisition
    desc_string['en']=Employee of the Spanish Inquisition

title=Firstname + + Lastname description= desc_string[localizer_language] context.editMetadata(title=title, description=description, subject=subject, localizer_language=localizer_language )

except: RESPONSE.redirect(%s/Employee_edit_form % context.absolute_url() \ + ?portal_status_message=Oops! ) else: RESPONSE.redirect(%s/Employee_view % context.absolute_url())

Now I'll describe the LocDefaultDublinCoreImpl class:

__version__ = "0.1"

__doc__ = """LocDefaultDublinCoreImpl"""

## ZOPE imports from Globals import InitializeClass from AccessControl import ClassSecurityInfo from Acquisition import aq_base from Products.CMFCore.utils import tuplize from DateTime.DateTime import DateTime

## CMF imports from Products.CMFDefault.DublinCore import DefaultDublinCoreImpl from Products.CMFCore.WorkflowCore import WorkflowAction from Products.CMFCore import CMFCorePermissions

# Import the needed classes from Localizer from Products.Localizer.LocalPropertyManager import LocalProperty from Products.Localizer.LocalPropertyManager import LocalPropertyManager

class LocDefaultDublinCoreImpl(LocalPropertyManager, DefaultDublinCoreImpl):

security = ClassSecurityInfo()

We override the attributes we want to have multilingual so that they are wrapped by the LocalPropertyManager:
    title = LocalProperty(title)
    subject = LocalProperty(subject)
    description = LocalProperty(description)
    language = LocalProperty(language)
The init method gets an additional argument localizer_language. The attributes are set via _editMetadata, which we also need to override.

def __init__( self , title='' , subject=() , description='' , contributors=() , effective_date=None , expiration_date=None , format=text/html , language='' , rights='' , localizer_language=de ): now = DateTime() self.creation_date = now self.modification_date = now self._editMetadata( title , subject , description , contributors , effective_date , expiration_date , format , language , rights , localizer_language )

# LocalPropertyManager needs some metadata, # this is similar to the PropertyManager mixin class. _local_properties_metadata = ({'id': title, 'type': 'string'}, {'id': subject, 'type': 'lines'}, {'id': description, 'type': 'text'}, {'id': language, 'type': 'text'}, )

# # DublinCore interface query methods #

The methods returning the DublinCore elements which are multilangual must be overridden. The language argument is set to None by default, so a call e.g. to title without a language (the original Title method accepts no argument) will return the Title in the language set as default in Localizer.
    security.declarePublic( Title )
    def Title( self, language=None ):
        "Dublin Core element - resource name"
        return self.getLocalProperty(title, language)

security.declarePublic( Subject ) def Subject( self, language=None ): "Dublin Core element - resource keywords" return self.getLocalProperty(subject, language)

security.declarePublic( Description ) def Description( self, language=None ): "Dublin Core element - resource summary" return self.getLocalProperty(description, language)

security.declarePublic( Language ) def Language( self, language=None ): """ Dublin Core element - resource language """ return self.getLocalProperty(language, language)

# # MutableDublinCore methods #

The methods setting the multilingual contents must also be overridden:
    security.declareProtected( CMFCorePermissions.ModifyPortalContent
                             , setTitle )
    def setTitle( self, title, language=None ):
        "Dublin Core element - resource name"
        self._setLocalPropValue(title, language, title)

security.declareProtected( CMFCorePermissions.ModifyPortalContent , setSubject ) def setSubject( self, subject, language=None ): "Dublin Core element - resource keywords" self._setLocalPropValue(subject, language, tuplize( subject, subject )) return

security.declareProtected( CMFCorePermissions.ModifyPortalContent , setDescription ) def setDescription( self, description, language=None ): "Dublin Core element - resource summary" self._setLocalPropValue(description, language, description)

security.declareProtected( CMFCorePermissions.ModifyPortalContent , setLanguage ) def setLanguage( self, language, lang=None ): """ Dublin Core element - resource language """ self._setLocalPropValue(language, lang, language)

# # Management tab methods #

We override _editMetadata to accept the localizer_language argument:
    security.declarePrivate( _editMetadata )
    def _editMetadata( self
                     , title=''
                     , subject=()
                     , description=''
                     , contributors=()
                     , effective_date=None
                     , expiration_date=None
                     , format=text/html
                     , language=None
                     , rights=''
                     , localizer_language=None
                     ):
        """
            Update the editable metadata for this resource.
        """
        self.setTitle( title, localizer_language )
        self.setSubject( subject, localizer_language )
        self.setDescription( description, localizer_language )
        self.setContributors( contributors )
        self.setEffectiveDate( effective_date )
        self.setExpirationDate( expiration_date )
        self.setFormat( format )
        self.setLanguage( language, localizer_language )
        self.setRights( rights )

security.declareProtected( CMFCorePermissions.ModifyPortalContent , editMetadata ) def editMetadata(self , title='' , subject=() , description='' , contributors=() , effective_date=None , expiration_date=None , format=text/html , language=en-US , rights='' , localizer_language=None ): """ used to be: editMetadata = WorkflowAction(_editMetadata) Need to add check for webDAV locked resource for TTW methods. """ self.failIfLocked() self._editMetadata(title=title , subject=subject , description=description , contributors=contributors , effective_date=effective_date , expiration_date=expiration_date , format=format , language=language , rights=rights , localizer_language=localizer_language ) self.reindexObject()

########################## ## Class Initialisation ## ##########################

InitializeClass(LocDefaultDublinCoreImpl)

We need a little install script, which registers our classes and skins to the types and skins tools:

Create a directory Extensions in the Product directory and put a file Install.py into it. I can't write much about the contents because i just took it from another product and used it It worked and so i didn't think much about that. You find the file in the LocCMFProduct. The only thing I changed was the placement of the LocCMFProduct skins in the skinpath. I added it right after custom because I changed the main_template to use the changeLanguageForm. Our skin must appear before zpt_generic! To execute the install script place an ExternalMethod in the CMF root:

        Id              : LocCMFProductInstall
        Title           :
        Module name     : LocCMFProduct.Install
        Function Name   : install
and click the Test tab.

O.K. we're almost through with it. Only the configuration of the Localizer is left. Install the Localizer product and add an instance of Localizer in the root of your Zope installation. Go to the Languages tab and set the languages to de and en. If you like to choose another language as de, you have to edit the sourcecodes to use another value. Here is some work to do to let the code automatically choose the languages you set in the Localizer instance. Anyone in?
The accept_methods in the Property tab should be set to accept_path accept_cookie by default.