Ca commence à faire un moment que j’ai rien écris ici !!!

C’est pas pour ce soir, mais je prépare doucement un petit article sur un trio prometteur pour mettre en place un serveur web !

Cherokee le dernier né des serveurs web libre, impressionnant sur le papier, une interface d’admin magnifique avec des wizard qui font oublier les fichiers de config d’apache, lighttpd et nginx, et d’après les benchmark publié sur leur site, simplement le plus rapide des serveurs web disponible actuellement !

Cherokee : http://www.cherokee-project.com/
uWSGI : http://projects.unbit.it/uwsgi/

RRRHWWWFFRRRR!!!§§§§§§!!
Pardon j’ai du passer 3 ou 4 soirée pour trouver une solution à mon problème !

Pour commencer par le commencement, je développe en ce moment une application facebook dont je n’en dirais pas plus pour le moment ;)

Dans cette appli, j’affiche plusieurs pages avec des onglets, rien de spécial.
Je développe cette appli avec Django et le module pyfacebook

Aucun soucis pour faire des forumulaires complexes en utilisant les tags FBML.

Mais pour une raison qui m’est toujours inconnue, l’une de mes page, la plus simple de toute, qui ne fait qu’afficher les TOS (term of service), les conditions d’utilisation en français, ne veut pas s’afficher correctement.

Le résultat de la page est tronqué ! Un peu aléatoirement et provoque un affichage bugé…

Pour comprendre le reste de l’article il faut expliquer comment fonctionne le FBML. Et schéma vaut mieux qu’un long discourt, voilà à quoi ressemble le scénario d’une requète sur une page Facebook:

Envoi d’une requète :
[utilisateur]  ——->> [Facebook]

