Personal tools
You are here: Home Documentation Tutorials Common Plone programming recipes
Support

Get Help

Join our chat rooms or support forums if you have more specific questions.

Plone Training
Learn how to design, build, and deploy a website in Plone through one of the numerous Plone training sessions around the world.
Find Plone training…
 
Document Actions

Common Plone programming recipes

Note: Return to tutorial view.

Basics of accessing and modifying objects programmatically using Python, Plone's programming language.

Introduction

Foreword for the text

This tutorial shows the basic ways to manipule Plone/Zope objects in Python. Everything should be here in a nutshell.

If you would like contribute something here (usually after painful learning experience :-) please add a comment and I'll edit the tutorial. I am gladly willing to clarify some things if they are explained unclearly.

Creating and cloning items

This part tells how to put in some data to your portal

Creating content objects

You can create Plone content objects using the parent folder's invokeFactory method. All folderish content types have this method.

invokeFactory's arguments are content type, id and then any number of parameters which set the default field values for the created item.
# Create folder called employees
# title = an example of optional initial values
folder = self.portal.invokeFactory("Folder", id="employees", title="Employees")


When objects are created from the Plone user interface, a method called PortalFolder.createObject is invoked. createObject gives an object a temporary id and sets the object id from the object title the first time the object is saved.

Bypassings invokeFactory security and type checks

invokeFactory method checks that the object is allowed to create within the context. The rules are for the end user in mind. Sometimes you want to create objects without security intervention - for example, workaround the global_allow flag in portal types. There is a method in CMFPlone.utils for such use cases.

from Products.CMFPlone.utils import _createObjectByType

# invokeFactory barks about global_allow flag
# We bypass invokeFactory checks by calling _createObjectByType
#self.invokeFactory(CheckoutTool.meta_type, CheckoutTool.id)
_createObjectByType(CheckoutTool.meta_type, self, CheckoutTool.id)


Creating Zope objects

Plone is not the only application running on Zope. There are a lot of pure products out there. Creating Zope objects differ somewhat of creating Plone objects.

Usually Zope 2.x objects have a special manage_add method which must be imported and called to create the object. Here is an example:

    # Create MySQL connection using ZMySQLDA product
# self = portal root here
from Products.ZMySQLDA.DA import manage_addZMySQLConnection
if not "mysql_connection" in self.objectIds():
manage_addZMySQLConnection(self, "mysql_connection", "MySQL connection", "toholampi@localhost root", check=True)

Cloning content objects

Copying an object is not simple as it sounds. One must always keep in mind what happens to
  • Unique object ids
  • Content object references
  • Search index data
Below is a trivial example how to form an object from a master copy into another folder.
            # Create a copy of the master object
classgroup_folder.manage_pasteObjects(proto_folder.manage_copyObjects([sourceId]))

# Correct id and title after succesful cloning
classgroup_folder.manage_renameObjects([sourceId], [targetId])

# Correct title
object = classgroup_folder[targetId]
object.setTitle(targetTitle)

# Update navigation tree and search index data
object.reindexObject()

Throght-the-web content creation

Plone uses function called createObject to initialize anedit form for a newly created object. createObject automatically generates default object id and sets it's state to "under creation" (object._at_creation_flag = True).

If you wish to invoke createObject from your Python script, below is an example.

## Script (Python) "add_expertise_area"
##title=Add expertise area button handler
##bind container=container
##bind context=context
##bind namespace=
##bind script=script
##bind state=state
##bind subpath=traverse_subpath
##parameters=id=''
##

# This script is invoked by a custom save button press.
#
# It saves the current object, creates a child object
# and then opens this child object in edit view.
#
# Author: Mikko Ohtamaa <mikko@redinnovation.com>


res = context.content_edit_impl(state, id)
context.plone_log("Got res:" + str(res))

# Add new expertise area and move to its edit form
if res.status == "success":

# Override default "changes saved" message
state.kwargs["portal_status_message"] = u"Please fill in expertise area details"
context.plone_log("Got state:" + str(state))

# Created returns URL for the new object
# The state object is changed by createObject, so we can discard this information
created = context.createObject(type_name = "expertiseArea")

Reading and writing field values

How to access and change the state of your content

Fields and accessors

Normally one creates custom content types using Archetypes Schema pattern. Schema is a list of fields and their properties.

