Automágica: durante 2017 estoy trabajando bastante en Automágica, mi software para editar libros: Más información - Posts relacionados

Algunos problemas y soluciones al levantar bases de datos legacy con Django

Con Django podemos levantar una base de datos legacy en lugar de definir nuestro modelo de datos y arrancar una aplicación desde cero. No es necesariamente una base de datos vieja, sino una base de datos heredada de otro sistema; puede ser de un sistema anterior que se está reemplazando o incluso de otro sistema que queremos usar de forma paralela.

Luego de configurar nuestra base de datos y ejecutar:

python manage.py inspectdb > models.py

obtenemos una definición de modelos basada en en las tablas de la base de datos. Lo siguiente es ver si anda.

Lo más probable es que obtengamos un NameError. Esto pasa cuando en la definición de alguno de los modelos se hace referencia a otro modelo aún no definido! La solución indicada es empezar a reordernar los modelos, pero una forma más fácil es cambiar los nombres por strings. Un ejemplo:

jefe = models.ForeingKey(Empleado)

por

jefe = models.ForeingKey('Empleado')

Otra queja que nos puede hacer Django es que tengamos atributos llamados id que no sean primary key. Solución: les cambiamos el nombre.

Eventualmente vamos a necesitar habilitar la aplicación admin (ya que estamos usando Django para levantar datos, nada mejor que usar su ABM estrella para ahorrarnos mucho trbajo). Los pasos para hacer con éxito son:

  1. Habilitar la aplicaccion admin en settings.py.
  2. Ejecutar syncdb para que se creen las tablas de esta aplicación.
  3. Agregar los modelos de la base de datos heredada al archivo admin.py de la aplicación.

Por supuesto, si los nuevos modelos son cientos, es bastante engorroso hacer esto a mano. Les paso un hack; así luce mi admin.py:

from django.contrib import admin

from django.db.models import base

from my_app.models import *



for k,v in locals().items():

    if isinstance(v, base.ModelBase):

        admin.site.register(v)

Tip final. Si obtenemos un error de este tipo:

OperationalError at /admin/mango/datapointusers/

(1054, "Unknown column 'dataPointUsers.id' in 'field list'")

es por que alguna de las tablas levantadas no tenía una columna que sea clave primaria. Django necesita esto para poder distinguir los objetos entre si. Para cada tabla dónde tengamos este problema, podemos ejecutar el siguiente comando SQL para agregarle una columna llamada id; clave primaria y auto numérico.