Transmission de la demande au serveur de l’application:
[Facebook] ——->> [serveur d'appli]

Traitement par le serveur de l’application puis envois du résultat au serveur Facebook:[Facebook] <<——- [serveur d'appli]

Traitement par Facebook puis envois du résultat à l’utilisateur:
[utilisateur] << ——- [Facebook]

Premier test, je vérifie si ce que me renvoi Facebook est bien complet, et non …

Je suis en mode debug sur l’appli et je vois que ce qui est transmis entre le serveur d’appli et Facebook est incomplet. Peut être que Facebook arrête de lire le code en tombant sur des erreurs.

Deuxième test, vérification de ce qui est renvoyé par l’appli et faisant directement la requète sans passer par Facebook

[utilisateur] <<——>> [serveur d'appli]

Et non dommage l’affichage et le code html sont correcte.

Troisième test, vérification des tags FBML, il est possible que Facebook arrête de lire ma page en tombant sur des erreurs.

J’utilise la console de test FBML en y copiant ce que mon serveur d’appli transmet à Facebook, je trouve une ou deux petites erreurs, les corrige, et .. la même chose ! Donc ça ne vient pas de là…

Après des heures de tests de modification des templates, de recherche Google dans tout les sens, impossible de trouver quelqu’un avec le même soucis !!

Rien sur une hypothétique limite sur la longueur du code non plus, de toute façon mes autres pages qui fonctionnent correctement n’ont pas de problème.

Je trouve juste un post sur un forum au sujet de problème avec l’encodage des caractères écris en chinois, mais ma page est faite en UTF-8, en anglais, sans caractère particulier ..

Je fini tout de même par trouver, au hazard des 1000 tests fait, une solution de contournement pour cette page de !§%§* !!!!!!!

Le tag FBML <fb:ref> !

Ce tag permet de charger du FBML depuis un cache sur le serveur Facebook.

Il y a deux façons de s’en servir, par une url ou une variable. d’une façon comme de l’autre il faut lancer un appel à l’API Facebook pour remplir ce cache avec le contenu FBML.

Je choisis la méthode avec la variable pour avoir moins de modifications à faire le jour où l’appli passera en production.

Pour mettre à jour le cache j’ai écris un petit script qui me permettra par la suite d’alléger les transferts Facebook<>serveur d’appli en mettant d’autres pages en cache :

from facebook import Facebook
from django.conf import settings

CACHEFILE = [
    ('TOS','/home/www/fbproject/***/templates/cache/tos.fbml'),
]

fb = Facebook(settings.FACEBOOK_API_KEY, settings.FACEBOOK_SECRET_KEY)
print settings.FACEBOOK_API_KEY
fb.auth.createToken()

fb.login()

for handle, cache_file in CACHEFILE:
    print "refreshing : %s" % handle
    FILE = open(cache_file,"r")
    fb.fbml.setRefHandle(handle, FILE.read())
    FILE.close()
    print "done"

Je bouge ma page tos.fbml dans le répertoire cache et à la place je crais une page avec juste :

<fb:ref handle=”TOS”>

Et là moment de vérité .. ça fonctionne, pourquoi ça ne fonctionnait pas avant ? Aucune idée !

Au moins j’aurais appris à faire du caching avec Facebook :)

En apprenant les bases de la lib JQuery j’ai commencé par quelque chose de simple, afficher / cacher des éléments d’une page.

Pour ça il faut sélectionner ce que l’on veut cacher et utiliser par exemple la fonction toggle().

Un exemple avec une liste :

$("ul").toggle()

J’ai besoin d’un événement pour déclencher cette fonction, un clique sur une image avec la class “.toggle” :

$(".toggle").click(function(){
  $("ul").toggle();
});

Il faut attendre que la page soit complètement chargé pour créer des événements, on place cette fonction dans un $(document).ready()

$(document).ready(function(){
  $(".toggle").click(function(){
    $("ul").toggle();
  });
});

Ensuite j’ai vu les fonctions $_ajax(), $_get(), $_post() … et comment modifier sa page avec le résultat avec append(), prepend(), before(), etc..

Un autre exemple, ici lorsqu’on clique sur une image de class “load_ajax”, un script serveur est appelé et le contenu de la liste est remplacé par son résultat.

$(".load_ajax").click(function(){
  var id = $("#id").val();
  $.get(
    'ajax.php',
    { id: id },
    function(result){
      $("ul").replaceWith(result);
    }
  );
)}

Maintenant si dans la fonction appelé sur le serveur je charge du html et que je souhaite y ajouter d’autres événements du même genre je me retrouve face à un problème, les événements créé dans mon $(“document”).ready() ne sont pas rebind automatiquement sur le nouveau contenu.

Il faut redéfinir les binds dans le callback de la fonction ajax.

J’ai commencé par utiliser la nouvelle fonctionnalité live() de JQuery 1.3 qui va automatiquement appliquer les binds sur le nouveau contenu.

C’est très pratique mais j’ai appris que ça ralentie fortement la page sur la plupart des navigateurs dès qu’on l’utilise un peu trop…

Donc il faut réécrire réutiliser la fonction dans le callback.
Pour ça je modifie ma fonction :

$(".toggle").click(function(){ $(".list").toggle() });

devient

$(".toggle").bind("click",toggle_list);

Je met mon toggle_list() en dehors du $(“document”).ready() :

function toggle_list(){ $("ul").toggle() });

et dans mon callback je rebind:

$("load_ajax").click(function(){
  var id = $("#id").val();
  $.get(
    'ajax.php',
    { id: id },
    function(result){
      $("ul").replaceWith(result);
      $(".toggle").bind("click",toggle_list);
    }
  );
)}

Comme j’ai beaucoup de fonctions de ce genre, je les rassemble dans une fonction pour faire tout d’un coup histoire de ne pas toutes les rebind à chaque fois :

function bind_events(){
$(".toggle").bind("click",toggle_list);
}

Au final mon code est le suivant:

$(document).ready(function(){
  bind_events()
});

function load_ajax_event(event){
  var id = $("#id").val();
  $.get(
    'ajax.php',
    { id: id },
    function(result){
      $("ul").replaceWith(result);
      bind_events();
    }
  );
)}

function toggle_list_event(event){
  $(".toggle_list").click(function(){
    $("ul").toggle();
  });
}

