<?xml version="1.0" encoding="UTF-8" ?>
<feed xmlns="http://purl.org/atom/ns#" version="0.3"
	  xmlns:dc="http://purl.org/dc/elements/1.1/"
	  xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	  xml:lang="fr">

  <title>Le blog d'Aozeo</title>
  <link rel="alternate" type="text/html" href="http://www.aozeo.com/blog/"/>
  <tagline type="text/plain" mode="escaped"></tagline>
  
  <modified>2007-12-31T23:59:59+01:00</modified>
  <generator version="1.2.3" url="http://www.dotclear.net/">DotClear</generator>
  
  <sy:updatePeriod>daily</sy:updatePeriod>
  <sy:updateFrequency>1</sy:updateFrequency>
  <sy:updateBase>2007-12-31T23:59:59+01:00</sy:updateBase>
  
<entry xml:lang="fr">
  <title>Excellente année 2008</title>
  <link rel="alternate" type="text/html" href="http://www.aozeo.com/blog/74-excellente-annee-2008" />
  <issued>2007-12-31T23:59:59+01:00</issued>
  <modified>2007-12-31T23:59:59+01:00</modified>
  <id>http://www.aozeo.com/blog/74-excellente-annee-2008</id>
  <author><name>Baptiste</name></author>
  <dc:subject>General</dc:subject>
  <summary>C'est un peu facile de souhaiter la bonne année sur un blog en hibernation, mais c'est un peu facile de ne pas la souhaiter...</summary>
  <content type="text/html" mode="escaped">&lt;p&gt;C'est un peu facile de souhaiter la bonne année sur un blog en hibernation, mais c'est un peu facile de ne pas la souhaiter...&lt;/p&gt; &lt;p&gt;Passez donc une année meilleure que la précédente si celle-ci fut mauvaise, similaire si 2007 vous a convenu, et un peu plus sobre si la dernière a fait exploser les compteurs, pour vous remettre un peu à votre place parce quand même.&lt;/p&gt;


&lt;p&gt;Excellente année 2008 donc, et à bientôt sur Aozeo pour la nouvelle version du site qui commence désormais a faire son âge. Aozeo v2(.0) sera graphique ou ne sera pas.&lt;/p&gt;</content>
</entry>
<entry xml:lang="fr">
  <title>Eiffel V, téléchargez vos vidéos</title>
  <link rel="alternate" type="text/html" href="http://www.aozeo.com/blog/73-eiffel-v" />
  <issued>2007-11-26T22:10:08+01:00</issued>
  <modified>2007-11-26T22:10:08+01:00</modified>
  <id>http://www.aozeo.com/blog/73-eiffel-v</id>
  <author><name>Baptiste</name></author>
  <dc:subject>General</dc:subject>
  <summary>Certes, ce blog ne connaît pas une activité débordante. Faisons abstraction : voici Eiffel V, un script graphique de téléchargement de vidéos qui tourne en local. Son boulot : récupérer tous les éléments vidéos d'une page et les proposer au téléchargement.</summary>
  <content type="text/html" mode="escaped">&lt;p&gt;Certes, ce blog ne connaît pas une activité débordante. Faisons abstraction&amp;nbsp;: voici Eiffel V, un script graphique de téléchargement de vidéos qui tourne en local. Son boulot&amp;nbsp;: récupérer tous les éléments vidéos d'une page et les proposer au téléchargement.&lt;/p&gt; &lt;p&gt;Quel avantage par rapport aux sites proposant ce service, type Keevid&amp;nbsp;?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Eiffel V s'exécute en local.&lt;/li&gt;
