Mostrando entradas con la etiqueta django. Mostrar todas las entradas
Mostrando entradas con la etiqueta django. Mostrar todas las entradas

viernes, 13 de noviembre de 2015

Django modelo de datos genérico usando abstract

Habitualmente, tenemos los mismos campos en varias tablas (modelos); Por ejemplo, la descripción, la fecha de alta, la fecha de última modificación, etc. Una forma de representar esto en los modelos es usar una clase genérica con los campos comunes y darle el atributo abstract. Las clases derivadas heredarán esos atributos.

El la sincronización, Django no creará ninguna tabla en la base de datos si está marcada como abstract.

En cuanto al nombre de la clave primaria del modelo, particularmente prefiero darle nombre en vez de dejar que Django la nombre como id.

Hay que ir con cuidado derivando modelos: No es lo mismo una tabla (o una entidad) derivada que una vista. Por ejemplo, podríamos tener una entidad llamada Persona y otras derivadas llamadas Cliente, Proveedor, Agente, Transportista, etc. Si en Django derivamos esos modelos directamente de un modelo base (Persona), en la base de datos se crearán tantas tablas nuevas como modelos derivados (Clientes, Proveedores, ...). Esto no tiene nada que ver con tener una tabla llamada Persona y diversas vistas de esa única tabla llamadas Clientes, Proveedores, etc. En Django cada modelo equivale a una tabla (entidad), no a una vista. Para crear el equivalente a una vista debemos usar un QuerySet.



```python

# -*- coding: utf-8 -*-
from django.db import models

LENGHT_DESC=150 # longitud de las descripciones
H_DESC = 'Entre descripción'

def cPk():
    # devuelve clave primaria
    return models.AutoField(primary_key=True)

def cFum():
    # devuelve fecha ultima modificación
    return models.DateTimeField(auto_now=True, null=False, blank=False)

def cDesc(ml=LENGHT_DESC, df=''):
    # devuelve descripción
    return models.CharField(max_length=LENGHT_DESC, help_text=H_DESC, null=False, blank=False, default= df)

def cFechaAlta():
    # fecha de alta del registro
    return models.DateTimeField(auto_now_add=True, null=False, blank=False)


class ModBase(models.Model):
    #
    # modelo base con descripcion, fecha de alta y fecha última modificación
    #
    descipcion=cDesc()
    fecha_alta=cFechaAlta()
    fum=cFum()
   
    class Meta:
        abstract=True


# esta tabla tendrá todos los campos de ModBase más los propios
class Personas(ModBase):
    # el nombre de la clave primaria es persona
    persona=cPk()


```    


También podríamos usar la el modelo base para hacer un logger de los cambios en la base de datos, apuntando el nombre de la tabla, operación, usuario, etc.

jueves, 12 de noviembre de 2015

Virtualenv python3 django

sudo apt-get install python-virtualenv
cd ~
virtualenv -p python3 env_django
source ~/env_django/bin/activate
pip install Django==1.8.6


Para MacOS este tutorial incluye información de cómo configurar pyvenv.

brew install python3
python3 --version
pip install --upgrade pip
pip install virtualenv
pip install virtualenvwrapper
mkdir ~/.virtualenvs
vi ~/.bashrc
--> Añadir:
export WORKON_HOME=~/.virtualenvs
source /usr/local/bin/virtualenvwrapper.sh
--<
source .bashrc
--> buscar directorio de python3 con
which python3
mkvirtualenv --python=python3_path env-django
--> ya tenemos el entorno con python3 activado
deactivate
workon env-django
pip install Django==1.9



martes, 25 de marzo de 2014

Django llamar a procedimientos almacenados Oracle con parámetros de salida

En el momento de escribir esto (1.6 de django) y después de darle varias vueltas, he llegado a la conclusión de que no es factible acceder al contenido de los parámetros de retorno sin usar directamente cx_Oracle, lo que en un momento dado puede repercutir en la portabilidad de la aplicación.

Teniendo esto claro, se trata de diseñar una estrategia que nos permita usar toda la potencia de los procedimientos almacenados con el mínimo coste en caso de cambiar de gestor de base de datos.

He intentado alejarme del estándar lo menos posible pero, como he comentado, no veo forma de librarse del uso directo de cx_Oracle. Esto es debido a las particularidades que presenta cada gestor a la hora de reservar espacio para las variables de salida en función del tipo. Por ello, lo que he hecho es escribir una función para cada tipo de parámetro de salida, por ejemplo,