function bind_events(){
  $(".toggle_list").bind("click",toggle_list_event);
  $(".load_ajax").bind("click",load_ajax_event);
}

Ce billet est une traduction simplifié de Best Practices for Speeding  Up Your Web Site du YAHOO! Developer Network, un article remplis de conseils bien pensés pour accélérer l’affichage d’un site Internet et diminuer l’utilisation de la bande passante utilisé sur le serveur.

J’y ai appris beaucoup de petites astuces et je tenais à vous les faire partager !


Minimiser le nombre de requêtes HTTP

Le navigateur passe du temps à charger chaque éléments d’une page, demander un fichier, établir la connexion, le télécharger.., il est primordial que le premier accès à une page, avant que le navigateur mette en cache les fichiers, se fasse rapidement.

Sprites CSS :

Combiner les images de fond en un fichier et utiliser les propriétés CSS background-image et background-position.

Image maps :

Pour les images alignés, comme une barre de navigation, n’utiliser qu’un fichier et définir les liens avec des coordonnées.

Inline image :

Intégrer le contenu d’une image dans le code html, mais tout les navigateurs ne le gèrent pas.

Utiliser un réseau de distribution de contenu

80 à 90% du temps passé pendant le chargement d’une page sert à télécharger des images, c’est aussi ce qui charge le plus le serveur en bande passante.

Un Content Delivery Network (CDN) en anglais, sers a délocaliser tout les fichiers static sur un réseau de serveurs spécialisé, redirigeant automatiquement vers le serveur le plus rapide pour l’utilisateur.

Les plus connus sont Akamai, Mirror Image ou Limelight, mais ils ont un coût, il existe aussi le réseau Coral DN gratuit.

Ajouter des en-têtes Expires et Control-Cache

Les navigateurs et les proxies utilisent ces en-têtes pour mettre en cache les fichiers et ne plus les demander au serveur.

Il est possible d’utiliser un cache à durée indéterminé, ExpiresDefault "access plus 10 years" pour Apache, mais il faut alors faire attention lors d’une mise à jour à ne pas utiliser le même nom !

Control-Cache permet de sécuriser le contenu pour un site demandant une authentification.

Gzip

Utiliser la compréssion gzip au niveau du serveur réduit globalement le traffic de 70%, 90% du traffic sur internet l’utilise !

Pour Apache 2 il faut utiliser le module mod_deflate.
Le serveur utilise ou non la compression en fonction du type de fichier, elle sera utilisée pour les fichiers texte html, xml, json, et ne le sera pas sur les images jpg ou les fichiers pdf pour ne pas utiliser le processeur inutilement.

Mettre les feuilles de style en haut de page

Les feuilles de style CSS doivent être chargé dans la section HEAD de la page HTML, sur certains navigateurs, comme Internet Explorer, la page ne commence pas à s’afficher avant d’avoir reçus la feuille de style.

La positionner en début de page permet un affichage progressif.

Mettre les scripts en fin de page

Les spécification HTTP/1.1 conseillent aux navigateurs de ne pas télécharger plus de deux fichiers simultanément par utilisateur.

Pendant que le navigateur télécharge le fichier de scripts il ne chargera pas d’autre fichier, en placant les scripts en fin de page on accélère le chargement de la page.

Ne pas utiliser les expressions CSS

Une expression css est un code javascript inclus dans un css comme dans cet exemple:

background-color: expression( (new Date()).getHours()%2 ? "#B8D4FF" : "#F08A00" );

Le problème est que l’expression est recalculé sur n’importe quel événement tel qu’un défilement de la page ou pire un mouvement de la souris.

Utiliser des JavaScript et CSS externe

En incluant dans une page les javascript et les css ils sont renvoyé au navigateur à chaque chargement.

En les plaçant dans des fichiers externe le navigateur les met en cache et n’aura plus à les recharger.

Réduire le nombre de requêtes DNS

Une requête DNS prend entre 20 et 120ms en moyenne, mettre ses fichiers sur plusieurs domaine favorise les téléchargements simultanés mais en utiliser trop augment le nombre de requêtes DNS, il est conseillé d’en utiliser deux.

