<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Daily craps]]></title><description><![CDATA[Python et autres drogues]]></description><link>https://write.voiney.fr/</link><image><url>https://write.voiney.fr/favicon.png</url><title>Daily craps</title><link>https://write.voiney.fr/</link></image><generator>Ghost 4.8</generator><lastBuildDate>Thu, 19 Mar 2026 13:29:07 GMT</lastBuildDate><atom:link href="https://write.voiney.fr/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Supprimer les POP d'une interfaces audio en USB sous linux]]></title><description><![CDATA[<p>TL;DR</p><pre><code class="language-bash">@zartop [~] lsusb
...
Bus 001 Device 026: ID 1235:8205 Focusrite-Novation Scarlett Solo USB
...

@zartop [~] sudo vim /etc/tlp.conf
USB_BLACKLIST=&quot;1235:8205&quot;

@zartop [~] sudo systemctl restart tlp.service</code></pre><p>Sur un portable utilisant Debian, l&apos;alimentation des ports USB est coup&#xE9; si aucune donn&</p>]]></description><link>https://write.voiney.fr/supprimer-les-pop-dune-interfaces-audio-en-usb-sous-linux/</link><guid isPermaLink="false">5f509bb413d383000183c570</guid><category><![CDATA[music]]></category><category><![CDATA[linux]]></category><dc:creator><![CDATA[Alex]]></dc:creator><pubDate>Thu, 03 Sep 2020 07:50:59 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1621264871558-5e850ec4113e?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDQ1fHxzcGVha2VyJTIwY29mZmVlfGVufDB8fHx8MTYyNTU3NDg3MQ&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1621264871558-5e850ec4113e?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDQ1fHxzcGVha2VyJTIwY29mZmVlfGVufDB8fHx8MTYyNTU3NDg3MQ&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Supprimer les POP d&apos;une interfaces audio en USB sous linux"><p>TL;DR</p><pre><code class="language-bash">@zartop [~] lsusb
...
Bus 001 Device 026: ID 1235:8205 Focusrite-Novation Scarlett Solo USB
...

@zartop [~] sudo vim /etc/tlp.conf
USB_BLACKLIST=&quot;1235:8205&quot;

@zartop [~] sudo systemctl restart tlp.service</code></pre><p>Sur un portable utilisant Debian, l&apos;alimentation des ports USB est coup&#xE9; si aucune donn&#xE9;e n&apos;est transmise. Cela pose probl&#xE8;me pour les interfaces audio USB qui, d&#xE8;s lors qu&apos;aucun son n&apos;est jou&#xE9;, fait &quot;POP&quot;. Le paquet responsable de cette coupure est <a href="https://linrunner.de/tlp">TLP</a>.</p><p>Heureusement, il est configurable et permet de by-passer cette fonctionnalit&#xE9; en renseignant une blacklist d&apos;ID d&apos;appareils USB. Tout se passe dans <code>/etc/tlp.conf</code>.</p><p>Commencez par trouver l&apos;ID de votre interface audio USB grace &#xE0; la commande <code>lsusb</code> : </p><pre><code class="language-bash">@zartop [~] lsusb
...
Bus 001 Device 026: ID 1235:8205 Focusrite-Novation Scarlett Solo USB
...</code></pre><p>Copiez l&apos;ID (ici <code>1235:8205</code>) et renseignez le dans la configuration <code>USB_BLACKLISK</code> de cette fa&#xE7;on :</p><pre><code>USB_BLACKLIST=&quot;1235:8205&quot;</code></pre><p>Un red&#xE9;marrage du service tlp et on est bon : <code>sudo systemctl restart tlp.service</code>.</p><p>Alex.</p>]]></content:encoded></item><item><title><![CDATA[J'ai enfin trouvé un bon métronome sur Android]]></title><description><![CDATA[<p>Voil&#xE0; plusieurs semaines que je me suis mis en qu&#xEA;te de trouver une application Android pour me servir de m&#xE9;tronome. Il y en a &#xE9;norm&#xE9;ment mais mon cahier est assez lourd.</p><p>Voil&#xE0; les pr&#xE9;-requis :</p><ul><li>Une indication sonore du rythme</li></ul>]]></description><link>https://write.voiney.fr/jai-enfin-trouve-un-bon-metronome-sur-android/</link><guid isPermaLink="false">5e664e5a75774000010e71dd</guid><category><![CDATA[music]]></category><category><![CDATA[android]]></category><dc:creator><![CDATA[Alex]]></dc:creator><pubDate>Thu, 18 Oct 2018 07:40:41 GMT</pubDate><media:content url="https://write.voiney.fr/content/images/2021/07/image.png" medium="image"/><content:encoded><![CDATA[<img src="https://write.voiney.fr/content/images/2021/07/image.png" alt="J&apos;ai enfin trouv&#xE9; un bon m&#xE9;tronome sur Android"><p>Voil&#xE0; plusieurs semaines que je me suis mis en qu&#xEA;te de trouver une application Android pour me servir de m&#xE9;tronome. Il y en a &#xE9;norm&#xE9;ment mais mon cahier est assez lourd.</p><p>Voil&#xE0; les pr&#xE9;-requis :</p><ul><li>Une indication sonore du rythme : tic et toc (accent sur le premier battement de la mesure)</li><li>Une indication visuelle du rythme : balancier, chiffre, ... tous sauf un flash lumineux</li><li>Plusieurs profils en m&#xE9;moire</li><li>Plusieurs type de rythme : noire, croche, double croche, triolet, ...</li><li>Avoir le choix du nombre de temps par mesure</li><li>Gratuite : puisque je joue essentiellement &#xE0; la maison, si je dois payer autant en prendre un physique</li><li>Pas ou peu de pub : surtout pas de pub envahissante en plein milieu de l&apos;&#xE9;cran du m&#xE9;tronome...</li><li>Fiable : si l&apos;app crash en plein exercice, c&apos;est pas bon !</li><li>Fluide</li><li>Interface portrait et paysage</li></ul><p>Avec tous ces crit&#xE8;res, j&apos;ai eu beaucoup de mal &#xE0; trouver. Mon choix s&apos;est finalement port&#xE9; vers l&apos;application <a href="https://play.google.com/store/apps/details?id=app.pg.stagemetronome&amp;hl=en">Stage Metronome</a> de <a href="https://play.google.com/store/apps/developer?id=PG+App+Studio">PG App Studio</a>.</p><p>Elle r&#xE9;unit <strong>TOUS</strong> les crit&#xE8;res, gratuitement et sans pub, que demander de plus ?</p><p></p><p>Alex.</p>]]></content:encoded></item><item><title><![CDATA[Installer GuitarPro 7.5 sur Debian avec wine]]></title><description><![CDATA[<p>Voil&#xE0; un petit script shell qui s&apos;occupe d&apos;installer GuitarPro 7.5 dans un prefix wine.</p><p>Il configure &#xE9;galement le smoothing RGB pour que les fonts soient clean !</p><p>Profitez bien !</p><pre><code>#! /bin/sh
WINE_FOLDER=$HOME/.wine-gp7
GP7_EXE=$HOME/Nextcloud/Documents/softwares/guitar-pro-7-setup.exe

# echo</code></pre>]]></description><link>https://write.voiney.fr/installer-guitarpro-7-5-sur-debian-avec-wine/</link><guid isPermaLink="false">5e664e5a75774000010e71dc</guid><category><![CDATA[scripting]]></category><dc:creator><![CDATA[Alex]]></dc:creator><pubDate>Wed, 19 Sep 2018 06:44:23 GMT</pubDate><content:encoded><![CDATA[<p>Voil&#xE0; un petit script shell qui s&apos;occupe d&apos;installer GuitarPro 7.5 dans un prefix wine.</p><p>Il configure &#xE9;galement le smoothing RGB pour que les fonts soient clean !</p><p>Profitez bien !</p><pre><code>#! /bin/sh
WINE_FOLDER=$HOME/.wine-gp7
GP7_EXE=$HOME/Nextcloud/Documents/softwares/guitar-pro-7-setup.exe

# echo &quot;Remove wine prefix&quot;
# rm -rf $WINE_FOLDER
echo &quot;Configure font smoothing&quot;
WINEPREFIX=$WINE_FOLDER winetricks settings fontsmooth=rgb
echo &quot;Install corefonts&quot;
WINEPREFIX=$WINE_FOLDER winetricks settings corefonts
echo &quot;Install GuitarPro7&quot;
WINEPREFIX=$WINE_FOLDER wine $GP7_EXE &amp;
exit 0
</code></pre>]]></content:encoded></item><item><title><![CDATA[Passage de Pelican à Ghost]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>J&apos;ai r&#xE9;cemment d&#xE9;cid&#xE9; de passer du moteur de blog <a href="http://docs.getpelican.com/en/stable/">Pelican</a> &#xE0; <a href="https://ghost.org">Ghost</a>.<br>
Ce changement a &#xE9;t&#xE9; motiv&#xE9; par l&apos;envie de <strong>pouvoir &#xE9;diter du contenu depuis n&apos;importe o&#xF9;.</strong><br>
En esp&#xE9;rant que cela me</p>]]></description><link>https://write.voiney.fr/passage-de-pelican-a-ghost/</link><guid isPermaLink="false">5e664e5a75774000010e71db</guid><dc:creator><![CDATA[Alex]]></dc:creator><pubDate>Wed, 11 Apr 2018 07:50:24 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>J&apos;ai r&#xE9;cemment d&#xE9;cid&#xE9; de passer du moteur de blog <a href="http://docs.getpelican.com/en/stable/">Pelican</a> &#xE0; <a href="https://ghost.org">Ghost</a>.<br>
Ce changement a &#xE9;t&#xE9; motiv&#xE9; par l&apos;envie de <strong>pouvoir &#xE9;diter du contenu depuis n&apos;importe o&#xF9;.</strong><br>
En esp&#xE9;rant que cela me motive pour le faire plus souvent !</p>
<h3 id="bienvenuesdoncsurcenouveaublog">Bienvenu(es) donc sur ce nouveau blog !</h3>
<p>Et pour ce faire, j&apos;ai cod&#xE9; un petit script python qui utilise <a href="https://pandoc.org/">Pandoc</a> pour convertir les articles<br>
que j&apos;avais &#xE9;crit en reStructuredText (RST) vers le format Markdown, utilis&#xE9; par le moteur de Ghost.</p>
<p>Il commence par extraire les tags RST &#xE0; coup d&apos;expression r&#xE9;guli&#xE8;re et utilise ensuite <a href="https://pandoc.org/">Pandoc</a><br>
pour s&apos;occuper de la conversion.</p>
<p>Il est disponnible en licence MIT sur mon <a href="https://github.com/avoiney/peli2ghost">repo Github</a>.</p>
<p>C&apos;est une premi&#xE8;re version qui recevra peut &#xEA;tre des am&#xE9;liorations.<br>
Si vou &#xEA;tes tent&#xE9;s, n&apos;h&#xE9;sitez pas &#xE0; contribuer !</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Traduire du texte dans VIM]]></title><description><![CDATA[Deux mapping pour traduire du texte dans le meilleur éditeur qui
    soit: VIM]]></description><link>https://write.voiney.fr/traduire-du-texte-dans-vim/</link><guid isPermaLink="false">5e664e5a75774000010e71d5</guid><category><![CDATA[scripting]]></category><category><![CDATA[vim]]></category><dc:creator><![CDATA[Alex]]></dc:creator><pubDate>Wed, 06 Dec 2017 18:04:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Il y a pas mal de plugin pour la traduction de texte pour les utilisateurs de VIM. Malheureusement, google a changer sa politique pour l&apos;utilisation de ses APIs, et les d&#xE9;veloppeurs de plugins n&apos;ont pas forc&#xE9;ment suivi. Du coup, il devient assez difficile de trouver quelque chose d&apos;utilisable.</p>
