El Blog de Trespams

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

Django, imatges i Imagekit

Django Imagekit és una llibreria creada per Justin Driscoll que ens permet crear miniatures i/o distints tamanys d'imatges a partir de la imatge original, i que s'integra molt bé amb Django. És una llibreria més senzilla que la de django-photologue ja que no té tota la funcionalitat per a crear gal·leries fotogràfiques.

El tutorial per a fer-la anar està força bé, però aprofitaré la benentesa per a fer cinc cèntims de com podem pujar una imatge al nostre site amb Django i presentar-la de nou.

El codi font de l'exemple complet és a appfusedjango.

El model

Per començar definim el model:

from imagekit.models import ImageModel
from django.db import models
from imagekit.specs import ImageSpec


class Photo(ImageModel):
image = models.ImageField(upload_to='photos')
comments = models.TextField()
num_views = models.PositiveIntegerField(editable=False, default=0)


class IKOptions:
spec_module = 'sample.specs'
cache_dir = 'photos/cache'
image_field = 'image'
save_count_as = 'num_views'

def thumb(self):
if self.image:
return '<img src="%s">' % self.thumbnail.url
else:
return ""

thumb.allow_tags = True
thumb.short_description = 'Foto'

En aquest cas es tracta d'un model molt senzill, guardam la imatge image al directori photos i posarem dins la base de dades tant la referència del fitxer (això és important, dins la base de dades no és guarda la imatges sinó sols la metadada) i el comentari.

El camp num_views ens pots servir per anar guardar la quantitat de vegades que s'ha vist la imatge i és utilitzat si volem per la llibreria d'Imagekit.

La part interessant és a la classe IKOptions. Aquesta defineix quin tractament se li donarà a la imatge, on s'enmagatzemaran les miniatures i a quin camp es fa referència.

  • spec_module És el mòdul d'ImageKit que es farà servir i que defineix els tamanys possibles. D'aquí una estona el veurem amb detall. En el nostre cas el modul es diu specs.
  • cache_dir : guardarem els distints tamanys generats a photos/cache
  • image_field: Les metadaes son pel camp image del model.

El mètode thumb ens serviex per poder posar la miniatura dins el llistat de l'admin.

Els tamanys

Per definir els tamanys Imagekit distingeix entre el que és la visualització de la imatge i les manipulacions que s'hi fan. La imatge original necessita passar per uns filtres que Imagekit anomena processors. Una imatge pot generar-se aplicant un o més d'aquests filtres.

from imagekit.specs import ImageSpec
from imagekit import processors


# define the thumnail processor
class ResizeThumb(processors.Resize):
width = 100
height = 75
crop = True


class ResizeDisplay(processors.Resize):
width = 600


class ResizeBig(processors.Resize):
width = 800
# define your spec


class Thumbnail(ImageSpec):
pre_cache = True
processors = [ResizeThumb, ]


class Display(ImageSpec):
processors = [ResizeDisplay, ]


class Big(ImageSpec):
processors = [ResizeBig, ]

A l'exemple sols faig servir un tipus de processor el de Resize per a redimensonar la imatge als tamanys que farem servir.

Pujam una imatge

Per pujar una imatge necessitam definir un formulari, el mètode que tractarà aquest formulari i les urls que farem servir, així com les plantilles que es mostraran.

Les urls

Aquesta és la part senzilla.

from django.conf.urls.defaults import *
from django.conf import settings
from django.contrib import admin