Compresser les fichiers JavaScript et CSS

Ces fichiers contiennent en général de nombreux caractères inutiles qui peuvent être supprimé.

Il existe des outils permettant d’automatiser cette tâche comme JSMin et YUI Compressor qui gère aussi les css.

Eviter les redirections

Une redirection est une réponse du serveur indiquant au navigateur d’aller sur une autre page, qui coûte du temps.

Peut de développeur savent que lorsque l’on souhaite aller sur le site http://example.com/blog l’utilisateur est redirigé vers http://example.com/blog/.

La première chose à faire est d’utiliser des liens correcte, il est aussi possible de configurer le serveur Apache avec mod_rewrite et Alias pour que l’adresse sans slash fonctionne aussi.

Celà peut aussi être fait sur le serveur DNS en ajoutant un enregistrement CNAME redirigeant vers la bonne adresse.

Configurer les ETags

Entity Tags (ETags) est une en-tête HTTP permettant de vérifier que le cache du navigateur correspond avec le cache du serveur grâce à un identifiant unique pour chaque fichier.

Si on utilise un cluster de serveurs web, l’identifiant sera différent sur chaque serveur et le cache ne fonctionnera plus comme il faut.

Il faut alors soit configurer les ETags correctement, soit ne pas s’en servir du tout et utiliser l’en-tête Last-Modified.

Rendre les requêtes Ajax cachable

Les sites utilisant des fonctions Ajax permettent de ne pas recharger la page à chaque action, mais ces requêtes sont nombreuses et il faut faire attention à ce que les en-têtes HTTP pour l’utilisation du cache soient bien utilisé.

Il en va de même pour le Gzip, la compression textuelle, etc ..

Vider le buffer tôt

La plupart des langages utilisé pour le web permettent de vider la buffer de la page vers le navigateur avant la fin du calcul de la page.

En PHP c’est la commande flush().

En utilisant cette fonction juste après la section HEADER de la page html le navigateur va pouvoir charger les fichiers externe alors que la page n’est pas encore complètement créé.

Utiliser GET avec Ajax

Une requête POST se fait en deux temps: en-têtes, puis les données, alors qu’une requête GET se fait en une fois. La réactivité est alors amélioré.

Charger des images après la page

Sur une page web il est fort probable que des images ne soient pas visible dès l’ouverture de la page, placé plus bas. Il est possible de retarder leur chargement à l’aide de JavaScript et d’outils tel que YUI ImageLoader

Pré-charger les images

A l’opposé de ce qui a été écris ci-dessus mais pour un autre but, il est possible de charger des images qui ne sont pas affiché du tout sur la page, mais le seront sur la suivante, pour que le navigateur les mettent déjà en cache.

Cette technique peut aussi être utile avant un changement du design d’un site web, on pré-cache les images pour que l’utilisateur n’ait pas l’impression que le nouveau design est plus que l’ancien déjà caché.

Réduire le nombre d’éléments DOM

Une page complexe c’est plus de données à charger et des javascripts plus long à trouver les éléments d’une page.

Si la page contient trop d’éléments DOM peut être que le code HTML est mal pensé ou que les css ne sont pas bien utilisé.

Le framework css blueprint permet de placer ses éléments sur une grille pour simplifier la structure de la page.

Eviter les erreurs 404

Les pages introuvable ne servent à rien :)

Il faut surtout l’éviter sur les appels de scripts, le navigateur tente d’interpréter la page d’erreur comme si c’était un javascript ce qui  fait perdre des ressources et du temps.

Mettre les fichiers statics sur un domaine sans cookies

En utilisant un sous-domaine ou un  domaine pour les fichiers static, différent de celui des pages, les cookies ne sont pas envoyé au serveur ce qui accélère le temps de réponse.

Réduire le nombre d’accès DOM

La recherche d’éléments DOM dans une page par un javascript est couteuse, il est préférable d’enregistrer dans des variables l’emplacement des éléments utilisé fréquemment.

Utiliser <link> à la place de @import