<p>J&apos;ai cherch&#xE9;, fouin&#xE9;, parcouru pas mal de thread et de blog et j&apos;en ai pondu deux petits mapping perso qui s&apos;appuient sur <a href="https://www.soimort.org/translate-shell/">translate-shell</a>, un soft qui permet de faire des traductions en utilisant Google&#xA9; translate.</p>
<p>Le premier mapping permet de prend le texte s&#xE9;lectionn&#xE9;, et de le traduire :</p>
<pre><code>vnoremap &lt;leader&gt;t y:sp newfile&lt;CR&gt;pgg^vG$:!trans -b :fr&lt;CR&gt;
</code></pre>
<p>Le second prend la ligne courante, et la traduit :</p>
<pre><code>nnoremap &lt;leader&gt;t ^v$y:sp newfile&lt;CR&gt;pgg^vG$:!trans -b :fr&lt;CR&gt;
</code></pre>
<p>Quelques explications sur ces mapping :</p>
<ul>
<li><code>vnoremap</code>{.sourceCode} et nnoremap permettent de cr&#xE9;er un raccourcis clavier en mode visuel et normal;</li>
<li><code>&lt;leader&gt;t</code>{.sourceCode} dit &#xE0; VIM d&apos;utiliser la touche <em>leader</em> suivi de la touche <em>t</em> comme raccourci;</li>
<li><code>y:sp newfile&lt;CR&gt;</code>{.sourceCode} fait dans l&apos;ordre : copier la s&#xE9;lection (y) etouvrir un buffer vide (:sp newfile&lt;CR&gt;);</li>
<li><code>pgg^vG$</code>{.sourceCode} copie la s&#xE9;lection (p), d&#xE9;place le curseur &#xE0; la premi&#xE8;re ligne (gg), d&#xE9;place le curseur au d&#xE9;but de la ligne (^), passe en mode visuel (v), d&#xE9;place le curseur &#xE0; la derni&#xE8;re ligne (G), et d&#xE9;place le curseur &#xE0; la fin de la ligne;</li>
<li><code>:!trans -b :fr</code>{.sourceCode} demande &#xE0; translate-shell de traduire la s&#xE9;lection en mode simplifi&#xE9; (!trans -b), vers la langue fran&#xE7;ais (:fr).</li>
</ul>
<p>Le texte &#xE0; traduire est ouvert dans un nouveau buffer, et est remplac&#xE9; par la traduction une fois celle-ci trouv&#xE9;e.</p>
<p>Et voil&#xE0; ! Le deuxi&#xE8;me mapping fait globalement la m&#xEA;me chose, &#xE0; ceci pr&#xE8;s qu&apos;il s&#xE9;lection la ligne courante avant d&apos;ouvrir le buffer et de faire la traduction.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Vous reprendrez bien un peu d'ORM ?]]></title><description><![CDATA[Fouillons un peu dans les entrailles de cette belle machine qu'est
    l'ORM de Django]]></description><link>https://write.voiney.fr/vous-reprendrez-bien-un-peu-dorm/</link><guid isPermaLink="false">5e664e5a75774000010e71d4</guid><dc:creator><![CDATA[Alex]]></dc:creator><pubDate>Thu, 27 Jul 2017 13:49:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h1 id="prrequis">Pr&#xE9; requis</h1>
<p>Commen&#xE7;ons cet article par quelques rappels histoire de savoir de quoi<br>
on va parler ici.</p>
<h2 id="orm">ORM</h2>
<blockquote>
<p>Un object-relational mapping (mapping objet-relationnel en fran&#xE7;ais)<br>
est une technique de programmation informatique qui cr&#xE9;e l&apos;illusion<br>
d&apos;une base de donn&#xE9;es orient&#xE9;e objet &#xE0; partir d&apos;une base de donn&#xE9;es<br>
relationnelle en d&#xE9;finissant des correspondances entre cette base de<br>
donn&#xE9;es et les objets du langage utilis&#xE9;. On pourrait le d&#xE9;signer par<br>
&#xAB; correspondance entre monde objet et monde relationnel &#xBB;. &#x2014;<br>
<a href="https://fr.wikipedia.org/wiki/Mapping_objet-relationnel">Wikip&#xE9;dia</a></p>
</blockquote>
<p>Un ORM est donc un outil qui permet d&apos;associer un ou plusieurs objets, &#xE0;<br>
une ou plusieurs tables d&apos;une base de donn&#xE9;es relationnelle. C&apos;est le<br>
parall&#xE8;le entre votre mod&#xE8;le de donn&#xE9;es et vos donn&#xE9;es telles qu&apos;elles<br>
sont stock&#xE9;es.</p>
<h2 id="django">Django</h2>
<p>Django est un framework web &#xE9;crit en Python. Il vous fournit tout (ou<br>
presque) ce dont vous avez besoin pour construire une application web en<br>
Python:</p>
<ul>
<li>un routeur d&apos;url;</li>
<li>une gestion de l&apos;authentification;</li>
<li>une gestion des sessions;</li>
<li>un moteur de template;</li>
<li>un <a href>ORM</a>;</li>
<li>un syst&#xE8;me de middleware;</li>
<li><em>et j&apos;en passe</em></li>
</ul>
<h1 id="utilisezlapuissancedelormdedjango">Utilisez la puissance de l&apos;ORM de Django</h1>
<p>Je ne vais pas passer sur les m&#xE9;thodes de base de l&apos;ORM, la<br>
<a href="https://docs.djangoproject.com/en/1.11/topics/db/queries/">documentation</a><br>
s&apos;en charge d&#xE9;j&#xE0; tr&#xE8;s bien elle-m&#xEA;me. Passons plut&#xF4;t &#xE0; une utilisation<br>
un peu plus avanc&#xE9;e et du coup plus sympa. Int&#xE9;ressons nous aux m&#xE9;thodes<br>
<strong>annotate</strong> et <strong>aggregate</strong>.</p>
<h2 id="miseenplace">Mise en place</h2>
<pre><code>class Student(models.Model):
    firstname = models.CharField(max_length=30)
    lastname = models.CharField(max_length=30)

    def __unicode__(self):
        return &apos;{} {}&apos;.format(self.firstname, self.lastname)


class Grade(models.Model):
    student = models.ForeignKey(Student)
    value = models.FloatField()
</code></pre>
<h2 id="annotate">Annotate</h2>
<p><em>Annotate</em> permet d&apos;ajouter, &#xE0; la vol&#xE9;e et <strong>&#xE0; chacun des &#xE9;l&#xE9;ments</strong><br>
retourn&#xE9;s par la requ&#xEA;te, un ou plusieurs attributs. De ce fait, il sera<br>
possible de filtrer, trier, ou faire des actions d&apos;agr&#xE9;gation sur ces<br>
attributs en base de donn&#xE9;es.</p>
<p>Regardons quelques exemples pour illustrer &#xE7;a :</p>
<pre><code>import random
from django.db.models import Avg
s1 = Student.objects.create(firstname=&apos;john&apos;, lastname=&apos;doe&apos;)
s2 = Student.objects.create(firstname=&apos;jane&apos;, lastname=&apos;doe&apos;)
for value in [random.randint(0, 20) for i in xrange(0, 30)]:
    Grade(student=s1, value).save()
for value in [random.randint(0, 20) for i in xrange(0, 30)]:
    Grade(student=s2, value).save()
# ajouter la moyenne des notes de chaque &#xE9;l&#xE8;ve
students = Student.objects.annotate(avg=Avg(&apos;grade__value&apos;))
print(students[0].avg)  # 8.23
print(students[1].avg)  # 11.3
print(students.filter(avg__gte=10))  # &lt;QuerySet [&lt;Student: john doe&gt;]&gt;
</code></pre>
<p>On peut &#xE9;galement ajouter un attribut avec une valeur arbitraire</p>
<pre><code>from django.db.models import ExpressionWrapper, Value, IntegerField
students = Student.objects.annotate(max_abs=ExpressionWrapper(Value(3),
                                                             output_field=IntegerField()))
print(students[0].max_abs)  # 3
print(students[1].max_abs)  # 3
</code></pre>
<p>Mais annotate nous permet de faire des choses plus complexes, comme<br>
concat&#xE9;ner plusieurs champs.</p>
<pre><code>from django.db.models import F, ExpressionWrapper, Value
from django.db.models.functions import Concat
students = students.annotate(fullname=ExpressionWrapper(Concat(F(&apos;firstname&apos;), Value(&apos; &apos;), F(&apos;lastname&apos;)),
                                                        output_field=CharField()))
print(students[1].fullname)  # jane doe
</code></pre>
<p>Ici, c&apos;est l&apos;objet F() qui permet de faire r&#xE9;f&#xE9;rence &#xE0; un attribut de<br>
l&apos;&#xE9;l&#xE9;ment en cours de traitement.</p>
<p>Admettons maintenant que vous vouliez, pour chaque &#xE9;l&#xE9;ment, indiquer si<br>
l&apos;&#xE9;l&#xE8;ve est re&#xE7;u ou non (c&apos;est &#xE0; dire, est-ce que sa moyenne est<br>
sup&#xE9;rieur ou &#xE9;gale &#xE0; 10). Utilisons Case() et `When()`:</p>
<pre><code>from django.db.models import Case, When
students_with_avg = Student.objects.annotate(avg=Avg(&apos;grade__value&apos;))
# ajoutons l&apos;admissibilit&#xE9;
admissible = ExpressionWrapper(Value(&apos;admis&apos;), output_field=CharField())
non_admissible = ExpressionWrapper(Value(&apos;non admis&apos;), output_field=CharField())
students = students_with_avg.annotate(admissible=Case(When(avg__gte=10, then=admissible),
                                                      default=non_admissible))