&lt;li&gt;Eiffel V est libre (licence &lt;a href=&quot;http://www.cecill.info/licences/Licence_CeCILL_V2-fr.html&quot; hreflang=&quot;fr&quot;&gt;CeCILL&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Eiffel V gère les vidéos incluses sur des sites tiers.&lt;/li&gt;
&lt;li&gt;Eiffel V est localisable.&lt;/li&gt;
&lt;li&gt;Eiffel V est conçu de manière à ce que vous puissiez ajouter les sites dont vous avez besoin.&lt;/li&gt;
&lt;li&gt;Eiffel V est en Python.&lt;/li&gt;
&lt;li&gt;Je suis gentil.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Quel inconvénient&amp;nbsp;?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Eiffel V ne fonctionne pas sur Windows.&lt;/li&gt;
&lt;li&gt;Eiffel V n'est pas inclus dans le navigateur.&lt;/li&gt;
&lt;li&gt;Eiffel V a quelques dépendances&amp;nbsp;: wxPython et BeautifulSoup.&lt;/li&gt;
&lt;li&gt;Que ça&amp;nbsp;?&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;Téléchargement et installation&lt;/h4&gt;

&lt;p&gt;Téléchargez l'&lt;a href=&quot;http://www.aozeo.net/documents/eiffelV-0.1.zip&quot; hreflang=&quot;fr&quot;&gt;archive&lt;/a&gt;, décompressez, lancez eiffel.py, et ça devrait y être...&lt;/p&gt;


&lt;h4&gt;Que fait-il vraiment&amp;nbsp;?&lt;/h4&gt;

&lt;p&gt;Il gère Youtube, Dailymotion, Myspace, Metacafe, Google Vidéos, même avec un lecteur inclus sur une autre page, et les lecteurs FLV basiques genre &lt;a href=&quot;http://flv-player.net/&quot; hreflang=&quot;fr&quot;&gt;celui de Neolao&lt;/a&gt;. Il repère les vidéos, vous demande lesquelles souhaitez-vous télécharger, puis s'en occupe. Si vous le lancez en ligne de commande avec une URL en argument, il démarre comme un grand. Si vous avez une idée de fonctionnalité supplémentaire, je suis preneur...&lt;/p&gt;


&lt;h4&gt;Ajouter un site de vidéos&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Créez une classe avec pour nom la première partie du domaine du site, avec une majuscule, en la dérivant de la classe &lt;code&gt;Site&lt;/code&gt;. Par exemple avec youtube.com, &lt;code&gt;class Youtube(Site)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Créez une méthode &lt;code&gt;_do&lt;/code&gt;, à laquelle sera passé l'élément &lt;code&gt;dom&lt;/code&gt;, un objet BeautifulSoup. Faîtes-en ce que vous voulez pour obtenir l'URL du fichier FLV et son titre. Vous avez éventuellement accès à &lt;code&gt;self.url&lt;/code&gt; qui est l'adresse de la page.&lt;/li&gt;
&lt;li&gt;S'il n'y a pas de vidéo (vidéo rejetée, supprimée, etc.) sur la page, créez un évènement vide&amp;nbsp;: &lt;code&gt;self.evt = ParsingIsDone(videos = [])&lt;/code&gt;. Sinon, créez celui-ci&amp;nbsp;: &lt;code&gt;self.evt = ParsingIsDone(videos = [Video(url_flv, titre)])&lt;/code&gt;. Puis ajoutez ces lignes à la fin de la méthode &lt;code&gt;_do&lt;/code&gt;&amp;nbsp;:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;if self.send_evt:
     wx.PostEvent(prog.frame, self.evt)&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Éventuellement, le site peut permettre à ses utilisateurs d'inclure son lecteur. Si vous désirez gérer ce cas, vous devez créer une méthode &lt;code&gt;_in&lt;/code&gt;, à laquelle sera passé l'objet BeautifulSoup représentant l'&lt;code&gt;&amp;lt;embed&amp;gt;&lt;/code&gt; ou l'&lt;code&gt;&amp;lt;object&amp;gt;&lt;/code&gt; du lecteur &lt;code&gt;flash_object&lt;/code&gt;, et la liste des vidéos déjà traitées &lt;code&gt;done_urls&lt;/code&gt;. En effet, certains sites utilisent et embed et object, il faut ainsi éviter les doublons.&lt;/li&gt;
&lt;li&gt;Tout d'abord, trouvez la véritable adresse de la vidéo, ou n'importe quel élément qui l'identifie à coup sûr et qui nécessite le moins de traitement. Vérifiez ensuite s'il se trouve dans &lt;code&gt;done_urls&lt;/code&gt;&amp;nbsp;:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;if url_trouvee in done_urls:
    self.evt = ParsingIsDone(videos = [])
    return&lt;/pre&gt;

&lt;p&gt;Puis ajoutez-le&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;done_urls.append(url_trouvee)&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Enfin, trouvez l'adresse du fichier FLV et le titre de la vidéo, puis créez l'évènement&amp;nbsp;: &lt;code&gt;self.evt = ParsingIsDone(videos = [Video(url_flv, title)])&lt;/code&gt;. Attention, pas besoin de l'envoyer avec wx.PostEvent cette fois-ci.&lt;/li&gt;
&lt;li&gt;Vous devez ensuite spécifier un élément qui sera caractéristique de votre site d'inclusion de vidéos, typiquement l'URL du lecteur Flash, que Eiffel V ira chercher dans le contenu des &amp;lt;embed&amp;gt; et des &amp;lt;object&amp;gt; de la page, comme http://lads.myspace.com/videos/vplayer.swf pour Myspace vids. Faîtes le en donnant à la classe un attribut &lt;code&gt;embed_url&lt;/code&gt;, par exemple&amp;nbsp;: &lt;code&gt;Vids_myspace.embed_url = 'http://lads.myspace.com/videos/vplayer.swf'&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Ajoutez votre nouvelle classe à la variable SITES.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Regardez les classes déjà présentes si vous avez besoin d'exemples, et envoyez-moi les vôtres (baptiste-at-aozeo_dot_com), que j'ajouterai à la prochaine version de Eiffel V :-)
Merci pour les retours...&lt;/p&gt;</content>
</entry>
<entry xml:lang="fr">
  <title>Passez à Jinja !</title>
  <link rel="alternate" type="text/html" href="http://www.aozeo.com/blog/72-jinja-moteur-de-template-pour-django" />
  <issued>2007-07-13T12:06:24+02:00</issued>
  <modified>2007-07-13T12:06:24+02:00</modified>
  <id>http://www.aozeo.com/blog/72-jinja-moteur-de-template-pour-django</id>
  <author><name>Baptiste</name></author>
  <dc:subject>Django</dc:subject>
  <summary>Je n'ai pas mis à jour ce blog depuis pas mal de temps pour cause d'activité débordante et je ne reprends le flambeau que pour quelques jours étant en vacances à partir de la semaine prochaine, mais c'est toujours un article de pris.


Je continue d'utiliser Django pour des projets professionnels et privés, et c'est vraiment un plaisir de jouer avec. Cependant, les limitations du langage de gabarits ont eu un jour raison de mon enthousiasme, et j'y ai cherché une alternative. Ne souhaitant pas recommencer l'apprentissage d'une nouvelle syntaxe, mon choix s'est rapidement porté sur Jinja, qui reprend en majorité celle de Django. Dans l'absolu, vous pouvez utiliser n'importe quel moteur de gabarits. Dans la pratique, il est préférable d'en choisir un qui soit adapté à Django !</summary>
  <content type="text/html" mode="escaped">&lt;p&gt;Je n'ai pas mis à jour ce blog depuis pas mal de temps pour cause d'activité débordante et je ne reprends le flambeau que pour quelques jours étant en vacances à partir de la semaine prochaine, mais c'est toujours un article de pris.&lt;/p&gt;


&lt;p&gt;Je continue d'utiliser Django pour des projets professionnels et privés, et c'est vraiment un plaisir de jouer avec. Cependant, les limitations du langage de gabarits ont eu un jour raison de mon enthousiasme, et j'y ai cherché une alternative. Ne souhaitant pas recommencer l'apprentissage d'une nouvelle syntaxe, mon choix s'est rapidement porté sur &lt;a href=&quot;http://jinja.pocoo.org/&quot; hreflang=&quot;en&quot;&gt;Jinja&lt;/a&gt;, qui reprend en majorité celle de Django. Dans l'absolu, vous pouvez utiliser n'importe quel moteur de gabarits. Dans la pratique, il est préférable d'en choisir un qui soit adapté à Django&amp;nbsp;!&lt;/p&gt; &lt;p&gt;Je vais présenter mon coup de cœur (qui n'était pas un coup de tête, je suis toujours fidèle à à mon choix deux mois plus tard) de manière certes non-exhaustive mais, je l'espère, complète, en se penchant sur les divers aspects de son fonctionnement.&lt;/p&gt;


&lt;h5&gt;Syntaxe&lt;/h5&gt;

&lt;p&gt;Commençons par le point le plus simple. La syntaxe de Jinja comporte &lt;a href=&quot;http://jinja.pocoo.org/documentation/fromdjango&quot; hreflang=&quot;en&quot;&gt;quelques différences&lt;/a&gt; avec celle de Django&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;L'appel aux méthodes n'est plus implicite, ainsi vous devrez écrire &lt;code&gt;{{ instance.get_absolute_url() }}&lt;/code&gt; au lieu de &lt;code&gt;{{ instance.get_absolute_url }}&lt;/code&gt;. C'est un peu contraignant lors de la migration car vous devrez vérifier chaque variable, mais le gain vaut à mon avis le coup&amp;nbsp;: une meilleure clarté, la gestion du cas de figure (bien qu'à éviter) méthode/attribut homonymes, et la possibilité de fournir des arguments.&lt;/li&gt;
&lt;li&gt;Les arguments des filtres sont à utiliser comme ceux des fonctions, ce qui permet d'en placer plusieurs&amp;nbsp;; on passe ainsi de &lt;code&gt;{{ liste|join:&quot;, &quot; }}&lt;/code&gt; à &lt;code&gt;{{ liste|join(&quot;, &quot;) }}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Point de tag {% include &quot;gabarit.html&quot; %} mais &lt;q&gt; rendertemplate(&quot;gabarit.html&quot;) &lt;/q&gt;.&lt;/li&gt;
&lt;li&gt;Plus de dérivées de &lt;code&gt;if&lt;/code&gt; comme &lt;code&gt;ifequal&lt;/code&gt;, au profit du vrai test, celui disponible en Python. C'est un avantage majeur qui permet d'éviter l'inévitable &lt;code&gt;{% if %}{% else %}&lt;/code&gt; du langage de Django, qui autorise de vraies comparaisons comme &lt;code&gt;&amp;gt;=&lt;/code&gt;, et qui souffre la combinaison des conditions avec les mots-clés &lt;code&gt;and&lt;/code&gt; et &lt;code&gt;or&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Plus non plus de &lt;code&gt;{% if %}&lt;/code&gt; les uns dans les autres pour simuler un banal &lt;code&gt;{% elif %}&lt;/code&gt;... Car ce tag existe dans Jinja&amp;nbsp;! Certains le jugent dangereux car il complexifie des gabarits, je trouve au contraire qu'il les simplifie,&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;{% if var %}
{% elif var2 %}
{% elif var3 %}
{% endif %}&lt;/pre&gt;

&lt;p&gt;étant autrement plus élégant que&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;{% if var %}
{% else %}
    {% if var2 %}
    {% else %}
         {% if var3 %}
          {% endif %}
    {% endif %}
{% endif %}&lt;/pre&gt;

&lt;h5&gt;Fonctionnement global&lt;/h5&gt;

&lt;p&gt;La classe &lt;a href=&quot;http://jinja.pocoo.org/documentation/api#environment&quot;&gt;Environment&lt;/a&gt; est la base de tout le système&amp;nbsp;: c'est le contexte dans lequel seront traduits vos gabarits&amp;nbsp;;  il se charge de les trouver à partir de leur chemin puis de les interpréter. Les différents filtres doivent y être recensés pour exister dans l'espace de nom du gabarit, et il contient votre configuration. Dans cet article, la référence à l'objet principal Environment se fera à travers la variable &lt;code&gt;env&lt;/code&gt;. Le schéma classique de fonctionnement est le suivant&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;TEMPLATE_PATH = settings.TEMPLATE_DIRS[0]
env = Environment(loader=FileSystemLoader(TEMPLATE_PATH))
gabarit = env.get_template('/chemin/vers/gabarit.html')
gabarit.render(contexte)&lt;/pre&gt;

&lt;p&gt;Par défaut, pour instancier l'environnement, vous devez simplement indiquer le chemin d'un dossier qui contiendra tous vos gabarits. Il est possible de personnaliser ce comportement en créant votre propre &lt;em&gt;Loader&lt;/em&gt;, mais pour ça, &lt;a href=&quot;http://jinja.pocoo.org/documentation/loaders&quot; hreflang=&quot;en&quot;&gt;référez-vous à la documentation&lt;/a&gt;, ce billet va déjà être bien assez dense&amp;nbsp;!&lt;/p&gt;

&lt;h5&gt;Filtres&lt;/h5&gt;

&lt;p&gt;Comme avec le système de Django, vous pouvez &lt;a href=&quot;http://jinja.pocoo.org/documentation/filters&quot;&gt;écrire vos propres filtres&lt;/a&gt; très simplement. Pour reprendre l'&lt;a href=&quot;http://www.djangoproject.com/documentation/templates_python/#writing-custom-template-filters&quot; hreflang=&quot;en&quot;&gt;exemple de la documentation&lt;/a&gt;, voici le filtre &lt;code&gt;cut&lt;/code&gt; version Jinja&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;def do_cut(arg=u''):
    def wrapped(env, context, valeur):
        return valeur.replace(arg, '')
    return wrapped&lt;/pre&gt;

&lt;p&gt;En fait, cet exemple est la version complète du filtre, avec un accès à l'environnement &lt;code&gt;env&lt;/code&gt; et au contexte à la variable éponyme. En procédant de cette manière, il est nécessaire d'enregistrer le filtre dans l'espace de nom de l'environnement avec &lt;code&gt;env.filters['cut'] = do_cut&lt;/code&gt;. Mais il est en réalité possible de faire plus simple&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;from jinja.filters import stringfilter&lt;/pre&gt;


&lt;pre&gt;def do_cut(valeur, arg):
    return valeur.replace(arg, '')
env.filters['cut'] = stringfilter(do_cut)&lt;/pre&gt;

&lt;p&gt;Oui, c'est la même chose que dans Django&amp;nbsp;! À une différence près&amp;nbsp;: la fonction peut prendre autant d'arguments que vous le désirez. Par exemple, pour faire un filtre similaire à &lt;code&gt;cut&lt;/code&gt; mais limitant la taille de la chaîne&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;def do_cut(valeur, arg, taille):
    return valeur.replace(arg, '')[:taille]
env.filters['cut'] = stringfilter(do_cut)&lt;/pre&gt;

&lt;p&gt;que l'on utilisera simplement en faisant &lt;code&gt;{{ somevariable|cut(0, 15) }}&lt;/code&gt;. C'est aussi simple que dans Django mais bien plus puissant, le nombre d'arguments n'étant pas limité et une possibilité d'accès au contexte étant réservée&amp;nbsp;!&lt;/p&gt;

&lt;h5&gt;Tags&lt;/h5&gt;

&lt;p&gt;Jinja ne permet pas de créer ses propres tags mais plutôt d'appeler des méthodes ayant éventuellement accès au contexte. Ce système fonctionne de deux façons. La première, basique, est de créer une fonction prenant ou non des arguments et retournant une chaîne Unicode, de l'enregistrer dans l'espace de nom avec &lt;code&gt;env.globals['ma_fonction'] = ma_fonction&lt;/code&gt;, puis de l'appeler comme vous l'auriez fait en Python avec &lt;code&gt;{{ ma_fonction(argument) }}&lt;/code&gt;.&lt;br /&gt;
Si vous souhaitez accéder au contexte (sous la forme  d'un objet &lt;a href=&quot;http://jinja.pocoo.org/documentation/api#context&quot;&gt;Context&lt;/a&gt;), la procédure est (très) légèrement plus compliquée car il faut faire appel à un décorateur, &lt;code&gt;from jinja.datastructure import contextcallable&lt;/code&gt;. Les deux premiers paramètres de la fonction seront alors &lt;code&gt;env&lt;/code&gt; et &lt;code&gt;contexte&lt;/code&gt;. Ce qui donne par exemple&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;@contextcallable
def ma_fonction(env, contexte, argument):
    ...
env.globals['ma_fonction'] = ma_fonction&lt;/pre&gt;

&lt;p&gt;Vous utiliserez votre fonction de la même façon, mais aurez à votre disposition ces deux nouveaux éléments. Pour que vous visualisiez mieux le principe, voici quelques filtres concrets reprenant &lt;a href=&quot;http://www.djangoproject.com/documentation/templates_python/#writing-custom-template-tags&quot; hreflang=&quot;en&quot;&gt;ceux de la documentation&lt;/a&gt; !&lt;br /&gt;
La puissance de Jinja n'attend pas, l'écriture du premier, &lt;code&gt;current_time&lt;/code&gt;, suffit à comprendre l'imparable simplicité de ce système&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;import datetime
def do_current_time(format):
    return datetime.datetime.now().strftime(format)
env.globals['current_time'] = do_current_time&lt;/pre&gt;

&lt;p&gt;La façon dont vous devrez l'utiliser ne doit plus être un mystère&amp;nbsp;: &lt;code&gt;{{ current_time(&quot;%Y-%m-%d %I:%M %p&quot;) }}&lt;/code&gt;.
3 lignes au lieu de 14 et une seule fonction. Plus besoin de vérifier l'existence des arguments et leur validité, tout se passe intuitivement comme vous l'auriez fait en Python&amp;nbsp;!&lt;/p&gt;


&lt;p&gt;&lt;code&gt;&lt;a href=&quot;http://www.djangoproject.com/documentation/templates_python/#passing-template-variables-to-the-tag&quot; hreflang=&quot;en&quot;&gt;format_time&lt;/a&gt;&lt;/code&gt; est aussi simple à écrire&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;import datetime
def do_format_time(date, format):
    return date.strftime(format)
env.globals['format_time'] = do_format_time&lt;/pre&gt;

&lt;p&gt;Jinja passe directement les variables du contexte utilisées comme arguments à la fonction, plus besoin de les y aller chercher. Ce filtre s'utilise bien sûr en écrivant &lt;code&gt;{{ format_time(blog_entry.date_updated, &quot;%Y-%m-%d %I:%M %p&quot;) }}&lt;/code&gt; et fait toujours 3 lignes au lieu de 18.&lt;br /&gt;
Simple, n'est-il pas&amp;nbsp;?&lt;/p&gt;


&lt;p&gt;Pour comprendre la seconde façon d'utiliser le système il est d'abord nécessaire de visualiser comment vous appellerez votre fonction&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;{% call ma_fonction() %}
Affichage du titre : « {{ titre }} ».
{% endcall %}&lt;/pre&gt;

&lt;p&gt;Jinja passera alors un argument &lt;code&gt;caller&lt;/code&gt; à la &lt;code&gt;ma_fonction&lt;/code&gt;, une &lt;strong&gt;méthode&lt;/strong&gt; qui, appelée, renvoie le contenu du tag &lt;code&gt;{% call %}&lt;/code&gt;, en l'occurrence ici &lt;q&gt;Affichage du titre&amp;nbsp;: «&amp;nbsp;Variable titre »&lt;/q&gt;. Si &lt;code&gt;caller&lt;/code&gt; n'est pas une simple chaîne, ce qui peut paraître étrange au premier abord, c'est en fait pour permettre la surcharge du contexte, car le contenu de &lt;code&gt;{% call %}&lt;/code&gt; ne sera évalué que lorsque la fonction sera appelée. C'est plutôt obscur, alors voici une application concrète, si l'on reprend l'exemple précédent en ajoutant un argument facultatif à &lt;code&gt;ma_fonction&lt;/code&gt;.&lt;/p&gt;

&lt;pre&gt;def ma_fonction(argument=None, caller=None):
    rendu = u''
    if caller and argument:
        rendu = caller(titre=argument)
    elif caller:
        rendu = caller()
    return rendu&lt;/pre&gt;

&lt;p&gt;Si &lt;code&gt;argument&lt;/code&gt; est fourni à l'appel de la fonction, il sera utilisé en lieu et place de la variable &lt;code&gt;titre&lt;/code&gt; du contexte, sinon, c'est le titre par défaut qui sera affiché. L'exemple avec &lt;code&gt;{% call %}&lt;/code&gt; un peu plus haut fonctionne donc toujours de la même façon, mais&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;{% call ma_fonction('Jinja est un meilleur système de gabarit que celui de Django') %}
Affichage du titre : « {{ titre }} ».
{% endcall %}&lt;/pre&gt;

&lt;p&gt;rendra &quot;Affichage du titre&amp;nbsp;: «&amp;nbsp;Jinja est un meilleur système de gabarit que celui de Django »&quot;.&lt;br /&gt;
Si vous souhaitez accéder au contexte avec une fonction de ce type, les choses se passent de la même façon qu'au départ, en utilisant le décorateur &lt;code&gt;@contextcallable&lt;/code&gt;, qui transformera les deux premiers arguments en &lt;code&gt;env&lt;/code&gt; et &lt;code&gt;contexte&lt;/code&gt;. La définition de notre méthode deviendrait simplement&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;@contextcallable
def ma_fonction(env, contexte, argument=None, caller=None):
    ...&lt;/pre&gt;

&lt;p&gt;Toujours pour donner des applications concrètes, reprenons l'exemple de &lt;code&gt;&lt;a href=&quot;http://www.djangoproject.com/documentation/templates_python/#setting-a-variable-in-the-context&quot; hreflang=&quot;en&quot;&gt;current_time&lt;/a&gt;&lt;/code&gt;, qui insère une variable dans le contexte. La première façon d'écrire les filtres pourrait suffire&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;@contextcallable
def do_current_time(env, context, format):
    context['current_time'] = datetime.datetime.now().strftime(format)&lt;/pre&gt;

&lt;p&gt;Mais ce ne serait sûrement pas nécessaire, Jinja intégrant un tag &lt;code&gt;set&lt;/code&gt;. Ainsi, avec le même &lt;code&gt;current_time&lt;/code&gt; que tout à l'heure, &lt;code&gt;{% set current_time = current_time(&quot;%Y-%M-%d %I:%M %p&quot;) %}&lt;/code&gt; ferait exactement la même chose que notre nouvelle méthode&amp;nbsp;!&lt;/p&gt;


&lt;p&gt;&lt;code&gt;get_current_time&lt;/code&gt; (cf. la documentation) pourrait se faire sans toucher au code, avec &lt;code&gt;{% set my_current_time = current_time(&quot;%Y-%M-%d %I:%M %p&quot;) %}&lt;/code&gt;, mais ce serait profiter des faiblesses du langage de Django. Pour faire vraiment la même chose que dans l'exemple proposé, 3 lignes contre 19 suffisent&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;@contextcallable
def do_set_current_time(env, context, format, nom_variable):
    context[nom_variable] = datetime.datetime.now().strftime(format)
env.globals['set_current_time'] = do_set_current_time&lt;/pre&gt;

&lt;p&gt;Que l'on utilise avec &lt;code&gt;{{ set_current_time(&quot;%Y-%M-%d %I:%M %p&quot;, &quot;my_current_time&quot;) }}&lt;/code&gt;, et tout ça bien sûr sans la moindre expression régulière, contrairement au système proposé par la documentation. En plus d'être incroyablement plus simple, Jinja permet de faire des fonctions plus efficaces&amp;nbsp;!&lt;/p&gt;


&lt;p&gt;La réécriture de &lt;code&gt;&lt;a href=&quot;http://www.djangoproject.com/documentation/templates_python/#parsing-until-another-block-tag&quot; hreflang=&quot;en&quot;&gt;upper&lt;/a&gt;&lt;/code&gt; utilise la seconde façon d'écrire ces fonctions et se révèle être un jeu d'enfant&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;def do_upper(caller=lambda: u''):
    return caller().upper()
env.globals['upper'] = do_upper&lt;/pre&gt;

&lt;p&gt;L'astuce de &lt;code&gt;caller=lambda: u''&lt;/code&gt; permet de ne pas avoir à tester l'existence de &lt;code&gt;caller&lt;/code&gt; qui renverra dans tous les cas une chaîne Unicode. Cette fonction fait 2 lignes, contre 10 pour celle de Django&amp;nbsp;! Mais la simplicité a un (petit) prix&amp;nbsp;: l'appel à la méthode est plus laid que dans Django&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;{% call upper() %}Ceci apparaîtra en majuscules, &lt;q&gt; votre_nom &lt;/q&gt;.{% endcall %}&lt;/pre&gt;

&lt;p&gt;Ce qui au final ne représente qu'un inconvénient plutôt négligeable.&lt;/p&gt;

&lt;h5&gt;Macros&lt;/h5&gt;

&lt;p&gt;Pour finir cet aperçu de l'utilisation de Jinja, je vais parler rapidement des macros, qui sont une fonctionnalité souvent demandée et assurément manquante à Django. Elles permettent de répéter un morceau de code sans le réécrire, un peu comme une variable. Comme les fonctions, elles peuvent aussi prendre des arguments. Par exemple&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;{% macro afficher_dictionnaire(dico) %}
   {% if dico %}
       &amp;lt;ul&amp;gt;
       {% for cle, valeur in dico|dictsort %}
            &amp;lt;li&amp;gt;{{ cle }} : {{ valeur }}&amp;lt;/li&amp;gt;
       {% endfor %}
       &amp;lt;/ul&amp;gt;
    {% endif %}
{% endmacro %}&lt;/pre&gt;

&lt;p&gt;Que vous utiliserez avec &lt;code&gt;{{ afficher_dictionnaire(form.errors) }}&lt;/code&gt;. C'est un atout majeur, car la consigne fétiche de Django, &lt;acronym title=&quot;Don&amp;#039;t Repeat Yourself (Ne vous répétez pas)&quot;&gt;DRY&lt;/acronym&gt;, est bien mise à mal lorsqu'on est obligé de recopier des bouts de code plusieurs fois pour simuler ce comportement&amp;nbsp;!&lt;/p&gt;

&lt;h5&gt;Utiliser Jinja avec Django&lt;/h5&gt;

&lt;p&gt;L'intégration à notre &lt;em&gt;framework&lt;/em&gt; préféré se fait sans douleur grâce à l'existence d'un &lt;code&gt;&lt;a href=&quot;http://jinja.pocoo.org/documentation/devrecipies#using-jinja-in-django&quot; hreflang=&quot;en&quot;&gt;render_to_response&lt;/a&gt;&lt;/code&gt;&lt;a href=&quot;http://jinja.pocoo.org/documentation/devrecipies#using-jinja-in-django&quot; hreflang=&quot;en&quot;&gt; pour Jinja&lt;/a&gt;. Il fonctionne exactement de la même façon que l'existant, aucun souci de migration à prévoir. Je vous conseille personnellement de créer un fichier utils/jinja.py à la racine de votre projet, et d'y placer votre render_to_response. Dès que vous aurez besoin d'un nouveau filtre, il vous suffira de l'éditer pour avoir accès à l'objet &lt;code&gt;env&lt;/code&gt; très simplement&amp;nbsp;! Enfin, gardez l'&lt;a href=&quot;http://jinja.pocoo.org/documentation/devrecipies#using-django-filters-with-jinja&quot; hreflang=&quot;en&quot;&gt;astuce permettant de transformer un filtre Django en filtre Jinja&lt;/a&gt; sous la main. Plus tard peut-être, un nouveau billet sur Jinja pour aborder tout ce qui ne l'a pas été aujourd'hui...&lt;/p&gt;

&lt;h5&gt;Conclusion&lt;/h5&gt;

&lt;p&gt;Cette présentation ne vaut assurément pas la &lt;a href=&quot;http://jinja.pocoo.org/documentation/&quot; hreflang=&quot;en&quot;&gt;documentation&lt;/a&gt;, mais permet de donner une idée de la puissance de Jinja qui surpasse en bien des points le langage de gabarits de Django. C'est un module qui m'est désormais indispensable, car je n'ai plus à bidouiller continuellement pour obtenir ce que je veux, ce qui était devenu obligatoire même en faisant attention à ne pas déporter ce qui pourrait être fait dans la vue dans le template. Si vous avez décidé de sauter la marche, le plus simple pour obtenir de l'aide (en anglais) reste IRC sur #pocoo@irc.freenode.net. Les développeurs principaux sont souvent présents et très réactifs en cas de souci&amp;nbsp;! Mais vous pouvez aussi poser votre question ici-même, peut-être pourrais-je y répondre...&lt;/p&gt;</content>
</entry>
<entry xml:lang="fr">
  <title>Mémento SPIP 2</title>
  <link rel="alternate" type="text/html" href="http://www.aozeo.com/blog/69-memento-spip-2" />
  <issued>2007-04-14T00:01:00+02:00</issued>
  <modified>2007-04-14T00:01:00+02:00</modified>
  <id>http://www.aozeo.com/blog/69-memento-spip-2</id>
  <author><name>Baptiste</name></author>
  <dc:subject>General</dc:subject>
  <summary>La première version de l'aide-mémoire SPIP était assez imparfaite pour mériter une petite révision. Au menu, des corrections de fautes (d'orthographes) et de vraies erreurs, des explications un peu moins incompréhensibles, une légère mise à jour du contenu et une présentation plus efficace avec un code joliment présenté (moins laidement, en tout cas).</summary>
  <content type="text/html" mode="escaped">&lt;p&gt;La première version de l'aide-mémoire SPIP était assez imparfaite pour mériter une petite révision. Au menu, des corrections de fautes (d'orthographes) et de vraies erreurs, des explications un peu moins incompréhensibles, une légère mise à jour du contenu et une présentation plus efficace avec un code joliment présenté (moins laidement, en tout cas).&lt;/p&gt; &lt;p&gt;La version PDF tient toujours sur deux pages, un A4 recto-verso :&lt;br /&gt;
