I no passa res
Escrit per Aaloy a 24 de May , 2009 a les 7:16 p.m.
En la literatura de gestió de projectes anglosaxona es parla d'un efecte teamicide, és a dir, d'un efecte aniquilador de projectes per referir-se a situacions que per la seva naturalesa més tard o més prest repercuteixen en el rendiment i la cohesió de l'equip de treball, poen-lo dur a la seva destrucció.
Avui parlaré d'una d'aquestes situacions, l'anomenada "no passa res" o en la seva variant de "tanmateix tot seguirà igual".
Supòs que més d'un podrà donar exemples d'aquesta situació i de la poca gràcia que fa. Us pos un grapat d'exemples de situacions:
-
L'equip ha fet una recomanació que no ha estat escoltada i arrel d'això s'ha de tornar a refer part de la feina. No hi ha conseqüències de cap casta per la persona que no va escoltar les recomanacions i sí més feina per l'equip.
-
S'ha posat al davant d'un projecte algú de provada ineptitud. Tothom sap que el projecte acabarà tard o malament o les dues coses. El projecte acaba com se suposava, malament, i s'assigna al responsable inepte a un nou projecte i a un altra persona a recollir els plats trencats.
-
S'ha fet una reunió de varies hores per decidir que en aquella reunió no es decidiria res. S'ha perdut el temps de tots els assistents, però no passa res.
-
S'ha fet una reunió per determinar un pla d'acció. Tothom hi està d'acord, però a les poques hores algú ja es surt de l'acordat. Es torna a la situació de desgavell anterior.
-
Un membre de l'equip es queixa de mala manera al cap de la feina d'un altre company. No passa res.
L'efecte destructor del "no passa res" ve donat per mor de l'efecte de pèrdua de control que es té damunt la feina. La gent arriba a la conclusió de que faci el que faci tot seguirà igual i que la seva feina o no feina no pot canviar les coses. Per tant s'acaba en gent que fa hores a la feina, però que aquestes no són productives.
Eliminar l'efecte "no passa res" és una tasca de comunicació. Sovint l'interessat donarà molta importància a temes que no la tenen i això se li ha d'explicar, altres vegades el tema sí que té importància, i les actuacions que se'n derivin (tot i que poden ser duites dins la discreció) s'han de fer públiques, potser sense assenyalar culpables però sempre fent saber que les coses es prenen seriosament.
L'opció de no fer res sols du a la desconfiança i a la deixadesa. El problemes grans de demà són els problemes petits que no hem resolt avui.
Traducciones/Translations by apertium
1 comentari, 0 trackbacks (URL) , Tags: Gestió de projectes
Django, imatges i Imagekit
Escrit per Aaloy a 23 de May , 2009 a les 4:46 p.m.
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | from django.db import models 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 diuspecs.cache_dir: guardarem els distints tamanys generats aphotos/cacheimage_field: Les metadaes son pel campimagedel 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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.
1 2 3 4 5 6 7 8 9 | 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<id>\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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | # -*- 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: # perhaps this is not a file 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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
1 2 3 | 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:
1 2 3 | <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 ;)
Traducciones/Translations by apertium
3 comentaris, 0 trackbacks (URL) , Tags: Python Django
Django vs PHP frameworks
Escrit per Aaloy a 10 de May , 2009 a les 6:36 p.m.
Llegint llegint he anat a parar a una plana que compara els principals bastiments (frameworks) PHP entre sí per demostrar com n'és de ràpid el KumbiaPHP comparat amb els altres.
Com que el codi utilitzat per les proves està disponible, doncs he vist que era un simple hello world, així que he creat un projecte hello_world a appfusedjango per poder comparar amb Django.
Disclaimer: La velocitat d'execució no ho és tot. Segur segur, que fet en x ben optimitzat l'aplicació y és més ràpida per aquesta prova. Això no és per veure qui la té més llarga, sols intent comparar coses més o manco semblants per saber on estam. Disclaimer 2: Qui en sap d'aquest tipus de proves és la gent que es dedica més a sistemes, en Bernat o en Guillem, per exemple :)
Amb què he fet les proves?
- La màquina es un PPC 64 de 2 MHz amb 1 Gb de RAM utilitzat Ubuntu 9.1 i Gnome com a Desktop. És a dir, no he fet servir un servidor i hi ha moltes coses executant-se.
- Python 2.5.2 (r252:60911, Jul 31 2008, 17:33:15) [GCC 4.2.3 (Ubuntu 4.2.3-2ubuntu7)] on linux2
- Com que em feia peresa muntar i configurar l'Apache he fet servir un servidor fet amb Django, el CherryPy, concretament el mòdul WSGI que podeu trobar al Django-Cerise sense executar-ho amb mode daemon per tenir major facilitat de modificar la quantitat de threads.
- La versió de Django és la trunk
- Per a les proves finals he optimitzat l'aplicació llevant tot el codi de depuració i middlewares que no es feien servir com el del la compressió gzip.
L'aplicació
L'aplicació no té cachés (tot a dummy i sense per-site-cache) i he fet que la plana en generar-se passi per la vista per a que es faci tot el recorregut MVT.
Els resultats:
executam ab -c 10 -t 60 http://localhost:8088/ cada vegada:
- Aplicació amb codi extra, servidor amb 10 threads 277 req/s
- Aplicació amb codi extra, servidor amb 3 threads 285 req/s
- Aplicació amb codi extra, servidor amb 5 threads 285 req/s
- Aplicació optimitzada, servidor amb 5 threads 334 req/s
- Aplicació optimitzada, servidor amb 3 threads 331 req/s
KumbiaPHP, el més ràpid de la comparativa PHP treu 34 req/s, casualitat? He fet alguna cosa malament? Potser, però no sóc el primer, hi ha gent que també ha notat un fort augment del rendiment en passar de PHP a Django.
I una de les proves:
ab -c 10 -t 60 http://localhost:8088/
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking localhost (be patient)
Completed 5000 requests
Completed 10000 requests
Completed 15000 requests
Completed 20000 requests
Finished 20009 requests
Server Software: CherryPy/3.0.3
Server Hostname: localhost
Server Port: 8088
Document Path: /
Document Length: 266 bytes
Concurrency Level: 10
Time taken for tests: 60.003 seconds
Complete requests: 20009
Failed requests: 0
Write errors: 0
Total transferred: 7723474 bytes
HTML transferred: 5322394 bytes
Requests per second: 333.47 [#/sec] (mean)
Time per request: 29.988 [ms] (mean)
Time per request: 2.999 [ms] (mean, across all concurrent requests)
Transfer rate: 125.70 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 2 82.1 0 3001
Processing: 4 27 41.7 24 2295
Waiting: 0 25 41.5 21 2291
Total: 5 30 92.1 24 3038
Percentage of the requests served within a certain time (ms)
50% 24
66% 28
75% 30
80% 32
90% 37
95% 43
98% 50
99% 59
100% 3038 (longest request)
(development)aaloy@G5:/tmp/djangocerise-master/src$ ab -c 10 -t 60 http://localhost:8088/
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking localhost (be patient)
Completed 5000 requests
Completed 10000 requests
Completed 15000 requests
Finished 19863 requests
Server Software: CherryPy/3.0.3
Server Hostname: localhost
Server Port: 8088
Document Path: /
Document Length: 266 bytes
Concurrency Level: 10
Time taken for tests: 60.006 seconds
Complete requests: 19863
Failed requests: 0
Write errors: 0
Total transferred: 7667358 bytes
HTML transferred: 5283558 bytes
Requests per second: 331.02 [#/sec] (mean)
Time per request: 30.210 [ms] (mean)
Time per request: 3.021 [ms] (mean, across all concurrent requests)
Transfer rate: 124.78 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.1 0 3
Processing: 12 30 9.0 29 361
Waiting: 10 28 8.7 27 361
Total: 12 30 9.1 29 362
Percentage of the requests served within a certain time (ms)
50% 29
66% 31
75% 33
80% 34
90% 37
95% 40
98% 44
99% 47
100% 362 (longest request)
Traducciones/Translations by apertium
6 comentaris, 0 trackbacks (URL) , Tags: Python Django
nose, per testejadors amb mala memòria
Escrit per Aaloy a 10 de May , 2009 a les 10:42 a.m.
Supòs que ja ningú dubta de la importància dels test unitaris a l'hora de programar. Els tests ens permeten provar que el que feim és correcte i repetir-ho tantes vegades com volguem i de manera controlada.
Els tests són una part important dels mecanismes de refactorització d'aplicacions, ja que ens asseguren que l'aplicació funciona de la mateixa manera abans i després de refactoritzar.
Els test, però, tenen un problema, fins ara els test unitaris s'han d'escriure d'una determinada manera, recordar les llibreries que has d'importar, com fer un testsuite. Per mi això significa anar a la documentació del pyUnit cada vegada o copiar un test anterior. És el que té dedicar-se a gestionar projectes, que no pots tenir al cap coses que sols fas servir de tant en tant, ja que sols dediques una quantitat mínima d'hores a programar.
Davant aquesta necessitat de fer tests sense tenir que preocupar-nos de la fontaneria intrínseca del pyUnit una de les millors opcions que hi ha és la llibreria nose. Vull dir, si sé que nose és perfecte per la feina (acudit fàcil).
Per fer un test típic basta recordar el següent:
- la sintaxis dels
assertde Python:assert condicio, missatge. - que gairebé qualsevol funció o classes que contengui
testoTestseparat amb quió baix és una funció testejable. - Que un test falla quan l'
assertfalla - Que fent un
nosetest -v nomexecutarem els tests en mode verbose del paquet o funció que li indiquem
La documentació és molt més àmplia, indica quan capturar la sortida, com fer l'equivalent al setup i al teardown dels pyUnit, capturar excepcions, etc. Però el 80% de vegades aquestes quatre coses que indic seran més que suficients.
La llibreria nose ens permet escriure els tests de la manera que estam tots acostumats quan escrivim codi per provar una funció, en nivell de formalització i cerimònia comparat amb els unittests clàssics és mínima, i per tant afavoreix que poguem convertir el codi de proves informal a codi per a la realització de test unitaris amb tant sols reanomenar la classe o funció que hem escrit, i moltes vegades ni tan sols això, ja que, no sé vosaltres, però jo ja tenia la mania de anomenar test a les funcions de prova :)
Traducciones/Translations by apertium
0 comentaris, 0 trackbacks (URL) , Tags: Python
Custom management commands
Escrit per Aaloy a 09 de May , 2009 a les 1:03 a.m.
Si heu fet al manco el tutorial de Django haureu fet servir per exemple el típic
python manage.py runserver
per a iniciar el servidor. A partir de la versió 1.0 de Django podem fer servir una sintaxi semblant per crear comandaments lligats a la nostra aplicació i que s'executin també d'aquesta manera.
Podem crear comandaments per fer qualsevol cosa, de fet es que acaba fent és executar un script de Python. Així doncs, ens podríem demanar quina avantatja hi ha en fer-ho així, vàries:
- No necessitam definir cap variable d'entorn per dir quin settings hem de fer servir, l'script manage.py ja se n'encarrega.
- El comandament quedarà lligat a l'aplicació. Per tant podem crear comandaments que depenguin d'una aplicació concreta i que la complementin.
- Podem accedir a l'ajuda del comandament si l'hem definida. Permet a l'usuari saber com ha de fer servir la comanda.
Podem crear tres tipus de comandament a mida, que trobam definits dins django.core.management.base
-
Comandaments que hereten de
AppCommandi que poden prendre com a paràmetre una llista d'aplicacions Django. -
Comandaments que hereten de
LabelCommandque poden prendre com a argument qualsevol cadena de text. -
Comandaments que hereten de
NoArgsCommandi que no prenen paràmetres.
Si anau a la documentació oficial de Django veureu que el que hi ha és més aviat poc. Potser perquè quan ja saps com és fa pareix tan fàcil que potser no mereix l'esforç. Tot i això, crec que és una opció prou útil i que hauria de tenir més importància a la documentació.
Afortunadament hi ha al manco dos articles prou bons que ens ajuden amb exemples a treure partit d'aquesta funcionalitat. Si anam al codi font comprovarem que tot està força documentat, però la realitat és que encara que a partir de la versió 1 fer aquests tipus de coses s'ha convertit en trivial, en versions anteriors no ho era.
Aquest apunt té per objectius fer publicitat d'aquesta funcionalitat, podeu trobar més informació al codi font de Django i als següents apunts:
i exemples tant al codi font de Django com a django-extensions .
Fes-hi una ullada, després de tot, fer un hello world d'aquesta manera és prou senzill:
1 2 3 4 5 6 | from django.core.management.base import NoArgsCommand class Command(NoAgrsCommand): help ="prints hello world" def handle_app(self, app, **options): print "Hello world" |
Per provar-ho creau un paquet python anomenat management dins la vostra aplicació Django, i dins aquest paquet un altre anomenat commands. Dins aquest hi posaríem els nostres scripts. Per exemple si la nostra aplicació es diu uep i volem crear un comandament que es cridi com python manage.py hello hauríem de crear una estructura com:
uep/
__init__.py
models.py
management/
__init__.py
commands/
___init__.py
hello.py
Els noms dels comandament poden col·lisionar, així que o bé tenim noms prou especials o bé convé prefixar-los amb el nom de la nostra aplicació per tal de tenir una oportunitat més.
Traducciones/Translations by apertium
0 comentaris, 0 trackbacks (URL) , Tags: Python Django
