<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Akei Blog</title><link href="http://blog.akei.com/" rel="alternate"></link><link href="http://blog.akei.com//feeds/all.atom.xml" rel="self"></link><id>http://blog.akei.com/</id><updated>2011-08-27T00:00:00+02:00</updated><entry><title>Scrape and test any webpage using PhantomJS</title><link href="http://blog.akei.com//javascript/2011/scrape-and-test-any-webpage-using-phantomjs.html" rel="alternate"></link><updated>2011-08-27T00:00:00+02:00</updated><author><name>Nicolas Perriault</name></author><id>tag:blog.akei.com,2011-08-27://javascript/2011/scrape-and-test-any-webpage-using-phantomjs.html/</id><summary type="html">&lt;p&gt;Have you ever tried to scrape or harvest data from an existing website — I mean, even ajax-bloated ones? Did you ever attempt to test javascript-dependent interactions within a Web application you built? Well, if you answered yes to one of the questions above, you might be interested in &lt;a href="http://phantomjs.org/"&gt;PhantomJS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;PhantomJS is a &lt;em&gt;headless WebKit with JavaScript API&lt;/em&gt;. By headless, they mean you can script a real &lt;a href="http://webkit.org/"&gt;Webkit&lt;/a&gt; based browser with no need for a full graphical interface installed.&lt;/p&gt;
&lt;h4 id="installation"&gt;Installation&lt;/h4&gt;
&lt;p&gt;On OSX, installation can be achieved using &lt;a href="https://github.com/mxcl/homebrew"&gt;homebrew&lt;/a&gt; (note that &lt;a href="itunes.apple.com/fr/app/xcode/id448457090?mt=12"&gt;XCode&lt;/a&gt; must be installed on your machine):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ brew install phantomjs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It can take a bit of time for the binaries to be built, especially because of their dependency to &lt;a href="qt.nokia.com/products/"&gt;Qt4&lt;/a&gt;. When it's done, you can test it this way:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ phantomjs
Usage: phantomjs [options] script.[js|coffee] [script argument [script argument ...]]
Options:
    --load-images=[yes|no]             Load all inlined images (default is 'yes').
    --load-plugins=[yes|no]            Load all plugins (i.e. 'Flash', 'Silverlight', ...) (default is 'no').
    --proxy=address:port               Set the network proxy.
    --disk-cache=[yes|no]              Enable disk cache (at desktop services cache storage location, default is 'no').
    --ignore-ssl-errors=[yes|no]       Ignore SSL errors (i.e. expired or self-signed certificate errors).
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Installation instructions for other platforms and alternative methods can be found on &lt;a href="http://code.google.com/p/phantomjs/wiki/Installation"&gt;the PhantomJS project wiki&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As a side note, there's also a Python implementation of PhantomJS, &lt;a href="http://dev.umaclan.com/projects/pyphantomjs"&gt;PyPhantomJS&lt;/a&gt;, which adds &lt;a href="http://dev.umaclan.com/projects/pyphantomjs/wiki/Plugins"&gt;plugins&lt;/a&gt; support! Also, I've found myself having no segfault using the Python version while the standard one is a bit more unstable on my box (no troll please).&lt;/p&gt;
&lt;p&gt;To install PyPhantomJS, let's use &lt;a href="http://www.pip-installer.org/"&gt;&lt;code&gt;pip&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ pip install PyPhantomJS
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The PyPhantomJS executable is named — &lt;em&gt;surprise&lt;/em&gt; — &lt;code&gt;pyphantomjs&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ pyphantomjs
usage: pyphantomjs [options] script.[js|coffee] [script argument [script argument ...]]
Minimalistic headless WebKit-based JavaScript-driven tool
positional arguments:
  script.[js|coffee]    The script to execute, and any args to pass to it
