<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Mathieu Agopian</title><link href="http://mathieu.agopian.info/blog" rel="alternate"></link><link href="http://mathieu.agopian.info/blog/feeds/all.atom.xml" rel="self"></link><id>http://mathieu.agopian.info/blog</id><updated>2011-11-14T10:49:00Z</updated><entry><title>Djangocong 2012 !</title><link href="http://mathieu.agopian.info/blog/djangocong-2012.html" rel="alternate"></link><updated>2011-11-14T10:49:00Z</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2011-11-14:/blog/djangocong-2012.html/</id><summary type="html">&lt;p&gt;C'est à nouveau ce moment de l'année ou la nouvelle tant attendue est
enfin annoncée : la &lt;a class="reference external" href="http://rencontres.django-fr.org/2012"&gt;conférence française&lt;/a&gt; sur &lt;a class="reference external" href="https://www.djangoproject.com/"&gt;Django&lt;/a&gt; aura lieu pour
la troisième année consécutive, cette fois-ci sur Montpellier.&lt;/p&gt;
&lt;p&gt;Cette année, donc, plusieurs nouveautés :&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;la conférence aura lieu sur Montpellier, les deux précédentes ayant
été sur Marseille&lt;/li&gt;
&lt;li&gt;il est possible de coucher sur place (ou juste à côté)&lt;/li&gt;
&lt;li&gt;le &lt;a class="reference external" href="http://www.maison-familiale-carnon.fr/"&gt;lieu des conférences&lt;/a&gt; est &lt;strong&gt;sur&lt;/strong&gt; la plage&lt;/li&gt;
&lt;li&gt;tous les lieux (conférences, couchage, apéro communautaire du samedi
soir) sont très proches et accessibles à pied&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Niveau programme, le même principe qui a fait le succès de la &lt;a class="reference external" href="http://rencontres.django-fr.org/2011"&gt;dernière
édition&lt;/a&gt; est repris :&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;samedi matin : &amp;quot;on a fait&amp;quot;, retours d'expérience sous la forme de
conférences courtes (12 minutes) ou de &lt;em&gt;lightning talks&lt;/em&gt; (5 minutes)&lt;/li&gt;
&lt;li&gt;samedi après-midi : &amp;quot;on discute&amp;quot;, ateliers participatifs, aussi
connus sous le nom de &lt;em&gt;barcamp&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;samedi soir : apéro communautaire, occasion idéale pour boire et
manger ensemble, faire connaissance, échanger à bâtons rompus ...&lt;/li&gt;
&lt;li&gt;dimanche matin : &amp;quot;on fait&amp;quot;, sous la forme de &lt;em&gt;sprints&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;dimanche après-midi : &amp;quot;on refait le monde&amp;quot;, soit sur la plage, en
balade, autour d'un bœuf musical ... détente !&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Mais le programme, lui, dépends en grande partie de TOI ! Il dépendra
des sujets que tu aura proposés, tu es donc responsable en grande partie
de sa teneur !&lt;/p&gt;
&lt;p&gt;Pour pouvoir organiser la meilleure rencontre possible, nous avons donc
besoin de ton aide, et ce de différentes manières. Voici ce que tu peut
faire pour &lt;strong&gt;notre&lt;/strong&gt; rencontre :&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;proposer un sujet de conférence : tu as créé ou utilisé une librairie
qui mérite d'être connue, tu as mis en place des méthodes ou outils
pour faciliter ton développement web avec Django, ou tout simplement
une expérience à partager ? C'est le format qu'il te faut !&lt;/li&gt;
&lt;li&gt;proposer un &lt;em&gt;lightning talk&lt;/em&gt; : idéal pour des sujets courts, pêchus,
dynamiques, rigolos, pour présenter la dernière nouveauté, pour faire
connaître son projet, sa communauté, une idée... beaucoup plus libre
et dynamique qu'une conférence*
*&lt;/li&gt;
&lt;li&gt;parler de cette rencontre (et de son appel à conférence) autour de
toi, en faisant connaître le lien vers &lt;a class="reference external" href="http://rencontres.django-fr.org/2012"&gt;Djangocong 2012&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;demander à des personnes que tu connaît de proposer une conférence ou
un &lt;em&gt;lightning talk&lt;/em&gt; sur un sujet qu'il maîtrise et que tu aimerait
voir présenté&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Tu as jusqu'au &lt;strong&gt;13 janvier 2012 inclus&lt;/strong&gt;, donc ne perd pas de temps !&lt;/p&gt;
&lt;p&gt;Pour ma part j'ai déjà proposé trois sujets, et toi ?&lt;/p&gt;
&lt;p&gt;P.S: tu comptes venir à la conférence, et tu aimerais amener femme et
enfants ? Il semblerait que tu ne soit pas le seul, il y aura donc
probablement d'autres &amp;quot;accompagnants&amp;quot; avec qui passer du bon temps !
Plus d'informations sur le sujet à venir, et je vous tiendrais bien
entendu informés de l'ouverture des inscriptions !&lt;/p&gt;
</summary><category term="conference"></category></entry><entry><title>Point-virgule</title><link href="http://mathieu.agopian.info/blog/point-virgule.html" rel="alternate"></link><updated>2011-10-13T08:30:00Z</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2011-10-13:/blog/point-virgule.html/</id><summary type="html">&lt;p&gt;&lt;em&gt;(Ceci est mon premier essai d'écriture. Je trouve l'idée sympa, mais la
réalisation est au mieux moyenne... faites moi connaître votre avis et
conseils d'amélioration dans les commentaires !)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Ça a recommencé.&lt;/p&gt;
&lt;p&gt;Tout va être à refaire, tout ce que nous avons construit, patiemment,
péniblement, durant les 80 dernières années. Voilà l'amer constat qu'il
m'est donné de faire, au crépuscule de ma vie.&lt;/p&gt;
&lt;p&gt;J'avais espéré pouvoir offrir à mes pairs, mes confrères et amis, un
avenir plus radieux que celui qui nous était initialement réservé.&lt;/p&gt;
&lt;p&gt;Tout s'était mis en place, pièce après pièce, comme un puzzle, comme un
Tetris bien engagé.&lt;/p&gt;
&lt;p&gt;Le point de départ : une morne journée d'octobre, et un développeur de
plus qui se fait dire que son boulot devrait être réservé aux débutants,
aux jeunes diplômés. Les mots résonnent encore dans ma tête : &amp;quot;Le
développement, c'est bouché, ça n'a pas d'avenir dans la vie d'un homme.
Il faut savoir évoluer, devenir chef de projet, consultant,
technico-commercial. Tout sauf rester prostré devant son ordinateur, en
concurrence avec les milliers d'autres programmeurs frais sortis de
l'école, toute cette main d'oeuvre bon marché. Il faut que tu saches
qu'aujourd'hui, trouver un programmeur c'est super facile. Un conseil,
évolue.&amp;quot; Ces mots lâchés par un patron vraiment plus au goût du jour,
dépassé, mais toujours aussi puissant, ont fait écho aux nombreuses
plaintes, rancoeurs et billets de blogs traitant du sujet : le
développement n'était pas apprécié à sa juste valeur, n'était pas
apprécié du tout en fait. Et cette mentalité rampante était devenue à la
mode, comme une évidence.&lt;/p&gt;
&lt;p&gt;Pourtant, le manque de développeurs était flagrant. Trouver un
collaborateur ou employé expérimenté, motivé et qui se tenait informé
des dernières avancées techniques était de plus en plus compliqué. Les
plus grandes entreprises comme Mozilla et Google attiraient la plupart
de ces perles rares qui savaient pouvoir y trouver un hâvre de paix, de
sérénité, et de liberté dans leur travail quotidien. Un endroit leur
permettant d'exprimer leur créativité, et leur plein potentiel.&lt;/p&gt;
&lt;p&gt;La bascule a d'abord été imperceptible. Sous l'action d'un petit groupe
de Djangonautes (terme maintenant oublié depuis longtemps), les
évènements se sont enchainés :&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;tout d'abord les prix journaliers des indépendants ont augmenté,
d'abord lentement, puis de plus en plus rapidement pour atteindre les
prétentions de rockstars du SEO.&lt;/li&gt;
&lt;li&gt;la mort d'une idôle de plusieurs générations, le grand Steve, et son
image de &amp;quot;technologie utilisable par tous&amp;quot;. Lui et son idée du
&amp;quot;design avant tout&amp;quot; avaient fait de l'ombre pendant trop longtemps au
travail des milliers de développeurs qui avaient été nécessaires à
ses gadgets.&lt;/li&gt;
&lt;li&gt;la création de nouveaux langages de programmation, comme Dart, qui
reniaient les avancées en lisibilité et clarté acquises
laborieusement par certains pionniers, dont Py^W ... celui que l'on
ne saurait nommer.&lt;/li&gt;
&lt;li&gt;la généralisation de l'utilisation de langages complexes, d'abord
Java, puis Ruby, Dart, Pronog, Cortran, kobol, et enfin, le langage
qui les remplaça tous, le seul et unique autorisé de nos jours,
l'Ensembleur.&lt;/li&gt;
&lt;li&gt;les initiatives incessantes de minimisation de l'intérêt de la
documentation, que ce soit sur les API, les langages, ou les projets,
leur ROI étant inexistant.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Une fois tout ça en place, l'étape suivant fût d'imposer des
restrictions sur les places disponibles en école informatique. Pour
faire voter une telle loi, il suffisait de se mettre quelques
politiciens dans la poche. En effet, depuis de très nombreuses années
déjà ils étaient devenus maître dans l'art de l'absurde, dans l'art de
présenter une idée comme étant la solution logique à un problème, alors
que c'était exactement le contraire qui était recherché.&lt;/p&gt;
&lt;p&gt;Quelques exemples:&lt;/p&gt;
&lt;p&gt;On ne veut pas que le piratage de la musique soit facile ? On va
limiter les téléchargements gratuits (et donc renforcer et enrichir les
solutions de piratage payantes).&lt;/p&gt;
&lt;p&gt;Il y a des problèmes de sécurité depuis qu'on a augmenté les sanctions,
et diminué la prévention ? Continuons dans cette voie, et augmentons
encore les sanctions, et diminuons d'autant la prévention.&lt;/p&gt;
&lt;p&gt;Ces raisonnements illogiques, présentés comme les seuls valables, ont
pavé le chemin à la grande loi : il y avait un manque d'informaticiens,
de développeurs ? Limitons les places dans les écoles d'informatique
pour les réserver à une &amp;quot;élite&amp;quot;, qui serait bien plus efficace. Grâce au
lobby alors puissant des Congs, cette loi fût votée dans le plus grand
secret, en pleine nuit, par une poignée de fidèles, et ne rencontra donc
aucune opposition.&lt;/p&gt;
&lt;p&gt;Le plan se déroulait à merveille jusque là, et il alors fallut franchir
le cap le plus difficile : le bannissement pur et simple du langage
qu'on ne saurait nommer, et du framework plébiscité jusque là par les
adeptes du web, le framework au poney magique.&lt;/p&gt;
&lt;p&gt;Sans cette dernière étape, il n'aurait jamais été possible d'arriver où
nous en sommes maintenant. Ou devrais-je dire, où nous en étions il y a
encore quelques années.&lt;/p&gt;
&lt;p&gt;Il n'était pas envisageable de garder un langage aussi facile,
accessible à tous et efficace, sous peine de fragiliser la main-mise des
informaticiens sur le précieux code.&lt;/p&gt;
&lt;p&gt;Nous, les développeurs, sommes maintenant une caste renommée et
appréciée de tous, ou plutôt crainte de tous. N'étant plus qu'une
centaine de par le monde, nous imposons notre volonté aux hommes d'états
comme aux chefs d'entreprise. Quand nous nous déplaçons, les foules nous
acclament en espérant que nous daignerons continuer à maintenir les
infrastructures, les services et programmes qui régulent la vie
quotidien des 12 milliards d'habitants de notre planète.&lt;/p&gt;
&lt;p&gt;Oui, dernièrement, la vieillesse aidant, nous nous sommes ramollis.
Notre emprise de fer s'est assouplie, et certains pseudo-langages ont
fait une timide apparition. Bien entendu, les créateurs étaient
immédiatement arrêtés, convertis, puis assujettis à notre cause.&lt;/p&gt;
&lt;p&gt;Oui, ces pseudo-langages persistaient parfois pendant quelques mois,
soutenus et améliorés par quelques dissidents, quelques résistants du
monde de l'opensource, ces illuminés qui ne comprenaient pas notre
vision. En général une descente de flics chez ces derniers suffisait à
calmer leurs ardeurs.&lt;/p&gt;
&lt;p&gt;Oui, depuis quelques années, notre influence diminuait, et les foules
qui s'ammassaient sur nos trajets avaient l'air moins craintives, plus
hargneuses et revendicatrices.&lt;/p&gt;
&lt;p&gt;Oui, j'ai entendu parler il y a quelques mois d'un langage qui
persistait, et son nom aurait dû me mettre la puce à l'oreille : Boa.&lt;/p&gt;
&lt;p&gt;Boa.&lt;/p&gt;
&lt;p&gt;Voilà un nom étrangement évocateur. Il paraît que son auteur est
Hollandais, et qui plus est, barbu. On m'a même dit que ce langage
n'utilisait ni accolade, ni même de point-virgule ! Sacrilège !&lt;/p&gt;
&lt;p&gt;Nous aurions dû nous en inquiéter, nous aurions dû préparer la relève
il y a bien longtemps, une relève forte qui nous aurait sorti de ce
mauvais pas.&lt;/p&gt;
&lt;p&gt;Ça a recommencé... mon infirmière, au détour d'une conversation, m'a
dit hier ces quelques mots, d'apparence anodine, mais qui résonnent
depuis dans ma tête. Ces quelques mots annonciateurs du retour du grand
mal :&lt;/p&gt;
&lt;p&gt;&amp;quot;j'ai une super idée, et je vais demander à mon neveu de me la
programmer&amp;quot;.&lt;/p&gt;
</summary><category term="polargeek"></category></entry><entry><title>La bidouille django du jour: appeller un templatetag depuis un autre templatetag</title><link href="http://mathieu.agopian.info/blog/la-bidouille-django-du-jour-appeller-un-templatetag-depuis-un-autre-templatetag.html" rel="alternate"></link><updated>2011-05-11T11:59:00Z</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2011-05-11:/blog/la-bidouille-django-du-jour-appeller-un-templatetag-depuis-un-autre-templatetag.html/</id><summary type="html">&lt;p&gt;Cet article n'est pas écrit par Mathieu. Enfin si, mais par &lt;a class="reference external" href="http://virgule.net/"&gt;un autre Mathieu&lt;/a&gt;, qui n'a pas de blog en ce moment et abuse^Wprofite
de la gentillesse du maître de ces lieux pour poster ici.&lt;/p&gt;
&lt;div class="section" id="introduction"&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Avez vous toujours rêvé d'appeler un templatetag Django depuis un autre
templatetag ? Allez, avouez-le : dans vos rêves les plus fous, vous
aimeriez pouvoir faire:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;#64;register.simple_tag
def my_templatetag():
    if condition:
        return tag1()
    else:
        return tag2()
&lt;/pre&gt;
&lt;p&gt;Ça marchera si vos tag1 et tag2 sont des simple_tags, vu que c'est
directement une chaîne de caractères qui est retournée. Là où ça
commence à se compliquer, c'est si vous voulez que ça marche avec
n'importe quel templatetag, et notamment les inclusion_tags. En effet,
en appelant un inclusion_tag &amp;quot;à la main&amp;quot;, vous vous retrouvez avec
juste le dictionnaire qu'il retourne comme contexte, au lieu d'avoir le
&amp;quot;vrai&amp;quot; résultat.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="zut-mais-comment-faire-alors"&gt;
&lt;h2&gt;Zut, mais comment faire alors ?&lt;/h2&gt;
&lt;p&gt;Pour comprendre comment faire pour contourner ce problème, il faut
regarder comment un templatetag fonctionne. Quand vous déclarez un
inclusion_tag ou un tag classique, que se passe-t-il ?&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Tout d'abord, via le décorateur &lt;tt class="docutils literal"&gt;&amp;#64;register.tag&lt;/tt&gt; ou
&lt;tt class="docutils literal"&gt;&amp;#64;register.inclusion_tag&lt;/tt&gt;, votre templatetag est enregistré dans la
variable register, que vous avez dû déclarer de la sorte:
&lt;tt class="docutils literal"&gt;register = Library()&lt;/tt&gt;.&lt;/li&gt;
&lt;li&gt;Si on inspecte cette fameuse &lt;tt class="docutils literal"&gt;Library&lt;/tt&gt;, on se rend compte que
chaque tag s'enregistre dans un dictionnaire, dans l'attribut tags.
Ce dictionnaire contient la fonction permettant de générer l'instance
de &lt;tt class="docutils literal"&gt;Node&lt;/tt&gt; correspondant à votre tag. Dans le cas d'un
inclusion_tag, il y a un peu de complexité qui vous est planquée, vu
que, contrairement à l'enregistrement d'un tag &amp;quot;classique&amp;quot; via
&lt;tt class="docutils literal"&gt;&amp;#64;register.tag&lt;/tt&gt;, vous ne déclarez pas de sous-classe de &lt;tt class="docutils literal"&gt;Node&lt;/tt&gt;,
et vous ne jouez pas non plus avec un parser de tokens.&lt;/li&gt;
&lt;li&gt;Au moment où le template rencontre votre templatetag, il récupère la
fonction à appeler dans l'instance de Library qui va bien, et
instancie le &lt;tt class="docutils literal"&gt;Node&lt;/tt&gt; avec &lt;tt class="docutils literal"&gt;parser&lt;/tt&gt; (l'instance du &lt;tt class="docutils literal"&gt;Parser&lt;/tt&gt;) et
&lt;tt class="docutils literal"&gt;token&lt;/tt&gt; (l'instance de &lt;tt class="docutils literal"&gt;Token&lt;/tt&gt; correspondant à tout ce qui était
entre {% et %})&lt;/li&gt;
&lt;li&gt;Ensuite, au moment d'afficher votre template, la méthode &lt;tt class="docutils literal"&gt;render()&lt;/tt&gt;
de chaque &lt;tt class="docutils literal"&gt;Node&lt;/tt&gt; est appelée. Celle-ci reçoit notamment le contexte
courant.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Du coup, la grosse bidouille consiste simplement à faire la même chose
que Django fait. Ça donne ça:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;#64;register.tag
def my_templatetag(parser, token):
    # On suppose que vous êtes dans le même module, sinon il faut aller chercher le register
    # qui va bien dans un autre module python
    if condition:
        return register.tags['tag1'](parser, token)
    else:
        return register.tags['tag2'](parser, token)
&lt;/pre&gt;
&lt;p&gt;La beauté du truc, c'est que vous enregistrez un tag normal, mais quand
il est appelé, il utilise une autre instance de &lt;tt class="docutils literal"&gt;Node&lt;/tt&gt;, qui provient
d'un de ses copains.&lt;/p&gt;
&lt;div class="section" id="gestion-des-parametres"&gt;
&lt;h3&gt;Gestion des paramètres&lt;/h3&gt;
&lt;p&gt;Il reste cependant un écueil : comment gérer les paramètres ? C'est en
fait très facile: il faut modifier le &lt;tt class="docutils literal"&gt;token&lt;/tt&gt; qu'on passe pour tromper
Django. Et c'est là où la bidouille est un peu crade, vous devez faire
comme si on appelait le templatetag depuis le template:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;#64;register.tag
def my_templatetag(parser, token):
    # On suppose que vous êtes dans le même module, sinon il faut aller chercher le register
    # qui va bien dans un autre module python
    if condition:
        token.contents = 'tag1 argument1 argument2'
        return register.tags['tag1'](parser, token)
    else:
        token.contents = 'tag2 argument1 argument2 argument3'
        return register.tags['tag2'](parser, token)
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Et voilà ! Je vous conseille, si vous utilisez cette technique, même si
vous n'avez pas besoin d'arguments, de toujours modifier
&lt;tt class="docutils literal"&gt;token.contents&lt;/tt&gt;, histoire d'être sûr.&lt;/p&gt;
&lt;p&gt;On peut encore pousser le vice plus loin et gérer des paramètres dans
notre templatetag 'parent', mais ça suffit pour aujourd'hui :)&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
</summary></entry><entry><title>Contribuer à Django, premiers pas (patcher la doc)</title><link href="http://mathieu.agopian.info/blog/contribuer-a-django-premiers-pas-patcher-la-doc.html" rel="alternate"></link><updated>2011-04-30T17:46:00Z</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2011-04-30:/blog/contribuer-a-django-premiers-pas-patcher-la-doc.html/</id><summary type="html">&lt;p&gt;Ceci est le troisième article dans la série, les premiers étant&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="./contribuer-a-django-premiers-pas-revue-de-tickets.html"&gt;Contribuer à Django, premiers pas (revue de tickets)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="./contribuer-a-django-premiers-pas-les-outils-lenvironnement.html"&gt;Contribuer à Django, premiers pas (les outils, l'environnement)&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;On sait donc quoi faire d'un ticket, et on a tous les outils en place
pour y répondre, c'est parti pour une contribution à la documentation !&lt;/p&gt;
&lt;div class="section" id="choisir-son-ticket"&gt;
&lt;h2&gt;Choisir son ticket&lt;/h2&gt;
&lt;p&gt;Nous avons vu dans le &lt;a class="reference external" href="./contribuer-a-django-premiers-pas-revue-de-tickets.htm"&gt;premier article&lt;/a&gt; de la série qu'on peut trouver
les tickets soit sur la &lt;a class="reference external" href="http://code.djangoproject.com/wiki/Reports"&gt;page Reports de trac&lt;/a&gt;, soit sur le &lt;a class="reference external" href="http://dddash.ep.io/"&gt;super
dashbord&lt;/a&gt; que Jacob Kaplan-Moss vient de créer.&lt;/p&gt;
&lt;p&gt;Pour ma part, je me focalise pour le moment sur les tickets qui sont
&lt;em&gt;unreviewed&lt;/em&gt; (non revus, qui viennent d'être créés).&lt;/p&gt;
&lt;p&gt;N'oubliez pas qu'il est tout à fait possible, et même encouragé, de :&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;ne pas s'occuper de tickets pour lesquels on ne se sent pas à la
hauteur&lt;/li&gt;
&lt;li&gt;demander de l'aide sur le salon &lt;em&gt;#django-dev&lt;/em&gt; sur le serveur irc
&lt;em&gt;freenode&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;lancer des discussions pour avoir plus d'informations ou d'avis sur
la liste de diffusion &lt;a class="reference external" href="http://groups.google.com/group/django-developers/"&gt;*django-dev*&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nous allons, pour illustrer ce billet, examiner le ticket &lt;a class="reference external" href="http://code.djangoproject.com/ticket/15886"&gt;#15886&lt;/a&gt;
dont le titre est &amp;quot;Improve django.core.serializers.get_serializer()
docs&amp;quot;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="revendiquer-le-ticket"&gt;
&lt;h2&gt;Revendiquer le ticket&lt;/h2&gt;
&lt;p&gt;Comme indiqué dans la &lt;a class="reference external" href="http://docs.djangoproject.com/en/dev/internals/contributing/#claiming-tickets"&gt;doc sur la contribution&lt;/a&gt;, il est conseillé de
&lt;em&gt;claim&lt;/em&gt; (revendiquer) un ticket avant de travailler dessus, pour éviter
de se retrouver à plusieurs sur le même ticket en parallèle, et sans le
savoir. Vu le nombre de contributeurs potentiels, il y a un risque que
quelqu'un d'autre soit déjà en train de travailler sur un patch.&lt;/p&gt;
&lt;p&gt;Revendiquer le ticket se fait très simplement en sélectionnant
&lt;em&gt;accept&lt;/em&gt;, tout en bas de la page de modification d'un ticket.&lt;/p&gt;
&lt;p&gt;Il est par contre inutile de revendiquer un ticket pour les cas les
plus simples, ne nécessitant que quelques minutes de travail. Il suffit
alors de soumettre le patch directement, et c'est ce que j'ai fait pour
l'exemple choisi pour illustrer cet article.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="premiere-etape-faire-une-revue-du-ticket"&gt;
&lt;h2&gt;Première étape : faire une revue du ticket&lt;/h2&gt;
&lt;p&gt;Comme vous pouvez le constater, j'ai revu le ticket, et j'ai posté un
commentaire conseillant de créer un autre ticket séparé pour n'avoir
qu'un seul problème à résoudre dans celui-ci. Je n'ai pas changé l'état
du ticket, n'étant pas certain de ce qu'il fallait en faire.&lt;/p&gt;
&lt;p&gt;Il faut noter que toute contribution (même un simple commentaire) peut
être utile et précieuse. Dans ce cas précis, il a servi à faire créer un
nouveau ticket, et à initier la discussion.&lt;/p&gt;
&lt;p&gt;Dans ce cas particulier, Jacob a passé le ticket en &lt;em&gt;accepted&lt;/em&gt;,
vraisemblablement parce qu'il ne restait pas d'objection : le problème
identifié dans le ticket (le manque de documentation) est maintenant
clairement séparé de la demande de nouvelle fonctionnalité, et le
&amp;quot;composant&amp;quot; concerné a bien été mis à jour.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="deuxieme-etape-proposer-un-patch"&gt;
&lt;h2&gt;Deuxième étape : proposer un patch&lt;/h2&gt;
&lt;p&gt;Et pour ça, rien de bien compliqué. Dans le &lt;a class="reference external" href="./contribuer-a-django-premiers-pas-les-outils-lenvironnement.html"&gt;précédent article&lt;/a&gt;, nous
avons vu comment mettre en place un projet brouillon, et surtout comme
cloner le code source de Django, et compiler sa documentation.&lt;/p&gt;
&lt;div class="section" id="modifier-la-documentation"&gt;
&lt;h3&gt;Modifier la documentation&lt;/h3&gt;
&lt;p&gt;Pour cela, il faut déjà repérer la page concernée dans la documentation
de Django. Toujours en nous référant au ticket #15886 de l'exemple, un
gentil contributeur a déjà apporté un commentaire, et indiqué le lien
vers la page
&lt;a class="reference external" href="http://docs.djangoproject.com/en/1.3/topics/serialization/"&gt;http://docs.djangoproject.com/en/1.3/topics/serialization/&lt;/a&gt; (oui, ça
tombe bien, ce gentil contributeur, c'était moi ;)).&lt;/p&gt;
&lt;p&gt;Si on inspecte l'url, on peut voir qu'il s'agit de la page
&amp;quot;serialization&amp;quot; dans la catégorie &amp;quot;topics&amp;quot;. Le fichier correspondant,
dans le code source de Django, se trouve donc logiquement dans&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ cd ~/projects/django/docs
$ vi serialization.txt
&lt;/pre&gt;
&lt;p&gt;Dans mon cas, j'utilise le &lt;a class="reference external" href="http://en.wikipedia.org/wiki/Editor_war"&gt;meilleur des éditeur&lt;/a&gt;, mais vous avez bien
entendu le choix des armes ;)&lt;/p&gt;
&lt;p&gt;Une fois le bon endroit localisé pour apporter la clarification
demandée dans le ticket, il suffit de rajouter quelques lignes, en
utilisant son meilleur anglais.&lt;/p&gt;
&lt;p&gt;Il est à ce propos fortement conseillé d'avoir au moins quelques
notions de &lt;a class="reference external" href="http://sphinx.pocoo.org/"&gt;sphinx&lt;/a&gt;, l'outil utilisé pour générer la doc, et connaître
les ajouts apportés pour Django dans la page sur la &lt;a class="reference external" href="https://docs.djangoproject.com/en/dev/internals/contributing/writing-documentation/"&gt;contribution à la
documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="compiler-la-documentation"&gt;
&lt;h3&gt;Compiler la documentation&lt;/h3&gt;
&lt;p&gt;Nous l'avons vu dans le précédent article, il suffit pour celà
d'utiliser la commande suivante :&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ make html
&lt;/pre&gt;
&lt;p&gt;Si il y a le moindre problème de syntaxe &lt;em&gt;reStructuredText&lt;/em&gt; vous le
verrez lors de l'exécution de cette commande. Il suffit alors de
consulter le résultat sur
&lt;a class="reference external" href="file:///chemin/vers/projects/django/docs/_build/html/index.html"&gt;file:///chemin/vers/projects/django/docs/_build/html/index.html&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="creer-le-patch"&gt;
&lt;h3&gt;Créer le patch&lt;/h3&gt;
&lt;p&gt;Si vous avez suivi la méthode proposé dans le précédent article, vous
avez le cloné le miroir &lt;em&gt;git&lt;/em&gt; de Django, et pouvez donc utilisez la
méthode ultra simple suivante :&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;créer une branche de développement pour y stocker les modifications à
apporter : &lt;tt class="docutils literal"&gt;$ git checkout &lt;span class="pre"&gt;-b&lt;/span&gt; dev&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;apporter les modifications nécessaires&lt;/li&gt;
&lt;li&gt;&lt;em&gt;commit&lt;/em&gt; les changements régulièrement, et retourner en 2. tant que
c'est nécessaire : &lt;tt class="docutils literal"&gt;$ git commit &lt;span class="pre"&gt;-a&lt;/span&gt; &lt;span class="pre"&gt;-m&lt;/span&gt; &amp;quot;un message de commit&amp;quot;&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;une fois fini, créer le patch :
&lt;tt class="docutils literal"&gt;$ git diff master &amp;gt; nom_de_mon_patch.diff&lt;/tt&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Il est très fortement recommandé de créer un patch avec l'extension
&lt;em&gt;.diff&lt;/em&gt; pour qu'il soit correctement traité et affiché par &lt;em&gt;trac&lt;/em&gt;, et de
l'attacher au ticket (au lieu de pointer vers un &lt;em&gt;diff&lt;/em&gt; ou un &lt;em&gt;pull
request&lt;/em&gt; sur github ou autre), pour simplifier au maximum la dure et
(déjà) fastidieuse tâche des &lt;em&gt;core developpers&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Et surtout ne pas oublier de rajouter le flag &amp;quot;has patch&amp;quot; !&lt;/p&gt;
&lt;p&gt;Pour reprendre notre exemple, vous pouvez &lt;a class="reference external" href="http://code.djangoproject.com/attachment/ticket/15886/get_serializer_key_error_doc.diff"&gt;visualiser le patch final&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="attendre-la-validation-de-la-communaute"&gt;
&lt;h3&gt;Attendre la validation de la communauté&lt;/h3&gt;
&lt;p&gt;Dans ce cas précis, j'étais inquiet de la dépendance qu'il pouvait y
avoir sur le ticket &lt;a class="reference external" href="http://code.djangoproject.com/ticket/15889"&gt;#15889&lt;/a&gt;, qui est le ticket créé en parallèle pour
la demande d'ajout de fonctionnalité (renvoyer une exception spécifique
au lieu de &lt;em&gt;KeyError&lt;/em&gt;). En effet, si le ticket #15889 était intégré dans
Django (avec la documentation associée), avant le ticket #15886, il
résulterait un problème de cohérence, avec deux parties de la même
documentation indiquant une levée d'exception différente.&lt;/p&gt;
&lt;p&gt;J'ai donc soulevé la question sur le salon irc &lt;em&gt;#django-dev&lt;/em&gt;, et la
réaction a été rapide : Alex a eu la gentillesse de répondre à mes
craintes en fermant le ticket comme étant un duplicat du ticket #15889.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="bah-oui-mais-mon-patch-alors"&gt;
&lt;h3&gt;Bah oui, mais mon patch alors ?&lt;/h3&gt;
&lt;p&gt;Et là, j'ai envie de répondre : &amp;quot;&lt;em&gt;C'est le jeu, ma pov' Lucette&lt;/em&gt;&amp;quot; !&lt;/p&gt;
&lt;p&gt;Plus sérieusement, peu importe le temps passé (dans ce cas, vraiment
minime) sur un ticket, au final le but est de&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;rendre Django meilleur&lt;/li&gt;
&lt;li&gt;apporter sa modeste contribution si nécessaire&lt;/li&gt;
&lt;li&gt;faciliter au maximum la tâche des &lt;em&gt;core devs&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;apprendre le plus possible au passage quand l'opportunité se présente&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="astuce-se-rajouter-dans-les-destinataires"&gt;
&lt;h2&gt;Astuce : se rajouter dans les destinataires&lt;/h2&gt;
&lt;p&gt;Il suffit pour cela de cocher la case &lt;em&gt;add to cc&lt;/em&gt; en bas du ticket, et
de sauvegarder. Si vous êtes enregistré et connecté, votre adresse mail
devrait être automatiquement ajoutée dans la liste des personnes qui
seront en copie du mail envoyé lors de chaque modification du ticket :
commentaire, changement d'état...&lt;/p&gt;
&lt;p&gt;L'intérêt, en plus de recevoir les mails sur les tickets qui nous
concernent, est de pouvoir faire un filtre personnalisé pour &lt;a class="reference external" href="http://code.djangoproject.com/query?status=assigned&amp;amp;status=closed&amp;amp;status=new&amp;amp;status=reopened&amp;amp;cc=~mathieu.agopian&amp;amp;col=changetime&amp;amp;col=id&amp;amp;col=summary&amp;amp;col=status&amp;amp;col=owner&amp;amp;col=type&amp;amp;col=milestone&amp;amp;desc=1&amp;amp;order=changetime"&gt;afficher
tous ces tickets&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Avec ce genre de requête personnalisée, il est beaucoup plus facile de
suivre l'évolution de &amp;quot;ses&amp;quot; tickets, et de pouvoir utiliser &lt;a class="reference external" href="http://groups.google.com/group/django-developers/browse_thread/thread/abc6cf0450812d82"&gt;l'offre
5-for-1&lt;/a&gt; !&lt;/p&gt;
&lt;/div&gt;
</summary></entry><entry><title>Sud Web, c'est bon pour ton web</title><link href="http://mathieu.agopian.info/blog/sud-web-cest-bon-pour-ton-web.html" rel="alternate"></link><updated>2011-04-27T09:30:00Z</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2011-04-27:/blog/sud-web-cest-bon-pour-ton-web.html/</id><summary type="html">&lt;p&gt;En un mot comme en cent :&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://sudweb.fr"&gt;&lt;img alt="Sud Web, 27 mai à Nîmes : Savoir Faire et Faire Savoir" src="images/sudweb.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Et toi, cher lecteur ? As-tu pensé à &lt;a class="reference external" href="http://sudweb.fr/pages/Inscription"&gt;réserver ta place&lt;/a&gt; ? Dépêche-toi,
il ne reste plus que 5 jours !&lt;/p&gt;
&lt;div class="section" id="mais-c-est-quoi-sud-web"&gt;
&lt;h2&gt;Mais c'est quoi Sud Web ?&lt;/h2&gt;
&lt;p&gt;Sud Web est une conférence qui aura lieu le 27 mai 2011 à Nîmes, donc
dans le sud, et (normalement) au soleil. Elle se tiendra sur une journée
dans les locaux de l'Ecoles des Mines d'Alès (oui, à Nîmes ;)), et
rassemblera quelques orateurs et oratrices trié(e)s sur le volet, avec
des sujets répondant au slogan &amp;quot;Savoir-faire et faire-savoir&amp;quot;.&lt;/p&gt;
&lt;p&gt;Méthodologies, standards, de grands noms du Web (&lt;a class="reference external" href="http://sudweb.fr/post/Karl-Dubost"&gt;Karl Dubost&lt;/a&gt; pour ne
citer que le plus connu) seront là pour nous présenter des sujets allant
de la technique (&lt;a class="reference external" href="http://sudweb.fr/post/SVG-pour-les-designers-et-les-developpeurs"&gt;SVG pour les designers et les développeurs&lt;/a&gt;) à la
méthode (&lt;a class="reference external" href="http://sudweb.fr/post/Anatomie-d-une-mission-agile"&gt;Anatomie d'une mission agile&lt;/a&gt;) en passant par des sujets plus
vastes (&lt;a class="reference external" href="http://sudweb.fr/post/Le-web-et-l-efficience-de-l-intervention-humanitaire"&gt;Le web et l'efficience de l'intervention humanitaire&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Il y en a bien d'autres (y compris plusieurs lightning talks vraiment
sympathiques), tous listés pour votre plus grand plaisir sur la &lt;a class="reference external" href="http://sudweb.fr/category/Programme"&gt;page du
programme&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Mais il n'y a pas que le programme, bien entendu, qui fait le succès
d'une conférence, il y a aussi et surtout les rencontres, et c'est pour
ça que nous vous avons concocté une soirée communautaire qui promet
d'être riche en échanges, et ce dans un cadre très agréable.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="y-a-un-lien-avec-paris-web"&gt;
&lt;h2&gt;Y'a un lien avec Paris Web ?&lt;/h2&gt;
&lt;p&gt;Oui et non. Comment ça elle est inutile ma réponse ?&lt;/p&gt;
&lt;div class="section" id="oui"&gt;
&lt;h3&gt;Oui&lt;/h3&gt;
&lt;p&gt;Sud Web est parti d'une initiative de plusieurs passionnés du Web, qui
lors de leur dernière rencontre à Paris Web 2010, ont décidé de se
lancer dans l'aventure de créer et organiser une conférence Web, mais
cette fois-ci dans le sud.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="non"&gt;
&lt;h3&gt;Non&lt;/h3&gt;
&lt;p&gt;Les organisateurs (dont j'ai la chance de faire partie) ont vraiment
souhaité avoir une conférence (petite sœur de Paris Web), mais sur une
thématique résolument différente.&lt;/p&gt;
&lt;p&gt;Oui, la conférence va recevoir quelques-uns des orateurs qui ont déjà
présenté à Paris Web (sur d'autres sujets), et oui Sud Web est aussi
&lt;a class="reference external" href="http://www.w3.org/participate/otherevents/"&gt;soutenu par le W3C&lt;/a&gt;, mais non, ce n'est pas une copie conforme avec
(probablement) le soleil en plus et (espérons) les grèves en moins.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="autre-chose"&gt;
&lt;h2&gt;Autre chose ?&lt;/h2&gt;
&lt;p&gt;Oui, plusieurs autres choses d'ailleurs, et en vrac :&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;une conférence &lt;a class="reference external" href="http://nowificonferences.com/fr"&gt;no-wifi&lt;/a&gt; comme lors de &lt;a class="reference external" href="http://rencontres.django-fr.org/2011/"&gt;Djangocong&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;un débat en fin de journée sur la question &lt;a class="reference external" href="http://sudweb.fr/post/Ou-va-le-Web"&gt;Où va le Web&lt;/a&gt; animé par
&lt;a class="reference external" href="http://sudweb.fr/post/Marc-Lipskier"&gt;Marc Lipskier&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;des goodies&lt;/li&gt;
&lt;li&gt;des repas compris dans le prix&lt;/li&gt;
&lt;li&gt;la chance pour moi de revoir &lt;a class="reference external" href="http://david.larlet.fr"&gt;David Larlet&lt;/a&gt;, &lt;a class="reference external" href="http://j-mad.com/blog/"&gt;Jean-Michel Armand&lt;/a&gt;,
&lt;a class="reference external" href="http://blog.akei.com/"&gt;Nicolas Perriault&lt;/a&gt;, &lt;a class="reference external" href="http://sudweb.fr/post/Mathieu-Pillard"&gt;Mathieu Pillard&lt;/a&gt; et &lt;a class="reference external" href="http://sudweb.fr/post/Nicolas-Dubois"&gt;Nicolas Dubois&lt;/a&gt; que
j'ai vus il y a peu à &lt;a class="reference external" href="http://rencontres.django-fr.org/2011/"&gt;Djangocong&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion ?&lt;/h2&gt;
&lt;p&gt;Moi j'y vais, et toi donc ? &lt;a class="reference external" href="http://sudweb.fr/pages/Inscription"&gt;Vite vite il ne reste plus que quelques jours !&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
</summary><category term="conference"></category></entry><entry><title>Contribuer à Django, premiers pas (les outils, l'environnement)</title><link href="http://mathieu.agopian.info/blog/contribuer-a-django-premiers-pas-les-outils-lenvironnement.html" rel="alternate"></link><updated>2011-04-25T13:52:00Z</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2011-04-25:/blog/contribuer-a-django-premiers-pas-les-outils-lenvironnement.html/</id><summary type="html">&lt;p&gt;Ceci est le deuxième article dans la série, le premier étant &lt;a class="reference external" href="./contribuer-a-django-premiers-pas-revue-de-tickets.html"&gt;Contribuer à Django, premiers pas (revue de tickets)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Maintenant qu'on a compris le &lt;em&gt;flow&lt;/em&gt; entre les différents états de
tickets, et les &lt;em&gt;flags&lt;/em&gt; associés, passons aux choses sérieuses : la
soumission d'un patch !&lt;/p&gt;
&lt;p&gt;Mais avant de pouvoir soumettre un patch, il faut avoir son
environnement prêt, c'est à dire une version récente du &lt;em&gt;trunk&lt;/em&gt;, un
projet brouillon et une application de test, et enfin les différentes
dépendances nécessaires.&lt;/p&gt;
&lt;p&gt;La démarche expliquée ici utilise &lt;em&gt;git&lt;/em&gt;, qui semble être la méthode la
plus répandue (autre que &lt;em&gt;svn&lt;/em&gt;, que je connais moins bien).&lt;/p&gt;
&lt;div class="section" id="creer-son-virtualenv"&gt;
&lt;h2&gt;Créer son virtualenv&lt;/h2&gt;
&lt;p&gt;Tout le monde sait comment créer un &lt;a class="reference external" href="http://www.virtualenv.org/en/latest/index.html"&gt;virtualenv&lt;/a&gt; de nos jours,
n'est-ce pas ? Pour ceux qui ne connaissent pas encore cet excellent
outil, je leur conseille vivement de s'y intéresser, ainsi qu'à
&lt;a class="reference external" href="http://www.pip-installer.org/en/latest/index.html"&gt;pip&lt;/a&gt;, et à &lt;a class="reference external" href="http://www.doughellmann.com/projects/virtualenvwrapper/"&gt;virtualenvwrapper&lt;/a&gt; pour les fainéant (et &lt;a class="reference external" href="http://mathieu.agopian.info/djangocong/dplf.html"&gt;la fainéantise&lt;/a&gt;, &lt;a class="reference external" href="http://vimeo.com/11381846"&gt;c'est bien&lt;/a&gt;).&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ mkvirtualenv scratch
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="creer-son-projet"&gt;
&lt;h2&gt;Créer son projet&lt;/h2&gt;
&lt;p&gt;Il est &lt;a class="reference external" href="http://mathieu.agopian.info/djangocong/dplf.html"&gt;recommandé&lt;/a&gt; d'avoir un squelette de projet Django, pour
pouvoir démarrer plus rapidement, sans avoir à répéter sans cesse les
mêmes actions. Celui que j'utilise pour mes projets réels &lt;a class="reference external" href="https://bitbucket.org/magopian/django_base/overview"&gt;se trouve
ici&lt;/a&gt;, et je vous conseille d'avoir le votre, avec vos propres réglages
!&lt;/p&gt;
&lt;p&gt;Dans le cas de la contribution à Django, j'ai créé un autre squelette,
&lt;a class="reference external" href="https://bitbucket.org/magopian/django_scratch"&gt;django_scratch&lt;/a&gt;, qui n'a pas d'autres applications installées que
celles par défaut, en rajoutant l'admin et une application de test vide.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ cd ~/projects/
$ hg clone https://magopian&amp;#64;bitbucket.org/magopian/django_scratch scratch
$ cd scratch
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="cloner-le-repository-de-django"&gt;
&lt;h2&gt;Cloner le repository de django&lt;/h2&gt;
&lt;p&gt;Il existe un miroir du &lt;em&gt;svn&lt;/em&gt; de Django &lt;a class="reference external" href="https://github.com/django/django"&gt;sur github&lt;/a&gt; que nous allons
utiliser, mais vu que je suis super sympa, je vous l'ai déjà mis dans
les &lt;em&gt;requirements&lt;/em&gt; de &lt;em&gt;pip&lt;/em&gt; :&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ pip install -r pip_requirements.txt
&lt;/pre&gt;
&lt;p&gt;Pour le même prix, &lt;em&gt;sphinx&lt;/em&gt; (qui dépends de &lt;em&gt;pygments&lt;/em&gt;) sera installé
par la même occasion, outil indispensable pour pouvoir contribuer à la
documentation.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="finaliser"&gt;
&lt;h2&gt;Finaliser&lt;/h2&gt;
&lt;p&gt;Vous avez normalement tout ce qu'il faut pour débuter votre carrière de
contributeur !&lt;/p&gt;
&lt;div class="section" id="une-base-de-donnee-sqlite"&gt;
&lt;h3&gt;Une base de donnée sqlite&lt;/h3&gt;
&lt;p&gt;Par défaut, c'est le moteur utilisé, car plus rapide pour les tests et
surtout beaucoup plus simple à configurer. Pour faciliter les choses, il
y a déjà &lt;em&gt;scratch.db.save&lt;/em&gt; qui est une base initialisée avec les tables
nécessaires à Django, et même un admin (login: admin, pass: admin).&lt;/p&gt;
&lt;p&gt;Pour l'utiliser, ou la ré-utiliser pour revenir à la configuration de
base :&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ cp scratch.db.save scratch.db
&lt;/pre&gt;
&lt;p&gt;Il y a aussi &lt;em&gt;mysql_settings.py&lt;/em&gt; et &lt;em&gt;postgresql_settings.py&lt;/em&gt; qui sont
là pour les cas où le moteur de base de données est important et doit
être testé. Il vous suffit d'écraser &lt;em&gt;settings.py&lt;/em&gt; avec le fichier de
votre choix, et de créer un fichier &lt;em&gt;creds.py&lt;/em&gt; qui contient (à modifier
avec vos user/pass bien entendu) :&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# creds.py
MY_CREDS = {'user': 'foo', 'pass': 'bar'}
PG_CREDS = {'user': 'foo', 'pass': 'bar'}
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="une-application-de-test"&gt;
&lt;h3&gt;Une application de test&lt;/h3&gt;
&lt;p&gt;Cette application est constituée d'un modèle &lt;em&gt;Foo&lt;/em&gt; avec un seul champ
&lt;em&gt;name&lt;/em&gt;, l'&lt;em&gt;AdminModel&lt;/em&gt; qui va avec, un &lt;em&gt;urls.py&lt;/em&gt; et &lt;em&gt;views.py&lt;/em&gt; basiques.&lt;/p&gt;
&lt;p&gt;Le but est d'avoir le minimum nécessaire à la reproduction de la
majorité des bugs, et ce avec un minimum de travail. N'oubliez pas de
faire un &lt;em&gt;syncdb&lt;/em&gt; pour que le(s) modèle(s) soi(en)t créé(s) !&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="le-trunk-de-django"&gt;
&lt;h3&gt;Le trunk de Django&lt;/h3&gt;
&lt;p&gt;Il a été cloné directement dans le répertoire &lt;em&gt;src&lt;/em&gt; de votre
&lt;em&gt;virtualenv&lt;/em&gt;. Si vous avez suivi exactement tout ce qui a été indiqué
dans ce billet, vous devriez pouvoir créer un lien symbolique du
répertoire de Django à un endroit plus pratique et accessible (vous
allez devoir y modifier des fichiers régulièrement) :&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ ln -s ~/.virtualenvs/scratch/src/django ~/projects/
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="de-la-documentation-en-sphinx"&gt;
&lt;h3&gt;De la documentation en sphinx&lt;/h3&gt;
&lt;p&gt;Elle se trouve directement dans le répertoire &lt;em&gt;docs&lt;/em&gt; du &lt;em&gt;trunk&lt;/em&gt; de
Django, et pour la générer, rien de plus simple :&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ cd ~/projects/django/docs
$ make html
&lt;/pre&gt;
&lt;p&gt;S'ensuit la génération de tous les fichiers &lt;em&gt;html&lt;/em&gt; de la documentation
de Django, que vous pourrez visualiser et contrôler en pointant votre
navigateur sur le répertoire &lt;tt class="docutils literal"&gt;_build/html/&lt;/tt&gt; :&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="file:///chemin/vers/projects/django/docs/_build/html/index.html"&gt;file:///chemin/vers/projects/django/docs/_build/html/index.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Bon, et donc, quand est-ce qu'on contribue ? Nous verrons ça dans le
prochain article, promis !&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
</summary></entry><entry><title>Contribuer à Django, premiers pas (revue de tickets)</title><link href="http://mathieu.agopian.info/blog/contribuer-a-django-premiers-pas-revue-de-tickets.html" rel="alternate"></link><updated>2011-04-24T17:41:00Z</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2011-04-24:/blog/contribuer-a-django-premiers-pas-revue-de-tickets.html/</id><summary type="html">&lt;p&gt;Contribuer au framework web Django... tout un programme, et très
effrayant de prime abord. Depuis des années que j'utilise le framework,
je commence tout juste à y contribuer, et je peux vous assurer que
jusqu'à hier encore, je ne pensais pas être à la hauteur.&lt;/p&gt;
&lt;p&gt;Or, ce qu'il y a de génial, c'est que tout le monde est à la hauteur,
et que chacun peut apporter sa pierre à l'édifice ! La contribution la
plus simple consiste à faire la revue de nouveau tickets, et c'est très
utile : si vous ne me croyez pas, regardez ce qu'il est écrit :&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="http://groups.google.com/group/django-developers/browse_thread/thread/abc6cf0450812d82"&gt;Jacob Kaplan-Moss (Django BDFL) sur la mailing list django-dev&lt;/a&gt; :
&amp;quot;this is totally something anyone here can do&amp;quot;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://docs.djangoproject.com/en/dev/internals/contributing/#triage-by-the-general-community"&gt;Documentation officielle Django page &amp;quot;contributing&amp;quot;&lt;/a&gt; : &amp;quot;there’s a
lot that general community members can do to help the triage process&amp;quot;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://docs.djangoproject.com/en/dev/howto/contribute/#the-spirit-of-contributing"&gt;Documentation officielle de Django page &amp;quot;how contribue&amp;quot;&lt;/a&gt; : &amp;quot;Django
is a community project, and every contribution helps. We can’t do
this without YOU!&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Le cheminement logique et officiel pour devenir un contributeur Django
est :&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;être motivé pour donner un peu de temps en retour à la communauté qui
a fait un framework qui me fait vivre !&lt;/li&gt;
&lt;li&gt;se créer un compte sur le &lt;a class="reference external" href="http://www.djangoproject.com/accounts/register/"&gt;trac de Django&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;lire dans l'ordre (ou le désordre) les pages suivantes&lt;ul&gt;
&lt;li&gt;&lt;a class="reference external" href="http://docs.djangoproject.com/en/dev/howto/contribute/"&gt;comment contribuer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://docs.djangoproject.com/en/dev/internals/contributing/"&gt;référence sur la contribution&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://docs.djangoproject.com/en/1.3/faq/contributing/"&gt;FAQ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://docs.djangoproject.com/en/dev/internals/documentation/"&gt;comment contribuer de la doc&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Une fois qu'on a lu tout ça, on est prêts à choisir des tickets, soit
sur la &lt;a class="reference external" href="http://code.djangoproject.com/wiki/Reports"&gt;page Reports de trac&lt;/a&gt;, soit sur le &lt;a class="reference external" href="http://dddash.ep.io"&gt;super dashbord&lt;/a&gt; que Jacob
Kaplan-Moss vient de créer.&lt;/p&gt;
&lt;div class="section" id="mais-c-est-trop-long-de-tout-lire"&gt;
&lt;h2&gt;Mais c'est trop long de tout lire !&lt;/h2&gt;
&lt;p&gt;Effectivement, il y a beaucoup de ressources, et toutes ne sont pas
indispensables pour démarrer.&lt;/p&gt;
&lt;p&gt;Pour vraiment débuter, je pense qu'il est indispensable de lire le
premier document sur &lt;a class="reference external" href="http://docs.djangoproject.com/en/dev/howto/contribute/"&gt;comment contribuer&lt;/a&gt; qui a l'avantage d'être assez
concis. Il faut ensuite comprendre les différentes étapes d'un ticket,
qui sont indiquées sur le deuxième document, dans la section &lt;a class="reference external" href="http://docs.djangoproject.com/en/dev/internals/contributing/#ticket-triage"&gt;Ticket
Triage&lt;/a&gt;, avec une image très claire.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="mais-c-est-trop-complique-je-suis-pas-assez-experimente-en-django"&gt;
&lt;h2&gt;Mais c'est trop compliqué, je suis pas assez expérimenté en Django !&lt;/h2&gt;
&lt;p&gt;Il n'est pas nécessaire d'être un expert pour pouvoir faire de la revue
de tickets. Si vous voyez un ticket que vous pensez pouvoir traiter,
faites-le, sinon passez au suivant!&lt;/p&gt;
&lt;p&gt;Les tickets qui sont en état &lt;em&gt;unreviewed&lt;/em&gt; peuvent être fermés, acceptés
ou passés en &amp;quot;besoin d'une décision de design&amp;quot; :&lt;/p&gt;
&lt;div class="section" id="ticket-ferme-avec-les-statuts-suivants"&gt;
&lt;h3&gt;Ticket fermé, avec les statuts suivants :&lt;/h3&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;strong&gt;invalid&lt;/strong&gt; si ce n'est ni un bug, ni une demande d'amélioration,
mais une question de support ou sur autre chose que django&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;duplicate&lt;/strong&gt; si le même bug ou demande d'amélioration a déjà été
signalé. Pour le savoir, il suffit de faire une recherche dans la
liste des tickets, puis de fermer le ticket en cours en indiquant que
c'est un duplicat du ticket #xxxx&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;worksforme&lt;/strong&gt; si vous avez pu tester que ça marche pour vous, avec
les informations fournies par le créateur du ticket&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;needsinfo&lt;/strong&gt; si il manque des informations pour pouvoir reproduire
le bug, mais que le bug a l'air d'être réel&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Les autres statuts (&lt;strong&gt;fixed&lt;/strong&gt; et &lt;strong&gt;wontfix&lt;/strong&gt;) ne concernent que les
&lt;em&gt;core developers&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Dans tous les cas, lors de la fermeture d'un ticket, il est primordial
de rester poli et agréable, en signalant le problème au créateur du
ticket, et lui indiquant qu'il peut rouvrir le ticket si il fournit plus
d'informations, si on a mal compris le problème...&lt;/p&gt;
&lt;p&gt;Il faut garder à l'esprit que le créateur a lui aussi passé du temps
pour signaler le problème, et qu'il lui a fallut de la motivation pour
faire la démarche de créer ce ticket !&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="ticket-accepte-avec-les-indications-suivantes"&gt;
&lt;h3&gt;Ticket accepté, avec les indications suivantes :&lt;/h3&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;strong&gt;has patch&lt;/strong&gt; dans le cas où un patch est fourni (documentation ou
code)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;needs documentation&lt;/strong&gt; si il y a un patch pour une fonctionnalité,
mais qu'il n'y a pas la documentation qui doit aller avec&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;needs tests&lt;/strong&gt; si il y a un patch pour une fonctionnalité sans les
tests unitaires nécessaires, ou un patch correctif sans les tests de
non régression&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;patch needs improvement&lt;/strong&gt; si le patch de documentation n'est pas
assez clair ou si le correctif n'est pas acceptable tel quel
(possibilité d'améliorations, conformité aux conventions de
codage...)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;easy pickings&lt;/strong&gt; pour indiquer que ce ticket peut être facilement et
rapidement réglé, même par un débutant (apparaîtra dans une &lt;a class="reference external" href="http://code.djangoproject.com/query?status=!closed&amp;amp;easy=1&amp;amp;stage=Accepted&amp;amp;order=priority"&gt;liste personnalisée&lt;/a&gt; accessible sur la &lt;a class="reference external" href="http://code.djangoproject.com/wiki/Reports"&gt;page report de trac&lt;/a&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="ticket-passe-en-design-decision-needed"&gt;
&lt;h3&gt;Ticket passé en &amp;quot;design decision needed&amp;quot;&lt;/h3&gt;
&lt;p&gt;C'est le cas notamment des tickets qui proposent des améliorations du
framework, ou des corrections de &amp;quot;bug&amp;quot; qui n'en sont pas forcément, ou
qui pourraient avoir plusieurs corrections possibles. Une fois le ticket
passé en &lt;em&gt;DDN&lt;/em&gt;, un des &lt;em&gt;core developers&lt;/em&gt; pourra le passer en &lt;strong&gt;wontfix&lt;/strong&gt;
ou en &lt;strong&gt;accepted&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="et-je-peux-changer-le-statut-de-mes-propres-tickets-alors"&gt;
&lt;h3&gt;Et je peux changer le statut de mes propres tickets alors ?&lt;/h3&gt;
&lt;p&gt;Non.&lt;/p&gt;
&lt;p&gt;Pour une réponse plus longue : il vaut toujours mieux au moins une
validation externe pour être sûr qu'on a pas oublié quelque chose, ou
mal compris la doc, ou fait une typo, ou ...&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="trop-long-j-ai-pas-lu"&gt;
&lt;h2&gt;Trop long, j'ai pas lu&lt;/h2&gt;
&lt;p&gt;C'est bien dommage. Il faut tout de même un minimum de motivation pour
rendre un peu à la communauté de ce qu'elle nous apporte.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="j-ai-lu-mais-tu-veux-bien-resumer"&gt;
&lt;h2&gt;J'ai lu, mais tu veux bien résumer ?&lt;/h2&gt;
&lt;p&gt;Pour débuter et commencer à contribuer, il est très facile de faire de
la revue de tickets, c'est-à-dire relire et (in)valider des tickets qui
viennent d'être créés.&lt;/p&gt;
&lt;p&gt;Dans certains cas, la validation (ou fermeture) du ticket ne
nécessitera même pas de test ou de reproduction. Pour les autres, il
vaut mieux lire le reste des documents que j'ai listés en début de cet
article, ou attendre que je fasse un résumé dans un prochain billet !&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="le-cadeau-bonux"&gt;
&lt;h2&gt;Le cadeau bonux&lt;/h2&gt;
&lt;p&gt;Jacob Kaplan-Moss a lancé &lt;a class="reference external" href="http://groups.google.com/group/django-developers/browse_thread/thread/abc6cf0450812d82"&gt;une idée&lt;/a&gt; pour motiver les revues de
tickets : 5 tickets revus, un ticket revu par un des &lt;em&gt;core developers&lt;/em&gt;
participant à l'opération (Jacob Kaplan-Moss, Alex Gaynor et Carl
Meyer).&lt;/p&gt;
&lt;p&gt;Donc si vous avez un ticket que vous aimeriez qu'un des &lt;em&gt;core dev&lt;/em&gt;
considère, vous savez ce qu'il vous reste à faire !&lt;/p&gt;
&lt;p&gt;Dans le prochain article, nous verrons &lt;a class="reference external" href="./contribuer-a-django-premiers-pas-les-outils-lenvironnement.html"&gt;comment mettre en place son
environnement&lt;/a&gt; pour pouvoir contribuer sans douleur !&lt;/p&gt;
&lt;/div&gt;
</summary></entry><entry><title>Djangocong 2011 : une cuvée d'exception</title><link href="http://mathieu.agopian.info/blog/djangocong-2011-une-cuvee-dexception.html" rel="alternate"></link><updated>2011-04-21T08:32:00Z</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2011-04-21:/blog/djangocong-2011-une-cuvee-dexception.html/</id><summary type="html">&lt;p&gt;La deuxième conférence Djangocong, organisée par nos cher président
(&lt;a class="reference external" href="http://david.larlet.fr/"&gt;David Larlet&lt;/a&gt;) et trésorier (&lt;a class="reference external" href="http://j-mad.com/blog/"&gt;Jean-Michel Armand&lt;/a&gt;) a eu lieu ce
week-end du 16 au 17 avril 2011, et a regroupé 75 personnes.&lt;/p&gt;
&lt;p&gt;Pour rappel, &lt;a class="reference external" href="http://rencontres.django-fr.org/2011/"&gt;Djangocong&lt;/a&gt; est la conférence sur, par et autour de la
communauté &lt;a class="reference external" href="http://www.djangoproject.com/"&gt;django&lt;/a&gt; &lt;a class="reference external" href="http://www.django-fr.org/"&gt;française&lt;/a&gt;. Le &amp;quot;g&amp;quot; final donne un accent
ensoleillé, les deux conférences ayant eu lieu à Marseille.&lt;/p&gt;
&lt;p&gt;Vous trouverez dans ce billet mon propre ressenti sur la conférence et
je me dois de vous prévenir : j'ai cette année encore apporté ma modeste
contribution à l'organisation sur place. Un &lt;a class="reference external" href="http://openetherpad.org/xhKlmTVfJ2"&gt;*etherpad*&lt;/a&gt; a été mis à
disposition pour regrouper les retours et remarques des présents.&lt;/p&gt;
&lt;div class="section" id="le-format"&gt;
&lt;h2&gt;Le format&lt;/h2&gt;
&lt;p&gt;Le format de cette année, sur l'initiative de David, était articulé
autour de trois temps forts :&lt;/p&gt;
&lt;div class="section" id="on-a-fait-nowificonfs"&gt;
&lt;h3&gt;On a fait (#nowificonfs)&lt;/h3&gt;
&lt;p&gt;Retours d'expérience de 12 minutes, ces présentations étaient beaucoup
plus courtes que ce qu'on peut voir en général. La durée relativement
courte a permis aux spectateurs de rester concentrés et attentifs. Par
contre, les orateurs ont pour la plupart dû sauter quelques diapos et
explications pour respecter les limites de durée.&lt;/p&gt;
&lt;p&gt;Format idéal pour des retours d'expériences, mais trop court si ça
avait été destiné à des présentations techniques ou de produits.&lt;/p&gt;
&lt;p&gt;Lors de cette première matinée, les organisateurs ont décidé
d'expérimenter en grandeur nature le &amp;quot;mouvement&amp;quot; lancé par la
&lt;a class="reference external" href="http://sudweb.fr"&gt;conférence Sud Web&lt;/a&gt; : les &lt;a class="reference external" href="http://nowificonferences.com/fr"&gt;conférences sans WiFi&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Le WiFi ayant été désactivé durant toute la durée des présentations,
j'ai pu compter en tout 4 ordinateurs ouverts, dont 2 pour des orateurs,
sur 75 présents. Pour référence, l'année dernière, sur environ 55
participants, il y avait en moyenne une vingtaines d'ordinateurs ouverts
et utilisés pendant les conférences.&lt;/p&gt;
&lt;p&gt;Les conséquences ont été multiples:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;plus grande attention apportée aux orateurs, mais surtout, plus grand
respect (difficile de s'adresser à des têtes baissées et des claviers
qui cliquettent)&lt;/li&gt;
&lt;li&gt;échanges humains plus poussés : j'ai remarqué une bien moins grande
utilisation de &lt;a class="reference external" href="https://twitter.com/#!/search/#djangocong"&gt;twitter&lt;/a&gt; par exemple, et plus de discussions
informelles (pendant les pauses, les repas, la soirée) sur les sujets
présentés, avec ou sans l'orateur d'ailleurs&lt;/li&gt;
&lt;li&gt;pas de &lt;em&gt;trending topics&lt;/em&gt; ni de retours à chaud, pas d'&lt;em&gt;etherpad&lt;/em&gt;
rempli à la volée pendant les conférences. Couplé à l'absence de
captation vidéo, cela exclu en grande partie les absents qui n'ont
finalement que les slides mis en ligne à postériori&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Vu que le WiFi a été réactivé le reste de la journée, ainsi que le
lendemain, les gens dépendants d'un accès à internet n'ont pas dû être
trop lésés.&lt;/p&gt;
&lt;p&gt;Idée : limiter l'accès internet à l'intérieur de l'amphi où se
déroulent les conférences, mais pas en dehors, par exemple en n'ayant
pas de WiFi, mais des connexions filaires. Ou alors ne pas limiter
l'accès à internet, mais demander aux spectateurs d'une conférence de ne
pas ouvrir leur laptops (pour prendre des notes, il y a le
papier+crayon!).&lt;/p&gt;
&lt;p&gt;Vous trouverez la liste des liens vers les supports de conférence sur
le &lt;a class="reference external" href="https://convore.com/django-fr/djangocong-2011-slides/"&gt;salon convore dédié&lt;/a&gt; et sur le &lt;a class="reference external" href="http://rencontres.django-fr.org/2011/"&gt;site de la rencontre&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="on-discute-barcamps"&gt;
&lt;h3&gt;On discute (barcamps)&lt;/h3&gt;
&lt;p&gt;Une grande première pour moi, et j'en ai été absolument ravi et
enthousiasmé. La possibilité de pouvoir échanger à plusieurs, au calme
dans une salle, sur un sujet précis, c'est vraiment inestimable.&lt;/p&gt;
&lt;p&gt;Ayant dû m'absenter pour aider Jean-Michel à préparer la salle pour le
soir, je n'ai pu pleinement participer qu'au &lt;em&gt;barcamp&lt;/em&gt; sur &amp;quot;Geeks sans
frontières&amp;quot;. Ce sera probablement le point de départ d'une communauté de
développeurs volontaires pour certains développements, soit d'utilité
publique (pour référence, certains avaient participé au sprint sur
&lt;a class="reference external" href="http://memopol2.lqdn.fr/"&gt;Memopol&lt;/a&gt;), soit dans les cas de force majeure (mise en place d'outils
pour trouver des maisons pour abriter des réfugiés suite à un tsunami ou
tremblement de terre par exemple).&lt;/p&gt;
&lt;p&gt;Il devrait bientôt y avoir une &lt;em&gt;mailing list&lt;/em&gt; pour les gens intéressés,
à suivre!&lt;/p&gt;
&lt;p&gt;Pour l'organisation des &lt;em&gt;barcamps&lt;/em&gt;, deux points d'amélioration pour la
prochaine édition : l'organisation un poil chaotique (trop organisée
selon certains sur l'&lt;em&gt;etherpad&lt;/em&gt;), et le manque de salles (l'amphi était
à chaque fois utilisé pour un des sujets, et vraiment pas adapté).&lt;/p&gt;
&lt;p&gt;J'ai aussi loupé les &lt;em&gt;lightning talks&lt;/em&gt; improvisés du samedi soir, je ne
pourrais donc pas vous en dire plus.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="on-fait-sprints"&gt;
&lt;h3&gt;On fait (sprints)&lt;/h3&gt;
&lt;p&gt;Après les discussions de la veille lors des &lt;em&gt;barcamps&lt;/em&gt;, un petit nombre
de sprints ont eu lieu le dimanche matin. Il y avait un grand nombre
d'absents, ce qui aurait été dommage et pénalisant pour un orateur si il
y avait eu des conférences.&lt;/p&gt;
&lt;p&gt;Il y a eu plusieurs sprints, dont un sur les &lt;a class="reference external" href="http://www.trunat.fr/djangocong/html/"&gt;bonnes pratiques pour des
applications réutilisables&lt;/a&gt;, un autre sur l'&lt;a class="reference external" href="https://github.com/magopian/django/commits/15667-template-widgets"&gt;intégration des
django-floppyforms&lt;/a&gt; dans la version 1.4 de django, et je suis sûr qu'il
en manque!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="les-repas-la-soiree"&gt;
&lt;h2&gt;Les repas, la soirée&lt;/h2&gt;
&lt;p&gt;Pour les chanceux ayant pu arriver le vendredi soir, il y a eu un
regroupement informel dans une crêperie. Ce fût un moment très agréable
et détendu, qui m'a permit de voir pour la première fois (et en avance!)
des gens que je côtoie depuis parfois des années sur internet.&lt;/p&gt;
&lt;p&gt;Les repas du midi (plateau repas le samedi, sandwich -délicieux- le
dimanche) ont été une excellente occasion de se regrouper dans l'herbe
au pied de l'école qui nous accueillait, et au soleil!&lt;/p&gt;
&lt;p&gt;Le seul petit regret que j'ai pu avoir, c'est de n'avoir pas pu
discuter assez (et assez longuement) avec les gens qui étaient là pour
la première fois, et qui (pour certains) avaient l'air d'être trop
timides pour s'intégrer aux différents petits groupes qui s'étaient
formés.&lt;/p&gt;
&lt;p&gt;Le samedi soir s'est déroulé à la &lt;a class="reference external" href="http://laboate.com/"&gt;Bo[a]te&lt;/a&gt; non loin du vieux port,
dans une ambiance vraiment détendue, et autour d'une énorme paella. La
encore, une occasion de pouvoir échanger, discuter, plaisanter, faire
mieux connaissance avec des personnes que je croise tous les jours sur
internet, et que je ne peux rencontrer en chair et en os qu'une ou deux
fois par an.&lt;/p&gt;
&lt;p&gt;N'ayant pas pu être présent au parc Longchamp le dimanche après-midi,
je n'ai pas eu la chance de voir les poneys ni de manger de la
barbapapa!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Pour moi, une édition encore meilleure que la première, et pourtant,
c'était pas évident, la première étant déjà excellente! Encore un
immense merci à Jean-Michel et David qui se sont décarcassés pour nous
concocter cette rencontre! Merci aussi à tous les &lt;a class="reference external" href="http://rencontres.django-fr.org/2011/"&gt;sponsors&lt;/a&gt; sans qui
nous n'aurions pas eu à manger, ni une salle aussi agréable pour le
samedi midi, ni une salle pour nous accueillir... et une mention
spéciale à Jean-Michel (&lt;a class="reference external" href="http://hybird.org/"&gt;Hybird&lt;/a&gt;) et David (&lt;a class="reference external" href="http://www.biologeek.com/"&gt;Biologeek&lt;/a&gt;) qui ont été
sponsors en plus d'organiser le tout!&lt;/p&gt;
&lt;p&gt;Je repars de cette rencontre avec un sentiment d'appartenance à une
communauté (comme joliment écrit par &lt;a class="reference external" href="https://twitter.com/#!/metamatik/status/59965746251448320"&gt;&amp;#64;metamatik&lt;/a&gt;), et la hâte d'être à
l'année prochaine, pour une édition qui sera sans aucun doute encore
meilleure! Et il me faut maintenant jeter un coup d’œil a quelques
solutions techniques qu'on m'a fortement recommandées et que je ne
connais pas encore : &lt;a class="reference external" href="https://github.com/dcramer/django-sentry"&gt;sentry&lt;/a&gt;, &lt;a class="reference external" href="http://www.peterbe.com/plog/gorun.py"&gt;go-run&lt;/a&gt;, &lt;a class="reference external" href="http://jenkins-ci.org/"&gt;jenkins&lt;/a&gt;, &lt;a class="reference external" href="http://integrityapp.com/"&gt;integrity&lt;/a&gt;.&lt;/p&gt;
&lt;div class="section" id="ils-en-parlent-sur-leur-blog"&gt;
&lt;h3&gt;Ils en parlent sur leur blog&lt;/h3&gt;
&lt;p&gt;J'ai créé un &lt;a class="reference external" href="https://convore.com/django-fr/djangocong-2011-les-blogs/"&gt;salon convore&lt;/a&gt; listant les billets des participants qui
font leur retour sur cette rencontre. N'hésitez pas à compléter!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
</summary><category term="mavie"></category><category term="conference"></category></entry><entry><title>django et le handler500: retourner une erreur 503</title><link href="http://mathieu.agopian.info/blog/django-et-le-handler500-retourner-une-erreur-503.html" rel="alternate"></link><updated>2011-04-14T17:04:00Z</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2011-04-14:/blog/django-et-le-handler500-retourner-une-erreur-503.html/</id><summary type="html">&lt;p&gt;Une mise en production ratée ? Un (local) settings oublié ? Un bug
inconnu jusqu'alors ?&lt;/p&gt;
&lt;p&gt;Dans les trois cas cités, il y a de fortes chances pour que vos
utilisateurs voient une erreur 500 (&lt;em&gt;internal server error&lt;/em&gt; : erreur
interne du serveur). Il est facile, en peaufinant son template
&lt;em&gt;500.html&lt;/em&gt; d'afficher un message d'erreur sympatique à l'utilisateur,
pour lui expliquer qu'il suffit de patienter, que vous êtes sur la
brèche, et que ce bug est en cours de résolution!&lt;/p&gt;
&lt;p&gt;En effet, votre serveur de production n'étant pas en &lt;em&gt;DEBUG = True&lt;/em&gt;
(hein, rassurez-moi), et si vous avez conservé le mécanisme par défaut
de django qui vous notifie des erreurs 500 par mail, vous êtes déjà au
courant du soucis.&lt;/p&gt;
&lt;p&gt;Seulement, si un &lt;em&gt;crawl bot&lt;/em&gt; (robot indexeur) passe par là, il va
tomber sur une page (ou plusieurs) qui ne fonctionne(nt) pas. Je ne sais
pas quel est l'impact sur le classement de votre site, mais ce qui est
sûr, c'est que ce genre d'erreur (ainsi que les erreurs 4xx) ne peut
avoir qu'un impact négatif.&lt;/p&gt;
&lt;p&gt;Heureusement, il existe le code d'erreur 503 (&lt;em&gt;service unavailable&lt;/em&gt; :
service indisponible) qui permet de spécifier un header &lt;em&gt;Retry-After&lt;/em&gt;
(réessayer après), avec une durée ou une date.&lt;/p&gt;
&lt;p&gt;Voici comment détourner le &lt;em&gt;handler500&lt;/em&gt; fournit par défaut par django,
pour renvoyer un code d'erreur 503, et le &lt;em&gt;header&lt;/em&gt; associé, dans notre
cas, un &lt;em&gt;Retry-After&lt;/em&gt; d'une heure :&lt;/p&gt;
&lt;div class="section" id="creer-son-propre-handler"&gt;
&lt;h2&gt;Créer son propre handler&lt;/h2&gt;
&lt;p&gt;Pour celà, créer par exemple le fichier &lt;em&gt;utils/handlers.py&lt;/em&gt; :&lt;/p&gt;
&lt;pre class="literal-block"&gt;
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from django import http
from django.views.decorators.csrf import requires_csrf_token
from django.template import Context, loader

class MyHttpResponseServerError(http.HttpResponse):
    status_code = 503

    def __init__(self, *args, **kwargs):
        http.HttpResponse.__init__(self, *args, **kwargs)
        self['Retry-After'] = '3600'

&amp;#64;requires_csrf_token
def handler500(request, template_name='500.html'):
    &amp;quot;&amp;quot;&amp;quot;Error handler that returns a 503 status code and a Retry-After header.

    Returning a 503 error code will tell crawl bots to retry later.

    &amp;quot;&amp;quot;&amp;quot;
    t = loader.get_template(template_name)
    return MyHttpResponseServerError(t.render(Context({})))
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="detourner-le-handler500-par-defaut"&gt;
&lt;h2&gt;Détourner le handler500 par défaut&lt;/h2&gt;
&lt;p&gt;Et maintenant, il suffit de spécifier dans son &lt;em&gt;ROOT URLCONF&lt;/em&gt; (le
fichier &lt;em&gt;urls.py&lt;/em&gt; à la racine du projet) :&lt;/p&gt;
&lt;pre class="literal-block"&gt;
handler500 = 'utils.handlers.handler500'
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Il est difficile de savoir si retourner un statut 503 est préférable à
un statut 500, en particulier sur le &lt;em&gt;ranking&lt;/em&gt; dans les moteurs de
recherche. Il y a bien &lt;a class="reference external" href="http://groups.google.com/group/google_webmaster_help-indexing/msg/bc49ca084f7e79c7"&gt;une réponse&lt;/a&gt; (approchante) d'un employé de
Google qui laisse penser qu'un 503 est recommandé.
Dans tous les cas, je trouve plus sympa et propre d'avoir une erreur
indiquant clairement que c'est un soucis temporaire, avec un ordre
d'idée du délai avant de réessayer de visiter cette url.&lt;/p&gt;
&lt;p&gt;Qu'en pensez-vous?&lt;/p&gt;
&lt;/div&gt;
</summary></entry><entry><title>La technique pomodoro : retour après plus d'un mois d'utilisation</title><link href="http://mathieu.agopian.info/blog/la-technique-pomodoro-retour-apres-plus-dun-mois-dutilisation.html" rel="alternate"></link><updated>2011-04-04T09:58:00Z</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2011-04-04:/blog/la-technique-pomodoro-retour-apres-plus-dun-mois-dutilisation.html/</id><summary type="html">&lt;p&gt;Ça y est, j'ai franchi la barre (fatidique?) de un mois d'utilisation de
ma nouvelle habitude, ce qui, selon certains, en fait une habitude
durable.&lt;/p&gt;
&lt;p&gt;Je me fends donc d'un billet, suite à l'article précédent: &lt;a class="reference external" href="./la-technique-pomodoro-retour-apres-deux-semaines-dutilisation.html"&gt;La
technique pomodoro : retour après deux semaines d’utilisation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Reprenons les points principaux de mon précédent billet:&lt;/p&gt;
&lt;div class="section" id="les-points-positifs"&gt;
&lt;h2&gt;Les points positifs&lt;/h2&gt;
&lt;div class="section" id="la-gestion-des-interruptions"&gt;
&lt;h3&gt;La gestion des interruptions&lt;/h3&gt;
&lt;p&gt;Un des deux points principaux dans mon utilisation de cette technique,
mais avec une petite modification : un pomodoro entammé m'aide à me
concentrer sur ma tâche en cours, et à ne pas être interrompu par toutes
les notifications &amp;quot;sociales sur le web&amp;quot; (mail, twitter, skype, irc...).
Par contre, je me suis rendu à l'évidence, elle ne me permet pas de me
protéger des interruptions réelles, telles que coups de téléphone, coup
de sonnette à la porte, ...&lt;/p&gt;
&lt;p&gt;Lors d'une interruption IRL, je distingue plusieurs cas:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;interruption pour le travail liée à ma tâche en cours : coup de
téléphone pour un retour de test où une question sur mon projet en
cours&lt;/li&gt;
&lt;li&gt;interruption pour le travail non liée à ma tâche en cours, mais très
courte et ne nécessitant pas de réflexion intellectuelle : coup de
téléphone me demandant si j'ai bien reçu un courrier, demande de
précision sur un autre projet...&lt;/li&gt;
&lt;li&gt;interruption non liée au travail&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Dans le premier cas, je n'interromps pas le pomodoro, voire même
j'enchaîne sur un autre pomodoro immédiatement après (en zappant la
pause) si nécessaire.&lt;/p&gt;
&lt;p&gt;Dans les deux derniers cas, je met le pomodoro en pause, et je le
reprends une fois l'interruption terminée&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="comptabiliser-le-temps-passe-aide-a-l-estimation-d-une-tache"&gt;
&lt;h3&gt;Comptabiliser le temps passé, aide à l'estimation d'une tâche&lt;/h3&gt;
&lt;p&gt;Avec le point précédent, le plus grand avantage que j'y trouve dans mon
utilisation de cette technique : j'ai regroupés les deux points, vu
qu'ils me semblent vraiment liés. Je tiens le compte du temps que j'ai
passé sur telle ou telle tâche, ce qui peut me servir pour un compte
rendu, ou par la suite pour ajuster mes estimations.&lt;/p&gt;
&lt;p&gt;Après plus d'un mois d'utilisation, je commence à voir les points sur
lesquels il faut que j'ajuste mes estimations, et ceux sur lesquels j'ai
assez d'expérience et de retour pour coller au plus proche de la
réalité.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="aide-a-lorganisation"&gt;
&lt;h3&gt;Aide à l’organisation&lt;/h3&gt;
&lt;p&gt;Même si, comme je l'indiquais précédemment, la technique pomodoro n'est
pas à proprement parler la technique GTD, elle me permet d'avoir une
liste de tâches à faire (à plus ou moins long terme), une sorte de
pense-bête, et une autre liste pour les tâches &amp;quot;en cours&amp;quot;, à réaliser
dans la journée ou à très court terme.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="les-points-negatifs"&gt;
&lt;h2&gt;Les points négatifs&lt;/h2&gt;
&lt;div class="section" id="les-25-minutes-de-pomodoro"&gt;
&lt;h3&gt;Les 25 minutes de Pomodoro&lt;/h3&gt;
&lt;p&gt;J'hésite beaucoup moins à présent à stopper la tâche en cours, qui me
demanderait &amp;quot;juste une minute de plus&amp;quot;, pour prendre ma pause, puis
enchaîner sur un nouveau pomodoro. En effet, un pomodoro effectué est
une victoire, pas un échec, cf ci-après.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="les-pauses"&gt;
&lt;h3&gt;Les pauses&lt;/h3&gt;
&lt;p&gt;J'en parlais, et je persiste et signe : les pauses sont importantes,
pour plusieurs raisons&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;savoir qu'on a une pause dans X minutes permet de garder sa
concentration sur la tâche en cours, et motive pour ne pas craquer et
succomber aux notifications et interruptions diverses&lt;/li&gt;
&lt;li&gt;une pause permet de souffler un peu, détendre les muscles de ses
épaules, jeter un oeil par la fenêtre, et par la même occasion faire
travailler les muscles occulaires&lt;/li&gt;
&lt;li&gt;combattre l'effet &amp;quot;tête dans le guidon&amp;quot; : je ne pense pas être le
seul dans ce cas. Lors d'un développement, il arrive qu'on se
focalise sur une solution, une implémentation, et une fois terminé,
on se rends compte qu'il existait une solution bien plus élégante
et/ou simple. Eh bien toutes les 30 minutes, on a une chance de s'en
rendre compte avant d'y avoir passé une journée !&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="les-interruptions"&gt;
&lt;h3&gt;Les interruptions&lt;/h3&gt;
&lt;p&gt;Voir les points positifs sur ce sujet : plutôt que de m'échiner à me
protéger des interruptions IRL, j'ai préféré faire avec et adapter la
technique à mes besoins.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="le-pomodoro-effectue-est-une-victoire-pas-un-echec"&gt;
&lt;h3&gt;Le pomodoro effectué est une victoire, pas un échec&lt;/h3&gt;
&lt;p&gt;L'avoir écrit m'a permit de le réaliser lors de la rédaction de mon
précédent billet. La méthode du &lt;a class="reference external" href="http://en.wikipedia.org/wiki/Rubber_duck_debugging"&gt;rubber ducking&lt;/a&gt; victorieuse à nouveau!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="les-points-a-travailler"&gt;
&lt;h2&gt;Les points à travailler&lt;/h2&gt;
&lt;p&gt;J'avais listé les points suivants:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;respect des pauses&lt;/li&gt;
&lt;li&gt;ressenti de la fin d'un pomodoro qui doit être positif&lt;/li&gt;
&lt;li&gt;gestion des interruptions&lt;/li&gt;
&lt;li&gt;utilisation de la technique en télé-travail&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Au vu de ce que j'ai écrit plus haut, je pense avoir trouvé la solution
qui me convenait pour chacun de ces points. Respect des pauses et
ressenti d'un pomodoro effectué allant de pair, et gestion des
interruptions &amp;quot;souple&amp;quot; qui me permet de ne pas gaspiller mon énergie à
aller à contre courant.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Cette technique, avec les quelques adaptations que j'y ai apportées
pour coller au mieux à ma méthode de travail, me convient parfaitement.
Je pense m'être un peu écarté de la philosophie de la technique
officielle, en n'annulant pas un pomodoro interrompu par une
interruption IRL, mais je suis content de pouvoir à présent mieux gérer
et suivre mon temps de travail effectif et productif.&lt;/p&gt;
&lt;p&gt;La gestion des interruptions &amp;quot;sociales&amp;quot; me permet aussi d'optimiser mon
temps de travail, et d'améliorer le rapport &amp;quot;travail effectif&amp;quot; / &amp;quot;temps
passé devant l'ordinateur&amp;quot;.&lt;/p&gt;
&lt;p&gt;Quelques chiffres:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;reccord de pomodoros dans une journée : 22&lt;/li&gt;
&lt;li&gt;reccord de pomodoros sur deux jours de week-end : 15&lt;/li&gt;
&lt;li&gt;moyenne de pomodoros en semaine : 11&lt;/li&gt;
&lt;li&gt;moyenne de pomodoros les jours avec 2 tâches ou moins : 12.4&lt;/li&gt;
&lt;li&gt;moyenne de pomodoros les jours avec 3 tâches : 11&lt;/li&gt;
&lt;li&gt;moyenne de pomodoros les jours avec 4 tâches ou plus :10.2&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Il semblerait donc, après un mois d'utilisation, que ce qui joue sur le
nombre de pomodoros par jour soit le nombre de tâches différentes à
effectuer par jour, et donc le nombre de fois où il faut que je fasse un
changement de contexte. Changer de tâches régulièrement n'est pas
anodin, et conforte ma croyance que le cerveau n'est pas multi-tâche
(hein &lt;a class="reference external" href="http://jehaisleprintemps.net/blog/"&gt;No`&lt;/a&gt;!).&lt;/p&gt;
&lt;p&gt;Mais plus que le nombre de tâches par jour, je pense que c'est l'état
de fatigue nerveuse qui influe le plus. Après une semaine à faire en
moyenne 15 pomodoros par jour (et un reccord de 22), je sens que les
jours à venir vont devoir être plus détendus.&lt;/p&gt;
&lt;p&gt;Et si il y a un seul élément à retenir et à surveiller, c'est le
sommeil. Une journée sans avoir assez dormi la veille sera une journée
molle, avec beaucoup de mal pour me concentrer, et une bien plus grande
tentation de papillonner au gré des notifications. Depuis maintenant
deux semaines, j'ai changé mon rythme de sommeil, et ne met plus mon
réveil le matin. Je dors donc entre 8 et 9h par nuit, et je me sens
beaucoup plus efficace dans la journée ! Ce sera peut-être le thème d'un
futur billet ;)&lt;/p&gt;
&lt;p&gt;Ce billet a été écrit en deux pomodoros complets, non interrompus.&lt;/p&gt;
&lt;/div&gt;
</summary></entry><entry><title>La technique pomodoro : retour après deux semaines d'utilisation</title><link href="http://mathieu.agopian.info/blog/la-technique-pomodoro-retour-apres-deux-semaines-dutilisation.html" rel="alternate"></link><updated>2011-03-02T16:35:00Z</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2011-03-02:/blog/la-technique-pomodoro-retour-apres-deux-semaines-dutilisation.html/</id><summary type="html">&lt;p&gt;Voici deux semaines maintenant que j'ai décidé d'essayer la &lt;a class="reference external" href="http://www.pomodorotechnique.com/"&gt;technique Pomodoro&lt;/a&gt; de gestion du temps. En attendant le retour d'expérience au
bout d'un mois (cf le &lt;a class="reference external" href="http://www.stevepavlina.com/blog/2005/04/30-days-to-success/"&gt;30 days to success&lt;/a&gt;), voici déjà mes réflexions
à mi-chemin.&lt;/p&gt;
&lt;div class="section" id="le-principe"&gt;
&lt;h2&gt;Le principe&lt;/h2&gt;
&lt;p&gt;Il est simple, très simple, et c'est d'ailleurs un des points forts de
cette technique : la facilité de compréhension et de mise en œuvre.&lt;/p&gt;
&lt;p&gt;Voici la traduction de ce qu'on peut trouver sur la page d'accueil du
site de la technique :&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;choisir une tâche à accomplir&lt;/li&gt;
&lt;li&gt;régler le Pomodoro sur 25 minutes (le Pomodoro est le minuteur)&lt;/li&gt;
&lt;li&gt;travailler sur la tâche jusqu'à ce que le Pomodoro sonne, et faire
une croix sur une feuille de papier&lt;/li&gt;
&lt;li&gt;faire une courte pause (5 minutes)&lt;/li&gt;
&lt;li&gt;tous les 4 Pomodoros, faire une pause plus longue (30 minutes)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;On peut ensuite rajouter quelques points à faire dans la journée :&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;tenir une liste à jour de toutes les tâches à faire&lt;/li&gt;
&lt;li&gt;chaque début de journée, choisir les tâches à faire pour la journée
en cours, et estimer le nombre de Pomodoros nécessaires pour chacune&lt;/li&gt;
&lt;li&gt;en fin de journée, faire le compte du nombre de Pomodoros effectués
dans la journée, faire le point sur les estimations&lt;/li&gt;
&lt;li&gt;lors d'une interruption&lt;ul&gt;
&lt;li&gt;si c'est possible, la noter sur une feuille &amp;quot;interruptions et
imprévus&amp;quot;, et s'en occuper à la fin d'un Pomodoro&lt;/li&gt;
&lt;li&gt;si il faut s'en occuper de suite, le Pomodoro en cours est perdu,
et ne doit pas être comptabilisé&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="les-points-positifs"&gt;
&lt;h2&gt;Les points positifs&lt;/h2&gt;
&lt;p&gt;Commençons par les bons côtés :&lt;/p&gt;
&lt;div class="section" id="la-gestion-des-interruptions"&gt;
&lt;h3&gt;La gestion des interruptions&lt;/h3&gt;
&lt;p&gt;Et c'est pour ce point en particulier que je comptais sur la technique
Pomodoro, et je ne suis pas déçu. De nos jours, surtout si on est
présents sur les réseaux sociaux (&lt;a class="reference external" href="https://twitter.com/#!/magopian"&gt;twitter&lt;/a&gt; pour ma part), sur irc,
qu'on suit des flux RSS, ou même seulement si on surveille ses mails, on
est baignés dans un flot d'informations. Ce flot n'est pas passif, puis
qu'il y a de très nombreuses notifications possibles, entre celles du
navigateur, de clients divers, de &amp;quot;highlights&amp;quot; irc...&lt;/p&gt;
&lt;p&gt;Or là, la règle est simple : pendant un Pomodoro, on reste concentré
sur la tâche qu'on s'est assignée, et on ignore les notifications. Et il
est beaucoup plus facile de les ignorer si on sait qu'on pourra y
succomber dans x minutes. Dès lors, on travaille d'une traite pendant 25
minutes, et la productivité augmente.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="comptabiliser-le-temps-passe"&gt;
&lt;h3&gt;Comptabiliser le temps passé&lt;/h3&gt;
&lt;p&gt;Rien de plus facile : en fin de journée, il suffit d'additionner les
petites croix tracées sur sa feuille pour savoir qu'on a travaillé n *
30 minutes. Je compte toujours les 5 minutes de pause dans le temps de
travail, car sans ces pauses, il ne serait pas possible de rester
concentré et productif. De la même manière qu'un employé de bureau ou un
ouvrier ne va pas décompter ses pauses café, je ne décompte pas ces
pauses qui sont, j'en suis persuadé, indispensables au bon déroulement
d'une journée de travail.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="aide-a-l-estimation-de-la-duree-d-une-tache"&gt;
&lt;h3&gt;Aide à l'estimation de la durée d'une tâche&lt;/h3&gt;
&lt;p&gt;En temps qu'informaticien, on me demande constamment des estimations de
durées : que ce soit pour régler un problème, implémenter une nouvelle
fonctionnalité, expérimenter de nouvelles solutions ... et ce, pour un
devis, pour avoir une date de mise en production ...&lt;/p&gt;
&lt;p&gt;Lorsque je rajoute une tâche dans ma liste, j'y met une estimation en
nombre de Pomodoros. Je peux ainsi voir les écarts entre cette
estimation et le temps qu'il m'a fallut en réalité en fin de journée. Et
j'ai bon espoir que cet écart baisse avec l'exercice !&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="aide-a-l-organisation"&gt;
&lt;h3&gt;Aide à l'organisation&lt;/h3&gt;
&lt;p&gt;J'imagine que tout le monde à déjà entendu parler du &lt;a class="reference external" href="http://en.wikipedia.org/wiki/Getting_Things_Done"&gt;GTD&lt;/a&gt;, cette
méthode qui permet de s'organiser afin de &amp;quot;faire les choses&amp;quot;. Eh bien
avec la technique Pomodoro, j'ai déjà une liste de tâches à faire, et
une liste de tâches que j'ai prévues pour la journée. Il y a pas mal de
points communs, et je pense que les adeptes de la méthode GTD peuvent y
trouver leur compte.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="les-points-negatifs"&gt;
&lt;h2&gt;Les points négatifs&lt;/h2&gt;
&lt;p&gt;Du côté des points négatifs, voici ceux que j'ai pu lister jusqu'à
présent. Je n'utilise la technique que depuis deux petites semaines, et
concerne principalement les soucis que j'ai rencontrés lors de
l'application de la technique à mon emploi en télé-travail.&lt;/p&gt;
&lt;div class="section" id="les-25-minutes-de-pomodoro"&gt;
&lt;h3&gt;Les 25 minutes de Pomodoro&lt;/h3&gt;
&lt;p&gt;25 minutes de travail, de concentration, puis 5 minutes de pause. Et 30
minutes de pause toutes les deux heures. Sauf que c'est pas toujours
aussi évident.&lt;/p&gt;
&lt;p&gt;Je me retrouve sans cesse confronté à des tâches qui me demandent
&amp;quot;juste une minute de plus&amp;quot;. Une minute de plus pour envoyer un mail de
notification suite à un problème résolu. Ou une minute de plus pour
mettre en production une fois que tous les tests sont passés. Je dis une
minute, mais ça peut être 2 minutes, voire 5 ... où s'arrêter ? A quel
point il faudrait plutôt faire une pause, et y passer un Pomodoro de
plus ?&lt;/p&gt;
&lt;p&gt;Lorsqu'une tâche est plus courte qu'un Pomodoro, il est conseillé de
&amp;quot;sur-apprendre&amp;quot;, de mettre à profit ce temps pour perfectionner son
geste. Il m'est arrivé, pour un Pomodoro où il me restait 15 minutes, de
refaire mon système de déploiement (merci &lt;a class="reference external" href="http://docs.fabfile.org/"&gt;Fabric&lt;/a&gt;). Super ! sauf que
du coup j'ai enchaîné sur un Pomodoro supplémentaire (non prévu en début
de journée), pour finaliser la réécriture de mon &lt;em&gt;fabfile&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="les-pauses"&gt;
&lt;h3&gt;Les pauses&lt;/h3&gt;
&lt;p&gt;Pour continuer sur le point précédent, je trouve ça parfois très dur de
prendre la pause de 5 minutes. Si je suis concentré, dans le &lt;a class="reference external" href="http://en.wikipedia.org/wiki/Flow_(psychology)"&gt;flow&lt;/a&gt;,
il m'est très difficile de m'interrompre pour cette pause.&lt;/p&gt;
&lt;p&gt;Si il ne manque qu'une minute, je suis tenté de continuer pour terminer
(et je le fais très souvent). Si je suis vraiment bien lancé, j'ai
parfois peur que la consultation de mes mails/irc/twitter/rss me
déconcentrent, me fassent sortir de mes rails, et que du coup ce soit
plus difficile de m'y remettre pour terminer ce que j'avais commencé.&lt;/p&gt;
&lt;p&gt;Travaillant de la maison, je n'ai toujours pas réussi à respecter les
pauses de 30 minutes. Comme il m'arrive de devoir travailler sur des
tâches non prévues, et trop courtes pour mériter un Pomodoro (réponse à
un mail pro par exemple), je déborde sur les pauses de 5 minutes, en me
disant que je me rattraperais avec ma pause repas par exemple.&lt;/p&gt;
&lt;p&gt;Dans d'autres cas, j'arrête tout simplement le timer à la fin d'un
Pomodoro (pause repas, vaisselle, allumer un feu...), et je zappe la
pause (de 5 minutes ou de 30 minutes, selon les cas). Mais du coup, je
pense perdre les avantages et la satisfaction de l'enchaînement de
Pomodoros.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="les-interruptions"&gt;
&lt;h3&gt;Les interruptions&lt;/h3&gt;
&lt;p&gt;Je suis en télé-travail, et il m'arrive, en général deux ou trois fois
dans la journée, d'avoir des interruptions de collègues de travail qui
ont besoin de moi. Ce peut être par skype ou téléphone, et c'est en
général difficile à reléguer à la fin d'un Pomodoro : si il me reste 15
minutes avant la fin, je ne peux pas &amp;quot;mettre en pause&amp;quot; mon collègue qui
a ses tâches à lui.&lt;/p&gt;
&lt;p&gt;Ces interruptions peuvent être très courtes (quel est mon mot de passe
pour le serveur bidule?), ou plus ou moins longues (on a un serveur en
rade, tu peux t'y coller de suite?). Que faire dans ce cas ? En théorie,
je dois interrompre mon Pomodoro et le &amp;quot;perdre&amp;quot;, mais du coup je ne peux
plus me servir du nombre de Pomodoros effectués dans la journée pour
suivre le temps travaillé, et il me faut alors me servir d'un outil
supplémentaire (le &lt;a class="reference external" href="http://projecthamster.wordpress.com/"&gt;projet hamster&lt;/a&gt; par exemple).&lt;/p&gt;
&lt;p&gt;Pour le moment en tout cas, je met le minuteur en pause, et le
recommence après l'interruption, si elle a été assez courte et assez peu
&amp;quot;mentale&amp;quot; pour ne pas m'avoir déconcentré.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="le-pomodoro-effectue-est-une-victoire-pas-un-echec"&gt;
&lt;h3&gt;Le pomodoro effectué est une victoire, pas un échec&lt;/h3&gt;
&lt;p&gt;En tout cas, en théorie, c'est comme ça que je devrais le ressentir. Le
slogan de la technique Pomodoro, c'est de faire du temps un allié. Or
quand je vois que je viens de finir un Pomodoro (de plus!) sur une tâche
que j'avais sous-estimée, je culpabilise. Et je rechigne à repartir pour
un Pomodoro supplémentaire.&lt;/p&gt;
&lt;p&gt;Il faut que j'apprenne à voir un Pomodoro effectué comme une
réalisation, une victoire, une bonne chose de faite. Et si j'ai
sous-estimé la tâche, que cela me serve de leçon pour la prochaine.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="les-points-a-travailler"&gt;
&lt;h2&gt;Les points à travailler&lt;/h2&gt;
&lt;p&gt;Il me reste encore deux bonnes semaines avant mon prochain point et
retour sur expérience. D'ici là, il faut que je travaille sur :&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;le respect des pauses : comme je l'indiquais en début de ce billet,
je compte les 5 minutes de pause dans mon temps de travail, et je
suis persuadé qu'elles sont indispensables. J'ai plus de mal avec les
pauses de 30 minutes, et c'est là-dessus qu'il faut que je mette le
paquet&lt;/li&gt;
&lt;li&gt;ressenti de la fin d'un pomodoro qui doit être positif&lt;/li&gt;
&lt;li&gt;gestion des interruptions : il faut que je prenne au moins une note
de chacune des interruptions que je n'ai pu remettre, même si je ne
&amp;quot;perds&amp;quot; pas systématiquement le Pomodoro en court. Une croix pour
chaque fois que je dois mettre le minuteur en pause, retour dans 15
jours ;)&lt;/li&gt;
&lt;li&gt;utilisation de la technique quand on est en télé-travail : est-ce
qu'il y aurait un moyen d'indiquer, par exemple par un statut ou un
indicatif de présence (skype, irc...) à mes collègue de travail que
je suis en cours de Pomodoro ? à creuser...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Je sais qu'il y a au moins une personne qui serait intéressée par le
dernier point : &lt;a class="reference external" href="http://twitter.com/#!/dzen"&gt;&amp;#64;dzen&lt;/a&gt; a plusieurs fois émis l'idée de synchroniser
les Pomodoros entre utilisateurs du salon irc ;)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;On m'avait prévenu, mais j'ai quand même été surpris : on fait beaucoup
moins de pomodoros qu'on imagine ! Je me met devant le PC vers 6h le
matin, et j'arrête ma journée de travail vers 20h (quand ça se passe
bien ;). Je dois donc passer plus de 12h devant le pc, et en comptant
large sur les pauses et interruptions, je pensais être productif au
moins pendant 8 à 9 heures.&lt;/p&gt;
&lt;p&gt;Et bien au final, sur les deux semaines passées, je suis en général
entre 10 à 12 Pomodoros par jour, si je ne compte que les 5 jours de
semaine. Ce qui fait donc entre 5 et 6 heures de productivité par jour.&lt;/p&gt;
&lt;p&gt;A cela il faut bien entendu rajouter toutes les interruptions
téléphoniques, le temps passé à répondre aux mails, la comptabilité...
toutes ces tâches que je ne me vois pas (pour l'instant?) consigner dans
des Pomodoros, mais que du coup, je ne compte pas non plus dans mon
&amp;quot;temps travaillé&amp;quot; en fin de journée.&lt;/p&gt;
&lt;p&gt;Ah, et pour les malins qui me diront que le pluriel de Pomodoro, c'est
Pomodori, je leur répondrais que :&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;on peut soit utiliser &lt;a class="reference external" href="http://www.omniglot.com/blog/?p=71#comment-488"&gt;pomodori soit pomidoro&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;sur le &lt;a class="reference external" href="http://www.pomodorotechnique.com/"&gt;site officiel&lt;/a&gt;, ils utilisent pomodoros&lt;/li&gt;
&lt;li&gt;je fais bien ce que je veux, na!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ce billet a été écrit en&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;3 Pomodoros complet avec 2 micro-interruptions&lt;/li&gt;
&lt;li&gt;1 Pomodoro interrompu (4 fois!)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</summary></entry><entry><title>django: redimensionner une image à la volée en préservant son ratio</title><link href="http://mathieu.agopian.info/blog/django-redimensionner-une-image-a-la-volee-en-preservant-son-ratio.html" rel="alternate"></link><updated>2011-02-16T13:15:00Z</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2011-02-16:/blog/django-redimensionner-une-image-a-la-volee-en-preservant-son-ratio.html/</id><summary type="html">&lt;p&gt;Une rapide recherche sur &amp;quot;django image thumbnail&amp;quot; vous sortira très
vraisemblablement de nombreuses solutions : snippets, applications,
astuces... en particulier la page &lt;a class="reference external" href="http://code.djangoproject.com/wiki/ThumbNails"&gt;Thumbnails sur le wiki de django&lt;/a&gt;
qui liste beaucoup de solutions et leurs avantages.&lt;/p&gt;
&lt;p&gt;Voulant faire simple, et profiter de toutes les améliorations que j'ai
pu trouver et compulser sur diverses solutions, voilà ma contribution.&lt;/p&gt;
&lt;div class="section" id="utiliser-pil-et-image-thumbnail"&gt;
&lt;h2&gt;Utiliser PIL et Image.thumbnail()&lt;/h2&gt;
&lt;p&gt;Python Image Library est une des (la?) solutions les plus avancées sur
le traitement d'image en python. Dans mon cas pratique, je veux, lors
d'un upload d'une photo, pouvoir :&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;redimensionner la photo, pour qu'elle fasse &lt;strong&gt;au maximum&lt;/strong&gt; 800x600 :
elle sera affichée avec un max-width et un max-height&lt;/li&gt;
&lt;li&gt;créer un thumbnail&lt;/li&gt;
&lt;li&gt;stocker les images au format jpg&lt;/li&gt;
&lt;li&gt;tout ceci sans au préalable sauver l'image sur le disque&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Beaucoup des solutions que j'ai pu trouver utilisaient la fonction
&lt;em&gt;resize()&lt;/em&gt; de PIL.Image, et utilisaient un calcul du ratio pour
conserver l'aspect de l'image. Heureusement, PIL a pensé au fainéant que
je suis, et fournit la fonction &lt;em&gt;thumbnail()&lt;/em&gt; qui va automatiquement
redimensionner l'image pour qu'elle rentre dans les dimensions maximales
qu'on lui fournit.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="le-modele"&gt;
&lt;h2&gt;Le modèle&lt;/h2&gt;
&lt;p&gt;Modèle très simple pour illustrer notre propos :&lt;/p&gt;
&lt;pre class="literal-block"&gt;
class Foo(models.Model):
    photo = models.ImageField(upload_to='photos/')
    thumbnail = models.ImageField(upload_to='photos/thumbs/')
    legend = models.CharField(max_length=50)
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="la-solution"&gt;
&lt;h2&gt;La solution&lt;/h2&gt;
&lt;p&gt;Cette solution est largement inspirée de cet article qui collait le
plus à la version que je souhaitais obtenir au final : &lt;a class="reference external" href="http://snipt.net/danfreak/generate-thumbnails-in-django-with-pil/"&gt;generate
thumbnails in django with PIL&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Tout d'abord le morceau de code qui récupère l'image en mémoire
(stockée dans un &lt;em&gt;InMemoryUploadedFile&lt;/em&gt;) et la converti en mode &lt;em&gt;RGB&lt;/em&gt; si
nécessaire :&lt;/p&gt;
&lt;pre class="literal-block"&gt;
from PIL import Image
from cStringIO import StringIO
from django.core.files.uploadedfile import SimpleUploadedFile

...

    def save(self, *args, **kwargs):
        if has_changed(self, 'photo'):
            # on va convertir l'image en jpg
            filename = path.splitext(path.split(self.photo.name)[-1])[0]
            filename = &amp;quot;%s.jpg&amp;quot; % filename

            image = Image.open(self.photo.file)

            if image.mode not in ('L', 'RGB'):
                image = image.convert('RGB')

            # d'abord la photo elle-même
            self.photo.save(
                    filename,
                    create_thumb(image, settings.IMAGE_MAX_SIZE),
                    save=False)

            # puis le thumbnail
            self.thumbnail.save(
                    '_%s' % filename,
                    create_thumb(image, settings.THUMB_MAX_SIZE),
                    save=False)
&lt;/pre&gt;
&lt;p&gt;Et enfin la fonction &lt;em&gt;create_thumb&lt;/em&gt; qui prends en paramètre une
PIL.Image et une taille du style &lt;em&gt;(800, 600)&lt;/em&gt; :&lt;/p&gt;
&lt;pre class="literal-block"&gt;
def create_thumb(image, size):
    &amp;quot;&amp;quot;&amp;quot;Returns the image resized to fit inside a box of the given size&amp;quot;&amp;quot;&amp;quot;
    image.thumbnail(size, Image.ANTIALIAS)
    temp = StringIO()
    image.save(temp, 'jpeg')
    temp.seek(0)
    return SimpleUploadedFile('temp', temp.read())
&lt;/pre&gt;
&lt;p&gt;Retourner un &lt;em&gt;SimpleUploadedFile&lt;/em&gt; permet de le fournir directement au
&lt;em&gt;save()&lt;/em&gt; de l'&lt;em&gt;ImageField&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="le-bonus"&gt;
&lt;h2&gt;Le bonus&lt;/h2&gt;
&lt;p&gt;Vous avez sûrement remarqué la fonction &lt;em&gt;has_changed()&lt;/em&gt; dans l'appel
de la méthode &lt;em&gt;save()&lt;/em&gt; ci-dessus... en effet, il serait peu utile, voire
même carrément indésirable de générer une nouvelle version de la photo
et de son thumbnail à chaque fois que notre modèle est sauvé!&lt;/p&gt;
&lt;p&gt;On se retrouverait vite avec autant de photos et de thumbnails que le
nombre de fois qu'on a sauvé notre modèle, même si la seule modification
portait sur la légende.&lt;/p&gt;
&lt;p&gt;Pour limiter ça, on ne redimensionne et génère le thumbnail que si la
photo a été modifiée, ce qu'on teste avec la fonction suivante :&lt;/p&gt;
&lt;pre class="literal-block"&gt;
def has_changed(instance, field, manager='objects'):
    &amp;quot;&amp;quot;&amp;quot;Returns true if a field has changed in a model

    May be used in a model.save() method.

    &amp;quot;&amp;quot;&amp;quot;
    if not instance.pk:
        return True
    manager = getattr(instance.__class__, manager)
    old = getattr(manager.get(pk=instance.pk), field)
    return not getattr(instance, field) == old
&lt;/pre&gt;
&lt;/div&gt;
</summary></entry><entry><title>Django forms, HTML5 et fieldsets</title><link href="http://mathieu.agopian.info/blog/la-beaute-de-lopen-source-et-du-libre.html" rel="alternate"></link><updated>2011-02-11T22:56:00Z</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2011-02-11:/blog/la-beaute-de-lopen-source-et-du-libre.html/</id><summary type="html">&lt;p&gt;J'adore l'open-source, et j'adore le libre.&lt;/p&gt;
&lt;p&gt;Aujourd'hui, à 20h30, je me dis que j'aimerais bien pouvoir utiliser
les nouveaux &lt;em&gt;form input&lt;/em&gt; de HTML5 dans mes formulaires &lt;a class="reference external" href="http://www.djangoproject.com/"&gt;django&lt;/a&gt;. Je me
rappelle alors de l'excellent &lt;a class="reference external" href="https://github.com/brutasse/django-floppyforms"&gt;django-floppyforms&lt;/a&gt; de ce cher &lt;a class="reference external" href="http://bruno.im/"&gt;Bruno Renié&lt;/a&gt;.&lt;/p&gt;
&lt;div class="section" id="django-floppy-forms-la-librairie-de-widgets"&gt;
&lt;h2&gt;django-floppy-forms : la librairie de widgets&lt;/h2&gt;
&lt;p&gt;Quelques minutes et un &lt;em&gt;pip install django-floppyforms&lt;/em&gt; plus tard, je
me retrouve avec un petit formulaire de test :&lt;/p&gt;
&lt;pre class="literal-block"&gt;
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import floppyforms as forms


class TestForm(forms.Form):
    name = forms.CharField()
    email = forms.EmailField(widget=forms.EmailInput(attrs={'placeholder': 'john&amp;#64;example.com'}))
    url = forms.URLField()
&lt;/pre&gt;
&lt;p&gt;Ce qui me donne le résultat visuel suivant :&lt;/p&gt;
&lt;img alt="Formulaire Django avec des inputs HTML5" class="align-center" src="images/html5_form.png" /&gt;
&lt;p&gt;On y voit bien le &lt;em&gt;placeholder&lt;/em&gt; dans le champ de type &lt;em&gt;email&lt;/em&gt;. Sur
l'image suivante, on voit la validation faite directement au niveau du
browser (firefox 4b11) :&lt;/p&gt;
&lt;img alt="Formulaire Django avec validation" class="align-center" src="images/html5_form_validation.png" /&gt;
&lt;p&gt;A 20h45 : &amp;quot;ce que j'aimerais vraiment bien, c'est pouvoir scinder mon
formulaire en &lt;em&gt;fieldsets&lt;/em&gt;, comme le permet l'administration de django&amp;quot;.
Va-t-il falloir que je me mette à développer? Que nenni, cher lecteur,
comme tu l'aura deviné, un autre développeur de talent, un certain &lt;a class="reference external" href="http://twitter.com/#!/carljm"&gt;Carl Meyer&lt;/a&gt; nous propose le très pratique &lt;a class="reference external" href="https://bitbucket.org/carljm/django-form-utils/src"&gt;django-form-utils&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="django-form-utils-une-methode-propre-pour-specifier-des-fieldsets"&gt;
&lt;h2&gt;django-form-utils : une méthode propre pour spécifier des fieldsets&lt;/h2&gt;
&lt;p&gt;Encore quelques minutes et un &lt;em&gt;pip install django-form-utils&lt;/em&gt; plus
tard, voici le code du formulaire :&lt;/p&gt;
&lt;pre class="literal-block"&gt;
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import floppyforms as forms
from form_utils.forms import BetterForm


class TestForm(BetterForm):
    name = forms.CharField()
    email = forms.EmailField(widget=forms.EmailInput(
                attrs={'placeholder': 'john&amp;#64;example.com'}))
    url = forms.URLField()

    class Meta:
        fieldsets = [
            ('test', {
                'fields': ['name', 'email'],
                'legend': 'a test legend',
            }),
            ('other test', {
                'fields': ['url'],
                'description': 'a test description',
                'classes': ['advanced', 'collapse']
            }),
        ]
        row_attrs = {'name': {'style': 'background: #f00'}}
&lt;/pre&gt;
&lt;p&gt;Et le template, un poil plus complexe que le &lt;em&gt;{{ form.as_p }}&lt;/em&gt;
précédent :&lt;/p&gt;
&lt;pre class="literal-block"&gt;
{% if form.non_field_errors %}{{ form.non_field_errors }}{% endif %}
{% for fieldset in form.fieldsets %}
&amp;lt;fieldset class=&amp;quot;{{ fieldset.classes }}&amp;quot;&amp;gt;
    {% if fieldset.legend %}&amp;lt;legend&amp;gt;{{ fieldset.legend }}&amp;lt;/legend&amp;gt;{% endif %}
    {% if fieldset.description %}
        &amp;lt;p class=&amp;quot;description&amp;quot;&amp;gt;{{ fieldset.description }}&amp;lt;/p&amp;gt;
    {% endif %}
    {% for field in fieldset %}
        {% if field.is_hidden %}
            {{ field }}
        {% else %}
            &amp;lt;p{{ field.row_attrs }}&amp;gt;
                {{ field.errors }}
                {{ field.label_tag }}
                {{ field }}
            &amp;lt;/p&amp;gt;
        {% endif %}
    {% endfor %}
&amp;lt;/fieldset&amp;gt;
{% endfor %}
&lt;/pre&gt;
&lt;p&gt;Et voici le résultat (les moqueurs reconnaîtrons mon talent pour tout
ce qui touche au design) :&lt;/p&gt;
&lt;img alt="Formulaire Django avec des fieldsets" class="align-center" src="images/html5_form_fieldsets.png" /&gt;
&lt;p&gt;21h00 : Tout ça est vraiment super. Mais... quitte a être
exigeant, est-ce que je pourrai avoir des fieldsets ET un template plus
simple? Par exemple un layout générique du style &lt;a class="reference external" href="http://sprawsm.com/uni-form/"&gt;uni-form&lt;/a&gt;... c'est là
que &lt;a class="reference external" href="http://pydanny.blogspot.com"&gt;Daniel Greenfeld&lt;/a&gt; entre en jeu avec son projet pour djangonautes
fainéants &lt;a class="reference external" href="https://github.com/pydanny/django-uni-form"&gt;django-uni-form&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="django-uni-form-les-uni-forms-pour-django"&gt;
&lt;h2&gt;django-uni-form: les uni-forms pour django&lt;/h2&gt;
&lt;p&gt;Vous connaissez le refrain, quelques minutes et un &lt;em&gt;pip install
django-uni-form&lt;/em&gt; plus tard :&lt;/p&gt;
&lt;pre class="literal-block"&gt;
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import floppyforms as forms
from django.forms import Form
from uni_form.helpers import FormHelper, Submit
from uni_form.helpers import Layout, Fieldset


class TestForm(Form):
    name = forms.CharField()
    email = forms.EmailField(widget=forms.EmailInput(
                attrs={'placeholder': 'john&amp;#64;example.com'}))
    url = forms.URLField()
    helper = FormHelper()
    layout = Layout(Fieldset('a test legend', 'name', 'email'),
                    Fieldset('other test', 'url'))
    helper.add_layout(layout)
    submit = Submit('submit','test this form')
    helper.add_input(submit)
&lt;/pre&gt;
&lt;p&gt;Et le template, beaucoup plus simple du coup :&lt;/p&gt;
&lt;pre class="literal-block"&gt;
{% load uni_form_tags %}
{% with form.helper as helper %}
    {% uni_form form helper %}
{% endwith %}
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Il fait bon être fainéant, utiliser django, et les outils déjà
disponibles en open-source. Les applications que j'ai présentées ici ont
été bien pensées, et sont &lt;em&gt;pluggable&lt;/em&gt; : on peut facilement les rajouter
à son projet et les utiliser ou même les combiner!&lt;/p&gt;
&lt;p&gt;Dans notre cas on a pu tester l'utilisation de widgets html5
(django-floppy-forms) avec un layout par fieldset défini dans la class
&lt;em&gt;Meta&lt;/em&gt; du formulaire (django-form-utils), ou grâce à un &lt;em&gt;helper&lt;/em&gt;
(django-uni-form).&lt;/p&gt;
&lt;p&gt;Pour le moment mon cœur balance entre les deux, la solution utilisant
la sous-class &lt;em&gt;Meta&lt;/em&gt; me paraissant plus naturelle, mais l'utilisation
d'un layout robuste et soigné comme les uni-form étant plus simple,
&amp;quot;standard&amp;quot; et économisant plus de temps.&lt;/p&gt;
&lt;p&gt;A quand un projet permettant d'hériter d'une classe UniForm, dans
laquelle on définit les fieldsets dans une sous-classe &lt;em&gt;Meta&lt;/em&gt; ?&lt;/p&gt;
&lt;p&gt;Ah, au fait, de 21h00 à 23h00, écriture de ce billet... une demi heure
pour tester trois projets, et presque deux heures pour en parler,
personne n'aurait une &lt;em&gt;pluggable app&lt;/em&gt; qui pond des billets ?&lt;/p&gt;
&lt;/div&gt;
</summary></entry><entry><title>Double encodage utf8 : afficher correctement avec python et django</title><link href="http://mathieu.agopian.info/blog/double-encodage-utf8-afficher-correctement-avec-python-et-django.html" rel="alternate"></link><updated>2010-12-27T16:39:00Z</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2010-12-27:/blog/double-encodage-utf8-afficher-correctement-avec-python-et-django.html/</id><summary type="html">&lt;p&gt;Nous avons vu dans un précédent article qu'il pouvait y avoir des soucis
de &lt;a class="reference external" href="./mysql-mysqldump-et-php-convertir-de-latin1-vers-utf8.rtml"&gt;double encodage utf8&lt;/a&gt;, par exemple pour des textes stockés dans une
base de donnée.&lt;/p&gt;
&lt;p&gt;Imaginons, un court instant (parce que plus longtemps que ça, ce serait
bien trop douloureux hein ;)), que nous ayons une base de donnée avec
certains champs de certaines tables qui sont doublement encodés en
UTF-8, par exemple le champ &lt;em&gt;name&lt;/em&gt;.&lt;/p&gt;
&lt;div class="section" id="le-probleme"&gt;
&lt;h2&gt;Le problème&lt;/h2&gt;
&lt;p&gt;Voici à quoi ressemblerait le modèle Django d'une telle table :&lt;/p&gt;
&lt;pre class="literal-block"&gt;
class Foo(models.Model):
    name = models.CharField(max_length=70)

    def __unicode__(self):
         return self.name
&lt;/pre&gt;
&lt;p&gt;Et voici un exemple d'affichage (dans l'admin) du nom d'un tel modèle :
&lt;em&gt;HelicoptÃ¨re&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;En effet, &lt;em&gt;Hélicoptère&lt;/em&gt;, représenté en utf8 par &lt;em&gt;Helicopt\xe8re&lt;/em&gt;, est
stocké en &lt;em&gt;latin1&lt;/em&gt; par MySQL, et donc renvoyé (doublement) encodé en
utf8 sous la forme &lt;em&gt;Helicopt\xc3\xa8re&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="la-solution"&gt;
&lt;h2&gt;La solution&lt;/h2&gt;
&lt;p&gt;Puisque la donnée est doublement encodée, il suffit de la décoder une
fois :&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;gt;&amp;gt;&amp;gt; f = Foo.objects.get(name__startswith='Helico')
&amp;gt;&amp;gt;&amp;gt; f.name
u'Helicopt\xc3\xa8re'
&amp;gt;&amp;gt;&amp;gt; f.name.encode('latin1')
'Helicopt\xc3\xa8re'
&amp;gt;&amp;gt;&amp;gt; f.name.encode('latin1').decode('utf8')
u'Helicopt\xe8re'
&lt;/pre&gt;
&lt;p&gt;Et le tour est joué!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="un-affichage-presque-propre-avec-django"&gt;
&lt;h2&gt;Un affichage (presque) propre avec django&lt;/h2&gt;
&lt;p&gt;Dans django, il est possible de spécifier une méthode &lt;em&gt;__unicode__&lt;/em&gt;
sur un modèle, pour gérer son affichage par défaut, qui est utilisé
notamment dans l'affichage des listes d'objets d'un modèle, dans
l'administration.&lt;/p&gt;
&lt;p&gt;Nous pouvons donc modifier la méthode de notre modèle indiqué en début
de cet article :&lt;/p&gt;
&lt;pre class="literal-block"&gt;
def __unicode__(self):
     return self.name.encode('latin1').decode('utf8')
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="mais-tout-n-est-pas-parfait"&gt;
&lt;h2&gt;Mais... tout n'est pas parfait&lt;/h2&gt;
&lt;p&gt;Il faut penser aux points suivants :&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;il faut bien utiliser &lt;em&gt;__unicode__&lt;/em&gt; à la place de &lt;em&gt;name&lt;/em&gt; partout
où c'est possible, par exemple si on spécifie un &lt;em&gt;list_display&lt;/em&gt; dans
le ModelAdmin&lt;/li&gt;
&lt;li&gt;lorsqu'on éditera l'objet, le &lt;em&gt;name&lt;/em&gt; doublement encodé apparaîtra, et
si on le modifie manuellement pour s'afficher correctement, on aura
une incohérence dans la table, avec des données doublement encodées,
et d'autres stockées correctement&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="une-autre-methode"&gt;
&lt;h2&gt;Une autre méthode&lt;/h2&gt;
&lt;p&gt;Il est possible de spécifier des options lors de la connexion à la DB
MySQL, avec django, pour lui demander d'utiliser le charset &lt;em&gt;latin1&lt;/em&gt;. Vu
que django lui-même ne parle qu'en utf8, demander à MySQL des données en
&lt;em&gt;latin1&lt;/em&gt; revient à lui demander de ne pas ré-encoder la donnée qu'il
pense être stockée en &lt;em&gt;latin1&lt;/em&gt;, mais qui sera ensuite correctement
affichée par django &lt;em&gt;:&lt;/em&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
DATABASES = {
    'hack': {
        'NAME': 'bar',
        'ENGINE': 'django.db.backends.mysql',
        'USER': 'baz',
        'PASSWORD': 'bizbaz',
        'HOST': 'localhost',
        'PORT': '',
        'OPTIONS': {
            'charset': 'latin1',
            'use_unicode': False,
        },
    },
}
&lt;/pre&gt;
&lt;p&gt;L'avantage ici est qu'il n'y a pas besoin d'avoir un traitement
spécifique des données, par exemple dans la méthode &lt;em&gt;__unicode__&lt;/em&gt;,
et que toutes les données lues seront dé-doublement-encodées (!?).&lt;/p&gt;
&lt;p&gt;Le gros inconvénient est que toutes les tables subiront le même
traitement, même celles qui ont un encodage et stockage correct.
L'utilisation d'un routeur et d'une deuxième entrée dans les &lt;em&gt;DATABASES&lt;/em&gt;
uniquement pour les tables ayant un soucis n'étant pas une solution
durable non plus, car celà limite les relations entre tables.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="et-tant-qu-a-faire-dans-le-crade"&gt;
&lt;h2&gt;Et tant qu'à faire dans le crade&lt;/h2&gt;
&lt;p&gt;La solution la plus dangereuse serait d'avoir un traitement &amp;quot;par
défaut&amp;quot; de toutes les données qu'on veut afficher, pour :&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;régler le problème du double-encodage si il y en a un&lt;/li&gt;
&lt;li&gt;afficher la donnée brute si on ne peut pas dé-double-encoder&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Exemple :&lt;/p&gt;
&lt;pre class="literal-block"&gt;
def __unicode__(self):
    try:
        return self.name.encode('latin1').decode('utf8')
    except:
        return self.name
&lt;/pre&gt;
&lt;p&gt;On ne se pose alors plus la question de la cohérence des données et de
leur encodate et stockage dans la base de donnée, mais on se met à dos
une énorme dette technique : le développement du traitement spécifique
de toutes les colonnes de toutes les tables le nécessitant peut être
titanesque, et bien plus lourd que la correction du problème à la base.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Rien ne vaut une DB saine, ses tables étant toutes encodées
correctement. Si vous commencez à utiliser des workarounds ou hacks
divers, vous vous en mordrez les doigts (il me manque déjà plusieurs
phalanges, croyez-moi).&lt;/p&gt;
&lt;p&gt;La &lt;a class="reference external" href="http://en.wikipedia.org/wiki/Technical_debt"&gt;dette technique&lt;/a&gt; (métaphore inventée par Ward Cunningham) est une
plaie dont il faut se préserver au maximum, et qu'il faut rembourser le
plus tôt possible.&lt;/p&gt;
&lt;/div&gt;
</summary><category term="mysql"></category><category term="python"></category></entry><entry><title>La vie a la couleur qu'on veut bien lui donner</title><link href="http://mathieu.agopian.info/blog/la-vie-a-la-couleur-quon-veut-bien-lui-donner.html" rel="alternate"></link><updated>2010-11-15T08:53:00Z</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2010-11-15:/blog/la-vie-a-la-couleur-quon-veut-bien-lui-donner.html/</id><summary type="html">&lt;p&gt;Ce billet n'est pas technique, il est une manière pour moi de mettre par
écrit, mettre en mots, un état d'esprit, une réflexion sur ma propension
a râler et rouspéter à la moindre occasion.&lt;/p&gt;
&lt;div class="section" id="je-suis-un-raleur"&gt;
&lt;h2&gt;Je suis un râleur&lt;/h2&gt;
&lt;p&gt;Je le sais, je le reconnais, et ce depuis &lt;strong&gt;longtemps&lt;/strong&gt;. Ce que je ne
sais pas, c'est pourquoi.&lt;/p&gt;
&lt;p&gt;Je pense avoir toujours eu de la chance, moins que d'autres sûrement,
mais bien plus, infiniment plus que d'autres : SDF, exclus, souffrants
d'un handicap, rejetés par leur famille (peut-être le plus grand des
malheurs?), torturés, génocidés (je suis d'origine arménienne), victimes
de la famine, d'épurations ethniques, d'ouragans, de guerres civiles, de
despotes et dictateurs en tous genre...&lt;/p&gt;
&lt;p&gt;Mon empathie me fait souvent souffrir pour les victimes d'injustices,
les laissés pour comptes, les galériens qui trimeront toute leur vie
pour ne pas arriver à subvenir à leurs besoins et ceux de leur famille.&lt;/p&gt;
&lt;p&gt;De mon côté, j'ai eu une enfance vraiment dorée, entouré de parents
aimants et de trois petites sœurs adorables. Je n'ai jamais manqué d'un
toit ni de nourriture. Même si nous n'étions pas aisés, je n'ai jamais
ressenti le moindre besoin.&lt;/p&gt;
&lt;p&gt;Je suis maintenant (jeune) marié à une femme extraordinaire, que j'aime
et qui m'aime (peut-être le plus grand des bonheurs?).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="mais-je-rale-gentiment"&gt;
&lt;h2&gt;Mais je râle gentiment&lt;/h2&gt;
&lt;p&gt;Suite à une discussion ce matin avec &lt;a class="reference external" href="http://j-mad.com/blog/"&gt;un ami&lt;/a&gt;, qui me relance
régulièrement pour que j'écrive un &lt;a class="reference external" href="http://polar-geek.org/"&gt;polar geek&lt;/a&gt;, je me suis remis en
question. Voici notre échange :&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;lt;magopian&amp;gt; voilà pourquoi je ne sais pas écrire de nouvelles :
    ça finirait en rouspétage
&amp;lt;magopian&amp;gt; j'ai pas envie qu'on m'appelle le caliméro du polar geek ^^
&amp;lt;J-Mad&amp;gt; ben ca pourrait être une idée cool de perso
&amp;lt;J-Mad&amp;gt; un gros râleur
&amp;lt;J-Mad&amp;gt; qui râle pour tout
&amp;lt;J-Mad&amp;gt; un de mes amis est comme ça
&amp;lt;J-Mad&amp;gt; quand il rale pour des trucs insignifiants
&amp;lt;J-Mad&amp;gt; on sait que tout va bien, qu'il faut juste qu'il râle sur un truc
&lt;/pre&gt;
&lt;p&gt;Un râleur continuel, le rouspéteur de service. Ce gars dont les
révoltes ne sont qu'une expression naturelle de sa personnalité, une
image de bougon, pas renfrogné mais facilement revendicateur.&lt;/p&gt;
&lt;p&gt;Un gars sympa quoi, pas méchant, plutôt agréable, et un peu marrant
avec ses coups de gueule et ses logorrhées inutiles.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="vraiment"&gt;
&lt;h2&gt;Vraiment?&lt;/h2&gt;
&lt;p&gt;Mais alors, si j'ai autant de chance, de bonheur, si j'aime autant être
de bonne humeur, rire, communiquer, partager, profiter, pourquoi râler?&lt;/p&gt;
&lt;p&gt;Pourquoi véhiculer la mauvaise humeur, la rancoeur, la colère même? Je
sais pourtant qu'un état d'humeur, que les sentiments sont contagieux.
Pourquoi alors, ne pas véhiculer la joie de vivre et la motivation?&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="id1"&gt;
&lt;h2&gt;La vie a la couleur qu'on veut bien lui donner&lt;/h2&gt;
&lt;p&gt;Voilà la phrase, toute simple et pourtant lourde de (bonnes)
conséquences, qu'une charmante dame m'a confié comme &amp;quot;astuce de
bonheur&amp;quot;. Une dame frappée par les malheurs, et qui pourtant a toujours
le sourire aux lèvres.&lt;/p&gt;
&lt;p&gt;&amp;quot;Moi, je veux la voir en rose, pas besoins de lunettes teintées
d'enfant pour ça, c'est juste un état d'esprit!&amp;quot; m'a-t'elle confié.&lt;/p&gt;
&lt;p&gt;Et bien moi aussi, je veux la voir en rose (en &lt;a class="reference external" href="http://djangopony.com/"&gt;poney rose&lt;/a&gt; même, hein
&lt;a class="reference external" href="http://twitter.com/#!/zebuline"&gt;Zébuline&lt;/a&gt; ;)&lt;/p&gt;
&lt;/div&gt;
</summary><category term="mavie"></category></entry><entry><title>lancer gunicorn avec supervisord</title><link href="http://mathieu.agopian.info/blog/lancer-gunicorn-avec-supervisord.html" rel="alternate"></link><updated>2010-08-31T16:48:00Z</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2010-08-31:/blog/lancer-gunicorn-avec-supervisord.html/</id><summary type="html">&lt;p&gt;Après avoir mis en place une méthode pour &lt;a class="reference external" href="./lancer-gunicorn-avec-runit.html"&gt;lancer gunicorn avec runit&lt;/a&gt;,
voici un second article pour lancer ce même &lt;a class="reference external" href="http://gunicorn.org/"&gt;gunicorn&lt;/a&gt; (qui poutre du
poney, rappelons-le), mais avec un outil que je trouve à l'utilisation
bien plus pratique : &lt;a class="reference external" href="http://supervisord.org/index.html"&gt;supervisord&lt;/a&gt;&lt;/p&gt;
&lt;div class="section" id="pourquoi-supervisord-et-pas-runit"&gt;
&lt;h2&gt;Pourquoi supervisord et pas runit ?&lt;/h2&gt;
&lt;p&gt;Après l'avoir utilisé sans aucun soucis depuis quelques temps sur le
serveur de secours de notre site web, je trouve que supervisord est plus
convivial à utiliser, et plus &amp;quot;clair&amp;quot; à mettre en place.&lt;/p&gt;
&lt;p&gt;Plus convivial à utiliser parce qu'il a un client CLI qui permet
d'interagir directement avec les processus monitorés, mais il y a aussi
les mêmes fonctionnalités par le biais d'une interface web fort
pratique.&lt;/p&gt;
&lt;p&gt;Plus &amp;quot;clair&amp;quot; à mettre en place parce qu'il se lance grâce à un simple
script d'init, et qu'on peut faire la configuration de tous ses
processus à monitorer dans un seul et même fichier de configuration, au
lieu d'avoir un répertoire et des liens symboliques à gérer (certains
préfèrent peut-être cette modularité, mais personnellement je trouve ça
beaucoup plus pénible à maintenir).&lt;/p&gt;
&lt;div class="section" id="installer-supervisord"&gt;
&lt;h3&gt;Installer supervisord&lt;/h3&gt;
&lt;p&gt;Etant donné que supervisord est en python, il est possible de
l'installer avec un simple&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ pip install supervisor
&lt;/pre&gt;
&lt;p&gt;Pour les &lt;em&gt;old-school&lt;/em&gt; qui ne profitent pas encore de la puissance et de
l'ergonomie de ce magnifique outil de Ian Bicking, il est possible de
remplacer &lt;em&gt;pip&lt;/em&gt; par &lt;em&gt;easy_install&lt;/em&gt; (même si je vous conseille plutôt de
faire un &lt;em&gt;easy_install pip&lt;/em&gt; puis de vous passer de &lt;em&gt;easy_install&lt;/em&gt; par
la suite!).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="configurer-supervisord"&gt;
&lt;h3&gt;Configurer supervisord&lt;/h3&gt;
&lt;p&gt;On le configure en deux temps : une première configuration de
supervisord lui-même, puis l'ajout des processus qu'on veut qu'il
monitore.&lt;/p&gt;
&lt;p&gt;Dans le fichier &lt;em&gt;/etc/supervisord.conf&lt;/em&gt; :&lt;/p&gt;
&lt;pre class="literal-block"&gt;
; configuration de supervisord lui-meme
[unix_http_server]
file=/tmp/supervisor.sock   ; chemin vers le fichier socket

[inet_http_server]
port=192.168.0.21:9001      ; adresse ip _LOCALE_ de la machine pour la connection web

[supervisord]
logfile=/var/log/supervisord.log ; fichier de log principal de supervisord

[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[supervisorctl]
serverurl=unix:///tmp/supervisor.sock ; connection pour le client CLI

; liste des processus qu'on veut confier a supervisord
[program:gu-www]
command=/path/to/venv/bin/python /path/to/venv/bin/gunicorn_django -b localhost:8080 --log-file=/path/to/log/gunicorn_gu-www.log --workers=3
directory=/folder/containing/settings/file/
user=www-data
autostart=true
autorestart=true
startsecs=10
redirect_stderr=true
stdout_logfile=/path/to/log/supervisor_gu-www.log
&lt;/pre&gt;
&lt;p&gt;Bien entendu, faites en sortes que l'utilisateur &lt;em&gt;www-data&lt;/em&gt; ai accès en
écriture aux fichiers de log &lt;em&gt;gunicorn_gu-www.log&lt;/em&gt; configuré où le
processus échouera lors de son lancement.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ATTENTION:&lt;/strong&gt; comme indiqué dans le commentaire pour la ligne de
configuration &lt;em&gt;inet_http_server&lt;/em&gt; il faut absolument faire en sorte que
l'adresse ne soit pas accessible à tout le monde! En effet il est
possible de redémarrer ou tout simplement stopper les processus par
cette interface! Vous pouvez désactiver complètement cette possibilité
en supprimant ces deux lignes, il n'y aura alors pas de moyen de
contrôler supervisord par une page web.&lt;/p&gt;
&lt;p&gt;La commande indiquée pour information lance &lt;em&gt;gunicorn_django&lt;/em&gt; avec
dans un environnement virtuel (ce que vous devriez faire vous aussi!).
Il n'est pas nécessaire d'indiquer le chemin vers le fichier
&lt;em&gt;settings.py&lt;/em&gt; dans les options de &lt;em&gt;gunicorn_django&lt;/em&gt; étant donné que le
processus sera lancé directement dans le répertoire le contenant (si
vous le configurez correctement avec le paramètre &lt;em&gt;directory&lt;/em&gt; comme
indiqué ci-dessus).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ATTENTION:&lt;/strong&gt; il faut absolument que &lt;em&gt;gunicorn_django&lt;/em&gt; soit lancé en
&lt;em&gt;foreground&lt;/em&gt; (en tâche principale, pas en
&lt;em&gt;arrière-plan&lt;/em&gt;/&lt;em&gt;background&lt;/em&gt;/&lt;em&gt;démon&lt;/em&gt;/&lt;em&gt;daemon&lt;/em&gt;), ce qui est le cas par défaut
si vous n'avez pas explicitement dit le contraire dans votre fichier de
configuration (ou en paramètre de la commande) de &lt;em&gt;gunicorn_django&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;En effet c'est &lt;em&gt;supervisord&lt;/em&gt; qui se charge de le faire : si le
processus est lancé en arrière-plan, &lt;em&gt;supervisord&lt;/em&gt; ne peut pas prendre
la main dessus et le contrôler ni le monitorer, et va essayer de le
lancer plusieurs fois d'affilée pensant qu'il échoue à chaque fois.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="lancer-supervisord-et-le-relancer-a-chaque-reboot"&gt;
&lt;h3&gt;Lancer supervisord et le relancer à chaque reboot&lt;/h3&gt;
&lt;p&gt;Il suffit pour cela de créer un script de démarrage dans &lt;em&gt;/etc/init.d/&lt;/em&gt;
et de le faire se lancer à chaque démarrage.&lt;/p&gt;
&lt;p&gt;Dans &lt;em&gt;/etc/init.d/supervisord&lt;/em&gt; :&lt;/p&gt;
&lt;pre class="literal-block"&gt;
#! /bin/sh
### BEGIN INIT INFO
# Provides:          supervisord
# Required-Start:    $remote_fs
# Required-Stop:     $remote_fs
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Example initscript
# Description:       This file should be used to construct scripts to be
#                    placed in /etc/init.d.
### END INIT INFO

# Author: Dan MacKinlay
# Based on instructions by Bertrand Mathieu
# http://zebert.blogspot.com/2009/05/installing-django-solr-varnish-and.html

# Do NOT &amp;quot;set -e&amp;quot;

# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin
DESC=&amp;quot;supervisord&amp;quot;
NAME=supervisord
DAEMON=supervisord
MANAGE=supervisorctl
DAEMON_ARGS=&amp;quot;&amp;quot;
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME

# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh

# Define LSB log_* functions.
# Depend on lsb-base (&amp;gt;= 3.0-6) to ensure that this file is present.
. /lib/lsb/init-functions

case &amp;quot;$1&amp;quot; in
  start)
    $DAEMON
        ;;
  stop)
    $MANAGE shutdown
        ;;
  reload|force-reload)
    $MANAGE reload
    ;;
  restart)
    $MANAGE restart
        ;;
  *)
        echo &amp;quot;Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}&amp;quot; &amp;gt;&amp;amp;2
        exit 3
        ;;
esac
&lt;/pre&gt;
&lt;p&gt;Ce script contient des commentaires en début de fichier qui permettent
de le faire se lancer automatiquement à chaque démarrage :&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ chmod a+x /etc/init.d/supervisord
$ update-rc.d supervisord defaults 99 # le chiffre fourni est la priorité
&lt;/pre&gt;
&lt;p&gt;Cette commande devrait indiquer qu'elle a créé tous les liens
symbolique nécessaire pour lancer ce script à chaque démarrage du
serveur. La priorité est le chiffre utilisé pour le nom du lien
symbolique (S99supervisord, K99supervisord...). Les scripts sont lancés
dans l'ordre croissant de ces chiffres : si on veut que supervisord se
lance dans les derniers (recommandé, pour que tous les services
indispensables soient déjà lancés), il faut un chiffre élevé.&lt;/p&gt;
&lt;p&gt;Il ne reste plus qu'à lancer &lt;em&gt;supervisord&lt;/em&gt; maintenant et vérifier que
notre site est accessible :&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ /etc/init.d/supervisord start
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="monitorer-et-controler-ses-processus"&gt;
&lt;h3&gt;Monitorer et contrôler ses processus&lt;/h3&gt;
&lt;p&gt;Soit en utilisant le client CLI :&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ supervisorctl
&lt;/pre&gt;
&lt;p&gt;Soit en accédant à la page web dont on a configuré l'adresse dans le
paramètre &lt;em&gt;inet_http_server&lt;/em&gt; dans le fichier de configuration de
&lt;em&gt;supervisord&lt;/em&gt;. De là vous pouvez redémarrer vos processus, les arrêter,
visualiser le contenu du fichier de log...&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="un-soucis"&gt;
&lt;h3&gt;Un soucis?&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Pensez à regarder dans les fichiers de log indiqués dans les
paramètres pour voir si il y a des indications sur le problème&lt;/li&gt;
&lt;li&gt;Vérifiez que la commande configuré dans /etc/&lt;em&gt;supervisord.conf&lt;/em&gt; se
lance correctement manuellement, et que le processus est bien en
&lt;em&gt;foreground&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Faites un tour sur l'excellente documentation du &lt;a class="reference external" href="http://supervisord.org/"&gt;projet supervisord&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Edit du 2010-12-14 : rajout de la priorité de lancement lors de l'appel
de la commande &lt;em&gt;update-rc.d&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
</summary></entry><entry><title>PyCon.fr 2010 : retour sur une conférence organisée par l'AFPY</title><link href="http://mathieu.agopian.info/blog/pycon-fr-2010-retour-sur-une-conference-organisee-par-lafpy.html" rel="alternate"></link><updated>2010-08-30T08:53:00Z</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2010-08-30:/blog/pycon-fr-2010-retour-sur-une-conference-organisee-par-lafpy.html/</id><summary type="html">&lt;p&gt;&lt;a class="reference external" href="http://www.pycon.fr/conference/edition2010"&gt;Cette édition de PyCon.fr&lt;/a&gt; était la deuxième à laquelle j'ai assisté.
Elle s'est déroulée, comme l'année dernière, à la CyberBase de la Cité
des Sciences à la Villette, Paris.&lt;/p&gt;
&lt;p&gt;Et comme l'année dernière, j'ai apporté ma maigre contribution à
l'organisation sur place, aux côté des super-motivés de l'&lt;a class="reference external" href="http://afpy.org"&gt;Association
Francophone PYthon&lt;/a&gt; (liste non exhaustive : Christophe Combelles,
Michael Scherrer, Gaël Pasgrimaud, Jean-Philippe Camguilhem, Olivier
Grisel).&lt;/p&gt;
&lt;div class="section" id="le-cadre"&gt;
&lt;h2&gt;Le cadre&lt;/h2&gt;
&lt;p&gt;Nous avons été encore une fois super bien accueillis par la CyberBase.
A disposition, un amphitéatre et une &amp;quot;base technique&amp;quot; (salle équipée de
nombreux ordinateurs, idéale pour les mises en applications et les
travaux pratiques). Les deux salles ont tout l'équipement nécessaire
pour la rétro-projection des présentations, et avaient cette année
encore la couverture vidéo de &lt;a class="reference external" href="http://ubicast.eu"&gt;Ubicast&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="les-sponsors"&gt;
&lt;h2&gt;Les sponsors&lt;/h2&gt;
&lt;p&gt;Vous trouverez la &lt;a class="reference external" href="http://www.pycon.fr/view?rql=Any+X,XD,RT+WHERE+C+eid+1450,+R+sponsoring_conf+C,+X+is_sponsor+R,+R+title+RT,+X+description+XD"&gt;liste des généreux sponsors&lt;/a&gt; sur le site de
&lt;a class="reference external" href="http://www.pycon.fr/conference/edition2010"&gt;PyCon.fr 2010&lt;/a&gt;. C'est en très grande partie grâce à eux qu'une telle
conférence peut être organisée, en restant gratuite, j'en profite donc
pour les remercier à nouveau (et les encourager à recommencer l'année
prochaine bien évidemment ;).&lt;/p&gt;
&lt;p&gt;Nous avons entre autres eu des présentations de plusieurs produits
libres et open-source des sponsors &lt;a class="reference external" href="http://www.logilab.fr"&gt;Logilab&lt;/a&gt; (avec leur solution Cubic
Web qui fait tourner le site de &lt;a class="reference external" href="http://pycon.fr"&gt;pycon.fr&lt;/a&gt;) et &lt;a class="reference external" href="http://itaapy.com"&gt;Itaapy&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="les-presentations"&gt;
&lt;h2&gt;Les présentations&lt;/h2&gt;
&lt;p&gt;Vous trouverez là aussi la liste sur le site de pycon.fr, et je vais
lister ici les présentations auxquelles j'ai pu assister et qui m'ont
marqué (les autres je les verrais en vidéo, grâce à &lt;a class="reference external" href="http://ubicast.eu"&gt;Ubicast&lt;/a&gt;, dès
qu'elles seront en ligne) :&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;plusieurs lightning talks, en particulier sur Pyaler, le GSoC sur
distutils 2 (vivement que ça sorte!), pysandbox, restkit&lt;/li&gt;
&lt;li&gt;Optimisation d'applications Django de Bruno Renié (mon co-champion
&amp;quot;planet&amp;quot; pour &lt;a class="reference external" href="http://django-fr.org"&gt;django-fr&lt;/a&gt; ;) : plein d'astuces pour limiter le
nombre de requêtes, utiliser le cache de manière intelligente ...&lt;/li&gt;
&lt;li&gt;MongoKit de Nicolas Clairon : comment utiliser une base NoSQL MongoDB
facilement et efficacement avec MongoKit&lt;/li&gt;
&lt;li&gt;Analyse statistique et classification automatique de texte de Olivier
Grisel : ça m'a replongé dans mon mémoire de DEA sur l'apprentissage
automatique ;)&lt;/li&gt;
&lt;li&gt;l'incontournabl)e Gunicorn de Benoit Chesneau : à mon avis, l'un des
plus grands pas en avant pour rendre python (et &lt;a class="reference external" href="http://www.djangoproject.com/"&gt;Django&lt;/a&gt;) très
facilement déployable&lt;/li&gt;
&lt;li&gt;Construire un CMS sur mesure avec Django de Yann Malet : il faut
arriver à vendre non pas du forfait (trop risqué pour le développeur,
trop cher pour le client, et risque d'effet tunnel), mais des cycles
de développement (par exemple de deux semaines, avec review +
paiement à chaque cycle, puis décision sur le cycle suivant). Voilà
quelque chose qui me plait, mais qui est très difficile à vendre à
des clients qui veulent surtout pouvoir chiffrer de manière très
précise leur budget&lt;/li&gt;
&lt;li&gt;Programmation en Flux de Jonathan Schemoul : présentation de &lt;a class="reference external" href="http://www.pyfproject.org/"&gt;PyF&lt;/a&gt;
qui permet de traiter de très nombreuses données en limitant la
mémoire utilisée (grâce aux &amp;quot;flux&amp;quot;). Très intéressant, et un outil
graphique en ligne qui m'a l'air très simple et pratique
d'utilisation&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="les-rencontres"&gt;
&lt;h2&gt;Les rencontres&lt;/h2&gt;
&lt;p&gt;J'ai pu revoir nombre de personnes déjà rencontrées à l'édition de
l'année précédent de PyCon.fr, ainsi qu'à &lt;a class="reference external" href="http://rencontres.django-fr.org/"&gt;Djangocong&lt;/a&gt;, personnes que
je connaissais déjà auparavant par internet (irc, twitter, mailing lists
de l'afpy et de django-fr ...). Revoir &amp;quot;en vrai&amp;quot; des personnes qu'on
côtoie très souvent de manière électronique est pour moi toujours une
joie et un enrichissement, car l'échange est bien plus facile, et on
tisse des liens bien plus étroits autour d'une bière que par internet!&lt;/p&gt;
&lt;p&gt;Et comme je l'ai &lt;a class="reference external" href="http://twitter.com/magopian"&gt;déjà twitté&lt;/a&gt;, j'ai par ailleurs été ravi de faire de
nouvelles connaissances, que j'espère pouvoir revoir bientôt (un an
entre chaque pycon.fr, ça fait long!). Il paraît qu'il devrait y avoir
d'ici peu un autre &lt;a class="reference external" href="http://www.afpy.org/Members/jpcw2002/national_afpyro_juillet_2010"&gt;afpyro national&lt;/a&gt; organisé, il faudra essayer de
rassembler les troupes sur Sophia/Nice à cette occasion! Peut-être une
occasion de tester le pastis magique de Jean-Philippe (il m'a promis
qu'il ferait un article là-dessus, que je lierai ici ;)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="les-bonus"&gt;
&lt;h2&gt;Les bonus&lt;/h2&gt;
&lt;p&gt;Comme si cela ne suffisait pas, il y a eu quelques bonus très
sympathiques :&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;un écran géant et tactile dans la CyberBase qui affichait en boucle
&lt;a class="reference external" href="http://isparade.jp/334515"&gt;isparade.jp sur le mot clé #pyconfr&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;j'ai découvert &lt;a class="reference external" href="http://bitbucket.org/bruno/aspirator/wiki/Home"&gt;aspirator&lt;/a&gt; de Bruno Renié, un news reader fait en
django, et qui à l'air vraiment génial... ce gars est vraiment doué,
ça en est limite énervant ;). Il travaille aussi sur wombat, un
projet de client mail&lt;/li&gt;
&lt;li&gt;j'ai revu une bonne partie de mon ex équipe de badminton qui prenait
le même avion que moi! (ils étaient aux championnats du monde qui
avait lieu ce même week-end sur Paris)&lt;/li&gt;
&lt;li&gt;Alexis Métaireau (&lt;a class="reference external" href="http://twitter.com/ametaireau"&gt;&amp;#64;ametaireau&lt;/a&gt;) nous a parlé de son travail pour le
GSoC avec Tarek Ziadé sur distutils 2 : vivement que ce soit dans la
stdlib!&lt;/li&gt;
&lt;li&gt;Rémi Ubscher (&lt;a class="reference external" href="http://twitter.com/natim"&gt;&amp;#64;natim&lt;/a&gt;) est Eclaireur lui aussi! Salut Fennec ;)&lt;/li&gt;
&lt;li&gt;Benoit Calvez (&lt;a class="reference external" href="http://twitter.com/_dzen"&gt;&amp;#64;_dzen&lt;/a&gt;) m'a parlé de &lt;a class="reference external" href="http://bitbucket.org/dzen/fabulator/overview"&gt;fabulator&lt;/a&gt; et de
&lt;a class="reference external" href="http://vineolia.fr/"&gt;vineolia&lt;/a&gt; : énorme potentiel (et il a d'autres projets dans les
cartons ;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Vivement la prochaine édition (peut-être sur Pau? ping Jean-Philippe ;)
et d'ici là, peut-être une nouvelle édition d'une rencontre Django en
france!&lt;/p&gt;
&lt;p&gt;Encore un énorme merci aux organisateurs, sponsors, présentateurs, et
aux participants, qui font de cet évènement un moment agréable et
enrichissant!&lt;/p&gt;
&lt;/div&gt;
</summary><category term="conference"></category></entry><entry><title>djangocong : rencontre Django à Marseille</title><link href="http://mathieu.agopian.info/blog/djangocong-rencontre-django-a-marseille.html" rel="alternate"></link><updated>2010-04-28T08:19:00Z</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2010-04-28:/blog/djangocong-rencontre-django-a-marseille.html/</id><summary type="html">&lt;p&gt;Comment décrire en un mot cette rencontre Django à Marseille le 24 et 25
Avril :&lt;/p&gt;
&lt;div class="section" id="excellent"&gt;
&lt;h2&gt;Excellent !&lt;/h2&gt;
&lt;p&gt;Après cette entrée en matière fort peu cavalière, permettez-moi de
développer en deux points :&lt;/p&gt;
&lt;div class="section" id="ce-que-j-ai-retenu-de-ma-conference"&gt;
&lt;h3&gt;Ce que j'ai retenu de ma conférence&lt;/h3&gt;
&lt;p&gt;Ma présentation portait sur toutes les astuces et raccourcis à la
disposition des développeurs web, et s'intitulait &lt;a class="reference external" href="http://agopian.info/djangocong/dplf.html"&gt;Django Pour Les Fainéants&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Les points positifs :&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;le format &amp;quot;interactif&amp;quot; : faire participer le public le plus possible
permet de le garder attentif, éveillé et intéressé&lt;/li&gt;
&lt;li&gt;les &amp;quot;bons points&amp;quot; : un autre moyen de captiver l'attention, en
récompensant par un bon point (humoristique) les participants&lt;ul&gt;
&lt;li&gt;bon point &amp;quot;glue&amp;quot; : le participant m'a posé une colle, à laquelle
je ne sais répondre&lt;/li&gt;
&lt;li&gt;bon point &amp;quot;fainéant&amp;quot; : le participant a proposé une
astuce/raccourcis/outil que je ne connaissais pas, ou n'ai pas
présenté dans la présentation&lt;/li&gt;
&lt;li&gt;bon point &amp;quot;django pony&amp;quot; : le participant a partagé une bonne
pratique django, ou parlé d'une astuce/raccourci/outil que je
présentais dans une diapo suivante&lt;/li&gt;
&lt;li&gt;bon point &amp;quot;casse nouilles&amp;quot; : donné à un complice, ou quelqu'un
dont on est sûr qu'il le prendra à la rigolade, et qui a fait une
remarque sur une typo, une parenthèse manquante...&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;les diapos &amp;quot;concises&amp;quot; ne contenant pas de phrases, mais des mots clé
ou listes de points : permet de garder l'attention du public sur ce
que l'on dit, en ne se servant des diapos que comme un support visuel
(pour pointer du doigt)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Les points négatifs :&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;le format &amp;quot;interactif&amp;quot; : difficulté à prévoir le temps de parole pour
la présentation. Si il y a beaucoup de participation, le temps de
parole prévu empiètera sur le temps consacré (25mn de présentation +
10mn de participation &amp;gt; 30mn). A contrario, si il n'y a pas assez
(pas du tout!) de participation, le temps de parole prévu sera trop
court (25mn &amp;lt; 30mn).&lt;/li&gt;
&lt;li&gt;le format &amp;quot;interactif&amp;quot; et les &amp;quot;bons points&amp;quot; : applicable pour des
salles plus grandes, avec un public plus nombreux? Avec 50 personnes,
je me demande si on atteignait pas la limite pour ce genre de
format...&lt;/li&gt;
&lt;li&gt;les diapos &amp;quot;concises&amp;quot; l'étaient peut-être trop : rajouter des photos
comme par exemple pour les présentations de &lt;a class="reference external" href="http://neokraft.net/2010/rencontres-django"&gt;Olivier Meunier&lt;/a&gt; pour
que ce soit plus agréable visuellement (ou comme pour celles de Cyril
Baÿ, private joke inside ;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Dans l'ensemble, je pense que la présentation s'est bien passée, et a
été bien reçue d'après les retours que j'en ai eu (merci à tous ceux qui
sont venus me complimenter, ça fait chaud au coeur!). N'hésitez pas à me
faire vos retours (positifs ET négatifs) dans les commentaires!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="ce-que-j-ai-retenu-des-autres-conferences"&gt;
&lt;h3&gt;Ce que j'ai retenu des autres conférences&lt;/h3&gt;
&lt;p&gt;Il y a déjà plusieurs comptes rendus très complets sur la toile (&lt;a class="reference external" href="http://blog.alwaysdata.com/fr/2010/04/27/francais-compte-rendu-des-rencontres-django-a-marseille/#more-113"&gt;liste
exhaustive faite par Nicolas Ferrari sur le blog d'AlwaysData&lt;/a&gt;), je
vais donc essayer de résumer ce que j'ai retenu de chaque présentation :&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Django 1.2 : un point de nouveautés, par &lt;a class="reference external" href="http://www.alwaysdata.com"&gt;Nicolas Ferrari&lt;/a&gt; : lire
les releases notes n'est pas suffisant! pour connaître toutes les
excellentes améliorations de la 1.2, il faut soit lire la conf de
Nicolas, soit suivre &lt;a class="reference external" href="http://djangoadvent.com/"&gt;DjangoAdvent&lt;/a&gt; ;)&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://blog.sietch-tabr.com/public/presentation_boite_a_outils_django_DjangoCong2010.pdf"&gt;Boîte à outils Django&lt;/a&gt;, par &lt;a class="reference external" href="http://blog.sietch-tabr.com/"&gt;Eric Veiras Galisson&lt;/a&gt; : de multiples
outils pour vraiment être un fainéant (cf ma présentation) et se
simplifier la vie !&lt;/li&gt;
&lt;li&gt;Cours de géo, par &lt;a class="reference external" href="http://github.com/samueladam"&gt;Samuel Adam&lt;/a&gt; : c'est pas si compliqué finalement,
et LA TERRE N'EST PAS RONDE! présentation très claire sur les outils
disponibles et leur puissance&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://neokraft.net/public/2010/djangocong/auth.pdf"&gt;Une authentification pour les contrôler tous&lt;/a&gt;, par &lt;a class="reference external" href="http://neokraft.net/2010/rencontres-django"&gt;Olivier
Meunier&lt;/a&gt; : une &amp;quot;fail story&amp;quot;, au moins aussi importante que la
&amp;quot;success story&amp;quot; aussi présentée, sur comment adapter
l'authentification django à sa sauce&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://media.bruno.im/djangocong-testing.pdf"&gt;Tester son projet Django&lt;/a&gt;, par &lt;a class="reference external" href="http://bruno.renie.fr/"&gt;Bruno Renié&lt;/a&gt; : on ne le dit jamais
assez, il faut tester ses applications. Non seulement un garde-fou,
c'est aussi un gage de la qualité du code final! (et chapeau pour la
présentation faite au pied levé pour remplacer un conférencier qui
n'a pu venir, et ce moins de trois jours avant la conférence!)&lt;/li&gt;
&lt;li&gt;Les dessous d’alwaysdata, par &lt;a class="reference external" href="http://www.alwaysdata.com"&gt;Cyril&lt;/a&gt; : on a pas vu beaucoup de
dessous, mais on en a appris énormément sur AlwaysData, leur sérieux,
leur compétence, et leur boulot au quotidien pour nous simplifier la
vie (et nous permettre d'être fainéants!)&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://www.slideshare.net/nperriault/symfony-pour-les-dveloppeurs-django-et-rciproquement"&gt;Django pour les développeurs Symfony&lt;/a&gt;, par &lt;a class="reference external" href="http://prendreuncafe.com/"&gt;Nicolas Perriault&lt;/a&gt; :
superbe présentation, donnée de main de maître (et hilarante), qui
donne un regard nouveau sur ce qu'il est possible de faire avec un
langage comme PHP. Au final, Symfony à l'air d'être une bonne
alternative à Django quand le client ne veut pas entendre parler
d'autre chose que PHP.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://media.jehaisleprintemps.net/talks/djangocong-2010/"&gt;Beer over IP&lt;/a&gt;, par &lt;a class="reference external" href="http://jehaisleprintemps.net/blog/"&gt;Bruno Bord&lt;/a&gt; : retour d'expérience sur un site
reconnu (par moi en tout cas ;) d'utilité publique.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://j-mad.com/blog/wp-content/uploads/2010/04/Djangocong_2010-Django_et_XMPP.pdf"&gt;Django et XMPP&lt;/a&gt;, par &lt;a class="reference external" href="http://j-mad.com/blog/"&gt;Jean-Michel Armand&lt;/a&gt; : quand utiliser XMPP,
et surtout quand ne pas l'utiliser! Un retour d'expérience donné par
notre cher Jean-Michel préféré!&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://www.slideshare.net/beoitc/gunicorn-django-djangocong20100425"&gt;Gunicorn, Django et WSGI&lt;/a&gt;, par &lt;a class="reference external" href="http://benoitc.im/"&gt;Benoît Chesneau&lt;/a&gt; : présentation
d'un outil que j'utilise en production depuis bientôt un mois sans
jamais avoir eu le moindre soucis.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://neokraft.net/public/2010/djangocong/amqp.pdf"&gt;Des lapins et des poneys, quand AMPQ rencontre Django&lt;/a&gt;, par
&lt;a class="reference external" href="http://neokraft.net/2010/rencontres-django"&gt;Olivier Meunier&lt;/a&gt; : explication d'une mise en oeuvre de AMPQ comme
moyen de communication entre différents processus, très instructif&lt;/li&gt;
&lt;li&gt;Introduction à &lt;a class="reference external" href="http://pinaxproject.com/"&gt;Pinax&lt;/a&gt;, par David Paccoud : boîte à outil pleine
d'applications réutilisables qui permet d'arriver à un produit fini
avec moins d'efforts et plus rapidement. La question étant, comment
faire la part entre un projet &amp;quot;fait main&amp;quot; qu'on maîtrise, et une
boîte à outil qu'on ne maîtrise pas forcément. Qu'en est-il de la
courbe d'apprentissage?&lt;/li&gt;
&lt;li&gt;« &lt;a class="reference external" href="http://copyleft.free.fr/djangocong-i18n/"&gt;Tain cong, Django speaks marseillais&lt;/a&gt;, par &lt;a class="reference external" href="http://copyleft.free.fr/"&gt;Stéphane Raimbault&lt;/a&gt; : il y a plusieurs formes plurielles dans certains
langages (Polonais par exemple), et beaucoup plus de spécificités que
je ne m'en doutais! Il va falloir que je reprenne ma copie...&lt;/li&gt;
&lt;li&gt;Internationalisation de contenus avec Django, par &lt;a class="reference external" href="http://www.marmelune.net/"&gt;Benoît Bryon&lt;/a&gt; :
présentation très claire des limites (et de certaines solutions) de
Django pour tout ce qui à trait à la traduction et localisation de
sites&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://www.slideshare.net/beoitc/couchdbkit-1-django"&gt;CouchDB et Django, l’utilisation de CouchDBKit&lt;/a&gt;, par &lt;a class="reference external" href="http://benoitc.im/"&gt;Benoît
Chesneau&lt;/a&gt; : pas forcément évident à prendre en main, CouchDB est
sensé être le choix incontournable quand on tient à ses données
(multiples mécanismes comme l'écriture régulière sur le disque pour
éviter les pertes de données lors d'un crash)&lt;/li&gt;
&lt;li&gt;Les limites de Django, par &lt;a class="reference external" href="http://larlet.fr/"&gt;David Larlet&lt;/a&gt; : qui n'a pas été
confronté à certaines de ces limites? Un regard réaliste sur ce qu'on
ne peut tout simplement pas faire (à l'heure actuelle) avec Django.&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="ce-que-j-ai-retenu-de-l-orga"&gt;
&lt;h3&gt;Ce que j'ai retenu de l'orga&lt;/h3&gt;
&lt;p&gt;Un immense merci à David et Jean-Michel, sans qui nous n'aurions tout
simplement pas eu cette occasion de se rencontrer entre passionnés de
Django (et d'autres! dédicace à &lt;a class="reference external" href="http://jeremy.wordpress.com/2010/04/25/djangocong-2010/"&gt;Jérémy Lecour&lt;/a&gt;), et de voir en chair
et en os des personnes (oserai-je dire amis?) qu'on cotoie parfois tous
les jours sur internet, certains depuis des années! Merci aussi à Johan
Charpentier pour l'impression des TShirts!&lt;/p&gt;
&lt;p&gt;J'ai été vraiment impressionné par la ponctualité (malgré les aléas de
la restauration et ses délais imprévus), le contenu très fourni et
divers de toutes les présentations, le professionnalisme et en même
temps l'accessibilité de chacun. Pouvoir discuter et échanger des
expériences avec des collègues (comme on dit dans le sud ;) est vraiment
enrichissant.&lt;/p&gt;
&lt;p&gt;Plus que tout, je tiens encore une fois à remercier Jean-Michel, qui
s'est occupé d'une (très) grande partie de la logistique, de nous avoir
trouvé les salles, chouchoutés (combien d'aller/retours il a fait pour
remplir la cafetière? et de trajets en voiture pour amener/chercher des
gens au métro/restau?), et de s'être autant appliqué à nous apporter
tout le confort possible.&lt;/p&gt;
&lt;p&gt;Vivement la prochaine rencontre!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
</summary><category term="conference"></category></entry><entry><title>MySQL, mysqldump et PHP : convertir de latin1 vers utf8</title><link href="http://mathieu.agopian.info/blog/mysql-mysqldump-et-php-convertir-de-latin1-vers-utf8.html" rel="alternate"></link><updated>2010-03-08T07:38:00Z</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2010-03-08:/blog/mysql-mysqldump-et-php-convertir-de-latin1-vers-utf8.html/</id><summary type="html">&lt;p&gt;Cet article à pour but de vous éviter, à vous lecteur, de vivre la perte
de neurones (et le gain de cheveux blancs) que j'ai subit dernièrement,
à investiguer des soucis de charset dans une base de donnée MySQL (et
l'affichage sur une page web par le biais de PHP).&lt;/p&gt;
&lt;p&gt;Je tiens à préciser que je ne suis pas un expert MySQL, et encore moins
un expert en encoding, et certaines définitions ou mots utilisés dans
cet article peuvent ne pas être utilisés à bon escient. Le fond et la
méthode présentée ont par contre été vérifiés et testés!&lt;/p&gt;
&lt;div class="section" id="introduction-a-l-encoding"&gt;
&lt;h2&gt;Introduction à l'encoding&lt;/h2&gt;
&lt;p&gt;Je ne parlerais pas ici de ASCII ou Unicode (normes utilisées pour
stocker les données), mais du jeu de caractères utilisé pour &lt;em&gt;encoder&lt;/em&gt;
ces données (et les afficher de manière lisible pour un être humain).
Pour commencer, quelques définitions:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="http://fr.wikipedia.org/wiki/Charset"&gt;encoding&lt;/a&gt; = character set = charset : jeu de caractères utilisé
pour représenter des données&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://fr.wikipedia.org/wiki/Utf8"&gt;utf8&lt;/a&gt; = UTF-8 : un encoding qui associe un caractère à chaque
&amp;quot;codepoint&amp;quot; &lt;a class="reference external" href="http://fr.wikipedia.org/wiki/Unicode"&gt;Unicode&lt;/a&gt; (particularité: tous les caractères hors
&lt;em&gt;latin1&lt;/em&gt; sont stockés sur deux octets)&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://fr.wikipedia.org/wiki/Latin1"&gt;latin1&lt;/a&gt; = latin-1 = ISO-8859-1 : un encoding qui associe un
caractère à chaque octet de la table &lt;a class="reference external" href="http://fr.wikipedia.org/wiki/American_Standard_Code_for_Information_Interchange"&gt;ASCII&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Pour résumer, chaque caractère peut être stocké sur le disque en
Unicode (ou en ASCII, beaucoup plus limité). Il est ensuite &lt;em&gt;encodé&lt;/em&gt;
(traduit, représenté) avec un jeu de caractères pour être affichable et
lisible par un être humain.&lt;/p&gt;
&lt;p&gt;Historiquement, pour les pays occidentaux, l'encodage était fait en
latin1 (caractères latins avec ses accentuations). De nos jours, de plus
en plus d'applications se tournent vers Unicode et l'encodage en UTF-8
qui permet de représenter l'ensemble des caractères utilisés
universellement (il n'est donc pas limité aux caractères latins, mais
inclut par exemple les caractères cyrilliques, chinois...).&lt;/p&gt;
&lt;div class="section" id="charset-utilise-par-les-tables-et-les-champs"&gt;
&lt;h3&gt;Charset utilisé par les tables et les champs&lt;/h3&gt;
&lt;p&gt;Pour consulter l'encodage utilisé par défaut pour une table ou un champ
particulier :&lt;/p&gt;
&lt;pre class="literal-block"&gt;
mysql&amp;gt; SHOW CREATE TABLE bar;
+-------+-------------------------------+
| Table | Create Table                  |
+-------+-------------------------------+
| bar   | CREATE TABLE `bar` (
  `id` int(11) default NULL,
  `firstname` char(20) default NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1 |
+-------+-------------------------------+
1 row in set (0.00 sec)
&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;ATTENTION :&lt;/strong&gt; les charset définits au niveau de la base de donnée, de
la table et du champ sont des &amp;quot;default charset&amp;quot;. Il est tout à fait
possible d'avoir une table avec un champ dont le contenu est en
&lt;em&gt;latin1&lt;/em&gt;, puis changer le &lt;em&gt;DEFAULT CHARACTER SET&lt;/em&gt; à &lt;em&gt;utf8&lt;/em&gt; pour ce
champ. Toutes les données existantes seront toujours en &lt;em&gt;latin1&lt;/em&gt;, par
contre toutes les nouvelles données entrées en &lt;em&gt;utf8&lt;/em&gt; seront en &lt;em&gt;utf8&lt;/em&gt;.
On est alors confronté au pire des problèmes : des charsets différents
au sein d'une table pour un même champ.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="charset-utilise-par-le-serveur-la-database-les-tables-les-champs-le-client-la-connexion-les-resultats"&gt;
&lt;h3&gt;Charset utilisé par le serveur, la database, les tables, les champs, le client, la connexion, les résultats...&lt;/h3&gt;
&lt;p&gt;Les encodages utilisés par le client, la connexion, le serveur, et
l'affichage des résultats sont consultable par la commande suivante:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
mysql&amp;gt; SHOW VARIABLES WHERE variable_name like 'char%';
+--------------------------+----------------------------+
| Variable_name &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;| Value &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;|
+--------------------------+----------------------------+
| character_set_client &amp;nbsp; &amp;nbsp; | utf8 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; |
| character_set_connection | utf8 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; |
| character_set_database &amp;nbsp; | utf8 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; |
| character_set_filesystem | binary &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; |
| character_set_results &amp;nbsp; &amp;nbsp;| utf8 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; |
| character_set_server &amp;nbsp; &amp;nbsp; | latin1 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; |
| character_set_system &amp;nbsp; &amp;nbsp; | utf8 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; |
| character_sets_dir &amp;nbsp; &amp;nbsp; &amp;nbsp; | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
8 rows in set (0.00 sec)
&lt;/pre&gt;
&lt;p&gt;Dans cet exemple, le client, la connexion, la database et les résultats
sont tous en &lt;em&gt;utf8&lt;/em&gt;. Il n'y a que le serveur lui-même qui est en
&lt;em&gt;latin1&lt;/em&gt; par défaut. Pour configurer le client, la connexion et les
résultats, on peut soit utiliser la commande&lt;/p&gt;
&lt;pre class="literal-block"&gt;
mysql&amp;gt; SET NAMES utf8;
&lt;/pre&gt;
&lt;p&gt;Soit configurer les variables décrites dans le paragraphe suivant :&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="les-variables-de-configuration"&gt;
&lt;h3&gt;Les variables de configuration&lt;/h3&gt;
&lt;p&gt;Elles peuvent être définies au niveau du fichier &lt;em&gt;/etc/mysql/my.cnf&lt;/em&gt;
(peut être situé à un autre endroit selon la distribution):&lt;/p&gt;
&lt;pre class="literal-block"&gt;
[mysqld]
default-character-set=utf8
character-set-server=utf8
collation-server=utf8_general_ci # utilisé pour les comparaisons

[mysqldump]
default-character-set=utf8

[mysql]
default-character-set=utf8
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="l-encoding-du-terminal-attention-au-piege"&gt;
&lt;h2&gt;L'encoding du terminal: attention au piège!&lt;/h2&gt;
&lt;p&gt;Quels que soit les charsets (par défaut) que vous avez configurés, il
faut savoir que c'est le charset du terminal (si vous êtes en ligne de
commande sur &lt;em&gt;mysql&lt;/em&gt; par exemple) qui sera utilisé lors d'un &lt;em&gt;UPDATE&lt;/em&gt; ou
&lt;em&gt;INSERT&lt;/em&gt; dans une table.&lt;/p&gt;
&lt;p&gt;Ainsi, même si vous avez tout configuré (y compris le &lt;em&gt;SET NAMES)&lt;/em&gt; pour
être en &lt;em&gt;latin1&lt;/em&gt;, lors d'une insertion dans une table, si votre terminal
est en &lt;em&gt;utf8&lt;/em&gt; la donnée sera stockée en &lt;em&gt;utf8&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Reportez-vous à &lt;a class="reference external" href="http://www.tuteurs.ens.fr/faq/utf8.html#test"&gt;cette astuce&lt;/a&gt; pour tester le charset (&lt;em&gt;latin1&lt;/em&gt; ou
&lt;em&gt;utf8&lt;/em&gt;) de votre terminal. Il faut &lt;strong&gt;ABSOLUMENT que votre client ai le
même encoding que votre terminal pour éviter les `conflits`_ (à partir
de la page 21).&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="detecter-le-charset-utilise-pour-un-champ"&gt;
&lt;h2&gt;Détecter le charset utilisé pour un champ&lt;/h2&gt;
&lt;p&gt;Commençons par une astuce pour différencier une donnée stockée en
&lt;em&gt;utf8&lt;/em&gt; de &lt;em&gt;latin1 :&lt;/em&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
mysql&amp;gt; select firstname, length(firstname) from bar;
+-----------+-------------------+
| firstname | length(firstname) |
+-----------+-------------------+
| dédé    |                 6 |
+-----------+-------------------+
1 row in set (0.00 sec)
&lt;/pre&gt;
&lt;p&gt;6 octets pour stocker 4 caractères ? C'est de l'&lt;em&gt;utf8&lt;/em&gt;! Les accents
sont stockés sur deux octets. Si ça avait été du &lt;em&gt;latin1&lt;/em&gt;, la longueur
de la donnée aurait été de 4 octets.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="mais-alors-je-peux-demander-a-mysql-de-convertir-mes-donnees"&gt;
&lt;h2&gt;Mais alors, je peux demander à MySQL de convertir mes données ?&lt;/h2&gt;
&lt;p&gt;Oui, mais pour savoir où aller, il faut savoir d'où on vient: avant de
demander à MySQL de convertir une donnée, il faut connaitre son encodage
actuel, et surtout dans quel encodage MySQL croit que ces données sont.&lt;/p&gt;
&lt;p&gt;Il faut bien garder en tête que lorsqu'on parle du charset d'une table,
d'un champ, d'une base de donnée... on parle du charset par défaut, et
donc du charset que le serveur va utiliser pour insérer/retourner des
données. Cela n'a aucune incidence sur l'encodage utilisé auparavant
pour les données.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;charset du serveur égal au charset du client : aucune conversion
n'est faite&lt;/li&gt;
&lt;li&gt;charset du serveur en &lt;em&gt;latin1&lt;/em&gt;, charset du client en &lt;em&gt;utf8&lt;/em&gt; : la
donnée va être encodée en &lt;em&gt;utf8&lt;/em&gt; (même si elle l'était déjà =&amp;gt;
problème de double encoding)&lt;/li&gt;
&lt;li&gt;charset du serveur en &lt;em&gt;utf8&lt;/em&gt;, charset du client en &lt;em&gt;latin1&lt;/em&gt; : la
donnée va être encodée en &lt;em&gt;latin1&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="mes-donnees-sont-stockees-en-utf8-et-mysql-ne-le-sait-pas"&gt;
&lt;h2&gt;Mes données sont stockées en &lt;em&gt;utf8&lt;/em&gt; et MySQL ne le sait pas!&lt;/h2&gt;
&lt;p&gt;Symptôme: quand on affiche le &lt;em&gt;length&lt;/em&gt; d'une donnée avec des caractères
accentués, ça donne un nombre d'octets plus grands que le nombre de
caractères. La donnée est donc en &lt;em&gt;utf8&lt;/em&gt;. Par contre, le serveur, la db,
la table, le champ... sont configurés pour être en &lt;em&gt;latin1&lt;/em&gt;. Et quand on
essaie de faire un &lt;em&gt;SET NAMES utf8&lt;/em&gt;, la donnée s'affiche avec des &amp;quot;Ã©&amp;quot; :
dans ce cas, c'est une donnée stockée en &lt;em&gt;utf8&lt;/em&gt;, mais qui est
interprétée comme du &lt;em&gt;latin1&lt;/em&gt; par MySQL, qui va donc l'encoder une
seconde fois en &lt;em&gt;utf8&lt;/em&gt; (problème de double encoding).&lt;/p&gt;
&lt;div class="section" id="la-solution"&gt;
&lt;h3&gt;La solution :&lt;/h3&gt;
&lt;p&gt;Le serveur pense que les données sont en &lt;em&gt;latin1&lt;/em&gt; et on sait qu'elles
sont en &lt;em&gt;utf8&lt;/em&gt; (notre charset final souhaité). Il suffit de&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;faire un dump de la base dans un fichier en &lt;em&gt;latin1&lt;/em&gt; pour qu'il n'y
ai aucune conversion (pas de double encoding)&lt;/li&gt;
&lt;li&gt;modifier ce fichier pour y faire disparaitre toute trace de &amp;quot;latin1&amp;quot;&lt;/li&gt;
&lt;li&gt;configurer la table, la base de donnée et le serveur pour qu'ils
soient en &amp;quot;default charset &lt;em&gt;utf8&lt;/em&gt;&amp;quot; (cf le chapitre sur les variables
de configuration)&lt;/li&gt;
&lt;li&gt;réimporter les données dedans&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="mes-donnees-sont-stockees-en-latin1-et-mysql-le-sait-mais-je-les-veux-en-utf8"&gt;
&lt;h2&gt;Mes données sont stockées en &lt;em&gt;latin1&lt;/em&gt; et MySQL le sait, mais je les veux en &lt;em&gt;utf8&lt;/em&gt;&lt;/h2&gt;
&lt;p&gt;Vu que le serveur sait que ses données sont en &lt;em&gt;latin1&lt;/em&gt;, il suffit de
lui demander de nous les fournir en &lt;em&gt;utf8&lt;/em&gt; :&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;faire un dump de la base dans un fichier en &lt;em&gt;utf8&lt;/em&gt; pour qu'il y ai
une conversion automatique à partir de &lt;em&gt;latin1&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;modifier ce fichier pour y faire disparaitre toute trace de &amp;quot;latin1&amp;quot;&lt;/li&gt;
&lt;li&gt;configurer la table, la base de donnée et le serveur pour qu'ils
soient en &amp;quot;default charset utf8&amp;quot; (cf le chapitre sur les variables de
configuration)&lt;/li&gt;
&lt;li&gt;réimporter les données dedans&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="et-php-dans-tout-ca-avant-ca-marchait-maintenant-j-ai-des"&gt;
&lt;h2&gt;Et PHP dans tout ça? Avant ça marchait, maintenant j'ai des � !&lt;/h2&gt;
&lt;p&gt;Ce cher PHP (hint: passez à &lt;a class="reference external" href="http://www.djangoproject.com/"&gt;Django&lt;/a&gt;! c'est bien plus beau!) ne prends
pas en compte les configurations mises au niveau du serveur (ou du
fichier de configuration) pour son encoding!&lt;/p&gt;
&lt;p&gt;Par défaut la commande &lt;em&gt;mysql_connect&lt;/em&gt; va toujours utiliser le charset
&lt;em&gt;latin1&lt;/em&gt; : vous pouvez en avoir la preuve avec la commande
&lt;em&gt;mysql_client_encoding&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;PHP va donc vous fournir des données interprétées en &lt;em&gt;latin1&lt;/em&gt; alors
qu'elles sont en &lt;em&gt;utf8&lt;/em&gt;, d'où les caractères � non valides.&lt;/p&gt;
&lt;p&gt;Il suffit alors d'utiliser la commande &lt;em&gt;mysql_set_charset('utf8', $connection)&lt;/em&gt; sur la connexion ouverte avec &lt;em&gt;mysql_connect&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Faites bien attention d'avoir définit &lt;em&gt;utf8&lt;/em&gt; pour l'encoding de vos
pages HTML soit par une balise &lt;em&gt;meta&lt;/em&gt; dans votre entête de page, ou en
ayant configuré votre serveur web pour servir les pages en &lt;em&gt;utf8&lt;/em&gt;. Un
moyen simple de vérifier ça est d'afficher les informations de la page.&lt;/p&gt;
&lt;/div&gt;
</summary><category term="mysql"></category><category term="php"></category></entry><entry><title>Django : Envoyer des emails HTML avec images inline (intégrées)</title><link href="http://mathieu.agopian.info/blog/django-envoyer-des-emails-html-avec-images-inline-integrees.html" rel="alternate"></link><updated>2010-02-17T10:37:00Z</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2010-02-17:/blog/django-envoyer-des-emails-html-avec-images-inline-integrees.html/</id><summary type="html">&lt;p&gt;Voyons comment envoyer des emails multiparties (texte et HTML) avec des
images &lt;em&gt;inline&lt;/em&gt; (intégrées dans le mail lui-même, et non en pièce
jointe), et ceci en utilisant des templates afin de profiter (par
exemple) de l'i18n avec gettext, des filtres et tags, de l'utilisation
du contexte...&lt;/p&gt;
&lt;p&gt;Ce code est un mélange de deux méthodes complémentaires, une sur les
&lt;a class="reference external" href="http://www.rossp.org/blog/2007/oct/25/easy-multi-part-e-mails-django/"&gt;mails HTML par Ross Poulton&lt;/a&gt;, et l'autre venant d'un &lt;a class="reference external" href="http://www.djangosnippets.org/snippets/285/"&gt;djangosnippet
par sleytr&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Pour en faciliter l'utilisation et la maintenance, j'ai mis ce petit
module &lt;a class="reference external" href="http://bitbucket.org/magopian/django-nice-emails/"&gt;django-nice-emails sur bitbucket&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Plutôt que de copier le code ici, je vais plutôt en décrire les grandes
étapes:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Utiliser le setting &lt;em&gt;DEFAULT_FROM_EMAIL&lt;/em&gt; si l'expéditeur n'est pas
fourni&lt;/li&gt;
&lt;li&gt;Créer un &lt;em&gt;django.template.Context&lt;/em&gt; à partir du dictionnaire fourni
(permet de remplacer les &lt;em&gt;{{ var }}&lt;/em&gt; dans les templates)&lt;/li&gt;
&lt;li&gt;Utiliser le contexte créé pour initialiser le contenu texte, HTML
ainsi que le sujet&lt;/li&gt;
&lt;li&gt;Transformer le destinataire fourni en liste (si ce n'est pas déjà une
liste de destinataires)&lt;/li&gt;
&lt;li&gt;Créer un &lt;em&gt;django.core.mail.EmailMultiAlternatives&lt;/em&gt; qui est la base de
notre email (basé sur le contenu texte)&lt;/li&gt;
&lt;li&gt;Rajouter la partie HTML&lt;/li&gt;
&lt;li&gt;Rajouter les images en &lt;em&gt;inline&lt;/em&gt; si nécéssaire&lt;/li&gt;
&lt;li&gt;Envoyer le mai&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Rien de compliqué donc dans ce code qui fait moins de 20 lignes
&amp;quot;utiles&amp;quot;.&lt;/p&gt;
&lt;p&gt;Voyons maintenant un exemple d'utilisation avec de la traduction et de
l'héritage de templates:&lt;/p&gt;
&lt;div class="section" id="les-templates"&gt;
&lt;h2&gt;Les templates&lt;/h2&gt;
&lt;p&gt;On utilise ici la méthode de Ross Poulton qui consiste à ne fournir en
paramètre &lt;em&gt;template_name&lt;/em&gt; que la base du nom de fichier, sans
l'extension. On fournit ensuite au &lt;em&gt;django.template.loader&lt;/em&gt; ce
&lt;em&gt;template_name&lt;/em&gt; avec l'extension &lt;em&gt;.txt&lt;/em&gt; et &lt;em&gt;.html&lt;/em&gt;, ces templates
doivent donc exister tous les deux.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;templates/test_email.txt&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
{% load i18n %}
{% trans &amp;quot;Bonjour&amp;quot; %} {{ nom }},

{% blocktrans %}Ceci est un exemple de &amp;quot;nice-email&amp;quot; que je vous fait parvenir,
à titre d'exemple, et bien que vous vous en fichiez{% endblocktrans %}.

{% trans &amp;quot;Cordialement&amp;quot; %}

Mathieu Agopian
&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;templates/test_email.html&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
{% extends &amp;quot;base_email.html&amp;quot; %}
{% load i18n %}
{% block email_content %}
&amp;lt;p&amp;gt;{% trans &amp;quot;Bonjour&amp;quot; %} {{ nom }},&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;
    {% blocktrans %}Ceci est un exemple de &amp;quot;nice-email&amp;quot; que je vous fait parvenir,
    à titre d'exemple, et bien que vous vous en fichiez{% endblocktrans %}.
&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;{% trans &amp;quot;Cordialement&amp;quot; %}&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;Mathieu Agopian&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;img src=&amp;quot;cid:signature&amp;quot; /&amp;gt;
{% endblock email_content %}
&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;templates/base_email.html&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;lt;table width=&amp;quot;600&amp;quot;&amp;gt;
&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;img src=&amp;quot;cid:logo&amp;quot; /&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;
    {% block email_content %}{% endblock email_content %}
&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="le-contexte"&gt;
&lt;h2&gt;Le contexte&lt;/h2&gt;
&lt;p&gt;Un simple dictionnaire python pour chaque tag utilisé dans les
templates:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
context = {'nom': 'Johnny Biboul'}
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="les-images"&gt;
&lt;h2&gt;Les images&lt;/h2&gt;
&lt;p&gt;Elles doivent être passées en paramètres dans un tuple de tuples, sous
la forme (('/chemin/vers/image.png', 'tagimage'),
'/chemin/vers/image2.png', 'tagimage2'), ...). Si les images sont dans
le répertoire &lt;em&gt;images&lt;/em&gt; du &lt;em&gt;MEDIA_ROOT&lt;/em&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
images = (
    (path.join(settings.MEDIA_ROOT, 'images', 'signature.png'), 'signature'),
    (path.join(settings.MEDIA_ROOT, 'image', 'logo.png'), 'logo'))
&lt;/pre&gt;
&lt;p&gt;Dans les templates, on utilisera les images sous la forme &lt;em&gt;&amp;lt;img
src='cid:tagimage' /&amp;gt;&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="le-code"&gt;
&lt;h2&gt;Le code&lt;/h2&gt;
&lt;pre class="literal-block"&gt;
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from os import path
from django.conf import settings
from django.utils.translation import ugettext
from utils.nicemails import send_nice_email


context = {'nom': 'Johnny Biboul'}
images = (
    (path.join(settings.MEDIA_ROOT, 'images', 'signature.png'), 'signature'),
    (path.join(settings.MEDIA_ROOT, 'images', 'logo.png'), 'logo'))
subject = ugettext(u&amp;quot;Test de mail pour %(nom)s&amp;quot;) % {'nom': '{{ nom }}'}
send_nice_email(template_name='test_email',
                email_context=context,
                subject=subject,
                recipients='johnny&amp;#64;biboul.com',
                sender='foo bar ',
                images=images)
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Il vous suffit de mettre ce code dans une de vos vues pour pouvoir
faire de jolis mails de confirmation d'inscription, des newsletters, ou
voir même (bouh! c'est mal!) du mass-mailing. Veillez néanmoins à ne pas
forcer la dose sur le html, ou les images inlines!&lt;/p&gt;
&lt;/div&gt;
</summary></entry><entry><title>lancer gunicorn avec runit</title><link href="http://mathieu.agopian.info/blog/lancer-gunicorn-avec-runit.html" rel="alternate"></link><updated>2010-02-10T11:37:00Z</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2010-02-10:/blog/lancer-gunicorn-avec-runit.html/</id><summary type="html">&lt;p&gt;&lt;strong&gt;ATTENTION&lt;/strong&gt; votre serviteur a fait le test pour vous sur une ubuntu:
après avoir installé &lt;tt class="docutils literal"&gt;runit&lt;/tt&gt; et &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;runit-run&lt;/span&gt;&lt;/tt&gt;, le système ne démarre plus.
Pour suivre les étapes de ce billet, il ne faut pas installer
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;runit-run&lt;/span&gt;&lt;/tt&gt;, qui ne doit être installé que lorsque l'on souhaite
remplacer totalement le système d'initialisation (et cela demande plus
de configuration qu'une simple installation du paquet).&lt;/p&gt;
&lt;p&gt;Pour les malheureux qui ont fait les frais de la première version de
ce billet (demandant d'installer &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;runit-run&lt;/span&gt;&lt;/tt&gt;), je ne peux que m'excuser
platement, et vous fournir la méthode &amp;quot;au secours rescue moi!&amp;quot;:&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://www.tenshu.fr/ubuntu/recuperer-une-installation-avec-un-cd-ubuntu/"&gt;récuperer une installation avec un cd ubuntu&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Une fois chrooté sur la partition root, il vous restera à désinstaller le paquet fautif et
redémarrer:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ aptitude purge runit-run
&lt;/pre&gt;
&lt;p&gt;Edité le 2010/02/10 à 20:58&lt;/p&gt;
&lt;hr class="docutils" /&gt;
&lt;p&gt;Pour faire suite au précédent billet &lt;a class="reference external" href="./gunicorn-un-server-wsgi-ultra-simple-a-utiliser-et-configurer.html"&gt;gunicorn: un server wsgi ultra simple à utiliser et configurer&lt;/a&gt;, voici une recette rapide pour lancer
automatiquement (et monitorer) gunicorn avec &lt;a class="reference external" href="http://smarden.org/runit/"&gt;runit&lt;/a&gt;.&lt;/p&gt;
&lt;div class="section" id="pourquoi-runit-et-pas-sysvinit-inittab-upstart"&gt;
&lt;h2&gt;Pourquoi runit et pas sysvinit, inittab, upstart, ...&lt;/h2&gt;
&lt;p&gt;Je vous laisse consulter la page &lt;a class="reference external" href="http://smarden.org/runit/benefits.html"&gt;benefits&lt;/a&gt; sur le site officiel pour
vous faire une idée. Pour les personnes ne parlant pas anglais, voici un
bref résumé:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Un répertoire par service, contenant un script &lt;em&gt;run&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Un environnement d'exécution propre et prédictible pour chaque
processus&lt;/li&gt;
&lt;li&gt;Service de logging (optionnel) qui sera lancé en même temps que le
processus, et en couvrira toute la durée de vie (redémarrages
compris!)&lt;/li&gt;
&lt;li&gt;Très peu encombrant, efficace... et peut complètement remplacer le
système d'initialisation de votre linux&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="utiliser-runit-avec-le-systeme-d-initialisation-actuel"&gt;
&lt;h2&gt;Utiliser runit avec le système d'initialisation actuel&lt;/h2&gt;
&lt;p&gt;Pour nous faciliter la vie, et ne pas avoir à modifier/importer de
nombreux scripts de démarrage pour tous les démons et services déjà
installés, nous allons utiliser runit &amp;quot;avec&amp;quot; le système d'initialisation
actuel.&lt;/p&gt;
&lt;div class="section" id="installer-runit"&gt;
&lt;h3&gt;Installer runit&lt;/h3&gt;
&lt;pre class="literal-block"&gt;
$ aptitude install runit
&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;ATTENTION:&lt;/strong&gt; Si vous installez aussi &lt;em&gt;runit-run&lt;/em&gt;, il vous faut
absolument configurer votre système (&lt;a class="reference external" href="http://smarden.org/runit/replaceinit.html"&gt;How to replace init&lt;/a&gt;), et ce,
avant de rebooter (sinon votre système ne démarrera pas, et vous serez
contraint à utiliser une méthode de récupération, comme celle présentée
en tête de ce billet).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="creer-un-repertoire-pour-le-service-gunicorn-et-son-script-de-lancement"&gt;
&lt;h3&gt;Créer un répertoire pour le service gunicorn et son script de lancement&lt;/h3&gt;
&lt;pre class="literal-block"&gt;
$ mkdir /etc/sv/gu-monprojet
$ vi /etc/sv/gu-monprojet/run
&lt;/pre&gt;
&lt;p&gt;Et voici le contenu du script run&lt;/p&gt;
&lt;pre class="literal-block"&gt;
#!/bin/bash
source /path/to/venv/bin/activate # activer le virtualenv
cd /path/to/django/project
exec gunicorn_django -b localhost:8080 --workers=3
&lt;/pre&gt;
&lt;p&gt;Avec la version de gunicorn utilisée pour l'écriture de cet article, il
est nécessaire d'être dans le répertoire du projet django (là ou se
situe le fichier &lt;em&gt;settings.py&lt;/em&gt;) pour lancer &lt;em&gt;gunicorn_django&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Dans une future version (la modification est dans le &lt;em&gt;trunk&lt;/em&gt; à l'heure
de l'écriture) il suffira d'indiquer le chemin vers le fichier
&lt;em&gt;settings.py&lt;/em&gt; comme paramètre à la commande &lt;em&gt;gunicorn_django&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Enfin, ne pas oublier de rajouter les droits d'exécution sur le script
qu'on vient de créer:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ chmod a+x /etc/sv/gu-monprojet
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="indiquer-a-runit-qu-il-doit-lancer-le-script"&gt;
&lt;h3&gt;Indiquer à runit qu'il doit lancer le script&lt;/h3&gt;
&lt;p&gt;Pour celà, un simple lien symbolique, et dans les secondes qui suivent
le script sera lancé:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ ln -s /etc/sv/gu-monprojet /etc/service/
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="et-c-est-tout"&gt;
&lt;h2&gt;Et c'est tout!&lt;/h2&gt;
&lt;p&gt;Il suffit maintenant d'en profiter en allant sur &lt;a class="reference external" href="http://localhost:8080"&gt;http://localhost:8080&lt;/a&gt;,
en configurant apache pour proxiser les requêtes directement dessus (cf
le billet &lt;a class="reference external" href="./gunicorn-un-server-wsgi-ultra-simple-a-utiliser-et-configurer.html"&gt;gunicorn: un server wsgi ultra simple à utiliser et
configurer&lt;/a&gt;), ou encore en utilisant la commande &lt;em&gt;sv&lt;/em&gt; pour gérer le
service gunicorn:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ sv status gu-monprojet
$ sv check gu-monprojet
$ sv up gu-monprojet
$ sv down gu-monprojet
$ sv restart gu-monprojet
$ sv hup gu-monprojet

...
&lt;/pre&gt;
&lt;/div&gt;
</summary><category term="web"></category><category term="runit"></category></entry><entry><title>gunicorn: un server wsgi ultra simple à utiliser et configurer</title><link href="http://mathieu.agopian.info/blog/gunicorn-un-server-wsgi-ultra-simple-a-utiliser-et-configurer.html" rel="alternate"></link><updated>2010-02-09T17:08:00Z</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2010-02-09:/blog/gunicorn-un-server-wsgi-ultra-simple-a-utiliser-et-configurer.html/</id><summary type="html">&lt;p&gt;Deux billets le même jour, c'est fête!&lt;/p&gt;
&lt;p&gt;Voici une recette simple pour installer, configurer et utiliser
&lt;a class="reference external" href="http://github.com/benoitc/gunicorn"&gt;gunicorn&lt;/a&gt; avec &lt;a class="reference external" href="http://www.apache.org/"&gt;apache&lt;/a&gt; et &lt;a class="reference external" href="http://www.djangoproject.com/"&gt;django&lt;/a&gt;.&lt;/p&gt;
&lt;div class="section" id="installer-gunicorn"&gt;
&lt;h2&gt;Installer gunicorn&lt;/h2&gt;
&lt;p&gt;Pour installer gunicorn dans son environnement virtuel:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ pip install -E /path/to/venv install gunicorn
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="configurer-apache-en-proxy"&gt;
&lt;h2&gt;Configurer Apache en proxy&lt;/h2&gt;
&lt;p&gt;Apache servira les fichiers statiques, et &amp;quot;proxisera&amp;quot; toutes les autres
requêtes directement à gunicorn qui sera lancé en local sur le port
8080:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;lt;VirtualHost *:80&amp;gt;
ServerName example.com
ServerAlias www.example.com

DocumentRoot /path/to/django/project

&amp;lt;Proxy *&amp;gt;
    Order deny,allow
    Allow from all
&amp;lt;/Proxy&amp;gt;

# laisser apache servir les fichiers statiques
ProxyPass /robots.txt !
ProxyPass /favicon.ico !
ProxyPass /static/ !

# proxiser toutes les autres requêtes vers gunicorn
ProxyPass / http://localhost:8080/

# robots.txt et favicon.ico sont dans /path/to/django/project/static/
Alias /robots.txt /path/to/django/project/static/robots.txt
Alias /favicon.ico /path/to/django/project/static/favicon.ico

&amp;lt;Directory /path/to/django/project&amp;gt;
    Order deny,allow
    Allow from all
    Options -Indexes
&amp;lt;/Directory&amp;gt;
&amp;lt;/VirtualHost&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Pour que le tout fonctionne correctement, il faut activer les modules
&lt;em&gt;mod_proxy&lt;/em&gt; et &lt;em&gt;mod_proxy_html&lt;/em&gt; (et en option &lt;em&gt;mod_cache&lt;/em&gt;):&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ a2enmod proxy proxy_http cache
&lt;/pre&gt;
&lt;p&gt;Puis de redémarrer le server Apache:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ /etc/init.d/apache2 restart
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="lancer-gunicorn"&gt;
&lt;h2&gt;Lancer gunicorn&lt;/h2&gt;
&lt;p&gt;Il suffit de se placer dans le répertoire du projet django (avec le
virtualenv activé), puis de taper:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ gunicorn_django -b localhost:8080 --workers=2
&lt;/pre&gt;
&lt;p&gt;Un ordre d'idée pour le calcul des &lt;em&gt;workers&lt;/em&gt;: un de plus que le nombre
de CPUs de la machine.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;On peut alors se créer un script (a placer dans /etc/init.d) et
l'activer pour qu'il se lance automatiquement au démarrage avec la
commande &lt;em&gt;update-rc.d&lt;/em&gt; (sous Debian), ou utiliser &lt;a class="reference external" href="http://smarden.org/runit/"&gt;runit&lt;/a&gt; (jamais
testé, peut-être un futur billet?).&lt;/p&gt;
&lt;p&gt;Encore mieux, remplacer Apache par Nginx! (jamais testé non plus, et
sûrement un futur billet ;)).&lt;/p&gt;
&lt;p&gt;On peut difficilement faire plus simple!&lt;/p&gt;
&lt;/div&gt;
</summary></entry><entry><title>Installer PIL (Python Imaging Library) facilement avec pip</title><link href="http://mathieu.agopian.info/blog/installer-pil-python-imaging-library-facilement-avec-pip.html" rel="alternate"></link><updated>2010-02-09T12:41:00Z</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2010-02-09:/blog/installer-pil-python-imaging-library-facilement-avec-pip.html/</id><summary type="html">&lt;p&gt;Le fabuleux utilitaire &lt;em&gt;pip&lt;/em&gt; de &lt;a class="reference external" href="http://ianbicking.appspot.com/"&gt;Ian Bicking&lt;/a&gt; est un remplacement à
&lt;em&gt;easy_install&lt;/em&gt; qui fonctionne très bien avec &lt;em&gt;virtualenv&lt;/em&gt; (pas
étonnant, c'est du même auteur!).&lt;/p&gt;
&lt;p&gt;Je laisse le soin au lecteur de consulter la documentation sur ces deux
utilitaires très pratiques et indispensables à tout développeur python.&lt;/p&gt;
&lt;p&gt;Voici la commande à utiliser pour installer PIL (Python Imaging
Library) dans votre environnement virtuel:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ pip -E /path/to/venv install http://effbot.org/downloads/Imaging-1.1.7.tar.gz
&lt;/pre&gt;
&lt;p&gt;Vous pouvez consulter la liste des versions disponibles en vous rendant
sur la page officielle de &lt;a class="reference external" href="http://www.pythonware.com/products/pil/"&gt;Python Imaging Library&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;ATTENTION: Il faut avoir les sources de python installées et
disponibles afin de pouvoir compiler le paquet. Sur debian, il vous
suffit de taper&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ aptitude install python-dev
&lt;/pre&gt;
&lt;p&gt;Si vous avez une erreur pip du genre&lt;/p&gt;
&lt;pre class="literal-block"&gt;
ImportError: No module named pkg_resources
&lt;/pre&gt;
&lt;p&gt;il vous faut aussi installer python-pkg-resources:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ aptitude install python-pkg-resources
&lt;/pre&gt;
</summary></entry><entry><title>Obfuscation de l'email alternative et accessible</title><link href="http://mathieu.agopian.info/blog/obfuscation-de-lemail-alternative-et-accessible.html" rel="alternate"></link><updated>2009-09-27T12:51:00Z</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2009-09-27:/blog/obfuscation-de-lemail-alternative-et-accessible.html/</id><summary type="html">&lt;p&gt;&lt;a class="reference external" href="http://acamo.fr/blog"&gt;English translation&lt;/a&gt; of this post also available.&lt;/p&gt;
&lt;div class="section" id="le-probleme"&gt;
&lt;h2&gt;Le Problème&lt;/h2&gt;
&lt;p&gt;Les quatre prérequis pour l'obfuscation d'une adresse mail sont les
suivants:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Ne pas pénaliser l'utilisateur: pouvoir cliquer sur le mail&lt;/li&gt;
&lt;li&gt;Ne pas pénaliser l'utilisateur: pouvoir copier/coller l'adresse&lt;/li&gt;
&lt;li&gt;Ne pas pénaliser l'utilisateur: l'addresse doit rester accessible (en
mode texte, sans css et/ou sans js)&lt;/li&gt;
&lt;li&gt;Difficilement récupérables par des spambots&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;L'ordre n'est pas une coïncidence: le but de l'obfuscation est bien
entendu d'empêcher les spambots de récuperer l'adresse email, &lt;strong&gt;mais
celà ne doit en aucun cas pénaliser l'utilisateur du site&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Les différentes méthodes d'obfuscation d'adresse email qui tournent sur
la toile, malheureusement, échouent en général au moins sur l'un des 4
points listés.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="la-solution"&gt;
&lt;h2&gt;La Solution&lt;/h2&gt;
&lt;p&gt;Pour les utilisateurs de webkit avec le javascript désactivé, voir les
&lt;a class="reference external" href="#limitations"&gt;limitations&lt;/a&gt;.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;lt;a href=&amp;quot;/contactform.html&amp;quot; id=&amp;quot;emailaddress&amp;quot;&amp;gt;
    prenom.nom&amp;lt;img src=&amp;quot;&amp;quot; alt=&amp;quot;&amp;#64;&amp;quot; /&amp;gt;example.com
&amp;lt;/a&amp;gt;

&amp;lt;script&amp;gt; type=&amp;quot;text/javascript&amp;quot;&amp;gt;
    mail = new Array(&amp;quot;prenom.nom&amp;quot;, &amp;quot;example.com&amp;quot;).join(&amp;quot;&amp;#64;&amp;quot;);
    ea = document.getElementById(&amp;quot;emailaddress&amp;quot;);
    ea.href = &amp;quot;mailto:&amp;quot; + mail;
    ea.innerHTML = mail;
&amp;lt;/script&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Et voici le résultat:&lt;/p&gt;
&lt;p&gt;&lt;a id="emailaddress" href="/contactform.html"&gt;prenom.nom&lt;img alt="@" /&gt;example.com&lt;/a&gt;&lt;br /&gt;
&lt;script type="text/javascript"&gt;
    mail = new Array("prenom.nom", "example.com").join("@");
    ea = document.getElementById("emailaddress");
    ea.href = "mailto:" + mail;
    ea.innerHTML = mail;
&lt;/script&gt;
&lt;/p&gt;&lt;/div&gt;
&lt;div class="section" id="lexplication"&gt;
&lt;h2&gt;L’Explication&lt;/h2&gt;
&lt;p&gt;Si javascript est activé, l’attribut href ainsi que le contenu du lien
sont modifiés afin que l’obfuscation soit invisible. Rien de nouveau donc.&lt;/p&gt;
&lt;p&gt;Si javascript est désactivé, en utilisant le contenu alternatif d’une
image inexistante pour remplacer le « &amp;#64; », on obtient plusieurs résultats intéressants:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;les lecteurs d’écrans liront l’attribut alt pour l’image, l’email étant donc très facilement déchiffrable&lt;/li&gt;
&lt;li&gt;l’attribut alt est affiché, rendant l’obfuscation invisible, même en mode texte ou sans css&lt;/li&gt;
&lt;li&gt;l’adresse est copiable sans aucun soucis&lt;/li&gt;
&lt;li&gt;l’email est toujours clickable, le lien menant sur un formulaire de contact&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Un gros avantage d’utiliser un attribut d’un tag html est qu’il contre les spambots qui ne récupèrement que le texte, et non les tags html, afin de ne pas être bloqués par les méthodes d’obfuscation basées sur l’insertion de tags invisibles ou de commentaires dans l’adresse.
Ici, supprimer le tag &amp;lt;img&amp;gt; revient à perdre l’information capitale du « &amp;#64; ».&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="les-limitations"&gt;
&lt;h2&gt;Les limitations&lt;/h2&gt;
&lt;p&gt;Il y a deux bugs non résolus dans Webkit à la date de rédaction de cet article:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://bugs.webkit.org/show_bug.cgi?id=5566"&gt;Bug 5566 – ALT attribute value not displayed when image is missing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://bugs.webkit.org/show_bug.cgi?id=11200"&gt;Bug 11200 – Image alt text not included in plain-text version when copying&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Les conséquences sont alors visible sur un navigateur utilisant Webkit, et avec le javascript désactivé:&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="Image manquante sous Webkit" src="images/missing_image.png" /&gt;
&lt;p class="caption"&gt;Image manquante sous Webkit&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;De plus, l’adresse n’est plus copiable, mais le lien (vers le formulaire de contact) reste disponible.&lt;/p&gt;
&lt;p&gt;Une solution serait alors d’utiliser une image contenant le symbole « &amp;#64; », mais celà peut poser des problèmes de style (couleur, famille et taille de la police, anti-aliasing…):&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="Adresse email avec une image remplaçant le &amp;#64;" src="images/image_for_at.png" /&gt;
&lt;p class="caption"&gt;Adresse email avec une image remplaçant le &amp;#64;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="note"&gt;
&lt;h3&gt;Note&lt;/h3&gt;
&lt;p&gt;Il est possible d’afficher l’attribut &lt;tt class="docutils literal"&gt;alt&lt;/tt&gt; de l’image sous webkit, en forçant la taille de l’image pour qu’elle soit assez grande:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;lt;img src=&amp;quot;&amp;quot; style=&amp;quot;height: 60px; vertical-align: top;&amp;quot; alt=&amp;quot;&amp;#64;&amp;quot; /&amp;gt;
&lt;/pre&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="Image assez grande pour afficher l'attribut alt" src="images/missing_image_css.png" /&gt;
&lt;p class="caption"&gt;Image assez grande pour afficher l'attribut alt&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;C’est hideux, mais ça reste lisible, et ça n’impacte que les quelques pourcents d’utilisateurs de webkit qui ont le javascript désactivé.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Voilà donc une nouvelle méthode d’obfuscation d’adresse email, qui devrait être efficace (au moins autant que les autres) contre les spambots, mais qui impacte le moins possible l’utilisateur final.
Et surtout, l’adresse est accessible pour les lecteurs d’écrans!&lt;/p&gt;
&lt;/div&gt;
</summary><category term="web"></category></entry><entry><title>Linux: savoir si le processeur est 32bits ou 64bits</title><link href="http://mathieu.agopian.info/blog/linux-savoir-si-le-processeur-est-32bits-ou-64bits.html" rel="alternate"></link><updated>2009-09-24T08:16:00Z</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2009-09-24:/blog/linux-savoir-si-le-processeur-est-32bits-ou-64bits.html/</id><summary type="html">&lt;p&gt;Voici une astuce rapide pour savoir si le processeur d'une machine
donnée supporte le 64bits:&lt;/p&gt;
&lt;p&gt;Il suffit de vérifier si le flag &lt;em&gt;lm&lt;/em&gt; est présent dans les informations
de &lt;em&gt;/proc/cpuinfo&lt;/em&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ cat /proc/cpuinfo | grep lm
flags&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov
pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe lm constant_tsc pebs
bts pni monitor ds_cpl est cid cx16 xtpr lahf_lm
&lt;/pre&gt;
&lt;p&gt;Le flag &lt;em&gt;lm&lt;/em&gt; signifie &amp;quot;long mode&amp;quot;, comme on peut le voir dans les
sources du noyau (&lt;em&gt;include/asm-i386/cpufeature.h&lt;/em&gt;):&lt;/p&gt;
&lt;pre class="literal-block"&gt;
#define X86_FEATURE_LM          (1*32+29) /* Long Mode (x86-64) */
&lt;/pre&gt;
&lt;p&gt;Vous trouverez une liste de tous les flags sur le &lt;a class="reference external" href="http://gagravarr.livejournal.com/138575.html"&gt;blog de Nick Burch&lt;/a&gt;
(en anglais).&lt;/p&gt;
</summary><category term="linux"></category><category term="systeme"></category></entry><entry><title>PyCON.fr, excellent!</title><link href="http://mathieu.agopian.info/blog/pyconfr-excellent.html" rel="alternate"></link><updated>2009-06-05T06:56:00Z</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2009-06-05:/blog/pyconfr-excellent.html/</id><summary type="html">&lt;p&gt;Et voilà, l'édition 2009 de PyCON.fr, ma toute première conférence sur
Python (et ma toute première conférence en tant qu'orateur!) est
terminée.&lt;/p&gt;
&lt;p&gt;Je tiens à remercier l'&lt;a class="reference external" href="http://afpy.org"&gt;AFPY&lt;/a&gt;, Association Francophone PYthon,
organisatrice de cet évènement. Je remercie aussi tous les membres de
l'association, et bénévoles, qui m'ont accueilli aussi chaleureusement!&lt;/p&gt;
&lt;p&gt;Je remercie &lt;a class="reference external" href="http://www.ubicast.eu/"&gt;Ubicast&lt;/a&gt; qui m'a permis d'avoir ma conférence filmée,
streamée et mise à disposition, et qui m'a permit de découvrir et
utiliser leur solution vidéo (très ergonomique et facile à utiliser,
même pour un novice comme moi).&lt;/p&gt;
&lt;p&gt;Je remercie enfin les personnes qui ont assisté à ma présentation, qui
ont été réactifs, attentifs et qui ont fait de cette première expérience
une réussite!&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="http://mathieu.agopian.info/pyconfr/"&gt;La présentation en ligne&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://bitbucket.org/magopian/pyconfrs5"&gt;Les sources sur bitbucket.org&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://mathieu.agopian.info/Contr%C3%B4le_de_versions_de_source:_pourquoi__comment.mp4"&gt;La vidéo&lt;/a&gt; (les quatres premières minutes de son manquent)&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://video.pycon.fr"&gt;Toutes les vidéos de PyCON.fr&lt;/a&gt; (à l'heure où j'écris ces lignes,
toutes les vidéos ne sont pas encore disponibles, mais ça ne saurait
tarder!)&lt;/li&gt;
&lt;/ul&gt;
</summary><category term="conference"></category></entry><entry><title>PyCon.fr: venez m'y voir!</title><link href="http://mathieu.agopian.info/blog/pyconfr-venez-my-voir.html" rel="alternate"></link><updated>2009-05-23T07:28:00Z</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2009-05-23:/blog/pyconfr-venez-my-voir.html/</id><summary type="html">&lt;p&gt;Toi, oui toi lecteur,&lt;/p&gt;
&lt;p&gt;sache qu'il y a une conférence sur Python, &lt;strong&gt;entièrement gratuite&lt;/strong&gt;,
qui se tiendra le 30 et 31 mai (autant dire, le week-end prochain) sur
Paris, à la Cité des Sciences et de l'Industrie de la Villette.&lt;/p&gt;
&lt;p&gt;Tarek Ziadé, le président de l'association &lt;a class="reference external" href="http://afpy.org"&gt;AFPY&lt;/a&gt;, donne un peu &lt;a class="reference external" href="http://www.afpy.org/Members/tarek/pycon-fr-09"&gt;plus de précisions&lt;/a&gt; sur cet évènement.&lt;/p&gt;
&lt;p&gt;Et enfin un lien vers l'évènement lui même, afin d'y trouver des
informations sur le programme: &lt;a class="reference external" href="http://pycon.fr"&gt;PyCon.fr 2009&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;J'y donnerai une présentation de 20 minutes sur un billet que j'ai
commencé sur ce blog: Les (D)VCS, pourquoi, comment? (le premier
article: &lt;a class="reference external" href="./le-controle-de-versions-de-sources-pourquoi.htm"&gt;Le contrôle de version de sources, pourquoi?&lt;/a&gt;)&lt;/p&gt;
</summary></entry><entry><title>Le contrôle de versions de sources: pourquoi?</title><link href="http://mathieu.agopian.info/blog/le-controle-de-versions-de-sources-pourquoi.html" rel="alternate"></link><updated>2009-03-30T19:07:00Z</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2009-03-30:/blog/le-controle-de-versions-de-sources-pourquoi.html/</id><summary type="html">&lt;p&gt;Je vais vous raconter l'histoire de Brian. Brian est ingénieur
informaticien.&lt;/p&gt;
&lt;div class="section" id="le-crash-disque"&gt;
&lt;h2&gt;Le crash disque&lt;/h2&gt;
&lt;p&gt;Brian n'a pas de chance, et il a failli devoir pointer à l'ANPE quand
il s'est rendu compte que&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Son disque dur avait crashé&lt;/li&gt;
&lt;li&gt;Il n'avait pas fait de sauvegarde de son boulot&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Heureusement, il a pu récuperer les sources sur le serveur de
production, et en moins de deux semaines il a pu réimplémenter les
dernières fonctionnalités et corrections de bugs qu'il avait apportées
au logiciel.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="le-boss-indecis"&gt;
&lt;h2&gt;Le boss indécis&lt;/h2&gt;
&lt;p&gt;Brian n'a vraiment pas de chance, il a un boss indécis qui vient de lui
dire qu'il ne voulait finalement plus de la dernière fonctionnalité en
date:&lt;/p&gt;
&lt;p&gt;&amp;quot;c'est une très mauvaise idée, commercialement parlant, supprime là au
plus vite&amp;quot;.&lt;/p&gt;
&lt;p&gt;Trois jours plus tard, Brian pense n'avoir oublié d'annuler les
modifications dans aucun fichier.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="la-faute-a-murphy"&gt;
&lt;h2&gt;La faute à Murphy&lt;/h2&gt;
&lt;p&gt;Brian, qui a la poisse, se retrouve à débuguer un morceau de code
obscur, et se demande tout à coup qui a bien pu créer ce &amp;quot;code
spaghetti&amp;quot;.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Serait-ce John, le cousin de l'oncle de sa soeur, qui code comme sa
grand-mère?&lt;/li&gt;
&lt;li&gt;Ou encore Steven, le surfer blond, qui sort juste de l'école et n'a
jamais appris à commenter son code?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Si seulement Brian le savait, il pourrait demander des éclaircissement
à l'auteur, et aurait quelqu'un à pointer du doigt à son boss qui vient
de sortir son fouet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="le-cpold-la-fausse-solution"&gt;
&lt;h2&gt;Le CPOLD: la fausse solution&lt;/h2&gt;
&lt;p&gt;Pour reprendre le &lt;a class="reference external" href="http://roland.entierement.nu/blog/2008/01/22/cpold-la-poudre-verte-du-suivi-de-versions.html"&gt;blog de Roland&lt;/a&gt;, le CPOLD à d'innombrables
qualités:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;pas de format de fichier complexe et susceptible de corruption&lt;/li&gt;
&lt;li&gt;pas de conflits&lt;/li&gt;
&lt;li&gt;aucun besoin d'un serveur dédié (on peut tout mettre ensemble, prod
et dev confondues)&lt;/li&gt;
&lt;li&gt;aucune limitation sur la gestion des branches&lt;/li&gt;
&lt;li&gt;une rapidité insurpassable&lt;/li&gt;
&lt;li&gt;une simplicité de mise en oeuvre et d'apprentissage enfantine&lt;/li&gt;
&lt;li&gt;pas de modèle de développement imposé (centralisé, distribué, en
quinconce, en hélice, toutes les variantes sont possibles)&lt;/li&gt;
&lt;li&gt;des sauvegardes facilitées&lt;/li&gt;
&lt;li&gt;etc...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Voici en quoi consiste la mise en place du CPOLD:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ cp fichier.py fichier.py.old
&lt;/pre&gt;
&lt;p&gt;Et voici un exemple de mise en oeuvre du CPOLD:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
foo_dev
&amp;nbsp;&amp;nbsp;&amp;nbsp; foo.py
&amp;nbsp;&amp;nbsp;&amp;nbsp; foo.py.old
&amp;nbsp;&amp;nbsp;&amp;nbsp; foo.py.old.2009_03_29
&amp;nbsp;&amp;nbsp;&amp;nbsp; foo.py.marche_pas
&amp;nbsp;&amp;nbsp;&amp;nbsp; foo.py.todelete
&amp;nbsp;&amp;nbsp;&amp;nbsp; foo.py.OLD.2006_05_12
&amp;nbsp;&amp;nbsp;&amp;nbsp; foo.py.bak
&amp;nbsp;&amp;nbsp;&amp;nbsp; foo.py.fonctionalite_bar
&amp;nbsp;&amp;nbsp;&amp;nbsp; foo.py.bug
&amp;nbsp;&amp;nbsp;&amp;nbsp; foo.py.save.20081210
&amp;nbsp;&amp;nbsp;&amp;nbsp; foo.py.check
&amp;nbsp;&amp;nbsp;&amp;nbsp; foo.py.test

foo_prod
&amp;nbsp;&amp;nbsp;&amp;nbsp; foo.py

foo_savedev.tgz
foo_save20090329_v2_0.tgz
foo_save20080412_v1_2.tgz
foo_save20060509_v1_0.tgz
&lt;/pre&gt;
&lt;p&gt;Et pour faire bien, voici un extrait du fichier foo.py:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
...
def bar(thing): # Added the 10th of june, 2006 -- Steven
&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;quot;&amp;quot;&amp;quot;This function is very usefull!&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp; # Brian: refactored 20080410 for release 1.2
&amp;nbsp;&amp;nbsp;&amp;nbsp; #if thing == &amp;quot;bar&amp;quot;: ### Steven: 11/06/06 fixed typo (was thing = &amp;quot;bar&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp; #&amp;nbsp;&amp;nbsp;&amp;nbsp; return True;
&amp;nbsp;&amp;nbsp;&amp;nbsp; return (thing == &amp;quot;bar&amp;quot;)
...
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="la-fausse-bonne-idee"&gt;
&lt;h2&gt;La fausse bonne idée&lt;/h2&gt;
&lt;p&gt;Nous avons déjà vu les avantages du CPOLD, maintenant les
inconvénients:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Duplication de fichiers&lt;/li&gt;
&lt;li&gt;Duplication de code&lt;/li&gt;
&lt;li&gt;Réduction dramatique de la lisibilité&lt;/li&gt;
&lt;li&gt;Difficulté de grouper des modifications (pour une fonctionnalité par
exemple)&lt;/li&gt;
&lt;li&gt;Ai-je déjà mentionné la duplication?&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Sommes-nous maintenant tous bien d'accord avec Brian pour dire que le
contrôle de versions, c'est indispensable? Et que le CPOLD, c'est
dépassé?&lt;/p&gt;
&lt;p&gt;Dans une prochaine histoire, les enfants, nous verrons avec Brian quels
sont les merveilleux outils à notre disposition: les &amp;quot;Version Control
System&amp;quot; !&lt;/p&gt;
&lt;hr class="docutils" /&gt;
&lt;p&gt;EDIT (05/06/2009): la totalité de cette article (cette première partie
ainsi que les deux suivantes, qui ne seront pas publiées sous forme de
billet) a été présentée à &lt;a class="reference external" href="http://pycon.fr"&gt;PyCON.fr&lt;/a&gt;. Vous trouverez les liens vers la
présentation (en ligne, au format vidéo, et les sources) dans le billet
&amp;quot;&lt;a class="reference external" href="./pyconfr-excellent.html"&gt;PyCON.fr: excellent!&lt;/a&gt;&amp;quot;&lt;/p&gt;
&lt;/div&gt;
</summary></entry><entry><title>Django, sqlite et mod_wsgi, attention au piège!</title><link href="http://mathieu.agopian.info/blog/django-sqlite-et-mod_wsgi-attention-au-piege.html" rel="alternate"></link><updated>2009-03-14T15:34:00Z</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2009-03-14:/blog/django-sqlite-et-mod_wsgi-attention-au-piege.html/</id><summary type="html">&lt;p&gt;Tout d'abord, je tiens à préciser que le problème qui suit n'est pas
limité à l'utilisation de django ou de mod_wsgi.&lt;/p&gt;
&lt;div class="section" id="le-contexte"&gt;
&lt;h2&gt;Le contexte&lt;/h2&gt;
&lt;p&gt;Utilisation de &lt;em&gt;SQLite&lt;/em&gt; pour un projet django déployé sur mod_wsgi:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# settings.py
DATABASE_ENGINE = 'sqlite3'
DATABASE_NAME = '/opt/mysite/mysite.db'
&lt;/pre&gt;
&lt;p&gt;Et voici les permissions sur le système de fichier:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
-rw-rw-rw- 1 ohan ohan 29696 2009-03-14 13:30 mysite.db
&lt;/pre&gt;
&lt;p&gt;Tous les répertoires parents sont eux en &lt;em&gt;755&lt;/em&gt; (lecture et exécution),
ce qui ne devrait donc poser aucun problème, même pour l'utilisateur
utilisé par les processus apache/mod_wsgi.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="le-probleme"&gt;
&lt;h2&gt;Le problème&lt;/h2&gt;
&lt;p&gt;Lors de la première tentative d'accès à la base de donnée (par exemple
en accédant à l'administration django), une erreur &lt;em&gt;500 INTERNAL SERVER
ERROR&lt;/em&gt; est renvoyée, et dans le fichier de log d'apache:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
OperationalError: unable to open database file
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="la-solution"&gt;
&lt;h2&gt;La solution&lt;/h2&gt;
&lt;p&gt;Lors de l'accès à un fichier de base de données, &lt;em&gt;SQLite&lt;/em&gt; va créer un
fichier journal qui lui servira (entre autres) à gérer les accès à cette
base. Plus d'informations sur la page expliquant les méthodes de
vérouillage: &lt;a class="reference external" href="http://www.sqlite.org/lockingv3.html"&gt;locking in sqlite v3&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Pour créer ce fichier, il faut donc que l'utilisateur puisse écrire
dans le répertoire parent.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
chmod a+rw /opt/mysite
&lt;/pre&gt;
&lt;p&gt;Ce problème ne devrait se présenter que lors d'un déploiement en
environnement de production pour un projet qui utilise &lt;em&gt;SQLite&lt;/em&gt;, ou sur
un environnement de test si, comme moi, vous préférez tester sur apache
directement, et non sur le &lt;em&gt;runserver.&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
</summary><category term="sqlite"></category></entry><entry><title>Checklist: différences entre MySQL et les modèles Django</title><link href="http://mathieu.agopian.info/blog/checklist-differences-entre-mysql-et-les-modeles-django.html" rel="alternate"></link><updated>2009-03-02T10:54:00Z</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2009-03-02:/blog/checklist-differences-entre-mysql-et-les-modeles-django.html/</id><summary type="html">&lt;p&gt;Comme vu dans un précédent billet (&lt;a class="reference external" href="./mysql-et-les-modeles-django.html"&gt;MySQL et les modèles Django&lt;/a&gt;), il
existe des inconsistances entre une base de donnée MySQL et sa
représentation par des modèles Django.&lt;/p&gt;
&lt;div class="section" id="creation-des-tables-a-partir-des-modeles-django"&gt;
&lt;h2&gt;Création des tables à partir des modèles Django&lt;/h2&gt;
&lt;p&gt;Lorsqu'on utilise &lt;em&gt;python manage.py syncdb&lt;/em&gt;, les tables créées dans la
base de données&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;N'auront aucun commentaire, que ce soit sur les champs ou les tables,
qu'il y ai ou non des &lt;em&gt;help_text&lt;/em&gt; et des &lt;em&gt;docstrings&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;N'auront aucun champ &lt;a class="reference external" href="http://dev.mysql.com/doc/refman/5.0/en/enum.html"&gt;*ENUM*&lt;/a&gt;: les champs possédant une &lt;a class="reference external" href="http://docs.djangoproject.com/en/dev/ref/models/fields/#choices"&gt;liste de
choix&lt;/a&gt; sont représentés par un &lt;a class="reference external" href="http://dev.mysql.com/doc/refman/5.0/en/char.html"&gt;*VARCHAR*&lt;/a&gt; de longueur égale à la
taille du plus long des choix&lt;/li&gt;
&lt;li&gt;Les valeurs par défaut indiquées dans les modèles ne sont pas
transmises dans les scripts de création des tables&lt;/li&gt;
&lt;li&gt;Les &lt;a class="reference external" href="http://docs.djangoproject.com/en/dev/ref/models/fields/#booleanfield"&gt;*BooleanField*&lt;/a&gt; sont représentés par des &lt;a class="reference external" href="http://dev.mysql.com/doc/refman/4.1/en/numeric-types.html"&gt;TINYINT&lt;/a&gt; de 1bit&lt;/li&gt;
&lt;li&gt;Un &lt;em&gt;DateTimeField&lt;/em&gt; avec le paramètre &lt;em&gt;auto_add_now&lt;/em&gt; ne sera pas
représenté par un &lt;em&gt;TIMESTAMP&lt;/em&gt; avec la valeur par défaut
&lt;em&gt;CURRENT_TIMESTAMP&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="inspection-des-tables-dans-django"&gt;
&lt;h2&gt;Inspection des tables dans Django&lt;/h2&gt;
&lt;p&gt;Par ailleurs, si la base de donnée existe déjà, et que les modèles sont
créés automatiquement par l'utilisation de la commande &lt;em&gt;python manage.py
inspectdb&lt;/em&gt;, il faudra garder à l'esprit que:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Les commentaires sur les champs et tables ne sont pas traduits en
&lt;em&gt;help_text&lt;/em&gt; ou &lt;em&gt;docstrings&lt;/em&gt;, il faudra donc les dupliquer
manuellement&lt;/li&gt;
&lt;li&gt;Un champ &lt;em&gt;ENUM&lt;/em&gt; sera représenté par un &lt;em&gt;CharField&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Les tailles de champ ne sont pas respectées. Par exemple, un
&lt;em&gt;Charfield&lt;/em&gt; sera avec un &lt;em&gt;max_length&lt;/em&gt; de 135 par défaut&lt;/li&gt;
&lt;li&gt;Les champs &lt;em&gt;TIMESTAMP&lt;/em&gt; avec une valeur par défaut de
&lt;em&gt;CURRENT_TIMESTAMP&lt;/em&gt; ne seront pas importés en champ avec une valeur
par défaut &lt;em&gt;auto_add_now&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Les valeurs par défaut ne sont pas importées&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Il y a deux derniers points à noter:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Le script d'inspection va importer les champs &lt;em&gt;primary_key&lt;/em&gt; pour
chaque table, alors qu'ils peuvent être automatiquement générés de
manière transparente: un modèle sans &lt;em&gt;primary_key&lt;/em&gt; explicite en aura
un de manière implicite&lt;/li&gt;
&lt;li&gt;Le script d'inspection va créer un modèle pour les tables utilisées
pour les relations &lt;em&gt;ManyToMany&lt;/em&gt; alors que là aussi, Django peut les
générer de manière implicite dans la plupart des cas (lorsque cette
table ne contient pas d'&lt;a class="reference external" href="http://docs.djangoproject.com/en/dev/topics/db/models/#intermediary-manytomany"&gt;information supplémentaire&lt;/a&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Je trouve préférable de laisser la gestion implicite des &lt;em&gt;primary_key&lt;/em&gt;
et des tables &lt;em&gt;ManyToMany&lt;/em&gt; quand c'est possible, dans un soucis de
concision et de lisibilité.&lt;/p&gt;
&lt;p&gt;Néanmoins, celà introduit une inconsistance entre les modèles et leur
représentation dans la base de donnée, inconsistance qui peut porter à
confusion.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="plus-de-coherence-entre-les-modeles-et-la-base-de-donnee"&gt;
&lt;h2&gt;Plus de cohérence entre les modèles et la base de donnée&lt;/h2&gt;
&lt;p&gt;Plusieurs possibilités:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;créer un script python qui va introspecter les modèles, et
automatiquement rajouter les &lt;em&gt;help_text&lt;/em&gt; comme commentaires aux
champs, et les &lt;em&gt;docstrings&lt;/em&gt; comme commentaires aux tables, ou
vice-versa&lt;/li&gt;
&lt;li&gt;Utiliser des &lt;a class="reference external" href="http://www.djangoproject.com/documentation/model-api/#database-backend-specific-sql-data"&gt;*Custom SQL*&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Créer des &lt;a class="reference external" href="http://docs.djangoproject.com/en/dev/howto/custom-model-fields/#howto-custom-model-fields"&gt;*Custom fields*&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Pour le type &lt;em&gt;ENUM&lt;/em&gt;, il existe un &lt;a class="reference external" href="http://www.djangosnippets.org/snippets/864/"&gt;*django snippet*&lt;/a&gt; qui vise à
apporter une meilleure cohérence dans son utilisation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Partagez vos astuces et faites part d'autres incohérences dans les
commentaires!&lt;/p&gt;
&lt;/div&gt;
</summary></entry><entry><title>MySQL et les modèles Django</title><link href="http://mathieu.agopian.info/blog/mysql-et-les-modeles-django.html" rel="alternate"></link><updated>2009-02-24T19:45:00Z</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2009-02-24:/blog/mysql-et-les-modeles-django.html/</id><summary type="html">&lt;div class="section" id="le-probleme"&gt;
&lt;h2&gt;Le problème&lt;/h2&gt;
&lt;p&gt;D'un côté, une base de donnée &lt;a class="reference external" href="http://www.mysql.com/"&gt;MySQL&lt;/a&gt; maintenue par &lt;a class="reference external" href="http://dev.mysql.com/downloads/workbench/5.1.html"&gt;MySQLWorkbench&lt;/a&gt;.
De l'autre, &lt;a class="reference external" href="http://www.djangoproject.com"&gt;Django&lt;/a&gt; et ses modèles.&lt;/p&gt;
&lt;p&gt;Entre les deux... pas grand chose.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="les-acquis"&gt;
&lt;h2&gt;Les acquis&lt;/h2&gt;
&lt;div class="section" id="mysql-et-mysqlworkbench"&gt;
&lt;h3&gt;MySQL et MySQLWorkbench&lt;/h3&gt;
&lt;p&gt;MySQLWorkbench est un outil excellent pour pouvoir maintenir une base
de donnée:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Gestion des commentaires sur les tables et champs&lt;/li&gt;
&lt;li&gt;Affichage des relations entre les tables d'une base de données sous
forme d'un schéma&lt;/li&gt;
&lt;li&gt;Facilité de modification d'une base de donnée en production par
l'export de scripts SQL&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="django-et-les-modeles"&gt;
&lt;h3&gt;Django et les modèles&lt;/h3&gt;
&lt;p&gt;Django, de son côté, nécéssite une représentation de la base de donnée
par des modèles. Ces modèles sont des classes codées en Python:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Facilité de suivi des modifications d'un modèle par le biais d'un
contrôle de version (Git, Mercurial, SVN ...) accompagné de
commentaires pour chaque &lt;em&gt;commit&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Facilité de documentation: un paramètre &lt;em&gt;help_text&lt;/em&gt; pour chaque
champ, un &lt;em&gt;docstring&lt;/em&gt; pour chaque modèle&lt;/li&gt;
&lt;li&gt;Abstraction de certaines tables et champs générés automatiquement
(comme les &lt;em&gt;primary key&lt;/em&gt; ou les tables &lt;em&gt;ManyToMany&lt;/em&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="les-outils"&gt;
&lt;h2&gt;Les outils&lt;/h2&gt;
&lt;p&gt;Comme indiqué plus haut, MySQLWorkbench permet d'exporter des scripts
SQL de création et/ou de modification. Il est aussi possible de &lt;em&gt;reverse
engineer&lt;/em&gt; le dump SQL d'une base de donnée, et ainsi créer toutes les
tables dans l'interface graphique, et y visualiser les relations sous
forme de schéma.&lt;/p&gt;
&lt;p&gt;Django permet l'export de scripts SQL de création, mais aussi
l'inspection d'une base de données (par la commande &lt;em&gt;python manage.py
inspectdb&lt;/em&gt;), ou encore l'exécution de scripts SQL &lt;em&gt;custom&lt;/em&gt; à chaque
création/modification d'une table (par la commande &lt;em&gt;python manage.py
syncdb&lt;/em&gt;).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="les-methodes"&gt;
&lt;h2&gt;Les méthodes&lt;/h2&gt;
&lt;div class="section" id="la-methode-basique"&gt;
&lt;h3&gt;La méthode basique&lt;/h3&gt;
&lt;p&gt;... qui casse complètement le principe &lt;em&gt;DRY&lt;/em&gt; (Don't Repeat Yourself):
maintenir les deux parties en parallèle.&lt;/p&gt;
&lt;p&gt;Lorsqu'il faut rajouter une table dans la base, ou y apporter une
modification, faire la modification dans MySQLWorkbench, puis répliquer
l'ajout/la modification dans les modèles Django.&lt;/p&gt;
&lt;p&gt;Pour: Méthode simple, pas de procédure ou méthode à mettre en place.&lt;/p&gt;
&lt;p&gt;Contre: Méthode manuelle, sujette à l'erreur humaine (faute de typo,
oubli...).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="mysqlworkbench-django"&gt;
&lt;h3&gt;MySQLWorkbench =&amp;gt; Django&lt;/h3&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Maintenance dans l'outil MySQLWorkbench, puis export des scripts de
création/modification.&lt;/li&gt;
&lt;li&gt;Execution de ce script sur la base de données pour la mettre à jour&lt;/li&gt;
&lt;li&gt;Utilisation de &lt;em&gt;inspectdb&lt;/em&gt; avec Django pour mettre à jour les modèles&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Pour: Méthode automatique, donc pas de risque de typo ou d'oubli&lt;/p&gt;
&lt;p&gt;Contre: Méthode très complexe à mettre en place. En effet, &lt;em&gt;inspectdb&lt;/em&gt;
est loin de créer des modèles fidèles, et il manque de nombreuses
informations (un futur billet sera écrit sur ce sujet).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="django-mysqlworkbench"&gt;
&lt;h3&gt;Django =&amp;gt; MySQLWorkbench&lt;/h3&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Ajout ou modification d'un modèle dans Django&lt;/li&gt;
&lt;li&gt;Mise à jour de la base de donnée en utilisant &lt;em&gt;python manage.py
syncdb&lt;/em&gt; (et des scripts &lt;em&gt;custom&lt;/em&gt; si nécéssaire)&lt;/li&gt;
&lt;li&gt;Utilisation de MySQLWorkbench pour &lt;em&gt;reverse engineer&lt;/em&gt; les scripts de
création de la base de donnée&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Pour: Méthode automatique, donc pas de risque de typo ou d'oubli. De
plus, contrairement à la méthode précédente, la représentation des
tables est 100% fidèle à la structure de la base de donnée créée par les
Django.&lt;/p&gt;
&lt;p&gt;Contre: Toutes les informations de commentaires (et d'autres, comme
nous le verrons dans un futur billet) sont perdues à chaque
modification.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Je n'ai malheureusement pas trouvé à l'heure actuelle de méthode
miracle. Il y a &lt;a class="reference external" href="http://code.djangoproject.com/wiki/SchemaEvolution"&gt;des pistes&lt;/a&gt;, mais il semble qu'il n'y ai rien de
concret pour le moment.&lt;/p&gt;
&lt;p&gt;Pour certains cas particuliers, l'une ou l'autre méthode sera préférée:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Création d'une nouvelle table: la méthode Django =&amp;gt; MySQLWorkbench
sera facile à mettre en place, et il n'y aura rien de perdu vu qu'il
n'y a pas d'existant&lt;/li&gt;
&lt;li&gt;Modification d'une table par le rajout d'un nouveau champ: l'une des
deux méthodes automatique peuvent faire l'affaire&lt;/li&gt;
&lt;li&gt;Modification d'un champ d'une table: la méthode manuelle sera en
général la plus rapide et la plus simple à mettre en place&lt;/li&gt;
&lt;li&gt;Modifications complexes, multiples, perte de donnée possible...: dans
ce cas, point de salut, il ne reste que la méthode manuelle, et une
grande concentration!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Et vous, quelle est votre méthode?&lt;/p&gt;
&lt;/div&gt;
</summary></entry><entry><title>Apprendre à faire, et faire</title><link href="http://mathieu.agopian.info/blog/apprendre-a-faire-et-faire.html" rel="alternate"></link><updated>2009-02-22T15:03:00Z</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2009-02-22:/blog/apprendre-a-faire-et-faire.html/</id><summary type="html">&lt;p&gt;Quelle est la meilleure méthode d'apprentissage?&lt;/p&gt;
&lt;div class="section" id="scenario-moi-j-apprends"&gt;
&lt;h2&gt;Scénario: &amp;quot;moi, j'apprends&amp;quot;&lt;/h2&gt;
&lt;p&gt;J'apprends.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Je commence par le tutoriel, que je lis de fond en comble&lt;/li&gt;
&lt;li&gt;Je continue par les concepts annexes&lt;/li&gt;
&lt;li&gt;J'approfondis quelques sujets qui me paraissent importants, utiles,
bons à connaître&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Malheureusement, très rapidement&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Je prends du retard sur mon projet&lt;/li&gt;
&lt;li&gt;Je m'éparpille, je me documente sur des concepts qui ne me serviront
peut-être jamais&lt;/li&gt;
&lt;li&gt;J'approfondis trop, au détriment d'une vue d'ensemble&lt;/li&gt;
&lt;li&gt;Et surtout, je commence à oublier ce que j'avais appris au début, je
perds le fil, je me rappelle à peine du tutoriel...&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="scenario-moi-je-fais"&gt;
&lt;h2&gt;Scénario: &amp;quot;moi, je fais&amp;quot;&lt;/h2&gt;
&lt;p&gt;Je me lance.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;J'essaie de comprendre les concepts de base, peut-être le début du
tutoriel&lt;/li&gt;
&lt;li&gt;Je me lance, je débute mon projet, et je défriche au fur et à mesure&lt;/li&gt;
&lt;li&gt;Quand j'ai besoin d'un nouveau concept, j'épluche rapidemment la
documentation, et j'applique mes acquis&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Malheureusement, très rapidement&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Je passe à côté de concepts très utiles et intéressants&lt;/li&gt;
&lt;li&gt;Je ne possède pas assez de bases pour développer mon projet
intelligemment&lt;/li&gt;
&lt;li&gt;Je ne connaît pas les outils qui me simplifieraient la tâche&lt;/li&gt;
&lt;li&gt;Et surtout, je me rends compte que mon projet mériterait un
&lt;em&gt;refactoring&lt;/em&gt;, alors qu'il débute à peine&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="scenario-le-juste-milieu"&gt;
&lt;h2&gt;Scénario: le juste milieu?&lt;/h2&gt;
&lt;p&gt;J'essaie de trouver le juste milieu entre &amp;quot;j'apprends&amp;quot; et &amp;quot;je fais&amp;quot;.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Je commence par le tutoriel, que je &lt;em&gt;fais&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;A chaque étape du tutoriel, je met en pratique ce que je viens
d'apprendre en faisant quelques modifications/améliorations&lt;/li&gt;
&lt;li&gt;Je me fais ensuite (rapidement) une vue d'ensemble des différents
concepts et outils disponibles&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Ensuite, j'entamme le projet. Le déroulement d'une journée de travail
pourrait être :&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Je démarre le projet par la partie la plus simple&lt;/li&gt;
&lt;li&gt;Dès que j'en ai besoin, j'approfondis un concept/outil nécessaire à
l'étape en court du projet&lt;/li&gt;
&lt;li&gt;Quand je rencontre un concept/outil adjacent, qui pourrait
m'intéresser dans l'avenir, je le marque comme &amp;quot;à lire&amp;quot;&lt;/li&gt;
&lt;li&gt;Une fois rentré chez moi, ou avant de commencer le travail le
lendemain, je passe un peu de temps à me documenter sur ce que j'ai
marqué comme &amp;quot;à lire&amp;quot;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="ma-methode"&gt;
&lt;h2&gt;Ma méthode&lt;/h2&gt;
&lt;p&gt;J'essaie de suivre au mieux le scénario &amp;quot;le juste milieu&amp;quot; : Apprendre
chaque jour un peu, et faire.
Voici un ordre d'idée du déroulement d'une journée de travail, une fois
que le projet est démarré:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Je me réserve une heure le matin, pour me documenter sur des sujets
généraux, des outils...&lt;/li&gt;
&lt;li&gt;puis j'avance sur le projet, et ne me documente que si nécessaire
pour résoudre un problème précis&lt;/li&gt;
&lt;li&gt;si je rencontre un sujet que j'aimerais approfondir, j'ouvre un
onglet dans mon navigateur pour le lendemain matin&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Quelques ordres de grandeur :&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Un tutoriel doit prendre quelques heures (au maximum une journée)&lt;/li&gt;
&lt;li&gt;Le temps passé à le mettre en pratique en faisant quelques
modifications/amélioration ne doit pas dépasser le temps passé à le
suivre (donc durée totale &amp;quot;tutoriel + mise en pratique&amp;quot; &amp;lt; 2 journées)&lt;/li&gt;
&lt;li&gt;Une fois le projet démarré, se documenter sur un outil/concept doit
prendre (en moyenne) moins de 50% du temps de travail (ce ratio doit
évaluer en faveur du développement au fur et à mesure de la maîtrise
du sujet)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Selon le sujet à apprendre, ces ordres de grandeur varient:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Plus d'apprentissage pour un nouveau langage de programmation (par
exemple, &lt;em&gt;Python&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;Plus de mise en pratique pour un nouvel outil (par exemple,
&lt;em&gt;Mercurial&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;Environ 50/50 pour un framework (par exempe, &lt;em&gt;Django&lt;/em&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="et-vous"&gt;
&lt;h2&gt;Et vous?&lt;/h2&gt;
&lt;p&gt;quelle est votre méthode? Avez-vous des astuces à partager?&lt;/p&gt;
&lt;/div&gt;
</summary></entry><entry><title>Django FileField et ImageField, upload_to et shell python</title><link href="http://mathieu.agopian.info/blog/django-filefield-et-imagefield-upload_to-et-shell-python.html" rel="alternate"></link><updated>2009-02-19T19:30:00Z</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2009-02-19:/blog/django-filefield-et-imagefield-upload_to-et-shell-python.html/</id><summary type="html">&lt;div class="section" id="le-parametre-upload-to-des-filefield-et-imagefield"&gt;
&lt;h2&gt;Le paramètre &lt;em&gt;upload_to&lt;/em&gt; des &lt;em&gt;FileField&lt;/em&gt; et &lt;em&gt;ImageField&lt;/em&gt;&lt;/h2&gt;
&lt;p&gt;Le champ &lt;a class="reference external" href="http://docs.djangoproject.com/en/dev/ref/models/fields/#filefield"&gt;upload_to&lt;/a&gt; permet d'indiquer où sauver un fichier de type
&lt;a class="reference external" href="http://docs.djangoproject.com/en/dev/topics/files/#the-file-object"&gt;django.core.files.File&lt;/a&gt; par rapport au &lt;a class="reference external" href="http://docs.djangoproject.com/en/dev/ref/settings/#media-root"&gt;MEDIA_ROOT&lt;/a&gt; spécifié
dans les settings.&lt;/p&gt;
&lt;p&gt;Ce champ peut être une chaîne de caractères, ou un &lt;em&gt;callable&lt;/em&gt;.&lt;/p&gt;
&lt;div class="section" id="chaine-de-caracteres-pour-upload-to"&gt;
&lt;h3&gt;Chaîne de caractères pour upload_to&lt;/h3&gt;
&lt;p&gt;Pour notre example, prenons le modèle suivant:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
class MonModele(models.Model):
&amp;nbsp;&amp;nbsp;&amp;nbsp; fichier = models.FileField(upload_to=&amp;quot;chemin&amp;quot;)
&lt;/pre&gt;
&lt;p&gt;Dans ce cas simple, tous les fichiers seronts sauvés dans le répertoire
&lt;em&gt;&amp;lt;MEDIA_ROOT&amp;gt;/chemin/&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Si on upload un fichier nommé &lt;em&gt;MonFichier.txt&lt;/em&gt;, le chemin complet (sans
le &lt;em&gt;MEDIA_ROOT&lt;/em&gt;) sera &lt;em&gt;chemin/MonFichier.txt&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Il est par ailleurs possible d'utiliser une syntaxe &lt;em&gt;strftime&lt;/em&gt; comme
indiqué dans la &lt;a class="reference external" href="http://docs.python.org/library/time.html#time.strftime"&gt;documentation du module time&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Pour stocker un fichier avec la date et l'heure:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
class MonModele(models.Model):
&amp;nbsp;&amp;nbsp;&amp;nbsp; fichier = models.FileField(upload_to=&amp;quot;chemin/%Y%m%d_%H%M%S&amp;quot;)
&lt;/pre&gt;
&lt;p&gt;Et le résultat sera &lt;em&gt;chemin/2009-02-19_18:40:03/MonFichier.txt&lt;/em&gt;, ce
qui n'est pas vraiment ce à quoi on s'attendait.&lt;/p&gt;
&lt;p&gt;En effet, cela créera un répertoire par fichier, au lieu de stocker la
date et l'heure dans le nom du fichier.&lt;/p&gt;
&lt;p&gt;Pour arriver à nos fins, il nous faut utiliser une fonction pour le
calcul du chemin de stockage du fichier.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="fonction-pour-upload-to"&gt;
&lt;h3&gt;Fonction pour &lt;em&gt;upload_to&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;Utiliser une fonction pour le calcul du chemin de stockage permet:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;de modifier le nom du fichier lui-même, et pas seulement son
répertoire de stockage&lt;/li&gt;
&lt;li&gt;d'utiliser des informations spécifiques à l'instance du modèle pour
lequel on stocke le fichier&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Il nous faut par contre respecter les contraintes suivantes:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;la fonction aura deux paramètres: &lt;em&gt;self&lt;/em&gt;, l'instance, et &lt;em&gt;filename&lt;/em&gt;,
le nom du fichier uploadé&lt;/li&gt;
&lt;li&gt;il n'est plus possible d'utiliser directement la syntaxe &lt;em&gt;strftime&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Voici le code qui va stocker un fichier avec la date et l'heure (et
prendra la date et l'heure automatiquement ajoutée au champ
&lt;em&gt;DateTimeField&lt;/em&gt; avec le paramètre &lt;em&gt;auto_add_now&lt;/em&gt;):&lt;/p&gt;
&lt;pre class="literal-block"&gt;
class MonModele(models.Model):
    date = models.DateTimeField(auto_now_add=True)
    def upload_path(self, filename):
        return 'chemin/%s_%s' % (self.date.strftime(&amp;quot;%Y%m%d_%H%M%S&amp;quot;), filename)
    fichier = models.FileField(upload_to=upload_path)
&lt;/pre&gt;
&lt;p&gt;Ce coup-ci, on obtient bien un fichier
&lt;em&gt;chemin/2009-02-19_18:40:03_MonFichier.txt&lt;/em&gt;, comme prévu.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="tester-un-modele-avec-filefield-dans-l-interpreteur-python"&gt;
&lt;h2&gt;Tester un modèle avec FileField dans l'interpréteur Python&lt;/h2&gt;
&lt;p&gt;Pour pouvoir tester un modèle avec un FileField dans un intepréteur
Python (&lt;em&gt;python manage.py shell&lt;/em&gt;), il y a quelques précautions à
prendre, comme commencer par &amp;quot;sauver&amp;quot; le &lt;em&gt;File&lt;/em&gt;, comme indiqué sur la
&lt;a class="reference external" href="http://docs.djangoproject.com/en/dev/ref/files/file/#additional-methods-on-files-attached-to-objects"&gt;page suivante&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;En effet, pour que le modèle soit correctement instancié, il faut:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;créer une instance du modèle&lt;/li&gt;
&lt;li&gt;la sauver (dans notre cas, afin que la date soit automatiquement
stockée, et devienne accessible dans &lt;em&gt;upload_path&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;ouvrir le fichier à &amp;quot;uploader&amp;quot;&lt;/li&gt;
&lt;li&gt;créer un &lt;em&gt;django.core.files.File&lt;/em&gt; à partir de ce fichier&lt;/li&gt;
&lt;li&gt;sauver ce fichier pour pouvoir ensuite le tester&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="literal-block"&gt;
&amp;gt;&amp;gt;&amp;gt; from mysite.models import *
&amp;gt;&amp;gt;&amp;gt; mm = MonModele()
&amp;gt;&amp;gt;&amp;gt; mm.save()
&amp;gt;&amp;gt;&amp;gt; mm.date
datetime.datetime(2009, 2, 19, 19, 4, 49, 538465)
&amp;gt;&amp;gt;&amp;gt; from django.core.files import File
&amp;gt;&amp;gt;&amp;gt; f = File(open(&amp;quot;MonFichier.txt&amp;quot;))
&amp;gt;&amp;gt;&amp;gt; mm.fichier.save(f.name, f, save=False)
&amp;gt;&amp;gt;&amp;gt; mm.fichier
&amp;lt;FieldFile: chemin/20090219_190449_settings.py&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Pour &amp;quot;sauver&amp;quot; le fichier, il faut fournir à la méthode &lt;em&gt;save&lt;/em&gt; le nom du
fichier (&lt;em&gt;f.name&lt;/em&gt;), le fichier lui-même (&lt;em&gt;f&lt;/em&gt;), et un paramètre
spécifiant si on veut sauvegarder le fichier dans la base de donnée ou
pas.&lt;/p&gt;
&lt;p&gt;Si nous ne voulions pas accéder à &lt;em&gt;date&lt;/em&gt; qui est stockée
automatiquement, il aurait été inutile de sauvegarder l'instance de
MonModele (&lt;em&gt;mm&lt;/em&gt;), et il aurait alors fallut remplacer le code de
&lt;em&gt;upload_path&lt;/em&gt; de la sorte&lt;/p&gt;
&lt;pre class="literal-block"&gt;
def upload_path(self, filename):
    return 'chemin/%s_%s' % (self.date.strftime(&amp;quot;%Y%m%d_%H%M%S&amp;quot;), filename)
&lt;/pre&gt;
&lt;p&gt;par&lt;/p&gt;
&lt;pre class="literal-block"&gt;
def upload_path(self, filename):
    import time
    return 'chemin/%s_%s' % (time.strftime(&amp;quot;%Y%m%d_%H%M%S&amp;quot;), filename)
&lt;/pre&gt;
&lt;/div&gt;
</summary></entry><entry><title>Django svn et mod_wsgi, attention au piège!</title><link href="http://mathieu.agopian.info/blog/django-svn-et-mod_wsgi-attention-au-piege.html" rel="alternate"></link><updated>2009-02-17T22:34:00Z</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2009-02-17:/blog/django-svn-et-mod_wsgi-attention-au-piege.html/</id><summary type="html">&lt;div class="section" id="scenario"&gt;
&lt;h2&gt;Scénario&lt;/h2&gt;
&lt;p&gt;Notre cher utilisateur &lt;em&gt;biboul&lt;/em&gt; se décide à installer la version de
développement de django, que nous appellerons django-trunk, comme
indiqué sur la page &lt;a class="reference external" href="http://docs.djangoproject.com/en/dev/topics/install/#installing-development-version"&gt;How To Install Django&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Il lance donc les commandes suivantes:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
biboul&amp;#64;laptop:~$ svn co http://code.djangoproject.com/svn/django/trunk/ django-trunk
biboul&amp;#64;laptop:~$ ln -s `pwd`/django-trunk/django /usr/lib/python2.5/site-packages/django
biboul&amp;#64;laptop:~$ ln -s `pwd`/django-trunk/django/bin/django-admin.py /usr/local/bin
&lt;/pre&gt;
&lt;p&gt;Il a bien entendu&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="http://code.google.com/p/modwsgi/wiki/QuickInstallationGuide"&gt;configuré&lt;/a&gt; son serveur apache pour utiliser le module WSGI&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://code.google.com/p/modwsgi/wiki/QuickConfigurationGuide"&gt;testé&lt;/a&gt; avec le script wsgi &amp;quot;hello world&amp;quot; que la configuration était bonne&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://code.google.com/p/modwsgi/wiki/IntegrationWithDjango"&gt;modifié&lt;/a&gt; le script wsgi de manière à utiliser son application django &lt;em&gt;mysite&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="le-probleme"&gt;
&lt;h2&gt;Le problème&lt;/h2&gt;
&lt;p&gt;Lors de la première requête à son application &lt;em&gt;mysite&lt;/em&gt;, une belle &amp;quot;&lt;em&gt;500
Internal Server Error&lt;/em&gt;&amp;quot; s'affiche, avec les messages d'erreurs suivants
dans le fichier &lt;em&gt;/var/log/apache2/error_log&lt;/em&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
...&amp;nbsp; mod_wsgi (pid=5803): Target WSGI script '/opt/tcs/tcs.wsgi' cannot be loaded as Python module.
...&amp;nbsp; mod_wsgi (pid=5803): Exception occurred processing WSGI script '/opt/tcs/tcs.wsgi'.
...&amp;nbsp; Traceback (most recent call last):
... &amp;nbsp; &amp;nbsp; &amp;nbsp; File &amp;quot;/opt/tcs/tcs.wsgi&amp;quot;, line 6, in &amp;lt;module&amp;gt;
... &amp;nbsp; &amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp; import django.core.handlers.wsgi
...&amp;nbsp; ImportError: No module named django.core.handlers.wsgi
&lt;/pre&gt;
&lt;p&gt;Mais pourquoi donc, alors qu'un &lt;em&gt;import django.core.handlers.wsgi&lt;/em&gt;
fonctionne correctement, que ce soit dans l'interpréteur ou dans le
shell django?&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="la-reponse"&gt;
&lt;h2&gt;La réponse&lt;/h2&gt;
&lt;p&gt;Tout simplement parce que le répertoire &lt;em&gt;django-trunk&lt;/em&gt; (dont notre cher
utilisateur &lt;em&gt;biboul&lt;/em&gt; a fait un lien symbolique dans le
répertoire*site-packages*) n'est pas accessible à un utilisateur
différent, si il n'est pas dans le même &lt;em&gt;group&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Or, par défaut et sur la plupart des distributions Linux, les processus
&lt;em&gt;apache&lt;/em&gt; sont lancé avec un utilisateur limité (&lt;em&gt;www-data&lt;/em&gt;, &lt;em&gt;www&lt;/em&gt; ou
encore &lt;em&gt;apache&lt;/em&gt;).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="la-solution"&gt;
&lt;h2&gt;La solution&lt;/h2&gt;
&lt;p&gt;Un simple &lt;em&gt;chmod 755&lt;/em&gt; des répertoires parents au répertoire
&lt;em&gt;django-trunk&lt;/em&gt; est suffisant pour régler ce problème.&lt;/p&gt;
&lt;p&gt;Une autre solution, plus propre et sécurisée, serait de placer le
répertoire &lt;em&gt;django-trunk&lt;/em&gt; dans le répertoire &lt;em&gt;/opt&lt;/em&gt;, et de modifier les
liens symboliques pour utiliser ce nouvel emplacement.&lt;/p&gt;
&lt;/div&gt;
</summary></entry><entry><title>30 ans, et toutes mes dents</title><link href="http://mathieu.agopian.info/blog/30-ans-et-toutes-mes-dents.html" rel="alternate"></link><updated>2009-02-15T23:06:00Z</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2009-02-15:/blog/30-ans-et-toutes-mes-dents.html/</id><summary type="html">&lt;p&gt;30 ans aujourd'hui.&lt;/p&gt;
&lt;p&gt;Il reste encore une petite heure dans cette première journée de mes 30
ans. Et je commence un blog.&lt;/p&gt;
&lt;p&gt;Je jette dans ce tout premier billet les motivations pour ce blog (qui
restent pour l'heure très vagues):&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Avoir un aide-mémoire&lt;/li&gt;
&lt;li&gt;Structurer mes pensées et divagations&lt;/li&gt;
&lt;li&gt;Ajouter ma pierre à l'édifice&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Quelques règles pour les futurs billets:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Travailler sur la concision et la clarté&lt;/li&gt;
&lt;li&gt;Une écriture claire, simple et directe&lt;/li&gt;
&lt;li&gt;Pas de billets inutiles&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A bientôt pour la suite.&lt;/p&gt;
</summary></entry></feed>