from django.db import connection
import cx_Oracle

def call_proc_str1(cadena, valors=None):

   """ llama a un procedimiento Oracle cuyo ultimo parámetro es un string de salida """    cur = connection.cursor()

    # preparam cadena per a Oracle
    cadena="BEGIN %s; END;;" % (cadena,)
    # preparam el parametre de sortida
    sortida=cur.cursor.var(cx_Oracle.STRING)
    # afeixim paràmetre com a darrer
    valors.append(sortida)
    try:
        cur.execute(cadena, valors)
        # retornam paràmetre de sortida
        return sortida.getvalue()
    except Exception, e:
        mensaje="%s Error: call_proc cadena#%s valors#%s" % (e, cadena, valors)
        logger.error(mensaje)
        raise Exception(mensaje)
    finally:
        cur.close()

Se observan varios detalles:
  1. Hay que formar la cadena de la llamada como cadena="BEGIN %s; END;;" % (cadena,). Es debido a la forma cómo se hace la llamada via execute.
  2. Se crea y se reserva espacio para un string mediante cur.cursor.var(cx_Oracle.STRING) y se añade al final de los parámetros de conexión.
  3. Extraemos el valor del parámetro mediante sortida.getvalue()

Para un procedimiento como este:

create or replace procedure prueba(
  param1 in varchar2,
  param2 in varchar2,
  param3 out varchar2) as .....


La llamada sería

try:

    plsql="prueba(%s, %s, %s)"
    param3=call_proc_str1(plsql, [param1, param2])
    return jOk(param3)

except Exception, ex:
    return jErr(str(ex))

Notar que usamos el placeholder %s de python-db en vez de el de Oracle.

En resumen; No es la mejor solución del mundo pero es fácil de portar si cambiamos de gestor de base de datos.

viernes, 6 de diciembre de 2013

Django hacer POST desde javascript con contenido binario

Si queremos hacer un POST con contenido binario como una imagen desde el navegador contra Django, tenemos que tener en cuenta que la llamada a ajax  codifica el stream en UTF-8.