optional arguments:
  -h, --help            show this help message and exit
  --disk-cache {yes,no}
                        Enable disk cache (default: no)
  --ignore-ssl-errors {yes,no}
                        Ignore SSL errors (default: no)
  --load-images {yes,no}
                        Load all inlined images (default: yes)
  --load-plugins {yes,no}
                        Load all plugins (i.e. Flash, Silverlight, ...) (default: no)
  --proxy address:port  Set the network proxy
  -v, --verbose         Show verbose debug messages
  --version             show this program's version and license
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Usage of the two versions is exactly the same.&lt;/p&gt;
&lt;h4 id="basic_usage"&gt;Basic usage&lt;/h4&gt;
&lt;p&gt;PhantomJS scripts can be written in standard JavaScript or in &lt;a href="http://jashkenas.github.com/coffee-script/"&gt;CoffeeScript&lt;/a&gt;. Mainly matter of taste here, but CoffeeScript syntax &lt;a href="http://robots.thoughtbot.com/post/9251081564/coffeescript-spartan-javascript"&gt;looks really interesting&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So let's write our first script, we want to retrieve the weather forecast for a given city using Google:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// script: meteo.js
var page = new WebPage()
, output = { errors: [], results: null };
if (phantom.args.length == 0) {
    console.log('You must specify a city, eg. "Paris, France"');
    phantom.exit(1);
}
page.open('http://www.google.fr/search?q=meteo+' + phantom.args[0], function (status) {
    if (status !== 'success') {
        output.errors.push('Unable to access network');
    } else {
        var cells = page.evaluate(function(){
            try {
                var cells = document.querySelectorAll('.tpo tr tr')[4].querySelectorAll('td');
                return Array.prototype.map.call(cells, function(cell) {
                    return cell.innerText.replace(/[^0-9]/g, '');
                });
            } catch (e) {
                return [];
            }
        });
        if (!cells || !cells.length &amp;gt; 0) {
            output.errors.push('No valid meteo data found');
        } else {
            output.results = {
                city: phantom.args[0],
                today: {
                    afternoon: cells[1],
                    morning:   cells[2],
                },
                tomorrow: {
                    afternoon: cells[3],
                    morning:   cells[4],
                }
            };
        }
        console.log(JSON.stringify(output, null, '    '));
    }
    phantom.exit();
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice we use the &lt;code&gt;phantom.args&lt;/code&gt; Array which contains the parameters passed to the script.&lt;/p&gt;
&lt;p&gt;The main magic happens in the &lt;code&gt;page.evaluate()&lt;/code&gt; method, we pass it a JavaScript function which will be evaluated &lt;strong&gt;within the retrieved page document environment&lt;/strong&gt;. It's a kind of &lt;em&gt;non-persistent XSS injection&lt;/em&gt; just to help you to operate on the page contents =)&lt;/p&gt;
&lt;p&gt;Now it's time to launch the script to see how it goes:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ phantomjs meteo.js "Montpellier, France"
{
    "errors": [],
    "results": {
        "city": "Montpellier, France",
        "today": {
            "afternoon": "29",
            "morning": "17"
        },
        "tomorrow": {
            "afternoon": "28",
            "morning": "17"
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now with an invalid city name:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ phantomjs meteo.js "Unexistent City"
{
    "errors": [
        "No valid meteo data found"
    ],
    "results": null
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let's try with another city, an existing one this time:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ phantomjs meteo.js "Paris, France"
{
    "errors": [],
    "results": {
        "city": "Paris, France",
        "today": {
            "afternoon": "21",
            "morning": "11"
        },
        "tomorrow": {
            "afternoon": "21",
            "morning": "11"
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As a side note and in case you were wondering, you now understand a bit more why I moved to Montpellier ;)&lt;/p&gt;
&lt;h4 id="i_can_haz_screenshots"&gt;I CAN HAZ SCREENSHOTS&lt;/h4&gt;
&lt;p&gt;PhantomJS also allows some nice tricks like injecting scripts to the remote page, very useful when a remote website doesn't ship with your favorite framework (eg. &lt;a href="http://jquery.com/"&gt;jQuery&lt;/a&gt;)… or can render a PNG image of a captured area of the webpage. The example below saves a capture of the weather forecast area:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// script: meteoclip.js
var page = new WebPage();
page.open('http://www.google.fr/search?q=meteo+montpellier,+France', function (status) {
    if (status !== 'success') {
        output.error = 'Unable to access network';
    } else {
        page.clipRect = {
            top: 127,
            left: 170,
            width: 400,
            height: 114
        }
        page.render('meteo.png');
        console.log('Capture saved');
    }
    phantom.exit();
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running the &lt;code&gt;meteoclip.js&lt;/code&gt; script will get yourself this fancy image stored in &lt;code&gt;meteo.jpg&lt;/code&gt;:&lt;/p&gt;
&lt;p style="margin:0 auto"&gt;
    &lt;img src="http://cl.ly/0w2a050b3x2g170u053A/meteo.png" alt="" /&gt;
&lt;/p&gt;

&lt;p&gt;There are tons of other cool topics to cover about PhantomJS, like navigation handling, automated logging in, external resources retrieving, functional testing, code organization… so I'll maybe post a bit more about it soon, who knows!&lt;/p&gt;</summary><category term="phantomjs"></category><category term="javascript"></category><category term="testing"></category></entry><entry><title>Une autre façon de gérer ses settings d'application Django</title><link href="http://blog.akei.com//python/2011/une-autre-facon-de-gerer-ses-settings-dapplication-django.html" rel="alternate"></link><updated>2011-04-13T00:00:00+02:00</updated><author><name>Nicolas Perriault</name></author><id>tag:blog.akei.com,2011-04-13://python/2011/une-autre-facon-de-gerer-ses-settings-dapplication-django.html/</id><summary type="html">&lt;p&gt;Je travaille actuellement sur une application Django que je compte publier sous licence libre, et je suis confronté au problème classique de l'exposition de la configuration au développeur via les &lt;em&gt;settings&lt;/em&gt; de son propre projet.&lt;/p&gt;
&lt;p&gt;Classiquement, on a tendance à proposer les settings "à plat", dans le module &lt;code&gt;settings.py&lt;/code&gt; du projet&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# settings.py
MY_APP_NAME_FOO = 42
MY_APP_NAME_ENABLE_CHUCK_NORRIZ_MODE = True
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Et donc depuis votre appli, vous pouvez récupérer les settings utilisateur de cette façon, en leur assignant une valeur par défaut s'ils ne sont pas déclarés&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# apps/myapp/foo.py
from django.conf import settings

FOO = getattr(settings, 'MY_APP_NAME_FOO', 42)
ENABLE_CHUCK_NORRIZ_MODE = getattr(settings, 'MY_APP_NAME_ENABLE_CHUCK_NORRIZ_MODE', False)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Simple, pratique, suffisant me direz vous. Oui, mais bon, c'est un petit peu verbeux à mon sens, et pas toujours souple pour gérer un catalogue de settings ainsi que leur surcharge. Et puis j'ai l'impression en préfixant systématiquement ces noms de variables de faire insulte à cette merveilleuse fonctionnalité qu'on appelle la gestion des espaces de noms (voire de refaire du PHP &amp;lt; 5.3, ce qui provoque chez moi des bouffées d'angoisse et entame un processus de décapilation douloureux, mais je m'égare).&lt;/p&gt;
&lt;p&gt;Qui plus est, personnellement en temps que développeur, j'aurai tendance à préférer gérer les settings correspondant à une application dans un dictionnaire dédié, un peu comme ce que propose la &lt;code&gt;django-debug-toolbar&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Par exemple, en reprenant l'exemple de code initial ou seul le setting &lt;code&gt;ENABLE_CHUCK_NORRIZ_MODE&lt;/code&gt; est finalement surchargé&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# settings.py
MY_APP_CONFIG = {
    'ENABLE_CHUCK_NORRIZ_MODE': True,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;J'ai donc trouvé un moyen assez simple de proposer cette fonctionnalité. Dans le fichier &lt;code&gt;__init__.py&lt;/code&gt; de votre module d'application, ajoutez le code suivant&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# apps/my_app/__init__.py
from django.conf import settings

app_settings = dict({
    'FOO': 42,
    'ENABLE_CHUCK_NORRIZ_MODE': False,
}, **getattr(settings, 'MY_APP_CONFIG', {}))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Vous constaterez qu'on fusionne bêtement les settings par défaut et ceux de l'utilisateur qui auront la priorité de surcharge. Ainsi, partout depuis votre application, vous pourrez accéder à ce dictionnaire de settings de cette façon&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# apps/my_app/utils.py
from . import app_settings

if app_settings.get('ENABLE_CHUCK_NORRIZ_MODE'):
    print 'Chuck Norriz is watching you'
else:
    print 'Dance dance, little lamb'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Et bien entendu, pour importer les settings de l'application depuis n'importe où (sous réserve que le module de l'application soit dans votre &lt;code&gt;PYTHON_PATH&lt;/code&gt;)&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# foo/bar.py
from my_app import app_settings

print app_settings.get('FOO') # 42
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Merci de votre attention, et à bientôt pour de nouvelle aventures.&lt;/p&gt;</summary><category term="django"></category><category term="python"></category><category term="settings"></category><category term="python"></category></entry><entry><title>Creating a Custom Panel for the Django Debug Toolbar</title><link href="http://blog.akei.com//python/2011/creating-a-custom-panel-for-the-django-debug-toolbar.html" rel="alternate"></link><updated>2011-03-11T00:00:00+01:00</updated><author><name>Nicolas Perriault</name></author><id>tag:blog.akei.com,2011-03-11://python/2011/creating-a-custom-panel-for-the-django-debug-toolbar.html/</id><summary type="html">&lt;p&gt;Sometimes you work on stuff you don't really control, eg. when interacting with some mysterious SOAP server accross the Internets, and you'd appreciate a little help from the Django ecosystem to ease debugging.&lt;/p&gt;
&lt;p&gt;That's — you guessed it — my case currently, and I really appreciated being able to create my own custom panel for adding specific debugging capabilities to the awesome &lt;a href="https://github.com/robhudson/django-debug-toolbar"&gt;Django Debug Toolbar&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here's how I did, learning mainly from the code of the panels shipping with the DJT. I'm supposing you have installed and configured the DJT in your project already.&lt;/p&gt;
&lt;p&gt;First of all, create a &lt;code&gt;panels.py&lt;/code&gt; module within one of your app (or wherever you want as it's in your python path) and create a DebugPanel derivated class, like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from debug_toolbar.panels import DebugPanel
from django.template.loader import render_to_string
from django.utils.translation import ugettext_lazy as _

class MyUsefulDebugPanel(DebugPanel):
    name = 'MyUseful'
    has_content = True

    def nav_title(self):
        return _('Useful Infos')

    def title(self):
        return _('My Useful Debug Panel')

    def url(self):
        return ''

    def content(self):
        context = self.context.copy()
        context.update({
            'infos': [
                {'plop': 'plip'},
                {'plop': 'plup'},
            ],
        })
        return render_to_string('panels/my_useful_panel.html', context)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The debug panel class methods and code should be self-explanatory enough. Just note you have to create a template, here &lt;code&gt;panels/my_useful_panel.html&lt;/code&gt; to be stored in your project &lt;code&gt;templates&lt;/code&gt; directory, with this kind of contents:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;p&amp;gt;Hey, these are useful informations, I swear:&amp;lt;/p&amp;gt;
&amp;lt;ul&amp;gt;
{% for info in infos %}
    &amp;lt;li&amp;gt;Plop is {{ info.plop}}&amp;lt;/li&amp;gt;
{% endfor %}
&amp;lt;/ul&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you have to register the new panel by adding its path to the &lt;code&gt;DEBUG_TOOLBAR_PANELS&lt;/code&gt; tuple, in your &lt;code&gt;settings.py&lt;/code&gt; (create it if it's not there):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DEBUG_TOOLBAR_PANELS = (
    'debug_toolbar.panels.version.VersionDebugPanel',
    'debug_toolbar.panels.timer.TimerDebugPanel',
    'debug_toolbar.panels.settings_vars.SettingsVarsDebugPanel',
    'debug_toolbar.panels.headers.HeaderDebugPanel',
    'debug_toolbar.panels.request_vars.RequestVarsDebugPanel',
    'debug_toolbar.panels.template.TemplateDebugPanel',
    'debug_toolbar.panels.sql.SQLDebugPanel',
    'debug_toolbar.panels.signals.SignalDebugPanel',
    'debug_toolbar.panels.logger.LoggingPanel',
    'my_app_name.panels.MyUsefulDebugPanel',
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Of course it's up to you to put really useful informations there, but here's the result, tadaa:&lt;/p&gt;
&lt;p&gt;&lt;img alt="result screenshot" src="http://media.tumblr.com/tumblr_lhfpagXiif1qbt2jc.png" /&gt;&lt;/p&gt;
&lt;p&gt;Thanks for reading, happy ponying.&lt;/p&gt;</summary><category term="django"></category><category term="djt"></category><category term="debug"></category><category term="python"></category><category term="tips"></category></entry><entry><title>Du Sud avec du Web dedans, et réciproquement</title><link href="http://blog.akei.com//company/2011/du-sud-avec-du-web-dedans-et-reciproquement.html" rel="alternate"></link><updated>2011-01-28T00:00:00+01:00</updated><author><name>Nicolas Perriault</name></author><id>tag:blog.akei.com,2011-01-28://company/2011/du-sud-avec-du-web-dedans-et-reciproquement.html/</id><summary type="html">&lt;p&gt;Ceux qui me connaissent le savent, je suis partisan d'une gestion intransigeante de la qualité sur le Web, et suis donc&amp;nbsp;— comme souvent dans ce cas&amp;nbsp;— un grand fan du cycle de conférences &lt;a href="http://www.paris-web.fr/"&gt;Paris&amp;nbsp;Web&lt;/a&gt; qui a lieu tous les ans en octobre à Paris. C'est l'occasion d'y faire un état des lieux des meilleurs pratiques, d'attraper un rhume, de découvrir de nouvelles techniques, de profiter des joies du métro, ou de rafraîchir ses connaissances (au propre comme au figuré). Surtout, c'est l'occasion d'échanger avec d'autres passionnés de la profession autour de breuvages houblonnés le soir venu en refaisant le Web jusqu'à plus d'heure ni soif.&lt;/p&gt;
&lt;p&gt;Mais voila, Paris, c'est &lt;a href="http://blog.breizh.bz/?166-la-france-vue-parles-toulousains-episode-7"&gt;loin&lt;/a&gt;. Et Paris, ça a tendance à un peu trop vouloir centraliser tout ce qui remue à mon goût. Je m'en rends forcément mieux compte depuis que j'ai déménagé à Montpellier il y a un an et demi (fichtre comme le temps passe). Et surtout, je constate que la région dans laquelle je vis est immensément riche de passion et de compétences autour de ce noble medium qui est le nôtre. Des gens bien, un peu partout autour de moi, qui n'ont pas toujours la possibilité de se déplacer jusqu'à la capitale, de s'y loger, de s'y nourrir, de s'y acheter le nombre d'écharpes nécessaires pour survivre, etc.&lt;/p&gt;
&lt;p&gt;Aussi, durant le trajet en voiture qui nous conduisait au dernier Paris&amp;nbsp;Web moi et mes compagnons de route, nous avons une idée assez folle&amp;nbsp;: organiser un évènement du même type que Paris&amp;nbsp;Web, mais dans le sud. C'est à dire plus proche de nous géographiquement et assurant une meilleure compatibilité feinéantique et calorifère. Ainsi naquit l'idée d'un &lt;a href="http://sudweb.fr/"&gt;SubWeb&lt;/a&gt;.&lt;/p&gt;
&lt;div style="text-align:center"&gt;
&lt;a href="http://sudweb.fr/"&gt;&lt;img src="http://sudweb.fr/img/interface/logo-sudweb.png"/&gt;&lt;/a&gt;
&lt;/div&gt;

&lt;p&gt;Après quelques tergiversations, tatônements, études de terrain et autres défrichements des sols argileux du Gard, nous avons fixé la date et le lieu de la première édition de SubWeb qui se tiendra donc le &lt;strong&gt;vendredi 27 mai 2011, à l'École des Mines d'Alès de Nîmes&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;À toutes fins utiles et pour éviter d'éventuelles déconvenues, il est cependant important de noter que &lt;a href="http://case.oncle-tom.net/2011/sud-web-2011/"&gt;SudWeb ne sera pas un Paris&amp;nbsp;Web transposé plus au sud&lt;/a&gt;. Personnellement, j'ai toujours considéré un peu dommage de ne se focaliser que sur l'aspect &lt;em&gt;frontend&lt;/em&gt; du Web — certes plein d'enjeux à ne surtout pas négliger — alors que d'autres aspects majeurs, bien que moins visibles des utilisateurs, méritent tout autant notre attention&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;les aspects &lt;em&gt;backend&lt;/em&gt;&amp;nbsp;: architecture technique, hébergement, plateformes, langages, serveur, etc.&amp;nbsp;;&lt;/li&gt;
&lt;li&gt;les aspects et enjeux &lt;em&gt;méthodologiques&lt;/em&gt; de la gestion technique&amp;nbsp;— voire commerciale&amp;nbsp;— d'un projet Web&amp;nbsp;;&lt;/li&gt;
&lt;li&gt;les &lt;em&gt;valeurs&lt;/em&gt; métier liées au Web et la production de valeur ajoutée d'un point de vue humain et non plus uniquement mercantile&amp;nbsp;;&lt;/li&gt;
&lt;li&gt;les &lt;em&gt;aspects métier&lt;/em&gt; à proprement parler, et les enjeux qui accompagnent le changement dans les pratiques et les usages associés au Web.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tout un programme&amp;nbsp;! En parlant de programme, un &lt;a href="http://bit.ly/sudweb-2011-appel-orateurs"&gt;appel à conférence&lt;/a&gt; est lancé jusqu'au 7 février inclus, et n'attend que &lt;a href="http://bit.ly/sudweb-2011-appel-orateurs"&gt;votre proposition d'intervention&lt;/a&gt; si vous souhaitez aussi partager un bon moment et votre expérience de &lt;em&gt;faiseur de Web&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Le processus d'inscription, les tarifs et modalités, quand à eux, vous seront communiqués dès que les &lt;em&gt;conditions normales de température et de pression&lt;/em&gt; seront optimales, stay tuned&amp;nbsp;;-)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;EDIT :&lt;/strong&gt; Les inscriptions sont désormais ouvertes, et les &lt;a href="http://tickets.web-ux.org/"&gt;tarifs&lt;/a&gt; connus :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;135 € pour la journée, repas inclus&lt;/li&gt;
&lt;li&gt;15 € supplémentaires vous seront demandés si vous désirez participez à la soirée communautaire le soir même&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Si vous êtes chômeur ou étudiant, &lt;a href="http://sudweb.fr/post/15-places-a-tarif-reduit-pour-les-etudiants-et-demandeurs-d-emploi-"&gt;Akei a le plaisir de sponsoriser votre place&lt;/a&gt; et prend en charge 45€ sur le prix du ticket d'entrée pour la journée de conférence, &lt;strong&gt;ramenant le prix à 90 €&lt;/strong&gt; au lieu de 135 €.&lt;/p&gt;
&lt;p&gt;Enfin, sachez qu'un financement du prix du ticket d'entrée est possible dans le cadre du &lt;a href="http://sudweb.fr/post/Prenez-un-pack-Web-UXSud-Web-et-profitez-du-DIF"&gt;Droit Individuel à la Formation (DIF)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Ne tardez pas pour &lt;a href="http://tickets.web-ux.org/"&gt;commander votre place en ligne&lt;/a&gt;, la &lt;strong&gt;clôture des inscriptions est prévue pour le 2 mai&lt;/strong&gt;.&lt;/p&gt;</summary><category term="sudweb"></category><category term="web"></category><category term="event"></category><category term="france"></category><category term="nîmes"></category></entry><entry><title>Enabling tab completion in OS X Python interactive interpreter</title><link href="http://blog.akei.com//python/2010/enabling-tab-completion-in-os-x-python-interactive-interpreter.html" rel="alternate"></link><updated>2010-12-29T00:00:00+01:00</updated><author><name>Nicolas Perriault</name></author><id>tag:blog.akei.com,2010-12-29://python/2010/enabling-tab-completion-in-os-x-python-interactive-interpreter.html/</id><summary type="html">&lt;p&gt;Python is awesome, and so is its native interactive interpreter. I &lt;a href="http://sontek.net/tips-and-tricks-for-the-python-interpreter"&gt;discovered today&lt;/a&gt; that it can even provide autocompletion using a very simple trick:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# in your ~/.profile
export PYTHONSTARTUP=$HOME/.pythonrc.py

# in a new ~/.pythonrc.py file
try:
    import readline
except ImportError:
    print("Module readline not available.")
else:
    import rlcompleter
    readline.parse_and_bind("tab: complete")
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Don't forget to source your &lt;code&gt;.profile&lt;/code&gt; with &lt;code&gt;$ source ~/.profile&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Magic? Well if like me you're running Mac OS X, it won't work, no autocompletion, nada. OS X seems to ship with a very poor (and obsolete) python, and no &lt;code&gt;readline&lt;/code&gt; implementation — which is mandatory to achieve our purpose. I even tried to install &lt;code&gt;readline&lt;/code&gt; by its own, but it won't solve the problem.&lt;/p&gt;
&lt;p&gt;So while being at tweaking up my python setup, let me get rid of the Apple stuff and install a fresh version of python using &lt;a href="http://mxcl.github.com/homebrew/"&gt;Homebrew&lt;/a&gt;, a great package manager for OSX:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ brew install readline python
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tadaa! Now you get autocompletion, plus a shiny python 2.7.1 (you could also install latest python3 running &lt;code&gt;brew install python3&lt;/code&gt; by the way).&lt;/p&gt;
&lt;p&gt;As a side note, if you &lt;a href="http://blog.akei.com/post/573774396/installer-django-dans-un-environnement-python-virtuel"&gt;work with virtualenvs&lt;/a&gt; like me, creating a new env will now involve specifying which python you want to use:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ mkvirtualenv -p /usr/local/Cellar/python/2.7.1/bin/python \
    --no-site-packages `pwd`/env
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's all folks.&lt;/p&gt;</summary><category term="autocomplete"></category><category term="command line"></category><category term="homebrew"></category><category term="osx"></category><category term="python"></category></entry><entry><title>Install PIL within a virtualenv on Snow Leopard</title><link href="http://blog.akei.com//python/2010/install-pil-within-a-virtualenv-on-snow-leopard.html" rel="alternate"></link><updated>2010-12-03T00:00:00+01:00</updated><author><name>Nicolas Perriault</name></author><id>tag:blog.akei.com,2010-12-03://python/2010/install-pil-within-a-virtualenv-on-snow-leopard.html/</id><summary type="html">&lt;p&gt;As a personal reminder, here's how to install &lt;a href="http://www.pythonware.com/products/pil/"&gt;PIL&lt;/a&gt; with jpeg and freetype support in a Python &lt;a href="/post/573774396/installer-django-dans-un-environnement-python-virtuel"&gt;virtualenv&lt;/a&gt; with a little help from &lt;a href="https://github.com/mxcl/homebrew"&gt;Homebrew&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ brew install jpeg
$ wget http://mirrors.fe.up.pt/pub/nongnu/freetype/freetype-2.4.4.tar.gz
$ tar xvzf freetype-2.4.4.tar.gz &amp;amp;&amp;amp; cd freetype-2.4.4
$ ./configure &amp;amp;&amp;amp; make &amp;amp;&amp;amp; sudo make install
$ cd .. &amp;amp;&amp;amp; rm -rf freetype-2.4.4*
$ mkvirtualenv fubar --no-site-packages
(fubar)$ pip install PIL
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should obtain something like this at the end of the installation process:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;--------------------------------------------------------------------
PIL 1.1.7 SETUP SUMMARY
--------------------------------------------------------------------
version       1.1.7
platform      darwin 2.6.1 (r261:67515, Jun 24 2010, 21:47:49)
              [GCC 4.2.1 (Apple Inc. build 5646)]
--------------------------------------------------------------------
--- TKINTER support available
--- JPEG support available
--- ZLIB (PNG/ZIP) support available
--- FREETYPE2 support available
*** LITTLECMS support not available
--------------------------------------------------------------------
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's all for today folks, thanks for your attention.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;EDIT:&lt;/strong&gt; If you want &lt;a href="http://www.littlecms.com/"&gt;little-cms&lt;/a&gt; support, just run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ brew install little-cms
(fubar)$ pip install PIL
&lt;/code&gt;&lt;/pre&gt;</summary><category term="homebrew"></category><category term="osx"></category><category term="pil"></category><category term="python"></category></entry><entry><title>Choisir son métier, arrêter de le (faire) subir</title><link href="http://blog.akei.com//agile/2010/choisir-son-metier-arreter-de-le-faire-subir.html" rel="alternate"></link><updated>2010-10-27T00:00:00+02:00</updated><author><name>Nicolas Perriault</name></author><id>tag:blog.akei.com,2010-10-27://agile/2010/choisir-son-metier-arreter-de-le-faire-subir.html/</id><summary type="html">&lt;p&gt;Je viens de finir la lecture de l'excellent billet de &lt;a href="http://www.miximum.fr/"&gt;Thibault&lt;/a&gt;, «&amp;nbsp;&lt;a href="http://www.miximum.fr/bien-developper/525-dialogue-avec-un-client"&gt;Dialogue avec un client&lt;/a&gt;&amp;nbsp;». Ce billet présente point pour point ma vision de ce qu'est une collaboration efficace autour d'un projet informatique&amp;nbsp;; je n'y apprends rien de vraiment nouveau (ayant partiellement nourri la conversation qu'il y relate), mais je suis ravi de voir que je ne suis pas le seul à partager le sentiment que la collaboration est vraiment à réinventer dans ce métier.&lt;/p&gt;
&lt;p&gt;Ce qui me chagrine plus en revanche, c'est la lecture de certains commentaires, relativement fatalistes et désabusés&amp;nbsp;; je ne resiste pas à la tentation d'en commenter certains.&lt;/p&gt;
&lt;h4 id="la_m+taphore_du_concessionnaire_automobile"&gt;La métaphore du concessionnaire automobile&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;Le client qui sait exactement ce qu’il veut voudra acheter son site comme une voiture.&lt;/p&gt;
&lt;p&gt;Il veut choisir les options et la peinture tout de suite et connaitre l’enveloppe budgétaire dès le début.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Nous y revoila, la fameuse métaphore du concessionnaire automobile, ça faisait longtemps. Comment comparer le développement d'un produit sur-mesures à l'achat d'un produit de type industriel&amp;nbsp;?&lt;/p&gt;
&lt;p style="text-align:center"&gt;
  &lt;img src="http://farm3.static.flickr.com/2175/2406879653_1f9fb2d592.jpg" alt="voiture"/&gt;
  &lt;br/&gt;
  &lt;small&gt;&lt;a href="http://www.flickr.com/photos/mayme/2406879653/"&gt;funny car&lt;/a&gt;, par &lt;a href="http://www.flickr.com/photos/mayme/"&gt;mayme&lt;/a&gt;&lt;/small&gt;
&lt;/p&gt;

&lt;p&gt;Je peux comprendre que certains, dans notre secteur, s'emploient à minimiser les coûts de production en génériquant des sites vitrines à base de CMS — voire de frameworks surpluginisés — généralement open-source, mais quand vous avez un client qui arrive avec un &lt;em&gt;cahier-décharge&lt;/em&gt; de quelques lignes du type&amp;nbsp;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Mâcon, le 27 juin,&lt;/p&gt;
&lt;p&gt;Monsieur,&lt;/p&gt;
&lt;p&gt;Veuillez trouver ci-après notre cahier des charges pour notre projet d'application Internet&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Un réseau social orienté vente de clous rouillés online, sans oublier les groupes géolocalisés d'amateurs et de collectionneurs&lt;/li&gt;
&lt;li&gt;Une place de marché permettant d'échanger des clous rouillés, avec des graphiques permettant de suivre les enchères en temps réel&lt;/li&gt;
&lt;li&gt;Fournir un extranet fournisseurs leur permettant de saisir leurs prix et leurs quantités&lt;/li&gt;
&lt;li&gt;En option l'intégration d'un service de location de marteaux d'occasion avec un partenaire qu'il nous reste à trouver&lt;/li&gt;
&lt;li&gt;L'intégration avec notre outil CRM maison "SugarForce", codé en Delphi par Mr Paul, ancien interne polyvalent décédé depuis&lt;/li&gt;
&lt;li&gt;Un &lt;em&gt;dacheborde&lt;/em&gt;, parcequ'il parait que cela booste la productivité des forces de vente&lt;/li&gt;
&lt;li&gt;Avec bien entendu des widgets 2.0 pour dynamiser l'ensemble&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;Non seulement c'est peu précis, vague, mal défini, mais quand vous lisez la dernière ligne, généralement l'envie vous prend d'arrêter ce métier séance tenante pour partir élever des chèvres dans le Larzac&amp;nbsp;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Pouvez-vous me fournir un devis &lt;em&gt;à tiroirs&lt;/em&gt; et vous engager sur une livraison  à la rentrée prochaine&amp;nbsp;?&lt;/p&gt;
&lt;p&gt;PS&amp;nbsp;: Je serai absent du 1er juillet au 31 août, voyez avec ma secrétaire (en congés du 15/07 au 15/08) pour toute questions complémentaires
PPS&amp;nbsp;: Je vous ai parlé de l'application Blackberry&amp;nbsp;?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Peut-on imaginer comparer ce type de demande à l'achat d'une &lt;em&gt;voiture&lt;/em&gt; en concession&amp;nbsp;?&lt;/p&gt;
&lt;p&gt;Allez, j'ai dix minutes à tuer, je vous imagine la discussion&amp;nbsp;:&lt;/p&gt;
&lt;div style="float:right;margin:0 0 10px 10px;text-align:center"&gt;
  &lt;img src="http://farm5.static.flickr.com/4024/4647740287_4e7f6ab518.jpg" alt="fish" style="width:230px"/&gt;
  &lt;br/&gt;
  &lt;small&gt;&lt;a href="http://www.flickr.com/photos/ferdinandreus/4647740287/"&gt;fish&lt;/a&gt;, par &lt;a href="http://www.flickr.com/photos/ferdinandreus/"&gt;ferdinandreus&lt;/a&gt;&lt;/small&gt;
&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;— Bonjour monsieur le concessionnaire&lt;br/&gt;
— Bonjour monsieur le client&lt;br/&gt;
— Je voudrais acheter une voiture&lt;br/&gt;
— Ma foi, vous êtes au bon endroit&amp;nbsp;! Quel modèle vous a tapé dans l'oeil&amp;nbsp;?&lt;br/&gt;
— Je veux quelque chose d'assez standard, comme celle-ci là bas, mais avec quelques ajustements mineurs&amp;nbsp;; j'aime bien l'arrière de celle-ci, l'avant de celle-là, l'aspect cabriolet de la petite là-bas, et le bas de caisse de celle cachée dans le fond, là. Il faudra aussi qu'elle soit amphibie, je compte aller pêcher avec quelques amis le week-end prochain. Idéalement, elle pourra également passer d'un mode cabriolet à mobile-home d'une simple pression sur un bouton en cas d'embouteillages prolongés. Avec le meilleur moteur que vous ayez à disposition, cela va sans dire. Ah, précisons tout de suite&amp;nbsp;: il me la faut pour le week-end prochain, puisque j'ai une partie de pêche, vous vous souvenez&amp;nbsp;? Bien entendu, je me résèrve le droit de vous demander dans ce laps de temps quelques options supplémentaires auxquelles je n'ai pas pensé. Vous avez le tarif catalogue sous la main&amp;nbsp;?&lt;br/&gt;
— &lt;em&gt;PAN&amp;nbsp;!&lt;/em&gt; (bruit de déflagration)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Redevenons sérieux deux secondes. &lt;strong&gt;Le développement d'une application Web sera &lt;em&gt;toujours&lt;/em&gt; spécifique et sur-mesures, arrêtons de nourrir l'illusion qu'en utilisant des outils Open Source relativement standardisés &lt;em&gt;techniquement&lt;/em&gt;, nous standardisons &lt;em&gt;fonctionnellement&lt;/em&gt; les développements.&lt;/strong&gt; Ce n'est tout simplement pas vrai 99% du temps.&lt;/p&gt;
&lt;p&gt;Et pour les fans de maquillage d'outils tout-prêts et autres produits industrial-wanabee, j'utilise souvent la formule suivante auprès de mes clients&amp;nbsp;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Un développement spécifique adaptera l'outil à votre métier&amp;nbsp;; un CMS adaptera votre métier à l'outil. La balle est dans votre camp (ou accessoirement dans votre pied.)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Ce qui m'amène à un deuxième constat que je fais de plus en plus au gré des avant-ventes&amp;nbsp;; il existe bel et bien deux métiers distincts, que beaucoup de prospects (et hélas, soyons francs, de prestataires) confondent allégrement&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;le métier &lt;em&gt;d'intégrateur&lt;/em&gt; de solutions&amp;nbsp;;&lt;/li&gt;
&lt;li&gt;le métier de &lt;em&gt;concepteur&lt;/em&gt; de solutions spécifiques.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;J'identifie la part de demandes entrantes du premier type de prestation à environ 80%, si ce n'est plus. J'ai personnellement décidé en créant mon activité de me consacrer exclusivement aux 20% restants. Et ma vie a changé, en plus mieux. Juste pour dire.&lt;/p&gt;
&lt;h3 id="+nbspparle_pas_aux_cons_+a_les_instruitnbsp+"&gt;«&amp;nbsp;Parle pas aux cons, ça les instruit&amp;nbsp;»&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;La moindre tentative de développement modulaire et par priorité sera rejetée et incomprise.&lt;/p&gt;
&lt;p&gt;Les méthodes agiles seront rejetées par la grande majorité des clients par peur. J’en viens même à penser que c’est une perte de temps d’en parler.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Oui, n'en parlons plus et subissons sans broncher, c'est plus confortable intellectuellement. Et puis si ça se vend plus facilement comme ça, pourquoi diable hésiter&amp;nbsp;? Faut juste s'habituer à marcher en canard au troisième recommandé avec AR. Ah mais oui je suis con, le commercial ayant vendu la chose a déjà touché sa comm' et ne gère plus les conséquences de sa vente depuis longtemps, j'oubliais.&lt;/p&gt;
&lt;p style="text-align:center"&gt;
  &lt;img src="http://farm4.static.flickr.com/3434/3747070194_16a125d4ea.jpg" alt="The Fear"/&gt;
  &lt;br/&gt;
  &lt;small&gt;&lt;a href="http://www.flickr.com/photos/stumayhew/3747070194"&gt;The Fear&lt;/a&gt;, par &lt;a href="http://www.flickr.com/photos/stumayhew/"&gt;stumayhew&lt;/a&gt;&lt;/small&gt;
&lt;/p&gt;

&lt;p&gt;Sérieusement, il faut se battre. Il faut chercher à convaincre, expliquer sans relâche. Et en cas de blocage rhédibitoire, ne rien lâcher, &lt;strong&gt;car d'expérience quelqu'un qui n'accepte aucune concession sur un plan méthodologique ne vous passera vraisemblablement &lt;em&gt;rien&lt;/em&gt; sur les aspects commerciaux.&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;De plus dans les méthodes de développement modulaires et surtout évolutives, les litiges sont beaucoup plus complexes à gérer.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Si litige il y a, c'est vraisemblablement que les principes fondamentaux de l'agilité n'ont jamais été appliqués sur le projet en question. Quelques rappels issus du &lt;a href="http://agilemanifesto.org/"&gt;manifeste agile&lt;/a&gt;&amp;nbsp;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Les individus et leurs interactions&lt;/strong&gt; plus que les processus et les outils&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Des logiciels opérationnels&lt;/strong&gt; plus qu’une documentation exhaustive&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;La collaboration avec les clients&lt;/strong&gt; plus que la négociation contractuelle&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;L’adaptation au changement&lt;/strong&gt; plus que le suivi d’un plan&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Vous avez noté le point 3&amp;nbsp;? Ah ben zut. Et je vous parle pas des trois autres, j'imagine que la lecture du compte-rendu de la décision de justice post-echec-projet vous accapare déjà un temps précieux.&lt;/p&gt;
&lt;p&gt;Enfin, j'aimerai bien savoir qu'est ce qui rend les litiges plus évidents à gérer à grands coups d'avenants avec A/R sur un forfait plutôt qu'un bon vieux dialogue autour d'une table dans le cadre d'un projet agile&amp;nbsp;?&lt;/p&gt;
&lt;p&gt;Gérer des litiges contractuels est un véritable métier, très pointu, qui demande de grosses compétences et un sang-froid certain. Ce métier ne me passionne personnellement pas, et je ne peux me résoudre à le considérer comme une fatalité inhérente à mon corps de métier. Je suis là pour fournir des réponses à des besoins précis, quitte à fournir une assistance pour faciliter leur formulation, dans la collaboration et le respect mutuel, au travers d'une communication de type horizontal. &lt;strong&gt;J'axe toute relation avant tout sur la confiance.&lt;/strong&gt; Libre à chacun de se raconter que tout ceci n'est qu'utopie, mais force est de constater qu'après un an d'activité je trouve des gens (des clients pardon, je crois que je m'y ferai jamais) qui embrassent cette manière de fonctionner. Avec d'excellents résultats, par ailleurs.&lt;/p&gt;
&lt;p&gt;Par contre, il est clair qu'avec de telles exigences, je ne donne pas suite à neuf appels entrants sur dix. Le mot est lâché&amp;nbsp;: &lt;strong&gt;je refuse des opportunités business&lt;/strong&gt;. SACRILÈGE&amp;nbsp;! Combien de gens me regardent avec des yeux ronds lorsque je raconte ça… C'est déprimant, d'un certain côté. Travailler plus pour gagner plus&amp;nbsp;? &lt;strong&gt;Et si on parlait plutôt de &lt;em&gt;travailler mieux pour vivre mieux&lt;/em&gt;&amp;nbsp;?&lt;/strong&gt;&lt;/p&gt;
&lt;p style="text-align:center"&gt;
  &lt;a href="http://twitter.com/laurentLC/status/28895308381"&gt;
    &lt;img src="http://media.tumblr.com/tumblr_layfxpSlVV1qbt2jc.png" alt="Avoir le temps…"/&gt;
  &lt;/a&gt;
&lt;/p&gt;

&lt;h4 id="courir_plus_pour_sessouffler_plus"&gt;&lt;em&gt;Courir plus pour s'essouffler plus&lt;/em&gt;&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;(…) si ça implique une absence de visibilité sur une enveloppe globale sur laquelle on s’engage, (c')est tout simplement rédhibitoire pour le client&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;La visibilité s'obtient par la connaissance de la capacité à produire en collaboration avec le client (on appelle ça la &lt;em&gt;vélocité&lt;/em&gt;, dans le jargon agile). &lt;strong&gt;C'est complètement utopique de prétendre estimer une capacité à produire sans connaître la capacité du client à fournir les éléments nécessaires à l'obtention de cette productivité dans la collaboration&lt;/strong&gt;. Et par pitié, qu'on ne me parle pas du cahier des charges de 400 pages que personne ne lit puisqu'obsolète au bout d'une semaine, le besoin ayant déjà changé par trois fois suite à la remontée de l'avis du numéro deux groupe/worldwide et de sa cousine Berthe qui est en deuxième année aux Gobelins.&lt;/p&gt;
&lt;p&gt;Enfin, je réalise que beaucoup de gens comprennent l'&lt;em&gt;agilité&lt;/em&gt; comme la capacité à faire du business malgré tout, voire à tomber du projet coûte que coûte, en s'adaptant aux obstacles au gré du vent et avec les moyens du bord (qui prennent souvent la forme de &lt;em&gt;stagiaires chef de projet&lt;/em&gt; et autres &lt;em&gt;experts techniques junior&lt;/em&gt; recrutés à &lt;a href="http://www.risacher.com/la-rache/index.php?z=2"&gt;la râche&lt;/a&gt; pour faire face à un surcroît de charge non prévue, soit dit en passant). C'est juste tout l'inverse&amp;nbsp;; &lt;strong&gt;je ne connais rien de plus structurant et contraignant en termes de processus que les méthodes agiles&amp;nbsp;!&lt;/strong&gt; Il n'y a pas de vaudou, l'obtention de résultats est à ce prix. Ainsi que d'une certaine façon, au prix d'un lâcher-prise et d'une certaine acceptation du changement, de chaque côté de la barrière (si tant est qu'il y en ait une).&lt;/p&gt;
&lt;p style="text-align:center"&gt;
  &lt;img src="http://farm2.static.flickr.com/1235/549652478_ca4758c99c.jpg" alt="High Wire Act"/&gt;
  &lt;br/&gt;
  &lt;small&gt;&lt;a href="http://www.flickr.com/photos/beginasyouare/549652478/"&gt;High Wire Act&lt;/a&gt;, par &lt;a href="http://www.flickr.com/photos/beginasyouare/"&gt;beginasyouare&lt;/a&gt;&lt;/small&gt;
&lt;/p&gt;

&lt;h4 id="+nbspbon_tu_conclues_l+nbsp_jvais_rater_lap+ronbsp+"&gt;«&amp;nbsp;Bon, tu conclues là&amp;nbsp;? J'vais rater l'apéro&amp;nbsp;»&lt;/h4&gt;
&lt;p&gt;Oui, je suis un peu décontenancé à la lecture de ces quelques commentaires. On constate tous que bien des commanditaires ne comprennent pas les enjeux de leur métier d'acheteur de prestation de développements informatiques. Je découvre qu'il est des professionnels tellement désabusés qu'ils ont visiblement renoncé à toute vélléité d'éducation de leurs prospects sur les questions d'organisation méthodologique et collaborative du travail, part intégrante du champ d'expertise de tout prestataire de développement informatique un tant soit peu digne de ce nom et prétendant offrir un conseil de qualité. Je ne peux me résoudre à croire qu'ils n'ont pas le choix, que tout ceci est inévitable, et que «&amp;nbsp;c'est la crise, il faut bien bouffer, ma pauvre dame&amp;nbsp;».&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Je pense au contraire qu'une part importante de notre métier, j'irai même jusqu'à parler de notre devoir déontologique, est de les accompagner dans la compréhension de leur rôle dans les processus collaboratifs projet afin de maximiser le potentiel de réussite de ces derniers coûte que coûte.&lt;/strong&gt; Et d'&lt;a href="http://www.alistapart.com/articles/no-one-nos-learning-to-say-no-to-bad-ideas/"&gt;apprendre à dire non&lt;/a&gt; lorsque cela s'avère nécessaire, plutôt que de s'engluer dans cette vision fataliste, nivelant par le bas la qualité globale d'une partie grandissante des réalisations auquel le Web est confronté (france.fr anyone?).&lt;/p&gt;
&lt;p&gt;Vous pouvez maintenant basher en commentaire, c'est fait pour ça ;)&lt;/p&gt;</summary><category term="méthodologie"></category><category term="humeur"></category><category term="qualité"></category></entry><entry><title>Hosting a Symfony app on NginX using PHP-FPM</title><link href="http://blog.akei.com//php/2010/hosting-a-symfony-app-on-nginx-using-php-fpm.html" rel="alternate"></link><updated>2010-09-27T00:00:00+02:00</updated><author><name>Nicolas Perriault</name></author><id>tag:blog.akei.com,2010-09-27://php/2010/hosting-a-symfony-app-on-nginx-using-php-fpm.html/</id><summary type="html">&lt;p&gt;I recently had to make a capacity planning study for a client of mine for which I've been developing a Symfony application. Despite the hardware/cloud architecture problem, I also tried to optimize application performances from a webserver software point of view (the application is currently hosted on a standard Apache2 server using mod_php5). I dug Google a bit and found some very enthusiastic comments on &lt;a href="http://php-fpm.org/"&gt;PHP-FPM&lt;/a&gt;, a PHP &lt;a href="http://www.fastcgi.com/"&gt;FastCGI&lt;/a&gt; implementation and the &lt;a href="http://nginx.org/"&gt;NginX&lt;/a&gt; web server.&lt;/p&gt;
&lt;p&gt;While PHP-FPM has just &lt;a href="http://www.php.net/manual/fr/install.fpm.php"&gt;made it to PHP core&lt;/a&gt; in version 5.3.3, the OS version of the linux server we are using, Ubuntu 10.04 LTS, only ships with 5.3.2. Fortunately, &lt;a href="https://launchpad.net/~brianmercer/+archive/php"&gt;Brian Mercer released a PHP-FPM sapi&lt;/a&gt; for these particular OS and PHP versions.&lt;/p&gt;
&lt;p&gt;So installing PHP and PHP-FPM on Ubuntu Lucid Lynx is as easy as:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo apt-get install install python-software-properties
$ sudo add-apt-repository ppa:brianmercer/php
$ sudo apt-get update
$ sudo apt-get install php5-cli php5-common php5-mysql php5-suhosin \
    php5-gd php5-fpm php5-cgi php-pear php5-memcache php-apc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Of course, feel free to add any supplementary packages you need. To start up the PHP-FPM service, run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo service php5-fpm start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As a side note, the service will run on port 9000 by default, but you can tweak this up by editing its configuration file located at &lt;code&gt;/etc/php5/fpm/php-fpm.conf&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now, let's install the NginX web server:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo apt-get install nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's it. Let's create a new site configuration for our Symfony application in a new &lt;code&gt;/etc/nginx/sites-available/mywonderfulwebsite.org&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;server {
    set $website_host "mywonderfulwebsite.org";
    set $website_root "/var/www/mywonderfulwebsite/web";
    set $default_controller "index.php";
    set $symfony_root "/var/www/mywonderfulwebsite/lib/vendor/symfony";

    listen 80;
    server_name $website_host;

    # Gzip
    gzip on;
    gzip_min_length 1000;
    gzip_types text/plain text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript;
    gzip_disable "MSIE [1-6]\.";

    access_log /var/log/nginx/$website_host.access.log;

    root $website_root;

    index $default_controller;

    charset utf-8;

    location /sf {
        # path to folder where all symfony assets are located
        alias $symfony_root/data/web/sf;
        expires max;
    }

    location / {
        # If the file exists as a static file serve it directly without
        # running all the other rewite tests on it
        if (-f $request_filename) {
            expires max;
            break;
        }

        if ($request_filename !~ "\.(js|htc|ico|gif|jpg|png|css)$") {
            rewrite ^(.*) /$default_controller$1 last;
        }
    }

    location ~ "^(.+\.php)($|/)" {

        set $script $uri;
        set $path_info "/";

        if ($uri ~ "^(.+\.php)($|/)") {
            set $script $1;
        }

        if ($uri ~ "^(.+\.php)(/.+)") {
            set $script $1;
            set $path_info $2;
        }

        include /etc/nginx/fastcgi_params;
        fastcgi_pass 127.0.0.1:9000;

        fastcgi_param SCRIPT_FILENAME $website_root$script;
        fastcgi_param SCRIPT_NAME $script;
        fastcgi_param PATH_INFO $path_info;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you may have noticed, I'm taking part of the convenient custom variable system of the NginX configuration syntax, so you'll probably just have to adapt the &lt;code&gt;$website_host&lt;/code&gt;, &lt;code&gt;$website_root&lt;/code&gt;, &lt;code&gt;$default_controller&lt;/code&gt; and &lt;code&gt;$symfony_root&lt;/code&gt; variables to your project needs.&lt;/p&gt;
&lt;p&gt;Enabling the website is achieved with a symbolic link, like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo ln -sf /etc/nginx/sites-available/mywonderfulwebsite.org \
              /etc/nginx/sites-enabled/mywonderfulwebsite.org
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Last but not least, don't forget to launch the webserver:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo /etc/init.d/nginx start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I did some benchmarks but unfortunately the hardware configuration being quite different between the old and the new platform, the statistics generated did not make much sense. But the reactivity of the application, and memory footprint is much much better with the new setup (I know, this is not a very scientific statement).&lt;/p&gt;
&lt;h4 id="sources"&gt;Sources&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://constantshift.com/install-php-fpm-5-3-2-on-ubuntu-10-04-lucid-lynx/"&gt;Install PHP-FPM 5.3.2 on Ubuntu 10.04 (Lucid Lynx) | Constant Shift&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://symfonynotes.com/2009/12/04/hosting-symfony-based-website-with-nginx/"&gt;Hosting symfony based website with nginx&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</summary><category term="nginx"></category><category term="php-fpm"></category><category term="symfony"></category></entry><entry><title>A First Look at node.js and Express</title><link href="http://blog.akei.com//javascript/2010/a-first-look-at-nodejs-and-express.html" rel="alternate"></link><updated>2010-09-01T00:00:00+02:00</updated><author><name>Nicolas Perriault</name></author><id>tag:blog.akei.com,2010-09-01://javascript/2010/a-first-look-at-nodejs-and-express.html/</id><summary type="html">&lt;p&gt;With all the hype coming to server-side Javascript lately, especially around &lt;a href="http://nodejs.org/"&gt;Node&lt;/a&gt;, I was feeling the need to give it a try to see how it goes. Also, getting back to work after three full weeks of unwired holidays was hard enough to worth deserving some playtime with cool and fun technologies.&lt;/p&gt;
&lt;p&gt;Node is described as an &lt;em&gt;Evented I/O Framework for Google's V8 JavaScript Engine&lt;/em&gt;. Think of it as a toolkit to produce high-performance distributed, event-driven and scalable non-blocking network servers. Okay, whatever the way I want to describe the project, it's buzzword-bingo™. Let's say it's mainly about catching events and react accordingly, to make load distribution and parallel processing easier and more effective.&lt;/p&gt;
&lt;h4 id="installing_node"&gt;Installing Node&lt;/h4&gt;
&lt;p&gt;Installation on my Mac went smoothly and took nearly two minutes by compiling it from the sources; here's how I did (there might be easier or better ways, I don't really care):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ mkdir tmp
$ git clone http://github.com/ry/node.git
$ cd node
$ ./configure &amp;amp;&amp;amp; make &amp;amp;&amp;amp; sudo make install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You now have access to the &lt;code&gt;node&lt;/code&gt; executable available on your system.&lt;/p&gt;
&lt;p&gt;A simple example of a Node HTTP server (put the code below in a &lt;code&gt;test.js&lt;/code&gt; file):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var http = require('http');

var server = http.createServer(function(req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.write('Hello World');
  res.end();
});

server.listen(3000, "127.0.0.1");
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then launch the created webserver using the command line:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ node test.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And point your browser at &lt;code&gt;http://127.0.0.1:3000&lt;/code&gt; to get printed &lt;code&gt;Hello World&lt;/code&gt;. Neat, huh?&lt;/p&gt;
&lt;h4 id="introducing_express_a_web_framework_on_top_of_node"&gt;Introducing Express, a Web Framework on top of Node&lt;/h4&gt;
&lt;p&gt;&lt;a href="http://expressjs.com/"&gt;Express&lt;/a&gt; is a Web framework built on top of Node, HTTP and &lt;a href="http://github.com/senchalabs/connect"&gt;Connect&lt;/a&gt;, allowing easy creation of full-fledged Web applications. It has &lt;a href="http://expressjs.com/guide.html#Routing"&gt;routing&lt;/a&gt;, handles &lt;a href="http://expressjs.com/guide.html#Configuration"&gt;environments&lt;/a&gt; as well as several &lt;a href="http://expressjs.com/guide.html#Template-Engines"&gt;template engines&lt;/a&gt; and &lt;a href="http://expressjs.com/guide.html"&gt;much more&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Installation is as easy as Node's one, so here we go:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ git clone http://github.com/visionmedia/express.git
$ cd express
$ git submodule update --init
$ sudo make install &amp;amp;&amp;amp; sudo make install-support
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's it. You can now write your own test application, eg. in a new &lt;code&gt;hello.js&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var app = require('express').createServer();

app.get('/', function(req, res){
    res.send('Hello World');
});

app.get('/hello/:name', function(req, res){
    res.send('Hello ' + req.param('name') + '!');
});

app.listen(3000, "127.0.0.1");

console.log('Server running at http://127.0.0.1:3000/');
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Launch your webapp server by the command line:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ node hello.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Express and will create a Node server listening to the local port 3000, so head your favorite browser to &lt;code&gt;http://127.0.0.1:3000/&lt;/code&gt; then &lt;code&gt;http://127.0.0.1:3000/hello/niko&lt;/code&gt; to get the picture of what the above code does. Those familiar with Web framework such as rails, django or symfony won't be much disturbed.&lt;/p&gt;
&lt;p&gt;Express also ships with an &lt;code&gt;express&lt;/code&gt; executable which provides useful commands. To create a new &lt;code&gt;hello&lt;/code&gt; application skeleton, just run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; $ express hello
   create : hello
   create : hello/app.js
   create : hello/logs
   create : hello/public/javascripts
   create : hello/pids
   create : hello/public/stylesheets
   create : hello/public/stylesheets/style.less
   create : hello/public/images
   create : hello/views/partials
   create : hello/views/layout.jade
   create : hello/views/index.jade
   create : hello/test
   create : hello/test/app.test.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Above command just created an &lt;code&gt;hello&lt;/code&gt; project directory where you can cd into and launch the server by its default front controller &lt;code&gt;app.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ cd hello
$ node app.js
Express server listening on port 3000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that the generated project skeleton implies using &lt;a href="jade-lang.com/"&gt;Jade&lt;/a&gt; as a tremplate engine and the &lt;a href="http://lesscss.org/"&gt;Less CSS syntax&lt;/a&gt;, while one might want to use something else, which is perfectly possible by &lt;a href="http://expressjs.com/guide.html#Configuration"&gt;configuring&lt;/a&gt; the project differently.&lt;/p&gt;
&lt;p&gt;Next steps documentation will be provided by &lt;a href="http://expressjs.com/guide.html"&gt;official Express documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Of course, Express might not be as full-featured as older well-established Web frameworks, but for simple needs it can be pretty easy to setup and deploy, and — probably equally importantly — fun to play with and learn.&lt;/p&gt;
&lt;h4 id="conclusion"&gt;Conclusion&lt;/h4&gt;
&lt;p&gt;As you can see, installing and using Node and Express is quite straightforward, even if you have to dig into the deeper Web to find docs, when they exist. Javascript is a great, agile and well-known language, and taking part of it server-side definitely makes sense if you want my opinion.&lt;/p&gt;
&lt;p&gt;Let's see how this will evolve in the future, as there are not as many backend-oriented libs in JavaScript as there are in other languages like python, ruby or php yet. But &lt;a href="http://github.com/ry/node/wiki/modules"&gt;more and more node modules&lt;/a&gt; are appearing day after day, such as &lt;a href="http://www.learnboost.com/mongoose/"&gt;Mongoose&lt;/a&gt; or &lt;a href="http://socket.io/"&gt;Socket.IO&lt;/a&gt;, which I'll definitely be playing with as soon as possible.&lt;/p&gt;
&lt;p&gt;Thanks for your attention, have fun, take care and don't break the Web.&lt;/p&gt;</summary><category term="nodejs"></category><category term="framework"></category><category term="express"></category></entry><entry><title>Tâches de déploiement spécifiques avec Symfony</title><link href="http://blog.akei.com//php/2010/taches-de-deploiement-specifiques-avec-symfony.html" rel="alternate"></link><updated>2010-05-25T00:00:00+02:00</updated><author><name>Nicolas Perriault</name></author><id>tag:blog.akei.com,2010-05-25://php/2010/taches-de-deploiement-specifiques-avec-symfony.html/</id><summary type="html">&lt;p&gt;&lt;a href="http://www.symfony-project.org/"&gt;Symfony&lt;/a&gt; propose une tâche de déploiement distant utilisant &lt;a href="http://fr.wikipedia.org/wiki/Rsync"&gt;&lt;code&gt;rsync&lt;/code&gt;&lt;/a&gt; fort pratique&amp;nbsp;: une fois configurés les &lt;a href="http://www.symfony-project.org/jobeet/1_4/Doctrine/en/22#chapter_22_deploying"&gt;paramètres du serveur distant&lt;/a&gt; dans le fichier &lt;code&gt;config/properties.ini&lt;/code&gt; de votre projet, un simple appel en ligne de commande synchronisera les fichiers du projet présents sur votre système de fichiers local vers l'hôte distant. Et si vous utilisez une &lt;a href="http://prendreuncafe.com/blog/post/2005/08/29/262-installer-sa-cle-ssh-sur-un-serveur-distant"&gt;clé SSH&lt;/a&gt;, l'opération ne vous demandera même pas de saisir votre mot de passe&amp;nbsp;!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ./symfony project:deploy monserveur --go
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Mais bien souvent, nous avons besoin de plus&amp;nbsp;: préparer un certain nombre de fichiers statiques comme les assets CSS, JavaScript ou les images, ou vider le cache sur la ou les machines distantes pour prendre en compte d'éventuelles modifications de la configuration ou du templating sur la plateforme de production (qui exploite l'environnement du même nom, nous sommes bien d'accord).&lt;/p&gt;
&lt;p&gt;Aussi, il est très simple de faire face à ces besoins spécifiques en créant vous même une tâche de déploiement projet, en surclassant la classe &lt;code&gt;sfProjectDeployTask&lt;/code&gt; fournie en standard par Symfony. Par exemple, voici la tâche de déploiement que j'utilise pour la mise à jour du site &lt;a href="http://www.akei.com/"&gt;Akei&lt;/a&gt;, exploitant mon plugin &lt;a href="http://github.com/n1k0/npAssetsOptimizerPlugin"&gt;&lt;code&gt;npAssetsOptimizerPlugin&lt;/code&gt;&lt;/a&gt; pour la gestion de la minification et l'assemblage des fichiers JavaScript, CSS et images PNG &amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php
class AkeiDeployTask extends sfProjectDeployTask
{
  /**
   * @see sfProjectDeployTask
   */
  protected function configure()
  {
    $this-&amp;gt;addArguments(array(
      new sfCommandArgument('server', sfCommandArgument::REQUIRED, 'The server name'),
    ));

    $this-&amp;gt;addOptions(array(
      new sfCommandOption('go', null, sfCommandOption::PARAMETER_NONE, 'Do the deployment'),
      new sfCommandOption('rsync-dir', null, sfCommandOption::PARAMETER_REQUIRED, 'The directory where to look for rsync*.txt files', 'config'),
      new sfCommandOption('rsync-options', null, sfCommandOption::PARAMETER_OPTIONAL, 'To options to pass to the rsync executable', '-azC --force --delete --progress'),
      new sfCommandOption('optimize', null, sfCommandOption::PARAMETER_OPTIONAL, 'The asset optimizations to make before deploying'),
    ));

    $this-&amp;gt;namespace = 'akei';
    $this-&amp;gt;name = 'deploy';
    $this-&amp;gt;briefDescription = 'Deploys Akei website to a given server';
  }

  /**
   * @see sfProjectDeployTask
   */
  protected function execute($arguments = array(), $options = array())
  {
    // Assets
    if (!is_null($options['optimize']))
    {
      $this-&amp;gt;logSection('deploy', 'assets optimization before deploying');
      $task = new npOptimizeAssetsTask($this-&amp;gt;dispatcher, $this-&amp;gt;formatter);
      $task-&amp;gt;run(array('application' =&amp;gt; 'main'), array('type' =&amp;gt; $options['optimize']));
    }

    // Deployment
    $this-&amp;gt;logSection('deploy', 'deploying...');
    parent::execute($arguments, $options);

    // Remote symfony cc
    if ($options['go'])
    {
      $this-&amp;gt;logSection('deploy', 'remote cache clear');
      $this-&amp;gt;getFilesystem()-&amp;gt;execute('ssh user@monserveur.foo "/path/to/www/akei/symfony cache:clear"');
    }

    $this-&amp;gt;logSection('deploy', 'done.');
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Comme vous pouvez le constater, cette tâche déclare un espace de nom &lt;code&gt;akei&lt;/code&gt; et propose une option supplémentaire, &lt;code&gt;optimize&lt;/code&gt;, permettant de préparer les assets avant déploiement&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ./symfony akei:deploy monserveur --optimize=stylesheet --go
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Cette commande va tout simplement minifier et assembler les feuilles de style définies par la configuration du plugin &lt;code&gt;npAssetsOptimizerPlugin&lt;/code&gt;, déployer les fichiers en production sur la machine &lt;code&gt;monserveur&lt;/code&gt; et vider le cache sur la machine distante une fois l'opération effectuée.&lt;/p&gt;
&lt;p&gt;Notez d'ailleurs l'emploi du très pratique appel à &lt;code&gt;$this-&amp;gt;getFilesystem()-&amp;gt;execute()&lt;/code&gt;, qui permet d'executer des appels à la ligne de commande locale depuis la classe de tâche elle-même&amp;nbsp;; ici, une execution distante à travers SSH.&lt;/p&gt;
&lt;p&gt;Bien entendu, cet exemple est très spécifique aux besoins du site Akei, mais vous pourriez en quelques minutes gérer plus finement une tâche de déploiement plus générique et configurable. Pensez-y pour vos projets ;)&lt;/p&gt;
&lt;p&gt;PS&amp;nbsp;: Ce billet a été écrit en 14 minutes. Merci de votre compréhension.&lt;/p&gt;</summary><category term="symfony"></category><category term="deploy"></category><category term="hosting"></category></entry><entry><title>Formation Symfony Doctrine</title><link href="http://blog.akei.com//company/2010/formation-symfony-doctrine.html" rel="alternate"></link><updated>2010-05-23T00:00:00+02:00</updated><author><name>Nicolas Perriault</name></author><id>tag:blog.akei.com,2010-05-23://company/2010/formation-symfony-doctrine.html/</id><summary type="html">&lt;p&gt;Une nouvelle offre de &lt;a href="http://www.akei.com/fr/services/formation-symfony-doctrine"&gt;formation Symfony 1.4 et Doctrine 1.2&lt;/a&gt; sur 4 jours a été publiée sur le site aujourd'hui&amp;nbsp;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Cette formation sur quatre journées consécutives, a pour but de donner toute autonomie aux développeurs &lt;a href="http://www.php.net/"&gt;PHP5&lt;/a&gt; aguéris à la pratique de la Programmation Orientée Objet pour développer des applications Web en utilisant le framework &lt;a href="http://www.symfony-project.org/"&gt;Symfony&lt;/a&gt; 1.4 et l'ORM &lt;a href="http://www.doctrine-project.org/"&gt;Doctrine&lt;/a&gt; 1.2.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Comme d'habitude, n'hésitez pas à &lt;a href="http://www.akei.com/fr/contact"&gt;prendre contact&lt;/a&gt; si vos besoins sont éventuellement plus spécifiques, il est toujours possible de concevoir une formation sur mesure totalement adaptée.&lt;/p&gt;</summary><category term="doctrine"></category><category term="symfony"></category><category term="training"></category></entry><entry><title>Installer Django dans un environnement Python virtuel avec pip, virtualenv et virtualenvwrapper</title><link href="http://blog.akei.com//python/2010/installer-django-dans-un-environnement-python-virtuel-avec-pip-virtualenv-et-virtualenvwrapper.html" rel="alternate"></link><updated>2010-05-05T00:00:00+02:00</updated><author><name>Nicolas Perriault</name></author><id>tag:blog.akei.com,2010-05-05://python/2010/installer-django-dans-un-environnement-python-virtuel-avec-pip-virtualenv-et-virtualenvwrapper.html/</id><summary type="html">&lt;p&gt;&lt;strong&gt;Ce billet résume les étapes nécessaires pour installer un ou plusieurs environnements de développement &lt;a href="http://www.djangoproject.com/"&gt;Django&lt;/a&gt; fonctionnels, portables et faciles à maintenir sous Mac OS X.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Même si Django est un framework relativement simple à installer, lorsqu'il s'agit de développer plusieurs projets mettant en œuvre différentes versions de ce dernier ou de librairies tierces nécessaires pour assurer son bon fonctionnement, le casse-tête peut rapidement devenir ingérable si l'on ne prend pas garde à bien isoler le contexte applicatif dans un environnement dédié, isolé du reste du système.&lt;/p&gt;
&lt;p&gt;Concrètement, imaginons que j'ai deux projets Django&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Le &lt;em&gt;projet A&lt;/em&gt; utilise Django 1.2-DEV, &lt;a href="http://github.com/robhudson/django-debug-toolbar"&gt;django_toolbar&lt;/a&gt; en version 0.8.3 et Python 2.6&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Le projet B&lt;/em&gt; utilise Django 1.1 et &lt;a href="http://pinaxproject.com/"&gt;Pinax&lt;/a&gt; en version 0.6 tournant avec Python 2.5&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Impossible dans ces conditions d'utiliser une version unique de chacune des librairies installées sur le système (et non, l'utilisation de liens symboliques et de préfixes n'est pas une solution acceptable sur le moyen/long terme).&lt;/p&gt;
&lt;p&gt;Aussi, découvrant progressivement la richesse de l'écosystème &lt;a href="http://python.org/"&gt;Python&lt;/a&gt;, je n'ai pas manqué de m'extasier devant la puissance et la simplicité d'outils tels que &lt;a href="http://pypi.python.org/pypi/pip"&gt;pip&lt;/a&gt;, &lt;a href="http://pypi.python.org/pypi/virtualenv"&gt;virtualenv&lt;/a&gt; et &lt;a href="http://www.doughellmann.com/projects/virtualenvwrapper/"&gt;virtualenvwrapper&lt;/a&gt; pour répondre à ces questions.&lt;/p&gt;
&lt;h4 id="pip_un_installeur_de_paquet_simple_et_efficace"&gt;&lt;code&gt;pip&lt;/code&gt;, un installeur de paquet simple et efficace&lt;/h4&gt;
&lt;p&gt;D'aucuns de ceux qui utilisent une distribution Linux connaissent le bonheur d'utiliser un gestionnaire de paquets. L'installation de programmes et de librairies s'effectuent la plupart du temps en ligne de commande, et la résolution des dépendances est totalement prise en charge de façon transparente.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://pypi.python.org/pypi/pip"&gt;pip&lt;/a&gt; est un gestionnaire de paquets Python, écrit lui-même en Python, qui tient ce rôle à merveille. L'installation de &lt;code&gt;pip&lt;/code&gt; est fort simple, pour peu de disposer de &lt;a href="http://pypi.python.org/pypi/setuptools"&gt;setup_tools&lt;/a&gt;&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo easy_install pip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pour installer un paquet, par exemple la dernière version stable de Django (la 1.1.1), il vous suffit de taper en ligne de commande&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo pip install Django
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pour chercher un paquet, c'est aussi simple que&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ pip search django-debug-toolbar
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id="virtualenv_un_environnement_python_virtuel_+tanche_et_cloisonn+"&gt;&lt;code&gt;virtualenv&lt;/code&gt;, un environnement Python virtuel étanche et cloisonné&lt;/h4&gt;
&lt;p&gt;&lt;a href="http://pypi.python.org/pypi/virtualenv"&gt;virtualenv&lt;/a&gt; vous propose ni plus ni moins de créer à la demande des environnements de travail virtuels pour tout ce qui touche à Python&amp;nbsp;: chaque environnement possède ses propres paquets et librairies, voire dispose de sa propre version de l'interpréteur !&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.doughellmann.com/projects/virtualenvwrapper/"&gt;virtualenvwrapper&lt;/a&gt;, quand à lui, est un jeu de scripts utilitaires permettant de créer, modifier, supprimer et - d'une façon plus générale - de travailler efficacement avec &lt;code&gt;virtualenv&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;L'installation de &lt;code&gt;virtualenv&lt;/code&gt; et de &lt;code&gt;virtualenvwrapper&lt;/code&gt;, ça tombe bien, peuvent se faire directement via &lt;code&gt;pip&lt;/code&gt;&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo pip install virtualenv virtualenvwrapper
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pour finir de configurer l'installation de &lt;code&gt;virtualenv&lt;/code&gt;, il nous reste cependant quelques étapes supplémentaires.&lt;/p&gt;
&lt;p&gt;Tout d'abord, il nous faut créer le répertoire qui contiendra nos environnements virtuels sur notre machine &lt;sup id="fnref:envpath"&gt;&lt;a href="#fn:envpath" rel="footnote"&gt;1&lt;/a&gt;&lt;/sup&gt;&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ mkdir ~/.virtualenvs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Il faut également renseigner ce chemin dans la variable d'environnement &lt;code&gt;$WORKON_HOME&lt;/code&gt; et instancier la gestion des environnements virtuels, le plus simple étant alors de placer les déclarations ad-hoc dans le fichier &lt;code&gt;~/.profile&lt;/code&gt; de votre compte utilisateur &lt;sup id="fnref:prf"&gt;&lt;a href="#fn:prf" rel="footnote"&gt;2&lt;/a&gt;&lt;/sup&gt;&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ echo "export WORKON_HOME=$HOME/.virtualenvs" &amp;gt;&amp;gt; ~/.profile
$ echo "export PIP_VIRTUALENV_BASE=$WORKON_HOME" &amp;gt;&amp;gt; ~/.profile
$ echo "export PIP_RESPECT_VIRTUALENV=true" &amp;gt;&amp;gt; ~/.profile
$ echo "source /usr/local/bin/virtualenvwrapper.sh" &amp;gt;&amp;gt; ~/.profile
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&amp;nbsp;:&lt;/strong&gt; il se peut que selon le mode d'installation utilisé, le chemin vers le fichier &lt;code&gt;/usr/local/bin/virtualenvwrapper.sh&lt;/code&gt; soit à adapter spécifiquement.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Comme nous venons d'ajouter des directives à notre fichier &lt;code&gt;~/.profile&lt;/code&gt;, il faut le recharger&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ source ~/.profile
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id="cr+er_son_premier_environnement_virtuel"&gt;Créer son premier environnement virtuel&lt;/h4&gt;
&lt;p&gt;Reprenons le cas de nos deux projets &lt;code&gt;A&lt;/code&gt; et &lt;code&gt;B&lt;/code&gt; ; nous avons besoin de créer deux environnements virtuels distincts pour travailler sereinement avec les paquets adéquats pour chacun d'eux&amp;nbsp;:&lt;/p&gt;
&lt;p&gt;Nous allons nous concentrer sur la création du premier environnement, pour l'occasion destiné à travailler sur le projet &lt;code&gt;A&lt;/code&gt;&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ mkvirtualenv DjangoEnvX --no-site-packages
New python executable in DjangoEnvX/bin/python
Installing setuptools............done.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;L'environnement a été créé. Notez trois choses importantes&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Les &lt;em&gt;setuptools&lt;/em&gt; ont été installés dans l'environnement, ainsi que &lt;code&gt;pip&lt;/code&gt; même s'il n'en est pas fait mention ; cela nous permettra de disposer de moyens d'installation depuis l'environnement en question&amp;nbsp;;&lt;/li&gt;
&lt;li&gt;L'option &lt;code&gt;--no-site-packages&lt;/code&gt; a été passée, ce qui permet de constituer un environnement intégralement vierge de tout paquet Python. Ne pas passer l'option aurait lié l'ensemble des paquets installés sur le système dans notre environnement de développement, ce que nous ne voulons justement pas&amp;nbsp;!&lt;/li&gt;
&lt;li&gt;Je ne nomme pas l'environnement de travail du nom du «&amp;nbsp;Projet&amp;nbsp;A&amp;nbsp;», dans la mesure où cet environnement pourrait éventuellement être réutilisé pour d'autres projets ayant des besoins et contraintes similaires.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Mais examinons de plus près ce que la commande &lt;code&gt;mkvirtualenv&lt;/code&gt; a créé pour nous&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;~$ workon DjangoEnvX
(DjangoEnvX)~ $ cdvirtualenv
(DjangoEnvX)~/.virtualenvs/DjangoEnvX $ ls -l
total 8
drwxr-xr-x   6 niko  staff  204 May  5 16:08 .
drwxr-xr-x  14 niko  staff  476 May  5 16:08 ..
lrwxr-xr-x   1 niko  staff   63 May  5 16:08 .Python -&amp;gt; /System/Library/Frameworks/Python.framework/Versions/2.6/Python
drwxr-xr-x   9 niko  staff  306 May  5 16:08 bin
drwxr-xr-x   3 niko  staff  102 May  5 16:08 include
drwxr-xr-x   3 niko  staff  102 May  5 16:08 lib
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notez les éléments suivants&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;L'utilisation de la commande &lt;code&gt;workon&lt;/code&gt;, fournie par &lt;code&gt;virtualenvwrapper&lt;/code&gt;, qui permet d'activer un environnement virtuel de travail ; l'autocomplétion du nom de l'environnement virtuel est d'ailleurs disponible&amp;nbsp;!&lt;/li&gt;
&lt;li&gt;La commande &lt;code&gt;cdvirtualenv&lt;/code&gt; nous place directement à la racine du répertoire de l'environnement virtuel&amp;nbsp;;&lt;/li&gt;
&lt;li&gt;Les répertoires &lt;code&gt;bin&lt;/code&gt;, &lt;code&gt;include&lt;/code&gt; et &lt;code&gt;lib&lt;/code&gt; ont été créés, ainsi qu'un lien symbolique vers la version de l'intérpréteur Python du système.&lt;/li&gt;
&lt;li&gt;Un préfixe (ici &lt;code&gt;(DjangoEnvX)&lt;/code&gt;) est ajouté devant le prompt lorqu'on travaille dans un environnement spécifique&amp;nbsp;: cela permet de toujours savoir dans quel environnement on travaille, afin d'éviter les mauvaises surprises ;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Je peux maintenant installer sereinement les paquets dont j'ai besoin dans le cadre de mon projet &lt;code&gt;A&lt;/code&gt;, où que je sois sur le système de fichiers. Par exemple, pour installer la version de dev de Django 1.2 depuis son mirroir git&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(DjangoEnvX)~ $ cd ~
(DjangoEnvX)~ $ pip install -e git+http://github.com/django/django.git#egg=django
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Vérifions que la version de développement de Django a bien été installée dans notre environnement &lt;code&gt;DjangoEnvX&lt;/code&gt;&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(DjangoEnvX)~ $ cdvirtualenv
(DjangoEnvX)~/.virtualenvs/DjangoEnvX $ ll src
total 0
drwxr-xr-x   3 niko  staff  102 May  5 17:07 .
drwxr-xr-x   7 niko  staff  238 May  5 17:08 ..
drwxr-xr-x  16 niko  staff  544 May  5 17:08 django
(DjangoEnvX)~/.virtualenvs/DjangoEnvX $ echo -e "import django\nprint django.get_version()"|python
1.2 beta 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Installons maintenant de la même façon le paquet &lt;code&gt;django-debug-toolbar&lt;/code&gt; en version 0.8.3&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(DjangoEnvX)~ $ pip install -e git+git://github.com/robhudson/django-debug-toolbar@0.8.3#egg=django-debug-toolbar
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nous avons maintenant nos paquets installés, créons un nouveau projet Django. On peut créer un répertoire n'importe où sur le système de fichiers, cela n'a aucune importance&amp;nbsp;: les environnements virtuels et les projets ne sont pas directement liés.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(DjangoEnvX)~ $ cd ~/Sites/
(DjangoEnvX)~/Sites $ django-admin.py startproject my_django_project
(DjangoEnvX)~/Sites $ cd my_django_project/
(DjangoEnvX)~/Sites/my_django_project $ ll
total 24
drwxr-xr-x   6 niko  staff   204 May  5 17:36 .
drwxr-xr-x  94 niko  staff  3196 May  5 17:36 ..
-rw-r--r--   1 niko  staff     0 May  5 17:36 __init__.py
-rwxr-xr-x   1 niko  staff   546 May  5 17:36 manage.py
-rw-r--r--   1 niko  staff  3313 May  5 17:36 settings.py
-rw-r--r--   1 niko  staff   564 May  5 17:36 urls.py
(DjangoEnvX)~/Sites/my_django_project $ ./manage.py runserver
Validating models...
0 errors found

Django version 1.2 beta 1, using settings 'my_django_project.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Voila, nous pouvons travailler sur notre projet dans l'environnement &lt;code&gt;DjangoEnvX&lt;/code&gt; l'esprit serein. On pourra éventuellement ajouter d'autre paquets, ceux-ci ne seront toujours installés que pour cet environnement. Si d'aventure nous voulions versionner la liste des dépendances installées va &lt;code&gt;pip&lt;/code&gt;, c'est aussi simple que&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(DjangoEnvX)~/Sites/my_django_project $ pip freeze &amp;gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Le fichier &lt;code&gt;requirements.txt&lt;/code&gt; ainsi créé contiendra la liste de tous les paquets installés dans l'environnement &lt;code&gt;DjangoEnvX&lt;/code&gt;&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(DjangoEnvX)~/Sites/my_django_project $ cat requirements.txt
-e git+http://github.com/django/django.git@25a45619fe5d7ff3d4f2dbf8f8879a3a00c3625d#egg=Django-1.2_beta_1-py2.6-dev
-e git://github.com/robhudson/django-debug-toolbar@ee1811238e91ae0ad33413b0d40d2f8482101951#egg=django_debug_toolbar-0.8.3-py2.6-dev
wsgiref==0.1.2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Libre à vous alors de versionner ce fichier, ce qui permettra à vos collaborateurs d'instancier un nouvel envronnement de travail et d'installer les dépendances requises d'une simple ligne de commande sur son poste de travail&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(WtfDevEnv)$ pip install -r /path/to/requirements.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;En espérant vous avoir été utile avec ce billet, je m'en retourne vaquer à mes occupations, et vous rappelle à toutes fins utiles qu'&lt;a href="http://www.akei.com/"&gt;Akei&lt;/a&gt; serait quand même super contente d'avoir &lt;a href="http://www.akei.com/fr/contact"&gt;un coup de fil de votre part&lt;/a&gt; pour travailler sur vos chouettes projets en devenir, pourquoi pas en créant plein de &lt;code&gt;virtualenv&lt;/code&gt; Python sympas comme tout ;)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Edit&amp;nbsp;:&lt;/strong&gt; Prise en compte de la variable d'environnement &lt;code&gt;PIP_RESPECT_VIRTUALENV&lt;/code&gt; pour que &lt;code&gt;pip&lt;/code&gt; detecte automatiquement la présence d'un environnement virtuel lors de son utilisation (merci Mathieu !)&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:envpath"&gt;
&lt;p&gt;Vous pouvez bien entendu créer ce répertoire où bon vous semble sur votre système, à partir du moment où votre utilisateur a les droits de lecture et d'écriture dessus.
&amp;#160;&lt;a href="#fnref:envpath" rev="footnote" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:prf"&gt;
&lt;p&gt;le fichier &lt;code&gt;~/.profile&lt;/code&gt; est chargé à chaque démarrage de session Max OS X. Utilisateurs de GNU/Linux, l'équivalent est le fichier &lt;code&gt;~/.bashrc&lt;/code&gt;.
&amp;#160;&lt;a href="#fnref:prf" rev="footnote" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</summary><category term="django"></category><category term="pip"></category><category term="virtualenv"></category></entry></feed>