In these examples, we have declared field called "mySomeField" in the schema of the content. Archetypes generates automatically following setters/getters (generated in Archetypes/ClassGen.py):

  • accessor (get method) getMySomeField()
  • mutator (set method) setMySomeField(value)
  • raw accessor (also known as edit accessor, get method which doesn't perform any character encoding) getMySomeFieldRaw()

Automatically generated accessors and mutators can be overriden by field properties. E.g. title accessor is Title() instead of getTitle().


value = myItem.getMagic()

myItem.setMagic(newValue)

Using Field.get and Field.set

Sometimes you cannot access accessor and mutator directly, or you want to wrap returned values for e.g. international character encoding. Field get and set functions do this.

f = myItem.getField("myFunnyField")

# Same Field instance is shared between all content objects so we need to give
# the target content object for Field methods as a parameter

oldValue = f.get(myItem)

f.set(myItem, newValue)
See file Archetypes/Field.py for more details.


Reindexing

For quick object look-ups, Plone maintains search catalogs and indexes. These indexes hold a copy of certain object attributes in search friendly form. In certain situations, when an object is being manipulated, changes are not reflected back to indexes automatically. Thus, the navigation tree, search and some other site functions go out-of-sync and start to behave oddly.

Namely, object.setTitle() doesn't change the object title in navtree which is generated from PathIndex.

To cure the situation call object.reindexObject() after calling mutators.

schema = ATContentTypeSchema.copy() + Schema(
# field list
(
TextField('someField',
widget=StringWidget(
label='Here is a text value',
description="Try to set this",
),
),
IntegerField('someIntegerField')
)

class MyType(ATCTContent):
""" We declare some custom content as example

"""

schema = schema
 
typeDescription= 'MyType custom content type'
meta_type = 'MyType'
archetype_name = 'MyType'

# ...
# Then later on you can manipulate fields
# assuming you have created portal.myfolder.item

myItem = portal.myfolder.myitem

myItem.setSomeField("Moo was here")

# increase counter
intFieldValue = myItem.getSomeIntegerField()
myItem.setSomeIntegerField(intFieldValue + 1)

Getting object type


# Getting object type directly from the class descriptor
type = object.portal_type

Listing folder contents, accessing portal root and site tools

How to travel around in content tree and use objects in the other parts of the site

Portal item storage

All items are stored in the portal hierarchically.

You can use normal Python attribute accessing to navigate in the portal using object ids. Attributes are transparently mapped to Zope objects. Also, object ids form the url of the object. Python interfaces don't differentiate between child objects and fields.

For example, to refer this How-to one could use following code in a script running at plone.org:

# you have obtain to plone.org portal root object somehow and it's 
# stored in local variable "portal"
documentation = portal.documentation
howTos = getattr(portal, "how-to") # note that we need use getattr because dash is invalid in syntax
myHowTo = getattr(howTos, "manipulating-plone-objects-programmatically")

Listing folder items

Method contentItems is defined in CMFCore/PortalFolder.py. See source code for details, e.g. filtering and other forms of listing.

items = folder.contentItems() # return Python list of children object tuple (id, object)

Listing objetcs like this is very very costly. You should avoid it always as possible. Read more about this in "waking up objects" in Plone core reference.

Listing folder items of certain types

Method listFolderContents retrieves the full objects in the folder (slow). It takes contentFilter argument. contentFilter argument is a dictionary and supports "portal_type" filter, which is a list of allowed portal types.

        # List all types in this folder whose portal_type is "CourseModulePage"
        return self.listFolderContents(contentFilter={"portal_type" : "CourseModulePage"})

 

 

Getting folder item ids

If you need to get ids only, use objectIds()

method. This is permormance wise.

 

# Return a list of object ids in the folder
ids = folder.objectIds()

 

Checking for the existence of a particular object id

If you want to know whether the folder has a certain item or not, you can use the following snippet. It's optimal code, but you can simplify it if you don't need to check if the folder is BTreeFolder.

 

# Use the BTreeFolder API if possible 
if base_hasattr(context, 'has_key'): 
    # BTreeFolder's has_key returns numeric values 
    return context.has_key('index_html') and True or False 
elif 'index_html' in context.objectIds(): 
    return True 
else: 
    return False 

 

 

Acquisition

For many things Plone uses a mechanism called acquisition. It's like inheritance in class tree, but acquisition receives attributes using object context hierarchy. Children can override parent container properties. Most likely you will deal with acquisition behavior when setting permissions and properties. Usually you don't need to call acquisition methods directly.

For example,

  • left_slots and right_slots properties controlling shown portlets are accessed via acquisition. left_slot and right_slot are initially declared in the portal root. Child folders can override settings to define their own portlets.
  • One can limit/allow different permissions hierarchially in Plone. Acquisition is the base behind this security mechanism.

For dealing with permissions, see Zope's AccessControl module documentation (source code).

For dealing with properties, see Zope's the documentation (source code) of PropertyManager class in Zope's OFS module.

Getting content parent container

 

You can travel content hierarchy backwards using acquistions.

from Acquisition import aq_parent

parent = aq_parent(context)

In some cases you might want to do parent = ac_parent(ac_inner(context)). Please could someone define difference between these two different use cases.

Getting portal root handle

The portal root in Plone code is referred as portal object.

In quick install script function install(self), the parameter self is handle to the portal.

In unit tests, you have self.portal.

If you don't have direct portal variable, you can acquire portal root using portal_url tool and acquistision.

from Products.CMFCore.utils import getToolByName

# you know some object which is refered as "context"
portal_url = getToolByName(context, "portal_url")
portal = portal_url.getPortalObject()

Getting Zope application server handle

Sometimes you want to mess directly with Zope application server instead of staying within your Plone site instance. To get the Zope root, use the following code:

 

app = context.restrictedTraverse('/')

 

Portal tools

 

Often you need to access tool and utilities instances which are under portal root. These are like portal_types (for type information) and portal_membership (logged in information). A function called getToolByName can retrieve these utility instances for you.

from Products.CMFCore.utils import getToolByName

# In content method
def getMySecretVariabl(self)
    plone_utils = getToolByName(self, "plone_utils")

# or in skin script
plone_utils = getToolByName(context, "plone_utils")

 

Deleting and renaming content objects

Deleting and renaming Plone objects programmatically

Deleting content object

Use parent_container.manage_delObjects to delete content.

With Zope, it's possible to delete items using the del keyword. Do not do this. Using pure del doesn't remove the item from various internal indexes leaving your portal in an inconsistent state. Instead, always use the method manage_delObjects for Plone objects.

# delObjects takes a sequence of ids as a paramater
self.portal.myfolder.manage_delObjects(["myitem"]) # takes list of item ids that are to be removed as a parameter

Renaming content objects

Changing id (URL visible) part:
folder.manage_renameObject(id='my_item_id', new_id='my_item_new_id')

Changing object title

folder.item.setTitle("My new content title")
folder.item.reindexObject()

Security model

This chapter takes an overview for Zope security model and how one sets permission barries in Python code

Security model


Python Methods are protected by Zope security manager. Each method declares permission required to access the method. When method is called from sandboxed context (HTTP URL, page template or site script) all calls going outside the sandbox are checked by Python security manager. After breaching out from the security sandbox, there are no further automatic security manager call checks, since the security management gives heavy performance overhead for each function call.

Security model diagram


All methods are usually accessible by normal URL (e.g. http://myhost/mycontent/getMyValue) and after penetrating security barries once there are no rechecks for the security.
It is very important to define proper permissions for each method which could manipulate or export private information.

There are several roles, which have set of permissions. Roles are inherited - a subfolder can have different permission set for the role as the parent folder.

Users and groups are given roles. Again, user can have different roles in the different part of the site.

Security inheritance



Defining security for your methods and modules


In Zope, Permissions are normal Python strings. The best practice is to refer to permission names through variables so that typing errors are catched in Python during loading and won't cause problems later on.

Below are few common Plone permissions. These are declared in CMFCore product permissions module. Permissions are listed in variable name = variable content pairs.
  • View = "View"
    • View item having this permissions i.e. access view related class methods and accessors like getId(), getText()
  • ModifyPortalContent = 'Modify portal content'
    • Edit item having this permission i.e. access edit related class methods and mutators like setId(), setText()
  • AddPortalContent = 'Add portal content'
    • User can add items to the folder with this permission
  • ListFolderContents = 'List folder contents'
    • User is allowed to see what items are inside the folder
  • AddXXXContent = "Add XXX content"
    • Each content type has its own permission to control creation of this content type. So, to add one new item you need both AddPortalContent and AddXXXContent permissions.

An example how to use permissions definitions from CMFCore:

from CMFCore import permissions

class REAgent(Member):
""" Real-estate agent content type """

security = ClassSecurityInfo()

archetype_name = 'REAgent'
meta_type = 'REAgent'
portal_type = 'REAgent'

schema = schema

security.declareProtected(permissions.View, "showImage")
def showImage(self, blaa):
""" This method is protected by View permissions. Everyone must have (inherited) View permission to call this method """
...

Module level functions are declared using Zope's ModuleSecurity and class methods are declared using ClassSecurity.

    • ClassSecurity.declarePublic("myMethodName")  Everything can access this functions - even your web browser
    • ClassSecurity.declarePrivate("myMethodName") Method is accessible after penetrating security barrier.
    • ClassSecurity.declareProtected(MY_PERMISSION_STRING_CONSTANT, 'myMethodName'). If user has permissions whose name is declared in MY_PERMISSION_STRING_CONSTANT string, the user can call the method and penetrate the security barrier.
After each class declaration you must remember to call InitializeClass or RegisterType which does security initialization!

For module security, read this tutorial.


# Zope imports
from AccessControl import ClassSecurityInfo, Unauthorized
from Globals import InitializeClass

# Plone imports
from Products.Archetypes.public import registerType

# Local imports
from Products.MyProduct.permissions import ADD_ISSUES_PERMISSION

class Issue(ATCTContent):
""" Usability issue for an application """

security = ClassSecurityInfo()

security.declareProtected(ADD_ISSUES_PERMISSION, 'initializeArchetype')
def doStuff(self, **kwargs):
""" Set voting enabled initially

called by the generated addXXX factory in types tool """
ATCTContent.initializeArchetype(self, **kwargs)
setattr(self, "enableRatings", True)
setattr(self, "enableVoting", True)

if(notAllowed == True):
raise Unauthorized, "Your user can't do this"

security.declarePublic('isVoteable')
def isVoteable(self):
""" Do not allow voting of sealed item """
workflowTool = getToolByName(self, 'portal_workflow')
#print "Is voteable:" + str(workflowTool.getStatusOf("issue_workflow", self))
return (workflowTool.getStatusOf("issue_workflow", self)["review_state"] == "in_progress")

registerType(Issue, PROJECTNAME)

class MyCustomClass:

security = ClassSecurityInfo()


security.declarePublic("blaa")
def blaa(self):
pass

InitializeClass(MyCustomClass)

Permissions

How to manipulate Zope permission in Python code

 

Checking for permissions

How to manually check if the current user has a permission available in the context. In the example below self

is used as context.

 

# Use portal_membership tool for checking permissions
mtool = context.portal_membership
checkPermission = mtool.checkPermission

# checkPermissions returns true if permission is granted
if checkPermission('Modify portal content', context):
    return "you can modify it"

#
# or...
#

if not getSecurityManager().checkPermission(MANAGE_USABILITY_ITEMS_PERMISSION, self):
     raise Unauthorized, "User " + str(getSecurityManager().getUser()) + " doesn't have permission to create Application Folders, missing permission:" + MANAGE_USABILITY_ITEMS_PERMISSION


Setting object permissions

 

Normally one should never set context object permissions directly in Plone. The correct approach is to create a workflow which has a state which sets the context object permissions.

 

 

Zope manages permission internally in form "a permission is allowed for certain roles + acquired roles if acquisition is turned on". Use method manage_permission

in AccessControl/Role.py to set permissions.

 

    def manage_permission(self, permission_to_manage,
                          roles=[], acquire=0, REQUEST=None):
        """Change the settings for the given permission.

        If optional arg acquire is true, then the roles for the permission
        are acquired, in addition to the ones specified, otherwise the
        permissions are restricted to only the designated roles.
        """

An example:

# Common Plone permissions have pseudovariable definitions 
# in this module
from Products.CMFCore import permissions

def fix_assignment_permissions(context):
    """ Makes the context available for student and tutor only.
    
    """
    
    # View is an one of Plone's own permissions. It defines
    # who are allowed to view the object.
    # Roles student, tutor and manager are allowed to view the context.
    context.manage_permission(
          permissions.View, 
          roles = ["Student", "Tutor", "Manager"],
          acquire=False)

 

Testing for security

In unit tests, PloneTestCase has method setRoles.

It sets the active security roles for unit test driver. Always use setRoles in unit testing code, so that the tests will catch security errors too.

 

    def testCreateServiceRequest(self):   
        """ Create service request and translate it through all states """
                
        self.setRoles(("Member",))
        
        self.portal.service_requests.invokeFactory("BuyerServiceRequest", id="testRequest")    
        req = self.portal.service_requests.testRequest
        
        self.setRoles((REAGENT_ROLE,))
                        
        workflowTool = self.portal.portal_workflow        
        workflowTool.doActionFor(req, "pick_request")
        workflowTool.doActionFor(req, "close_request")
        
        self.setRoles(("Manager",))
        workflowTool.doActionFor(req, "reopen_request")
        self.setRoles((REAGENT_ROLE,))
        workflowTool.doActionFor(req, "pick_request")
        workflowTool.doActionFor(req, "close_request")

 

Manipulating permissions

The easiest way to set custom permission for roles is to do it via workflows.  Please refer to this

tutorial.  Note that workflows cannot user permissions before permissions are declared at portal root level.

 

See Zope/AccessControl/Role.py for methods if you need to do it directly.

 

This might come in handy:

 

# Python imports
import types
from StringIO import StringIO

# Zope imports
from AccessControl.Permission import Permission

def addPermissionsForRole(context, role, wanted_permissions):
    """ Add permissions for a role in the context
    
    Parameters:
        @param context Portal object (portal itself, Archetypes item, any inherited from RoleManager)
        @param role role name, as a string
        @param wanted_permissions tuple of permissions (string names) to add for the role    
        
    All wanted_permissions lose their acquiring ability
    """        
    
    assert type(wanted_permissions) == types.TupleType
            
    #print "Doing role:" + role + " perms:" + str(wanted_permissions)
    for p in context.ac_inherited_permissions(all=True):
        name, value = p[:2]
        p=Permission(name,value, context)        
        roles=list(p.getRoles())
            
        #print "Permission:" + name + " roles " + str(roles)
        if name in wanted_permissions:
            if role not in roles:
                roles.append(role)
            p.setRoles(tuple(roles))  
            
def removePermissionsFromRole(context, role, wanted_permissions):
    """ Remove permissions for a role in the context
    
    Parameters:
        @param context Portal object (portal itself, Archetypes item, any inherited from RoleManager)
        @param role role name, as a string
        @param wanted_permissions tuple of permissions (string names) to add for the role    
        
    All wanted_permissions lose their acquiring ability
    """    
    
    assert type(wanted_permissions) == types.TupleType
            
    #print "Doing role:" + role + " perms:" + str(wanted_permissions)
    for p in context.ac_inherited_permissions(all=True):
        name, value = p[:2]
        p=Permission(name,value, context)        
        roles=list(p.getRoles())
            
        #print "Permission:" + name + " roles " + str(roles)
        if name in wanted_permissions:            
            if role in roles:
                roles.remove(role)
            p.setRoles(tuple(roles))   

 

 

Roles

Creating roles, dealing with content objects and roles

Creating a new role

The example below creates a role programmatically

From PloneInstallation RoleInstaller.py
    def doInstall(self, context):
"""Creates the new role
@param context: an InstallationContext object
"""
context.portal._addRole(self.role)
context.logInfo("Added role '%s'" % self.role)
if self.model:
# Copies permissions from an existing role
permissions = self._currentPermissions(context, self.model)
context.portal.manage_role(self.role, permissions=permissions)
context.logInfo("Give permissions of '%s' to '%s'" %
(self.model, self.role))
if self.allowed:
context.portal.manage_role(self.role, permissions=self.allowed)
context.logInfo("Allowed permissions %s to '%s'" %
(', '.join(["'" + p + "'" for p in self.allowed]),
self.role))
if self.denied:
permissions = self._currentPermissions(context, self.role)
for p in self.denied:
permissions.remove(p)
context.portal.manage_role(self.role, permissions=permissions)
context.logInfo("Denied permissions %s to '%s'" %
(', '.join(["'" + p + "'" for p in self.denied]),
self.role))
return


Checking a role for the current user in context

user.getId() in context.users_with_local_role('Owner')

Manipulating roles

Adding local rules for a user. The role is effective only in the context and nested child objects.

object.manage_addLocalRoles(username, ("My Custom Role",))

Adding a role for a group in context



Users

Getting logged in user, manipulating user database

Getting logged in user

Getting the current logged in user and his/her username

from Products.CMFCore.utils import getToolByName

mt = getToolByName(self, 'portal_membership')
if mt.isAnonymousUser(): # the user has not logged in
pass
else:
member = mt.getAuthenticatedMember()
username = member.getUserName()

Delete users

Plone 2.5 way:

        try:
self.portal.acl_users.source_users.doDeleteUser("hr")
except KeyError:
# User does not exist
pass

Get member fullname


        mt = getToolByName(self, 'portal_membership')
member = mt.getAuthenticatedMember()
fullname" : member.getProperty('fullname')

Get member email address

        mt = getToolByName(self, 'portal_membership')
member = mt.getAuthenticatedMember()
fullname" : member.getProperty('fullname')

Manipulating user database

Manipulating users depends a bit what kind of user backend you have
  • Zope internal user database
  • CMFMember or other product which presents users as site content
  • External user database through PlonePAS (e.g. LDAP Windows user accounts)
Plain Plone example:
    def createMember(self, id, pw, email, roles=('Member',)):
pr = self.portal.portal_registration
member = pr.addMember(id, pw, roles, properties={ 'username': id, 'email' : email })
return member
CMFMember example:
    def createREAgent(self, id):
md = self.portal.portal_memberdata
tmp_id = id + '_tmp_id'
md.invokeFactory(type_name='REAgent', id=tmp_id)
return md._getOb(tmp_id)

Setting a member property on all members

This example shows how to change the editor for all users.

Below code is used from an external method, it was placed as 'switchToKupu.py' inside a product's 'Extensions/' directory. This was used to move users from Epoz to Kupu:

def switchToKupu(self):
out = []
# Collect members
pm = self.portal_membership
for memberId in pm.listMemberIds():
member = pm.getMemberById(memberId)
editor = member.getProperty('wysiwyg_editor', None)
if editor == 'Kupu':
out.append('%s: Kupu already selected, leaving alone' % memberId)
else:
member.setMemberProperties({'wysiwyg_editor': 'Kupu'})
out.append('%s: Kupu has been set' % memberId)
return "\n".join(out)

Workflows

How to deal with worklows programmatically

Default workflows

For Plone stock workflow state ids and transition ids see DCWorkflow/Default.py

Creating workflows

To create or manipulate workflows please refer to this tutorial.


Getting workflow state of an object

If you want to read the workflow state of an object, use the following snippet:

        workflowTool = getToolByName(self.portal, "portal_workflow")                
# Returns workflow state object
status = workflowTool.getStatusOf("plone_workflow", object)
# Plone workflows use variable called "review_state" to store state id
# of the object state
state = status["review_state"]
assert state == "published", "Got state:" + str(state)

Setting workflow state

To set workflow state programmatically, you need to use WorkflowTool

        portal.invokeFactory("SampleContent", id="sampleProperty")                    
      
        workflowTool = getToolByName(context, "portal_workflow")       
workflowTool.doActionFor(portal.sampleProperty, "submit")
WorkflowTool also can list available actions. Note that there can be several workflows per object. This is important to know when retrieving the current workflow state.

Getting installed workflows

Gets the list of ids of all installed workflows. Test if there is one particular present.

  # Get all site workflows
ids = workflowTool.getWorkflowIds()
self.failUnless("link_workflow" in ids, "Had workflows " + str(ids))

Getting default workflow for a portal type

 # Get default workflow for the type
chain = workflowTool.getChainForPortalType(ExpensiveLink.portal_type)
self.failUnless(chain == ("link_workflow",), "Had workflow chain" + str(chain))

Getting workflows for an object

How to test which workflow the object has
        # See that we have a right workflow in place
workflowTool = getToolByName(context, "portal_workflow")
# Returns tuple of all workflows assigned for a context object
chain = workflowTool.getChainFor(context)

# there must be only one workflow for our object
self.failUnless(len(chain) == 1)

# this must must be the workflow name
self.failUnless(chain[0] == 'link_workflow', "Had workflow " + str(chain[0]))



Important content methods

Getting content type, URL, workflow state, etc.

Schema and fields

Getting Field from content

field = content.getField("myFieldName")

Getting schema

schema = content.getSchema()
Iterating through fields

for id in schema.keys():
field = schema[id]

Item type


Getting item type

item.getTypeInfo().getId() # return factory type information id, e.g. portal_type attribute

URL

If you want to give URLs for your content object in page templates and page scripts
url = item.absolute_url()
or in page template

<a href="#" tal:attributes="href string:${here/absolute_url}">

Creating content types

How to create your custom content types for Plone

I am not going to repeat everything which can be found from Martin Aspeli's excelent RichDocument tutorial.

Views and templates

Plone has different sets of views appearing for each content type. The basic views are "view" and "edit". This chapter tells how to add and manipulate views.

Changing default view template

The most common use case is that one wishes to have a customized view template for a new content type.

First, change "default_view" attribute in AT class definition. This attribute tells what page template is used to view the content object.

class consultantInformation(ATCTFolder):
"""
"""
default_view = "consultant_view"

schema = schema



Adding dynamic views

Dynamic views appear in the object's Display menu. The manager can override default view mode for the object. For example, Plone ships with "Album view" for folders which makes folders behave like photo albums, showing thumbnailed images.

  1. Add your custom template to supplied views of your content class
    class WorkPackageFolder(ATFolder):
    """ Contains work package items
    """
    schema = schema

    filter_content_types = True

    typeDescription= 'Work package folder'
    meta_type = 'WorkPackageFolder'
    archetype_name = 'Work package folder'

    # Generate user friendly id from item title during creation
    # Effective only for ATContentTypes based classes
    _at_rename_from_title = True

    suppl_views = ('my_template_name',)

  2. Create a custom view template my_template_name.pt. Good starting points are base_view.pt in Archetypes product and various templates in ATContentTypes product.
  3. Create my_template_name.pt.metadata file which will contain user readable label for your view and other information. This must be in the same folder with the view template.
    [default]
    title = My view name

Overriding default edit template

The default edit template is called "base_edit.cpt". Here are instructions how you replace it for your AT class to add custom text on the template.

  • Copy base_edit.cpt and base_edit.metadata to your skins directory
  • Rename them to specific to your item, e.g. wnc_edit.cpt and wnc_edit.metadata
  • Edit wnc_edit.cpt. The following exampe adds a new button Add expertise area next to Save. Note that <input> name must be in form of "form.button.xxx".

<metal:use_body use-macro="body_macro">

<metal:block fill-slot="buttons"
tal:define="fieldset_index python:fieldsets.index(fieldset);
n_fieldsets python:len(fieldsets)">

<input tal:condition="python:fieldset_index &gt; 0"
class="context"
tabindex=""
type="submit"
name="form_previous"
value="Previous"
i18n:attributes="value label_previous;"
tal:attributes="tabindex tabindex/next;
disabled python:test(isLocked, 'disabled', None);"
/>
<input tal:condition="python:fieldset_index &lt; n_fieldsets - 1"
class="context"
tabindex=""
type="submit"
name="form_next"
value="Next"
i18n:attributes="value label_next;"
tal:attributes="tabindex tabindex/next;
disabled python:test(isLocked, 'disabled', None);"
/>
<input class="context"
tabindex=""
type="submit"
name="form_submit"
value="Save"
i18n:attributes="value label_save;"
tal:attributes="tabindex tabindex/next;
disabled python:test(isLocked, 'disabled', None);"
/>

<input class="context"
tabindex=""
type="submit"
name="add_expertise_area"
value="Add expertise area"
/>

<input class="standalone"
tabindex=""
type="submit"
name="form.button.cancel"
value="Cancel"
i18n:attributes="value label_cancel;"
tal:attributes="tabindex tabindex/next"
/>
</metal:block>


</metal:use_body>
  • Add following to your content type class
from Products.ATContentTypes.content.base import updateActions, updateAliases

class WNCCompany(ATCTContent):
""" Consultancy company listing entry
"""
security = ClassSecurityInfo()

# This name appears in the 'add' box
archetype_name = 'Consultancy company'

meta_type = portal_type = 'WNCCompany'

global_allow = True

_at_rename_after_creation = True

schema = schema

# Override default edit view
actions = updateActions(ATCTFolder, (
{ 'id': 'edit',
'name': 'Edit',
'action': 'string:${object_url}/wnc_edit',
'permissions': (permissions.ModifyPortalContent,),
},
))

Page template and widget magic

How to perform often requested tricks with Archetypes widgets and page templates

Foreword

Forgive me if page template formatting become messy when copy-pasting code into Kupu.

 

Constructing a callable macro name dynamically

Try this code (also, example in the section below):

 

         <tal:block tal:define="macro_path python: path('here/%s/macros' % page_template_name);
			        callable_macro macro_path/my_custom_macro_name;">
                                           
                        
                                <tal:use-macro metal:use-macro="callable_macro" />
                            
          </tal:block>

 

Iterating through certain content types in a folder

 

The following macro serves as a base how to iterate certain content types in a folder

 

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
    lang="en">
    
    <!-- per_content_type_renderer macro definition
    
    List certain content types and calls a target macro for them.
    Target macro is given as a page template filename which contains the macro.
    
    Takes arguments:
    
    wanted_item_type: string, content type id. 
    				  Be careful with space padding in template code.
    
    view_macro: which macro to be called, template file basename, 
    			file contains 'listing_core' macro

    Author: Mikko Ohtamaa

    http://www.redinnovation.com
    
    -->
    
    <body>

        <metal:macro define-macro="per_content_type_renderer">
            <tal:foldercontents 
                define="contentFilter    contentFilter|request/contentFilter|nothing;
                limit_display    limit_display|request/limit_display|nothing;
                more_url         more_url|request/more_url|string:folder_contents;
                contentsMethod   python:test(here.portal_type=='Topic', here.queryCatalog, here.getFolderContents);
                folderContents   folderContents|python:contentsMethod(contentFilter, batch=True);
                use_view_action  site_properties/typesUseViewActionInListings|python:();
                over_limit       python: limit_display and len(folderContents) > limit_display;
                folderContents   python: (over_limit and folderContents[:limit_display]) or folderContents;
                batch            folderContents">
                <tal:listing condition="folderContents">
     
                    <div tal:repeat="item folderContents">
                        <tal:block tal:define="item_url item/getURL|item/absolute_url;
                            item_type           item/portal_type;
                            item_object         item/getObject;
                            item_creator        item/Creator;
                            macro_path python: path('here/%s/macros' % view_macro);
			                callable_macro macro_path/listing_core;">
                                           
                            <tal:activity tal:condition="python: item_type == wanted_item_type">                           
                                <tal:use-macro metal:use-macro="callable_macro" />
                            </tal:activity>
                            
                        </tal:block>
                    </div>
                </tal:listing>
            </tal:foldercontents>
        </metal:macro>            
       
	</body>
</html>

 

 

Rendering widget directly from folder listing

 

This is an often heard request - one wants to render a widget outside Archetypes rendering flow

 


<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
    lang="en">
    
    <body>
    	<tal:define metal:define-macro="render_field">
            
            <!-- Calling Archetypes widget renderer directly for a certain field
            
              This snippet is useful if one wants to render AT widgets outside their context 
              object, e.g. in a folder summary view.
            
              The following code takes an arbitary AT content object in item_object variable.
              
              It checks whether this item object has a field "Staff" and then calls 
              Staff default widget renderer. We need to perform the trick of redeclaring
              context variable which might confuse some code (e.g. permissions) so 
              be careful.
              
              Takes arguments:
              
              target_item: Object whose field we are rendering
              
              field_name: Field name as a string
              
              use_label: True or False whether label should be rendered
              
              Author: Mikko Ohtamaa
              
              www.redinnovation.com            
            
              -->

			<tal:has-field tal:condition="python: field_name in target_item.schema">
				<tal:field-context tal:define="context python: target_item;
								field python: target_item.schema[field_name];
								widget_view python: target_item.widget(field.getName(), mode='view', use_label=use_label);
			                    field_macros here/widgets/field/macros;
            			        label_macro view_macros/label | label_macro | field_macros/label;
			                    data_macro view_macros/data | data_macro | field_macros/data;
								">
                
				    <div tal:define="fieldtypename python:field.getType().split('.')[-1]"
						tal:attributes="class string:field ArchetypesField-${fieldtypename};
	                    id string:archetypes-fieldname-${field/getName}">
	                    
		                <tal:if_perm condition="python:'view' in widget.modes and 'r' in field.mode and field.checkPermission('r',here)">
		          			<tal:if_use_label tal:condition="python: use_label">
	    				    	<metal:use_label use-macro="label_macro" />
				          	</tal:if_use_label>
				          	
				            <metal:use_data use-macro="data_macro|default" />
				        </tal:if_perm>
	 			    </div>
 			    </tal:field-context>
            </tal:has-field>

        </tal:define>     
	</body>
</html>

Example how to use the page template above:

 

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
    lang="en">
    
    <body>
    	<tal:define metal:define-macro="my_funny_macro">
	    <h1>Render item_object.Staff field</h1>
            
            <tal:field-render-core
            	tal:define="target_item python: item_object;
            				field_name string:Staff;
					use_label python: False">
		            <tal:call-renderer metal:use-macro="here/render_field/macros/render_field" />
	        </tal:field-render-core>

        </tal:define>
	</body>
</html>

Including a nested context in item

The following snippet will include code from an nested item in the context folder.  The nested item will be rendered in a custom widget view macro.

    <!-- VIEW -->
    <metal:define define-macro="view">
        <tal:no-fees condition="python: not 'course-fees' in context.objectIds()">
        	<p tal:condition="not: isAnon">
        		Please add Fees page with id "course-fees" to see it here. 
        		You can use contents tab/rename button to change the id of the object.
        		This messages is visible for editors only.
        	</p>
        </tal:no-fees>
        
        <tal:has-fees condition="python:'course-fees' in context.objectIds()">
        	<tal:new-context define="context python:context['course-fees']; here python:context['course-fees']">
        		<metal:body use-macro="here/base/macros/body" />
        	</tal:new-context>
        </tal:has-fees>
    </metal:define>

 

 

Quick installer snippets

Each Plone product has quick installer script which prepares the portal for the product. Here are some useful snippets which you can reuse.

PloneInstallation

I sincerely recommend using PloneInstallation product in quick installer scripts. It has many classes needed not to reinvent the wheel every time one writes a quick installer script.


Enabling Large Plone Folders

Large Plone Folders (also known as BTreeFolders) use binary trees as the folder index. This makes folder look ups faster on large item counts. By default, creation of Large Plone Folders is disabled. To enable it, run this code

    # Allow creation of large folders    
lpf = portal.portal_types.getTypeInfo("Large Plone Folder")
lpf.global_allow = True


Hiding actions

If you use quick installer script to customize your Plone site you might want to hide certain actions from the end users
    # Hide some actions
actionsTool = self.portal_actions
act = actionsTool.getActionInfo("document_actions/print")
act.condition = "python: False"

act = actionsTool.getActionInfo("document_actions/sendto")
act.condition = "python: False"

Removing permissions and preventing anonymous registration

This code removes the permission to create new users from anonymous visitors. The wanted side effect is that Join link also disappears.

# Python imports
import types

# Zope imports
from AccessControl.Permission import Permission

# Plone imports
from Products.CMFCore.permissions import *

def removePermissionsFromRole(context, role, wanted_permissions):
""" Remove permissions for a role in the context.

Parameters:
@param context Portal object (portal itself, Archetypes item, any inherited from RoleManager)
@param role role name, as a string
@param wanted_permissions tuple of permissions (string names) to add for the role

All wanted_permissions lose their acquiring ability
"""

assert type(wanted_permissions) == types.TupleType

#print "Doing role:" + role + " perms:" + str(wanted_permissions)
for p in context.ac_inherited_permissions(all=True):
name, value = p[:2]
p=Permission(name,value, context)
roles=list(p.getRoles())

#print "Permission:" + name + " roles " + str(roles)
if name in wanted_permissions:
if role in roles:
roles.remove(role)
p.setRoles(tuple(roles))

# Prevent registration at the site
removePermissionsFromRole(self, "Anonymous", (AddPortalMember,))

Adding permissions for custom roles

The following snippets allows you to add permissions for roles

# Python imports
import types

# Zope imports
from AccessControl.Permission import Permission

# Plone imports
from Products.CMFCore.permissions import *

def addPermissionsForRole(context, role, wanted_permissions):
""" Add permissions for a role in the context.

Parameters:
@param context Portal object (portal itself, Archetypes item, any inherited from RoleManager)
@param role role name, as a string
@param wanted_permissions tuple of permissions (string names) to add for the role

All wanted_permissions lose their acquiring ability
"""

assert type(wanted_permissions) == types.TupleType

#print "Doing role:" + role + " perms:" + str(wanted_permissions)
for p in context.ac_inherited_permissions(all=True):
name, value = p[:2]
p=Permission(name,value, context)
roles=list(p.getRoles())

#print "Permission:" + name + " roles " + str(roles)
if name in wanted_permissions:
if role not in roles:
roles.append(role)
p.setRoles(tuple(roles))

# Make anonymous link submitting possible
addPermissionsForRole(self.link_pool, "Anonymous", (AddPortalContent,))

Adding external method

    # Add external method
#
# External methods are Python code which lie in Zope content
# structure. Each external method has a module in Extensions folder
# and Zope object in Zope. You can call external methods by typing
# in URL directly. External methods bypass Zope security mechanism.
#
# Usually external methods are used for automated maintenance tasks.
#
#
# Following adds a function from poll.py which is in the product's Extensions folder
#
# self = portal root
from config import PROJECTNAME
from Products.ExternalMethod.ExternalMethod import manage_addExternalMethod
if not "poll_sql" in self.objectIds():
manage_addExternalMethod(self, "poll_sql", "Poll external SQL source", PROJECTNAME + ".poller", "poll_sql")

Developer scripts

Command line scripts which are useful in product development

Unit test runner for Windows

Since Plone 2.5.1 invoking per product unit test modules has become a major pain.

NOTE: This is only necessary on MS Windows. Linux, OSX and other *NIX platforms can run the tests normally from the commandline.


Here is a short .bat file which allows one to run unit tests within the context of one product.
@echo off

REM Invoking unit test directly doesn't work anymore on Plone 2.5.1
REM See http://plone.org/documentation/error/attributeerror-test_user_1_

set PYTHON=d:\python24\python.exe
set ZOPE_HOME=F:\workspace\plone-2.5.1\Zope-2.9.6\Zope
set INSTANCE_HOME=F:/workspace/plone-2.5.1/instance
set SOFTWARE_HOME=%ZOPE_HOME%\lib\python
set CONFIG_FILE=%INSTANCE_HOME%\etc\zope.conf
set PYTHONPATH=%SOFTWARE_HOME%
set TEST_RUN=%ZOPE_HOME%\bin\test.py

"%PYTHON%" "%TEST_RUN%" --config-file="%CONFIG_FILE%" --usecompiled -vp --package-path=%INSTANCE_HOME%/Products/lsmintra Products.lsmintra

Portal catalog queries

Portal catalog provides search indexing information for Plone site. Portal catalog queries are much faster than walking through objects manually,

Searching content objects by author and type

The following snippet will perform a search which returns all items for a certain type and a certain creator.

# Search site for a consultant profile whose creator the current
# user is
#

from Products.CMFCore.utils import getToolByName

portal_catalog = getToolByName(context, 'portal_catalog')
mt = getToolByName(context, 'portal_membership')
if mt.isAnonymousUser():
# the user has not logged in
return None
else:
member = mt.getAuthenticatedMember()
username = member.getUserName()

# Please refer to portal_catalog tool
# Zope management interface for default Plone seach indexes
query = {}
query["Creator"] = username
query["Type"] = "Consultant Profile"

# Return brain objects for search results
brains = portal_catalog.searchResults(**query)

context.plone_log("Got results:" + str(brains))

if len(brains) > 0:
# Return the real object of the first search hit
return brains[0].getObject()
else:
# Np hits - no profile created yet
return None

Test existence of index and metadata colums


    # Test if catalog has a search index
if not "getFirmName" in catalog_tool.indexes():
catalog_tool.manage_addIndex("getFirmName", "ZCTextIndex", extra)

# Test if catalog has a metadata column
if not "getSummary" in catalog_tool.schema():
catalog_tool.manage_addColumn("getSummary")


Actions for content objects

Actions are state changing triggers users perform on objects. For example, edit object, copy or print are actions. This chapter describes how to add new actions and manipulate existing actions.

Adding a tab to content type

To be done

Enabling and disabling actions site wide


def disable_actions(portal):
""" Remove unneeded Plone actions

@param portal Plone instance
"""

# getActionObject takes parameter category/action id
# For ids and categories please refer to portal_actins in ZMI
actionInformation = portal.portal_actions.getActionObject("document_actions/rss")

# See ActionInformation.py / ActionInformation for available edits
actionInformation.edit(visible=False)


Enabling and disabling actions for content type

A sample code to disable few stock Plone actions. The example product RTFExport available here.

from Products.ATContentTypes.content.base import updateActions, updateAliases

class Employee(ATCTContent):
""" Employee record


"""
 
# Add RTF export action icon for the object
# Hide properties and sharings tabs
# Hide cut and copy actions
actions = updateActions(ATCTContent,
(
{
'id' : 'export_rtf',
'name' : 'Export as RTF',
'action' : 'string:$object_url/export_rtf',
'permissions' : (View,),
'category' : "document_actions",
},
{
'id' : 'metadata',
'visible' : False,
},
{
'id' : 'local_roles',
'visible' : False,
},
{
'id' : 'sendto',
'visible' : False,
},
{
'id' : 'cut',
'visible' : False,
},
{
'id' : 'copy',
'visible' : False,
},
)
)

Overriding edit action

An usual use case is using custom edit form. Add the following snippet to your content class definition to use consultant_edit form as the edit form:

    actions = updateActions(ATCTFolder, (
{ 'id': 'edit',
'name': 'Edit',
'action': 'string:${object_url}/consultant_edit',
'permissions': (permissions.ModifyPortalContent,),
},
))


Properties

Properties are flexible key-value pairs assigned to content types and tools. Properties are passed to child content objects via acquisition.

Properties

Properties are a special kind of acquired values. Properties have automatically generated user interface in Zope Management Interface to deal with them. If you hit any folder or object in ZMI it has properties tab were can fiddle around with these.

The most common properties one would want to change are probably left_slots and right_slots which control the appearing of portlets in Plone 2.5.x. (Plone 3.0 has reworked portlet system).

Setting properties

Setting properties to an object causes it to override parent properties in an acquisition chain. Properties must not exist on the object before calling _setProperty. _setProperty takes property type which can be found out on ZMI Properties tab.

Overriding portlet settings in a subfolder. No portlets are used for items inside this folder:
    data_storage._setProperty('left_slots', [], 'lines')    
data_storage._setProperty('right_slots', [], 'lines')

Updating properties


Existing properties can be updated with _updateProperty. This only works if properties have been created using set before. Properties must exist on the target object itself, inherit properties are not count in.

Updating properties left_slots and right_slots for the portal root:

        portal._updateProperty("left_slots", ["here/portlet_navigation/macros/portlet","here/portlet_login/macros/portlet"])
        portal._updateProperty("right_slots", [])


Testing existence of a property

Use object.hasProperty.

The following example code will set or update a property.
    # The following code will create or update property.
# Update default view page template for assigments folder
# default_page property tells the custom page
# template used to render this particular content object
# in view mode
if not assignments.hasProperty("default_page"):
# Create the property
assignments._setProperty("default_page", "")

# Override assigments value (old or new created)
assignments._updateProperty("default_page", "assigments_view")

Site and navigation tree properties


There is a special tool portal_properties which manages most of Plone's site wide properties. Please refer to its content by peeking it in ZMI.

Example: Modifying a navigation tree behavior

self.portal_properties.navtree_properties._updateProperty("topLevel", 1)

Portlets

How to deal with portlets

Activating a custom portlet

This is Plone 2.x way. Plone 3.0 has revamped portlet system. The item shows portlets which are defined in left_slots or right_slots properties.

    # Activate shopping portlet 
# This can be done for any folder
# self = portal root
right_slots = self.right_slots
new_portlet_macro = "here/portlet_shopper/macros/portlet"
if not new_portlet_macro in right_slots:
self._updateProperty("right_slots", right_slots + (new_portlet_macro,))

Creating a new content type 1-2-3

The check list what you need to do when you create new content types for Plone.

This applies for Plone 2.1.x and still works in Plone 2.5.x. It is encouraged to use newer mechanisms provided by Five subsystem when you start new products from scracth. Please read Martin Aspeli's excellent tutorial about the subject.

Archetypes is a Plone subsystem to define new content types. Content types are Python classes which have special attributes described by Archetypes product. The most import of them is schema which defines what fields your content type has. Archetypes reference manual is handy.

File system product skeleton

You need a file system product where new content types is added. You can take the  existing product and rip its flesh away or use examples provided by Archetypes manual.
 

Content type Python module

Create a Python module containing your content type class declaration

  • Create a .py module to the "content" folder of the product. 
  • Add Necessary dependency imports for fields, widgets, parent schema and parent class

Example

# Plone imports
from Products.Archetypes.public import *
from Products.ATContentTypes.content.base import ATCTContent
from Products.ATContentTypes.content.base import updateActions, updateAliases
from Products.ATContentTypes.content.schemata import ATContentTypeSchema
from Products.ATContentTypes.content.schemata import finalizeATCTSchema

# Local imports
from Products.MyCustom.BulletField import BulletField
from Products.MyCustom.BulletWidget import BulletWidget

from Products.MyCustom.config import *

  • Create the schema definition for your content type. See Archetypes manual for available fields, widgets and their properties.

Example

schema=ATFolderSchema.copy() + Schema((
                                                                
        TextField('isItForMe',
               default='',
               searchable=True,
               widget=RichWidget(
                    label='Is it for me?',
                    )
                ),
    
        TextField('benefits',
                       default='',
                       searchable=True,
                       widget=RichWidget(
                             label='What are the benefits of taking this course?',                                             
                             )
                        ),
        
        LinksField('whatDoILearn',
                       default='',
                       searchable=True,
                       widget=RichWidget(label='What do I learn?', macro="what_do_i_learn_widget.pt")
                        ),
    ))
finalizeATCTSchema(schema)
        

The class definition defines security and general properties of your content type.

Example:

class CoursePage(ATFolder):
    """ Courses page content type
    
    """
    
    # Internal programming id which Plone uses to refer this type
    portal_type = meta_type = 'CoursePage' 
    
    # User readable name in "Add new content" drop down menu
    archetype_name = 'Course page'  
    
    # Help text for Add new content drop down menu
    typeDescription = "A course description telling what students should expect for this course"
    
    # Fields used in this content type (as defined above)
    schema = schema
        
    # If true, this content type can be created anywhere at the site
    global_allow = True
    
    
    # We limit what kind of items are allowed in this folderish content type
    filter_content_types = True
    
    # List of allowed content types
    allowed_content_types = [ "GeneralLSMPage", "CourseModulePage", "CourseModePage", "GeneralLSMPage2", "CourseFeesModePage" ]

You need to use registerType to register your class definition for Zope security manager.

# CoursePage is your Python class
# PROJECTNAME is the name of your product and it's usually declared in config.py    
registerType(CoursePage, PROJECTNAME)

Initializing the product

To make your content type available for Plone

  • You need to import it during the Plone start-up sequence
  • You need to run the quick installer script to create persistent portal_type entries for your content type

 

In __init__.py of your product, import the new content type module so that Zope security manager is initialized for it

 

"""
            
    Copyright 2006 xxx
    
"""

__author__  = 'Mikko Ohtamaa <mikko@redinnovation.com>'
__docformat__ = 'epytext'

from Products.Archetypes.public import process_types, listTypes
from Products.Archetypes.ArchetypeTool import getType
from Products.CMFCore.DirectoryView import registerDirectory
from Products.CMFCore import utils as CMFCoreUtils

from config import *
from Permissions import *

def initialize(context):
    """ Registers all classes to Zope
        
    @param context Zope/App/ProductContext instance
    
    """
        
    # initialize security context
    from content import MyCustomContentTypeModule

    # Register our skins directory - this makes it available via portal_skins.
    registerDirectory(SKINS_DIR, GLOBALS)
    
    # helper function to go through all content types in your product
    content_types, constructors, ftis = process_types(
        listTypes(PROJECTNAME),
        PROJECTNAME)

    # Initialize content types
    CMFCoreUtils.ContentInit(
        PROJECTNAME + ' Content',
        content_types      = content_types,
        permission         = PermissionNameToCreateThisContent,
        extra_constructors = constructors,
        fti                = ftis,
        ).initialize(context)    
    

Then, you need to remember to register types in Extension/Install.py quick installer script. This creates persistent portal_type entries for your content type. Each time you change the general properties of your content type (everything except schema) you need to rerun the quick installer.

"""

    Extension/Install.py quick installer script
            
    Your Copyright line here
    
"""

__author__  = 'Mikko Ohtamaa <mikko@redinnovation.com>'
__docformat__ = 'epytext'

# Python imports
from cStringIO import StringIO

# Plone imports
from Products.Archetypes.public import listTypes
from Products.Archetypes.Extensions.utils import installTypes, install_subskin

# Local imports
from Products.MyProduct.Extensions.utils import *
from Products.MyProduct.config import *

def install(self):
    """ This is called by Plone quick installer tool 
    
    @param self portal instance object
    
    @return String which is added for the installer log
    """
    out = StringIO()

    # Register layout files in the portal
    install_subskin(self, out, GLOBALS)           
    
    registerStylesheets(self, out, STYLESHEETS)
    registerScripts(self, out, JAVASCRIPTS)
    
     # Register Archetypes types in the portal
    installTypes(self, out, listTypes(PROJECTNAME), PROJECTNAME)
    
    print >> out, "Installation completed."
    return out.getvalue()


def uninstall(self):
    # TODO
    out = StringIO()
    
    

 

 


For any issues with the web site functionality, please file a ticket.

Please consult the policy on plone.org content if you want your content published on this site.

Servers and hosting by