ALTER TABLE `dataPointUsers` ADD `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY;


El hosting para Django más barato

Hace un tiempo recomendé Webfaction como el mejor hosting para Django (debe aún serlo). Pero algunas veces no queremos el mejor, con el más barato nos alcanza :D

Tal vez son un adolecente aprendiendo a programar, o simplemente rata. Para un programador PHP es bastante fácil encontrar un hosting en Internet que le de un servicio gratuito, subir sus cosas y mostrarlas al mundo. Cuando programás en Django... es más difícil.

Hasta hoy; les paso el dato: AlwaysData.com

Hosting Django gratuito.

Tengo 2 cuentas funcionando muy bien. Y no se asusten por que está en francés (eso me detuvo la primer vez que lo vi); luego de sacar la cuenta tenés acceso al panel de administración que está en muchos idiomas; incluyendo español. Tenés para elegir distintas versiones de Python y Djando, MySQL o PosgreSQL, acceso SHELL, FTP y WebDAV. Qué más querés?

También podés tener PHP o Ruby on Rails.

Espero les sirva.


os.path en el settings.py de Django para mayor comodidad

En el archivo de configuración settings.py de un proyecto Django, por lo general tenemos que setear variables como MEDIA_ROOT o STATIC_DOC_ROOT. Su contenido en una instalación Windows suele ser algo como: 'C:\Windows\camino\hasta\mi\projecto'. Y en Linux: '/home/usuario/camino/a/mi/proyecto'. El problema surge cuando el proyecto es desarrollado en varias máquinas a la vez, y con distintos sistemas operativos. Más aún, si hacemos lo anterior, seguramente versionaremos el proyecto y con él, al archivo de configuración. No sería raro que tras una actualización, el archivo se actualice con los valores que puso algún compañero de trabajo.

Mi solución es definir primero una variable para el proyecto:

PROJECT_PATH = os.path.abspath(os.path.dirname(file))

Luego podemos usarla para definir el path absoluto a la carpeta con archivos de media:

MEDIA_ROOT = os.path.join(PROJECT_PATH, 'media')

nuestros templates:

TEMPLATE_DIRS = (

# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".

# Always use forward slashes, even on Windows.

# Don't forget to use absolute paths, not relative paths.

os.path.join(PROJECT_PATH, 'templates')

)

o cualquier otra variable de configuración que requiere una ruta de directorios.

Con esta solución podemos cambiar el proyecto de carpeta, disco o computadora y seguirá funcionando.


Django Middleware

Mientras estudié el tema para hacer un experimento, traduje el 80% de la documentación oficial. La dejo aquí más algunos condimentos personales.

Middleware

Es un sub framework que permite modificaciones al sistema de procesamiento de request/response de Django. Es un sistema de plugins liviano y de bajo nivel que permite alterar globalmente las entradas y salidas de Django.

Cada componente middleware es responsable de hacer alguna función específica.

Activar componentes middleware

Para hacerlo, añadirlo a la lista MIDDLEWARE_CLASSES en la configuración de Django (settings.py). En esta lista, cada componente se representa por un string: el camino completo al nombre de la clase. Por ejemplo:

MIDDLEWARE_CLASSES = (

'django.middleware.common.CommonMiddleware',

'django.contrib.sessions.middleware.SessionMiddleware',

'django.contrib.auth.middleware.AuthenticationMiddleware',

)

En el tratamiento de requests y la generación de responses, existen dos faces:

1) Fase Request (se llama a los métodos process_request() y process_view())

2) Fase Response (se llama a los métodos process_response() y process_exception())

En la primera, las clases son aplicadas desde la primera a la última, según el orden de la lista mencionada. En la segunda fase, se aplican en orden inverso; podemos pensarlo como una cebolla:

middleware

Una instalación de Django puede funcionar sin ningún middleware, pero esto no es recomendado.

Para escribir middleware propios

Cada componente es una clase Python, que no tiene que extender a ninguna clase en particular y debe definir uno o más de los siguientes métodos.

process_request(self, request)

request es un objeto HttpRequest. El método es llamada por cada request, antes de que Django decida que vista ejecutar.

Debe retornar None o un objeto HttpResponse. Si retorna None, Django seguirá procesando el request, ejecutando los otros middlewares y luego la vista apropiada. Si retorna un objeto HttpResponse, Django no hará nada más, solo retornar ese objeto.

process_view(self, request, view_func, view_args, view_kwargs)

request es un objeto HttpRequest. view_func es la función Python que Django está por usar. (Es el objeto function, no el nombre de la función en un string). view_args es un alista de argumentos posicionales que serán pasados a la vista. Y view_kwargs es un diccionario de argumentos de palagra clave que serán pasados a la vista. Ni view_args ni view_kwargs incluye al primer argumento de la vista (request).

process_view() es llamado antes de que Django ejecute la vista. Debe retornar None o un objeto HttpResponse. Si retorna None, Django seguirá procesando el request, ejecutando otros process_view() y luego la vista apropiada. Si retorna un objeto HttpResponse, Django no hará nada más, solo retornar ese objeto.

process_response(self, request, response)

request es un objeto HttpRequest. response es un objeto HttpResponse retornado por una vista de Django.

process_response() debe retornar un objeto HttpResponse. Puede altener el objeto response dado o puede crear uno nuevo.

A diferencia de proces_request() y process_view(), este siempre es ejecutado.

process_exception(self, request, exception)

request es un objeto HttpRequest. exception es un objeto Exception lanzado por la vista.

Django llama a process_exception() cuando la vista lanza una excepción. process_exception() debe retornar None o un objeto HttpResponse. Si retorna un objeto HttpResponse, la respuesta es devuelta al navegador. De lo contrario, el sistema por defecto para manejo de excepciones entra en acción.

__init__

Por lo general estas clases no tienen estado; simplemente contienen a los anteriores métodos. Se puede usar el método init pero se debe tener en cuenta que Django inicializa estas clases sin argumentos y que el método init es llamado solo una vez, cuando el servidor web arranca.

Experimento

Con lo anterior en mente, me quedaron algunas dudas al respecto:

  • si modifico los argumentos de view_func en process_view y retorno None, ¿estos son pasados modificados a la vista?
  • si lo anterior es falso, puedo lograr el mismo efecto haciendo:
    def process_view(self,request, view_func, view_args, view_kwargs):
    
        # modificar request, view_args y view_kwargs
    
        return view_func(request, view_args, view_kwargs)
    ?
  • ¿puedo definir process_response de tal forma que examine response?


URLs elegantes con Django

Este texto es parte del informe de nuestro proyecto final de carrera, lo publico en forma separada aquí por que tiene valor propio y puede convencer a más de un programador PHP de probar Django. Las URLs elegantes son la forma natural de las URL en Django.

Manejo de URLs

Tener URLs elegantes y limpias es un requisito común para aplicaciones web modernas. Django provee un mecanismo de manejo de URLs basado en expresiones regulares que asocia una expresión regular a una vista.

Para diseñar las URLs de una aplicación Django, se construye una especie de tabla que mapea patrones de URL a funciones Python a ejecutar (vistas). Con esto se logra que las URLs estén desacopladas del resto de la aplicación.

El siguiente es un ejemplo de una entrada de esa tabla:

(r'^index/$', index)

Suponiendo que la aplicación web está corriendo en el servidor con nombre localhost, cuando alguien acceda a http://localhost/index/ se ejecutará la vista ‘index’.

El siguiente es un ejemplo un poco más complejo:

(r'^cliente/datos/(\d+)/$', cliente_datos)

En el patrón de la URL se utilizan paréntesis para capturar una parte de la misma y poder accederla luego como parámetros en la vista. La expresión encerrada entre paréntesis se denomina grupo y son propias de las expresiones regulares en Python. Así, siempre que se acceda a, por ejemplo, http://localhost/cliente/datos/1/ o http://localhost/cliente/datos/100/ se ejecutará la vista cliente_datos y recibirá como parámetro el número correspondiente.

Algo similar sucede en el siguiente ejemplo:

(r'^inmueble/fotos/(\d+)/eliminar/(\d+)/$', eliminar_foto)

Con la diferencia de que ahora la vista recibe dos parámetros, en este caso particular el primero corresponde a un identificador de inmueble y el segundo a un identificador de foto.

Las expresiones regulares en Python soportan también lo que se denomina grupos nombrados. Esto permite obtener un grupo por su nombre. Si escribimos una especificación de URL como la siguiente:

(r'^inmueble/fotos/(?P<inmueble>\d+)/eliminar/(?P<foto>\d+)/$', eliminar_foto)

la vista será llamada utilizando parámetros nombrados. Esto tiene la ventaja de que si cambia el orden de los parámetros en la URL, la vista seguirá funcionando sin que se necesite redefinirla.


FPDF en Django

Si intentás usar FPDF en Django hay algunas cosas que necesitás saber:

    <li>(ya sabés qué) <a href="http://www.fpdf.org/">FPDF</a> está originalmente escrita en PHP y permite generar documentos PDF sin usar PDFLib (C).</li>
    
    <li>(ya sabés qué) hay más de un port de esta librería a Python. Todos son incompletos.</li>
    
    <li>Usá este <a href="http://www.nsis.com.ar/svn/pyfpdf/" target="_blank">http://www.nsis.com.ar/svn/pyfpdf/</a> (parcheado en Argentina para utilizar unicode).</li>
    
    <li>FPDF <a href="http://www.fpdf.org/en/FAQ.php#q7">trabaja con la codificación ISO-8859-1</a>.</li>
    
    <li>Mi código fuente Django usa la cotificación UTF-8 y en los documentos resultantes aparecían caracteres raros en lugar de vocales con tilde o eñes.</li>
    
    <li>Lo soluciné haciendo una modificacicón en el método Output:</li>
    

    self.buffer = buffer.encode('iso-8859-1')



    SQL Debug en Django

    ¿Cómo saber en Django qué sentencias SQL se están ejecutando detrás de su ORM? Según la FAQ, podemos hacerlo de esta forma:

    Make sure your Django DEBUG setting is set to True. Then, just do this:

    >>> from django.db import connection
    
    >>> connection.queries
    
    [{'sql': 'SELECT polls_polls.id,polls_polls.question,polls_polls.pub_date FROM polls_polls',
    
    'time': '0.002'}]

    connection.queries is only available if DEBUG is True. It's a list of dictionaries in order of query execution. Each dictionary has the following:

    sql -- The raw SQL statement

    time -- How long the statement took to execute, in seconds.

    connection.queries includes all SQL statements -- INSERTs, UPDATES, SELECTs, etc. Each time your app hits the database, the query will be recorded.

    Cada vez que se realiza una nueva petición, esa variable es sobre escrita con las consultas que se ejecutaron en la vista asociada. La forma de verlas es accediendo a connection.queries en cada vista de nuestro interés. Para facilitar esta tarea y no tener código intrusivo, escribí un decorador:

    
    from django.db import connection
    
    def sql_debug(f):
    
        '''
    
        Decorador útil para inspeccionar las sentencias SQL que se ejecutan en
    
        cada request.
    
        '''
    
        def inner(*args, **kwargs):
    
            r = f(*args, **kwargs)
    
            for d in connection.queries:
    
                print "time: %s\n sql:%s\n\n" % (d['time'], d['sql'])
    
            return r
    
        return inner
    
    


    Diagrama de capas de Django

    ¿Cuál sería un diagrama de capas correcto para representar una instalación de Django? Ensayé el siguiente gráfico; es para una instalación en particular, pero se puede cambiar GNU/Linux por Sistema Operativo, PostgreSQL por Base de Datos y Apache por Servidor Web para hacerlo más genérico:

    La idea detrás de un diagrama de capas es expresar que los componentes de una capa le brindan servicios a los de la capa superior mediante alguna interfaz.

    ¿Mejores ideas? ¿Conviene otro tipo de diagrama?


    Generar diagramas de clases a partir de modelos de Django

    La siguiente es la mejor forma que encontré de tomar todos los archivos models.py de las aplicaciones que componen un proyecto Django y generar un diagrama de clases completo, listo para exportar cómo imagen. Utilizo un comando incluido en django-command-extensions.

    Obtener e instalar django-extensions

    Para Django 1.0 hay que bajar la última versión del svn:

    svn checkout http://django-command-extensions.googlecode.com/svn/trunk/ django-command-extensions

    E instalarlo ejecutando el script de instalación:

    cd django-command-extensions

    sudo python setup.py install

    Podemos probar si se instaló correctamente abriendo una consola Python y ejecutando:

    >>> import django_extensions

    Para que este disponible en nuestro proyecto debemos agregar la aplicación a settings.py:

    INSTALLED_APPS = (

    ...

    'django_extensions'

    )

    Cuando ejecutemos:

    python manage.py help

    veremos una lista de los nuevos comandos disponibles.

    Generar archivo .dot

    Uno de ellos es graph_models el cual nos permitirá generar un archivo en formato .dot:

    python manage.py graph_models -a > mi_proyecto.dot

    Generar archivo .png

    O una imagen png:

    python manage.py graph_models -a -g -o mi_proyecto.png

    Para que el anterior comando funcione, necesitamos tener instalado pygraphviz. En mi Ubuntu no tenía el paquete python-pygraphviz por lo que tuve que bajarlo desde su página web. Para instalarlo requiere tener instalados los paquetes graphviz y graphviz-dev.

    Ejemplo

    El siguiente comando genera el diagrama de clases para auth, la popular aplicación que viene con Django y sirve como ejemplo de cómo generar el diagrama para solo una aplicación del proyecto:

    python manage.py graph_models auth -g -o mi_proyecto_auth.png

    Nota

    Les recomiendo bajar la versión de pygraphviz empaquetada de Cheeseshop en lugar de la del svn, ya que con esta el comando de django-extensions lanza una excepción. Correguí el programa, abrí un ticket y envié un parche. Para cuando leas esto probablemente ya se haya incorporado a trunk.