Para desactivar este funcionamiento y hacer el envío "raw" tenemos que poner el responseType como 'blob', de esta forma:

    $("#bt_envia").on("click", function (e) {

        var x = new XMLHttpRequest();
        x.onload = function() {
            // Create a form
            var fd = new FormData();
            fd.append("upfile", x.response); // x.response is a Blob object
            fd.append("csrfmiddlewaretoken", "{{ csrf_token }}");

            // Upload to your server
            var y = new XMLHttpRequest();
            y.onload = function() {
                alert('Fichero subido!!');
            };
            y.open('POST', '/gestion/prova/');
            y.send(fd);
        };
        x.responseType = 'blob';   
        x.open('GET', 'http://planetary.s3.amazonaws.com/assets/images/spacecraft/2013/20131108_2013-3896_f537.jpg', true);
        x.send();

En la parte del servidor, los FILES nos vendrán directamente en el formato nativo, así que sólo tenemos que tratarlos directamente, por ejemplo grabando una imagen en un fichero.

def prova(request):
    #print str(request.body)
    # veure https://docs.djangoproject.com/en/dev/topics/http/file-uploads/
    print "Els files son ", str(request.FILES)
    f = request.FILES['upfile']    
    with open('c:/prova.jpg', 'wb+') as destination:
        for chunk in f.chunks():
            destination.write(chunk)

    return HttpResponse('Mu guay')


Fuente: varios en stackoverflow

viernes, 30 de agosto de 2013

Django desde un template llamar a un procedimiento del primer objeto de un query

Puede usarse el with con esta sintaxis:

<img {% with im.inmuebleimagen_set.all|first as imagen %}
src="{{imagen.get_url}}"{%endwith%}
/>

martes, 27 de agosto de 2013

Django cambiar de lenguaje desde el view

Para cambiar el LANG en el servidor, asumiendo que tenemos todo el sistema de traducción correctamente configurado, llamamos via POST a la ficha setlang y le pasamos el campo language con la lengua seleccionada:

En settings definimos los lenguajes con los que trabajamos:

LANGUAGES = (
    ('es', ugettext(u'Español')),
    ('de', ugettext(u'German')),
    ('en', ugettext(u'English')),
);


En urls, incluimos las url de i18n:

(r'^i18n/', include('django.conf.urls.i18n')),


Y ya en el template podemos llamar a la ficha /i18n/setlang para setear el lenguaje:

<li class="gbt">
    <form name="setLangSpanish" action="/i18n/setlang/" method="POST">{% csrf_token %}
        <input name="next" type="hidden" value="/" />
        <input type="hidden" name="language" value="es" />
        <a href="#" class="language_off" onclick="document.setLangSpanish.submit();return false;">
            <span class="language_off sprachwahl">Español</span></a>
    </form>
</li>
<li class="gbt">
    <form name="setLangEnglish" action="/i18n/setlang/" method="POST">{% csrf_token %}
        <input name="next" type="hidden" value="/" />
        <input type="hidden" name="language" value="en" />
        <a href="#" class="language_off" onclick="document.setLangEnglish.submit();return false;">
            <span class="language_off sprachwahl">English</span></a>
    </form>
</li>
<li class="gbt">
    <form name="setLangDeusch" action="/i18n/setlang/" method="POST">{% csrf_token %}
        <input name="next" type="hidden" value="/" />
        <input type="hidden" name="language" value="de" />
        <a href="#" class="language_off" onclick="document.setLangDeusch.submit();return false;">
            <span class="language_off sprachwahl">Deusch</span></a>
    </form>                
</li>


Fuente: oscarcp http://blog.oscarcp.com/?p=163

martes, 23 de julio de 2013

Django filtro de query usando un string

Es común que queramos pasar directamente un string al filtro de una consulta, por ejemplo, desde una url. Dado que el parámetro de filter es un diccionario, lo podemos hacer mediante el operador de desempaquetado de diccionarios **.

Recordemos que para desempaquetar una lista usamos el operador * y para desempaquetar un diccionario el **:

Ejemplo de *


>>> pepe=(1,10,)
>>> range(pepe)
Traceback (most recent call last):
File "", line 1, in
TypeError: range() integer end argument expected, got tuple.
>>> range(*pepe)
[1, 2, 3, 4, 5, 6, 7, 8, 9]

Ejemplo de **



>>> pepe={ "venus" : "blanco", "tierra": "azul", "marte": "rojo" }
>>> '{tierra}'.format(pepe)
Traceback (most recent call last):
File "", line 1, in
KeyError: 'tierra'
>>> '{tierra}'.format(**pepe)
'azul'
>>>


En ambos ejemplos observamos que al usar * o ** pasamos con el tipo correcto. Pero ¿Cómo aprovecharlo para pasar directamente un filtro a la consulta?. Yo lo hago de la siguiente forma:


# un string
filtro="nacionalidad__exact@ESP"
# lo parseamos en clave-valor
f=filtro.split("@")
# pasamos como diccionario
p=Persona.objects.filter(**{f[0]:f[1]})
# el efecto es el mismo que hacer
p=Persona.objects.filter(nacionalidad__exact='ESP')

Y ahora veamos cómo implementarlo.

En la página web:

$('a.pais').click(function() {
   var pais=$(this).attr("pais");
   $('#{{entidad}}').load("?filtro="+escape('nacionalidad__exact@'+pais)+"&type=ajax");
});


Notemos que en la url pasamos,
  1. Un interrogante: Hará un get sobre la página actual.
  2. Un nombre de parámetro (filtro). Si queremos añadir más parámetros los separamos con &
  3. El valor del parametro poniendo un igual después del nombre, por ejemplo,  filtro=nacionalidad__exact@ESP 
En la parte del servidor lo que hacemos es procesar directamente el filtro pasando de string a diccionario así:


def get_filtro( self, request):
  """ aplica el filtro y devuelve el modelo filtrado """
   filtro=request.GET.get('filtro', False)
   if filtro:
  try:
     f=filtro.split('@')
     return Persona.objects.filter(**{f[0]:f[1]})
   except Exception, e:
    logger.error('desempaquetando filtro '+str(e))

   return Persona.objects.all()


La ventaja de este método es que es muy sencillo crear toda clase de filtros directamente en la página web teniendo un solo código que los gestiona en el servidor. 

Una última puntualización: Si no todos los usuarios pueden ver todo el modelo i.e. ver todas las personas Persona.objects.all() hay que ir con cuidado ya que es muy sencillo manipular a mano la url para que nos devuelva cualquier registro. Por ejemplo:

?filtro=moroso__exact@1

Nos devolvería todos los morosos ;-)



lunes, 15 de julio de 2013

Django error "name field too short" generando permisos en el admin si verbose_name está presente

El verbose_name de un modelo sirve para dar una descripción extendida de él. Si está presente, es usada por el admin para describir el modelo, si no, se usa el nombre de la clase. Por ejemplo:

class Cliente(models.Model):
  ...
  ...
   verbose_name=u"Esta descripción saldrá en el admin, es un poco larga para mostrar el error"
   verbose_name_plural=u"Esta es la descripción que se usa en plural"


Para poder administrar el modelo, lo incluimos y registramos en admin.py:

class ClienteAdmin(admin.ModelAdmin):  
    list_display=('nombre','codigo',)
    ....
admin.site.register(Cliente, ClienteAdmin)  


En el primer runserver que hagamos, el admin intentará registrar el modelo y crear los permisos de add, change y delete. El problema es que la descripción del permiso es del estilo de "Can add "+verbose_name y se almacena en el campo name de la tabla auth_permission que está definido con 80 caracteres de largo. Por lo tanto, la creación de permisos fallará si se supera esa longuitud y nos encontraremos con el modelo registrado en el admin pero sin poderle asignar permisos.

Registré el bug y resulta que lo han cerrado como duplicado  de otro de hace cinco años!!!. Claro que es fácil quejarse y no currar en el proyecto....

De momento, para resolverlo basta con editar a mano la longitud del campo:

ALTER TABLE auth_permission MODIFY name VARCHAR(500);


miércoles, 10 de julio de 2013

Django name field on auth_permission too short

If the verbose_name of the table is over 80 characters the syncdb command will silently fail.
The name field on auth_permissions table is too short to store if verbose_name are that long, so it doesn't update and you cannot assign permissions.
The obvious workaround is to manually change the field length on the table.
I have opened the ticket 20728

Thanks Victor!

sábado, 11 de mayo de 2013

Instalar Django en Windows

Una de las formas es esta:
  1. Bajar e instalar python (2.7 en este momento)
  2. Incluir C:\Python27;C:\Python27\Scripts en el path
  3. Copiar este script de instalación de distribute en un fichero (distribute_setup.py) y ejecutarlo python distribute_setup.py
  4. Bajar ultima versión de pip y copiar a directorio; instalar usando python setup.py install
  5. Instalar Django con pip install Django==1.5 (1.5 es la última versión en este momento)
  6. Probar python -c "import django" (no debe dar ningún error)
  7. Crear la última killer app ;-)