&lt;a href=&quot;/documents/mementospip2.01.pdf&quot;&gt;Télécharger le mémento 2.01&lt;/a&gt;&lt;/p&gt;


&lt;p&gt;On a soulevé le problème, alors voici une version sans couleur, pour les daltoniens et ceux qui économisent leurs cartouches :&lt;br /&gt;
&lt;a href=&quot;/documents/mementospip2.01incolore.pdf&quot;&gt;Télécharger le mémento 2.01 sans couleur&lt;/a&gt;&lt;/p&gt;


&lt;p&gt;Son contenu ne sera par contre pas visible en ligne, contrairement à la version précédente. Ça peut être un inconvénient, mais c'est impossible de maintenir deux versions parallèles d'un même document&amp;nbsp;! Il faut en tout cas noter que quelques lignes de cet aide-mémoire concernent la 1.9.2, il serait donc bon de considérer ce mémento 2 comme destiné à cette version de SPIP. Pour les réfractaires, il existe toujours &lt;a href=&quot;http://www.aozeo.com/blog/50-aide-memoire-spip&quot;&gt;la première version&lt;/a&gt;.&lt;br /&gt;
Première version qui avait finalement été traduite &lt;a href=&quot;http://zone.spip.org/trac/spip-zone/browser/_doc_/memo/memento_it.pdf&quot; hreflang=&quot;it&quot; title=&quot;Le mémento 1 en italien !&quot;&gt;en italien&lt;/a&gt; par Renato après une tardive mise en ligne des sources. Cette fois-ci, elle n'attendra pas, le fichier .sla (Scribus) est d'ores et déjà &lt;a href=&quot;http://zone.spip.org/trac/spip-zone/browser/_doc_/memo/2/memento_fr.sla&quot;&gt;disponible sur la zone&lt;/a&gt;. Vous pouvez le traduire et proposer des modifications, pour peu d'installer &lt;a href=&quot;http://www.scribus.net/&quot;&gt;Scribus&lt;/a&gt; et les typos nécessaires (voir plus bas).&lt;/p&gt;