students = students.annotate(fullname=ExpressionWrapper(Concat(F(&apos;firstname&apos;), Value(&apos; &apos;), F(&apos;lastname&apos;)),
                                                        output_field=CharField()))

print(dict(students.values_list(&apos;fullname&apos;, &apos;admissible&apos;)))
# output: {&apos;john doe&apos;: &apos;non admis&apos;,
#          &apos;jane doe&apos;: &apos;admis&apos;}
</code></pre>
<h2 id="aggregate">Aggregate</h2>
<p><em>Aggregate</em> quant &#xE0; elle, s&apos;applique <strong>sur l&apos;ensemble des &#xE9;l&#xE9;ments</strong><br>
retourn&#xE9;s par la requ&#xEA;te. Elle permet d&apos;ex&#xE9;cuter une fonction (Sum, par<br>
exemple) sur le r&#xE9;sultat de la requ&#xEA;te. Voyons quelques exemples.</p>
<pre><code>from django.db.models import Max, Min
# trouvons la moyenne g&#xE9;n&#xE9;rale
students_with_avg = Student.objects.annotate(student_avg=Avg(&apos;grade__value&apos;))
global_avg = students_with_avg.aggregate(Avg(&apos;sutdent_avg&apos;))
print(global_avg)  # {&apos;student_avg__avg&apos;: 9.765}
# trouvons la note la plus basse
print(Grade.objects.aggregate(min_value=Min(&apos;value&apos;),
                              max_value=Max(&apos;value&apos;)))
