A continuación voy a explicar todos los pasos que he seguido para migrar una aplicación desarrollada en Zope con: ZPT,. SQL Methods, python scripts, MySQL... a Django usando como sistema de plantillas ZPT.
Los apartados en que he dividido el texto son:
- Instalación: Django, ZPT para Django, mod_python, pagetemplates para Django, MySQL, python_mysqldb...
- Migrando de Zope a Django: recetas para pasar los ZPT, código, SQL, internacionalización, media...
01. Instalación.
La instalación la haremos en un servidor Debian estable. De hecho un requisito básico para esta instalación será mantener todos los paquetes en stable (mod_python, Zope...).
He instalado los siguientes paquetes:
Apache:
apt-get install apache2 apache2-common apache2-mpm-worker apache2-utils libapr0 libexpat1 openssl ssl-cert
Para Django:
apt-get install python-dev
Para Zope me he encontrado con algunos problemas:
Inicialmente el python por defecto es el 2.3, además no hay mod_python en estable para 2.4, así que he buscado un Zope3 que se pueda ejecutar con python2.3.5 y he visto que la ultima version de Zope3 necesitaba 2.4, he estado revisando versiones estables de Zope3, y estas son las conclusiones:
| Versión Zope | Versión Python | Válido para nuestra instalación |
| ZopeX3-3.00 | 2.3.4 | OK |
| ZopeX3-3.0.1 | 2.3.5 | OK |
| Zope-3.1 | 2.3.5 | OK |
| Zope-3.2b2 | 2.4.2 | NOK |
| Zope-3.2.0 | 2.4.2 | NOK |
Nos quedamos con la última válida: Zope-3.1.0.tgz
Compilamos Zope3, descargamos el .tgz y en el directorio de la extracción:
| Zope-3.1.0# ./configure Zope-3.1.0# make Zope-3.1.0# make install |
El 'make install' por defecto hace esto:
| /usr/bin/python install.py -q build /usr/bin/python install.py -q install --skip-build --home "/usr/local/Zope-3.1.0" |
Como sólo necesitemos en la ruta de librerías de PYTHON lo referente a los pagetemplates (ZPT) vamos a copiar sólo a /usr/lib/python2.3/site-packages el pagetemplate y sus dependencias, las dependencias son:
| zope.componentzope.exceptionszope.i18nzope.i18nmessageidzope.interfacezope.schemazope.talzope.taleszope.testingpytz |
Ojo, no hay que olvidar los __init__ del directorio zope (sino no es un modulo ;-) ).
Hay otras dependencias para poder usar los ZPT de Zope3:
apt-get install python-docutils python-roman
Django:
Descargamos la última versión estable, ahora mismo la estable es la 0.91:
wget http://www.djangoproject.com/download/0.91/tarball/
Descomprimimos e instalamos en el Python Path:
tar xfvz Django-0.91.tar.gz
cd Django-0.91
python setup.py install
pagetemplates para Django:
Descargamos este paquete que nos permitirá llamar a las pagetemplates de Zope desde Django:
wget http://www.zope.org/Members/shh/DjangoPageTemplates/1.0.2/djangopagetemplates-1.0.2.tar.gz
Descomprimimos el anterior dentro en el site-packages (del PYTHON_PATH) dentro de django/contrib.
Usando 'ñ', acentos... en los ZPT:
Zope siempre espera que la codificación que le entre en los ZPT sea unicode, pero ahora mismo tenemos:
| Sw | Codificación |
| MySQL | latin1 |
| Python | ascii |
| Zope | unicode |
| HTML(ZPTs) | utf-8 |
La solución a este embrollo es que Zope internamente trabaje en unicode (no es problema siempre que lo que le pasemos, lo que recuperemos y lo que mostremos tenga
la misma codificación), así pues el escenario que queremos es:
| Sw | Codificación |
| MySQL | latin1 |
| Python | latin1 |
| HTML | latin1 |
| Zope | unicode |
| browser | latin1 |
Así pues hay que hacer los siguientes cambios:
- MySQL: no hay que tocar nada, las tablas ya son latin1.
- Python: crear el fichero /usr/lib/python2.3/site-packages/sitecustomize.py que contenga.
- HTML generado: esto no es un
tema de configuración del servidor sino que hay que tener en
cuenta cuando se programe. Cuando hagamos un HttpResponse para llamar
al template a renderizar indicaremos que su codificación es
latin1.
- Para el browser: esto al igual que el anterior no es configuración del servidor sino que lo haremos dentro de los ZPT, hay que indicar que la codificación a usar el latin1:
Evidentemente otra opción sería pasar todo a utf-8, que sería lo más "moderno", pero esto lo dejamos para más adelante, ya que requiere recodificar el contenido de la base de datos, en este link se indica como hacerlo, por si a alguien le apetece. Si lo hacéis comentadme cómo os fue:
http://www.linuxparatodos.net/geeklog/staticpages/index.php?page=como-mysql-utf8
Test:
Para probar que va todo bien, desde un proyecto creado (o crear uno sin nada, para testear), lanzamos:
python manage.py shell
from django.contrib.pagetemplate import pagetemplate
Si no aparece ningún error quiere decir que la instalación de "Django + ZPT" inicialmente va bien.
mod_python:
apt-get install libapache2-mod-python
Hay que indicar a mod_python donde tiene que buscar los ficheros de las aplicaciones (esto es mejor que tener que editar el PythonPath para el usuario que lance Apache):
debian:/etc/apache2/mods-available# cat mod_python.load
| LoadModule python_module /usr/lib/apache2/modules/mod_python.soPythonPath "['/home/moscardon/proyectos/Django/',] + sys.path" | |
Internacionalización:
apt-get install gettext
Cliente MySQL para Python:
Cliente de línea de comandos por si lo necesitamos y cliente para python:
apt-get install mysql-client python2.3-mysqldb libdbd-mysql-perl libdbi-perl libmysqlclient12 libnet-daemon-perl libplrpc-perl mysql-common
Servidor MySQL:
En nuestro caso tenemos el servidor MySQL en la misma máquina.
apt-get install mysql-server
Cambiar el password de root:
mosca:/home/moscardon/temp# su - root
mosca:~# /usr/bin/mysqladmin -u root password 'moscardon'
02. Migrando de Zope a Django.
Indicar la ruta de los templates:
Hay que añadir a TEMPLATE_DIRS de settings la ruta donde estaran los templates, pej:
| TEMPLATE_DIRS = ("/home/moscardon/proyectos/Django/mch/ch/templates",# Put strings here, like "/home/html/django_templates".# Always use forward slashes, even on Windows.) |
Recetas:
SQLMethods:
En los ZPT hay que quitar las lamadas a los SQL Methods y ponerlas en las views asignando los resultados a variables que pasaremos a los templates.
ZPT original:
| <option tal:define="personaList python: container.sql.sql_CH_MASTER_PERSONA_list" tal:repeat="idpersona personaList"tal:attributes="value python: idpersona['idpersona']"tal:content="python: idpersona['nombre_persona'] + ' -> ' + idpersona['nombre_equipo'] "></option> |
ZPT nuevo para Django:
| <optiontal:repeat="idpersona personaList"tal:attributes="value python: idpersona['idpersona']"tal:content="python: idpersona['nombre_persona'] + ' -> ' + idpersona['nombre_equipo'] "></option> |
views.py:
| t = pagetemplate.get_template('ejemplo') personaList = tabla.get_values() c = Context({'personaList': personaList,}) return HttpResponse(t.render(c)) |
Define de Variables:
Los define de variables se suprimen en los ZPT y se pasan a los views.
ZPT original:
| <span tal:define="variable python: 'hola'"> |
ZPT nuevo: (no necesita para nada el define)
views.py:
| t = pagetemplate.get_template('ejemplo') c = Context({'variable': 'hola', }) Return HttpResponse(t.render(c)) |
Plantillas html:
En el ZPT donde se quiere usar la plantilla, pondremos:
| <span tal:replace="structure python: footer">footer</span> |
En el views hay que dar valor a footer:
| footer = open(pagetemplate.TEMPLATE_DIRS[0]+'/footer.pt').read() |
Y pasar la variable a la plantilla:
| c = Context({'footer': footer,}) |
Queries con joins para sacar información relacionada de varias tablas:
Por ejemplo:
select t.id, t.start_date, a.description, p.description
from tareas t, actividades a, personas p
where t.actividad = a.actividad and
t.persona = p.persona
y queremos mostrar en el HTML
{descripcion de la actividad} {descripcion de la persona} {start_date} {oculto el id}
En el views.py haremos:
listat = tareass.get_values()
a = actividadess.get_values()
p = personass.get_values()
for i in range(len(a)):
diccionario_actividades[a[i]['id']] = a[i]['description']
for i in range(len(p)):
diccionario_personas[p[i]['id']] = p[i]['description']
pasaremos como parámetros al html listat, diccionario_actividades, diccionario_personas
Y en el html pondremos:
en el repeat --> tal:repeat="t listat"
Y para mostrar:
{diccionario_actividades[t['actividad_id']]} {diccionario_personas[t['persona_id']]} {t['start_date']} {t['id']}
Queries en las que el WHERE tiene joins con varias tablas:
select t.*
from tareas t, actividades a
where a.tipologia = 5 and
t.actividad = a.actividad
Las actividades tienen asociadas tipologias y las tareas asociadas actividades el filtro es por las tipologias que tienen las tareas
t = tareass.get_values(actividad__tipologia__id__exact=5)
actividad -> es el campo de tareas que hace la join con la tabla actividad
tipologia__id -> es el campo de la tabla actividad por la que queremos filtrar
Queries con or:
select * from tareas t
where t.start between '01/01/2005' and '31/12/2005' and
( t.end is null or t.end <= '31/12/2005' )
Sería:
tareass.get_values(start__range=(datetime(*time.strptime('01/01/2005', "%d/%m/%Y")[0:3]),
datetime(*time.strptime('31/12/2005', "%d/%m/%Y")[0:3])),
complex=(Q(end__isnull=True) | Q(end_date__lte=datetime(*time.strptime('31/12/2005', "%d/%m/%Y")[0:3]))
)
Updates (con foreign key incluso):
t = taskss.get_object(pk=request.GET['id'])
t.person_id=request.GET['idperson'] # <-- FK
t.code=request.GET['code']
t.create_date=datetime(*time.strptime(request.GET['create_date'], "%Y-%m-%d %H:%M:%S")[0:3])
t.description=request.GET['description']
t.end_date=datetime(*time.strptime(request.GET['end_date'], "%Y-%m-%d %H:%M:%S")[0:3])
t.requester=request.GET['requester']
t.comments=request.GET['comments']
t.priority=request.GET['priority']
t.status_id=request.GET['idstatus'] # <-- FK
t.activity_id=request.GET['idactivity'] # <-- FK
t.reviews_number=request.GET['reviews_number']
t.duration=request.GET['duration']
t.start_date=datetime(*time.strptime(request.GET['start_date'], "%Y-%m-%d %H:%M:%S")[0:3])
t.save()
Inserts:
task = taskss.tasks(
person=master_persons.get_object(pk=request.GET['idperson']),
code=request.GET['code'],
create_date=datetime(*time.strptime(request.GET['create_date'], "%d/%m/%Y")[0:3]),
description=request.GET['description'],
end_date=datetime(*time.strptime(request.GET['end_date'], "%d/%m/%Y")[0:3]),
requester=request.GET['requester'],
comments=request.GET['comments'],
priority=request.GET['priority'],
status=master_statuss.get_object(pk=request.GET['idstatus']),
activity=master_activitys.get_object(pk=request.GET['idactivity']),
reviews_number=request.GET['reviews_number'],
duration=request.GET['duration'],
start_date=datetime(*time.strptime(request.GET['start_date'], "%d/%m/%Y")[0:3]))
task.save()
Link sobre como trabajar en equipo con Django:
http://www.socialistsoftware.com/post/using-django-with-a-small-team/
Link sobre internacionalización con Django:
http://www.djangoproject.com/documentation/i18n/
Usar media en Django:
Crear un directorio media dentro del proyecto:
/home/moscardon/projectos/django/mch/ch/media/
Hacer un link a la media del admin (si queremos usar el admin):
cd /home/moscardon/projectos/django/mch/ch/media/
ln -s /usr/lib/python2.3/site-packages/Django-0.91-py2.3.egg/django/contrib/admin/media admin
Cambiar el settings.py:
| MEDIA_ROOT = '/home/moscardon/projectos/django/mch/ch/media/'MEDIA_URL = 'http://c02007381/media/'ADMIN_MEDIA_PREFIX = 'http://c02007381/media/admin/' |
Ahora pej. para usar un style sheet (css):
copiamos el style_sheet al directorio media y ponemos en el html:
| <style type="text/css"><!-- @import url(/media/style_sheet);--></style> |
Usando 'ñ', acentos... en los ZPT:
Zope siempre espera que la codificación que le entre en los ZPT sea unicode, pero ahora mismo tenemos:
| Sw | Codificación |
| MySQL | latin1 |
| Python | ascii |
| Zope | unicode |
| HTML(ZPTs) | utf-8 |
La solución a este embrollo es que Zope internamente trabaje en unicode (no es problema siempre que lo que le pasemos, lo que recuperemos y lo que mostremos tenga
la misma codificación), así pues el escenario que queremos es:
| Sw | Codificación |
| MySQL | latin1 |
| Python | latin1 |
| HTML | latin1 |
| Zope | unicode |
| browser | latin1 |
Así pues hay que hacer algunos cambios a nivel de servidor que ya se han comentado en el apartado 01. Instalación, a continuación lo que hay que tener en cuenta a la hora de desarrollar:
- HTML generado: Cuando hagamos un HttpResponse para llamar
al template a renderizar indicaremos que su codificación es
latin1.
- Para el browser: dentro de los ZPT, hay que indicar que la codificación a usar el latin1:
Evidentemente otra opción sería pasar todo a utf-8, que sería lo más "moderno", pero esto lo dejamos para más adelante, ya que requiere recodificar el contenido de la base de datos, en este link se indica como hacerlo, por si a alguien le apetece. Si lo hacéis comentadme cómo os fue:
http://www.linuxparatodos.net/geeklog/staticpages/index.php?page=como-mysql-utf8


Comments
Datos de performance?
Recetas
Estoy interesado en los datos comparativos de performance entre los dos sistemas, pero parece que no los agregaron, lo haran algun dia?
Coco
Teniendo en cuenta lo limitad
Teniendo en cuenta lo limitado que es tu servidor físico (y más aun en el servidor virtual con QEMU), si la reducción de uso de memoria es tan significativa, el incremento de rendimiento que notarás será grande pues tendrá más memoria para cache y no deberá hacer tanto swapping. Creo que has tomado una buena decisión "saltando" a Django.
Saludos,
Kilian
www.kilian.chouza.com
Lo añadiré
Tengo la intención de en cuanto tenga comparativas de rendimiento entre ambas plataformas para la misma aplicación añadirlas al Post.
Re: Lo has conseguido !
Pues sí, pero es que hay mucha diferencia entre un Zope y un Django.
Django es "sólo" una serie de librerías que generan las páginas, la base de datos es MySQL. Zope es algo bestial, con su servidor interno de base de datos de objetos (ZODB), su modelo de clases para "hacer de todo", sus herramientas de control de usuarios, permisos a nivel de objetos...
Sólo con el tema de no usar el ZODB y usar MySQL ya se gana muchísimo, pero si a esto unimos el tema de dejar de usar Medusa (el servidor Web de Zope) para usar Apache + mod_python, pues el ahorro de recursos aún se nota más. Y aún más si tenemos en cuenta que Zope para funcionar tiene que arrancar todo su core (management), y dejarlo en memoria, esto es una cantidad de memoria fija y no rebajable, evidentemente en Python esto no tiene equivalencia.
Actualmente el servidor es una máquina virtual (QUEMU, of course), y sólo está migrada la aplicación al 40%, con lo que es difícil comparar de igual a igual ambas soluciones. Lo que si que se puede asegurar es que en memoria el consumo es muy, muy inferior en Django que en Zope.
El hecho de usar MySQL en
El hecho de usar MySQL en lugar del ZODB y Apache en lugar de Medusa no son ventajas a favor de Django y en contra de Zope, pues que yo sepa prácticamente todos los desarrolladores Zope usan una BBDD externa, como MySQL u ORACLE a la que cargan con el DataWarehouse y además usan Apache delante del Zope como servidor Web.
Que Django tiene sus ventajas y desventajas respecto a Zope está claro, pero no creo que haga falta decir verdades a medias para "vender" la opción que uno ha preferido. Es mi opinión, vamos.
Saludos.
Más o menos
Hola, que yo sepa no hay forma de evitar usar el ZODB para las páginas, ficheros y objetos internos de Zope. ¿ Hay forma de evitar arrancar el ZODB y que funcione el servidor de aplicaciones ?.
De todos modos creo que no he explicado bien mi artículo porque lo que vengo a decir es que mi server está justo de recursos y que pasar de ZODB+Medusa a mysql u otro servidor light + Apache u otro servidor light me hace ahorrar recursos.
¿ Verdades a medias ?, anda ya!!!!
¡Lo has conseguido!
Hola,
Veo que al final has conseguido librarte de Zope. ¿Que tal va de rendimiento y recursos usados ahora? ¿se nota más descargado el servidor?
Saludos,
Kilian
Enviar un comentario nuevo