&lt;p&gt;Merci de faire remonter erreurs et coquilles pour obtenir une version, allez, parfaite&amp;nbsp;! Et car la demande a été récurrente, un jour (un jour, pas bientôt) arrivera le mémento débutant qui n'aura plus grand chose à voir avec un aide-mémoire mais qui présentera le fonctionnement de SPIP pour ceux qui veulent commencer à y toucher mais qui ne comprennent rien à la documentation. Et un jour plus tard, le mémento bidouilleur qui listera avec une subjectivité revendiquée les chouettes plugins à ne pas manquer pour SPIPer avec joie et entrain.&lt;/p&gt;


&lt;p&gt;Pour finir, un dernier détail car certains avaient été gênés par ce point, le mémento 2 n'utilise que des polices libres&amp;nbsp;: &lt;a href=&quot;http://www.gringod.com/2006/02/24/return-of-monacottf/&quot; hreflang=&quot;en&quot;&gt;Monaco&lt;/a&gt;, &lt;a href=&quot;http://dejavu.sourceforge.net/wiki/index.php/Main_Page&quot;&gt;DejaVu&lt;/a&gt; et &lt;a href=&quot;http://www.dafont.com/steinem.font&quot;&gt;Steinem&lt;/a&gt;.&lt;/p&gt;


&lt;h4&gt;Historique des modifications&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;2.01&amp;nbsp;: Correction d'une erreur sur l'emplacement des squelettes de formulaires en 1.9.2 &amp;amp; explications un peu moins concises sur la #PAGINATION. Merci Alexandra.&lt;/li&gt;
&lt;li&gt;2.0&amp;nbsp;: Version initiale&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry xml:lang="fr">
  <title>Django 0.96 est sorti</title>
  <link rel="alternate" type="text/html" href="http://www.aozeo.com/blog/68-django-096-est-sorti" />
  <issued>2007-03-27T16:54:51+02:00</issued>
  <modified>2007-03-27T16:54:51+02:00</modified>
  <id>http://www.aozeo.com/blog/68-django-096-est-sorti</id>
  <author><name>Baptiste</name></author>
  <dc:subject>Django</dc:subject>
  <summary>La nouvelle version de Django, à priori dernière étape avant la prochaine 1.0, vient d'être publiée. Voici un récapitulatif des changements, d'après les notes de version.</summary>
  <content type="text/html" mode="escaped">&lt;p&gt;La nouvelle version de Django, à priori dernière étape avant la prochaine 1.0, &lt;a href=&quot;http://www.djangoproject.com/weblog/2007/mar/23/096/&quot; hreflang=&quot;en&quot;&gt;vient d'être publiée&lt;/a&gt;. Voici un récapitulatif des changements, d'après les &lt;a href=&quot;http://www.djangoproject.com/documentation/release_notes_0.96/&quot; hreflang=&quot;en&quot;&gt;notes de version&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;Les deux nouveautés principales sont les nouveaux formulaires, &lt;em&gt;newforms&lt;/em&gt;, dont j'ai parlé récemment, et l'arrivée d'un outil permettant de tester &quot;industriellement&quot; les applications et de remplir la base de données automatiquement (&lt;em&gt;fixtures&lt;/em&gt;).&lt;/p&gt;


