Plone API
Applies to Plone versions 2.0 and 2.1. This is an introduction to Plone's api usage, developed for ifPeople's Plone/Zope training in Argentina by Emanuel Sartor (emanuel at menttes.com) - menttes: http://www.menttes.com, and translated to English by Natalia B. Bidart (nbidart at except dot com dot ar) - except: http://except.com.ar. The tutorial is also available in Spanish - http://www.ifpeople.net/fairsource/courses/material/apiPlone .
This is an introduction to plone's api usage, by Emanuel Sartor (emanuel at menttes.com) - menttes: menttes.com, translated by Natalia B. Bidart (nbidart at except dot com dot ar) - except: excpet.com.ar.
Introduction
This document describes how Plone's objects can be manipulated by using Plone/Zope's API.
Very often, we'd like to manipulate Plone's objects by using scripts (Python). We are going to learn how to use Plone/Zope's API in order to be able to:
- Manipulate content objects.
- Use Plone's catalog.
- Manipulate users and groups.
Manipulate content objects
Script's examples to create, delete and modify content objects.
We call content objects to those objects that can be added through Plone's interface in order to keep the portal's content: documents, images, links, folders, news and files.
The content objects can be manipulated by means of scripts written in Python. In other words, content objects can be created, modified and deleted; its state can be modified, and the information each object holds can be accessed.
Example 1: how to access a content object from a Python script.
First of all, let's create a content object through Plone's interface. Then, we're going to access it from a script according to this steps:
- Add a document (Page) with id
my_document. This step is sightly different if we're working under Plone 2.1.1 because the page's adding form only requests for the page's title, in this case we writeMy Document(and the id gets automatically generated). - From ZMI, navigate trough your Plone Site's folder and go to
portal_skins/custom. There, add a script (Script (Python)) with the following content:from Products.CMFCore.utils import getToolByName
urltool = getToolByName(context, "portal_url")
portal = urltool.getPortalObject()
document = getattr(portal, "my_document")
print document.Title()
print document.CookedBody()
return printed
In the above example, we can see the use of getToolByName and
getattr functions, and getPortalObject, Title and CookedBody
methods. Let's dig deeper:
getToolByName(obj, name, default=[])It's imported from a CMF module. It returns the tool given by name associated with obj.getattr(object, name[, default])Is a zope's function and it returns the attribute name's value from object. name has to be a string. If object has no attribute named name, anAttributeErrorexception is raised, except that the third (optional) argument is set.hasattr(object, name)Is a zope's function and it returns True if the string name is the name of any attribute of object, False otherwise.getPortalObject(self)This is a class method from Products.CMFCore.URLTool.URLTool. It returns the Portal object.
Example 2: how to modify a document's content from a Python script.
This is done by modifying the script shown in example 1: we add a
new sentence before the print block:
document.edit(text_format="html",
text="<div><b>This is a new text...</b></div>")
edit method can be used according to the following:
edit(self, text_format, text, file="", safety_belt="")Method that belongs to CMFDefaults.Document.Document class. text_format can be any ofhtml,structured-text, orplain. text is the document's body.
Example 3: how to copy and paste content objects from a Python script.
- Create a folder with id
my_folder. Remember that this process is a little different if working with Plone 2.1.1 (you only have to provie the new folder's name). - Below, a script tha copies the recently created document
my_documentinto the new folder 'my_folder':from Products.CMFCore.utils import getToolByName
urltool = getToolByName(context, "portal_url")
portal = urltool.getPortalObject()
cb_copy_data = portal.manage_copyObjects(["my_document"])
folder = getattr(portal, "my_folder")
folder.manage_pasteObjects(cb_copy_data)
Here, we can distinguish the use of two (three) important methods:
manage_copyObjects(self, ids=None, REQUEST=None, RESPONSE=None)Method from OFS.CopySupport.CopyContainer class. It places a reference to the objects identified by ids list inside the pasteboard.manage_cutObjects(self, ids=None, REQUEST=None)Same as above.manage_pasteObjects(self, cb_copy_data=None, REQUEST=None)Method from OFS.CopySupport.CopyContainer class. It pastes the object referenced by cb_copy_data, which has to be the result of eithermanage_copyObjectsormanage_cutObjects.
Example 4: how to delete a content object from a Python script.
The following script deletes the document created on example 1:
from Products.CMFCore.utils import getToolByNameurltool = getToolByName(context, "portal_url")
portal = urltool.getPortalObject()
portal.manage_delObjects(["my_document"])
manage_delObjects is the key method on this example:
manage_delObjects(self, ids=[], REQUEST=None)Method from CMFPlone.PloneFolder.BasePloneFolder class. It deletes all elements which id belongs to ids list.
Example 5: how to create a document, a folder and an event from a Python script.
Consider the following script:
from Products.CMFCore.utils import getToolByName
urltool = getToolByName(context, "portal_url")
catalogtool = getToolByName(context, "portal_catalog")
portal = urltool.getPortalObject()
# create a document
doc = portal.invokeFactory("Document", "test_doc")
document = getattr(portal, "test_doc")
#document.setTitle("Test document")
#document.setDescription("This is the description of a test document")
document.editMetadata(title="Test document",
description="This is the description of a test document",
subject="")
document.edit(text_format="html",
text="<b>This is a test document!<b>")
# create a folder
fld = portal.invokeFactory("Folder", "test_folder")
folder = getattr(portal, "test_folder")
folder.setTitle("My Folder")
folder.setDescription("This is the description of a test folder")
catalogtool.refreshCatalog()
# create an event
evt = folder.invokeFactory("Event", id="event")
event = getattr(folder, "event")
event.edit(title = "Foo",
start_date="2003-09-18",
end_date="2003-09-19",
location="home",
description="This is the description of a test event")
event.editMetadata(subject="Appointment")
Here, a few explanations about the methods used in the above example:
editMetadata(self, obj, allowDiscussion=None, title=None, subject=None, description=None, contributors=None, effective_date=None, expiration_date=None, format=None, language=None, rights=None, **kwargs)Method defined in CMFPlone.PloneTool.PloneTool class.invokeFactory(self, type_name, id, RESPONSE=None, args, *kw)type_name is the name of the content type target for creation. Please note that this id has to be the same as the one shown in the toolportal_types(but it might be different from the type name shown in Plone's user interface). id is the identifier (short name) of the new object: this name is used to later build the url. This method accepts some others parameters that are passed to the object's constructor method. One of the most common is title.refreshCatalog(self, clear=0)This methods reindexes all objects found inside self.
Example 6: how to change the state of a private document from a Python script.
Consider the following script:
from Products.CMFCore.utils import getToolByName
urltool = getToolByName(context, "portal_url")
portal = urltool.getPortalObject()
document = getattr(portal, "my_document")
review_state = document.portal_workflow.getInfoFor(document, "review_state", "")
print "The initial state is: " + review_state + "\n"if not review_state in ("rejected", "retracted", "private"):
document.portal_workflow.doActionFor(document, "hide", comment="")
review_state = document.portal_workflow.getInfoFor(document, "review_state", "")
print "The final state is: " + review_state + "\n"
return printed
We can observe the use of two methods:
getInfoFor(self, ob, name, default=[], wf_id=None, args, *kw)This is a method from CMFCore.WorkflowTool.WorkflowTool class. It returns an specific property (given by name) relative to a workflow for a given object ob.doActionFor(self, ob, action, wf_id=None, args, *kw)Is a method from CMFCore.WorkflowTool.WorkflowTool class. It executes the action over a workflow for the given object ob.
Use Plone's catalog
Plone provides a tool (Catalog), that can be queried about content objects inside a given Portal.
In this section we're going to learn the searchResults method. First,
the use of it is going to be explained, and secondly, an example of its
common usage is going to be shown.
searchResults(self, REQUEST=None, **kw)This method is defined in CMFCore.CatalogTool.CatalogTool class, and its implemented thourgh a call toZCatalog.searchResultsmethod. Extra parameters are passed so results are limited according to the user's permissions. The paremeters define the query to be made to the catalog instance. The query can be a keyword, or a mapping, or part of aREQUESTobject (tipically from an HTML document). The index of the catalog to be consulted is either the keyword's name, a mapping's key, or an attribute of a record object.- record objects' attributes
- query: a sequence of objects or a single value to be passed as the query to the index (mandatory).
- operator: specifies the combination of search results when
query is a sequence of values (optional, default:
OR). Possible values:AND,ORfor Keyword Indexes and Path Indexes;AND,OR,ANDNOT,NEARfor Text Indexes. - range: defines a search range over an index (optional,
default: off).
Possible values:
minto search for object with larger values than the maximun of the values passed in query,maxsame asminbut searching for smaller values than the minimun, andminmaxwhich is a combination of the formers. - level: only for Path indixes. It specifies the directory level in which start the search (optional, default: 0).
- record objects' attributes
Example 7: how to list all documents ordered decreasingly by id.
The following script lists all documents in the current Portal ordered decreasingly by id. Please note that portal_type restrictes the type of the content objects, and sort_on and sort_order specify the requerid ordering.
results = context.portal_catalog.searchResults(sort_on="id",
portal_type="Document",
sort_order="reverse")
print [i.getObject().id for i in results]
return printed
Under Plone 2.1.1, if we'd want to order results by title, we should
make the searchResults call setting sort_on parameter to
sortable_title.
Please also note that the method getObject is called for each result
obtained from the catalog. This is because the objects resulting of a
catalog search are not content objects themself. As you migth guessed,
getObject method returns the real content object.
Example 8: how to list all private objects that include texto word.
Each content object defines what part of its information is searchable. The parameter SearchableText searches for that information in every object. The parameter review_state filters objects according its state:
results = context.portal_catalog.searchResults(SearchableText="texto",
review_state="private")
print [i.getObject().Title() for i in results]
return printed
Example 9: how to list all objects with creation date after
November, 30th, 2005 and with creation user admin.
This scripts shows how a record object can be passed to
searchResults method. The Date parameter receives a record object
to query for those objects created after a given date. The Creator
parameter filters the objects that weren't created by admin user:
results = context.portal_catalog.searchResults
(Date={"query": DateTime("2005/11/30"), "range": "min"},
Creator="admin")
print [i.getObject().Title() for i in results]
return printed
Manipulate users and groups
In this section we describe how to use the Plone's API to create users, groups, and how to assign users to groups.
Example 10: how to add a user.
Consider the following:
id = "user"
fullname = "Emanuel Sartor"
password = "changeme"
email = "emanuel@menttes.com"
roles = ("Manager",)
status=""
props = {"username": id,
"fullname": fullname,
"password": password,
"email": email}
# add a new member to the Portal
try:
context.portal_registration.addMember(id, password, roles,domains="",
properties=props)
status+="The user "+fullname+" was successfully added.\n"
except:
status+="The user "+fullname+" was not added.\n"
print status
return printed
addMember(self, id, password, roles, domains, properties)Creates an returns a PortalMember.
More on RegistrationTool-class.html
Example 11: how to create a group named Group0.
Consider the following:
groupname = "Group0"
status=""
# add a new group
try:
context.portal_groups.addGroup(groupname,)
status += "The group was successfully added.\n"
except:
status += "Manager group couldn't be added\n"
print status
return printed
addGroup(self, id, roles, groups, args, *kw)
More on GroupsTool-class.html
Example 12: how to add a user to a group.
Consider the following:
id = "usuario"
groupname = "Group0"
status=""
# assign the user "user" to the group "groupname"
try:
group = context.portal_groups.getGroupById(groupname)
group.addMember(id)
status += "The user "+id+" was successfully added to "+groupname+".\n"
except:
status += "The user "+id+" was not added to group "+groupname+".\n"
print status
return printed
Exercices
Exercice 1:
Codify an script that receives two string arguments doc1 and doc2,
and assume that this two parameters correspond to two document
identifiers in the current folder. Make the script modify the content
of doc2 so it'll hold the concatenation of doc1 and 'doc2's content.
Exercice 2:
Codify an script that:
- Creates a folder with title
Eventsin plone portal's root. - Moves all portal's objects with type
Eventto the newly created folder. - Changes the state of all portal's event to
Published.
Help:
obj.aq_inner.aq_parentis the object's parent ofobj.obj.objectValues()lists all obj's children.