23. Tipos Dexterity - Parte II: Creciendo¶
Los tipos de contenidos talks existentes todavía le faltan algunas funcionalidades que queremos utilizar.
En esta parte vamos a tratar:
Añadir una interfaz marker para nuestro tipo de contenido talk.
Crear catálogo de índices personalizados.
Hacer consultas al catálogo a estos.
Permitir a algunas características más por defecto para nuestro tipo de contenido.
Agregar una interfaz marker para el tipo de contenido talk¶
Interfaces Marcador¶
El tipo de contenido Talk no es aun ciudadano de primera clase porque no implementa su propia interfaz. Las interfaces son como etiquetas de nombre, diciendo a otros elementos quien y que eres y que puedes hacer. Una interfaz marker es como una etiqueta. Los tipo de contenido talks actualmente tienen una interfaz marker auto generado en plone.dexterity.schema.generated.Plone_0_talk
.
El problema es que el nombre de la instancia Plone
es parte del nombre de la interfaz. si tu ahora mueves estos tipos a un sitio con otro nombre, el código que usa en estas interfaces no podrán ser capaces de encontrar estos objetos.
Para solventar esto agregamos un nuevo archivo interfaces.py
:
1 2 3 4 5 6 | from zope.interface import Interface
class ITalk(Interface):
"""Marker interface for Talks
"""
|
ITalk
es una interfaz marker. Podemos anexar Vistas y Viewlets al contenido que provee estas interfaces. Ahora veremos como proveer esta interfaz. Hay dos soluciones para esto.
Dejemos que sean las instancias de una clase que implementa esta interfaz.
Registra esta interfaz como un comportamiento y habilite este en el tipo de contenido Talk.
La primera opción tiene un inconveniente importante: Sólo nuevas charlas pueden ser instancias de una nueva clase. Nos gustaría o podríamos tal vez tener que migrar a las charlas existentes o eliminarlas.
Así que vamos a registrar la interfaz como un comportamiento en el archivo behaviors/configure.zcml
<plone:behavior
title="Talk"
description="Marker interface for talks to be able to bind views to."
provides="..interfaces.ITalk"
/>
Y habilite ese comportamiento en la definición de tipo de contenido en su configuración GenericSetup en el archivo profiles/default/types/talk.xml
1 2 3 4 5 6 | <property name="behaviors">
<element value="plone.app.dexterity.behaviors.metadata.IDublinCore"/>
<element value="plone.app.content.interfaces.INameFromTitle"/>
<element value="ploneconf.site.behaviors.social.ISocial"/>
<element value="ploneconf.site.interfaces.ITalk"/>
</property>
|
Vuelva a instalar el complemento, aplicar el comportamiento a mano o ejecutar una paso de actualización GenericSetup (ver más abajo) y la interfaz estará allí.
Entonces podemos con seguridad unir la vista talkview
a la nueva interfaz marker, agregando el siguiente código ZCML:
<browser:page
name="talkview"
for="ploneconf.site.interfaces.ITalk"
layer="zope.interface.Interface"
class=".views.TalkView"
template="templates/talkview.pt"
permission="zope2.View"
/>
Ahora la vista /talkview
puede solamente se usada en objetos que implementan dicha interfaz. Ahora también podemos consultar el catálogo para los objetos que proporcionan esta interfaz catalog(object_provides="ploneconf.site.interfaces.ITalk")
. Las vistas talklistview y demoview no reciben esta restricción, ya que no sólo son utilizados en los tipos de contenidos talks.
Nota
Sólo para completarlo, esto es lo que tendría que pasar por la primera opción:
Crear una nueva clase que herede desde
plone.dexterity.content.Container
e implementa la interfaz marker.from plone.dexterity.content import Container from ploneconf.site.interfaces import ITalk from zope.interface import implements class Talk(Container): implements(ITalk)
Modificar la clase para los nuevos tipos de contenidos talks en el archivo
profiles/default/types/talk.xml
1 2 3 4 5
... <property name="add_permission">cmf.AddPortalContent</property> <property name="klass">ploneconf.site.content.talk.Talk</property> <property name="behaviors"> ...
Crear un paso de actualización para modificar la clase de tipo de contenido existente. Un código ejemplo de como hacer esto lo encuentra en el paquete ftw.upgrade.
Pasos de actualización¶
Cuando los proyectos se desarrollan a veces tendrá que modificar varias cosas mientras que el sitio ya este arriba, lleno de contenido y usuarios. Los pasos de actualización son piezas de código que se ejecutan al actualizar de una versión de un complemento a una más reciente. Ellos pueden hacer casi cualquier cosa.
Nosotros crearemos un paso de actualización que:
Ejecute el paso
typeinfo
(es decir, carga los almacenes de configuración GenericSetup enprofiles/default/types.xml
yprofiles/default/types/...
por lo que no tiene que volver a instalar el complemento para tener nuestros cambios hechos) yhaga una limpieza de algún contenido que pudiera estar esparcidos alrededor del sitio en las primeras etapas de su creación. Nos movemos todas los tipos de contenidos talks en una carpeta llamadas
talks
(a menos que ya están allí).
Los pasos de actualización son usualmente registrados en en su propio archivo ZCML. Cree el archivo upgrades.zcml
, agregando el siguiente código ZCML:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <configure
xmlns="http://namespaces.zope.org/zope"
xmlns:i18n="http://namespaces.zope.org/i18n"
xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
i18n_domain="ploneconf.site">
<genericsetup:upgradeStep
title="Update and cleanup talks"
description="Updates typeinfo and moves talks to a folder 'talks'"
source="1"
destination="1001"
handler="ploneconf.site.upgrades.upgrade_site"
sortkey="1"
profile="ploneconf.site:default"
/>
</configure>
|
El paso de actualización cambia la versión número del perfil GenericSetup del complemento ploneconf.site
de 1 a 1001. La versión se almacena en profiles/default/metadata.xml
. Cámbielo a:
<version>1001</version>
Incluir el nuevo archivo upgrades.zcml
en nuestro archivo configure.zcml
, agregando el siguiente código ZCML:
<include file="upgrades.zcml" />
Ahora GenericSetup espera el código como un método upgrade_site
en el archivo upgrades.py
. Vamos a crearlo, agregando el siguiente código Python:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | from plone import api
import logging
default_profile = 'profile-ploneconf.site:default'
logger = logging.getLogger('ploneconf.site')
def upgrade_site(setup):
setup.runImportStepFromProfile(default_profile, 'typeinfo')
catalog = api.portal.get_tool('portal_catalog')
portal = api.portal.get()
if 'the-event' not in portal:
theevent = api.content.create(
container=portal,
type='Folder',
id='the-event',
title='The event')
else:
theevent = portal['the-event']
if 'talks' not in theevent:
talks = api.content.create(
container=theevent,
type='Folder',
id='talks',
title='Talks')
else:
talks = theevent['talks']
talks_url = talks.absolute_url()
brains = catalog(portal_type='talk')
for brain in brains:
if talks_url in brain.getURL():
continue
obj = brain.getObject()
logger.info('Moving %s to %s' % (obj.absolute_url(), talks.absolute_url()))
api.content.move(
source=obj,
target=talks,
safe_id=True)
|
Después de iniciar de nuevo el sitio, nosotros podemos ejecutar el paso de actualización:
Valla al panel control Complementos en la dirección URL http://localhost:8080/Plone/prefs_install_products_form. Ahora debe haber una advertencia This add-on has been upgraded. Old profile version was 1. New profile version is 1001 y un botón al lado de él.
Ejecutar el paso de actualización haciendo clic en el.
En la consola debería ver mensajes de registro como este:
INFO ploneconf.site Moving http://localhost:8080/Plone/old-talk1 to http://localhost:8080/Plone/the-event/talks
Alternativamente, usted puede seleccionar los pasos de actualización para ejecutarlo de la siguiente manera:
En la ZMI valla a la herramienta portal_setup
Valla a la pestaña Upgrades
Seleccione ploneconf.site desde la lista desplegable y haga clic Choose profile
Ejecutar el paso de actualización.
Nota
Actualizando desde una versión anterior de Plone a uno más reciente también puede ejecutar los pasos de actualización del paquete plone.app.upgrade
. Usted debe ser capaz de actualizar un sitio limpio de Plone 2.5 a Plone 5.0a2 con un solo clic.
Para un ejemplo ver el paso de actualización para Plone 5.0a1 https://github.com/plone/plone.app.upgrade/blob/master/plone/app/upgrade/v50/alphas.py#L23
Agregar una browserlayer¶
Una BrowserLayer es otro ejemplo de la interfaz marker. Las BrowserLayers nos permiten fácilmente habilitar y deshabilitar opiniones y funcionalidad de otro sitio basado en complementos y temas instalados.
Como queremos que las características que escribimos sólo estén disponible cuando el paquete ploneconf.site
actualmente este instalado podemos asociarlos a una BrowserLayer.
En el archivo interfaces.py
nosotros agregamos el siguiente código Python:
class IPloneconfSiteLayer(Interface):
"""Marker interface for the Browserlayer
"""
Nosotros registramos la BrowserLayer en GenericSetup en el archivo profiles/default/browserlayer.xml
<?xml version="1.0"?>
<layers>
<layer name="ploneconf.site"
interface="ploneconf.site.interfaces.IPloneconfSiteLayer" />
</layers>
Después de volver a instalar el complemento podemos obligar a las vistas talkview
, demoview
y talklistview
a que sean parte de nuestra layer. Aquí hay un ejemplo usando la vista talkview
.
<browser:page
name="talklistview"
for="ploneconf.site.interfaces.ITalk"
layer="..interfaces.IPloneconfSiteLayer"
class=".views.TalkListView"
template="templates/talklistview.pt"
permission="zope2.View"
/>
Tenga en cuenta el ruta relativa Python ..interfaces.IPloneconfSiteLayer
. Es equivalente a la ruta absoluta ploneconf.site.interfaces.IPloneconfSiteLayer
.
Ejercicio¶
¿Es necesario vincular el viewlet social desde el capítulo 20 de este nuevo Browser-Layer?
Solución
No, no haría ninguna diferencia desde el viewlet ya está asociada a la interfaz marker ploneconf.site.behaviors.social.ISocial
.
Agregar índices del catálogo¶
En la vista talklistview nosotros tuvimos que despertar todos los objetos para acceder a algunos de sus atributos. Eso está bien si no tenemos muchos objetos y ellos son objetos Dexterity ligeros. Si tuviéramos miles de objetos que esto podría no ser una buena idea.
En lugar de cargar a todos en la memoria usaremos índices del catálogo para obtener los datos que queremos mostrar.
Agregar un nuevo archivo profiles/default/catalog.xml
, con el siguiente código XML:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <?xml version="1.0"?>
<object name="portal_catalog">
<index name="type_of_talk" meta_type="FieldIndex">
<indexed_attr value="type_of_talk"/>
</index>
<index name="speaker" meta_type="FieldIndex">
<indexed_attr value="speaker"/>
</index>
<index name="audience" meta_type="KeywordIndex">
<indexed_attr value="audience"/>
</index>
<column value="audience" />
<column value="type_of_talk" />
<column value="speaker" />
</object>
|
Esto agrega los nuevos índices para los tres campos que queremos mostrar en el listado. No es que audience es un KeywordIndex
porque el campo es de varios valores, pero queremos una entrada de índice independiente para cada valor a un objeto.
La entrada column ..
nos permite visualizar estos valores de estos índices en la vista tableview
de colecciones.
Nota
Hasta Plone 4.3.2 agregar índices en el archivo catalog.xml
era perjudicial porque ¡al volver a instalar el complemento purgaba los índices! Ver mas detalles en la dirección URL https://www.starzel.de/blog/a-reminder-about-catalog-indexes.
Para ejecutar código personalizado adicional al volver instalar un complemento que puedes usar un archivo setuphandler.py.
Volver a instalar el complemento
Valla a la URL http://localhost:8080/Plone/portal_catalog/manage_catalogIndexes para inspeccionar el ingreso de datos e inspeccionar los nuevos índices al catálogo
Consulta para índices personalizados¶
Los nuevos índices se comportan como los que Plone tiene construido en:
>>> (Pdb) from Products.CMFCore.utils import getToolByName
>>> (Pdb) catalog = getToolByName(self.context, 'portal_catalog')
>>> (Pdb) catalog(type_of_talk='Keynote')
[<Products.ZCatalog.Catalog.mybrains object at 0x10737b9a8>, <Products.ZCatalog.Catalog.mybrains object at 0x10737b9a8>]
>>> (Pdb) catalog(audience=('Advanced', 'Professionals'))
[<Products.ZCatalog.Catalog.mybrains object at 0x10737b870>, <Products.ZCatalog.Catalog.mybrains object at 0x10737b940>, <Products.ZCatalog.Catalog.mybrains object at 0x10737b9a8>]
>>> (Pdb) brain = catalog(type_of_talk='Keynote')[0]
>>> (Pdb) brain.speaker
u'David Glick'
Ahora podemos usar los nuevos índices para mejorar la vista talklistview
así que no tenemos que despertar los objetos nunca más.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | class TalkListView(BrowserView):
""" A list of talks
"""
def talks(self):
results = []
portal_catalog = getToolByName(self.context, 'portal_catalog')
current_path = "/".join(self.context.getPhysicalPath())
brains = portal_catalog(portal_type="talk",
path=current_path)
for brain in brains:
results.append({
'title': brain.Title,
'description': brain.Description,
'url': brain.getURL(),
'audience': ', '.join(brain.audience or []),
'type_of_talk': brain.type_of_talk,
'speaker': brain.speaker,
'uuid': brain.UID,
})
return results
|
La plantilla no necesita ser cambiado y el resultado no cambió también.
Agregar criterio de colección¶
Para ser capaz de buscar contenido en tipo de contenidos Colecciones utilizando los nuevos índices tendríamos que registrarlos como criterios para el widget de cadena de consulta que usan las Colecciones.
Agregar un nuevo archivo profiles/default/registry.xml
, agregando el siguiente código XML:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <registry>
<records interface="plone.app.querystring.interfaces.IQueryField"
prefix="plone.app.querystring.field.audience">
<value key="title">Audience</value>
<value key="description">A custom speaker index</value>
<value key="enabled">True</value>
<value key="sortable">False</value>
<value key="operations">
<element>plone.app.querystring.operation.string.is</element>
</value>
<value key="group">Metadata</value>
</records>
<records interface="plone.app.querystring.interfaces.IQueryField"
prefix="plone.app.querystring.field.type_of_talk">
<value key="title">Type of Talk</value>
<value key="description">A custom index</value>
<value key="enabled">True</value>
<value key="sortable">False</value>
<value key="operations">
<element>plone.app.querystring.operation.string.is</element>
</value>
<value key="group">Metadata</value>
</records>
</registry>
|
Añadir más características a través de GenericSetup¶
Habilitar el control de versiones y una visión del diff para los tipos de contenidos talks a través de GenericSetup.
Agregar nuevo archivo profiles/default/repositorytool.xml
, con el siguiente código XML:
1 2 3 4 5 6 7 8 9 | <?xml version="1.0"?>
<repositorytool>
<policymap>
<type name="talk">
<policy name="at_edit_autoversion"/>
<policy name="version_on_revert"/>
</type>
</policymap>
</repositorytool>
|
Agregar nuevo archivo profiles/default/diff_tool.xml
, con el siguiente código XML:
1 2 3 4 5 6 7 8 | <?xml version="1.0"?>
<object>
<difftypes>
<type portal_type="talk">
<field name="any" difftype="Compound Diff for Dexterity types"/>
</type>
</difftypes>
</object>
|