miércoles, 17 de octubre de 2012

Campos calculados en django

Las operaciones sobre los objetos model de django se mapean a SQL mediante el ORM (Object-relational mapping). Un problema que tiene el ORM de django es que no tiene soporte directo para los campos calculados, que son aquellos que se obtienen a partir de los valores de los campos del registro para cada registro.

Por ejemplo, tenemos unos movimientos en los que interviene cantidad, precio y comisión. ¿Qué sentido tiene guardar en la BD el campo importe si en realidad ya tenemos toda la información para calcularlo?.

class Movimiento(models.Model):
   cantidad=models.DecimalField()
   precio=models.DecimalField()
   comision=models.DecimalField()

¿Cómo calculamos entonces el importe?. Hay dos alternativas: Una usando una property de python en el propio objeto model y otra usando extra en el queryset. Veamos:

class Movimiento(models.Model):
   cantidad=models.DecimalField()
   precio=models.DecimalField()
   comision=models.DecimalField()

   def _get_importe(self):
      return self.cantidad*self.cambio*(1-self.comision)
   importe = property(_get_importe)

Y ahí tenemos el importe del movimiento via movimiento.importe

La otra solución es usar extra en el queryset para añadir "a mano" el campo en la consulta SQL, sería:

target=movimiento.objects.extra(select={
          'importe': 'cantidad*cambio*(1-comision)',})
for f in target:
    print f.importe

Entonces, ¿Cuál es el problema?. Pues que todo esto son soluciones "de mentirijilla" puesto que realmente el campo importe no existe como tal en el gestor de BD y no podemos hacer cosas como sumar todos los importes haciendo una agregación, así esto

total_importe=modelo.aggregate(Sum('importe'))

Nos genera un error diciendo que el campo importe no existe.

Sin duda, el soporte para campos calculados es uno de las mejoras del ORM que puede trabajarse.


