El Blog de Trespams

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

Imatges de la web al model Django

L'altra dia estava fent una aplicació web part de la qual consisteix en aprofitar els continguts de la web anterior, continguts als que no tenim accés directe.

La part de text va ser senzilla, però obtenir una imatge de la web i associar-la a un model Django va resultar una mica més interessant del que suposava. Tant que vaig pensar que potser convenia posar-ho en forma d'apunt.

Per posar-ho en context vaig començar a fer una aplicació Django, la qual tenia que obtenir la imatge i guardar-la, però ja que hi era, vaig voler fer quelcom més educatiu, així que l'aplicació es va anant transformant amb codi per a descarregar-se les fotografies associades a la plana principal de meneame. Si provau el codi mirau de canviar la url que ja que amablement Ricardo em va dir que no hi havia problema en fer algunes proves, tampoc és cosa que anem putejant la web.

Així doncs, aquest apunt serà un poc mescladissa d'utilitats, d' screen scrapping, de thread, processos i altres herbes. Miraré d'anar a poc a poc, però ja us aviso que hi ha de tot i molt!

Definim el model

El més habitual quan hom tracta amb imatges és tenir-ne que fer algun tipus de conversió, si més no per presentar-les a l'administrador de Django. És tan habitual que quan he de fer servir un camp ImageField de Django ja ho converteixo directament a un camp ImageField d'una llibreria que va ideal per a la manipulació d'imagtes sorl.thumbnail.

Així doncs, el nostre model serà molt senzill, tindrà un identificador per poder distingir les notícies, el titular, el texte de la notícia i com no la imatge.

M'ha queda talment així:

# !/usr/bin/env python # -*- coding: UTF-8 -*-
from django.db import models
from sorl.thumbnail import ImageField
from sorl.thumbnail import get_thumbnail


class NoticiasPortada(models.Model):
"""Base de datos de noticias"""

def get_upload_path(instance, filename):
return "fotos/%s" % filename

identificador = models.IntegerField()
titular = models.CharField(max_length=200)
texto = models.TextField()
thumbnail = ImageField(upload_to=get_upload_path, blank=True, null=True)

def img(self):
if self.thumbnail:
try:
im = get_thumbnail(self.thumbnail, '50x50', crop='center', quality=80)
return ' ' % im.url
except IOError:
return "error"
else:
return "%s" % self.identificador

img.allow_tags = True
img.short_description = 'img'

def __unicode__(self):
return self.titular

Com es pot veure Sorl incorpora una sèrie d'utilitats per a la conversió d'imatges que ens van molt bé. Així podem definir el mètode img dins el model per tal de poder-ne fer referència a l'administrador de d'Django.

Fixem-nos també com li podem passar un mètode per tal de poder calcular a quin lloc es deixarà la imatge. Aquest mètode ha de tenir la signatura nomfuncio(instància, nom) on instància és la instància de l'objecte al qual s'associarà la imatge i el nom és el nom de la imatge en sí. Això és molt útil per poder deixar les imatges a diferents carpetes segons convingui.

A l'admin.py podem fer

from django.contrib import admin
from models import NoticiasPortada


class NoticiasPortadaAdmin(admin.ModelAdmin):
list_display = ('img', 'identificador', 'titular')
search_fields = ('titular', 'texto')
admin.site.register(NoticiasPortada, NoticiasPortadaAdmin)

Sorl té opcions per a que al formulari se'ns presenti també la imatge, però per ara ho deixarem així.

La càrrega de les imatges

La càrrega de les imatges la podríem haver fet de moltes maners, però com que ja vaig posar un article de com fer comandes de Django, doncs ho aprofitaré. Crearem una comanda anomenada importar que es podrà cridar com

python manage.py importar

Per això s'ha de crear un paquet Python dins l'aplicació amb l'estructura

app models.py management __init__.py commands __init__.py importar.py

Si feis servir django-extensions recordau que hi ha una comanda anomenada create_command que crea directament aquesta estructura.

Per a importar les imatges utilitzarem la classe InMemoryUpladedFile que es troba a django.core.files.uploadedfile. Podem passar una instància d'aquesta classe al nostre model dins l'atribut thumbnail i Django se n'encarregarà de la resta. La complexitat addicional està en que ens hem de davallar la imatge de la web.

def download_photo(self, img_url, filename, field_name):
img = cStringIO.StringIO()
image_on_web = urllib.urlopen(img_url)
while True:
buf = image_on_web.read(65536)
if len(buf) == 0:
break
img.write(buf)
image_on_web.close()
return InMemoryUploadedFile(file=img, field_name=field_name, name=filename, content_type="image/jpeg",
size=img.tell(), charset=None)

Per davallar-nos la imatge farem servir la llibreria urllib. Passant-li una url es capaç de llegir-la i donar-nos el contingut. Com que la memòria de moment no és un requeriment, el que farem serà deixar el contingut dins un buffer que es comporta a tots els efectes com un fitxer. Feis una ullada al duck typing per saber com és això possible.

InMemoryUploadedFile espera un fitxer (d'aquí el truc de fer servir StringIO), el nom del fitxer, el content_type, el tamany i el charset, així com el nom del camp.

De fet, però ho podem fer una mica més senzill, ja que no necessitam el nom del camp. Django té una altra classe que fa el cid més senzill, és el SimpleUploadedFile de manera que el codi queda un poc més net:

def download_photo_simple(self, img_url, filename):
img = cStringIO.StringIO()
image_on_web = urllib.urlopen(img_url)
while True:
buf = image_on_web.read(65536)
if len(buf) == 0:
break
img.write(buf)
image_on_web.close()
return SimpleUploadedFile(content=img.getvalue(), name=filename, content_type="image/jpeg")

Però encara així és molta línea per a tan poca cosa. Em feia ganes provar una llibreria que promet simplificar el procés d'accés als recursos web. La llibreria requests. Teniu en compte que el codi amb urllib encara es complicaria més en una aplicació real, ja que convé controlar les excepcions que hi pot haver. El tema està força ben explicat a l'apunt urllib2 - The Missing Manual, així que no m'estendré més.

L'avantatge de requests és que ens permet abstreure'ns d'aquests excepcions i tractar únicament amb codis d'estat web. Així el que ens interessarà és obtenir la imatge si el codi és 200 (tot bé) o retornar un None si hi ha problemes. Així el codi es simplifica encara més.

def download_more_simple(self, img_url, filename):
r = requests.get(img_url)
if r.status_code == 200:
return SimpleUploadedFile(content=r.content, name=filename, content_type=r.headers.get('content-type'))
else:
return None

Amb això, crear una instància de NoticiasPortada pot quedar com

noticia = NoticiasPortada(identificador=id, texto=texto, titular=titular,
thumbnail=self.download_more_simple(src, 'thumb-%s.jpg' % id))
noticia.save()

on id és l'identificador de la notícia i src representa la url de la imatge.

Com ho treim a això? Doncs és una bona pregunta. A la segona part de l'article us ho explico. Fins demà!

blog comments powered by Disqus