# output: {&apos;min_value&apos;: 3, &apos;max_value&apos;: 18}
</code></pre>
<p>Voil&#xE0; d&#xE9;j&#xE0; de quoi jouer un peu avec l&apos;ORM de Django.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Découverte mobile: un puzzle game nommé Enyo]]></title><description><![CDATA[Pour le premier article de la catégorie 'OnTheGo', je vous propose de
découvrir un jeu qui m'a déjà fait perdre plusieurs heures de ma vie :
[Enyo](http://www.enyo-game.com).]]></description><link>https://write.voiney.fr/decouverte-mobile-un-puzzle-game-nomme-enyo/</link><guid isPermaLink="false">5e664e5a75774000010e71d9</guid><category><![CDATA[android]]></category><dc:creator><![CDATA[Alex]]></dc:creator><pubDate>Mon, 05 Sep 2016 07:58:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Pour le premier article de la cat&#xE9;gorie &apos;OnTheGo&apos;, je vous propose de d&#xE9;couvrir un jeu qui m&apos;a d&#xE9;j&#xE0; fait perdre plusieurs heures de ma vie : <a href="http://www.enyo-game.com">Enyo</a>.</p>
<p><img src="/content/images/2018/04/enyo.launch.png" alt="enyo.launch" loading="lazy"></p>
<p>C&apos;est un jeux de la cat&#xE9;gorie <em>Puzzle Game</em> dans lequel vous incarnez <em>Enyo</em>, la d&#xE9;esse grecque des champs de bataille. Le but est de r&#xE9;soudre des plateaux de 10x10, compos&#xE9;s de terre ou de pi&#xE8;ges. Pour r&#xE9;soudre un<br>
plateau, il vous faudra vous d&#xE9;barrasser des ennemis pr&#xE9;sent sur les cases en utilisant vos capacit&#xE9;s guerri&#xE8;re :</p>
<ul>
<li>le coup de bouclier (sert &#xE9;galement au d&#xE9;placement de base)</li>
<li>le grappin, qui permet d&apos;attraper un ennemis et de le tirer derri&#xE8;re<br>
soi</li>
<li>le lancer de bouclier, qui permet d&apos;attaquer un ennemis &#xE0; distance</li>
<li>le saut, qui, une fois sur deux, assomme les ennemis alentours</li>
</ul>
<p>Pour vous d&#xE9;barrasser d&apos;un ennemi, il faut le faire tomber dans un pi&#xE8;ge. De prima bord, le jeu semble facile, mais ne vous y laisser pas prendre, il demande une v&#xE9;ritable r&#xE9;flexion, d&apos;autant que le nombre de monstres augmente au fil des niveaux !</p>
<p>Le jeux est disponible &quot;gratuitement&quot; (moyennant publicit&#xE9;s) sur <a href="https://itunes.apple.com/us/app/enyo/id1118353232">iOS</a> et <a href="https://play.google.com/store/apps/details?id=com.tinytouchtales.enyo">android</a>. Un achat in-app est possible sur iOS pour supprimer la publicit&#xE9;.</p>
<p><img src="/content/images/2018/04/enyo.firstlvl.png" alt="enyo.firstlvl" loading="lazy"></p>
<p><img src="/content/images/2018/04/enyo.sndlvl.png" alt="enyo.sndlvl" loading="lazy"></p>
<p>Amusez vous bien !</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Créer un Manager pour modèle abstrait]]></title><description><![CDATA[Dans ce nouveau article sur Django, nous allons définir un ModelManager pour des modèles abstraits, qui comme vous le savez, ne peuvent pas être requêtées en l'état.

L'héritage entre deux modèles en Django peux se faire de 2 façon dans le framework : de manière concrète ou abstraite.]]></description><link>https://write.voiney.fr/creer-un-manager-pour-modele-abstrait/</link><guid isPermaLink="false">5e664e5a75774000010e71cc</guid><dc:creator><![CDATA[Alex]]></dc:creator><pubDate>Fri, 02 Sep 2016 07:15:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Dans ce nouveau article sur Django, nous allons d&#xE9;finir un ModelManager pour des mod&#xE8;les abstraits, qui comme vous le savez, ne peuvent pas &#xEA;tre requ&#xEA;t&#xE9;es en l&apos;&#xE9;tat.</p>
<p>L&apos;h&#xE9;ritage entre deux mod&#xE8;les en Django peux se faire de 2 fa&#xE7;on dans le framework : de mani&#xE8;re concr&#xE8;te ou abstraite.</p>
<p>Un h&#xE9;ritage concret est tr&#xE8;s pratique, dans le cas de cl&#xE9; &#xE9;trang&#xE8;re par exemple. En effet, il est impossible de cr&#xE9;er un lien vers un mod&#xE8;le abstrait, puisque justement il n&apos;a pas d&apos;existence en DB. L&apos;inconv&#xE9;nient est qu&apos;il est n&#xE9;cessaire de maintenir une table pour le mod&#xE8;le papa.</p>
<p>L&apos;h&#xE9;ritage abstrait, quant &#xE0; lui, est utile lorsque des mod&#xE8;les distincts partagent des attributs ou des m&#xE9;thodes, mais qu&apos;ils n&apos;ont pas n&#xE9;cessairement de lien.</p>
<p>En r&#xE9;sum&#xE9;, les mod&#xE8;les concrets sont li&#xE9;s entre eux par un lien 1-1, qui fait office de cl&#xE9; primaire. Lorsqu&apos;un mod&#xE8;le h&#xE9;rite d&apos;un mod&#xE8;le abstrait, ce lien n&apos;existe pas.</p>
<p>Il devient donc p&#xE9;nible de faire d&apos;obtenir la liste des instances d&apos;un mod&#xE8;le qui h&#xE9;rite d&apos;un mod&#xE8;le abstrait.</p>
<p>Voici un petit bout de code qui permet, de mani&#xE8;re d&#xE9;tourn&#xE9;e, d&apos;obtenir toutes les instances d&apos;un enfant d&apos;un mod&#xE8;le abstrait :</p>
<pre><code class="language-python">from django.core.exceptions import FieldError, ObjectDoesNotExist
from django.db.models.query import QuerySet
import functools


class AbstractQuerySetSequence(list):
    def query(self, _name, *args, **kwargs):
        objects = AbstractQuerySetSequence()
        for k, qs in self:
            try:
                objects.append((k, getattr(qs, _name)(*args, **kwargs)))
            except FieldError as e:
                objects.append((k, qs))
            except Exception as e:
                objects.append((k, e))
        return objects

    def __getattr__(self, _name):
        try:
            return getattr(super(AbstractQuerySetSequence, self), _name)
        except AttributeError:
            return functools.partial(self.query, _name)

    def to_json(self):
        return {k.___name__: list(v) for k, v in self}


class AbstractModelChildrenRegister(object):
    &quot;&quot;&quot; Class to register abstract model children and
    query throught them.
    &quot;&quot;&quot;
    def __init__(self, *args, **kwargs):
        self.models = []

    def register(self, model):
        self.models.append(model)

    def managers(self):
        return [m.objects for m in self.models]

    def query(self, _name, *args, **kwargs):
        objects = AbstractQuerySetSequence()
        for manager in self.managers():
            try:
                attr = getattr(manager, _name)
                obj = attr(*args, **kwargs)
            except FieldError:
                obj = attr()
            except Exception as err:
                obj = err
            objects.append((manager.model, obj))
        return objects

    def get_one(self, *args, **kwargs):
        for manager in self.managers():
            try:
                obj = manager.get(*args, **kwargs)
            except:
                pass
            else:
                return obj
        raise ObjectDoesNotExist

    def __getattr__(self, _name):
        if not hasattr(QuerySet(), _name):
            return getattr(super(AbstractModelChildrenRegister, self), _name)
        return functools.partial(self.query, _name)
</code></pre>
<p>Ce &apos;Manager&apos;, plac&#xE9; sur un mod&#xE8;le abstrait, maintient un registre des classes enfantes de celui-ci. On expose ensuite l&apos;api d&apos;un ModelManager et on r&#xE9;percute les appelles &#xE0; cette api sur les mod&#xE8;les du registre. Le r&#xE9;sultat est une <strong>liste de tuples</strong> de ce type : <code>(ModelEnfant, QuerySet)</code>. En cas de <code>FieldError</code>, la queryset pr&#xE9;c&#xE9;dente (ou <code>.all()</code>) est renvoy&#xE9;e. Si c&apos;est un autre type d&apos;erreur qui est lev&#xE9;, la queryset est remplac&#xE9;e par celle-ci.</p>
<p>Et voil&#xE0; !</p>
<p>Bon &#xE7;a &#xE0; l&apos;air vraiment super sur le papier, mais est-ce que &#xE7;a fonctionne ? Voyons un exemple. Je passe volontairement les d&#xE9;tails de l&apos;initialisation du projet Django.</p>
<pre><code class="language-python">from django.db import models
from demo.utils.abstract import AbstractModelChildrenRegister


class TitledModel(models.Model):
    title = models.CharField(max_length=32)

    register = AbstractModelChildrenRegister()

    class Meta:
        abstract = True


class Serie(TitledModel):
    pass
TitledModel.register.register(Serie)


class Article(TitledModel):
    draft = models.BooleanField(default=False)
    content = models.TextField()
    serie = models.ForeignKey(Serie, related_name=&quot;articles&quot;, null=True, blank=True)
TitledModel.register.register(Article)
</code></pre>
<p>Voil&#xE0; on a cr&#xE9;&#xE9; deux mod&#xE8;les qui impl&#xE9;mentent <code>TitledModel</code>. On va cr&#xE9;er quelques objets et voir comment notre bidouille s&apos;en sort:</p>
<pre><code class="language-python">&gt;&gt;&gt; from demo.models import TitledModel, Article
&gt;&gt;&gt; Article.objects.create(title=&apos;Mon super article 1&apos;,
                           content=&apos;Super article de la mort&apos;)
&lt;Article: Article object&gt;
&gt;&gt;&gt; Article.objects.create(title=&apos;Mon super article 2&apos;,
                           content=&apos;Autre super article de la mort&apos;)
&lt;Article: Article object&gt;
&gt;&gt;&gt; TitledModel.register.all()
[(demo.models.Serie, []), (demo.models.Article, [&lt;Article: Article object&gt;])]
&gt;&gt;&gt; TitledModel.register.filter(title=&apos;Mon super article 1&apos;)\
...                     .values_list(&apos;title&apos;, flat=True)
[(demo.models.Serie, []), (demo.models.Article, [u&apos;Mon super article 1&apos;])]
</code></pre>
<p>&#xC7;a marche !</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Faire un ModelField Django encodé]]></title><description><![CDATA[Dans ce post, nous allons voir une approche pour faire un ModelField Django qui encode (et décode) des données pour nous, de manière transparente.]]></description><link>https://write.voiney.fr/faire-un-modelfield-django-encode/</link><guid isPermaLink="false">5e664e5a75774000010e71d0</guid><dc:creator><![CDATA[Alex]]></dc:creator><pubDate>Tue, 20 Oct 2015 03:36:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Django est plein de surprises. Sa class Model est impressionnante de<br>
complexit&#xE9;. Voici un exemple de ce qui est possible de faire:</p>
<pre><code class="language-python"># utilisation
class MonModel(models.Model):
    ...
    data = EncryptedField(encoder=Base64Enc)
    ...

mon_instance = MonModel()
mon_instance.data = &quot;coucou&quot;
print(mon_instance.data)
# &apos;coucou&apos;
print(mon_instance.data_enc)
# &apos;Y291Y291\n&apos;
</code></pre>
<p>Moi, &#xE7;a me botte. Au final, &#xE7;a s&apos;apparente &#xE0; ce que Django fait pour les mots de passe de la base User avec la m&#xE9;thode <code>set_password</code>,<br>
mais en plus pratique.<br>
On va voir le d&#xE9;tail des op&#xE9;rations pour arriver &#xE0; ce r&#xE9;sultat.</p>
<p>Tout d&apos;abord, il faut impl&#xE9;menter un objet qui permet d&apos;encoder et de d&#xE9;coder des messages : un <em>encodeur</em>. Pour l&apos;exemple, on va en faire un que ne fait<br>
strictement rien, et un objet qui cr&#xE9;e une repr&#xE9;sentation <strong>base64</strong> du contenu.</p>
<pre><code class="language-python">from django.db import models
import base64

class BaseEnc(object):
    def encode(self, value):
        return value

    def decode(self, msg):
        return msg

class Base64Enc(object):
    def encode(self, value):
        return base64.encodestring(value)

    def decode(self, msg):
        return base64.decodestring(msg)
</code></pre>
<p>Maintenant, on cr&#xE9;e une sous-classe de <code>TextField</code> et on surcharge quelques m&#xE9;thodes.</p>
<pre><code class="language-python">class EncodedField(models.TextField):

    description = &quot;An encoded field&quot;

    def __init__(self, encoder=BaseEnc, *args, **kwargs):
        self.encoder = encoder()
        super(EncodedField, self).__init__(*args, **kwargs)

    def contribute_to_class(self, cls, name):
        if self.db_column is None:
            self.db_column = name
        self.field_name = name + &apos;_enc&apos;
        super(EncodedField, self).contribute_to_class(cls, self.field_name)
        setattr(cls, name, property(self.get_data, self.set_data))

    def get_data(self, obj):
        if getattr(obj, self.field_name):
            return self.encoder.decode(getattr(obj, self.field_name))
        return None

    def set_data(self, obj, data):
        if data:
            setattr(obj, self.field_name, self.encoder.encode(data))
        else:
            setattr(obj, self.field_name, None)

</code></pre>
<p>La magie op&#xE8;re dans la m&#xE9;thode <code>contribute_to_class</code>. La raison d&apos;&#xEA;tre de cette m&#xE9;thode est tr&#xE8;s bien expliqu&#xE9;e dans <a href="http://lazypython.blogspot.fr/2008/11/django-models-digging-little-deeper.html">cet article d&apos;Alex Gaynor</a>. Pour ceux d&apos;entre vous qui ne seraient pas anglophones, ou qui auraient la flemme de tout lire voici un raccourci :</p>
<p>Certaines classes de Django poss&#xE8;de une m&#xE9;thode <code>add_to_class</code>. C&apos;est le cas des classes qui h&#xE9;ritent de <code>ModelBase</code> par exemple.<br>
Lorsque vous ajoutez un attribut <code>data = MaValeur</code> &#xE0; un mod&#xE8;le <code>MonModel</code>, <code>add_to_class(&apos;data&apos;, MaValeur)</code> est appel&#xE9;e.<br>
Deux cas de figurent se pr&#xE9;sentent, soit <code>MaValeur</code> ne poss&#xE8;de pas de m&#xE9;thode <code>contribute_to_class</code>, soit cette m&#xE9;thode existe.</p>
<p>Dans le premier cas, <code>add_to_class</code> va simplement faire un <code>setattr(&apos;data&apos;, MaValeur)</code>. Dans le second, c&apos;est <code>contribute_to_class</code> qui va &#xEA;tre appel&#xE9;e.</p>
<p>Ok, maintenant que c&apos;est clair, on reprend.</p>
<p>Notre m&#xE9;thode <code>contribute_to_class</code> va faire plusieurs choses:</p>
<ul>
<li>changer l&apos;attribut <code>field_name</code> de notre attribut en ajoutant <code>&quot;_enc&quot;</code> au nom de l&apos;attribut</li>
<li>d&#xE9;finir un getter/setter attach&#xE9; au nom de l&apos;attribut telle qu&apos;on l&apos;a d&#xE9;finit dans le mod&#xE8;le</li>
</ul>
<p>Par exemple, pour un attribut appel&#xE9; <code>data</code>, les donn&#xE9;es enregistr&#xE9;es en DB se retrouveront dans l&apos;attribut <code>data_enc</code>, tandis que l&apos;attribut <code>data</code> retournera le resultat de <code>instance_de_champ.get_data(&apos;data_enc&apos;)</code>. Et voil&#xE0;, notre valeur est d&#xE9;cod&#xE9;e !</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Un plugin d'export pour ListJs]]></title><description><![CDATA[Je suis tombé l'autre jour sur un **projet formidable en pure Javascript** pour afficher, trier (et bien d'autre chose) des listes html sous diverses formes : list (ul, ol), table, ...), j'ai nommé : [ListJs](http://www.listjs.com).]]></description><link>https://write.voiney.fr/un-plugin-dexport-pour-listjs/</link><guid isPermaLink="false">5e664e5a75774000010e71ce</guid><category><![CDATA[javascript]]></category><dc:creator><![CDATA[Alex]]></dc:creator><pubDate>Sat, 20 Jun 2015 11:25:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Je suis tomb&#xE9; l&apos;autre jour sur un <strong>projet formidable en pure<br>
Javascript</strong> pour afficher, trier (et bien d&apos;autre chose) des listes<br>
html sous diverses formes : list (ul, ol), table, ...), j&apos;ai nomm&#xE9; :<br>
<a href="http://www.listjs.com">ListJs</a>.</p>
<p>Il fait super bien le taf et du coup je m&apos;en sers <strong>TR&#xC8;S</strong> souvent.</p>
<p>Il int&#xE8;gre un syst&#xE8;me de plugins et d&apos;ailleur le d&#xE9;veloppeur en propose<br>
certains :</p>
<ul>
<li><a href="http://www.listjs.com/docs/plugins/pagination">ListPagination</a></li>
<li><a href="http://www.listjs.com/docs/plugins/fuzzysearch">ListSearch</a></li>
</ul>
<p>J&apos;en propose un &#xE9;galement pour permettre l&apos;export de la-dites liste,<br>
pour l&apos;instant uniquement en CSV mais d&apos;autres formats arriveront plus<br>
tard. Il est disponible sur mon repo Github :<br>
<a href="https://github.com/avcreation/list.exportable.js">ListExportable</a>.</p>
<p>Il reste quelques petites choses &#xE0; faire :</p>
<ul>
<li>Impl&#xE9;menter d&apos;autres formats d&apos;exports</li>
<li>Faire des tests...</li>
</ul>
<h3 id="dpendances">D&#xE9;pendances</h3>
<p>Il n&#xE9;cessite uniquement de ListJs (forc&#xE9;ment), et de jQuery (&gt;= 1.7)<br>
pour la gestion de l&apos;&#xE9;v&#xE8;nement <code>click</code>.</p>
<h3 id="usage">Usage</h3>
<pre><code>$(document).ready(function () {
	// defining list options
	var options = {
		valueNames: [&apos;col1&apos;, &apos;col2&apos;, &apos;...&apos;]
		plugins: [
		  ListExportable({
			linkContainersSelector: &quot;.exporters&quot;,
			exportLinkTemplate: &apos;&lt;a href=&apos;&apos;&gt;Export {{type}}&lt;/a&gt;&apos;,
			type: [&apos;csv&apos;],  // only csv for now, sorry...
			filename: &quot;export&quot;,  // some browsers will not handle the filename definition
		  })
		]
	};

	var listObj = new List(&apos;myTabelListId&apos;, options);

});
</code></pre>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Créer une grille responsive à partir d'une liste avec angularjs]]></title><description><![CDATA[Les framework CSS nous ont apporté énormément d'outils pour créer du
contenu responsive. Le plus appreciable est le système de grille, qui
permet d'afficher des éléments en respectant un pattern bien définit. Je
vous laisse consulter la doc des framework pour en apprendre plus.]]></description><link>https://write.voiney.fr/creer-une-grille-responsive-a-partir-dune-liste-avec-angularjs/</link><guid isPermaLink="false">5e664e5a75774000010e71cf</guid><category><![CDATA[angularjs]]></category><dc:creator><![CDATA[Alex]]></dc:creator><pubDate>Thu, 21 May 2015 09:24:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Les framework CSS nous ont apport&#xE9; &#xE9;norm&#xE9;ment d&apos;outils pour cr&#xE9;er du<br>
contenu responsive. Le plus appreciable est le syst&#xE8;me de grille, qui<br>
permet d&apos;afficher des &#xE9;l&#xE9;ments en respectant un pattern bien d&#xE9;finit. Je<br>
vous laisse consulter la doc des framework pour en apprendre plus.</p>
<p>Ce syst&#xE8;me de grille est extr&#xE8;mement pratique lorsque le nombre<br>
d&apos;&#xE9;l&#xE9;ments que l&apos;on a &#xE0; afficher est fixe. On peut, par exemple,<br>
d&#xE9;couper notre liste d&apos;&#xE9;l&#xE9;ments en une liste de listes de N &#xE9;l&#xE9;ments, N<br>
&#xE9;tant le nombre d&apos;&#xE9;l&#xE9;ments &#xE0; afficher par ligne.</p>
<p>En python on aurait par exemple &#xE7;a c&#xF4;t&#xE9; serveur :</p>
<pre><code>&gt;&gt;&gt; def list_to_rows(l, elem_by_row):
        return [[l[l.index(x) + i] for i in xrange(0, elem_by_row) if l.index(x) + i &lt; len(l)]
                for x in l[::elem_by_row]]