Fuentes:
http://stackoverflow.com/questions/3690343/django-orm-equivalent-for-this-sql-calculated-field-derived-from-related-table

lunes, 8 de octubre de 2012

Localizar las plantillas

Si queremos que los importes monetarios de nuestras plantillas nos salgan (en España) así:

1.256,56 €

en vez de así:

1256.56 €

tenemos que localizar la plantilla y dejar que django haga el trabajo duro.

Para empezar, en settings.py indicamos que queremos usar la localización añadiendo:

DEFAULT_CHARSET='utf-8'
THOUSAND_SEPARATOR= '.'
DECIMAL_SEPARATOR = ','
NUMBER_GROUPING = 3
USE_THOUSAND_SEPARATOR = True
FIRST_DAY_OF_WEEK = 1
LANGUAGE_CODE = 'es-es'
USE_L10N = True


En la plantilla, cargamos l10n y activamos la localización así:

{% load l10n %}
{% localize on %}

blah, blah, blah...

{% endlocalize %}


Y de forma mágica los importes aparecerán correctamente formateados. Para controlar la cantidad de decimales diferentes del estándar también podemos usar el filtro floatformat:precision así:

{{ importe|floatformat:6 }}


Error de codificación de caracteres al generar pdf con django y pisa

Hacía tiempo que me perseguía un pequeño problema al generar pdf desde django/pisa con caracteres utf8>255. Por defecto, pisa usa latin-1/ISO 8859-1 (un byte) para generar los pdf y al transcodificar los caracteres de la template (p.e. el símbolo euro €) me saltaban errores.

En la doc oficial de pisa tenemos que:


pdf = pisa.pisaDocument(StringIO.StringIO(html.encode("UTF-8")), result)


Pero buscando en stackoverflow he encontrado que pisaDocument acepta además el parámetro encoding con el que en realidad le indicamos la codificación que debe usar con lo que queda:

pdf = pisa.pisaDocument(StringIO.StringIO(html.encode("UTF-8")), result, encoding='UTF-8')  

;-)

jueves, 4 de octubre de 2012

Filtrar entre fechas con django

Siempre se me olvida la sintaxis de range que selecciona entre dos valores inclusives:


self.modelo.objects.filter(fecha__range=(desde, hasta))

jueves, 2 de agosto de 2012

Aún otro ejemplo de try, except, else, finally / Yet another try, except, else, finally example

Estaba pasando datos de una DB mysql a otra en django y me ha salido este "snippet" que ejemplifica el uso de try-except-else-finally.

try contiene el código que queremos proteger
except captura la excepción (usar la clase genérica exception para capturarlas todas)
else código que se ejecutará si no salta ninguna excepción
finally código que se ejecutará en todo caso tanto si hay como si no hay excepción

Las únicas obligatorias son try y except. Si queremos capturar más de una excepción, las ponemos una detrás de otra:

from django.core.exceptions import ObjectDoesNotExist
try:
    cl=Cliente.objects.get(clave=id)
except ObjectDoesNotExist, ex:
    print "el cliente no existe"
except Exception, ex:
    print "se ha producido un error", str(ex)

Ejemplo:


try:  
   try:  
     c=db_marcajes.cursor(MySQLdb.cursors.DictCursor)  
     c.execute("select id, fecha, event, door, user from logs where fecha>%s", (last_marcaje,))  
     rows=c.fetchall()     
   except Exception, e:  
     print "Impossible efectuar consulta"  
     exit(1)  
   else:  
     elapsed = (time.clock() - start)  
     print "Consulta realitzada en", elapsed, "msegs"  
     num_rows=len(rows)  
     print "Numero de filas ", num_rows  
     i=0    
     while i<num_rows:  
       row=rows[i]     
       print "procesando fila ", repr(row)  
       try:  
         em=Empleado.objects.get(pin=row['user'])  
         pt=Puerta.objects.get(pk=row['door'])  
         print ("guardando acceso %s de %s" % (row['user'],em.descripcion,))  
       except Exception, ex:  
         print ("Error de busqueda empleado %s puerta %s: %s", ( row['user'], row['door'], ex, ))  
       else:  
         try:  
           acc=Acceso(  
             empleado=em,  
             es=row['event'],  
             fecha=row['fecha'],  
             puerta=pt,  
             )  
           #acc.save()  
           print "Guardado"  
         except Exception, ex:  
           print ("Error grabando acceso: %s" % (ex,))  
       finally:  
         i+=1  
         last_marcaje=row['fecha']  
   finally:  
     c.close()           
 finally:  
   # guardam darrer marcatje  
   conv=Convenio.objects.get(pk=1)  
   conv.last_marcaje=last_marcaje  
   conv.save()  

