Generar arxius xls amb Python III
Escrit per Aaloy a 29 de October , 2011 a les 11:28 a.m.
En aquesta tercera entrega veurem com podem fer que des de la nostra aplicació Django es puguin generar i servir els arxius que hem generat amb la llibreria xlwt.
La mecànica és la mateixa que la que hi ha als tutorials de Django
que mostren com servir un arxiu csv o un pdf, així que per a que
serveixi d'alguna cosa més us mostraré como ho feim utilitzant les
generic class views incorporades a Django 1.3.
El que volem fer és poder mostrar els resultats per pantalla i després poder afegir un link que ens retorni les dades que tenim en pantalla en forma d'arixu descarregable.
ListView
Per el nostre propòsit farem servir la classe ListView que es troba
a django.views.generic
La utilització més típica d'aquesta classes implica sobreescriure el
mètode get_query_set de tal manera que ens retorni les dades que
mostrarem a la plantilla, i, com no, indicar-li el nom de la plantilla
a la qual s'han de mostrar els resultats.
El codi seria quelcom semblant a això per la part de views.py
class TestView(ListView):
"""Classe de proves per l'article"""
template_name='tests/user_list.html'
def get_queryset(self):
"""Listam tots els usuaris que no són superusuaris"""
return User.objects.filter(is_superuser=False)
a l'arxiu urls.py crearem la url que apunti a la nostra classe,
afegint
url(r'^test/$', TestView.as_view(), name='trespams-test'),
i important TestView des del mòdul views.py
la plantilla
{% extends "base.html" %}
{% block main %}
<table>
<a href="{% url trespams-test %}?exporta=1">Exporta</a>
{% for user in object_list %}
<tr>
<td>{{user.username}}</td><td>{{user.email}}</td>
</tr>
{% endfor %}
</table>
{% endblock %}
A la plantilla com podeu veure hi ha un object_list que no hem
definit enlloc. Això ho fa la pròpia classe, que passa tot el que ve
del get_querset al contexte de la plantilla dins una variable
anomenada object_list. Podem canviar aquest nom si no ens agrada.
<a href="{% url trespams-test %}?exporta=1">Exporta</a>
Hem definit un enllaç que apunta a la mateixa url i per tant a la matea vista, però afefint-hi un paràmetre que ens indicará que el que volem no és tornar a generar la plana sinó exportar el fitxer.
Si creau una aplicació de proves i provau el codi veureu que encara no fa res, tranquils, ara hi anam...
Canviam la renderització
El gran avantatges de les class views és que estan construïdes de
manera molt modular, de manera que podem sobreescriure tot allò que
convingui per a modificar-ne el comportament.
En el nostre cas volem que es renderitzi un contingut o un altre
segons tinguem o no un paràmetre concret a la url, i per axiò el que
farem serà sobreescriure el mètode render_to_response
class TestView(ListView):
template_name='tests/user_list.html'
def get_queryset(self):
return User.objects.filter(is_superuser=False)
def render_to_response(self, context, **response_kwargs):
if self.request.GET.get('exporta'):
return self.render_to_fitxer(context, **response_kwargs)
else:
return super(TestView, self).render_to_response(context,
**response_kwargs)
El que deim és, si hi ha el paràmetre exporta al GET llavors fas un
render_to_fitxer (que encara no hem definit), en cas contrari, fas
el que es suposava que havies de fer.
Definim el render_to_fitxer
Ja sols ens queda definir el render_to_fitxer per fer que enlloc de
l'HTML obtinguem un arxiu
class TestView(ListView):
template_name='tests/user_list.html'
def get_queryset(self):
return User.objects.filter(is_superuser=False)
def render_to_fitxer(self, context, **kwargs):
"Sortida cap a un fitxer xls"
import xlwt
from django import http
response = http.HttpResponse(
content_type='application/vnd.ms-excel; charset=utf-8',
**kwargs)
response['Content-Disposition'] = 'attachment; filename=usuaris.xls'
usuaris = self.get_queryset()
wb = xlwt.Workbook()
ws = wb.add_sheet('usuaris')
fila = 0
for usuari in usuaris:
ws.write(fila, 0, usuari.username)
ws.write(fila, 1, usuari.email)
fila += 1
wb.save(response)
return response
def render_to_response(self, context, **response_kwargs):
if self.request.GET.get('exporta'):
return self.render_to_fitxer(context, **response_kwargs)
else:
return super(TestView, self).render_to_response(context,
**response_kwargs)
Els imports que hi ha al mètode render_to_fitxer poden estar a la
capçalera del mòdul perfectament, els he posat aquí per mostrar
explícitament els mòduls que es necessiten dins el mètode.
De la resta de codi, fixau-vos com podem modificar les capçaleres de
resposta per indicar que el que volem és generar un arxiu el
content_type del qual és de tipus excel i que ho tornarem en format
utf-8.
A més al Content-Disposition hem indicat que el contingut es
servirà com a un adjunt i amb un nom específic.
Per a la generació de les dades hem reaprofitat get_querset,
tanmateix el que volem és el mateix que hi ha en pantalla. Es pot
arribar a optimitzar per posar el contingut dins una caché i evitar
que es torni a repetir la consulta, però això és una optimitzaició
prematura.
Finalment, cal notar que no es genera un arxiu temporal, sinó que la
resposta, que tenim dins la variable response és comporta ella
mateixa com si fos un fitxer. És la màgia del duck typing.
I això és tot! Fins la propera!
Traducciones/Translations by apertium
0 comentaris, 0 trackbacks (URL) , Tags: Python Django
Generar arxius xls amb Python II
Escrit per Aaloy a 20 de October , 2011 a les 9:27 p.m.
A l'article anterior havíem fet un petit "hello world" en format xls, ara toca fer alguna cosa més de profit, així que veurem com podem generar un full de càlcul a partir de dades.
En altres temps, quan donava formació ofimàtica, un dels exercicis que feia fer als alumnes quan tocava el tema de les fulles de càlcul era el de fer una taula de multiplicar, serveix per perdre la por a la cosa aquesta dels nombres i mostra els conceptes fonamentals, ara podem fer el mateix. Per no fer-ho molt complicat anem per la taula del dos
#!/usr/bin/env python
#-*- coding: UTF-8 -*-
import xlwt
fmt = xlwt.easyxf
h1 = fmt('font: name Arial, height 200, bold on;')
wbk = xlwt.Workbook()
full = wbk.add_sheet('Taula del 2')
full.write(0, 0, "Taula del 2", h1)
for x in range(1, 11):
full.write(x, 0, 2)
full.write(x, 1, "x")
full.write(x, 2, x)
full.write(x, 3, "=")
full.write(x, 4, xlwt.Formula('A%s*C%s' % (x+1, x+1)))
wbk.save('taula.xls')
Podem veure com hem creat fàcilment una fórmula per a fer la multiplicació. També podem veure com per posar les fórmules hem de tenir en compte que s'ha de fer en la notació de files i columnes pròpia de les fulles de càlcul, és a dir, les columnes s'anomenen a partir de la lletra A. Això xoca un tant amb tot el maneig de la llibreria que utilitza una notació de coordenades o la l'origen està en la cel·la superior esquerra.
Per tractar amb això la llibreria proporciona un mòdul anomenat Utils que ens proporciona les eines per fer les transformacions entre la notació de fulla de càlcul i la notació de coordenades de la llibreria.
#!/usr/bin/env python
#-*- coding: UTF-8 -*-
import xlwt
fmt = xlwt.easyxf
h1 = fmt('font: name Arial, height 200, bold on;')
wbk = xlwt.Workbook()
full = wbk.add_sheet('Taula del 2')
full.write(0, 0, "Taula del 2", h1)
for x in range(1, 11):
full.write(x, 0, 2)
full.write(x, 1, "x")
full.write(x, 2, x)
full.write(x, 3, "=")
num = xlwt.Utils.rowcol_to_cell(x, 0)
taula = xlwt.Utils.rowcol_to_cell(x, 2)
formula = xlwt.Formula("%s*%s" % (num, taula))
full.write(x, 4, formula)
wbk.save('taula.xls')
Formats
Quan tractam amb informació és important a més de presentar-la, fer-ho en un format entenidor. Els fulls de càlcul ho fan prou bé a això, i des de fa molt temps tenen la capacitat de presentar-nos les dates en formats legibles i no en el seu format intern, i les quantitats numèrics ben formatejades, per exemple.
Amb la llibreria xlwt podem utilitzar els formats de cel·la de la
fulla de càlcul, podeu consultar-los anant a les propietats d'una
cel·la o bé fer una ullada a l'exmemple num_formats.py que ve amb
la llibreria.
#!/usr/bin/env python
#-*- coding: UTF-8 -*-
import xlwt
import decimal
fmt = xlwt.easyxf
wbk = xlwt.Workbook()
full = wbk.add_sheet('Caixa')
moneda = fmt('font: name Times' ,
num_format_str=u'#,#0.#0 "€";[Red]-#,#0.#0 "€";')
full.write(0, 0, decimal.Decimal("11133"), moneda)
full.write(0, 1, -3939.93, moneda)
wbk.save('nombre.xls')
A l'exemple podem veure com xlwt tracta perfectament tant el format numèric sencer (o float) com el Decimal.
De la mateixa manera podem fer feina amb diferents formats de dates o formats de temps.
#!/usr/bin/env python
#-*- coding: UTF-8 -*-
import xlwt
import datetime
fmt = xlwt.easyxf
wbk = xlwt.Workbook()
full = wbk.add_sheet('Avui')
data = fmt(num_format_str=u'D MMM YYYY')
full.write(0, 0, datetime.date.today(), data)
wbk.save('dates.xls')
Ho deix aquí per avui, a la propera entrega veurem com passar tot això la web i fer que Django ens doni un petit informe creat amb xlwt amb les dades del model.
Traducciones/Translations by apertium
0 comentaris, 0 trackbacks (URL) , Tags: Python Django
Generar arxius xls amb Python I
Escrit per Aaloy a 17 de October , 2011 a les 9:25 p.m.
Una de les coses que volia que l'aplicació de gestió de comunitat de propietaris que fem és la de tenir algun tipus de via per a poder exportar la informació introduïda. D'aquesta manera el client no està captiu i les seves dades són realment seves, però a més també volia que aquesta informació es presentàs de manera que també pogués ser útil.
El format csv està prou bé, però és massa simple a l'hora de presentar les dades. Necessitava quelcom del format del qual fora prou potent per poder aplicar estils i format bàsic, i al mateix temps que no implicàs una despesa addicional per al client.
Cercant, cercant vaig arribar a la llibreria xlwt. Aquesta llibreria permet crear fitxers en format xls (Excel) d'una manera prou senzilla i ràpida com per a ser just el que necessitàvem. El format es pot llegir tant mitjançant aplicacions privatives, que de d'aquí no recomanaré, o des d'aplicacions lliures com l'OpenOffice o LibreOffice.
Per a qui vulgui saber-ho tot podeu fer una ullada al codi font, però si us conformau amb un poquet menys, podeu llegir-vos el tutorial. Si voleu anar per feina podeu seguir llegint i us ne faré cinc cèntims de la llibreria.
Hello world
El primer que hem de fer és instal·lar la llibreria. Així que si no heu instal·lat pip ja tardau i després no deixeu de fer el mateix amb el virtualenv i el virtualenvwrapper, ja que sempre, sempre, farem feina des d'un entorn virtual.
Així doncs:
mkvirtualenv fc
workon fc
pip install xlwt
El que farem serà crear un full de càlcul amb les paraules "Hello world" a la primera cel·la (A1)
#!/usr/bin/env python
#-*- coding: UTF-8 -*-
import xlwt
wbk = xlwt.Workbook()
full = wbk.add_sheet('full 1')
full.write(0,0,'Hello world')
wbk.save('hello.xls')
Ja està! Hem importat la llibreria, creat el llibre, afegit un full que hem anomenat "full 1" i segidament escreit a aquest full la frase mítica.
Podem afegir tans fulls com volguem sense problemes, però ens hem de fixar bé que la cel·la A1 correspon a les coordenades zero, zero del full de càlcul.
Un poc de format
Podem aplicar estils a una cel·la fent servir dos mètodes: podem utilitzar la classe XFStyle que ens permet crear l'estil dessitjat pas a pas, o bé aplicar un format molt més legible pels humans i molt més proper al que seria un full d'estil css: easyxf
En aquest article faré servir aquest darrer mètode, ja que està prou ben documentat i com us dic, és molt més legible. Així, el que faré serà fer que el nostre "Hello world" es present un poc més vistós.
#!/usr/bin/env python
#-*- coding: UTF-8 -*-
import xlwt
fmt = xlwt.easyxf
h1 = fmt('font: name Arial, height 400, bold on;')
wbk = xlwt.Workbook()
full = wbk.add_sheet('full 1')
full.row(0).height = 800
full.write(0,0,'Hello world', h1)
wbk.save('hello2.xls')
He creat un estil anomenat h1 amb font Arial i negreta. L'alçada l'he posada com a 400, és a dir 20px. Com a base preneu que un tamany de font 200 equival a una alçada de 10px.
Com que volem que la fila tengui més alçada que la font, hem
establert la propietat height de la fila a 800.
Això és tot per avui, al proper article (demàs segurament) ja un full complet, amb format numèric i alguna fórmula.
Traducciones/Translations by apertium
0 comentaris, 0 trackbacks (URL) , Tags: Python Django
Propietariosonline, la visió tècnica
Escrit per Aaloy a 08 de October , 2011 a les 11:01 a.m.
Aquesta setmana és obligat parlar del llançament de propietariosonline.com, una aplicació web per a la gestió de comunitats de propietaris que hem llançat des d'APSL. Junt amb la web de trobacasa forma el gruix dels projectes propis. Actualment estam en fase beta, és a dir, mirant de netejar tots els possibles errors que puguin haver passat els nostres testeigs inicials i sobre tot, copsant les opinions dels beta-testers, per tal d'anar incorporant els suggeriments de millora que ens facin. Personalment una de les coses que més em motiven a l'hora de fer un programa és que aquest sigui útil, i per això res millor que fer-lo en col·laboració amb els usuaris potencials del programa.
Com que supòs que no llegiu això per a que us parli de la gestió de les comunitats de propietaris, aniré entrant en matèria.
L'aplicació està desenvolupada amb Django en la seva versió 1.3. És la primera aplicació grossa en la que feim un ús intensiu de les generic class views. Com molts sabreu abans de la versió 1.3 la manera de passar del la petició (el request) cap a la sortida html era mitjançant una funció que es posava a l'arxiu views.py. Amb la versió 1.3 això també es pot fer, però hi ha la possibilitat de que aquestes funcions siguin classes.
Per una aplicació com propietariosonline això ha significat poder fer herència de classes i reutilitzar moltíssima funcionalitat. Ahir llegia un apunt on un usuari de Django es queixava que la quantitat de codi escrit utilitzant les classes era major que sols utilitzant les funcions. En casos puntuals potser veritat, però quan es pot fer ús de l'herència, la reutilització de codi i sobretot l'estructuració que tens compensa de sobres tenir que aprendre una nova manera de fer les coses.
Com es tracta d'un projecte propi, hem aprofitat per fer experiments i provar noves utilitats i llibreries. Ja se sap, els experiments a casa i amb gasosa, així que res millor que un projecte com aquest. Es prou gran com que l'experiència sigui extrapolable a altres projectes i no hi ha la pressió externa que representa un client que està pagant per hores.
Una de les llibreries que hem utilitzat és django-tables2 que ha significat passar de crear les taules de dades amb html a utilitzar codi Python per a enllaçar estructura i visualització. A l'aplicació es fan servir molts llistats tabulats i aquesta utilitat ens ha permès reutilitzar estructures de taula, definir formats de columnes i sobretot estalviar-nos una gran quantitat de codi HTML. De retruc les modificacions també són més senzilles, ja que per exemple modificar com apareixen les quantitat numèriques és tant senzill com modificar un tipus de columna que hem definit amb Python. Per exemple, per posar un check hem definit una columna com:
class BooleanColumn(Column):
def render(self, value):
valor = "on" if value else "off"
return mark_safe('<img src="%s/img/check-%s.png" />' \
% (settings.STATIC_URL, valor))
D'aquesta manera quan a un llistat es necessiti un camp booleà utilitzarem aquest tipus de columna. Si pel que sigui volem canviar com es presenta doncs no hem d'anar taula a taula a fer els canvis, sinó que podem anar directament al tipus de columna.
Una altra de les utilitat que hem fet servir es diu Sentry una utilitat que utilitza la gent de Quora per a monitoritzar els errors 500 i logs d'error. Fins ara aquests tipus d'errors els controlàvem amb els missatges d'e-mail que ens envia la pròpia aplicació de Django. El problema amb això és que no escala bé. L'altra dia de pagès ens trobàrem amb un problema on això es veu perfectament:
Un dels nostres clients està connectat amb una web amb molt tràfic que li envia peticions. Aquesta web va sofrir un atac i va començar a enviar peticions mal formades a tort i dret, i un dels afectats va ser el nostre client. Una de les màximes de Python és que les excepcions no ha de passar desapercebudes, així que personalment program de manera que si una cosa no ha de petar i peta, doncs me'n vull assabentar. Això va fer que rebéssim milers de missatges en poques hores. L'aplicació no es va veure afectada, però la bústia d'avisos feia goig! Al mateix temps un altra client va tenir una petada a l'aplicació, que també va enviar el corresponent e-mail. En condicions normals haguéssim vist l'error i l'haguéssim pogut solucionar en pocs minuts, però l'avís es va perdre entre els milers de missatges anteriors i no ens n'adonàrem fins passats un dia o dos. És veritat que es pot configurar el sistema de correu per a que distribueixi els missatges per client i per aplicació, però en condicions normals això no es tan còmode com tenir-ho tot centralitzat a un punt.
Aquí és on Sentry ens soluciona la vida. Hem configurat una aplicació amb Sentry que centralitza tots els missatges d'error. D'aquesta manera a la consola sols apareix el missatges i el nombre de vegades que s'ha produït l'error. Així encara que es produeixi una situació com la que explicava és molt més difícil que l'error passi desapercebut, ja que cada error idèntic s'agrupa dins Sentry. A més el format de visualització dels errors és fantàstic, molt semblant a com Django presenta els errors en mode depuració, la qual cosa fa que identificar el problema sigui encara més fàcil.
Propietariosonline va servir com a excusa i experiment, però a hores d'ara ja tenim el client de Sentry instal·lat al 90% de les aplicacions i la consola de Sentry com a una eina fonamental de monitorització, sols comparable en utilitat a Nagios en la monitorització de sistemes.
La resta d'utilitats que hem fet sevir ja són vells coneguts: django-nose per als tests unitaris, django-redis-cache, south, sorl-htumbnail, django-debug-toolbar, django-extensions, ipdb, robots etc. no s'han convertit en part fonamental de les nostres aplicacions.
Hores d'ara duim un mes just de dedicació al projecte. Fet i fet podem dir que hem dedicat dues persones a temps complet durant un mes. De fet és un poc menys, ja que hem fet manteniment d'altres projectes, però si sumam la feina de maquetació de la web principal i la feina de sistemes, doncs els resultat és si fa no fa aquest: 2 mesos-home de dedicació (encara que diferents perfils).
Posem-hi xifres! He utilitzat el programa cloc per comptar les línies de codi. He llevat la totalitat de codi javascript i css (i less), ja que fonamentalment hem utilitat llibreries jQuery i el bootstrap de twitter, el resultat és:
285 text files.
282 unique files.
1348 files ignored.
http://cloc.sourceforge.net v 1.53 T=1.0 s (248.0 files/s, 19589.0 lines/s)
-------------------------------------------------------------------------------
Language files blank comment code scale 3rd gen. equiv
-------------------------------------------------------------------------------
Python 147 1593 872 11995 x 4.20 = 50379.00
HTML 90 298 32 4088 x 1.90 = 7767.20
XML 11 7 0 704 x 1.90 = 1337.60
-------------------------------------------------------------------------------
SUM: 248 1898 904 16787 x 3.54 = 59483.80
-------------------------------------------------------------------------------
És interesant veure que aquesta eina (encara que agafat amb pinces) ens dóna també l'equivalent en línies de codi si haguéssim fet servir un llenguatge de programació menys potent que Python. Donat que el nombre de línies de codi que pot escriure un programador és constant, llavors això vol dir que el mateix programa d'haver-ho fet en un altra llenguatge ens hagués duit 4 vegades més de temps. Per pensar-hi!
A la part de sistemes hem aprofitat també per potenciar el Fabric per a tota la gestió i desplegament de l'aplicació, que corre amb uWSGI i ngnix. Això ens permet pujar modificacions a diari, mantenint el cicle d'entorns de desenvolupament, preproducció i producció.
Encara hi ha coses que es poden millorar (de fet sempre n'hi haurà). Quan l'economia ens ho permeti volem incorporar un servidor per a la integració contínua, però a poc a poc esperam anar arribant-hi. M'agrada pensar que hem aconseguit un procés de millora contínua, on a cada projecte aconseguim tenir les coses millor que al projecte anterior i anar incorporant el que hem après també a les aplicacions que ja hi ha en producció.
Si tot això tindrà viabilitat econòmica i ens permetrà sobreviure com a empresa el temps ho dirà. Però el que sempre he tingut el convenciment que conformar-se i no aspirar a millorar sols duu a l'empobriment espiritual i és tan perillós o més que el risc que corres intentant millorar.
Traducciones/Translations by apertium
6 comentaris, 0 trackbacks (URL) , Tags: Python Django APSL