Avec Internet Explorer @import est équivalent de <link> pour charger les css, à la différence que le chargement se fait en fin de page, ce qui ralentit l’affichage comme dit plus tôt.

Optimiser les images

Vérifier que la palette utilisé pour un GIF ne comprend que les couleurs affichés, imagemagick le fait très bien

Convertir les GIF en PNG et voir si la taille est plus faible.

Optimiser les PNG, avec pngcrush par exemple :

pngcrush image.png -rem alla -reduce -brute result.png

Optimiser les JPG avec jpegtran :

jpegtran -copy none -optimize -perfect src.jpg dest.jpg

Optimiser les sprites CSS

Utiliser des fichiers PNG avec au maximum 256 couleurs et placer les sprites horizontalement donne de meilleurs résultats à la compression que verticalement.

Créer un favicon.ico petit et cachable

Tout les navigateurs demandent automatiquement un fichier favicon.ico à la racine du domaine.

Il faut en créer un, de taille 1K maximum, et le rendre cachable pour que le navigateur ne le demande pas sur chaque page avec une erreur 404.

Dans un projet Django en cours de développement, j’ai une page un peu lourde en traitement que je ne souhaite pas charger en intégralité directement.
Modifier une page Html en complétant le code avec des données venant du serveur c’est faire de l’ Ajax. J’ai choisis d’utiliser le framework javascript JQuery pour faire ça, il communique facilement avec Django.

La technique traditionnel et de demander au server un objet JSON et de le parser pour écrire du code Html dans sa page.

Dans la page Html:

<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="script.js"></script>
...
<a class="requete_ajax" href="">Requete Ajax</a>

Dans script.js:

$(document).ready(function(){
    $(".requete_ajax").click(function(event){
        event.preventDefault();
        $.get(
            'get_ajax',
            { id : 123 },
            function( result ) {
                alert(result);
            }
        );
    });
});

Dans urls.py:

url(r'^get_ajax$', get_ajax, name='get_ajax'),

Et dans views.py:

def get_ajax(request):
    response_dict = {'foo': 'bar'}
    return HttpResponse(simplejson.dumps(response_dict),mimetype='application/javascript')

Le script modifie le comportement du lien de la page Html de la classe .get_ajax pour lancer une requête ajax et affiche le résultat dans une boite de dialogue.

Dans cet exemple le JSON renvoyé est simple, et il est possible d’envoyer des objets bien plus compliqué, mais faut-il encore savoir s’en servir après, et aujourd’hui je ne sais pas ! Je débute avec JQuery ça viendra !

Dans mon cas la partie de la page que je ne souhaite plus charger directement est déjà écrite dans le template. Ca serait dommage de l’effacer et la réécrire dans le javascript. Alors j’ai cherché un moyen de récupérer un tags {% block %} au fond de mon template pour le traiter et le renvoyer en Ajax.

J’ai trouvé un snippet qui permet de n’utiliser qu’un block d’un template :
http://www.djangosnippets.org/snippets/942/

Je l’ai très légérement modifié pour trouver les tags {% block %} dans une boucle {% for %} :

22: for key in ('nodelist', 'nodelist_true', 'nodelist_false', 'nodelist_loop'):

Dans la vue je n’envoie plus de JSON mais un morceau de template

return_str = render_block_to_string('app/index.html', 'items', {'items': item_list}, context)
return HttpResponse(return_str)

Le block {% block items %} est compilé et renvoyé à notre page Html avec JQuery, le traitement est alors bien plus simple:

function( result ) { $(this).replaceWith(result); }

Posted in Django | No Comments »

Le monde des navigateurs internet bouge beaucoup en ce moment avec Opera 10 sortie le, Firefox 3.7 qui pointe son nez en alpha, Chromium de Google enfin disponible pour linux en version 64 bits avec un ppa et Internet Explorer 8 qui a fait couler beaucoup d’encore.

L’utilisateur lambda ne voit que les fonctionnalités visible mais toutes ces versions sont un vrai casse tête pour les développeur web avec l’implémentation des recommandations du w3c plus ou moins approximative. La nouvelle génération de navigateurs se tourne doucement vers le nouveau HTML 5.0 qui verra le jour dans un an, alors qu’il n’y a pas si longtemps aucun ne passait le test Acid3 avec 100% de réussite.