viernes, 27 de julio de 2012

Prevenir el borrado de objetos via clave foránea / Prevent object deletion via foreign key

Una de los comportamientos por defecto más problemáticos de django es el de emular el DELETE CASCADE  cuando borramos una clave foránea. Desde luego, hay que ser animalito. Ya me veo a decenas de usuarios noveles preguntándose adonde han ido a parar sus datos después de efectuar un borrado de una clave foránea que tenía objetos relacionados.

La forma de "proteger" los objetos relacionados es via la definición del ForeignKey.on_delete que puede tomar los siguientes valores

CASCADE: Borrado en cascada, comportamiento por defecto.
PROTECT: Protege al objeto relacionado del borrado haciendo saltar la excepción django.db.models.ProtectedError.
SET_NULL: Asigna el valor null a la clave, sólo posible si null es True.
SET_DEFAULT: Pone la clave foránea a su valor por defecto (debe estar definido).
SET(): Asigna a la clave foránea el valor pasado en el set.
DO_NOTHING: No hace nada y deja el comportamiento por defecto de la db. (Si la hemos creado con syncdb, no hará nada)


Una práctica aconsejable en un entorno de usuario sería capturar la excepción via PROTECT y devolver un mensaje de error con la información para el usuario.Por ejemplo:

Si en la definición del modelo Factura usamos

cliente=models.ForeignKey(Cliente, on_delete=models.PROTECT)

Y ahora queremos borrar un cliente que tiene una factura asociada

try:
  cliente.delete()
except django.db.models.ProtectedError, ex:
  return HttpResponse( ('El cliente %s tiene al menos una factura y no puede borrarse.' % (cliente.descripcion,)), 'text/html')

miércoles, 29 de febrero de 2012

Menor de edad en python / Legal age in python

Hoy día 29/02/2012 (29 de febrero de año bisiesto), me he encontrado con que el algoritmo que usaba para el cálculo de minoría de edad me ha fallado. Después de varias indagaciones he dejado este como bueno:

from datetime import date, datetime, timedelta

def menor_edad(nascut):
  """ accepts a string date in iso format Y-m-d and returns True if
        it is in spanish legal age today """
  t=timedelta(seconds=31556926*18)
  fa_divuit=date.today()-t
  fecha=datetime.strptime(nascut, '%Y-%m-%d').date()
  return (fecha > =fa_divuit)


viernes, 24 de febrero de 2012

Filtrado fácil de clave foránea en una ficha de django

Es sabido que django mapea las claves foráneas a un ModelChoiceField en ModelForm, pero ¿qué ocurre cuando sólo queremos seleccionar un subconjunto de ellas?.

Por ejemplo, supongamos que estamos dando de alta un empleado y a la hora de seleccionar su categoría sólo queremos que aparezcan las que están asociadas a su sección.

Tenemos la ficha:


class AddForm(forms.ModelForm):
  class Meta:
   model=Empleado
    fields=('clave', 'descripcion', 'categoria', 'jefe_seccion', 'email',)


La propiedad queryset del campo categoría estará definida por defecto como Categoria.objects.all(), así que hacemos:

p=AddForm()
p.fields['categoria'].queryset=Categoria.objects.filter(seccion=self.seccion)

Con lo que en la ficha sólo nos saldrán las categorías de la sección actual.
Otra forma más complicada consiste en pasarle el parámetro al constructor de la clase pero hay que hacerlo con los kwargs por que si no tendremos problemas en cuando queramos usar la ficha para editar.

class AddForm(forms.ModelForm):
  categoria=forms.ModelChoiceField( label=u'Categoría', queryset = Categoria.objects.none())
  def __init__(self, *args, **kwargs):
   sec = kwargs.pop('sec', None)
   super(AddForm, self).__init__(*args, **kwargs)
   if sec:
    self.fields['categoria'].queryset = Categoria.objects.filter(seccion = sec)
  class Meta:
   model=Empleado
   fields=('clave', 'descripcion', 'categoria', 'jefe_seccion', 'email',)

martes, 7 de febrero de 2012

