Api de Plone
Introducción al uso de la api de plone. Aplicable a versiones de Plone 2.0 y 2.1. Desarrollado por el taller Plone/Zope de ifPeople.net. Por Emanuel Sartor (emanuel at menttes.com) - menttes: http://www.menttes.com
Introducción al uso de la api de plone. Por Emanuel Sartor (emanuel at menttes.com) - menttes: http://www.menttes.com
Introducción
En este documento describe cómo se pueden manipular los objetos Plone por medio del uso de la API de Plone/Zope.
A menudo se desean manipular los objetos Plone por medio de scripts de Python, veremos ejemplos de cómo usar la API de Plone/Zope para realizar las siguientes tareas:
- Manipular objetos de contenido.
- Usar el catálogo de Plone.
- Manipular usuarios y grupos.
Manipular objetos de contenido
Llamamos objetos de contenido a los objetos que agregamos a través de la interfaz de Plone para manter el contenido del portal: documentos, imágenes, enlaces, carpetas, eventos, noticias y archivos.
Los objetos de contenido pueden ser manipulados por medio de scripts escritos en Python. Es decir, se pueden crear, modificar y borrar objetos de contenido; puede modificar el estado de un objeto de contenido; y se puede acceder a la información que cada objeto de contenido contiene.
Ejemplo 1: acceder a un objeto de contenido desde un script.
Vamos a crear un objeto de contenido usando la interfaz de Plone y luego lo vamos a acceder desde un script siguiendo los siguientes pasos.
- Agregamos un documento (página) a nuestro sitio Plone cuyo identificador sea
mi-documento. En Plone 2.1.1 sólo escribimos el títuloMi documentoy el identificador se genera automáticamente, en Plone 2.0.x debemos escribir el identificador en el formulario de creación del documento. - A través del ZMI vamos a
portal_skins/customy agregamos un objetoScript (Python)cuyo contenido sea:from Products.CMFCore.utils import getToolByName
urltool = getToolByName(context, 'portal_url')
portal = urltool.getPortalObject()
document = getattr(portal, 'mi-documento')
print document.Title()
print document.CookedBody()
return printed
En el script se observa el uso de las funciones getToolByName y getattr, y de los métodos getPortalObject, Title y CookedBody. Veamos cómo se usa cada uno.
- getToolByName(obj, name, default=[]) Se importa desde un módulo CMF. Devuelve la herramienta (tool) dada por name (string) dentro del objeto obj.
- getattr(object, name) es una función de Zope. Devuelve el valor del atributo nombrado de object. name tiene que ser string. Si name es uno de los atributos de object, el resultado es el valor de ese atributo. Por ejemplo, getattr(x,
foobar) es equivalente a x.foobar. Si el atributo no existe, se devuelve default, de otra forma se lanza AttributeError. - hasattr(object, name) es una función de Zope.los argumentos son un objeto y un string. El resultado es 1 si el string es el nombre de alguno de los atributos del objeto, y es 0 en caso contrario. (Esto se implementa llamando a gettattr(object, name) y checando si lanza una excepción o no).
- getPortalObject(self) es un método de la clase Products.CMFCore.URLTool.URLTool que devuelve el objeto Portal.
Ejemplo 2: modificar el contenido de un documento por medio de un script.
Antes de las sentencias print en el script del ejemplo 1, agregamos:
document.edit(text_format='html', text='<div><b>Cambiamos el texto...</b></div>')
Como vemos, se utiliza el método edit, que describimos a continuación.
- edit(self, text_format, text, file='', safety_belt='') es un método de la clase CMFDefaults.Document.Document. text_format puede valer
html,structured-texto 'plain'; text es el cuerpo del documento.
Ejemplo 3: copiar y pegar objetos de contenido.
- Crear una carpeta con identificador mi-carpeta. Recordar que en Plone 2.1.1 basta con crear una carpeta con título
Mi carpetay que en Plone 2.0.x tendremos que escribir el identificador en el formulario de creación. - El script que se muestra abajo, copia el documento creado en el ejemplo 1 y lo pega dentro de la carpeta recientemente creada:
from Products.CMFCore.utils import getToolByName
urltool = getToolByName(context, 'portal_url')
portal = urltool.getPortalObject()
cb_copy_data = portal.manage_copyObjects(['mi-documento'])
folder = getattr(portal, 'mi-carpeta')
folder.manage_pasteObjects(cb_copy_data)
Aquí vemos la utilización de dos métodos: manage_copyObjects y manage_pasteObjects, ambos se explican a continuación junto con manage_cutObjects.
- manage_copyObjects(self, ids=None, REQUEST=None, RESPONSE=None) es un método de la clase OFS.CopySupport.CopyContainer. Pone una referencia a los objetos nombrados en ids en el portapapeles.
- manage_cutObjects(self, ids=None, REQUEST=None) es un método de la clase OFS.CopySupport.CopyContainer.Pone una referencia a los objetos nombrados en ids en el portapapeles.
- manage_pasteObjects(self, cb_copy_data=None, REQUEST=None) es un método de la clase OFS.CopySupport.CopyContainer. Pega los objetos previamente copiados en el objeto actual. Si es llamado desde código Python, se debe pasar el resultado de una llamada previa a manage_cutObjects o manage_copyObjects como primer argumento.
Ejemplo 4: borrar un objeto de contenido.
El siguiete script borra el documento que creamos en el ejemplo 1:
from Products.CMFCore.utils import getToolByName
urltool = getToolByName(context, 'portal_url')
portal = urltool.getPortalObject()
portal.manage_delObjects(['mi-documento'])
Aquí usamos el método manage_delObjects que se explica a continuación.
- manage_delObjects(self, ids=[], REQUEST=None) es un método de la clase CMFPlone.PloneFolder.BasePloneFolder. Elimina todos aquellos elementos cuyos identificadores aparezcan en ids.
Ejemplo 5: crear un documento, una carpeta y un evento desde un script.
Considerar:
from Products.CMFCore.utils import getToolByName
urltool = getToolByName(context, 'portal_url')
catalogtool = getToolByName(context, 'portal_catalog')
portal = urltool.getPortalObject()
# Creando un documento
doc = portal.invokeFactory('Document', 'test-doc')
document = getattr(portal, 'test-doc')
#document.setTitle('Documento de prueba')
#document.setDescription('Esta es la descripción de un documentos de prueba')
document.editMetadata(title='Documento de prueba', description='Esta es una descripción', subject='')
document.edit(text_format='html', text='<b>Este es un dodumento de prueba</b>')
# Creando una carpeta
fld = portal.invokeFactory('Folder', 'test-folder')
folder = getattr(portal, 'test-folder')
folder.setTitle('Mi Carpeta')
folder.setDescription('Esta es la descripción de la carpeta.')
catalogtool.refreshCatalog()
# Creando un evento
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='Este es un evento de prueba.')
event.editMetadata(subject='Appointment')
- 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) definido en la clase CMFPlone.PloneTool.PloneTool
- invokeFactory(self, type_name, id, RESPONSE=None, args, *kw):
type_name es el nombre del tipo de contenido que se quiere crear. Notar
que este es el id del objeto que está en la herramienta
portal_types, el cual puede no ser el mismo que aparece en la interfaz de usuario de Plone, la cual usa el título del tipo, si está disponible. id es el identificador (short name) del nuevo objeto, el que aparecerá en la URL. invokeFactory también acepta otros parámetros, los cuales se pasan al constructor del objeto, se pueden usar si se conoce el constructor; uno muy común es title. - refreshCatalog(self, clear=0) reindexa todos los objetos que encuentra.
Ejemplo 6: cambiar el estado de un docuemtno a privado.
Considerar:
from Products.CMFCore.utils import getToolByName
urltool = getToolByName(context, 'portal_url')
portal = urltool.getPortalObject()
document = getattr(portal, 'mi-documento')
review_state = document.portal_workflow.getInfoFor(document, 'review_state', '')
print 'El estado inicial era: ' + 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 'El estado final es: ' + review_state + '\n'
return printed
En este script se observa el uso de dos métodos: getInfoFor y doActionFor, veamos en más detalle cada uno de ellos
- getInfoFor(self, ob, name, default=[], wf_id=None, args, *kw) es un método de la clase CMFCore.WorkflowTool.WorkflowTool. Devuelve una propiedad específica (dada por name), relativa al workflow para un objeto ob.
- doActionFor(self, ob, action, wf_id=None, args, *kw) es un método de la clase CMFCore.WorkflowTool.WorkflowTool. Ejecuta la acción de workflow dada por action para el objeto ob.
Usar el catálogo de Plone
En esta sección veremos el uso del método searchResults, inicialmente se describirá el uso de la misma y posteriormente veremos ejemplos de su uso.
El método searchResults(self, REQUEST=None, **kw)
Este método está definido en CMFCore.CatalogTool.CatalogTool, hace una llamada ZCatalog.searchResults con argumentos extra que limitan el resultado de lo que al usuario le está permitido ver.
El método searchResults() de ZCatalog acepta parámetros que definen una consulta a ser hecha al catálogo. La consulta puede ser pasada como una palabra clave (keyword), como un diccionario (mapping) o como parte de un objeto REQUEST, tipicamente desde un formulario HTML.
El índice del catálog a ser consultado será el nombre del keyword, una clave en un diccionario, o un atributo de un record object.
Atributos de los record objects
- query: una secuencia de objetos o un único valor a ser pasado como consulta al índice (obligatorio).
- operator: especifica la combinación de resultados de búsqueda cuando query es una secuencia de valores (opcional, por defecto: or). Valores permitidos: and, or, para Keyword Indexes y Path Indexes; and, or, andnot, near, para Text Indexes.
- range: define un rango de búsqueda sobre un índice (opcional, por defecto: no activado). Valores permitidos: min, para buscar los objetos con valores más grandes que el mínimo de los valores pasados en el parámetro query; max, para buscar los objetos con valores más pequeños que el máximo de los valores pasados en el parámetro query; minmax, para buscar los objetos con valores más grandes que el mínimo de los valores pasados en el parámetro query y con valores más pequeños que el máximo de los valores pasados en el parámetro query.
- level: se aplica sólo al índice Path. Especifica el nivel de directorio para comenzar la búsqueda (opcional, por defecto: 0)
Ejemplo 7: listar todos los documentos del portal inversamente ordenados por id.
El siguiente script lista todos los documentos del portal inversamente ordenados por id, notar que portal_type restringe el tipo de los objetos de contenido, y sort_on y sort_order hacen la ordenación propuesta. Si se quisiera ordenar por título, en Plone 2.1.1, deberíamos pasar el valor sortable_title en el parámetro sort_on:
results = context.portal_catalog.searchResults(sort_on='id', portal_type='Document', sort_order='reverse')
print [i.getObject().id for i in results]
return printed
Notar que usamos el método getObject puesto que el resultado arrojado por searchResults
no son los objetos de contenido en sí. Es decir, el método get object
devuelve el objeto de contenido asociado a un item del resultado de una
búsqueda en el catálogo.
Ejemplo 8: listar todos los objetos del portal que sean privados y que incluyan la palabra texto.
Cada objeto de contenido define cuál de su información es searchable. El parámetro SearchableText busca sobre esa información en cada objeto. El parámetro review_state filtra objetos de acuerdo a su estado:
results = context.portal_catalog.searchResults(SearchableText='texto', review_state='private')
print [i.getObject().Title() for i in results]
return printed
Ejemplo 9: listar todos los objetos del sitio cuya fecha de
creación sea posterior al 30 de noviembre de 2005 y hayan sido creados
por el usuario admin.
En este script se muestra cómo se puede pasar un record object al método searchResults. Al parámetro Date se le pasa un record object para consultar por aquellos objetos que fueron creados en una fecha posterior al 30 de noviembre de 2005. El parámetro Creator filtra aquellos objetos que no fueron creados por el usuario admin:
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
Manipular usuarios y grupos
Ejemplo 10:
Considerar:
id = 'usuario'
fullname = 'Emanuel Sartor'
password = 'facil'
email = 'emanuel@menttes.com'
roles = ('Manager',)
status=''
props = {'username': id,
'fullname': fullname,
'password': password,
'email': email}
#Agregando un nuevo miembro al portal
try:
context.portal_registration.addMember(id, password, roles, domains='',properties=props)
status+='El usuario '+fullname+' fue agregado exitosamente.\n'
except:
status+='El usuario '+fullname+' no pudo serr agregado.\n'
print status
return printed
- addMember(self, id, password, roles, domains, properties) Crea un PortalMember y lo devuelve.
Más en: http://api.plone.org/Plone/2.1.1/public/CMFPlone.RegistrationTool.RegistrationTool-class.html
Ejemplo 11: crear un grupo con nombre Grupo0.
Considerar:
groupname = 'Grupo0'
status=''
#Agregando un nuevo grupo al portal
try:
context.portal_groups.addGroup(groupname,)
status+='El grupo '+groupname+' fue agregado exitosamente.\n'
except:
status+="Manager group couldn't be added\n"
print status
return printed
- addGroup(self, id, roles, groups, args, *kw)
Más en: http://api.plone.org/Plone/2.1.1/public/CMFPlone.GroupsTool.GroupsTool-class.html
Ejemplo 12: agregar el usuario creado en el ejemplo 10 al grupo creado en el ejemplo 11.
Considerar:
id = 'usuario'
groupname = 'Grupo0'
status=''
# Asignando el usuario al grupo
try:
group = context.portal_groups.getGroupById(groupname)
group.addMember(id)
status+='El usuario '+id+' fue agregado exitosamente al grupo '+groupname+'.\n'
except:
status+='El usuario '+id+' no pudo ser agregado al grupo '+groupname+'.\n'
print status
return printed
Ejercicios
Ejercicio 1:
Hacer un script que tome dos argumentos doc1 y doc2. Asumiendo que ambos son strings
identificadores de dos documentos en la carpeta actual, modificar el
contenido del segundo para que contenga la concatenación del contenido
del primero y el segundo.
Ejercicio 2:
Hacer un script que:
- Cree una carpeta con título
Eventosen la raiz del portal Plone. - Mueva todos los objetos de tipo
Eventen el portal a la carpeta creada en el item anterior. - Cambie el estado de todos los eventos del portal a
Published.
Ayuda:
obj.aq_inner.aq_parentes el objeto padre deobjobj.objectValues()es la lista de objetos hijos deobj.
