sábado, 4 de septiembre de 2010

Lanzar informes Crystal Reports desde Python



Hola. En este post vamos a ver cómo lanzar informes de Crystal Reports desde Python. Veremos la forma tanto de enviar directamente el informe a la impresora, como de hacer una visión preliminar utilizando formatos de salida PDF, así como de paso de parámetros a dichos informes, entre otras cosas. Todo ello desde Python, por supuesto.

Si el lector desconoce Crystal Reports, solo decir que es un software de creación de informes de todo tipo a partir de un origen ú orígenes de datos (bases de datos, cubos OLAP, Excel, ficheros de texto, etc), de los más utilizados a nivel mundial, debido fundamentalmente a su gran adaptabilidad con el software de desarrollo de Microsoft (Visual Studio).

No es propósito de este artículo enseñar cómo funciona Crystal Reports, así que se supone que el lector tiene conocimientos mínimos sobre este software de creación de informes. Decir que Crystal está pensado para plataformas Microsoft Windows. En mi caso, utilizaré Crystal Reports XI sobre Windows XP Service Pack 3.

Para poder utilizar Python y Crystal Reports primeramente hay que descargarse, de SourceForge, Python for Windows Extensions (pywin32) de Mark Hammond. En mi caso, que tengo Python 2.5, me descargo pywin32 para Python 2.5 y se instala normalmente.

Darse cuenta que se instala PythonWin.

¿Y por qué instalarse pywin32? La respuesta a continuación.

Microsoft's Common Object Model (COM)

Component Object Model (COM) es una plataforma de Microsoft para componentes de software. Esta plataforma es utilizada para permitir la comunicación entre procesos y la creación dinámica de objetos, en cualquier lenguaje de programación que soporte dicha tecnología. Es decir, utilizando COM se puede escribir código en un lenguaje de programación, como C++ y usar el código en otro lenguaje de programación, como Visual Basic ó Python.

El término COM es a menudo usado en el mundo del desarrollo de software como un término que abarca las tecnologías OLE, OLE Automation, ActiveX, COM+ y DCOM.

Python tiene un soporte excelente para COM. De hecho, el soporte de Python es tan bueno que se puede usar para extender Python.

Python puede usar y crear objetos COM. Si se necesita escribir una extensión Python exclusivamente para Windows, habría que considerar utilizar COM.

Esencialmente COM es una manera de implementar objetos neutrales con respecto al lenguaje, de manera que pueden ser usados en entornos distintos de aquel en que fueron creados. COM permite la reutilización de objetos sin conocimiento de su implementación interna, ya que fuerza a los implementadores de componentes a proveer interfaces bien definidos que están separados de la implementación.

Para utilizar COM en Python es necesario instalar Python Win32 extensions, tal como se ha indicado al comienzo del post.

Utilizando un componente COM

Los pasos para usar un objeto COM en Python son los mismos que en cualquier otro lenguaje de programación. Primero, hay que obtener una instancia de la clase, esto es, un objeto. Una vez instanciado, se utilizan los métodos y atributos del objeto COM como si fueran objetos normales en Python.

Para utilizar un objeto COM en un código Python hay que comenzar ejecutando la utilidad makepy, en PythonWin, para buscar el componente COM que queremos usar.


Esto se hace básicamente por dos motivos (aunque no es obligatorio). El primero, cuando se importan las constantes de clase, se dispone de los nombres de las constantes definidos en la librería de tipos (typelib), y en segundo lugar obtenemos completitud de código de los métodos y atributos del componente (si no se utiliza makepy habrá que usar valores numéricos en ver de nombres de constantes).

Abrimos makepy y se nos muestra una lista de los componentes COM registrados del sistema. Seleccionamos el que queremos utilizar.

¿Qué componente de Crystal Reports utilizar?

NOTA: Hace unos años que empecé a trabajar con Crystal Reports, en su versión 8.5. Atacaba una base de datos, desde Microsoft Visual Basic 6, con el objeto Crystal Reports ActiveX (CRYSTL32.OCX), el cual es sencillo de utilizar. Sin embargo Crystal Decisions (ahora SAP) desde la versión 7 de Crystal Reports recomienda el uso del Report Designer Component (RDC) para todo desarrollo o integración con aplicaciones propias. Es más, trabajar con RDC aporta herramientas nuevas. Por ejemplo con RDC se puede exportar a PDF, no pudiendo con Crystal Reports ActiveX.

El RDC agrupa a cuatro controles (por lo menos en la versión 8.5 de Crystal):

  • Designer Runtime Library (CRAXDRT.DLL): Es un servidor de automatización COM que provee la manipulación del informe así como de sus funciones de impresión, y de exportación.
  • Distributable Report Designer: Es un control ActiveX que permite diseñar y crear informes dentro de una aplicación. Se necesitan licencias de usuario para su uso. No se utilizará aquí.
  • Designer Designe Runtime Library (CRAXDDRT.DLL) que provee toda la funcionalidad del primero pero que además soporta el Distributable Report Designer.
  • Crystal Report Viewer: Es un control ActiveX que se usa para las vistas previas de informes.