Configurar logging en python y django con yaml

Os paso el esquema de logging que utilizo en mis aplicaciones por si os puede servir de ayuda. Lo he ido montando a partir de diversas fuentes y experiencias.

Un punto importante es dejar el fichero de configuración del logging fuera del programa para independizar mejor el sistema de logging de la aplicación.

Este puede ser un ejemplo del fichero de configuración en yaml (logger.conf):


version: 1
formatters:
  brief:
    format: '%(name)s - %(levelname)s - %(message)s'
  simple:
    format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
  detailed:
    format: '%(asctime)s %(module)-17s line:%(lineno)-4d %(levelname)-8s %(message)s'
  email:
    format: 'Timestamp: %(asctime)s\nModule: %(module)s\n Line: %(lineno)d\nMessage: %(message)s'
handlers:
  console:
    class: logging.StreamHandler
    level: DEBUG
    formatter: brief
    stream: ext://sys.stdout
  file:
    class: logging.handlers.RotatingFileHandler
    level: INFO
    formatter: detailed
    filename: app.log
    mode: a
    maxBytes: 10485760
    backupCount: 5
  smtp:
    class: logging.handlers.SMTPHandler
    level: ERROR
    formatter: email
    mailhost: smtp.xxxxx.es
    fromaddr: yyy@xxxx.es
    toaddrs:  yyyy@xxxx.es
    subject:  Error en aplicacion XX
root:
  level: DEBUG
  handlers: [console]

Se observan los formateadores de los mensajes y los handlers para la cónsola, fichero y correo. Por defecto el logging sale por cónsola, si queremos que los errores salgan por email handlers de root sería [console, smtp], para fichero que rotara cada maxBytes [file], etc... Realmente cómodo.

Para cargar la configuración, primero parseamos el yaml (PyYAML) y luego lo enviamos a dictConfig, que realiza la configuración así:


import yaml
import logging


#nos aseguramos de que dictConfig esté presente (python 2.7), sino, lo cargamos de la libreria de django 



try: 
    from logging.config import dictConfig 
except ImportError: 
    from django.utils.dictconfig import dictConfig

# Cargamos la configuracion del fichero, la parseamos y la enviamos a dictConf
try:
    logging.config.dictConfig(yaml.load(file('logger.conf','r')))
except Exception, e:
    print "Error configurating logger ", str(e)

Ahora ya podemos el usar el sistema. En general, es conveniente nombrar cada logger con el nombre de su módulo para que cuando haya muchos mensajes sepamos cuál los está enviando, para ello podemos usar el valor de __name__, así:

logger=logging.getLogger(__name__) # __name__ contiene el nombre del modulo
logger.debug("blah blah")

Si queremos que cada clase tenga su logger privado, tenemos que tomar una pequeña precaución para evitar un error si otros usan una librería nuestra con otro sistema de handles,

Declaramos una clase "nula":

class Nullhandler(logging.Handler):
   def emit(self, record):
      pass

y ahora, en la clase hacemos

class Clase():
   def __init__(self):
      self.__logger=logging.getLogger(__name__)
      self.__logger.addHandler(NullHandler())
      self.__logger.debug("clase iniciada")

Como veis es un tema un poco denso pero que una vez bien configurado nos será imprescindible para trazar correctamente las aplicaciones. Espero que os haya ayudado.

lunes, 5 de diciembre de 2011

Shell en django

Ocurre muchas veces que necesitamos una shell en django para realizar pruebas o tareas de mantenimiento. Conseguirla es muy sencillo ya que settings.py se cargará desde el primer momento en que importemos un modelo o cualquier módulo de django.  Lo único que tenemos que hacer es exportar la variable DJANGO_SETTINGS_MODULE con el valor del fichero de settings que queremos utilizar. Yo uso este script (suponiendo que nuestros settings están en settings.py):

shell.sh
export DJANGO_SETTINGS_MODULE="settings"
python


desde aquí ya podemos operar con los modelos:

from gesion.models import Cliente
c=Cliente(clave='444', descripcion='Pepe Perez')
c.save()


Si queremos acceder desde un script independiente en python podemos usar setup_environ de esta forma:

mantenimiento.py
from django.core.management import setup_environ

try:
  import settings
except ImportError:
  import sys
  sys.stderr.write("No encuentro el fichero de settings")
  sys.exit(1)

setup_environ(settings)

....


Espero que os sirva.