&gt;&gt;&gt;&gt; mylist = [1,2,3,4,5,6,7]
&gt;&gt;&gt; list_to_rows(mylist, 2)
[[1, 2], [3, 4], [5, 6], [7]]
</code></pre>
<p>Ici on utilise le slicing python en d&#xE9;finissant un step. <strong>Attention, &#xE7;a<br>
ne marchera pas avec un g&#xE9;n&#xE9;rateur !</strong></p>
<p>On aurait plus qu&apos;&#xE0; parcourir notre liste de liste c&#xF4;t&#xE9; HTML pour<br>
afficher les lignes successives, ici en Jinja par exemple.</p>
<pre><code>&lt;div&gt;
    {% for row in mylist %}
    &lt;div class=&quot;row&quot;&gt;
        {% for elem in row %}
        &lt;article&gt;
            &lt;div class=&quot;col s6&quot;&gt;  &lt;!-- On indique s6 parce qu&apos;on veut afficher 2 &#xE9;l&#xE9;ment par ligne (list_to_rows(mylist, 2)) --&gt;
            {{ elem }}
            &lt;/div&gt;
        &lt;/article&gt;
        {% endfor %}
    &lt;/div&gt;
    {% endfor %}
&lt;/div&gt;
</code></pre>
<p>C&apos;est simple et efficace. Mais si on veut pouvoir filtrer dynamiquement<br>
sur cette liste c&#xF4;t&#xE9; client, &#xE7;a ne fonctionnera plus. Enlevez un &#xE9;l&#xE9;ment<br>
&#xE0; votre liste une fois le Jinja rendu et c&apos;est le dr&#xE2;me.</p>
<p>On peut pour cela faire confiance &#xE0; AngularJs. C&#xF4;t&#xE9; serveur, on envoie<br>
simplement notre liste. On ne s&apos;en occupe pas d&apos;avantage.</p>
<p>C&#xF4;t&#xE9; client en revenche, on va pouvoir controller la fa&#xE7;on donc elle<br>
sera affich&#xE9;e :</p>
<pre><code>&lt;div ng-app=&quot;myapp&quot; ng-controller=&quot;AppCtrl&quot;&gt;
    &lt;article ng-repeat=&quot;elmt in list&quot;&gt;
        &lt;span ng-switch=&quot;$index%3&quot;&gt;
            &lt;div class=&quot;row&quot; ng-switch-when=0&gt;
                {{ elmt.name }}
            &lt;/div&gt;
		&lt;/span&gt;
    &lt;/article&gt;
&lt;/div&gt;
</code></pre>
<p>Il faut quelques explications je pense. Tout d&apos;abord, on met notre<br>
affichage dans le scope de notre controller <code>AppCtrl</code></p>
<p>Ensuite, on boucle sur la liste avec la directive <code>ng-repeat</code>. On ne va<br>
s&apos;interesser qu&apos;aux &#xE9;l&#xE9;ments donc l&apos;index est divisble par 3 avec la<br>
directive <code>ng-switch=&quot;outerIndex%3&quot;</code> et <code>ng-switch-when=0</code>.</p>
<p>Bon, c&apos;est d&#xE9;j&#xE0; pas mal. Mais nous on veut tout afficher.</p>
<p>On va donc modifier un peu notre template Angular pour &#xE7;a.</p>
<pre><code>&lt;div ng-app=&quot;myapp&quot; ng-controller=&quot;AppCtrl&quot;&gt;
    &lt;article ng-repeat=&quot;_ in list&quot; ng-init=&quot;outerIndex=$index&quot;&gt;
        &lt;span ng-switch=&quot;outerIndex%3&quot;&gt;
            &lt;div class=&quot;row&quot; ng-switch-when=0&gt;
                &lt;div ng-repeat=&quot;elmt in list.slice(outerIndex, outerIndex+3) track by $index&quot;&gt;
                    &lt;div ng-if=&quot;elmt&quot;&gt;
                        &lt;div class=&quot;col s4 bordered&quot;&gt;
                            {{ elmt.name }}
                        &lt;/div&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/span&gt;
    &lt;/article&gt;
&lt;/div&gt;
</code></pre>
<p>On fait une deuxi&#xE8;me boucle dans la premi&#xE8;re. Cette boucle, on la fait<br>
sur un slice de la list originale, entre l&apos;index de l&apos;&#xE9;l&#xE9;ment d&#xE9;finit<br>
par le switch, et 3 index plus loin. En gros, pour le 4&#xE8;me &#xE9;l&#xE9;ment de la<br>
liste originale (index 3, divisible par 3 donc), on va faire un slice de<br>
la liste de l&apos;index 3 &#xE0; 6. Ce qui donnera les 4&#xE8;me, 5&#xE8;me et 6&#xE8;me<br>
&#xE9;l&#xE9;ments de la liste.</p>
<p>Ces &#xE9;l&#xE9;ments, on veut qu&apos;ils soient identifi&#xE9;s par leur <code>$index</code> dans la<br>
seconde boucle (AngularJs pleurniche si la liste contient des<br>
doublons...). On est donc oblig&#xE9; de stocker la variable d&apos;indexation de<br>
la premi&#xE8;re boucle dans une autre variable nomm&#xE9;e ici <code>outerIndex</code> et<br>
d&apos;utiliser <code>track by $index</code> dans la seconde.</p>
<p>Ensuite, si l&apos;&#xE9;l&#xE9;ment existe, on affiche l&apos;&#xE9;l&#xE9;ments dans un colonne de<br>
largeur 4 (puisqu&apos;on veut afficher 3 &#xE9;l&#xE9;ments par ligne dans cette<br>
exemple).</p>
<p>Voil&#xE0;, la premi&#xE8;re &#xE9;tape est faite. C&apos;&#xE9;tait le plus important. Relisez<br>
cette partie, faites des tests pour bien comprendre tout ce qu&apos;il se<br>
passe.</p>
<p>Je vous parlais de filtrage, ou tout du moins, de modifications dans la<br>
liste. L&apos;avantage d&apos;angular et de sa directive <code>ng-repeat</code>, c&apos;est qu&apos;il<br>
va modifier le DOM si vous modifier la liste &#xE0; partir duquel il est<br>
cr&#xE9;&#xE9;. Si vous retirez 3 &#xE9;l&#xE9;ments &#xE0; la liste, vous faites sauter une<br>
ligne.</p>
<p>Du coup, pourquoi ne pas utiliser de filtre sur cette affichage ? Bah<br>
pourquoi pas :</p>
<pre><code>&lt;div ng-app=&quot;myapp&quot; ng-controller=&quot;AppCtrl&quot;&gt;
    &lt;article ng-repeat=&quot;_ in (fList = (list | filter: greaterThan(&apos;id&apos;, 2)))&quot; ng-init=&quot;outerIndex=$index&quot;&gt; &lt;span ng-switch=&quot;outerIndex%3&quot;&gt;
        &lt;div class=&quot;row&quot; ng-switch-when=0&gt;
            &lt;div ng-repeat=&quot;elmt in fList.slice(outerIndex, outerIndex+3) track by $index&quot;&gt;
                &lt;div ng-if=&quot;elmt&quot;&gt;
                    &lt;div class=&quot;col s4 bordered&quot;&gt;
                        {{ elmt.name }}
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/span&gt;
    &lt;/article&gt;
&lt;/div&gt;
</code></pre>
<p>L&apos;astuce ici repose dans la premi&#xE8;re directive <code>ng-repeat</code> : on stocke<br>
le r&#xE9;sultat du filtre dans une variable <code>fList</code> pour l&apos;utiliser dans la<br>
seconde boucle. Et c&apos;est tout. Vous pouvez cha&#xEE;ner vos filtre, rendre le<br>
nombre de ligne dynamique, ... Il y a d&#xE9;j&#xE0; de quoi s&apos;amuser un petit<br>
moment !</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Créer une API versionnée avec Django Tastypie]]></title><description><![CDATA[Tastypie est très bien pour construire une API à partir de modèle
Django. Elle propose tout un tas d'authentification et les sources sont
bien écrites, de sorte que lorsqu'on à un besoin non répertorié dans la
doc, on peut quand même se débrouiller.]]></description><link>https://write.voiney.fr/creer-une-api-versionnee-avec-django-tastypie/</link><guid isPermaLink="false">5e664e5a75774000010e71d2</guid><category><![CDATA[api]]></category><dc:creator><![CDATA[Alex]]></dc:creator><pubDate>Thu, 19 Mar 2015 06:53:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Tastypie est tr&#xE8;s bien pour construire une API &#xE0; partir de mod&#xE8;le<br>
Django. Elle propose tout un tas d&apos;authentification et les sources sont<br>
bien &#xE9;crites, de sorte que lorsqu&apos;on &#xE0; un besoin non r&#xE9;pertori&#xE9; dans la<br>
doc, on peut quand m&#xEA;me se d&#xE9;brouiller.</p>
<p>Il y a quand m&#xEA;me un petit d&#xE9;faut : le versionnement. Alors peut &#xEA;tre<br>
que j&apos;ai loup&#xE9; quelque chose, mais je n&apos;ai rien vu qui permette de<br>
versionner correctement l&apos;Api construire avec cette lib. C&apos;est dommage<br>
parce que &#xE7;a partait bien : on peut en effet cr&#xE9;er plusieurs Api avec<br>
des noms (ou dans notre cas des versions) diff&#xE9;rentes, mais rien n&apos;est<br>
tr&#xE8;s clair dans la doc quant au comportement des resources.</p>
<p>Du coup, voici une solution que j&apos;utilise qui permet de maintenir<br>
plusieurs versions d&apos;Api, sans trop de probl&#xE8;mes :</p>
<p>On commence par l&apos;arborescence de notre application :</p>
<pre><code>- CookBook /
- cookbook /
    - __init__.py
    - settings.py
    - urls
    - wsgy.py