En este artículo veremos la utilización de Designer Runtime Library, para aprender como intregrar el RDC con Python.

¿Qué es el RDC?

El Report Designer Component es un modelo de objeto de interfaz dual basado en tecnología COM, un estándar, que como anteriormente se ha comentado, permite a aplicaciones y objetos componentes comunicarse unos con otros. El estandar no especifica como están estructurados los objetos, solo definen como se comunican entre ellos. El RDC se puede utilizar en cualquier entorno de desarrollo que soporte COM, como es el caso de Python.

Buscando por el makepy nos encontramos con el Crystal Reports ActiveX Designer Runtime Library 11.


Haciendo un makepy al Designer Runtime Library

Hacemos click en OK y nos genera el fichero .py en donde se encuentran las clases, constantes y demás cosas que nos interesan para poder acceder a Crystal Reports.

Vamos al directorio gen_py (en C:\Python25\Lib\site-packages\win32com\gen_py) , que es en donde se ha generado el fichero .py.

Utilizando un IDE (aquí he utilizado Pyragua) ó el propio PythonWin podemos ver el fichero que se genera. En realidad lo que tenemos son todas las constantes y clases que necesitaremos para poder acceder a Crystal Reports.

Por otra parte, si nos vamos al manual que trae Crystal Reports para desarrolladores, el Crystal Reports Developer's Help (CrystalDevHelp.chm), en la sección Understanding the RDC Object Model/The Primary Objects and Collections, nos encontramos con un gráfico de la jerarquía de clases para utilizar en Crystal.

NOTA: Los ficheros de ayuda de Crystal Reports que vienen con el producto los podemos encontrar en:

Si nos vamos al fichero .py generado podemos ver que se trata del módulo que contiene la implementación de la jerarquía de clases expuesta.

¿Cómo utilizar un componente COM en Python?

Utilizar un componente COM en Python es muy sencillo. Después de ejecutar makepy se importa el módulo COM soportado:

import win32com.client as componenteCOM

Si se utilizó la utilidad makepy sobre el componente tendremos definidas laos nombres de las constantes de clase.

from win32com.client import constants

Una vez que el módulo se haya importado se puede instanciar el componente COM de la siguiente forma:

objeto = componenteCOM.Dispatch('CrystalRuntime.Application')

Para instanciar un componente COM se necesita saber el nombre de la clase del componente, que se puede encontrar en la documentación de dicho componente ó mirando las entradas del registro para el objeto. Si se ejecuta el makepy sobre el componente el nombre del componente y GUID se generan en el fichero .py creado.


Lanzar informes de Crystal Reports desde Python: El Ejemplo definitivo.

Bien, después de una introducción algo extensa vamos a ir directamente al ejemplo de uso de informes Crystal por parte de Python.

Como se ha comentado anteriormente, Crystal lee de un origen de datos, en nuestro caso tablas de bases de datos, y mediante un diseñador de informes, configuramos y creamos el informe que será lanzado cada vez que se requiera, como por ejemplo un diseño de factura, albarán, cuenta de resultados, etc.

En Crystal hay dos formas de obtener datos de tablas:

1) Crystal Reports dispone de un asistente de bases de datos para crear los vínculos (relaciones) entre tablas. Se pueden crear ú obtener las relaciones entre las tablas para diseñar el report.

2) Podemos crear en nuestra base de datos una tabla, con unos campos definidos, de modo que se recarguen siempre que se quiera realizar un informe. Esto es, calcular previamente y cargar en esa tabla los campos que conformarán nuestro informe. Este es un truco que se realiza muchas veces para minimizar la complejidad de informes Crystal, de modo que las operaciones que se pudieran realizar sobre el informe se hacen por programa y el resultado se guarda en la tabla de la que lee Crystal. Recordar que esto es bueno cuando la base de datos es muy compleja, esto es, tiene unos vínculos muy grandes, muchas relaciones, y es difícil crear informes. De esta manera, solamente es necesario cargar una tabla. Importante tener en cuenta que dicha tabla es de la que lee Crystal Reports.

NOTA: El lector debe de ser consciente que nunca se ha comentado que Crystal sea fácil de manejar. Únicamente que es una herramienta extremadamente potente para la creación y diseño de informes de gestión.

Imaginemos que tenemos una base de datos con la siguiente estructura:


Por un lado imaginemos que se ha creado una capa gráfica por encima (con wxPython, por ejemplo), para insertar datos. Únicamente nos queda realizar ciertos informes para sacar datos por impresora. Vamos a crear un informe tipo factura básico para nuestro sistema de información.

Abrimos Crystal Reports, conectamos con la base de datos, creamos los vínculos entre tablas que nos harán falta, y diseñamos el informe, tal que así:

Suponemos que se han incluido datos en las tablas de la base de datos...

En nuestro fichero .py que se encargará de la gestión del lanzamiento de informes Crystal podría haber un código tal que así:

