<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Mathieu Agopian &#187; django</title>
	<atom:link href="http://mathieu.agopian.info/blog/category/django/feed/" rel="self" type="application/rss+xml" />
	<link>http://mathieu.agopian.info/blog</link>
	<description>Un blog utilisant WordPress</description>
	<lastBuildDate>Tue, 31 Aug 2010 15:48:00 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>lancer gunicorn avec supervisord</title>
		<link>http://mathieu.agopian.info/blog/2010/08/lancer-gunicorn-avec-supervisord/</link>
		<comments>http://mathieu.agopian.info/blog/2010/08/lancer-gunicorn-avec-supervisord/#comments</comments>
		<pubDate>Tue, 31 Aug 2010 15:48:00 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[Python]]></category>
		<category><![CDATA[django]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[mod_wsgi]]></category>
		<category><![CDATA[système]]></category>
		<category><![CDATA[web]]></category>
		<category><![CDATA[supervisord]]></category>

		<guid isPermaLink="false">http://mathieu.agopian.info/blog/?p=337</guid>
		<description><![CDATA[Après avoir mis en place une méthode pour lancer gunicorn avec runit, voici un second article pour lancer ce même gunicorn (qui poutre du poney, rappelons-le), mais avec un outil que je trouve à l&#8217;utilisation bien plus pratique : supervisord Pourquoi supervisord et pas runit ? Après l&#8217;avoir utilisé sans aucun soucis depuis quelques temps sur [...]]]></description>
			<content:encoded><![CDATA[<p>Après avoir mis en place une méthode pour <a title="Lancer gunicorn avec runit" href="http://mathieu.agopian.info/blog/2010/02/lancer-gunicorn-avec-runit/" target="_self">lancer gunicorn avec runit</a>, voici un second article pour lancer ce même <a title="Site officle de Green Unicorn" href="http://gunicorn.org/" target="_self">gunicorn</a> (qui poutre du poney, rappelons-le), mais avec un outil que je trouve à l&#8217;utilisation bien plus pratique : <a title="Site officiel de supervisord" href="http://supervisord.org/index.html" target="_blank">supervisord</a></p>
<h3>Pourquoi supervisord et pas runit ?</h3>
<p>Après l&#8217;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 &laquo;&nbsp;clair&nbsp;&raquo; à mettre en place.</p>
<p>Plus convivial à utiliser parce qu&#8217;il a un client CLI qui permet d&#8217;interagir directement avec les processus monitorés, mais il y a aussi les mêmes fonctionnalités par le biais d&#8217;une interface web fort pratique.</p>
<p>Plus &laquo;&nbsp;clair&nbsp;&raquo; à mettre en place parce qu&#8217;il se lance grâce à un simple script d&#8217;init, et qu&#8217;on peut faire la configuration de tous ses processus à monitorer dans un seul et même fichier de configuration, au lieu d&#8217;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).</p>
<h4>Installer supervisord</h4>
<p>Etant donné que supervisord est en python, il est possible de l&#8217;installer avec un simple</p>
<pre>$ pip install supervisor</pre>
<p>Pour les <em>old-school</em> qui ne profitent pas encore de la puissance et de l&#8217;ergonomie de ce magnifique outil de Ian Bicking, il est possible de remplacer <em>pip</em> par <em>easy_install</em> (même si je vous conseille plutôt de faire un <em>easy_install pip</em> puis de vous passer de <em>easy_install</em> par la suite!).</p>
<h4>Configurer supervisord</h4>
<p>On le configure en deux temps : une première configuration de supervisord lui-même, puis l&#8217;ajout des processus qu&#8217;on veut qu&#8217;il monitore.</p>
<p>Dans le fichier <em>/etc/supervisord.conf</em> :</p>
<pre>; 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</pre>
<p>Bien entendu, faites en sortes que l&#8217;utilisateur <em>www-data</em> ai accès en écriture aux fichiers de log <em>gunicorn_gu-www.log</em> configuré où le processus échouera lors de son lancement.</p>
<p><strong><span style="color: #ff0000;"><span style="text-decoration: underline;">ATTENTION:</span></span></strong> comme indiqué dans le commentaire pour la ligne de configuration <em>inet_http_server</em> il faut absolument faire en sorte que l&#8217;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&#8217;y aura alors pas de moyen de contrôler supervisord par une page web.</p>
<p>La commande indiquée pour information lance <em>gunicorn_django</em> avec dans un environnement virtuel (ce que vous devriez faire vous aussi!). Il n&#8217;est pas nécessaire d&#8217;indiquer le chemin vers le fichier <em>settings.py</em> dans les options de <em>gunicorn_django</em> étant donné que le processus sera lancé directement dans le répertoire le contenant (si vous le configurez correctement avec le paramètre <em>directory</em> comme indiqué ci-dessus).</p>
<p><strong><span style="color: #ff0000;"><span style="text-decoration: underline;">ATTENTION:</span></span></strong> il faut absolument que <em>gunicorn_django</em> soit lancé en <em>foreground</em> (en tâche principale, pas en arrière-plan/<em>background/</em>démon/<em>daemon</em>), ce qui est le cas par défaut si vous n&#8217;avez pas explicitement dit le contraire dans votre fichier de configuration (ou en paramètre de la commande) de <em>gunicorn_django</em>.<br />
En effet c&#8217;est <em>supervisord</em> qui se charge de le faire : si le processus est lancé en arrière-plan, <em>supervisord</em> ne peut pas prendre la main dessus et le contrôler ni le monitorer, et va essayer de le lancer plusieurs fois d&#8217;affilée pensant qu&#8217;il échoue à chaque fois.</p>
<h4>Lancer supervisord et le relancer à chaque reboot</h4>
<p>Il suffit pour cela de créer un script de démarrage dans <em>/etc/init.d/</em> et de le faire se  lancer à chaque démarrage.</p>
<p>Dans <em>/etc/init.d/supervisord</em> :</p>
<pre>#! /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 "set -e"

# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin
DESC="supervisord"
NAME=supervisord
DAEMON=supervisord
MANAGE=supervisorctl
DAEMON_ARGS=""
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 (&gt;= 3.0-6) to ensure that this file is present.
. /lib/lsb/init-functions

case "$1" in
  start)
    $DAEMON
        ;;
  stop)
    $MANAGE shutdown
        ;;
  reload|force-reload)
    $MANAGE reload
    ;;
  restart)
    $MANAGE restart
        ;;
  *)
        echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" &gt;&amp;2
        exit 3
        ;;