- manage.py
- recipes /
    - __init__.py
    - admin.py
    - migrations /
        __init__.py
    - models.py
    - tests.py
    - views.py
</code></pre>
<p>On va faire une b&#xEA;te application de snippets de code. Le fichier<br>
<code>recipes/models.py</code> ressemble &#xE0; &#xE7;a :</p>
<pre><code># -*- coding: utf8 -*-
from django.db import models

class Recipe(models.Model):
    &apos;&apos;&apos; &apos;&apos;&apos;
    snippet = models.TextField()
    name = models.CharField(max_length=255)
    language = models.CharField(max_length=255)
    edited = models.DateTimeField(auto_now=True)
    created = models.DateTimeField(auto_now_add=True)


class FileCheatSheet(models.Model):
    &apos;&apos;&apos; &apos;&apos;&apos;
    name = models.CharField(max_length=255)
    sheet = models.FileField(upload_to=&quot;cheat_sheets&quot;)
</code></pre>
<p>Voil&#xE0;, c&apos;est pas bien compliqu&#xE9;, mais c&apos;est pas le but. Je vous passe<br>
les &#xE9;tapes de migrations de DB et tout ce qui s&apos;en suit.</p>
<p>Bon, maintenant qu&apos;on a notre mod&#xE8;le, on va cr&#xE9;er la ressource pour<br>
l&apos;api. (Oubliez pas d&apos;ajouter <code>tastypie</code> dans le tuple <code>INSTALLED_APPS</code>)</p>
<p>On va commencer par cr&#xE9;er un module <code>api</code> dans l&apos;app <code>recipes</code>. Il<br>
contiendra un module <code>v1</code> qui lui m&#xEA;me contiendra un fichier<br>
<code>resources.py</code></p>
<pre><code>- CookBook /
- cookbook /
    - ...
- recipes /
    - ...
    - api /
        - __init__.py
        - v1 /
            - __init__.py
            - resources.py
- ...
</code></pre>
<p>Voil&#xE0;, on &#xE0; la base de notre api. L&apos;avantage de cette arborescence,<br>
c&apos;est que l&apos;on s&#xE9;pare bien les ressources pour chaque version.</p>
<p>Notre fichier de ressources pour la v1 pourrai ressembler &#xE0; &#xE7;a :</p>
<pre><code># api/v1/resources.py
# -*- coding: utf8 -*-
from tastypie.resources import ModelResource
from tastypie.authorization import Authorization
from recipes.models import Recipe, FileCheatSheet


class RecipeResource(ModelResource):
    &apos;&apos;&apos; The resource exposing the Recipe model &apos;&apos;&apos;

    class Meta:
        queryset = Recipe.objects.all()
        resource_name = &quot;recipes&quot;
        list_allowed_method = [&apos;get&apos;, &apos;post&apos;, &apos;put&apos;, &apos;delete&apos;, &apos;patch&apos;]
        detail_allowed_method = [&apos;get&apos;, &apos;post&apos;, &apos;put&apos;, &apos;delete&apos;, &apos;patch&apos;]
        authorization = Authorization()


class FileCheatSheetResource(ModelResource):
    &apos;&apos;&apos; The resource exposing the FileCheatSheet model &apos;&apos;&apos;

    class Meta:
        queryset = FileCheatSheet.objects.all()
        resource_name = &quot;cheatsheets&quot;
        list_allowed_method = [&apos;get&apos;, &apos;post&apos;, &apos;put&apos;, &apos;delete&apos;, &apos;patch&apos;]
        detail_allowed_method = [&apos;get&apos;, &apos;post&apos;, &apos;put&apos;, &apos;delete&apos;, &apos;patch&apos;]
        authorization = Authorization()

def deserialize(self, request, data, format=None):
    format = format.lower()
    if format is None:
        format = request.META.get(&apos;CONTENT_TYPE&apos;, &apos;application/json&apos;)
    if format == &apos;application/x-www-form-urlencoded&apos;:
        return request.POST
    elif format.startswith(&apos;multipart&apos;):
        data = request.POST.copy()
        data.update(request.FILES)
        return data
    return super(FileCheatSheetResource, self).deserialize(request, data, format)



# api/v1/__init__.py
# -*- coding: utf8 -*-
from tastypie.api import Api
from recipes.api.v1.resources import RecipeResource, FileCheatSheetResource

# define v1 api
api = Api(api_name=&quot;v1&quot;)
# register urls
api.register(RecipeResource())
api.register(FileCheatSheetResource())
</code></pre>
<p>Ici, il y a une petite subtilit&#xE9; : comme on va uploader un fichier via<br>
l&apos;api (pour l&apos;attribut <code>sheet</code>), il faut d&#xE9;finir un d&#xE9;s&#xE9;rialiseur pour<br>
le Content-Type <code>multipart/form-data</code>. Il faut simplement ajouter les<br>
donn&#xE9;es contenu dans le <code>QueryDict request.FILES</code> &#xE0; <code>request.POST</code> (on<br>
est oblig&#xE9; de la copier dans une autre variable puisque c&apos;est un objet<br>
immuable).</p>
<p>Dans api/v1/<strong>init</strong>.py, on va enregistrer nos ressources pour qu&apos;elles<br>
soit expos&#xE9;es :</p>
<pre><code># api/v1/__init__.py
# -*- coding: utf8 -*-
from tastypie.api import Api
from recipes.api.v1.resources import RecipeResource, FileCheatSheetResource

# define v1 api
api = Api(api_name=&quot;v1&quot;)
# register urls
api.register(RecipeResource())
api.register(FileCheatSheetResource())
</code></pre>
<p>On a plus qu&apos;&#xE0; injecter les urls de l&apos;api dans nos urls du projet :</p>
<pre><code># cookbook/urls.py
# -*- coding: utf8 -*-
...
from recipes.api.v1 import api as api_v1
...

app_patterns = patterns(&apos;&apos;,
    ...
    url(r&apos;^api/&apos;, include(api_v1.urls)),
    ...
)
</code></pre>
<p>Voil&#xE0;, notre api est fonctionnelle. J&apos;ai volontairement pass&#xE9; sous<br>
silence la partie authentification et autorisation, ce n&apos;est pas l&apos;objet<br>
ici. On utilise ici la classe <code>Authorization</code> qui autorise tout et<br>
n&apos;importe quoi du moment que les verbes sont disponible dans<br>
<code>list_allowed_method</code> et <code>detail_allowed_method</code>. A bien s&#xFB;r ne pas<br>
faire en prod...</p>
<p>Bon c&apos;est pas mal &#xE7;a, mais maintenant, j&apos;aimerai bien pouvoir ajouter un<br>
nouveau snippet depuis l&apos;api en lui envoyant un ficher, plut&#xF4;t que du<br>
contenu brut, parce qu&apos;avec un CURL, c&apos;est plus pratique. Eh bien c&apos;est<br>
maintenant qu&apos;il va falloir faire une v2.</p>
<p>Du coup on va cr&#xE9;er un nouveau module v2 dans notre arborescence :</p>
<pre><code>- CookBook /
- cookbook /
    - ...
- recipes /
    - ...
    - api /
        - __init__.py
        - v1 /
            - ...
        - v2 /
            - __init__.py
            - resources.py
- ...
</code></pre>
<p>Maintenant on va d&#xE9;finir nos ressources, avec les changements. Mais on<br>
n&apos;a peut &#xEA;tre pas envie de dupliquer le code de <code>FileCheatSheetResource</code><br>
vu qu&apos;on ne va pas le changer. La documentation de Tastypie n&apos;apportant<br>
aucune solution, voici celle que j&apos;ai choisi :</p>
<pre><code># api/v2/resources.py
# -*- coding: utf8 -*-
from tastypie.resources import ModelResource
from tastypie.authorization import Authorization
from tastypie import fields
from recipes.models import Recipe
from recipes.api.v1.resources import FileCheatSheetResource as V1FileCheatSheetResource


class RecipeResource(ModelResource):
    &apos;&apos;&apos; The resource exposing the Recipe model &apos;&apos;&apos;
    snippet_raw = fields.CharField(null=True, blank=True)
    snippet_file = fields.FileField(null=True, blank=True)

    class Meta:
        queryset = Recipe.objects.all()
        resource_name = &quot;recipes&quot;
        list_allowed_method = [&apos;get&apos;, &apos;post&apos;, &apos;put&apos;, &apos;delete&apos;, &apos;patch&apos;]
        detail_allowed_method = [&apos;get&apos;, &apos;post&apos;, &apos;put&apos;, &apos;delete&apos;, &apos;patch&apos;]
        authorization = Authorization()
        excludes = (&apos;snippet&apos;,)

def deserialize(self, request, data, format=None):
    format = format.lower()
    if format is None:
        format = request.META.get(&apos;CONTENT_TYPE&apos;, &apos;application/json&apos;)
    if format == &apos;application/x-www-form-urlencoded&apos;:
        return request.POST
    elif format.startswith(&apos;multipart&apos;):
        data = request.POST.copy()
        data[&apos;snippet&apos;] = request.POST.get(&apos;snippet_raw&apos;, None)
        if &quot;snippet_file&quot; in request.FILES:
            data[&apos;snippet&apos;] = request.FILES[&apos;snipper_file&apos;].read()
        return data
    return super(RecipeResource, self).deserialize(request, data, format)

def save(self, bundle, skip_errors=False):
    &apos;&apos;&apos; Override the save method to create the snippet object
        from file or raw data
    &apos;&apos;&apos;
    snippet_buffer = bundle.data[&apos;snippet&apos;]
    # create the object
    obj = Recipe(snippet=snippet_buffer)
    # switch objects
    bundle.obj = obj
    return super(RecipeResource, self).save(bundle, skip_errors)