Acid3 fait tout une batterie de tests HTML, DOM, DOM2, CSS3, SVG, Unicode, le résultat dépend du nombre de tests réussis.

Internet Explorer 7: 14/100 (j’ai pas vérifié)
Internet Explorer 8 : 20/100 (non plus)
Firefox 3.5.4pre : 91/100 (daily build)
Firefox 3.7.1apre : 94/100 (en développement)
Chromium 4.0.200.0 : 100/100
Opera 10 : 100/100

Internet Explorer toujours à la rue dans sa dernière version, et la version 6 on n’en parlera pas pour pas se facher mais elle est encore beaucoup utilisé en entreprise.

Mais en fait pourquoi je parle de ça ?

Parce qu’ en 2009 pour qu’un site web affiche son contenu comme voulu chez tout le monde il faut toujours faire des acrobaties algorithmique à se cogner la tête sur la table.

Heureusement des framework qui gèrent ces problèmes de compatibilité ont vu le jour, pour javascript et pour les feuilles de style css (je viens de le découvrir!)

Dans le billet d’avant j’explique comment réduire le nombre de requêtes sql par page pour réduire la charge demandé par un site web, mais on peut faire mieux !

En se basant toujours sur notre exemple de liste de livres, imaginons que maintenant nous avons 50 000 livres et que notre site devient très populaire, le chargement de la liste recommence à faire mal au serveur et l’hébergeur recommence à grogner.

La page exécute une requête sur 50 000 livres à chaque consultation, ça faire un peu mal, et non j’ai pas envie de faire de la pagination pour n’avoir que 20 livres affiché à la fois, de toute façon c’est fictif je fais ce que je veux !

Un moyen simple de réduire la charge est de faire une page statique; là le problème est réglé sauf que j’ai un site dynamique et je veu qu’il le reste.

On va utiliser les fonctions de mise en cache de django, ça tombe bien la documentation sur le sujet est très bien faite une fois encore.

La méthode la plus rapide est de tout mettre en mémoire avec memcached et cmemcache qui fait l’interface avec python.

Installation de memcached sur ubuntu:

$ sudo apt-get install memcached

Installation de cmemcache:

$ sudo apt-get install libmemcache-dev libmemcache0 python-setuptools python-dev
$ wget http://gijsbert.org/downloads/cmemcache/cmemcache-0.95.tar.bz2
$ tar xvfj cmemcache-0.95.tar.bz2
$ cd cmemcache-0.95/
$ sudo python setup.py install

Ensuite il faut configurer Django, dans le settings.py on ajoute

CACHE_BACKEND ='memcached://127.0.0.1:11211/'

Django est prêt à utiliser les fonctions de memcached !

Pour ce billet on va faire simple en utilisant un middleware fournis avec django qui garde en mémoire le résultat de toutes les pages du site, il ajoute aussi des en-têtes qui indiquent au navigateur internet ainsi qu’au proxy la date de dernière modification de la page pour mettre à contribution le cache du firefox des visiteurs.

Il faut modifier MIDDLEWARE_CLASSES toujours dans le settings.py

MIDDLEWARE_CLASSES = (
    'django.middleware.cache.UpdateCacheMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.cache.FetchFromCacheMiddleware',
)

Il faut juste mettre UpdateCacheMiddleware en premier de la liste et le FetchFromCacheMiddleware tout à la fin.

On ajoute aussi quelques variables, encore une fois dans le settings.py

CACHE_MIDDLEWARE_SECONDS  = ’600′
CACHE_MIDDLEWARE_KEY_PREFIX = ‘monsite’

La première variable indique pendant combien de temps les pages restent en cache et la seconde évite des problèmes si plusieurs sites utilisent memcached.

Si le site permet de se connecter à un compte il vaut mieux ajouter une variable CACHE_MIDDLEWARE_ANONYMOUS_ONLY = True pour ne pas avoir des résultats bizarre.