esac</pre>
<p>Ce script contient des commentaires en début de fichier qui permettent de le faire se lancer automatiquement à chaque démarrage :</p>
<pre>$ chmod a+x /etc/init.d/supervisord
$ update-rc.d supervisord defaults</pre>
<p>Cette commande devrait indiquer qu&#8217;elle a créé tous les liens symbolique nécessaire pour lancer ce script à chaque démarrage du serveur.<br />
Il ne reste plus qu&#8217;à lancer <em>supervisord</em> maintenant et vérifier que notre site est accessible :</p>
<pre>$ /etc/init.d/supervisord start</pre>
<h4>Monitorer et contrôler ses processus</h4>
<p>Soit en utilisant le client CLI :</p>
<pre>$ supervisorctl</pre>
<p>Soit en accédant à la page web dont on a configuré l&#8217;adresse dans le paramètre <em>inet_http_server</em> dans le fichier de configuration de <em>supervisord</em>. De là vous pouvez redémarrer vos processus, les arrêter, visualiser le contenu du fichier de log&#8230;</p>
<h4>Un soucis?</h4>
<ul>
<li>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</li>
<li>Vérifiez que la commande configuré dans /etc/<em>supervisord.conf</em> se lance correctement manuellement, et que le processus est bien en <em>foreground</em></li>
<li>Faites un tour sur l&#8217;excellente documentation du <a title="Site officiel du projet supervisord" href="http://supervisord.org/" target="_self">projet supervisord</a></li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://mathieu.agopian.info/blog/2010/08/lancer-gunicorn-avec-supervisord/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>PyCon.fr 2010 : retour sur une conférence organisée par l&#8217;AFPY</title>
		<link>http://mathieu.agopian.info/blog/2010/08/pycon-fr-2010-retour-sur-une-conference-organisee-par-lafpy/</link>
		<comments>http://mathieu.agopian.info/blog/2010/08/pycon-fr-2010-retour-sur-une-conference-organisee-par-lafpy/#comments</comments>
		<pubDate>Mon, 30 Aug 2010 07:53:32 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[Python]]></category>
		<category><![CDATA[django]]></category>
		<category><![CDATA[pyconfr]]></category>

		<guid isPermaLink="false">http://mathieu.agopian.info/blog/?p=327</guid>
		<description><![CDATA[Cette édition de PyCon.fr était la deuxième à laquelle j&#8217;ai assisté. Elle s&#8217;est déroulée, comme l&#8217;année dernière, à la CyberBase de la Cité des Sciences à la Villette, Paris. Et comme l&#8217;année dernière, j&#8217;ai apporté ma maigre contribution à l&#8217;organisation sur place, aux côté des super-motivés de l&#8217;Association Francophone PYthon (liste non exhaustive : Christophe [...]]]></description>
			<content:encoded><![CDATA[<p><a title="Site officiel de PyCon.fr 2010" href="http://www.pycon.fr/conference/edition2010" target="_self">Cette édition de PyCon.fr</a> était la deuxième à laquelle j&#8217;ai assisté. Elle s&#8217;est déroulée, comme l&#8217;année dernière, à la CyberBase de la Cité des Sciences à la Villette, Paris.</p>
<p>Et comme l&#8217;année dernière, j&#8217;ai apporté ma maigre contribution à l&#8217;organisation sur place, aux côté des super-motivés de l&#8217;<a title="Site officiel de l'Association Francophone PYthon" href="http://afpy.org" target="_self">Association Francophone PYthon</a> (liste non exhaustive : Christophe Combelles, Michael Scherrer, Gaël Pasgrimaud, Jean-Philippe Camguilhem, Olivier Grisel).</p>
<h3>Le cadre</h3>
<p>Nous avons été encore une fois super bien accueillis par la CyberBase. A disposition, un amphitéatre et une &laquo;&nbsp;base technique&nbsp;&raquo; (salle équipée de nombreux ordinateurs, idéale pour les mises en applications et les travaux pratiques). Les deux salles ont tout l&#8217;équipement nécessaire pour la rétro-projection des présentations, et avaient cette année encore la couverture vidéo de <a title="Site officiel de Ubicast" href="http://ubicast.eu" target="_self">Ubicast</a>.</p>
<h3>Les sponsors</h3>
<p>Vous trouverez la <a title="Liste des sponsors pour PyCon.fr 2010" 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" target="_self">liste des généreux sponsors</a> sur le site de <a title="Site officiel de PyCon.fr 2010" href="http://www.pycon.fr/conference/edition2010" target="_self">PyCon.fr 2010</a>. C&#8217;est en très grande partie grâce à eux qu&#8217;une telle conférence peut être organisée, en restant gratuite, j&#8217;en profite donc pour les remercier à nouveau (et les encourager à recommencer l&#8217;année prochaine bien évidemment <img src='http://mathieu.agopian.info/blog/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> .</p>
<p>Nous avons entre autres eu des présentations de plusieurs produits libres et open-source des sponsors <a title="Site officiel de Logilab" href="http://www.logilab.fr" target="_self">Logilab</a> (avec leur solution Cubic Web qui fait tourner le site de <a title="Site officiel de PyCon.fr" href="http://pycon.fr" target="_self">pycon.fr</a>) et <a title="Site officiel de Itaapy" href="http://itaapy.com" target="_self">Itaapy</a>.</p>
<h3>Les présentations</h3>
<p>Vous trouverez là aussi la liste sur le site de pycon.fr, et je vais lister ici les présentations auxquelles j&#8217;ai pu assister et qui m&#8217;ont marqué (les autres je les verrais en vidéo, grâce à <a title="Site officiel de Ubicast" href="http://ubicast.eu" target="_self">Ubicast</a>, dès qu&#8217;elles seront en ligne) :</p>
<ul>
<li>plusieurs lightning talks, en particulier sur Pyaler, le GSoC sur distutils 2 (vivement que ça sorte!), pysandbox, restkit</li>
<li>Optimisation d&#8217;applications Django de Bruno Renié (mon co-champion &laquo;&nbsp;planet&nbsp;&raquo; pour <a title="Site officiel de Django pour la communauté francophone" href="http://django-fr.org" target="_self">django-fr</a> <img src='http://mathieu.agopian.info/blog/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' />  : plein d&#8217;astuces pour limiter le nombre de requêtes, utiliser le cache de manière intelligente &#8230;</li>
<li>MongoKit de Nicolas Clairon : comment utiliser une base NoSQL MongoDB facilement et efficacement avec MongoKit</li>
<li>Analyse statistique et classification automatique de texte de Olivier Grisel : ça m&#8217;a replongé dans mon mémoire de DEA sur l&#8217;apprentissage automatique <img src='http://mathieu.agopian.info/blog/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </li>
<li>l&#8217;incontournabl)e Gunicorn de Benoit Chesneau : à mon avis, l&#8217;un des plus grands pas en avant pour rendre python (et <a title="Site officiel du framework de développement web Django" href="http://www.djangoproject.com/" target="_self">Django</a>) très facilement déployable</li>
<li>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&#8217;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</li>
<li>Programmation en Flux de Jonathan Schemoul : présentation de <a title="Site officiel de PyF" href="http://www.pyfproject.org/" target="_self">PyF</a> qui permet de traiter de très nombreuses données en limitant la mémoire utilisée (grâce aux &laquo;&nbsp;flux&nbsp;&raquo;). Très intéressant, et un outil graphique en ligne qui m&#8217;a l&#8217;air très simple et pratique d&#8217;utilisation</li>
</ul>
<h3>Les rencontres</h3>
<p>J&#8217;ai pu revoir nombre de personnes déjà rencontrées à l&#8217;édition de l&#8217;année précédent de PyCon.fr, ainsi qu&#8217;à <a title="Conférence Django sur Marseille en 2010" href="http://rencontres.django-fr.org/" target="_self">Djangocong</a>, personnes que je connaissais déjà auparavant par internet (irc, twitter, mailing lists de l&#8217;afpy et de django-fr &#8230;). Revoir &laquo;&nbsp;en vrai&nbsp;&raquo; des personnes qu&#8217;on côtoie très souvent de manière électronique est pour moi toujours une joie et un enrichissement, car l&#8217;échange est bien plus facile, et on tisse des liens bien plus étroits autour d&#8217;une bière que par internet!</p>
<p>Et comme je l&#8217;ai <a title="mon compte twitter" href="http://twitter.com/magopian" target="_self">déjà twitté</a>, j&#8217;ai par ailleurs été ravi de faire de nouvelles connaissances, que j&#8217;espère pouvoir revoir bientôt (un an entre chaque pycon.fr, ça fait long!). Il paraît qu&#8217;il devrait y avoir d&#8217;ici peu un autre <a title="Afpyro National, apéro autour de Python" href="http://www.afpy.org/Members/jpcw2002/national_afpyro_juillet_2010" target="_self">afpyro national</a> 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&#8217;a promis qu&#8217;il ferait un article là-dessus, que je lierai ici <img src='http://mathieu.agopian.info/blog/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </p>
<h3>Les bonus</h3>
<p>Comme si cela ne suffisait pas, il y a eu quelques bonus très sympathiques :</p>
<ul>
<li>un écran géant et tactile dans la CyberBase qui affichait en boucle <a title="ISParade sur le mot clé #pyconfr" href="http://isparade.jp/334515" target="_self">isparade.jp sur le mot clé #pyconfr</a></li>
<li>j&#8217;ai découvert <a title="Aspirator, un news reader en django" href="http://bitbucket.org/bruno/aspirator/wiki/Home" target="_self">aspirator</a> de Bruno Renié, un news reader fait en django, et qui à l&#8217;air vraiment génial&#8230; ce gars est vraiment doué, ça en est limite énervant <img src='http://mathieu.agopian.info/blog/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> . Il travaille aussi sur wombat, un projet de client mail</li>
<li>j&#8217;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)</li>
<li>Alexis Métaireau (<a title="Compte Twitter de Alexis Métaireau" href="http://twitter.com/ametaireau" target="_self">@ametaireau</a>) nous a parlé de son travail pour le GSoC avec Tarek Ziadé sur distutils 2 : vivement que ce soit dans la stdlib!</li>
<li>Rémi Ubscher (<a title="Compte twitter de Rémi Ubscher" href="http://twitter.com/natim" target="_self">@natim</a>) est Eclaireur lui aussi! Salut Fennec <img src='http://mathieu.agopian.info/blog/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </li>
<li>Benoit Calvez (<a title="Compte twitter de Benoit Calvez" href="http://twitter.com/_dzen" target="_self">@_dzen</a>) m&#8217;a parlé de <a title="Fabulator, un système de déploiement basé sur fabric" href="http://bitbucket.org/dzen/fabulator/overview" target="_self">fabulator</a> et de <a title="Site officiel de vinéolia, site communautaire sur le vin" href="http://vineolia.fr/" target="_self">vineolia</a> : énorme potentiel (et il a d&#8217;autres projets dans les cartons <img src='http://mathieu.agopian.info/blog/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </li>
</ul>
<h3>Conclusion</h3>
<p>Vivement la prochaine édition (peut-être sur Pau? ping Jean-Philippe <img src='http://mathieu.agopian.info/blog/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' />  et d&#8217;ici là, peut-être une nouvelle édition d&#8217;une rencontre Django en france!</p>
<p>Encore un énorme merci aux organisateurs, sponsors, présentateurs, et aux participants, qui font de cet évènement un moment agréable et enrichissant!</p>
]]></content:encoded>
			<wfw:commentRss>http://mathieu.agopian.info/blog/2010/08/pycon-fr-2010-retour-sur-une-conference-organisee-par-lafpy/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>djangocong : rencontre Django à Marseille</title>
		<link>http://mathieu.agopian.info/blog/2010/04/djangocong-rencontre-django-a-marseille/</link>
		<comments>http://mathieu.agopian.info/blog/2010/04/djangocong-rencontre-django-a-marseille/#comments</comments>
		<pubDate>Wed, 28 Apr 2010 07:19:08 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[django]]></category>

		<guid isPermaLink="false">http://mathieu.agopian.info/blog/?p=310</guid>
		<description><![CDATA[Comment décrire en un mot cette rencontre Django à Marseille le 24 et 25 Avril : Excellent ! Après cette entrée en matière fort peu cavalière, permettez-moi de développer en deux points : Ce que j&#8217;ai retenu de ma conférence Ma présentation portait sur toutes les astuces et raccourcis à la disposition des développeurs web, [...]]]></description>
			<content:encoded><![CDATA[<p><span style="font-weight: normal;">Comment décrire en un mot cette rencontre Django à Marseille le 24 et 25 Avril :</span></p>
<h3><span style="text-decoration: underline;">Excellent !</span></h3>
<p>Après cette entrée en matière fort peu cavalière, permettez-moi de développer en deux points :</p>
<h4>Ce que j&#8217;ai retenu de ma conférence</h4>
<p>Ma présentation portait sur toutes les astuces et raccourcis à la disposition des développeurs web, et s&#8217;intitulait <a title="Conférence Django Pour Les Fainéants à Djangocong" href="http://agopian.info/djangocong/dplf.html" target="_self">Django Pour Les Fainéants</a>.</p>
<p><span style="text-decoration: underline;">Les points positifs</span> :</p>
<ol>
<li>le format &laquo;&nbsp;interactif&nbsp;&raquo; : faire participer le public le plus possible permet de le garder attentif, éveillé et intéressé</li>
<li>les &laquo;&nbsp;bons points&nbsp;&raquo; : un autre moyen de captiver l&#8217;attention, en récompensant par un bon point (humoristique) les participants
<ul>
<li>bon point &laquo;&nbsp;glue&nbsp;&raquo; : le participant m&#8217;a posé une colle, à laquelle je ne sais répondre</li>
<li>bon point &laquo;&nbsp;fainéant&nbsp;&raquo; : le participant a proposé une astuce/raccourcis/outil que je ne connaissais pas, ou n&#8217;ai pas présenté dans la présentation</li>
<li>bon point &laquo;&nbsp;django pony&nbsp;&raquo; : le participant a partagé une bonne pratique django, ou parlé d&#8217;une astuce/raccourci/outil que je présentais dans une diapo suivante</li>
<li>bon point &laquo;&nbsp;casse nouilles&nbsp;&raquo; : donné à un complice, ou quelqu&#8217;un dont on est sûr qu&#8217;il le prendra à la rigolade, et qui a fait une remarque sur une typo, une parenthèse manquante&#8230;</li>
</ul>
</li>
<li>les diapos &laquo;&nbsp;concises&nbsp;&raquo; ne contenant pas de phrases, mais des mots clé ou listes de points : permet de garder l&#8217;attention du public sur ce que l&#8217;on dit, en ne se servant des diapos que comme un support visuel (pour pointer du doigt)</li>
</ol>
<p><span style="text-decoration: underline;">Les points négatifs</span> :</p>
<ol>
<li>le format &laquo;&nbsp;interactif&nbsp;&raquo; : 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 &gt; 30mn). A contrario, si il n&#8217;y a pas assez (pas du tout!) de participation, le temps de parole prévu sera trop court (25mn &lt; 30mn).</li>
<li>le format &laquo;&nbsp;interactif&nbsp;&raquo; et les &laquo;&nbsp;bons points&nbsp;&raquo; : 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&#8230;</li>
<li>les diapos &laquo;&nbsp;concises&nbsp;&raquo; l&#8217;étaient peut-être trop : rajouter des photos comme par exemple pour les présentations de <a title="Présentations de Olivier Meunier à Djangocong" href="http://neokraft.net/2010/rencontres-django">Olivier Meunier</a> pour que ce soit plus agréable visuellement (ou comme pour celles de Cyril Baÿ, private joke inside <img src='http://mathieu.agopian.info/blog/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </li>
</ol>
<p>Dans l&#8217;ensemble, je pense que la présentation s&#8217;est bien passée, et a été bien reçue d&#8217;après les retours que j&#8217;en ai eu (merci à tous ceux qui sont venus me complimenter, ça fait chaud au coeur!). N&#8217;hésitez pas à me faire vos retours (positifs ET négatifs) dans les commentaires!</p>
<h4>Ce que j&#8217;ai retenu des autres conférences</h4>
<p>Il y a déjà plusieurs comptes rendus très complets sur la toile (<a href="http://blog.alwaysdata.com/fr/2010/04/27/francais-compte-rendu-des-rencontres-django-a-marseille/#more-113">liste exhaustive faite par Nicolas Ferrari sur le blog d&#8217;AlwaysData</a>), je vais donc essayer de résumer ce que j&#8217;ai retenu de chaque présentation :</p>
<ol>
<li>Django 1.2 : un point de nouveautés, par <a href="http://www.alwaysdata.com">Nicolas Ferrari</a> : ﻿lire les releases notes n&#8217;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 <a href="http://djangoadvent.com/">DjangoAdvent</a> <img src='http://mathieu.agopian.info/blog/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </li>
<li><a href="http://blog.sietch-tabr.com/public/presentation_boite_a_outils_django_DjangoCong2010.pdf">Boîte à outils Django</a>, par <a href="http://blog.sietch-tabr.com/">Eric Veiras Galisson</a> : de multiples outils pour vraiment être un fainéant (cf ma présentation) et se simplifier la vie !</li>
<li>Cours de géo, par <a href="http://github.com/samueladam">Samuel Adam</a> : c&#8217;est pas si compliqué finalement, et LA TERRE N&#8217;EST PAS RONDE! présentation très claire sur les outils disponibles et leur puissance</li>
<li><a href="http://neokraft.net/public/2010/djangocong/auth.pdf">Une authentification pour les « contrôler » tous</a>, par <a href="http://neokraft.net/">Olivier Meunier</a> : une &laquo;&nbsp;fail story&nbsp;&raquo;, au moins aussi importante que la &laquo;&nbsp;success story&nbsp;&raquo; aussi présentée, sur comment adapter l&#8217;authentification django à sa sauce</li>
<li><a href="http://media.bruno.im/djangocong-testing.pdf">Tester son projet Django</a>, par <a href="http://bruno.renie.fr/">Bruno Renié</a> : on ne le dit jamais assez, il faut tester ses applications. Non seulement un garde-fou, c&#8217;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&#8217;a pu venir, et ce moins de trois jours avant la conférence!)</li>
<li>Les dessous d’alwaysdata, par <a href="http://www.alwaysdata.com">Cyril</a> : 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&#8217;être fainéants!)</li>
<li><a href="http://www.slideshare.net/nperriault/symfony-pour-les-dveloppeurs-django-et-rciproquement">Django pour les développeurs Symfony</a>, par <a href="http://prendreuncafe.com/">Nicolas Perriault</a> : superbe présentation, donnée de main de maître (et hilarante), qui donne un regard nouveau sur ce qu&#8217;il est possible de faire avec un langage comme PHP. Au final, Symfony à l&#8217;air d&#8217;être une bonne alternative à Django quand le client ne veut pas entendre parler d&#8217;autre chose que PHP.</li>
<li><a href="http://media.jehaisleprintemps.net/talks/djangocong-2010/">Beer over IP</a>, par <a href="http://jehaisleprintemps.net/blog/">Bruno Bord</a> : retour d&#8217;expérience sur un site reconnu (par moi en tout cas <img src='http://mathieu.agopian.info/blog/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' />  d&#8217;utilité publique.</li>
<li><a href="http://j-mad.com/blog/wp-content/uploads/2010/04/Djangocong_2010-Django_et_XMPP.pdf">Django et XMPP</a>, par <a href="http://j-mad.com/blog/">Jean-Michel Armand</a> : quand utiliser XMPP, et surtout quand ne pas l&#8217;utiliser! Un retour d&#8217;expérience donné par notre cher Jean-Michel préféré!</li>
<li><a href="http://www.slideshare.net/beoitc/gunicorn-django-djangocong20100425">Gunicorn, Django et WSGI</a>, par <a href="http://benoitc.im/">Benoît Chesneau</a> : présentation d&#8217;un outil que j&#8217;utilise en production depuis bientôt un mois sans jamais avoir eu le moindre soucis.</li>
<li><a href="http://neokraft.net/public/2010/djangocong/amqp.pdf">Des lapins et des poneys, quand AMPQ rencontre Django</a>, par <a href="http://neokraft.net/">Olivier Meunier</a> : explication d&#8217;une mise en oeuvre de AMPQ comme moyen de communication entre différents processus, très instructif</li>
<li>Introduction à <a href="http://pinaxproject.com/">Pinax</a>, par David Paccoud : boîte à outil pleine d&#8217;applications réutilisables qui permet d&#8217;arriver à un produit fini avec moins d&#8217;efforts et plus rapidement. La question étant, comment faire la part entre un projet &laquo;&nbsp;fait main&nbsp;&raquo; qu&#8217;on maîtrise, et une boîte à outil qu&#8217;on ne maîtrise pas forcément. Qu&#8217;en est-il de la courbe d&#8217;apprentissage?</li>
<li>« <a href="http://copyleft.free.fr/djangocong-i18n/">‘Tain cong’, Django speaks marseillais</a>« , par <a href="http://copyleft.free.fr/">Stéphane Raimbault</a> : il y a plusieurs formes plurielles dans certains langages (Polonais par exemple), et beaucoup plus de spécificités que je ne m&#8217;en doutais! Il va falloir que je reprenne ma copie&#8230;</li>
<li>Internationalisation de contenus avec Django, par <a href="http://www.marmelune.net/">Benoît Bryon</a> : présentation très claire des limites (et de certaines solutions) de Django pour tout ce qui à trait à la traduction et localisation de sites</li>
<li><a href="http://www.slideshare.net/beoitc/couchdbkit-1-django">CouchDB et Django, l’utilisation de CouchDBKit</a>, par <a href="http://benoitc.im/">Benoît Chesneau</a> : 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&#8217;écriture régulière sur le disque pour éviter les pertes de données lors d&#8217;un crash)</li>
<li>Les limites de Django, par <a href="http://larlet.fr/">David Larlet</a> : qui n&#8217;a pas été confronté à certaines de ces limites? Un regard réaliste sur ce qu&#8217;on ne peut tout simplement pas faire (à l&#8217;heure actuelle) avec Django.</li>
</ol>
<h4>Ce que j&#8217;ai retenu de l&#8217;orga</h4>
<p>Un immense merci à David et Jean-Michel, sans qui nous n&#8217;aurions tout simplement pas eu cette occasion de se rencontrer entre passionnés de Django (et d&#8217;autres! dédicace à <a href="http://jeremy.wordpress.com/2010/04/25/djangocong-2010/">Jérémy Lecour</a>), et de voir en chair et en os des personnes (oserai-je dire amis?) qu&#8217;on cotoie parfois tous les jours sur internet, certains depuis des années! Merci aussi à Johan Charpentier pour l&#8217;impression des TShirts!</p>
<p>J&#8217;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&#8217;accessibilité de chacun. Pouvoir discuter et échanger des expériences avec des collègues (comme on dit dans le sud <img src='http://mathieu.agopian.info/blog/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' />  est vraiment enrichissant.</p>
<p>Plus que tout, je tiens encore une fois à remercier Jean-Michel, qui s&#8217;est occupé d&#8217;une (très) grande partie de la logistique, de nous avoir trouvé les salles, chouchoutés (combien d&#8217;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&#8217;être autant appliqué à nous apporter tout le confort possible.</p>
<p>Vivement la prochaine rencontre!</p>
]]></content:encoded>
			<wfw:commentRss>http://mathieu.agopian.info/blog/2010/04/djangocong-rencontre-django-a-marseille/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Django : Envoyer des emails HTML avec images inline (intégrées)</title>
		<link>http://mathieu.agopian.info/blog/2010/02/django-envoyer-des-emails-html-avec-images-inline-integrees/</link>
		<comments>http://mathieu.agopian.info/blog/2010/02/django-envoyer-des-emails-html-avec-images-inline-integrees/#comments</comments>
		<pubDate>Wed, 17 Feb 2010 09:37:33 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[Python]]></category>
		<category><![CDATA[django]]></category>
		<category><![CDATA[web]]></category>
		<category><![CDATA[html email]]></category>
		<category><![CDATA[inline image]]></category>
		<category><![CDATA[smtplib]]></category>

		<guid isPermaLink="false">http://mathieu.agopian.info/blog/?p=231</guid>
		<description><![CDATA[Voyons comment envoyer des emails multiparties (texte et HTML) avec des images inline (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&#8217;i18n avec gettext, des filtres et tags, de l&#8217;utilisation du contexte&#8230; Ce code est un mélange de deux méthodes complémentaires, [...]]]></description>
			<content:encoded><![CDATA[<p>Voyons comment envoyer des emails multiparties (texte et HTML) avec des images <em>inline</em> (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&#8217;i18n avec gettext, des filtres et tags, de l&#8217;utilisation du contexte&#8230;</p>
<p>Ce code est un mélange de deux méthodes complémentaires, une sur les <a title="Mails HTML par Ross Poulton" href="http://www.rossp.org/blog/2007/oct/25/easy-multi-part-e-mails-django/" target="_self">mails HTML par Ross Poulton</a>, et l&#8217;autre venant d&#8217;un <a title="Djangosnippet par sleytr" href="http://www.djangosnippets.org/snippets/285/" target="_self">djangosnippet par sleytr</a>.</p>
<p>Pour en faciliter l&#8217;utilisation et la maintenance, j&#8217;ai mis ce petit module <a title="Page du projet django-nice-emails sur bitbucket" href="http://bitbucket.org/magopian/django-nice-emails/" target="_self">django-nice-emails sur bitbucket</a>.</p>
<p>Plutôt que de copier le code ici, je vais plutôt en décrire les grandes étapes:</p>
<ol>
<li>Utiliser le setting <em>DEFAULT_FROM_EMAIL</em> si l&#8217;expéditeur n&#8217;est pas fourni</li>
<li>Créer un <em>django.template.Context</em> à partir du dictionnaire fourni (permet de remplacer les <em>{{ var }}</em> dans les templates)</li>
<li>Utiliser le contexte créé pour initialiser le contenu texte, HTML ainsi que le sujet</li>
<li>Transformer le destinataire fourni en liste (si ce n&#8217;est pas déjà une liste de destinataires)</li>
<li>Créer un <em>django.core.mail.EmailMultiAlternatives</em> qui est la base de notre email (basé sur le contenu texte)</li>
<li>Rajouter la partie HTML</li>
<li>Rajouter les images en <em>inline</em> si nécéssaire</li>
<li>Envoyer le mail</li>
</ol>
<p>Rien de compliqué donc dans ce code qui fait moins de 20 lignes &laquo;&nbsp;utiles&nbsp;&raquo;.</p>
<p>Voyons maintenant un exemple d&#8217;utilisation avec de la traduction et de l&#8217;héritage de templates:</p>
<h3>Les templates</h3>
<p>On utilise ici la méthode de Ross Poulton qui consiste à ne fournir en paramètre <em>template_name</em> que la base du nom de fichier, sans l&#8217;extension. On fournit ensuite au <em>django.template.loader</em> ce <em>template_name</em> avec l&#8217;extension <em>.txt</em> et <em>.html</em>, ces templates doivent donc exister tous les deux.</p>
<p><span style="text-decoration: underline;"><strong>templates/test_email.txt</strong></span></p>
<pre class="brush:text">{% load i18n %}
{% trans "Bonjour" %} {{ nom }},

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

{% trans "Cordialement" %}

Mathieu Agopian</pre>
<p><span style="text-decoration: underline;"><strong>templates/test_email.html</strong></span></p>
<pre class="brush:html">{% extends "base_email.html" %}
{% load i18n %}
{% block email_content %}
&lt;p&gt;{% trans "Bonjour" %} {{ nom }},&lt;/p&gt;

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

&lt;p&gt;{% trans "Cordialement" %}&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Mathieu Agopian&lt;/em&gt;&lt;/p&gt;
&lt;img src="cid:signature" /&gt;
{% endblock email_content %}</pre>
<p><span style="text-decoration: underline;"><strong>templates/base_email.html</strong></span></p>
<pre class="brush:html">&lt;table width="600"&gt;
&lt;tr&gt;&lt;td&gt;&lt;img src="cid:logo" /&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;
    {% block email_content %}{% endblock email_content %}
&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;</pre>
<h3>Le contexte</h3>
<p>Un simple dictionnaire python pour chaque tag utilisé dans les templates:</p>
<pre class="brush:python">context = {'nom': 'Johnny Biboul'}</pre>
<h3>Les images</h3>
<p>Elles doivent être passées en paramètres dans un tuple de tuples, sous la forme ((&#8216;/chemin/vers/image.png&#8217;, &#8216;tagimage&#8217;), &#8216;/chemin/vers/image2.png&#8217;, &#8216;tagimage2&#8242;), &#8230;). Si les images sont dans le répertoire <em>images</em> du <em>MEDIA_ROOT</em>:</p>
<pre class="brush:python">images = (
    (path.join(settings.MEDIA_ROOT, 'images', 'signature.png'), 'signature'),
    (path.join(settings.MEDIA_ROOT, 'image', 'logo.png'), 'logo'))</pre>
<p>Dans les templates, on utilisera les images sous la forme <em>&lt;img src=&#8217;cid:tagimage&#8217; /&gt;</em>.</p>
<h3>Le code</h3>
<pre class="brush:python">#!/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"Test de mail pour %(nom)s") % {'nom': '{{ nom }}'}

send_nice_email(
        template_name='test_email',
        email_context=context,
        subject=subject,
        recipients='johnny@biboul.com',
        sender='foo bar ',
        images=images)</pre>
<h3>Conclusion</h3>
<p>Il vous suffit de mettre ce code dans une de vos vues pour pouvoir faire de jolis mails de confirmation d&#8217;inscription, des newsletters, ou voir même (bouh! c&#8217;est mal!) du mass-mailing. Veillez néanmoins à ne pas forcer la dose sur le html, ou les images inlines!</p>
]]></content:encoded>
			<wfw:commentRss>http://mathieu.agopian.info/blog/2010/02/django-envoyer-des-emails-html-avec-images-inline-integrees/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>lancer gunicorn avec runit</title>
		<link>http://mathieu.agopian.info/blog/2010/02/lancer-gunicorn-avec-runit/</link>
		<comments>http://mathieu.agopian.info/blog/2010/02/lancer-gunicorn-avec-runit/#comments</comments>
		<pubDate>Wed, 10 Feb 2010 10:37:39 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[Python]]></category>
		<category><![CDATA[django]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[mod_wsgi]]></category>
		<category><![CDATA[système]]></category>
		<category><![CDATA[web]]></category>
		<category><![CDATA[apache]]></category>
		<category><![CDATA[gunicorn]]></category>
		<category><![CDATA[mod_proxy]]></category>
		<category><![CDATA[runit]]></category>
		<category><![CDATA[wsgi]]></category>

		<guid isPermaLink="false">http://mathieu.agopian.info/blog/?p=214</guid>
		<description><![CDATA[ATTENTION votre serviteur a fait le test pour vous sur une ubuntu: après avoir installé runit et runit-run, le système ne démarre plus. Pour suivre les étapes de ce billet, il ne faut pas installer runit-run, qui ne doit être installé que lorsque l&#8217;on souhaite remplacer totalement le système d&#8217;initialisation (et cela demande plus de configuration qu&#8217;une [...]]]></description>
			<content:encoded><![CDATA[<p><span style="color: #ff0000;"><strong><span style="text-decoration: underline;">ATTENTION</span><span style="font-weight: normal;"> votre serviteur a fait le test pour vous sur une ubuntu: après avoir installé <em>runit</em> et <em>runit-run</em>, le système ne démarre plus. Pour suivre les étapes de ce billet, il ne faut <span style="text-decoration: underline;">pas</span> installer <em>runit-run</em>, qui ne doit être installé que lorsque l&#8217;on souhaite remplacer totalement le système d&#8217;initialisation (et cela demande plus de configuration qu&#8217;une simple installation du paquet).</span></strong></span></p>
<p><span style="color: #ff0000;"><strong><span style="font-weight: normal;">Pour les malheureux qui ont fait les frais de la première version de ce billet (demandant d&#8217;installer <em>runit-run</em>), je ne peux que m&#8217;excuser platement, et vous fournir la méthode &laquo;&nbsp;au secours rescue moi!&nbsp;&raquo;: <a title="Récuperer son installation ubuntu" href="http://www.tenshu.fr/ubuntu/recuperer-une-installation-avec-un-cd-ubuntu/" target="_self">récuperer une installation avec un cd ubuntu</a>. Une fois chrooté sur la partition root, il vous restera à désinstaller le paquet fautif et redémarrer:</span></strong></span><br />
<span style="color: #ff0000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$ aptitude purge runit-run</span></p>
<h6><span style="color: #ff0000;"><span style="color: #000000;">Edité le 2010/02/10 à 20:58</span></span></h6>
<hr />Pour faire suite au précédent billet <a title="Recette d'installation et d'utilisation de gunicorn avec django" href="/blog/2010/02/gunicorn-un-server-wsgi-ultra-simple-a-utiliser-et-configurer/" target="_self">gunicorn: un server wsgi ultra simple à utiliser et configurer</a>, voici une recette rapide pour lancer automatiquement (et monitorer) gunicorn avec <a title="Page officielle du project runit" href="http://smarden.org/runit/" target="_self">runit</a>.</p>
<h3>Pourquoi runit et pas sysvinit, inittab, upstart, &#8230;</h3>
<p>Je vous laisse consulter la page <a title="Bénéfices de runit par rapport aux autres systèmes" href="http://smarden.org/runit/benefits.html" target="_self">benefits</a> sur le site officiel pour vous faire une idée. Pour les personnes ne parlant pas anglais, voici un bref résumé:</p>
<ol>
<li>Un répertoire par service, contenant un script <em>run</em></li>
<li>Un environnement d&#8217;exécution propre et prédictible pour chaque processus</li>
<li>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!)</li>
<li>Très peu encombrant, efficace&#8230; et peut complètement remplacer le système d&#8217;initialisation de votre linux</li>
</ol>
<h3>Utiliser runit avec le système d&#8217;initialisation actuel</h3>
<p>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 &laquo;&nbsp;avec&nbsp;&raquo; le système d&#8217;initialisation actuel.</p>
<h4>Installer runit</h4>
<pre class="brush:shell">$ aptitude install runit</pre>
<p><strong><span style="color: #ff0000;"><span style="text-decoration: underline;">ATTENTION:</span></span></strong> Si vous installez aussi <em>runit-run</em>, il vous faut absolument configurer votre système (<a title="Méthode à suivre pour remplacer init" href="http://smarden.org/runit/replaceinit.html" target="_self">﻿How to replace init</a>), 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).</p>
<h4>Créer un répertoire pour le service gunicorn et son script de lancement</h4>
<pre class="brush:shell">$ mkdir /etc/sv/gu-monprojet
$ vi /etc/sv/gu-monprojet/run</pre>
<p>Et voici le contenu du script run</p>
<pre class="brush:shell">#!/bin/bash
source /path/to/venv/bin/activate # activer le virtualenv
cd /path/to/django/project
exec gunicorn_django -b localhost:8080 --workers=3</pre>
<p>Avec la version de gunicorn utilisée pour l&#8217;écriture de cet article, il est nécessaire d&#8217;être dans le répertoire du projet django (là ou se situe le fichier <em>settings.py</em>) pour lancer <em>gunicorn_django</em>.</p>
<p>Dans une future version (la modification est dans le <em>trunk</em> à l&#8217;heure de l&#8217;écriture) il suffira d&#8217;indiquer le chemin vers le fichier <em>settings.py</em> comme paramètre à la commande <em>gunicorn_django</em>.</p>
<p>Enfin, ne pas oublier de rajouter les droits d&#8217;exécution sur le script qu&#8217;on vient de créer:</p>
<pre class="brush:shell">$ chmod a+x /etc/sv/gu-monprojet</pre>
<h4>Indiquer à runit qu&#8217;il doit lancer le script</h4>
<p>Pour celà, un simple lien symbolique, et dans les secondes qui suivent le script sera lancé:</p>
<pre class="brush:shell">$ ln -s /etc/sv/gu-monprojet /etc/service/</pre>
<h3>Et c&#8217;est tout!</h3>
<p>Il suffit maintenant d&#8217;en profiter en allant sur http://localhost:8080, en configurant apache pour proxiser les requêtes directement dessus (cf le billet <a title="Recette d'installation et d'utilisation de gunicorn avec django" href="/blog/2010/02/gunicorn-un-server-wsgi-ultra-simple-a-utiliser-et-configurer/" target="_self">gunicorn: un server wsgi ultra simple à utiliser et configurer</a>), ou encore en utilisant la commande <em>sv</em> pour gérer le service gunicorn:</p>
<pre class="brush:shell">$ sv status gu-monprojet
$ sv check gu-monprojet
$ sv up gu-monprojet
$ sv down gu-monprojet
$ sv restart gu-monprojet
$ sv hup gu-monprojet
...</pre>
]]></content:encoded>
			<wfw:commentRss>http://mathieu.agopian.info/blog/2010/02/lancer-gunicorn-avec-runit/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>gunicorn: un server wsgi ultra simple à utiliser et configurer</title>
		<link>http://mathieu.agopian.info/blog/2010/02/gunicorn-un-server-wsgi-ultra-simple-a-utiliser-et-configurer/</link>
		<comments>http://mathieu.agopian.info/blog/2010/02/gunicorn-un-server-wsgi-ultra-simple-a-utiliser-et-configurer/#comments</comments>
		<pubDate>Tue, 09 Feb 2010 16:08:46 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[Python]]></category>
		<category><![CDATA[django]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[mod_wsgi]]></category>
		<category><![CDATA[web]]></category>
		<category><![CDATA[apache]]></category>
		<category><![CDATA[gunicorn]]></category>
		<category><![CDATA[mod_proxy]]></category>
		<category><![CDATA[wsgi]]></category>

		<guid isPermaLink="false">http://mathieu.agopian.info/blog/?p=198</guid>
		<description><![CDATA[Deux billets le même jour, c&#8217;est fête! Voici une recette simple pour installer, configurer et utiliser gunicorn avec apache et django. Installer gunicorn Pour installer gunicorn dans son environnement virtuel: $ pip install -E /path/to/venv install gunicorn Configurer Apache en proxy Apache servira les fichiers statiques, et &#171;&#160;proxisera&#160;&#187; toutes les autres requêtes directement à gunicorn [...]]]></description>
			<content:encoded><![CDATA[<p>Deux billets le même jour, c&#8217;est fête!</p>
<p>Voici une recette simple pour installer, configurer et utiliser <a title="Page officielle du projet gunicorn" href="http://github.com/benoitc/gunicorn" target="_self">gunicorn</a> avec <a title="Page officielle du serveur web Apache" href="http://www.apache.org/" target="_self">apache</a> et <a title="Page officielle du projet Django, framework web en python" href="http://www.djangoproject.com/" target="_self">django</a>.</p>
<h3>Installer gunicorn</h3>
<p>Pour installer gunicorn dans son environnement virtuel:</p>
<pre>$ pip install -E /path/to/venv install gunicorn</pre>
<h3>Configurer Apache en proxy</h3>
<p>Apache servira les fichiers statiques, et &laquo;&nbsp;proxisera&nbsp;&raquo; toutes les autres requêtes directement à gunicorn qui sera lancé en local sur le port 8080:</p>
<pre>&lt;VirtualHost *:80&gt;
ServerName example.com
ServerAlias www.example.com

DocumentRoot /path/to/django/project

&lt;Proxy *&gt;
    Order deny,allow
    Allow from all
&lt;/Proxy&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

&lt;Directory /path/to/django/project&gt;
    Order deny,allow
    Allow from all
    Options -Indexes
&lt;/Directory&gt;
&lt;/VirtualHost&gt;</pre>
<p>Pour que le tout fonctionne correctement, il faut activer les modules <em>mod_proxy</em> et <em>mod_proxy_html</em> (et en option <em>mod_cache</em>):</p>
<pre>$ a2enmod proxy proxy_http cache</pre>
<p>Puis de redémarrer le server Apache:</p>
<pre>$ /etc/init.d/apache2 restart</pre>
<h3>Lancer gunicorn</h3>
<p>Il suffit de se placer dans le répertoire du projet django (avec le virtualenv activé), puis de taper:</p>
<pre>$ gunicorn_django -b localhost:8080 --workers=2</pre>
<p>Un ordre d&#8217;idée pour le calcul des <em>workers</em>: un de plus que le nombre de CPUs de la machine.</p>
<h3>Conclusion</h3>
<p>On peut alors se créer un script (a placer dans /etc/init.d) et l&#8217;activer pour qu&#8217;il se lance automatiquement au démarrage avec la commande <em>update-rc.d</em> (sous Debian), ou utiliser <a title="runit, un remplacement pour sysvinit" href="http://smarden.org/runit/" target="_self">runit</a> (jamais testé, peut-être un futur billet?).</p>
<p>Encore mieux, remplacer Apache par Nginx! (jamais testé non plus, et sûrement un futur billet <img src='http://mathieu.agopian.info/blog/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> ).</p>
<p>On peut difficilement faire plus simple!</p>
]]></content:encoded>
			<wfw:commentRss>http://mathieu.agopian.info/blog/2010/02/gunicorn-un-server-wsgi-ultra-simple-a-utiliser-et-configurer/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Django, sqlite et mod_wsgi, attention au piège!</title>
		<link>http://mathieu.agopian.info/blog/2009/03/django-sqlite-et-mod_wsgi-attention-au-piege/</link>
		<comments>http://mathieu.agopian.info/blog/2009/03/django-sqlite-et-mod_wsgi-attention-au-piege/#comments</comments>
		<pubDate>Sat, 14 Mar 2009 14:34:18 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[django]]></category>
		<category><![CDATA[mod_wsgi]]></category>
		<category><![CDATA[sqlite]]></category>

		<guid isPermaLink="false">http://mathieu.agopian.info/blog/?p=111</guid>
		<description><![CDATA[Tout d&#8217;abord, je tiens à préciser que le problème qui suit n&#8217;est pas limité à l&#8217;utilisation de django ou de mod_wsgi. Le contexte Utilisation de SQLite pour un projet django déployé sur mod_wsgi: # settings.py DATABASE_ENGINE = 'sqlite3' DATABASE_NAME = '/opt/mysite/mysite.db' Et voici les permissions sur le système de fichier: -rw-rw-rw- 1 ohan ohan 29696 [...]]]></description>
			<content:encoded><![CDATA[<p>Tout d&#8217;abord, je tiens à préciser que le problème qui suit n&#8217;est pas limité à l&#8217;utilisation de django ou de mod_wsgi.</p>
<h3>Le contexte</h3>
<p>Utilisation de <em>SQLite</em> pour un projet django déployé sur mod_wsgi:</p>
<pre># settings.py
DATABASE_ENGINE = 'sqlite3'
DATABASE_NAME = '/opt/mysite/mysite.db'</pre>
<p>Et voici les permissions sur le système de fichier:</p>
<pre>-rw-rw-rw- 1 ohan ohan 29696 2009-03-14 13:30 mysite.db</pre>
<p>Tous les répertoires parents sont eux en <em>755</em> (lecture et exécution), ce qui ne devrait donc poser aucun problème, même pour l&#8217;utilisateur utilisé par les processus apache/mod_wsgi.</p>
<h3>Le problème</h3>
<p>Lors de la première tentative d&#8217;accès à la base de donnée (par exemple en accédant à l&#8217;administration django), une erreur <em>500 INTERNAL SERVER ERROR</em> est renvoyée, et dans le fichier de log d&#8217;apache:</p>
<pre>OperationalError: unable to open database file</pre>
<h3>La solution</h3>
<p>Lors de l&#8217;accès à un fichier de base de données, <em>SQLite</em> va créer un fichier journal qui lui servira (entre autres) à gérer les accès à cette base. Plus d&#8217;informations sur la page expliquant les méthodes de vérouillage: <a title="La gestion du lock pour les bases de données sqlite" href="http://www.sqlite.org/lockingv3.html" target="_self">locking in sqlite v3</a>.</p>
<p>Pour créer ce fichier, il faut donc que l&#8217;utilisateur puisse écrire dans le répertoire parent.</p>
<pre>chmod a+rw /opt/mysite</pre>
<p>Ce problème ne devrait se présenter que lors d&#8217;un déploiement en environnement de production pour un projet qui utilise <em>SQLite</em>, ou sur un environnement de test si, comme moi, vous préférez tester sur apache directement, et non sur le <em>runserver.</em></p>
]]></content:encoded>
			<wfw:commentRss>http://mathieu.agopian.info/blog/2009/03/django-sqlite-et-mod_wsgi-attention-au-piege/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Checklist: différences entre MySQL et les modèles Django</title>
		<link>http://mathieu.agopian.info/blog/2009/03/checklist-differences-entre-mysql-et-les-modeles-django/</link>
		<comments>http://mathieu.agopian.info/blog/2009/03/checklist-differences-entre-mysql-et-les-modeles-django/#comments</comments>
		<pubDate>Mon, 02 Mar 2009 09:54:36 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[django]]></category>

		<guid isPermaLink="false">http://mathieu.agopian.info/blog/?p=85</guid>
		<description><![CDATA[Comme vu dans un précédent billet (MySQL et les modèles Django), il existe des inconsistances entre une base de donnée MySQL et sa représentation par des modèles Django. Création des tables à partir des modèles Django Lorsqu&#8217;on utilise python manage.py syncdb, les tables créées dans la base de données N&#8217;auront aucun commentaire, que ce soit [...]]]></description>
			<content:encoded><![CDATA[<p>Comme vu dans un précédent billet (<a title="Problème: comment maintenir MySQL et les modèles Django?" href="/blog/2009/02/mysql-et-les-modeles-django/" target="_self">MySQL et les modèles Django</a>), il existe des inconsistances entre une base de donnée MySQL et sa représentation par des modèles Django.</p>
<h3>Création des tables à partir des modèles Django</h3>
<p>Lorsqu&#8217;on utilise <em>python manage.py syncdb</em>, les tables créées dans la base de données</p>
<ol>
<li>N&#8217;auront aucun commentaire, que ce soit sur les champs ou les tables, qu&#8217;il y ai ou non des <em>help_text</em> et des <em>docstrings</em></li>
<li>N&#8217;auront aucun champ <a title="Le type ENUM dans MySQL" href="http://dev.mysql.com/doc/refman/5.0/en/enum.html" target="_self"><em>ENUM</em></a>: les champs possédant une <a title="Liste de choix pour champ CharField dans Django" href="http://docs.djangoproject.com/en/dev/ref/models/fields/#choices" target="_self">liste de choix</a> sont représentés par un <a title="Le type VARCHAR en MySQL" href="http://dev.mysql.com/doc/refman/5.0/en/char.html" target="_self"><em>VARCHAR</em></a> de longueur égale à la taille du plus long des choix</li>
<li>Les valeurs par défaut indiquées dans les modèles ne sont pas transmises dans les scripts de création des tables</li>
<li>Les <a title="Le champ BooleanField dans Django" href="http://docs.djangoproject.com/en/dev/ref/models/fields/#booleanfield" target="_self"><em>BooleanField</em></a> sont représentés par des <a title="Le type TINYINT en MySQL" href="http://dev.mysql.com/doc/refman/4.1/en/numeric-types.html" target="_self">TINYINT</a> de 1bit</li>
<li>Un <em>DateTimeField</em> avec le paramètre <em>auto_add_now</em> ne sera pas représenté par un <em>TIMESTAMP</em> avec la valeur par défaut <em>CURRENT_TIMESTAMP</em></li>
</ol>
<h3>Inspection des tables dans Django</h3>
<p>Par ailleurs, si la base de donnée existe déjà, et que les modèles sont créés automatiquement par l&#8217;utilisation de la commande <em>python manage.py inspectdb</em>, il faudra garder à l&#8217;esprit que:</p>
<ol>
<li>Les commentaires sur les champs et tables ne sont pas traduits en <em>help_text</em> ou <em>docstrings</em>, il faudra donc les dupliquer manuellement</li>
<li>Un champ <em>ENUM</em> sera représenté par un <em>CharField</em></li>
<li>Les tailles de champ ne sont pas respectées. Par exemple, un <em>Charfield</em> sera avec un <em>max_length</em> de 135 par défaut</li>
<li>Les champs <em>TIMESTAMP</em> avec une valeur par défaut de <em>CURRENT_TIMESTAMP</em> ne seront pas importés en champ avec une valeur par défaut <em>auto_add_now</em></li>
<li>Les valeurs par défaut ne sont pas importées</li>
</ol>
<p>Il y a deux derniers points à noter:</p>
<ol>
<li>Le script d&#8217;inspection va importer les champs <em>primary_key</em> pour chaque table, alors qu&#8217;ils peuvent être automatiquement générés de manière transparente: un modèle sans <em>primary_key</em> explicite en aura un de manière implicite</li>
<li>Le script d&#8217;inspection va créer un modèle pour les tables utilisées pour les relations <em>ManyToMany</em> 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<a title="Stockage d'informations complémentaires dans une table ManyToMany" href="http://docs.djangoproject.com/en/dev/topics/db/models/#intermediary-manytomany" target="_self">&#8216;information supplémentaire</a>)</li>
</ol>
<p>Je trouve préférable de laisser la gestion implicite des <em>primary_key</em> et des tables <em>ManyToMany</em> quand c&#8217;est possible, dans un soucis de concision et de lisibilité.</p>
<p>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.</p>
<h3>Plus de cohérence entre les modèles et la base de donnée</h3>
<p>Plusieurs possibilités:</p>
<ul>
<li>créer un script python qui va introspecter les modèles, et automatiquement rajouter les <em>help_text</em> comme commentaires aux champs, et les <em>docstrings</em> comme commentaires aux tables, ou vice-versa</li>
<li>Utiliser des <a title="Custom SQL en Django" href="http://www.djangoproject.com/documentation/model-api/#database-backend-specific-sql-data" target="_self"><em>Custom SQL</em></a></li>
<li>Créer des <a title="Custom fields en Django" href="http://docs.djangoproject.com/en/dev/howto/custom-model-fields/#howto-custom-model-fields" target="_self"><em>Custom fields</em></a></li>
<li>Pour le type <em>ENUM</em>, il existe un <a title="Django snippet: ENUM field" href="http://www.djangosnippets.org/snippets/864/" target="_self"><em>django snippet</em></a> qui vise à apporter une meilleure cohérence dans son utilisation</li>
</ul>
<p>Partagez vos astuces et faites part d&#8217;autres incohérences dans les commentaires!</p>
]]></content:encoded>
			<wfw:commentRss>http://mathieu.agopian.info/blog/2009/03/checklist-differences-entre-mysql-et-les-modeles-django/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>MySQL et les modèles Django</title>
		<link>http://mathieu.agopian.info/blog/2009/02/mysql-et-les-modeles-django/</link>
		<comments>http://mathieu.agopian.info/blog/2009/02/mysql-et-les-modeles-django/#comments</comments>
		<pubDate>Tue, 24 Feb 2009 18:45:15 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[django]]></category>

		<guid isPermaLink="false">http://mathieu.agopian.info/blog/?p=11</guid>
		<description><![CDATA[Le problème D&#8217;un côté, une base de donnée MySQL maintenue par MySQLWorkbench. De l&#8217;autre, Django et ses modèles. Entre les deux&#8230; pas grand chose. Les acquis MySQL et MySQLWorkbench MySQLWorkbench est un outil excellent pour pouvoir maintenir une base de donnée: Gestion des commentaires sur les tables et champs Affichage des relations entre les tables [...]]]></description>
			<content:encoded><![CDATA[<h3>Le problème</h3>
<p>D&#8217;un côté, une base de donnée <a title="Site officiel de MySQL" href="http://www.mysql.com/" target="_self">MySQL</a> maintenue par <a title="MySQLWorkbench, outil de gestion de base de données MySQL" href="http://dev.mysql.com/downloads/workbench/5.1.html" target="_self">MySQLWorkbench</a>. De l&#8217;autre, <a title="Site officiel de Django, framework Web en Python" href="http://www.djangoproject.com" target="_self">Django</a> et ses modèles.</p>
<p>Entre les deux&#8230; pas grand chose.</p>
<h3>Les acquis</h3>
<h4>MySQL et MySQLWorkbench</h4>
<p>MySQLWorkbench est un outil excellent pour pouvoir maintenir une base de donnée:</p>
<ol>
<li>Gestion des commentaires sur les tables et champs</li>
<li>Affichage des relations entre les tables d&#8217;une base de données sous forme d&#8217;un schéma</li>
<li>Facilité de modification d&#8217;une base de donnée en production par l&#8217;export de scripts SQL</li>
</ol>
<h4>Django et les modèles</h4>
<p>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:</p>
<ol>
<li>Facilité de suivi des modifications d&#8217;un modèle par le biais d&#8217;un contrôle de version (Git, Mercurial, SVN &#8230;) accompagné de commentaires pour chaque <em>commit</em></li>
<li>Facilité de documentation: un paramètre <em>help_text</em> pour chaque champ, un <em>docstring</em> pour chaque modèle</li>
<li>Abstraction de certaines tables et champs générés automatiquement (comme les <em>primary key</em> ou les tables <em>ManyToMany</em>)</li>
</ol>
<h3>Les outils</h3>
<p>Comme indiqué plus haut, MySQLWorkbench permet d&#8217;exporter des scripts SQL de création et/ou de modification. Il est aussi possible de <em>reverse engineer</em> le dump SQL d&#8217;une base de donnée, et ainsi créer toutes les tables dans l&#8217;interface graphique, et y visualiser les relations sous forme de schéma.</p>
<p>Django permet l&#8217;export de scripts SQL de création, mais aussi l&#8217;inspection d&#8217;une base de données (par la commande <em>python manage.py inspectdb</em>), ou encore l&#8217;exécution de scripts SQL <em>custom</em> à chaque création/modification d&#8217;une table (par la commande <em>python manage.py syncdb</em>).</p>
<h3>Les méthodes</h3>
<h4>La méthode basique</h4>
<p>&#8230; qui casse complètement le principe <em>DRY </em>(Don&#8217;t Repeat Yourself): maintenir les deux parties en parallèle.</p>
<p>Lorsqu&#8217;il faut rajouter une table dans la base, ou y apporter une modification, faire la modification dans MySQLWorkbench, puis répliquer l&#8217;ajout/la modification dans les modèles Django.</p>
<p>Pour: Méthode simple, pas de procédure ou méthode à mettre en place.<br />
Contre: Méthode manuelle, sujette à l&#8217;erreur humaine (faute de typo, oubli&#8230;).</p>
<h4>MySQLWorkbench =&gt; Django</h4>
<ol>
<li>Maintenance dans l&#8217;outil MySQLWorkbench, puis export des scripts de création/modification.</li>
<li>Execution de ce script sur la base de données pour la mettre à jour</li>
<li>Utilisation de <em>inspectdb</em> avec Django pour mettre à jour les modèles</li>
</ol>
<p>Pour: Méthode automatique, donc pas de risque de typo ou d&#8217;oubli<br />
Contre: Méthode très complexe à mettre en place. En effet, <em>inspectdb</em> est loin de créer des modèles fidèles, et il manque de nombreuses informations (un futur billet sera écrit sur ce sujet).</p>
<h4>Django =&gt; MySQLWorkbench</h4>
<ol>
<li>Ajout ou modification d&#8217;un modèle dans Django</li>
<li>Mise à jour de la base de donnée en utilisant <em>python manage.py syncdb</em> (et des scripts <em>custom</em> si nécéssaire)</li>
<li>Utilisation de MySQLWorkbench pour <em>reverse engineer</em> les scripts de création de la base de donnée</li>
</ol>
<p>Pour: Méthode automatique, donc pas de risque de typo ou d&#8217;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.<br />
Contre: Toutes les informations de commentaires (et d&#8217;autres, comme nous le verrons dans un futur billet) sont perdues à chaque modification.</p>
<h3>Conclusion</h3>
<p>Je n&#8217;ai malheureusement pas trouvé à l&#8217;heure actuelle de méthode miracle. Il y a <a title="Une possible évolution de la maintenance d'une base de données avec Django" href="http://code.djangoproject.com/wiki/SchemaEvolution" target="_self">des pistes</a>, mais il semble qu&#8217;il n&#8217;y ai rien de concret pour le moment.</p>
<p>Pour certains cas particuliers, l&#8217;une ou l&#8217;autre méthode sera préférée:</p>
<ol>
<li>Création d&#8217;une nouvelle table: la méthode Django =&gt; MySQLWorkbench sera facile à mettre en place, et il n&#8217;y aura rien de perdu vu qu&#8217;il n&#8217;y a pas d&#8217;existant</li>
<li>Modification d&#8217;une table par le rajout d&#8217;un nouveau champ: l&#8217;une des deux méthodes automatique peuvent faire l&#8217;affaire</li>
<li>Modification d&#8217;un champ d&#8217;une table: la méthode manuelle sera en général la plus rapide et la plus simple à mettre en place</li>
<li>Modifications complexes, multiples, perte de donnée possible&#8230;: dans ce cas, point de salut, il ne reste que la méthode manuelle, et une grande concentration!</li>
</ol>
<p>Et vous, quelle est votre méthode?</p>
]]></content:encoded>
			<wfw:commentRss>http://mathieu.agopian.info/blog/2009/02/mysql-et-les-modeles-django/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Django FileField et ImageField, upload_to et shell python</title>
		<link>http://mathieu.agopian.info/blog/2009/02/django-filefield-et-imagefield-upload_to-et-shell-python/</link>
		<comments>http://mathieu.agopian.info/blog/2009/02/django-filefield-et-imagefield-upload_to-et-shell-python/#comments</comments>
		<pubDate>Thu, 19 Feb 2009 18:30:16 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[django]]></category>

		<guid isPermaLink="false">http://mathieu.agopian.info/blog/?p=52</guid>
		<description><![CDATA[Le paramètre upload_to des FileField et ImageField Le champ upload_to permet d&#8217;indiquer où sauver un fichier de type django.core.files.File par rapport au MEDIA_ROOT spécifié dans les settings. Ce champ peut être une chaîne de caractères, ou un callable. Chaîne de caractères pour upload_to Pour notre example, prenons le modèle suivant: class MonModele(models.Model):     fichier = [...]]]></description>
			<content:encoded><![CDATA[<h3>Le paramètre <em>upload_to</em> des <em>FileField</em> et <em>ImageField</em></h3>
<p>Le champ <a title="Documentation de FileField.upload_to sur le site de Python" href="http://docs.djangoproject.com/en/dev/ref/models/fields/#filefield" target="_self"><em>upload_to</em></a> permet d&#8217;indiquer où sauver un fichier de type <a title="Documentation de File sur le site de Python" href="http://docs.djangoproject.com/en/dev/topics/files/#the-file-object" target="_self"><em>django.core.files.File</em></a> par rapport au <a title="Documentation de MEDIA_ROOT sur le site de Python" href="http://docs.djangoproject.com/en/dev/ref/settings/#media-root" target="_self"><em>MEDIA_ROOT</em></a> spécifié dans les settings.</p>
<p>Ce champ peut être une chaîne de caractères, ou un <em>callable</em>.</p>
<h4>Chaîne de caractères pour upload_to</h4>
<p>Pour notre example, prenons le modèle suivant:</p>
<pre>class MonModele(models.Model):
    fichier = models.FileField(upload_to="chemin")</pre>
<p>Dans ce cas simple, tous les fichiers seronts sauvés dans le répertoire &lt;<em>MEDIA_ROOT&gt;/chemin/</em>.</p>
<p>Si on upload un fichier nommé <em>MonFichier.txt</em>, le chemin complet (sans le <em>MEDIA_ROOT</em>) sera <em>chemin/MonFichier.txt</em>.</p>
<p>Il est par ailleurs possible d&#8217;utiliser une syntaxe <em>strftime</em> comme indiqué dans la <a title="Documentation de strftime sur le site de Python" href="http://docs.python.org/library/time.html#time.strftime" target="_self">documentation du module time</a>.</p>
<p>Pour stocker un fichier avec la date et l&#8217;heure:</p>
<pre>class MonModele(models.Model):
    fichier = models.FileField(upload_to="chemin/%Y%m%d_%H%M%S")</pre>
<p>Et le résultat sera <em>chemin/2009-02-19_18:40:03/MonFichier.txt</em>, ce qui n&#8217;est pas vraiment ce à quoi on s&#8217;attendait.</p>
<p>En effet, cela créera un répertoire par fichier, au lieu de stocker la date et l&#8217;heure dans le nom du fichier.</p>
<p>Pour arriver à nos fins, il nous faut utiliser une fonction pour le calcul du chemin de stockage du fichier.</p>
<h4>Fonction pour <em>upload_to</em></h4>
<p>Utiliser une fonction pour le calcul du chemin de stockage permet:</p>
<ol>
<li>de modifier le nom du fichier lui-même, et pas seulement son répertoire de stockage</li>
<li>d&#8217;utiliser des informations spécifiques à l&#8217;instance du modèle pour lequel on stocke le fichier</li>
</ol>
<p>Il nous faut par contre respecter les contraintes suivantes:</p>
<ol>
<li>la fonction aura deux paramètres: <em>self</em>, l&#8217;instance, et <em>filename</em>, le nom du fichier uploadé</li>
<li>il n&#8217;est plus possible d&#8217;utiliser directement la syntaxe <em>strftime</em></li>
</ol>
<p>Voici le code qui va stocker un fichier avec la date et l&#8217;heure (et prendra la date et l&#8217;heure automatiquement ajoutée au champ <em>DateTimeField</em> avec le paramètre <em>auto_add_now</em>):</p>
<pre>class MonModele(models.Model):
    date = models.DateTimeField(auto_now_add=True)
    def upload_path(self, filename):
        return 'chemin/%s_%s' % (self.date.strftime("%Y%m%d_%H%M%S"), filename)
    fichier = models.FileField(upload_to=upload_path)</pre>
<p>Ce coup-ci, on obtient bien un fichier <em>chemin/2009-02-19_18:40:03_MonFichier.txt</em>, comme prévu<em>.<br />
</em></p>
<h3>Tester un modèle avec FileField dans l&#8217;interpréteur Python</h3>
<p>Pour pouvoir tester un modèle avec un FileField dans un intepréteur Python (<em>python manage.py shell</em>), il y a quelques précautions à prendre, comme commencer par &laquo;&nbsp;sauver&nbsp;&raquo; le <em>File</em>, comme indiqué sur la <a title="Additionnal methods on Files" href="http://docs.djangoproject.com/en/dev/ref/files/file/#additional-methods-on-files-attached-to-objects" target="_self">page suivante</a>.</p>
<p>En effet, pour que le modèle soit correctement instancié, il faut:</p>
<ol>
<li>créer une instance du modèle</li>
<li>la sauver (dans notre cas, afin que la date soit automatiquement stockée, et devienne accessible dans <em>upload_path</em>)</li>
<li>ouvrir le fichier à &laquo;&nbsp;uploader&nbsp;&raquo;</li>
<li>créer un <em>django.core.files.File</em> à partir de ce fichier</li>
<li>sauver ce fichier pour pouvoir ensuite le tester</li>
</ol>
<pre>&gt;&gt;&gt; from mysite.models import *
&gt;&gt;&gt; mm = MonModele()
&gt;&gt;&gt; mm.save()
&gt;&gt;&gt; mm.date
datetime.datetime(2009, 2, 19, 19, 4, 49, 538465)
&gt;&gt;&gt; from django.core.files import File
&gt;&gt;&gt; f = File(open("MonFichier.txt"))
&gt;&gt;&gt; mm.fichier.save(f.name, f, save=False)
&gt;&gt;&gt; mm.fichier
&lt;FieldFile: chemin/20090219_190449_settings.py&gt;</pre>
<p>Pour &laquo;&nbsp;sauver&nbsp;&raquo; le fichier, il faut fournir à la méthode <em>save</em> le nom du fichier (<em>f.name</em>), le fichier lui-même (<em>f</em>), et un paramètre spécifiant si on veut sauvegarder le fichier dans la base de donnée ou pas.</p>
<p>Si nous ne voulions pas accéder à <em>date</em> qui est stockée automatiquement, il aurait été inutile de sauvegarder l&#8217;instance de MonModele (<em>mm</em>), et il aurait alors fallut remplacer le code de <em>upload_path</em> de la sorte</p>
<pre>def upload_path(self, filename):
    return 'chemin/%s_%s' % (self.date.strftime("%Y%m%d_%H%M%S"), filename)</pre>
<p>par</p>
<pre>def upload_path(self, filename):
    import time
    return 'chemin/%s_%s' % (time.strftime("%Y%m%d_%H%M%S"), filename)</pre>
]]></content:encoded>
			<wfw:commentRss>http://mathieu.agopian.info/blog/2009/02/django-filefield-et-imagefield-upload_to-et-shell-python/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>
