Django-transmeta ou comment traduire son contenu sans se faire suer

Et oui, vous ne rêvez pas, malgré le fait que le mois de février soit ridiculement court, je trouve tout de même le temps d’écrire mon billet de l’app django du mois. Comme quoi, tout arrive.

Bon il faut dire, que j’ai du coup sélectionné une app assez petite, que j’ai découvert grâce à #django-fr (merci les gars) et avec une doc très bien conçu que je vais donc pouvoir honteusement recopier en parti pour vous expliquer comment cela fonctionne.

1- Où on le trouve, comment on l’installe, tout ça quoi (et la doc) ?

Sur la page qui lui est dédiée sur google code. Et pour l’installer, point de tar.gz, de easy_install ou de pip. Le seul moyen de l’installer consistera à faire un bon vieux svn checkout des familles.

Au niveau de la doc, il n’y a que la page du project home du google code. Mais par contre, elle est super bien foutue.

Rendre heureux aussi, ceux qui ne parlent pas français (personne n’est parfait 🙂 ).

Django-transmeta tente de répondre à un besoin tout con, comment traduire efficacement son contenu, lorsqu’on a un site multi langue. Parmi tout les moyens imaginables pour faire cela (j’en reparlerais en fin de billet), transmeta a choisi de modifier directement les tables des modeles à contenu traduisible.

2- Comment ça marche ?

Je vais reprendre, texto, les exemples donnés par la page web de transmeta, parce qu’ils très clairs. Prenons l’exemple d’un model qui décrit un bouquin.

Tel que :

class Book(models.Model):
title = models.CharField(max_length=200)
description = models.TextField()
body = models.TextField(default='')
price = models.FloatField()

Là, rien n’est traduisible. Imaginons que l’on veuille traduire la description et le body. Rien de plus simple, le code du model devient alors :

class Book(models.Model):
__metaclass__ = TransMeta

title = models.CharField(max_length=200)
description = models.TextField()
body = models.TextField(default='')
price = models.FloatField()

class Meta:
translate = ('description', 'body', )

On rajoute une metaclass et on défini dans la classe méta, les champs que l’on veut traduire.

Il faut ensuite, dans le settings.py, définir les langues que l’on veut gérer, par exemple :

LANGUAGES = (
('es', ugettext('Español')),
('en', ugettext('English')),
)

Et qu’est ce que cela fait ?
Un petit manage.py sqlall va vous le dire :

CREATE TABLE "fooapp_book" (
"id" serial NOT NULL PRIMARY KEY,
"title" varchar(200) NOT NULL,
"description_en" text,
"description_es" text NOT NULL,
"body_es" text NOT NULL,
"body_en" text NOT NULL,
"price" double precision NOT NULL
)
;

apparaît ici la solution utilisé pour transmeta. Chaque champ qui est défini comme traduisible donne lieu à n champs en bd, pour n langues.

Là vous allez me dire : ‘Ok, bien cool, mais comment on fait quand on rajouter une langue ou que l’on veut faire en sorte qu’un champ existant qui n’était pas traduisible le soit ?’
Forcément, c’est prévu. Sinon, je n’aurais pas osé faire de billet. Donc il y des commandes manage.py spécial pour altérer les tables déjà existantes et permettre de rajouter soit des langues, soit des champs traduisibles.
Dernière chose, il y a tout ce qu’il faut de prévu pour pouvoir gérer ses champs dans l’admin.

3- Retour sur la méthode

Le principe de fonctionnement de transmeta implique de devoir utiliser des alter table à chaque fois que l’on veut rajouter une langue ou un champ. J’avoue que je n’ai jamais aimé les alter table sur du sql. Est ce un tort que j’ai ? Je ne sais pas.
Vous me direz que si l’on utilise pas d’alter table, on se retrouve presque les mains liées et qu’il ne reste que peu de solution, à part des grosses tables sous la forme ‘langue code’, ‘field name’, ‘trad’. Il est vrai. Mais bon, j’aime pas les alter table.

4- Retour sur le titre du billet :

Tout le monde aura compris, bien entendu, le jeux de mot. C’est une référence directe à transmeta, l’entreprise américaine qui faisait des processeurs ni ne chauffaient que très peu et qui donc n’avait pas besoin de ventilo. D’où le sans suer. C’est nul, je sais.

Ajouter ses filters perso aux filters builtins

Vu le manque de temps dont je souffre, je n’ai pas vraiment le temps d’écrire de long billet. J’espère pouvoir changer cela assez vite. Mais en attendant, une petite astuce que j’ai découvert il y a peu et que pour l’instant, je dois l’avouer, je n’utilise pas beaucoup. Mais ça peut-être utile.

Django offre deux outils merveilleux pour bosser avec ses templates, les templatetags et les filter. J’en utilise à tour de bras, sans arrêts.