&lt;p&gt;Les newforms se situent pour le moment dans &lt;code&gt;django.newforms&lt;/code&gt;, et les anciens dans &lt;code&gt;django.oldforms&lt;/code&gt;. À la prochaine version, les nouveaux formulaires seront placés dans &lt;code&gt;django.forms&lt;/code&gt;, et les anciens supprimés. Il est donc de temps de profiter de la 0.96 pour les mettre à jour en douceur.&lt;br /&gt;
Les tests sont plus complexes à synthétiser, je vous conseiller de lire &lt;a href=&quot;http://www.djangoproject.com/documentation/testing/&quot; hreflang=&quot;en&quot;&gt;la documentation (en anglais)&lt;/a&gt;. On retiendra la possibilité de charger automatiquement des données pour les besoins du test, ce qui est assez pratique pour gagner du temps. &lt;code&gt;manage.py&lt;/code&gt; prend désormais deux nouvelles options&amp;nbsp;: &lt;code&gt;dumpdata&lt;/code&gt; et &lt;code&gt;loaddata&lt;/code&gt;, qui permettent respectivement de copier les données de la base et d'en insérer.&lt;/p&gt;


&lt;p&gt;Une petite modification qui change pas mal de choses&amp;nbsp;: les fichiers URLconfs peuvent désormais appeler directement des fonctions, au lieu de chaînes. Voici concrètement ce que ça peut changer, exemple directement repris du site officiel&amp;nbsp;:&lt;/p&gt;

&lt;h5&gt;Avant&lt;/h5&gt;

&lt;pre&gt;from django.conf.urls.defaults import *

urlpatterns = patterns('',
    ('^mavue/$', 'monsite.monapp.views.mavue')
)&lt;/pre&gt;

&lt;h5&gt;Maintenant (il ne s'agit que d'une possibilité, l'ancienne syntaxe fonctionne toujours)&lt;/h5&gt;

&lt;pre&gt;from django.conf.urls.defaults import *
from monsite.monapp.views import mavue

urlpatterns = patterns('',
    ('^mavue/$', mavue)
)&lt;/pre&gt;

&lt;p&gt;Ce qui autorise l'inclusion d'un décorateur directement dans le fichier &lt;code&gt;urls.py&lt;/code&gt;. Par exemple, ce code ajoute une authentification à une vue générique&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;from django.conf.urls.defaults import *
from django.contrib.auth.decorators import login_required
from django.views.generic.list_detail import object_list
from monsite.monapp.models import MonModele

info = {
    &quot;queryset&quot; : MonModele.objects.all(),
}

urlpatterns = patterns('',
    ('^mavue/$', login_required(object_list), info)
)&lt;/pre&gt;


&lt;p&gt;Enfin, la version requise du module MySQL Python est désormais la 1.2.1p2&amp;nbsp;; mais pour ne pas casser les sites ne pouvant effectuer la mise à jour, il est toujours possible d'en utiliser une plus ancienne en passant le paramètre &lt;code&gt;DATABASE_ENGINE&lt;/code&gt; de &lt;code&gt;settings.py&lt;/code&gt; à &quot;mysql_old&quot;.&lt;/p&gt;


&lt;p&gt;La publication de cette version a été un peu soudaine, la traduction Française n'est donc pas entièrement à jour... Un patch a été proposé. Pour conclure, une dernière nouveauté certes complètement mineure, mais en étant l'auteur j'en suis honteusement et orgueilleusement fier, le module &lt;code&gt;humanize&lt;/code&gt; est désormais localisé. C'est à dire que &lt;code&gt;&lt;q&gt; nombre&lt;/q&gt;&lt;/code&gt; ne donnera plus &quot;1st&quot; mais 1er (avec même le &quot;er&quot; en exposant).&lt;/p&gt;</content>
</entry>
<entry xml:lang="fr">
  <title>Newforms : des validateurs personnalisés</title>
  <link rel="alternate" type="text/html" href="http://www.aozeo.com/blog/67-django-newforms-validateurs-personnalises" />
  <issued>2007-03-14T17:55:00+01:00</issued>
  <modified>2007-03-14T17:55:00+01:00</modified>
  <id>http://www.aozeo.com/blog/67-django-newforms-validateurs-personnalises</id>
  <author><name>Baptiste</name></author>
  <dc:subject>Django</dc:subject>
  <summary>Avec les newforms de Django, il est relativement facile d'ajouter des validations personnalisées à vos champs : il faut simplement créer une classe dérivée du type de champ que vous souhaitez utiliser.</summary>
  <content type="text/html" mode="escaped">&lt;p&gt;Avec les newforms de Django, il est relativement facile d'ajouter des validations personnalisées à vos champs&amp;nbsp;: il faut simplement créer une classe dérivée du type de champ que vous souhaitez utiliser.&lt;/p&gt; &lt;p&gt;Dans cet exemple, nous allons simplement vérifier que l'utilisateur donné n'existe pas (pour, par exemple, un formulaire d'inscription). Il faut effectuer quelques imports avant tout, bien sûr&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;from django import newforms as forms
from django.contrib.auth.models import User&lt;/pre&gt;