admin.autodiscover()
urlpatterns = patterns('', url(r'^$', 'sample.views.index', name="main-page"),
url(r'^display/(?P\d+)/$', 'sample.views.display', name="display-image"), ....

Definim una url per l'index que identificarem per nom main-page i que serà tractada al mètode index del mòdul views del nostre paquet sample.

I una altra ulr que ens permetrà visualitzar la imatge a partir del seu identificador. Anomenam a aquesta url display-image, original que és un...

El formulari

# -*- coding: UTF-8 -*-
from django import forms
from PIL import Image


class AttachmentForm(forms.Form):
"""Form for the attachment sample. Added a simple validation to accept only png files checking for 'image/png'
in the content_type of the file"""
image = forms.FileField(help_text="add a png or jpg file")
comments = forms.CharField(widget=forms.Textarea, help_text="describe the image")

def clean(self):
"Validate the entire form"
cleaned = self.cleaned_data
try:
file = cleaned['image']
except Exception, e:
raise forms.ValidationError("Not valid file: %s" % e)
if not file.content_type.lower() in ["image/jpeg", "image/png", "image/jpg"]:
raise forms.ValidationError("Just jpg or png files please")
im = Image.open(file)
if not im.format in ['JPEG', 'PNG']:
raise forms.ValidationError("Just jpg or png files please")
return cleaned

El formulari com es pot veure és d'allò més normalet, definim un camp per la imatge i un camp per als comentaris.

La part "nova" està en la validació. No ens podem fiar del que ens diu la gent que puja, així que le que farem és comprovar que el mime type es correspon amb un format vàlid, i com que fins i tot això es pot manipular, farem una comprovació addicional amb PIL per a comprovar que la imatge és el que diu ser. Aquesta comprovació dependrà del vostre nivell de paranoia.

Tractant la imatge

from models import Photo
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response
from forms import AttachmentForm


def index(request):
"Obtains the attachment and saves it to the disk"
if request.method == 'POST':
form = AttachmentForm(request.POST, request.FILES)
if form.is_valid():
f = form.cleaned_data['image']
foto = Photo()
foto.image.save(f.name, f)
foto.comments = form.cleaned_data['comments']
foto.save()
return HttpResponseRedirect('/')
else:
form = AttachmentForm()
fotos = Photo.objects.all()
return render_to_response('index.html', {'form': form, 'fotos': fotos})

Per a tractar arxius i imatges la part delicada és recordar que hem de passar la informació al formulari afegint el request.FILES i pensar a posar al formulari enctype="multipart/form-data"

A més d'això hem de guardar dues vegades: una per la imatge, que guardarà el contingut dins el sistema de fitxers, i una altra per la resta del model i les metadades de la imatge. Per això tenim un foto.image.save(f.nam, f) guarda la imatge al sistema de fitxers.

Mostrar una imatge és prou senzill

def display(request, id):
foto = Photo.objects.get(pk=id)
return render_to_response('image.html', {'foto': foto})

Mostrant les imatges

Al template hi passam objectes del tipus Photo, que recordem tenen tota la fontaneria del ImageKit.

Això vol dir que a més de la imatge original, puc fer servir els formats que he definit a specs: Thumbnail, Display, Big.

Per utilitzar-los en la nostra plana sols hi hem de fer referència:

<li>
<img src="{{foto.thumbnail.url}}"/>
</li>
<li>
<img src="{{foto.display.url}}"/>
</li>
<li>
<img src="{{foto.big.url}}"/>
</li>

com podem veure sols és cosa de fer referència al tamny definit i treure'n el que ens interessa, en el nostre cas la url per a mostrar la imatge.

Per darrera ImageKit se n'ha encarregat de fer les transformacions i guardar la imatge a la caché, de manera que la feina pesada de generació dels distints tamnays sols se fa un cop.

A partir d'aquí ens podem comlicar tant com voguem, per exemple:

  • Al mètode clean fer que no es puguin pujar imatges de més d'un tamany.
  • Reescriure el mètode save del model per a que no es guardi la imatge original sinó una altra imatges ja reduïda.
  • Escriure més processors per fer més manipulacions a les imatges.
  • etc. etc.

Però el és segur és que amb això que us he contat tingueu el 90% dels casos solucionats.

Nota: Oscar, esper que això et servesqui ;)

blog comments powered by Disqus