El Blog de Trespams

Blog personal sobre tecnologia, gestió de projectes i coses que se me passen pel cap

Generar arxius xls amb Python III

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!

blog comments powered by Disqus