&lt;p&gt;Pour un créer champ classique, on pourrait utiliser&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;nom_utilisateur = CharField(label=&quot;Nom d'utilisateur&quot;, min_length=3, max_length=128)&lt;/pre&gt;

&lt;p&gt;Nous allons donc dériver CharField&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;class UserField(forms.CharField):&lt;/pre&gt;

&lt;p&gt;Comme nous l'avons vu dans le précédent article, la validation des différents champs est définie dans leurs méthodes &lt;code&gt;clean&lt;/code&gt;. Il suffit donc de l'étendre&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;    def clean(self, value):
        super(UserField, self).clean(value)&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;super&lt;/code&gt; permet d'appeler la méthode &lt;code&gt;clean&lt;/code&gt; de la classe parente, qui vérifie notamment que la longueur du champ est bien comprise dans l'intervalle [min_length, max_length] et qu'il est rempli si l'attribut &lt;code&gt;required&lt;/code&gt; est à &lt;code&gt;True&lt;/code&gt;. C'est une ligne à placer au début de toute méthode d'une classe dérivée (évidemment, seulement si elle est étendue, c'est à dire définie dans la classe parente) pour éviter les soucis.&lt;br /&gt;
La vérification, enfin&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;        try:
            User.objects.get(username=value)
            raise forms.ValidationError(&quot;Quelqu'un utilise déjà ce nom d'utilisateur ! Choisissez-en un autre.&quot;)
        except User.DoesNotExist:
            return value&lt;/pre&gt;

&lt;p&gt;L'explication est très simple&amp;nbsp;: on cherche tout d'abord un utilisateur existant déjà et avec cet identifiant. Si il n'existe pas, alors Django lève une exception User.DoesNotExist, et on peut retourner la valeur pour dire que tout va bien. Par contre, si l'utilisateur existe, la fin du &lt;code&gt;try&lt;/code&gt; est exécutée, et une exception ValidationError est levée, avec le texte d'explication fourni en argument (qui sera affiché comme l'erreur &quot;Ce champ est obligatoire&quot;).&lt;br /&gt;
La ligne du formulaire qui va créer le champ ressemble désormais à&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;nom_utilisateur = UserField(label=&quot;Nom d'utilisateur&quot;, min_length=3, max_length=128)&lt;/pre&gt;

&lt;p&gt;Django va ensuite se débrouiller tout seul, empêcher de valider le formulaire, et ajouter l'erreur à &lt;code&gt;form.errors&lt;/code&gt;. Plus besoin de faire quoi que ce soit, si vous affichez votre formulaire grâce à &lt;q&gt; form &lt;/q&gt; dans votre gabarit (ou même si vous séparez erreurs et champs, avec &lt;q&gt; form.errors &lt;/q&gt;).&lt;/p&gt;


&lt;p&gt;Simple, et efficace&amp;nbsp;!&lt;/p&gt;</content>
</entry>
<entry xml:lang="fr">
  <title>Introduction aux newforms de Django</title>
  <link rel="alternate" type="text/html" href="http://www.aozeo.com/blog/66-introduction-aux-newforms-de-django" />
  <issued>2007-03-07T19:30:00+01:00</issued>
  <modified>2007-03-07T19:30:00+01:00</modified>
  <id>http://www.aozeo.com/blog/66-introduction-aux-newforms-de-django</id>
  <author><name>Baptiste</name></author>
  <dc:subject>Django</dc:subject>
  <summary>Pour inaugurer cette catégorie dédiée au framework web Django, qui présente l'intérêt majeur d'être écrit en Python, voici une présentation des nouveaux formulaires qui seront introduits dans la prochaine version 0.96, et qui sont déjà disponibles dans la version de développement. Il vaut mieux commencer à les comprendre dès maintenant, étant donné que les anciens ne seront rapidement plus disponibles.</summary>
  <content type="text/html" mode="escaped">&lt;p&gt;Pour inaugurer cette catégorie dédiée au &lt;em&gt;framework&lt;/em&gt; web Django, qui présente l'intérêt majeur d'être écrit en Python, voici une présentation des nouveaux formulaires qui seront introduits dans la prochaine version 0.96, et qui sont déjà disponibles dans la version de développement. Il vaut mieux commencer à les comprendre dès maintenant, étant donné que les anciens ne seront rapidement plus disponibles.&lt;/p&gt; &lt;p&gt;&lt;em&gt;À noter que l'absence de documentation n'a pas permis à ce billet d'être exhaustif... Quelques fonctionnalités des &lt;/em&gt;newforms&lt;em&gt; ne seront pas abordées.&lt;/em&gt;&lt;/p&gt;


&lt;h3&gt;Commencer&lt;/h3&gt;

&lt;p&gt;Il convient tout d'abord de les importer&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;from django import newforms as forms&lt;/pre&gt;

&lt;p&gt;Commençons par créer un formulaire. C'est très simple, un formulaire est une classe, dérivée de django.newforms.Form, dans laquelle on peut définir les différents champs qui seront disponibles, exactement comme dans les modèles.&lt;/p&gt;

&lt;pre&gt;class FormulaireCommentaire(forms.Form):
    nom = forms.CharField()
    email = forms.EmailField()
    message = forms.CharField()&lt;/pre&gt;


&lt;p&gt;Pour créer le formulaire, il suffit d'instancier la classe&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;form = FormulaireCommentaire()&lt;/pre&gt;


&lt;h3&gt;Méthodes et attributs&lt;/h3&gt;

&lt;p&gt;Une classe forms.Form peut prendre plusieurs arguments. Le premier permet de le pré-remplir (par exemple, s'il a déjà été envoyé mais qu'il contient des erreurs, et qu'il doit être réaffiché).&lt;br /&gt;
Vous pouvez également spécifier l'argument &lt;code&gt;auto_id&lt;/code&gt;, qui est un motif pour les identifiants des champs&amp;nbsp;: &lt;code&gt;champ_%s_formulaire&lt;/code&gt; donnera, pour le &quot;nom&quot;, champ_nom_formulaire.&lt;/p&gt;

&lt;pre&gt;data = request.POST.copy ()
form = FormulaireCommentaire(data, auto_id='commentaire_%s')&lt;/pre&gt;

&lt;p&gt;L'attribut &lt;code&gt;is_bound&lt;/code&gt; renvoie &lt;code&gt;True&lt;/code&gt; si le formulaire contient des données, &lt;code&gt;False&lt;/code&gt; sinon. Quand vous y avez inséré du contenu, vous pouvez vérifier sa validité en testant &lt;code&gt;form.is_valid()&lt;/code&gt;. Si certains champs sont mal remplis, &lt;code&gt;form.errors&lt;/code&gt; retourne un dictionnaire de la forme {'champ': [u'Erreur']}&amp;nbsp;; mais vous pouvez également afficher une erreur particulière en utilisant &lt;code&gt;form['champ'].errors&lt;/code&gt;.
Quand votre formulaire est valide - et seulement lorsqu'il l'est -, vous pouvez accéder à l'attribut &lt;code&gt;clean_data&lt;/code&gt;, qui contient les données (sous la forme d'un dictionnaire) converties en objets Python correspondants. Pour savoir quel champ retourne quel type de données, regardez la liste des types de champs plus bas. De la même façon que pour les erreurs, vous pouvez accéder au contenu d'un champ en utilisant &lt;code&gt;form.clean_data['champ']&lt;/code&gt;. Si le formulaire n'est pas valide, vous devrez vous baser sur les données POST&amp;nbsp;: &lt;code&gt;request.POST['champ']&lt;/code&gt;, mais ce ne sera pas un objet Python.&lt;/p&gt;


&lt;p&gt;Avant d'aller plus loin, regardons les arguments communs à tous les champs&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;required&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;C'est assez parlant, True (défaut) pour que le champ doive être rempli, False pour qu'il puisse rester vide.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;widget&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;L'argument &lt;code&gt;widget&lt;/code&gt; permet de choisir comment afficher un champ, plus d'explications dans la suite.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;label&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Permet de spécifier le nom du champ.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;help_text&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Permet de spécifier un texte qui apporte des précisions sur le champ.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;initial&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;La valeur par défaut que prendra le champ.&lt;br /&gt;&lt;/p&gt;


&lt;h3&gt;Afficher les formulaires&lt;/h3&gt;

&lt;p&gt;Pour afficher le formulaire, vous pouvez simplement le transmettre à votre gabarit, puis l'afficher avec &lt;code&gt;{{ form }}&lt;/code&gt;. Mais par défaut, il est rendu comme faisant partie d'un tableau, ce qui est loin de convenir à la majorité des cas. Vous pourrez ainsi faire &lt;code&gt;{{ form.as_p }}&lt;/code&gt; pour afficher le formulaire dans des paragraphes, et &lt;code&gt;{{ form.as_ul }}&lt;/code&gt; pour l'afficher sous forme de liste. Attention, si vous avez passé le paramètre &lt;code&gt;auto_id&lt;/code&gt; à &lt;code&gt;False&lt;/code&gt;, les &amp;lt;label&amp;gt; ne seront pas affichés&amp;nbsp;! De ces trois façons, les messages d'erreur seront également affichés sous les champs concernés.&lt;/p&gt;


&lt;p&gt;Cependant, on peut désirer personnaliser un peu plus la sortie HTML du formulaire, et Django le permet. Ainsi, &lt;code&gt;form['nom']&lt;/code&gt; affichera seulement l'&amp;lt;input&amp;gt;&amp;nbsp;; &lt;code&gt;form['nom'].errors&lt;/code&gt; les erreurs, et @@form['nom'].label l'intitulé du champ. Il faudra donc assembler ces éléments.&lt;/p&gt;


&lt;h3&gt;Widgets&lt;/h3&gt;


&lt;p&gt;L'argument &lt;code&gt;widget&lt;/code&gt; permet d'associer un type de champ (sortie HTML) à l'un d'entre eux. Par exemple, un même champ classique CharField pourra être affiché avec différents widgets&amp;nbsp;: TextInput (par défaut), PasswordInput, HiddenInput, Textarea... Ils permettent donc de choisir comment afficher un champ, en fonction de son objectif. Il est également possible de créer les vôtres en dérivant la classe &lt;code&gt;forms.Widget&lt;/code&gt; (ou l'un d'entre eux directement si vous souhaitez ne modifier qu'un détail), si vous avez un besoin plus spécifique. Parmi les autres widgets disponibles, retenons FileInput, CheckboxInput, Select, SelectMultiple, RadioSelect, CheckboxSelectMultiple, dont les noms sont suffisamment parlants. Attention, les widgets n'influent pas sur le contenu du champ, seulement sur la manière dont il est présenté.&lt;/p&gt;


&lt;h3&gt;Exemple&lt;/h3&gt;

&lt;p&gt;Voici comment afficher un formulaire basique de commentaire, et l'ajouter à la base de données.&lt;/p&gt;

&lt;pre&gt;def vue_article(request, id_article):
    class FormulaireCommentaire(forms.Form):
        nom = forms.CharField(&quot;Votre nom&quot;)
        email = forms.EmailField(&quot;Votre email&quot;, help_text = &quot;Veuillez indiquer une adresse mail valide&quot;, required = False)
        message = forms.CharField(widget = forms.Textarea)&lt;/pre&gt;


&lt;pre&gt;    try:
        article = Article.objects.get(id=id_article)
    except Article.DoesNoExist:
        raise Http404&lt;/pre&gt;


&lt;pre&gt;    form = FormulaireCommentaire()&lt;/pre&gt;


&lt;pre&gt;    if request.method =='POST': 
        #On copie les données POST
        data = request.POST.copy()
        #Et on crée un formulaire avec
        form = FormulaireCommentaire(data)
        message = None
        #S'il ne contient pas d'erreur...
        if form.is_valid():
            #On peut créer un nouveau commentaire
            comment = Comment(nom = form.clean_data['nom'], email = form.clean_data['email'], message = form.clean_data['message']
            #Et l'enregister
            comment.save()
            #On remplace form par un nouveau formulaire vide
            form = FormulaireCommentaire()
            message = &quot;Commentaire enregistré avec succès !&quot;
    return render_to_response(&quot;article.html&quot;, {'article': article, 'form': form})&lt;/pre&gt;


&lt;p&gt;Ensuite, votre gabarit&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;{% extends base %}
{% block content %}
&lt;q&gt; article.text &lt;/q&gt;
{% endblock content %}
{% block content_bottom %}
{# Si on a un message de succès, on l'affiche. #}
{% if message %}
&amp;lt;p class=&quot;message&quot;&amp;gt;&lt;q&gt; message &lt;/q&gt;&amp;lt;/p&amp;gt;
{% endif %}
&amp;lt;form action=&quot;&quot; method=&quot;POST&quot;&amp;gt;
{# On fait une boucle sur le formulaire pour avoir une sortie plus personnalisée, et car ce ne serait pas intéressant, sinon ! #}
{% for field in form %}
    {# On n'oublie pas de relier le label au champ, grâce à l'attribut auto_id #}
    &amp;lt;p&amp;gt;&amp;lt;label for=&quot;{{ field.auto_id }}&quot;&amp;gt;{{ field.label }}&amp;lt;/label&amp;gt;
    {{ field }}
    {% if field.errors %}
    &amp;lt;span class=&quot;error&quot;&amp;gt;
        {{ field.errors }}
    &amp;lt;/span&amp;gt;
    {% endif %}
    {# On affiche la ligne d'aide si jamais elle est présente #}
    {% if field.help_text %}
        &amp;lt;span class=&quot;help&quot;&amp;gt;
        {{ field.help_text }}
        &amp;lt;/span&amp;gt;
    {% endif %}
    &amp;lt;/p&amp;gt;
{% endfor %}
&amp;lt;/form&amp;gt;
{% endblock content_bottom %}&lt;/pre&gt;

&lt;h3&gt;Conclusion&lt;/h3&gt;


&lt;p&gt;Voici un bref aperçu des nouveaux formulaires de Django. Si ils permettent d'harmoniser leur création avec la structure des modèles en offrant une syntaxe cohérente et puissante, ils présentent le défaut majeur de ne pas pouvoir regrouper certains champs pour former des &amp;lt;fieldset&amp;gt;, qui se révèlent pourtant très utiles dans la création de formulaires avancés. Cela m'oblige à utiliser des palliatifs grossiers dans mon gabarit... et ça ne me plaît pas&amp;nbsp;!&lt;/p&gt;


&lt;h3&gt;Les champs&lt;/h3&gt;


&lt;p&gt;Voici maintenant les différents types de champs disponibles dans une classe forms.Form&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Field&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Classe de base, qui représente un champ de texte simple. &lt;code&gt;clean&lt;/code&gt; retourne une chaîne.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CharField&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Comme &lt;code&gt;Field&lt;/code&gt;, mais un peu plus avancé, avec les arguments &lt;code&gt;min_length&lt;/code&gt; et &lt;code&gt;max_length&lt;/code&gt; qui stipulent respectivement un nombre minimum et maximum de caractères. &lt;code&gt;clean&lt;/code&gt; retourne une chaîne.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;IntegerField&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Un entier&amp;nbsp;; prend les arguments &lt;code&gt;min_value&lt;/code&gt; et &lt;code&gt;max_value&lt;/code&gt; pour choisir les nombres auquels la valeur ne doit respectivement pas être inférieure et supérieure. &lt;code&gt;clean&lt;/code&gt; retourne un entier.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DateField&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Une date, prend un argument &lt;code&gt;input_formats&lt;/code&gt; qui peut être un &lt;em&gt;tuple&lt;/em&gt; de formats de date acceptés, comme par exemple &quot;%d/%m/%Y&quot; pour &quot;7/3/2007&quot;. Par défaut, un bon nombre sont déjà définis... &lt;code&gt;clean&lt;/code&gt; retourne un objet &lt;code&gt;datetime.date&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TimeField&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Une heure, prend un argument &lt;code&gt;input_formats&lt;/code&gt; qui peut être un &lt;em&gt;tuple&lt;/em&gt; de formats d'heure acceptés, comme par exemple &quot;%Hh%M:%S&quot; pour &quot;17h23:12&quot;. &lt;code&gt;clean&lt;/code&gt; retourne un objet &lt;code&gt;datetime.time&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DateTimeField&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Une date et une heure, prend un argument &lt;code&gt;input_formats&lt;/code&gt; qui peut être un &lt;em&gt;tuple&lt;/em&gt; de formats de date et heure acceptés, comme par exemple &quot;%Hh%M:%S %d/%m/%Y&quot; pour &quot;17h23:12 7/3/2007&quot;. &lt;code&gt;clean&lt;/code&gt; retourne un objet &lt;code&gt;datetime.datetime&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;RegexField&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Comme &lt;code&gt;CharField&lt;/code&gt;, à la différence que son contenu doit correspondre à l'expression régulière fournie à l'argument &lt;code&gt;regex&lt;/code&gt; (requis). Prend également un argument &lt;code&gt;error_message&lt;/code&gt;, qui sera retourné si l'expression régulière ne valide pas le champ. &lt;code&gt;clean&lt;/code&gt; retourne une chaîne.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;EmailField&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Une adresse email valide, avec les arguments &lt;code&gt;min_length&lt;/code&gt; et &lt;code&gt;max_length&lt;/code&gt;. &lt;code&gt;clean&lt;/code&gt; retourne une chaîne.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;URLField&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Une URL valide (du format http://www.aozeo.com/). Prends un argument &lt;code&gt;verify_exists&lt;/code&gt;, qui, passé à &lt;code&gt;True&lt;/code&gt;, vérifiera que l'URL répond et ne renvoie pas une 404&amp;nbsp;; &lt;code&gt;validator_user_agent&lt;/code&gt; permet de choisir l'USER-AGENT qui sera envoyé lors du test. &lt;code&gt;clean&lt;/code&gt; retourne une chaîne.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;BooleanField&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Un champ vrai ou faux classique. &lt;code&gt;clean&lt;/code&gt; retourne &lt;code&gt;True&lt;/code&gt; ou &lt;code&gt;False&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ChoiceField&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Un choix entre les différentes options passées en argument à &lt;code&gt;choices&lt;/code&gt;, sous la forme d'un &lt;em&gt;tuple&lt;/em&gt;&amp;nbsp;: ( ('identifiant_option_1', 'Option 1'), ('identifiant_option_2', 'Seconde option') ). &lt;code&gt;clean&lt;/code&gt; retourne une chaîne.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;MultipleChoiceField&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Comme &lt;code&gt;ChoiceField&lt;/code&gt;, mais autorise la sélection de plusieurs valeurs. &lt;code&gt;clean&lt;/code&gt; retourne une liste de chaînes.&lt;/p&gt;</content>
</entry>
<entry xml:lang="fr">
  <title>Webinaire W3C sur les bonnes pratiques du Web Mobile</title>
  <link rel="alternate" type="text/html" href="http://www.aozeo.com/blog/65-webinaire-w3c-sur-les-bonnes-pratiques-du-web-mobile" />
  <issued>2007-02-27T17:59:50+01:00</issued>
  <modified>2007-02-27T17:59:50+01:00</modified>
  <id>http://www.aozeo.com/blog/65-webinaire-w3c-sur-les-bonnes-pratiques-du-web-mobile</id>
  <author><name>Baptiste</name></author>
  <dc:subject>Web</dc:subject>
  <summary>Poussé par Karl Dubost, j'ai assisté au premier webinaire français organisé par le W3C, qui concernait les bonnes pratiques du web mobile.</summary>
  <content type="text/html" mode="escaped">&lt;p&gt;Poussé par &lt;a href=&quot;http://www.la-grange.net/karl/&quot;&gt;Karl Dubost&lt;/a&gt;, j'ai assisté au premier webinaire français organisé par le W3C, qui concernait les bonnes pratiques du web mobile.&lt;/p&gt; &lt;p&gt;Une expérience très réussie, félicitations à Cédric Kiss pour sa présentation&amp;nbsp;; et un succès logistique admirable. On pouvait entendre l'animateur sur un flux audio, voir en parallèle les diapos en ligne qu'il déclenchait au fur et à mesure, tout en pouvant poser des questions, et ce avec 32 auditeurs.&lt;/p&gt;


&lt;p&gt;Cette conférence - qui sera prochainement mise en ligne -, a permis de cerner réellement les problématiques du web mobile, un des domaines ayant le plus fort potentiel d'expansion de l'Internet actuel, mais bien mal connu et bien mal aimé, de par ses importantes contraintes.&lt;/p&gt;


&lt;p&gt;Ce qui ressort, pour moi, de cette heure de séminaire, quand au support&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Il est très difficile d'établir une &quot;configuration&quot; de base sur laquelle partir pour l'élaboration de sites destinés à des outils mobiles, les différences entre appareils sont illustrées par ce &lt;a href=&quot;http://www.w3.org/2005/MWI/BPWG/techs/XhtmlBasic11Support&quot; hreflang=&quot;en&quot;&gt;bref récapitulatif des capacités de quelques téléphones&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cependant, on peut considérer une configuration &quot;moyenne&quot; des appareils actuellement en service, qui est bien sûr destinée à évoluer rapidement&amp;nbsp;: 120px de large, pas de JavaScript, des pages de 20ko maximum, et un support des images JPEG et GIF.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;À l'inverse de l'Internet classique, où la démocratisation de l'ADSL rend la contrainte du poids de plus en plus marginale, elle est très importante pour le média portable, car l'accès est en général facturé au poids,&lt;/li&gt;
&lt;li&gt;Il faut accorder une importance à la différence de finalité&amp;nbsp;: l'utilisateur qui visite un site sur son téléphone portable n'a pas le même objectif qu'un visiteur sur son ordinateur de bureau&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Mais ce moyen d'accès au Web est à prendre sérieusement en compte, car l'utilisation d'un portable est plus courante que celle d'un ordinateur et étalée sur la journée entière&amp;nbsp;!&lt;/p&gt;


&lt;p&gt;En ce qui concerne le développement de sites pour ce média, ce qui nous intéresse&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Le support de l'UTF-8 peut-être considéré comme acquis&lt;/li&gt;
&lt;li&gt;Il est compliqué de détecter un media mobile, mais on peut essayer de se baser sur l'Agent-Utilisateur et les entêtes transmis par le navigateur&lt;/li&gt;
&lt;li&gt;On doit penser aux mobiles dès la création du site, par exemple lors de l'élaboration des messages d'erreur&amp;nbsp;: un site qui requiert un &quot;Page précédente&quot; au moindre problème n'est pas efficace, c'est lent pour les utilisateurs de téléphones&lt;/li&gt;
&lt;li&gt;Pour les formulaires, préférer autant que possible les &amp;lt;select&amp;gt; aux &amp;lt;input type=&quot;text&quot;&amp;gt;, et ranger les choix par pertinence pour faciliter la sélection&lt;/li&gt;
&lt;li&gt;Utiliser des &amp;lt;label&amp;gt; pour mettre en évidence la nature des champs&lt;/li&gt;
&lt;li&gt;Les ancres sont utiles, mais provoquent sur certains médias un rechargement complet de la page&amp;nbsp;!&lt;/li&gt;
&lt;li&gt;Pour les pages longues, les diviser peut être une solution&amp;nbsp;; mais ça comporte selon moi un inconvénient majeur&amp;nbsp;: si on cherche un passage donné, ce n'est pas pratique de devoir visiter les 10 subdivisions de la page pour la trouver, comme il est nécessaire de le faire avec l'adaptation que fait subir Google aux pages visitées à partir du &lt;a href=&quot;http://www.google.fr/xhtml/&quot;&gt;moteur de recherche mobile&lt;/a&gt;, que l'on peut constater sur la &lt;a href=&quot;http://www.google.fr/gwt/n?mrestrict=xhtml&amp;amp;site=search&amp;amp;q=philosophie&amp;amp;source=m&amp;amp;hl=fr&amp;amp;ei=01LkRfbyDr28wQH0hY8C&amp;amp;ct=res&amp;amp;cd=1&amp;amp;rd=1&amp;amp;u=http%3A%2F%2Ffr.wikipedia.org%2Fwiki%2FPhilosophie&quot;&gt;page Philosophie de Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Le JavaScript peut-être interprété par certains téléphones, mais pas la majorité&amp;nbsp;: il faut le rendre non indispensable et non obstrusif&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Quand à la présentation et à l'apparence des sites mobiles&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;On peut s'appuyer sur les CSS media=&quot;handled&quot;, même si certains appareils interprètent également les feuilles de style media=&quot;screen&quot;&amp;nbsp;: j'imagine qu'il faut penser à surcharger les règles CSS&lt;/li&gt;
&lt;li&gt;Il faut absolument limiter la taille des images, pour réduire temps de chargement et coûts&lt;/li&gt;
&lt;li&gt;Si les tableaux fonctionnent bien sur tous les navigateurs classiques, ils sont à proscrire totalement des pages mobiles&amp;nbsp;! Ils alourdissent sérieusement la page (risquent de provoquer une erreur mémoire chez les périphériques les moins robustes), et sont très mal rendus, de par l'affichage linéaire des téléphones portables.&lt;/li&gt;
&lt;li&gt;Il faut également utiliser les CSS avec parcimonie&amp;nbsp;: des feuilles de style trop longues sont elles aussi susceptibles de causer des problèmes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cédric a développé le concept du «&amp;nbsp;One Web », qui est de présenter un seul contenu, une seule page, même si elle peut-être adaptée au média&amp;nbsp;; c'est à dire de ne pas avoir deux sites redondants, un pour les téléphones portables et un pour les ordinateurs, mais un unique, prenant en compte les spécificités des différents moyens d'accès au site. L'utilisation des CSS media=&quot;handled&quot; est la pierre angulaire de cette &quot;philosophie&quot;, si j'ose dire, mais ne constitue pas le seul paramètre à prendre en compte&amp;nbsp;: les pages doivent également être pensées pour l'utilisateur mobile. Privilégiez les images miniatures avec un lien vers la version originale, pas de contenu susceptible de faire apparaître un ascenseur horizontal, préférez du texte concis et allant droit au but.&lt;/p&gt;


&lt;p&gt;Pour ceux qui s'y intéressent, vous pouvez donner vos avis sur le &lt;a href=&quot;http://www.w3.org/TR/2007/WD-mobileOK-basic10-tests-20070130/&quot;&gt;second brouillon de la spécification mobileOK&lt;/a&gt;, visant à déterminer les critères d'accessibilité d'un site aux médias portables, et à terme de faire office de &quot;label qualité&quot; pour les utilisateurs.&lt;/p&gt;


&lt;p&gt;Si vous désirez tester le rendu de votre site sur un téléphone, il y a des émulateurs de navigateurs pour mobiles, mais ce sont en général ceux des plateformes avancées (Symbian, Windows Mobile, ...). Sans avoir besoin d'aller jusque là, on peut se contenter de l'extension Web Developer pour Firefox (finalement, elle a toujours une utilité !), qui propose deux options intéressantes&amp;nbsp;: &quot;Linéariser la page&quot; et &quot;Rendu sur petits écrans&quot;, ce qui donne sur le blog d'Aozeo &lt;a href=&quot;/dotclear/images/blog-lineaire.png&quot;&gt;cette image&lt;/a&gt;. En situation réelle, et sur un téléphone Nokia interprétant les CSS media=&quot;screen&quot; (merci à celui qui a effectué les photos), le &lt;a href=&quot;/dotclear/images/blog-nokia.jpg&quot;&gt;résultat&lt;/a&gt; est beaucoup moins réjouissant. Pourquoi&amp;nbsp;? Car le téléphone interprète comme il peut des instructions qui ne lui sont pas destinées, et ça fâche. Mais bientôt, ce blog sera mobileOK, grâce à la création d'une feuille de style &quot;handled&quot;&amp;nbsp;!&lt;/p&gt;


&lt;p&gt;Bref, un webinaire très enrichissant, sur un domaine très prometteur et voué à une grande expansion, comme le prouve la décision récente de l'opérateur Ten de rendre l'Internet mobile &lt;a href=&quot;http://www.tastonten.com/index.php?2007/02/22/73-c-est-enorme&quot;&gt;gratuit et illimité pour ses abonnés&lt;/a&gt;. Je serai au rendez-vous pour le prochain&amp;nbsp;!&lt;/p&gt;</content>
</entry>
<entry xml:lang="fr">
  <title>Gérez vos polices de caractère avec Fonty Python</title>
  <link rel="alternate" type="text/html" href="http://www.aozeo.com/blog/64-gerez-vos-polices-de-caractere-avec-fonty-python" />
  <issued>2007-02-17T18:04:07+01:00</issued>
  <modified>2007-02-17T18:04:07+01:00</modified>
  <id>http://www.aozeo.com/blog/64-gerez-vos-polices-de-caractere-avec-fonty-python</id>
  <author><name>Baptiste</name></author>
  <dc:subject>General</dc:subject>
  <summary>Si vous touchez parfois au Gimp, vous avez sûrement déjà du être confronté à la recherche d'une police de caractère qui convienne à l'usage que vous désirez en faire. Et pour peu que vous en ayez pas mal, ça devient rapidement infernal !</summary>
  <content type="text/html" mode="escaped">&lt;p&gt;Si vous touchez parfois au Gimp, vous avez sûrement déjà du être confronté à la recherche d'une police de caractère qui convienne à l'usage que vous désirez en faire. Et pour peu que vous en ayez pas mal, ça devient rapidement infernal&amp;nbsp;!&lt;/p&gt; &lt;p&gt;En cherchant un logiciel me permettant de gérer mes typos, je suis tombé sur &lt;a href=&quot;https://savannah.nongnu.org/projects/fontypython&quot;&gt;Fonty Python&lt;/a&gt;. Il permet de visualiser ses polices de caractères, et de les organiser en &lt;em&gt;pogs&lt;/em&gt;, listes qui les référencent. Il est ainsi facile d'installer et de désinstaller plusieurs jeux de polices en fonction de ses besoins.&lt;/p&gt;


&lt;p&gt;Cependant, Fonty Python ne correspondait pas tout à fait à ce dont j'avais besoin. Je me suis donc essayé à Python - agréable expérience, pour y apporter les modifications qui me semblaient utiles. Avant de continuer, je préviens tout de suite que son auteur n'a pas souhaité sortir cette nouvelle version avant de l'avoir étudié en détail, ce qu'il ne peut pour le moment pas faire, elle n'est donc pas encore disponible.
&lt;br /&gt;Au menu&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;utilisation de gettext, et traduction de l'application&lt;/li&gt;
&lt;li&gt;tags&lt;/li&gt;
&lt;li&gt;notation&lt;/li&gt;
&lt;li&gt;sauvegarde des miniatures pour accélérer l'affichage&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ce que résume cette capture :&lt;br /&gt;
&lt;a href=&quot;/dotclear/images/fontypython-devel.png&quot;&gt;&lt;img src=&quot;/dotclear/images/fontypython-devel.png&quot; alt=&quot;Capture d&amp;#039;écran de la version de développement de Fonty Python&quot; /&gt;&lt;/a&gt;&lt;/p&gt;


&lt;p&gt;Bientôt, donc, un gestionnaire de polices de caractère complet pour Linux (et même dès maintenant si vous n'avez pas besoin des nouveautés exposées ci-dessus)&amp;nbsp;!&lt;/p&gt;


&lt;p&gt;Cette première expérience Python m'a fait découvrir un langage très puissant et performant, auquel je ne reprocherai que la documentation, loin d'égaler celle de PHP par exemple. J'en retiendrai également que wxWidgets est un créateur d'interfaces à éviter, au vu de son nombre de bugs et de comportements inattendus&amp;nbsp;! Je viens de me pencher sur pyGTK qui semble être plus intéressant, mais pour une grosse application, je pencherai plutôt pour pyQT4, qui a le mérite de ressembler, sous GNOME, à une application GNOME.&lt;/p&gt;</content>
</entry>
<entry xml:lang="fr">
  <title>W3-Campus, édition 2007</title>
  <link rel="alternate" type="text/html" href="http://www.aozeo.com/blog/63-w3-campus-edition-2007" />
  <issued>2007-02-05T10:42:34+01:00</issued>
  <modified>2007-02-05T10:42:34+01:00</modified>
  <id>http://www.aozeo.com/blog/63-w3-campus-edition-2007</id>
  <author><name>Baptiste</name></author>
  <dc:subject>Web</dc:subject>
  <summary>Webmaster-Hub a lancé l'année dernière la première édition de W3-Campus, un séminaire sur le Web (plus sur son utilisation que sur la technique).</summary>
  <content type="text/html" mode="escaped">&lt;p&gt;Webmaster-Hub a lancé l'année dernière la première édition de W3-Campus, un séminaire sur le Web (plus sur son utilisation que sur la technique).&lt;/p&gt; &lt;p&gt;Vu le succès de l'édition précédente, il ne pouvait qu'être reconduit, cette fois sous le signe du référencement et de la communication. Il se déroulera les 30 et 31 mars, à Marseille, avec les interventions - entre autres - de Sébastien Billard, de Philippe Yonnet (directeur du département Internet à Studyrama) ou de Paypal France. Le programme &lt;a href=&quot;http://www.w3-campus.com/article30.html&quot;&gt;est&lt;/a&gt; &lt;a href=&quot;http://www.w3-campus.com/article31.html&quot;&gt;dense&lt;/a&gt;, nul doute que vous trouverez ce qui vous intéresse si vous touchez à ce domaine. &lt;br /&gt;
La cité phocéenne est un peu éloignée de mes pâturages parisiens, je ne pourrai pas m'y rendre, mais les intéressés pourront faire confiance à l'organisation qui s'occupera de vous loger (dans un hôtel ***, excusez du peu) et de vous inviter à la soirée de clôture à bord d'un vrai bateau ancien en vrai bois.&lt;br /&gt;
Bref, une fin de semaine entièrement planifiée et sans effort&amp;nbsp;: comptez 590€ pour le séminaire, l'hébergement et les repas&amp;nbsp;; 305 pour les deux jours de conférence seuls.&lt;/p&gt;


&lt;p&gt;&lt;a href=&quot;http://www.w3-campus.com/resa.php&quot; hreflang=&quot;fr&quot;&gt;Réservez !&lt;/a&gt;&lt;/p&gt;</content>
</entry>
</feed>