Ce qu’il y a d’ennuyeux, il faut bien le dire, c’est dans le cas où l’on utilise un filter, dans tout les templates, de devoir faire, dans chaque template le {% load myfilter %}

Sachant qu’en plus si j’inclus un template A dans un autre B, le template A ‘n’hérite pas’ des filters et templatetags qui ont été déclaré dans B. Je devrais donc de nouveau dans A me refrapper les {% load ..

Pas de quoi raler plus que ça, je vous l’accorde.

Mais quand même, ce n’est pas très DRY tout ça, si on doit faire un load dans tout les templates de son appli.

Heureusement, papi Django a pensé à nous. Et il y a la possibilité de déclarer un filter comme étant un builtins. Il sera donc inclus par défaut, dans tout les templates.

Comment on fait ce petit miracle ? Rien de plus facile. Il suffit de mettre quelque part dans son code (dans l’__init__ de son app par exemple ) :

from django.template import add_to_builtins
add_to_builtins('nom_app.templatetags.nom_fichier_filter')

C’est tout de même assez facile, non ?

Par contre, je pense qu’il ne faut pas abuser de cette petite astuce, je soupçonne une diminution des perfs possible dans le cas d’une utilisation sans modération.

Unpacking de tuples dans les templates django

Il peut arriver que l’on est besoin de passer à ses templates des données sous la forme de tuple. Mais comment dans le template, récupéré les données qui sont dans le tuple ?

Question importante s’il en est.

Je vous rassure tout de suite, là réponse n’est pas ‘construire une classe qui sert à rien pour pouvoir faire instance.nom_donneée_membre. ‘

Il y en en fait deux méthodes.

Méthode 1

on peut unpack (désolé je n’ai pas trouvé de traduction françaises pour unpack) les tuples en utilisant la notation .NumeroChamp. Petit exemple :

tuples = [ ( foo, barr, tut ) , ( a , b , c ) , ( x, y , z ) ]

{% for tuple in tuples %}
    {{ tuple.0 }} {{ tuple.1}} {{ tuple.2 }}
{% endfor %}

C’est déjà pas mal, mais c’est peu lisible. Il y a donc mieux, la méthode 2.

Méthode 2

Celle ci permet d’unpack directement vos tuples dans la boucle for, en donnant un nom à chaque variable. Dans ce cas là, il faut donc que chaque tuple est la même structure.

Imaginons par exemple que l’on construit des liens à la volée, avec des tuples qui contiendraient deux champ, l’url et le nom du lien, on aurait donc :

{% for url , name  in tuples %}
    <a href="{{url}} "> {{name}}</a>
{% endfor %}

Plutôt sympa non ?

Django 1.2 beta, the news

Comme le petit billet qui listait les nouveautés de la version alpha de django 1.2 a eu un petit succès, j’ai décidé de récidiver et de faire la même chose pour la sortie de la version beta. Ce qui sera d’ailleurs beaucoup moins fatiguant, vu qu’il y a peu de nouveautés entre l’alpha et la toute récente beta.

Pour ceux qui veulent les releases notes précises, en anglais, c’est par ici, sur le site de django.

1- Trucs obsolètes ou changés.

1.1 Le lanceur de test

Le lanceur de test passe dans un modèle class-based. L’ancien style fonctionne encore mais il faut penser à passer dans le nouveau style.

1.2 La Syndication

La classe qui Feed qui gère les feeds a été modifiée (mais elle garde le même nom) et en plus elle change de package. De django.contrib.syndication.feeds.Feed elle devient django.contrib.syndication.views.Feed. L’api du truc ne change pas vraiment, mais maintenant on peut l’utiliser dans des vues.

De plus un atom:link apparaît, pour être en concordance avec les RSS best practices.

1.3 Encodage de cookies

L’ancien encodage des virgules et des points virgules bugguaient dans certain navigateur (IE par exemple..) Du coup, l’encodage a été changé.

2- Ce qui est complètement nouveau dans la beta

Tout d’abord, il faut savoir que la sortie de cette beta marque le gel des nouveautés dans la 1.2. Et non. Plus rien de nouveau. Il n’y aura plus maintenant que de la correction de bug (ma bonne dame). Mais en attendant, cette beta amène quand même quelque nouveautés.

2.1 Les permissions par objet

Ce n’est pas géré directement par le système par défaut de gestion des permissions. Mais cette beta permet maintenant à des systèmes tiers de gestion de permissions, de mettre en place un système de gestion de permissions par objet. Et ça, ça tue. (Pour être exact, ce système était déjà présent dans l’alpha mais pas documenté du tout).

2.2 Les permissions pour les anonymes.

Encore une modification du système d’authent / droit d’accés, qui permettra au système tiers de gestion d’authenfication, de gérer les droits pour les utilisateurs anonyme. Cette nouveauté permet de centraliser et d’homogénéiser les choses.

2.3 le select_related () passe à la gonflette

Peut-être grâce au pouvoir du glaive ancestral, ou peut-être juste à un gentil codeur core-team django, en tout cas, maintenant, le select_related accepte les related_name en paramètre.

3- Conclusion

Et voilà, c’est ‘déjà’ fini… Mais bon, je trouve que ça fait déjà pas mal de nouveautés (et de nouveautés utile en plus) pour une version beta. Pour la roadmap, rien n’a changé :
RC autour du 2 mars
version finale autour du 9 mars.

Il ne me reste donc plus qu’une seule chose à ajouter : Vivement le mois de mars.

je dois bien avouer que j’avais prévu d’insérer ici un proverbe concernant le mois de mars… mais malheureusement après une rapide recherche google j’ai eu le choix entre :

  • Pluie de mars ne vaut pas pisse de renard.
  • Mars qui rit malgré les averses prépare en secret le printemps
  • Au mois de mars, pluie et vent fou, sur nos gardes tenons-nous.
  • Mars en brume, mois de mai enrhume.
  • D’autre que je vais vous épargner.

Je ne finirais donc pas par un proverbe de mars. Tant pis.

petit mémo python

J’aime lire du code des autres. Enfin quand c’est du code de qualité, bien entendu. (Parce que question code bien pourri… j’ai été servi, plus qu’à mon tour, sur les différentes reprises de projets complètement à la ramasse auxquels j’ai eu la joie de participer).

Pourquoi j’aime lire du code bien écrit ?

Parce que tout simplement, j’apprend beaucoup en faisant cela. Et parfois, je découvre des ‘trucs’, que je trouve tellement élégant que je me dis ‘mais comment je pouvais faire sans’.

C’est une des raisons qui fait que je me fais assez régulièrement des sessions de lecture du code du django. La dernière remonte à .. hier soir.

Et j’y ai découvert une écriture que je trouve très élégante. C’est celle-ci :

def func(foo):
    return foo

toto = func(arg) or "bip"

A quoi sert-elle ? elle permet de remplacer le code suivant :

def func(foo):
    return foo

bar = func(None)
toto = bar if bar else "bip"

Une petite explication en bon français ? (pas forcément utile, j’en conviens, vu la simplicité de la chose, mais bon).

Imaginons que l’on veuille donner une valeur à une variable. Cette valeur peut-être soit la valeur de retour d’une fonction, soit si le code de retour est None (ou False) une chaine de caractère. Sans le petit truc avec or, on est obligé d’écrire le code que j’ai mis juste au dessus et qui utilise une variable temporaire (et un opérateur if .. else .. que je n’aime pas trop).

Voila, je suis sur que je n’ai rien appris à personne, mais bon, on ne sait jamais.

Tagcon, où comment écrire simplement les templatetags, cong !!

Oui, je sais, je devrais renouveler un peu mon stock de blagues. Parce que mettre des cong à la fin de tout et n’importe quoi, pour singer l’accent marseillais, ça commence à …. Je sais. (j’en profite pour vous rappeler que le 24 et 25 avril, il va y avoir la DjangoCong à Marseille, une super convention fr où ça va parler django, encore et encore).

Et, oui, la django-app du mois de janvier arrive vraiment sur le fil, à quelques heures du dépassement de délai. Mais faudra dire ça au crétin qui a décidé qu’une journée ça ne serait que 24h et qu’il fallait obligatoirement dormir chaque nuit, ou presque … (bordeille comment je vais faire en février moi..)

Enfin, je reste dans les délais, c’est le principal. Et pour le deuxième mois consécutif, je fais une petit entorse au principe de cette catégorie, vue que ce mois-ci, encore une fois, ce n’est pas une vrai django-app que je présente, mais un ‘simple’ module python.

0- Et ça sert à quoi  ?

En fait tagcon est ‘inutile’ (donc indispensable vous allez me dire… ne niez pas, je vous ai tous entendu le dire…). Il sert simplement à faciliter l’écriture des templatetags (et surtout des templates tags à argument). On pourrait se dire que c’est assez inutile. Mais en fait, ça permet de gagner un peu de temps et de réduire d’une façon assez intéressante la taille de son code. Donc..

Un petit exemple, tiré de la doc, parce que comme me l’a fait remarquer daks (dont je conseille le blog, qui vient d’arriver dans mes RSS) , ça manque :

from django.contrib.auth.models import User
from django.template import Library
import tagcon

register = Library()

class UserListTag(tagcon.TemplateTag):
    limit = tagcon.IntegerArg(default=10)

    def render(self, context):
        self.resolve(context)
        yield "<ul>"
        for user in User.objects.all()[:self.args.limit]:
            yield "<li>%s</li>" % (user.username,)
        yield "</ul>"

1- Où on le trouve, comment on l’installe, tout ça quoi ?

Alors alors, sur le github qui va bien. (une petite remarque qui a rien à voir, je préfère de loin bitbucket à github … et pas juste parce que mercurial c’est mieux que git).

Une fois qu’on a récupérer le package (par fork si on a un compte github ou avec un petit tar.gz), il y a deux façon de l’installer:

  • un petit python setup.py install
  • en copiant directement le fichier base.py qui se trouve dans tagcon.tagcon (dans ce cas là on pourra renommer base.py en tagcon.py).

2- La documentation.

Alors là… Il n’y a pas grand chose. Un fichier readme qui donne un petit exemple et c’est tout. Après, il vous faudra lire les tests et le fichier source directement. Ce n’est pas donc super fourni, mais d’un autre coté, vous me direz que le module est tellement simple que ça suffit. Et vous ne seriez pas loin d’avoir raison. Mais bon.

3- En conclusion

Au départ, quand j’ai découvert ce petit module (je crois que je dois, encore une fois, remercier benoitc pour cela), je n’étais vraiment pas convaincu de son utilité. Il me semblait presque inutile et puis un peu restrictif en obligeant à utiliser la fonction render (alors que j’ai plus l’habitude d’enregistrer les fichier template html avec le décorateur inclustion_tag). Mais au final, on se ren compte que ça simplifie pas mal les choses et on y prend rapidement goût.

4- dernier paragraphe complètement HS.

Ma liste de django-app à tester est loin d’être vide (avec entre autre piston, haystack,..) et ça ne risque pas de changer, vu que chaque semaine, je découvre une nouvelle app. Toutefois, j’écris ces billets avant tout pour qu’ils soient utile . Donc, si vous connaissez des app que vous adorez et que vous aimerez faire découvrir à tous. Laissez moi leur nom en commentaire et je m’efforcerais de les tester.

Où les middleware envahissent la django-application du mois

Je sais, je suis impardonnable, je n’ai pas fait de billet sur la django-application de décembre. Vous pourriez même rajouter que je le suis encore plus parce que décembre c’est le mois des vacances de Noël et que qui dit vacances dit temps pour écrire un billet.

Oui, mais non. Parce que faut pas croire, ça prend du temps tout ces repas de fêtes. Plein de temps. Sans compter le temps que l’on passe à arpenter les magasins (IRL ou sur le net d’ailleurs) pour trouver des cadeaux. Et sans parler du fait que comme décembre est un mois de vacances, pendant les jours de non-vacances, faut bosser encore plus pour essayer de faire autant que pendant un mois normal, mais en moins de jours.

Autant dire que non, je n’ai pas eu le temps pour la django-app du mois.

Et que vu mon emploi du temps en janvier, ça va être tendu pour le billet de celle de janvier. Mais, mais, dans ma grande mansuétude, j’ai décidé de faire un billet de django app du mois de décembre, en retard.

Et pour une fois, je ne parlerais pas de django-app, mais de middleware. Mais oui, les middleware django, vous savez, ce mécanisme génial qui permet de pluger de petit bout de code à différents niveaux de traitement des requêtes. Ce qui permet de modifier plus ou moins profondément le comportement de django au niveau de la gestion de ses entrées / sorties.

Ca permet de faire plein de trucs, et plus encore.

Aujourd’hui je parlerais de deux d’entre eux, un que j’utilise très souvent et un autre que je vais utiliser sous peu..

1- Loguer les requêtes SQL que fait django

from django.db import connection
from django.template import Template, Context
from django.conf import settings

#
# Log all SQL statements direct to the console (when running in DEBUG)
# Intended for use with the django development server.
#

class SQLLogToConsoleMiddleware:
    def process_response(self, request, response):
        if settings.DEBUG and connection.queries:
            time = sum([float(q['time']) for q in connection.queries])        
            t = Template("{{count}} quer{{count|pluralize:"y,ies"}} in {{time}} seconds:\n\n{% for sql in sqllog %}[{{forloop.counter}}] {{sql.time}}s: {{sql.sql|safe}}{% if not forloop.last %}\n\n{% endif %}{% endfor %}")
            print t.render(Context({'sqllog':connection.queries,'count':len(connection.queries),'time':time}))                
        return response

J’ai trouvé ce tout petit middleware sur djangosnippets. Il se contente d’afficher la liste de toutes les requêtes SQL faites par django, en donnant le temps pris par leur execution.

C’est bien utile pour vérifier que ces vues ne font pas de la merde et pour tenter d’optimiser en factorisant des choses.

2- Django-maintenancemode

Celui-là, je l’ai découvert au détour d’une conversation sur #django-fr. Carrément plus pratique que la bête page html qui dit ‘en maintenance’, utile à connaître et à avoir dans sa django-boîte à outil.

Les nouveautés de django 1.2 alpha

Avant de commencer à te faire perdre ton temps, je tiens à te prévenir cher lecteur. Si tu as lu la release note de django 1.2 alpha 1, tu n’apprendras rien de plus en lisant ce petit billet.

Pourquoi alors l’écrire ?

Au cas où tu n’aurais pas encore lu la release note et que lire du français t’amuse plus que lire de l’anglais. Et puis comme ça, en plus, tu as le lien vers la release note.

1- Les trucs nouveaux

1.1- protection contre les attaques CSRF.

Les attaques CSRF (Cross-Site Request Foregery) sont rendus  possible par le partage des données de login/session entre les différents onglets d’un navigateur. Du coup, si vous êtes logués sur un site quelconque (prenons facebook comme exemple) et que vous allez sur le site d’un vilain méchant pirate, le vilain méchant pirate pourrait faire des actions sur Facebook, comme si c’était vous qui les faisiez. (Bon pour pouvoir faire cela, il faut que vous cliquiez sur un lien du site du vilain pirate, ou que vous soumettiez un formulaire ou que vous executiez du js).

Django implémente maintenant une façon cool de s’en protéger. (avant il existait un middleware pour le faire). Moralité, si Facebook était en django 1.2, les attaques CSRF ne seraient pas possible.

1.2- Backends des emails

On peut maintenant configurer la façon que django aura d’envoyer des emails. Sympa pour pouvoir debugger et tester les envois d’emails, ça évitera d’avoir à passer par des simulateurs de SMTP.

1.3- Le framework d’envoi de messages

Il remplacera l’ancienne API d’envoi de message et apparemment, il va être juste terrible. Les users authentifiés ou anonyme pourront en envoyer, on pourra les stocket en session ou en cookie, gérer des levels, des tags, que du bon quoi

1.4- Support des multiples bases de données

Alors ça, je l’attendais avec une impatience extrême. On pourra maintenant dire à django de se connecter à X bd différentes, choisir pour chaque requête bd sur quelle base on veut la faire ou où est ce que l’on veut sauver une instance de modèle. Que du bon quoi. Pour ne pas dire de la tuerie

1.5- Un tag if coolos

Là on peut résumer en une phrase : ‘Fini le ifnotequal a b et vive le if a != b’.

Les opérateurs de comparaisons sont enfin supporté. On pourra donc utiliser :

  • ==
  • !=
  • <
  • >
  • <=
  • >=
  • in
  • et and or et not (qui fonctionnaient déjà).

Et en plus, on pourra utiliser des filtres en même temps. Avec un {% if  inst1.var1|bla == inst2.var2|tut %}

Le pied je vous dis.

1.6- Gestion du cache des templates

On pourra configurer un truc de gestion de cache des templates. Et ça, c’est bien.

1.7- Clés en BD

On peut maintenant utiliser les naturals keys dans les fixtures (j’ai pas trouvé de traduction française qui me convenait). Ce qui peut-être bien sympa quand par exemple on a des fixtures qui bossent avec des content types (au hasard hein )

Et on peut maintenant utiliser des int de 64 bits avec le BigIntegerField .

1.8- En vrac

  • une commande en plus sur le runtest qui permet de faire sortir le lanceur de test dés qu’il y a une erreur.
  • Une amélioration de la localisation pour les dates et les nombres qui seront affichés (si l’option est activée) de la façon qui va bien en fonction de la locale.
  • L’ajout de la  propriété readonly_fields pour les champs non éditable (enfin)
  • la possibilité de customiser les couleurs pour les highlight dans la parti admin.

2- Les trucs  obsolètes ou incompatibles

Bon alors déjà, une nouvelle qui va faire que tout le monde va respirer, dans le 1.2 les trucs obsolètes seront encore supportés. Le support n’en sera supprimé que dans la version 1.4.  Ça laisse donc un peu de temps pour organiser ses migrations.

2.1- Le middleware CSRF

Forcément si la protection anti CSRF est refait d’une manière mieux, l’ancienne manière devient obsolète.

2.2- SMTPConnection

Ceux qui utilisaient la classe SMTPConnection pour envoyer des mails devront changer leur code. Heureusement les modifs sont légère (il suffit apparemment de remplacer l’appel au constructeur de la classe par un appel a get_connection () )

2.3- La configuration de la base de données

Forcément ça change. Faudra passer dans la syntaxe multidb, même si on a qu’une seule db.

2.4- L’API d’envoi de message.

Là encore, faut passer à la nouvelle version. Mais ça n’a pas l’air super compliqué. Du replace de nom de fonction et ça devrait le faire.

2.5- Les helpers de formatage de date.

Les fonctions django.utils.translation.get_date_formats() et django.utils.translation.get_partial_date_formats() sont obsolètes et il faudra les remplacer par django.utils.formats.get_format()

2.6- En vrac

  • Du fait des nouvelles fonctionnalités du if, appeler des variable ‘and’, ‘or’ ou ‘not’ est encore moins une bonne idée, vu que vous allez vous manger des TemplateSyntaxError. Bon de toute façon, c’était déjà une très très mauvaise idée d’appeler des variables ‘and’, ‘or’ ou ‘not’, donc …
  • Le LazyObject. Ceux qui utilisaient cette classe non documentée vont devoir modifier leur façon de l’utiliser.
  • Modification de ce que contient le __dict__ dans les instances de Model : Jusqu’à présent le __dict__ ne contenait que les attributs qui correspondaient au champ du modèle. Maintenant il contient un attribut _state qui sert à gérer les db multiple. Ceux qui itere sur le __dict__ de Model devront faire attention à filtrer le _state
  • les fonction get_db_prep_* des Fields ont changé de signature (et il y en a 2 de plus get_db_prep_save et get_prep_lookup )
  • du fait de l’utilisation du cache loader, il faut bien vérifier que les templates tags utilisés sont bien thread-safe (je sais pas pourquoi je sens que ce point là va être le point de départ de dizaines de touffes de cheveux de geeks arrachées).
  • Code de retour du lanceur de test : Le code de retour du lanceur de test ne correspond plus au nombre de test raté. C’est maintenant 0 si aucun test n’a merdé, 1 si il y a eu des merdes.

3- Conclusion

Que du bon dans cette nouvelle version donc. Et en plus pas spécialement beaucoup de modifications à faire pour profiter des nouvelles améliorations. Vivement le 9 mars que la stable sorte quoi. 🙂

Django : Dict’s Rivers, toi aussi devient fan.

C’est benoitc qui en me parlant des dicts fournis par Django m’a fait découvrir les quelques classes bien utiles définis dans datastructures.py du module utils de django.

Ce sont donc, comme le titre de ce petit billet (plus mémo que billet) l’explicite bien des dicts.

1- Le MergeDict

On le construit en lui donnant plusieurs dict. Il en fait une espèce de gros dict virtuel qui contient tout les dicts. Si on tente une opération sur une clé (un has_key, un get ou un __get_item__ ) il la cherchera  dans tout les dicts. Par contre si elle est présente dans plusieurs dict, il utilisera la première occurrence qu’il trouve (pour le get par exemple).

Un dict qui peut être utile, surement.

2- Le MultiValueDict

Là encore, le nom parle de lui-même. C’est donc un dict qui permet d’avoir non pas une seule valeur, mais une liste de valeur par clé. En plus des fonctions classiques des dicts, elle offre donc getlist, setlist, setlistdefault et appendlist pour pouvoir interagir avec les instances de la classe.

3- Le DotExpandedDict

Ce Dict est un peu un OVNI. En fait la classe se contente de redéfinir un constructeur qui va vous permettre de construire des dicts qui contiennent eux mêmes des dicts et celant en passant en paramètres des chaînes de caractères construite d’une manière précise (en utilisant des .)

Un exemple (celui donné dans le code) sera surement plus parlant.

>>> d = DotExpandedDict({'person.1.firstname': ['Simon'], \
'person.1.lastname': ['Willison'], \
'person.2.firstname': ['Adrian'], \
'person.2.lastname': ['Holovaty']})
>>> d
{'person': {'1': {'lastname': ['Willison'], 'firstname': ['Simon']}, '2': {'lastname':['Holovaty'], 'firstname': ['Adrian']}}}
>>> d['person']
{'1': {'lastname': ['Willison'], 'firstname': ['Simon']}, '2': {'lastname': ['Holovaty'], 'firstname': ['Adrian']}}
>>> d['person']['1']
{'lastname': ['Willison'], 'firstname': ['Simon']}

4- Le SortedDict

C’est mon préféré, tout simplement parce que j’ai déjà du l’utiliser et qu’il m’a permis de ne pas avoir à le recoder moi-même. Il se contente de garder l’ordre dans lequel on insère des choses dedans. Pour être clair, lorsque l’on itère dessus, il va utiliser l’ordre d’insertion des données. Et ça, c’est vraiment cool.

5- Le reste

il reste encore deux classes dans ce sympathique petit fichier.

Une ImmutableList, qui fonctionne comme un tuple et qui est complétement imutable (et l’on peut même choisir le message d’erreur qu’il vous envoie à la figure pour vous le dire qu’il est immutable.

Un DictWrapper qui permet de s’assurer que les valeurs contenues dans le dict seront passées à travers une fonction avant d’être donné à l’utilisateur, si la clé en rapport commence par un préfix donné.

Django-ROA ou… Mince je trouve pas de jeux de mots là

Ça sera bien la première fois que je ne fais pas de jeux de mots dans le titre d’un billet la Django-app du mois. Mais là, je n’ai vraiment pas d’inspiration et comme je suis sur un timing plutôt serré (le billet devant être publié d’ici au plus tard 4h20 pour pouvoir prétendre à être un billet de novembre), je ne vais pas trop attendre qu’elle n’arrive. Je me rattraperai le mois prochain (je pense parler de piston le mois prochain, tout un poème…).

Django-ROA donc, cette jolie app est développée par David Larlet que j’ai déjà cité dans mes précédents billets. Il permet de gérer des ressources distantes normalement accessibles en REST, en passant à travers l’ORM de django (et donc les modèles Django).

Et oui, ça permet, d’une façon plus que facile de faire comme si des modèles distants étaient des modèles locaux.

Le petit schéma suivant (que j’ai très vilainement récupéré du site de David, sur le billet parlant de Django-ROA) explique tout, bien mieux que ne le ferraient mille de mes mots.

django-roa-diagram-petit

Bien entendu, on est pas obligé de se limiter à faire communiquer notre Django à un autre Django distant. On peut se connecter à tout ce qui a une interface HTTP. Op timeline de Twitter, Op n’importe quelle BD discutant le HTTP…

Je vois à vos sourires béats que vous êtes en train de comprendre combien Django-ROA peut être utile. Et vous avez raison.

1- Où on le trouve, comment on l’installe, tout ça quoi ?

Alors on le trouve sur la page bitbucket qui lui est consacré. Pour l’installation, il y a deux possibilités :

  • soit avec hg et op on récupère la dernière version des sources
  • soit avec easy_install

Attention toutefois, le package easy_install contient une version du trunk Django 1.2. Si vous ne voulez pas l’utiliser, il faudra penser à supprimer le répertoire en question.

Dans les deux cas Django-roa vient avec une version de restkit et une version de Django-piston. Si vous avez déjà ces deux librairies là, là aussi il faudra penser à faire le nettoyage.

Enfin, c’est important, si vous êtes en Django < trunk, il vous faudra patcher votre Django pour pouvoir utiliser les many to many en distant. Si vous êtes en Django SVN, il vous faudra attendre que David ait rendu Django-ROA compatible Django 1.2 (c’est peut-être déjà le cas d’ailleurs).

2- Niveau documentation

Il y a le wiki du bitbucket ainsi que l’article du blog de David dont j’ai déjà donné le lien (mais je le redonne pour les étourdis). Il y a également le code source des tests, qui permet de bien comprendre comment tout fonctionne. Et je vous recommande vraiment de lire le code des tests si vous voulez comprendre.

3- Qu’est-ce qu’on fait et comment on le fait ?

Qu’est-ce qu’on fait, je vous l’ai déjà dit. Faut suivre un peu. On connecte notre Django à notre Django (ou d’autre base distante, voir l’exemple avec Twitter). Mais ce qu’il faut savoir c’est que Django-ROA permet de gérer la partie serveur et la partie client, dans le cas où vous développez vous même la partie ‘Django distante’.

Maintenant Django-ROA est encore en dev, et il peut être utile de connaître quelques petits tuyaux, que bien entendu, je vais vous donner.

4- Les tuyaux de Jmad…

4.1- Erreur 500 côté serveur

Sur la partie serveur, il se peut que vous vous trouviez avec des erreurs 500 assez silencieuses. C’est moi ce que j’ai eu. Il se trouve que c’est dû au logging.debug et à un problème (allez savoir pourquoi) d’encodage UTF-8. Enlevez les logging et tout roule. Je n’ai pas eu le temps de chercher et faire remonter à David le pourquoi du comment de ce problème, mais comme je me suis creusé la tête quelques temps avant de trouver le problème, je préfère vous informer.

4.2- Sérialisation…

Par défaut, on peut sérialiser les objets de trois façons :

  • en JSON,
  • en XML,
  • en utilisant le sérialiseur fait par David pour les tests qui est un sérialiseur XML un peu modifié.

Allez savoir pourquoi, mais pour moi, seul le troisième daignait fonctionner.

4.3- Authentification

Pour l’instant l’authentification sur les services distants n’est pas gérée. Si vous en avez absolument besoin, n’hésitez pas à envoyer un mail à David.

5- Conclusion

Django-ROA est encore un module en dev, il manque quelques fonctionnalités qui pourraient le faire passer du statut d’app intéressante et utile à celui d’app complétement indispensable. Mais il est d’ors est déjà bien utile, quand des problématiques de communication entre bases distantes se mettent à apparaître.

Il me semble juste que l’on devrait changer son nom, pour par exemple Django-CROA. Ça m’aurait permis d’avoir un bien meilleur titre du genre Django-CROA et le crapaud est en toi, ou alors Django-CROA , l’app qui voulait devenir aussi grosse qu’un bœuf…) enfin, tant pis.

Django-tagging, et le tag est en toi

Me revoilà donc pour le désormais presque célèbre billet mensuel de la django-app du mois. Comme d’habitude, je le fais sur le fils, limite à être en retard. Mais c’est bien normal, après tout il me faut bien un mois complet pour choisir, tester à fond et rédiger un magnifique billet concernant l’app du mois.

L’app d’octobre est donc django-tagging. Qui comme son nom l’indique sert à taguer des models.

Où on le trouve, comment on l’installe, tout ça quoi ?

On le trouve, très simplement sur google-code, sur sa page projet.  Pour l’installation, là aussi c’est du très classique :

  • pip
  • easy_install
  • télécharger un tar.gz
  • utiliser le svn de google-code

moi j’ai choisi, une fois n’est pas coutume d’utiliser easy_install.

Niveau documentation ?

Un fichier en ReStructuredText, accessible directement sur le site et présent dans les sources donne la doc et des exemples d’utilisation. Il n’est d’ailleurs par forcément à jour. Certaines fonctionnalités décrites comme étant dans la version de dev sont en fait dans la 0.3

En plus, trois petites pages sont présentes sur le wiki :

  • une pour parler d’un tips d’utilisation (avec des properties)
  • une pour les incompatibilité entre versions
  • une pour les idées et le planning

Ils sont où les murs alors ? ( ou qu’est ce qu’on peut faire avec django-tagging)

Ok, ce titre de chapitre est surement le plus pourris de ma carrière de créateur de titre de chapitre, mais tant pis j’assume.

Le configurer

Il y a quelques possibilité de configuration, en setant des variables dans le settings.py du projet. On peut par exemple forcer à ce que les tags soient en minuscule ou donner la longueur maxi d’un tag.

L’utiliser

On peut donc taguer des instances de model. Mais bon en disant cela, j’ai rien dit. En fait on peut register des models pour pouvoir ensuite simplement rajouter des tags (ou les lister) aux instances de ces models.

On peut également passer directement par les managers de Tags, fournit par l’app, pour taguer des instances.

L’utiliser encore plus

il y a quelques fonctions utilitaires fournis avec l’app.

On pourra par exemple ( avec la fonction get_intersection_by_model) récupéré le sous-ensemble des instances contenu dans le queryset passé en paramêtre 1 qui sont tagués avec tout les tags d’une liste passé en paramêtre 2

De même avoir toutes les instances d’un models tagués par un ou plusieurs tags est possible avec la fonction get_by_model, mais aussi les unions d’instance ou les groupe d’instance ayant les même tag qu’un autre objet …

Et aussi (et presque surtout aurais-je envie de dire) la fonction calculate_cloud qui permet de calculer le nuage de tag en calculant une taille de font pour chacun des tags passé en paramètre. (l’algorithme utilisé pour le calcul est configurable, en plus)

afficher les tags

Avec 4 templatestags différent pour afficher le nuage de tag, tout les tags d’un objet, tout les objets (d’un model précis) tagués avec un tag précis, ou tout les tags qui sont reliés à un model (enfin à ses instances, vous m’avez compris)

Conclusion.

Même si la façon d’ajouter un tag peu sembler un peu alambiqué (pour pouvoir avoir des tags avec des espaces, ou avec des espaces plus des guillemets, etc .. ) , c’est vraiment une bonne app, qui ne fait qu’une chose, mais qui le fait très bien. Donc, je ne peux que conseiller son utilisation.

Le built-in any, vous aussi vous allez aimer les sucettes à l’any ….

J’ai mis quelques temps à le découvrir ce built-in, pour l’oublier presque aussi vite. Je l’ai redécouvert au détour d’une conversation python au boulot.

A quoi sert-il donc ? En fait ‘à rien’, simplement à alléger l’écriture (niveau perf, je ne sais pas si cela change quelque chose, ça par contre)

any prend un seul paramêtre, un iterable et renvoie True dés que l’un des iterables est vrai. Si je cite la doc python, any est équivalent à une fonction définie comme suit :

def any(iterable):
    for element in iterable:
        if element:
            return True
    return False

un petit exemple sur une recherche de sous-chaine dans des chaines :

avant :

for word in list_words:
    if word.find(substring) != -1:
        return True
return False

après :

return any ( word.find(substring) != -1 for word in list_words )

C’est plus léger, tout le monde sera d’accord avec moi …

Et puis, vu qu’on y est, il existe aussi le built-ins all qui prend lui aussi un itérable en paramètre et renvoie True si tout les éléments de l’itérable sont True.
(note de bas de billet : oui je sais, mes titres de billets sont toujours aussi … mais j’assume)