def dehydrate(self, bundle):
    &apos;&apos;&apos; Override the dehydrate methode to re-add the snippet attribute
        and remove snipper_raw and snipper_file ones
    &apos;&apos;&apos;
    bundle = super(RecipeResource, self).dehydrate(bundle)
    bundle.data.pop(&apos;snippet_raw&apos;)
    bundle.data.pop(&apos;snippet_file&apos;)
    bundle.data[&apos;snippet&apos;] = bundle.obj.snippet
    return bundle


class FileCheatSheetResource(V1FileCheatSheetResource):
    &apos;&apos;&apos; Nothing to change for this resource in v2 &apos;&apos;&apos;
    pass
</code></pre>
<p>Quelques explications sont n&#xE9;cessaires. Dans cette version de la<br>
ressource pour les <code>Recipe</code>, on va d&#xE9;finir nous m&#xEA;me les champs que l&apos;on<br>
veut pouvoir populer via l&apos;api. Comme on d&#xE9;finit un FileField, on est<br>
oblig&#xE9; de remettre un d&#xE9;s&#xE9;rialiseur pour r&#xE9;cup&#xE9;rer les donn&#xE9;es d&apos;un<br>
formulaire multipart.</p>
<p>Ici on ne se contente pas de copier les donn&#xE9;es de <code>requests.FILES</code> dans<br>
ce que retourne le d&#xE9;s&#xE9;rialiseur. On va renvoyer le contenu du fichier<br>
que l&apos;on a envoy&#xE9; (si c&apos;est le cas) ou bien le contenu brut.</p>
<p>La m&#xE9;thode <code>save</code> est l&#xE0; pour cr&#xE9;er l&apos;objet. On r&#xE9;cup&#xE8;re la valeur<br>
<code>snippet_raw</code> et on y va.</p>
<p>Quant &#xE0; elle, la m&#xE9;thode <code>dehydrate</code> est l&#xE0; pour nettoyer un peu les<br>
donn&#xE9;es qu&apos;on expose. En effet, comme on demande &#xE0; envoyer <code>wnippet_raw</code><br>
ou <code>snippet_file</code>, et qu&apos;on &#xE0; exclu <code>snippet</code>, les premiers seront<br>
afficher tandis que le dernier ne le sera pas. On utilise dehydrate pour<br>
remettre de l&apos;ordre dans tout &#xE7;a.</p>
<p>Voil&#xE0;, on a plus qu&apos;&#xE0; enregistrer nos ressources pour les exposer.</p>
<pre><code># api/v2/__init__.py
# -*- coding: utf8 -*-
from tastypie.api import Api
from recipes.api.v2.resources import RecipeResource, FileCheatSheetResource

# define v2 api
api = Api(api_name=&quot;v2&quot;)
# register urls
api.register(RecipeResource())
api.register(FileCheatSheetResource())
</code></pre>
<p>L&apos;inconv&#xE9;nient, de cette mani&#xE8;re de faire, c&apos;est que l&apos;ont est <strong>oblig&#xE9;<br>
d&apos;importer les ressources inchang&#xE9;es de la v1</strong> pour en cr&#xE9;er une<br>
nouvelle, &quot;vide&quot;.</p>
<p>En contrepartie, l&apos;attribut <code>resource_uri</code> des objets conserve la<br>
version de l&apos;api utilis&#xE9;e pour faire l&apos;appel. C&apos;est une question<br>
d&apos;harmonie et &#xE7;a &#xE9;vite de perdre les utilisateurs.</p>
<p>Il y a n&#xE9;anmoins une autre fa&#xE7;on de proc&#xE9;der :</p>
<pre><code># api/v2/resources.py
# -*- coding: utf8 -*-
from tastypie.resources import ModelResource
from tastypie.authorization import Authorization
from tastypie import fields
from recipes.models import Recipe


class RecipeResource(ModelResource):
    &apos;&apos;&apos; The resource exposing the Recipe model &apos;&apos;&apos;
    ...

# cette fois, on ne surcharge pas le FileCheatSheetResource de la v1



# api/v1/__init__.py
# -*- coding: utf8 -*-
from tastypie.api import Api
from recipes.api.v1.resources import RecipeResource, FileCheatSheetResource

# define v1 api
api = Api(api_name=&quot;v1&quot;)
# register urls
api.register(RecipeResource())
api.register(FileCheatSheetResource())

# api/v2/__init__.py
# -*- coding: utf8 -*-
from tastypie.api import Api
from recipes.api.v2.resources import RecipeResource
from recipes.api.v1.resources import FileCheatSheetResource

# define v2 api
api = Api(api_name=&quot;v2&quot;)
# register urls
api.register(RecipeResource())
api.register(FileCheatSheetResource())
</code></pre>
<p>L&apos;avantage de cette fa&#xE7;on de proc&#xE9;der, c&apos;est qu&apos;on ne <strong>duplique pas de<br>
code</strong>. De plus, le changelog de la v2 est tout fait, puisque qu&apos;il<br>
suffit de regarder le fichier <code>api/v2/resources.py</code> pour voir ce qui a<br>
chang&#xE9; par rapport &#xE0; la v1.</p>
<p>En revanche, lorsque l&apos;on va interroger l&apos;api sur la v1, les uri des<br>
objects qui sont identiques en v1 et en v2 seront renvoy&#xE9;s avec la v2.<br>
Ca peut &#xEA;tre perturbant pour les utilisateurs non-avertis. Ils risquent<br>
de se demander pourquoi ont lui renvoie l&apos;uri en v2 alors qu&apos;il parle &#xE0;<br>
la v1.</p>
<p>En bref, si votre Api ne contient pas beaucoup de ressources, &#xE7;a peut<br>
valoir le coup d&apos;utiliser la premi&#xE8;re version. Si elle en contient<br>
&#xE9;norm&#xE9;ment, &#xE7;a va vite devenir p&#xE9;nible. A vous de voir &#xE0; ce moment l&#xE0; si<br>
la coh&#xE9;rence des uri est importante o&#xF9; pas.</p>
<p>Rappelons tout de m&#xEA;me que, puisque <strong>l&apos;objet n&apos;est pas alt&#xE9;r&#xE9;</strong> entre<br>
les deux versions, &#xE7;a ne cassera absoluement rien !</p>
<p>&#xC0; vous !</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Envoyer un signal lors d'une BasicAuthentification avec Django]]></title><description><![CDATA[Déclancher un signal d'authentifaction avec Django Tastypie]]></description><link>https://write.voiney.fr/envoyer-un-signal-lors-dune-basicauthentification-avec-django/</link><guid isPermaLink="false">5e664e5a75774000010e71d1</guid><category><![CDATA[api]]></category><dc:creator><![CDATA[Alex]]></dc:creator><pubDate>Wed, 18 Mar 2015 14:02:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Tastypie propose plusieurs m&#xE9;thodes d&apos;authentification<br>
(<code>ApiKeyAuthentication</code>, <code>BasicAuthentification</code>,<br>
<code>OAuthAuthentication</code>). Malheureusement,</p>
<p>elles ne d&#xE9;clenchent pas de signaux lors du succ&#xE8;s de l&apos;op&#xE9;ration. On va<br>
voir comment faire notre propre classe d&apos;authentification pour envoyer<br>
un signal lorsque l&apos;utilisateur r&#xE9;ussi &#xE0; se connecter.</p>
<p>D&apos;apr&#xE8;s <a href="https://django-tastypie.readthedocs.org/en/latest/authentication.html">la doc de<br>
Tastypie</a>,<br>
on peut impl&#xE9;menter notre authentification de cette fa&#xE7;on :</p>
<pre><code>from tastypie.authentication import Authentication

class SillyAuthentication(Authentication):
	def is_authenticated(self, request, **kwargs):
		if &apos;daniel&apos; in request.user.username:
			return True
		return False

# Optional but recommended
def get_identifier(self, request):
	return request.user.username
</code></pre>
<p>Du coup, on a juste &#xE0; r&#xE9;cup&#xE9;rer le r&#xE9;sultat de l&apos;authentification et<br>
envoyer le signal si on a <code>True</code>.</p>
<pre><code>from tastypie.authentication import BasicAuthentication
from django.contrib.auth.signals import user_logged_in

class SignaledBasicAuthentication(BasicAuthentication):
	&quot;&quot;&quot; Send a signal when finish BasicAuthentication &quot;&quot;&quot;
	def is_authenticated(self, request, **kwargs):
		authorized = super(SignaledBasicAuthentication, self).is_authenticated(request, **kwargs)
		if authorized is True:
			user_logged_in.send(sender=request.user.__class__, request=request, user=request.user)
		return authorized
</code></pre>
<p>Dans l&apos;exemple ci-dessus, on surcharge une BasicAuthentication, mais<br>
toutes les classes d&apos;authentification de tastypie sont sur le m&#xEA;me<br>
sch&#xE9;ma.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Profiler une fonction facilement]]></title><description><![CDATA[Lorsque l'on cherche à optimiser son code, on a souvent besoin de
    trouver quels blocs d'instructions posent problème. Le plus souvent,
    on va chercher à trouver les morceaux de code qui prennent le plus
    de temps à s'executer. Voyons quelles solutions s'offrent à nous.]]></description><link>https://write.voiney.fr/profiler-une-fonction-facilement/</link><guid isPermaLink="false">5e664e5a75774000010e71d7</guid><category><![CDATA[python]]></category><dc:creator><![CDATA[Alex]]></dc:creator><pubDate>Fri, 06 Feb 2015 06:50:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Lorsque l&apos;on cherche &#xE0; optimiser son code, on a souvent besoin de<br>
trouver quels blocs d&apos;instructions posent probl&#xE8;me. Le plus souvent, on<br>
va chercher &#xE0; trouver les morceaux de code qui prennent le plus de temps<br>
&#xE0; s&apos;executer. Voyons quelles solutions s&apos;offrent &#xE0; nous.</p>
<p>Dans un premier temps, et de mani&#xE8;re tout &#xE0; fait intuitive, on peut<br>
printer des <code>time</code>{.sourceCode} avant et apr&#xE8;s le bloc d&apos;instruction &#xE0;<br>
bencher :</p>
<pre><code class="language-{.sourceCode">import time