from win32com.client import Dispatch
import os

Nombres de fichero.
nom_fich_rpt = os.path.realpath('FACTURA_CLIENTE2.rpt')
nom_fich_pdf = os.path.realpath('factura_cliente_aux2.pdf')

Abrimos fichero Crystal Reports RPT.
aplicacion = Dispatch('CrystalRunTime.Application')
informe = aplicacion.OpenReport(nom_fich_rpt)

NOTA: Este informe Crystal Reports ataca a una diagrama de E-R definido en el diseñador de informes de Crystal. Se le tiene que pasar como parámetro el número de factura.

Impresión directa, pidiendo el parámetro.

Descartamos los datos que se hayan grabado con el informe. De esta manera obligamos Crystal a que nos pregunte los parámetros que necesita el informe.
if informe.HasSavedData: informe.DiscardSavedData()

Imprimimos directamente.
informe.PrintOut(promptUser=False)

Exportar informe RPT a PDF y visualizarlo. Para ello se tiene que tener instalado en la máquina programa que abra ficheros PDF, tal como Acrobat Reader ó similar, que esté predeterminado a usarse en la apertura de este tipo de archivos.

Configuramos exportación. Los parámetros de configuración (sus valores) se pueden obtener de los ficheros de ayuda que vienen con Crystal anteriormente comentados.
informe.ExportOptions.DiskFileName = nom_fich_pdf
informe.ExportOptions.DestinationType = '1'
informe.ExportOptions.FormatType = '31'
Obtenemos campos de parámetros.
parametros = informe.ParameterFields
p1 = parametros(1)
Limpiamos valor del parámetro.
p1.ClearCurrentValueAndRange()
Pedimos nuevo parámetro.
j = raw_input('Nº de factura: ')
Configuramos parámetro del informe.
p1.AddCurrentValue(j)
Exportamos informe.
informe.Export(False)
Lanzar el programa por defecto que abre los ficheros PDF.
os.startfile(nom_fich_pdf)

Podemos obtener las tablas que se utilizan en el informe.
for i in informe.Database.Tables: print i.Name

Y mostrar la sentencia SQL del informe.
print informe.SQLQueryString

Si queremos que Crystal nos muestre a qué fichero exportar de una lista predeterminada, puede hacerse, de la siguiente manera:
informe.Export(True)



CONCLUSIONES

En este post se ha visto unas nociones básicas de objetos COM, y como desde Python, por medio de pywin32, se puede utilizar dicha tecnología.

Crystal Reports en un generador de informes, comercial, nada barato (rondando los 600 euros), pero extremadamente potente, que se puede utilizar para la creación de informes para nuestro sistema de información. Evidentemente hay otros productos en el mercado, como ReportLab (del que tanto se ha hablado en este blog), que tiene una versión gratuita, pero sin el diseñador y la potencialidad de su entorno de desarrollo.

Con este artículo se pretende que el lector se haya dado cuenta de las posibilidades de Python de poder acceder a otros paquetes de software, su integración con los mismos, y su extremada sencillez de utilización (uso de acceso a COM, no de utilización de Crystal).

Saludos.

1 comentario:

  1. Excelente la Presentación. He tratado de hacer todo lo que usted describe en la publicación. Pero no he podido poner a funcionar el reporte, cuando llego a esta instrucción:

    objeto = componenteCOM.Dispatch('CrystalRuntime.Application')

    Me da el siguiente error:

    Traceback (most recent call last):
    File "C:\Python35\lib\site-packages\win32com\client\dynamic.py", line 89, in _GetGoodDispatch
    IDispatch = pythoncom.connect(IDispatch)
    pywintypes.com_error: (-2147221021, 'Operación no disponible', None, None)

    During handling of the above exception, another exception occurred:

    Traceback (most recent call last):
    File "nuevoreporte", line 22, in
    main()
    File "nuevoreporte", line 12, in main
    app = Dispatch('CrystalRuntime.Application')
    File "C:\Python35\lib\site-packages\win32com\client\__init__.py", line 95, in Dispatch
    dispatch, userName = dynamic._GetGoodDispatchAndUserName(dispatch,userName,clsctx)
    File "C:\Python35\lib\site-packages\win32com\client\dynamic.py", line 114, in _GetGoodDispatchAndUserName
    return (_GetGoodDispatch(IDispatch, clsctx), userName)
    File "C:\Python35\lib\site-packages\win32com\client\dynamic.py", line 91, in _GetGoodDispatch
    IDispatch = pythoncom.CoCreateInstance(IDispatch, None, clsctx, pythoncom.IID_IDispatch)
    pywintypes.com_error: (-2147221164, 'Clase no registrada', None, None)

    ***Repl Closed***

    No he podido pasar de acá y me urge mucho comenzar a realizar reportes en -cristal y python.

    Mucho le agradecería me pudiera ayudar.

    Roberto Matarrita
    Costa Rica
    rmatarria@gmail.com

    ResponderEliminar