Une autre astuce bien sympa, surtout pour affiche les titres de 50 000 livres d’un coup est d’utiliser la fonction de compression des pages en gzip, pour ça il faut ajouter le middleware ‘django.middleware.gzip.GZipMiddleware’ juste avant le FetchFromCacheMiddleware.

Grâce à memcache et les fonctions de django notre site internet de bibliothèque ne fait pas plus d’une requête sql toute les 10 minutes, certaines pages n’ont même plus besoin d’être envoyé aux visiteurs en utilisant leur cache et les pages envoyé sont compressé !

Tout ça se traduit par moins de charge sur le serveur et moins de trafique, et le site est plus rapide !

Posted in Django | 1 Comment »

Je développe actuellement un projet avec django, un framework python dédié à la conception de sites internet vraiment très complet.
Expliquer en détail comment fonctionne django n’est pas le sujet de ce billet mais si ça vous intéresse la documentation est vraiment complète!

Prenons un exemple, totalement ficit !
Nous avons un site web hébergé sur un petit serveur mutualisé, qui permet de consulter une petite bibliothèque de 1 000 livres.

La base de données est géré complètement par django en s’appuyant sur un modèle que l’on a défini.

Nous avons un modèle Auteur et un modèle Livre dont le champs auteur est une instance du modèle Auteur

class Auteur(models.Model):
    nom = model.Charfield()

class Livre(models.Model):
    titre = models.Charfield()
    auteur = model.ForeignKey('Auteur')

Dans la vue on va récupérer la liste des livres à envoyer dans le template ‘list_livre.html’:

def list_livre(request):
    liste = Livre.objects.all()
    return render_to_response('liste_livre.html', { 'livres' : liste })

Le template :

<html>
 <body>
  <h1>Liste des livres</h1>
  <ul>
  {% for livre in livres %}
    <li>{{ livre.titre }} écris par {{ livre.auteur.nom }}</li>
  {% endfor %}
  </ul>
 </body>
</html>

Tout s’affiche correctement ! Sauf que l’hébergeur commence à menacer de fermer le site qui prend beaucoup de ressources et ralentie les autres sites du même serveur (j’ai dis qu’il était mutualisé!), il va falloir faire quelque chose …

Intéressons nous aux requêtes sql envoyé, le middleware DebugFooter fait vraiment des merveilles, il ajoute en fin de page la liste des requêtes sql et le temps d’exécution pour chaque.

Pour notre exemple la vue exécute comme demandé :

SELECT livre.titre, livre.auteur FROM livre

et là surprise il y a aussi une autre requête pour chaque livre !

SELECT auteur.nom FROM auteur WHERE auteur=x

Le système de template de django demande lui même les informations qu’il lui manque à la base de données, et pas de la façon la plus propre.
Pour une liste de 1000 livres avec des auteurs différent on obtient 1001 requêtes SQL

Pour éviter que le template demande tout ça on va réécrire notre requête dans la vue correctement pour avoir la liste des auteurs en même temps

liste = Livre.objects.all()

devient

liste = Livre.objects.only('titre','auteur__nom').select_related('auteur').all()

Comme ça le template a bien toutes les informations nécessaire et l’affichage de la page ne demande plus qu’une seule requête, l’affichage est bien plus rapide et l’hébergeur ne m’embête plus !

SELECT livre.titre, auteur.nom FROM livre INNER JOIN auteur ON (livre.auteur = auteur.id )

select_related() ajoute au résultat les auteurs, only() permet de contrôler quels champs sont renvoyé, pas vraiment indispensable dans cet exemple minimaliste mais très utile lorsqu’on utilise des modèles avec beaucoup de champs.

Posted in Django | No Comments »

Welcome !

August 23rd, 2009

Tout d’abord commençons par les politesses de courtoisie, c’est un endroit respectable ici !  Cher visiteur, bienvenu sur mon blog !

Au menu, des trucs de geek en fonction de mon humeur du moment, sécurité informatique, programmation web, découverte d’outils pratique et que sais-je encore …