t = time.time()
# mes instructions ...
print time.time() - t
</code></pre>
<p>On aura le r&#xE9;sultat en secondes. Ca peut suffire, mais pas toujours.<br>
Lorsque les instructions font appel &#xE0; des m&#xE9;thodes de biblioth&#xE8;ques<br>
tierces, on ne va pas s&apos;amuser &#xE0; mettre des print dans les sources de<br>
l&#xE0;-dite biblioth&#xE8;que. Il existe un profileur dans la lib standart de<br>
python qui est bien pratique, j&apos;ai nomm&#xE9;<br>
<a href="https://docs.python.org/2/library/profile.html">cProfile</a>. Attention<br>
cependant, il va au bout des choses. Si votre fonction est grosse, cela<br>
risque de devenir vite illisible.</p>
<p>L&apos;utilisation est relativement simple. Essentiellement 2 cas de figures<br>
:</p>
<ul>
<li>Soit vous voulez un benchmark d&apos;une fonction de la lib standart et<br>
vous utilisez <code>cProfile.runc(&quot;&lt;mon appel&gt;&quot;)</code>{.sourceCode}</li>
<li>Soit vous voulez un benchmark d&apos;un truc bien &#xE0; vous :<br>
<code>cProfile.runctx(&quot;&lt;mon appel&gt;&quot;, None, locals())</code>{.sourceCode}</li>
</ul>
<p>Si la seconde solution vous revient en pleine face avec une<br>
<code>NameError</code>{.sourceCode}, remplacez <code>None</code>{.sourceCode} par<br>
<code>globals()</code>{.sourceCode}. Attention cependant, le dict<br>
<code>globals()</code>{.sourceCode} est tr&#xE8;s consistant.</p>
<p>Du coup on aurait par exemple &#xE7;a :</p>
<pre><code class="language-{.sourceCode">import cProfile

def my_func(arg1, arg2):
    print(arg1, arg2)

cProfile.runctx(&quot;my_func(&apos;Mulder&apos;, &apos;Scully&apos;)&quot;, None, locals()
# ou, si locals() est pas suffisant,
# cProfile.runctx(&quot;my_func(&apos;Mulder&apos;, &apos;Scully&apos;)&quot;, globals(), locals())
Mulder Scully
         5 function calls in 0.000 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.000    0.000 &lt;stdin&gt;:1(my_func)
        1    0.000    0.000    0.000    0.000 &lt;string&gt;:1(&lt;module&gt;)
        1    0.000    0.000    0.000    0.000 {built-in method exec}
        1    0.000    0.000    0.000    0.000 {built-in method print}
        1    0.000    0.000    0.000    0.000 {method &apos;disable&apos; of &apos;_lsprof.Profiler&apos; objects}
</code></pre>
<p>Ici on vois que notre fonction prend un temps tellement n&#xE9;gligeable que<br>
cProfile le consid&#xE8;re comme nul.</p>
<p>Ce qui est surtout int&#xE9;ressant de noter, c&apos;est que cProfile affiche le<br>
temps d&apos;ex&#xE9;cution, ainsi que le nombre d&apos;ex&#xE9;cutions des fonctions<br>
built-in appel&#xE9;es. C&apos;est &#xE7;a qui nous int&#xE9;resse lorsque l&apos;on veut un<br>
benchmark d&apos;une fonction &#xE0; nous utilisant une biblioth&#xE8;que tierce.</p>
<p>Bon, &#xE7;a, c&apos;&#xE9;tait pour l&apos;utilisation dans un shell Python. Vous pouvez<br>
aussi l&apos;utiliser dans vos sources, mais &#xE7;a va vite devenir p&#xE9;nible si<br>
votre fonction prend pl&#xE9;thore d&apos;arguments. Du coup, voil&#xE0; un petit<br>
d&#xE9;corateur pour vous faciliter le profilage.</p>
<pre><code class="language-{.sourceCode">import cProfile

def profiler(sort=-1, use_globals=False, use_locals=True):
    def decr(func):
        def wrp(*args, **kwargs):
            g = globals() if use_globals else None
            l = locals() if use_locals else None
            cProfile.runctx(&quot;func(*args, **kwargs)&quot;, g, l, sort=sort)
            return func(*args, **kwargs)
        return wrp
    return decr
</code></pre>
<p>Rien de bien magique ici, il est sous la forme d&apos;un g&#xE9;n&#xE9;rateur de<br>
d&#xE9;corateur, pour pouvoir lui passer l&apos;index par lequel cProfile va trier<br>
les r&#xE9;sultats. Si vous voulez en savoir plus sur les d&#xE9;corateurs, un<br>
petit tour sur le <a href="http://sametmax.com/comprendre-les-decorateurs-python-pas-a-pas-partie-1/">formidable article de<br>
Sam&amp;Max</a><br>
vous fera le plus grand bien !</p>
<p>&#xC0; vous de jouer !</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Convertir une "Generalized Time" string en datetime]]></title><description><![CDATA[En ce moment, je bosse beaucoup avec un Active Directory, logiciel
Microsoft utilisant Ldap. Si on met de côté les erreurs abstraites
incompréhensible, l'obligation d'envoyer des string sur la plupart des
champs (l'unicode serait quand même vachement plus cool) et j'en passe,
c'est sympa.]]></description><link>https://write.voiney.fr/convertir-une-generalized-time-string-en-datetime/</link><guid isPermaLink="false">5e664e5a75774000010e71d3</guid><category><![CDATA[python]]></category><dc:creator><![CDATA[Alex]]></dc:creator><pubDate>Wed, 04 Feb 2015 08:38:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>En ce moment, je bosse beaucoup avec un Active Directory, logiciel<br>
Microsoft utilisant Ldap. Si on met de c&#xF4;t&#xE9; les erreurs abstraites<br>
incompr&#xE9;hensible, l&apos;obligation d&apos;envoyer des string sur la plupart des<br>
champs (l&apos;unicode serait quand m&#xEA;me vachement plus cool) et j&apos;en passe,<br>
c&apos;est sympa.</p>
<p>La plupart des champs temporels de l&apos;AD utilise le type &quot;Long Int&quot;, et<br>
contrairement &#xE0; ce qu&apos;on pourrait croire, ce n&apos;est pas un <strong>timestamp</strong>.<br>
C&apos;est un <strong>filetime</strong>. Le filetime est la <em>repr&#xE9;sentation du nombre<br>
d&apos;intervals de 100 nano-secondes depuis le 1er Janvier 1601 (UTC)</em>.<br>
Pourquoi faire simple quand on peut faire &#xE7;a.</p>
<p>Passe encore, j&apos;ai trouv&#xE9; un petit module qui permet de convertir tout<br>
&#xE7;a chez <a href="http://reliablybroken.com/b/2009/09/working-with-active-directory-filetime-values-in-python/">Reliably<br>
Broken</a>.</p>
<p>Mais comme le disent nos chers conseillers SFR, &quot;Et c&apos;est pas fini !&quot; :<br>
non content d&apos;utiliser une repr&#xE9;sentation de datetime exotique, ils en<br>
utilisent une autre : le GeneralizedTime String.</p>
<p>C&apos;est une cha&#xEE;ne de caract&#xE8;res qui concat&#xE8;ne l&apos;ann&#xE9;e (sur 4 chiffres),<br>
le mois (sur 2), le jour (sur 2), l&apos;heure (sur 2) et optionnelement les<br>
minutes, les secondes, les microsecondes (sur 3 chiffres) et un<br>
indicateur pour l&apos;offset heures/minutes par rapport &#xE0; l&apos;UTC, l&apos;UTC, ou<br>
rien du tout. Plus d&apos;info sur<br>
l&apos;<a href="http://www.obj-sys.com/asn1tutorial/node14.html">ASN.1</a></p>
<p>Concr&#xE8;tement, &#xE7;a ressemble &#xE0; &#xE7;a :</p>
<pre><code>20150131143554.230          # datetime(2015, 01, 31, 14, 35, 554, 230)
20150131143554.230Z         # datetime(2015, 01, 31, 14, 35, 554, 230, tzinfo=&lt;UTC&gt;)
20150131143554.230+0300     # datetime(2015, 01, 31, 11, 35, 554, 230, tzinfo=&lt;UTC&gt;)
</code></pre>
<p>C&apos;est relativement simple &#xE0; parser, mais n&apos;ayant rien trouv&#xE9; pour<br>
convertir &#xE7;a en datetime sur le grand internet mondial, voici une petite<br>
tool function pour le faire.</p>
<pre><code>&quot;&quot;&quot; Tool function to convert Generalized Time string
    to Python datetime object
&quot;&quot;&quot;

import datetime
import pytz

def gt_to_dt(gt):
    &quot;&quot;&quot; Convert GeneralizedTime string to python datetime object

        &gt;&gt;&gt; gt_to_dt(&quot;20150131143554.230&quot;)
        datetime.datetime(2015, 1, 31, 14, 35, 54, 230)
        &gt;&gt;&gt; gt_to_dt(&quot;20150131143554.230Z&quot;)
        datetime.datetime(2015, 1, 31, 14, 35, 54, 230, tzinfo=&lt;UTC&gt;)
        &gt;&gt;&gt; gt_to_dt(&quot;20150131143554.230+0300&quot;)
        datetime.datetime(2015, 1, 31, 11, 35, 54, 230, tzinfo=&lt;UTC&gt;)
    &quot;&quot;&quot;
    # check UTC and offset from local time
    utc = False
    if &quot;Z&quot; in gt.upper():
        utc = True
        gt = gt[:-1]
    if gt[-5] in [&apos;+&apos;, &apos;-&apos;]:
        # offsets are given from local time to UTC, so substract the offset to get UTC time
        hour_offset, min_offset = -int(gt[-5] + gt[-4:-2]), -int(gt[-5] + gt[-2:])
        utc = True
        gt = gt[:-5]
    else:
        hour_offset, min_offset = 0, 0

    # microseconds are optionnals
    if &quot;.&quot; in gt:
        microsecond = int(gt[gt.index(&apos;.&apos;) + 1:])
        gt = gt[:gt.index(&apos;.&apos;)]
    else:
        microsecond = 0

    # seconds and minutes are optionnals too
    if len(gt) == 14:
        year, month, day, hours, minutes, sec = int(gt[:4]), int(gt[4:6]), int(gt[6:8]), int(gt[8:10]), int(gt[10:12]), int(gt[12:])
        hours += hour_offset
        minutes += min_offset
    elif len(gt) == 12:
        year, month, day, hours, minutes, sec = int(gt[:4]), int(gt[4:6]), int(gt[6:8]), int(gt[8:10]), int(gt[10:]), 0
        hours += hour_offset
        minutes += min_offset
    elif len(gt) == 10:
        year, month, day, hours, minutes, sec = int(gt[:4]), int(gt[4:6]), int(gt[6:8]), int(gt[8:]), 0, 0
        hours += hour_offset
        minutes += min_offset
    else:
        # can&apos;t be a generalized time
        raise ValueError(&apos;This is not a generalized time string&apos;)

    # construct aware or naive datetime
    if utc:
        dt = datetime.datetime(year, month, day, hours, minutes, sec, microsecond, tzinfo=pytz.UTC)
    else:
        dt = datetime.datetime(year, month, day, hours, minutes, sec, microsecond)
    # done !
    return dt</code></pre>
<!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>